Skip to content

feat: AG-UI client-to-server compliance#511

Open
AlemTuzlak wants to merge 32 commits intomainfrom
feat/ag-ui-client-compliance
Open

feat: AG-UI client-to-server compliance#511
AlemTuzlak wants to merge 32 commits intomainfrom
feat/ag-ui-client-compliance

Conversation

@AlemTuzlak
Copy link
Copy Markdown
Contributor

@AlemTuzlak AlemTuzlak commented Apr 27, 2026

Summary

  • Wire format: @tanstack/ai-client now POSTs AG-UI RunAgentInput ({threadId, runId, state, messages, tools, context, forwardedProps}) instead of {messages, data}. New helpers chatParamsFromRequestBody and mergeAgentTools exported from @tanstack/ai for server endpoints.
  • chat() accepts optional threadId, runId, parentRunId for AG-UI run correlation; parentRunId plumbed through every provider adapter into RUN_STARTED events.
  • ChatClient generates and persists threadId per session, fresh runId per send; client-side tools auto-advertised in the request payload's tools field.
  • Foreign AG-UI client compatibility: convertMessagesToModelMessages dedups fan-out tool messages already covered by anchor parts, drops reasoning/activity, collapses developersystem. A pure AG-UI client can hit a TanStack server and have its history reassembled correctly.
  • Breaking: legacy {messages, data} body shape is rejected with 400 + migration-pointing error. Coordinated minor bump for @tanstack/ai, @tanstack/ai-client, @tanstack/ai-react, @tanstack/ai-solid, @tanstack/ai-vue, @tanstack/ai-svelte, @tanstack/ai-react-ui.

Migration guide: docs/migration/ag-ui-compliance.md

Test Plan

  • Manually exercise ts-react-chat in a browser: send messages, trigger a server tool, trigger a client tool, verify streaming and threadId continuity (subagents could not visually verify the chat UI)
  • Manually exercise ts-vue-chat in a browser to confirm the migrated vite middleware works end-to-end
  • Verify ts-svelte-chat and ts-solid-chat chat flows in a browser
  • Run full E2E suite in CI (pnpm --filter @tanstack/ai-e2e test:e2e) — local subset showed 24 hard pass + 11 flaky-but-passing-on-retry; confirm CI behavior matches
  • Review the migration guide for clarity from a fresh-eyes perspective; the security note on forwardedProps should land before any reader is tempted to spread it
  • Confirm the changeset's framework package list is what we want released together (currently bumps all 7 framework packages even where their source didn't change, to keep clients and servers in lockstep)
  • Sanity check: all 7 provider adapters (openai, anthropic, gemini, ollama, groq, grok, openrouter) emit RUN_STARTED with parentRunId when supplied

Summary by CodeRabbit

  • New Features
    • AG-UI request wire format: persistent per-session threadId, per-send runId (optional parentRunId), forwardedProps for arbitrary JSON, client-advertised tools, improved multimodal message/tool serialization and deduplication.
  • Documentation
    • Migration guide, updated API docs/examples, and new request-parsing & wire-conversion utilities documented.
  • Tests
    • New unit and end-to-end suites validating request shape, thread/run semantics, tool merging, and legacy rejection.
  • Chores
    • Added codemods and tooling to assist migration.

…oles

Add a pre-pass to convertMessagesToModelMessages that drops tool fan-out
duplicates when a UIMessage anchor already covers the tool result, filters
out AG-UI reasoning/activity messages with no ModelMessage equivalent, and
collapses developer messages to the system role.
Implements a server-side helper that validates an incoming request body
against RunAgentInputSchema from @ag-ui/core and returns a spread-friendly
params object. Re-attaches stripped `parts` fields from raw messages to
preserve UIMessage compatibility after AG-UI Zod strip-mode parsing.
…mRequestBody

Replaces inline anonymous type with the Context type from @ag-ui/core
to reuse the canonical shape instead of re-declaring it.
- Export uiMessagesToWire and WireMessage from @tanstack/ai
- Add RunAgentInputContext interface to connection-adapters.ts
- Extend ConnectConnectionAdapter.connect and SubscribeConnectionAdapter.send with optional runContext param
- fetchServerSentEvents and fetchHttpStream now POST AG-UI RunAgentInput shape (threadId, runId, state, messages, tools, context, forwardedProps) instead of bare {messages, data}
- stream() and rpcStream() accept _runContext for type consistency but pass through unchanged
- normalizeConnectionAdapter wrapper forwards runContext to connect()
- Add generateRunId() helper for fallback threadId/runId generation
- Make uiMessagesToWire defensive against ModelMessage-shaped inputs (no parts field)
- Update test assertions: body.data -> body.forwardedProps, body.model/provider -> body.forwardedProps.*
…tise client tools

Adds threadId field to ChatClient (persisted across sends, configurable via
ChatClientOptions.threadId) and builds a per-send runContext with a fresh
runId and serialized clientTools advertised to the connection adapter.
…s-react-chat to RunAgentInput endpoint

- Widen TextActivityOptions.messages to accept Array<UIMessage | ModelMessage | ConstrainedModelMessage>
  (function body already handled both at runtime)
- Migrate examples/ts-react-chat to chatParamsFromRequestBody + mergeAgentTools
Migrate ts-solid-chat and ts-svelte-chat server endpoints to use
chatParamsFromRequestBody and mergeAgentTools, accepting RunAgentInput
wire format. Switches provider selection from data.provider to
params.forwardedProps.provider. Passes threadId and runId through to
chat(). ts-group-chat uses Cap'n Web RPC WebSockets (no HTTP body
parsing) and is intentionally skipped.
…ance spec

Switch api.chat.ts, api.tools-test.ts, and api.middleware-test.ts from
destructuring body.data.* (legacy shape) to chatParamsFromRequestBody(),
extracting provider/feature/scenario/middlewareMode from params.forwardedProps.
Pass params.threadId and params.runId through to chat() calls.

Add ag-ui-compliance.spec.ts to assert the wire body has all RunAgentInput
fields (threadId, runId, state, messages, tools, context, forwardedProps),
that threadId persists across sends within a session, runId is fresh per send,
and that user/assistant messages carry their parts arrays.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Clients now POST AG-UI RunAgentInput payloads (threadId, runId, state, messages, tools, context, forwardedProps). The patch adds parsing/validation helpers, tool-merge logic, wire serializers, thread/run plumbing across ChatClient and adapters, updates servers/examples/tests/docs, and adds a codemod and migration guide.

Changes

AG-UI Client Compliance

