{"slug": "tanstack-ai-now-fully-speaks-ag-ui", "title": "TanStack AI now fully speaks AG-UI", "summary": "TanStack AI has achieved full compliance with the AG-UI open protocol for agent-to-frontend communication, enabling bidirectional interoperability between any AG-UI client and server. The update makes @tanstack/ai-client requests use the standard RunAgentInput format, allowing TanStack-built endpoints to accept requests from any AG-UI compliant client and vice versa without breaking existing code. This closes the final gap in cross-vendor agent communication, joining CopilotKit, CrewAI, and LangGraph adapters in a shared wire format.", "body_md": "*by Alem Tuzlak on May 17, 2026.*\n\nHalf the protocol was already there.\n\nFor a while now, endpoints built with @tanstack/ai have emitted [AG-UI](https://ag-ui.com) events on the wire going out. The streaming side of the conversation (RUN_STARTED, tool-call events, run finish, errors) was already a compliant AG-UI event stream. The piece that was still proprietary was the *other* direction: the request body going from client to server. The TanStack client POSTed { messages, data }, not AG-UI's RunAgentInput.\n\nThat last half is what this release fixes. **TanStack AI is now fully AG-UI compliant in both directions.** Server-to-client events were AG-UI before. Client-to-server requests are AG-UI now. The round trip is done.\n\nThe same @tanstack/ai-client can hit any AG-UI server. Any AG-UI client can hit an endpoint built with @tanstack/ai, wherever you host it (TanStack Start, Next.js, Hono, raw Node, Bun, anywhere). And nothing about your existing code breaks.\n\nAG-UI is an open protocol for agent-to-frontend communication. It defines a single wire format, RunAgentInput, that carries the conversation, the tools, the thread and run IDs, and arbitrary forwarded properties. Servers that speak AG-UI can be addressed by any compliant client. Clients that emit AG-UI can talk to any compliant server.\n\nWith server-to-client AG-UI already in place, a @tanstack/ai endpoint could *stream* to a compliant client. But the client-to-server side was a one-way mirror: only the TanStack client could *send* requests that endpoint understood. The asymmetry meant true cross-vendor interop was still gated on rewriting your request layer.\n\nClosing that gap is what this release does. The whole ecosystem (CopilotKit, CrewAI, LangGraph adapters, and now TanStack AI) gets to share the same plumbing in both directions.\n\nBefore this release, @tanstack/ai-client POSTed:\n\n```\n{\n  \"messages\": [...],\n  \"data\": { ... }\n}\n{\n  \"messages\": [...],\n  \"data\": { ... }\n}\n```\n\nAfter:\n\n```\n{\n  \"threadId\": \"thread-7f2a\",\n  \"runId\": \"run-a91\",\n  \"state\": {},\n  \"messages\": [...],\n  \"tools\": [...],\n  \"context\": [],\n  \"forwardedProps\": { ... },\n  \"data\": { ... }\n}\n{\n  \"threadId\": \"thread-7f2a\",\n  \"runId\": \"run-a91\",\n  \"state\": {},\n  \"messages\": [...],\n  \"tools\": [...],\n  \"context\": [],\n  \"forwardedProps\": { ... },\n  \"data\": { ... }\n}\n```\n\nThe new envelope is the full AG-UI RunAgentInput. The old data field is still emitted as a mirror of forwardedProps so legacy servers reading body.data.X keep working unchanged. threadId persists per session, runId is fresh per send, and tools carries the client's clientTools declarations so the server can dispatch tool calls without a static registry.\n\nServer-to-client events haven't changed shape, because they were already AG-UI compliant. They just now carry the matching threadId and runId you sent in.\n\nThree new things to know about, all opt-in.\n\nThese were always part of the AG-UI event semantics on the way out. They're now first-class options on chat() and flow through every provider adapter into RUN_STARTED events for observability and run correlation.\n\n``` js\nimport { chat } from '@tanstack/ai'\nimport { openaiText } from '@tanstack/ai-openai/adapters'\n\nconst stream = chat({\n  adapter: openaiText('gpt-4o'),\n  threadId: 'thread-7f2a',\n  runId: 'run-a91',\n  messages: [...],\n})\njs\nimport { chat } from '@tanstack/ai'\nimport { openaiText } from '@tanstack/ai-openai/adapters'\n\nconst stream = chat({\n  adapter: openaiText('gpt-4o'),\n  threadId: 'thread-7f2a',\n  runId: 'run-a91',\n  messages: [...],\n})\n```\n\nIf you don't pass them, the runtime auto-generates a stable threadId per request and a fresh runId per call. Existing code that didn't know about them keeps working.\n\nA one-import helper that reads req.json(), validates the body against the AG-UI RunAgentInputSchema, and gives you a clean params object. On invalid input it throws a 400 Response that frameworks like TanStack Start, SolidStart, Remix, and React Router 7 return to the client automatically.\n\n``` js\nimport {\n  chat,\n  chatParamsFromRequest,\n  toServerSentEventsResponse,\n} from '@tanstack/ai'\nimport { openaiText } from '@tanstack/ai-openai/adapters'\n\nexport async function POST(req: Request) {\n  const params = await chatParamsFromRequest(req)\n  const stream = chat({\n    adapter: openaiText('gpt-4o'),\n    messages: params.messages,\n    threadId: params.threadId,\n    tools: serverTools,\n  })\n  return toServerSentEventsResponse(stream)\n}\njs\nimport {\n  chat,\n  chatParamsFromRequest,\n  toServerSentEventsResponse,\n} from '@tanstack/ai'\nimport { openaiText } from '@tanstack/ai-openai/adapters'\n\nexport async function POST(req: Request) {\n  const params = await chatParamsFromRequest(req)\n  const stream = chat({\n    adapter: openaiText('gpt-4o'),\n    messages: params.messages,\n    threadId: params.threadId,\n    tools: serverTools,\n  })\n  return toServerSentEventsResponse(stream)\n}\n```\n\nThat's the whole server. No body shape to remember, no manual validation, and a typed params.forwardedProps if you want client-driven options like provider, model, or temperature.\n\nuseChat({ body: {...} }) still works, but body is now @deprecated. The canonical name is forwardedProps, which is what the new wire format calls the field. A jscodeshift codemod ships in the repo to flip every site:\n\n```\nnpx jscodeshift \\\n  --parser=tsx \\\n  -t https://raw.githubusercontent.com/TanStack/ai/main/codemods/ag-ui-compliance/transform.ts \\\n  \"src/**/*.{ts,tsx}\"\nnpx jscodeshift \\\n  --parser=tsx \\\n  -t https://raw.githubusercontent.com/TanStack/ai/main/codemods/ag-ui-compliance/transform.ts \\\n  \"src/**/*.{ts,tsx}\"\n```\n\nIt's import-source gated, so files that don't import from @tanstack/ai* are left alone.\n\nThis is the part most \"wire format change\" releases get wrong. The upgrade ships three compatibility bridges so old code keeps running:\n\n| Surface | Legacy (still works) | Canonical |\n|---|---|---|\n| Client option | body: { ... } | forwardedProps: { ... } |\n| Server wire field | body.data.X (mirror of forwardedProps) | body.forwardedProps.X |\n| Server chat() option | conversationId | threadId |\n\nAn existing endpoint reading body.data.provider keeps reading body.data.provider because the client emits both data and forwardedProps with the same content. A chat({ conversationId }) call keeps working because conversationId is now a deprecated alias of threadId. Mix old and new freely. The bridges will be removed in the next major release, so migrate at your convenience.\n\nWith both halves of the protocol compliant, the boundaries between AI SDKs get a lot blurrier.\n\n**A pure AG-UI client (no TanStack code) hitting a @tanstack/ai endpoint** works end-to-end. Tool messages pass through as ModelMessage entries with role: 'tool'. AG-UI reasoning and activity messages with no TanStack equivalent are dropped at the boundary. developer messages collapse to system role. The outbound event stream was already AG-UI, so the foreign client renders it natively.\n\n**A TanStack client hitting a foreign AG-UI server** works for the common cases. Single-turn user messages mirror to AG-UI's content field. Server-emitted events stream and render. Multi-turn history with tool results from prior turns survives because the client sends AG-UI fan-out duplicates alongside the TanStack anchor messages.\n\nThe practical upshot: if you've been waiting to try a different inference provider, a different framework's agent runtime, or a different orchestrator, the wire is no longer the thing standing in your way. Both directions speak the same language.\n\nA few things were intentionally left out:\n\nUpgrade @tanstack/ai and @tanstack/ai-client to the latest. If you're using one of the framework wrappers (@tanstack/ai-react, -vue, -svelte, -solid, -preact), bump those too so the client wire stays in lockstep.\n\nThe AI stack is supposed to be the part you compose, not the part that locks you in. AG-UI is how that starts being true across vendors. With this release, TanStack AI is the first SDK to ship full bidirectional client-to-server *and* server-to-client compliance against the AG-UI 0.0.52 spec. The next agent runtime you adopt should not be the one that finally forces you to rewrite your wire layer.", "url": "https://wpnews.pro/news/tanstack-ai-now-fully-speaks-ag-ui", "canonical_source": "https://tanstack.com/blog/ag-ui-compliance", "published_at": "2026-05-17 12:00:00+00:00", "updated_at": "2026-05-27 08:07:13.596894+00:00", "lang": "en", "topics": ["ai-tools", "ai-infrastructure", "ai-agents"], "entities": ["TanStack", "AG-UI", "Alem Tuzlak", "TanStack Start", "Next.js", "Hono", "Node", "Bun"], "alternates": {"html": "https://wpnews.pro/news/tanstack-ai-now-fully-speaks-ag-ui", "markdown": "https://wpnews.pro/news/tanstack-ai-now-fully-speaks-ag-ui.md", "text": "https://wpnews.pro/news/tanstack-ai-now-fully-speaks-ag-ui.txt", "jsonld": "https://wpnews.pro/news/tanstack-ai-now-fully-speaks-ag-ui.jsonld"}}