docs: update AI SDK comparison for persistence, durability, and adapters - #1097
Conversation
Refresh the Vercel AI SDK comparison for v7. Cover chat persistence, resumable streams, durable sandbox runs, Code Mode drivers, harness adapters, and the official LLM provider list.
📝 WalkthroughWalkthroughThe comparison updates Vercel AI SDK coverage to v7. It adds documentation for persistence, resumable streams, durable sandbox runs, Code Mode, coding-agent sandboxes, adapters, frameworks, realtime voice, and package version configuration. ChangesVercel AI SDK comparison
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
View your CI Pipeline Execution ↗ for commit 6143335
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-acp
@tanstack/ai-angular
@tanstack/ai-anthropic
@tanstack/ai-bedrock
@tanstack/ai-byteplus
@tanstack/ai-claude-code
@tanstack/ai-client
@tanstack/ai-code-mode
@tanstack/ai-code-mode-skills
@tanstack/ai-codex
@tanstack/ai-cohere
@tanstack/ai-devtools-core
@tanstack/ai-durable-stream
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-grok-build
@tanstack/ai-groq
@tanstack/ai-isolate-cloudflare
@tanstack/ai-isolate-daytona
@tanstack/ai-isolate-node
@tanstack/ai-isolate-quickjs
@tanstack/ai-isolate-quickjs-bun
@tanstack/ai-mcp
@tanstack/ai-memory
@tanstack/ai-mistral
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-opencode
@tanstack/ai-openrouter
@tanstack/ai-persistence
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-sandbox
@tanstack/ai-sandbox-cloudflare
@tanstack/ai-sandbox-daytona
@tanstack/ai-sandbox-docker
@tanstack/ai-sandbox-local-process
@tanstack/ai-sandbox-sprites
@tanstack/ai-sandbox-vercel
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-utils
@tanstack/ai-vercel-gateway
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/openai-base
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
docs/comparison/vercel-ai-sdk.md (1)
336-336: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winUpdate the edited OpenAI model IDs.
Use
gpt-5.6at lines 336, 389, and 845. Use a supportedopenai/gpt-5.6-*variant at line 876. Keepclaude-opus-4-8at line 436.🤖 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/comparison/vercel-ai-sdk.md` at line 336, Update the OpenAI model identifiers in the documented examples: change the models at the entries corresponding to lines 336, 389, and 845 to gpt-5.6, and replace the line 876 value with a supported openai/gpt-5.6-* variant. Leave claude-opus-4-8 unchanged.Source: Coding guidelines
🤖 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 `@docs/comparison/vercel-ai-sdk.md`:
- Around line 865-890: Extend the Vercel AI SDK example after the POST route
with a client snippet using useChat and the appropriate transport configuration.
Pass the chat ID as a request parameter or body value so it reaches the existing
route, matching the persistence flow shown by the TanStack AI example.
- Around line 875-888: Update the stream handling around result.stream and
createUIMessageStreamResponse to start a server-side drain before returning the
response, ensuring the stream continues to completion after client disconnects
so onEnd still invokes saveChat. Preserve the existing UI response stream and
message-saving behavior.
- Around line 333-341: Update the POST handler and related persistence flows to
derive the authenticated user from the server session, authorize ownership of
client-provided threadId and runId before calling chat, reconstructChat,
resumeServerSentEventsResponse, or sandbox takeover, and reject unauthorized
requests. Clearly mark literal identifiers such as support-chat as demo-only.
- Around line 871-877: Update the POST handler to validate the combined loaded
messages and new message with validateUIMessages before calling
convertToModelMessages or streamText. Handle validation failures immediately,
and only pass the validated messages into model conversion while preserving the
existing chat-loading flow.
- Around line 412-414: Update the withSandbox durability documentation to
clarify that durable cross-host runs require durableStream with an external
stream backend plus shared run and lock stores; otherwise, explicitly label the
memoryStream example as single-process development only.
---
Nitpick comments:
In `@docs/comparison/vercel-ai-sdk.md`:
- Line 336: Update the OpenAI model identifiers in the documented examples:
change the models at the entries corresponding to lines 336, 389, and 845 to
gpt-5.6, and replace the line 876 value with a supported openai/gpt-5.6-*
variant. Leave claude-opus-4-8 unchanged.
🪄 Autofix
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 Plus
Run ID: 2d138e4a-5b58-4a1c-9a47-f7375b6a237c
📒 Files selected for processing (1)
docs/comparison/vercel-ai-sdk.md
| export async function POST(request: Request) { | ||
| const params = await chatParamsFromRequest(request) | ||
| const stream = chat({ | ||
| adapter: openaiText('gpt-5.5'), | ||
| messages: params.messages, | ||
| threadId: params.threadId, | ||
| runId: params.runId, | ||
| middleware: [withPersistence(persistence)], | ||
| }) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- target file ---'
sed -n '300,470p;820,890p' docs/comparison/vercel-ai-sdk.md
printf '%s\n' '--- relevant identifiers across docs ---'
rg -n -C 3 'withPersistence|reconstructChat|resumeServerSentEventsResponse|threadId|runId|sandbox|takeover|support-chat' docs/comparison/vercel-ai-sdk.md
printf '%s\n' '--- API definitions and usage ---'
rg -n -C 4 'function reconstructChat|class ReconstructChat|interface ReconstructChatOptions|resumeServerSentEventsResponse|withPersistence' packages docs | head -n 300Repository: TanStack/ai
Length of output: 50369
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- persistence API files ---'
fd -t f . packages/ai-persistence packages/ai packages/ai-durable-stream packages/ai-sandbox | head -n 120
printf '%s\n' '--- persistence symbols outside changelogs ---'
rg -n -g '!**/CHANGELOG.md' -C 5 'reconstructChat|withPersistence|authorize|resumeServerSentEventsResponse|takeover|threadId|runId' packages/ai-persistence packages/ai packages/ai-durable-stream packages/ai-sandbox docs/persistence docs/resumable-streams docs/sandbox | head -n 500
printf '%s\n' '--- persistence overview ---'
sed -n '1,180p' docs/persistence/overview.md
printf '%s\n' '--- target document around all cited ranges ---'
sed -n '330,460p;830,885p' docs/comparison/vercel-ai-sdk.mdRepository: TanStack/ai
Length of output: 50369
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- persistence package source ---'
fd -t f . packages/ai-persistence/src packages/ai-durable-stream/src packages/ai-sandbox/src
printf '%s\n' '--- authorization and helper signatures ---'
rg -n -g '!**/CHANGELOG.md' -g '*.ts' -C 8 'reconstructChat|authorize|resumeServerSentEventsResponse|sandboxRunDriver|takeover' packages/ai-persistence/src packages/ai/src packages/ai-durable-stream/src packages/ai-sandbox/src
printf '%s\n' '--- persistence overview authorization guidance ---'
rg -n -C 8 'authorize|session|ownership|threadId|reconstructChat' docs/persistence/overview.md
printf '%s\n' '--- sandbox takeover documentation ---'
rg -n -C 8 'authorize|ownership|session|threadId|runId|takeover|GET' docs/sandbox docs/resumable-streams/overview.mdRepository: TanStack/ai
Length of output: 50370
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- exact persistence source files ---'
fd -t f . packages/ai-persistence/src | sort
printf '%s\n' '--- exact helper definitions ---'
rg -l -g '*.ts' 'reconstructChat|authorize' packages/ai-persistence/src packages/ai/src packages/ai-durable-stream/src packages/ai-sandbox/src | while read -r f; do
echo "### $f"
rg -n -C 12 'reconstructChat|authorize' "$f"
done
printf '%s\n' '--- resumable response definitions ---'
rg -l -g '*.ts' 'resumeServerSentEventsResponse' packages/ai/src packages/ai-durable-stream/src | while read -r f; do
echo "### $f"
rg -n -C 16 'resumeServerSentEventsResponse' "$f"
done
printf '%s\n' '--- takeover route examples and guidance ---'
rg -n -C 12 -g '*.md' -g '*.mdx' 'sandboxRunDriver|resumeServerSentEventsResponse|authorize|ownership|session|threadId|runId' docs/sandbox/takeover.md docs/sandbox/durable-runs.md docs/resumable-streams/overview.md docs/resumable-streams/advanced.md 2>/dev/nullRepository: TanStack/ai
Length of output: 50369
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
target = Path("docs/comparison/vercel-ai-sdk.md").read_text()
reconstruct = Path("packages/ai-persistence/src/reconstruct.ts").read_text()
response = Path("packages/ai/src/stream-to-response.ts").read_text()
overview = Path("docs/persistence/overview.md").read_text()
checks = {
"comparison_has_client_thread_id": "threadId: 'support-chat'" in target,
"comparison_has_server_client_ids": all(x in target for x in [
"threadId: params.threadId", "runId: params.runId"
]),
"comparison_has_reconstruct_authorize_example": "authorize:" in target,
"reconstruct_exposes_authorize": "authorize?:" in reconstruct,
"reconstruct_authorizes_before_store_load": reconstruct.index("options?.authorize") < reconstruct.index("messageStore.loadThread"),
"resume_helper_has_no_authorize_option": "authorize?:" not in response[response.index("export function resumeServerSentEventsResponse"):response.index("export function resumeHttpResponse")],
"overview_warns_without_authorization": "anyone who guesses a thread id gets the whole transcript" in overview,
}
for name, value in checks.items():
print(f"{name}={value}")
if not all(checks.values()):
raise SystemExit("unexpected documentation/API shape")
PYRepository: TanStack/ai
Length of output: 476
Authorize client-provided thread and run identifiers.
Derive identity from the server session and check ownership before invoking chat with persistence, reconstructChat, resumeServerSentEventsResponse, or sandbox takeover. Otherwise, a guessed identifier can expose another user's transcript or stream, or control another user's run. Mark literal IDs such as support-chat as demo-only.
🤖 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/comparison/vercel-ai-sdk.md` around lines 333 - 341, Update the POST
handler and related persistence flows to derive the authenticated user from the
server session, authorize ownership of client-provided threadId and runId before
calling chat, reconstructChat, resumeServerSentEventsResponse, or sandbox
takeover, and reject unauthorized requests. Clearly mark literal identifiers
such as support-chat as demo-only.
Source: Learnings
| Without durability, TanStack AI destroys the sandbox on disconnect. That stops a runaway bill. With durability, the agent detaches, writes a journal at `/tmp/tanstack-runs/<runId>.ndjson`, and a later `GET` can take the run over. | ||
|
|
||
| Turn it on with both `runs` and `durability` on `withSandbox`. Passing only one leaves the default destroy-on-disconnect behavior. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- target excerpt ---'
sed -n '390,465p' docs/comparison/vercel-ai-sdk.md
printf '%s\n' '--- stream API references ---'
rg -n --glob '!node_modules' 'memoryStream|durableStream|withSandbox|journal|tanstack-runs' .
printf '%s\n' '--- candidate TanStack AI files ---'
git ls-files | rg '(^|/)(model-meta\.ts|.*sandbox.*|.*stream.*|.*durability.*)'Repository: TanStack/ai
Length of output: 50368
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- target excerpt ---'
sed -n '400,455p' docs/comparison/vercel-ai-sdk.md
printf '%s\n' '--- focused implementation references ---'
rg -n --glob '!**/CHANGELOG.md' --glob '!**/*.test.*' --glob '!node_modules/**' \
'function memoryStream|const memoryStream|memoryStream\s*=|function durableStream|const durableStream|durableStream\s*=|withSandbox' \
packages docs | head -n 240
printf '%s\n' '--- package files ---'
git ls-files packages | rg '(^|/)(stream|durability|sandbox|run|journal)[^/]*\.(ts|tsx|md)$' | head -n 240Repository: TanStack/ai
Length of output: 32691
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- memoryStream implementation ---'
sed -n '250,390p' packages/ai/src/stream-durability.ts
printf '%s\n' '--- durableStream implementation ---'
sed -n '430,535p' packages/ai-durable-stream/src/durable-stream.ts
printf '%s\n' '--- stream response durability path ---'
sed -n '330,455p' packages/ai/src/stream-to-response.ts
printf '%s\n' '--- official durable-run guidance in repository ---'
sed -n '1,115p' docs/sandbox/durable-runs.md
sed -n '95,190p' docs/sandbox/takeover.md
printf '%s\n' '--- journal guidance ---'
sed -n '35,115p' docs/sandbox/journal.mdRepository: TanStack/ai
Length of output: 27284
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- comparison context ---'
sed -n '330,465p' docs/comparison/vercel-ai-sdk.md
printf '%s\n' '--- takeover attach route ---'
rg -n -A90 -B20 'GET|attach|durableStream\(' docs/sandbox/takeover.md | head -n 260
printf '%s\n' '--- stream durability type comments ---'
sed -n '1,120p' packages/ai/src/stream-durability.ts
sed -n '535,620p' packages/ai-durable-stream/src/durable-stream.ts
printf '%s\n' '--- memory backend caveats across docs ---'
rg -n -A8 -B8 'process-local|single-process|development stand-in|real deployment|multi-process|different host' \
docs/sandbox docs/comparison/vercel-ai-sdk.md packages/ai/src/stream-durability.ts packages/ai-durable-stream/src/durable-stream.tsRepository: TanStack/ai
Length of output: 31052
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
comparison = Path("docs/comparison/vercel-ai-sdk.md").read_text()
implementation = Path("packages/ai/src/stream-durability.ts").read_text()
target = comparison[comparison.index("### Durable Sandbox Runs"):comparison.index("### Connection Adapters")]
assert "The next request can land on a different host." in target
assert "const adapter = memoryStream(request)" in target
assert "withSandbox(sandbox, {" in target
assert "durability: { adapter }" in target
assert "const memoryLogs = new Map<string, MemoryLog>()" in implementation
assert "process-global map" in implementation
assert "single-process deployments only" in implementation
print("durable sandbox sample claims cross-host takeover while wiring memoryStream")
print("memoryStream stores logs in a process-global Map and is documented for single-process deployments only")
PYRepository: TanStack/ai
Length of output: 328
Use durableStream for cross-host durable runs. memoryStream stores the delivery log in a process-local map, so a GET on another host cannot replay or take over the run. Use an external stream backend and shared run and lock stores, or label this example as single-process development only.
🤖 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/comparison/vercel-ai-sdk.md` around lines 412 - 414, Update the
withSandbox durability documentation to clarify that durable cross-host runs
require durableStream with an external stream backend plus shared run and lock
stores; otherwise, explicitly label the memoryStream example as single-process
development only.
| **Vercel AI SDK** - You own `saveChat` / `loadChat` and call them from `onEnd`: | ||
|
|
||
| ```ts | ||
| import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream } from 'ai' | ||
| import { loadChat, saveChat } from './chat-store' | ||
|
|
||
| export async function POST(req: Request) { | ||
| const { message, id } = await req.json() | ||
| const messages = [...(await loadChat(id)), message] | ||
|
|
||
| const result = streamText({ | ||
| model: 'openai/gpt-5.5', | ||
| messages: await convertToModelMessages(messages), | ||
| }) | ||
|
|
||
| return createUIMessageStreamResponse({ | ||
| stream: toUIMessageStream({ | ||
| stream: result.stream, | ||
| originalMessages: messages, | ||
| onEnd: ({ messages: next }) => { | ||
| saveChat({ chatId: id, messages: next }) | ||
| }, | ||
| }), | ||
| }) | ||
| } | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
file="docs/comparison/vercel-ai-sdk.md"
printf '%s\n' '--- target section ---'
sed -n '800,930p' "$file"
printf '%s\n' '--- section headings ---'
rg -n '^#{1,6} ' "$file" | sed -n '1,140p'
printf '%s\n' '--- client/server persistence terms ---'
rg -n -C 3 'useChat|DefaultChatTransport|chatId|saveChat|loadChat|persistence|reload|client|server' "$file" | sed -n '1,240p'Repository: TanStack/ai
Length of output: 20674
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
path = Path("docs/comparison/vercel-ai-sdk.md")
text = path.read_text()
section = text.split("### Persistence\n", 1)[1].split("### Agent Loop Control\n", 1)[0]
blocks = re.findall(r"```(?:tsx?|jsx?)\n(.*?)```", section, re.S)
print(f"persistence_code_blocks={len(blocks)}")
for i, block in enumerate(blocks, 1):
side = "TanStack" if "fetchServerSentEvents" in block or "withPersistence" in block else "Vercel" if "createUIMessageStreamResponse" in block or "toUIMessageStream" in block else "other"
print(f"block_{i}_side={side}")
print(f"block_{i}_has_useChat={bool(re.search(r'\\buseChat\\b', block))}")
print(f"block_{i}_has_chat_id_transport={bool(re.search(r'(chatId|id).*transport|transport.*(chatId|id)', block, re.S))}")
vercel = next(
block for block in blocks
if "createUIMessageStreamResponse" in block and "toUIMessageStream" in block
)
print(f"vercel_has_useChat={bool(re.search(r'\\buseChat\\b', vercel))}")
print(f"vercel_has_transport={bool(re.search(r'\\btransport\\b', vercel))}")
print(f"vercel_has_id_in_route={bool(re.search(r'\\bid\\b', vercel))}")
PYRepository: TanStack/ai
Length of output: 398
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
path = Path("docs/comparison/vercel-ai-sdk.md")
text = path.read_text()
section = text.split("### Persistence\n", 1)[1].split("### Agent Loop Control\n", 1)[0]
blocks = re.findall(r"```(?:tsx?|jsx?)\n(.*?)```", section, re.S)
use_chat = re.compile(r"\buseChat\b")
transport = re.compile(r"\btransport\b")
chat_id = re.compile(r"\b(?:chatId|id|threadId)\b")
print(f"persistence_code_blocks={len(blocks)}")
for i, block in enumerate(blocks, 1):
side = (
"TanStack"
if "fetchServerSentEvents" in block or "withPersistence" in block
else "Vercel"
if "createUIMessageStreamResponse" in block or "toUIMessageStream" in block
else "other"
)
print(f"block_{i}_side={side}")
print(f"block_{i}_has_useChat={bool(use_chat.search(block))}")
print(f"block_{i}_has_transport={bool(transport.search(block))}")
print(f"block_{i}_has_chat_identifier={bool(chat_id.search(block))}")
vercel = next(
block for block in blocks
if "createUIMessageStreamResponse" in block and "toUIMessageStream" in block
)
print(f"vercel_has_useChat={bool(use_chat.search(vercel))}")
print(f"vercel_has_transport={bool(transport.search(vercel))}")
print(f"vercel_has_chat_identifier={bool(chat_id.search(vercel))}")
PYRepository: TanStack/ai
Length of output: 2230
Add the client half to the Vercel persistence example.
The section includes server and client snippets for TanStack AI, but only a server route for Vercel AI SDK. Add a matching useChat and transport configuration that passes the chat ID to the route.
🤖 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/comparison/vercel-ai-sdk.md` around lines 865 - 890, Extend the Vercel
AI SDK example after the POST route with a client snippet using useChat and the
appropriate transport configuration. Pass the chat ID as a request parameter or
body value so it reaches the existing route, matching the persistence flow shown
by the TanStack AI example.
Source: Coding guidelines
| export async function POST(req: Request) { | ||
| const { message, id } = await req.json() | ||
| const messages = [...(await loadChat(id)), message] | ||
|
|
||
| const result = streamText({ | ||
| model: 'openai/gpt-5.5', | ||
| messages: await convertToModelMessages(messages), |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- target excerpt ---'
sed -n '835,900p' docs/comparison/vercel-ai-sdk.md
printf '%s\n' '--- validation API references ---'
rg -n "validateUIMessages|convertToModelMessages|loadChat|streamText" docs packages examples --glob '!node_modules' 2>/dev/null | head -200
printf '%s\n' '--- package and source candidates ---'
git ls-files | rg '(^|/)(package.json|model-meta\.ts|.*message.*|.*chat.*)' | head -200Repository: TanStack/ai
Length of output: 20702
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- target file structure ---'
ast-grep outline docs/comparison/vercel-ai-sdk.md 2>/dev/null || true
printf '%s\n' '--- nearby comparison content ---'
sed -n '760,930p' docs/comparison/vercel-ai-sdk.md
printf '%s\n' '--- repository metadata ---'
find . -maxdepth 2 -name package.json -o -name pnpm-lock.yaml | sort | head -50Repository: TanStack/ai
Length of output: 7150
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
p = Path("docs/comparison/vercel-ai-sdk.md")
text = p.read_text()
for needle in ("validateUIMessages", "convertToModelMessages", "gpt-5.5"):
print(f"--- occurrences of {needle} ---")
for i, line in enumerate(text.splitlines(), 1):
if needle in line:
print(f"{i}: {line}")
PYRepository: TanStack/ai
Length of output: 1593
🌐 Web query:
AI SDK 6 chat persistence validateUIMessages convertToModelMessages official documentation
💡 Result:
In the AI SDK, validateUIMessages and convertToModelMessages are essential functions for managing chatbot message persistence and integrating UI messages with core AI model functions [1][2]. validateUIMessages validateUIMessages is an asynchronous function used to validate UI messages against specified schemas for metadata, data parts, and tools [3][4]. It ensures data integrity and type safety by checking messages before they are processed or rendered [3]. Usage: It is typically used on the server when loading chat history from a database, particularly if those messages contain tool calls, custom metadata, or data parts [1][5]. import { validateUIMessages } from 'ai'; const validatedMessages = await validateUIMessages({ messages, tools, dataSchemas, metadataSchema, }); A related function, safeValidateUIMessages, performs the same validation but returns a result object (with a success boolean) instead of throwing an error [6][7]. convertToModelMessages convertToModelMessages transforms an array of UI messages (typically from the useChat hook) into an array of ModelMessage objects, which are compatible with core AI functions such as streamText and generateText [2][8]. Usage: It is commonly used after validation to prepare message history for the AI model [1][5]. It supports optional configurations to handle incomplete tool calls and custom data part conversion [2][9]. import { convertToModelMessages } from 'ai'; const modelMessages = await convertToModelMessages(validatedMessages, { tools, // Optional: define how to convert custom data parts convertDataPart: (part) => { /*... */ } }); When implementing chatbot persistence, the standard pattern involves loading previous messages, validating them with validateUIMessages, converting them with convertToModelMessages for the AI stream, and finally saving the updated message list (often via an onEnd or onFinish callback) [1][10].
Citations:
- 1: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence
- 2: https://ai-sdk.dev/docs/reference/ai-sdk-ui/convert-to-model-messages
- 3: https://ai-sdk.dev/docs/reference/ai-sdk-core/validate-ui-messages
- 4: https://app.unpkg.com/ai@7.0.34/files/docs/07-reference/01-ai-sdk-core/32-validate-ui-messages.mdx
- 5: https://github.com/vercel/ai/blob/a23b6767/content/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx
- 6: https://ai-sdk.dev/docs/reference/ai-sdk-core/safe-validate-ui-messages
- 7: https://github.com/vercel/ai/blob/main/packages/ai/src/ui/validate-ui-messages.ts
- 8: https://ai-sdk.dev/v7/docs/reference/ai-sdk-ui/convert-to-model-messages
- 9: https://github.com/vercel/ai/blob/main/packages/ai/src/ui/convert-to-model-messages.ts
- 10: https://ai-sdk.dev/v5/docs/ai-sdk-ui/chatbot-message-persistence
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- persistence documentation references ---'
sed -n '330,375p' docs/comparison/vercel-ai-sdk.md
rg -n -C 5 "validateUIMessages|Chat Persistence|message persistence" docs packages examples pnpm-lock.yaml package.json --glob '!node_modules'
printf '%s\n' '--- message type and conversion implementations ---'
rg -n "function (convertToModelMessages|validateUIMessages)|const (convertToModelMessages|validateUIMessages)|validate.*Message|convert.*Message" packages --glob '*.{ts,tsx,js}' | head -200Repository: TanStack/ai
Length of output: 24929
🌐 Web query:
site:ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence validateUIMessages messages
💡 Result:
The validateUIMessages function is a utility provided by the Vercel AI SDK [1]. It is primarily used to validate messages retrieved from storage—such as a database—before they are processed by a model or sent to the AI SDK's streaming functions [1]. This validation step is essential when messages contain tool calls, custom metadata, or data parts [1]. By using validateUIMessages, developers ensure that the stored messages conform to the current schemas for tools, data parts, and metadata [1]. This prevents errors that might occur if the underlying schema definitions have changed since the messages were originally saved. To use the function, you pass an object containing the messages you wish to validate along with your current tool definitions and relevant schemas [1]: const validatedMessages = await validateUIMessages({ messages, tools, dataPartsSchema, metadataSchema, }); Once validated, these messages can be safely converted to model messages and processed within your application's workflow [1].
Citations:
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from urllib.request import urlopen
from bs4 import BeautifulSoup
urls = [
"https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence",
"https://raw.githubusercontent.com/vercel/ai/main/content/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx",
]
for url in urls:
print(f"--- {url} ---")
try:
data = urlopen(url, timeout=15).read().decode()
if url.endswith(".mdx"):
print(data[:20000])
else:
soup = BeautifulSoup(data, "html.parser")
text = soup.get_text("\n")
lines = [line.strip() for line in text.splitlines() if line.strip()]
for i, line in enumerate(lines):
if "validateUIMessages" in line or "convertToModelMessages" in line:
print("\n".join(lines[max(0, i-8):i+18]))
except Exception as e:
print(type(e).__name__, e)
PYRepository: TanStack/ai
Length of output: 265
🏁 Script executed:
#!/bin/bash
set -e
for url in \
'https://raw.githubusercontent.com/vercel/ai/main/content/docs/04-ai-sdk-ui/03-chatbot-message-persistence.mdx' \
'https://raw.githubusercontent.com/vercel/ai/main/packages/ai/src/ui/validate-ui-messages.ts' \
'https://raw.githubusercontent.com/vercel/ai/main/packages/ai/src/ui/convert-to-model-messages.ts'; do
printf '\n--- %s ---\n' "$url"
curl -L --fail --silent --show-error "$url" | sed -n '1,260p'
doneRepository: TanStack/ai
Length of output: 28599
Validate messages before model conversion.
Call validateUIMessages on [...previousMessages, message] and handle validation failures before convertToModelMessages and streamText. This protects persisted tool calls, metadata, and data parts from invalid schemas.
🤖 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/comparison/vercel-ai-sdk.md` around lines 871 - 877, Update the POST
handler to validate the combined loaded messages and new message with
validateUIMessages before calling convertToModelMessages or streamText. Handle
validation failures immediately, and only pass the validated messages into model
conversion while preserving the existing chat-loading flow.
| const result = streamText({ | ||
| model: 'openai/gpt-5.5', | ||
| messages: await convertToModelMessages(messages), | ||
| }) | ||
|
|
||
| return createUIMessageStreamResponse({ | ||
| stream: toUIMessageStream({ | ||
| stream: result.stream, | ||
| originalMessages: messages, | ||
| onEnd: ({ messages: next }) => { | ||
| saveChat({ chatId: id, messages: next }) | ||
| }, | ||
| }), | ||
| }) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- target snippet ---'
sed -n '840,910p' docs/comparison/vercel-ai-sdk.md
printf '%s\n' '--- stream API references ---'
rg -n "consumeStream|onEnd|createUIMessageStreamResponse|toUIMessageStream|streamText" . -g '!node_modules' -g '!dist' -g '!build' | head -200
printf '%s\n' '--- package metadata ---'
rg -n '"ai"|"`@ai-sdk/`' package.json pnpm-lock.yaml '**/package.json' 2>/dev/null | head -120Repository: TanStack/ai
Length of output: 14074
🌐 Web query:
Vercel AI SDK streamText consumeStream client disconnect onEnd createUIMessageStreamResponse official persistence guide
💡 Result:
To handle client disconnects and ensure message persistence in the Vercel AI SDK when using streamText, you must explicitly consume the stream on the backend [1][2]. Core Mechanism for Persistence By default, streamText uses backpressure; if the client disconnects, the stream is aborted, which can lead to broken conversations [1]. To ensure the stream completes and triggers your onEnd logic even if the client leaves, call result.consumeStream on your StreamTextResult before returning the response [1][2]. Example Implementation: import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream } from 'ai'; import { saveChat } from '@util/chat-store'; export async function POST(req: Request) { const { messages, chatId } = await req.json; const result = streamText({ model, messages: await convertToModelMessages(messages), }); // Consume the stream to ensure it runs to completion & triggers onEnd // even when the client response is aborted: result.consumeStream; return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, originalMessages: messages, onEnd: ({ messages }) => { saveChat({ chatId, messages }); }, }), }); } Important Considerations: 1. Handling Abort Logic: If you are also implementing explicit stream cancellation (e.g., a "Stop" button), note that forwarding req.signal as the abortSignal to streamText will cancel the generation upon ANY client disconnect, including accidental ones [2][3]. If you require resumable streams, you must avoid using req.signal directly in a way that terminates the process on unintentional disconnects; instead, use a dedicated stop endpoint to handle intentional user cancellation [3][4]. 2. Error Handling: Ensure your code handles potential errors from consumeStream correctly. In some environments, if an abort signal is passed, consumeStream may throw a response-aborted error that should be caught to prevent it from bubbling up as a network error [5]. 3. Official Documentation: Refer to the official AI SDK documentation on Chatbot Message Persistence [1] and Advanced: Stopping Streams [2] for the most up-to-date patterns.
Citations:
- 1: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence
- 2: https://ai-sdk.dev/docs/advanced/stopping-streams
- 3: https://ai-sdk.dev/docs/troubleshooting/abort-breaks-resumable-streams
- 4: https://ai-sdk.dev/v7/docs/ai-sdk-ui/chatbot-resume-streams
- 5: fix (core): consume stream on abort vercel/ai#5492
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
p = Path("docs/comparison/vercel-ai-sdk.md")
lines = p.read_text().splitlines()
for i in range(850, 901):
print(f"{i+1}: {lines[i]}")
PY
printf '%s\n' '--- local package/version references ---'
rg -n '"ai"|"`@ai-sdk/`' --glob 'package.json' --glob 'pnpm-lock.yaml' . | head -120 || true
printf '%s\n' '--- local API references ---'
rg -n 'consumeStream|createUIMessageStreamResponse|toUIMessageStream|onEnd' --glob '!node_modules' --glob '!dist' --glob '!build' . | head -200 || true
printf '%s\n' '--- official docs text ---'
curl -L --max-time 20 -sS 'https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence' | tr '\n' ' ' | grep -oE '.{0,300}(consumeStream|onFinish|onEnd).{0,500}' | head -20 || trueRepository: TanStack/ai
Length of output: 24380
Drain the stream after client disconnects.
If the client disconnects before result.stream finishes, onEnd may not run and saveChat may not execute. Start a server-side drain before returning the response.
Proposed fix
const result = streamText({
model: 'openai/gpt-5.5',
messages: await convertToModelMessages(messages),
})
+ void result.consumeStream()
+
return createUIMessageStreamResponse({📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const result = streamText({ | |
| model: 'openai/gpt-5.5', | |
| messages: await convertToModelMessages(messages), | |
| }) | |
| return createUIMessageStreamResponse({ | |
| stream: toUIMessageStream({ | |
| stream: result.stream, | |
| originalMessages: messages, | |
| onEnd: ({ messages: next }) => { | |
| saveChat({ chatId: id, messages: next }) | |
| }, | |
| }), | |
| }) | |
| const result = streamText({ | |
| model: 'openai/gpt-5.5', | |
| messages: await convertToModelMessages(messages), | |
| }) | |
| void result.consumeStream() | |
| return createUIMessageStreamResponse({ | |
| stream: toUIMessageStream({ | |
| stream: result.stream, | |
| originalMessages: messages, | |
| onEnd: ({ messages: next }) => { | |
| saveChat({ chatId: id, messages: next }) | |
| }, | |
| }), | |
| }) |
🤖 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/comparison/vercel-ai-sdk.md` around lines 875 - 888, Update the stream
handling around result.stream and createUIMessageStreamResponse to start a
server-side drain before returning the response, ensuring the stream continues
to completion after client disconnects so onEnd still invokes saveChat. Preserve
the existing UI response stream and message-saving behavior.
Kiira was pinned to ai@6, so the v7 persistence snippet failed. Bump the checker pins. Migration fences still type-check because v7 keeps the old aliases.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
kiira.config.ts (1)
32-32: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winSet the CI Node.js version explicitly.
ai@^7.0.0requires Node.js>=22..nvmrcspecifies24.8.0, but no workflow selects.nvmrcor setsnode-version. Configure CI with Node.js 22 or newer beforepnpm installandpnpm test:docs.🤖 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 `@kiira.config.ts` at line 32, Update the CI workflow to explicitly select Node.js 22 or newer, preferably using the repository’s .nvmrc value, before the pnpm install and pnpm test:docs steps. Ensure the workflow’s setup-node configuration applies to those commands.Source: MCP tools
🤖 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 `@kiira.config.ts`:
- Line 32: Update the CI workflow to explicitly select Node.js 22 or newer,
preferably using the repository’s .nvmrc value, before the pnpm install and pnpm
test:docs steps. Ensure the workflow’s setup-node configuration applies to those
commands.
Changes
Refresh the Vercel AI SDK comparison page for AI SDK v7.
withPersistence,reconstructChat), generation persistence, resumable streams (memoryStream/durableStream), and durable sandbox runs (runs+durability).@ai-sdk/code-mode.@tanstack/ai-angular), plus community adapters.Docs-only. No changeset.
Checklist
pnpm run test:pr.pnpm test:docs(link check). No broken links.Release Impact
Summary by CodeRabbit
Documentation
Chores