Layer / File(s) Summary
Data Shape & Types
packages/typescript/ai/src/types.ts, packages/typescript/ai-client/src/types.ts
Adds parentRunId to TextOptions and threadId?: string to ChatClientOptions; marks legacy conversationId as deprecated.
Wire Serialization
packages/typescript/ai/src/utilities/ag-ui-wire.ts, packages/typescript/ai/src/index.ts
Adds uiMessagesToWire and WireMessage; re-exports wire helpers from package entrypoint.
Message Conversion
packages/typescript/ai/src/activities/chat/messages.ts
convertMessagesToModelMessages gains pre-scan dedupe of anchored tool results, drops reasoning/activity, and maps developersystem.
Request Parsing & Tool Merge
packages/typescript/ai/src/utilities/chat-params.ts, packages/typescript/ai/src/index.ts
Adds chatParamsFromRequestBody, chatParamsFromRequest (validation + 400 semantics) and mergeAgentTools (server-wins merge; client-only stubs); re-exports from entrypoint.
Client Runtime & Adapter APIs
packages/typescript/ai-client/src/chat-client.ts, packages/typescript/ai-client/src/connection-adapters.ts
ChatClient persists threadId, generates per-send runId, tracks deprecated body vs canonical forwardedProps, constructs RunAgentInputContext; adapters gain runContext parameter and exported RunAgentInputContext type.
Adapter Implementations
packages/typescript/ai-client/src/connection-adapters.ts, packages/typescript/ai-*/src/adapters/text.ts
Fetch/SSE/HTTP adapters build AG-UI RunAgentInput POST bodies (threadId/runId/parentRunId, messages via uiMessagesToWire, tools from runContext.clientTools, forwardedProps with legacy data mirror). Provider adapters propagate parentRunId into RUN_STARTED events.
Framework Integrations
packages/typescript/ai-*/src/*use-chat.ts, packages/typescript/ai-svelte/src/create-chat.svelte.ts, packages/typescript/ai-svelte/src/types.ts
Framework hooks forward forwardedProps into ChatClient, sync updates via updateOptions, add updateForwardedProps (Svelte) and deprecate body/updateBody while preserving backward-compatible merging.
Server Routes & Examples
examples/.../src/routes/*, testing/e2e/src/routes/*, examples/ts-vue-chat/vite.config.ts
Server handlers now use chatParamsFromRequestBody (return 400 on invalid), derive provider/model from params.forwardedProps, build server tool registries and call mergeAgentTools, and invoke chat() with params.messages, params.threadId, params.runId. Many routes prefer body.forwardedProps fallback.
Types & Middleware Context
packages/typescript/ai/src/activities/chat/index.ts, packages/typescript/ai/src/activities/chat/middleware/types.ts, packages/typescript/ai/src/activities/chat/middleware/compose.ts
TextActivityOptions.messages widened to accept `UIMessage
Tests
packages/typescript/ai-client/tests/*, packages/typescript/ai/tests/*, testing/e2e/tests/*, packages/typescript/ai/tests/*
Adapter tests updated to assert forwardedProps; new unit tests for uiMessagesToWire, chatParamsFromRequestBody, mergeAgentTools, convertMessagesToModelMessages; ChatClient tests updated for thread/run/forwardedProps behavior; new Playwright e2e tests for AG-UI compliance, foreign-client compatibility, and legacy-shape rejection.
Docs, Migration & Codemod
docs/migration/ag-ui-compliance.md, docs/*, .changeset/*, codemods/*, codemods/ag-ui-compliance/transform.ts
Adds migration guide, changes API/docs/examples for thread/run/forwardedProps/parentRunId and helper functions; introduces a jscodeshift codemod with fixtures, tests, runner, and READMEs.
Workspace / Config & Packaging
pnpm-workspace.yaml, codemods/package.json, codemods/tsconfig.json, package.json, packages/typescript/ai/package.json
Adds codemods workspace, codemods package manifest and runner (run.mjs), root codemod:ag-ui-compliance script; bumps @ag-ui/core dependency to ^0.0.52.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant ChatClient as ChatClient
    participant Adapter as Connection Adapter
    participant Server as Server Endpoint
    participant Engine as Chat Engine

    Client->>ChatClient: send(messages)
    ChatClient->>ChatClient: build runContext(threadId, runId, parentRunId?, clientTools, forwardedProps)
    ChatClient->>Adapter: send(messages, data?, abortSignal?, runContext)
    Adapter->>Adapter: uiMessagesToWire(messages) + merge forwardedProps/data
    Adapter->>Server: POST /api/chat RunAgentInput{threadId, runId, state, messages, tools, context, forwardedProps}
    Server->>Server: chatParamsFromRequestBody(body) => params (or 400)
    Server->>Server: mergedTools = mergeAgentTools(serverTools, params.tools)
    Server->>Engine: chat(params.messages, { tools: mergedTools, threadId, runId, parentRunId })
    Engine-->>Server: stream chunks (RUN_STARTED includes parentRunId, tokens..., RUN_FINISHED)
    Server-->>Adapter: forward stream
    Adapter-->>ChatClient: deliver streamed events (with runIds)
    ChatClient-->>Client: streamed response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐇
I hop through threads and freshly make a run,
Tools line up, forwardedProps weigh a ton,
Messages wired, parts in tidy rows,
A codemod nibbles, migration garden grows—
Hooray, the compliance carrot's won.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ag-ui-client-compliance

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 27, 2026

🚀 Changeset Version Preview

7 package(s) bumped directly, 26 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-react 0.8.1 → 1.0.0 Changeset
@tanstack/ai-react-ui 0.6.3 → 1.0.0 Changeset
@tanstack/ai-solid 0.7.1 → 1.0.0 Changeset
@tanstack/ai-svelte 0.7.1 → 1.0.0 Changeset
@tanstack/ai-vue 0.7.1 → 1.0.0 Changeset
@tanstack/ai-anthropic 0.8.4 → 1.0.0 Dependent
@tanstack/ai-code-mode 0.1.9 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.1.9 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.1 → 1.0.0 Dependent
@tanstack/ai-event-client 0.2.9 → 1.0.0 Dependent
@tanstack/ai-fal 0.7.1 → 1.0.0 Dependent
@tanstack/ai-gemini 0.10.1 → 1.0.0 Dependent
@tanstack/ai-grok 0.7.1 → 1.0.0 Dependent
@tanstack/ai-groq 0.1.9 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.9 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.9 → 1.0.0 Dependent
@tanstack/ai-ollama 0.6.11 → 1.0.0 Dependent
@tanstack/ai-openai 0.8.3 → 1.0.0 Dependent
@tanstack/ai-openrouter 0.8.3 → 1.0.0 Dependent
@tanstack/ai-preact 0.6.21 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.6.3 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.15.0 → 0.16.0 Changeset
@tanstack/ai-client 0.9.0 → 0.10.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-code-mode-models-eval 0.0.13 → 0.0.14 Dependent
@tanstack/ai-devtools-core 0.3.26 → 0.3.27 Dependent
@tanstack/ai-isolate-cloudflare 0.2.0 → 0.2.1 Dependent
@tanstack/ai-vue-ui 0.1.32 → 0.1.33 Dependent
@tanstack/preact-ai-devtools 0.1.30 → 0.1.31 Dependent
@tanstack/react-ai-devtools 0.2.30 → 0.2.31 Dependent
@tanstack/solid-ai-devtools 0.2.30 → 0.2.31 Dependent
ts-svelte-chat 0.1.39 → 0.1.40 Dependent
ts-vue-chat 0.1.39 → 0.1.40 Dependent
vanilla-chat 0.0.36 → 0.0.37 Dependent

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Apr 27, 2026

View your CI Pipeline Execution ↗ for commit efa547a

Command Status Duration Result
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 1s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-08 17:07:28 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 27, 2026

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@511

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@511

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@511

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@511

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/@tanstack/ai-code-mode-skills@511

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@511

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@511

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@511

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@511

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@511

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@511

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@511

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@511

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@511

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@511

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@511

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@511

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@511

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@511

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@511

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@511

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@511

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@511

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@511

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@511

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@511

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@511

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@511

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@511

commit: 02da6d3

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/typescript/ai-client/src/connection-adapters.ts (1)

218-228: ⚠️ Potential issue | 🟡 Minor

Synthesized RUN_FINISHED should reuse runContext.runId so the terminal event correlates with the actual run.

Today the wrapper fabricates a fresh run-${Date.now()} runId, which won't match the runId the chat-client advertised for this send. Downstream consumers correlating by runId will see a mismatched terminal event.

♻️ Suggested fix
-        if (!abortSignal?.aborted && !hasTerminalEvent) {
-          push({
-            type: 'RUN_FINISHED',
-            runId: `run-${Date.now()}`,
-            model: 'connect-wrapper',
-            timestamp: Date.now(),
-            finishReason: 'stop',
-          } as unknown as StreamChunk)
-        }
+        if (!abortSignal?.aborted && !hasTerminalEvent) {
+          push({
+            type: 'RUN_FINISHED',
+            runId: runContext?.runId ?? `run-${Date.now()}`,
+            model: 'connect-wrapper',
+            timestamp: Date.now(),
+            finishReason: 'stop',
+          } as unknown as StreamChunk)
+        }

Same applies to the synthesized RUN_ERROR path — consider adding runId: runContext?.runId so error events carry the correlation as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/typescript/ai-client/src/connection-adapters.ts` around lines 218 -
228, The synthesized terminal events currently fabricate a new runId causing
correlation breaks; update the code that pushes synthetic events (the push calls
creating type: 'RUN_FINISHED' and the analogous 'RUN_ERROR' path) to set runId:
runContext?.runId (or runContext.runId) instead of `run-${Date.now()}` so the
synthetic RUN_FINISHED and RUN_ERROR events carry the original runContext.runId
and properly correlate with the original run.
🧹 Nitpick comments (13)
packages/typescript/ai/src/activities/chat/messages.ts (1)

104-111: Minor: developer→system content type-narrowing.

(msg as { content: string }).content assumes string content, but a ModelMessage-typed input has content: string | null | Array<ContentPart>. AG-UI's developer role spec is text-only in practice, so this is unlikely to bite, but if a foreign client sends a developer message with array content it will be passed through as-is and the resulting "system" ModelMessage will have a non-string content the downstream provider adapters may not handle gracefully.

Consider preserving the original content type or coercing explicitly:

🛡️ Optional safer narrowing
-    // AG-UI developer — collapse to system
-    if (role === 'developer') {
-      modelMessages.push({
-        role: 'system' as ModelMessage['role'],
-        content: (msg as { content: string }).content,
-      } as ModelMessage)
-      continue
-    }
+    // AG-UI developer — collapse to system
+    if (role === 'developer') {
+      const content = (msg as { content: unknown }).content
+      modelMessages.push({
+        role: 'system' as ModelMessage['role'],
+        content: typeof content === 'string' ? content : String(content ?? ''),
+      } as ModelMessage)
+      continue
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/typescript/ai/src/activities/chat/messages.ts` around lines 104 -
111, The current developer→system conversion casts msg to { content: string }
and forces a string which can drop or mis-handle legitimate non-string
ModelMessage.content (null or Array<ContentPart>); update the conversion in the
developer branch (where role === 'developer' and you push into modelMessages) to
preserve or safely coerce the original msg.content: use the actual msg.content
as ModelMessage['content'] (no blind cast) or, if you must ensure a string,
explicitly normalize by checking typeof msg.content and
Array.isArray(msg.content> (transform ContentPart[] to a string via join or
mapping) or pass through null/array unchanged so downstream provider adapters
receive a valid ModelMessage['content'] type.
packages/typescript/ai/src/utilities/chat-params.ts (2)

38-94: Optional: drop the Promise wrapper.

chatParamsFromRequestBody performs only synchronous work but returns a Promise via Promise.reject/Promise.resolve. Either drop the wrapper (return the value/throw) or mark the function async for symmetry; the Promise<...> return type forces every caller into await for no real reason.

♻️ Proposed simplification
-export function chatParamsFromRequestBody(body: unknown): Promise<{
+export function chatParamsFromRequestBody(body: unknown): {
   messages: Array<UIMessage | ModelMessage>
   threadId: string
   runId: string
   parentRunId?: string
   tools: Array<{ name: string; description: string; parameters: JSONSchema }>
   forwardedProps: Record<string, unknown>
   state: unknown
   context: Array<AGUIContext>
-}> {
+} {
   const parseResult = RunAgentInputSchema.safeParse(body)
   if (!parseResult.success) {
-    return Promise.reject(
-      new AGUIError(
-        `Request body is not a valid AG-UI RunAgentInput. ` +
-          `If you're upgrading from a previous `@tanstack/ai-client` release, ` +
-          `see docs/migration/ag-ui-compliance.md. ` +
-          `Validation errors: ${parseResult.error.message}`,
-      ),
-    )
+    throw new AGUIError(
+      `Request body is not a valid AG-UI RunAgentInput. ` +
+        `If you're upgrading from a previous `@tanstack/ai-client` release, ` +
+        `see docs/migration/ag-ui-compliance.md. ` +
+        `Validation errors: ${parseResult.error.message}`,
+    )
   }
   ...
-  return Promise.resolve({
+  return {
     messages,
     ...
-  })
+  }
 }

If callers already await it, leaving the Promise<> for backwards-compat may be intentional — feel free to ignore.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/typescript/ai/src/utilities/chat-params.ts` around lines 38 - 94,
The function chatParamsFromRequestBody currently wraps synchronous logic in
Promise.reject/Promise.resolve; change it to a synchronous function that returns
the parsed object or throws on error: update the signature to return the plain
result type (remove Promise<...>), replace Promise.reject(new AGUIError(...))
with throw new AGUIError(...), and replace Promise.resolve({...}) with a plain
return {...}; keep the same validation logic and references to
RunAgentInputSchema, AGUIError, isValidParts, and the parsed/messages handling
so callers can be updated to remove unnecessary awaits (or keep them if backward
compatibility is desired).

65-78: Consider the limitations of the suggested id-based refactor.

While the concern about index-based alignment is valid in principle, the current implementation is safe because Zod's .strip() only removes unknown properties and preserves array length and order. The suggested defensive refactor to use message id has a limitation: ModelMessage does not have an id field, only UIMessage does (line 355 in types.ts). Since chatParamsFromRequestBody returns Array<UIMessage | ModelMessage>, the refactored code would need to handle both cases—either by always including id in ModelMessage or by adding a fallback. If the goal is purely defensive against future schema changes that might filter/reorder messages, a more robust approach would ensure all message types carry an id or use a different keying strategy that works for both.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/typescript/ai/src/utilities/chat-params.ts` around lines 65 - 78,
The review notes that switching from index-based alignment to id-based matching
is tricky because ModelMessage lacks an id (UIMessage has id); update the merge
logic in chatParamsFromRequestBody (the mapped block over parsed.messages using
rawMessages, isValidParts, and parts) to be defensive: when raw message has an
id and parsed message is a UIMessage with the same id, use raw.parts; otherwise
fall back to the current index-based behavior so ModelMessage still
works—alternatively add an optional id to ModelMessage across types.ts and
handle both cases so the parts-matching logic works whether messages are
UIMessage or ModelMessage.
testing/e2e/tests/ag-ui-old-client-rejection.spec.ts (1)

17-19: Optional: tighten the error-message assertion.

The current regex passes when any one of AG-UI, RunAgentInput, or migration appears, so a 400 with an unrelated body mentioning "migration" would still pass. If the intent is to verify the migration-pointing error specifically, consider asserting both that a protocol identifier and a migration cue are present.

♻️ Suggested tightening
-  expect(body).toMatch(/AG-UI|RunAgentInput|migration/i)
+  expect(body).toMatch(/AG-UI|RunAgentInput/i)
+  expect(body).toMatch(/migration/i)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testing/e2e/tests/ag-ui-old-client-rejection.spec.ts` around lines 17 - 19,
The current assertion on the response body uses a single regex that matches any
of "AG-UI", "RunAgentInput", or "migration", which can yield false positives;
update the test to require both a protocol identifier and a migration cue by
replacing the single expect(body).toMatch(/AG-UI|RunAgentInput|migration/i)
check with two assertions (e.g., expect(body).toMatch(/AG-UI|RunAgentInput/) and
expect(body).toMatch(/migration/i)) or a single regex with lookaheads to ensure
both are present, targeting the existing body variable and the expect(body) call
in this spec.
packages/typescript/ai-client/src/types.ts (1)

310-313: ChatRequestBody is stale and contradicts the wire format change.

Per the PR, @tanstack/ai-client now POSTs AG-UI RunAgentInput ({threadId, runId, state, messages, tools, context, forwardedProps}), and the legacy shape ({messages, data}) is explicitly rejected with 400. This interface still documents the old shape, misleading any downstream consumer importing it.

Remove it or replace it with a type alias to RunAgentInput (if available from @ag-ui/core) to align the public API with the actual wire format.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/typescript/ai-client/src/types.ts` around lines 310 - 313, Update
the stale ChatRequestBody interface to match the new wire format: remove the old
ChatRequestBody definition and instead export a type alias to RunAgentInput from
`@ag-ui/core` (e.g. export type ChatRequestBody = RunAgentInput) or delete the
symbol entirely if RunAgentInput is unavailable; ensure you import RunAgentInput
from '@ag-ui/core' and update any exported symbols so the public types reflect
the actual POST payload rather than the legacy {messages, data} shape.
testing/e2e/tests/ag-ui-foreign-client.spec.ts (1)

59-63: Strengthen the developer-role assertion to verify the run actually finishes.

response.ok() alone passes even if the body contains a RUN_ERROR (or no run events at all), so this test would not catch a regression where the developer→system collapse is correct but the run subsequently fails server-side. Mirror Test 1's expect(text).toContain('RUN_FINISHED') here.

♻️ Proposed change
     const response = await request.post('/api/chat', {
       data: body,
       headers: { 'Content-Type': 'application/json' },
     })
     expect(response.ok()).toBe(true)
+    const text = await response.text()
+    expect(text).toContain('RUN_FINISHED')
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@testing/e2e/tests/ag-ui-foreign-client.spec.ts` around lines 59 - 63, The
test currently only checks response.ok() which can miss server-side run
failures; after obtaining the response from request.post('/api/chat') (the
response variable), read the response text (e.g., response.text()) and add an
assertion like expect(text).toContain('RUN_FINISHED') to verify the
developer→system run actually completed successfully, mirroring the assertion
used in Test 1.
packages/typescript/ai-client/src/chat-client.ts (1)

615-615: Reuse generateUniqueId('run') for the runId.

threadId (L85) and other IDs use this.generateUniqueId(prefix); this one inlines an only-slightly-different format (slice(2, 8) vs. substring(7)). Reusing the helper keeps ID generation consistent and centralizes any future changes (e.g., switching to crypto.randomUUID).

♻️ Proposed change
-        runId: `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
+        runId: this.generateUniqueId('run'),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/typescript/ai-client/src/chat-client.ts` at line 615, Replace the
inline runId creation with the existing helper to ensure consistent ID format:
change the runId assignment that currently uses
`run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` to call
`this.generateUniqueId('run')` so it matches other IDs like `threadId` which use
`this.generateUniqueId(prefix)`; update the runId assignment in the same block
where `runId` is set to use `this.generateUniqueId('run')` and ensure no other
callers rely on the old inline format.
packages/typescript/ai/src/index.ts (1)

177-179: Comment understates the public surface area.

Once uiMessagesToWire and WireMessage are exported from the package barrel, they become public API for any consumer — calling them "used internally by @tanstack/ai-client" risks giving the impression they're private and free to break in patch releases. Consider either dropping the "internally" wording or moving these to a sub-path export if you want to keep them out of the stability contract.

♻️ Proposed comment tweak
-// AG-UI wire serialization (used internally by `@tanstack/ai-client`)
+// AG-UI wire serialization (used by `@tanstack/ai-client`; also part of the public API)
 export { uiMessagesToWire } from './utilities/ag-ui-wire'
 export type { WireMessage } from './utilities/ag-ui-wire'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/typescript/ai/src/index.ts` around lines 177 - 179, The comment
currently labels uiMessagesToWire and WireMessage as "used internally by
`@tanstack/ai-client`", understating that once exported from the package barrel
they are public API; update the comment or the export to reflect intended
stability: either remove or reword "used internally by `@tanstack/ai-client`" to
indicate these are public exports (e.g., "public serialization utilities"), or
instead move the exports uiMessagesToWire and WireMessage out of the barrel into
a dedicated internal sub-path export so they are not part of the package root
public surface; adjust the lines exporting uiMessagesToWire and WireMessage in
index.ts accordingly.
examples/ts-vue-chat/vite.config.ts (2)

215-219: Provider value isn't validated against the known Provider set.

Unlike the Svelte handler (which checks params.forwardedProps.provider in adapterConfig), this cast accepts any string and relies on the default: branch of the switch at lines 227-245 for fallback. That works today, but a future refactor that drops the default: case would silently pass an invalid string through. Consider validating against a known set up-front for parity with the other examples.

♻️ Suggested validation
-            const fp = params.forwardedProps as Record<string, unknown>
-            const provider: Provider =
-              typeof fp.provider === 'string'
-                ? (fp.provider as Provider)
-                : 'openai'
+            const fp = params.forwardedProps as Record<string, unknown>
+            const knownProviders = ['openai', 'anthropic', 'gemini', 'ollama'] as const
+            const provider: Provider =
+              typeof fp.provider === 'string' &&
+              (knownProviders as readonly string[]).includes(fp.provider)
+                ? (fp.provider as Provider)
+                : 'openai'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ts-vue-chat/vite.config.ts` around lines 215 - 219, The provider
string is being cast without validation; update the assignment that creates
provider from fp (params.forwardedProps) so it validates the value against the
known provider set (e.g., keys of adapterConfig or a local knownProviders
collection) before casting—if fp.provider is a string and exists in that set use
it, otherwise fall back to 'openai'; modify the logic around the provider
variable creation (where fp and Provider are referenced) to perform this
membership check to match the Svelte handler approach.

253-255: Inconsistent server tool registry vs. the Svelte/React examples.

This handler only registers getGuitars and addToCartToolServer as server tools, while the Svelte example registers getGuitars, recommendGuitarToolDef, addToCartToolServer, addToWishListToolDef, and getPersonalGuitarPreferenceToolDef, and the React example registers an even larger superset. Tool definitions without .server(...) are still useful in serverTools because mergeAgentTools will treat them as no-execute entries — the LLM still sees them, but the runtime emits ClientToolRequest events. Unless the Vue client side is advertising all of these via params.tools, the model in this example will be missing recommendGuitar, addToWishList, and getPersonalGuitarPreference.

Worth either making the example consistent with the others or adding a comment explaining why Vue diverges.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ts-vue-chat/vite.config.ts` around lines 253 - 255, The serverTools
registry currently only includes getGuitars and addToCartToolServer which omits
recommendGuitarToolDef, addToWishListToolDef, and
getPersonalGuitarPreferenceToolDef used in other examples; update the
serverTools Object.fromEntries to include those tool defs (so mergeAgentTools
treats them as no-execute entries) or add a clarifying comment explaining why
Vue intentionally omits them and that the client must advertise missing tools
via params.tools; reference the symbols getGuitars, addToCartToolServer,
recommendGuitarToolDef, addToWishListToolDef,
getPersonalGuitarPreferenceToolDef, and mergeAgentTools when making the change.
examples/ts-svelte-chat/src/routes/api/chat/+server.ts (1)

109-117: Optional: avoid surfacing raw validator messages to clients in production handlers.

Returning error.message directly is fine for an example, but it surfaces Zod's internal validation text (paths, expected types) to any caller. For production-bound copies of this handler, consider returning a stable, sanitized 400 body and logging the detailed error server-side.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ts-svelte-chat/src/routes/api/chat/`+server.ts around lines 109 -
117, The handler currently returns raw validator messages from the catch block
when calling chatParamsFromRequestBody; instead, sanitize responses for
production by returning a fixed, non-revealing 400 body (e.g., "Invalid request
payload") and log the detailed error server-side (including the caught error and
context) so debugging info is preserved without exposing Zod internals to
clients; update the catch around chatParamsFromRequestBody/params to perform
logging and return the sanitized message.
packages/typescript/ai/tests/chat-params.test.ts (1)

41-54: Optional: also assert the rejection error message points at the missing field for better debug ergonomics.

These tests confirm that chatParamsFromRequestBody rejects when threadId/runId/messages are absent, but they only assert that some error is thrown. If RunAgentInputSchema ever changes to make one of these optional with a default, these tests would still pass. A regex assertion (e.g., rejects.toThrow(/threadId/)) would tighten the contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/typescript/ai/tests/chat-params.test.ts` around lines 41 - 54,
Update the three tests that currently call chatParamsFromRequestBody(rest) and
only assert a rejection to also assert the rejection message references the
missing field; for each test replace the generic rejects.toThrow() assertion
with a regex check like rejects.toThrow(/threadId/), rejects.toThrow(/runId/),
and rejects.toThrow(/messages/) respectively so the tests fail if
RunAgentInputSchema changes to make a field optional or provide a default.
packages/typescript/ai-client/src/connection-adapters.ts (1)

501-506: Stream and RPC adapters intentionally exclude runContext from the wire shape—this is by design.

Both stream() (line 501) and rpcStream() (line 532) drop the runContext parameter because their factory functions only accept messages and data. Unlike the fetch() and webSocket() adapters, which embed threadId and runId in their wire payloads, direct server functions and RPC endpoints don't use the AG-UI wire format.

If an RPC backend or direct server function needs session correlation, consider extending its factory signature to optionally accept runContext and extract threadId/runId from there.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/typescript/ai-client/src/connection-adapters.ts` around lines 501 -
506, The adapters currently drop runContext (see async *connect(messages, data,
_abortSignal, _runContext) and the rpcStream() adapter) which is intentional,
but unclear in the code; either add a short clarifying comment above connect and
rpcStream explaining that streamFactory and RPC factory functions accept only
(messages, data) and therefore runContext/threadId/runId are not sent on the
wire, or if session correlation is required, update the streamFactory and the
RPC factory signatures to optionally accept runContext and then forward
_runContext from async *connect (and the rpcStream connector) so threadId/runId
can be extracted and embedded as needed. Ensure you reference the connect
method, streamFactory, rpcStream, and runContext when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/ts-react-chat/src/routes/api.tanchat.ts`:
- Around line 154-158: The provider selection currently treats any non-empty
string as valid, so set provider must be validated against the known adapters
before using it; change the logic that computes provider (the expression using
params.forwardedProps.provider and Provider) to check membership in
adapterConfig (or a Set of adapterConfig keys) and only accept it if it exists,
otherwise default to 'openai'. Specifically, validate
params.forwardedProps.provider against adapterConfig keys prior to calling
adapterConfig[provider]() to prevent undefined lookups and return a 400 or fall
back to 'openai' when the provided key is not present.

In `@packages/typescript/ai-client/src/chat-client.ts`:
- Around line 613-624: The client tool parameter serialization is using raw
AnyClientTool.inputSchema (which may be a Standard Schema) instead of JSON
Schema; update the mapping that builds runContext.clientTools (the
Array.from(this.clientToolsRef.current.values()).map callback that sets
name/description/parameters) to call convertSchemaToJsonSchema(inputSchema) when
inputSchema exists, falling back to { type: 'object' } if conversion returns
falsy, so RunAgentInput.tools[].parameters are valid JSON Schema; reference
convertSchemaToJsonSchema and the runContext/clientTools mapping when making
this change.

In `@packages/typescript/ai-client/src/connection-adapters.ts`:
- Around line 313-329: Change the uiMessagesToWire signature to accept
Array<UIMessage | ModelMessage> (instead of only Array<UIMessage>) so callers no
longer need unsafe casts; update the function declaration for uiMessagesToWire
and any related type imports/exports, and adjust internal typing if necessary to
preserve existing defensive access (e.g., using msg.parts ?? msg.content) so
both ConnectConnectionAdapter.connect and SubscribeConnectionAdapter.send can
pass their messages: Array<UIMessage> | Array<ModelMessage> directly without
casting.

In `@packages/typescript/ai/docs/chat-architecture.md`:
- Line 86: The link text in packages/typescript/ai/docs/chat-architecture.md
references "docs/migration/ag-ui-compliance.md" which is a broken relative path
from this nested location; update that link to the correct repository-relative
path (either replace "docs/migration/ag-ui-compliance.md" with
"../../../../docs/migration/ag-ui-compliance.md" or use an absolute repo-root
path like "/docs/migration/ag-ui-compliance.md") so the link resolves correctly.

---

Outside diff comments:
In `@packages/typescript/ai-client/src/connection-adapters.ts`:
- Around line 218-228: The synthesized terminal events currently fabricate a new
runId causing correlation breaks; update the code that pushes synthetic events
(the push calls creating type: 'RUN_FINISHED' and the analogous 'RUN_ERROR'
path) to set runId: runContext?.runId (or runContext.runId) instead of
`run-${Date.now()}` so the synthetic RUN_FINISHED and RUN_ERROR events carry the
original runContext.runId and properly correlate with the original run.

---

Nitpick comments:
In `@examples/ts-svelte-chat/src/routes/api/chat/`+server.ts:
- Around line 109-117: The handler currently returns raw validator messages from
the catch block when calling chatParamsFromRequestBody; instead, sanitize
responses for production by returning a fixed, non-revealing 400 body (e.g.,
"Invalid request payload") and log the detailed error server-side (including the
caught error and context) so debugging info is preserved without exposing Zod
internals to clients; update the catch around chatParamsFromRequestBody/params
to perform logging and return the sanitized message.

In `@examples/ts-vue-chat/vite.config.ts`:
- Around line 215-219: The provider string is being cast without validation;
update the assignment that creates provider from fp (params.forwardedProps) so
it validates the value against the known provider set (e.g., keys of
adapterConfig or a local knownProviders collection) before casting—if
fp.provider is a string and exists in that set use it, otherwise fall back to
'openai'; modify the logic around the provider variable creation (where fp and
Provider are referenced) to perform this membership check to match the Svelte
handler approach.
- Around line 253-255: The serverTools registry currently only includes
getGuitars and addToCartToolServer which omits recommendGuitarToolDef,
addToWishListToolDef, and getPersonalGuitarPreferenceToolDef used in other
examples; update the serverTools Object.fromEntries to include those tool defs
(so mergeAgentTools treats them as no-execute entries) or add a clarifying
comment explaining why Vue intentionally omits them and that the client must
advertise missing tools via params.tools; reference the symbols getGuitars,
addToCartToolServer, recommendGuitarToolDef, addToWishListToolDef,
getPersonalGuitarPreferenceToolDef, and mergeAgentTools when making the change.

In `@packages/typescript/ai-client/src/chat-client.ts`:
- Line 615: Replace the inline runId creation with the existing helper to ensure
consistent ID format: change the runId assignment that currently uses
`run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` to call
`this.generateUniqueId('run')` so it matches other IDs like `threadId` which use
`this.generateUniqueId(prefix)`; update the runId assignment in the same block
where `runId` is set to use `this.generateUniqueId('run')` and ensure no other
callers rely on the old inline format.

In `@packages/typescript/ai-client/src/connection-adapters.ts`:
- Around line 501-506: The adapters currently drop runContext (see async
*connect(messages, data, _abortSignal, _runContext) and the rpcStream() adapter)
which is intentional, but unclear in the code; either add a short clarifying
comment above connect and rpcStream explaining that streamFactory and RPC
factory functions accept only (messages, data) and therefore
runContext/threadId/runId are not sent on the wire, or if session correlation is
required, update the streamFactory and the RPC factory signatures to optionally
accept runContext and then forward _runContext from async *connect (and the
rpcStream connector) so threadId/runId can be extracted and embedded as needed.
Ensure you reference the connect method, streamFactory, rpcStream, and
runContext when making the change.

In `@packages/typescript/ai-client/src/types.ts`:
- Around line 310-313: Update the stale ChatRequestBody interface to match the
new wire format: remove the old ChatRequestBody definition and instead export a
type alias to RunAgentInput from `@ag-ui/core` (e.g. export type ChatRequestBody =
RunAgentInput) or delete the symbol entirely if RunAgentInput is unavailable;
ensure you import RunAgentInput from '@ag-ui/core' and update any exported
symbols so the public types reflect the actual POST payload rather than the
legacy {messages, data} shape.

In `@packages/typescript/ai/src/activities/chat/messages.ts`:
- Around line 104-111: The current developer→system conversion casts msg to {
content: string } and forces a string which can drop or mis-handle legitimate
non-string ModelMessage.content (null or Array<ContentPart>); update the
conversion in the developer branch (where role === 'developer' and you push into
modelMessages) to preserve or safely coerce the original msg.content: use the
actual msg.content as ModelMessage['content'] (no blind cast) or, if you must
ensure a string, explicitly normalize by checking typeof msg.content and
Array.isArray(msg.content> (transform ContentPart[] to a string via join or
mapping) or pass through null/array unchanged so downstream provider adapters
receive a valid ModelMessage['content'] type.

In `@packages/typescript/ai/src/index.ts`:
- Around line 177-179: The comment currently labels uiMessagesToWire and
WireMessage as "used internally by `@tanstack/ai-client`", understating that once
exported from the package barrel they are public API; update the comment or the
export to reflect intended stability: either remove or reword "used internally
by `@tanstack/ai-client`" to indicate these are public exports (e.g., "public
serialization utilities"), or instead move the exports uiMessagesToWire and
WireMessage out of the barrel into a dedicated internal sub-path export so they
are not part of the package root public surface; adjust the lines exporting
uiMessagesToWire and WireMessage in index.ts accordingly.

In `@packages/typescript/ai/src/utilities/chat-params.ts`:
- Around line 38-94: The function chatParamsFromRequestBody currently wraps
synchronous logic in Promise.reject/Promise.resolve; change it to a synchronous
function that returns the parsed object or throws on error: update the signature
to return the plain result type (remove Promise<...>), replace
Promise.reject(new AGUIError(...)) with throw new AGUIError(...), and replace
Promise.resolve({...}) with a plain return {...}; keep the same validation logic
and references to RunAgentInputSchema, AGUIError, isValidParts, and the
parsed/messages handling so callers can be updated to remove unnecessary awaits
(or keep them if backward compatibility is desired).
- Around line 65-78: The review notes that switching from index-based alignment
to id-based matching is tricky because ModelMessage lacks an id (UIMessage has
id); update the merge logic in chatParamsFromRequestBody (the mapped block over
parsed.messages using rawMessages, isValidParts, and parts) to be defensive:
when raw message has an id and parsed message is a UIMessage with the same id,
use raw.parts; otherwise fall back to the current index-based behavior so
ModelMessage still works—alternatively add an optional id to ModelMessage across
types.ts and handle both cases so the parts-matching logic works whether
messages are UIMessage or ModelMessage.

In `@packages/typescript/ai/tests/chat-params.test.ts`:
- Around line 41-54: Update the three tests that currently call
chatParamsFromRequestBody(rest) and only assert a rejection to also assert the
rejection message references the missing field; for each test replace the
generic rejects.toThrow() assertion with a regex check like
rejects.toThrow(/threadId/), rejects.toThrow(/runId/), and
rejects.toThrow(/messages/) respectively so the tests fail if
RunAgentInputSchema changes to make a field optional or provide a default.

In `@testing/e2e/tests/ag-ui-foreign-client.spec.ts`:
- Around line 59-63: The test currently only checks response.ok() which can miss
server-side run failures; after obtaining the response from
request.post('/api/chat') (the response variable), read the response text (e.g.,
response.text()) and add an assertion like
expect(text).toContain('RUN_FINISHED') to verify the developer→system run
actually completed successfully, mirroring the assertion used in Test 1.

In `@testing/e2e/tests/ag-ui-old-client-rejection.spec.ts`:
- Around line 17-19: The current assertion on the response body uses a single
regex that matches any of "AG-UI", "RunAgentInput", or "migration", which can
yield false positives; update the test to require both a protocol identifier and
a migration cue by replacing the single
expect(body).toMatch(/AG-UI|RunAgentInput|migration/i) check with two assertions
(e.g., expect(body).toMatch(/AG-UI|RunAgentInput/) and
expect(body).toMatch(/migration/i)) or a single regex with lookaheads to ensure
both are present, targeting the existing body variable and the expect(body) call
in this spec.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9681e92e-f463-4cf2-b11b-43fe638ec324

📥 Commits

Reviewing files that changed from the base of the PR and between ff33855 and 8b1cdb6.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (36)
  • .changeset/ag-ui-client-compliance.md
  • docs/config.json
  • docs/migration/ag-ui-compliance.md
  • examples/ts-react-chat/src/routes/api.tanchat.ts
  • examples/ts-solid-chat/src/routes/api.chat.ts
  • examples/ts-svelte-chat/src/routes/api/chat/+server.ts
  • examples/ts-vue-chat/vite.config.ts
  • packages/typescript/ai-anthropic/src/adapters/text.ts
  • packages/typescript/ai-client/src/chat-client.ts
  • packages/typescript/ai-client/src/connection-adapters.ts
  • packages/typescript/ai-client/src/types.ts
  • packages/typescript/ai-client/tests/connection-adapters.test.ts
  • packages/typescript/ai-gemini/src/adapters/text.ts
  • packages/typescript/ai-grok/src/adapters/text.ts
  • packages/typescript/ai-groq/src/adapters/text.ts
  • packages/typescript/ai-ollama/src/adapters/text.ts
  • packages/typescript/ai-openai/src/adapters/text.ts
  • packages/typescript/ai-openrouter/src/adapters/text.ts
  • packages/typescript/ai/docs/chat-architecture.md
  • packages/typescript/ai/package.json
  • packages/typescript/ai/skills/ai-core/ag-ui-protocol/SKILL.md
  • packages/typescript/ai/src/activities/chat/index.ts
  • packages/typescript/ai/src/activities/chat/messages.ts
  • packages/typescript/ai/src/index.ts
  • packages/typescript/ai/src/types.ts
  • packages/typescript/ai/src/utilities/ag-ui-wire.ts
  • packages/typescript/ai/src/utilities/chat-params.ts
  • packages/typescript/ai/tests/ag-ui-wire.test.ts
  • packages/typescript/ai/tests/chat-params.test.ts
  • packages/typescript/ai/tests/messages.test.ts
  • testing/e2e/src/routes/api.chat.ts
  • testing/e2e/src/routes/api.middleware-test.ts
  • testing/e2e/src/routes/api.tools-test.ts
  • testing/e2e/tests/ag-ui-compliance.spec.ts
  • testing/e2e/tests/ag-ui-foreign-client.spec.ts
  • testing/e2e/tests/ag-ui-old-client-rejection.spec.ts

Comment thread examples/ts-react-chat/src/routes/api.tanchat.ts Outdated
Comment thread packages/typescript/ai-client/src/chat-client.ts
Comment on lines +313 to +329
// Build AG-UI RunAgentInput payload
const wireMessages = uiMessagesToWire(messages as Array<UIMessage>)
const requestBody = {
messages,
data,
...resolvedOptions.body,
threadId: runContext?.threadId ?? generateRunId('thread'),
runId: runContext?.runId ?? generateRunId('run'),
...(runContext?.parentRunId !== undefined && {
parentRunId: runContext.parentRunId,
}),
state: {},
messages: wireMessages,
tools: runContext?.clientTools ?? [],
context: [],
forwardedProps: {
...(runContext?.forwardedProps ?? {}),
...data,
...resolvedOptions.body,
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm uiMessagesToWire's expected input shape and whether it tolerates ModelMessage.
fd -t f 'wire' packages/typescript/ai/src
rg -nP -C5 'export\s+function\s+uiMessagesToWire' packages/typescript/ai/src
echo '---'
# Find every connect()/send() caller in chat-client to confirm what it passes.
rg -nP -C3 '\.(connect|send)\s*\(' packages/typescript/ai-client/src

Repository: TanStack/ai

Length of output: 4889


🏁 Script executed:

# Read the specific lines in connection-adapters.ts to understand the context
sed -n '310,335p' packages/typescript/ai-client/src/connection-adapters.ts

Repository: TanStack/ai

Length of output: 941


🏁 Script executed:

# Check the send() method signature to see if it declares union type
rg -nP -B15 'async send\(' packages/typescript/ai-client/src/connection-adapters.ts | head -40

Repository: TanStack/ai

Length of output: 681


🏁 Script executed:

# Check line 429 for the second cast
sed -n '425,435p' packages/typescript/ai-client/src/connection-adapters.ts

Repository: TanStack/ai

Length of output: 498


🏁 Script executed:

# Look for UIMessage and ModelMessage type definitions
rg -nP 'type\s+(UIMessage|ModelMessage)' packages/typescript/ai/src

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

# Get full send() method signature with types
sed -n '195,215p' packages/typescript/ai-client/src/connection-adapters.ts

Repository: TanStack/ai

Length of output: 660


🏁 Script executed:

# Search for type definitions with different patterns
rg -nP '(export\s+(type|interface)\s+(UIMessage|ModelMessage))|(UIMessage|ModelMessage)\s*=' packages/typescript/ai/src | head -20

Repository: TanStack/ai

Length of output: 635


🏁 Script executed:

# Find the fetchHttpStream function and line 429 area
rg -nP 'fetchHttpStream' packages/typescript/ai-client/src/connection-adapters.ts

Repository: TanStack/ai

Length of output: 416


🏁 Script executed:

# Check what's around the second occurrence (if it exists)
sed -n '420,440p' packages/typescript/ai-client/src/connection-adapters.ts

Repository: TanStack/ai

Length of output: 838


🏁 Script executed:

# Get full fetchHttpStream function signature
sed -n '410,425p' packages/typescript/ai-client/src/connection-adapters.ts

Repository: TanStack/ai

Length of output: 707


🏁 Script executed:

# Check what interface/type contains the send() method
rg -nP -B30 'async send\(' packages/typescript/ai-client/src/connection-adapters.ts | grep -E '(interface|type|function|return|:.*\{)' | head -20

Repository: TanStack/ai

Length of output: 246


🏁 Script executed:

# Read the UIMessage and ModelMessage interface definitions
sed -n '289,360p' packages/typescript/ai/src/types.ts

Repository: TanStack/ai

Length of output: 1653


🏁 Script executed:

# Find ConnectConnectionAdapter interface definition
rg -nP 'interface\s+ConnectConnectionAdapter|type\s+ConnectConnectionAdapter' packages/typescript/ai-client/src

Repository: TanStack/ai

Length of output: 238


🏁 Script executed:

# Also search in types or definitions files
rg -nP 'ConnectConnectionAdapter' packages/typescript/ai-client/src --type ts

Repository: TanStack/ai

Length of output: 1984


🏁 Script executed:

# Read the ConnectConnectionAdapter interface definition
sed -n '89,125p' packages/typescript/ai-client/src/connection-adapters.ts

Repository: TanStack/ai

Length of output: 984


🏁 Script executed:

# Read the full uiMessagesToWire implementation
sed -n '47,150p' packages/typescript/ai/src/utilities/ag-ui-wire.ts

Repository: TanStack/ai

Length of output: 2700


🏁 Script executed:

# Confirm the second occurrence at line 429
sed -n '415,440p' packages/typescript/ai-client/src/connection-adapters.ts

Repository: TanStack/ai

Length of output: 1062


🏁 Script executed:

# Search for all instances of the uiMessagesToWire cast pattern
rg -n 'uiMessagesToWire.*as\s+Array<UIMessage>' packages/typescript/ai-client/src

Repository: TanStack/ai

Length of output: 325


🏁 Script executed:

# Check other adapter functions (webSocketStream, sseStream, customAdapter) for same pattern
grep -n "export function" packages/typescript/ai-client/src/connection-adapters.ts

Repository: TanStack/ai

Length of output: 242


Update uiMessagesToWire to accept the declared union type instead of using an unsafe cast.

The ConnectConnectionAdapter.connect() and SubscribeConnectionAdapter.send() interfaces declare messages: Array<UIMessage> | Array<ModelMessage>, but implementations cast to Array<UIMessage> before calling uiMessagesToWire, which is typed to accept only Array<UIMessage>. While uiMessagesToWire is defensively implemented to handle both shapes (it safely accesses msg.parts with fallback to msg.content), the cast obscures the actual capability and violates type safety.

Change the uiMessagesToWire signature to accept Array<UIMessage | ModelMessage> instead of casting unsafely. This fixes both occurrences (lines 314 and 429).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/typescript/ai-client/src/connection-adapters.ts` around lines 313 -
329, Change the uiMessagesToWire signature to accept Array<UIMessage |
ModelMessage> (instead of only Array<UIMessage>) so callers no longer need
unsafe casts; update the function declaration for uiMessagesToWire and any
related type imports/exports, and adjust internal typing if necessary to
preserve existing defensive access (e.g., using msg.parts ?? msg.content) so
both ConnectConnectionAdapter.connect and SubscribeConnectionAdapter.send can
pass their messages: Array<UIMessage> | Array<ModelMessage> directly without
casting.

Comment thread packages/typescript/ai/docs/chat-architecture.md Outdated
AlemTuzlak added 3 commits May 5, 2026 18:20
Connection adapters now POST AG-UI RunAgentInput envelopes with input
data nested under forwardedProps. Update the image, tts, transcription,
and video route handlers (sse + http-stream) to read from
body.forwardedProps ?? body.data ?? body so they work for both the
AG-UI envelope (connection adapter path) and the existing
server-function fetcher path.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
testing/e2e/src/routes/api.tts.stream.ts (1)

13-13: ⚡ Quick win

Consider extracting the forwardedProps normalization into a shared utility.

The line const data = body.forwardedProps ?? body.data ?? body is duplicated verbatim across all 8 media route handlers. A small shared helper would centralize the fallback chain and make future protocol changes a single-file edit.

♻️ Proposed helper (e.g., in a shared lib/request-utils.ts)
+// lib/request-utils.ts
+export function resolveRequestData(body: Record<string, unknown>): unknown {
+  return body['forwardedProps'] ?? body['data'] ?? body
+}

Then in each route:

-const data = body.forwardedProps ?? body.data ?? body
+const data = resolveRequestData(body)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@testing/e2e/src/routes/api.tts.stream.ts` at line 13, Extract the duplicated
fallback logic "const data = body.forwardedProps ?? body.data ?? body" into a
shared helper (e.g., normalizeForwardedProps(body): any) located in a common
utils module, export it, and replace each media route handler's inline
expression with a call to normalizeForwardedProps(body); ensure the helper
accepts the request body, returns the resolved payload (forwardedProps > data >
body), and update imports in the handlers (e.g., api.tts.stream route and the
other 7 media handlers) to use the new function so future protocol changes are
centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@testing/e2e/src/routes/api.tts.stream.ts`:
- Line 13: Extract the duplicated fallback logic "const data =
body.forwardedProps ?? body.data ?? body" into a shared helper (e.g.,
normalizeForwardedProps(body): any) located in a common utils module, export it,
and replace each media route handler's inline expression with a call to
normalizeForwardedProps(body); ensure the helper accepts the request body,
returns the resolved payload (forwardedProps > data > body), and update imports
in the handlers (e.g., api.tts.stream route and the other 7 media handlers) to
use the new function so future protocol changes are centralized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0ef6a9b8-b763-40d5-8a0a-9004b928f028

📥 Commits

Reviewing files that changed from the base of the PR and between 8b1cdb6 and c359aba.

📒 Files selected for processing (9)
  • packages/typescript/ai-anthropic/src/adapters/text.ts
  • testing/e2e/src/routes/api.image.stream.ts
  • testing/e2e/src/routes/api.image.ts
  • testing/e2e/src/routes/api.transcription.stream.ts
  • testing/e2e/src/routes/api.transcription.ts
  • testing/e2e/src/routes/api.tts.stream.ts
  • testing/e2e/src/routes/api.tts.ts
  • testing/e2e/src/routes/api.video.stream.ts
  • testing/e2e/src/routes/api.video.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/typescript/ai-anthropic/src/adapters/text.ts

AlemTuzlak added 2 commits May 5, 2026 20:10
…s precedence

Fixes surfaced by full-PR review:

- chat-client: extract runId from RUN_ERROR via the AG-UI passthrough
  field, not just RUN_FINISHED. A per-run RUN_ERROR carrying a runId
  was falling into the no-runId branch and clearing every active run
  in the session, breaking concurrent multi-run state.

- connection-adapters: synthesized RUN_FINISHED and RUN_ERROR in the
  legacy connect-wrapper now carry the caller's threadId and runId
  from runContext instead of bogus 'connect-wrapper' / Date.now() ids,
  so client-side activeRunIds tracking matches.

- connection-adapters: reverse forwardedProps merge order so per-call
  data wins over the static fetchServerSentEvents/fetchHttpStream
  options.body, matching the documented "per-message body takes
  priority" contract.

- ts-react-chat example: validate provider against adapterConfig keys
  (was accepting any string and crashing inside the try block); use the
  client-supplied model for openrouter (was hardcoded to openai/gpt-5.1).
Make the AG-UI compliance migration fully non-breaking. Existing client
and server code keeps working; new canonical names are introduced
alongside deprecated aliases that route to the same internal slot.

Client (@tanstack/ai-client + framework wrappers):
- New `forwardedProps` option on `useChat` / `ChatClient` / `updateOptions`,
  preferred over the legacy `body` (now `@deprecated`). Both populate the
  same wire field.
- Connection adapters emit a `data` mirror of `forwardedProps` on the wire
  so server endpoints reading `body.data.X` keep working.
- Drop the auto-emitted `conversationId: this.uniqueId`. Threads are now
  identified by the AG-UI top-level `threadId` field.
- Svelte: new `updateForwardedProps` method; `updateBody` retained as
  deprecated alias.
- Framework `useChat` types automatically pick up `forwardedProps` via
  `Omit<ChatClientOptions, ...>`.

Server (@tanstack/ai):
- New `chatParamsFromRequest(req)` helper: reads `req.json()`, validates
  against AG-UI `RunAgentInputSchema`, throws a 400 `Response` on bad
  input (auto-handled by Remix-style frameworks).
- `chat()`: `conversationId` option is now a true deprecated alias of
  `threadId`. Both names route to `this.threadId` resolution; explicit
  `threadId` wins.
- `ChatMiddlewareContext`: canonical `threadId: string` plus deprecated
  `conversationId?` alias (always equals `threadId`). Internal callers
  switched to `ctx.threadId`.

Codemod:
- New `codemods/` workspace with a jscodeshift transform for the safe
  client-side renames: useChat/ChatClient body→forwardedProps,
  updateOptions body→forwardedProps, Svelte updateBody→updateForwardedProps,
  chat() conversationId→threadId. Each rename is import-source gated;
  conflicts (both legacy + canonical present) are left alone.
- Top-level `pnpm codemod:ag-ui-compliance` script.
- 6 vitest cases covering positive, negative (no-imports), and
  conflict-handling paths.

Docs:
- Migration guide rewritten with three deprecation bridges, tier-based
  server upgrade path, conversationId→threadId section, and codemod
  invocation.
- API references for ai, ai-client, ai-react, ai-vue, ai-solid,
  ai-svelte, ai-preact updated with `forwardedProps`/`threadId` options
  and deprecation notes.
- Quick-starts (React, Vue, Svelte) drop the now-redundant
  `conversationId` plumbing.
- Middleware doc updated to document `ctx.threadId` and the alias.
- runtime-adapter-switching and multimodal-content code examples
  switched from `body.data.X` / `body: {...}` to `forwardedProps`.
AlemTuzlak added 2 commits May 8, 2026 16:46
Bucket (a) findings from the 7-agent CR round:

- mergeAgentTools: return Array<Tool> instead of Record<string, Tool>.
  chat({ tools }) takes an array — the documented usage was broken at
  the type level. Tests updated to reflect the array shape.

- chatParamsFromRequest: 400 Response no longer echoes the raw Zod
  validation paths (which can include user-payload fragments). Returns
  a fixed migration-pointing message; the underlying parser/AGUI error
  is attached as `cause` for server-side logs.

- Migration guide: add a callout that servers reading
  `body.forwardedProps?.conversationId` will now get undefined,
  because the upgraded client no longer auto-emits it. The threadId
  fallback handles within-request correlation, but cross-request
  stable identifiers now require reading params.threadId.

- chat-params test: build a fresh Request for each call so the
  malformed-JSON path is actually exercised on both invocations
  (Request.json() consumes the body stream).

- codemod runner: introduce codemods/run.mjs which resolves user-
  supplied paths against INIT_CWD instead of the package directory
  pnpm switches into via --filter exec. The root pnpm script now
  delegates to this runner so `pnpm codemod:ag-ui-compliance "src/**"`
  works from repo root as documented.

- Workspace alignment: codemods/package.json now uses @types/node
  ^24.10.1 and vitest ^4.0.14 to match the root workspace, so sherif
  doesn't flag inconsistent versions.

Bucket (b) cleanup:
- data wire-mirror is now shallow-cloned so future mutation of either
  field can't corrupt the other.
- Codemod surfaces conflict-leave-alone cases via api.report so the
  user has actionable signal when both legacy and canonical keys
  coexist on the same call site.
…ace)

Bucket (a) findings from the 7-agent confirmation round:

- Codemod renameProperty: shorthand `useChat({ body })` was being
  rewritten to broken `{ forwardedProps }` referencing an undefined
  identifier. Now expanded to `{ forwardedProps: body }`. Added
  shorthand-body fixture + test.

- Codemod test harness: now captures `api.report` calls and the
  conflict-leave-alone test asserts the warning fires. Previously the
  test passed a no-op report stub, so a regression in the conflict-
  warning path would have gone undetected. Also added an explicit
  "no-reports for clean transforms" case.

- ChatClient.updateOptions: tracking body / forwardedProps in a single
  merged slot meant `updateOptions({ forwardedProps })` would silently
  wipe a previously-set body (and vice versa). Now stored separately
  and merged at send time; updateOptions replaces each slot
  independently. Added regression tests for both partial-update
  directions.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/api/ai-client.md (1)

176-188: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

The stream() example payload omits required RunAgentInput fields — clarify in comments which adapters are AG-UI-compliant.

The example shows only { messages, forwardedProps: data }, but servers using chatParamsFromRequestBody expect full RunAgentInput with threadId, runId, state, context, and tools. The stream() callback only receives (messages, data, signal) and cannot access ChatClient-managed fields like threadId and runId.

Built-in adapters (fetchServerSentEvents, fetchHttpStream) construct the complete RunAgentInput internally; stream() is an escape hatch for custom or legacy endpoints that don't require the full shape. Update the comment to clarify this:

📝 Suggested doc update
 const adapter = stream(async (messages, data, signal) => {
-  // `data` here carries the merged forwardedProps. The fetch-based
-  // adapters serialize it as the AG-UI `RunAgentInput.forwardedProps`
-  // field on the wire (with a backward-compat `data` mirror).
+  // NOTE: The `stream()` callback receives only `messages` and merged
+  // `forwardedProps` (as `data`). It does NOT receive `threadId`, `runId`,
+  // or other AG-UI `RunAgentInput` fields managed by `ChatClient`.
+  // Use `fetchServerSentEvents` or `fetchHttpStream` for fully AG-UI-compliant
+  // servers that use `chatParamsFromRequestBody`. Use `stream()` for custom or
+  // legacy endpoints that don't require the full `RunAgentInput` shape.
   const response = await fetch("/api/chat", {
     method: "POST",
     body: JSON.stringify({ messages, forwardedProps: data }),
     signal,
   });
   return processStream(response);
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/api/ai-client.md` around lines 176 - 188, Update the comment in the
stream(...) example to clarify that the stream adapter callback only receives
(messages, data, signal) and does NOT include ChatClient-managed RunAgentInput
fields (threadId, runId, state, context, tools); note that built-in adapters
fetchServerSentEvents and fetchHttpStream build the full RunAgentInput
internally (and servers using chatParamsFromRequestBody expect that shape), and
explain that stream is an escape hatch for custom/legacy endpoints that only
need {messages, forwardedProps} or that callers must construct the full
RunAgentInput themselves when talking to AG-UI-compliant servers.
♻️ Duplicate comments (3)
docs/getting-started/quick-start.md (1)

111-118: ⚠️ Potential issue | 🟠 Major

Verify that threadId and other AG-UI fields should be passed to chat().

Same issue as the TanStack Start example above: the code only extracts body.messages but the comment claims chat() reads the AG-UI threadId. Consider using the chatParamsFromRequestBody helper or explicitly passing threadId and runId to chat().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/getting-started/quick-start.md` around lines 111 - 118, The example only
passes body.messages to chat() even though the comment says chat() reads AG-UI
fields like threadId; update the call to include the AG-UI params by either
calling chat(chatParamsFromRequestBody(body, { adapter: openaiText("gpt-5.2"),
messages: body.messages })) or explicitly pass threadId and runId from body
(e.g., messages: body.messages, threadId: body.threadId, runId: body.runId) so
chat() receives the AG-UI correlation fields; locate the chat(...) invocation in
the snippet and add the missing parameters or use the chatParamsFromRequestBody
helper.
docs/getting-started/quick-start-svelte.md (1)

49-56: ⚠️ Potential issue | 🟠 Major

Verify that threadId and other AG-UI fields should be passed to chat().

The same issue applies here as in the React quick-start: the code parses the full body but only passes body.messages to chat(), while the comment claims chat() uses the AG-UI threadId for devtools correlation. Consider using the chatParamsFromRequestBody helper or explicitly passing threadId and runId from the body.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/getting-started/quick-start-svelte.md` around lines 49 - 56, The code
passes only body.messages into chat() (see chat({ adapter: openaiText('gpt-4o'),
messages: body.messages })) but the request body may include AG-UI fields like
threadId/runId needed for devtools correlation; update the call to include those
fields (either by calling chatParamsFromRequestBody(body) and spreading its
result into chat(), or by explicitly passing threadId and runId from body
alongside messages) so chat() receives the full set of chat params for correct
correlation.
packages/typescript/ai-client/src/chat-client.ts (1)

641-647: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Convert client tool inputSchema to JSON Schema before serializing to the wire

AnyClientTool.inputSchema typically holds a Standard Schema (e.g., Zod) per the toolDefinition() pattern, but RunAgentInput.tools[].parameters must be JSON Schema for foreign AG-UI servers to interpret. Passing the raw schema instance through will produce unusable parameter shapes for those servers. The same package already exports convertSchemaToJsonSchema and uses it in realtime-client.ts for this exact purpose.

♻️ Proposed fix

Add the import:

 import {
+  convertSchemaToJsonSchema,
   StreamProcessor,
   generateMessageId,
   normalizeToUIMessage,
 } from '@tanstack/ai'

Then convert at serialization time:

         clientTools: Array.from(this.clientToolsRef.current.values()).map(
           (t) => ({
             name: t.name,
             description: t.description,
-            parameters: t.inputSchema || { type: 'object' },
+            parameters:
+              (t.inputSchema && convertSchemaToJsonSchema(t.inputSchema)) || {
+                type: 'object',
+              },
           }),
         ),

As per coding guidelines, "Use Zod for schema validation and tool definition across the library" — Zod schemas must be converted to JSON Schema before being placed on the wire.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/typescript/ai-client/src/chat-client.ts` around lines 641 - 647, The
serialized client tools are sending raw AnyClientTool.inputSchema instances
(e.g., Zod) instead of JSON Schema; update the serialization in chat-client.ts
where clientTools: Array.from(this.clientToolsRef.current.values()).map(...) is
built to call the existing convertSchemaToJsonSchema on t.inputSchema (falling
back to { type: 'object' } when undefined) so RunAgentInput.tools[].parameters
is valid JSON Schema for AG-UI servers; import convertSchemaToJsonSchema (as
used in realtime-client.ts) and use it in that map before sending.
🧹 Nitpick comments (9)
docs/getting-started/quick-start.md (1)

275-277: ⚡ Quick win

Validate documentation links.

As per coding guidelines, verify that the documentation links are valid using the pnpm test:docs command. This ensures that references to ../tools/tools, ../tools/client-tools, and ../api/ai resolve correctly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/getting-started/quick-start.md` around lines 275 - 277, Run the
documentation link validator (pnpm test:docs) and confirm the three links listed
(../tools/tools, ../tools/client-tools, ../api/ai) resolve; if any fail, update
the corresponding link target in quick-start.md to the correct relative path or
canonical doc slug (replace the broken ../tools/tools, ../tools/client-tools, or
../api/ai entries), then re-run pnpm test:docs until all link checks pass.
docs/getting-started/quick-start-svelte.md (1)

163-165: ⚡ Quick win

Validate documentation links.

As per coding guidelines, verify that the documentation links are valid using the pnpm test:docs command to ensure references to ../tools/tools, ../adapters/openai, and ./quick-start resolve correctly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/getting-started/quick-start-svelte.md` around lines 163 - 165, Run the
docs link validation (pnpm test:docs) and fix any broken references in
quick-start-svelte.md: verify that the three links ../tools/tools,
../adapters/openai, and ./quick-start resolve to existing markdown pages and
update their targets to the correct filenames/paths (or add missing
files/redirects) so the test passes; re-run pnpm test:docs and commit the
corrected link targets.
packages/typescript/ai-client/src/chat-client.ts (1)

640-640: 💤 Low value

Reuse generateUniqueId('run') helper for consistency

The class already has a generateUniqueId(prefix) helper used for chat, thread, and stream IDs. Inlining a different format here (slice(2, 8) vs substring(7)) introduces an unnecessary divergence in ID shape and makes future format changes brittle.

♻️ Proposed change
       const runContext = {
         threadId: this.threadId,
-        runId: `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
+        runId: this.generateUniqueId('run'),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/typescript/ai-client/src/chat-client.ts` at line 640, Replace the
inline runId generation with the existing generateUniqueId helper to keep ID
format consistent: find the place creating runId
(`run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`) and call
generateUniqueId('run') instead; ensure any surrounding code that expects the
prior string still works with the helper output and remove the inline expression
so all IDs (chat/thread/stream/run) are produced by generateUniqueId.
codemods/tsconfig.json (1)

15-15: 💤 Low value

Optional: extend include glob to cover .tsx and .cts/.mts

Currently **/*.ts won't pick up future TSX-based transforms, runners, or tests outside __testfixtures__. Cheap to broaden now and avoids silent type-check gaps later.

