Migrate AI SDK 6.x to 7.0
Use the command below to add the migration skill:
npx skills add vercel/ai --skill migrate-ai-sdk-v6-to-v7Then ask your agent:
Use the migrate-ai-sdk-v6-to-v7 skill and migrate my app from AI SDK v6 to v7.Recommended Migration Process
- Backup your project. If you use a versioning control system, make sure all previous versions are committed.
- Upgrade to AI SDK 7.0.
- Follow the breaking changes guide below.
- Verify your project is working as expected.
- Commit your changes.
An example upgrade command would be:
pnpm install ai @ai-sdk/react @ai-sdk/openai @ai-sdk/otelCodemods
The AI SDK provides Codemod transformations to help upgrade your codebase when a feature is deprecated, removed, or otherwise changed.
Codemods are transformations that run on your codebase automatically. They allow you to easily apply many changes without having to manually go through every file.
You can run all v7 codemods (v6 -> v7 migration) by running the following command from the root of your project:
npx @ai-sdk/codemod v7Individual codemods can be run by specifying the name of the codemod:
npx @ai-sdk/codemod <codemod-name> <path>For example, to run a specific v7 codemod:
npx @ai-sdk/codemod v7/rename-system-to-instructions src/Codemods are intended as a tool to help you with the upgrade process. They may not cover all of the changes you need to make. You may need to make additional changes manually.
Codemod Table
| Codemod Name | Description |
|---|---|
remove-experimental-custom-provider | Replaces experimental_customProvider with customProvider |
remove-experimental-generate-image | Replaces experimental_generateImage and Experimental_GenerateImageResult with stable names |
replace-experimental-output-with-output | Replaces experimental_output options and result access with output |
remove-experimental-prepare-step | Replaces experimental_prepareStep with prepareStep |
replace-cached-input-tokens | Replaces usage.cachedInputTokens with usage.inputTokenDetails.cacheReadTokens |
replace-reasoning-tokens | Replaces usage.reasoningTokens with usage.outputTokenDetails.reasoningTokens |
remove-experimental-active-tools | Replaces experimental_activeTools with activeTools |
remove-tool-call-options-type | Replaces the removed ToolCallOptions type with ToolExecutionOptions |
remove-is-tool-or-dynamic-tool-uipart | Replaces isToolOrDynamicToolUIPart with isToolUIPart |
remove-media-content-part-type | Replaces tool result content parts with type: 'media' with type: 'file-data' |
replace-anthropic-cache-creation-input-tokens | Replaces Anthropic cacheCreationInputTokens metadata access with standard usage fields |
rename-experimental-transcribe | Renames experimental_transcribe and Experimental_TranscriptionResult to stable names |
rename-experimental-generate-speech | Renames experimental_generateSpeech and Experimental_SpeechResult to stable names |
rename-call-settings-type | Replaces CallSettings with LanguageModelCallOptions & Omit<RequestOptions, 'timeout'> |
rename-step-count-is | Renames stepCountIs to isStepCount |
rename-system-to-instructions | Renames system prompt options, lifecycle fields, and repair-tool-call fields to instructions |
rename-experimental-on-start-to-on-start | Renames experimental_onStart to onStart |
rename-experimental-on-step-start-to-on-step-start | Renames experimental_onStepStart to onStepStart |
rename-on-finish-to-on-end | Renames onFinish callbacks to onEnd |
rename-on-step-finish-to-on-step-end | Renames onStepFinish callbacks to onStepEnd |
rename-experimental-on-finish-to-on-end | Renames experimental_onFinish callbacks to onEnd |
rename-experimental-telemetry-to-telemetry | Renames experimental_telemetry options to telemetry |
rename-on-rerank-finish-to-on-rerank-end | Renames telemetry onRerankFinish callbacks to onRerankEnd |
rename-on-embed-finish-to-on-embed-end | Renames telemetry onEmbedFinish callbacks to onEmbedEnd |
rename-full-stream-to-stream | Renames streamText result fullStream access to stream |
move-include-raw-chunks-to-include | Moves includeRawChunks into include.rawChunks |
rename-experimental-include-to-include | Renames experimental_include to include |
rename-experimental-on-tool-call-start-to-on-tool-execution-start | Renames experimental_onToolCallStart to onToolExecutionStart |
rename-experimental-on-tool-call-finish-to-on-tool-execution-end | Renames experimental_onToolCallFinish to onToolExecutionEnd |
rename-experimental-context-to-context | Renames tool callback experimental_context access to context |
rename-google-generative-ai-to-google | Renames Google provider types, classes, and functions that include GoogleGenerativeAI to Google |
replace-image-message-part-with-file | Replaces image message parts with file parts using mediaType: 'image' |
All Packages
Minimum Node.js Version
AI SDK 7.0 requires Node.js 22 or later. The SDK is tested on Node.js 22, 24, and 26.
Node.js 18 and 20 are no longer supported. Node.js 22 reached end-of-maintenance on April 30, 2026; for production workloads, prefer Node.js 24 (LTS) or Node.js 26. See the Node.js release schedule for current status and support timelines.
Update the engines field in your package.json if you enforce a minimum Node.js version:
{ "engines": { "node": ">=22" }}ESM Only — CommonJS Support Removed
All AI SDK packages are now ESM-only. The require() function is no longer supported.
If your project uses CommonJS (require()), switch to ESM import syntax:
const { generateText } = require('ai');const { openai } = require('@ai-sdk/openai');import { generateText } from 'ai';import { openai } from '@ai-sdk/openai';If your package.json does not already include "type": "module", add it or rename your files to use the .mjs extension.
AI SDK Core
Core API Renames and Removals
Provider Management: Remove Deprecated experimental_customProvider
The deprecated experimental_customProvider export has been removed in AI SDK 7. Replace it with customProvider.
import { experimental_customProvider } from 'ai';
export const myProvider = experimental_customProvider({ languageModels: { // ... },});import { customProvider } from 'ai';
export const myProvider = customProvider({ languageModels: { // ... },});This is only an import and symbol rename. The customProvider options and
behavior are unchanged.
Remove Deprecated experimental_generateImage Export
The deprecated experimental_generateImage export has been removed in AI SDK 7. Replace it with generateImage.
The deprecated Experimental_GenerateImageResult type export has also been removed. Replace it with GenerateImageResult.
import { experimental_generateImage, type Experimental_GenerateImageResult,} from 'ai';
const result: Experimental_GenerateImageResult = await experimental_generateImage({ model: yourImageModel, prompt: 'A red panda eating bamboo', });import { generateImage, type GenerateImageResult } from 'ai';
const result: GenerateImageResult = await generateImage({ model: yourImageModel, prompt: 'A red panda eating bamboo',});experimental_transcribe Renamed to transcribe
The transcription API has graduated out of experimental status and has been
renamed to transcribe. The Experimental_TranscriptionResult type has also
been renamed to TranscriptionResult.
import { experimental_transcribe as transcribe, type Experimental_TranscriptionResult,} from 'ai';
const result: Experimental_TranscriptionResult = await transcribe({ model: yourTranscriptionModel, audio,});import { transcribe, type TranscriptionResult } from 'ai';
const result: TranscriptionResult = await transcribe({ model: yourTranscriptionModel, audio,});The old names continue to work as deprecated aliases in AI SDK 7 and will be removed in a future major release.
experimental_generateSpeech Renamed to generateSpeech
The speech generation function has graduated out of experimental status and has
been renamed to generateSpeech. The Experimental_SpeechResult type has also
been renamed to SpeechResult.
import { experimental_generateSpeech as generateSpeech } from 'ai';
const result = await generateSpeech({ model: yourSpeechModel, text: 'Hello',});import { generateSpeech } from 'ai';
const result = await generateSpeech({ model: yourSpeechModel, text: 'Hello',});The old experimental_generateSpeech and Experimental_SpeechResult exports
continue to work as deprecated aliases in AI SDK 7 and will be removed in a
future major release.
Structured Outputs: Remove Deprecated experimental_output Option and Result
The deprecated experimental_output option has been removed in AI SDK 7. Replace all remaining usages with output.
The deprecated generateText() result property experimental_output has also been removed. Read result.output instead.
This name was deprecated in AI SDK 6, so if you already migrated to output, no changes are needed. Otherwise, update both the call options and any result access:
const result = await generateText({ model: yourModel, experimental_output: Output.object({ schema: recipeSchema, }), prompt: 'Generate a recipe.',});
console.log(result.experimental_output);const result = await generateText({ model: yourModel, output: Output.object({ schema: recipeSchema, }), prompt: 'Generate a recipe.',});
console.log(result.output);CallSettings Renamed to LanguageModelCallOptions and RequestOptions
CallSettings has been split into LanguageModelCallOptions (model-facing options) and RequestOptions (transport options). Replace usages in custom wrappers or helpers:
CallSettings→LanguageModelCallOptions & Omit<RequestOptions, 'timeout'>(note:CallSettingsnever includedtimeout)
The deprecated CallSettings type remains available in AI SDK 7.
Stop Condition Helper Rename: stepCountIs -> isStepCount
Rename imports and usage in tool-loop stop conditions:
import { stepCountIs } from 'ai';
stopWhen: stepCountIs(3);import { isStepCount } from 'ai';
stopWhen: isStepCount(3);Prompts and Step Preparation
system Renamed to instructions
The top-level prompt option for system instructions has been renamed from
system to instructions.
const result = await generateText({ model: yourModel, system: 'You are a helpful assistant.', prompt: 'Hello!',});const result = await generateText({ model: yourModel, instructions: 'You are a helpful assistant.', prompt: 'Hello!',});This applies to AI SDK functions that accept prompt or messages, including
generateText, streamText, generateObject, streamObject, and streamUI.
The same rename applies to prepareStep results for generateText and
streamText, and to the options passed into experimental_repairToolCall:
const result = streamText({ model: yourModel, prompt: 'Hello!', prepareStep: () => ({ system: 'Use concise answers for this step.', }),});const result = streamText({ model: yourModel, prompt: 'Hello!', prepareStep: () => ({ instructions: 'Use concise answers for this step.', }),});The system option is still accepted as a deprecated fallback. When both
instructions and system are provided, instructions takes precedence.
prepareStep Instructions Carry Forward
In AI SDK 7, instructions returned from prepareStep are used in future steps
until prepareStep returns another instructions or system override. This
matches how messages returned from prepareStep carry forward.
In AI SDK 6, prepareStep instruction overrides only applied to the current
step. Later steps fell back to the top-level system instructions unless they
returned their own override.
If your prepareStep logic depends on one-step-only instruction overrides,
return the desired instructions for each step explicitly:
const result = streamText({ model: yourModel, instructions: 'Use the default behavior.', prompt: 'Hello!', prepareStep: ({ stepNumber, initialInstructions }) => ({ instructions: stepNumber === 0 ? 'Use special instructions for the first step.' : initialInstructions, }),});The prepareStep callback receives both instructions, which is the current
instruction state for the step, and initialInstructions, which is the
top-level instruction value from the original call.
If your tool call repair function forwards the current system instructions to
another model call, read and pass instructions instead of system:
const result = await generateText({ model: yourModel, tools, prompt: 'Hello!', experimental_repairToolCall: async ({ system, messages }) => { return repairWithModel({ system, messages }); },});const result = await generateText({ model: yourModel, tools, prompt: 'Hello!', experimental_repairToolCall: async ({ instructions, messages }) => { return repairWithModel({ instructions, messages }); },});Lifecycle callback events for generateText, streamText, and agents also use
instructions instead of system:
const result = await generateText({ model: yourModel, prompt: 'Hello!', experimental_onStart: ({ system }) => { console.log(system); },});const result = await generateText({ model: yourModel, prompt: 'Hello!', onStart: ({ instructions }) => { console.log(instructions); },});Prompt Messages: System Messages in prompt or messages Are Rejected by Default
AI SDK 7 rejects system messages in the prompt or messages fields by default. System instructions should usually be passed with the top-level instructions option.
This can break older persisted chats or custom prompt arrays that include { role: 'system' } messages:
const result = await generateText({ model: yourModel, messages: [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'Hello!' }, ],});Move system instructions to the instructions option when possible:
const result = await generateText({ model: yourModel, instructions: 'You are a helpful assistant.', messages: [{ role: 'user', content: 'Hello!' }],});If you need to keep existing chat histories that already contain system messages, opt in to the previous behavior with allowSystemInMessages: true:
Only use allowSystemInMessages for trusted messages. If users can submit or
edit these messages, they could inject a system message that overrides or sets
the system prompt. In most cases, system instructions should only be set by
trusted server-side code through the instructions property.
const result = await generateText({ model: yourModel, allowSystemInMessages: true, messages: persistedMessages,});This applies to AI SDK functions that accept prompt or messages, including generateText, streamText, generateObject, streamObject, and streamUI.
Remove Deprecated experimental_prepareStep Option
The deprecated experimental_prepareStep option has been removed in AI SDK 7. Replace all remaining usages with prepareStep.
This option was deprecated in AI SDK 5, so if you already migrated to prepareStep, no changes are needed. Otherwise, update generateText calls:
const result = await generateText({ model: yourModel, tools: { weather }, experimental_prepareStep: ({ stepNumber }) => { console.log('Preparing step', stepNumber); return { activeTools: ['weather'], }; },});const result = await generateText({ model: yourModel, tools: { weather }, prepareStep: ({ stepNumber }) => { console.log('Preparing step', stepNumber); return { activeTools: ['weather'], }; },});prepareStep Message Overrides Carry Forward
When prepareStep returns messages, those messages are now used as the base for subsequent steps. The next step receives those messages plus the response messages from the previous step.
In AI SDK 6, a messages override only applied to the current step. To keep that behavior, rebuild the current step's messages from initialMessages and responseMessages inside prepareStep:
const result = await generateText({ model: yourModel, tools: { weather }, prepareStep: ({ initialMessages, responseMessages }) => { return { messages: [ ...initialMessages, ...responseMessages, // add any one-step-only message changes here ], }; },});Lifecycle Events
experimental_onStart Renamed to onStart
The generation start callback for generateText, streamText, and agents has
been renamed from experimental_onStart to onStart.
const result = await generateText({ model: yourModel, prompt: 'Hello!', experimental_onStart: () => { console.log('Generation started'); },});const result = await generateText({ model: yourModel, prompt: 'Hello!', onStart: () => { console.log('Generation started'); },});experimental_onStepStart Renamed to onStepStart
The per-step start callback for generateText, streamText, and agents has
been renamed from experimental_onStepStart to onStepStart.
const result = await generateText({ model: yourModel, prompt: 'Hello!', experimental_onStepStart: ({ stepNumber }) => { console.log(`Step ${stepNumber} started`); },});const result = await generateText({ model: yourModel, prompt: 'Hello!', onStepStart: ({ stepNumber }) => { console.log(`Step ${stepNumber} started`); },});onFinish Renamed to onEnd
The final lifecycle callback for generateText, streamText, and agents has
been renamed from onFinish to onEnd.
const result = await generateText({ model: yourModel, prompt: 'Hello!', onFinish: ({ text }) => { console.log(text); },});const result = await generateText({ model: yourModel, prompt: 'Hello!', onEnd: ({ text }) => { console.log(text); },});The same rename applies to streamText, Agent.generate(),
Agent.stream(), and ToolLoopAgent settings. The onFinish option is still
accepted as a deprecated alias. When both onEnd and onFinish are provided,
onEnd takes precedence.
onStepFinish Renamed to onStepEnd
The per-step lifecycle callback has been renamed from onStepFinish to
onStepEnd.
const result = await generateText({ model: yourModel, prompt: 'Hello!', onStepFinish: ({ stepNumber, usage }) => { console.log(`Step ${stepNumber} used ${usage.totalTokens} tokens`); },});const result = await generateText({ model: yourModel, prompt: 'Hello!', onStepEnd: ({ stepNumber, usage }) => { console.log(`Step ${stepNumber} used ${usage.totalTokens} tokens`); },});This applies to generateText, streamText, generateObject,
streamObject, agents, workflow agents, and UI message stream helpers.
Telemetry integrations should implement onStepEnd.
The onStepFinish option is still accepted as a deprecated alias for
user-facing callbacks. When both onStepEnd and onStepFinish are provided,
onStepEnd takes precedence.
Embed Callbacks
The per-call callback option for completed embed and embedMany operations has been renamed from experimental_onFinish to onEnd.
const result = await embed({ model: yourEmbeddingModel, value, experimental_onFinish(event) { console.log('Embedding finished:', event.usage.tokens); },});const result = await embed({ model: yourEmbeddingModel, value, onEnd(event) { console.log('Embedding ended:', event.usage.tokens); },});Rerank Callback
The per-call callback option for completed rerank operations has been renamed from experimental_onFinish to onEnd.
const result = await rerank({ model: yourRerankingModel, documents, query, experimental_onFinish(event) { console.log('Rerank finished:', event.ranking.length); },});const result = await rerank({ model: yourRerankingModel, documents, query, onEnd(event) { console.log('Rerank ended:', event.ranking.length); },});Usage and Result Shape Changes
cachedInputTokens and reasoningTokens Removed from LanguageModelUsage
The deprecated top-level cachedInputTokens and reasoningTokens fields have been removed from LanguageModelUsage.
Use inputTokenDetails.cacheReadTokens and outputTokenDetails.reasoningTokens instead:
const result = await generateText({ model: yourModel, prompt: 'Hello!',});
console.log(result.usage.cachedInputTokens);console.log(result.usage.reasoningTokens);const result = await generateText({ model: yourModel, prompt: 'Hello!',});
console.log(result.usage.inputTokenDetails.cacheReadTokens);console.log(result.usage.outputTokenDetails.reasoningTokens);Telemetry
OpenTelemetry Moved to @ai-sdk/otel
OpenTelemetry span collection is no longer built into the ai package. To continue receiving OpenTelemetry traces, you must install the new @ai-sdk/otel package and register the OpenTelemetry instance globally.
pnpm install @ai-sdk/otelPreviously, OpenTelemetry spans were emitted automatically when experimental_telemetry was enabled:
import { generateText } from 'ai';
const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_telemetry: { isEnabled: true },});Now, you must install @ai-sdk/otel and register the OpenTelemetry instance once at application startup. For Next.js, place this in your instrumentation.ts file alongside your OpenTelemetry provider setup:
import { registerTelemetry } from 'ai';import { OpenTelemetry } from '@ai-sdk/otel';
registerTelemetry(new OpenTelemetry());
// ... your OpenTelemetry provider setup (e.g. registerOTel, NodeTracerProvider)For Node.js applications (without Next.js), register the integration at the top level of your entry file.
This applies to all AI SDK functions that accept experimental_telemetry, including generateText, streamText, ToolLoopAgent, embed, embedMany, and rerank.
Enabled by Default When an Integration Is Registered
In AI SDK 6, telemetry was opt-in — you had to set experimental_telemetry: { isEnabled: true } on every call to emit events. In AI SDK 7, telemetry is opt-out: once you register a telemetry integration (for example OpenTelemetry or DevToolsTelemetry), all AI SDK calls emit telemetry events by default.
import { generateText } from 'ai';
const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_telemetry: { isEnabled: true },});import { generateText } from 'ai';import { registerTelemetry } from 'ai';import { OpenTelemetry } from '@ai-sdk/otel';
registerTelemetry(new OpenTelemetry());
const result = await generateText({ model: yourModel, prompt: 'Hello',});You can safely remove experimental_telemetry: { isEnabled: true } from all of your calls. If you are already passing other fields like functionId or integrations, keep them — only isEnabled: true is redundant:
const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_telemetry: { isEnabled: true, functionId: 'my-awesome-function', },});const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_telemetry: { functionId: 'my-awesome-function', },});To opt out of telemetry for a specific call, set isEnabled: false:
const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_telemetry: { isEnabled: false },});To disable telemetry globally, do not register any telemetry integrations.
This applies to all AI SDK functions that accept experimental_telemetry, including generateText, streamText, ToolLoopAgent, embed, embedMany, and rerank.
tracer Property Removed from experimental_telemetry
The tracer property on experimental_telemetry has been removed. If you were passing a custom OpenTelemetry Tracer, pass it to the OpenTelemetry constructor instead:
import { generateText } from 'ai';import { trace } from '@opentelemetry/api';
const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_telemetry: { isEnabled: true, tracer: trace.getTracer('my-app'), },});import { registerTelemetry } from 'ai';import { OpenTelemetry } from '@ai-sdk/otel';import { trace } from '@opentelemetry/api';
registerTelemetry( new OpenTelemetry({ tracer: trace.getTracer('my-app'), }),);This applies to all AI SDK functions that accept experimental_telemetry, including streamText, generateObject, streamObject, embed, and embedMany.
If you were not passing a custom tracer (relying on the default global tracer), no changes are needed — the OpenTelemetry is registered globally by default and uses trace.getTracer('ai') when no custom tracer is provided.
experimental_telemetry Renamed to telemetry
The telemetry option has graduated out of experimental status and has been renamed to telemetry. The old name experimental_telemetry continues to work as a deprecated alias in AI SDK 7 and will be removed in a future major release.
import { generateText } from 'ai';
const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_telemetry: { functionId: 'story-agent', },});import { generateText } from 'ai';
const result = await generateText({ model: yourModel, prompt: 'Hello', telemetry: { functionId: 'story-agent', },});This applies to all AI SDK functions and agents that accept telemetry configuration, including generateText, streamText, generateObject, streamObject, embed, embedMany, rerank, ToolLoopAgent, and WorkflowAgent.
No behavior changes beyond the rename. You can migrate incrementally — mixed usage of telemetry and experimental_telemetry in the same codebase is supported during the deprecation window.
onRerankFinish Renamed to onRerankEnd
The telemetry integration callback for individual reranking model calls has been renamed from onRerankFinish to onRerankEnd.
This also applies to the type field emitted on AI_SDK_TELEMETRY_TRACING_CHANNEL: tracing-channel subscribers now receive onRerankEnd instead of onRerankFinish.
Update telemetry integrations and tracing-channel subscribers to use onRerankEnd.
import type { Telemetry } from 'ai';
const telemetry: Telemetry = { onRerankFinish(event) { console.log('Rerank finished:', event.ranking.length); },};import type { Telemetry } from 'ai';
const telemetry: Telemetry = { onRerankEnd(event) { console.log('Rerank ended:', event.ranking.length); },};onEmbedFinish Renamed to onEmbedEnd
The telemetry integration callback for individual embedding model calls has been renamed from onEmbedFinish to onEmbedEnd.
This also applies to the type field emitted on AI_SDK_TELEMETRY_TRACING_CHANNEL: tracing-channel subscribers now receive onEmbedEnd instead of onEmbedFinish.
Update telemetry integrations and tracing-channel subscribers to use onEmbedEnd.
import type { Telemetry } from 'ai';
const telemetry: Telemetry = { onEmbedFinish(event) { console.log('Embedding finished:', event.embeddings.length); },};import type { Telemetry } from 'ai';
const telemetry: Telemetry = { onEmbedEnd(event) { console.log('Embedding ended:', event.embeddings.length); },};Streaming and Include Options
StreamTextResult.fullStream Renamed to stream
The full event stream returned by streamText has been renamed from
fullStream to stream.
const result = streamText({ model: yourModel, prompt: 'Hello!',});
for await (const part of result.fullStream) { console.log(part);}const result = streamText({ model: yourModel, prompt: 'Hello!',});
for await (const part of result.stream) { console.log(part);}The fullStream property is still available as a deprecated alias for
stream.
streamText onChunk Receives All Stream Parts
In AI SDK 7, streamText calls onChunk for every TextStreamPart emitted by
the stream. In AI SDK 6, onChunk only received a subset of stream parts such as
text deltas, reasoning deltas, sources, tool calls, tool input deltas, tool
results, custom parts, and raw chunks.
Update handlers that assume the old subset to guard the chunk types they process:
const result = streamText({ model: yourModel, prompt: 'Hello', onChunk({ chunk }) { if (chunk.type === 'text-delta') { console.log(chunk.text); } },});onChunk can now also receive lifecycle, boundary, and terminal parts such as
start, start-step, text-start, text-end, reasoning-start,
reasoning-end, tool-input-end, finish-step, finish, abort, and
error.
Move includeRawChunks to include.rawChunks
The top-level includeRawChunks option on streamText is deprecated in AI SDK 7. Move it into the include options object as rawChunks.
const result = streamText({ model: yourModel, prompt: 'Hello', includeRawChunks: true,});const result = streamText({ model: yourModel, prompt: 'Hello', include: { rawChunks: true, },});The deprecated top-level option continues to work in AI SDK 7, so you can migrate incrementally.
Rename experimental_include to include
The experimental_include option for generateText, streamText, and
ToolLoopAgent is now stable and has been renamed to include.
const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_include: { requestBody: false, },});const result = await generateText({ model: yourModel, prompt: 'Hello', include: { requestBody: false, },});The deprecated experimental_include name continues to work for backwards
compatibility. If both include and experimental_include are provided,
include takes precedence.
Request and Response Bodies Are Excluded by Default
In AI SDK 7, generateText and streamText no longer include request bodies in
step results by default. generateText also no longer includes response bodies
by default. This reduces memory usage, especially when prompts or provider
responses contain large payloads such as images or files.
If your application reads result.request.body, step.request.body,
result.response.body, or step.response.body, opt in with include.
const result = await generateText({ model: yourModel, prompt: 'Hello', include: { requestBody: true, responseBody: true, },});For streamText, only requestBody is available:
const result = streamText({ model: yourModel, prompt: 'Hello', include: { requestBody: true, },});Result Message Changes
Step Response Messages Are No Longer Accumulated
In AI SDK 7, step.response.messages on each StepResult only contains the
response messages produced by that particular step.
In AI SDK 6, step.response.messages accumulated response messages from all
previous steps. If your application reads step.response.messages from an
intermediate or final step and expects the full assistant/tool message history,
use the top-level result.responseMessages instead.
const result = await generateText({ model: yourModel, prompt: 'Use tools if needed', tools, stopWhen: isStepCount(5),});
// Accumulated response messages from all steps:const responseMessages = result.responseMessages;
// Response messages produced by each individual step:const stepMessages = result.steps.map(step => step.response.messages);If you specifically need to reconstruct response messages from step results, flatten the per-step messages:
const responseMessages = result.steps.flatMap(step => step.response.messages);Prefer result.responseMessages when you want the full response message history.
It also includes response messages produced before the first model step, such as
tool results from approved tool calls in the input messages.
Tools and Tool Execution
Tool Execution Callbacks
The tool execution callback options for generateText and streamText have been renamed:
experimental_onToolCallStartis nowonToolExecutionStartexperimental_onToolCallFinishis nowonToolExecutionEnd
The old names continue to work as deprecated aliases in AI SDK 7 and will be removed in a future major release. They are only used as fallbacks when the new callback names are not provided.
const result = await generateText({ model: yourModel, tools, prompt: 'Hello', experimental_onToolCallStart(event) { console.log('Tool starting:', event.toolCall.toolName); }, experimental_onToolCallFinish(event) { console.log('Tool finished:', event.toolCall.toolName); },});const result = await generateText({ model: yourModel, tools, prompt: 'Hello', onToolExecutionStart(event) { console.log('Tool starting:', event.toolCall.toolName); }, onToolExecutionEnd(event) { console.log('Tool finished:', event.toolCall.toolName); },});Context: experimental_context Became Tool context, and Shared Runtime Data Moved to runtimeContext
In AI SDK 7, the tool callback option previously exposed as experimental_context has been renamed to context and is now stable.
The bigger behavioral change is that AI SDK now separates:
- tool-specific
context, which is passed to tool callbacks fromtoolsContext - shared generation/agent runtime data, which flows through
runtimeContext
In AI SDK 6, tools often read from one generic runtime context object. In AI SDK 7, each tool gets its own scoped context, and you provide those values through toolsContext, keyed by tool name.
tool() infers the type of a tool's context from its contextSchema, so each tool only sees the fields declared for that specific tool.
In AI SDK 6, tool code often accessed experimental_context and had to cast it manually:
const weather = tool({ inputSchema: z.object({ location: z.string(), }), execute: async ({ location }, { experimental_context }) => { const { weatherApiKey } = experimental_context as { weatherApiKey: string; };
return getWeather(location, weatherApiKey); },});In AI SDK 7, rename experimental_context to context, declare the tool-specific context with contextSchema, and pass the per-tool values through toolsContext. The execute callback, approval callback, and tool input lifecycle hooks then receive a typed context automatically:
const weather = tool({ inputSchema: z.object({ location: z.string(), }), contextSchema: z.object({ apiKey: z.string(), }), execute: async ({ location }, { context: { apiKey } }) => { return getWeather(location, apiKey); },});
const result = await generateText({ model: yourModel, tools: { weather }, runtimeContext: { requestId: 'req-123', }, toolsContext: { weather: { apiKey: process.env.WEATHER_API_KEY!, }, }, prepareStep: async ({ runtimeContext, toolsContext }) => { console.log(runtimeContext.requestId); console.log(toolsContext.weather.apiKey);
return {}; },});Use runtimeContext for shared generation or agent state that should be visible in prepareStep, events, and step results. Tool callbacks no longer read from that shared object. A tool's context now comes from its own entry in toolsContext, so it is limited to the fields that tool declared in contextSchema.
Potential breaking issues:
- If you still destructure
experimental_contextin tool callbacks, rename it tocontext. - If you were previously passing one shared runtime object for both orchestration and tools, split it into
runtimeContextandtoolsContext. - Move tool-specific values into
toolsContextunder the matching tool name. - Rename shared top-level
contextusage toruntimeContextingenerateText,streamText, andToolLoopAgent. - Rename
prepareStepusage fromcontexttoruntimeContext. - If a tool callback accesses fields that are not declared in its
contextSchema, TypeScript will now report errors. Add those fields to that tool's schema. - If you have helper types or wrappers that assumed
experimental_contextorcontextwasunknown, update them to accept a genericCONTEXTtype. - If at least one tool declares
contextSchema,toolsContextbecomes required and only includes the tools that actually declare contextual data. - If you were relying on every tool callback seeing the same full runtime object, move shared step data to
runtimeContext, or explicitly provide the needed per-tool data in each tool'stoolsContextentry.
Migrate Deprecated needsApproval to toolApproval
The needsApproval property on tool() and dynamicTool() is deprecated in
AI SDK 7 for generateText, streamText, and ToolLoopAgent.
Move approval logic to the toolApproval setting on the call or agent instead.
This keeps approval policy close to the generation or agent configuration and
lets you vary it by request.
const deleteFile = tool({ inputSchema: z.object({ path: z.string(), }), needsApproval: async ({ path }) => !path.startsWith('/tmp/'), execute: async ({ path }) => { await removeFile(path); return { success: true }; },});
await streamText({ model: yourModel, tools: { deleteFile },});const deleteFile = tool({ inputSchema: z.object({ path: z.string(), }), execute: async ({ path }) => { await removeFile(path); return { success: true }; },});
await streamText({ model: yourModel, tools: { deleteFile }, toolApproval: { deleteFile: async ({ path }) => path.startsWith('/tmp/') ? undefined : 'user-approval', },});If you were using needsApproval: true, migrate it to
toolApproval: { myTool: 'user-approval' }. If you were using a
needsApproval function, move that logic into a per-tool
SingleToolApprovalFunction or a generic toolApproval callback. This
guidance applies to generateText, streamText, and ToolLoopAgent.
Remove Deprecated experimental_activeTools Option
The deprecated experimental_activeTools option has been removed in AI SDK 7. Replace all remaining usages with activeTools.
This option was deprecated in AI SDK 5, so if you already migrated to activeTools, no changes are needed. Otherwise, update both generateText and streamText calls:
const result = await generateText({ model: yourModel, tools: { weather }, experimental_activeTools: ['weather'],});const result = await generateText({ model: yourModel, tools: { weather }, activeTools: ['weather'],});Remove Deprecated ToolCallOptions Type
The deprecated ToolCallOptions type has been removed in AI SDK 7. Replace all remaining usages with ToolExecutionOptions.
If you already migrated away from the deprecated name in AI SDK 6, no changes are needed. Otherwise, update imports and type references:
import { ToolCallOptions } from 'ai';
function executeWithOptions(options: ToolCallOptions) { // ...}import { ToolExecutionOptions } from 'ai';
function executeWithOptions(options: ToolExecutionOptions) { // ...}UI Messages
Remove Deprecated isToolOrDynamicToolUIPart Function
The deprecated isToolOrDynamicToolUIPart function has been removed in AI SDK 7. Replace all remaining usages with isToolUIPart.
If you already migrated away from the deprecated name in AI SDK 6, no changes are needed. Otherwise, update imports and calls:
import { isToolOrDynamicToolUIPart } from 'ai';
if (isToolOrDynamicToolUIPart(part)) { console.log('Tool part found');}import { isToolUIPart } from 'ai';
if (isToolUIPart(part)) { console.log('Tool part found');}Tool and Message Content Parts
Remove Deprecated media Content Part Type
The deprecated tool result content part of { type: 'media' } has been removed in AI SDK 7.
Use { type: 'file-data' } for all inline file content, including images.
Tool Result Content: Migrate Away From image-* and file-* variants to file
All image-* and legacy file-* content part types for toModelOutput results are deprecated in AI SDK 7 in favor of a single canonical file variant that mirrors the top-level FilePart shape. Auto-migration is applied at runtime, so existing tool outputs continue to work without code changes. However, updating to the new shape is recommended.
The new shape carries a tagged data discriminated union, and it always has mediaType:
{ type: 'file-data', data, mediaType, filename? }→{ type: 'file', mediaType, filename, data: { type: 'data', data } }{ type: 'file-url', url, mediaType }→{ type: 'file', mediaType, data: { type: 'url', url: new URL(url) } }{ type: 'file-reference', providerReference }→{ type: 'file', mediaType, data: { type: 'reference', reference: providerReference } }
Images are just files with an image media type, so the image-* aliases collapse into the same shape — pass mediaType: 'image' (or a more specific image/* subtype) instead:
{ type: 'image-data', data, mediaType }→{ type: 'file', mediaType, data: { type: 'data', data } }{ type: 'image-url', url }→{ type: 'file', mediaType: 'image', data: { type: 'url', url: new URL(url) } }{ type: 'image-file-reference', providerReference }→{ type: 'file', mediaType: 'image', data: { type: 'reference', reference: providerReference } }
The -id content part types (file-id and image-file-id) are also generally deprecated in favor of the reference data shape, which carries a full ProviderReference (a provider-to-file-ID map like { openai: 'file_123', anthropic: 'file_abc' }) that lets the same logical file be reused across providers. The single-ID -id variants required the runtime to know which provider the ID belonged to; the explicit reference shape removes that ambiguity:
{ type: 'file-id', fileId }→{ type: 'file', mediaType, data: { type: 'reference', reference: { [provider]: fileId } } }{ type: 'image-file-id', fileId }→{ type: 'file', mediaType: 'image', data: { type: 'reference', reference: { [provider]: fileId } } }
mediaType on the new file variant accepts either a full IANA type (e.g. 'image/png') or just a top-level segment (e.g. 'image', 'audio', 'video', 'text'). When only a top-level segment is provided, the subtype is auto-detected from inline bytes where possible. Update parsing/validation and discriminated unions to handle the single canonical file variant.
Message Parts: Migrate Away From Deprecated image Part
The { type: 'image', image, mediaType? } user-message content part is deprecated. Use { type: 'file', data, mediaType } with an image mediaType instead.
{ type: 'image', image: bytes }{ type: 'file', mediaType: 'image', data: bytes }mediaType on FilePart now accepts either a full IANA type (e.g. 'image/png') or just a top-level segment (e.g. 'image'). When only a top-level segment is provided, the subtype is auto-detected from inline bytes where possible.
Message Parts: Handle New reasoning-file Content Type
Some models can now return files as part of their reasoning trace, separate from regular output files. These files, which would have previously were using the file type, are now in a distinct reasoning-file type.
Update all exhaustive part handling (TypeScript unions, switch statements, runtime validators, renderers, serializers) to support type: 'reasoning-file'.
Also audit any code that reads generated files from result.files or step.files: files referenced in reasoning are now represented as reasoning-file parts in content / reasoning. In practice, this should rarely require an update because models usually reference the same file in their reasoning that they later output as regular content. Prior to supporting reasoning-file as a distinct type, this would result in the same files appearing in result.files or step.files as a duplicate.
Reasoning
Reasoning Configuration: Remove Overlapping Settings
The new top-level reasoning option is the provider-agnostic way to control reasoning effort.
When migrating to the top-level reasoning option, remove overlapping reasoning settings from providerOptions.
If both are present, provider-specific reasoning settings in providerOptions take precedence, which can silently bypass your new top-level reasoning configuration.
Multi-Step Result Shape
generateText and streamText usage Now Includes All Steps
The usage property on generateText and streamText results now returns the total token usage across all steps. This matches what totalUsage previously represented.
Use finalStep.usage to read the previous usage behavior, which only returned token usage from the final step:
const result = await generateText({ model: yourModel, prompt: 'Write a haiku, then revise it.', stopWhen: stepCountIs(2),});
console.log(result.usage);console.log(result.totalUsage);const result = await generateText({ model: yourModel, prompt: 'Write a haiku, then revise it.', stopWhen: isStepCount(2),});
console.log(result.finalStep.usage); // final step onlyconsole.log(result.usage); // all stepstotalUsage is deprecated. Replace result.totalUsage with result.usage.
generateText and streamText Result Properties Now Include All Steps
The top-level content, toolCalls, staticToolCalls, dynamicToolCalls,
toolResults, staticToolResults, dynamicToolResults, files, sources,
and warnings properties on generateText and streamText results now return
values accumulated across every step.
In AI SDK 6, these properties only returned values from the final step. Use
finalStep to read the previous behavior:
const result = await generateText({ model: yourModel, prompt: 'Use a tool, then summarize the result.', stopWhen: stepCountIs(2),});
console.log(result.toolCalls);console.log(result.toolResults);console.log(result.files);console.log(result.sources);console.log(result.warnings);console.log(result.content);const result = await generateText({ model: yourModel, prompt: 'Use a tool, then summarize the result.', stopWhen: isStepCount(2),});
console.log(result.finalStep.toolCalls); // final step onlyconsole.log(result.finalStep.toolResults); // final step onlyconsole.log(result.finalStep.files); // final step onlyconsole.log(result.finalStep.sources); // final step onlyconsole.log(result.finalStep.warnings); // final step onlyconsole.log(result.finalStep.content); // final step onlyconsole.log(result.toolCalls); // all stepsconsole.log(result.toolResults); // all stepsconsole.log(result.files); // all stepsconsole.log(result.sources); // all stepsconsole.log(result.warnings); // all stepsconsole.log(result.content); // all stepsFor streamText, await finalStep first when you need final-step-only values:
const result = streamText({ model: yourModel, prompt: 'Use a tool, then summarize the result.', stopWhen: isStepCount(2),});
const finalStep = await result.finalStep;
console.log(finalStep.toolCalls); // final step onlyconsole.log(finalStep.toolResults); // final step onlyconsole.log(finalStep.files); // final step onlyconsole.log(finalStep.sources); // final step onlyconsole.log(finalStep.warnings); // final step onlyconsole.log(finalStep.content); // final step onlyconsole.log(await result.toolCalls); // all stepsconsole.log(await result.toolResults); // all stepsconsole.log(await result.files); // all stepsconsole.log(await result.sources); // all stepsconsole.log(await result.warnings); // all stepsconsole.log(await result.content); // all stepsFinal-Step Result Properties Moved to finalStep
The top-level reasoning, reasoningText, request, response, and providerMetadata result properties on generateText and streamText are deprecated in AI SDK 7.
Use result.finalStep when you need final-step reasoning or final-step metadata:
const result = await generateText({ model: yourModel, prompt: 'Write a haiku, then revise it.', stopWhen: stepCountIs(2),});
console.log(result.reasoningText);console.log(result.request.body);console.log(result.response.headers);console.log(result.providerMetadata?.anthropic);const result = await generateText({ model: yourModel, prompt: 'Write a haiku, then revise it.', stopWhen: isStepCount(2),});
console.log(result.finalStep.reasoningText);console.log(result.finalStep.request.body);console.log(result.finalStep.response.headers);console.log(result.finalStep.providerMetadata?.anthropic);For streamText, await finalStep first:
const result = streamText({ model: yourModel, prompt: 'Write a haiku, then revise it.', stopWhen: isStepCount(2),});
const finalStep = await result.finalStep;
console.log(finalStep.reasoningText);console.log(finalStep.request.body);console.log(finalStep.response.headers);The deprecated top-level aliases continue to work in AI SDK 7 so you can migrate incrementally.
generateText and streamText onEnd Result Properties Changed
The onEnd callback for generateText, streamText, and agents follows
the same result property changes as generateText and streamText results:
usagenow contains usage across all steps.totalUsageis deprecated; useusageinstead.content,toolCalls,toolResults,files,sources, andwarningsnow contain values from all steps.- Final-step-only properties are available on
finalStep. The top-levelreasoning,reasoningText,request,response, andproviderMetadataproperties are deprecated.
const result = await generateText({ model: yourModel, prompt: 'Use a tool, then summarize the result.', stopWhen: stepCountIs(2), onFinish(event) { console.log(event.usage); // final step only console.log(event.totalUsage); // all steps console.log(event.toolCalls); // final step only console.log(event.providerMetadata); },});const result = await generateText({ model: yourModel, prompt: 'Use a tool, then summarize the result.', stopWhen: isStepCount(2), onEnd(event) { console.log(event.finalStep.usage); // final step only console.log(event.usage); // all steps console.log(event.finalStep.toolCalls); // final step only console.log(event.toolCalls); // all steps console.log(event.finalStep.providerMetadata); },});The deprecated top-level aliases on the onEnd event continue to work in AI
SDK 7 so you can migrate incrementally.
Stream Response Helpers
streamText Response Helpers Deprecated — Use Stateless Helpers
The toUIMessageStream, toUIMessageStreamResponse, pipeUIMessageStreamToResponse, toTextStreamResponse, and pipeTextStreamToResponse methods on the streamText result are now deprecated. They still work in v7 (with deprecation warnings) and will be removed in the next major release.
The equivalent stateless helpers live on the top-level 'ai' export. Use them directly so the same transformation can be composed over any stream / textStream — not just a streamText result.
UI message stream
const result = streamText({ model, prompt });
const uiStream = result.toUIMessageStream({ originalMessages, generateMessageId, onFinish,});import { streamText, toUIMessageStream } from 'ai';
const result = streamText({ model, prompt });
const uiStream = toUIMessageStream({ stream: result.stream, generateMessageId, originalMessages, onFinish,});UI message stream Response
return result.toUIMessageStreamResponse({ originalMessages });import { createUIMessageStreamResponse, toUIMessageStream } from 'ai';
const uiStream = toUIMessageStream({ stream: result.stream, generateMessageId, originalMessages, onFinish,});
return createUIMessageStreamResponse({ stream: uiStream,});Pipe UI message stream to Node.js response
result.pipeUIMessageStreamToResponse(response, { originalMessages });import { pipeUIMessageStreamToResponse, toUIMessageStream } from 'ai';
const uiStream = toUIMessageStream({ stream: result.stream, generateMessageId, originalMessages, onFinish,});
pipeUIMessageStreamToResponse({ stream: uiStream, response,});Text stream Response
return result.toTextStreamResponse();import { createTextStreamResponse, toTextStream } from 'ai';
const textStream = toTextStream({ stream: result.stream,});
return createTextStreamResponse({ stream: textStream });Pipe text stream to Node.js response
result.pipeTextStreamToResponse(response);import { pipeTextStreamToResponse, toTextStream } from 'ai';
const textStream = toTextStream({ stream: result.stream,});
pipeTextStreamToResponse({ response, stream: textStream });MCP Package
MCP Transport: redirect Default Changed from 'follow' to 'error'
The redirect option on MCPTransportConfig (used by both HTTP and SSE transports) now defaults to 'error' instead of 'follow'. This means HTTP redirects are rejected by default to prevent server-side request forgery (SSRF) attacks where an MCP server could redirect requests to unintended hosts.
If your MCP server relies on HTTP redirects, explicitly set redirect: 'follow' in your transport configuration:
const mcpClient = await createMCPClient({ transport: { type: 'http', url: 'https://your-server.com/mcp', },});const mcpClient = await createMCPClient({ transport: { type: 'http', url: 'https://your-server.com/mcp', redirect: 'follow', },});If the MCP server you use does not issue redirects, no changes are needed — the new default is more secure.
Vue Package
Chat Class Deprecated in Favor of useChat Composable
The Chat class exported from @ai-sdk/vue is deprecated in AI SDK 7 in favor of the new useChat composable. useChat exposes reactive refs for messages, status, and error, and automatically recreates the underlying chat when its init object changes — so reactive inputs (a route param, a selected model, etc.) flow through without manual orchestration.
<script setup lang="ts">import { Chat } from '@ai-sdk/vue';
const chat = new Chat({});</script>
<template> <div v-for="m in chat.messages" :key="m.id"> <!-- ... --> </div> <button @click="chat.sendMessage({ text: 'hi' })">Send</button></template><script setup lang="ts">import { useChat } from '@ai-sdk/vue';
const { messages, sendMessage } = useChat({ id: chatId, transport: new DefaultChatTransport({ api: `/api/chats/${chatId}`, body: { model: model.value }, }),});</script>
<template> <div v-for="m in messages" :key="m.id"> <!-- ... --> </div> <button @click="sendMessage({ text: 'hi' })">Send</button></template>To make the init reactive (recreate the chat when its inputs change), pass a getter or ref:
const { messages, sendMessage } = useChat(() => ({ id: chatId.value, transport: new DefaultChatTransport({ api: `/api/chats/${chatId.value}`, body: { model: model.value }, }),}));The Chat class continues to work as a deprecated export, so you can migrate incrementally.
OpenAI Provider
Responses Reasoning Summary Defaults to Detailed
For the OpenAI Responses provider, setting reasoning or providerOptions.openai.reasoningEffort to a value other than 'none' now defaults providerOptions.openai.reasoningSummary to 'detailed'.
If you want to keep reasoning summaries disabled, set providerOptions.openai.reasoningSummary to null.
Anthropic Provider
providerMetadata.anthropic.cacheCreationInputTokens Removed
The Anthropic-specific cacheCreationInputTokens field on providerMetadata.anthropic has been removed from the responses of generateText and streamText (as well as the @ai-sdk/google-vertex/anthropic sub-provider). This duplicated information that is already available on the standard, provider-agnostic usage object.
Use result.usage.inputTokenDetails.cacheWriteTokens to read the number of tokens written to the cache, and result.usage.inputTokenDetails.cacheReadTokens to read the number of tokens served from the cache:
const result = await generateText({ model: anthropic('claude-sonnet-4-5'), messages: [ /* ... messages with cacheControl ... */ ],});
console.log(result.providerMetadata?.anthropic?.cacheCreationInputTokens);const result = await generateText({ model: anthropic('claude-sonnet-4-5'), messages: [ /* ... messages with cacheControl ... */ ],});
console.log(result.usage.inputTokenDetails.cacheWriteTokens);If you need the raw Anthropic-shaped usage payload (including cache_creation_input_tokens, cache_read_input_tokens, cache_creation, service_tier, etc.), it is still available unchanged at result.finalStep.providerMetadata?.anthropic?.usage.
Google Provider
Renamed Types, Classes, and Functions: GenerativeAI Affix Removed
Every type, class, and function in @ai-sdk/google that contained GoogleGenerativeAI has been renamed to use simply Google — for example, createGoogleGenerativeAI → createGoogle and GoogleGenerativeAIProvider → GoogleProvider.
The old names still work as deprecated aliases, but you should migrate to the new names, if you currently reference them.
The main entry point the google constant, remains unchanged, so if that's all you use from the provider, no changes are required.
xAI Provider
Default Model Now Uses the Responses API
In AI SDK 7, xai(modelId) (and xai.languageModel(modelId)) uses the xAI Responses API by default instead of the Chat Completions API. Both APIs were already available in AI SDK 6 via xai.chat(modelId) and xai.responses(modelId); AI SDK 7 only changes which one xai(modelId) uses by default. To keep using the Chat Completions API, use xai.chat(modelId).
// used the Chat Completions APIconst model = xai('grok-4.3');// now uses the Responses APIconst model = xai('grok-4.3');
// use the Chat Completions API explicitlyconst chatModel = xai.chat('grok-4.3');Migration Skill
Use the command below to add the migration skill and guide your agent
npx skills add vercel/ai --skill migrate-ai-sdk-v6-to-v7