A deterministic, provider-free migration lab for comparing Vercel AI SDK 6 and AI SDK 7 under production-oriented conditions.
This repository does not stop at renamed APIs. It verifies the behavior that usually breaks real applications during an SDK upgrade:
- multi-step tool-call aggregation;
- persistence and response-message shape;
- tool failure handling;
- page close, network disconnect and manual cancellation;
- partial-state persistence after abort;
- tool idempotency and duplicate-execution prevention;
- explicit retry attempts;
- total, step and chunk timeout strategy.
The fixtures use mock language models from ai/test. No external provider, API key, paid request or network-dependent model output is required.
| Concern | AI SDK 6 fixture | AI SDK 7 fixture |
|---|---|---|
| Package | ai@6.0.230 |
ai@7.0.31 |
| Instruction option | system |
instructions |
| Completion callback | onFinish |
onEnd |
| Full event stream | fullStream |
stream |
| Successful run tool calls at top level | 0 |
1 |
| Final-step tool calls | read from steps[0]/step data |
finalStep.toolCalls = 0 |
| Raw response messages in the same workflow | 3 |
1 |
| Timeout | external AbortController |
totalMs, stepMs, chunkMs |
The same workflow produced the same final answer in both versions. The important differences were the result semantics and storage shape around that answer.
xbstack-ai-sdk-7-migration-demo
├── README.md
├── package.json
├── .nvmrc
├── v6/
│ ├── src/
│ │ └── experiment.ts
│ ├── package.json
│ ├── package-lock.json
│ └── tsconfig.json
├── v7/
│ ├── src/
│ │ ├── experiment.ts
│ │ └── codemod-output.ts
│ ├── package.json
│ ├── package-lock.json
│ └── tsconfig.json
├── migration-diff/
│ ├── README.md
│ └── codemod-output.ts
├── benchmarks/
│ ├── v6.json
│ ├── v7.json
│ ├── results.json
│ └── comparison.md
├── docs/
│ └── architecture.md
├── scripts/
│ └── build-comparison.mjs
└── LICENSE
- Node.js
22.18.0is the verified environment. - npm is used because each fixture has an isolated lockfile.
- AI SDK 6 and AI SDK 7 are never installed into the same fixture.
- No environment variables are required.
- No model provider credentials are required.
With nvm:
nvm usenpm run setup
npm testnpm test performs:
- AI SDK 6 TypeScript check;
- AI SDK 7 TypeScript check;
- AI SDK 6 deterministic scenarios;
- AI SDK 7 deterministic scenarios;
- benchmark/result aggregation;
- Markdown comparison generation.
For an already-installed checkout:
npm run verifynpm run typecheck
npm run test:v6
npm run test:v7
npm run benchmarkYou can also run a fixture directly:
npm --prefix v6 test
npm --prefix v7 testBoth fixtures execute the same workflow:
user asks for order A-100
↓
model emits lookupOrder tool call
↓
tool returns ready_for_pickup
↓
model returns final answer
Both versions return:
Order A-100 is ready for pickup.
The result fields differ:
- AI SDK 6 exposes the successful tool call/result on the first step; top-level
toolCallsandtoolResultsare0in this fixture. - AI SDK 7 exposes one full-run tool call/result at the top level;
finalStep.toolCallsandfinalStep.toolResultsare0because the last step only contains text.
Do not blindly keep code that treats top-level tool fields as “the final step.” In AI SDK 7:
const allRunToolCalls = result.toolCalls;
const finalStepToolCalls = result.finalStep.toolCalls;The fixture stores a version-neutral flow:
conversation
↓
message
↓
tool_call
↓
tool_result
↓
final_response
The raw SDK payload is retained only as audit evidence.
For the same workflow:
- AI SDK 6 returned three raw response messages: assistant tool call, tool result and final assistant text.
- AI SDK 7 returned one raw response message containing the final assistant text while full-run tool data was available elsewhere.
Do not use these as stable database contracts:
result.response.messages.length
result.response.messages[index]
result.toolCalls // without deciding whether you need full-run or final-step semanticsPersist application records such as runs, steps, tool_calls, tool_attempts and tool_results. See docs/architecture.md.
The same tool throws in both fixtures. Both runs include a tool-error content part and continue to a final explanation.
The aggregation differs:
- v6 top-level tool calls/results:
0 / 0; - v7 top-level tool calls/results:
1 / 0.
A failed invocation can be part of the full-run tool-call history without becoming a successful tool result.
Three abort reasons are tested:
page_closed;network_disconnected;manual_cancel.
Each scenario performs a tool call, commits the tool result, then aborts before the final model response.
The fixture verifies:
- the user message survives;
- the completed tool result survives;
- the final assistant response is absent;
- the run is marked aborted;
- the tool executes exactly once;
- no duplicate tool execution occurs after cancellation.
Abort is not rollback. Once a side effect is committed, preserve it and resume from persisted state. Use an idempotency key for every side-effecting tool.
The retry fixture models one logical tool call with two internal attempts:
tool attempt 1 → failed
↓
tool attempt 2 → succeeded
↓
one successful tool result returned to the model
It records separate states for:
- run;
- SDK step;
- logical tool call;
- tool attempt;
- final tool result.
Model/provider retries and tool retries are different policies. A provider retry repeats model I/O. A tool retry may repeat an external side effect. Track and constrain them independently.
AI SDK 6 uses an application-owned timer and AbortController in this repository.
AI SDK 7 verifies:
timeout: {
totalMs: 20,
}The broader production configuration can separate:
timeout: {
totalMs: 30_000,
stepMs: 15_000,
chunkMs: 5_000,
}The deterministic v7 fixture observed TimeoutError.
- Replace
systemwithinstructions. - Replace
fullStreamconsumers withstream. - Move final lifecycle logic from
onFinishtoonEnd. - Audit every read of
toolCallsandtoolResults. - Use
finalStepwhen final-step-only semantics are required.
- Persist the user message before model execution.
- Introduce version-neutral run and step records.
- Store logical tool calls separately from attempts.
- Keep raw SDK payloads as optional audit evidence.
- Stop asserting fixed response message counts.
- Propagate
AbortSignalto providers and tools. - Add page-close, disconnect and manual-cancel tests.
- Add idempotency keys for side-effecting tools.
- Define retryable and non-retryable errors.
- Separate total, step and chunk timeout budgets.
- Record aborted and timed-out states instead of deleting partial history.
- Run v6 and v7 fixtures in CI.
- Migrate one internal workflow first.
- Compare normalized events, not only final text.
- Observe tool attempts and cancellation metrics.
- Keep the v6 adapter available during staged rollout.
The isolated codemod run changed:
system→instructions;fullStream→stream.
It left onFinish unchanged in the sample. The pinned AI SDK 7 package accepted that compatibility alias during TypeScript checking, but production code should move lifecycle handling explicitly to onEnd.
This repository does not claim that one version is faster or cheaper.
The fixtures intentionally exclude:
- real model quality;
- provider network latency;
- token pricing;
- provider-specific retry behavior;
- React hooks and UI transport;
- RSC behavior;
- telemetry backends;
- production databases.
The benchmark files measure deterministic structural behavior only.
- Chinese migration article: https://www.xbstack.com/ai/vercel-ai-sdk-7-migration-production/?utm_source=github&utm_medium=repository&utm_campaign=ai_sdk7_migration&utm_content=readme_zh
- English migration article: https://www.xbstack.com/en/ai/vercel-ai-sdk-7-migration-production/?utm_source=github&utm_medium=repository&utm_campaign=ai_sdk7_migration&utm_content=readme_en
- AI engineering hub: https://www.xbstack.com/en/ai/?utm_source=github&utm_medium=repository&utm_campaign=ai_sdk7_migration&utm_content=ai_hub
- Newsletter: https://www.xbstack.com/en/newsletter/?utm_source=github&utm_medium=repository&utm_campaign=ai_sdk7_migration&utm_content=newsletter
MIT © Xiaobai / XBSTACK