♻️ Proposed change
-  "include": ["**/*.ts"],
+  "include": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@codemods/tsconfig.json` at line 15, The tsconfig "include" currently only
matches "**/*.ts" which omits .tsx, .cts and .mts files; update the "include"
array (the "include" property in codemods/tsconfig.json) to a broader glob such
as "**/*.{ts,tsx,cts,mts}" or add additional patterns so TypeScript will pick up
TSX-based transforms, ESM/CJS TS files and avoid silent type-check gaps.
packages/typescript/ai-solid/src/use-chat.ts (1)

42-85: 💤 Low value

createMemo second argument is the initial value, not a dependency array

In Solid, createMemo(fn, value?, options?) takes an optional initial value as the second argument; reactivity is driven by tracked signal reads inside fn. Passing [clientId] is React-style thinking — it sets the memo's initial value to a string[] and does not drive re-creation based on clientId. Since clientId is a plain const (line 23), not a reactive signal, and the memo function body contains no tracked reactive reads, this memo effectively runs once at mount. The comment "Only recreate when clientId changes" is misleading.

To align with Solid best practices, remove the second argument and clarify that reactivity comes from tracked sources only.

♻️ Suggested fix
-  }, [clientId])
+  })

And update the comment to reflect actual behavior:

-    // Only recreate when clientId changes
-    // Connection and other options are captured at creation time
+    // Connection and other options are captured at creation time;
+    // memo runs once since no tracked reactive sources are read inside.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/typescript/ai-solid/src/use-chat.ts` around lines 42 - 85, The
createMemo call is using a React-style dependency array ([clientId]) as its
second argument which in Solid is actually the initial value, so the memo runs
only once and the comment "Only recreate when clientId changes" is misleading;
remove the second argument from createMemo and either make clientId a tracked
signal or explicitly recreate the ChatClient in a reactive effect if you do want
it to update, and update the inline comment near createMemo to state that the
client is created once at mount (or mention the chosen reactive approach) and
that reactivity depends on tracked signals inside the memo (refer to the
createMemo call and the clientId constant to locate the code).
codemods/run.mjs (1)

