|
| 1 | +# AI SDK Contract Standardization: Phase 1 Foundation |
| 2 | + |
| 3 | +> **Status:** Implemented on branch `codex/ai-sdk-contract-phase-1` and published in PR [#171](https://github.com/NickB03/polymorph/pull/171). |
| 4 | +
|
| 5 | +**Goal:** Establish AI SDK-native contracts as the primary chat/runtime boundary without breaking the current product surfaces for research UX, canvas, image generation, citations, guest chat, or authenticated chat. |
| 6 | + |
| 7 | +## Original Phase 1 Intent |
| 8 | + |
| 9 | +Phase 1 was intentionally foundation-first: |
| 10 | + |
| 11 | +- introduce an agent-owned contract surface for chat tools and UI messages |
| 12 | +- treat validated AI SDK `UIMessage` envelopes as the canonical transport and persistence shape |
| 13 | +- replace bespoke continuation and rendering glue where AI SDK-native flows now exist |
| 14 | +- preserve product-specific behavior that still differentiates the app: |
| 15 | + - research mode UX |
| 16 | + - citations / link previews |
| 17 | + - canvas artifacts and progress events |
| 18 | + - image generation |
| 19 | + |
| 20 | +This phase was not intended to finish the full standardization. It was intended to create the contract boundary that phase 2 can build on. |
| 21 | + |
| 22 | +## Verified Implemented Scope |
| 23 | + |
| 24 | +### 1. Shared chat-agent contract landed |
| 25 | + |
| 26 | +The repo now has a dedicated `lib/agents/chat/` surface: |
| 27 | + |
| 28 | +- [contract.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/lib/agents/chat/contract.ts) |
| 29 | +- [toolset.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/lib/agents/chat/toolset.ts) |
| 30 | +- [message-contract.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/lib/agents/chat/message-contract.ts) |
| 31 | +- [ui-types.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/lib/agents/chat/ui-types.ts) |
| 32 | +- [specialists.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/lib/agents/chat/specialists.ts) |
| 33 | + |
| 34 | +What this changed: |
| 35 | + |
| 36 | +- `ChatAgentTools` is now the shared tool contract instead of `researcher` being the implicit global owner. |
| 37 | +- `createChatValidationContract()` validates incoming UI messages against a shared metadata + data-part schema. |
| 38 | +- `ChatAgentUIMessage` / `ChatAgentUITools` are now inferred from the shared contract. |
| 39 | +- a specialist fixture and registry surface now exist so the next phase can add a live specialist without another contract rewrite. |
| 40 | + |
| 41 | +### 2. Researcher now composes from the shared contract |
| 42 | + |
| 43 | +[researcher.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/lib/agents/researcher.ts) no longer defines the global tool contract itself. |
| 44 | + |
| 45 | +What remains bespoke in `researcher.ts` after phase 1: |
| 46 | + |
| 47 | +- search-mode prompt selection |
| 48 | +- request-local search pacing |
| 49 | +- mode-specific active tool lists |
| 50 | +- eval-mode exclusion of interactive tools |
| 51 | + |
| 52 | +That is acceptable for phase 1. It keeps runtime behavior intact while moving the type and validation boundary into `lib/agents/chat/`. |
| 53 | + |
| 54 | +### 3. Canonical `UIMessage` persistence landed |
| 55 | + |
| 56 | +The `messages` table now stores the canonical message envelope directly: |
| 57 | + |
| 58 | +- [schema.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/lib/db/schema.ts) |
| 59 | +- [0020_chat_ui_message_contract.sql](/Users/nick/.codex/worktrees/3a35/vana-v2/drizzle/0020_chat_ui_message_contract.sql) |
| 60 | +- [message-mapping.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/lib/utils/message-mapping.ts) |
| 61 | + |
| 62 | +What changed: |
| 63 | + |
| 64 | +- `messages.ui_message` was added as `jsonb` |
| 65 | +- DB writes now persist the full canonical `UIMessage` |
| 66 | +- DB reads now prefer `ui_message` as the source of truth and only fall back to legacy part reconstruction when necessary |
| 67 | + |
| 68 | +This is the most important structural change in phase 1. New features no longer need to depend on legacy per-part reconstruction as the primary contract. |
| 69 | + |
| 70 | +### 4. Transport and continuation moved closer to AI SDK-native flows |
| 71 | + |
| 72 | +The chat transport now prefers canonical `messages` payloads: |
| 73 | + |
| 74 | +- [route.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/app/api/chat/route.ts) |
| 75 | +- [chat-request.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/components/chat-request.ts) |
| 76 | +- [prepare-messages.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/lib/streaming/helpers/prepare-messages.ts) |
| 77 | +- [create-chat-stream-response.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/lib/streaming/create-chat-stream-response.ts) |
| 78 | +- [create-ephemeral-chat-stream-response.ts](/Users/nick/.codex/worktrees/3a35/vana-v2/lib/streaming/create-ephemeral-chat-stream-response.ts) |
| 79 | + |
| 80 | +What changed: |
| 81 | + |
| 82 | +- request normalization now prefers `messages` |
| 83 | +- the legacy single `message` shape remains only as a compatibility fallback when `messages` is absent |
| 84 | +- guest and authenticated paths both validate normalized messages before model conversion |
| 85 | +- continuation now flows through validated message state instead of the earlier custom rewrite path being the default |
| 86 | + |
| 87 | +### 5. Tool rendering was reduced to a registry seam |
| 88 | + |
| 89 | +Rendering logic moved out of the global message renderer into a dedicated tool-part helper: |
| 90 | + |
| 91 | +- [tool-part-registry.tsx](/Users/nick/.codex/worktrees/3a35/vana-v2/components/tool-ui/tool-part-registry.tsx) |
| 92 | +- [render-message.tsx](/Users/nick/.codex/worktrees/3a35/vana-v2/components/render-message.tsx) |
| 93 | + |
| 94 | +What changed: |
| 95 | + |
| 96 | +- `displayOptionList` and `displayQuestionWizard` interactive flows now resolve through the tool-part registry seam |
| 97 | +- research-mode suppression for citations / link previews is still preserved |
| 98 | +- generic tool output rendering remains available through the existing Tool UI registry |
| 99 | + |
| 100 | +This is still not the final per-tool folder model, but it is no longer a large inline special-case block inside `render-message.tsx`. |
| 101 | + |
| 102 | +### 6. Client auto-continuation now uses AI SDK-native completion checks |
| 103 | + |
| 104 | +[chat.tsx](/Users/nick/.codex/worktrees/3a35/vana-v2/components/chat.tsx) now uses: |
| 105 | + |
| 106 | +- `lastAssistantMessageIsCompleteWithToolCalls` |
| 107 | +- `addToolOutput` |
| 108 | + |
| 109 | +This replaced the previous custom “fire once, rewrite request, continue” path as the normal continuation mechanism for interactive tools. |
| 110 | + |
| 111 | +## Acceptance Criteria Status |
| 112 | + |
| 113 | +- [x] A shared chat-agent contract exists and owns tool typing plus UI message validation. |
| 114 | +- [x] Canonical `UIMessage` storage exists and is preferred on round-trip reads. |
| 115 | +- [x] Interactive tool continuations now follow AI SDK-native completion flow in the client. |
| 116 | +- [x] Rendering is thinner than before and has a dedicated tool-part registry seam. |
| 117 | +- [x] A specialist surface exists and includes one proof fixture. |
| 118 | +- [x] Existing search, fetch, citations, guest/auth chat, canvas, and image-generation behavior still has an integration path after the migration. |
| 119 | +- [ ] A new community pattern can be dropped in with only local adapters. |
| 120 | + - Not proven yet. Phase 2 must ship the first real portability proof. |
| 121 | +- [ ] All remaining runtime ownership is agent-local. |
| 122 | + - Not finished yet. `app/api/chat/route.ts`, `create-chat-stream-response.ts`, and `researcher.ts` still coordinate too much. |
| 123 | + |
| 124 | +## Validation Run |
| 125 | + |
| 126 | +The phase 1 branch was verified with: |
| 127 | + |
| 128 | +- `bun run typecheck` |
| 129 | +- `bun run test -- app/api/chat/__tests__/route.test.ts lib/streaming/helpers/__tests__/prepare-messages.test.ts lib/streaming/helpers/__tests__/prepare-tool-result-messages.test.ts components/chat.test.tsx components/chat-request.test.ts lib/actions/__tests__/chat.test.ts lib/utils/__tests__/message-mapping-ui-message.test.ts lib/agents/chat/__tests__/specialists.test.ts` |
| 130 | + |
| 131 | +## Known Gaps And Explicit Non-Goals |
| 132 | + |
| 133 | +Phase 1 intentionally did **not** complete the following: |
| 134 | + |
| 135 | +- split the remaining runtime into explicit agent-owned server wrappers |
| 136 | +- convert the current flat `lib/tools/*` surface into per-tool contracts |
| 137 | +- ship a live specialist with real orchestration and renderer support |
| 138 | +- prove community portability by porting an external AI SDK pattern end to end |
| 139 | +- remove legacy compatibility paths for older stored messages |
| 140 | + |
| 141 | +Those are phase 2 concerns, not phase 1 defects. |
| 142 | + |
| 143 | +## Phase 2 Handoff |
| 144 | + |
| 145 | +Phase 2 should treat this branch state as the baseline and should not revisit the phase 1 contract decision. |
| 146 | + |
| 147 | +Phase 2 should: |
| 148 | + |
| 149 | +- make the shared chat-agent contract the default path for new work |
| 150 | +- modularize real tool verticals behind local contracts |
| 151 | +- adopt canonical `ui_message` storage as the normal persisted read/write path |
| 152 | +- ship one live specialist |
| 153 | +- prove that one external AI SDK/community pattern ports in without core runtime surgery |
| 154 | +- write detailed phase 3 planning only after phase 2 acceptance criteria pass |
| 155 | + |
| 156 | +The phase 2 execution document is [2026-04-23-ai-sdk-contract-standardization-phase-2.md](/Users/nick/.codex/worktrees/3a35/vana-v2/docs/superpowers/plans/2026-04-23-ai-sdk-contract-standardization-phase-2.md). |
0 commit comments