45-50: ⚡ Quick win

Silent exit when jscodeshift binary isn't found.

If spawnSync cannot exec jscodeshift (e.g., ENOENT — binary not in PATH), result.status is null and result.error is set, but the current code just exits with 1 and no output. A user running the script outside of pnpm workspace context will see a bare non-zero exit with no diagnostic.

🛠 Proposed fix
 const result = spawnSync('jscodeshift', args, {
   cwd: here,
   stdio: 'inherit',
   shell: process.platform === 'win32',
 })
+if (result.error) {
+  console.error(`[codemod-runner] Failed to launch jscodeshift: ${result.error.message}`)
+  console.error('Ensure jscodeshift is installed: pnpm install')
+  process.exit(1)
+}
 process.exit(result.status ?? 1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@codemods/run.mjs` around lines 45 - 50, The code currently calls
spawnSync('jscodeshift', ...) and blindly exits with result.status ?? 1, which
yields a silent non-zero exit when the binary isn't found; update the logic
after the spawnSync call (the variable result in run.mjs) to check if
result.error is set (and result.status is null), and if so write a clear
diagnostic to stderr including result.error.code and result.error.message (and
mention 'jscodeshift' not found / PATH issue), then exit with a non-zero status;
otherwise preserve the existing exit behavior using result.status.
docs/api/ai.md (2)

197-224: ⚡ Quick win

Clarify which fields in the returned shape are optional, and align example with the “merge tools” pattern.

Two small doc-accuracy nits in the chatParamsFromRequest section:

  1. Line 222 documents the return shape as { messages, threadId, runId, parentRunId?, tools, forwardedProps, state, context }, marking only parentRunId as optional. Per AG-UI RunAgentInputSchema, tools, forwardedProps, state, and context are also optional/may default to empty — readers will assume each is always populated as written.
  2. The example on lines 207–211 hands serverTools directly to chat({ tools }) without merging in params.tools, which is the exact anti-pattern the new mergeAgentTools helper exists to prevent (and which the section just below recommends). Using mergeAgentTools(serverTools, params.tools) here would make the canonical happy-path consistent across the doc.
📝 Suggested adjustments
 export async function POST(req: Request) {
   const params = await chatParamsFromRequest(req);
   const stream = chat({
     adapter: openaiText("gpt-4o"),
     messages: params.messages,
-    tools: serverTools,
+    tools: mergeAgentTools(serverTools, params.tools),
   });
   return toServerSentEventsResponse(stream);
 }
-A promise resolving to `{ messages, threadId, runId, parentRunId?, tools, forwardedProps, state, context }`.
+A promise resolving to `{ messages, threadId, runId, parentRunId?, tools?, forwardedProps?, state?, context? }` (optional fields follow the AG-UI `RunAgentInputSchema` defaults).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/api/ai.md` around lines 197 - 224, Update the docs for
chatParamsFromRequest to mark all optional fields returned by
RunAgentInputSchema (parentRunId, tools, forwardedProps, state, context) as
optional/defaultable rather than required; adjust the return-shape line to
reflect e.g. { messages, threadId, runId, parentRunId?, tools?, forwardedProps?,
state?, context? } and mention defaults/empty values where appropriate; also
update the example to use mergeAgentTools(serverTools, params.tools)
(referencing mergeAgentTools, serverTools and params.tools) so the example
merges provided agent tools with server-side tools instead of replacing them.

230-238: ⚡ Quick win

Catch block accesses error.message without narrowing.

In TypeScript strict mode, catch (error) is typed as unknown (the default since TS 4.4). The example on line 236 dereferences error.message directly, which is a compile error users will hit when they copy-paste this snippet. Suggest narrowing to keep the doc copy-paste-clean.

📝 Proposed fix
 const body = await req.json();
 try {
   const params = await chatParamsFromRequestBody(body);
   // ...
 } catch (error) {
-  return new Response(error.message, { status: 400 });
+  const message = error instanceof Error ? error.message : "Invalid request body";
+  return new Response(message, { status: 400 });
 }

As per coding guidelines: "Use TypeScript 5.9.3 as the primary language for implementation".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/api/ai.md` around lines 230 - 238, The catch block in the example
dereferences error.message while TypeScript treats catch error as unknown;
update the catch to narrow the error before accessing .message (for example, use
`if (error instanceof Error) { return new Response(error.message, { status: 400
}) } else { return new Response(String(error), { status: 400 }) }`) so the
snippet around req.json() and chatParamsFromRequestBody(body) compiles under
strict TS; ensure you reference the same catch block and use instanceof Error or
String(error) conversion to produce a safe message.
codemods/ag-ui-compliance/transform.ts (1)

192-207: 💤 Low value

args.find(...ObjectExpression) matches any positional argument, not the first.

The doc comment on renameKeyOnCalls (line 173) says "first object-literal argument", but Array.prototype.find returns the first match in iteration order, which can be at any index. For TanStack’s callees this is fine in practice (the options bag is the leading argument), but if a user has a wrapper like useChat(deps, { body }) the rename still triggers — and conversely a non-options object passed as the first argument would be mutated.

Tightening to args[0]?.type === 'ObjectExpression' matches the documented intent and avoids surprising matches in wrapped patterns. Low priority, but cheap.

📝 Suggested fix
-      const args = path.node.arguments
-      const objArg = args.find(
-        (a): a is ObjectExpression => a.type === 'ObjectExpression',
-      )
-      if (!objArg) return
+      const firstArg = path.node.arguments[0]
+      if (!firstArg || firstArg.type !== 'ObjectExpression') return
+      const objArg = firstArg

(Apply the same change to the new ChatClient(...) block at lines 252–255.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@codemods/ag-ui-compliance/transform.ts` around lines 192 - 207, The current
implementation in renameKeyOnCalls uses args.find((a): a is ObjectExpression =>
a.type === 'ObjectExpression') which can match any positional argument; change
this to check only the first argument (e.g., if (args[0]?.type ===
'ObjectExpression') ...) so the function only renames the first object-literal
argument as the doc comment promises; update the code paths that call
renameProperty(objArg, oldKey, newKey) and stats updates accordingly and make
the identical change in the new ChatClient(...) handling block so both
call-sites only consider args[0] as an ObjectExpression.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@codemods/ag-ui-compliance/__testfixtures__/chat-conversation-id.output.ts`:
- Around line 4-12: The codemod output incorrectly reads threadId from
body.forwardedProps?.conversationId after renaming conversationId → threadId;
update the POST handler so chat(...) receives the top-level threadId (use
body.threadId) or use chatParamsFromRequestBody(body) and pass params.threadId,
or simply omit threadId to let the runtime generate it; change the reference in
the chat call (in the POST function) from body.forwardedProps?.conversationId to
one of the supported sources (body.threadId or params.threadId) and document the
limitation in the migration guide.

In `@codemods/ag-ui-compliance/README.md`:
- Around line 54-57: Update the fenced conflict-warning code block in the README
(the block containing "[ag-ui-compliance] path/to/file.tsx:42 — useChat({ body
}): both legacy and canonical keys are already present; left alone. Merge by
hand.") to include a language specifier (use "text") after the opening triple
backticks so the block becomes ```text ... ``` and satisfies the MD040
markdownlint rule.

In `@codemods/ag-ui-compliance/transform.test.ts`:
- Around line 1-5: Reorder and alphabetize the import statements to satisfy
ESLint import-order and sorting rules: move all node: builtin imports (e.g., the
import that includes resolve and dirname from 'node:path' and fileURLToPath from
'node:url' and readFileSync from 'node:fs') before third-party imports, then
place the third-party import from 'vitest' after them; also alphabetically sort
all named imports so change "import { describe, it, expect } from 'vitest'" to
ordered names "describe, expect, it" and change "import { resolve, dirname }
from 'node:path'" to "dirname, resolve" (leave jscodeshift import as-is). Ensure
the final import block has node: imports first (with alphabetized specifiers),
then third-party imports.

In `@codemods/ag-ui-compliance/transform.ts`:
- Around line 295-304: The rename currently triggers whenever hasSvelte is true
(any import from `@tanstack/ai-svelte`) and blindly replaces all
MemberExpression.property named "updateBody"; change the guard to require that
createChat is actually imported (check importedNames.has('createChat') like
other renames) before walking MemberExpression, so only files that import
createChat get .updateBody → .updateForwardedProps renames (optionally you can
further tighten by resolving the receiver binding of the MemberExpression to
ensure it originates from a createChat return value, but at minimum replace the
hasSvelte check with an importedNames.has('createChat') check to avoid unrelated
rewrites).
- Around line 141-163: The shorthand expansion mutates existing Identifier nodes
and can accidentally change both key and value if the parser shares the node;
update the logic in renameProperty so you create fresh Identifier nodes instead
of mutating oldProp.key in-place: detect the shorthand via propAsAny.shorthand
and propAsAny.value, then use a fresh identifier (e.g. via j.identifier(...) or
by cloning the value) for the value/key rather than reusing the existing node,
set propAsAny.shorthand = false, assign the new Identifier to
oldProp.key.name/new key slot and ensure oldProp.value continues to reference
the original Identifier node (or a copied node) so `{ body }` becomes `{
forwardedProps: body }` without sharing references; if renameProperty does not
yet accept j, add a j: JSCodeshift param to construct fresh nodes.

In `@codemods/package.json`:
- Line 13: The package.json entry for the types dependency uses an old semver
("@types/jscodeshift": "^0.12.0") that won't match the installed jscodeshift
17.x; update the constraint for "@types/jscodeshift" to a 17.x range (for
example "^17.0.0" or "^17.3.0") so the types package aligns with jscodeshift
(refer to the "@types/jscodeshift" key and the jscodeshift dependency) and then
run install to update the lockfile.

In `@docs/getting-started/quick-start.md`:
- Line 67: The OpenAI model identifier used in the adapter call (adapter:
openaiText("gpt-5.2")) is invalid; update the string to a current valid model
identifier such as "gpt-5.5" (or another supported model) so the openaiText
adapter uses a supported model; locate the adapter: openaiText(...) line and
replace "gpt-5.2" with the chosen valid model name.
- Around line 61-68: Replace the manual extraction of body.messages and calling
chat(...) with using the chatParamsFromRequestBody helper to parse the full
RunAgentInput and then pass its fields into chat(); specifically, call
chatParamsFromRequestBody(await request.json()) to get params, then call chat({
adapter: openaiText(...), messages: params.messages, threadId: params.threadId,
runId: params.runId, parentRunId: params.parentRunId, tools:
mergeAgentTools(serverTools, params.tools) }); make this change for both
examples where chat(...) is invoked so AG-UI correlation (threadId/runId/etc.)
and tool merging are preserved.

In `@packages/typescript/ai/tests/chat-params.test.ts`:
- Line 1: The imports are out of order causing a sort-imports ESLint error;
update the import statement in the test file so that named imports are sorted
alphabetically (place expect before it) — i.e., adjust the import from "vitest"
in the file that contains the test (look for the top-level import statement
importing describe, it, expect) so the names are reordered to describe, expect,
it (or alphabetically as required) to satisfy the linter.

---

Outside diff comments:
In `@docs/api/ai-client.md`:
- Around line 176-188: Update the comment in the stream(...) example to clarify
that the stream adapter callback only receives (messages, data, signal) and does
NOT include ChatClient-managed RunAgentInput fields (threadId, runId, state,
context, tools); note that built-in adapters fetchServerSentEvents and
fetchHttpStream build the full RunAgentInput internally (and servers using
chatParamsFromRequestBody expect that shape), and explain that stream is an
escape hatch for custom/legacy endpoints that only need {messages,
forwardedProps} or that callers must construct the full RunAgentInput themselves
when talking to AG-UI-compliant servers.

---

Duplicate comments:
In `@docs/getting-started/quick-start-svelte.md`:
- Around line 49-56: The code passes only body.messages into chat() (see chat({
adapter: openaiText('gpt-4o'), messages: body.messages })) but the request body
may include AG-UI fields like threadId/runId needed for devtools correlation;
update the call to include those fields (either by calling
chatParamsFromRequestBody(body) and spreading its result into chat(), or by
explicitly passing threadId and runId from body alongside messages) so chat()
receives the full set of chat params for correct correlation.

In `@docs/getting-started/quick-start.md`:
- Around line 111-118: The example only passes body.messages to chat() even
though the comment says chat() reads AG-UI fields like threadId; update the call
to include the AG-UI params by either calling
chat(chatParamsFromRequestBody(body, { adapter: openaiText("gpt-5.2"), messages:
body.messages })) or explicitly pass threadId and runId from body (e.g.,
messages: body.messages, threadId: body.threadId, runId: body.runId) so chat()
receives the AG-UI correlation fields; locate the chat(...) invocation in the
snippet and add the missing parameters or use the chatParamsFromRequestBody
helper.

In `@packages/typescript/ai-client/src/chat-client.ts`:
- Around line 641-647: The serialized client tools are sending raw
AnyClientTool.inputSchema instances (e.g., Zod) instead of JSON Schema; update
the serialization in chat-client.ts where clientTools:
Array.from(this.clientToolsRef.current.values()).map(...) is built to call the
existing convertSchemaToJsonSchema on t.inputSchema (falling back to { type:
'object' } when undefined) so RunAgentInput.tools[].parameters is valid JSON
Schema for AG-UI servers; import convertSchemaToJsonSchema (as used in
realtime-client.ts) and use it in that map before sending.

---

Nitpick comments:
In `@codemods/ag-ui-compliance/transform.ts`:
- Around line 192-207: The current implementation in renameKeyOnCalls uses
args.find((a): a is ObjectExpression => a.type === 'ObjectExpression') which can
match any positional argument; change this to check only the first argument
(e.g., if (args[0]?.type === 'ObjectExpression') ...) so the function only
renames the first object-literal argument as the doc comment promises; update
the code paths that call renameProperty(objArg, oldKey, newKey) and stats
updates accordingly and make the identical change in the new ChatClient(...)
handling block so both call-sites only consider args[0] as an ObjectExpression.

In `@codemods/run.mjs`:
- Around line 45-50: The code currently calls spawnSync('jscodeshift', ...) and
blindly exits with result.status ?? 1, which yields a silent non-zero exit when
the binary isn't found; update the logic after the spawnSync call (the variable
result in run.mjs) to check if result.error is set (and result.status is null),
and if so write a clear diagnostic to stderr including result.error.code and
result.error.message (and mention 'jscodeshift' not found / PATH issue), then
exit with a non-zero status; otherwise preserve the existing exit behavior using
result.status.

In `@codemods/tsconfig.json`:
- Line 15: The tsconfig "include" currently only matches "**/*.ts" which omits
.tsx, .cts and .mts files; update the "include" array (the "include" property in
codemods/tsconfig.json) to a broader glob such as "**/*.{ts,tsx,cts,mts}" or add
additional patterns so TypeScript will pick up TSX-based transforms, ESM/CJS TS
files and avoid silent type-check gaps.

In `@docs/api/ai.md`:
- Around line 197-224: Update the docs for chatParamsFromRequest to mark all
optional fields returned by RunAgentInputSchema (parentRunId, tools,
forwardedProps, state, context) as optional/defaultable rather than required;
adjust the return-shape line to reflect e.g. { messages, threadId, runId,
parentRunId?, tools?, forwardedProps?, state?, context? } and mention
defaults/empty values where appropriate; also update the example to use
mergeAgentTools(serverTools, params.tools) (referencing mergeAgentTools,
serverTools and params.tools) so the example merges provided agent tools with
server-side tools instead of replacing them.
- Around line 230-238: The catch block in the example dereferences error.message
while TypeScript treats catch error as unknown; update the catch to narrow the
error before accessing .message (for example, use `if (error instanceof Error) {
return new Response(error.message, { status: 400 }) } else { return new
Response(String(error), { status: 400 }) }`) so the snippet around req.json()
and chatParamsFromRequestBody(body) compiles under strict TS; ensure you
reference the same catch block and use instanceof Error or String(error)
conversion to produce a safe message.

In `@docs/getting-started/quick-start-svelte.md`:
- Around line 163-165: Run the docs link validation (pnpm test:docs) and fix any
broken references in quick-start-svelte.md: verify that the three links
../tools/tools, ../adapters/openai, and ./quick-start resolve to existing
markdown pages and update their targets to the correct filenames/paths (or add
missing files/redirects) so the test passes; re-run pnpm test:docs and commit
the corrected link targets.

In `@docs/getting-started/quick-start.md`:
- Around line 275-277: Run the documentation link validator (pnpm test:docs) and
confirm the three links listed (../tools/tools, ../tools/client-tools,
../api/ai) resolve; if any fail, update the corresponding link target in
quick-start.md to the correct relative path or canonical doc slug (replace the
broken ../tools/tools, ../tools/client-tools, or ../api/ai entries), then re-run
pnpm test:docs until all link checks pass.

In `@packages/typescript/ai-client/src/chat-client.ts`:
- Line 640: Replace the inline runId generation with the existing
generateUniqueId helper to keep ID format consistent: find the place creating
runId (`run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`) and call
generateUniqueId('run') instead; ensure any surrounding code that expects the
prior string still works with the helper output and remove the inline expression
so all IDs (chat/thread/stream/run) are produced by generateUniqueId.

In `@packages/typescript/ai-solid/src/use-chat.ts`:
- Around line 42-85: The createMemo call is using a React-style dependency array
([clientId]) as its second argument which in Solid is actually the initial
value, so the memo runs only once and the comment "Only recreate when clientId
changes" is misleading; remove the second argument from createMemo and either
make clientId a tracked signal or explicitly recreate the ChatClient in a
reactive effect if you do want it to update, and update the inline comment near
createMemo to state that the client is created once at mount (or mention the
chosen reactive approach) and that reactivity depends on tracked signals inside
the memo (refer to the createMemo call and the clientId constant to locate the
code).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cc17b099-e38c-473b-a866-f3169e1ae947

📥 Commits

Reviewing files that changed from the base of the PR and between e055a3d and c8231fa.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (58)
  • codemods/README.md
  • codemods/ag-ui-compliance/README.md
  • codemods/ag-ui-compliance/__testfixtures__/chat-client-body.input.ts
  • codemods/ag-ui-compliance/__testfixtures__/chat-client-body.output.ts
  • codemods/ag-ui-compliance/__testfixtures__/chat-conversation-id.input.ts
  • codemods/ag-ui-compliance/__testfixtures__/chat-conversation-id.output.ts
  • codemods/ag-ui-compliance/__testfixtures__/conflict-leave-alone.input.ts
  • codemods/ag-ui-compliance/__testfixtures__/conflict-leave-alone.output.ts
  • codemods/ag-ui-compliance/__testfixtures__/no-imports.input.ts
  • codemods/ag-ui-compliance/__testfixtures__/no-imports.output.ts
  • codemods/ag-ui-compliance/__testfixtures__/shorthand-body.input.tsx
  • codemods/ag-ui-compliance/__testfixtures__/shorthand-body.output.tsx
  • codemods/ag-ui-compliance/__testfixtures__/svelte-update-body.input.ts
  • codemods/ag-ui-compliance/__testfixtures__/svelte-update-body.output.ts
  • codemods/ag-ui-compliance/__testfixtures__/use-chat-body.input.tsx
  • codemods/ag-ui-compliance/__testfixtures__/use-chat-body.output.tsx
  • codemods/ag-ui-compliance/transform.test.ts
  • codemods/ag-ui-compliance/transform.ts
  • codemods/package.json
  • codemods/run.mjs
  • codemods/tsconfig.json
  • docs/advanced/middleware.md
  • docs/advanced/multimodal-content.md
  • docs/advanced/runtime-adapter-switching.md
  • docs/api/ai-client.md
  • docs/api/ai-preact.md
  • docs/api/ai-react.md
  • docs/api/ai-solid.md
  • docs/api/ai-svelte.md
  • docs/api/ai-vue.md
  • docs/api/ai.md
  • docs/getting-started/quick-start-svelte.md
  • docs/getting-started/quick-start-vue.md
  • docs/getting-started/quick-start.md
  • docs/migration/ag-ui-compliance.md
  • package.json
  • packages/typescript/ai-client/src/chat-client.ts
  • packages/typescript/ai-client/src/connection-adapters.ts
  • packages/typescript/ai-client/src/types.ts
  • packages/typescript/ai-client/tests/chat-client.test.ts
  • packages/typescript/ai-client/tests/connection-adapters.test.ts
  • packages/typescript/ai-preact/src/use-chat.ts
  • packages/typescript/ai-react/src/use-chat.ts
  • packages/typescript/ai-solid/README.md
  • packages/typescript/ai-solid/src/use-chat.ts
  • packages/typescript/ai-svelte/src/create-chat.svelte.ts
  • packages/typescript/ai-svelte/src/types.ts
  • packages/typescript/ai-vue/src/use-chat.ts
  • packages/typescript/ai/skills/ai-core/ag-ui-protocol/SKILL.md
  • packages/typescript/ai/src/activities/chat/index.ts
  • packages/typescript/ai/src/activities/chat/middleware/compose.ts
  • packages/typescript/ai/src/activities/chat/middleware/types.ts
  • packages/typescript/ai/src/index.ts
  • packages/typescript/ai/src/types.ts
  • packages/typescript/ai/src/utilities/chat-params.ts
  • packages/typescript/ai/tests/chat-params.test.ts
  • packages/typescript/ai/tests/middleware.test.ts
  • pnpm-workspace.yaml
✅ Files skipped from review due to trivial changes (22)
  • pnpm-workspace.yaml
  • codemods/ag-ui-compliance/testfixtures/conflict-leave-alone.input.ts
  • package.json
  • codemods/ag-ui-compliance/testfixtures/shorthand-body.input.tsx
  • codemods/ag-ui-compliance/testfixtures/svelte-update-body.output.ts
  • codemods/ag-ui-compliance/testfixtures/use-chat-body.output.tsx
  • docs/api/ai-preact.md
  • docs/advanced/middleware.md
  • codemods/README.md
  • docs/api/ai-vue.md
  • docs/advanced/multimodal-content.md
  • codemods/ag-ui-compliance/testfixtures/chat-client-body.input.ts
  • docs/api/ai-react.md
  • codemods/ag-ui-compliance/testfixtures/no-imports.output.ts
  • codemods/ag-ui-compliance/testfixtures/svelte-update-body.input.ts
  • codemods/ag-ui-compliance/testfixtures/conflict-leave-alone.output.ts
  • docs/advanced/runtime-adapter-switching.md
  • docs/getting-started/quick-start-vue.md
  • codemods/ag-ui-compliance/testfixtures/shorthand-body.output.tsx
  • packages/typescript/ai/src/activities/chat/middleware/compose.ts
  • packages/typescript/ai-solid/README.md
  • packages/typescript/ai/skills/ai-core/ag-ui-protocol/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/typescript/ai/src/index.ts
  • packages/typescript/ai/src/types.ts
  • packages/typescript/ai-client/src/types.ts
  • packages/typescript/ai/src/activities/chat/index.ts
  • packages/typescript/ai-client/tests/connection-adapters.test.ts
  • packages/typescript/ai-client/src/connection-adapters.ts

Comment thread codemods/ag-ui-compliance/README.md Outdated
Comment thread codemods/ag-ui-compliance/transform.test.ts Outdated
Comment on lines +141 to +163
if (oldProp.key.type === 'Identifier') {
// For shorthand `{ body }`, the AST stores `key === value === Identifier('body')`
// (or two equal-named Identifier nodes plus `shorthand: true`). Mutating
// only the key would leave the printer emitting `{ forwardedProps }`,
// which silently references an undefined identifier in the user's
// scope. Expand to long form so the original `body` reference survives:
// `{ body }` → `{ forwardedProps: body }`.
const propAsAny = oldProp as unknown as {
shorthand?: boolean
value?: { type?: string; name?: string }
}
if (
propAsAny.shorthand &&
propAsAny.value?.type === 'Identifier' &&
propAsAny.value.name === oldName
) {
// Leave value pointing at the original identifier; only flip
// `shorthand` off and rename the key.
propAsAny.shorthand = false
}
oldProp.key.name = newName
return 'renamed'
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm the fixtures actually exercise shorthand `{ body }` and assert
# the resulting value identifier is preserved (not collapsed to forwardedProps).
fd -t f 'transform.test.ts' codemods
fd -t d 'fixtures' codemods
rg -nP -C2 '\{\s*body\s*\}' codemods

Repository: TanStack/ai

Length of output: 5011


🏁 Script executed:

#!/bin/bash
# Examine the test file structure and fixtures
cat -n codemods/ag-ui-compliance/transform.test.ts | head -100

Repository: TanStack/ai

Length of output: 4013


🏁 Script executed:

#!/bin/bash
# Look at available fixtures
ls -la codemods/ag-ui-compliance/__testfixtures__/

Repository: TanStack/ai

Length of output: 1210


🏁 Script executed:

#!/bin/bash
# Check the fixture for use-chat-body transformation
cat codemods/ag-ui-compliance/__testfixtures__/use-chat-body.input.tsx
cat codemods/ag-ui-compliance/__testfixtures__/use-chat-body.output.tsx

Repository: TanStack/ai

Length of output: 627


🏁 Script executed:

#!/bin/bash
# Examine the shorthand-body fixture which is specifically about preserving identifier reference
cat codemods/ag-ui-compliance/__testfixtures__/shorthand-body.input.tsx
echo "---OUTPUT---"
cat codemods/ag-ui-compliance/__testfixtures__/shorthand-body.output.tsx

Repository: TanStack/ai

Length of output: 1088


🏁 Script executed:

#!/bin/bash
# Let's look at the actual implementation in transform.ts around lines 141-163
cat -n codemods/ag-ui-compliance/transform.ts | sed -n '130,175p'

Repository: TanStack/ai

Length of output: 1952


🏁 Script executed:

#!/bin/bash
# Let's also check how jscodeshift babel parser represents shorthand properties
# by examining the comments and understanding the AST shape described
cat -n codemods/ag-ui-compliance/transform.ts | sed -n '140,170p'

Repository: TanStack/ai

Length of output: 1358


🏁 Script executed:

#!/bin/bash
# Verify the test actually runs and passes by checking test structure
# and that all referenced fixtures exist
wc -l codemods/ag-ui-compliance/__testfixtures__/shorthand-body.input.tsx
wc -l codemods/ag-ui-compliance/__testfixtures__/shorthand-body.output.tsx

Repository: TanStack/ai

Length of output: 198


🏁 Script executed:

#!/bin/bash
# Check if there's any documentation or code that ensures parser behavior
rg -n 'withParser.*tsx|parser.*babel' codemods/ag-ui-compliance/

Repository: TanStack/ai

Length of output: 144


Use fresh Identifier nodes for shorthand property expansion to eliminate parser-dependent behavior.

The current implementation at line 159–161 handles shorthand properties by setting shorthand = false and mutating oldProp.key.name = newName. The code comment correctly acknowledges that some AST representations use a shared node reference (key === value === Identifier('body')), but the implementation does not guard against this case. If a parser produces a shared reference, mutating the key automatically mutates the value, collapsing { forwardedProps: body } into { forwardedProps: forwardedProps } — the exact failure mode the comment warns about.

A test fixture (shorthand-body) exists and currently passes because the default babel parser (via jscodeshift.withParser('tsx')) produces separate Identifier nodes. However, the code is fragile and depends on parser internals. The safe fix is to construct fresh nodes rather than rely on implementation details:

Suggested fix — assign fresh Identifier nodes
-      // Leave value pointing at the original identifier; only flip
-      // `shorthand` off and rename the key.
-      propAsAny.shorthand = false
+      // Replace both key and value with fresh Identifier nodes so we
+      // can't accidentally mutate a shared reference (some parsers
+      // emit shorthand props with key === value).
+      propAsAny.shorthand = false
+      ;(oldProp as unknown as { value: unknown }).value = {
+        type: 'Identifier',
+        name: oldName,
+      }
+      ;(oldProp as unknown as { key: unknown }).key = {
+        type: 'Identifier',
+        name: newName,
+      }
+      return 'renamed'
     }
     oldProp.key.name = newName
     return 'renamed'

(or, more idiomatically, take j: JSCodeshift into renameProperty and use j.identifier(...) for both nodes.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@codemods/ag-ui-compliance/transform.ts` around lines 141 - 163, The shorthand
expansion mutates existing Identifier nodes and can accidentally change both key
and value if the parser shares the node; update the logic in renameProperty so
you create fresh Identifier nodes instead of mutating oldProp.key in-place:
detect the shorthand via propAsAny.shorthand and propAsAny.value, then use a
fresh identifier (e.g. via j.identifier(...) or by cloning the value) for the
value/key rather than reusing the existing node, set propAsAny.shorthand =
false, assign the new Identifier to oldProp.key.name/new key slot and ensure
oldProp.value continues to reference the original Identifier node (or a copied
node) so `{ body }` becomes `{ forwardedProps: body }` without sharing
references; if renameProperty does not yet accept j, add a j: JSCodeshift param
to construct fresh nodes.

Comment thread codemods/ag-ui-compliance/transform.ts
Comment thread codemods/package.json Outdated
Comment on lines +61 to +68
const body = await request.json();

try {
// Create a streaming chat response
// Create a streaming chat response. `chat()` reads the AG-UI
// `threadId` for devtools correlation when available.
const stream = chat({
adapter: openaiText("gpt-5.2"),
messages,
conversationId,
messages: body.messages,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the exported chatParamsFromRequestBody helper and chat() signature

# Search for chatParamsFromRequestBody export and implementation
echo "=== chatParamsFromRequestBody helper ==="
rg -nP --type=ts -A10 'export.*chatParamsFromRequestBody'

# Search for chat function signature to see threadId/runId parameters
echo -e "\n=== chat() function signature ==="
rg -nP --type=ts -A15 'export (function|const) chat[^a-zA-Z]'

# Look for usage examples of chatParamsFromRequestBody in the codebase
echo -e "\n=== chatParamsFromRequestBody usage examples ==="
rg -nP --type=ts -C3 'chatParamsFromRequestBody\('

Repository: TanStack/ai

Length of output: 12635


🏁 Script executed:

# Find the TextActivityOptions type definition
rg -nP --type=ts -A30 'type TextActivityOptions' packages/typescript/ai/src/activities/chat

Repository: TanStack/ai

Length of output: 37


🏁 Script executed:

# Also check if chat() is documented with threadId/runId parameters
rg -nP --type=ts -B5 -A20 'threadId.*runId|parentRunId' packages/typescript/ai/src/activities/chat/index.ts | head -60

Repository: TanStack/ai

Length of output: 2364


🏁 Script executed:

# Check how chat() is actually called in the test files with params from chatParamsFromRequestBody
rg -nP --type=ts -A10 'params = await chatParamsFromRequestBody' testing/e2e/src/routes/api.chat.ts

Repository: TanStack/ai

Length of output: 510


🏁 Script executed:

# Check the actual quick-start.md file to see the comment in question
cat docs/getting-started/quick-start.md | sed -n '60,70p'

Repository: TanStack/ai

Length of output: 379


🏁 Script executed:

# Also check the Next.js example section mentioned
cat docs/getting-started/quick-start.md | sed -n '110,120p'

Repository: TanStack/ai

Length of output: 331


🏁 Script executed:

# Look at the full context of both examples to understand intent
cat docs/getting-started/quick-start.md | sed -n '44,90p'

Repository: TanStack/ai

Length of output: 1313


Use chatParamsFromRequestBody helper and explicitly pass AG-UI correlation parameters to chat().

The comment states that chat() reads the AG-UI threadId "when available," but chat() doesn't passively read threadId—it auto-generates one if not provided. The code manually extracts only body.messages, ignoring threadId and runId, which are needed for devtools correlation with AG-UI.

All other examples in the codebase (testing/, examples/) use the chatParamsFromRequestBody helper which parses and validates the full RunAgentInput (messages, threadId, runId, parentRunId, tools, context, etc.). The quick-start.md examples should follow the same pattern:

const params = await chatParamsFromRequestBody(await request.json());
const stream = chat({
  adapter: openaiText("gpt-4o"),
  messages: params.messages,
  threadId: params.threadId,
  runId: params.runId,
  parentRunId: params.parentRunId,
  tools: mergeAgentTools(serverTools, params.tools),
});

This applies to both the TanStack Start (lines 61–68) and Next.js (lines 111–118) examples.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/getting-started/quick-start.md` around lines 61 - 68, Replace the manual
extraction of body.messages and calling chat(...) with using the
chatParamsFromRequestBody helper to parse the full RunAgentInput and then pass
its fields into chat(); specifically, call chatParamsFromRequestBody(await
request.json()) to get params, then call chat({ adapter: openaiText(...),
messages: params.messages, threadId: params.threadId, runId: params.runId,
parentRunId: params.parentRunId, tools: mergeAgentTools(serverTools,
params.tools) }); make this change for both examples where chat(...) is invoked
so AG-UI correlation (threadId/runId/etc.) and tool merging are preserved.

// Create a streaming chat response. `chat()` reads the AG-UI
// `threadId` for devtools correlation when available.
const stream = chat({
adapter: openaiText("gpt-5.2"),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the latest OpenAI GPT model version available as of May 2026?

💡 Result:

The latest OpenAI GPT model version available as of May 8, 2026, is GPT-5.5. Official OpenAI API documentation lists gpt-5.5 as the flagship frontier model for complex reasoning and coding [1][2][3]. It was released on April 23, 2026 [4][5][6], with API access following on April 24 [4][7], and GPT-5.5 Instant as the new default for ChatGPT on May 5 [8][9]. Model catalogs confirm GPT-5.5 and GPT-5.5 pro as the top models, ahead of GPT-5.4 variants [3]. No sources indicate a later GPT-5.6 or higher version released by May 8, 2026 [all results].

Citations:


Replace "gpt-5.2" with a valid OpenAI model identifier.

The model identifier "gpt-5.2" does not exist in the OpenAI API. As of May 2026, the latest available model is GPT-5.5 (released April 23, 2026). Use "gpt-5.5" or another valid model identifier instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/getting-started/quick-start.md` at line 67, The OpenAI model identifier
used in the adapter call (adapter: openaiText("gpt-5.2")) is invalid; update the
string to a current valid model identifier such as "gpt-5.5" (or another
supported model) so the openaiText adapter uses a supported model; locate the
adapter: openaiText(...) line and replace "gpt-5.2" with the chosen valid model
name.

Comment thread packages/typescript/ai/tests/chat-params.test.ts Outdated
AlemTuzlak and others added 6 commits May 8, 2026 18:13
Critical
- Codemod fixture for `chat({ conversationId })`: read from
  `body.threadId` (the AG-UI top-level wire field) instead of the
  now-undefined `body.forwardedProps?.conversationId`. Document the
  value-source limitation in the codemod README.

Major
- chat-client.ts: convert each client tool's `inputSchema` to JSON
  Schema via `convertSchemaToJsonSchema` before sending on the wire.
  `inputSchema` is a Standard Schema instance (Zod, ArkType, Valibot,
  etc.); foreign AG-UI servers expect JSON Schema in
  `RunAgentInput.tools[].parameters`. Mirrors the existing realtime-
  client.ts pattern.
- Codemod: narrow the Svelte `updateBody` rename gate from "any import
  from `@tanstack/ai-svelte`" to "imports `createChat`". A file that
  imports types or other helpers from the package but uses an unrelated
  `.updateBody(...)` from elsewhere will no longer be falsely rewritten.

Minor
- ESLint sort-imports: alphabetize named imports in
  `transform.test.ts` and `chat-params.test.ts`.
- `codemods/package.json`: bump `@types/jscodeshift` to `^17.1.1` to
  match `jscodeshift@^17.1.1` (was `^0.12.0`, mismatched major).
- `chat-architecture.md`: fix relative link to the migration guide
  from the package's nested docs/ directory.
- Codemod README: add `text` language identifier to the conflict-
  warning code block to satisfy MD040.

Skipped
- "gpt-5.2 doesn't exist": false positive (CodeRabbit's web search
  hallucinated a 2026 release; the rest of the repo uses gpt-5.2).
- Tier-2 quick-start expansion: keeping the Tier-1 minimal
  `body.messages` example intentional. Tier-2 helper usage is
  documented in the migration guide.
- `uiMessagesToWire` signature widening: out-of-scope refactor across
  packages; documented for follow-up.
…gentTools call

mergeAgentTools changed from `Record<string, Tool>` to `Array<Tool>` in
the round-1 CR pass, but this example was still constructing a record
via Object.fromEntries(...). Pass the array directly and drop the now-
unused `Tool` type import.
Knip statically analyzes imports to find unused files, but the codemod
fixtures are loaded at test time via readFileSync(...) — knip can't see
those reads and was flagging all 14 fixtures as unused. Add
codemods/**/__testfixtures__/** to knip's ignore list.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant