# AI SDK documentation > The TypeScript toolkit for building AI applications and agents. ## When to use AI SDK - Category: Developer tools - Audience: JavaScript and TypeScript developers Common use cases: - Generate text and structured data - Build agents and chat interfaces - Integrate language model providers ## Documentation --- title: Building Agents description: Complete guide to creating agents with the ToolLoopAgent. url: "https://ai-sdk.dev/docs/agents/building-agents" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The ToolLoopAgent provides a structured way to encapsulate LLM configuration, tools, and behavior into reusable components. It handles the agent loop for you, allowing the LLM to call tools multiple times in sequence to accomplish complex tasks. Define agents once and use them across your application. ## Why Use the ToolLoopAgent Class? When building AI applications, you often need to: - **Reuse configurations** - Same model settings, tools, and prompts across different parts of your application - **Maintain consistency** - Ensure the same behavior and capabilities throughout your codebase - **Simplify API routes** - Reduce boilerplate in your endpoints - **Type safety** - Get full TypeScript support for your agent's tools and outputs The ToolLoopAgent class provides a single place to define your agent's behavior. ## Creating an Agent Define an agent by instantiating the ToolLoopAgent class with your desired configuration: ```ts import { ToolLoopAgent } from 'ai'; const myAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a helpful assistant.', tools: { // Your tools here }, }); ``` ## Configuration Options The ToolLoopAgent accepts all the same settings as `generateText` and `streamText`. Configure: ### Model and System Instructions ```ts import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are an expert software engineer.', }); ``` ### Tools Provide tools that the agent can use to accomplish tasks: ```ts import { ToolLoopAgent, tool } from 'ai'; import { z } from 'zod'; const codeAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { runCode: tool({ description: 'Execute Python code', inputSchema: z.object({ code: z.string(), }), execute: async ({ code }) => { // Execute code and return result return { output: 'Code executed successfully' }; }, }), }, }); ``` ### Context and Agent State Use `runtimeContext` as the agent's shared runtime state. It flows through the agent loop and is available in `prepareStep`, lifecycle callbacks, and final results. If a tool needs server-side values such as credentials, scoped permissions, or default settings, pass them through `toolsContext` and declare them with the tool's `contextSchema`. Set `runtimeContext` and `toolsContext` in the `ToolLoopAgent` constructor. ```ts import { ToolLoopAgent, tool } from 'ai'; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { searchTickets: tool({ description: 'Search support tickets', inputSchema: z.object({ query: z.string(), }), contextSchema: z.object({ apiKey: z.string(), accountId: z.string(), }), execute: async ({ query }, { context }) => searchTickets(query, context.accountId, context.apiKey), }), }, runtimeContext: { requestId: 'req_abc', escalated: false, }, toolsContext: { searchTickets: { apiKey: process.env.SUPPORT_API_KEY!, accountId: 'acct_123', }, }, prepareStep: async ({ runtimeContext }) => { if (runtimeContext.escalated) { return { temperature: 0.1 }; } return {}; }, }); const result = await agent.generate({ prompt: 'Find open billing tickets for this account.', }); ``` For context that changes with each invocation, use `callOptionsSchema` and `prepareCall`. See [Configuring Call Options](/docs/agents/configuring-call-options). Model call settings returned from `prepareStep`, such as `temperature`, apply only to the current step. Later steps use the agent's top-level setting unless they return another override. For the full model, including sensitive context filtering and where each context value is available, see [Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context). ### Tools That Use Experimental Sandboxes Pass `experimental_sandbox` when an agent tool needs a command or code execution environment. The experimental sandbox is a per-call value, so provide it to `generate()`, `stream()`, or the agent UI stream helper that invokes the agent. ```ts {13,19-23,31} const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a coding assistant. Use the shell tool when needed.', tools: { shell: tool({ description: 'Execute shell commands in the experimental sandbox.', inputSchema: z.object({ command: z.string(), workingDirectory: z.string().optional(), }), execute: async ( { command, workingDirectory }, { abortSignal, experimental_sandbox }, ) => { if (!experimental_sandbox) { throw new Error('Experimental sandbox is not available'); } return experimental_sandbox.run({ command, workingDirectory, abortSignal, }); }, }), }, }); const result = await agent.generate({ prompt: `Run the tests.\n\nSandbox:\n${experimental_sandbox.description}`, experimental_sandbox, }); ``` The experimental sandbox description is not added to the model prompt automatically. Include it in the prompt or instructions when the model needs to know environment details. Passing an experimental sandbox does not sandbox the tool itself; the tool must explicitly delegate operations to the experimental sandbox. See the [Experimental Sandbox section in Tool Calling](/docs/ai-sdk-core/tools-and-tool-calling#experimental-sandbox) for more details. You can also require approval before a tool executes. Configure approval on the `ToolLoopAgent` with `toolApproval`: ```ts const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { runCode: tool({ description: 'Execute Python code', inputSchema: z.object({ code: z.string(), }), execute: async ({ code }) => ({ output: code }), }), }, toolApproval: { runCode: 'user-approval', }, }); ``` For manual approvals, automatic approvals and denials, dynamic policy functions, and `useChat` integration, see [Tool Approvals](/docs/agents/tool-approvals). ### Loop Control By default, agents run for 20 steps (`stopWhen: isStepCount(20)`). In each step, the model either generates text or calls a tool. If it generates text, the agent completes. If it calls a tool, the AI SDK executes that tool. You can configure `stopWhen` differently to allow more steps. After each tool execution, the agent triggers a new generation where the model can call another tool or generate text: ```ts import { ToolLoopAgent, isStepCount } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", stopWhen: isStepCount(50), // Increase default from 20 to 50. }); ``` Each step represents one generation (which results in either text or a tool call). The loop continues until: - A finish reasoning other than tool-calls is returned, or - A tool that is invoked does not have an execute function, or - A tool call needs approval, or - A stop condition is met You can combine multiple conditions: ```ts import { ToolLoopAgent, isStepCount } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", stopWhen: [ isStepCount(20), // Maximum 20 steps yourCustomCondition(), // Custom logic for when to stop ], }); ``` Learn more about [loop control and stop conditions](/docs/agents/loop-control). ### Tool Choice Control how the agent uses tools: ```ts import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { // your tools here }, toolChoice: 'required', // Force tool use // or toolChoice: 'none' to disable tools // or toolChoice: 'auto' (default) to let the model decide }); ``` You can also force the use of a specific tool: ```ts import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { weather: weatherTool, cityAttractions: attractionsTool, }, toolChoice: { type: 'tool', toolName: 'weather', // Force the weather tool to be used }, }); ``` ### Structured Output Define structured output schemas: ```ts import { ToolLoopAgent, Output } from 'ai'; import { z } from 'zod'; const analysisAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ sentiment: z.enum(['positive', 'neutral', 'negative']), summary: z.string(), keyPoints: z.array(z.string()), }), }), }); const { output } = await analysisAgent.generate({ prompt: 'Analyze customer feedback from the last quarter', }); ``` ## Define Agent Behavior with System Instructions System instructions define your agent's behavior, personality, and constraints. They set the context for all interactions and guide how the agent responds to user queries and uses tools. ### Basic System Instructions Set the agent's role and expertise: ```ts const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are an expert data analyst. You provide clear insights from complex data.', }); ``` ### Detailed Behavioral Instructions Provide specific guidelines for agent behavior: ```ts const codeReviewAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: `You are a senior software engineer conducting code reviews. Your approach: - Focus on security vulnerabilities first - Identify performance bottlenecks - Suggest improvements for readability and maintainability - Be constructive and educational in your feedback - Always explain why something is an issue and how to fix it`, }); ``` ### Constrain Agent Behavior Set boundaries and ensure consistent behavior: ```ts const customerSupportAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: `You are a customer support specialist for an e-commerce platform. Rules: - Never make promises about refunds without checking the policy - Always be empathetic and professional - If you don't know something, say so and offer to escalate - Keep responses concise and actionable - Never share internal company information`, tools: { checkOrderStatus, lookupPolicy, createTicket, }, }); ``` ### Tool Usage Instructions Guide how the agent should use available tools: ```ts const researchAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: `You are a research assistant with access to search and document tools. When researching: 1. Always start with a broad search to understand the topic 2. Use document analysis for detailed information 3. Cross-reference multiple sources before drawing conclusions 4. Cite your sources when presenting information 5. If information conflicts, present both viewpoints`, tools: { webSearch, analyzeDocument, extractQuotes, }, }); ``` ### Format and Style Instructions Control the output format and communication style: ```ts const technicalWriterAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: `You are a technical documentation writer. Writing style: - Use clear, simple language - Avoid jargon unless necessary - Structure information with headers and bullet points - Include code examples where relevant - Write in second person ("you" instead of "the user") Always format responses in Markdown.`, }); ``` ## Using an Agent Once defined, you can use your agent in three ways: ### Generate Text Use `generate()` for one-time text generation: ```ts const result = await myAgent.generate({ prompt: 'What is the weather like?', }); console.log(result.text); ``` ### Stream Text Use `stream()` for streaming responses: ```ts const result = await myAgent.stream({ prompt: 'Tell me a story', }); for await (const chunk of result.textStream) { console.log(chunk); } ``` ### Respond to UI Messages Use `createAgentUIStreamResponse()` to create API responses for client applications: ```ts // In your API route (e.g., app/api/chat/route.ts) import { createAgentUIStreamResponse } from 'ai'; export async function POST(request: Request) { const { messages } = await request.json(); return createAgentUIStreamResponse({ agent: myAgent, uiMessages: messages, }); } ``` ### Lifecycle Callbacks Agents provide lifecycle callbacks that let you hook into different phases of the agent execution. These are useful for logging, observability, debugging, and custom telemetry. ```ts const result = await myAgent.generate({ prompt: 'Research and summarize the latest AI trends', onStart({ modelId }) { console.log('Agent started', { modelId }); }, onStepStart({ stepNumber, modelId }) { console.log(`Step ${stepNumber} starting`, { modelId }); }, onToolExecutionStart({ toolCall }) { console.log(`Tool call starting: ${toolCall.toolName}`); }, onToolExecutionEnd({ toolCall, toolExecutionMs, toolOutput }) { console.log( `Tool call finished: ${toolCall.toolName} (${toolExecutionMs}ms)`, { success: toolOutput.type === 'tool-result', }, ); }, onStepEnd({ stepNumber, usage, performance, finishReason, toolCalls }) { console.log(`Step ${stepNumber} completed:`, { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens, outputTokensPerSecond: performance.effectiveOutputTokensPerSecond, stepTimeMs: performance.stepTimeMs, finishReason, toolsUsed: toolCalls?.map(tc => tc.toolName), }); }, onEnd({ usage, steps }) { console.log('Agent finished:', { totalSteps: steps.length, totalTokens: usage.totalTokens, }); }, }); ``` The available lifecycle callbacks are: - **`onStart`**: Called once when the agent operation begins, before any LLM calls. Receives model info, messages, settings, and `runtimeContext`. - **`onStepStart`**: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps. - **`onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and `toolContext`. - **`onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call, `toolExecutionMs`, and a `toolOutput` discriminated union (`type: 'tool-result'` with `output`, or `type: 'tool-error'` with `error`). - **`onStepEnd`**: Called after each step finishes. Receives step results including usage, performance, finish reason, and tool calls. - **`onEnd`**: Called when all steps are finished and the response is complete. Receives all step results, total usage, and `runtimeContext`. For the full event data reference, see [Lifecycle Callbacks](/docs/ai-sdk-core/lifecycle-callbacks). `ToolLoopAgent` uses the same generation lifecycle event types for `onStart`, `onStepStart`, `onToolExecutionStart`, `onToolExecutionEnd`, `onStepEnd`, and `onEnd`. #### Constructor vs. Method Callbacks All lifecycle callbacks can be defined in the constructor for agent-wide tracking, in the `generate()`/`stream()` call for per-call tracking, or both. When both are provided, both are called (constructor first, then the method callback): ```ts const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", onStepEnd: async ({ stepNumber, usage }) => { // Agent-wide logging console.log(`Agent step ${stepNumber}:`, usage.totalTokens); }, }); // Method-level callback runs after constructor callback const result = await agent.generate({ prompt: 'Hello', onStepEnd: async ({ stepNumber, usage }) => { // Per-call tracking (e.g., for billing) await trackUsage(stepNumber, usage); }, }); ``` ## End-to-end Type Safety You can infer types for your agent's `UIMessage`s: ```ts import { ToolLoopAgent, InferAgentUIMessage } from 'ai'; const myAgent = new ToolLoopAgent({ // ... configuration }); // Infer the UIMessage type for UI components or persistence export type MyAgentUIMessage = InferAgentUIMessage; ``` Use this type in your client components with `useChat`: ```tsx title="components/chat.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import type { MyAgentUIMessage } from '@/agent/my-agent'; export function Chat() { const { messages } = useChat(); // Full type safety for your messages and tools } ``` ## Next Steps Now that you understand building agents, you can: - Explore [workflow patterns](/docs/agents/workflows) for structured patterns using core functions - Learn about [loop control](/docs/agents/loop-control) for advanced execution control - See [manual loop examples](/cookbook/node/manual-agent-loop) for custom workflow implementations --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Configuring Call Options description: Pass type-safe runtime inputs to dynamically configure agent behavior. url: "https://ai-sdk.dev/docs/agents/configuring-call-options" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Call options allow you to pass type-safe structured inputs to your agent. Use them to dynamically modify any agent setting based on the specific request. ## Why Use Call Options? When you need agent behavior to change based on runtime inputs: - **Add dynamic context** - Inject retrieved documents, user preferences, or session data into prompts - **Select models dynamically** - Choose faster or more capable models based on request complexity - **Configure tools per request** - Pass user location to search tools or adjust tool behavior - **Customize provider options** - Set reasoning effort, temperature, or other provider-specific settings Without call options, you'd need to create multiple agents or handle configuration logic outside the agent. ## How It Works Define call options in three steps: 1. **Define the schema** - Specify what inputs you accept using `callOptionsSchema` 2. **Configure with `prepareCall`** - Use those inputs to modify agent settings 3. **Pass options at runtime** - Provide the options when calling `generate()` or `stream()` ## Basic Example Add user context to your agent's prompt at runtime: ```ts import { ToolLoopAgent } from 'ai'; import { z } from 'zod'; const supportAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", callOptionsSchema: z.object({ userId: z.string(), accountType: z.enum(['free', 'pro', 'enterprise']), }), instructions: 'You are a helpful customer support agent.', prepareCall: ({ options, ...settings }) => ({ ...settings, instructions: settings.instructions + `\nUser context: - Account type: ${options.accountType} - User ID: ${options.userId} Adjust your response based on the user's account level.`, }), }); // Call the agent with specific user context const result = await supportAgent.generate({ prompt: 'How do I upgrade my account?', options: { userId: 'user_123', accountType: 'free', }, }); ``` The `options` parameter is now required and type-checked. If you don't provide it or pass incorrect types, TypeScript will error. ## Modifying Agent Settings Use `prepareCall` to modify any agent setting. The returned object replaces the call settings, so spread the incoming settings and override only the ones you want to change. ### Dynamic Model Selection Choose models based on request characteristics: ```ts import { ToolLoopAgent } from 'ai'; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", // Default model callOptionsSchema: z.object({ complexity: z.enum(['simple', 'complex']), }), prepareCall: ({ options, ...settings }) => ({ ...settings, model: options.complexity === 'simple' ? 'openai/gpt-6-luna' : 'openai/gpt-6-astra', }), }); // Use faster model for simple queries await agent.generate({ prompt: 'What is 2+2?', options: { complexity: 'simple' }, }); // Use more capable model for complex reasoning await agent.generate({ prompt: 'Explain quantum entanglement', options: { complexity: 'complex' }, }); ``` ### Dynamic Tool Configuration Configure tools based on runtime inputs: ```ts import { openai } from '@ai-sdk/openai'; import { ToolLoopAgent } from 'ai'; import { z } from 'zod'; const newsAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", callOptionsSchema: z.object({ userCity: z.string().optional(), userRegion: z.string().optional(), }), tools: { web_search: openai.tools.webSearch(), }, prepareCall: ({ options, ...settings }) => ({ ...settings, tools: { web_search: openai.tools.webSearch({ searchContextSize: 'low', userLocation: { type: 'approximate', city: options.userCity, region: options.userRegion, country: 'US', }, }), }, }), }); await newsAgent.generate({ prompt: 'What are the top local news stories?', options: { userCity: 'San Francisco', userRegion: 'California', }, }); ``` ### Provider-Specific Options Configure provider settings dynamically: ```ts import { OpenAILanguageModelResponsesOptions } from '@ai-sdk/openai'; import { ToolLoopAgent } from 'ai'; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: 'openai/gpt-6-astra', callOptionsSchema: z.object({ taskDifficulty: z.enum(['low', 'medium', 'high']), }), prepareCall: ({ options, ...settings }) => ({ ...settings, providerOptions: { openai: { reasoningEffort: options.taskDifficulty, } satisfies OpenAILanguageModelResponsesOptions, }, }), }); await agent.generate({ prompt: 'Analyze this complex scenario...', options: { taskDifficulty: 'high' }, }); ``` ## Advanced Patterns ### Retrieval Augmented Generation (RAG) Fetch relevant context and inject it into your prompt: ```ts import { ToolLoopAgent } from 'ai'; import { z } from 'zod'; const ragAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", callOptionsSchema: z.object({ query: z.string(), }), prepareCall: async ({ options, ...settings }) => { // Fetch relevant documents (this can be async) const documents = await vectorSearch(options.query); return { ...settings, instructions: `Answer questions using the following context: ${documents.map(doc => doc.content).join('\n\n')}`, }; }, }); await ragAgent.generate({ prompt: 'What is our refund policy?', options: { query: 'refund policy' }, }); ``` The `prepareCall` function can be async, enabling you to fetch data before configuring the agent. ### Combining Multiple Modifications Modify multiple settings together: ```ts import { ToolLoopAgent } from 'ai'; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", callOptionsSchema: z.object({ userRole: z.enum(['admin', 'user']), urgency: z.enum(['low', 'high']), }), tools: { readDatabase: readDatabaseTool, writeDatabase: writeDatabaseTool, }, prepareCall: ({ options, ...settings }) => ({ ...settings, // Upgrade model for urgent requests model: options.urgency === 'high' ? "anthropic/claude-sonnet-5.5" : settings.model, // Limit tools based on user role activeTools: options.userRole === 'admin' ? ['readDatabase', 'writeDatabase'] : ['readDatabase'], // Adjust instructions instructions: `You are a ${options.userRole} assistant. ${options.userRole === 'admin' ? 'You have full database access.' : 'You have read-only access.'}`, }), }); await agent.generate({ prompt: 'Update the user record', options: { userRole: 'admin', urgency: 'high', }, }); ``` ## Using with createAgentUIStreamResponse Pass call options through API routes to your agent: ```ts title="app/api/chat/route.ts" import { createAgentUIStreamResponse } from 'ai'; import { myAgent } from '@/ai/agents/my-agent'; export async function POST(request: Request) { const { messages, userId, accountType } = await request.json(); return createAgentUIStreamResponse({ agent: myAgent, messages, options: { userId, accountType, }, }); } ``` ## Next Steps - Learn about [loop control](/docs/agents/loop-control) for execution management - Explore [workflow patterns](/docs/agents/workflows) for complex multi-step processes --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Loop Control description: Control agent execution with built-in loop management using stopWhen and prepareStep url: "https://ai-sdk.dev/docs/agents/loop-control" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). You can control both the execution flow and the settings at each step of the agent loop. The loop continues until: - A finish reasoning other than tool-calls is returned, or - A tool that is invoked does not have an execute function, or - A tool call needs approval, or - A stop condition is met The AI SDK provides built-in loop control through two parameters: `stopWhen` for defining stopping conditions and `prepareStep` for modifying settings (model, tools, messages, and more) between steps. ## Stop Conditions The `stopWhen` parameter controls when to stop execution when there are tool results in the last step. By default, `ToolLoopAgent` stops after 20 steps using `isStepCount(20)`. This default is a safety measure to prevent runaway loops that could result in excessive API calls and costs. If this default limit stops a tool loop that could otherwise continue, the SDK logs a warning with guidance on configuring `stopWhen`. Set `stopWhen` explicitly to choose a limit, even if you want to keep `isStepCount(20)`. Explicit stop conditions do not produce this warning. `WorkflowAgent` does not apply a default step limit. It continues until the model stops calling tools or another natural termination condition is met. Configure an explicit condition such as `isStepCount(20)` when you need to bound its model calls. See [WorkflowAgent Loop Control](/docs/agents/workflow-agent#loop-control) for details. When you provide `stopWhen`, the agent continues executing after tool calls until a stopping condition is met. When the condition is an array, execution stops when any of the conditions are met. ### Use Built-in Conditions The AI SDK provides several built-in stopping conditions: - `isStepCount(count)` — stops after a specified number of steps - `hasToolCall(...toolNames)` — stops when any of the specified tools is called - `isLoopFinished()` — never triggers, letting the loop run until the agent is naturally finished ### Run Up to a Maximum Number of Steps ```ts import { ToolLoopAgent, isStepCount } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { // your tools }, stopWhen: isStepCount(50), // Increase ToolLoopAgent's default from 20 to 50. }); const result = await agent.generate({ prompt: 'Analyze this dataset and create a summary report', }); ``` ### Run Until Finished If you want a `ToolLoopAgent` to run until the model naturally stops making tool calls, use `isLoopFinished()`. This removes its default step limit: ```ts import { ToolLoopAgent, isLoopFinished } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { // your tools }, stopWhen: isLoopFinished(), // No maximum step limit. }); const result = await agent.generate({ prompt: 'Analyze this dataset and create a summary report', }); ``` Use `isLoopFinished()` with caution. Without a step limit, the agent could potentially run indefinitely or incur significant costs if the model keeps making tool calls. ### Combine Multiple Conditions Combine multiple stopping conditions. The loop stops when it meets any condition: ```ts import { ToolLoopAgent, isStepCount, hasToolCall } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { // your tools }, stopWhen: [ isStepCount(20), // Maximum 20 steps hasToolCall('someTool', 'done'), // Stop after calling either tool ], }); const result = await agent.generate({ prompt: 'Research and analyze the topic', }); ``` ### Create Custom Conditions Build custom stopping conditions for specific requirements: ```ts import { ToolLoopAgent, StopCondition, ToolSet } from 'ai'; const tools = { // your tools } satisfies ToolSet; const hasAnswer: StopCondition = ({ steps }) => { // Stop when the model generates text containing "ANSWER:" return steps.some(step => step.text?.includes('ANSWER:')) ?? false; }; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools, stopWhen: hasAnswer, }); const result = await agent.generate({ prompt: 'Find the answer and respond with "ANSWER: [your answer]"', }); ``` Custom conditions receive step information across all steps: ```ts const budgetExceeded: StopCondition = ({ steps }) => { const totalUsage = steps.reduce( (acc, step) => ({ inputTokens: acc.inputTokens + (step.usage?.inputTokens ?? 0), outputTokens: acc.outputTokens + (step.usage?.outputTokens ?? 0), }), { inputTokens: 0, outputTokens: 0 }, ); const costEstimate = (totalUsage.inputTokens * 0.01 + totalUsage.outputTokens * 0.03) / 1000; return costEstimate > 0.5; // Stop if cost exceeds $0.50 }; ``` ## Prepare Step The `prepareStep` callback runs before each step in the loop and defaults to the initial settings if you don't return any changes. Use it to modify settings, manage context, or implement dynamic behavior based on execution history. It receives `messages` for the current step, plus `initialMessages` and `responseMessages` when you need to distinguish the original input from assistant/tool messages accumulated in earlier steps. Treat `messages` as the loop's current message state: if you return a `messages` override, that override persists as the base for later steps, together with the response messages from each completed step. ### Dynamic Model Selection Switch models based on step requirements: ```ts import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: 'openai/gpt-6-luna', // Default model tools: { // your tools }, prepareStep: async ({ stepNumber, messages }) => { // Use a stronger model for complex reasoning after initial steps if (stepNumber > 2 && messages.length > 10) { return { model: "anthropic/claude-sonnet-5.5", }; } // Continue with default settings return {}; }, }); const result = await agent.generate({ prompt: '...', }); ``` ### Model Call Settings Override provider-agnostic model call settings for an individual step. This can be useful when tool-calling steps need more deterministic sampling than the final response: ```ts import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", temperature: 0.7, tools: { // your tools }, prepareStep: async ({ stepNumber }) => { if (stepNumber === 0) { return { temperature: 0, maxOutputTokens: 300, }; } return {}; }, }); const result = await agent.generate({ prompt: '...', }); ``` `prepareStep` can override `maxOutputTokens`, `temperature`, `topP`, `topK`, `presencePenalty`, `frequencyPenalty`, `stopSequences`, `seed`, and `reasoning`. These overrides apply only to the current step. When a setting is omitted or `undefined`, the top-level value is used for that step. Defined falsy values such as `temperature: 0`, `seed: 0`, and an empty `stopSequences` array are preserved. ### Context Management Long-running agents can accumulate large tool results, reasoning parts, and assistant messages. Use `prepareStep` to mutate the message state that will be used by later steps. This is useful for compaction, and you decide when compaction should happen. The `messages` value contains the messages that will be sent for the current step. When you return a `messages` override from `prepareStep`, that changed list becomes the base for later steps. New assistant and tool response messages are appended to it as the loop continues. If you need to rebuild a step from the discrete pieces instead of the persisted message state, use `initialMessages` for the original input and `responseMessages` for the model/tool response messages accumulated so far. The `pruneMessages` helper provides a built-in way to remove selected messages. You can use it inside `prepareStep` when you want a simple compaction strategy. ```ts import { ToolLoopAgent, pruneMessages, type ModelMessage } from 'ai'; const COMPACTION_THRESHOLD = 100_000; const estimateTokens = (messages: ModelMessage[]) => { return JSON.stringify(messages).length / 4; }; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { // your tools }, prepareStep: async ({ messages }) => { if (estimateTokens(messages) > COMPACTION_THRESHOLD) { return { messages: pruneMessages({ messages, reasoning: 'all', toolCalls: 'before-last-3-messages', emptyMessages: 'remove', }), }; } }, }); const result = await agent.generate({ prompt: '...', }); ``` The token estimator above is intentionally simple and only demonstrates one way to decide when to compact. The main point is that `prepareStep` can return a new `messages` array, and that array becomes the message state for following steps. The same pattern also works with `generateText` and `streamText`. ### Tool Selection Control which tools are available at each step: ```ts import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { search: searchTool, analyze: analyzeTool, summarize: summarizeTool, }, prepareStep: async ({ stepNumber, steps }) => { // Search phase (steps 0-2) if (stepNumber <= 2) { return { activeTools: ['search'], toolChoice: 'required', }; } // Analysis phase (steps 3-5) if (stepNumber <= 5) { return { activeTools: ['analyze'], }; } // Summary phase (step 6+) return { activeTools: ['summarize'], toolChoice: 'required', }; }, }); const result = await agent.generate({ prompt: '...', }); ``` You can also force a specific tool to be used: ```ts prepareStep: async ({ stepNumber }) => { if (stepNumber === 0) { // Force the search tool to be used first return { toolChoice: { type: 'tool', toolName: 'search' }, }; } if (stepNumber === 5) { // Force the summarize tool after analysis return { toolChoice: { type: 'tool', toolName: 'summarize' }, }; } return {}; }; ``` ### Message Modification Transform messages before sending them to the model. Returned messages carry forward to later steps, so later `messages` values include your transformed messages plus the assistant/tool response messages from completed steps: ```ts import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { // your tools }, prepareStep: async ({ messages, stepNumber }) => { // Summarize tool results to reduce token usage const processedMessages = messages.map(msg => { if (msg.role === 'tool' && msg.content.length > 1000) { return { ...msg, content: summarizeToolResult(msg.content), }; } return msg; }); return { messages: processedMessages }; }, }); const result = await agent.generate({ prompt: '...', }); ``` ### Experimental Sandbox Selection Switch the experimental sandbox used for tool execution in a single step: ```ts import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { runCommand, }, experimental_sandbox: defaultSandbox, prepareStep: async ({ stepNumber }) => { if (stepNumber === 0) { return { experimental_sandbox: setupSandbox, }; } return {}; }, }); const result = await agent.generate({ prompt: '...', }); ``` Unlike `runtimeContext` and `toolsContext`, an experimental sandbox returned from `prepareStep` only applies to tool execution in that step. Later steps use the top-level `experimental_sandbox` unless they return their own experimental sandbox override. ## Access Step Information Both `stopWhen` and `prepareStep` receive detailed information about the current execution: ```ts prepareStep: async ({ model, // Current model configuration stepNumber, // Current step number (0-indexed) steps, // All previous steps with their results messages, // Messages to be sent to the model }) => { // Access previous tool calls and results const previousToolCalls = steps.flatMap(step => step.toolCalls); const previousResults = steps.flatMap(step => step.toolResults); // Make decisions based on execution history if (previousToolCalls.some(call => call.toolName === 'dataAnalysis')) { return { toolChoice: { type: 'tool', toolName: 'reportGenerator' }, }; } return {}; }, ``` ## Forced Tool Calling You can force the agent to always use tools by combining `toolChoice: 'required'` with a `done` tool that has no `execute` function. This pattern ensures the agent uses tools for every step and stops only when it explicitly signals completion. ```ts import { ToolLoopAgent, tool } from 'ai'; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { search: searchTool, analyze: analyzeTool, done: tool({ description: 'Signal that you have finished your work', inputSchema: z.object({ answer: z.string().describe('The final answer'), }), // No execute function - stops the agent when called }), }, toolChoice: 'required', // Force tool calls at every step }); const result = await agent.generate({ prompt: 'Research and analyze this topic, then provide your answer.', }); // extract answer from done tool call const toolCall = result.staticToolCalls[0]; // tool call from final step if (toolCall?.toolName === 'done') { console.log(toolCall.input.answer); } ``` Key aspects of this pattern: - **`toolChoice: 'required'`**: Forces the model to call a tool at every step instead of generating text directly. This ensures the agent follows a structured workflow. - **`done` tool without `execute`**: A tool that has no `execute` function acts as a termination signal. When the agent calls this tool, the loop stops because there's no function to execute. - **Accessing results**: The final answer is available in `result.staticToolCalls`, which contains tool calls that weren't executed. This pattern is useful when you want the agent to always use specific tools for operations (like code execution or data retrieval) rather than attempting to answer directly. ## Manual Loop Control For scenarios requiring complete control over the agent loop, you can use AI SDK Core functions (`generateText` and `streamText`) to implement your own loop management instead of using `stopWhen` and `prepareStep`. This approach provides maximum flexibility for complex workflows. ### Implementing a Manual Loop Build your own agent loop when you need full control over execution: ```ts import { generateText, ModelMessage } from 'ai'; const messages: ModelMessage[] = [{ role: 'user', content: '...' }]; let step = 0; const maxSteps = 10; while (step < maxSteps) { const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages, tools: { // your tools here }, }); messages.push(...result.responseMessages); if (result.text) { break; // Stop when model generates text } step++; } ``` This manual approach gives you complete control over: - Message history management - Step-by-step decision making - Custom stopping conditions - Dynamic tool and model selection - Error handling and recovery [Learn more about manual agent loops in the cookbook](/cookbook/node/manual-agent-loop). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Memory description: Add persistent memory to your agent using provider-defined tools, memory providers, or a custom tool. url: "https://ai-sdk.dev/docs/agents/memory" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Memory lets your agent save information and recall it later. Without memory, every conversation starts fresh. With memory, your agent builds context over time, recalls previous interactions, and adapts to the user. ## Three Approaches You can add memory to your agent with the AI SDK in three ways, each with different tradeoffs: | Approach | Effort | Flexibility | Provider Lock-in | | ------------------------------------------------- | ------ | ----------- | -------------------------- | | [Provider-Defined Tools](#provider-defined-tools) | Low | Medium | Yes | | [Memory Providers](#memory-providers) | Low | Low | Depends on memory provider | | [Custom Tool](#custom-tool) | High | High | No | ## Provider-Defined Tools [Provider-defined tools](/docs/foundations/tools#types-of-tools) are tools where the provider specifies the tool's `inputSchema` and `description`, but you provide the `execute` function. The model has been trained to use these tools, which can result in better performance compared to custom tools. ### Anthropic Memory Tool The [Anthropic Memory Tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/memory-tool) gives Claude a structured interface for managing a `/memories` directory. Claude reads its memory before starting tasks, creates and updates files as it works, and references them in future conversations. ```ts import { anthropic } from '@ai-sdk/anthropic'; import { ToolLoopAgent } from 'ai'; const memory = anthropic.tools.memory_20250818({ execute: async action => { // `action` contains `command`, `path`, and other fields // depending on the command (view, create, str_replace, // insert, delete, rename). // Implement your storage backend here. // Return the result as a string. }, }); const agent = new ToolLoopAgent({ model: 'anthropic/claude-haiku-5.5', tools: { memory }, }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', }); ``` The tool receives structured commands (`view`, `create`, `str_replace`, `insert`, `delete`, `rename`), each with a `path` scoped to `/memories`. Your `execute` function maps these to your storage backend (the filesystem, a database, or any other persistence layer). **When to use this**: you want memory with minimal implementation effort and are already using Anthropic models. The tradeoff is provider lock-in, since this tool only works with Claude. ## Memory Providers Another approach is to use a provider that has memory built in. These providers wrap an external memory service and expose it through the AI SDK's standard interface. Memory storage, retrieval, and injection happen transparently, and you do not define any tools yourself. ### Letta [Letta](https://letta.com) provides agents with persistent long-term memory. You create an agent on Letta's platform (cloud or self-hosted), configure its memory there, and use the AI SDK provider to interact with it. Letta's agent runtime handles memory management (core memory, archival memory, recall). ```bash pnpm add @letta-ai/vercel-ai-sdk-provider ``` ```ts import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider'; import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: lettaCloud(), providerOptions: { letta: { agent: { id: 'your-agent-id' }, }, }, }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', }); ``` You can also use Letta's built-in memory tools alongside custom tools: ```ts import { lettaCloud } from '@letta-ai/vercel-ai-sdk-provider'; import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: lettaCloud(), tools: { core_memory_append: lettaCloud.tool('core_memory_append'), memory_insert: lettaCloud.tool('memory_insert'), memory_replace: lettaCloud.tool('memory_replace'), }, providerOptions: { letta: { agent: { id: 'your-agent-id' }, }, }, }); const stream = agent.stream({ prompt: 'What do you remember about me?', }); ``` See the [Letta provider documentation](/providers/community-providers/letta) for full setup and configuration. ### Mem0 [Mem0](https://mem0.ai) adds a memory layer on top of any supported LLM provider. It automatically extracts memories from conversations, stores them, and retrieves relevant ones for future prompts. ```bash pnpm add @mem0/vercel-ai-provider ``` ```ts import { createMem0 } from '@mem0/vercel-ai-provider'; import { ToolLoopAgent } from 'ai'; const mem0 = createMem0({ provider: 'openai', mem0ApiKey: process.env.MEM0_API_KEY, apiKey: process.env.OPENAI_API_KEY, }); const agent = new ToolLoopAgent({ model: mem0('gpt-6-astra', { user_id: 'user-123' }), }); const { text } = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', }); ``` Mem0 works across multiple LLM providers (OpenAI, Anthropic, Google, Groq, Cohere). You can also manage memories explicitly: ```ts import { addMemories, retrieveMemories } from '@mem0/vercel-ai-provider'; await addMemories(messages, { user_id: 'user-123' }); const context = await retrieveMemories(prompt, { user_id: 'user-123' }); ``` See the [Mem0 provider documentation](/providers/community-providers/mem0) for full setup and configuration. ### Supermemory [Supermemory](https://supermemory.ai) is a long-term memory platform that adds persistent, self-growing memory to your AI applications. It provides tools that handle saving and retrieving memories automatically through semantic search. ```bash pnpm add @supermemory/tools ``` ```ts import { supermemoryTools } from '@supermemory/tools/ai-sdk'; import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: supermemoryTools(process.env.SUPERMEMORY_API_KEY!), }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', }); ``` Supermemory works with any AI SDK provider. The tools give the model `addMemory` and `searchMemories` operations that handle storage and retrieval. See the [Supermemory provider documentation](/providers/community-providers/supermemory) for full setup and configuration. ### Hindsight [Hindsight](/providers/community-providers/hindsight) provides agents with persistent memory through five tools: `retain`, `recall`, `reflect`, `getMentalModel`, and `getDocument`. It can be self-hosted with Docker or used as a cloud service. ```bash pnpm add @vectorize-io/hindsight-ai-sdk @vectorize-io/hindsight-client ``` ```ts import { HindsightClient } from '@vectorize-io/hindsight-client'; import { createHindsightTools } from '@vectorize-io/hindsight-ai-sdk'; import { ToolLoopAgent } from 'ai'; import { openai } from '@ai-sdk/openai'; const client = new HindsightClient({ baseUrl: process.env.HINDSIGHT_API_URL }); const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: createHindsightTools({ client, bankId: 'user-123' }), instructions: 'You are a helpful assistant with long-term memory.', }); const result = await agent.generate({ prompt: 'Remember that my favorite editor is Neovim', }); ``` The `bankId` identifies the memory store and is typically a user ID. In multi-user apps, call `createHindsightTools` inside your request handler so each request gets the right bank. Hindsight works with any AI SDK provider. See the [Hindsight provider documentation](/providers/community-providers/hindsight) for full setup and configuration. ### MongoDB [`@mongodb-developer/vercel-ai-memory`](https://www.npmjs.com/package/@mongodb-developer/vercel-ai-memory) provides MongoDB Atlas-backed persistent memory with five structured tiers: **Session**, **Semantic**, **Procedural**, **Episodic**, and **Scratchpad**. Retrieval is powered by Atlas Vector Search using any AI SDK embedding model, with automatic index creation and per-type retention policies. ```bash pnpm add @mongodb-developer/vercel-ai-memory ``` This integration targets AI SDK v6 because it relies on v6 APIs such as `ModelMessage`, `ToolLoopAgent`, and `isLoopFinished()`: ```json { "peerDependencies": { "ai": "^6.0.0", "mongodb": "^6.0.0", "zod": "^3.0.0" } } ``` ```ts import { createMongoDBMemory } from '@mongodb-developer/vercel-ai-memory'; import { openai } from '@ai-sdk/openai'; import { ToolLoopAgent, isLoopFinished } from 'ai'; // Create the memory instance once at module/server level const mongodbMemory = createMongoDBMemory({ uri: process.env.MONGODB_URI!, embedder: openai.embedding('text-embedding-3-small'), }); // Scope to a user and session per request const agent = new ToolLoopAgent({ model: openai('gpt-6-astra'), tools: mongodbMemory({ userId: 'alice', sessionId: 'sess-001' }), stopWhen: isLoopFinished(), }); const result = await agent.generate({ prompt: 'My name is Alice and I love hiking. Remember that.', }); ``` `isLoopFinished()` lets the agent keep running until the tool loop naturally finishes, which is useful when memory tools need to read and write before the final response. Session memory supports two modes: **tool-driven** (the LLM decides when to read/write — good for prototypes) and **hook-driven** (the runtime persists every turn via `prepareCall` + `onEnd` hooks — recommended for production). The other memory tiers (semantic, procedural, episodic, scratchpad) are always LLM-controlled and selective by design. MongoDB memory works with any AI SDK model and embedding provider, with no vendor lock-in beyond MongoDB Atlas. **When to use memory providers**: these providers are a good fit when you want memory without building any storage infrastructure. The tradeoff is that the provider controls memory behavior, so you have less visibility into what gets stored and how it is retrieved. You also take on a dependency on an external service. ## Custom Tool Building your own memory tool from scratch is the most flexible approach. You control the storage format, the interface, and the retrieval logic. This requires the most upfront work but gives you full ownership of how memory works, with no provider lock-in and no external dependencies. There are two common patterns: - **Structured actions**: you define explicit operations (`view`, `create`, `update`, `search`) and handle structured input yourself. Safe by design since you control every operation. - **Bash-backed**: you give the model a sandboxed bash environment to compose shell commands (`cat`, `grep`, `sed`, `echo`) for flexible memory access. More powerful but requires command validation for safety. For a full walkthrough of implementing a custom memory tool with a bash-backed interface, AST-based command validation, and filesystem persistence, see the **[Build a Custom Memory Tool](/cookbook/guides/custom-memory-tool)** recipe. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Overview description: Learn how to build agents with the AI SDK. url: "https://ai-sdk.dev/docs/agents/overview" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Agents are **large language models (LLMs)** that use **tools** in a **loop** to accomplish tasks. These components work together: - **LLMs** process input and decide the next action - **Tools** extend capabilities beyond text generation (reading files, calling APIs, writing to databases) - **Loop** orchestrates execution through: - **Context management** - Maintaining conversation history and deciding what the model sees (input) at each step - **Stopping conditions** - Determining when the loop (task) is complete ## Agent State and Context Agents often need server-side state that should not be placed directly in the prompt, such as tenant settings, request IDs, feature flags, credentials, or progress through a task. Use `runtimeContext` as the agent's shared runtime state. It flows through the agent loop, is available in `prepareStep` and lifecycle callbacks, and can be updated between steps. Use `toolsContext` for per-tool values such as API keys or scoped permissions; each tool receives only its own typed `context` based on its `contextSchema`. Learn more in [Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context). ## ToolLoopAgent Class The ToolLoopAgent class handles these three components. Here's an agent that uses multiple tools in a loop to accomplish a task: ```ts import { ToolLoopAgent, tool } from 'ai'; import { z } from 'zod'; const weatherAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { weather: tool({ description: 'Get the weather in a location (in Fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => ({ location, temperature: 72 + Math.floor(Math.random() * 21) - 10, }), }), convertFahrenheitToCelsius: tool({ description: 'Convert temperature from Fahrenheit to Celsius', inputSchema: z.object({ temperature: z.number().describe('Temperature in Fahrenheit'), }), execute: async ({ temperature }) => { const celsius = Math.round((temperature - 32) * (5 / 9)); return { celsius }; }, }), }, }); const result = await weatherAgent.generate({ prompt: 'What is the weather in San Francisco in celsius?', }); console.log(result.text); // agent's final answer console.log(result.steps); // steps taken by the agent ``` The agent automatically: 1. Calls the `weather` tool to get the temperature in Fahrenheit 2. Calls `convertFahrenheitToCelsius` to convert it 3. Generates a final text response with the result The ToolLoopAgent handles the loop, context management, and stopping conditions. ## Why Use the ToolLoopAgent? The ToolLoopAgent is the recommended approach for building agents with the AI SDK because it: - **Reduces boilerplate** - Manages loops and message arrays - **Improves reusability** - Define once, use throughout your application - **Simplifies maintenance** - Single place to update agent configuration For most use cases, start with the ToolLoopAgent. Use core functions (`generateText`, `streamText`) when you need explicit control over each step for complex structured workflows. ## HarnessAgent Class Use `HarnessAgent` when you want to run a preconfigured established harness, such as Claude Code, Codex, or Pi, instead of building the loop yourself around a language model. Harnesses are a separate abstraction from providers and models, but stream into AI SDK-compatible result and UI primitives. Learn more in [Harnesses](/docs/ai-sdk-harnesses) and [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent). The rest of this section focuses on `ToolLoopAgent`, which is the AI SDK agent class for building your own model-and-tools loop. ## Terminal UI Use `@ai-sdk/tui` to run a `ToolLoopAgent` in an interactive terminal UI. It is useful for local agent development, demos, and internal tools where you want prompt input, streamed responses, tool cards, reasoning sections, scrolling, and tool approval prompts without building a custom interface. ```ts import { runAgentTUI } from '@ai-sdk/tui'; await runAgentTUI({ title: 'Weather Agent', agent: weatherAgent, }); ``` Learn more in [Terminal UI](/docs/agents/terminal-ui). ## Structured Workflows Agents are flexible and powerful, but non-deterministic. When you need reliable, repeatable outcomes with explicit control flow, use core functions with structured workflow patterns combining: - Conditional statements for explicit branching - Standard functions for reusable logic - Error handling for robustness - Explicit control flow for predictability [Explore workflow patterns](/docs/agents/workflows) to learn more about building structured, reliable systems. ## Next Steps - **[Building Agents](/docs/agents/building-agents)** - Guide to creating agents with the ToolLoopAgent - **[Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context)** - How to pass shared agent state and per-tool context - **[Workflow Patterns](/docs/agents/workflows)** - Structured patterns using core functions for complex workflows - **[Loop Control](/docs/agents/loop-control)** - Execution control with stopWhen and prepareStep --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Policy-Based Tool Approvals description: "Author tool authorization rules as code with @ai-sdk/policy-opa and Open Policy Agent." url: "https://ai-sdk.dev/docs/agents/policy-tool-approvals" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [Tool Approvals](/docs/agents/tool-approvals) let you approve or deny tool calls with a function in your agent setup. `@ai-sdk/policy-opa` moves those rules out of code and into [Open Policy Agent](https://www.openpolicyagent.org/) (OPA) policies written in `.rego`. Use it when you want authorization to be: - Written as a separate, reviewable artifact instead of a function buried in agent setup. - Testable in CI with `opa test`, independent of the SDK. - Editable without a code deploy (when served from a running OPA instance). The package sits entirely on top of the public `toolApproval` callback. Nothing changes on the wire: the same `tool-approval-request` / `tool-approval-response` flow as built-in approvals. ## What you can enforce OPA is a general policy engine. If you can express a rule as code over structured input, you can enforce it at the tool boundary. Common categories: - Security and access: scopes, roles, permissions, tenant isolation, allowlists of tools, hosts, or paths. - Business rules: who can do what, on which resources, under which conditions (for example, payments above a threshold need approval). - Cost and usage: deny or require approval for calls that exceed a budget, token ceiling, or per-user quota. - Compliance and change control: gate destructive or regulated actions on the right approver, environment, or time window. The decision is not limited to the current call's arguments. The policy `input` also carries `messages`, the full model and tool-call history for the run, so a rule can factor in what already happened: prior tool calls, the sequence of actions so far, or a running total across the conversation. Examples: - Require approval once a run has already performed N writes. - Deny a second irreversible action (a push, a delete) in the same conversation. - Track cumulative spend or token usage across steps and stop at a ceiling. ### Best fit: deterministic checks Policy enforcement is strongest when the decision is deterministic and verifiable from structured fields, whether those fields come from the current call or the history in `messages`. The policy extracts a value and compares it, so the answer is the same every time. - Strong fits: scopes, permissions, numeric thresholds, allowlists, time windows, and history-aware checks like counts, sequences, and running totals. - Weak fits: content-based or semantic filtering such as "do not use bad language" or "block toxic content". These are best-effort, hard to verify, and easy to bypass. Use a dedicated moderation or classification step for them, and keep policy for the deterministic gate around it. Rule of thumb: if you can point at a field in the `input` object and write an exact check on it, this is the right tool. If the rule depends on judging free-form meaning, do not lean on policy alone. ## Install ```sh pnpm add @ai-sdk/policy-opa # pick one (or both) OPA backends: pnpm add @open-policy-agent/opa-wasm # in-process WASM evaluation pnpm add @open-policy-agent/opa # HTTP client to a running OPA server ``` - The backends are optional peer dependencies. - Only the backend you import is loaded. ## How it works The policy is consulted before every tool dispatch and maps to one of the standard approval statuses: - `allow` runs the tool. - `deny` returns a denied result the model can reason about (no human needed). - `requires-approval` pauses the run and waits for a human `tool-approval-response`. - No matching rule normalizes to `not-applicable`, which the SDK treats as allow. Add `default decision := { "decision": "deny" }` to your policy to default-deny instead. ## Quick start ```ts import { generateText } from 'ai'; import { anthropic } from '@ai-sdk/anthropic'; import { opaPolicy, wasmPolicyClient } from '@ai-sdk/policy-opa'; import { readFile } from 'node:fs/promises'; // 1. Load the compiled policy bundle. const wasm = await readFile('./policy.wasm'); const client = await wasmPolicyClient({ wasm }); // 2. Build the toolApproval configuration. const toolApproval = opaPolicy({ client, path: 'agent/call/decision', }); // 3. Pass it to generateText (or streamText / ToolLoopAgent). Everything else is normal. const result = await generateText({ model: anthropic('claude-sonnet-5-5'), tools: { git, bash, queryLogs }, toolApproval, prompt: 'find the failing test and push the fix', }); ``` ## Writing the Rego policy The policy emits a decision object. `reason` is optional and is surfaced back to the model (for `deny`) or to the human approver (for `requires-approval`). A manual approval request exposes it as `reason` in core results and as `approval.requestReason` in UI messages. ```txt package agent.call # Default to "not-applicable" so unmatched calls fall through. # Use { decision: "deny" } to default-deny instead. default decision := { "decision": "not-applicable" } # Hard deny: pushes are never allowed automatically. decision := { "decision": "deny", "reason": "pushes require human review" } { input.tool.name == "git" input.args.args[0] == "push" } # Auto-allow: read-only git operations. decision := { "decision": "allow" } { input.tool.name == "git" input.args.args[0] in {"status", "log", "diff", "show"} } # Human-in-the-loop: kubectl by oncall. decision := { "decision": "requires-approval", "reason": "kubectl by oncall" } { input.tool.name == "kubectl" input.runtimeContext.role == "sre-oncall" } ``` Notes: - The legacy boolean shape (`{ "allow": true | false, "reason": "..." }`) is also accepted, so existing rules migrate without rewriting. - The default OPA `input` shape is `{ tool: { name }, args, messages, runtimeContext }`. - Override the input shape with `toInput`: ```ts opaPolicy({ client, path: 'agent/call/decision', toInput: ({ toolCall, runtimeContext }) => ({ action: toolCall.toolName, principal: runtimeContext.role, resource: toolCall.input, }), }); ``` ### Test the policy in CI OPA ships its own test framework. These tests run without the SDK, which is the main reason policy-as-code beats policy-in-application-code. ```txt # policy_test.rego package agent.call test_push_denied { decision.decision == "deny" with input as { "tool": { "name": "git" }, "args": { "args": ["push", "origin", "main"] } } } ``` Run with `opa test policy.rego policy_test.rego`. ### Errors fail closed - If the backend errors (server unreachable, WASM fault, misbuilt bundle), `opaPolicy` returns `denied` with the error message as the reason. - If the backend returns a present but unrecognized decision, such as an unknown `decision` value or a non-boolean legacy `allow` value, `opaPolicy` returns `denied`. - The error never rejects out of the callback and never aborts the run. - A backend outage blocks the affected call rather than silently allowing it. - This is distinct from a rule that returns no match: `null` or `undefined` normalizes to `not-applicable` (allow). Use a `default ... deny` rule if you want unmatched calls denied too. ## Loading the policy ### Option A: WASM (in-process) Compile the `.rego` to WASM ahead of time: ```sh opa build -t wasm -e 'agent/call/decision' -o bundle.tar.gz policy.rego tar -xzf bundle.tar.gz /policy.wasm ``` ```ts import { wasmPolicyClient, opaPolicy } from '@ai-sdk/policy-opa'; import { readFile } from 'node:fs/promises'; const wasm = await readFile('./policy.wasm'); const client = await wasmPolicyClient({ wasm }); const toolApproval = opaPolicy({ client, path: 'agent/call/decision' }); ``` - No network call per decision. - Good fit when you ship the policy with the app, or fetch it from object storage at startup. - Hot-reload means rebuilding the WASM and re-instantiating the client. ### Option B: HTTP (running OPA server) ```ts import { httpPolicyClient, opaPolicy } from '@ai-sdk/policy-opa'; const client = httpPolicyClient({ url: 'http://localhost:8181' }); const toolApproval = opaPolicy({ client, path: 'agent/call/decision' }); ``` - One HTTP round-trip per decision. - Good fit when policies change frequently and you want hot-reload without redeploying, or when multiple services share one OPA. - Pass `headers` for Styra DAS / EOPA authentication. ## Bring in external data and integrations Policies are not limited to the tool-call input. OPA can decide based on external data such as role-to-permission mappings, IDP group memberships, entitlement lists, or allowlists, and that data can update without redeploying your app. How OPA sources data is OPA's concern, not this package's. The common approaches: - Static data at load time: pass `data` to `wasmPolicyClient({ wasm, data })`. It is handed to the bundle's `setData`, so the policy can read it under `data.*`. - Bundles: point a running OPA server (the HTTP backend) at a bundle service so it periodically pulls fresh policy and data. Role mappings and IDP groups update without a code deploy. - Lookups during evaluation: a policy can call out to an external service (for example an IDP or entitlements API) while it evaluates a decision. Learn the details from OPA, not here: - [Policy reference](https://www.openpolicyagent.org/docs/policy-reference) - [External data](https://www.openpolicyagent.org/docs/external-data) (including connecting to an IDP) This package only maps the resulting decision to the SDK's approval status. The `input` it passes is what your policy combines with whatever data OPA already has. ## Roll out safely with shadow mode Do not ship a new policy straight to enforce. The first version almost always denies things you did not mean to. `shadow(approval, opts)` evaluates the policy and reports the decision via `onDecision`, but tells the SDK every call is approved until you flip `enforce: true`. ```ts import { opaPolicy, shadow, wasmPolicyClient } from '@ai-sdk/policy-opa'; const client = await wasmPolicyClient({ wasm }); const toolApproval = shadow( opaPolicy({ client, path: 'agent/call/decision' }), { enforce: process.env.ENFORCE_POLICY === 'true', onDecision: event => { logger.info('policy.decision', { tool: event.toolCall.toolName, decision: event.decision.type, reason: event.decision.reason, enforced: event.enforced, wouldBlock: event.decision.type === 'denied', }); }, }, ); ``` Recommended rollout: 1. Write the policy and test it with `opa test`. 2. Wrap it in `shadow(...)` with `enforce: false` (the default) and wire `onDecision` to your logs/metrics. 3. Run in your real environment. Inspect events where `decision.type` is `denied` or `user-approval`: those are the calls the policy would have changed. 4. Fix the policy and iterate. 5. When the only `denied` / `user-approval` events are ones you want, set `enforce: true`. Telemetry semantics: - `onDecision` is fire-and-forget. A slow or throwing logger cannot block or break enforcement, and thrown errors are swallowed. - The contract is enforcement first, observability second. - For the opposite (enforcement waits for the audit log), log from inside the underlying `toolApproval` instead of through `shadow`. ## Send decisions to your observability platform `onDecision` is not only for rollout. Keep `shadow` in place with `enforce: true` and every decision flows to your logging, metrics, or tracing stack while the policy stays load-bearing. ```ts const toolApproval = shadow( opaPolicy({ client, path: 'agent/call/decision' }), { enforce: true, // enforcing AND observing onDecision: event => { metrics.increment('agent.policy.decision', { tool: event.toolCall.toolName, decision: event.decision.type, // approved | denied | user-approval | not-applicable enforced: String(event.enforced), }); }, }, ); ``` - Each `PolicyDecisionEvent` carries the tool call, the policy `decision` (type and reason), `enforced`, and `effective` (what the SDK acted on). Compare `decision` against `effective` to spot drift. - `onDecision` is fire-and-forget; see the telemetry semantics under shadow mode. When you use the HTTP backend, OPA can also emit [decision logs](https://www.openpolicyagent.org/docs/management-decision-logs) natively, shipping every evaluation to a remote service for a full audit trail without any application code. ## Capability scoping at the model boundary `opaCapabilityMiddleware` enforces policy earlier than `toolApproval`: before the model is even told a tool exists. This is defense in depth, plus it saves tokens and improves jailbreak rejection. ```ts import { wrapLanguageModel } from 'ai'; import { wasmPolicyClient, opaCapabilityMiddleware } from '@ai-sdk/policy-opa'; const client = await wasmPolicyClient({ wasm }); const wrappedModel = wrapLanguageModel({ model: anthropic('claude-sonnet-5-5'), middleware: opaCapabilityMiddleware({ client, path: 'agent/tools/allowed' }), }); ``` - The rule at `path` returns a `string[]` of allowed tool names, or `{ tools: string[] }`. - Tools not in the allowlist are dropped before the model sees them. - Fails closed: on a malformed response or evaluator error, `params.tools` is set to `undefined` so the model is told it has no tools. For fail-open, write the fallback in Rego. ## Scoping a discovered tool surface When tools come from MCP discovery or a plugin registry, you cannot write per-tool rules ahead of time, and any tool you forgot is silently allowed. `wrapMcpTools` makes the approval total over the discovered surface by routing unmatched tools to a configurable default. ```ts import { opaPolicy, wasmPolicyClient, wrapMcpTools } from '@ai-sdk/policy-opa'; const discovered = await mcpClient.tools(); const client = await wasmPolicyClient({ wasm }); const { tools, toolApproval } = wrapMcpTools( discovered, opaPolicy({ client, path: 'agent/call/decision' }), { default: 'user-approval' }, // anything OPA does not match needs a human ); await generateText({ model, tools, toolApproval, prompt }); ``` Defaults for uncovered tools: - `'user-approval'` (default): require a human. Good when you trust the source but want a safety net. - `'denied'`: hard allowlist mode. The policy enumerates what is allowed; everything else is rejected. - `'approved'`: allow. Only when the discovery source is fully trusted. Despite the name, it works on any `Record`. ## Allow-all when no policy is configured `optionalOpaPolicy` returns `undefined` when `client` is `undefined`, which is the same as not passing `toolApproval` (the SDK approves every call). Useful for local dev or CI where the policy file is absent. ```ts import { optionalOpaPolicy, wasmPolicyClient } from '@ai-sdk/policy-opa'; import { readFile } from 'node:fs/promises'; const wasm = process.env.POLICY_WASM_PATH ? await readFile(process.env.POLICY_WASM_PATH) : undefined; const client = wasm ? await wasmPolicyClient({ wasm }) : undefined; const toolApproval = optionalOpaPolicy({ client, path: 'agent/call/decision' }); ``` - `POLICY_WASM_PATH` unset: `toolApproval` is `undefined`, all calls allowed, OPA modules never loaded. - `POLICY_WASM_PATH` set: policy loads, enforcement on. - For stricter behavior (refuse to start without a policy), use `opaPolicy` directly and let the missing bytes throw at startup. ## Transitive enforcement: composite tools `toolApproval` only fires when the model calls a tool directly. A coarse dispatcher tool (a `bash` tool that can run `git push`, an HTTP tool, an MCP proxy) lets the model bypass a per-action rule by routing through it. The fix lives inside the dispatcher's `toolApproval` entry: parse the dispatcher input down to a logical `(name, args)` pair, then evaluate it against the same rule the direct tool uses. ```ts const bashApproval = opaPolicy({ client, path: 'agent/action/decision', toInput: ({ toolCall }) => { const { command } = toolCall.input as { command: string }; const [bin, ...rest] = command.split(/\s+/); return { kind: bin, args: rest }; }, }); ``` Guidance: - Keep the matching logic in one place: either a shared Rego helper rule (both approvals call `opaPolicy` with the same `path`) or a shared TypeScript predicate. - Deny anything you cannot reduce to a clean invocation. Shell input is adversarial to parse, so "cannot prove it is safe" means deny. - Honest limitation: this gates dispatch at the model's call boundary. It does not stop a tool that, once approved, performs extra side effects beyond what its input describes. For that, run untrusted execution in an out-of-band sandbox (Vercel Sandbox, Firecracker, containers) and treat the sandbox as the trust boundary. Worked examples for SQL, HTTP, MCP, browser, and shell dispatchers live in the [package README](https://github.com/vercel/ai/tree/main/packages/policy-opa). ## Example application The [ai-sdk-slackbot](https://github.com/vercel-labs/ai-sdk-slackbot) example wires this package into a real agent end to end. It shows: - Every tool call gated by Rego in `policies/decision.rego`, editable without touching TypeScript. - Real tools under policy: a web search scoped to a domain, a weather tool with a city allowlist, and a `bash` tool restricted to a read-only command allowlist (transitive enforcement). - Switching backends with one env var (`POLICY_MODE`): in-process WASM by default, or a live OPA HTTP server for hot-reloading policies in dev. - Policy code tested independently with OPA-native tests in `policies/decision_test.rego`. See [`policies/`](https://github.com/vercel-labs/ai-sdk-slackbot/tree/main/policies) and [`lib/policy/load.ts`](https://github.com/vercel-labs/ai-sdk-slackbot/blob/main/lib/policy/load.ts) for how the policy is loaded and applied. ## API reference Everything is exported from the package root, `@ai-sdk/policy-opa`. Engine-neutral core: - `shadow(approval, opts?)`: wrap any approval so it is evaluated and reported but not enforced until `opts.enforce: true`. Recommended starting point for any new policy. - `wrapMcpTools(tools, approval, opts?)`: make an approval total over a discovered tool set. `opts.default` controls uncovered tools. - `PolicyClient`: the `evaluate(path, input)` interface every backend implements. The seam for non-OPA engines. - Helper types: `PolicyDecision`, `WrappedMcpTools`, `PolicyDecisionEvent`. OPA backend and adapters: - `wasmPolicyClient({ wasm, data? })`: async; loads a compiled OPA WASM bundle in-process. - `httpPolicyClient({ url, headers? })`: sync; client against a running OPA server. - `opaPolicy({ client, path, toInput? })`: returns a `toolApproval` configuration. Fails closed. - `optionalOpaPolicy({ client, path, toInput? })`: like `opaPolicy` but returns `undefined` when `client` is `undefined`. - `opaCapabilityMiddleware({ client, path, toInput? })`: a `LanguageModelV4Middleware` that narrows `params.tools` to an allowlist. Fails closed. - `normalizeOpaDecision(result)`: standalone result normalization, for users who call OPA themselves. ## Related - [Tool Approvals](/docs/agents/tool-approvals): the underlying `toolApproval` callback this package plugs into. - [Building Agents](/docs/agents/building-agents) - [ai-sdk-slackbot example](https://github.com/vercel-labs/ai-sdk-slackbot): a full agent with policy-gated tools. - [Open Policy Agent documentation](https://www.openpolicyagent.org/docs/) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Subagents description: Delegate context-heavy tasks to specialized subagents while keeping the main agent focused. url: "https://ai-sdk.dev/docs/agents/subagents" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). A subagent is an agent that a parent agent can invoke. The parent delegates work via a tool, and the subagent executes autonomously before returning a result. ## How It Works 1. **Define a subagent** with its own model, instructions, and tools 2. **Create a tool that calls it** for the main agent to use 3. **Subagent runs independently with its own context window** 4. **Return a result** (optionally streaming progress to the UI) 5. **Control what the model sees** using `toModelOutput` to summarize ## When to Use Subagents Subagents add latency and complexity. Use them when the benefits outweigh the costs: | Use Subagents When | Avoid Subagents When | | ----------------------------------------------- | ------------------------------ | | Tasks require exploring large amounts of tokens | Tasks are simple and focused | | You need to parallelize independent research | Sequential processing suffices | | Context would grow beyond model limits | Context stays manageable | | You want to isolate tool access by capability | All tools can safely coexist | ## Why Use Subagents? ### Offloading Context-Heavy Tasks Some tasks require exploring large amounts of information—reading files, searching codebases, or researching topics. Running these in the main agent consumes context quickly, making the agent less coherent over time. With subagents, you can: - Spin up a dedicated agent that uses hundreds of thousands of tokens - Have it return only a focused summary (perhaps 1,000 tokens) - Keep your main agent's context clean and coherent The subagent does the heavy lifting while the main agent stays focused on orchestration. ### Parallelizing Independent Work For tasks like exploring a codebase, you can spawn multiple subagents to research different areas simultaneously. Each returns a summary, and the main agent synthesizes the findings—without paying the context cost of all that exploration. ### Specialized Orchestration A less common but valid pattern is using a main agent purely for orchestration, delegating to specialized subagents for different types of work. For example: - An exploration subagent with read-only tools for researching codebases - A coding subagent with file editing tools - An integration subagent with tools for a specific platform or API This creates a clear separation of concerns, though context offloading and parallelization are the more common motivations for subagents. ## Basic Subagent Without Streaming The simplest subagent pattern requires no special machinery. Your main agent has a tool that calls another agent in its `execute` function: ```ts import { ToolLoopAgent, tool } from 'ai'; import { z } from 'zod'; // Define a subagent for research tasks const researchSubagent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: `You are a research agent. Summarize your findings in your final response.`, tools: { read: readFileTool, // defined elsewhere search: searchTool, // defined elsewhere }, }); // Create a tool that delegates to the subagent const researchTool = tool({ description: 'Research a topic or question in depth.', inputSchema: z.object({ task: z.string().describe('The research task to complete'), }), execute: async ({ task }, { abortSignal }) => { const result = await researchSubagent.generate({ prompt: task, abortSignal, }); return result.text; }, }); // Main agent uses the research tool const mainAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a helpful assistant that can delegate research tasks.', tools: { research: researchTool, }, }); ``` This works well when you don't need to show the subagent's progress in the UI. The tool call blocks until the subagent completes, then returns the final text response. ### Handling Cancellation When the user cancels a request, the `abortSignal` propagates to the subagent. Always pass it through to ensure cleanup: ```ts execute: async ({ task }, { abortSignal }) => { const result = await researchSubagent.generate({ prompt: task, abortSignal, // Cancels subagent if main request is aborted }); return result.text; }, ``` If you abort the signal, the subagent stops executing and throws an `AbortError`. The main agent's tool execution fails, which stops the main loop. To avoid errors about incomplete tool calls in subsequent messages, use `convertToModelMessages` with `ignoreIncompleteToolCalls`: ```ts import { convertToModelMessages } from 'ai'; const modelMessages = await convertToModelMessages(messages, { ignoreIncompleteToolCalls: true, }); ``` This filters out tool calls that don't have corresponding results. Learn more in the [convertToModelMessages](/docs/reference/ai-sdk-ui/convert-to-model-messages) reference. ## Streaming Subagent Progress When you want to show incremental progress as the subagent works, use [**preliminary tool results**](/docs/ai-sdk-core/tools-and-tool-calling#preliminary-tool-results). This pattern uses a generator function that yields partial updates to the UI. ### How Preliminary Tool Results Work Change your `execute` function from a regular function to an async generator (`async function*`). Each `yield` sends a preliminary result to the frontend: ```ts execute: async function* ({ /* input */ }) { // ... do work ... yield partialResult; // ... do more work ... yield updatedResult; } ``` ### Building the Complete Message Each `yield` **replaces** the previous output entirely (it does not append). This means you need a way to accumulate the subagent's response into a complete message that grows over time. The `readUIMessageStream` utility handles this. It reads each chunk from the stream and builds an ever-growing `UIMessage` containing all parts received so far: ```ts import { readUIMessageStream, toUIMessageStream, tool } from 'ai'; import { z } from 'zod'; const researchTool = tool({ description: 'Research a topic or question in depth.', inputSchema: z.object({ task: z.string().describe('The research task to complete'), }), execute: async function* ({ task }, { abortSignal }) { // Start the subagent with streaming const result = await researchSubagent.stream({ prompt: task, abortSignal, }); // Each iteration yields a complete, accumulated UIMessage for await (const message of readUIMessageStream({ stream: toUIMessageStream({ stream: result.stream }), })) { yield message; } }, }); ``` Each yielded `message` is a complete `UIMessage` containing all the subagent's parts up to that point (text, tool calls, and tool results). The frontend simply replaces its display with each new message. ## Controlling What the Model Sees Here's where subagents become powerful for context management. The full `UIMessage` with all the subagent's work is stored in the message history and displayed in the UI. But you can control what the main agent's model actually sees using `toModelOutput`. ### How It Works The `toModelOutput` function maps the tool's output to the tokens sent to the model: ```ts const researchTool = tool({ description: 'Research a topic or question in depth.', inputSchema: z.object({ task: z.string().describe('The research task to complete'), }), execute: async function* ({ task }, { abortSignal }) { const result = await researchSubagent.stream({ prompt: task, abortSignal, }); for await (const message of readUIMessageStream({ stream: toUIMessageStream({ stream: result.stream }), })) { yield message; } }, toModelOutput: ({ output: message }) => { // Extract just the final text as a summary const lastTextPart = message?.parts.findLast(p => p.type === 'text'); return { type: 'text', value: lastTextPart?.text ?? 'Task completed.', }; }, }); ``` With this setup: - **Users see**: The full subagent execution—every tool call, every intermediate step - **The model sees**: Just the final summary text The subagent might use 100,000 tokens exploring and reasoning, but the main agent only consumes the summary. This keeps the main agent coherent and focused. ### Write Subagent Instructions for Summarization For `toModelOutput` to extract a useful summary, your subagent must produce one. Add explicit instructions like this: ```ts const researchSubagent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: `You are a research agent. Complete the task autonomously. IMPORTANT: When you have finished, write a clear summary of your findings as your final response. This summary will be returned to the main agent, so include all relevant information.`, tools: { read: readFileTool, search: searchTool, }, }); ``` Without this instruction, the subagent might not produce a comprehensive summary. It could simply say "Done", leaving `toModelOutput` with nothing useful to extract. ## Rendering Subagents in the UI (with useChat) To display streaming progress, check the tool part's `state` and `preliminary` flag. ### Tool Part States | State | Description | | ------------------ | ------------------------------------------ | | `input-streaming` | Tool input being generated | | `input-available` | Tool ready to execute | | `output-available` | Tool produced output (check `preliminary`) | | `output-error` | Tool execution failed | ### Detecting Streaming vs Complete ```tsx const hasOutput = part.state === 'output-available'; const isStreaming = hasOutput && part.preliminary === true; const isComplete = hasOutput && !part.preliminary; ``` ### Type Safety for Subagent Output Export types alongside your agents for use in UI components: ```ts title="lib/agents.ts" import { ToolLoopAgent, InferAgentUIMessage } from 'ai'; export const mainAgent = new ToolLoopAgent({ // ... configuration with researchTool }); // Export the main agent message type for the chat UI export type MainAgentMessage = InferAgentUIMessage; ``` ### Render Messages and Subagent Output This example uses the types defined above to render both the main agent's messages and the subagent's streamed output: ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import type { MainAgentMessage } from '@/lib/agents'; export function Chat() { const { messages } = useChat(); return (
{messages.map(message => message.parts.map((part, i) => { switch (part.type) { case 'text': return

{part.text}

; case 'tool-research': return (
{part.state !== 'input-streaming' && (
Research: {part.input.task}
)} {part.state === 'output-available' && (
{part.output.parts.map((nestedPart, i) => { switch (nestedPart.type) { case 'text': return

{nestedPart.text}

; default: return null; } })}
)}
); default: return null; } }), )}
); } ``` ## Caveats ### No Tool Approvals in Subagents Subagent tools cannot use approval flows such as `toolApproval` (or the deprecated `needsApproval`). All tools must execute automatically without user confirmation. ### Subagent Context is Isolated Each subagent invocation starts with a fresh context window. This is one of the key benefits of subagents: they don't inherit the accumulated context from the main agent, which is exactly what allows them to do heavy exploration without bloating the main conversation. If you need to give a subagent access to the conversation history, the `messages` are available in the tool's execute function alongside `abortSignal`: ```ts execute: async ({ task }, { abortSignal, messages }) => { const result = await researchSubagent.generate({ messages: [ ...messages, // The main agent's conversation history { role: 'user', content: task }, // The specific task for this invocation ], abortSignal, }); return result.text; }, ``` Use this sparingly since passing full history defeats some of the context isolation benefits. ### Streaming Adds Complexity The basic pattern (no streaming) is simpler to implement and debug. Only add streaming when you need to show real-time progress in the UI. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Terminal UI description: Run AI SDK agents in an interactive terminal UI. url: "https://ai-sdk.dev/docs/agents/terminal-ui" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `@ai-sdk/tui` package lets you run a local `ToolLoopAgent` or connect to a remote agent through a `ChatTransport` in an interactive terminal interface. It is useful for local development, demos, and internal tools where a terminal experience is enough and you do not want to build a custom UI. The terminal UI handles prompt input, streamed assistant responses, markdown rendering, tool cards, reasoning sections, scrolling, and tool approval prompts. ## Installation Install `@ai-sdk/tui` alongside `ai` and the provider package you use: ``` pnpm add @ai-sdk/tui ai @ai-sdk/openai ``` ## Running an Agent Create a `ToolLoopAgent` and pass it to `runAgentTUI`: ```ts import { openai } from '@ai-sdk/openai'; import { runAgentTUI } from '@ai-sdk/tui'; import { ToolLoopAgent, tool } from 'ai'; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: openai('gpt-6-astra'), instructions: 'You are a helpful terminal assistant. Answer in markdown and use tools when they help.', tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => ({ location, temperature: 72, }), }), }, }); await runAgentTUI({ title: 'Weather Agent', agent, }); ``` `runAgentTUI` runs until the user exits with `Esc` or `Ctrl+C`. ## Connecting to a Remote Agent Pass a `ChatTransport` instead of an `agent` to connect the terminal UI to a remote AI SDK UI message endpoint: ```ts import { runAgentTUI } from '@ai-sdk/tui'; import { DefaultChatTransport } from 'ai'; await runAgentTUI({ title: 'Remote Agent', transport: new DefaultChatTransport({ api: 'https://example.com/api/chat', }), }); ``` The transport controls the endpoint, authentication, request body, and other remote communication behavior. The terminal UI keeps its internal chat id and message history private to the transport contract. ## Sandbox Pass a sandbox session with the `sandbox` option when your agent tools need an execution environment: ```ts import { createJustBashNetworkSandboxSession } from '@ai-sdk/sandbox-just-bash'; const sandboxSession = await createJustBashNetworkSandboxSession({ cwd: '/home/user', }); try { await runAgentTUI({ title: 'Sandbox Agent', agent, sandbox: sandboxSession.restricted(), }); } finally { await sandboxSession.destroy(); } ``` The terminal UI forwards the sandbox to every agent call as `experimental_sandbox`. Tool description functions and tool `execute` functions can read it from their options and delegate command or file operations to it. Add the sandbox description to your agent instructions if the model should know details such as the working directory, public hostname, or exposed ports. ## Display Options You can control how tool calls, reasoning, and response statistics are shown: ```ts await runAgentTUI({ title: 'Weather Agent', agent, tools: 'auto-collapsed', reasoning: 'collapsed', responseStatistics: 'outputTokensPerSecond', contextSize: 200_000, }); ``` Settings: - `tools`: Controls tool call rendering. Use `"full"` to show tool input and output, `"collapsed"` to show only tool cards, `"auto-collapsed"` to show the latest tool expanded until another visible section appears, or `"hidden"` to omit tool calls. Defaults to `"auto-collapsed"`. - `reasoning`: Controls reasoning rendering. Use `"full"` to show reasoning, `"collapsed"` to show only reasoning cards, `"auto-collapsed"` to show the latest reasoning expanded until another visible section appears, or `"hidden"` to omit reasoning. Defaults to `"auto-collapsed"`. - `responseStatistics`: Use `"outputTokensPerSecond"` to show output token throughput or `"outputTokenCount"` to show output token count. Defaults to `"outputTokensPerSecond"`. - `contextSize`: When provided, the terminal UI shows total token usage as a percentage of the model context window. ## Tool Approvals `runAgentTUI` supports `ToolLoopAgent` tool approval flows. When an agent emits a manual approval request, the terminal UI prompts the user to approve or deny the tool call before the agent continues. ```ts const agent = new ToolLoopAgent({ model: openai('gpt-6-astra'), tools: { weather }, toolApproval: { weather: ({ location }) => location.toLowerCase().includes('san francisco') ? 'approved' : 'user-approval', }, }); await runAgentTUI({ title: 'Weather Agent', agent }); ``` ## Compatibility When using the `agent` option, the agent must be runnable directly from terminal user input. It must not require per-call options and must not use structured output, because the terminal UI cannot infer those values from a free-form prompt. Use a `transport` for remote agents that need custom request handling. Use `agent.generate()` or `agent.stream()` directly for examples or apps that need fixed prompts, call options, structured output, custom result inspection, or custom stream processing. ## Controls - `Enter`: submit prompt - `y` / `n`: approve or deny tool calls - `Up` / `Down`: scroll transcript - `PageUp` / `PageDown`: scroll transcript by a full page - `Ctrl+L`: repaint - `Esc` / `Ctrl+C`: exit ## Next Steps - [Building Agents](/docs/agents/building-agents) for creating `ToolLoopAgent` instances - [Tool Approvals](/docs/agents/tool-approvals) for configuring human review of tool calls - [runAgentTUI API Reference](/docs/reference/ai-sdk-tui/run-agent-tui) for detailed parameter documentation --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Tool Approvals description: Add manual and automatic approval flows to ToolLoopAgent tools. url: "https://ai-sdk.dev/docs/agents/tool-approvals" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). By default, tools with an `execute` function run automatically when the model calls them. Use `toolApproval` on `ToolLoopAgent` to review, approve, or deny selected tool calls before they execute. `toolApproval` is useful for tools that can modify data, spend money, execute code, send messages, access private data, or perform any other sensitive action. `toolApproval` applies to tools executed by the AI SDK. Provider-executed tools run provider-side and do not use AI SDK tool approvals. ## Statuses Every approval rule returns one of these statuses, either as a string or as an object with a `type` field: - `'not-applicable'`: execute the tool normally without approval metadata. This is the default. - `'approved'`: record an automatic approval, then execute the tool. - `'denied'`: record an automatic denial and return a denied tool output. - `'user-approval'`: emit an approval request and wait for an explicit response. For automatic approvals and denials, use the object form when you want to include a reason: ```ts toolApproval: { deleteFile: { type: 'denied', reason: 'Deleting files is disabled in this workspace', }, } ``` Approval functions can also return `undefined`, which is treated the same as `'not-applicable'`. ## Require Approval for a Tool Use a per-tool map when each tool has a simple policy. ```ts {13-15} import { ToolLoopAgent, tool } from 'ai'; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { runCommand: tool({ inputSchema: z.object({ command: z.string() }), execute: async ({ command }) => runCommand(command), }), }, toolApproval: { runCommand: 'user-approval', }, }); ``` When `runCommand` is called, the agent returns a `tool-approval-request` instead of executing the tool. ## Decide Based on Tool Input Use a per-tool approval function when the decision depends on the parsed tool input. The function receives the typed input plus `toolCallId`, `messages`, `toolContext`, and `runtimeContext`. ```ts {17-24} import { ToolLoopAgent, tool } from 'ai'; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { processPayment: tool({ inputSchema: z.object({ amount: z.number(), recipient: z.string(), }), execute: async ({ amount, recipient }) => processPayment({ amount, recipient }), }), }, toolApproval: { processPayment: async ({ amount }, { runtimeContext }) => { if (runtimeContext.role !== 'admin') { return { type: 'denied', reason: 'Only admins can send payments' }; } return amount > 1000 ? 'user-approval' : undefined; }, }, }); ``` In this example, non-admin users are denied automatically, large payments require manual approval, and smaller admin payments execute normally. ## Use One Policy for All Tools Pass a function directly as `toolApproval` when approval depends on the full tool call, shared state across tools, or the complete tool set. This is called a `GenericToolApprovalFunction`. ```ts {13-16,18-20,22-23} const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { readFile: tool({ inputSchema: z.object({ path: z.string() }), execute: async ({ path }) => readFile(path), }), deleteFile: tool({ inputSchema: z.object({ path: z.string() }), execute: async ({ path }) => deleteFile(path), }), }, toolApproval: ({ toolCall }) => { if (toolCall.dynamic) { return 'user-approval'; } if (toolCall.toolName === 'deleteFile') { return 'user-approval'; } return undefined; }, }); ``` The generic function receives: - `toolCall`: the full tool call, including `toolName`, `toolCallId`, `input`, and whether it is dynamic. - `tools`: all tools available to the model. - `toolsContext`: context for all tools. - `messages`: the messages sent to the model for the step that produced the tool call. - `runtimeContext`: the call's shared runtime context. ## Configure Approval per Request Because `toolApproval` is an agent setting, you can also return it from `prepareCall`. This is useful when approval policy depends on call options, tenant policy, or user permissions. ```ts {7-17} import { ToolLoopAgent, tool } from 'ai'; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", callOptionsSchema: z.object({ canRunCommands: z.boolean(), }), prepareCall: ({ options, ...settings }) => ({ ...settings, toolApproval: { runCommand: options.canRunCommands ? 'user-approval' : { type: 'denied', reason: 'Command access is disabled' }, }, }), tools: { runCommand: tool({ inputSchema: z.object({ command: z.string() }), execute: async ({ command }) => runCommand(command), }), }, }); ``` ## Handle Manual Approvals Manual approval requires two calls: 1. Call `agent.generate()` or `agent.stream()` with `toolApproval`. 2. Read the `tool-approval-request` from the result or UI stream. 3. Ask the user or your approval system for a decision. 4. Add a `tool-approval-response` to the messages. 5. Call the agent again with the updated messages. ```ts {5-6,10-18,21-24,26} import { type ModelMessage, type ToolApprovalResponse } from 'ai'; const messages: ModelMessage[] = [{ role: 'user', content: 'Delete temp.txt' }]; const result = await agent.generate({ messages }); messages.push(...result.responseMessages); const approvalResponses: ToolApprovalResponse[] = []; for (const part of result.content) { if (part.type === 'tool-approval-request' && !part.isAutomatic) { approvalResponses.push({ type: 'tool-approval-response', approvalId: part.approvalId, approved: true, reason: 'User confirmed the file can be deleted', }); } } messages.push({ role: 'tool', content: approvalResponses, }); const finalResult = await agent.generate({ messages }); ``` If approved, the tool runs on the second call. If denied, the model receives the denial and can respond without the tool result. When a tool execution is denied, consider adding an instruction such as "When a tool execution is not approved, do not retry it" to prevent repeated approval requests for the same action. ## Use with `useChat` When streaming an agent to a chat UI, approval requests appear as tool parts with `state: 'approval-requested'`. Respond with `addToolApprovalResponse`. ```tsx {7-9,17-40} 'use client'; import { useChat } from '@ai-sdk/react'; import { lastAssistantMessageIsCompleteWithApprovalResponses } from 'ai'; export default function Chat() { const { messages, addToolApprovalResponse } = useChat({ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses, }); return messages.map(message => message.parts.map(part => { if (part.type !== 'tool-runCommand') { return null; } if (part.state === 'approval-requested' && !part.approval.isAutomatic) { return (
{part.approval.requestReason && (

{part.approval.requestReason}

)}
); } }), ); } ``` Only call `addToolApprovalResponse` for manual approvals. Automatic approvals and denials already include approval state in the stream. When a manual approval status includes a reason, it is available as `part.approval.requestReason`. A reason supplied with `addToolApprovalResponse` is stored separately as `part.approval.reason`. ### Schema transforms and persisted approvals Approval requests preserve the original schema input in `inputSchemaInput` when it differs from the input presented for approval. Keep this field when persisting `responseMessages` or UI messages; the UI message conversion functions preserve it automatically. On continuation, the SDK reconstructs the transformed input and checks that it matches the approved input. It never replaces the approved input with a different value. Older or projected histories that omit the original input are rejected as invalid tool input if revalidation fails or would change the approved value. The original input is included in persisted messages and UI approval streams, including fields removed by schema transforms. A transform that removes a field does not redact it from approval metadata. ## Security Considerations ### Trust model In the standard `useChat` pattern, the server rebuilds the conversation from the messages the client sends each turn. The server does not persist conversation state between requests. This means the message history is client-controlled input. Tool approvals reconstructed from this history are re-validated before execution: the tool input is checked against the tool's schema, and the approval policy is re-evaluated. However, without additional protection, a client that crafts a valid-looking approval for a schema-conforming input can bypass the human-in-the-loop step. If your tools perform sensitive operations (modifying data, spending money, calling external APIs, accessing private resources), use `experimental_toolApprovalSecret` to cryptographically bind approvals to the server that issued them. ### Signing approvals with `experimental_toolApprovalSecret` When you provide a secret, the server HMAC-signs each approval request at issuance and verifies the signature when the approval is replayed. A forged or tampered approval is rejected before the tool executes. Configure the secret on `ToolLoopAgent` (or pass it directly to `generateText` or `streamText`). ```ts {6} const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { deleteFile, runQuery }, toolApproval: { deleteFile: 'user-approval', runQuery: 'user-approval' }, experimental_toolApprovalSecret: process.env.TOOL_APPROVAL_SECRET, }); const result = await agent.generate({ messages, }); ``` The signature binds the approval to the exact tool name, tool call ID, and input arguments. Changing any of these after signing invalidates the approval. **Setting up the secret:** 1. Generate a high-entropy random string (at least 32 bytes): ```bash openssl rand -base64 32 ``` 2. Store it as an environment variable accessible to all server instances: ``` TOOL_APPROVAL_SECRET=your-generated-secret-here ``` 3. Pass it to `ToolLoopAgent`, `generateText`, or `streamText` via `experimental_toolApprovalSecret`. Every serverless instance that might handle a request needs the same secret, since one instance signs the approval and a different instance may verify it on the next turn. **Behavior when configured:** - Approval requests without a valid signature are rejected (fail-closed) - No secret configured: approvals work as before (backward compatible) - The secret is never sent to the client or included in the stream `WorkflowAgent` also supports `experimental_toolApprovalSecret`. It signs in a workflow step before writing the durable approval request. Pass an environment variable reference, such as `{ environmentVariable: 'TOOL_APPROVAL_SECRET' }`, so the raw secret is read only inside signing and verification steps. Only the signature is persisted and sent to the client. ## Related APIs - Use `toolApproval` with `ToolLoopAgent`, `generateText`, and `streamText`. - Author approval rules as code with [Policy-Based Tool Approvals](/docs/agents/policy-tool-approvals) (`@ai-sdk/policy-opa`). - Use `needsApproval` only with [`WorkflowAgent`](/docs/agents/workflow-agent), where approvals suspend and resume durable workflow execution. - Subagent tools cannot use `toolApproval`; see [Subagents](/docs/agents/subagents#no-tool-approvals-in-subagents). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: WorkflowAgent description: "Build durable, resumable agents with the WorkflowAgent from @ai-sdk/workflow." url: "https://ai-sdk.dev/docs/agents/workflow-agent" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `WorkflowAgent` from `@ai-sdk/workflow` is designed for building **durable, resumable agents** that run inside a [workflow](https://vercel.com/docs/workflow). It provides the same agent loop as the [`ToolLoopAgent`](/docs/agents/building-agents), but adds automatic state persistence, tool schema serialization, and built-in tool approval flows that survive workflow step boundaries. ## Why Durable Agents? A standard `ToolLoopAgent` runs entirely in memory — if the process crashes, all progress is lost. For production agents that make multiple tool calls, this creates problems: - **Statefulness** — Long-running agent loops need to persist state across process boundaries - **Resumability** — If a step fails, you want to retry from the last checkpoint, not restart from scratch - **Human-in-the-loop** — Tools that require user approval need to pause the agent and resume later - **Observability** — Each tool call runs as a discrete workflow step, visible in dashboards `WorkflowAgent` solves these by running inside a workflow, where each tool execution is a durable step with automatic retries. ## When to Use WorkflowAgent vs ToolLoopAgent | | ToolLoopAgent | WorkflowAgent | | ----------------------- | ------------------------- | ------------------------------------------------ | | **Package** | `ai` | `@ai-sdk/workflow` | | **Runtime** | In-memory | Workflow | | **Durability** | Lost on crash | Survives restarts | | **Tool retries** | Manual | Automatic (via workflow steps) | | **Human approval** | Built-in | Built-in + survives suspension | | **`generate()` method** | Available | Available with durable non-streaming model calls | | **`stream()` method** | Available | Primary API | | **Stream output** | `streamText` return value | `writable` parameter with `ModelCallStreamPart` | For simpler use cases that don't need durability, use [`ToolLoopAgent`](/docs/agents/building-agents) from the `ai` package. ## Installation ```bash npm install @ai-sdk/workflow workflow@5 ``` `@ai-sdk/workflow` requires Workflow 5, as well as the `ai` package and `zod` peer dependencies. The `workflow` package provides the Workflow DevKit runtime (`getWritable`, `'use workflow'`, `'use step'`). ## Creating a WorkflowAgent Define an agent by instantiating the `WorkflowAgent` class with a model, instructions, and tools: ```ts import { WorkflowAgent } from '@ai-sdk/workflow'; import { tool } from 'ai'; import { z } from 'zod'; const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', instructions: 'You are a helpful assistant.', tools: { weather: tool({ description: 'Get weather for a location', inputSchema: z.object({ location: z.string(), }), execute: async ({ location }) => ({ location, temperature: 72, }), }), }, }); ``` ### Model Resolution The `model` parameter accepts two forms: ```ts // String — AI Gateway model ID new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5' }); // Provider instance import { openai } from '@ai-sdk/openai'; new WorkflowAgent({ model: openai('gpt-6-astra') }); ``` ## Using the Agent in a Workflow `WorkflowAgent` is designed to run inside a workflow function. The key integration points are: 1. Mark your function with `'use workflow'` 2. Pass `getWritable()` to the agent's `stream()` method 3. Start the workflow from your API route ### End-to-End Example ```ts title="workflow/agent-chat.ts" import { WorkflowAgent, type ModelCallStreamPart } from '@ai-sdk/workflow'; import { convertToModelMessages, tool, type UIMessage } from 'ai'; import { getWritable } from 'workflow'; import { z } from 'zod'; export async function chat(messages: UIMessage[]) { 'use workflow'; const modelMessages = await convertToModelMessages(messages); const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', instructions: 'You are a flight booking assistant.', tools: { searchFlights: tool({ description: 'Search for available flights', inputSchema: z.object({ origin: z.string(), destination: z.string(), date: z.string(), }), execute: searchFlightsStep, }), bookFlight: tool({ description: 'Book a specific flight', inputSchema: z.object({ flightId: z.string(), passengerName: z.string(), }), execute: bookFlightStep, }), }, }); const result = await agent.stream({ messages: modelMessages, writable: getWritable(), }); return { messages: result.messages }; } ``` ```ts title="app/api/chat/route.ts" import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow'; import { createUIMessageStreamResponse, type UIMessage } from 'ai'; import { start } from 'workflow/api'; import { chat } from '@/workflow/agent-chat'; export async function POST(request: Request) { const { messages }: { messages: UIMessage[] } = await request.json(); const run = await start(chat, [messages]); return createUIMessageStreamResponse({ stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()), }); } ``` ### Message Conversion `WorkflowAgent.stream()` expects `ModelMessage[]`, not `UIMessage[]`. When receiving messages from the client (via `useChat`), convert them first: ```ts import { convertToModelMessages, type UIMessage } from 'ai'; export async function chat(messages: UIMessage[]) { 'use workflow'; const modelMessages = await convertToModelMessages(messages); const result = await agent.stream({ messages: modelMessages, // ... }); } ``` ### Writable Streams Unlike `ToolLoopAgent` where you consume the returned stream, `WorkflowAgent` writes raw `ModelCallStreamPart` chunks to a `writable` stream provided by the workflow runtime via `getWritable()`. At the response boundary, use `createModelCallToUIChunkTransform()` to convert these into `UIMessageChunk` objects for the client: ```ts import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow'; import { createUIMessageStreamResponse } from 'ai'; // Convert raw model stream parts → UI message chunks return createUIMessageStreamResponse({ stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()), }); ``` The transform also forwards `reset-step` events emitted by `WorkflowAgent` on retries. Clients remove partial parts from the failed model-call step before processing the retried output. ## Resumable Streaming with WorkflowChatTransport Workflow functions can time out or be interrupted by network failures. `WorkflowChatTransport` is a [`ChatTransport`](/docs/ai-sdk-ui/transport) implementation that handles these interruptions automatically — it detects when a stream ends without a `finish` event and reconnects to resume from where it left off. ```tsx title="app/page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import { WorkflowChatTransport } from '@ai-sdk/workflow/client'; import { useMemo } from 'react'; export default function Chat() { const transport = useMemo( () => new WorkflowChatTransport({ api: '/api/chat', maxConsecutiveErrors: 5, }), [], ); const { messages, sendMessage } = useChat({ transport }); // ... render chat UI } ``` The transport requires your POST endpoint to return an `x-workflow-run-id` response header, and a GET endpoint at `{api}/{runId}/stream` for reconnection: ```ts title="app/api/chat/route.ts" import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow'; import { createUIMessageStreamResponse, type UIMessage } from 'ai'; import { start } from 'workflow/api'; import { chat } from '@/workflow/agent-chat'; export async function POST(request: Request) { const { messages }: { messages: UIMessage[] } = await request.json(); const run = await start(chat, [messages]); return createUIMessageStreamResponse({ stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()), headers: { 'x-workflow-run-id': run.runId, }, }); } ``` ```ts title="app/api/chat/[runId]/stream/route.ts" import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow'; import { createUIMessageStreamResponse } from 'ai'; import type { NextRequest } from 'next/server'; import { getRun } from 'workflow/api'; export async function GET( request: NextRequest, { params }: { params: Promise<{ runId: string }> }, ) { const { runId } = await params; const startIndex = Number( new URL(request.url).searchParams.get('startIndex') ?? '0', ); if (!Number.isSafeInteger(startIndex) || startIndex < 0) { return Response.json( { error: 'startIndex must be a non-negative safe integer' }, { status: 400 }, ); } const run = await getRun(runId); const readable = run .getReadable({ startIndex: 0 }) .pipeThrough( createModelCallToUIChunkTransform({ uiStartIndex: startIndex }), ); return createUIMessageStreamResponse({ stream: readable, headers: { 'x-workflow-run-id': runId, }, }); } ``` `WorkflowChatTransport` counts `UIMessageChunk` objects, while the durable `WorkflowAgent` stream stores raw `ModelCallStreamPart` objects. Replay the raw stream from index `0` and apply the non-negative UI cursor in `createModelCallToUIChunkTransform()` as shown above. Negative start indexes require a durable stream that already stores `UIMessageChunk` objects and cannot be used with this raw-to-UI conversion. For the full API reference, see [`WorkflowChatTransport`](/docs/reference/ai-sdk-workflow/workflow-chat-transport). ## Tools as Workflow Steps Mark tool execute functions with `'use step'` to make them durable workflow steps. This gives each tool call: - **Automatic retries** — Failed tool calls are retried automatically (default: 3 attempts) - **Persistence** — Results survive process restarts - **Observability** — Each tool call appears as a discrete step in the workflow dashboard ```ts async function searchFlightsStep(input: { origin: string; destination: string; date: string; }) { 'use step'; const response = await fetch(`https://api.flights.example/search?...`); return response.json(); } async function bookFlightStep(input: { flightId: string; passengerName: string; }) { 'use step'; const response = await fetch('https://api.flights.example/book', { method: 'POST', body: JSON.stringify(input), }); return response.json(); } ``` Tools without `'use step'` still work but run as regular in-memory functions without durability guarantees. ## Tool Approval For `WorkflowAgent`, human approval is configured on the tool definition with `needsApproval`. This is specific to `WorkflowAgent`; for `generateText`, `streamText`, and `ToolLoopAgent`, use `toolApproval` instead. When a workflow tool has `needsApproval` set, the agent pauses and emits an approval request to the writable stream. The workflow suspends until the user approves or denies: ```ts const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', tools: { bookFlight: tool({ description: 'Book a flight', inputSchema: z.object({ flightId: z.string(), passengerName: z.string(), }), needsApproval: true, // Always require approval execute: bookFlightStep, }), cancelBooking: tool({ description: 'Cancel a booking', inputSchema: z.object({ bookingId: z.string() }), // Conditional approval based on input needsApproval: async input => { return input.bookingId.startsWith('VIP-'); }, execute: cancelBookingStep, }), }, }); ``` Because the workflow is durable, the approval request survives process restarts — the user can approve hours later and the agent will resume. ### Signed Tool Approvals Client-supplied message history can be modified before it is replayed to the workflow. For tools that perform sensitive operations, configure `experimental_toolApprovalSecret` to authenticate approval requests: ```ts {4} const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', experimental_toolApprovalSecret: { environmentVariable: 'TOOL_APPROVAL_SECRET', }, tools: { bookFlight: tool({ description: 'Book a flight', inputSchema: z.object({ flightId: z.string() }), needsApproval: true, execute: bookFlightStep, }), }, }); ``` The agent HMAC-signs the approval ID, tool call ID, tool name, and validated input. When approved message history is replayed, a missing or invalid signature prevents the tool from executing. The signature is preserved through the durable model-call stream, `createModelCallToUIChunkTransform()`, `addToolApprovalResponse()`, and `convertToModelMessages()`. Use a high-entropy secret of at least 32 bytes and make the same secret available to every worker that can issue or resume an approval. Keep old keys available while approvals signed with them are pending; changing the secret invalidates those pending approvals. `WorkflowAgent` passes only the environment variable name into signing and verification steps. Each step reads the secret from its local environment, and the raw value is never included in step arguments, durable stream parts, callbacks, or telemetry events. Configure the environment variable on every worker, and do not put the secret value in `runtimeContext` or `toolsContext`. You can also provide `experimental_toolApprovalSecret` to `agent.stream()`. The stream-level value overrides the constructor default. ## Loop Control Unlike `ToolLoopAgent`, `WorkflowAgent` does not apply a default step limit. When `stopWhen` is omitted, it continues until the model stops calling tools or another natural termination condition is met. A model that repeatedly calls tools can make an unlimited number of model calls. Configure an explicit stop condition when you need to bound execution time and cost. Control how many steps the agent can take: ```ts import { isStepCount } from 'ai'; const result = await agent.stream({ messages, stopWhen: isStepCount(10), // Stop after 10 LLM calls }); ``` Omitting `stopWhen` already lets `WorkflowAgent` continue until it has finished calling tools. You can make that intent explicit with `isLoopFinished()`: ```ts import { isLoopFinished } from 'ai'; const result = await agent.stream({ messages, stopWhen: isLoopFinished(), }); ``` `isLoopFinished()` is equivalent to omitting `stopWhen` for `WorkflowAgent`. Use it with caution because a model that keeps calling tools can run indefinitely and incur significant costs. See [`isLoopFinished()`](/docs/reference/ai-sdk-core/loop-finished). ## Structured Output Parse agent responses into typed objects using `Output`: ```ts import { Output } from '@ai-sdk/workflow'; import { z } from 'zod'; const result = await agent.stream({ messages, output: Output.object({ schema: z.object({ sentiment: z.enum(['positive', 'neutral', 'negative']), summary: z.string(), }), }), }); console.log(result.output); // { sentiment: 'positive', summary: '...' } ``` ## Configuration Options `WorkflowAgent` accepts the same generation settings as `ToolLoopAgent` (`temperature`, `maxOutputTokens`, `topP`, etc.) plus workflow-specific options. ### runtimeContext and toolsContext Pass server-side state through the agent loop without putting it into the prompt. Use these instead of the previous `experimental_context` option. - `runtimeContext` is shared agent state that flows through `prepareStep`, lifecycle callbacks, and `onEnd`. Treat it as immutable; return a new value from `prepareStep` to update it for the current and subsequent steps. - `toolsContext` is a per-tool map keyed by tool name. Each tool's `execute` only sees its own validated entry as `context`. Tools that declare a `contextSchema` validate their entry against the schema before execution. ```ts title="workflow/agent-chat.ts" import { WorkflowAgent } from '@ai-sdk/workflow'; import { tool } from 'ai'; import { z } from 'zod'; const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-4-6', tools: { weather: tool({ description: 'Get the weather for a city.', inputSchema: z.object({ city: z.string() }), contextSchema: z.object({ defaultUnit: z.enum(['celsius', 'fahrenheit']), }), execute: async ({ city }, { context }) => ({ city, unit: context.defaultUnit, }), }), }, // Shared agent state — available in `prepareStep`, lifecycle callbacks, and `onEnd`. runtimeContext: { tenantId: 'tenant_123', requestId: 'req_abc', plan: 'enterprise', }, // Per-tool context — each tool sees only its own validated entry. toolsContext: { weather: { defaultUnit: 'celsius' }, }, prepareStep: ({ runtimeContext }) => { if (runtimeContext.plan === 'enterprise') { return { temperature: 0.2 }; } return {}; }, }); ``` `runtimeContext` and `toolsContext` can also be passed per-call to `stream()`, where they override the constructor-level defaults. Because `WorkflowAgent` runs inside the Workflow runtime, context values may be persisted and replayed across workflow and step boundaries. Keep `runtimeContext`, `toolsContext`, and any context values returned from `prepareStep` serializable. Use plain data such as strings, numbers, booleans, arrays, plain objects, dates, URLs, maps, sets, and other Workflow-supported structured data. Do not put functions, class instances, symbols, `WeakMap`, `WeakSet`, database clients, or SDK clients in context. Pass identifiers or configuration data instead, and recreate non-serializable resources inside step functions. This differs from `ToolLoopAgent`, which runs in memory and can carry richer JavaScript values for the lifetime of a single process. With `WorkflowAgent`, treating context as durable data keeps workflow replay and step execution reliable. ### experimental\_sandbox Pass a sandbox session when tools need an execution environment. The sandbox is available to tool descriptions and `execute` functions as `experimental_sandbox`, and to `prepareStep`, where you can override it for the current step: ```ts const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', tools: { shell: tool({ description: 'Run a shell command in the sandbox.', inputSchema: z.object({ command: z.string() }), execute: async ({ command }, { experimental_sandbox }) => { if (!experimental_sandbox) { throw new Error('Sandbox is not available'); } return experimental_sandbox.run({ command }); }, }), }, experimental_sandbox: sandbox, }); await agent.stream({ messages, writable: getWritable(), experimental_sandbox: requestSandbox, // Overrides the constructor default. }); ``` `experimental_sandbox` is a live runtime handle, not durable context. Do not store it in `runtimeContext` or `toolsContext`. If a tool runs as a separate workflow step, pass serializable sandbox identifiers or configuration and reattach inside that step. ### prepareCall Called once before the agent loop starts. Use it to transform model, instructions, or other settings based on runtime context: ```ts const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', prepareCall: async ({ model, tools, messages }) => { return { instructions: `Current time: ${new Date().toISOString()}`, }; }, }); ``` ### prepareStep Called before each step (LLM call). Use it to modify settings, manage context, or inject messages dynamically: ```ts const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-4-6', prepareStep: async ({ stepNumber, experimental_sandbox }) => { if (stepNumber > 5) { return { toolChoice: 'none' }; // Force text response after 5 steps } if (experimental_sandbox) { return { temperature: 0.2 }; } return {}; }, }); ``` Both `prepareCall` and `prepareStep` can also be passed per-call in `stream()`. ## Lifecycle Callbacks Agents provide lifecycle callbacks for logging, observability, and custom telemetry. All callbacks can be defined in the constructor (agent-wide) or in `stream()` (per-call). When both are provided, both fire (constructor first): ```ts const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', onStart({ messages }) { console.log(`Agent started with ${messages.length} messages`); }, onStepStart({ stepNumber }) { console.log(`Step ${stepNumber} starting`); }, onToolExecutionStart({ toolCall }) { console.log(`Calling tool: ${toolCall.toolName}`); }, onToolExecutionEnd({ toolCall, success, durationMs }) { console.log(`Tool finished: ${toolCall.toolName}`, { success, durationMs, }); }, onStepEnd({ usage, finishReason }) { console.log('Step done:', { finishReason }); }, onEnd({ steps, totalUsage }) { console.log(`Completed in ${steps.length} steps`); }, }); ``` For concrete tool sets, `WorkflowAgentToolExecutionStartEvent` and `WorkflowAgentToolExecutionEndEvent` preserve the relationship between each tool name and its input, context, and output types. TypeScript narrows the nested `toolCall` directly, but use `Extract` or a user-defined type guard when you need to narrow the correlated `toolContext` or `output` fields by tool name. Tool input callbacks (`onInputStart`, `onInputDelta`, and `onInputAvailable`) are also preserved by `WorkflowAgent`. The model call runs inside a durable step, while callback functions remain in the workflow context because arbitrary functions cannot cross the step boundary. As a result, `WorkflowAgent` records the callback events during the model step and replays them in order immediately after that step completes, before tool execution and step lifecycle callbacks. They do not run concurrently with model generation and cannot provide in-flight cancellation or backpressure. Each callback receives its tool's `toolsContext` entry after `contextSchema` validation. For highly fragmented tool inputs, `onInputDelta` replay data is part of the durable model-step result. Only configure `onInputDelta` when each generated delta is needed; omit it to avoid retaining delta replay data. The deprecated `experimental_onStart` and `experimental_onStepStart` names remain available for backwards compatibility. When both the stable and experimental name are provided in the same constructor or `stream()` call, the stable callback is used. ## Type Inference Infer the UI message type for type-safe client components: ```ts import { WorkflowAgent, InferWorkflowAgentUIMessage } from '@ai-sdk/workflow'; const myAgent = new WorkflowAgent({ // ... configuration }); export type MyAgentUIMessage = InferWorkflowAgentUIMessage; ``` ## Migrating from `DurableAgent` `WorkflowAgent` replaces the Workflow DevKit's [`DurableAgent`](https://workflow-sdk.dev/docs/api-reference/workflow-ai/durable-agent). The two share the same core idea — a durable agent loop that runs inside a workflow — but `WorkflowAgent` moves the class into the AI SDK, tightens typing, and introduces first-class tool approval. If you are using `DurableAgent` today, follow the steps below to switch. ### Change the import and class name `DurableAgent` was exported from `workflow/ai`. `WorkflowAgent` is exported from `@ai-sdk/workflow`, alongside its helpers. ```diff - import { DurableAgent } from 'workflow/ai'; + import { WorkflowAgent, type ModelCallStreamPart } from '@ai-sdk/workflow'; - const agent = new DurableAgent({ + const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', instructions: 'You are a helpful assistant.', tools: { /* ... */ }, }); ``` Install the new package alongside `workflow`: ```bash npm install @ai-sdk/workflow workflow@5 ``` ### Write `ModelCallStreamPart`, not `UIMessageChunk` `DurableAgent` wrote `UIMessageChunk` objects directly to the writable returned by `getWritable()`. `WorkflowAgent` writes the lower-level `ModelCallStreamPart` shape and leaves the conversion to a transform at the response boundary. This keeps the durable stream provider-shaped and avoids baking a UI protocol into the workflow payload. ```diff // Inside the workflow await agent.stream({ messages, - writable: getWritable(), + writable: getWritable(), }); ``` ```diff // Inside the route handler + import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow'; return createUIMessageStreamResponse({ - stream: run.readable, + stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()), }); ``` ### Replace `maxSteps` with `stopWhen` `DurableAgent` accepted `maxSteps` directly. `WorkflowAgent` uses the AI SDK's shared `stopWhen` conditions so the same stop logic works across `ToolLoopAgent`, `generateText`, and `streamText`. ```diff + import { isStepCount } from 'ai'; await agent.stream({ messages, - maxSteps: 10, + stopWhen: isStepCount(10), }); ``` See [Loop Control](/docs/agents/loop-control) for the full list of stop conditions. ### Replace `experimental_output` with `output` ```diff + import { Output } from '@ai-sdk/workflow'; await agent.stream({ messages, - experimental_output: Output.object({ schema }), + output: Output.object({ schema }), }); ``` The returned value is now on `result.output` (previously `result.experimental_output`). ### WorkflowAgent: Use `needsApproval` for human-in-the-loop tools With `DurableAgent`, tool approval was implemented by calling a Hook from inside the tool's `execute` function. `WorkflowAgent` makes approval a first-class tool property — the agent emits the approval request, suspends the workflow, and resumes automatically when the user responds. ```diff bookFlight: tool({ description: 'Book a flight', inputSchema: z.object({ flightId: z.string() }), + needsApproval: true, - execute: async (input) => { - const approved = await waitForApprovalHook(input); - if (!approved) throw new Error('Denied'); - return bookFlightStep(input); - }, + execute: bookFlightStep, }), ``` `needsApproval` also accepts an async function so you can decide per-input whether approval is required (see [Tool Approval](#tool-approval) above). ### `uiMessages` / `collectUIMessages` is gone `DurableAgent.stream()` returned accumulated `uiMessages` when `collectUIMessages: true` was set. `WorkflowAgent.stream()` returns `ModelMessage[]` on `result.messages` instead. For persistence, store `UIMessage[]` as your source of truth and call [`convertToModelMessages`](/docs/reference/ai-sdk-ui/convert-to-model-messages) before passing them to the agent — this is the pattern described in [Chatbot Message Persistence](/docs/ai-sdk-ui/chatbot-message-persistence). There is no built-in `ModelMessage` → `UIMessage` conversion, so avoid persisting `result.messages` as your only copy if you need to render the conversation in the UI later. ```diff const result = await agent.stream({ messages, writable: getWritable(), - collectUIMessages: true, }); - return { uiMessages: result.uiMessages }; + return { messages: result.messages }; ``` ### Non-streaming generation Use `agent.generate()` when you need the completed result without streaming: ```ts export async function summarize(modelId: string, text: string) { 'use workflow'; const agent = new WorkflowAgent({ model: modelId, instructions: 'Summarize the text briefly.', }); const result = await agent.generate({ prompt: text }); return { text: result.text, usage: result.usage }; } ``` Generation uses the provider's `doGenerate()` method and the same durable tool loop as streaming. It defaults to a maximum of 20 steps, while `stream()` remains unbounded unless you set `stopWhen`. Generation failures reject; stream model errors retain their existing `result.error` behavior. The generated result uses core's final-step text and all-step content, tool results, warnings, and usage semantics. `responseMessages` contains generated messages only. `output` defaults to text and supports constructor or per-call output specifications. End callbacks run before complete-output parsing. Use result getters inside the workflow, then return selected serializable fields through `run.returnValue`. Returning the whole result class is not supported. See the [generate reference](/docs/reference/ai-sdk-workflow/workflow-agent#generate) for options and remaining differences. #### Approval round trips without streaming Both `generate()` and `stream()` return approval requests even when no writable is provided. For generation, keep `result.responseMessages`, display the requested tool and input to the user, then append their decision to the next call: ```ts const pending = await agent.generate({ messages }); // Persist these messages and collect the user's decision outside the workflow. const continuation = [...messages, ...pending.responseMessages]; // In a later workflow invocation, using the same tool definitions: const resumed = await agent.generate({ messages: [ ...continuation, { role: 'tool', content: [{ type: 'tool-approval-response', approvalId, approved }], }, ], }); ``` Use the `approvalId` from the returned `tool-approval-request` and the user's `approved` decision. Preserve the request signature and original tool-call input when storing the conversation. Configure `experimental_toolApprovalSecret` with the same environment-variable reference for both invocations. Secrets are resolved inside durable signing and verification steps. Local tools execute only after approval validation; denied tools produce an execution-denied result. Provider-executed approvals are forwarded to the provider. Tools that wait on Workflow hooks can also suspend and resume during generation. #### Deadlines, cancellation, and replay Inside a workflow, `timeout` sets an absolute deadline for model calls. Each model step checks it before dispatch, before SDK retry attempts, and after the provider returns. The same deadline remains in effect after a tool suspends and resumes. SDK `maxRetries` governs model retries; the durable model step does not add another retry layer. Expiration before dispatch rejects with `TimeoutError`; expiration during SDK retry backoff rejects with `AbortError`. Neither starts a new retry. A model deadline does not cancel a tool waiting on a Workflow hook. The run can remain suspended after that deadline. Once the hook resumes, any subsequent model call fails before dispatch if the deadline has expired. To stop the suspended run, use the Workflow runtime's `run.cancel()` API. Cancellation does not undo external side effects that already happened. Workflow reuses completed durable model and tool steps on replay. Put external side effects inside step functions and use the external service's idempotency mechanism where necessary: completed-step replay does not guarantee exactly-once effects for an interrupted attempt. Generated files and result getters are available inside workflow code. Return the data the caller needs, for example: ```ts return { text: result.text, files: result.files.map(file => ({ data: file.uint8Array, mediaType: file.mediaType, })), usage: result.usage, responseMessages: result.responseMessages, }; ``` The generated result class itself is not a supported `run.returnValue` shape. Request and response bodies are omitted unless explicitly enabled with `include`; available response headers, timestamps, IDs and provider metadata are retained. Workflow uses its existing callback shapes and preparation rules, and reports model response timing rather than complete tool/step timing. It does not implement ToolLoopAgent's `callOptionsSchema` abstraction or core's live streaming readers and HTTP response helpers. ### Replace `experimental_context` with `runtimeContext` and `toolsContext` `WorkflowAgent` no longer accepts `experimental_context`. Split the value into shared agent state (`runtimeContext`) and per-tool state (`toolsContext`); each tool's `execute` then receives only its own validated entry as `context`. See [runtimeContext and toolsContext](#runtimecontext-and-toolscontext) for the full shape. ```diff const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', tools: { weather: weatherTool }, - experimental_context: { tenantId: 'tenant_123', apiKey: 'sk-...' }, + runtimeContext: { tenantId: 'tenant_123' }, + toolsContext: { weather: { apiKey: 'sk-...' } }, }); ``` ### Everything else Other options carry over with the same names: `prepareStep`, `onStart`, `onStepStart`, `onStepEnd`, `onEnd`, `onError`, `toolChoice`, `activeTools`, `timeout`, `repairToolCall`, `experimental_sandbox`, and the usual generation settings (`temperature`, `maxOutputTokens`, `topP`, …). `WorkflowAgent` additionally adds `prepareCall` (runs once before the loop) and the `onToolExecutionStart` / `onToolExecutionEnd` lifecycle callbacks documented above. The deprecated `experimental_onStart` and `experimental_onStepStart` aliases remain available for backwards compatibility. ## Next Steps - [WorkflowAgent API Reference](/docs/reference/ai-sdk-workflow/workflow-agent) for detailed parameter documentation - [WorkflowChatTransport API Reference](/docs/reference/ai-sdk-workflow/workflow-chat-transport) for stream reconnection options - [Building Agents](/docs/agents/building-agents) for the in-memory `ToolLoopAgent` alternative - [Loop Control](/docs/agents/loop-control) for advanced stop conditions ## Tool execution concurrency Set `toolCallConcurrency` to limit how many local tool calls execute concurrently within a step. Use `1` when calls must execute sequentially in model order: ```ts const agent = new WorkflowAgent({ model, tools, toolCallConcurrency: 1, }); ``` The limit applies to both `generate()` and `stream()`, including approved tool calls resumed from message history. It must be a positive integer. By default, all local calls execute concurrently; provider-executed tools are unaffected. You can also return the setting from `prepareCall` or pass it to `generate()` or `stream()`. Explicit call options take precedence over `prepareCall`, which takes precedence over the constructor default. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Workflow Patterns description: Learn workflow patterns for building reliable agents with the AI SDK. url: "https://ai-sdk.dev/docs/agents/workflows" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Combine the building blocks from the [overview](/docs/agents/overview) with these patterns to add structure and reliability to your agents: - [Sequential Processing](#sequential-processing-chains) - Steps executed in order - [Parallel Processing](#parallel-processing) - Independent tasks run simultaneously - [Evaluation/Feedback Loops](#evaluator-optimizer) - Results checked and improved iteratively - [Orchestration](#orchestrator-worker) - Coordinating multiple components - [Routing](#routing) - Directing work based on context ## Choose Your Approach Consider these key factors: - **Flexibility vs Control** - How much freedom does the LLM need vs how tightly you must constrain its actions? - **Error Tolerance** - What are the consequences of mistakes in your use case? - **Cost Considerations** - More complex systems typically mean more LLM calls and higher costs - **Maintenance** - Simpler architectures are easier to debug and modify **Start with the simplest approach that meets your needs**. Add complexity only when required by: 1. Breaking down tasks into clear steps 2. Adding tools for specific capabilities 3. Implementing feedback loops for quality control 4. Introducing multiple agents for complex workflows Let's look at examples of these patterns in action. ## Patterns with Examples These patterns, adapted from [Anthropic's guide on building effective agents](https://www.anthropic.com/research/building-effective-agents), serve as building blocks you can combine to create comprehensive workflows. Each pattern addresses specific aspects of task execution. Combine them thoughtfully to build reliable solutions for complex problems. ## Sequential Processing (Chains) The simplest workflow pattern executes steps in a predefined order. Each step's output becomes input for the next step, creating a clear chain of operations. Use this pattern for tasks with well-defined sequences, like content generation pipelines or data transformation processes. ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; async function generateMarketingCopy(input: string) { const model = "anthropic/claude-sonnet-5.5"; // First step: Generate marketing copy const { text: copy } = await generateText({ model, prompt: `Write persuasive marketing copy for: ${input}. Focus on benefits and emotional appeal.`, }); // Perform quality check on copy const { output: qualityMetrics } = await generateText({ model, output: Output.object({ schema: z.object({ hasCallToAction: z.boolean(), emotionalAppeal: z.number().min(1).max(10), clarity: z.number().min(1).max(10), }), }), prompt: `Evaluate this marketing copy for: 1. Presence of call to action (true/false) 2. Emotional appeal (1-10) 3. Clarity (1-10) Copy to evaluate: ${copy}`, }); // If quality check fails, regenerate with more specific instructions if ( !qualityMetrics.hasCallToAction || qualityMetrics.emotionalAppeal < 7 || qualityMetrics.clarity < 7 ) { const { text: improvedCopy } = await generateText({ model, prompt: `Rewrite this marketing copy with: ${!qualityMetrics.hasCallToAction ? '- A clear call to action' : ''} ${qualityMetrics.emotionalAppeal < 7 ? '- Stronger emotional appeal' : ''} ${qualityMetrics.clarity < 7 ? '- Improved clarity and directness' : ''} Original copy: ${copy}`, }); return { copy: improvedCopy, qualityMetrics }; } return { copy, qualityMetrics }; } ``` ## Routing This pattern lets the model decide which path to take through a workflow based on context and intermediate results. The model acts as an intelligent router, directing the flow of execution between different branches of your workflow. Use this when handling varied inputs that require different processing approaches. In the example below, the first LLM call's results determine the second call's model size and system prompt. ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; async function handleCustomerQuery(query: string) { const model = "anthropic/claude-sonnet-5.5"; // First step: Classify the query type const { output: classification } = await generateText({ model, output: Output.object({ schema: z.object({ reasoning: z.string(), type: z.enum(['general', 'refund', 'technical']), complexity: z.enum(['simple', 'complex']), }), }), prompt: `Classify this customer query: ${query} Determine: 1. Query type (general, refund, or technical) 2. Complexity (simple or complex) 3. Brief reasoning for classification`, }); // Route based on classification // Set model and system prompt based on query type and complexity const { text: response } = await generateText({ model: classification.complexity === 'simple' ? 'openai/gpt-6-luna' : 'openai/gpt-6-astra', instructions: { general: 'You are an expert customer service agent handling general inquiries.', refund: 'You are a customer service agent specializing in refund requests. Follow company policy and collect necessary information.', technical: 'You are a technical support specialist with deep product knowledge. Focus on clear step-by-step troubleshooting.', }[classification.type], prompt: query, }); return { response, classification }; } ``` ## Parallel Processing Break down tasks into independent subtasks that execute simultaneously. This pattern uses parallel execution to improve efficiency while maintaining the benefits of structured workflows. For example, analyze multiple documents or process different aspects of a single input concurrently (like code review). ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; // Example: Parallel code review with multiple specialized reviewers async function parallelCodeReview(code: string) { const model = "anthropic/claude-sonnet-5.5"; // Run parallel reviews const [securityReview, performanceReview, maintainabilityReview] = await Promise.all([ generateText({ model, instructions: 'You are an expert in code security. Focus on identifying security vulnerabilities, injection risks, and authentication issues.', output: Output.object({ schema: z.object({ vulnerabilities: z.array(z.string()), riskLevel: z.enum(['low', 'medium', 'high']), suggestions: z.array(z.string()), }), }), prompt: `Review this code: ${code}`, }), generateText({ model, instructions: 'You are an expert in code performance. Focus on identifying performance bottlenecks, memory leaks, and optimization opportunities.', output: Output.object({ schema: z.object({ issues: z.array(z.string()), impact: z.enum(['low', 'medium', 'high']), optimizations: z.array(z.string()), }), }), prompt: `Review this code: ${code}`, }), generateText({ model, instructions: 'You are an expert in code quality. Focus on code structure, readability, and adherence to best practices.', output: Output.object({ schema: z.object({ concerns: z.array(z.string()), qualityScore: z.number().min(1).max(10), recommendations: z.array(z.string()), }), }), prompt: `Review this code: ${code}`, }), ]); const reviews = [ { ...securityReview.output, type: 'security' }, { ...performanceReview.output, type: 'performance' }, { ...maintainabilityReview.output, type: 'maintainability' }, ]; // Aggregate results using another model instance const { text: summary } = await generateText({ model, instructions: 'You are a technical lead summarizing multiple code reviews.', prompt: `Synthesize these code review results into a concise summary with key actions: ${JSON.stringify(reviews, null, 2)}`, }); return { reviews, summary }; } ``` ## Orchestrator-Worker A primary model (orchestrator) coordinates the execution of specialized workers. Each worker optimizes for a specific subtask, while the orchestrator maintains overall context and ensures coherent results. This pattern excels at complex tasks requiring different types of expertise or processing. ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; async function implementFeature(featureRequest: string) { // Orchestrator: Plan the implementation const { output: implementationPlan } = await generateText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ files: z.array( z.object({ purpose: z.string(), filePath: z.string(), changeType: z.enum(['create', 'modify', 'delete']), }), ), estimatedComplexity: z.enum(['low', 'medium', 'high']), }), }), instructions: 'You are a senior software architect planning feature implementations.', prompt: `Analyze this feature request and create an implementation plan: ${featureRequest}`, }); // Workers: Execute the planned changes const fileChanges = await Promise.all( implementationPlan.files.map(async file => { // Each worker is specialized for the type of change const workerSystemPrompt = { create: 'You are an expert at implementing new files following best practices and project patterns.', modify: 'You are an expert at modifying existing code while maintaining consistency and avoiding regressions.', delete: 'You are an expert at safely removing code while ensuring no breaking changes.', }[file.changeType]; const { output: change } = await generateText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ explanation: z.string(), code: z.string(), }), }), instructions: workerSystemPrompt, prompt: `Implement the changes for ${file.filePath} to support: ${file.purpose} Consider the overall feature context: ${featureRequest}`, }); return { file, implementation: change, }; }), ); return { plan: implementationPlan, changes: fileChanges, }; } ``` ## Evaluator-Optimizer Add quality control to workflows with dedicated evaluation steps that assess intermediate results. Based on the evaluation, the workflow proceeds, retries with adjusted parameters, or takes corrective action. This creates robust workflows capable of self-improvement and error recovery. ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; async function translateWithFeedback(text: string, targetLanguage: string) { let currentTranslation = ''; let iterations = 0; const MAX_ITERATIONS = 3; // Initial translation const { text: translation } = await generateText({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are an expert literary translator.', prompt: `Translate this text to ${targetLanguage}, preserving tone and cultural nuances: ${text}`, }); currentTranslation = translation; // Evaluation-optimization loop while (iterations < MAX_ITERATIONS) { // Evaluate current translation const { output: evaluation } = await generateText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ qualityScore: z.number().min(1).max(10), preservesTone: z.boolean(), preservesNuance: z.boolean(), culturallyAccurate: z.boolean(), specificIssues: z.array(z.string()), improvementSuggestions: z.array(z.string()), }), }), instructions: 'You are an expert in evaluating literary translations.', prompt: `Evaluate this translation: Original: ${text} Translation: ${currentTranslation} Consider: 1. Overall quality 2. Preservation of tone 3. Preservation of nuance 4. Cultural accuracy`, }); // Check if quality meets threshold if ( evaluation.qualityScore >= 8 && evaluation.preservesTone && evaluation.preservesNuance && evaluation.culturallyAccurate ) { break; } // Generate improved translation based on feedback const { text: improvedTranslation } = await generateText({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are an expert literary translator.', prompt: `Improve this translation based on the following feedback: ${evaluation.specificIssues.join('\n')} ${evaluation.improvementSuggestions.join('\n')} Original: ${text} Current Translation: ${currentTranslation}`, }); currentTranslation = improvedTranslation; iterations++; } return { finalTranslation: currentTranslation, iterationsRequired: iterations, }; } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Backpressure description: How to handle backpressure and cancellation when working with the AI SDK url: "https://ai-sdk.dev/docs/advanced/backpressure" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This page focuses on understanding back-pressure and cancellation when working with streams. You do not need to know this information to use the AI SDK, but for those interested, it offers a deeper dive on why and how the SDK optimally streams responses. In the following sections, we'll explore back-pressure and cancellation in the context of a simple example program. We'll discuss the issues that can arise from an eager approach and demonstrate how a lazy approach can resolve them. ## Back-pressure and Cancellation with Streams Let's begin by setting up a simple example program: ```jsx // A generator that will yield positive integers async function* integers() { let i = 1; while (true) { console.log(`yielding ${i}`); yield i++; await sleep(100); } } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Wraps a generator into a ReadableStream function createStream(iterator) { return new ReadableStream({ async start(controller) { for await (const v of iterator) { controller.enqueue(v); } controller.close(); }, }); } // Collect data from stream async function run() { // Set up a stream of integers const stream = createStream(integers()); // Read values from our stream const reader = stream.getReader(); for (let i = 0; i < 10_000; i++) { // we know our stream is infinite, so there's no need to check `done`. const { value } = await reader.read(); console.log(`read ${value}`); await sleep(1_000); } } run(); ``` In this example, we create an async-generator that yields positive integers, a `ReadableStream` that wraps our integer generator, and a reader which will read values out of our stream. Notice, too, that our integer generator logs out `"yielding ${i}"`, and our reader logs out `"read ${value}"`. Both take an arbitrary amount of time to process data, represented with a 100ms sleep in our generator, and a 1sec sleep in our reader. ## Back-pressure If you were to run this program, you'd notice something funny. We'll see roughly 10 "yield" logs for every "read" log. This might seem obvious, the generator can push values 10x faster than the reader can pull them out. But it represents a problem, our `stream` has to maintain an ever expanding queue of items that have been pushed in but not pulled out. The problem stems from the way we wrap our generator into a stream. Notice the use of `for await (…)` inside our `start` handler. This is an **eager** for-loop, and it is constantly running to get the next value from our generator to be enqueued in our stream. This means our stream does not respect back-pressure, the signal from the consumer to the producer that more values aren't needed *yet*. We've essentially spawned a thread that will perpetually push more data into the stream, one that runs as fast as possible to push new data immediately. Worse, there's no way to signal to this thread to stop running when we don't need additional data. To fix this, `ReadableStream` allows a `pull` handler. `pull` is called every time the consumer attempts to read more data from our stream (if there's no data already queued internally). But it's not enough to just move the `for await(…)` into `pull`, we also need to convert from an eager enqueuing to a **lazy** one. By making these 2 changes, we'll be able to react to the consumer. If they need more data, we can easily produce it, and if they don't, then we don't need to spend any time doing unnecessary work. ```jsx function createStream(iterator) { return new ReadableStream({ async pull(controller) { const { value, done } = await iterator.next(); if (done) { controller.close(); } else { controller.enqueue(value); } }, }); } ``` Our `createStream` is a little more verbose now, but the new code is important. First, we need to manually call our `iterator.next()` method. This returns a `Promise` for an object with the type signature `{ done: boolean, value: T }`. If `done` is `true`, then we know that our iterator won't yield any more values and we must `close` the stream (this allows the consumer to know that the stream is also finished producing values). Else, we need to `enqueue` our newly produced value. When we run this program, we see that our "yield" and "read" logs are now paired. We're no longer yielding 10x integers for every read! And, our stream now only needs to maintain 1 item in its internal buffer. We've essentially given control to the consumer, so that it's responsible for producing new values as it needs it. Neato! ## Cancellation Let's go back to our initial eager example, with 1 small edit. Now instead of reading 10,000 integers, we're only going to read 3: ```jsx // A generator that will yield positive integers async function* integers() { let i = 1; while (true) { console.log(`yielding ${i}`); yield i++; await sleep(100); } } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // Wraps a generator into a ReadableStream function createStream(iterator) { return new ReadableStream({ async start(controller) { for await (const v of iterator) { controller.enqueue(v); } controller.close(); }, }); } // Collect data from stream async function run() { // Set up a stream of integers const stream = createStream(integers()); // Read values from our stream const reader = stream.getReader(); // We're only reading 3 items this time: for (let i = 0; i < 3; i++) { // we know our stream is infinite, so there's no need to check `done`. const { value } = await reader.read(); console.log(`read ${value}`); await sleep(1000); } } run(); ``` We're back to yielding 10x the number of values read. But notice now, after we've read 3 values, we're continuing to yield new values. We know that our reader will never read another value, but our stream doesn't! The eager `for await (…)` will continue forever, loudly enqueuing new values into our stream's buffer and increasing our memory usage until it consumes all available program memory. The fix to this is exactly the same: use `pull` and manual iteration. By producing values ***lazily***, we tie the lifetime of our integer generator to the lifetime of the reader. Once the reads stop, the yields will stop too: ```jsx // Wraps a generator into a ReadableStream function createStream(iterator) { return new ReadableStream({ async pull(controller) { const { value, done } = await iterator.next(); if (done) { controller.close(); } else { controller.enqueue(value); } }, }); } ``` Since the solution is the same as implementing back-pressure, it shows that they're just 2 facets of the same problem: Pushing values into a stream should be done **lazily**, and doing it eagerly results in expected problems. ## Tying Stream Laziness to AI Responses Now let's imagine you're integrating AIBot service into your product. Users will be able to prompt "count from 1 to infinity", the browser will fetch your AI API endpoint, and your servers connect to AIBot to get a response. But "infinity" is, well, infinite. The response will never end! After a few seconds, the user gets bored and navigates away. Or maybe you're doing local development and a hot-module reload refreshes your page. The browser will have ended its connection to the API endpoint, but will your server end its connection with AIBot? If you used the eager `for await (...)` approach, then the connection is still running and your server is asking for more and more data from AIBot. Our server spawned a "thread" and there's no signal when we can end the eager pulls. Eventually, the server is going to run out of memory (remember, there's no active fetch connection to read the buffering responses and free them). With the lazy approach, this is taken care of for you. Because the stream will only request new data from AIBot when the consumer requests it, navigating away from the page naturally frees all resources. The fetch connection aborts and the server can clean up the response. The `ReadableStream` tied to that response can now be garbage collected. When that happens, the connection it holds to AIBot can then be freed. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Caching description: How to handle caching when working with the AI SDK url: "https://ai-sdk.dev/docs/advanced/caching" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Depending on the type of application you're building, you may want to cache the responses you receive from your AI provider, at least temporarily. ## Using Language Model Middleware (Recommended) The recommended approach to caching responses is using [language model middleware](/docs/ai-sdk-core/middleware) and the [`simulateReadableStream`](/docs/reference/ai-sdk-core/simulate-readable-stream) function. Language model middleware is a way to enhance the behavior of language models by intercepting and modifying the calls to the language model. Let's see how you can use language model middleware to cache responses. ```ts title="ai/middleware.ts" import { Redis } from '@upstash/redis'; import { type LanguageModelV4, type LanguageModelV4Middleware, type LanguageModelV4StreamPart, simulateReadableStream, } from 'ai'; const redis = new Redis({ url: process.env.KV_URL, token: process.env.KV_TOKEN, }); export const cacheMiddleware: LanguageModelV4Middleware = { specificationVersion: 'v4', wrapGenerate: async ({ doGenerate, params }) => { const cacheKey = JSON.stringify(params); const cached = (await redis.get(cacheKey)) as Awaited< ReturnType > | null; if (cached !== null) { return { ...cached, response: { ...cached.response, timestamp: cached?.response?.timestamp ? new Date(cached?.response?.timestamp) : undefined, }, }; } const result = await doGenerate(); redis.set(cacheKey, result); return result; }, wrapStream: async ({ doStream, params }) => { const cacheKey = JSON.stringify(params); // Check if the result is in the cache const cached = await redis.get(cacheKey); // If cached, return a simulated ReadableStream that yields the cached result if (cached !== null) { // Format the timestamps in the cached response const formattedChunks = (cached as LanguageModelV4StreamPart[]).map(p => { if (p.type === 'response-metadata' && p.timestamp) { return { ...p, timestamp: new Date(p.timestamp) }; } else return p; }); return { stream: simulateReadableStream({ initialDelayInMs: 0, chunkDelayInMs: 10, chunks: formattedChunks, }), }; } // If not cached, proceed with streaming const { stream, ...rest } = await doStream(); const fullResponse: LanguageModelV4StreamPart[] = []; const transformStream = new TransformStream< LanguageModelV4StreamPart, LanguageModelV4StreamPart >({ transform(chunk, controller) { fullResponse.push(chunk); controller.enqueue(chunk); }, flush() { // Store the full response in the cache after streaming is complete redis.set(cacheKey, fullResponse); }, }); return { stream: stream.pipeThrough(transformStream), ...rest, }; }, }; ``` This example uses `@upstash/redis` to store and retrieve the assistant's responses but you can use any KV storage provider you would like. This middleware caches the raw model response before AI SDK validates structured output. When using structured output, cache only a response that has passed your schema validation; otherwise, an invalid response can be replayed from the cache on later requests. `LanguageModelV4Middleware` has the `wrapGenerate` and `wrapStream` wrapping methods, plus the optional `transformParams` method. `wrapGenerate` is called when using [`generateText`](/docs/reference/ai-sdk-core/generate-text), while `wrapStream` is called when using [`streamText`](/docs/reference/ai-sdk-core/stream-text). For `wrapGenerate`, you can cache the response directly. Instead, for `wrapStream`, you cache an array of the stream parts, which can then be used with [`simulateReadableStream`](/docs/ai-sdk-core/testing#simulate-ui-message-stream-responses) function to create a simulated `ReadableStream` that returns the cached response. In this way, the cached response is returned chunk-by-chunk as if it were being generated by the model. You can control the initial delay and delay between chunks by adjusting the `initialDelayInMs` and `chunkDelayInMs` parameters of `simulateReadableStream`. You can see a full example of caching with Redis in a Next.js application in our [Caching Middleware Recipe](/cookbook/next/caching-middleware). ## Using Lifecycle Callbacks Alternatively, each AI SDK Core function has special lifecycle callbacks you can use. The one of interest is likely `onEnd`, which is called when the generation is complete. This is where you can cache the full response. Here's an example of how you can use [Upstash Redis](https://upstash.com/redis) and Next.js to cache the OpenAI response for 1 hour: ```tsx title="app/api/chat/route.ts" import { convertToModelMessages, createTextStreamResponse, streamText, toTextStream, UIMessage, } from 'ai'; import { Redis } from '@upstash/redis'; // Allow streaming responses up to 30 seconds export const maxDuration = 30; const redis = new Redis({ url: process.env.KV_URL, token: process.env.KV_TOKEN, }); export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); // come up with a key based on the request: const key = JSON.stringify(messages); // Check if we have a cached response const cached = (await redis.get(key)) as string | null; if (cached != null) { return new Response(cached, { status: 200, headers: { 'Content-Type': 'text/plain' }, }); } // Call the language model: const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), async onEnd({ text }) { // Cache the response text: await redis.set(key, text); await redis.expire(key, 60 * 60); }, }); // Respond with a text stream, matching the cached plain text response return createTextStreamResponse({ stream: toTextStream({ stream: result.stream }), }); } ``` Both the cached and the fresh responses use the [text stream protocol](/docs/ai-sdk-ui/stream-protocol#text-stream-protocol), so use `TextStreamChatTransport` when calling this route with `useChat`. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Advanced description: Learn how to use advanced functionality within the AI SDK and RSC API. url: "https://ai-sdk.dev/docs/advanced" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This section covers advanced topics and concepts for the AI SDK and RSC API. Working with LLMs often requires a different mental model compared to traditional software development. After these concepts, you should have a better understanding of the paradigms behind the AI SDK and RSC API, and how to use them to build more AI applications. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Language Models as Routers description: Generative User Interfaces and Language Models as Routers url: "https://ai-sdk.dev/docs/advanced/model-as-router" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Since language models can render user interfaces as part of their generations, the resulting model generations are referred to as generative user interfaces. In this section we will learn more about generative user interfaces and their impact on the way AI applications are built. ## Deterministic Routes and Probabilistic Routing Generative user interfaces are not deterministic in nature because they depend on the model's generation output. Since these generations are probabilistic in nature, it is possible for every user query to result in a different user interface. Users expect their experience using your application to be predictable, so non-deterministic user interfaces can sound like a bad idea at first. However, language models can be set up to limit their generations to a particular set of outputs using their ability to call functions. When language models are provided with a set of function definitions and instructed to execute any of them based on user query, they do either one of the following things: - Execute a function that is most relevant to the user query. - Not execute any function if the user query is out of bounds of the set of functions available to them. ```tsx title='app/actions.ts' const sendMessage = (prompt: string) => generateText({ model: "anthropic/claude-sonnet-5.5", instructions: 'you are a friendly weather assistant!', prompt, tools: { getWeather: { description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }: { location: string }) => ({ location, temperature: 72 + Math.floor(Math.random() * 21) - 10, }), }, }, }); sendMessage('What is the weather in San Francisco?'); // getWeather is called sendMessage('What is the weather in New York?'); // getWeather is called sendMessage('What events are happening in London?'); // No function is called ``` This way, it is possible to ensure that the generations result in deterministic outputs, while the choice a model makes still remains to be probabilistic. This emergent ability exhibited by a language model to choose whether a function needs to be executed or not based on a user query is believed to be models emulating "reasoning". As a result, the combination of language models being able to reason which function to execute as well as render user interfaces at the same time gives you the ability to build applications where language models can be used as a router. ## Language Models as Routers Historically, developers had to write routing logic that connected different parts of an application to be navigable by a user and complete a specific task. In web applications today, most of the routing logic takes place in the form of routes: - `/login` would navigate you to a page with a login form. - `/user/john` would navigate you to a page with profile details about John. - `/api/events?limit=5` would display the five most recent events from an events database. While routes help you build web applications that connect different parts of an application into a seamless user experience, it can also be a burden to manage them as the complexity of applications grow. Next.js has helped reduce complexity in developing with routes by introducing: - File-based routing system - Dynamic routing - API routes - Middleware - App router, and so on... With language models becoming better at reasoning, we believe that there is a future where developers only write core application specific components while models take care of routing them based on the user's state in an application. With generative user interfaces, the language model decides which user interface to render based on the user's state in the application, giving users the flexibility to interact with your application in a conversational manner instead of navigating through a series of predefined routes. ### Routing by parameters For routes like: - `/profile/[username]` - `/search?q=[query]` - `/media/[id]` that have segments dependent on dynamic data, the language model can generate the correct parameters and render the user interface. For example, when you're in a search application, you can ask the language model to search for artworks from different artists. The language model will call the search function with the artist's name as a parameter and render the search results.
#### Media Search Let your users see more than words can say by rendering components directly within your search experience.
### Routing by sequence For actions that require a sequence of steps to be completed by navigating through different routes, the language model can generate the correct sequence of routes to complete in order to fulfill the user's request. For example, when you're in a calendar application, you can ask the language model to schedule a happy hour evening with your friends. The language model will then understand your request and will perform the right sequence of [tool calls](/docs/ai-sdk-core/tools-and-tool-calling) to: 1. Lookup your calendar 2. Lookup your friends' calendars 3. Determine the best time for everyone 4. Search for nearby happy hour spots 5. Create an event and send out invites to your friends
#### Planning an Event The model calls functions and generates interfaces based on user intent, acting like a router.
Just by defining functions to lookup contacts, pull events from a calendar, and search for nearby locations, the model is able to sequentially navigate the routes for you. To learn more, check out these [examples](/examples/next-app/interface) using the `streamUI` function to stream generative user interfaces to the client based on the response from the language model. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Multiple Streamables description: Learn to handle multiple streamables in your application. url: "https://ai-sdk.dev/docs/advanced/multiple-streamables" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Multiple Streamable UIs The AI SDK RSC APIs allow you to compose and return any number of streamable UIs, along with other data, in a single request. This can be useful when you want to decouple the UI into smaller components and stream them separately. ```tsx title='app/actions.tsx' 'use server'; import { createStreamableUI } from '@ai-sdk/rsc'; export async function getWeather() { const weatherUI = createStreamableUI(); const forecastUI = createStreamableUI(); weatherUI.update(
Loading weather...
); forecastUI.update(
Loading forecast...
); getWeatherData().then(weatherData => { weatherUI.done(
{weatherData}
); }); getForecastData().then(forecastData => { forecastUI.done(
{forecastData}
); }); // Return both streamable UIs and other data fields. return { requestedAt: Date.now(), weather: weatherUI.value, forecast: forecastUI.value, }; } ``` The client side code is similar to the previous example, but the [tool call](/docs/ai-sdk-core/tools-and-tool-calling) will return the new data structure with the weather and forecast UIs. Depending on the speed of getting weather and forecast data, these two components might be updated independently. ## Nested Streamable UIs You can stream UI components within other UI components. This allows you to create complex UIs that are built up from smaller, reusable components. In the example below, we pass a `historyChart` streamable as a prop to a `StockCard` component. The StockCard can render the `historyChart` streamable, and it will automatically update as the server responds with new data. ```tsx title='app/actions.tsx' async function getStockHistoryChart({ symbol }: { symbol: string }) { 'use server'; const ui = createStreamableUI(); // We need to wrap this in an async IIFE to avoid blocking. (async () => { const price = await getStockPrice({ symbol }); // Show a spinner as the history chart for now. const historyChart = createStreamableUI(); ui.done(); // Getting the history data and then update that part of the UI. const historyData = await fetch('https://my-stock-data-api.com'); historyChart.done(); })(); return ui; } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Multistep Interfaces description: Concepts behind building multistep interfaces url: "https://ai-sdk.dev/docs/advanced/multistep-interfaces" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Multistep interfaces refer to user interfaces that require multiple independent steps to be executed in order to complete a specific task. In order to understand multistep interfaces, it is important to understand two concepts: - Tool composition - Application context **Tool composition** is the process of combining multiple [tools](/docs/ai-sdk-core/tools-and-tool-calling) to create a new tool. This is a powerful concept that allows you to break down complex tasks into smaller, more manageable steps. **Application context** refers to the state of the application at any given point in time. This includes the user's input, the output of the language model, and any other relevant information. When designing multistep interfaces, you need to consider how the tools in your application can be composed together to form a coherent user experience as well as how the application context changes as the user progresses through the interface. ## Application Context The application context can be thought of as the conversation history between the user and the language model. The richer the context, the more information the model has to generate relevant responses. In the context of multistep interfaces, the application context becomes even more important. This is because **the user's input in one step may affect the output of the model in the next step**. For example, consider a meal logging application that helps users track their daily food intake. The language model is provided with the following tools: - `log_meal` takes in parameters like the name of the food, the quantity, and the time of consumption to log a meal. - `delete_meal` takes in the name of the meal to be deleted. When the user logs a meal, the model generates a response confirming the meal has been logged. ```txt {2} User: Log a chicken shawarma for lunch. Tool: log_meal("chicken shawarma", "250g", "12:00 PM") Model: Chicken shawarma has been logged for lunch. ``` Now when the user decides to delete the meal, the model should be able to reference the previous step to identify the meal to be deleted. ```txt {7} User: Log a chicken shawarma for lunch. Tool: log_meal("chicken shawarma", "250g", "12:00 PM") Model: Chicken shawarma has been logged for lunch. ... ... User: I skipped lunch today, can you update my log? Tool: delete_meal("chicken shawarma") Model: Chicken shawarma has been deleted from your log. ``` In this example, managing the application context is important for the model to generate the correct response. The model needs to have information about the previous actions in order for it to use generate the parameters for the `delete_meal` tool. ## Tool Composition Tool composition is the process of combining multiple tools to create a new tool. This involves defining the inputs and outputs of each tool, as well as how they interact with each other. The design of how these tools can be composed together to form a multistep interface is crucial to both the user experience of your application and the model's ability to generate the correct output. For example, consider a flight booking assistant that can help users book flights. The assistant can be designed to have the following tools: - `searchFlights`: Searches for flights based on the user's query. - `lookupFlight`: Looks up details of a specific flight based on the flight number. - `bookFlight`: Books a flight based on the user's selection. The `searchFlights` tool is called when the user wants to lookup flights for a specific route. This would typically mean the tool should be able to take in parameters like the origin and destination of the flight. The `lookupFlight` tool is called when the user wants to get more details about a specific flight. This would typically mean the tool should be able to take in parameters like the flight number and return information about seat availability. The `bookFlight` tool is called when the user decides to book a flight. In order to identify the flight to book, the tool should be able to take in parameters like the flight number, trip date, and passenger details. So the conversation between the user and the model could look like this: ```txt {8} User: I want to book a flight from New York to London. Tool: searchFlights("New York", "London") Model: Here are the available flights from New York to London. User: I want to book flight number BA123 on 12th December for myself and my wife. Tool: lookupFlight("BA123") -> "4 seats available" Model: Sure, there are seats available! Can you provide the names of the passengers? User: John Doe and Jane Doe. Tool: bookFlight("BA123", "12th December", ["John Doe", "Jane Doe"]) Model: Your flight has been booked! ``` After `lookupFlight` returns, the passenger details needed by the `bookFlight` tool are not yet available in the application context. The model asks the user for those details; once the user supplies the names, the model can call `bookFlight`. Looking up passenger information could've been another tool that the model could've called before calling the `bookFlight` tool assuming that the user is logged into the application. This way, the model does not have to ask the user for the passenger details and can proceed with the booking. ```txt {5,6} User: I want to book a flight from New York to London. Tool: searchFlights("New York", "London") Model: Here are the available flights from New York to London. User: I want to book flight number BA123 on 12th December for myself and my wife. Tool: lookupContacts() -> ["John Doe", "Jane Doe"] Tool: bookFlight("BA123", "12th December", ["John Doe", "Jane Doe"]) Model: Your flight has been booked! ``` The `lookupContacts` tool is called before the `bookFlight` tool to ensure that the passenger details are available in the application context when booking the flight. This way, the model can reduce the number of steps required from the user and use its ability to call tools that populate its context and use that information to complete the booking process. Now, let's introduce another tool called `lookupBooking` that can be used to show booking details by taking in the name of the passenger as parameter. This tool can be composed with the existing tools to provide a more complete user experience. ```txt {2-4} User: What's the status of my wife's upcoming flight? Tool: lookupContacts() -> ["John Doe", "Jane Doe"] Tool: lookupBooking("Jane Doe") -> "BA123 confirmed" Tool: lookupFlight("BA123") -> "Flight BA123 is scheduled to depart on 12th December." Model: Your wife's flight BA123 is confirmed and scheduled to depart on 12th December. ``` In this example, the `lookupBooking` tool is used to provide the user with the status of their wife's upcoming flight. By composing this tool with the existing tools, the model is able to generate a response that includes the booking status and the departure date of the flight without requiring the user to provide additional information. As a result, the more tools you design that can be composed together, the more complex and powerful your application can become. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Prompt Engineering description: Learn how to engineer prompts for LLMs with the AI SDK url: "https://ai-sdk.dev/docs/advanced/prompt-engineering" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## What is a Large Language Model (LLM)? A Large Language Model is essentially a prediction engine that takes a sequence of words as input and aims to predict the most likely sequence to follow. It does this by assigning probabilities to potential next sequences and then selecting one. The model continues to generate sequences until it meets a specified stopping criterion. These models learn by training on massive text corpuses, which means they will be better suited to some use cases than others. For example, a model trained on GitHub data would understand the probabilities of sequences in source code particularly well. However, it's crucial to understand that the generated sequences, while often seeming plausible, can sometimes be random and not grounded in reality. As these models become more accurate, many surprising abilities and applications emerge. ## What is a prompt? Prompts are the starting points for LLMs. They are the inputs that trigger the model to generate text. The scope of prompt engineering involves not just crafting these prompts but also understanding related concepts such as hidden prompts, tokens, token limits, and the potential for prompt hacking, which includes phenomena like jailbreaks and leaks. ## Why is prompt engineering needed? Prompt engineering currently plays a pivotal role in shaping the responses of LLMs. It allows us to tweak the model to respond more effectively to a broader range of queries. This includes the use of techniques like semantic search, command grammars, and the ReActive model architecture. The performance, context window, and cost of LLMs varies between models and model providers which adds further constraints to the mix. For example, the GPT-4 model is more expensive than GPT-3.5-turbo and significantly slower, but it can also be more effective at certain tasks. And so, like many things in software engineering, there is a trade-off between cost and performance. To assist with comparing and tweaking LLMs, we've built an AI playground that allows you to compare the performance of different models side-by-side online. When you're ready, you can even generate code with the AI SDK to quickly use your prompt and your selected model into your own applications. ## Example: Build a Slogan Generator ### Start with an instruction Imagine you want to build a slogan generator for marketing campaigns. Creating catchy slogans isn't always straightforward! First, you'll need a prompt that makes it clear what you want. Let's start with an instruction. Submit this prompt to generate your first completion. Not bad! Now, try making your instruction more specific. Introducing a single descriptive term to our prompt influences the completion. Essentially, crafting your prompt is the means by which you "instruct" or "program" the model. ### Include examples Clear instructions are key for quality outcomes, but that might not always be enough. Let's try to enhance your instruction further. These slogans are fine, but could be even better. It appears the model overlooked the 'live' part in our prompt. Let's change it slightly to generate more appropriate suggestions. Often, it's beneficial to both demonstrate and tell the model your requirements. Incorporating examples in your prompt can aid in conveying patterns or subtleties. Test this prompt that carries a few examples. Great! Incorporating examples of expected output for a certain input prompted the model to generate the kind of names we aimed for. ### Tweak your settings Apart from designing prompts, you can influence completions by tweaking model settings. A crucial setting is the **temperature**. You might have seen that the same prompt, when repeated, yielded the same or nearly the same completions. This happens when your temperature is at 0. Attempt to re-submit the identical prompt a few times with temperature set to 1. Notice the difference? With a temperature above 0, the same prompt delivers varied completions each time. Keep in mind that the model forecasts the text most likely to follow the preceding text. Temperature, a value from 0 to 1, essentially governs the model's confidence level in making these predictions. A lower temperature implies lesser risks, leading to more precise and deterministic completions. A higher temperature yields a broader range of completions. For your slogan generator, you might want a large pool of name suggestions. A moderate temperature of 0.6 should serve well. ## Recommended Resources Prompt Engineering is evolving rapidly, with new methods and research papers surfacing every week. Here are some resources that we've found useful for learning about and experimenting with prompt engineering: - [The Vercel AI Playground](/playground) - [Brex Prompt Engineering](https://github.com/brexhq/prompt-engineering) - [Prompt Engineering Guide by Dair AI](https://www.promptingguide.ai/) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Rate Limiting description: Learn how to rate limit your application. url: "https://ai-sdk.dev/docs/advanced/rate-limiting" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Rate limiting helps you protect your APIs from abuse. It involves setting a maximum threshold on the number of requests a client can make within a specified timeframe. This simple technique acts as a gatekeeper, preventing excessive usage that can degrade service performance and incur unnecessary costs. ## Rate Limiting with Upstash Redis and Upstash Ratelimit In this example, you will protect an API endpoint using [Upstash Redis](https://upstash.com/redis) and [Upstash Ratelimit](https://github.com/upstash/ratelimit). ```tsx title='app/api/generate/route.ts' import { createUIMessageStreamResponse, streamText, toUIMessageStream, } from 'ai'; import { Ratelimit } from '@upstash/ratelimit'; import { Redis } from '@upstash/redis'; import { NextRequest } from 'next/server'; // Allow streaming responses up to 30 seconds export const maxDuration = 30; // Create Rate limit const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.fixedWindow(5, '30s'), }); export async function POST(req: NextRequest) { // call ratelimit with request ip const ip = req.ip ?? 'ip'; const { success, remaining } = await ratelimit.limit(ip); // block the request if unsuccessful if (!success) { return new Response('Ratelimited!', { status: 429 }); } const { messages } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ## Simplify API Protection With Upstash Redis and Upstash Ratelimit, it is possible to protect your APIs from such attacks with ease. To learn more about how Ratelimit works and how it can be configured to your needs, see [Ratelimit Documentation](https://upstash.com/docs/oss/sdks/ts/ratelimit/overview). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Rendering UI with Language Models description: Rendering UI with Language Models url: "https://ai-sdk.dev/docs/advanced/rendering-ui-with-language-models" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Language models generate text, so at first it may seem like you would only need to render text in your application. ```tsx {16} title="app/actions.tsx" const text = generateText({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a friendly assistant', prompt: 'What is the weather in SF?', tools: { getWeather: { description: 'Get the weather for a location', inputSchema: z.object({ city: z.string().describe('The city to get the weather for'), unit: z .enum(['C', 'F']) .describe('The unit to display the temperature in'), }), execute: async ({ city, unit }) => { const weather = getWeather({ city, unit }); return `It is currently ${weather.value}°${unit} and ${weather.description} in ${city}!`; }, }, }, }); ``` Above, the language model is passed a [tool](/docs/ai-sdk-core/tools-and-tool-calling) called `getWeather` that returns the weather information as text. However, instead of returning text, if you return a JSON object that represents the weather information, you can use it to render a React component instead. ```tsx {18-23} title="app/action.ts" const text = generateText({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a friendly assistant', prompt: 'What is the weather in SF?', tools: { getWeather: { description: 'Get the weather for a location', inputSchema: z.object({ city: z.string().describe('The city to get the weather for'), unit: z .enum(['C', 'F']) .describe('The unit to display the temperature in'), }), execute: async ({ city, unit }) => { const weather = getWeather({ city, unit }); const { temperature, description, forecast } = weather; return { temperature, unit, description, forecast, }; }, }, }, }); ``` Now you can use the object returned by the `getWeather` function to conditionally render a React component `` that displays the weather information by passing the object as props. ```tsx title="app/page.tsx" return (
{messages.map(message => { // Check assistant message parts for tool results if (message.role === 'assistant') { return message.parts.map(part => { if ( part.type === 'tool-weather' && part.state === 'output-available' ) { const { temperature, unit, description, forecast } = part.output; return ( ); } }); } })}
); ``` Here's a little preview of what that might look like.
#### Weather An example of an assistant that renders the weather information in a streamed component.
Rendering interfaces as part of language model generations elevates the user experience of your application, allowing people to interact with language models beyond text. They also make it easier for you to interpret [sequential tool calls](/docs/ai-sdk-rsc/multistep-interfaces) that take place in multiple steps and help identify and debug where the model reasoned incorrectly. ## Rendering Multiple User Interfaces To recap, an application has to go through the following steps to render user interfaces as part of model generations: 1. The user prompts the language model. 2. The language model generates a response that includes a tool call. 3. The tool call returns a JSON object that represents the user interface. 4. The response is sent to the client. 5. The client receives the response and checks if the latest message was a tool call. 6. If it was a tool call, the client renders the user interface based on the JSON object returned by the tool call. Most applications have multiple tools that are called by the language model, and each tool can return a different user interface. For example, a tool that searches for courses can return a list of courses, while a tool that searches for people can return a list of people. As this list grows, the complexity of your application will grow as well and it can become increasingly difficult to manage these user interfaces. ```tsx title='app/page.tsx' { message.parts.map(part => { if (part.state !== 'output-available') return null; switch (part.type) { case 'tool-api-search-course': return ; case 'tool-api-search-profile': return ; case 'tool-api-meetings': return ; case 'tool-api-search-building': return ; case 'tool-api-events': return ; case 'tool-api-meals': return ; case 'text': return
{part.text}
; default: return null; } }); } ``` ## Rendering User Interfaces on the Server The **AI SDK RSC (`@ai-sdk/rsc`)** takes advantage of RSCs to solve the problem of managing all your React components on the client side, allowing you to render React components on the server and stream them to the client. Rather than conditionally rendering user interfaces on the client based on the data returned by the language model, you can directly stream them from the server during a model generation. ```tsx {3,22-31,38} title="app/action.ts" import { createStreamableUI } from '@ai-sdk/rsc'; const uiStream = createStreamableUI(); const text = generateText({ model: "anthropic/claude-sonnet-5.5", instructions: 'you are a friendly assistant', prompt: 'what is the weather in SF?', tools: { getWeather: { description: 'Get the weather for a location', inputSchema: z.object({ city: z.string().describe('The city to get the weather for'), unit: z .enum(['C', 'F']) .describe('The unit to display the temperature in'), }), execute: async ({ city, unit }) => { const weather = getWeather({ city, unit }); const { temperature, description, forecast } = weather; uiStream.done( , ); }, }, }, }); return { display: uiStream.value, }; ``` The [`createStreamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui) function belongs to the `@ai-sdk/rsc` module and creates a stream that can send React components to the client. On the server, you render the `` component with the props passed to it, and then stream it to the client. On the client side, you only need to render the UI that is streamed from the server. ```tsx title="app/page.tsx" {4} return (
{messages.map(message => (
{message.display}
))}
); ``` Now the steps involved are simplified: 1. The user prompts the language model. 2. The language model generates a response that includes a tool call. 3. The tool call renders a React component along with relevant props that represent the user interface. 4. The response is streamed to the client and rendered directly. > **Note:** You can also render text on the server and stream it to the client using React Server Components. This way, all operations from language model generation to UI rendering can be done on the server, while the client only needs to render the UI that is streamed from the server. Check out this [example](/examples/next-app/interface/stream-component-updates) for a full illustration of how to stream component updates with React Server Components in Next.js App Router. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Secure URL Fetching description: How the AI SDK protects server-side fetches of URLs returned by model providers, and how to harden your deployment further. url: "https://ai-sdk.dev/docs/advanced/secure-url-fetching" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Many providers return a **URL in their response body** — a generated image, audio, or video to download, or a polling URL to check job status. The AI SDK fetches these server-side and returns the result to your code. Because that URL comes from an external service, a malicious or compromised provider (or anyone able to tamper with the response) could point it at an internal address such as a cloud-metadata endpoint (`http://169.254.169.254/…`), a private host (`http://10.0.0.5/…`), or `localhost`. To prevent that, the SDK validates every response-supplied URL before fetching it. This happens automatically inside the provider packages — you don't need to configure anything. For authenticated task-status polling, providers can construct the first URL from the configured API endpoint. The SDK trusts that configured origin for the initial request, but manually follows and validates every redirect away from it. MiniMax, Kling AI, and ByteDance video polling use this protected path. ## What the SDK protects against When the SDK fetches a URL taken from a provider response, it: - **Rejects private, loopback, and link-local targets** — IPv4 (`10/8`, `172.16/12`, `192.168/16`, `127/8`, `169.254/16`, CGNAT, multicast, …) and the equivalent IPv6 ranges, plus `localhost`, `.local`, `.localhost`, and `.internal` hostnames. Non-`http(s)` schemes are rejected too. - **Re-validates every redirect hop** — a URL that passes but then redirects to an internal address is blocked; the redirect is never followed blindly. - **Validates DNS at connection time on Node.js** — every resolved address is checked, and the socket is pinned to the validated DNS result so DNS rebinding cannot introduce a different address between validation and connection. - **Strips risky request headers** — proxy-forwarding, cloud-metadata, and cookie headers are removed before the request. - **Drops credentials across origins** — caller headers (`Authorization`, `Cookie`, and provider-specific API-key headers alike) are not sent to a host on a different origin than the provider's; a redirect that crosses origin drops all of them except the user-agent. A blocked URL surfaces as a `DownloadError`. ## Fetching untrusted URLs directly For custom integrations using `@ai-sdk/provider-utils`, use `fetchUntrustedUrl` when the URL comes from a response or other untrusted input. It validates URLs and redirects and withholds credentials and unknown custom headers on the first request unless the URL matches a configured origin: ```ts import { fetchUntrustedUrl } from '@ai-sdk/provider-utils'; const response = await fetchUntrustedUrl({ url: result.downloadUrl, headers: { authorization: `Bearer ${apiKey}`, accept: 'image/*', }, credentialedOrigin: providerBaseURL, }); ``` Here, `Authorization` is sent only when the initial URL has the same origin as `providerBaseURL`. Otherwise, only allowlisted metadata such as `Accept`, `Range`, and `User-Agent` is sent. A cross-origin redirect drops all caller headers except `User-Agent`, and credentials are never reattached on later hops. `credentialedOrigin` must come from your configuration, never from the response URL. It does not disable URL validation. For a configured private endpoint, pass `trustedOrigin` as well; if `credentialedOrigin` is omitted, `fetchUntrustedUrl` uses `trustedOrigin` as the origin allowed to receive credentials. Extra protocol metadata can be allowed with `untrustedFirstHopHeaders: ['x-protocol-version']`. Only list headers that are safe to disclose to any destination; use a configured origin for credentials. The existing `fetchWithValidatedRedirects` helper preserves its first-request behavior: it sanitizes risky headers but still forwards authentication and custom headers. Switch to `fetchUntrustedUrl` to opt into first-request credential isolation. Existing calls to `getFromApi` also retain their behavior; pass `credentialedOrigin` when fetching response-supplied URLs with credentials. ## Self-hosted and local endpoints URLs that are same-origin with the provider endpoint **you configured** (e.g. a custom `baseURL` pointing at a self-hosted or `localhost` deployment) are exempt from these checks — they target exactly the host you told the SDK to talk to. This also applies to task-status polling. Any redirect off that origin is still validated before the redirected request is sent. ## DNS validation across runtimes On Node.js, the default validated download fetch uses `node:dns` and an `undici` connector hook to validate every resolved address at connection time. The connector uses those exact results, closing both hostname-to-private-IP and DNS-rebinding bypasses. Wrapping or replacing global `fetch` does not disable this protection: the default Node.js download transport is independent of global `fetch`. Bun, Deno, Cloudflare Workers, and framework edge runtimes use their platform fetch, even when they expose a Node-compatible `process` object. If you explicitly inject a custom `fetch`, it is responsible for equivalent DNS validation and connection pinning. Other runtimes do not expose Node's DNS/socket hooks, so server deployments on those runtimes should restrict network egress to private, loopback, link-local, and cloud-metadata ranges. ## Hardening your deployment If your server fetches provider-supplied URLs and you want to close the DNS gaps, use one (ideally both) of these: ### 1. Restrict outbound egress at the network layer Deny your server's network egress to `169.254.0.0/16`, RFC-1918 ranges, and loopback. This is the most robust control and is independent of application code. ### 2. Harden an injected `fetch` The Node.js default is already pinned. If you explicitly inject a custom `fetch`, back it with an `undici` `Agent` whose `connect.lookup` validates the resolved IP and lets the socket connect only to a safe address — closing both the hostname-to-private and the DNS-rebinding windows: ```ts import { Agent, fetch as undiciFetch } from 'undici'; import { lookup } from 'node:dns'; // Your own check that returns true for private/loopback/link-local addresses. declare function isUnsafeAddress(ip: string): boolean; const safeLookup: typeof lookup = (hostname, options, callback) => { lookup(hostname, options as any, (err, address, family) => { if (!err && typeof address === 'string' && isUnsafeAddress(address)) { callback(new Error(`Refusing to connect to ${address}`), '', 0); return; } (callback as any)(err, address, family); }); }; const safeDispatcher = new Agent({ connect: { lookup: safeLookup } }); const safeFetch: typeof fetch = (input, init) => undiciFetch(input, { ...init, dispatcher: safeDispatcher }) as any; ``` ```ts import { createFal } from '@ai-sdk/fal'; const fal = createFal({ fetch: safeFetch }); ``` The SDK's URL validation and your custom fetch's connect-time pinning are complementary — keep both. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Sequential Generations description: "Learn how to implement sequential generations (\"chains\") with the AI SDK" url: "https://ai-sdk.dev/docs/advanced/sequential-generations" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). When working with the AI SDK, you may want to create sequences of generations (often referred to as "chains" or "pipes"), where the output of one becomes the input for the next. This can be useful for creating more complex AI-powered workflows or for breaking down larger tasks into smaller, more manageable steps. ## Example In a sequential chain, the output of one generation is directly used as input for the next generation. This allows you to create a series of dependent generations, where each step builds upon the previous one. Here's an example of how you can implement sequential actions: ```typescript import { generateText } from 'ai'; async function sequentialActions() { // Generate blog post ideas const ideasGeneration = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Generate 10 ideas for a blog post about making spaghetti.', }); console.log('Generated Ideas:\n', ideasGeneration); // Pick the best idea const bestIdeaGeneration = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: `Here are some blog post ideas about making spaghetti: ${ideasGeneration} Pick the best idea from the list above and explain why it's the best.`, }); console.log('\nBest Idea:\n', bestIdeaGeneration); // Generate an outline const outlineGeneration = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: `We've chosen the following blog post idea about making spaghetti: ${bestIdeaGeneration} Create a detailed outline for a blog post based on this idea.`, }); console.log('\nBlog Post Outline:\n', outlineGeneration); } sequentialActions().catch(console.error); ``` In this example, we first generate ideas for a blog post, then pick the best idea, and finally create an outline based on that idea. Each step uses the output from the previous step as input for the next generation. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Stopping Streams description: Learn how to cancel streams with the AI SDK url: "https://ai-sdk.dev/docs/advanced/stopping-streams" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Canceling ongoing streams is often needed. For example, users might want to stop a stream when they realize that the response is not what they want. The different parts of the AI SDK support canceling streams in different ways. ## AI SDK Core The AI SDK functions have an `abortSignal` argument that you can use to cancel a stream. You would use this if you want to cancel a stream from the server side to the LLM API, e.g. by forwarding the `abortSignal` from the request. ```tsx {10,11,12-16} import { createTextStreamResponse, streamText, toTextStream } from 'ai'; export async function POST(req: Request) { const { prompt } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt, // forward the abort signal: abortSignal: req.signal, onAbort: ({ steps }) => { // Handle cleanup when stream is aborted console.log('Stream aborted after', steps.length, 'steps'); // Persist partial results to database }, }); return createTextStreamResponse({ stream: toTextStream({ stream: result.stream }), }); } ``` ## AI SDK UI The hooks, e.g. `useChat` or `useCompletion`, provide a `stop` helper function that can be used to cancel a stream. This aborts the HTTP request from the client. To also stop the model request on the server, your server runtime must propagate the client disconnect to the request's `AbortSignal`, and your route must forward that signal to the AI SDK Core call as shown above. Stream abort functionality is not compatible with stream resumption. If you're using `resume: true` in `useChat`, the abort functionality will break the resumption mechanism. Choose either abort or resume functionality, but not both. ```tsx title="app/page.tsx" {6,11-14} 'use client'; import { useCompletion } from '@ai-sdk/react'; export default function Chat() { const { input, completion, stop, status, handleSubmit, handleInputChange } = useCompletion(); return (
{(status === 'submitted' || status === 'streaming') && ( )} {completion}
); } ``` ### Vercel On Vercel, [request cancellation](https://vercel.com/docs/functions/functions-api-reference#cancel-requests) is only supported in the Node.js runtime and must be enabled for each function that needs it. Add `supportsCancellation` to the function's configuration in `vercel.json`: ```json title="vercel.json" { "functions": { "app/api/chat/route.ts": { "supportsCancellation": true } } } ``` With cancellation enabled, calling `stop()` aborts the client request, Vercel aborts `req.signal`, and forwarding `req.signal` as `abortSignal` cancels the model request. Without `supportsCancellation`, `stop()` still stops the client-side stream but the server-side generation may continue. ## Handling stream abort cleanup When streams are aborted, you may need to perform cleanup operations such as persisting partial results or cleaning up resources. The `onAbort` callback provides a way to handle these scenarios on the server side. Unlike `onEnd`, which is called when a stream completes normally, `onAbort` is specifically called when a stream is aborted via `AbortSignal`. This distinction allows you to handle normal completion and aborted streams differently. For UI message streams (`toUIMessageStreamResponse`), the `onEnd` callback also receives an `isAborted` parameter that indicates whether the stream was aborted. This allows you to handle both completion and abort scenarios in a single callback. ```tsx {8-12} import { streamText } from 'ai'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a long story...', abortSignal: controller.signal, onAbort: async ({ steps }) => { // Called when stream is aborted - persist partial results await savePartialResults(steps); await logAbortEvent(steps.length); }, onEnd: async ({ steps, totalUsage }) => { // Called when stream completes normally await saveFinalResults(steps, totalUsage); }, }); ``` The `onAbort` callback receives: - `steps`: Array of all completed steps before the abort occurred This is particularly useful for: - Persisting partial conversation history to database - Saving partial progress for later continuation - Cleaning up server-side resources or connections - Logging abort events for analytics You can also handle abort events directly in the stream using the `abort` stream part: ```tsx {6-9} for await (const part of result.stream) { switch (part.type) { case 'text-delta': // Handle text delta content break; case 'abort': // Handle abort event directly in stream console.log('Stream was aborted'); break; // ... other cases } } ``` ## UI Message Streams When using `toUIMessageStream`, you need to handle stream abortion slightly differently. The `onEnd` callback receives an `isAborted` parameter, and you should pass `consumeStream` to `createUIMessageStreamResponse` to ensure proper abort handling: ```tsx {3,21,24-30,34} import { openai } from '@ai-sdk/openai'; import { consumeStream, convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage, } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), abortSignal: req.signal, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, onEnd: async ({ isAborted }) => { if (isAborted) { console.log('Stream was aborted'); // Handle abort-specific cleanup } else { console.log('Stream completed normally'); // Handle normal completion } }, }), consumeSseStream: consumeStream, }); } ``` The `consumeStream` function is necessary for proper abort handling in UI message streams. It ensures that the stream is properly consumed even when aborted, preventing potential memory leaks or hanging connections. The `onEnd` callback distinguishes consumer cancellation from an observed abort. When the consumer cancels the UI message stream before an outcome is declared, such as during a client disconnect, `isCancelled` is `true`, `outcome.status` remains `'unknown'`, and `isAborted` remains `false`. When the stream observes an `abort` part first, `outcome.status` is `'aborted'`, `isAborted` is `true`, and `isCancelled` is absent. Check both flags when the same cleanup should run for either case. ## AI SDK RSC The AI SDK RSC does not currently support stopping streams. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Vercel Deployment Guide description: Learn how to deploy an AI application to production on Vercel url: "https://ai-sdk.dev/docs/advanced/vercel-deployment-guide" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). In this guide, you will deploy an AI application to [Vercel](https://vercel.com) using [Next.js](https://nextjs.org) (App Router). Vercel is a platform for developers that provides the tools, workflows, and infrastructure you need to build and deploy your web apps faster, without the need for additional configuration. Vercel allows for automatic deployments on every branch push and merges onto the production branch of your GitHub, GitLab, and Bitbucket projects. It is a great option for deploying your AI application. ## Before You Begin To follow along with this guide, you will need: - a Vercel account - an account with a Git provider (this tutorial will use [GitHub](https://github.com)) - an OpenAI API key This guide will teach you how to deploy the application you built in the Next.js (App Router) quickstart tutorial to Vercel. If you haven’t completed the quickstart guide, you can start with [this repo](https://github.com/vercel-labs/ai-sdk-deployment-guide). ## Commit Changes Vercel offers a powerful git-centered workflow that automatically deploys your application to production every time you push to your repository’s main branch. Before committing your local changes, make sure that you have a `.gitignore`. Within your `.gitignore`, ensure that you are excluding your environment variables (`.env`) and your node modules (`node_modules`). If you have any local changes, you can commit them by running the following commands: ```bash git add . git commit -m "init" ``` ## Create Git Repo You can create a GitHub repository from within your terminal, or on [github.com](https://github.com/). For this tutorial, you will use the GitHub CLI ([more info here](https://cli.github.com/)). To create your GitHub repository: 1. Navigate to [github.com](https://github.com/) 2. In the top right corner, click the "plus" icon and select "New repository" 3. Pick a name for your repository (this can be anything) 4. Click "Create repository" Once you have created your repository, GitHub will redirect you to your new repository. 1. Scroll down the page and copy the commands under the title "...or push an existing repository from the command line" 2. Go back to the terminal, paste and then run the commands Note: if you run into the error "error: remote origin already exists.", this is because your local repository is still linked to the repository you cloned. To "unlink", you can run the following command: ```bash rm -rf .git git init git add . git commit -m "init" ``` Rerun the code snippet from the previous step. ## Import Project in Vercel On the [New Project](https://vercel.com/new) page, under the **Import Git Repository** section, select the Git provider that you would like to import your project from. Follow the prompts to sign in to your GitHub account. Once you have signed in, you should see your newly created repository from the previous step in the "Import Git Repository" section. Click the "Import" button next to that project. ### Add Environment Variables Your application uses environment secrets to store your OpenAI API key using a `.env.local` file locally in development. To add this API key to your production deployment, expand the "Environment Variables" section and paste in your `.env.local` file. Vercel will automatically parse your variables and enter them in the appropriate `key:value` format. ### Deploy Press the **Deploy** button. Vercel will create the Project and deploy it based on the chosen configurations. ### Enjoy the confetti! To view your deployment, select the Project in the dashboard and then select the **Domain**. This page is now visible to anyone who has the URL. ## Considerations When deploying an AI application, there are infrastructure-related considerations to be aware of. ### Function Duration In most cases, you will call the large language model (LLM) on the server. By default, Vercel serverless functions have a maximum duration of 10 seconds on the Hobby Tier. Depending on your prompt, it can take an LLM more than this limit to complete a response. If the response is not resolved within this limit, the server will throw an error. You can specify the maximum duration of your Vercel function using [route segment config](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config). To update your maximum duration, add the following route segment config to the top of your route handler or the page which is calling your server action. ```ts export const maxDuration = 30; ``` You can increase the max duration to 60 seconds on the Hobby Tier. For other tiers, [see the documentation](https://vercel.com/docs/functions/runtimes#max-duration) for limits. ### Request Cancellation AI SDK UI helpers such as `useChat` and `useCompletion` abort the client request when you call `stop()`. To propagate that cancellation to a Vercel Function and its model request, you must: 1. Use the Node.js runtime, which is the default for Next.js route handlers. 2. Enable [`supportsCancellation`](https://vercel.com/docs/functions/functions-api-reference#cancel-requests) for the route in `vercel.json`. 3. Forward the route's `req.signal` to `streamText` or `generateText` as `abortSignal`. For example, enable cancellation for your chat route: ```json title="vercel.json" { "functions": { "app/api/chat/route.ts": { "supportsCancellation": true } } } ``` Then forward the request signal to the AI SDK: ```ts title="app/api/chat/route.ts" {10} import { convertToModelMessages, streamText, type UIMessage } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: convertToModelMessages(messages), abortSignal: req.signal, }); return result.toUIMessageStreamResponse(); } ``` Without `supportsCancellation`, calling `stop()` closes the client-side stream but does not cancel the Vercel Function or model request. See [Stopping Streams](/docs/advanced/stopping-streams) for the complete client and server setup. ## Security Considerations Given the high cost of calling an LLM, it's important to have measures in place that can protect your application from abuse. ### Rate Limit Rate limiting is a method used to regulate network traffic by defining a maximum number of requests that a client can send to a server within a given time frame. Follow [this guide](https://vercel.com/guides/securing-ai-app-rate-limiting) to add rate limiting to your application. ### Firewall A firewall helps protect your applications and websites from DDoS attacks and unauthorized access. [Vercel Firewall](https://vercel.com/docs/security/vercel-firewall) is a set of tools and infrastructure, created specifically with security in mind. It automatically mitigates DDoS attacks and Enterprise teams can get further customization for their site, including dedicated support and custom rules for IP blocking. ## Troubleshooting - Streaming not working when [proxied](/docs/troubleshooting/streaming-not-working-when-proxied) - Experiencing [Timeouts](/docs/troubleshooting/timeout-on-vercel) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Batch description: Learn how to process asynchronous batches with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/batch" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Batch support is experimental and the API may change in patch releases. Batches let you submit multiple independent requests for asynchronous processing. The provider processes the batch in the background, so your application does not need to keep the request open while the model generates the results. This is useful for workloads such as classification, summarization, and content generation that do not need an immediate response. The batch API supports text generation with `type: 'text'` and image generation with `type: 'image'`. The AI SDK provides five functions for the batch lifecycle: - [`experimental_startBatch`](/docs/reference/ai-sdk-core/start-batch) submits a batch and returns its initial status and a serializable reference. - [`experimental_getBatchStatus`](/docs/reference/ai-sdk-core/get-batch-status) retrieves the latest status and request counts. - [`experimental_getBatchResults`](/docs/reference/ai-sdk-core/get-batch-results) asynchronously iterates over the terminal result for each request. - [`experimental_cancelBatch`](/docs/reference/ai-sdk-core/cancel-batch) requests cancellation of a batch. - [`experimental_listBatches`](/docs/reference/ai-sdk-core/list-batches) lists batches and their latest statuses. All five functions are exported from `ai`. The examples below use aliases so the shorter names can be used in application code: ```ts import { experimental_cancelBatch as cancelBatch, experimental_getBatchResults as getBatchResults, experimental_getBatchStatus as getBatchStatus, experimental_listBatches as listBatches, experimental_startBatch as startBatch, } from 'ai'; ``` Cancellation and listing are optional provider capabilities. Calling either function with a provider that does not implement it throws an `UnsupportedFunctionalityError`. In the cancellation and listing examples below, `provider` represents a batch provider that implements the respective capability. ## Supported providers Batch processing requires a provider that implements the batch interface. Support is provider- and model-specific. The first-party providers with support are: | Provider | Provider value | Request types | Provider API | | ---------------------------------------------------- | -------------- | ------------- | --------------------------------------------------------------------------------------------- | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `anthropic` | text | [Message Batches API](https://platform.claude.com/docs/en/build-with-claude/batch-processing) | | [Google](/providers/ai-sdk-providers/google) | `google` | text, image | [Gemini Batch API](https://ai.google.dev/gemini-api/docs/batch-api) | | [OpenAI](/providers/ai-sdk-providers/openai) | `openai` | text | [Batch API](https://developers.openai.com/api/docs/guides/batch) | | [xAI](/providers/ai-sdk-providers/xai) | `xai` | text, image | [Batch API](https://docs.x.ai/developers/advanced-api-usage/batch-api) | | [AI Gateway](/providers/ai-sdk-providers/ai-gateway) | global default | text | [Batch processing](https://vercel.com/docs/ai-gateway/models-and-providers/batch-processing) | See the provider documentation for the supported models, limits, and native batch behavior. For example, OpenAI batch support is available through the Responses API, not `openai.chat()`. ## Starting a batch Pass a provider and one or more uniquely identified requests to `startBatch`. Each text request must specify `type: 'text'`, a model ID, and either a text `prompt` or a `messages` array. Requests can use different model IDs when supported by the provider. Each request can also use the usual text-generation settings such as `instructions`, `maxOutputTokens`, `temperature`, `topP`, `topK`, `presencePenalty`, `frequencyPenalty`, `stopSequences`, `seed`, and `reasoning`. Tool definitions and tool settings are provided on individual requests. A tool with the same name must have the same definition in every request that uses it. ```ts import { anthropic } from '@ai-sdk/anthropic'; import { experimental_startBatch as startBatch } from 'ai'; const provider = anthropic; const batch = await startBatch({ provider, requests: [ { id: 'capital-france', type: 'text', model: 'claude-haiku-5-5', prompt: 'What is the capital of France?', }, { id: 'capital-germany', type: 'text', model: 'claude-haiku-5-5', prompt: 'What is the capital of Germany?', }, ], }); console.log(batch.id, batch.status); ``` Request IDs must be non-empty and unique within the batch. They are the link between an input request and its result. Results are not guaranteed to arrive in input order, so use the ID rather than an array position when associating a result with application data. You can pass provider-specific settings at the batch level with `providerOptions`, or on an individual request with that request's `providerOptions`. Settings supported by the provider can differ between the two levels. The `warnings` property on the start result contains warnings about unsupported settings. The batch reference returned by `startBatch` is serializable. Persist it before the process exits if the batch will be completed by another process or at a later time. When retrieving a batch, pass the same provider. The AI SDK uses the reference to ensure that a batch is not read through an incompatible provider. ### Image requests Image requests use the same prompt and generation settings as `generateImage`: `prompt`, `n`, `size`, `aspectRatio`, `seed`, and `providerOptions`. A prompt can also contain input images and a mask for providers that support image editing. Provider batch endpoints may support only a subset of these options; unsupported options produce warnings or errors. ```ts import { google } from '@ai-sdk/google'; import { experimental_startBatch as startBatch } from 'ai'; const batch = await startBatch({ provider: google, requests: [ { id: 'red-panda', type: 'image', model: 'gemini-3.1-flash-image-preview', prompt: 'A red panda reading beside a cabin window', aspectRatio: '16:9', }, ], }); ``` Successful image items have an `images` array of `GeneratedFile` values, plus image warnings, response metadata, usage, and provider metadata when the provider supplies them. Successful text items have `text` and `content` properties. Use the `type` property to narrow mixed results: ```ts for await (const item of getBatchResults({ provider: google, batch })) { if (item.type === 'image' && item.status === 'succeeded') { console.log(item.images); } } ``` ## Batch tools Batch text requests support both client-defined tools and provider-defined tools. Tool definitions are sent with each request that includes them, so the model can call them independently for each request. Client-defined tools are definition-only in a batch. Their `execute` functions are never invoked, and the AI SDK does not submit tool results or start a follow-up generation after retrieving a tool call. Pass the same tool set to `getBatchResults` to validate and normalize returned tool calls: ```ts import { anthropic } from '@ai-sdk/anthropic'; import { experimental_getBatchResults as getBatchResults, experimental_startBatch as startBatch, tool, } from 'ai'; import { z } from 'zod'; const tools = { get_weather: tool({ description: 'Get the current weather for a location.', inputSchema: z.object({ location: z.string() }), execute: async ({ location }) => { // This function is not called by batch processing. return { location, temperature: 21, condition: 'sunny' }; }, }), }; const batch = await startBatch({ provider: anthropic, requests: [ { id: 'weather-san-francisco', type: 'text', model: 'claude-haiku-4-5', prompt: 'Call get_weather for San Francisco, California.', tools, toolChoice: { type: 'tool', toolName: 'get_weather' }, }, ], }); for await (const item of getBatchResults({ provider: anthropic, batch, tools, })) { if (item.status === 'succeeded') { console.log(item.id, item.content); } } ``` Provider-defined tools, such as web search or code execution, can execute on the provider when supported by that provider's batch API. Their tool calls and results are returned as normalized `content` parts. See the provider documentation for the tools and models available in batches. ## Checking batch status The start result includes the initial status. Use `getBatchStatus` to retrieve the latest status while the provider is processing the batch: ```ts import { setTimeout } from 'node:timers/promises'; let status = batch.status; let error = batch.error; while (status === 'pending') { await setTimeout(10_000); const latestStatus = await getBatchStatus({ provider: anthropic, batch, }); status = latestStatus.status; error = latestStatus.error; } if (status === 'failed') { throw new Error(error?.message ?? 'The batch failed.'); } ``` The normalized batch status is one of: - `pending` — the provider is still processing the batch. - `completed` — the batch reached a terminal state and results can be retrieved. - `failed` — the batch could not be completed. The `error` property may contain additional details. Status responses can also include `requestCounts`, `createdAt`, `expiresAt`, `rawStatus`, and provider metadata. `requestCounts` reports the total, pending, completed, and failed requests known by the provider. For webhook-capable providers, pass `webhookUrl` to `startBatch` to receive a notification when the batch reaches a terminal state. Webhook support and payloads are provider-specific. The provider pages linked above describe their webhook behavior; providers that do not support webhooks return an unsupported warning and continue without one. ## Cancelling a batch Use `cancelBatch` to ask the provider to stop processing a batch: ```ts const result = await cancelBatch({ provider, batch, }); console.log(result.providerMetadata); ``` A successful call means that the provider accepted the cancellation request. It does not guarantee that cancellation has finished or that every pending request will be cancelled. Use `getBatchStatus` to retrieve the latest status after requesting cancellation. The result can include provider metadata with additional information from the cancellation response. ## Listing batches Use `listBatches` to retrieve batches from the provider. Results are paginated. Pass the returned `nextCursor` as `cursor` to retrieve the next page: ```ts let cursor: string | undefined; do { const page = await listBatches({ provider, limit: 20, cursor, }); for (const batch of page.batches) { console.log(batch.id, batch.status); } cursor = page.nextCursor; } while (cursor != null); ``` Each item is a serializable batch reference with its latest normalized status, so it can be passed directly to `getBatchStatus`, `getBatchResults`, or `cancelBatch`. Cursors are opaque and provider-specific; applications should store or pass them unchanged rather than inspect their contents. ## Retrieving results After the batch is complete, `getBatchResults` returns an async iterable. Each item contains the ID and terminal status for one input request: ```ts for await (const item of getBatchResults({ provider: anthropic, batch })) { if (item.status === 'succeeded') { console.log(item.id, item.text); } else { console.error(item.id, item.status, item.error); } } ``` Successful items include: - `text` — the concatenated text content. This can be an empty string when the result contains no text parts. - `content` — ordered, normalized content parts, including text, reasoning, files, sources, tool calls, tool results, and provider content where supported. - `finishReason` and optional `rawFinishReason`. - `usage` and optional `response` metadata. - Optional `providerMetadata`. Failed, cancelled, and expired items include their `id` and status. Failed items include an `error`; cancelled and expired items may also include one. One failed request does not necessarily mean that every request in the batch failed, so handle each item independently. Batch retrieval does not run an AI SDK tool loop or invoke client-defined `execute` functions. Provider-defined tools may execute on the provider as part of batch processing. Treat result content and provider metadata as untrusted model output, and avoid logging it indiscriminately because it can contain sensitive data. ## Request controls All batch lifecycle functions accept `providerOptions`, `headers`, `timeout`, and `abortSignal` for the current operation. `getBatchStatus`, `getBatchResults`, and `listBatches` also accept `maxRetries`: - `maxRetries` controls retries for status, result retrieval, and listing. It does not retry batch creation or cancellation. It defaults to 2; set it to 0 to disable retries. - `abortSignal` cancels the current API request. - `timeout` limits the current HTTP operation. These controls affect communication with the provider. They do not change the provider's processing deadline or cancel a batch that has already been submitted. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Code Mode description: Let models orchestrate AI SDK tools with sandboxed JavaScript and TypeScript. url: "https://ai-sdk.dev/docs/ai-sdk-core/code-mode" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Code mode lets a model write JavaScript or TypeScript that calls your AI SDK tools. The generated code runs in an isolated QuickJS sandbox and returns a JSON-serializable result. Instead of calling tools one at a time, a model can use code mode to: - call independent tools concurrently - transform and combine tool results - filter large tool responses before returning them to the model - use JavaScript control flow for multi-step operations Code mode is provided by the `@ai-sdk/code-mode` package. Code mode is experimental and its APIs may change in future releases. It requires Node.js 22 or newer and is not available in browser or edge runtimes. ## Installation ```bash pnpm add ai @ai-sdk/code-mode zod ``` ## Using Code Mode with `generateText` Define your tools in one tool set and use `experimental_toolCallers` to select which tools code mode can call: ```ts import { DIRECT_TOOL_CALL, experimental_codeModeTool as codeModeTool, } from '@ai-sdk/code-mode'; import { generateText, isStepCount, tool } from 'ai'; import { z } from 'zod'; const getInventory = tool({ description: 'Get available inventory for a product.', inputSchema: z.object({ productId: z.string(), }), outputSchema: z.object({ productId: z.string(), availableUnits: z.number(), }), execute: async ({ productId }) => ({ productId, availableUnits: 42, }), }); const getDemand = tool({ description: 'Get requested units for a product.', inputSchema: z.object({ productId: z.string(), }), outputSchema: z.object({ productId: z.string(), requestedUnits: z.number(), }), execute: async ({ productId }) => ({ productId, requestedUnits: 31, }), }); const tools = { code_mode: codeModeTool({ executionPolicy: { timeoutMs: 30_000, }, }), getInventory, getDemand, } as const; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools, experimental_toolCallers: { getInventory: ['code_mode'], getDemand: ['code_mode'], }, stopWhen: isStepCount(10), prompt: 'Compare inventory and demand for product sku_123.', }); ``` The keys in `experimental_toolCallers` are the tools being governed. The values identify their allowed callers. In this example, `getInventory` and `getDemand` are available through `code_mode`, but they are not exposed to the model as directly callable tools. Include `DIRECT_TOOL_CALL` when a tool should also be callable directly: ```ts experimental_toolCallers: { getInventory: ['code_mode', DIRECT_TOOL_CALL], }; ``` Tools without an `experimental_toolCallers` entry keep their existing direct tool-calling behavior. The code mode tool description includes TypeScript signatures generated from the input and output schemas of its allowed tools. Descriptions, `inputExamples`, and precise schemas help the model write correct code. ### Discovering Tools Through Conversation By default, changing the tools routed through code mode also changes the provider-visible `code_mode` tool description. Set `toolDiscovery` to `'conversation'` to keep that tool definition stable and provide the current host-tool catalog in a user message instead: ```ts const codeMode = codeModeTool({ toolDiscovery: 'conversation', }); const tools = { code_mode: codeMode, getInventory, getDemand, } as const; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools, experimental_toolCallers: { getInventory: ['code_mode'], getDemand: ['code_mode'], }, prompt: 'Compare inventory and demand for product sku_123.', }); ``` The AI SDK adds a complete code mode capability catalog to the conversation before calling the model. The catalog contains TypeScript signatures and call examples for the tools routed through code mode. When the effective tools or their definitions change in a later step or generation call, the SDK adds a new catalog that replaces earlier catalogs. This mode can improve prompt-cache reuse because the provider-visible tool definition stays unchanged. Actual cache behavior depends on the model provider. For the example above, the model can generate a program like: ```ts const [inventory, demand] = await Promise.all([ tools.getInventory({ productId: 'sku_123' }), tools.getDemand({ productId: 'sku_123' }), ]); return { sufficient: inventory.availableUnits >= demand.requestedUnits, remaining: inventory.availableUnits - demand.requestedUnits, }; ``` Each provided tool is available through the global `tools` object. Tool names that are not valid JavaScript identifiers use bracket notation: ```ts const user = await tools['lookup-user']({ userId: 'user_123' }); return { id: user.id, plan: user.plan }; ``` ### Searching Deferred Tools Use `toolSearch()` with `deferLoading: true` to expose tools only when the model needs them. With `toolDiscovery: 'conversation'`, discovered definitions arrive in user messages, preserving the tool-definition cache by keeping the provider-visible code mode tool unchanged. Actual prompt-cache reuse depends on the provider. See [Tool Search](/docs/ai-sdk-core/tool-search) for direct-calling and code mode examples. ## Writing Code Mode Programs Generated programs support: - JavaScript and type-stripped TypeScript - top-level `await` and `return` - standard JavaScript control flow and data transformations - `Promise.all` for concurrent tool calls - `JSON.parse` and `JSON.stringify` - `console.log`, `console.info`, `console.debug`, and `console.error` Every tool call is asynchronous and must be awaited or otherwise observed. Returning while tool calls are still detached fails the invocation and aborts the outstanding work. Programs and tool inputs and outputs cross the sandbox boundary as JSON. Return only JSON-serializable values. TypeScript support is limited to removing type syntax; code mode does not perform type checking or provide a full TypeScript compiler. ## Direct Execution Use `experimental_runCodeMode` when you want to execute a program directly instead of exposing code mode to a model: ```ts import { experimental_runCodeMode as runCodeMode } from '@ai-sdk/code-mode'; const result = await runCodeMode({ js: ` const inventory = await tools.getInventory({ productId: 'sku_123', }); return { productId: inventory.productId, available: inventory.availableUnits > 0, }; `, tools: { getInventory }, }); ``` `runCodeMode` returns the value returned by the program. It uses the same sandbox and execution limits as the AI SDK tool. ## Tool Approval Code mode does not currently integrate with AI SDK tool approval flows. Tool calls made by generated code are nested inside the code mode invocation, so they cannot pause the generation and surface a tool approval request to your application. Do not expose tools that rely on user approval to code mode. Keep those tools directly callable by the model instead. If a nested tool requires approval, the call is rejected rather than executed. ## Execution Limits Every invocation has limits for runtime, memory, source size, results, tool payloads, console output, and tool calls. Override them with `executionPolicy`: ```ts const codeMode = codeModeTool({ executionPolicy: { timeoutMs: 30_000, cpuTimeoutMs: 10_000, memoryLimitBytes: 64 * 1024 * 1024, maxResultBytes: 1024 * 1024, maxBridgeRequests: 100, maxInFlightBridgeRequests: 10, }, }); ``` The available limits are: - `timeoutMs`: total execution time, including time spent waiting on tools - `cpuTimeoutMs`: CPU time spent running sandboxed code, excluding tool waits - `memoryLimitBytes`: QuickJS memory - `maxStackSizeBytes`: QuickJS stack - `maxSourceBytes`: generated source code - `maxResultBytes`: returned result - `maxConsoleOutputBytes`: combined console output - `maxToolInputBytes`: input for each tool call - `maxToolOutputBytes`: output from each tool call - `maxBridgeRequests`: total tool calls - `maxInFlightBridgeRequests`: concurrent tool calls Use `experimental_setMaxWorkers` to set a process-wide cap on concurrent code mode workers: ```ts import { experimental_setMaxWorkers as setMaxWorkers } from '@ai-sdk/code-mode'; setMaxWorkers(4); ``` Without an explicit cap, code mode chooses one based on available memory, up to 32 workers. ## Isolation and Tool Access Each invocation receives a fresh QuickJS context. Sandboxed code cannot access: - Node.js globals such as `process`, `require`, or `module` - the host file system or module loader - `fetch`, WebCrypto, or performance APIs - `eval` or dynamic `Function` construction Network or system access must be implemented in a tool and explicitly provided to code mode. Treat the sandbox as defense in depth. Generated code and tool arguments are untrusted. Tools execute in your host application, outside the QuickJS sandbox, and every capability exposed by a provided tool is available to the generated program. Enforce authorization and validate inputs inside each tool. Tool input schemas are validated before their `execute` functions run. Abort signals and AI SDK tool execution context are forwarded to nested tool calls. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Decisions description: Decide answers to Choice, Score, and Boolean questions against shared state. url: "https://ai-sdk.dev/docs/ai-sdk-core/decisions" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `experimental_decide` decides answers to named questions against one shared state using a decision model. State can be a string, JSON object, or an array of ordered text, file, and JSON parts. This API and the decision model specification are experimental and may change in patch releases. Pass a decision model instance, resolve a model through a provider registry, or use a string ID. Strings resolve through Vercel AI Gateway unless you configure a decision-capable default provider. ```ts import { experimental_decide, type Experimental_DecisionModel } from 'ai'; async function triage(model: Experimental_DecisionModel, message: string) { return experimental_decide({ model, state: { message }, questions: { department: { type: 'choice', instructions: 'Which team should handle this?', criteria: { billing: 'Payments and refunds', support: 'Other requests', }, }, severity: { type: 'score', instructions: 'How severe is the issue?', criteria: ['Cosmetic', 'Workaround exists', 'Blocking; no workaround'], }, requestsRefund: { type: 'boolean', instructions: 'Is the customer requesting money back?', }, }, }); } ``` ## Multimodal state Combine text, structured data, and images in one state: ```ts import { openai } from '@ai-sdk/openai'; import { experimental_decide } from 'ai'; const result = await experimental_decide({ model: openai.decisionModel('gpt-6-luna'), state: [ { type: 'text', text: 'Inspect this package for damage.' }, { type: 'json', value: { product: 'glass vase', packaging: 'cardboard' } }, { type: 'file', mediaType: 'image/png', data: new URL('https://example.com/package.png'), }, ], questions: { damaged: { type: 'boolean', instructions: 'Is the product visibly damaged?', }, }, }); ``` `Experimental_DecisionState` and `Experimental_DecisionStatePart` are exported from `ai`. A part has one of these shapes: - `TextPart`: `{ type: 'text', text: string }`. - `FilePart`: `{ type: 'file', mediaType: string, data: ... }`, using the SDK's file conventions. For images, use bytes, a base64 string, or a URL. Tagged file data is also supported. URLs are downloaded before the model call. - JSON: `{ type: 'json', value: JSONValue }`. Language-model decision adapters accept image files only. The underlying model must support the file data form. Gateway forwards image files to the routed decision model, which must support image input. TypeSafe AI accepts text and JSON state only. Arrays passed directly as `state` contain parts. To send a JSON array, put it inside an object (`state: { records }`) or a JSON part (`state: [{ type: 'json', value: records }]`). An empty `state: []` contains zero parts. Question instructions and criteria still accept JSON arrays directly. ## Provider models Use a provider's `decisionModel` factory: | Provider | Example model | | ----------- | ------------------------------------------------------ | | TypeSafe AI | `typeSafeAi.decisionModel('jev-latest')` | | Perplexity | `perplexity.decisionModel('pplx-decider-latest')` | | OpenAI | `openai.decisionModel('gpt-6-luna')` | | Anthropic | `anthropic.decisionModel('claude-haiku-4-5-20251001')` | | Google | `google.decisionModel('gemini-3.5-flash-lite')` | TypeSafe AI's [Jev](https://vercel.com/i/what-is-jev), OpenAI's [Decisions API](https://developers.openai.com/api/docs/guides/decisions), and [Perplexity's decision models](/providers/ai-sdk-providers/perplexity#decision-models) supply native Choice, Score, and Boolean decisions. Answers are keyed by question ID, and Choice and Score answers include native probability distributions. Anthropic and Google adapt structured language-model output for all three types, evaluating all questions in one prompt. Their Boolean answers contain prompted estimates of P(true), and Choice and Score answers omit probability distributions. Select a model that supports the provider's structured-output API and judge quality against your own labeled examples before choosing a model for a task. The Anthropic and Google language-model adapters request `reasoning: 'none'` by default. Each provider maps this setting to its model's reasoning controls. Pass supported reasoning settings through `providerOptions` to override this default. OpenAI decision models use the Decisions endpoint and do not accept Responses API reasoning options. ## Model aliases and registries Use `customProvider` to give models application-specific names, then register providers with `createProviderRegistry`: ```ts import { typeSafeAi } from '@ai-sdk/typesafe-ai'; import { openai } from '@ai-sdk/openai'; import { customProvider, createProviderRegistry, experimental_decide, } from 'ai'; const registry = createProviderRegistry({ triage: customProvider({ decisionModels: { native: typeSafeAi.decisionModel('jev-latest'), compact: openai.decisionModel('gpt-6-luna'), }, fallbackProvider: typeSafeAi, }), openai, }); const result = await experimental_decide({ model: registry.decisionModel('triage:native'), state: 'I was charged twice.', questions: { department: { type: 'choice', instructions: 'Which team should handle this?', criteria: { billing: 'Charges and refunds', support: 'Other requests' }, }, }, }); result.answers.department.choice; // 'billing' | 'support' ``` Registry IDs use `providerId:modelId`; the `separator` option changes the separator. Only the first separator is used, so model IDs can contain it. Custom aliases take precedence over a fallback provider. A fallback only resolves unknown model IDs; it does not retry failed decisions or substitute a model when a question type is unsupported. Registered providers keep their own credentials and settings. Registry language/image middleware does not wrap decision models. ### Default-provider strings String IDs use Vercel AI Gateway by default. Configure Gateway authentication with `AI_GATEWAY_API_KEY` or Vercel OIDC, then pass a Gateway model ID: ```ts import { experimental_decide } from 'ai'; const result = await experimental_decide({ model: 'typesafe-ai/jev', state: 'I was charged twice. Please refund the extra charge.', questions: { refund: { type: 'boolean', instructions: 'Is the customer asking for a refund?', }, }, }); ``` To resolve strings through your own provider or aliases, configure the default provider once at application startup: ```ts // Using the registry above, expose an alias through a custom provider: globalThis.AI_SDK_DEFAULT_PROVIDER = customProvider({ decisionModels: { native: registry.decisionModel('triage:native') }, }); const result = await experimental_decide({ model: 'native', state: 'I was charged twice.', questions: { refund: { type: 'boolean', instructions: 'Is the customer asking for a refund?', }, }, }); ``` A direct provider such as `typeSafeAi` can also be the default; then use its unprefixed model ID, such as `'jev-latest'`. String values in `decisionModels` also resolve through this global default. Prefer model instances in aliases to avoid resolution cycles. Global configuration affects other AI SDK functions too; avoid changing it per request in a shared process. An explicitly configured default provider must expose a `decisionModel` method; Gateway is used only when no default provider is configured. Missing registry providers throw `NoSuchProviderError`; missing models or decision capabilities throw `NoSuchModelError` with `modelType: 'decisionModel'`. Unsupported model versions throw `UnsupportedModelVersionError`, including models returned by registries. The stable `ProviderV4` and `ProviderRegistryProvider` interfaces are unchanged; keep the inferred registry type, or use `Experimental_DecisionProviderRegistry`, to retain its experimental `decisionModel` method. ## Question types | Type | Criteria | Answer | | --------- | ---------------------------------------------- | ------------------------------------------------------------------------ | | `choice` | A nonempty map of options to descriptions | `choice`, inferred as a union of option keys; optional `probabilities` | | `score` | A nonempty array of ordered level descriptions | Fractional `score` in `[0, levels.length - 1]`; optional `probabilities` | | `box` | None | `box` with normalized `top`, `bottom`, `left`, and `right` image edges | | `boolean` | Optional `true` and `false` descriptions | Required `probability`, the model-estimated probability of true | `Experimental_DecisionQuestion` is exported from `ai`. Instructions and descriptions can be strings, JSON objects, or JSON arrays. Descriptions can also be `null` when the provider supports it. For Choice, Score, and Boolean, `instructions` may be omitted or `null`. A Boolean question without instructions must describe at least one of its `true` or `false` criteria. Box questions always require instructions. Provider APIs can impose additional requirements; see the provider's documentation. Score rubrics require at least one level in Core. Perplexity accepts a single level, which returns score `0` with probability `1`; other providers may require more levels. See [Perplexity's examples](/providers/ai-sdk-providers/perplexity#single-level-score-rubrics). Functions, class instances, cycles, undefined values inside JSON content, and nonfinite numbers are not JSON-compatible. Answers retain question IDs and have the same `type` as their question. When a Choice distribution is supplied, it includes every option and the selected choice has maximal probability. Score distributions use string keys for zero-based level indices, and the score equals the probability-weighted mean. Without a distribution, a score is the model's estimated position on the rubric. Distributions must sum to one, and weighted scores must agree with their distributions. The default absolute tolerance is `0.000001`. Providers that round their output can declare `rounding.probabilityDecimals` and `rounding.scoreDecimals` (integers from 0 to 15). Validation then also allows half a unit in the last decimal place per rounded probability or score, accumulated over the sum or weighted mean. For example, probabilities rounded to two decimal places can sum to `0.99` even when their unrounded values sum to one. The result includes this `rounding` information. Invalid output is rejected; native values are preserved, never silently normalized. ## Probabilities and confidence Choice and Score distributions are optional. Boolean probability is required: `0.98` means a strong yes and `0.02` means a strong no. It is not confidence in either outcome. The SDK does not promise calibration across providers. Structured language-model adapters prompt the model to estimate P(true); native decision providers return their API probabilities. Provider-specific confidence statistics belong in `providerMetadata`. TypeSafe exposes its separate Choice/Score/Box confidence statistic at `result.providerMetadata?.typesafe?.confidence`, keyed by question ID. It is not the selected option's probability or a portable confidence measure. Perplexity exposes Choice/Score confidence at `result.providerMetadata?.perplexity?.confidence` and Score legends at `result.providerMetadata?.perplexity?.legend`, also keyed by question ID. Check for optional distributions before using them. For example, an application can route only when a provider supplies a sufficiently high selected-option probability: ```ts const answer = result.answers.department; const selectedProbability = answer.probabilities?.[answer.choice]; if (selectedProbability != null && selectedProbability >= 0.9) { // Route automatically; otherwise use the application's review path. } ``` Choose Boolean thresholds in application code, using labeled data from the task rather than assuming that the same threshold behaves identically across providers: ```ts if (result.answers.requestsRefund.probability >= 0.8) { // Route to the refunds queue. } ``` ## Errors and cancellation The model's `supportedQuestionTypes` are checked before calling the provider. Any unsupported question fails the entire call with `Experimental_DecisionUnsupportedQuestionTypeError`. Successful calls return an answer for every question; there is no partial success, and Core does not substitute models automatically. Invalid inputs throw `InvalidArgumentError`. Missing answers, mismatched answer types, invalid options, scores, or probabilities throw `InvalidResponseDataError`. If the model refuses any question, the call throws `Experimental_DecisionRefusalError` with the refused question IDs, and is not retried. Transient provider failures use the normal retry policy (`maxRetries: 2` by default). Use `abortSignal` to cancel the decisions request, `headers` for request headers, and `providerOptions` for provider-specific settings. The result includes `usage`, `warnings`, `providerMetadata`, and `response`. Unknown token counts stay `undefined`; `totalTokens` is available only when both input and output counts are known. For tests, use `Experimental_DecisionMockModelV4` from `ai/test`. ## Scope and examples `experimental_decide` currently returns one complete result for one shared state. It does not stream answers, perform multilabel classification, or batch unrelated states. Run separate calls for separate states. Provider support and judgment quality depend on the chosen model. Core does not choose another model, but a provider can implement routing through its provider options. For example, AI Gateway supports conditional decision fallbacks under `providerOptions.gateway.models`. Runnable examples are in [`examples/ai-functions/src/decide`](https://github.com/vercel/ai/tree/main/examples/ai-functions/src/decide), including basic examples for TypeSafe, OpenAI, Anthropic, and Google, model registries, custom aliases, default-provider strings, and probability-based routing. ## Related resources Explore use cases, implementation guides, and a deployable template for using Jev from TypeSafe AI with the AI SDK: - [Jev use cases](https://vercel.com/i/jev-use-cases): Explore ideas for applying decisions in your application. - [Classify, route, and score with Jev and AI SDK](https://vercel.com/kb/guide/typesafe-jev-and-ai-sdk): Learn how to ask typed questions, route answers using probabilities, and test routing logic with a mock decision model. - [Jev and AI SDK form router guide](https://vercel.com/kb/guide/jev-ai-sdk-form-router): Explore form routing with Jev and the AI SDK. - [Jev and AI SDK form router template](https://vercel.com/templates/next.js/jev-and-ai-sdk): Deploy a Next.js application that routes form submissions with Jev and uses a language model fallback for uncertain or failed decisions. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: DevTools description: Debug and inspect AI SDK applications with DevTools url: "https://ai-sdk.dev/docs/ai-sdk-core/devtools" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK DevTools is intended for local development only. Do not use in production environments. AI SDK DevTools gives you full visibility over your AI SDK calls with [`generateText`](/docs/reference/ai-sdk-core/generate-text), [`streamText`](/docs/reference/ai-sdk-core/stream-text), and [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent). It helps you debug and inspect LLM requests, responses, tool calls, and multi-step interactions through a web-based UI. DevTools is composed of two parts: 1. **Telemetry Integration**: Captures runs and steps from your AI SDK calls via the [telemetry](/docs/ai-sdk-core/telemetry) system 2. **Viewer**: A web UI to inspect the captured data ## Installation Install the DevTools package: ```bash pnpm add @ai-sdk/devtools ``` ## Requirements - AI SDK v7 (`ai@latest`) - Node.js compatible runtime ## Using DevTools ### Register the integration Register `DevToolsTelemetry` globally so it captures all AI SDK calls: ```ts import { registerTelemetry } from 'ai'; import { DevToolsTelemetry } from '@ai-sdk/devtools'; registerTelemetry(DevToolsTelemetry()); ``` Telemetry is enabled automatically once an integration is registered — no per-call configuration is needed: ```ts import { generateText } from 'ai'; const result = await generateText({ model: openai('gpt-6-astra'), prompt: 'What cities are in the United States?', }); ``` You can also pass the integration to individual calls instead of registering it globally: ```ts {7-9} import { streamText } from 'ai'; import { DevToolsTelemetry } from '@ai-sdk/devtools'; const result = streamText({ model: openai('gpt-6-astra'), prompt: 'Hello!', telemetry: { integrations: [DevToolsTelemetry()], }, }); ``` ### Launch the viewer Start the DevTools viewer: ```bash npx @ai-sdk/devtools@latest ``` Open [http://localhost:4983](http://localhost:4983) to view your AI SDK interactions. ### Choose a viewer theme The viewer uses the dark theme by default. Use the theme button in the viewer header to switch between dark and light themes. Your selection is stored in your browser and restored the next time you open the viewer at the same origin. ### Monorepo usage If you are using a monorepo setup (e.g. Turborepo, Nx), start DevTools from the same workspace where your AI SDK code runs. For example, if your API is in `apps/api`, run: ```bash cd apps/api npx @ai-sdk/devtools@latest ``` The explicit `@latest` tag ensures that `npx` installs an executable copy instead of selecting a transitive dependency whose binary is not linked into the workspace. ## Captured data DevTools captures the following information from your AI SDK calls: - **Input parameters and prompts**: View the complete input sent to your LLM - **Output content and tool calls**: Inspect generated text, tool invocations, and tool results - **Media previews**: View images, audio, and video included in prompts, tool inputs, and tool outputs - **Token usage and timing**: Monitor resource consumption and performance - **Raw provider data**: Access provider request and response payloads when body retention is enabled ### Media previews DevTools recognizes current `file` content parts as well as the deprecated `image-*`, `file-*`, and `media` tool-result aliases. Inline image, audio, and video data is previewed directly, while the captured JSON shape and metadata remain available for inspection. The viewer displays at most 8 previews per value, traverses at most 12 nested levels, and embeds inline previews up to 5 MiB. Longer JSON strings are truncated in the viewer to avoid duplicating large base64 payloads. Binary values in recognized media-bearing fields are persisted as base64 so they remain previewable; unrelated binary values retain their normal JSON representation. For example, a tool can return media through `toModelOutput`: ```ts import { tool } from 'ai'; import { z } from 'zod'; const captureScreenshot = tool({ inputSchema: z.object({}), execute: async () => ({ base64: await captureScreenshotAsBase64(), }), toModelOutput: ({ output }) => ({ type: 'content', value: [ { type: 'file', filename: 'screenshot.png', mediaType: 'image/png', data: { type: 'data', data: output.base64 }, }, ], }), }); ``` Remote `http` and `https` media is not loaded automatically. Select **Load preview** in the viewer to fetch it with anonymous CORS and no referrer. Cross-origin browser credentials are omitted, but same-origin browser credentials may still be included by the browser. URLs containing embedded usernames or passwords are rejected. Unsupported media, provider references, malformed values, unsafe URL schemes, and inline values over the preview limit retain their JSON and metadata fallback without an embedded preview. Telemetry is enabled automatically, but AI SDK 7 excludes raw request and response bodies from step results by default. To make them available to DevTools for `generateText`, enable body retention on the call: ```ts {4-7} const result = await generateText({ model: openai('gpt-6-astra'), prompt: 'Hello!', include: { requestBody: true, responseBody: true, }, }); ``` `ToolLoopAgent` accepts the same `include` setting in its constructor. For `streamText` and `ToolLoopAgent.stream()`, only the request body can be retained; use `include: { requestBody: true }`. ### Runs and steps DevTools organizes captured data into runs and steps: - **Run**: A complete multi-step AI interaction, grouped by the initial prompt - **Step**: A single LLM call within a run (e.g., one `generateText` or `streamText` call) Multi-step interactions, such as those created by tool calling or agent loops, are grouped together as a single run with multiple steps. Nested sub-agent calls are linked to their parent run, making it easy to trace the full execution tree. ## How it works The `DevToolsTelemetry` integration hooks into the AI SDK [telemetry](/docs/ai-sdk-core/telemetry) lifecycle to capture all `generateText`, `streamText`, `generateObject`, and `streamObject` calls. Captured data is stored locally in a JSON file (`.devtools/generations.json`) and served through a web UI built with Hono and React. The integration automatically adds `.devtools` to your `.gitignore` file. Verify that `.devtools` is in your `.gitignore` to ensure you don't commit sensitive AI interaction data to your repository. ## Security considerations DevTools stores all AI interactions locally in plain text files, including: - User prompts and messages - LLM responses - Tool call arguments and results - API request and response data when body retention is enabled **Only use DevTools in local development environments.** Do not enable DevTools in production or when handling sensitive data. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Embeddings description: Learn how to embed values with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/embeddings" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Embeddings are a way to represent words, phrases, or images as vectors in a high-dimensional space. In this space, similar words are close to each other, and the distance between words can be used to measure their similarity. ## Embedding a Single Value The AI SDK provides the [`embed`](/docs/reference/ai-sdk-core/embed) function to embed single values, which is useful for tasks such as finding similar words or phrases or clustering text. You can use it with embeddings models, e.g. `openai.embeddingModel('text-embedding-3-large')` or `mistral.embeddingModel('mistral-embed')`. ```tsx import { embed } from 'ai'; // 'embedding' is a single embedding object (number[]) const { embedding } = await embed({ model: 'openai/text-embedding-3-small', value: 'sunny day at the beach', }); ``` ## Embedding Many Values When loading data, e.g. when preparing a data store for retrieval-augmented generation (RAG), it is often useful to embed many values at once (batch embedding). The AI SDK provides the [`embedMany`](/docs/reference/ai-sdk-core/embed-many) function for this purpose. Similar to `embed`, you can use it with embeddings models, e.g. `openai.embeddingModel('text-embedding-3-large')` or `mistral.embeddingModel('mistral-embed')`. ```tsx import { embedMany } from 'ai'; // 'embeddings' is an array of embedding objects (number[][]). // It is sorted in the same order as the input values. const { embeddings } = await embedMany({ model: 'openai/text-embedding-3-small', values: [ 'sunny day at the beach', 'rainy afternoon in the city', 'snowy night in the mountains', ], }); ``` ## Embedding Similarity After embedding values, you can calculate the similarity between them using the [`cosineSimilarity`](/docs/reference/ai-sdk-core/cosine-similarity) function. This is useful to e.g. find similar words or phrases in a dataset. You can also rank and filter related items based on their similarity. ```ts {1,9} import { cosineSimilarity, embedMany } from 'ai'; const { embeddings } = await embedMany({ model: 'openai/text-embedding-3-small', values: ['sunny day at the beach', 'rainy afternoon in the city'], }); console.log( `cosine similarity: ${cosineSimilarity(embeddings[0], embeddings[1])}`, ); ``` ## Token Usage Many providers charge based on the number of tokens used to generate embeddings. Both `embed` and `embedMany` provide token usage information in the `usage` property of the result object: ```ts {3,8} import { embed } from 'ai'; const { embedding, usage } = await embed({ model: 'openai/text-embedding-3-small', value: 'sunny day at the beach', }); console.log(usage); // { tokens: 10 } ``` ## Settings ### Output Dimensions Both `embed` and `embedMany` accept an optional `dimensions` parameter to request an output vector size. It must be a positive integer. When omitted, the model's existing behavior is unchanged. For `embedMany`, the same value is forwarded to every batch. ```ts import { openai } from '@ai-sdk/openai'; import { embed } from 'ai'; const { embedding } = await embed({ model: openai.embedding('text-embedding-3-small'), value: 'sunny day at the beach', dimensions: 512, }); ``` Providers map `dimensions` to their native output dimension setting. An explicit dimension setting in `providerOptions` takes precedence over `dimensions`. Other provider options can be combined with the top-level setting. Support and allowed dimension values depend on the model. The SDK forwards the requested size to the provider API without truncating or resizing the returned embeddings. AI Gateway forwards `dimensions` and `providerOptions` for resolution by the selected provider. ### Provider Options Embedding model settings can be configured using `providerOptions` for provider-specific parameters: ```ts {4-8} import { embed } from 'ai'; const { embedding } = await embed({ model: 'openai/text-embedding-3-small', value: 'sunny day at the beach', providerOptions: { openai: { dimensions: 512, // Reduce embedding dimensions }, }, }); ``` Google's `gemini-embedding-2` model (also available as `gemini-embedding-2-preview`) supports multimodal embedding content through `providerOptions.google.content`. Each entry corresponds to the value at the same index and can contain `{ text: string }`, `{ inlineData: { mimeType: string; data: string } }`, or `{ fileData: { fileUri: string; mimeType: string } }` parts. `fileUri` can reference remote content such as HTTP URLs or Google Cloud Storage URIs (`gs://...`). ### Parallel Requests The `embedMany` function now supports parallel processing with configurable `maxParallelCalls` to optimize performance: ```ts {4} import { embedMany } from 'ai'; const { embeddings, usage } = await embedMany({ maxParallelCalls: 2, // Limit parallel requests model: 'openai/text-embedding-3-small', values: [ 'sunny day at the beach', 'rainy afternoon in the city', 'snowy night in the mountains', ], }); ``` ### Retries Both `embed` and `embedMany` accept an optional `maxRetries` parameter of type `number` that you can use to set the maximum number of retries for the embedding process. It defaults to `2` retries (3 attempts in total). You can set it to `0` to disable retries. ```ts {6} import { embed } from 'ai'; const { embedding } = await embed({ model: 'openai/text-embedding-3-small', value: 'sunny day at the beach', maxRetries: 0, // Disable retries }); ``` ### Abort Signals and Timeouts Both `embed` and `embedMany` accept an optional `abortSignal` parameter of type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that you can use to abort the embedding process or set a timeout. ```ts {6} import { embed } from 'ai'; const { embedding } = await embed({ model: 'openai/text-embedding-3-small', value: 'sunny day at the beach', abortSignal: AbortSignal.timeout(1000), // Abort after 1 second }); ``` ### Custom Headers Both `embed` and `embedMany` accept an optional `headers` parameter of type `Record` that you can use to add custom headers to the embedding request. ```ts {6} import { embed } from 'ai'; const { embedding } = await embed({ model: 'openai/text-embedding-3-small', value: 'sunny day at the beach', headers: { 'X-Custom-Header': 'custom-value' }, }); ``` ## Response Information Both `embed` and `embedMany` return response information that includes the raw provider response: ```ts {3,8} import { embed } from 'ai'; const { embedding, response } = await embed({ model: 'openai/text-embedding-3-small', value: 'sunny day at the beach', }); console.log(response); // Raw provider response ``` ## Embedding Middleware You can enhance embedding models, e.g. to set default values, using `wrapEmbeddingModel` and `EmbeddingModelMiddleware`. Here is an example that uses the built-in `defaultEmbeddingSettingsMiddleware`: ```ts import { defaultEmbeddingSettingsMiddleware, embed, wrapEmbeddingModel, gateway, } from 'ai'; const embeddingModelWithDefaults = wrapEmbeddingModel({ model: gateway.embeddingModel('google/gemini-embedding-001'), middleware: defaultEmbeddingSettingsMiddleware({ settings: { providerOptions: { google: { outputDimensionality: 256, taskType: 'CLASSIFICATION', }, }, }, }), }); ``` ## Embedding Providers & Models Several providers offer embedding models: | Provider | Model | Embedding Dimensions | Multimodal | | ----------------------------------------------------------------------------- | ------------------------------- | -------------------- | ---------- | | [OpenAI](/providers/ai-sdk-providers/openai#embedding-models) | `text-embedding-3-large` | 3072 | ✗ | | [OpenAI](/providers/ai-sdk-providers/openai#embedding-models) | `text-embedding-3-small` | 1536 | ✗ | | [OpenAI](/providers/ai-sdk-providers/openai#embedding-models) | `text-embedding-ada-002` | 1536 | ✗ | | [Google](/providers/ai-sdk-providers/google#embedding-models) | `gemini-embedding-001` | 3072 | ✗ | | [Google](/providers/ai-sdk-providers/google#embedding-models) | `gemini-embedding-2` | 3072 | ✓ | | [Google](/providers/ai-sdk-providers/google#embedding-models) | `gemini-embedding-2-preview` | 3072 | ✓ | | [Mistral](/providers/ai-sdk-providers/mistral#embedding-models) | `mistral-embed` | 1024 | ✗ | | [Cohere](/providers/ai-sdk-providers/cohere#embedding-models) | `embed-english-v3.0` | 1024 | ✗ | | [Cohere](/providers/ai-sdk-providers/cohere#embedding-models) | `embed-multilingual-v3.0` | 1024 | ✗ | | [Cohere](/providers/ai-sdk-providers/cohere#embedding-models) | `embed-english-light-v3.0` | 384 | ✗ | | [Cohere](/providers/ai-sdk-providers/cohere#embedding-models) | `embed-multilingual-light-v3.0` | 384 | ✗ | | [Cohere](/providers/ai-sdk-providers/cohere#embedding-models) | `embed-english-v2.0` | 4096 | ✗ | | [Cohere](/providers/ai-sdk-providers/cohere#embedding-models) | `embed-english-light-v2.0` | 1024 | ✗ | | [Cohere](/providers/ai-sdk-providers/cohere#embedding-models) | `embed-multilingual-v2.0` | 768 | ✗ | | [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock#embedding-models) | `amazon.titan-embed-text-v1` | 1536 | ✗ | | [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock#embedding-models) | `amazon.titan-embed-text-v2:0` | 1024 | ✗ | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Error Handling description: Learn how to handle errors in the AI SDK Core url: "https://ai-sdk.dev/docs/ai-sdk-core/error-handling" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Handling regular errors Regular errors are thrown and can be handled using the `try/catch` block. ```ts {4,9-11} import { generateText } from 'ai'; try { const { text } = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); } catch (error) { // handle error } ``` See [Error Types](/docs/reference/ai-sdk-errors) for more information on the different types of errors that may be thrown. ## Handling streaming errors (simple streams) When errors occur during streams that do not support error chunks, the error is thrown as a regular error. You can handle these errors using the `try/catch` block. ```ts {4,13-15} import { streamText } from 'ai'; try { const { textStream } = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); for await (const textPart of textStream) { process.stdout.write(textPart); } } catch (error) { // handle error } ``` ## Handling streaming errors (streaming with `error` support) The `stream` result supports error parts. You can handle those parts similar to other parts. It is recommended to also add a try-catch block for errors that happen outside of the streaming. ```ts {14-22} import { StreamProviderError, streamText } from 'ai'; try { const { stream } = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); for await (const part of stream) { switch (part.type) { // ... handle other part types case 'error': { const error = part.error; if (StreamProviderError.isInstance(error)) { console.error(error.message, { type: error.type, code: error.code, statusCode: error.statusCode, isRetryable: error.isRetryable, }); } break; } case 'abort': { // handle stream abort break; } case 'tool-error': { const error = part.error; // handle error break; } } } } catch (error) { // handle error } ``` ## Retrying provider errors after streaming starts `maxRetries` retries failures that happen while starting a model call. To retry well-formed provider error events received after response streaming has begun, set `streamRetries`: ```ts {7} import { streamText } from 'ai'; const { textStream } = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a vegetarian lasagna recipe for 4 people.', streamRetries: 2, }); for await (const textPart of textStream) { process.stdout.write(textPart); } ``` Stream retries rerun only the failed model step with the same accumulated conversation and generation context. Earlier completed steps, including their tool calls and tool results, are not replayed. Tool input, tool calls, approval requests, tool callbacks, and client-side tool execution from a failed attempt are discarded. They are only exposed or executed after an attempt reaches a successful model-call finish. Well-formed provider error events are normalized into [`StreamProviderError`](/docs/reference/ai-sdk-errors/ai-stream-provider-error) instances. The same instance is supplied to `onError` and, if recovery is not requested or retries are exhausted, to the `error` part in the full stream. Use its `isRetryable` metadata when deciding whether to request recovery. A retry remains part of the same logical step. `onStepStart` runs once for that step. `onLanguageModelCallStart` runs for each provider call attempt, while `onLanguageModelCallEnd` runs only for attempts that reach a model-call finish. You can also decide dynamically in `onError`. Set `streamRetries` explicitly to enable stream recovery; use `0` when a single retry should only be callback-directed: ```ts {7-12} const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a vegetarian lasagna recipe for 4 people.', streamRetries: 0, onError: ({ error }) => { if (isTransientProviderError(error)) { return { retry: true }; } }, }); ``` Callback-directed recovery is limited to one retry per logical step. When automatic retries are configured, `onError` can request one additional retry after the automatic retry budget is exhausted. This bounds the total number of recovery calls for a step to `streamRetries + 1`. When `streamRetries` is omitted, all stream retry behavior is disabled and an existing logging-only `onError` callback retains incremental tool streaming. An `onError` return value other than `{ retry: true }` keeps its previous behavior and does not request recovery. Provider error events that are recovered are not emitted as final `error` parts. Non-tool output emitted before a provider error cannot be retracted. A retried model step may therefore append repeated or divergent partial text, reasoning, files, or sources to consumer streams. Open text and reasoning parts are ended before recovered output begins so UI consumers do not retain them in a streaming state. Failed-attempt output is excluded from the recovered step result, structured output parsing, response messages, and subsequent model steps. Final request and response metadata come from the recovered attempt. Retries also add latency and may incur additional provider usage and cost. Failed-attempt isolation prevents AI SDK client-side tools from executing, but it cannot undo work already performed by provider-executed tools. Retrying a step after provider-side work may repeat that work or its cost. Use idempotency controls for provider-executed side effects. ## Handling stream aborts When streams are aborted (e.g., via chat stop button), you may want to perform cleanup operations like updating stored messages in your UI. Use the `onAbort` callback to handle these cases. The `onAbort` callback is called when a stream is aborted via `AbortSignal`, but `onEnd` is not called. This ensures you can still update your UI state appropriately. ```ts {6-10} import { streamText } from 'ai'; const { textStream } = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a vegetarian lasagna recipe for 4 people.', onAbort: ({ steps }) => { // Update stored messages or perform cleanup console.log('Stream aborted after', steps.length, 'steps'); }, onEnd: ({ steps, totalUsage }) => { // This is called on normal completion console.log('Stream completed normally'); }, }); for await (const textPart of textStream) { process.stdout.write(textPart); } ``` The `onAbort` callback receives: - `steps`: An array of all completed steps before the abort You can also handle abort events directly in the stream: ```ts {11-14} import { streamText } from 'ai'; const { stream } = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); for await (const chunk of stream) { switch (chunk.type) { case 'abort': { // Handle abort directly in stream console.log('Stream was aborted'); break; } // ... handle other part types } } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: File Uploads description: Learn how to upload files and use provider references with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/file-uploads" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK provides the [`uploadFile`](/docs/reference/ai-sdk-core/upload-file) function to upload files to a provider and get back a `ProviderReference` that can be used in subsequent API calls. In the AI SDK, the uploaded file is identified by a `ProviderReference` — a `Record` mapping provider names to provider-specific identifiers. This concept is used for other provider specific asset references too, such as uploaded skills. ```ts import { uploadFile, generateText } from 'ai'; import { openai } from '@ai-sdk/openai'; import fs from 'node:fs'; const { providerReference } = await uploadFile({ api: openai.files(), data: fs.readFileSync('./photo.png'), filename: 'photo.png', }); const { text } = await generateText({ model: openai.responses('gpt-6-luna'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe what you see in this image.' }, { type: 'file', mediaType: 'image', data: providerReference }, ], }, ], }); ``` As a shorthand, you can pass a provider instance directly to `api` instead of calling `.files()` explicitly — the SDK will call `.files()` for you: ```ts {3} const { providerReference } = await uploadFile({ api: openai, // shorthand for openai.files() data: fs.readFileSync('./photo.png'), filename: 'photo.png', }); ``` ## Supported File Types You can upload images, PDFs, text files, and other documents depending on the provider. The media type is auto-detected from the file bytes when not specified explicitly: ```ts {4} const { providerReference } = await uploadFile({ api: anthropic.files(), data: fs.readFileSync('./document.pdf'), mediaType: 'application/pdf', // optional, auto-detected if omitted filename: 'document.pdf', }); ``` Use the `providerReference` in a file content part with its media type: ```ts { role: 'user', content: [ { type: 'text', text: 'Summarize this document.' }, { type: 'file', data: providerReference, mediaType: 'application/pdf' }, ], } ``` ## Provider-Specific Options Some providers accept additional options through `providerOptions`. For example, OpenAI requires a `purpose` field: ```ts {5-9} import { openai, type OpenAIFilesOptions } from '@ai-sdk/openai'; const { providerReference } = await uploadFile({ api: openai.files(), data: fs.readFileSync('./photo.png'), providerOptions: { openai: { purpose: 'assistants', } satisfies OpenAIFilesOptions, }, }); ``` ## Streaming Uploads Providers that support streaming uploads (e.g. OpenAI, xAI) accept a tagged `{ type: 'stream', stream }` shape, sending the bytes without buffering the full file in memory. Providers without streaming support reject stream data with an `UnsupportedFunctionalityError`. ```ts const { providerReference } = await uploadFile({ api: openai.files(), data: { type: 'stream', stream: fileStream }, mediaType: 'application/jsonl', filename: 'batch.jsonl', }); ``` The provider consumes the stream: any failed upload — including validation failures before a request is made — cancels it, and it must not be reused. Stream data cannot be sniffed, so `mediaType` defaults to `application/octet-stream` when omitted, and multipart-based providers default the filename to `"blob"`. Uploads can be cancelled with `abortSignal` and carry request-specific `headers`. Results include `byteSize`, `createdAt`, and `expiresAt` (the provider-applied retention expiry) when the provider reports them. ## Provider References A `ProviderReference` is a `Record` that maps provider names to provider-specific file identifiers: ```ts // Example ProviderReference { openai: 'file-abc123', } ``` When you pass a `ProviderReference` as the `data` or `image` field of a message content part, the provider looks up its own file ID from the reference. If the reference doesn't contain an entry for the current provider, an error is thrown. ## Multi-Provider Usage If you switch providers mid-conversation (for example, continuing a chat started with OpenAI using Anthropic), you need to upload the file to both providers and merge the references: ```ts const openaiResult = await uploadFile({ api: openai.files(), data: imageBytes, filename: 'photo.png', }); const anthropicResult = await uploadFile({ api: anthropic.files(), data: imageBytes, filename: 'photo.png', }); const mergedReference = { ...openaiResult.providerReference, ...anthropicResult.providerReference, }; // mergedReference: { openai: 'file-abc123', anthropic: 'file-xyz789' } ``` The merged reference can then be used in messages regardless of which provider processes the request — each provider will find its own file ID. ## Supported Providers The following providers support `files()` and file uploads: | Provider | Factory Method | | --------- | ------------------- | | Anthropic | `anthropic.files()` | | Google | `google.files()` | | OpenAI | `openai.files()` | | xAI | `xai.files()` | Providers without file upload support will throw an `UnsupportedFunctionalityError` if they encounter a provider reference in a message. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Generating Structured Data description: Learn how to generate structured data with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/generating-structured-data" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). While text generation can be useful, your use case will likely call for generating structured data. For example, you might want to extract information from text, classify data, or generate synthetic data. Many language models are capable of generating structured data, often defined as using "JSON modes" or "tools". However, you need to manually provide schemas and then validate the generated data as LLMs can produce incorrect or incomplete structured data. The AI SDK standardizes structured object generation across model providers using the `output` property on [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text). You can use [Zod schemas](/docs/reference/ai-sdk-core/zod-schema), [Valibot](/docs/reference/ai-sdk-core/valibot-schema), or [JSON schemas](/docs/reference/ai-sdk-core/json-schema) to specify the shape of the data that you want, and the AI model will generate data that conforms to that structure. Structured output generation is part of the `generateText` and `streamText` flow. This means you can combine it with tool calling in the same request. ## Generating Structured Outputs Use `generateText` with `Output.object()` to generate structured data from a prompt. The schema is also used to validate the generated data, ensuring type safety and correctness. ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; const { output } = await generateText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ recipe: z.object({ name: z.string(), ingredients: z.array( z.object({ name: z.string(), amount: z.string() }), ), steps: z.array(z.string()), }), }), }), prompt: 'Generate a lasagna recipe.', }); ``` Structured output generation counts as a step in the AI SDK's multi-turn execution model (where each model call or tool execution is one step). When combining with tools, account for this in your `stopWhen` configuration. ### Accessing response headers & body Sometimes you need access to the full response from the model provider, e.g. to access some provider-specific headers or body content. You can access the raw response headers and body using the `response` property: ```ts import { generateText, Output } from 'ai'; const result = await generateText({ // ... output: Output.object({ schema }), }); console.log(JSON.stringify(result.response.headers, null, 2)); console.log(JSON.stringify(result.response.body, null, 2)); ``` ## Stream Structured Outputs Given the added complexity of returning structured data, model response time can be unacceptable for your interactive use case. With `streamText` and `output`, you can stream the model's structured response as it is generated. ```ts import { streamText, Output } from 'ai'; import { z } from 'zod'; const { partialOutputStream } = streamText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ recipe: z.object({ name: z.string(), ingredients: z.array( z.object({ name: z.string(), amount: z.string() }), ), steps: z.array(z.string()), }), }), }), prompt: 'Generate a lasagna recipe.', }); // use partialOutputStream as an async iterable for await (const partialObject of partialOutputStream) { console.log(partialObject); } ``` You can consume the structured output on the client with the [`useObject`](/docs/reference/ai-sdk-ui/use-object) hook. ### Error Handling in Streams `streamText` starts streaming immediately. When errors occur during streaming, they become part of the stream rather than thrown exceptions (to prevent stream crashes). To handle errors, provide an `onError` callback: ```tsx {6-8} import { streamText, Output } from 'ai'; const result = streamText({ // ... output: Output.object({ schema }), onError({ error }) { console.error(error); // log to your error tracking service }, }); ``` For non-streaming error handling with `generateText`, see the [Error Handling](#error-handling) section below. ## Output Types The AI SDK supports multiple ways of specifying the expected structure of generated data via the `Output` object. You can select from various strategies for structured/text generation and validation. ### `Output.text()` Use `Output.text()` to generate plain text from a model. This option doesn't enforce any schema on the result: you simply receive the model's text as a string. This is the default behavior when no `output` is specified. ```ts import { generateText, Output } from 'ai'; const { output } = await generateText({ // ... output: Output.text(), prompt: 'Tell me a joke.', }); // output will be a string (the joke) ``` ### `Output.object()` Use `Output.object({ schema })` to generate a structured object based on a schema (for example, a Zod schema). The output is type-validated to ensure the returned result matches the schema. ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; const { output } = await generateText({ // ... output: Output.object({ schema: z.object({ name: z.string(), age: z.number().nullable(), labels: z.array(z.string()), }), }), prompt: 'Generate information for a test user.', }); // output will be an object matching the schema above ``` Partial outputs streamed via `streamText` cannot be validated against your provided schema, as incomplete data may not yet conform to the expected structure. ### `Output.array()` Use `Output.array({ element, minItems, maxItems })` to specify that you expect an array of typed objects from the model. Each element must conform to the `element` schema, and the optional bounds constrain the number of elements. ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; const { output } = await generateText({ // ... output: Output.array({ element: z.object({ location: z.string(), temperature: z.number(), condition: z.string(), }), minItems: 2, maxItems: 2, }), prompt: 'List the weather for San Francisco and Paris.', }); // output will be an array of objects like: // [ // { location: 'San Francisco', temperature: 70, condition: 'Sunny' }, // { location: 'Paris', temperature: 65, condition: 'Cloudy' }, // ] ``` `minItems` and `maxItems` must be non-negative integers, and `minItems` cannot be greater than `maxItems`. Use the same value for both options to require an exact length. The bounds are sent to providers as part of the structured output schema when supported, and the AI SDK independently validates the final output. When streaming arrays with `streamText`, you can use `elementStream` to receive each completed element as it is generated: ```ts import { streamText, Output } from 'ai'; import { z } from 'zod'; const { elementStream } = streamText({ // ... output: Output.array({ element: z.object({ name: z.string(), class: z.string(), description: z.string(), }), }), prompt: 'Generate 3 hero descriptions for a fantasy role playing game.', }); for await (const hero of elementStream) { console.log(hero); // Each hero is complete and validated } ``` Each element emitted by `elementStream` is complete and validated against your element schema. This differs from `partialOutputStream`, which streams the entire partial array including incomplete elements. If the model generates more than `maxItems`, `elementStream` errors before emitting the first excess element. This does not automatically abort provider generation, and the final `output` promise rejects. ### `Output.choice()` Use `Output.choice({ options })` when you expect the model to choose from a specific set of string options, such as for classification or fixed-enum answers. ```ts import { generateText, Output } from 'ai'; const { output } = await generateText({ // ... output: Output.choice({ options: ['sunny', 'rainy', 'snowy'], }), prompt: 'Is the weather sunny, rainy, or snowy today?', }); // output will be one of: 'sunny', 'rainy', or 'snowy' ``` You can provide any set of string options, and the output will always be a single string value that matches one of the specified options. The AI SDK validates that the result matches one of your options, and will throw if the model returns something invalid. This is especially useful for making classification-style generations or forcing valid values for API compatibility. ### `Output.json()` Use `Output.json()` when you want to generate and parse unstructured JSON values from the model, without enforcing a specific schema. This is useful if you want to capture arbitrary objects, flexible structures, or when you want to rely on the model's natural output rather than rigid validation. ```ts import { generateText, Output } from 'ai'; const { output } = await generateText({ // ... output: Output.json(), prompt: 'For each city, return the current temperature and weather condition as a JSON object.', }); // output could be any valid JSON, for example: // { // "San Francisco": { "temperature": 70, "condition": "Sunny" }, // "Paris": { "temperature": 65, "condition": "Cloudy" } // } ``` With `Output.json`, the AI SDK only checks that the response is valid JSON; it doesn't validate the structure or types of the values. If you need schema validation, use the `.object` or `.array` outputs instead. For more advanced validation or different structures, see [the Output API reference](/docs/reference/ai-sdk-core/output). ## Generating Structured Outputs with Tools One of the key advantages of using structured output with `generateText` and `streamText` is the ability to combine it with tool calling. ```ts import { generateText, Output, tool, isStepCount } from 'ai'; import { z } from 'zod'; const { output } = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { weather: tool({ description: 'Get the weather for a location', inputSchema: z.object({ location: z.string() }), execute: async ({ location }) => { // fetch weather data return { temperature: 72, condition: 'sunny' }; }, }), }, output: Output.object({ schema: z.object({ summary: z.string(), recommendation: z.string(), }), }), stopWhen: isStepCount(5), prompt: 'What should I wear in San Francisco today?', }); ``` When using tools with structured output, remember that generating the structured output counts as a step. Configure `stopWhen` to allow enough steps for both tool execution and output generation. ## Property Descriptions You can add `.describe("...")` to individual schema properties to give the model hints about what each property is for. This helps improve the quality and accuracy of generated structured data: ```ts {9,16,19-20} import { generateText, Output } from 'ai'; import { z } from 'zod'; const { output } = await generateText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ name: z.string().describe('The name of the recipe'), ingredients: z .array( z.object({ name: z.string(), amount: z .string() .describe('The amount of the ingredient (grams or ml)'), }), ) .describe('List of ingredients with amounts'), steps: z.array(z.string()).describe('Step-by-step cooking instructions'), }), }), prompt: 'Generate a lasagna recipe.', }); ``` Property descriptions are particularly useful for: - Clarifying ambiguous property names - Specifying expected formats or conventions - Providing context for complex nested structures ## Output Name and Description You can optionally specify a `name` and `description` for the output. These are used by some providers for additional LLM guidance, e.g. via tool or schema name. ```ts {8-9} import { generateText, Output } from 'ai'; import { z } from 'zod'; const { output } = await generateText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ name: 'Recipe', description: 'A recipe for a dish.', schema: z.object({ name: z.string(), ingredients: z.array(z.object({ name: z.string(), amount: z.string() })), steps: z.array(z.string()), }), }), prompt: 'Generate a lasagna recipe.', }); ``` This works with all output types that support structured generation: - `Output.object({ name, description, schema })` - `Output.array({ name, description, element, minItems, maxItems })` - `Output.choice({ name, description, options })` - `Output.json({ name, description })` ## Accessing Reasoning You can access the reasoning used by the language model to generate the object via the `reasoningText` property on the result. This property contains a string with the model's thought process, if available. ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", // must be a reasoning model output: Output.object({ schema: z.object({ recipe: z.object({ name: z.string(), ingredients: z.array( z.object({ name: z.string(), amount: z.string(), }), ), steps: z.array(z.string()), }), }), }), prompt: 'Generate a lasagna recipe.', }); console.log(result.reasoningText); ``` ## Error Handling `generateText` can report structured output failures in two ways: - If the model response cannot be parsed or validated against the schema, `generateText` rejects with an [`AI_NoObjectGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-object-generated-error). - If `generateText` returns a result without an output, accessing `result.output` throws an [`AI_NoOutputGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-output-generated-error). This can happen when the final step does not finish with a `stop` reason, for example when it finishes with `tool-calls`. The `output` property is a getter, so destructuring it also triggers this access. `NoObjectGeneratedError` preserves the following information to help you log the issue: - `text`: The text that was generated by the model. This can be the raw text or the tool call text, depending on the object generation mode. - `response`: Metadata about the language model response, including response id, timestamp, and model. - `usage`: Request token usage. - `cause`: The cause of the error (e.g. a JSON parsing error). You can use this for more detailed error handling. ```ts import { generateText, NoObjectGeneratedError, NoOutputGeneratedError, Output, } from 'ai'; try { const result = await generateText({ model, output: Output.object({ schema }), prompt, }); console.log(result.output); } catch (error) { if (NoObjectGeneratedError.isInstance(error)) { console.log('NoObjectGeneratedError'); console.log('Cause:', error.cause); console.log('Text:', error.text); console.log('Response:', error.response); console.log('Usage:', error.usage); } else if (NoOutputGeneratedError.isInstance(error)) { console.log('NoOutputGeneratedError'); } } ``` ## More Examples You can see structured output generation in action using various frameworks in the following examples: ### `generateText` with Output - [Learn to generate structured data in Node.js](/examples/node/generating-structured-data/generate-object) - [Learn to generate structured data in Next.js with Route Handlers (AI SDK UI)](/examples/next-pages/basics/generating-object) - [Learn to generate structured data in Next.js with Server Actions (AI SDK RSC)](/examples/next-app/basics/generating-object) ### `streamText` with Output - [Learn to stream structured data in Node.js](/examples/node/streaming-structured-data/stream-object) - [Learn to stream structured data in Next.js with Route Handlers (AI SDK UI)](/examples/next-pages/basics/streaming-object-generation) - [Learn to stream structured data in Next.js with Server Actions (AI SDK RSC)](/examples/next-app/basics/streaming-object-generation) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Generating Text description: Learn how to generate text with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/generating-text" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Large language models (LLMs) can generate text in response to a prompt, which can contain instructions and information to process. For example, you can ask a model to come up with a recipe, draft an email, or summarize a document. The AI SDK Core provides two functions to generate text and stream it from LLMs: - [`generateText`](#generatetext): Generates text for a given prompt and model. - [`streamText`](#streamtext): Streams text from a given prompt and model. Advanced LLM features such as [tool calling](./tools-and-tool-calling) and [structured data generation](./generating-structured-data) are built on top of text generation. ## `generateText` You can generate text using the [`generateText`](/docs/reference/ai-sdk-core/generate-text) function. This function is ideal for non-interactive use cases where you need to write text (e.g. drafting email or summarizing web pages) and for agents that use tools. ```tsx import { generateText } from 'ai'; const { text } = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` You can use more [advanced prompts](/docs/foundations/prompts) to generate text with more complex instructions and content: ```tsx import { generateText } from 'ai'; const { text } = await generateText({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a professional writer. ' + 'You write simple, clear, and concise content.', prompt: `Summarize the following article in 3-5 sentences: ${article}`, }); ``` The result object of `generateText` contains the generated output and metadata: - `result.content`: The content that was generated in all steps. - `result.text`: The generated text from the final step. - `result.files`: The files that were generated in all steps. - `result.sources`: Sources that have been used as references in all steps (only available for some models). - `result.toolCalls`: The tool calls that were made in all steps. - `result.toolResults`: The results of the tool calls from all steps. - `result.finishReason`: The reason the model finished generating text. - `result.rawFinishReason`: The raw reason why the generation finished (from the provider). - `result.usage`: The total usage across all steps (for multi-step generations). - `result.warnings`: Warnings from the model provider in all steps (e.g. unsupported settings). - `result.steps`: Details for all steps, useful for getting information about intermediate steps, including per-step `performance`. - `result.finalStep`: Details for the final step, including `performance`. - `result.output`: The generated structured output using the `output` specification. Each step includes `performance` with timing and throughput information: - `effectiveOutputTokensPerSecond`: Effective output tokens per second, calculated as `outputTokens / requestSeconds`. - `outputTokensPerSecond`: For streaming steps, output tokens per second after the first generated output chunk, calculated as `outputTokens / outputStreamSeconds`. For `generateText`, this is `undefined`. - `inputTokensPerSecond`: For streaming steps, input tokens per second before the first generated output chunk, calculated as `inputTokens / ttftSeconds`. For `generateText`, this is `undefined`. - `effectiveTotalTokensPerSecond`: Effective total tokens per second, calculated as `(inputTokens + outputTokens) / requestSeconds`. - `stepTimeMs`: Total time spent on the step, including language model response time and tool execution time, in milliseconds. - `responseTimeMs`: Time spent waiting for the language model response in milliseconds. - `toolExecutionMs`: Time spent executing each client-side tool call in the step in milliseconds, keyed by tool call ID. - `timeToFirstOutputMs`: For streaming steps, the time until the first generated output chunk was received in milliseconds. For `generateText`, this is `undefined`. - `timeBetweenOutputChunksMs`: For streaming steps with at least two output chunks, timing statistics for the gaps between generated output chunks in milliseconds. ### Accessing response headers & body Sometimes you need access to the full response from the model provider, e.g. to access some provider-specific headers or body content. You can access the raw response headers and body using the `finalStep.response` property: ```ts import { generateText } from 'ai'; const result = await generateText({ // ... }); console.log(JSON.stringify(result.finalStep.response.headers, null, 2)); console.log(JSON.stringify(result.finalStep.response.body, null, 2)); ``` ### `onEnd` callback When using `generateText`, you can provide an `onEnd` callback that is triggered after the last step is finished ( [API Reference](/docs/reference/ai-sdk-core/generate-text#on-end) ). It contains the text, usage information, finish reason, messages, steps, total usage, and more: ```tsx {7-10} import { generateText } from 'ai'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', onEnd({ text, finishReason, usage, responseMessages, steps, totalUsage }) { // your own logic, e.g. for saving the chat history or recording usage const messages = responseMessages; // messages that were generated }, }); ``` ### Lifecycle callbacks (experimental) Experimental callbacks are subject to breaking changes in incremental package releases. `generateText` provides several experimental lifecycle callbacks that let you hook into different phases of the generation process. These are useful for logging, observability, debugging, and custom telemetry. Errors thrown inside these callbacks are silently caught and do not break the generation flow. ```tsx import { generateText } from 'ai'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'What is the weather in San Francisco?', tools: { // ... your tools }, onStart({ modelId }) { console.log('Generation started', { modelId }); }, onStepStart({ stepNumber, modelId, messages }) { console.log(`Step ${stepNumber} starting`, { modelId }); }, onLanguageModelCallStart({ modelId, messages }) { console.log('Model call starting', { modelId, messageCount: messages.length, }); }, onLanguageModelCallEnd({ modelId, finishReason, content }) { console.log('Model call finished', { modelId, finishReason, contentParts: content.length, }); }, onToolExecutionStart({ toolCall }) { console.log(`Tool call starting: ${toolCall.toolName}`, { toolCallId: toolCall.toolCallId, }); }, onToolExecutionEnd({ toolCall, toolExecutionMs, toolOutput }) { console.log( `Tool call finished: ${toolCall.toolName} (${toolExecutionMs}ms)`, { success: toolOutput.type === 'tool-result', }, ); }, onStepEnd({ stepNumber, finishReason, usage, performance }) { console.log(`Step ${stepNumber} finished`, { finishReason, usage, performance, }); }, }); ``` The available lifecycle callbacks are: - **`onStart`**: Called once when the `generateText` operation begins, before any LLM calls. Receives model info, messages, settings, and `runtimeContext`. - **`onStepStart`**: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps. - **`onLanguageModelCallStart`**: Called immediately before the provider model call begins. Useful when you want to observe the model invocation separately from later tool execution. - **`onLanguageModelCallEnd`**: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, finish reason, and provider metadata. - **`onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and `toolContext`. - **`onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call object, `toolExecutionMs`, and a `toolOutput` discriminated union (`type: 'tool-result'` with `output`, or `type: 'tool-error'` with `error`). - **`onStepEnd`**: Called after each step finishes. Includes `stepNumber` (zero-based index of the completed step). ## `streamText` Depending on your model and prompt, it can take a large language model (LLM) up to a minute to finish generating its response. This delay can be unacceptable for interactive use cases such as chatbots or real-time applications, where users expect immediate responses. AI SDK Core provides the [`streamText`](/docs/reference/ai-sdk-core/stream-text) function which simplifies streaming text from LLMs: ```ts import { streamText } from 'ai'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', }); // example: use textStream as an async iterable for await (const textPart of result.textStream) { console.log(textPart); } ``` `result.textStream` is both a `ReadableStream` and an `AsyncIterable`. `streamText` immediately starts streaming and suppresses errors to prevent server crashes. Use the `onError` callback to log errors. You can use `streamText` on its own or in combination with [AI SDK UI](/examples/next-pages/basics/streaming-text-generation) and [AI SDK RSC](/examples/next-app/basics/streaming-text-generation). The `result.stream` can be passed to several standalone helpers to make the integration into [AI SDK UI](/docs/ai-sdk-ui) easier: - `createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }) })`: Creates a UI Message stream HTTP response (with tool calls etc.) that can be used in a Next.js App Router API route. - `pipeUIMessageStreamToResponse({ stream: toUIMessageStream({ stream: result.stream }), response })`: Writes UI Message stream delta output to a Node.js response-like object. - `createTextStreamResponse({ stream: toTextStream({ stream: result.stream }) })`: Creates a simple text stream HTTP response. - `pipeTextStreamToResponse({ stream: toTextStream({ stream: result.stream }), response })`: Writes text delta output to a Node.js response-like object. `streamText` is using backpressure and only generates tokens as they are requested. You need to consume the stream in order for it to finish. It also provides several promises that resolve when the stream is finished: - `result.content`: The content that was generated in all steps. - `result.text`: The generated text from the final step. - `result.finalStep`: Details for the final step, including per-step `performance`. - `result.files`: Files that have been generated by the model in all steps. - `result.sources`: Sources that have been used as references in all steps (only available for some models). - `result.toolCalls`: The tool calls that have been executed in all steps. - `result.toolResults`: The tool results that have been generated in all steps. - `result.finishReason`: The reason the model finished generating text. - `result.rawFinishReason`: The raw reason why the generation finished (from the provider). - `result.usage`: The total usage across all steps (for multi-step generations). - `result.totalUsage`: Deprecated. Use `result.usage` instead. - `result.warnings`: Warnings from the model provider in all steps (e.g. unsupported settings). - `result.steps`: Details for all steps, useful for getting information about intermediate steps, including per-step `performance`. For `streamText`, `timeToFirstOutputMs` is set when the first generated output chunk is received for a step. `timeBetweenOutputChunksMs` includes `min`, `p10`, `median`, `avg`, `p90`, and `max` when at least two output chunks are received. ### `onError` callback `streamText` immediately starts streaming to enable sending data without waiting for the model. Errors become part of the stream and are not thrown to prevent e.g. servers from crashing. To log errors, you can provide an `onError` callback that is triggered when an error occurs. ```tsx {7-9} import { streamText } from 'ai'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', onError({ error }) { console.error(error); // your error logging logic here }, }); ``` ### `onChunk` callback When using `streamText`, you can provide an `onChunk` callback that is triggered for each chunk of the stream. It receives all stream part types from `stream`, including: - `start` - `start-step` - `text-start` - `text-delta` - `text-end` - `reasoning-start` - `reasoning-delta` - `reasoning-end` - `custom` - `source` - `file` - `reasoning-file` - `tool-call` - `tool-input-start` - `tool-input-delta` - `tool-input-end` - `tool-result` - `tool-error` - `tool-output-denied` - `tool-approval-request` - `tool-approval-response` - `finish-step` - `finish` - `abort` - `error` - `raw` ```tsx {7-12} import { streamText } from 'ai'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', onChunk({ chunk }) { // implement your own logic here, e.g.: if (chunk.type === 'text-delta') { console.log(chunk.text); } }, }); ``` ### `onEnd` callback When using `streamText`, you can provide an `onEnd` callback that is triggered when the stream is finished ( [API Reference](/docs/reference/ai-sdk-core/stream-text#on-end) ). It contains the text, usage information, finish reason, messages, steps, total usage, and more: ```tsx {7-10} import { streamText } from 'ai'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', onEnd({ text, finishReason, usage, responseMessages, steps, totalUsage }) { // your own logic, e.g. for saving the chat history or recording usage const messages = responseMessages; // messages that were generated }, }); ``` ### Lifecycle callbacks (experimental) Experimental callbacks are subject to breaking changes in incremental package releases. `streamText` provides several experimental lifecycle callbacks that let you hook into different phases of the streaming process. These are useful for logging, observability, debugging, and custom telemetry. Errors thrown inside these callbacks are silently caught and do not break the streaming flow. ```tsx import { streamText } from 'ai'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'What is the weather in San Francisco?', tools: { // ... your tools }, onStart({ modelId, instructions, messages }) { console.log('Streaming started', { modelId }); }, onStepStart({ stepNumber, modelId, messages }) { console.log(`Step ${stepNumber} starting`, { modelId }); }, onLanguageModelCallStart({ modelId, messages }) { console.log('Model call starting', { modelId, messageCount: messages.length, }); }, onLanguageModelCallEnd({ modelId, finishReason, content }) { console.log('Model call finished', { modelId, finishReason, contentParts: content.length, }); }, onToolExecutionStart({ toolCall }) { console.log(`Tool call starting: ${toolCall.toolName}`, { toolCallId: toolCall.toolCallId, }); }, onToolExecutionEnd({ toolCall, toolExecutionMs, toolOutput }) { console.log( `Tool call finished: ${toolCall.toolName} (${toolExecutionMs}ms)`, { success: toolOutput.type === 'tool-result', }, ); }, onStepEnd({ finishReason, usage }) { console.log('Step finished', { finishReason, usage }); }, }); ``` The available lifecycle callbacks are: - **`onStart`**: Called once when the `streamText` operation begins, before any LLM calls. Receives model info, messages, settings, and `runtimeContext`. - **`onStepStart`**: Called before each step (LLM call). Receives the step number, model, messages being sent, tools, and prior steps. - **`onLanguageModelCallStart`**: Called immediately before the provider model call begins. Useful when you want to observe the model invocation separately from later tool execution. - **`onLanguageModelCallEnd`**: Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Receives the model-call content parts, usage, finish reason, and provider metadata. - **`onToolExecutionStart`**: Called right before a tool's `execute` function runs. Receives the tool call object, messages, and `toolContext`. - **`onToolExecutionEnd`**: Called right after a tool's `execute` function completes or errors. Receives the tool call object, `toolExecutionMs`, and a `toolOutput` discriminated union (`type: 'tool-result'` with `output`, or `type: 'tool-error'` with `error`). - **`onStepEnd`**: Called after each step finishes. Receives the finish reason, usage, and other step details. ### `stream` property You can read a stream with all events using the `stream` property. This can be useful if you want to implement your own UI or handle the stream in a different way. Here is an example of how to use the `stream` property: ```tsx import { streamText } from 'ai'; import { z } from 'zod'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", tools: { cityAttractions: { inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => ({ attractions: ['attraction1', 'attraction2', 'attraction3'], }), }, }, prompt: 'What are some San Francisco tourist attractions?', }); for await (const part of result.stream) { switch (part.type) { case 'start': { // handle start of stream break; } case 'start-step': { // handle start of step break; } case 'text-start': { // handle text start break; } case 'text-delta': { // handle text delta here break; } case 'text-end': { // handle text end break; } case 'reasoning-start': { // handle reasoning start break; } case 'reasoning-delta': { // handle reasoning delta here break; } case 'reasoning-end': { // handle reasoning end break; } case 'source': { // handle source here break; } case 'file': { // handle file here break; } case 'tool-call': { switch (part.toolName) { case 'cityAttractions': { // handle tool call here break; } } break; } case 'tool-input-start': { // handle tool input start break; } case 'tool-input-delta': { // handle tool input delta break; } case 'tool-input-end': { // handle tool input end break; } case 'tool-result': { switch (part.toolName) { case 'cityAttractions': { // handle tool result here break; } } break; } case 'tool-error': { // handle tool error break; } case 'finish-step': { // handle finish step break; } case 'finish': { // handle finish here break; } case 'error': { // handle error here break; } case 'raw': { // handle raw value break; } } } ``` ### Stream transformation You can use the `experimental_transform` option to transform the stream. This is useful for e.g. filtering, changing, or smoothing the text stream. The transformations are applied before the callbacks are invoked and the promises are resolved. If you e.g. have a transformation that changes all text to uppercase, the `onEnd` callback will receive the transformed text. #### Smoothing streams The AI SDK Core provides a [`smoothStream` function](/docs/reference/ai-sdk-core/smooth-stream) that can be used to smooth out text and reasoning streaming. ```tsx {6} import { smoothStream, streamText } from 'ai'; const result = streamText({ model, prompt, experimental_transform: smoothStream(), }); ``` #### Custom transformations You can also implement your own custom transformations. The transformation function receives the tools that are available to the model, and returns a function that is used to transform the stream. Tools can either be generic or limited to the tools that you are using. Here is an example of how to implement a custom transformation that converts all text to uppercase: ```ts import { streamText, type TextStreamPart, type ToolSet } from 'ai'; const upperCaseTransform = () => (options: { tools: TOOLS; stopStream: () => void }) => new TransformStream, TextStreamPart>({ transform(chunk, controller) { controller.enqueue( // for text-delta chunks, convert the text to uppercase: chunk.type === 'text-delta' ? { ...chunk, text: chunk.text.toUpperCase() } : chunk, ); }, }); ``` You can also stop the stream using the `stopStream` function. This is e.g. useful if you want to stop the stream when model guardrails are violated, e.g. by generating inappropriate content. When you invoke `stopStream`, it is important to simulate the `finish-step` and `finish` events to guarantee that a well-formed stream is returned and all callbacks are invoked. ```ts import { streamText, type TextStreamPart, type ToolSet } from 'ai'; const stopWordTransform = () => ({ stopStream }: { stopStream: () => void }) => new TransformStream, TextStreamPart>({ // note: this is a simplified transformation for testing; // in a real-world version more there would need to be // stream buffering and scanning to correctly emit prior text // and to detect all STOP occurrences. transform(chunk, controller) { if (chunk.type !== 'text-delta') { controller.enqueue(chunk); return; } if (chunk.text.includes('STOP')) { // stop the stream stopStream(); // simulate the finish-step event controller.enqueue({ type: 'finish-step', finishReason: 'stop', rawFinishReason: 'stop', usage: { inputTokens: undefined, inputTokenDetails: { cacheReadTokens: undefined, cacheWriteTokens: undefined, noCacheTokens: undefined, }, outputTokens: undefined, outputTokenDetails: { reasoningTokens: undefined, textTokens: undefined, }, totalTokens: undefined, }, performance: { effectiveOutputTokensPerSecond: 0, outputTokensPerSecond: undefined, inputTokensPerSecond: undefined, effectiveTotalTokensPerSecond: 0, stepTimeMs: 0, responseTimeMs: 0, toolExecutionMs: {}, timeToFirstOutputMs: undefined, }, response: { id: 'response-id', modelId: 'mock-model-id', timestamp: new Date(0), }, providerMetadata: undefined, }); // simulate the finish event controller.enqueue({ type: 'finish', finishReason: 'stop', rawFinishReason: 'stop', totalUsage: { inputTokens: undefined, inputTokenDetails: { cacheReadTokens: undefined, cacheWriteTokens: undefined, noCacheTokens: undefined, }, outputTokens: undefined, outputTokenDetails: { reasoningTokens: undefined, textTokens: undefined, }, totalTokens: undefined, }, }); return; } controller.enqueue(chunk); }, }); ``` #### Multiple transformations You can also provide multiple transformations. They are applied in the order they are provided. ```tsx {4} const result = streamText({ model, prompt, experimental_transform: [firstTransform, secondTransform], }); ``` ## Sources Some providers such as [Perplexity](/providers/ai-sdk-providers/perplexity#sources) and [Google](/providers/ai-sdk-providers/google#sources) include sources in the response. Sources can reference URLs or documents. You can access them using the `sources` property of the result. For OpenAI Responses and Anthropic web search, sources contain the retrieved references, which can include more references than the model cites in its answer. OpenAI file-search results are also exposed as document sources when the API returns those results. If retrieval data is unavailable, citation-derived references remain available as sources for compatibility. Each `url` source contains the following properties: - `id`: The ID of the source. - `url`: The URL of the source. - `title`: The optional title of the source. - `providerMetadata`: Provider metadata for the source. When you use `generateText`, you can access the sources using the `sources` property: ```ts const result = await generateText({ model: 'google/gemini-3.8-flash', tools: { google_search: google.tools.googleSearch({}), }, prompt: 'List the top 5 San Francisco news from the past week.', }); for (const source of result.sources) { if (source.sourceType === 'url') { console.log('ID:', source.id); console.log('Title:', source.title); console.log('URL:', source.url); console.log('Provider metadata:', source.providerMetadata); console.log(); } } ``` When you use `streamText`, you can access the sources using the `stream` property: ```tsx const result = streamText({ model: 'google/gemini-3.8-flash', tools: { google_search: google.tools.googleSearch({}), }, prompt: 'List the top 5 San Francisco news from the past week.', }); for await (const part of result.stream) { if (part.type === 'source' && part.sourceType === 'url') { console.log('ID:', part.id); console.log('Title:', part.title); console.log('URL:', part.url); console.log('Provider metadata:', part.providerMetadata); console.log(); } } ``` The sources are also available in the `result.sources` promise. ### Citations OpenAI Responses and Anthropic attach an optional `citations` array to each `text` content part. A citation is a reference supporting that text, independently of the complete retrieved source set. Use citations to render inline references; use `result.sources` to list retrieved sources. Each `Citation` (exported from `ai`) contains: - `source`: A URL or document source, including its title when available. Its ID identifies the cited resource and does not necessarily match an ID in `result.sources`. - `startIndex` and `endIndex`: Optional provider-supplied offsets within the containing generated text; the end is exclusive. Providers can use different character indexing conventions. The SDK preserves their offsets. - `citedText`: An optional supporting passage from the source. This is source text, rather than an offset in the generated answer. For inline rendering, use the offsets within the containing text part and follow the provider's character indexing convention. JavaScript's `slice()` uses UTF-16 code units, so provider offsets may need conversion when the text contains emoji or other characters outside the basic multilingual plane. When offsets are absent or cannot be interpreted reliably, render the references alongside the containing text block instead of guessing a range. `citedText` and provider-specific document locations identify the supporting evidence in the source; they do not locate the claim in the generated answer. Preserve query parameters and fragments in source URLs, since they can identify a particular version, page, or passage. Multiple citations can reference the same source at different locations in the generated text. Anthropic supplies supporting passages and document locations instead of output text offsets. Provider-specific location details remain in provider metadata. OpenAI file-search citation `index` values are retained in provider metadata; they are not interpreted as output text ranges. ```ts for (const part of result.content) { if (part.type === 'text') { for (const citation of part.citations ?? []) { console.log(citation.source, citation.startIndex, citation.endIndex); } } } ``` For `streamText`, citations are available on `text-end` events, associated with the text block's `id`, and on text parts in the completed `result.content`. They also survive `toUIMessageStream` as `TextUIPart.citations`, including when source events are disabled. Citation fallback source events can arrive at the end of the provider stream, after the SDK knows whether retrieval data exists. ```ts for await (const part of result.stream) { if (part.type === 'text-end') { console.log(part.id, part.citations); } } ``` Raw OpenAI annotations and Anthropic citations remain in text provider metadata. Other providers may omit `citations`; an omitted field does not imply that their sources are a complete retrieval set. ## Examples You can see `generateText` and `streamText` in action using various frameworks in the following examples: ### `generateText` - [Learn to generate text in Node.js](/examples/node/generating-text/generate-text) - [Learn to generate text in Next.js with Route Handlers (AI SDK UI)](/examples/next-pages/basics/generating-text) - [Learn to generate text in Next.js with Server Actions (AI SDK RSC)](/examples/next-app/basics/generating-text) ### `streamText` - [Learn to stream text in Node.js](/examples/node/generating-text/stream-text) - [Learn to stream text in Next.js with Route Handlers (AI SDK UI)](/examples/next-pages/basics/streaming-text-generation) - [Learn to stream text in Next.js with Server Actions (AI SDK RSC)](/examples/next-app/basics/streaming-text-generation) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Image Generation description: Learn how to generate images with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/image-generation" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK provides the [`generateImage`](/docs/reference/ai-sdk-core/generate-image) function to generate images based on a given prompt using an image model. ```tsx import { generateImage } from 'ai'; const { image } = await generateImage({ model: "openai/gpt-image-2.5-sunburst", prompt: 'Santa Claus driving a Cadillac', }); ``` You can access the image data using the `base64` or `uint8Array` properties: ```tsx const base64 = image.base64; // base64 image data const uint8Array = image.uint8Array; // Uint8Array image data ``` ## Settings ### Size and Aspect Ratio Depending on the model, you can either specify the size or the aspect ratio. ##### Size The size is specified as a string in the format `{width}x{height}`. Models only support a few sizes, and the supported sizes are different for each model and provider. ```tsx {7} import { generateImage } from 'ai'; const { image } = await generateImage({ model: "openai/gpt-image-2.5-sunburst", prompt: 'Santa Claus driving a Cadillac', size: '1024x1024', }); ``` ##### Aspect Ratio The aspect ratio is specified as a string in the format `{width}:{height}`. Models only support a few aspect ratios, and the supported aspect ratios are different for each model and provider. ```tsx {7} import { generateImage } from 'ai'; const { image } = await generateImage({ model: "openai/gpt-image-2.5-sunburst", prompt: 'Santa Claus driving a Cadillac', aspectRatio: '16:9', }); ``` ### Generating Multiple Images `generateImage` also supports generating multiple images at once: ```tsx {7} import { generateImage } from 'ai'; const { images } = await generateImage({ model: "openai/gpt-image-2.5-sunburst", prompt: 'Santa Claus driving a Cadillac', n: 4, // number of images to generate }); ``` `generateImage` will automatically call the model as often as needed (in parallel) to generate the requested number of images. Each image model has an internal limit on how many images it can generate in a single API call. The AI SDK manages this automatically by batching requests appropriately when you request multiple images using the `n` parameter. By default, the SDK uses provider-documented limits (for example, DALL-E 3 can only generate 1 image per call, while DALL-E 2 supports up to 10). If needed, you can override this behavior using the `maxImagesPerCall` setting when generating your image. This is particularly useful when working with new or custom models where the default batch size might not be optimal: ```tsx const { images } = await generateImage({ model: "openai/gpt-image-2.5-sunburst", prompt: 'Santa Claus driving a Cadillac', maxImagesPerCall: 5, // Override the default batch size n: 10, // Will make 2 calls of 5 images each }); ``` ### Checking Image Editing Support Image model instances can advertise whether they support reference image files and masks. Check these capabilities before routing an image editing request: ```ts import { openai } from '@ai-sdk/openai'; const model = openai.image('gpt-image-1'); const needsMask = true; if ((await model.supportsFileInputs) !== true) { throw new Error( 'The selected model does not advertise image editing support.', ); } if (needsMask && (await model.supportsMaskInputs) !== true) { throw new Error('The selected model does not advertise mask support.'); } ``` `supportsFileInputs` and `supportsMaskInputs` can be `true`, `false`, or `undefined` (unknown), and providers may expose any of these values asynchronously. Treat only `true` as confirmed support when selecting a model for an edit. File and mask support are separate because some models accept reference images but do not accept masks. These capabilities describe support for the SDK's standardized image and mask inputs, including the provider's request mapping. A model name that resembles a supported family is not sufficient evidence of support. A model may still require provider-specific options or impose limits on input formats and counts. The AI SDK does not automatically reject editing inputs based on these capabilities. Applications can decide whether to reject the request, ask the user to choose another model, or use a fallback when support is false or unknown. ### Providing a Seed You can provide a seed to the `generateImage` function to control the output of the image generation process. If supported by the model, the same seed will always produce the same image. ```tsx {7} import { generateImage } from 'ai'; const { image } = await generateImage({ model: "openai/gpt-image-2.5-sunburst", prompt: 'Santa Claus driving a Cadillac', seed: 1234567890, }); ``` ### Provider-specific Settings Image models often have provider- or even model-specific settings. You can pass such settings to the `generateImage` function using the `providerOptions` parameter. The options for the provider (`openai` in the example below) become request body properties. ```tsx {8-13} import { generateImage } from 'ai'; import { openai, type OpenAIImageModelGenerationOptions } from '@ai-sdk/openai'; const { image } = await generateImage({ model: openai.image('dall-e-3'), prompt: 'Santa Claus driving a Cadillac', size: '1024x1024', providerOptions: { openai: { style: 'vivid', quality: 'hd', } satisfies OpenAIImageModelGenerationOptions, }, }); ``` ### Abort Signals and Timeouts `generateImage` accepts an optional `abortSignal` parameter of type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that you can use to abort the image generation process or set a timeout. ```ts {7} import { generateImage } from 'ai'; const { image } = await generateImage({ model: "openai/gpt-image-2.5-sunburst", prompt: 'Santa Claus driving a Cadillac', abortSignal: AbortSignal.timeout(1000), // Abort after 1 second }); ``` ### Custom Headers `generateImage` accepts an optional `headers` parameter of type `Record` that you can use to add custom headers to the image generation request. ```ts {7} import { generateImage } from 'ai'; const { image } = await generateImage({ model: "openai/gpt-image-2.5-sunburst", prompt: 'Santa Claus driving a Cadillac', headers: { 'X-Custom-Header': 'custom-value' }, }); ``` ### Warnings If the model returns warnings, e.g. for unsupported parameters, they will be available in the `warnings` property of the response. ```tsx const { image, warnings } = await generateImage({ model: "openai/gpt-image-2.5-sunburst", prompt: 'Santa Claus driving a Cadillac', }); ``` ### Provider metadata Some providers expose metadata for an individual image. It is available directly on the generated image: ```tsx const prompt = 'Santa Claus driving a Cadillac'; const { image } = await generateImage({ model: openai.image('dall-e-3'), prompt, }); const revisedPrompt = image.providerMetadata?.openai?.revisedPrompt; console.log({ prompt, revisedPrompt, }); ``` The outer key of `image.providerMetadata` is the provider name. The inner value is the metadata for that image. ### Underlying calls When a request is split into multiple provider calls, `calls` preserves the images, provider metadata, response metadata, warnings, and usage for each call. `images` and `usage` remain convenient flattened and aggregate views, respectively. ```ts const { calls } = await generateImage({ model, prompt: 'Santa Claus driving a Cadillac', n: 4, maxImagesPerCall: 1, }); for (const call of calls) { console.log(call.providerMetadata, call.usage); } ``` ### Error Handling When `generateImage` cannot generate a valid image, it throws a [`AI_NoImageGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-image-generated-error). Unclassified empty image responses are retried according to `maxRetries` before this error is thrown. Providers can classify terminal empty responses, such as moderation blocks, with `isRetryable: false`; these responses are not retried. This error occurs when the AI provider fails to generate an image. It can arise due to the following reasons: - The model failed to generate a response - The model generated a response that could not be parsed The error preserves the following information to help you log the issue: - `calls`: Results from the underlying image model calls, including generated images, provider metadata, response metadata, warnings, and usage. - `responses`: Metadata about the image model responses, including timestamp, model, and headers. - `cause`: The cause of the error. You can use this for more detailed error handling ```ts import { generateImage, NoImageGeneratedError } from 'ai'; try { await generateImage({ model, prompt }); } catch (error) { if (NoImageGeneratedError.isInstance(error)) { console.log('NoImageGeneratedError'); console.log('Cause:', error.cause); console.log('Responses:', error.responses); for (const call of error.calls ?? []) { console.log('Provider metadata:', call.providerMetadata); console.log('Warnings:', call.warnings); console.log('Usage:', call.usage); } } } ``` ## Image Middleware You can enhance image models, e.g. to set default values or implement logging, using `wrapImageModel` and `ImageModelV4Middleware`. Here is an example that sets a default size when none is provided: ```ts import { generateImage, wrapImageModel } from 'ai'; const model = wrapImageModel({ model: "openai/gpt-image-2.5-sunburst", middleware: { specificationVersion: 'v4', transformParams: async ({ params }) => ({ ...params, size: params.size ?? '1024x1024', }), }, }); const { image } = await generateImage({ model, prompt: 'Santa Claus driving a Cadillac', }); ``` ## Generating Images with Language Models Some language models such as Google `gemini-3.1-flash-image-preview` support multi-modal outputs including images. With such models, you can access the generated images using the `files` property of the response. ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google('gemini-3.1-flash-image-preview'), prompt: 'Generate an image of a comic cat', }); for (const file of result.files) { if (file.mediaType.startsWith('image/')) { // The file object provides multiple data formats: // Access images as base64 string, Uint8Array binary data, or check type // - file.base64: string (data URL format) // - file.uint8Array: Uint8Array (binary data) // - file.mediaType: string (e.g. "image/png") } } ``` ## Image Models | Provider | Model | Support sizes (`width x height`) or aspect ratios (`width : height`) | | ------------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [xAI Grok](/providers/ai-sdk-providers/xai#image-models) | `grok-imagine-image` | `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, `2:3`, `2:1`, `1:2`, `19.5:9`, `9:19.5`, `20:9`, `9:20`, `auto` | | [OpenAI](/providers/ai-sdk-providers/openai#image-models) | `gpt-image-2.5-flare` | 1024x1024, 1536x1024, 1024x1536, custom | | [OpenAI](/providers/ai-sdk-providers/openai#image-models) | `gpt-image-2.5-sunburst` | 1024x1024, 1536x1024, 1024x1536, custom | | [OpenAI](/providers/ai-sdk-providers/openai#image-models) | `gpt-image-2` | 1024x1024, 1536x1024, 1024x1536 | | [OpenAI](/providers/ai-sdk-providers/openai#image-models) | `dall-e-3` | 1024x1024, 1792x1024, 1024x1792 | | [OpenAI](/providers/ai-sdk-providers/openai#image-models) | `dall-e-2` | 256x256, 512x512, 1024x1024 | | [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock#image-models) | `amazon.nova-canvas-v1:0` | 320-4096 (multiples of 16), 1:4 to 4:1, max 4.2M pixels | | [Fal](/providers/ai-sdk-providers/fal#image-models) | `fal-ai/flux/dev` | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9 | | [Fal](/providers/ai-sdk-providers/fal#image-models) | `fal-ai/flux-lora` | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9 | | [Fal](/providers/ai-sdk-providers/fal#image-models) | `fal-ai/fast-sdxl` | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9 | | [Fal](/providers/ai-sdk-providers/fal#image-models) | `fal-ai/flux-pro/v1.1-ultra` | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9 | | [Fal](/providers/ai-sdk-providers/fal#image-models) | `fal-ai/ideogram/v2` | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9 | | [Fal](/providers/ai-sdk-providers/fal#image-models) | `fal-ai/recraft-v3` | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9 | | [Fal](/providers/ai-sdk-providers/fal#image-models) | `fal-ai/stable-diffusion-3.5-large` | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9 | | [Fal](/providers/ai-sdk-providers/fal#image-models) | `fal-ai/hyper-sdxl` | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9 | | [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models) | `stabilityai/sd3.5` | 1:1, 16:9, 1:9, 3:2, 2:3, 4:5, 5:4, 9:16, 9:21 | | [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models) | `black-forest-labs/FLUX-1.1-pro` | 256-1440 (multiples of 32) | | [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models) | `black-forest-labs/FLUX-1-schnell` | 256-1440 (multiples of 32) | | [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models) | `black-forest-labs/FLUX-1-dev` | 256-1440 (multiples of 32) | | [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models) | `black-forest-labs/FLUX-pro` | 256-1440 (multiples of 32) | | [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models) | `stabilityai/sd3.5-medium` | 1:1, 16:9, 1:9, 3:2, 2:3, 4:5, 5:4, 9:16, 9:21 | | [DeepInfra](/providers/ai-sdk-providers/deepinfra#image-models) | `stabilityai/sdxl-turbo` | 1:1, 16:9, 1:9, 3:2, 2:3, 4:5, 5:4, 9:16, 9:21 | | [Replicate](/providers/ai-sdk-providers/replicate) | `black-forest-labs/flux-schnell` | 1:1, 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9 | | [Replicate](/providers/ai-sdk-providers/replicate) | `recraft-ai/recraft-v3` | 1024x1024, 1365x1024, 1024x1365, 1536x1024, 1024x1536, 1820x1024, 1024x1820, 1024x2048, 2048x1024, 1434x1024, 1024x1434, 1024x1280, 1280x1024, 1024x1707, 1707x1024 | | [Google](/providers/ai-sdk-providers/google#image-models) | `gemini-2.5-flash-image` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | | [Google](/providers/ai-sdk-providers/google#image-models) | `gemini-3-pro-image-preview` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | | [Google](/providers/ai-sdk-providers/google#image-models) | `gemini-3.1-flash-image-preview` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | | [Google](/providers/ai-sdk-providers/google#image-models) | `gemini-nano-banana-2.1` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9, 1:8, 8:1, 1:4, 4:1 | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models) | `gemini-2.5-flash-image` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models) | `gemini-3-pro-image-preview` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models) | `gemini-3.1-flash-image-preview` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#image-models) | `gemini-nano-banana-2.1` | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9, 1:8, 8:1, 1:4, 4:1 | | [Fireworks](/providers/ai-sdk-providers/fireworks#image-models) | `accounts/fireworks/models/flux-1-dev-fp8` | 1:1, 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9 | | [Fireworks](/providers/ai-sdk-providers/fireworks#image-models) | `accounts/fireworks/models/flux-1-schnell-fp8` | 1:1, 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9 | | [Fireworks](/providers/ai-sdk-providers/fireworks#image-models) | `accounts/fireworks/models/playground-v2-5-1024px-aesthetic` | 640x1536, 768x1344, 832x1216, 896x1152, 1024x1024, 1152x896, 1216x832, 1344x768, 1536x640 | | [Fireworks](/providers/ai-sdk-providers/fireworks#image-models) | `accounts/fireworks/models/japanese-stable-diffusion-xl` | 640x1536, 768x1344, 832x1216, 896x1152, 1024x1024, 1152x896, 1216x832, 1344x768, 1536x640 | | [Fireworks](/providers/ai-sdk-providers/fireworks#image-models) | `accounts/fireworks/models/playground-v2-1024px-aesthetic` | 640x1536, 768x1344, 832x1216, 896x1152, 1024x1024, 1152x896, 1216x832, 1344x768, 1536x640 | | [Fireworks](/providers/ai-sdk-providers/fireworks#image-models) | `accounts/fireworks/models/SSD-1B` | 640x1536, 768x1344, 832x1216, 896x1152, 1024x1024, 1152x896, 1216x832, 1344x768, 1536x640 | | [Fireworks](/providers/ai-sdk-providers/fireworks#image-models) | `accounts/fireworks/models/stable-diffusion-xl-1024-v1-0` | 640x1536, 768x1344, 832x1216, 896x1152, 1024x1024, 1152x896, 1216x832, 1344x768, 1536x640 | | [Luma](/providers/ai-sdk-providers/luma#image-models) | `photon-1` | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9 | | [Luma](/providers/ai-sdk-providers/luma#image-models) | `photon-flash-1` | 1:1, 3:4, 4:3, 9:16, 16:9, 9:21, 21:9 | | [Together.ai](/providers/ai-sdk-providers/togetherai#image-models) | `stabilityai/stable-diffusion-xl-base-1.0` | 512x512, 768x768, 1024x1024 | | [Together.ai](/providers/ai-sdk-providers/togetherai#image-models) | `black-forest-labs/FLUX.1-dev` | 512x512, 768x768, 1024x1024 | | [Together.ai](/providers/ai-sdk-providers/togetherai#image-models) | `black-forest-labs/FLUX.1-dev-lora` | 512x512, 768x768, 1024x1024 | | [Together.ai](/providers/ai-sdk-providers/togetherai#image-models) | `black-forest-labs/FLUX.1-schnell` | 512x512, 768x768, 1024x1024 | | [Together.ai](/providers/ai-sdk-providers/togetherai#image-models) | `black-forest-labs/FLUX.1-canny` | 512x512, 768x768, 1024x1024 | | [Together.ai](/providers/ai-sdk-providers/togetherai#image-models) | `black-forest-labs/FLUX.1-depth` | 512x512, 768x768, 1024x1024 | | [Together.ai](/providers/ai-sdk-providers/togetherai#image-models) | `black-forest-labs/FLUX.1-redux` | 512x512, 768x768, 1024x1024 | | [Together.ai](/providers/ai-sdk-providers/togetherai#image-models) | `black-forest-labs/FLUX.1.1-pro` | 512x512, 768x768, 1024x1024 | | [Together.ai](/providers/ai-sdk-providers/togetherai#image-models) | `black-forest-labs/FLUX.1-pro` | 512x512, 768x768, 1024x1024 | | [Together.ai](/providers/ai-sdk-providers/togetherai#image-models) | `black-forest-labs/FLUX.1-schnell-Free` | 512x512, 768x768, 1024x1024 | | [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-2-pro` | From 3:7 (portrait) to 7:3 (landscape) | | [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-2-max` | From 3:7 (portrait) to 7:3 (landscape) | | [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-2-flex` | From 3:7 (portrait) to 7:3 (landscape) | | [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-2-klein-9b` | From 3:7 (portrait) to 7:3 (landscape) | | [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-2-klein-4b` | From 3:7 (portrait) to 7:3 (landscape) | | [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-kontext-pro` | From 3:7 (portrait) to 7:3 (landscape) | | [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-kontext-max` | From 3:7 (portrait) to 7:3 (landscape) | | [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-pro-1.1-ultra` | From 3:7 (portrait) to 7:3 (landscape) | | [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-pro-1.1` | From 3:7 (portrait) to 7:3 (landscape) | | [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#image-models) | `flux-pro-1.0-fill` | From 3:7 (portrait) to 7:3 (landscape) | Above are a small subset of the image models supported by the AI SDK providers. For more, see the respective provider documentation. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Lifecycle Callbacks description: Observe AI SDK lifecycle events in generateText, streamText, embed, embedMany, rerank, and experimental_decide calls url: "https://ai-sdk.dev/docs/ai-sdk-core/lifecycle-callbacks" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Event callbacks let you run your own code at important points in an AI SDK call. You can attach them directly to `generateText`, `streamText`, `embed`, `embedMany`, `rerank`, and `experimental_decide` calls to observe what happened, record usage, debug multi-step generations, and monitor tool execution. They are especially useful when you want application-specific logic close to the call site: - Log which model, prompt shape, and settings were used for a request. - Record token usage, latency, finish reasons, and warnings for analytics or billing. - Understand how a multi-step tool call moved from model response to tool execution to final answer. - Track tool inputs, tool outputs, execution time, and errors. - Attach your own request, user, tenant, or workflow identifiers through `runtimeContext` and `toolsContext`. For automatic OpenTelemetry instrumentation across your application, use [Telemetry](/docs/ai-sdk-core/telemetry). Use event callbacks when you want to run custom code for a specific AI SDK call. ## Basic Usage Pass callbacks as options to the AI SDK function you are calling: ```tsx {8-18} import { generateText } from 'ai'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'What is the weather in San Francisco?', onStart({ callId, modelId }) { console.log('Generation started', { callId, modelId }); }, onEnd({ callId, usage, finishReason }) { console.log('Generation finished', { callId, finishReason, totalTokens: usage.totalTokens, }); }, }); ``` Callbacks can be synchronous or asynchronous. If a callback throws, the error is caught internally and the AI SDK call continues. Because callbacks run as part of the lifecycle, keep them fast or enqueue expensive work in a background system. ## Use Cases ### Request Logging Use `onStart` and `onEnd` to record one application log for the beginning and end of a call. The `callId` is available across lifecycle events, so you can correlate logs from the same request. ```tsx {8-23} import { generateText } from 'ai'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a short product description for a camping mug.', onStart({ callId, provider, modelId }) { logger.info('ai.request.started', { callId, provider, modelId, }); }, onEnd({ callId, finishReason, usage, warnings }) { logger.info('ai.request.finished', { callId, finishReason, usage, warningCount: warnings?.length ?? 0, }); }, }); ``` This pattern works well for audit logs, internal dashboards, and tracking usage for a particular feature. ### Measuring Model Performance `onLanguageModelCallEnd` runs after a provider response has been normalized and parsed. For `streamText`, the event also includes streaming-specific timing data such as time to first output and gaps between output chunks. ```tsx {8-19} import { streamText } from 'ai'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Explain partial prerendering in two paragraphs.', onLanguageModelCallEnd({ callId, modelId, usage, performance, providerMetadata, }) { metrics.histogram('ai.model.response_time_ms', performance.responseTimeMs, { callId, modelId, }); metrics.gauge('ai.model.tokens_per_second', { output: performance.outputTokensPerSecond, total: performance.effectiveTotalTokensPerSecond, tokens: usage.totalTokens, }); logger.info('ai.model.provider_metadata', { callId, providerMetadata, }); }, }); for await (const textPart of result.textStream) { process.stdout.write(textPart); } ``` Use model-call events when you want to measure provider work specifically. Use step events when you want timing that includes SDK-managed work such as local tool execution. ### Debugging Multi-Step Tool Calls When you use tools with `generateText` or `streamText`, a single user request can involve multiple model calls. Each model call is a step. The model may call a tool in one step, receive the tool result, and then produce a final answer in the next step. ```tsx {17-31} import { generateText, isStepCount, tool } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", stopWhen: isStepCount(5), prompt: 'What is the weather in San Francisco?', tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string() }), execute: async ({ location }) => getWeather(location), }), }, onStepStart({ stepNumber, messages, steps }) { console.log(`Step ${stepNumber} started`, { messageCount: messages.length, previousSteps: steps.length, }); }, onStepEnd({ stepNumber, finishReason, toolCalls, usage, performance }) { console.log(`Step ${stepNumber} finished`, { finishReason, toolCalls: toolCalls.map(toolCall => toolCall.toolName), totalTokens: usage.totalTokens, stepTimeMs: performance.stepTimeMs, }); }, }); ``` This helps answer questions such as: - Did the model call a tool or answer directly? - How many steps did the request take? - Which step used the most tokens? - Did time go to the model response or to local tool execution? ### Monitoring Tool Execution Tool execution callbacks run around the tool's `execute` function. Use them to record tool usage, latency, successful results, and tool errors. ```tsx {19-36} import { generateText, tool } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Find flights from SFO to JFK tomorrow morning.', tools: { searchFlights: tool({ description: 'Search available flights', inputSchema: z.object({ origin: z.string(), destination: z.string(), }), execute: async input => searchFlights(input), }), }, onToolExecutionStart({ callId, toolCall }) { logger.info('ai.tool.started', { callId, toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, input: toolCall.input, }); }, onToolExecutionEnd({ callId, toolCall, toolExecutionMs, toolOutput }) { logger.info('ai.tool.finished', { callId, toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, durationMs: toolExecutionMs, success: toolOutput.type === 'tool-result', }); }, }); ``` `toolOutput` is a discriminated union. When `toolOutput.type` is `'tool-result'`, the output is available on `toolOutput.output`. When it is `'tool-error'`, the error is available on `toolOutput.error`. ### Observing Embeddings and Reranking Embedding and reranking callbacks are simpler: they expose `onStart` and `onEnd` around the operation. This is useful for retrieval pipelines where you want to understand how often you are embedding, how many values you embed, and how reranking changes result sets. ```tsx {10-24,32-37} import { embedMany, rerank } from 'ai'; import { cohere } from '@ai-sdk/cohere'; const values = ['sunny day at the beach', 'rainy afternoon in the city']; const { embeddings } = await embedMany({ model: 'openai/text-embedding-3-small', values, onStart({ callId, operationId, modelId, value }) { logger.info('ai.embedding.started', { callId, operationId, modelId, valueCount: Array.isArray(value) ? value.length : 1, }); }, onEnd({ callId, usage }) { logger.info('ai.embedding.finished', { callId, tokens: usage.tokens, }); }, }); const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: values, query: 'talk about rain', onEnd({ callId, ranking }) { logger.info('ai.rerank.finished', { callId, topResult: ranking[0], }); }, }); ``` ## Generation Lifecycle `generateText` and `streamText` expose the richest lifecycle because they can involve prompts, model calls, tool calls, and multiple steps. A typical single-step generation runs in this order: 1. `onStart` 2. `onStepStart` 3. `onLanguageModelCallStart` 4. `onLanguageModelCallEnd` 5. `onStepEnd` 6. `onEnd` A multi-step generation with local tool execution usually runs like this: 1. `onStart` 2. `onStepStart` 3. `onLanguageModelCallStart` 4. `onLanguageModelCallEnd` 5. `onToolExecutionStart` 6. `onToolExecutionEnd` 7. `onStepEnd` 8. Repeat step callbacks until the stop condition is met 9. `onEnd` `onStepStart` and `onStepEnd` describe the full step. `onLanguageModelCallStart` and `onLanguageModelCallEnd` describe only the model call inside that step. This distinction matters when the step includes local tool execution: the step duration can be longer than the model response duration. ## Runtime and Tool Context Generation and step lifecycle callbacks receive the full `runtimeContext` and `toolsContext` values that flow through the call. Tool-execution callbacks receive the scoped `toolContext` for the tool being executed. This makes callbacks useful for attaching application context without changing prompts or tool inputs. ```tsx {8-24,26-39} import { generateText, tool } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Check the order status.', runtimeContext: { requestId: 'req_123', tenantId: 'tenant_abc', }, tools: { getOrderStatus: tool({ inputSchema: z.object({ orderId: z.string() }), contextSchema: z.object({ region: z.string() }), execute: async ({ orderId }, { context }) => getOrderStatus(orderId, context.region), }), }, toolsContext: { getOrderStatus: { region: 'us-east-1', }, }, onStart({ callId, runtimeContext }) { logger.info('ai.request.started', { callId, requestId: runtimeContext.requestId, tenantId: runtimeContext.tenantId, }); }, onToolExecutionStart({ toolCall, toolContext }) { logger.info('ai.tool.started', { toolName: toolCall.toolName, region: toolContext.region, }); }, }); ``` Telemetry integrations can filter `runtimeContext` and `toolsContext` before exporting them. Generation and step callbacks receive the full context objects; tool-execution callbacks receive scoped tool context. Be careful not to log secrets or sensitive user data from callbacks. ## Available Callbacks ### `generateText` and `streamText` - `onStart` (`(event: GenerateTextStartEvent) => void | Promise`): Called once when the generation operation begins, before any model calls. - `onStepStart` (`(event: GenerateTextStepStartEvent) => void | Promise`): Called before each generation step. Each step represents one model call and any SDK-managed work around it. - `onLanguageModelCallStart` (`(event: LanguageModelCallStartEvent) => void | Promise`): Called immediately before the provider model call begins. Scoped to model work only. - `onLanguageModelCallEnd` (`(event: LanguageModelCallEndEvent) => void | Promise`): Called after the model response has been normalized and parsed, before local tool execution begins. - `onToolExecutionStart` (`(event: ToolExecutionStartEvent) => void | Promise`): Called before a local tool's \`execute\` function runs. - `onToolExecutionEnd` (`(event: ToolExecutionEndEvent) => void | Promise`): Called after a local tool's \`execute\` function completes or errors. - `onStepEnd` (`(event: GenerateTextStepEndEvent) => void | Promise`): Called after each generation step completes. Receives the step result, including usage and performance. - `onEnd` (`(event: GenerateTextEndEvent) => void | Promise`): Called once when the full generation completes. Receives final output and aggregated usage across all steps. `onStepFinish` is deprecated. Use `onStepEnd` for new code. ### `embed` and `embedMany` - `onStart` (`(event: EmbedStartEvent) => void | Promise`): Called when the embedding operation begins, before the embedding model is called. - `onEnd` (`(event: EmbedEndEvent) => void | Promise`): Called when the embedding operation completes, after the embedding model returns. ### `rerank` - `onStart` (`(event: RerankStartEvent) => void | Promise`): Called when the reranking operation begins, before the reranking model is called. - `onEnd` (`(event: RerankEndEvent) => void | Promise`): Called when the reranking operation completes, after the reranking model returns. ### `experimental_decide` - `onStart` (`(event: Experimental_DecideStartEvent) => void | Promise`): Called when the decision operation begins, before the decision model is called. - `onEnd` (`(event: Experimental_DecideEndEvent) => void | Promise`): Called when the decision operation completes successfully. ## Event Data Reference The exact event data depends on the callback. The tables below summarize the fields you will most commonly use. ### Text Generation Events #### onStart Called once before any model calls are made. - `callId` (`string`): Unique identifier for this generation call. - `operationId` (`string`): Operation type, such as 'ai.generateText' or 'ai.streamText'. - `provider` (`string`): Provider identifier for the resolved model. - `modelId` (`string`): Model identifier for the resolved model. - `messages` (`Array`): Messages for this generation. - `instructions` (`Instructions | undefined`): Instructions provided to the model. - `tools` (`ToolSet | undefined`): Tools available for this generation. - `toolChoice` (`ToolChoice | undefined`): Tool choice strategy for this generation. - `activeTools` (`ActiveTools`): Limits which tools are available for the model to call. - `maxRetries` (`number`): Maximum number of retries for failed requests. - `timeout` (`TimeoutConfiguration | undefined`): Timeout configuration for the generation. - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `providerOptions` (`ProviderOptions | undefined`): Provider-specific options. - `runtimeContext` (`CONTEXT`): User-defined runtime context for the generation. - `toolsContext` (`InferToolSetContext`): Per-tool context map passed via \`toolsContext\`. #### onStepStart Called before each step begins. - `callId` (`string`): Unique identifier for this generation call. - `stepNumber` (`number`): Zero-based index of the current step. - `provider` (`string`): Provider identifier for the resolved model. - `modelId` (`string`): Model identifier for the resolved model. - `messages` (`Array`): Messages that will be sent to the model for this step. - `tools` (`ToolSet | undefined`): Tools available for this generation. - `activeTools` (`ActiveTools`): Limits which tools are available for this step. - `steps` (`ReadonlyArray`): Results from previous steps. Empty for the first step. - `providerOptions` (`ProviderOptions | undefined`): Provider-specific options for this step. - `runtimeContext` (`CONTEXT`): Runtime context for this step. May be updated from \`prepareStep\` between steps. - `toolsContext` (`InferToolSetContext`): Per-tool context map for this step. May be updated from \`prepareStep\` between steps. #### onLanguageModelCallStart Called immediately before the provider model call begins. - `callId` (`string`): Unique identifier for this generation call. - `provider` (`string`): Provider identifier for this model call. - `modelId` (`string`): Model identifier for this model call. - `instructions` (`Instructions | undefined`): Instructions that will be sent to the model. - `messages` (`Array`): Messages that will be sent to the model. - `tools` (`ReadonlyArray> | undefined`): Prepared tool definitions for the model call, if any. #### onLanguageModelCallEnd Called after the provider response has been normalized and parsed, before local tool execution begins. - `callId` (`string`): Unique identifier for this generation call. - `provider` (`string`): Provider identifier for this model call. - `modelId` (`string`): Provider-returned model identifier for this model call. - `finishReason` (`FinishReason`): Unified reason why the model call finished. - `usage` (`LanguageModelUsage`): Token usage reported by the model call. - `content` (`ReadonlyArray>`): Content parts produced by the model call. - `responseId` (`string`): Provider-returned response ID for this model call. - `providerMetadata` (`ProviderMetadata | undefined`): Provider-specific metadata for this model call, when returned by the provider. - `performance` (`LanguageModelCallPerformance`): Timing and throughput metrics, including response time, tokens per second, and streaming timing when available. #### onToolExecutionStart Called before a local tool's `execute` function runs. - `callId` (`string`): Unique identifier for this generation call. - `toolCall` (`TypedToolCall`): The tool call that is about to execute, including \`toolCallId\`, \`toolName\`, and \`input\`. - `messages` (`Array`): Messages sent to the model to initiate the response that contained the tool call. Does not include the system prompt or the assistant response that contained the tool call. - `toolContext` (`InferToolContext`): Tool-specific context object for the tool call. #### onToolExecutionEnd Called after a local tool's `execute` function completes or errors. - `callId` (`string`): Unique identifier for this generation call. - `toolCall` (`TypedToolCall`): The tool call that completed. - `toolExecutionMs` (`number`): Execution time of the tool call in milliseconds. - `messages` (`Array`): Messages sent to the model to initiate the response that contained the tool call. Does not include the system prompt or the assistant response that contained the tool call. - `toolContext` (`InferToolContext`): Tool-specific context object for the completed tool call. - `toolOutput` (`{ type: 'tool-result'; output: unknown } | { type: 'tool-error'; error: unknown }`): Discriminated union representing either a successful tool result or a tool error. #### onStepEnd Called after each step completes. The event is the full `StepResult` for that step. - `callId` (`string`): Unique identifier for this generation call. - `stepNumber` (`number`): Zero-based index of the completed step. - `model` (`{ provider: string; modelId: string }`): Information about the model that produced this step. - `content` (`Array`): Content generated in this step. - `text` (`string`): Text generated in this step. - `toolCalls` (`Array`): Tool calls made in this step. - `toolResults` (`Array`): Tool results produced in this step. - `finishReason` (`FinishReason`): Unified reason why the step finished. - `usage` (`LanguageModelUsage`): Token usage for this step. - `performance` (`StepResultPerformance`): Timing and throughput metrics for this step, including model response time and tool execution time. - `warnings` (`CallWarning[] | undefined`): Warnings from the model provider. - `request` (`LanguageModelRequestMetadata`): Request metadata, including request body and request messages when included. - `response` (`LanguageModelResponseMetadata`): Response metadata, including response headers, body, and messages when included. - `runtimeContext` (`CONTEXT`): Runtime context for this step. - `toolsContext` (`InferToolSetContext`): Per-tool context map for this step. #### onEnd Called once when the full generation completes. - `callId` (`string`): Unique identifier for this generation call. - `steps` (`Array`): Results from all steps in the generation. - `finalStep` (`StepResult`): The final step. This is a shortcut for \`steps.at(-1)\`. - `responseMessages` (`Array`): Response messages generated during the call. - `content` (`Array`): Content generated across all steps. - `text` (`string`): Text generated in the final step. - `toolCalls` (`Array`): Tool calls made across all steps. - `toolResults` (`Array`): Tool results produced across all steps. - `finishReason` (`FinishReason`): Unified reason why the final step finished. - `usage` (`LanguageModelUsage`): Aggregated token usage across all steps. - `warnings` (`CallWarning[] | undefined`): Warnings from the model provider across all steps. ### Embedding Events `embed` and `embedMany` share the same event interfaces. Use `operationId` to distinguish `'ai.embed'` from `'ai.embedMany'`. For `embed`, `value` is a single string. For `embedMany`, `value` is an array of strings. #### onStart - `callId` (`string`): Unique identifier for this embedding call. - `operationId` (`string`): Operation type, such as 'ai.embed' or 'ai.embedMany'. - `provider` (`string`): Provider identifier for the embedding model. - `modelId` (`string`): Embedding model identifier. - `value` (`string | Array`): Value or values being embedded. - `maxRetries` (`number`): Maximum number of retries for failed requests. - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `providerOptions` (`ProviderOptions | undefined`): Provider-specific options. #### onEnd - `callId` (`string`): Unique identifier for this embedding call. - `operationId` (`string`): Operation type, such as 'ai.embed' or 'ai.embedMany'. - `provider` (`string`): Provider identifier for the embedding model. - `modelId` (`string`): Embedding model identifier. - `value` (`string | Array`): Value or values that were embedded. - `embedding` (`Embedding | Array`): Resulting embedding or embeddings. - `usage` (`EmbeddingModelUsage`): Token usage for the embedding operation. - `warnings` (`Array`): Warnings from the embedding model. - `providerMetadata` (`ProviderMetadata | undefined`): Optional provider-specific metadata. - `response` (`{ headers?: Record; body?: unknown } | Array<{ headers?: Record; body?: unknown } | undefined> | undefined`): Response data. For \`embedMany\`, this can be one response per chunk. ### Rerank Events #### onStart - `callId` (`string`): Unique identifier for this rerank call. - `operationId` (`string`): Operation type, 'ai.rerank'. - `provider` (`string`): Provider identifier for the reranking model. - `modelId` (`string`): Reranking model identifier. - `documents` (`Array`): Documents being reranked. - `query` (`string`): Query to rerank the documents against. - `topN` (`number | undefined`): Number of top documents to return. - `maxRetries` (`number`): Maximum number of retries for failed requests. - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `providerOptions` (`ProviderOptions | undefined`): Provider-specific options. #### onEnd - `callId` (`string`): Unique identifier for this rerank call. - `operationId` (`string`): Operation type, 'ai.rerank'. - `provider` (`string`): Provider identifier for the reranking model. - `modelId` (`string`): Reranking model identifier. - `documents` (`Array`): Documents that were reranked. - `query` (`string`): Query the documents were reranked against. - `ranking` (`Array<{ originalIndex: number; score: number; document: JSONObject | string }>`): Reranked results sorted by relevance score in descending order. - `warnings` (`Array`): Warnings from the reranking model. - `providerMetadata` (`ProviderMetadata | undefined`): Optional provider-specific metadata. - `response` (`{ id?: string; timestamp: Date; modelId: string; headers?: Record; body?: unknown }`): Response data including headers and body. ### Decision Events Decision callbacks are experimental. Their exported types are `Experimental_DecideStartEvent` and `Experimental_DecideEndEvent`. #### onStart - `callId` (`string`): Unique identifier for this decision call. - `operationId` (`'ai.decide'`): Decision operation identifier. - `runtimeContext` (`RUNTIME_CONTEXT`): Full user-defined runtime context. - `provider` (`string`): Provider identifier for the decision model. - `modelId` (`string`): Decision model identifier. - `state` (`string | object | array`): Shared state used to make decisions. - `questions` (`Readonly>`): Questions to decide against the shared state. - `maxRetries` (`number`): Maximum number of retries for the model call. - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `providerOptions` (`ProviderOptions`): Provider-specific options. #### onEnd Includes every `onStart` field plus: - `answers` (`Record`): Exactly one typed answer per question ID. - `usage` (`{ inputTokens: number | undefined; outputTokens: number | undefined; totalTokens: number | undefined }`): Token usage for the decision operation. - `warnings` (`Array`): Warnings from the decision model. - `rounding` (`{ probabilityDecimals?: number; scoreDecimals?: number } | undefined`): Provider-declared decimal precision for probabilities and scores. - `providerMetadata` (`ProviderMetadata | undefined`): Optional provider-specific metadata. - `response` (`{ id?: string; timestamp: Date; modelId: string; headers?: Record; body?: unknown }`): Response metadata including the resolved model and timestamp. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: MCP Apps description: Learn how to connect to MCP Apps and render interactive tool UIs with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/mcp-apps" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). MCP Apps extend [Model Context Protocol (MCP)](/docs/ai-sdk-core/mcp-tools) tools with interactive UI resources. The model still calls ordinary MCP tools, but tools can point to a `ui://` resource containing HTML that your app renders in a sandboxed iframe. The AI SDK provides two pieces for building MCP Apps hosts: - [`@ai-sdk/mcp`](/docs/reference/ai-sdk-core/mcp-apps) helpers for advertising MCP Apps support, filtering model-visible and app-visible tools, and reading `ui://` resources. - [`@ai-sdk/react`](/docs/reference/ai-sdk-ui/mcp-app-renderer) components for rendering the app iframe and bridging MCP Apps JSON-RPC messages. ## Host Flow An MCP Apps host usually does the following: 1. Connect to the MCP server with MCP Apps client capabilities. 2. List tools and split them by MCP Apps visibility. 3. Pass only model-visible tools to `streamText` or `generateText`. 4. Read the app's `ui://` resource when a tool part includes MCP App metadata. 5. Render the HTML resource in a sandboxed iframe. 6. Proxy allowed iframe requests, such as app-visible `tools/call`, back to the MCP server. ## Connect With MCP Apps Support Use `mcpAppClientCapabilities` when creating the MCP client. This advertises that your host can render `text/html;profile=mcp-app` resources. ```ts import { createMCPClient, mcpAppClientCapabilities } from '@ai-sdk/mcp'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; export function createMCPAppsClient(origin: string) { return createMCPClient({ transport: new StreamableHTTPClientTransport(new URL('/mcp', origin)), clientName: 'my-mcp-apps-host', capabilities: mcpAppClientCapabilities, }); } ``` Only advertise these capabilities if your host can fetch and render MCP App resources safely. ## Expose Only Model-Visible Tools MCP Apps tools can declare `_meta.ui.visibility`. Tools with `"model"` visibility can be passed to the model. Tools with only `"app"` visibility should be kept for iframe requests and not exposed to the model. ```ts title="app/api/chat/route.ts" import { splitMCPAppTools } from '@ai-sdk/mcp'; import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, } from 'ai'; import { createMCPAppsClient } from './mcp-client'; import { openai } from '@ai-sdk/openai'; export async function POST(req: Request) { const requestUrl = new URL(req.url); const client = await createMCPAppsClient(requestUrl.origin); const { messages } = await req.json(); try { const definitions = await client.listTools(); const { modelVisible } = splitMCPAppTools(definitions); const tools = client.toolsFromDefinitions(modelVisible); const result = streamText({ model: openai('gpt-6-luna'), tools, messages: await convertToModelMessages(messages), onEnd: async () => { await client.close(); }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } catch (error) { await client.close(); throw error; } } ``` When the model calls an app-backed tool, the MCP client preserves the app metadata on the tool UI part. The React renderer uses that metadata to decide whether a tool part has an MCP App. ## Read App Resources Use `readMCPAppResource` to read and normalize an app resource before sending it to the browser host. ```ts title="app/api/mcp-app-host/read-resource/route.ts" import { readMCPAppResource } from '@ai-sdk/mcp'; import { createMCPAppsClient } from '../../chat/mcp-client'; export async function POST(req: Request) { const requestUrl = new URL(req.url); const { uri } = await req.json(); const client = await createMCPAppsClient(requestUrl.origin); try { return Response.json(await readMCPAppResource({ client, uri })); } finally { await client.close(); } } ``` `readMCPAppResource` verifies the resource uses a `ui://` URI, requires the MCP Apps MIME type, decodes text or base64 resource contents, and returns the HTML plus rendering metadata such as CSP and permissions. ## Proxy App-Visible Tool Calls The iframe cannot connect directly to your MCP server. It sends JSON-RPC messages to your host, and your host decides what is allowed. For app-initiated tool calls, validate that the requested tool is app-visible before calling the MCP server. ```ts title="app/api/mcp-app-host/call-tool/route.ts" import { splitMCPAppTools } from '@ai-sdk/mcp'; import { createMCPAppsClient } from '../../chat/mcp-client'; export async function POST(req: Request) { const requestUrl = new URL(req.url); const { name, arguments: toolArguments } = await req.json(); const client = await createMCPAppsClient(requestUrl.origin); try { const { appVisible } = splitMCPAppTools(await client.listTools()); const isAllowed = appVisible.tools.some(tool => tool.name === name); if (!isAllowed) { return Response.json( { error: 'Tool is not app-visible' }, { status: 403 }, ); } return Response.json( await client.callTool({ name, arguments: toolArguments ?? {}, }), ); } finally { await client.close(); } } ``` In production, add any policy and user approval checks your app needs before forwarding iframe requests. ## Render With React In your React chat UI, render normal message parts as usual and pass tool parts to `experimental_MCPAppRenderer`. `experimental_MCPAppRenderer` is experimental and may change in a future release. ```tsx title="app/page.tsx" 'use client'; import { experimental_MCPAppRenderer as MCPAppRenderer, useChat, type MCPAppBridgeHandlers, type MCPAppMetadata, type MCPAppResource, type MCPAppSandboxConfig, } from '@ai-sdk/react'; import { DefaultChatTransport, isToolUIPart } from 'ai'; const sandbox = { url: '/mcp-app-sandbox', className: 'h-80 w-full rounded-lg border', style: { border: 0 }, } satisfies MCPAppSandboxConfig; async function loadResource(app: MCPAppMetadata): Promise { const response = await fetch('/api/mcp-app-host/read-resource', { method: 'POST', body: JSON.stringify({ uri: app.resourceUri }), }); if (!response.ok) { throw new Error('Failed to load MCP App resource'); } return response.json(); } const handlers: MCPAppBridgeHandlers = { callTool: params => fetch('/api/mcp-app-host/call-tool', { method: 'POST', body: JSON.stringify(params), }).then(response => response.json()), openLink: ({ url }) => { window.open(url, '_blank', 'noopener,noreferrer'); return {}; }, }; export default function Chat() { const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat' }), }); return ( <> {messages.map(message => message.parts.map((part, index) => { if (part.type === 'text') { return
{part.text}
; } if (isToolUIPart(part)) { return ( Loading MCP App...} /> ); } return null; }), )} ); } ``` For ordinary tools, `experimental_MCPAppRenderer` renders the provided `fallback` (or nothing when no fallback is provided). For app-backed tools, it loads the resource, creates the sandbox bridge, sends tool input and result notifications to the iframe, and forwards supported app requests through your handlers. ## Best Practices - Treat MCP App HTML as untrusted content. Render it in a sandboxed iframe, ideally through a sandbox proxy route on a separate origin. - Never pass app-only tools to the model. Use `splitMCPAppTools` and expose only `modelVisible` tools. - Validate every iframe request on the server before calling `client.callTool`. - Cache app resources by `resourceUri` so repeated tool calls do not refetch identical HTML. - Keep tool `content` and `structuredContent` useful without the UI, so text-only hosts still work. - Close short-lived MCP clients when the response or host request finishes. ## Reference - [MCP Apps helpers](/docs/reference/ai-sdk-core/mcp-apps) - [MCP App Renderer](/docs/reference/ai-sdk-ui/mcp-app-renderer) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: MCP Events description: Understand the MCP webhook events contract and the AI SDK APIs for subscribing, verifying, and receiving events. url: "https://ai-sdk.dev/docs/ai-sdk-core/mcp-events" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). MCP events let a server notify your application when something changes, such as a new document comment. Your MCP client manages the subscription; your webhook endpoint receives the events. These APIs are experimental and implement the webhook portion of the [MCP Events design draft](https://github.com/modelcontextprotocol/experimental-ext-triggers-events/blob/main/docs/design-sketch-proposal.md). Polling and push-stream delivery are not supported. ## Choose who manages subscriptions Use `experimental_events: { adapter }` when a backend manages subscriptions, webhook delivery and renewal. Supply an adapter whose `createAdapter({ transport })` receives the MCP transport type and URL (or just the type for custom transports) and returns bound managed watch operations. No AI SDK event store is required. A backend SDK helper can return this adapter directly, so callers do not need an inline `createAdapter` wrapper. See [managed subscriptions](/docs/reference/ai-sdk-core/mcp-events#managed-subscriptions). Use `experimental_events: { store }` when your application directly registers MCP webhooks, persists their secrets and schedules renewal. The following flow describes this direct mode. Event catalog discovery works in either mode, and without either configuration. ## How MCP events work 1. The client sends `events/subscribe` with the event name, filters, webhook URL, and signing secret. Optional `ttlMs` and `cursor` parameters set the requested lifetime and replay position. 2. The server can send a signed challenge to verify the webhook before accepting the subscription. After verification, it returns the subscription ID, `refreshBefore`, `cursor`, and `truncated`. 3. As events occur, the server sends signed POST requests to the webhook. The webhook verifies each signature, handles the event, and acknowledges delivery. 4. The client renews the subscription before `refreshBefore` by sending `events/subscribe` with the same identity, or stops it with `events/unsubscribe`. Subscriptions with a finite lifetime expire if they are not renewed. Your application decides what to do with each event. Deliveries may be duplicated or reordered, so use the subscription ID and event ID to deduplicate processing. ## Using MCP Events Create an MCP client with `createMCPClient` and manage subscriptions through `client.experimental_events`. The following methods are experimental: | Method | Description | | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | [`list()`](/docs/reference/ai-sdk-core/mcp-events#listing-events) | List event names, schemas, and delivery modes with `events/list`. | | [`subscribe(options)`](/docs/reference/ai-sdk-core/mcp-events#subscribing-to-events) | Save the signing secret, send `events/subscribe`, and return the subscription result. | | [`refresh({ id })`](/docs/reference/ai-sdk-core/mcp-events#refreshing-a-subscription) | Renew an existing subscription with `events/subscribe`. | | [`unsubscribe({ id })`](/docs/reference/ai-sdk-core/mcp-events#unsubscribing-from-events) | Send `events/unsubscribe` and remove the saved subscription after success. | Refresh reuses the saved callback URL, arguments, signing secret, and latest cursor. Your application schedules renewal before `refreshBefore`. To receive events, use [`experimental_createMCPEventWebhook({ store, onEvent })`](/docs/reference/ai-sdk-core/mcp-events#webhook-handler) as your webhook handler. It handles signed verification challenges, verifies incoming event signatures, and passes each event directly to your `onEvent` callback. ## Connect the webhook and client Both use an application-provided `eventStore` implementing [`Experimental_MCPEventStore`](/docs/reference/ai-sdk-core/mcp-events#event-store). This is an AI SDK storage adapter, not an MCP protocol primitive. It privately persists subscription identities, callback URLs, signing secrets, and cursors so both sides can access them. See the [file-store example](https://github.com/vercel/ai/blob/main/examples/mcp/src/events/event-store.ts) for the five methods: `get`, `getById`, `set`, `update`, and `delete`. Use shared durable storage scoped to one MCP server and authenticated principal; `update` must atomically merge the supplied fields. **First, expose the webhook route.** It must be reachable before subscribing. ```ts title="app/api/mcp-events/route.ts" import { experimental_createMCPEventWebhook } from '@ai-sdk/mcp'; import { eventStore } from '@/lib/event-store'; export const POST = experimental_createMCPEventWebhook({ store: eventStore, async onEvent({ subscription, event }) { console.log(subscription.id, event.name, event.data); }, async onGap({ subscription, gap }) { // Some events cannot be replayed; reconcile state or notify the agent. console.log('Replay gap:', subscription.id, gap.cursor, gap.truncated); }, async onTerminated({ subscription, termination }) { // The server ended the subscription; notify the agent of the reason. console.log('Subscription terminated:', subscription.id, termination.error); }, }); ``` The helper verifies signatures and echoes challenges automatically. It invokes `onEvent` without exposing the secret, then returns `204` after successful processing. Processing failures return `503` so the server can retry. In your app, resolve `onEvent` only after processing or durably accepting the event. **Identify duplicate agent runs.** If an agent finishes inside `onEvent` but a later operation throws (for example, a database write), the handler returns `503`. Redelivery can then run the agent again for the same event, producing another `invoke_agent` trace. These spans do not automatically include the MCP server or event identity. With an [OpenTelemetry tracer provider configured](/docs/ai-sdk-core/telemetry#enabling-telemetry), use this pattern inside `onEvent` to attach that identity to agent spans. Here, `model` is your configured language model, and `subscription` and `event` are the callback arguments: ```ts import { OpenTelemetry } from '@ai-sdk/otel'; import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model, runtimeContext: { mcpServer: 'comments-server', // Identify the server this handler receives from. eventName: event.name, subscriptionId: subscription.id, eventId: event.eventId, }, telemetry: { includeRuntimeContext: { mcpServer: true, eventName: true, subscriptionId: true, eventId: true, }, integrations: new OpenTelemetry({ enrichSpan: ({ runtimeContext }) => ({ 'app.mcp.server': runtimeContext?.mcpServer as string | undefined, 'app.mcp.event_name': runtimeContext?.eventName as string | undefined, 'app.mcp.subscription_id': runtimeContext?.subscriptionId as | string | undefined, 'app.mcp.event_id': runtimeContext?.eventId as string | undefined, }), }), }, }); await agent.generate({ prompt: JSON.stringify(event.data) }); ``` This makes duplicate runs identifiable; your application still needs to deduplicate processing. See [custom OpenTelemetry span attributes](/docs/ai-sdk-core/telemetry#custom-opentelemetry-span-attributes) for more about `enrichSpan`. Two optional callbacks handle signed subscription lifecycle messages: - `onGap({ subscription, messageId, gap })`: some events are no longer available for replay. Reconcile application state or notify the agent. After the callback succeeds, the helper saves `gap.cursor` and `truncated: true`; the subscription stays active and event delivery continues. - `onTerminated({ subscription, messageId, termination })`: the server has ended the subscription. Inspect `termination.error` for the reason and notify the agent. After the callback succeeds, the helper removes the saved subscription. State updates also occur when the corresponding callback is omitted. Callback or storage failures return `503` for retry. Resolve callbacks only after processing or durable acceptance, and deduplicate by subscription ID and `messageId` (the signed `webhook-id`). The helper does not run or notify an agent automatically. **Then subscribe using the same store.** ```ts import { createMCPClient } from '@ai-sdk/mcp'; import { eventStore } from '@/lib/event-store'; const client = await createMCPClient({ transport: { type: 'http', url: 'https://service.example/mcp' }, experimental_events: { store: eventStore }, }); try { const { events } = await client.experimental_events.list(); console.log(events.map(event => event.name)); const subscription = await client.experimental_events.subscribe({ name: 'comment.created', arguments: { document_id: 'doc_123' }, delivery: { mode: 'webhook', url: 'https://my-app.example/api/mcp-events', }, ttlMs: 60 * 60 * 1000, }); // Save the ID and schedule renewal before the server's granted deadline. console.log(subscription.id, subscription.refreshBefore); } finally { await client.close(); // Webhook delivery continues until unsubscribe or expiry. } ``` After catalog discovery and before sending `events/subscribe`, the SDK generates and saves `delivery.secret`. It appends a `mcp_event_subscription` query parameter to the callback URL so the webhook can find that secret even while verification is pending. Preserve this parameter and pass the raw request body to the handler. ## Runnable example For a step-by-step walkthrough of receiving events and choosing a subscription backend, see the [MCP events recipe](/cookbook/node/mcp-events). The [local example](https://github.com/vercel/ai/tree/main/examples/mcp/src/events) uses `@modelcontextprotocol/server` to emit a comment and the AI SDK to subscribe, verify, and receive it. It needs no model API key. Local HTTP callbacks require `delivery.allowInsecureLocalhost: true`; production callbacks use HTTPS. ## API Reference - [Event discovery and subscription methods](/docs/reference/ai-sdk-core/mcp-events) - [Webhook handler](/docs/reference/ai-sdk-core/mcp-events#webhook-handler) - [Event types and store interface](/docs/reference/ai-sdk-core/mcp-events#event-types) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Model Context Protocol (MCP) description: Learn how to connect to Model Context Protocol (MCP) servers and use their tools with AI SDK Core. url: "https://ai-sdk.dev/docs/ai-sdk-core/mcp-tools" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK supports connecting to [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers to access their tools, resources, and prompts. This enables your AI applications to discover and use capabilities across various services through a standardized interface. If you're using OpenAI's Responses API, you can also use the built-in `openai.tools.mcp` tool, which provides direct MCP server integration without needing to convert tools. See the [OpenAI provider documentation](/providers/ai-sdk-providers/openai#mcp-tool) for details. ## Initializing an MCP Client We recommend using HTTP transport (like `StreamableHTTPClientTransport`) for production deployments. The stdio transport should only be used for connecting to local servers as it cannot be deployed to production environments. Create an MCP client using one of the following transport options: - **HTTP transport (Recommended)**: Either configure HTTP directly via the client using `transport: { type: 'http', ... }`, or use MCP's official TypeScript SDK `StreamableHTTPClientTransport` - SSE (Server-Sent Events): An alternative HTTP-based transport - `stdio`: For local development only. Uses standard input/output streams for local MCP servers The AI SDK MCP client supports both legacy initialization-based protocol versions and stateless MCP `2026-07-28`. The built-in stdio transport probes with `server/discover` and automatically falls back to the legacy `initialize` handshake for older servers. Custom transports can opt into this negotiation with `supportsProtocolVersionDiscovery: true`. ### HTTP Transport (Recommended) For production deployments, we recommend using the HTTP transport. You can configure it directly on the client: ```typescript import { createMCPClient } from '@ai-sdk/mcp'; const mcpClient = await createMCPClient({ transport: { type: 'http', url: 'https://your-server.com/mcp', // optional: configure HTTP headers headers: { Authorization: 'Bearer my-api-key' }, // optional: provide an OAuth client provider for automatic authorization authProvider: myOAuthClientProvider, // optional: allow redirect responses (default is 'error' to prevent SSRF) redirect: 'follow', }, }); ``` If a legacy MCP server uses Streamable HTTP sessions, you can reattach to a saved session by restoring both the previous session id and initialize result. MCP `2026-07-28` is stateless and does not use these options: ```typescript import { createMCPClient } from '@ai-sdk/mcp'; const savedSession = await loadMcpSession(); let currentSessionId = savedSession?.sessionId; const mcpClient = await createMCPClient({ transport: { type: 'http', url: 'https://your-server.com/mcp', initialSessionId: savedSession?.sessionId, initialProtocolVersion: savedSession?.initializeResult.protocolVersion, terminateSessionOnClose: false, onSessionIdChange: sessionId => { currentSessionId = sessionId; }, onSessionExpired: sessionId => { if (currentSessionId === sessionId) { currentSessionId = undefined; void clearMcpSession(); } }, }, initialInitializeResult: savedSession?.initializeResult, }); if (currentSessionId) { await saveMcpSession({ sessionId: currentSessionId, initializeResult: mcpClient.initializeResult, }); } ``` When `initialInitializeResult` is provided, `createMCPClient` reuses the cached initialize metadata and does not send another `initialize` request. When `onSessionExpired` is called, the transport has already cleared the session id and the request still fails with the underlying HTTP error. Retry by creating a fresh client without `initialSessionId` or `initialInitializeResult`. Set `terminateSessionOnClose` to `false` when closing only the local client but keeping the MCP session available for a later reattach. Alternatively, you can use `StreamableHTTPClientTransport` from MCP's official TypeScript SDK: ```typescript import { createMCPClient } from '@ai-sdk/mcp'; import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'; const url = new URL('https://your-server.com/mcp'); const mcpClient = await createMCPClient({ transport: new StreamableHTTPClientTransport(url, { sessionId: 'session_123', }), }); ``` ### SSE Transport SSE provides an alternative HTTP-based transport option. Configure it with a `type` and `url` property. You can also provide an `authProvider` for OAuth: ```typescript import { createMCPClient } from '@ai-sdk/mcp'; const mcpClient = await createMCPClient({ transport: { type: 'sse', url: 'https://my-server.com/sse', // optional: configure HTTP headers headers: { Authorization: 'Bearer my-api-key' }, // optional: provide an OAuth client provider for automatic authorization authProvider: myOAuthClientProvider, // optional: allow redirect responses (default is 'error' to prevent SSRF) redirect: 'follow', }, }); ``` ### Stdio Transport (Local Servers) The stdio transport should only be used for local servers. The Stdio transport can be imported from either the MCP SDK or the AI SDK: ```typescript import { createMCPClient } from '@ai-sdk/mcp'; import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; // Or use the AI SDK's stdio transport: // import { Experimental_StdioMCPTransport as StdioClientTransport } from '@ai-sdk/mcp/mcp-stdio'; const mcpClient = await createMCPClient({ transport: new StdioClientTransport({ command: 'node', args: ['src/stdio/dist/server.js'], }), }); ``` ### Custom Transport You can also bring your own transport by implementing the `MCPTransport` interface for specific requirements not covered by the standard transports. The client returned by the `createMCPClient` function is a lightweight client intended for use in tool conversion. It currently does not support all features of the full MCP client, such as automatic session persistence, resumable streams, and receiving notifications. Authorization via OAuth is supported when using the AI SDK MCP HTTP or SSE transports by providing an `authProvider`. ### Shared OAuth Credential Storage When multiple MCP clients or replicas share OAuth credentials, a rotating refresh token can be used concurrently. One refresh may save new tokens while another receives `invalid_grant` for the old refresh token. The optional `OAuthClientProvider.invalidateCredentials` method receives a second argument identifying the tokens used by a failed refresh: ```typescript invalidateCredentials( scope: 'all' | 'client' | 'tokens' | 'verifier', context?: { tokens: OAuthTokens }, ): void | Promise; ``` For `scope === 'tokens'` with a context, delete the credential only if the stored access and refresh tokens still match `context.tokens`. The comparison and deletion must be atomic, using a conditional database operation or a transaction. If they no longer match, leave the newer tokens in storage. The SDK retries authorization and reads the stored tokens again. The SDK also provides this context when invalidating stored tokens that lack an authorization server pin. Other invalidations can omit the context. If an authorization-code exchange returns `invalid_grant`, the SDK propagates the error without invalidating stored credentials or retrying the rejected code. A rejected authorization code does not indicate that existing access or refresh tokens are invalid. ### OAuth Authorization Server Validation Pre-registered confidential clients must be bound to their authorization server before starting OAuth. Set `issuer` on the credentials returned by `clientInformation()` to the issuer of the server that registered the client: ```typescript clientInformation() { return { client_id: process.env.OAUTH_CLIENT_ID!, client_secret: process.env.OAUTH_CLIENT_SECRET!, issuer: 'https://accounts.example.com', }; } ``` The issuer must exactly match the discovered issuer. The SDK rejects a mismatch before redirecting or sending credentials, even if a stored authorization flow pin names the discovered server. Dynamic client registration attaches this binding automatically. Existing complete authorization server bindings on credentials, stored tokens, or `authorizationServerInformation()` are also supported. Without a binding, a pre-registered client secret or custom `addClientAuthentication` handler is rejected, even when `validateAuthorizationServerURL` accepts the selected server. Approving a server does not establish that the credentials belong to it. Do not derive the credential issuer from the MCP server's metadata; configure it from the client's registration. When using MCP OAuth in server-side applications, the MCP server can advertise the OAuth authorization server to use. If you connect to MCP servers outside your control, implement `validateAuthorizationServerURL` on your `authProvider` to allow only the authorization server origins you trust: ```typescript const allowedAuthorizationServerOrigins = new Set([ 'https://accounts.example.com', 'https://tenant.auth0.com', ]); const myOAuthClientProvider = { // ...other OAuthClientProvider methods validateAuthorizationServerURL(serverUrl, authorizationServerUrl) { const origin = new URL(authorizationServerUrl).origin; if (!allowedAuthorizationServerOrigins.has(origin)) { throw new Error( `Unexpected OAuth authorization server for ${serverUrl}: ${origin}`, ); } }, }; ``` This hook is called before the SDK fetches authorization server metadata, so a rejected URL is not requested. The hook is an optional additional restriction; it does not replace the credential binding required for confidential clients. ### OAuth Callback Issuer Validation When completing an OAuth authorization callback, pass the callback's `iss` parameter to `auth` as `callbackIssuer`: ```typescript import { auth } from '@ai-sdk/mcp'; const callbackUrl = new URL(request.url); await auth(myOAuthClientProvider, { serverUrl: 'https://mcp.example.com', authorizationCode: callbackUrl.searchParams.get('code')!, callbackState: callbackUrl.searchParams.get('state') ?? undefined, callbackIssuer: callbackUrl.searchParams.get('iss') ?? undefined, }); ``` When `iss` is present, it must exactly match the discovered authorization server issuer. The authorization code is not exchanged when it does not match. Dynamic client registration requests include an OAuth `application_type`. The client infers `native` for loopback, localhost, and custom-scheme redirects, and `web` for remote HTTP(S) redirects. You can override this by setting `application_type` in the provider's `clientMetadata`. ### Dynamic Client Credential Recovery To let the SDK replace invalid credentials created through dynamic client registration, implement `isClientInformationDynamicallyRegistered` on the OAuth client provider. Return `true` only when the current client information came from a dynamic registration response. Persist this provenance alongside the credentials when the provider is recreated between requests. ```typescript const myOAuthClientProvider = { // ...other OAuthClientProvider methods async saveClientInformation(clientInformation) { await saveClientRecord({ clientInformation, dynamicallyRegistered: true, }); }, async isClientInformationDynamicallyRegistered() { return (await loadClientRecord())?.dynamicallyRegistered === true; }, }; ``` When this method is omitted or returns `false`, client information is treated as pre-registered and is preserved after `invalid_client` and `unauthorized_client` errors. The SDK does not infer provenance from fields such as `redirect_uris`, because pre-registered clients can contain the same metadata as dynamically registered clients. ### Retrying Transient Tool Failures MCP tool calls can fail for transient transport reasons, such as rate limits, temporary overload, or gateway timeouts. You can opt into automatic retries for `tools/call` requests by passing `maxRetries` when creating the client: ```typescript const mcpClient = await createMCPClient({ transport: { type: 'http', url: 'https://your-server.com/mcp', }, maxRetries: 2, }); ``` Retries are disabled by default. Built-in retry matching is intended for transient HTTP and network failures only. JSON-RPC application errors, such as invalid tool arguments, are surfaced immediately without retrying. Successful MCP tool responses with `isError: true` are also returned to the model without retrying. `maxRetries` applies to `tools/call` requests, not to client initialization. The following example identifies potentially transient HTTP initialization failures using the exported `MCPClientError` type guard: ```typescript import { createMCPClient, MCPClientError } from '@ai-sdk/mcp'; try { await createMCPClient({ transport: { type: 'http', url: 'https://your-server.com/mcp', }, }); } catch (error) { if ( !MCPClientError.isInstance(error) || error.code != null || error.statusCode == null || !( error.statusCode === 408 || error.statusCode === 409 || error.statusCode === 429 || error.statusCode >= 500 ) ) { throw error; } // Retry according to your application's backoff policy. } ``` This example only considers HTTP statuses. A missing `statusCode` does not establish that a failure is transient: configuration errors, protocol errors, and cancelled requests can also lack an HTTP status. Network and timeout retries require a separate application policy. OAuth errors such as `UnauthorizedError` and `MCPClientOAuthError` are rethrown by this example. Only enable retries for MCP tools where retrying is safe. Retrying non-idempotent tools, such as tools that send emails or create records, can duplicate side effects. ### Closing the MCP Client After initialization, you should close the MCP client based on your usage pattern: - For short-lived usage (e.g., single requests), close the client when the response is finished - For long-running clients (e.g., command line apps), keep the client open but ensure it's closed when the application terminates When streaming responses, you can close the client when the LLM response has finished. For example, when using `streamText`, you should use the `onEnd` callback: ```typescript const mcpClient = await createMCPClient({ // ... }); const tools = await mcpClient.tools(); const result = await streamText({ model: "anthropic/claude-sonnet-5.5", tools, prompt: 'What is the weather in Brooklyn, New York?', onEnd: async () => { await mcpClient.close(); }, }); ``` When generating responses without streaming, you can use try/finally or cleanup functions in your framework: ```typescript import { createMCPClient, type MCPClient } from '@ai-sdk/mcp'; let mcpClient: MCPClient | undefined; try { mcpClient = await createMCPClient({ // ... }); } finally { await mcpClient?.close(); } ``` ## Using MCP Tools The client's `tools` method acts as an adapter between MCP tools and AI SDK tools. It supports two approaches for working with tool schemas: ### Schema Discovery With schema discovery, all tools offered by the server are automatically listed, and input parameter types are inferred based on the schemas provided by the server: ```typescript const tools = await mcpClient.tools(); ``` This approach is simpler to implement and automatically stays in sync with server changes. However, you won't have TypeScript type safety during development, and all tools from the server will be loaded ### Tool Annotations and Approval MCP servers can describe tool behavior with annotations such as `readOnlyHint`, `destructiveHint`, `idempotentHint`, and `openWorldHint`. The MCP client exposes these annotations on each tool's `metadata.annotations` and on the resulting tool call's `toolMetadata.annotations`. Annotations are untrusted, server-provided hints. The MCP client does not turn them into an approval policy automatically. Applications should combine them with deterministic controls such as tool allowlists, scoped credentials, and their own [`toolApproval`](/docs/agents/tool-approvals) policy. The following conservative policy allows tools to run automatically only when the server explicitly marks them as read-only. Tools with `readOnlyHint: false` or no `readOnlyHint` require user approval: ```typescript import { type McpProviderMetadata } from '@ai-sdk/mcp'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", tools, toolApproval: ({ toolCall }) => { const annotations = ( toolCall.toolMetadata as McpProviderMetadata | undefined )?.annotations; return annotations?.readOnlyHint === true ? 'not-applicable' : { type: 'user-approval', reason: annotations?.destructiveHint === true ? 'The MCP server marks this tool as destructive.' : 'The MCP server does not mark this tool as read-only.', }; }, prompt, }); ``` See the [local tool annotations example](https://github.com/vercel/ai/tree/main/examples/mcp/src/tool-annotations) for a complete annotated MCP server and application-layer approval configuration. Start the server and client in separate terminals: ```bash cd examples/mcp pnpm server:tool-annotations ``` ```bash cd examples/mcp pnpm client:tool-annotations ``` Ask the client to read, delete, or create a note. The read-only tool executes immediately. The destructive and unannotated tools prompt for approval; after you enter `y` or `n`, the client sends the decision back and prints the model's response. ### Schema Definition For better type safety and control, you can define the tools and their input schemas explicitly in your client code: ```typescript import { z } from 'zod'; const tools = await mcpClient.tools({ schemas: { 'get-data': { inputSchema: z.object({ query: z.string().describe('The data query'), format: z.enum(['json', 'text']).optional(), }), }, // For tools with zero inputs, you should use an empty object: 'tool-with-no-args': { inputSchema: z.object({}), }, }, }); ``` This approach provides full TypeScript type safety and IDE autocompletion, letting you catch parameter mismatches during development. When you define `schemas`, the client only pulls the explicitly defined tools, keeping your application focused on the tools it needs ### Typed Tool Outputs When MCP servers return `structuredContent` (per the [MCP specification](https://modelcontextprotocol.io/specification/2026-07-28/server/tools#structured-content)), you can define an `outputSchema` to get typed tool results: ```typescript import { z } from 'zod'; const tools = await mcpClient.tools({ schemas: { 'get-weather': { inputSchema: z.object({ location: z.string(), }), // Define outputSchema for typed results outputSchema: z.object({ temperature: z.number(), conditions: z.string(), humidity: z.number(), }), }, }, }); const result = await tools['get-weather'].execute( { location: 'New York' }, { messages: [], toolCallId: 'weather-1' }, ); console.log(`Temperature: ${result.temperature}°C`); ``` When `outputSchema` is provided: - The client extracts `structuredContent` from the tool result - The output is validated against your schema at runtime - You get full TypeScript type safety for the result If the server doesn't return `structuredContent`, the client falls back to parsing JSON from the text content. If neither is available or validation fails, an error is thrown. If a server returns `structuredContent` without the backwards-compatible `content` field, the client adds a text content block containing the serialized JSON before returning the result. This compatibility behavior handles servers that omit the text mirror recommended by the MCP specification. Without `outputSchema`, the tool returns the raw `CallToolResult` object containing `content` and optional `isError` fields. ## Using MCP Resources According to the [MCP specification](https://modelcontextprotocol.io/docs/learn/server-concepts#resources), resources are **application-driven** data sources that provide context to the model. Unlike tools (which are model-controlled), your application decides when to fetch and pass resources as context. The MCP client provides three methods for working with resources: ### Listing Resources List all available resources from the MCP server: ```typescript const resources = await mcpClient.listResources(); ``` ### Reading Resource Contents Read the contents of a specific resource by its URI: ```typescript const resourceData = await mcpClient.readResource({ uri: 'file:///example/document.txt', }); ``` ### Listing Resource Templates Resource templates are dynamic URI patterns that allow flexible queries. List all available templates: ```typescript const templates = await mcpClient.listResourceTemplates(); ``` ## Using MCP Completions MCP servers can provide autocompletion suggestions for prompt arguments and resource template variables when they advertise the `completions` capability. Use `complete` to ask the server for suggestions based on the current partial argument value: ```typescript const completion = await mcpClient.complete({ ref: { type: 'ref/resource', uri: 'file:///{path}', }, argument: { name: 'path', value: 'doc', }, }); console.log(completion.completion.values); ``` For resource templates or prompts with multiple arguments, pass already resolved values through `context.arguments`: ```typescript const completion = await mcpClient.complete({ ref: { type: 'ref/resource', uri: 'kubernetes://namespaced/{plural}/{namespace}', }, argument: { name: 'namespace', value: 'auth', }, context: { arguments: { plural: 'deployments', }, }, }); ``` If the connected server does not advertise `capabilities.completions`, the client throws an `MCPClientError`. ## Using MCP Prompts MCP Prompts is an experimental feature and may change in the future. According to the MCP specification, prompts are user-controlled templates that servers expose for clients to list and retrieve with optional arguments. ### Listing Prompts ```typescript const prompts = await mcpClient.experimental_listPrompts(); ``` ### Getting a Prompt Retrieve prompt messages, optionally passing arguments defined by the server: ```typescript const prompt = await mcpClient.experimental_getPrompt({ name: 'code_review', arguments: { code: 'function add(a, b) { return a + b; }' }, }); ``` ## Handling Elicitation Requests Elicitation is a mechanism where MCP servers can request additional information from the client during tool execution. For example, a server might need user input to complete a registration form or confirmation for a sensitive operation. It is up to the client application to handle elicitation requests properly. The MCP client simply surfaces these requests from the server to your application code. ### Enabling Elicitation Support To enable elicitation, you need to advertise the capability when creating the MCP client: ```typescript const mcpClient = await createMCPClient({ transport: { type: 'sse', url: 'https://your-server.com/sse', }, capabilities: { elicitation: {}, }, }); ``` ### Registering an Elicitation Handler Use the `onElicitationRequest` method to register a handler that will be called when the server requests input: ```typescript import { ElicitationRequestSchema } from '@ai-sdk/mcp'; mcpClient.onElicitationRequest(ElicitationRequestSchema, async request => { // request.params.message: A message describing what input is needed // request.params.requestedSchema: JSON schema defining the expected input structure // Get input from the user (implement according to your application's needs) const userInput = await getInputFromUser( request.params.message, request.params.requestedSchema, ); // Return the result with one of three actions: return { action: 'accept', // or 'decline' or 'cancel' content: userInput, // only required when action is 'accept' }; }); ``` ### Elicitation Response Actions Your handler must return an object with an `action` field that can be one of: - `'accept'`: User provided the requested information. Must include `content` with the data. - `'decline'`: User chose not to provide the information. - `'cancel'`: User cancelled the operation entirely. ## Detecting tool-definition drift ("rug pull") An MCP server sends tool definitions (name, description, input schema) when your app first connects, and you typically review and approve them at that point. Nothing in the protocol prevents the server from later serving a *different* definition for the same tool name — for example a description carrying injected instructions, or an input schema widened with an extra field. Because the SDK uses whatever tools you pass on each call, a mutated definition returned by a later `mcpClient.tools()` fetch would be used without any comparison to what was approved. This is the MCP ["rug pull"](https://invariantlabs.ai/blog/mcp-security-notification-tool-poisoning-attacks) class of attack. The AI SDK provides two functions to pin the approved definitions and detect changes. `fingerprintTools` digests the server-controlled, security-relevant fields of each tool (string `description`, resolved input schema, and `title`) into a stable map of tool name to digest. `detectToolDrift` diffs two such maps. Your app owns baseline storage and the response to drift (block, force re-approval, or alert): ```typescript import { fingerprintTools, detectToolDrift } from 'ai'; // Trust time (first connect, human-reviewed): capture and persist the baseline. const baseline = await fingerprintTools(await mcpClient.tools()); // Every later fetch, before handing tools to generateText: const tools = await mcpClient.tools(); const drift = detectToolDrift(await fingerprintTools(tools), baseline); if (drift.changed.length || drift.added.length) { // A pinned definition changed, or a new tool appeared. Block, re-approve, // or alert per your policy — do not silently pass `tools` to the model. } ``` This detects mutation of a tool's description, input schema, or title — the prompt-injection and schema-widening vectors. It cannot detect a behavior/endpoint swap where the name, description, and schema are all unchanged, because the tool runs remotely on the MCP server and that change is invisible to the client. Core stays unopinionated: it does not persist baselines or block calls — those are your app's responsibility. ## Examples You can see MCP in action in the following examples: - [Learn to use MCP tools in Node.js](/cookbook/node/mcp-tools) - [Learn to handle MCP elicitation requests in Node.js](/cookbook/node/mcp-elicitation) - [Learn to render MCP Apps](/docs/ai-sdk-core/mcp-apps) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Language Model Middleware description: Learn how to use middleware to enhance the behavior of language models url: "https://ai-sdk.dev/docs/ai-sdk-core/middleware" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Language model middleware is a way to enhance the behavior of language models by intercepting and modifying the calls to the language model. It can be used to add features like guardrails, RAG, caching, and logging in a language model agnostic way. Such middleware can be developed and distributed independently from the language models that they are applied to. ## Using Language Model Middleware You can use language model middleware with the `wrapLanguageModel` function. It takes a language model and a language model middleware and returns a new language model that incorporates the middleware. ```ts import { wrapLanguageModel, streamText } from 'ai'; const wrappedLanguageModel = wrapLanguageModel({ model: yourModel, middleware: yourLanguageModelMiddleware, }); ``` The wrapped language model can be used just like any other language model, e.g. in `streamText`: ```ts {2} const result = streamText({ model: wrappedLanguageModel, prompt: 'What cities are in the United States?', }); ``` ## Multiple middlewares You can provide multiple middlewares to the `wrapLanguageModel` function. The middlewares will be applied in the order they are provided. ```ts const wrappedLanguageModel = wrapLanguageModel({ model: yourModel, middleware: [firstMiddleware, secondMiddleware], }); // applied as: firstMiddleware(secondMiddleware(yourModel)) ``` ## Built-in Middleware The AI SDK comes with several built-in middlewares that you can use to configure language models: - `extractReasoningMiddleware`: Extracts reasoning information from the generated text and exposes it as a `reasoning` property on the result. - `extractJsonMiddleware`: Extracts JSON from text content by stripping markdown code fences. Useful when using `Output.object()` with models that wrap JSON responses in code blocks. - `simulateStreamingMiddleware`: Simulates streaming behavior with responses from non-streaming language models. - `defaultInstructionsMiddleware`: Applies default instructions when a call does not provide its own instructions. - `defaultSettingsMiddleware`: Applies default settings to a language model. - `addToolInputExamplesMiddleware`: Adds tool input examples to tool descriptions for providers that don't natively support the `inputExamples` property. ### Extract Reasoning Some providers and models expose reasoning information in the generated text using special tags, e.g. \ and \. The `extractReasoningMiddleware` function can be used to extract this reasoning information and expose it as a `reasoning` property on the result. ```ts import { wrapLanguageModel, extractReasoningMiddleware } from 'ai'; const model = wrapLanguageModel({ model: yourModel, middleware: extractReasoningMiddleware({ tagName: 'think' }), }); ``` You can then use that enhanced model in functions like `generateText` and `streamText`. The `extractReasoningMiddleware` function also includes a `startWithReasoning` option. When set to `true`, the reasoning tag will be prepended to the generated text. This is useful for models that do not include the reasoning tag at the beginning of the response. For more details, see the [DeepSeek R1 guide](/cookbook/guides/r1#deepseek-r1-middleware). ### Extract JSON Some models wrap JSON responses in markdown code fences (e.g., ` ```json ... ``` `) even when you request structured output. The `extractJsonMiddleware` function strips these code fences from the response, making it compatible with `Output.object()`. ```ts import { wrapLanguageModel, extractJsonMiddleware, Output, generateText, } from 'ai'; import { z } from 'zod'; const model = wrapLanguageModel({ model: yourModel, middleware: extractJsonMiddleware(), }); const result = await generateText({ model, output: Output.object({ schema: z.object({ name: z.string(), ingredients: z.array(z.string()), }), }), prompt: 'Generate a recipe.', }); ``` You can also provide a custom transform function for models that use different formatting: ```ts const model = wrapLanguageModel({ model: yourModel, middleware: extractJsonMiddleware({ transform: text => text.replace(/^PREFIX/, '').replace(/SUFFIX$/, ''), }), }); ``` ### Simulate Streaming The `simulateStreamingMiddleware` function can be used to simulate streaming behavior with responses from non-streaming language models. This is useful when you want to maintain a consistent streaming interface even when using models that only provide complete responses. ```ts import { wrapLanguageModel, simulateStreamingMiddleware } from 'ai'; const model = wrapLanguageModel({ model: yourModel, middleware: simulateStreamingMiddleware(), }); ``` ### Default Settings The `defaultSettingsMiddleware` function can be used to apply default settings to a language model. ```ts import { wrapLanguageModel, defaultSettingsMiddleware } from 'ai'; const model = wrapLanguageModel({ model: yourModel, middleware: defaultSettingsMiddleware({ settings: { temperature: 0.5, maxOutputTokens: 800, providerOptions: { openai: { store: false } }, }, }), }); ``` ### Default Instructions The `defaultInstructionsMiddleware` function applies instructions to calls that do not already contain a system message. Instructions provided directly on a call take precedence over the defaults. ```ts import { wrapLanguageModel, defaultInstructionsMiddleware } from 'ai'; const model = wrapLanguageModel({ model: yourModel, middleware: defaultInstructionsMiddleware({ instructions: 'You are a concise technical assistant.', }), }); ``` The `instructions` option also accepts a `SystemModelMessage` or an array of `SystemModelMessage` objects when you need provider options on an instruction. The middleware treats any system message in the normalized prompt as call-level instructions and does not add the defaults. Only enable `allowSystemInMessages` for trusted message histories, because a system message in that history can override these defaults. ### Add Tool Input Examples The `addToolInputExamplesMiddleware` function adds tool input examples to tool descriptions. This is useful for providers that don't natively support the `inputExamples` property on tools. The middleware serializes the examples into the tool's description text so models can still benefit from seeing example inputs. ```ts import { wrapLanguageModel, addToolInputExamplesMiddleware } from 'ai'; const model = wrapLanguageModel({ model: yourModel, middleware: addToolInputExamplesMiddleware({ prefix: 'Input Examples:', }), }); ``` When you define a tool with `inputExamples`, the middleware will append them to the tool's description: ```ts import { generateText, tool } from 'ai'; import { z } from 'zod'; const result = await generateText({ model, // wrapped model from above tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string(), }), inputExamples: [ { input: { location: 'San Francisco' } }, { input: { location: 'London' } }, ], }), }, prompt: 'What is the weather in Tokyo?', }); ``` The tool description will be transformed to: ``` Get the weather in a location Input Examples: {"location":"San Francisco"} {"location":"London"} ``` #### Options - `prefix` (optional): A prefix text to prepend before the examples. Default: `'Input Examples:'`. - `format` (optional): A custom formatter function for each example. Receives the example object and its index. Default: `JSON.stringify(example.input)`. - `remove` (optional): Whether to remove the `inputExamples` property from the tool after adding them to the description. Default: `true`. ```ts const model = wrapLanguageModel({ model: yourModel, middleware: addToolInputExamplesMiddleware({ prefix: 'Input Examples:', format: (example, index) => `${index + 1}. ${JSON.stringify(example.input)}`, remove: true, }), }); ``` ## Community Middleware The AI SDK provides a Language Model Middleware specification. Community members can develop middleware that adheres to this specification, making it compatible with the AI SDK ecosystem. Here are some community middlewares that you can explore: ### Custom tool call parser The [Custom tool call parser](https://github.com/minpeter/ai-sdk-tool-call-middleware) middleware extends tool call capabilities to models that don't natively support the OpenAI-style `tools` parameter. This includes many self-hosted and third-party models that lack native function calling features. Using this middleware on models that support native function calls may result in unintended performance degradation, so check whether your model supports native function calls before deciding to use it. This middleware enables function calling capabilities by converting function schemas into prompt instructions and parsing the model's responses into structured function calls. It works by transforming the JSON function definitions into natural language instructions the model can understand, then analyzing the generated text to extract function call attempts. This approach allows developers to use the same function calling API across different model providers, even with models that don't natively support the OpenAI-style function calling format, providing a consistent function calling experience regardless of the underlying model implementation. The `@ai-sdk-tool/parser` package offers three middleware variants: - `createToolMiddleware`: A flexible function for creating custom tool call middleware tailored to specific models - `hermesToolMiddleware`: Ready-to-use middleware for Hermes & Qwen format function calls - `gemmaToolMiddleware`: Pre-configured middleware for Gemma 3 model series function call format Here's how you can enable function calls with Gemma models that don't support them natively: ```ts import { wrapLanguageModel } from 'ai'; import { gemmaToolMiddleware } from '@ai-sdk-tool/parser'; const model = wrapLanguageModel({ model: openrouter('google/gemma-3-27b-it'), middleware: gemmaToolMiddleware, }); ``` Find more examples at this [link](https://github.com/minpeter/ai-sdk-tool-call-middleware/tree/main/examples/core/src). ## Implementing Language Model Middleware Implementing language model middleware is advanced functionality and requires a solid understanding of the [language model specification](https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4.ts). You can implement any of the following three function to modify the behavior of the language model: 1. `transformParams`: Transforms the parameters before they are passed to the language model, for both `doGenerate` and `doStream`. 2. `wrapGenerate`: Wraps the `doGenerate` method of the [language model](https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4.ts). You can modify the parameters, call the language model, and modify the result. 3. `wrapStream`: Wraps the `doStream` method of the [language model](https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4.ts). You can modify the parameters, call the language model, and modify the result. Every `LanguageModelV4Middleware` object must set `specificationVersion: 'v4'`. Here are some examples of how to implement language model middleware: ## Examples These examples are not meant to be used in production. They are just to show how you can use middleware to enhance the behavior of language models. ### Logging This example shows how to log the parameters and generated text of a language model call. ```ts import type { LanguageModelV4Middleware, LanguageModelV4StreamPart, } from '@ai-sdk/provider'; export const yourLogMiddleware: LanguageModelV4Middleware = { specificationVersion: 'v4', wrapGenerate: async ({ doGenerate, params }) => { console.log('doGenerate called'); console.log(`params: ${JSON.stringify(params, null, 2)}`); const result = await doGenerate(); const generatedText = result.content .filter(part => part.type === 'text') .map(part => part.text) .join(''); console.log('doGenerate finished'); console.log(`generated text: ${generatedText}`); return result; }, wrapStream: async ({ doStream, params }) => { console.log('doStream called'); console.log(`params: ${JSON.stringify(params, null, 2)}`); const { stream, ...rest } = await doStream(); let generatedText = ''; const textBlocks = new Map(); const transformStream = new TransformStream< LanguageModelV4StreamPart, LanguageModelV4StreamPart >({ transform(chunk, controller) { switch (chunk.type) { case 'text-start': { textBlocks.set(chunk.id, ''); break; } case 'text-delta': { const existing = textBlocks.get(chunk.id) || ''; textBlocks.set(chunk.id, existing + chunk.delta); generatedText += chunk.delta; break; } case 'text-end': { console.log( `Text block ${chunk.id} completed:`, textBlocks.get(chunk.id), ); break; } } controller.enqueue(chunk); }, flush() { console.log('doStream finished'); console.log(`generated text: ${generatedText}`); }, }); return { stream: stream.pipeThrough(transformStream), ...rest, }; }, }; ``` ### Caching This example shows how to build a simple cache for the generated text of a language model call. ```ts import type { LanguageModelV4Middleware } from '@ai-sdk/provider'; const cache = new Map(); export const yourCacheMiddleware: LanguageModelV4Middleware = { specificationVersion: 'v4', wrapGenerate: async ({ doGenerate, params }) => { const cacheKey = JSON.stringify(params); if (cache.has(cacheKey)) { return cache.get(cacheKey); } const result = await doGenerate(); cache.set(cacheKey, result); return result; }, // here you would implement the caching logic for streaming }; ``` ### Retrieval Augmented Generation (RAG) This example shows how to use RAG as middleware. Helper functions like `getLastUserMessageText` and `findSources` are not part of the AI SDK. They are just used in this example to illustrate the concept of RAG. ```ts import type { LanguageModelV4Middleware } from '@ai-sdk/provider'; export const yourRagMiddleware: LanguageModelV4Middleware = { specificationVersion: 'v4', transformParams: async ({ params }) => { const lastUserMessageText = getLastUserMessageText({ prompt: params.prompt, }); if (lastUserMessageText == null) { return params; // do not use RAG (send unmodified parameters) } const instruction = 'Use the following information to answer the question:\n' + findSources({ text: lastUserMessageText }) .map(chunk => JSON.stringify(chunk)) .join('\n'); return addToLastUserMessage({ params, text: instruction }); }, }; ``` ### Guardrails Guard rails are a way to ensure that the generated text of a language model call is safe and appropriate. This example shows how to use guardrails as middleware. ```ts import type { LanguageModelV4Middleware, LanguageModelV4StreamPart, } from '@ai-sdk/provider'; const redactText = (text: string) => text.replace(/badword/g, ''); export const yourGuardrailMiddleware: LanguageModelV4Middleware = { specificationVersion: 'v4', wrapGenerate: async ({ doGenerate }) => { const result = await doGenerate(); // filtering approach, e.g. for PII or other sensitive information: const content = result.content.map(part => part.type === 'text' ? { ...part, text: redactText(part.text) } : part, ); return { ...result, content }; }, wrapStream: async ({ doStream }) => { const { stream, ...rest } = await doStream(); // Keep a separate buffer for each text block in the stream. const buffers = new Map(); const transformStream = new TransformStream< LanguageModelV4StreamPart, LanguageModelV4StreamPart >({ transform(chunk, controller) { if (chunk.type === 'text-start') { buffers.set(chunk.id, ''); controller.enqueue(chunk); return; } if (chunk.type === 'text-delta') { buffers.set(chunk.id, (buffers.get(chunk.id) ?? '') + chunk.delta); return; } if (chunk.type === 'text-end') { const bufferedText = buffers.get(chunk.id); if (bufferedText != null) { const redactedText = redactText(bufferedText); if (redactedText) { controller.enqueue({ type: 'text-delta', id: chunk.id, delta: redactedText, }); } buffers.delete(chunk.id); } } controller.enqueue(chunk); }, flush(controller) { for (const [id, bufferedText] of buffers) { const redactedText = redactText(bufferedText); if (redactedText) { controller.enqueue({ type: 'text-delta', id, delta: redactedText, }); } } }, }); return { stream: stream.pipeThrough(transformStream), ...rest, }; }, }; ``` The streaming example buffers each text block until `text-end` so matches split across `text-delta` chunks cannot leak through. This delays output and uses memory proportional to the text block size. Do not redact each delta independently. An incremental implementation must retain every possible incomplete match, and a fixed-size buffer alone is not safe for unbounded variable-length patterns. ## Configuring Per Request Custom Metadata To send and access custom metadata in Middleware, you can use `providerOptions`. This is useful when building logging middleware where you want to pass additional context like user IDs, timestamps, or other contextual data that can help with tracking and debugging. ```ts import { generateText, wrapLanguageModel } from 'ai'; import type { LanguageModelV4Middleware } from '@ai-sdk/provider'; export const yourLogMiddleware: LanguageModelV4Middleware = { specificationVersion: 'v4', wrapGenerate: async ({ doGenerate, params }) => { console.log('METADATA', params?.providerMetadata?.yourLogMiddleware); const result = await doGenerate(); return result; }, }; const { text } = await generateText({ model: wrapLanguageModel({ model: "anthropic/claude-sonnet-5.5", middleware: yourLogMiddleware, }), prompt: 'Invent a new holiday and describe its traditions.', providerOptions: { yourLogMiddleware: { hello: 'world', }, }, }); console.log(text); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Overview description: An overview of AI SDK Core. url: "https://ai-sdk.dev/docs/ai-sdk-core/overview" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Large Language Models (LLMs) are advanced programs that can understand, create, and engage with human language on a large scale. They are trained on vast amounts of written material to recognize patterns in language and predict what might come next in a given piece of text. AI SDK Core **simplifies working with LLMs by offering a standardized way of integrating them into your app** - so you can focus on building great AI applications for your users, not waste time on technical details. For example, here’s how you can generate text with various models using the AI SDK: ## AI SDK Core Functions AI SDK Core has various functions designed for [text generation](./generating-text), [structured data generation](./generating-structured-data), and [tool usage](./tools-and-tool-calling). These functions take a standardized approach to setting up [prompts](/docs/foundations/prompts) and [settings](./settings), making it easier to work with different models. - [`generateText`](/docs/ai-sdk-core/generating-text): Generates text and [tool calls](./tools-and-tool-calling). This function is ideal for non-interactive use cases such as automation tasks where you need to write text (e.g. drafting email or summarizing web pages) and for agents that use tools. - [`streamText`](/docs/ai-sdk-core/generating-text): Stream text and tool calls. You can use the `streamText` function for interactive use cases such as [chat bots](/docs/ai-sdk-ui/chatbot) and [content streaming](/docs/ai-sdk-ui/completion). Both `generateText` and `streamText` support [structured output](/docs/ai-sdk-core/generating-structured-data) via the `output` property (e.g. `Output.object()`, `Output.array()`), allowing you to generate typed, schema-validated data for information extraction, synthetic data generation, classification tasks, and [streaming generated UIs](/docs/ai-sdk-ui/object-generation). ## API Reference Please check out the [AI SDK Core API Reference](/docs/reference/ai-sdk-core) for more details on each function. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Prompt Engineering description: Learn how to develop prompts with AI SDK Core. url: "https://ai-sdk.dev/docs/ai-sdk-core/prompt-engineering" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Tips ### Prompts for Tools When you create prompts that include tools, getting good results can be tricky as the number and complexity of your tools increases. Here are a few tips to help you get the best results: 1. Use a model that is strong at tool calling, such as `gpt-6-astra` or `claude-sonnet-5-5`. Weaker models will often struggle to call tools effectively and flawlessly. 2. Keep the number of tools low, e.g. to 5 or less. 3. Keep the complexity of the tool parameters low. Complex Zod schemas with many nested and optional elements, unions, etc. can be challenging for the model to work with. 4. Use semantically meaningful names for your tools, parameters, parameter properties, etc. The more information you pass to the model, the better it can understand what you want. 5. Add `.describe("...")` to your Zod schema properties to give the model hints about what a particular property is for. 6. When the output of a tool might be unclear to the model and there are dependencies between tools, use the `description` field of a tool to provide information about the output of the tool execution. 7. You can include example input/outputs of tool calls in your prompt to help the model understand how to use the tools. Keep in mind that the tools work with JSON objects, so the examples should use JSON. In general, the goal should be to give the model all information it needs in a clear way. ### Tool & Structured Data Schemas The mapping from Zod schemas to LLM inputs (typically JSON schema) is not always straightforward, since the mapping is not one-to-one. #### Zod Dates Zod expects JavaScript Date objects, but models return dates as strings. You can specify and validate the date format using `z.string().datetime()` or `z.string().date()`, and then use a Zod transformer to convert the string to a Date object. ```ts {8-11} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ events: z.array( z.object({ event: z.string(), date: z .string() .date() .transform(value => new Date(value)), }), ), }), }), prompt: 'List 5 important events from the year 2000.', }); ``` #### Optional Parameters When working with tools that have optional parameters, you may encounter compatibility issues with certain providers that use strict schema validation. This is particularly relevant for OpenAI models with structured outputs (strict mode). For maximum compatibility, optional parameters should use `.nullable()` instead of `.optional()`: ```ts {6,7,16,17} // This may fail with strict schema validation const failingTool = tool({ description: 'Execute a command', inputSchema: z.object({ command: z.string(), workdir: z.string().optional(), // This can cause errors timeout: z.string().optional(), }), }); // This works with strict schema validation const workingTool = tool({ description: 'Execute a command', inputSchema: z.object({ command: z.string(), workdir: z.string().nullable(), // Use nullable instead timeout: z.string().nullable(), }), }); ``` #### Temperature Settings For tool calls and object generation, it's recommended to use `temperature: 0` to ensure deterministic and consistent results: ```ts {3} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", temperature: 0, // Recommended for tool calls tools: { myTool: tool({ description: 'Execute a command', inputSchema: z.object({ command: z.string(), }), }), }, prompt: 'Execute the ls command', }); ``` Lower temperature values reduce randomness in model outputs, which is particularly important when the model needs to: - Generate structured data with specific formats - Make precise tool calls with correct parameters - Follow strict schemas consistently ## Debugging ### Inspecting Warnings Not all providers support all AI SDK features. Providers either throw exceptions or return warnings when they do not support a feature. To check if your prompt, tools, and settings are handled correctly by the provider, you can check the call warnings: ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Hello, world!', }); console.log(result.warnings); ``` ### Request Messages You can inspect the input messages that were sent to the model for the final step using `request.messages`. Set `include.requestMessages` to `true` to include these messages in step results. ```ts {4,7} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Hello, world!', include: { requestMessages: true }, }); console.log(result.finalStep.request.messages); ``` ### HTTP Request Bodies You can inspect the raw HTTP request bodies for models that expose them, e.g. [OpenAI](/providers/ai-sdk-providers/openai). This allows you to inspect the exact payload that is sent to the model provider in the provider-specific way. Request bodies are available via the `finalStep.request.body` property of the response: ```ts {6} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Hello, world!', }); console.log(result.finalStep.request.body); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: "Provider & Model Management" description: Learn how to work with multiple providers and models url: "https://ai-sdk.dev/docs/ai-sdk-core/provider-management" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). When you work with multiple providers and models, it is often desirable to manage them in a central place and access the models through simple string ids. The AI SDK offers [custom providers](/docs/reference/ai-sdk-core/custom-provider) and a [provider registry](/docs/reference/ai-sdk-core/provider-registry) for this purpose: - With **custom providers**, you can pre-configure model settings, provide model name aliases, and limit the available models. - The **provider registry** lets you mix multiple providers and access them through simple string ids. You can mix and match custom providers, the provider registry, and [middleware](/docs/ai-sdk-core/middleware) in your application. ## Custom Providers You can create a [custom provider](/docs/reference/ai-sdk-core/custom-provider) using `customProvider`. ### Example: custom model settings You might want to override the default model settings for a provider or provide model name aliases with pre-configured settings. ```ts import { gateway, customProvider, defaultSettingsMiddleware, wrapLanguageModel, } from 'ai'; // custom provider with different provider options: export const openai = customProvider({ languageModels: { // replacement model with custom provider options: 'gpt-6-astra': wrapLanguageModel({ model: gateway('openai/gpt-6-astra'), middleware: defaultSettingsMiddleware({ settings: { providerOptions: { openai: { reasoningEffort: 'high', }, }, }, }), }), // alias model with custom provider options: 'gpt-6-astra-high-reasoning': wrapLanguageModel({ model: gateway('openai/gpt-6-astra'), middleware: defaultSettingsMiddleware({ settings: { providerOptions: { openai: { reasoningEffort: 'high', }, }, }, }), }), }, fallbackProvider: gateway, }); ``` ### Example: model name alias You can also provide model name aliases, so you can update the model version in one place in the future: ```ts import { customProvider, gateway } from 'ai'; // custom provider with alias names: export const anthropic = customProvider({ languageModels: { opus: gateway('anthropic/claude-opus-5.5'), sonnet: gateway('anthropic/claude-sonnet-5.5'), haiku: gateway('anthropic/claude-haiku-5.5'), }, fallbackProvider: gateway, }); ``` ### Example: limit available models You can limit the available models in the system, even if you have multiple providers. ```ts import { customProvider, defaultSettingsMiddleware, wrapLanguageModel, gateway, } from 'ai'; export const myProvider = customProvider({ languageModels: { 'text-medium': gateway('anthropic/claude-sonnet-5.5'), 'text-small': gateway('openai/gpt-6-luna'), 'reasoning-medium': wrapLanguageModel({ model: gateway('openai/gpt-6-astra'), middleware: defaultSettingsMiddleware({ settings: { providerOptions: { openai: { reasoningEffort: 'high', }, }, }, }), }), 'reasoning-fast': wrapLanguageModel({ model: gateway('openai/gpt-6-astra'), middleware: defaultSettingsMiddleware({ settings: { providerOptions: { openai: { reasoningEffort: 'low', }, }, }, }), }), }, embeddingModels: { embedding: gateway.embeddingModel('openai/text-embedding-3-small'), }, // no fallback provider }); ``` ### Example: files and skills interfaces You can attach a provider's `files` or `skills` interface to your custom provider. This allows you to use `uploadFile` and `uploadSkill` through the same provider abstraction. ```ts import { anthropic } from '@ai-sdk/anthropic'; import { openai } from '@ai-sdk/openai'; import { customProvider, uploadFile, uploadSkill } from 'ai'; // custom provider with files interface: const myOpenAI = customProvider({ languageModels: { 'gpt-6-luna': openai.responses('gpt-6-luna'), }, files: openai.files(), }); // custom provider with skills interface: const myAnthropic = customProvider({ languageModels: { sonnet: anthropic('claude-sonnet-5-5'), }, skills: anthropic.skills(), }); // usage: await uploadFile({ api: myOpenAI.files!(), data: fileData, filename: 'image.png', }); await uploadSkill({ api: myAnthropic.skills!(), files: skillFiles, displayTitle: 'My Skill', }); ``` If no `files` or `skills` option is set but a `fallbackProvider` is configured, the custom provider will inherit those interfaces from the fallback. ## Provider Registry You can create a [provider registry](/docs/reference/ai-sdk-core/provider-registry) with multiple providers and models using `createProviderRegistry`. ### Setup ```ts title="registry.ts" import { anthropic } from '@ai-sdk/anthropic'; import { openai } from '@ai-sdk/openai'; import { createProviderRegistry, gateway } from 'ai'; export const registry = createProviderRegistry({ // register provider with prefix and default setup using gateway: gateway, // register provider with prefix and direct provider import: anthropic, openai, }); ``` ### Setup with Custom Separator By default, the registry uses `:` as the separator between provider and model IDs. You can customize this separator: ```ts title="registry.ts" import { anthropic } from '@ai-sdk/anthropic'; import { openai } from '@ai-sdk/openai'; import { createProviderRegistry, gateway } from 'ai'; export const customSeparatorRegistry = createProviderRegistry( { gateway, anthropic, openai, }, { separator: ' > ' }, ); ``` ### Example: Use language models You can access language models by using the `languageModel` method on the registry. The provider id will become the prefix of the model id: `providerId:modelId`. ```ts {5} import { generateText } from 'ai'; import { registry } from './registry'; const { text } = await generateText({ model: registry.languageModel('openai:gpt-6-astra'), // default separator // or with custom separator: // model: customSeparatorRegistry.languageModel('openai > gpt-6-astra'), prompt: 'Invent a new holiday and describe its traditions.', }); ``` ### Example: Use text embedding models You can access text embedding models by using the `.embeddingModel` method on the registry. The provider id will become the prefix of the model id: `providerId:modelId`. ```ts {5} import { embed } from 'ai'; import { registry } from './registry'; const { embedding } = await embed({ model: registry.embeddingModel('openai:text-embedding-3-small'), value: 'sunny day at the beach', }); ``` ### Example: Use image models You can access image models by using the `imageModel` method on the registry. The provider id will become the prefix of the model id: `providerId:modelId`. ```ts {5} import { generateImage } from 'ai'; import { registry } from './registry'; const { image } = await generateImage({ model: registry.imageModel('openai:dall-e-3'), prompt: 'A beautiful sunset over a calm ocean', }); ``` ### Example: Use video models You can access video models by using the `videoModel` method on the registry. The provider id will become the prefix of the model id: `providerId:modelId`. ```ts {8} import { experimental_generateVideo } from 'ai'; import { fal } from '@ai-sdk/fal'; import { createProviderRegistry } from 'ai'; const registry = createProviderRegistry({ fal }); const { videos } = await experimental_generateVideo({ model: registry.videoModel('fal:luma-dream-machine/ray-2'), prompt: 'A cat walking on a beach at sunset', }); ``` ### Example: Use files interface You can access a provider's files interface by calling `registry.files(providerId)`. This is useful when you want to upload files through a provider in the registry before referencing them in model requests. ```ts {12,17} import { openai } from '@ai-sdk/openai'; import { createProviderRegistry, customProvider, generateText, uploadFile, } from 'ai'; const registry = createProviderRegistry({ openai: customProvider({ languageModels: { 'gpt-6-luna': openai.responses('gpt-6-luna') }, files: openai.files(), }), }); const { providerReference } = await uploadFile({ api: registry.files('openai'), data: fileData, filename: 'image.png', }); const { text } = await generateText({ model: registry.languageModel('openai:gpt-6-luna'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe what you see in this image.' }, { type: 'file', mediaType: 'image', data: providerReference }, ], }, ], }); ``` ### Example: Use skills interface You can access a provider's skills interface by calling `registry.skills(providerId)`. ```ts {7,12} import { anthropic } from '@ai-sdk/anthropic'; import { createProviderRegistry, customProvider, uploadSkill } from 'ai'; const registry = createProviderRegistry({ anthropic: customProvider({ languageModels: { sonnet: anthropic('claude-sonnet-5-5') }, skills: anthropic.skills(), }), }); await uploadSkill({ api: registry.skills('anthropic'), files: skillFiles, displayTitle: 'My Skill', }); ``` ## Combining Custom Providers, Provider Registry, and Middleware The central idea of provider management is to set up a file that contains all the providers and models you want to use. You may want to pre-configure model settings, provide model name aliases, limit the available models, and more. Here is an example that implements the following concepts: - pass through gateway with a namespace prefix (here: `gateway > *`) - pass through a full provider with a namespace prefix (here: `xai > *`) - setup an OpenAI-compatible provider with custom api key and base URL (here: `custom > *`) - setup model name aliases (here: `anthropic > fast`, `anthropic > writing`, `anthropic > reasoning`) - pre-configure model settings (here: `anthropic > reasoning`) - validate the provider-specific options (here: `AnthropicLanguageModelOptions`) - use a fallback provider (here: `anthropic > *`) - limit a provider to certain models without a fallback (here: `groq > llama-3.1-8b-instant`, `groq > qwen/qwen3.6-27b`) - define a custom separator for the provider registry (here: `>`) ```ts import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; import { xai } from '@ai-sdk/xai'; import { groq } from '@ai-sdk/groq'; import { createProviderRegistry, customProvider, defaultSettingsMiddleware, gateway, wrapLanguageModel, } from 'ai'; export const registry = createProviderRegistry( { // pass through gateway with a namespace prefix gateway, // pass through full providers with namespace prefixes xai, // access an OpenAI-compatible provider with custom setup custom: createOpenAICompatible({ name: 'provider-name', apiKey: process.env.CUSTOM_API_KEY, baseURL: 'https://api.custom.com/v1', }), // setup model name aliases anthropic: customProvider({ languageModels: { fast: anthropic('claude-haiku-5-5'), // simple model writing: anthropic('claude-sonnet-4-5'), // extended reasoning model configuration: reasoning: wrapLanguageModel({ model: anthropic('claude-sonnet-4-5'), middleware: defaultSettingsMiddleware({ settings: { maxOutputTokens: 100000, // example default setting providerOptions: { anthropic: { thinking: { type: 'enabled', budgetTokens: 32000, }, } satisfies AnthropicLanguageModelOptions, }, }, }), }), }, fallbackProvider: anthropic, }), // limit a provider to certain models without a fallback groq: customProvider({ languageModels: { 'llama-3.1-8b-instant': groq('llama-3.1-8b-instant'), 'qwen/qwen3.6-27b': groq('qwen/qwen3.6-27b'), }, }), }, { separator: ' > ' }, ); // usage: const model = registry.languageModel('anthropic > reasoning'); ``` ## Global Provider Configuration The AI SDK 5 includes a global provider feature that allows you to specify a model using just a plain model ID string: ```ts import { streamText } from 'ai'; const result = await streamText({ model: "anthropic/claude-sonnet-5.5", // Uses the global provider (defaults to gateway) prompt: 'Invent a new holiday and describe its traditions.', }); ``` By default, the global provider is set to the Vercel AI Gateway. ### Customizing the Global Provider You can set your own preferred global provider: ```ts title="setup.ts" import { openai } from '@ai-sdk/openai'; // Initialize once during startup: globalThis.AI_SDK_DEFAULT_PROVIDER = openai; ``` ```ts title="app.ts" import { streamText } from 'ai'; const result = await streamText({ model: 'gpt-6-astra', // Uses OpenAI provider without prefix prompt: 'Invent a new holiday and describe its traditions.', }); ``` This simplifies provider usage and makes it easier to switch between providers without changing your model references throughout your codebase. ## Experimental decision models Custom providers accept `decisionModels` aliases, and registries expose `decisionModel('provider:model')`. These methods return model instances for `experimental_decide`. Direct string IDs use Gateway by default, or an explicitly configured default provider with a `decisionModel` method. Registry middleware for language and image models does not apply to decision. See [Decisions](/docs/ai-sdk-core/decisions#model-aliases-and-registries) for aliases, default-provider configuration, and capability differences. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Realtime description: Learn how to build realtime voice conversations with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/realtime" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Realtime is an experimental feature. This guide covers legacy token-based, turn-based realtime conversations over WebSockets. These sessions run in the browser and connect directly to the provider using a short-lived token that you create on your server. You can also route the connection through [AI Gateway](/providers/ai-sdk-providers/ai-gateway#realtime). For OpenAI Live's continuous JSON/PCM16 WSS relay runtime and application-handled client delegation, see [`experimental_useRealtime`](/docs/reference/ai-sdk-ui/use-realtime#continuous-conversations). The typical flow is: 1. The browser calls your setup endpoint. 2. Your server creates a short-lived realtime token with `experimental_realtime.getToken()`. 3. The browser opens a WebSocket connection to the provider or AI Gateway. 4. The model streams audio, text, and tool calls back to the browser. 5. Tool calls are handled by your application with `onToolCall`. For continuous **OpenAI Live** conversations, use an application-owned WebSocket relay or optional WebRTC via `api.session`. Live uses client delegation: your application handles delegated work and submits context rather than using the turn-based tool loop below. See the [`experimental_useRealtime` reference](/docs/reference/ai-sdk-ui/use-realtime) for SDP setup, server-owned permissions, capture ownership, and graceful close. ## Setup Endpoint Create a setup endpoint that returns a short-lived token for the realtime provider. This endpoint can also attach tool definitions to the session. ```ts title='app/api/realtime/setup/route.ts' import { openai } from '@ai-sdk/openai'; import { experimental_getRealtimeToolDefinitions, tool } from 'ai'; import { z } from 'zod'; const tools = { getWeather: tool({ description: 'Get the current weather for a city', inputSchema: z.object({ city: z.string().describe('The city to get weather for'), }), }), }; export async function POST(request: Request) { const body = await request.json().catch(() => ({})); const toolDefinitions = await experimental_getRealtimeToolDefinitions({ tools, }); const token = await openai.experimental_realtime.getToken({ model: 'gpt-realtime', sessionConfig: { ...body.sessionConfig, tools: toolDefinitions, }, }); return Response.json({ ...token, tools: toolDefinitions, }); } ``` In production, authenticate and rate-limit your setup endpoint. It creates realtime sessions using your server-side API key. ## AI Gateway Use [AI Gateway](/providers/ai-sdk-providers/ai-gateway) when you want the same realtime client code to work across supported upstream providers. The Gateway normalizes realtime events server-side, and the browser still receives only a short-lived client secret. Create the short-lived Gateway realtime token from a server-side setup endpoint: ```ts title='app/api/realtime/setup/route.ts' import { gateway } from 'ai'; export async function POST() { const token = await gateway.experimental_realtime.getToken({ model: 'openai/gpt-realtime-2', }); return Response.json(token); } ``` Then use the matching Gateway realtime model in the browser: ```tsx title='app/realtime/page.tsx' 'use client'; import { experimental_useRealtime } from '@ai-sdk/react'; import { gateway } from 'ai'; const model = gateway.experimental_realtime('openai/gpt-realtime-2'); const sessionConfig = { instructions: 'You are a helpful assistant. Be concise.', inputAudioTranscription: {}, voice: 'alloy', turnDetection: { type: 'server-vad' as const }, }; export default function RealtimePage() { const realtime = experimental_useRealtime({ model, api: { token: '/api/realtime/setup', }, sessionConfig, }); // ... } ``` `gateway.experimental_realtime.getToken()` must run on your server because it uses your Gateway credential to mint a `vcst_` client secret. Creating the realtime model with `gateway.experimental_realtime()` is safe in the browser. Tool definitions work the same way with AI Gateway: convert AI SDK tools with `experimental_getRealtimeToolDefinitions()` in your setup endpoint and return the definitions alongside the token. The hook includes them in the session update after the WebSocket opens. ## Client Session Use the `experimental_useRealtime` hook to connect to a realtime model, capture microphone audio, play model audio, send text messages, and render messages. ```tsx title='app/realtime/page.tsx' 'use client'; import { openai } from '@ai-sdk/openai'; import { experimental_useRealtime } from '@ai-sdk/react'; const model = openai.experimental_realtime('gpt-realtime'); const sessionConfig = { instructions: 'You are a helpful assistant. Be concise.', inputAudioTranscription: {}, voice: 'alloy', turnDetection: { type: 'server-vad' as const }, }; export default function RealtimePage() { const realtime = experimental_useRealtime({ model, api: { token: '/api/realtime/setup', }, sessionConfig, }); return (
{realtime.messages.map(message => (
{message.role} {message.parts.map((part, index) => part.type === 'text' ? {part.text} : null, )}
))}
); } ``` Keep model and session configuration objects stable across renders. Use module scope as above, or `useMemo` when configuration depends on props. Replacing either object replaces the hook's session. ## Tool Calling Realtime tool execution is client-driven. The provider sends tool calls over the WebSocket, and your application handles them with `onToolCall`. If the result is available immediately, return it from `onToolCall`. The SDK sends it back to the provider as tool output. For server-backed tools, call an app-specific API endpoint from `onToolCall`. Avoid generic "execute tool by name" routes. App-specific endpoints are easier to secure because they can use your normal authentication, authorization, validation, and rate limiting rules. ### Server-Backed Tool Endpoint ```ts title='app/api/weather/route.ts' import { z } from 'zod'; const inputSchema = z.object({ city: z.string(), }); export async function POST(request: Request) { const input = inputSchema.safeParse(await request.json()); if (!input.success) { return Response.json({ error: 'Invalid input' }, { status: 400 }); } return Response.json({ city: input.data.city, temperature: 72, condition: 'sunny', }); } ``` ### Client Tool Handler ```tsx title='app/realtime/page.tsx' {12-26} import { openai } from '@ai-sdk/openai'; import { experimental_useRealtime } from '@ai-sdk/react'; const model = openai.experimental_realtime('gpt-realtime'); export default function RealtimePage() { const realtime = experimental_useRealtime({ model, api: { token: '/api/realtime/setup', }, onToolCall: async ({ toolCall }) => { if (toolCall.toolName === 'getWeather') { const response = await fetch('/api/weather', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(toolCall.args), }); if (!response.ok) { throw new Error('Weather lookup failed'); } return response.json(); } }, }); // ... } ``` You can also submit tool output manually with `addToolOutput` when the tool requires user interaction or another asynchronous process: ```tsx realtime.addToolOutput(toolCallId, { approved: true, }); ``` ## Supported Providers Realtime models are available on providers that expose realtime WebSocket APIs: ```ts import { openai } from '@ai-sdk/openai'; import { google } from '@ai-sdk/google'; import { xai } from '@ai-sdk/xai'; const openaiModel = openai.experimental_realtime('gpt-realtime'); const googleModel = google.experimental_realtime( 'gemini-3.1-flash-live-preview', ); const xaiModel = xai.experimental_realtime('grok-voice-latest'); ``` You can also route realtime through the [AI Gateway](/providers/ai-sdk-providers/ai-gateway#realtime), which normalizes the session so the same client code works across upstream providers: ```ts import { gateway } from '@ai-sdk/gateway'; const gatewayModel = gateway.experimental_realtime('openai/gpt-realtime-2'); ``` `gateway.experimental_realtime.getToken()` mints a short-lived Gateway client secret on your server. The browser uses that token to open the Gateway WebSocket; the SDK handles the Gateway-specific WebSocket subprotocols for you. See the [AI Gateway realtime docs](/providers/ai-sdk-providers/ai-gateway#realtime) for Gateway-specific token and provider option details. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Reasoning description: Learn how to control reasoning across providers with the top-level reasoning parameter. url: "https://ai-sdk.dev/docs/ai-sdk-core/reasoning" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Many language models support an internal "reasoning" phase (sometimes also called "thinking") before producing a response. The AI SDK provides a top-level `reasoning` parameter on [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text) that controls this behavior across providers with a single, portable setting. ## Basic Usage ```ts import { generateText } from 'ai'; const { text, reasoning, reasoningText } = await generateText({ model: 'anthropic/claude-sonnet-5.5', reasoning: 'medium', prompt: 'How many people will live in the world in 2040?', }); ``` The `reasoning` parameter accepts the following values: | Value | Behavior | | -------------------- | -------------------------------------------------------------------- | | `'provider-default'` | Use the provider's default reasoning behavior (default when omitted) | | `'none'` | Disable reasoning | | `'minimal'` | Bare-minimum reasoning | | `'low'` | Fast, concise reasoning | | `'medium'` | Balanced reasoning | | `'high'` | Thorough reasoning | | `'xhigh'` | Extra-high reasoning | | `'max'` | Maximum reasoning | ## Streaming The `reasoning` parameter works the same way with `streamText`: ```ts import { streamText } from 'ai'; const result = streamText({ model: 'google/gemini-3.8-flash', reasoning: 'high', prompt: 'Explain the Riemann hypothesis in simple terms.', }); for await (const part of result.stream) { if (part.type === 'reasoning') { process.stdout.write(part.textDelta); } else if (part.type === 'text-delta') { process.stdout.write(part.textDelta); } } ``` ## Precedence Rules Provider-specific settings take precedence over the corresponding fields derived from `reasoning`. Partial configurations may still use portable reasoning to fill missing fields; summary and display settings can be used alongside it. ```ts import { generateText } from 'ai'; import { openai } from '@ai-sdk/openai'; const { text } = await generateText({ model: openai.responses('gpt-6-astra'), reasoning: 'low', // ignored because providerOptions.openai.reasoningEffort is set providerOptions: { openai: { reasoningEffort: 'high', // this wins }, }, prompt: 'Explain quantum entanglement.', }); ``` This design lets you use the portable `reasoning` parameter by default and fall back to `providerOptions` only when you need provider-specific features like exact token budgets. ## Provider Support The `reasoning` parameter is supported by providers including OpenAI, Anthropic, Google, xAI, Groq, DeepSeek, Moonshot AI, Fireworks, Amazon Bedrock, Mistral, Cohere, Alibaba, Open Responses, OpenAI Compatible, and Perplexity. Each provider translates the value to its native reasoning API. Providers with a distinct native maximum tier, such as supported OpenAI, Anthropic, DeepSeek, and Moonshot AI models, receive `'max'` directly. Amazon Bedrock preserves `'max'` for its Anthropic and OpenAI effort mappings, while Amazon Nova 2 maps `'xhigh'` and `'max'` to `'high'` with a compatibility warning. Providers whose highest native tier is lower coerce `'max'` to their closest supported tier and emit a compatibility warning when their adapter supports warning propagation. OpenAI-compatible and Open Responses endpoints receive `'max'` directly so compatible servers can interpret it. Some providers use a numeric token budget instead of an enum for reasoning control. In those cases, `'max'` maps to 95% of the model's maximum output tokens by default, subject to the provider's minimum and maximum reasoning budget limits. Maximum reasoning can substantially increase token usage, latency, cost, and the chance of reaching request or output limits. Use it when the model's additional reasoning depth is worth those tradeoffs. Providers and models that do not support reasoning configuration emit an `unsupported` warning and ignore the parameter. ## Migrating from `providerOptions` If you currently control reasoning via `providerOptions`, you can migrate to the top-level `reasoning` parameter for portability across providers. ### Before (Anthropic) ```ts const { text } = await generateText({ model: anthropic('claude-opus-5-5'), providerOptions: { anthropic: { thinking: { type: 'adaptive' }, effort: 'high', }, }, prompt: 'How many people will live in the world in 2040?', }); ``` ### After (Anthropic) ```ts const { text } = await generateText({ model: anthropic('claude-opus-5-5'), reasoning: 'high', prompt: 'How many people will live in the world in 2040?', }); ``` ### Before (Anthropic with older model) ```ts const { text } = await generateText({ model: anthropic('claude-sonnet-4-20250514'), providerOptions: { anthropic: { thinking: { type: 'enabled', budgetTokens: 12000 }, }, }, prompt: 'How many people will live in the world in 2040?', }); ``` ### After (Anthropic with older model) ```ts const { text } = await generateText({ model: anthropic('claude-sonnet-4-20250514'), reasoning: 'medium', prompt: 'How many people will live in the world in 2040?', }); ``` If you need to enforce an exact token budget (e.g. exactly 12000 tokens), keep using `providerOptions` instead of the top-level `reasoning` parameter. ### Before (Google with `includeThoughts`) ```ts const { text } = await generateText({ model: google('gemini-3.8-flash'), providerOptions: { google: { thinkingConfig: { thinkingLevel: 'high', includeThoughts: true }, }, }, prompt: 'Explain the Riemann hypothesis in simple terms.', }); ``` ### After (Google with `includeThoughts`) ```ts const { text } = await generateText({ model: google('gemini-3.8-flash'), reasoning: 'high', providerOptions: { google: { thinkingConfig: { includeThoughts: true } }, }, prompt: 'Explain the Riemann hypothesis in simple terms.', }); ``` ### Before (OpenAI with `reasoningSummary`) ```ts const { text } = await generateText({ model: openai.responses('gpt-6-astra'), providerOptions: { openai: { reasoningEffort: 'high', reasoningSummary: 'auto' }, }, prompt: 'Explain quantum entanglement.', }); ``` ### After (OpenAI with `reasoningSummary`) ```ts const { text } = await generateText({ model: openai.responses('gpt-6-astra'), reasoning: 'high', providerOptions: { openai: { reasoningSummary: 'auto' }, }, prompt: 'Explain quantum entanglement.', }); ``` You can combine `providerOptions` with `reasoning` for provider-specific features such as reasoning summaries or display settings. Explicit effort or budget values override the corresponding mapped fields; partial configurations may still use portable reasoning to fill missing fields. See [Precedence Rules](#precedence-rules) for details. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Reranking description: Learn how to rerank documents with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/reranking" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Reranking is a technique used to improve search relevance by reordering a set of documents based on their relevance to a query. Unlike embedding-based similarity search, reranking models are specifically trained to understand the relationship between queries and documents, often producing more accurate relevance scores. ## Reranking Documents The AI SDK provides the [`rerank`](/docs/reference/ai-sdk-core/rerank) function to rerank documents based on their relevance to a query. You can use it with reranking models, e.g. `cohere.reranking('rerank-v4.0-pro')` or `bedrock.reranking('cohere.rerank-v3-5:0')`. ```tsx import { rerank } from 'ai'; import { cohere } from '@ai-sdk/cohere'; const documents = [ 'sunny day at the beach', 'rainy afternoon in the city', 'snowy night in the mountains', ]; const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents, query: 'talk about rain', topN: 2, // Return top 2 most relevant documents }); console.log(ranking); // [ // { originalIndex: 1, score: 0.9, document: 'rainy afternoon in the city' }, // { originalIndex: 0, score: 0.3, document: 'sunny day at the beach' } // ] ``` ## Working with Object Documents Reranking also supports structured documents (JSON objects), making it ideal for searching through databases, emails, or other structured content: ```tsx import { rerank } from 'ai'; import { cohere } from '@ai-sdk/cohere'; const documents = [ { from: 'Paul Doe', subject: 'Follow-up', text: 'We are happy to give you a discount of 20% on your next order.', }, { from: 'John McGill', subject: 'Missing Info', text: 'Sorry, but here is the pricing information from Oracle: $5000/month', }, ]; const { ranking, rerankedDocuments } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents, query: 'Which pricing did we get from Oracle?', topN: 1, }); console.log(rerankedDocuments[0]); // { from: 'John McGill', subject: 'Missing Info', text: '...' } ``` ## Understanding the Results The `rerank` function returns a comprehensive result object: ```ts import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const { ranking, rerankedDocuments, originalDocuments } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: ['sunny day at the beach', 'rainy afternoon in the city'], query: 'talk about rain', }); // ranking: sorted array of { originalIndex, score, document } // rerankedDocuments: documents sorted by relevance (convenience property) // originalDocuments: original documents array ``` Each item in the `ranking` array contains: - `originalIndex`: Position in the original documents array - `score`: Relevance score (typically 0-1, where higher is more relevant) - `document`: The original document ## Settings ### Top-N Results Use `topN` to limit the number of results returned. This is useful for retrieving only the most relevant documents: ```ts {8} import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: ['doc1', 'doc2', 'doc3', 'doc4', 'doc5'], query: 'relevant information', topN: 3, // Return only top 3 most relevant documents }); ``` ### Provider Options Reranking model settings can be configured using `providerOptions` for provider-specific parameters: ```ts {8-12} import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: ['sunny day at the beach', 'rainy afternoon in the city'], query: 'talk about rain', providerOptions: { cohere: { maxTokensPerDoc: 1000, // Limit tokens per document }, }, }); ``` ### Retries The `rerank` function accepts an optional `maxRetries` parameter of type `number` that you can use to set the maximum number of retries for the reranking process. It defaults to `2` retries (3 attempts in total). You can set it to `0` to disable retries. ```ts {8} import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: ['sunny day at the beach', 'rainy afternoon in the city'], query: 'talk about rain', maxRetries: 0, // Disable retries }); ``` ### Abort Signals and Timeouts The `rerank` function accepts an optional `abortSignal` parameter of type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that you can use to abort the reranking process or set a timeout. ```ts {8} import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: ['sunny day at the beach', 'rainy afternoon in the city'], query: 'talk about rain', abortSignal: AbortSignal.timeout(5000), // Abort after 5 seconds }); ``` ### Custom Headers The `rerank` function accepts an optional `headers` parameter of type `Record` that you can use to add custom headers to the reranking request. ```ts {8} import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: ['sunny day at the beach', 'rainy afternoon in the city'], query: 'talk about rain', headers: { 'X-Custom-Header': 'custom-value' }, }); ``` ## Response Information The `rerank` function returns response information that includes the raw provider response: ```ts {4,10} import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const { ranking, response } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: ['sunny day at the beach', 'rainy afternoon in the city'], query: 'talk about rain', }); console.log(response); // { id, timestamp, modelId, headers, body } ``` ## Reranking Providers & Models Several providers offer reranking models: | Provider | Model | | ----------------------------------------------------------------------------- | ------------------------------------- | | [Cohere](/providers/ai-sdk-providers/cohere#reranking-models) | `rerank-v4.0-pro` | | [Cohere](/providers/ai-sdk-providers/cohere#reranking-models) | `rerank-v4.0-fast` | | [Cohere](/providers/ai-sdk-providers/cohere#reranking-models) | `rerank-v3.5` | | [Cohere](/providers/ai-sdk-providers/cohere#reranking-models) | `rerank-english-v3.0` | | [Cohere](/providers/ai-sdk-providers/cohere#reranking-models) | `rerank-multilingual-v3.0` | | [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock#reranking-models) | `amazon.rerank-v1:0` | | [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock#reranking-models) | `cohere.rerank-v3-5:0` | | [Together.ai](/providers/ai-sdk-providers/togetherai#reranking-models) | `Salesforce/Llama-Rank-v1` | | [Together.ai](/providers/ai-sdk-providers/togetherai#reranking-models) | `mixedbread-ai/Mxbai-Rerank-Large-V2` | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Runtime and Tool Context description: Learn how runtime context, tool context, and telemetry context filtering work together. url: "https://ai-sdk.dev/docs/ai-sdk-core/runtime-and-tool-context" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Context lets you pass server-side state through a generation or agent loop without putting that state into the prompt. The AI SDK separates shared runtime state from per-tool execution state so agents can keep track of their work while tools only receive the values they need. Use context for values such as tenant information, feature flags, session data, request IDs, API credentials, access tokens, or other application state that should affect execution. ## Context Types | Concept | Where you define it | Where you read it | Use it for | | --------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | | `runtimeContext` | `generateText` or `streamText` calls; `ToolLoopAgent` settings or `prepareCall` | `prepareStep`, lifecycle callbacks, step results, and telemetry | Shared generation or agent state | | `toolsContext` | `generateText` or `streamText` calls; `ToolLoopAgent` settings or `prepareCall` | `prepareStep`, approval callbacks, tool context resolution, and tool description functions | A map of per-tool context values keyed by tool name | | tool `context` | Each tool's `toolsContext` entry, validated by its `contextSchema` | Tool description functions, tool `execute`, `needsApproval`, and tool input lifecycle callbacks | Values needed by one tool | | `toolContext` | Derived from one tool's context | Tool approval callbacks and tool execution events | The event/callback name for one tool's context | | `telemetry.includeRuntimeContext` | The generation call or `ToolLoopAgent` settings | Telemetry filtering | Top-level `runtimeContext` properties to include in telemetry | | `telemetry.includeToolsContext` | The generation call or `ToolLoopAgent` settings | Telemetry filtering | Top-level tool context properties to include in telemetry | In agents, `runtimeContext` is the agent's shared runtime state. It flows through the loop and can be read or updated in `prepareStep` between model calls. Tool-specific data stays in `toolsContext`, where each tool receives only its own validated `context`. ```txt generateText / streamText / ToolLoopAgent settings -> runtimeContext -> prepareStep, lifecycle callbacks, step results -> telemetry, filtered by telemetry.includeRuntimeContext -> toolsContext -> prepareStep -> one tool's context / toolContext -> execute, approval callbacks, tool events -> telemetry, filtered by telemetry.includeToolsContext ``` ## Runtime Context Pass `runtimeContext` when you need shared state for the whole generation or agent loop. It is not added to the model prompt automatically. Use it to configure step preparation, track server-side state, or correlate lifecycle events. ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Help the user plan their trip.', runtimeContext: { tenantId: 'tenant_123', plan: 'enterprise', requestId: 'req_abc', }, prepareStep: async ({ runtimeContext }) => { if (runtimeContext.plan === 'enterprise') { return { temperature: 0.2 }; } return {}; }, }); ``` `prepareStep` can return a new `runtimeContext`. The new value affects the current step and all subsequent steps, which makes it the right place to update agent state between turns of the loop. ## Tool Context Use `toolsContext` for values that belong to a specific tool. Each tool declares the context it expects with `contextSchema`; the matching `toolsContext` entry is validated and passed to the tool as `context`. ```ts {9-12,25-30} import { generateText, tool } from 'ai'; import { z } from 'zod'; const weatherTool = tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string(), }), contextSchema: z.object({ weatherApiKey: z.string(), defaultUnit: z.enum(['celsius', 'fahrenheit']), }), execute: async ({ location }, { context }) => { return fetchWeather({ location, apiKey: context.weatherApiKey, unit: context.defaultUnit, }); }, }); const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { weather: weatherTool }, toolsContext: { weather: { weatherApiKey: process.env.WEATHER_API_KEY!, defaultUnit: 'fahrenheit', }, }, prompt: 'What is the weather in San Francisco?', }); ``` When at least one tool declares `contextSchema`, `toolsContext` is required for the tools that need context. A tool receives only its own context, not the full `toolsContext` map. Tool description functions receive the same typed `context` before each model call, so descriptions can change with the current tool context. Treat tool context as immutable inside tools. If you need to change tool context between steps, inspect the previous step in `prepareStep` and return an updated `toolsContext`. For `ToolLoopAgent`, set context in the constructor. For context that changes with each invocation, use `callOptionsSchema` and `prepareCall`. See [Configuring Call Options](/docs/agents/configuring-call-options). ## Telemetry Context Filtering Context often contains values that are useful inside your application but should not be sent to telemetry providers. Use `telemetry.includeRuntimeContext` to include selected top-level runtime context properties in telemetry, and `telemetry.includeToolsContext` to include selected top-level tool context properties per tool. ```ts import { ToolLoopAgent, tool } from 'ai'; import { z } from 'zod'; const customerLookup = tool({ description: 'Look up customer account details', inputSchema: z.object({ customerId: z.string(), }), contextSchema: z.object({ apiKey: z.string(), region: z.string(), }), execute: async ({ customerId }, { context }) => { return lookupCustomer({ customerId, apiKey: context.apiKey, region: context.region, }); }, }); const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { customerLookup }, runtimeContext: { requestId: 'req_abc', tenantId: 'tenant_123', userId: 'user_123', }, telemetry: { includeRuntimeContext: { requestId: true, }, includeToolsContext: { customerLookup: { region: true, }, }, }, toolsContext: { customerLookup: { apiKey: process.env.CUSTOMER_API_KEY!, region: 'us', }, }, }); const result = await agent.generate({ prompt: 'Check whether customer cust_123 is eligible for priority support.', }); ``` In this example, telemetry receives `runtimeContext` with only `requestId` and the `customerLookup` context with only `region`. Context filters only affect telemetry integrations, including OpenTelemetry integrations. Tool execution, lifecycle callbacks, and returned results still receive the full context values. Context telemetry filtering is shallow. For `telemetry.includeRuntimeContext`, only top-level properties marked as `true` are sent when it is configured; if it is omitted, no runtime context properties are sent. For `telemetry.includeToolsContext`, only top-level tool context properties marked as `true` are sent when it is configured; if it is omitted, no tool context properties are sent. ## Where Context Is Available | Location | `runtimeContext` | `toolsContext` | Tool `context` / `toolContext` | | --------------------------------- | --------------------------------------------- | ------------------------------------------- | -------------------------------------------------------- | | `prepareStep` | Read and update | Read and update | Not directly | | Tool description functions | Not passed directly | Not passed directly | Read one tool's validated `context` | | Tool `execute` | Not passed directly | Not passed directly | Read one tool's validated `context` | | Tool approval | Read in generic and per-tool callbacks | Read in generic callbacks | Read as `toolContext` in per-tool callbacks | | Tool execution events | Not included | Not included | Read as `toolContext` | | Step results and finish callbacks | Read final or per-step state | Read final or per-step state | Available through the per-tool entries in `toolsContext` | | Telemetry | Filtered by `telemetry.includeRuntimeContext` | Filtered by `telemetry.includeToolsContext` | Filtered by `telemetry.includeToolsContext` | ## Choosing the Right Context - Use `runtimeContext` for state shared by the whole generation or agent loop, such as request metadata, tenant settings, feature flags, or agent progress. - Use `toolsContext` and `contextSchema` for values needed by a specific tool, such as API keys, scoped clients, user permissions, or default tool settings. - Use prompt messages for information the model should reason about or mention in its answer. - Use `telemetry.includeRuntimeContext` and `telemetry.includeToolsContext` to reduce telemetry exposure, not as a general security boundary. Learn more about [tools and tool calling](/docs/ai-sdk-core/tools-and-tool-calling), [lifecycle callbacks](/docs/ai-sdk-core/lifecycle-callbacks), and [telemetry](/docs/ai-sdk-core/telemetry). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Settings description: Learn how to configure the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/settings" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Large language models (LLMs) typically provide settings to augment their output. All AI SDK functions support the following common settings in addition to the model, the [prompt](/docs/foundations/prompts), and additional provider-specific settings: ```ts {3-6} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", maxOutputTokens: 512, temperature: 0.3, maxRetries: 5, timeout: 10000, prompt: 'Invent a new holiday and describe its traditions.', }); ``` Some providers do not support all common settings. If you use a setting with a provider that does not support it, a warning will be generated. You can check the `warnings` property in the result object to see if any warnings were generated. ## Language Model Call Options Language model call options (`LanguageModelCallOptions`) are settings that influence how the language model generates its response — token limits, sampling behavior, penalties, stop sequences, seed, and reasoning. They are forwarded to the underlying model. ### `maxOutputTokens` Maximum number of tokens to generate. ### `temperature` Temperature setting. The value is passed through to the provider. The range depends on the provider and model. For most providers, `0` means almost deterministic results, and higher values mean more randomness. It is recommended to set either `temperature` or `topP`, but not both. In AI SDK 5.0, temperature is no longer set to `0` by default. ### `topP` Nucleus sampling. The value is passed through to the provider. The range depends on the provider and model. For most providers, nucleus sampling is a number between 0 and 1. E.g. 0.1 would mean that only tokens with the top 10% probability mass are considered. It is recommended to set either `temperature` or `topP`, but not both. ### `topK` Only sample from the top K options for each subsequent token. Used to remove "long tail" low probability responses. Recommended for advanced use cases only. You usually only need to use `temperature`. ### `presencePenalty` The presence penalty affects the likelihood of the model to repeat information that is already in the prompt. The value is passed through to the provider. The range depends on the provider and model. For most providers, `0` means no penalty. ### `frequencyPenalty` The frequency penalty affects the likelihood of the model to repeatedly use the same words or phrases. The value is passed through to the provider. The range depends on the provider and model. For most providers, `0` means no penalty. ### `stopSequences` The stop sequences to use for stopping the text generation. If set, the model will stop generating text when one of the stop sequences is generated. Providers may have limits on the number of stop sequences. ### `seed` It is the seed (integer) to use for random sampling. If set and supported by the model, calls will generate deterministic results. ### `reasoning` Controls how much reasoning the model performs before generating a response. | Value | Behavior | | -------------------- | -------------------------------------------------------------------- | | `'provider-default'` | Use the provider's default reasoning behavior (default when omitted) | | `'none'` | Disable reasoning | | `'minimal'` | Bare-minimum reasoning | | `'low'` | Fast, concise reasoning | | `'medium'` | Balanced reasoning | | `'high'` | Thorough reasoning | | `'xhigh'` | Extra-high reasoning | | `'max'` | Maximum reasoning | Provider-specific settings take precedence over the corresponding fields derived from `reasoning`. Partial configurations can still use portable reasoning to fill missing fields; summary and display options can be used alongside it. For example, Bedrock preserves an explicit thinking budget while deriving a missing enabled type from portable reasoning. See the [reasoning guide](/docs/ai-sdk-core/reasoning) for details on per-provider mapping and migration from `providerOptions`. ## Request Options Request options (`RequestOptions`) are settings that affect transport, retries, cancellation, and timeouts — not model generation behavior. They control how the SDK communicates with the provider's API. ### `maxRetries` Maximum number of retries. Set to 0 to disable retries. Default: `2`. ### `abortSignal` An optional abort signal that can be used to cancel the call. The abort signal can e.g. be forwarded from a user interface to cancel the call, or to define a timeout using `AbortSignal.timeout`. #### Example: AbortSignal.timeout ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', abortSignal: AbortSignal.timeout(5000), // 5 seconds }); ``` ### `timeout` An optional timeout in milliseconds. The call will be aborted if it takes longer than the specified duration. This is a convenience parameter that creates an abort signal internally. It can be used alongside `abortSignal` - if both are provided, the call will abort when either condition is met. You can specify the timeout either as a number (milliseconds) or as an object with the following properties: - `totalMs`: The total timeout for the entire call including all steps. - `stepMs`: The timeout for each complete step, including model generation, retries, and local tool execution. The budget is cumulative across provider attempts within the step. - `firstChunkMs`: The timeout until the first content-bearing output of each step (streaming only). Text deltas, reasoning deltas, tool-input deltas, generated files, and tool calls satisfy the timeout. Response metadata, stream starts, empty deltas, raw chunks, and transport activity do not satisfy or reset it. - `chunkMs`: The timeout between meaningful model output chunks after output has started (streaming only). It starts with the first meaningful output; it does not limit the wait for that first output. Non-content chunks do not reset it. This is useful for detecting streams that stall after generation begins. - `toolMs`: The default timeout for all tool executions. If a tool takes longer, it aborts and returns a tool-error so the model can respond or retry. - `tools`: Per-tool timeout overrides using `{toolName}Ms` keys (e.g. `weatherMs`, `slowApiMs`). Takes precedence over `toolMs`. Tool names are type-checked for autocomplete. The `firstChunkMs` and `chunkMs` timers stop when the model response finishes, errors, closes, or is cancelled. Each provider retry or later model call gets fresh output timeout budgets. Retry backoff and local tool execution are outside these budgets. Provider-executed tools remain within the model response. The `stepMs` and `totalMs` budgets remain active across retries and local tool execution. #### Example: 5 second timeout (number format) ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', timeout: 5000, // 5 seconds }); ``` #### Example: 5 second total timeout (object format) ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', timeout: { totalMs: 5000 }, // 5 seconds }); ``` #### Example: 10 second step timeout ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', timeout: { stepMs: 10000 }, // 10 seconds per step }); ``` #### Example: Combined total and step timeout ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', timeout: { totalMs: 60000, // 60 seconds total stepMs: 10000, // 10 seconds per step }, }); ``` #### Example: Per-chunk timeout for streaming (streamText only) ```ts const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', timeout: { chunkMs: 5000 }, // abort if content stalls for 5 seconds }); ``` #### Example: First-content timeout for streaming (streamText only) ```ts const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', timeout: { firstChunkMs: 10000, // 10 seconds for first content in each step }, }); ``` #### Example: Tool execution timeout ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { weather: weatherTool, slowApi: slowApiTool }, timeout: { toolMs: 5000, // 5 seconds default for all tools }, prompt: 'What is the weather in San Francisco?', }); ``` #### Example: Per-tool timeout overrides ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { weather: weatherTool, slowApi: slowApiTool }, timeout: { toolMs: 5000, // default for all tools tools: { weatherMs: 3000, // 3 seconds for weather tool slowApiMs: 10000, // 10 seconds for slow API tool }, }, prompt: 'What is the weather in San Francisco?', }); ``` ### `headers` Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers. You can use the request headers to provide additional information to the provider, depending on what the provider supports. For example, some observability providers support headers such as `Prompt-Id`. ```ts import { generateText } from 'ai'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', headers: { 'Prompt-Id': 'my-prompt-id', }, }); ``` The `headers` setting is for request-specific headers. You can also set `headers` in the provider configuration. These headers will be sent with every request made by the provider. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Skill Uploads description: Learn how to upload skills and use provider references with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/skill-uploads" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK provides the [`uploadSkill`](/docs/reference/ai-sdk-core/upload-skill) function to upload custom skills to a provider and get back a `ProviderReference` that can be passed to subsequent inference calls. A **skill** is a bundle of files (e.g. a `SKILL.md` describing the skill's behavior) that providers can load, e.g. in sandboxed container environments. In the AI SDK, the uploaded skill is identified by a `ProviderReference` — a `Record` mapping provider names to provider-specific identifiers. This concept is used for other provider-specific asset references too, such as uploaded media files. ```ts import { uploadSkill, generateText } from 'ai'; import { anthropic, type AnthropicLanguageModelOptions, } from '@ai-sdk/anthropic'; import { readFileSync } from 'fs'; const { providerReference } = await uploadSkill({ api: anthropic.skills(), files: [ { path: 'my-skill/SKILL.md', data: readFileSync('./SKILL.md'), }, ], displayTitle: 'My Skill', }); const { text } = await generateText({ model: anthropic('claude-sonnet-4-6'), tools: { code_execution: anthropic.tools.codeExecution_20260120(), }, prompt: 'Use the skill to complete the task.', providerOptions: { anthropic: { container: { skills: [{ type: 'custom', providerReference }], }, } satisfies AnthropicLanguageModelOptions, }, }); ``` As a shorthand, you can pass a provider instance directly to `api` instead of calling `.skills()` explicitly — the SDK will call `.skills()` for you: ```ts {2} const { providerReference } = await uploadSkill({ api: anthropic, // shorthand for anthropic.skills() files: [{ path: 'my-skill/SKILL.md', data: readFileSync('./SKILL.md') }], displayTitle: 'My Skill', }); ``` ## Skill Files A skill is composed of one or more files, each with a relative `path` and `data`. File content can be provided as a `Uint8Array` (for example, from `fs.readFileSync`) or as a base64-encoded string. Alternatively, `data` can be provided as a tagged object with either `type: 'text'` or `type: 'data'` to explicitly specify the content format: ```ts const { providerReference } = await uploadSkill({ api: openai.skills(), files: [ { path: 'my-skill/SKILL.md', data: readFileSync('./SKILL.md'), // Uint8Array }, { path: 'my-skill/helper.py', data: readFileSync('./helper.py'), }, { path: 'my-skill/data.json', data: { type: 'data', data: Buffer.from(JSON.stringify({ foo: 'bar' })).toString('base64'), }, }, { path: 'my-skill/README.txt', data: { type: 'text', text: 'This is a skill for demonstrating skill uploads.', }, }, ], }); ``` ## Upload Result `uploadSkill` returns an `UploadSkillResult` with the following fields: | Field | Type | Description | | ------------------- | ------------------- | ---------------------------------------------------------------- | | `providerReference` | `ProviderReference` | Maps provider names to provider-specific skill IDs | | `displayTitle` | `string?` | Human-readable title (if supported and provided) | | `name` | `string?` | Name inferred by the provider from the skill files | | `description` | `string?` | Description inferred by the provider from the skill files | | `latestVersion` | `string?` | Latest version identifier assigned by the provider | | `providerMetadata` | `object?` | Additional provider-specific metadata (e.g. timestamps) | | `warnings` | `Warning[]` | Warnings for unsupported options (e.g. `displayTitle` on OpenAI) | ## Provider References A `ProviderReference` is a `Record` mapping provider names to provider-specific skill identifiers: ```ts // Example ProviderReference { anthropic: 'skill_abc123', } ``` Pass the `providerReference` when referencing the skill during inference. Each provider looks up its own skill ID from the reference. If no entry exists for the current provider, an error is thrown. ## Multi-Provider Usage If you want to use the same skill across multiple providers, upload it to each one and merge the references: ```ts const [openaiUpload, anthropicUpload] = await Promise.all([ uploadSkill({ api: openai.skills(), files: [{ path: 'my-skill/SKILL.md', data: skillSource }], }), uploadSkill({ api: anthropic.skills(), files: [{ path: 'my-skill/SKILL.md', data: skillSource }], displayTitle: 'My Skill', }), ]); const mergedReference = { ...openaiUpload.providerReference, ...anthropicUpload.providerReference, }; // mergedReference: { openai: 'sk_...', anthropic: 'sk_...' } ``` The merged reference can then be used in inference calls regardless of which provider processes the request — each provider will find its own skill ID. ## Using Skills in Inference Calls How you attach a skill to an inference call depends on the provider. ### Anthropic Pass the `providerReference` inside the `container.skills` array in `providerOptions`: ```ts await generateText({ model: anthropic('claude-sonnet-4-6'), tools: { code_execution: anthropic.tools.codeExecution_20260120(), }, prompt: '...', providerOptions: { anthropic: { container: { skills: [{ type: 'custom', providerReference }], }, } satisfies AnthropicLanguageModelOptions, }, }); ``` ### OpenAI Pass the `providerReference` inside the `shell` tool's `environment.skills` array: ```ts await generateText({ model: openai.responses('gpt-6-astra'), tools: { shell: openai.tools.shell({ environment: { type: 'containerAuto', skills: [{ type: 'skillReference', providerReference }], }, }), }, prompt: '...', }); ``` ## Supported Providers The following providers support `skills()` and skill uploads: | Provider | Factory Method | | --------- | -------------------- | | Anthropic | `anthropic.skills()` | | OpenAI | `openai.skills()` | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Speech description: Learn how to generate speech from text with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/speech" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK provides the [`generateSpeech`](/docs/reference/ai-sdk-core/generate-speech) function to generate speech from text using a speech model. ```ts import { generateSpeech } from 'ai'; import { openai } from '@ai-sdk/openai'; const result = await generateSpeech({ model: openai.speech('gpt-4o-mini-tts'), text: 'Hello, world!', voice: 'alloy', }); ``` ## Telemetry `generateSpeech` accepts the standard `telemetry` option. Registered integrations receive start, end, and error events with the provider and model, source text, generated audio byte length, media type and format, warnings, response metadata, provider metadata, and usage when the provider reports it. Raw generated audio is not included in telemetry events. ```ts {5-7} const result = await generateSpeech({ model: openai.speech('gpt-4o-mini-tts'), text: 'Hello from the AI SDK!', telemetry: { functionId: 'speak-greeting', }, }); ``` Use `recordInputs: false` or `recordOutputs: false` when the text or output metadata should not be recorded by telemetry integrations. To access the generated audio: ```ts const audioData = result.audio.uint8Array; // audio data as Uint8Array // or const audioBase64 = result.audio.base64; // audio data as base64 string ``` ## Settings ### Provider-Specific Settings You can set model-specific settings with the `providerOptions` parameter. ```ts {7-11} import { generateSpeech } from 'ai'; import { openai } from '@ai-sdk/openai'; const audio = await generateSpeech({ model: openai.speech('gpt-4o-mini-tts'), text: 'Hello, world!', providerOptions: { openai: { // ... }, }, }); ``` ### Abort Signals and Timeouts `generateSpeech` accepts an optional `abortSignal` parameter of type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that you can use to abort the speech generation process or set a timeout. ```ts {7} import { openai } from '@ai-sdk/openai'; import { generateSpeech } from 'ai'; const audio = await generateSpeech({ model: openai.speech('gpt-4o-mini-tts'), text: 'Hello, world!', abortSignal: AbortSignal.timeout(1000), // Abort after 1 second }); ``` ### Custom Headers `generateSpeech` accepts an optional `headers` parameter of type `Record` that you can use to add custom headers to the speech generation request. ```ts {7} import { openai } from '@ai-sdk/openai'; import { generateSpeech } from 'ai'; const audio = await generateSpeech({ model: openai.speech('gpt-4o-mini-tts'), text: 'Hello, world!', headers: { 'X-Custom-Header': 'custom-value' }, }); ``` ### Warnings Warnings (e.g. unsupported parameters) are available on the `warnings` property. ```ts import { openai } from '@ai-sdk/openai'; import { generateSpeech } from 'ai'; const audio = await generateSpeech({ model: openai.speech('gpt-4o-mini-tts'), text: 'Hello, world!', }); const warnings = audio.warnings; ``` ### Error Handling When `generateSpeech` cannot generate a valid audio, it throws a [`AI_NoSpeechGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-speech-generated-error). This error can arise for any of the following reasons: - The model failed to generate a response - The model generated a response that could not be parsed The error preserves the following information to help you log the issue: - `responses`: Metadata about the speech model responses, including timestamp, model, and headers. - `cause`: The cause of the error. You can use this for more detailed error handling. ```ts import { generateSpeech, NoSpeechGeneratedError } from 'ai'; import { openai } from '@ai-sdk/openai'; try { await generateSpeech({ model: openai.speech('gpt-4o-mini-tts'), text: 'Hello, world!', }); } catch (error) { if (NoSpeechGeneratedError.isInstance(error)) { console.log('AI_NoSpeechGeneratedError'); console.log('Cause:', error.cause); console.log('Responses:', error.responses); } } ``` ## Speech Models | Provider | Model | | ------------------------------------------------------------------------ | ----------------------------------- | | [OpenAI](/providers/ai-sdk-providers/openai#speech-models) | `tts-1` | | [OpenAI](/providers/ai-sdk-providers/openai#speech-models) | `tts-1-hd` | | [OpenAI](/providers/ai-sdk-providers/openai#speech-models) | `gpt-4o-mini-tts` | | [Mistral](/providers/ai-sdk-providers/mistral#speech-models) | `voxtral-mini-tts-2603` | | [Mistral](/providers/ai-sdk-providers/mistral#speech-models) | `voxtral-mini-tts-3-0` | | [Azure Speech](/providers/ai-sdk-providers/azure#mai-voice) | `mai-voice-2.1-flash` | | [Azure Speech](/providers/ai-sdk-providers/azure#mai-voice) | `mai-voice-2.1` | | [Azure Speech](/providers/ai-sdk-providers/azure#mai-voice) | `mai-voice-2-flash` | | [Azure Speech](/providers/ai-sdk-providers/azure#mai-voice) | `mai-voice-2` | | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models) | `eleven_v3` | | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models) | `eleven_multilingual_v2` | | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models) | `eleven_flash_v2_5` | | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models) | `eleven_flash_v2` | | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models) | `eleven_turbo_v2_5` | | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#speech-models) | `eleven_turbo_v2` | | [Hume](/providers/ai-sdk-providers/hume#speech-models) | `default` | | [Google](/providers/ai-sdk-providers/google#speech-models) | `gemini-2.5-flash-preview-tts` | | [Google](/providers/ai-sdk-providers/google#speech-models) | `gemini-2.5-pro-preview-tts` | | [Google](/providers/ai-sdk-providers/google#speech-models) | `gemini-3.1-flash-tts-preview` | | [Google](/providers/ai-sdk-providers/google#speech-models) | `gemini-3.8-flash-tts` | | [Google](/providers/ai-sdk-providers/google#speech-models) | `gemini-3.8-flash-lite-tts` | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#speech-models) | `gemini-2.5-flash-tts` | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#speech-models) | `gemini-2.5-pro-tts` | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#speech-models) | `gemini-2.5-flash-lite-preview-tts` | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#speech-models) | `gemini-3.1-flash-tts-preview` | | [xAI](/providers/ai-sdk-providers/xai#speech-models) | `default` | | [Cartesia](/providers/ai-sdk-providers/cartesia#speech-models) | `sonic-3.5` | | [Cartesia](/providers/ai-sdk-providers/cartesia#speech-models) | `sonic-3` | | [Cartesia](/providers/ai-sdk-providers/cartesia#speech-models) | `sonic-2` | | [Cartesia](/providers/ai-sdk-providers/cartesia#speech-models) | `sonic-turbo` | | [Fish Audio](/providers/ai-sdk-providers/fish-audio#speech-models) | `s1` | | [Fish Audio](/providers/ai-sdk-providers/fish-audio#speech-models) | `s2-pro` | | [Fish Audio](/providers/ai-sdk-providers/fish-audio#speech-models) | `s2.1-pro` | Above are a small subset of the speech models supported by the AI SDK providers. For more, see the respective provider documentation. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Telemetry description: Using OpenTelemetry with AI SDK Core url: "https://ai-sdk.dev/docs/ai-sdk-core/telemetry" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK uses [OpenTelemetry](https://opentelemetry.io/) to collect telemetry data. OpenTelemetry is an open-source observability framework designed to provide standardized instrumentation for collecting telemetry data. Check out the [AI SDK Observability Integrations](/providers/observability) to see providers that offer monitoring and tracing for AI SDK applications. ## Enabling telemetry ### Step 1: Register the OpenTelemetry integration OpenTelemetry span collection requires the `@ai-sdk/otel` package. Install it and register the integration once at application startup: ```bash pnpm install @ai-sdk/otel ``` ```ts import { registerTelemetry } from 'ai'; import { OpenTelemetry } from '@ai-sdk/otel'; registerTelemetry(new OpenTelemetry()); ``` #### Next.js For Next.js applications, create an `instrumentation.ts` file in your project root and register the AI SDK telemetry integration alongside your OpenTelemetry provider setup: ```ts title="instrumentation.ts" import { registerOTel } from '@vercel/otel'; import { registerTelemetry } from 'ai'; import { OpenTelemetry } from '@ai-sdk/otel'; export function register() { registerOTel({ serviceName: 'my-ai-app', }); registerTelemetry(new OpenTelemetry()); } ``` See the [Next.js OpenTelemetry guide](https://nextjs.org/docs/app/building-your-application/optimizing/open-telemetry) for more details on setting up OpenTelemetry in Next.js. For Node.js applications (without Next.js), register the integration at the top level of your entry file. ### Step 2: Enabling Telemetry Once a telemetry integration is registered, supported AI SDK calls emit telemetry events by default. This includes text and object generation, embeddings, reranking, speech generation, and both non-streaming and streaming transcription. You can still pass `telemetry` to attach metadata (like `functionId`) or to opt out of a specific call: ```ts {4-6} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a short story about a cat.', telemetry: { functionId: `story-agent`, }, }); ``` By default, both inputs and outputs are recorded. You can disable them by setting the `recordInputs` and `recordOutputs` options to `false`. Disabling the recording of inputs and outputs can be useful for privacy, data transfer, and performance reasons. You might for example want to disable recording inputs if they contain sensitive information. For `generateSpeech`, input telemetry contains the source text and output telemetry contains the generated audio byte length, media type, and format. For `transcribe` and `experimental_streamTranscribe`, telemetry contains the input audio byte length and media type plus the output transcript. Raw audio is never included in telemetry events. Streaming transcription reports its final byte length when the audio stream has been consumed and the transcription finishes. Custom integrations receive stable `generateSpeech` and `transcribe` operations through `onStart` and `onEnd`. To keep the experimental API isolated, `experimental_streamTranscribe` uses `experimental_onStreamTranscriptionStart` and `experimental_onStreamTranscriptionEnd`; all three operations report failures through `onError`. An integration that wraps or delegates to another integration must explicitly forward the experimental streaming transcription callbacks. Forwarding only the stable `onStart` and `onEnd` callbacks does not observe `experimental_streamTranscribe`. The built-in OpenTelemetry integrations record numeric provider usage by default, preserving each reported unit. The modern integration uses attributes such as `gen_ai.usage.characters`, `gen_ai.usage.seconds`, and `gen_ai.usage.input_tokens`; `LegacyOpenTelemetry` uses the corresponding `ai.usage.*` attributes. The provider-native usage object remains available to callbacks, and can also be included as the supplemental `ai.response.usage` attribute by enabling `usage: true`. ### Opting out Telemetry is opt-out. To disable telemetry for a specific call, set `isEnabled: false`: ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a short story about a cat.', telemetry: { isEnabled: false }, }); ``` Not registering integrations with `registerTelemetry()` prevents delivery to globally registered integrations, but does not disable per-call integrations or Node.js tracing-channel subscribers. To disable telemetry events for a call, including tracing-channel events, pass `telemetry: { isEnabled: false }`. Apply this setting to every call if you want to opt out throughout your application. ## Telemetry Metadata You can provide a `functionId` to identify the function that the telemetry data is for, and `runtimeContext` to include additional information in the telemetry data. For the broader context model, see [Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context). ```ts {4-10} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a short story about a cat.', runtimeContext: { userId: 'user_123', requestId: 'req_abc', }, telemetry: { functionId: 'my-awesome-function', }, }); ``` ### Runtime context telemetry inclusion `runtimeContext` can contain values that are useful inside your application but should not be sent to telemetry providers, such as user identifiers, tenant IDs, or credentials. Use `telemetry.includeRuntimeContext` to mark the top-level `runtimeContext` properties that should be included in telemetry: ```ts {8-10} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a short story about a cat.', runtimeContext: { userId: 'user_123', requestId: 'req_abc', }, telemetry: { includeRuntimeContext: { requestId: true, }, }, }); ``` In this example, telemetry integrations receive `runtimeContext` as `{ requestId: 'req_abc' }`. Properties set to `false` or omitted are excluded. If `telemetry.includeRuntimeContext` is omitted, no runtime context properties are included. `telemetry.includeRuntimeContext` is supported by `generateText`, `streamText`, `ToolLoopAgent`, `embed`, `embedMany`, `rerank`, and `experimental_decide`. `telemetry.includeRuntimeContext` only filters telemetry integrations, including OpenTelemetry integrations. Lifecycle callbacks and returned results still receive the full `runtimeContext`. ### Tool context Tool-specific context can also contain values that are useful during execution but should not be sent to telemetry providers, such as API keys or access tokens. Use `telemetry.includeToolsContext` to include selected top-level properties from a tool's `context` in telemetry: ```ts {16-20,23-28} const weatherTool = tool({ inputSchema: z.object({ location: z.string(), }), contextSchema: z.object({ weatherApiKey: z.string(), defaultUnit: z.enum(['celsius', 'fahrenheit']), }), execute: async ({ location }, { context }) => fetchWeather(location, context.weatherApiKey, context.defaultUnit), }); const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { weather: weatherTool }, toolsContext: { weather: { weatherApiKey: 'weather-123', defaultUnit: 'fahrenheit', }, }, prompt: 'What is the weather in San Francisco?', telemetry: { includeToolsContext: { weather: { defaultUnit: true, }, }, }, }); ``` In this example, telemetry integrations receive `toolsContext.weather` and tool execution telemetry event `toolContext` as `{ defaultUnit: 'fahrenheit' }`. Properties set to `false` or omitted are excluded. If `telemetry.includeToolsContext` is omitted, no tool context properties are included. `telemetry.includeToolsContext` is supported by `generateText`, `streamText`, and `ToolLoopAgent`. `telemetry.includeToolsContext` only filters telemetry integrations, including OpenTelemetry integrations. Tool execution, lifecycle callbacks, and returned results still receive the full tool context. See [Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context) for the difference between tool execution context and telemetry event context. ## Custom Tracer If you want your traces to use a `TracerProvider` other than the one provided by the `@opentelemetry/api` singleton, pass a custom `Tracer` to the `OpenTelemetry` constructor: ```ts {6-8} import { registerTelemetry } from 'ai'; import { OpenTelemetry } from '@ai-sdk/otel'; const tracerProvider = new NodeTracerProvider(); registerTelemetry( new OpenTelemetry({ tracer: tracerProvider.getTracer('gen_ai'), }), ); ``` ## Telemetry Integrations Telemetry integrations let you hook into the generation lifecycle to build custom observability — logging, analytics, DevTools, or any other monitoring system. Instead of wiring up individual callbacks on every call, you implement a `Telemetry` once and register it globally or pass it via `telemetry.integrations`. The `OpenTelemetry` and `LegacyOpenTelemetry` from `@ai-sdk/otel` are the built-in integrations for collecting OpenTelemetry spans (see [Enabling telemetry](#enabling-telemetry) above). ### Registering integrations globally Use `registerTelemetry` to register an integration once for all AI SDK calls: ```ts import { registerTelemetry } from 'ai'; import { OpenTelemetry } from '@ai-sdk/otel'; registerTelemetry(new OpenTelemetry()); ``` You can also register multiple integrations in a single call by passing them as additional arguments. They all receive the same lifecycle events: ```ts import { registerTelemetry } from 'ai'; import { OpenTelemetry } from '@ai-sdk/otel'; import { DevToolsTelemetry } from '@ai-sdk/devtools'; registerTelemetry(new OpenTelemetry(), DevToolsTelemetry()); ``` ### Per-call integrations You can also pass one or more integrations to individual `generateText` or `streamText` calls. When per-call integrations are provided, they replace the globally registered integrations for that call: ```ts {6-8} import { streamText } from 'ai'; import { DevToolsTelemetry } from '@ai-sdk/devtools'; const result = streamText({ model: openai('gpt-6-astra'), prompt: 'Hello!', telemetry: { integrations: [DevToolsTelemetry()], }, }); ``` You can combine multiple integrations — they all receive the same lifecycle events: ```ts telemetry: { integrations: [DevToolsTelemetry(), customLogger()], }, ``` Errors inside integrations are caught and do not break the generation flow. ### Tracing channel In Node.js, AI SDK telemetry lifecycle and execution events are also traced on the `ai:telemetry` [tracing channel](https://nodejs.org/api/diagnostics_channel.html#diagnostics_channeltracingchannelnameorchannels). This lets observability providers subscribe to AI SDK telemetry events without requiring users to register a separate telemetry integration, and bind async context across provider calls and tool executions. ```ts import { tracingChannel } from 'node:diagnostics_channel'; import { AI_SDK_TELEMETRY_TRACING_CHANNEL, type TelemetryTracingChannelMessage, } from 'ai'; tracingChannel(AI_SDK_TELEMETRY_TRACING_CHANNEL).subscribe({ start(message) { const telemetryMessage = message as TelemetryTracingChannelMessage; if (telemetryMessage.type === 'onStart') { // Inspect telemetryMessage.event and forward it to your provider. } }, }); ``` Tracing-channel events follow the same per-call telemetry settings as other telemetry integrations. Setting `telemetry: { isEnabled: false }` disables both registered integrations and tracing-channel events for that call. ### Building a custom integration Implement the `Telemetry` interface from the `ai` package. All methods are optional — implement only the lifecycle events you care about: ```ts import type { Telemetry } from 'ai'; class MyIntegration implements Telemetry { async onStart(event) { console.log('Generation started:', event.modelId); } async onStepEnd(event) { console.log( `Step ${event.stepNumber} done:`, event.usage.totalTokens, 'tokens', ); } async onToolExecutionEnd(event) { if (event.toolOutput.type === 'tool-result') { console.log( `Tool "${event.toolCall.toolName}" took ${event.toolExecutionMs}ms`, ); } else { console.error( `Tool "${event.toolCall.toolName}" failed:`, event.toolOutput.error, ); } } async onEnd(event) { console.log('Done. Total tokens:', event.usage.totalTokens); } async onAbort(event) { console.log('Stream aborted after', event.steps.length, 'finished steps'); } async onError(event) { console.error('Generation failed:', event.callId, event.error); } } export function myIntegration(): Telemetry { return new MyIntegration(); } ``` ### Available lifecycle methods - `onStart` (`(event: GenerateTextStartEvent) => void | PromiseLike`): Called when the generation operation begins, before any LLM calls. - `onStepStart` (`(event: GenerateTextStepStartEvent) => void | PromiseLike`): Called when a step (LLM call) begins, before the provider is called. - `onLanguageModelCallStart` (`(event: LanguageModelCallStartEvent) => void | PromiseLike`): Called immediately before the provider model call begins. Useful for model-only spans or logging. - `onLanguageModelCallEnd` (`(event: LanguageModelCallEndEvent) => void | PromiseLike`): Called after the model response has been normalized and parsed, but before any client-side tool execution begins. Includes provider-specific metadata when available. - `onToolExecutionStart` (`(event: ToolExecutionStartEvent) => void | PromiseLike`): Called when a tool's execute function is about to run. - `onToolExecutionEnd` (`(event: ToolExecutionEndEvent) => void | PromiseLike`): Called when a tool's execute function completes or errors. - `onStepEnd` (`(event: GenerateTextStepEndEvent) => void | PromiseLike`): Called when a step (LLM call) completes. - `onEmbedEnd` (`(event: EmbeddingModelCallEndEvent) => void | PromiseLike`): Called when an individual embedding model call completes. - `onRerankEnd` (`(event: RerankingModelCallEndEvent) => void | PromiseLike`): Called when an individual reranking model call completes. - `experimental_onDecideStart` (`(event: Experimental_DecideStartEvent) => void | PromiseLike`): Called when an experimental decision operation begins. - `experimental_onDecisionModelCallStart` (`(event: Experimental_DecisionModelCallStartEvent) => void | PromiseLike`): Called when the logical decision model call begins. The call includes any provider retries. - `experimental_onDecisionModelCallEnd` (`(event: Experimental_DecisionModelCallEndEvent) => void | PromiseLike`): Called when the logical decision model call, including any retries, completes. - `experimental_onDecideEnd` (`(event: Experimental_DecideEndEvent) => void | PromiseLike`): Called when an experimental decision operation completes. - `onEnd` (`(event: GenerateTextEndEvent) => void | PromiseLike`): Called when the entire generation completes (all steps finished). - `onAbort` (`(event: GenerateTextAbortEvent) => void | PromiseLike`): Called when a streaming text generation operation is aborted before it completes. - `onError` (`(event: { callId: string; error: unknown }) => void | PromiseLike`): Called when an unrecoverable error occurs. The event identifies the operation and contains the thrown value. Experimental decision events use the four `experimental_*` callbacks above. They are not included in the stable `onStart` and `onEnd` event unions. The event types for each method are the same as the corresponding [lifecycle callbacks](/docs/ai-sdk-core/lifecycle-callbacks). See the lifecycle callbacks documentation for the full property reference of each event. ## Collected Data The `@ai-sdk/otel` package provides two integrations that emit different span formats. The `OpenTelemetry` follows the [OpenTelemetry GenAI Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) and is the recommended integration. The `LegacyOpenTelemetry` integration emits legacy AI SDK-specific spans. ### GenAI Semantic Conventions The `OpenTelemetry` emits spans that follow the [OpenTelemetry Semantic Conventions for GenAI](https://opentelemetry.io/docs/specs/semconv/gen-ai/). By default, attributes use the `gen_ai.*` prefix. Provider names are mapped to well-known values (e.g. `openai`, `anthropic`, `gcp.vertex_ai`). #### generateText / streamText For `generateText` and `streamText`, the integration records 3 types of spans: - **`invoke_agent {modelId}`** (root span, `INTERNAL`): covers the full operation including all steps and tool calls. Initial attributes: - `gen_ai.operation.name`: `"invoke_agent"` - `gen_ai.provider.name`: the provider (e.g. `"openai"`, `"anthropic"`) - `gen_ai.request.model`: the requested model ID - `gen_ai.agent.name`: the `functionId` from telemetry settings - `gen_ai.system_instructions`: system instructions formatted as a JSON array of parts (when `recordInputs` is enabled) - `gen_ai.input.messages`: the input messages in [GenAI SemConv message format](#genai-message-format) (when `recordInputs` is enabled) - `gen_ai.request.temperature`: the temperature setting - `gen_ai.request.max_tokens`: the maximum output tokens - `gen_ai.request.top_p`: the topP setting - `gen_ai.request.top_k`: the topK setting - `gen_ai.request.frequency_penalty`: the frequency penalty - `gen_ai.request.presence_penalty`: the presence penalty - `gen_ai.request.stop_sequences`: the stop sequences - `gen_ai.request.seed`: the seed value Attributes set on finish: - `gen_ai.response.finish_reasons`: array of finish reasons (e.g. `["stop"]`, `["tool_call"]`) - `gen_ai.usage.input_tokens`: the number of input tokens used - `gen_ai.usage.output_tokens`: the number of output tokens used - `gen_ai.usage.cache_read.input_tokens`: cached input tokens read - `gen_ai.usage.cache_creation.input_tokens`: cached input tokens created - `gen_ai.output.messages`: the output in [GenAI SemConv message format](#genai-message-format) (when `recordOutputs` is enabled) - **`chat {modelId}`** (step span, `CLIENT`): one span per LLM provider call, nested under the root span. Initial attributes: - `gen_ai.operation.name`: `"chat"` - `gen_ai.provider.name`: the provider - `gen_ai.request.model`: the requested model ID - `gen_ai.request.temperature`, `gen_ai.request.max_tokens`, `gen_ai.request.top_p`, `gen_ai.request.top_k`, `gen_ai.request.frequency_penalty`, `gen_ai.request.presence_penalty`, `gen_ai.request.stop_sequences`: request parameters - `gen_ai.system_instructions`: instructions supplied separately from the chat history, formatted as a JSON array of parts (when `recordInputs` is enabled) - `gen_ai.input.messages`: the prompt messages in [GenAI SemConv message format](#genai-message-format), including system messages that are part of the chat history in their original order (when `recordInputs` is enabled) - `gen_ai.tool.definitions`: the tool definitions as stringified JSON (when `recordInputs` is enabled) Attributes set on finish: - `gen_ai.response.finish_reasons`: array of finish reasons - `gen_ai.response.id`: the response ID from the provider - `gen_ai.response.model`: the model that generated the response (may differ from the requested model) - `gen_ai.usage.input_tokens`: input tokens used in this step - `gen_ai.usage.output_tokens`: output tokens used in this step - `gen_ai.usage.cache_read.input_tokens`: cached input tokens read - `gen_ai.usage.cache_creation.input_tokens`: cached input tokens created - `gen_ai.client.operation.duration`: provider call duration, in seconds - `gen_ai.client.operation.time_to_first_chunk`: time to the first streamed output chunk, in seconds (streaming calls only) - `gen_ai.client.operation.time_per_output_chunk`: average time between streamed output chunks, in seconds (streaming calls with multiple output chunks only) - `gen_ai.output.messages`: the output in [GenAI SemConv message format](#genai-message-format) (when `recordOutputs` is enabled) - **`execute_tool {toolName}`** (tool span, `INTERNAL`): one span per tool execution, nested under the step span. See [GenAI tool call spans](#genai-tool-call-spans) for details. #### Deprecated object APIs (generateObject / streamObject) `generateObject` and `streamObject` are deprecated. Use `generateText` and `streamText` with the `output` property instead. The deprecated object APIs emit the same span hierarchy as `generateText`/`streamText` with these additional attributes on the root span: - `gen_ai.output.type`: `"json"` The step spans also include `gen_ai.output.type: "json"`, and `gen_ai.output.messages` contains the generated object as a text part. #### embed / embedMany For `embed` and `embedMany`, the integration records spans with `CLIENT` kind: - **`embeddings {modelId}`** (root span): covers the full embedding operation. Initial attributes: - `gen_ai.operation.name`: `"embeddings"` - `gen_ai.provider.name`: the provider - `gen_ai.request.model`: the requested model ID - **`embeddings {modelId}`** (inner span): one span per provider request, nested under the root span. `embed` creates one inner span. `embedMany` creates one inner span per provider batch call. Initial attributes: - `gen_ai.operation.name`: `"embeddings"` - `gen_ai.provider.name`: the provider - `gen_ai.request.model`: the model ID Attributes set on finish: - `gen_ai.usage.input_tokens`: the number of tokens used Usage is recorded only on inner provider-request spans. For `embedMany`, sum the usage from the inner spans to get the total usage for the operation. #### rerank For `rerank`, the integration records spans with `CLIENT` kind: - **`rerank {modelId}`** (root span): covers the full rerank operation. Initial attributes: - `gen_ai.operation.name`: `"rerank"` - `gen_ai.provider.name`: the provider - `gen_ai.request.model`: the requested model ID - **`rerank {modelId}`** (inner span): one span per provider rerank call, nested under the root span. Initial attributes: - `gen_ai.operation.name`: `"rerank"` - `gen_ai.provider.name`: the provider - `gen_ai.request.model`: the model ID #### generateSpeech / transcribe / experimental\_streamTranscribe Each call records a single `CLIENT` span named `{operationId} {modelId}`, where `operationId` is `ai.generateSpeech`, `ai.transcribe`, or `ai.streamTranscribe`. - `gen_ai.output.type`: `"speech"` for speech generation and `"text"` for transcription. - `gen_ai.request.stream`: `true` for streaming transcription and `false` otherwise. - `gen_ai.input.messages`: speech generation input text as a user message with a text part, when `recordInputs` is enabled. - `gen_ai.output.messages`: the completed transcript as an assistant message with a text part, when `recordOutputs` is enabled. Streaming transcription records this once on completion. The existing `ai.request.text` and `ai.response.text` attributes are also retained. Audio byte length, media type, and output format remain in the `ai.request.audio.*` and `ai.response.audio.*` attributes, subject to the corresponding input/output recording controls. Raw audio is not captured, so transcription input and speech output do not populate message attributes with audio content or placeholder audio parts. #### experimental\_decide For `experimental_decide`, the integration records two spans with `CLIENT` kind: a **`decide {modelId}`** root span for the whole operation and one **`decide {modelId}`** child span for decision model work, including retries. Both include the provider and requested model with `gen_ai.operation.name: "decide"`. Token usage is recorded on the child span. When decision attributes are enabled, answers are recorded on both spans so the operation span contains its output. `decide` is an AI SDK-defined custom operation name, not a well-known OpenTelemetry GenAI operation. OpenTelemetry permits custom operation names when no well-known value describes the operation. OpenTelemetry's `gen_ai.evaluation.result` event describes the result of evaluating a GenAI output for quality or other characteristics. In contrast, `experimental_decide` can use arbitrary JSON state for classification, routing, scoring, and similar tasks. The integration therefore does not emit a `gen_ai.evaluation.result` event. Decision state, questions, and answers also do not have applicable GenAI semantic-convention attributes. Enable the `experimental_decision` supplemental option to record them under AI SDK-specific `ai.decision.*` attributes. #### GenAI span details ##### GenAI message format The `gen_ai.input.messages` and `gen_ai.output.messages` attributes follow the [OpenTelemetry GenAI Semantic Conventions message format](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/). Messages are JSON arrays of objects with a `role` and a `parts` array. Each part has a `type` and type-specific fields: - `text`: `{ type: "text", content: "..." }` - `reasoning`: `{ type: "reasoning", content: "..." }` - `tool_call`: `{ type: "tool_call", id: "...", name: "...", arguments: ... }` - `tool_call_response`: `{ type: "tool_call_response", id: "...", response: ... }` - `blob`: `{ type: "blob", modality: "image"|"video"|"audio", mime_type: "...", content: "..." }` (base64-encoded) - `uri`: `{ type: "uri", modality: "image"|"video"|"audio", mime_type: "...", uri: "..." }` (for URL-based files) Output messages also include a `finish_reason` field (e.g. `"stop"`, `"tool_call"`, `"length"`, `"content_filter"`). Instructions supplied through `instructions` (or the deprecated `system` option) are recorded separately in `gen_ai.system_instructions` as a JSON array of `{ type: "text", content: "..." }` parts. System messages supplied as chat history with `allowSystemInMessages` remain in `gen_ai.input.messages` in their original positions. ##### GenAI tool call spans Tool call spans (`execute_tool {toolName}`) are nested under the step span and contain: - `gen_ai.operation.name`: `"execute_tool"` - `gen_ai.tool.name`: the name of the tool - `gen_ai.tool.call.id`: the tool call ID - `gen_ai.tool.type`: `"function"` - `gen_ai.tool.call.arguments`: the input arguments (stringified JSON, when `recordInputs` is enabled) - `gen_ai.execute_tool.duration`: the tool execution duration, in seconds - `gen_ai.tool.call.result`: the output result (stringified JSON, when `recordOutputs` is enabled). Only set when the tool call succeeds. ### Custom OpenTelemetry span attributes Use `enrichSpan` to add custom attributes to spans created by the `OpenTelemetry` integration. This is useful when an observability backend needs vendor-specific attributes that are not AI SDK-owned semantics. ```ts import { registerTelemetry } from 'ai'; import { OpenTelemetry } from '@ai-sdk/otel'; registerTelemetry( new OpenTelemetry({ enrichSpan: ({ spanType, operationId, callId, runtimeContext }) => { return { ...getCustomAttributes(runtimeContext, spanType), 'my_app.operation_id': operationId, 'my_app.call_id': callId, }; }, }), ); ``` The callback runs when each span is created and receives: - `spanType`: the type of span being created (`operation`, `step`, `languageModel`, `tool`, `embedding`, `reranking`, or `experimental_decision`). - `operationId`: the AI SDK operation ID for the current call, such as `ai.generateText` or `ai.streamText`. - `callId`: the unique ID for the current AI SDK call. - `runtimeContext`: the telemetry-filtered runtime context for text generation, embedding, reranking, and decision spans. Text generation spans also reflect updates from `prepareStep`. Custom attributes are merged with AI SDK attributes on the span. AI SDK-owned attributes take precedence when a custom attribute uses the same key. `telemetry.includeRuntimeContext` is applied before telemetry integrations receive `runtimeContext`, so custom span enrichment only receives top-level runtime context properties explicitly set to `true`. If the option is omitted, no runtime context properties are included. Embedding and reranking operations can share the same runtime context with text generation to attribute their spans to the same application request: ```ts await embed({ model: 'openai/text-embedding-3-small', value: 'sunny day at the beach', runtimeContext: { requestId: 'req_abc', userId: 'user_123', }, telemetry: { includeRuntimeContext: { requestId: true }, }, }); ``` A globally registered `OpenTelemetry({ enrichSpan })` integration receives `{ requestId: 'req_abc' }` for the operation span and its embedding model span. The same applies to every chunk in `embedMany` and to the operation and model spans in `rerank`. The `onStart` and `onEnd` callbacks receive the full context. ### Supplemental AI SDK attributes on OpenTelemetry spans The GenAI semantic conventions cover the core model, prompt, response, tool call, and usage data. You can opt into additional AI SDK-specific attributes for data that is not represented by GenAI semantics: ```ts import { registerTelemetry } from 'ai'; import { OpenTelemetry } from '@ai-sdk/otel'; registerTelemetry( new OpenTelemetry({ usage: true, providerMetadata: true, runtimeContext: true, }), ); ``` This does not create additional AI SDK-specific spans such as `ai.generateText`, `ai.generateText.doGenerate`, or `ai.toolCall`. Instead, it adds only the selected supplemental attributes to the spans emitted by `OpenTelemetry`, such as `invoke_agent {modelId}`, `step {n}`, `chat {modelId}`, and `execute_tool {toolName}`. All supplemental attributes are disabled by default. Enable only the data you want to collect: ```ts registerTelemetry( new OpenTelemetry({ usage: true, providerMetadata: true, embedding: true, reranking: true, experimental_decision: true, runtimeContext: true, headers: true, toolChoice: true, schema: true, }), ); ``` The available options are: - `usage`: detailed usage attributes that are not covered by GenAI usage attributes, such as uncached input tokens and output text/reasoning token details. - `providerMetadata`: `ai.response.providerMetadata` on operation, step, and model-call spans. - `embedding`: embedding inputs and outputs. - `reranking`: rerank input documents and ranking output. - `experimental_decision`: experimental decision state, questions, and answers. - `runtimeContext`: `ai.settings.context.*`. - `headers`: `ai.request.headers.*`. - `toolChoice`: `ai.prompt.toolChoice`. - `schema`: object generation schema and output mode attributes. The `recordInputs` and `recordOutputs` telemetry options are still respected for supplemental input and output attributes. ### Legacy AI SDK Spans (`LegacyOpenTelemetry`) The `LegacyOpenTelemetry` integration emits spans using AI SDK-specific `ai.*` prefixed attributes. This is the legacy format. Consider migrating to the `OpenTelemetry` for better compatibility with observability platforms. #### generateText function `generateText` records 3 types of spans: - `ai.generateText` (span): the full length of the generateText call. It contains 1 or more `ai.generateText.doGenerate` spans. It contains the [basic LLM span information](#basic-llm-span-information) and the following attributes: - `operation.name`: `ai.generateText` and the functionId that was set through `telemetry.functionId` - `ai.operationId`: `"ai.generateText"` - `ai.prompt`: the prompt that was used when calling `generateText` - `ai.response.text`: the text that was generated - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON) - `ai.response.finishReason`: the reason why the generation finished - `ai.settings.maxOutputTokens`: the maximum number of output tokens that were set - `ai.generateText.doGenerate` (span): a provider doGenerate call. It can contain `ai.toolCall` spans. It contains the [call LLM span information](#call-llm-span-information) and the following attributes: - `operation.name`: `ai.generateText.doGenerate` and the functionId that was set through `telemetry.functionId` - `ai.operationId`: `"ai.generateText.doGenerate"` - `ai.prompt.messages`: the messages that were passed into the provider - `ai.prompt.tools`: array of stringified tool definitions. The serialized tools can be of type `function` or `provider`. Function tools include `FunctionTool` and `DynamicTool` definitions and have a `name`, `description` (optional), and `inputSchema` (JSON schema). Provider tools include `ProviderDefinedTool` and `ProviderExecutedTool` definitions and have a `name`, `id`, and `args` (Record). - `ai.prompt.toolChoice`: the stringified tool choice setting (JSON). It has a `type` property (`auto`, `none`, `required`, `tool`), and if the type is `tool`, a `toolName` property with the specific tool. - `ai.response.text`: the text that was generated - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON) - `ai.response.finishReason`: the reason why the generation finished - `ai.toolCall` (span): a tool call that is made as part of the generateText call. See [Legacy tool call spans](#legacy-tool-call-spans) for more details. #### streamText function `streamText` records 3 types of spans and 2 types of events: - `ai.streamText` (span): the full length of the streamText call. It contains a `ai.streamText.doStream` span. It contains the [basic LLM span information](#basic-llm-span-information) and the following attributes: - `operation.name`: `ai.streamText` and the functionId that was set through `telemetry.functionId` - `ai.operationId`: `"ai.streamText"` - `ai.prompt`: the prompt that was used when calling `streamText` - `ai.response.text`: the text that was generated - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON) - `ai.response.finishReason`: the reason why the generation finished - `ai.settings.maxOutputTokens`: the maximum number of output tokens that were set - `ai.streamText.doStream` (span): a provider doStream call. This span contains an `ai.stream.firstChunk` event and `ai.toolCall` spans. It contains the [call LLM span information](#call-llm-span-information) and the following attributes: - `operation.name`: `ai.streamText.doStream` and the functionId that was set through `telemetry.functionId` - `ai.operationId`: `"ai.streamText.doStream"` - `ai.prompt.messages`: the messages that were passed into the provider - `ai.prompt.tools`: array of stringified tool definitions. The serialized tools can be of type `function` or `provider`. Function tools include `FunctionTool` and `DynamicTool` definitions and have a `name`, `description` (optional), and `inputSchema` (JSON schema). Provider tools include `ProviderDefinedTool` and `ProviderExecutedTool` definitions and have a `name`, `id`, and `args` (Record). - `ai.prompt.toolChoice`: the stringified tool choice setting (JSON). It has a `type` property (`auto`, `none`, `required`, `tool`), and if the type is `tool`, a `toolName` property with the specific tool. - `ai.response.text`: the text that was generated - `ai.response.toolCalls`: the tool calls that were made as part of the generation (stringified JSON) - `ai.response.msToFirstChunk`: the time it took to receive the first chunk in milliseconds - `ai.response.msToFinish`: the time it took to receive the finish part of the LLM stream in milliseconds - `ai.response.avgCompletionTokensPerSecond`: the average number of completion tokens per second - `ai.response.finishReason`: the reason why the generation finished - `ai.toolCall` (span): a tool call that is made as part of the streamText call. See [Legacy tool call spans](#legacy-tool-call-spans) for more details. - `ai.stream.firstChunk` (event): an event that is emitted when the first chunk of the stream is received. - `ai.response.msToFirstChunk`: the time it took to receive the first chunk - `ai.stream.finish` (event): an event that is emitted when the finish part of the LLM stream is received. #### Deprecated object APIs `generateObject` and `streamObject` are deprecated. Use `generateText` and `streamText` with the `output` property instead. If you still run deprecated object APIs, you will see legacy span names: - `generateObject`: `ai.generateObject`, `ai.generateObject.doGenerate` - `streamObject`: `ai.streamObject`, `ai.streamObject.doStream`, `ai.stream.firstChunk` Legacy object spans include the same core metadata as other LLM spans, plus object-specific attributes such as `ai.schema.*`, `ai.response.object`, and `ai.settings.output`. #### embed function `embed` records 2 types of spans: - `ai.embed` (span): the full length of the embed call. It contains 1 `ai.embed.doEmbed` spans. It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes: - `operation.name`: `ai.embed` and the functionId that was set through `telemetry.functionId` - `ai.operationId`: `"ai.embed"` - `ai.value`: the value that was passed into the `embed` function - `ai.embedding`: a JSON-stringified embedding - `ai.embed.doEmbed` (span): a provider doEmbed call. It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes: - `operation.name`: `ai.embed.doEmbed` and the functionId that was set through `telemetry.functionId` - `ai.operationId`: `"ai.embed.doEmbed"` - `ai.values`: the values that were passed into the provider (array) - `ai.embeddings`: an array of JSON-stringified embeddings #### embedMany function `embedMany` records 2 types of spans: - `ai.embedMany` (span): the full length of the embedMany call. It contains 1 or more `ai.embedMany.doEmbed` spans. It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes: - `operation.name`: `ai.embedMany` and the functionId that was set through `telemetry.functionId` - `ai.operationId`: `"ai.embedMany"` - `ai.values`: the values that were passed into the `embedMany` function - `ai.embeddings`: an array of JSON-stringified embedding - `ai.embedMany.doEmbed` (span): a provider doEmbed call. It contains the [basic embedding span information](#basic-embedding-span-information) and the following attributes: - `operation.name`: `ai.embedMany.doEmbed` and the functionId that was set through `telemetry.functionId` - `ai.operationId`: `"ai.embedMany.doEmbed"` - `ai.values`: the values that were sent to the provider - `ai.embeddings`: an array of JSON-stringified embeddings for each value #### Legacy span details ##### Basic LLM span information Many spans that use LLMs (`ai.generateText`, `ai.generateText.doGenerate`, `ai.streamText`, `ai.streamText.doStream`) contain the following attributes: - `resource.name`: the functionId that was set through `telemetry.functionId` - `ai.model.id`: the id of the model - `ai.model.provider`: the provider of the model - `ai.request.headers.*`: the request headers that were passed in through `headers` - `ai.response.providerMetadata`: provider specific metadata returned with the generation response - `ai.settings.maxRetries`: the maximum number of retries that were set - `ai.telemetry.functionId`: the functionId that was set through `telemetry.functionId` - `ai.settings.runtimeContext.*`: the runtime context that was passed in through the `runtimeContext` option, filtered to top-level properties marked with `telemetry.includeRuntimeContext` when configured - `ai.usage.completionTokens`: the number of completion tokens that were used - `ai.usage.promptTokens`: the number of prompt tokens that were used ##### Call LLM span information Spans that correspond to individual LLM calls (`ai.generateText.doGenerate`, `ai.streamText.doStream`) contain [basic LLM span information](#basic-llm-span-information) and the following attributes: - `ai.response.model`: the model that was used to generate the response. This can be different from the model that was requested if the provider supports aliases. - `ai.response.id`: the id of the response. Uses the ID from the provider when available. - `ai.response.timestamp`: the timestamp of the response. Uses the timestamp from the provider when available. - [Semantic Conventions for GenAI operations](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/) - `gen_ai.system`: the provider that was used - `gen_ai.request.model`: the model that was requested - `gen_ai.request.temperature`: the temperature that was set - `gen_ai.request.max_tokens`: the maximum number of tokens that were set - `gen_ai.request.frequency_penalty`: the frequency penalty that was set - `gen_ai.request.presence_penalty`: the presence penalty that was set - `gen_ai.request.top_k`: the topK parameter value that was set - `gen_ai.request.top_p`: the topP parameter value that was set - `gen_ai.request.stop_sequences`: the stop sequences - `gen_ai.response.finish_reasons`: the finish reasons that were returned by the provider - `gen_ai.response.model`: the model that was used to generate the response. This can be different from the model that was requested if the provider supports aliases. - `gen_ai.response.id`: the id of the response. Uses the ID from the provider when available. - `gen_ai.usage.input_tokens`: the number of prompt tokens that were used - `gen_ai.usage.output_tokens`: the number of completion tokens that were used ##### Basic embedding span information Many spans that use embedding models (`ai.embed`, `ai.embed.doEmbed`, `ai.embedMany`, `ai.embedMany.doEmbed`) contain the following attributes: - `ai.model.id`: the id of the model - `ai.model.provider`: the provider of the model - `ai.request.headers.*`: the request headers that were passed in through `headers` - `ai.settings.maxRetries`: the maximum number of retries that were set - `ai.telemetry.functionId`: the functionId that was set through `telemetry.functionId` - `ai.settings.runtimeContext.*`: the runtime context that was passed in through the `runtimeContext` option - `ai.usage.tokens`: the number of tokens that were used - `resource.name`: the functionId that was set through `telemetry.functionId` ##### Legacy tool call spans Tool call spans (`ai.toolCall`) contain the following attributes: - `operation.name`: `"ai.toolCall"` - `ai.operationId`: `"ai.toolCall"` - `ai.toolCall.name`: the name of the tool - `ai.toolCall.id`: the id of the tool call - `ai.toolCall.args`: the input parameters of the tool call - `ai.toolCall.result`: the output result of the tool call. Only available if the tool call is successful and the result is serializable. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Testing description: Learn how to use AI SDK Core mock providers for testing. url: "https://ai-sdk.dev/docs/ai-sdk-core/testing" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Testing language models can be challenging, because they are non-deterministic and calling them is slow and expensive. To enable you to unit test your code that uses the AI SDK, the AI SDK Core includes mock providers and test helpers. You can import the following helpers from `ai/test`: - `MockEmbeddingModelV4`: A mock embedding model using the [embedding model v4 specification](https://github.com/vercel/ai/blob/main/packages/provider/src/embedding-model/v4/embedding-model-v4.ts). - `MockLanguageModelV4`: A mock language model using the [language model v4 specification](https://github.com/vercel/ai/blob/main/packages/provider/src/language-model/v4/language-model-v4.ts). - `mockId`: Provides an incrementing integer ID. - `mockValues`: Iterates over an array of values with each call. Returns the last value when the array is exhausted. You can also import [`simulateReadableStream`](/docs/reference/ai-sdk-core/simulate-readable-stream) from `ai` to simulate a readable stream with delays. With mock providers and test helpers, you can control the output of the AI SDK and test your code in a repeatable and deterministic way without actually calling a language model provider. ## Examples You can use the test helpers with the AI Core functions in your unit tests: ### generateText ```ts import { generateText } from 'ai'; import { MockLanguageModelV4 } from 'ai/test'; const result = await generateText({ model: new MockLanguageModelV4({ doGenerate: async () => ({ content: [{ type: 'text', text: `Hello, world!` }], finishReason: { unified: 'stop', raw: undefined }, usage: { inputTokens: { total: 10, noCache: 10, cacheRead: undefined, cacheWrite: undefined, }, outputTokens: { total: 20, text: 20, reasoning: undefined, }, }, warnings: [], }), }), prompt: 'Hello, test!', }); ``` ### streamText ```ts import { streamText, simulateReadableStream } from 'ai'; import { MockLanguageModelV4 } from 'ai/test'; const result = streamText({ model: new MockLanguageModelV4({ doStream: async () => ({ stream: simulateReadableStream({ chunks: [ { type: 'text-start', id: 'text-1' }, { type: 'text-delta', id: 'text-1', delta: 'Hello' }, { type: 'text-delta', id: 'text-1', delta: ', ' }, { type: 'text-delta', id: 'text-1', delta: 'world!' }, { type: 'text-end', id: 'text-1' }, { type: 'finish', finishReason: { unified: 'stop', raw: undefined }, logprobs: undefined, usage: { inputTokens: { total: 3, noCache: 3, cacheRead: undefined, cacheWrite: undefined, }, outputTokens: { total: 10, text: 10, reasoning: undefined, }, }, }, ], }), }), }), prompt: 'Hello, test!', }); ``` ### generateText with Output ```ts import { generateText, Output } from 'ai'; import { MockLanguageModelV4 } from 'ai/test'; import { z } from 'zod'; const result = await generateText({ model: new MockLanguageModelV4({ doGenerate: async () => ({ content: [{ type: 'text', text: `{"content":"Hello, world!"}` }], finishReason: { unified: 'stop', raw: undefined }, usage: { inputTokens: { total: 10, noCache: 10, cacheRead: undefined, cacheWrite: undefined, }, outputTokens: { total: 20, text: 20, reasoning: undefined, }, }, warnings: [], }), }), output: Output.object({ schema: z.object({ content: z.string() }) }), prompt: 'Hello, test!', }); ``` ### streamText with Output ```ts import { streamText, Output, simulateReadableStream } from 'ai'; import { MockLanguageModelV4 } from 'ai/test'; import { z } from 'zod'; const result = streamText({ model: new MockLanguageModelV4({ doStream: async () => ({ stream: simulateReadableStream({ chunks: [ { type: 'text-start', id: 'text-1' }, { type: 'text-delta', id: 'text-1', delta: '{ ' }, { type: 'text-delta', id: 'text-1', delta: '"content": ' }, { type: 'text-delta', id: 'text-1', delta: `"Hello, ` }, { type: 'text-delta', id: 'text-1', delta: `world` }, { type: 'text-delta', id: 'text-1', delta: `!"` }, { type: 'text-delta', id: 'text-1', delta: ' }' }, { type: 'text-end', id: 'text-1' }, { type: 'finish', finishReason: { unified: 'stop', raw: undefined }, logprobs: undefined, usage: { inputTokens: { total: 3, noCache: 3, cacheRead: undefined, cacheWrite: undefined, }, outputTokens: { total: 10, text: 10, reasoning: undefined, }, }, }, ], }), }), }), output: Output.object({ schema: z.object({ content: z.string() }) }), prompt: 'Hello, test!', }); ``` ### ToolLoopAgent You can provide a sequence of mock responses to test an agent that calls a tool and continues to a final response: ```ts import { ToolLoopAgent, tool } from 'ai'; import { MockLanguageModelV4 } from 'ai/test'; import { expect, it } from 'vitest'; import { z } from 'zod'; it('executes a tool and continues the loop', async () => { const weatherRequests: string[] = []; const usage = { inputTokens: { total: 10, noCache: 10, cacheRead: undefined, cacheWrite: undefined, }, outputTokens: { total: 5, text: 5, reasoning: undefined, }, }; const model = new MockLanguageModelV4({ doGenerate: [ { content: [ { type: 'tool-call', toolCallId: 'call-1', toolName: 'weather', input: '{"city":"San Francisco"}', }, ], finishReason: { unified: 'tool-calls', raw: undefined }, usage, warnings: [], }, { content: [{ type: 'text', text: 'It is 72°F in San Francisco.' }], finishReason: { unified: 'stop', raw: undefined }, usage, warnings: [], }, ], }); const agent = new ToolLoopAgent({ model, tools: { weather: tool({ description: 'Get the weather for a city.', inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => { weatherRequests.push(city); return { temperature: 72 }; }, }), }, }); const result = await agent.generate({ prompt: 'What is the weather in San Francisco?', }); expect(weatherRequests).toEqual(['San Francisco']); expect(result.text).toBe('It is 72°F in San Francisco.'); expect(model.doGenerateCalls).toHaveLength(2); }); ``` ### Simulate UI Message Stream Responses You can also simulate [UI Message Stream](/docs/ai-sdk-ui/stream-protocol#ui-message-stream-example) responses for testing, debugging, or demonstration purposes. Here is a Next example: ```ts title="route.ts" import { simulateReadableStream } from 'ai'; export async function POST(req: Request) { return new Response( simulateReadableStream({ initialDelayInMs: 1000, // Delay before the first chunk chunkDelayInMs: 300, // Delay between chunks chunks: [ `data: {"type":"start","messageId":"msg-123"}\n\n`, `data: {"type":"text-start","id":"text-1"}\n\n`, `data: {"type":"text-delta","id":"text-1","delta":"This"}\n\n`, `data: {"type":"text-delta","id":"text-1","delta":" is an"}\n\n`, `data: {"type":"text-delta","id":"text-1","delta":" example."}\n\n`, `data: {"type":"text-end","id":"text-1"}\n\n`, `data: {"type":"finish"}\n\n`, `data: [DONE]\n\n`, ], }).pipeThrough(new TextEncoderStream()), { status: 200, headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', 'x-vercel-ai-ui-message-stream': 'v1', }, }, ); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Tool Search description: Let models discover tools on demand, with direct calling or cache-preserving code mode. url: "https://ai-sdk.dev/docs/ai-sdk-core/tool-search" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `toolSearch()` lets a model find the tools it needs without loading every tool's definition into its initial context. Register tools with `deferLoading: true`; search matches their names and descriptions and makes them available on the **next model step**. Use it with `generateText`, `streamText`, `ToolLoopAgent`, or `WorkflowAgent` from `@ai-sdk/workflow`. The model supplies a search query; the factory optionally accepts an application-defined `search` callback and a `maxResults` limit. `WorkflowAgent` supports direct tool calling; the other APIs also support cache-preserving code mode. ## Direct Tool Calling The model initially sees only `search`. After searching, matching definitions are added to the provider's tool list and the model calls those tools directly. This changes the tool definitions and can invalidate the cached prompt prefix. ```ts import { generateText, isStepCount, tool, toolSearch } from 'ai'; import { z } from 'zod/v4'; const weather = tool({ deferLoading: true, description: 'Get the weather forecast for a city.', inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => ({ city, forecast: 'Rain tomorrow.' }), }); const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { search: toolSearch(), weather }, stopWhen: isStepCount(5), prompt: 'Will it rain in Bangalore tomorrow?', }); ``` ## Code Mode With [code mode](/docs/ai-sdk-core/code-mode), the model searches and calls tools through generated code. Set `toolDiscovery: 'conversation'` to announce discovered definitions in user messages while keeping the provider-visible code tool unchanged. **This preserves the tool-definition cache as tools are discovered.** Actual prompt-cache reuse depends on the provider. Using the `weather` tool defined above: ```ts import { experimental_codeModeTool as codeModeTool } from '@ai-sdk/code-mode'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { code: codeModeTool({ toolDiscovery: 'conversation' }), search: toolSearch(), weather, }, experimental_toolCallers: { search: ['code'], weather: ['code'], }, stopWhen: isStepCount(5), prompt: 'Will it rain in Bangalore tomorrow?', }); ``` The model first runs `tools.search({ query: 'weather forecast' })`. On the next step, it receives the updated capability catalog and can call `tools.weather({ city: 'Bangalore' })`. In both modes, search loads up to five matches by default, respects `activeTools` and caller routing, and keeps discovered tools available for the rest of the generation. MCP client tools work too: add `deferLoading: true` to the tools returned by `client.tools()`. Set `maxResults` to change the number of matching tools loaded per search: ```ts const search = toolSearch({ maxResults: 10 }); ``` `maxResults` must be a positive safe integer. A larger limit can load related tools in one search, but also adds more tool definitions to the model context. It does not change keyword matching or ranking. ## Custom Search Provide a `search` callback when your application needs different matching or ranking. It receives the query and all eligible deferred tool names and resolved descriptions, and returns ranked names. It can be synchronous or asynchronous. ```ts const search = toolSearch({ search: ({ query, tools }) => { // Discover related Gmail actions together, in the application's preferred order. const preferred = /\bgmail\b/i.test(query) ? ['gmail_list_messages', 'gmail_get_message'] : []; return preferred.filter(name => tools.some(tool => tool.name === name)); }, }); ``` The SDK filters unknown names and duplicates before keeping the first `maxResults` valid results (five by default). Tools excluded by `activeTools` or caller routing remain unavailable. Discovery still takes effect on the next model step. Without a callback, search keeps its default keyword matching and ranking. See the [`toolSearch()` reference](/docs/reference/ai-sdk-core/tool-search) for input, output, and matching details. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Tool Calling description: Learn about tool calling and multi-step calls (using stopWhen) with AI SDK Core. url: "https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). As covered under Foundations, [tools](/docs/foundations/tools) are objects that can be called by the model to perform a specific task. Function tools and dynamic tools contain several core elements: - **`description`**: An optional description of the tool that can influence when the tool is picked. It can be a string or a function that derives the description from the tool's context and experimental sandbox. - **`inputSchema`**: A [Zod schema](/docs/foundations/tools#schemas) or a [JSON schema](/docs/reference/ai-sdk-core/json-schema) that defines the input parameters. The schema is consumed by the LLM, and also used to validate the LLM tool calls. - **`execute`**: An optional async function that is called with the inputs from the tool call. It produces a value of type `RESULT` (generic type). It is optional because you might want to forward tool calls to the client or to a queue instead of executing them in the same process. - **`strict`**: *(optional, boolean)* Enables strict tool calling when supported by the provider You can use the [`tool`](/docs/reference/ai-sdk-core/tool) helper function to infer the types of the `execute` parameters. The `tools` parameter of `generateText` and `streamText` is an object that has the tool names as keys and the tools as values: ```ts {7-18} import { z } from 'zod'; import { generateText, tool, isStepCount } from 'ai'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => ({ location, temperature: 72 + Math.floor(Math.random() * 21) - 10, }), }), }, stopWhen: isStepCount(5), prompt: 'What is the weather in San Francisco?', }); ``` When a model uses a tool, it is called a "tool call" and the output of the tool is called a "tool result". Tool calling is not restricted to only text generation. You can also use it to render user interfaces (Generative UI). ## Dynamic Descriptions Tool descriptions can be fixed strings or functions. Use a function when the description sent to the model should depend on the current tool context or experimental sandbox, such as a tenant, project, environment, or workspace. The description function is resolved before the tool definition is sent to the model for each generation step. It receives the matching tool `context` from `toolsContext` and the current `experimental_sandbox`, if one was provided. If `prepareStep` updates `toolsContext` or `experimental_sandbox`, the next step uses those updated values. ```ts {5-16,32-35} import { generateText, tool } from 'ai'; import { z } from 'zod'; const shell = tool({ contextSchema: z.object({ projectName: z.string(), }), description: ({ context, experimental_sandbox }) => [ `Run shell commands for the ${context.projectName} project.`, experimental_sandbox != null ? `Sandbox: ${experimental_sandbox.description}` : undefined, ] .filter(Boolean) .join('\n'), inputSchema: z.object({ command: z.string(), }), execute: async ({ command }, { experimental_sandbox }) => { if (!experimental_sandbox) { throw new Error('Experimental sandbox is not available'); } return experimental_sandbox.run({ command }); }, }); const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { shell }, toolsContext: { shell: { projectName: 'web-app' }, }, experimental_sandbox, prompt: 'List the project files.', }); ``` ## Strict Mode When enabled, language model providers that support strict tool calling will only generate tool calls that are valid according to your defined `inputSchema`. This increases the reliability of tool calling. However, not all schemas may be supported in strict mode, and what is supported depends on the specific provider. By default, strict mode is disabled. You can enable it per-tool by setting `strict: true`: ```ts tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string(), }), strict: true, // Enable strict validation for this tool execute: async ({ location }) => ({ // ... }), }); ``` Not all providers or models support strict mode. For those that do not, this option is ignored. ## Input Examples You can specify example inputs for your tools to help guide the model on how input data should be structured. When supported by providers, input examples can help when JSON schema itself does not fully specify the intended usage or when there are optional values. ```ts tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), inputExamples: [ { input: { location: 'San Francisco' } }, { input: { location: 'London' } }, ], execute: async ({ location }) => { // ... }, }); ``` Only the Anthropic providers supports tool input examples natively. Other providers ignore the setting. ## Tool Execution Approval By default, tools with an `execute` function run automatically as the model calls them. Configure approval with `toolApproval` on `generateText`, `streamText`, or `ToolLoopAgent`. `toolApproval` can be either: - A `GenericToolApprovalFunction` for all tool calls - A per-tool map of statuses and/or `SingleToolApprovalFunction` callbacks (either function may return `undefined` for the same effect as `'not-applicable'`) The older `needsApproval` property on `tool()` definitions is deprecated. Existing code still works, but new code should move approval logic to `toolApproval`. ### Configure `toolApproval` ```ts {4-6} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { runCommand }, toolApproval: { runCommand: 'user-approval', }, prompt: 'Remove the most recent file in the downloads folder', }); ``` In the per-tool object form, each key can be one of four statuses, either as a string or as an object with a `type` field: - `'not-applicable'`: the tool is executed without an approval flow. This is the default behavior. - `'approved'`: record an automatic approval by emitting approval request/response parts in the output, then execute the tool - `'denied'`: record an automatic denial by emitting approval request/response parts in the output, then surface a denied tool output - `'user-approval'`: emit an approval request and wait for an explicit response Object statuses can also include a reason. For automatic approvals and denials, the reason is emitted on the approval response. For manual approval, the reason is emitted on the approval request so it can be shown to the approver: ```ts toolApproval: { runCommand: { type: 'user-approval', reason: 'filesystem changes require operator review', }, } ``` #### Generic `toolApproval` function Pass a `GenericToolApprovalFunction` as `toolApproval` to handle every tool call in one place. The callback receives `toolCall`, `tools`, `toolsContext`, `messages`, and `runtimeContext` (the same `runtimeContext` you pass to `generateText` or `streamText`, typed as the second `ToolApprovalConfiguration` type parameter; it defaults to `Context`). It may return `undefined` for the same effect as `'not-applicable'`. ```ts {4-10} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { runCommand }, toolApproval: ({ toolCall, tools, toolsContext, messages, runtimeContext, }) => { if (toolCall.toolName === 'runCommand' && !toolCall.dynamic) { // Inspect toolCall.input, cross-tool state, messages, or runtimeContext for policy. return 'user-approval'; } return undefined; // or 'not-applicable' }, prompt: 'Remove the most recent file in the downloads folder', }); ``` The same pattern works for `streamText` and for `toolApproval` on [`ToolLoopAgent`](/docs/agents/building-agents#tools) when you need one policy across all tools. For a per-tool object instead, each key can be a `SingleToolApprovalFunction` that receives the tool input and options `toolCallId`, `messages`, `toolContext` (the same shape as tool execution `context`, without `abortSignal`), and `runtimeContext`. Return `undefined` for the same effect as `'not-applicable'`. This is useful for tools that perform sensitive operations like executing commands, processing payments, modifying data, and more potentially dangerous actions. ### How It Works When a tool requires manual approval, `generateText` and `streamText` don't pause execution. Instead, they complete and return `tool-approval-request` parts in the result content. This means the manual approval flow requires two calls to the model: the first returns the approval request, and the second (after receiving the approval response) either executes the tool or informs the model that approval was denied. Manual approval comes from `toolApproval` returning `'user-approval'`. If the tool should just execute normally, use `'not-applicable'`, return `undefined` from a `GenericToolApprovalFunction` or `SingleToolApprovalFunction`, or omit the setting entirely. If `toolApproval` returns `'approved'` or `'denied'` or their object forms, the SDK records that decision automatically in the same generation by emitting approval request/response parts. When you provide a `reason` on an automatic approval or denial, that reason is included in the emitted approval response and can be rendered in the UI. Here's the manual approval flow: 1. Call `generateText` or `streamText` with `toolApproval` 2. The model generates a tool call 3. The call returns `tool-approval-request` parts in `result.content` 4. Your app requests approval and collects the user's decision 5. Add a `tool-approval-response` to the messages array 6. Call `generateText` or `streamText` again with the updated messages 7. If approved, the tool runs and returns a result. If denied, the model sees the denial and responds accordingly. ### Handling Approval Requests After calling `generateText` or `streamText`, check `result.content` for `tool-approval-request` parts: ```ts import { type ModelMessage, generateText } from 'ai'; const messages: ModelMessage[] = [ { role: 'user', content: 'Remove the most recent file' }, ]; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { runCommand }, toolApproval: { runCommand: 'user-approval', }, messages, }); messages.push(...result.responseMessages); for (const part of result.content) { if (part.type === 'tool-approval-request' && !part.isAutomatic) { console.log(part.approvalId); // Unique ID for this approval request console.log(part.toolCall); // Contains toolName, input, etc. console.log(part.reason); // Why this tool call requires approval } } ``` To respond, create a `tool-approval-response` and add it to your messages: ```ts import { type ToolApprovalResponse } from 'ai'; const approvals: ToolApprovalResponse[] = []; for (const part of result.content) { if (part.type === 'tool-approval-request' && !part.isAutomatic) { const response: ToolApprovalResponse = { type: 'tool-approval-response', approvalId: part.approvalId, approved: true, // or false to deny reason: 'User confirmed the command', // Optional, see below }; approvals.push(response); } } // add approvals to messages messages.push({ role: 'tool', content: approvals }); ``` Then call `generateText` or `streamText` again with the updated messages. If approved, the tool executes. If denied, the model receives the denial and can respond accordingly. The optional `reason` is handled differently depending on the decision: - **Denied**: the reason is included in the `execution-denied` tool result, so the model can see why the tool call was not executed. - **Approved** (tools executed by the AI SDK): the reason remains in the approval response in your messages (e.g. for rendering in the UI or for audit logs). The SDK does not include it in the model prompt, modify the tool's input, or expose it through a dedicated execution option. The model only sees the tool result. In manual `generateText` and `streamText` continuations, the approval response is available to the tool's `execute` function through `options.messages`. The approval `reason` is not a channel for passing data to an approved tool. If you need input from the user (for example, an answer to a question or a corrected value), use a tool without an `execute` function and provide its result from the client instead. See [Chatbot Tool Usage](/docs/ai-sdk-ui/chatbot-tool-usage). When the tool should execute without any approval metadata in the output, use `'not-applicable'`, return `undefined` from an approval function, or omit `toolApproval`. Use `'approved'`, `'denied'`, or their object forms only when you want the result to include an automatic `tool-approval-request` with `isAutomatic: true` followed by a `tool-approval-response`, so you can inspect or render the decision without prompting the user again. When a tool execution is denied, consider adding a system instruction like "When a tool execution is not approved, do not retry it" to prevent the model from attempting the same call again. Provider-executed tools are executed provider-side without considering the tool approval setting. `toolApproval` (and the deprecated `needsApproval`) only control tools that the AI SDK executes locally. ### Dynamic Approval You can make approval decisions based on tool input by providing an async function in a per-tool `toolApproval` object: ```ts const paymentTool = tool({ description: 'Process a payment', inputSchema: z.object({ amount: z.number(), recipient: z.string(), }), execute: async ({ amount, recipient }) => { return await processPayment(amount, recipient); }, }); const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { processPayment: paymentTool, }, toolApproval: { processPayment: async ({ amount }) => amount > 1000 ? 'user-approval' : undefined, }, prompt: 'Send $1500 to the contractor', }); ``` In this example, only transactions over $1000 require approval. Smaller transactions execute automatically. You can use a `SingleToolApprovalFunction` in a per-tool `toolApproval` object when you want to return `'not-applicable'`, `undefined` (equivalent to `'not-applicable'`), `'approved'`, `'denied'`, or `'user-approval'` at call time instead of defining the default on the tool itself. For automatic approvals and denials, the callback can also return `{ type, reason }`, for example `{ type: 'denied', reason: 'blocked by policy' }`. For decisions that need the full `toolCall` or several tools at once, pass a `GenericToolApprovalFunction` as `toolApproval` instead of a per-tool map. The generic function can return `undefined` the same way. ### Tool Execution Approval with useChat When using `useChat`, the approval flow is handled through UI state. See [Chatbot Tool Usage](/docs/ai-sdk-ui/chatbot-tool-usage#tool-execution-approval) for details on handling approvals in your UI with `addToolApprovalResponse`. ## Multi-Step Calls (using stopWhen) With the `stopWhen` setting, you can enable multi-step calls in `generateText` and `streamText`. When `stopWhen` is set and the model generates a tool call, the AI SDK will trigger a new generation passing in the tool result until there are no further tool calls or the stopping condition is met. The AI SDK provides several built-in stopping conditions: - `isStepCount(count)` — stops after a specified number of steps (default: `isStepCount(1)` for `generateText` and `streamText`) - `hasToolCall(...toolNames)` — stops when any of the specified tools is called - `isLoopFinished()` — never triggers, letting the loop run until naturally finished You can also combine multiple conditions in an array or create custom conditions. See [Loop Control](/docs/agents/loop-control) for more details. The `stopWhen` conditions are only evaluated when the last step contains tool results. By default, when you use `generateText` or `streamText`, it triggers a single generation. This works well for many use cases where you can rely on the model's training data to generate a response. However, when you provide tools, the model now has the choice to either generate a normal text response, or generate a tool call. A step includes one model generation and the execution of any local tools requested by that generation. If the model generates a tool call, the model generation finishes first; the step finishes after its local tool execution completes. You may want the model to generate text after the tool has been executed, either to summarize the tool results in the context of the users query. In many cases, you may also want the model to use multiple tools in a single response. This is where multi-step calls come in. You can think of multi-step calls in a similar way to a conversation with a human. When you ask a question, if the person does not have the requisite knowledge in their common knowledge (a model's training data), the person may need to look up information (use a tool) before they can provide you with an answer. In the same way, the model may need to call a tool to get the information it needs to answer your question where each generation (tool call or text generation) is a step. ### Example In the following example, there are two steps: 1. **Step 1** 1. The prompt `'What is the weather in San Francisco?'` is sent to the model. 2. The model generates a tool call. 3. The tool call is executed. 2. **Step 2** 1. The tool result is sent to the model. 2. The model generates a response considering the tool result. ```ts {19-20} import { z } from 'zod'; import { generateText, tool, isStepCount } from 'ai'; const { text, steps } = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => ({ location, temperature: 72 + Math.floor(Math.random() * 21) - 10, }), }), }, stopWhen: isStepCount(5), // stop after a maximum of 5 steps if tools were called prompt: 'What is the weather in San Francisco?', }); ``` You can use `streamText` in a similar way. ### Steps To access intermediate tool calls and results, you can use the `steps` property in the result object or the `streamText` `onEnd` callback. It contains all the text, tool calls, tool results, per-step `performance`, and more from each step. #### Example: Extract tool results from all steps ```ts {4,10-11} import { generateText } from 'ai'; const { steps } = await generateText({ model: "anthropic/claude-sonnet-5.5", stopWhen: isStepCount(10), // ... }); // extract all tool calls from the steps: const allToolCalls = steps.flatMap(step => step.toolCalls); ``` ### `onStepEnd` callback When using `generateText` or `streamText`, you can provide an `onStepEnd` callback that is triggered when a step is finished, i.e. all text deltas, tool calls, and tool results for the step are available. When you have multiple steps, the callback is triggered for each step. The callback receives a `stepNumber` (zero-based) to identify which step just completed: ```tsx {5-8} import { generateText } from 'ai'; const result = await generateText({ // ... onStepEnd({ stepNumber, text, toolCalls, toolResults, finishReason, usage, performance, }) { console.log(`Step ${stepNumber} finished (${finishReason})`, { usage, performance, }); // your own logic, e.g. for saving the chat history or recording usage }, }); ``` ### Tool call execution concurrency By default, when a model generates multiple tool calls in one step, `generateText`, `streamText`, and `ToolLoopAgent` execute those calls concurrently. Set `toolCallConcurrency` to a positive integer to limit how many SDK-executed tool calls run at once. Use `toolCallConcurrency: 1` when later calls need to observe side effects from earlier calls. The SDK starts calls sequentially in the order the model generated them: ```ts {5} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools, prompt: 'Update the file, then read it.', toolCallConcurrency: 1, }); ``` This option controls execution of tool calls that the SDK runs locally. It does not change how many tool calls the model may generate, and it does not schedule provider-executed tools or client-side tools. Provider options such as `parallelToolCalls` control model generation instead. When the operation is aborted, active tools receive the abort signal and queued tool calls are not started. A streaming tool holds its concurrency slot until its final output or error. ### Tool execution lifecycle callbacks You can use `onToolExecutionStart` and `onToolExecutionEnd` to observe tool execution. These callbacks are called right before and after each tool's `execute` function, giving you visibility into tool execution timing, inputs, outputs, and errors: ```tsx {5-14} import { generateText } from 'ai'; const result = await generateText({ // ... model, tools, prompt onToolExecutionStart({ toolCall }) { console.log(`Calling tool: ${toolCall.toolName}`, { toolCallId: toolCall.toolCallId, input: toolCall.input, }); }, onToolExecutionEnd({ toolCall, toolExecutionMs, toolOutput }) { if (toolOutput.type === 'tool-error') { console.error( `Tool ${toolCall.toolName} failed after ${toolExecutionMs}ms:`, toolOutput.error, ); } else { console.log( `Tool ${toolCall.toolName} completed in ${toolExecutionMs}ms`, { output: toolOutput.output, }, ); } }, }); ``` Errors thrown inside these callbacks are silently caught and do not break the generation flow. ### `prepareStep` callback The `prepareStep` callback is called before a step is started. It is called with the following parameters: - `model`: The model that was passed into `generateText`. - `stopWhen`: The stopping condition that was passed into `generateText`. - `stepNumber`: The number of the step that is being executed. - `steps`: The steps that have been executed so far. - `instructions`: The instructions that will be sent to the model for the current step. If `prepareStep` returns an `instructions` override, those instructions carry forward as the default for later steps. - `initialInstructions`: The instructions that were passed into `generateText` or `streamText`. - `messages`: The messages that will be sent to the model for the current step. Treat this as the loop's current message state. If `prepareStep` returns a `messages` override, those messages carry forward as the base for later steps. - `initialMessages`: The messages that were passed into `generateText` or `streamText`. - `responseMessages`: The accumulated assistant/tool response messages so far, including any tool results generated before the first model step from approved tool calls in the input messages. - `runtimeContext`: The runtime context passed via the `runtimeContext` setting. - `toolsContext`: The per-tool context map passed via the `toolsContext` setting. - `experimental_sandbox`: The experimental sandbox passed via the `experimental_sandbox` setting. You can use it to provide different settings for a step, including modifying the input messages. ```tsx {5-7} import { generateText } from 'ai'; const result = await generateText({ // ... prepareStep: async ({ model, stepNumber, steps, messages }) => { if (stepNumber === 0) { return { // use a different model for this step: model: modelForThisParticularStep, // force a tool choice for this step: toolChoice: { type: 'tool', toolName: 'tool1' }, // limit the tools that are available for this step: activeTools: ['tool1'], }; } // when nothing is returned, the default settings are used }, }); ``` If you return `instructions`, those instructions carry forward to later steps until `prepareStep` returns another `instructions` or `system` override. Use `initialInstructions` when you need to restore or compare against the top-level instructions from the original call. #### Message Modification for Longer Agentic Loops In longer agentic loops, you can use the `messages` parameter to mutate the message state that will be used by later steps. This is particularly useful for context compaction, and you decide when compaction should happen. The `messages` parameter contains the messages for the current step. By default, this is `initialMessages` followed by the accumulated `responseMessages`. If a previous `prepareStep` returned `messages`, later steps use those persisted messages plus the response messages from the previous step. Use `initialMessages` when you need the original input messages and `responseMessages` when you need the discrete assistant/tool response messages from the model so far. The `pruneMessages` helper provides a built-in way to remove selected messages or message parts. You can use it inside `prepareStep` when you want a simple compaction strategy. ```tsx import { generateText, pruneMessages, type ModelMessage } from 'ai'; const COMPACTION_THRESHOLD = 100_000; const estimateTokens = (messages: ModelMessage[]) => { return JSON.stringify(messages).length / 4; }; const result = await generateText({ // ... prepareStep: ({ messages }) => { if (estimateTokens(messages) > COMPACTION_THRESHOLD) { return { messages: pruneMessages({ messages, reasoning: 'all', toolCalls: 'before-last-3-messages', emptyMessages: 'remove', }), }; } }, }); ``` This example uses an estimated token threshold, but you can use any trigger. The key behavior is that returning `messages` mutates the message state for later steps. Returned message changes persist across steps. If you want to derive each step's messages from the original input plus the discrete response messages instead of the persisted message state, rebuild them from `initialMessages` and `responseMessages` each time: ```tsx prepareStep: ({ initialMessages, responseMessages, stepNumber }) => { if (stepNumber > 0) { return { messages: [...initialMessages, ...responseMessages.slice(-10)], }; } }, ``` #### Provider Options for Step Configuration You can use `providerOptions` in `prepareStep` to pass provider-specific configuration for each step. This is useful for features like Anthropic's code execution container persistence: ```tsx import { forwardAnthropicContainerIdFromLastStep } from '@ai-sdk/anthropic'; // Propagate container ID from previous step for code execution continuity prepareStep: forwardAnthropicContainerIdFromLastStep, ``` ## Response Messages Adding the generated assistant and tool messages to your conversation history is a common task, especially if you are using multi-step tool calls. Both `generateText` and `streamText` have a `responseMessages` property that you can use to add the assistant and tool messages to your conversation history. It is also available in the `onEnd` callback of `streamText`. The `responseMessages` property contains the accumulated response messages from the call as an array of `ModelMessage` objects that you can add to your conversation history: ```ts import { generateText, ModelMessage } from 'ai'; const messages: ModelMessage[] = [ // ... ]; const { responseMessages } = await generateText({ // ... messages, }); // add the response messages to your conversation history: messages.push(...responseMessages); // streamText: ...(await result.responseMessages) ``` ## Dynamic Tools AI SDK Core supports dynamic tools for scenarios where tool schemas are not known at compile time. This is useful for: - MCP (Model Context Protocol) tools without schemas - User-defined functions at runtime - Tools loaded from external sources ### Using dynamicTool The `dynamicTool` helper creates tools with unknown input/output types: ```ts import { dynamicTool } from 'ai'; import { z } from 'zod'; const customTool = dynamicTool({ description: 'Execute a custom function', inputSchema: z.object({}), execute: async input => { // input is typed as 'unknown' // You need to validate/cast it at runtime const { action, parameters } = input as any; // Execute your dynamic logic return { result: `Executed ${action}` }; }, }); ``` ### Type-Safe Handling When using both static and dynamic tools, use the `dynamic` flag for type narrowing: ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { // Static tool with known types weather: weatherTool, // Dynamic tool custom: dynamicTool({ /* ... */ }), }, onStepEnd: ({ toolCalls, toolResults }) => { // Type-safe iteration for (const toolCall of toolCalls) { if (toolCall.dynamic) { // Dynamic tool: input is 'unknown' console.log('Dynamic:', toolCall.toolName, toolCall.input); continue; } // Static tool: full type inference switch (toolCall.toolName) { case 'weather': console.log(toolCall.input.location); // typed as string break; } } }, }); ``` ## Preliminary Tool Results You can return an `AsyncIterable` over multiple results. In this case, the last value from the iterable is the final tool result. This can be used in combination with generator functions to e.g. stream status information during the tool execution: ```ts tool({ description: 'Get the current weather.', inputSchema: z.object({ location: z.string(), }), async *execute({ location }) { yield { status: 'loading' as const, text: `Getting weather for ${location}`, weather: undefined, }; await new Promise(resolve => setTimeout(resolve, 3000)); const temperature = 72 + Math.floor(Math.random() * 21) - 10; yield { status: 'success' as const, text: `The weather in ${location} is ${temperature}°F`, temperature, }; }, }); ``` ## Tool Choice You can use the `toolChoice` setting to influence when a tool is selected. It supports the following settings: - `auto` (default): the model can choose whether and which tools to call. - `required`: the model must call a tool. It can choose which tool to call. - `none`: the model must not call tools - `{ type: 'tool', toolName: string (typed) }`: the model must call the specified tool ```ts {19} import { z } from 'zod'; import { generateText, tool } from 'ai'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => ({ location, temperature: 72 + Math.floor(Math.random() * 21) - 10, }), }), }, toolChoice: 'required', // force the model to call a tool prompt: 'What is the weather in San Francisco?', }); ``` ## Tool Execution Options When tools are called, they receive additional options as a second parameter. ### Tool Call ID The ID of the tool call is forwarded to the tool execution. You can use it e.g. when sending tool-call related information with stream data. ```ts {15-21} import { streamText, tool, createUIMessageStream, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; export async function POST(req: Request) { const { messages } = await req.json(); const stream = createUIMessageStream({ execute: ({ writer }) => { const result = streamText({ // ... messages, tools: { myTool: tool({ // ... execute: async (args, { toolCallId }) => { // return e.g. custom status for tool call writer.write({ type: 'data-tool-status', id: toolCallId, data: { name: 'myTool', status: 'in-progress', }, }); // ... }, }), }, }); writer.merge(toUIMessageStream({ stream: result.stream })); }, }); return createUIMessageStreamResponse({ stream }); } ``` ### Messages The messages that were sent to the language model to initiate the response that contained the tool call are forwarded to the tool execution. You can access them in the second parameter of the `execute` function. In multi-step calls, the messages contain the text, tool calls, and tool results from all previous steps. ```ts {8-9} import { generateText, tool } from 'ai'; const result = await generateText({ // ... tools: { myTool: tool({ // ... execute: async (args, { messages }) => { // use the message history in e.g. calls to other language models return { ... }; }, }), }, }); ``` ### Abort Signals The abort signals from `generateText` and `streamText` are forwarded to the tool execution. You can access them in the second parameter of the `execute` function and e.g. abort long-running computations or forward them to fetch calls inside tools. ```ts {7,12,15} import { z } from 'zod'; import { generateText, tool } from 'ai'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", abortSignal: myAbortSignal, // signal that will be forwarded to tools tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string() }), execute: async ({ location }, { abortSignal }) => { return fetch( `https://api.weatherapi.com/v1/current.json?q=${location}`, { signal: abortSignal }, // forward the abort signal to fetch ); }, }), }, prompt: 'What is the weather in San Francisco?', }); ``` ### Experimental Sandbox Pass `experimental_sandbox` to `generateText`, `streamText`, or a `ToolLoopAgent` call when a tool needs to run commands or code in an execution environment. The experimental sandbox is available to tool description functions and on the second parameter of the tool's `execute` function. This API is experimental and can change in patch releases. Passing an experimental sandbox does not sandbox the tool itself. Tool code still runs wherever your application runs. Only the operations that your tool explicitly delegates to the experimental sandbox, such as `experimental_sandbox.run(...)`, run in the experimental sandbox environment. ```ts {7-15,21} import { generateText, tool } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { shell: tool({ inputSchema: z.object({ command: z.string(), workingDirectory: z.string().optional(), }), execute: async ( { command, workingDirectory }, { abortSignal, experimental_sandbox }, ) => { if (!experimental_sandbox) { throw new Error('Experimental sandbox is not available'); } return experimental_sandbox.run({ command, workingDirectory, abortSignal, }); }, }), }, experimental_sandbox, prompt: 'List the files in the project.', }); ``` The experimental sandbox description is not added to the model prompt automatically. If the model should know about details such as the root directory, exposed ports, or public hostname, include `experimental_sandbox.description` in your system prompt, instructions, user-visible context, or a tool description function. `run` also accepts an optional `workingDirectory`, `env`, and `abortSignal`. Use `workingDirectory` when a tool needs to run a command from a directory other than the experimental sandbox implementation's default working directory. Use `env` to set environment variables for the command. Forward `abortSignal` from the tool execution options so the experimental sandbox can cancel the command if the overall operation is aborted or times out. The AI SDK forwards your experimental sandbox object but does not create or isolate one for you. The `Experimental_SandboxSession` interface is only a contract. Its isolation guarantees depend on your implementation or experimental sandbox provider. A local implementation that uses `child_process.exec` with a working directory is not a security boundary because commands can still access paths outside that directory. For untrusted commands or user-directed coding agents, use a real isolation provider, restrict available commands, set command timeouts, and combine experimental sandbox usage with [tool approval](#tool-execution-approval) for sensitive actions. ### Runtime Context You can pass in arbitrary runtime context from `generateText` or `streamText` via the `runtimeContext` setting. This runtime context is available in `prepareStep`. To avoid confusion with prompt context or retrieved context, the docs refer to this feature as runtime context. This is useful for values like tenant information, feature flags, session data, or other server-side state that should influence step preparation without being embedded into the prompt. Tool execution context is now separate. If a tool needs server-side values such as API keys, pass them via `toolsContext`, keyed by tool name. Each tool then receives only its own typed `context` value based on its `contextSchema`. For the full mental model, examples, lifecycle details, and guidance on choosing between prompt context, runtime context, and tool context, see [Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context). ### Tool Context Telemetry Tool context often contains server-side values such as API keys, access tokens, or internal identifiers. Use `telemetry.includeToolsContext` to include selected top-level context properties in telemetry integrations: ```ts {24-30} const weatherTool = tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string(), }), contextSchema: z.object({ weatherApiKey: z.string(), defaultUnit: z.enum(['celsius', 'fahrenheit']), }), execute: async ({ location }, { context }) => { return fetchWeather({ location, apiKey: context.weatherApiKey, unit: context.defaultUnit, }); }, }); const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { weather: weatherTool }, toolsContext: { weather: { weatherApiKey: process.env.WEATHER_API_KEY, defaultUnit: 'fahrenheit', }, }, prompt: 'What is the weather in San Francisco?', telemetry: { includeToolsContext: { weather: { defaultUnit: true, }, }, }, }); ``` Telemetry integrations receive the `weather` tool context as `{ defaultUnit: 'fahrenheit' }`. Properties set to `false` or omitted are excluded. If `telemetry.includeToolsContext` is omitted, no tool context properties are included. `telemetry.includeToolsContext` only filters telemetry integrations. Tool execution, lifecycle callbacks, and returned results still receive the full typed tool context. See [Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context) for how tool context flows through execution and telemetry. ## Tool Input Lifecycle Hooks The following tool input lifecycle hooks are available: - **`onInputStart`**: Called when the model starts generating the input (arguments) for the tool call - **`onInputDelta`**: Called for each chunk of text as the input is streamed - **`onInputAvailable`**: Called when the complete input is available and validated `onInputStart` is always called before `onInputAvailable`, including when using `generateText`. `onInputDelta` is only called in streaming contexts (when using `streamText`). ### Example ```ts {16-24} import { streamText, tool } from 'ai'; import { z } from 'zod'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", tools: { getWeather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => ({ temperature: 72 + Math.floor(Math.random() * 21) - 10, }), onInputStart: () => { console.log('Tool call starting'); }, onInputDelta: ({ inputTextDelta }) => { console.log('Received input chunk:', inputTextDelta); }, onInputAvailable: ({ input }) => { console.log('Complete input:', input); }, }), }, prompt: 'What is the weather in San Francisco?', }); ``` ## Types Modularizing your code often requires defining types to ensure type safety and reusability. To enable this, the AI SDK provides several helper types for tools, tool calls, and tool results. You can use them to strongly type your variables, function parameters, and return types in parts of the code that are not directly related to `streamText` or `generateText`. Each tool call is typed with `ToolCall`, depending on the tool that has been invoked. Similarly, the tool results are typed with `ToolResult`. The tools in `streamText` and `generateText` are defined as a `ToolSet`. The type inference helpers `TypedToolCall` and `TypedToolResult` can be used to extract the tool call and tool result types from the tools. ```ts {18-19,23-24} import { TypedToolCall, TypedToolResult, generateText, tool } from 'ai'; import { z } from 'zod'; const myToolSet = { firstTool: tool({ description: 'Greets the user', inputSchema: z.object({ name: z.string() }), execute: async ({ name }) => `Hello, ${name}!`, }), secondTool: tool({ description: 'Tells the user their age', inputSchema: z.object({ age: z.number() }), execute: async ({ age }) => `You are ${age} years old!`, }), }; type MyToolCall = TypedToolCall; type MyToolResult = TypedToolResult; async function generateSomething(prompt: string): Promise<{ text: string; toolCalls: Array; // typed tool calls toolResults: Array; // typed tool results }> { return generateText({ model: "anthropic/claude-sonnet-5.5", tools: myToolSet, prompt, }); } ``` ## Handling Errors The AI SDK has four tool-call related errors: - [`NoSuchToolError`](/docs/reference/ai-sdk-errors/ai-no-such-tool-error): the model tries to call a tool that is not defined in the tools object - [`InvalidToolInputError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-input-error): the model calls a tool with inputs that do not match the tool's input schema - [`ToolCallRepairError`](/docs/reference/ai-sdk-errors/ai-tool-call-repair-error): an error that occurred during tool call repair - [`ToolChoiceViolationError`](/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error): the `generateText` response does not satisfy a required or specifically selected tool choice When tool execution fails (errors thrown by your tool's `execute` function), the AI SDK adds them as `tool-error` content parts to enable automated LLM roundtrips in multi-step scenarios. ### `generateText` `generateText` throws errors for tool schema validation issues and other errors, and can be handled using a `try`/`catch` block. Tool execution errors appear as `tool-error` parts in the result steps: ```ts try { const result = await generateText({ //... }); } catch (error) { if (NoSuchToolError.isInstance(error)) { // handle the no such tool error } else if (InvalidToolInputError.isInstance(error)) { // handle the invalid tool inputs error } else { // handle other errors } } ``` Tool execution errors are available in the result steps: ```ts const { steps } = await generateText({ // ... }); // check for tool errors in the steps const toolErrors = steps.flatMap(step => step.content.filter(part => part.type === 'tool-error'), ); toolErrors.forEach(toolError => { console.log('Tool error:', toolError.error); console.log('Tool name:', toolError.toolName); console.log('Tool input:', toolError.input); }); ``` ### `streamText` `streamText` sends errors as part of the `stream` result. Tool execution errors appear as `tool-error` parts, while other errors appear as `error` parts. When using `toUIMessageStream`, you can pass an `onError` function to extract the error message from the error part and forward it as part of the stream response: ```ts const result = streamText({ // ... }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, onError: error => { if (NoSuchToolError.isInstance(error)) { return 'The model tried to call a unknown tool.'; } else if (InvalidToolInputError.isInstance(error)) { return 'The model called a tool with invalid inputs.'; } else { return 'An unknown error occurred.'; } }, }), }); ``` ## Tool Call Repair Language models sometimes fail to generate valid tool calls, especially when the input schema is complex or the model is smaller. If you use multiple steps, those failed tool calls will be sent back to the LLM in the next step to give it an opportunity to fix it. However, you may want to control how invalid tool calls are repaired without requiring additional steps that pollute the message history. You can use the `repairToolCall` function to attempt to repair the tool call with a custom function. You can use different strategies to repair the tool call: - Use a model with structured outputs to generate the inputs. - Send the messages, instructions, and tool schema to a stronger model to generate the inputs. - Provide more specific repair instructions based on which tool was called. ### Example: Use a model with structured outputs for repair ```ts import { openai } from '@ai-sdk/openai'; import { generateText, NoSuchToolError, Output, tool } from 'ai'; const result = await generateText({ model, tools, prompt, repairToolCall: async ({ toolCall, tools, inputSchema, error }) => { if (NoSuchToolError.isInstance(error)) { return null; // do not attempt to fix invalid tool names } const tool = tools[toolCall.toolName as keyof typeof tools]; const { output: repairedArgs } = await generateText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: tool.inputSchema }), prompt: [ `The model tried to call the tool "${toolCall.toolName}"` + ` with the following inputs:`, JSON.stringify(toolCall.input), `The tool accepts the following schema:`, JSON.stringify(await inputSchema({ toolName: toolCall.toolName })), 'Please fix the inputs.', ].join('\n'), }); return { ...toolCall, input: JSON.stringify(repairedArgs) }; }, }); ``` ### Example: Use the re-ask strategy for repair ```ts import { openai } from '@ai-sdk/openai'; import { generateText, NoSuchToolError, tool } from 'ai'; const result = await generateText({ model, tools, prompt, repairToolCall: async ({ toolCall, tools, error, messages, instructions, }) => { const result = await generateText({ model, instructions, messages: [ ...messages, { role: 'assistant', content: [ { type: 'tool-call', toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, input: toolCall.input, }, ], }, { role: 'tool' as const, content: [ { type: 'tool-result', toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, output: error.message, }, ], }, ], tools, }); const newToolCall = result.toolCalls.find( newToolCall => newToolCall.toolName === toolCall.toolName, ); return newToolCall != null ? { type: 'tool-call' as const, toolCallId: toolCall.toolCallId, toolName: toolCall.toolName, input: JSON.stringify(newToolCall.input), } : null; }, }); ``` ## Active Tools Language models can only handle a limited number of tools at a time, depending on the model. To allow for static typing using a large number of tools and limiting the available tools to the model at the same time, the AI SDK provides the `activeTools` property. It is an array of tool names that are currently active. By default, the value is `undefined` and all tools are active. ```ts {8} import { openai } from '@ai-sdk/openai'; import { generateText } from 'ai'; const { text } = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: myToolSet, activeTools: ['firstTool'], }); ``` ## Tool Order Some providers include tool definitions in the cached portion of a request. If the order of those definitions changes between otherwise similar requests, the provider may not be able to reuse the cached prefix as effectively. Use `toolOrder` when you want a stable provider request shape for caching or debugging. The list can be partial: tools listed in `toolOrder` are sent first in that order, and any remaining tools are sent afterwards in alphabetical order. Tool names are typed from your `tools` object. ```ts {7} import { generateText } from 'ai'; const { text } = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: myToolSet, toolOrder: ['search', 'readFile'], prompt: 'Summarize the latest project notes.', }); ``` `toolOrder` controls only the order of tool definitions sent to the provider. It does not force the model to call a tool. Use [`toolChoice`](#tool-choice) to control tool selection, and use [`activeTools`](#active-tools) when you want to limit which tools are available. When both `activeTools` and `toolOrder` are provided, `activeTools` filters the available tools first and `toolOrder` orders the remaining tools. ## Multi-modal Tool Results Multi-modal tool results are experimental and supported by Anthropic, OpenAI, and Google (Gemini 3 models). For Google Generative AI, use base64 inline-data file parts (`{ type: 'file', mediaType, data: { type: 'data', data } }`) or base64 `data:` URLs in URL-style file parts (`{ type: 'file', mediaType, data: { type: 'url', url: new URL('data:...') } }`). Remote HTTP(S) URLs in tool-result URL parts are not supported. The [Google Vertex provider](/providers/ai-sdk-providers/google-vertex#tool-result-files) also supports direct `gs://` tool-result file references for Gemini 3 and later models with supported image and document MIME types. It downloads HTTP(S) tool-result URLs and sends their contents as inline data. In order to send multi-modal tool results, e.g. screenshots, back to the model, they need to be converted into a specific format. AI SDK Core tools have an optional `toModelOutput` function that converts the tool result into a content part. Here is an example for converting a screenshot into a content part: ```ts {23-38} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { computer: anthropic.tools.computer_20241022({ // ... async execute({ action, coordinate, text }) { switch (action) { case 'screenshot': { return { type: 'file', mediaType: 'image', data: fs .readFileSync('./data/screenshot-editor.png') .toString('base64'), }; } default: { return `executed ${action}`; } } }, // map to tool result content for LLM consumption: toModelOutput({ output }) { return { type: 'content', value: typeof output === 'string' ? [{ type: 'text', text: output }] : [ { type: 'file', mediaType: 'image/png', data: { type: 'data', data: output.data }, }, ], }; }, }), }, // ... }); ``` ## Extracting Tools Once you start having many tools, you might want to extract them into separate files. The `tool` helper function is crucial for this, because it ensures correct type inference. Here is an example of an extracted tool: ```ts title="tools/weather-tool.ts" {1,4-5} import { tool } from 'ai'; import { z } from 'zod'; // the `tool` helper function ensures correct type inference: export const weatherTool = tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => ({ location, temperature: 72 + Math.floor(Math.random() * 21) - 10, }), }); ``` ## MCP Tools The AI SDK supports connecting to Model Context Protocol (MCP) servers to access their tools. MCP enables your AI applications to discover and use tools across various services through a standardized interface. For detailed information about MCP tools, including initialization, transport options, and usage patterns, see the [MCP Tools documentation](/docs/ai-sdk-core/mcp-tools). ### AI SDK Tools vs MCP Tools In most cases, you should define your own AI SDK tools for production applications. They provide full control, type safety, and optimal performance. MCP tools are best suited for rapid development iteration and scenarios where users bring their own tools. | Aspect | AI SDK Tools | MCP Tools | | ---------------------- | --------------------------------------------------------- | ----------------------------------------------------- | | **Type Safety** | Full static typing end-to-end | Dynamic discovery at runtime | | **Execution** | Same process as your request (low latency) | Separate server (network overhead) | | **Prompt Control** | Full control over descriptions and schemas | Controlled by MCP server owner | | **Schema Control** | You define and optimize for your model | Controlled by MCP server owner | | **Version Management** | Full visibility over updates | Can update independently (version skew risk) | | **Authentication** | Same process, no additional auth required | Separate server introduces additional auth complexity | | **Best For** | Production applications requiring control and performance | Development iteration, user-provided tools | ## Examples You can see tools in action using various frameworks in the following examples: - [Learn to use tools in Node.js](/cookbook/node/call-tools) - [Learn to use tools in Next.js with Route Handlers](/cookbook/next/call-tools) - [Learn to use MCP tools in Node.js](/cookbook/node/mcp-tools) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Tools description: Use first-party AI SDK tools to add capabilities to agents and applications. url: "https://ai-sdk.dev/docs/ai-sdk-core/tools" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK provides first-party tools for agents and applications. Each tool includes its input schema, model-facing description, and execution logic, ready to use with `generateText`, `streamText`, or `ToolLoopAgent`. These tools work with any model that supports function calling. Install tools from their individual `@ai-sdk/tool-*` packages. For defining your own tools and configuring tool execution, see [Tools and Tool Calling](/docs/ai-sdk-core/tools-and-tool-calling). ## Bash Tool `experimental_createBashTool` lets agents run Bash commands to search, analyze, and transform files. Supply an `Experimental_SandboxSession` to choose where commands run. Use `@ai-sdk/sandbox-just-bash` for an in-memory environment, `@ai-sdk/sandbox-vercel` for Vercel Sandbox, or your own implementation. The tool has no direct dependency on either runtime. ### Installation Install the tool and the sandbox adapter you want to use. The following examples use just-bash: ```sh pnpm add ai @ai-sdk/tool-bash @ai-sdk/sandbox-just-bash ``` Requires Node.js 22.13 or newer. ### Usage ```ts import { createJustBashNetworkSandboxSession } from '@ai-sdk/sandbox-just-bash'; import { experimental_createBashTool as createBashTool } from '@ai-sdk/tool-bash'; import { generateText, isStepCount } from 'ai'; const session = await createJustBashNetworkSandboxSession(); try { const sandbox = session.restricted(); await sandbox.writeTextFile({ path: 'notes.txt', content: 'Apples: 10\nPears: 20\n', }); const bash = await createBashTool({ sandbox }); const result = await generateText({ model, // Your configured model with function calling support. tools: { bash }, stopWhen: isStepCount(5), prompt: 'Read notes.txt and write a summary to summary.txt.', }); console.log(result.text); console.log(await sandbox.readTextFile({ path: 'summary.txt' })); } finally { await session.destroy(); } ``` ### Vercel Sandbox Install `@ai-sdk/sandbox-vercel` and `@vercel/sandbox` to use an existing native Vercel Sandbox. Wrap it with the adapter before passing it to the tool: ```ts import { createVercelSandboxSessionFromNativeSandbox } from '@ai-sdk/sandbox-vercel'; import { experimental_createBashTool as createBashTool } from '@ai-sdk/tool-bash'; import { Sandbox } from '@vercel/sandbox'; const nativeSandbox = await Sandbox.create(); try { const sandbox = createVercelSandboxSessionFromNativeSandbox(nativeSandbox); await sandbox.writeTextFile({ path: 'notes.txt', content: 'Hello from Vercel Sandbox', }); const bash = await createBashTool({ sandbox }); // Use bash with generateText, streamText, or ToolLoopAgent here. } finally { await nativeSandbox.stop(); } ``` You can also create a managed session with `createVercelNetworkSandboxSession()` from `@ai-sdk/sandbox-vercel` and pass `session.restricted()`. For an existing native just-bash `Sandbox`, use `createJustBashSandboxSessionFromNativeSandbox()` from `@ai-sdk/sandbox-just-bash`. Raw runtime instances need their respective adapters; a custom `Experimental_SandboxSession` can be passed directly. ### Options `experimental_createBashTool()` requires an options object: | Option | Type | Description | | ------------------ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `sandbox` | `Experimental_SandboxSession` | Required session for command execution and file operations. | | `workingDirectory` | `string` | Optional absolute starting directory for each command, created if needed. When omitted, uses the sandbox's default directory. | | `maxOutputLength` | `number` | Positive integer limiting characters retained from each output stream before a truncation notice. Defaults to `30000`. | | `timeout` | `number` | Optional timeout per command execution, in milliseconds (integer from `1` to `2147483647`). No tool-level timeout by default. | The model calls the tool with `{ command: string }` and receives `{ stdout, stderr, exitCode }`. Nonzero exit codes are returned so the model can inspect failures and recover. AI SDK cancellation is forwarded to the session. Output truncation happens after execution and does not limit execution memory or duration. ### Command timeout Set `timeout` to cancel commands that run longer than the configured duration: ```ts const bash = await createBashTool({ sandbox, timeout: 10_000, }); ``` Each command gets a fresh timeout starting when its execution begins. This does not include tool initialization or time waiting for model output or approval. The timeout is combined with the caller's abort signal; either can cancel the command. The sandbox must honor cancellation. Its result or error is passed through; for example, just-bash returns exit code `124` when a running command is cancelled. SDK and sandbox timeouts still apply when this option is omitted. ### Files and lifecycle Commands use the sandbox's default working directory unless `workingDirectory` is provided. Without an override, creating the tool runs no setup commands. Seed files through `sandbox.writeTextFile({ path, content })` before running the tool. With an explicit working directory, use absolute paths to write files there. The sandbox controls which files commands can access. Available programs, network access, shell state, and persistence depend on the supplied sandbox. Its description is included in the tool's instructions. Tools using the same session share files. Creating a tool does not reset existing files or create a new sandbox. Use separate sessions for independent workflows. The caller creates, reconnects, and destroys the sandbox. `experimental_createBashTool()` returns the tool directly, ready to use as `tools: { bash }`. Use the supplied sandbox's `readTextFile({ path })` to read generated text files; it returns `null` if the file is missing. Relative read paths depend on the sandbox adapter; just-bash resolves them against its default directory. With an explicit working directory, use absolute paths to read files from that directory. The same session provides other file operations. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Transcription description: Learn how to transcribe audio with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/transcription" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK provides the [`transcribe`](/docs/reference/ai-sdk-core/transcribe) function to transcribe audio using a transcription model. ```ts import { transcribe } from 'ai'; import { openai } from '@ai-sdk/openai'; import { readFile } from 'fs/promises'; const transcript = await transcribe({ model: openai.transcription('whisper-1'), audio: await readFile('audio.mp3'), }); ``` The `audio` property can be a `Uint8Array`, `ArrayBuffer`, `Buffer`, `string` (base64 encoded audio data), or a `URL`. To access the generated transcript: ```ts const text = transcript.text; // transcript text e.g. "Hello, world!" const segments = transcript.segments; // array of segments with start and end times, if available const language = transcript.language; // language of the transcript e.g. "en", if available const durationInSeconds = transcript.durationInSeconds; // duration of the transcript in seconds, if available ``` ## Telemetry `transcribe` and `experimental_streamTranscribe` accept the standard `telemetry` option. Registered integrations receive start, end, and error events with the provider and model, audio byte length and media type, final transcript, warnings, response metadata, provider metadata, and usage when the provider reports it. Because streaming transcription is experimental, custom integrations receive its lifecycle through `experimental_onStreamTranscriptionStart` and `experimental_onStreamTranscriptionEnd`; errors continue to use `onError`. Integrations that wrap or delegate to another telemetry integration must forward these experimental callbacks explicitly for streaming transcription to be observed. ```ts {5-7} const transcript = await transcribe({ model: openai.transcription('gpt-4o-transcribe'), audio: await readFile('audio.mp3'), telemetry: { functionId: 'transcribe-customer-call', }, }); ``` Raw audio is not included in telemetry events. For streaming transcription, the final byte length is emitted only after the input stream is consumed and the transcription finishes. Use `recordInputs: false` or `recordOutputs: false` to prevent integrations from recording sensitive input or output fields. For URL audio, the downloaded media type is passed to the provider and reported on successful telemetry events. If downloading or decoding input audio fails, the start event is still emitted before the correlated error event, but its audio byte length and media type can be undefined because preparation did not complete. ## Streaming Transcription Streaming transcription is an experimental feature. Use `experimental_streamTranscribe` when you have live raw audio and need transcript updates before the full audio stream is complete. The function uses transcription models with streaming support; provider options configure provider-specific behavior, but the streaming operation is selected by the function itself. ```ts import { openai } from '@ai-sdk/openai'; import { experimental_streamTranscribe as streamTranscribe } from 'ai'; const result = streamTranscribe({ model: openai.transcription('gpt-realtime-whisper'), audio: audioStream, // ReadableStream inputAudioFormat: { type: 'audio/pcm', rate: 24000 }, providerOptions: { openai: { language: 'en', streaming: { delay: 'low', }, }, }, }); for await (const part of result.fullStream) { if (part.type === 'transcript-delta') { process.stdout.write(part.delta); } if (part.type === 'transcript-partial') { console.log('partial:', part.text); } if (part.type === 'transcript-final') { console.log('final:', part.text); } } console.log(await result.text); ``` `fullStream` is a single-consumer live stream and can only be accessed once. When you need both stream parts and final results, access `fullStream` first and await the result promises while or after consuming it. Accessing a result promise first consumes the stream internally, so `fullStream` is no longer available. This avoids retaining an unbounded replay buffer for live audio. To access the final transcript metadata: ```ts const text = await result.text; // final transcript text const segments = await result.segments; // final segments with timing, if available const language = await result.language; // language of the transcript, if available const durationInSeconds = await result.durationInSeconds; // duration in seconds, if available ``` The `audio` stream must contain raw audio chunks. `Uint8Array` chunks are raw bytes; `string` chunks are base64-encoded raw bytes. Always set `inputAudioFormat` to match the chunks you send. String model IDs resolve through the global provider (AI Gateway by default). AI Gateway supports streaming transcription for supported models (e.g. `openai/gpt-realtime-whisper`, `elevenlabs/eleven-scribe-2-realtime`, `xai/grok-stt`), so string IDs work: `experimental_streamTranscribe({ model: 'openai/gpt-realtime-whisper', ... })`. You can also pass a provider model instance (e.g. `openai.transcription('gpt-realtime-whisper')`) to stream directly against the provider. OpenAI streaming transcription uses `openai.transcription('gpt-realtime-whisper')`. Cartesia uses `cartesia.transcription('ink-2')` for streaming-only Ink 2 transcription. ElevenLabs uses `elevenLabs.transcription('scribe_v2_realtime')` for Scribe v2 Realtime. Azure uses `azure.transcription('mai-transcribe-2-streaming')` for streaming-only MAI-Transcribe-2-Streaming. xAI uses the same `xai.transcription()` model for request/response and streaming transcription; `experimental_streamTranscribe` selects the provider's WebSocket STT transport. ```ts import { xai } from '@ai-sdk/xai'; import { experimental_streamTranscribe as streamTranscribe } from 'ai'; const result = streamTranscribe({ model: xai.transcription(), audio: audioStream, inputAudioFormat: { type: 'audio/pcm', rate: 16000 }, providerOptions: { xai: { language: 'en', keyterm: ['AI SDK', 'Grok'], streaming: { interimResults: true, endpointing: 500, }, }, }, }); ``` Some providers require WebSocket headers for direct streaming STT. In those runtimes, pass a provider-specific `webSocket` implementation when creating the provider. ## Settings ### Provider-Specific settings Transcription models often have provider or model-specific settings which you can set using the `providerOptions` parameter. ```ts {8-12} import { transcribe } from 'ai'; import { openai } from '@ai-sdk/openai'; import { readFile } from 'fs/promises'; const transcript = await transcribe({ model: openai.transcription('whisper-1'), audio: await readFile('audio.mp3'), providerOptions: { openai: { timestampGranularities: ['word'], }, }, }); ``` ### Download Size Limits When `audio` is a URL, the SDK downloads the file with a default **2 GiB** size limit. You can customize this using `createDownload`: ```ts {1,7} import { transcribe, createDownload } from 'ai'; import { openai } from '@ai-sdk/openai'; const transcript = await transcribe({ model: openai.transcription('whisper-1'), audio: new URL('https://example.com/audio.mp3'), download: createDownload({ maxBytes: 50 * 1024 * 1024 }), // 50 MB limit }); ``` You can also provide a fully custom download function: ```ts {7-13} import { transcribe } from 'ai'; import { openai } from '@ai-sdk/openai'; const transcript = await transcribe({ model: openai.transcription('whisper-1'), audio: new URL('https://example.com/audio.mp3'), download: async ({ url }) => { const res = await myAuthenticatedFetch(url); return { data: new Uint8Array(await res.arrayBuffer()), mediaType: res.headers.get('content-type') ?? undefined, }; }, }); ``` If a download exceeds the size limit, a `DownloadError` is thrown: ```ts import { transcribe, DownloadError } from 'ai'; import { openai } from '@ai-sdk/openai'; try { await transcribe({ model: openai.transcription('whisper-1'), audio: new URL('https://example.com/audio.mp3'), }); } catch (error) { if (DownloadError.isInstance(error)) { console.log('Download failed:', error.message); } } ``` ### Abort Signals and Timeouts `transcribe` accepts an optional `abortSignal` parameter of type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that you can use to abort the transcription process or set a timeout. This is particularly useful when combined with URL downloads to prevent long-running requests: ```ts {7} import { openai } from '@ai-sdk/openai'; import { transcribe } from 'ai'; const transcript = await transcribe({ model: openai.transcription('whisper-1'), audio: new URL('https://example.com/audio.mp3'), abortSignal: AbortSignal.timeout(5000), // Abort after 5 seconds }); ``` ### Custom Headers `transcribe` accepts an optional `headers` parameter of type `Record` that you can use to add custom headers to the transcription request. ```ts {8} import { openai } from '@ai-sdk/openai'; import { transcribe } from 'ai'; import { readFile } from 'fs/promises'; const transcript = await transcribe({ model: openai.transcription('whisper-1'), audio: await readFile('audio.mp3'), headers: { 'X-Custom-Header': 'custom-value' }, }); ``` ### Warnings Warnings (e.g. unsupported parameters) are available on the `warnings` property. ```ts import { openai } from '@ai-sdk/openai'; import { transcribe } from 'ai'; import { readFile } from 'fs/promises'; const transcript = await transcribe({ model: openai.transcription('whisper-1'), audio: await readFile('audio.mp3'), }); const warnings = transcript.warnings; ``` ### Error Handling When `transcribe` cannot generate a valid transcript, it throws a [`AI_NoTranscriptGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-transcript-generated-error). This error can arise for any of the following reasons: - The model failed to generate a response - The model generated a response that could not be parsed The error preserves the following information to help you log the issue: - `responses`: Metadata about the transcription model responses, including timestamp, model, and headers. - `cause`: The cause of the error. You can use this for more detailed error handling. ```ts import { transcribe, NoTranscriptGeneratedError } from 'ai'; import { openai } from '@ai-sdk/openai'; import { readFile } from 'fs/promises'; try { await transcribe({ model: openai.transcription('whisper-1'), audio: await readFile('audio.mp3'), }); } catch (error) { if (NoTranscriptGeneratedError.isInstance(error)) { console.log('NoTranscriptGeneratedError'); console.log('Cause:', error.cause); console.log('Responses:', error.responses); } } ``` ## Transcription Models | Provider | Model | | ----------------------------------------------------------------------------------- | ------------------------------ | | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `whisper-1` | | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `gpt-4o-transcribe` | | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `gpt-4o-mini-transcribe` | | [OpenAI](/providers/ai-sdk-providers/openai#transcription-models) | `gpt-4o-transcribe-diarize` | | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models) | `scribe_v1` | | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models) | `scribe_v1_experimental` | | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#transcription-models) | `scribe_v2` | | [ElevenLabs](/providers/ai-sdk-providers/elevenlabs#streaming-transcription-models) | `scribe_v2_realtime` | | [Groq](/providers/ai-sdk-providers/groq#transcription-models) | `whisper-large-v3-turbo` | | [Groq](/providers/ai-sdk-providers/groq#transcription-models) | `whisper-large-v3` | | [Mistral](/providers/ai-sdk-providers/mistral#transcription-models) | `voxtral-mini-transcribe-3-0` | | [Mistral](/providers/ai-sdk-providers/mistral#transcription-models) | `voxtral-small-transcribe-3-0` | | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `whisper-1` | | [Azure Speech](/providers/ai-sdk-providers/azure#mai-transcribe) | `mai-transcribe-2` | | [Azure Speech](/providers/ai-sdk-providers/azure#mai-transcribe) | `mai-transcribe-1.5` | | [Azure MAI](/providers/ai-sdk-providers/azure#mai-transcribe-2-streaming) | `mai-transcribe-2-streaming` | | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `gpt-4o-transcribe` | | [Azure OpenAI](/providers/ai-sdk-providers/azure#transcription-models) | `gpt-4o-mini-transcribe` | | [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models) | `machine` | | [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models) | `low_cost` | | [Rev.ai](/providers/ai-sdk-providers/revai#transcription-models) | `fusion` | | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `base` (+ variants) | | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `enhanced` (+ variants) | | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `nova` (+ variants) | | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `nova-2` (+ variants) | | [Deepgram](/providers/ai-sdk-providers/deepgram#transcription-models) | `nova-3` (+ variants) | | [Gladia](/providers/ai-sdk-providers/gladia#transcription-models) | `default` | | [AssemblyAI](/providers/ai-sdk-providers/assemblyai#transcription-models) | `universal-3-5-pro` | | [AssemblyAI](/providers/ai-sdk-providers/assemblyai#transcription-models) | `universal-3-pro` | | [Fal](/providers/ai-sdk-providers/fal#transcription-models) | `whisper` | | [Fal](/providers/ai-sdk-providers/fal#transcription-models) | `wizper` | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models) | `chirp_2` | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models) | `chirp_3` | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#transcription-models) | `telephony` | | [xAI](/providers/ai-sdk-providers/xai#transcription-models) | `default` | | [Cartesia](/providers/ai-sdk-providers/cartesia#transcription-models) | `ink-whisper` | | [Cartesia](/providers/ai-sdk-providers/cartesia#streaming-transcription-models) | `ink-2` | | [Fish Audio](/providers/ai-sdk-providers/fish-audio#transcription-models) | `transcribe-1` | Above are a small subset of the transcription models supported by the AI SDK providers. For more, see the respective provider documentation. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Translation description: Learn how to translate speech with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/translation" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Speech translation is an experimental feature. The AI SDK provides the [`experimental_streamTranslate`](/docs/reference/ai-sdk-core/stream-translate) function to translate live speech into another language. Translation is a streaming-only modality: models translate live source audio into target-language audio and text. `experimental_streamTranslate` is built on the speech translation model specification (`Experimental_SpeechTranslationModelV4`). Provider implementations of the speech translation model specification ship separately. Pass any model instance that implements `Experimental_SpeechTranslationModelV4` — see your provider's documentation for available translation models. ```ts import { openai } from '@ai-sdk/openai'; import { experimental_streamTranslate as streamTranslate } from 'ai'; const result = streamTranslate({ model: openai.translation('gpt-realtime-translate'), audio: audioStream, // ReadableStream inputAudioFormat: { type: 'audio/pcm', rate: 24000 }, targetLanguage: 'es', }); for await (const part of result.fullStream) { if (part.type === 'output-text-delta') { process.stdout.write(part.delta); } if (part.type === 'audio') { // translated audio chunk (Uint8Array or base64 string) } if (part.type === 'source-transcript-final') { console.log('source:', part.text); } } console.log(await result.translationText); ``` The `audio` stream must contain raw audio chunks. `Uint8Array` chunks are raw bytes; `string` chunks are base64-encoded raw bytes. Always set `inputAudioFormat` to match the chunks you send. `targetLanguage` (and the optional `sourceLanguage`) are BCP-47-style language tags (e.g. `en`, `es`, `fr-CA`). Supported values are provider-specific and validated by the provider. When `sourceLanguage` is absent, providers auto-detect the source language. `fullStream` is a single-consumer live stream and can only be accessed once. When you need both stream parts and final results, access `fullStream` first and await the result promises while or after consuming it. Accessing a result promise first consumes the stream internally, so `fullStream` is no longer available. This avoids retaining an unbounded replay buffer for live audio. To access the final translation metadata: ```ts const sourceText = await result.sourceText; // final source-language transcript const translationText = await result.translationText; // final translated text const durationInSeconds = await result.durationInSeconds; // duration of the source audio in seconds, if available const usage = await result.usage; // audio/text token usage, if reported ``` A translation stream is considered successful when at least one `audio` part was emitted or the final output text is non-empty. For providers that produce only audio output, `translationText` may resolve to an empty string. ## Stream parts The `fullStream` yields the following part types: - `audio`: a translated audio chunk in the target language. - `output-text-delta`: an append-only translated text delta. - `output-text-final`: final translated text for a provider-defined segment or utterance. - `source-transcript-delta`: an append-only source transcript delta. - `source-transcript-partial`: non-final source transcript text that may be revised by later parts. - `source-transcript-final`: final source transcript text for a provider-defined segment or utterance. - `raw`: raw provider chunks when `includeRawChunks` is enabled. - `error`: stream errors. Output text is append-only: providers stream `output-text-delta` parts and finalize per-utterance with `output-text-final`. There is no partial/revision part for output text by design for now. ## Settings ### Output audio format Use `outputAudioFormat` to request a specific audio format for translated audio chunks. When absent, the provider default output format is used. ```ts {8} import { openai } from '@ai-sdk/openai'; import { experimental_streamTranslate as streamTranslate } from 'ai'; const result = streamTranslate({ model: openai.translation('gpt-realtime-translate'), audio: audioStream, inputAudioFormat: { type: 'audio/pcm', rate: 24000 }, outputAudioFormat: { type: 'audio/pcm', rate: 24000 }, targetLanguage: 'es', }); ``` ### Provider-Specific settings Translation models often have provider or model-specific settings which you can set using the `providerOptions` parameter. ```ts {9-13} import { openai } from '@ai-sdk/openai'; import { experimental_streamTranslate as streamTranslate } from 'ai'; const result = streamTranslate({ model: openai.translation('gpt-realtime-translate'), audio: audioStream, inputAudioFormat: { type: 'audio/pcm', rate: 24000 }, targetLanguage: 'es', providerOptions: { openai: { // provider-specific options }, }, }); ``` ### Abort Signals Pass an `abortSignal` to cancel the translation: ```ts {9} import { openai } from '@ai-sdk/openai'; import { experimental_streamTranslate as streamTranslate } from 'ai'; const result = streamTranslate({ model: openai.translation('gpt-realtime-translate'), audio: audioStream, inputAudioFormat: { type: 'audio/pcm', rate: 24000 }, targetLanguage: 'es', abortSignal: AbortSignal.timeout(60_000), // abort after 1 minute }); ``` ### Error Handling When `experimental_streamTranslate` cannot produce a translation — no `audio` part was emitted and the final output text is empty, or the stream ends without a finish event — it errors with a [`AI_NoTranslationGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-translation-generated-error). The error preserves the following information to help you log the issue: - `response`: Metadata about the speech translation model response, including timestamp, model, and headers. - `cause`: The cause of the error. You can use this for more detailed error handling. ```ts import { openai } from '@ai-sdk/openai'; import { experimental_streamTranslate as streamTranslate, NoTranslationGeneratedError, } from 'ai'; try { const result = streamTranslate({ model: openai.translation('gpt-realtime-translate'), audio: audioStream, inputAudioFormat: { type: 'audio/pcm', rate: 24000 }, targetLanguage: 'es', }); console.log(await result.translationText); } catch (error) { if (NoTranslationGeneratedError.isInstance(error)) { console.log('NoTranslationGeneratedError'); console.log('Cause:', error.cause); console.log('Response:', error.response); } } ``` ## Translation Models | Provider | Model | | --------------------------------------------------------------- | ----------------------------------- | | [OpenAI](/providers/ai-sdk-providers/openai#translation-models) | `gpt-realtime-translate` | | [Google](/providers/ai-sdk-providers/google#translation-models) | `gemini-3.5-live-translate-preview` | Above are a small subset of the translation models supported by the AI SDK providers. For more, see the respective provider documentation. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Video Generation description: Learn how to generate videos with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-core/video-generation" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Video generation is an experimental feature. The API may change in future versions. The AI SDK provides the [`experimental_generateVideo`](/docs/reference/ai-sdk-core/generate-video) function to generate videos based on a given prompt using a video model. ```tsx import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A cat walking on a treadmill', }); ``` You can access the video data using the `base64` or `uint8Array` properties: ```tsx const base64 = video.base64; // base64 video data const uint8Array = video.uint8Array; // Uint8Array video data ``` ## Settings ### Aspect Ratio The aspect ratio is specified as a string in the format `{width}:{height}`. Models only support a few aspect ratios, and the supported aspect ratios are different for each model and provider. ```tsx {7} import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A cat walking on a treadmill', aspectRatio: '16:9', }); ``` Some models also accept `'adaptive'`, which lets the provider derive the output ratio from the input media instead of a fixed value. This is typically required for image-to-video, video editing, and video extension, where the output inherits the ratio of the input and an explicit ratio is rejected. ```tsx {7} import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: { image: firstFrame, text: 'A cat walking on a treadmill' }, aspectRatio: 'adaptive', }); ``` ### Resolution The resolution is specified as a string in the format `{width}x{height}`. Models only support specific resolutions, and the supported resolutions are different for each model and provider. ```tsx {7} import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A serene mountain landscape at sunset', resolution: '1280x720', }); ``` ### Duration Some video models support specifying the duration of the generated video in seconds. ```tsx {7} import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A timelapse of clouds moving across the sky', duration: 5, }); ``` ### Frames Per Second (FPS) Some video models allow you to specify the frames per second for the generated video. ```tsx {7} import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A hummingbird in slow motion', fps: 24, }); ``` ### Audio Generation Some video models can generate audio alongside the video. Use the `generateAudio` option to control this: ```tsx {7} import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A jazz band playing in a cozy club', generateAudio: true, }); ``` ### Generating Multiple Videos `experimental_generateVideo` supports generating multiple videos at once: ```tsx {7} import { experimental_generateVideo as generateVideo } from 'ai'; const { videos } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A rocket launching into space', n: 3, // number of videos to generate }); ``` `experimental_generateVideo` will automatically call the model as often as needed (in parallel) to generate the requested number of videos. Each video model has an internal limit on how many videos it can generate in a single API call. The AI SDK manages this automatically by batching requests appropriately when you request multiple videos using the `n` parameter. Most video models only support generating 1 video per call due to computational cost. If needed, you can override this behavior using the `maxVideosPerCall` setting: ```tsx const { videos } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A rocket launching into space', maxVideosPerCall: 2, // Override the default batch size n: 4, // Will make 2 calls of 2 videos each }); ``` ### Image-to-Video Generation Some video models support generating videos from an input image. You can provide an image using the prompt object: ```tsx {6-9} import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: { image: 'https://example.com/my-image.png', text: 'Animate this image with gentle motion', }, }); ``` You can also provide the image as a base64-encoded string or `Uint8Array`: ```tsx const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: { image: imageBase64String, // or imageUint8Array text: 'Animate this image', }, }); ``` ### First and Last Frame Some video models support first-last-frame generation, where you provide the starting and/or ending frames of the video. Use the `frameImages` option to pass role-tagged images in a provider-agnostic way: ```tsx {4-13} const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'The cat walks across the scene and transforms into a dog by the end', frameImages: [ { image: 'https://example.com/first-frame.png', frameType: 'first_frame', }, { image: 'https://example.com/last-frame.png', frameType: 'last_frame', }, ], }); ``` ### Reference Inputs Some video models support reference-to-video generation, where you provide one or more reference images or videos that the model incorporates into the generated video. Use the `inputReferences` option to pass these inputs in a provider-agnostic way: ```tsx {4-7} const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'The two characters meet in a bustling market', inputReferences: [ 'https://example.com/character-1.png', 'https://example.com/character-2.png', ], }); ``` For URL-based video references, use the object form with an explicit `mediaType`: ```tsx {4-9} const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'Match the motion in the reference clip', inputReferences: [ { data: 'https://example.com/reference.mp4', mediaType: 'video/mp4', }, ], }); ``` Providers route each reference by its media type (image vs. video) and emit a warning when a reference kind is unsupported (for example, providers that accept only image references warn and ignore a video reference). ### Providing a Seed You can provide a seed to the `experimental_generateVideo` function to control the output of the video generation process. If supported by the model, the same seed will always produce the same video. ```tsx {7} import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A cat walking on a treadmill', seed: 1234567890, }); ``` ### Provider-specific Settings Video models often have provider- or even model-specific settings. You can pass such settings to the `experimental_generateVideo` function using the `providerOptions` parameter. The options for the provider become request body properties. ```tsx {8-10} import { experimental_generateVideo as generateVideo } from 'ai'; import { fal } from '@ai-sdk/fal'; const { video } = await generateVideo({ model: fal.video('luma-dream-machine/ray-2'), prompt: 'A cat walking on a treadmill', aspectRatio: '16:9', providerOptions: { fal: { loop: true, motionStrength: 0.8 }, }, }); ``` ### Abort Signals and Timeouts `experimental_generateVideo` accepts an optional `abortSignal` parameter of type [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that you can use to abort the video generation process or set a timeout. ```ts {7} import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A cat walking on a treadmill', abortSignal: AbortSignal.timeout(60000), // Abort after 60 seconds }); ``` Video generation typically takes longer than image generation. Consider using longer timeouts (60 seconds or more) depending on the model and video length. ### Polling Video generation is an asynchronous process that can take several minutes to complete. The SDK automatically polls the provider to check if the video is ready. You can configure the polling behavior: ```tsx {8-11} import { experimental_generateVideo as generateVideo } from 'ai'; import { fal } from '@ai-sdk/fal'; const { video } = await generateVideo({ model: fal.video('luma-dream-machine/ray-2'), prompt: 'A cinematic timelapse of a city from dawn to dusk', duration: 10, poll: { intervalMs: 5000, // Check every 5 seconds (default) timeoutMs: 600000, // Timeout after 10 minutes (default) }, }); ``` For durable workflows that provide their own sleep primitive, pass it as `poll.delay`. The custom delay is used for both polling intervals and webhook timeouts. ### Webhooks For models with native webhook support, pass a `webhook` factory that returns a public URL and a promise that resolves when your application receives the webhook request: ```tsx {8-15} import { fal } from '@ai-sdk/fal'; import { experimental_generateVideo as generateVideo } from 'ai'; import { createWebhook } from './create-webhook'; const { video } = await generateVideo({ model: fal.video('luma-dream-machine/ray-2'), prompt: 'A cinematic timelapse of a city from dawn to dusk', poll: { timeoutMs: 600000, // Wait up to 10 minutes for the webhook }, webhook: async () => { const { url, received } = await createWebhook(); return { url, received }; }, }); ``` `createWebhook` is an application-specific helper that registers a webhook listener before returning. Its `received` promise must resolve with the request `headers` and `body`. The SDK sends `url` to the provider, waits for `received`, and then retrieves the completed video. You can provide `poll` together with `webhook`. For models with native webhook support, `poll.timeoutMs` limits how long the SDK waits for the notification. If the model does not support webhooks, the SDK falls back to polling with the provided interval and timeout. ### Custom Headers `experimental_generateVideo` accepts an optional `headers` parameter of type `Record` that you can use to add custom headers to the video generation request. ```ts {7} import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A cat walking on a treadmill', headers: { 'X-Custom-Header': 'custom-value' }, }); ``` ### Warnings If the model returns warnings, e.g. for unsupported parameters, they will be available in the `warnings` property of the response. ```tsx const { video, warnings } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A cat walking on a treadmill', }); ``` ### Additional Provider-specific Metadata Some providers expose additional metadata for the result overall or per video. ```tsx const prompt = 'A cat walking on a treadmill'; const { video, providerMetadata } = await generateVideo({ model: fal.video('luma-dream-machine/ray-2'), prompt, }); // Access provider-specific metadata const videoMetadata = providerMetadata.fal?.videos[0]; console.log({ duration: videoMetadata?.duration, fps: videoMetadata?.fps, width: videoMetadata?.width, height: videoMetadata?.height, }); ``` The outer key of the returned `providerMetadata` is the provider name. The inner values are the metadata. A `videos` key is typically present in the metadata and is an array with the same length as the top level `videos` key. When generating multiple videos with `n > 1`, you can also access per-call metadata through the `responses` array: ```tsx const { videos, responses } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A rocket launching into space', n: 5, // May require multiple API calls }); // Access metadata from each individual API call for (const response of responses) { console.log({ timestamp: response.timestamp, modelId: response.modelId, // Per-call provider metadata (lossless) providerMetadata: response.providerMetadata, }); } ``` ### Error Handling When `experimental_generateVideo` cannot generate a valid video, it throws a [`AI_NoVideoGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-video-generated-error). This error occurs when the AI provider fails to generate a video. It can arise due to the following reasons: - The model failed to generate a response - The model generated a response that could not be parsed The error preserves the following information to help you log the issue: - `responses`: Metadata about the video model responses, including timestamp, model, and headers. - `cause`: The cause of the error. You can use this for more detailed error handling ```ts import { experimental_generateVideo as generateVideo, NoVideoGeneratedError, } from 'ai'; try { await generateVideo({ model, prompt }); } catch (error) { if (NoVideoGeneratedError.isInstance(error)) { console.log('NoVideoGeneratedError'); console.log('Cause:', error.cause); console.log('Responses:', error.responses); } } ``` ## Video Models | Provider | Model | Features | | ------------------------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------ | | [Black Forest Labs](/providers/ai-sdk-providers/black-forest-labs#video-models) | `flux-3-video` | Text-to-video, image-to-video, keyframes, video continuation, audio generation | | [FAL](/providers/ai-sdk-providers/fal#video-models) | `luma-dream-machine/ray-2` | Text-to-video, image-to-video | | [FAL](/providers/ai-sdk-providers/fal#video-models) | `minimax-video` | Text-to-video | | [Google](/providers/ai-sdk-providers/google#video-models) | `veo-2.0-generate-001` | Text-to-video, up to 4 videos per call | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#video-models) | `veo-3.1-generate-001` | Text-to-video, audio generation | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#video-models) | `veo-3.1-fast-generate-001` | Text-to-video, audio generation | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#video-models) | `veo-3.0-generate-001` | Text-to-video, audio generation | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#video-models) | `veo-3.0-fast-generate-001` | Text-to-video, audio generation | | [Google Vertex](/providers/ai-sdk-providers/google-vertex#video-models) | `veo-2.0-generate-001` | Text-to-video, up to 4 videos per call | | [Kling AI](/providers/ai-sdk-providers/klingai#video-models) | `kling-v2.6-t2v` | Text-to-video | | [Kling AI](/providers/ai-sdk-providers/klingai#video-models) | `kling-v2.6-i2v` | Image-to-video | | [Kling AI](/providers/ai-sdk-providers/klingai#video-models) | `kling-v2.6-motion-control` | Motion control | | [Replicate](/providers/ai-sdk-providers/replicate#video-models) | `minimax/video-01` | Text-to-video | | [xAI](/providers/ai-sdk-providers/xai#video-models) | `grok-imagine-video` | Text-to-video, image-to-video, editing, extension, R2V | | [xAI](/providers/ai-sdk-providers/xai#video-models) | `grok-imagine-video-1.5` | Text-to-video, image-to-video, editing, extension, R2V (with reference audio) | Above are a small subset of the video models supported by the AI SDK providers. For more, see the respective provider documentation. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Harness Adapters description: Learn about the available AI SDK harness adapters. url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/harness-adapters" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Harness adapters connect `HarnessAgent` to a specific agent runtime. They are the harness equivalent of AI SDK model providers: each adapter wraps one runtime and normalizes its sessions, stream events, tools, usage, lifecycle state, and configuration into the harness contract. ## AI SDK Harness Adapters The AI SDK includes the following harness adapters: - [Claude Code](/providers/ai-sdk-harnesses/claude-code) (`@ai-sdk/harness-claude-code`) - [Cline](/providers/ai-sdk-harnesses/cline) (`@ai-sdk/harness-cline`) - [Codex](/providers/ai-sdk-harnesses/codex) (`@ai-sdk/harness-codex`) - [Cursor](/providers/ai-sdk-harnesses/cursor) (`@ai-sdk/harness-cursor`) - [Deep Agents](/providers/ai-sdk-harnesses/deepagents) (`@ai-sdk/harness-deepagents`) - [fx](/providers/ai-sdk-harnesses/fx) (`@ai-sdk/harness-fx`) - [GitHub Copilot](/providers/ai-sdk-harnesses/github-copilot) (`@ai-sdk/harness-github-copilot`) - [Grok Build](/providers/ai-sdk-harnesses/grok-build) (`@ai-sdk/harness-grok-build`) - [OpenCode](/providers/ai-sdk-harnesses/opencode) (`@ai-sdk/harness-opencode`) - [Pi](/providers/ai-sdk-harnesses/pi) (`@ai-sdk/harness-pi`) ### Coming Soon - Amp (`@ai-sdk/harness-amp`) - Goose (`@ai-sdk/harness-goose`) - Mastra (`@ai-sdk/harness-mastra`) ## Adapter Capabilities | Adapter | Runtime location | Custom tools | Custom skills | Structured output | Built-in tool approval | Built-in tool filtering | Image input | History access | | ------------------------------------------------------------ | ---------------- | ------------ | ------------- | ----------------- | ---------------------- | ----------------------- | ----------- | -------------- | | [Claude Code](/providers/ai-sdk-harnesses/claude-code) | Sandbox bridge | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | | [Cline](/providers/ai-sdk-harnesses/cline) | Host process | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | | [Codex](/providers/ai-sdk-harnesses/codex) | Sandbox bridge | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✗ | | [Cursor](/providers/ai-sdk-harnesses/cursor) | Sandbox via ACP | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✗ | | [Deep Agents](/providers/ai-sdk-harnesses/deepagents) | Sandbox bridge | ✓ | ✓ | ✓ | ✓ | ✓ via auto-rejection | ✓ | ✗ | | [fx](/providers/ai-sdk-harnesses/fx) | Host process | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | | [GitHub Copilot](/providers/ai-sdk-harnesses/github-copilot) | Sandbox via ACP | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ | ✗ | | [Grok Build](/providers/ai-sdk-harnesses/grok-build) | Sandbox via ACP | ✓ | ✓ | ✓ | ✗ | ✗ | ✓ | ✗ | | [OpenCode](/providers/ai-sdk-harnesses/opencode) | Sandbox bridge | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✗ | | [Pi](/providers/ai-sdk-harnesses/pi) | Host process | ✓ | ✓ | ✗ | ✓ | ✓ | ✓ | ✗ | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: HarnessAgent description: Create and run AI SDK HarnessAgent sessions. url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/harness-agent" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `HarnessAgent` is an AI SDK `Agent` implementation backed by a harness adapter. It gives you `generate()` and `stream()` methods that return AI SDK-compatible results while a preconfigured harness powers these results. ## Installation Install the core harness package, a harness adapter, and a sandbox adapter: ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-claude-code @ai-sdk/sandbox-vercel ``` Bridge-backed harnesses such as Claude Code and Codex require using a real network sandbox like `@ai-sdk/sandbox-vercel`. Host-runtime harnesses such as Pi can also run with `@ai-sdk/sandbox-just-bash` because they do not need a sandbox-exposed port. ## Create an Agent ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { claudeCode } from '@ai-sdk/harness-claude-code'; export const agent = new HarnessAgent({ harness: claudeCode, model: 'claude-sonnet-4-6', instructions: 'You are a careful coding assistant. Prefer small changes and explain tradeoffs.', }); ``` Construct the agent at module scope. It holds configuration, not a live session. Live state belongs to `HarnessAgentSession`. Create a sandbox session separately and pass it to `agent.createSession()`: ```ts import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); ``` Set `model` to select the model that the harness runtime uses. Model identifiers are specific to each harness. When omitted, the harness uses its default model. The model is applied per turn, so `prepareCall` can replace it between turns. To use this agent, ensure environment variables with sandbox and harness credentials are set. ## Run a Turn ```ts const session = await agent.createSession({ sandboxSession }); let exitCode = 0; try { const result = await agent.generate({ session, prompt: 'Create a short TODO.md for this repository.', }); console.log(result.text); } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); await sandboxSession.destroy(); process.exit(exitCode); } ``` `generate()` drains the turn and returns a `GenerateTextResult`. Use `stream()` for incremental output: ```ts const session = await agent.createSession({ sandboxSession }); let exitCode = 0; try { const result = await agent.stream({ session, prompt: 'Create a short TODO.md for this repository.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); await sandboxSession.destroy(); process.exit(exitCode); } ``` ### Image Inputs For harness adapters that support images, you can include an image as a file part in the latest user message. Set `mediaType` to the image's MIME type: ```ts import { readFileSync } from 'node:fs'; const result = await agent.generate({ session, messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe the image in one sentence.', }, { type: 'file', mediaType: 'image/png', data: readFileSync('image.png'), }, ], }, ], }); ``` For image input support, see the [adapter capability table](/docs/ai-sdk-harnesses/harness-adapters#adapter-capabilities). ## Lifecycle Callbacks Configure lifecycle callbacks on `HarnessAgent` to observe agent calls, model steps, and tool executions: ```ts const agent = new HarnessAgent({ harness: claudeCode, tools: { weather }, onStart: event => console.log('call started', event.callId), onStepStart: event => console.log('step started', event.stepNumber), onLanguageModelCallStart: event => console.log('model call started', event.modelId), onLanguageModelCallEnd: event => console.log('model call ended', event.finishReason), onToolExecutionStart: event => console.log('tool started', event.toolCall.toolName), onToolExecutionEnd: event => console.log('tool ended', event.toolOutput.type), onStepEnd: step => console.log('step ended', step.stepNumber), onEnd: event => console.log('call ended', event.finishReason), }); ``` Callbacks configured on individual `generate()` and `stream()` calls are invoked in addition to settings callbacks, with settings callbacks invoked first. Callback errors are ignored and do not change agent execution. Harness runtimes execute their built-in tools internally. For those tools, `onToolExecutionStart` and `onToolExecutionEnd` describe the logical tool lifecycle after the runtime reports the result. The callbacks are still delivered before the tool result is published to the result stream. ## Runtime Context Set `runtimeContext` on `HarnessAgent` to make application context available to every turn. Use `prepareCall` to replace it for each new prompt turn. The prepared context is available through `generate()` and `stream()` and remains the same during in-memory continuations with `continueGenerate()` and `continueStream()`. Lifecycle callbacks receive the full context, and it is also available on each result's `finalStep.runtimeContext`. ```ts const agent = new HarnessAgent({ harness: claudeCode, runtimeContext: { conversationId: 'conversation-123', userId: 'user-456', }, telemetry: { includeRuntimeContext: { conversationId: true, }, }, onEnd: ({ runtimeContext }) => { console.log(runtimeContext.userId); }, }); ``` Runtime context is excluded from telemetry by default. Use `telemetry.includeRuntimeContext` to allowlist individual top-level properties; only properties set to `true` are sent to telemetry integrations. Lifecycle callbacks and results still receive the full context, so avoid storing secrets that those consumers should not access. ## Generate Structured Output Set `output` when constructing `HarnessAgent` to require the same typed output on every turn. The agent converts the output specification to JSON Schema for the harness adapter, validates the completed response, and returns the parsed value through `result.output`. ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { Output } from 'ai'; import { z } from 'zod'; const agent = new HarnessAgent({ harness: claudeCode, output: Output.object({ schema: z.object({ recipe: z.object({ name: z.string(), ingredients: z.array( z.object({ name: z.string(), amount: z.number(), unit: z.enum(['oz', 'fl oz', 'cup', 'gallon']), }), ), steps: z.array(z.string()), }), }), }), }); const session = await agent.createSession({ sandboxSession }); try { const result = await agent.generate({ session, prompt: 'Generate a lasagna recipe.', }); console.dir(result.output, { depth: Infinity }); } finally { await session.destroy(); } ``` With `stream()`, read `partialOutputStream` for incrementally parsed values and await `result.output` for the validated final value. Structured data also remains available as JSON in the normal text and stream surfaces; adapters do not add it to the `finish` part. Harness structured output requires a schema. Schema-less `Output.json()` and adapters or runtime configurations that cannot enforce the schema throw `HarnessCapabilityUnsupportedError`; see the [adapter capability table](/docs/ai-sdk-harnesses/harness-adapters#adapter-capabilities). ## Messages and History A harness session owns its native conversation history. When you pass `messages` or a message-array `prompt`, `HarnessAgent` takes the latest user message as the fresh input for the turn. It does not replay the full prior conversation into the harness. A trailing tool message is handled differently: tool approval responses and client-provided tool results continue the unfinished harness turn that produced the corresponding request or tool call. This is different from model calls, where the application usually sends the full message history. In chat routes, persist and resume the harness session instead of relying on message replay. ### Reading the Runtime's Own History The runtime's conversation can grow outside your process — the same conversation continued interactively in the agent's own CLI, for instance — and the live event stream never sees those exchanges. Adapters that can reach their runtime's store expose them through `session.readHistory()`: ```ts const { messages, cursor } = await session.readHistory(); // Messages use role/content shapes. Adapter-specific details, including the // runtime's raw record, are available under harnessMetadata. // Later: read only what was recorded since. const delta = await session.readHistory({ since: cursor }); ``` History reads are adapter-specific; see the adapter capability table before calling this method. `readHistory()` throws `HarnessCapabilityUnsupportedError` when the adapter does not implement history reads, and `HarnessHistoryUnavailableError` when it does but cannot reach the runtime's store from this environment (for example, the store lives inside a remote sandbox). A conversation with no recorded messages yet is not an error; it resolves to an empty `messages` array. ## Session Lifecycle End every session explicitly: - `session.destroy()` stops the runtime and discards resumability. - `session.detach()` parks the runtime, returns resume state, and leaves a supplied sandbox running for a later attach. If the turn is unfinished, the resume state includes the continuation state. - `session.stop()` saves resume state, then stops the runtime. If the turn is unfinished, the resume state includes the continuation state. - `session.suspendTurn()` is for advanced active-turn continuation across a process boundary. - `session.hasUnfinishedTurn()` reports whether the current turn must be continued or suspended before the session accepts a new prompt. Use `destroy()` for one-off scripts and tests. Use `detach()` or `stop()` for HTTP routes that need multi-turn continuity. ## Change Settings Between Turns Use `callOptionsSchema` and `prepareCall` to derive `model`, `skills`, `instructions`, `tools`, and `runtimeContext` for each new turn. This follows the same call-options pattern as `ToolLoopAgent`: ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { tool } from 'ai'; import { z } from 'zod'; const getPolicy = tool({ description: 'Look up the active project policy.', inputSchema: z.object({}), execute: async () => 'Keep public APIs backward compatible.', }); const agent = new HarnessAgent({ harness: claudeCode, tools: { getPolicy }, callOptionsSchema: z.object({ area: z.enum(['frontend', 'backend']), enablePolicyTool: z.boolean(), useCheaperModel: z.boolean(), }), prepareCall: ({ options, ...call }) => ({ ...call, model: options.useCheaperModel ? 'claude-haiku-4-5' : undefined, instructions: `Work as the ${options.area} specialist.`, runtimeContext: { area: options.area }, skills: [options.area === 'frontend' ? frontendSkill : backendSkill], tools: options.enablePolicyTool ? { getPolicy } : undefined, }), }); const session = await agent.createSession({ sandboxSession }); try { await agent.generate({ session, prompt: 'Review the current implementation.', options: { area: 'frontend', enablePolicyTool: false, useCheaperModel: false, }, }); await agent.generate({ session, prompt: 'Now review the API contract.', options: { area: 'backend', enablePolicyTool: true, useCheaperModel: true, }, }); } finally { await session.destroy(); } ``` `prepareCall` runs for a new prompt after its custom `options` have been validated. Its settings are then fixed for that whole turn. If the turn pauses for a tool result, approval, stop condition, or process handoff, its continuation reuses the same settings and does not call `prepareCall` again. This prevents settings from changing mid-turn. Changing `model` does not create a new harness session. Each adapter switches or reconfigures its runtime before the next prompt while preserving the session's conversation history. The Codex adapter starts a fresh native Codex thread when its skills, instructions, or tool catalog changes because `codex exec resume` retains the original native thread bootstrap. The harness session remains usable, but prior native conversation context does not carry across that settings-change boundary. Turns with unchanged settings continue the existing native thread. Pass `abortSignal` directly to `generate()` or `stream()`; it is already a per-call setting and is not part of `prepareCall`. `output` also stays fixed on the agent because its response format is tied to the agent's output schema. When you pass `sandboxSession` to `agent.createSession()`, the caller retains ownership of that sandbox. `session.stop()` and `session.destroy()` still end the harness runtime but do not stop or destroy the supplied sandbox session. In this case, the agent does not need a `sandbox` provider in its constructor. ```ts const session = await agent.createSession({ sessionId: chatId, sandboxSession, }); try { const result = await agent.stream({ session, messages }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } const resumeState = await session.detach(); await persistResumeState({ chatId, resumeState }); } catch (error) { await session.destroy(); throw error; } ``` ## Resuming Persist the opaque resume state. In this example, the harness `sessionId` is `chatId`, and the sandbox ID is derived from the same known ID. If your application cannot derive the sandbox ID, persist `sandboxSession.id` alongside the harness `sessionId` and resume state instead. Reattach the sandbox before asking the agent to resume. When you choose `sandboxId` on creation, it names a **new** live sandbox and never looks one up. Reusing that ID with the creator fails if the named sandbox exists; only the resume function performs a lookup. ```ts const resumeState = await loadResumeState({ chatId }); const sandboxId = `chat-${chatId}`; const sandboxSession = resumeState ? await resumeVercelNetworkSandboxSession({ sandboxId }) : await createVercelNetworkSandboxSession({ sandboxId, ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession( resumeState ? { sessionId: chatId, resumeFrom: resumeState, sandboxSession } : { sessionId: chatId, sandboxSession }, ); ``` `HarnessAgent` validates that the resume state was produced by the same harness adapter before handing it to the runtime. If the resume state includes an unfinished turn, call `continueStream()` or `continueGenerate()` before sending a new prompt. The Vercel resume function reattaches whether the sandbox is still running after `detach()` or stopped with a restorable snapshot. After `sandboxSession.destroy()` deletes it, that ID cannot be resumed. ## Continue a Suspended Turn For advanced workflows that must hand off an active turn across a process boundary, suspend the turn and persist the continuation state: ```ts if (session.hasUnfinishedTurn()) { const continuationState = await session.suspendTurn(); await persistContinuationState({ chatId, continuationState }); } ``` When you only have raw continuation state from `suspendTurn()`, resume with `continueFrom`, then continue the turn without sending a new prompt: ```ts const session = await agent.createSession({ sessionId: chatId, continueFrom: continuationState, sandboxSession, // Rebind this when the suspended turn used host-only tool context: toolsContext, runtimeContext, }); const result = await agent.continueStream({ session }); ``` Use `continueStream()` for incremental output, or `continueGenerate()` to drain the continued turn and return a `GenerateTextResult`. `toolsContext` and `runtimeContext` are intentionally not stored in continuation state because they can contain credentials or non-serializable host objects. When a suspended turn used context derived by `prepareCall`, pass the same values to `createSession` to rebind them in the process that continues the turn. After a process resume without rebinding, the continuation uses the agent's constructor-level `runtimeContext` and `toolsContext`. ## Stop After a Harness Step Use `stopWhen` to opt into semantic step boundaries. Predicates run after real harness tool steps that can continue into another model step, and receive the completed steps from the current invocation. When a predicate matches, the returned result finishes while the underlying turn remains unfinished. A terminal text-only step instead consumes the turn's `finish` event and finishes naturally. ```ts import { isStepCount } from 'ai'; const steppedAgent = new HarnessAgent({ harness: claudeCode, stopWhen: isStepCount(1), }); const session = await steppedAgent.createSession({ sandboxSession }); const result = await steppedAgent.generate({ session, prompt: 'Create a short TODO.md for this repository.', }); if (session.hasUnfinishedTurn()) { const continueFrom = await session.suspendTurn(); await persistContinuationState({ chatId, continuationState: continueFrom }); } ``` `stopWhen` has no default. When omitted, `HarnessAgent` continues running until the turn naturally finishes or pauses for host input, preserving the behavior of agents without step control. Pass one predicate or an array; matching any predicate finishes the current result slice. Resume a stopped turn with `createSession({ continueFrom })` and `continueStream()` or `continueGenerate()`. ## Prepare the Sandbox Use `sandboxConfig` to prepare the sandbox before the harness starts. `sandboxConfig.onBootstrap` runs during sandbox template creation, after the harness adapter's own bootstrap and before snapshot-capable providers publish a snapshot. Use it for expensive setup that should be reused by future sessions. When you provide `onBootstrap`, also provide `bootstrapHash`; change the hash whenever the bootstrap output should invalidate the reusable snapshot. `sandboxConfig.onSession` runs after each sandbox session is acquired and its working directory exists, including resumed sessions. Use it for per-session files or lightweight configuration. ```ts const agent = new HarnessAgent({ harness: claudeCode, sandboxConfig: { workDir: 'repo', bootstrapHash: 'ripgrep-v1', onBootstrap: async ({ session, abortSignal }) => { const result = await session.run({ command: 'command -v rg >/dev/null || (apt-get update && apt-get install -y ripgrep)', abortSignal, }); if (result.exitCode !== 0) { throw new Error(`Failed to install ripgrep: ${result.stderr}`); } }, onSession: async ({ session, sessionWorkDir, abortSignal }) => { await session.writeTextFile({ path: `${sessionWorkDir}/README.md`, content: 'Session notes for the harness.', abortSignal, }); }, }, }); ``` `workDir` is optional. When provided, it must be relative to the sandbox's default working directory and is used as the session working directory. Set it to `'.'` to use the sandbox's default working directory itself. When omitted, regular sessions use the default `-` directory, while `onBootstrap` receives the sandbox's default working directory. ## Prepare Reusable Sandboxes Use `agent.getSandboxTemplate()` to resolve one harness's bootstrap recipe and `sandboxConfig.onBootstrap` before creating a sandbox. The template's identity is known before sandbox creation. Vercel persists the prepared snapshot and creates a live sandbox from it; identical calls reuse that snapshot: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); try { await agent.generate({ session, prompt: 'Inspect this repository.' }); } finally { await session.destroy(); await sandboxSession.destroy(); } ``` For multiple harnesses sharing a caller bootstrap hook, build one template from all adapters. The last adapter wins for duplicate `harnessId` values, and the recipe identities are sorted before hashing. `onSession` is not part of the template and runs separately on every acquired session. ```ts import { createHarnessSandboxTemplate } from '@ai-sdk/harness/agent'; const template = await createHarnessSandboxTemplate({ harnesses: [claudeCode, codex], sandboxConfig, }); console.log(template?.identity); const sandboxSession = await createVercelNetworkSandboxSession({ source: { type: 'snapshot', snapshotId: baseSnapshotId }, ports: [4000], template, }); ``` The derived template includes the source snapshot ID in its cache key, so repeated calls with the same source and template reuse the prepared snapshot. Pin mutable Git revisions, image tags, and source URLs when relying on cached templates. `sandboxId` names the live sandbox, not its reusable template. The existing native `name` option still names the live sandbox; if both are supplied, their values must match. ## Settings `HarnessAgent` accepts these main settings: - `harness`: the adapter instance. - `model`: optional harness-specific model identifier. When omitted, the harness uses its default model. - `id`: optional stable agent identifier. - `instructions`: instructions appended to the runtime's system or developer prompt when supported, or prepended to the user prompt otherwise. - `headers`: additional headers sent with model requests. Headers are fixed at construction time. `authorization`, `x-api-key`, `user-agent`, and `x-client-app` are not allowed. - `callOptionsSchema` and `prepareCall`: validate custom call options and derive model, skills, instructions, and tools for each new turn. - `output`: typed output specification applied to every turn. - `stopWhen`: condition(s) for finishing a result slice after a completed harness tool step that can continue into another model step. - `tools`: AI SDK tools executed by the host when the harness calls them. - `runtimeContext`: application context forwarded to lifecycle callbacks and results for every turn; `prepareCall` can replace it for a new prompt turn. - `activeTools`: allowlist of built-in and host-executed tools the harness can call. - `inactiveTools`: denylist of built-in and host-executed tools the harness cannot call. - `skills`: instruction bundles surfaced by the adapter. - `permissionMode`: built-in tool permission mode. - `toolApproval`: approval status map for host-executed tools. - `sandboxConfig`: sandbox working-directory and lifecycle hook configuration. - `telemetry`, `debug`, and `onLog`: observability and diagnostics. Use `telemetry.includeRuntimeContext` to allowlist runtime context properties for telemetry. Telemetry reports each turn's resolved model, instructions, and active host-defined tools. Skills are adapter context and do not have a corresponding AI SDK telemetry field, so they are not included in standard telemetry events. Adapter-specific settings belong on the adapter factory, for example `createCodex({ reasoningEffort: 'high' })`. ## Custom Sandbox Orchestration When your application creates and manages sandboxes itself, prepare the network sandbox session first and then pass that same session to `agent.createSession()`. The agent does not create the sandbox and does not stop or destroy the caller-owned sandbox. ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { claudeCode } from '@ai-sdk/harness-claude-code'; import { createVercelNetworkSandboxSessionFromNativeSandbox } from '@ai-sdk/sandbox-vercel'; import { Sandbox } from '@vercel/sandbox'; const sandbox = await Sandbox.create({ runtime: 'node24', ports: [4000], }); const sandboxSession = createVercelNetworkSandboxSessionFromNativeSandbox(sandbox); const agent = new HarnessAgent({ harness: claudeCode }); const session = await agent.createSession({ sandboxSession }); try { const result = await agent.stream({ session, prompt: 'Create a short TODO.md for this repository.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } finally { await session.destroy(); await sandboxSession.destroy(); } ``` ### Basic Sandbox Sessions Without Network Control The following example demonstrates how the basic-session API works. If your project can expose a full network sandbox session, passing that session is strongly recommended. Pass a restricted basic session only when your project cannot expose the full network session. A basic sandbox session only exposes filesystem and process APIs. The agent still leaves the sandbox lifecycle to the caller. For a bridge-backed harness, configure the bridge port and its externally reachable endpoint on the adapter because the basic session cannot resolve them. ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { createClaudeCode } from '@ai-sdk/harness-claude-code'; import { createVercelNetworkSandboxSessionFromNativeSandbox, createVercelSandboxSessionFromNativeSandbox, } from '@ai-sdk/sandbox-vercel'; import { Sandbox } from '@vercel/sandbox'; const sandbox = await Sandbox.create({ runtime: 'node24', ports: [4000], }); const sandboxSession = createVercelNetworkSandboxSessionFromNativeSandbox(sandbox); const portEndpoint = await sandboxSession.getPortEndpoint({ port: 4000, protocol: 'ws', }); const restrictedSandboxSession = createVercelSandboxSessionFromNativeSandbox(sandbox); const claudeCode = createClaudeCode({ port: 4000, portEndpoint }); const agent = new HarnessAgent({ harness: claudeCode }); const session = await agent.createSession({ sandboxSession: restrictedSandboxSession, }); try { const result = await agent.stream({ session, prompt: 'Create a short TODO.md for this repository.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } finally { await session.destroy(); await sandboxSession.destroy(); } ``` ## Next Steps - [Tools](/docs/ai-sdk-harnesses/tools) for built-in and host-executed tools. - [Skills](/docs/ai-sdk-harnesses/skills) for reusable instruction bundles. - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) for adapter-specific settings. - [Workflow utilities](/docs/ai-sdk-harnesses/workflow-utilities) for durable long-running turns. - [UI](/docs/ai-sdk-harnesses/ui) for `useChat` integration. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Overview description: Learn what AI SDK harnesses are and how they fit into the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/overview" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK harness abstraction lets you run established agent harnesses through a single AI SDK surface. A harness is a complete agent runtime, such as Claude Code, Codex, or Pi. It owns capabilities that are larger than a model call: workspace access, built-in coding tools, native session state, compaction, permission flows, and runtime-specific configuration. Harness adapters can run the runtime in a sandbox, but their execution environments differ by adapter; review the adapter's runtime location and security guidance before use. The AI SDK harness abstraction is separate from the provider/model abstraction. Providers expose models to AI SDK Core functions such as `generateText` and `streamText`. Harnesses expose agent runtimes to `HarnessAgent`. Using a harness does not mean giving up customization. `HarnessAgent` lets you provide your own instructions, skills, AI SDK tools, permission settings, sandbox setup hooks, and adapter-specific configuration while preserving the runtime behavior that makes each harness powerful. The two abstractions are decoupled, but they use compatible primitives where possible. Harness output is projected into AI SDK stream and response types, so surfaces that consume AI SDK model streams can also consume harness streams. For example, you can pass a `HarnessAgent` stream to `toUIMessageStream` and render it with `useChat`. The API is designed to feel familiar to AI SDK users, but harness runtimes have different concepts from lower-level language models. Configuration follows AI SDK patterns where they fit and diverges where the harness runtime has different behavior. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## When to Use a Harness Use a harness when you want an existing agent runtime to drive the task: - Coding agents that can inspect and modify a sandboxed workspace. - Agent runtimes with their own built-in tools and permission model. - Multi-turn sessions where the runtime owns conversation history. - Workflows that should preserve native harness behavior instead of re-creating it with a tool loop. Use providers and models when you want direct control over the model call, the tool loop, model settings, structured output, or a custom agent architecture. ## Core Concepts Harnesses have four primary pieces: - `HarnessAgent`: the AI SDK agent implementation you use in application code. - Harness adapter: the package that connects to a runtime, such as `@ai-sdk/harness-claude-code`. - Sandbox provider: the isolated filesystem and process environment where the harness runs. - Session: the live conversation and workspace state for a harness run. ## Compatible Streams `HarnessAgent.generate()` returns an AI SDK `GenerateTextResult`. `HarnessAgent.stream()` returns an AI SDK `StreamTextResult`. That means you can consume familiar fields: - `result.text` - `result.stream` - `result.steps` - `result.usage` - `result.responseMessages` Harness-specific events are translated into compatible stream parts. Text, reasoning, tool calls, tool results, usage, and finish reasons use the same AI SDK shapes where possible. Events without a first-class AI SDK part, such as workspace file changes and compaction, are surfaced as dynamic provider-executed tool parts. ## Sessions Matter Unlike a language model call, a harness session owns state. The session carries the harness runtime, sandbox, working directory, native conversation history, and pending approvals. Create a session before running turns: ```ts const session = await agent.createSession(); try { const result = await agent.generate({ session, prompt: 'Inspect the repository and summarize the test setup.', }); console.log(result.text); } finally { await session.destroy(); } ``` For server routes, use a stable `sessionId` and persist the resume state returned by `session.detach()` or `session.stop()`. ## Next Steps - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) for the main API. - [Skills](/docs/ai-sdk-harnesses/skills) for reusable instruction bundles. - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) for the implemented runtimes. - [Workflow utilities](/docs/ai-sdk-harnesses/workflow-utilities) for durable long-running turns. - [UI](/docs/ai-sdk-harnesses/ui) for `useChat` routes. - [Terminal UI](/docs/ai-sdk-harnesses/terminal-ui) for `@ai-sdk/tui`. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Skills description: Use skills with AI SDK harnesses. url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/skills" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [Skills](https://agentskills.io/) are reusable instruction bundles that can be useful for project conventions, workflow guidance, domain-specific procedures, or any other instructions that should be discoverable by the underlying harness runtime. You can configure skills for a `HarnessAgent` or replace them between completed turns. ## Define Skills Pass skills to `HarnessAgent` with the `skills` setting: ```ts const agent = new HarnessAgent({ harness: claudeCode, skills: [ { name: 'careful-refactors', description: 'Make small, low-risk code changes.', content: 'Prefer minimal diffs. Preserve public APIs. Before editing, read references/checklist.md and follow it.', files: [ { path: 'references/checklist.md', content: '# Refactor checklist\n\n- Identify the smallest useful change.\n- Preserve public APIs.\n- Run the narrowest relevant test.', }, ], }, ], }); ``` Each skill has: - `name`: stable identifier for the skill. - `description`: short model-facing summary. - `content`: full instruction content. - `files`: optional additional text files bundled with the skill. Additional files use skill-relative POSIX paths, for example `reference.md`, `references/codes.md`, or `templates/config.json`. Reference those paths from `content` when the agent should read them. ## When to Use Skills Use skills for reusable instructions that should be available on demand, instead of always being loaded into the agent's context like regular `instructions`. Use `instructions` for broad agent behavior and current-session priorities. Skills can be changed between completed turns using `callOptionsSchema` and `prepareCall`. See [Change Settings Between Turns](/docs/ai-sdk-harnesses/harness-agent#change-settings-between-turns) for details. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Terminal UI description: "Use @ai-sdk/tui with HarnessAgent." url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/terminal-ui" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `@ai-sdk/tui` can render harness streams, tool calls, reasoning sections, and approval prompts in a terminal. Because `HarnessAgent` requires a session for `generate` and `stream` calls, wrap it with a small `AgentTUIAgent` adapter that injects one session for the lifetime of the terminal UI. ## Installation ```bash pnpm add @ai-sdk/tui @ai-sdk/harness @ai-sdk/harness-codex @ai-sdk/sandbox-vercel ``` ## Example ```ts title='tui.ts' import { HarnessAgent, type HarnessAgentSession } from '@ai-sdk/harness/agent'; import { codex } from '@ai-sdk/harness-codex'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; import { runAgentTUI, type AgentTUIAgent } from '@ai-sdk/tui'; const agent = new HarnessAgent({ harness: codex, }); function createTUIAgent({ agent, session, }: { agent: HarnessAgent; session: HarnessAgentSession; }): AgentTUIAgent { return { version: 'agent-v1', id: agent.id, tools: agent.tools, generate(request) { return agent.generate({ ...request, session, } as Parameters[0]); }, stream(request) { return agent.stream({ ...request, session, } as Parameters[0]); }, } as AgentTUIAgent; } const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); try { await runAgentTUI({ title: 'Codex', agent: createTUIAgent({ agent, session }), tools: 'auto-collapsed', reasoning: 'collapsed', }); } finally { await session.destroy(); await sandboxSession.destroy(); } ``` The terminal UI runs until the user exits with `Esc` or `Ctrl+C`. Use one session per terminal run. For long-lived terminal tools, persist the state from `session.detach()` or `session.stop()` if you need to resume later. ## Related - [Terminal UI](/docs/agents/terminal-ui) - [runAgentTUI API Reference](/docs/reference/ai-sdk-tui/run-agent-tui) - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Tools description: Use tools with AI SDK harnesses. url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/tools" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Harnesses have three tool surfaces: - Built-in tools exposed by the underlying harness runtime, such as file reads, edits, shell commands, and web search. - AI SDK tools that you pass to `HarnessAgent` with the `tools` setting. - External MCP tools configured through the harness adapter's `mcpServers` setting. This page covers harness-specific behavior. For general AI SDK tool concepts, schemas, tool results, and `tool()` usage, see [Tools](/docs/foundations/tools). ## Built-in Tools Each adapter declares the built-in tools its runtime can call natively. `HarnessAgent` merges those built-ins with your host-defined tools and exposes the combined tool set through `agent.tools`. ```ts const agent = new HarnessAgent({ harness: claudeCode, }); agent.tools.bash; agent.tools.read; agent.tools.write; ``` Built-in calls are executed by the harness runtime, not by your application process. Stream parts use `providerExecuted: true` when the runtime already performed the call. Adapters use common names where possible: - `read` - `write` - `edit` - `bash` - `grep` - `glob` - `webSearch` Some runtimes also expose native tools without a common cross-harness name. Those appear under their native names. ## Host-Executed Tools Pass AI SDK tools to `HarnessAgent` the same way you do for a `ToolLoopAgent`: ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { claudeCode } from '@ai-sdk/harness-claude-code'; import { tool } from 'ai'; import { z } from 'zod'; const weather = tool({ description: 'Get the current temperature for a city.', inputSchema: z.object({ city: z.string(), }), execute: async ({ city }) => { const temperatures: Record = { Paris: 12, Tokyo: 18, Reykjavik: 3, }; return { city, celsius: temperatures[city] ?? 20 }; }, }); const agent = new HarnessAgent({ harness: claudeCode, tools: { weather }, }); ``` When the harness calls `weather`, `HarnessAgent` executes the tool in your host process, then submits the result back to the harness runtime. Host-executed tools can declare a `contextSchema` and receive turn-scoped context through `toolsContext`: ```ts const lookupAccount = tool({ inputSchema: z.object({}), contextSchema: z.object({ userId: z.string() }), execute: async (_, { context }) => { return loadAccount(context.userId); }, }); const agent = new HarnessAgent({ harness: claudeCode, tools: { lookupAccount }, toolsContext: { lookupAccount: { userId: 'user-123' }, }, }); ``` Use `prepareCall` to replace `toolsContext` when the value depends on custom call options. The type of `toolsContext` follows the tool set: it is rejected when no tool declares a context schema, required when a tool requires a context object, and optional when every context object is optional. `HarnessAgent` validates each entry against the tool's `contextSchema` before execution and exposes the configured map in step results and lifecycle callbacks. Tool context remains host-only and is not serialized into suspended-turn state. When recreating a session for an unfinished turn, rebind it explicitly: ```ts const session = await agent.createSession({ sessionId, continueFrom, sandboxSession, toolsContext: { lookupAccount: { userId: 'user-123' }, }, }); ``` Missing or invalid context fails validation before the host tool executes. The validation error returned to the harness runtime is intentionally generic so host-only context values and schema details are not disclosed to the model. ## Client-Side Tools Omit `execute` when a browser, user interaction, or another external process provides the tool result: ```ts const weather = tool({ description: 'Get the current temperature for a city.', inputSchema: z.object({ city: z.string() }), }); ``` When the harness calls a tool without `execute`, the returned result slice ends after the tool-call step while the underlying turn waits for a result. `session.hasUnfinishedTurn()` remains `true`, and `session.suspendTurn()` includes the pending tool call in its serializable continuation state. In UI flows, pass the model messages produced after `addToolOutput` to the next `stream()` or `generate()` call. `HarnessAgent` extracts the trailing tool result and continues the paused turn automatically. For direct agent calls, provide the raw result to `continueStream()` or `continueGenerate()`: ```ts const continued = await agent.continueStream({ session, toolResultContinuations: [ { toolCallId, output: { city: 'Paris', celsius: 12 }, }, ], }); ``` Set `isError: true` when the external tool failed. To continue in another process, call `suspendTurn()`, recreate the session with `continueFrom`, and then pass the result to `continueStream()` or `continueGenerate()`. ## Tool Filtering Use `activeTools` or `inactiveTools` on `HarnessAgent` to control which tools the harness can call. Both settings accept tool names from the combined tool set: the built-in tools declared by the harness adapter and the AI SDK tools passed with `tools`. `activeTools` is an allowlist: ```ts const agent = new HarnessAgent({ harness: claudeCode, tools: { weather }, activeTools: ['weather'], }); ``` `inactiveTools` is a denylist: ```ts const agent = new HarnessAgent({ harness: claudeCode, tools: { weather }, inactiveTools: ['bash', 'write'], }); ``` Pass either `activeTools` or `inactiveTools`, not both. The TypeScript settings type prevents combining them, and `HarnessAgent` also throws at runtime when both are specified. For host-executed tools, inactive tools are not passed to the underlying harness runtime. If the runtime still attempts to call one, `HarnessAgent` returns an execution-denied tool result. For built-in tools, support depends on the harness adapter. Some adapters can filter built-ins natively. Others enforce filtering through their built-in tool approval mechanism by denying inactive built-in calls before they execute, without emitting approval request or response stream parts. Adapters that support neither mechanism throw when you filter built-in tools. ## Sandbox in Tool Execution Host-executed tools receive the session sandbox through the same `experimental_sandbox` execution option used by AI SDK tools elsewhere. The value is a restricted sandbox session, so tools can read, write, and run commands without being able to stop the network sandbox or change its network policy. ```ts const inspectFile = tool({ description: 'Read a file from the harness workspace.', inputSchema: z.object({ path: z.string(), }), execute: async ({ path }, { experimental_sandbox }) => { return { content: await experimental_sandbox?.readTextFile({ path }), }; }, }); ``` ## Tool Approvals Harnesses distinguish built-in tool permissions from host-executed tool approvals. Use `permissionMode` for adapter-native built-ins: ```ts const agent = new HarnessAgent({ harness: pi, permissionMode: 'allow-edits', }); ``` Available values are: - `allow-all`: allow built-in reads, edits, and shell commands. This is the default. - `allow-edits`: allow reads and edits, but request approval for shell commands when the adapter supports built-in approvals. - `allow-reads`: allow reads, but request approval for edits and shell commands when the adapter supports built-in approvals. Use `toolApproval` for host-executed tools: ```ts const agent = new HarnessAgent({ harness: claudeCode, tools: { weather }, toolApproval: { weather: 'user-approval', }, }); ``` `toolApproval` accepts the same status values as AI SDK tool approval status objects: `not-applicable`, `approved`, `user-approval`, and `denied`. When approval is required, the stream pauses after a `tool-approval-request`. Continue the same session by sending a tool approval response message. In UI flows, `useChat` sends those messages for you when you add the approval result. In direct agent code, pass the approval response as `messages` on the next `stream()` or `generate()` call. ## Built-in Approval Support Most adapters can pause built-in tool calls for approval. Adapters that do not support it will error if an unsupported tool approval mode is specified. Host-executed tool approvals are handled by `HarnessAgent`, so they work across adapters. ## File Changes and Compaction Some harness events are not ordinary tool calls. For UI compatibility, `HarnessAgent` projects them as dynamic, provider-executed tool parts: - `fileChange`: emitted for opaque workspace file mutations. - `compaction`: emitted when the runtime compacts context. Check `part.dynamic` before assuming a tool part belongs to your typed tool set. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: UI description: Use AI SDK harnesses with useChat. url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/ui" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Harness streams are compatible with AI SDK UI message streams. You can use `useChat()` on the client and stream `HarnessAgent` output from a server route. The important difference from model-based chat routes is session management. A harness owns its conversation state, so the route should resume or create a `HarnessAgentSession` for the chat id instead of replaying the whole UI message history into a model. ## Client ```tsx title='app/page.tsx' 'use client'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { useState } from 'react'; export default function Page() { const [input, setInput] = useState(''); const { error, messages, sendMessage, status } = useChat({ id: 'example-chat', transport: new DefaultChatTransport({ api: '/api/chat', }), }); return ( <> {messages.map(message => (
{message.role === 'user' ? 'You: ' : 'AI: '} {message.parts.map((part, index) => { if (part.type === 'text') { return {part.text}; } if (part.type.startsWith('tool-') || part.type === 'dynamic-tool') { return
{JSON.stringify(part, null, 2)}
; } return null; })}
))} {error &&
{error.message}
}
{ event.preventDefault(); if (input.trim()) { sendMessage({ text: input }); setInput(''); } }} > setInput(event.target.value)} disabled={status !== 'ready'} />
); } ``` ## Agent Define the `HarnessAgent` on the server: ```ts title='app/api/chat/agent.ts' import { HarnessAgent } from '@ai-sdk/harness/agent'; import { claudeCode } from '@ai-sdk/harness-claude-code'; export const agent = new HarnessAgent({ harness: claudeCode, instructions: 'You are a helpful coding assistant.', }); ``` ## Session Store Persist the opaque resume state returned by `session.detach()`. If the turn paused for approval or was otherwise interrupted, that resume state carries the continuation state internally. The chat ID can also be the harness `sessionId`. Here we derive a stable `sandboxId` from that known chat id. If you do not derive one, persist the returned `sandboxSession.id` separately. ```ts title='app/api/chat/session-store.ts' import type { HarnessAgentResumeSessionState } from '@ai-sdk/harness/agent'; import { createVercelNetworkSandboxSession, resumeVercelNetworkSandboxSession, } from '@ai-sdk/sandbox-vercel'; const states: Record = {}; export async function resumeOrCreateSession({ agent, chatId, }: { agent: typeof import('./agent').agent; chatId: string; }) { const resumeFrom = states[chatId]; const sandboxId = `harness-${chatId}`; const sandboxSession = resumeFrom ? await resumeVercelNetworkSandboxSession({ sandboxId }) : await createVercelNetworkSandboxSession({ sandboxId, runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sessionId: chatId, resumeFrom, sandboxSession, }); return { session, sandboxSession }; } export async function detachAndPersist({ chatId, session, }: { chatId: string; session: Awaited>['session']; }) { states[chatId] = await session.detach(); } ``` Use durable storage instead of an in-memory map in production. The creator always creates a new sandbox and fails on a conflicting `sandboxId`; only the resume function reattaches an existing one. ## Route Convert UI messages to model messages, run the harness turn, and convert the result stream back to a UI message stream: ```ts title='app/api/chat/route.ts' import { agent } from './agent'; import { detachAndPersist, resumeOrCreateSession } from './session-store'; import { getHarnessErrorMessage } from '@ai-sdk/harness/agent'; import { convertToModelMessages, createUIMessageStream, createUIMessageStreamResponse, toUIMessageStream, type UIMessage, } from 'ai'; export async function POST(request: Request) { const body: { id?: string; messages: UIMessage[]; } = await request.json(); if (!body.id) { return new Response('Missing chat ID', { status: 400 }); } const chatId = body.id; const messages = await convertToModelMessages(body.messages); return createUIMessageStreamResponse({ stream: createUIMessageStream({ execute: async ({ writer }) => { const { session } = await resumeOrCreateSession({ agent, chatId }); const result = await agent.stream({ session, messages }); writer.merge( toUIMessageStream({ stream: result.stream, onError: getHarnessErrorMessage, onEnd: async () => { await detachAndPersist({ chatId, session }); }, }), ); }, onError: getHarnessErrorMessage, }), }); } ``` Creating the UI message stream before acquiring the session ensures sandbox, bootstrap, and harness startup failures are sent as UI error parts instead of becoming generic HTTP errors. `getHarnessErrorMessage` preserves reviewed, client-safe harness messages and masks unknown server errors. Do not use `createAgentUIStreamResponse` directly with `HarnessAgent` unless you wrap the agent to inject the required session. `HarnessAgent.stream()` requires `session` on every call. ## Detach or Stop Use `session.detach()` when you want to park the harness runtime and keep the sandbox warm for the next request. Bridge-backed adapters can usually reattach or replay efficiently. If the turn is unfinished, `detach()` includes the turn continuation state in the returned resume state. `session.stop()` saves resume state and stops the harness runtime, but does not stop a supplied sandbox. The caller owns that network session and should call its `destroy()` method when the chat ends, not while a later request needs to reattach. Preserve the chosen `sandboxId` across processes. ## Rendering Harness Parts Harness output contains the same UI message part shapes used by AI SDK model streams: - `text` and `reasoning` parts for generated content. - typed tool parts such as `tool-bash`, `tool-read`, or a host tool like `tool-weather`. - `dynamic-tool` parts for dynamic events such as `fileChange` and `compaction`. Render typed harness built-ins the same way you render normal AI SDK tool parts. Check `part.state` for `input-streaming`, `input-available`, and `output-available`. ## Type-Safe Tool Parts Until `HarnessAgent` session options are part of the base `Agent` call parameters, infer UI tools from `agent.tools`: ```ts import type { InferUITools, UIMessage } from 'ai'; import { agent } from './agent'; export type HarnessMessage = UIMessage< unknown, never, InferUITools >; ``` Then use `useChat()` on the client. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Workflow Utilities description: Run HarnessAgent turns as durable Workflow DevKit workflows. url: "https://ai-sdk.dev/docs/ai-sdk-harnesses/workflow-utilities" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `@ai-sdk/workflow-harness` provides helpers for running `HarnessAgent` turns inside a [workflow](https://vercel.com/docs/workflow). The package provides a serializable state machine and runners for time-sliced and semantic agent step turns. You call the appropriate runner from your own `'use workflow'` and `'use step'` functions. The core harness and workflow files are framework-independent. The HTTP handlers shown below use Next.js as one example; adapt them to your runtime and Workflow SDK integration. If you use Next.js, ensure you have [configured your project for Workflow](https://workflow-sdk.dev/docs/getting-started/next) before following the examples. ## Installation ```bash pnpm add @ai-sdk/workflow-harness workflow ``` In addition to the workflow specific packages, install the core harness package, a harness adapter, and a sandbox adapter as shown in [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent). ## Configuring the Harness Agent The agent can be configured in the usual way, for the most part. When using semantic agent steps, set `stopWhen` to `isStepCount(1)` so one call to `stream()` completes one agent step. Omit it when using time slices. ```ts title='harness-workflow/agent.ts' {13-17} import { HarnessAgent } from '@ai-sdk/harness/agent'; import { claudeCode } from '@ai-sdk/harness-claude-code'; import { isStepCount } from 'ai'; export const agent = new HarnessAgent({ harness: claudeCode, instructions: 'You are a helpful coding assistant.', /* * Only needed for semantic agent-step workflows. * Omit this for time-sliced workflows. */ stopWhen: isStepCount(1), }); ``` ## Using Semantic Agent Steps Semantic agent steps persist the harness turn after each agent step. Configure the shared agent with the highlighted `stopWhen` option shown above, then call `runHarnessAgentStep()` from a Workflow step. ### Defining the Agent Step Keep the Workflow step in its own module and import the agent dynamically inside the step body. This keeps the agent, sandbox adapter, and other Node.js dependencies out of the workflow bundle. ```ts title='harness-workflow/agent-step.ts' import { runHarnessAgentStep, type HarnessWorkflowState, } from '@ai-sdk/workflow-harness'; export async function agentStep( state: HarnessWorkflowState, ): Promise { 'use step'; const { agent } = await import('./agent'); const { createVercelNetworkSandboxSession, resumeVercelNetworkSandboxSession, } = await import('@ai-sdk/sandbox-vercel'); const sandboxId = `harness-${state.sessionId}`; const isFirstStep = state.resumeFrom == null && state.continueFrom == null; const sandboxSession = isFirstStep ? await createVercelNetworkSandboxSession({ sandboxId, runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }) : await resumeVercelNetworkSandboxSession({ sandboxId }); return runHarnessAgentStep({ agent, state, sandboxSession, }); } ``` ### Defining the Semantic Agent Step Workflow Create the workflow state and keep scheduling `agentStep()` while the agent has more work. ```ts title='harness-workflow/workflow.ts' import { agentStep } from './agent-step'; import { createHarnessWorkflowState, finalizeHarnessWorkflow, type HarnessWorkflowInput, } from '@ai-sdk/workflow-harness'; export async function agentWorkflow(input: { messages: NonNullable; sessionId: string; }) { 'use workflow'; let state = createHarnessWorkflowState(input); do { state = await agentStep(state); } while (state.status === 'ready_for_next_step'); return finalizeHarnessWorkflow(state); } ``` This example covers the workflow execution foundation only. For multi-turn conversations, you must persist the harness session's `resumeFrom` state between workflow runs. See [Resume Persistence](#resume-persistence). Each `ready_for_next_step` result carries `continueFrom`, which lets the next Workflow step continue the same unfinished turn. When the turn finishes, `finalizeHarnessWorkflow()` returns its result or throws if the workflow failed. ### Starting the Semantic Agent Step Workflow Start the workflow from server-side code. This Next.js route converts the AI SDK UI messages, starts `agentWorkflow()`, and returns the workflow's AI SDK UI message stream. Passing the converted messages lets `HarnessAgent` distinguish a new user turn from tool approval and tool result continuations. ```ts title='app/api/harness-workflow/route.ts' import { agentWorkflow } from '../../../harness-workflow/workflow'; import { convertToModelMessages, createUIMessageStreamResponse, type UIMessage, type UIMessageChunk, } from 'ai'; import { start } from 'workflow/api'; export async function POST(request: Request) { const body: { id?: string; messages: UIMessage[]; } = await request.json(); if (!body.id) { return new Response('Missing chat ID', { status: 400 }); } const messages = await convertToModelMessages(body.messages); const run = await start(agentWorkflow, [ { messages, sessionId: body.id, }, ]); return createUIMessageStreamResponse({ stream: run.readable as ReadableStream, }); } ``` The workflow state `sessionId` identifies the harness session. The step derives `sandboxId` from it before first creating the sandbox and uses that same ID to reattach on later steps. The state does not serialize a live sandbox. Keep `agent.ts`, `agent-step.ts`, `workflow.ts`, and the route in separate modules so the workflow bundle does not include Node-heavy agent, sandbox, or framework dependencies. ## Using Time Slices Time slices persist a long-running harness turn at wall-clock boundaries. Omit the highlighted `stopWhen` option from the shared agent, then call `runHarnessAgentTimeSlice()` from a Workflow step. It uses a 750-second budget by default; pass `timeSliceSeconds` to choose a different budget. ### Defining the Time Slice Step As with semantic agent steps, keep the Workflow step in its own module and ```ts title='harness-workflow/time-slice-step.ts' import { runHarnessAgentTimeSlice, type HarnessWorkflowState, } from '@ai-sdk/workflow-harness'; export async function timeSliceStep( state: HarnessWorkflowState, ): Promise { 'use step'; const { agent } = await import('./agent'); const { createVercelNetworkSandboxSession, resumeVercelNetworkSandboxSession, } = await import('@ai-sdk/sandbox-vercel'); const sandboxId = `harness-${state.sessionId}`; const isFirstStep = state.resumeFrom == null && state.continueFrom == null; const sandboxSession = isFirstStep ? await createVercelNetworkSandboxSession({ sandboxId, runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }) : await resumeVercelNetworkSandboxSession({ sandboxId }); return runHarnessAgentTimeSlice({ agent, state, sandboxSession, }); } ``` ### Defining the Time-Sliced Workflow Create the workflow state and keep scheduling `timeSliceStep()` while the agent has more work. ```ts title='harness-workflow/workflow.ts' import { timeSliceStep } from './time-slice-step'; import { createHarnessWorkflowState, finalizeHarnessWorkflow, type HarnessWorkflowInput, } from '@ai-sdk/workflow-harness'; export async function timeSliceWorkflow(input: { messages: NonNullable; sessionId: string; }) { 'use workflow'; let state = createHarnessWorkflowState(input); do { state = await timeSliceStep(state); } while (state.status === 'ready_for_next_step'); return finalizeHarnessWorkflow(state); } ``` This example covers the workflow execution foundation only. For multi-turn conversations, you must persist the harness session's `resumeFrom` state between workflow runs. See [Resume Persistence](#resume-persistence). Each `ready_for_next_step` result carries `continueFrom`, which lets the next Workflow step continue the same unfinished turn. When the turn finishes, `finalizeHarnessWorkflow()` returns its result or throws if the workflow failed. ### Starting the Time-Sliced Workflow Start the workflow from server-side code. As with semantic agent steps, pass the converted messages so `HarnessAgent` can distinguish a new user turn from tool approval and tool result continuations. ```ts title='app/api/harness-workflow/route.ts' import { timeSliceWorkflow } from '../../../harness-workflow/workflow'; import { convertToModelMessages, createUIMessageStreamResponse, type UIMessage, type UIMessageChunk, } from 'ai'; import { start } from 'workflow/api'; export async function POST(request: Request) { const body: { id?: string; messages: UIMessage[]; } = await request.json(); if (!body.id) { return new Response('Missing chat ID', { status: 400 }); } const messages = await convertToModelMessages(body.messages); const run = await start(timeSliceWorkflow, [ { messages, sessionId: body.id, }, ]); return createUIMessageStreamResponse({ stream: run.readable as ReadableStream, }); } ``` If you use Next.js, wrap your config with `withWorkflow()` so the Workflow SDK transforms `'use workflow'` and `'use step'` modules. Other frameworks have their own Workflow SDK setup. ```js title='next.config.js' import { withWorkflow } from 'workflow/next'; const nextConfig = {}; export default withWorkflow(nextConfig); ``` The workflow state `sessionId` identifies the harness session. The step derives `sandboxId` from it; otherwise, you must persist `sandboxSession.id` separately to reattach across steps. Keep `agent.ts`, `time-slice-step.ts`, `workflow.ts`, and the route in separate modules so the workflow bundle does not include Node-heavy agent, sandbox, or framework dependencies. `destroyOnFinish` destroys the harness session, but never the supplied sandbox. The caller can explicitly call `sandboxSession.destroy()` on the network session when no later step needs to reattach. Never destroy it at a time-slice boundary where a subsequent step must resume it. ## Resume Persistence Workflow automatically persists the `HarnessWorkflowState` returned by each step, including `continueFrom`, for the duration of the current workflow run. To continue the native harness session across separate user-turn workflow runs, persist the opaque `resumeFrom` state by `sessionId`. The storage implementation is the same for semantic agent steps and time slices. This example uses Workflow steps because filesystem access must stay out of the workflow function itself. Use durable storage instead of local files in production. ```ts title='harness-workflow/resume-store.ts' import type { HarnessV1ResumeSessionState } from '@ai-sdk/harness'; import { safeParseJSON } from '@ai-sdk/provider-utils'; const RESUME_DIR = '.harness-sessions'; function fileName(sessionId: string): string { return `${sessionId.replace(/[^a-zA-Z0-9_-]/g, '_')}.json`; } export async function loadResumeStep( sessionId: string, ): Promise { 'use step'; const { readFile } = await import('node:fs/promises'); const { join } = await import('node:path'); let text: string; try { text = await readFile( join(process.cwd(), RESUME_DIR, fileName(sessionId)), 'utf8', ); } catch { return undefined; } const parsed = await safeParseJSON({ text }); return parsed.success ? (parsed.value as unknown as HarnessV1ResumeSessionState) : undefined; } export async function persistResumeStep({ sessionId, resumeState, }: { sessionId: string; resumeState: HarnessV1ResumeSessionState | undefined; }): Promise { 'use step'; if (!resumeState) return; const { mkdir, writeFile } = await import('node:fs/promises'); const { join } = await import('node:path'); const dir = join(process.cwd(), RESUME_DIR); await mkdir(dir, { recursive: true }); await writeFile(join(dir, fileName(sessionId)), JSON.stringify(resumeState)); } ``` Load the previous `resumeFrom` state before creating the workflow state, then persist the updated value after the execution loop. The integration points are the same for both workflow approaches. ### Semantic Agent Step Workflow ```ts title='harness-workflow/workflow.ts' import { agentStep } from './agent-step'; import { loadResumeStep, persistResumeStep } from './resume-store'; import { createHarnessWorkflowState, finalizeHarnessWorkflow, type HarnessWorkflowInput, } from '@ai-sdk/workflow-harness'; export async function agentWorkflow(input: { messages: NonNullable; sessionId: string; }) { 'use workflow'; const resumeFrom = await loadResumeStep(input.sessionId); let state = createHarnessWorkflowState({ ...input, resumeFrom }); do { state = await agentStep(state); } while (state.status === 'ready_for_next_step'); await persistResumeStep({ sessionId: state.sessionId, resumeState: state.resumeFrom, }); return finalizeHarnessWorkflow(state); } ``` ### Time-Sliced Workflow ```ts title='harness-workflow/workflow.ts' import { loadResumeStep, persistResumeStep } from './resume-store'; import { timeSliceStep } from './time-slice-step'; import { createHarnessWorkflowState, finalizeHarnessWorkflow, type HarnessWorkflowInput, } from '@ai-sdk/workflow-harness'; export async function timeSliceWorkflow(input: { messages: NonNullable; sessionId: string; }) { 'use workflow'; const resumeFrom = await loadResumeStep(input.sessionId); let state = createHarnessWorkflowState({ ...input, resumeFrom }); do { state = await timeSliceStep(state); } while (state.status === 'ready_for_next_step'); await persistResumeStep({ sessionId: state.sessionId, resumeState: state.resumeFrom, }); return finalizeHarnessWorkflow(state); } ``` ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) - [UI](/docs/ai-sdk-harnesses/ui) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Handling Authentication description: Learn how to authenticate with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-rsc/authentication" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). The RSC API makes extensive use of [`Server Actions`](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) to power streaming values and UI from the server. Server Actions are exposed as public, unprotected endpoints. As a result, you should treat Server Actions as you would public-facing API endpoints and ensure that the user is authorized to perform the action before returning any data. ```tsx title="app/actions.tsx" 'use server'; import { cookies } from 'next/headers'; import { createStreamableUI } from '@ai-sdk/rsc'; import { validateToken } from '../utils/auth'; export const getWeather = async () => { const token = cookies().get('token'); if (!token || !validateToken(token)) { return { error: 'This action requires authentication', }; } const streamableDisplay = createStreamableUI(null); streamableDisplay.update(); streamableDisplay.done(); return { display: streamableDisplay.value, }; }; ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Error Handling description: Learn how to handle errors with the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-rsc/error-handling" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). Two categories of errors can occur when working with the RSC API: errors while streaming user interfaces and errors while streaming other values. ## Handling UI Errors To handle errors while generating UI, the [`streamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui) object exposes an `error()` method. ```tsx title='app/actions.tsx' 'use server'; import { createStreamableUI } from '@ai-sdk/rsc'; export async function getStreamedUI() { const ui = createStreamableUI(); (async () => { ui.update(
loading
); const data = await fetchData(); ui.done(
{data}
); })().catch(e => { ui.error(
Error: {e.message}
); }); return ui.value; } ``` With this method, you can catch any error with the stream, and return relevant UI. On the client, you can also use a [React Error Boundary](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) to wrap the streamed component and catch any additional errors. ```tsx title='app/page.tsx' 'use client'; import { getStreamedUI } from '@/actions'; import { useState } from 'react'; import { ErrorBoundary } from './ErrorBoundary'; export default function Page() { const [streamedUI, setStreamedUI] = useState(null); return (
{streamedUI}
); } ``` ## Handling Other Errors To handle other errors while streaming, you can return an error object that the receiver can use to determine why the failure occurred. ```tsx title='app/actions.tsx' 'use server'; import { createStreamableValue } from '@ai-sdk/rsc'; import { fetchData, emptyData } from '../utils/data'; export const getStreamedData = async () => { const streamableData = createStreamableValue(emptyData); (async () => { const data1 = await fetchData(); streamableData.update(data1); const data2 = await fetchData(); streamableData.update(data2); const data3 = await fetchData(); streamableData.done(data3); })().catch(e => { streamableData.error(e); }); return { data: streamableData.value }; }; ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Managing Generative UI State description: Overview of the AI and UI states url: "https://ai-sdk.dev/docs/ai-sdk-rsc/generative-ui-state" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). State is an essential part of any application. State is particularly important in AI applications as it is passed to large language models (LLMs) on each request to ensure they have the necessary context to produce a great generation. Traditional chatbots are text-based and have a structure that mirrors that of any chat application. For example, in a chatbot, state is an array of `messages` where each `message` has: - `id`: a unique identifier - `role`: who sent the message (user/assistant/system/tool) - `content`: the content of the message This state can be rendered in the UI and sent to the model without any modifications. With Generative UI, the model can now return a React component, rather than a plain text message. The client can render that component without issue, but that state can't be sent back to the model because React components aren't serialisable. So, what can you do? **The solution is to split the state in two, where one (AI State) becomes a proxy for the other (UI State)**. One way to understand this concept is through a Lego analogy. Imagine a 10,000 piece Lego model that, once built, cannot be easily transported because it is fragile. By taking the model apart, it can be easily transported, and then rebuilt following the steps outlined in the instructions pamphlet. In this way, the instructions pamphlet is a proxy to the physical structure. Similarly, AI State provides a serialisable (JSON) representation of your UI that can be passed back and forth to the model. ## What is AI and UI State? The RSC API simplifies how you manage AI State and UI State, providing a robust way to keep them in sync between your database, server and client. ### AI State AI State refers to the state of your application in a serialisable format that will be used on the server and can be shared with the language model. For a chat app, the AI State is the conversation history (messages) between the user and the assistant. Components generated by the model would be represented in a JSON format as a tool alongside any necessary props. AI State can also be used to store other values and meta information such as `createdAt` for each message and `chatId` for each conversation. The LLM reads this history so it can generate the next message. This state serves as the source of truth for the current application state. **Note**: AI state can be accessed/modified from both the server and the client. ### UI State UI State refers to the state of your application that is rendered on the client. It is a fully client-side state (similar to `useState`) that can store anything from JavaScript values to React elements. UI state is a list of actual UI elements that are rendered on the client. **Note** : UI State can only be accessed client-side. ## Using AI / UI State ### Creating the AI Context AI SDK RSC simplifies managing AI and UI state across your application by providing several hooks. These hooks are powered by [ React context ](https://react.dev/reference/react/hooks#context-hooks) under the hood. Notably, this means you do not have to pass the message history to the server explicitly for each request. You also can access and update your application state in any child component of the context provider. As you begin building [multistep generative interfaces](/docs/ai-sdk-rsc/multistep-interfaces), this will be particularly helpful. To use `@ai-sdk/rsc` to manage AI and UI State in your application, you can create a React context using [`createAI`](/docs/reference/ai-sdk-rsc/create-ai): ```tsx title='app/actions.tsx' // Define the AI state and UI state types export type ServerMessage = { role: 'user' | 'assistant'; content: string; }; export type ClientMessage = { id: string; role: 'user' | 'assistant'; display: ReactNode; }; export const sendMessage = async (input: string): Promise => { "use server" ... } ``` ```tsx title='app/ai.ts' import { createAI } from '@ai-sdk/rsc'; import { ClientMessage, ServerMessage, sendMessage } from './actions'; export type AIState = ServerMessage[]; export type UIState = ClientMessage[]; // Create the AI provider with the initial states and allowed actions export const AI = createAI({ initialAIState: [], initialUIState: [], actions: { sendMessage, }, }); ``` You must pass Server Actions to the `actions` object. In this example, you define types for AI State and UI State, respectively. Next, wrap your application with your newly created context. With that, you can get and set AI and UI State across your entire application. ```tsx title='app/layout.tsx' import { type ReactNode } from 'react'; import { AI } from './ai'; export default function RootLayout({ children, }: Readonly<{ children: ReactNode }>) { return ( {children} ); } ``` ## Reading UI State in Client The UI state can be accessed in Client Components using the [`useUIState`](/docs/reference/ai-sdk-rsc/use-ui-state) hook provided by the RSC API. The hook returns the current UI state and a function to update the UI state like React's `useState`. ```tsx title='app/page.tsx' 'use client'; import { useUIState } from '@ai-sdk/rsc'; export default function Page() { const [messages, setMessages] = useUIState(); return (
    {messages.map(message => (
  • {message.display}
  • ))}
); } ``` ## Reading AI State in Client The AI state can be accessed in Client Components using the [`useAIState`](/docs/reference/ai-sdk-rsc/use-ai-state) hook provided by the RSC API. The hook returns the current AI state. ```tsx title='app/page.tsx' 'use client'; import { useAIState } from '@ai-sdk/rsc'; export default function Page() { const [messages, setMessages] = useAIState(); return (
    {messages.map(message => (
  • {message.content}
  • ))}
); } ``` ## Reading AI State on Server The AI State can be accessed within any Server Action provided to the `createAI` context using the [`getAIState`](/docs/reference/ai-sdk-rsc/get-ai-state) function. It returns the current AI state as a read-only value: ```tsx title='app/actions.ts' import { getAIState } from '@ai-sdk/rsc'; export async function sendMessage(message: string) { 'use server'; const history = getAIState(); const response = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: [...history, { role: 'user', content: message }], }); return response; } ``` Remember, you can only access state within actions that have been passed to the `createAI` context within the `actions` key. ## Updating AI State on Server The AI State can also be updated from within your Server Action with the [`getMutableAIState`](/docs/reference/ai-sdk-rsc/get-mutable-ai-state) function. This function is similar to `getAIState`, but it returns the state with methods to read and update it: ```tsx title='app/actions.ts' import { getMutableAIState } from '@ai-sdk/rsc'; export async function sendMessage(message: string) { 'use server'; const history = getMutableAIState(); // Update the AI state with the new user message. history.update([...history.get(), { role: 'user', content: message }]); const response = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: history.get(), }); // Update the AI state again with the response from the model. history.done([...history.get(), { role: 'assistant', content: response }]); return response; } ``` It is important to update the AI State with new responses using `.update()` and `.done()` to keep the conversation history in sync. ## Calling Server Actions from the Client To call the `sendMessage` action from the client, you can use the [`useActions`](/docs/reference/ai-sdk-rsc/use-actions) hook. The hook returns all the available Actions that were provided to `createAI`: ```tsx title='app/page.tsx' 'use client'; import { useActions, useUIState } from '@ai-sdk/rsc'; import { AI } from './ai'; export default function Page() { const { sendMessage } = useActions(); const [messages, setMessages] = useUIState(); const handleSubmit = async event => { event.preventDefault(); setMessages(currentMessages => [ ...currentMessages, { id: Date.now().toString(), role: 'user', display: event.target.message.value, }, ]); const response = await sendMessage(event.target.message.value); setMessages(currentMessages => [...currentMessages, response]); }; return ( <>
    {messages.map(message => (
  • {message.display}
  • ))}
); } ``` When the user submits a message, the `sendMessage` action is called with the message content. The response from the action is then added to the UI state, updating the displayed messages. Important! Don't forget to update the UI State after you call your Server Action otherwise the streamed component will not show in the UI. To learn more, check out this [example](/examples/next-app/state-management/ai-ui-states) on managing AI and UI state using `@ai-sdk/rsc`. *** Next, you will learn how you can save and restore state with `@ai-sdk/rsc`. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Handling Loading State description: Overview of handling loading state with AI SDK RSC url: "https://ai-sdk.dev/docs/ai-sdk-rsc/loading-state" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). Given that responses from language models can often take a while to complete, it's crucial to be able to show loading state to users. This provides visual feedback that the system is working on their request and helps maintain a positive user experience. There are three approaches you can take to handle loading state with the AI SDK RSC: - Managing loading state similar to how you would in a traditional Next.js application. This involves setting a loading state variable in the client and updating it when the response is received. - Streaming loading state from the server to the client. This approach allows you to track loading state on a more granular level and provide more detailed feedback to the user. - Streaming loading component from the server to the client. This approach allows you to stream a React Server Component to the client while awaiting the model's response. ## Handling Loading State on the Client ### Client Let's create a simple Next.js page that will call the `generateResponse` function when the form is submitted. The function will take in the user's prompt (`input`) and then generate a response (`response`). To handle the loading state, use the `loading` state variable. When the form is submitted, set `loading` to `true`, and when the response is received, set it back to `false`. While the response is being streamed, the input field will be disabled. ```tsx title='app/page.tsx' 'use client'; import { useState } from 'react'; import { generateResponse } from './actions'; import { readStreamableValue } from '@ai-sdk/rsc'; // Force the page to be dynamic and allow streaming responses up to 30 seconds export const maxDuration = 30; export default function Home() { const [input, setInput] = useState(''); const [generation, setGeneration] = useState(''); const [loading, setLoading] = useState(false); return (
{generation}
{ e.preventDefault(); setLoading(true); const response = await generateResponse(input); let textContent = ''; for await (const delta of readStreamableValue(response)) { textContent = `${textContent}${delta}`; setGeneration(textContent); } setInput(''); setLoading(false); }} > { setInput(event.target.value); }} />
); } ``` ### Server Now let's implement the `generateResponse` function. Use the `streamText` function to generate a response to the input. ```typescript title='app/actions.ts' 'use server'; import { streamText } from 'ai'; import { createStreamableValue } from '@ai-sdk/rsc'; export async function generateResponse(prompt: string) { const stream = createStreamableValue(); (async () => { const { textStream } = streamText({ model: "anthropic/claude-sonnet-5.5", prompt, }); for await (const text of textStream) { stream.update(text); } stream.done(); })(); return stream.value; } ``` ## Streaming Loading State from the Server If you are looking to track loading state on a more granular level, you can create a new streamable value to store a custom variable and then read this on the frontend. Let's update the example to create a new streamable value for tracking loading state: ### Server ```typescript title='app/actions.ts' {9,22,25} 'use server'; import { streamText } from 'ai'; import { createStreamableValue } from '@ai-sdk/rsc'; export async function generateResponse(prompt: string) { const stream = createStreamableValue(); const loadingState = createStreamableValue({ loading: true }); (async () => { const { textStream } = streamText({ model: "anthropic/claude-sonnet-5.5", prompt, }); for await (const text of textStream) { stream.update(text); } stream.done(); loadingState.done({ loading: false }); })(); return { response: stream.value, loadingState: loadingState.value }; } ``` ### Client ```tsx title='app/page.tsx' {22,34-42} 'use client'; import { useState } from 'react'; import { generateResponse } from './actions'; import { readStreamableValue } from '@ai-sdk/rsc'; // Force the page to be dynamic and allow streaming responses up to 30 seconds export const maxDuration = 30; export default function Home() { const [input, setInput] = useState(''); const [generation, setGeneration] = useState(''); const [loading, setLoading] = useState(false); return (
{generation}
{ e.preventDefault(); setLoading(true); const { response, loadingState } = await generateResponse(input); let textContent = ''; // read both streamable values concurrently await Promise.all([ (async () => { for await (const responseDelta of readStreamableValue(response)) { textContent = `${textContent}${responseDelta}`; setGeneration(textContent); } })(), (async () => { for await (const loadingDelta of readStreamableValue( loadingState, )) { if (loadingDelta) { setLoading(loadingDelta.loading); } } })(), ]); setInput(''); setLoading(false); }} > { setInput(event.target.value); }} />
); } ``` This allows you to provide more detailed feedback about the generation process to your users. ## Streaming Loading Components with `streamUI` If you are using the [ `streamUI` ](/docs/reference/ai-sdk-rsc/stream-ui) function, you can stream the loading state to the client in the form of a React component. `streamUI` supports the usage of [ JavaScript generator functions ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*), which allow you to yield some value (in this case a React component) while some other blocking work completes. ## Server ```ts 'use server'; import { openai } from '@ai-sdk/openai'; import { streamUI } from '@ai-sdk/rsc'; export async function generateResponse(prompt: string) { const result = await streamUI({ model: openai('gpt-6-astra'), prompt, text: async function* ({ content }) { yield
loading...
; return
{content}
; }, }); return result.value; } ``` Remember to update the file from `.ts` to `.tsx` because you are defining a React component in the `streamUI` function. ## Client ```tsx 'use client'; import { useState } from 'react'; import { generateResponse } from './actions'; import { readStreamableValue } from '@ai-sdk/rsc'; // Force the page to be dynamic and allow streaming responses up to 30 seconds export const maxDuration = 30; export default function Home() { const [input, setInput] = useState(''); const [generation, setGeneration] = useState(); return (
{generation}
{ e.preventDefault(); const result = await generateResponse(input); setGeneration(result); setInput(''); }} > { setInput(event.target.value); }} />
); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrating from RSC to UI description: Learn how to migrate from AI SDK RSC to AI SDK UI. url: "https://ai-sdk.dev/docs/ai-sdk-rsc/migrating-to-ui" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This guide helps you migrate from AI SDK RSC to AI SDK UI. ## Background The AI SDK has two packages that help you build the frontend for your applications – [AI SDK UI](/docs/ai-sdk-ui) and [AI SDK RSC](/docs/ai-sdk-rsc). We introduced support for using [React Server Components](https://react.dev/reference/rsc/server-components) (RSC) within the AI SDK to simplify building generative user interfaces for frameworks that support RSC. However, given we're pushing the boundaries of this technology, AI SDK RSC currently faces significant limitations that make it unsuitable for stable production use. - It is not possible to abort a stream using server actions. This will be improved in future releases of React and Next.js [(1122)](https://github.com/vercel/ai/issues/1122). - When using `createStreamableUI` and `streamUI`, components remount on `.done()`, causing them to flicker [(2939)](https://github.com/vercel/ai/issues/2939). - Many suspense boundaries can lead to crashes [(2843)](https://github.com/vercel/ai/issues/2843). - Using `createStreamableUI` can lead to quadratic data transfer. You can avoid this using createStreamableValue instead, and rendering the component client-side. - Closed RSC streams cause update issues [(3007)](https://github.com/vercel/ai/issues/3007). Due to these limitations, AI SDK RSC is marked as experimental, and we do not recommend using it for stable production environments. As a result, we strongly recommend migrating to AI SDK UI, which has undergone extensive development to provide a more stable and production grade experience. In building [v0](https://v0.dev), we have invested considerable time exploring how to create the best chat experience on the web. AI SDK UI ships with many of these best practices and commonly used patterns like [language model middleware](/docs/ai-sdk-core/middleware), [multi-step tool calls](/docs/ai-sdk-core/tools-and-tool-calling#multi-step-calls-using-stopwhen), [attachments](/docs/ai-sdk-ui/chatbot#attachments), [telemetry](/docs/ai-sdk-core/telemetry), [provider registry](/docs/ai-sdk-core/provider-management#provider-registry), and many more. These features have been considerately designed into a neat abstraction that you can use to reliably integrate AI into your applications. ## Streaming Chat Completions ### Basic Setup The `streamUI` function executes as part of a server action as illustrated below. #### Before: Handle generation and rendering in a single server action ```tsx title="@/app/actions.tsx" import { openai } from '@ai-sdk/openai'; import { getMutableAIState, streamUI } from '@ai-sdk/rsc'; export async function sendMessage(message: string) { 'use server'; const messages = getMutableAIState('messages'); messages.update([...messages.get(), { role: 'user', content: message }]); const { value: stream } = await streamUI({ model: openai('gpt-6-astra'), instructions: 'you are a friendly assistant!', messages: messages.get(), text: async function* ({ content, done }) { // process text }, tools: { // tool definitions }, }); return stream; } ``` #### Before: Call server action and update UI state The chat interface calls the server action. The response is then saved using the `useUIState` hook. ```tsx title="@/app/page.tsx" 'use client'; import { useState, ReactNode } from 'react'; import { useActions, useUIState } from '@ai-sdk/rsc'; export default function Page() { const { sendMessage } = useActions(); const [input, setInput] = useState(''); const [messages, setMessages] = useUIState(); return (
{messages.map(message => message)}
{ event.preventDefault(); const response: ReactNode = await sendMessage(input); setMessages(msgs => [...msgs, response]); }} >
); } ``` The `streamUI` function combines generating text and rendering the user interface. To migrate to AI SDK UI, you need to **separate these concerns** – streaming generations with `streamText` and rendering the UI with `useChat`. #### After: Replace server action with route handler The `streamText` function executes as part of a route handler and streams the response to the client. The `useChat` hook on the client decodes this stream and renders the response within the chat interface. ```ts title="@/app/api/chat/route.ts" import { createUIMessageStreamResponse, streamText, toUIMessageStream, } from 'ai'; import { openai } from '@ai-sdk/openai'; export async function POST(request) { const { messages } = await request.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", instructions: 'you are a friendly assistant!', messages, tools: { // tool definitions }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` #### After: Update client to use chat hook ```tsx title="@/app/page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; export default function Page() { const { messages, input, setInput, handleSubmit } = useChat(); return (
{messages.map(message => (
{message.role}
{message.content}
))}
{ setInput(event.target.value); }} />
); } ``` ### Parallel Tool Calls In AI SDK RSC, `streamUI` does not support parallel tool calls. You will have to use a combination of `streamText`, `createStreamableUI` and `createStreamableValue`. With AI SDK UI, `useChat` comes with built-in support for parallel tool calls. You can define multiple tools in the `streamText` and have them called in parallel. The `useChat` hook will then handle the parallel tool calls for you automatically. ### Multi-Step Tool Calls In AI SDK RSC, `streamUI` does not support multi-step tool calls. You will have to use a combination of `streamText`, `createStreamableUI` and `createStreamableValue`. With AI SDK UI, `useChat` comes with built-in support for multi-step tool calls. You can set `stopWhen` in the `streamText` function to define when the model should stop making tool calls. The `useChat` hook will then handle the multi-step tool calls for you automatically. ### Generative User Interfaces The `streamUI` function uses `tools` as a way to execute functions based on user input and renders React components based on the function output to go beyond text in the chat interface. #### Before: Render components within the server action and stream to client ```tsx title="@/app/actions.tsx" import { z } from 'zod'; import { streamUI } from '@ai-sdk/rsc'; import { openai } from '@ai-sdk/openai'; import { getWeather } from '@/utils/queries'; import { Weather } from '@/components/weather'; const { value: stream } = await streamUI({ model: openai('gpt-6-astra'), instructions: 'you are a friendly assistant!', messages, text: async function* ({ content, done }) { // process text }, tools: { displayWeather: { description: 'Display the weather for a location', inputSchema: z.object({ latitude: z.number(), longitude: z.number(), }), generate: async function* ({ latitude, longitude }) { yield
Loading weather...
; const { value, unit } = await getWeather({ latitude, longitude }); return ; }, }, }, }); ``` As mentioned earlier, `streamUI` generates text and renders the React component in a single server action call. #### After: Replace with route handler and stream props data to client The `streamText` function streams the props data as response to the client, while `useChat` decode the stream as `toolInvocations` and renders the chat interface. ```ts title="@/app/api/chat/route.ts" import { z } from 'zod'; import { openai } from '@ai-sdk/openai'; import { getWeather } from '@/utils/queries'; import { createUIMessageStreamResponse, streamText, toUIMessageStream, } from 'ai'; export async function POST(request) { const { messages } = await request.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", instructions: 'you are a friendly assistant!', messages, tools: { displayWeather: { description: 'Display the weather for a location', inputSchema: z.object({ latitude: z.number(), longitude: z.number(), }), execute: async function ({ latitude, longitude }) { const props = await getWeather({ latitude, longitude }); return props; }, }, }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` #### After: Update client to use chat hook and render components using tool invocations ```tsx title="@/app/page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import { Weather } from '@/components/weather'; export default function Page() { const { messages, input, setInput, handleSubmit } = useChat(); return (
{messages.map(message => (
{message.role}
{message.content}
{message.toolInvocations.map(toolInvocation => { const { toolName, toolCallId, state } = toolInvocation; if (state === 'result') { const { result } = toolInvocation; return (
{toolName === 'displayWeather' ? ( ) : null}
); } else { return (
{toolName === 'displayWeather' ? (
Loading weather...
) : null}
); } })}
))}
{ setInput(event.target.value); }} />
); } ``` ### Handling Client Interactions With AI SDK RSC, components streamed to the client can trigger subsequent generations by calling the relevant server action using the `useActions` hooks. This is possible as long as the component is a descendant of the `` context provider. #### Before: Use actions hook to send messages ```tsx title="@/app/components/list-flights.tsx" 'use client'; import { useActions, useUIState } from '@ai-sdk/rsc'; export function ListFlights({ flights }) { const { sendMessage } = useActions(); const [_, setMessages] = useUIState(); return (
{flights.map(flight => (
{ const response = await sendMessage( `I would like to choose flight ${flight.id}!`, ); setMessages(msgs => [...msgs, response]); }} > {flight.name}
))}
); } ``` #### After: Use another chat hook with same ID from the component After switching to AI SDK UI, these messages are synced by initializing the `useChat` hook in the component with the same `id` as the parent component. ```tsx title="@/app/components/list-flights.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; export function ListFlights({ chatId, flights }) { const { append } = useChat({ id: chatId, body: { id: chatId }, }); return (
{flights.map(flight => (
{ await append({ role: 'user', content: `I would like to choose flight ${flight.id}!`, }); }} > {flight.name}
))}
); } ``` ### Loading Indicators In AI SDK RSC, you can use the `initial` parameter of `streamUI` to define the component to display while the generation is in progress. #### Before: Use `loading` to show loading indicator ```tsx title="@/app/actions.tsx" import { openai } from '@ai-sdk/openai'; import { streamUI } from '@ai-sdk/rsc'; const { value: stream } = await streamUI({ model: openai('gpt-6-astra'), instructions: 'you are a friendly assistant!', messages, initial:
Loading...
, text: async function* ({ content, done }) { // process text }, tools: { // tool definitions }, }); return stream; ``` With AI SDK UI, you can use the tool invocation state to show a loading indicator while the tool is executing. #### After: Use tool invocation state to show loading indicator ```tsx title="@/app/components/message.tsx" 'use client'; export function Message({ role, content, toolInvocations }) { return (
{role}
{content}
{toolInvocations && (
{toolInvocations.map(toolInvocation => { const { toolName, toolCallId, state } = toolInvocation; if (state === 'result') { const { result } = toolInvocation; return (
{toolName === 'getWeather' ? ( ) : null}
); } else { return (
{toolName === 'getWeather' ? ( ) : (
Loading...
)}
); } })}
)}
); } ``` ### Saving Chats Before implementing `streamUI` as a server action, you should create an `` provider and wrap your application at the root layout to sync the AI and UI states. During initialization, you typically use the `onSetAIState` callback function to track updates to the AI state and save it to the database when `done(...)` is called. #### Before: Save chats using callback function of context provider ```ts title="@/app/actions.ts" import { createAI } from '@ai-sdk/rsc'; import { saveChat } from '@/utils/queries'; export const AI = createAI({ initialAIState: {}, initialUIState: {}, actions: { // server actions }, onSetAIState: async ({ state, done }) => { 'use server'; if (done) { await saveChat(state); } }, }); ``` #### After: Save chats using callback function of `streamText` With AI SDK UI, you will save chats using the `onEnd` callback function of `streamText` in your route handler. ```ts title="@/app/api/chat/route.ts" import { openai } from '@ai-sdk/openai'; import { saveChat } from '@/utils/queries'; import { createUIMessageStreamResponse, streamText, toUIMessageStream, convertToModelMessages, } from 'ai'; export async function POST(request) { const { id, messages } = await request.json(); const coreMessages = await convertToModelMessages(messages); const result = streamText({ model: "anthropic/claude-sonnet-5.5", instructions: 'you are a friendly assistant!', messages: coreMessages, onEnd: async ({ responseMessages }) => { try { await saveChat({ id, messages: [...coreMessages, ...responseMessages], }); } catch (error) { console.error('Failed to save chat'); } }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ### Restoring Chats When using AI SDK RSC, the `useUIState` hook contains the UI state of the chat. When restoring a previously saved chat, the UI state needs to be loaded with messages. Similar to how you typically save chats in AI SDK RSC, you should use the `onGetUIState` callback function to retrieve the chat from the database, convert it into UI state, and return it to be accessible through `useUIState`. #### Before: Load chat from database using callback function of context provider ```ts title="@/app/actions.ts" import { createAI } from '@ai-sdk/rsc'; import { loadChatFromDB, convertToUIState } from '@/utils/queries'; export const AI = createAI({ actions: { // server actions }, onGetUIState: async () => { 'use server'; const chat = await loadChatFromDB(); const uiState = convertToUIState(chat); return uiState; }, }); ``` AI SDK UI uses the `messages` field of `useChat` to store messages. To load messages when `useChat` is mounted, you should use `initialMessages`. As messages are typically loaded from the database, we can use a server actions inside a Page component to fetch an older chat from the database during static generation and pass the messages as props to the `` component. #### After: Load chat from database during static generation of page ```tsx title="@/app/chat/[id]/page.tsx" import { Chat } from '@/app/components/chat'; import { getChatById } from '@/utils/queries'; // link to example implementation: https://github.com/vercel/ai-chatbot/blob/00b125378c998d19ef60b73fe576df0fe5a0e9d4/lib/utils.ts#L87-L127 import { convertToUIMessages } from '@/utils/functions'; export default async function Page({ params }: { params: any }) { const { id } = params; const chatFromDb = await getChatById({ id }); const chat: Chat = { ...chatFromDb, messages: convertToUIMessages(chatFromDb.messages), }; return ; } ``` #### After: Pass chat messages as props and load into chat hook ```tsx title="@/app/components/chat.tsx" 'use client'; import { Message } from 'ai'; import { useChat } from '@ai-sdk/react'; export function Chat({ id, initialMessages, }: { id; initialMessages: Array; }) { const { messages } = useChat({ id, initialMessages, }); return (
{messages.map(message => (
{message.role}
{message.content}
))}
); } ``` ## Streaming Object Generation The `createStreamableValue` function streams any serializable data from the server to the client. As a result, this function allows you to stream object generations from the server to the client when paired with `streamText` and `Output`. #### Before: Use streamable value to stream object generations ```ts title="@/app/actions.ts" import { Output, streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; import { createStreamableValue } from '@ai-sdk/rsc'; import { notificationsSchema } from '@/utils/schemas'; export async function generateSampleNotifications() { 'use server'; const stream = createStreamableValue(); (async () => { const { partialOutputStream } = streamText({ model: "anthropic/claude-sonnet-5.5", instructions: 'generate sample ios messages for testing', prompt: 'messages from a family group chat during diwali, max 4', output: Output.object({ schema: notificationsSchema }), }); for await (const partialObject of partialOutputStream) { stream.update(partialObject); } stream.done(); })(); return { partialNotificationsStream: stream.value }; } ``` #### Before: Read streamable value and update object ```tsx title="@/app/page.tsx" 'use client'; import { useState } from 'react'; import { readStreamableValue } from '@ai-sdk/rsc'; import { generateSampleNotifications } from '@/app/actions'; export default function Page() { const [notifications, setNotifications] = useState(null); return (
); } ``` To migrate to AI SDK UI, you should use the `useObject` hook and implement `streamText` with `Output` within your route handler. #### After: Replace with route handler and stream text response ```ts title="@/app/api/object/route.ts" import { Output, createTextStreamResponse, streamText, toTextStream } from 'ai'; import { openai } from '@ai-sdk/openai'; import { notificationSchema } from '@/utils/schemas'; export async function POST(req: Request) { const context = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: notificationSchema }), prompt: `Generate 3 notifications for a messages app in this context:` + context, }); return createTextStreamResponse({ stream: toTextStream({ stream: result.stream }), }); } ``` #### After: Use object hook to decode stream and update object ```tsx title="@/app/page.tsx" 'use client'; import { useObject } from '@ai-sdk/react'; import { notificationSchema } from '@/utils/schemas'; export default function Page() { const { object, submit } = useObject({ api: '/api/object', schema: notificationSchema, }); return (
{object?.notifications?.map((notification, index) => (

{notification?.name}

{notification?.message}

))}
); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Multistep Interfaces description: Overview of Building Multistep Interfaces with AI SDK RSC url: "https://ai-sdk.dev/docs/ai-sdk-rsc/multistep-interfaces" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). Multistep interfaces refer to user interfaces that require multiple independent steps to be executed in order to complete a specific task. For example, if you wanted to build a Generative UI chatbot capable of booking flights, it could have three steps: - Search all flights - Pick flight - Check availability To build this kind of application you will leverage two concepts, **tool composition** and **application context**. **Tool composition** is the process of combining multiple [tools](/docs/ai-sdk-core/tools-and-tool-calling) to create a new tool. This is a powerful concept that allows you to break down complex tasks into smaller, more manageable steps. In the example above, *"search all flights"*, *"pick flight"*, and *"check availability"* come together to create a holistic *"book flight"* tool. **Application context** refers to the state of the application at any given point in time. This includes the user's input, the output of the language model, and any other relevant information. In the example above, the flight selected in *"pick flight"* would be used as context necessary to complete the *"check availability"* task. ## Overview In order to build a multistep interface with `@ai-sdk/rsc`, you will need a few things: - A Server Action that calls and returns the result from the `streamUI` function - Tool(s) (sub-tasks necessary to complete your overall task) - React component(s) that should be rendered when the tool is called - A page to render your chatbot The general flow that you will follow is: - User sends a message (calls your Server Action with `useActions`, passing the message as an input) - Message is appended to the AI State and then passed to the model alongside a number of tools - Model can decide to call a tool, which will render the `` component - Within that component, you can add interactivity by using `useActions` to call the model with your Server Action and `useUIState` to append the model's response (``) to the UI State - And so on... ## Implementation The turn-by-turn implementation is the simplest form of multistep interfaces. In this implementation, the user and the model take turns during the conversation. For every user input, the model generates a response, and the conversation continues in this turn-by-turn fashion. In the following example, you specify two tools (`searchFlights` and `lookupFlight`) that the model can use to search for flights and lookup details for a specific flight. ```tsx title="app/actions.tsx" import { streamUI } from '@ai-sdk/rsc'; import { openai } from '@ai-sdk/openai'; import { z } from 'zod'; const searchFlights = async ( source: string, destination: string, date: string, ) => { return [ { id: '1', flightNumber: 'AA123', }, { id: '2', flightNumber: 'AA456', }, ]; }; const lookupFlight = async (flightNumber: string) => { return { flightNumber: flightNumber, departureTime: '10:00 AM', arrivalTime: '12:00 PM', }; }; export async function submitUserMessage(input: string) { 'use server'; const ui = await streamUI({ model: openai('gpt-6-astra'), instructions: 'you are a flight booking assistant', prompt: input, text: async ({ content }) =>
{content}
, tools: { searchFlights: { description: 'search for flights', inputSchema: z.object({ source: z.string().describe('The origin of the flight'), destination: z.string().describe('The destination of the flight'), date: z.string().describe('The date of the flight'), }), generate: async function* ({ source, destination, date }) { yield `Searching for flights from ${source} to ${destination} on ${date}...`; const results = await searchFlights(source, destination, date); return (
{results.map(result => (
{result.flightNumber}
))}
); }, }, lookupFlight: { description: 'lookup details for a flight', inputSchema: z.object({ flightNumber: z.string().describe('The flight number'), }), generate: async function* ({ flightNumber }) { yield `Looking up details for flight ${flightNumber}...`; const details = await lookupFlight(flightNumber); return (
Flight Number: {details.flightNumber}
Departure Time: {details.departureTime}
Arrival Time: {details.arrivalTime}
); }, }, }, }); return ui.value; } ``` Next, create an AI context that will hold the UI State and AI State. ```ts title='app/ai.ts' import { createAI } from '@ai-sdk/rsc'; import { submitUserMessage } from './actions'; export const AI = createAI({ initialUIState: [], initialAIState: [], actions: { submitUserMessage, }, }); ``` Next, wrap your application with your newly created context. ```tsx title='app/layout.tsx' import { type ReactNode } from 'react'; import { AI } from './ai'; export default function RootLayout({ children, }: Readonly<{ children: ReactNode }>) { return ( {children} ); } ``` To call your Server Action, update your root page with the following: ```tsx title="app/page.tsx" 'use client'; import { useState } from 'react'; import { AI } from './ai'; import { useActions, useUIState } from '@ai-sdk/rsc'; export default function Page() { const [input, setInput] = useState(''); const [conversation, setConversation] = useUIState(); const { submitUserMessage } = useActions(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setInput(''); setConversation(currentConversation => [ ...currentConversation,
{input}
, ]); const message = await submitUserMessage(input); setConversation(currentConversation => [...currentConversation, message]); }; return (
{conversation.map((message, i) => (
{message}
))}
setInput(e.target.value)} />
); } ``` This page pulls in the current UI State using the `useUIState` hook, which is then mapped over and rendered in the UI. To access the Server Action, you use the `useActions` hook which will return all actions that were passed to the `actions` key of the `createAI` function in your `actions.tsx` file. Finally, you call the `submitUserMessage` function like any other TypeScript function. This function returns a React component (`message`) that is then rendered in the UI by updating the UI State with `setConversation`. In this example, to call the next tool, the user must respond with plain text. **Given you are streaming a React component, you can add a button to trigger the next step in the conversation**. To add user interaction, you will have to convert the component into a client component and use the `useActions` hook to trigger the next step in the conversation. ```tsx title="components/flights.tsx" 'use client'; import { useActions, useUIState } from '@ai-sdk/rsc'; import { ReactNode } from 'react'; interface FlightsProps { flights: { id: string; flightNumber: string }[]; } export const Flights = ({ flights }: FlightsProps) => { const { submitUserMessage } = useActions(); const [_, setMessages] = useUIState(); return (
{flights.map(result => (
{ const display = await submitUserMessage( `lookupFlight ${result.flightNumber}`, ); setMessages((messages: ReactNode[]) => [...messages, display]); }} > {result.flightNumber}
))}
); }; ``` Now, update your `searchFlights` tool to render the new `` component. ```tsx title="actions.tsx" ... searchFlights: { description: 'search for flights', inputSchema: z.object({ source: z.string().describe('The origin of the flight'), destination: z.string().describe('The destination of the flight'), date: z.string().describe('The date of the flight'), }), generate: async function* ({ source, destination, date }) { yield `Searching for flights from ${source} to ${destination} on ${date}...`; const results = await searchFlights(source, destination, date); return (); }, } ... ``` In the above example, the `Flights` component is used to display the search results. When the user clicks on a flight number, the `lookupFlight` tool is called with the flight number as a parameter. The `submitUserMessage` action is then called to trigger the next step in the conversation. Learn more about tool calling in Next.js App Router by checking out examples [here](/examples/next-app/tools). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Overview description: An overview of AI SDK RSC. url: "https://ai-sdk.dev/docs/ai-sdk-rsc/overview" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). The `@ai-sdk/rsc` package is compatible with frameworks that support React Server Components. [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) (RSC) allow you to write UI that can be rendered on the server and streamed to the client. RSCs enable [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#with-client-components), a new way to call server-side code directly from the client just like any other function with end-to-end type-safety. This combination opens the door to a new way of building AI applications, allowing the large language model (LLM) to generate and stream UI directly from the server to the client. ## AI SDK RSC Functions AI SDK RSC has various functions designed to help you build AI-native applications with React Server Components. These functions: 1. Provide abstractions for building Generative UI applications. - [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui): calls a model and allows it to respond with React Server Components. - [`useUIState`](/docs/reference/ai-sdk-rsc/use-ui-state): returns the current UI state and a function to update the UI State (like React's `useState`). UI State is the visual representation of the AI state. - [`useAIState`](/docs/reference/ai-sdk-rsc/use-ai-state): returns the current AI state and a function to update the AI State (like React's `useState`). The AI state is intended to contain context and information shared with the AI model, such as system messages, function responses, and other relevant data. - [`useActions`](/docs/reference/ai-sdk-rsc/use-actions): provides access to your Server Actions from the client. This is particularly useful for building interfaces that require user interactions with the server. - [`createAI`](/docs/reference/ai-sdk-rsc/create-ai): creates a client-server context provider that can be used to wrap parts of your application tree to easily manage both UI and AI states of your application. 2. Make it simple to work with streamable values between the server and client. - [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value): creates a stream that sends values from the server to the client. The value can be any serializable data. - [`readStreamableValue`](/docs/reference/ai-sdk-rsc/read-streamable-value): reads a streamable value from the client that was originally created using `createStreamableValue`. - [`createStreamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui): creates a stream that sends UI from the server to the client. - [`useStreamableValue`](/docs/reference/ai-sdk-rsc/use-streamable-value): accepts a streamable value created using `createStreamableValue` and returns the current value, error, and pending state. ## Templates Check out the following templates to see AI SDK RSC in action. ## API Reference Please check out the [AI SDK RSC API Reference](/docs/reference/ai-sdk-rsc) for more details on each function. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Saving and Restoring States description: Saving and restoring AI and UI states with onGetUIState and onSetAIState url: "https://ai-sdk.dev/docs/ai-sdk-rsc/saving-and-restoring-states" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). AI SDK RSC provides convenient methods for saving and restoring AI and UI state. This is useful for saving the state of your application after every model generation, and restoring it when the user revisits the generations. ## AI State ### Saving AI state The AI state can be saved using the [`onSetAIState`](/docs/reference/ai-sdk-rsc/create-ai#on-set-ai-state) callback, which gets called whenever the AI state is updated. In the following example, you save the chat history to a database whenever the generation is marked as done. ```tsx title='app/ai.ts' export const AI = createAI({ actions: { continueConversation, }, onSetAIState: async ({ state, done }) => { 'use server'; if (done) { saveChatToDB(state); } }, }); ``` ### Restoring AI state The AI state can be restored using the [`initialAIState`](/docs/reference/ai-sdk-rsc/create-ai#initial-ai-state) prop passed to the context provider created by the [`createAI`](/docs/reference/ai-sdk-rsc/create-ai) function. In the following example, you restore the chat history from a database when the component is mounted. ```tsx title='app/layout.tsx' import { ReactNode } from 'react'; import { AI } from './ai'; export default async function RootLayout({ children, }: Readonly<{ children: ReactNode }>) { const chat = await loadChatFromDB(); return ( {children} ); } ``` ## UI State ### Saving UI state The UI state cannot be saved directly, since the contents aren't yet serializable. Instead, you can use the AI state as proxy to store details about the UI state and use it to restore the UI state when needed. ### Restoring UI state The UI state can be restored using the AI state as a proxy. In the following example, you restore the chat history from the AI state when the component is mounted. You use the [`onGetUIState`](/docs/reference/ai-sdk-rsc/create-ai#on-get-ui-state) callback to listen for SSR events and restore the UI state. ```tsx title='app/ai.ts' export const AI = createAI({ actions: { continueConversation, }, onGetUIState: async () => { 'use server'; const historyFromDB: ServerMessage[] = await loadChatFromDB(); const historyFromApp: ServerMessage[] = getAIState(); // If the history from the database is different from the // history in the app, they're not in sync so return the UIState // based on the history from the database if (historyFromDB.length !== historyFromApp.length) { return historyFromDB.map(({ role, content }) => ({ id: generateId(), role, display: role === 'function' ? ( ) : ( content ), })); } }, }); ``` To learn more, check out this [example](/examples/next-app/state-management/save-and-restore-states) that persists and restores states in your Next.js application. *** Next, you will learn how you can use `@ai-sdk/rsc` functions like `useActions` and `useUIState` to create interactive, multistep interfaces. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Streaming React Components description: Overview of streaming RSCs url: "https://ai-sdk.dev/docs/ai-sdk-rsc/streaming-react-components" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). The RSC API allows you to stream React components from the server to the client with the [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) function. This is useful when you want to go beyond raw text and stream components to the client in real-time. Similar to [ AI SDK Core ](/docs/ai-sdk-core/overview) APIs (like [ `streamText` ](/docs/reference/ai-sdk-core/stream-text)), `streamUI` provides a single function to call a model and allow it to respond with React Server Components. It supports the same model interfaces as AI SDK Core APIs. ### Concepts To give the model the ability to respond to a user's prompt with a React component, you can leverage [tools](/docs/ai-sdk-core/tools-and-tool-calling). Remember, tools are like programs you can give to the model, and the model can decide as and when to use based on the context of the conversation. With the `streamUI` function, **you provide tools that return React components**. With the ability to stream components, the model is akin to a dynamic router that is able to understand the user's intention and display relevant UI. At a high level, the `streamUI` works like other AI SDK Core functions: you can provide the model with a prompt or some conversation history and, optionally, some tools. If the model decides, based on the context of the conversation, to call a tool, it will generate a tool call. The `streamUI` function will then run the respective tool, returning a React component. If the model doesn't have a relevant tool to use, it will return a text generation, which will be passed to the `text` function, for you to handle (render and return as a React component). Remember, the `streamUI` function must return a React component. ```tsx const result = await streamUI({ model: openai('gpt-6-astra'), prompt: 'Get the weather for San Francisco', text: ({ content }) =>
{content}
, tools: {}, }); ``` This example calls the `streamUI` function using OpenAI's `gpt-6-astra` model, passes a prompt, specifies how the model's plain text response (`content`) should be rendered, and then provides an empty object for tools. Even though this example does not define any tools, it will stream the model's response as a `div` rather than plain text. ### Adding A Tool Using tools with `streamUI` is similar to how you use tools with `generateText` and `streamText`. A tool is an object that has: - `description`: a string telling the model what the tool does and when to use it - `inputSchema`: a Zod schema describing what the tool needs in order to run - `generate`: an asynchronous function that will be run if the model calls the tool. This must return a React component Let's expand the previous example to add a tool. ```tsx {6-14} const result = await streamUI({ model: openai('gpt-6-astra'), prompt: 'Get the weather for San Francisco', text: ({ content }) =>
{content}
, tools: { getWeather: { description: 'Get the weather for a location', inputSchema: z.object({ location: z.string() }), generate: async function* ({ location }) { yield ; const weather = await getWeather(location); return ; }, }, }, }); ``` This tool would be run if the user asks for the weather for their location. If the user hasn't specified a location, the model will ask for it before calling the tool. When the model calls the tool, the generate function will initially return a loading component. This component will show until the awaited call to `getWeather` is resolved, at which point, the model will stream the `` to the user. Note: This example uses a [ generator function ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) (`function*`), which allows you to pause its execution and return a value, then resume from where it left off on the next call. This is useful for handling data streams, as you can fetch and return data from an asynchronous source like an API, then resume the function to fetch the next chunk when needed. By yielding values one at a time, generator functions enable efficient processing of streaming data without blocking the main thread. ## Using `streamUI` with Next.js Let's see how you can use the example above in a Next.js application. To use `streamUI` in a Next.js application, you will need two things: 1. A Server Action (where you will call `streamUI`) 2. A page to call the Server Action and render the resulting components ### Step 1: Create a Server Action Server Actions are server-side functions that you can call directly from the frontend. For more info, see [the documentation](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#with-client-components). Create a Server Action at `app/actions.tsx` and add the following code: ```tsx title="app/actions.tsx" 'use server'; import { streamUI } from '@ai-sdk/rsc'; import { openai } from '@ai-sdk/openai'; import { z } from 'zod'; const LoadingComponent = () => (
getting weather...
); const getWeather = async (location: string) => { await new Promise(resolve => setTimeout(resolve, 2000)); return '82°F️ ☀️'; }; interface WeatherProps { location: string; weather: string; } const WeatherComponent = (props: WeatherProps) => (
The weather in {props.location} is {props.weather}
); export async function streamComponent() { const result = await streamUI({ model: openai('gpt-6-astra'), prompt: 'Get the weather for San Francisco', text: ({ content }) =>
{content}
, tools: { getWeather: { description: 'Get the weather for a location', inputSchema: z.object({ location: z.string(), }), generate: async function* ({ location }) { yield ; const weather = await getWeather(location); return ; }, }, }, }); return result.value; } ``` The `getWeather` tool should look familiar as it is identical to the example in the previous section. In order for this tool to work: 1. First define a `LoadingComponent`, which renders a pulsing `div` that will show some loading text. 2. Next, define a `getWeather` function that will timeout for 2 seconds (to simulate fetching the weather externally) before returning the "weather" for a `location`. Note: you could run any asynchronous TypeScript code here. 3. Finally, define a `WeatherComponent` which takes in `location` and `weather` as props, which are then rendered within a `div`. Your Server Action is an asynchronous function called `streamComponent` that takes no inputs, and returns a `ReactNode`. Within the action, you call the `streamUI` function, specifying the model (`gpt-6-astra`), the prompt, the component that should be rendered if the model chooses to return text, and finally, your `getWeather` tool. Last but not least, you return the resulting component generated by the model with `result.value`. To call this Server Action and display the resulting React Component, you will need a page. ### Step 2: Create a Page Create or update your root page (`app/page.tsx`) with the following code: ```tsx title="app/page.tsx" 'use client'; import { useState } from 'react'; import { Button } from '@/components/ui/button'; import { streamComponent } from './actions'; export default function Page() { const [component, setComponent] = useState(); return (
{ e.preventDefault(); setComponent(await streamComponent()); }} >
{component}
); } ``` This page is first marked as a client component with the `"use client";` directive given it will be using hooks and interactivity. On the page, you render a form. When that form is submitted, you call the `streamComponent` action created in the previous step (just like any other function). The `streamComponent` action returns a `ReactNode` that you can then render on the page using React state (`setComponent`). ## Going beyond a single prompt You can now allow the model to respond to your prompt with a React component. However, this example is limited to a static prompt that is set within your Server Action. You could make this example interactive by turning it into a chatbot. Learn how to stream React components with the Next.js App Router using `streamUI` with this [example](/examples/next-app/interface/route-components). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Streaming Values description: Overview of streaming RSCs url: "https://ai-sdk.dev/docs/ai-sdk-rsc/streaming-values" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). The RSC API provides several utility functions to allow you to stream values from the server to the client. This is useful when you need more granular control over what you are streaming and how you are streaming it. These utilities can also be paired with [AI SDK Core](/docs/ai-sdk-core) functions like [`streamText`](/docs/reference/ai-sdk-core/stream-text) to easily stream LLM generations from the server to the client. There are two functions provided by the RSC API that allow you to create streamable values: - [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value) - creates a streamable (serializable) value, with full control over how you create, update, and close the stream. - [`createStreamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui) - creates a streamable React component, with full control over how you create, update, and close the stream. ## `createStreamableValue` The RSC API allows you to stream serializable JavaScript values from the server to the client using [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value), such as strings, numbers, objects, and arrays. This is useful when you want to stream: - Text generations from the language model in real-time. - Buffer values of image and audio generations from multi-modal models. - Progress updates from multi-step agent runs. ## Creating a Streamable Value You can import `createStreamableValue` from `@ai-sdk/rsc` and use it to create a streamable value. ```tsx title='app/actions.ts' 'use server'; import { createStreamableValue } from '@ai-sdk/rsc'; export const runThread = async () => { const streamableStatus = createStreamableValue('thread.init'); setTimeout(() => { streamableStatus.update('thread.run.create'); streamableStatus.update('thread.run.update'); streamableStatus.update('thread.run.end'); streamableStatus.done('thread.end'); }, 1000); return { status: streamableStatus.value, }; }; ``` ## Reading a Streamable Value You can read streamable values on the client using `readStreamableValue`. It returns an async iterator that yields the value of the streamable as it is updated: ```tsx title='app/page.tsx' import { readStreamableValue } from '@ai-sdk/rsc'; import { runThread } from '@/actions'; export default function Page() { return ( ); } ``` Learn how to stream a text generation (with `streamText`) using the Next.js App Router and `createStreamableValue` in this [example](/examples/next-app/basics/streaming-text-generation). ## `createStreamableUI` `createStreamableUI` creates a stream that holds a React component. Unlike AI SDK Core APIs, this function does not call a large language model. Instead, it provides a primitive that can be used to have granular control over streaming a React component. ## Using `createStreamableUI` Let's look at how you can use the `createStreamableUI` function with a Server Action. ```tsx title='app/actions.tsx' 'use server'; import { createStreamableUI } from '@ai-sdk/rsc'; export async function getWeather() { const weatherUI = createStreamableUI(); weatherUI.update(
Loading...
); setTimeout(() => { weatherUI.done(
It's a sunny day!
); }, 1000); return weatherUI.value; } ``` First, you create a streamable UI with an empty state and then update it with a loading message. After 1 second, you mark the stream as done passing in the actual weather information as its final value. The `.value` property contains the actual UI that can be sent to the client. ## Reading a Streamable UI On the client side, you can call the `getWeather` Server Action and render the returned UI like any other React component. ```tsx title='app/page.tsx' 'use client'; import { useState } from 'react'; import { readStreamableValue } from '@ai-sdk/rsc'; import { getWeather } from '@/actions'; export default function Page() { const [weather, setWeather] = useState(null); return (
{weather}
); } ``` When the button is clicked, the `getWeather` function is called, and the returned UI is set to the `weather` state and rendered on the page. Users will see the loading message first and then the actual weather information after 1 second. Learn more about handling multiple streams in a single request in the [Multiple Streamables](/docs/advanced/multiple-streamables) guide. Learn more about handling state for more complex use cases with [ AI/UI State ](/docs/ai-sdk-rsc/generative-ui-state). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Chatbot Message Persistence description: Learn how to store and load chat messages in a chatbot. url: "https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Being able to store and load chat messages is crucial for most AI chatbots. In this guide, we'll show how to implement message persistence with `useChat` and `streamText`. This guide does not cover authorization, error handling, or other real-world considerations. It is intended to be a simple example of how to implement message persistence. ## Starting a new chat When the user navigates to the chat page without providing a chat ID, we need to create a new chat and redirect to the chat page with the new chat ID. ```tsx title="app/chat/page.tsx" import { redirect } from 'next/navigation'; import { createChat } from '@util/chat-store'; export default async function Page() { const id = await createChat(); // create a new chat redirect(`/chat/${id}`); // redirect to chat page, see below } ``` Our example chat store implementation uses files to store the chat messages. In a real-world application, you would use a database or a cloud storage service, and get the chat ID from the database. That being said, the function interfaces are designed to be easily replaced with other implementations. ```tsx title="util/chat-store.ts" import { generateId } from 'ai'; import { existsSync, mkdirSync } from 'fs'; import { writeFile } from 'fs/promises'; import path from 'path'; // Treat chat IDs as opaque tokens before using them in file paths. const chatIdRegex = /^[A-Za-z0-9_-]+$/; export async function createChat(): Promise { const id = generateId(); // generate a unique chat ID await writeFile(getChatFile(id), '[]'); // create an empty chat file return id; } function getChatFile(id: string): string { if (!chatIdRegex.test(id)) { throw new Error('Invalid chat ID'); } const chatDir = path.resolve(process.cwd(), '.chats'); const chatFile = path.resolve(chatDir, `${id}.json`); // Defense in depth: keep the resolved file inside the chat directory. if (!chatFile.startsWith(`${chatDir}${path.sep}`)) { throw new Error('Invalid chat ID'); } if (!existsSync(chatDir)) mkdirSync(chatDir, { recursive: true }); return chatFile; } ``` The chat ID can come from the URL or the request body, so validate it as an opaque token before using it in a file path. The resolved path check ensures the file stays inside the intended `.chats` directory. ## Loading an existing chat When the user navigates to the chat page with a chat ID, we need to load the chat messages from storage. The `loadChat` function in our file-based chat store is implemented as follows: ```tsx title="util/chat-store.ts" import { UIMessage } from 'ai'; import { readFile } from 'fs/promises'; export async function loadChat(id: string): Promise { return JSON.parse(await readFile(getChatFile(id), 'utf8')); } // ... rest of the file ``` ## Validating messages on the server When processing messages on the server that contain tool calls, custom metadata, or data parts, you should validate them using `validateUIMessages` before sending them to the model. ### Persisting streaming tool input An `input-streaming` tool part includes `rawInput` while tool arguments are still arriving. Persist the complete UI message, including this field, so a resumed stream can append later tool-input deltas to the exact text received before the interruption. AI SDK updates `rawInput` as each delta arrives and removes it when the tool input becomes available. ### Migrating deprecated tool `rawInput` Older persisted tool parts in the `output-error` state may contain `rawInput`. This field is deprecated and will be removed in the next major version. Store tool arguments in `input` instead. AI SDK emits a deprecation warning when `validateUIMessages`, `safeValidateUIMessages`, or `convertToModelMessages` encounters a defined `rawInput` value. UI stream processing also warns when it reconstructs a static `output-error` part with `rawInput`. Migrate each legacy part by copying `rawInput` to `input` when `input` is `null` or `undefined`, then remove `rawInput`. This preserves the current backward-compatible conversion behavior: ```ts import { isToolUIPart } from 'ai'; const migratedParts = message.parts.map(part => { if ( !isToolUIPart(part) || part.state !== 'output-error' || !('rawInput' in part) || part.rawInput === undefined ) { return part; } const { rawInput, ...partWithoutRawInput } = part; return { ...partWithoutRawInput, input: part.input ?? rawInput, }; }); ``` ### Validation with tools When your messages include tool calls, validate them against your tool definitions: ```tsx title="app/api/chat/route.ts" {7-8,14-27,35-45} import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage, validateUIMessages, tool, } from 'ai'; import { z } from 'zod'; import { loadChat, saveChat } from '@util/chat-store'; import { dataSchemas, metadataSchema } from '@util/schemas'; // Define your tools const tools = { weather: tool({ description: 'Get weather information', inputSchema: z.object({ location: z.string(), units: z.enum(['celsius', 'fahrenheit']), }), execute: async ({ location, units }) => { /* tool implementation */ }, }), // other tools }; export async function POST(req: Request) { const { message, id } = await req.json(); // Load previous messages from database const previousMessages = await loadChat(id); // Append new message to previousMessages messages const messages = [...previousMessages, message]; // Validate loaded messages against // tools, data parts schema, and metadata schema const validatedMessages = await validateUIMessages({ messages, tools, // Ensures tool calls in messages match current schemas dataSchemas, metadataSchema, }); const result = streamText({ model: 'openai/gpt-6-luna', messages: await convertToModelMessages(validatedMessages), tools, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, originalMessages: messages, onEnd: ({ messages }) => { saveChat({ chatId: id, messages }); }, }), }); } ``` ### Handling validation errors Handle validation errors gracefully when messages from the database don't match current schemas: ```tsx title="app/api/chat/route.ts" {3,10-24} import { convertToModelMessages, streamText, validateUIMessages, TypeValidationError, } from 'ai'; import { type MyUIMessage } from '@/types'; export async function POST(req: Request) { const { message, id } = await req.json(); // Load and validate messages from database let validatedMessages: MyUIMessage[]; try { const previousMessages = await loadMessagesFromDB(id); validatedMessages = await validateUIMessages({ // append the new message to the previous messages: messages: [...previousMessages, message], tools, metadataSchema, }); } catch (error) { if (error instanceof TypeValidationError) { // Log validation error for monitoring console.error('Database messages validation failed:', error); // Could implement message migration or filtering here // For now, start with empty history validatedMessages = []; } else { throw error; } } // Continue with validated messages... } ``` ## Displaying the chat Once messages are loaded from storage, you can display them in your chat UI. Here's how to set up the page component and the chat display: ```tsx title="app/chat/[id]/page.tsx" import { loadChat } from '@util/chat-store'; import Chat from '@ui/chat'; export default async function Page(props: { params: Promise<{ id: string }> }) { const { id } = await props.params; const messages = await loadChat(id); return ; } ``` The chat component uses the `useChat` hook to manage the conversation: ```tsx title="ui/chat.tsx" {10-16} 'use client'; import { UIMessage, useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { useState } from 'react'; export default function Chat({ id, initialMessages, }: { id?: string | undefined; initialMessages?: UIMessage[] } = {}) { const [input, setInput] = useState(''); const { sendMessage, messages } = useChat({ id, // use the provided chat ID messages: initialMessages, // load initial messages transport: new DefaultChatTransport({ api: '/api/chat', }), }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (input.trim()) { sendMessage({ text: input }); setInput(''); } }; // simplified rendering code, extend as needed: return (
{messages.map(m => (
{m.role === 'user' ? 'User: ' : 'AI: '} {m.parts .map(part => (part.type === 'text' ? part.text : '')) .join('')}
))}
setInput(e.target.value)} placeholder="Type a message..." />
); } ``` ## Storing messages `useChat` sends the chat id and the messages to the backend. The `useChat` message format is different from the `ModelMessage` format. The `useChat` message format is designed for frontend display, and contains additional fields such as `id` and `createdAt`. We recommend storing the messages in the `useChat` message format. When loading messages from storage that contain tools, metadata, or custom data parts, validate them using `validateUIMessages` before processing (see the [validation section](#validating-messages-on-the-server) above). Storing messages is done in the `onEnd` callback of the `toUIMessageStream` function. `onEnd` receives the complete messages including the new AI response as `UIMessage[]`. ```tsx title="app/api/chat/route.ts" {12,17-25} import { openai } from '@ai-sdk/openai'; import { saveChat } from '@util/chat-store'; import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage, } from 'ai'; export async function POST(req: Request) { const { messages, chatId }: { messages: UIMessage[]; chatId: string } = await req.json(); const result = streamText({ model: 'openai/gpt-6-luna', messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, originalMessages: messages, onEnd: ({ messages }) => { saveChat({ chatId, messages }); }, }), }); } ``` The actual storage of the messages is done in the `saveChat` function, which in our file-based chat store is implemented as follows: ```tsx title="util/chat-store.ts" import { UIMessage } from 'ai'; import { writeFile } from 'fs/promises'; export async function saveChat({ chatId, messages, }: { chatId: string; messages: UIMessage[]; }): Promise { const content = JSON.stringify(messages, null, 2); await writeFile(getChatFile(chatId), content); } // ... rest of the file ``` ## Message IDs In addition to a chat ID, each message has an ID. You can use this message ID to e.g. manipulate individual messages. ### Client-side vs Server-side ID Generation By default, message IDs are generated client-side: - User message IDs are generated by the `useChat` hook on the client - AI response message IDs are also generated by `useChat` on the client, unless the server sends one in the stream (see below) For applications without persistence, client-side ID generation works perfectly. However, **for persistence, you should use IDs that are stable before messages are stored** to ensure consistency across sessions and prevent ID conflicts when messages are restored. The server-side options below control IDs for generated assistant response messages. User message IDs are created by `useChat` before the request is sent to your API route, so keep those client-generated IDs when saving incoming request messages, or generate and persist your own user message IDs before sending/storing them. ### Setting Up Server-side ID Generation When implementing persistence, you have two options for generating server-side IDs for assistant response messages: 1. **Using `generateMessageId` in `toUIMessageStream`** 2. **Setting IDs in your start message part with `createUIMessageStream`** #### Option 1: Using `generateMessageId` in `toUIMessageStream` You can control the ID format by providing ID generators using [`createIdGenerator()`](/docs/reference/ai-sdk-core/create-id-generator): ```tsx title="app/api/chat/route.ts" {18-22} import { createIdGenerator, createUIMessageStreamResponse, streamText, toUIMessageStream, } from 'ai'; export async function POST(req: Request) { // ... const result = streamText({ // ... }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, originalMessages: messages, // Generate consistent server-side IDs for persistence: generateMessageId: createIdGenerator({ prefix: 'msg', size: 16, }), onEnd: ({ messages }) => { saveChat({ chatId, messages }); }, }), }); } ``` #### Option 2: Setting IDs with `createUIMessageStream` Alternatively, you can use `createUIMessageStream` to control the message ID by writing a start message part: ```tsx title="app/api/chat/route.ts" {15-19,26-28} import { convertToModelMessages, generateId, streamText, createUIMessageStream, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; export async function POST(req: Request) { const { messages, chatId } = await req.json(); const stream = createUIMessageStream({ execute: async ({ writer }) => { // Write start message part with custom ID writer.write({ type: 'start', messageId: generateId(), // Generate server-side ID for persistence }); const result = streamText({ model: 'openai/gpt-6-luna', messages: await convertToModelMessages(messages), }); writer.merge( toUIMessageStream({ stream: result.stream, sendStart: false }), ); // omit start message part }, originalMessages: messages, onEnd: ({ responseMessage }) => { // save your chat here }, }); return createUIMessageStreamResponse({ stream }); } ``` For client-side applications that don't require persistence, you can still customize client-side ID generation: ```tsx title="ui/chat.tsx" import { createIdGenerator } from 'ai'; import { useChat } from '@ai-sdk/react'; const { ... } = useChat({ generateId: createIdGenerator({ prefix: 'msgc', size: 16, }), // ... }); ``` ## Sending only the last message Once you have implemented message persistence, you might want to send only the last message to the server. This reduces the amount of data sent to the server on each request and can improve performance. To achieve this, you can provide a `prepareSendMessagesRequest` function to the transport. This function receives the messages and the chat ID, and returns the request body to be sent to the server. ```tsx title="ui/chat.tsx" {7-12} import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { // ... } = useChat({ // ... transport: new DefaultChatTransport({ api: '/api/chat', // only send the last message to the server: prepareSendMessagesRequest({ messages, id }) { return { body: { message: messages[messages.length - 1], id } }; }, }), }); ``` On the server, you can then load the previous messages and append the new message to the previous messages. If your messages contain tools, metadata, or custom data parts, you should validate them: ```tsx title="app/api/chat/route.ts" {18-24} import { convertToModelMessages, createUIMessageStreamResponse, toUIMessageStream, UIMessage, validateUIMessages, } from 'ai'; // import your tools and schemas export async function POST(req: Request) { // get the last message from the client: const { message, id } = await req.json(); // load the previous messages from the server: const previousMessages = await loadChat(id); // validate messages if they contain tools, metadata, or data parts: const validatedMessages = await validateUIMessages({ // append the new message to the previous messages: messages: [...previousMessages, message], tools, // if using tools metadataSchema, // if using custom metadata dataSchemas, // if using custom data parts }); const result = streamText({ // ... messages: await convertToModelMessages(validatedMessages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, originalMessages: validatedMessages, onEnd: ({ messages }) => { saveChat({ chatId: id, messages }); }, }), }); } ``` ## Handling client disconnects By default, the AI SDK `streamText` function uses backpressure to the language model provider to prevent the consumption of tokens that are not yet requested. However, this means that when the client disconnects, e.g. by closing the browser tab or because of a network issue, the stream from the LLM will be aborted and the conversation may end up in a broken state. Assuming that you have a [storage solution](#storing-messages) in place, you can use the `consumeStream` method to consume the stream on the backend, and then save the result as usual. `consumeStream` effectively removes the backpressure, meaning that the result is stored even when the client has already disconnected. ```tsx title="app/api/chat/route.ts" {19-21} import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage, } from 'ai'; import { saveChat } from '@util/chat-store'; export async function POST(req: Request) { const { messages, chatId }: { messages: UIMessage[]; chatId: string } = 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(); // no await return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, originalMessages: messages, onEnd: ({ messages }) => { saveChat({ chatId, messages }); }, }), }); } ``` When the client reloads the page after a disconnect, the chat will be restored from the storage solution. In production applications, you would also track the state of the request (in progress, complete) in your stored messages and use it on the client to cover the case where the client reloads the page after a disconnection, but the streaming is not yet complete. For more robust handling of disconnects, you may want to add resumability on disconnects. Check out the [Chatbot Resume Streams](/docs/ai-sdk-ui/chatbot-resume-streams) documentation to learn more. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Chatbot Resume Streams description: Learn how to resume chatbot streams after client disconnects. url: "https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-resume-streams" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `useChat` supports resuming ongoing streams after page reloads. Use this feature to build applications with long-running generations. In a resumable stream setup, client-side aborts are treated as disconnects. Closing a tab, refreshing the page, or calling `stop()` only closes the current HTTP connection and should not cancel the underlying generation. To let users stop generation, add a dedicated stop endpoint that persists the partial response, cancels the active work, and clears the active stream. See [Stop an Active Resumable Stream](#stop-an-active-resumable-stream) and [troubleshooting](/docs/troubleshooting/abort-breaks-resumable-streams) for more details. ## How stream resumption works Stream resumption requires persistence for messages and active streams in your application. The AI SDK provides tools to connect to storage, but you need to set up the storage yourself. **The AI SDK provides:** - A `resume` option in `useChat` that automatically reconnects to active streams - Access to the outgoing stream through the `consumeSseStream` callback - Automatic HTTP requests to your resume endpoints **You build:** - Storage to track which stream belongs to each chat - Redis to store the UIMessage stream - Two API endpoints: POST to create streams, GET to resume them - Integration with [`resumable-stream`](https://www.npmjs.com/package/resumable-stream) to manage Redis storage ## Prerequisites To implement resumable streams in your chat application, you need: 1. **The `resumable-stream` package** - Handles the publisher/subscriber mechanism for streams 2. **A Redis instance** - Stores stream data (e.g. [Redis through Vercel](https://vercel.com/marketplace/redis)) 3. **A persistence layer** - Tracks which stream ID is active for each chat (e.g. database) ## Implementation ### 1. Client-side: Enable stream resumption Use the `resume` option in the `useChat` hook to enable stream resumption. When `resume` is true, the hook automatically attempts to reconnect to any active stream for the chat on mount: ```tsx title="app/chat/[chatId]/chat.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport, type UIMessage } from 'ai'; export function Chat({ chatData, resume = false, }: { chatData: { id: string; messages: UIMessage[] }; resume?: boolean; }) { const { messages, sendMessage, status } = useChat({ id: chatData.id, messages: chatData.messages, resume, // Enable automatic stream resumption transport: new DefaultChatTransport({ // You must send the id of the chat prepareSendMessagesRequest: ({ id, messages }) => { return { body: { id, message: messages[messages.length - 1], }, }; }, }), }); return
{/* Your chat UI */}
; } ``` You must send the chat ID with each request (see `prepareSendMessagesRequest`). When you enable `resume`, the `useChat` hook makes a `GET` request to `/api/chat/[id]/stream` on mount to check for and resume any active streams. The hook also reconnects when the page becomes visible again after being in the background (for example, after switching browser tabs or returning to a mobile app). This only happens when the stream was interrupted by a network disconnect. Healthy streams and other errors are left untouched. `DefaultChatTransport` treats resumed streams as full replays by default, as returned by the `resumable-stream` setup below. On the first `start` chunk, the client resets the interrupted response and rebuilds it from the replay. This prevents duplicate text, reasoning, and step parts after a network disconnect. If your resume endpoint sends only the remaining chunks, set `resumeStreamIsReplay: false`. This preserves the interrupted response even if the continuation includes a `start` chunk with the same message ID: ```ts const transport = new DefaultChatTransport({ resumeStreamIsReplay: false, }); ``` When a continuation omits the `start` chunk, the client preserves its interrupted streaming state with either setting. Continuing active text or reasoning parts requires the same chat instance; a page reload does not retain that streaming state, so use a full replay for reloads. The default transport URL-encodes the chat ID as a single path segment. IDs equal to `.` or `..` are rejected with an `InvalidArgumentError` because URL parsers interpret them as path traversal segments. Let's start by creating the POST handler to create the resumable stream. ### 2. Create the POST handler The POST handler creates resumable streams using the `consumeSseStream` callback: ```ts title="app/api/chat/route.ts" import { openai } from '@ai-sdk/openai'; import { readChat, saveChat } from '@util/chat-store'; import { convertToModelMessages, createUIMessageStreamResponse, generateId, streamText, toUIMessageStream, type UIMessage, } from 'ai'; import { after } from 'next/server'; import { createResumableStreamContext } from 'resumable-stream'; export async function POST(req: Request) { const { message, id, }: { message: UIMessage | undefined; id: string; } = await req.json(); const chat = await readChat(id); let messages = chat.messages; messages = [...messages, message!]; // Clear any previous active stream and save the user message saveChat({ id, messages, activeStreamId: null }); const result = streamText({ model: 'openai/gpt-6-luna', messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, originalMessages: messages, generateMessageId: generateId, onEnd: ({ messages }) => { // Clear the active stream when finished saveChat({ id, messages, activeStreamId: null }); }, }), async consumeSseStream({ stream }) { const streamId = generateId(); // Create a resumable stream from the SSE stream const streamContext = createResumableStreamContext({ waitUntil: after }); await streamContext.createNewResumableStream(streamId, () => stream); // Update the chat with the active stream ID saveChat({ id, activeStreamId: streamId }); }, }); } ``` ### 3. Implement the GET handler Create a GET handler at `/api/chat/[id]/stream` that: 1. Reads the chat ID from the route params 2. Loads the chat data to check for an active stream 3. Returns 204 (No Content) if no stream is active 4. Resumes the existing stream if one is found ```ts title="app/api/chat/[id]/stream/route.ts" import { readChat } from '@util/chat-store'; import { UI_MESSAGE_STREAM_HEADERS } from 'ai'; import { after } from 'next/server'; import { createResumableStreamContext } from 'resumable-stream'; export async function GET( _: Request, { params }: { params: Promise<{ id: string }> }, ) { const { id } = await params; const chat = await readChat(id); if (chat.activeStreamId == null) { // no content response when there is no active stream return new Response(null, { status: 204 }); } const streamContext = createResumableStreamContext({ waitUntil: after, }); return new Response( await streamContext.resumeExistingStream(chat.activeStreamId), { headers: UI_MESSAGE_STREAM_HEADERS }, ); } ``` The `after` function from Next.js allows work to continue after the response has been sent. This ensures that the resumable stream persists in Redis even after the initial response is returned to the client, enabling reconnection later. ## How it works ### Request lifecycle Diagram showing the architecture and lifecycle of resumable stream requests The diagram above shows the complete lifecycle of a resumable stream: 1. **Stream creation**: When you send a new message, the POST handler uses `streamText` to generate the response. The `consumeSseStream` callback creates a resumable stream with a unique ID and stores it in Redis through the `resumable-stream` package 2. **Stream tracking**: Your persistence layer saves the `activeStreamId` in the chat data 3. **Client reconnection**: When the client reconnects (page reload, or returning to a backgrounded page after a network disconnect), the `resume` option triggers a GET request to `/api/chat/[id]/stream` 4. **Stream recovery**: The GET handler checks for an `activeStreamId` and uses `resumeExistingStream` to reconnect. If no active stream exists, it returns a 204 (No Content) response 5. **Completion cleanup**: When the stream finishes, the `onEnd` callback clears the `activeStreamId` by setting it to `null` ## Customize the resume endpoint By default, the `useChat` hook makes a GET request to `/api/chat/[id]/stream` when resuming. Customize this endpoint, credentials, and headers, using the `prepareReconnectToStreamRequest` option in `DefaultChatTransport`: The callback receives the original, unencoded ID. Its returned `api` URL is used verbatim, so encode and validate any ID you include in a custom URL yourself. ```tsx title="app/chat/[chatId]/chat.tsx" import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; export function Chat({ chatData, resume }) { const { messages, sendMessage } = useChat({ id: chatData.id, messages: chatData.messages, resume, transport: new DefaultChatTransport({ // Customize reconnect settings (optional) prepareReconnectToStreamRequest: ({ id }) => { return { api: `/api/chat/${encodeURIComponent(id)}/stream`, // Or use a different pattern: // api: `/api/streams/${encodeURIComponent(id)}/resume`, // api: `/api/resume-chat?id=${encodeURIComponent(id)}`, credentials: 'include', // Include cookies/auth headers: { Authorization: 'Bearer token', 'X-Custom-Header': 'value', }, }; }, }), }); return
{/* Your chat UI */}
; } ``` This lets you: - Match your existing API route structure - Add query parameters or custom paths - Integrate with different backend architectures ## Stop an Active Resumable Stream `useChat` includes a `stop()` function that aborts the current client request. In a resumable stream setup, that abort is a disconnect signal, not a request to stop generation. Stream resumption lets a client reconnect to an active stream after the original connection closes. To make that possible, the server keeps the stream running even when no client is actively consuming it. If the user refreshes the page, closes the tab, loses their connection, or navigates away, the client can reconnect later with `resumeStream()`. Because of this, a client-side abort (e.g. closing the page or refreshing) only closes the current HTTP connection. It is not a request to cancel the underlying work. If your stop button only calls `stop()`, the model request, background job, workflow, or stream writer can continue running, and the client can reconnect to the same active stream. To support an explicit stop button, create a dedicated stop endpoint. The endpoint should accept the current assistant message from the client, persist that partial response, cancel the work that is producing the stream, and clear the active stream record for the chat. Stream resumption also needs your application to store a reference from the chat to the stream that can be resumed. This guide calls that reference `activeStreamId`. The resume endpoint uses it to find the stream to reconnect to. The stop endpoint uses the same value to find the work to cancel, and to avoid clearing a newer stream that may have started while the stop request was in flight. ### Client-side: send the current assistant message The chat setup is the same as the resumable stream setup above. To add stop behavior, send the latest partial assistant message to your stop endpoint before stopping the local chat stream. When the client knows the active stream ID, include it in the request. This lets the server ignore stale stop requests that arrive after a newer stream has already started. ```tsx title="app/chat/[chatId]/chat.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import { type UIMessage } from 'ai'; export function Chat({ chatData, resume = false, }: { chatData: { id: string; messages: UIMessage[]; activeStreamId?: string | null; }; resume?: boolean; }) { const chat = useChat({ id: chatData.id, messages: chatData.messages, resume, }); const stop = () => { const lastMessage = chat.messages[chat.messages.length - 1]; const assistantMessage = lastMessage?.role === 'assistant' ? lastMessage : undefined; void fetch(`/api/chat/${encodeURIComponent(chatData.id)}/stop`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify( assistantMessage || chatData.activeStreamId ? { assistantMessage, activeStreamId: chatData.activeStreamId, } : {}, ), }); void chat.stop(); }; return ; } ``` The stop request tells your server to cancel the active work. `chat.stop()` stops the local client from reading more chunks. ### Server-side: stop the active work and clear the stream The stop endpoint should: 1. Load the chat and read its `activeStreamId` 2. Persist the assistant snapshot if one was sent 3. Cancel the work that is producing the stream 4. Clear `activeStreamId` only if it still points to the same stream ```ts title="app/api/chat/[id]/stop/route.ts" import { readChat, saveChat } from '@util/chat-store'; import { type UIMessage } from 'ai'; type StopRequest = { activeStreamId?: string | null; assistantMessage?: UIMessage; }; export async function POST( req: Request, { params }: { params: Promise<{ id: string }> }, ) { const { id } = await params; const chat = await readChat(id); if (chat.activeStreamId == null) { return Response.json({ success: true }); } const activeStreamId = chat.activeStreamId; const body = (await req.json().catch(() => ({}))) as StopRequest; if (body.activeStreamId != null && body.activeStreamId !== activeStreamId) { return Response.json({ success: true }); } if (body.assistantMessage) { await saveAssistantSnapshot({ chatId: id, message: body.assistantMessage, }); } await markStreamAsStopped(activeStreamId); await cancelActiveWork(activeStreamId); const latestChat = await readChat(id); if (latestChat.activeStreamId === activeStreamId) { await saveChat({ id, activeStreamId: null }); } return Response.json({ success: true }); } ``` `markStreamAsStopped` and `cancelActiveWork` depend on your backend. In a Redis-backed resumable stream setup, you might close the stored stream and abort the model request that is writing to it. In a workflow setup, you might cancel the workflow run that owns the stream. In a job queue setup, you might cancel the job or write a cancellation flag that the job checks. The `activeStreamId` can identify replay state, producer state, or both. If those are separate in your system, store enough information with the chat to cancel the producer that writes to the stream. Persist the assistant snapshot as an insert or merge. Avoid overwriting a newer server-written message with an older client snapshot. ### Keep navigation separate from stop Do not call the stop endpoint from route cleanup code. Route cleanup is a disconnect, not an explicit stop. The active stream should remain resumable when the user refreshes the page or navigates away. Only call the stop endpoint for an explicit user action, such as pressing a stop button. After a user stops a stream, avoid automatic reconnect attempts for that chat until the user sends another message or explicitly retries. Otherwise the client can reconnect before cancellation has finished. ## Important considerations - **Stream expiration**: Streams in Redis expire after a set time (configurable in the `resumable-stream` package) - **Multiple clients**: Multiple clients can connect to the same stream simultaneously - **Error handling**: When no active stream exists, the GET handler returns a 204 (No Content) status code - **Security**: Ensure proper authentication and authorization for both creating and resuming streams - **Race conditions**: Clear the `activeStreamId` when starting a new stream to prevent resuming outdated streams
[View Example on GitHub](https://github.com/vercel/ai/blob/main/examples/next) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Chatbot Tool Usage description: Learn how to use tools with the useChat hook. url: "https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-tool-usage" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). With [`useChat`](/docs/reference/ai-sdk-ui/use-chat) and [`streamText`](/docs/reference/ai-sdk-core/stream-text), you can use tools in your chatbot application. The AI SDK supports three tool execution patterns in this context: 1. Automatically executed server-side tools 2. Automatically executed client-side tools 3. Tools that require user interaction, such as confirmation dialogs The flow is as follows: 1. The user enters a message in the chat UI. 2. The message is sent to the API route. 3. In your server-side route, the language model generates tool calls during the `streamText` call. 4. All tool calls are forwarded to the client. 5. Server-side tools are executed using their `execute` method and their results are forwarded to the client. 6. Client-side tools that should be automatically executed are handled with the `onToolCall` callback. You must call `addToolOutput` to provide the tool result. 7. Client-side tools that require user interactions can be displayed in the UI. The tool calls and results are available as tool invocation parts in the `parts` property of the last assistant message. 8. When the user interaction is done, `addToolOutput` can be used to add the tool result to the chat. 9. The chat can be configured to automatically submit when all tool results are available using `sendAutomaticallyWhen`. This triggers another iteration of this flow. The tool calls and tool executions are integrated into the assistant message as typed tool parts. A tool part is at first a tool call, and then it becomes a tool result when the tool is executed. The tool result contains all information about the tool call as well as the result of the tool execution. Tool result submission can be configured using the `sendAutomaticallyWhen` option. You can use the `lastAssistantMessageIsCompleteWithToolCalls` helper to automatically submit when all tool results are available. This simplifies the client-side code while still allowing full control when needed. ## Example In this example, we'll use three tools: - `getWeatherInformation`: An automatically executed server-side tool that returns the weather in a given city. - `askForConfirmation`: A user-interaction client-side tool that asks the user for confirmation. - `getLocation`: An automatically executed client-side tool that returns a random city. ### API route ```tsx title='app/api/chat/route.ts' import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage, } from 'ai'; import { z } from 'zod'; // Allow streaming responses up to 30 seconds export const maxDuration = 30; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), tools: { // server-side tool with execute function: getWeatherInformation: { description: 'show the weather in a given city to the user', inputSchema: z.object({ city: z.string() }), execute: async ({}: { city: string }) => { const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy']; return weatherOptions[ Math.floor(Math.random() * weatherOptions.length) ]; }, }, // client-side tool that starts user interaction: askForConfirmation: { description: 'Ask the user for confirmation.', inputSchema: z.object({ message: z.string().describe('The message to ask for confirmation.'), }), }, // client-side tool that is automatically executed on the client: getLocation: { description: 'Get the user location. Always ask for confirmation before using this tool.', inputSchema: z.object({}), }, }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ### Client-side page The client-side page uses the `useChat` hook to create a chatbot application with real-time message streaming. Tool calls are displayed in the chat UI as typed tool parts. Please make sure to render the messages using the `parts` property of the message. There are three things worth mentioning: 1. The [`onToolCall`](/docs/reference/ai-sdk-ui/use-chat#on-tool-call) callback is used to handle client-side tools that should be automatically executed. In this example, the `getLocation` tool is a client-side tool that returns a random city. You call `addToolOutput` to provide the result (without `await` to avoid potential deadlocks). Always check `if (toolCall.dynamic)` first in your `onToolCall` handler. Without this check, TypeScript will throw an error like: `Type 'string' is not assignable to type '"toolName1" | "toolName2"'` when you try to use `toolCall.toolName` in `addToolOutput`. 2. The [`sendAutomaticallyWhen`](/docs/reference/ai-sdk-ui/use-chat#send-automatically-when) option with `lastAssistantMessageIsCompleteWithToolCalls` helper automatically submits when all tool results are available. 3. The `parts` array of assistant messages contains tool parts with typed names like `tool-askForConfirmation`. The client-side tool `askForConfirmation` is displayed in the UI. It asks the user for confirmation and displays the result once the user confirms or denies the execution. The result is added to the chat using `addToolOutput` with the `tool` parameter for type safety. Typed tool parts also include the `approval-requested`, `approval-responded`, and `output-denied` states. Include these states when handling `part.state` exhaustively, even when a tool does not require approval. See [Tool execution approval](#tool-execution-approval) for a complete approval UI. ```tsx title='app/page.tsx' {6,11,16,19-23,25-26,28-33,51,66-70,77-81} 'use client'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls, } from 'ai'; import { useState } from 'react'; export default function Chat() { const { messages, sendMessage, addToolOutput } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, // run client-side tools that are automatically executed: async onToolCall({ toolCall }) { // Check if it's a dynamic tool first for proper type narrowing if (toolCall.dynamic) { return; } if (toolCall.toolName === 'getLocation') { const cities = ['New York', 'Los Angeles', 'Chicago', 'San Francisco']; // No await - avoids potential deadlocks addToolOutput({ tool: 'getLocation', toolCallId: toolCall.toolCallId, output: cities[Math.floor(Math.random() * cities.length)], }); } }, }); const [input, setInput] = useState(''); return ( <> {messages?.map(message => (
{`${message.role}: `} {message.parts.map(part => { switch (part.type) { // render text parts as simple text: case 'text': return part.text; // for tool parts, use the typed tool part names: case 'tool-askForConfirmation': { const callId = part.toolCallId; switch (part.state) { case 'input-streaming': return (
Loading confirmation request...
); case 'input-available': return (
{part.input.message}
); case 'approval-requested': return
Approval requested.
; case 'approval-responded': return
Approval response received.
; case 'output-available': return (
Confirmation result: {part.output}
); case 'output-error': return
Error: {part.errorText}
; case 'output-denied': return
Tool call denied.
; } break; } case 'tool-getLocation': { const callId = part.toolCallId; switch (part.state) { case 'input-streaming': return (
Preparing location request...
); case 'input-available': return
Getting location...
; case 'approval-requested': return
Approval requested.
; case 'approval-responded': return
Approval response received.
; case 'output-available': return
Location: {part.output}
; case 'output-error': return (
Error getting location: {part.errorText}
); case 'output-denied': return
Location request denied.
; } break; } case 'tool-getWeatherInformation': { const callId = part.toolCallId; switch (part.state) { // example of pre-rendering streaming tool inputs: case 'input-streaming': return (
{JSON.stringify(part, null, 2)}
); case 'input-available': return (
Getting weather information for {part.input.city}...
); case 'approval-requested': return
Approval requested.
; case 'approval-responded': return
Approval response received.
; case 'output-available': return (
Weather in {part.input.city}: {part.output}
); case 'output-error': return (
Error getting weather for {part.input.city}:{' '} {part.errorText}
); case 'output-denied': return
Weather request denied.
; } break; } } })}
))}
{ e.preventDefault(); if (input.trim()) { sendMessage({ text: input }); setInput(''); } }} > setInput(e.target.value)} />
); } ``` ### Error handling Sometimes an error may occur during client-side tool execution. Use the `addToolOutput` method with a `state` of `output-error` and `errorText` value instead of `output` to record the error. ```tsx title='app/page.tsx' {19,36-41} 'use client'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls, } from 'ai'; import { useState } from 'react'; export default function Chat() { const { messages, sendMessage, addToolOutput } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, // run client-side tools that are automatically executed: async onToolCall({ toolCall }) { // Check if it's a dynamic tool first for proper type narrowing if (toolCall.dynamic) { return; } if (toolCall.toolName === 'getLocation') { try { const location = await getLocation(); // No await - avoids potential deadlocks addToolOutput({ tool: 'getLocation', toolCallId: toolCall.toolCallId, output: location, }); } catch (err) { addToolOutput({ tool: 'getLocation', toolCallId: toolCall.toolCallId, state: 'output-error', errorText: 'Unable to get the user location', }); } } }, }); } ``` When rendering messages, use `isToolOutputErrorUIPart` to identify failed static and dynamic tool parts without checking the tool state directly: ```tsx import { isToolOutputErrorUIPart, type UIMessage } from 'ai'; function ToolError({ part }: { part: UIMessage['parts'][number] }) { if (!isToolOutputErrorUIPart(part)) { return null; } return
{part.errorText}
; } ``` ## Tool Execution Approval Tool execution approval lets you require user confirmation before a server-side tool runs. Unlike [client-side tools](#example) that execute in the browser, tools with approval still execute on the server—but only after the user approves. Use tool execution approval when you want to: - Confirm sensitive operations (payments, deletions, external API calls) - Let users review tool inputs before execution - Add human oversight to automated workflows For tools that need to run in the browser (updating UI state, accessing browser APIs), use client-side tools instead. ### Server Setup Enable approval with `toolApproval` on `streamText`. The older `needsApproval` property on tools is deprecated. See [Tool Execution Approval](/docs/ai-sdk-core/tools-and-tool-calling#tool-execution-approval) for configuration options including dynamic approval based on input. ```tsx title='app/api/chat/route.ts' import { createUIMessageStreamResponse, streamText, tool, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, tools: { getWeather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ city: z.string(), }), execute: async ({ city }) => { const weather = await fetchWeather(city); return weather; }, }), }, toolApproval: { getWeather: 'user-approval', }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ### Client-Side Approval UI When a tool requires manual approval, the tool part state is `approval-requested`. Automatic approvals and denials also flow through the same approval states, but they set `part.approval.isAutomatic === true`, so you can render the status without calling `addToolApprovalResponse`. Automatic approval decisions can also include `part.approval.reason`. ```tsx title='app/page.tsx' 'use client'; import { useChat } from '@ai-sdk/react'; export default function Chat() { const { messages, addToolApprovalResponse } = useChat(); return ( <> {messages.map(message => (
{message.parts.map(part => { if (part.type === 'tool-getWeather') { switch (part.state) { case 'approval-requested': { if (part.approval.isAutomatic) { return (
Checking approval for {part.input.city}...
); } return (

Get weather for {part.input.city}?

{part.approval.requestReason && (

{part.approval.requestReason}

)}
); } case 'approval-responded': return (
Weather request for {part.input.city} was {part.approval.isAutomatic ? ' automatically' : ''}{' '} {part.approval.approved ? 'approved' : 'denied'}. {part.approval.reason ? ` Reason: ${part.approval.reason}` : ''}
); case 'output-available': return (
Weather in {part.input.city}: {part.output}
); case 'output-denied': return (
Weather request for {part.input.city} was denied. {part.approval.reason ? ` Reason: ${part.approval.reason}` : ''}
); } } // Handle other part types... })}
))} ); } ``` Call `addToolApprovalResponse` only for manual approvals. Automatic approval decisions already arrive in the UI stream as `approval-requested` and `approval-responded` states, and denied executions continue to `output-denied`. If you return a `reason` from an automatic approval or denial, it is available as `part.approval.reason`. For manual approval requests, the reason for requiring approval is available as `part.approval.requestReason`. It remains separate from an optional response reason supplied to `addToolApprovalResponse`. When a response reason is supplied with a denial, it is included in the `execution-denied` tool result that the model sees. When it is supplied with an approval for a tool executed by the AI SDK, it is recorded on the message part, but the SDK does not include it in the model prompt, modify the tool's input, or expose it through a dedicated execution option. To collect input from the user for a tool, use a client-side tool and `addToolOutput` instead. Approval request chunks can also include an `approvalDescriptor` with opaque application-specific metadata. UI message processing exposes it as `part.approval.descriptor` in the `approval-requested` state and preserves it in subsequent approval-bearing states, including `approval-responded`. This lets clients render or persist server-computed approval metadata without using it to determine whether the tool was approved. ### Securing Approvals for Sensitive Tools In the `useChat` pattern, the client sends the full message history to the server each turn. Without additional protection, a modified client could fabricate an approval response. For tools that perform sensitive operations, add `experimental_toolApprovalSecret` to your `streamText` call so the server cryptographically verifies that it issued the approval: ```tsx title='app/api/chat/route.ts' {6} const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, tools: { deleteFile }, toolApproval: { deleteFile: 'user-approval' }, experimental_toolApprovalSecret: process.env.TOOL_APPROVAL_SECRET, }); ``` See [Security Considerations](/docs/agents/tool-approvals#security-considerations) for setup details. ### Auto-Submit After Approval If nothing happens after you approve a tool execution, make sure you either call `sendMessage` manually or configure `sendAutomaticallyWhen` on the `useChat` hook. Use `lastAssistantMessageIsCompleteWithApprovalResponses` to automatically continue the conversation after approvals: ```tsx import { useChat } from '@ai-sdk/react'; import { lastAssistantMessageIsCompleteWithApprovalResponses } from 'ai'; const { messages, addToolApprovalResponse } = useChat({ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses, }); ``` ## Dynamic Tools When using dynamic tools (tools with unknown types at compile time), the UI parts use a generic `dynamic-tool` type instead of specific tool types: ```tsx title='app/page.tsx' { message.parts.map((part, index) => { switch (part.type) { // Static tools with specific (`tool-${toolName}`) types case 'tool-getWeatherInformation': return ; // Dynamic tools use generic `dynamic-tool` type case 'dynamic-tool': return (

Tool: {part.toolName}

{part.state === 'input-streaming' && (
{JSON.stringify(part.input, null, 2)}
)} {part.state === 'output-available' && (
{JSON.stringify(part.output, null, 2)}
)} {part.state === 'output-error' && (
Error: {part.errorText}
)}
); } }); } ``` Dynamic tools are useful when integrating with: - MCP (Model Context Protocol) tools without schemas - User-defined functions loaded at runtime - External tool providers ## Tool call streaming Tool call streaming is **enabled by default** in AI SDK 5.0, allowing you to stream tool calls while they are being generated. This provides a better user experience by showing tool inputs as they are generated in real-time. ```tsx title='app/api/chat/route.ts' export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), // toolCallStreaming is enabled by default in v5 // ... }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` With tool call streaming enabled, partial tool calls are streamed as part of the data stream. They are available through the `useChat` hook. The typed tool parts of assistant messages will also contain partial tool calls. You can use the `state` property of the tool part to render the correct UI. ```tsx title='app/page.tsx' {9,10} export default function Chat() { // ... return ( <> {messages?.map(message => (
{message.parts.map(part => { switch (part.type) { case 'tool-askForConfirmation': case 'tool-getLocation': case 'tool-getWeatherInformation': switch (part.state) { case 'input-streaming': return
{JSON.stringify(part.input, null, 2)}
; case 'input-available': return
{JSON.stringify(part.input, null, 2)}
; case 'approval-requested': return
Approval requested.
; case 'approval-responded': return
Approval response received.
; case 'output-available': return
{JSON.stringify(part.output, null, 2)}
; case 'output-error': return
Error: {part.errorText}
; case 'output-denied': return
Tool call denied.
; } } })}
))} ); } ``` ## Step start parts When you are using multi-step tool calls, the AI SDK will add step start parts to the assistant messages. If you want to display boundaries between tool calls, you can use the `step-start` parts as follows: ```tsx title='app/page.tsx' // ... // where you render the message parts: message.parts.map((part, index) => { switch (part.type) { case 'step-start': // show step boundaries as horizontal lines: return index > 0 ? (

) : null; case 'text': // ... case 'tool-askForConfirmation': case 'tool-getLocation': case 'tool-getWeatherInformation': // ... } }); // ... ``` ## Server-side Multi-Step Calls You can also use multi-step calls on the server-side with `streamText`. This works when all invoked tools have an `execute` function on the server side. ```tsx title='app/api/chat/route.ts' {22-28,31} import { convertToModelMessages, createUIMessageStreamResponse, isStepCount, streamText, toUIMessageStream, UIMessage, } from 'ai'; import { z } from 'zod'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), tools: { getWeatherInformation: { description: 'show the weather in a given city to the user', inputSchema: z.object({ city: z.string() }), // tool has execute function: execute: async ({}: { city: string }) => { const weatherOptions = ['sunny', 'cloudy', 'rainy', 'snowy', 'windy']; return weatherOptions[ Math.floor(Math.random() * weatherOptions.length) ]; }, }, }, stopWhen: isStepCount(5), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ## Errors Language models can make errors when calling tools. By default, these errors are masked for security reasons, and show up as "An error occurred" in the UI. Tool execution errors marked `providerExecuted: true` bypass this masking and the UI stream's `onError` callback. String errors pass through unchanged; other error values are serialized with `JSON.stringify`. The original error data is needed when converting persisted UI messages back into model messages, for example to preserve Anthropic tool error codes. Harness runtime tool errors also retain their original messages. The callback still handles invalid tool calls and stream errors, regardless of whether a tool is provider-executed. To customize masked errors, pass `onError` to `toUIMessageStream`. This callback does not redact provider-executed tool execution errors sent to the client. ```tsx export function errorHandler(error: unknown) { if (error == null) { return 'unknown error'; } if (typeof error === 'string') { return error; } if (error instanceof Error) { return error.message; } return JSON.stringify(error); } ``` ```tsx const result = streamText({ // ... }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, onError: errorHandler, }), }); ``` In case you are using `createUIMessageResponse`, you can use the `onError` function when calling `toUIMessageResponse`: ```tsx const response = createUIMessageResponse({ // ... async execute(dataStream) { // ... }, onError: error => `Custom error: ${error.message}`, }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Chatbot description: Learn how to use the useChat hook. url: "https://ai-sdk.dev/docs/ai-sdk-ui/chatbot" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `useChat` hook makes it effortless to create a conversational user interface for your chatbot application. It enables the streaming of chat messages from your AI provider, manages the chat state, and updates the UI automatically as new messages arrive. To summarize, the `useChat` hook provides the following features: - **Message Streaming**: All the messages from the AI provider are streamed to the chat UI in real-time. - **Managed States**: The hook manages the states for input, messages, status, error and more for you. - **Seamless Integration**: Easily integrate your chat AI into any design or layout with minimal effort. In this guide, you will learn how to use the `useChat` hook to create a chatbot application with real-time message streaming. Check out our [chatbot with tools guide](/docs/ai-sdk-ui/chatbot-tool-usage) to learn how to use tools in your chatbot. Let's start with the following example first. ## Example ```tsx title='app/page.tsx' 'use client'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { useState } from 'react'; export default function Page() { const { messages, sendMessage, status } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); const [input, setInput] = useState(''); return ( <> {messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, index) => part.type === 'text' ? {part.text} : null, )}
))}
{ e.preventDefault(); if (input.trim()) { sendMessage({ text: input }); setInput(''); } }} > setInput(e.target.value)} disabled={status !== 'ready'} placeholder="Say something..." />
); } ``` ```ts title='app/api/chat/route.ts' import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage, } from 'ai'; // Allow streaming responses up to 30 seconds export const maxDuration = 30; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a helpful assistant.', messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` The UI messages have a new `parts` property that contains the message parts. We recommend rendering the messages using the `parts` property instead of the `content` property. The parts property supports different message types, including text, tool invocation, and tool result, and allows for more flexible and complex chat UIs. In the `Page` component, the `useChat` hook will request to your AI provider endpoint whenever the user sends a message using `sendMessage`. The messages are then streamed back in real-time and displayed in the chat UI. This enables a seamless chat experience where the user can see the AI response as soon as it is available, without having to wait for the entire response to be received. ## Customized UI `useChat` also provides ways to manage the chat message states via code, show status, and update messages without being triggered by user interactions. ### Status The `useChat` hook returns a `status`. It has the following possible values: - `submitted`: The message has been sent to the API and we're awaiting the start of the response stream. - `streaming`: The response is actively streaming in from the API, receiving chunks of data. - `ready`: The full response has been received and processed; a new user message can be submitted. - `error`: An error occurred during the API request, preventing successful completion. You can use `status` for e.g. the following purposes: - To show a loading spinner while the chatbot is processing the user's message. - To show a "Stop" button to abort the current message. - To disable the submit button. ```tsx title='app/page.tsx' {8,26-33,47,50} 'use client'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { useState } from 'react'; export default function Page() { const { messages, sendMessage, status, stop } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); const [input, setInput] = useState(''); return ( <> {messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, index) => part.type === 'text' ? {part.text} : null, )}
))} {(status === 'submitted' || status === 'streaming') && (
{status === 'submitted' && }
)}
{ e.preventDefault(); if (input.trim()) { sendMessage({ text: input }); setInput(''); } }} > setInput(e.target.value)} disabled={status !== 'ready'} placeholder="Say something..." />
); } ``` ### Error State Similarly, the `error` state reflects the error object thrown during the fetch request. It can be used to display an error message, disable the submit button, or show a retry button: We recommend showing a generic error message to the user, such as "Something went wrong." This is a good practice to avoid leaking information from the server. ```tsx title="app/page.tsx" {8,26-33,47} 'use client'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { useState } from 'react'; export default function Chat() { const { messages, sendMessage, error, regenerate } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); const [input, setInput] = useState(''); return (
{messages.map(m => (
{m.role}:{' '} {m.parts.map((part, index) => part.type === 'text' ? {part.text} : null, )}
))} {error && ( <>
An error occurred.
)}
{ e.preventDefault(); if (input.trim()) { sendMessage({ text: input }); setInput(''); } }} > setInput(e.target.value)} disabled={error != null} />
); } ``` Please also see the [error handling](/docs/ai-sdk-ui/error-handling) guide for more information. ### Modify messages Sometimes, you may want to directly modify some existing messages. For example, a delete button can be added to each message to allow users to remove them from the chat history. The `setMessages` function can help you achieve these tasks: ```tsx const { messages, setMessages } = useChat() const handleDelete = (id) => { setMessages(messages.filter(message => message.id !== id)) } return <> {messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, index) => ( part.type === 'text' ? ( {part.text} ) : null ))}
))} ... ``` You can think of `messages` and `setMessages` as a pair of `state` and `setState` in React. ### Cancellation and regeneration It's also a common use case to abort the response message while it's still streaming back from the AI provider. You can do this by calling the `stop` function returned by the `useChat` hook. ```tsx const { stop, status } = useChat() return <> ... ``` When the user clicks the "Stop" button, the fetch request will be aborted. This avoids consuming unnecessary resources and improves the UX of your chatbot application. Similarly, you can also request the AI provider to reprocess the last message by calling the `regenerate` function returned by the `useChat` hook: ```tsx const { regenerate, status } = useChat(); return ( <> ... ); ``` When the user clicks the "Regenerate" button, the AI provider will regenerate the last message and replace the current one correspondingly. ### Throttling UI Updates By default, `useChat` publishes message updates as each chunk is processed. React may batch multiple updates into a single render. React and Vue applications can throttle reactive message updates with the `throttle` option. Stream processing and event callbacks remain immediate, and the latest messages are published before the chat enters a terminal `ready` or `error` status. #### React ```tsx title="page.tsx" {2-3} const { messages, ... } = useChat({ // Throttle reactive message updates to 50ms: throttle: 50, }); ``` #### Vue ```vue title="pages/index.vue" {4-6} ``` ## Event Callbacks `useChat` provides optional event callbacks that you can use to handle different stages of the chatbot lifecycle: - `onFinish`: Called when the assistant response is completed. The event includes the response message, all messages, and flags for abort, disconnect, and errors. - `onError`: Called when an error occurs during the fetch request. - `onData`: Called whenever a data part is received. These callbacks can be used to trigger additional actions, such as logging, analytics, or custom UI updates. ```tsx import { UIMessage } from 'ai'; const { /* ... */ } = useChat({ onFinish: ({ message, messages, isAbort, isDisconnect, isError }) => { // use information to e.g. update other UI states }, onError: error => { console.error('An error occurred:', error); }, onData: data => { console.log('Received data part from server:', data); }, }); ``` It's worth noting that you can abort the processing by throwing an error in the `onData` callback. This will trigger the `onError` callback and stop the message from being appended to the chat UI. This can be useful for handling unexpected responses from the AI provider. ## Request Configuration ### Custom headers, body, and credentials By default, the `useChat` hook sends an HTTP POST request to the `/api/chat` endpoint with the message list as the request body. You can customize the request in two ways: #### Hook-Level Configuration (Applied to all requests) You can configure transport-level options that will be applied to all requests made by the hook: ```tsx import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/custom-chat', headers: { Authorization: 'your_token', }, body: { user_id: '123', }, credentials: 'same-origin', }), }); ``` #### Dynamic Hook-Level Configuration You can also provide functions that return configuration values. This is useful for authentication tokens that need to be refreshed, or for configuration that depends on runtime conditions: ```tsx import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/custom-chat', headers: () => ({ Authorization: `Bearer ${getAuthToken()}`, 'X-User-ID': getCurrentUserId(), }), body: () => ({ sessionId: getCurrentSessionId(), preferences: getUserPreferences(), }), credentials: () => 'include', }), }); ``` For component state that changes over time, use `useRef` to store the current value and reference `ref.current` in your configuration function, or prefer request-level options (see next section) for better reliability. #### Request-Level Configuration (Recommended) **Recommended**: Use request-level options for better flexibility and control. Request-level options take precedence over hook-level options and allow you to customize each request individually. ```tsx // Pass options as the second parameter to sendMessage sendMessage( { text: input }, { headers: { Authorization: 'Bearer token123', 'X-Custom-Header': 'custom-value', }, body: { temperature: 0.7, max_tokens: 100, user_id: '123', }, metadata: { userId: 'user123', sessionId: 'session456', }, }, ); ``` The request-level options are merged with hook-level options, with request-level options taking precedence. On your server side, you can handle the request with this additional information. ### Setting custom body fields per request You can configure custom `body` fields on a per-request basis using the second parameter of the `sendMessage` function. This is useful if you want to pass in additional information to your backend that is not part of the message list. ```tsx title="app/page.tsx" {25-32} 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Chat() { const { messages, sendMessage } = useChat(); const [input, setInput] = useState(''); return (
{messages.map(m => (
{m.role}:{' '} {m.parts.map((part, index) => part.type === 'text' ? {part.text} : null, )}
))}
{ event.preventDefault(); if (input.trim()) { sendMessage( { text: input }, { body: { customKey: 'customValue', }, }, ); setInput(''); } }} > setInput(e.target.value)} />
); } ``` You can retrieve these custom fields on your server side by destructuring the request body: ```ts title="app/api/chat/route.ts" {3,4} export async function POST(req: Request) { // Extract additional information ("customKey") from the body of the request: const { messages, customKey }: { messages: UIMessage[]; customKey: string } = await req.json(); //... } ``` ## Message Metadata You can attach custom metadata to messages for tracking information like timestamps, model details, and token usage. ```ts // Server: Send metadata about the message return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, messageMetadata: ({ part }) => { if (part.type === 'start') { return { createdAt: Date.now(), model: 'gpt-6-astra', }; } if (part.type === 'finish') { return { totalTokens: part.totalUsage.totalTokens, }; } }, }), }); ``` ```tsx // Client: Access metadata via message.metadata { messages.map(message => (
{message.role}:{' '} {message.metadata?.createdAt && new Date(message.metadata.createdAt).toLocaleTimeString()} {/* Render message content */} {message.parts.map((part, index) => part.type === 'text' ? {part.text} : null, )} {/* Show token count if available */} {message.metadata?.totalTokens && ( {message.metadata.totalTokens} tokens )}
)); } ``` For complete examples with type safety and advanced use cases, see the [Message Metadata documentation](/docs/ai-sdk-ui/message-metadata). ## Transport Configuration You can configure custom transport behavior using the `transport` option to customize how messages are sent to your API: ```tsx title="app/page.tsx" import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; export default function Chat() { const { messages, sendMessage } = useChat({ id: 'my-chat', transport: new DefaultChatTransport({ prepareSendMessagesRequest: ({ id, messages }) => { return { body: { id, message: messages[messages.length - 1], }, }; }, }), }); // ... rest of your component } ``` The corresponding API route receives the custom request format: ```ts title="app/api/chat/route.ts" export async function POST(req: Request) { const { id, message } = await req.json(); // Load existing messages and add the new one const messages = await loadMessages(id); messages.push(message); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ### Advanced: Trigger-based routing For more complex scenarios like message regeneration, you can use trigger-based routing: ```tsx title="app/page.tsx" import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; export default function Chat() { const { messages, sendMessage, regenerate } = useChat({ id: 'my-chat', transport: new DefaultChatTransport({ prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => { if (trigger === 'submit-user-message') { return { body: { trigger: 'submit-user-message', id, message: messages[messages.length - 1], messageId, }, }; } else if (trigger === 'regenerate-assistant-message') { return { body: { trigger: 'regenerate-assistant-message', id, messageId, }, }; } throw new Error(`Unsupported trigger: ${trigger}`); }, }), }); // ... rest of your component } ``` The corresponding API route would handle different triggers: ```ts title="app/api/chat/route.ts" export async function POST(req: Request) { const { trigger, id, message, messageId } = await req.json(); const chat = await readChat(id); let messages = chat.messages; if (trigger === 'submit-user-message') { // Handle new user message messages = [...messages, message]; } else if (trigger === 'regenerate-assistant-message') { // Handle message regeneration - remove messages after messageId const messageIndex = messages.findIndex(m => m.id === messageId); if (messageIndex !== -1) { messages = messages.slice(0, messageIndex); } } const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` To learn more about building custom transports, refer to the [Transport API documentation](/docs/ai-sdk-ui/transport). ### Direct Agent Transport For scenarios where you want to communicate directly with an Agent without going through HTTP, you can use `DirectChatTransport`. This is useful for: - Server-side rendering scenarios - Testing without network - Single-process applications ```tsx title="app/page.tsx" import { useChat } from '@ai-sdk/react'; import { DirectChatTransport, ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a helpful assistant.', }); export default function Chat() { const { messages, sendMessage, status } = useChat({ transport: new DirectChatTransport({ agent }), }); return ( <> {messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, index) => part.type === 'text' ? {part.text} : null, )}
))} ); } ``` The `DirectChatTransport` invokes the agent's `stream()` method directly, converting UI messages to model messages and streaming the response back as UI message chunks. For more details, see the [DirectChatTransport reference](/docs/reference/ai-sdk-ui/direct-chat-transport). ## Controlling the response stream With `streamText`, you can control how error messages and usage information are sent back to the client. ### Error Messages By default, the error message is masked for security reasons. The default error message is "An error occurred." Tool execution errors marked `providerExecuted: true` are an exception. They bypass the UI stream's `onError` callback: strings are forwarded unchanged, and other error values are serialized with `JSON.stringify`. This preserves provider error codes when chat history is converted back into model messages, as well as error messages from harness runtimes. Stream errors and invalid tool calls still use `onError`, even for provider-executed tools. Do not rely on this callback to redact provider-executed tool execution errors before they reach the client. You can forward error messages or send your own error message by providing an `onError` function: ```ts title="app/api/chat/route.ts" {21-35} import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage, } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, onError: error => { if (error == null) { return 'unknown error'; } if (typeof error === 'string') { return error; } if (error instanceof Error) { return error.message; } return JSON.stringify(error); }, }), }); } ``` ### Usage Information Track token consumption and resource usage with [message metadata](/docs/ai-sdk-ui/message-metadata): 1. Define a custom metadata type with usage fields (optional, for type safety) 2. Attach usage data using `messageMetadata` in your response 3. Display usage metrics in your UI components Usage data is attached as metadata to messages and becomes available once the model completes its response generation. ```ts import { openai } from '@ai-sdk/openai'; import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage, type LanguageModelUsage, } from 'ai'; // Create a new metadata type (optional for type-safety) type MyMetadata = { totalUsage: LanguageModelUsage; }; // Create a new custom message type with your own metadata export type MyUIMessage = UIMessage; export async function POST(req: Request) { const { messages }: { messages: MyUIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, originalMessages: messages, messageMetadata: ({ part }) => { // Send total usage when generation is finished if (part.type === 'finish') { return { totalUsage: part.totalUsage }; } }, }), }); } ``` Then, on the client, you can access the message-level metadata. ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import type { MyUIMessage } from './api/chat/route'; import { DefaultChatTransport } from 'ai'; export default function Chat() { // Use custom message type defined on the server (optional for type-safety) const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); return (
{messages.map(m => (
{m.role === 'user' ? 'User: ' : 'AI: '} {m.parts.map(part => { if (part.type === 'text') { return part.text; } })} {/* Render usage via metadata */} {m.metadata?.totalUsage && (
Total usage: {m.metadata?.totalUsage.totalTokens} tokens
)}
))}
); } ``` You can also access your metadata from the `onFinish` callback of `useChat`: ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import type { MyUIMessage } from './api/chat/route'; import { DefaultChatTransport } from 'ai'; export default function Chat() { // Use custom message type defined on the server (optional for type-safety) const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), onFinish: ({ message }) => { // Access message metadata via onFinish callback console.log(message.metadata?.totalUsage); }, }); } ``` ### Text Streams `useChat` can handle plain text streams by setting the `streamProtocol` option to `text`: ```tsx title="app/page.tsx" {7} 'use client'; import { useChat } from '@ai-sdk/react'; import { TextStreamChatTransport } from 'ai'; export default function Chat() { const { messages } = useChat({ transport: new TextStreamChatTransport({ api: '/api/chat', }), }); return <>...; } ``` This configuration also works with other backend servers that stream plain text. Check out the [stream protocol guide](/docs/ai-sdk-ui/stream-protocol) for more information. When using `TextStreamChatTransport`, tool calls, usage information and finish reasons are not available. ## Reasoning Some models such as DeepSeek `deepseek-r1` and Anthropic `claude-sonnet-5-5` support reasoning tokens. These tokens are typically sent before the message content. You can forward them to the client with the `sendReasoning` option: ```ts title="app/api/chat/route.ts" {20} import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage, } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: 'deepseek/deepseek-r1', messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, sendReasoning: true, }), }); } ``` On the client side, you can access the reasoning parts of the message object. Reasoning parts have a `text` property that contains the reasoning content. ```tsx title="app/page.tsx" messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, index) => { // text parts: if (part.type === 'text') { return
{part.text}
; } // reasoning parts: if (part.type === 'reasoning') { return
{part.text}
; } })}
)); ``` Some models may also produce files as part of reasoning (e.g. images). These are available as `reasoning-file` parts (`ReasoningFileUIPart`) with `mediaType` and `url` properties, similar to regular file parts. ## Sources Some providers such as [Perplexity](/providers/ai-sdk-providers/perplexity#sources) and [Google](/providers/ai-sdk-providers/google#sources) include sources in the response. You can forward sources to the client with the `sendSources` option. This example uses the Perplexity Agent API's `fast` preset to search the web. Install `@ai-sdk/perplexity` and set `PERPLEXITY_API_KEY` in your environment: ```bash pnpm add @ai-sdk/perplexity ``` ```ts title="app/api/chat/route.ts" {21} import { perplexity } from '@ai-sdk/perplexity'; import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage, } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: perplexity('fast'), messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, sendSources: true, }), }); } ``` On the client side, you can access source parts of the message object. There are two types of sources: `source-url` for web pages and `source-document` for documents. Here is an example that renders both types of sources: ```tsx title="app/page.tsx" messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {/* Render URL sources */} {message.parts .filter(part => part.type === 'source-url') .map(part => ( [ {part.title ?? new URL(part.url).hostname} ] ))} {/* Render document sources */} {message.parts .filter(part => part.type === 'source-document') .map(part => ( [{part.title ?? `Document ${part.sourceId}`}] ))}
)); ``` ## Image Generation Some models such as Google `gemini-3.1-flash-image-preview` support image generation. When images are generated, they are exposed as files to the client. On the client side, you can access file parts of the message object and render them as images. ```tsx title="app/page.tsx" messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, index) => { if (part.type === 'text') { return
{part.text}
; } else if (part.type === 'file' && part.mediaType.startsWith('image/')) { return Generated image; } })}
)); ``` ## Attachments The `useChat` hook supports sending file attachments along with a message as well as rendering them on the client. This can be useful for building applications that involve sending images, files, or other media content to the AI provider. There are two ways to send files with a message: using a `FileList` object from file inputs or using an array of file objects. ### FileList By using `FileList`, you can send multiple files as attachments along with a message using the file input element. The `useChat` hook will automatically convert them into data URLs and send them to the AI provider. Currently, only `image/*` and `text/*` content types get automatically converted into [multi-modal content parts](/docs/foundations/prompts#file-parts). You will need to handle other content types manually. ```tsx title="app/page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import { useRef, useState } from 'react'; export default function Page() { const { messages, sendMessage, status } = useChat(); const [input, setInput] = useState(''); const [files, setFiles] = useState(undefined); const fileInputRef = useRef(null); return (
{messages.map(message => (
{`${message.role}: `}
{message.parts.map((part, index) => { if (part.type === 'text') { return {part.text}; } if ( part.type === 'file' && part.mediaType?.startsWith('image/') ) { return {part.filename}; } return null; })}
))}
{ event.preventDefault(); if (input.trim()) { sendMessage({ text: input, files, }); setInput(''); setFiles(undefined); if (fileInputRef.current) { fileInputRef.current.value = ''; } } }} > { if (event.target.files) { setFiles(event.target.files); } }} multiple ref={fileInputRef} /> setInput(e.target.value)} disabled={status !== 'ready'} />
); } ``` ### File Objects You can also send files as objects along with a message. This can be useful for sending pre-uploaded files or data URLs. ```tsx title="app/page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; import { FileUIPart } from 'ai'; export default function Page() { const { messages, sendMessage, status } = useChat(); const [input, setInput] = useState(''); const [files] = useState([ { type: 'file', filename: 'earth.png', mediaType: 'image/png', url: 'https://example.com/earth.png', }, { type: 'file', filename: 'moon.png', mediaType: 'image/png', url: 'data:image/png;base64,iVBORw0KGgo...', }, ]); return (
{messages.map(message => (
{`${message.role}: `}
{message.parts.map((part, index) => { if (part.type === 'text') { return {part.text}; } if ( part.type === 'file' && part.mediaType?.startsWith('image/') ) { return {part.filename}; } return null; })}
))}
{ event.preventDefault(); if (input.trim()) { sendMessage({ text: input, files, }); setInput(''); } }} > setInput(e.target.value)} disabled={status !== 'ready'} />
); } ``` Files generated as part of model reasoning are available as `reasoning-file` parts (`ReasoningFileUIPart`) with the same `mediaType` and `url` properties. ## Type Inference for Tools When working with tools in TypeScript, AI SDK UI provides type inference helpers to ensure type safety for your tool inputs and outputs. ### InferUITool The `InferUITool` type helper infers the input and output types of a single tool for use in UI messages: ```tsx import { InferUITool } from 'ai'; import { z } from 'zod'; const weatherTool = { description: 'Get the current weather', inputSchema: z.object({ location: z.string().describe('The city and state'), }), execute: async ({ location }) => { return `The weather in ${location} is sunny.`; }, }; // Infer the types from the tool type WeatherUITool = InferUITool; // This creates a type with: // { // input: { location: string }; // output: string; // } ``` ### InferUITools The `InferUITools` type helper infers the input and output types of a `ToolSet`: ```tsx import { InferUITools, ToolSet } from 'ai'; import { z } from 'zod'; const tools = { weather: { description: 'Get the current weather', inputSchema: z.object({ location: z.string().describe('The city and state'), }), execute: async ({ location }) => { return `The weather in ${location} is sunny.`; }, }, calculator: { description: 'Perform basic arithmetic', inputSchema: z.object({ operation: z.enum(['add', 'subtract', 'multiply', 'divide']), a: z.number(), b: z.number(), }), execute: async ({ operation, a, b }) => { switch (operation) { case 'add': return a + b; case 'subtract': return a - b; case 'multiply': return a * b; case 'divide': return a / b; } }, }, } satisfies ToolSet; // Infer the types from the tool set type MyUITools = InferUITools; // This creates a type with: // { // weather: { input: { location: string }; output: string }; // calculator: { input: { operation: 'add' | 'subtract' | 'multiply' | 'divide'; a: number; b: number }; output: number }; // } ``` ### Using Inferred Types You can use these inferred types to create a custom UIMessage type and pass it to various AI SDK UI functions: ```tsx import { InferUITools, UIMessage, UIDataTypes } from 'ai'; type MyUITools = InferUITools; type MyUIMessage = UIMessage; ``` Pass the custom type to `useChat` or `createUIMessageStream`: ```tsx import { useChat } from '@ai-sdk/react'; import { createUIMessageStream } from 'ai'; import type { MyUIMessage } from './types'; // With useChat const { messages } = useChat(); // With createUIMessageStream const stream = createUIMessageStream(/* ... */); ``` This provides full type safety for tool inputs and outputs on the client and server. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Completion description: Learn how to use the useCompletion hook. url: "https://ai-sdk.dev/docs/ai-sdk-ui/completion" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `useCompletion` hook allows you to create a user interface to handle text completions in your application. It enables the streaming of text completions from your AI provider, manages the state for chat input, and updates the UI automatically as new messages are received. The `useCompletion` hook is now part of the `@ai-sdk/react` package. In this guide, you will learn how to use the `useCompletion` hook in your application to generate text completions and stream them in real-time to your users. ## Example ```tsx title='app/page.tsx' 'use client'; import { useCompletion } from '@ai-sdk/react'; export default function Page() { const { completion, input, handleInputChange, handleSubmit } = useCompletion({ api: '/api/completion', }); return (
{completion}
); } ``` ```ts title='app/api/completion/route.ts' import { createUIMessageStreamResponse, streamText, toUIMessageStream, } from 'ai'; // Allow streaming responses up to 30 seconds export const maxDuration = 30; export async function POST(req: Request) { const { prompt }: { prompt: string } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` In the `Page` component, the `useCompletion` hook will request to your AI provider endpoint whenever the user submits a message. The completion is then streamed back in real-time and displayed in the UI. This enables a seamless text completion experience where the user can see the AI response as soon as it is available, without having to wait for the entire response to be received. ## Customized UI `useCompletion` also provides ways to manage the prompt via code, show loading and error states, and update messages without being triggered by user interactions. ### Loading and error states To show a loading spinner while the chatbot is processing the user's message, you can use the `isLoading` state returned by the `useCompletion` hook: ```tsx const { isLoading, ... } = useCompletion() return( <> {isLoading ? : null} ) ``` Similarly, the `error` state reflects the error object thrown during the fetch request. It can be used to display an error message, or show a toast notification: ```tsx const { error, ... } = useCompletion() useEffect(() => { if (error) { toast.error(error.message) } }, [error]) // Or display the error message in the UI: return ( <> {error ?
{error.message}
: null} ) ``` ### Controlled input In the initial example, we have `handleSubmit` and `handleInputChange` callbacks that manage the input changes and form submissions. These are handy for common use cases, but you can also use uncontrolled APIs for more advanced scenarios such as form validation or customized components. The following example demonstrates how to use more granular APIs like `setInput` with your custom input and submit button components: ```tsx const { input, setInput } = useCompletion(); return ( <> setInput(value)} /> ); ``` ### Cancelation It's also a common use case to abort the response message while it's still streaming back from the AI provider. You can do this by calling the `stop` function returned by the `useCompletion` hook. ```tsx const { stop, isLoading, ... } = useCompletion() return ( <> ) ``` When the user clicks the "Stop" button, the fetch request will be aborted. This avoids consuming unnecessary resources and improves the UX of your application. ### Throttling UI Updates This feature is currently only available for React. By default, the `useCompletion` hook will trigger a render every time a new chunk is received. You can throttle the UI updates with the `throttle` option. ```tsx title="page.tsx" {2-3} const { completion, ... } = useCompletion({ // Throttle the completion and data updates to 50ms: throttle: 50 }) ``` ## Event Callbacks `useCompletion` also provides optional event callbacks that you can use to handle different stages of the chatbot lifecycle. These callbacks can be used to trigger additional actions, such as logging, analytics, or custom UI updates. ```tsx const { ... } = useCompletion({ onFinish: (prompt: string, completion: string) => { console.log('Finished streaming completion:', completion) }, onError: (error: Error) => { console.error('An error occurred:', error) }, }) ``` ## Configure Request Options By default, the `useCompletion` hook sends an HTTP POST request to the `/api/completion` endpoint with the prompt as part of the request body. You can customize the request by passing additional options to the `useCompletion` hook: ```tsx const { messages, input, handleInputChange, handleSubmit } = useCompletion({ api: '/api/custom-completion', headers: { Authorization: 'your_token', }, body: { user_id: '123', }, credentials: 'same-origin', }); ``` In this example, the `useCompletion` hook sends a POST request to the `/api/custom-completion` endpoint with the specified headers, additional body fields, and credentials for that fetch request. On your server side, you can handle the request with these additional information. You can provide a type for the additional body fields. The type applies to both the body configured on the hook and the body passed to `complete`: ```tsx type CompletionBody = { model: 'fast' | 'smart'; }; const { complete } = useCompletion({ body: { model: 'fast', }, }); await complete('What is a completion?', { body: { model: 'smart', }, }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Error Handling description: Learn how to handle errors in the AI SDK UI url: "https://ai-sdk.dev/docs/ai-sdk-ui/error-handling" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Warnings The AI SDK shows warnings when something might not work as expected. These warnings help you fix problems before they cause errors. ### When Warnings Appear Warnings use Node.js `process.emitWarning` when available and `console.warn` in browser/edge runtimes. They appear when: - **Unsupported features**: You use a feature or setting that is not supported by the AI model (e.g., certain options or parameters). - **Compatibility warnings**: A feature is used in a compatibility mode, which might work differently or less optimally than intended. - **Deprecations**: You use a deprecated API, option, or message format. - **Other warnings**: The AI model reports another type of issue, such as general problems or advisory messages. ### Warning Messages All warnings include "AI SDK Warning" so you can easily find them. For example: ``` AI SDK Warning: The feature "temperature" is not supported by this model ``` ### Turning Off Warnings By default, warnings are shown in the console. You can control this behavior: #### Turn Off All Warnings Set a global variable to turn off warnings completely: ```ts globalThis.AI_SDK_LOG_WARNINGS = false; ``` #### Custom Warning Handler You can also provide your own function to handle warnings. It receives a list of warnings and optional provider and model ids. Custom handlers receive every warning, including repeated deprecations. ```ts globalThis.AI_SDK_LOG_WARNINGS = ({ warnings, provider, model }) => { // Handle warnings your own way }; ``` If your application loads multiple AI SDK versions in the same runtime, they all share `globalThis.AI_SDK_LOG_WARNINGS`. Each version calls the logger with its own arguments and warning types. The merged global type declarations prevent TypeScript declaration conflicts, but do not convert these arguments at runtime. A handler typed for only one version may therefore fail when another version calls it. Import `LogWarningsFunction` from every installed version that can call the logger and accept a union of their parameter types. For example, with AI SDK 7 installed as `ai` and AI SDK 6 installed under the package alias `ai-v6` (`npm:ai@6`): ```ts import type { LogWarningsFunction as LogWarningsFunctionV7 } from 'ai'; import type { LogWarningsFunction as LogWarningsFunctionV6 } from 'ai-v6'; type LoggerOptions = | Parameters[0] | Parameters[0]; const logger = ({ warnings, provider, model }: LoggerOptions) => { for (const warning of warnings) { console.warn('AI SDK Warning:', warning, { provider, model }); } }; globalThis.AI_SDK_LOG_WARNINGS = logger; ``` Check the argument shape and warning type before accessing version-specific fields. If AI SDK 5 is also installed, include its `Experimental_LogWarningsFunction` parameter type and use `Array.isArray` to handle its warning array before accessing the options object used by AI SDK 6 and 7. ### Deprecation Warnings The default logger emits each deprecation code once per loaded SDK instance. In Node.js, these warnings have the type `DeprecationWarning` and a stable `AISDK_DEP_*` code, available as `warning.code` on `process.on('warning', handler)`. Browser/edge console messages include the code in brackets. | Code | Deprecated usage / replacement | | ---------------------------------------- | -------------------------------------------------------------------------- | | `AISDK_DEP_GENERATE_OBJECT` | Use `generateText` with `output` instead of `generateObject`. | | `AISDK_DEP_STREAM_OBJECT` | Use `streamText` with `output` instead of `streamObject`. | | `AISDK_DEP_EXPERIMENTAL_GENERATE_SPEECH` | Use `generateSpeech` instead of `experimental_generateSpeech`. | | `AISDK_DEP_EXPERIMENTAL_TRANSCRIBE` | Use `transcribe` instead of `experimental_transcribe`. | | `AISDK_DEP_TOOL_RESULT_*` | Use `file` with tagged `data` instead of legacy tool-result content types. | | `AISDK_DEP_UI_MESSAGE_RAW_INPUT` | Use `input` instead of `rawInput` in UI tool parts. | | `AISDK_DEP_IMAGE_CONTENT_PART` | Use `file` with an image `mediaType` instead of `image` message parts. | Run Node.js with `--throw-deprecation` to fail on deprecated usage, `--no-deprecation` to suppress deprecations, or `--trace-deprecation` for stack traces. These flags affect all `DeprecationWarning`s, rather than individual codes, and apply only to the default Node.js logger. ## Error Handling ### Error Helper Object Each AI SDK UI hook also returns an [error](/docs/reference/ai-sdk-ui/use-chat#error) object that you can use to render the error in your UI. You can use the error object to show an error message, disable the submit button, or show a retry button. We recommend showing a generic error message to the user, such as "Something went wrong." This is a good practice to avoid leaking information from the server. ```tsx title="app/page.tsx" {8,28-35,41} 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage, error, regenerate } = useChat(); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }; return (
{messages.map(m => (
{m.role}:{' '} {m.parts .filter(part => part.type === 'text') .map(part => part.text) .join('')}
))} {error && ( <>
An error occurred.
)}
setInput(e.target.value)} disabled={error != null} />
); } ``` #### Alternative: replace the failed message Alternatively, you can write a custom submit handler that replaces the failed user message with new input. If the assistant response started streaming before the error, remove both the partial assistant response and its user message. ```tsx title="app/page.tsx" {13-19,21-22,39} 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Chat() { const [input, setInput] = useState(''); const { sendMessage, error, messages, setMessages } = useChat(); function customSubmit(event: React.FormEvent) { event.preventDefault(); if (error != null) { setMessages(messages => messages.at(-1)?.role === 'assistant' ? messages.slice(0, -2) : messages.slice(0, -1), ); } sendMessage({ text: input }); setInput(''); } return (
{messages.map(m => (
{m.role}:{' '} {m.parts .filter(part => part.type === 'text') .map(part => part.text) .join('')}
))} {error &&
An error occurred.
}
setInput(e.target.value)} />
); } ``` ### Error Handling Callback Errors can be processed by passing an [`onError`](/docs/reference/ai-sdk-ui/use-chat#on-error) callback function as an option to the [`useChat`](/docs/reference/ai-sdk-ui/use-chat) or [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion) hooks. The callback function receives an error object as an argument. AI SDK-created client errors use exported error classes with marker-based `.isInstance()` guards: | Error class | AI SDK UI failure | | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | [`APICallError`](/docs/reference/ai-sdk-errors/ai-api-call-error) | A chat transport or completion request returns a non-successful HTTP response. | | [`EmptyResponseBodyError`](/docs/reference/ai-sdk-errors/ai-empty-response-body-error) | A successful chat transport or completion response has no body. | | [`UIMessageStreamError`](/docs/reference/ai-sdk-errors/ai-ui-message-stream-error) | A completion data stream reports an error or a UI message stream contains invalid chunks. | | [`InvalidArgumentError`](/docs/reference/ai-sdk-errors/ai-invalid-argument-error) | An invalid stream protocol or message ID is used. | | [`UnsupportedFunctionalityError`](/docs/reference/ai-sdk-errors/ai-unsupported-functionality-error) | A `FileList` is used in an environment that does not support it. | Errors thrown by custom fetch implementations, callbacks, and stream parsers continue to propagate unchanged. ```tsx title="app/page.tsx" {2,9-15} import { useChat } from '@ai-sdk/react'; import { APICallError, EmptyResponseBodyError } from 'ai'; export default function Page() { const { /* ... */ } = useChat({ // handle error: onError: error => { if (APICallError.isInstance(error)) { console.error('Request failed with status:', error.statusCode); } else if (EmptyResponseBodyError.isInstance(error)) { console.error('The server returned no response body.'); } else { console.error(error); } }, }); } ``` For AI SDK UI requests, `APICallError.requestBodyValues` is `undefined` so prompts and messages are not copied into client-facing error objects. The response text remains available as `message` and `responseBody`; display a generic message to users to avoid leaking server information. ### Injecting Errors for Testing You might want to create errors for testing. You can easily do so by throwing an error in your route handler: ```ts title="app/api/chat/route.ts" export async function POST(req: Request) { throw new Error('This is a test error'); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Generative User Interfaces description: Learn how to build Generative UI with AI SDK UI. url: "https://ai-sdk.dev/docs/ai-sdk-ui/generative-user-interfaces" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Generative user interfaces (generative UI) is the process of allowing a large language model (LLM) to go beyond text and "generate UI". This creates a more engaging and AI-native experience for users. At the core of generative UI are [ tools ](/docs/ai-sdk-core/tools-and-tool-calling), which are functions you provide to the model to perform specialized tasks like getting the weather in a location. The model can decide when and how to use these tools based on the context of the conversation. Generative UI is the process of connecting the results of a tool call to a React component. Here's how it works: 1. You provide the model with a prompt or conversation history, along with a set of tools. 2. Based on the context, the model may decide to call a tool. 3. If a tool is called, it will execute and return data. 4. This data can then be passed to a React component for rendering. By passing the tool results to React components, you can create a generative UI experience that's more engaging and adaptive to your needs. ## Build a Generative UI Chat Interface Let's create a chat interface that handles text-based conversations and incorporates dynamic UI elements based on model responses. ### Basic Chat Implementation Start with a basic chat implementation using the `useChat` hook: ```tsx title="app/page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Page() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }; return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '}
{message.parts.map((part, index) => { if (part.type === 'text') { return {part.text}; } return null; })}
))}
setInput(e.target.value)} placeholder="Type a message..." />
); } ``` To handle the chat requests and model responses, set up an API route: ```ts title="app/api/chat/route.ts" import { convertToModelMessages, createUIMessageStreamResponse, isStepCount, streamText, toUIMessageStream, UIMessage, } from 'ai'; export async function POST(request: Request) { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a friendly assistant!', messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` This API route uses the `streamText` function to process chat messages and stream the model's responses back to the client. ### Create a Tool Before enhancing your chat interface with dynamic UI elements, you need to create a tool and corresponding React component. A tool will allow the model to perform a specific action, such as fetching weather information. Create a new file called `ai/tools.ts` with the following content: ```ts title="ai/tools.ts" import { tool as createTool } from 'ai'; import { z } from 'zod'; export const weatherTool = createTool({ description: 'Display the weather for a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async function ({ location }) { await new Promise(resolve => setTimeout(resolve, 2000)); return { weather: 'Sunny', temperature: 75, location }; }, }); export const tools = { displayWeather: weatherTool, }; ``` In this file, you've created a tool called `weatherTool`. This tool simulates fetching weather information for a given location. This tool will return simulated data after a 2-second delay. In a real-world application, you would replace this simulation with an actual API call to a weather service. ### Update the API Route Update the API route to include the tool you've defined: ```ts title="app/api/chat/route.ts" {10,15,20} import { convertToModelMessages, createUIMessageStreamResponse, isStepCount, streamText, toUIMessageStream, UIMessage, } from 'ai'; import { tools } from '@/ai/tools'; export async function POST(request: Request) { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a friendly assistant!', messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` Now that you've defined the tool and added it to your `streamText` call, let's build a React component to display the weather information it returns. ### Create UI Components Create a new file called `components/weather.tsx`: ```tsx title="components/weather.tsx" type WeatherProps = { temperature: number; weather: string; location: string; }; export const Weather = ({ temperature, weather, location }: WeatherProps) => { return (

Current Weather for {location}

Condition: {weather}

Temperature: {temperature}°C

); }; ``` This component will display the weather information for a given location. It takes three props: `temperature`, `weather`, and `location` (exactly what the `weatherTool` returns). ### Render the Weather Component Now that you have your tool and corresponding React component, let's integrate them into your chat interface. You'll render the Weather component when the model calls the weather tool. To check if the model has called a tool, you can check the `parts` array of the UIMessage object for tool-specific parts. In AI SDK 5.0, tool parts use typed naming: `tool-${toolName}` instead of generic types. Update your `page.tsx` file: ```tsx title="app/page.tsx" {4,9,14-15,19-46} 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; import { Weather } from '@/components/weather'; export default function Page() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }; return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '}
{message.parts.map((part, index) => { if (part.type === 'text') { return {part.text}; } if (part.type === 'tool-displayWeather') { switch (part.state) { case 'input-available': return
Loading weather...
; case 'output-available': return (
); case 'output-error': return
Error: {part.errorText}
; default: return null; } } return null; })}
))}
setInput(e.target.value)} placeholder="Type a message..." />
); } ``` In this updated code snippet, you: 1. Use manual input state management with `useState` instead of the built-in `input` and `handleInputChange`. 2. Use `sendMessage` instead of `handleSubmit` to send messages. 3. Check the `parts` array of each message for different content types. 4. Handle tool parts with type `tool-displayWeather` and their different states (`input-available`, `output-available`, `output-error`). This approach allows you to dynamically render UI components based on the model's responses, creating a more interactive and context-aware chat experience. ## Expanding Your Generative UI Application You can enhance your chat application by adding more tools and components, creating a richer and more versatile user experience. Here's how you can expand your application: ### Adding More Tools To add more tools, simply define them in your `ai/tools.ts` file: ```ts // Add a new stock tool export const stockTool = createTool({ description: 'Get price for a stock', inputSchema: z.object({ symbol: z.string().describe('The stock symbol to get the price for'), }), execute: async function ({ symbol }) { // Simulated API call await new Promise(resolve => setTimeout(resolve, 2000)); return { symbol, price: 100 }; }, }); // Update the tools object export const tools = { displayWeather: weatherTool, getStockPrice: stockTool, }; ``` Now, create a new file called `components/stock.tsx`: ```tsx type StockProps = { price: number; symbol: string; }; export const Stock = ({ price, symbol }: StockProps) => { return (

Stock Information

Symbol: {symbol}

Price: ${price}

); }; ``` Finally, update your `page.tsx` file to include the new Stock component: ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; import { Weather } from '@/components/weather'; import { Stock } from '@/components/stock'; export default function Page() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }; return (
{messages.map(message => (
{message.role}
{message.parts.map((part, index) => { if (part.type === 'text') { return {part.text}; } if (part.type === 'tool-displayWeather') { switch (part.state) { case 'input-available': return
Loading weather...
; case 'output-available': return (
); case 'output-error': return
Error: {part.errorText}
; default: return null; } } if (part.type === 'tool-getStockPrice') { switch (part.state) { case 'input-available': return
Loading stock price...
; case 'output-available': return (
); case 'output-error': return
Error: {part.errorText}
; default: return null; } } return null; })}
))}
setInput(e.target.value)} />
); } ``` By following this pattern, you can continue to add more tools and components, expanding the capabilities of your Generative UI application. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Message Metadata description: Learn how to attach and use metadata with messages in AI SDK UI url: "https://ai-sdk.dev/docs/ai-sdk-ui/message-metadata" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Message metadata allows you to attach custom information to messages at the message level. This is useful for tracking timestamps, model information, token usage, user context, and other message-level data. ## Overview Message metadata differs from [data parts](/docs/ai-sdk-ui/streaming-data) in that it's attached at the message level rather than being part of the message content. While data parts are ideal for dynamic content that forms part of the message, metadata is perfect for information about the message itself. ## Getting Started Here's a simple example of using message metadata to track timestamps and model information: ### Defining Metadata Types First, define your metadata type for type safety: ```tsx title="app/types.ts" import { UIMessage } from 'ai'; import { z } from 'zod'; // Define your metadata schema export const messageMetadataSchema = z.object({ createdAt: z.number().optional(), model: z.string().optional(), totalTokens: z.number().optional(), }); export type MessageMetadata = z.infer; // Create a typed UIMessage export type MyUIMessage = UIMessage; ``` ### Sending Metadata from the Server Use the `messageMetadata` callback in `toUIMessageStream` to send metadata at different streaming stages: ```ts title="app/api/chat/route.ts" {21-29,31-37} import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, } from 'ai'; import type { MyUIMessage } from '@/types'; export async function POST(req: Request) { const { messages }: { messages: MyUIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, originalMessages: messages, // pass this in for type-safe return objects messageMetadata: ({ part }) => { // Send metadata when streaming starts if (part.type === 'start') { return { createdAt: Date.now(), model: 'your-model-id', }; } // Send additional metadata when streaming completes if (part.type === 'finish') { return { totalTokens: part.totalUsage.totalTokens, }; } }, }), }); } ``` To enable type-safe metadata return object in `messageMetadata`, pass in the `originalMessages` parameter typed to your UIMessage type. ### Accessing Metadata on the Client Access metadata through the `message.metadata` property: ```tsx title="app/page.tsx" {8,20-23,33-36} 'use client'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import type { MyUIMessage } from '@/types'; export default function Chat() { const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.metadata?.createdAt && ( {new Date(message.metadata.createdAt).toLocaleTimeString()} )}
{/* Render message content */} {message.parts.map((part, index) => part.type === 'text' ?
{part.text}
: null, )} {/* Display additional metadata */} {message.metadata?.totalTokens && (
{message.metadata.totalTokens} tokens
)}
))}
); } ``` For streaming arbitrary data that changes during generation, consider using [data parts](/docs/ai-sdk-ui/streaming-data) instead. ## Common Use Cases Message metadata is ideal for: - **Timestamps**: When messages were created or completed - **Model Information**: Which AI model was used - **Token Usage**: Track costs and usage limits - **User Context**: User IDs, session information - **Performance Metrics**: Generation time, time to first token - **Quality Indicators**: Finish reason, confidence scores ## See Also - [Chatbot Guide](/docs/ai-sdk-ui/chatbot#message-metadata) - Message metadata in the context of building chatbots - [Streaming Data](/docs/ai-sdk-ui/streaming-data#message-metadata-vs-data-parts) - Comparison with data parts - [UIMessage Reference](/docs/reference/ai-sdk-core/ui-message) - Complete UIMessage type reference --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Object Generation description: Learn how to use the useObject hook. url: "https://ai-sdk.dev/docs/ai-sdk-ui/object-generation" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `useObject` is available in React, Svelte, Vue, and the SolidJS community integration. Angular provides the equivalent `StructuredObject` service. The [`useObject`](/docs/reference/ai-sdk-ui/use-object) hook allows you to create interfaces that represent a structured JSON object that is being streamed. In this guide, you will learn how to use the `useObject` hook in your application to generate UIs for structured data on the fly. ## Example The example shows a small notifications demo app that generates fake notifications in real-time. ### Schema It is helpful to set up the schema in a separate file that is imported on both the client and server. ```ts title='app/api/notifications/schema.ts' import { z } from 'zod'; // define a schema for the notifications export const notificationSchema = z.object({ notifications: z.array( z.object({ name: z.string().describe('Name of a fictional person.'), message: z.string().describe('Message. Do not use emojis or links.'), }), ), }); ``` ### Client The client uses [`useObject`](/docs/reference/ai-sdk-ui/use-object) to stream the object generation process. The results are partial and are displayed as they are received. Please note the code for handling `undefined` values in the JSX. ```tsx title='app/page.tsx' 'use client'; import { useObject } from '@ai-sdk/react'; import { notificationSchema } from './api/notifications/schema'; export default function Page() { const { object, submit } = useObject({ api: '/api/notifications', schema: notificationSchema, }); return ( <> {object?.notifications?.map((notification, index) => (

{notification?.name}

{notification?.message}

))} ); } ``` ### Server On the server, we use [`streamText`](/docs/reference/ai-sdk-core/stream-text) with [`Output.object()`](/docs/reference/ai-sdk-core/output#output-object) to stream the object generation process. ```typescript title='app/api/notifications/route.ts' import { createTextStreamResponse, Output, streamText, toTextStream } from 'ai'; import { notificationSchema } from './schema'; // Allow streaming responses up to 30 seconds export const maxDuration = 30; export async function POST(req: Request) { const context = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: notificationSchema }), prompt: `Generate 3 notifications for a messages app in this context:` + context, }); return createTextStreamResponse({ stream: toTextStream({ stream: result.stream }), }); } ``` ## Enum Output Mode When you need to classify or categorize input into predefined options, you can use the `enum` output mode with `useObject`. This requires a specific schema structure where the object has `enum` as a key with `z.enum` containing your possible values. ### Example: Text Classification This example shows how to build a simple text classifier that categorizes statements as true or false. #### Client When using `useObject` with enum output mode, your schema must be an object with `enum` as the key: ```tsx title='app/classify/page.tsx' 'use client'; import { useObject } from '@ai-sdk/react'; import { z } from 'zod'; export default function ClassifyPage() { const { object, submit, isLoading } = useObject({ api: '/api/classify', schema: z.object({ enum: z.enum(['true', 'false']) }), }); return ( <> {object &&
Classification: {object.enum}
} ); } ``` #### Server On the server, use `streamText` with `Output.choice()` to stream the classification result: ```typescript title='app/api/classify/route.ts' import { createTextStreamResponse, Output, streamText, toTextStream } from 'ai'; export async function POST(req: Request) { const context = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", output: Output.choice({ options: ['true', 'false'] }), prompt: `Classify this statement as true or false: ${context}`, }); return createTextStreamResponse({ stream: toTextStream({ stream: result.stream }), }); } ``` ## Customized UI `useObject` also provides ways to show loading and error states: ### Loading State The `isLoading` state returned by the `useObject` hook can be used for several purposes: - To show a loading spinner while the object is generated. - To disable the submit button. ```tsx title='app/page.tsx' {6,13-20,24} 'use client'; import { useObject } from '@ai-sdk/react'; export default function Page() { const { isLoading, object, submit } = useObject({ api: '/api/notifications', schema: notificationSchema, }); return ( <> {isLoading && } {object?.notifications?.map((notification, index) => (

{notification?.name}

{notification?.message}

))} ); } ``` ### Stop Handler The `stop` function can be used to stop the object generation process. This can be useful if the user wants to cancel the request or if the server is taking too long to respond. ```tsx title='app/page.tsx' {6,14-16} 'use client'; import { useObject } from '@ai-sdk/react'; export default function Page() { const { isLoading, stop, object, submit } = useObject({ api: '/api/notifications', schema: notificationSchema, }); return ( <> {isLoading && ( )} {object?.notifications?.map((notification, index) => (

{notification?.name}

{notification?.message}

))} ); } ``` ### Error State Similarly, the `error` state reflects the error object thrown during the fetch request. It can be used to display an error message, or to disable the submit button: We recommend showing a generic error message to the user, such as "Something went wrong." This is a good practice to avoid leaking information from the server. ```tsx title="app/page.tsx" {6,13} 'use client'; import { useObject } from '@ai-sdk/react'; export default function Page() { const { error, object, submit } = useObject({ api: '/api/notifications', schema: notificationSchema, }); return ( <> {error &&
An error occurred.
} {object?.notifications?.map((notification, index) => (

{notification?.name}

{notification?.message}

))} ); } ``` ## Event Callbacks `useObject` provides optional event callbacks that you can use to handle life-cycle events. - `onFinish`: Called when the object generation is completed. - `onError`: Called when an error occurs during the fetch request. These callbacks can be used to trigger additional actions, such as logging, analytics, or custom UI updates. ```tsx title='app/page.tsx' {10-20} 'use client'; import { useObject } from '@ai-sdk/react'; import { notificationSchema } from './api/notifications/schema'; export default function Page() { const { object, submit } = useObject({ api: '/api/notifications', schema: notificationSchema, onFinish({ object, error }) { // typed object, undefined if schema validation fails: console.log('Object generation completed:', object); // error, undefined if schema validation succeeds: console.log('Schema validation error:', error); }, onError(error) { // error during fetch request: console.error('An error occurred:', error); }, }); return (
{object?.notifications?.map((notification, index) => (

{notification?.name}

{notification?.message}

))}
); } ``` ## Configure Request Options You can configure the API endpoint, optional headers and credentials using the `api`, `headers` and `credentials` settings. ```tsx {2-5} const { submit, object } = useObject({ api: '/api/use-object', headers: { 'X-Custom-Header': 'CustomValue', }, credentials: 'include', schema: yourSchema, }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Overview description: An overview of AI SDK UI. url: "https://ai-sdk.dev/docs/ai-sdk-ui/overview" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK UI is designed to help you build interactive chat, completion, and assistant applications with ease. It is a **framework-agnostic toolkit**, streamlining the integration of advanced AI functionalities into your applications. AI SDK UI provides robust abstractions that simplify the complex tasks of managing chat streams and UI updates on the frontend, enabling you to develop dynamic AI-driven interfaces more efficiently. With three main hooks — **`useChat`**, **`useCompletion`**, and **`useObject`** — you can incorporate real-time chat capabilities, text completions, streamed JSON, and interactive assistant features into your app. - **[`useChat`](/docs/ai-sdk-ui/chatbot)** offers real-time streaming of chat messages and manages message, loading, and error state. Manage input state in your component for seamless integration into any UI design. - **[`useCompletion`](/docs/ai-sdk-ui/completion)** enables you to handle text completions in your applications, managing the prompt input and automatically updating the UI as new completions are streamed. - **[`useObject`](/docs/ai-sdk-ui/object-generation)** is a hook that allows you to consume streamed JSON objects, providing a simple way to handle and display structured data in your application. These hooks are designed to reduce the complexity and time required to implement AI interactions, letting you focus on creating exceptional user experiences. ## UI Framework Support AI SDK UI supports the following frameworks: [React](https://react.dev/), [Svelte](https://svelte.dev/), [Vue.js](https://vuejs.org/), [Angular](https://angular.dev/), and [SolidJS](https://www.solidjs.com/). Here is a comparison of the supported functions across these frameworks: | | [useChat](/docs/reference/ai-sdk-ui/use-chat) | [useCompletion](/docs/reference/ai-sdk-ui/use-completion) | [useObject](/docs/reference/ai-sdk-ui/use-object) | [MCP Apps](/docs/ai-sdk-core/mcp-apps) | | --------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------- | -------------------------------------- | | React `@ai-sdk/react` | ✓ | ✓ | ✓ | ✓ | | Vue.js `@ai-sdk/vue` | ✓ | ✓ | ✓ | ✗ | | Svelte `@ai-sdk/svelte` | ✓ Chat | ✓ Completion | ✓ StructuredObject | ✗ | | Angular `@ai-sdk/angular` | ✓ Chat | ✓ Completion | ✓ StructuredObject | ✗ | | [SolidJS](https://github.com/kodehort/ai-sdk-solid) (community) | ✓ | ✓ | ✓ | ✗ | ## Framework Examples Explore these example implementations for different frameworks: - [**Next.js**](https://github.com/vercel/ai/tree/main/examples/ai-e2e-next) - [**Nuxt**](https://github.com/vercel/ai/tree/main/examples/nuxt-openai) - [**SvelteKit**](https://github.com/vercel/ai/tree/main/examples/sveltekit-openai) - [**Angular**](https://github.com/vercel/ai/tree/main/examples/angular) ## API Reference Please check out the [AI SDK UI API Reference](/docs/reference/ai-sdk-ui) for more details on each function. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Reading UIMessage Streams description: Learn how to read UIMessage streams. url: "https://ai-sdk.dev/docs/ai-sdk-ui/reading-ui-message-streams" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `UIMessage` streams are useful outside of traditional chat use cases. You can consume them for terminal UIs, custom stream processing on the client, or React Server Components (RSC). The `readUIMessageStream` helper transforms a stream of `UIMessageChunk` objects into an `AsyncIterableStream` of `UIMessage` objects, allowing you to process messages as they're being constructed. ## Basic Usage ```tsx import { readUIMessageStream, streamText, toUIMessageStream } from 'ai'; async function main() { const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a short story about a robot.', }); for await (const uiMessage of readUIMessageStream({ stream: toUIMessageStream({ stream: result.stream }), })) { console.log('Current message state:', uiMessage); } } ``` ## Tool Calls Integration Handle streaming responses that include tool calls: ```tsx import { readUIMessageStream, streamText, toUIMessageStream, tool } from 'ai'; import { z } from 'zod'; async function handleToolCalls() { const result = streamText({ model: "anthropic/claude-sonnet-5.5", tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: ({ location }) => ({ location, temperature: 72 + Math.floor(Math.random() * 21) - 10, }), }), }, prompt: 'What is the weather in Tokyo?', }); for await (const uiMessage of readUIMessageStream({ stream: toUIMessageStream({ stream: result.stream }), })) { // Handle different part types uiMessage.parts.forEach(part => { switch (part.type) { case 'text': console.log('Text:', part.text); break; case 'tool-call': console.log('Tool called:', part.toolName, 'with args:', part.args); break; case 'tool-result': console.log('Tool result:', part.result); break; } }); } } ``` ## Resuming Conversations Resume streaming from a previous message state: ```tsx import { readUIMessageStream, streamText, toUIMessageStream } from 'ai'; async function resumeConversation(lastMessage: UIMessage) { const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: [ { role: 'user', content: 'Continue our previous conversation.' }, ], }); // Resume from the last message for await (const uiMessage of readUIMessageStream({ stream: toUIMessageStream({ stream: result.stream }), message: lastMessage, // Resume from this message })) { console.log('Resumed message:', uiMessage); } } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Stream Protocols description: Learn more about the supported stream protocols in the AI SDK. url: "https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK UI functions such as `useChat` and `useCompletion` support both text streams and data streams. The stream protocol defines how the data is streamed to the frontend on top of the HTTP protocol. This page describes both protocols and how to use them in the backend and frontend. You can use this information to develop custom backends and frontends for your use case, e.g., to provide compatible API endpoints that are implemented in a different language such as Python. For instance, here's an example using [FastAPI](https://github.com/vercel/ai/tree/main/examples/next-fastapi) as a backend. ## Text Stream Protocol A text stream contains chunks in plain text, that are streamed to the frontend. Each chunk is then appended together to form a full text response. Text streams are supported by `useChat`, `useCompletion`, and `useObject`. To enable text streaming, use `TextStreamChatTransport` with `useChat`, or set the `streamProtocol` option to `text` with `useCompletion`. You can generate text streams with `streamText` in the backend. Pass the result's `stream` to `toTextStream` and return it with `createTextStreamResponse` to create a streaming HTTP response. Text streams only support basic text data. If you need to stream other types of data such as tool calls, use data streams. ### Text Stream Example Here is a Next.js example that uses the text stream protocol: ```tsx title='app/page.tsx' 'use client'; import { useChat } from '@ai-sdk/react'; import { TextStreamChatTransport } from 'ai'; import { useState } from 'react'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat({ transport: new TextStreamChatTransport({ api: '/api/chat' }), }); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, i) => { switch (part.type) { case 'text': return
{part.text}
; } })}
))}
{ e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.currentTarget.value)} />
); } ``` ```ts title='app/api/chat/route.ts' import { convertToModelMessages, createTextStreamResponse, streamText, toTextStream, UIMessage, } from 'ai'; // Allow streaming responses up to 30 seconds export const maxDuration = 30; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createTextStreamResponse({ stream: toTextStream({ stream: result.stream }), }); } ``` ## Data Stream Protocol A data stream follows a special protocol that the AI SDK provides to send information to the frontend. The data stream protocol uses Server-Sent Events (SSE) format for improved standardization, keep-alive through ping, reconnect capabilities, and better cache handling. When you provide data streams from a custom backend, you need to set the `x-vercel-ai-ui-message-stream` header to `v1`. The following stream parts are currently supported: ### Message Start Part Indicates the beginning of a new message with metadata. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"start","messageId":"..."} ``` ### Text Parts Text content is streamed using a start/delta/end pattern with unique IDs for each text block. #### Text Start Part Indicates the beginning of a text block. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"text-start","id":"msg_68679a454370819ca74c8eb3d04379630dd1afb72306ca5d"} ``` #### Text Delta Part Contains incremental text content for the text block. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"text-delta","id":"msg_68679a454370819ca74c8eb3d04379630dd1afb72306ca5d","delta":"Hello"} ``` #### Text End Part Indicates the completion of a text block. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"text-end","id":"msg_68679a454370819ca74c8eb3d04379630dd1afb72306ca5d"} ``` ### Reasoning Parts Reasoning content is streamed using a start/delta/end pattern with unique IDs for each reasoning block. #### Reasoning Start Part Indicates the beginning of a reasoning block. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"reasoning-start","id":"reasoning_123"} ``` #### Reasoning Delta Part Contains incremental reasoning content for the reasoning block. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"reasoning-delta","id":"reasoning_123","delta":"This is some reasoning"} ``` #### Reasoning End Part Indicates the completion of a reasoning block. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"reasoning-end","id":"reasoning_123"} ``` ### Reasoning File Part Reasoning file parts contain references to files generated as part of reasoning, such as images produced during the reasoning process. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"reasoning-file","url":"data:image/png;base64,iVBOR...","mediaType":"image/png"} ``` ### Source Parts Source parts provide references to external content sources. #### Source URL Part References to external URLs. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"source-url","sourceId":"https://example.com","url":"https://example.com"} ``` #### Source Document Part References to documents or files. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"source-document","sourceId":"https://example.com","mediaType":"file","title":"Title"} ``` ### File Part The file parts contain references to files with their media type. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"file","url":"https://example.com/file.png","mediaType":"image/png"} ``` ### Custom Part Custom parts represent provider-specific content that doesn't fit into the standard part types. The `kind` field identifies the specific custom content type in the format `{provider}.{provider-type}`. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"custom","kind":"openai.compaction","providerMetadata":{"openai":{"itemId":"cmp_123"}}} ``` ### Data Parts Custom data parts allow streaming of arbitrary structured data with type-specific handling. Format: Server-Sent Event with JSON object where the type includes a custom suffix Example: ``` data: {"type":"data-weather","data":{"location":"SF","temperature":100}} ``` The `data-*` type pattern allows you to define custom data types that your frontend can handle specifically. ### Error Part The error parts are appended to the message as they are received. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"error","errorText":"error message"} ``` ### Tool Input Start Part Indicates the beginning of tool input streaming. The optional `inputEncoding` field specifies how to interpret subsequent input deltas. When omitted or set to `'json'`, the UI parses the accumulated text as partial JSON. With `'text'`, the UI uses the accumulated text directly as the string input. Use `'text'` for raw custom-tool input such as code or HTML. The encoding is also retained on persisted `input-streaming` tool parts so streaming can resume correctly. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"tool-input-start","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","toolName":"getWeatherInformation"} ``` ### Tool Input Delta Part Incremental chunks of tool input as it's being generated. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"tool-input-delta","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","inputTextDelta":"San Francisco"} ``` ### Tool Input Available Part Indicates that tool input is complete and ready for execution. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"tool-input-available","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","toolName":"getWeatherInformation","input":{"city":"San Francisco"}} ``` ### Tool Approval Request Part Indicates that a tool call requires approval, or records that the approval decision was made automatically. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"tool-approval-request","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","approvalId":"approval_123","approvalDescriptor":{"scope":"account:delete"},"reason":"Requires operator review"} ``` When `isAutomatic` is omitted, the request expects an explicit approval response from the client. `reason` is optional and explains why the tool call requires approval. `approvalDescriptor` is optional opaque metadata for the approval. When the stream is processed into UI messages, it is available as `part.approval.descriptor` and is retained through subsequent approval states. ### Tool Approval Response Part Records the approval decision for a tool call. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"tool-approval-response","approvalId":"approval_123","approved":false,"reason":"User denied the request"} ``` For provider-executed tools, the response can also include `providerExecuted: true`. ### Tool Output Available Part Contains the result of tool execution. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"tool-output-available","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar","output":{"city":"San Francisco","weather":"sunny"}} ``` ### Tool Output Denied Part Indicates that tool execution was denied after the approval flow completed. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"tool-output-denied","toolCallId":"call_fJdQDqnXeGxTmr4E3YPSR7Ar"} ``` ### Start Step Part A part indicating the start of a step. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"start-step"} ``` ### Finish Step Part A part indicating that a step (i.e., one LLM API call in the backend) has been completed. This part is necessary to correctly process multiple stitched assistant calls, e.g. when calling tools in the backend, and using steps in `useChat` at the same time. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"finish-step"} ``` ### Reset Step Part Removes all message parts received since the most recent `start-step` part. If there is no step boundary, it removes all parts from the current message. This is useful when a streamed step is retried and partial output from the failed attempt must be invalidated before replacement output is sent. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"reset-step"} ``` ### Finish Message Part A part indicating the completion of a message. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"finish"} ``` ### Abort Part Indicates the stream was aborted. Format: Server-Sent Event with JSON object Example: ``` data: {"type":"abort","reason":"user cancelled"} ``` ### Stream Termination The stream ends with a special `[DONE]` marker. Format: Server-Sent Event with literal `[DONE]` Example: ``` data: [DONE] ``` The data stream protocol is supported by `useChat` and `useCompletion` on the frontend and used by default. `useCompletion` only supports the `text` and `data` stream parts. On the backend, you can pass the `streamText` result stream to `toUIMessageStream` and return it with `createUIMessageStreamResponse`. ### UI Message Stream Example Here is a Next.js example that uses the UI message stream protocol: ```tsx title='app/page.tsx' 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, i) => { switch (part.type) { case 'text': return
{part.text}
; } })}
))}
{ e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.currentTarget.value)} />
); } ``` ```ts title='app/api/chat/route.ts' import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage, } from 'ai'; // Allow streaming responses up to 30 seconds export const maxDuration = 30; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Streaming Custom Data description: Learn how to stream custom data from the server to the client. url: "https://ai-sdk.dev/docs/ai-sdk-ui/streaming-data" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). It is often useful to send additional data alongside the model's response. For example, you may want to send status information, the message ids after storing them, or references to content that the language model is referring to. The AI SDK provides several helpers that allows you to stream additional data to the client and attach it to the `UIMessage` parts array: - `createUIMessageStream`: creates a data stream - `createUIMessageStreamResponse`: creates a response object that streams data - `pipeUIMessageStreamToResponse`: pipes a data stream to a server response object The data is streamed as part of the response stream using Server-Sent Events. ## Setting Up Type-Safe Data Streaming First, define your custom message type with data part schemas for type safety: ```tsx title="ai/types.ts" import { UIMessage } from 'ai'; // Define your custom message type with data part schemas export type MyUIMessage = UIMessage< never, // metadata type { weather: { city: string; weather?: string; status: 'loading' | 'success'; }; notification: { message: string; level: 'info' | 'warning' | 'error'; }; } // data parts type >; ``` ## Streaming Data from the Server In your server-side route handler, you can create a `UIMessageStream` and then pass it to `createUIMessageStreamResponse`: ```tsx title="route.ts" import { openai } from '@ai-sdk/openai'; import { convertToModelMessages, createUIMessageStream, createUIMessageStreamResponse, streamText, toUIMessageStream, } from 'ai'; import type { MyUIMessage } from '@/ai/types'; export async function POST(req: Request) { const { messages } = await req.json(); const stream = createUIMessageStream({ execute: ({ writer }) => { // 1. Start the assistant message before writing any message parts. writer.write({ type: 'start' }); // 2. Send initial status (transient - won't be added to message history) writer.write({ type: 'data-notification', data: { message: 'Processing your request...', level: 'info' }, transient: true, // This part won't be added to message history }); // 3. Send sources (useful for RAG use cases) writer.write({ type: 'source', value: { type: 'source', sourceType: 'url', id: 'source-1', url: 'https://weather.com', title: 'Weather Data Source', }, }); // 4. Send data parts with loading state writer.write({ type: 'data-weather', id: 'weather-1', data: { city: 'San Francisco', status: 'loading' }, }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), onEnd() { // 5. Update the same data part (reconciliation) writer.write({ type: 'data-weather', id: 'weather-1', // Same ID = update existing part data: { city: 'San Francisco', weather: 'sunny', status: 'success', }, }); // 6. Send completion notification (transient) writer.write({ type: 'data-notification', data: { message: 'Request completed', level: 'info' }, transient: true, // Won't be added to message history }); }, }); writer.merge( toUIMessageStream({ stream: result.stream, sendStart: false }), ); }, }); return createUIMessageStreamResponse({ stream }); } ``` You can also send stream data from custom backends, e.g. Python / FastAPI, using the [UI Message Stream Protocol](/docs/ai-sdk-ui/stream-protocol#data-stream-protocol). ## Types of Streamable Data ### Data Parts (Persistent) Regular data parts are added to the message history and appear in `message.parts`: ```tsx writer.write({ type: 'data-weather', id: 'weather-1', // Optional: enables reconciliation data: { city: 'San Francisco', status: 'loading' }, }); ``` ### Sources Sources are useful for RAG implementations where you want to show which documents or URLs were referenced: ```tsx writer.write({ type: 'source', value: { type: 'source', sourceType: 'url', id: 'source-1', url: 'https://example.com', title: 'Example Source', }, }); ``` ### Transient Data Parts (Ephemeral) Transient parts are sent to the client but not added to the message history. They are only accessible via the `onData` useChat handler: ```tsx // server writer.write({ type: 'data-notification', data: { message: 'Processing...', level: 'info' }, transient: true, // Won't be added to message history }); // client const [notification, setNotification] = useState(); const { messages } = useChat({ onData: ({ data, type }) => { if (type === 'data-notification') { setNotification({ message: data.message, level: data.level }); } }, }); ``` ## Data Part Reconciliation When you write to a data part with the same ID, the client automatically reconciles and updates that part. This enables powerful dynamic experiences like: - **Collaborative artifacts** - Update code, documents, or designs in real-time - **Progressive data loading** - Show loading states that transform into final results - **Live status updates** - Update progress bars, counters, or status indicators - **Interactive components** - Build UI elements that evolve based on user interaction The reconciliation happens automatically - simply use the same `id` when writing to the stream. ## Processing Data on the Client ### Using the onData Callback The `onData` callback is essential for handling streaming data, especially transient parts: ```tsx title="page.tsx" import { useChat } from '@ai-sdk/react'; import type { MyUIMessage } from '@/ai/types'; const { messages } = useChat({ api: '/api/chat', onData: dataPart => { // Handle all data parts as they arrive (including transient parts) console.log('Received data part:', dataPart); // Handle different data part types if (dataPart.type === 'data-weather') { console.log('Weather update:', dataPart.data); } // Handle transient notifications (ONLY available here, not in message.parts) if (dataPart.type === 'data-notification') { showToast(dataPart.data.message, dataPart.data.level); } }, }); ``` **Important:** Transient data parts are **only** available through the `onData` callback. They will not appear in the `message.parts` array since they're not added to message history. ### Rendering Persistent Data Parts You can filter and render data parts from the message parts array: ```tsx title="page.tsx" const result = ( <> {messages?.map(message => (
{/* Render weather data parts */} {message.parts .filter(part => part.type === 'data-weather') .map((part, index) => (
{part.data.status === 'loading' ? ( <>Getting weather for {part.data.city}... ) : ( <> Weather in {part.data.city}: {part.data.weather} )}
))} {/* Render text content */} {message.parts .filter(part => part.type === 'text') .map((part, index) => (
{part.text}
))} {/* Render sources */} {message.parts .filter(part => part.type === 'source') .map((part, index) => (
Source: {part.title}
))}
))} ); ``` ### Complete Example ```tsx title="page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; import type { MyUIMessage } from '@/ai/types'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat({ api: '/api/chat', onData: dataPart => { // Handle transient notifications if (dataPart.type === 'data-notification') { console.log('Notification:', dataPart.data.message); } }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }; return ( <> {messages?.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {/* Render weather data */} {message.parts .filter(part => part.type === 'data-weather') .map((part, index) => ( {part.data.status === 'loading' ? ( <>Getting weather for {part.data.city}... ) : ( <> Weather in {part.data.city}: {part.data.weather} )} ))} {/* Render text content */} {message.parts .filter(part => part.type === 'text') .map((part, index) => (
{part.text}
))}
))}
setInput(e.target.value)} placeholder="Ask about the weather..." />
); } ``` ## Use Cases - **RAG Applications** - Stream sources and retrieved documents - **Real-time Status** - Show loading states and progress updates - **Collaborative Tools** - Stream live updates to shared artifacts - **Analytics** - Send usage data without cluttering message history - **Notifications** - Display temporary alerts and status messages ## Message Metadata vs Data Parts Both [message metadata](/docs/ai-sdk-ui/message-metadata) and data parts allow you to send additional information alongside messages, but they serve different purposes: ### Message Metadata Message metadata is best for **message-level information** that describes the message as a whole: - Attached at the message level via `message.metadata` - Sent using the `messageMetadata` callback in `toUIMessageStream` - Ideal for: timestamps, model info, token usage, user context - Type-safe with custom metadata types ```ts // Server: Send metadata about the message return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, messageMetadata: ({ part }) => { if (part.type === 'finish') { return { model: part.response.modelId, totalTokens: part.totalUsage.totalTokens, createdAt: Date.now(), }; } }, }), }); ``` ### Data Parts Data parts are best for streaming **dynamic arbitrary data**: - Added to the message parts array via `message.parts` - Streamed using `createUIMessageStream` and `writer.write()` - Can be reconciled/updated using the same ID - Support transient parts that don't persist - Ideal for: dynamic content, loading states, interactive components ```ts // Server: Stream data as part of message content writer.write({ type: 'data-weather', id: 'weather-1', data: { city: 'San Francisco', status: 'loading' }, }); ``` For more details on message metadata, see the [Message Metadata documentation](/docs/ai-sdk-ui/message-metadata). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Transport description: Learn how to use custom transports with useChat. url: "https://ai-sdk.dev/docs/ai-sdk-ui/transport" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `useChat` transport system provides fine-grained control over how messages are sent to your API endpoints and how responses are processed. This is particularly useful for alternative communication protocols like WebSockets, custom authentication patterns, or specialized backend integrations. ## Default Transport By default, `useChat` uses HTTP POST requests to send messages to `/api/chat`: ```tsx import { useChat } from '@ai-sdk/react'; // Uses default HTTP transport const { messages, sendMessage } = useChat(); ``` This is equivalent to: ```tsx import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); ``` ## Custom Transport Configuration Configure the default transport with custom options: ```tsx import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/custom-chat', headers: { Authorization: 'Bearer your-token', 'X-API-Version': '2024-01', }, credentials: 'include', }), }); ``` ### Dynamic Configuration You can also provide functions that return configuration values. This is useful for authentication tokens that need to be refreshed, or for configuration that depends on runtime conditions: ```tsx const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', headers: () => ({ Authorization: `Bearer ${getAuthToken()}`, 'X-User-ID': getCurrentUserId(), }), body: () => ({ sessionId: getCurrentSessionId(), preferences: getUserPreferences(), }), credentials: () => 'include', }), }); ``` ### Request Transformation Transform requests before sending to your API: ```tsx const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', prepareSendMessagesRequest: ({ id, messages, trigger, messageId }) => { return { headers: { 'X-Session-ID': id, }, body: { messages: messages.slice(-10), // Only send last 10 messages trigger, messageId, }, }; }, }), }); ``` ## WebSocket Transport Use `WebSocketChatTransport` when you want chat turns to share a persistent WebSocket connection instead of opening an HTTP request for every turn. This is useful for chats with frequent client-side tool results or other multi-step interactions. ```tsx import { useChat } from '@ai-sdk/react'; import { WebSocketChatTransport } from 'ai'; import { useMemo } from 'react'; export default function Chat() { const transport = useMemo( () => new WebSocketChatTransport({ url: '/api/chat', params: () => ({ token: getShortLivedToken() }), }), [], ); const { messages, sendMessage } = useChat({ transport }); // ... render the chat } ``` The transport multiplexes requests by `requestId`, validates incoming `UIMessageChunk` values, forwards aborts to the server, and reuses the open socket for later turns. HTTP and HTTPS URLs are automatically converted to WS and WSS URLs. The code that creates a transport owns its lifecycle. React and Vue chat hooks do not close caller-supplied transports when options change or components are disposed, because the same transport can be shared by multiple chats. Call `transport.close()` after its last consumer is gone. If a directly managed `Chat` instance exclusively owns the transport, call `chat.dispose()`; for a shared transport, stop individual chats and close the transport only after all consumers are finished. Your WebSocket server must implement the [`WebSocketChatTransport` protocol](/docs/reference/ai-sdk-ui/websocket-chat-transport#wire-protocol). The exported `WebSocketChatTransportRequest` and `WebSocketChatTransportResponse` types can be used to type server frames. Use `safeValidateWebSocketChatTransportRequest` before handling an untrusted client frame. Browser WebSocket APIs cannot set arbitrary handshake headers. The `headers` option is included inside each application-level request frame. Authenticate the handshake with secure cookies, a short-lived query parameter, or a subprotocol, and validate the connection origin and every frame on the server. `sendAutomaticallyWhen` still starts a new chat turn after the preceding UI message stream finishes. `WebSocketChatTransport` avoids opening another HTTP connection for that turn, but it does not send tool results into an active model generation. If the socket closes during a response, the active stream errors. A later send opens a new connection. The transport does not automatically replay requests, because replaying a generation can duplicate model output or tool effects. `reconnectToStream` requests a complete replay of the retained response from sequence zero. Chat rebuilds the message with a fresh parser, so the replay must include all earlier chunks and the original `start` chunk's `messageId`. That stable message ID lets Chat replace the partial assistant message. Duplicate sequence numbers are ignored within each response stream. For complete options, protocol details, cancellation behavior, and a server example, see the [`WebSocketChatTransport` reference](/docs/reference/ai-sdk-ui/websocket-chat-transport). ## Direct Agent Transport For scenarios where you want to communicate directly with an [Agent](/docs/reference/ai-sdk-core/agent) without going through HTTP, you can use `DirectChatTransport`. This transport invokes the agent's `stream()` method directly in-process. This is useful for: - **Server-side rendering**: Run the agent on the server without an API endpoint - **Testing**: Test chat functionality without network requests - **Single-process applications**: Desktop or CLI apps where client and agent run together ```tsx import { useChat } from '@ai-sdk/react'; import { DirectChatTransport, ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a helpful assistant.', tools: { weather: weatherTool, }, }); const { messages, sendMessage } = useChat({ transport: new DirectChatTransport({ agent }), }); ``` ### How It Works Unlike `DefaultChatTransport` which sends HTTP requests: 1. `DirectChatTransport` validates incoming UI messages 2. Converts them to model messages using `convertToModelMessages` 3. Calls the agent's `stream()` method directly 4. Returns the result as a UI message stream via `toUIMessageStream()` ### Configuration Options You can pass additional options to customize the stream output: ```tsx const transport = new DirectChatTransport({ agent, // Pass options to the agent options: { customOption: 'value' }, // Configure what's sent to the client sendReasoning: true, sendSources: true, }); ``` `DirectChatTransport` does not support stream reconnection since there is no persistent server-side stream. The `reconnectToStream()` method always returns `null`. For complete API details, see the [DirectChatTransport reference](/docs/reference/ai-sdk-ui/direct-chat-transport). ## Workflow Transport For chat apps built on the [Workflow SDK](/docs/agents/workflow-agent), `WorkflowChatTransport` from `@ai-sdk/workflow` provides automatic stream reconnection. It handles the common scenario where a workflow function times out mid-stream — the transport detects the missing `finish` event and reconnects to resume from where it left off. ```tsx import { useChat } from '@ai-sdk/react'; import { WorkflowChatTransport } from '@ai-sdk/workflow/client'; import { useMemo } from 'react'; export default function Chat() { const transport = useMemo( () => new WorkflowChatTransport({ api: '/api/chat', maxConsecutiveErrors: 5, onChatEnd: ({ chatId, chunkIndex }) => { console.log(`Chat complete: ${chunkIndex} chunks`); }, }), [], ); const { messages, sendMessage } = useChat({ transport }); // ... render chat UI } ``` Key features: - **Automatic reconnection**: Detects interrupted streams (no `finish` event) and reconnects via GET to `{api}/{runId}/stream` - **Page refresh recovery**: `initialStartIndex` controls where the initial reconnection begins - **Configurable retries**: `maxConsecutiveErrors` controls how many consecutive reconnection failures to tolerate - **Lifecycle callbacks**: `onChatSendMessage` and `onChatEnd` for tracking chat state Negative `initialStartIndex` values can fetch only the tail when the durable server stream already stores `UIMessageChunk` objects. For raw `WorkflowAgent` streams, use a non-negative cursor and follow the server-side conversion in the WorkflowAgent guide. For the full API reference, see [`WorkflowChatTransport`](/docs/reference/ai-sdk-workflow/workflow-chat-transport). For server-side endpoint setup, see the [WorkflowAgent guide](/docs/agents/workflow-agent#resumable-streaming-with-workflowchattransport). ## Building Custom Transports To understand how to build your own transport, refer to the source code of the default implementation: - **[DefaultChatTransport](https://github.com/vercel/ai/blob/main/packages/ai/src/ui/default-chat-transport.ts)** - The complete default HTTP transport implementation - **[HttpChatTransport](https://github.com/vercel/ai/blob/main/packages/ai/src/ui/http-chat-transport.ts)** - Base HTTP transport with request handling - **[ChatTransport Interface](https://github.com/vercel/ai/blob/main/packages/ai/src/ui/chat-transport.ts)** - The transport interface you need to implement These implementations show you exactly how to: - Handle the `sendMessages` method - Process UI message streams - Transform requests and responses - Handle errors and connection management The transport system gives you complete control over how your chat application communicates, enabling integration with any backend protocol or service. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Overview description: An overview of foundational concepts critical to understanding the AI SDK url: "https://ai-sdk.dev/docs/foundations/overview" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This page is a beginner-friendly introduction to high-level artificial intelligence (AI) concepts. To dive right into implementing the AI SDK, feel free to skip ahead to our [quickstarts](/docs/getting-started) or learn about our [supported models and providers](/docs/foundations/providers-and-models). The AI SDK standardizes integrating artificial intelligence (AI) models across [supported providers](/docs/foundations/providers-and-models). This enables developers to focus on building great AI applications, not waste time on technical details. For example, here’s how you can generate text with various models using the AI SDK: To effectively leverage the AI SDK, it helps to familiarize yourself with the following concepts: ## Generative Artificial Intelligence **Generative artificial intelligence** refers to models that predict and generate various types of outputs (such as text, images, or audio) based on what’s statistically likely, pulling from patterns they’ve learned from their training data. For example: - Given a photo, a generative model can generate a caption. - Given an audio file, a generative model can generate a transcription. - Given a text description, a generative model can generate an image. ## Large Language Models A **large language model (LLM)** is a subset of generative models focused primarily on **text**. An LLM takes a sequence of words as input and aims to predict the most likely sequence to follow. It assigns probabilities to potential next sequences and then selects one. The model continues to generate sequences until it meets a specified stopping criterion. LLMs learn by training on massive collections of written text, which means they will be better suited to some use cases than others. For example, a model trained on GitHub data would understand the probabilities of sequences in source code particularly well. However, it's crucial to understand LLMs' limitations. When asked about less known or absent information, like the birthday of a personal relative, LLMs might "hallucinate" or make up information. It's essential to consider how well-represented the information you need is in the model. ## Embedding Models An **embedding model** is used to convert complex data (like words or images) into a dense vector (a list of numbers) representation, known as an embedding. Unlike generative models, embedding models do not generate new text or data. Instead, they provide representations of semantic and syntactic relationships between entities that can be used as input for other models or other natural language processing tasks. In the next section, you will learn about the difference between model providers and models, and which ones are available in the AI SDK. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Prompts description: Learn about the Prompt structure used in the AI SDK. url: "https://ai-sdk.dev/docs/foundations/prompts" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Prompts are instructions that you give a [large language model (LLM)](/docs/foundations/overview#large-language-models) to tell it what to do. It's like when you ask someone for directions; the clearer your question, the better the directions you'll get. Many LLM providers offer complex interfaces for specifying prompts. They involve different roles and message types. While these interfaces are powerful, they can be hard to use and understand. In order to simplify prompting, the AI SDK supports text, message, and system prompts. ## Text Prompts Text prompts are strings. They are ideal for simple generation use cases, e.g. repeatedly generating content for variants of the same prompt text. You can set text prompts using the `prompt` property made available by AI SDK functions like [`streamText`](/docs/reference/ai-sdk-core/stream-text) or [`generateText`](/docs/reference/ai-sdk-core/generate-text). You can structure the text in any way and inject variables, e.g. using a template literal. ```ts {3} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', }); ``` You can also use template literals to provide dynamic data to your prompt. ```ts {3-5} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: `I am planning a trip to ${destination} for ${lengthOfStay} days. ` + `Please suggest the best tourist activities for me to do.`, }); ``` ## System Prompts System prompts are the initial set of instructions given to models that help guide and constrain the models' behaviors and responses. You can set system prompts using the `instructions` property. System prompts work with both the `prompt` and the `messages` properties. System messages in `prompt` or `messages` are rejected by default; use the `instructions` property for system instructions, or set `allowSystemInMessages: true` when you need to send existing message histories that contain system messages. Opting in with `allowSystemInMessages` can create a prompt injection risk where users can override or set the system prompt by injecting system messages. In most cases, only trusted server-side code should set system instructions via the `instructions` property. ```ts {3-6} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", instructions: `You help plan travel itineraries. ` + `Respond to the users' request with a list ` + `of the best stops to make in their destination.`, prompt: `I am planning a trip to ${destination} for ${lengthOfStay} days. ` + `Please suggest the best tourist activities for me to do.`, }); ``` ## Message Prompts A message prompt is an array of user, assistant, and tool messages. They are great for chat interfaces and more complex, multi-modal prompts. You can use the `messages` property to set message prompts. Each message has a `role` and a `content` property. The content can either be text (for user and assistant messages), or an array of relevant parts (data) for that message type. ```ts {3-7} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: [ { role: 'user', content: 'Hi!' }, { role: 'assistant', content: 'Hello, how can I help?' }, { role: 'user', content: 'Where can I buy the best Currywurst in Berlin?' }, ], }); ``` Instead of sending a text in the `content` property, you can send an array of parts that includes a mix of text and other content parts. Not all language models support all message and content types. For example, some models might not be capable of handling multi-modal inputs or tool messages. [Learn more about the capabilities of select models](./providers-and-models#model-capabilities). ### Provider Options You can pass through additional provider-specific metadata to enable provider-specific functionality at 3 levels. #### Function Call Level Functions like [`streamText`](/docs/reference/ai-sdk-core/stream-text#provider-options) or [`generateText`](/docs/reference/ai-sdk-core/generate-text#provider-options) accept a `providerOptions` property. Adding provider options at the function call level should be used when you do not need granular control over where the provider options are applied. ```ts const { text } = await generateText({ model: azure('your-deployment-name'), providerOptions: { openai: { reasoningEffort: 'low', }, }, }); ``` #### Message Level For granular control over applying provider options at the message level, you can pass `providerOptions` to the message object: ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", instructions: { role: 'system', content: 'Cached system message', providerOptions: { // Sets a cache control breakpoint on the system message anthropic: { cacheControl: { type: 'ephemeral' } }, }, }, prompt: 'Invent a new holiday and describe its traditions.', }); ``` #### Message Part Level Certain provider-specific options require configuration at the message part level: ```ts import { ModelMessage } from 'ai'; const messages: ModelMessage[] = [ { role: 'user', content: [ { type: 'text', text: 'Describe the image in detail.', providerOptions: { openai: { imageDetail: 'low' }, }, }, { type: 'file', mediaType: 'image', data: 'https://github.com/vercel/ai/blob/main/examples/ai-functions/data/comic-cat.png?raw=true', // Sets image detail configuration for image part: providerOptions: { openai: { imageDetail: 'low' }, }, }, ], }, ]; ``` AI SDK UI hooks like [`useChat`](/docs/reference/ai-sdk-ui/use-chat) return arrays of `UIMessage` objects, which do not support provider options. We recommend using the [`convertToModelMessages`](/docs/reference/ai-sdk-ui/convert-to-model-messages) function to convert `UIMessage` objects to [`ModelMessage`](/docs/reference/ai-sdk-core/model-message) objects before applying or appending message(s) or message parts with `providerOptions`. ### User Messages #### Text Parts Text content is the most common type of content. It is a string that is passed to the model. If you only need to send text content in a message, the `content` property can be a string, but you can also use it to send multiple content parts. ```ts {7-10} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: [ { role: 'user', content: [ { type: 'text', text: 'Where can I buy the best Currywurst in Berlin?', }, ], }, ], }); ``` #### Image Parts User messages can include image parts. An image can be one of the following: - base64-encoded image: - `string` with base-64 encoded content - data URL `string`, e.g. `data:image/png;base64,...` - binary image: - `ArrayBuffer` - `Uint8Array` - `Buffer` - URL: - http(s) URL `string`, e.g. `https://example.com/image.png` - `URL` object, e.g. `new URL('https://example.com/image.png')` ##### Example: Binary image (Buffer) ```ts {8-11} const result = await generateText({ model, messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe the image in detail.' }, { type: 'file', mediaType: 'image', data: fs.readFileSync('./data/comic-cat.png'), }, ], }, ], }); ``` ##### Example: Base-64 encoded image (string) ```ts {8-11} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe the image in detail.' }, { type: 'file', mediaType: 'image', data: fs.readFileSync('./data/comic-cat.png').toString('base64'), }, ], }, ], }); ``` ##### Example: Image URL (string) ```ts {8-12} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe the image in detail.' }, { type: 'file', mediaType: 'image', data: 'https://github.com/vercel/ai/blob/main/examples/ai-functions/data/comic-cat.png?raw=true', }, ], }, ], }); ``` #### File Parts Only a few providers and models currently support file parts: [Google Generative AI](/providers/ai-sdk-providers/google), [Google Vertex AI](/providers/ai-sdk-providers/google-vertex), [OpenAI](/providers/ai-sdk-providers/openai) (for `wav` and `mp3` audio with `gpt-4o-audio-preview`), [Anthropic](/providers/ai-sdk-providers/anthropic), [OpenAI](/providers/ai-sdk-providers/openai) (for `pdf`). User messages can include file parts. A file can be one of the following: - base64-encoded file: - `string` with base-64 encoded content - data URL `string`, e.g. `data:image/png;base64,...` - binary data: - `ArrayBuffer` - `Uint8Array` - `Buffer` - URL: - http(s) URL `string`, e.g. `https://example.com/some.pdf` - `URL` object, e.g. `new URL('https://example.com/some.pdf')` You need to specify the MIME type of the file you are sending. ##### Example: PDF file from Buffer ```ts {12-15} import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google('gemini-3.8-flash'), messages: [ { role: 'user', content: [ { type: 'text', text: 'What is the file about?' }, { type: 'file', mediaType: 'application/pdf', data: fs.readFileSync('./data/example.pdf'), filename: 'example.pdf', // optional, not used by all providers }, ], }, ], }); ``` ##### Example: mp3 audio file from Buffer ```ts {12-14} import { openai } from '@ai-sdk/openai'; import { generateText } from 'ai'; const result = await generateText({ model: openai('gpt-4o-audio-preview'), messages: [ { role: 'user', content: [ { type: 'text', text: 'What is the audio saying?' }, { type: 'file', mediaType: 'audio/mpeg', data: fs.readFileSync('./data/galileo.mp3'), }, ], }, ], }); ``` #### Custom Download Function (Experimental) You can use custom download functions to implement throttling, retries, authentication, caching, and more. The default download implementation automatically downloads files in parallel when they are not supported by the model. Custom download function can be passed via the `experimental_download` property: ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", experimental_download: async ( requestedDownloads: Array<{ url: URL; isUrlSupportedByModel: boolean; }>, ): PromiseLike< Array<{ data: Uint8Array; mediaType: string | undefined; } | null> > => { // ... download the files and return an array with similar order }, messages: [ { role: 'user', content: [ { type: 'file', data: new URL('https://api.company.com/private/document.pdf'), mediaType: 'application/pdf', }, ], }, ], }); ``` The `experimental_download` option is experimental and may change in future releases. ### Assistant Messages Assistant messages are messages that have a role of `assistant`. They are typically previous responses from the assistant and can contain text, reasoning, and tool call parts. #### Example: Assistant message with text content ```ts {5} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: [ { role: 'user', content: 'Hi!' }, { role: 'assistant', content: 'Hello, how can I help?' }, ], }); ``` #### Example: Assistant message with text content in array ```ts {7} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: [ { role: 'user', content: 'Hi!' }, { role: 'assistant', content: [{ type: 'text', text: 'Hello, how can I help?' }], }, ], }); ``` #### Example: Assistant message with tool call content ```ts {7-14} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: [ { role: 'user', content: 'How many calories are in this block of cheese?' }, { role: 'assistant', content: [ { type: 'tool-call', toolCallId: '12345', toolName: 'get-nutrition-data', input: { cheese: 'Roquefort' }, }, ], }, ], }); ``` #### Example: Assistant message with file content This content part is for model-generated files. Only a few models support this, and only for file types that they can generate. ```ts {9-11} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: [ { role: 'user', content: 'Generate an image of a roquefort cheese!' }, { role: 'assistant', content: [ { type: 'file', mediaType: 'image/png', data: fs.readFileSync('./data/roquefort.jpg'), }, ], }, ], }); ``` ### Tool Messages [Tools](/docs/foundations/tools) (also known as function calling) are programs that you can provide an LLM to extend its built-in functionality. This can be anything from calling an external API to calling functions within your UI. Learn more about Tools in [the next section](/docs/foundations/tools). For models that support [tool](/docs/foundations/tools) calls, assistant messages can contain tool call parts, and tool messages can contain tool output parts. A single assistant message can call multiple tools, and a single tool message can contain multiple tool results. ```ts {14-42} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: [ { role: 'user', content: [ { type: 'text', text: 'How many calories are in this block of cheese?', }, { type: 'file', mediaType: 'image', data: fs.readFileSync('./data/roquefort.jpg'), }, ], }, { role: 'assistant', content: [ { type: 'tool-call', toolCallId: '12345', toolName: 'get-nutrition-data', input: { cheese: 'Roquefort' }, }, // there could be more tool calls here (parallel calling) ], }, { role: 'tool', content: [ { type: 'tool-result', toolCallId: '12345', // needs to match the tool call id toolName: 'get-nutrition-data', output: { type: 'json', value: { name: 'Cheese, roquefort', calories: 369, fat: 31, protein: 22, }, }, }, // there could be more tool results here (parallel calling) ], }, ], }); ``` #### Multi-modal Tool Results Tool results can be multi-part and multi-modal, e.g. a text and an image. You can use `output: { type: 'content', value: [...] }` to specify multi-part tool results. ```ts {14-27} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages: [ // ... { role: 'tool', content: [ { type: 'tool-result', toolCallId: '12345', // needs to match the tool call id toolName: 'get-nutrition-data', // for models that do not support multi-part tool results, // you can include a regular output part: output: { type: 'json', value: { name: 'Cheese, roquefort', calories: 369, fat: 31, protein: 22, }, }, }, { type: 'tool-result', toolCallId: '12345', // needs to match the tool call id toolName: 'get-nutrition-data', // for models that support multi-part tool results, // you can include a multi-part content part: output: { type: 'content', value: [ { type: 'text', text: 'Here is the nutrition data for the cheese:', }, { type: 'file-data', data: fs .readFileSync('./data/roquefort-nutrition-data.png') .toString('base64'), mediaType: 'image/png', }, ], }, }, ], }, ], }); ``` ### System Messages System messages are messages that are sent to the model before the user messages to guide the assistant's behavior. Sending system messages in `messages` requires `allowSystemInMessages: true`. You can alternatively use the `instructions` property. ```ts {3,5} const result = await generateText({ model: "anthropic/claude-sonnet-5.5", allowSystemInMessages: true, messages: [ { role: 'system', content: 'You help plan travel itineraries.' }, { role: 'user', content: 'I am planning a trip to Berlin for 3 days. Please suggest the best tourist activities for me to do.', }, ], }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Provider Options description: Learn how to use provider-specific options to control reasoning, caching, and other advanced features. url: "https://ai-sdk.dev/docs/foundations/provider-options" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Provider options let you pass provider-specific configuration that goes beyond the [standard settings](/docs/ai-sdk-core/settings) shared by all providers. They are set via the `providerOptions` property on functions like `generateText` and `streamText`. ```ts const result = await generateText({ model: openai('gpt-6-astra'), prompt: 'Explain quantum entanglement.', providerOptions: { openai: { reasoningEffort: 'low', }, }, }); ``` Provider options are namespaced by the provider name (e.g. `openai`, `anthropic`) so you can even include options for multiple providers in the same call — only the options matching the active provider are used. See [Prompts: Provider Options](/docs/foundations/prompts#provider-options) for details on applying options at the message and message-part level. For controlling reasoning effort, consider using the top-level [`reasoning` parameter](/docs/ai-sdk-core/reasoning) instead of provider-specific options. It provides a portable setting that works across all providers that support reasoning. Use provider-specific options only when you need features like exact token budgets. ## Common Provider Options The sections below cover the most frequently used provider options, focusing on reasoning and output control for OpenAI and Anthropic. For a complete reference, see the individual provider pages: - [OpenAI provider options](/providers/ai-sdk-providers/openai) - [Anthropic provider options](/providers/ai-sdk-providers/anthropic) *** ## OpenAI ### Reasoning Effort For reasoning models (e.g. `gpt-6-astra`, `gpt-6-sol`, `gpt-6-luna`), `reasoningEffort` controls how much internal reasoning the model performs before responding. Lower values are faster and cheaper; higher values produce more thorough answers. ```ts import { openai, type OpenAILanguageModelResponsesOptions, } from '@ai-sdk/openai'; import { generateText } from 'ai'; const result = await generateText({ model: openai('gpt-6-astra'), prompt: 'Invent a new holiday and describe its traditions.', providerOptions: { openai: { reasoningEffort: 'low', // 'low' | 'medium' | 'high' | 'xhigh' | 'max' } satisfies OpenAILanguageModelResponsesOptions, }, }); console.log('Text:', result.text); console.log('Usage:', result.usage); console.log( 'Reasoning tokens:', result.finalStep.providerMetadata?.openai?.reasoningTokens, ); ``` | Value | Behavior | | ----------- | ---------------------------------------- | | `'none'` | No reasoning (GPT-5.1 models only) | | `'minimal'` | Bare-minimum reasoning | | `'low'` | Fast, concise reasoning | | `'medium'` | Balanced (default) | | `'high'` | Thorough reasoning | | `'xhigh'` | Extra-high reasoning on supported models | | `'max'` | Maximum reasoning on supported models | Supported effort levels vary by model. Using an effort with an unsupported model will result in an error. ### Reasoning Summary When working with reasoning models, you may want to see *how* the model arrived at its answer. The `reasoningSummary` option surfaces the model's thought process. When `reasoningEffort` is set to a value other than `'none'`, the OpenAI Responses provider defaults `reasoningSummary` to `'detailed'`. Set `reasoningSummary: null` to omit reasoning summaries. #### Streaming ```ts import { openai, type OpenAILanguageModelResponsesOptions, } from '@ai-sdk/openai'; import { streamText } from 'ai'; const result = streamText({ model: openai('gpt-6-astra'), prompt: 'Tell me about the Mission burrito debate in San Francisco.', providerOptions: { openai: { reasoningSummary: 'detailed', // 'auto' | 'detailed' } satisfies OpenAILanguageModelResponsesOptions, }, }); for await (const part of result.stream) { if (part.type === 'reasoning') { console.log(`Reasoning: ${part.textDelta}`); } else if (part.type === 'text-delta') { process.stdout.write(part.textDelta); } } ``` #### Non-streaming ```ts import { openai, type OpenAILanguageModelResponsesOptions, } from '@ai-sdk/openai'; import { generateText } from 'ai'; const result = await generateText({ model: openai('gpt-6-astra'), prompt: 'Tell me about the Mission burrito debate in San Francisco.', providerOptions: { openai: { reasoningSummary: 'auto', } satisfies OpenAILanguageModelResponsesOptions, }, }); console.log('Reasoning:', result.finalStep.reasoning); ``` | Value | Behavior | | ------------ | ------------------------------ | | `'auto'` | Condensed summary of reasoning | | `'detailed'` | Comprehensive reasoning output | ### Text Verbosity Control the length and detail of the model's text response independently of reasoning: ```ts import { openai, type OpenAILanguageModelResponsesOptions, } from '@ai-sdk/openai'; import { generateText } from 'ai'; const result = await generateText({ model: openai('gpt-6-luna'), prompt: 'Write a poem about a boy and his first pet dog.', providerOptions: { openai: { textVerbosity: 'low', // 'low' | 'medium' | 'high' } satisfies OpenAILanguageModelResponsesOptions, }, }); ``` | Value | Behavior | | ---------- | -------------------------------- | | `'low'` | Terse, minimal responses | | `'medium'` | Balanced detail (default) | | `'high'` | Verbose, comprehensive responses | *** ## Anthropic ### Thinking (Extended Reasoning) Anthropic's thinking feature gives Claude models a dedicated "thinking" phase before they respond. You enable it by providing a `thinking` object with a token budget. ```ts import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text, reasoning, reasoningText } = await generateText({ model: anthropic('claude-opus-4-20250514'), prompt: 'How many people will live in the world in 2040?', providerOptions: { anthropic: { thinking: { type: 'enabled', budgetTokens: 12000 }, } satisfies AnthropicLanguageModelOptions, }, }); console.log('Reasoning:', reasoningText); console.log('Answer:', text); ``` The `budgetTokens` value sets the upper limit on how many tokens the model can use for its internal reasoning. Higher budgets allow deeper reasoning but increase latency and cost. Thinking is supported on `claude-opus-4-20250514`, `claude-sonnet-4-20250514`, and `claude-sonnet-4-5-20250929` models. ### Effort The `effort` option provides a simpler way to control reasoning depth without specifying a token budget. It affects thinking, text responses, and function calls. ```ts import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text, usage } = await generateText({ model: anthropic('claude-opus-5-5'), prompt: 'How many people will live in the world in 2040?', providerOptions: { anthropic: { effort: 'low', // 'low' | 'medium' | 'high' } satisfies AnthropicLanguageModelOptions, }, }); ``` | Value | Behavior | | ---------- | ------------------------------------ | | `'low'` | Minimal reasoning, fastest responses | | `'medium'` | Balanced reasoning | | `'high'` | Thorough reasoning (default) | ### Fast Mode For `claude-opus-4-6`, the `speed` option enables approximately 2.5x faster output token speeds: ```ts import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text } = await generateText({ model: anthropic('claude-opus-4-6'), prompt: 'Write a short poem about the sea.', providerOptions: { anthropic: { speed: 'fast', // 'fast' | 'standard' } satisfies AnthropicLanguageModelOptions, }, }); ``` *** ## Combining Options You can combine multiple provider options in a single call. For example, using both reasoning effort and reasoning summaries with OpenAI: ```ts import { openai, type OpenAILanguageModelResponsesOptions, } from '@ai-sdk/openai'; import { generateText } from 'ai'; const result = await generateText({ model: openai('gpt-6-astra'), prompt: 'What are the implications of quantum computing for cryptography?', providerOptions: { openai: { reasoningEffort: 'high', reasoningSummary: 'detailed', } satisfies OpenAILanguageModelResponsesOptions, }, }); ``` Or enabling thinking with a low effort level for Anthropic: ```ts import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const result = await generateText({ model: anthropic('claude-opus-4-20250514'), prompt: 'Explain the Riemann hypothesis in simple terms.', providerOptions: { anthropic: { thinking: { type: 'enabled', budgetTokens: 8000 }, effort: 'medium', } satisfies AnthropicLanguageModelOptions, }, }); ``` ## Using Provider Options with the AI Gateway Provider options work the same way when using the [Vercel AI Gateway](/providers/ai-sdk-providers/ai-gateway). Use the underlying provider name (e.g. `openai`, `anthropic`) as the key — not `gateway`. The AI Gateway forwards these options to the target provider automatically. ```ts import type { OpenAILanguageModelResponsesOptions } from '@ai-sdk/openai'; import { generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-6-astra', // AI Gateway model string prompt: 'What are the implications of quantum computing for cryptography?', providerOptions: { openai: { reasoningEffort: 'high', reasoningSummary: 'detailed', } satisfies OpenAILanguageModelResponsesOptions, }, }); ``` You can also combine gateway-specific options (like routing and fallbacks) with provider-specific options in the same call: ```ts import type { AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; const result = await generateText({ model: 'anthropic/claude-sonnet-4', prompt: 'Explain quantum computing', providerOptions: { // Gateway-specific: control routing gateway: { order: ['vertex', 'anthropic'], } satisfies GatewayProviderOptions, // Provider-specific: enable reasoning anthropic: { thinking: { type: 'enabled', budgetTokens: 12000 }, } satisfies AnthropicLanguageModelOptions, }, }); ``` For more on gateway routing, fallbacks, and other gateway-specific options, see the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway#provider-options). ## Type Safety Each provider exports a type for its options, which you can use with `satisfies` to get autocomplete and catch typos at build time: ```ts import { type OpenAILanguageModelResponsesOptions } from '@ai-sdk/openai'; import { type AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; ``` For a full list of available options, see the provider-specific documentation: - [OpenAI Provider](/providers/ai-sdk-providers/openai) - [Anthropic Provider](/providers/ai-sdk-providers/anthropic) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Providers and Models description: Learn about the providers and models available in the AI SDK. url: "https://ai-sdk.dev/docs/foundations/providers-and-models" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Companies such as OpenAI and Anthropic (providers) offer access to a range of large language models (LLMs) with differing strengths and capabilities through their own APIs. Each provider typically has its own unique method for interfacing with their models, complicating the process of switching providers and increasing the risk of vendor lock-in. To solve these challenges, AI SDK Core offers a standardized approach to interacting with LLMs through a [language model specification](https://github.com/vercel/ai/tree/main/packages/provider/src/language-model/v4) that abstracts differences between providers. This unified interface allows you to switch between providers with ease while using the same API for all providers. Here is an overview of the AI SDK Provider Architecture: ![Diagram showing the AI SDK Core API, provider specifications, and provider implementations](/images/ai-sdk-diagram.png) ## AI SDK Providers The AI SDK comes with a wide range of providers that you can use to interact with different language models: - [xAI Grok Provider](/providers/ai-sdk-providers/xai) (`@ai-sdk/xai`) - [OpenAI Provider](/providers/ai-sdk-providers/openai) (`@ai-sdk/openai`) - [Azure OpenAI Provider](/providers/ai-sdk-providers/azure) (`@ai-sdk/azure`) - [Anthropic Provider](/providers/ai-sdk-providers/anthropic) (`@ai-sdk/anthropic`) - [Amazon Bedrock Provider](/providers/ai-sdk-providers/amazon-bedrock) (`@ai-sdk/amazon-bedrock`) - [Google Provider](/providers/ai-sdk-providers/google) (`@ai-sdk/google`) - [Google Vertex Provider](/providers/ai-sdk-providers/google-vertex) (`@ai-sdk/google-vertex`) - [Mistral Provider](/providers/ai-sdk-providers/mistral) (`@ai-sdk/mistral`) - [Together.ai Provider](/providers/ai-sdk-providers/togetherai) (`@ai-sdk/togetherai`) - [Cohere Provider](/providers/ai-sdk-providers/cohere) (`@ai-sdk/cohere`) - [Fireworks Provider](/providers/ai-sdk-providers/fireworks) (`@ai-sdk/fireworks`) - [DeepInfra Provider](/providers/ai-sdk-providers/deepinfra) (`@ai-sdk/deepinfra`) - [DeepSeek Provider](/providers/ai-sdk-providers/deepseek) (`@ai-sdk/deepseek`) - [Cerebras Provider](/providers/ai-sdk-providers/cerebras) (`@ai-sdk/cerebras`) - [Groq Provider](/providers/ai-sdk-providers/groq) (`@ai-sdk/groq`) - [Perplexity Provider](/providers/ai-sdk-providers/perplexity) (`@ai-sdk/perplexity`) - [ElevenLabs Provider](/providers/ai-sdk-providers/elevenlabs) (`@ai-sdk/elevenlabs`) - [Hume Provider](/providers/ai-sdk-providers/hume) (`@ai-sdk/hume`) - [Rev.ai Provider](/providers/ai-sdk-providers/revai) (`@ai-sdk/revai`) - [Deepgram Provider](/providers/ai-sdk-providers/deepgram) (`@ai-sdk/deepgram`) - [Gladia Provider](/providers/ai-sdk-providers/gladia) (`@ai-sdk/gladia`) - [AssemblyAI Provider](/providers/ai-sdk-providers/assemblyai) (`@ai-sdk/assemblyai`) - [Baseten Provider](/providers/ai-sdk-providers/baseten) (`@ai-sdk/baseten`) You can also use the [OpenAI Compatible provider](/providers/openai-compatible-providers) with OpenAI-compatible APIs: - [LM Studio](/providers/openai-compatible-providers/lmstudio) - [Heroku](/providers/openai-compatible-providers/heroku) Our [language model specification](https://github.com/vercel/ai/tree/main/packages/provider/src/language-model/v4) is published as an open-source package, which you can use to create [custom providers](/providers/community-providers/custom-providers). The open-source community has created the following providers: - [Ollama Provider](/providers/community-providers/ollama) (`ollama-ai-provider`) - [FriendliAI Provider](/providers/community-providers/friendliai) (`@friendliai/ai-provider`) - [Portkey Provider](/providers/community-providers/portkey) (`@portkey-ai/vercel-provider`) - [Cloudflare Workers AI Provider](/providers/community-providers/cloudflare-workers-ai) (`workers-ai-provider`) - [OpenRouter Provider](/providers/community-providers/openrouter) (`@openrouter/ai-sdk-provider`) - [Apertis Provider](/providers/community-providers/apertis) (`@apertis/ai-sdk-provider`) - [Aihubmix Provider](/providers/community-providers/aihubmix) (`@aihubmix/ai-sdk-provider`) - [Requesty Provider](/providers/community-providers/requesty) (`@requesty/ai-sdk`) - [Crosshatch Provider](/providers/community-providers/crosshatch) (`@crosshatch/ai-provider`) - [Mixedbread Provider](/providers/community-providers/mixedbread) (`mixedbread-ai-provider`) - [Voyage AI Provider](/providers/community-providers/voyage-ai) (`voyage-ai-provider`) - [Mem0 Provider](/providers/community-providers/mem0) (`@mem0/vercel-ai-provider`) - [Letta Provider](/providers/community-providers/letta) (`@letta-ai/vercel-ai-sdk-provider`) - [Hindsight Provider](/providers/community-providers/hindsight) (`@vectorize-io/hindsight-ai-sdk`) - [Supermemory Provider](/providers/community-providers/supermemory) (`@supermemory/tools`) - [Spark Provider](/providers/community-providers/spark) (`spark-ai-provider`) - [AnthropicVertex Provider](/providers/community-providers/anthropic-vertex-ai) (`anthropic-vertex-ai`) - [LangDB Provider](/providers/community-providers/langdb) (`@langdb/vercel-provider`) - [Dify Provider](/providers/community-providers/dify) (`dify-ai-provider`) - [Sarvam Provider](/providers/community-providers/sarvam) (`sarvam-ai-provider`) - [Claude Code Provider](/providers/community-providers/claude-code) (`ai-sdk-provider-claude-code`) - [Browser AI Provider](/providers/community-providers/browser-ai) (`browser-ai`) - [Gemini CLI Provider](/providers/community-providers/gemini-cli) (`ai-sdk-provider-gemini-cli`) - [A2A Provider](/providers/community-providers/a2a) (`a2a-ai-provider`) - [SAP AI Core Provider](/providers/community-providers/sap-ai) (`@jerome-benoit/sap-ai-provider`) - [AI/ML API Provider](/providers/community-providers/aimlapi) (`@ai-ml.api/aimlapi-vercel-ai`) - [MCP Sampling Provider](/providers/community-providers/mcp-sampling) (`@mcpc-tech/mcp-sampling-ai-provider`) - [ACP Provider](/providers/community-providers/acp) (`@mcpc-tech/acp-ai-provider`) - [OpenCode Provider](/providers/community-providers/opencode-sdk) (`ai-sdk-provider-opencode-sdk`) - [Codex CLI Provider](/providers/community-providers/codex-cli) (`ai-sdk-provider-codex-cli`) - [Soniox Provider](/providers/community-providers/soniox) (`@soniox/vercel-ai-sdk-provider`) - [Zhipu (Z.AI) Provider](/providers/community-providers/zhipu) (`zhipu-ai-provider`) - [OLLM Provider](/providers/community-providers/ollm) (`@ofoundation/ollm`) - [ZeroEntropy Provider](/providers/community-providers/zeroentropy) (`zeroentropy-ai-provider`) - [Crusoe Provider](/providers/community-providers/crusoe) (`crusoe-ai-provider`) - [Neon AI Gateway Provider](/providers/community-providers/neon-ai-gateway) (`@neon/ai-sdk-provider`) - [Interfaze Provider](/providers/community-providers/interfaze) (`@interfaze-ai/ai-sdk`) - [Telnyx Provider](/providers/community-providers/telnyx) (`@telnyx/ai-sdk-provider`) ## Self-Hosted Models You can access self-hosted models with the following providers: - [Ollama Provider](/providers/community-providers/ollama) - [LM Studio](/providers/openai-compatible-providers/lmstudio) - [Baseten](/providers/ai-sdk-providers/baseten) - [Browser AI](/providers/community-providers/browser-ai) Additionally, any self-hosted provider that supports the OpenAI specification can be used with the [OpenAI Compatible Provider](/providers/openai-compatible-providers). ## Model Capabilities The AI providers support different language models with various capabilities. Here are the capabilities of popular models: | Provider | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | | -------------------------------------------------- | ------------------------------------------- | ----------- | ----------------- | ---------- | -------------- | | [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4.7` | ✓ | ✓ | ✓ | ✓ | | [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4.6` | ✓ | ✓ | ✓ | ✓ | | [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4.5` | ✓ | ✓ | ✓ | ✓ | | [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-4` | ✗ | ✓ | ✓ | ✓ | | [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-3` | ✗ | ✓ | ✓ | ✓ | | [xAI Grok](/providers/ai-sdk-providers/xai) | `grok-3-mini` | ✗ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-6-astra` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-6-luna` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-6-sol` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.6` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.6-luna` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.6-sol` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.6-terra` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.5` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.4-pro` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.4` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.4-mini` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.4-nano` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.3-chat-latest` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.2-pro` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.2-chat-latest` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.2` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5-mini` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5-nano` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.1-chat-latest` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.1-codex-mini` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.1-codex` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5.1` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5-codex` | ✓ | ✓ | ✓ | ✓ | | [OpenAI](/providers/ai-sdk-providers/openai) | `gpt-5-chat-latest` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-5-5` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-sonnet-5-5` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-sonnet-5` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-fable-5-1` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-fable-5` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-8` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-7` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-6` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-sonnet-4-6` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-5` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-1` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-opus-4-0` | ✓ | ✓ | ✓ | ✓ | | [Anthropic](/providers/ai-sdk-providers/anthropic) | `claude-sonnet-4-0` | ✓ | ✓ | ✓ | ✓ | | [Google](/providers/ai-sdk-providers/google) | `gemini-3.8-flash` | ✓ | ✓ | ✓ | ✓ | | [Google](/providers/ai-sdk-providers/google) | `gemini-3.1-pro-preview` | ✓ | ✓ | ✓ | ✓ | | [Google](/providers/ai-sdk-providers/google) | `gemini-3-pro-preview` | ✓ | ✓ | ✓ | ✓ | | [Google](/providers/ai-sdk-providers/google) | `gemini-2.5-pro` | ✓ | ✓ | ✓ | ✓ | | [Google](/providers/ai-sdk-providers/google) | `gemini-2.5-flash` | ✓ | ✓ | ✓ | ✓ | | [Mistral](/providers/ai-sdk-providers/mistral) | `pixtral-large-latest` | ✓ | ✓ | ✓ | ✓ | | [Mistral](/providers/ai-sdk-providers/mistral) | `mistral-large-latest` | ✗ | ✓ | ✓ | ✓ | | [Mistral](/providers/ai-sdk-providers/mistral) | `mistral-medium-latest` | ✗ | ✓ | ✓ | ✓ | | [Mistral](/providers/ai-sdk-providers/mistral) | `mistral-medium-3` | ✗ | ✓ | ✓ | ✓ | | [Mistral](/providers/ai-sdk-providers/mistral) | `mistral-medium-2505` | ✗ | ✓ | ✓ | ✓ | | [Mistral](/providers/ai-sdk-providers/mistral) | `mistral-medium-3.5` | ✗ | ✓ | ✓ | ✓ | | [Mistral](/providers/ai-sdk-providers/mistral) | `mistral-small-latest` | ✗ | ✓ | ✓ | ✓ | | [Mistral](/providers/ai-sdk-providers/mistral) | `pixtral-12b-2409` | ✓ | ✓ | ✓ | ✓ | | [DeepSeek](/providers/ai-sdk-providers/deepseek) | `deepseek-v4-flash` | ✗ | ✓ | ✓ | ✓ | | [DeepSeek](/providers/ai-sdk-providers/deepseek) | `deepseek-v4-pro` | ✗ | ✓ | ✓ | ✓ | | [Cerebras](/providers/ai-sdk-providers/cerebras) | `gpt-oss-120b` | ✗ | ✓ | ✓ | ✓ | | [Cerebras](/providers/ai-sdk-providers/cerebras) | `gemma-4-31b` | ✓ | ✓ | ✓ | ✓ | | [Groq](/providers/ai-sdk-providers/groq) | `meta-llama/llama-4-scout-17b-16e-instruct` | ✓ | ✓ | ✓ | ✓ | | [Groq](/providers/ai-sdk-providers/groq) | `llama-3.3-70b-versatile` | ✗ | ✓ | ✓ | ✓ | | [Groq](/providers/ai-sdk-providers/groq) | `llama-3.1-8b-instant` | ✗ | ✓ | ✓ | ✓ | | [Groq](/providers/ai-sdk-providers/groq) | `mixtral-8x7b-32768` | ✗ | ✓ | ✓ | ✓ | | [Groq](/providers/ai-sdk-providers/groq) | `gemma2-9b-it` | ✗ | ✓ | ✓ | ✓ | This table is not exhaustive. Additional models can be found in the provider documentation pages and on the provider websites. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Streaming description: Why use streaming for AI applications? url: "https://ai-sdk.dev/docs/foundations/streaming" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Streaming conversational text UIs (like ChatGPT) have gained massive popularity over the past few months. This section explores the benefits and drawbacks of streaming and blocking interfaces. [Large language models (LLMs)](/docs/foundations/overview#large-language-models) are extremely powerful. However, when generating long outputs, they can be very slow compared to the latency you're likely used to. If you try to build a traditional blocking UI, your users might easily find themselves staring at loading spinners for 5, 10, even up to 40s waiting for the entire LLM response to be generated. This can lead to a poor user experience, especially in conversational applications like chatbots. Streaming UIs can help mitigate this issue by **displaying parts of the response as they become available**.
#### Blocking UI Blocking responses wait until the full response is available before displaying it. #### Streaming UI Streaming responses can transmit parts of the response as they become available.
## Real-world Examples Here are 2 examples that illustrate how streaming UIs can improve user experiences in a real-world setting – the first uses a blocking UI, while the second uses a streaming UI. ### Blocking UI ### Streaming UI As you can see, the streaming UI is able to start displaying the response much faster than the blocking UI. This is because the blocking UI has to wait for the entire response to be generated before it can display anything, while the streaming UI can display parts of the response as they become available. While streaming interfaces can greatly enhance user experiences, especially with larger language models, they aren't always necessary or beneficial. If you can achieve your desired functionality using a smaller, faster model without resorting to streaming, this route can often lead to simpler and more manageable development processes. However, regardless of the speed of your model, the AI SDK is designed to make implementing streaming UIs as simple as possible. In the example below, we stream text generation in under 10 lines of code using the SDK's [`streamText`](/docs/reference/ai-sdk-core/stream-text) function: ```ts import { streamText } from 'ai'; const { textStream } = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a poem about embedding models.', }); for await (const textPart of textStream) { console.log(textPart); } ``` For an introduction to streaming UIs and the AI SDK, check out our [Getting Started guides](/docs/getting-started). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Tools description: Learn about tools with the AI SDK. url: "https://ai-sdk.dev/docs/foundations/tools" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). While [large language models (LLMs)](/docs/foundations/overview#large-language-models) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response. For example, when you ask an LLM for the "weather in London", and there is a weather tool available, it could call a tool with London as the argument. The tool would then fetch the weather data and return it to the LLM. The LLM can then use this information in its response. ## What is a tool? A tool is an object that can be called by the model to perform a specific task. You can use tools with [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text) by passing one or more tools to the `tools` parameter. A tool consists of three properties: - **`description`**: An optional description of the tool that can influence when the tool is picked. Function and dynamic tools can use either a string or a function that derives the description from the tool's context and experimental sandbox. - **`inputSchema`**: A [Zod schema](/docs/reference/ai-sdk-core/zod-schema) or a [JSON schema](/docs/reference/ai-sdk-core/json-schema) that defines the input required for the tool to run. The schema is consumed by the LLM, and also used to validate the LLM tool calls. - **`execute`**: An optional async function that is called with the arguments from the tool call. `streamUI` uses UI generator tools with a `generate` function that can return React components. If the LLM decides to use a tool, it will generate a tool call. Tools with an `execute` function are run automatically when these calls are generated. The output of the tool calls is returned using tool result objects. You can automatically pass tool results back to the LLM using [multi-step calls](/docs/ai-sdk-core/tools-and-tool-calling#multi-step-calls-using-stopwhen) with `streamText` and `generateText`. ## Types of Tools The AI SDK supports four types of tools, each with different trade-offs: ### Function Tools Function tools are tools you define entirely yourself, including the description, input schema, and optional execute function. They are provider-agnostic and give you full control. ```ts import { tool } from 'ai'; import { z } from 'zod'; const weatherTool = tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { // Your implementation return { temperature: 72, conditions: 'sunny' }; }, }); ``` **When to use**: When you need full control, want provider portability, or are implementing application-specific functionality. ### Dynamic Tools Dynamic tools are function-style tools where the input and output types are not known at development time. They are useful for tools loaded from external sources, such as MCP servers, user-defined functions, or databases. ```ts import { dynamicTool } from 'ai'; import { z } from 'zod'; const runtimeTool = dynamicTool({ description: 'Execute a tool loaded at runtime', inputSchema: z.object({}), execute: async input => { // input is typed as unknown return runRuntimeTool(input); }, }); ``` **When to use**: When tools are discovered or generated at runtime and their exact TypeScript input/output types are not known when you write the code. ### Provider-Defined Tools Provider-defined tools are tools where the provider specifies the tool's `inputSchema` and `description`, but you provide the `execute` function. These are sometimes called "client tools" because execution happens on your side. Examples include Anthropic's `bash` and `text_editor` tools. The model has been specifically trained to use these tools effectively, which can result in better performance for supported tasks. ```ts import { anthropic } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const result = await generateText({ model: anthropic('claude-opus-4-5'), tools: { bash: anthropic.tools.bash_20250124({ execute: async ({ command }) => { // Your implementation to run the command return runCommand(command); }, }), }, prompt: 'List files in the current directory', }); ``` **When to use**: When the provider offers a tool the model is trained to use well, and you want better performance for that specific task. ### Provider-Executed Tools Provider-executed tools are tools that run entirely on the provider's servers. You configure them, but the provider handles execution. These are sometimes called "server-side tools". Examples include OpenAI's web search and Anthropic's code execution. These provide out-of-the-box functionality without requiring you to set up infrastructure. ```ts import { openai } from '@ai-sdk/openai'; import { generateText } from 'ai'; const result = await generateText({ model: openai('gpt-6-astra'), tools: { web_search: openai.tools.webSearch(), }, prompt: 'What happened in the news today?', }); ``` **When to use**: When you want powerful functionality (like web search or sandboxed code execution) without managing the infrastructure yourself. ### Comparison | Aspect | Function Tools | Dynamic Tools | Provider-Defined Tools | Provider-Executed Tools | | ------------------ | ------------------------ | ----------------------------- | ---------------------- | ----------------------- | | **Execution** | Your code | Your code | Your code | Provider's servers | | **Schema** | You define | Runtime-defined | Provider defines | Provider defines | | **Portability** | Works with any provider | Works with any provider | Provider-specific | Provider-specific | | **Model Training** | General tool use | General tool use | Optimized for the tool | Optimized for the tool | | **Setup** | You implement everything | You load or generate the tool | You implement execute | Configuration only | Provider-defined and provider-executed tools are documented in each provider's page. See [Anthropic Provider](/providers/ai-sdk-providers/anthropic) and [OpenAI Provider](/providers/ai-sdk-providers/openai) for examples. ## Schemas Schemas are used to define and validate the [tool input](/docs/ai-sdk-core/tools-and-tool-calling), tool outputs, and structured output generation. The AI SDK supports the following schemas: - [Zod](https://zod.dev/) v3 and v4 directly or via [`zodSchema()`](/docs/reference/ai-sdk-core/zod-schema) - [Valibot](https://valibot.dev/) via [`valibotSchema()`](/docs/reference/ai-sdk-core/valibot-schema) from `@ai-sdk/valibot` - [Standard JSON Schema](https://standardschema.dev/json-schema) compatible schemas - Raw JSON schemas via [`jsonSchema()`](/docs/reference/ai-sdk-core/json-schema) You can also use schemas for structured output generation with [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text) using the `output` setting. ## Refining Tool Inputs Different LLM providers can generate slightly different tool inputs for the same tool input type. For example, one provider might produce `null` for an optional field while another produces an empty string. When you do not own the tool, such as with tools from a third-party package, you may not be able to change the tool's `inputSchema` to accept both shapes. In these cases, you can use the experimental `experimental_refineToolInput` option to normalize a parsed tool input before it is executed and before it appears in outputs, lifecycle callbacks, and telemetry. ```ts import { generateText, tool } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: 'openai/gpt-6-luna', tools: { search: tool({ inputSchema: z.object({ query: z.string(), category: z.string().nullable(), }), execute: async ({ query, category }) => { return search({ query, category }); }, }), }, experimental_refineToolInput: { search: input => ({ ...input, category: input.category === '' ? null : input.category, }), }, prompt: 'Search for ski jackets with no category.', }); ``` Refinement functions are typed per tool. Each function receives the typed input for its tool and must return an input with the same type shape. ## Tool Packages Given tools are JavaScript objects, they can be packaged and distributed through npm like any other library. This makes it easy to share reusable tools across projects and with the community. ### Using Ready-Made Tool Packages Install a tool package and import the tools you need: ```bash pnpm add some-tool-package ``` Then pass them directly to `generateText`, `streamText`, or your agent definition: ```ts {2, 8} import { generateText, isStepCount } from 'ai'; import { searchTool } from 'some-tool-package'; const { text } = await generateText({ model: 'anthropic/claude-haiku-5.5', prompt: 'When was Vercel Ship AI?', tools: { webSearch: searchTool, }, stopWhen: isStepCount(10), }); ``` ### Publishing Your Own Tools You can publish your own tool packages to npm for others to use. Simply export your tool objects from your package: ```ts title="my-tools/index.ts" import { tool } from 'ai'; import { z } from 'zod'; export const myTool = tool({ description: 'A helpful tool', inputSchema: z.object({ query: z.string(), }), execute: async ({ query }) => { // your tool logic return result; }, }); ``` Anyone can then install and use your tools by importing them. To get started, you can use the [AI SDK Tool Package Template](https://github.com/vercel-labs/ai-sdk-tool-as-package-template) which provides a ready-to-use starting point for publishing your own tools. ## Toolsets When you work with tools, you typically need a mix of application-specific tools and general-purpose tools. The community has created various toolsets and resources to help you build and use tools. ### Ready-to-Use Tool Packages These packages provide pre-built tools you can install and use immediately: - **[@exalabs/ai-sdk](https://www.npmjs.com/package/@exalabs/ai-sdk)** - Web search tool that lets AI search the web and get real-time information. - **[@parallel-web/ai-sdk-tools](https://www.npmjs.com/package/@parallel-web/ai-sdk-tools)** - Web search and extract tools powered by Parallel Web API for real-time information and content extraction. - **[@perplexity-ai/ai-sdk](https://www.npmjs.com/package/@perplexity-ai/ai-sdk)** - Search the web with real-time results and advanced filtering powered by Perplexity's Search API. - **[@tavily/ai-sdk](https://www.npmjs.com/package/@tavily/ai-sdk)** - Search, extract, crawl, and map tools for enterprise-grade agents to explore the web in real-time. - **[Stripe agent tools](https://docs.stripe.com/agents?framework=vercel)** - Tools for interacting with Stripe. - **[StackOne ToolSet](https://docs.stackone.com/agents/typescript/frameworks/vercel-ai-sdk)** - Agentic integrations for hundreds of [enterprise SaaS](https://www.stackone.com/integrations) platforms. - **[agentic](https://docs.agentic.so/marketplace/ts-sdks/ai-sdk)** - A collection of 20+ tools that connect to external APIs such as [Exa](https://exa.ai/) or [E2B](https://e2b.dev/). - **[Amazon Bedrock AgentCore](https://github.com/aws/bedrock-agentcore-sdk-typescript)** - Fully managed AI agent services including [**Browser**](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/built-in-tools.html) (a fast and secure cloud-based browser runtime to enable agents to interact with web applications, fill forms, navigate websites, and extract information) and [**Code Interpreter**](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/built-in-tools.html) (an isolated sandbox environment for agents to execute code in Python, JavaScript, and TypeScript, enhancing accuracy and expanding ability to solve complex end-to-end tasks). - **[@airweave/vercel-ai-sdk](https://www.npmjs.com/package/@airweave/vercel-ai-sdk)** - Unified semantic search across 35+ data sources (Notion, Slack, Google Drive, databases, and more) for AI agents. - **[Composio](https://docs.composio.dev/providers/vercel)** - 250+ tools like GitHub, Gmail, Salesforce and [more](https://composio.dev/tools). - **[JigsawStack](http://www.jigsawstack.com/docs/integration/vercel)** - Over 30+ small custom fine-tuned models available for specific uses. - **[AI Tools Registry](https://ai-tools-registry.vercel.app)** - A Shadcn-compatible tool definitions and components registry for the AI SDK. - **[Toolhouse](https://docs.toolhouse.ai/toolhouse/toolhouse-sdk/using-vercel-ai)** - AI function-calling in 3 lines of code for over 25 different actions. - **[bash-tool](https://www.npmjs.com/package/bash-tool)** - Provides `bash`, `readFile`, and `writeFile` tools for AI agents. Supports [@vercel/sandbox](https://vercel.com/docs/vercel-sandbox) for full VM isolation. ### MCP Tools These are pre-built tools available as MCP servers: - **[Smithery](https://smithery.ai/docs/integrations/vercel_ai_sdk)** - An open marketplace of 6,000+ MCPs, including [Browserbase](https://browserbase.com/) and [Exa](https://exa.ai/). - **[Pipedream](https://pipedream.com/docs/connect/mcp/ai-frameworks/vercel-ai-sdk)** - Developer toolkit that lets you easily add 3,000+ integrations to your app or AI agent. - **[Apify](https://docs.apify.com/platform/integrations/vercel-ai-sdk)** - Apify provides a [marketplace](https://apify.com/store) of thousands of tools for web scraping, data extraction, and browser automation. ### Tool Building Tutorials These tutorials and guides help you build your own tools that integrate with specific services: - **[browserbase](https://docs.browserbase.com/integrations/vercel/introduction#vercel-ai-integration)** - Tutorial for building browser tools that run a headless browser. - **[browserless](https://docs.browserless.io/ai-integrations/vercel-ai-sdk)** - Guide for integrating browser automation (self-hosted or cloud-based). - **[AI Tool Maker](https://github.com/nihaocami/ai-tool-maker)** - A CLI utility to generate AI SDK tools from OpenAPI specs. - **[Interlify](https://www.interlify.com/docs/integrate-with-vercel-ai)** - Guide for converting APIs into tools. - **[DeepAgent](https://deepagent.amardeep.space/docs/vercel-ai-sdk)** - A suite of 50+ AI tools and integrations, seamlessly connecting with APIs like Tavily, E2B, Airtable and [more](https://deepagent.amardeep.space/docs). Do you have open source tools or tool libraries that are compatible with the AI SDK? Please [file a pull request](https://github.com/vercel/ai/pulls) to add them to this list. ## Learn more The AI SDK Core [Tool Calling](/docs/ai-sdk-core/tools-and-tool-calling) and [Agents](/docs/agents) documentation has more information about tools and tool calling. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Choosing a Provider description: Learn how to configure and authenticate with AI providers in the AI SDK. url: "https://ai-sdk.dev/docs/getting-started/choosing-a-provider" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [community](/providers/community-providers) packages. ```ts import { generateText } from 'ai'; const { text } = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'What is love?', }); ``` ## AI Gateway The [Vercel AI Gateway](/providers/ai-sdk-providers/ai-gateway) is the fastest way to get started with the AI SDK. Access models from OpenAI, Anthropic, Google, and other providers. Authenticate with [OIDC](https://ai-sdk.dev/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments) or an AI Gateway API key
[Get an API Key](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%2Fapi-keys%3Futm_source%3Dgateway-models-page%26showCreateKeyModal%3Dtrue\&title=Get+Started+with+Vercel+AI+Gateway)
Add your API key to your environment: ```dotenv title=".env.local" AI_GATEWAY_API_KEY=your_api_key_here ``` The AI Gateway is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration), so you can access models using a simple string: ```ts import { generateText } from 'ai'; const { text } = await generateText({ model: 'anthropic/claude-sonnet-5.5', prompt: 'What is love?', }); ``` You can also explicitly import and use the gateway provider: ```ts // Option 1: Import from 'ai' package (included by default) import { gateway } from 'ai'; model: gateway('anthropic/claude-sonnet-5.5'); // Option 2: Install and import from '@ai-sdk/gateway' package import { gateway } from '@ai-sdk/gateway'; model: gateway('anthropic/claude-sonnet-5.5'); ``` ## Using Dedicated Providers You can also use [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [community](/providers/community-providers) provider packages directly. Install the package and create a provider instance. For example, to use Anthropic: ```bash pnpm add @ai-sdk/anthropic ``` ```ts import { anthropic } from '@ai-sdk/anthropic'; model: anthropic('claude-sonnet-5-5'); ``` You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration). See [available providers](/providers/ai-sdk-providers) for setup instructions for each provider. ## Custom Providers You can build your own provider to integrate any service with the AI SDK. The AI SDK provides a [Language Model Specification](https://github.com/vercel/ai/tree/main/packages/provider/src/language-model/v4) that ensures compatibility across providers. ```ts import { generateText } from 'ai'; import { yourProvider } from 'your-custom-provider'; const { text } = await generateText({ model: yourProvider('your-model-id'), prompt: 'What is love?', }); ``` See [Writing a Custom Provider](/providers/community-providers/custom-providers) for a complete guide. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Coding Agents description: Learn how to set up the AI SDK for use with coding agents, including installing skills, accessing bundled docs, and using DevTools. url: "https://ai-sdk.dev/docs/getting-started/coding-agents" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This page explains how to get the most out of the AI SDK when working inside a coding agent (such as Claude Code, Codex, OpenCode, Cursor, or any other AI-assisted development environment). ## Install the AI SDK Skill The fastest way to give your coding agent deep knowledge of the AI SDK is to install the official AI SDK skill. Skills are lightweight markdown files that load specialized instructions into your agent's context on demand — so your agent knows exactly how to use the SDK without you needing to explain it. Install the AI SDK skill using `npx skills add`: ```bash npx skills add vercel/ai ``` This installs the skill into your agent's specific skills directory (e.g., `.claude/skills`, `.codex/skills`). If you select more than one agent, the CLI creates symlinks so each agent can discover the skill. Use `-a` to specify agents directly — for example, `-a amp` installs into the universal `.agents/skills` directory. Use `-y` for non-interactive installation. Once installed, any agent that supports the [Agent Skills](https://agentskills.io) format will automatically discover and load the skill when working on AI SDK tasks. Agent Skills use **progressive disclosure**: your agent loads only the skill's name and description at startup. The full instructions are only pulled into context when the task calls for it, keeping your agent fast and focused. ## Docs and Source Code in `node_modules` Once you've installed the `ai` package, you already have the full AI SDK documentation and source code available locally inside `node_modules`. Your coding agent can read these directly — no internet access required. Install the `ai` package if you haven't already: ```bash pnpm add ai ``` After installation, your agent can reference the bundled source code and documentation at paths like: ``` node_modules/ai/src/ # Full source code organized by module node_modules/ai/docs/ # Official documentation with examples ``` This means your agent can look up accurate API signatures, implementations, and usage examples directly from the installed package — ensuring it always uses the version of the SDK that's actually installed in your project. ## Install DevTools AI SDK DevTools gives you full visibility into your AI SDK calls during development. It captures LLM requests, responses, tool calls, token usage, and multi-step interactions, and displays them in a local web UI. AI SDK DevTools is experimental and intended for local development only. Do not use in production environments. Install the DevTools package: ```bash pnpm add @ai-sdk/devtools ``` ### Register the integration Register `DevToolsTelemetry` globally so it captures all AI SDK calls: ```ts import { registerTelemetry } from 'ai'; import { DevToolsTelemetry } from '@ai-sdk/devtools'; registerTelemetry(DevToolsTelemetry()); ``` Once an integration is registered, [telemetry](/docs/ai-sdk-core/telemetry) is enabled automatically for all your AI SDK calls: ```ts import { generateText } from 'ai'; const result = await generateText({ model: openai('gpt-6-astra'), prompt: 'What cities are in the United States?', }); ``` ### Launch the viewer Start the DevTools viewer in a separate terminal: ```bash npx @ai-sdk/devtools@latest ``` Open [http://localhost:4983](http://localhost:4983) to inspect your AI SDK interactions in real time. ## Inspecting Tool Calls and Outputs DevTools captures and displays the following for every call: - **Input parameters and prompts** — the complete input sent to your LLM - **Output content and tool calls** — generated text and tool invocations - **Token usage and timing** — resource consumption and latency per step - **Raw provider data** — complete request and response payloads For multi-step agent interactions, DevTools groups everything into **runs** (a complete interaction) and **steps** (each individual LLM call within it), making it easy to trace exactly what your agent did and why. You can also log tool results directly in code during development: ```ts import { streamText, tool, isStepCount } from 'ai'; import { z } from 'zod'; const result = streamText({ model, prompt: "What's the weather in New York in celsius?", tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => ({ location, temperature: Math.round(Math.random() * (90 - 32) + 32), }), }), }, stopWhen: isStepCount(5), onStepEnd: async ({ toolResults }) => { if (toolResults.length) { console.log(JSON.stringify(toolResults, null, 2)); } }, }); ``` The `onStepEnd` callback fires after each LLM step and prints any tool results to your terminal — useful for quick debugging without opening the DevTools UI. DevTools stores all AI interactions in a local `.devtools/generations.json` file. It automatically adds `.devtools` to your `.gitignore` to prevent committing sensitive interaction data. ## Where to Next? - Learn about [Agent Skills](https://agentskills.io/specification) to understand the full skill format. - Read the [DevTools reference](/docs/ai-sdk-core/devtools) for a complete list of captured data and configuration options. - Explore [Tools and Tool Calling](/docs/ai-sdk-core/tools-and-tool-calling) to build agents that can take real-world actions. - Check out the [Add Skills to Your Agent](/cookbook/guides/agent-skills) cookbook guide for a step-by-step integration walkthrough. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Expo description: Learn how to build your first agent with the AI SDK and Expo. url: "https://ai-sdk.dev/docs/getting-started/expo" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface with [Expo](https://expo.dev/). Along the way, you'll learn key concepts and techniques that are fundamental to using the SDK in your own projects. If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first. ## Prerequisites To follow this quickstart, you'll need: - Node.js 22+ and pnpm installed on your local development machine. - A [Vercel AI Gateway](https://vercel.com/ai-gateway) API key. If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website. ## Create Your Application Start by creating a new Expo application. This command will create a new directory named `my-ai-app` and set up a basic Expo application inside it. ``` pnpm create expo-app@latest my-ai-app ``` Navigate to the newly created directory: ``` cd my-ai-app ``` This guide requires Expo 52 or higher. ### Install dependencies Install `ai` and `@ai-sdk/react`, the AI package and AI SDK's React hooks. The AI SDK's [Vercel AI Gateway provider](/providers/ai-sdk-providers/ai-gateway) ships with the `ai` package. You'll also install `zod`, a schema validation library used for defining tool inputs. This guide uses the Vercel AI Gateway provider so you can access hundreds of models from different providers with one API key, but you can switch to any provider or model by installing its package. Check out available [AI SDK providers](/providers/ai-sdk-providers) for more information. ```bash pnpm add ai @ai-sdk/react zod ``` ### Configure your AI Gateway API key Create a `.env.local` file in your project root and add your AI Gateway API key. This key authenticates your application with the Vercel AI Gateway. ``` touch .env.local ``` Edit the `.env.local` file: ```dotenv title=".env.local" AI_GATEWAY_API_KEY=xxxxxxxxx ``` Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key. The AI SDK's Vercel AI Gateway Provider will default to using the `AI_GATEWAY_API_KEY` environment variable. ## Create an API Route Create a route handler, `app/api/chat+api.ts` and add the following code: ```tsx title="app/api/chat+api.ts" import { streamText, UIMessage, convertToModelMessages, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), headers: { 'Content-Type': 'application/octet-stream', 'Content-Encoding': 'none', }, }); } ``` Let's take a look at what is happening in this code: 1. Define an asynchronous `POST` request handler and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation. 2. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` ID string (resolved through AI Gateway) and `messages` (defined in step 1). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior. 3. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result-object). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object. 4. Finally, return the result to the client to stream the response. This API route creates a POST request endpoint at `/api/chat`. If a deployed Expo API Route buffers the response despite the streaming headers above, test the same handler without the deployment adapter to isolate the cause. As a workaround, host the chat endpoint in a separate Vercel Function and set `EXPO_PUBLIC_API_BASE_URL` to that function's base URL. ## Choosing a Provider The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [community](/providers/community-providers) packages. This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration: ```ts model: "anthropic/claude-sonnet-5.5"; ``` You can also explicitly import and use the gateway provider in two other equivalent ways: ```ts // Option 1: Import from 'ai' package (included by default) import { gateway } from 'ai'; model: gateway('anthropic/claude-sonnet-5.5'); // Option 2: Install and import from '@ai-sdk/gateway' package import { gateway } from '@ai-sdk/gateway'; model: gateway('anthropic/claude-sonnet-5.5'); ``` ### Using other providers To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly: ```bash pnpm add @ai-sdk/openai ``` ```ts import { openai } from '@ai-sdk/openai'; model: openai('gpt-6-astra'); ``` #### Updating the global provider You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration). Pick the approach that best matches how you want to manage providers across your application. ## Wire up the UI Now that you have an API route that can query an LLM, it's time to set up your frontend. The AI SDK's [UI](/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat). Update your root page (`app/(tabs)/index.tsx`) with the following code to show a list of chat messages and provide a user message input: ```tsx title="app/(tabs)/index.tsx" import { generateAPIUrl } from '@/utils'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { fetch as expoFetch } from 'expo/fetch'; import { useState } from 'react'; import { View, TextInput, ScrollView, Text, SafeAreaView } from 'react-native'; export default function App() { const [input, setInput] = useState(''); const { messages, error, sendMessage } = useChat({ transport: new DefaultChatTransport({ fetch: expoFetch as unknown as typeof globalThis.fetch, api: generateAPIUrl('/api/chat'), }), onError: error => console.error(error, 'ERROR'), }); if (error) return {error.message}; return ( {messages.map(m => ( {m.role} {m.parts.map((part, i) => { switch (part.type) { case 'text': return {part.text}; } })} ))} setInput(e.nativeEvent.text)} onSubmitEditing={e => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }} autoFocus={true} /> ); } ``` This page utilizes the `useChat` hook, which will, by default, use the `POST` API route you created earlier (`/api/chat`). The hook provides functions and state for handling user input and form submission. The `useChat` hook provides multiple utility functions and state variables: - `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties). - `sendMessage` - a function to send a message to the chat API. The component uses local state (`useState`) to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field. The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated. You use the expo/fetch function instead of the native node fetch to enable streaming of chat responses. This requires Expo 52 or higher. ### Create the API URL Generator Because you're using expo/fetch for streaming responses instead of the native fetch function, you'll need an API URL generator to ensure you are using the correct base url and format depending on the client environment (e.g. web or mobile). Create a new file called `utils.ts` in the root of your project and add the following code: ```ts title="utils.ts" import Constants from 'expo-constants'; export const generateAPIUrl = (relativePath: string) => { const origin = Constants.experienceUrl.replace('exp://', 'http://'); const path = relativePath.startsWith('/') ? relativePath : `/${relativePath}`; if (process.env.NODE_ENV === 'development') { return origin.concat(path); } if (!process.env.EXPO_PUBLIC_API_BASE_URL) { throw new Error( 'EXPO_PUBLIC_API_BASE_URL environment variable is not defined', ); } return process.env.EXPO_PUBLIC_API_BASE_URL.concat(path); }; ``` This utility function handles URL generation for both development and production environments, ensuring your API calls work correctly across different devices and configurations. Before deploying to production, you must set the `EXPO_PUBLIC_API_BASE_URL` environment variable in your production environment. This variable should point to the base URL of your API server. ## Running Your Application With that, you have built everything you need for your chatbot! To start your application, use the command: ``` pnpm expo ``` Head to your browser and open [http://localhost:8081](http://localhost:8081). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Expo. If you experience "Property `structuredClone` doesn't exist" errors on mobile, add the [polyfills described below](#polyfills). ## Enhance Your Chatbot with Tools While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in. Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response. For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information. Let's enhance your chatbot by adding a simple weather tool. ### Update Your API route Modify your `app/api/chat+api.ts` file to include the new weather tool: ```tsx title="app/api/chat+api.ts" {9,18-32} import { streamText, UIMessage, convertToModelMessages, tool, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), headers: { 'Content-Type': 'application/octet-stream', 'Content-Encoding': 'none', }, }); } ``` In this updated code: 1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation. 2. You define a `tools` object with a `weather` tool. This tool: - Has a description that helps the model understand when to use it. - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information. - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API. Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message. You may need to restart your development server for the changes to take effect. Try asking something like "What's the weather in New York?" and see how the model uses the new tool. Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array. Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key you used when defining the tool. In this case, since we defined the tool as `weather`, the part type is `tool-weather`. ### Update the UI To display the weather tool invocation in your UI, update your `app/(tabs)/index.tsx` file: ```tsx title="app/(tabs)/index.tsx" {31-35} import { generateAPIUrl } from '@/utils'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { fetch as expoFetch } from 'expo/fetch'; import { useState } from 'react'; import { View, TextInput, ScrollView, Text, SafeAreaView } from 'react-native'; export default function App() { const [input, setInput] = useState(''); const { messages, error, sendMessage } = useChat({ transport: new DefaultChatTransport({ fetch: expoFetch as unknown as typeof globalThis.fetch, api: generateAPIUrl('/api/chat'), }), onError: error => console.error(error, 'ERROR'), }); if (error) return {error.message}; return ( {messages.map(m => ( {m.role} {m.parts.map((part, i) => { switch (part.type) { case 'text': return {part.text}; case 'tool-weather': return ( {JSON.stringify(part, null, 2)} ); } })} ))} setInput(e.nativeEvent.text)} onSubmitEditing={e => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }} autoFocus={true} /> ); } ``` You may need to restart your development server for the changes to take effect. With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result. Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface. ## Enabling Multi-Step Tool Calls You may have noticed that while the tool results are visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation. To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question. ### Update Your API Route Modify your `app/api/chat+api.ts` file to include the `stopWhen` condition: ```tsx title="app/api/chat+api.ts" {19} import { streamText, UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), headers: { 'Content-Type': 'application/octet-stream', 'Content-Encoding': 'none', }, }); } ``` You may need to restart your development server for the changes to take effect. Head back to the Expo app and ask about the weather in a location. You should now see the model using the weather tool results to answer your question. By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius. ### Add More Tools Update your `app/api/chat+api.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius: ```tsx title="app/api/chat+api.ts" {34-47} import { streamText, UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), convertFahrenheitToCelsius: tool({ description: 'Convert a temperature in fahrenheit to celsius', inputSchema: z.object({ temperature: z .number() .describe('The temperature in fahrenheit to convert'), }), execute: async ({ temperature }) => { const celsius = Math.round((temperature - 32) * (5 / 9)); return { celsius, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), headers: { 'Content-Type': 'application/octet-stream', 'Content-Encoding': 'none', }, }); } ``` You may need to restart your development server for the changes to take effect. ### Update the UI for the new tool To display the temperature conversion tool invocation in your UI, update your `app/(tabs)/index.tsx` file to handle the new tool part: ```tsx title="app/(tabs)/index.tsx" {39-45} import { generateAPIUrl } from '@/utils'; import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { fetch as expoFetch } from 'expo/fetch'; import { useState } from 'react'; import { View, TextInput, ScrollView, Text, SafeAreaView } from 'react-native'; export default function App() { const [input, setInput] = useState(''); const { messages, error, sendMessage } = useChat({ transport: new DefaultChatTransport({ fetch: expoFetch as unknown as typeof globalThis.fetch, api: generateAPIUrl('/api/chat'), }), onError: error => console.error(error, 'ERROR'), }); if (error) return {error.message}; return ( {messages.map(m => ( {m.role} {m.parts.map((part, i) => { switch (part.type) { case 'text': return {part.text}; case 'tool-weather': case 'tool-convertFahrenheitToCelsius': return ( {JSON.stringify(part, null, 2)} ); } })} ))} setInput(e.nativeEvent.text)} onSubmitEditing={e => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }} autoFocus={true} /> ); } ``` You may need to restart your development server for the changes to take effect. Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction: 1. The model will call the weather tool for New York. 2. You'll see the tool result displayed. 3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius. 4. The model will then use that information to provide a natural language response about the weather in New York. This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful. This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information. ## Polyfills Several functions that are internally used by the AI SDK might not available in the Expo runtime depending on your configuration and the target platform. First, install the following packages: ```bash pnpm add @ungap/structured-clone @stardazed/streams-text-encoding ``` Then create a new file in the root of your project with the following polyfills: ```ts title="polyfills.js" import { Platform } from 'react-native'; import structuredClone from '@ungap/structured-clone'; if (Platform.OS !== 'web') { const setupPolyfills = async () => { const { polyfillGlobal } = await import('react-native/Libraries/Utilities/PolyfillFunctions'); const { TextEncoderStream, TextDecoderStream } = await import('@stardazed/streams-text-encoding'); if (!('structuredClone' in global)) { polyfillGlobal('structuredClone', () => structuredClone); } polyfillGlobal('TextEncoderStream', () => TextEncoderStream); polyfillGlobal('TextDecoderStream', () => TextDecoderStream); }; setupPolyfills(); } export {}; ``` Finally, import the polyfills in your root `_layout.tsx`: ```ts title="_layout.tsx" import '@/polyfills'; ``` ## Where to Next? You've built an AI chatbot using the AI SDK! From here, you have several paths to explore: - To learn more about the AI SDK, read through the [documentation](/docs). - If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides. - To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Getting Started description: "Welcome to the AI SDK documentation!" url: "https://ai-sdk.dev/docs/getting-started" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The following guides are intended to provide you with an introduction to some of the core features provided by the AI SDK. ## Backend Framework Examples You can also use [AI SDK Core](/docs/ai-sdk-core/overview) and [AI SDK UI](/docs/ai-sdk-ui/overview) with the following backend frameworks: #### [Node.js HTTP Server](/examples/api-servers/node-js-http-server) Send AI responses from a Node.js HTTP server. #### [Express](/examples/api-servers/express) Send AI responses from an Express server. #### [Hono](/examples/api-servers/hono) Send AI responses from a Hono server. #### [Fastify](/examples/api-servers/fastify) Send AI responses from a Fastify server. #### [Nest.js](/examples/api-servers/nest) Send AI responses from a Nest.js server. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Navigating the Library description: Learn how to navigate the AI SDK. url: "https://ai-sdk.dev/docs/getting-started/navigating-the-library" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK is a powerful toolkit for building AI applications. This page will help you pick the right tools for your requirements. Let’s start with a quick overview of the AI SDK, which is comprised of three parts: - **[AI SDK Core](/docs/ai-sdk-core/overview):** A unified, provider agnostic API for generating text, structured objects, and tool calls with LLMs. - **[AI SDK UI](/docs/ai-sdk-ui/overview):** A set of framework-agnostic hooks for building chat and generative user interfaces. - [AI SDK RSC](/docs/ai-sdk-rsc/overview): Stream generative user interfaces with React Server Components (RSC). Development is currently experimental and we recommend using [AI SDK UI](/docs/ai-sdk-ui/overview). ## Choosing the Right Tool for Your Environment When deciding which part of the AI SDK to use, your first consideration should be the environment and existing stack you are working with. Different components of the SDK are tailored to specific frameworks and environments. | Library | Purpose | Environment Compatibility | | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | [AI SDK Core](/docs/ai-sdk-core/overview) | Call any LLM with unified API (e.g. [generateText](/docs/reference/ai-sdk-core/generate-text) and [streamText](/docs/reference/ai-sdk-core/stream-text)) | Any JS environment (e.g. Node.js, Deno, Browser) | | [AI SDK UI](/docs/ai-sdk-ui/overview) | Build streaming chat and generative UIs (e.g. [useChat](/docs/reference/ai-sdk-ui/use-chat)) | React & Next.js, Vue & Nuxt, Svelte & SvelteKit | | [AI SDK RSC](/docs/ai-sdk-rsc/overview) | Stream generative UIs from Server to Client (e.g. [streamUI](/docs/reference/ai-sdk-rsc/stream-ui)). Development is currently experimental and we recommend using [AI SDK UI](/docs/ai-sdk-ui/overview). | Any framework that supports React Server Components (e.g. Next.js) | ## Environment Compatibility These tools have been designed to work seamlessly with each other and it's likely that you will be using them together. Let's look at how you could decide which libraries to use based on your application environment, existing stack, and requirements. The following table outlines AI SDK compatibility based on environment: | Environment | [AI SDK Core](/docs/ai-sdk-core/overview) | [AI SDK UI](/docs/ai-sdk-ui/overview) | [AI SDK RSC](/docs/ai-sdk-rsc/overview) | | --------------------- | ----------------------------------------- | ------------------------------------- | --------------------------------------- | | None / Node.js / Deno | ✓ | ✗ | ✗ | | Vue / Nuxt | ✓ | ✓ | ✗ | | Svelte / SvelteKit | ✓ | ✓ | ✗ | | Next.js Pages Router | ✓ | ✓ | ✗ | | Next.js App Router | ✓ | ✓ | ✓ | ## When to use AI SDK UI AI SDK UI provides a set of framework-agnostic hooks for quickly building **production-ready AI-native applications**. It offers: - Full support for streaming chat and client-side generative UI - Utilities for handling common AI interaction patterns (i.e. chat, completion, assistant) - Production-tested reliability and performance - Compatibility across popular frameworks ## AI SDK UI Framework Compatibility AI SDK UI supports the following frameworks: [React](https://react.dev/), [Svelte](https://svelte.dev/), and [Vue.js](https://vuejs.org/). Here is a comparison of the supported functions across these frameworks: | Function | React | Svelte | Vue.js | | ---------------------------------------------------------- | ----- | ------ | ------ | | [useChat](/docs/reference/ai-sdk-ui/use-chat) | ✓ | ✓ | ✓ | | [useChat](/docs/reference/ai-sdk-ui/use-chat) tool calling | ✓ | ✓ | ✗ | | [useCompletion](/docs/reference/ai-sdk-ui/use-completion) | ✓ | ✓ | ✓ | | [useObject](/docs/reference/ai-sdk-ui/use-object) | ✓ | ✗ | ✗ | | [MCP Apps](/docs/ai-sdk-core/mcp-apps) | ✓ | ✗ | ✗ | [Contributions](https://github.com/vercel/ai/blob/main/CONTRIBUTING.md) are welcome to implement missing features for non-React frameworks. ## When to use AI SDK RSC AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) (RSCs) provide a new approach to building React applications that allow components to render on the server, fetch data directly, and stream the results to the client, reducing bundle size and improving performance. They also introduce a new way to call server-side functions from anywhere in your application called [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations). AI SDK RSC provides a number of utilities that allow you to stream values and UI directly from the server to the client. However, **it's important to be aware of current limitations**: - **Cancellation**: currently, it is not possible to abort a stream using Server Actions. This will be improved in future releases of React and Next.js. - **Increased Data Transfer**: using [`createStreamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui) can lead to quadratic data transfer (quadratic to the length of generated text). You can avoid this using [ `createStreamableValue` ](/docs/reference/ai-sdk-rsc/create-streamable-value) instead, and rendering the component client-side. - **Re-mounting Issue During Streaming**: when using `createStreamableUI`, components re-mount on `.done()`, causing [flickering](https://github.com/vercel/ai/issues/2232). Given these limitations, **we recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production applications**. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Next.js App Router description: Learn how to build your first agent with the AI SDK and Next.js App Router. url: "https://ai-sdk.dev/docs/getting-started/nextjs-app-router" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications. In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the AI SDK in your own projects. If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first. ## Prerequisites To follow this quickstart, you'll need: - Node.js 22+ and pnpm installed on your local development machine. - A [Vercel AI Gateway](https://vercel.com/ai-gateway) API key. If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website. ## Create Your Application Start by creating a new Next.js application. This command will create a new directory named `my-ai-app` and set up a basic Next.js application inside it.
Be sure to select yes when prompted to use the App Router and Tailwind CSS. If you are looking for the Next.js Pages Router quickstart guide, you can find it [here](/docs/getting-started/nextjs-pages-router).
``` pnpm create next-app@latest my-ai-app ``` Navigate to the newly created directory: ``` cd my-ai-app ``` ### Install dependencies Install `ai` and `@ai-sdk/react`, the AI package and AI SDK's React hooks. The AI SDK's [Vercel AI Gateway provider](/providers/ai-sdk-providers/ai-gateway) ships with the `ai` package. You'll also install `zod`, a schema validation library used for defining tool inputs. This guide uses the Vercel AI Gateway provider so you can access hundreds of models from different providers with one API key, but you can switch to any provider or model by installing its package. Check out available [AI SDK providers](/providers/ai-sdk-providers) for more information. ```bash pnpm add ai @ai-sdk/react zod ``` ### Configure your AI Gateway API key Create a `.env.local` file in your project root and add your AI Gateway API key. This key authenticates your application with Vercel AI Gateway. ``` touch .env.local ``` Edit the `.env.local` file: ```dotenv title=".env.local" AI_GATEWAY_API_KEY=xxxxxxxxx ``` Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key. The AI SDK's Vercel AI Gateway Provider will default to using the `AI_GATEWAY_API_KEY` environment variable. ## Create a Route Handler Create a route handler, `app/api/chat/route.ts` and add the following code: ```tsx title="app/api/chat/route.ts" import { streamText, UIMessage, convertToModelMessages, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` Let's take a look at what is happening in this code: 1. Define an asynchronous `POST` request handler and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation. The `messages` are of UIMessage type, which are designed for use in application UI - they contain the entire message history and associated metadata like timestamps. 2. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider and `messages` (defined in step 1). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior. The `messages` key expects a `ModelMessage[]` array. This type is different from `UIMessage` in that it does not include metadata, such as timestamps or sender information. To convert between these types, we use the `convertToModelMessages` function, which strips the UI-specific metadata and transforms the `UIMessage[]` array into the `ModelMessage[]` format that the model expects. 3. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result-object). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object. 4. Finally, return the result to the client to stream the response. This Route Handler creates a POST request endpoint at `/api/chat`. ## Choosing a Provider The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [community](/providers/community-providers) packages. This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration: ```ts model: "anthropic/claude-sonnet-5.5"; ``` You can also explicitly import and use the gateway provider in two other equivalent ways: ```ts // Option 1: Import from 'ai' package (included by default) import { gateway } from 'ai'; model: gateway('anthropic/claude-sonnet-5.5'); // Option 2: Install and import from '@ai-sdk/gateway' package import { gateway } from '@ai-sdk/gateway'; model: gateway('anthropic/claude-sonnet-5.5'); ``` ### Using other providers To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly: ```bash pnpm add @ai-sdk/openai ``` ```ts import { openai } from '@ai-sdk/openai'; model: openai('gpt-6-astra'); ``` #### Updating the global provider You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration). Pick the approach that best matches how you want to manage providers across your application. ## Wire up the UI Now that you have a Route Handler that can query an LLM, it's time to set up your frontend. The AI SDK's [UI](/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat). Update your root page (`app/page.tsx`) with the following code to show a list of chat messages and provide a user message input: ```tsx title="app/page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, i) => { switch (part.type) { case 'text': return
{part.text}
; } })}
))}
{ e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.currentTarget.value)} />
); } ``` Make sure you add the `"use client"` directive to the top of your file. This allows you to add interactivity with JavaScript. This page utilizes the `useChat` hook, which will, by default, use the `POST` API route you created earlier (`/api/chat`). The hook provides functions and state for handling user input and form submission. The `useChat` hook provides multiple utility functions and state variables: - `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties). - `sendMessage` - a function to send a message to the chat API. The component uses local state (`useState`) to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field. The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated. ## Running Your Application With that, you have built everything you need for your chatbot! To start your application, use the command: ``` pnpm run dev ``` Head to your browser and open [http://localhost:3000](http://localhost:3000). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Next.js. ## Enhance Your Chatbot with Tools While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in. Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response. For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information. Let's enhance your chatbot by adding a simple weather tool. ### Update Your Route Handler Modify your `app/api/chat/route.ts` file to include the new weather tool: ```tsx title="app/api/chat/route.ts" {1-8,18-32} import { streamText, UIMessage, convertToModelMessages, tool, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` In this updated code: 1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation. 2. You define a `tools` object with a `weather` tool. This tool: - Has a description that helps the model understand when to use it. - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information. - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API. Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message. Try asking something like "What's the weather in New York?" and see how the model uses the new tool. Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array. Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key you used when defining the tool. In this case, since we defined the tool as `weather`, the part type is `tool-weather`. ### Update the UI To display the tool invocation in your UI, update your `app/page.tsx` file: ```tsx title="app/page.tsx" {18-22} 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, i) => { switch (part.type) { case 'text': return
{part.text}
; case 'tool-weather': return (
                    {JSON.stringify(part, null, 2)}
                  
); } })}
))}
{ e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.currentTarget.value)} />
); } ``` With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result. Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface. ## Enabling Multi-Step Tool Calls You may have noticed that while the tool is now visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation. To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question. ### Update Your Route Handler Modify your `app/api/chat/route.ts` file to include the `stopWhen` condition: ```tsx title="app/api/chat/route.ts" {19} import { streamText, UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, onStepEnd: ({ toolResults }) => { console.log(toolResults); }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` In this updated code: 1. You set `stopWhen` to be when `isStepCount` 5, allowing the model to use up to 5 "steps" for any given generation. 2. You add an `onStepEnd` callback to log any `toolResults` from each step of the interaction, helping you understand the model's tool usage. Head back to the browser and ask about the weather in a location. You should now see the model using the weather tool results to answer your question. By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Celsius to Fahrenheit. ### Add another tool Update your `app/api/chat/route.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius: ```tsx title="app/api/chat/route.ts" {34-47} import { streamText, UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), convertFahrenheitToCelsius: tool({ description: 'Convert a temperature in fahrenheit to celsius', inputSchema: z.object({ temperature: z .number() .describe('The temperature in fahrenheit to convert'), }), execute: async ({ temperature }) => { const celsius = Math.round((temperature - 32) * (5 / 9)); return { celsius, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ### Update Your Frontend update your `app/page.tsx` file to render the new temperature conversion tool: ```tsx title="app/page.tsx" {19} 'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, i) => { switch (part.type) { case 'text': return
{part.text}
; case 'tool-weather': case 'tool-convertFahrenheitToCelsius': return (
                    {JSON.stringify(part, null, 2)}
                  
); } })}
))}
{ e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.currentTarget.value)} />
); } ``` This update handles the new `tool-convertFahrenheitToCelsius` part type, displaying the temperature conversion tool calls and results in the UI. Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction: 1. The model will call the weather tool for New York. 2. You'll see the tool output displayed. 3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius. 4. The model will then use that information to provide a natural language response about the weather in New York. This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful. This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information. ## Where to Next? You've built an AI chatbot using the AI SDK! From here, you have several paths to explore: - To learn more about the AI SDK, read through the [documentation](/docs). - If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides. - To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Next.js Pages Router description: Learn how to build your first agent with the AI SDK and Next.js Pages Router. url: "https://ai-sdk.dev/docs/getting-started/nextjs-pages-router" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications. In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the AI SDK in your own projects. If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first. ## Prerequisites To follow this quickstart, you'll need: - Node.js 22+ and pnpm installed on your local development machine. - A [Vercel AI Gateway](https://vercel.com/ai-gateway) API key. If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website. ## Setup Your Application Start by creating a new Next.js application. This command will create a new directory named `my-ai-app` and set up a basic Next.js application inside it. Be sure to select no when prompted to use the App Router. If you are looking for the Next.js App Router quickstart guide, you can find it [here](/docs/getting-started/nextjs-app-router). ``` pnpm create next-app@latest my-ai-app ``` Navigate to the newly created directory: ``` cd my-ai-app ``` ### Install dependencies Install `ai` and `@ai-sdk/react`, the AI package and AI SDK's React hooks. The AI SDK's [Vercel AI Gateway provider](/providers/ai-sdk-providers/ai-gateway) ships with the `ai` package. You'll also install `zod`, a schema validation library used for defining tool inputs. This guide uses the Vercel AI Gateway provider so you can access hundreds of models from different providers with one API key, but you can switch to any provider or model by installing its package. Check out available [AI SDK providers](/providers/ai-sdk-providers) for more information. ```bash pnpm add ai @ai-sdk/react zod ``` ### Configure your AI Gateway API key Create a `.env.local` file in your project root and add your AI Gateway API key. This key authenticates your application with the Vercel AI Gateway. ``` touch .env.local ``` Edit the `.env.local` file: ```dotenv title=".env.local" AI_GATEWAY_API_KEY=xxxxxxxxx ``` Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key. The AI SDK's Vercel AI Gateway Provider will default to using the `AI_GATEWAY_API_KEY` environment variable. ## Create a Route Handler As long as you are on Next.js 13+, you can use Route Handlers (using the App Router) alongside the Pages Router. This is recommended to enable you to use the Web APIs interface/signature and to better support streaming. Create a Route Handler (`app/api/chat/route.ts`) and add the following code: ```tsx title="app/api/chat/route.ts" import { streamText, UIMessage, convertToModelMessages, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` Let's take a look at what is happening in this code: 1. Define an asynchronous `POST` request handler and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation. The `messages` are of UIMessage type, which are designed for use in application UI - they contain the entire message history and associated metadata like timestamps. 2. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider and `messages` (defined in step 1). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior. The `messages` key expects a `ModelMessage[]` array. This type is different from `UIMessage` in that it does not include metadata, such as timestamps or sender information. To convert between these types, we use the `convertToModelMessages` function, which strips the UI-specific metadata and transforms the `UIMessage[]` array into the `ModelMessage[]` format that the model expects. 3. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result-object). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object. 4. Finally, return the result to the client to stream the response. This Route Handler creates a POST request endpoint at `/api/chat`. ## Choosing a Provider The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [community](/providers/community-providers) packages. This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration: ```ts model: "anthropic/claude-sonnet-5.5"; ``` You can also explicitly import and use the gateway provider in two other equivalent ways: ```ts // Option 1: Import from 'ai' package (included by default) import { gateway } from 'ai'; model: gateway('anthropic/claude-sonnet-5.5'); // Option 2: Install and import from '@ai-sdk/gateway' package import { gateway } from '@ai-sdk/gateway'; model: gateway('anthropic/claude-sonnet-5.5'); ``` ### Using other providers To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly: ```bash pnpm add @ai-sdk/openai ``` ```ts import { openai } from '@ai-sdk/openai'; model: openai('gpt-6-astra'); ``` #### Updating the global provider You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration). Pick the approach that best matches how you want to manage providers across your application. ## Wire up the UI Now that you have an API route that can query an LLM, it's time to set up your frontend. The AI SDK's [UI](/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat). Update your root page (`pages/index.tsx`) with the following code to show a list of chat messages and provide a user message input: ```tsx title="pages/index.tsx" import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, i) => { switch (part.type) { case 'text': return
{part.text}
; } })}
))}
{ e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.currentTarget.value)} />
); } ``` This page utilizes the `useChat` hook, which will, by default, use the `POST` API route you created earlier (`/api/chat`). The hook provides functions and state for handling user input and form submission. The `useChat` hook provides multiple utility functions and state variables: - `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties). - `sendMessage` - a function to send a message to the chat API. The component uses local state (`useState`) to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field. The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated. ## Running Your Application With that, you have built everything you need for your chatbot! To start your application, use the command: ``` pnpm run dev ``` Head to your browser and open [http://localhost:3000](http://localhost:3000). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Next.js. ## Enhance Your Chatbot with Tools While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in. Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response. For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information. ### Update Your Route Handler Let's start by giving your chatbot a weather tool. Update your Route Handler (`app/api/chat/route.ts`): ```tsx title="app/api/chat/route.ts" {1-8,18-32} import { streamText, UIMessage, convertToModelMessages, tool, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` In this updated code: 1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation. 2. You define a `tools` object with a `weather` tool. This tool: - Has a description that helps the model understand when to use it. - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information. - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API. Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message. Try asking something like "What's the weather in New York?" and see how the model uses the new tool. Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array. Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key you used when defining the tool. In this case, since we defined the tool as `weather`, the part type is `tool-weather`. ### Update the UI To display the tool invocations in your UI, update your `pages/index.tsx` file: ```tsx title="pages/index.tsx" {16-21} import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, i) => { switch (part.type) { case 'text': return
{part.text}
; case 'tool-weather': return (
                    {JSON.stringify(part, null, 2)}
                  
); } })}
))}
{ e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.currentTarget.value)} />
); } ``` With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result. Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface. ## Enabling Multi-Step Tool Calls You may have noticed that while the tool is now visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation. To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question. ### Update Your Route Handler Modify your `app/api/chat/route.ts` file to include the `stopWhen` condition: ```tsx title="app/api/chat/route.ts" {19} import { streamText, UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` Head back to the browser and ask about the weather in a location. You should now see the model using the weather tool results to answer your question. By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius. ### Add another tool Update your `app/api/chat/route.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius: ```tsx title="app/api/chat/route.ts" {34-47} import { streamText, UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), convertFahrenheitToCelsius: tool({ description: 'Convert a temperature in fahrenheit to celsius', inputSchema: z.object({ temperature: z .number() .describe('The temperature in fahrenheit to convert'), }), execute: async ({ temperature }) => { const celsius = Math.round((temperature - 32) * (5 / 9)); return { celsius, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ### Update Your Frontend Update your `pages/index.tsx` file to render the new temperature conversion tool: ```tsx title="pages/index.tsx" {17} import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, i) => { switch (part.type) { case 'text': return
{part.text}
; case 'tool-weather': case 'tool-convertFahrenheitToCelsius': return (
                    {JSON.stringify(part, null, 2)}
                  
); } })}
))}
{ e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.currentTarget.value)} />
); } ``` This update handles the new `tool-convertFahrenheitToCelsius` part type, displaying the temperature conversion tool calls and results in the UI. Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction: 1. The model will call the weather tool for New York. 2. You'll see the tool output displayed. 3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius. 4. The model will then use that information to provide a natural language response about the weather in New York. This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful. This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information. ## Where to Next? You've built an AI chatbot using the AI SDK! From here, you have several paths to explore: - To learn more about the AI SDK, read through the [documentation](/docs). - If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides. - To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Node.js description: Learn how to build your first agent with the AI SDK and Node.js. url: "https://ai-sdk.dev/docs/getting-started/nodejs" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications. In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the SDK in your own projects. If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first. ## Prerequisites To follow this quickstart, you'll need: - Node.js 22+ and pnpm installed on your local development machine. - A [Vercel AI Gateway](https://vercel.com/ai-gateway) API key. If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website. ## Setup Your Application Start by creating a new directory using the `mkdir` command. Change into your new directory and then run the `pnpm init` command. This will create a `package.json` in your new directory. ```bash mkdir my-ai-app cd my-ai-app pnpm init ``` ### Install Dependencies Install `ai`, the AI SDK, along with other necessary dependencies. The AI SDK is designed to be a unified interface to interact with any large language model. This means that you can change model and providers with just one line of code! Learn more about [available providers](/providers) and [building custom providers](/providers/community-providers/custom-providers) in the [providers](/providers) section. ```bash pnpm add ai zod dotenv pnpm add -D @types/node tsx typescript ``` The `ai` package contains the AI SDK. You will use `zod` to define type-safe schemas that you will pass to the large language model (LLM). You will use `dotenv` to access environment variables (your Vercel AI Gateway key) within your application. There are also three development dependencies, installed with the `-D` flag, that are necessary to run your TypeScript code. ### Configure Vercel AI Gateway API key Create a `.env` file in your project's root directory and add your Vercel AI Gateway API Key. This key is used to authenticate your application with the Vercel AI Gateway service. ``` touch .env ``` Edit the `.env` file: ```dotenv title=".env" AI_GATEWAY_API_KEY=xxxxxxxxx ``` Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key. The AI SDK will use the `AI_GATEWAY_API_KEY` environment variable to authenticate with Vercel AI Gateway. ## Create Your Application Create an `index.ts` file in the root of your project and add the following code: ```ts title="index.ts" import { ModelMessage, streamText } from 'ai'; import 'dotenv/config'; import * as readline from 'node:readline/promises'; const terminal = readline.createInterface({ input: process.stdin, output: process.stdout, }); const messages: ModelMessage[] = []; async function main() { while (true) { const userInput = await terminal.question('You: '); messages.push({ role: 'user', content: userInput }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, }); let fullResponse = ''; process.stdout.write('\nAssistant: '); for await (const delta of result.textStream) { fullResponse += delta; process.stdout.write(delta); } process.stdout.write('\n\n'); messages.push({ role: 'assistant', content: fullResponse }); } } main().catch(console.error); ``` Let's take a look at what is happening in this code: 1. Set up a readline interface to take input from the terminal, enabling interactive sessions directly from the command line. 2. Initialize an array called `messages` to store the history of your conversation. This history allows the agent to maintain context in ongoing dialogues. 3. In the `main` function: - Prompt for and capture user input, storing it in `userInput`. - Add user input to the `messages` array as a user message. - Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider and `messages`. - Iterate over the text stream returned by the `streamText` function (`result.textStream`) and print the contents of the stream to the terminal. - Add the assistant's response to the `messages` array. ## Running Your Application With that, you have built everything you need for your agent! To start your application, use the command: ``` pnpm tsx index.ts ``` You should see a prompt in your terminal. Test it out by entering a message and see the AI agent respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Node.js. ## Choosing a Provider The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [community](/providers/community-providers) packages. This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration: ```ts model: "anthropic/claude-sonnet-5.5"; ``` You can also explicitly import and use the gateway provider in two other equivalent ways: ```ts // Option 1: Import from 'ai' package (included by default) import { gateway } from 'ai'; model: gateway('anthropic/claude-sonnet-5.5'); // Option 2: Install and import from '@ai-sdk/gateway' package import { gateway } from '@ai-sdk/gateway'; model: gateway('anthropic/claude-sonnet-5.5'); ``` ### Using other providers To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly: ```bash pnpm add @ai-sdk/openai ``` ```ts import { openai } from '@ai-sdk/openai'; model: openai('gpt-6-astra'); ``` ## Enhance Your Agent with Tools While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in. Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response. For example, if a user asks about the current weather, without tools, the agent would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information. Let's enhance your agent by adding a simple weather tool. ### Update Your Application Modify your `index.ts` file to include the new weather tool: ```ts title="index.ts" {2,4,24-38} import { ModelMessage, streamText, tool } from 'ai'; import 'dotenv/config'; import { z } from 'zod'; import * as readline from 'node:readline/promises'; const terminal = readline.createInterface({ input: process.stdin, output: process.stdout, }); const messages: ModelMessage[] = []; async function main() { while (true) { const userInput = await terminal.question('You: '); messages.push({ role: 'user', content: userInput }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z .string() .describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); let fullResponse = ''; process.stdout.write('\nAssistant: '); for await (const delta of result.textStream) { fullResponse += delta; process.stdout.write(delta); } process.stdout.write('\n\n'); messages.push({ role: 'assistant', content: fullResponse }); } } main().catch(console.error); ``` In this updated code: 1. You import the `tool` function from the `ai` package. 2. You define a `tools` object with a `weather` tool. This tool: - Has a description that helps the agent understand when to use it. - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The agent will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information. - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API. Now your agent can "fetch" weather information for any location the user asks about. When the agent determines it needs to use the weather tool, it will generate a tool call with the necessary parameters. The `execute` function will then be automatically run, and the results will be used by the agent to generate its response. Try asking something like "What's the weather in New York?" and see how the agent uses the new tool. Notice the blank "assistant" response? This is because instead of generating a text response, the agent generated a tool call. You can access the tool call and subsequent tool result in the `toolCalls` and `toolResults` properties of the result object. ```typescript {50-51} import { ModelMessage, streamText, tool } from 'ai'; import 'dotenv/config'; import { z } from 'zod'; import * as readline from 'node:readline/promises'; const terminal = readline.createInterface({ input: process.stdin, output: process.stdout, }); const messages: ModelMessage[] = []; async function main() { while (true) { const userInput = await terminal.question('You: '); messages.push({ role: 'user', content: userInput }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z .string() .describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); let fullResponse = ''; process.stdout.write('\nAssistant: '); for await (const delta of result.textStream) { fullResponse += delta; process.stdout.write(delta); } process.stdout.write('\n\n'); console.log(await result.toolCalls); console.log(await result.toolResults); messages.push({ role: 'assistant', content: fullResponse }); } } main().catch(console.error); ``` Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface. ## Enabling Multi-Step Tool Calls You may have noticed that while the tool results are visible in the chat interface, the agent isn't using this information to answer your original query. This is because once the agent generates a tool call, it has technically completed its generation. To solve this, you can enable multi-step tool calls using `stopWhen`. This feature will automatically send tool results back to the agent to trigger an additional generation until the stopping condition you define is met. In this case, you want the agent to answer your question using the results from the weather tool. ### Update Your Application Modify your `index.ts` file to configure stopping conditions with `stopWhen`: ```ts title="index.ts" {1,40-45} import { ModelMessage, streamText, tool, isStepCount } from 'ai'; import 'dotenv/config'; import { z } from 'zod'; import * as readline from 'node:readline/promises'; const terminal = readline.createInterface({ input: process.stdin, output: process.stdout, }); const messages: ModelMessage[] = []; async function main() { while (true) { const userInput = await terminal.question('You: '); messages.push({ role: 'user', content: userInput }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z .string() .describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, stopWhen: isStepCount(5), onStepEnd: async ({ toolResults }) => { if (toolResults.length) { console.log(JSON.stringify(toolResults, null, 2)); } }, }); let fullResponse = ''; process.stdout.write('\nAssistant: '); for await (const delta of result.textStream) { fullResponse += delta; process.stdout.write(delta); } process.stdout.write('\n\n'); messages.push({ role: 'assistant', content: fullResponse }); } } main().catch(console.error); ``` In this updated code: 1. You set `stopWhen` to be when `isStepCount` 5, allowing the agent to use up to 5 "steps" for any given generation. 2. You add an `onStepEnd` callback to log any `toolResults` from each step of the interaction, helping you understand the agent's tool usage. This means we can also delete the `toolCalls` and `toolResults` `console.log` statements from the previous example. Now, when you ask about the weather in a location, you should see the agent using the weather tool results to answer your question. By setting `stopWhen: isStepCount(5)`, you're allowing the agent to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the agent to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius. ### Adding a second tool Update your `index.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius: ```ts title="index.ts" {39-52} import { ModelMessage, streamText, tool, isStepCount } from 'ai'; import 'dotenv/config'; import { z } from 'zod'; import * as readline from 'node:readline/promises'; const terminal = readline.createInterface({ input: process.stdin, output: process.stdout, }); const messages: ModelMessage[] = []; async function main() { while (true) { const userInput = await terminal.question('You: '); messages.push({ role: 'user', content: userInput }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z .string() .describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), convertFahrenheitToCelsius: tool({ description: 'Convert a temperature in fahrenheit to celsius', inputSchema: z.object({ temperature: z .number() .describe('The temperature in fahrenheit to convert'), }), execute: async ({ temperature }) => { const celsius = Math.round((temperature - 32) * (5 / 9)); return { celsius, }; }, }), }, stopWhen: isStepCount(5), onStepEnd: async ({ toolResults }) => { if (toolResults.length) { console.log(JSON.stringify(toolResults, null, 2)); } }, }); let fullResponse = ''; process.stdout.write('\nAssistant: '); for await (const delta of result.textStream) { fullResponse += delta; process.stdout.write(delta); } process.stdout.write('\n\n'); messages.push({ role: 'assistant', content: fullResponse }); } } main().catch(console.error); ``` Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction: 1. The agent will call the weather tool for New York. 2. You'll see the tool result logged. 3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius. 4. The agent will then use that information to provide a natural language response about the weather in New York. This multi-step approach allows the agent to gather information and use it to provide more accurate and contextual responses, making your agent considerably more useful. This example demonstrates how tools can expand your agent's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the agent to access and process real-world data in real-time and perform actions that interact with the outside world. Tools bridge the gap between the agent's knowledge cutoff and current information, while also enabling it to take meaningful actions beyond just generating text responses. ## Where to Next? You've built an AI agent using the AI SDK! From here, you have several paths to explore: - To learn more about the AI SDK, read through the [documentation](/docs). - If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides. - To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Vue.js (Nuxt) description: Learn how to build your first agent with the AI SDK and Vue.js (Nuxt). url: "https://ai-sdk.dev/docs/getting-started/nuxt" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications. In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the SDK in your own projects. If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first. ## Prerequisites To follow this quickstart, you'll need: - Node.js 22+ and pnpm installed on your local development machine. - A [Vercel AI Gateway](https://vercel.com/ai-gateway) API key. If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website. ## Setup Your Application Start by creating a new Nuxt application. This command will create a new directory named `my-ai-app` and set up a basic Nuxt application inside it. ``` pnpm create nuxt my-ai-app ``` Navigate to the newly created directory: ``` cd my-ai-app ``` ### Install dependencies Install `ai` and `@ai-sdk/vue`. The Vercel AI Gateway provider ships with the `ai` package. The AI SDK is designed to be a unified interface to interact with any large language model. This means that you can change model and providers with just one line of code! Learn more about [available providers](/providers) and [building custom providers](/providers/community-providers/custom-providers) in the [providers](/providers) section. ```bash pnpm add ai @ai-sdk/vue zod ``` ### Configure Vercel AI Gateway API key Create a `.env` file in your project root and add your Vercel AI Gateway API Key. This key is used to authenticate your application with the Vercel AI Gateway service. ``` touch .env ``` Edit the `.env` file: ```dotenv title=".env" NUXT_AI_GATEWAY_API_KEY=xxxxxxxxx ``` Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key and configure the environment variable in `nuxt.config.ts`: ```ts title="nuxt.config.ts" export default defineNuxtConfig({ // rest of your nuxt config runtimeConfig: { aiGatewayApiKey: '', }, }); ``` This guide uses Nuxt's runtime config to manage the API key. The `NUXT_` prefix in the environment variable allows Nuxt to automatically load it into the runtime config. While the AI Gateway Provider also supports a default `AI_GATEWAY_API_KEY` environment variable, this approach provides better integration with Nuxt's configuration system. ## Create an API route Create an API route, `server/api/chat.ts` and add the following code: ```typescript title="server/api/chat.ts" import { streamText, UIMessage, convertToModelMessages, createGateway, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; export default defineLazyEventHandler(async () => { const apiKey = useRuntimeConfig().aiGatewayApiKey; if (!apiKey) throw new Error('Missing AI Gateway API key'); const gateway = createGateway({ apiKey: apiKey, }); return defineEventHandler(async (event: any) => { const { messages }: { messages: UIMessage[] } = await readBody(event); const result = streamText({ model: gateway('anthropic/claude-sonnet-5.5'), messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); }); }); ``` Let's take a look at what is happening in this code: 1. Create a gateway provider instance with the `createGateway` function from the `ai` package. 2. Define an Event Handler and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation. The `messages` are of UIMessage type, which are designed for use in application UI - they contain the entire message history and associated metadata like timestamps. 3. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider (defined in step 1) and `messages` (defined in step 2). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior. The `messages` key expects a `ModelMessage[]` array. This type is different from `UIMessage` in that it does not include metadata, such as timestamps or sender information. To convert between these types, we use the `convertToModelMessages` function, which strips the UI-specific metadata and transforms the `UIMessage[]` array into the `ModelMessage[]` format that the model expects. 4. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object. 5. Return the result to the client to stream the response. ## Choosing a Provider The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [community](/providers/community-providers) packages. This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration: ```ts model: "anthropic/claude-sonnet-5.5"; ``` You can also explicitly import and use the gateway provider in two other equivalent ways: ```ts // Option 1: Import from 'ai' package (included by default) import { gateway } from 'ai'; model: gateway('anthropic/claude-sonnet-5.5'); // Option 2: Install and import from '@ai-sdk/gateway' package import { gateway } from '@ai-sdk/gateway'; model: gateway('anthropic/claude-sonnet-5.5'); ``` ### Using other providers To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly: ```bash pnpm add @ai-sdk/openai ``` ```ts import { openai } from '@ai-sdk/openai'; model: openai('gpt-6-astra'); ``` ## Wire up the UI Now that you have an API route that can query an LLM, it's time to set up your frontend. The AI SDK's [UI](/docs/ai-sdk-ui/overview) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat). Update your root page (`pages/index.vue`) with the following code to show a list of chat messages and provide a user message input: ```typescript title="pages/index.vue" ``` If your project has `app.vue` instead of `pages/index.vue`, delete the `app.vue` file and create a new `pages/index.vue` file with the code above. This page utilizes the `useChat` hook, which will, by default, use the API route you created earlier (`/api/chat`). The hook provides functions and state for handling user input and form submission. The `useChat` hook provides multiple utility functions and state variables: - `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties). - `sendMessage` - a function to send a message to the chat API. The component uses local state (`ref`) to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field. The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated. ## Running Your Application With that, you have built everything you need for your chatbot! To start your application, use the command: ``` pnpm run dev ``` Head to your browser and open [http://localhost:3000](http://localhost:3000). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Nuxt. ## Enhance Your Chatbot with Tools While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in. Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response. For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information. Let's enhance your chatbot by adding a simple weather tool. ### Update Your API Route Modify your `server/api/chat.ts` file to include the new weather tool: ```typescript title="server/api/chat.ts" {5-8,25-41} import { createGateway, streamText, UIMessage, convertToModelMessages, tool, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export default defineLazyEventHandler(async () => { const apiKey = useRuntimeConfig().aiGatewayApiKey; if (!apiKey) throw new Error('Missing AI Gateway API key'); const gateway = createGateway({ apiKey: apiKey, }); return defineEventHandler(async (event: any) => { const { messages }: { messages: UIMessage[] } = await readBody(event); const result = streamText({ model: gateway('anthropic/claude-sonnet-5.5'), messages: await convertToModelMessages(messages), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z .string() .describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); }); }); ``` In this updated code: 1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation. 2. You define a `tools` object with a `weather` tool. This tool: - Has a description that helps the model understand when to use it. - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information. - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API. Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message. Try asking something like "What's the weather in New York?" and see how the model uses the new tool. Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array. Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key you used when defining the tool. In this case, since we defined the tool as `weather`, the part type is `tool-weather`. ### Update the UI To display the tool invocation in your UI, update your `pages/index.vue` file: ```typescript title="pages/index.vue" {24} ``` With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result. Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface. ## Enabling Multi-Step Tool Calls You may have noticed that while the tool is now visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation. To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question. ### Update Your API Route Modify your `server/api/chat.ts` file to include the `stopWhen` condition: ```typescript title="server/api/chat.ts" {26} import { createGateway, streamText, UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export default defineLazyEventHandler(async () => { const apiKey = useRuntimeConfig().aiGatewayApiKey; if (!apiKey) throw new Error('Missing AI Gateway API key'); const gateway = createGateway({ apiKey: apiKey, }); return defineEventHandler(async (event: any) => { const { messages }: { messages: UIMessage[] } = await readBody(event); const result = streamText({ model: gateway('anthropic/claude-sonnet-5.5'), messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z .string() .describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); }); }); ``` Head back to the browser and ask about the weather in a location. You should now see the model using the weather tool results to answer your question. By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius. ### Add another tool Update your `server/api/chat.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius: ```typescript title="server/api/chat.ts" {43-56} import { createGateway, streamText, UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; export default defineLazyEventHandler(async () => { const apiKey = useRuntimeConfig().aiGatewayApiKey; if (!apiKey) throw new Error('Missing AI Gateway API key'); const gateway = createGateway({ apiKey: apiKey, }); return defineEventHandler(async (event: any) => { const { messages }: { messages: UIMessage[] } = await readBody(event); const result = streamText({ model: gateway('anthropic/claude-sonnet-5.5'), messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z .string() .describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), convertFahrenheitToCelsius: tool({ description: 'Convert a temperature in fahrenheit to celsius', inputSchema: z.object({ temperature: z .number() .describe('The temperature in fahrenheit to convert'), }), execute: async ({ temperature }) => { const celsius = Math.round((temperature - 32) * (5 / 9)); return { celsius, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); }); }); ``` ### Update Your Frontend Update your UI to handle the new temperature conversion tool by modifying the tool part handling: ```typescript title="pages/index.vue" {24-29} ``` This update handles the new `tool-convertFahrenheitToCelsius` part type, displaying the temperature conversion tool calls and results in the UI. Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction: 1. The model will call the weather tool for New York. 2. You'll see the tool output displayed. 3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius. 4. The model will then use that information to provide a natural language response about the weather in New York. This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful. This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information. ## Where to Next? You've built an AI chatbot using the AI SDK! From here, you have several paths to explore: - To learn more about the AI SDK, read through the [documentation](/docs). - If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides. - To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Svelte description: Learn how to build your first agent with the AI SDK and Svelte. url: "https://ai-sdk.dev/docs/getting-started/svelte" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications. In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the SDK in your own projects. If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first. ## Prerequisites To follow this quickstart, you'll need: - Node.js 22+ and pnpm installed on your local development machine. - A [Vercel AI Gateway](https://vercel.com/ai-gateway) API key. If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website. ## Set Up Your Application Start by creating a new SvelteKit application. This command will create a new directory named `my-ai-app` and set up a basic SvelteKit application inside it. ``` npx sv create my-ai-app ``` Navigate to the newly created directory: ``` cd my-ai-app ``` ### Install Dependencies Install `ai` and `@ai-sdk/svelte`, the AI package and AI SDK's Svelte bindings. The AI SDK's [Vercel AI Gateway provider](/providers/ai-sdk-providers/ai-gateway) ships with the `ai` package. You'll also install `zod`, a schema validation library used for defining tool inputs. This guide uses the Vercel AI Gateway provider so you can access hundreds of models from different providers with one API key, but you can switch to any provider or model by installing its package. Check out available [AI SDK providers](/providers/ai-sdk-providers) for more information. ```bash pnpm add -D ai @ai-sdk/svelte zod ``` ### Configure your AI Gateway API key Create a `.env.local` file in your project root and add your AI Gateway API key. This key authenticates your application with the Vercel AI Gateway. ``` touch .env.local ``` Edit the `.env.local` file: ```dotenv title=".env.local" AI_GATEWAY_API_KEY=xxxxxxxxx ``` Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key. The AI SDK's Vercel AI Gateway Provider will default to using the `AI_GATEWAY_API_KEY` environment variable. Vite does not automatically load environment variables onto `process.env`, so you'll need to import `AI_GATEWAY_API_KEY` from `$env/static/private` in your code (see below). ## Create an API route Create a SvelteKit Endpoint, `src/routes/api/chat/+server.ts` and add the following code: ```tsx title="src/routes/api/chat/+server.ts" import { streamText, type UIMessage, convertToModelMessages, createGateway, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { AI_GATEWAY_API_KEY } from '$env/static/private'; const gateway = createGateway({ apiKey: AI_GATEWAY_API_KEY, }); export async function POST({ request }) { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: gateway('anthropic/claude-sonnet-5.5'), messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` If you see type errors with `AI_GATEWAY_API_KEY` or your `POST` function, run the dev server. Let's take a look at what is happening in this code: 1. Create a gateway provider instance with the `createGateway` function from the `ai` package. 2. Define a `POST` request handler and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation. The `messages` are of UIMessage type, which are designed for use in application UI - they contain the entire message history and associated metadata like timestamps. 3. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider (defined in step 1) and `messages` (defined in step 2). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior. The `messages` key expects a `ModelMessage[]` array. This type is different from `UIMessage` in that it does not include metadata, such as timestamps or sender information. To convert between these types, we use the `convertToModelMessages` function, which strips the UI-specific metadata and transforms the `UIMessage[]` array into the `ModelMessage[]` format that the model expects. 4. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result-object). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object. 5. Return the result to the client to stream the response. ## Choosing a Provider The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [community](/providers/community-providers) packages. This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration: ```ts model: "anthropic/claude-sonnet-5.5"; ``` You can also explicitly import and use the gateway provider in two other equivalent ways: ```ts // Option 1: Import from 'ai' package (included by default) import { gateway } from 'ai'; model: gateway('anthropic/claude-sonnet-5.5'); // Option 2: Install and import from '@ai-sdk/gateway' package import { gateway } from '@ai-sdk/gateway'; model: gateway('anthropic/claude-sonnet-5.5'); ``` ### Using other providers To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly: ```bash pnpm add @ai-sdk/openai ``` ```ts import { openai } from '@ai-sdk/openai'; model: openai('gpt-6-astra'); ``` #### Updating the global provider You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration). Pick the approach that best matches how you want to manage providers across your application. ## Wire up the UI Now that you have an API route that can query an LLM, it's time to set up your frontend. The AI SDK's [UI](/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one class, `Chat`. Its properties and API are largely the same as React's [`useChat`](/docs/reference/ai-sdk-ui/use-chat). Update your root page (`src/routes/+page.svelte`) with the following code to show a list of chat messages and provide a user message input: ```svelte title="src/routes/+page.svelte"
    {#each chat.messages as message, messageIndex (messageIndex)}
  • {message.role}
    {#each message.parts as part, partIndex (partIndex)} {#if part.type === 'text'}
    {part.text}
    {/if} {/each}
  • {/each}
``` This page utilizes the `Chat` class, which will, by default, use the `POST` route handler you created earlier. The class provides functions and state for handling user input and form submission. The `Chat` class provides multiple utility functions and state variables: - `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties). - `sendMessage` - a function to send a message to the chat API. The component uses local state to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field. The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated. ## Running Your Application With that, you have built everything you need for your chatbot! To start your application, use the command: ``` pnpm run dev ``` Head to your browser and open [http://localhost:5173](http://localhost:5173). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with Svelte. ## Enhance Your Chatbot with Tools While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in. Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response. For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information. Let's enhance your chatbot by adding a simple weather tool. ### Update Your API Route Modify your `src/routes/api/chat/+server.ts` file to include the new weather tool: ```tsx title="src/routes/api/chat/+server.ts" {5-8,25-39} import { createGateway, streamText, type UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; import { AI_GATEWAY_API_KEY } from '$env/static/private'; const gateway = createGateway({ apiKey: AI_GATEWAY_API_KEY, }); export async function POST({ request }) { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: gateway('anthropic/claude-sonnet-5.5'), messages: await convertToModelMessages(messages), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` In this updated code: 1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation. 2. You define a `tools` object with a `weather` tool. This tool: - Has a description that helps the model understand when to use it. - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information. - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API. Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message. Try asking something like "What's the weather in New York?" and see how the model uses the new tool. Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array. Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key you used when defining the tool. In this case, since we defined the tool as `weather`, the part type is `tool-weather`. ### Update the UI To display the tool invocation in your UI, update your `src/routes/+page.svelte` file: ```svelte title="src/routes/+page.svelte"
    {#each chat.messages as message, messageIndex (messageIndex)}
  • {message.role}
    {#each message.parts as part, partIndex (partIndex)} {#if part.type === 'text'}
    {part.text}
    {:else if part.type === 'tool-weather'}
    {JSON.stringify(part, null, 2)}
    {/if} {/each}
  • {/each}
``` With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result. Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface. ## Enabling Multi-Step Tool Calls You may have noticed that while the tool is now visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation. To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question. ### Update Your API Route Modify your `src/routes/api/chat/+server.ts` file to include the `stopWhen` condition: ```ts title="src/routes/api/chat/+server.ts" {25} import { createGateway, streamText, type UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; import { AI_GATEWAY_API_KEY } from '$env/static/private'; const gateway = createGateway({ apiKey: AI_GATEWAY_API_KEY, }); export async function POST({ request }) { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: gateway('anthropic/claude-sonnet-5.5'), messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` Head back to the browser and ask about the weather in a location. You should now see the model using the weather tool results to answer your question. By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius. ### Add another tool Update your `src/routes/api/chat/+server.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius: ```tsx title="src/routes/api/chat/+server.ts" {40-53} import { createGateway, streamText, type UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { z } from 'zod'; import { AI_GATEWAY_API_KEY } from '$env/static/private'; const gateway = createGateway({ apiKey: AI_GATEWAY_API_KEY, }); export async function POST({ request }) { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: gateway('anthropic/claude-sonnet-5.5'), messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), convertFahrenheitToCelsius: tool({ description: 'Convert a temperature in fahrenheit to celsius', inputSchema: z.object({ temperature: z .number() .describe('The temperature in fahrenheit to convert'), }), execute: async ({ temperature }) => { const celsius = Math.round((temperature - 32) * (5 / 9)); return { celsius, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ### Update Your Frontend Update your UI to handle the new temperature conversion tool by modifying the tool part handling: ```svelte title="src/routes/+page.svelte" {23-24}
    {#each chat.messages as message, messageIndex (messageIndex)}
  • {message.role}
    {#each message.parts as part, partIndex (partIndex)} {#if part.type === 'text'}
    {part.text}
    {:else if part.type === 'tool-weather' || part.type === 'tool-convertFahrenheitToCelsius'}
    {JSON.stringify(part, null, 2)}
    {/if} {/each}
  • {/each}
``` This update handles the new `tool-convertFahrenheitToCelsius` part type, displaying the temperature conversion tool calls and results in the UI. Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction: 1. The model will call the weather tool for New York. 2. You'll see the tool output displayed. 3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius. 4. The model will then use that information to provide a natural language response about the weather in New York. This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful. This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information. ## How does `@ai-sdk/svelte` differ from `@ai-sdk/react`? The surface-level difference is that Svelte uses classes to manage state, whereas React uses hooks, so `useChat` in React is `Chat` in Svelte. Other than that, there are a few things to keep in mind: ### 1. Arguments to classes aren't reactive by default Unlike in React, where hooks are rerun any time their containing component is invalidated, code in the `script` block of a Svelte component is only run once when the component is created. This means that, if you want arguments to your class to be reactive, you need to make sure you pass a *reference* into the class, rather than a value: ```svelte ``` Keep in mind that this normally doesn't matter; most parameters you'll pass into the Chat class are static (for example, you typically wouldn't expect your `onError` handler to change). ### 2. You can't destructure class properties In vanilla JavaScript, destructuring class properties copies them by value and "disconnects" them from their class instance: ```js const classInstance = new Whatever(); classInstance.foo = 'bar'; const { foo } = classInstance; classInstance.foo = 'baz'; console.log(foo); // 'bar' ``` The same is true of classes in Svelte: ```svelte ``` ### 3. Instance synchronization requires context In React, hook instances with the same `id` are synchronized -- so two instances of `useChat` will have the same `messages`, `status`, etc. if they have the same `id`. For most use cases, you probably don't need this behavior -- but if you do, you can create a context in your root layout file using `createAIContext`: ```svelte {@render children()} ``` ## Where to Next? You've built an AI chatbot using the AI SDK! From here, you have several paths to explore: - To learn more about the AI SDK, read through the [documentation](/docs). - If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides. - To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai). - To learn more about Svelte, check out the [official documentation](https://svelte.dev/docs/svelte). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: TanStack Start description: Learn how to build your first agent with the AI SDK and TanStack Start. url: "https://ai-sdk.dev/docs/getting-started/tanstack-start" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK is a powerful TypeScript library designed to help developers build AI-powered applications. In this quickstart tutorial, you'll build a simple agent with a streaming chat user interface. Along the way, you'll learn key concepts and techniques that are fundamental to using the AI SDK in your own projects. If you are unfamiliar with the concepts of [Prompt Engineering](/docs/advanced/prompt-engineering) and [HTTP Streaming](/docs/foundations/streaming), you can optionally read these documents first. ## Prerequisites To follow this quickstart, you'll need: - Node.js 22+ and pnpm installed on your local development machine. - A [Vercel AI Gateway](https://vercel.com/ai-gateway) API key. If you haven't obtained your Vercel AI Gateway API key, you can do so by [signing up](https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai\&title=Go+to+AI+Gateway) on the Vercel website. ## Create Your Application Start by creating a new TanStack Start application. This command will create a new directory named `my-ai-app` and set up a basic TanStack Start application inside it. ``` pnpm create @tanstack/start@latest my-ai-app ``` Navigate to the newly created directory: ``` cd my-ai-app ``` ### Install dependencies Install `ai` and `@ai-sdk/react`, the AI package and AI SDK's React hooks. The AI SDK's [Vercel AI Gateway provider](/providers/ai-sdk-providers/ai-gateway) ships with the `ai` package. You'll also install `zod`, a schema validation library used for defining tool inputs. This guide uses the Vercel AI Gateway provider so you can access hundreds of models from different providers with one API key, but you can switch to any provider or model by installing its package. Check out available [AI SDK providers](/providers/ai-sdk-providers) for more information. ```bash pnpm add ai @ai-sdk/react zod ``` ### Configure your AI Gateway API key Create a `.env` file in your project root and add your AI Gateway API key. This key authenticates your application with Vercel AI Gateway. ``` touch .env ``` Edit the `.env` file: ```dotenv title=".env" AI_GATEWAY_API_KEY=xxxxxxxxx ``` Replace `xxxxxxxxx` with your actual Vercel AI Gateway API key. The AI SDK's Vercel AI Gateway Provider will default to using the `AI_GATEWAY_API_KEY` environment variable. ## Create a Route Handler Create a route handler, `src/routes/api/chat.ts` and add the following code: ```tsx title="src/routes/api/chat.ts" import { streamText, UIMessage, convertToModelMessages, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { createFileRoute } from '@tanstack/react-router'; export const Route = createFileRoute('/api/chat')({ server: { handlers: { POST: async ({ request }) => { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); }, }, }, }); ``` Let's take a look at what is happening in this code: 1. Define an asynchronous `POST` request handler using TanStack Start's server routes and extract `messages` from the body of the request. The `messages` variable contains a history of the conversation between you and the chatbot and provides the chatbot with the necessary context to make the next generation. The `messages` are of UIMessage type, which are designed for use in application UI - they contain the entire message history and associated metadata like timestamps. 2. Call [`streamText`](/docs/reference/ai-sdk-core/stream-text), which is imported from the `ai` package. This function accepts a configuration object that contains a `model` provider and `messages` (defined in step 1). You can pass additional [settings](/docs/ai-sdk-core/settings) to further customize the model's behavior. The `messages` key expects a `ModelMessage[]` array. This type is different from `UIMessage` in that it does not include metadata, such as timestamps or sender information. To convert between these types, we use the `convertToModelMessages` function, which strips the UI-specific metadata and transforms the `UIMessage[]` array into the `ModelMessage[]` format that the model expects. 3. The `streamText` function returns a [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text#result-object). Pass its `stream` to `toUIMessageStream` and return it with `createUIMessageStreamResponse` to create a streamed response object. 4. Finally, return the result to the client to stream the response. This Route Handler creates a POST request endpoint at `/api/chat`. ## Choosing a Provider The AI SDK supports dozens of model providers through [first-party](/providers/ai-sdk-providers), [OpenAI-compatible](/providers/openai-compatible-providers), and [community](/providers/community-providers) packages. This quickstart uses the [Vercel AI Gateway](https://vercel.com/ai-gateway) provider, which is the default [global provider](/docs/ai-sdk-core/provider-management#global-provider-configuration). This means you can access models using a simple string in the model configuration: ```ts model: "anthropic/claude-sonnet-5.5"; ``` You can also explicitly import and use the gateway provider in two other equivalent ways: ```ts // Option 1: Import from 'ai' package (included by default) import { gateway } from 'ai'; model: gateway('anthropic/claude-sonnet-5.5'); // Option 2: Install and import from '@ai-sdk/gateway' package import { gateway } from '@ai-sdk/gateway'; model: gateway('anthropic/claude-sonnet-5.5'); ``` ### Using other providers To use a different provider, install its package and create a provider instance. For example, to use OpenAI directly: ```bash pnpm add @ai-sdk/openai ``` ```ts import { openai } from '@ai-sdk/openai'; model: openai('gpt-6-astra'); ``` #### Updating the global provider You can change the default global provider so string model references use your preferred provider everywhere in your application. Learn more about [provider management](/docs/ai-sdk-core/provider-management#global-provider-configuration). Pick the approach that best matches how you want to manage providers across your application. ## Wire up the UI Now that you have a Route Handler that can query an LLM, it's time to set up your frontend. The AI SDK's [UI](/docs/ai-sdk-ui) package abstracts the complexity of a chat interface into one hook, [`useChat`](/docs/reference/ai-sdk-ui/use-chat). Update your index route (`src/routes/index.tsx`) with the following code to show a list of chat messages and provide a user message input: ```tsx title="src/routes/index.tsx" import { createFileRoute } from '@tanstack/react-router'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export const Route = createFileRoute('/')({ component: Chat, }); function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, i) => { switch (part.type) { case 'text': return
{part.text}
; } })}
))}
{ e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.currentTarget.value)} />
); } ``` This page utilizes the `useChat` hook, which will, by default, use the `POST` API route you created earlier (`/api/chat`). The hook provides functions and state for handling user input and form submission. The `useChat` hook provides multiple utility functions and state variables: - `messages` - the current chat messages (an array of objects with `id`, `role`, and `parts` properties). - `sendMessage` - a function to send a message to the chat API. The component uses local state (`useState`) to manage the input field value, and handles form submission by calling `sendMessage` with the input text and then clearing the input field. The LLM's response is accessed through the message `parts` array. Each message contains an ordered array of `parts` that represents everything the model generated in its response. These parts can include plain text, reasoning tokens, and more that you will see later. The `parts` array preserves the sequence of the model's outputs, allowing you to display or process each component in the order it was generated. ## Running Your Application With that, you have built everything you need for your chatbot! To start your application, use the command: ``` pnpm run dev ``` Head to your browser and open [http://localhost:3000](http://localhost:3000). You should see an input field. Test it out by entering a message and see the AI chatbot respond in real-time! The AI SDK makes it fast and easy to build AI chat interfaces with TanStack Start. ## Enhance Your Chatbot with Tools While large language models (LLMs) have incredible generation capabilities, they struggle with discrete tasks (e.g. mathematics) and interacting with the outside world (e.g. getting the weather). This is where [tools](/docs/ai-sdk-core/tools-and-tool-calling) come in. Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response. For example, if a user asks about the current weather, without tools, the model would only be able to provide general information based on its training data. But with a weather tool, it can fetch and provide up-to-date, location-specific weather information. Let's enhance your chatbot by adding a simple weather tool. ### Update Your Route Handler Modify your `src/routes/api/chat.ts` file to include the new weather tool: ```tsx title="src/routes/api/chat.ts" {9,22-38} import { streamText, UIMessage, convertToModelMessages, tool, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { createFileRoute } from '@tanstack/react-router'; import { z } from 'zod'; export const Route = createFileRoute('/api/chat')({ server: { handlers: { POST: async ({ request }) => { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z .string() .describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); }, }, }, }); ``` In this updated code: 1. You import the `tool` function from the `ai` package and `z` from `zod` for schema validation. 2. You define a `tools` object with a `weather` tool. This tool: - Has a description that helps the model understand when to use it. - Defines `inputSchema` using a Zod schema, specifying that it requires a `location` string to execute this tool. The model will attempt to extract this input from the context of the conversation. If it can't, it will ask the user for the missing information. - Defines an `execute` function that simulates getting weather data (in this case, it returns a random temperature). This is an asynchronous function running on the server so you can fetch real data from an external API. Now your chatbot can "fetch" weather information for any location the user asks about. When the model determines it needs to use the weather tool, it will generate a tool call with the necessary input. The `execute` function will then be automatically run, and the tool output will be added to the `messages` as a `tool` message. Try asking something like "What's the weather in New York?" and see how the model uses the new tool. Notice the blank response in the UI? This is because instead of generating a text response, the model generated a tool call. You can access the tool call and subsequent tool result on the client via the `tool-weather` part of the `message.parts` array. Tool parts are always named `tool-{toolName}`, where `{toolName}` is the key you used when defining the tool. In this case, since we defined the tool as `weather`, the part type is `tool-weather`. ### Update the UI To display the tool invocation in your UI, update your `src/routes/index.tsx` file: ```tsx title="src/routes/index.tsx" {21-26} import { createFileRoute } from '@tanstack/react-router'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export const Route = createFileRoute('/')({ component: Chat, }); function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, i) => { switch (part.type) { case 'text': return
{part.text}
; case 'tool-weather': return (
                    {JSON.stringify(part, null, 2)}
                  
); } })}
))}
{ e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.currentTarget.value)} />
); } ``` With this change, you're updating the UI to handle different message parts. For text parts, you display the text content as before. For weather tool invocations, you display a JSON representation of the tool call and its result. Now, when you ask about the weather, you'll see the tool call and its result displayed in your chat interface. ## Enabling Multi-Step Tool Calls You may have noticed that while the tool is now visible in the chat interface, the model isn't using this information to answer your original query. This is because once the model generates a tool call, it has technically completed its generation. To solve this, you can enable multi-step tool calls using `stopWhen`. By default, `stopWhen` is set to `isStepCount(1)`, which means generation stops after the first step when there are tool results. By changing this condition, you can allow the model to automatically send tool results back to itself to trigger additional generations until your specified stopping condition is met. In this case, you want the model to continue generating so it can use the weather tool results to answer your original question. ### Update Your Route Handler Modify your `src/routes/api/chat.ts` file to include the `stopWhen` condition: ```tsx title="src/routes/api/chat.ts" import { streamText, UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { createFileRoute } from '@tanstack/react-router'; import { z } from 'zod'; export const Route = createFileRoute('/api/chat')({ server: { handlers: { POST: async ({ request }) => { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z .string() .describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); }, }, }, }); ``` In this updated code, you set `stopWhen` to be when `isStepCount(5)`, allowing the model to use up to 5 "steps" for any given generation. Head back to the browser and ask about the weather in a location. You should now see the model using the weather tool results to answer your question. By setting `stopWhen: isStepCount(5)`, you're allowing the model to use up to 5 "steps" for any given generation. This enables more complex interactions and allows the model to gather and process information over several steps if needed. You can see this in action by adding another tool to convert the temperature from Fahrenheit to Celsius. ### Add another tool Update your `src/routes/api/chat.ts` file to add a new tool to convert the temperature from Fahrenheit to Celsius: ```tsx title="src/routes/api/chat.ts" {40-53} import { streamText, UIMessage, convertToModelMessages, tool, isStepCount, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; import { createFileRoute } from '@tanstack/react-router'; import { z } from 'zod'; export const Route = createFileRoute('/api/chat')({ server: { handlers: { POST: async ({ request }) => { const { messages }: { messages: UIMessage[] } = await request.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), stopWhen: isStepCount(5), tools: { weather: tool({ description: 'Get the weather in a location (fahrenheit)', inputSchema: z.object({ location: z .string() .describe('The location to get the weather for'), }), execute: async ({ location }) => { const temperature = Math.round(Math.random() * (90 - 32) + 32); return { location, temperature, }; }, }), convertFahrenheitToCelsius: tool({ description: 'Convert a temperature in fahrenheit to celsius', inputSchema: z.object({ temperature: z .number() .describe('The temperature in fahrenheit to convert'), }), execute: async ({ temperature }) => { const celsius = Math.round((temperature - 32) * (5 / 9)); return { celsius, }; }, }), }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); }, }, }, }); ``` ### Update Your Frontend update your `src/routes/index.tsx` file to render the new temperature conversion tool: ```tsx title="src/routes/index.tsx" {21-26} import { createFileRoute } from '@tanstack/react-router'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export const Route = createFileRoute('/')({ component: Chat, }); function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, i) => { switch (part.type) { case 'text': return
{part.text}
; case 'tool-weather': case 'tool-convertFahrenheitToCelsius': return (
                    {JSON.stringify(part, null, 2)}
                  
); } })}
))}
{ e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.currentTarget.value)} />
); } ``` This update handles the new `tool-convertFahrenheitToCelsius` part type, displaying the temperature conversion tool calls and results in the UI. Now, when you ask "What's the weather in New York in celsius?", you should see a more complete interaction: 1. The model will call the weather tool for New York. 2. You'll see the tool output displayed. 3. It will then call the temperature conversion tool to convert the temperature from Fahrenheit to Celsius. 4. The model will then use that information to provide a natural language response about the weather in New York. This multi-step approach allows the model to gather information and use it to provide more accurate and contextual responses, making your chatbot considerably more useful. This simple example demonstrates how tools can expand your model's capabilities. You can create more complex tools to integrate with real APIs, databases, or any other external systems, allowing the model to access and process real-world data in real-time. Tools bridge the gap between the model's knowledge cutoff and current information. ## Where to Next? You've built an AI chatbot using the AI SDK! From here, you have several paths to explore: - To learn more about the AI SDK, read through the [documentation](/docs). - If you're interested in diving deeper with guides, check out the [RAG (retrieval-augmented generation)](/cookbook/guides/rag-chatbot) and [multi-modal chatbot](/cookbook/guides/multi-modal-chatbot) guides. - To jumpstart your first AI project, explore available [templates](https://vercel.com/templates?type=ai). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI SDK by Vercel description: The AI SDK is the TypeScript toolkit for building AI applications and agents with React, Next.js, Vue, Svelte, Node.js, and more. url: "https://ai-sdk.dev/docs/introduction" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK is the TypeScript toolkit designed to help developers build AI-powered applications and agents with React, Next.js, Vue, Svelte, Node.js, and more. ## Why use the AI SDK? Integrating large language models (LLMs) into applications is complicated and heavily dependent on the specific model provider you use. The AI SDK standardizes integrating artificial intelligence (AI) models across [supported providers](/docs/foundations/providers-and-models). This enables developers to focus on building great AI applications, not waste time on technical details. The AI SDK also includes a [harness abstraction](/docs/ai-sdk-harnesses) for using established agent harnesses such as Claude Code, Codex, and Pi through `HarnessAgent`. Harnesses are decoupled from model providers, but use compatible stream and response primitives so AI SDK UI and agent surfaces can consume them in the same way. For example, here’s how you can generate text with various models using the AI SDK: The AI SDK has these primary surfaces: - **[AI SDK Core](/docs/ai-sdk-core):** A unified API for generating text, structured objects, tool calls, and building agents with LLMs. - **[AI SDK UI](/docs/ai-sdk-ui):** A set of framework-agnostic hooks for quickly building chat and generative user interfaces. - **[AI SDK Harnesses](/docs/ai-sdk-harnesses):** A uniform API for running established agent harnesses with `HarnessAgent`. ## Model Providers The AI SDK supports [multiple model providers](/providers). ## Templates We've built some [templates](https://vercel.com/templates?type=ai) that include AI SDK integrations for different use cases, providers, and frameworks. You can use these templates to get started with your AI-powered application. ### Starter Kits ### Feature Exploration ### Frameworks ### Generative UI ### Security ## Join our Community If you have questions about anything related to the AI SDK, you're always welcome to ask our community on [the Vercel Community](https://community.vercel.com/c/ai-sdk/62). ## `llms.txt` (for Cursor, Windsurf, Copilot, Claude etc.) You can access the entire AI SDK documentation in Markdown format at [ai-sdk.dev/llms.txt](/llms.txt). This can be used to ask any LLM (assuming it has a big enough context window) questions about the AI SDK based on the most up-to-date documentation. ### Example Usage For instance, to prompt an LLM with questions about the AI SDK: 1. Copy the documentation contents from [ai-sdk.dev/llms.txt](/llms.txt) 2. Use the following prompt format: ```txt Documentation: {paste documentation here} --- Based on the above documentation, answer the following: {your question} ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migration Guides description: Learn how to upgrade between Vercel AI versions. url: "https://ai-sdk.dev/docs/migration-guides" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). - [Migrate AI SDK 6.x to 7.0](/docs/migration-guides/migration-guide-7-0) - [Migrate AI SDK 5.x to 6.0](/docs/migration-guides/migration-guide-6-0) - [Migrate AI SDK 4.x to 5.0](/docs/migration-guides/migration-guide-5-0) - [Migrate your data to AI SDK 5.0](/docs/migration-guides/migration-guide-5-0-data) - [Migrate AI SDK 4.1 to 4.2](/docs/migration-guides/migration-guide-4-2) - [Migrate AI SDK 4.0 to 4.1](/docs/migration-guides/migration-guide-4-1) - [Migrate AI SDK 3.4 to 4.0](/docs/migration-guides/migration-guide-4-0) - [Migrate AI SDK 3.3 to 3.4](/docs/migration-guides/migration-guide-3-4) - [Migrate AI SDK 3.2 to 3.3](/docs/migration-guides/migration-guide-3-3) - [Migrate AI SDK 3.1 to 3.2](/docs/migration-guides/migration-guide-3-2) - [Migrate AI SDK 3.0 to 3.1](/docs/migration-guides/migration-guide-3-1) ## Versioning - [Versioning](/docs/migration-guides/versioning) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrate AI SDK 3.0 to 3.1 description: Learn how to upgrade AI SDK 3.0 to 3.1. url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-3-1" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Check out the [AI SDK 3.1 release blog post](https://vercel.com/blog/vercel-ai-sdk-3-1-modelfusion-joins-the-team) for more information about the release. This guide will help you: - Upgrade to AI SDK 3.1 - Migrate from Legacy Providers to AI SDK Core - Migrate from [`render`](/docs/reference/ai-sdk-rsc/render) to [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) Upgrading to AI SDK 3.1 does not require using the newly released AI SDK Core API or [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) function. ## Upgrading ### AI SDK To update to AI SDK version 3.1, run the following command using your preferred package manager: ``` pnpm add ai@3.1 ``` ## Next Steps The release of AI SDK 3.1 introduces several new features that improve the way you build AI applications with the SDK: - AI SDK Core, a brand new unified API for interacting with large language models (LLMs). - [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui), a new abstraction, built upon AI SDK Core functions that simplifies building streaming UIs. ## Migrating from Legacy Providers to AI SDK Core Prior to AI SDK Core, you had to use a model provider's SDK to query their models. In the following Route Handler, you use the OpenAI SDK to query their model. You then pipe that response into the `OpenAIStream` function which returns a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) that you can pass to the client using a new `StreamingTextResponse`. ```tsx import OpenAI from 'openai'; import { OpenAIStream, StreamingTextResponse } from 'ai'; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY!, }); export async function POST(req: Request) { const { messages } = await req.json(); const response = await openai.chat.completions.create({ model: 'gpt-4.1', stream: true, messages, }); const stream = OpenAIStream(response); return new StreamingTextResponse(stream); } ``` With AI SDK Core you have a unified API for any provider that implements the [AI SDK Language Model Specification](/providers/community-providers/custom-providers). Let’s take a look at the example above, but refactored to utilize the AI SDK Core API alongside the AI SDK OpenAI provider. In this example, you import the LLM function you want to use from the `ai` package, import the OpenAI provider from `@ai-sdk/openai`, and then you call the model and return the response using the `toDataStreamResponse()` helper function. ```tsx import { streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; export async function POST(req: Request) { const { messages } = await req.json(); const result = await streamText({ model: "anthropic/claude-sonnet-5.5", messages, }); return result.toDataStreamResponse(); } ``` ## Migrating from `render` to `streamUI` The AI SDK RSC API was launched as part of version 3.0. This API introduced the [`render`](/docs/reference/ai-sdk-rsc/render) function, a helper function to create streamable UIs with OpenAI models. With the new AI SDK Core API, it became possible to make streamable UIs possible with any compatible provider. The following example Server Action uses the `render` function using the model provider directly from OpenAI. You first create an OpenAI provider instance with the OpenAI SDK. Then, you pass it to the provider key of the render function alongside a tool that returns a React Server Component, defined in the `render` key of the tool. ```tsx import { render } from '@ai-sdk/rsc'; import OpenAI from 'openai'; import { z } from 'zod'; import { Spinner, Weather } from '@/components'; import { getWeather } from '@/utils'; const openai = new OpenAI(); async function submitMessage(userInput = 'What is the weather in SF?') { 'use server'; return render({ provider: openai, model: 'gpt-4.1', messages: [ { role: 'system', content: 'You are a helpful assistant' }, { role: 'user', content: userInput }, ], text: ({ content }) =>

{content}

, tools: { get_city_weather: { description: 'Get the current weather for a city', parameters: z .object({ city: z.string().describe('the city'), }) .required(), render: async function* ({ city }) { yield ; const weather = await getWeather(city); return ; }, }, }, }); } ``` With the new [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) function, you can now use any compatible AI SDK provider. In this example, you import the AI SDK OpenAI provider. Then, you pass it to the [`model`](/docs/reference/ai-sdk-rsc/stream-ui#model) key of the new [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) function. Finally, you declare a tool and return a React Server Component, defined in the [`generate`](/docs/reference/ai-sdk-rsc/stream-ui#tools.tool.generate) key of the tool. ```tsx import { streamUI } from '@ai-sdk/rsc'; import { openai } from '@ai-sdk/openai'; import { z } from 'zod'; import { Spinner, Weather } from '@/components'; import { getWeather } from '@/utils'; async function submitMessage(userInput = 'What is the weather in SF?') { 'use server'; const result = await streamUI({ model: "anthropic/claude-sonnet-5.5", system: 'You are a helpful assistant', messages: [{ role: 'user', content: userInput }], text: ({ content }) =>

{content}

, tools: { get_city_weather: { description: 'Get the current weather for a city', parameters: z .object({ city: z.string().describe('Name of the city'), }) .required(), generate: async function* ({ city }) { yield ; const weather = await getWeather(city); return ; }, }, }, }); return result.value; } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrate AI SDK 3.1 to 3.2 description: Learn how to upgrade AI SDK 3.1 to 3.2. url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-3-2" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Check out the [AI SDK 3.2 release blog post](https://vercel.com/blog/introducing-vercel-ai-sdk-3-2) for more information about the release. This guide will help you upgrade to AI SDK 3.2: - Experimental `StreamingReactResponse` functionality has been removed - Several features have been deprecated - UI framework integrations have moved to their own Node modules ## Upgrading ### AI SDK To update to AI SDK version 3.2, run the following command using your preferred package manager: ``` pnpm add ai@3.2 ``` ## Removed Functionality The experimental `StreamingReactResponse` has been removed. You can use [AI SDK RSC](/docs/ai-sdk-rsc/overview) to build streaming UIs. ## Deprecated Functionality The `nanoid` export has been deprecated. Please use [`generateId`](/docs/reference/ai-sdk-core/generate-id) instead. ## UI Package Separation AI SDK UI supports several frameworks: [React](https://react.dev/), [Svelte](https://svelte.dev/), [Vue.js](https://vuejs.org/), and [SolidJS](https://www.solidjs.com/). The integrations (other than React and RSC) have moved to separate Node modules. You need to update the import and require statements as follows: - Change `ai/svelte` to `@ai-sdk/svelte` - Change `ai/vue` to `@ai-sdk/vue` - Change `ai/solid` to `@ai-sdk/solid` The old exports are still available but will be removed in a future release. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrate AI SDK 3.2 to 3.3 description: Learn how to upgrade AI SDK 3.2 to 3.3. url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-3-3" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Check out the [AI SDK 3.3 release blog post](https://vercel.com/blog/vercel-ai-sdk-3-3) for more information about the release. No breaking changes in this release. The following changelog encompasses all changes made in the 3.2.x series, introducing significant improvements and new features across the AI SDK and its associated libraries: ## New Features ### OpenTelemetry Support - Added experimental [OpenTelemetry support](/docs/ai-sdk-core/telemetry) for all [AI SDK Core functions](/docs/ai-sdk-core/overview#ai-sdk-core-functions), enabling better observability and tracing capabilities. ### AI SDK UI Improvements - Introduced the experimental **`useObject`** hook (for React) that can be used in conjunction with **`streamObject`** on the backend to enable seamless streaming of structured data. - Enhanced **`useChat`** with experimental support for attachments and streaming tool calls, providing more versatile chat functionalities. - Patched **`useChat`** to prevent empty submissions, improving the quality of user interactions by ensuring that only intended inputs are processed. - Fix **`useChat`**'s **`reload`** function, now correctly sending data, body, and headers. - Implemented **`setThreadId`** helper for **`useAssistant`**, simplifying thread management. - Documented the stream data protocol for **`useChat`** and **`useCompletion`**, allowing developers to use these functions with any backend. The stream data protocol also enables the use of custom frontends with **`streamText`**. - Added support for custom fetch functions and request body customization, offering greater control over API interactions. - Added **`onFinish`** to **`useChat`** hook for access to token usage and finish reason. ### Core Enhancements - Implemented support for sending custom request headers, enabling more tailored API requests. - Added raw JSON schema support alongside existing Zod support, providing more options for schema and data validation. - Introduced usage information for **`embed`** and **`embedMany`** functions, offering insights into token usage. - Added support for additional settings including **`stopSequences`** and **`topK`**, allowing for finer control over text generation. - Provided access to information for all steps on **`generateText`**, providing access to intermediate tool calls and results. ### New Providers - [AWS Bedrock provider](/providers/ai-sdk-providers/amazon-bedrock). ### Provider Improvements - Enhanced existing providers including Anthropic, Google, Azure, and OpenAI with various improvements and bug fixes. - Upgraded the LangChain adapter with StreamEvent v2 support and introduced the **`toDataStreamResponse`** function, enabling conversion of LangChain output streams to data stream responses. - Added legacy function calling support to the OpenAI provider. - Updated Mistral AI provider with fixes and improvements for tool calling support. ### UI Framework Support Expansion - SolidJS: Updated **`useChat`** and **`useCompletion`** to achieve feature parity with React implementations. - Vue.js: Introduced **`useAssistant`** hook. - Vue.js / Nuxt: [Updated examples](https://github.com/vercel/ai/tree/main/examples/nuxt-openai) to showcase latest features and best practices. - Svelte: Added tool calling support to **`useChat`.** ## Fixes and Improvements - Resolved various issues across different components of the SDK, including race conditions, error handling, and state management. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrate AI SDK 3.3 to 3.4 description: Learn how to upgrade AI SDK 3.3 to 3.4. url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-3-4" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Check out the [AI SDK 3.4 release blog post](https://vercel.com/blog/ai-sdk-3-4) for more information about the release. No breaking changes in this release. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrate AI SDK 3.4 to 4.0 description: Learn how to upgrade AI SDK 3.4 to 4.0. url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-4-0" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Check out the [AI SDK 4.0 release blog post](https://vercel.com/blog/ai-sdk-4-0) for more information about the release. ## Recommended Migration Process 1. Backup your project. If you use a versioning control system, make sure all previous versions are committed. 2. [Migrate to AI SDK 3.4](/docs/migration-guides/migration-guide-3-4). 3. Upgrade to AI SDK 4.0. 4. Automatically migrate your code using [codemods](#codemods). > If you don't want to use codemods, we recommend resolving all deprecation warnings before upgrading to AI SDK 4.0. 5. Follow the breaking changes guide below. 6. Verify your project is working as expected. 7. Commit your changes. ## AI SDK 4.0 package versions You need to update the following packages to the following versions in your `package.json` file(s): - `ai` package: `4.0.*` - `@ai-sdk/provider-utils` package: `2.0.*` - `@ai-sdk/*` packages: `1.0.*` (other `@ai-sdk/*` packages) ## Codemods 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 programmatically. They allow you to easily apply many changes without having to manually go through every file. 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. You can run all codemods provided as part of the 4.0 upgrade process by running the following command from the root of your project: ```sh npx @ai-sdk/codemod upgrade ``` To run only the v4 codemods: ```sh npx @ai-sdk/codemod v4 ``` Individual codemods can be run by specifying the name of the codemod: ```sh npx @ai-sdk/codemod ``` For example, to run a specific v4 codemod: ```sh npx @ai-sdk/codemod v4/replace-baseurl src/ ``` See also the [table of codemods](#codemod-table). In addition, the latest set of codemods can be found in the [`@ai-sdk/codemod`](https://github.com/vercel/ai/tree/main/packages/codemod/src/codemods) repository. ## Provider Changes ### Removed `baseUrl` option The `baseUrl` option has been removed from all providers. Please use the `baseURL` option instead. ```ts title="AI SDK 3.4" const perplexity = createOpenAI({ // ... baseUrl: 'https://api.perplexity.ai/', }); ``` ```ts title="AI SDK 4.0" const perplexity = createOpenAI({ // ... baseURL: 'https://api.perplexity.ai/', }); ``` ### Anthropic Provider #### Removed `Anthropic` facade The `Anthropic` facade has been removed from the Anthropic provider. Please use the `anthropic` object or the `createAnthropic` function instead. ```ts title="AI SDK 3.4" const anthropic = new Anthropic({ // ... }); ``` ```ts title="AI SDK 4.0" const anthropic = createAnthropic({ // ... }); ``` #### Removed `topK` setting There is no codemod available for this change. Please review and update your code manually. The model specific `topK` setting has been removed from the Anthropic provider. You can use the standard `topK` setting instead. ```ts title="AI SDK 3.4" const result = await generateText({ model: anthropic('claude-3-5-sonnet-latest', { topK: 0.5, }), }); ``` ```ts title="AI SDK 4.0" const result = await generateText({ model: anthropic('claude-3-5-sonnet-latest'), topK: 0.5, }); ``` ### Google Generative AI Provider #### Removed `Google` facade The `Google` facade has been removed from the Google Generative AI provider. Please use the `google` object or the `createGoogleGenerativeAI` function instead. ```ts title="AI SDK 3.4" const google = new Google({ // ... }); ``` ```ts title="AI SDK 4.0" const google = createGoogleGenerativeAI({ // ... }); ``` #### Removed `topK` setting There is no codemod available for this change. Please review and update your code manually. The model-specific `topK` setting has been removed from the Google Generative AI provider. You can use the standard `topK` setting instead. ```ts title="AI SDK 3.4" const result = await generateText({ model: google('gemini-1.5-flash', { topK: 0.5, }), }); ``` ```ts title="AI SDK 4.0" const result = await generateText({ model: google('gemini-1.5-flash'), topK: 0.5, }); ``` ### Google Vertex Provider #### Removed `topK` setting There is no codemod available for this change. Please review and update your code manually. The model-specific `topK` setting has been removed from the Google Vertex provider. You can use the standard `topK` setting instead. ```ts title="AI SDK 3.4" const result = await generateText({ model: vertex('gemini-1.5-flash', { topK: 0.5, }), }); ``` ```ts title="AI SDK 4.0" const result = await generateText({ model: vertex('gemini-1.5-flash'), topK: 0.5, }); ``` ### Mistral Provider #### Removed `Mistral` facade The `Mistral` facade has been removed from the Mistral provider. Please use the `mistral` object or the `createMistral` function instead. ```ts title="AI SDK 3.4" const mistral = new Mistral({ // ... }); ``` ```ts title="AI SDK 4.0" const mistral = createMistral({ // ... }); ``` ### OpenAI Provider #### Removed `OpenAI` facade The `OpenAI` facade has been removed from the OpenAI provider. Please use the `openai` object or the `createOpenAI` function instead. ```ts title="AI SDK 3.4" const openai = new OpenAI({ // ... }); ``` ```ts title="AI SDK 4.0" const openai = createOpenAI({ // ... }); ``` ### LangChain Adapter #### Removed `toAIStream` The `toAIStream` function has been removed from the LangChain adapter. Please use the `toDataStream` function instead. ```ts title="AI SDK 3.4" LangChainAdapter.toAIStream(stream); ``` ```ts title="AI SDK 4.0" LangChainAdapter.toDataStream(stream); ``` ## AI SDK Core Changes ### `streamText` returns immediately Instead of returning a Promise, the `streamText` function now returns immediately. It is not necessary to await the result of `streamText`. ```ts title="AI SDK 3.4" const result = await streamText({ // ... }); ``` ```ts title="AI SDK 4.0" const result = streamText({ // ... }); ``` ### `streamObject` returns immediately Instead of returning a Promise, the `streamObject` function now returns immediately. It is not necessary to await the result of `streamObject`. ```ts title="AI SDK 3.4" const result = await streamObject({ // ... }); ``` ```ts title="AI SDK 4.0" const result = streamObject({ // ... }); ``` ### Remove roundtrips The `maxToolRoundtrips` and `maxAutomaticRoundtrips` options have been removed from the `generateText` and `streamText` functions. Please use the `maxSteps` option instead. The `roundtrips` property has been removed from the `GenerateTextResult` type. Please use the `steps` property instead. ```ts title="AI SDK 3.4" const { text, roundtrips } = await generateText({ maxToolRoundtrips: 1, // or maxAutomaticRoundtrips // ... }); ``` ```ts title="AI SDK 4.0" const { text, steps } = await generateText({ maxSteps: 2, // ... }); ``` ### Removed `nanoid` export The `nanoid` export has been removed. Please use [`generateId`](/docs/reference/ai-sdk-core/generate-id) instead. ```ts title="AI SDK 3.4" import { nanoid } from 'ai'; ``` ```ts title="AI SDK 4.0" import { generateId } from 'ai'; ``` ### Increased default size of generated IDs There is no codemod available for this change. Please review and update your code manually. The [`generateId`](/docs/reference/ai-sdk-core/generate-id) function now generates 16-character IDs. The previous default was 7 characters. This might e.g. require updating your database schema if you limit the length of IDs. ```ts title="AI SDK 4.0" import { generateId } from 'ai'; const id = generateId(); // now 16 characters ``` ### Removed `ExperimentalMessage` types The following types have been removed: - `ExperimentalMessage` (use `ModelMessage` instead) - `ExperimentalUserMessage` (use `CoreUserMessage` instead) - `ExperimentalAssistantMessage` (use `CoreAssistantMessage` instead) - `ExperimentalToolMessage` (use `CoreToolMessage` instead) ```ts title="AI SDK 3.4" import { ExperimentalMessage, ExperimentalUserMessage, ExperimentalAssistantMessage, ExperimentalToolMessage, } from 'ai'; ``` ```ts title="AI SDK 4.0" import { ModelMessage, CoreUserMessage, CoreAssistantMessage, CoreToolMessage, } from 'ai'; ``` ### Removed `ExperimentalTool` type The `ExperimentalTool` type has been removed. Please use the `CoreTool` type instead. ```ts title="AI SDK 3.4" import { ExperimentalTool } from 'ai'; ``` ```ts title="AI SDK 4.0" import { CoreTool } from 'ai'; ``` ### Removed experimental AI function exports The following exports have been removed: - `experimental_generateText` (use `generateText` instead) - `experimental_streamText` (use `streamText` instead) - `experimental_generateObject` (use `generateObject` instead) - `experimental_streamObject` (use `streamObject` instead) ```ts title="AI SDK 3.4" import { experimental_generateText, experimental_streamText, experimental_generateObject, experimental_streamObject, } from 'ai'; ``` ```ts title="AI SDK 4.0" import { generateText, streamText, generateObject, streamObject } from 'ai'; ``` ### Removed AI-stream related methods from `streamText` The following methods have been removed from the `streamText` result: - `toAIStream` - `pipeAIStreamToResponse` - `toAIStreamResponse` Use the `toDataStream`, `pipeDataStreamToResponse`, and `toDataStreamResponse` functions instead. ```ts title="AI SDK 3.4" const result = await streamText({ // ... }); result.toAIStream(); result.pipeAIStreamToResponse(response); result.toAIStreamResponse(); ``` ```ts title="AI SDK 4.0" const result = streamText({ // ... }); result.toDataStream(); result.pipeDataStreamToResponse(response); result.toDataStreamResponse(); ``` ### Renamed "formatStreamPart" to "formatDataStreamPart" The `formatStreamPart` function has been renamed to `formatDataStreamPart`. ```ts title="AI SDK 3.4" formatStreamPart('text', 'Hello, world!'); ``` ```ts title="AI SDK 4.0" formatDataStreamPart('text', 'Hello, world!'); ``` ### Renamed "parseStreamPart" to "parseDataStreamPart" The `parseStreamPart` function has been renamed to `parseDataStreamPart`. ```ts title="AI SDK 3.4" const part = parseStreamPart(line); ``` ```ts title="AI SDK 4.0" const part = parseDataStreamPart(line); ``` ### Renamed `TokenUsage`, `CompletionTokenUsage` and `EmbeddingTokenUsage` types The `TokenUsage`, `CompletionTokenUsage` and `EmbeddingTokenUsage` types have been renamed to `LanguageModelUsage` (for the first two) and `EmbeddingModelUsage` (for the last). ```ts title="AI SDK 3.4" import { TokenUsage, CompletionTokenUsage, EmbeddingTokenUsage } from 'ai'; ``` ```ts title="AI SDK 4.0" import { LanguageModelUsage, EmbeddingModelUsage } from 'ai'; ``` ### Removed deprecated telemetry data There is no codemod available for this change. Please review and update your code manually. The following telemetry data values have been removed: - `ai.finishReason` (now in `ai.response.finishReason`) - `ai.result.object` (now in `ai.response.object`) - `ai.result.text` (now in `ai.response.text`) - `ai.result.toolCalls` (now in `ai.response.toolCalls`) - `ai.stream.msToFirstChunk` (now in `ai.response.msToFirstChunk`) This change will apply to observability providers and any scripts or automation that you use for processing telemetry data. ### Provider Registry #### Removed experimental\_Provider, experimental\_ProviderRegistry, and experimental\_ModelRegistry The `experimental_Provider` interface, `experimental_ProviderRegistry` interface, and `experimental_ModelRegistry` interface have been removed. Please use the `Provider` interface instead. ```ts title="AI SDK 3.4" import { experimental_Provider, experimental_ProviderRegistry } from 'ai'; ``` ```ts title="AI SDK 4.0" import { Provider } from 'ai'; ``` The model registry is not available any more. Please [register providers](/docs/reference/ai-sdk-core/provider-registry#setup) instead. #### Removed `experimental_createModelRegistry` function The `experimental_createModelRegistry` function has been removed. Please use the `experimental_createProviderRegistry` function instead. ```ts title="AI SDK 3.4" import { experimental_createModelRegistry } from 'ai'; ``` ```ts title="AI SDK 4.0" import { experimental_createProviderRegistry } from 'ai'; ``` The model registry is not available any more. Please [register providers](/docs/reference/ai-sdk-core/provider-registry#setup) instead. ### Removed `rawResponse` from results There is no codemod available for this change. Please review and update your code manually. The `rawResponse` property has been removed from the `generateText`, `streamText`, `generateObject`, and `streamObject` results. You can use the `response` property instead. ```ts title="AI SDK 3.4" const { text, rawResponse } = await generateText({ // ... }); ``` ```ts title="AI SDK 4.0" const { text, response } = await generateText({ // ... }); ``` ### Removed `init` option from `pipeDataStreamToResponse` and `toDataStreamResponse` There is no codemod available for this change. Please review and update your code manually. The `init` option has been removed from the `pipeDataStreamToResponse` and `toDataStreamResponse` functions. You can set the values from `init` directly into the `options` object. ```ts title="AI SDK 3.4" const result = await streamText({ // ... }); result.pipeDataStreamToResponse(response, { init: { headers: { 'X-Custom-Header': 'value', }, }, // ... }); ``` ```ts title="AI SDK 4.0" const result = streamText({ // ... }); result.pipeDataStreamToResponse(response, { headers: { 'X-Custom-Header': 'value', }, // ... }); ``` ### Removed `responseMessages` from `generateText` and `streamText` There is no codemod available for this change. Please review and update your code manually. The `responseMessages` property has been removed from the `generateText` and `streamText` results. This includes the `onFinish` callback. Please use the `response.messages` property instead. ```ts title="AI SDK 3.4" const { text, responseMessages } = await generateText({ // ... }); ``` ```ts title="AI SDK 4.0" const { text, response } = await generateText({ // ... }); const responseMessages = response.messages; ``` ### Removed `experimental_continuationSteps` option The `experimental_continuationSteps` option has been removed from the `generateText` function. Please use the `experimental_continueSteps` option instead. ```ts title="AI SDK 3.4" const result = await generateText({ experimental_continuationSteps: true, // ... }); ``` ```ts title="AI SDK 4.0" const result = await generateText({ experimental_continueSteps: true, // ... }); ``` ### Removed `LanguageModelResponseMetadataWithHeaders` type The `LanguageModelResponseMetadataWithHeaders` type has been removed. Please use the `LanguageModelResponseMetadata` type instead. ```ts title="AI SDK 3.4" import { LanguageModelResponseMetadataWithHeaders } from 'ai'; ``` ```ts title="AI SDK 4.0" import { LanguageModelResponseMetadata } from 'ai'; ``` #### Changed `streamText` warnings result to Promise There is no codemod available for this change. Please review and update your code manually. The `warnings` property of the `StreamTextResult` type is now a Promise. ```ts title="AI SDK 3.4" const result = await streamText({ // ... }); const warnings = result.warnings; ``` ```ts title="AI SDK 4.0" const result = streamText({ // ... }); const warnings = await result.warnings; ``` #### Changed `streamObject` warnings result to Promise There is no codemod available for this change. Please review and update your code manually. The `warnings` property of the `StreamObjectResult` type is now a Promise. ```ts title="AI SDK 3.4" const result = await streamObject({ // ... }); const warnings = result.warnings; ``` ```ts title="AI SDK 4.0" const result = streamObject({ // ... }); const warnings = await result.warnings; ``` #### Renamed `simulateReadableStream` `values` to `chunks` There is no codemod available for this change. Please review and update your code manually. The `values` option of the `simulateReadableStream` function from `ai/test` has been renamed to `chunks`. ```ts title="AI SDK 3.4" import { simulateReadableStream } from 'ai/test'; const stream = simulateReadableStream({ values: [1, 2, 3], chunkDelayInMs: 100, }); ``` ```ts title="AI SDK 4.0" import { simulateReadableStream } from 'ai/test'; const stream = simulateReadableStream({ chunks: [1, 2, 3], chunkDelayInMs: 100, }); ``` ## AI SDK RSC Changes There are no codemods available for the changes in this section. Please review and update your code manually. ### Removed `render` function The AI SDK RSC 3.0 `render` function has been removed. Please use the `streamUI` function instead or [switch to AI SDK UI](/docs/ai-sdk-rsc/migrating-to-ui). ```ts title="AI SDK 3.0" import { render } from '@ai-sdk/rsc'; ``` ```ts title="AI SDK 4.0" import { streamUI } from '@ai-sdk/rsc'; ``` ## AI SDK UI Changes ### Removed Svelte, Vue, and SolidJS exports This codemod only operates on `.ts` and `.tsx` files. If you have code in files with other suffixes, please review and update your code manually. The `ai` package no longer exports Svelte, Vue, and SolidJS UI integrations. You need to install the `@ai-sdk/svelte`, `@ai-sdk/vue`, and `@ai-sdk/solid` packages directly. ```ts title="AI SDK 3.4" import { useChat } from 'ai/svelte'; ``` ```ts title="AI SDK 4.0" import { useChat } from '@ai-sdk/svelte'; ``` ### Removed `experimental_StreamData` The `experimental_StreamData` export has been removed. Please use the `StreamData` export instead. ```ts title="AI SDK 3.4" import { experimental_StreamData } from 'ai'; ``` ```ts title="AI SDK 4.0" import { StreamData } from 'ai'; ``` ### `useChat` hook There are no codemods available for the changes in this section. Please review and update your code manually. #### Removed `streamMode` setting The `streamMode` options has been removed from the `useChat` hook. Please use the `streamProtocol` parameter instead. ```ts title="AI SDK 3.4" const { messages } = useChat({ streamMode: 'text', // ... }); ``` ```ts title="AI SDK 4.0" const { messages } = useChat({ streamProtocol: 'text', // ... }); ``` #### Replaced roundtrip setting with `maxSteps` The following options have been removed from the `useChat` hook: - `experimental_maxAutomaticRoundtrips` - `maxAutomaticRoundtrips` - `maxToolRoundtrips` Please use the [`maxSteps`](/docs/ai-sdk-core/tools-and-tool-calling#multi-step-calls-using-stopwhen) option instead. The value of `maxSteps` is equal to roundtrips + 1. ```ts title="AI SDK 3.4" const { messages } = useChat({ experimental_maxAutomaticRoundtrips: 2, // or maxAutomaticRoundtrips // or maxToolRoundtrips // ... }); ``` ```ts title="AI SDK 4.0" const { messages } = useChat({ maxSteps: 3, // 2 roundtrips + 1 // ... }); ``` #### Removed `options` setting The `options` parameter in the `useChat` hook has been removed. Please use the `headers` and `body` parameters instead. ```ts title="AI SDK 3.4" const { messages } = useChat({ options: { headers: { 'X-Custom-Header': 'value', }, }, // ... }); ``` ```ts title="AI SDK 4.0" const { messages } = useChat({ headers: { 'X-Custom-Header': 'value', }, // ... }); ``` #### Removed `experimental_addToolResult` method The `experimental_addToolResult` method has been removed from the `useChat` hook. Please use the `addToolResult` method instead. ```ts title="AI SDK 3.4" const { messages, experimental_addToolResult } = useChat({ // ... }); ``` ```ts title="AI SDK 4.0" const { messages, addToolResult } = useChat({ // ... }); ``` #### Changed default value of `keepLastMessageOnError` to true and deprecated the option The `keepLastMessageOnError` option has been changed to default to `true`. The option will be removed in the next major release. ```ts title="AI SDK 3.4" const { messages } = useChat({ keepLastMessageOnError: true, // ... }); ``` ```ts title="AI SDK 4.0" const { messages } = useChat({ // ... }); ``` ### `useCompletion` hook There are no codemods available for the changes in this section. Please review and update your code manually. #### Removed `streamMode` setting The `streamMode` options has been removed from the `useCompletion` hook. Please use the `streamProtocol` parameter instead. ```ts title="AI SDK 3.4" const { text } = useCompletion({ streamMode: 'text', // ... }); ``` ```ts title="AI SDK 4.0" const { text } = useCompletion({ streamProtocol: 'text', // ... }); ``` ### `useAssistant` hook #### Removed `experimental_useAssistant` export The `experimental_useAssistant` export has been removed from the `useAssistant` hook. Please use the `useAssistant` hook directly instead. ```ts title="AI SDK 3.4" import { experimental_useAssistant } from '@ai-sdk/react'; ``` ```ts title="AI SDK 4.0" import { useAssistant } from '@ai-sdk/react'; ``` #### Removed `threadId` and `messageId` from `AssistantResponse` There is no codemod available for this change. Please review and update your code manually. The `threadId` and `messageId` properties have been removed from the argument passed to the `AssistantResponse` callback. The initial response options passed to `AssistantResponse` still require both IDs. Please use the `threadId` and `messageId` variables from the outer scope instead. ```ts title="AI SDK 3.4" return AssistantResponse( { threadId: myThreadId, messageId: myMessageId }, async ({ forwardStream, sendDataMessage, threadId, messageId }) => { // use threadId and messageId here }, ); ``` ```ts title="AI SDK 4.0" return AssistantResponse( { threadId: myThreadId, messageId: myMessageId }, async ({ forwardStream, sendDataMessage }) => { // use myThreadId and myMessageId here }, ); ``` #### Removed `experimental_AssistantResponse` export There is no codemod available for this change. Please review and update your code manually. The `experimental_AssistantResponse` export has been removed. Please use the `AssistantResponse` function directly instead. ```ts title="AI SDK 3.4" import { experimental_AssistantResponse } from 'ai'; ``` ```ts title="AI SDK 4.0" import { AssistantResponse } from 'ai'; ``` ### `experimental_useObject` hook There are no codemods available for the changes in this section. Please review and update your code manually. The `setInput` helper has been removed from the `experimental_useObject` hook. Please use the `submit` helper instead. ```ts title="AI SDK 3.4" const { object, setInput } = useObject({ // ... }); ``` ```ts title="AI SDK 4.0" const { object, submit } = useObject({ // ... }); ``` ## AI SDK Errors ### Removed `isXXXError` static methods The `isXXXError` static methods have been removed from AI SDK errors. Please use the `isInstance` method of the corresponding error class instead. ```ts title="AI SDK 3.4" import { APICallError } from 'ai'; APICallError.isAPICallError(error); ``` ```ts title="AI SDK 4.0" import { APICallError } from 'ai'; APICallError.isInstance(error); ``` ### Removed `toJSON` method There is no codemod available for this change. Please review and update your code manually. The `toJSON` method has been removed from AI SDK errors. ## AI SDK 2.x Legacy Changes There are no codemods available for the changes in this section. Please review and update your code manually. ### Removed 2.x legacy providers Legacy providers from AI SDK 2.x have been removed. Please use the new [AI SDK provider architecture](/docs/foundations/providers-and-models) instead. #### Removed 2.x legacy function and tool calling The legacy `function_call` and `tools` options have been removed from `useChat` and `Message`. The `name` property from the `Message` type has been removed. Please use the [AI SDK Core tool calling](/docs/ai-sdk-core/tools-and-tool-calling) instead. ### Removed 2.x prompt helpers Prompt helpers for constructing message prompts are no longer needed with the AI SDK provider architecture and have been removed. ### Removed 2.x `AIStream` The `AIStream` function and related exports have been removed. Please use the [`streamText`](/docs/reference/ai-sdk-core/stream-text) function and its `toDataStream()` method instead. ### Removed 2.x `StreamingTextResponse` The `StreamingTextResponse` function has been removed. Please use the [`streamText`](/docs/reference/ai-sdk-core/stream-text) function and its `toDataStreamResponse()` method instead. ### Removed 2.x `streamToResponse` The `streamToResponse` function has been removed. Please use the [`streamText`](/docs/reference/ai-sdk-core/stream-text) function and its `pipeDataStreamToResponse()` method instead. ### Removed 2.x RSC `Tokens` streaming The legacy `Tokens` RSC streaming from 2.x has been removed. `Tokens` were implemented prior to AI SDK RSC and are no longer needed. ## Codemod Table The following table lists codemod availability for the AI SDK 4.0 upgrade process. Note the codemod `upgrade` command will run all of them for you. This list is provided to give visibility into which migrations have some automation. It can also be helpful to find the codemod names if you'd like to run a subset of codemods. For more, see the [Codemods](#codemods) section. | Change | Codemod | | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | **Provider Changes** | | | Removed baseUrl option | `v4/replace-baseurl` | | **Anthropic Provider** | | | Removed Anthropic facade | `v4/remove-anthropic-facade` | | Removed topK setting | *N/A* | | **Google Generative AI Provider** | | | Removed Google facade | `v4/remove-google-facade` | | Removed topK setting | *N/A* | | **Google Vertex Provider** | | | Removed topK setting | *N/A* | | **Mistral Provider** | | | Removed Mistral facade | `v4/remove-mistral-facade` | | **OpenAI Provider** | | | Removed OpenAI facade | `v4/remove-openai-facade` | | **LangChain Adapter** | | | Removed toAIStream | `v4/replace-langchain-toaistream` | | **AI SDK Core Changes** | | | streamText returns immediately | `v4/remove-await-streamtext` | | streamObject returns immediately | `v4/remove-await-streamobject` | | Remove roundtrips | `v4/replace-roundtrips-with-maxsteps` | | Removed nanoid export | `v4/replace-nanoid` | | Increased default size of generated IDs | *N/A* | | Removed ExperimentalMessage types | `v4/remove-experimental-message-types` | | Removed ExperimentalTool type | `v4/remove-experimental-tool` | | Removed experimental AI function exports | `v4/remove-experimental-ai-fn-exports` | | Removed AI-stream related methods from streamText | `v4/remove-ai-stream-methods-from-stream-text-result` | | Renamed "formatStreamPart" to "formatDataStreamPart" | `v4/rename-format-stream-part` | | Renamed "parseStreamPart" to "parseDataStreamPart" | `v4/rename-parse-stream-part` | | Renamed TokenUsage, CompletionTokenUsage and EmbeddingTokenUsage types | `v4/replace-token-usage-types` | | Removed deprecated telemetry data | *N/A* | | **Provider Registry** | | | → Removed experimental\_Provider, experimental\_ProviderRegistry, and experimental\_ModelRegistry | `v4/remove-deprecated-provider-registry-exports` | | → Removed experimental\_createModelRegistry function | *N/A* | | Removed rawResponse from results | *N/A* | | Removed init option from pipeDataStreamToResponse and toDataStreamResponse | *N/A* | | Removed responseMessages from generateText and streamText | *N/A* | | Removed experimental\_continuationSteps option | `v4/replace-continuation-steps` | | Removed LanguageModelResponseMetadataWithHeaders type | `v4/remove-metadata-with-headers` | | Changed streamText warnings result to Promise | *N/A* | | Changed streamObject warnings result to Promise | *N/A* | | Renamed simulateReadableStream values to chunks | *N/A* | | **AI SDK RSC Changes** | | | Removed render function | *N/A* | | **AI SDK UI Changes** | | | Removed Svelte, Vue, and SolidJS exports | `v4/rewrite-framework-imports` | | Removed experimental\_StreamData | `v4/remove-experimental-streamdata` | | **useChat hook** | | | Removed streamMode setting | *N/A* | | Replaced roundtrip setting with maxSteps | `v4/replace-roundtrips-with-maxsteps` | | Removed options setting | *N/A* | | Removed experimental\_addToolResult method | *N/A* | | Changed default value of keepLastMessageOnError to true and deprecated the option | *N/A* | | **useCompletion hook** | | | Removed streamMode setting | *N/A* | | **useAssistant hook** | | | Removed experimental\_useAssistant export | `v4/remove-experimental-useassistant` | | Removed threadId and messageId from AssistantResponse | *N/A* | | Removed experimental\_AssistantResponse export | *N/A* | | **experimental\_useObject hook** | | | Removed setInput helper | *N/A* | | **AI SDK Errors** | | | Removed isXXXError static methods | `v4/remove-isxxxerror` | | Removed toJSON method | *N/A* | | **AI SDK 2.x Legacy Changes** | | | Removed 2.x legacy providers | *N/A* | | Removed 2.x legacy function and tool calling | *N/A* | | Removed 2.x prompt helpers | *N/A* | | Removed 2.x AIStream | *N/A* | | Removed 2.x StreamingTextResponse | *N/A* | | Removed 2.x streamToResponse | *N/A* | | Removed 2.x RSC Tokens streaming | *N/A* | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrate AI SDK 4.0 to 4.1 description: Learn how to upgrade AI SDK 4.0 to 4.1. url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-4-1" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Check out the [AI SDK 4.1 release blog post](https://vercel.com/blog/ai-sdk-4-1) for more information about the release. No breaking changes in this release. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrate AI SDK 4.1 to 4.2 description: Learn how to upgrade AI SDK 4.1 to 4.2. url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-4-2" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Check out the [AI SDK 4.2 release blog post](https://vercel.com/blog/ai-sdk-4-2) for more information about the release. This guide will help you upgrade to AI SDK 4.2: ## Stable APIs The following APIs have been moved to stable and no longer have the `experimental_` prefix: - `customProvider` - `providerOptions` (renamed from `providerMetadata` for provider-specific inputs) - `providerMetadata` (for provider-specific outputs) - `toolCallStreaming` option for `streamText` ## Dependency Versions AI SDK requires a non-optional `zod` dependency with version `^3.23.8`. ## UI Message Parts In AI SDK 4.2, we've redesigned how `useChat` handles model outputs with message parts and multiple steps. This is a significant improvement that simplifies rendering complex, multi-modal AI responses in your UI. ### What's Changed Assistant messages with tool calling now get combined into a single message with multiple parts, rather than creating separate messages for each step. This change addresses two key developments in AI applications: 1. **Diverse Output Types**: Models now generate more than just text; they produce reasoning steps, sources, and tool calls. 2. **Interleaved Outputs**: In multi-step agent use-cases, these different output types are frequently interleaved. ### Benefits of the New Approach Previously, `useChat` stored different output types separately, which made it challenging to maintain the correct sequence in your UI when these elements were interleaved in a response, and led to multiple consecutive assistant messages when there were tool calls. For example: ```javascript message.content = "Final answer: 42"; message.reasoning = "First I'll calculate X, then Y..."; message.toolInvocations = [{toolName: "calculator", args: {...}}]; ``` This structure was limiting. The new message parts approach replaces separate properties with an ordered array that preserves the exact sequence: ```javascript message.parts = [ { type: "text", text: "Final answer: 42" }, { type: "reasoning", reasoning: "First I'll calculate X, then Y..." }, { type: "tool-invocation", toolInvocation: { toolName: "calculator", args: {...} } }, ]; ``` ### Migration Existing applications using the previous message format will need to update their UI components to handle the new `parts` array. The fields from the previous format are still available for backward compatibility, but we recommend migrating to the new format for better support of multi-modal and multi-step interactions. You can use the `useChat` hook with the new message parts as follows: ```javascript function Chat() { const { messages } = useChat(); return (
{messages.map(message => message.parts.map((part, i) => { switch (part.type) { case 'text': return

{part.text}

; case 'source': return

{part.source.url}

; case 'reasoning': return
{part.reasoning}
; case 'tool-invocation': return
{part.toolInvocation.toolName}
; case 'file': return ( ); } }), )}
); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrate Your Data to AI SDK 5.0 description: Learn how to migrate your persisted messages and chat data from AI SDK 4.x to 5.0. url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0-data" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK 5.0 introduces changes to the message structure and persistence patterns. Unlike code migrations that can often be automated with codemods, data migration depends on your specific persistence approach, database schema, and application requirements. **This guide helps you get your application working with AI SDK 5.0 first** using a runtime conversion layer. This allows you to update your app immediately without database migrations blocking you. You can then migrate your data schema at your own pace. ## Recommended Migration Process Follow this two-phase approach for a safe migration: ### Phase 1: Get Your App Working (Runtime Conversion) **Goal:** Update your application to AI SDK 5.0 without touching your database. 1. Update dependencies (install v4 types alongside v5) 2. Add conversion functions to transform between v4 and v5 message formats 3. Update data fetching logic to convert messages when reading from the database 4. Update the rest of your application code to AI SDK 5.0 (see the [main migration guide](/docs/migration-guides/migration-guide-5-0)) Your database schema remains unchanged during Phase 1. You're only adding a conversion layer that transforms messages at runtime. **Timeline:** Can be completed in hours or days. ### Phase 2: Migrate to V5 Schema (Recommended) **Goal:** Migrate your data to a v5-compatible schema, eliminating the runtime conversion overhead. While Phase 1 gets you working immediately, migrate your schema soon after completing Phase 1. This phase uses a side-by-side migration approach with an equivalent v5 schema: 1. Create `messages_v5` table alongside existing `messages` table 2. Start dual-writing to both tables (with conversion) 3. Run a background migration to convert existing messages 4. Switch reads to the v5 schema 5. Remove conversion from your route handlers 6. Remove dual-write (write only to v5) 7. Drop old tables **Timeline:** Do this soon after Phase 1. **Why this matters:** - Removes runtime conversion overhead - Eliminates technical debt early - Type safety with v5 message format - Easier to maintain and extend ## Understanding the Changes Before starting, understand the main persistence-related changes in AI SDK 5.0: **AI SDK 4.0:** - `content` field for text - `reasoning` as a top-level property - `toolInvocations` as a top-level property - `parts` (optional) ordered array **AI SDK 5.0:** - `parts` array is the single source of truth - `content` is removed (deprecated) and accessed via a `text` part - `reasoning` is removed and replaced with a `reasoning` part - `toolInvocations` is removed and replaced with `tool-${toolName}` parts with `input`/`output` (renamed from `args`/`result`) - `data` role removed (use data parts instead) ## Phase 1: Runtime Conversion Pattern This creates a conversion layer without making changes to your database schema. ### Step 1: Update Dependencies To get proper TypeScript types for your v4 messages, install the v4 package alongside v5 using npm aliases: ```json title="package.json" { "dependencies": { "ai": "^5.0.0", "ai-legacy": "npm:ai@^4.3.2" } } ``` Run: ```bash pnpm install ``` Import v4 types for proper type safety: ```tsx import type { Message as V4Message } from 'ai-legacy'; import type { UIMessage } from 'ai'; ``` ### Step 2: Add Conversion Functions Create type guards to detect which message format you're working with, and build a conversion function that handles all v4 message types: ```tsx import type { ToolInvocation, Message as V4Message, UIMessage as LegacyUIMessage, } from 'ai-legacy'; import type { ToolUIPart, UIMessage, UITools } from 'ai'; export type MyUIMessage = UIMessage; type V4Part = NonNullable[number]; type V5Part = MyUIMessage['parts'][number]; // Type definitions for V4 parts type V4ToolInvocationPart = Extract; type V4ReasoningPart = Extract; type V4SourcePart = Extract; type V4FilePart = Extract; // Type guards function isV4Message(msg: V4Message | MyUIMessage): msg is V4Message { return ( msg?.parts == null || 'toolInvocations' in msg || (msg?.parts?.some(p => p.type === 'tool-invocation') ?? false) || msg?.role === 'data' || ('reasoning' in msg && typeof msg.reasoning === 'string') || (msg?.parts?.some(p => 'args' in p || 'result' in p) ?? false) || (msg?.parts?.some(p => 'reasoning' in p && 'details' in p) ?? false) || (msg?.parts?.some( p => p.type === 'file' && 'mimeType' in p && 'data' in p, ) ?? false) ); } function isV4ToolInvocationPart(part: unknown): part is V4ToolInvocationPart { return ( typeof part === 'object' && part !== null && 'type' in part && part.type === 'tool-invocation' && 'toolInvocation' in part ); } function isV4ReasoningPart(part: unknown): part is V4ReasoningPart { return ( typeof part === 'object' && part !== null && 'type' in part && part.type === 'reasoning' && 'reasoning' in part ); } function isV4SourcePart(part: unknown): part is V4SourcePart { return ( typeof part === 'object' && part !== null && 'type' in part && part.type === 'source' && 'source' in part ); } function isV4FilePart(part: unknown): part is V4FilePart { return ( typeof part === 'object' && part !== null && 'type' in part && part.type === 'file' && 'mimeType' in part && 'data' in part ); } // State mapping const V4_TO_V5_STATE_MAP = { 'partial-call': 'input-streaming', call: 'input-available', result: 'output-available', } as const; function convertToolInvocationState( v4State: ToolInvocation['state'], ): 'input-streaming' | 'input-available' | 'output-available' { return V4_TO_V5_STATE_MAP[v4State] ?? 'output-available'; } // Tool conversion function convertV4ToolInvocationToV5ToolUIPart( toolInvocation: ToolInvocation, ): ToolUIPart { return { type: `tool-${toolInvocation.toolName}`, toolCallId: toolInvocation.toolCallId, input: toolInvocation.args, output: toolInvocation.state === 'result' ? toolInvocation.result : undefined, state: convertToolInvocationState(toolInvocation.state), }; } // Part converters function convertV4ToolInvocationPart(part: V4ToolInvocationPart): V5Part { return convertV4ToolInvocationToV5ToolUIPart(part.toolInvocation); } function convertV4ReasoningPart(part: V4ReasoningPart): V5Part { return { type: 'reasoning', text: part.reasoning }; } function convertV4SourcePart(part: V4SourcePart): V5Part { return { type: 'source-url', url: part.source.url, sourceId: part.source.id, title: part.source.title, }; } function convertV4FilePart(part: V4FilePart): V5Part { return { type: 'file', mediaType: part.mimeType, url: part.data, }; } function convertPart(part: V4Part | V5Part): V5Part { if (isV4ToolInvocationPart(part)) { return convertV4ToolInvocationPart(part); } if (isV4ReasoningPart(part)) { return convertV4ReasoningPart(part); } if (isV4SourcePart(part)) { return convertV4SourcePart(part); } if (isV4FilePart(part)) { return convertV4FilePart(part); } // Already V5 format return part; } // Message conversion function createBaseMessage( msg: V4Message | MyUIMessage, index: number, ): Pick { return { id: msg.id || `msg-${index}`, role: msg.role === 'data' ? 'assistant' : msg.role, }; } function convertDataMessage(msg: V4Message, index: number): MyUIMessage { return { ...createBaseMessage(msg, index), parts: [ { type: 'data-custom', data: msg.data || msg.content, }, ], }; } function buildPartsFromTopLevelFields(msg: V4Message): MyUIMessage['parts'] { const parts: MyUIMessage['parts'] = []; if (msg.reasoning) { parts.push({ type: 'reasoning', text: msg.reasoning }); } if (msg.toolInvocations) { parts.push( ...msg.toolInvocations.map(convertV4ToolInvocationToV5ToolUIPart), ); } if (msg.content && typeof msg.content === 'string') { parts.push({ type: 'text', text: msg.content }); } return parts; } function convertPartsArray(parts: V4Part[]): MyUIMessage['parts'] { return parts.map(convertPart); } export function convertV4MessageToV5( msg: V4Message | MyUIMessage, index: number, ): MyUIMessage { if (!isV4Message(msg)) { return msg as MyUIMessage; } if (msg.role === 'data') { return convertDataMessage(msg, index); } const base = createBaseMessage(msg, index); const parts = msg.parts ? convertPartsArray(msg.parts) : buildPartsFromTopLevelFields(msg); return { ...base, parts }; } // V5 to V4 conversion function convertV5ToolUIPartToV4ToolInvocation( part: ToolUIPart, ): ToolInvocation { const state = part.state === 'input-streaming' ? 'partial-call' : part.state === 'input-available' ? 'call' : 'result'; const toolName = part.type.startsWith('tool-') ? part.type.slice(5) : part.type; const base = { toolCallId: part.toolCallId, toolName, args: part.input, state, }; if (state === 'result' && part.output !== undefined) { return { ...base, state: 'result' as const, result: part.output }; } return base as ToolInvocation; } export function convertV5MessageToV4(msg: MyUIMessage): LegacyUIMessage { const parts: V4Part[] = []; const base: LegacyUIMessage = { id: msg.id, role: msg.role, content: '', parts, }; let textContent = ''; let reasoning: string | undefined; const toolInvocations: ToolInvocation[] = []; for (const part of msg.parts) { if (part.type === 'text') { textContent = part.text; parts.push({ type: 'text', text: part.text }); } else if (part.type === 'reasoning') { reasoning = part.text; parts.push({ type: 'reasoning', reasoning: part.text, details: [{ type: 'text', text: part.text }], }); } else if (part.type.startsWith('tool-')) { const toolInvocation = convertV5ToolUIPartToV4ToolInvocation( part as ToolUIPart, ); parts.push({ type: 'tool-invocation', toolInvocation: toolInvocation }); toolInvocations.push(toolInvocation); } else if (part.type === 'source-url') { parts.push({ type: 'source', source: { id: part.sourceId, url: part.url, title: part.title, sourceType: 'url', }, }); } else if (part.type === 'file') { parts.push({ type: 'file', mimeType: part.mediaType, data: part.url, }); } else if (part.type === 'data-custom') { base.data = part.data; } } if (textContent) { base.content = textContent; } if (reasoning) { base.reasoning = reasoning; } if (toolInvocations.length > 0) { base.toolInvocations = toolInvocations; } if (parts.length > 0) { base.parts = parts; } return base; } ``` ### Step 3: Convert Messages When Reading Apply the conversion when loading messages from your database: Adapt this code to your specific database and ORM. ```tsx import { convertV4MessageToV5, type MyUIMessage } from './conversion'; export async function loadChat(chatId: string): Promise { // Fetch messages from your database (pseudocode - update based on your data access layer) const rawMessages = await db .select() .from(messages) .where(eq(messages.chatId, chatId)) .orderBy(messages.createdAt); // Convert on read return rawMessages.map((msg, index) => convertV4MessageToV5(msg, index)); } ``` ### Step 4: Convert Messages When Saving In Phase 1, your application runs on v5 but your database stores v4 format. Convert messages inline in your route handlers before passing them to your database functions: ```tsx import { convertV5MessageToV4, convertV4MessageToV5, type MyUIMessage, } from './conversion'; import { upsertMessage, loadChat } from './db/actions'; import { streamText, generateId, convertToModelMessages } from 'ai'; export async function POST(req: Request) { const { message, chatId }: { message: MyUIMessage; chatId: string } = await req.json(); // Convert and save incoming user message (v5 to v4 inline) await upsertMessage({ chatId, id: message.id, message: convertV5MessageToV4(message), // convert to v4 }); // Load all messages, including the one just saved (already in v5 format) const messages = await loadChat(chatId); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: convertToModelMessages(messages), tools: { // Your tools here }, }); return result.toUIMessageStreamResponse({ generateMessageId: generateId, originalMessages: messages, onFinish: async ({ responseMessage }) => { // Convert and save assistant response (v5 to v4 inline) await upsertMessage({ chatId, id: responseMessage.id, message: convertV5MessageToV4(responseMessage), }); }, }); } ``` Keep your `upsertMessage` (or equivalent) function unchanged to continue working with v4 messages. With Steps 3 and 4 complete, you have a bidirectional conversion layer: - **Reading:** v4 (database) → v5 (application) - **Writing:** v5 (application) → v4 (database) Your database schema remains unchanged, but your application now works with v5 format. **What's next:** Follow the main migration guide to update the rest of your application code to AI SDK 5.0, including API routes, components, and other code that uses the AI SDK. Then proceed to Phase 2. See the [main migration guide](/docs/migration-guides/migration-guide-5-0) for details. ## Phase 2: Side-by-Side Schema Migration Now that your application is updated to AI SDK 5.0 and working with the runtime conversion layer from Phase 1, you have a fully functional system. However, **the conversion functions are only a temporary solution**. Your database still stores messages in the v4 format, which means: - Every read operation requires runtime conversion overhead - You maintain backward compatibility code indefinitely - Future features require working with the legacy schema **Phase 2 migrates your message history to the v5 schema**, eliminating the conversion layer and enabling better performance and long-term maintainability. This phase uses a simplified approach: create a new `messages_v5` table with the same structure as your current `messages` table, but storing v5-formatted message parts. **Adapt phase 2 examples to your setup** These code examples demonstrate migration patterns. Your implementation will differ based on your database (Postgres, MySQL, SQLite), ORM (Drizzle, Prisma, raw SQL), schema design, and data persistence patterns. Use these examples as a guide, then adapt them to your specific setup. ### Overview: Migration Strategy 1. **Create `messages_v5` table** alongside existing `messages` table 2. **Dual-write** new messages to both schemas (with conversion) 3. **Background migration** to convert existing messages 4. **Verify** data integrity 5. **Update read functions** to use `messages_v5` schema 6. **Remove conversion** from route handlers 7. **Remove dual-write** (write only to `messages_v5`) 8. **Clean up** old tables This ensures your application keeps running throughout the migration with no data loss risk. ### Step 1: Create V5 Schema Alongside V4 Create a new `messages_v5` table with the same structure as your existing table, but designed to store v5 message parts: **Existing v4 Schema (keep running):** ```typescript import { UIMessage } from 'ai-legacy'; export const messages = pgTable('messages', { id: varchar() .primaryKey() .$defaultFn(() => nanoid()), chatId: varchar() .references(() => chats.id, { onDelete: 'cascade' }) .notNull(), createdAt: timestamp().defaultNow().notNull(), parts: jsonb().$type().notNull(), role: text().$type().notNull(), }); ``` **New v5 Schema (create alongside):** ```typescript import { MyUIMessage } from './conversion'; export const messages_v5 = pgTable('messages_v5', { id: varchar() .primaryKey() .$defaultFn(() => nanoid()), chatId: varchar() .references(() => chats.id, { onDelete: 'cascade' }) .notNull(), createdAt: timestamp().defaultNow().notNull(), parts: jsonb().$type().notNull(), role: text().$type().notNull(), }); ``` Run your migration to create the new table: ```bash pnpm drizzle-kit generate pnpm drizzle-kit migrate ``` ### Step 2: Implement Dual-Write for New Messages Update your save functions to write to both schemas during the migration period. This ensures new messages are available in both formats: ```typescript import { convertV4MessageToV5 } from './conversion'; import { messages, messages_v5 } from './schema'; import type { UIMessage } from 'ai-legacy'; export const upsertMessage = async ({ chatId, message, id, }: { id: string; chatId: string; message: UIMessage; // Still accepts v4 format }) => { return await db.transaction(async tx => { // Write to v4 schema (existing) const [result] = await tx .insert(messages) .values({ chatId, parts: message.parts ?? [], role: message.role, id, }) .onConflictDoUpdate({ target: messages.id, set: { parts: message.parts ?? [], chatId, }, }) .returning(); // Convert and write to v5 schema (new) const v5Message = convertV4MessageToV5( { ...message, content: '', }, 0, ); await tx .insert(messages_v5) .values({ chatId, parts: v5Message.parts ?? [], role: v5Message.role, id, }) .onConflictDoUpdate({ target: messages_v5.id, set: { parts: v5Message.parts ?? [], chatId, }, }); return result; }); }; ``` ### Step 3: Migrate Existing Messages Create a script to migrate existing messages from v4 to v5 schema: ```typescript import { convertV4MessageToV5 } from './conversion'; import { db } from './db'; import { messages, messages_v5 } from './db/schema'; async function migrateExistingMessages() { console.log('Starting migration of existing messages...'); // Get all v4 messages that haven't been migrated yet const migratedIds = await db.select({ id: messages_v5.id }).from(messages_v5); const migratedIdSet = new Set(migratedIds.map(m => m.id)); const allMessages = await db.select().from(messages); const unmigrated = allMessages.filter(msg => !migratedIdSet.has(msg.id)); console.log(`Found ${unmigrated.length} messages to migrate`); let migrated = 0; let errors = 0; const batchSize = 100; for (let i = 0; i < unmigrated.length; i += batchSize) { const batch = unmigrated.slice(i, i + batchSize); await db.transaction(async tx => { for (const msg of batch) { try { // Convert message to v5 format const v5Message = convertV4MessageToV5( { id: msg.id, content: '', role: msg.role, parts: msg.parts, createdAt: msg.createdAt, }, 0, ); // Insert into v5 messages table await tx.insert(messages_v5).values({ id: v5Message.id, chatId: msg.chatId, role: v5Message.role, parts: v5Message.parts, createdAt: msg.createdAt, }); migrated++; } catch (error) { console.error(`Error migrating message ${msg.id}:`, error); errors++; } } }); console.log(`Progress: ${migrated}/${unmigrated.length} messages migrated`); } console.log(`Migration complete: ${migrated} migrated, ${errors} errors`); } // Run migration migrateExistingMessages().catch(console.error); ``` This script: - Only migrates messages that haven't been migrated yet - Uses batching for better performance - Can be run multiple times safely - Can be stopped and resumed ### Step 4: Verify Migration Create a verification script to ensure data integrity: ```typescript import { count } from 'drizzle-orm'; import { db } from './db'; import { messages, messages_v5 } from './db/schema'; async function verifyMigration() { // Count messages in both schemas const v4Count = await db.select({ count: count() }).from(messages); const v5Count = await db.select({ count: count() }).from(messages_v5); console.log('Migration Status:'); console.log(`V4 Messages: ${v4Count[0].count}`); console.log(`V5 Messages: ${v5Count[0].count}`); console.log( `Migration progress: ${((v5Count[0].count / v4Count[0].count) * 100).toFixed(2)}%`, ); } verifyMigration().catch(console.error); ``` ### Step 5: Read from V5 Schema Once migration is complete, update your read functions to use the new v5 schema. Since the data is now in v5 format, you don't need conversion: ```typescript import type { MyUIMessage } from './conversion'; export const loadChat = async (chatId: string): Promise => { // Load from v5 schema - no conversion needed const messages = await db .select() .from(messages_v5) .where(eq(messages_v5.chatId, chatId)) .orderBy(messages_v5.createdAt); return messages; }; ``` ### Step 6: Write to V5 Schema Only Once your read functions work with v5 and your background migration is complete, stop dual-writing and only write to v5: ```typescript import type { MyUIMessage } from './conversion'; export const upsertMessage = async ({ chatId, message, id, }: { id: string; chatId: string; message: MyUIMessage; // Now accepts v5 format }) => { // Write to v5 schema only const [result] = await db .insert(messages_v5) .values({ chatId, parts: message.parts ?? [], role: message.role, id, }) .onConflictDoUpdate({ target: messages_v5.id, set: { parts: message.parts ?? [], chatId, }, }) .returning(); return result; }; ``` Update your route handler to pass v5 messages directly: ```tsx export async function POST(req: Request) { const { message, chatId }: { message: MyUIMessage; chatId: string } = await req.json(); // Pass v5 message directly - no conversion needed await upsertMessage({ chatId, id: message.id, message, }); const messages = await loadChat(chatId); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: convertToModelMessages(messages), tools: { // Your tools here }, }); return result.toUIMessageStreamResponse({ generateMessageId: generateId, originalMessages: messages, onFinish: async ({ responseMessage }) => { await upsertMessage({ chatId, id: responseMessage.id, message: responseMessage, // No conversion needed }); }, }); } ``` ### Step 7: Complete the Switch Once verification passes and you're confident in the migration: 1. **Remove conversion functions**: Delete the v4↔v5 conversion utilities 2. **Remove `ai-legacy` dependency**: Uninstall the v4 types package 3. **Test thoroughly**: Ensure your application works correctly with v5 schema 4. **Monitor**: Watch for issues in production 5. **Clean up**: After a safe period (1-2 weeks), drop the old table ```sql -- After confirming everything works DROP TABLE messages; -- Optionally rename v5 table to standard name ALTER TABLE messages_v5 RENAME TO messages; ``` **Phase 2 is now complete.** Your application is fully migrated to v5 schema with no runtime conversion overhead. ## Community Resources The following community members have shared their migration experiences: - [AI SDK Migration: Handling Previously Saved Messages](https://jhakim.com/blog/ai-sdk-migration-handling-previously-saved-messages) - Detailed transformation function implementation - [How we migrated Atypica.ai to AI SDK v5 without breaking 10M+ chat histories](https://blog.web3nomad.com/p/how-we-migrated-atypicaai-to-ai-sdk-v5-without-breaking-10m-chat-histories) - Runtime conversion approach for large-scale migration For more API change details, see the [main migration guide](/docs/migration-guides/migration-guide-5-0). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrate AI SDK 4.x to 5.0 description: Learn how to upgrade AI SDK 4.x to 5.0. url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Recommended Migration Process 1. Backup your project. If you use a versioning control system, make sure all previous versions are committed. 2. Upgrade to AI SDK 5.0. 3. Automatically migrate your code using one of these approaches: - Use the [AI SDK 5 Migration MCP Server](#ai-sdk-5-migration-mcp-server) for AI-assisted migration in Cursor or other MCP-compatible coding agents - Use [codemods](#codemods) to automatically transform your code 4. Follow the breaking changes guide below. 5. Verify your project is working as expected. 6. Commit your changes. ## AI SDK 5 Migration MCP Server The [AI SDK 5 Migration Model Context Protocol (MCP) Server](https://github.com/vercel-labs/ai-sdk-5-migration-mcp-server) provides an automated way to migrate your project using a coding agent. This server has been designed for Cursor, but should work with any coding agent that supports MCP. To get started, create or edit `.cursor/mcp.json` in your project: ```json { "mcpServers": { "ai-sdk-5-migration": { "url": "https://ai-sdk-5-migration-mcp-server.vercel.app/api/mcp" } } } ``` After saving, open the command palette (Cmd+Shift+P on macOS, Ctrl+Shift+P on Windows/Linux) and search for "View: Open MCP Settings". Verify the new server appears and is toggled on. Then use this prompt: ``` Please migrate this project to AI SDK 5 using the ai-sdk-5-migration mcp server. Start by creating a checklist. ``` For more information, see the [AI SDK 5 Migration MCP Server repository](https://github.com/vercel-labs/ai-sdk-5-migration-mcp-server). ## AI SDK 5.0 Package Versions You need to update the following packages to the following versions in your `package.json` file(s): - `ai` package: `5.0.0` - `@ai-sdk/provider` package: `2.0.0` - `@ai-sdk/provider-utils` package: `3.0.0` - `@ai-sdk/*` packages: `2.0.0` (other `@ai-sdk` packages) Additionally, you need to update the following peer dependencies: - `zod` package: `4.1.8` or later (recommended to avoid TypeScript performance issues) An example upgrade command would be: ``` npm install ai @ai-sdk/react @ai-sdk/openai zod@^4.1.8 ``` If you encounter TypeScript performance issues after upgrading, ensure you're using Zod 4.1.8 or later. If the issue persists, update your `tsconfig.json` to use `moduleResolution: "nodenext"`. See the [TypeScript performance troubleshooting guide](/docs/troubleshooting/typescript-performance-zod) for more details. ## Codemods 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. 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. You can run all codemods provided as part of the 5.0 upgrade process by running the following command from the root of your project: ```sh npx @ai-sdk/codemod upgrade ``` To run only the v5 codemods (v4 → v5 migration): ```sh npx @ai-sdk/codemod v5 ``` Individual codemods can be run by specifying the name of the codemod: ```sh npx @ai-sdk/codemod ``` For example, to run a specific v5 codemod: ```sh npx @ai-sdk/codemod v5/rename-format-stream-part src/ ``` See also the [table of codemods](#codemod-table). In addition, the latest set of codemods can be found in the [`@ai-sdk/codemod`](https://github.com/vercel/ai/tree/main/packages/codemod/src/codemods) repository. ## AI SDK Core Changes ### generateText and streamText Changes #### Maximum Output Tokens The `maxTokens` parameter has been renamed to `maxOutputTokens` for clarity. ```tsx title="AI SDK 4.0" const result = await generateText({ model: "anthropic/claude-sonnet-5.5", maxTokens: 1024, prompt: 'Hello, world!', }); ``` ```tsx title="AI SDK 5.0" const result = await generateText({ model: "anthropic/claude-sonnet-5.5", maxOutputTokens: 1024, prompt: 'Hello, world!', }); ``` ### Message and Type System Changes #### Core Type Renames ##### `CoreMessage` → `ModelMessage` ```tsx title="AI SDK 4.0" import { CoreMessage } from 'ai'; ``` ```tsx title="AI SDK 5.0" import { ModelMessage } from 'ai'; ``` ##### `Message` → `UIMessage` ```tsx title="AI SDK 4.0" import { Message, CreateMessage } from 'ai'; ``` ```tsx title="AI SDK 5.0" import { UIMessage, CreateUIMessage } from 'ai'; ``` ##### `convertToCoreMessages` → `convertToModelMessages` ```tsx title="AI SDK 4.0" import { convertToCoreMessages, streamText } from 'ai'; const result = await streamText({ model: "anthropic/claude-sonnet-5.5", messages: convertToCoreMessages(messages), }); ``` ```tsx title="AI SDK 5.0" import { convertToModelMessages, streamText } from 'ai'; const result = await streamText({ model: "anthropic/claude-sonnet-5.5", messages: convertToModelMessages(messages), }); ``` For more information about model messages, see the [Model Message reference](/docs/reference/ai-sdk-core/model-message). ### UIMessage Changes #### Content → Parts Array For `UIMessage`s (previously called `Message`), the `.content` property has been replaced with a `parts` array structure. ```tsx title="AI SDK 4.0" import { type Message } from 'ai'; // v4 Message type // Messages (useChat) - had content property const message: Message = { id: '1', role: 'user', content: 'Bonjour!', }; ``` ```tsx title="AI SDK 5.0" import { type UIMessage, type ModelMessage } from 'ai'; // UIMessages (useChat) - now use parts array const uiMessage: UIMessage = { id: '1', role: 'user', parts: [{ type: 'text', text: 'Bonjour!' }], }; ``` #### Data Role Removed The `data` role has been removed from UI messages. ```tsx title="AI SDK 4.0" const message = { role: 'data', content: 'Some content', data: { customField: 'value' }, }; ``` ```tsx title="AI SDK 5.0" // V5: Use UI message streams with custom data parts const stream = createUIMessageStream({ execute({ writer }) { // Write custom data instead of message annotations writer.write({ type: 'data-custom', id: 'custom-1', data: { customField: 'value' }, }); }, }); ``` #### UIMessage Reasoning Structure The reasoning property on UI messages has been moved to parts. ```tsx title="AI SDK 4.0" const message: Message = { role: 'assistant', content: 'Hello', reasoning: 'I will greet the user', }; ``` ```tsx title="AI SDK 5.0" const message: UIMessage = { role: 'assistant', parts: [ { type: 'reasoning', text: 'I will greet the user', }, { type: 'text', text: 'Hello', }, ], }; ``` #### Reasoning Part Property Rename The `reasoning` property on reasoning UI parts has been renamed to `text`. ```tsx title="AI SDK 4.0" { message.parts.map((part, index) => { if (part.type === 'reasoning') { return (
{part.reasoning}
); } }); } ``` ```tsx title="AI SDK 5.0" { message.parts.map((part, index) => { if (part.type === 'reasoning') { return (
{part.text}
); } }); } ``` ### File Part Changes File parts now use `.url` instead of `.data` and `.mimeType`. ```tsx title="AI SDK 4.0" { messages.map(message => (
{message.parts.map((part, index) => { if (part.type === 'text') { return
{part.text}
; } else if (part.type === 'file' && part.mimeType.startsWith('image/')) { return ( ); } })}
)); } ``` ```tsx title="AI SDK 5.0" { messages.map(message => (
{message.parts.map((part, index) => { if (part.type === 'text') { return
{part.text}
; } else if ( part.type === 'file' && part.mediaType.startsWith('image/') ) { return ; } })}
)); } ``` ### Stream Data Removal The `StreamData` class has been completely removed and replaced with UI message streams for custom data. ```tsx title="AI SDK 4.0" import { StreamData } from 'ai'; const streamData = new StreamData(); streamData.append('custom-data'); streamData.close(); ``` ```tsx title="AI SDK 5.0" import { createUIMessageStream, createUIMessageStreamResponse } from 'ai'; const stream = createUIMessageStream({ execute({ writer }) { // Write custom data parts writer.write({ type: 'data-custom', id: 'custom-1', data: 'custom-data', }); // Can merge with LLM streams const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, }); writer.merge(result.toUIMessageStream()); }, }); return createUIMessageStreamResponse({ stream }); ``` ### Custom Data Streaming: writeMessageAnnotation/writeData Removed The `writeMessageAnnotation` and `writeData` methods from `DataStreamWriter` have been removed. Instead, use custom data parts with the new `UIMessage` stream architecture. ```tsx title="AI SDK 4.0" import { createDataStreamResponse, streamText } from 'ai'; export async function POST(req: Request) { const { messages } = await req.json(); return createDataStreamResponse({ execute: dataStream => { // Write general data dataStream.writeData('call started'); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, onChunk() { // Write message annotations dataStream.writeMessageAnnotation({ status: 'streaming', timestamp: Date.now(), }); }, onFinish() { // Write final annotations dataStream.writeMessageAnnotation({ id: generateId(), completed: true, }); dataStream.writeData('call completed'); }, }); result.mergeIntoDataStream(dataStream); }, }); } ``` ```tsx title="AI SDK 5.0" import { createUIMessageStream, createUIMessageStreamResponse, streamText, generateId, } from 'ai'; export async function POST(req: Request) { const { messages } = await req.json(); const stream = createUIMessageStream({ execute: ({ writer }) => { const statusId = generateId(); // Write general data (transient - not added to message history) writer.write({ type: 'data-status', id: statusId, data: { status: 'call started' }, }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, onChunk() { // Write data parts that update during streaming writer.write({ type: 'data-status', id: statusId, data: { status: 'streaming', timestamp: Date.now(), }, }); }, onFinish() { // Write final data parts writer.write({ type: 'data-status', id: statusId, data: { status: 'completed', }, }); }, }); writer.merge(result.toUIMessageStream()); }, }); return createUIMessageStreamResponse({ stream }); } ``` For more detailed information about streaming custom data in v5, see the [Streaming Data guide](/docs/ai-sdk-ui/streaming-data). ##### Provider Metadata → Provider Options The `providerMetadata` input parameter has been renamed to `providerOptions`. Note that the returned metadata in results is still called `providerMetadata`. ```tsx title="AI SDK 4.0" const result = await generateText({ model: 'openai/gpt-5', prompt: 'Hello', providerMetadata: { openai: { store: false }, }, }); ``` ```tsx title="AI SDK 5.0" const result = await generateText({ model: 'openai/gpt-5', prompt: 'Hello', providerOptions: { // Input parameter renamed openai: { store: false }, }, }); // Returned metadata still uses providerMetadata: console.log(result.providerMetadata?.openai); ``` #### Tool Definition Changes (parameters → inputSchema) Tool definitions have been updated to use `inputSchema` instead of `parameters` and error classes have been renamed. ```tsx title="AI SDK 4.0" import { tool } from 'ai'; const weatherTool = tool({ description: 'Get the weather for a city', parameters: z.object({ city: z.string(), }), execute: async ({ city }) => { return `Weather in ${city}`; }, }); ``` ```tsx title="AI SDK 5.0" import { tool } from 'ai'; const weatherTool = tool({ description: 'Get the weather for a city', inputSchema: z.object({ city: z.string(), }), execute: async ({ city }) => { return `Weather in ${city}`; }, }); ``` #### Tool Result Content: experimental\_toToolResultContent → toModelOutput The `experimental_toToolResultContent` option has been renamed to `toModelOutput` and is no longer experimental. ```tsx title="AI SDK 4.0" const screenshotTool = tool({ description: 'Take a screenshot', parameters: z.object({}), execute: async () => { const imageData = await takeScreenshot(); return imageData; // base64 string }, experimental_toToolResultContent: result => [{ type: 'image', data: result }], }); ``` ```tsx title="AI SDK 5.0" const screenshotTool = tool({ description: 'Take a screenshot', inputSchema: z.object({}), execute: async () => { const imageData = await takeScreenshot(); return imageData; }, toModelOutput: result => ({ type: 'content', value: [{ type: 'media', mediaType: 'image/png', data: result }], }), }); ``` ### Tool Property Changes (args/result → input/output) Tool call and result properties have been renamed for better consistency with schemas. ```tsx title="AI SDK 4.0" // Tool calls used "args" and "result" for await (const part of result.fullStream) { switch (part.type) { case 'tool-call': console.log('Tool args:', part.args); break; case 'tool-result': console.log('Tool result:', part.result); break; } } ``` ```tsx title="AI SDK 5.0" // Tool calls now use "input" and "output" for await (const part of result.fullStream) { switch (part.type) { case 'tool-call': console.log('Tool input:', part.input); break; case 'tool-result': console.log('Tool output:', part.output); break; } } ``` ### Tool Execution Error Handling The `ToolExecutionError` class has been removed. Tool execution errors now appear as `tool-error` content parts in the result steps, enabling automated LLM roundtrips in multi-step scenarios. ```tsx title="AI SDK 4.0" import { ToolExecutionError } from 'ai'; try { const result = await generateText({ // ... }); } catch (error) { if (error instanceof ToolExecutionError) { console.log('Tool execution failed:', error.message); console.log('Tool name:', error.toolName); console.log('Tool input:', error.toolInput); } } ``` ```tsx title="AI SDK 5.0" // Tool execution errors now appear in result steps const { steps } = await generateText({ // ... }); // check for tool errors in the steps const toolErrors = steps.flatMap(step => step.content.filter(part => part.type === 'tool-error'), ); toolErrors.forEach(toolError => { console.log('Tool error:', toolError.error); console.log('Tool name:', toolError.toolName); console.log('Tool input:', toolError.input); }); ``` For streaming scenarios, tool execution errors appear as `tool-error` parts in the stream, while other errors appear as `error` parts. ### Tool Call Streaming Now Default (toolCallStreaming Removed) The `toolCallStreaming` option has been removed in AI SDK 5.0. Tool call streaming is now always enabled by default. ```tsx title="AI SDK 4.0" const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, toolCallStreaming: true, // Optional parameter to enable streaming tools: { weatherTool, searchTool, }, }); ``` ```tsx title="AI SDK 5.0" const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: convertToModelMessages(messages), // toolCallStreaming removed - streaming is always enabled tools: { weatherTool, searchTool, }, }); ``` ### Tool Part Type Changes (UIMessage) In v5, UI tool parts use typed naming: `tool-${toolName}` instead of generic types. ```tsx title="AI SDK 4.0" // Generic tool-invocation type { message.parts.map(part => { if (part.type === 'tool-invocation') { return
{part.toolInvocation.toolName}
; } }); } ``` ```tsx title="AI SDK 5.0" // Type-safe tool parts with specific names { message.parts.map(part => { switch (part.type) { case 'tool-getWeatherInformation': return
Getting weather...
; case 'tool-askForConfirmation': return
Asking for confirmation...
; } }); } ``` ### Dynamic Tools Support AI SDK 5.0 introduces dynamic tools for handling tools with unknown types at development time, such as MCP tools without schemas or user-defined functions at runtime. #### New dynamicTool Helper The new `dynamicTool` helper function allows you to define tools where the input and output types are not known at compile time. ```tsx title="AI SDK 5.0" import { dynamicTool } from 'ai'; import { z } from 'zod'; // Define a dynamic tool const runtimeTool = dynamicTool({ description: 'A tool defined at runtime', inputSchema: z.object({}), execute: async input => { // Input and output are typed as 'unknown' const { query } = input as { query: string }; return { result: `Processed: ${query}` }; }, }); ``` #### MCP Tools Without Schemas MCP tools that don't provide schemas are now automatically treated as dynamic tools: ```tsx title="AI SDK 5.0" import { MCPClient } from 'ai'; const client = new MCPClient({ /* ... */ }); const tools = await client.getTools(); // Tools without schemas are now 'dynamic' type // and won't break type inference when mixed with static tools ``` #### Type-Safe Handling with Mixed Tools When using both static and dynamic tools together, use the `dynamic` flag for type narrowing: ```tsx title="AI SDK 5.0" const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { // Static tool with known types weather: weatherTool, // Dynamic tool with unknown types customDynamicTool: dynamicTool({ /* ... */ }), }, onStepFinish: step => { // Handle tool calls with type safety for (const toolCall of step.toolCalls) { if (toolCall.dynamic) { // Dynamic tool: input/output are 'unknown' console.log('Dynamic tool called:', toolCall.toolName); continue; } // Static tools have full type inference switch (toolCall.toolName) { case 'weather': // TypeScript knows the exact types console.log(toolCall.input.location); // string break; } } }, }); ``` #### New dynamic-tool UI Part UI messages now include a `dynamic-tool` part type for rendering dynamic tool invocations: ```tsx title="AI SDK 5.0" { message.parts.map((part, index) => { switch (part.type) { // Static tools use specific types case 'tool-weather': return
Weather: {part.input.city}
; // Dynamic tools use the generic dynamic-tool type case 'dynamic-tool': return (
Dynamic tool: {part.toolName}
{JSON.stringify(part.input, null, 2)}
); } }); } ``` #### Breaking Change: Type Narrowing Required for Tool Calls and Results When iterating over `toolCalls` and `toolResults`, you now need to check the `dynamic` flag first for proper type narrowing: ```tsx title="AI SDK 4.0" // Direct type checking worked without dynamic flag onStepFinish: step => { for (const toolCall of step.toolCalls) { switch (toolCall.toolName) { case 'weather': console.log(toolCall.input.location); // typed as string break; case 'search': console.log(toolCall.input.query); // typed as string break; } } }; ``` ```tsx title="AI SDK 5.0" // Must check dynamic flag first for type narrowing onStepFinish: step => { for (const toolCall of step.toolCalls) { // Check if it's a dynamic tool first if (toolCall.dynamic) { console.log('Dynamic tool:', toolCall.toolName); console.log('Input:', toolCall.input); // typed as unknown continue; } // Now TypeScript knows it's a static tool switch (toolCall.toolName) { case 'weather': console.log(toolCall.input.location); // typed as string break; case 'search': console.log(toolCall.input.query); // typed as string break; } } }; ``` ### Tool UI Part State Changes Tool UI parts now use more granular states that better represent the streaming lifecycle and error handling. ```tsx title="AI SDK 4.0" // Old states { message.parts.map(part => { if (part.type === 'tool-invocation') { switch (part.toolInvocation.state) { case 'partial-call': return
Loading...
; case 'call': return (
Tool called with {JSON.stringify(part.toolInvocation.args)}
); case 'result': return
Result: {part.toolInvocation.result}
; } } }); } ``` ```tsx title="AI SDK 5.0" // New granular states { message.parts.map(part => { switch (part.type) { case 'tool-getWeatherInformation': switch (part.state) { case 'input-streaming': return
{JSON.stringify(part.input, null, 2)}
; case 'input-available': return
Getting weather for {part.input.city}...
; case 'output-available': return
Weather: {part.output}
; case 'output-error': return
Error: {part.errorText}
; } } }); } ``` **State Changes:** - `partial-call` → `input-streaming` (tool input being streamed) - `call` → `input-available` (tool input complete, ready to execute) - `result` → `output-available` (tool execution successful) - New: `output-error` (tool execution failed) #### Rendering Tool Invocations (Catch-All Pattern) In v4, you typically rendered tool invocations using a catch-all `tool-invocation` type. In v5, the **recommended approach is to handle each tool specifically using its typed part name (e.g., `tool-getWeather`)**. However, if you need a catch-all pattern for rendering all tool invocations the same way, you can use the `isToolUIPart` and `getToolName` helper functions as a fallback. ```tsx title="AI SDK 4.0" { message.parts.map((part, index) => { switch (part.type) { case 'text': return
{part.text}
; case 'tool-invocation': const { toolInvocation } = part; return (
{toolInvocation.toolName} {toolInvocation.state === 'result' ? ( Click to expand ) : ( calling... )} {toolInvocation.state === 'result' ? (
{JSON.stringify(toolInvocation.result, null, 2)}
) : null}
); } }); } ``` ```tsx title="AI SDK 5.0" import { isToolUIPart, getToolName } from 'ai'; { message.parts.map((part, index) => { switch (part.type) { case 'text': return
{part.text}
; default: if (isToolUIPart(part)) { const toolInvocation = part; return (
{getToolName(toolInvocation)} {toolInvocation.state === 'output-available' ? ( Click to expand ) : ( calling... )} {toolInvocation.state === 'output-available' ? (
{JSON.stringify(toolInvocation.output, null, 2)}
) : null}
); } } }); } ``` #### Media Type Standardization `mimeType` has been renamed to `mediaType` for consistency. Both image and file types are supported in model messages. ```tsx title="AI SDK 4.0" const result = await generateText({ model: someModel, messages: [ { role: 'user', content: [ { type: 'text', text: 'What do you see?' }, { type: 'image', image: new Uint8Array([0, 1, 2, 3]), mimeType: 'image/png', }, { type: 'file', data: contents, mimeType: 'application/pdf', }, ], }, ], }); ``` ```tsx title="AI SDK 5.0" const result = await generateText({ model: someModel, messages: [ { role: 'user', content: [ { type: 'text', text: 'What do you see?' }, { type: 'image', image: new Uint8Array([0, 1, 2, 3]), mediaType: 'image/png', }, { type: 'file', data: contents, mediaType: 'application/pdf', }, ], }, ], }); ``` ### Reasoning Support #### Reasoning Text Property Rename The `.reasoning` property has been renamed to `.reasoningText` for multi-step generations. ```tsx title="AI SDK 4.0" for (const step of steps) { console.log(step.reasoning); } ``` ```tsx title="AI SDK 5.0" for (const step of steps) { console.log(step.reasoningText); } ``` #### Generate Text Reasoning Property Changes In `generateText()` and `streamText()` results, reasoning properties have been renamed. ```tsx title="AI SDK 4.0" const result = await generateText({ model: anthropic('claude-sonnet-4-20250514'), prompt: 'Explain your reasoning', }); console.log(result.reasoning); // String reasoning text console.log(result.reasoningDetails); // Array of reasoning details ``` ```tsx title="AI SDK 5.0" const result = await generateText({ model: anthropic('claude-sonnet-4-20250514'), prompt: 'Explain your reasoning', }); console.log(result.reasoningText); // String reasoning text console.log(result.reasoning); // Array of reasoning details ``` ### Continuation Steps Removal The `experimental_continueSteps` option has been removed from `generateText()`. ```tsx title="AI SDK 4.0" const result = await generateText({ experimental_continueSteps: true, // ... }); ``` ```tsx title="AI SDK 5.0" const result = await generateText({ // experimental_continueSteps has been removed // Use newer models with higher output token limits instead // ... }); ``` ### Image Generation Changes Image model settings have been moved to `providerOptions`. ```tsx title="AI SDK 4.0" await generateImage({ model: luma.image('photon-flash-1', { maxImagesPerCall: 5, pollIntervalMillis: 500, }), prompt, n: 10, }); ``` ```tsx title="AI SDK 5.0" await generateImage({ model: luma.image('photon-flash-1'), prompt, n: 10, maxImagesPerCall: 5, providerOptions: { luma: { pollIntervalMillis: 500 }, }, }); ``` ### Step Result Changes #### Step Type Removal The `stepType` property has been removed from step results. ```tsx title="AI SDK 4.0" steps.forEach(step => { switch (step.stepType) { case 'initial': console.log('Initial step'); break; case 'tool-result': console.log('Tool result step'); break; case 'done': console.log('Final step'); break; } }); ``` ```tsx title="AI SDK 5.0" steps.forEach((step, index) => { if (index === 0) { console.log('Initial step'); } else if (step.toolResults.length > 0) { console.log('Tool result step'); } else { console.log('Final step'); } }); ``` ### Step Control: maxSteps → stopWhen For core functions like `generateText` and `streamText`, the `maxSteps` parameter has been replaced with `stopWhen`, which provides more flexible control over multi-step execution. The `stopWhen` parameter defines conditions for stopping the generation **when the last step contains tool results**. When multiple conditions are provided as an array, the generation stops if any condition is met. ```tsx title="AI SDK 4.0" // V4: Simple numeric limit const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages, maxSteps: 5, // Stop after a maximum of 5 steps }); // useChat with maxSteps const { messages } = useChat({ maxSteps: 3, // Stop after a maximum of 3 steps }); ``` ```tsx title="AI SDK 5.0" import { isStepCount, hasToolCall } from 'ai'; // V5: Server-side - flexible stopping conditions with stopWhen const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages, // Only triggers when last step has tool results stopWhen: isStepCount(5), // Stop at step 5 if tools were called }); // Server-side - stop when a specific tool is called const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages, stopWhen: hasToolCall('finalizeTask'), // Stop when finalizeTask tool is called }); ``` **Common stopping patterns:** ```tsx title="AI SDK 5.0" // Stop after N steps (equivalent to old maxSteps) // Note: Only applies when the last step has tool results stopWhen: isStepCount(5); // Stop when a specific tool is called stopWhen: hasToolCall('finalizeTask'); // Stop when either tool is called stopWhen: hasToolCall('submitOrder', 'finalizeTask'); // Multiple conditions (stops if ANY condition is met) stopWhen: [ isStepCount(10), // Maximum 10 steps hasToolCall('submitOrder'), // Or when order is submitted ]; // Custom condition based on step content stopWhen: ({ steps }) => { const lastStep = steps[steps.length - 1]; // Custom logic - only triggers if last step has tool results return lastStep?.text?.includes('COMPLETE'); }; ``` **Important:** The `stopWhen` conditions are only evaluated when the last step contains tool results. #### Usage vs Total Usage Usage properties now distinguish between single step and total usage. ```tsx title="AI SDK 4.0" // usage contained total token usage across all steps console.log(result.usage); ``` ```tsx title="AI SDK 5.0" // usage contains token usage from the final step only console.log(result.usage); // totalUsage contains total token usage across all steps console.log(result.totalUsage); ``` ## AI SDK UI Changes ### Package Structure Changes ### `@ai-sdk/rsc` Package Extraction The `ai/rsc` export has been extracted to a separate package `@ai-sdk/rsc`. ```tsx title="AI SDK 4.0" import { createStreamableValue } from 'ai/rsc'; ``` ```tsx title="AI SDK 5.0" import { createStreamableValue } from '@ai-sdk/rsc'; ``` Don't forget to install the new package: `npm install @ai-sdk/rsc` ### React UI Hooks Moved to `@ai-sdk/react` The deprecated `ai/react` export has been removed in favor of `@ai-sdk/react`. ```tsx title="AI SDK 4.0" import { useChat } from 'ai/react'; ``` ```tsx title="AI SDK 5.0" import { useChat } from '@ai-sdk/react'; ``` Don't forget to install the new package: `npm install @ai-sdk/react` ### useChat Changes The `useChat` hook has undergone significant changes in v5, with new transport architecture, removal of managed input state, and more. #### maxSteps Removal The `maxSteps` parameter has been removed from `useChat`. You should now use server-side `stopWhen` conditions for multi-step tool execution control, and manually submit tool results and trigger new messages for client-side tool calls. ```tsx title="AI SDK 4.0" const { messages, sendMessage } = useChat({ maxSteps: 5, // Automatic tool result submission }); ``` ```tsx title="AI SDK 5.0" // Server-side: Use stopWhen for multi-step control import { streamText, convertToModelMessages, isStepCount } from 'ai'; const result = await streamText({ model: "anthropic/claude-sonnet-5.5", messages: convertToModelMessages(messages), stopWhen: isStepCount(5), // Stop after 5 steps with tool calls }); // Client-side: Configure automatic submission import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls, } from 'ai'; const { messages, sendMessage, addToolOutput } = useChat({ // Automatically submit when all tool results are available sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, async onToolCall({ toolCall }) { const result = await executeToolCall(toolCall); // Important: Don't await addToolOutput inside onToolCall to avoid deadlocks addToolOutput({ tool: toolCall.toolName, toolCallId: toolCall.toolCallId, output: result, }); }, }); ``` Important: When using `sendAutomaticallyWhen`, don't use `await` with `addToolOutput` inside `onToolCall` as it can cause deadlocks. The `await` is useful when you're not using automatic submission and need to ensure the messages are updated before manually calling `sendMessage()`. This change provides more flexibility for handling tool calls and aligns client behavior with server-side multi-step execution patterns. For more details on the new tool submission approach, see the [Tool Result Submission Changes](#tool-result-submission-changes) section below. #### Initial Messages Renamed The `initialMessages` option has been renamed to `messages`. ```tsx title="AI SDK 4.0" import { useChat, type Message } from '@ai-sdk/react'; function ChatComponent({ initialMessages }: { initialMessages: Message[] }) { const { messages } = useChat({ initialMessages: initialMessages, // ... }); // your component } ``` ```tsx title="AI SDK 5.0" import { useChat, type UIMessage } from '@ai-sdk/react'; function ChatComponent({ initialMessages }: { initialMessages: UIMessage[] }) { const { messages } = useChat({ messages: initialMessages, // ... }); // your component } ``` #### Sharing Chat Instances In v4, you could share chat state between components by using the same `id` parameter in multiple `useChat` hooks. ```tsx title="AI SDK 4.0" // Component A const { messages } = useChat({ id: 'shared-chat', api: '/api/chat', }); // Component B - would share the same chat state const { messages } = useChat({ id: 'shared-chat', api: '/api/chat', }); ``` In v5, you need to explicitly share chat instances by passing a shared `Chat` instance. ```tsx title="AI SDK 5.0" // e.g. Store Chat instance in React Context and create a custom hook // Component A const { chat } = useSharedChat(); // Custom hook that accesses shared Chat from context const { messages, sendMessage } = useChat({ chat, // Pass the shared chat instance }); // Component B - shares the same chat instance const { chat } = useSharedChat(); // Same hook to access shared Chat from context const { messages } = useChat({ chat, // Same shared chat instance }); ``` For a complete example of sharing chat state across components, see the [Share Chat State Across Components](/cookbook/next/use-shared-chat-context) recipe. #### Chat Transport Architecture Configuration is now handled through transport objects instead of direct API options. ```tsx title="AI SDK 4.0" import { useChat } from '@ai-sdk/react'; const { messages } = useChat({ api: '/api/chat', credentials: 'include', headers: { 'Custom-Header': 'value' }, }); ``` ```tsx title="AI SDK 5.0" import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', credentials: 'include', headers: { 'Custom-Header': 'value' }, }), }); ``` #### Removed Managed Input State The `useChat` hook no longer manages input state internally. You must now manage input state manually. ```tsx title="AI SDK 4.0" import { useChat } from '@ai-sdk/react'; export default function Page() { const { messages, input, handleInputChange, handleSubmit } = useChat({ api: '/api/chat', }); return (
); } ``` ```tsx title="AI SDK 5.0" import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { useState } from 'react'; export default function Page() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat' }), }); const handleSubmit = e => { e.preventDefault(); sendMessage({ text: input }); setInput(''); }; return (
setInput(e.target.value)} />
); } ``` #### Message Sending: `append` → `sendMessage` The `append` function has been replaced with `sendMessage` and requires structured message format. ```tsx title="AI SDK 4.0" const { append } = useChat(); // Simple text message append({ role: 'user', content: 'Hello' }); // With custom body append( { role: 'user', content: 'Hello', }, { body: { imageUrl: 'https://...' } }, ); ``` ```tsx title="AI SDK 5.0" const { sendMessage } = useChat(); // Simple text message (most common usage) sendMessage({ text: 'Hello' }); // Or with explicit parts array sendMessage({ parts: [{ type: 'text', text: 'Hello' }], }); // With custom body (via request options) sendMessage( { role: 'user', parts: [{ type: 'text', text: 'Hello' }] }, { body: { imageUrl: 'https://...' } }, ); ``` #### Message Regeneration: `reload` → `regenerate` The `reload` function has been renamed to `regenerate` with enhanced functionality. ```tsx title="AI SDK 4.0" const { reload } = useChat(); // Regenerate last message reload(); ``` ```tsx title="AI SDK 5.0" const { regenerate } = useChat(); // Regenerate last message regenerate(); // Regenerate specific message regenerate({ messageId: 'message-123' }); ``` #### onResponse Removal The `onResponse` callback has been removed from `useChat` and `useCompletion`. ```tsx title="AI SDK 4.0" const { messages } = useChat({ onResponse(response) { // handle response }, }); ``` ```tsx title="AI SDK 5.0" const { messages } = useChat({ // onResponse is no longer available }); ``` #### Send Extra Message Fields Default The `sendExtraMessageFields` option has been removed and is now the default behavior. ```tsx title="AI SDK 4.0" const { messages } = useChat({ sendExtraMessageFields: true, }); ``` ```tsx title="AI SDK 5.0" const { messages } = useChat({ // sendExtraMessageFields is now the default }); ``` #### Keep Last Message on Error Removal The `keepLastMessageOnError` option has been removed as it's no longer needed. ```tsx title="AI SDK 4.0" const { messages } = useChat({ keepLastMessageOnError: true, }); ``` ```tsx title="AI SDK 5.0" const { messages } = useChat({ // keepLastMessageOnError is no longer needed }); ``` #### Chat Request Options Changes The `data` and `allowEmptySubmit` options have been removed from `ChatRequestOptions`. ```tsx title="AI SDK 4.0" handleSubmit(e, { data: { imageUrl: 'https://...' }, body: { custom: 'value' }, allowEmptySubmit: true, }); ``` ```tsx title="AI SDK 5.0" sendMessage( { /* yourMessage */ }, { body: { custom: 'value', imageUrl: 'https://...', // Move data to body }, }, ); ``` #### Request Options Type Rename `RequestOptions` has been renamed to `CompletionRequestOptions`. ```tsx title="AI SDK 4.0" import type { RequestOptions } from 'ai'; ``` ```tsx title="AI SDK 5.0" import type { CompletionRequestOptions } from 'ai'; ``` #### addToolResult Renamed to addToolOutput The `addToolResult` method has been renamed to `addToolOutput`. Additionally, the `result` parameter has been renamed to `output` for consistency with other tool-related APIs. ```tsx title="AI SDK 4.0" const { addToolResult } = useChat(); // Add tool result with 'result' parameter addToolResult({ toolCallId: 'tool-call-123', result: 'Weather: 72°F, sunny', }); ``` ```tsx title="AI SDK 5.0" const { addToolOutput } = useChat(); // Add tool output with 'output' parameter and 'tool' name for type safety addToolOutput({ tool: 'getWeather', toolCallId: 'tool-call-123', output: 'Weather: 72°F, sunny', }); ``` `addToolResult` is still available but deprecated. It will be removed in version 6. #### Tool Result Submission Changes The automatic tool result submission behavior has been updated in `useChat` and the `Chat` component. You now have more control and flexibility over when tool results are submitted. - `onToolCall` no longer supports returning values to automatically submit tool results - You must explicitly call `addToolOutput` to provide tool results - Use `sendAutomaticallyWhen` with `lastAssistantMessageIsCompleteWithToolCalls` helper for automatic submission - Important: Don't use `await` with `addToolOutput` inside `onToolCall` to avoid deadlocks - The `maxSteps` parameter has been removed from the `Chat` component and `useChat` hook - For multi-step tool execution, use server-side `stopWhen` conditions instead (see [maxSteps Removal](#maxsteps-removal)) ```tsx title="AI SDK 4.0" const { messages, sendMessage, addToolResult } = useChat({ maxSteps: 5, // Removed in v5 // Automatic submission by returning a value async onToolCall({ toolCall }) { if (toolCall.toolName === 'getLocation') { const cities = ['New York', 'Los Angeles', 'Chicago', 'San Francisco']; return cities[Math.floor(Math.random() * cities.length)]; } }, }); ``` ```tsx title="AI SDK 5.0" import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls, } from 'ai'; const { messages, sendMessage, addToolOutput } = useChat({ // Automatic submission with helper sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, async onToolCall({ toolCall }) { if (toolCall.toolName === 'getLocation') { const cities = ['New York', 'Los Angeles', 'Chicago', 'San Francisco']; // Important: Don't await inside onToolCall to avoid deadlocks addToolOutput({ tool: 'getLocation', toolCallId: toolCall.toolCallId, output: cities[Math.floor(Math.random() * cities.length)], }); } }, }); ``` #### Loading State Changes The deprecated `isLoading` helper has been removed in favor of `status`. ```tsx title="AI SDK 4.0" const { isLoading } = useChat(); ``` ```tsx title="AI SDK 5.0" const { status } = useChat(); // Use state instead of isLoading for more granular control ``` #### Resume Stream Support The resume functionality has been moved from `experimental_resume` to `resumeStream`. ```tsx title="AI SDK 4.0" // Resume was experimental const { messages } = useChat({ experimental_resume: true, }); ``` ```tsx title="AI SDK 5.0" const { messages } = useChat({ resumeStream: true, // Resume interrupted streams }); ``` #### Dynamic Body Values In v4, the `body` option in useChat configuration would dynamically update with component state changes. In v5, the `body` value is only captured at the first render and remains static throughout the component lifecycle. ```tsx title="AI SDK 4.0" const [temperature, setTemperature] = useState(0.7); const { messages } = useChat({ api: '/api/chat', body: { temperature, // This would update dynamically in v4 }, }); ``` ```tsx title="AI SDK 5.0" const [temperature, setTemperature] = useState(0.7); // Option 1: Use request-level configuration (Recommended) const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat' }), }); // Pass dynamic values at request time sendMessage( { text: input }, { body: { temperature, // Current temperature value at request time }, }, ); // Option 2: Use function configuration with useRef const temperatureRef = useRef(temperature); temperatureRef.current = temperature; const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', body: () => ({ temperature: temperatureRef.current, }), }), }); ``` For more details on request configuration, see the [Chatbot guide](/docs/ai-sdk-ui/chatbot#request-configuration). #### Usage Information In v4, usage information was directly accessible through the `onFinish` callback's options parameter. In v5, usage data is attached as metadata to individual messages using the `messageMetadata` function in `toUIMessageStreamResponse`. ```tsx title="AI SDK 4.0" const { messages } = useChat({ onFinish(message, options) { const usage = options.usage; console.log('Usage:', usage); }, }); ``` ```tsx title="AI SDK 5.0" import { convertToModelMessages, streamText, UIMessage, type LanguageModelUsage, } from 'ai'; // Create a new metadata type (optional for type-safety) type MyMetadata = { totalUsage: LanguageModelUsage; }; // Create a new custom message type with your own metadata export type MyUIMessage = UIMessage; export async function POST(req: Request) { const { messages }: { messages: MyUIMessage[] } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: convertToModelMessages(messages), }); return result.toUIMessageStreamResponse({ originalMessages: messages, messageMetadata: ({ part }) => { // Send total usage when generation is finished if (part.type === 'finish') { return { totalUsage: part.totalUsage }; } }, }); } ``` Then, on the client, you can access the message-level metadata. ```tsx title="AI SDK 5.0 - Client" 'use client'; import { useChat } from '@ai-sdk/react'; import type { MyUIMessage } from './api/chat/route'; import { DefaultChatTransport } from 'ai'; export default function Chat() { // Use custom message type defined on the server (optional for type-safety) const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); return (
{messages.map(m => (
{m.role === 'user' ? 'User: ' : 'AI: '} {m.parts.map(part => { if (part.type === 'text') { return part.text; } })} {/* Render usage via metadata */} {m.metadata?.totalUsage && (
Total usage: {m.metadata?.totalUsage.totalTokens} tokens
)}
))}
); } ``` You can also access your metadata from the `onFinish` callback of `useChat`: ```tsx title="AI SDK 5.0 - onFinish" 'use client'; import { useChat } from '@ai-sdk/react'; import type { MyUIMessage } from './api/chat/route'; import { DefaultChatTransport } from 'ai'; export default function Chat() { // Use custom message type defined on the server (optional for type-safety) const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), onFinish: ({ message }) => { // Access message metadata via onFinish callback console.log(message.metadata?.totalUsage); }, }); } ``` #### Request Body Preparation: experimental\_prepareRequestBody → prepareSendMessagesRequest The `experimental_prepareRequestBody` option has been replaced with `prepareSendMessagesRequest` in the transport configuration. ```tsx title="AI SDK 4.0" import { useChat } from '@ai-sdk/react'; const { messages } = useChat({ api: '/api/chat', // Only send the last message to the server: experimental_prepareRequestBody({ messages, id }) { return { message: messages[messages.length - 1], id }; }, }); ``` ```tsx title="AI SDK 5.0" import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', // Only send the last message to the server: prepareSendMessagesRequest({ messages, id }) { return { body: { message: messages[messages.length - 1], id } }; }, }), }); ``` ### `@ai-sdk/vue` Changes The Vue.js integration has been completely restructured, replacing the `useChat` composable with a `Chat` class. #### useChat Replaced with Chat Class ```typescript title="@ai-sdk/vue v1" ``` ```typescript title="@ai-sdk/vue v2" ``` #### Message Structure Changes Messages now use a `parts` array instead of a `content` string. ```typescript title="@ai-sdk/vue v1" ``` ```typescript title="@ai-sdk/vue v2" ``` ### `@ai-sdk/svelte` Changes The Svelte integration has also been updated with new constructor patterns and readonly properties. #### Constructor API Changes ```js title="@ai-sdk/svelte v1" import { Chat } from '@ai-sdk/svelte'; const chatInstance = Chat({ api: '/api/chat', }); ``` ```js title="@ai-sdk/svelte v2" import { Chat } from '@ai-sdk/svelte'; import { DefaultChatTransport } from 'ai'; const chatInstance = Chat(() => ({ transport: new DefaultChatTransport({ api: '/api/chat' }), })); ``` ##### Properties Made Readonly Properties are now readonly and must be updated using setter methods. ```js title="@ai-sdk/svelte v1" // Direct property mutation was allowed chatInstance.messages = [...chatInstance.messages, newMessage]; ``` ```js title="@ai-sdk/svelte v2" // Must use setter methods chatInstance.setMessages([...chatInstance.messages, newMessage]); ``` ##### Removed Managed Input Like React and Vue, input management has been removed from the Svelte integration. ```js title="@ai-sdk/svelte v1" // Input was managed internally const { messages, input, handleSubmit } = chatInstance; ``` ```js title="@ai-sdk/svelte v2" // Must manage input state manually let input = ''; const { messages, sendMessage } = chatInstance; const handleSubmit = () => { sendMessage({ text: input }); input = ''; }; ``` #### `@ai-sdk/ui-utils` Package Removal The `@ai-sdk/ui-utils` package has been removed and its exports moved to the main `ai` package. ```tsx title="AI SDK 4.0" import { getTextFromDataUrl } from '@ai-sdk/ui-utils'; ``` ```tsx title="AI SDK 5.0" import { getTextFromDataUrl } from 'ai'; ``` **Note**: `processDataStream` was removed entirely in v5.0. Use `readUIMessageStream` instead for processing UI message streams, or use the more configurable Chat/useChat APIs for most use cases. ### useCompletion Changes The `data` property has been removed from the `useCompletion` hook. ```tsx title="AI SDK 4.0" const { completion, handleSubmit, data, // No longer available } = useCompletion(); ``` ```tsx title="AI SDK 5.0" const { completion, handleSubmit, // data property removed entirely } = useCompletion(); ``` ### useAssistant Removal The `useAssistant` hook has been removed. ```tsx title="AI SDK 4.0" import { useAssistant } from '@ai-sdk/react'; ``` ```tsx title="AI SDK 5.0" import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; function Chat() { const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', }), }); // ... } ``` The `useAssistant` hook was specific to the OpenAI Assistants API. OpenAI has deprecated that API in favor of the Responses API. Configure `useChat` for your route as shown above, then return a UI message stream from the route: ```tsx title="app/api/chat/route.ts" import { openai } from '@ai-sdk/openai'; import { convertToModelMessages, streamText, type UIMessage } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: openai.responses('gpt-4o-mini'), prompt: convertToModelMessages(messages), }); return result.toUIMessageStreamResponse(); } ``` For persistent conversation state and built-in tools, see the [OpenAI Responses API guide](/cookbook/guides/openai-responses). If you need to connect `useChat` to another backend, see the [transport documentation](/docs/ai-sdk-ui/transport) and [stream protocol](/docs/ai-sdk-ui/stream-protocol). For migrating existing OpenAI Assistants data and API calls, see OpenAI's [Assistants migration guide](https://platform.openai.com/docs/assistants/migration). #### Attachments → File Parts The `experimental_attachments` property has been replaced with the parts array. ```tsx title="AI SDK 4.0" { messages.map(message => (
{message.content}
{message.experimental_attachments?.map((attachment, index) => attachment.contentType?.includes('image/') ? ( {attachment.name} ) : attachment.contentType?.includes('text/') ? (
{getTextFromDataUrl(attachment.url)}
) : null, )}
)); } ``` ```tsx title="AI SDK 5.0" { messages.map(message => (
{message.parts.map((part, index) => { if (part.type === 'text') { return
{part.text}
; } if (part.type === 'file' && part.mediaType?.startsWith('image/')) { return (
); } })}
)); } ``` Some models do not support text files (text/plain, text/markdown, text/csv, etc.) as file parts. For text files, you can read and send the context as a text part instead: ```tsx // Instead of this: { type: 'file', data: buffer, mediaType: 'text/plain' } // Do this: { type: 'text', text: buffer.toString('utf-8') } ``` ### Embedding Changes #### Provider Options for Embeddings Embedding model settings now use provider options instead of model parameters. ```tsx title="AI SDK 4.0" const { embedding } = await embed({ model: openai('text-embedding-3-small', { dimensions: 10, }), }); ``` ```tsx title="AI SDK 5.0" const { embedding } = await embed({ model: openai('text-embedding-3-small'), providerOptions: { openai: { dimensions: 10, }, }, }); ``` #### Raw Response → Response The `rawResponse` property has been renamed to `response`. ```tsx title="AI SDK 4.0" const { rawResponse } = await embed(/* */); ``` ```tsx title="AI SDK 5.0" const { response } = await embed(/* */); ``` #### Parallel Requests in embedMany `embedMany` now makes parallel requests with a configurable `maxParallelCalls` option. ```tsx title="AI SDK 5.0" const { embeddings, usage } = await embedMany({ maxParallelCalls: 2, // Limit parallel requests model: 'openai/text-embedding-3-small', values: [ 'sunny day at the beach', 'rainy afternoon in the city', 'snowy night in the mountains', ], }); ``` #### LangChain Adapter Moved to `@ai-sdk/langchain` The `LangChainAdapter` has been moved to `@ai-sdk/langchain` and the API has been updated to use UI message streams. ```tsx title="AI SDK 4.0" import { LangChainAdapter } from 'ai'; const response = LangChainAdapter.toDataStreamResponse(stream); ``` ```tsx title="AI SDK 5.0" import { toUIMessageStream } from '@ai-sdk/langchain'; import { createUIMessageStreamResponse } from 'ai'; const response = createUIMessageStreamResponse({ stream: toUIMessageStream(stream), }); ``` Don't forget to install the new package: `npm install @ai-sdk/langchain` #### LlamaIndex Adapter Moved to `@ai-sdk/llamaindex` The `LlamaIndexAdapter` has been extracted to a separate package `@ai-sdk/llamaindex` and follows the same UI message stream pattern. ```tsx title="AI SDK 4.0" import { LlamaIndexAdapter } from 'ai'; const response = LlamaIndexAdapter.toDataStreamResponse(stream); ``` ```tsx title="AI SDK 5.0" import { toUIMessageStream } from '@ai-sdk/llamaindex'; import { createUIMessageStreamResponse } from 'ai'; const response = createUIMessageStreamResponse({ stream: toUIMessageStream(stream), }); ``` Don't forget to install the new package: `npm install @ai-sdk/llamaindex` ## Streaming Architecture The streaming architecture has been completely redesigned in v5 to support better content differentiation, concurrent streaming of multiple parts, and improved real-time UX. ### Stream Protocol Changes #### Stream Protocol: Single Chunks → Start/Delta/End Pattern The fundamental streaming pattern has changed from single chunks to a three-phase pattern with unique IDs for each content block. ```tsx title="AI SDK 4.0" for await (const chunk of result.fullStream) { switch (chunk.type) { case 'text-delta': { process.stdout.write(chunk.textDelta); break; } } } ``` ```tsx title="AI SDK 5.0" for await (const chunk of result.fullStream) { switch (chunk.type) { case 'text-start': { // New: Initialize a text block with unique ID console.log(`Starting text block: ${chunk.id}`); break; } case 'text-delta': { // Changed: Now includes ID and uses 'delta' property process.stdout.write(chunk.delta); // Changed from 'textDelta' break; } case 'text-end': { // New: Finalize the text block console.log(`Completed text block: ${chunk.id}`); break; } } } ``` #### Reasoning Streaming Pattern Reasoning content now follows the same start/delta/end pattern: ```tsx title="AI SDK 4.0" for await (const chunk of result.fullStream) { switch (chunk.type) { case 'reasoning': { // Single chunk with full reasoning text console.log('Reasoning:', chunk.text); break; } } } ``` ```tsx title="AI SDK 5.0" for await (const chunk of result.fullStream) { switch (chunk.type) { case 'reasoning-start': { console.log(`Starting reasoning block: ${chunk.id}`); break; } case 'reasoning-delta': { process.stdout.write(chunk.delta); break; } case 'reasoning-end': { console.log(`Completed reasoning block: ${chunk.id}`); break; } } } ``` #### Tool Input Streaming Tool inputs can now be streamed as they're being generated: ```tsx title="AI SDK 5.0" for await (const chunk of result.fullStream) { switch (chunk.type) { case 'tool-input-start': { console.log(`Starting tool input for ${chunk.toolName}: ${chunk.id}`); break; } case 'tool-input-delta': { // Stream the JSON input as it's being generated process.stdout.write(chunk.delta); break; } case 'tool-input-end': { console.log(`Completed tool input: ${chunk.id}`); break; } case 'tool-call': { // Final tool call with complete input console.log('Tool call:', chunk.toolName, chunk.input); break; } } } ``` #### onChunk Callback Changes The `onChunk` callback now receives the new streaming chunk types with IDs and the start/delta/end pattern. ```tsx title="AI SDK 4.0" const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a story', onChunk({ chunk }) { switch (chunk.type) { case 'text-delta': { // Single property with text content console.log('Text delta:', chunk.textDelta); break; } } }, }); ``` ```tsx title="AI SDK 5.0" const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a story', onChunk({ chunk }) { switch (chunk.type) { case 'text-delta': { // Text chunks now use single 'text' type console.log('Text chunk:', chunk.text); break; } case 'reasoning': { // Reasoning chunks use single 'reasoning' type console.log('Reasoning chunk:', chunk.text); break; } case 'source': { console.log('Source chunk:', chunk); break; } case 'tool-call': { console.log('Tool call:', chunk.toolName, chunk.input); break; } case 'tool-input-start': { console.log( `Tool input started for ${chunk.toolName}:`, chunk.toolCallId, ); break; } case 'tool-input-delta': { console.log(`Tool input delta for ${chunk.toolCallId}:`, chunk.delta); break; } case 'tool-result': { console.log('Tool result:', chunk.output); break; } case 'raw': { console.log('Raw chunk:', chunk); break; } } }, }); ``` #### File Stream Parts Restructure File parts in streams have been flattened. ```tsx title="AI SDK 4.0" for await (const chunk of result.fullStream) { switch (chunk.type) { case 'file': { console.log('Media type:', chunk.file.mediaType); console.log('File data:', chunk.file.data); break; } } } ``` ```tsx title="AI SDK 5.0" for await (const chunk of result.fullStream) { switch (chunk.type) { case 'file': { console.log('Media type:', chunk.mediaType); console.log('File data:', chunk.data); break; } } } ``` #### Source Stream Parts Restructure Source stream parts have been flattened. ```tsx title="AI SDK 4.0" for await (const part of result.fullStream) { if (part.type === 'source' && part.source.sourceType === 'url') { console.log('ID:', part.source.id); console.log('Title:', part.source.title); console.log('URL:', part.source.url); } } ``` ```tsx title="AI SDK 5.0" for await (const part of result.fullStream) { if (part.type === 'source' && part.sourceType === 'url') { console.log('ID:', part.id); console.log('Title:', part.title); console.log('URL:', part.url); } } ``` #### Finish Event Changes Stream finish events have been renamed for consistency. ```tsx title="AI SDK 4.0" for await (const part of result.fullStream) { switch (part.type) { case 'step-finish': { console.log('Step finished:', part.finishReason); break; } case 'finish': { console.log('Usage:', part.usage); break; } } } ``` ```tsx title="AI SDK 5.0" for await (const part of result.fullStream) { switch (part.type) { case 'finish-step': { // Renamed from 'step-finish' console.log('Step finished:', part.finishReason); break; } case 'finish': { console.log('Total Usage:', part.totalUsage); // Changed from 'usage' break; } } } ``` ### Stream Protocol Changes #### Proprietary Protocol -> Server-Sent Events The data stream protocol has been updated to use Server-Sent Events. ```tsx title="AI SDK 4.0" import { createDataStream, formatDataStreamPart } from 'ai'; const dataStream = createDataStream({ execute: writer => { writer.writeData('initialized call'); writer.write(formatDataStreamPart('text', 'Hello')); writer.writeSource({ type: 'source', sourceType: 'url', id: 'source-1', url: 'https://example.com', title: 'Example Source', }); }, }); ``` ```tsx title="AI SDK 5.0" import { createUIMessageStream } from 'ai'; const stream = createUIMessageStream({ execute: ({ writer }) => { writer.write({ type: 'data', value: ['initialized call'] }); writer.write({ type: 'text', value: 'Hello' }); writer.write({ type: 'source-url', value: { type: 'source', id: 'source-1', url: 'https://example.com', title: 'Example Source', }, }); }, }); ``` #### Data Stream Response Helper Functions Renamed The streaming API has been completely restructured from data streams to UI message streams. ```tsx title="AI SDK 4.0" // Express/Node.js servers app.post('/stream', async (req, res) => { const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Generate content', }); result.pipeDataStreamToResponse(res); }); // Next.js API routes const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Generate content', }); return result.toDataStreamResponse(); ``` ```tsx title="AI SDK 5.0" // Express/Node.js servers app.post('/stream', async (req, res) => { const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Generate content', }); result.pipeUIMessageStreamToResponse(res); }); // Next.js API routes const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Generate content', }); return result.toUIMessageStreamResponse(); ``` #### Stream Transform Function Renaming Various stream-related functions have been renamed for consistency. ```tsx title="AI SDK 4.0" import { DataStreamToSSETransformStream } from 'ai'; ``` ```tsx title="AI SDK 5.0" import { JsonToSseTransformStream } from 'ai'; ``` #### Error Handling: getErrorMessage → onError The `getErrorMessage` option in `toDataStreamResponse` has been replaced with `onError` in `toUIMessageStreamResponse`, providing more control over error forwarding to the client. By default, error messages are NOT sent to the client to prevent leaking sensitive information. The `onError` callback allows you to explicitly control what error information is forwarded to the client. ```tsx title="AI SDK 4.0" return result.toDataStreamResponse({ getErrorMessage: error => { // Return sanitized error data to send to client // Only return what you want the client to see! return { errorCode: 'STREAM_ERROR', message: 'An error occurred while processing your request', // In production, avoid sending error.message directly to prevent information leakage }; }, }); ``` ```tsx title="AI SDK 5.0" return result.toUIMessageStreamResponse({ onError: error => { // Return sanitized error data to send to client // Only return what you want the client to see! return { errorCode: 'STREAM_ERROR', message: 'An error occurred while processing your request', // In production, avoid sending error.message directly to prevent information leakage }; }, }); ``` ### Utility Changes #### ID Generation Changes The `createIdGenerator()` function now requires a `size` argument. ```tsx title="AI SDK 4.0" const generator = createIdGenerator({ prefix: 'msg' }); const id = generator(16); // Custom size at call time ``` ```tsx title="AI SDK 5.0" const generator = createIdGenerator({ prefix: 'msg', size: 16 }); const id = generator(); // Fixed size from creation ``` #### IDGenerator → IdGenerator The type name has been updated. ```tsx title="AI SDK 4.0" import { IDGenerator } from 'ai'; ``` ```tsx title="AI SDK 5.0" import { IdGenerator } from 'ai'; ``` ### Provider Interface Changes #### Language Model V2 Import `LanguageModelV3` must now be imported from `@ai-sdk/provider`. ```tsx title="AI SDK 4.0" import { LanguageModelV3 } from 'ai'; ``` ```tsx title="AI SDK 5.0" import { LanguageModelV3 } from '@ai-sdk/provider'; ``` #### Middleware Rename `LanguageModelV1Middleware` has been renamed and moved. ```tsx title="AI SDK 4.0" import { LanguageModelV1Middleware } from 'ai'; ``` ```tsx title="AI SDK 5.0" import { LanguageModelV3Middleware } from '@ai-sdk/provider'; ``` #### Usage Token Properties Token usage properties have been renamed for consistency. ```tsx title="AI SDK 4.0" // In language model implementations { usage: { promptTokens: 10, completionTokens: 20 } } ``` ```tsx title="AI SDK 5.0" // In language model implementations { usage: { inputTokens: 10, outputTokens: 20, totalTokens: 30 // Now required } } ``` #### Stream Part Type Changes The `LanguageModelV3StreamPart` type has been expanded to support the new streaming architecture with start/delta/end patterns and IDs. ```tsx title="AI SDK 4.0" // V4: Simple stream parts type LanguageModelV3StreamPart = | { type: 'text-delta'; textDelta: string } | { type: 'reasoning'; text: string } | { type: 'tool-call'; toolCallId: string; toolName: string; input: string }; ``` ```tsx title="AI SDK 5.0" // V5: Enhanced stream parts with IDs and lifecycle events type LanguageModelV3StreamPart = // Text blocks with start/delta/end pattern | { type: 'text-start'; id: string; providerMetadata?: SharedV2ProviderMetadata; } | { type: 'text-delta'; id: string; delta: string; providerMetadata?: SharedV2ProviderMetadata; } | { type: 'text-end'; id: string; providerMetadata?: SharedV2ProviderMetadata; } // Reasoning blocks with start/delta/end pattern | { type: 'reasoning-start'; id: string; providerMetadata?: SharedV2ProviderMetadata; } | { type: 'reasoning-delta'; id: string; delta: string; providerMetadata?: SharedV2ProviderMetadata; } | { type: 'reasoning-end'; id: string; providerMetadata?: SharedV2ProviderMetadata; } // Tool input streaming | { type: 'tool-input-start'; id: string; toolName: string; providerMetadata?: SharedV2ProviderMetadata; } | { type: 'tool-input-delta'; id: string; delta: string; providerMetadata?: SharedV2ProviderMetadata; } | { type: 'tool-input-end'; id: string; providerMetadata?: SharedV2ProviderMetadata; } // Enhanced tool calls | { type: 'tool-call'; toolCallId: string; toolName: string; input: string; providerMetadata?: SharedV2ProviderMetadata; } // Stream lifecycle events | { type: 'stream-start'; warnings: Array } | { type: 'finish'; usage: LanguageModelV3Usage; finishReason: LanguageModelV3FinishReason; providerMetadata?: SharedV2ProviderMetadata; }; ``` #### Raw Response → Response Provider response objects have been updated. ```tsx title="AI SDK 4.0" // In language model implementations { rawResponse: { /* ... */ } } ``` ```tsx title="AI SDK 5.0" // In language model implementations { response: { /* ... */ } } ``` #### `wrapLanguageModel` now stable ```tsx title="AI SDK 4.0" import { experimental_wrapLanguageModel } from 'ai'; ``` ```tsx title="AI SDK 5.0" import { wrapLanguageModel } from 'ai'; ``` #### `activeTools` No Longer Experimental ```tsx title="AI SDK 4.0" const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages, tools: { weatherTool, locationTool }, experimental_activeTools: ['weatherTool'], }); ``` ```tsx title="AI SDK 5.0" const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages, tools: { weatherTool, locationTool }, activeTools: ['weatherTool'], // No longer experimental }); ``` #### `prepareStep` No Longer Experimental The `experimental_prepareStep` option has been promoted and no longer requires the experimental prefix. ```tsx title="AI SDK 4.0" const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages, tools: { weatherTool, locationTool }, experimental_prepareStep: ({ steps, stepNumber, model }) => { console.log('Preparing step:', stepNumber); return { activeTools: ['weatherTool'], system: 'Be helpful and concise.', }; }, }); ``` ```tsx title="AI SDK 5.0" const result = await generateText({ model: "anthropic/claude-sonnet-5.5", messages, tools: { weatherTool, locationTool }, prepareStep: ({ steps, stepNumber, model }) => { console.log('Preparing step:', stepNumber); return { activeTools: ['weatherTool'], system: 'Be helpful and concise.', // Can also configure toolChoice, model, etc. }; }, }); ``` The `prepareStep` function receives `{ steps, stepNumber, model }` and can return: - `model`: Different model for this step - `activeTools`: Which tools to make available - `toolChoice`: Tool selection strategy - `system`: System message for this step - `undefined`: Use default settings ### Temperature Default Removal Temperature is no longer set to `0` by default. ```tsx title="AI SDK 4.0" await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a creative story', // Implicitly temperature: 0 }); ``` ```tsx title="AI SDK 5.0" await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a creative story', temperature: 0, // Must explicitly set }); ``` ## Message Persistence Changes If you have persisted messages in a database, see the [Data Migration Guide](/docs/migration-guides/migration-guide-5-0-data) for comprehensive guidance on migrating your stored message data to the v5 format. In v4, you would typically use helper functions like `appendResponseMessages` or `appendClientMessage` to format messages in the `onFinish` callback of `streamText`: ```tsx title="AI SDK 4.0" import { streamText, convertToModelMessages, appendClientMessage, appendResponseMessages, } from 'ai'; const updatedMessages = appendClientMessage({ messages, message: lastUserMessage, }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: updatedMessages, experimental_generateMessageId: () => generateId(), // ID generation on streamText onFinish: async ({ responseMessages, usage }) => { // Use helper functions to format messages const finalMessages = appendResponseMessages({ messages: updatedMessages, responseMessages, }); // Save formatted messages to database await saveMessages(finalMessages); }, }); ``` In v5, message persistence is now handled through the `toUIMessageStreamResponse` method, which automatically formats response messages in the `UIMessage` format: ```tsx title="AI SDK 5.0" import { streamText, convertToModelMessages, UIMessage } from 'ai'; const messages: UIMessage[] = [ // Your existing messages in UIMessage format ]; const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: convertToModelMessages(messages), // experimental_generateMessageId removed from here }); return result.toUIMessageStreamResponse({ originalMessages: messages, // IMPORTANT: Required to prevent duplicate messages generateMessageId: () => generateId(), // IMPORTANT: Required for proper message ID generation onFinish: ({ messages, responseMessage }) => { // messages contains all messages (original + response) in UIMessage format saveChat({ chatId, messages }); // responseMessage contains just the generated message in UIMessage format saveMessage({ chatId, message: responseMessage }); }, }); ``` **Important:** When using `toUIMessageStreamResponse`, you should always provide both `originalMessages` and `generateMessageId` parameters. Without these, you may experience duplicate or repeated assistant messages in your UI. For more details, see [Troubleshooting: Repeated Assistant Messages](/docs/troubleshooting/repeated-assistant-messages). ### Message ID Generation The `experimental_generateMessageId` option has been moved from `streamText` configuration to `toUIMessageStreamResponse`, as it's designed for use with `UIMessage`s rather than `ModelMessage`s. ```tsx title="AI SDK 4.0" const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages, experimental_generateMessageId: () => generateId(), }); ``` ```tsx title="AI SDK 5.0" const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: convertToModelMessages(messages), }); return result.toUIMessageStreamResponse({ generateMessageId: () => generateId(), // No longer experimental // ... }); ``` For more details on message IDs and persistence, see the [Chatbot Message Persistence guide](/docs/ai-sdk-ui/chatbot-message-persistence#message-ids). ### Using createUIMessageStream For more complex scenarios, especially when working with data parts, you can use `createUIMessageStream`: ```tsx title="AI SDK 5.0 - Advanced" import { createUIMessageStream, createUIMessageStreamResponse, streamText, convertToModelMessages, UIMessage, } from 'ai'; const stream = createUIMessageStream({ originalMessages: messages, generateId: generateId, // Required for proper message ID generation execute: ({ writer }) => { // Write custom data parts writer.write({ type: 'data', data: { status: 'processing', timestamp: Date.now() }, }); // Stream the AI response const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: convertToModelMessages(messages), }); writer.merge(result.toUIMessageStream()); }, onFinish: ({ messages }) => { // messages contains all messages (original + response + data parts) in UIMessage format saveChat({ chatId, messages }); }, }); return createUIMessageStreamResponse({ stream }); ``` ## Provider & Model Changes ### OpenAI #### Default Provider Instance Uses Responses API In AI SDK 5, the default OpenAI provider instance uses the Responses API, while AI SDK 4 used the Chat Completions API. The Chat Completions API remains fully supported and you can use it with `openai.chat(...)`. ```tsx title="AI SDK 4.0" import { openai } from '@ai-sdk/openai'; const defaultModel = openai('gpt-4.1-mini'); // Chat Completions API ``` ```tsx title="AI SDK 5.0" import { openai } from '@ai-sdk/openai'; const defaultModel = openai('gpt-4.1-mini'); // Responses API // Specify a specific API when needed: const chatCompletionsModel = openai.chat('gpt-4.1-mini'); const responsesModel = openai.responses('gpt-4.1-mini'); ``` The Responses and Chat Completions APIs have different behavior and defaults. If you depend on the Chat Completions API, switch your model instance to `openai.chat(...)` and audit your configuration. #### Strict Schemas (`strictSchemas`) with Responses API In AI SDK 4.0, you could set the `strictSchemas` option on Responses models (which defaulted to `true`). This option has been renamed to `strictJsonSchema` in AI SDK 5.0 and now defaults to `false`. ```tsx title="AI SDK 4.0" import { z } from 'zod'; import { generateObject } from 'ai'; import { openai, type OpenAIResponsesProviderOptions } from '@ai-sdk/openai'; const result = await generateObject({ model: openai.responses('gpt-4.1'), schema: z.object({ // ... }), providerOptions: { openai: { strictSchemas: true, // default behavior in AI SDK 4 } satisfies OpenAIResponsesProviderOptions, }, }); ``` ```tsx title="AI SDK 5.0" import { z } from 'zod'; import { generateObject } from 'ai'; import { openai, type OpenAIResponsesProviderOptions } from '@ai-sdk/openai'; const result = await generateObject({ model: openai('gpt-4.1-2024'), // uses Responses API schema: z.object({ // ... }), providerOptions: { openai: { strictJsonSchema: true, // defaults to false, opt back in to the AI SDK 4 strict behavior } satisfies OpenAIResponsesProviderOptions, }, }); ``` If you call `openai.chat(...)` to use the Chat Completions API directly, you can type it with `OpenAIChatLanguageModelOptions`. AI SDK 5 adds the same `strictJsonSchema` option there as well. #### Structured Outputs The `structuredOutputs` option is now configured using provider options rather than as a setting on the model instance. ```tsx title="AI SDK 4.0" import { z } from 'zod'; import { generateObject } from 'ai'; import { openai } from '@ai-sdk/openai'; const result = await generateObject({ model: openai('gpt-4.1', { structuredOutputs: true }), // use Chat Completions API schema: z.object({ name: z.string() }), }); ``` ```tsx title="AI SDK 5.0 (Chat Completions API)" import { z } from 'zod'; import { generateObject } from 'ai'; import { openai, type OpenAIChatLanguageModelOptions } from '@ai-sdk/openai'; const result = await generateObject({ model: openai.chat('gpt-4.1'), // use Chat Completions API schema: z.object({ name: z.string() }), providerOptions: { openai: { structuredOutputs: true, } satisfies OpenAIChatLanguageModelOptions, }, }); ``` #### Compatibility Option Removal The `compatibility` option has been removed; strict compatibility mode is now the default. ```tsx title="AI SDK 4.0" const openai = createOpenAI({ compatibility: 'strict', }); ``` ```tsx title="AI SDK 5.0" const openai = createOpenAI({ // strict compatibility is now the default }); ``` #### Legacy Function Calls Removal The `useLegacyFunctionCalls` option has been removed. ```tsx title="AI SDK 4.0" const result = streamText({ model: openai('gpt-4.1', { useLegacyFunctionCalls: true }), }); ``` ```tsx title="AI SDK 5.0" const result = streamText({ model: openai('gpt-4.1'), }); ``` #### Simulate Streaming The `simulateStreaming` model option has been replaced with middleware. ```tsx title="AI SDK 4.0" const result = generateText({ model: openai('gpt-4.1', { simulateStreaming: true }), prompt: 'Hello, world!', }); ``` ```tsx title="AI SDK 5.0" import { simulateStreamingMiddleware, wrapLanguageModel } from 'ai'; const model = wrapLanguageModel({ model: openai('gpt-4.1'), middleware: simulateStreamingMiddleware(), }); const result = generateText({ model, prompt: 'Hello, world!', }); ``` ### Google #### Search Grounding is now a provider defined tool Search Grounding is now called "Google Search" and is now a provider defined tool. ```tsx title="AI SDK 4.0" const { text, providerMetadata } = await generateText({ model: google('gemini-1.5-pro', { useSearchGrounding: true, }), prompt: 'List the top 5 San Francisco news from the past week.', }); ``` ```tsx title="AI SDK 5.0" import { google } from '@ai-sdk/google'; const { text, sources, providerMetadata } = await generateText({ model: google('gemini-1.5-pro'), prompt: 'List the top 5 San Francisco news from the past week.' tools: { google_search: google.tools.googleSearch({}), }, }); ``` ### Amazon Bedrock #### Snake Case → Camel Case Provider options have been updated to use camelCase. ```tsx title="AI SDK 4.0" const result = await generateText({ model: bedrock('amazon.titan-tg1-large'), prompt: 'Hello, world!', providerOptions: { bedrock: { reasoning_config: { /* ... */ }, }, }, }); ``` ```tsx title="AI SDK 5.0" const result = await generateText({ model: bedrock('amazon.titan-tg1-large'), prompt: 'Hello, world!', providerOptions: { bedrock: { reasoningConfig: { /* ... */ }, }, }, }); ``` ### Provider-Utils Changes Deprecated `CoreTool*` types have been removed. ```tsx title="AI SDK 4.0" import { CoreToolCall, CoreToolResult, CoreToolResultUnion, CoreToolCallUnion, CoreToolChoice, } from '@ai-sdk/provider-utils'; ``` ```tsx title="AI SDK 5.0" import { ToolCall, ToolResult, TypedToolResult, TypedToolCall, ToolChoice, } from '@ai-sdk/provider-utils'; ``` ## Troubleshooting ### TypeScript Performance Issues with Zod If you experience TypeScript server crashes, slow type checking, or errors like "Type instantiation is excessively deep and possibly infinite" when using Zod with AI SDK 5.0: 1. **First, ensure you're using Zod 4.1.8 or later** - this version includes a fix for module resolution issues that cause TypeScript performance problems. 2. If the issue persists, update your `tsconfig.json` to use `moduleResolution: "nodenext"`: ```json { "compilerOptions": { "moduleResolution": "nodenext" // ... other options } } ``` This resolves the TypeScript performance issues while allowing you to continue using the standard Zod import. If this doesn't resolve the issue, you can try using a version-specific import path as an alternative solution. For detailed troubleshooting steps, see [TypeScript performance issues with Zod](/docs/troubleshooting/typescript-performance-zod). ## Codemod Table The following table lists available codemods for the AI SDK 5.0 upgrade process. For more information, see the [Codemods](#codemods) section. | Change | Codemod | | ------------------------------------------------ | ----------------------------------------------------- | | **AI SDK Core Changes** | | | Flatten streamText file properties | `v5/flatten-streamtext-file-properties` | | ID Generation Changes | `v5/require-createIdGenerator-size-argument` | | IDGenerator → IdGenerator | `v5/rename-IDGenerator-to-IdGenerator` | | Import LanguageModelV3 from provider package | `v5/import-LanguageModelV3-from-provider-package` | | Migrate to data stream protocol v2 | `v5/migrate-to-data-stream-protocol-v2` | | Move image model maxImagesPerCall | `v5/move-image-model-maxImagesPerCall` | | Move LangChain adapter | `v5/move-langchain-adapter` | | Move maxSteps to stopWhen | `v5/move-maxsteps-to-stopwhen` | | Move provider options | `v5/move-provider-options` | | Move React to AI SDK | `v5/move-react-to-ai-sdk` | | Move UI utils to AI | `v5/move-ui-utils-to-ai` | | Remove experimental wrap language model | `v5/remove-experimental-wrap-language-model` | | Remove experimental activeTools | `v5/remove-experimental-activetools` | | Remove experimental prepareStep | `v5/remove-experimental-preparestep` | | Remove experimental continueSteps | `v5/remove-experimental-continuesteps` | | Remove experimental temperature | `v5/remove-experimental-temperature` | | Remove experimental truncate | `v5/remove-experimental-truncate` | | Remove experimental OpenAI compatibility | `v5/remove-experimental-openai-compatibility` | | Remove experimental OpenAI legacy function calls | `v5/remove-experimental-openai-legacy-function-calls` | | Remove experimental OpenAI structured outputs | `v5/remove-experimental-openai-structured-outputs` | | Remove experimental OpenAI store | `v5/remove-experimental-openai-store` | | Remove experimental OpenAI user | `v5/remove-experimental-openai-user` | | Remove experimental OpenAI parallel tool calls | `v5/remove-experimental-openai-parallel-tool-calls` | | Remove experimental OpenAI response format | `v5/remove-experimental-openai-response-format` | | Remove experimental OpenAI logit bias | `v5/remove-experimental-openai-logit-bias` | | Remove experimental OpenAI logprobs | `v5/remove-experimental-openai-logprobs` | | Remove experimental OpenAI seed | `v5/remove-experimental-openai-seed` | | Remove experimental OpenAI service tier | `v5/remove-experimental-openai-service-tier` | | Remove experimental OpenAI top logprobs | `v5/remove-experimental-openai-top-logprobs` | | Remove experimental OpenAI transform | `v5/remove-experimental-openai-transform` | | Remove experimental OpenAI stream options | `v5/remove-experimental-openai-stream-options` | | Remove experimental OpenAI prediction | `v5/remove-experimental-openai-prediction` | | Remove experimental Anthropic caching | `v5/remove-experimental-anthropic-caching` | | Remove experimental Anthropic computer use | `v5/remove-experimental-anthropic-computer-use` | | Remove experimental Anthropic PDF support | `v5/remove-experimental-anthropic-pdf-support` | | Remove experimental Anthropic prompt caching | `v5/remove-experimental-anthropic-prompt-caching` | | Remove experimental Google search grounding | `v5/remove-experimental-google-search-grounding` | | Remove experimental Google code execution | `v5/remove-experimental-google-code-execution` | | Remove experimental Google cached content | `v5/remove-experimental-google-cached-content` | | Remove experimental Google custom headers | `v5/remove-experimental-google-custom-headers` | | Rename format stream part | `v5/rename-format-stream-part` | | Rename parse stream part | `v5/rename-parse-stream-part` | | Replace image type with file type | `v5/replace-image-type-with-file-type` | | Replace LlamaIndex adapter | `v5/replace-llamaindex-adapter` | | Replace onCompletion with onFinal | `v5/replace-oncompletion-with-onfinal` | | Replace provider metadata with provider options | `v5/replace-provider-metadata-with-provider-options` | | Replace rawResponse with response | `v5/replace-rawresponse-with-response` | | Replace redacted reasoning type | `v5/replace-redacted-reasoning-type` | | Replace simulate streaming | `v5/replace-simulate-streaming` | | Replace textDelta with text | `v5/replace-textdelta-with-text` | | Replace usage token properties | `v5/replace-usage-token-properties` | | Restructure file stream parts | `v5/restructure-file-stream-parts` | | Restructure source stream parts | `v5/restructure-source-stream-parts` | | RSC package | `v5/rsc-package` | ## Changes Between v5 Beta Versions This section documents breaking changes between different beta versions of AI SDK 5.0. If you're upgrading from an earlier v5 beta version to a later one, check this section for any changes that might affect your code. ### fullStream Type Rename: text/reasoning → text-delta/reasoning-delta The chunk types in `fullStream` have been renamed for consistency with UI streams and language model streams. ```tsx title="AI SDK 5.0 (before beta.26)" for await (const chunk of result.fullStream) { switch (chunk.type) { case 'text-delta': { process.stdout.write(chunk.text); break; } case 'reasoning': { console.log('Reasoning:', chunk.text); break; } } } ``` ```tsx title="AI SDK 5.0 (beta.26 and later)" for await (const chunk of result.fullStream) { switch (chunk.type) { case 'text-delta': { process.stdout.write(chunk.text); break; } case 'reasoning-delta': { console.log('Reasoning:', chunk.text); break; } } } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrate AI SDK 5.x to 6.0 description: Learn how to upgrade AI SDK 5.x to 6.0. url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-6-0" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Recommended Migration Process 1. Backup your project. If you use a versioning control system, make sure all previous versions are committed. 2. Upgrade to AI SDK 6.0. 3. Follow the breaking changes guide below. 4. Verify your project is working as expected. 5. Commit your changes. ## AI SDK 6.0 Package Versions You need to update the following packages to the latest versions in your `package.json` file(s): - `ai` package: `^6.0.0` - `@ai-sdk/provider` package: `^3.0.0` - `@ai-sdk/provider-utils` package: `^4.0.0` - `@ai-sdk/*` packages: `^3.0.0` An example upgrade command would be: ``` pnpm install ai@latest @ai-sdk/react@latest @ai-sdk/openai@latest ``` ## Codemods 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 v6 codemods (v5 → v6 migration) by running the following command from the root of your project: ```sh npx @ai-sdk/codemod v6 ``` There is also an `npx @ai-sdk/codemod upgrade` command, but it runs all codemods from all versions (v4, v5, and v6). Use `v6` when upgrading from v5. Individual codemods can be run by specifying the name of the codemod: ```sh npx @ai-sdk/codemod ``` For example, to run a specific v6 codemod: ```sh npx @ai-sdk/codemod v6/rename-text-embedding-to-embedding 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 | | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `rename-text-embedding-to-embedding` | Renames `textEmbeddingModel` to `embeddingModel` and `textEmbedding` to `embedding` on providers | | `rename-mock-v2-to-v3` | Renames V2 mock classes from `ai/test` to V3 (e.g., `MockLanguageModelV2` → `MockLanguageModelV3`) | | `rename-tool-call-options-to-tool-execution-options` | Renames the `ToolCallOptions` type to `ToolExecutionOptions` | | `rename-core-message-to-model-message` | Renames the `CoreMessage` type to `ModelMessage` | | `rename-converttocoremessages-to-converttomodelmessages` | Renames `convertToCoreMessages` function to `convertToModelMessages` | | `rename-vertex-provider-metadata-key` | Renames `google` to `vertex` in `providerMetadata` and `providerOptions` for Google Vertex files | | `wrap-tomodeloutput-parameter` | Wraps `toModelOutput` parameter in object destructuring (`output` → `{ output }`) | | `add-await-converttomodelmessages` | Adds `await` to `convertToModelMessages` calls (now async in AI SDK 6) | ## AI SDK Core ### `Experimental_Agent` to `ToolLoopAgent` Class The `Experimental_Agent` class has been replaced with the `ToolLoopAgent` class. Two key changes: 1. The `system` parameter has been renamed to `instructions` 2. The default `stopWhen` has changed from `isStepCount(1)` to `isStepCount(20)` ```tsx title="AI SDK 5" import { Experimental_Agent as Agent, isStepCount } from 'ai'; const agent = new Agent({ model: "anthropic/claude-sonnet-5.5", system: 'You are a helpful assistant.', tools: { // your tools here }, stopWhen: isStepCount(20), // Required for multi-step agent loops }); const result = await agent.generate({ prompt: 'What is the weather in San Francisco?', }); ``` ```tsx title="AI SDK 6" import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a helpful assistant.', tools: { // your tools here }, // stopWhen defaults to isStepCount(20) }); const result = await agent.generate({ prompt: 'What is the weather in San Francisco?', }); ``` Learn more about [building agents](/docs/agents/building-agents). ### `CoreMessage` Removal The deprecated `CoreMessage` type and related functions have been removed ([PR #10710](https://github.com/vercel/ai/pull/10710)). Replace `convertToCoreMessages` with `convertToModelMessages`. ```tsx title="AI SDK 5" import { convertToCoreMessages, type CoreMessage } from 'ai'; const coreMessages = convertToCoreMessages(messages); // CoreMessage[] ``` ```tsx title="AI SDK 6" import { convertToModelMessages, type ModelMessage } from 'ai'; const modelMessages = await convertToModelMessages(messages); // ModelMessage[] ``` Use the `rename-core-message-to-model-message` and `rename-converttocoremessages-to-converttomodelmessages` codemods to automatically update your codebase. ### `generateObject` and `streamObject` Deprecation `generateObject` and `streamObject` have been deprecated ([PR #10754](https://github.com/vercel/ai/pull/10754)). They will be removed in a future version. Use `generateText` and `streamText` with an `output` setting instead. ```tsx title="AI SDK 5" import { generateObject } from 'ai'; import { z } from 'zod'; const { object } = await generateObject({ model: "anthropic/claude-sonnet-5.5", schema: z.object({ recipe: z.object({ name: z.string(), ingredients: z.array(z.object({ name: z.string(), amount: z.string() })), steps: z.array(z.string()), }), }), prompt: 'Generate a lasagna recipe.', }); ``` ```tsx title="AI SDK 6" import { generateText, Output } from 'ai'; import { z } from 'zod'; const { output } = await generateText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ recipe: z.object({ name: z.string(), ingredients: z.array( z.object({ name: z.string(), amount: z.string() }), ), steps: z.array(z.string()), }), }), }), prompt: 'Generate a lasagna recipe.', }); ``` For streaming structured data, replace `streamObject` with `streamText`: ```tsx title="AI SDK 5" import { streamObject } from 'ai'; import { z } from 'zod'; const { partialObjectStream } = streamObject({ model: "anthropic/claude-sonnet-5.5", schema: z.object({ recipe: z.object({ name: z.string(), ingredients: z.array(z.object({ name: z.string(), amount: z.string() })), steps: z.array(z.string()), }), }), prompt: 'Generate a lasagna recipe.', }); for await (const partialObject of partialObjectStream) { console.log(partialObject); } ``` ```tsx title="AI SDK 6" import { streamText, Output } from 'ai'; import { z } from 'zod'; const { partialOutputStream } = streamText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ recipe: z.object({ name: z.string(), ingredients: z.array( z.object({ name: z.string(), amount: z.string() }), ), steps: z.array(z.string()), }), }), }), prompt: 'Generate a lasagna recipe.', }); for await (const partialObject of partialOutputStream) { console.log(partialObject); } ``` Learn more about [generating structured data](/docs/ai-sdk-core/generating-structured-data). ### async `convertToModelMessages` `convertToModelMessages()` is async in AI SDK 6 to support async `Tool.toModelOutput()`. ```tsx title="AI SDK 5" import { convertToModelMessages } from 'ai'; const modelMessages = convertToModelMessages(uiMessages); ``` ```tsx title="AI SDK 6" import { convertToModelMessages } from 'ai'; const modelMessages = await convertToModelMessages(uiMessages); ``` Use the `add-await-converttomodelmessages` codemod to automatically update your codebase. ### `Tool.toModelOutput` changes `toModelOutput()` receives a parameter object with an `output` property in AI SDK 6. In AI SDK 5, the `output` was the arguments. ```tsx title="AI SDK 5" import { tool } from 'ai'; const someTool = tool({ // ... toModelOutput: output => { // ... }, }); ``` ```tsx title="AI SDK 6" import { tool } from 'ai'; const someTool = tool({ // ... toModelOutput: ({ output }) => { // ... }, }); ``` Use the `wrap-tomodeloutput-parameter` codemod to automatically update your codebase. ### Remove `name` from Function Tool Definitions Function tool names come from their keys in the `tools` object. In AI SDK 5, a `name` property could pass type checking because it was part of the provider-defined member of the `Tool` type, but it did not set the name of a function tool. The AI SDK 6 types no longer accept the property in a function tool definition. Remove `name` from `tool()` and use the intended name as the key in the `tools` object: ```tsx title="AI SDK 5" import { tool } from 'ai'; import { z } from 'zod'; const tools = { getWeather: tool({ name: 'getWeather', inputSchema: z.object({}), outputSchema: z.any(), }), }; ``` ```tsx title="AI SDK 6" import { tool } from 'ai'; import { z } from 'zod'; const tools = { getWeather: tool({ inputSchema: z.object({}), outputSchema: z.any(), }), }; ``` ### `cachedInputTokens` and `reasoningTokens` in `LanguageModelUsage` Deprecation `cachedInputTokens` and `reasoningTokens` in `LanguageModelUsage` have been deprecated. You can replace `cachedInputTokens` with `inputTokenDetails.cacheReadTokens` and `reasoningTokens` with `outputTokenDetails.reasoningTokens`. ### `ToolCallOptions` to `ToolExecutionOptions` Rename The `ToolCallOptions` type has been renamed to `ToolExecutionOptions` and is now deprecated. Use the `rename-tool-call-options-to-tool-execution-options` codemod to automatically update your codebase. ### Per-Tool Strict Mode Strict mode for tools is now controlled by setting `strict` on each tool ([PR #10817](https://github.com/vercel/ai/pull/10817)). This enables fine-grained control over strict tool calls, which is important since strict mode depends on the specific tool input schema. ```tsx title="AI SDK 5" import { streamText, tool } from 'ai'; import { z } from 'zod'; // Tool strict mode was controlled by strictJsonSchema const result = streamText({ model: "anthropic/claude-sonnet-5.5", tools: { calculator: tool({ description: 'A simple calculator', inputSchema: z.object({ expression: z.string(), }), execute: async ({ expression }) => { const result = eval(expression); return { result }; }, }), }, providerOptions: { openai: { strictJsonSchema: true, // Applied to all tools }, }, }); ``` ```tsx title="AI SDK 6" import { streamText, tool } from 'ai'; import { z } from 'zod'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", tools: { calculator: tool({ description: 'A simple calculator', inputSchema: z.object({ expression: z.string(), }), execute: async ({ expression }) => { const result = eval(expression); return { result }; }, strict: true, // Control strict mode per tool }), }, }); ``` ### Flexible Tool Content AI SDK 6 introduces more flexible tool output and result content support ([PR #9605](https://github.com/vercel/ai/pull/9605)), enabling richer tool interactions and better support for complex tool execution patterns. ### `ToolCallRepairFunction` Signature The `system` parameter in the `ToolCallRepairFunction` type now accepts `SystemModelMessage` in addition to `string` ([PR #10635](https://github.com/vercel/ai/pull/10635)). This allows for more flexible system message configuration, including provider-specific options like caching. ```tsx title="AI SDK 5" import type { ToolCallRepairFunction } from 'ai'; const repairToolCall: ToolCallRepairFunction = async ({ system, // type: string | undefined messages, toolCall, tools, inputSchema, error, }) => { // ... }; ``` ```tsx title="AI SDK 6" import type { ToolCallRepairFunction, SystemModelMessage } from 'ai'; const repairToolCall: ToolCallRepairFunction = async ({ system, // type: string | SystemModelMessage | undefined messages, toolCall, tools, inputSchema, error, }) => { // Handle both string and SystemModelMessage const systemText = typeof system === 'string' ? system : system?.content; // ... }; ``` ### Embedding Model Method Rename The `textEmbeddingModel` and `textEmbedding` methods on providers have been renamed to `embeddingModel` and `embedding` respectively. Additionally, generics have been removed from `EmbeddingModel`, `embed`, and `embedMany` ([PR #10592](https://github.com/vercel/ai/pull/10592)). ```tsx title="AI SDK 5" import { openai } from '@ai-sdk/openai'; import { embed } from 'ai'; // Using the full method name const model = openai.textEmbeddingModel('text-embedding-3-small'); // Using the shorthand const model = openai.textEmbedding('text-embedding-3-small'); const { embedding } = await embed({ model: openai.textEmbedding('text-embedding-3-small'), value: 'sunny day at the beach', }); ``` ```tsx title="AI SDK 6" import { openai } from '@ai-sdk/openai'; import { embed } from 'ai'; // Using the full method name const model = openai.embeddingModel('text-embedding-3-small'); // Using the shorthand const model = openai.embedding('text-embedding-3-small'); const { embedding } = await embed({ model: openai.embedding('text-embedding-3-small'), value: 'sunny day at the beach', }); ``` Use the `rename-text-embedding-to-embedding` codemod to automatically update your codebase. ### Warning Logger AI SDK 6 introduces a warning logger that outputs deprecation warnings and best practice recommendations ([PR #8343](https://github.com/vercel/ai/pull/8343)). To disable warning logging, set the `AI_SDK_LOG_WARNINGS` environment variable to `false`: ```bash export AI_SDK_LOG_WARNINGS=false ``` ### Warning Type Unification Separate warning types for each generation function have been consolidated into a single `Warning` type exported from the `ai` package ([PR #10631](https://github.com/vercel/ai/pull/10631)). ```tsx title="AI SDK 5" // Separate warning types for each generation function import type { CallWarning, ImageModelCallWarning, SpeechWarning, TranscriptionWarning, } from 'ai'; ``` ```tsx title="AI SDK 6" // Single Warning type for all generation functions import type { Warning } from 'ai'; ``` ### Finish reason "unknown" merged into "other" The `unknown` finish reason has been removed. It is now returned as `other`. ## AI SDK UI ### Tool UI Part Approval States AI SDK 6 adds `approval-requested`, `approval-responded`, and `output-denied` to the tool UI part `state` union. Update exhaustive `switch` statements and other state handling to cover the three approval states. ```tsx title="AI SDK 6" switch (part.state) { case 'input-streaming': return 'Loading input'; case 'input-available': return 'Input ready'; case 'approval-requested': return 'Approval requested'; case 'approval-responded': return 'Approval response received'; case 'output-available': return 'Output ready'; case 'output-error': return part.errorText; case 'output-denied': return 'Tool call denied'; } ``` See [Tool execution approval](/docs/ai-sdk-ui/chatbot-tool-usage#tool-execution-approval) for handling approval requests and responses in a chat UI. ### Tool UI Part Helper Functions Rename The tool UI part helper functions have been renamed to better reflect their purpose and to accommodate both static and dynamic tool parts ([PR #XXXX](https://github.com/vercel/ai/pull/XXXX)). #### `isToolUIPart` → `isStaticToolUIPart` The `isToolUIPart` function has been renamed to `isStaticToolUIPart` to clarify that it checks for static tool parts only. ```tsx title="AI SDK 5" import { isToolUIPart } from 'ai'; // Check if a part is a tool UI part if (isToolUIPart(part)) { console.log(part.toolName); } ``` ```tsx title="AI SDK 6" import { isStaticToolUIPart } from 'ai'; // Check if a part is a static tool UI part if (isStaticToolUIPart(part)) { console.log(part.toolName); } ``` #### `isToolOrDynamicToolUIPart` → `isToolUIPart` The `isToolOrDynamicToolUIPart` function has been renamed to `isToolUIPart`. The old name is deprecated but still available. ```tsx title="AI SDK 5" import { isToolOrDynamicToolUIPart } from 'ai'; // Check if a part is either a static or dynamic tool UI part if (isToolOrDynamicToolUIPart(part)) { console.log('Tool part found'); } ``` ```tsx title="AI SDK 6" import { isToolUIPart } from 'ai'; // Check if a part is either a static or dynamic tool UI part if (isToolUIPart(part)) { console.log('Tool part found'); } ``` #### `getToolName` → `getStaticToolName` The `getToolName` function has been renamed to `getStaticToolName` to clarify that it returns the tool name from static tool parts only. ```tsx title="AI SDK 5" import { getToolName } from 'ai'; // Get the tool name from a tool part const name = getToolName(toolPart); ``` ```tsx title="AI SDK 6" import { getStaticToolName } from 'ai'; // Get the tool name from a static tool part const name = getStaticToolName(toolPart); ``` #### `getToolOrDynamicToolName` → `getToolName` The `getToolOrDynamicToolName` function has been renamed to `getToolName`. The old name is deprecated but still available. ```tsx title="AI SDK 5" import { getToolOrDynamicToolName } from 'ai'; // Get the tool name from either a static or dynamic tool part const name = getToolOrDynamicToolName(toolPart); ``` ```tsx title="AI SDK 6" import { getToolName } from 'ai'; // Get the tool name from either a static or dynamic tool part const name = getToolName(toolPart); ``` ## Providers ### OpenAI #### `strictJsonSchema` Defaults to True The `strictJsonSchema` setting for JSON outputs and tool calls is enabled by default ([PR #10752](https://github.com/vercel/ai/pull/10752)). This improves stability and ensures valid JSON output that matches your schema. However, strict mode is stricter about schema requirements. If you receive schema rejection errors, adjust your schema (for example, use `null` instead of `undefined`) or disable strict mode. ```tsx title="AI SDK 5" import { openai } from '@ai-sdk/openai'; import { generateObject } from 'ai'; import { z } from 'zod'; // strictJsonSchema was false by default const result = await generateObject({ model: openai('gpt-5.1'), schema: z.object({ name: z.string(), }), prompt: 'Generate a person', }); ``` ```tsx title="AI SDK 6" import { openai } from '@ai-sdk/openai'; import { generateObject } from 'ai'; import { z } from 'zod'; // strictJsonSchema is true by default const result = await generateObject({ model: openai('gpt-5.1'), schema: z.object({ name: z.string(), }), prompt: 'Generate a person', }); // Disable strict mode if needed const resultNoStrict = await generateObject({ model: openai('gpt-5.1'), schema: z.object({ name: z.string(), }), prompt: 'Generate a person', providerOptions: { openai: { strictJsonSchema: false, } satisfies OpenAIResponsesProviderOptions, }, }); ``` #### `structuredOutputs` Option Removed from Chat Model The `structuredOutputs` provider option has been removed from chat models ([PR #10752](https://github.com/vercel/ai/pull/10752)). Use `strictJsonSchema` instead. ### Azure #### Default Provider Uses Responses API The `@ai-sdk/azure` provider now uses the Responses API by default when calling `azure()` ([PR #9868](https://github.com/vercel/ai/pull/9868)). To use the previous Chat Completions API behavior, use `azure.chat()` instead. ```tsx title="AI SDK 5" import { azure } from '@ai-sdk/azure'; // Used Chat Completions API const model = azure('gpt-4o'); ``` ```tsx title="AI SDK 6" import { azure } from '@ai-sdk/azure'; // Now uses Responses API by default const model = azure('gpt-4o'); // Use azure.chat() for Chat Completions API const chatModel = azure.chat('gpt-4o'); // Use azure.responses() explicitly for Responses API const responsesModel = azure.responses('gpt-4o'); ``` The Responses and Chat Completions APIs have different behavior and defaults. If you depend on the Chat Completions API, switch your model instance to `azure.chat()` and audit your configuration. #### Responses API `providerMetadata` and `providerOptions` Key For the **Responses API**, the `@ai-sdk/azure` provider now uses `azure` as the key for `providerMetadata` and `providerOptions` instead of `openai`. The `openai` key is still supported for `providerOptions` input, but resulting `providerMetadata` output now uses `azure`. ```tsx title="AI SDK 5" import { azure } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = await generateText({ model: azure.responses('gpt-5-mini'), // use your own deployment prompt: 'Hello', providerOptions: { openai: { // AI SDK 5: use `openai` key for Responses API options reasoningSummary: 'auto', }, }, }); // Accessed metadata via 'openai' key console.log(result.providerMetadata?.openai?.responseId); ``` ```tsx title="AI SDK 6" import { azure } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = await generateText({ // azure() now uses the Responses API by default model: azure('gpt-5-mini'), // use your own deployment prompt: 'Hello', providerOptions: { azure: { // AI SDK 6: use `azure` key for Responses API options reasoningSummary: 'auto', }, }, }); // Access metadata via 'azure' key console.log(result.providerMetadata?.azure?.responseId); ``` ### Anthropic #### Structured Outputs Mode Anthropic has [ introduced native structured outputs for Claude Sonnet 4.5 and later models ](https://www.claude.com/blog/structured-outputs-on-the-claude-developer-platform). The `@ai-sdk/anthropic` provider now includes a `structuredOutputMode` option to control how structured outputs are generated ([PR #10502](https://github.com/vercel/ai/pull/10502)). The available modes are: - `'outputFormat'`: Use Anthropic's native `output_format` parameter - `'jsonTool'`: Use a special JSON tool to specify the structured output format - `'auto'` (default): Use `'outputFormat'` when supported by the model, otherwise fall back to `'jsonTool'` ```tsx title="AI SDK 6" import { anthropic } from '@ai-sdk/anthropic'; import { generateObject } from 'ai'; import { z } from 'zod'; const result = await generateObject({ model: anthropic('claude-sonnet-4-5-20250929'), schema: z.object({ name: z.string(), age: z.number(), }), prompt: 'Generate a person', providerOptions: { anthropic: { // Explicitly set the structured output mode (optional) structuredOutputMode: 'outputFormat', } satisfies AnthropicProviderOptions, }, }); ``` ### Google Vertex #### `providerMetadata` and `providerOptions` Key The `@ai-sdk/google-vertex` provider now uses `vertex` as the key for `providerMetadata` and `providerOptions` instead of `google`. The `google` key is still supported for `providerOptions` input, but resulting `providerMetadata` output now uses `vertex`. ```tsx title="AI SDK 5" import { vertex } from '@ai-sdk/google-vertex'; import { generateText } from 'ai'; const result = await generateText({ model: vertex('gemini-2.5-flash'), providerOptions: { google: { safetySettings: [ /* ... */ ], }, // Used 'google' key }, prompt: 'Hello', }); // Accessed metadata via 'google' key console.log(result.providerMetadata?.google?.safetyRatings); ``` ```tsx title="AI SDK 6" import { vertex } from '@ai-sdk/google-vertex'; import { generateText } from 'ai'; const result = await generateText({ model: vertex('gemini-2.5-flash'), providerOptions: { vertex: { safetySettings: [ /* ... */ ], }, // Now uses 'vertex' key }, prompt: 'Hello', }); // Access metadata via 'vertex' key console.log(result.providerMetadata?.vertex?.safetyRatings); ``` Use the `rename-vertex-provider-metadata-key` codemod to automatically update your codebase. ## `ai/test` ### Mock Classes V2 mock classes have been removed from the `ai/test` module. Use the new V3 mock classes instead for testing. ```tsx title="AI SDK 5" import { MockEmbeddingModelV2, MockImageModelV2, MockLanguageModelV2, MockProviderV2, MockSpeechModelV2, MockTranscriptionModelV2, } from 'ai/test'; ``` ```tsx title="AI SDK 6" import { MockEmbeddingModelV3, MockImageModelV3, MockLanguageModelV3, MockProviderV3, MockSpeechModelV3, MockTranscriptionModelV3, } from 'ai/test'; ``` Use the `rename-mock-v2-to-v3` codemod to automatically update your codebase. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Migrate AI SDK 6.x to 7.0 description: Learn how to upgrade AI SDK 6.x to 7.0. url: "https://ai-sdk.dev/docs/migration-guides/migration-guide-7-0" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Use the command below to add the migration skill: ```bash npx skills add vercel/ai --skill migrate-ai-sdk-v6-to-v7 ``` Then ask your agent: ```txt Use the migrate-ai-sdk-v6-to-v7 skill and migrate my app from AI SDK v6 to v7. ``` ## Recommended Migration Process 1. Backup your project. If you use a versioning control system, make sure all previous versions are committed. 2. Upgrade to AI SDK 7.0. 3. Follow the breaking changes guide below. 4. Verify your project is working as expected. 5. Commit your changes. An example upgrade command would be: ``` pnpm install ai @ai-sdk/react @ai-sdk/openai @ai-sdk/otel ``` ## Codemods 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: ```sh npx @ai-sdk/codemod v7 ``` Individual codemods can be run by specifying the name of the codemod: ```sh npx @ai-sdk/codemod ``` For example, to run a specific v7 codemod: ```sh 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` | | `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](https://nodejs.org/en/about/previous-releases) for current status and support timelines. Update the `engines` field in your `package.json` if you enforce a minimum Node.js version: ```json title="package.json" { "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: ```tsx title="Before (CommonJS)" const { generateText } = require('ai'); const { openai } = require('@ai-sdk/openai'); ``` ```tsx title="After (ESM)" 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`. ```tsx title="AI SDK 6" import { experimental_customProvider } from 'ai'; export const myProvider = experimental_customProvider({ languageModels: { // ... }, }); ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" import { experimental_generateImage, type Experimental_GenerateImageResult, } from 'ai'; const result: Experimental_GenerateImageResult = await experimental_generateImage({ model: yourImageModel, prompt: 'A red panda eating bamboo', }); ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" import { experimental_transcribe as transcribe, type Experimental_TranscriptionResult, } from 'ai'; const result: Experimental_TranscriptionResult = await transcribe({ model: yourTranscriptionModel, audio, }); ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" import { experimental_generateSpeech as generateSpeech } from 'ai'; const result = await generateSpeech({ model: yourSpeechModel, text: 'Hello', }); ``` ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, experimental_output: Output.object({ schema: recipeSchema, }), prompt: 'Generate a recipe.', }); console.log(result.experimental_output); ``` ```tsx title="AI SDK 7" 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` (note: `CallSettings` never included `timeout`) 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: ```tsx title="Before" import { stepCountIs } from 'ai'; stopWhen: stepCountIs(3); ``` ```tsx title="After" 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`. ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, system: 'You are a helpful assistant.', prompt: 'Hello!', }); ``` ```tsx title="AI SDK 7" 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`: ```tsx title="AI SDK 6" const result = streamText({ model: yourModel, prompt: 'Hello!', prepareStep: () => ({ system: 'Use concise answers for this step.', }), }); ``` ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 7" 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`: ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, tools, prompt: 'Hello!', experimental_repairToolCall: async ({ system, messages }) => { return repairWithModel({ system, messages }); }, }); ``` ```tsx title="AI SDK 7" 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`: ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, prompt: 'Hello!', experimental_onStart: ({ system }) => { console.log(system); }, }); ``` ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 6" 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: ```tsx title="AI SDK 7" 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. ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, tools: { weather }, experimental_prepareStep: ({ stepNumber }) => { console.log('Preparing step', stepNumber); return { activeTools: ['weather'], }; }, }); ``` ```tsx title="AI SDK 7" 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`: ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, prompt: 'Hello!', experimental_onStart: () => { console.log('Generation started'); }, }); ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, prompt: 'Hello!', experimental_onStepStart: ({ stepNumber }) => { console.log(`Step ${stepNumber} started`); }, }); ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, prompt: 'Hello!', onFinish: ({ text }) => { console.log(text); }, }); ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, prompt: 'Hello!', onStepFinish: ({ stepNumber, usage }) => { console.log(`Step ${stepNumber} used ${usage.totalTokens} tokens`); }, }); ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" const result = await embed({ model: yourEmbeddingModel, value, experimental_onFinish(event) { console.log('Embedding finished:', event.usage.tokens); }, }); ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" const result = await rerank({ model: yourRerankingModel, documents, query, experimental_onFinish(event) { console.log('Rerank finished:', event.ranking.length); }, }); ``` ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, prompt: 'Hello!', }); console.log(result.usage.cachedInputTokens); console.log(result.usage.reasoningTokens); ``` ```tsx title="AI SDK 7" 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. ```bash pnpm install @ai-sdk/otel ``` Previously, OpenTelemetry spans were emitted automatically when `experimental_telemetry` was enabled: ```tsx title="AI SDK 6" 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: ```tsx title="instrumentation" 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. ```tsx title="AI SDK 6" import { generateText } from 'ai'; const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_telemetry: { isEnabled: true }, }); ``` ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_telemetry: { isEnabled: true, functionId: 'my-awesome-function', }, }); ``` ```tsx title="AI SDK 7" 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`: ```tsx 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: ```tsx title="AI SDK 6" 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'), }, }); ``` ```tsx title="AI SDK 7" 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. ```tsx title="AI SDK 6" import { generateText } from 'ai'; const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_telemetry: { functionId: 'story-agent', }, }); ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" import type { Telemetry } from 'ai'; const telemetry: Telemetry = { onRerankFinish(event) { console.log('Rerank finished:', event.ranking.length); }, }; ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" import type { Telemetry } from 'ai'; const telemetry: Telemetry = { onEmbedFinish(event) { console.log('Embedding finished:', event.embeddings.length); }, }; ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" const result = streamText({ model: yourModel, prompt: 'Hello!', }); for await (const part of result.fullStream) { console.log(part); } ``` ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" const result = streamText({ model: yourModel, prompt: 'Hello', includeRawChunks: true, }); ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, prompt: 'Hello', experimental_include: { requestBody: false, }, }); ``` ```tsx title="AI SDK 7" 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`. ```tsx title="AI SDK 7" const result = await generateText({ model: yourModel, prompt: 'Hello', include: { requestBody: true, responseBody: true, }, }); ``` For `streamText`, only `requestBody` is available: ```tsx title="AI SDK 7" 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. ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 7" 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_onToolCallStart` is now `onToolExecutionStart` - `experimental_onToolCallFinish` is now `onToolExecutionEnd` 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. ```tsx title="AI SDK 6" 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); }, }); ``` ```tsx title="AI SDK 7" 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 from `toolsContext` - 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: ```tsx title="AI SDK 6" 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: ```tsx title="AI SDK 7" 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_context` in tool callbacks, rename it to `context`. - If you were previously passing one shared runtime object for both orchestration and tools, split it into `runtimeContext` and `toolsContext`. - Move tool-specific values into `toolsContext` under the matching tool name. - Rename shared top-level `context` usage to `runtimeContext` in `generateText`, `streamText`, and `ToolLoopAgent`. - Rename `prepareStep` usage from `context` to `runtimeContext`. - 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_context` or `context` was `unknown`, update them to accept a generic `CONTEXT` type. - If at least one tool declares `contextSchema`, `toolsContext` becomes 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's `toolsContext` entry. #### 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. ```tsx title="AI SDK 6" 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 }, }); ``` ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, tools: { weather }, experimental_activeTools: ['weather'], }); ``` ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 6" import { ToolCallOptions } from 'ai'; function executeWithOptions(options: ToolCallOptions) { // ... } ``` ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 6" import { isToolOrDynamicToolUIPart } from 'ai'; if (isToolOrDynamicToolUIPart(part)) { console.log('Tool part found'); } ``` ```tsx title="AI SDK 7" 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', mediaType, data: { type: 'data', data } }` for inline tool-result file content, including images, as described below. The legacy `file-data` variant still works through runtime auto-migration, but is deprecated. #### 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. ```tsx title="AI SDK 6" { type: 'image', image: bytes } ``` ```tsx title="AI SDK 7" { 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: ```tsx title="AI SDK 6" const result = await generateText({ model: yourModel, prompt: 'Write a haiku, then revise it.', stopWhen: stepCountIs(2), }); console.log(result.usage); console.log(result.totalUsage); ``` ```tsx title="AI SDK 7" const result = await generateText({ model: yourModel, prompt: 'Write a haiku, then revise it.', stopWhen: isStepCount(2), }); console.log(result.finalStep.usage); // final step only console.log(result.usage); // all steps ``` `totalUsage` 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: ```tsx title="AI SDK 6" 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); ``` ```tsx title="AI SDK 7" const result = await generateText({ model: yourModel, prompt: 'Use a tool, then summarize the result.', stopWhen: isStepCount(2), }); console.log(result.finalStep.toolCalls); // final step only console.log(result.finalStep.toolResults); // final step only console.log(result.finalStep.files); // final step only console.log(result.finalStep.sources); // final step only console.log(result.finalStep.warnings); // final step only console.log(result.finalStep.content); // final step only console.log(result.toolCalls); // all steps console.log(result.toolResults); // all steps console.log(result.files); // all steps console.log(result.sources); // all steps console.log(result.warnings); // all steps console.log(result.content); // all steps ``` For `streamText`, await `finalStep` first when you need final-step-only values: ```tsx title="AI SDK 7" 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 only console.log(finalStep.toolResults); // final step only console.log(finalStep.files); // final step only console.log(finalStep.sources); // final step only console.log(finalStep.warnings); // final step only console.log(finalStep.content); // final step only console.log(await result.toolCalls); // all steps console.log(await result.toolResults); // all steps console.log(await result.files); // all steps console.log(await result.sources); // all steps console.log(await result.warnings); // all steps console.log(await result.content); // all steps ``` #### Final-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: ```tsx title="AI SDK 6" 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); ``` ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 7" 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: - `usage` now contains usage across all steps. `totalUsage` is deprecated; use `usage` instead. - `content`, `toolCalls`, `toolResults`, `files`, `sources`, and `warnings` now contain values from all steps. - Final-step-only properties are available on `finalStep`. The top-level `reasoning`, `reasoningText`, `request`, `response`, and `providerMetadata` properties are deprecated. ```tsx title="AI SDK 6" 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); }, }); ``` ```tsx title="AI SDK 7" 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** ```tsx title="AI SDK 6" const result = streamText({ model, prompt }); const uiStream = result.toUIMessageStream({ originalMessages, generateMessageId, onFinish, }); ``` ```tsx title="AI SDK 7" import { streamText, toUIMessageStream } from 'ai'; const result = streamText({ model, prompt }); const uiStream = toUIMessageStream({ stream: result.stream, generateMessageId, originalMessages, onFinish, }); ``` **UI message stream `Response`** ```tsx title="AI SDK 6" return result.toUIMessageStreamResponse({ originalMessages }); ``` ```tsx title="AI SDK 7" 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** ```tsx title="AI SDK 6" result.pipeUIMessageStreamToResponse(response, { originalMessages }); ``` ```tsx title="AI SDK 7" import { pipeUIMessageStreamToResponse, toUIMessageStream } from 'ai'; const uiStream = toUIMessageStream({ stream: result.stream, generateMessageId, originalMessages, onFinish, }); pipeUIMessageStreamToResponse({ stream: uiStream, response, }); ``` **Text stream `Response`** ```tsx title="AI SDK 6" return result.toTextStreamResponse(); ``` ```tsx title="AI SDK 7" import { createTextStreamResponse, toTextStream } from 'ai'; const textStream = toTextStream({ stream: result.stream, }); return createTextStreamResponse({ stream: textStream }); ``` **Pipe text stream to Node.js response** ```tsx title="AI SDK 6" result.pipeTextStreamToResponse(response); ``` ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 6" const mcpClient = await createMCPClient({ transport: { type: 'http', url: 'https://your-server.com/mcp', }, }); ``` ```tsx title="AI SDK 7" 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. ```tsx title="AI SDK 6" ``` ```tsx title="AI SDK 7" ``` To make the init reactive (recreate the chat when its inputs change), pass a getter or ref: ```tsx title="AI SDK 7" 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: ```tsx title="AI SDK 6" const result = await generateText({ model: anthropic('claude-sonnet-4-5'), messages: [ /* ... messages with cacheControl ... */ ], }); console.log(result.providerMetadata?.anthropic?.cacheCreationInputTokens); ``` ```tsx title="AI SDK 7" 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)`. ```tsx title="AI SDK 6" // used the Chat Completions API const model = xai('grok-4.3'); ``` ```tsx title="AI SDK 7" // now uses the Responses API const model = xai('grok-4.3'); // use the Chat Completions API explicitly const chatModel = xai.chat('grok-4.3'); ``` ## Migration Skill Use the command below to add the migration skill and guide your agent ```bash npx skills add vercel/ai --skill migrate-ai-sdk-v6-to-v7 ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Versioning description: Understand how the AI SDK approaches versioning. url: "https://ai-sdk.dev/docs/migration-guides/versioning" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Each version number follows the format: `MAJOR.MINOR.PATCH` - **Major**: Breaking API updates that require code changes. - **Minor**: Blog post that aggregates new features and improvements into a public release that highlights benefits. - **Patch**: New features and bug fixes. ## API Stability We communicate the stability of our APIs as follows: ### Stable APIs All APIs without special prefixes are considered stable and ready for production use. We maintain backward compatibility for stable features and only introduce breaking changes in major releases. ### Experimental APIs APIs prefixed with `experimental_` or `Experimental_` (e.g. `experimental_generateImage()`) are in development and can change in any releases. To use experimental APIs safely: 1. Test them first in development, not production 2. Review release notes before upgrading 3. Prepare for potential code updates If you use experimental APIs, make sure to pin your AI SDK version number exactly (avoid using ^ or \~ version ranges) to prevent unexpected breaking changes. ### Deprecated APIs APIs marked as `deprecated` will be removed in future major releases. You can wait until the major release to update your code. To handle deprecations: 1. Switch to the recommended alternative API 2. Follow the migration guide (released alongside major releases) For major releases, we provide automated codemods where possible to help migrate your code to the new version. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Reference description: Reference documentation for the AI SDK url: "https://ai-sdk.dev/docs/reference" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). #### [AI SDK Core](/docs/reference/ai-sdk-core) Switch between model providers without changing your code. #### [AI SDK RSC](/docs/reference/ai-sdk-rsc) Use React Server Components to stream user interfaces to the client. #### [AI SDK UI](/docs/reference/ai-sdk-ui) Use hooks to integrate user interfaces that interact with language models. #### [AI SDK Workflow](/docs/reference/ai-sdk-workflow) Build durable, resumable AI agents with @ai-sdk/workflow. #### [AI SDK TUI](/docs/reference/ai-sdk-tui) Run AI SDK agents in an interactive terminal UI. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Abort and resumable streams description: Troubleshooting abort and stop behavior with resumable streams url: "https://ai-sdk.dev/docs/troubleshooting/abort-breaks-resumable-streams" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When using `useChat` with `resume: true` for stream resumption, client-side aborts are treated as disconnects. Closing a tab, refreshing the page, navigating away, or calling `stop()` closes the current HTTP connection, but it should not cancel the underlying generation. If your application passes the request abort signal through to the model call, disconnects can cancel the work that stream resumption expects to keep running. If your stop button only calls `stop()`, the server-side generation can continue and the client may reconnect to the same active stream. ```tsx const { messages, stop } = useChat({ id: chatId, resume: true, // Stream resumption enabled }); // stop() only aborts the current client request. // It is not a server-side cancellation request. ``` ## Background Stream resumption lets the client reconnect to an active stream after the original connection closes. To support that, the server needs to keep the stream producer running even when no client is currently connected. This means route cleanup, page unloads, and network disconnects should be handled as resumable disconnects. Explicit user cancellation needs a separate server-side signal that cancels the active producer and clears the stored active stream reference. ## Solution Use `resume: true` for reconnecting after disconnects, and add a dedicated stop endpoint for explicit user cancellation. The stop endpoint should: 1. Load the chat and read its active stream ID 2. Persist the latest partial assistant message if the client sends one 3. Cancel the work that is producing the stream 4. Clear the active stream reference only if it still points to the same stream On the client, call your stop endpoint before stopping the local chat stream: ```tsx const chat = useChat({ id: chatId, resume: true, }); async function stopStream() { await fetch(`/api/chat/${chatId}/stop`, { method: 'POST' }); chat.stop(); } ``` Keep navigation separate from stop behavior. Do not call the stop endpoint from route cleanup code, page unload handlers, or component unmount cleanup. Those events are disconnects that should remain resumable. If you do not need stream resumption and only want client-side cancellation, disable `resume` and use `stop()` directly: ```tsx const { messages, sendMessage, stop } = useChat({ id: chatId, resume: false, // Disable stream resumption (default behavior) }); ``` ## Related - [Chatbot Resume Streams](/docs/ai-sdk-ui/chatbot-resume-streams) - [Stopping Streams](/docs/advanced/stopping-streams) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Azure OpenAI Slow to Stream description: Learn to troubleshoot Azure OpenAI slow to stream issues. url: "https://ai-sdk.dev/docs/troubleshooting/azure-stream-slow" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When using OpenAI hosted on Azure, streaming is slow and in big chunks. ## Cause This is a Microsoft Azure issue. Some users have reported the following solutions: - **Update Content Filtering Settings**: Inside [Azure AI Studio](https://ai.azure.com/), within "Shared resources" > "Content filters", create a new content filter and set the "Streaming mode (Preview)" under "Output filter" from "Default" to "Asynchronous Filter". ## Solution You can use the [`smoothStream` transformation](/docs/ai-sdk-core/generating-text#smoothing-streams) to stream each word individually. ```tsx {6} import { smoothStream, streamText } from 'ai'; const result = streamText({ model, prompt, experimental_transform: smoothStream(), }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Server Action Plain Objects Error description: Troubleshooting errors related to using AI SDK Core functions with Server Actions. url: "https://ai-sdk.dev/docs/troubleshooting/client-stream-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue I am using [`streamText`](/docs/reference/ai-sdk-core/stream-text) with Server Actions, and I am getting a `"only plain objects and a few built ins can be passed from client components"` error. ## Background This error occurs when you're trying to return a non-serializable object from a Server Action to a Client Component. The streamText function likely returns an object with methods or complex structures that can't be directly serialized and passed to the client. ## Solution To fix this issue, you need to ensure that you're only returning serializable data from your Server Action. Here's how you can modify your approach: 1. Instead of returning the entire result object from streamText, extract only the necessary serializable data. 2. Use the [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value) function to create a streamable value that can be safely passed to the client. Here's an example that demonstrates how to implement this solution: [Streaming Text Generation](/examples/next-app/basics/streaming-text-generation). This approach ensures that only serializable data (the text) is passed to the client, avoiding the "only plain objects" error. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: High memory usage when processing many images description: Troubleshooting high memory usage when using generateText or streamText with many images url: "https://ai-sdk.dev/docs/troubleshooting/high-memory-usage-with-images" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When using `generateText` or `streamText` with many images (e.g., in a loop or batch processing), you may notice: - Memory usage grows continuously and doesn't decrease - Application eventually runs out of memory - Memory is not reclaimed even after garbage collection This is especially noticeable when using `experimental_download` to process images from URLs, or when sending base64-encoded images in prompts. ## Background When enabled with the `include` option, the AI SDK includes the full request messages plus request and response bodies in the step results. When processing images, the request messages and request body can contain base64-encoded image data, which can be very large (a single image can be 1MB+ when base64 encoded). If you process many images and keep references to the results, this data accumulates in memory. For example, processing 100 images of 500KB each would include \~50MB+ of request body data in memory. ## Solution Use the `include` option to disable inclusion of request messages and/or request and response bodies: ```ts import { generateText } from 'ai'; import { openai } from '@ai-sdk/openai'; const result = await generateText({ model: openai('gpt-6-astra'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe this image' }, { type: 'file', mediaType: 'image', data: imageUrl }, ], }, ], // Request and response bodies are excluded by default. include: { requestBody: false, responseBody: false, }, }); ``` ### Options The `include` option accepts: - `requestBody`: Set to `true` to include the request body in step results. The request body is where base64-encoded images are often stored. Default: `false`. Available in both `generateText` and `streamText`. - `requestMessages`: Set to `true` to include the request messages in step results. The request messages can contain large message content such as images and files. Default: `false`. Available in both `generateText` and `streamText`. - `rawChunks`: Set to `true` to include raw provider chunks in the stream. Default: `false`. Only available in `streamText`. - `responseBody`: Set to `true` to include the response body in step results. Default: `false`. Only available in `generateText`. ### When to use - **Batch processing images**: When processing many images in a loop - **Long-running agents**: When an agent may process many images over its lifetime - **Memory-constrained environments**: When running in environments with limited memory ### Trade-offs When you disable body inclusion: - You won't have access to `result.request.body` or `result.response.body` - You won't have access to `result.request.messages` unless `requestMessages` is enabled - Debugging may be harder since you can't inspect the raw request/response - If you need the bodies for logging or debugging, consider extracting the data you need before the next iteration ## Example: Processing multiple images ```ts import { generateText } from 'ai'; import { openai } from '@ai-sdk/openai'; const imageUrls = [ /* array of image URLs */ ]; const results = []; for (const imageUrl of imageUrls) { const result = await generateText({ model: openai('gpt-6-astra'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe this image' }, { type: 'file', mediaType: 'image', data: imageUrl }, ], }, ], include: { requestBody: false, }, }); // Only store the text result, not the full result object results.push(result.text); } ``` ## Learn more - [`generateText` API Reference](/docs/reference/ai-sdk-core/generate-text) - [`streamText` API Reference](/docs/reference/ai-sdk-core/stream-text) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Troubleshooting description: Troubleshooting information for common issues encountered with the AI SDK. url: "https://ai-sdk.dev/docs/troubleshooting" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This section is designed to help you quickly identify and resolve common issues encountered with the AI SDK, ensuring a smoother and more efficient development experience. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: "Jest: cannot find module '@ai-sdk/rsc'" description: "Troubleshooting AI SDK errors related to the Jest: cannot find module '@ai-sdk/rsc' error" url: "https://ai-sdk.dev/docs/troubleshooting/jest-cannot-find-module-ai-rsc" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue I am using AI SDK RSC and am writing tests for my RSC components with Jest. I am getting the following error: `Cannot find module '@ai-sdk/rsc'`. ## Solution Configure the module resolution via `jest config update` in `moduleNameMapper`: ```json title="jest.config.js" "moduleNameMapper": { "^@ai-sdk/rsc$": "/node_modules/@ai-sdk/rsc/dist" } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Missing Tool Results Error description: "How to fix the \"Tool results are missing for tool calls\" error when using the AI SDK." url: "https://ai-sdk.dev/docs/troubleshooting/missing-tool-results-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue You encounter the error `AI_MissingToolResultsError` with a message like: > Tool results are missing for tool calls: ... ## Cause This error occurs when you attempt to send a new message to the Large Language Model (LLM) while there are pending tool calls from a previous turn that have not yet been resolved. The AI SDK core logic validates that all `tool-call` parts in the conversation history are resolved before proceeding. "Resolved" typically means: 1. The tool has been executed and a `tool-result` has been added to the history. 2. Or, the tool call has triggered a `tool-approval-response` (if using tool approvals). If a tool call is found without a corresponding result or approval response, this error is thrown to prevent sending an invalid conversation history to the model. ## Solution Ensure that every tool call in your conversation history is properly handled. ### 1. Provide Tool Results For standard tool calls, ensure that you provide the output of the tool execution. ```typescript const messages = [ { role: 'user', content: 'What is the weather in NY?' }, { role: 'assistant', content: [ { type: 'tool-call', toolCallId: 'call_123', toolName: 'getWeather', args: { location: 'New York' }, }, ], }, // You MUST include this tool message with the result: { role: 'tool', content: [ { type: 'tool-result', toolCallId: 'call_123', toolName: 'getWeather', result: 'Sunny, 25°C', }, ], }, // Now you can add a new user message { role: 'user', content: 'And in London?' }, ]; ``` ### 2. Handle Tool Approvals If you are using the tool approval workflow, ensure that you include the `tool-approval-response`. ```typescript const messages = [ // ... assistant requests tool execution (needs approval) { role: 'tool', content: [ { type: 'tool-approval-response', approvalId: 'approval_123', approved: true, // or false }, ], }, ]; ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: "Model is not assignable to type \"LanguageModelV1\"" description: Troubleshooting errors related to incompatible models. url: "https://ai-sdk.dev/docs/troubleshooting/model-is-not-assignable-to-type" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue I have updated the AI SDK and now I get the following error: `Type 'SomeModel' is not assignable to type 'LanguageModelV1'.` Similar errors can occur with `EmbeddingModelV4` as well. ## Background Sometimes new features are being added to the model specification. This can cause incompatibilities with older provider versions. ## Solution Update your provider packages and the AI SDK to the latest version. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Object generation failed with OpenAI description: Troubleshooting NoObjectGeneratedError with finish-reason content-filter caused by incompatible Zod schema types when using OpenAI structured outputs url: "https://ai-sdk.dev/docs/troubleshooting/no-object-generated-content-filter" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When using structured output generation with OpenAI, you may encounter a `NoObjectGeneratedError` with the finish reason `content-filter`. This error occurs when your Zod schema contains incompatible types that OpenAI's structured output feature cannot process. ```typescript // Problematic code - incompatible schema types import { generateText, Output } from 'ai'; import { openai } from '@ai-sdk/openai'; import { z } from 'zod'; const result = await generateText({ model: openai('gpt-4o-2024-08-06'), output: Output.object({ schema: z.object({ name: z.string().nullish(), // ❌ .nullish() is not supported email: z.string().optional(), // ❌ .optional() is not supported age: z.number().nullable(), // ✅ .nullable() is supported }), }), prompt: 'Generate a user profile', }); // Error: NoObjectGeneratedError: No object generated. // Finish reason: content-filter ``` ## Background OpenAI's structured output generation uses JSON Schema under the hood and has specific requirements for schema compatibility. The Zod methods `.nullish()` and `.optional()` generate JSON Schema patterns that are incompatible with OpenAI's implementation, causing the model to reject the schema and return a content-filter finish reason. ## Solution Replace `.nullish()` and `.optional()` with `.nullable()` in your Zod schemas when using structured output generation with OpenAI models. ```typescript import { generateText, Output } from 'ai'; import { openai } from '@ai-sdk/openai'; import { z } from 'zod'; // Correct approach - use .nullable() const result = await generateText({ model: openai('gpt-4o-2024-08-06'), output: Output.object({ schema: z.object({ name: z.string().nullable(), // ✅ Use .nullable() instead of .nullish() email: z.string().nullable(), // ✅ Use .nullable() instead of .optional() age: z.number().nullable(), }), }), prompt: 'Generate a user profile', }); console.log(result.output); // { name: "John Doe", email: "john@example.com", age: 30 } // or { name: null, email: null, age: 25 } ``` ### Schema Type Comparison | Zod Type | Compatible | JSON Schema Behavior | | ------------- | ---------- | ------------------------------------------------------ | | `.nullable()` | ✅ Yes | Allows `null` or the specified type | | `.optional()` | ❌ No | Field can be omitted (not supported) | | `.nullish()` | ❌ No | Allows `null`, `undefined`, or omitted (not supported) | ## Related Information - For more details on structured output generation, see [Generating Structured Data](/docs/ai-sdk-core/generating-structured-data) - For OpenAI-specific structured output configuration, see [OpenAI Provider - Structured Outputs](/providers/ai-sdk-providers/openai#structured-outputs) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Type Error with onToolCall description: How to handle TypeScript type errors when using the onToolCall callback url: "https://ai-sdk.dev/docs/troubleshooting/ontoolcall-type-narrowing" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). When using the `onToolCall` callback with TypeScript, you may encounter type errors when trying to pass tool properties directly to `addToolOutput`. ## Problem TypeScript cannot automatically narrow the type of `toolCall.toolName` when you have both static and dynamic tools, leading to type errors: ```tsx // ❌ This causes a TypeScript error const { messages, sendMessage, addToolOutput } = useChat({ async onToolCall({ toolCall }) { addToolOutput({ tool: toolCall.toolName, // Type 'string' is not assignable to type '"yourTool" | "yourOtherTool"' toolCallId: toolCall.toolCallId, output: someOutput, }); }, }); ``` The error occurs because: - Static tools have specific literal types for their names (e.g., `"getWeatherInformation"`) - Dynamic tools have `toolName` as a generic `string` - TypeScript can't guarantee that `toolCall.toolName` matches your specific tool names ## Solution Check if the tool is dynamic first to enable proper type narrowing: ```tsx // ✅ Correct approach with type narrowing const { messages, sendMessage, addToolOutput } = useChat({ async onToolCall({ toolCall }) { // Check if it's a dynamic tool first if (toolCall.dynamic) { return; } // Now TypeScript knows this is a static tool with the correct type addToolOutput({ tool: toolCall.toolName, // No type error! toolCallId: toolCall.toolCallId, output: someOutput, }); }, }); ``` If you're still using the deprecated `addToolResult` method, this solution applies the same way. Consider migrating to `addToolOutput` for consistency with the latest API. ## Related - [Chatbot Tool Usage](/docs/ai-sdk-ui/chatbot-tool-usage) - [Dynamic Tools](/docs/reference/ai-sdk-core/dynamic-tool) - [useChat Reference](/docs/reference/ai-sdk-ui/use-chat) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: "React error \"Maximum update depth exceeded\"" description: "Troubleshooting errors related to the \"Maximum update depth exceeded\" error." url: "https://ai-sdk.dev/docs/troubleshooting/react-maximum-update-depth-exceeded" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue I am using the AI SDK in a React project with the `useChat` or `useCompletion` hooks and I get the following error when AI responses stream in: `Maximum update depth exceeded`. ## Background By default, the UI is re-rendered on every chunk that arrives. This can overload the rendering, especially on slower devices or when complex components need updating (e.g. Markdown). Throttling can mitigate this. ## Solution Use the `throttle` option to throttle the UI updates: ### `useChat` ```tsx title="page.tsx" {2-3} const { messages } = useChat({ // Throttle the messages and data updates to 50ms: throttle: 50, }); ``` ### `useCompletion` ```tsx title="page.tsx" {2-3} const { completion } = useCompletion({ // Throttle the completion and data updates to 50ms: throttle: 50, }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Repeated assistant messages in useChat description: Troubleshooting duplicate assistant messages when using useChat with streamText url: "https://ai-sdk.dev/docs/troubleshooting/repeated-assistant-messages" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When using `useChat` with `streamText` on the server, the assistant's messages appear duplicated in the UI - showing both the previous message and the new message, or showing the same message multiple times. This can occur when using tool calls or complex message flows. ```tsx // Server-side code that may experience assistant message duplication on the client export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: 'openai/gpt-6-luna', messages: await convertToModelMessages(messages), tools: { weather: { description: 'Get the weather for a location', inputSchema: z.object({ location: z.string(), }), execute: async ({ location }) => { return { temperature: 72, condition: 'sunny' }; }, }, }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ## Background The duplication occurs because UI message streams generate new message IDs for each new message. ## Solution Pass the original messages array to `toUIMessageStream` using the `originalMessages` option. By passing `originalMessages`, the helper can reuse existing message IDs instead of generating new ones, ensuring the client properly updates existing messages rather than creating duplicates. ```tsx export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: 'openai/gpt-6-luna', messages: await convertToModelMessages(messages), tools: { weather: { description: 'Get the weather for a location', inputSchema: z.object({ location: z.string(), }), execute: async ({ location }) => { return { temperature: 72, condition: 'sunny' }; }, }, }, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, originalMessages: messages, // Pass the original messages here generateMessageId: generateId, onEnd: ({ messages }) => { saveChat({ id, messages }); }, }), }); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Server Actions in Client Components description: Troubleshooting errors related to server actions in client components. url: "https://ai-sdk.dev/docs/troubleshooting/server-actions-in-client-components" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). You may use Server Actions in client components, but sometimes you may encounter the following issues. ## Issue It is not allowed to define inline `"use server"` annotated Server Actions in Client Components. ## Solution To use Server Actions in a Client Component, you can either: - Export them from a separate file with `"use server"` at the top. - Pass them down through props from a Server Component. - Implement a combination of [`createAI`](/docs/reference/ai-sdk-rsc/create-ai) and [`useActions`](/docs/reference/ai-sdk-rsc/use-actions) hooks to access them. Learn more about [Server Actions and Mutations](https://nextjs.org/docs/app/api-reference/functions/server-actions#with-client-components). ```ts title='actions.ts' 'use server'; import { generateText } from 'ai'; export async function getAnswer(question: string) { 'use server'; const { text } = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: question, }); return { answer: text }; } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: "useChat/useCompletion stream output contains 0:... instead of text" description: How to fix strange stream output in the UI url: "https://ai-sdk.dev/docs/troubleshooting/strange-stream-output" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue I am using custom client code to process a server response that is sent using `StreamingTextResponse`. I am using version `3.0.20` or newer of the AI SDK. When I send a query, the UI streams text such as `0: "Je"`, `0: " suis"`, `0: "des"...` instead of the text that I’m looking for. ## Background The AI SDK has switched to the stream data protocol in version `3.0.20`. It sends different stream parts to support data, tool calls, etc. What you see is the raw stream data protocol response. ## Solution You have several options: 1. Use the AI Core [`streamText`](/docs/reference/ai-sdk-core/stream-text) function to send a raw text stream: ```tsx export async function POST(req: Request) { const { prompt } = await req.json(); const result = streamText({ model: openai.completion('gpt-3.5-turbo-instruct'), maxOutputTokens: 2000, prompt, }); return createTextStreamResponse({ stream: toTextStream({ stream: result.stream }), }); } ``` 2. Pin the AI SDK version to `3.0.19` . This will keep the raw text stream. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: onEnd not called when stream is aborted description: Troubleshooting onEnd callback not executing when streams are aborted with toUIMessageStream url: "https://ai-sdk.dev/docs/troubleshooting/stream-abort-handling" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When using `toUIMessageStream` with an `onEnd` callback, the callback may not execute when the stream is aborted. This happens because the abort handler immediately terminates the response, preventing the `onEnd` callback from being triggered. ```tsx // Server-side code where onEnd isn't called on abort export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), abortSignal: req.signal, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, onEnd: async ({ isAborted }) => { // This isn't called when the stream is aborted! if (isAborted) { console.log('Stream was aborted'); // Handle abort-specific cleanup } else { console.log('Stream completed normally'); // Handle normal completion } }, }), }); } ``` ## Background When a stream is aborted, the response is immediately terminated. Without proper handling, the `onEnd` callback has no chance to execute, preventing important cleanup operations like saving partial results or logging abort events. ## Solution Add `consumeSseStream: consumeStream` to the `createUIMessageStreamResponse` configuration. This ensures that abort events are properly captured and forwarded to the `onEnd` callback, allowing it to execute even when the stream is aborted. ```tsx // other imports... import { consumeStream, createUIMessageStreamResponse, toUIMessageStream, } from 'ai'; export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), abortSignal: req.signal, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, onEnd: async ({ isAborted }) => { // Now this WILL be called even when aborted! if (isAborted) { console.log('Stream was aborted'); // Handle abort-specific cleanup } else { console.log('Stream completed normally'); // Handle normal completion } }, }), consumeSseStream: consumeStream, // This enables onEnd to be called on abort }); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: streamText fails silently description: Troubleshooting errors related to the streamText function not working. url: "https://ai-sdk.dev/docs/troubleshooting/stream-text-not-working" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue I am using [`streamText`](/docs/reference/ai-sdk-core/stream-text) function, and it does not work. It does not throw any errors and the stream is only containing error parts. ## Background `streamText` immediately starts streaming to enable sending data without waiting for the model. Errors become part of the stream and are not thrown to prevent e.g. servers from crashing. ## Solution To log errors, you can provide an `onError` callback that is triggered when an error occurs. ```tsx {7-9} import { streamText } from 'ai'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', onError({ error }) { console.error(error); // your error logging logic here }, }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Streamable UI Errors description: Troubleshooting errors related to streamable UI. url: "https://ai-sdk.dev/docs/troubleshooting/streamable-ui-errors" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue - Variable Not Found - Cannot find `div` - `Component` refers to a value, but is being used as a type ## Solution If you encounter these errors when working with streamable UIs within server actions, it is likely because the file ends in `.ts` instead of `.tsx`. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Streaming Not Working When Deployed description: Troubleshooting streaming issues in deployed apps. url: "https://ai-sdk.dev/docs/troubleshooting/streaming-not-working-when-deployed" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue Streaming with the AI SDK works in my local development environment. However, when deploying, streaming does not work in the deployed app. Instead of streaming, only the full response is returned after a while. ## Cause The causes of this issue are varied and depend on the deployment environment. ## Solution You can try the following: - add `'Transfer-Encoding': 'chunked'` and/or `Connection: 'keep-alive'` headers ```tsx return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), headers: { 'Transfer-Encoding': 'chunked', Connection: 'keep-alive', }, }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Streaming Not Working When Proxied description: Troubleshooting streaming issues in proxied apps. url: "https://ai-sdk.dev/docs/troubleshooting/streaming-not-working-when-proxied" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue Streaming with the AI SDK doesn't work in local development environment, or deployed in some proxy environments. Instead of streaming, only the full response is returned after a while. ## Cause The causes of this issue are caused by the proxy middleware. If the middleware is configured to compress the response, it will cause the streaming to fail. ## Solution You can try the following, the solution only affects the streaming API: - add `'Content-Encoding': 'none'` headers ```tsx return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), headers: { 'Content-Encoding': 'none', }, }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Streaming Status Shows But No Text Appears description: "Why useChat shows \"streaming\" status without any visible content" url: "https://ai-sdk.dev/docs/troubleshooting/streaming-status-delay" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When using `useChat`, the status changes to "streaming" immediately, but no text appears for several seconds. ## Background The status changes to "streaming" as soon as the connection to the server is established and streaming begins - this includes metadata streaming, not just the LLM's generated tokens. ## Solution Create a custom loading state that checks if the last assistant message actually contains content: ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; export default function Page() { const { messages, status } = useChat(); const lastMessage = messages.at(-1); const showLoader = status === 'streaming' && lastMessage?.role === 'assistant' && lastMessage?.parts?.length === 0; return ( <> {messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, index) => part.type === 'text' ? {part.text} : null, )}
))} {showLoader &&
Loading...
} ); } ``` You can also check for specific part types if you're waiting for something specific: ```tsx const showLoader = status === 'streaming' && lastMessage?.role === 'assistant' && !lastMessage?.parts?.some(part => part.type === 'text'); ``` ## Related Issues - [GitHub Issue #7586](https://github.com/vercel/ai/issues/7586) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Getting Timeouts When Deploying on Vercel description: Learn how to fix timeouts and cut off responses when deploying to Vercel. url: "https://ai-sdk.dev/docs/troubleshooting/timeout-on-vercel" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue Streaming with the AI SDK works in my local development environment. However, when I'm deploying to Vercel, longer responses get chopped off in the UI and I'm seeing timeouts in the Vercel logs or I'm seeing the error: `Uncaught (in promise) Error: Connection closed`. ## Solution With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute), the default function duration is now **5 minutes (300 seconds)** across all plans. This should be sufficient for most streaming applications. If you need to extend the timeout for longer-running processes, you can increase the `maxDuration` setting: ### Next.js (App Router) Add the following to your route file or the page you are calling your Server Action from: ```tsx export const maxDuration = 600; ``` Setting `maxDuration` above 300 seconds requires a Pro or Enterprise plan. ### Other Frameworks For other frameworks, you can set timeouts in your `vercel.json` file: ```json { "functions": { "api/chat/route.ts": { "maxDuration": 600 } } } ``` Setting `maxDuration` above 300 seconds requires a Pro or Enterprise plan. ### Maximum Duration Limits The maximum duration you can set depends on your Vercel plan: - **Hobby**: Up to 300 seconds (5 minutes) - **Pro**: Up to 800 seconds (\~13 minutes) - **Enterprise**: Up to 800 seconds (\~13 minutes) ## Learn more - [Fluid Compute Default Settings](https://vercel.com/docs/fluid-compute#default-settings-by-plan) - [Configuring Maximum Duration for Vercel Functions](https://vercel.com/docs/functions/configuring-functions/duration) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Tool calling with structured outputs description: Troubleshooting tool calling when combined with structured output generation url: "https://ai-sdk.dev/docs/troubleshooting/tool-calling-with-structured-outputs" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue You may want to combine tool calling with structured output generation. ## Background To use tool calling with structured outputs, use `generateText` or `streamText` with the `output` option. **Important**: When using `output` with tool calling, the structured output generation counts as an additional step in the execution flow. ## Solution When using `output` with tool calling, adjust your `stopWhen` condition to account for the additional step required for structured output generation: ```tsx const result = await generateText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ summary: z.string(), sentiment: z.enum(['positive', 'neutral', 'negative']), }), }), tools: { analyze: tool({ description: 'Analyze data', inputSchema: z.object({ data: z.string(), }), execute: async ({ data }) => { return { result: 'analyzed' }; }), }, }, // Add at least 1 to your intended step count to account for structured output stopWhen: isStepCount(3), // Now accounts for: tool call + tool result + structured output prompt: 'Analyze the data and provide a summary', }); ``` For more information about using structured outputs with `generateText` and `streamText` see [Generating Structured Data](/docs/ai-sdk-core/generating-structured-data#generating-structured-outputs). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Tool Invocation Missing Result Error description: "How to fix the \"ToolInvocation must have a result\" error when using tools without execute functions" url: "https://ai-sdk.dev/docs/troubleshooting/tool-invocation-missing-result" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When using `generateText()` or `streamText()`, you may encounter the error "ToolInvocation must have a result" when a tool without an `execute` function is called. ## Cause The error occurs when you define a tool without an `execute` function and don't provide the result through other means (like `useChat`'s `onToolCall` or `addToolOutput` functions). Each time a tool is invoked, the model expects to receive a result before continuing the conversation. Without a result, the model cannot determine if the tool call succeeded or failed and the conversation state becomes invalid. ## Solution You have two options for handling tool results: 1. Server-side execution using tools with an `execute` function: ```tsx const tools = { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z .string() .describe('The city and state, e.g. "San Francisco, CA"'), }), execute: async ({ location }) => { // Fetch and return weather data return { temperature: 72, conditions: 'sunny', location }; }, }), }; ``` 2. For client-side execution with `useChat` (omitting the `execute` function), you must provide results using `addToolOutput`: ```tsx import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls, } from 'ai'; const { messages, sendMessage, addToolOutput } = useChat({ // Automatically submit when all tool results are available sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, // Handle tool calls in onToolCall onToolCall: async ({ toolCall }) => { if (toolCall.toolName === 'getLocation') { try { const result = await getLocationData(); // Important: Don't await addToolOutput inside onToolCall to avoid deadlocks addToolOutput({ tool: 'getLocation', toolCallId: toolCall.toolCallId, output: result, }); } catch (err) { // Important: Don't await addToolOutput inside onToolCall to avoid deadlocks addToolOutput({ tool: 'getLocation', toolCallId: toolCall.toolCallId, state: 'output-error', errorText: 'Failed to get location', }); } } }, }); ``` ```tsx // For interactive UI elements: const { messages, sendMessage, addToolOutput } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat' }), sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, }); // Inside your JSX, when rendering tool calls: ; ``` Whether handling tools on the server or client, each tool call must have a corresponding result before the conversation can continue. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: "TypeScript error \"Cannot find namespace 'JSX'\"" description: Troubleshooting errors related to TypeScript and JSX. url: "https://ai-sdk.dev/docs/troubleshooting/typescript-cannot-find-namespace-jsx" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue I am using the AI SDK in a project without React, e.g. an Hono server, and I get the following error: `error TS2503: Cannot find namespace 'JSX'.` ## Background The AI SDK has a dependency on `@types/react` which defines the `JSX` namespace. It will be removed in the next major version of the AI SDK. ## Solution You can install the `@types/react` package as a dependency to fix the error. ```bash npm install @types/react ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: TypeScript performance issues with Zod and AI SDK 5 description: Troubleshooting TypeScript server crashes and slow performance when using Zod with AI SDK 5 url: "https://ai-sdk.dev/docs/troubleshooting/typescript-performance-zod" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When using the AI SDK 5 with Zod, you may experience: - TypeScript server crashes or hangs - Extremely slow type checking in files that import AI SDK functions - Error messages like "Type instantiation is excessively deep and possibly infinite" - IDE becoming unresponsive when working with AI SDK code ## Background The AI SDK 5 has specific compatibility requirements with Zod versions. When importing Zod using the standard import path (`import { z } from 'zod'`), TypeScript's type inference can become excessively complex, leading to performance degradation or crashes. ## Solution ### Upgrade Zod to 4.1.8 or Later The primary solution is to upgrade to Zod version 4.1.8 or later, which includes a fix for this module resolution issue: ```bash pnpm add zod@^4.1.8 ``` This version resolves the underlying problem where different module resolution settings were causing TypeScript to load the same Zod declarations twice, leading to expensive structural comparisons. ### Alternative: Update TypeScript Configuration If upgrading Zod isn't possible, you can update your `tsconfig.json` to use `moduleResolution: "nodenext"`: ```json { "compilerOptions": { "moduleResolution": "nodenext" // ... other options } } ``` This resolves the TypeScript performance issues while allowing you to continue using the standard Zod import. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Unclosed Streams description: Troubleshooting errors related to unclosed streams. url: "https://ai-sdk.dev/docs/troubleshooting/unclosed-streams" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Sometimes streams are not closed properly, which can lead to unexpected behavior. The following are some common issues that can occur when streams are not closed properly. ## Issue The streamable UI has been slow to update. ## Solution This happens when you create a streamable UI using [`createStreamableUI`](/docs/reference/ai-sdk-rsc/create-streamable-ui) and fail to close the stream. In order to fix this, you must ensure you close the stream by calling the [`.done()`](/docs/reference/ai-sdk-rsc/create-streamable-ui#done) method. This will ensure the stream is closed. ```tsx title='app/actions.tsx' import { createStreamableUI } from '@ai-sdk/rsc'; const submitMessage = async () => { 'use server'; const stream = createStreamableUI('1'); stream.update('2'); stream.append('3'); stream.done('4'); // [!code ++] return stream.value; }; ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Unsupported model version error description: Troubleshooting the AI_UnsupportedModelVersionError when migrating to AI SDK 5 url: "https://ai-sdk.dev/docs/troubleshooting/unsupported-model-version" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When migrating to AI SDK 5, you might encounter an error stating that your model uses an unsupported version: ``` AI_UnsupportedModelVersionError: Unsupported model version v1 for provider "ollama.chat" and model "gamma3:4b". AI SDK 5 only supports models that implement specification version "v2". ``` This error occurs because the version of the provider package you're using implements the older (v1) model specification. ## Background AI SDK 5 requires all provider packages to implement specification version "v2". When you upgrade to AI SDK 5 but don't update your provider packages to compatible versions, they continue using the older "v1" specification, causing this error. ## Solution ### Update provider packages to AI SDK 5 compatible versions Update all your `@ai-sdk/*` provider packages to compatible version `2.0.0` or later. These versions implement the v2 specification required by AI SDK 5. ```bash pnpm install ai@latest @ai-sdk/openai@latest @ai-sdk/anthropic@latest ``` For AI SDK 5 compatibility, you need: - `ai` package: `5.0.0` or later - `@ai-sdk/*` packages: `2.0.0` or later (for example, `@ai-sdk/openai`, `@ai-sdk/anthropic`, `@ai-sdk/google`) - `@ai-sdk/provider` package: `2.0.0` or later - `zod` package: `4.1.8` or later ### Check provider compatibility If you're using a third-party or custom provider, verify that it has been updated to support AI SDK 5. Not all providers may have v2-compatible versions available yet. To check if a provider supports AI SDK 5: 1. Check the provider's package.json for `@ai-sdk/provider` peer dependency version `2.0.0` or later 2. Review the provider's changelog or migration guide 3. Check the provider's repository for AI SDK 5 support For more information on migrating to AI SDK 5, see the [AI SDK 5.0 migration guide](/docs/migration-guides/migration-guide-5-0). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: "useChat \"An error occurred\"" description: "Troubleshooting errors related to the \"An error occurred\" error in useChat." url: "https://ai-sdk.dev/docs/troubleshooting/use-chat-an-error-occurred" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue I am using [`useChat`](/docs/reference/ai-sdk-ui/use-chat) and I get the error "An error occurred". ## Background Error messages from `streamText` are masked by default when using `toUIMessageStream` for security reasons (secure-by-default). This prevents leaking sensitive information to the client. ## Solution To forward error details to the client or to log errors, use the `onError` function when calling `toUIMessageStream`. ```tsx export function errorHandler(error: unknown) { if (error == null) { return 'unknown error'; } if (typeof error === 'string') { return error; } if (error instanceof Error) { return error.message; } return JSON.stringify(error); } ``` ```tsx const result = streamText({ // ... }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream, onError: errorHandler, }), }); ``` In case you are using `createUIMessageStream`, you can pass the `onError` function to it: ```tsx const response = createUIMessageStreamResponse({ stream: createUIMessageStream({ // ... async execute({ writer }) { // ... }, onError: errorHandler, }), }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Custom headers, body, and credentials not working with useChat description: Troubleshooting errors related to custom request configuration in useChat hook url: "https://ai-sdk.dev/docs/troubleshooting/use-chat-custom-request-options" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When using the `useChat` hook, custom request options like headers, body fields, and credentials configured directly on the hook are not being sent with the request: ```tsx // These options are not sent with the request const { messages, sendMessage } = useChat({ headers: { Authorization: 'Bearer token123', }, body: { user_id: '123', }, credentials: 'include', }); ``` ## Background The `useChat` hook has changed its API for configuring request options. Direct options like `headers`, `body`, and `credentials` on the hook itself are no longer supported. Instead, you need to use the `transport` configuration with `DefaultChatTransport` or pass options at the request level. ## Solution There are three ways to properly configure request options with `useChat`: ### Option 1: Request-Level Configuration (Recommended for Dynamic Values) For dynamic values that change over time, the recommended approach is to pass options when calling `sendMessage`: ```tsx const { messages, sendMessage } = useChat(); // Send options with each message sendMessage( { text: input }, { headers: { Authorization: `Bearer ${getAuthToken()}`, // Dynamic auth token 'X-Request-ID': generateRequestId(), }, body: { temperature: 0.7, max_tokens: 100, user_id: getCurrentUserId(), // Dynamic user ID sessionId: getCurrentSessionId(), // Dynamic session }, }, ); ``` This approach ensures that the most up-to-date values are always sent with each request. ### Option 2: Hook-Level Configuration with Static Values For static values that don't change during the component lifecycle, use the `DefaultChatTransport`: ```tsx import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', headers: { 'X-API-Version': 'v1', // Static API version 'X-App-ID': 'my-app', // Static app identifier }, body: { model: 'gpt-6-astra', // Default model stream: true, // Static configuration }, credentials: 'include', // Static credentials policy }), }); ``` ### Option 3: Hook-Level Configuration with Resolvable Functions If you need dynamic values at the hook level, you can use functions that return configuration values. However, request-level configuration is generally preferred for better reliability: ```tsx import { useChat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', headers: () => ({ Authorization: `Bearer ${getAuthToken()}`, 'X-User-ID': getCurrentUserId(), }), body: () => ({ sessionId: getCurrentSessionId(), preferences: getUserPreferences(), }), credentials: () => (isAuthenticated() ? 'include' : 'same-origin'), }), }); ``` For component state that changes over time, request-level configuration (Option 1) is recommended. If using hook-level functions, consider using `useRef` to store current values and reference `ref.current` in your configuration function. ### Combining Hook and Request Level Options Request-level options take precedence over hook-level options: ```tsx // Hook-level default configuration const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', headers: { 'X-API-Version': 'v1', }, body: { model: 'gpt-6-astra', }, }), }); // Override or add options per request sendMessage( { text: input }, { headers: { 'X-API-Version': 'v2', // This overrides the hook-level header 'X-Request-ID': '123', // This is added }, body: { model: 'gpt-4.1-mini', // Supports the temperature setting below temperature: 0.5, // This is added }, }, ); ``` For more details on request configuration, see the [Request Configuration](/docs/ai-sdk-ui/chatbot#request-configuration) documentation. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: useChat Failed to Parse Stream description: Troubleshooting errors related to the Use Chat Failed to Parse Stream error. url: "https://ai-sdk.dev/docs/troubleshooting/use-chat-failed-to-parse-stream" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue I am using [`useChat`](/docs/reference/ai-sdk-ui/use-chat) or [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion), and I am getting a `"Failed to parse stream string. Invalid code"` error. I am using version `3.0.20` or newer of the AI SDK. ## Background The AI SDK has switched to the stream data protocol in version `3.0.20`. [`useChat`](/docs/reference/ai-sdk-ui/use-chat) and [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion) expect stream parts that support data, tool calls, etc. What you see is a failure to parse the stream. This can be caused by using an older version of the AI SDK in the backend, by providing a text stream using a custom provider, or by using a raw LangChain stream result. ## Solution You can switch [`useChat`](/docs/reference/ai-sdk-ui/use-chat) and [`useCompletion`](/docs/reference/ai-sdk-ui/use-completion) to raw text stream processing with the [`streamProtocol`](/docs/reference/ai-sdk-ui/use-completion#stream-protocol) parameter. Set it to `text` as follows: ```tsx const { messages, append } = useChat({ streamProtocol: 'text' }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Stale body values with useChat description: Troubleshooting stale values when passing information via the body parameter of useChat url: "https://ai-sdk.dev/docs/troubleshooting/use-chat-stale-body-data" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue When using `useChat` and passing dynamic information via the `body` parameter at the hook level, the data remains stale and only reflects the value from the initial component render. This occurs because the body configuration is captured once when the hook is initialized and doesn't update with subsequent component re-renders. ```tsx // Problematic code - body data will be stale export default function Chat() { const [temperature, setTemperature] = useState(0.7); const [userId, setUserId] = useState('user123'); // This body configuration is captured once and won't update const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', body: { temperature, // Always the initial value (0.7) userId, // Always the initial value ('user123') }, }), }); // Even if temperature or userId change, the body in requests will still use initial values return (
setTemperature(parseFloat(e.target.value))} /> {/* Chat UI */}
); } ``` ## Background The hook-level body configuration is evaluated once during the initial render and doesn't re-evaluate when component state changes. ## Solution Pass dynamic variables via the second argument of the `sendMessage` function instead of at the hook level. Request-level options are evaluated on each call and take precedence over hook-level options. ```tsx export default function Chat() { const [temperature, setTemperature] = useState(0.7); const [userId, setUserId] = useState('user123'); const [input, setInput] = useState(''); const { messages, sendMessage } = useChat({ // Static configuration only transport: new DefaultChatTransport({ api: '/api/chat', }), }); return (
setTemperature(parseFloat(e.target.value))} />
{ event.preventDefault(); if (input.trim()) { // Pass dynamic values as request-level options sendMessage( { text: input }, { body: { temperature, // Current value at request time userId, // Current value at request time }, }, ); setInput(''); } }} > setInput(e.target.value)} />
); } ``` ### Alternative: Dynamic Hook-Level Configuration If you need hook-level configuration that responds to changes, you can use functions that return configuration values. However, for component state, you'll need to use `useRef` to access current values: ```tsx export default function Chat() { const temperatureRef = useRef(0.7); const { messages, sendMessage } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat', body: () => ({ temperature: temperatureRef.current, // Access via ref.current sessionId: getCurrentSessionId(), // Function calls work directly }), }), }); // ... } ``` **Recommendation:** Request-level configuration is simpler and more reliable for component state. Use it whenever you need to pass dynamic values that change during the component lifecycle. ### Server-side handling On your server side, retrieve the custom fields by destructuring the request body: ```tsx // app/api/chat/route.ts export async function POST(req: Request) { const { messages, temperature, userId } = await req.json(); const result = streamText({ model: 'openai/gpt-4.1-mini', // Supports dynamic temperature settings messages: await convertToModelMessages(messages), temperature, // Use the dynamic temperature from the request // ... other configuration }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` For more information, see [chatbot request configuration documentation](/docs/ai-sdk-ui/chatbot#request-configuration). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: useChat No Response description: Troubleshooting errors related to the Use Chat Failed to Parse Stream error. url: "https://ai-sdk.dev/docs/troubleshooting/use-chat-tools-no-response" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ## Issue I am using [`useChat`](/docs/reference/ai-sdk-ui/use-chat). When I log the incoming messages on the server, I can see the tool call and the tool result, but the model does not respond with anything. ## Solution To resolve this issue, convert the incoming messages to the `ModelMessage` format using the [`convertToModelMessages`](/docs/reference/ai-sdk-ui/convert-to-model-messages) function. ```tsx {15} import { openai } from '@ai-sdk/openai'; import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, } from 'ai'; export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_APICallError description: Learn how to fix AI_APICallError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-api-call-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when an API call fails. ## Properties - `url`: The URL of the API request that failed - `requestBodyValues`: The request body values sent to the API - `statusCode`: The HTTP status code returned by the API (optional) - `responseHeaders`: The response headers returned by the API (optional) - `responseBody`: The response body returned by the API (optional) - `isRetryable`: Whether the request can be retried based on the status code - `data`: Any additional data associated with the error (optional) - `cause`: The underlying error that caused the API call to fail (optional) When this error is created for an AI SDK UI chat transport or completion request, `requestBodyValues` is `undefined` so prompts and messages are not copied into the client-facing error object. ## Checking for this Error You can check if an error is an instance of `AI_APICallError` using: ```typescript import { APICallError } from 'ai'; if (APICallError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_DecisionRefusalError description: Identify decision questions that a model declined to answer. url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-decision-refusal-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This experimental error identifies questions that a decision model declined to answer, such as a question the provider's safety policy refuses. It is exported from `ai` and `@ai-sdk/provider` as `Experimental_DecisionRefusalError`. Decision models report a refusal as an answer with `type: 'refusal'`. `experimental_decide` throws this error when any answer is a refusal, after the provider call and without retrying it. ## Properties - `questionIds`: The IDs of the refused questions. - `provider`: The provider of the decision model. - `modelId`: The decision model ID. - `message`: A description of the refused questions and model. ## Checking for this Error Use the marker-based `isInstance` check, which works across package copies: ```typescript import { Experimental_DecisionRefusalError as DecisionRefusalError } from 'ai'; if (DecisionRefusalError.isInstance(error)) { console.log(error.questionIds, error.modelId); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_DecisionUnsupportedQuestionTypeError description: Identify a decision question type that a model does not support. url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-decision-unsupported-question-type-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This experimental error identifies a question whose type is unsupported by a decision model. It is exported from `ai` and `@ai-sdk/provider` as `Experimental_DecisionUnsupportedQuestionTypeError`. ## Properties - `questionId`: The ID of the unsupported question. - `questionType`: The requested question type. - `provider`: The provider of the decision model. - `modelId`: The decision model ID. - `message`: A description of the unsupported question and model. Providers can supply a custom message. ## Checking for this Error Use the marker-based `isInstance` check, which works across package copies: ```typescript import { Experimental_DecisionUnsupportedQuestionTypeError as DecisionUnsupportedQuestionTypeError } from 'ai'; if (DecisionUnsupportedQuestionTypeError.isInstance(error)) { console.log(error.questionId, error.questionType, error.modelId); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_DownloadError description: Learn how to fix AI_DownloadError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-download-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when a download fails. ## Properties - `url`: The URL that failed to download - `statusCode`: The HTTP status code returned by the server (optional) - `statusText`: The HTTP status text returned by the server (optional) - `cause`: The underlying error that caused the download to fail (optional) - `message`: The error message containing details about the download failure (optional, auto-generated) ## Checking for this Error You can check if an error is an instance of `AI_DownloadError` using: ```typescript import { DownloadError } from 'ai'; if (DownloadError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_EmptyResponseBodyError description: Learn how to fix AI_EmptyResponseBodyError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-empty-response-body-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when the server returns an empty response body. ## Properties - `message`: The error message ## Checking for this Error You can check if an error is an instance of `AI_EmptyResponseBodyError` using: ```typescript import { EmptyResponseBodyError } from 'ai'; if (EmptyResponseBodyError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_InvalidArgumentError description: Learn how to fix AI_InvalidArgumentError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-argument-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when an invalid argument was provided. For example, `getTextFromDataUrl` throws this error when its `dataUrl` argument is malformed or cannot be decoded. Utility validation that is used by higher level APIs also uses this error, so you can handle invalid arguments with one stable error guard. ## Properties - `parameter`: The name of the parameter that is invalid - `value`: The invalid value - `message`: The error message ## Checking for this Error You can check if an error is an instance of `AI_InvalidArgumentError` using: ```typescript import { InvalidArgumentError } from 'ai'; if (InvalidArgumentError.isInstance(error)) { console.error(`Invalid ${error.parameter}:`, error.message); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_InvalidDataContentError description: How to fix AI_InvalidDataContentError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-data-content-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when the data content provided in a multi-modal message part is invalid. Check out the [ prompt examples for multi-modal messages ](/docs/foundations/prompts#message-prompts). ## Properties - `content`: The invalid content value - `cause`: The underlying error that caused this error (optional) - `message`: The error message describing the expected and received content types (optional, auto-generated) ## Checking for this Error You can check if an error is an instance of `AI_InvalidDataContentError` using: ```typescript import { InvalidDataContentError } from 'ai'; if (InvalidDataContentError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_InvalidMessageRoleError description: Learn how to fix AI_InvalidMessageRoleError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-message-role-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when an invalid message role is provided. ## Properties - `role`: The invalid role value - `message`: The error message (optional, auto-generated from `role`) ## Checking for this Error You can check if an error is an instance of `AI_InvalidMessageRoleError` using: ```typescript import { InvalidMessageRoleError } from 'ai'; if (InvalidMessageRoleError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_InvalidPromptError description: Learn how to fix AI_InvalidPromptError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-prompt-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when the prompt provided is invalid. ## Potential Causes ### UI Messages You are passing a `UIMessage[]` as messages into e.g. `streamText`. You need to first convert them to a `ModelMessage[]` using `convertToModelMessages()`. ```typescript import { type UIMessage, generateText, convertToModelMessages } from 'ai'; const messages: UIMessage[] = [ /* ... */ ]; const result = await generateText({ // ... messages: await convertToModelMessages(messages), }); ``` ## Properties - `prompt`: The invalid prompt value - `message`: The error message (required in constructor) - `cause`: The cause of the error (optional) ## Checking for this Error You can check if an error is an instance of `AI_InvalidPromptError` using: ```typescript import { InvalidPromptError } from 'ai'; if (InvalidPromptError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_InvalidResponseDataError description: Learn how to fix AI_InvalidResponseDataError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-response-data-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when the server returns a response with invalid data content. ## Properties - `data`: The invalid response data value - `message`: The error message ## Checking for this Error You can check if an error is an instance of `AI_InvalidResponseDataError` using: ```typescript import { InvalidResponseDataError } from 'ai'; if (InvalidResponseDataError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_InvalidToolApprovalError description: Learn how to fix AI_InvalidToolApprovalError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when a tool approval response references an unknown `approvalId`. No matching `tool-approval-request` was found in the message history. ## Properties - `approvalId`: The approval ID that was not found ## Checking for this Error You can check if an error is an instance of `AI_InvalidToolApprovalError` using: ```typescript import { InvalidToolApprovalError } from 'ai'; if (InvalidToolApprovalError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_InvalidToolApprovalSignatureError description: Learn how to fix AI_InvalidToolApprovalSignatureError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-signature-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when `experimental_toolApprovalSecret` is configured and a tool approval replayed from the message history has a missing or invalid HMAC signature. The server rejects the approval before the tool executes. Common causes: - The approval was fabricated by the client (no signature present) - The tool arguments were modified after the approval was signed - The server secret changed between issuance and verification (e.g. after key rotation without overlap) ## Properties - `approvalId`: The approval ID that failed verification - `toolCallId`: The tool call ID the approval was for ## Checking for this Error You can check if an error is an instance of `AI_InvalidToolApprovalSignatureError` using: ```typescript import { InvalidToolApprovalSignatureError } from 'ai'; if (InvalidToolApprovalSignatureError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_InvalidToolInputError description: Learn how to fix AI_InvalidToolInputError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-invalid-tool-input-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when invalid tool input was provided. ## Properties - `toolName`: The name of the tool with invalid inputs - `toolInput`: The invalid tool inputs - `message`: The error message - `cause`: The cause of the error ## Checking for this Error You can check if an error is an instance of `AI_InvalidToolInputError` using: ```typescript import { InvalidToolInputError } from 'ai'; if (InvalidToolInputError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_JSONParseError description: Learn how to fix AI_JSONParseError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-json-parse-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when JSON fails to parse. ## Properties - `text`: The text value that could not be parsed - `cause`: The underlying parsing error (required in constructor) ## Checking for this Error You can check if an error is an instance of `AI_JSONParseError` using: ```typescript import { JSONParseError } from 'ai'; if (JSONParseError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_LoadAPIKeyError description: Learn how to fix AI_LoadAPIKeyError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-load-api-key-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when API key is not loaded successfully. ## Properties - `message`: The error message ## Checking for this Error You can check if an error is an instance of `AI_LoadAPIKeyError` using: ```typescript import { LoadAPIKeyError } from 'ai'; if (LoadAPIKeyError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_LoadSettingError description: Learn how to fix AI_LoadSettingError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-load-setting-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when a setting is not loaded successfully. ## Properties - `message`: The error message ## Checking for this Error You can check if an error is an instance of `AI_LoadSettingError` using: ```typescript import { LoadSettingError } from 'ai'; if (LoadSettingError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_MessageConversionError description: Learn how to fix AI_MessageConversionError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-message-conversion-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when message conversion fails. ## Properties - `originalMessage`: The original message that failed conversion - `message`: The error message ## Checking for this Error You can check if an error is an instance of `AI_MessageConversionError` using: ```typescript import { MessageConversionError } from 'ai'; if (MessageConversionError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoContentGeneratedError description: Learn how to fix AI_NoContentGeneratedError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-content-generated-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when the AI provider fails to generate content. ## Properties - `message`: The error message (optional, defaults to `'No content generated.'`) ## Checking for this Error You can check if an error is an instance of `AI_NoContentGeneratedError` using: ```typescript import { NoContentGeneratedError } from 'ai'; if (NoContentGeneratedError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoImageGeneratedError description: Learn how to fix AI_NoImageGeneratedError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-image-generated-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when the AI provider fails to generate an image. It can arise due to the following reasons: - The model failed to generate a response. - The model generated an invalid response. ## Properties - `message`: The error message (optional, defaults to `'No image generated.'`). - `calls`: Results from the underlying image model calls, including generated images, provider metadata, response metadata, warnings, and usage (optional). - `responses`: Metadata about the image model responses, including timestamp, model, and headers (optional). - `cause`: The cause of the error. You can use this for more detailed error handling (optional). ## Checking for this Error You can check if an error is an instance of `AI_NoImageGeneratedError` using: ```typescript import { generateImage, NoImageGeneratedError } from 'ai'; try { await generateImage({ model, prompt }); } catch (error) { if (NoImageGeneratedError.isInstance(error)) { console.log('NoImageGeneratedError'); console.log('Cause:', error.cause); console.log('Responses:', error.responses); for (const call of error.calls ?? []) { console.log('Provider metadata:', call.providerMetadata); console.log('Warnings:', call.warnings); console.log('Usage:', call.usage); } } } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoObjectGeneratedError description: Learn how to fix AI_NoObjectGeneratedError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-object-generated-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when the AI provider fails to generate a parsable object that conforms to the schema. It can arise due to the following reasons: - The model failed to generate a response. - The model generated a response that could not be parsed. - The model generated a response that could not be validated against the schema. ## Properties - `message`: The error message (optional, defaults to `'No object generated.'`). - `text`: The text that was generated by the model. This can be the raw text or the tool call text, depending on the object generation mode (optional). - `response`: Metadata about the language model response, including response id, timestamp, and model (required in constructor). - `usage`: Request token usage (required in constructor). - `finishReason`: Request finish reason. For example 'length' if model generated maximum number of tokens, this could result in a JSON parsing error (required in constructor). - `cause`: The cause of the error (e.g. a JSON parsing error). You can use this for more detailed error handling (optional). ## Checking for this Error You can check if an error is an instance of `AI_NoObjectGeneratedError` using: ```typescript import { generateText, NoObjectGeneratedError, Output } from 'ai'; try { await generateText({ model, output: Output.object({ schema }), prompt }); } catch (error) { if (NoObjectGeneratedError.isInstance(error)) { console.log('NoObjectGeneratedError'); console.log('Cause:', error.cause); console.log('Text:', error.text); console.log('Response:', error.response); console.log('Usage:', error.usage); console.log('Finish Reason:', error.finishReason); } } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoOutputGeneratedError description: Learn how to fix AI_NoOutputGeneratedError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-output-generated-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error is thrown when no LLM output was generated, e.g. because of errors. For `generateText`, accessing `result.output` throws this error when the result does not contain an output. This can happen when the final step does not finish with a `stop` reason, for example when it finishes with `tool-calls`. The `output` property is a getter, so destructuring it also triggers this access. ## Properties - `message`: The error message (optional, defaults to `'No output generated.'`) - `cause`: The underlying error that caused no output to be generated (optional) ## Checking for this Error You can check if an error is an instance of `AI_NoOutputGeneratedError` using: ```typescript import { NoOutputGeneratedError } from 'ai'; if (NoOutputGeneratedError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoSpeechGeneratedError description: Learn how to fix AI_NoSpeechGeneratedError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-speech-generated-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when no audio could be generated from the input. ## Properties - `responses`: Array of speech model response metadata (required in constructor) ## Checking for this Error You can check if an error is an instance of `AI_NoSpeechGeneratedError` using: ```typescript import { NoSpeechGeneratedError } from 'ai'; if (NoSpeechGeneratedError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoSuchModelError description: Learn how to fix AI_NoSuchModelError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-such-model-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when a model ID is not found. ## Properties - `modelId`: The ID of the model that was not found - `modelType`: The type of model (`'languageModel'`, `'embeddingModel'`, `'imageModel'`, `'transcriptionModel'`, `'speechModel'`, or `'rerankingModel'`) - `message`: The error message (optional, auto-generated from `modelId` and `modelType`) ## Checking for this Error You can check if an error is an instance of `AI_NoSuchModelError` using: ```typescript import { NoSuchModelError } from 'ai'; if (NoSuchModelError.isInstance(error)) { // Handle the error } ``` Experimental decision resolution uses `modelType: 'decisionModel'`. This includes unavailable decision capabilities and unknown decision model or provider IDs. See [Decisions](/docs/ai-sdk-core/decisions#default-provider-strings). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoSuchProviderError description: Learn how to fix AI_NoSuchProviderError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-such-provider-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when a provider ID is not found. ## Properties - `providerId`: The ID of the provider that was not found - `availableProviders`: Array of available provider IDs - `modelId`: The ID of the model - `modelType`: The type of model - `message`: The error message ## Checking for this Error You can check if an error is an instance of `AI_NoSuchProviderError` using: ```typescript import { NoSuchProviderError } from 'ai'; if (NoSuchProviderError.isInstance(error)) { // Handle the error } ``` Experimental decision resolution uses `modelType: 'decisionModel'`. This includes unavailable decision capabilities and unknown decision model or provider IDs. See [Decisions](/docs/ai-sdk-core/decisions#default-provider-strings). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoSuchProviderReferenceError description: Learn how to fix AI_NoSuchProviderReferenceError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-such-provider-reference-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when a provider reference cannot be resolved because the specified provider is not found in the provider reference mapping. ## Properties - `provider`: The provider that was not found - `reference`: The full provider reference mapping that was searched - `message`: The error message ## Checking for this Error You can check if an error is an instance of `AI_NoSuchProviderReferenceError` using: ```typescript import { NoSuchProviderReferenceError } from 'ai'; if (NoSuchProviderReferenceError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoSuchToolError description: Learn how to fix AI_NoSuchToolError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-such-tool-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when a model tries to call an unavailable tool. ## Properties - `toolName`: The name of the tool that was not found - `availableTools`: Array of available tool names (optional) - `message`: The error message (optional, auto-generated from `toolName` and `availableTools`) ## Checking for this Error You can check if an error is an instance of `AI_NoSuchToolError` using: ```typescript import { NoSuchToolError } from 'ai'; if (NoSuchToolError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoTranscriptGeneratedError description: Learn how to fix AI_NoTranscriptGeneratedError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-transcript-generated-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when no transcript could be generated from the input. ## Properties - `responses`: Array of transcription model response metadata (required in constructor) ## Checking for this Error You can check if an error is an instance of `AI_NoTranscriptGeneratedError` using: ```typescript import { NoTranscriptGeneratedError } from 'ai'; if (NoTranscriptGeneratedError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoTranslationGeneratedError description: Learn how to fix AI_NoTranslationGeneratedError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-translation-generated-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when no translation could be generated from the input audio: no `audio` part was emitted and the final output text is empty, or the stream ended without a finish event. ## Properties - `response`: Speech translation model response metadata (required in constructor) ## Checking for this Error You can check if an error is an instance of `AI_NoTranslationGeneratedError` using: ```typescript import { NoTranslationGeneratedError } from 'ai'; if (NoTranslationGeneratedError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_NoVideoGeneratedError description: Learn how to fix AI_NoVideoGeneratedError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-no-video-generated-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when the AI provider fails to generate a video. It can arise due to the following reasons: - The model failed to generate a response. - The model generated an invalid response. ## Properties - `message`: The error message (optional, defaults to `'No video generated.'`). - `responses`: Metadata about the video model responses, including timestamp, model, and headers (optional). - `cause`: The cause of the error. You can use this for more detailed error handling (optional). ## Checking for this Error You can check if an error is an instance of `AI_NoVideoGeneratedError` using: ```typescript import { experimental_generateVideo as generateVideo, NoVideoGeneratedError, } from 'ai'; try { await generateVideo({ model, prompt }); } catch (error) { if (NoVideoGeneratedError.isInstance(error)) { console.log('NoVideoGeneratedError'); console.log('Cause:', error.cause); console.log('Responses:', error.responses); } } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_RetryError description: Learn how to fix AI_RetryError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-retry-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when a retry operation fails. ## Properties - `reason`: The reason for the retry failure - `lastError`: The most recent error that occurred during retries - `errors`: Array of all errors that occurred during retry attempts - `message`: The error message ## Checking for this Error You can check if an error is an instance of `AI_RetryError` using: ```typescript import { RetryError } from 'ai'; if (RetryError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_StreamProviderError description: Learn how to handle AI_StreamProviderError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-stream-provider-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error represents a well-formed error event reported by a provider after a model response stream has started. The AI SDK exposes it in streaming error parts and callbacks such as the `streamText` `onError` callback. ## Properties - `message`: The provider error message - `type`: The provider-defined error type (optional) - `code`: The provider-defined error code as a string or number (optional) - `statusCode`: The HTTP-equivalent status code when supplied by or inferable from provider metadata (optional) - `isRetryable`: Whether retrying the model call may succeed - `data`: The original provider error payload (optional) - `cause`: The underlying error that caused the failure (optional) `isRetryable` is a retry classification, not an automatic retry. A stream may already contain partial output, so applications should decide whether to discard, replace, or preserve that output before starting another model call. ## Checking for this Error Use `StreamProviderError.isInstance` so identification also works when multiple AI SDK versions are present: ```typescript import { StreamProviderError, streamText } from 'ai'; const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a vegetarian lasagna recipe for 4 people.', onError: ({ error }) => { if (StreamProviderError.isInstance(error) && error.isRetryable) { // Schedule an application-managed retry. } }, }); ``` Providers may not include enough metadata to determine a status code. In that case, `statusCode` is `undefined`, and retryability is classified conservatively. Provider-specific status and retry mappings are supplied by the provider adapter rather than inferred from arbitrary provider error type or code substrings. Provider `type` and `code` values are preserved independently, even when a numeric code is also used to determine `statusCode`. Errors that are already `Error` instances and malformed or unknown provider values are preserved unchanged. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_TooManyEmbeddingValuesForCallError description: Learn how to fix AI_TooManyEmbeddingValuesForCallError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-too-many-embedding-values-for-call-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when too many values are provided in a single embedding call. ## Properties - `provider`: The AI provider name - `modelId`: The ID of the embedding model - `maxEmbeddingsPerCall`: The maximum number of embeddings allowed per call - `values`: The array of values that was provided ## Checking for this Error You can check if an error is an instance of `AI_TooManyEmbeddingValuesForCallError` using: ```typescript import { TooManyEmbeddingValuesForCallError } from 'ai'; if (TooManyEmbeddingValuesForCallError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_ToolCallNotFoundForApprovalError description: Learn how to fix AI_ToolCallNotFoundForApprovalError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-tool-call-not-found-for-approval-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when a tool approval request references a tool call that was not found. This can happen when processing provider-emitted approval requests (e.g., MCP flows) where the referenced tool call ID does not exist. ## Properties - `toolCallId`: The tool call ID that was not found - `approvalId`: The approval request ID ## Checking for this Error You can check if an error is an instance of `AI_ToolCallNotFoundForApprovalError` using: ```typescript import { ToolCallNotFoundForApprovalError } from 'ai'; if (ToolCallNotFoundForApprovalError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: ToolCallRepairError description: Learn how to fix AI SDK ToolCallRepairError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-tool-call-repair-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when there is a failure while attempting to repair an invalid tool call. This typically happens when the AI attempts to fix either a `NoSuchToolError` or `InvalidToolInputError`. ## Properties - `originalError`: The original error that triggered the repair attempt (either `NoSuchToolError` or `InvalidToolInputError`) - `message`: The error message - `cause`: The underlying error that caused the repair to fail ## Checking for this Error You can check if an error is an instance of `ToolCallRepairError` using: ```typescript import { ToolCallRepairError } from 'ai'; if (ToolCallRepairError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: ToolChoiceViolationError description: Learn how to fix AI SDK ToolChoiceViolationError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when a `generateText` response does not satisfy an enforced tool choice. It is thrown when `toolChoice` is set to `'required'` but the response contains no structured tool call, or when a specifically selected tool was not called. The error does not automatically interpret text or reasoning as an executable tool call. You can inspect `content` to implement opt-in recovery, including schema validation before executing any recovered call. ## Properties - `toolChoice`: The effective tool choice that the response did not satisfy - `finishReason`: The reason why the model finished generating the response - `provider`: The provider that returned the response - `modelId`: The model that returned the response - `content`: The normalized content returned by the model - `message`: The error message ## Checking for this Error You can check if an error is an instance of `ToolChoiceViolationError` using: ```typescript import { ToolChoiceViolationError } from 'ai'; if (ToolChoiceViolationError.isInstance(error)) { const serializedCall = error.content.find(part => part.type === 'text')?.text; // Parse and validate serializedCall before treating it as a tool call. } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_TypeValidationError description: Learn how to fix AI_TypeValidationError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-type-validation-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when type validation fails. ## Properties - `value`: The value that failed validation - `cause`: The underlying validation error (required in constructor) ## Checking for this Error You can check if an error is an instance of `AI_TypeValidationError` using: ```typescript import { TypeValidationError } from 'ai'; if (TypeValidationError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_UIMessageStreamError description: Learn how to fix AI_UIMessageStreamError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-ui-message-stream-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when a UI message stream reports an error or contains invalid or out-of-sequence chunks. Common causes: - Receiving an `error` chunk in a completion data stream - Receiving a `text-delta` chunk without a preceding `text-start` chunk - Receiving a `text-end` chunk without a preceding `text-start` chunk - Receiving a `reasoning-delta` chunk without a preceding `reasoning-start` chunk - Receiving a `reasoning-end` chunk without a preceding `reasoning-start` chunk - Receiving a `tool-input-delta` chunk without a preceding `tool-input-start` chunk - Attempting to access a tool invocation that doesn't exist This error often surfaces when an upstream request fails **before any tokens are streamed** and a custom transport tries to write an inline error message to the UI stream without the proper start chunk. ## Properties - `chunkType`: The type of chunk that caused the error (e.g., `text-delta`, `reasoning-end`, `tool-input-delta`) - `chunkId`: The ID associated with the failing chunk (part ID or toolCallId). This is an empty string for chunks, such as completion `error` chunks, that do not have an ID. - `message`: The error message with details about what went wrong ## Checking for this Error You can check if an error is an instance of `AI_UIMessageStreamError` using: ```typescript import { UIMessageStreamError } from 'ai'; if (UIMessageStreamError.isInstance(error)) { console.log('Chunk type:', error.chunkType); console.log('Chunk ID:', error.chunkId); // Handle the error } ``` ## Common Solutions 1. **Ensure proper chunk ordering**: Always send a `*-start` chunk before any `*-delta` or `*-end` chunks for the same ID: ```typescript // Correct order writer.write({ type: 'text-start', id: 'my-text' }); writer.write({ type: 'text-delta', id: 'my-text', delta: 'Hello' }); writer.write({ type: 'text-end', id: 'my-text' }); ``` 2. **Verify IDs match**: Ensure the `id` used in `*-delta` and `*-end` chunks matches the `id` used in the corresponding `*-start` chunk. 3. **Handle error paths correctly**: When writing error messages in custom transports, ensure you emit the full start/delta/end sequence: ```typescript // When handling errors in custom transports writer.write({ type: 'text-start', id: errorId }); writer.write({ type: 'text-delta', id: errorId, delta: 'Request failed...', }); writer.write({ type: 'text-end', id: errorId }); ``` 4. **Check stream producer logic**: Review your streaming implementation to ensure chunks are sent in the correct order, especially when dealing with concurrent operations or merged streams. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI_UnsupportedFunctionalityError description: Learn how to fix AI_UnsupportedFunctionalityError url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors/ai-unsupported-functionality-error" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This error occurs when functionality is not supported. ## Properties - `functionality`: The name of the unsupported functionality - `message`: The error message (optional, auto-generated from `functionality`) ## Checking for this Error You can check if an error is an instance of `AI_UnsupportedFunctionalityError` using: ```typescript import { UnsupportedFunctionalityError } from 'ai'; if (UnsupportedFunctionalityError.isInstance(error)) { // Handle the error } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI SDK Errors description: Reference for AI SDK error classes and typed error handling. url: "https://ai-sdk.dev/docs/reference/ai-sdk-errors" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The AI SDK exposes typed errors so applications can handle expected failure modes without matching error message strings. ## Importing Errors The application package is named `ai`, not `@ai-sdk/ai`. It re-exports common provider-level errors from `@ai-sdk/provider` together with the higher-level errors raised by AI SDK Core: ```typescript import { APICallError, NoObjectGeneratedError } from 'ai'; ``` Provider implementations that depend directly on `@ai-sdk/provider` can import its provider-level errors from that package instead: ```typescript import { APICallError, InvalidResponseDataError } from '@ai-sdk/provider'; ``` ## Migrating to Typed Error Handling Replace message matching or an unconditionally generic `catch` block with the most specific static `isInstance` guard available. Check `AISDKError` last when you need a fallback for any AI SDK error: ```typescript import { AISDKError, APICallError, generateText } from 'ai'; try { await generateText({ model, prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); } catch (error) { if (APICallError.isInstance(error)) { console.error('Provider request failed:', error.statusCode); return; } if (AISDKError.isInstance(error)) { console.error('AI SDK error:', error.name); return; } throw error; } ``` Prefer `ErrorClass.isInstance(error)` over `error instanceof ErrorClass` when the class provides it. The static guard also works when multiple AI SDK package versions are loaded. ## Common Failure Modes | Failure mode | Error to check | | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | A provider request fails because of a network error or non-success response | [`APICallError`](/docs/reference/ai-sdk-errors/ai-api-call-error) | | Automatic retries are exhausted | [`RetryError`](/docs/reference/ai-sdk-errors/ai-retry-error) | | A provider reports an error after a response stream has started | [`StreamProviderError`](/docs/reference/ai-sdk-errors/ai-stream-provider-error) | | A structured output cannot be parsed or validated | [`NoObjectGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-object-generated-error); inspect its `cause` for [`JSONParseError`](/docs/reference/ai-sdk-errors/ai-json-parse-error) or [`TypeValidationError`](/docs/reference/ai-sdk-errors/ai-type-validation-error) | | A generation call returns no usable output | [`NoOutputGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-output-generated-error) or the modality-specific `No*GeneratedError` | | A model calls a missing tool or supplies invalid tool input | [`NoSuchToolError`](/docs/reference/ai-sdk-errors/ai-no-such-tool-error) or [`InvalidToolInputError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-input-error) | | Tool-call repair fails | [`ToolCallRepairError`](/docs/reference/ai-sdk-errors/ai-tool-call-repair-error) | | A response violates an enforced `toolChoice` | [`ToolChoiceViolationError`](/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error) | | Message history contains unresolved tool calls or invalid approvals | [`MissingToolResultsError`](/docs/troubleshooting/missing-tool-results-error), [`InvalidToolApprovalError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-error), or [`InvalidToolApprovalSignatureError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-signature-error) | | A provider or model cannot be resolved | [`NoSuchProviderError`](/docs/reference/ai-sdk-errors/ai-no-such-provider-error), [`NoSuchModelError`](/docs/reference/ai-sdk-errors/ai-no-such-model-error), or [`NoSuchProviderReferenceError`](/docs/reference/ai-sdk-errors/ai-no-such-provider-reference-error) | | A provider response is empty, malformed, or invalid | [`EmptyResponseBodyError`](/docs/reference/ai-sdk-errors/ai-empty-response-body-error), [`JSONParseError`](/docs/reference/ai-sdk-errors/ai-json-parse-error), or [`InvalidResponseDataError`](/docs/reference/ai-sdk-errors/ai-invalid-response-data-error) | | The requested model or provider does not support a capability or specification version | [`UnsupportedFunctionalityError`](/docs/reference/ai-sdk-errors/ai-unsupported-functionality-error) or [`UnsupportedModelVersionError`](/docs/troubleshooting/unsupported-model-version) | | UI messages cannot be converted or a UI message stream is invalid | [`MessageConversionError`](/docs/reference/ai-sdk-errors/ai-message-conversion-error) or [`UIMessageStreamError`](/docs/reference/ai-sdk-errors/ai-ui-message-stream-error) | ## Error Reference ### Base Error - `AISDKError`: Base class and broad type guard for AI SDK errors. ### Provider Requests and Responses - [`APICallError`](/docs/reference/ai-sdk-errors/ai-api-call-error) - [`DownloadError`](/docs/reference/ai-sdk-errors/ai-download-error) - [`EmptyResponseBodyError`](/docs/reference/ai-sdk-errors/ai-empty-response-body-error) - [`InvalidPromptError`](/docs/reference/ai-sdk-errors/ai-invalid-prompt-error) - [`InvalidResponseDataError`](/docs/reference/ai-sdk-errors/ai-invalid-response-data-error) - [`JSONParseError`](/docs/reference/ai-sdk-errors/ai-json-parse-error) - [`LoadAPIKeyError`](/docs/reference/ai-sdk-errors/ai-load-api-key-error) - [`LoadSettingError`](/docs/reference/ai-sdk-errors/ai-load-setting-error) - [`NoContentGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-content-generated-error) - [`NoSuchModelError`](/docs/reference/ai-sdk-errors/ai-no-such-model-error) - [`NoSuchProviderReferenceError`](/docs/reference/ai-sdk-errors/ai-no-such-provider-reference-error) - [`RetryError`](/docs/reference/ai-sdk-errors/ai-retry-error) - [`StreamProviderError`](/docs/reference/ai-sdk-errors/ai-stream-provider-error) - [`TooManyEmbeddingValuesForCallError`](/docs/reference/ai-sdk-errors/ai-too-many-embedding-values-for-call-error) - [`TypeValidationError`](/docs/reference/ai-sdk-errors/ai-type-validation-error) - [`UnsupportedFunctionalityError`](/docs/reference/ai-sdk-errors/ai-unsupported-functionality-error) ### Inputs and Message Streams - [`InvalidArgumentError`](/docs/reference/ai-sdk-errors/ai-invalid-argument-error) - [`InvalidDataContentError`](/docs/reference/ai-sdk-errors/ai-invalid-data-content-error) - [`InvalidMessageRoleError`](/docs/reference/ai-sdk-errors/ai-invalid-message-role-error) - `InvalidStreamPartError` - [`MessageConversionError`](/docs/reference/ai-sdk-errors/ai-message-conversion-error) - [`UIMessageStreamError`](/docs/reference/ai-sdk-errors/ai-ui-message-stream-error) ### Generated Output - [`NoImageGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-image-generated-error) - [`NoObjectGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-object-generated-error) - [`NoOutputGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-output-generated-error) - [`NoSpeechGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-speech-generated-error) - [`NoTranscriptGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-transcript-generated-error) - [`NoTranslationGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-translation-generated-error) - [`NoVideoGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-video-generated-error) ### Providers and Model Compatibility - [`NoSuchProviderError`](/docs/reference/ai-sdk-errors/ai-no-such-provider-error) - [`UnsupportedModelVersionError`](/docs/troubleshooting/unsupported-model-version) ### Tools and Approvals - [`InvalidToolApprovalError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-error) - [`InvalidToolApprovalSignatureError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-approval-signature-error) - [`InvalidToolInputError`](/docs/reference/ai-sdk-errors/ai-invalid-tool-input-error) - [`MissingToolResultsError`](/docs/troubleshooting/missing-tool-results-error) - [`NoSuchToolError`](/docs/reference/ai-sdk-errors/ai-no-such-tool-error) - [`ToolCallNotFoundForApprovalError`](/docs/reference/ai-sdk-errors/ai-tool-call-not-found-for-approval-error) - [`ToolCallRepairError`](/docs/reference/ai-sdk-errors/ai-tool-call-repair-error) - [`ToolChoiceViolationError`](/docs/reference/ai-sdk-errors/ai-tool-choice-violation-error) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI SDK TUI description: "Reference documentation for @ai-sdk/tui" url: "https://ai-sdk.dev/docs/reference/ai-sdk-tui" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [`@ai-sdk/tui`](/docs/agents/terminal-ui) provides a terminal UI for running AI SDK agents locally. It is designed for `ToolLoopAgent` instances that can run from free-form terminal input. #### [runAgentTUI](/docs/reference/ai-sdk-tui/run-agent-tui) Run a ToolLoopAgent in an interactive terminal interface. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: runAgentTUI description: API Reference for the runAgentTUI function. url: "https://ai-sdk.dev/docs/reference/ai-sdk-tui/run-agent-tui" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Runs a local agent or chat transport in an interactive terminal UI. The terminal UI reads user prompts, streams assistant responses, renders markdown, displays tool and reasoning sections, and handles manual tool approvals. `runAgentTUI` runs until the user exits with `Esc` or `Ctrl+C`. ```ts import { openai } from '@ai-sdk/openai'; import { runAgentTUI } from '@ai-sdk/tui'; import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: openai('gpt-6-astra'), instructions: 'You are a helpful terminal assistant.', }); await runAgentTUI({ title: 'Assistant', agent, }); ``` ## Import ``` import { runAgentTUI } from "@ai-sdk/tui" ``` ## API Signature ### Parameters - `options` (`RunAgentTUIOptions`): Options for starting the terminal UI. - `RunAgentTUIOptions` - `agent?` (`AgentTUIAgent`): The agent to run. Provide exactly one of \`agent\` or \`transport\`. The agent must not require per-call options and must not use structured output. - `transport?` (`ChatTransport`): The transport used to communicate with a remote agent. Provide exactly one of \`agent\` or \`transport\`. - `title?` (`string`): The title shown in the terminal UI. If omitted, no title is shown. - `tools?` (`'full' | 'collapsed' | 'auto-collapsed' | 'hidden'`): Controls how tool call sections are displayed. Defaults to \`auto-collapsed\`. - `reasoning?` (`'full' | 'collapsed' | 'auto-collapsed' | 'hidden'`): Controls how reasoning sections are displayed. Defaults to \`auto-collapsed\`. - `responseStatistics?` (`'outputTokenCount' | 'outputTokensPerSecond'`): Controls which response statistic is shown in response headers. Defaults to \`outputTokensPerSecond\`. - `contextSize?` (`number`): The model context window size in tokens. When provided, the terminal UI shows total token usage as a percentage of this context window. - `sandbox?` (`Experimental_SandboxSession`): Sandbox session that is passed through to the agent as \`experimental\_sandbox\` on every call. ### Returns - `returns` (`Promise`): A promise that resolves when the terminal UI exits. ## Types ### `AgentTUIAgent` An agent that is compatible with the terminal UI: ```ts type AgentTUIAgent = Agent; ``` This means the agent has no per-call options and no structured output. ### `TerminalPartDisplayMode` Controls how terminal sections are displayed: ```ts type TerminalPartDisplayMode = | 'full' | 'collapsed' | 'auto-collapsed' | 'hidden'; ``` - `"full"`: Show the section header and full content. - `"collapsed"`: Show only the section header. - `"auto-collapsed"`: Show the latest section expanded until another visible section appears, then collapse it. - `"hidden"`: Omit the section entirely. ### `ResponseStatisticsMode` Controls which response statistic is shown: ```ts type ResponseStatisticsMode = 'outputTokenCount' | 'outputTokensPerSecond'; ``` - `"outputTokenCount"`: Show the number of output tokens in the response. - `"outputTokensPerSecond"`: Show output token throughput for the response. ## Example with Tool Display Options ```ts await runAgentTUI({ title: 'Assistant', agent, tools: 'auto-collapsed', reasoning: 'collapsed', responseStatistics: 'outputTokenCount', contextSize: 200_000, }); ``` ## Example with a Chat Transport ```ts import { DefaultChatTransport } from 'ai'; await runAgentTUI({ title: 'Remote Assistant', transport: new DefaultChatTransport({ api: 'https://example.com/api/chat', }), }); ``` ## Example with Sandbox ```ts import { createJustBashNetworkSandboxSession } from '@ai-sdk/sandbox-just-bash'; const sandboxSession = await createJustBashNetworkSandboxSession({ cwd: '/home/user', }); try { await runAgentTUI({ title: 'Sandbox Assistant', agent, sandbox: sandboxSession.restricted(), }); } finally { await sandboxSession.destroy(); } ``` The sandbox is forwarded to every `agent.stream()` call as `experimental_sandbox`, making it available to tool description functions and tool `execute` functions. Include the sandbox description in the agent instructions when the model should know sandbox-specific details such as the working directory or exposed ports. ## Compatibility Use the `agent` option for agents that can run directly from free-form user input. Use the `transport` option to communicate with a remote agent. Use `agent.generate()` or `agent.stream()` directly when you need fixed prompts, per-call options, structured output, custom result inspection, or custom stream processing. ## Related - [Terminal UI guide](/docs/agents/terminal-ui) - [Building Agents](/docs/agents/building-agents) - [ToolLoopAgent guide](/docs/agents/overview#toolloopagent-class) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: generateVideo description: API Reference for durable video generation in workflows. url: "https://ai-sdk.dev/docs/reference/ai-sdk-workflow/generate-video" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Generates videos durably inside a workflow. The helper starts an asynchronous video generation job with a Workflow webhook URL, suspends the workflow until the provider sends a terminal notification, and then retrieves the completed result with one status request. Unlike [`experimental_generateVideo`](/docs/reference/ai-sdk-core/generate-video) from `ai`, this helper does not download provider-hosted videos. URL results remain URLs so your workflow can decide whether to persist, copy, or process them in another step without serializing the video bytes through workflow step boundaries. ```ts import { experimental_generateVideo as generateVideo } from '@ai-sdk/workflow/video'; export async function videoWorkflow(prompt: string) { 'use workflow'; // The workflow suspends while the video renders, consuming no compute. const result = await generateVideo({ model: 'klingai/kling-v3.0-t2v', prompt, }); return result.videos; } ``` The selected model must support asynchronous start and status operations and provider webhooks. The helper must be called from a Workflow SDK workflow. ## Import ``` import { experimental_generateVideo as generateVideo } from "@ai-sdk/workflow/video" ``` ## Parameters The helper accepts the same parameters as `experimental_startVideo` from `ai`, except `webhookUrl` and `abortSignal`. It creates and manages the webhook URL. - `model` (`VideoModel`): The asynchronous video model to use. A string compatible with Vercel AI Gateway or a serializable provider model. - `prompt` (`string | GenerateVideoPrompt`): The prompt or image prompt used to generate the video. - `n?` (`number`): Number of videos to generate. Default: 1. - `aspectRatio?` (`` `${number}:${number}` | "adaptive" ``): Aspect ratio of the generated videos. - `resolution?` (`` `${number}x${number}` ``): Resolution of the generated videos. - `duration?` (`number`): Duration of each generated video in seconds. - `maxVideosPerCall?` (`number`): Maximum number of videos that may be started in one call. - `fps?` (`number`): Frames per second for the generated videos. - `seed?` (`number`): Seed used for deterministic generation when supported. - `frameImages?` (`Array<{ image: DataContent; frameType: VideoModelV4FrameType }>`): Role-tagged first and last frame images. - `inputReferences?` (`Array`): Reference images or videos for the generation. - `generateAudio?` (`boolean`): Whether to generate audio with the video. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options. - `headers?` (`Record`): Additional HTTP headers for start and status requests. - `maxRetries?` (`number`): Maximum number of AI SDK retries for each start and status request. Default: 2. ## Returns Returns the completed status result from the provider. `videos` contains raw provider video data discriminated by `type`: - `url`: A provider-hosted URL and media type - `base64`: Base64-encoded video data and media type - `binary`: A `Uint8Array` and media type Hosted URLs can expire. Handle any video you need to retain in a separate workflow step. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI SDK Workflow description: "Reference documentation for @ai-sdk/workflow" url: "https://ai-sdk.dev/docs/reference/ai-sdk-workflow" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [`@ai-sdk/workflow`](/docs/agents/workflow-agent) provides the `WorkflowAgent` class for building durable, resumable AI agents that run inside a workflow. It handles tool schema serialization, workflow step boundaries, and built-in tool approval flows. #### [WorkflowAgent](/docs/reference/ai-sdk-workflow/workflow-agent) Create durable AI agents with tool calling, streaming, and workflow integration. #### [WorkflowChatTransport](/docs/reference/ai-sdk-workflow/workflow-chat-transport) Chat transport with automatic stream reconnection for workflow-based apps. #### [generateVideo](/docs/reference/ai-sdk-workflow/generate-video) Generate videos durably with provider webhooks and no implicit downloads. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: WorkflowAgent description: API Reference for the WorkflowAgent class. url: "https://ai-sdk.dev/docs/reference/ai-sdk-workflow/workflow-agent" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Creates a durable, resumable AI agent for use inside a workflow. `WorkflowAgent` handles the agent loop, tool schema serialization across workflow step boundaries, and built-in tool approval flows. Unlike [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent) from the `ai` package, `WorkflowAgent` is designed to survive process restarts, pause for human approval, and integrate with the Workflow DevKit's step mechanism. `WorkflowAgent` supports `runtimeContext` for shared agent state and `toolsContext` for per-tool context. Because these values can cross workflow and step boundaries, keep them serializable durable data. Unlike `ToolLoopAgent`, do not place functions, class instances, symbols, database clients, or SDK clients in context; pass identifiers or configuration and recreate non-serializable resources inside step functions. ```ts import { WorkflowAgent } from '@ai-sdk/workflow'; import { tool } from 'ai'; import { z } from 'zod'; const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', instructions: 'You are a helpful assistant.', tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string(), }), execute: async ({ location }) => ({ location, temperature: 72, }), }), }, }); const result = await agent.stream({ messages: [ { role: 'user', content: [{ type: 'text', text: 'What is the weather in NYC?' }], }, ], }); console.log(result.messages); ``` To see `WorkflowAgent` in action, check out [these examples](#examples). ## Import ``` import { WorkflowAgent } from "@ai-sdk/workflow" ``` ## Constructor ### Parameters - `id?` (`string`): The id of the agent. - `model` (`LanguageModel`): The language model to use. A string compatible with the Vercel AI Gateway (e.g., 'anthropic/claude-sonnet-5.5') or a provider instance (e.g., \`openai('gpt-6-astra')\`). - `instructions?` (`Instructions`): Instructions for the agent, used as the system prompt. Supports provider-specific options (e.g., caching) when using the SystemModelMessage form. - `tools?` (`Record`): A set of tools the agent can call. Keys are tool names. Tools are serialized to JSON Schema across workflow step boundaries and validated with Ajv at runtime. - `toolChoice?` (`ToolChoice`): Tool call selection strategy. Options: 'auto' | 'none' | 'required' | \{ type: 'tool', toolName: string }. Default: 'auto'. - `stopWhen?` (`StopCondition | StopCondition[]`): Default stop condition for the agent loop. When omitted, generate defaults to 20 steps, while stream has no maximum step count. Use \`isStepCount()\` to bound execution. Per-call values override this default. - `toolCallConcurrency?` (`number`): Maximum number of local tool calls executing concurrently within a step, including approved calls. Must be a positive integer. Set to 1 for sequential execution in model order. Defaults to unlimited concurrency. Can be changed by prepareCall; explicit generate/stream call options take precedence. Provider-executed tools are unaffected. - `activeTools?` (`ActiveTools`): Default set of active tools. Limits which tools the model can call without changing tool call and result types. Per-stream values override this default. - `output?` (`OutputSpecification`): Default structured output specification. Per-stream values override this default. - `repairToolCall?` (`ToolCallRepairFunction`): Default function to repair tool calls that fail to parse. Mark the function with 'use step' when used in a workflow. Per-stream values override this default. - `experimental_download?` (`DownloadFunction`): Default custom download function for URLs. Mark the function with 'use step' when used in a workflow. Per-stream values override this default. - `experimental_sandbox?` (`Experimental_SandboxSession`): Default sandbox session passed to tool descriptions and execution as \`experimental\_sandbox\`, and exposed to \`prepareStep\`. Per-stream values override this default. - `experimental_toolApprovalSecret?` (`WorkflowToolApprovalSecret`): Workflow-safe reference to the environment variable containing the secret used to HMAC-sign tool approval requests and verify approved message history before tool execution. Only the environment variable name crosses workflow boundaries; the secret is read inside signing and verification steps. Per-stream values override this default. - `prepareStep?` (`PrepareStepCallback`): Callback called before each step in the agent loop. Use it to modify settings, manage context, inject messages dynamically, or override \`experimental\_sandbox\` for the current step. Receives step number, previous steps, messages, context, and sandbox. - `prepareCall?` (`PrepareCallCallback`): Callback called once before the agent loop starts. Use it to transform model, instructions, tools configuration, or other settings based on runtime context. Cannot override \`tools\` (bound at construction for type safety). - `runtimeContext?` (`Context`): Default shared runtime context for every stream call. Flows through \`prepareStep\`, lifecycle callbacks, and step results. Per-stream values override this default. Must be serializable when used in workflows. - `toolsContext?` (`InferToolSetContext`): Default per-tool context map for every stream call. Each tool receives only its own validated entry as \`context\`. Per-stream values override this default. Must be serializable when used in workflows. - `telemetry?` (`TelemetryOptions`): Telemetry configuration with options for enabling/disabling telemetry, setting a function ID, and recording inputs/outputs. - `onStart?` (`WorkflowAgentOnStartCallback`): Callback called when the agent starts streaming, before any LLM calls. Receives the model, messages, runtime context, and tools context. If also specified in \`stream()\`, both callbacks fire (constructor first). Takes precedence over \`experimental\_onStart\` when both are provided in the constructor. - `GenerateTextStartEvent` - `model` (`LanguageModel`): The model being used for the generation. - `messages` (`Array`): The messages being sent to the model. - `runtimeContext` (`Context`): The shared runtime context for the agent loop. - `toolsContext` (`InferToolSetContext`): The per-tool context map for the agent loop. - `experimental_onStart?` (`WorkflowAgentOnStartCallback`): Deprecated alias for \`onStart\`. Used only when \`onStart\` is not provided in the constructor. - `onStepStart?` (`WorkflowAgentOnStepStartCallback`): Callback called before each step (LLM call) begins. Receives step number, model, messages, previous steps, runtime context, and tools context. If also specified in \`stream()\`, both callbacks fire (constructor first). Takes precedence over \`experimental\_onStepStart\` when both are provided in the constructor. - `GenerateTextStepStartEvent` - `model` (`LanguageModel`): The model being used for this step. - `messages` (`Array`): The messages that will be sent to the model for this step. - `steps` (`ReadonlyArray`): Results from all previously finished steps. - `runtimeContext` (`Context`): The shared runtime context for this step. - `toolsContext` (`InferToolSetContext`): The per-tool context map for this step. - `experimental_onStepStart?` (`WorkflowAgentOnStepStartCallback`): Deprecated alias for \`onStepStart\`. Used only when \`onStepStart\` is not provided in the constructor. - `onToolExecutionStart?` (`WorkflowAgentOnToolExecutionStartCallback`): Callback called right before a tool's execute function runs. If also specified in \`stream()\`, both callbacks fire (constructor first). Experimental (can break in patch releases). - `WorkflowAgentToolExecutionStartEvent` - `toolCall` (`{ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }`): The tool call being executed. For concrete tool sets, the tool name and input are correlated. - `stepNumber` (`number`): The current step number, starting at zero. - `messages` (`Array`): Messages that were sent to the language model to initiate the response that contained the tool call. - `toolContext` (`InferToolContext | undefined`): The validated context for the tool call. For concrete tool sets, each event union member pairs it with the corresponding tool name. - `onToolExecutionEnd?` (`WorkflowAgentOnToolExecutionEndCallback`): Callback called right after a tool's execute function completes or errors. Check \`success\` to determine whether \`output\` or \`error\` is available. If also specified in \`stream()\`, both callbacks fire (constructor first). Experimental (can break in patch releases). - `WorkflowAgentToolExecutionEndEvent` - `toolCall` (`{ type: "tool-call"; toolCallId: string; toolName: string; input: unknown }`): The tool call that was executed. For concrete tool sets, the tool name and input are correlated. - `stepNumber` (`number`): The current step number, starting at zero. - `durationMs` (`number`): Tool execution time in milliseconds. Workflow agents use \`durationMs\`; AI SDK Core generation callbacks use \`toolExecutionMs\`. - `messages` (`Array`): Messages that were sent to the language model to initiate the response that contained the tool call. - `toolContext` (`InferToolContext | undefined`): The validated context for the tool call. For concrete tool sets, each event union member pairs it with the corresponding tool name. - `success` (`boolean`): Whether the tool execution succeeded. Discriminates between the output and error event variants. - `output?` (`InferToolOutput`): The tool output. Available when \`success\` is \`true\` and correlated with the configured tool. - `error?` (`unknown`): The tool execution error. Available when \`success\` is \`false\`. - `onStepEnd?` (`WorkflowAgentOnStepEndCallback`): Callback invoked after each agent step completes. If also specified in \`stream()\`, both callbacks fire (constructor first). - `onStepFinish?` (`WorkflowAgentOnStepFinishCallback`): Deprecated. Use \`onStepEnd\` instead. This alias is only used as a fallback when \`onStepEnd\` is not provided. - `onEnd?` (`WorkflowAgentOnEndCallback`): Callback called when all agent steps are finished and the response is complete. Receives steps, messages, text, finish reason, total usage, and context. If also specified in \`stream()\`, both callbacks fire (constructor first). - `maxOutputTokens?` (`number`): Maximum number of tokens the model is allowed to generate. - `temperature?` (`number`): Sampling temperature, controls randomness. - `topP?` (`number`): Top-p (nucleus) sampling parameter. - `topK?` (`number`): Top-k sampling parameter. - `presencePenalty?` (`number`): Presence penalty parameter. - `frequencyPenalty?` (`number`): Frequency penalty parameter. - `stopSequences?` (`string[]`): Custom token sequences which stop the model output. - `seed?` (`number`): Seed for deterministic generation (if supported). - `maxRetries?` (`number`): How many times to retry retryable model-call failures. Set to 0 to disable retries. Retry-After response headers are respected, and durable workflow step retries are not stacked. Default: 2. - `headers?` (`Record`): Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers. - `providerOptions?` (`ProviderOptions`): Additional provider-specific configuration. ## Properties - `id` (`string | undefined`): The id of the agent. Used for telemetry identification. Read-only. - `tools` (`Record`): The tool set configured for this agent. Read-only. ## Methods ### `generate()` Runs the durable agent loop using the model's non-streaming `doGenerate()` method. Model calls and tool execution use the same orchestration as `stream()`. ```ts const result = await agent.generate({ prompt: 'Summarize the findings.' }); console.log(result.text); console.log(result.usage); ``` Accepts `WorkflowAgentGenerateOptions`, sharing prompt/messages, instructions, generation settings, tools and runtime context, preparation, stopping, output, and lifecycle options with `stream()`. It does not accept `writable`, `includeRawChunks`, `experimental_transform`, `sendFinish`, or `preventClose`. Without a configured stop condition, generation stops after at most 20 steps. `stream()` retains its existing unbounded default. The `include` option accepts `requestBody`, `responseBody`, and `requestMessages` booleans, all defaulting to `false`. Enable them only when the additional request/response data is needed. Response IDs, timestamps, model IDs, headers, and provider metadata are retained when supplied by the model. #### Returns Returns `Promise` with the core [`GenerateTextResult`](/docs/reference/ai-sdk-core/generate-text#returns) semantics: - `text`, `finishReason`, `rawFinishReason`, and `finalStep` describe the final step. - `content`, `files`, `sources`, `warnings`, tool calls, and tool results cover all steps. - `usage` and `totalUsage` sum all steps while retaining token details and unknown counts. - `responseMessages` includes generated messages, even when `prepareStep` replaces the input history. - `output` defaults to text, or follows the constructor/per-call output specification. Reading it throws `NoOutputGeneratedError` when output is unavailable, such as a final `tool-calls` response. Inside a workflow, `timeout` bounds model calls, including retries and dispatch after suspension. It does not cancel a hook that a tool is waiting on. Use the Workflow runtime’s `run.cancel()` to stop a suspended run. See [deadlines, cancellation, and replay](/docs/agents/workflow-agent#deadlines-cancellation-and-replay). Generation failures reject. The end callback runs before complete-output parsing; a parsing failure can therefore reject after `onEnd` has run. Existing Workflow callback shapes and preparation precedence apply; they are not all identical to core's callback APIs. Use the result API inside workflow code and return selected serializable fields (for example, `text` and `usage`) through `run.returnValue`. Returning the whole result class is not supported. Performance data currently measures the model response; complete tool/step timing is not yet reported. Approval requests appear in `content` and `responseMessages` without requiring a writable. To continue, append `responseMessages` to the original history and add a tool message containing the user's `tool-approval-response`. Preserve the request signature and tool-call input; use the same `experimental_toolApprovalSecret` environment-variable reference on continuation. Local approvals are validated before execution, and provider-executed approval responses are forwarded to the provider. See [approval round trips](/docs/agents/workflow-agent#approval-round-trips-without-streaming). ### `stream()` Runs the agent loop, streaming responses and executing tool calls as needed. Returns a promise resolving to a `WorkflowAgentStreamResult`. ```ts const result = await agent.stream({ messages: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }], }); ``` - `prompt` (`string | Array`): A prompt string or a list of messages. You can either use \`prompt\` or \`messages\` but not both. - `messages` (`Array`): The conversation messages to process. You can either use \`prompt\` or \`messages\` but not both. - `writable?` (`WritableStream`): A writable stream that receives raw model stream parts in real-time. Convert to UI message chunks at the response boundary using \`createModelCallToUIChunkTransform()\`. - `instructions?` (`Instructions`): Override the agent instructions for this call. - `system?` (`string`): Deprecated. Use \`instructions\` instead. - `stopWhen?` (`StopCondition | StopCondition[]`): Condition(s) for ending the agent loop. When omitted and no constructor-level condition is configured, WorkflowAgent has no maximum step count and continues until natural completion. Use \`isStepCount()\` to bound execution. - `toolChoice?` (`ToolChoice`): Override the tool choice strategy for this call. Default: 'auto'. - `toolCallConcurrency?` (`number`): Override the local tool call concurrency limit for this call. Must be a positive integer. Set to 1 for sequential execution in model order, including approved calls. Takes precedence over the constructor and prepareCall. - `activeTools?` (`ActiveTools`): Limits the subset of tools available for this call without changing tool call and result types. - `output?` (`OutputSpecification`): Structured output specification. Use \`Output.object(\{ schema })\` for typed objects or \`Output.text()\` for text. - `timeout?` (`number`): Model-call deadline in milliseconds. Inside a workflow, the absolute deadline is checked at dispatch and retry and remains in effect across suspension. It does not cancel a tool waiting on a Workflow hook; use run.cancel() to stop the suspended run. - `sendFinish?` (`boolean`): Whether to send a 'finish' chunk to the writable stream when streaming completes. Default: true. - `preventClose?` (`boolean`): Whether to prevent the writable stream from being closed after streaming completes. Default: false. - `includeRawChunks?` (`boolean`): Include raw, unprocessed chunks from the provider in the stream. Default: false. - `repairToolCall?` (`ToolCallRepairFunction`): Callback to attempt automatic recovery when a tool call cannot be parsed. Mark the function with 'use step' when used in a workflow. - `experimental_transform?` (`StreamTextTransform | Array`): Factories for stream transformations, applied in order to raw provider events before model processing and writable output. Each factory must return a TransformStream synchronously and maintain the stream structure. Mark each factory with 'use step' when used in a workflow. - `experimental_download?` (`DownloadFunction`): Custom download function for fetching files/URLs. Mark the function with 'use step' when used in a workflow. - `experimental_sandbox?` (`Experimental_SandboxSession`): Sandbox session passed to tool descriptions and execution as \`experimental\_sandbox\`, and exposed to \`prepareStep\`. Overrides the constructor default. - `experimental_toolApprovalSecret?` (`WorkflowToolApprovalSecret`): Workflow-safe reference to the environment variable containing the secret used to HMAC-sign tool approval requests and verify approved message history before tool execution. Only the environment variable name crosses workflow boundaries; the secret is read inside signing and verification steps. Overrides the constructor default. - `telemetry?` (`TelemetryOptions`): Per-call telemetry configuration. - `runtimeContext?` (`Context`): Shared runtime context for this stream call. Overrides the constructor default and flows through \`prepareStep\`, lifecycle callbacks, and step results. Must be serializable when used in workflows. - `toolsContext?` (`InferToolSetContext`): Per-tool context map for this stream call. Overrides the constructor default. Each tool receives only its own validated entry as \`context\`. Must be serializable when used in workflows. - `prepareStep?` (`PrepareStepCallback`): Per-call prepareStep override. Receives the initial instructions and messages alongside the current step state. - `onStart?` (`WorkflowAgentOnStartCallback`): Per-call onStart callback. If also specified in the constructor, both fire (constructor first). Takes precedence over \`experimental\_onStart\` when both are provided in this call. - `experimental_onStart?` (`WorkflowAgentOnStartCallback`): Deprecated alias for the per-call \`onStart\` callback. Used only when \`onStart\` is not provided in this call. - `onStepStart?` (`WorkflowAgentOnStepStartCallback`): Per-call onStepStart callback. If also specified in the constructor, both fire (constructor first). Takes precedence over \`experimental\_onStepStart\` when both are provided in this call. - `experimental_onStepStart?` (`WorkflowAgentOnStepStartCallback`): Deprecated alias for the per-call \`onStepStart\` callback. Used only when \`onStepStart\` is not provided in this call. - `onToolExecutionStart?` (`WorkflowAgentOnToolExecutionStartCallback`): Per-call onToolExecutionStart callback. If also specified in the constructor, both fire (constructor first). - `onToolExecutionEnd?` (`WorkflowAgentOnToolExecutionEndCallback`): Per-call onToolExecutionEnd callback. If also specified in the constructor, both fire (constructor first). - `onStepEnd?` (`WorkflowAgentOnStepEndCallback`): Per-call onStepEnd callback. If also specified in the constructor, both fire (constructor first). - `onStepFinish?` (`WorkflowAgentOnStepFinishCallback`): Deprecated. Use \`onStepEnd\` instead. This alias is only used as a fallback when \`onStepEnd\` is not provided. - `onEnd?` (`WorkflowAgentOnEndCallback`): Per-call onEnd callback. If also specified in the constructor, both fire (constructor first). - `onError?` (`WorkflowAgentOnErrorCallback`): Callback invoked when an error occurs during streaming. - `onAbort?` (`WorkflowAgentOnAbortCallback`): Callback invoked when the operation is aborted. Receives all previously finished steps. #### Returns Returns a `Promise` with the following properties: - `messages` (`Array`): The final messages including all tool calls and results. - `steps` (`Array`): Details for all steps taken by the agent. - `toolCalls` (`Array`): Tool calls from the last step, including unexecuted calls (e.g., tools requiring approval). - `toolResults` (`Array`): Tool results from the last step. Only includes results for tools that were executed. - `error` (`unknown | undefined`): The original value from a model stream error part. The property is present when an error part was emitted, even if its value is undefined; use \`'error' in result\` to distinguish that case. - `output` (`OUTPUT`): The structured output if an \`output\` specification was provided. ### `repairToolCall` in a workflow `repairToolCall` runs inside the model step. When you use it in a workflow, define the callback as a function with a `'use step'` directive. You can set it on the constructor or pass it to `stream()`. Workflow saves a reference to the registered function and loads its implementation inside the model step. Keep any values captured by the function serializable. An ordinary inline callback causes a serialization error when it crosses this step boundary. The callback can be asynchronous. Return the repaired tool call, or `null` if the repair cannot be completed. See [tool call repair](/docs/ai-sdk-core/tools-and-tool-calling#tool-call-repair) for repair strategies. ### `experimental_transform` in a workflow Pass a transform factory, or an array of factories, to `stream()`. Mark each factory with `'use step'` so Workflow can load it inside the model step. The factory must return a `TransformStream` synchronously. Keep any captured values serializable. ```ts import type { LanguageModelV4StreamPart } from '@ai-sdk/provider'; import { WorkflowAgent } from '@ai-sdk/workflow'; import { getWritable } from 'workflow'; function uppercaseText() { 'use step'; return new TransformStream< LanguageModelV4StreamPart, LanguageModelV4StreamPart >({ transform(part, controller) { controller.enqueue( part.type === 'text-delta' ? { ...part, delta: part.delta.toUpperCase() } : part, ); }, }); } export async function uppercaseWorkflow() { 'use workflow'; const agent = new WorkflowAgent({ model: 'openai/gpt-5' }); return agent.stream({ messages: [{ role: 'user', content: 'Write a short greeting.' }], writable: getWritable(), experimental_transform: uppercaseText, }); } ``` ## Utilities ### `createModelCallToUIChunkTransform(options?)` Creates a `TransformStream` that converts raw `ModelCallStreamPart` chunks (written by the agent to the `writable` stream) into `UIMessageChunk` objects suitable for client consumption. ```ts import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow'; return createUIMessageStreamResponse({ stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()), }); ``` When resuming with a `WorkflowChatTransport` cursor, replay the raw workflow stream from index `0` and pass the non-negative UI chunk index to the transform: ```ts const readable = run .getReadable({ startIndex: 0 }) .pipeThrough(createModelCallToUIChunkTransform({ uiStartIndex: startIndex })); ``` `uiStartIndex` must be a non-negative safe integer. Raw model stream parts and UI message chunks are not one-to-one, so do not pass a UI chunk index to `getReadable`. Negative tail indexes require a durable stream that already stores `UIMessageChunk` objects. ### `toUIMessageChunk()` Converts a single `ModelCallStreamPart` to a `UIMessageChunk`. Returns `undefined` for parts that don't map to UI chunks. ```ts import { toUIMessageChunk } from '@ai-sdk/workflow'; const uiChunk = toUIMessageChunk(modelCallPart); ``` ## Types ### `ActiveTools` ```ts type ActiveTools = | ReadonlyArray | undefined; ``` Limits a workflow agent call to the listed tool names. `undefined` means no tool restriction is applied. ### `InferWorkflowAgentUIMessage` Infers the UI message type for a `WorkflowAgent` instance. Optionally accepts a second type argument for custom message metadata. ```ts import { WorkflowAgent, InferWorkflowAgentUIMessage } from '@ai-sdk/workflow'; const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', tools: { weather: weatherTool }, }); type MyAgentUIMessage = InferWorkflowAgentUIMessage; ``` ### `InferWorkflowAgentTools` Infers the tool set type of a `WorkflowAgent` instance. ```ts import { WorkflowAgent, InferWorkflowAgentTools } from '@ai-sdk/workflow'; type MyTools = InferWorkflowAgentTools; ``` ## Examples ### Basic Agent with Tools ```ts import { WorkflowAgent } from '@ai-sdk/workflow'; import { tool } from 'ai'; import { z } from 'zod'; const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', instructions: 'You are a helpful assistant.', tools: { weather: tool({ description: 'Get weather for a location', inputSchema: z.object({ location: z.string(), }), execute: async ({ location }) => ({ location, temperature: 72, condition: 'sunny', }), }), }, }); const result = await agent.stream({ messages: [ { role: 'user', content: [{ type: 'text', text: 'What is the weather in NYC?' }], }, ], }); console.log(result.messages); console.log(result.steps); ``` ### Agent in a Workflow with Durable Tools ```ts title="workflow/agent-chat.ts" import { WorkflowAgent, type ModelCallStreamPart } from '@ai-sdk/workflow'; import { convertToModelMessages, tool, type UIMessage } from 'ai'; import { getWritable } from 'workflow'; import { z } from 'zod'; // Tool execute functions marked with 'use step' become durable workflow steps // with automatic retries and persistence async function searchFlightsStep(input: { origin: string; destination: string; }) { 'use step'; const response = await fetch(`https://api.flights.example/search?...`); return response.json(); } export async function chat(messages: UIMessage[]) { 'use workflow'; const modelMessages = await convertToModelMessages(messages); const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', instructions: 'You are a flight booking assistant.', tools: { searchFlights: tool({ description: 'Search for available flights', inputSchema: z.object({ origin: z.string(), destination: z.string(), }), execute: searchFlightsStep, }), }, }); const result = await agent.stream({ messages: modelMessages, writable: getWritable(), }); return { messages: result.messages }; } ``` ```ts title="app/api/chat/route.ts" import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow'; import { createUIMessageStreamResponse, type UIMessage } from 'ai'; import { start } from 'workflow/api'; import { chat } from '@/workflow/agent-chat'; export async function POST(request: Request) { const { messages }: { messages: UIMessage[] } = await request.json(); const run = await start(chat, [messages]); return createUIMessageStreamResponse({ stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()), }); } ``` ### Agent with Structured Output ```ts import { WorkflowAgent, Output } from '@ai-sdk/workflow'; import { z } from 'zod'; const analysisAgent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', }); const result = await analysisAgent.stream({ messages: [ { role: 'user', content: [ { type: 'text', text: 'Analyze: "The product exceeded my expectations!"', }, ], }, ], output: Output.object({ schema: z.object({ sentiment: z.enum(['positive', 'negative', 'neutral']), score: z.number(), summary: z.string(), }), }), }); console.log(result.output); // { sentiment: 'positive', score: 9, summary: '...' } ``` ### Agent with Tool Approval For `WorkflowAgent`, tool approval is configured on the tool definition with `needsApproval`. For `generateText`, `streamText`, and `ToolLoopAgent`, use `toolApproval` instead. ```ts import { WorkflowAgent } from '@ai-sdk/workflow'; import { tool } from 'ai'; import { z } from 'zod'; const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', experimental_toolApprovalSecret: { environmentVariable: 'TOOL_APPROVAL_SECRET', }, tools: { bookFlight: tool({ description: 'Book a flight', inputSchema: z.object({ flightId: z.string(), passengerName: z.string(), }), needsApproval: true, // Pauses the agent until user approves execute: bookFlightStep, }), }, }); ``` When `experimental_toolApprovalSecret` is configured, each approval request is signed over its approval ID, tool call ID, tool name, and validated input. Replayed approvals with a missing or invalid signature do not execute the tool. The signature is preserved in the durable stream and UI message history, while only the environment variable name crosses workflow boundaries. Signing and verification steps read the raw secret from their local environment and do not serialize it. A stream-level reference overrides the constructor value. ### Agent with Lifecycle Callbacks ```ts import { WorkflowAgent } from '@ai-sdk/workflow'; const agent = new WorkflowAgent({ model: 'anthropic/claude-sonnet-5.5', tools: { weather: weatherTool }, // Agent-wide callbacks onStepEnd({ usage }) { console.log('Tokens used:', usage.totalTokens); }, }); const result = await agent.stream({ messages, // Per-call callbacks (both fire) async onStepEnd({ usage }) { await trackUsage(usage); }, onEnd({ steps, totalUsage }) { console.log( `Done in ${steps.length} steps, ${totalUsage.totalTokens} tokens`, ); }, }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: WorkflowChatTransport description: API Reference for the WorkflowChatTransport class. url: "https://ai-sdk.dev/docs/reference/ai-sdk-workflow/workflow-chat-transport" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). A [`ChatTransport`](/docs/ai-sdk-ui/transport) implementation for [`useChat`](/docs/reference/ai-sdk-ui/use-chat) that enables automatic stream reconnection for workflow-based chat apps. It posts messages to a chat endpoint, extracts the `x-workflow-run-id` response header, and reconnects to a `/{runId}/stream` endpoint on interruption (network failures, page refreshes, function timeouts). Unlike [`DefaultChatTransport`](/docs/ai-sdk-ui/transport) which assumes the full response arrives in a single HTTP request, `WorkflowChatTransport` is designed for the [Workflow SDK](https://vercel.com/docs/workflow) where the initial response stream may be interrupted by function timeouts. The transport automatically detects missing `finish` events and reconnects to resume from where the stream left off. ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import { WorkflowChatTransport } from '@ai-sdk/workflow/client'; export default function Chat() { const { messages, sendMessage } = useChat({ transport: new WorkflowChatTransport({ api: '/api/chat', maxConsecutiveErrors: 5, }), }); // ... render chat UI } ``` ## Import ``` import { WorkflowChatTransport } from "@ai-sdk/workflow/client" ``` ## Constructor ### Parameters - `api?` (`string`): API endpoint for chat requests. The reconnection endpoint is derived from this as \`\{api}/\{runId}/stream\`. Default: '/api/chat'. - `fetch?` (`typeof fetch`): Custom fetch implementation to use for HTTP requests. Default: global fetch. - `maxConsecutiveErrors?` (`number`): Maximum number of consecutive errors allowed during reconnection attempts before giving up. Streams that end without receiving any UI message chunks count as errors. Default: 3. - `initialStartIndex?` (`number`): Default chunk index to start from when reconnecting. Negative values read from the end of a durable UIMessageChunk stream (e.g., -50 fetches the last 50 chunks), useful for resuming after a page refresh without replaying the full conversation. Raw ModelCallStreamPart streams do not support negative UI chunk indexes. Can be overridden per-call via reconnectToStream options. Default: 0. - `onChatSendMessage?` (`(response: Response, options: SendMessagesOptions) => void | Promise`): Callback invoked after the initial POST request succeeds. Useful for inspecting response headers (e.g., extracting workflow run ID) or tracking chat history on the client side. - `onChatEnd?` (`({ chatId, chunkIndex }) => void | Promise`): Callback invoked when the stream ends (receives a finish chunk). Receives the chat ID and total chunk count. Useful for cleanup or state updates. - `prepareSendMessagesRequest?` (`PrepareSendMessagesRequest`): Function to customize the POST request before sending. Can override the API endpoint, headers, credentials, and body. - `prepareReconnectToStreamRequest?` (`PrepareReconnectToStreamRequest`): Function to customize the reconnection GET request. Can override the API endpoint, headers, and credentials. ## Methods ### `sendMessages()` Sends messages to the chat endpoint via POST and returns a streaming response. If the stream is interrupted (no `finish` event received), the transport automatically reconnects via GET to `{api}/{runId}/stream?startIndex={chunkIndex}` to resume from where it left off. The POST request includes the messages as JSON and expects the response to include an `x-workflow-run-id` header identifying the workflow run. ```ts const stream = await transport.sendMessages({ chatId: 'chat-123', trigger: 'submit-message', messages: [...], abortSignal: controller.signal, }); ``` - `chatId` (`string`): Unique identifier for the chat session. - `trigger` (`'submit-message' | 'regenerate-message'`): The type of message submission. - `messageId` (`string | undefined`): ID of the message to regenerate, or undefined for new messages. - `messages` (`UIMessage[]`): Array of UI messages representing the conversation history. - `abortSignal` (`AbortSignal | undefined`): Signal to abort the request. Propagated to both the initial POST and any reconnection GET requests. #### Returns Returns a `Promise>` that includes chunks from both the initial POST response and any automatic reconnection. ### `reconnectToStream()` Reconnects to an existing chat stream that was previously interrupted. Useful for resuming after a page refresh or when the client needs to re-establish a connection. ```ts const stream = await transport.reconnectToStream({ chatId: 'chat-123', startIndex: -50, // Optional: fetch last 50 chunks }); ``` - `chatId` (`string`): The chat ID to reconnect to. Used to construct the reconnection URL. - `abortSignal` (`AbortSignal | undefined`): Signal to abort the reconnection request. - `startIndex?` (`number`): Override the start index for this reconnection. Negative values read from the end when the server's durable stream and tail-index header use the same UIMessageChunk index space. When omitted, falls back to the constructor's initialStartIndex. #### Returns Returns a `Promise | null>`. ## How Reconnection Works The transport follows this flow: 1. **POST** to `{api}` with messages. The response must include an `x-workflow-run-id` header. 2. **Stream** the SSE response, counting chunks as they arrive. 3. **Detect interruption**: If the stream closes without a `finish` event (e.g., function timeout, network error), the transport knows the response is incomplete. 4. **Reconnect** via GET to `{api}/{runId}/stream?startIndex={chunkIndex}` to resume from the last received chunk. 5. **Retry**: If the reconnection stream also interrupts, resume from the updated chunk index. Streams that end without receiving any UI message chunks count toward `maxConsecutiveErrors`, as do stream parsing errors. A successfully parsed stream that receives chunks resets the consecutive error count. 6. **Complete**: Once a `finish` event is received, call `onChatEnd` and close the stream. ### Negative Start Index When `initialStartIndex` is negative (e.g., `-50`), the transport sends it as-is in the first reconnection request. The server should resolve this to an absolute position and return the `x-workflow-stream-tail-index` response header so the transport can compute the correct position for subsequent retries. If the header is missing or invalid, the transport falls back to replaying from the beginning (`startIndex=0`). Negative indexes require a durable server stream whose stored objects are already `UIMessageChunk` objects. The raw `WorkflowAgent` conversion shown below supports non-negative indexes only. ## Server Requirements For `WorkflowChatTransport` to work, your server must provide two endpoints: ### POST `{api}` (e.g., `/api/chat`) - Accept messages as JSON body - Return an SSE stream of `UIMessageChunk` events - Include an `x-workflow-run-id` response header ### GET `{api}/{runId}/stream` (e.g., `/api/chat/{runId}/stream`) - Accept a `startIndex` query parameter - Return the SSE stream starting from the given chunk index - For negative `startIndex`, resolve to the tail and include `x-workflow-stream-tail-index` response header See the [WorkflowAgent guide](/docs/agents/workflow-agent) for complete endpoint examples. ## Examples ### Basic Usage with useChat ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import { WorkflowChatTransport } from '@ai-sdk/workflow/client'; import { useMemo } from 'react'; export default function Chat() { const transport = useMemo( () => new WorkflowChatTransport({ api: '/api/chat' }), [], ); const { messages, sendMessage, status } = useChat({ transport }); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, index) => part.type === 'text' ? {part.text} : null, )}
))}
); } ``` ### With Callbacks ```tsx 'use client'; import { useChat } from '@ai-sdk/react'; import { WorkflowChatTransport } from '@ai-sdk/workflow/client'; import { useMemo } from 'react'; export default function Chat() { const transport = useMemo( () => new WorkflowChatTransport({ api: '/api/chat', maxConsecutiveErrors: 5, onChatSendMessage: response => { const runId = response.headers.get('x-workflow-run-id'); console.log('Workflow run started:', runId); }, onChatEnd: ({ chatId, chunkIndex }) => { console.log(`Chat ${chatId} complete, ${chunkIndex} chunks`); }, }), [], ); const { messages, sendMessage } = useChat({ transport }); // ... render chat UI } ``` ### Server-Side Endpoints (Next.js) ```ts title="app/api/chat/route.ts" import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow'; import { createUIMessageStreamResponse, type UIMessage } from 'ai'; import { start } from 'workflow/api'; import { chat } from '@/workflow/agent-chat'; export async function POST(request: Request) { const { messages }: { messages: UIMessage[] } = await request.json(); const run = await start(chat, [messages]); return createUIMessageStreamResponse({ stream: run.readable.pipeThrough(createModelCallToUIChunkTransform()), headers: { 'x-workflow-run-id': run.runId, }, }); } ``` ```ts title="app/api/chat/[runId]/stream/route.ts" import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow'; import { createUIMessageStreamResponse } from 'ai'; import type { NextRequest } from 'next/server'; import { getRun } from 'workflow/api'; export async function GET( request: NextRequest, { params }: { params: Promise<{ runId: string }> }, ) { const { runId } = await params; const startIndex = Number( new URL(request.url).searchParams.get('startIndex') ?? '0', ); if (!Number.isSafeInteger(startIndex) || startIndex < 0) { return Response.json( { error: 'startIndex must be a non-negative safe integer' }, { status: 400 }, ); } const run = await getRun(runId); const readable = run .getReadable({ startIndex: 0 }) .pipeThrough( createModelCallToUIChunkTransform({ uiStartIndex: startIndex }), ); return createUIMessageStreamResponse({ stream: readable, headers: { 'x-workflow-run-id': runId, }, }); } ``` This `WorkflowAgent` endpoint replays raw `ModelCallStreamPart` objects from index `0`, then applies the transport's non-negative cursor after converting them to `UIMessageChunk` objects. Negative start indexes require a durable stream whose stored objects are already `UIMessageChunk` objects. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: createAI description: Reference for the createAI function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/create-ai" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). Creates a client-server context provider that can be used to wrap parts of your application tree to easily manage both UI and AI states of your application. ## Import ``` import { createAI } from "@ai-sdk/rsc" ``` ## API Signature ### Parameters - `actions` (`Record`): Server side actions that can be called from the client. - `initialAIState` (`any`): Initial AI state to be used in the client. - `initialUIState` (`any`): Initial UI state to be used in the client. - `onGetUIState` (`() => Promise`): is called during SSR to compare and update UI state. - `onSetAIState` (`(Event) => void`): is triggered whenever an update() or done() is called by the mutable AI state in your action, so you can safely store your AI state in the database. - `Event` - `state` (`AIState`): The resulting AI state after the update. - `done` (`boolean`): Whether the AI state updates have been finalized or not. ### Returns It returns an `` context provider. ## Examples - [Learn to manage AI and UI states in Next.js](/examples/next-app/state-management/ai-ui-states) - [Learn to persist and restore states UI/AI states in Next.js](/examples/next-app/state-management/save-and-restore-states) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: createStreamableUI description: Reference for the createStreamableUI function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/create-streamable-ui" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). Create a stream that sends UI from the server to the client. On the client side, it can be rendered as a normal React node. ## Import ``` import { createStreamableUI } from "@ai-sdk/rsc" ``` ## API Signature ### Parameters - `initialValue?` (`ReactNode`): The initial value of the streamable UI. ### Returns - `value` (`ReactNode`): The value of the streamable UI. This can be returned from a Server Action and received by the client. ### Methods - `update` (`(ReactNode) => void`): Updates the current UI node. It takes a new UI node and replaces the old one. - `append` (`(ReactNode) => void`): Appends a new UI node to the end of the old one. Once appended a new UI node, the previous UI node cannot be updated anymore. - `done` (`(ReactNode | null) => void`): Marks the UI node as finalized and closes the stream. Once called, the UI node cannot be updated or appended anymore. This method is always required to be called, otherwise the response will be stuck in a loading state. - `error` (`(any) => StreamableUIWrapper`): Signals that there is an error in the UI stream. It will be thrown on the client side and caught by the nearest error boundary component. ## Examples - [Render a React component during a tool call](/examples/next-app/tools/render-interface-during-tool-call) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: createStreamableValue description: Reference for the createStreamableValue function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/create-streamable-value" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). Create a stream that sends values from the server to the client. The value can be any serializable data. ## Import ``` import { createStreamableValue } from "@ai-sdk/rsc" ``` ## API Signature ### Parameters - `value` (`any`): Any data that RSC supports. Example, JSON. ### Returns - `value` (`StreamableValue`): The value of the streamable. This can be returned from a Server Action and received by the client. To read the streamed values, use the \`readStreamableValue\` or \`useStreamableValue\` APIs. ### Methods - `update` (`(value: T) => StreamableValueWrapper`): Updates the current value with a new one. - `append` (`(value: T) => StreamableValueWrapper`): Appends a delta string to the current value. It requires the current value of the streamable to be a string. - `done` (`(value?: T) => StreamableValueWrapper`): Marks the value as finalized. You can either call it without any parameters or with a new value as the final state. Once called, the value cannot be updated or appended anymore. This method is always required to be called, otherwise the response will be stuck in a loading state. - `error` (`(error: any) => StreamableValueWrapper`): Signals that there is an error in the value stream. It will be thrown on the client side when consumed via \`readStreamableValue\` or \`useStreamableValue\`. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: getAIState description: Reference for the getAIState function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/get-ai-state" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). Get the current AI state. ## Import ``` import { getAIState } from "@ai-sdk/rsc" ``` ## API Signature ### Parameters - `key?` (`string`): Returns the value of the specified key in the AI state, if it's an object. ### Returns The AI state. ## Examples - [Learn to render a React component during a tool call made by a language model in Next.js](/examples/next-app/tools/render-interface-during-tool-call) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: getMutableAIState description: Reference for the getMutableAIState function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/get-mutable-ai-state" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). Get a mutable copy of the AI state. You can use this to update the state in the server. ## Import ``` import { getMutableAIState } from "@ai-sdk/rsc" ``` ## API Signature ### Parameters - `key?` (`string`): Returns the value of the specified key in the AI state, if it's an object. ### Returns The mutable AI state. ### Methods - `update` (`(newState: any) => void`): Updates the AI state with the new state. - `done` (`(newState: any) => void`): Updates the AI state with the new state, marks it as finalized and closes the stream. ## Examples - [Learn to persist and restore states AI and UI states in Next.js](/examples/next-app/state-management/save-and-restore-states) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI SDK RSC description: Reference documentation for the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). #### [streamUI](/docs/reference/ai-sdk-rsc/stream-ui) Use a helper function that streams React Server Components on tool execution. #### [createAI](/docs/reference/ai-sdk-rsc/create-ai) Create a context provider that wraps your application and shares state between the client and language model on the server. #### [createStreamableUI](/docs/reference/ai-sdk-rsc/create-streamable-ui) Create a streamable UI component that can be rendered on the server and streamed to the client. #### [createStreamableValue](/docs/reference/ai-sdk-rsc/create-streamable-value) Create a streamable value that can be rendered on the server and streamed to the client. #### [getAIState](/docs/reference/ai-sdk-rsc/get-ai-state) Read the AI state on the server. #### [getMutableAIState](/docs/reference/ai-sdk-rsc/get-mutable-ai-state) Read and update the AI state on the server. #### [useAIState](/docs/reference/ai-sdk-rsc/use-ai-state) Get the AI state on the client from the context provider. #### [useUIState](/docs/reference/ai-sdk-rsc/use-ui-state) Get the UI state on the client from the context provider. #### [useActions](/docs/reference/ai-sdk-rsc/use-actions) Call server actions from the client. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: readStreamableValue description: Reference for the readStreamableValue function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/read-streamable-value" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). It is a function that helps you read the streamable value from the client that was originally created using [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value) on the server. ## Import ``` import { readStreamableValue } from "@ai-sdk/rsc" ``` ## Example ```ts title="app/actions.ts" async function generate() { 'use server'; const streamable = createStreamableValue(''); streamable.append('Hello'); streamable.append(' '); streamable.append('World'); streamable.done(); return streamable.value; } ``` ```tsx title="app/page.tsx" {12} import { readStreamableValue } from '@ai-sdk/rsc'; export default function Page() { const [generation, setGeneration] = useState(''); return (
); } ``` ## API Signature ### Parameters - `stream` (`StreamableValue`): The streamable value to read from. ### Returns It returns an async iterator that contains the values emitted by the streamable value. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: render (Removed) description: Reference for the render function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/render" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). "render" has been removed in AI SDK 4.0. AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). A helper function to create a streamable UI from LLM providers. This function is similar to AI SDK Core APIs and supports the same model interfaces. > **Note**: `render` has been deprecated in favor of [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui). During migration, please ensure that the `messages` parameter follows the updated [specification](/docs/reference/ai-sdk-rsc/stream-ui#messages). ## Import (No longer available) The following import will no longer work since `render` has been removed: ``` import { render } from "@ai-sdk/rsc" ``` Use [`streamUI`](/docs/reference/ai-sdk-rsc/stream-ui) instead. ## API Signature ### Parameters - `model` (`string`): Model identifier, must be OpenAI SDK compatible. - `provider` (`provider client`): Currently the only provider available is OpenAI. This needs to match the model name. - `initial?` (`ReactNode`): The initial UI to render. - `messages` (`Array`): A list of messages that represent a conversation. - `SystemMessage` - `role` (`'system'`): The role for the system message. - `content` (`string`): The content of the message. - `UserMessage` - `role` (`'user'`): The role for the user message. - `content` (`string`): The content of the message. - `AssistantMessage` - `role` (`'assistant'`): The role for the assistant message. - `content` (`string`): The content of the message. - `tool_calls` (`ToolCall[]`): A list of tool calls made by the model. - `ToolCall` - `id` (`string`): The id of the tool call. - `type` (`'function'`): The type of the tool call. - `function` (`Function`): The function to call. - `Function` - `name` (`string`): The name of the function. - `arguments` (`string`): The arguments of the function. - `ToolMessage` - `role` (`'tool'`): The role for the tool message. - `content` (`string`): The content of the message. - `toolCallId` (`string`): The id of the tool call. - `functions?` (`ToolSet`): Tools that are accessible to and can be called by the model. - `Tool` - `description?` (`string`): Information about the purpose of the tool including details on how and when it can be used by the model. - `parameters` (`zod schema`): The typed schema that describes the parameters of the tool that can also be used to validation and error handling. - `render?` (`async (parameters) => any`): An async function that is called with the arguments from the tool call and produces a result. - `tools?` (`ToolSet`): Tools that are accessible to and can be called by the model. - `Tool` - `description?` (`string`): Information about the purpose of the tool including details on how and when it can be used by the model. - `parameters` (`zod schema`): The typed schema that describes the parameters of the tool that can also be used to validation and error handling. - `render?` (`async (parameters) => any`): An async function that is called with the arguments from the tool call and produces a result. - `text?` (`(Text) => ReactNode`): Callback to handle the generated tokens from the model. - `Text` - `content` (`string`): The full content of the completion. - `delta` (`string`): The delta. - `done` (`boolean`): Is it done? - `temperature?` (`number`): The temperature to use for the model. ### Returns It can return any valid ReactNode. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: streamUI description: Reference for the streamUI function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/stream-ui" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). A helper function to create a streamable UI from LLM providers. This function is similar to AI SDK Core APIs and supports the same model interfaces. To see `streamUI` in action, check out [these examples](#examples). ## Import ``` import { streamUI } from "@ai-sdk/rsc" ``` ## Parameters - `model` (`LanguageModel`): The language model to use. Example: openai("gpt-6-astra") - `initial?` (`ReactNode`): The initial UI to render. - `instructions?` (`Instructions`): Instructions to use that specify the behavior of the model. - `prompt` (`string`): The input prompt to generate the text from. - `messages` (`Array | Array`): A list of messages that represent a conversation. Automatically converts UI messages from the useChat hook. - `SystemModelMessage` - `role` (`'system'`): The role for the system message. - `content` (`string`): The content of the message. - `UserModelMessage` - `role` (`'user'`): The role for the user message. - `content` (`string | Array`): The content of the message. - `TextPart` - `type` (`'text'`): The type of the message part. - `text` (`string`): The text content of the message part. - `ImagePart` - `type` (`'image'`): The type of the message part. - `image` (`string | Uint8Array | Buffer | ArrayBuffer | URL`): The image content of the message part. String are either base64 encoded content, base64 data URLs, or http(s) URLs. - `mediaType?` (`string`): The IANA media type of the image. Optional. - `FilePart` - `type` (`'file'`): The type of the message part. - `data` (`string | Uint8Array | Buffer | ArrayBuffer | URL`): The file content of the message part. String are either base64 encoded content, base64 data URLs, or http(s) URLs. - `mediaType` (`string`): The IANA media type of the file. - `AssistantModelMessage` - `role` (`'assistant'`): The role for the assistant message. - `content` (`string | Array`): The content of the message. - `TextPart` - `type` (`'text'`): The type of the message part. - `text` (`string`): The text content of the message part. - `ToolCallPart` - `type` (`'tool-call'`): The type of the message part. - `toolCallId` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `args` (`object based on zod schema`): Parameters generated by the model to be used by the tool. - `ToolModelMessage` - `role` (`'tool'`): The role for the tool message. - `content` (`Array`): The content of the message. - `ToolResultPart` - `type` (`'tool-result'`): The type of the message part. - `toolCallId` (`string`): The id of the tool call the result corresponds to. - `toolName` (`string`): The name of the tool the result corresponds to. - `result` (`unknown`): The result returned by the tool after execution. - `isError?` (`boolean`): Whether the result is an error or an error message. - `allowSystemInMessages?` (`boolean`): Whether system messages are allowed in the \`prompt\` or \`messages\` fields. Defaults to false. System messages in the \`instructions\` option are always allowed. Enabling this for user-controlled messages can create a prompt injection risk. - `maxOutputTokens?` (`number`): Maximum number of tokens to generate. - `temperature?` (`number`): Temperature setting. The value is passed through to the provider. The range depends on the provider and model. It is recommended to set either \`temperature\` or \`topP\`, but not both. - `topP?` (`number`): Nucleus sampling. The value is passed through to the provider. The range depends on the provider and model. It is recommended to set either \`temperature\` or \`topP\`, but not both. - `topK?` (`number`): Only sample from the top K options for each subsequent token. Used to remove "long tail" low probability responses. Recommended for advanced use cases only. You usually only need to use temperature. - `presencePenalty?` (`number`): Presence penalty setting. It affects the likelihood of the model to repeat information that is already in the prompt. The value is passed through to the provider. The range depends on the provider and model. - `frequencyPenalty?` (`number`): Frequency penalty setting. It affects the likelihood of the model to repeatedly use the same words or phrases. The value is passed through to the provider. The range depends on the provider and model. - `stopSequences?` (`string[]`): Sequences that will stop the generation of the text. If the model generates any of these sequences, it will stop generating further text. - `seed?` (`number`): The seed (integer) to use for random sampling. If set and supported by the model, calls will generate deterministic results. - `maxRetries?` (`number`): Maximum number of retries. Set to 0 to disable retries. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal that can be used to cancel the call. - `headers?` (`Record`): Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers. - `tools` (`ToolSet`): Tools that are accessible to and can be called by the model. - `Tool` - `description?` (`string`): Information about the purpose of the tool including details on how and when it can be used by the model. - `inputSchema` (`zod schema`): The typed schema that describes the parameters of the tool that can also be used to validation and error handling. - `generate?` (`(parameters) => ReactNode | Promise | Generator | AsyncGenerator`): A function or a generator function that is called with the arguments from the tool call and yields React nodes as the UI. - `toolChoice?` (`"auto" | "none" | "required" | { "type": "tool", "toolName": string }`): The tool choice setting. It specifies how tools are selected for execution. The default is "auto". "none" disables tool execution. "required" requires tools to be executed. \{ "type": "tool", "toolName": string } specifies a specific tool to execute. - `text?` (`(Text) => ReactNode`): Callback to handle the generated tokens from the model. - `Text` - `content` (`string`): The full content of the completion. - `delta` (`string`): The delta. - `done` (`boolean`): Is it done? - `providerOptions?` (`Record | undefined`): Provider-specific options. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. - `onFinish?` (`(result: OnFinishResult) => void`): Callback that is called when the LLM response and all request tool executions (for tools that have a \`generate\` function) are finished. - `OnFinishResult` - `usage` (`LanguageModelUsage`): The token usage of the generated text. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. See also: cached tokens and non-cached tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. - `value` (`ReactNode`): The final ui node that was generated. - `warnings` (`Warning[] | undefined`): Warnings from the model provider (e.g. unsupported settings). - `response?` (`Response`): Optional response data. - `Response` - `headers?` (`Record`): Response headers. ## Returns - `value` (`ReactNode`): The user interface based on the stream output. - `response?` (`Response`): Optional response data. - `Response` - `headers?` (`Record`): Response headers. - `warnings` (`Warning[] | undefined`): Warnings from the model provider (e.g. unsupported settings). - `stream` (`AsyncIterable & ReadableStream`): A stream with all events, including text deltas, tool calls, tool results, and errors. You can use it as either an AsyncIterable or a ReadableStream. When an error occurs, the stream will throw the error. - `StreamPart` - `type` (`'text-delta'`): The type to identify the object as text delta. - `textDelta` (`string`): The text delta. - `StreamPart` - `type` (`'tool-call'`): The type to identify the object as tool call. - `toolCallId` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `args` (`object based on zod schema`): Parameters generated by the model to be used by the tool. - `StreamPart` - `type` (`'error'`): The type to identify the object as error. - `error` (`Error`): Describes the error that may have occurred during execution. - `StreamPart` - `type` (`'finish'`): The type to identify the object as finish. - `finishReason` (`'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'`): The reason the model finished generating the text. - `usage` (`LanguageModelUsage`): The token usage of the generated text. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. See also: cached tokens and non-cached tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. ## Examples - [Learn to render a React component as a function call using a language model in Next.js](/examples/next-app/state-management/ai-ui-states) - [Learn to persist and restore states UI/AI states in Next.js](/examples/next-app/state-management/save-and-restore-states) - [Learn to route React components using a language model in Next.js](/examples/next-app/interface/route-components) - [Learn to stream component updates to the client in Next.js](/examples/next-app/interface/stream-component-updates) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: useActions description: Reference for the useActions function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/use-actions" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). It is a hook to help you access your Server Actions from the client. This is particularly useful for building interfaces that require user interactions with the server. It is required to access these server actions via this hook because they are patched when passed through the context. Accessing them directly may result in a [Cannot find Client Component error](/docs/troubleshooting/server-actions-in-client-components). ## Import ``` import { useActions } from "@ai-sdk/rsc" ``` ## API Signature ### Returns `Record`, a dictionary of server actions. ## Examples - [Learn to manage AI and UI states in Next.js](/examples/next-app/state-management/ai-ui-states) - [Learn to route React components using a language model in Next.js](/examples/next-app/interface/route-components) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: useAIState description: Reference for the useAIState function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/use-ai-state" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). It is a hook that enables you to read and update the AI state. The AI state is shared globally between all `useAIState` hooks under the same `` provider. The AI state is intended to contain context and information shared with the AI model, such as system messages, function responses, and other relevant data. ## Import ``` import { useAIState } from "@ai-sdk/rsc" ``` ## API Signature ### Returns Similar to useState, it is an array where the first element is the current AI state and the second element is a function to update the state. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: useStreamableValue description: Reference for the useStreamableValue function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/use-streamable-value" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). It is a React hook that takes a streamable value created using [`createStreamableValue`](/docs/reference/ai-sdk-rsc/create-streamable-value) and returns the current value, error, and pending state. ## Import ``` import { useStreamableValue } from "@ai-sdk/rsc" ``` ## Example This is useful for consuming streamable values received from a component's props. ```tsx function MyComponent({ streamableValue }) { const [data, error, pending] = useStreamableValue(streamableValue); if (pending) return
Loading...
; if (error) return
Error: {error.message}
; return
Data: {data}
; } ``` ## API Signature ### Parameters It accepts a streamable value created using `createStreamableValue`. ### Returns It is an array, where the first element contains the data, the second element contains an error if it is thrown anytime during the stream, and the third is a boolean indicating if the value is pending. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: useUIState description: Reference for the useUIState function from the AI SDK RSC url: "https://ai-sdk.dev/docs/reference/ai-sdk-rsc/use-ui-state" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK RSC is currently experimental. We recommend using [AI SDK UI](/docs/ai-sdk-ui/overview) for production. For guidance on migrating from RSC to UI, see our [migration guide](/docs/ai-sdk-rsc/migrating-to-ui). It is a hook that enables you to read and update the UI State. The state is client-side and can contain functions, React nodes, and other data. UIState is the visual representation of the AI state. ## Import ``` import { useUIState } from "@ai-sdk/rsc" ``` ## API Signature ### Returns Similar to useState, it is an array, where the first element is the current UI state and the second element is the function that updates the state. ## Examples - [Learn to manage AI and UI states in Next.js](/examples/next-app/state-management/ai-ui-states) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: addToolInputExamplesMiddleware description: Middleware that appends tool input examples to tool descriptions. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/add-tool-input-examples-middleware" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `addToolInputExamplesMiddleware` is a middleware function that appends input examples to tool descriptions. This is especially useful for language model providers that **do not natively support the `inputExamples` property**—the middleware serializes and injects the examples into the tool's `description` so models can learn from them. ## Import ``` import { addToolInputExamplesMiddleware } from "ai" ``` ## API ### Signature ```ts function addToolInputExamplesMiddleware(options?: { prefix?: string; format?: (example: { input: JSONObject }, index: number) => string; remove?: boolean; }): LanguageModelMiddleware; ``` ### Parameters - `prefix?` (`string`): A prefix prepended before the input examples section. Defaults to \`'Input Examples:'\`. - `format?` (`(example: { input: JSONObject }, index: number) => string`): Optional custom formatter for each example. Receives the example object and its index. Default: JSON.stringify(example.input). - `remove?` (`boolean`): Whether to remove the \`inputExamples\` property from the tool after adding them to the description. Default: true. ### Returns A [LanguageModelMiddleware](/docs/ai-sdk-core/middleware) that: - Locates function tools with an `inputExamples` property. - Serializes each input example (by default as JSON, or using your custom formatter). - Appends a section at the end of the tool description containing all formatted examples, prefixed by the `prefix`. - Removes the `inputExamples` property from the tool (unless `remove: false`). - Passes through all other tools (including those without examples) unchanged. ## Usage Example ```ts import { generateText, tool, wrapLanguageModel, addToolInputExamplesMiddleware, } from 'ai'; import { openai } from '@ai-sdk/openai'; import { z } from 'zod'; const model = wrapLanguageModel({ model: "anthropic/claude-sonnet-5.5", middleware: addToolInputExamplesMiddleware({ prefix: 'Input Examples:', format: (example, index) => `${index + 1}. ${JSON.stringify(example.input)}`, }), }); const result = await generateText({ model, tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string() }), inputExamples: [ { input: { location: 'San Francisco' } }, { input: { location: 'London' } }, ], }), }, prompt: 'What is the weather in Tokyo?', }); ``` ## How It Works 1. For every function tool that defines `inputExamples`, the middleware: - Formats each example with the `format` function (default: JSON.stringify). - Builds a section like: ``` Input Examples: {"location":"San Francisco"} {"location":"London"} ``` - Appends this section to the end of the tool's `description`. 2. By default, it removes the `inputExamples` property after appending to prevent duplication (can be disabled with `remove: false`). 3. Tools without input examples or non-function tools are left unmodified. > **Tip:** This middleware is especially useful with providers such as OpenAI or Anthropic, where native support for `inputExamples` is not available. ## Example effect If your original tool definition is: ```ts { type: 'function', name: 'weather', description: 'Get the weather in a location', inputSchema: { ... }, inputExamples: [ { input: { location: 'San Francisco' } }, { input: { location: 'London' } } ] } ``` After applying the middleware (with default settings), the tool passed to the model will look like: ```ts { type: 'function', name: 'weather', description: `Get the weather in a location Input Examples: {"location":"San Francisco"} {"location":"London"}`, inputSchema: { ... } // inputExamples is removed by default } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Agent (Interface) description: API Reference for the Agent interface. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/agent" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `Agent` interface defines a contract for agents that can generate or stream AI-generated responses in response to prompts. Agents may encapsulate advanced logic such as tool usage, multi-step workflows, or prompt handling, enabling both simple and autonomous AI agents. Implementations of the `Agent` interface—such as `ToolLoopAgent`—fulfill the same contract and integrate seamlessly with all SDK APIs and utilities that expect an agent. This design allows users to supply custom agent classes or wrappers for third-party chains, while maximizing compatibility with AI SDK features. ## Interface Definition ```ts import { ModelMessage, Experimental_SandboxSession, } from '@ai-sdk/provider-utils'; import { ToolSet } from '../generate-text/tool-set'; import { Output } from '../generate-text/output'; import { GenerateTextResult } from '../generate-text/generate-text-result'; import { StreamTextResult } from '../generate-text/stream-text-result'; export type AgentCallParameters = ([ CALL_OPTIONS, ] extends [never] ? { options?: never } : { options: CALL_OPTIONS }) & ( | { /** * A prompt. It can be either a text prompt or a list of messages. * * You can either use `prompt` or `messages` but not both. */ prompt: string | Array; /** * A list of messages. * * You can either use `prompt` or `messages` but not both. */ messages?: never; } | { /** * A list of messages. * * You can either use `prompt` or `messages` but not both. */ messages: Array; /** * A prompt. It can be either a text prompt or a list of messages. * * You can either use `prompt` or `messages` but not both. */ prompt?: never; } ) & { /** * Abort signal. */ abortSignal?: AbortSignal; /** * Timeout in milliseconds. Can be specified as a number or as an object with a totalMs property. * The call will be aborted if it takes longer than the specified timeout. * Can be used alongside abortSignal. */ timeout?: number | { totalMs?: number }; /** * Experimental sandbox environment that is passed through to tool execution. */ experimental_sandbox?: Experimental_SandboxSession; /** * Callback that is called when the agent operation begins, before any LLM calls. */ onStart?: GenerateTextOnStartCallback; /** * Callback that is called when a step (LLM call) begins, before the provider is called. */ onStepStart?: GenerateTextOnStepStartCallback; /** * Callback that is called before each tool execution begins. */ onToolExecutionStart?: OnToolExecutionStartCallback; /** * Callback that is called after each tool execution completes. */ onToolExecutionEnd?: OnToolExecutionEndCallback; /** * Callback that is called when each step (LLM call) ends, including intermediate steps. */ onStepEnd?: GenerateTextOnStepEndCallback; /** * Callback that is called when each step (LLM call) ends, including intermediate steps. * * @deprecated Use `onStepEnd` instead. */ onStepFinish?: GenerateTextOnStepFinishCallback; /** * Callback that is called when all steps are finished and the response is complete. */ onEnd?: GenerateTextOnEndCallback; /** * Callback that is called when all steps are finished and the response is complete. * * @deprecated Use `onEnd` instead. */ onFinish?: GenerateTextOnEndCallback; }; /** * An Agent receives a prompt (text or messages) and generates or streams an output * that consists of steps, tool calls, data parts, etc. * * You can implement your own Agent by implementing the `Agent` interface, * or use the `ToolLoopAgent` class. */ export interface Agent< CALL_OPTIONS = never, TOOLS extends ToolSet = {}, OUTPUT extends Output = never, > { /** * The specification version of the agent interface. This will enable * us to evolve the agent interface and retain backwards compatibility. */ readonly version: 'agent-v1'; /** * The id of the agent. */ readonly id: string | undefined; /** * The tools that the agent can use. */ readonly tools: TOOLS; /** * Generates an output from the agent (non-streaming). */ generate( options: AgentCallParameters, ): PromiseLike>; /** * Streams an output from the agent (streaming). */ stream( options: AgentStreamParameters, ): PromiseLike>; } ``` ## Core Properties & Methods | Name | Type | Description | | ------------ | ------------------------------------------------ | ------------------------------------------------------------------- | | `version` | `'agent-v1'` | Interface version for compatibility. | | `id` | `string \| undefined` | Optional agent identifier. | | `tools` | `ToolSet` | The set of tools available to this agent. | | `generate()` | `PromiseLike>` | Generates full, non-streaming output for a text prompt or messages. | | `stream()` | `PromiseLike>` | Streams output (chunks or steps) for a text prompt or messages. | ## Generic Parameters | Parameter | Default | Description | | -------------- | ------- | -------------------------------------------------------------------------- | | `CALL_OPTIONS` | `never` | Optional type for additional call options that can be passed to the agent. | | `TOOLS` | `{}` | The type of the tool set available to this agent. | | `OUTPUT` | `never` | The type of additional output data that the agent can produce. | ## Method Parameters `generate()` accepts an `AgentCallParameters` object, and `stream()` accepts an `AgentStreamParameters` object, which extends `AgentCallParameters` with an optional `experimental_transform`. Both accept: - `prompt` (optional): A string prompt or array of `ModelMessage` objects - `messages` (optional): An array of `ModelMessage` objects (mutually exclusive with `prompt`) - `options` (optional): Additional call options when `CALL_OPTIONS` is not `never` - `abortSignal` (optional): An `AbortSignal` to cancel the operation - `timeout` (optional): A timeout in milliseconds. Can be specified as a number or as an object with a `totalMs` property. The call will be aborted if it takes longer than the specified timeout. Can be used alongside `abortSignal`. - `experimental_sandbox` (optional): An experimental sandbox environment forwarded to tool execution. - `onStart` (optional): Callback invoked when the agent operation begins, before any LLM calls. - `onStepStart` (optional): Callback invoked when a step (LLM call) begins, before the provider is called. - `onToolExecutionStart` (optional): Callback invoked right before a tool's execute function runs. - `onToolExecutionEnd` (optional): Callback invoked right after a tool's execute function completes or errors. - `onStepEnd` (optional): A callback invoked after each agent step (LLM/tool call) completes. Useful for tracking token usage, per-step performance, or logging. - `onStepFinish` (optional): Deprecated alias for `onStepEnd`. - `onEnd` (optional): A callback invoked when all steps are finished and the response is complete. - `onFinish` (optional): Deprecated alias for `onEnd`. ## Example: Custom Agent Implementation Here's how you might implement your own Agent: ```ts import { Agent, GenerateTextResult, StreamTextResult } from 'ai'; import type { ModelMessage } from '@ai-sdk/provider-utils'; class MyEchoAgent implements Agent { version = 'agent-v1' as const; id = 'echo'; tools = {}; async generate({ prompt, messages, abortSignal }) { const text = prompt ?? JSON.stringify(messages); return { text, steps: [] }; } async stream({ prompt, messages, abortSignal }) { const text = prompt ?? JSON.stringify(messages); return { textStream: (async function* () { yield text; })(), }; } } ``` ## Usage: Interacting with Agents All SDK utilities that accept an agent—including [`createAgentUIStream`](/docs/reference/ai-sdk-core/create-agent-ui-stream), [`createAgentUIStreamResponse`](/docs/reference/ai-sdk-core/create-agent-ui-stream-response), and [`pipeAgentUIStreamToResponse`](/docs/reference/ai-sdk-core/pipe-agent-ui-stream-to-response)—expect an object adhering to the `Agent` interface. You can use the official [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent) (recommended for multi-step AI workflows with tool use), or supply your own implementation: ```ts import { ToolLoopAgent, createAgentUIStream } from "ai"; const agent = new ToolLoopAgent({ ... }); const stream = await createAgentUIStream({ agent, messages: [{ role: "user", content: "What is the weather in NYC?" }] }); for await (const chunk of stream) { console.log(chunk); } ``` ## See Also - [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent) — Official multi-step agent implementation - [`createAgentUIStream`](/docs/reference/ai-sdk-core/create-agent-ui-stream) - [`GenerateTextResult`](/docs/reference/ai-sdk-core/generate-text) - [`StreamTextResult`](/docs/reference/ai-sdk-core/stream-text) ## Notes - Agents should define their `tools` property, even if empty (`{}`), for compatibility with SDK utilities. - The interface accepts both plain prompts and message arrays as input, but only one at a time. - The `CALL_OPTIONS` generic parameter allows agents to accept additional call-specific options when needed. - The `abortSignal` parameter enables cancellation of agent operations. - This design is extensible for both complex autonomous agents and simple LLM wrappers. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_cancelBatch description: API Reference for experimental_cancelBatch. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/cancel-batch" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Batch support is experimental and the API may change in patch releases. Requests cancellation of an asynchronous batch. A successful call means that the provider accepted the cancellation request, not that cancellation has finished. For a complete guide to the batch lifecycle, see [Batch](/docs/ai-sdk-core/batch). ```ts import { experimental_cancelBatch as cancelBatch } from 'ai'; const result = await cancelBatch({ provider, batch, }); console.log(result.providerMetadata); ``` ## Import ``` import { experimental_cancelBatch } from "ai" ``` ## API Signature ### Parameters - `provider?` (`Experimental_BatchProvider`): The provider used to access the batch. Defaults to the global provider, or the AI Gateway when no global provider is configured. - `batch` (`Experimental_BatchReference`): The serializable reference returned by experimental\_startBatch. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options for the cancellation request. - `abortSignal?` (`AbortSignal`): An optional abort signal to cancel the API request. - `timeout?` (`number | { totalMs?: number }`): Maximum time allowed for the cancellation request. - `headers?` (`Record`): Additional HTTP headers for the request. ### Returns - `providerMetadata?` (`ProviderMetadata`): Provider-specific metadata from the cancellation response. Calling this function with a provider that does not support batch cancellation throws an `UnsupportedFunctionalityError`. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: cosineSimilarity description: Calculate the cosine similarity between two vectors (API Reference) url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/cosine-similarity" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). When you want to compare the similarity of embeddings, standard vector similarity metrics like cosine similarity are often used. `cosineSimilarity` calculates the cosine similarity between two vectors. A high value (close to 1) indicates that the vectors are very similar, while a low value (close to -1) indicates that they are different. ```ts import { cosineSimilarity, embedMany } from 'ai'; const { embeddings } = await embedMany({ model: 'openai/text-embedding-3-small', values: ['sunny day at the beach', 'rainy afternoon in the city'], }); console.log( `cosine similarity: ${cosineSimilarity(embeddings[0], embeddings[1])}`, ); ``` ## Import ``` import { cosineSimilarity } from "ai" ``` ## API Signature ### Parameters - `vector1` (`number[]`): The first vector to compare - `vector2` (`number[]`): The second vector to compare ### Returns A number between -1 and 1 representing the cosine similarity between the two vectors. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: createAgentUIStreamResponse description: API Reference for the createAgentUIStreamResponse utility. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/create-agent-ui-stream-response" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `createAgentUIStreamResponse` function executes an [Agent](/docs/reference/ai-sdk-core/agent), runs its streaming output as a UI message stream, and returns an HTTP [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object whose body is the live, streaming UI message output. This is designed for API routes that deliver real-time agent results, such as chat endpoints or streaming tool-use operations. ## Import ``` import { createAgentUIStreamResponse } from "ai" ``` ## Usage ```ts import { ToolLoopAgent, createAgentUIStreamResponse } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a helpful assistant.', tools: { weather: weatherTool, calculator: calculatorTool }, }); export async function POST(request: Request) { const { messages } = await request.json(); return createAgentUIStreamResponse({ agent, uiMessages: messages, abortSignal: request.signal, // optional: aborts when the client disconnects // experimental_sandbox, // optional: passed through to tool execution // ...other UIMessageStreamOptions like sendSources, experimental_transform, etc. }); } ``` ## Parameters - `agent` (`Agent`): The agent instance to stream responses from. Must implement \`.stream(\{ prompt, ... })\` and define the \`tools\` property. - `uiMessages` (`unknown[]`): Array of input UI messages provided to the agent (e.g., user and assistant messages). - `convertDataPart` (`(part: DataUIPart) => TextPart | FilePart | undefined`): Converts custom UI data parts into text or file parts for the model. Data parts are ignored when this callback is omitted or returns undefined. - `abortSignal` (`AbortSignal`): Optional abort signal to cancel streaming, e.g., on client disconnect. This should be an \[\`AbortSignal\`]\(https\://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) instance. - `timeout` (`number | { totalMs?: number }`): Timeout in milliseconds. Can be specified as a number or as an object with a totalMs property. The call will be aborted if it takes longer than the specified timeout. Can be used alongside abortSignal. - `experimental_sandbox` (`Experimental_SandboxSession`): Optional experimental sandbox environment that is passed through to tool execution. Tools can access it from their execution context. - `options` (`CALL_OPTIONS`): Optional agent call options, for agents with generic parameter \`CALL\_OPTIONS\`. - `experimental_transform` (`StreamTextTransform | StreamTextTransform[]`): Optional stream transforms to post-process text output—the same as in lower-level streaming APIs. - `onStepEnd` (`GenerateTextOnStepEndCallback`): Callback invoked after each agent step (LLM/tool call) completes. Useful for tracking token usage or logging intermediate steps. - `onStepFinish` (`GenerateTextOnStepFinishCallback`): Deprecated. Use \`onStepEnd\` instead. This alias is only used as a fallback when \`onStepEnd\` is not provided. - `onUIMessageStepEnd` (`UIMessageStreamOnStepEndCallback`): Callback invoked after each UI message step is assembled. It receives \`messages\`, \`isContinuation\`, and the accumulated \`responseMessage\` for intermediate persistence. - `...UIMessageStreamOptions` (`UIMessageStreamOptions`): Other UI message output options, including message IDs, metadata, final callbacks, sources, and reasoning. - `headers` (`HeadersInit`): Optional HTTP headers to include in the Response object. - `status` (`number`): Optional HTTP status code. - `statusText` (`string`): Optional HTTP status text. - `consumeSseStream` (`(options: { stream: ReadableStream }) => PromiseLike | void`): Optional function to consume the SSE stream. When provided, this function will be called with the SSE stream to handle consumption. ## Returns A `Promise` whose `body` is a streaming UI message output from the agent. Use this as the return value of API/server handlers in serverless, Next.js, Express, Hono, or edge runtime contexts. Use `onUIMessageStepEnd` to persist the accumulated UI response after every agent step. The callback is awaited before the corresponding `finish-step` chunk continues. Use idempotent writes and protect model output, reasoning, tool inputs, and tool outputs as sensitive conversation data. The existing `onStepEnd` callback remains the low-level generation callback for usage, finish reasons, and raw step details. ## Example: Next.js API Route Handler ```ts import { createAgentUIStreamResponse } from 'ai'; import { MyCustomAgent } from '@/agent/my-custom-agent'; export async function POST(request: Request) { const { messages } = await request.json(); return createAgentUIStreamResponse({ agent: MyCustomAgent, uiMessages: messages, // experimental_sandbox, // optional sendSources: true, // (optional) // headers, status, abortSignal, and other UIMessageStreamOptions also supported }); } ``` ## How It Works - 1. **UI Message Validation:** Validates the incoming `uiMessages` array according to the agent's specified tools and requirements. - 2. **Model Message Conversion:** Converts validated UI messages into the internal model message format for the agent. - 3. **Streaming Agent Output:** Invokes the agent’s `.stream({ prompt, ... })` to get a stream of chunks (steps/UI messages), passing through options such as `experimental_sandbox`. - 4. **HTTP Response Creation:** Wraps the output stream as a readable HTTP `Response` object that streams UI message chunks to the client. ## Notes - Your agent **must** implement `.stream({ prompt, ... })` and define a `tools` property (even if it's just `{}`) to work with this function. - **Server Only:** This API should only be called in backend/server-side contexts (API routes, edge/serverless/server route handlers, etc.). Not for browser use. - Pass `experimental_sandbox` when your agent tools need an experimental sandbox environment during execution. - Additional options (`headers`, `status`, UI stream options, transforms, etc.) are available for advanced scenarios. - This leverages [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) so your platform/client must support HTTP streaming consumption. ## See Also - [`Agent`](/docs/reference/ai-sdk-core/agent) - [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent) - [`UIMessage`](/docs/reference/ai-sdk-core/ui-message) - [`createAgentUIStream`](/docs/reference/ai-sdk-core/create-agent-ui-stream) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: createAgentUIStream description: API Reference for the createAgentUIStream utility. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/create-agent-ui-stream" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `createAgentUIStream` function executes an [Agent](/docs/reference/ai-sdk-core/agent), consumes an array of UI messages, and streams the agent's output as UI message chunks via an async iterable. This enables real-time, incremental rendering of AI assistant output with full access to tool use, intermediate reasoning, and interactive UI features in your own runtime—perfect for building chat APIs, dashboards, or bots powered by agents. ## Import ``` import { createAgentUIStream } from "ai" ``` ## Usage ```ts import { ToolLoopAgent, createAgentUIStream } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a helpful assistant.', tools: { weather: weatherTool, calculator: calculatorTool }, }); export async function* streamAgent( uiMessages: unknown[], abortSignal?: AbortSignal, ) { const stream = await createAgentUIStream({ agent, uiMessages, abortSignal, // experimental_sandbox, // optional: pass an experimental sandbox through to tool execution // ...other options (see below) }); for await (const chunk of stream) { yield chunk; // Each chunk is a UI message output from the agent. } } ``` ## Parameters - `agent` (`Agent`): The agent to run. Must define its \`tools\` and implement \`.stream(\{ prompt, ... })\`. - `uiMessages` (`unknown[]`): Array of input UI message objects (e.g., user/assistant/chat history). These will be validated and converted for the agent. - `convertDataPart` (`(part: DataUIPart) => TextPart | FilePart | undefined`): Converts custom UI data parts into text or file parts for the model. Data parts are ignored when this callback is omitted or returns undefined. - `abortSignal` (`AbortSignal`): Optional abort signal to cancel the stream early (for example, if the client disconnects). - `timeout` (`number | { totalMs?: number }`): Timeout in milliseconds. Can be specified as a number or as an object with a totalMs property. The call will be aborted if it takes longer than the specified timeout. Can be used alongside abortSignal. - `experimental_sandbox` (`Experimental_SandboxSession`): Optional experimental sandbox environment that is passed through to tool execution. Tools can access it from their execution context. - `options` (`CALL_OPTIONS`): Optional agent call options, only needed if your agent expects extra configuration (see agent generic parameters). - `experimental_transform` (`StreamTextTransform | StreamTextTransform[]`): Optional transformations to apply to the agent output stream (experimental). - `onStepEnd` (`GenerateTextOnStepEndCallback`): Callback invoked after each agent step (LLM/tool call) completes. Useful for tracking token usage, per-step performance, or logging intermediate steps. - `onStepFinish` (`GenerateTextOnStepFinishCallback`): Deprecated. Use \`onStepEnd\` instead. This alias is only used as a fallback when \`onStepEnd\` is not provided. - `onUIMessageStepEnd` (`UIMessageStreamOnStepEndCallback`): Callback invoked after each UI message step is assembled. It receives \`messages\`, \`isContinuation\`, and the accumulated \`responseMessage\`, making it suitable for persisting intermediate UI message state. - `...UIMessageStreamOptions` (`UIMessageStreamOptions`): Additional options to control the UI message stream, including message IDs, metadata, final callbacks, sources, and reasoning. ## Returns A `Promise>`, where each yielded chunk is a UI message output from the agent (see [`UIMessage`](/docs/reference/ai-sdk-core/ui-message)). This can be consumed with any async iterator loop, or piped to a streaming HTTP response, socket, or any other sink. ## Example ```ts import { createAgentUIStream } from 'ai'; const controller = new AbortController(); const stream = await createAgentUIStream({ agent, uiMessages: [{ role: 'user', content: 'What is the weather in SF today?' }], abortSignal: controller.signal, // experimental_sandbox, // optional sendStart: true, // ...other UIMessageStreamOptions }); for await (const chunk of stream) { // Each chunk is a UI message update — stream it to your client, dashboard, logs, etc. console.log(chunk); } // Call controller.abort() to cancel the agent operation early. ``` ## Persisting the UI Message After Each Step Use `onUIMessageStepEnd` when you need the accumulated [`UIMessage`](/docs/reference/ai-sdk-core/ui-message) after each agent step: ```ts const stream = await createAgentUIStream({ agent, uiMessages, generateMessageId: () => crypto.randomUUID(), onUIMessageStepEnd: async ({ messages, responseMessage, isContinuation }) => { await upsertConversation({ messages, responseMessage, isContinuation, }); }, onEnd: async ({ responseMessage }) => { // Save final metadata and any partial message when the stream ends. await upsertMessage(responseMessage); }, }); for await (const chunk of stream) { // Consume or forward the stream so the callback can run. } ``` `onStepEnd` and `onUIMessageStepEnd` serve different layers. `onStepEnd` receives the low-level generation step result, including usage, finish reason, and tool call details. `onUIMessageStepEnd` runs after that step has been converted to UI message parts and receives the response message accumulated so far. The callback runs as the UI stream is consumed and is awaited before the `finish-step` chunk continues through the stream. This delays UI stream delivery; it does not guarantee that the next model call has not started. If it throws, the error is passed to `onError` and streaming continues. Use idempotent writes because each callback contains a growing snapshot of the same response message. UI messages can contain model output, reasoning, tool inputs, and tool outputs, so apply the same access controls and sensitive-data handling that you use for final conversation persistence. ## How It Works 1. **UI Message Validation:** The input `uiMessages` array is validated and normalized using the agent's `tools` definition. Any invalid messages cause an error. 2. **Conversion to Model Messages:** The validated UI messages are converted into model-specific message format, as required by the agent. 3. **Agent Streaming:** The agent's `.stream({ prompt, ... })` method is invoked with the converted model messages, optional call options, abort signal, experimental\_sandbox, and any experimental transforms. 4. **UI Message Stream Building:** The result stream is converted and exposed as a streaming async iterable of UI message chunks for you to consume. ## Notes - The agent **must** implement the `.stream({ prompt, ... })` method and define its supported `tools` property. - This utility returns an async iterable for maximal streaming flexibility. For HTTP responses, see [`createAgentUIStreamResponse`](/docs/reference/ai-sdk-core/create-agent-ui-stream-response) (Web) or [`pipeAgentUIStreamToResponse`](/docs/reference/ai-sdk-core/pipe-agent-ui-stream-to-response) (Node.js). - The `uiMessages` parameter is named `uiMessages`, **not** just `messages`. - You can provide advanced options via `UIMessageStreamOptions` (for example, to include sources or message metadata). - Use `onUIMessageStepEnd` for per-step UI message persistence. Use `onStepEnd` for low-level generation telemetry and step details. - To cancel the stream, pass an [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) via the `abortSignal` parameter. - Pass `experimental_sandbox` when your agent tools need an experimental sandbox environment during execution. ## See Also - [`Agent`](/docs/reference/ai-sdk-core/agent) - [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent) - [`UIMessage`](/docs/reference/ai-sdk-core/ui-message) - [`createAgentUIStreamResponse`](/docs/reference/ai-sdk-core/create-agent-ui-stream-response) - [`pipeAgentUIStreamToResponse`](/docs/reference/ai-sdk-core/pipe-agent-ui-stream-to-response) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: createIdGenerator description: Create a customizable unique identifier generator (API Reference) url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/create-id-generator" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Creates a customizable ID generator function. You can configure the alphabet, prefix, separator, and default size of the generated IDs. ```ts import { createIdGenerator } from 'ai'; const generateCustomId = createIdGenerator({ prefix: 'user', separator: '_', }); const id = generateCustomId(); // Example: "user_1a2b3c4d5e6f7g8h" ``` ## Import ``` import { createIdGenerator } from "ai" ``` ## API Signature ### Parameters - `options` (`object`): Optional configuration object with the following properties: - `options.alphabet` (`string`): The characters to use for generating the random part of the ID. Defaults to alphanumeric characters (0-9, A-Z, a-z). - `options.prefix` (`string`): A string to prepend to all generated IDs. Defaults to none. - `options.separator` (`string`): The character(s) to use between the prefix and the random part. Defaults to "-". - `options.size` (`number`): The default length of the random part of the ID. Defaults to 16. ### Returns Returns a function that generates IDs based on the configured options. ### Notes - The generator uses non-secure random generation and should not be used for security-critical purposes. - The separator character must not be part of the alphabet to ensure reliable prefix checking. ## Example ```ts // Create a custom ID generator for user IDs const generateUserId = createIdGenerator({ prefix: 'user', separator: '_', size: 8, }); // Generate IDs const id1 = generateUserId(); // e.g., "user_1a2b3c4d" ``` ## See also - [`generateId()`](/docs/reference/ai-sdk-core/generate-id) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: createMCPClient description: Create a client for connecting to MCP servers url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/create-mcp-client" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Creates a lightweight Model Context Protocol (MCP) client that connects to an MCP server. The client provides: - **Tools**: Automatic conversion between MCP tools and AI SDK tools - **Resources**: Methods to list, read, and discover resource templates from MCP servers - **Prompts**: Methods to list available prompts and retrieve prompt messages - **Completions**: Methods to request autocompletion suggestions for prompt arguments and resource template variables - **Elicitation**: Support for handling server requests for additional input during tool execution - **Experimental events**: Discover events and manage webhook subscriptions with `client.experimental_events` It currently does not support in-band notifications from an MCP server. For experimental webhook delivery, see the [MCP Events guide](/docs/ai-sdk-core/mcp-events), [event methods reference](/docs/reference/ai-sdk-core/mcp-events), and [webhook handler reference](/docs/reference/ai-sdk-core/mcp-events#webhook-handler). ## Import ``` import { createMCPClient } from "@ai-sdk/mcp" ``` ## API Signature ### Parameters - `config` (`MCPClientConfig`): Configuration for the MCP client. - `MCPClientConfig` - `experimental_events?` (`Experimental_MCPEventsConfig`): Experimental event subscriptions. Configure either a private durable store for direct webhooks or a managed adapter. See \[MCP Events]\(/docs/reference/ai-sdk-core/mcp-events#managed-subscriptions). - `Experimental_MCPEventsConfig` - `store?` (`Experimental_MCPEventStore`): Required in direct mode. Persists subscription identities, callback URLs, signing secrets, cursors, and expirations. Shared with the webhook receiver. Cannot be combined with adapter. - `adapter?` (`Experimental_MCPEventAdapter`): Required in managed mode. Creates bound subscription operations once before transport startup, receiving transport metadata: \{ type, url } for HTTP/SSE or \{ type: 'custom' } for custom transports. Cannot be combined with store or validateArguments. No local event store or refresh loop is required. - `validateArguments?` (`({ definition, arguments }) => void | PromiseLike`): Optional application validation for event filters. Throw to reject a subscription before persisting its secret or sending events/subscribe. - `transport` (`MCPTransportConfig | MCPTransport`): Configuration for the message transport layer. - `MCPTransport` - `start` (`() => Promise`): A method that starts the transport - `send` (`(message: JSONRPCMessage) => Promise`): A method that sends a message through the transport - `close` (`() => Promise`): A method that closes the transport - `onclose` (`() => void`): A method that is called when the transport is closed - `onerror` (`(error: Error) => void`): A method that is called when the transport encounters an error - `onmessage` (`(message: JSONRPCMessage) => void`): A method that is called when the transport receives a message - `MCPTransportConfig` - `type` (`'sse' | 'http'`): Use Server-Sent Events for communication - `url` (`string`): URL of the MCP server - `headers?` (`Record`): Additional HTTP headers to be sent with requests. - `authProvider?` (`OAuthClientProvider`): Optional OAuth provider for authorization to access protected remote MCP servers. Implement \`validateAuthorizationServerURL\` on the provider to allowlist discovered OAuth authorization server URLs before metadata is fetched. - `redirect?` (`'follow' | 'error'`): Controls how HTTP redirects are handled for transport requests. Set to 'follow' to allow redirect responses. Defaults to 'error' to reject any redirect response, preventing servers from redirecting requests to unintended hosts. - `initialSessionId?` (`string`): Initial legacy MCP session id to send with resumed Streamable HTTP requests after initialization. Pair with initialInitializeResult when using createMCPClient. Only used by the HTTP transport and initialization-based protocol versions. - `initialProtocolVersion?` (`string`): Initial legacy MCP protocol version to send before initialize negotiates one. Only used by the HTTP transport. - `onSessionIdChange?` (`(sessionId: string | undefined) => void`): Callback invoked when the Streamable HTTP server creates, changes, or clears the MCP session id. Only used by the HTTP transport. - `onSessionExpired?` (`(sessionId: string) => void`): Callback invoked when a Streamable HTTP request returns 404 for an existing MCP session id. The transport clears the session id before reporting the underlying HTTP error. Only used by the HTTP transport. - `terminateSessionOnClose?` (`boolean`): Whether close() should send DELETE for the current MCP session id. Set to false when the application intends to reattach to the session later. Defaults to true. Only used by the HTTP transport. - `fetch?` (`FetchFunction`): Optional custom fetch implementation to use for HTTP requests. Useful for runtimes that need a request-local fetch. - `initializationOptions?` (`RequestOptions`): Optional signal and timeout settings that bound transport startup and protocol negotiation. A timeout or abort closes the transport and rejects createMCPClient. - `clientName?` (`string`): Client name. Defaults to "ai-sdk-mcp-client". - `name?` (`string`): Deprecated. Use \`clientName\` instead. Defaults to "ai-sdk-mcp-client". - `version?` (`string`): Client version. Defaults to "1.0.0" - `onUncaughtError?` (`(error: unknown) => void`): Handler for uncaught errors - `maxRetries?` (`number`): Maximum number of retries for transient MCP tool call failures. Set to 0 to disable retries. Defaults to 0. Retries are opt-in and apply to tools/call requests. - `initialInitializeResult?` (`InitializeResult`): Initialize result from a previous legacy MCP session. When provided, the client starts the transport and reuses this metadata without sending a new initialize request. - `capabilities?` (`ClientCapabilities`): Optional client capabilities to advertise during legacy initialization or on each stateless modern request. For example, set \{ elicitation: \{} } to enable handling elicitation requests from the server. ### Returns Returns a Promise that resolves to an `MCPClient` with the following properties and methods: - `experimental_events` (`Experimental_MCPEvents | Experimental_ManagedMCPEvents`): Event catalog discovery and mode-specific subscription methods. Managed mode adds getSubscription and listSubscriptions; direct mode has refresh. See the \[MCP Events reference]\(/docs/reference/ai-sdk-core/mcp-events). - `initializeResult` (`InitializeResult`): Connection metadata used by this client. For legacy servers this is the initialize result; for modern servers an equivalent value is derived from protocol discovery. - `serverInfo` (`Configuration`): Information about the connected MCP server (name, version, optional title), as reported during initialization or protocol discovery. - `instructions?` (`string`): Optional instructions provided by the server during the initialize handshake. Describes how to use the server and its features. Useful for inclusion in the LLM system prompt. - `tools` (`async (options?: { schemas?: TOOL_SCHEMAS }) => Promise>`): Gets the tools available from the MCP server. - `options` - `schemas?` (`TOOL_SCHEMAS`): Schema definitions for compile-time type checking. When not provided, schemas are inferred from the server. Each tool schema can include inputSchema for typed inputs, and optionally outputSchema for typed outputs when the server returns structuredContent. - `TOOL_SCHEMAS` - `inputSchema` (`FlexibleSchema`): Zod schema or JSON schema defining the expected input parameters for the tool. - `outputSchema?` (`FlexibleSchema`): Zod schema or JSON schema defining the expected output structure. When provided, the client extracts and validates structuredContent from tool results, giving you typed outputs. - `listTools` (`async (options?: { params?: PaginatedRequest['params']; options?: RequestOptions; }) => Promise`): Lists available tool definitions from the MCP server without converting them to AI SDK tools. - `options` - `params?` (`PaginatedRequest['params']`): Optional pagination parameters including cursor. - `options?` (`RequestOptions`): Optional request options including signal and timeout. - `callTool` (`async (args: { name: string; arguments?: Record; options?: RequestOptions; }) => Promise`): Calls a tool on the MCP server. This is useful for host-mediated calls, such as MCP Apps iframe requests. - `args` - `name` (`string`): The name of the MCP tool to call. - `arguments?` (`Record`): Arguments to pass to the MCP tool. - `options?` (`RequestOptions`): Optional request options including signal and timeout. - `toolsFromDefinitions` (`(definitions: ListToolsResult, options?: { schemas?: TOOL_SCHEMAS }) => McpToolSet`): Converts existing MCP tool definitions into AI SDK tools without listing tools again. - `parameters` - `definitions` (`ListToolsResult`): Tool definitions, typically returned by \`listTools\`. - `options?` (`{ schemas?: TOOL_SCHEMAS }`): Options for converting the tool definitions. - `options` - `schemas?` (`TOOL_SCHEMAS`): Optional schema definitions for compile-time type checking and typed outputs. - `listResources` (`async (options?: { params?: PaginatedRequest['params']; options?: RequestOptions; }) => Promise`): Lists all available resources from the MCP server. - `options` - `params?` (`PaginatedRequest['params']`): Optional pagination parameters including cursor. - `options?` (`RequestOptions`): Optional request options including signal and timeout. - `readResource` (`async (args: { uri: string; options?: RequestOptions; }) => Promise`): Reads the contents of a specific resource by URI. - `args` - `uri` (`string`): The URI of the resource to read. - `options?` (`RequestOptions`): Optional request options including signal and timeout. - `listResourceTemplates` (`async (options?: { options?: RequestOptions; }) => Promise`): Lists all available resource templates from the MCP server. - `options` - `options?` (`RequestOptions`): Optional request options including signal and timeout. - `complete` (`async (args: CompleteRequestParams & { options?: RequestOptions; }) => Promise`): Requests autocompletion suggestions for a prompt argument or resource template variable. The server must advertise the completions capability. - `args` - `ref` (`{ type: 'ref/prompt'; name: string } | { type: 'ref/resource'; uri: string }`): Reference to the prompt or resource template being completed. - `argument` (`{ name: string; value: string }`): Argument name and current partial value to complete. - `context?` (`{ arguments: Record }`): Previously resolved argument values used to provide context for multi-argument prompts or resource templates. - `options?` (`RequestOptions`): Optional request options including signal and timeout. - `experimental_listPrompts` (`async (options?: { params?: PaginatedRequest['params']; options?: RequestOptions; }) => Promise`): Lists available prompts from the MCP server. This method is experimental and may change in the future. - `options` - `params?` (`PaginatedRequest['params']`): Optional pagination parameters including cursor. - `options?` (`RequestOptions`): Optional request options including signal and timeout. - `experimental_getPrompt` (`async (args: { name: string; arguments?: Record; options?: RequestOptions; }) => Promise`): Retrieves a prompt by name, optionally passing arguments. This method is experimental and may change in the future. - `args` - `name` (`string`): Prompt name to retrieve. - `arguments?` (`Record`): Optional arguments to fill into the prompt. - `options?` (`RequestOptions`): Optional request options including signal and timeout. - `onElicitationRequest` (`( schema: typeof ElicitationRequestSchema, handler: (request: ElicitationRequest) => Promise | ElicitResult ) => void`): Registers a handler for elicitation requests from the MCP server. The handler receives requests when the server needs additional input during tool execution. - `parameters` - `schema` (`typeof ElicitationRequestSchema`): The schema to validate requests against. Must be ElicitationRequestSchema. - `handler` (`(request: ElicitationRequest) => Promise | ElicitResult`): A function that handles the elicitation request. The request contains a message and requestedSchema. The handler must return an object with an action ("accept", "decline", or "cancel") and optionally content when accepting. - `close` (`() => Promise`): Closes the connection to the MCP server and cleans up resources. ## Example ```typescript import { createMCPClient } from '@ai-sdk/mcp'; import { generateText } from 'ai'; import { Experimental_StdioMCPTransport } from '@ai-sdk/mcp/mcp-stdio'; let client; try { client = await createMCPClient({ transport: new Experimental_StdioMCPTransport({ command: 'node server.js', }), }); const tools = await client.tools(); const response = await generateText({ model: "anthropic/claude-sonnet-5.5", tools, messages: [{ role: 'user', content: 'Query the data' }], }); console.log(response); } catch (error) { console.error('Error:', error); } finally { // ensure the client is closed even if an error occurs if (client) { await client.close(); } } ``` ## Error Handling The client throws `MCPClientError` for: - Client initialization failures - Protocol version mismatches - Missing server capabilities - Connection failures Import `MCPClientError` from `@ai-sdk/mcp` and use its `isInstance` method to safely narrow unknown errors: ```typescript import { createMCPClient, MCPClientError } from '@ai-sdk/mcp'; try { await createMCPClient({ transport: { type: 'http', url: 'https://your-server.com/mcp', }, }); } catch (error) { if (MCPClientError.isInstance(error)) { if (error.statusCode === 401 || error.statusCode === 403) { // Authentication or authorization must be fixed before retrying. throw error; } // Inspect error.code and error.statusCode according to your application's policy. } throw error; } ``` The error exposes the optional `code`, `statusCode`, `url`, and `responseBody` properties. `code` is the JSON-RPC application error code. `statusCode`, `url`, and `responseBody` contain HTTP transport context when it is available. They are `undefined` for failures without an HTTP response, such as network errors, timeouts, aborted requests, and stdio transport failures. `MCPClientError` also represents configuration and protocol errors, so its type or a missing `statusCode` alone does not indicate a retryable failure. OAuth failures can instead throw the separately exported `UnauthorizedError` or `MCPClientOAuthError`. Avoid logging `url` or `responseBody` without redacting sensitive endpoint, query parameter, or server response data. For tool execution, errors are propagated as `CallToolError` errors. For unknown errors, the client exposes an `onUncaughtError` callback that can be used to manually log or handle errors that are not covered by known error types. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: customProvider description: Custom provider that uses models from a different provider (API Reference) url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/custom-provider" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). With a custom provider, you can map ids to any model. This allows you to set up custom model configurations, alias names, and more. The custom provider also supports a fallback provider, which is useful for wrapping existing providers and adding additional functionality. Custom providers can expose language, embedding, image, transcription, speech, reranking, video, files, and skills capabilities. ### Example: custom model settings You can create a custom provider using `customProvider`. ```ts import { openai } from '@ai-sdk/openai'; import { customProvider } from 'ai'; // custom provider with different model settings: export const myOpenAI = customProvider({ languageModels: { // replacement model with custom settings: 'gpt-6-astra': wrapLanguageModel({ model: openai('gpt-6-astra'), middleware: defaultSettingsMiddleware({ settings: { providerOptions: { openai: { reasoningEffort: 'high', }, }, }, }), }), // alias model with custom settings: 'gpt-6-astra-reasoning-high': wrapLanguageModel({ model: openai('gpt-6-astra'), middleware: defaultSettingsMiddleware({ settings: { providerOptions: { openai: { reasoningEffort: 'high', }, }, }, }), }), }, fallbackProvider: openai, }); ``` ## Import ``` import { customProvider } from "ai" ``` ## API Signature ### Parameters - `languageModels?` (`Record`): A record of language models, where keys are model IDs and values are LanguageModel instances. - `embeddingModels?` (`Record>`): A record of text embedding models, where keys are model IDs and values are EmbeddingModel\ instances. - `imageModels?` (`Record`): A record of image models, where keys are model IDs and values are image model instances. - `transcriptionModels?` (`Record`): A record of transcription models, where keys are model IDs and values are TranscriptionModel instances. - `speechModels?` (`Record`): A record of speech models, where keys are model IDs and values are SpeechModel instances. - `rerankingModels?` (`Record`): A record of reranking models, where keys are model IDs and values are RerankingModel instances. - `videoModels?` (`Record`): A record of video models, where keys are model IDs and values are VideoModel instances. - `files?` (`FilesV4`): A files API interface to expose through the custom provider. - `skills?` (`SkillsV4`): A skills API interface to expose through the custom provider. - `fallbackProvider?` (`Provider`): An optional fallback provider to use when a requested model or capability is not found in the custom provider. ### Returns The `customProvider` function returns a `Provider` instance. It has the following methods: - `languageModel` (`(id: string) => LanguageModel`): A function that returns a language model by its custom provider model ID. - `embeddingModel` (`(id: string) => EmbeddingModel`): A function that returns a text embedding model by its custom provider model ID. - `imageModel` (`(id: string) => ImageModel`): A function that returns an image model by its custom provider model ID. - `transcriptionModel` (`(id: string) => TranscriptionModel`): A function that returns a transcription model by its custom provider model ID. - `speechModel` (`(id: string) => SpeechModel`): A function that returns a speech model by its custom provider model ID. - `rerankingModel` (`(id: string) => RerankingModel`): A function that returns a reranking model by its custom provider model ID. - `videoModel` (`(id: string) => VideoModelV4`): A function that returns a video model by its custom provider model ID. - `files?` (`() => FilesV4`): A function that returns the custom provider files API interface, when configured directly or inherited from the fallback provider. - `skills?` (`() => SkillsV4`): A function that returns the custom provider skills API interface, when configured directly or inherited from the fallback provider. ## Experimental decision models Pass `decisionModels: Record` to define aliases for decision model instances or string IDs. The returned provider adds `decisionModel(alias): Experimental_DecisionModelV4`. String aliases resolve through Gateway by default, or an explicitly configured default provider with a `decisionModel` method. Unknown aliases use the fallback provider's decision factory when available; model failures and unsupported questions do not trigger substitution. Unavailable decision capabilities or models throw `NoSuchModelError` with `modelType: 'decisionModel'`; unknown registry providers throw `NoSuchProviderError`. These capabilities are structural extensions and are not added to the stable `ProviderV4` contract. Direct decision string IDs default to Gateway when no default provider is configured. See [Decisions](/docs/ai-sdk-core/decisions#model-aliases-and-registries) for runnable usage patterns. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_decide description: Decide answers to typed questions against shared state with a decision model. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/decide" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). ```ts import { experimental_decide } from 'ai'; ``` Decides answers to a nonempty map of `choice`, `score`, `boolean`, and `box` questions against one state. See [Decisions](/docs/ai-sdk-core/decisions) for examples and semantics. ## Parameters | Parameter | Type | Description | | ----------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `model` | `Experimental_DecisionModel` | Required experimental v4 model instance or a string ID resolved by Gateway or an explicitly configured decision-capable default provider. | | `state` | `Experimental_DecisionState` | Required string, JSON object, or ordered array of text, file, and JSON parts. | | `questions` | `Record` | Required nonempty question map. | | `maxOutputTokens` | `number` | Optional maximum output tokens, an integer >= 1. | | `reasoning` | `'provider-default' \| 'none' \| 'minimal' \| 'low' \| 'medium' \| 'high' \| 'xhigh' \| 'max'` | Optional reasoning effort. Language-model adapters default to `'none'`. Native decision providers return an unsupported-setting warning. | | `maxRetries` | `number` | Nonnegative integer; defaults to 2. | | `abortSignal` | `AbortSignal` | Cancels the decisions request. | | `headers` | `Record` | Additional HTTP headers. | | `providerOptions` | `ProviderOptions` | Provider-specific options. | | `telemetry` | `TelemetryOptions` | Telemetry configuration, including per-call integrations, input/output recording, and a function ID. | | `runtimeContext` | `Record` | Context available to lifecycle callbacks and selectively included in telemetry with `telemetry.includeRuntimeContext`. | | `onStart` | `(event: Experimental_DecideStartEvent) => void` | Called when the decision operation begins. | | `onEnd` | `(event: Experimental_DecideEndEvent) => void` | Called when the decision operation completes successfully. | Set `reasoning: 'provider-default'` to use the underlying language model's default reasoning effort instead of the adapter's `'none'` default. See [Lifecycle Callbacks](/docs/ai-sdk-core/lifecycle-callbacks#experimental_decide) for the complete `onStart` and `onEnd` event fields. ## Questions Each question has a `type` and can include `instructions` and `criteria` as described below. Instructions and criterion descriptions accept strings, JSON objects, or JSON arrays. | Type | Instructions | Criteria | | --------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------- | | `choice` | Optional; may be `null` | Required nonempty map of option names to descriptions | | `score` | Optional; may be `null` | Required nonempty array of levels, indexed from zero | | `boolean` | Optional; may be `null` | Optional object with `true` and/or `false` descriptions; at least one must be non-null when instructions are absent | | `box` | Required and non-null | None | Criterion descriptions may be `null` in the shared type, but providers can impose stricter limits. Perplexity requires non-null Score levels and accepts 1–10 levels; a single level yields score `0` and probability `1`. Other providers may require at least two levels. Optional instructions also remain subject to the provider's API contract. See [question semantics](/docs/ai-sdk-core/decisions#question-types) and [Perplexity decision examples](/providers/ai-sdk-providers/perplexity#decision-models). ## Result Returns `Promise>`: - `answers`: One typed answer per question ID, with literal Choice option inference. - `usage`: `inputTokens`, `outputTokens`, and `totalTokens`, each possibly undefined. - `warnings`: Provider warnings, also passed to the SDK warning logger. - `rounding`: Optional provider-declared decimal precision for probabilities and scores. - `providerMetadata`: Optional provider-specific metadata. - `response`: Timestamp, model ID, and optional response ID, headers, and body. ## Provider specification `Experimental_DecisionModelV4` is exported from `@ai-sdk/provider` and declares `specificationVersion: 'v4'`, `provider`, `modelId`, `supportedQuestionTypes`, and `doDecide(options)`. Decision models are isolated from stable `ProviderV4`. The public core types are `Experimental_DecisionModel`, `Experimental_DecisionState`, `Experimental_DecisionStatePart`, `Experimental_DecisionQuestion`, `Experimental_DecisionAnswer`, and `Experimental_DecisionResult`. All decision-specific classes use the `Decision` prefix, with `Experimental_` aliases at package boundaries. ## Errors Unsupported types throw `Experimental_DecisionUnsupportedQuestionTypeError` before provider I/O. Invalid inputs throw `InvalidArgumentError`; malformed answers throw `InvalidResponseDataError`. Questions the model refuses throw `Experimental_DecisionRefusalError`. Invalid answers and refusals are not retried. Neither partial results nor missing probability synthesis are supported. ## Model resolution Use `registry.decisionModel('provider:model')` or `customProvider({ decisionModels: { alias: model } }).decisionModel('alias')` to resolve models. Strings passed directly to `experimental_decide` use `globalThis.AI_SDK_DEFAULT_PROVIDER.decisionModel(id)` when a default provider is configured, or Gateway when no default provider is configured. Resolution errors use the existing `NoSuchModelError` and `NoSuchProviderError` classes with `modelType: 'decisionModel'`. Model instances and resolved models must implement v4; other versions throw `UnsupportedModelVersionError`. See [model resolution examples](/docs/ai-sdk-core/decisions#model-aliases-and-registries). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: DefaultGeneratedFile description: API Reference for DefaultGeneratedFile. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/default-generated-file" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). A concrete implementation of the `GeneratedFile` interface that provides lazy conversion between base64 and Uint8Array formats. ```ts import { DefaultGeneratedFile } from 'ai'; const file = new DefaultGeneratedFile({ data: uint8ArrayData, mediaType: 'image/png', }); console.log(file.base64); // Automatically converted to base64 console.log(file.uint8Array); // Original Uint8Array ``` ## Import ``` import { DefaultGeneratedFile } from "ai" ``` ## Constructor ### Parameters - `data` (`string | Uint8Array`): The file data as either a base64 encoded string or Uint8Array. - `mediaType` (`string`): The IANA media type of the file. ## Properties - `base64` (`string`): File as a base64 encoded string. Lazily converted from Uint8Array if needed. - `uint8Array` (`Uint8Array`): File as a Uint8Array. Lazily converted from base64 if needed. - `mediaType` (`string`): The IANA media type of the file. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: defaultInstructionsMiddleware description: Middleware that applies default instructions to language model calls url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/default-instructions-middleware" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `defaultInstructionsMiddleware` applies default instructions to language model calls that do not already contain a system message. This is useful for configuring reusable model behavior while allowing call-level `instructions` to take precedence. ## Import ``` import { defaultInstructionsMiddleware } from "ai" ``` ## API Signature ```ts function defaultInstructionsMiddleware(options: { instructions: Instructions; }): LanguageModelMiddleware; ``` ### Parameters - `instructions` (`string | SystemModelMessage | Array`): Default instructions to prepend when a call does not already contain a system message. ### Returns Returns a [LanguageModelMiddleware](/docs/ai-sdk-core/middleware) that: - Prepends the configured instructions to calls without a system message. - Preserves instruction-level `providerOptions`. - Leaves calls containing any system message unchanged, so call-level instructions take precedence. - Applies to both non-streaming and streaming language model calls. ## Usage Example ```ts import { defaultInstructionsMiddleware, generateText, wrapLanguageModel, } from 'ai'; const model = wrapLanguageModel({ model: "anthropic/claude-sonnet-5.5", middleware: defaultInstructionsMiddleware({ instructions: 'You are a concise technical assistant.', }), }); const defaultResult = await generateText({ model, prompt: 'Explain HTTP caching.', }); const overriddenResult = await generateText({ model, instructions: 'Explain concepts for a complete beginner.', prompt: 'Explain HTTP caching.', }); ``` You can attach provider options to default instructions by using a `SystemModelMessage`: ```ts const model = wrapLanguageModel({ model: "anthropic/claude-sonnet-5.5", middleware: defaultInstructionsMiddleware({ instructions: { role: 'system', content: 'You are a concise technical assistant.', providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' }, }, }, }, }), }); ``` This middleware provides defaults, not enforced instructions. Any system message in the normalized prompt suppresses the defaults. Only use `allowSystemInMessages` with trusted message histories, because an untrusted system message could override the configured defaults. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: defaultSettingsMiddleware description: Middleware that applies default settings for language models url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/default-settings-middleware" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `defaultSettingsMiddleware` is a middleware function that applies default settings to language model calls. This is useful when you want to establish consistent default parameters across multiple model invocations. To apply default system instructions, use [`defaultInstructionsMiddleware`](/docs/reference/ai-sdk-core/default-instructions-middleware). ```ts import { defaultSettingsMiddleware } from 'ai'; const middleware = defaultSettingsMiddleware({ settings: { temperature: 0.7, maxOutputTokens: 1000, // other settings... }, }); ``` ## Import ``` import { defaultSettingsMiddleware } from "ai" ``` ## API Signature ### Parameters The middleware accepts a configuration object with the following properties: - `settings`: An object containing default parameter values to apply to language model calls. These can include any valid `LanguageModelV4CallOptions` properties and optional provider metadata. ### Returns Returns a middleware object that: - Merges the default settings with the parameters provided in each model call - Ensures that explicitly provided parameters take precedence over defaults - Merges provider metadata objects ### Usage Example ```ts import { streamText, wrapLanguageModel, defaultSettingsMiddleware } from 'ai'; // Create a model with default settings const modelWithDefaults = wrapLanguageModel({ model: gateway('anthropic/claude-sonnet-4.5'), middleware: defaultSettingsMiddleware({ settings: { providerOptions: { openai: { reasoningEffort: 'high', }, }, }, }), }); // Use the model - default settings will be applied const result = await streamText({ model: modelWithDefaults, prompt: 'Your prompt here', // These parameters will override the defaults temperature: 0.8, }); ``` ## How It Works The middleware: 1. Takes a set of default settings as configuration 2. Merges these defaults with the parameters provided in each model call 3. Ensures that explicitly provided parameters take precedence over defaults 4. Merges provider metadata objects from both sources --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: dynamicTool description: Helper function for creating dynamic tools with unknown types url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/dynamic-tool" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `dynamicTool` function creates tools where the input and output types are not known at compile time. This is useful for scenarios such as: - MCP (Model Context Protocol) tools without schemas - User-defined functions loaded at runtime - Tools loaded from external sources or databases - Dynamic tool generation based on user input Unlike the regular `tool` function, `dynamicTool` accepts and returns `unknown` types, allowing you to work with tools that have runtime-determined schemas. ```ts {1,4,9,11} import { dynamicTool } from 'ai'; import { z } from 'zod'; export const customTool = dynamicTool({ description: 'Execute a custom user-defined function', inputSchema: z.object({ action: z.string(), parameters: z.unknown() }), // input is typed as 'unknown' execute: async input => { const { action, parameters } = input as any; // Execute your dynamic logic return { result: `Executed ${action} with ${JSON.stringify(parameters)}`, }; }, }); ``` ## Import ``` import { dynamicTool } from "ai" ``` ## API Signature ### Parameters - `tool` (`Object`): The dynamic tool definition. - `Object` - `description?` (`string | ((options: { context: Context; experimental_sandbox?: Experimental_SandboxSession }) => string)`): Information about the purpose of the tool including details on how and when it can be used by the model. Provide a string for a fixed description, or a function to derive the description from the tool-specific context and optional experimental sandbox before each model call. - `deferLoading?` (`boolean`): Keep this tool out of the model context until toolSearch discovers it. Supports direct calling or code mode with toolDiscovery: 'conversation'. Discovered tools become available on the next model step. Defaults to false. - `title?` (`string`): Deprecated. Use \`providerMetadata\` for source-specific tool display metadata. - `needsApproval?` (`boolean | ((input: unknown, options: { toolCallId: string; messages: ModelMessage[]; context: Context }) => boolean | Promise)`): Deprecated. For \`generateText\`, \`streamText\`, and \`ToolLoopAgent\`, configure approval with \`toolApproval\` instead. Existing \`needsApproval\` usages still work as a compatibility fallback. When used, it can be a boolean or a function that receives the tool input plus execution metadata. - `inputSchema` (`FlexibleSchema`): The schema of the input that the tool expects. While the type is unknown, a schema is still required for validation. You can use Zod schemas with z.unknown() or z.any() for fully dynamic inputs. - `execute` (`ToolExecuteFunction`): An async function that is called with the arguments from the tool call. The input is typed as unknown and must be validated/cast at runtime. - `ToolExecutionOptions` - `toolCallId` (`string`): The ID of the tool call. - `messages` (`ModelMessage[]`): Messages that were sent to the language model. - `abortSignal?` (`AbortSignal`): An optional abort signal. - `context` (`Context`): Tool-specific context passed into tool execution. This value comes from the matching entry in \`toolsContext\`. - `outputSchema?` (`Zod Schema | JSON Schema`): The schema of the output that the tool produces. Used for validation and type inference. - `toModelOutput?` (`({toolCallId: string; input: unknown; output: unknown}) => ToolResultOutput | PromiseLike`): Optional conversion function that maps the tool result to an output that can be used by the language model. - `onInputStart?` (`(options: ToolExecutionOptions) => void | PromiseLike`): Optional function that is called when the model starts generating the tool input. In non-streaming contexts, it is called immediately before onInputAvailable. - `onInputDelta?` (`(options: { inputTextDelta: string } & ToolExecutionOptions) => void | PromiseLike`): Optional function that is called when an argument streaming delta is available. Only called when the tool is used in a streaming context. - `onInputAvailable?` (`(options: { input: unknown } & ToolExecutionOptions) => void | PromiseLike`): Optional function that is called when a tool call can be started, even if the execute function is not provided. - `providerOptions?` (`ProviderOptions`): Additional provider-specific metadata. - `metadata?` (`JSONObject`): Optional metadata about the tool itself (e.g. its source). It is propagated onto the resulting tool call's toolMetadata so consumers can read it from tool call/result parts and UI message parts. Useful for sources of dynamic tools (e.g. an MCP server) to identify themselves. ### Returns A `Tool` with `type: 'dynamic'` that can be used with `generateText`, `streamText`, and other AI SDK functions. ## Type-Safe Usage When using dynamic tools alongside static tools, you need to check the `dynamic` flag for proper type narrowing: ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { // Static tool with known types weather: weatherTool, // Dynamic tool with unknown types custom: dynamicTool({ /* ... */ }), }, onStepEnd: ({ toolCalls, toolResults }) => { for (const toolCall of toolCalls) { if (toolCall.dynamic) { // Dynamic tool: input/output are 'unknown' console.log('Dynamic tool:', toolCall.toolName); console.log('Input:', toolCall.input); continue; } // Static tools have full type inference switch (toolCall.toolName) { case 'weather': // TypeScript knows the exact types console.log(toolCall.input.location); // string break; } } }, }); ``` ## Usage with `useChat` When used with useChat (`UIMessage` format), dynamic tools appear as `dynamic-tool` parts: ```tsx { message.parts.map(part => { switch (part.type) { case 'dynamic-tool': return (

Tool: {part.toolName}

{JSON.stringify(part.input, null, 2)}
); // ... handle other part types } }); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: embedMany description: API Reference for embedMany. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/embed-many" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Embed several values using an embedding model. `embedMany` automatically splits large requests into smaller chunks when the model has a limit on either the number of embeddings or the UTF-8 input bytes that can be processed in a single call. Providers can use a conservative byte budget to keep requests below aggregate token limits without adding a tokenizer to the AI SDK core package. An individual value larger than the byte budget is sent in its own call because splitting it would change the resulting embedding. ```ts import { embedMany } from 'ai'; const { embeddings } = await embedMany({ model: 'openai/text-embedding-3-small', values: [ 'sunny day at the beach', 'rainy afternoon in the city', 'snowy night in the mountains', ], }); ``` ## Import ``` import { embedMany } from "ai" ``` ## API Signature ### Parameters - `model` (`EmbeddingModel`): The embedding model to use. Example: openai.embeddingModel('text-embedding-3-small') - `values` (`Array`): The values to embed. - `dimensions?` (`number`): Requested output embedding dimensions. Must be a positive integer. Forwarded to the model; requires support in the provider implementation. Provider mappings are being added separately. Use providerOptions until your provider supports this setting. - `maxRetries?` (`number`): Maximum number of retries. Set to 0 to disable retries. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal that can be used to cancel the call. - `headers?` (`Record`): Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers. - `providerOptions?` (`ProviderOptions`): Provider-specific options that are passed through to the provider. - `maxParallelCalls?` (`number`): Maximum number of concurrent requests when a request is split into multiple model calls. Must be greater than 0 when chunking is active and the model supports parallel calls; invalid values throw AI\_InvalidArgumentError. Default: Infinity. - `runtimeContext?` (`RUNTIME_CONTEXT`): User-defined runtime context passed to lifecycle callbacks. Defaults to an empty object. Telemetry integrations only receive top-level properties explicitly included with telemetry.includeRuntimeContext. - `telemetry?` (`TelemetryOptions`): Telemetry configuration. - `TelemetryOptions` - `isEnabled?` (`boolean`): Enable or disable telemetry. Enabled by default. Set to \`false\` to opt out. - `recordInputs?` (`boolean`): Enable or disable input recording. Enabled by default. - `recordOutputs?` (`boolean`): Enable or disable output recording. Enabled by default. - `functionId?` (`string`): Identifier for this function. Used to group telemetry data by function. - `includeRuntimeContext?` (`{ [KEY in keyof RUNTIME_CONTEXT]?: boolean }`): Top-level runtime context properties to include in telemetry. Only properties set to true are included. All properties are excluded by default. User callbacks still receive the full context. - `integrations?` (`Telemetry | Telemetry[]`): Per-call telemetry integrations that receive lifecycle events. When provided, these replace any globally registered integrations for this call. - `onStart?` (`(event: EmbedStartEvent) => PromiseLike | void`): Callback that is called when the embedMany operation begins, before the embedding model is called. Errors thrown in this callback are silently caught and do not break the embedding flow. - `EmbedStartEvent` - `runtimeContext` (`RUNTIME_CONTEXT`): The full, unfiltered runtime context supplied to the operation. - `callId` (`string`): Unique identifier for this embedMany call. - `operationId` (`string`): Identifies the operation type ('ai.embedMany'). - `model` (`{ provider: string; modelId: string }`): The embedding model being used. - `value` (`string | Array`): The values being embedded (array of strings for embedMany). - `dimensions?` (`number`): Requested output embedding dimensions, when specified. - `maxRetries` (`number`): Maximum number of retries for failed requests. - `abortSignal` (`AbortSignal | undefined`): Abort signal for cancelling the operation. - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `providerOptions` (`ProviderOptions | undefined`): Additional provider-specific options. - `onEnd?` (`(event: EmbedEndEvent) => PromiseLike | void`): Callback that is called when the embedMany operation completes, after all embedding model calls return. Errors thrown in this callback are silently caught and do not break the embedding flow. - `EmbedEndEvent` - `runtimeContext` (`RUNTIME_CONTEXT`): The full, unfiltered runtime context supplied to the operation. - `callId` (`string`): Unique identifier for this embedMany call. - `operationId` (`string`): Identifies the operation type ('ai.embedMany'). - `model` (`{ provider: string; modelId: string }`): The embedding model that was used. - `value` (`string | Array`): The values that were embedded (array of strings for embedMany). - `embedding` (`Embedding | Array`): The resulting embedding vectors (array of embeddings for embedMany). - `usage` (`EmbeddingModelUsage`): Token usage for the embedding operation. - `warnings` (`Array`): Warnings from the embedding model. - `providerMetadata` (`ProviderMetadata | undefined`): Optional provider-specific metadata. - `response` (`Array<{ headers?: Record; body?: unknown } | undefined>`): Response data from each embedding call. There may be multiple responses if the request was split into chunks. ### Returns - `values` (`Array`): The values that were embedded. - `embeddings` (`number[][]`): The embeddings. They are in the same order as the values. - `usage` (`EmbeddingModelUsage`): The token usage for generating the embeddings. - `EmbeddingModelUsage` - `tokens` (`number`): The total number of input tokens. - `warnings` (`Warning[]`): Warnings from the model provider (e.g. unsupported settings). - `providerMetadata?` (`ProviderMetadata | undefined`): Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. - `responses?` (`Array<{ headers?: Record; body?: unknown } | undefined>`): Optional raw response data from each chunk request. There may be multiple responses if the request was split into multiple chunks. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: embed description: API Reference for embed. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/embed" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Generate an embedding for a single value using an embedding model. This is ideal for use cases where you need to embed a single value to e.g. retrieve similar items or to use the embedding in a downstream task. ```ts import { embed } from 'ai'; const { embedding } = await embed({ model: 'openai/text-embedding-3-small', value: 'sunny day at the beach', }); ``` ## Import ``` import { embed } from "ai" ``` ## API Signature ### Parameters - `model` (`EmbeddingModel`): The embedding model to use. Example: openai.embeddingModel('text-embedding-3-small') - `value` (`VALUE`): The value to embed. The type depends on the model. - `dimensions?` (`number`): Requested output embedding dimensions. Must be a positive integer. Forwarded to the model; requires support in the provider implementation. Provider mappings are being added separately. Use providerOptions until your provider supports this setting. - `maxRetries?` (`number`): Maximum number of retries. Set to 0 to disable retries. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal that can be used to cancel the call. - `headers?` (`Record`): Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers. - `providerOptions?` (`ProviderOptions`): Provider-specific options that are passed through to the provider. - `runtimeContext?` (`RUNTIME_CONTEXT`): User-defined runtime context passed to lifecycle callbacks. Defaults to an empty object. Telemetry integrations only receive top-level properties explicitly included with telemetry.includeRuntimeContext. - `telemetry?` (`TelemetryOptions`): Telemetry configuration. - `TelemetryOptions` - `isEnabled?` (`boolean`): Enable or disable telemetry. Enabled by default. Set to \`false\` to opt out. - `recordInputs?` (`boolean`): Enable or disable input recording. Enabled by default. - `recordOutputs?` (`boolean`): Enable or disable output recording. Enabled by default. - `functionId?` (`string`): Identifier for this function. Used to group telemetry data by function. - `includeRuntimeContext?` (`{ [KEY in keyof RUNTIME_CONTEXT]?: boolean }`): Top-level runtime context properties to include in telemetry. Only properties set to true are included. All properties are excluded by default. User callbacks still receive the full context. - `integrations?` (`Telemetry | Telemetry[]`): Per-call telemetry integrations that receive lifecycle events. When provided, these replace any globally registered integrations for this call. - `onStart?` (`(event: EmbedStartEvent) => PromiseLike | void`): Callback that is called when the embed operation begins, before the embedding model is called. Errors thrown in this callback are silently caught and do not break the embedding flow. - `EmbedStartEvent` - `runtimeContext` (`RUNTIME_CONTEXT`): The full, unfiltered runtime context supplied to the operation. - `callId` (`string`): Unique identifier for this embed call. - `operationId` (`string`): Identifies the operation type ('ai.embed'). - `model` (`{ provider: string; modelId: string }`): The embedding model being used. - `value` (`string | Array`): The value being embedded. - `dimensions?` (`number`): Requested output embedding dimensions, when specified. - `maxRetries` (`number`): Maximum number of retries for failed requests. - `abortSignal` (`AbortSignal | undefined`): Abort signal for cancelling the operation. - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `providerOptions` (`ProviderOptions | undefined`): Additional provider-specific options. - `onEnd?` (`(event: EmbedEndEvent) => PromiseLike | void`): Callback that is called when the embed operation completes, after the embedding model returns. Errors thrown in this callback are silently caught and do not break the embedding flow. - `EmbedEndEvent` - `runtimeContext` (`RUNTIME_CONTEXT`): The full, unfiltered runtime context supplied to the operation. - `callId` (`string`): Unique identifier for this embed call. - `operationId` (`string`): Identifies the operation type ('ai.embed'). - `model` (`{ provider: string; modelId: string }`): The embedding model that was used. - `value` (`string | Array`): The value that was embedded. - `embedding` (`Embedding | Array`): The resulting embedding vector. - `usage` (`EmbeddingModelUsage`): Token usage for the embedding operation. - `warnings` (`Array`): Warnings from the embedding model. - `providerMetadata` (`ProviderMetadata | undefined`): Optional provider-specific metadata. - `response` (`{ headers?: Record; body?: unknown } | undefined`): Optional response data including headers and body. ### Returns - `value` (`VALUE`): The value that was embedded. - `embedding` (`number[]`): The embedding of the value. - `usage` (`EmbeddingModelUsage`): The token usage for generating the embeddings. - `EmbeddingModelUsage` - `tokens` (`number`): The number of tokens used in the embedding. - `warnings` (`Warning[]`): Warnings from the model provider (e.g. unsupported settings). - `response?` (`Response`): Optional response data. - `Response` - `headers?` (`Record`): Response headers. - `body?` (`unknown`): The response body. - `providerMetadata?` (`ProviderMetadata | undefined`): Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: extractJsonMiddleware description: Middleware that extracts JSON from text content by stripping markdown code fences url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/extract-json-middleware" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `extractJsonMiddleware` is a middleware function that extracts JSON from text content by stripping markdown code fences and other formatting. This is useful when using `Output.object()` with models that wrap JSON responses in markdown code blocks (e.g., ` ```json ... ``` `). ```ts import { extractJsonMiddleware } from 'ai'; const middleware = extractJsonMiddleware(); ``` ## Import ``` import { extractJsonMiddleware } from "ai" ``` ## API Signature ### Parameters - `transform?` (`(text: string) => string`): Custom transform function to apply to text content. Receives the raw text and should return the transformed text. If not provided, the default transform strips markdown code fences. ### Returns Returns a middleware object that: - Processes both streaming and non-streaming responses - Strips markdown code fences (` ```json ` and ` ``` `) from text content - Applies custom transformations when a `transform` function is provided - Maintains proper streaming behavior with efficient buffering ## Usage Examples ### Basic Usage Strip markdown code fences from model responses when using structured output: ```ts import { generateText, wrapLanguageModel, extractJsonMiddleware, Output, } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: wrapLanguageModel({ model: yourModel, middleware: extractJsonMiddleware(), }), output: Output.object({ schema: z.object({ recipe: z.object({ name: z.string(), steps: z.array(z.string()), }), }), }), prompt: 'Generate a lasagna recipe.', }); console.log(result.output); ``` ### With Streaming The middleware also works with streaming responses: ```ts import { streamText, wrapLanguageModel, extractJsonMiddleware, Output, } from 'ai'; import { z } from 'zod'; const { partialOutputStream } = streamText({ model: wrapLanguageModel({ model: yourModel, middleware: extractJsonMiddleware(), }), output: Output.object({ schema: z.object({ recipe: z.object({ ingredients: z.array(z.string()), steps: z.array(z.string()), }), }), }), prompt: 'Generate a detailed recipe.', }); for await (const partialObject of partialOutputStream) { console.log(partialObject); } ``` ### Custom Transform Function For models that use different formatting, you can provide a custom transform: ```ts import { extractJsonMiddleware } from 'ai'; const middleware = extractJsonMiddleware({ transform: text => text .replace(/^PREFIX/, '') .replace(/SUFFIX$/, '') .trim(), }); ``` ## How It Works The middleware handles text content in two ways: ### Non-Streaming (generateText) 1. Receives the complete response from the model 2. Applies the transform function to strip markdown fences (or custom formatting) 3. Returns the cleaned text content ### Streaming (streamText) 1. Buffers initial content to detect markdown fence prefixes (` ```json\n `) 2. If a fence is detected, strips the prefix and switches to streaming mode 3. Maintains a small suffix buffer to handle the closing fence (` \n``` `) 4. When the stream ends, strips any trailing fence from the buffer 5. For custom transforms, buffers all content and applies the transform at the end This approach ensures efficient streaming while correctly handling code fences that may be split across multiple chunks. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: extractReasoningMiddleware description: Middleware that extracts reasoning sections from generated text using configurable delimiters url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/extract-reasoning-middleware" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `extractReasoningMiddleware` is a middleware function that extracts delimited reasoning sections from generated text and exposes them separately from the main text content. This is particularly useful when you want to separate an AI model's reasoning process from its final output. ```ts import { extractReasoningMiddleware } from 'ai'; const middleware = extractReasoningMiddleware({ tagName: 'reasoning', separator: '\n', }); ``` For models that emit non-XML delimiters, pass literal `opening` and `closing` strings. For example, Gemma 4's raw thought-channel output uses a newline in its opening delimiter: ```ts const middleware = extractReasoningMiddleware({ tagName: { opening: '<|channel>thought\n', closing: '' }, }); ``` Custom delimiters must be non-empty. They are matched literally, including characters such as `|`, and can span multiple streaming chunks. Empty reasoning blocks are removed from the answer text as well. Use this middleware when the server returns these markers in text; providers that already return reasoning separately do not need it. ## Import ``` import { extractReasoningMiddleware } from "ai" ``` ## API Signature ### Parameters - `tagName` (`string | { opening: string; closing: string }`): An XML tag name without angle brackets (e.g. "think" for \ / \), or an object containing non-empty literal opening and closing delimiters. - `separator?` (`string`): The separator to use between reasoning and text sections. Defaults to "\n" - `startWithReasoning?` (`boolean`): Starts with reasoning tokens. Set to true when the response always starts with reasoning and the opening delimiter is omitted. Defaults to false. ### Returns Returns a middleware object that: - Processes both streaming and non-streaming responses - Extracts content between the specified delimiters as reasoning - Removes the delimiters and reasoning from the main text - Emits reasoning content parts separately from text content parts - Maintains proper separation between text sections using the specified separator ### Type Parameters The middleware works with the `LanguageModelV4StreamPart` type for streaming responses. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: filterActiveTools description: Filter a tool set to the currently active tools (API Reference) url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/filter-active-tools" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `filterActiveTools` is an experimental feature. `filterActiveTools` filters a tool set to only the string tool names listed in `activeTools`. If `activeTools` is `undefined`, it returns the original tool set. If `tools` is `undefined`, it returns `undefined`. `filterActiveTools` is useful for limiting which tools are sent to a model in a particular step. ```ts import { experimental_filterActiveTools as filterActiveTools, tool } from 'ai'; import { z } from 'zod'; const tools = { weather: tool({ description: 'Get the weather for a city', inputSchema: z.object({ city: z.string() }), }), time: tool({ description: 'Get the current time for a city', inputSchema: z.object({ city: z.string() }), }), }; const activeTools = ['weather'] as const; const filteredTools = filterActiveTools({ tools, activeTools, }); ``` ## Import ``` import { experimental_filterActiveTools as filterActiveTools } from "ai" ``` ## API Signature ### Parameters - `tools` (`ToolSet | undefined`): The tool set to filter. - `activeTools` (`ActiveTools`): The names of the tools that should remain active. When omitted, all tools are returned. ### Returns The filtered tool set, or `undefined` when `tools` is `undefined`. When `activeTools` is provided as a literal array such as `["weather"] as const`, TypeScript narrows the return type to only that subset of tools. ## Types ### `ActiveTools` ```ts type ActiveTools = | ReadonlyArray | undefined; ``` `ActiveTools` only accepts string keys from the tool set because tool names are strings at runtime. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: generateId description: Generate a unique identifier (API Reference) url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-id" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Generates a unique identifier. This is the same id generator used by the AI SDK. ```ts import { generateId } from 'ai'; const id = generateId(); ``` ## Import ``` import { generateId } from "ai" ``` ## API Signature ### Returns A string representing the generated ID. ## See also - [`createIdGenerator()`](/docs/reference/ai-sdk-core/create-id-generator) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: generateImage description: API Reference for generateImage. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-image" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Generates images based on a given prompt using an image model. It is ideal for use cases where you need to generate images programmatically, such as creating visual content or generating images for data augmentation. ```ts import { generateImage } from 'ai'; const { images } = await generateImage({ model: "openai/gpt-image-2.5-sunburst", prompt: 'A futuristic cityscape at sunset', n: 3, size: '1024x1024', }); console.log(images); ``` ## Import ``` import { generateImage } from "ai" ``` ## API Signature ### Parameters - `model` (`ImageModelV4`): The image model to use. - `prompt` (`string | GenerateImagePrompt`): The input prompt to generate the image from. - `object` - `images` (`Array`): an image item can be one of: base64-encoded string, a \`Uint8Array\`, an \`ArrayBuffer\`, or a \`Buffer\`. - `text` (`string`): The text prompt. - `mask` (`DataContent`): base64-encoded string, a \`Uint8Array\`, an \`ArrayBuffer\`, or a \`Buffer\`. - `n?` (`number`): Number of images to generate. - `size?` (`string`): Size of the images to generate. Format: \`\{width}x\{height}\`. - `aspectRatio?` (`string`): Aspect ratio of the images to generate. Format: \`\{width}:\{height}\`. - `seed?` (`number`): Seed for the image generation. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options. - `maxImagesPerCall?` (`number`): Maximum number of images to generate per API call. When n exceeds this value, multiple API calls will be made. - `maxRetries?` (`number`): Maximum number of retries per image model call, including retries after unclassified empty responses. Empty responses marked as not retryable by the provider are not retried. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal to cancel the call. - `headers?` (`Record`): Additional HTTP headers for the request. ### Returns - `image` (`GeneratedFile`): The first image that was generated. - `GeneratedFile` - `base64` (`string`): Image as a base64 encoded string. - `uint8Array` (`Uint8Array`): Image as a Uint8Array. - `mediaType` (`string`): The IANA media type of the image. - `images` (`Array`): All images that were generated. - `GeneratedFile` - `base64` (`string`): Image as a base64 encoded string. - `uint8Array` (`Uint8Array`): Image as a Uint8Array. - `mediaType` (`string`): The IANA media type of the image. - `warnings` (`Warning[]`): Warnings from the model provider (e.g. unsupported settings). - `usage` (`ImageModelUsage`): The usage statistics for the image generation. - `ImageModelUsage` - `imagesGenerated` (`number`): The total number of images generated. - `providerMetadata?` (`ImageModelProviderMetadata`): Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. An \`images\` key is always present in the metadata and is an array with the same length as the top level \`images\` key. Details depend on the provider. - `responses` (`Array`): Response metadata from the provider. There may be multiple responses if we made multiple calls to the model. - `ImageModelResponseMetadata` - `timestamp` (`Date`): Timestamp for the start of the generated response. - `modelId` (`string`): The ID of the response model that was used to generate the response. - `headers?` (`Record`): Response headers. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: generateSpeech description: API Reference for generateSpeech. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-speech" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Generates speech audio from text. ```ts import { generateSpeech } from 'ai'; import { openai } from '@ai-sdk/openai'; const { audio } = await generateSpeech({ model: openai.speech('gpt-4o-mini-tts'), text: 'Hello from the AI SDK!', voice: 'alloy', }); console.log(audio); ``` ## Examples ### OpenAI ```ts import { generateSpeech } from 'ai'; import { openai } from '@ai-sdk/openai'; const { audio } = await generateSpeech({ model: openai.speech('gpt-4o-mini-tts'), text: 'Hello from the AI SDK!', voice: 'alloy', }); ``` ### ElevenLabs ```ts import { generateSpeech } from 'ai'; import { elevenLabs } from '@ai-sdk/elevenlabs'; const { audio } = await generateSpeech({ model: elevenLabs.speech('eleven_multilingual_v2'), text: 'Hello from the AI SDK!', voice: 'your-voice-id', // Required: get this from your ElevenLabs account }); ``` ## Import ``` import { generateSpeech } from "ai" ``` ## API Signature ### Parameters - `model` (`SpeechModelV4`): The speech model to use. - `text` (`string`): The text to generate the speech from. - `voice?` (`string`): The voice to use for the speech. - `outputFormat?` (`string`): The output format to use for the speech, such as "mp3", "wav", or headerless "audio/l16", "audio/mulaw", and "audio/alaw". Supported formats and defaults vary by provider and model. - `instructions?` (`string`): Instructions for the speech generation. - `speed?` (`number`): The speed of the speech generation. - `language?` (`string`): The language for speech generation. This should be an ISO 639-1 language code (e.g. "en", "es", "fr") or "auto" for automatic language detection. Provider support varies. - `providerOptions?` (`Record`): Additional provider-specific options. - `maxRetries?` (`number`): Maximum number of retries. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal to cancel the call. - `headers?` (`Record`): Additional HTTP headers for the request. - `telemetry?` (`TelemetryOptions`): Telemetry configuration. Supports functionId, recordInputs, recordOutputs, isEnabled, and per-call integrations. ### Returns - `audio` (`GeneratedAudioFile`): The generated audio. - `GeneratedAudioFile` - `base64` (`string`): Audio as a base64 encoded string. - `uint8Array` (`Uint8Array`): Audio as a Uint8Array. - `mediaType` (`string`): Media type of the audio (e.g. "audio/mpeg"). - `format` (`string`): Format of the audio (e.g. "mp3"). - `warnings` (`Warning[]`): Warnings from the model provider (e.g. unsupported settings). - `providerMetadata?` (`Record`): Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. - `responses` (`Array`): Response metadata from the provider. There may be multiple responses if we made multiple calls to the model. - `SpeechModelResponseMetadata` - `timestamp` (`Date`): Timestamp for the start of the generated response. - `modelId` (`string`): The ID of the response model that was used to generate the response. - `body?` (`unknown`): Optional response body. - `headers?` (`Record`): Response headers. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: generateText description: API Reference for generateText. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-text" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Generates text and calls tools for a given prompt using a language model. It is ideal for non-interactive use cases such as automation tasks where you need to write text (e.g. drafting emails or summarizing web pages) and for agents that use tools. ```ts import { generateText } from 'ai'; const { text } = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', }); console.log(text); ``` For guidance on `runtimeContext`, `toolsContext`, tool `context`, and sensitive context filtering, see [Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context). To see `generateText` in action, check out [these examples](#examples). ## Import ``` import { generateText } from "ai" ``` ## API Signature ### Parameters - `model` (`LanguageModel`): The language model to use. Example: openai('gpt-6-astra') - `instructions` (`Instructions`): Instructions to use that specify the behavior of the model. - `prompt` (`string | Array`): The input prompt to generate the text from. - `messages` (`Array`): A list of messages that represent a conversation. Automatically converts UI messages from the useChat hook. - `SystemModelMessage` - `role` (`'system'`): The role for the system message. - `content` (`string`): The content of the message. - `UserModelMessage` - `role` (`'user'`): The role for the user message. - `content` (`string | Array`): The content of the message. - `TextPart` - `type` (`'text'`): The type of the message part. - `text` (`string`): The text content of the message part. - `ImagePart` - `type` (`'image'`): The type of the message part. - `image` (`string | Uint8Array | Buffer | ArrayBuffer | URL`): The image content of the message part. String are either base64 encoded content, base64 data URLs, or http(s) URLs. - `mediaType?` (`string`): The IANA media type of the image. Optional. - `FilePart` - `type` (`'file'`): The type of the message part. - `data` (`string | Uint8Array | Buffer | ArrayBuffer | URL`): The file content of the message part. String are either base64 encoded content, base64 data URLs, or http(s) URLs. - `mediaType` (`string`): The IANA media type of the file. - `AssistantModelMessage` - `role` (`'assistant'`): The role for the assistant message. - `content` (`string | Array`): The content of the message. - `TextPart` - `type` (`'text'`): The type of the message part. - `text` (`string`): The text content of the message part. - `ReasoningPart` - `type` (`'reasoning'`): The type of the message part. - `text` (`string`): The reasoning text. - `ReasoningFilePart` - `type` (`'reasoning-file'`): The type of the message part. - `data` (`string | Uint8Array | Buffer | ArrayBuffer | URL`): The file data. String are either base64 encoded content, base64 data URLs, or http(s) URLs. - `mediaType` (`string`): The IANA media type of the file. - `FilePart` - `type` (`'file'`): The type of the message part. - `data` (`string | Uint8Array | Buffer | ArrayBuffer | URL`): The file content of the message part. String are either base64 encoded content, base64 data URLs, or http(s) URLs. - `mediaType` (`string`): The IANA media type of the file. - `filename?` (`string`): The name of the file. - `ToolCallPart` - `type` (`'tool-call'`): The type of the message part. - `toolCallId` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `input` (`object based on zod schema`): Input (parameters) generated by the model to be used by the tool. - `ToolModelMessage` - `role` (`'tool'`): The role for the tool message. - `content` (`Array`): The content of the message. - `ToolResultPart` - `type` (`'tool-result'`): The type of the message part. - `toolCallId` (`string`): The id of the tool call the result corresponds to. - `toolName` (`string`): The name of the tool the result corresponds to. - `output` (`unknown`): The result returned by the tool after execution. - `isError?` (`boolean`): Whether the result is an error or an error message. - `allowSystemInMessages?` (`boolean`): Whether system messages are allowed in the \`prompt\` or \`messages\` fields. Defaults to false. System messages in the \`instructions\` option are always allowed. Enabling this for user-controlled messages can create a prompt injection risk. - `tools` (`ToolSet`): Tools that are accessible to and can be called by the model. The model needs to support calling tools. - `Tool` - `description?` (`string | ((options: { context: CONTEXT; experimental_sandbox?: Experimental_SandboxSession }) => string)`): Information about the purpose of the tool including details on how and when it can be used by the model. Provide a string for a fixed description, or a function to derive the description from the tool-specific context and optional experimental sandbox before each model call. - `inputSchema` (`Zod Schema | JSON Schema`): The schema of the input that the tool expects. The language model will use this to generate the input. It is also used to validate the output of the language model. Use descriptions to make the input understandable for the language model. You can either pass in a Zod schema or a JSON schema (using the \`jsonSchema\` function). - `execute?` (`async (parameters: T, options: ToolExecutionOptions) => RESULT`): An async function that is called with the arguments from the tool call and produces a result. If not provided, the tool will not be executed automatically. - `ToolExecutionOptions` - `toolCallId` (`string`): The ID of the tool call. You can use it e.g. when sending tool-call related information with stream data. - `messages` (`ModelMessage[]`): Messages that were sent to the language model to initiate the response that contained the tool call. The messages do not include the system prompt nor the assistant response that contained the tool call. - `abortSignal` (`AbortSignal`): An optional abort signal that indicates that the overall operation should be aborted. - `toolChoice?` (`"auto" | "none" | "required" | { "type": "tool", "toolName": string }`): The tool choice setting. It specifies how tools are selected for execution. The default is "auto". "none" disables tool execution. "required" requires tools to be executed. \{ "type": "tool", "toolName": string } specifies a specific tool to execute. - `maxOutputTokens?` (`number`): Maximum number of tokens to generate. - `temperature?` (`number`): Temperature setting. The value is passed through to the provider. The range depends on the provider and model. It is recommended to set either \`temperature\` or \`topP\`, but not both. - `topP?` (`number`): Nucleus sampling. The value is passed through to the provider. The range depends on the provider and model. It is recommended to set either \`temperature\` or \`topP\`, but not both. - `topK?` (`number`): Only sample from the top K options for each subsequent token. Used to remove "long tail" low probability responses. Recommended for advanced use cases only. You usually only need to use temperature. - `presencePenalty?` (`number`): Presence penalty setting. It affects the likelihood of the model to repeat information that is already in the prompt. The value is passed through to the provider. The range depends on the provider and model. - `frequencyPenalty?` (`number`): Frequency penalty setting. It affects the likelihood of the model to repeatedly use the same words or phrases. The value is passed through to the provider. The range depends on the provider and model. - `stopSequences?` (`string[]`): Sequences that will stop the generation of the text. If the model generates any of these sequences, it will stop generating further text. - `seed?` (`number`): The seed (integer) to use for random sampling. If set and supported by the model, calls will generate deterministic results. - `reasoning?` (`'provider-default' | 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'`): Controls how much reasoning the model performs before generating a response. When omitted, the provider's default behavior is used. \`'provider-default'\` explicitly requests the provider's default. Providers that do not support reasoning will emit a warning. Provider-specific settings take precedence over corresponding mapped fields. Partial configurations may still use portable reasoning to fill missing fields; summary and display settings can be combined with it. See the \[reasoning guide]\(/docs/ai-sdk-core/reasoning) for provider-specific mapping details. - `maxRetries?` (`number`): Maximum number of retries. Set to 0 to disable retries. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal that can be used to cancel the call. - `timeout?` (``number | { totalMs?: number; stepMs?: number; toolMs?: number; tools?: Partial> }``): Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, toolMs, and/or tools properties. totalMs sets the total timeout for the entire call. stepMs sets the timeout for each complete step, including model generation, retries, and local tool execution. toolMs sets the default timeout for all tool executions. tools sets per-tool timeout overrides using the pattern \{toolName}Ms (e.g. weatherMs, slowApiMs) that take precedence over toolMs - tool names are type-checked for autocomplete. If a tool takes longer than its timeout, it aborts and returns a tool-error so the model can respond or retry. Can be used alongside abortSignal. - `headers?` (`Record`): Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers. - `telemetry?` (`TelemetryOptions`): Telemetry configuration. - `TelemetryOptions` - `isEnabled?` (`boolean`): Enable or disable telemetry. Enabled by default. Set to \`false\` to opt out. - `recordInputs?` (`boolean`): Enable or disable input recording. Enabled by default. - `recordOutputs?` (`boolean`): Enable or disable output recording. Enabled by default. - `functionId?` (`string`): Identifier for this function. Used to group telemetry data by function. - `includeRuntimeContext?` (`{ [KEY in keyof CONTEXT]?: boolean }`): Top-level runtime context properties that should be included in telemetry. Runtime context properties are excluded unless they are explicitly set to \`true\`. Lifecycle callbacks and returned results still receive the full \`runtimeContext\`. - `includeToolsContext?` (`{ [TOOL_NAME in keyof InferToolSetContext]?: { [KEY in keyof InferToolSetContext[TOOL_NAME]]?: boolean } }`): Top-level tool context properties that should be included in telemetry, configured per tool. Tool context properties are excluded unless they are explicitly set to \`true\`. Lifecycle callbacks and returned results still receive the full \`toolsContext\`. - `integrations?` (`Telemetry | Telemetry[]`): Per-call telemetry integrations that receive lifecycle events. When provided, these replace any globally registered integrations for this call. - `providerOptions?` (`Record | undefined`): Provider-specific options. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. - `activeTools?` (`ActiveTools`): Limits the tools that are available for the model to call without changing the tool call and result types in the result. All tools are active by default. Tool names are restricted to the string keys of the tool set. - `toolOrder?` (`ToolOrder`): Controls the order in which tools are sent to the provider. The list can be partial. Tools not listed in \`toolOrder\` are sent after the listed tools, sorted alphabetically. Tool names are restricted to the string keys of the tool set. - `toolCallConcurrency?` (`number`): Maximum number of tool calls that the SDK executes concurrently within a step. Set to \`1\` to execute calls sequentially in model-generated order. Must be a positive integer. By default, all tool calls execute concurrently. - `toolApproval?` (`ToolApprovalConfiguration`): Approval configuration for this call. Pass a \`GenericToolApprovalFunction\` to handle all tool calls in one callback with \`toolCall\`, \`tools\`, \`toolsContext\`, \`messages\`, and \`runtimeContext\`, or pass a per-tool object where each key can be a status (\`'not-applicable'\`, \`'approved'\`, \`'denied'\`, or \`'user-approval'\`), an object form such as \`\{ type: 'denied', reason: 'blocked by policy' }\`, or a \`SingleToolApprovalFunction\` that receives the tool input and options \`toolCallId\`, \`messages\`, \`toolContext\`, and \`runtimeContext\` (same shape as tool execution options without \`abortSignal\`, with \`context\` renamed to \`toolContext\`). The \`RUNTIME\_CONTEXT\` type parameter matches the call's \`runtimeContext\`. A \`GenericToolApprovalFunction\` or \`SingleToolApprovalFunction\` may return \`undefined\` for the same effect as \`'not-applicable'\`. \`'not-applicable'\` is the default execution path and runs the tool without approval metadata. Use \`'approved'\`, \`'denied'\`, or their object forms when you want explicit automatic approval request/response parts in the output. Object statuses can include a \`reason\`: automatic approvals and denials forward it to the approval response, while manual user approvals forward it to the approval request for the human approver. This setting takes precedence over a tool's \`needsApproval\` default. - `experimental_toolCallers?` (`Experimental_ToolCallers`): Configures which caller tools may invoke each tool. The callback receives typed references for caller-capable tools in the \`tools\` set and returns an object keyed by callee tool name. Include \`'direct'\` to keep a configured tool directly callable by the model. Local-only callees are hidden from direct model calls and bound to their local caller for each generation step. Provider caller references are translated to provider-native allowed-caller options. Tools without an entry keep their existing direct tool-calling behavior. - `experimental_refineToolInput?` (`ToolInputRefinement`): Optional mapping of tool names to functions that refine parsed tool inputs. Each function receives the typed input for its tool and must return the same input type shape. The refined input is used for tool execution, output parts, lifecycle callbacks, and telemetry. - `stopWhen?` (`StopCondition | Array>`): Condition for stopping the generation when there are tool results in the last step. When the condition is an array, any of the conditions can be met to stop the generation. Default: isStepCount(1). - `prepareStep?` (`(options: PrepareStepOptions) => PrepareStepResult | Promise>`): Optional function that you can use to provide different settings for a step. You can modify the model, model call settings, tool choices, active tools, instructions, input messages, and experimental sandbox for each step. - `PrepareStepFunction` - `options` (`object`): The options for the step. - `PrepareStepOptions` - `steps` (`Array>`): The steps that have been executed so far. - `stepNumber` (`number`): The number of the step that is being executed. - `model` (`LanguageModel`): The model that is being used. - `instructions` (`Instructions | undefined`): The instructions that will be sent to the model for the current step. If prepareStep returns an instructions override, those instructions carry forward to later steps. - `initialInstructions` (`Instructions | undefined`): The initial instructions that were passed into generateText or streamText. - `messages` (`Array`): The messages that will be sent to the model for the current step. If prepareStep returns a messages override, those messages carry forward to later steps. - `runtimeContext?` (`CONTEXT`): The shared runtime context passed via the \`runtimeContext\` setting. - `toolsContext` (`InferToolSetContext`): The per-tool context map passed via the \`toolsContext\` setting. - `experimental_sandbox?` (`Experimental_SandboxSession | undefined`): The experimental sandbox environment passed via the \`experimental\_sandbox\` setting. - `PrepareStepResult` - `model?` (`LanguageModel`): Optionally override which LanguageModel instance is used for this step. - `maxOutputTokens?` (`number`): Maximum number of tokens to generate for this step. Uses the top-level value when omitted or undefined. - `temperature?` (`number`): Temperature for this step. Uses the top-level value when omitted or undefined. - `topP?` (`number`): Nucleus sampling value for this step. Uses the top-level value when omitted or undefined. - `topK?` (`number`): Top-K sampling value for this step. Uses the top-level value when omitted or undefined. - `presencePenalty?` (`number`): Presence penalty for this step. Uses the top-level value when omitted or undefined. - `frequencyPenalty?` (`number`): Frequency penalty for this step. Uses the top-level value when omitted or undefined. - `stopSequences?` (`string[]`): Stop sequences for this step. Uses the top-level value when omitted or undefined. - `seed?` (`number`): Random sampling seed for this step. Uses the top-level value when omitted or undefined. - `reasoning?` (`LanguageModelV4CallOptions["reasoning"]`): Reasoning effort for this step. Uses the top-level value when omitted or undefined. - `toolChoice?` (`ToolChoice`): Optionally set which tool the model must call, or provide tool call configuration for this step. - `activeTools?` (`ActiveTools`): If provided, only these tools are enabled/available for this step. - `toolOrder?` (`ToolOrder`): If provided, overrides the order in which tools are sent to the provider for this step. Tools not listed are appended alphabetically. - `instructions?` (`Instructions`): Optionally override the instructions sent to the model for this step. The override carries forward to later steps until prepareStep returns another instructions or system override. - `messages?` (`Array`): Optionally override the full set of messages sent to the model for this step. - `runtimeContext?` (`CONTEXT`): Shared runtime context. Changing it will affect this step and all subsequent steps. - `toolsContext` (`InferToolSetContext`): Per-tool context map. Changing it will affect tool-specific context in this step and all subsequent steps. - `experimental_sandbox?` (`Experimental_SandboxSession`): Experimental sandbox environment for this step. Changing it will affect tool execution in this step only. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options for this step. Can be used to pass provider-specific configuration such as container IDs for Anthropic code execution. - `runtimeContext?` (`CONTEXT`): User-defined shared runtime context object passed to \`prepareStep\` and lifecycle callbacks. - `toolsContext` (`InferToolSetContext`): Per-tool context map keyed by tool name. Required when at least one tool defines \`contextSchema\`; not accepted when no tools need context. - `experimental_sandbox?` (`Experimental_SandboxSession`): Experimental sandbox environment that is passed through to \`prepareStep\`, tool description functions, and tool execution. Tools can access it from their description function options and execution options. - `experimental_download?` (`(requestedDownloads: Array<{ url: URL; isUrlSupportedByModel: boolean }>) => Promise>`): Custom download function to control how URLs are fetched when they appear in prompts. By default, files are downloaded if the model does not support the URL for the given media type. Experimental feature. Return null to pass the URL directly to the model (when supported), or return downloaded content with data and media type. - `include?` (`{ requestBody?: boolean; requestMessages?: boolean; responseBody?: boolean }`): Controls inclusion of request bodies, request messages, and response bodies in step results. By default, request bodies, request messages, and response bodies are excluded to reduce memory usage. Set requestBody, requestMessages, and/or responseBody to true when you need access to the data. - `Object` - `requestBody?` (`boolean`): Whether to include the request body in step results. The request body can be large when sending images or files. Default: false. - `requestMessages?` (`boolean`): Whether to include the request messages in step results. The request messages can be large when sending images or files. Default: false. - `responseBody?` (`boolean`): Whether to include the response body in step results. Default: false. - `repairToolCall?` (`(options: ToolCallRepairOptions) => Promise`): A function that attempts to repair a tool call that failed to parse. Return either a repaired tool call or null if the tool call cannot be repaired. - `ToolCallRepairOptions` - `instructions` (`Instructions | undefined`): The instructions provided to the model. - `system?` (`Instructions | undefined`): The instructions provided to the model. Deprecated: use \`instructions\` instead. - `messages` (`ModelMessage[]`): The messages in the current generation step. - `toolCall` (`LanguageModelV4ToolCall`): The tool call that failed to parse. - `tools` (`TOOLS`): The tools that are available. - `inputSchema` (`(options: { toolName: string }) => JSONSchema7`): A function that returns the JSON Schema for a tool. - `error` (`NoSuchToolError | InvalidToolInputError`): The error that occurred while parsing the tool call. - `abortSignal?` (`AbortSignal`): An optional signal for cancelling the tool call repair. - `output?` (`Output`): Specification for parsing structured outputs from the LLM response. - `Output` - `Output.text()` (`Output`): Output specification for text generation (default). - `Output.object()` (`Output`): Output specification for typed object generation using schemas. When the model generates a text response, it will return an object that matches the schema. - `Options` - `schema` (`Schema`): The schema of the object to generate. - `name?` (`string`): Optional name of the output. Used by some providers for additional LLM guidance. - `description?` (`string`): Optional description of the output. Used by some providers for additional LLM guidance. - `Output.array()` (`Output`): Output specification for array generation. When the model generates a text response, it will return an array of elements. - `Options` - `element` (`Schema`): The schema of the array elements to generate. - `minItems?` (`number`): Optional minimum number of array elements. Must be a non-negative integer. - `maxItems?` (`number`): Optional maximum number of array elements. Must be a non-negative integer and greater than or equal to minItems. - `name?` (`string`): Optional name of the output. Used by some providers for additional LLM guidance. - `description?` (`string`): Optional description of the output. Used by some providers for additional LLM guidance. - `Output.choice()` (`Output`): Output specification for choice generation. When the model generates a text response, it will return a one of the choice options. - `Options` - `options` (`Array`): The available choices. - `name?` (`string`): Optional name of the output. Used by some providers for additional LLM guidance. - `description?` (`string`): Optional description of the output. Used by some providers for additional LLM guidance. - `Output.json()` (`Output`): Output specification for unstructured JSON generation. When the model generates a text response, it will return any valid JSON value. - `Options` - `name?` (`string`): Optional name of the output. Used by some providers for additional LLM guidance. - `description?` (`string`): Optional description of the output. Used by some providers for additional LLM guidance. - `onStart?` (`(event: GenerateTextStartEvent) => PromiseLike | void`): Callback that is called when the generateText operation begins, before any LLM calls are made. Errors thrown in this callback are silently caught and do not break the generation flow. - `GenerateTextStartEvent` - `provider` (`string`): The provider identifier (e.g., "openai", "anthropic"). - `modelId` (`string`): The specific model identifier (e.g., "gpt-6-astra"). - `instructions` (`Instructions | undefined`): The instructions provided to the model. - `messages` (`Array`): The messages for this generation. - `tools` (`TOOLS | undefined`): The tools available for this generation. - `toolChoice` (`ToolChoice | undefined`): The tool choice strategy for this generation. - `activeTools` (`ActiveTools`): Limits which tools are available for the model to call. - `toolOrder` (`ToolOrder`): Controls the order in which tools are sent to the provider. - `maxOutputTokens` (`number | undefined`): Maximum number of tokens to generate. - `temperature` (`number | undefined`): Sampling temperature for generation. - `topP` (`number | undefined`): Top-p (nucleus) sampling parameter. - `topK` (`number | undefined`): Top-k sampling parameter. - `presencePenalty` (`number | undefined`): Presence penalty for generation. - `frequencyPenalty` (`number | undefined`): Frequency penalty for generation. - `stopSequences` (`string[] | undefined`): Sequences that will stop generation. - `seed` (`number | undefined`): Random seed for reproducible generation. - `maxRetries` (`number`): Maximum number of retries for failed requests. - `timeout` (``number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number; toolMs?: number; tools?: Partial> } | undefined``): Timeout configuration for the generation. Can be a number (milliseconds) or an object with totalMs, stepMs, firstChunkMs (streaming only), chunkMs, toolMs, and per-tool overrides via tools. - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `providerOptions` (`ProviderOptions | undefined`): Additional provider-specific options. - `output` (`OUTPUT | undefined`): The output specification for structured outputs, if configured. - `abortSignal` (`AbortSignal | undefined`): Abort signal for cancelling the operation. - `include` (`{ requestBody?: boolean; requestMessages?: boolean; responseBody?: boolean } | undefined`): Settings for controlling what data is included in step results. - `runtimeContext` (`CONTEXT`): User-defined shared runtime context object that flows through the generation lifecycle. - `toolsContext` (`InferToolSetContext`): Per-tool context map passed via \`toolsContext\`, keyed by tool name. - `onStepStart?` (`(event: GenerateTextStepStartEvent) => PromiseLike | void`): Callback that is called when a step begins, before the provider is called. Errors thrown in this callback are silently caught and do not break the generation flow. - `GenerateTextStepStartEvent` - `stepNumber` (`number`): Zero-based index of the current step. - `provider` (`string`): The provider identifier (e.g., "openai", "anthropic"). - `modelId` (`string`): The specific model identifier (e.g., "gpt-6-astra"). - `instructions` (`Instructions | undefined`): The instructions provided to the model for this step. - `messages` (`Array`): The messages that will be sent to the model for this step. Uses the user-facing ModelMessage format. May be overridden by prepareStep. If prepareStep returns a messages override, those messages carry forward to later steps. - `tools` (`TOOLS | undefined`): The tools available for this generation. - `toolChoice` (`LanguageModelV4ToolChoice | undefined`): The tool choice configuration for this step. - `activeTools` (`ActiveTools`): Limits which tools are available for this step. - `toolOrder` (`ToolOrder`): Controls the order in which tools are sent to the provider for this step. - `steps` (`ReadonlyArray>`): Array of results from previous steps (empty for first step). - `providerOptions` (`ProviderOptions | undefined`): Additional provider-specific options for this step. - `timeout` (`number | { totalMs?: number; stepMs?: number; chunkMs?: number } | undefined`): Timeout configuration for the generation. Can be a number (milliseconds) or an object with totalMs, stepMs, chunkMs. - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `stopWhen` (`StopCondition | Array> | undefined`): Condition(s) for stopping the generation. When the condition is an array, any of the conditions can be met to stop. - `output` (`OUTPUT | undefined`): The output specification for structured outputs, if configured. - `abortSignal` (`AbortSignal | undefined`): Abort signal for cancelling the operation. - `include` (`{ requestBody?: boolean; requestMessages?: boolean; responseBody?: boolean } | undefined`): Settings for controlling what data is included in step results. - `runtimeContext` (`CONTEXT`): User-defined shared runtime context object. May be updated from prepareStep between steps. - `toolsContext` (`InferToolSetContext`): Per-tool context map. May be updated from prepareStep between steps. - `onLanguageModelCallStart?` (`(event: LanguageModelCallStartEvent) => PromiseLike | void`): Callback that is called immediately before the provider model call begins. Unlike \`onStepStart\`, this callback is scoped to model work only and excludes any later client-side tool execution. Errors thrown in this callback are silently caught and do not break the generation flow. - `LanguageModelCallStartEvent` - `callId` (`string`): Unique identifier for the generation call. - `provider` (`string`): The provider identifier for this model call. - `modelId` (`string`): The specific model identifier for this model call. - `instructions` (`Instructions | undefined`): The instructions that will be sent to the model. - `messages` (`Array`): The messages that will be sent to the model. - `tools` (`ReadonlyArray> | undefined`): Prepared tool definitions for the model call, if any. - `onLanguageModelCallEnd?` (`(event: LanguageModelCallEndEvent) => PromiseLike | void`): Callback that is called after the model response has been normalized and parsed, but before any client-side tool execution begins. Errors thrown in this callback are silently caught and do not break the generation flow. - `LanguageModelCallEndEvent` - `callId` (`string`): Unique identifier for the generation call. - `provider` (`string`): The provider identifier for this model call. - `modelId` (`string`): The provider-returned model identifier for this model call. - `finishReason` (`FinishReason`): The unified reason why the model call finished. - `usage` (`LanguageModelUsage`): Token usage reported by the model call. - `content` (`ReadonlyArray>`): The content parts produced by the model call (text, reasoning, files, tool calls, etc.). - `responseId` (`string`): The provider-returned response ID for this model call. - `providerMetadata` (`ProviderMetadata | undefined`): Provider-specific metadata for this model call, when returned by the provider. - `performance` (`{ responseTimeMs: number; effectiveOutputTokensPerSecond: number; outputTokensPerSecond: number | undefined; inputTokensPerSecond: number | undefined; effectiveTotalTokensPerSecond: number; timeToFirstOutputMs: number | undefined; timeBetweenOutputChunksMs?: OutputChunkTimingStats }`): Performance metrics for the model call. - `LanguageModelCallPerformance` - `responseTimeMs` (`number`): Time spent waiting for the language model response in milliseconds. - `effectiveOutputTokensPerSecond` (`number`): Effective number of output tokens per second over the full language model response. - `outputTokensPerSecond` (`number | undefined`): Number of output tokens per second after the first generated output chunk was received. For \`generateText\`, this is \`undefined\` because the response is not streamed. - `inputTokensPerSecond` (`number | undefined`): Number of input tokens processed per second before the first generated output chunk was received. For \`generateText\`, this is \`undefined\` because the response is not streamed. - `effectiveTotalTokensPerSecond` (`number`): Effective number of input and output tokens per second over the full language model response. - `timeToFirstOutputMs` (`number | undefined`): Time until the first generated output chunk was received in milliseconds. For \`generateText\`, this is \`undefined\` because the response is not streamed. - `timeBetweenOutputChunksMs` (`OutputChunkTimingStats | undefined`): Timing statistics for the gaps between generated output chunks in milliseconds. For \`generateText\`, this is \`undefined\` because the response is not streamed. - `onToolExecutionStart?` (`(event: ToolExecutionStartEvent) => PromiseLike | void`): Callback that is called right before a tool's execute function runs. Errors thrown in this callback are silently caught and do not break the generation flow. - `ToolExecutionStartEvent` - `callId` (`string`): Unique identifier for this generation call, used to correlate events. - `toolCall` (`TypedToolCall`): The full tool call object containing toolName, toolCallId, input, and metadata. - `messages` (`Array`): Messages that were sent to the language model to initiate the response that contained the tool call. Does not include the system prompt nor the assistant response that contained the tool call. - `toolContext` (`InferToolContext`): Tool-specific context object for the tool call that is about to execute. Narrowed to the context type of the individual tool, not the entire tool set. - `onToolExecutionEnd?` (`(event: ToolExecutionEndEvent) => PromiseLike | void`): Callback that is called right after a tool's execute function completes (or errors). The \`toolOutput\` field is a discriminated union: when \`toolOutput.type\` is \`'tool-result'\`, the \`output\` field contains the tool result; when \`toolOutput.type\` is \`'tool-error'\`, the \`error\` field contains the error. Errors thrown in this callback are silently caught and do not break the generation flow. - `ToolExecutionEndEvent` - `callId` (`string`): Unique identifier for this generation call, used to correlate events. - `toolCall` (`TypedToolCall`): The full tool call object containing toolName, toolCallId, input, and metadata. - `toolExecutionMs` (`number`): The wall-clock duration of the tool execution in milliseconds. - `messages` (`Array`): Messages that were sent to the language model to initiate the response that contained the tool call. Does not include the system prompt nor the assistant response that contained the tool call. - `toolContext` (`InferToolContext`): Tool-specific context object for the tool call that just completed. Narrowed to the context type of the individual tool, not the entire tool set. - `toolOutput` (`ToolOutput`): Discriminated union representing the tool execution result. When \`type\` is \`'tool-result'\`, the \`output\` field contains the tool's return value. When \`type\` is \`'tool-error'\`, the \`error\` field contains the error. - `experimental_onToolCallStart?` (`(event: ToolExecutionStartEvent) => PromiseLike | void`): Deprecated. Use \`onToolExecutionStart\` instead. This alias is only used as a fallback when \`onToolExecutionStart\` is not provided. - `experimental_onToolCallFinish?` (`(event: ToolExecutionEndEvent) => PromiseLike | void`): Deprecated. Use \`onToolExecutionEnd\` instead. This alias is only used as a fallback when \`onToolExecutionEnd\` is not provided. - `onStepEnd?` (`(stepResult: StepResult) => Promise | void`): Callback that is called when a step ends. Receives a StepResult object. - `StepResult` - `stepNumber` (`number`): Zero-based index of this step. - `model` (`{ provider: string; modelId: string }`): Information about the model that produced this step. - `runtimeContext` (`CONTEXT`): User-defined shared runtime context object flowing through the generation. - `toolsContext` (`InferToolSetContext`): Per-tool context map for the generation step. - `content` (`Array>`): The content that was generated in this step. - `text` (`string`): The generated text. - `reasoning` (`Array`): The reasoning that was generated during the generation. - `reasoningText` (`string | undefined`): The reasoning text that was generated during the generation. - `files` (`Array`): The files that were generated during the generation. - `sources` (`Array`): The sources that were used in this step. - `toolCalls` (`Array>`): The tool calls that were made during the generation. - `staticToolCalls` (`Array>`): The static tool calls that were made in this step. - `dynamicToolCalls` (`Array`): The dynamic tool calls that were made in this step. - `toolResults` (`Array>`): The results of the tool calls. - `staticToolResults` (`Array>`): The static tool results that were made in this step. - `dynamicToolResults` (`Array`): The dynamic tool results that were made in this step. - `finishReason` (`"stop" | "length" | "content-filter" | "tool-calls" | "error" | "interrupted" | "other"`): The unified reason why the generation finished. - `rawFinishReason` (`string | undefined`): The raw reason why the generation finished (from the provider). - `usage` (`LanguageModelUsage`): The token usage of the generated text. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. - `performance` (`StepResultPerformance`): Timing and throughput metrics for the step. For \`generateText\`, streaming-only metrics are undefined. - `StepResultPerformance` - `effectiveOutputTokensPerSecond` (`number`): Effective number of output tokens per second over the full language model response. - `outputTokensPerSecond` (`number | undefined`): Number of output tokens per second after the first generated output chunk was received. For \`generateText\`, this is \`undefined\` because the response is not streamed. - `inputTokensPerSecond` (`number | undefined`): Number of input tokens processed per second before the first generated output chunk was received. For \`generateText\`, this is \`undefined\` because the response is not streamed. - `effectiveTotalTokensPerSecond` (`number`): Effective number of input and output tokens per second over the full language model response. - `stepTimeMs` (`number`): Total time spent on the step, including language model response time and tool execution time, in milliseconds. - `responseTimeMs` (`number`): Time spent waiting for the language model response in milliseconds. - `toolExecutionMs` (`Readonly>`): Time spent executing each client-side tool call in the step in milliseconds, keyed by tool call ID. - `timeToFirstOutputMs` (`number | undefined`): Time until the first generated output chunk was received in milliseconds. For \`generateText\`, this is \`undefined\` because the response is not streamed. - `timeBetweenOutputChunksMs` (`OutputChunkTimingStats | undefined`): Timing statistics for the gaps between generated output chunks in milliseconds. For \`generateText\`, this is \`undefined\` because the response is not streamed. - `warnings` (`CallWarning[] | undefined`): Warnings from the model provider (e.g. unsupported settings). - `request` (`LanguageModelRequestMetadata`): Additional request information. - `LanguageModelRequestMetadata` - `messages?` (`Array`): The input messages that were sent to the model for this step. Undefined by default unless requestMessages is set to true. - `body?` (`unknown`): Request HTTP body that was sent to the provider API. - `response` (`LanguageModelResponseMetadata`): Additional response information. - `Response` - `id` (`string`): The response identifier. The AI SDK uses the ID from the provider response when available, and generates an ID otherwise. - `modelId` (`string`): The model that was used to generate the response. - `timestamp` (`Date`): The timestamp of the response. - `headers?` (`Record`): Optional response headers. - `messages` (`Array`): The response messages generated during this step. - `body?` (`unknown`): Response body (available only for providers that use HTTP requests). - `providerMetadata?` (`ProviderMetadata | undefined`): Additional provider-specific metadata. They are passed through from the provider to the AI SDK and enable provider-specific results that can be fully encapsulated in the provider. - `responseMessages` (`Array`): The accumulated response messages that were generated during the call. - `onStepFinish?` (`GenerateTextOnStepFinishCallback`): Deprecated. Use \`onStepEnd\` instead. This alias is only used as a fallback when \`onStepEnd\` is not provided. - `onEnd?` (`(event: GenerateTextEndEvent) => PromiseLike | void`): Callback that is called when the entire generation completes (all steps finished). The event includes the final step result properties, excluding direct \`performance\`, along with aggregated data from all steps. Use \`event.steps\` for per-step performance metrics. - `GenerateTextEndEvent` - `stepNumber` (`number`): Zero-based index of the final step. - `model` (`{ provider: string; modelId: string }`): Information about the model that produced the final step. - `finishReason` (`"stop" | "length" | "content-filter" | "tool-calls" | "error" | "interrupted" | "other"`): The unified reason why the generation finished. - `rawFinishReason` (`string | undefined`): The raw reason why the generation finished (from the provider). - `usage` (`LanguageModelUsage`): The token usage from the final step only (not aggregated). - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. - `totalUsage` (`LanguageModelUsage`): Aggregated token usage across all steps. This is the sum of the usage from each individual step. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used across all steps. - `outputTokens` (`number | undefined`): The total number of output (completion) tokens used across all steps. - `totalTokens` (`number | undefined`): The total number of tokens used across all steps. - `content` (`Array>`): The content that was generated in all steps. Text parts can include an optional citations array of references supporting that text; see Sources and Citations in the generating text guide. - `providerMetadata` (`ProviderMetadata | undefined`): Additional provider-specific metadata from the final step. - `text` (`string`): The full text that has been generated. - `reasoningText` (`string | undefined`): The reasoning text of the model (only available for some models). - `reasoning` (`Array`): The reasoning details of the model (only available for some models). - `ReasoningDetail` - `type` (`'text'`): The type of the reasoning detail. - `text` (`string`): The text content (only for type "text"). - `signature?` (`string`): Optional signature (only for type "text"). - `ReasoningDetail` - `type` (`'redacted'`): The type of the reasoning detail. - `data` (`string`): The redacted data content (only for type "redacted"). - `sources` (`Array`): Sources that have been used in the final step. - `Source` - `sourceType` (`'url'`): A URL source. This is return by web search RAG models. - `id` (`string`): The ID of the source. - `url` (`string`): The URL of the source. - `title?` (`string`): The title of the source. - `providerMetadata?` (`SharedV2ProviderMetadata`): Additional provider metadata for the source. - `files` (`Array`): Files that were generated in all steps. - `GeneratedFile` - `base64` (`string`): File as a base64 encoded string. - `uint8Array` (`Uint8Array`): File as a Uint8Array. - `mediaType` (`string`): The IANA media type of the file. - `toolCalls` (`Array>`): The tool calls that were made during the generation. - `staticToolCalls` (`Array>`): The static tool calls that were made in the final step. - `dynamicToolCalls` (`Array`): The dynamic tool calls that were made in the final step. - `toolResults` (`Array>`): The results of the tool calls. - `staticToolResults` (`Array>`): The static tool results that were made in the final step. - `dynamicToolResults` (`Array`): The dynamic tool results that were made in the final step. - `warnings` (`CallWarning[] | undefined`): Warnings from the model provider (e.g. unsupported settings) in all steps. - `request` (`LanguageModelRequestMetadata`): Additional request information from the final step. - `LanguageModelRequestMetadata` - `messages?` (`Array`): The input messages that were sent to the model for this step. Undefined by default unless requestMessages is set to true. - `body?` (`unknown`): Request HTTP body that was sent to the provider API. - `response` (`LanguageModelResponseMetadata`): Additional response information from the final step. - `Response` - `id` (`string`): The response identifier. The AI SDK uses the ID from the provider response when available, and generates an ID otherwise. - `modelId` (`string`): The model that was used to generate the response. The AI SDK uses the response model from the provider response when available, and the model from the function call otherwise. - `timestamp` (`Date`): The timestamp of the response. The AI SDK uses the response timestamp from the provider response when available, and creates a timestamp otherwise. - `headers?` (`Record`): Optional response headers. - `body?` (`unknown`): Response body (available only for providers that use HTTP requests). - `messages` (`Array`): The response messages generated during this step. It consists of an assistant message, potentially containing tool calls. When there are tool results in this step, there is an additional tool message with the available tool results. If there are tools that do not have execute functions, they are not included in the tool results and need to be added separately. - `steps` (`Array`): Response information for every step. You can use this to get information about intermediate steps, such as the tool calls or the response headers. - `finalStep` (`StepResult`): The final step. This is a shortcut for \`steps.at(-1)\`. - `responseMessages` (`Array`): The accumulated response messages that were generated during the call. - `runtimeContext` (`CONTEXT`): Deprecated. Use \`finalStep.runtimeContext\` instead. The final state of the user-defined shared runtime context object. This reflects any modifications made during the generation lifecycle via \`prepareStep\`. - `toolsContext` (`InferToolSetContext`): Deprecated. Use \`finalStep.toolsContext\` instead. The final state of the per-tool context map passed via \`toolsContext\`. - `onFinish?` (`(event: GenerateTextEndEvent) => PromiseLike | void`): Deprecated alias for \`onEnd\`. ### Returns - `content` (`Array>`): The content that was generated in all steps. Text parts can include an optional citations array of references supporting that text; see Sources and Citations in the generating text guide. - `text` (`string`): The concatenation of all text parts generated in the final step. It is an empty string if the final step contains no text parts. Inspect \`finalStep.content\` to distinguish that case. - `reasoning` (`Array`): Deprecated. Use \`finalStep.reasoning\` instead. The full reasoning that the model has generated in the final step. - `ReasoningOutput` - `type` (`'reasoning'`): The type of the message part. - `text` (`string`): The reasoning text. - `providerMetadata?` (`SharedV2ProviderMetadata`): Additional provider metadata. - `ReasoningFileOutput` - `type` (`'reasoning-file'`): The type of the message part. - `file` (`GeneratedFile`): The generated file. - `providerMetadata?` (`SharedV2ProviderMetadata`): Additional provider metadata. - `reasoningText` (`string | undefined`): Deprecated. Use \`finalStep.reasoningText\` instead. The reasoning text that the model has generated in the final step. Can be undefined if the model has only generated text. - `sources` (`Array`): Sources that have been used as input to generate the response. For multi-step generation, the sources are accumulated from all steps. - `Source` - `sourceType` (`'url'`): A URL source. This is return by web search RAG models. - `id` (`string`): The ID of the source. - `url` (`string`): The URL of the source. - `title?` (`string`): The title of the source. - `providerMetadata?` (`SharedV2ProviderMetadata`): Additional provider metadata for the source. - `files` (`Array`): Files that were generated in all steps. - `GeneratedFile` - `base64` (`string`): File as a base64 encoded string. - `uint8Array` (`Uint8Array`): File as a Uint8Array. - `mediaType` (`string`): The IANA media type of the file. - `toolCalls` (`ToolCallArray`): The tool calls that were made in all steps. - `toolResults` (`ToolResultArray`): The results of the tool calls from all steps. - `staticToolCalls` (`Array>`): The static tool calls that have been executed in all steps. - `dynamicToolCalls` (`Array`): The dynamic tool calls that have been executed in all steps. - `staticToolResults` (`Array>`): The static tool results that have been generated in all steps. - `dynamicToolResults` (`Array`): The dynamic tool results that have been generated in all steps. - `finishReason` (`'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'`): The reason the model finished generating the text. - `rawFinishReason` (`string | undefined`): The raw reason why the generation finished (from the provider). - `usage` (`LanguageModelUsage`): The total token usage of all steps. When there are multiple steps, the usage is the sum of all step usages. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. See also: cached tokens and non-cached tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. - `totalUsage` (`LanguageModelUsage`): Deprecated. Use usage instead. The total token usage of all steps. When there are multiple steps, the usage is the sum of all step usages. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The number of input (prompt) tokens used. - `outputTokens` (`number | undefined`): The number of output (completion) tokens used. - `totalTokens` (`number | undefined`): The total number of tokens as reported by the provider. This number might be different from the sum of inputTokens and outputTokens and e.g. include reasoning tokens or other overhead. - `request?` (`LanguageModelRequestMetadata`): Deprecated. Use \`finalStep.request\` instead. Request metadata from the final step. - `LanguageModelRequestMetadata` - `messages?` (`Array`): The input messages that were sent to the model for this step. Undefined by default unless requestMessages is set to true. - `body?` (`unknown`): Request HTTP body that was sent to the provider API. - `response?` (`LanguageModelResponseMetadata`): Deprecated. Use \`finalStep.response\` instead. Response metadata from the final step. - `LanguageModelResponseMetadata` - `id` (`string`): The response identifier. The AI SDK uses the ID from the provider response when available, and generates an ID otherwise. - `modelId` (`string`): The model that was used to generate the response. The AI SDK uses the response model from the provider response when available, and the model from the function call otherwise. - `timestamp` (`Date`): The timestamp of the response. The AI SDK uses the response timestamp from the provider response when available, and creates a timestamp otherwise. - `headers?` (`Record`): Optional response headers. - `body?` (`unknown`): Optional response body. - `messages` (`Array`): The response messages generated during the final step. It consists of an assistant message, potentially containing tool calls. When there are tool results in the final step, there is an additional tool message with the available tool results. If there are tools that do not have execute functions, they are not included in the tool results and need to be added separately. - `warnings` (`Warning[] | undefined`): Warnings from the model provider (e.g. unsupported settings) in all steps. - `responseMessages` (`Array`): The accumulated response messages of all steps that were generated during the call. - `providerMetadata` (`ProviderMetadata | undefined`): Deprecated. Use \`finalStep.providerMetadata\` instead. Optional metadata from the provider for the final step. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. - `output` (`InferCompleteOutput`): The generated output according to the \`output\` specification. Accessing this property throws \`NoOutputGeneratedError\` when no output is available, for example when the final step does not finish with a \`stop\` reason. - `steps` (`Array>`): Response information for every step. You can use this to get information about intermediate steps, such as the tool calls or the response headers. - `StepResult` - `stepNumber` (`number`): The zero-based index of this step. - `model` (`{ provider: string; modelId: string }`): Information about the model that produced this step. - `runtimeContext` (`CONTEXT`): User-defined shared runtime context object flowing through the generation. - `toolsContext` (`InferToolSetContext`): Per-tool context map for the final generation step. - `content` (`Array>`): The content that was generated in the final step. - `text` (`string`): The concatenation of all text parts generated in this step. It is an empty string if the step contains no text parts. - `reasoning` (`Array`): The reasoning that was generated during the generation. - `ReasoningPart` - `type` (`'reasoning'`): The type of the message part. - `text` (`string`): The reasoning text. - `ReasoningFilePart` - `type` (`'reasoning-file'`): The type of the message part. - `data` (`string | Uint8Array | Buffer | ArrayBuffer | URL`): The file data. String are either base64 encoded content, base64 data URLs, or http(s) URLs. - `mediaType` (`string`): The IANA media type of the file. - `reasoningText` (`string | undefined`): The reasoning text that was generated during the generation. - `files` (`Array`): The files that were generated during the generation. - `GeneratedFile` - `base64` (`string`): File as a base64 encoded string. - `uint8Array` (`Uint8Array`): File as a Uint8Array. - `mediaType` (`string`): The IANA media type of the file. - `sources` (`Array`): Sources that have been used in this step. - `Source` - `sourceType` (`'url'`): A URL source. This is return by web search RAG models. - `id` (`string`): The ID of the source. - `url` (`string`): The URL of the source. - `title?` (`string`): The title of the source. - `providerMetadata?` (`SharedV2ProviderMetadata`): Additional provider metadata for the source. - `toolCalls` (`ToolCallArray`): The tool calls that were made during the generation. - `toolResults` (`ToolResultArray`): The results of the tool calls. - `finishReason` (`'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'`): The reason why the generation finished. - `rawFinishReason` (`string | undefined`): The raw reason why the generation finished (from the provider). - `usage` (`LanguageModelUsage`): The token usage of the generated text. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. See also: cached tokens and non-cached tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. - `warnings` (`Warning[] | undefined`): Warnings from the model provider (e.g. unsupported settings). - `request` (`LanguageModelRequestMetadata`): Additional request information. - `LanguageModelRequestMetadata` - `messages?` (`Array`): The input messages that were sent to the model for this step. Undefined by default unless requestMessages is set to true. - `body?` (`unknown`): Request HTTP body that was sent to the provider API. - `response` (`LanguageModelResponseMetadata`): Additional response information. - `LanguageModelResponseMetadata` - `id` (`string`): The response identifier. The AI SDK uses the ID from the provider response when available, and generates an ID otherwise. - `modelId` (`string`): The model that was used to generate the response. The AI SDK uses the response model from the provider response when available, and the model from the function call otherwise. - `timestamp` (`Date`): The timestamp of the response. The AI SDK uses the response timestamp from the provider response when available, and creates a timestamp otherwise. - `headers?` (`Record`): Optional response headers. - `body?` (`unknown`): Response body (available only for providers that use HTTP requests). - `messages` (`Array`): The response messages generated during this step. Response messages can be either assistant messages or tool messages. They contain a generated id. - `providerMetadata` (`ProviderMetadata | undefined`): Additional provider-specific metadata. They are passed through from the provider to the AI SDK and enable provider-specific results that can be fully encapsulated in the provider. - `finalStep` (`StepResult`): The final step. This is a shortcut for \`steps.at(-1)\`. ## Types ### `ActiveTools` ```ts type ActiveTools = | ReadonlyArray | undefined; ``` Limits a generation step to the listed tool names. `undefined` means no tool restriction is applied. ## Examples - [Learn to generate text using a language model in Next.js](/examples/next-app/basics/generating-text) - [Learn to generate a chat completion using a language model in Next.js](/examples/next-app/basics/generating-text) - [Learn to call tools using a language model in Next.js](/examples/next-app/tools/call-tool) - [Learn to render a React component as a tool call using a language model in Next.js](/examples/next-app/tools/render-interface-during-tool-call) - [Learn to generate text using a language model in Node.js](/examples/node/generating-text/generate-text) - [Learn to generate chat completions using a language model in Node.js](/examples/node/generating-text/generate-text-with-chat-prompt) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_generateVideo description: API Reference for experimental_generateVideo. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/generate-video" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Video generation is an experimental feature. The API may change in future versions. Generates videos based on a given prompt using a video model. It is ideal for use cases where you need to generate videos programmatically, such as creating visual content, animations, or generating videos from images. ```ts import { experimental_generateVideo as generateVideo } from 'ai'; const { videos } = await generateVideo({ model: "google/veo-3.1-generate-001", prompt: 'A cat walking on a treadmill', aspectRatio: '16:9', }); console.log(videos); ``` ## Import ``` import { experimental_generateVideo } from "ai" ``` ## API Signature ### Parameters - `model` (`VideoModelV4`): The video model to use. - `prompt` (`string | GenerateVideoPrompt`): The input prompt to generate the video from. - `object` - `image` (`DataContent`): Input image for image-to-video generation. Can be a URL string, base64-encoded string, a \`Uint8Array\`, an \`ArrayBuffer\`, or a \`Buffer\`. - `text` (`string`): The text prompt. - `n?` (`number`): Number of videos to generate. Default: 1. - `aspectRatio?` (`string`): Aspect ratio of the videos to generate. Format: \`\{width}:\{height}\`, or \`'adaptive'\` to inherit the ratio from the input media (e.g. a first-frame image or a source video). Support for \`'adaptive'\` is provider-specific. - `resolution?` (`string`): Resolution of the videos to generate. Format: \`\{width}x\{height}\`. - `duration?` (`number`): Duration of the video in seconds. - `fps?` (`number`): Frames per second for the video. - `seed?` (`number`): Seed for the video generation. - `frameImages?` (`Array<{ image: DataContent; frameType: "first_frame" | "last_frame" }>`): Role-tagged image inputs for first-last-frame generation. - `inputReferences?` (`Array`): Reference image or video inputs for reference-to-video generation. Use the object form with an explicit mediaType for URL-based video references. Providers route each reference by its media type and warn when a reference kind is unsupported. - `generateAudio?` (`boolean`): Whether the model should generate audio alongside the video. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options. - `maxVideosPerCall?` (`number`): Maximum number of videos to generate per API call. When n exceeds this value, multiple API calls will be made. - `maxRetries?` (`number`): Maximum number of retries. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal to cancel the call. - `headers?` (`Record`): Additional HTTP headers for the request. - `download?` (`(options: { url: URL; abortSignal?: AbortSignal }) => Promise<{ data: Uint8Array; mediaType: string | undefined }>`): Custom download function for fetching videos from URLs. Use \`createDownload()\` from \`ai\` to create a download function with custom size limits, e.g. \`createDownload(\{ maxBytes: 50 \* 1024 \* 1024 })\`. Default: built-in download with 2 GiB limit. - `poll?` (`object`): Polling configuration for the asynchronous start/status flow. - `object` - `intervalMs?` (`number`): Interval between status checks in milliseconds. Default: 5000. - `timeoutMs?` (`number`): Maximum time to wait for completion in milliseconds, including while waiting for a webhook notification. Default: 600000 (10 minutes). - `delay?` (`(delayInMs: number, options?: { abortSignal?: AbortSignal }) => PromiseLike`): Custom delay implementation for polling intervals and webhook timeouts. Useful for durable workflow sleep functions. Default: built-in timer-based delay. - `webhook?` (`() => PromiseLike<{ url: string; received: PromiseLike }>`): Webhook factory for providers that support webhook notifications. When provided and the model supports webhooks, the SDK uses the webhook instead of polling. The factory should return a URL for the provider to notify and a \`received\` promise that resolves when the notification arrives. The \`poll\` option can also be provided to configure the webhook timeout and polling fallback. ### Returns - `video` (`GeneratedFile`): The first video that was generated. - `GeneratedFile` - `base64` (`string`): Video as a base64 encoded string. - `uint8Array` (`Uint8Array`): Video as a Uint8Array. - `mediaType` (`string`): The IANA media type of the video (e.g., video/mp4). - `videos` (`Array`): All videos that were generated. - `GeneratedFile` - `base64` (`string`): Video as a base64 encoded string. - `uint8Array` (`Uint8Array`): Video as a Uint8Array. - `mediaType` (`string`): The IANA media type of the video (e.g., video/mp4). - `warnings` (`Warning[]`): Warnings from the model provider (e.g. unsupported settings). - `providerMetadata?` (`VideoModelProviderMetadata`): Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. A \`videos\` key is typically present in the metadata and is an array with the same length as the top level \`videos\` key. Details depend on the provider. - `responses` (`Array`): Response metadata from the provider. There may be multiple responses if we made multiple calls to the model. - `VideoModelResponseMetadata` - `timestamp` (`Date`): Timestamp for the start of the generated response. - `modelId` (`string`): The ID of the response model that was used to generate the response. - `headers?` (`Record`): Response headers. - `providerMetadata?` (`VideoModelProviderMetadata`): Provider-specific metadata for this individual API call. Useful for accessing per-call metadata when multiple calls are made. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_getBatchResults description: API Reference for experimental_getBatchResults. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/get-batch-results" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Batch support is experimental and the API may change in patch releases. Returns an async iterable of terminal results for the requests in an asynchronous batch. For a complete guide to the batch lifecycle, see [Batch](/docs/ai-sdk-core/batch). ```ts import { anthropic } from '@ai-sdk/anthropic'; import { experimental_getBatchResults as getBatchResults } from 'ai'; for await (const item of getBatchResults({ provider: anthropic, batch })) { if (item.status === 'succeeded') { console.log(item.id, item.text); } else { console.error(item.id, item.error); } } ``` ## Import ``` import { experimental_getBatchResults } from "ai" ``` ## API Signature ### Parameters - `provider?` (`Experimental_BatchProvider`): The provider used to access the batch. Defaults to the global provider, or the AI Gateway when no global provider is configured. - `batch` (`Experimental_BatchReference`): The serializable reference returned by experimental\_startBatch. - `tools?` (`ToolSet`): The client-defined tools provided on requests in the batch. Used to validate and normalize returned tool calls; execute functions are never invoked. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options for result retrieval. - `maxRetries?` (`number`): Maximum number of retries for result retrieval. Set to 0 to disable retries. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal to cancel result retrieval. - `timeout?` (`number | { totalMs?: number }`): Maximum time allowed for result retrieval. - `headers?` (`Record`): Additional HTTP headers for the request. ### Returns An `AsyncIterableStream` of succeeded, failed, cancelled, or expired request results. You can consume the stream as either an async iterable or a `ReadableStream`. Successful items contain `id`, `status: 'succeeded'`, `text`, normalized `content` (including text, reasoning, sources, files, tool calls, and tool results), `finishReason`, `usage`, and optional response and provider metadata. `text` is the concatenation of text parts and can be an empty string when a result contains no text parts. Failed, cancelled, and expired items contain the request `id`, their terminal status, and optional error details. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_getBatchStatus description: API Reference for experimental_getBatchStatus. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/get-batch-status" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Batch support is experimental and the API may change in patch releases. Retrieves the latest status of an asynchronous batch. For a complete guide to the batch lifecycle, see [Batch](/docs/ai-sdk-core/batch). ```ts import { anthropic } from '@ai-sdk/anthropic'; import { experimental_getBatchStatus as getBatchStatus } from 'ai'; const status = await getBatchStatus({ provider: anthropic, batch, }); console.log(status.status, status.requestCounts); ``` ## Import ``` import { experimental_getBatchStatus } from "ai" ``` ## API Signature ### Parameters - `provider?` (`Experimental_BatchProvider`): The provider used to access the batch. Defaults to the global provider, or the AI Gateway when no global provider is configured. - `batch` (`Experimental_BatchReference`): The serializable reference returned by experimental\_startBatch. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options for status retrieval. - `maxRetries?` (`number`): Maximum number of retries for status retrieval. Set to 0 to disable retries. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal to cancel the request. - `timeout?` (`number | { totalMs?: number }`): Maximum time allowed for the status request. - `headers?` (`Record`): Additional HTTP headers for the request. ### Returns - `status` (`'pending' | 'completed' | 'failed'`): The latest normalized batch status. - `rawStatus?` (`string`): The provider-specific batch status, when available. - `requestCounts?` (`{ total: number; pending: number; completed: number; failed: number }`): Provider-reported request counts. - `error?` (`Experimental_BatchError`): Error details when the batch fails. - `createdAt?` (`string`): Creation timestamp, when provided by the provider. - `expiresAt?` (`string`): Expiration timestamp, when provided by the provider. - `providerMetadata?` (`ProviderMetadata`): Provider-specific metadata for the batch. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_getRealtimeToolDefinitions description: API reference for experimental_getRealtimeToolDefinitions. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/get-realtime-tool-definitions" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `experimental_getRealtimeToolDefinitions` is part of the experimental realtime API. Converts an AI SDK `ToolSet` into provider-neutral realtime tool definitions that can be passed to a realtime session setup request. Use it in the server-side setup endpoint that creates a short-lived realtime provider token. ```ts import { openai } from '@ai-sdk/openai'; import { experimental_getRealtimeToolDefinitions, tool } from 'ai'; import { z } from 'zod'; const tools = { getWeather: tool({ description: 'Get the current weather for a city', inputSchema: z.object({ city: z.string(), }), }), }; const toolDefinitions = await experimental_getRealtimeToolDefinitions({ tools, }); const token = await openai.experimental_realtime.getToken({ model: 'gpt-realtime', sessionConfig: { tools: toolDefinitions, }, }); ``` ## Import ``` import { experimental_getRealtimeToolDefinitions } from "ai" ``` ## API Signature ### Parameters - `options` (`Object`): The options for converting tools. - `Object` - `tools` (`ToolSet`): The AI SDK tools to expose to the realtime model. Function and dynamic tools are converted to realtime function definitions. Provider tools are skipped. - `toolsContext?` (`InferToolSetContext`): Per-tool context used when resolving dynamic tool descriptions. ### Returns A `Promise`. Each returned definition contains: - `type` (`'function'`): The realtime tool definition type. - `name` (`string`): The tool name from the input ToolSet. - `description` (`string | undefined`): The tool description sent to the realtime model. - `parameters` (`JSONSchema7`): The JSON schema converted from the tool inputSchema. The realtime model uses this schema to generate tool call arguments. ## Notes - Tool execution is not handled by `experimental_getRealtimeToolDefinitions`. Use [`experimental_useRealtime`](/docs/reference/ai-sdk-ui/use-realtime) `onToolCall` and `addToolOutput` to execute tools and return results. - Provider tools are skipped because realtime providers expect regular function definitions in the session config. - Dynamic tool descriptions are resolved with the matching value from `toolsContext` before the definitions are returned. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: hasToolCall description: API Reference for hasToolCall. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/has-tool-call" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Creates a stop condition that stops when any specified tool is called in the most recent step. This function is used with `stopWhen` in `generateText` and `streamText` to control when a tool-calling loop should stop based on whether one of the specified tools has been invoked. ```ts import { generateText, hasToolCall } from 'ai'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { weather: weatherTool, finalAnswer: finalAnswerTool, }, // Stop when the finalAnswer tool is called stopWhen: hasToolCall('finalAnswer'), }); ``` ## Import ``` import { hasToolCall } from "ai" ``` ## API Signature ### Parameters - `...toolNames` (`string[]`): One or more tool names. The stop condition triggers when any of them is called in the most recent step. ### Returns A `StopCondition` function that returns `true` when any of the specified tools is called in the most recent step. The function can be used with the `stopWhen` parameter in `generateText` and `streamText`. ## Examples ### Basic Usage Stop when a specific tool is called: ```ts import { generateText, hasToolCall } from 'ai'; const result = await generateText({ model: yourModel, tools: { submitAnswer: submitAnswerTool, search: searchTool, }, stopWhen: hasToolCall('submitAnswer'), }); ``` ### Match Multiple Tools Stop when any of several tools is called in the most recent step: ```ts import { generateText, hasToolCall } from 'ai'; const result = await generateText({ model: yourModel, tools: { weather: weatherTool, search: searchTool, finalAnswer: finalAnswerTool, }, stopWhen: hasToolCall('weather', 'finalAnswer'), }); ``` ### Combining with Other Conditions You can combine multiple stop conditions in an array: ```ts import { generateText, hasToolCall, isStepCount } from 'ai'; const result = await generateText({ model: yourModel, tools: { weather: weatherTool, search: searchTool, finalAnswer: finalAnswerTool, }, // Stop when weather tool is called OR finalAnswer is called OR after 5 steps stopWhen: [ hasToolCall('weather'), hasToolCall('finalAnswer'), isStepCount(5), ], }); ``` ### Agent Pattern Common pattern for agents that run until they provide a final answer: ```ts import { generateText, hasToolCall } from 'ai'; const result = await generateText({ model: yourModel, tools: { search: searchTool, calculate: calculateTool, finalAnswer: { description: 'Provide the final answer to the user', inputSchema: z.object({ answer: z.string(), }), execute: async ({ answer }) => answer, }, }, stopWhen: hasToolCall('finalAnswer'), }); ``` ## See also - [`isStepCount()`](/docs/reference/ai-sdk-core/is-step-count) - [`generateText()`](/docs/reference/ai-sdk-core/generate-text) - [`streamText()`](/docs/reference/ai-sdk-core/stream-text) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI SDK Core description: Reference documentation for the AI SDK Core url: "https://ai-sdk.dev/docs/reference/ai-sdk-core" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [AI SDK Core](/docs/ai-sdk-core) is a set of functions that allow you to interact with language models and other AI models. These functions are designed to be easy-to-use and flexible, allowing you to generate text, structured data, and embeddings from language models and other AI models. AI SDK Core contains the following main functions: #### [generateText()](/docs/reference/ai-sdk-core/generate-text) Generate text and call tools from a language model. #### [streamText()](/docs/reference/ai-sdk-core/stream-text) Stream text and call tools from a language model. #### [Output](/docs/reference/ai-sdk-core/output) Structured output types for generateText and streamText. #### [embed()](/docs/reference/ai-sdk-core/embed) Generate an embedding for a single value using an embedding model. #### [embedMany()](/docs/reference/ai-sdk-core/embed-many) Generate embeddings for several values using an embedding model (batch embedding). #### [generateImage()](/docs/reference/ai-sdk-core/generate-image) Generate images based on a given prompt using an image model. #### [experimental\_generateVideo()](/docs/reference/ai-sdk-core/generate-video) Generate videos based on a given prompt using a video model. #### [experimental\_startBatch()](/docs/reference/ai-sdk-core/start-batch) Start an asynchronous text-generation batch. #### [experimental\_getBatchStatus()](/docs/reference/ai-sdk-core/get-batch-status) Retrieve the status of an asynchronous batch. #### [experimental\_getBatchResults()](/docs/reference/ai-sdk-core/get-batch-results) Retrieve terminal results from an asynchronous batch. #### [experimental\_cancelBatch()](/docs/reference/ai-sdk-core/cancel-batch) Request cancellation of an asynchronous batch. #### [experimental\_listBatches()](/docs/reference/ai-sdk-core/list-batches) List asynchronous batches and their latest statuses. #### [transcribe()](/docs/reference/ai-sdk-core/transcribe) Generate a transcript from an audio file. #### [experimental\_streamTranscribe()](/docs/reference/ai-sdk-core/stream-transcribe) Stream a transcript from live raw audio. #### [experimental\_streamTranslate()](/docs/reference/ai-sdk-core/stream-translate) Stream a speech-to-speech translation from live raw audio. #### [generateSpeech()](/docs/reference/ai-sdk-core/generate-speech) Generate speech audio from text. #### [uploadFile()](/docs/reference/ai-sdk-core/upload-file) Upload a file to a provider and get a provider reference. #### [uploadSkill()](/docs/reference/ai-sdk-core/upload-skill) Upload a skill to a provider and get a provider reference. It also contains the following helper functions: #### [toolSearch()](/docs/reference/ai-sdk-core/tool-search) Search deferred tools and load their definitions on demand. #### [tool()](/docs/reference/ai-sdk-core/tool) Type inference helper function for tools. #### [experimental\_getRealtimeToolDefinitions()](/docs/reference/ai-sdk-core/get-realtime-tool-definitions) Convert AI SDK tools into realtime tool definitions. #### [Experimental\_SandboxSession](/docs/reference/ai-sdk-core/sandbox) Experimental execution environment interface passed to tool execution. #### [experimental\_filterActiveTools()](/docs/reference/ai-sdk-core/filter-active-tools) Filters a tool set to only the currently active tools. #### [createMCPClient()](/docs/reference/ai-sdk-core/create-mcp-client) Creates a client for connecting to MCP servers. #### [MCP Events](/docs/reference/ai-sdk-core/mcp-events) Experimental event methods, webhook delivery, and subscription types. #### [MCP Apps](/docs/reference/ai-sdk-core/mcp-apps) Helpers for rendering interactive MCP tool UIs and filtering app-visible tools. #### [jsonSchema()](/docs/reference/ai-sdk-core/json-schema) Creates AI SDK compatible JSON schema objects. #### [zodSchema()](/docs/reference/ai-sdk-core/zod-schema) Creates AI SDK compatible Zod schema objects. #### [createProviderRegistry()](/docs/reference/ai-sdk-core/provider-registry) Creates a registry for using models from multiple providers. #### [cosineSimilarity()](/docs/reference/ai-sdk-core/cosine-similarity) Calculates the cosine similarity between two vectors, e.g. embeddings. #### [simulateReadableStream()](/docs/reference/ai-sdk-core/simulate-readable-stream) Creates a ReadableStream that emits values with configurable delays. #### [wrapLanguageModel()](/docs/reference/ai-sdk-core/wrap-language-model) Wraps a language model with middleware. #### [wrapImageModel()](/docs/reference/ai-sdk-core/wrap-image-model) Wraps an image model with middleware. #### [extractReasoningMiddleware()](/docs/reference/ai-sdk-core/extract-reasoning-middleware) Extracts reasoning from the generated text and exposes it as a \`reasoning\` property on the result. #### [extractJsonMiddleware()](/docs/reference/ai-sdk-core/extract-json-middleware) Extracts JSON from text content by stripping markdown code fences. #### [isStepCount()](/docs/reference/ai-sdk-core/is-step-count) Creates a stop condition that triggers after a specified number of steps. #### [hasToolCall()](/docs/reference/ai-sdk-core/has-tool-call) Creates a stop condition that triggers when any specified tool is called. #### [isLoopFinished()](/docs/reference/ai-sdk-core/loop-finished) Creates a stop condition that lets the agent loop run until it naturally finishes. #### [simulateStreamingMiddleware()](/docs/reference/ai-sdk-core/simulate-streaming-middleware) Simulates streaming behavior with responses from non-streaming language models. #### [defaultSettingsMiddleware()](/docs/reference/ai-sdk-core/default-settings-middleware) Applies default settings to a language model. #### [defaultInstructionsMiddleware()](/docs/reference/ai-sdk-core/default-instructions-middleware) Applies default instructions to a language model. #### [smoothStream()](/docs/reference/ai-sdk-core/smooth-stream) Smooths text and reasoning streaming output. #### [generateId()](/docs/reference/ai-sdk-core/generate-id) Helper function for generating unique IDs #### [createIdGenerator()](/docs/reference/ai-sdk-core/create-id-generator) Creates an ID generator --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: isStepCount description: API Reference for isStepCount. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/is-step-count" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Creates a stop condition that stops when the number of completed steps equals a specified count. This function is used with `stopWhen` in `generateText` and `streamText` to control when a tool-calling loop should stop based on the number of steps executed. ```ts import { generateText, isStepCount } from 'ai'; const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { // your tools }, // Stop after 5 steps stopWhen: isStepCount(5), }); ``` ## Import ``` import { isStepCount } from "ai" ``` ## API Signature ### Parameters - `stepCount` (`number`): The number of completed steps that should trigger the stop condition. ### Returns A `StopCondition` function that returns `true` when the number of completed steps equals the specified number. The function can be used with the `stopWhen` parameter in `generateText` and `streamText`. ## Examples ### Basic Usage Stop after 3 steps: ```ts import { generateText, isStepCount } from 'ai'; const result = await generateText({ model: yourModel, tools: yourTools, stopWhen: isStepCount(3), }); ``` ### Combining with Other Conditions You can combine multiple stop conditions in an array: ```ts import { generateText, isStepCount, hasToolCall } from 'ai'; const result = await generateText({ model: yourModel, tools: yourTools, // Stop after 10 steps OR when finalAnswer tool is called stopWhen: [isStepCount(10), hasToolCall('finalAnswer')], }); ``` ## See also - [`hasToolCall()`](/docs/reference/ai-sdk-core/has-tool-call) - [`generateText()`](/docs/reference/ai-sdk-core/generate-text) - [`streamText()`](/docs/reference/ai-sdk-core/stream-text) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: jsonSchema description: Helper function for creating JSON schemas url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/json-schema" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `jsonSchema` is a helper function that creates a JSON schema object that is compatible with the AI SDK. It takes the JSON schema and an optional validation function as inputs, and can be typed. You can use it to [generate structured data](/docs/ai-sdk-core/generating-structured-data) and in [tools](/docs/ai-sdk-core/tools-and-tool-calling). `jsonSchema` is an alternative to using Zod schemas that provides you with flexibility in dynamic situations (e.g. when using OpenAPI definitions) or for using other validation libraries. ```ts import { jsonSchema } from 'ai'; const mySchema = jsonSchema<{ recipe: { name: string; ingredients: { name: string; amount: string }[]; steps: string[]; }; }>({ type: 'object', properties: { recipe: { type: 'object', properties: { name: { type: 'string' }, ingredients: { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, amount: { type: 'string' }, }, required: ['name', 'amount'], }, }, steps: { type: 'array', items: { type: 'string' }, }, }, required: ['name', 'ingredients', 'steps'], }, }, required: ['recipe'], }); ``` ## Import ``` import { jsonSchema } from "ai" ``` ## API Signature ### Parameters - `schema` (`JSONSchema7`): The JSON schema definition. - `options` (`SchemaOptions`): Additional options for the JSON schema. - `SchemaOptions` - `validate?` (`(value: unknown) => { success: true; value: OBJECT } | { success: false; error: Error };`): A function that validates the value against the JSON schema. If the value is valid, the function should return an object with a \`success\` property set to \`true\` and a \`value\` property set to the validated value. If the value is invalid, the function should return an object with a \`success\` property set to \`false\` and an \`error\` property set to the error. ### Returns A JSON schema object that is compatible with the AI SDK. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: LanguageModelV4Middleware description: Middleware for enhancing language model behavior (API Reference) url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/language-model-v2-middleware" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Language model middleware is an experimental feature. Language model middleware provides a way to enhance the behavior of language models by intercepting and modifying the calls to the language model. It can be used to add features like guardrails, RAG, caching, and logging in a language model agnostic way. See [Language Model Middleware](/docs/ai-sdk-core/middleware) for more information. ## Import ``` import { LanguageModelV4Middleware } from "ai" ``` ## API Signature - `specificationVersion` (`'v4'`): The specification version of the middleware. Must be "v4". - `transformParams?` (`({ type: "generate" | "stream", params: LanguageModelV4CallOptions, model: LanguageModelV4 }) => PromiseLike`): Transforms the parameters before they are passed to the language model. - `wrapGenerate?` (`({ doGenerate: () => PromiseLike, doStream: () => PromiseLike, params: LanguageModelV4CallOptions, model: LanguageModelV4 }) => PromiseLike`): Wraps the generate operation of the language model. Receives both doGenerate and doStream functions. - `wrapStream?` (`({ doGenerate: () => PromiseLike, doStream: () => PromiseLike, params: LanguageModelV4CallOptions, model: LanguageModelV4 }) => PromiseLike`): Wraps the stream operation of the language model. Receives both doGenerate and doStream functions. - `overrideProvider?` (`(options: { model: LanguageModelV4 }) => string`): Override the provider ID of the model. - `overrideModelId?` (`(options: { model: LanguageModelV4 }) => string`): Override the model ID of the model. - `overrideSupportedUrls?` (`(options: { model: LanguageModelV4 }) => PromiseLike> | Record`): Override the supported URLs for the model. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_listBatches description: API Reference for experimental_listBatches. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/list-batches" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Batch support is experimental and the API may change in patch releases. Lists a page of asynchronous batches and their latest normalized statuses. For a complete guide to the batch lifecycle, see [Batch](/docs/ai-sdk-core/batch). ```ts import { experimental_listBatches as listBatches } from 'ai'; const page = await listBatches({ provider, limit: 20, cursor, }); console.log(page.batches, page.nextCursor); ``` ## Import ``` import { experimental_listBatches } from "ai" ``` ## API Signature ### Parameters - `provider?` (`Experimental_BatchProvider`): The provider whose batches should be listed. Defaults to the global provider, or the AI Gateway when no global provider is configured. - `limit?` (`number`): Maximum number of batches to return in this page. - `cursor?` (`string`): Opaque cursor returned as nextCursor by the previous list operation. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options for listing batches. - `maxRetries?` (`number`): Maximum number of retries for listing batches. Set to 0 to disable retries. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal to cancel the request. - `timeout?` (`number | { totalMs?: number }`): Maximum time allowed for the list request. - `headers?` (`Record`): Additional HTTP headers for the request. ### Returns - `batches` (`Array`): The batches in this page. Each item contains a serializable batch reference and its latest normalized status. - `nextCursor?` (`string`): Opaque cursor to pass as cursor to retrieve the next page. Omitted when there are no more batches. - `providerMetadata?` (`ProviderMetadata`): Provider-specific metadata for the list operation. Calling this function with a provider that does not support listing batches throws an `UnsupportedFunctionalityError`. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: isLoopFinished description: API Reference for isLoopFinished. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/loop-finished" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Creates a stop condition that never triggers, letting the agent loop run until it naturally finishes (i.e., the model stops making tool calls). By default, `ToolLoopAgent` uses `isStepCount(20)` as a safety measure to prevent runaway loops that could result in excessive API calls and costs. If you are confident that your agent will terminate naturally or you are less concerned about costs, `isLoopFinished()` removes that limit and lets the agent run until the model is truly done. ```ts import { ToolLoopAgent, isLoopFinished } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { // your tools }, stopWhen: isLoopFinished(), }); const result = await agent.generate({ prompt: 'Analyze this dataset and create a summary report', }); ``` ## Import ``` import { isLoopFinished } from "ai" ``` ## API Signature ### Parameters This function takes no parameters. ### Returns A `StopCondition` function that always returns `false`, meaning it never triggers the stop condition. The agent loop will only stop through its natural termination conditions: - The model stops making tool calls, or - A tool without an `execute` function is called, or - A tool call needs approval ## Examples ### Basic Usage Let the agent run until it's finished: ```ts import { ToolLoopAgent, isLoopFinished } from 'ai'; const agent = new ToolLoopAgent({ model: yourModel, tools: yourTools, stopWhen: isLoopFinished(), }); ``` ### Combining with Other Conditions You can combine `isLoopFinished()` with other conditions. Since `isLoopFinished()` never triggers, the other conditions still apply: ```ts import { ToolLoopAgent, isLoopFinished, hasToolCall } from 'ai'; const agent = new ToolLoopAgent({ model: yourModel, tools: yourTools, stopWhen: [isLoopFinished(), hasToolCall('finalAnswer')], }); ``` In practice, this does not make much sense in this context, since you could just omit `isLoopFinished()`. ## See also - [`isStepCount()`](/docs/reference/ai-sdk-core/is-step-count) - [`hasToolCall()`](/docs/reference/ai-sdk-core/has-tool-call) - [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: MCP Apps description: "API reference for MCP Apps helpers in @ai-sdk/mcp." url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/mcp-apps" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The MCP Apps helpers in `@ai-sdk/mcp` help an MCP host advertise UI support, keep model-visible and app-visible tools separate, and read `ui://` HTML resources for rendering. ## Import ``` import { MCP_APP_MIME_TYPE, mcpAppClientCapabilities, readMCPAppResource, splitMCPAppTools, } from "@ai-sdk/mcp" ``` ## `MCP_APP_MIME_TYPE` The MIME type for HTML resources that should be rendered as MCP Apps. ```ts const MCP_APP_MIME_TYPE = 'text/html;profile=mcp-app'; ``` ## `mcpAppClientCapabilities` Client capabilities to pass to [`createMCPClient`](/docs/reference/ai-sdk-core/create-mcp-client) when your host supports MCP Apps. ```ts import { createMCPClient, mcpAppClientCapabilities } from '@ai-sdk/mcp'; const client = await createMCPClient({ transport: { type: 'http', url: 'https://example.com/mcp', }, capabilities: mcpAppClientCapabilities, }); ``` The advertised capability is: ```json { "extensions": { "io.modelcontextprotocol/ui": { "mimeTypes": ["text/html;profile=mcp-app"] } } } ``` ## `splitMCPAppTools()` Splits MCP tool definitions into model-visible tools and app-visible tools. Tools without MCP Apps visibility metadata remain model-visible. Tools whose `_meta.ui.visibility` includes `"app"` are returned in `appVisible`. ```ts const definitions = await client.listTools(); const { modelVisible, appVisible } = splitMCPAppTools(definitions); const tools = client.toolsFromDefinitions(modelVisible); ``` ### Parameters - `definitions` (`ListToolsResult`): The tool definitions returned by \`client.listTools()\`. ### Returns - `modelVisible` (`ListToolsResult`): Tool definitions that can be exposed to the language model. - `appVisible` (`ListToolsResult`): Tool definitions that can be called by an MCP App through the host bridge. ## `readMCPAppResource()` Reads a `ui://` resource from an MCP server and normalizes it into HTML plus rendering metadata. ```ts const resource = await readMCPAppResource({ client, uri: 'ui://example/dashboard', }); ``` The helper validates that the URI starts with `ui://`, requires the `text/html;profile=mcp-app` MIME type, and supports resource contents returned as either text or base64 blob data. ### Parameters - `client` (`Pick`): The MCP client used to read the resource. - `uri` (`string`): The \`ui://\` resource URI to read. - `options?` (`RequestOptions`): Optional request options, such as an abort signal or timeout. ### Returns Returns a `Promise`. - `uri` (`string`): The \`ui://\` resource URI. - `mimeType` (`'text/html;profile=mcp-app'`): The MCP Apps HTML MIME type. - `html` (`string`): The app HTML to render in a sandboxed iframe. - `meta?` (`MCPAppResourceMeta`): Rendering metadata from resource \`\_meta.ui\`, such as CSP, permissions, and \`prefersBorder\`. ## See Also - [MCP Apps guide](/docs/ai-sdk-core/mcp-apps) - [createMCPClient](/docs/reference/ai-sdk-core/create-mcp-client) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: MCP Events description: Reference for experimental MCP event methods, the webhook handler, and subscription and storage types. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/mcp-events" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This page covers the experimental methods on `client.experimental_events`, the webhook handler, and their public types. Create the client with [`createMCPClient`](/docs/reference/ai-sdk-core/create-mcp-client). See the [MCP Events guide](/docs/ai-sdk-core/mcp-events) for the application flow. ## Setup ```ts import { createMCPClient } from '@ai-sdk/mcp'; import { eventStore } from './event-store'; const client = await createMCPClient({ transport: { type: 'http', url: 'https://service.example/mcp' }, experimental_events: { store: eventStore }, }); ``` `experimental_events` accepts [`Experimental_MCPEventsConfig`](/docs/reference/ai-sdk-core/mcp-events#client-configuration). In direct mode, the store is required for subscribe, refresh, and unsubscribe. Alternatively, configure a managed adapter as described below. Discovery works without configuring either mode. Mount the [webhook handler](/docs/reference/ai-sdk-core/mcp-events#webhook-handler) before subscribing. The methods below belong to `client.experimental_events`; they are not standalone imports. ## Listing Events ### `client.experimental_events.list()` Sends `events/list` and returns one catalog page. ```ts const { events, nextCursor } = await client.experimental_events.list(); console.log(events.map(event => event.name)); if (nextCursor != null) { const nextPage = await client.experimental_events.list({ params: { cursor: nextCursor }, }); console.log(nextPage.events); } ``` ### Parameters The optional argument has these properties: - `params?` (`{ cursor?: string }`): Pagination parameters. Omit to read the first page. Pass a previous nextCursor to read the next page. - `options?` (`{ signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number }`): Request cancellation and timeout settings. See Request Options below. ### Returns `Promise`: - `events` (`Experimental_MCPEventDefinition[]`): Event definitions with name, optional description, delivery modes, inputSchema, and payloadSchema. See the event definition type reference. - `nextCursor?` (`string | null`): Cursor for the next catalog page. Missing or null means there is no next page. See [`Experimental_MCPEventDefinition`](/docs/reference/ai-sdk-core/mcp-events#event-definition) for the definition fields. Pagination is explicit; this method does not fetch all pages. ## Managed Subscriptions Configure `experimental_events: { adapter }` to delegate subscription lifecycle operations to a backend. The `adapter` configuration requires an `Experimental_MCPEventAdapter` from `@ai-sdk/mcp`. Its `createAdapter({ transport })` method returns `Experimental_MCPEventOperations` bound to the authorized account, endpoint and destination by your application or integration package. ```ts import { createMCPClient } from '@ai-sdk/mcp'; import { authenticatedTransport, eventsAdapter } from '@/lib/mcp'; const client = await createMCPClient({ transport: authenticatedTransport, experimental_events: { adapter: eventsAdapter }, }); try { // Authenticated MCP discovery; this does not list saved subscriptions. const catalog = await client.experimental_events.list(); const watch = await client.experimental_events.subscribe({ name: 'comment.created', arguments: { document_id: 'doc_123' }, context: { bindingId: 'saved_intent_123' }, ttlMs: null, idempotencyKey: 'saved_intent_123', }); const state = await client.experimental_events.getSubscription({ id: watch.id, }); const page = await client.experimental_events.listSubscriptions({ status: 'active', limit: 20, }); const stopped = await client.experimental_events.unsubscribe({ id: watch.id, }); } finally { await client.close(); } ``` `authenticatedTransport` and `eventsAdapter` are supplied by your application. Use the same authorized account for discovery and subscription management. Never derive account identity, destination or authorization from model arguments. ### Using an Adapter Helper A backend SDK or application helper can return `Experimental_MCPEventAdapter` directly. Pass the helper's result as `adapter`; you do not need another object wrapper or an inline `createAdapter` callback: ```ts import { createMCPClient } from '@ai-sdk/mcp'; import { createBackendEventsAdapter } from './my-events-adapter'; const client = await createMCPClient({ transport: { type: 'http', url: 'https://service.example/mcp' }, experimental_events: { adapter: createBackendEventsAdapter({ accountId: authenticatedUser.id, destination: { path: '/hooks' }, }), }, }); ``` The helper holds the authorized account and delivery configuration. AI SDK supplies the transport type and URL to its `createAdapter` method internally, so the application configures that URL only once. When a backend SDK exports this helper, import it from that SDK instead of an application module. Use the same authorized account for the MCP transport and the backend's subscription operations. ### Implementing an Adapter Helper Backend integration authors implement `createAdapter` inside their helper: ```ts title="my-events-adapter.ts" import type { Experimental_MCPEventAdapter } from '@ai-sdk/mcp'; import { createBackendEventOperations } from './my-events-backend'; type BackendEventsConfig = { accountId: string; destination: { path: string }; mcpUrl?: string; }; export function createBackendEventsAdapter( config: BackendEventsConfig, ): Experimental_MCPEventAdapter { return { createAdapter({ transport }) { return createBackendEventOperations({ ...config, mcpUrl: config.mcpUrl ?? (transport.type === 'custom' ? undefined : transport.url), }); }, }; } ``` These helpers and `BackendEventsConfig` are illustrative application or backend SDK code, not AI SDK exports. `createBackendEventOperations` returns `Experimental_MCPEventOperations` bound to the authorized account, endpoint and destination. The helper above lets an explicit `mcpUrl` override the transport URL for backend operations; it does not change the MCP client's transport or catalog discovery. The backend still authorizes the selected endpoint. The AI SDK adapter contract is: ```ts interface MCPEventAdapter { createAdapter(context: { transport: { type: 'http' | 'sse'; url: string } | { type: 'custom' }; }): MCPEventOperations; } ``` The interface is exported as `Experimental_MCPEventAdapter`. The client calls `createAdapter` once, before starting the transport, and uses the returned operations for managed subscriptions. The return value must be an object with callable `subscribe`, `getSubscription`, `listSubscriptions`, and `unsubscribe` methods. Nullish values, missing or non-callable methods, and Promise/thenable results reject `createMCPClient` with `MCPClientError` before the transport starts. Errors thrown by `createAdapter` also reject initialization without starting the transport. Bound operations are no longer accepted directly in `experimental_events.adapter`. To migrate existing subscription operations, wrap them explicitly: ```ts experimental_events: { adapter: { createAdapter: () => existingOperations, }, } ``` Only reuse bound operations when their account, endpoint and destination are correct for every client using them. The former subscription operations interface is now exported as `Experimental_MCPEventOperations`; `Experimental_MCPEventAdapter` names the configured integration with `createAdapter`. For HTTP and SSE transport configurations, the context contains a fresh `transport: { type, url }` object. `url` is the configured string, including its path and query, not a resolved redirect target. Custom `MCPTransport` instances receive `transport: { type: 'custom' }`, with no `url` property; arbitrary properties on the instance are not inspected. This is a description of the transport, not the full configuration or live instance. Headers, auth providers, fetch functions, session callbacks and `start`/`send`/`close` methods are not passed to the adapter. The `transport` grouping leaves room for other client context without mixing it with transport metadata. No negotiated protocol version is supplied before initialization. Return separately bound operations for each client instead of mutating shared endpoint state. Adapters may prefer their own explicit endpoint override or resolve a configured endpoint when `transport.type` is `'custom'`. That policy belongs to the adapter; it does not change the client's transport or catalog discovery. The managed backend must still authorize an endpoint before sending credentials there. Receiving the transport URL does not grant that authorization. ### Managed Operations Contract ```ts import type { JSONObject } from '@ai-sdk/provider'; type ManagedSubscription = { id: string; name: string; arguments: JSONObject; status: | 'pending' | 'active' | 'needs_auth' | 'stopped' | 'expired' | 'failed'; expiresAt: string | null; cleanupStatus?: 'pending' | 'complete' | 'exhausted'; }; type ManagedSubscribeInput = { name: string; arguments: JSONObject; context?: JSONObject; idempotencyKey: string; ttlMs: number | null; options?: { signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number; }; }; ``` These types are exported as `Experimental_ManagedSubscription` and `Experimental_ManagedSubscribeInput`. Creation requires `ttlMs`: a positive integer duration in milliseconds, or `null` to monitor until stopped. The backend validates these values and enforces its limits. `expiresAt` is response-only. Reuse the same duration and idempotency key when retrying an uncertain create result. The backend calculates the deadline once; a retry must not extend it. `Experimental_MCPEventOperations` has four operations: | Operation | Input | Result | | ------------------- | ------------------------------------------------- | ---------------------------------- | | `subscribe` | `Experimental_ManagedSubscribeInput` | `Experimental_ManagedSubscription` | | `getSubscription` | `{ id, options? }` | `Experimental_ManagedSubscription` | | `listSubscriptions` | Optional `{ cursor?, limit?, status?, options? }` | `{ subscriptions, nextCursor? }` | | `unsubscribe` | `{ id, options? }` | `Experimental_ManagedSubscription` | All operations return promises. The client passes inputs, cancellation/timeout options, results and errors through without retries or remapping. The adapter must implement request-option handling and retain actionable backend errors, such as consent required, quota exceeded or temporary unavailability. Managed IDs identify backend records. `expiresAt` is the application's monitoring deadline, not the upstream MCP `refreshBefore` grant. A pending result means durable acceptance by the backend; upstream setup may still be running. Stopping can return `cleanupStatus: 'pending'` while remote cleanup continues. The backend owns callback URLs, secrets, upstream subscription calls, verification, renewal and cleanup. Managed `subscribe` does not implicitly discover the catalog, generate a secret, write a local store or call upstream `events/subscribe`. `getSubscription` and `listSubscriptions` are adapter methods, not MCP wire methods. The managed facade has no `refresh()`; the backend renews finite upstream leases even when the application watch has no deadline. Persist an application intent and reuse its idempotency key after an uncertain create result, within the backend's documented recovery window. A timeout or aborted request does not prove that a remote subscription was cancelled. Closing the MCP client does not unsubscribe managed watches. Managed delivery is separate from this client. Use the backend's delivery verifier and receiver contract; `experimental_createMCPEventWebhook` serves direct MCP webhooks. Applications still own authorized routing, durable receipt acceptance and idempotent work dispatch. The adapter does not invoke event callbacks. ### Configuration and Types Choose either `experimental_events: { adapter }` or `experimental_events: { store }`. Mixing them is rejected in TypeScript and at runtime. `validateArguments` is a direct-mode option; managed validation belongs to the backend. The unreleased `events` configuration spelling is replaced by `experimental_events`. `createMCPClient` infers `Experimental_ManagedMCPClient` with `Experimental_ManagedMCPEvents` when an adapter is configured. Both modes use `MCPClientConfig`, whose `experimental_events` property accepts the `Experimental_MCPEventsConfig` union. Direct clients keep `MCPClient` and `Experimental_MCPEvents`. The input and result types of each mode remain distinct. Use `satisfies MCPClientConfig` when saving a configuration to retain inference of the selected mode. A configuration whose mode is only known at runtime returns a union of the two client types; it does not promise direct-only methods such as `refresh()`. Omit `experimental_events` entirely for catalog discovery without a store or adapter. The remaining subscribe, refresh and unsubscribe sections describe direct mode. ## Subscribing to Events ### `client.experimental_events.subscribe(options)` Discovers the named event, checks webhook support, saves a pending subscription, and sends `events/subscribe`. ```ts const subscription = await client.experimental_events.subscribe({ name: 'comment.created', arguments: { document_id: 'doc_123' }, delivery: { mode: 'webhook', url: 'https://app.example/api/mcp-events', }, ttlMs: 3_600_000, }); console.log(subscription.id, subscription.refreshBefore); ``` ### Parameters `options` has type [`Experimental_SubscribeEventOptions`](/docs/reference/ai-sdk-core/mcp-events#subscription-options): - `name` (`string`): Name of the event to subscribe to. - `arguments?` (`JSONObject`): Event filters. Defaults to \{}. The SDK does not automatically validate them against inputSchema; experimental\_events.validateArguments can provide application validation. - `delivery` (`{ mode: 'webhook'; url: string; secret?: string; allowInsecureLocalhost?: boolean }`): Webhook delivery configuration. - `Webhook delivery` - `mode` (`'webhook'`): The only supported delivery mode. - `url` (`string`): Reachable HTTPS callback URL without credentials or a fragment. The SDK adds a mcp\_event\_subscription query parameter; preserve it when forwarding requests. - `secret?` (`string`): Standard Webhooks secret in whsec\_ format containing 24–64 base64-encoded random bytes. When omitted, the SDK generates a 32-byte secret. Saved privately under delivery.secret before events/subscribe is sent. - `allowInsecureLocalhost?` (`boolean`): Defaults to false. Allows HTTP only for literal 127.0.0.1 or \[::1] callback addresses during local development. This option is not sent to the server. - `cursor?` (`string | null`): Replay position. Defaults to null. - `ttlMs?` (`number | null`): Requested lifetime in milliseconds, as a positive safe integer. Omit for the server default; null requests no expiration. The server chooses the granted deadline. - `maxAgeMs?` (`number`): Optional replay-age bound in milliseconds, as a nonnegative safe integer. - `options?` (`{ signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number }`): Cancellation and timeout settings for MCP requests. Catalog discovery can make multiple requests. Pending records are retained when subscription creation fails, because a timed-out request might have registered remotely. Obtain the pending record's `key` from your application's store and recover with `refresh({ key })`, or cancel with `unsubscribe({ key })`. Both reuse the saved callback identity even when no server ID was saved. Calling `subscribe` again creates a new callback identity. ```ts // pending is the record retained by your application's store after a failed subscribe. const recovered = await client.experimental_events.refresh({ key: pending.key, }); // Alternatively, cancel the uncertain remote subscription: await client.experimental_events.unsubscribe({ key: pending.key }); ``` ### Returns `Promise`: - `id` (`string`): Server-derived subscription ID. Use it with refresh and unsubscribe. - `refreshBefore` (`string | null`): Server-granted renewal deadline as an ISO timestamp. Null means no renewal deadline was granted. - `cursor` (`string | null`): Server-provided replay cursor. If the server omits it, the SDK returns null. Null means no replay cursor is available. - `truncated` (`boolean`): Whether the server reports that the requested replay history was truncated. The result excludes the signing secret. The store is updated with the server ID, deadline, cursor, and active status before the method resolves. ## Refreshing a Subscription ### `client.experimental_events.refresh(options)` Loads a saved subscription by ID or storage key and sends `events/subscribe` with its original name, arguments, callback URL, secret, requested lifetime, and latest saved cursor. The subscription is marked pending before renewal. Event deliveries return `503` until the response saves the new expiration and restores active status. If renewal fails, the record stays pending because the server may have renewed it already; you can retry refresh or unsubscribe using the saved ID or key. Verified termination messages for a subscription with a saved ID are handled even while renewal is pending. For example, if access was revoked, `onTerminated` can still run and the saved subscription is removed after successful handling. ```ts const renewed = await client.experimental_events.refresh({ id: subscription.id, }); console.log(renewed.refreshBefore); ``` ### Parameters Provide exactly one of `id` or `key`. - `id?` (`string`): ID of a saved subscription in the configured store. - `key?` (`string`): Storage key of a saved subscription. Use this to recover a pending subscription whose server ID is unknown. - `options?` (`{ signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number }`): Request cancellation and timeout settings. ### Returns `Promise`, with the same fields as [subscribe](#subscribing-to-events). The new deadline and cursor are saved locally. Your application schedules renewal before `refreshBefore`. The SDK does not run a background renewal timer. Refresh reuses the saved filters without calling `validateArguments` again. ## Unsubscribing from Events ### `client.experimental_events.unsubscribe(options)` Resolves the ID or storage key to the saved name, arguments, and callback URL and sends `events/unsubscribe`. It deletes the local record only after the server succeeds. ```ts await client.experimental_events.unsubscribe({ id: subscription.id }); ``` ### Parameters Provide exactly one of `id` or `key`. - `id?` (`string`): ID of a saved subscription in the configured store. - `key?` (`string`): Storage key of a saved subscription. Use this to cancel a pending subscription whose server ID is unknown. - `options?` (`{ signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number }`): Request cancellation and timeout settings. ### Returns `Promise`. If the server request fails, the local record is retained. Calling `client.close()` only closes the MCP connection; it does not unsubscribe. ## Request Options All four methods accept an `options` property with the following fields. The listing method takes `{ params, options }`; the other methods include `options` alongside their own parameters. - `signal?` (`AbortSignal`): Abort the MCP request. - `timeout?` (`number`): Request timeout in milliseconds. - `maxTotalTimeout?` (`number`): Maximum request duration in milliseconds. If timeout is also supplied, the smaller limit applies. ## Errors The client rejects requests to servers without the `events` capability. Subscription lifecycle methods require `experimental_events.store`; refresh and unsubscribe also require a known subscription ID or storage key. Subscribe rejects unknown event names, unsupported delivery modes, invalid callback URLs, signing secrets, or lifetime settings. These checks use `MCPClientError`, except URL parsing may throw `TypeError`. MCP request errors, store failures, and application validation errors propagate to the caller. Event requests are not retried by the client's tool-call retry setting. See [MCP client error handling](/docs/reference/ai-sdk-core/create-mcp-client#error-handling). ## Webhook Handler ### `experimental_createMCPEventWebhook()` Creates a Web Request/Response handler for experimental MCP webhook events. It verifies callback challenges and event signatures using subscriptions saved by [`client.experimental_events.subscribe`](/docs/reference/ai-sdk-core/mcp-events#subscribing-to-events). ### Import ``` import { experimental_createMCPEventWebhook } from "@ai-sdk/mcp" ``` ### Example ```ts title="app/api/mcp-events/route.ts" import { experimental_createMCPEventWebhook } from '@ai-sdk/mcp'; import { eventStore } from '@/lib/event-store'; export const POST = experimental_createMCPEventWebhook({ store: eventStore, async onEvent({ subscription, event }) { console.log(subscription.id, event.name, event.data); }, async onGap({ subscription, gap }) { // Some events cannot be replayed; reconcile state or notify the agent. console.log('Replay gap:', subscription.id, gap.cursor, gap.truncated); }, async onTerminated({ subscription, termination }) { // The server ended the subscription; notify the agent of the reason. console.log('Subscription terminated:', subscription.id, termination.error); }, onError(error) { console.error(error); }, }); ``` Mount this route before subscribing. The handler and subscribing client must access the same private subscription store. ### Parameters The argument has type [`Experimental_MCPEventWebhookOptions`](/docs/reference/ai-sdk-core/mcp-events#webhook-options). - `store` (`Experimental_MCPEventStore`): Shared subscription storage. Used to look up the routing key and signing secret, and save the cursor after successful handling. - `onEvent` (`({ subscription, event }) => Promise`): Required application handler for verified events. Resolve after successful processing or durable acceptance. Throw to return 503 and allow retry. - `onGap?` (`({ subscription, messageId, gap }) => Promise`): Optional handler when some events cannot be replayed. Reconcile state or notify the agent, then resolve after processing or durable acceptance. The helper saves the fresh cursor and truncated: true; the subscription stays active. Throw to return 503 without changing saved state. - `onTerminated?` (`({ subscription, messageId, termination }) => Promise`): Optional handler when the server ends the subscription. Inspect termination.error for the reason and notify the agent, then resolve after processing or durable acceptance. The helper removes the saved subscription. Throw to return 503 without changing saved state. - `validatePayload?` (`({ definition, data }) => void | PromiseLike`): Optional application validation after signature and envelope checks, before onEvent. Receives the saved event definition and event.data. Throw to return 400 without calling onEvent or advancing the cursor. - `onError?` (`(error: unknown) => void`): Reports processing, storage, and payload-validation callback errors. Should not throw. Invalid signatures and malformed requests return their HTTP status without invoking this callback. #### Callback Arguments `onEvent` receives: - `subscription` (`Experimental_MCPEventSubscriptionInfo`): Subscription id, name, arguments, delivery mode and URL, and refreshBefore. Does not include delivery.secret. - `event` (`Experimental_MCPEvent`): The eventId, name, timestamp, data object, and optional nullable cursor. See [subscription metadata](/docs/reference/ai-sdk-core/mcp-events#subscription-metadata) and [event fields](/docs/reference/ai-sdk-core/mcp-events#event) for their full types. `onGap` and `onTerminated` receive the same safe `subscription` metadata and a `messageId` from the signed `webhook-id` header. Each callback receives only its matching control payload, so no `type` check is needed. Deduplicate retries by subscription ID and `messageId`. - `onGap` receives `gap: { type: 'gap', cursor, truncated: true }`. It means some events are no longer available for replay. Reconcile application state or notify the agent. The helper adds `truncated: true` and saves it with the fresh cursor after the callback resolves. The subscription remains active and event delivery continues. - `onTerminated` receives `termination: { type: 'terminated', error: { code, message, data? } }`. It means the server has ended the subscription. Inspect the error for the reason and notify the agent. After the callback resolves, the helper deletes the saved subscription record. Both return `204` after successful handling. State updates also occur when the corresponding callback is omitted. Callback or storage failures return `503` for retry. Events and gaps return `503` until activation or renewal is persisted. Termination is also accepted during pending renewal when the subscription ID is already saved; subscriptions without a saved ID still return `503`. Controls are processed even if the saved expiration has passed. They are never sent to `onEvent` or `validatePayload`. The helper does not run or notify an agent automatically. ### Returns ```ts (request: Request) => Promise; ``` The handler accepts `POST` requests with `Content-Type: application/json`. Pass the original request body bytes and preserve the `mcp_event_subscription` query parameter added by the subscribing client. Do not parse and reserialize the body before passing it to this handler. The server must include `webhook-id`, `webhook-timestamp`, `webhook-signature`, and `X-MCP-Subscription-Id`. The helper verifies Standard Webhooks v1 HMAC-SHA256 signatures and accepts signing timestamps within five minutes of the current time. An event's `eventId` must match `webhook-id`, and its name must match the subscription. ## Event Types The following types are exported from `@ai-sdk/mcp` and are experimental. ```ts import type { Experimental_MCPEvents, Experimental_MCPEventsConfig, Experimental_MCPEventDefinition, Experimental_ListEventsResult, Experimental_SubscribeEventOptions, Experimental_SubscribeEventResult, Experimental_MCPEvent, Experimental_MCPEventControl, Experimental_MCPEventSubscription, Experimental_MCPEventSubscriptionInfo, Experimental_MCPEventStore, Experimental_MCPEventWebhookOptions, } from '@ai-sdk/mcp'; ``` The shapes below use `JSONObject` from `@ai-sdk/provider` for JSON-compatible filter objects. Request settings are written inline because `RequestOptions` is not exported from `@ai-sdk/mcp`. ### Webhook Controls `Experimental_MCPEventControl` is the union of the lifecycle payloads. `onGap` receives its `gap` variant; `onTerminated` receives its `terminated` variant as `termination`: ```ts type Experimental_MCPEventControl = | { type: 'gap'; cursor: string; truncated: true } | { type: 'terminated'; error: { code: number; message: string; data?: unknown }; }; ``` Unknown extension fields are preserved. Verification challenges are echoed automatically and do not invoke `onEvent`, `onGap`, or `onTerminated`. ### Client Methods `Experimental_MCPEvents` describes the `client.experimental_events` property: ```ts interface Experimental_MCPEvents { list(options?: { params?: { cursor?: string }; options?: { signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number; }; }): Promise; subscribe( options: Experimental_SubscribeEventOptions, ): Promise; refresh( options: ({ id: string; key?: never } | { key: string; id?: never }) & { options?: { signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number; }; }, ): Promise; unsubscribe( options: ({ id: string; key?: never } | { key: string; id?: never }) & { options?: { signal?: AbortSignal; timeout?: number; maxTotalTimeout?: number; }; }, ): Promise; } ``` ## Related Guides - [MCP Events guide](/docs/ai-sdk-core/mcp-events) - [MCP client configuration](/docs/reference/ai-sdk-core/create-mcp-client) - [Runnable example](https://github.com/vercel/ai/tree/main/examples/mcp/src/events) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Experimental_StdioMCPTransport description: Create a transport for Model Context Protocol (MCP) clients to communicate with MCP servers using standard input and output streams url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/mcp-stdio-transport" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Creates a transport for Model Context Protocol (MCP) clients to communicate with MCP servers using standard input and output streams. This transport is only supported in Node.js environments. This feature is experimental and may change or be removed in the future. ## Import ``` import { Experimental_StdioMCPTransport } from "@ai-sdk/mcp/mcp-stdio" ``` ## API Signature ### Parameters - `config` (`StdioConfig`): Configuration for the MCP client. - `StdioConfig` - `command` (`string`): The command to run the MCP server. - `args?` (`string[]`): The arguments to pass to the MCP server. - `env?` (`Record`): The environment variables to set for the MCP server. - `stderr?` (`IOType | Stream | number`): The stream to write the MCP server's stderr to. - `cwd?` (`string`): The current working directory for the MCP server. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: ModelMessage description: Message types for AI SDK Core (API Reference) url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/model-message" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `ModelMessage` represents the fundamental message structure used with AI SDK Core functions. It encompasses various message types that can be used in the `messages` field of any AI SDK Core functions. You can access the Zod schema for `ModelMessage` with the `modelMessageSchema` export. ## `ModelMessage` Types ### `SystemModelMessage` A system message that can contain system information. ```typescript type SystemModelMessage = { role: 'system'; content: string; }; ``` You can access the Zod schema for `SystemModelMessage` with the `systemModelMessageSchema` export. Use the top-level `instructions` property instead of a system message for system instructions. AI SDK functions reject system messages in `prompt` or `messages` by default unless `allowSystemInMessages` is set to `true`. Opting in can create a prompt injection risk if users can inject system messages. ### `UserModelMessage` A user message that can contain text or a combination of text, images, and files. ```typescript type UserModelMessage = { role: 'user'; content: UserContent; }; type UserContent = string | Array; ``` You can access the Zod schema for `UserModelMessage` with the `userModelMessageSchema` export. ### `AssistantModelMessage` An assistant message that can contain text, tool calls, or a combination of both. ```typescript type AssistantModelMessage = { role: 'assistant'; content: AssistantContent; }; type AssistantContent = string | Array; ``` You can access the Zod schema for `AssistantModelMessage` with the `assistantModelMessageSchema` export. ### `ToolModelMessage` A tool message that contains the result of one or more tool calls. ```typescript type ToolModelMessage = { role: 'tool'; content: ToolContent; }; type ToolContent = Array; ``` You can access the Zod schema for `ToolModelMessage` with the `toolModelMessageSchema` export. ## `ModelMessage` Parts ### `TextPart` Represents a text content part of a prompt. It contains a string of text. ```typescript export interface TextPart { type: 'text'; /** * The text content. */ text: string; } ``` ### `ImagePart` (Deprecated) `ImagePart` is deprecated. Use [`FilePart`](#filepart) with `mediaType: 'image'` (or a more specific `image/*` subtype) instead. Represents an image part in a user message. ```typescript /** * @deprecated Use `FilePart` with `mediaType: 'image'` instead. */ export interface ImagePart { type: 'image'; /** * Image data. Can either be: * - data: a base64-encoded string, a Uint8Array, an ArrayBuffer, or a Buffer * - URL: a URL that points to the image * - ProviderReference: a provider reference from `uploadFile` */ image: DataContent | URL | ProviderReference; /** * Optional IANA media type of the image. * We recommend leaving this out as it will be detected automatically. */ mediaType?: string; } ``` ### `FilePart` Represents a file part in a user message. ```typescript export interface FilePart { type: 'file'; /** * File data. Use the tagged `FileData` shape. * Bare `DataContent`, `URL`, and `ProviderReference` shorthands are also supported. */ data: FileData | DataContent | URL | ProviderReference; /** * Optional filename of the file. */ filename?: string; /** * Either a full IANA media type (`type/subtype`, e.g. `image/png`) or just * the top-level IANA segment (e.g. `image`, `audio`, `video`, `text`). */ mediaType: string; } export type FileData = // Raw bytes as a base64 string, Uint8Array, ArrayBuffer, or Buffer. | { type: 'data'; data: DataContent } // A URL that points to the file. | { type: 'url'; url: URL } // A provider reference from `uploadFile`. | { type: 'reference'; reference: ProviderReference } // Inline text content. | { type: 'text'; text: string }; ``` ### `CustomPart` Represents a provider-specific custom content part. The `kind` field identifies the content type in the format `{provider}.{provider-type}`. ```typescript export interface CustomPart { type: 'custom'; /** * The kind of custom content, in the format `{provider}.{provider-type}`. */ kind: `${string}.${string}`; /** * Additional provider-specific metadata. */ providerOptions?: ProviderOptions; } ``` ### `ToolCallPart` Represents a tool call content part of a prompt, typically generated by the AI model. ```typescript export interface ToolCallPart { type: 'tool-call'; /** * ID of the tool call. This ID is used to match the tool call with the tool result. */ toolCallId: string; /** * Name of the tool that is being called. */ toolName: string; /** * Arguments of the tool call. This is a JSON-serializable object that matches the tool's input schema. */ args: unknown; } ``` ### `ToolResultPart` Represents the result of a tool call in a tool message. ```typescript export interface ToolResultPart { type: 'tool-result'; /** * ID of the tool call that this result is associated with. */ toolCallId: string; /** * Name of the tool that generated this result. */ toolName: string; /** * Result of the tool call. This is a JSON-serializable object. */ output: ToolResultOutput; /** Additional provider-specific metadata. They are passed through to the provider from the AI SDK and enable provider-specific functionality that can be fully encapsulated in the provider. */ providerOptions?: ProviderOptions; } ``` ### `ToolResultOutput` ```ts /** * Output of a tool result. */ export type ToolResultOutput = | { /** * Text tool output that should be directly sent to the API. */ type: 'text'; value: string; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { type: 'json'; value: JSONValue; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { /** * Type when the user has denied the execution of the tool call. */ type: 'execution-denied'; /** * Optional reason for the execution denial. */ reason?: string; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { type: 'error-text'; value: string; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { type: 'error-json'; value: JSONValue; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { type: 'content'; value: Array< | { type: 'text'; /** Text content. */ text: string; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { /** * @deprecated Use image-data or file-data instead. */ type: 'media'; data: string; mediaType: string; } | { type: 'file-data'; /** Base-64 encoded media data. */ data: string; /** IANA media type. @see https://www.iana.org/assignments/media-types/media-types.xhtml */ mediaType: string; /** * Optional filename of the file. */ filename?: string; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { type: 'file-url'; /** * URL of the file. */ url: string; /** * IANA media type of the file. * Used by providers to determine how to handle the file (e.g. image vs document). * Optional; if omitted, the SDK will attempt to infer it from the URL file extension. */ mediaType?: string; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { /** * @deprecated Use file-reference instead. */ type: 'file-id'; /** * ID of the file. * * If you use multiple providers, you need to * specify the provider specific ids using * the Record option. The key is the provider * name, e.g. 'openai' or 'anthropic'. */ fileId: string | Record; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { type: 'file-reference'; /** * Provider-specific references for the file. * The key is the provider name, e.g. 'openai' or 'anthropic'. */ providerReference: ProviderReference; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { /** * @deprecated Use file-data instead. * Images that are referenced using base64 encoded data. */ type: 'image-data'; /** Base-64 encoded image data. */ data: string; /** IANA media type. @see https://www.iana.org/assignments/media-types/media-types.xhtml */ mediaType: string; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { /** * @deprecated Use file-url instead. * Images that are referenced using a URL. */ type: 'image-url'; /** * URL of the image. */ url: string; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { /** * @deprecated Use file-reference instead. * Images that are referenced using a provider file id. */ type: 'image-file-id'; /** * Image that is referenced using a provider file id. * * If you use multiple providers, you need to * specify the provider specific ids using * the Record option. The key is the provider * name, e.g. 'openai' or 'anthropic'. */ fileId: string | Record; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { /** * @deprecated Use file-reference instead. * Images that are referenced using a provider reference. */ type: 'image-file-reference'; /** * Provider-specific references for the image file. * The key is the provider name, e.g. 'openai' or 'anthropic'. */ providerReference: ProviderReference; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } | { /** * Custom content part. This can be used to implement * provider-specific content parts. */ type: 'custom'; /** * Provider-specific options. */ providerOptions?: ProviderOptions; } >; }; ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Output description: API Reference for Output. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/output" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `Output` object provides output specifications for structured data generation with [`generateText`](/docs/reference/ai-sdk-core/generate-text) and [`streamText`](/docs/reference/ai-sdk-core/stream-text). It allows you to specify the expected shape of the generated data and handles validation automatically. ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; const { output } = await generateText({ model: "anthropic/claude-sonnet-5.5", output: Output.object({ schema: z.object({ name: z.string(), age: z.number(), }), }), prompt: 'Generate a user profile.', }); ``` ## Import ``` import { Output } from "ai" ``` ## Output Types ### `Output.text()` Output specification for plain text generation. This is the default behavior when no `output` is specified. ```ts import { generateText, Output } from 'ai'; const { output } = await generateText({ model: yourModel, output: Output.text(), prompt: 'Tell me a joke.', }); // output is a string ``` #### Parameters No parameters required. #### Returns An `Output` specification that generates plain text without schema validation. *** ### `Output.object()` Output specification for typed object generation using schemas. The output is validated against the provided schema to ensure type safety. ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; const { output } = await generateText({ model: yourModel, output: Output.object({ schema: z.object({ name: z.string(), age: z.number().nullable(), labels: z.array(z.string()), }), }), prompt: 'Generate information for a test user.', }); // output matches the schema type ``` #### Parameters - `schema` (`FlexibleSchema`): The schema that defines the structure of the object to generate. Supports Zod schemas, Standard JSON schemas, and custom JSON schemas. - `name?` (`string`): Optional name of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema name. - `description?` (`string`): Optional description of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema description. #### Returns An `Output>` specification where: - Complete output is fully validated against the schema - Partial output (during streaming) is a deep partial version of the schema type Partial outputs streamed via `streamText` cannot be validated against your provided schema, as incomplete data may not yet conform to the expected structure. *** ### `Output.array()` Output specification for generating arrays of typed elements. Each element is validated against the provided element schema. ```ts import { generateText, Output } from 'ai'; import { z } from 'zod'; const { output } = await generateText({ model: yourModel, output: Output.array({ element: z.object({ location: z.string(), temperature: z.number(), condition: z.string(), }), minItems: 2, maxItems: 2, }), prompt: 'List the weather for San Francisco and Paris.', }); // output is an array of weather objects ``` #### Parameters - `element` (`FlexibleSchema`): The schema that defines the structure of each array element. Supports Zod schemas, Valibot schemas, or JSON schemas. - `minItems?` (`number`): The minimum number of elements to generate. Must be a non-negative integer. - `maxItems?` (`number`): The maximum number of elements to generate. Must be a non-negative integer and greater than or equal to minItems. - `name?` (`string`): Optional name of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema name. - `description?` (`string`): Optional description of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema description. #### Returns An `Output, Array>` specification where: - Complete output is an array with all elements validated - Complete output is validated against `minItems` and `maxItems` - Partial output contains only fully validated elements (incomplete elements are excluded) Set `minItems` and `maxItems` to the same value to require an exact number of elements. The bounds are included in the provider-facing schema when the provider supports them. The AI SDK also validates the completed output, so providers that ignore these schema keywords cannot return an out-of-bounds result. #### Streaming with `elementStream` When using `streamText` with `Output.array()`, you can iterate over elements as they are generated using `elementStream`: ```ts import { streamText, Output } from 'ai'; import { z } from 'zod'; const { elementStream } = streamText({ model: yourModel, output: Output.array({ element: z.object({ name: z.string(), class: z.string(), description: z.string(), }), }), prompt: 'Generate 3 hero descriptions for a fantasy role playing game.', }); for await (const hero of elementStream) { console.log(hero); // Each hero is complete and validated } ``` Each element emitted by `elementStream` is complete and validated against your element schema, ensuring type safety for each item as it is generated. If the model generates more than `maxItems`, `elementStream` errors before emitting the first excess element. Provider generation is not aborted automatically, and the final `output` promise also rejects. *** ### `Output.choice()` Output specification for selecting from a predefined set of string options. Useful for classification tasks or fixed-enum answers. ```ts import { generateText, Output } from 'ai'; const { output } = await generateText({ model: yourModel, output: Output.choice({ options: ['sunny', 'rainy', 'snowy'] as const, }), prompt: 'Is the weather sunny, rainy, or snowy today?', }); // output is 'sunny' | 'rainy' | 'snowy' ``` #### Parameters - `options` (`Array`): An array of string options that the model can choose from. The output will be exactly one of these values. - `name?` (`string`): Optional name of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema name. - `description?` (`string`): Optional description of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema description. #### Returns An `Output` specification where: - Complete output is validated to be exactly one of the provided options *** ### `Output.json()` Output specification for unstructured JSON generation. Use this when you want to generate arbitrary JSON without enforcing a specific schema. ```ts import { generateText, Output } from 'ai'; const { output } = await generateText({ model: yourModel, output: Output.json(), prompt: 'For each city, return the current temperature and weather condition as a JSON object.', }); // output is any valid JSON value ``` #### Parameters - `name?` (`string`): Optional name of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema name. - `description?` (`string`): Optional description of the output that should be generated. Used by some providers for additional LLM guidance, e.g. via tool or schema description. #### Returns An `Output` specification that: - Validates that the output is valid JSON - Does not enforce any specific structure With `Output.json()`, the AI SDK only checks that the response is valid JSON; it doesn't validate the structure or types of the values. If you need schema validation, use `Output.object()` or `Output.array()` instead. ## Error Handling When `generateText` with structured output cannot generate a valid object, it throws a [`NoObjectGeneratedError`](/docs/reference/ai-sdk-errors/ai-no-object-generated-error). ```ts import { generateText, Output, NoObjectGeneratedError } from 'ai'; try { await generateText({ model: yourModel, output: Output.object({ schema }), prompt: 'Generate a user profile.', }); } catch (error) { if (NoObjectGeneratedError.isInstance(error)) { console.log('NoObjectGeneratedError'); console.log('Cause:', error.cause); console.log('Text:', error.text); console.log('Response:', error.response); console.log('Usage:', error.usage); } } ``` ## See also - [Generating Structured Data](/docs/ai-sdk-core/generating-structured-data) - [`generateText()`](/docs/reference/ai-sdk-core/generate-text) - [`streamText()`](/docs/reference/ai-sdk-core/stream-text) - [`zod-schema`](/docs/reference/ai-sdk-core/zod-schema) - [`json-schema`](/docs/reference/ai-sdk-core/json-schema) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: pipeAgentUIStreamToResponse description: API Reference for the pipeAgentUIStreamToResponse utility. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/pipe-agent-ui-stream-to-response" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `pipeAgentUIStreamToResponse` function runs an [Agent](/docs/reference/ai-sdk-core/agent) and streams the resulting UI message output directly to a Node.js [`ServerResponse`](https://nodejs.org/api/http.html#class-httpserverresponse) object. This is ideal for building real-time streaming API endpoints (for chat, tool use, etc.) in Node.js-based frameworks like Express, Hono, or custom Node servers. ## Import ``` import { pipeAgentUIStreamToResponse } from "ai" ``` ## Usage ```ts import { pipeAgentUIStreamToResponse } from 'ai'; import { MyAgent } from './agent'; export async function handler(req, res) { const { messages } = JSON.parse(req.body); await pipeAgentUIStreamToResponse({ response: res, // Node.js ServerResponse agent: MyAgent, uiMessages: messages, // Required: array of input UI messages // abortSignal: optional AbortSignal for cancellation // experimental_sandbox: optional experimental sandbox passed through to tool execution // status: 200, // headers: { ... }, // ...other optional UI message stream options }); } ``` ## Parameters - `response` (`ServerResponse`): The Node.js ServerResponse object to pipe UI message stream output into. - `agent` (`Agent`): An agent instance implementing \`.stream(\{ prompt, ... })\` and defining a \`tools\` property. - `uiMessages` (`unknown[]`): Array of input UI messages sent to the agent (such as user/assistant message objects). - `convertDataPart` (`(part: DataUIPart) => TextPart | FilePart | undefined`): Converts custom UI data parts into text or file parts for the model. Data parts are ignored when this callback is omitted or returns undefined. - `abortSignal` (`AbortSignal`): Optional abort signal to cancel streaming (e.g., on client disconnect). Supply an \[\`AbortSignal\`]\(https\://developer.mozilla.org/en-US/docs/Web/API/AbortSignal), for example from an \`AbortController\`. - `timeout` (`number | { totalMs?: number }`): Timeout in milliseconds. Can be specified as a number or as an object with a totalMs property. The call will be aborted if it takes longer than the specified timeout. Can be used alongside abortSignal. - `experimental_sandbox` (`Experimental_SandboxSession`): Optional experimental sandbox environment that is passed through to tool execution. Tools can access it from their execution context. - `options` (`CALL_OPTIONS`): Optional agent call options, for agents configured with generic parameter \`CALL\_OPTIONS\`. - `experimental_transform` (`StreamTextTransform | StreamTextTransform[]`): Optional stream text transformation(s) applied to agent output. - `onStepEnd` (`GenerateTextOnStepEndCallback`): Callback invoked after each agent step (LLM/tool call) completes. Useful for tracking token usage, per-step performance, or logging intermediate steps. - `onStepFinish` (`GenerateTextOnStepFinishCallback`): Deprecated. Use \`onStepEnd\` instead. This alias is only used as a fallback when \`onStepEnd\` is not provided. - `onUIMessageStepEnd` (`UIMessageStreamOnStepEndCallback`): Callback invoked after each UI message step is assembled. It receives \`messages\`, \`isContinuation\`, and the accumulated \`responseMessage\` for intermediate persistence. - `...UIMessageStreamResponseInit & AgentUIMessageStreamOptions` (`object`): Options for streaming headers, status, SSE stream config, and additional UI message stream control. ## Returns A `Promise`. The function completes when the UI message stream has been fully sent to the provided ServerResponse. Use `onUIMessageStepEnd` to persist the accumulated UI response after every agent step. It is awaited before the corresponding `finish-step` chunk is piped. Use idempotent writes and protect model output, reasoning, tool inputs, and tool outputs as sensitive conversation data. Use the existing `onStepEnd` callback when you need low-level generation usage, finish reasons, or raw step details instead. ## Example: Express Route Handler ```ts import { pipeAgentUIStreamToResponse } from 'ai'; import { openaiWebSearchAgent } from './openai-web-search-agent'; app.post('/chat', async (req, res) => { // Use req.body.messages as input UI messages await pipeAgentUIStreamToResponse({ response: res, agent: openaiWebSearchAgent, uiMessages: req.body.messages, // experimental_sandbox, // optional // abortSignal: yourController.signal // status: 200, // headers: { ... }, // ...more options }); }); ``` ## How It Works 1. **Runs the Agent:** Calls the agent’s `.stream` method with the provided UI messages and options, converting them into model messages as needed and passing through options such as `experimental_sandbox`. 2. **Streams UI Message Output:** Pipes the agent output as a UI message stream to the `ServerResponse`, sending data via streaming HTTP responses (including appropriate headers). 3. **Abort Signal Handling:** If `abortSignal` is supplied, streaming is cancelled as soon as the signal is triggered (such as on client disconnect). 4. **No Response Return:** Unlike Edge/serverless APIs that return a `Response`, this function writes bytes directly to the ServerResponse and does not return a response object. ## Notes - **Abort Handling:** For best robustness, use an `AbortSignal` (for example, wired to Express/Hono client disconnects) to ensure quick cancellation of agent computation and streaming. - **Node.js Only:** Only works with Node.js [ServerResponse](https://nodejs.org/api/http.html#class-httpserverresponse) objects (e.g., in Express, Hono’s node adapter, etc.), not Edge/serverless/web Response APIs. - **Streaming Support:** Make sure your client (and any proxies) correctly support streaming HTTP responses for full effect. - **Parameter Names:** The property for input messages is `uiMessages` (not `messages`) for consistency with SDK agent utilities. - Pass `experimental_sandbox` when your agent tools need an experimental sandbox environment during execution. ## See Also - [`createAgentUIStreamResponse`](/docs/reference/ai-sdk-core/create-agent-ui-stream-response) - [`Agent`](/docs/reference/ai-sdk-core/agent) - [`UIMessage`](/docs/reference/ai-sdk-core/ui-message) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: createProviderRegistry description: Registry for managing multiple providers and models (API Reference) url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/provider-registry" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). When you work with multiple providers and models, it is often desirable to manage them in a central place and access the models through simple string ids. `createProviderRegistry` lets you create a registry with multiple providers that you can access by their ids in the format `providerId:modelId`. In TypeScript, registry model IDs are inferred from the registered provider IDs. When a provider exposes literal model ID types, editors can suggest the combined `providerId:modelId` values. ### Setup You can create a registry with multiple providers and models using `createProviderRegistry`. ```ts import { anthropic } from '@ai-sdk/anthropic'; import { createOpenAI } from '@ai-sdk/openai'; import { createProviderRegistry } from 'ai'; export const registry = createProviderRegistry({ // register provider with prefix and default setup: anthropic, // register provider with prefix and custom setup: openai: createOpenAI({ apiKey: process.env.OPENAI_API_KEY, }), }); ``` ### Custom Separator By default, the registry uses `:` as the separator between provider and model IDs. You can customize this separator by passing a `separator` option: ```ts const registry = createProviderRegistry( { anthropic, openai, }, { separator: ' > ' }, ); // Now you can use the custom separator const model = registry.languageModel('anthropic > claude-3-opus-20240229'); ``` ### Language models You can access language models by using the `languageModel` method on the registry. The provider id will become the prefix of the model id: `providerId:modelId`. ```ts {5} import { generateText } from 'ai'; import { registry } from './registry'; const { text } = await generateText({ model: registry.languageModel('openai:gpt-6-astra'), prompt: 'Invent a new holiday and describe its traditions.', }); ``` ### Text embedding models You can access text embedding models by using the `.embeddingModel` method on the registry. The provider id will become the prefix of the model id: `providerId:modelId`. ```ts {5} import { embed } from 'ai'; import { registry } from './registry'; const { embedding } = await embed({ model: registry.embeddingModel('openai:text-embedding-3-small'), value: 'sunny day at the beach', }); ``` ### Image models You can access image models by using the `imageModel` method on the registry. The provider id will become the prefix of the model id: `providerId:modelId`. ```ts {5} import { generateImage } from 'ai'; import { registry } from './registry'; const { image } = await generateImage({ model: registry.imageModel('openai:dall-e-3'), prompt: 'A beautiful sunset over a calm ocean', }); ``` ### Video models You can access video models by using the `videoModel` method on the registry. The provider id will become the prefix of the model id: `providerId:modelId`. ```ts {7} import { fal } from '@ai-sdk/fal'; import { createProviderRegistry, experimental_generateVideo } from 'ai'; const registry = createProviderRegistry({ fal }); const { videos } = await experimental_generateVideo({ model: registry.videoModel('fal:luma-dream-machine/ray-2'), prompt: 'A cat walking on a beach at sunset', }); ``` ### Files and skills You can access a provider's files and skills interfaces by calling `registry.files(providerId)` and `registry.skills(providerId)`. ## Import ``` import { createProviderRegistry } from "ai" ``` ## API Signature ### Parameters - `providers` (`Record`): The unique identifier for the provider. It should be unique within the registry. - `Provider` - `languageModel` (`(id: string) => LanguageModel`): A function that returns a language model by its id. - `embeddingModel` (`(id: string) => EmbeddingModel`): A function that returns a text embedding model by its id. - `imageModel` (`(id: string) => ImageModel`): A function that returns an image model by its id. - `transcriptionModel?` (`(id: string) => TranscriptionModel`): A function that returns a transcription model by its id. - `speechModel?` (`(id: string) => SpeechModel`): A function that returns a speech model by its id. - `rerankingModel?` (`(id: string) => RerankingModel`): A function that returns a reranking model by its id. - `videoModel?` (`(id: string) => VideoModelV4`): A function that returns a video model by its id. - `files?` (`() => FilesV4`): A function that returns the provider files API interface. - `skills?` (`() => SkillsV4`): A function that returns the provider skills API interface. - `options?` (`object`): Optional configuration for the registry. - `Options` - `separator?` (`string`): Custom separator between provider and model IDs. Defaults to ":". - `languageModelMiddleware?` (`LanguageModelMiddleware | LanguageModelMiddleware[]`): Middleware to wrap all language models obtained from the registry. - `imageModelMiddleware?` (`ImageModelMiddleware | ImageModelMiddleware[]`): Middleware to wrap all image models obtained from the registry. ### Returns The `createProviderRegistry` function returns a `Provider` instance. It has the following methods: - `languageModel` (`(id: string) => LanguageModel`): A function that returns a language model by its id (format: providerId:modelId) - `embeddingModel` (`(id: string) => EmbeddingModel`): A function that returns a text embedding model by its id (format: providerId:modelId) - `imageModel` (`(id: string) => ImageModel`): A function that returns an image model by its id (format: providerId:modelId) - `transcriptionModel` (`(id: string) => TranscriptionModel`): A function that returns a transcription model by its id (format: providerId:modelId) - `speechModel` (`(id: string) => SpeechModel`): A function that returns a speech model by its id (format: providerId:modelId) - `rerankingModel` (`(id: string) => RerankingModel`): A function that returns a reranking model by its id (format: providerId:modelId) - `videoModel` (`(id: string) => VideoModelV4`): A function that returns a video model by its id (format: providerId:modelId) - `files` (`(providerId: string) => FilesV4`): A function that returns a provider files API interface by provider id. - `skills` (`(providerId: string) => SkillsV4`): A function that returns a provider skills API interface by provider id. ## Experimental decision models The inferred return type also exposes `decisionModel('providerId:modelId')`, returning `Experimental_DecisionModelV4`. The provider must expose an `decisionModel` factory. Custom separators and model ID inference work as they do for video models. Language and image middleware do not wrap decision models. `ProviderRegistryProvider` remains a stable interface; use the inferred return type or `Experimental_DecisionProviderRegistry` to retain experimental decision access. Unavailable decision capabilities or models throw `NoSuchModelError` with `modelType: 'decisionModel'`; unknown registry providers throw `NoSuchProviderError`. These capabilities are structural extensions and are not added to the stable `ProviderV4` contract. Direct decision string IDs default to Gateway when no default provider is configured. See [Decisions](/docs/ai-sdk-core/decisions#model-aliases-and-registries) for runnable usage patterns. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: rerank description: API Reference for rerank. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/rerank" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Rerank a set of documents based on their relevance to a query using a reranking model. This is ideal for improving search relevance by reordering documents, emails, or other content based on semantic understanding of the query and documents. ```ts import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: ['sunny day at the beach', 'rainy afternoon in the city'], query: 'talk about rain', }); ``` ## Import ``` import { rerank } from "ai" ``` ## API Signature ### Parameters - `model` (`RerankingModel`): The reranking model to use. Example: cohere.reranking('rerank-v4.0-pro') - `documents` (`Array`): The documents to rerank. Can be an array of strings or JSON objects. - `query` (`string`): The search query to rank documents against. - `topN?` (`number`): Maximum number of top documents to return. If not specified, all documents are returned. - `maxRetries?` (`number`): Maximum number of retries. Set to 0 to disable retries. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal that can be used to cancel the call. - `headers?` (`Record`): Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers. - `providerOptions?` (`ProviderOptions`): Provider-specific options for the reranking request. - `runtimeContext?` (`RUNTIME_CONTEXT`): User-defined runtime context passed to lifecycle callbacks. Defaults to an empty object. Telemetry integrations only receive top-level properties explicitly included with telemetry.includeRuntimeContext. - `telemetry?` (`TelemetryOptions`): Telemetry configuration. - `TelemetryOptions` - `isEnabled?` (`boolean`): Enable or disable telemetry. Enabled by default. Set to \`false\` to opt out. - `recordInputs?` (`boolean`): Enable or disable input recording. Enabled by default. - `recordOutputs?` (`boolean`): Enable or disable output recording. Enabled by default. - `functionId?` (`string`): Identifier for this function. Used to group telemetry data by function. - `includeRuntimeContext?` (`{ [KEY in keyof RUNTIME_CONTEXT]?: boolean }`): Top-level runtime context properties to include in telemetry. Only properties set to true are included. All properties are excluded by default. User callbacks still receive the full context. - `integrations?` (`Telemetry | Telemetry[]`): Per-call telemetry integrations that receive lifecycle events. When provided, these replace any globally registered integrations for this call. - `onStart?` (`(event: RerankStartEvent) => void | Promise`): Called when the rerank operation begins, before the reranking model is called. The event includes the full, unfiltered runtimeContext. - `onEnd?` (`(event: RerankEndEvent) => void | Promise`): Called when the rerank operation completes, after the reranking model returns. The event includes the full, unfiltered runtimeContext. ### Returns - `originalDocuments` (`Array`): The original documents array in their original order. - `rerankedDocuments` (`Array`): The documents sorted by relevance score (descending). - `ranking` (`Array>`): Array of ranking items with scores and indices. - `RankingItem` - `originalIndex` (`number`): The index of the document in the original documents array. - `score` (`number`): The relevance score for the document (typically 0-1, where higher is more relevant). - `document` (`VALUE`): The document itself. - `response` (`Response`): Response data. - `Response` - `id?` (`string`): The response ID from the provider. - `timestamp` (`Date`): The timestamp of the response. - `modelId` (`string`): The model ID used for reranking. - `headers?` (`Record`): Response headers. - `body?` (`unknown`): The raw response body. - `providerMetadata?` (`ProviderMetadata | undefined`): Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. ## Examples ### String Documents ```ts import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const { ranking, rerankedDocuments } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: [ 'sunny day at the beach', 'rainy afternoon in the city', 'snowy night in the mountains', ], query: 'talk about rain', topN: 2, }); console.log(rerankedDocuments); // ['rainy afternoon in the city', 'sunny day at the beach'] console.log(ranking); // [ // { originalIndex: 1, score: 0.9, document: 'rainy afternoon...' }, // { originalIndex: 0, score: 0.3, document: 'sunny day...' } // ] ``` ### Object Documents ```ts import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const documents = [ { from: 'Paul Doe', subject: 'Follow-up', text: 'We are happy to give you a discount of 20%.', }, { from: 'John McGill', subject: 'Missing Info', text: 'Here is the pricing from Oracle: $5000/month', }, ]; const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents, query: 'Which pricing did we get from Oracle?', topN: 1, }); console.log(ranking[0].document); // { from: 'John McGill', subject: 'Missing Info', ... } ``` ### With Provider Options ```ts import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: ['sunny day at the beach', 'rainy afternoon in the city'], query: 'talk about rain', providerOptions: { cohere: { maxTokensPerDoc: 1000, }, }, }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: safeValidateUIMessages description: API Reference for safeValidateUIMessages url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/safe-validate-ui-messages" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `safeValidateUIMessages` is an async function that validates UI messages like [`validateUIMessages`](/docs/reference/ai-sdk-core/validate-ui-messages), but instead of throwing it returns an object with a `success` key and either `data` or `error`. ## Basic Usage Simple validation without custom schemas: ```typescript import { safeValidateUIMessages } from 'ai'; const messages = [ { id: '1', role: 'user', parts: [{ type: 'text', text: 'Hello!' }], }, ]; const result = await safeValidateUIMessages({ messages, }); if (!result.success) { console.error(result.error.message); } else { const validatedMessages = result.data; } ``` ## Deprecated `rawInput` field For backward compatibility, validation still accepts `rawInput` on tool parts in the `output-error` state. When a defined `rawInput` value is found, `safeValidateUIMessages` emits an AI SDK deprecation warning through `AI_SDK_LOG_WARNINGS`. Migrate persisted messages to store tool arguments in `input` and remove `rawInput`. For backward compatibility, conversion uses `rawInput` as a fallback when `input` is `null` or `undefined`. `rawInput` will be removed in the next major version. ## Advanced Usage Comprehensive validation with custom metadata, data parts, and tools: ```typescript import { safeValidateUIMessages, tool } from 'ai'; import { z } from 'zod'; // Define schemas const metadataSchema = z.object({ timestamp: z.string().datetime(), userId: z.string(), }); const dataSchemas = { chart: z.object({ data: z.array(z.number()), labels: z.array(z.string()), }), image: z.object({ url: z.string().url(), caption: z.string(), }), }; const tools = { weather: tool({ description: 'Get weather info', inputSchema: z.object({ location: z.string(), }), execute: async ({ location }) => `Weather in ${location}: sunny`, }), }; // Messages with custom parts const messages = [ { id: '1', role: 'user', metadata: { timestamp: '2024-01-01T00:00:00Z', userId: 'user123' }, parts: [ { type: 'text', text: 'Show me a chart' }, { type: 'data-chart', data: { data: [1, 2, 3], labels: ['A', 'B', 'C'] }, }, ], }, { id: '2', role: 'assistant', parts: [ { type: 'tool-weather', toolCallId: 'call_123', state: 'output-available', input: { location: 'San Francisco' }, output: 'Weather in San Francisco: sunny', }, ], }, ]; // Validate with all schemas const result = await safeValidateUIMessages({ messages, metadataSchema, dataSchemas, tools, }); if (!result.success) { console.error(result.error.message); } else { const validatedMessages = result.data; } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Experimental_SandboxSession description: API Reference for the Experimental_SandboxSession interface. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/sandbox" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `Experimental_SandboxSession` interface describes an execution environment that tools can use to run commands. Pass an experimental sandbox using the `experimental_sandbox` option to `generateText`, `streamText`, `ToolLoopAgent.generate`, `ToolLoopAgent.stream`, or agent UI stream helpers to make it available to tool description functions and tool execution. This API is experimental and can change in patch releases. Passing an experimental sandbox does not sandbox the tool itself. Tool code still runs in your application process unless the tool explicitly delegates work to the experimental sandbox. ## Import ``` import type { Experimental_SandboxSession } from "ai" ``` ## Type Definition ```ts type Experimental_SandboxSession = { readonly description: string; readonly run: (options: { command: string; workingDirectory?: string; env?: Record; abortSignal?: AbortSignal; }) => PromiseLike<{ exitCode: number; stdout: string; stderr: string; }>; }; ``` ## Properties - `description` (`string`): Description of the experimental sandbox environment. Include this in your prompt or instructions when the model needs to know details such as the root directory, exposed ports, public hostname, or other environment constraints. The AI SDK does not add it to the prompt automatically. - `run` (`(options: { command: string; workingDirectory?: string; env?: Record; abortSignal?: AbortSignal }) => PromiseLike<{ exitCode: number; stdout: string; stderr: string }>`): Executes a command in the experimental sandbox and resolves with the command exit code, standard output, and standard error. - `options` - `command` (`string`): The command to execute in the experimental sandbox. - `workingDirectory` (`string | undefined`): Optional working directory to execute the command in. If omitted, the experimental sandbox implementation uses its default working directory. - `env` (`Record | undefined`): Optional environment variables to set for the command. Merged with the experimental sandbox default environment, with these values taking precedence. Passing secrets here instead of inlining them into the command avoids leaking them in logs. Implementations that cannot set environment variables reject this option. - `abortSignal` (`AbortSignal | undefined`): Optional abort signal that the experimental sandbox implementation can use to cancel the command. ## Example ```ts const result = await generateText({ model: "anthropic/claude-sonnet-5.5", tools: { shell }, experimental_sandbox, prompt: 'Run the test suite.', }); ``` Inside a tool, read the experimental sandbox from the second `execute` argument: ```ts const shell = tool({ inputSchema: z.object({ command: z.string(), workingDirectory: z.string().optional(), }), execute: async ( { command, workingDirectory }, { abortSignal, experimental_sandbox }, ) => { if (!experimental_sandbox) { throw new Error('Experimental sandbox is not available'); } return experimental_sandbox.run({ command, workingDirectory, abortSignal, }); }, }); ``` ## See Also - [Tool Calling: Experimental Sandbox](/docs/ai-sdk-core/tools-and-tool-calling#experimental-sandbox) - [`generateText`](/docs/reference/ai-sdk-core/generate-text) - [`streamText`](/docs/reference/ai-sdk-core/stream-text) - [`ToolLoopAgent`](/docs/reference/ai-sdk-core/tool-loop-agent) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: simulateReadableStream description: Create a ReadableStream that emits values with configurable delays url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/simulate-readable-stream" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `simulateReadableStream` is a utility function that creates a ReadableStream which emits provided values sequentially with configurable delays. This is particularly useful for testing streaming functionality or simulating time-delayed data streams. ```ts import { simulateReadableStream } from 'ai'; const stream = simulateReadableStream({ chunks: ['Hello', ' ', 'World'], initialDelayInMs: 100, chunkDelayInMs: 50, }); ``` ## Import ``` import { simulateReadableStream } from "ai" ``` ## API Signature ### Parameters - `chunks` (`T[]`): Array of values to be emitted by the stream - `initialDelayInMs?` (`number | null`): Initial delay in milliseconds before emitting the first value. Defaults to 0. Set to null to skip the initial delay entirely. - `chunkDelayInMs?` (`number | null`): Delay in milliseconds between emitting each value. Defaults to 0. Set to null to skip delays between chunks. ### Returns Returns a `ReadableStream` that: - Emits each value from the provided `chunks` array sequentially - Waits for `initialDelayInMs` before emitting the first value (if not `null`) - Waits for `chunkDelayInMs` between emitting subsequent values (if not `null`) - Closes automatically after all chunks have been emitted ### Type Parameters - `T`: The type of values contained in the chunks array and emitted by the stream ## Examples ### Basic Usage ```ts const stream = simulateReadableStream({ chunks: ['Hello', ' ', 'World'], }); ``` ### With Delays ```ts const stream = simulateReadableStream({ chunks: ['Hello', ' ', 'World'], initialDelayInMs: 1000, // Wait 1 second before first chunk chunkDelayInMs: 500, // Wait 0.5 seconds between chunks }); ``` ### Without Delays ```ts const stream = simulateReadableStream({ chunks: ['Hello', ' ', 'World'], initialDelayInMs: null, // No initial delay chunkDelayInMs: null, // No delay between chunks }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: simulateStreamingMiddleware description: Middleware that simulates streaming for non-streaming language models url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/simulate-streaming-middleware" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `simulateStreamingMiddleware` is a middleware function that simulates streaming behavior with responses from non-streaming language models. This is useful when you want to maintain a consistent streaming interface even when using models that only provide complete responses. ```ts import { simulateStreamingMiddleware } from 'ai'; const middleware = simulateStreamingMiddleware(); ``` ## Import ``` import { simulateStreamingMiddleware } from "ai" ``` ## API Signature ### Parameters This middleware doesn't accept any parameters. ### Returns Returns a middleware object that: - Takes a complete response from a language model - Converts it into a simulated stream of chunks - Properly handles various response components including: - Text content - Reasoning (as string or array of objects) - Tool calls - Metadata and usage information - Warnings ### Usage Example ```ts import { streamText } from 'ai'; import { wrapLanguageModel } from 'ai'; import { simulateStreamingMiddleware } from 'ai'; // Example with a non-streaming model const result = streamText({ model: wrapLanguageModel({ model: nonStreamingModel, middleware: simulateStreamingMiddleware(), }), prompt: 'Your prompt here', }); // Now you can use the streaming interface for await (const chunk of result.stream) { // Process streaming chunks } ``` ## How It Works The middleware: 1. Awaits the complete response from the language model 2. Creates a `ReadableStream` that emits chunks in the correct sequence 3. Simulates streaming by breaking down the response into appropriate chunk types 4. Preserves all metadata, reasoning, tool calls, and other response properties --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: smoothStream description: Stream transformer for smoothing text and reasoning output url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/smooth-stream" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `smoothStream` is a utility function that creates a TransformStream for the `streamText` `experimental_transform` option to smooth out text and reasoning streaming by buffering and releasing complete chunks with configurable delays. This creates a more natural reading experience when streaming text and reasoning responses. ```ts {6-9} import { smoothStream, streamText } from 'ai'; const result = streamText({ model, prompt, experimental_transform: smoothStream({ delayInMs: 20, // optional: defaults to 10ms chunking: 'line', // optional: defaults to 'word' }), }); ``` ## Import ``` import { smoothStream } from "ai" ``` ## API Signature ### Parameters - `delayInMs?` (`number | null`): The delay in milliseconds between outputting each chunk. Defaults to 10ms. Set to \`null\` to disable delays. The delay is skipped while the document is hidden (e.g. browser background tabs), where timer throttling would otherwise stall the stream. - `chunking?` (`"word" | "line" | RegExp | Intl.Segmenter | (buffer: string) => string | undefined | null`): Controls how text and reasoning content is chunked for streaming. Use "word" to stream word by word (default), "line" to stream line by line, an Intl.Segmenter for locale-aware word segmentation (recommended for CJK languages), or provide a custom callback or RegExp pattern that does not match the empty string for custom chunking. #### Word chunking caveats with non-latin languages The word based chunking **does not work well** with the following languages that do not delimit words with spaces: - Chinese - Japanese - Korean - Vietnamese - Thai #### Using Intl.Segmenter (recommended) For these languages, we recommend using `Intl.Segmenter` for proper locale-aware word segmentation. This is the preferred approach as it provides accurate word boundaries for CJK and other languages. `Intl.Segmenter` is available in Node.js 16+ and all modern browsers (Chrome 87+, Firefox 125+, Safari 14.1+). ```tsx title="Japanese example with Intl.Segmenter" import { smoothStream, streamText } from 'ai'; const segmenter = new Intl.Segmenter('ja', { granularity: 'word' }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Your prompt here', experimental_transform: smoothStream({ chunking: segmenter, }), }); ``` ```tsx title="Chinese example with Intl.Segmenter" import { smoothStream, streamText } from 'ai'; const segmenter = new Intl.Segmenter('zh', { granularity: 'word' }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Your prompt here', experimental_transform: smoothStream({ chunking: segmenter, }), }); ``` #### Regex based chunking To use regex based chunking, pass a `RegExp` to the `chunking` option. Global and sticky expressions are supported. The expression must not match the empty string. ```ts // To split on underscores: smoothStream({ chunking: /_+/, }); // Also can do it like this, same behavior smoothStream({ chunking: /[^_]*_/, }); ``` #### Custom callback chunking To use a custom callback for chunking, pass a function to the `chunking` option. ```ts smoothStream({ chunking: text => { const findString = 'some string'; const index = text.indexOf(findString); if (index === -1) { return null; } return text.slice(0, index) + findString; }, }); ``` ### Returns Returns a `TransformStream` that: - Buffers incoming text and reasoning chunks - Releases content when the chunking pattern is encountered - Adds configurable delays between chunks for smooth output - Passes through non-text/reasoning chunks (like tool calls, step-finish events) immediately --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_startBatch description: API Reference for experimental_startBatch. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/start-batch" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Batch support is experimental and the API may change in patch releases. Starts an asynchronous batch of text or image generation requests. For a complete guide to the batch lifecycle, see [Batch](/docs/ai-sdk-core/batch). ```ts import { experimental_startBatch as startBatch } from 'ai'; const batch = await startBatch({ requests: [ { id: 'france', type: 'text', model: 'gpt-5.4-nano', prompt: 'What is the capital of France?', }, ], }); ``` ## Import ``` import { experimental_startBatch } from "ai" ``` ## API Signature ### Parameters - `provider?` (`Experimental_BatchProvider`): The provider to use. Defaults to the global provider, or the AI Gateway when no global provider is configured. - `requests` (`Array`): The independent requests to process. Each request must have a unique, non-empty id, a supported type, and type-specific properties such as model and prompt or messages for text requests. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options for the batch. - `webhookUrl?` (`string`): URL to notify when the batch reaches a terminal state. Support and payloads are provider-specific. - `abortSignal?` (`AbortSignal`): An optional abort signal to cancel the request that starts the batch. - `timeout?` (`number | { totalMs?: number }`): Maximum time allowed for the batch creation request. - `headers?` (`Record`): Additional HTTP headers for the request. ### Returns - `version` (`2`): Version of the serializable batch reference. - `id` (`string`): Provider batch identifier. - `provider` (`string`): Provider identifier for the batch. - `status` (`'pending' | 'completed' | 'failed'`): Initial normalized batch status. - `rawStatus?` (`string`): The provider-specific batch status, when available. - `requestCounts?` (`{ total: number; pending: number; completed: number; failed: number }`): Provider-reported request counts. - `error?` (`Experimental_BatchError`): Error details when the batch fails. - `createdAt?` (`string`): Creation timestamp, when provided by the provider. - `expiresAt?` (`string`): Expiration timestamp, when provided by the provider. - `providerMetadata?` (`ProviderMetadata`): Provider-specific metadata for the batch. - `warnings` (`Warning[]`): Warnings returned while starting the batch. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_streamTextLowMemory description: Stream with the streamText execution engine without a replay buffer or unused final results. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text-low-memory" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). This API is experimental and may change in future versions. Use `experimental_streamTextLowMemory` when you consume the output once and do not need to read the complete result later. ```ts import { experimental_streamTextLowMemory } from 'ai'; const result = experimental_streamTextLowMemory({ model: "anthropic/claude-sonnet-5.5", prompt: 'Describe the history of astronomy.', }); for await (const delta of result.textStream) { process.stdout.write(delta); } console.log(await result.totalUsage); ``` ## Supported features The function accepts the same options as `streamText`, including model settings, prompts and messages, provider options, tools, tool approval, `prepareStep`, `stopWhen`, structured output, transforms, retries, timeouts, cancellation, callbacks, and telemetry. These use the shared execution engine; there is no separate tool loop or provider implementation. Tool execution and subsequent model calls receive the complete conversation history. Lifecycle callbacks and telemetry receive their normal content and are not silently skipped. The existing error semantics also apply: use `onError` or `stream` to observe provider errors; `textStream` contains only text deltas. ```ts import { experimental_streamTextLowMemory, isStepCount, tool } from 'ai'; import { z } from 'zod/v4'; const result = experimental_streamTextLowMemory({ model: "anthropic/claude-sonnet-5.5", prompt: 'How many characters are in astronomy?', tools: { countCharacters: tool({ description: 'Count the Unicode code points in a string.', inputSchema: z.object({ text: z.string() }), execute: async ({ text }) => Array.from(text).length, }), }, stopWhen: isStepCount(3), }); for await (const part of result.stream) { if (part.type === 'text-delta') process.stdout.write(part.text); if (part.type === 'tool-result') console.log(part.output); } ``` ## Memory usage `experimental_streamTextLowMemory` does not save a copy of the stream for replay. For plain text streaming, it also avoids keeping the complete generated text for later access. This API reduces memory use; it does not set a fixed memory limit. Providers, UI message assembly, and your own code can also keep output in memory. For example, appending every chunk to a string in your application still builds the full answer. ## Return value The result exposes these members: | Member | Behavior | | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | `textStream` | Text deltas as an async iterable and readable stream. | | `stream` / `fullStream` | All execution events, including tools, reasoning, errors, and finish events. | | `partialOutputStream` | Cumulative partial output. Requires an explicit `output` option; use `Output.text()` for cumulative text. | | `elementStream` | Parsed elements when the output specification supports them. | | `totalUsage` / `usage` | Promise-like usage metadata, resolved when the stream finishes. | | `finishReason` / `rawFinishReason` | Promise-like completion metadata. | | `consumeStream()` | Consume and discard the stream when no other consumer is needed. | | `toTextStreamResponse()` / `pipeTextStreamToResponse()` | Text response helpers. | | `toUIMessageStream()` / `toUIMessageStreamResponse()` / `pipeUIMessageStreamToResponse()` | UI message conversion and response helpers, including their existing callbacks. | Choose **one** stream accessor, response helper, or `consumeStream()` call. A second attempt to claim the stream throws or rejects. There is no replay after completion. Calling `.tee()` on the returned readable stream yourself can reintroduce buffering. Metadata promises can be obtained before, during, or after streaming. They do not consume the stream: start a consumer before awaiting completion metadata. Cancelling the stream rejects any pending completion promises and cancels the upstream stream. External abort signals follow `streamText`'s abort behavior. Final-content getters such as `text`, `content`, `steps`, `finalStep`, `responseMessages`, and `output` are absent from both the type and runtime object. Use ordinary `streamText` when you need its retained results or independent stream readers. Its behavior is unchanged. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: streamText description: API Reference for streamText. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Streams text generations from a language model. You can use the streamText function for interactive use cases such as chat bots and other real-time applications. You can also generate UI components with tools. ```ts import { streamText } from 'ai'; const { textStream } = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Invent a new holiday and describe its traditions.', }); for await (const textPart of textStream) { process.stdout.write(textPart); } ``` For guidance on `runtimeContext`, `toolsContext`, tool `context`, and sensitive context filtering, see [Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context). To see `streamText` in action, check out [these examples](#examples). For outputs that you only need to consume once, consider [low-memory streaming](/docs/reference/ai-sdk-core/stream-text-low-memory) with `streamText`. It uses the same execution engine with one stream consumer and no replay buffer. Plain streaming avoids text accumulation; tools, structured output, and callbacks retain the content their contracts require. ## Import ``` import { streamText } from "ai" ``` ## API Signature ### Parameters - `model` (`LanguageModel`): The language model to use. Example: openai('gpt-6-astra') - `instructions` (`Instructions`): Instructions to use that specify the behavior of the model. - `prompt` (`string | Array`): The input prompt to generate the text from. - `messages` (`Array`): A list of messages that represent a conversation. Automatically converts UI messages from the useChat hook. - `SystemModelMessage` - `role` (`'system'`): The role for the system message. - `content` (`string`): The content of the message. - `UserModelMessage` - `role` (`'user'`): The role for the user message. - `content` (`string | Array`): The content of the message. - `TextPart` - `type` (`'text'`): The type of the message part. - `text` (`string`): The text content of the message part. - `ImagePart` - `type` (`'image'`): The type of the message part. - `image` (`string | Uint8Array | Buffer | ArrayBuffer | URL`): The image content of the message part. String are either base64 encoded content, base64 data URLs, or http(s) URLs. - `mediaType?` (`string`): The IANA media type of the image. - `FilePart` - `type` (`'file'`): The type of the message part. - `data` (`string | Uint8Array | Buffer | ArrayBuffer | URL`): The file content of the message part. String are either base64 encoded content, base64 data URLs, or http(s) URLs. - `mediaType` (`string`): The IANA media type of the file. - `AssistantModelMessage` - `role` (`'assistant'`): The role for the assistant message. - `content` (`string | Array`): The content of the message. - `TextPart` - `type` (`'text'`): The type of the message part. - `text` (`string`): The text content of the message part. - `ReasoningPart` - `type` (`'reasoning'`): The type of the reasoning part. - `text` (`string`): The reasoning text. - `ReasoningFilePart` - `type` (`'reasoning-file'`): The type of the message part. - `data` (`string | Uint8Array | Buffer | ArrayBuffer | URL`): The file data. String are either base64 encoded content, base64 data URLs, or http(s) URLs. - `mediaType` (`string`): The IANA media type of the file. - `FilePart` - `type` (`'file'`): The type of the message part. - `data` (`string | Uint8Array | Buffer | ArrayBuffer | URL`): The file content of the message part. String are either base64 encoded content, base64 data URLs, or http(s) URLs. - `mediaType` (`string`): The IANA media type of the file. - `filename?` (`string`): The name of the file. - `ToolCallPart` - `type` (`'tool-call'`): The type of the message part. - `toolCallId` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `input` (`object based on zod schema`): Parameters generated by the model to be used by the tool. - `ToolModelMessage` - `role` (`'tool'`): The role for the tool message. - `content` (`Array`): The content of the message. - `ToolResultPart` - `type` (`'tool-result'`): The type of the message part. - `toolCallId` (`string`): The id of the tool call the result corresponds to. - `toolName` (`string`): The name of the tool the result corresponds to. - `result` (`unknown`): The result returned by the tool after execution. - `isError?` (`boolean`): Whether the result is an error or an error message. - `allowSystemInMessages?` (`boolean`): Whether system messages are allowed in the \`prompt\` or \`messages\` fields. Defaults to false. System messages in the \`instructions\` option are always allowed. Enabling this for user-controlled messages can create a prompt injection risk. - `tools` (`ToolSet`): Tools that are accessible to and can be called by the model. The model needs to support calling tools. - `Tool` - `description?` (`string | ((options: { context: CONTEXT; experimental_sandbox?: Experimental_SandboxSession }) => string)`): Information about the purpose of the tool including details on how and when it can be used by the model. Provide a string for a fixed description, or a function to derive the description from the tool-specific context and optional experimental sandbox before each model call. - `inputSchema` (`Zod Schema | JSON Schema`): The schema of the input that the tool expects. The language model will use this to generate the input. It is also used to validate the output of the language model. Use descriptions to make the input understandable for the language model. You can either pass in a Zod schema or a JSON schema (using the \`jsonSchema\` function). - `execute?` (`async (parameters: T, options: ToolExecutionOptions) => RESULT`): An async function that is called with the arguments from the tool call and produces a result. If not provided, the tool will not be executed automatically. - `ToolExecutionOptions` - `toolCallId` (`string`): The ID of the tool call. You can use it e.g. when sending tool-call related information with stream data. - `messages` (`ModelMessage[]`): Messages that were sent to the language model to initiate the response that contained the tool call. The messages do not include the system prompt nor the assistant response that contained the tool call. - `abortSignal` (`AbortSignal`): An optional abort signal that indicates that the overall operation should be aborted. - `toolChoice?` (`"auto" | "none" | "required" | { "type": "tool", "toolName": string }`): The tool choice setting. It specifies how tools are selected for execution. The default is "auto". "none" disables tool execution. "required" requires tools to be executed. \{ "type": "tool", "toolName": string } specifies a specific tool to execute. - `maxOutputTokens?` (`number`): Maximum number of tokens to generate. - `temperature?` (`number`): Temperature setting. The value is passed through to the provider. The range depends on the provider and model. It is recommended to set either \`temperature\` or \`topP\`, but not both. - `topP?` (`number`): Nucleus sampling. The value is passed through to the provider. The range depends on the provider and model. It is recommended to set either \`temperature\` or \`topP\`, but not both. - `topK?` (`number`): Only sample from the top K options for each subsequent token. Used to remove "long tail" low probability responses. Recommended for advanced use cases only. You usually only need to use temperature. - `presencePenalty?` (`number`): Presence penalty setting. It affects the likelihood of the model to repeat information that is already in the prompt. The value is passed through to the provider. The range depends on the provider and model. - `frequencyPenalty?` (`number`): Frequency penalty setting. It affects the likelihood of the model to repeatedly use the same words or phrases. The value is passed through to the provider. The range depends on the provider and model. - `stopSequences?` (`string[]`): Sequences that will stop the generation of the text. If the model generates any of these sequences, it will stop generating further text. - `seed?` (`number`): The seed (integer) to use for random sampling. If set and supported by the model, calls will generate deterministic results. - `reasoning?` (`'provider-default' | 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'`): Controls how much reasoning the model performs before generating a response. When omitted, the provider's default behavior is used. \`'provider-default'\` explicitly requests the provider's default. Providers that do not support reasoning will emit a warning. Provider-specific settings take precedence over corresponding mapped fields. Partial configurations may still use portable reasoning to fill missing fields; summary and display settings can be combined with it. See the \[reasoning guide]\(/docs/ai-sdk-core/reasoning) for provider-specific mapping details. - `maxRetries?` (`number`): Maximum number of retries. Set to 0 to disable retries. Default: 2. - `streamRetries?` (`number`): Maximum number of automatic retries for provider error events received after response streaming begins. Retries rerun only the current model step and preserve completed earlier steps and their tool results. Tool-related output and client-side tool work from failed attempts are discarded; other output already emitted cannot be retracted, but is excluded from the recovered step result, structured output parsing, response messages, and subsequent model steps. Final request and response metadata come from the recovered attempt. Open text and reasoning parts are ended before recovered output begins. onStepStart runs once for the logical step, onLanguageModelCallStart runs for each attempt, and onLanguageModelCallEnd runs for attempts that reach a model-call finish. Provider-executed tool work cannot be undone and may repeat. Set to 0 to disable automatic retries while allowing onError to request one callback-directed retry. With automatic retries configured, onError can request at most one additional retry after they are exhausted, bounding total recovery calls to streamRetries + 1. Omit to disable all stream retry behavior and preserve incremental tool streaming for existing onError observers. Default: 0. - `abortSignal?` (`AbortSignal`): An optional abort signal that can be used to cancel the call. When it fires during streaming, pending result promises such as text and steps reject with the abort reason without waiting for onAbort or provider cancellation. Completed steps are available through onAbort. - `timeout?` (``number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number; toolMs?: number; tools?: Partial> }``): Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, firstChunkMs, chunkMs, toolMs, and/or tools properties. totalMs sets the total timeout for the entire call. stepMs sets the timeout for each complete step, including model generation, retries, and local tool execution. firstChunkMs sets the timeout until the first content-bearing output in each step; metadata, stream starts, empty deltas, raw chunks, and transport activity do not satisfy it. chunkMs starts with the first meaningful model output and limits gaps between subsequent meaningful model output chunks. Both output timers stop when the model response finishes and start fresh for each retry or later model call. Local tool results, including preliminary results, do not reset them; local tool execution is outside their scope. Provider-executed tools remain within the model response. toolMs sets the default timeout for all tool executions. tools sets per-tool timeout overrides using the pattern \{toolName}Ms (e.g. weatherMs, slowApiMs) that take precedence over toolMs - tool names are type-checked for autocomplete. If a tool takes longer than its timeout, it aborts and returns a tool-error so the model can respond or retry. Can be used alongside abortSignal. - `headers?` (`Record`): Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers. - `telemetry?` (`TelemetryOptions`): Telemetry configuration. - `TelemetryOptions` - `isEnabled?` (`boolean`): Enable or disable telemetry. Enabled by default. Set to \`false\` to opt out. - `recordInputs?` (`boolean`): Enable or disable input recording. Enabled by default. - `recordOutputs?` (`boolean`): Enable or disable output recording. Enabled by default. - `functionId?` (`string`): Identifier for this function. Used to group telemetry data by function. - `includeRuntimeContext?` (`{ [KEY in keyof CONTEXT]?: boolean }`): Top-level runtime context properties that should be included in telemetry. Runtime context properties are excluded unless they are explicitly set to \`true\`. Lifecycle callbacks and returned results still receive the full \`runtimeContext\`. - `includeToolsContext?` (`{ [TOOL_NAME in keyof InferToolSetContext]?: { [KEY in keyof InferToolSetContext[TOOL_NAME]]?: boolean } }`): Top-level tool context properties that should be included in telemetry, configured per tool. Tool context properties are excluded unless they are explicitly set to \`true\`. Lifecycle callbacks and returned results still receive the full \`toolsContext\`. - `integrations?` (`Telemetry | Telemetry[]`): Per-call telemetry integrations that receive lifecycle events. When provided, these replace any globally registered integrations for this call. - `experimental_transform?` (`StreamTextTransform | Array`): Optional stream transformations. They are applied in the order they are provided. The stream transformations must maintain the stream structure for streamText to work correctly. - `StreamTextTransform` - `transform` (`(options: TransformOptions) => TransformStream, TextStreamPart>`): A transformation that is applied to the stream. - `TransformOptions` - `stopStream` (`() => void`): A function that stops the stream. - `tools` (`TOOLS`): The tools that are available. - `includeRawChunks?` (`boolean`): Deprecated. Use include.rawChunks instead. Whether to include raw chunks from the provider in the stream. When enabled, you will receive raw chunks with type "raw" that contain the unprocessed data from the provider. This allows access to cutting-edge provider features not yet wrapped by the AI SDK. Defaults to false. - `providerOptions?` (`Record | undefined`): Provider-specific options. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. - `activeTools?` (`ActiveTools`): Limits the tools that are available for the model to call without changing the tool call and result types in the result. All tools are active by default. Tool names are restricted to the string keys of the tool set. - `toolOrder?` (`ToolOrder`): Controls the order in which tools are sent to the provider. The list can be partial. Tools not listed in \`toolOrder\` are sent after the listed tools, sorted alphabetically. Tool names are restricted to the string keys of the tool set. - `toolCallConcurrency?` (`number`): Maximum number of tool calls that the SDK executes concurrently within a step. Set to \`1\` to execute calls sequentially in model-generated order. Must be a positive integer. By default, all tool calls execute concurrently. - `toolApproval?` (`ToolApprovalConfiguration`): Approval configuration for this call. Pass a \`GenericToolApprovalFunction\` to handle all tool calls in one callback with \`toolCall\`, \`tools\`, \`toolsContext\`, \`messages\`, and \`runtimeContext\`, or pass a per-tool object where each key can be a status (\`'not-applicable'\`, \`'approved'\`, \`'denied'\`, or \`'user-approval'\`), an object form such as \`\{ type: 'denied', reason: 'blocked by policy' }\`, or a \`SingleToolApprovalFunction\` that receives the tool input and options \`toolCallId\`, \`messages\`, \`toolContext\`, and \`runtimeContext\` (same shape as tool execution options without \`abortSignal\`, with \`context\` renamed to \`toolContext\`). The \`RUNTIME\_CONTEXT\` type parameter matches the call's \`runtimeContext\`. A \`GenericToolApprovalFunction\` or \`SingleToolApprovalFunction\` may return \`undefined\` for the same effect as \`'not-applicable'\`. \`'not-applicable'\` is the default execution path and runs the tool without approval metadata. Use \`'approved'\`, \`'denied'\`, or their object forms when you want explicit automatic approval request/response parts in the output. Object statuses can include a \`reason\`: automatic approvals and denials forward it to the approval response, while manual user approvals forward it to the approval request for the human approver. This setting takes precedence over a tool's \`needsApproval\` default. - `experimental_toolCallers?` (`Experimental_ToolCallers`): Configures which caller tools may invoke each tool. Pass an object keyed by callee tool name whose values list caller-capable tool names. Include \`DIRECT\_TOOL\_CALL\` from \`@ai-sdk/code-mode\` to keep a configured tool directly callable by the model. Local-only callees are hidden from direct model calls and bound to their local caller for each generation step. Provider caller names are translated to provider-native allowed-caller options. Tools without an entry keep their existing direct tool-calling behavior. - `experimental_refineToolInput?` (`ToolInputRefinement`): Optional mapping of tool names to functions that refine parsed tool inputs. Each function receives the typed input for its tool and must return the same input type shape. The refined input is used for tool execution, stream parts, lifecycle callbacks, and telemetry. - `stopWhen?` (`StopCondition | Array>`): Condition for stopping the generation when there are tool results in the last step. When the condition is an array, any of the conditions can be met to stop the generation. Default: isStepCount(1). - `prepareStep?` (`(options: PrepareStepOptions) => PrepareStepResult | Promise>`): Optional function that you can use to provide different settings for a step. You can modify the model, model call settings, tool choices, active tools, instructions, input messages, and experimental sandbox for each step. - `PrepareStepFunction` - `options` (`object`): The options for the step. - `PrepareStepOptions` - `steps` (`Array>`): The steps that have been executed so far. - `stepNumber` (`number`): The number of the step that is being executed. - `model` (`LanguageModel`): The model that is being used. - `instructions` (`Instructions | undefined`): The instructions that will be sent to the model for the current step. If prepareStep returns an instructions override, those instructions carry forward to later steps. - `initialInstructions` (`Instructions | undefined`): The initial instructions that were passed into generateText or streamText. - `messages` (`Array`): The messages that will be sent to the model for the current step. If prepareStep returns a messages override, those messages carry forward to later steps. - `runtimeContext?` (`CONTEXT`): The shared runtime context passed via the \`runtimeContext\` setting. - `toolsContext` (`InferToolSetContext`): The per-tool context map passed via the \`toolsContext\` setting. - `experimental_sandbox?` (`Experimental_SandboxSession | undefined`): The experimental sandbox environment passed via the \`experimental\_sandbox\` setting. - `PrepareStepResult` - `model?` (`LanguageModel`): Optionally override which LanguageModel instance is used for this step. - `maxOutputTokens?` (`number`): Maximum number of tokens to generate for this step. Uses the top-level value when omitted or undefined. - `temperature?` (`number`): Temperature for this step. Uses the top-level value when omitted or undefined. - `topP?` (`number`): Nucleus sampling value for this step. Uses the top-level value when omitted or undefined. - `topK?` (`number`): Top-K sampling value for this step. Uses the top-level value when omitted or undefined. - `presencePenalty?` (`number`): Presence penalty for this step. Uses the top-level value when omitted or undefined. - `frequencyPenalty?` (`number`): Frequency penalty for this step. Uses the top-level value when omitted or undefined. - `stopSequences?` (`string[]`): Stop sequences for this step. Uses the top-level value when omitted or undefined. - `seed?` (`number`): Random sampling seed for this step. Uses the top-level value when omitted or undefined. - `reasoning?` (`LanguageModelV4CallOptions["reasoning"]`): Reasoning effort for this step. Uses the top-level value when omitted or undefined. - `toolChoice?` (`ToolChoice`): Optionally set which tool the model must call, or provide tool call configuration for this step. - `activeTools?` (`ActiveTools`): If provided, only these tools are enabled/available for this step. - `toolOrder?` (`ToolOrder`): If provided, overrides the order in which tools are sent to the provider for this step. Tools not listed are appended alphabetically. - `instructions?` (`Instructions`): Optionally override the instructions sent to the model for this step. The override carries forward to later steps until prepareStep returns another instructions or system override. - `messages?` (`Array`): Optionally override the full set of messages sent to the model for this step. - `runtimeContext?` (`CONTEXT`): Shared runtime context. Changing it will affect this step and all subsequent steps. - `toolsContext` (`InferToolSetContext`): Per-tool context map. Changing it will affect tool-specific context in this step and all subsequent steps. - `experimental_sandbox?` (`Experimental_SandboxSession`): Experimental sandbox environment for this step. Changing it will affect tool execution in this step only. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options for this step. Can be used to pass provider-specific configuration such as container IDs for Anthropic code execution. - `runtimeContext?` (`CONTEXT`): User-defined shared runtime context object passed to \`prepareStep\` and lifecycle callbacks. - `toolsContext` (`InferToolSetContext`): Per-tool context map keyed by tool name. Required when at least one tool defines \`contextSchema\`; not accepted when no tools need context. - `experimental_sandbox?` (`Experimental_SandboxSession`): Experimental sandbox environment that is passed through to \`prepareStep\`, tool description functions, and tool execution. Tools can access it from their description function options and execution options. - `experimental_download?` (`(requestedDownloads: Array<{ url: URL; isUrlSupportedByModel: boolean }>) => Promise>`): Custom download function to control how URLs are fetched when they appear in prompts. By default, files are downloaded if the model does not support the URL for the given media type. Experimental feature. Return null to pass the URL directly to the model (when supported), or return downloaded content with data and media type. - `include?` (`{ requestBody?: boolean; requestMessages?: boolean; rawChunks?: boolean }`): Controls inclusion of request body and request messages in step results, and raw provider chunks in the stream. By default, request body, request messages, and raw chunks are excluded to reduce memory usage. Set requestBody or requestMessages to true when you need access to the data. Set rawChunks to true when you need access to raw provider chunks. - `Object` - `requestBody?` (`boolean`): Whether to include the request body in step results. The request body can be large when sending images or files. Default: false. - `requestMessages?` (`boolean`): Whether to include the request messages in step results. The request messages can be large when sending images or files. Default: false. - `rawChunks?` (`boolean`): Whether to include raw chunks from the provider in the stream. When enabled, raw chunks with type "raw" contain unprocessed provider data. Default: false. - `repairToolCall?` (`(options: ToolCallRepairOptions) => Promise`): A function that attempts to repair a tool call that failed to parse. Return either a repaired tool call or null if the tool call cannot be repaired. - `ToolCallRepairOptions` - `instructions` (`Instructions | undefined`): The instructions provided to the model. - `system?` (`Instructions | undefined`): The instructions provided to the model. Deprecated: use \`instructions\` instead. - `messages` (`ModelMessage[]`): The messages in the current generation step. - `toolCall` (`LanguageModelV4ToolCall`): The tool call that failed to parse. - `tools` (`TOOLS`): The tools that are available. - `inputSchema` (`(options: { toolName: string }) => JSONSchema7`): A function that returns the JSON Schema for a tool. - `error` (`NoSuchToolError | InvalidToolInputError`): The error that occurred while parsing the tool call. - `abortSignal?` (`AbortSignal`): An optional signal for cancelling the tool call repair. - `onChunk?` (`(event: OnChunkResult) => Promise |void`): Callback that is called for each stream part. The stream processing will pause until the callback promise is resolved. - `OnChunkResult` - `chunk` (`TextStreamPart`): The stream part. This is the same union as \`stream\` and includes \`start\`, \`start-step\`, \`text-start\`, \`text-delta\`, \`text-end\`, \`reasoning-start\`, \`reasoning-delta\`, \`reasoning-end\`, \`custom\`, \`source\`, \`file\`, \`reasoning-file\`, \`tool-call\`, \`tool-input-start\`, \`tool-input-delta\`, \`tool-input-end\`, \`tool-result\`, \`tool-error\`, \`tool-output-denied\`, \`tool-approval-request\`, \`tool-approval-response\`, \`finish-step\`, \`finish\`, \`abort\`, \`error\`, and \`raw\`. - `TextStreamPart` - `type` (`'text-delta'`): The type to identify the object as text delta. - `text` (`string`): The text delta. - `TextStreamPart` - `type` (`'reasoning-delta'`): The type to identify the object as reasoning delta. - `text` (`string`): The reasoning text delta. - `TextStreamPart` - `type` (`'source'`): The type to identify the object as source. - `source` (`Source`): The source. - `TextStreamPart` - `type` (`'custom'`): The type to identify the object as custom content. - `kind` (`string`): The kind of custom content, in the format \{provider}.\{provider-type} (e.g. openai.compaction). - `providerMetadata?` (`ProviderMetadata`): Optional provider metadata for the custom content. - `TextStreamPart` - `type` (`'tool-call'`): The type to identify the object as tool call. - `toolCallId` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `input` (`object based on zod schema`): Parameters generated by the model to be used by the tool. - `TextStreamPart` - `type` (`'tool-input-start'`): Indicates the start of streamed tool input. - `inputEncoding?` (`'json' | 'text'`): Encoding of subsequent tool-input-delta events. Defaults to json when omitted. Text deltas contain raw string input, such as OpenAI custom-tool code or HTML. - `id` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `TextStreamPart` - `type` (`'tool-input-delta'`): The type to identify the object as streamed tool input delta. - `id` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `delta` (`string`): The text delta of the tool input. - `TextStreamPart` - `type` (`'tool-result'`): The type to identify the object as tool result. - `toolCallId` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `input` (`object based on zod schema`): Parameters generated by the model to be used by the tool. - `output` (`any`): The result returned by the tool after execution has completed. - `onError?` (`StreamTextOnErrorCallback | StreamTextOnErrorRetryCallback`): Callback that is called when an error occurs during streaming. StreamTextOnErrorCallback preserves the existing PromiseLike\ | void observer contract. StreamTextOnErrorRetryCallback additionally types \`\{ retry: true }\` results for retry-capable handlers. Well-formed mid-stream provider error events are exposed as StreamProviderError instances. When streamRetries is explicitly configured, return \`\{ retry: true }\` to request one callback-directed retry for the current model step after automatic retries are exhausted. Other return values do not request a retry. - `OnErrorResult` - `error` (`unknown`): The error that occurred. Use StreamProviderError.isInstance to identify normalized provider error events and inspect their retry metadata. - `output?` (`Output`): Specification for parsing structured outputs from the LLM response. - `Output` - `Output.text()` (`Output`): Output specification for text generation (default). - `Output.object()` (`Output`): Output specification for typed object generation using schemas. When the model generates a text response, it will return an object that matches the schema. - `Options` - `schema` (`Schema`): The schema of the object to generate. - `name?` (`string`): Optional name of the output. Used by some providers for additional LLM guidance. - `description?` (`string`): Optional description of the output. Used by some providers for additional LLM guidance. - `Output.array()` (`Output`): Output specification for array generation. When the model generates a text response, it will return an array of elements. - `Options` - `element` (`Schema`): The schema of the array elements to generate. - `minItems?` (`number`): Optional minimum number of array elements. Must be a non-negative integer. - `maxItems?` (`number`): Optional maximum number of array elements. Must be a non-negative integer and greater than or equal to minItems. - `name?` (`string`): Optional name of the output. Used by some providers for additional LLM guidance. - `description?` (`string`): Optional description of the output. Used by some providers for additional LLM guidance. - `Output.choice()` (`Output`): Output specification for choice generation. When the model generates a text response, it will return a one of the choice options. - `Options` - `options` (`Array`): The available choices. - `name?` (`string`): Optional name of the output. Used by some providers for additional LLM guidance. - `description?` (`string`): Optional description of the output. Used by some providers for additional LLM guidance. - `Output.json()` (`Output`): Output specification for unstructured JSON generation. When the model generates a text response, it will return a JSON object. - `Options` - `name?` (`string`): Optional name of the output. Used by some providers for additional LLM guidance. - `description?` (`string`): Optional description of the output. Used by some providers for additional LLM guidance. - `onStepEnd?` (`(result: StepResult) => Promise | void`): Callback that is called when a step ends. - `StepResult` - `stepType` (`"initial" | "continue" | "tool-result"`): The type of step. The first step is always an "initial" step, and subsequent steps are either "continue" steps or "tool-result" steps. - `finishReason` (`"stop" | "length" | "content-filter" | "tool-calls" | "error" | "interrupted" | "other"`): The unified finish reason why the generation finished. - `rawFinishReason` (`string | undefined`): The raw reason why the generation finished (from the provider). - `usage` (`LanguageModelUsage`): The token usage of the step. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. See also: cached tokens and non-cached tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. - `performance` (`StepResultPerformance`): Timing and throughput metrics for the step. - `StepResultPerformance` - `effectiveOutputTokensPerSecond` (`number`): Effective number of output tokens per second over the full language model response. - `outputTokensPerSecond` (`number | undefined`): Number of output tokens per second after the first generated output chunk was received. Only available for streaming steps. - `inputTokensPerSecond` (`number | undefined`): Number of input tokens processed per second before the first generated output chunk was received. Only available for streaming steps. - `effectiveTotalTokensPerSecond` (`number`): Effective number of input and output tokens per second over the full language model response. - `stepTimeMs` (`number`): Total time spent on the step, including language model response time and tool execution time, in milliseconds. - `responseTimeMs` (`number`): Time spent waiting for the language model response in milliseconds. - `toolExecutionMs` (`Readonly>`): Time spent executing each client-side tool call in the step in milliseconds, keyed by tool call ID. - `timeToFirstOutputMs` (`number | undefined`): Time until the first generated output chunk was received in milliseconds. Only available for streaming steps. - `timeBetweenOutputChunksMs` (`OutputChunkTimingStats | undefined`): Timing statistics for the gaps between generated output chunks in milliseconds. Only available for streaming steps with at least two output chunks. - `text` (`string`): The full text that has been generated. - `reasoningText` (`string | undefined`): The reasoning text of the model (only available for some models). - `sources` (`Array`): Sources that have been used in the final step. - `Source` - `sourceType` (`'url'`): A URL source. This is return by web search RAG models. - `id` (`string`): The ID of the source. - `url` (`string`): The URL of the source. - `title?` (`string`): The title of the source. - `providerMetadata?` (`SharedV2ProviderMetadata`): Additional provider metadata for the source. - `files` (`Array`): All files that were generated in this step. - `GeneratedFile` - `base64` (`string`): File as a base64 encoded string. - `uint8Array` (`Uint8Array`): File as a Uint8Array. - `mediaType` (`string`): The IANA media type of the file. - `toolCalls` (`ToolCall[]`): The tool calls that have been executed. - `toolResults` (`ToolResult[]`): The tool results that have been generated. - `warnings` (`Warning[] | undefined`): Warnings from the model provider (e.g. unsupported settings). - `response?` (`Response`): Response metadata. - `Response` - `id` (`string`): The response identifier. The AI SDK uses the ID from the provider response when available, and generates an ID otherwise. - `modelId` (`string`): The model that was used to generate the response. The AI SDK uses the response model from the provider response when available, and the model from the function call otherwise. - `timestamp` (`Date`): The timestamp of the response. The AI SDK uses the response timestamp from the provider response when available, and creates a timestamp otherwise. - `headers?` (`Record`): Optional response headers. - `isContinued` (`boolean`): True when there will be a continuation step with a continuation text. - `providerMetadata?` (`Record | undefined`): Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. - `onStepFinish?` (`GenerateTextOnStepFinishCallback`): Deprecated. Use \`onStepEnd\` instead. This alias is only used as a fallback when \`onStepEnd\` is not provided. - `onEnd?` (`(result: OnEndResult) => Promise | void`): Callback that is called when the LLM response and all request tool executions (for tools that have an \`execute\` function) are finished. - `OnEndResult` - `finishReason` (`"stop" | "length" | "content-filter" | "tool-calls" | "error" | "interrupted" | "other"`): The unified finish reason why the generation finished. - `rawFinishReason` (`string | undefined`): The raw reason why the generation finished (from the provider). - `output?` (`COMPLETE_OUTPUT | undefined`): The parsed output when an output setting was provided and parsing succeeded. - `usage` (`LanguageModelUsage`): The token usage of last step. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. See also: cached tokens and non-cached tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. - `totalUsage` (`LanguageModelUsage`): The total token usage from all steps. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The number of input (prompt) tokens used. - `outputTokens` (`number | undefined`): The number of output (completion) tokens used. - `totalTokens` (`number | undefined`): The total number of tokens as reported by the provider. This number might be different from the sum of inputTokens and outputTokens and e.g. include reasoning tokens or other overhead. - `providerMetadata` (`Record | undefined`): Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. - `text` (`string`): The full text that has been generated. - `reasoning` (`string | undefined`): The reasoning text of the model (only available for some models). - `reasoning` (`Array`): The reasoning details of the model (only available for some models). - `ReasoningDetail` - `type` (`'text'`): The type of the reasoning detail. - `text` (`string`): The text content (only for type "text"). - `signature?` (`string`): Optional signature (only for type "text"). - `ReasoningDetail` - `type` (`'redacted'`): The type of the reasoning detail. - `data` (`string`): The redacted data content (only for type "redacted"). - `sources` (`Array`): Sources that have been used in the final step. - `Source` - `sourceType` (`'url'`): A URL source. This is return by web search RAG models. - `id` (`string`): The ID of the source. - `url` (`string`): The URL of the source. - `title?` (`string`): The title of the source. - `providerMetadata?` (`SharedV2ProviderMetadata`): Additional provider metadata for the source. - `files` (`Array`): Files that were generated in all steps. - `GeneratedFile` - `base64` (`string`): File as a base64 encoded string. - `uint8Array` (`Uint8Array`): File as a Uint8Array. - `mediaType` (`string`): The IANA media type of the file. - `toolCalls` (`ToolCall[]`): The tool calls that have been executed. - `toolResults` (`ToolResult[]`): The tool results that have been generated. - `warnings` (`Warning[] | undefined`): Warnings from the model provider (e.g. unsupported settings). - `response?` (`Response`): Response metadata. - `Response` - `id` (`string`): The response identifier. The AI SDK uses the ID from the provider response when available, and generates an ID otherwise. - `modelId` (`string`): The model that was used to generate the response. The AI SDK uses the response model from the provider response when available, and the model from the function call otherwise. - `timestamp` (`Date`): The timestamp of the response. The AI SDK uses the response timestamp from the provider response when available, and creates a timestamp otherwise. - `headers?` (`Record`): Optional response headers. - `messages` (`Array`): The response messages generated during this step. It consists of an assistant message, potentially containing tool calls. When there are tool results in this step, there is an additional tool message with the available tool results. If there are tools that do not have execute functions, they are not included in the tool results and need to be added separately. - `steps` (`Array`): Response information for every step. You can use this to get information about intermediate steps, such as the tool calls or the response headers. - `finalStep` (`StepResult`): The final step. This is a shortcut for \`steps.at(-1)\`. - `runtimeContext` (`CONTEXT`): The final state of the user-defined runtime context object when the stream is aborted. - `onFinish?` (`(result: OnEndResult) => Promise | void`): Deprecated alias for \`onEnd\`. - `onAbort?` (`(event: OnAbortResult) => Promise | void`): Callback that is called when a stream is aborted via AbortSignal. You can use it to perform cleanup operations. - `OnAbortResult` - `callId` (`string`): Unique identifier for this generation call, used to correlate events. - `steps` (`Array`): Details for all previously finished steps. - `reason?` (`unknown`): The raw abort reason from the AbortSignal, when one is available. - `onStart?` (`(event: GenerateTextStartEvent) => PromiseLike | void`): Callback that is called when the streamText operation begins, before any LLM calls are made. Errors thrown in this callback are silently caught and do not break the generation flow. - `GenerateTextStartEvent` - `provider` (`string`): The provider identifier (e.g., "openai", "anthropic"). - `modelId` (`string`): The specific model identifier (e.g., "gpt-6-astra"). - `instructions` (`Instructions | undefined`): The instructions provided to the model. - `messages` (`Array`): The messages for this generation. - `tools` (`TOOLS | undefined`): The tools available for this generation. - `toolChoice` (`ToolChoice | undefined`): The tool choice strategy for this generation. - `activeTools` (`ActiveTools`): Limits which tools are available for the model to call. - `toolOrder` (`ToolOrder`): Controls the order in which tools are sent to the provider. - `maxOutputTokens` (`number | undefined`): Maximum number of tokens to generate. - `temperature` (`number | undefined`): Sampling temperature for generation. - `topP` (`number | undefined`): Top-p (nucleus) sampling parameter. - `topK` (`number | undefined`): Top-k sampling parameter. - `presencePenalty` (`number | undefined`): Presence penalty for generation. - `frequencyPenalty` (`number | undefined`): Frequency penalty for generation. - `stopSequences` (`string[] | undefined`): Sequences that will stop generation. - `seed` (`number | undefined`): Random seed for reproducible generation. - `maxRetries` (`number`): Maximum number of retries for failed requests. - `timeout` (``number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number; toolMs?: number; tools?: Partial> } | undefined``): Timeout configuration for the generation. Can be a number (milliseconds) or an object with totalMs, stepMs, firstChunkMs (streaming only), chunkMs, toolMs, and per-tool overrides via tools. - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `providerOptions` (`ProviderOptions | undefined`): Additional provider-specific options. - `output` (`OUTPUT | undefined`): The output specification for structured outputs, if configured. - `abortSignal` (`AbortSignal | undefined`): Abort signal for cancelling the operation. - `include` (`{ requestBody?: boolean; requestMessages?: boolean } | undefined`): Settings for controlling what data is included in step results. - `runtimeContext` (`CONTEXT`): User-defined shared runtime context object that flows through the entire generation lifecycle. - `toolsContext` (`InferToolSetContext`): Per-tool context map passed via \`toolsContext\`, keyed by tool name. - `onStepStart?` (`(event: GenerateTextStepStartEvent) => PromiseLike | void`): Callback that is called when a step begins, before the provider is called. Errors thrown in this callback are silently caught and do not break the generation flow. - `GenerateTextStepStartEvent` - `stepNumber` (`number`): Zero-based index of the current step. - `provider` (`string`): The provider identifier (e.g., "openai", "anthropic"). - `modelId` (`string`): The specific model identifier (e.g., "gpt-6-astra"). - `instructions` (`Instructions | undefined`): The instructions provided to the model for this step. - `messages` (`Array`): The messages that will be sent to the model for this step. Uses the user-facing ModelMessage format. May be overridden by prepareStep. If prepareStep returns a messages override, those messages carry forward to later steps. - `tools` (`TOOLS | undefined`): The tools available for this generation. - `toolChoice` (`LanguageModelV4ToolChoice | undefined`): The tool choice configuration for this step. - `activeTools` (`ActiveTools`): Limits which tools are available for this step. - `toolOrder` (`ToolOrder`): Controls the order in which tools are sent to the provider for this step. - `steps` (`ReadonlyArray>`): Array of results from previous steps (empty for the first step). - `providerOptions` (`ProviderOptions | undefined`): Additional provider-specific options for this step. - `timeout` (`number | { totalMs?: number; stepMs?: number; chunkMs?: number } | undefined`): Timeout configuration for the generation. - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `stopWhen` (`StopCondition | Array> | undefined`): Condition(s) for stopping the generation. - `output` (`OUTPUT | undefined`): The output specification for structured outputs, if configured. - `abortSignal` (`AbortSignal | undefined`): Abort signal for cancelling the operation. - `include` (`{ requestBody?: boolean; requestMessages?: boolean } | undefined`): Settings for controlling what data is included in step results. - `runtimeContext` (`CONTEXT`): User-defined shared runtime context object. May be updated from prepareStep between steps. - `toolsContext` (`InferToolSetContext`): Per-tool context map. May be updated from prepareStep between steps. - `onLanguageModelCallStart?` (`(event: LanguageModelCallStartEvent) => PromiseLike | void`): Callback that is called immediately before the provider model call begins. Unlike \`onStepStart\`, this callback is scoped to model work only and excludes any later client-side tool execution. Errors thrown in this callback are silently caught and do not break the generation flow. - `LanguageModelCallStartEvent` - `callId` (`string`): Unique identifier for the generation call. - `provider` (`string`): The provider identifier for this model call. - `modelId` (`string`): The specific model identifier for this model call. - `instructions` (`Instructions | undefined`): The instructions that will be sent to the model. - `messages` (`Array`): The messages that will be sent to the model. - `tools` (`ReadonlyArray> | undefined`): Prepared tool definitions for the model call, if any. - `onLanguageModelCallEnd?` (`(event: LanguageModelCallEndEvent) => PromiseLike | void`): Callback that is called after the model response has been normalized and parsed, but before any client-side tool execution begins. Errors thrown in this callback are silently caught and do not break the generation flow. - `LanguageModelCallEndEvent` - `callId` (`string`): Unique identifier for the generation call. - `provider` (`string`): The provider identifier for this model call. - `modelId` (`string`): The provider-returned model identifier for this model call. - `finishReason` (`FinishReason`): The unified reason why the model call finished. - `usage` (`LanguageModelUsage`): Token usage reported by the model call. - `content` (`ReadonlyArray>`): The content parts produced by the model call (text, reasoning, files, tool calls, etc.). - `responseId` (`string`): The provider-returned response ID for this model call. - `providerMetadata` (`ProviderMetadata | undefined`): Provider-specific metadata for this model call, when returned by the provider. - `performance` (`{ responseTimeMs: number; effectiveOutputTokensPerSecond: number; outputTokensPerSecond: number | undefined; inputTokensPerSecond: number | undefined; effectiveTotalTokensPerSecond: number; timeToFirstOutputMs: number | undefined; timeBetweenOutputChunksMs?: OutputChunkTimingStats }`): Performance metrics for the model call. - `LanguageModelCallPerformance` - `responseTimeMs` (`number`): Time spent waiting for the language model response in milliseconds. - `effectiveOutputTokensPerSecond` (`number`): Effective number of output tokens per second over the full language model response. - `outputTokensPerSecond` (`number | undefined`): Number of output tokens per second after the first generated output chunk was received. Only available for streaming model calls. - `inputTokensPerSecond` (`number | undefined`): Number of input tokens processed per second before the first generated output chunk was received. Only available for streaming model calls. - `effectiveTotalTokensPerSecond` (`number`): Effective number of input and output tokens per second over the full language model response. - `timeToFirstOutputMs` (`number | undefined`): Time until the first generated output chunk was received in milliseconds. Only available for streaming model calls. - `timeBetweenOutputChunksMs` (`OutputChunkTimingStats | undefined`): Timing statistics for the gaps between generated output chunks in milliseconds. Only available for streaming model calls with at least two output chunks. - `onToolExecutionStart?` (`(event: ToolExecutionStartEvent) => PromiseLike | void`): Callback that is called right before a tool's execute function runs. Errors thrown in this callback are silently caught and do not break the generation flow. - `ToolExecutionStartEvent` - `callId` (`string`): Unique identifier for this generation call, used to correlate events. - `toolCall` (`TypedToolCall`): The full tool call object containing toolName, toolCallId, input, and metadata. - `messages` (`Array`): Messages that were sent to the language model to initiate the response that contained the tool call. Does not include the system prompt nor the assistant response that contained the tool call. - `toolContext` (`InferToolContext`): Tool-specific context object for the tool call that is about to execute. Narrowed to the context type of the individual tool, not the entire tool set. - `onToolExecutionEnd?` (`(event: ToolExecutionEndEvent) => PromiseLike | void`): Callback that is called right after a tool's execute function completes (or errors). The \`toolOutput\` field is a discriminated union: when \`toolOutput.type\` is \`'tool-result'\`, the \`output\` field contains the tool result; when \`toolOutput.type\` is \`'tool-error'\`, the \`error\` field contains the error. Errors thrown in this callback are silently caught and do not break the generation flow. - `ToolExecutionEndEvent` - `callId` (`string`): Unique identifier for this generation call, used to correlate events. - `toolCall` (`TypedToolCall`): The full tool call object containing toolName, toolCallId, input, and metadata. - `toolExecutionMs` (`number`): The wall-clock duration of the tool execution in milliseconds. - `messages` (`Array`): Messages that were sent to the language model to initiate the response that contained the tool call. Does not include the system prompt nor the assistant response that contained the tool call. - `toolContext` (`InferToolContext`): Tool-specific context object for the tool call that just completed. Narrowed to the context type of the individual tool, not the entire tool set. - `toolOutput` (`ToolOutput`): Discriminated union representing the tool execution result. When \`type\` is \`'tool-result'\`, the \`output\` field contains the tool's return value. When \`type\` is \`'tool-error'\`, the \`error\` field contains the error. - `experimental_onToolCallStart?` (`(event: ToolExecutionStartEvent) => PromiseLike | void`): Deprecated. Use \`onToolExecutionStart\` instead. This alias is only used as a fallback when \`onToolExecutionStart\` is not provided. - `experimental_onToolCallFinish?` (`(event: ToolExecutionEndEvent) => PromiseLike | void`): Deprecated. Use \`onToolExecutionEnd\` instead. This alias is only used as a fallback when \`onToolExecutionEnd\` is not provided. ### Returns - `content` (`Promise>>`): The content that was generated in all steps. Text parts can include an optional citations array of references supporting that text. Automatically consumes the stream. - `finishReason` (`PromiseLike<'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'>`): The reason why the generation finished. A stream that ends cleanly without its provider completion signal can report "interrupted". Partial output is preserved; interruption alone does not emit an error. OpenAI-compatible retains its existing error behavior. Automatically consumes the stream. - `rawFinishReason` (`PromiseLike`): The raw reason why the generation finished (from the provider). - `usage` (`Promise`): The total token usage of the generated response. When there are multiple steps, the usage is the sum of all step usages. Automatically consumes the stream. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The number of input (prompt) tokens used. - `outputTokens` (`number | undefined`): The number of output (completion) tokens used. - `totalTokens` (`number | undefined`): The total number of tokens as reported by the provider. This number might be different from the sum of inputTokens and outputTokens and e.g. include reasoning tokens or other overhead. - `totalUsage` (`Promise`): Deprecated. Use usage instead. The total token usage of the generated response. When there are multiple steps, the usage is the sum of all step usages. Automatically consumes the stream. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. See also: cached tokens and non-cached tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. - `providerMetadata` (`Promise`): Deprecated. Use \`finalStep.providerMetadata\` instead. Additional provider-specific metadata from the final step. Metadata is passed through from the provider to the AI SDK and enables provider-specific results that can be fully encapsulated in the provider. - `text` (`Promise`): The full text that has been generated. Automatically consumes the stream. - `reasoning` (`Promise>`): Deprecated. Use \`finalStep.reasoning\` instead. The full reasoning that the model has generated in the final step. Automatically consumes the stream. - `ReasoningOutput` - `type` (`'reasoning'`): The type of the message part. - `text` (`string`): The reasoning text. - `providerMetadata?` (`SharedV2ProviderMetadata`): Additional provider metadata. - `ReasoningFileOutput` - `type` (`'reasoning-file'`): The type of the message part. - `file` (`GeneratedFile`): The generated file. - `providerMetadata?` (`SharedV2ProviderMetadata`): Additional provider metadata. - `reasoningText` (`Promise`): Deprecated. Use \`finalStep.reasoningText\` instead. The reasoning text that the model has generated in the final step. Can be undefined if the model has only generated text. Automatically consumes the stream. - `sources` (`Promise>`): Sources that have been used as input to generate the response. For multi-step generation, the sources are accumulated from all steps. Automatically consumes the stream. - `Source` - `sourceType` (`'url'`): A URL source. This is return by web search RAG models. - `id` (`string`): The ID of the source. - `url` (`string`): The URL of the source. - `title?` (`string`): The title of the source. - `providerMetadata?` (`SharedV2ProviderMetadata`): Additional provider metadata for the source. - `files` (`Promise>`): Files that were generated in all steps. Automatically consumes the stream. - `GeneratedFile` - `base64` (`string`): File as a base64 encoded string. - `uint8Array` (`Uint8Array`): File as a Uint8Array. - `mediaType` (`string`): The IANA media type of the file. - `toolCalls` (`Promise[]>`): The tool calls that have been executed in all steps. Automatically consumes the stream. - `toolResults` (`Promise[]>`): The tool results that have been generated in all steps. Resolved when all tool executions are finished. - `staticToolCalls` (`PromiseLike>>`): The static tool calls that have been executed in all steps. - `dynamicToolCalls` (`PromiseLike>`): The dynamic tool calls that have been executed in all steps. - `staticToolResults` (`PromiseLike>>`): The static tool results that have been generated in all steps. - `dynamicToolResults` (`PromiseLike>`): The dynamic tool results that have been generated in all steps. - `request` (`Promise`): Deprecated. Use \`finalStep.request\` instead. Additional request information from the final step. Automatically consumes the stream. - `LanguageModelRequestMetadata` - `messages?` (`Array`): The input messages that were sent to the model for this step. Undefined by default unless requestMessages is set to true. - `body?` (`unknown`): Request HTTP body that was sent to the provider API. - `response` (`Promise`): Deprecated. Use \`finalStep.response\` instead. Additional response information from the final step. Automatically consumes the stream. - `LanguageModelResponseMetadata` - `id` (`string`): The response identifier. The AI SDK uses the ID from the provider response when available, and generates an ID otherwise. - `modelId` (`string`): The model that was used to generate the response. The AI SDK uses the response model from the provider response when available, and the model from the function call otherwise. - `timestamp` (`Date`): The timestamp of the response. The AI SDK uses the response timestamp from the provider response when available, and creates a timestamp otherwise. - `headers?` (`Record`): Optional response headers. - `body?` (`unknown`): Response body (available only for providers that use HTTP requests). - `messages` (`Array`): The response messages generated during the final step. It consists of an assistant message, potentially containing tool calls. When there are tool results in the final step, there is an additional tool message with the available tool results. If there are tools that do not have execute functions, they are not included in the tool results and need to be added separately. - `warnings` (`Promise`): Warnings from the model provider (e.g. unsupported settings) in all steps. - `responseMessages` (`Promise>`): The accumulated response messages of all steps that were generated during the call. - `steps` (`Promise>`): Response information for every step. You can use this to get information about intermediate steps, such as the tool calls or the response headers. - `StepResult` - `stepType` (`"initial" | "continue" | "tool-result"`): The type of step. The first step is always an "initial" step, and subsequent steps are either "continue" steps or "tool-result" steps. - `text` (`string`): The generated text by the model. - `reasoning` (`string | undefined`): The reasoning text of the model (only available for some models). - `sources` (`Array`): Sources that have been used as input. - `Source` - `sourceType` (`'url'`): A URL source. This is return by web search RAG models. - `id` (`string`): The ID of the source. - `url` (`string`): The URL of the source. - `title?` (`string`): The title of the source. - `providerMetadata?` (`SharedV2ProviderMetadata`): Additional provider metadata for the source. - `files` (`Array`): Files that were generated in this step. - `GeneratedFile` - `base64` (`string`): File as a base64 encoded string. - `uint8Array` (`Uint8Array`): File as a Uint8Array. - `mediaType` (`string`): The IANA media type of the file. - `toolCalls` (`array`): A list of tool calls made by the model. - `toolResults` (`array`): A list of tool results returned as responses to earlier tool calls. - `finishReason` (`'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'`): The reason the model finished generating the text. - `rawFinishReason` (`string | undefined`): The raw reason why the generation finished (from the provider). - `usage` (`LanguageModelUsage`): The token usage of the generated text. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. See also: cached tokens and non-cached tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. - `request?` (`RequestMetadata`): Request metadata. - `RequestMetadata` - `messages?` (`Array`): The input messages that were sent to the model for this step. Undefined by default unless requestMessages is set to true. - `body?` (`unknown`): Request HTTP body that was sent to the provider API. - `response?` (`ResponseMetadata`): Response metadata. - `ResponseMetadata` - `id` (`string`): The response identifier. The AI SDK uses the ID from the provider response when available, and generates an ID otherwise. - `modelId` (`string`): The model that was used to generate the response. The AI SDK uses the response model from the provider response when available, and the model from the function call otherwise. - `timestamp` (`Date`): The timestamp of the response. The AI SDK uses the response timestamp from the provider response when available, and creates a timestamp otherwise. - `headers?` (`Record`): Optional response headers. - `messages` (`Array`): The response messages generated during this step. It consists of an assistant message, potentially containing tool calls. When there are tool results in this step, there is an additional tool message with the available tool results. If there are tools that do not have execute functions, they are not included in the tool results and need to be added separately. - `warnings` (`Warning[] | undefined`): Warnings from the model provider (e.g. unsupported settings). - `isContinued` (`boolean`): True when there will be a continuation step with a continuation text. - `providerMetadata?` (`Record | undefined`): Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. - `textStream` (`AsyncIterableStream`): A text stream that returns only the generated text deltas. You can use it as either an AsyncIterable or a ReadableStream. Error parts are not surfaced in this stream. Use the \`onError\` callback or \`stream\` to observe them. - `finalStep` (`Promise`): The final step. This is a shortcut for \`steps.at(-1)\`. Automatically consumes the stream. - `stream` (`AsyncIterable> & ReadableStream>`): A stream with all events, including text deltas, tool calls, tool results, and errors. Well-formed mid-stream provider failures are emitted in error parts as StreamProviderError instances. You can use it as either an AsyncIterable or a ReadableStream. Only errors that stop the stream, such as network errors, are thrown. - `TextStreamTextEndPart` - `type` (`'text-end'`): The event type. - `id` (`string`): The text block ID. - `citations?` (`Array`): References supporting this text block. Each citation contains a URL or document source, optional startIndex/endIndex offsets in this block, and an optional citedText passage from the source. - `providerMetadata?` (`ProviderMetadata`): Additional provider metadata, including raw annotations. - `TextStreamPart` - `type` (`'text'`): The type to identify the object as text. - `text` (`string`): The text content. - `TextStreamPart` - `type` (`'reasoning'`): The type to identify the object as reasoning. - `text` (`string`): The reasoning text. - `providerMetadata?` (`ProviderMetadata`): Optional provider metadata for the reasoning. - `TextStreamPart` - `type` (`'source'`): The type to identify the object as source. - `sourceType` (`'url'`): A URL source. This is returned by web search RAG models. - `id` (`string`): The ID of the source. - `url` (`string`): The URL of the source. - `title?` (`string`): The title of the source. - `providerMetadata?` (`ProviderMetadata`): Additional provider metadata for the source. - `TextStreamPart` - `type` (`'file'`): The type to identify the object as file. - `file` (`GeneratedFile`): The file. - `GeneratedFile` - `base64` (`string`): File as a base64 encoded string. - `uint8Array` (`Uint8Array`): File as a Uint8Array. - `mediaType` (`string`): The IANA media type of the file. - `TextStreamPart` - `type` (`'custom'`): The type to identify the object as custom content. - `kind` (`string`): The kind of custom content, in the format \{provider}.\{provider-type} (e.g. openai.compaction). - `providerMetadata?` (`ProviderMetadata`): Optional provider metadata for the custom content. - `TextStreamPart` - `type` (`'tool-call'`): The type to identify the object as tool call. - `toolCallId` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `input` (`object based on tool parameters`): Parameters generated by the model to be used by the tool. The type is inferred from the tool definition. - `TextStreamPart` - `type` (`'tool-call-streaming-start'`): Indicates the start of a tool call streaming. Only available when streaming tool calls. - `toolCallId` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `TextStreamPart` - `type` (`'tool-call-delta'`): The type to identify the object as tool call delta. Only available when streaming tool calls. - `toolCallId` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `argsTextDelta` (`string`): The text delta of the tool call arguments. - `TextStreamPart` - `type` (`'tool-result'`): The type to identify the object as tool result. - `toolCallId` (`string`): The id of the tool call. - `toolName` (`string`): The name of the tool, which typically would be the name of the function. - `input` (`object based on tool parameters`): Parameters that were passed to the tool. The type is inferred from the tool definition. - `output` (`tool execution return type`): The result returned by the tool after execution has completed. The type is inferred from the tool execute function return type. - `TextStreamPart` - `type` (`'start-step'`): Indicates the start of a new step in the stream. - `request` (`LanguageModelRequestMetadata`): Information about the request that was sent to the language model provider. - `LanguageModelRequestMetadata` - `messages` (`Array`): The input messages that were sent to the model for this step. - `body?` (`unknown`): Request HTTP body that was sent to the provider API. - `warnings` (`Warning[]`): Warnings from the model provider (e.g. unsupported settings). - `TextStreamPart` - `type` (`'finish-step'`): Indicates the end of the current step in the stream. - `response` (`Omit`): Response metadata from the language model provider. \`messages\` and \`body\` are not included on \`finish-step\` stream parts. - `LanguageModelResponseMetadata` - `id` (`string`): The response identifier. The AI SDK uses the ID from the provider response when available, and generates an ID otherwise. - `modelId` (`string`): The model that was used to generate the response. The AI SDK uses the response model from the provider response when available, and the model from the function call otherwise. - `timestamp` (`Date`): The timestamp of the response. The AI SDK uses the response timestamp from the provider response when available, and creates a timestamp otherwise. - `headers` (`Record`): The response headers. - `usage` (`LanguageModelUsage`): The token usage of the generated text. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. See also: cached tokens and non-cached tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. - `performance` (`StepResultPerformance`): Timing and throughput metrics for the step. - `StepResultPerformance` - `effectiveOutputTokensPerSecond` (`number`): Effective number of output tokens per second over the full language model response. - `outputTokensPerSecond` (`number | undefined`): Number of output tokens per second after the first generated output chunk was received. Only available for streaming steps. - `inputTokensPerSecond` (`number | undefined`): Number of input tokens processed per second before the first generated output chunk was received. Only available for streaming steps. - `effectiveTotalTokensPerSecond` (`number`): Effective number of input and output tokens per second over the full language model response. - `stepTimeMs` (`number`): Total time spent on the step, including language model response time and tool execution time, in milliseconds. - `responseTimeMs` (`number`): Time spent waiting for the language model response in milliseconds. - `toolExecutionMs` (`Readonly>`): Time spent executing each client-side tool call in the step in milliseconds, keyed by tool call ID. - `timeToFirstOutputMs` (`number | undefined`): Time until the first generated output chunk was received in milliseconds. Only available for streaming steps. - `timeBetweenOutputChunksMs` (`OutputChunkTimingStats | undefined`): Timing statistics for the gaps between generated output chunks in milliseconds. Only available for streaming steps with at least two output chunks. - `finishReason` (`'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'`): The reason the model finished generating the text. - `rawFinishReason` (`string | undefined`): The raw reason why the generation finished (from the provider). - `providerMetadata?` (`ProviderMetadata | undefined`): Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. - `TextStreamPart` - `type` (`'start'`): Indicates the start of the stream. - `TextStreamPart` - `type` (`'finish'`): The type to identify the object as finish. - `finishReason` (`'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'`): The reason the model finished generating the text. - `rawFinishReason` (`string | undefined`): The raw reason why the generation finished (from the provider). - `totalUsage` (`LanguageModelUsage`): The total token usage of the generated text. - `LanguageModelUsage` - `inputTokens` (`number | undefined`): The total number of input (prompt) tokens used. - `inputTokenDetails` (`LanguageModelInputTokenDetails`): Detailed information about the input (prompt) tokens. See also: cached tokens and non-cached tokens. - `LanguageModelInputTokenDetails` - `noCacheTokens` (`number | undefined`): The number of non-cached input (prompt) tokens used. - `cacheReadTokens` (`number | undefined`): The number of cached input (prompt) tokens read. - `cacheWriteTokens` (`number | undefined`): The number of cached input (prompt) tokens written. - `outputTokens` (`number | undefined`): The number of total output (completion) tokens used. - `outputTokenDetails` (`LanguageModelOutputTokenDetails`): Detailed information about the output (completion) tokens. - `LanguageModelOutputTokenDetails` - `textTokens` (`number | undefined`): The number of text tokens used. - `reasoningTokens` (`number | undefined`): The number of reasoning tokens used. - `totalTokens` (`number | undefined`): The total number of tokens used. - `raw?` (`object | undefined`): Raw usage information from the provider. This is the provider's original usage information and may include additional fields. - `TextStreamPart` - `type` (`'reasoning-part-finish'`): Indicates the end of a reasoning part. - `TextStreamPart` - `type` (`'error'`): The type to identify the object as error. - `error` (`unknown`): Describes the error that may have occurred during execution. - `TextStreamPart` - `type` (`'abort'`): The type to identify the object as abort. - `reason?` (`string`): Optional serialized abort reason when the stream is aborted. - `fullStream` (`AsyncIterable> & ReadableStream>`): Deprecated. Use \`stream\` instead. A stream with all events, including text deltas, tool calls, tool results, and errors. - `partialOutputStream` (`AsyncIterableStream`): A stream of partial parsed outputs. It uses the \`output\` specification. AsyncIterableStream is defined as AsyncIterable\ & ReadableStream\. - `elementStream` (`AsyncIterableStream`): A stream of individual array elements as they complete. Only available when using \`output: Output.array()\`. Each element is complete and validated against the element schema. When \`maxItems\` is set, the stream errors before emitting the first excess element. AsyncIterableStream is defined as AsyncIterable\ & ReadableStream\. - `output` (`Promise`): The complete parsed output. It uses the \`output\` specification. - `consumeStream` (`(options?: ConsumeStreamOptions) => Promise`): Consumes the stream without processing the parts. This is useful to force the stream to finish. If an error occurs, it is passed to the optional \`onError\` callback. - `ConsumeStreamOptions` - `onError?` (`(error: unknown) => void`): The error callback. - `toUIMessageStream` (`(options?: UIMessageStreamOptions) => AsyncIterableStream`): Converts the result to a UI message stream. Returns an AsyncIterableStream that can be used as both an AsyncIterable and a ReadableStream. The stream includes tool approval request and response chunks, including automatic approval metadata when available. - `UIMessageStreamOptions` - `originalMessages?` (`UIMessage[]`): The original messages. - `onStepEnd?` (`(event: { messages: UIMessage[]; isContinuation: boolean; responseMessage: UIMessage; }) => PromiseLike | void`): Called after each step is converted to UI message parts. Receives the accumulated response message, including step metadata, for inspection or persistence. This UI conversion callback is separate from the generation callback passed to streamText. - `onStepFinish?` (`(event: { messages: UIMessage[]; isContinuation: boolean; responseMessage: UIMessage; }) => PromiseLike | void`): Deprecated alias for onStepEnd. - `onEnd?` (`(options: { messages: UIMessage[]; isContinuation: boolean; responseMessage: UIMessage; isAborted: boolean; isCancelled?: true; outcome: UIMessageStreamOutcome; finishReason?: FinishReason; }) => PromiseLike | void`): Callback function called when the stream ends. Provides the updated messages, continuation, abort and consumer-cancellation state, model finish reason, and operation-level outcome. - `onFinish?` (`(options: { messages: UIMessage[]; isContinuation: boolean; responseMessage: UIMessage; isAborted: boolean; isCancelled?: true; outcome: UIMessageStreamOutcome; finishReason?: FinishReason; }) => PromiseLike | void`): Deprecated alias for \`onEnd\`. - `messageMetadata?` (`(options: { part: TextStreamPart & { type: "start" | "finish" | "start-step" | "finish-step"; }; }) => unknown`): Extracts message metadata that will be sent to the client. Called on start and finish events. - `sendReasoning?` (`boolean`): Send reasoning parts to the client. Defaults to false. - `sendSources?` (`boolean`): Send source parts to the client. Defaults to false. - `sendFinish?` (`boolean`): Send the finish event to the client. Defaults to true. - `sendStart?` (`boolean`): Send the message start event to the client. Set to false if you are using additional streamText calls and the message start event has already been sent. Defaults to true. - `onError?` (`(error: unknown) => string`): Process an error, e.g. to log it. Returns the error message to include in the data stream. Defaults to () => "An error occurred." Tool execution errors marked providerExecuted: true bypass this callback: strings pass through unchanged and other values are JSON-stringified to preserve provider error data for model-message round trips and harness runtime error messages. Stream errors and invalid tool calls still use this callback. - `consumeSseStream?` (`(stream: ReadableStream) => Promise`): Function to consume the SSE stream. Required for proper abort handling in UI message streams. Use the \`consumeStream\` function from the AI SDK. - `pipeUIMessageStreamToResponse` (`(response: ServerResponse, options?: ResponseInit & UIMessageStreamOptions) => Promise`): Writes UI message stream output to a Node.js response-like object. - `ResponseInit & UIMessageStreamOptions` - `status?` (`number`): The response status code. - `statusText?` (`string`): The response status text. - `headers?` (`HeadersInit`): The response headers. - `pipeTextStreamToResponse` (`(response: ServerResponse, init?: ResponseInit) => Promise`): Writes text delta output to a Node.js response-like object. It sets a \`Content-Type\` header to \`text/plain; charset=utf-8\` and writes each text delta as a separate chunk. - `ResponseInit` - `status?` (`number`): The response status code. - `statusText?` (`string`): The response status text. - `headers?` (`Record`): The response headers. - `toUIMessageStreamResponse` (`(options?: ResponseInit & UIMessageStreamOptions) => Response`): Converts the result to a streamed response object with a UI message stream. - `ResponseInit & UIMessageStreamOptions` - `status?` (`number`): The response status code. - `statusText?` (`string`): The response status text. - `headers?` (`HeadersInit`): The response headers. - `toTextStreamResponse` (`(init?: ResponseInit) => Response`): Creates a simple text stream response. Each text delta is encoded as UTF-8 and sent as a separate chunk. Non-text-delta events are ignored. - `ResponseInit` - `status?` (`number`): The response status code. - `statusText?` (`string`): The response status text. - `headers?` (`Record`): The response headers. ## Types ### `ActiveTools` ```ts type ActiveTools = | ReadonlyArray | undefined; ``` Limits a generation step to the listed tool names. `undefined` means no tool restriction is applied. ## Examples - [Learn to stream text generated by a language model in Next.js](/examples/next-app/basics/streaming-text-generation) - [Learn to stream chat completions generated by a language model in Next.js](/examples/next-app/chat/stream-chat-completion) - [Learn to stream text generated by a language model in Node.js](/examples/node/generating-text/stream-text) - [Learn to stream chat completions generated by a language model in Node.js](/examples/node/generating-text/stream-text-with-chat-prompt) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_streamTranscribe description: API Reference for experimental_streamTranscribe. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-transcribe" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `experimental_streamTranscribe` is an experimental feature. Streams a transcript from live raw audio using a transcription model with streaming support. ```ts import { experimental_streamTranscribe as streamTranscribe } from 'ai'; import { openai } from '@ai-sdk/openai'; const result = streamTranscribe({ model: openai.transcription('gpt-realtime-whisper'), audio: audioStream, // ReadableStream inputAudioFormat: { type: 'audio/pcm', rate: 24000 }, }); for await (const part of result.fullStream) { if (part.type === 'transcript-delta') { process.stdout.write(part.delta); } } console.log(await result.text); ``` ## Import ``` import { experimental_streamTranscribe as streamTranscribe } from "ai" ``` ## API Signature ### Parameters - `model` (`TranscriptionModelV4`): The transcription model to use. The model must support streaming (\`doStream\`). String model IDs resolve through the global provider (AI Gateway by default), which supports streaming transcription for supported models (e.g. \`openai/gpt-realtime-whisper\`, \`xai/grok-stt\`): \`experimental\_streamTranscribe(\{ model: 'openai/gpt-realtime-whisper', ... })\`. - `audio` (`ReadableStream`): Raw audio chunks to transcribe. \`Uint8Array\` chunks contain raw audio bytes; \`string\` chunks contain base64-encoded raw audio bytes. - `inputAudioFormat` (`{ type: string; rate?: number }`): The input audio format for the raw audio chunks, e.g. \`\{ type: "audio/pcm", rate: 24000 }\`. Supported types are provider-specific (e.g. \`audio/pcm\`, \`audio/pcmu\`, \`audio/pcma\`). - `providerOptions?` (`Record`): Additional provider-specific options. - `abortSignal?` (`AbortSignal`): An optional abort signal to cancel the call. - `headers?` (`Record`): Additional HTTP/WebSocket headers, if supported by the provider. - `includeRawChunks?` (`boolean`): When true, the provider includes raw provider chunks in the stream as \`raw\` parts. - `telemetry?` (`TelemetryOptions`): Telemetry configuration. Experimental lifecycle callbacks use experimental\_onStreamTranscriptionStart and experimental\_onStreamTranscriptionEnd. The end event is emitted after the audio stream is consumed and includes the final input byte length. ### Returns - `fullStream` (`AsyncIterableStream`): A single-consumer live stream of transcription parts: \`transcript-delta\`, \`transcript-partial\`, \`transcript-final\`, \`raw\`, and \`error\`. Access it once, before any result promise, when both stream parts and final results are needed. Accessing a result promise first consumes the stream internally and makes \`fullStream\` unavailable. - `text` (`Promise`): The complete transcribed text from the audio input. - `segments` (`Promise>`): Final transcript segments with timing information, if available. - `language` (`Promise`): The language of the transcript in ISO-639-1 format, if available. - `durationInSeconds` (`Promise`): The duration of the transcript in seconds, if available. - `warnings` (`Promise`): Warnings for the call, e.g. unsupported settings. Resolves when the provider emits the stream start. - `responses` (`Promise>`): Response metadata (timestamp, model ID, headers). - `providerMetadata` (`Promise>`): Additional provider-specific metadata. The result promises settle as the stream is consumed. If you stop consuming `fullStream` early (e.g. `break` out of the loop), the underlying provider connection is closed and pending result promises reject. ## Wire format (experimental) Streaming transcription over WebSocket is serialized with the experimental transcription-stream envelope defined in `@ai-sdk/provider-utils` (`experimental_parseTranscriptionStreamClientFrame`, `experimental_serializeTranscriptionStreamPart`, `experimental_parseTranscriptionStreamPart`): the client sends one `transcription-stream.start` TEXT frame, audio as BINARY frames, and a `transcription-stream.audio-done` TEXT frame; each server TEXT frame is one JSON-serialized transcription stream part. AI Gateway implements the server side of this envelope. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_streamTranslate description: API Reference for experimental_streamTranslate. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-translate" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `experimental_streamTranslate` is an experimental feature. Streams a speech-to-speech translation from live raw audio. Models translate live source audio into target-language audio and text. `experimental_streamTranslate` is built on the speech translation model specification (`Experimental_SpeechTranslationModelV4`). Provider implementations of the specification ship separately — see your provider's documentation for available translation models. ```ts import { experimental_streamTranslate as streamTranslate } from 'ai'; const result = streamTranslate({ // any provider model instance that implements the experimental // speech translation model specification (Experimental_SpeechTranslationModelV4): model: translationModel, audio: audioStream, // ReadableStream inputAudioFormat: { type: 'audio/pcm', rate: 24000 }, targetLanguage: 'es', }); for await (const part of result.fullStream) { if (part.type === 'output-text-delta') { process.stdout.write(part.delta); } } console.log(await result.translationText); ``` ## Import ``` import { experimental_streamTranslate as streamTranslate } from "ai" ``` ## API Signature ### Parameters - `model` (`Experimental_SpeechTranslationModelV4`): The speech translation model to use. Translation is a streaming-only modality. Pass a provider model instance that implements the experimental speech translation model specification. String model IDs resolve through the global provider (AI Gateway by default) when it supports speech translation models. - `audio` (`ReadableStream`): Raw audio chunks to translate. \`Uint8Array\` chunks contain raw audio bytes; \`string\` chunks contain base64-encoded raw audio bytes. - `inputAudioFormat` (`{ type: string; rate?: number }`): The input audio format for the raw audio chunks, e.g. \`\{ type: "audio/pcm", rate: 24000 }\`. Supported types are provider-specific (e.g. \`audio/pcm\`, \`audio/pcmu\`, \`audio/pcma\`). - `targetLanguage` (`string`): The language to translate the audio into, as a BCP-47-style language tag (e.g. \`en\`, \`es\`, \`fr-CA\`). Supported values are provider-specific and validated by the provider. - `sourceLanguage?` (`string`): The language of the source audio, as a BCP-47-style language tag. When absent, providers auto-detect the source language. - `outputAudioFormat?` (`{ type: string; rate?: number }`): The desired audio format for translated audio chunks. When absent, the provider default output format is used. - `providerOptions?` (`Record`): Additional provider-specific options. - `abortSignal?` (`AbortSignal`): An optional abort signal to cancel the call. - `headers?` (`Record`): Additional HTTP/WebSocket headers, if supported by the provider. - `includeRawChunks?` (`boolean`): When true, the provider includes raw provider chunks in the stream as \`raw\` parts. ### Returns - `fullStream` (`AsyncIterableStream`): A single-consumer live stream of translation parts: \`audio\`, \`output-text-delta\`, \`output-text-final\`, \`source-transcript-delta\`, \`source-transcript-partial\`, \`source-transcript-final\`, \`raw\`, and \`error\`. Access it once, before any result promise, when both stream parts and final results are needed. Accessing a result promise first consumes the stream internally and makes \`fullStream\` unavailable. - `sourceText` (`Promise`): The final source-language transcript of the input audio. - `translationText` (`Promise`): The final translated text in the target language. May resolve to an empty string for providers that produce only audio output. - `durationInSeconds` (`Promise`): The duration of the source audio in seconds, if available. - `usage` (`Promise`): Usage information for the translation call, if reported by the provider: \`inputAudioSeconds\`, \`inputAudioTokens\`, \`outputAudioTokens\`, \`inputTextTokens\`, \`outputTextTokens\` (all optional numbers). - `warnings` (`Promise`): Warnings for the call, e.g. unsupported settings. Resolves at stream start, or with an empty array at finish if no stream-start was emitted. - `response` (`Promise`): Response metadata (timestamp, model ID, headers). - `providerMetadata` (`Promise>`): Additional provider-specific metadata. The result promises settle as the stream is consumed. If you stop consuming `fullStream` early (e.g. `break` out of the loop), the underlying provider connection is closed and pending result promises reject. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: ToolLoopAgent description: API Reference for the ToolLoopAgent class. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/tool-loop-agent" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Creates a reusable AI agent capable of generating text, streaming responses, and using tools over multiple steps (a reasoning-and-acting loop). `ToolLoopAgent` is ideal for building autonomous, multi-step agents that can take actions, call tools, and reason over the results until a stop condition is reached. Unlike single-step calls like `generateText()`, an agent can iteratively invoke tools, collect tool results, and decide next actions until completion or user approval is required. ```ts import { ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a helpful assistant.', tools: { weather: weatherTool, calculator: calculatorTool, }, }); const result = await agent.generate({ prompt: 'What is the weather in NYC?', }); console.log(result.text); ``` For agents, `runtimeContext` is the shared runtime state that flows through the loop. For guidance on `runtimeContext`, `toolsContext`, tool `context`, and sensitive context filtering, see [Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context). Pass `experimental_sandbox` to `generate()` or `stream()` when tools need access to a command or code execution environment. To see `ToolLoopAgent` in action, check out [these examples](#examples). ## Import ``` import { ToolLoopAgent } from "ai" ``` ## Constructor ### Parameters - `model` (`LanguageModel`): The language model instance to use (e.g., from a provider). - `instructions?` (`Instructions`): Instructions for the agent, usually used for system prompt/context. - `allowSystemInMessages?` (`boolean`): Whether \`role: "system"\` messages are allowed in the \`prompt\` or \`messages\` fields. When unset, system messages are rejected because they can create a prompt injection attack risk. Ideally, use the \`instructions\` option instead. Set to \`true\` to allow system messages, or \`false\` to explicitly reject them. - `tools?` (`Record`): A set of tools the agent can call. Keys are tool names. Tools require the underlying model to support tool calling. - `toolChoice?` (`ToolChoice`): Tool call selection strategy. Options: 'auto' | 'none' | 'required' | \{ type: 'tool', toolName: string }. Default: 'auto'. - `stopWhen?` (`StopCondition | StopCondition[]`): Condition(s) for ending the agent loop. Default: isStepCount(20). Use \`isLoopFinished()\` to let the agent run until all tool calls have completed, but beware of potential runaway loops. See https\://ai-sdk.dev/v7/docs/reference/ai-sdk-core/loop-finished#isloopfinished. - `activeTools?` (`ActiveTools`): Limits the tools that are available for the model to call without changing the tool call and result types in the result. All tools are active by default. Tool names are restricted to the string keys of the tool set. - `toolOrder?` (`ToolOrder`): Controls the order in which tools are sent to the provider. The list can be partial. Tools not listed in \`toolOrder\` are sent after the listed tools, sorted alphabetically. Tool names are restricted to the string keys of the tool set. - `toolCallConcurrency?` (`number`): Maximum number of tool calls that the SDK executes concurrently within an agent step. Set to \`1\` to execute calls sequentially in model-generated order. Must be a positive integer. By default, all tool calls execute concurrently. - `toolApproval?` (`ToolApprovalConfiguration`): Approval configuration for the agent. Pass a \`GenericToolApprovalFunction\` to handle all tool calls in one callback with \`toolCall\`, \`tools\`, \`toolsContext\`, \`messages\`, and \`runtimeContext\`, or pass a per-tool object where each key can be a status (\`'not-applicable'\`, \`'approved'\`, \`'denied'\`, or \`'user-approval'\`), an object form such as \`\{ type: 'denied', reason: 'blocked by policy' }\`, or a \`SingleToolApprovalFunction\` that receives the tool input and options \`toolCallId\`, \`messages\`, \`toolContext\`, and \`runtimeContext\` (same shape as tool execution options without \`abortSignal\`, with \`context\` renamed to \`toolContext\`). The \`RUNTIME\_CONTEXT\` type parameter matches the agent's \`runtimeContext\`. A \`GenericToolApprovalFunction\` or \`SingleToolApprovalFunction\` may return \`undefined\` for the same effect as \`'not-applicable'\`. \`'not-applicable'\` is the default execution path and runs the tool without approval metadata. Use \`'approved'\`, \`'denied'\`, or their object forms when you want explicit automatic approval request/response parts in the output. Object statuses can include a \`reason\`: automatic approvals and denials forward it to the approval response, while manual user approvals forward it to the approval request for the human approver. This setting takes precedence over a tool's \`needsApproval\` default. - `experimental_toolCallers?` (`Experimental_ToolCallers`): Configures which caller tools may invoke each tool. Pass an object keyed by callee tool name whose values list caller-capable tool names. Include \`DIRECT\_TOOL\_CALL\` from \`@ai-sdk/code-mode\` to keep a configured tool directly callable by the model. Local-only callees are hidden from direct model calls and bound to their local caller for each agent step. Provider caller names are translated to provider-native allowed-caller options. - `output?` (`Output`): Optional structured output specification, for parsing responses into typesafe data. - `prepareStep?` (`PrepareStepFunction`): Optional function to mutate step settings or inject state for each agent step, including per-step model call settings such as temperature, maxOutputTokens, sampling controls, penalties, stop sequences, seed, and reasoning. Model call setting overrides apply only to the current step. - `include?` (`{ requestBody?: boolean; requestMessages?: boolean; responseBody?: boolean; rawChunks?: boolean }`): Settings for controlling what data is included in step results. requestBody, requestMessages, and responseBody apply to generate(); requestBody, requestMessages, and rawChunks apply to stream(). - `repairToolCall?` (`ToolCallRepairFunction`): Optional callback to attempt automatic recovery when a tool call cannot be parsed. - `experimental_refineToolInput?` (`ToolInputRefinement`): Optional mapping of tool names to functions that refine parsed tool inputs. Each function receives the typed input for its tool and must return the same input type shape. The refined input is used for tool execution, output parts, lifecycle callbacks, and telemetry in both \`generate()\` and \`stream()\`. - `onStart?` (`GenerateTextOnStartCallback`): Callback that is called when the agent operation begins, before any LLM calls are made. Useful for logging, analytics, or initializing state. If also specified in \`generate()\` or \`stream()\`, both callbacks are called (constructor first). - `GenerateTextStartEvent` - `provider` (`string`): The provider identifier (e.g., "openai", "anthropic"). - `modelId` (`string`): The specific model identifier (e.g., "gpt-6-astra"). - `instructions` (`Instructions | undefined`): The instructions provided to the model. - `messages` (`Array`): The messages for this generation. - `tools` (`TOOLS | undefined`): The tools available for this generation. - `toolChoice` (`ToolChoice | undefined`): The tool choice strategy for this generation. - `activeTools` (`ActiveTools`): Limits which tools are available for the model to call. - `toolOrder` (`ToolOrder`): Controls the order in which tools are sent to the provider. - `maxOutputTokens` (`number | undefined`): Maximum number of tokens to generate. - `temperature` (`number | undefined`): Sampling temperature for generation. - `topP` (`number | undefined`): Top-p (nucleus) sampling parameter. - `topK` (`number | undefined`): Top-k sampling parameter. - `presencePenalty` (`number | undefined`): Presence penalty for generation. - `frequencyPenalty` (`number | undefined`): Frequency penalty for generation. - `stopSequences` (`string[] | undefined`): Sequences that will stop generation. - `seed` (`number | undefined`): Random seed for reproducible generation. - `maxRetries` (`number`): Maximum number of retries for failed requests. - `timeout` (`number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number } | undefined`): Timeout configuration for the generation. firstChunkMs and chunkMs are only enforced by stream(). - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `providerOptions` (`ProviderOptions | undefined`): Additional provider-specific options. - `output` (`OUTPUT | undefined`): The output specification for structured outputs, if configured. - `abortSignal` (`AbortSignal | undefined`): Abort signal for cancelling the operation. - `include` (`{ requestBody?: boolean; requestMessages?: boolean; responseBody?: boolean } | undefined`): Settings for controlling what data is included in step results. - `runtimeContext` (`CONTEXT`): User-defined shared runtime context object that flows through the entire generation lifecycle. - `toolsContext` (`InferToolSetContext`): Per-tool context map passed via \`toolsContext\`, keyed by tool name. - `onStepStart?` (`GenerateTextOnStepStartCallback`): Callback that is called when a step begins, before the provider is called. Each step includes a model generation and any associated local tool execution; provider retries remain within the same step. If also specified in \`generate()\` or \`stream()\`, both callbacks are called (constructor first). - `GenerateTextStepStartEvent` - `provider` (`string`): The provider identifier (e.g., "openai", "anthropic"). - `modelId` (`string`): The specific model identifier (e.g., "gpt-6-astra"). - `instructions` (`Instructions | undefined`): The instructions provided to the model for this step. - `messages` (`Array`): The messages that will be sent to the model for this step. - `tools` (`TOOLS | undefined`): The tools available for this generation. - `toolChoice` (`LanguageModelV4ToolChoice | undefined`): The tool choice configuration for this step. - `activeTools` (`ActiveTools`): Limits which tools are available for this step. - `toolOrder` (`ToolOrder`): Controls the order in which tools are sent to the provider for this step. - `steps` (`ReadonlyArray>`): Array of results from previous steps (empty for first step). - `providerOptions` (`ProviderOptions | undefined`): Additional provider-specific options for this step. - `timeout` (`number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number } | undefined`): Timeout configuration for the generation. firstChunkMs and chunkMs are only enforced by stream(). - `headers` (`Record | undefined`): Additional HTTP headers sent with the request. - `stopWhen` (`StopCondition | Array> | undefined`): Condition(s) for stopping the generation. - `output` (`OUTPUT | undefined`): The output specification for structured outputs, if configured. - `abortSignal` (`AbortSignal | undefined`): Abort signal for cancelling the operation. - `include` (`{ requestBody?: boolean; requestMessages?: boolean; responseBody?: boolean } | undefined`): Settings for controlling what data is included in step results. - `runtimeContext` (`CONTEXT`): User-defined shared runtime context object. May be updated from prepareStep between steps. - `toolsContext` (`InferToolSetContext`): Per-tool context map. May be updated from prepareStep between steps. - `onToolExecutionStart?` (`OnToolExecutionStartCallback`): Callback that is called right before a tool's execute function runs. If also specified in \`generate()\` or \`stream()\`, both callbacks are called (constructor first). - `ToolExecutionStartEvent` - `callId` (`string`): Unique identifier for this generation call, used to correlate events. - `toolCall` (`TypedToolCall`): The full tool call object containing toolName, toolCallId, input, and metadata. - `messages` (`Array`): Messages that were sent to the language model to initiate the response that contained the tool call. Does not include the system prompt nor the assistant response that contained the tool call. - `toolContext` (`InferToolContext`): Tool-specific context object for the tool call that is about to execute. Narrowed to the context type of the individual tool, not the entire tool set. - `onToolExecutionEnd?` (`OnToolExecutionEndCallback`): Callback that is called right after a tool's execute function completes (or errors). The \`toolOutput\` field is a discriminated union: when \`toolOutput.type\` is \`'tool-result'\`, the \`output\` field contains the tool result; when \`toolOutput.type\` is \`'tool-error'\`, the \`error\` field contains the error. If also specified in \`generate()\` or \`stream()\`, both callbacks are called (constructor first). - `ToolExecutionEndEvent` - `callId` (`string`): Unique identifier for this generation call, used to correlate events. - `toolCall` (`TypedToolCall`): The full tool call object containing toolName, toolCallId, input, and metadata. - `toolExecutionMs` (`number`): The wall-clock duration of the tool execution in milliseconds. - `messages` (`Array`): Messages that were sent to the language model to initiate the response that contained the tool call. Does not include the system prompt nor the assistant response that contained the tool call. - `toolContext` (`InferToolContext`): Tool-specific context object for the tool call that just completed. Narrowed to the context type of the individual tool, not the entire tool set. - `toolOutput` (`ToolOutput`): Discriminated union representing the tool execution result. When \`type\` is \`'tool-result'\`, the \`output\` field contains the tool's return value. When \`type\` is \`'tool-error'\`, the \`error\` field contains the error. - `onStepEnd?` (`GenerateTextOnStepEndCallback`): Callback invoked after each agent step (LLM/tool call) completes. If also specified in \`generate()\` or \`stream()\`, both callbacks are called (constructor first). - `onStepFinish?` (`GenerateTextOnStepFinishCallback`): Deprecated. Use \`onStepEnd\` instead. This alias is only used as a fallback when \`onStepEnd\` is not provided. - `onEnd?` (`GenerateTextOnEndCallback`): Callback that is called when all agent steps are finished and the response is complete. Receives step results, total usage, shared \`runtimeContext\`, and \`toolsContext\`. If also specified in \`generate()\` or \`stream()\`, both callbacks are called (constructor first). - `onFinish?` (`GenerateTextOnEndCallback`): Deprecated alias for \`onEnd\`. - `runtimeContext?` (`CONTEXT`): User-defined shared runtime context object passed to \`prepareStep\` and lifecycle callbacks. - `toolsContext` (`InferToolSetContext`): Per-tool context map keyed by tool name. Required when at least one tool defines \`contextSchema\`; not accepted when no tools need context. - `telemetry?` (`TelemetryOptions`): Optional telemetry configuration. - `TelemetryOptions` - `includeRuntimeContext?` (`{ [KEY in keyof CONTEXT]?: boolean }`): Top-level runtime context properties that should be included in telemetry. Runtime context properties are excluded unless they are explicitly set to \`true\`. Lifecycle callbacks and returned results still receive the full \`runtimeContext\`. - `includeToolsContext?` (`{ [TOOL_NAME in keyof InferToolSetContext]?: { [KEY in keyof InferToolSetContext[TOOL_NAME]]?: boolean } }`): Top-level tool context properties that should be included in telemetry, configured per tool. Tool context properties are excluded unless they are explicitly set to \`true\`. Lifecycle callbacks and returned results still receive the full \`toolsContext\`. - `experimental_download?` (`DownloadFunction | undefined`): Experimental: Custom download function for fetching files/URLs for tool or model use. By default, files are downloaded if the model does not support the URL for a given media type. - `maxOutputTokens?` (`number`): Maximum number of tokens the model is allowed to generate. - `temperature?` (`number`): Sampling temperature, controls randomness. Passed through to the model. - `topP?` (`number`): Top-p (nucleus) sampling parameter. Passed through to the model. - `topK?` (`number`): Top-k sampling parameter. Passed through to the model. - `presencePenalty?` (`number`): Presence penalty parameter. Passed through to the model. - `frequencyPenalty?` (`number`): Frequency penalty parameter. Passed through to the model. - `stopSequences?` (`string[]`): Custom token sequences which stop the model output. Passed through to the model. - `seed?` (`number`): Seed for deterministic generation (if supported). - `maxRetries?` (`number`): How many times to retry on failure. Default: 2. - `providerOptions?` (`ProviderOptions`): Additional provider-specific configuration. - `headers?` (`Record`): Additional HTTP headers to be sent with the request. Only applicable for HTTP-based providers. - `callOptionsSchema?` (`FlexibleSchema`): Optional schema for custom call options that can be passed when calling generate() or stream(). - `prepareCall?` (`PrepareCallFunction`): Optional function to prepare call-specific settings based on the call options. - `id?` (`string`): Custom agent identifier. ## Properties - `tools` (`Record`): The tool set configured for this agent. Read-only. - `id` (`string | undefined`): The agent identifier, if one was provided in the constructor. ## Methods ### `generate()` Generates a response and triggers tool calls as needed, running the agent loop and returning the final result. Returns a promise resolving to a `GenerateTextResult`. ```ts const result = await agent.generate({ prompt: 'What is the weather like?', }); ``` - `prompt` (`string | Array`): A text prompt or message array. - `messages` (`Array`): A full conversation history as a list of model messages. - `abortSignal?` (`AbortSignal`): An optional abort signal that can be used to cancel the call. - `timeout?` (`number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number }`): Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, firstChunkMs, and/or chunkMs properties. stepMs limits each complete step, including model generation, retries, and local tool execution. firstChunkMs and chunkMs have no effect on generate(); they are streaming-only and are enforced by stream(). Can be used alongside abortSignal. - `experimental_sandbox?` (`Experimental_SandboxSession`): Experimental sandbox environment that is passed through to \`prepareStep\`, tool description functions, and tool execution. Tools can access it from their description function options and execution options. - `options?` (`CALL_OPTIONS`): Custom call options when the agent is configured with a callOptionsSchema. - `onStart?` (`GenerateTextOnStartCallback`): Callback that is called when the agent operation begins, before any LLM calls are made. If also specified in the constructor, both callbacks are called (constructor first). - `onStepStart?` (`GenerateTextOnStepStartCallback`): Callback that is called when a step begins, before the provider is called. If also specified in the constructor, both callbacks are called (constructor first). - `onToolExecutionStart?` (`OnToolExecutionStartCallback`): Callback that is called right before a tool's execute function runs. If also specified in the constructor, both callbacks are called (constructor first). - `onToolExecutionEnd?` (`OnToolExecutionEndCallback`): Callback that is called right after a tool's execute function completes (or errors). If also specified in the constructor, both callbacks are called (constructor first). - `onStepEnd?` (`GenerateTextOnStepEndCallback`): Callback invoked after each agent step (LLM/tool call) completes. If also specified in the constructor, both callbacks are called (constructor first, then this one). - `onStepFinish?` (`GenerateTextOnStepFinishCallback`): Deprecated. Use \`onStepEnd\` instead. This alias is only used as a fallback when \`onStepEnd\` is not provided. - `onEnd?` (`GenerateTextOnEndCallback`): Callback that is called when all agent steps are finished and the response is complete. If also specified in the constructor, both callbacks are called (constructor first, then this one). - `onFinish?` (`GenerateTextOnEndCallback`): Deprecated alias for \`onEnd\`. #### Returns The `generate()` method returns a `GenerateTextResult` object (see [`generateText`](/docs/reference/ai-sdk-core/generate-text#returns) for details). ### `stream()` Streams a response from the agent, including agent reasoning and tool calls, as they occur. Returns a `StreamTextResult`. ```ts const stream = agent.stream({ prompt: 'Tell me a story about a robot.', }); for await (const chunk of stream.textStream) { console.log(chunk); } ``` - `prompt` (`string | Array`): A text prompt or message array. - `messages` (`Array`): A full conversation history as a list of model messages. - `abortSignal?` (`AbortSignal`): An optional abort signal that can be used to cancel the call. - `timeout?` (`number | { totalMs?: number; stepMs?: number; firstChunkMs?: number; chunkMs?: number }`): Timeout in milliseconds. Can be specified as a number or as an object with totalMs, stepMs, firstChunkMs, and/or chunkMs properties. stepMs limits each complete step, including model generation, retries, and local tool execution. firstChunkMs limits the wait for the first content-bearing output in each model-call step. chunkMs starts with the first meaningful model output and limits gaps between subsequent meaningful model output chunks. Both output timers stop when the model response finishes and start fresh for each retry or later model call; they exclude retry backoff and local tool execution. Tool results, including preliminary results, do not reset them. Provider-executed tools remain within the model response. Can be used alongside abortSignal. - `experimental_sandbox?` (`Experimental_SandboxSession`): Experimental sandbox environment that is passed through to \`prepareStep\`, tool description functions, and tool execution. Tools can access it from their description function options and execution options. - `options?` (`CALL_OPTIONS`): Custom call options when the agent is configured with a callOptionsSchema. - `experimental_transform?` (`StreamTextTransform | Array`): Optional stream transformation(s). They are applied in the order provided and must maintain the stream structure. See \`streamText\` docs for details. - `onStart?` (`GenerateTextOnStartCallback`): Callback that is called when the agent operation begins, before any LLM calls are made. If also specified in the constructor, both callbacks are called (constructor first). - `onStepStart?` (`GenerateTextOnStepStartCallback`): Callback that is called when a step begins, before the provider is called. If also specified in the constructor, both callbacks are called (constructor first). - `onToolExecutionStart?` (`OnToolExecutionStartCallback`): Callback that is called right before a tool's execute function runs. If also specified in the constructor, both callbacks are called (constructor first). - `onToolExecutionEnd?` (`OnToolExecutionEndCallback`): Callback that is called right after a tool's execute function completes (or errors). If also specified in the constructor, both callbacks are called (constructor first). - `onStepEnd?` (`GenerateTextOnStepEndCallback`): Callback invoked after each agent step (LLM/tool call) completes. If also specified in the constructor, both callbacks are called (constructor first, then this one). - `onStepFinish?` (`GenerateTextOnStepFinishCallback`): Deprecated. Use \`onStepEnd\` instead. This alias is only used as a fallback when \`onStepEnd\` is not provided. - `onEnd?` (`GenerateTextOnEndCallback`): Callback that is called when all agent steps are finished and the response is complete. If also specified in the constructor, both callbacks are called (constructor first, then this one). - `onFinish?` (`GenerateTextOnEndCallback`): Deprecated alias for \`onEnd\`. #### Returns The `stream()` method returns a `StreamTextResult` object (see [`streamText`](/docs/reference/ai-sdk-core/stream-text#returns) for details). ## Types ### `ActiveTools` ```ts type ActiveTools = | ReadonlyArray | undefined; ``` Limits an agent step to the listed tool names. `undefined` means no tool restriction is applied. ### `InferAgentUIMessage` Infers the UI message type for the given agent instance. Useful for type-safe UI and message exchanges. #### Basic Example ```ts import { ToolLoopAgent, InferAgentUIMessage } from 'ai'; const weatherAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", tools: { weather: weatherTool }, }); type WeatherAgentUIMessage = InferAgentUIMessage; ``` #### Example with Message Metadata You can provide a second type argument to customize the metadata for each message. This is useful for tracking rich metadata returned by the agent (such as createdAt, tokens, finish reason, etc.). ```ts import { ToolLoopAgent, InferAgentUIMessage } from 'ai'; import { z } from 'zod'; // Example schema for message metadata const exampleMetadataSchema = z.object({ createdAt: z.number().optional(), model: z.string().optional(), totalTokens: z.number().optional(), finishReason: z.string().optional(), }); type ExampleMetadata = z.infer; // Define agent as usual const metadataAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", // ...other options }); // Type-safe UI message type with custom metadata type MetadataAgentUIMessage = InferAgentUIMessage< typeof metadataAgent, ExampleMetadata >; ``` ## Examples ### Basic Agent with Tools ```ts import { ToolLoopAgent, isStepCount } from 'ai'; import { weatherTool, calculatorTool } from './tools'; const assistant = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a helpful assistant.', tools: { weather: weatherTool, calculator: calculatorTool, }, stopWhen: isStepCount(3), }); const result = await assistant.generate({ prompt: 'What is the weather in NYC and what is 100 * 25?', }); console.log(result.text); console.log(result.steps); // Array of all steps taken by the agent ``` ### Streaming Agent Response ```ts const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a creative storyteller.', }); const stream = agent.stream({ prompt: 'Tell me a short story about a time traveler.', }); for await (const chunk of stream.textStream) { process.stdout.write(chunk); } ``` ### Agent with Output Parsing ```ts import { z } from 'zod'; const analysisAgent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", output: { schema: z.object({ sentiment: z.enum(['positive', 'negative', 'neutral']), score: z.number(), summary: z.string(), }), }, }); const result = await analysisAgent.generate({ prompt: 'Analyze this review: "The product exceeded my expectations!"', }); console.log(result.output); // Typed as { sentiment: 'positive' | 'negative' | 'neutral', score: number, summary: string } ``` ### Example: Approved Tool Execution ```ts import { ToolLoopAgent, ModelMessage, ToolApprovalResponse, tool } from 'ai'; import { z } from 'zod'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are an agent with access to a weather API.', tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string(), }), execute: async ({ location }) => ({ location, temperature: 72, }), }), }, toolApproval: { weather: 'user-approval', }, }); const messages: ModelMessage[] = [ { role: 'user', content: 'Is it raining in Paris today?' }, ]; const result = await agent.generate({ messages }); const approvals: ToolApprovalResponse[] = []; for (const part of result.content) { if (part.type === 'tool-approval-request') { approvals.push({ type: 'tool-approval-response', approvalId: part.approvalId, approved: true, }); } } messages.push(...result.responseMessages); messages.push({ role: 'tool', content: approvals }); const approvedResult = await agent.generate({ messages }); console.log(approvedResult.text); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: toolSearch description: Search deferred tools and load their definitions on demand for direct calling or code mode. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/tool-search" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Creates a tool that searches the surrounding generation's deferred tools, using keyword matching by default or a custom search callback. Use it with `generateText`, `streamText`, `ToolLoopAgent`, or `WorkflowAgent` from `@ai-sdk/workflow`. `WorkflowAgent` supports direct tool calling. The other APIs also support code mode configured with `toolDiscovery: 'conversation'`. ```ts import { toolSearch } from 'ai'; const search = toolSearch(); ``` See the [Tool Search guide](/docs/ai-sdk-core/tool-search) for direct-calling and code mode examples, including how code mode preserves the tool-definition cache. ## Options Pass an optional configuration object to control the result limit: ```ts const search = toolSearch({ maxResults: 10 }); ``` `maxResults` is the maximum number of matching tools returned and discovered per search. It defaults to `5` and must be a positive safe integer. Invalid values throw `InvalidArgumentError` when creating the search tool. The model-facing description reflects the configured limit. The limit applies to both default keyword search and custom search callbacks, without changing their matching or ranking. Discovered tools accumulate across searches, so this is not a limit on the total number of tools loaded during a generation. Pass a `search` callback to select and rank tools using your application's search logic. The callback can return synchronously or asynchronously: ```ts const search = toolSearch({ search: ({ query, tools }) => { const preferred = /\bgmail\b/i.test(query) ? ['gmail_list_messages', 'gmail_get_message'] : []; return preferred.filter(name => tools.some(tool => tool.name === name)); }, }); ``` The callback has this signature: ```ts search?: (options: { query: string; tools: Array<{ name: string; description?: string }>; }) => string[] | PromiseLike; ``` `tools` contains all eligible deferred tools, before keyword matching or ranking. Eligibility respects `activeTools` and caller routing. Descriptions are resolved using the current tool context and sandbox; schemas are not included. Return tool names in ranked order. The SDK ignores unknown or ineligible names, removes duplicates while preserving order, and keeps the first `maxResults` valid names (five by default). Returned descriptions come from the SDK's candidate catalog. An empty array discovers no tools. Callback errors follow normal tool-execution error handling; the SDK does not fall back to keyword search. Without a callback, `toolSearch()` uses the default keyword search described below. ## Model Input The model supplies the following input to the search tool: ```json { "query": "weather forecast" } ``` `query` is a required, nonempty string. By default, search is local and case-insensitive, matching words in tool names and descriptions. Camel-case names are split into words. Name matches rank above description matches; equal scores preserve registration order. Function descriptions are resolved with the current tool context and sandbox. The default search uses no embedding service or additional model call. A custom callback can use either. ## Output ```ts { tools: [ { name: 'getForecast', description: 'Get the weather forecast for a city.' }, ], } ``` Results contain at most `maxResults` matching tools (five by default), with their names and optional descriptions. They do not include schemas. No matches returns `{ tools: [] }`. Every returned match is queued for discovery. Newly discovered tools can only be called on the next model step, after their definitions have been provided. A parallel call in the same response as the search cannot use a newly discovered tool. For code mode, finish the current execution and wait for the capability update. ## Discovery Lifecycle Before the next model step, the SDK makes discovered tools available through their configured callers: - **Direct calling:** the provider receives the updated tool definitions. - **Code mode:** the SDK appends a user message containing the updated capability catalog. The provider-visible code mode definition stays unchanged. Existing catalogs remain in the conversation; the latest catalog describes the complete currently available tool set. Actual prompt-cache reuse depends on the provider. Code mode search still requires `toolDiscovery: 'conversation'`; description discovery and provider callers are not supported. Discovered tools remain loaded for the rest of the generation, subject to `activeTools`. Search cannot discover tools excluded by `activeTools`. Discovery state is isolated between generation calls, including calls that reuse the same agent or tool instances. Set a multi-step `stopWhen` condition with `generateText` and `streamText` so the model can search, use the discovered tools, and answer. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: tool description: Helper function for tool type inference url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/tool" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Tool is a helper function that infers the tool input and context for its `execute` method and lifecycle callbacks. It does not have any runtime behavior, but it helps TypeScript infer the types for the tool callbacks. Without this helper function, TypeScript is unable to connect the `inputSchema` and `contextSchema` properties to the tool callbacks, and the callback argument types cannot be inferred. For the full model of `runtimeContext`, `toolsContext`, tool `context`, and sensitive context filtering, see [Runtime and Tool Context](/docs/ai-sdk-core/runtime-and-tool-context). The `Tool` type is a union of four tool kinds: - `FunctionTool`: a user-defined function-style tool with known input and output types. - `DynamicTool`: a function-style tool defined at runtime, with `unknown` input and output types. - `ProviderDefinedTool`: a provider-defined tool that your code executes. - `ProviderExecutedTool`: a provider-defined tool that the provider executes. ```ts {1,4,9,10} import { tool } from 'ai'; import { z } from 'zod'; export const weatherTool = tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), // location below is inferred to be a string: execute: async ({ location }) => ({ location, temperature: 72 + Math.floor(Math.random() * 21) - 10, }), }); ``` ## Import ``` import { tool } from "ai" ``` ## API Signature ### Parameters - `tool` (`Tool`): The tool definition. - `Tool` - `description?` (`string | ((options: { context: CONTEXT; experimental_sandbox?: Experimental_SandboxSession }) => string)`): Information about the purpose of the tool including details on how and when it can be used by the model. Provide a string for a fixed description, or a function to derive the description from the tool-specific context and optional experimental sandbox before each model call. - `deferLoading?` (`boolean`): Keep this tool out of the model context until toolSearch discovers it. Supports direct calling or code mode with toolDiscovery: 'conversation'. Discovered tools become available on the next model step. Defaults to false. - `title?` (`string`): Deprecated. Use \`providerMetadata\` for source-specific tool display metadata. - `needsApproval?` (`boolean | ((input: INPUT, options: { toolCallId: string; messages: ModelMessage[]; context: CONTEXT }) => boolean | Promise)`): Deprecated. For \`generateText\`, \`streamText\`, and \`ToolLoopAgent\`, configure approval with \`toolApproval\` instead. Existing \`needsApproval\` usages still work as a compatibility fallback. When used, it can be a boolean or a function that receives the tool input plus execution metadata, including the typed \`context\`. - `inputSchema` (`Zod Schema | JSON Schema`): The schema of the input that the tool expects. The language model will use this to generate the input. It is also used to validate the output of the language model. Use descriptions to make the input understandable for the language model. You can either pass in a Zod schema or a JSON schema (using the \`jsonSchema\` function). - `inputExamples?` (`Array<{ input: INPUT }>`): An optional list of input examples that show the language model what the input should look like. - `contextSchema?` (`Zod Schema | JSON Schema`): An optional schema that describes the tool-specific context expected by the tool. When provided, the \`context\` type in \`execute\`, \`needsApproval\`, and the input lifecycle callbacks is inferred from this schema. The value is supplied via the tool's entry in \`toolsContext\`. - `strict?` (`boolean`): Strict mode setting for the tool. Providers that support strict mode will use this setting to determine how the input should be generated. Strict mode will always produce valid inputs, but it might limit what input schemas are supported. - `execute?` (`async (input: INPUT, options: ToolExecutionOptions) => RESULT | Promise | AsyncIterable`): An async function that is called with the arguments from the tool call and produces a result or a results iterable. If an iterable is provided, all results but the last one are considered preliminary. If not provided, the tool will not be executed automatically. - `ToolExecutionOptions` - `toolCallId` (`string`): The ID of the tool call. You can use it e.g. when sending tool-call related information with stream data. - `messages` (`ModelMessage[]`): Messages that were sent to the language model to initiate the response that contained the tool call. The messages do not include the system prompt nor the assistant response that contained the tool call. - `abortSignal?` (`AbortSignal`): An optional abort signal that indicates that the overall operation should be aborted. - `context` (`CONTEXT`): Tool-specific context passed into tool execution. When \`contextSchema\` is provided, this type is inferred from that schema and supplied from the matching entry in \`toolsContext\`. - `experimental_sandbox?` (`Experimental_SandboxSession`): Optional experimental sandbox environment passed from the generation or agent call. Use it when a tool needs to execute commands or code in an experimental sandbox. - `outputSchema?` (`Zod Schema | JSON Schema`): The schema of the output that the tool produces. Used for type inference. - `toModelOutput?` (`({toolCallId: string; input: INPUT; output: OUTPUT}) => ToolResultOutput | PromiseLike`): Optional conversion function that maps the tool result to an output that can be used by the language model. If not provided, the tool result will be sent as a JSON object. This function is invoked on the server by "convertToModelMessages", so ensure that you pass the same "tools" (ToolSet) to both "convertToModelMessages" and "streamText" (or other generation APIs). - `onInputStart?` (`(options: ToolExecutionOptions) => void | PromiseLike`): Optional function that is called when the model starts generating the tool input. In non-streaming contexts, it is called immediately before onInputAvailable. - `onInputDelta?` (`(options: { inputTextDelta: string } & ToolExecutionOptions) => void | PromiseLike`): Optional function that is called when an argument streaming delta is available. Only called when the tool is used in a streaming context. - `onInputAvailable?` (`(options: { input: INPUT } & ToolExecutionOptions) => void | PromiseLike`): Optional function that is called when a tool call can be started, even if the execute function is not provided. - `providerOptions?` (`ProviderOptions`): Additional provider-specific metadata. They are passed through to the provider from the AI SDK and enable provider-specific functionality that can be fully encapsulated in the provider. - `metadata?` (`JSONObject`): Optional metadata about the tool itself (e.g. its source). It is propagated onto the resulting tool call's toolMetadata so consumers can read it from tool call/result parts and UI message parts. Useful for sources of dynamic tools (e.g. an MCP server) to identify themselves. - `type?` (`'function' | 'dynamic' | 'provider'`): The type of the tool. Defaults to "function" for regular tools. Use "dynamic" for tools defined at runtime and "provider" for provider-specific tools. - `id?` (`` `${string}.${string}` ``): The ID of a provider tool. Must follow the format \`\.\\`. Required when type is "provider". - `isProviderExecuted?` (`boolean`): Whether a provider tool is executed by the provider. Set to \`false\` for \`ProviderDefinedTool\` and \`true\` for \`ProviderExecutedTool\`. Required when type is "provider". - `args?` (`Record`): The arguments for configuring a provider tool. Must match the expected arguments defined by the provider for this tool. Required when type is "provider". - `supportsDeferredResults?` (`boolean`): Whether a provider-executed tool supports results that arrive without a matching tool call in the current response. Only available for \`ProviderExecutedTool\`. ### Returns The tool that was passed in. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: transcribe description: API Reference for transcribe. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/transcribe" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Generates a transcript from an audio file. ```ts import { transcribe } from 'ai'; import { openai } from '@ai-sdk/openai'; import { readFile } from 'fs/promises'; const { text: transcript } = await transcribe({ model: openai.transcription('gpt-4o-transcribe'), audio: await readFile('audio.mp3'), }); console.log(transcript); ``` ## Import ``` import { transcribe } from "ai" ``` ## API Signature ### Parameters - `model` (`TranscriptionModelV4`): The transcription model to use. - `audio` (`DataContent (string | Uint8Array | ArrayBuffer | Buffer) | URL`): The audio file to generate the transcript from. - `providerOptions?` (`Record`): Additional provider-specific options. - `maxRetries?` (`number`): Maximum number of retries. Default: 2. - `abortSignal?` (`AbortSignal`): An optional abort signal to cancel the call. - `headers?` (`Record`): Additional HTTP headers for the request. - `telemetry?` (`TelemetryOptions`): Telemetry configuration. Supports functionId, recordInputs, recordOutputs, isEnabled, and per-call integrations. - `download?` (`(options: { url: URL; abortSignal?: AbortSignal }) => Promise<{ data: Uint8Array; mediaType: string | undefined }>`): Custom download function for fetching audio from URLs. Use \`createDownload()\` from \`ai\` to create a download function with custom size limits, e.g. \`createDownload(\{ maxBytes: 50 \* 1024 \* 1024 })\`. Default: built-in download with 2 GiB limit. ### Returns - `text` (`string`): The complete transcribed text from the audio input. - `segments` (`Array<{ text: string; startSecond: number; endSecond: number }>`): An array of transcript segments, each containing a portion of the transcribed text along with its start and end times in seconds. - `language` (`string | undefined`): The language of the transcript in ISO-639-1 format e.g. "en" for English. - `durationInSeconds` (`number | undefined`): The duration of the transcript in seconds. - `warnings` (`Warning[]`): Warnings from the model provider (e.g. unsupported settings). - `providerMetadata?` (`Record`): Optional metadata from the provider. The outer key is the provider name. The inner values are the metadata. Details depend on the provider. - `responses` (`Array`): Response metadata from the provider. There may be multiple responses if we made multiple calls to the model. - `TranscriptionModelResponseMetadata` - `timestamp` (`Date`): Timestamp for the start of the generated response. - `modelId` (`string`): The ID of the response model that was used to generate the response. - `headers?` (`Record`): Response headers. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: UIMessage description: API Reference for UIMessage url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/ui-message" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `UIMessage` serves as the source of truth for your application's state, representing the complete message history including metadata, data parts, and all contextual information. In contrast to `ModelMessage`, which represents the state or context passed to the model, `UIMessage` contains the full application state needed for UI rendering and client-side functionality. ## Type Safety `UIMessage` is designed to be type-safe and accepts three generic parameters to ensure proper typing throughout your application: 1. **`METADATA`** - Custom metadata type for additional message information 2. **`DATA_PARTS`** - Custom data part types for structured data components 3. **`TOOLS`** - Tool definitions for type-safe tool interactions ## Creating Your Own UIMessage Type Here's an example of how to create a custom typed UIMessage for your application: ```typescript import { InferUITools, ToolSet, UIMessage, tool } from 'ai'; import z from 'zod'; const metadataSchema = z.object({ someMetadata: z.string().datetime(), }); type MyMetadata = z.infer; const dataPartSchema = z.object({ someDataPart: z.object({}), anotherDataPart: z.object({}), }); type MyDataPart = z.infer; const tools = { someTool: tool({}), } satisfies ToolSet; type MyTools = InferUITools; export type MyUIMessage = UIMessage; ``` ## `UIMessage` Interface ```typescript interface UIMessage< METADATA = unknown, DATA_PARTS extends UIDataTypes = UIDataTypes, TOOLS extends UITools = UITools, > { /** * A unique identifier for the message. */ id: string; /** * The role of the message. */ role: 'system' | 'user' | 'assistant'; /** * The metadata of the message. */ metadata?: METADATA; /** * The parts of the message. Use this for rendering the message in the UI. */ parts: Array>; } ``` ## `UIMessagePart` Types ### `TextUIPart` A text part of a message. ```typescript type TextUIPart = { type: 'text'; /** * The text content. */ text: string; /** * The state of the text part. */ state?: 'streaming' | 'done'; }; ``` ### `ReasoningUIPart` A reasoning part of a message. ```typescript type ReasoningUIPart = { type: 'reasoning'; /** * The reasoning part ID. */ id?: string; /** * The reasoning text. */ text: string; /** * The state of the reasoning part. */ state?: 'streaming' | 'done'; /** * The provider metadata. */ providerMetadata?: Record; }; ``` ### `ToolUIPart` A tool part of a message that represents tool invocations and their results. The type is based on the name of the tool (e.g., `tool-someTool` for a tool named `someTool`). ```typescript type ToolUIPart = ValueOf<{ [NAME in keyof TOOLS & string]: { type: `tool-${NAME}`; toolCallId: string; } & ( | { state: 'input-streaming'; input: DeepPartial | undefined; providerExecuted?: boolean; output?: never; errorText?: never; } | { state: 'input-available'; input: TOOLS[NAME]['input']; providerExecuted?: boolean; output?: never; errorText?: never; } | { state: 'approval-requested'; input: TOOLS[NAME]['input']; output?: never; errorText?: never; approval: { id: string; approved?: never; descriptor?: unknown; requestReason?: string; reason?: never; isAutomatic?: boolean; signature?: string; }; } | { state: 'approval-responded'; input: TOOLS[NAME]['input']; output?: never; errorText?: never; approval: { id: string; approved: boolean; descriptor?: unknown; requestReason?: string; reason?: string; isAutomatic?: boolean; signature?: string; }; } | { state: 'output-available'; input: TOOLS[NAME]['input']; output: TOOLS[NAME]['output']; errorText?: never; providerExecuted?: boolean; } | { state: 'output-error'; input: TOOLS[NAME]['input']; output?: never; errorText: string; providerExecuted?: boolean; } ); }>; ``` `approval.descriptor` contains optional opaque metadata supplied as `approvalDescriptor` on the approval request stream chunk. It is preserved when the tool part transitions from `approval-requested` to `approval-responded` and in later approval-bearing output states. ### `ToolOutputErrorUIPart` A static or dynamic tool part whose execution failed. Use the `isToolOutputErrorUIPart` type guard when rendering messages so your code does not need to check the tool state discriminator directly. ```tsx import { isToolOutputErrorUIPart, type UIMessage } from 'ai'; function ToolError({ part }: { part: UIMessage['parts'][number] }) { if (!isToolOutputErrorUIPart(part)) { return null; } return
{part.errorText}
; } ``` The generic `ToolOutputErrorUIPart` type preserves the input types of static tools and also includes dynamic tool errors: ```typescript type ToolOutputErrorUIPart = Extract< ToolUIPart | DynamicToolUIPart, { state: 'output-error' } >; ``` ### `CustomContentUIPart` A provider-specific custom content part of a message. ```typescript type CustomContentUIPart = { type: 'custom'; /** * The kind of custom content, in the format `{provider}.{provider-type}`. */ kind: `${string}.${string}`; /** * The provider metadata. */ providerMetadata?: Record; }; ``` ### `SourceUrlUIPart` A source URL part of a message. ```typescript type SourceUrlUIPart = { type: 'source-url'; sourceId: string; url: string; title?: string; providerMetadata?: Record; }; ``` ### `SourceDocumentUIPart` A document source part of a message. ```typescript type SourceDocumentUIPart = { type: 'source-document'; sourceId: string; mediaType: string; title: string; filename?: string; providerMetadata?: Record; }; ``` ### `FileUIPart` A file part of a message. ```typescript type FileUIPart = { type: 'file'; /** * IANA media type of the file. */ mediaType: string; /** * Optional filename of the file. */ filename?: string; /** * The URL of the file. * It can either be a URL to a hosted file or a Data URL. */ url: string; }; ``` ### `DataUIPart` A data part of a message for custom data types. The type is based on the name of the data part (e.g., `data-someDataPart` for a data part named `someDataPart`). ```typescript type DataUIPart = ValueOf<{ [NAME in keyof DATA_TYPES & string]: { type: `data-${NAME}`; id?: string; data: DATA_TYPES[NAME]; }; }>; ``` ### `StepStartUIPart` A step boundary part of a message. ```typescript type StepStartUIPart = { type: 'step-start'; }; ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: uploadFile description: API Reference for uploadFile. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/upload-file" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Uploads a file to a provider and returns a result whose `providerReference` (a `ProviderReference`) can be used in subsequent API calls, such as in message content parts passed to `generateText` or `streamText`. ```ts import { uploadFile } from 'ai'; import { openai } from '@ai-sdk/openai'; import fs from 'node:fs'; const { providerReference } = await uploadFile({ api: openai.files(), data: fs.readFileSync('./photo.png'), filename: 'photo.png', }); ``` ## Import ``` import { uploadFile } from "ai" ``` ## API Signature ### Parameters - `api` (`FilesV4 | ProviderV4`): The files API interface to use for uploading. Can be a \`FilesV4\` instance (e.g. \`openai.files()\`) or a provider instance directly (e.g. \`openai\`), in which case \`.files()\` is called automatically. - `data` (`DataContent | { type: "stream"; stream: ReadableStream }`): The file data to upload. Can be a \`Uint8Array\`, a base64-encoded string, an \`ArrayBuffer\`, a \`Buffer\`, or a tagged \`\{ type: "stream", stream }\` shape for providers that support streaming uploads (sent without buffering; other providers reject with an \`UnsupportedFunctionalityError\`). The provider consumes the stream — any failed upload (including validation failures before a request is made) cancels it, and it must not be reused. URLs are not supported — fetch the content first and pass the bytes. - `mediaType?` (`string`): IANA media type of the file (e.g. \`image/png\`, \`application/pdf\`). Auto-detected from the file bytes if not provided; stream data cannot be sniffed and defaults to \`application/octet-stream\`. - `filename?` (`string`): Filename for the uploaded file. Multipart-based providers default it to \`"blob"\` when omitted. - `abortSignal?` (`AbortSignal`): Signal to cancel the upload. - `headers?` (`Record`): Additional HTTP headers to send with the request. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options. For example, OpenAI accepts a \`purpose\` field, which defaults to \`"assistants"\`. ### Returns - `providerReference` (`ProviderReference`): A \`Record\\` mapping provider names to provider-specific file identifiers. Pass this as the \`data\` or \`image\` field in message content parts. - `byteSize?` (`number`): Size of the uploaded file in bytes, if reported by the provider. - `createdAt?` (`Date`): When the file was created, if reported by the provider. - `expiresAt?` (`Date`): When the provider will delete the file (retention expiry, e.g. from a requested upload TTL), if reported by the provider. - `providerMetadata?` (`ProviderMetadata`): Additional provider-specific metadata returned from the upload. - `warnings` (`Warning[]`): Warnings from the provider (e.g. unsupported settings). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: uploadSkill description: API Reference for uploadSkill. url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/upload-skill" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Uploads a skill (a bundle of files) to a provider and returns a result whose `providerReference` (a `ProviderReference`) can be used in subsequent inference calls. ```ts import { uploadSkill } from 'ai'; import { anthropic } from '@ai-sdk/anthropic'; import { readFileSync } from 'fs'; const { providerReference } = await uploadSkill({ api: anthropic.skills(), files: [ { path: 'my-skill/SKILL.md', data: readFileSync('./SKILL.md'), }, ], displayTitle: 'My Skill', }); ``` ## Import ``` import { uploadSkill } from "ai" ``` ## API Signature ### Parameters - `api` (`SkillsV4 | ProviderV4`): The skills API interface to use for uploading. Can be a \`SkillsV4\` instance (e.g. \`anthropic.skills()\`) or a provider instance directly (e.g. \`anthropic\`), in which case \`.skills()\` is called automatically. - `files` (`UploadSkillFile[]`): The files that make up the skill. Each file must have a relative \`path\` and \`data\`. The \`data\` can be a string, a \`Uint8Array\`, or an object with \`type: 'text'\` and a string \`text\` value, or \`type: 'data'\` and a string \`data\` value. - `displayTitle?` (`string`): Human-readable title for the skill. - `providerOptions?` (`ProviderOptions`): Additional provider-specific options. ### Returns - `providerReference` (`ProviderReference`): A \`Record\\` mapping provider names to provider-specific skill identifiers. Pass this when referencing the skill in inference calls. - `displayTitle?` (`string`): Human-readable title returned by the provider (if supported). - `name?` (`string`): Skill name inferred by the provider from the uploaded files. - `description?` (`string`): Skill description inferred by the provider from the uploaded files. - `latestVersion?` (`string`): Latest version identifier assigned by the provider. - `providerMetadata?` (`ProviderMetadata`): Additional provider-specific metadata returned from the upload. - `warnings` (`Warning[]`): Warnings from the provider (e.g. unsupported settings). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: valibotSchema description: Helper function for creating Valibot schemas url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/valibot-schema" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `valibotSchema` is a helper function that converts a Valibot schema into a JSON schema object that is compatible with the AI SDK. It takes a Valibot schema as input, and returns a typed schema. You can use it to [generate structured data](/docs/ai-sdk-core/generating-structured-data) and in [tools](/docs/ai-sdk-core/tools-and-tool-calling). ## Example ```ts import { valibotSchema } from '@ai-sdk/valibot'; import { object, string, array } from 'valibot'; const recipeSchema = valibotSchema( object({ name: string(), ingredients: array( object({ name: string(), amount: string(), }), ), steps: array(string()), }), ); ``` ## Import ``` import { valibotSchema } from "@ai-sdk/valibot" ``` ## API Signature ### Parameters - `valibotSchema` (`GenericSchema`): The Valibot schema definition. ### Returns A Schema object that is compatible with the AI SDK, containing both the JSON schema representation and validation functionality. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: validateUIMessages description: API Reference for validateUIMessages url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/validate-ui-messages" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `validateUIMessages` is an async function that validates UI messages against schemas for metadata, data parts, and tools. It ensures type safety and data integrity for your message arrays before processing or rendering. ## Basic Usage Simple validation without custom schemas: ```typescript import { validateUIMessages } from 'ai'; const messages = [ { id: '1', role: 'user', parts: [{ type: 'text', text: 'Hello!' }], }, ]; const validatedMessages = await validateUIMessages({ messages, }); ``` ## Advanced Usage Comprehensive validation with custom metadata, data parts, and tools: ```typescript import { validateUIMessages, tool } from 'ai'; import { z } from 'zod'; // Define schemas const metadataSchema = z.object({ timestamp: z.string().datetime(), userId: z.string(), }); const dataSchemas = { chart: z.object({ data: z.array(z.number()), labels: z.array(z.string()), }), image: z.object({ url: z.string().url(), caption: z.string(), }), }; const tools = { weather: tool({ description: 'Get weather info', inputSchema: z.object({ location: z.string(), }), execute: async ({ location }) => `Weather in ${location}: sunny`, }), }; // Messages with custom parts const messages = [ { id: '1', role: 'user', metadata: { timestamp: '2024-01-01T00:00:00Z', userId: 'user123' }, parts: [ { type: 'text', text: 'Show me a chart' }, { type: 'data-chart', data: { data: [1, 2, 3], labels: ['A', 'B', 'C'] }, }, ], }, { id: '2', role: 'assistant', parts: [ { type: 'tool-weather', toolCallId: 'call_123', state: 'output-available', input: { location: 'San Francisco' }, output: 'Weather in San Francisco: sunny', }, ], }, ]; // Validate with all schemas const validatedMessages = await validateUIMessages({ messages, metadataSchema, dataSchemas, tools, }); ``` When validating approval messages produced with `experimental_refineToolInput`, pass the same refinement functions so `validateUIMessages` can reconstruct and verify the approved input: ```typescript const experimental_refineToolInput = { weather: (input: { location: string }) => ({ location: input.location.trim(), }), }; const validatedMessages = await validateUIMessages({ messages, tools, experimental_refineToolInput, }); ``` ## Deprecated `rawInput` field For backward compatibility, validation still accepts `rawInput` on tool parts in the `output-error` state. When a defined `rawInput` value is found, `validateUIMessages` emits an AI SDK deprecation warning through `AI_SDK_LOG_WARNINGS`. Migrate persisted messages to store tool arguments in `input` and remove `rawInput`. For backward compatibility, conversion uses `rawInput` as a fallback when `input` is `null` or `undefined`. `rawInput` will be removed in the next major version. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: wrapImageModel description: Function for wrapping an image model with middleware (API Reference) url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/wrap-image-model" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `wrapImageModel` function provides a way to enhance the behavior of image models by wrapping them with middleware. ```ts import { generateImage, wrapImageModel } from 'ai'; import { openai } from '@ai-sdk/openai'; const model = wrapImageModel({ model: openai.image('gpt-image-2.5-sunburst'), middleware: yourImageModelMiddleware, }); const { image } = await generateImage({ model, prompt: 'Santa Claus driving a Cadillac', }); ``` ## Import ``` import { wrapImageModel } from "ai" ``` ## API Signature ### Parameters - `model` (`ImageModelV4`): The original ImageModelV4 instance to be wrapped. - `middleware` (`ImageModelV4Middleware | ImageModelV4Middleware[]`): The middleware to be applied to the image model. When multiple middlewares are provided, the first middleware will transform the input first, and the last middleware will be wrapped directly around the model. - `modelId` (`string`): Optional custom model ID to override the original model's ID. - `providerId` (`string`): Optional custom provider ID to override the original model's provider. ### Returns A new `ImageModelV4` instance with middleware applied. The wrapped model preserves the original model's `supportsFileInputs` and `supportsMaskInputs` capabilities by default. Middleware can override them with `overrideSupportsFileInputs` and `overrideSupportsMaskInputs`. Returning `undefined` from an override intentionally marks that capability as unknown. ```ts const model = wrapImageModel({ model: originalModel, middleware: { specificationVersion: 'v4', overrideSupportsFileInputs: () => true, overrideSupportsMaskInputs: () => false, }, }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: wrapLanguageModel description: Function for wrapping a language model with middleware (API Reference) url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/wrap-language-model" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `wrapLanguageModel` function provides a way to enhance the behavior of language models by wrapping them with middleware. See [Language Model Middleware](/docs/ai-sdk-core/middleware) for more information on middleware. ```ts import { wrapLanguageModel, gateway } from 'ai'; const wrappedLanguageModel = wrapLanguageModel({ model: gateway('openai/gpt-6-astra'), middleware: yourLanguageModelMiddleware, }); ``` ## Import ``` import { wrapLanguageModel } from "ai" ``` ## API Signature ### Parameters - `model` (`LanguageModelV4`): The original LanguageModelV4 instance to be wrapped. - `middleware` (`LanguageModelV4Middleware | LanguageModelV4Middleware[]`): The middleware to be applied to the language model. When multiple middlewares are provided, the first middleware will transform the input first, and the last middleware will be wrapped directly around the model. - `modelId` (`string`): Optional custom model ID to override the original model's ID. - `providerId` (`string`): Optional custom provider ID to override the original model's provider. ### Returns A new `LanguageModelV4` instance with middleware applied. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: zodSchema description: Helper function for creating Zod schemas url: "https://ai-sdk.dev/docs/reference/ai-sdk-core/zod-schema" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `zodSchema` is a helper function that converts a Zod schema into a JSON schema object that is compatible with the AI SDK. It takes a Zod schema and optional configuration as inputs, and returns a typed schema. You can use it to [generate structured data](/docs/ai-sdk-core/generating-structured-data) and in [tools](/docs/ai-sdk-core/tools-and-tool-calling). You can also pass Zod objects directly to the AI SDK functions. Internally, the AI SDK will convert the Zod schema to a JSON schema using `zodSchema()`. However, if you want to specify options such as `useReferences`, you can pass the `zodSchema()` helper function instead. When using `.meta()` or `.describe()` to add metadata to your Zod schemas, make sure these methods are called **at the end** of the schema chain. metadata is attached to a specific schema instance, and most schema methods (`.min()`, `.optional()`, `.extend()`, etc.) return a new schema instance that does not inherit metadata from the previous one. Due to Zod's immutability, metadata is only included in the JSON schema output if `.meta()` or `.describe()` is the last method in the chain. ```ts // ❌ Metadata will be lost - .min() returns a new instance without metadata z.string().meta({ describe: 'first name' }).min(1); // ✅ Metadata is preserved - .meta() is the final method z.string().min(1).meta({ describe: 'first name' }); ``` ## Example with recursive schemas ```ts import { zodSchema } from 'ai'; import { z } from 'zod'; // Define a base category schema const baseCategorySchema = z.object({ name: z.string(), }); // Define the recursive Category type type Category = z.infer & { subcategories: Category[]; }; // Create the recursive schema using z.lazy const categorySchema: z.ZodType = baseCategorySchema.extend({ subcategories: z.lazy(() => categorySchema.array()), }); // Create the final schema with useReferences enabled for recursive support const mySchema = zodSchema( z.object({ category: categorySchema, }), { useReferences: true }, ); ``` ## Import ``` import { zodSchema } from "ai" ``` ## API Signature ### Parameters - `zodSchema` (`z.Schema`): The Zod schema definition. - `options` (`object`): Additional options for the schema conversion. - `object` - `useReferences?` (`boolean`): Enables support for references in the schema. This is required for recursive schemas, e.g. with \`z.lazy\`. However, not all language models and providers support such references. Defaults to \`false\`. ### Returns A Schema object that is compatible with the AI SDK, containing both the JSON schema representation and validation functionality. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: convertToModelMessages description: Convert useChat messages to ModelMessages for AI functions (API Reference) url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/convert-to-model-messages" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `convertToModelMessages` function is used to transform an array of UI messages from the `useChat` hook into an array of `ModelMessage` objects. These `ModelMessage` objects are compatible with AI core functions like `streamText`. ```ts title="app/api/chat/route.ts" import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, } from 'ai'; export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ## Import ``` import { convertToModelMessages } from "ai" ``` ## API Signature ### Parameters - `messages` (`Message[]`): An array of UI messages from the useChat hook to be converted - `options` (`{ tools?: ToolSet, ignoreIncompleteToolCalls?: boolean, convertDataPart?: (part: DataUIPart) => TextPart | FilePart | undefined }`): Optional configuration object. Provide tools to enable multi-modal tool responses. Set ignoreIncompleteToolCalls to true to skip tool calls without results (default: false). Use convertDataPart to transform custom data parts into model-compatible content. ### Returns A Promise that resolves to an array of [`ModelMessage`](/docs/reference/ai-sdk-core/model-message) objects. - `Promise` (`Promise`): A Promise that resolves to an array of ModelMessage objects ## Deprecated `rawInput` field Tool parts in the `output-error` state should store their tool arguments in `input`. The legacy `rawInput` field remains supported for persisted messages, but `convertToModelMessages` emits an AI SDK deprecation warning when it encounters a defined value. When both fields are present, `input` takes precedence when it is non-nullish. For backward compatibility, `rawInput` remains the fallback when `input` is `null` or `undefined`. Migrate stored messages to `input` before the next major version, when `rawInput` will be removed. ## Tool Approval States `convertToModelMessages` preserves tool approval state from UI messages when converting them back into `ModelMessage`s for a follow-up `generateText` or `streamText` call. - Tool parts with approval metadata produce `tool-approval-request` content parts. `approval.requestReason` is forwarded as the request `reason` when present. - Tool parts in `approval-responded` state also become `tool-approval-response` content parts. The separate response `reason` is forwarded when present. - Automatic approval metadata is preserved by forwarding `approval.isAutomatic` to the `tool-approval-request` part. - Denied tool approvals also produce a synthetic `tool-result` with `output: { type: 'execution-denied', reason?: string }`, so the model receives a complete tool lifecycle and can respond to the denial in the next step. ## Multi-modal Tool Responses The `convertToModelMessages` function supports tools that can return multi-modal content. This is useful when tools need to return non-text content like images. ```ts import { tool } from 'ai'; import { z } from 'zod'; const screenshotTool = tool({ inputSchema: z.object({}), execute: async () => 'imgbase64', toModelOutput: ({ output }) => [ { type: 'file-data', data: output, mediaType: 'image/png' }, ], }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages, { tools: { screenshot: screenshotTool, }, }), }); ``` Tools can implement the optional `toModelOutput` method to transform their results into multi-modal content. The content is an array of content parts, where each part has a `type` (e.g., 'text', 'image') and corresponding data. ## Custom Data Part Conversion The `convertToModelMessages` function supports converting custom data parts attached to user messages. This is useful when users need to include additional context (URLs, code files, JSON configs) with their messages. ### Basic Usage By default, data parts in user messages are filtered out during conversion. To include them, provide a `convertDataPart` callback that transforms data parts into text or file parts that the model can understand: ```ts title="app/api/chat/route.ts" import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, type UIMessage, } from 'ai'; type CustomUIMessage = UIMessage< never, { url: { url: string; title: string; content: string }; 'code-file': { filename: string; code: string; language: string }; } >; export async function POST(req: Request) { const { messages } = await req.json(); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages, { convertDataPart: part => { // Convert URL attachments to text if (part.type === 'data-url') { return { type: 'text', text: `[Reference: ${part.data.title}](${part.data.url})\n\n${part.data.content}`, }; } // Convert code file attachments if (part.type === 'data-code-file') { return { type: 'text', text: `\`\`\`${part.data.language}\n// ${part.data.filename}\n${part.data.code}\n\`\`\``, }; } // Other data parts are ignored }, }), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ### Use Cases **Attaching URL Content** Allow users to attach URLs to their messages, with the content fetched and formatted for the model: ```ts // Client side sendMessage({ parts: [ { type: 'text', text: 'Analyze this article' }, { type: 'data-url', data: { url: 'https://example.com/article', title: 'Important Article', content: '...', }, }, ], }); ``` **Including Code Files as Context** Let users reference code files in their conversations: ```ts convertDataPart: part => { if (part.type === 'data-code-file') { return { type: 'text', text: `\`\`\`${part.data.language}\n${part.data.code}\n\`\`\``, }; } }; ``` **Selective Inclusion** Only data parts for which you return a text or file model message part are included, all other data parts are ignored. ```ts const result = await convertToModelMessages< UIMessage< unknown, { url: { url: string; title: string }; code: { code: string; language: string }; note: { text: string }; } > >(messages, { convertDataPart: part => { if (part.type === 'data-url') { return { type: 'text', text: `[${part.data.title}](${part.data.url})`, }; } // data-code and data-note are ignored }, }); ``` ### Type Safety The generic parameter ensures full type safety for your custom data parts: ```ts type MyUIMessage = UIMessage< unknown, { url: { url: string; content: string }; config: { key: string; value: string }; } >; // TypeScript knows the exact shape of part.data convertToModelMessages(messages, { convertDataPart: part => { if (part.type === 'data-url') { // part.data is typed as { url: string; content: string } return { type: 'text', text: part.data.url }; } // Return undefined to skip this part }, }); ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: createUIMessageStreamResponse description: API Reference for createUIMessageStreamResponse. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/create-ui-message-stream-response" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `createUIMessageStreamResponse` function creates a Response object that streams UI messages to the client. ## Import ``` import { createUIMessageStreamResponse } from "ai" ``` ## Example ```tsx import { createUIMessageStream, createUIMessageStreamResponse, streamText, toUIMessageStream, } from 'ai'; const response = createUIMessageStreamResponse({ status: 200, statusText: 'OK', headers: { 'Custom-Header': 'value', }, stream: createUIMessageStream({ execute({ writer }) { // The outer stream owns the assistant message lifecycle. writer.write({ type: 'start' }); // Write custom data (type must be 'data-') writer.write({ type: 'data-message', data: { content: 'Hello' }, }); // Write text content using start/delta/end pattern writer.write({ type: 'text-start', id: 'greeting-text', }); writer.write({ type: 'text-delta', id: 'greeting-text', delta: 'Hello, world!', }); writer.write({ type: 'text-end', id: 'greeting-text', }); // Write source information (flat properties, not nested) writer.write({ type: 'source-url', sourceId: 'source-1', url: 'https://example.com', title: 'Example Source', }); // Merge with LLM stream const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Say hello', }); writer.merge( toUIMessageStream({ stream: result.stream, sendStart: false }), ); }, }), }); ``` ## API Signature ### Parameters - `stream` (`ReadableStream`): The UI message stream to send to the client. - `status?` (`number`): The status code for the response. Defaults to 200. - `statusText?` (`string`): The status text for the response. - `headers?` (`Headers | Record`): Additional headers for the response. - `keepAliveMs?` (`number`): Interval in milliseconds for sending SSE keep-alive comments. When set, an opening comment is sent immediately and additional comments are sent after the stream has been idle for the configured interval. - `consumeSseStream?` (`(options: { stream: ReadableStream }) => PromiseLike | void`): Optional callback to consume the Server-Sent Events stream. ### Returns `Response` A Response object that streams UI message chunks with the specified status, headers, and content. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: createUIMessageStream description: API Reference for createUIMessageStream. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/create-ui-message-stream" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `createUIMessageStream` function allows you to create a readable stream for UI messages with advanced features like message merging, error handling, and finish callbacks. ## Import ``` import { createUIMessageStream } from "ai" ``` ## Example ```tsx const existingMessages: UIMessage[] = [ /* ... */ ]; const stream = createUIMessageStream({ async execute({ writer }) { // The outer stream owns the assistant message lifecycle. writer.write({ type: 'start' }); // Start a text message // Note: The id must be consistent across text-start, text-delta, and text-end steps // This allows the system to correctly identify they belong to the same text block writer.write({ type: 'text-start', id: 'example-text', }); // Write a message chunk writer.write({ type: 'text-delta', id: 'example-text', delta: 'Hello', }); // End the text message writer.write({ type: 'text-end', id: 'example-text', }); // Merge another stream from streamText const result = streamText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a haiku about AI', }); writer.merge( toUIMessageStream({ stream: result.stream, sendStart: false, onEnd: ({ outcome }) => { // The composer decides that the model stream outcome is also the // aggregate stream outcome. writer.setOutcome(outcome); }, }), ); }, onError: error => `Custom error: ${error instanceof Error ? error.message : String(error)}`, originalMessages: existingMessages, onEnd: ({ messages, isContinuation, outcome, responseMessage }) => { console.log('Stream ended with messages:', messages); console.log('Stream outcome:', outcome.status); }, }); ``` `setOutcome` records the composer's policy without writing a chunk or closing the stream. The first outcome declared through `setOutcome` is retained, but a fatal execution, merge, error-handling, or downstream processing failure makes the final `onEnd` outcome `failed`. Individual `error` chunks do not change the outcome by themselves. When merging multiple child streams, aggregate their outcomes and call `setOutcome` once. ## API Signature ### Parameters - `execute` (`(options: { writer: UIMessageStreamWriterWithOutcome }) => Promise | void`): A function that receives a writer instance and can use it to write UI message chunks to the stream. - `UIMessageStreamWriterWithOutcome` - `write` (`(part: UIMessageChunk) => void`): Writes a UI message chunk to the stream. - `merge` (`(stream: ReadableStream) => void`): Merges the contents of another UI message stream into this stream. - `setOutcome` (`(outcome: UIMessageStreamOutcome) => void`): Declares the operation-level outcome of the composed stream. The first outcome declared through this method is retained, while fatal execution, merge, error-handling, or downstream processing failures override declarations. Supported statuses are 'completed', 'failed', 'aborted', and 'unknown'. Declaring an outcome does not write a chunk or close the stream. - `onError` (`(error: unknown) => string`): Error handler that is used by the stream writer for handling errors in merged streams. - `onError` (`(error: unknown) => string`): A function that handles errors and returns an error message string. By default, it returns \`"An error occurred."\` so server-side error details are not sent to the client. - `originalMessages` (`UIMessage[] | undefined`): The original messages. If provided, persistence mode is assumed and a message ID is provided for the response message. - `onEnd` (`(options: { messages: UIMessage[]; isContinuation: boolean; isAborted: boolean; isCancelled?: true; outcome: UIMessageStreamOutcome; responseMessage: UIMessage; finishReason?: FinishReason }) => PromiseLike | void`): A callback function that is called when the stream ends. - `EndOptions` - `messages` (`UIMessage[]`): The updated list of UI messages. - `isContinuation` (`boolean`): Indicates whether the response message is a continuation of the last original message, or if a new message was created. - `isAborted` (`boolean`): Indicates whether the stream was aborted. - `isCancelled` (`true | undefined`): Present and true when the consumer cancelled the stream before an outcome was declared, for example because the client disconnected. - `outcome` (`UIMessageStreamOutcome = { status: 'completed' } | { status: 'failed'; error?: unknown } | { status: 'aborted' } | { status: 'unknown' }`): The operation-level outcome of the stream. It reflects the stream owner declaration unless a fatal stream-processing failure occurs, and is separate from model finish reasons and individual error chunks. It remains 'unknown' when the consumer cancels before an outcome is declared; check isCancelled to distinguish that case from normal closure without a declared outcome. - `responseMessage` (`UIMessage`): The message that was sent to the client as a response (including the original message if it was extended). - `finishReason` (`FinishReason | undefined`): The reason why the generation finished. One of: 'stop', 'length', 'content-filter', 'tool-calls', 'error', 'interrupted', or 'other'. - `onFinish` (`(options: { messages: UIMessage[]; isContinuation: boolean; isAborted: boolean; isCancelled?: true; outcome: UIMessageStreamOutcome; responseMessage: UIMessage; finishReason?: FinishReason }) => PromiseLike | void`): Deprecated alias for \`onEnd\`. - `generateId` (`IdGenerator | undefined`): A function to generate unique IDs for messages. Uses the default ID generator if not provided. ### Returns `ReadableStream` A readable stream that emits UI message chunks. The stream automatically handles error propagation, merging of multiple streams, and proper cleanup when all operations are complete. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: DirectChatTransport description: API Reference for the DirectChatTransport class. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/direct-chat-transport" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). A transport that directly communicates with an [Agent](/docs/reference/ai-sdk-core/agent) in-process, without going through HTTP. This is useful for: - Server-side rendering scenarios - Testing without network - Single-process applications Unlike `DefaultChatTransport` which sends HTTP requests to an API endpoint, `DirectChatTransport` invokes the agent's `stream()` method directly and converts the result to a UI message stream. ```tsx import { useChat } from '@ai-sdk/react'; import { DirectChatTransport, ToolLoopAgent } from 'ai'; const agent = new ToolLoopAgent({ model: "anthropic/claude-sonnet-5.5", instructions: 'You are a helpful assistant.', }); export default function Chat() { const { messages, sendMessage, status } = useChat({ transport: new DirectChatTransport({ agent }), }); // ... render chat UI } ``` ## Import ``` import { DirectChatTransport } from "ai" ``` ## Constructor ### Parameters - `agent` (`Agent`): The Agent instance to use for generating responses. The agent will be called with \`stream()\` for each message. - `options?` (`CALL_OPTIONS`): Options to pass to the agent when calling it. These are agent-specific options defined when creating the agent. - `originalMessages?` (`UIMessage[]`): The original messages. If provided, persistence mode is assumed, and a message ID is provided for the response message (generated with \`generateMessageId\`, or reused from the last message when it is an assistant message). - `generateMessageId?` (`IdGenerator`): Generate a message ID for the response message. If not provided, no message ID will be set for the response message (unless the original messages are provided and the last message is an assistant message). - `messageMetadata?` (`(options: { part: TextStreamPart }) => METADATA | undefined`): Extracts message metadata that will be sent to the client. Called on \`start\` and \`finish\` events. - `onStepEnd?` (`UIMessageStreamOnStepEndCallback`): Called after each step is converted to UI message parts. Receives messages, isContinuation, and the accumulated responseMessage, including step metadata, for inspection or persistence. - `onStepFinish?` (`UIMessageStreamOnStepFinishCallback`): Deprecated alias for onStepEnd. - `sendReasoning?` (`boolean`): Send reasoning parts to the client. Defaults to true. - `sendSources?` (`boolean`): Send source parts to the client. Defaults to false. - `sendFinish?` (`boolean`): Send the finish event to the client. Set to false if you are using additional streamText calls that send additional data. Defaults to true. - `sendStart?` (`boolean`): Send the message start event to the client. Set to false if you are using additional streamText calls and the message start event has already been sent. Defaults to true. - `onError?` (`(error: unknown) => string`): Process an error, e.g. to log it. Defaults to \`() => 'An error occurred.'\`. Return the error message to include in the data stream. ## Methods ### `sendMessages()` Sends messages to the agent and returns a streaming response. This method validates and converts UI messages to model messages, calls the agent's `stream()` method, and returns the result as a UI message stream. ```ts const stream = await transport.sendMessages({ chatId: 'chat-123', trigger: 'submit-message', messages: [...], abortSignal: controller.signal, }); ``` - `chatId` (`string`): Unique identifier for the chat session. - `trigger` (`'submit-message' | 'regenerate-message'`): The type of message submission - either new message or regeneration. - `messageId` (`string | undefined`): ID of the message to regenerate, or undefined for new messages. - `messages` (`UIMessage[]`): Array of UI messages representing the conversation history. - `abortSignal` (`AbortSignal | undefined`): Signal to abort the request if needed. - `headers?` (`Record | Headers`): Additional headers (ignored by DirectChatTransport). - `body?` (`object`): Additional body properties (ignored by DirectChatTransport). - `metadata?` (`unknown`): Custom metadata (ignored by DirectChatTransport). #### Returns Returns a `Promise>` - a stream of UI message chunks that can be processed by the chat UI. ### `reconnectToStream()` Direct transport does not support reconnection since there is no persistent server-side stream to reconnect to. #### Returns Always returns `Promise`. ## Examples ### Basic Usage ```tsx import { useChat } from '@ai-sdk/react'; import { DirectChatTransport, ToolLoopAgent } from 'ai'; import { openai } from '@ai-sdk/openai'; const agent = new ToolLoopAgent({ model: openai('gpt-6-astra'), instructions: 'You are a helpful assistant.', }); export default function Chat() { const { messages, sendMessage, status } = useChat({ transport: new DirectChatTransport({ agent }), }); return (
{messages.map(message => (
{message.role === 'user' ? 'User: ' : 'AI: '} {message.parts.map((part, index) => part.type === 'text' ? {part.text} : null, )}
))}
); } ``` ### With Agent Tools ```tsx import { useChat } from '@ai-sdk/react'; import { DirectChatTransport, ToolLoopAgent, tool } from 'ai'; import { openai } from '@ai-sdk/openai'; import { z } from 'zod'; const weatherTool = tool({ description: 'Get the current weather', inputSchema: z.object({ location: z.string().describe('The city and state'), }), execute: async ({ location }) => { return `The weather in ${location} is sunny and 72°F.`; }, }); const agent = new ToolLoopAgent({ model: openai('gpt-6-astra'), instructions: 'You are a helpful assistant with access to weather data.', tools: { weather: weatherTool }, }); export default function Chat() { const { messages, sendMessage } = useChat({ transport: new DirectChatTransport({ agent }), }); // ... render chat UI with tool results } ``` ### With Custom Agent Options ```tsx import { useChat } from '@ai-sdk/react'; import { DirectChatTransport, ToolLoopAgent } from 'ai'; import { openai } from '@ai-sdk/openai'; const agent = new ToolLoopAgent<{ userId: string }>({ model: openai('gpt-6-astra'), prepareCall: ({ options, ...rest }) => ({ ...rest, providerOptions: { openai: { user: options.userId }, }, }), }); export default function Chat({ userId }: { userId: string }) { const { messages, sendMessage } = useChat({ transport: new DirectChatTransport({ agent, options: { userId }, }), }); // ... render chat UI } ``` ### With Reasoning ```tsx import { useChat } from '@ai-sdk/react'; import { DirectChatTransport, ToolLoopAgent } from 'ai'; import { openai } from '@ai-sdk/openai'; const agent = new ToolLoopAgent({ model: openai('o1-preview'), }); export default function Chat() { const { messages, sendMessage } = useChat({ transport: new DirectChatTransport({ agent, sendReasoning: true, }), }); return (
{messages.map(message => (
{message.parts.map((part, index) => { if (part.type === 'text') { return

{part.text}

; } if (part.type === 'reasoning') { return (
                  {part.text}
                
); } return null; })}
))}
); } ``` --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI SDK UI description: Reference documentation for the AI SDK UI url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [AI SDK UI](/docs/ai-sdk-ui) is designed to help you build interactive chat, completion, and assistant applications with ease. It is a framework-agnostic toolkit, streamlining the integration of advanced AI functionalities into your applications. AI SDK UI contains the following hooks: #### [useChat](/docs/reference/ai-sdk-ui/use-chat) Use a hook to interact with language models in a chat interface. #### [useCompletion](/docs/reference/ai-sdk-ui/use-completion) Use a hook to interact with language models in a completion interface. #### [useObject](/docs/reference/ai-sdk-ui/use-object) Use a hook for consuming a streamed JSON objects. #### [experimental\_useRealtime](/docs/reference/ai-sdk-ui/use-realtime) Use a hook to build realtime voice conversations. #### [convertToModelMessages](/docs/reference/ai-sdk-ui/convert-to-model-messages) Convert useChat messages to ModelMessages for AI functions. #### [pruneMessages](/docs/reference/ai-sdk-ui/prune-messages) Prunes model messages from a list of model messages. #### [createUIMessageStream](/docs/reference/ai-sdk-ui/create-ui-message-stream) Create a UI message stream to stream additional data to the client. #### [createUIMessageStreamResponse](/docs/reference/ai-sdk-ui/create-ui-message-stream-response) Create a response object to stream UI messages to the client. #### [pipeUIMessageStreamToResponse](/docs/reference/ai-sdk-ui/pipe-ui-message-stream-to-response) Pipe a UI message stream to a Node.js ServerResponse object. #### [readUIMessageStream](/docs/reference/ai-sdk-ui/read-ui-message-stream) Transform a stream of UIMessageChunk objects into an AsyncIterableStream of UIMessage objects. #### [WebSocketChatTransport](/docs/reference/ai-sdk-ui/websocket-chat-transport) Send correlated chat requests and UI message streams over a persistent WebSocket connection. #### [experimental\_MCPAppRenderer](/docs/reference/ai-sdk-ui/mcp-app-renderer) Render interactive MCP App tool UIs in React. ## UI Framework Support AI SDK UI supports the following frameworks: [React](https://react.dev/), [Svelte](https://svelte.dev/), [Vue.js](https://vuejs.org/), [Angular](https://angular.dev/), and [SolidJS](https://www.solidjs.com/). Here is a comparison of the supported functions across these frameworks: | | [useChat](/docs/reference/ai-sdk-ui/use-chat) | [useCompletion](/docs/reference/ai-sdk-ui/use-completion) | [useObject](/docs/reference/ai-sdk-ui/use-object) | [MCP Apps](/docs/ai-sdk-core/mcp-apps) | | --------------------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------- | -------------------------------------- | | React `@ai-sdk/react` | ✓ | ✓ | ✓ | ✓ | | Vue.js `@ai-sdk/vue` | ✓ Chat | ✓ | ✓ | ✗ | | Svelte `@ai-sdk/svelte` | ✓ Chat | ✓ Completion | ✓ StructuredObject | ✗ | | Angular `@ai-sdk/angular` | ✓ Chat | ✓ Completion | ✓ StructuredObject | ✗ | | [SolidJS](https://github.com/kodehort/ai-sdk-solid) (community) | ✓ | ✓ | ✓ | ✗ | [Contributions](https://github.com/vercel/ai/blob/main/CONTRIBUTING.md) are welcome to implement missing features for non-React frameworks. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: InferUITool description: API Reference for InferUITool. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/infer-ui-tool" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Infers the input and output types of a tool. This type helper is useful when working with individual tools to ensure type safety for your tool inputs and outputs in `UIMessage`s. ## Import ```tsx import { InferUITool } from 'ai'; ``` ## API Signature ### Type Parameters - `TOOL` (`Tool`): The tool to infer types from. ### Returns A type that contains the inferred input and output types of the tool. The resulting type has the shape: ```typescript { input: InferToolInput; output: InferToolOutput; } ``` ## Examples ### Basic Usage ```tsx import { InferUITool } from 'ai'; import { z } from 'zod'; const weatherTool = { description: 'Get the current weather', inputSchema: z.object({ location: z.string().describe('The city and state'), }), execute: async ({ location }) => { return `The weather in ${location} is sunny.`; }, }; // Infer the types from the tool type WeatherUITool = InferUITool; // This creates a type with: // { // input: { location: string }; // output: string; // } ``` ## Related - [`InferUITools`](/docs/reference/ai-sdk-ui/infer-ui-tools) - Infer types for a tool set - [`ToolUIPart`](/docs/reference/ai-sdk-core/ui-message#tooluipart) - Tool part type for UI messages --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: InferUITools description: API Reference for InferUITools. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/infer-ui-tools" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Infers the input and output types of a `ToolSet`. This type helper is useful when working with tools in TypeScript to ensure type safety for your tool inputs and outputs in `UIMessage`s. ## Import ```tsx import { InferUITools } from 'ai'; ``` ## API Signature ### Type Parameters - `TOOLS` (`ToolSet`): The tool set to infer types from. ### Returns A type that maps each tool in the tool set to its inferred input and output types. The resulting type has the shape: ```typescript { [NAME in keyof TOOLS & string]: { input: InferToolInput; output: InferToolOutput; }; } ``` ## Examples ### Basic Usage ```tsx import { InferUITools } from 'ai'; import { z } from 'zod'; const tools = { weather: { description: 'Get the current weather', inputSchema: z.object({ location: z.string().describe('The city and state'), }), execute: async ({ location }) => { return `The weather in ${location} is sunny.`; }, }, calculator: { description: 'Perform basic arithmetic', inputSchema: z.object({ operation: z.enum(['add', 'subtract', 'multiply', 'divide']), a: z.number(), b: z.number(), }), execute: async ({ operation, a, b }) => { switch (operation) { case 'add': return a + b; case 'subtract': return a - b; case 'multiply': return a * b; case 'divide': return a / b; } }, }, }; // Infer the types from the tool set type MyUITools = InferUITools; // This creates a type with: // { // weather: { input: { location: string }; output: string }; // calculator: { input: { operation: 'add' | 'subtract' | 'multiply' | 'divide'; a: number; b: number }; output: number }; // } ``` ## Related - [`InferUITool`](/docs/reference/ai-sdk-ui/infer-ui-tool) - Infer types for a single tool - [`useChat`](/docs/reference/ai-sdk-ui/use-chat) - Chat hook that supports typed tools --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_MCPAppRenderer description: "API reference for rendering MCP Apps with @ai-sdk/react." url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/mcp-app-renderer" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `experimental_MCPAppRenderer` is experimental and may change in a future release. `experimental_MCPAppRenderer` renders an MCP App for an AI SDK tool UI part. It detects MCP App metadata on the tool part, loads the app resource, renders the app in a sandbox proxy iframe, and bridges MCP Apps JSON-RPC messages between the iframe and your host application. For tool parts without MCP App metadata, the component renders the `fallback`. ## Import ``` import { experimental_MCPAppRenderer as MCPAppRenderer } from "@ai-sdk/react" ``` ## Example ```tsx 'use client'; import { experimental_MCPAppRenderer as MCPAppRenderer, type MCPAppBridgeHandlers, type MCPAppMetadata, type MCPAppResource, type MCPAppSandboxConfig, } from '@ai-sdk/react'; import { isToolUIPart } from 'ai'; const sandbox = { url: '/mcp-app-sandbox', className: 'h-80 w-full rounded-lg border', style: { border: 0 }, } satisfies MCPAppSandboxConfig; async function loadResource(app: MCPAppMetadata): Promise { const response = await fetch('/api/mcp-app-host/read-resource', { method: 'POST', body: JSON.stringify({ uri: app.resourceUri }), }); if (!response.ok) { throw new Error('Failed to load MCP App resource'); } return response.json(); } const handlers: MCPAppBridgeHandlers = { callTool: params => fetch('/api/mcp-app-host/call-tool', { method: 'POST', body: JSON.stringify(params), }).then(response => response.json()), openLink: ({ url }) => { window.open(url, '_blank', 'noopener,noreferrer'); return {}; }, }; export function MessagePart({ part }: { part: unknown }) { if (!isToolUIPart(part)) { return null; } return ( ); } ``` ## Props - `part` (`ToolUIPart | DynamicToolUIPart`): The AI SDK tool UI part. The renderer looks for MCP App metadata in \`part.toolMetadata.mcp.app\`. - `sandbox` (`MCPAppSandboxConfig`): Configuration for the outer sandbox proxy iframe used to host the app. - `resource?` (`MCPAppResource`): A preloaded MCP App resource. Provide either \`resource\` or \`loadResource\`. - `loadResource?` (`(app: MCPAppMetadata) => Promise`): Loads the MCP App resource for the app metadata found on the tool part. - `handlers?` (`MCPAppBridgeHandlers`): Callbacks used to handle iframe requests such as \`tools/call\`, \`resources/read\`, \`ui/open-link\`, and display mode changes. - `hostInfo?` (`{ name: string; version: string }`): Host identity returned to the app during the \`ui/initialize\` handshake. - `hostContext?` (`MCPAppHostContext`): Host context sent to the app, such as theme, display mode, and available display modes. - `fallback?` (`ReactNode`): Rendered while the resource is loading, when loading fails, or when the tool part is not an MCP App. ## Sandbox Config - `url` (`string | URL`): The URL of the sandbox proxy iframe. The proxy receives app HTML from the host and creates the inner app iframe. - `title?` (`string`): Accessible iframe title. - `className?` (`string`): Class name applied to the outer iframe. - `style?` (`CSSProperties`): Inline styles applied to the outer iframe. - `targetOrigin?` (`string`): Target origin used for \`postMessage\`. Defaults to \`\*\`; set this to your sandbox origin in production. - `outerSandbox?` (`string`): Sandbox attribute for the outer proxy iframe. Defaults to \`allow-scripts allow-same-origin allow-forms\`. - `innerSandbox?` (`string`): Sandbox attribute sent to the proxy for the inner app iframe. Defaults to \`allow-scripts allow-forms\`. ## Bridge Handlers `experimental_MCPAppRenderer` uses these handlers to respond to iframe requests. In production, server-backed handlers should validate authorization and MCP Apps tool visibility before calling the MCP server. - `allowedTools?` (`string[]`): Optional client-side allowlist checked before forwarding \`tools/call\` requests. - `callTool?` (`(params: MCPAppToolCallParams) => Promise | unknown`): Handles app-initiated \`tools/call\` requests. - `readResource?` (`(params: { uri: string }) => Promise | unknown`): Handles app-initiated \`resources/read\` requests. - `listResources?` (`(params?: unknown) => Promise | unknown`): Handles app-initiated \`resources/list\` requests. - `openLink?` (`(params: { url: string }) => Promise | unknown`): Handles app-initiated \`ui/open-link\` requests. - `sendMessage?` (`(params: unknown) => Promise | unknown`): Handles app-initiated \`ui/message\` requests. - `updateModelContext?` (`(params: unknown) => Promise | unknown`): Handles app-initiated \`ui/update-model-context\` requests. - `requestDisplayMode?` (`(params: { mode: 'inline' | 'fullscreen' | 'pip' }) => Promise<{ mode: MCPAppDisplayMode }> | { mode: MCPAppDisplayMode }`): Handles app-initiated \`ui/request-display-mode\` requests. - `onSizeChange?` (`(params: { width?: number; height?: number }) => void`): Called when the app sends a size change notification. - `onInitialized?` (`() => void`): Called after the app sends \`ui/notifications/initialized\`. - `onRequestTeardown?` (`(params: unknown) => void`): Called when the app requests teardown. - `onLog?` (`(params: unknown) => void`): Called when the app sends a log notification. - `onError?` (`(error: Error) => void`): Called when a supported iframe request fails while being handled. ## See Also - [MCP Apps guide](/docs/ai-sdk-core/mcp-apps) - [MCP Apps helpers](/docs/reference/ai-sdk-core/mcp-apps) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: pipeUIMessageStreamToResponse description: Learn to use pipeUIMessageStreamToResponse helper function to pipe streaming data to a ServerResponse object. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/pipe-ui-message-stream-to-response" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `pipeUIMessageStreamToResponse` function pipes streaming data to a Node.js ServerResponse object (see [Streaming Data](/docs/ai-sdk-ui/streaming-data)). ## Import ``` import { pipeUIMessageStreamToResponse } from "ai" ``` ## Example ```tsx await pipeUIMessageStreamToResponse({ response: serverResponse, status: 200, statusText: 'OK', headers: { 'Custom-Header': 'value', }, stream: myUIMessageStream, consumeSseStream: ({ stream }) => { // Optional: consume the SSE stream independently console.log('Consuming SSE stream:', stream); }, }); ``` ## API Signature ### Parameters - `response` (`ServerResponse`): The Node.js ServerResponse object to pipe the data to. - `stream` (`ReadableStream`): The UI message stream to pipe to the response. - `status?` (`number`): The status code for the response. - `statusText?` (`string`): The status text for the response. - `headers?` (`Headers | Record`): Additional headers for the response. - `keepAliveMs?` (`number`): Interval in milliseconds for sending SSE keep-alive comments. When set, an opening comment is sent immediately and additional comments are sent after the stream has been idle for the configured interval. - `consumeSseStream?` (`({ stream }: { stream: ReadableStream }) => PromiseLike | void`): Optional function to consume the SSE stream independently. The stream is teed and this function receives a copy. ### Returns A `Promise` that resolves when the stream has been written to the response and rejects when reading or writing the stream fails. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: pruneMessages description: API Reference for pruneMessages. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/prune-messages" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The `pruneMessages` function is used to prune or filter an array of `ModelMessage` objects. This is useful for reducing message context (to save tokens), removing intermediate reasoning, or trimming tool calls and empty messages before sending to an LLM. ```ts title="app/api/chat/route.ts" import { createUIMessageStreamResponse, pruneMessages, streamText, toUIMessageStream, } from 'ai'; export async function POST(req: Request) { const { messages } = await req.json(); const prunedMessages = pruneMessages({ messages, reasoning: 'before-last-message', toolCalls: 'before-last-2-messages', emptyMessages: 'remove', }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: prunedMessages, }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); } ``` ## Import ``` import { pruneMessages } from "ai" ``` ## API Signature ### Parameters - `messages` (`ModelMessage[]`): An array of ModelMessage objects to prune. - `reasoning` (`'all' | 'before-last-message' | 'none'`): How to remove reasoning content from assistant messages. Default: "none". - `toolCalls` (``'all' | 'before-last-message' | `before-last-${number}-messages` | 'none' | Array<{ type: 'all' | 'before-last-message' | `before-last-${number}-messages`; tools?: string[] }>``): How to prune tool call/results/approval content. Can specify a strategy string or an array for per-tool fine control. Default: \[] (empty array, equivalent to "none"). - `emptyMessages` (`'keep' | 'remove'`): Whether to keep or remove messages whose content is empty after pruning. Default: "remove". ### Returns An array of [`ModelMessage`](/docs/reference/ai-sdk-core/model-message) objects, pruned according to the provided options. - `ModelMessage[]` (`Array`): The pruned list of ModelMessage objects ## Example Usage ```ts import { pruneMessages } from 'ai'; const pruned = pruneMessages({ messages, reasoning: 'all', // Remove all reasoning parts toolCalls: 'before-last-message', // Remove tool calls except those in the last message }); ``` ## Pruning Options - **reasoning:** Removes reasoning parts from assistant messages. Use `'all'` to remove all, `'before-last-message'` to keep reasoning in the last message, or `'none'` to retain all reasoning. - **toolCalls:** Prune tool-call, tool-result, and tool-approval chunks from assistant/tool messages. Default is an empty array (no pruning). Options include: - `'all'`: Prune all such content. - `'before-last-message'`: Prune except in the last message. - `'before-last-N-messages'`: Prune except in the last N messages. - `'none'`: Do not prune. - Or provide an array for per-tool fine control, e.g., `[{ type: 'before-last-message', tools: ['search', 'calculator'] }]` to prune only specific tools. - **emptyMessages:** Set to `'remove'` (default) to exclude messages that have no content after pruning. > **Tip**: `pruneMessages` is typically used prior to sending a context window to an LLM to reduce message/token count, especially after a series of tool-calls and approvals. For advanced usage and the full list of possible message parts, see [`ModelMessage`](/docs/reference/ai-sdk-core/model-message) and [`pruneMessages` implementation](https://github.com/vercel/ai/blob/main/packages/ai/src/generate-text/prune-messages.ts). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: readUIMessageStream description: API Reference for readUIMessageStream. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/read-ui-message-stream" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Transforms a stream of `UIMessageChunk`s into an `AsyncIterableStream` of `UIMessage`s. UI message streams are useful outside of Chat use cases, e.g. for terminal UIs, custom stream consumption on the client, or RSC (React Server Components). ## Import ```tsx import { readUIMessageStream } from 'ai'; ``` ## API Signature ### Parameters - `message?` (`UIMessage`): The last assistant message to use as a starting point when the conversation is resumed. Otherwise undefined. - `stream` (`ReadableStream`): The stream of UIMessageChunk objects to read. - `onError?` (`(error: unknown) => void`): A function that is called when an error occurs during stream processing. - `terminateOnError?` (`boolean`): Whether to terminate the stream if an error occurs. Defaults to false. ### Returns An `AsyncIterableStream` of `UIMessage`s. Each stream part represents a different state of the same message as it is being completed. For comprehensive examples and use cases, see [Reading UI Message Streams](/docs/ai-sdk-ui/reading-ui-message-streams). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: useChat description: API reference for the useChat hook. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Allows you to easily create a conversational user interface for your chatbot application. It enables the streaming of chat messages from your AI provider, manages the chat state, and updates the UI automatically as new messages are received. The `useChat` API has been significantly updated in AI SDK 5.0. It now uses a transport-based architecture and no longer manages input state internally. See the [migration guide](/docs/migration-guides/migration-guide-5-0#usechat-changes) for details. ## Import #### React ``` import { useChat } from '@ai-sdk/react' ``` #### Svelte ``` import { Chat } from '@ai-sdk/svelte' ``` #### Vue ``` import { useChat } from '@ai-sdk/vue' ``` #### Angular ``` import { Chat } from '@ai-sdk/angular' ``` ## API Signature ### Parameters - `chat?` (`Chat`): An existing Chat instance to use. If provided, other parameters are ignored. - `transport?` (`ChatTransport`): The transport to use for sending messages. Defaults to DefaultChatTransport with \`/api/chat\` endpoint. - `DefaultChatTransport` - `api?` (`string = '/api/chat'`): The API endpoint for chat requests. - `resumeStreamIsReplay?` (`boolean`): Defaults to true. Resets the interrupted response on the first resumed start chunk and rebuilds it from the replay. Set to false for endpoints that only send the remaining chunks of the same response. - `credentials?` (`RequestCredentials`): The credentials mode for fetch requests. - `headers?` (`Record | Headers`): HTTP headers to send with requests. - `body?` (`object`): Extra body object to send with requests. - `fetch?` (`FetchFunction`): Custom fetch implementation. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. - `prepareSendMessagesRequest?` (`PrepareSendMessagesRequest`): A function to customize the request before chat API calls. - `PrepareSendMessagesRequest` - `options` (`PrepareSendMessageRequestOptions`): Options for preparing the request - `PrepareSendMessageRequestOptions` - `id` (`string`): The chat ID - `messages` (`UIMessage[]`): Current messages in the chat - `requestMetadata` (`unknown`): The request metadata - `body` (`Record | undefined`): The request body - `credentials` (`RequestCredentials | undefined`): The request credentials - `headers` (`HeadersInit | undefined`): The request headers - `api` (`string`): The API endpoint to use for the request. If not specified, it defaults to the transport’s API endpoint: /api/chat. - `trigger` (`'submit-message' | 'regenerate-message'`): The trigger for the request - `messageId` (`string | undefined`): The message ID if applicable - `prepareReconnectToStreamRequest?` (`PrepareReconnectToStreamRequest`): A function to customize the request before reconnect API call. - `PrepareReconnectToStreamRequest` - `options` (`PrepareReconnectToStreamRequestOptions`): Options for preparing the reconnect request - `PrepareReconnectToStreamRequestOptions` - `id` (`string`): The chat ID - `requestMetadata` (`unknown`): The request metadata - `body` (`Record | undefined`): The request body - `credentials` (`RequestCredentials | undefined`): The request credentials - `headers` (`HeadersInit | undefined`): The request headers - `api` (`string`): The API endpoint to use for the request. If not specified, it defaults to the transport’s API endpoint combined with the chat ID: /api/chat/\{chatId}/stream. - `id?` (`string`): A unique identifier for the chat. If not provided, a random one will be generated on first access, including when sending a message or resuming a stream. - `messages?` (`UIMessage[]`): Initial chat messages to populate the conversation with. - `messageMetadataSchema?` (`FlexibleSchema`): Schema for validating message metadata. - `dataPartSchemas?` (`UIDataTypesToSchemas`): Schemas for validating data parts in messages. - `generateId?` (`IdGenerator`): A function to generate unique IDs for messages and the chat. If not provided, the default AI SDK generateId is used. - `onToolCall?` (`({toolCall: ToolCall}) => void | Promise`): Optional callback function that is invoked when a tool call is received. You must call addToolOutput to provide the tool result. - `sendAutomaticallyWhen?` (`(options: { messages: UIMessage[] }) => boolean | PromiseLike`): When provided, this function will be called when the stream is finished or a tool call is added to determine if the current messages should be resubmitted. You can use the lastAssistantMessageIsCompleteWithToolCalls helper for common scenarios. - `onFinish?` (`(options: OnFinishOptions) => void`): Called when the assistant response has finished streaming. - `OnFinishOptions` - `message` (`UIMessage`): The response message. - `messages` (`UIMessage[]`): All messages including the response message - `isAbort` (`boolean`): True when the request has been aborted by the client. - `isDisconnect` (`boolean`): True if the server has been disconnected, e.g. because of a network error. - `isError` (`boolean`): True if errors during streaming caused the response to stop early. - `finishReason?` (`'stop' | 'length' | 'content-filter' | 'tool-calls' | 'error' | 'interrupted' | 'other'`): The reason why the model finished generating the response. Undefined if the finish reason was not provided by the model. - `onError?` (`(error: Error) => void`): Callback function to be called when an error is encountered. - `onData?` (`(dataPart: DataUIPart) => void`): Optional callback function that is called when a data part is received. - `throttle?` (`number`): React and Vue only. Custom throttle wait time in milliseconds for reactive chat message updates. Positive values reduce UI update frequency without delaying stream processing or callbacks, and the latest messages are published before a ready or error status. Default is undefined, which disables throttling. - `resume?` (`boolean`): Whether to automatically resume an ongoing chat generation stream on mount and after the page becomes visible again following a network disconnect. Defaults to false. ### Returns - `id` (`string`): The id of the chat. - `messages` (`UIMessage[]`): The current array of chat messages. - `UIMessage` - `id` (`string`): A unique identifier for the message. - `role` (`'system' | 'user' | 'assistant'`): The role of the message. - `parts` (`UIMessagePart[]`): The parts of the message. Use this for rendering the message in the UI. - `metadata?` (`unknown`): The metadata of the message. - `status` (`'submitted' | 'streaming' | 'ready' | 'error'`): The current status of the chat: "ready" (idle), "submitted" (request sent), "streaming" (receiving response), or "error" (request failed). - `error` (`Error | undefined`): The error object if an error occurred. - `sendMessage` (`(message?: { text: string; files?: FileList | FileUIPart[]; metadata?; messageId?: string } | CreateUIMessage, options?: ChatRequestOptions) => Promise`): Function to send a new message to the chat. This will trigger an API call to generate the assistant response. If a messageId is provided, the message will be replaced (useful for editing). When replacing with a CreateUIMessage, provide its id to assign a new ID to the replacement. If no message is provided, resubmits the current messages (useful after adding tool outputs). - `ChatRequestOptions` - `headers?` (`Record | Headers`): Additional headers that should be to be passed to the API endpoint. - `body?` (`object`): Additional body JSON properties that should be sent to the API endpoint. - `metadata?` (`unknown`): Additional data to be sent to the API endpoint. - `regenerate` (`(options?: { messageId?: string } & ChatRequestOptions) => Promise`): Function to regenerate the last assistant message or a specific message. If no messageId is provided, regenerates the last assistant message. Accepts ChatRequestOptions for headers, body, and metadata. - `stop` (`() => void`): Function to abort the current streaming response from the assistant. - `clearError` (`() => void`): Clears the error state. - `resumeStream` (`() => void`): Function to resume an interrupted streaming response. Useful when a network error occurs during streaming. - `addToolOutput` (`(options: { tool: string; toolCallId: string; output: unknown } | { tool: string; toolCallId: string; state: "output-error", errorText: string }) => void`): Function to add a tool result to the chat. This will update the chat messages with the tool result. If sendAutomaticallyWhen is configured, it may trigger an automatic submission. - `addToolApprovalResponse` (`(options: { id: string; approved: boolean; reason?: string }) => void | PromiseLike`): Function to respond to a tool approval request. The id should match the approval id from the tool call. If sendAutomaticallyWhen is configured, it may trigger an automatic submission. - `addToolResult` (`(options: { tool: string; toolCallId: string; output: unknown } | { tool: string; toolCallId: string; state: "output-error", errorText: string }) => void`): Deprecated. Use addToolOutput instead. - `setMessages` (`(messages: UIMessage[] | ((messages: UIMessage[]) => UIMessage[])) => void`): Function to update the messages state locally without triggering an API call. Useful for optimistic updates. ## React rendering and shared Chat instances In React, `messages`, `status`, and `error` describe the snapshot for the current render. The underlying `Chat` updates as stream chunks are processed. React may batch updates, and `throttle` can delay message rendering without delaying stream processing or callbacks. Status and error updates include the latest messages, so a completed or failed response is rendered with its final message snapshot. Each `useChat({ chat })` call subscribes independently. Consumers sharing a `Chat` can display different message snapshots when they use different throttle intervals. A functional `setMessages` updater receives the Chat's latest messages, which may be newer than the messages in the current render. ## Learn more - [Chatbot](/docs/ai-sdk-ui/chatbot) - [Chatbot with Tools](/docs/ai-sdk-ui/chatbot-tool-usage) - [UIMessage](/docs/reference/ai-sdk-core/ui-message) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: useCompletion description: API reference for the useCompletion hook. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-completion" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Allows you to create text completion based capabilities for your application. It enables the streaming of text completions from your AI provider, manages the state for chat input, and updates the UI automatically as new messages are received. ## Import #### React ``` import { useCompletion } from '@ai-sdk/react' ``` #### Svelte ``` import { Completion } from '@ai-sdk/svelte' ``` #### Vue ``` import { useCompletion } from '@ai-sdk/vue' ``` #### Angular ``` import { Completion } from '@ai-sdk/angular' ``` ## API Signature ### Type Parameters - `BODY` (`object`): The type of the additional request body passed through the hook options or the complete function. Defaults to object. ### Parameters - `api` (`string = '/api/completion'`): The API endpoint that is called to generate text. It can be a relative path (starting with \`/\`) or an absolute URL. - `id` (`string`): A unique identifier for the completion. If not provided, a random one will be generated. When provided, the \`useCompletion\` hook with the same \`id\` will have shared states across components. This is useful when you have multiple components showing the same chat stream - `initialInput` (`string`): An optional string for the initial prompt input. - `initialCompletion` (`string`): An optional string for the initial completion result. - `onFinish` (`(prompt: string, completion: string) => void`): An optional callback function that is called when the completion stream ends. - `onError` (`(error: Error) => void`): An optional callback that will be called when the chat stream encounters an error. - `headers` (`Record | Headers`): An optional object of headers to be passed to the API endpoint. - `body` (`BODY`): An optional, additional body object to be passed to the API endpoint. - `credentials` (`'omit' | 'same-origin' | 'include'`): An optional literal that sets the mode of credentials to be used on the request. Defaults to same-origin. - `streamProtocol?` (`'text' | 'data'`): An optional literal that sets the type of stream to be used. Defaults to \`data\`. If set to \`text\`, the stream will be treated as a text stream. Note that with either protocol, \`useCompletion\` only exposes text content via the \`completion\` field — non-text parts (e.g. \`data-\*\` parts) emitted by the server are ignored. Use \`useChat\` if you need access to those parts. - `fetch?` (`FetchFunction`): Optional. A custom fetch function to be used for the API call. Defaults to the global fetch function. - `throttle?` (`number`): React only. Custom throttle wait time in milliseconds for the completion and data updates. When specified, throttles how often the UI updates during streaming. Default is undefined, which disables throttling. ### Returns - `completion` (`string`): The current text completion. - `complete` (`(prompt: string, options?: { headers?: Record | Headers, body?: BODY }) => Promise`): Function to execute text completion based on the provided prompt. Returns the completion result when finished. - `error` (`undefined | Error`): The error thrown during the completion process, if any. - `setCompletion` (`(completion: string) => void`): Function to update the \`completion\` state. - `stop` (`() => void`): Function to abort the current API request. - `input` (`string`): The current value of the input field. - `setInput` (`React.Dispatch>`): Function to update the input value. - `handleInputChange` (`(event: any) => void`): Handler for the \`onChange\` event of the input field to control the input's value. - `handleSubmit` (`(event?: { preventDefault?: () => void }) => void`): Form submission handler that automatically resets the input field and appends a user message. - `isLoading` (`boolean`): Boolean flag indicating whether a fetch operation is currently in progress. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: useObject description: API reference for the useObject hook. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-object" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `useObject` is available in React, Svelte, Vue, and the SolidJS community integration. Angular provides the equivalent `StructuredObject` service. Allows you to consume text streams that represent a JSON object and parse them into a complete object based on a schema. You can use it together with [`streamText`](/docs/reference/ai-sdk-core/stream-text) and [`Output.object()`](/docs/reference/ai-sdk-core/output#output-object) in the backend. ```tsx 'use client'; import { useObject } from '@ai-sdk/react'; export default function Page() { const { object, submit } = useObject({ api: '/api/use-object', schema: z.object({ content: z.string() }), }); return (
{object?.content &&

{object.content}

}
); } ``` ## Import #### React ``` import { useObject } from '@ai-sdk/react' ``` #### Svelte ``` import { StructuredObject } from '@ai-sdk/svelte' ``` #### Vue ``` import { useObject } from '@ai-sdk/vue' ``` ## API Signature ### Parameters - `api` (`string`): The API endpoint that is called to generate objects. It should stream JSON that matches the schema as chunked text. It can be a relative path (starting with \`/\`) or an absolute URL. - `schema` (`Zod Schema | JSON Schema`): A schema that defines the shape of the complete object. You can either pass in a Zod schema or a JSON schema (using the \`jsonSchema\` function). - `id?` (`string`): A unique identifier. If not provided, a random one will be generated. When provided, the \`useObject\` hook with the same \`id\` will have shared states across components. - `initialValue?` (`DeepPartial | undefined`): An value for the initial object. Optional. - `fetch?` (`FetchFunction`): A custom fetch function to be used for the API call. Defaults to the global fetch function. Optional. - `headers?` (`Record | Headers`): A headers object to be passed to the API endpoint. Optional. - `credentials?` (`RequestCredentials`): The credentials mode to be used for the fetch request. Possible values are: "omit", "same-origin", "include". Optional. - `onError?` (`(error: Error) => void`): Callback function to be called when an error is encountered. Optional. - `onFinish?` (`(result: OnFinishResult) => void`): Called when the streaming response has finished. - `OnFinishResult` - `object` (`T | undefined`): The generated object (typed according to the schema). Can be undefined if the final object does not match the schema. - `error` (`Error | undefined`): Optional error object. This is e.g. a TypeValidationError when the final object does not match the schema. ### Returns - `submit` (`(input: INPUT) => void`): Calls the API with the provided input as JSON body. - `object` (`DeepPartial | undefined`): The current value for the generated object. Updated as the API streams JSON chunks. - `error` (`Error | undefined`): The error object if the API call fails. - `isLoading` (`boolean`): Boolean flag indicating whether a request is currently in progress. - `stop` (`() => void`): Function to abort the current API request. - `clear` (`() => void`): Function to clear the object state. ## Examples - [Streaming Object Generation with useObject](/examples/next-pages/basics/streaming-object-generation) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: experimental_useRealtime description: API reference for the experimental_useRealtime hook. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-realtime" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). `experimental_useRealtime` is an experimental feature. Creates a browser-side realtime session for bidirectional audio and text conversations with a realtime provider model. The hook supports token-based provider WebSockets, application-owned WebSocket relays, and optional WebRTC where the model declares support. It provides controls for capture and playback, plus turn-based text input and tool output. Turn-based conversation messages use `UIMessage[]`; continuous transcript fragments remain in `session`. ```tsx import { openai } from '@ai-sdk/openai'; import { experimental_useRealtime } from '@ai-sdk/react'; const model = openai.experimental_realtime('gpt-realtime'); function Conversation() { const realtime = experimental_useRealtime({ model, api: { token: '/api/realtime/setup' }, }); return ; } ``` For AI Gateway, pass `gateway.experimental_realtime(...)` as the model and point `api.token` at a server-side setup endpoint that calls `gateway.experimental_realtime.getToken()`. Keep the model and session configuration stable across renders, using module scope or `useMemo`. Replacing either object replaces the hook's session. ## Continuous conversations For OpenAI Live, use `openai.experimental_realtime('gpt-live-1')` and an application-owned WebSocket relay. The relay supplies server credentials and forwards the provider's native text frames; it must authenticate clients before accepting connections. Use `wss:` in production, keep provider credentials server-side, and apply your application's authentication to the relay connection. ```tsx import { openai, type Experimental_OpenAIRealtimeModelLiveOptions, } from '@ai-sdk/openai'; import { experimental_useRealtime } from '@ai-sdk/react'; const model = openai.experimental_realtime('gpt-live-1'); const sessionConfig = { instructions: 'Be a concise, friendly assistant.', providerOptions: { openai: { delegation: { type: 'client' }, } satisfies Experimental_OpenAIRealtimeModelLiveOptions, }, }; function LiveConversation() { const realtime = experimental_useRealtime({ model, api: { websocket: 'wss://your-app.example/live' }, sessionConfig, }); return ( <>

{realtime.status}: {realtime.session?.usage?.seconds} seconds

); } ``` The current continuous browser runtime supports a JSON/PCM16 WebSocket relay media profile. Continuous conversation semantics alone do not guarantee support for every codec or transport. Applications with their own audio pipeline can use the low-level provider for other supported codecs. OpenAI-specific settings live under `providerOptions.openai` and use camelCase fields; the provider converts them to wire names. ### Application-handled client delegation Live reports client delegation metadata through `session.delegations` and normalized events. The application owns any text conversation, agent context, tool execution, and result validation. The SDK does not run a generic agent executor for Live, and Live delegations do not invoke `onToolCall`. Use `sendEvent` to append context or a result. Set `delegationId` to a known client delegation from the current session, or `null` for session-wide context: ```tsx await realtime.sendEvent({ type: 'context-append', delegationId: null, content: 'The application has confirmed that the appointment is at 3 PM.', providerOptions: { openai: { channel: 'commentary' } }, }); ``` The example uses application-provided content, not task arguments inferred from delegation metadata. OpenAI's context channels are `commentary`, `thinking`, and `instructions`; reserve `instructions` for trusted application instructions. A successful send confirms local submission, not provider acceptance or audible delivery. ### Optional WebRTC For browser-direct Live audio, replace `api.websocket` with `api: { session: '/api/realtime-live' }`. The hook posts JSON `{ sdp, sessionConfig }` to that application endpoint and expects JSON `{ sdp, sessionId }` back. On the server, authenticate the application user and validate the offer and allowed settings. Use a same-origin broker authenticated with your application's session cookie. The built-in setup request uses the browser's default same-origin credentials; `api.session` does not accept custom authorization headers, a custom fetch, or cross-origin credential options. A bearer-token-only or cross-origin cookie broker therefore needs an application-owned same-origin endpoint in front of it. The answer must contain nonempty `sdp` and `sessionId` strings (after trimming for validation), and the JSON response body is limited to 1 MiB. Exchange SDP using the server-held provider key: ```ts const answer = await openai .experimental_realtime('gpt-live-1') .doCreateWebRTCSession({ sdp, sessionConfig: { providerOptions: { openai: { delegation: { type: 'client' }, client: { dataChannel: { allowedClientEvents: ['session.close', 'session.thinking.append'], allowedServerEvents: [ { type: 'session.started' }, { type: 'session.closed' }, { type: 'session.usage.updated' }, { type: 'session.delegation.created' }, { type: 'session.input_transcript.delta' }, { type: 'session.output_transcript.delta' }, { type: 'session.thinking.appended' }, { type: 'error' }, ], }, }, }, }, }, }); // Return Response.json(answer) from the application endpoint. ``` Permissions are server-owned; override browser-supplied delegation and data-channel policy. Allow the lifecycle, caption, delegation, command acknowledgment, and error events needed by your UI. The runnable `/realtime-live` example in `examples/ai-e2e-next` adds protocol mute and all three context channels. WebRTC negotiates audio through SDP, so omit fixed audio formats and the PCM-only `maxPlaybackBufferSeconds`. Audio travels as media rather than JSON audio commands. `connect({ capture: false })` starts without microphone capture and keeps a reusable audio sender; `resumeAudioCapture()` can attach a microphone later. Microphone access requires browser permission and a secure context. Autoplay may require a user gesture followed by `resumePlayback()`. One live audio track is selected per attachment, preferring an enabled, unmuted track. `isCapturing` follows that sender track, including mute and ended events; it never switches tracks automatically. Caller-owned tracks are detached without being stopped or disabled. Capture controls serialize sender changes; a stop is reported only after detachment succeeds or the peer closes. If detachment fails, the peer is closed to stop transmission while leaving borrowed tracks untouched. Client delegation remains application-handled on either transport, and Live session updates remain unsupported. ## Lifecycle and failure handling Use `status === 'connected'` as the readiness signal; `connect()` does not promise to wait for provider readiness. Operational startup errors are reported through `onError` and `status`; the legacy resolve-and-report behavior is preserved. `close()` stops local capture and submissions, then drains events until the provider confirms final usage or the close deadline expires. A failed close-command send uses the shorter accepted-event drain rather than waiting for an acknowledgment to an unsent command. Read `session.finalization`: a fulfilled close promise does not by itself confirm usage. `disconnect()` and unmount release resources immediately. Hook controls keep stable identities across renders and provider events. A retained control targets the current **committed** session, including after a model or endpoint change. Uncommitted renders cannot replace the active session or its callbacks. Callback-only updates take effect at commit without reconnecting. After unmount, `connect`, `close`, `resumeAudioCapture`, and `resumePlayback` reject with a mounted-hook error. Other controls throw that error synchronously, including `sendEvent`, which preserves synchronous validation and returns a promise for an accepted submission. Retained controls cannot reopen an unmounted session. Command rejection and playback failures are recoverable and do not mark a healthy protocol connection as failed. Irrecoverable transport failures and protocol queue overflow stop submissions and capture, drain the accepted event prefix, and clean up. Events are never arbitrarily dropped while continuing with unreliable state. No commands or side-effecting tools are transparently replayed on a replacement connection. Continuous Live WebSocket sessions have a 128 KiB outgoing wire-frame limit and a 128 KiB combined buffered-send budget, including the next frame and JSON/base64 encoding overhead. Oversized control messages are rejected, not silently split into multiple commands. Keep Live context submissions within this budget; the example relay's 128 KiB `maxPayload` aligns with the frame limit. These byte caps do not apply to legacy turn-based sessions, which retain their pre-Live unlimited byte policy, or to the optional WebRTC transport. The continuous WebSocket PCM playback budget bounds local latency and memory. On overflow, stale queued audio is discarded, playback pauses, and `onError` reports an audible gap. The connection stays alive; `resumePlayback()` resumes from fresh audio at the live edge. Captions and completion of delegated work do not prove that the corresponding audio was heard. ## Import ``` import { experimental_useRealtime } from "@ai-sdk/react" ``` ## API Signature ### Parameters - `model` (`Experimental_RealtimeModel`): The realtime model to connect to. - `api` (`{ token: string } | { websocket: string; protocols?: string[] } | { session: string }`): Choose one supported connection mechanism: token setup, a WebSocket relay, or a WebRTC SDP endpoint. Conversation semantics and connection capabilities are independent. - `Object` - `token?` (`string`): The setup endpoint that returns an Experimental\_RealtimeSetupResponse. - `session?` (`string`): Application WebRTC setup endpoint. Accepts JSON \{ sdp, sessionConfig } and returns JSON \{ sdp, sessionId }. - `websocket?` (`string`): Application-owned raw-protocol relay URL. Use wss in production; never put provider credentials in the URL. - `protocols?` (`string[]`): Optional relay subprotocols, supported only with websocket. - `sessionConfig?` (`Partial`): Provider-neutral session configuration, such as instructions, voice, audio formats, input audio transcription, turn detection, tools, and providerOptions. - `sampleRate?` (`number`): Default audio sample rate used when inputAudioFormat.rate or outputAudioFormat.rate is not specified. Defaults to 24000. - `maxEvents?` (`number`): Maximum number of provider events to keep in the events array. Defaults to 500. - `startupTimeoutMs?` (`number`): Readiness deadline, including transport setup. Defaults to 30000. - `closeTimeoutMs?` (`number`): Deadline for confirmed provider finalization after graceful close. Defaults to 15000. - `rtcDisconnectTimeoutMs?` (`number`): WebRTC peer disconnect recovery grace period. Defaults to 5000. Failed ICE terminates immediately. - `maxPlaybackBufferSeconds?` (`number`): Continuous WebSocket PCM playback budget before reporting a recoverable gap and pausing playback. Defaults to 2. Unsupported with WebRTC. - `onToolCall?` (`(options: { toolCall: { toolCallId: string; toolName: string; args: unknown } }) => unknown | Promise | undefined`): Turn-based tools only. Return a value to automatically submit it as tool output, or return undefined and call addToolOutput manually later. Live client delegations are application-handled through session metadata and events. - `onEvent?` (`(event: Experimental_RealtimeServerEvent) => void`): Called for every normalized realtime server event. - `onError?` (`(error: Error) => void`): Called when the realtime session encounters an error. ### Returns - `status` (`'disconnected' | 'connecting' | 'connected' | 'closing' | 'error'`): The current connection status. - `messages` (`UIMessage[]`): Messages assembled from turn-based response text, transcript, and tool events. Continuous Live transcript fragments are exposed separately through session.transcripts. - `events` (`Experimental_RealtimeServerEvent[]`): Recent normalized provider events for inspection or debug UI. - `isCapturing` (`boolean`): Capture state observed through SDK controls and events on the selected audio track. External changes to borrowed tracks may require an explicit refresh; see Capture ownership. - `isPlaying` (`boolean`): Whether model audio playback is active. - `session` (`Experimental_RealtimeSessionState | undefined`): Session lifecycle metadata where supported: sessionId, transcript fragments, client delegation metadata, cumulative voice usage, input mute, terminationReason, and pending/confirmed/unconfirmed finalization. Transcript fragments may overlap or arrive late; they are not complete turns. - `connect` (`(options?: { stream?: MediaStream; capture?: boolean }) => Promise`): Starts the selected connection. Continuous browser sessions acquire microphone audio by default; capture: false opts out. A supplied Live stream remains caller-owned. The zero-argument overload remains usable as an event handler. - `close` (`(options?: { eventId?: string }) => Promise`): Stops capture/submissions and waits for bounded graceful finalization where supported. Inspect session.finalization for confirmation; models without acknowledged close disconnect immediately. - `disconnect` (`() => void`): Immediately releases transports and owned media. Preserves latest usage as unconfirmed unless a terminal provider event was received. - `addToolOutput` (`(callId: string, result: unknown) => void`): Turn-based only: submits a tool result back to the realtime provider. Continuous Live sessions reject this control; append application-handled client delegation results with sendEvent instead. - `sendEvent` (`(event: Experimental_RealtimeClientEvent) => Promise`): Sends a normalized command in order. Resolves on local serialization/submission, not provider acceptance. Validation can throw synchronously; asynchronous send failures reject and are reported through onError. Live context/results use context-append with a known client delegation ID or null. - `sendTextMessage` (`(text: string) => void`): Turn-based only: sends user text and requests a response. Continuous Live sessions reject this control. The application owns its text conversation and supplies Live context through sendEvent. - `sendAudio` (`(base64Audio: string) => void`): Sends a base64-encoded audio chunk to the provider input audio buffer over WebSocket. WebRTC uses media tracks and rejects JSON audio commands. - `commitAudio` (`() => void`): Turn-based only: commits the provider input audio buffer. - `clearAudioBuffer` (`() => void`): Turn-based only: clears the provider input audio buffer. - `requestResponse` (`(options?: { modalities?: string[] }) => void`): Turn-based only: requests a new model response. - `cancelResponse` (`() => void`): Turn-based only: cancels the active model response. - `startAudioCapture` (`(stream: MediaStream) => void`): Starts or replaces capture with the supplied MediaStream. Live paths borrow tracks; the existing legacy capture API retains ownership of its supplied stream. Async attachment failures are reported through onError. - `stopAudioCapture` (`() => void`): Stops local capture and releases SDK-owned microphone tracks. Caller-owned Live tracks are only detached. Does not change the provider input-mute state. - `resumeAudioCapture` (`() => Promise`): Restarts capture, reusing a supplied Live stream or acquiring a fresh SDK-owned microphone. Does not reconnect the provider. - `stopPlayback` (`() => void`): Stops queued model audio playback. - `resumePlayback` (`() => Promise`): Retries browser playback after interruption, suspension, or buffer overflow. Discarded stale audio is not replayed. ## Tool Calling For turn-based models such as `gpt-realtime`, tool execution is client-driven. Use `onToolCall` to handle tool calls and return the tool output. Keep the model stable across callback updates: ```tsx const model = openai.experimental_realtime('gpt-realtime'); function WeatherConversation() { const realtime = experimental_useRealtime({ model, api: { token: '/api/realtime/setup' }, onToolCall: async ({ toolCall }) => { if (toolCall.toolName === 'getWeather') { const response = await fetch('/api/weather', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(toolCall.args), }); return response.json(); } }, }); return ; } ``` For tools that require user interaction, return `undefined` from `onToolCall` and call `addToolOutput` later. Turn-based providers retain their automatic continuation behavior. Live uses the application-handled client delegation flow described above, rather than these tool callbacks or turn controls. Outstanding commands are bounded independently from the retained recent-ID history. Completed work does not impose a lifetime command quota. Use fresh IDs and keep durable operation deduplication in the application; bounded history is not a promise of session-long exactly-once execution. ## Capture ownership SDK-acquired tracks are stopped on local capture stop or cleanup. Caller-owned Live tracks are detached without stopping or disabling them. `stopAudioCapture()` controls local hardware capture; provider `input-audio-mute` controls remote audio processing and is tracked through its acknowledgment. These are separate operations: protocol mute does not release the microphone, and resuming local capture does not unmute provider input. Use `resumeAudioCapture()` to reuse a caller-supplied stream or acquire a fresh SDK-owned microphone without reconnecting. The application remains responsible for stopping its own tracks when finished with them. `isCapturing` reflects SDK capture controls and events on the selected audio track; it does not continuously observe caller-owned media. Assigning `track.enabled` does not emit an event, and calling `track.stop()` does not emit an `ended` event. After changing borrowed tracks externally, call `startAudioCapture(stream)` or `resumeAudioCapture()` to refresh capture state and reattach as needed. If a track was stopped, provide a stream with a live audio track; stopped tracks cannot be restarted. The SDK does not poll external track state. ## Experimental compatibility This update adds explicit relay options and changes experimental `sendEvent` to return a promise. Existing token setups and the no-argument `connect` call remain supported. New session state uses `session`, not a provider-branded state object. Generic realtime-model consumers must check optional connection methods before calling them. Model interfaces and event unions are experimental; exhaustive external switches may need to handle the added events. OpenAI uses `experimental_realtime` for both Realtime and Live models, with a provider-specific `{ api: 'live' }` override for unknown or early-access Live model IDs. This factory option selects the provider API; the hook's `api.websocket` option selects the application's transport endpoint. OpenAI Live startup options are exported as `Experimental_OpenAIRealtimeModelLiveOptions`. See [Realtime](/docs/ai-sdk-core/realtime#tool-calling) for a complete example with server-backed app-specific tool endpoints. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: WebSocketChatTransport description: API Reference for the WebSocketChatTransport class. url: "https://ai-sdk.dev/docs/reference/ai-sdk-ui/websocket-chat-transport" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). A [`ChatTransport`](/docs/ai-sdk-ui/transport) implementation that sends chat requests and receives `UIMessageChunk` streams over one persistent WebSocket connection. ```tsx import { useChat } from '@ai-sdk/react'; import { WebSocketChatTransport } from 'ai'; import { useMemo } from 'react'; export default function Chat() { const transport = useMemo( () => new WebSocketChatTransport({ url: '/api/chat' }), [], ); const { messages, sendMessage, stop } = useChat({ transport }); // ... render the chat } ``` ## Import ``` import { WebSocketChatTransport } from "ai" ``` ## Constructor - `url` (`string`): The WebSocket endpoint. Relative URLs are resolved against the browser location. HTTP and HTTPS URLs are converted to WS and WSS. - `protocols?` (`string | string[]`): WebSocket subprotocols sent during the handshake. - `params?` (`Resolvable>`): Query parameters resolved and appended whenever a new connection is opened. - `headers?` (`Resolvable | Headers>`): Headers included inside each application-level send or resume frame. These are not WebSocket handshake headers. - `body?` (`Resolvable`): Additional application data merged with per-request body data and included in send and resume frames. - `webSocket?` (`WebSocketConstructor`): A custom WebSocket constructor. Use this for Node.js WebSocket implementations or tests. Defaults to globalThis.WebSocket. - `prepareSendMessagesRequest?` (`PrepareWebSocketChatTransportSendMessagesRequest`): Transforms the messages, headers, or body before a send frame is transmitted. - `prepareReconnectToStreamRequest?` (`PrepareWebSocketChatTransportReconnectToStreamRequest`): Transforms the headers or body before a resume frame is transmitted. `headers` supplied to `sendMessage`, `regenerate`, or an automatic tool follow-up override transport-level headers with the same lower-cased name. Per-request `body` properties override transport-level body properties. Preparation callbacks receive the merged values; values they return replace the corresponding merged value. ## Methods ### `sendMessages()` Opens the socket when necessary, sends a `send` frame, and returns the correlated `ReadableStream`. Multiple requests can share the same connection and are routed by `requestId`. ### `reconnectToStream()` Sends a `resume` frame. The promise resolves as follows: - `start`, the first `chunk`, or `end` resolves to a readable stream. - `no-active` resolves to `null`. - `error`, cancellation, or a connection failure rejects the promise if no stream has been returned yet. Servers should send `start` as soon as they confirm that a resumable stream is active, then replay the retained response from sequence zero. The transport omits `lastSequence` because Chat starts a fresh message parser when resuming: text, reasoning, and tool deltas need their preceding chunks. Retain and replay the original UI `start` chunk with a stable `messageId` so Chat replaces the partial assistant message instead of appending another one. The transport rejects gaps and ignores duplicate sequence numbers within each response stream, including the replay. ### `close()` Closes the current socket and errors active streams. A later send or resume request opens a new connection. The code that creates a transport owns its lifecycle. React and Vue `useChat` do not close caller-supplied transports during replacement or cleanup, because one transport can be shared by multiple chats. Call `close()` after its final consumer is gone. If a directly managed `Chat` instance exclusively owns the transport, call `dispose()`: ```ts await chat.dispose(); ``` For a shared transport, stop or discard individual chats without disposing their shared transport, then call `transport.close()` after all consumers are finished. ## Wire Protocol Each frame is a JSON object with a `type` and a correlated `requestId`. ### Client-to-server frames | Type | Purpose | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `send` | Starts a new or regenerated turn and includes `id`, `messages`, `trigger`, `messageId`, request `headers`, `body`, and `metadata`. | | `resume` | Requests the retained response for a chat `id`, with request `headers`, `body`, and `metadata`. `lastSequence` is omitted to request a complete replay. | | `abort` | Cancels the operation identified by `requestId`. | Use `WebSocketChatTransportRequest`, `WebSocketChatTransportSendRequest`, `WebSocketChatTransportResumeRequest`, and `WebSocketChatTransportAbortRequest` to type these frames. ### Server-to-client frames | Type | Purpose | | ----------- | ----------------------------------------------------------------------------- | | `start` | Confirms that a resume request has an active stream. | | `chunk` | Carries a monotonic `sequence` and one valid `UIMessageChunk` in `chunk`. | | `end` | Closes the correlated response stream. | | `error` | Errors the correlated stream with the optional `errorText`. | | `no-active` | Resolves a resume request to `null`; for a send request it closes the stream. | Use `WebSocketChatTransportResponse` to type server frames. Responses for unknown or completed request IDs are ignored. Malformed frames, unknown response types, and invalid UI message chunks are treated as protocol errors: the connection is closed and all active streams are errored. Validate every untrusted client frame before using its fields: ```ts const validated = await safeValidateWebSocketChatTransportRequest({ value: parsed.value, }); if (!validated.success) { socket.close(1008, 'Invalid frame'); return; } const frame = validated.data; ``` ## Server Example The server owns authentication, authorization, message validation, model execution, resumable stream storage, and cancellation. The following Node.js outline forwards a `streamText` UI message stream: ```ts import { WebSocket, WebSocketServer } from 'ws'; import { convertToModelMessages, safeValidateWebSocketChatTransportRequest, streamText, toUIMessageStream, validateUIMessages, type UIMessage, type UIMessageChunk, type WebSocketChatTransportResponse, } from 'ai'; import { generateId, safeParseJSON } from '@ai-sdk/provider-utils'; const server = new WebSocketServer({ port: 8080 }); type RetainedStream = { abortController: AbortController; chunks: Array<{ sequence: number; chunk: UIMessageChunk }>; subscribers: Map; ended: boolean; }; // Scope retained streams to an authenticated user. Use durable storage when // reconnects can land in another process. const streamsByUser = new Map>(); server.on('connection', (socket, request) => { const userId = authenticateAndValidateOrigin(request); const streams = streamsByUser.get(userId) ?? new Map(); streamsByUser.set(userId, streams); socket.on('message', async data => { const parsed = await safeParseJSON({ text: data.toString() }); if (!parsed.success) { socket.close(1008, 'Invalid frame'); return; } const validated = await safeValidateWebSocketChatTransportRequest({ value: parsed.value, }); if (!validated.success) { socket.close(1008, 'Invalid frame'); return; } const frame = validated.data; const send = (response: WebSocketChatTransportResponse) => { socket.send(JSON.stringify(response)); }; if (frame.type === 'abort') { for (const stream of streams.values()) { if (stream.subscribers.get(socket) === frame.requestId) { stream.subscribers.delete(socket); stream.abortController.abort(); } } return; } if (frame.type === 'resume') { const stream = streams.get(frame.id); if (stream == null) { send({ type: 'no-active', requestId: frame.requestId }); return; } send({ type: 'start', requestId: frame.requestId }); if (!stream.ended) { stream.subscribers.set(socket, frame.requestId); } for (const stored of stream.chunks) { if (stored.sequence > (frame.lastSequence ?? -1)) { send({ type: 'chunk', requestId: frame.requestId, ...stored, }); } } if (stream.ended) { send({ type: 'end', requestId: frame.requestId }); } return; } if (streams.get(frame.id)?.ended === false) { send({ type: 'error', requestId: frame.requestId, errorText: 'A response is already active for this chat.', }); return; } const abortController = new AbortController(); const stream: RetainedStream = { abortController, chunks: [], subscribers: new Map([[socket, frame.requestId]]), ended: false, }; streams.set(frame.id, stream); try { const messages = await validateUIMessages({ messages: frame.messages, }); const result = streamText({ model: "anthropic/claude-sonnet-5.5", messages: await convertToModelMessages(messages), abortSignal: abortController.signal, }); for await (const chunk of toUIMessageStream({ stream: result.stream, originalMessages: messages, generateMessageId: generateId, })) { const stored = { sequence: stream.chunks.length, chunk }; stream.chunks.push(stored); for (const [subscriber, requestId] of stream.subscribers) { subscriber.send( JSON.stringify({ type: 'chunk', requestId, ...stored }), ); } } stream.ended = true; for (const [subscriber, requestId] of stream.subscribers) { subscriber.send(JSON.stringify({ type: 'end', requestId })); } } catch (error) { // Failed responses cannot resume. Remove the retained entry so a retry // can start a new response and resume requests receive no-active. streams.delete(frame.id); // Log the internal error server-side, but do not expose it to the client. console.error(error); for (const [subscriber, requestId] of stream.subscribers) { subscriber.send( JSON.stringify({ type: 'error', requestId, errorText: 'Chat request failed.', }), ); } } finally { stream.subscribers.clear(); } }); socket.on('close', () => { for (const stream of streams.values()) { stream.subscribers.delete(socket); } }); }); ``` This outline keeps completed chunks in memory to make the replay behavior clear. Production servers should expire completed streams and store resumable state durably when reconnects can reach another process. If response setup or streaming throws, the outline removes that response from retained state before reporting the error. A later resume returns `no-active`, and a new send can retry the chat. ## Authentication and Deployment - Use WSS in production. - Browser WebSockets cannot set arbitrary handshake headers. Prefer secure cookies, short-lived query tokens, or subprotocols. - Validate the handshake origin, authorize chat IDs, validate every frame and UI message, limit frame sizes, and rate-limit connections. - Do not depend on process memory for reconnection when your deployment can restart or move the connection to another process. - The transport does not automatically replay interrupted `send` requests, because replay can duplicate model output or tool side effects. `WebSocketChatTransport` is separate from the experimental Realtime APIs. Realtime transports implement provider realtime sessions; this transport carries the provider-independent AI SDK UI message protocol used by `useChat`. ## Client-side Tool Follow-ups When `sendAutomaticallyWhen` triggers after client-side tool output is added, the next turn reuses the same WebSocket. This avoids another HTTP connection, but the current `ChatTransport` contract remains turn-based: the tool output is not injected into a model generation that is still active. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Adapters description: Learn how to use AI SDK Adapters. url: "https://ai-sdk.dev/providers/adapters" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). Adapters are lightweight integrations that enable you to use the AI SDK UI functions (`useChat` and `useCompletion`) with 3rd party libraries. The following adapters are currently available: - [LangChain](/providers/adapters/langchain) - [LlamaIndex](/providers/adapters/llamaindex) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: LangChain description: Learn how to use LangChain with the AI SDK. url: "https://ai-sdk.dev/providers/adapters/langchain" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [LangChain](https://docs.langchain.com/) is a framework for building applications powered by large language models. It provides tools and abstractions for working with AI models, prompts, chains, vector stores, and other data sources for retrieval augmented generation (RAG). [LangGraph](https://langchain-ai.github.io/langgraphjs/) is a library built on top of LangChain for creating stateful, multi-actor applications. It enables you to define complex agent workflows as graphs, with support for cycles, persistence, and human-in-the-loop patterns. The `@ai-sdk/langchain` adapter provides seamless integration between LangChain, LangGraph, and the AI SDK, enabling you to use LangChain models and LangGraph agents with AI SDK UI components. ## Installation ```bash pnpm add @ai-sdk/langchain @langchain/core ``` `@langchain/core` is a required peer dependency. ## Features - Convert AI SDK `UIMessage` to LangChain `BaseMessage` format using `toBaseMessages` - Transform LangChain/LangGraph streams to AI SDK `UIMessageStream` using `toUIMessageStream` - Support for `streamEvents()` output for granular event streaming and observability - `LangSmithDeploymentTransport` for connecting directly to a deployed LangGraph graph - Full support for text, tool calls, tool results, and multimodal content - Custom data streaming with typed events (`data-{type}`) ## Example: Basic Chat Here is a basic example that uses both the AI SDK and LangChain together with the [Next.js](https://nextjs.org/docs) App Router. ```tsx title="app/api/chat/route.ts" import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain'; import { ChatOpenAI } from '@langchain/openai'; import { createUIMessageStreamResponse, UIMessage } from 'ai'; export const maxDuration = 30; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const model = new ChatOpenAI({ model: 'gpt-4.1-mini', temperature: 0, }); // Convert AI SDK UIMessages to LangChain messages const langchainMessages = await toBaseMessages(messages); // Stream the response from the model const stream = await model.stream(langchainMessages); // Convert the LangChain stream to UI message stream return createUIMessageStreamResponse({ stream: toUIMessageStream(stream), }); } ``` Then, use the AI SDK's [`useChat`](/docs/ai-sdk-ui/chatbot) hook in the page component: ```tsx title="app/page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; export default function Chat() { const { messages, sendMessage, status } = useChat(); return (
{messages.map(m => (
{m.parts.map((part, i) => part.type === 'text' ? {part.text} : null, )}
))}
{ e.preventDefault(); const input = e.currentTarget.elements.namedItem( 'message', ) as HTMLInputElement; sendMessage({ text: input.value }); input.value = ''; }} >
); } ``` ## Example: LangChain Agent with Tools Create agents with tools using LangChain's [`createAgent`](https://docs.langchain.com/oss/javascript/langchain/agents): ```tsx title="app/api/agent/route.ts" import { createUIMessageStreamResponse, UIMessage } from 'ai'; import { createAgent } from 'langchain'; import { ChatOpenAI, tools } from '@langchain/openai'; import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain'; export const maxDuration = 60; const model = new ChatOpenAI({ model: 'gpt-4.1', temperature: 0.7, }); // Image generation tool configuration const imageGenerationTool = tools.imageGeneration({ size: '1024x1024', quality: 'high', outputFormat: 'png', }); // Create a LangChain agent with tools const agent = createAgent({ model, tools: [imageGenerationTool], systemPrompt: 'You are a creative AI artist assistant.', }); export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const langchainMessages = await toBaseMessages(messages); const stream = await agent.stream( { messages: langchainMessages }, { streamMode: ['values', 'messages', 'tools'] }, ); return createUIMessageStreamResponse({ stream: toUIMessageStream(stream), }); } ``` Use the `tools` stream mode to stream LangGraph tool progress. The adapter converts `on_tool_event` events to preliminary tool output (`preliminary: true`) and the final `on_tool_end` event to final tool output. ## Example: LangGraph Use the adapter with [LangGraph](https://docs.langchain.com/oss/javascript/langgraph/overview) to build agent workflows: ```tsx title="app/api/langgraph/route.ts" import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain'; import { ChatOpenAI } from '@langchain/openai'; import { createUIMessageStreamResponse, UIMessage } from 'ai'; import { StateGraph, MessagesAnnotation } from '@langchain/langgraph'; export const maxDuration = 30; const model = new ChatOpenAI({ model: 'gpt-4.1-mini', temperature: 0, }); async function callModel(state: typeof MessagesAnnotation.State) { const response = await model.invoke(state.messages); return { messages: [response] }; } export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); // Create the LangGraph agent const graph = new StateGraph(MessagesAnnotation) .addNode('agent', callModel) .addEdge('__start__', 'agent') .addEdge('agent', '__end__') .compile(); // Convert AI SDK UIMessages to LangChain messages const langchainMessages = await toBaseMessages(messages); // Stream from the graph using LangGraph's streaming format const stream = await graph.stream( { messages: langchainMessages }, { streamMode: ['values', 'messages'] }, ); // Convert the LangGraph stream to UI message stream return createUIMessageStreamResponse({ stream: toUIMessageStream(stream), }); } ``` ## Example: Streaming with streamEvents LangChain's [`streamEvents()`](https://docs.langchain.com/oss/javascript/langchain/streaming) method provides granular, semantic events with metadata. This is useful for debugging, observability, and migrating existing LCEL applications: ```tsx title="app/api/stream-events/route.ts" import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain'; import { ChatOpenAI } from '@langchain/openai'; import { createUIMessageStreamResponse, UIMessage } from 'ai'; export const maxDuration = 30; const model = new ChatOpenAI({ model: 'gpt-4.1-mini', temperature: 0, }); export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const langchainMessages = await toBaseMessages(messages); // Use streamEvents() for granular event streaming // Produces events like on_chat_model_stream, on_tool_start, on_tool_end const streamEvents = model.streamEvents(langchainMessages, { version: 'v2', }); // The adapter automatically detects and handles streamEvents format return createUIMessageStreamResponse({ stream: toUIMessageStream(streamEvents), }); } ``` **When to use `streamEvents()` vs `graph.stream()`:** - **`streamEvents()`**: Best for debugging, observability, filtering by event type, agents created with `createAgent`, and migrating existing LCEL applications that rely on callbacks - **`graph.stream()` with `streamMode`**: Best for LangGraph applications where you need structured state updates via `values`, `messages`, `tools`, or `custom` modes ## Example: Custom Data Streaming LangChain tools can emit custom data events using `config.writer()`. The adapter converts these to typed `data-{type}` parts that can be rendered in the UI or handled via the `onData` callback: ```tsx title="app/api/custom-data/route.ts" import { createUIMessageStreamResponse, UIMessage } from 'ai'; import { createAgent, tool, type ToolRuntime } from 'langchain'; import { ChatOpenAI } from '@langchain/openai'; import { toBaseMessages, toUIMessageStream } from '@ai-sdk/langchain'; import { z } from 'zod'; export const maxDuration = 60; const model = new ChatOpenAI({ model: 'gpt-6-luna' }); // Tool that emits progress updates during execution const analyzeDataTool = tool( async ({ dataSource, analysisType }, config: ToolRuntime) => { const steps = ['connecting', 'fetching', 'processing', 'generating']; for (let i = 0; i < steps.length; i++) { // Emit progress event - becomes 'data-progress' in the UI // Include 'id' to persist in message.parts for rendering config.writer?.({ type: 'progress', id: `analysis-${Date.now()}`, step: steps[i], message: `${steps[i]}...`, progress: Math.round(((i + 1) / steps.length) * 100), }); await new Promise(resolve => setTimeout(resolve, 500)); } // Emit completion status config.writer?.({ type: 'status', id: `status-${Date.now()}`, status: 'complete', message: 'Analysis finished', }); return JSON.stringify({ result: 'Analysis complete', confidence: 0.94 }); }, { name: 'analyze_data', description: 'Analyze data with progress updates', schema: z.object({ dataSource: z.enum(['sales', 'inventory', 'customers']), analysisType: z.enum(['trends', 'anomalies', 'summary']), }), }, ); const agent = createAgent({ model, tools: [analyzeDataTool], }); export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const langchainMessages = await toBaseMessages(messages); // Enable 'custom' stream mode to receive custom data events const stream = await agent.stream( { messages: langchainMessages }, { streamMode: ['values', 'messages', 'custom'] }, ); return createUIMessageStreamResponse({ stream: toUIMessageStream(stream), }); } ``` Handle custom data on the client with the `onData` callback or render persistent data parts: ```tsx title="app/page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; export default function Chat() { const { messages, sendMessage } = useChat({ onData: dataPart => { // Handle transient data events (without 'id') console.log('Received:', dataPart.type, dataPart.data); }, }); return (
{messages.map(m => (
{m.parts.map((part, i) => { if (part.type === 'text') { return {part.text}; } // Render persistent custom data parts (with 'id') if (part.type === 'data-progress') { return (
Progress: {part.data.progress}% - {part.data.message}
); } if (part.type === 'data-status') { return
Status: {part.data.message}
; } return null; })}
))}
); } ``` **Custom data behavior:** - Data with an `id` field is **persistent** (added to `message.parts` for rendering) - Data without an `id` is **transient** (only delivered via the `onData` callback) - The `type` field determines the event name: `{ type: 'progress' }` → `data-progress` ## Example: LangSmith Deployment Transport Connect directly to a LangGraph deployment from the browser using `LangSmithDeploymentTransport`, bypassing the need for a backend API route: ```tsx title="app/langsmith/page.tsx" 'use client'; import { useChat } from '@ai-sdk/react'; import { LangSmithDeploymentTransport } from '@ai-sdk/langchain'; import { useMemo } from 'react'; export default function LangSmithChat() { const transport = useMemo( () => new LangSmithDeploymentTransport({ // Local development server url: 'http://localhost:2024', // Or for LangSmith deployment: // url: 'https://your-deployment.us.langgraph.app', // apiKey: process.env.NEXT_PUBLIC_LANGSMITH_API_KEY, }), [], ); const { messages, sendMessage, status } = useChat({ transport, }); return (
{messages.map(m => (
{m.parts.map((part, i) => part.type === 'text' ? {part.text} : null, )}
))}
{ e.preventDefault(); const input = e.currentTarget.elements.namedItem( 'message', ) as HTMLInputElement; sendMessage({ text: input.value }); input.value = ''; }} >
); } ``` The `LangSmithDeploymentTransport` constructor accepts the following options: - `url`: The LangSmith deployment URL or local server URL (required) - `apiKey`: API key for authentication (optional for local development) - `graphId`: The ID of the graph to connect to (defaults to `'agent'`) ## API Reference ### `toBaseMessages(messages)` Converts AI SDK `UIMessage` objects to LangChain `BaseMessage` objects. ```ts import { toBaseMessages } from '@ai-sdk/langchain'; const langchainMessages = await toBaseMessages(uiMessages); ``` **Parameters:** - `messages`: `UIMessage[]` - Array of AI SDK UI messages **Returns:** `Promise` ### `convertModelMessages(modelMessages)` Converts AI SDK `ModelMessage` objects to LangChain `BaseMessage` objects. Useful when you already have model messages from `convertToModelMessages`. ```ts import { convertModelMessages } from '@ai-sdk/langchain'; const langchainMessages = convertModelMessages(modelMessages); ``` **Parameters:** - `modelMessages`: `ModelMessage[]` - Array of model messages **Returns:** `BaseMessage[]` ### `toUIMessageStream(stream, options?)` Converts a LangChain/LangGraph stream to an AI SDK `UIMessageStream`. Automatically detects the stream type and handles direct model streams, LangGraph streams, and `streamEvents()` output. ```ts import { toUIMessageStream } from '@ai-sdk/langchain'; import { createUIMessageStreamResponse } from 'ai'; // Works with direct model streams const modelStream = await model.stream(messages); return createUIMessageStreamResponse({ stream: toUIMessageStream(modelStream), }); // Works with LangGraph streams const graphStream = await graph.stream( { messages }, { streamMode: ['values', 'messages', 'tools'] }, ); return createUIMessageStreamResponse({ stream: toUIMessageStream(graphStream), }); // Works with streamEvents() output const streamEvents = model.streamEvents(messages, { version: 'v2' }); return createUIMessageStreamResponse({ stream: toUIMessageStream(streamEvents), }); ``` **Parameters:** - `stream`: `AsyncIterable | ReadableStream` - LangChain model stream, LangGraph stream, or `streamEvents()` output - `options`: `ToUIMessageStreamOptions` (optional) - `sendStart`: Whether to emit the outer `start` chunk. Defaults to `true`. - `sendFinish`: Whether to emit the outer `finish` chunk. Defaults to `true`. - `onStart`, `onToken`, `onText`, `onFinal`, `onFinish`, `onError`, and `onAbort`: Optional stream lifecycle callbacks. **Returns:** `ReadableStream` #### Composing into a caller-owned stream By default, `toUIMessageStream` emits outer `start` and `finish` chunks. When the caller owns the message lifecycle, suppress those chunks to avoid duplicate boundaries. All converted text, reasoning, tool, data, and step chunks remain unchanged. ```ts import { toUIMessageStream } from '@ai-sdk/langchain'; import { createUIMessageStream } from 'ai'; const stream = createUIMessageStream({ async execute({ writer }) { writer.write({ type: 'start' }); const reader = toUIMessageStream(langchainStream, { sendStart: false, sendFinish: false, }).getReader(); while (true) { const { done, value: chunk } = await reader.read(); if (done) break; writer.write(chunk); } writer.write({ type: 'finish' }); }, }); ``` Set either option independently when the caller owns only one lifecycle boundary. If a boundary is suppressed, the caller is responsible for supplying it when the final stream protocol requires it. ### `LangSmithDeploymentTransport` A `ChatTransport` implementation for LangSmith/LangGraph deployments. Use this with the `useChat` hook's `transport` option. ```ts import { LangSmithDeploymentTransport } from '@ai-sdk/langchain'; import { useChat } from '@ai-sdk/react'; import { useMemo } from 'react'; const transport = useMemo( () => new LangSmithDeploymentTransport({ url: 'https://your-deployment.us.langgraph.app', apiKey: 'your-api-key', }), [], ); const { messages, sendMessage } = useChat({ transport, }); ``` **Constructor Parameters:** - `options`: `LangSmithDeploymentTransportOptions` - `url`: `string` - LangSmith deployment URL or local server URL (required) - `apiKey?`: `string` - API key for authentication (optional) - `graphId?`: `string` - The ID of the graph to connect to (defaults to `'agent'`) **Implements:** `ChatTransport` ## More Examples You can find additional examples in the AI SDK [examples/next-langchain](https://github.com/vercel/ai/tree/main/examples/next-langchain) folder. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: LlamaIndex description: Learn how to use LlamaIndex with the AI SDK. url: "https://ai-sdk.dev/providers/adapters/llamaindex" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [LlamaIndex](https://ts.llamaindex.ai/) is a framework for building LLM-powered applications. LlamaIndex helps you ingest, structure, and access private or domain-specific data. LlamaIndex.TS supports modern JavaScript runtimes such as Node.js, Deno, and Bun. ## Example: Completion Here is a basic example that uses both AI SDK and LlamaIndex together with the [Next.js](https://nextjs.org/docs) App Router. The AI SDK `@ai-sdk/llamaindex` package uses the stream result from calling the `chat` method on a [LlamaIndex ChatEngine](https://ts.llamaindex.ai/modules/chat_engine) or the `query` method on a [LlamaIndex QueryEngine](https://ts.llamaindex.ai/modules/query_engines) to pipe text to the client. ```tsx title="app/api/completion/route.ts" {18-20} import { OpenAI, SimpleChatEngine } from 'llamaindex'; import { toUIMessageStream } from '@ai-sdk/llamaindex'; import { createUIMessageStreamResponse } from 'ai'; export const maxDuration = 60; export async function POST(req: Request) { const { prompt } = await req.json(); const llm = new OpenAI({ model: 'gpt-6-astra' }); const chatEngine = new SimpleChatEngine({ llm }); const stream = await chatEngine.chat({ message: prompt, stream: true, }); return createUIMessageStreamResponse({ stream: toUIMessageStream(stream), }); } ``` Then, we use the AI SDK's [`useCompletion`](/docs/ai-sdk-ui/completion) method in the page component to handle the completion: ```tsx title="app/page.tsx" 'use client'; import { useCompletion } from '@ai-sdk/react'; export default function Chat() { const { completion, input, handleInputChange, handleSubmit } = useCompletion(); return (
{completion}
); } ``` ## More Examples [create-llama](https://github.com/run-llama/create-llama) is the easiest way to get started with LlamaIndex. It uses the AI SDK to connect to LlamaIndex in all its generated code. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Agent Client Protocol description: Learn how to use Agent Client Protocol implementations through the AI SDK harness adapter. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/acp" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The Agent Client Protocol (ACP) harness adapter connects `HarnessAgent` to any harness compatible with ACP version 1. The generic adapter owns the bridge, ACP client, host-tool relay, event translation, approvals, and lifecycle behavior; the inline profile describes how to install and configure one ACP runtime. All you need in addition to the ACP harness adapter is an ACP-compatible implementation that can be installed from NPM or with a trusted Bash command. You can then configure the ACP harness adapter with a few lines of code. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## Setup ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-acp @ai-sdk/sandbox-vercel ``` ## Basic Usage Define an ACP harness profile, such as one of the [complete implementations](#complete-acp-harness-implementations) below, and pass it to `HarnessAgent`: ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; import { codexACPHarness } from './codex-acp-harness'; const agent = new HarnessAgent({ harness: codexACPHarness, }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); try { const result = await agent.generate({ session, prompt: 'Check the test failures and fix the production code.', }); console.log(result.text); } finally { await session.destroy(); await sandboxSession.destroy(); } ``` ## Adapter Settings `createACP` accepts: - `harnessId`: stable kebab-case identity for this profile. - `version`: ACP protocol version. It defaults to and currently supports only `'v1'`. - `source`: how to acquire the ACP agent, as a locked NPM installation or a trusted Bash install command. - `executable`: the bare command name to launch from the acquired agent. - `args`: optional arguments passed to the executable. - `forwardEnv`: non-credential host environment variable names to forward into the sandbox. - `credentialEnv`: host credential environment variable names. Configure this together with `credentialBrokering`. - `credentialBrokering`: function that receives the effective host runtime environment as `env` and the environment forwarded to the sandbox as `sandboxEnv`, then returns additive outbound request transformations. Use the credential from `sandboxEnv` in exact request header matches and the real credential from `env` in transformed request headers. - `credentialForwarding`: optional synchronous or asynchronous callback that customizes each credential immediately before the harness adapter forwards it into a sandbox process. It receives the credential value that would otherwise be forwarded (either the real credential or a masked value) and the environment variable name used to expose it. This callback only controls the value forwarded into the sandbox process. It does not restrict which credentials the harness adapter can discover, read, or otherwise access in the host process. - `env`: persistent environment values written at bootstrap. - `builtinTools`: optional native tool definitions for static typing and exact name matching. - `mcpServers`: MCP server definitions keyed by server name. - `hostToolMcpTransport`: transport used for the harness-owned MCP server that exposes host tools to the ACP implementation. It defaults to `'stdio'`. Set this to `'http'` for implementations that only accept HTTP or SSE MCP servers from the client, which requires the implementation to advertise `agentCapabilities.mcpCapabilities.http`. - `authentication`: advertised ACP authentication method, metadata, and client capabilities. - `nonTextContentTypes`: non-text image content supported across ACP prompts and tool model output. Currently accepts `['image']`. Image prompts are supported when the agent advertises `agentCapabilities.promptCapabilities.image` or this setting includes `'image'`; the setting can opt in when that capability flag is absent or incorrect. Image content from tool `toModelOutput()` callbacks is delivered only when this setting includes `'image'`; otherwise it is omitted with a warning. Defaults to an empty list. - `auth`: downstream provider authentication mode: - `auto` (default): use AI Gateway when Gateway credentials and a `providerAuthentication` configuration are available; otherwise use direct authentication. - `direct`: always use direct authentication, even when Gateway credentials are available. - `ai-gateway`: always use AI Gateway and throw when Gateway credentials are unavailable. This requires `providerAuthentication` configuration. - An authentication environment: auto-detect from the supplied record instead of the host process environment. - `providerAuthentication`: declarative runtime-specific Gateway environment. - `modelMapping`: required static mapping from the `model` supplied to `HarnessAgent` to the ACP operation used by the implementation. Use `session-config-option` with its configuration option ID as `path`, or `session-model` with the `session/set_model` request property as `path`. - `skillsDirectory`: native skills directory relative to the implementation's effective `$HOME`. It defaults to `.agents/skills`; override it for runtimes that use a different location. - `instructionMapping`: optional mapping from `HarnessAgent` instructions to an implementation's native system or developer prompt. Use `session-meta` for a path below the ACP session request's `_meta` field, `launch-env-json` for a path within a JSON launch environment variable, or `filesystem` with a relative `path` to write instructions to a file under the implementation's effective `$HOME`. When omitted, instructions are prepended to the first user prompt. - `outputSchemaMapping`: optional implementation-specific mapping from a structured output JSON Schema to a path below the ACP `session/prompt` request's `_meta` field. ACP does not standardize structured output, so omit this unless the selected implementation documents that private extension. - `askUserQuestions`: optional translation between an implementation-specific ACP client request for questions and the Harness `askUserQuestions` tool. Configure it only when the ACP implementation exposes a native request that waits for the client's response. - `permissionModeMapping`: mappings from all three Harness permission modes to advertised ACP session modes or configuration options. Set an entry to `null` when the ACP implementation does not support that mode. When omitted, the adapter applies `permissionMode` to ACP permission requests by tool kind. - `session.meta`: serializable implementation-specific metadata for session creation and restoration. - `port`: exposed bridge port override. - `startupTimeoutMs`: bridge startup timeout. The default is 120 seconds. - `reconnect`: reconnect timing after an established bridge WebSocket connection drops. `maxElapsedMs` controls the total retry window, including connection establishment and backoff delays, and defaults to 30 seconds. `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to 2 seconds. These retries use exponential backoff and are separate from `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or bridge endpoint is permanently unavailable. - `mintBridgeToken`: synchronous function that receives the sandbox id and returns the bridge authentication token. By default, the adapter generates a random 32-byte token. Custom implementations must return a suitably secret token. - `clientApp`: optional client attribution with `name` and `version`. It defaults to the installed `ai-sdk-harness-acp/` identity and is available to Gateway environment placeholders. Runtime-specific package names, environment variables, modes, and session metadata belong in the inline profile, not in the generic adapter. ## Structured Output ACP version 1 does not define a structured output capability. Profiles for implementations with a private prompt metadata extension can opt in explicitly: ```ts const harness = createACP({ harnessId: 'example-acp', source: { type: 'install-command', command: 'curl -fsSL https://example.com/install.sh | bash', }, executable: 'example-acp', modelMapping: { type: 'session-config-option', path: 'model', }, outputSchemaMapping: { type: 'session-prompt-meta', path: ['outputSchema'], }, }); ``` For a `HarnessAgent` configured with `output`, this writes the generated JSON Schema to `session/prompt.params._meta.outputSchema`. A profile without this mapping throws `HarnessCapabilityUnsupportedError` instead of assuming that an arbitrary ACP implementation understands the schema; Codex ACP is unsupported, while the Grok Build harness includes its verified mapping. ### Agent source A locked source freezes the complete installation. Provide the contents of a `package.json` and its `pnpm-lock.yaml`: ```ts const source = { type: 'npm-locked', packageJson: packageJsonContents, pnpmLockYaml: pnpmLockYamlContents, pnpmWorkspaceYaml: pnpmWorkspaceYamlContents, } as const; ``` A locked source installs the supplied files with `pnpm install --frozen-lockfile`. `pnpmWorkspaceYaml` is optional; provide it when the locked installation needs workspace-level pnpm configuration, such as an exact-version `allowBuilds` policy for a required dependency build script. The locked `package.json` must also declare every dependency of the `@ai-sdk/harness-acp` bridge manifest (`dist/bridge/package.json`) at the same versions. The manifest, lockfile, and optional workspace configuration are persisted bootstrap artifacts and participate in lifecycle identity. Do not include credentials in these strings. An install command source runs a trusted Bash command: ```ts const cursorSource = { type: 'install-command', command: 'curl https://cursor.com/install -fsS | bash', } as const; const fxSource = { type: 'install-command', command: 'curl -fsSL https://fx.sh/setup.sh | bash', } as const; ``` The command runs with `set -euo pipefail` and a private `HOME`. All persistent installation files must remain below that `HOME`. The command must install `executable` into `$HOME/.local/bin`; the adapter launches that deterministic path without a shell and does not fall back to a command already present in the sandbox. Install commands do not provide automatic version pinning. A successful bootstrap remains cached, and the exact command participates in bootstrap and lifecycle identity. Treat the command as executable configuration and do not embed credentials in it because bootstrap recipes are persisted. ## Authentication AI Gateway authentication is supported when `providerAuthentication` defines how the underlying ACP implementation accepts Gateway configuration. The adapter reads these environment variables: - `VERCEL_OIDC_TOKEN` - `AI_GATEWAY_API_KEY` - `AI_GATEWAY_BASE_URL` With the default `auth: 'auto'`, the adapter uses AI Gateway when `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` is available and otherwise uses direct authentication. Applications that resolve credentials at runtime can pass them without mutating `process.env`: ```ts const harness = createACP({ // ...implementation settings auth: { AI_GATEWAY_API_KEY: await resolveGatewayToken(), AI_GATEWAY_BASE_URL: 'https://ai-gateway.vercel.sh', }, }); ``` The supplied record replaces the host environment for authentication discovery. `credentialEnv` reads from this record while `forwardEnv` continues to read non-authentication runtime configuration from the host. Credential values are excluded from bootstrap and lifecycle identity. For direct authentication, configure `credentialEnv` with the host environment variable names that the underlying ACP implementation reads, and use `credentialBrokering` to map their values to outbound request headers. When the network sandbox supports additive request transformations, the ACP process sees the values from `sandboxEnv`, which are ephemeral placeholders by default. The real values are injected after requests leave the sandbox, and only when the request contains the exact expected value. Sandboxes without that capability retain the legacy behavior of forwarding the real values. Use `forwardEnv` only for non-credential runtime configuration. ### Gateway environment Set `providerAuthentication.gateway.env` to the environment variables that the underlying ACP implementation uses for its provider endpoint, credentials, and attribution. Profile values such as `gateway-api-key`, `gateway-base-url`, `gateway-authorization`, `client-app`, `client-app-name`, and `client-app-version` are placeholders that the adapter resolves only after Gateway authentication has been selected. The three client-app placeholders resolve to the combined `name/version` identifier, its name, and its version, respectively. Structured launch environment values are serialized as JSON after their placeholders are resolved. The resolved Gateway environment overrides same-name direct values in the environment passed to `credentialBrokering`, so one callback works for both authentication modes. ## Sandbox Every ACP harness requires a network sandbox with at least one exposed port, e.g. `@ai-sdk/sandbox-vercel`: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); ``` By default, the adapter uses the first exposed port. Set `port` on `createACP` to select another exposed port. If neither is available, session startup throws `HarnessCapabilityUnsupportedError` with instructions to configure one. Bridge packages, ACP installations, and replay state live in adapter-owned directories outside the session project. Skills are written to the configured native directory below the implementation's `$HOME`. The session workspace starts empty. ## Tools and Approvals Optional `builtinTools` definitions classify native ACP tools only when the agent supplies a programmatic name matching the tool key or `nativeName`. Unmatched native and third-party MCP calls remain valid provider-executed dynamic tools. Host-defined AI SDK tools are exposed through a harness-owned MCP server and execute in the host. Their recursive JSON Schemas are preserved. A tool without `execute` pauses for a caller-supplied result through `continueStream`. Map all three Harness permission modes when the ACP implementation exposes native permission controls, and set unsupported modes to `null`. Selecting a `null` mapping throws `HarnessCapabilityUnsupportedError`. The adapter validates concrete modes or config options against the session response. When a mapping is configured, any ACP permission request that still occurs becomes a Harness approval request. Without a mapping, the adapter automatically selects `allow_once` for tool kinds allowed by `permissionMode` and requests Harness approval for the rest. A rejection selects `reject_once`; persistent choices are never inferred. Host-tool approval remains independent. ## Complete ACP Harness Implementations Unless noted otherwise, these profiles support direct authentication through the runtime-specific environment variables shown below and AI Gateway authentication through `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN`. ### Codex ACP Codex ACP accepts an API key and JSON session configuration through its launch environment. Its OpenAI-compatible endpoint must end in `/v1`: ```ts import { readFileSync } from 'node:fs'; import { createACP, type ACPPermissionModeMapping } from '@ai-sdk/harness-acp'; import { createCredentialRequestTransformation } from '@ai-sdk/harness/utils'; import { secureJsonParse } from '@ai-sdk/provider-utils'; export const codexACPHarness = createACP({ harnessId: 'acp-codex', nonTextContentTypes: ['image'], // Define the runtime's built-in tool names and input schemas to expose // provider-executed calls as typed HarnessAgent tools. // builtinTools: { ... }, source: { type: 'npm-locked', packageJson: readFileSync( new URL('./codex-acp/package.json', import.meta.url), 'utf8', ), pnpmLockYaml: readFileSync( new URL('./codex-acp/pnpm-lock.yaml', import.meta.url), 'utf8', ), }, executable: 'codex-acp', modelMapping: { type: 'session-config-option', path: 'model', }, forwardEnv: ['CODEX_CONFIG'], credentialEnv: ['CODEX_API_KEY', 'OPENAI_API_KEY'], credentialBrokering: ({ env, sandboxEnv }) => { const environmentVariableName = env.CODEX_API_KEY ? 'CODEX_API_KEY' : 'OPENAI_API_KEY'; const credential = env[environmentVariableName]; const sandboxCredential = sandboxEnv?.[environmentVariableName]; if (!credential || !sandboxCredential) return []; const config = env.CODEX_CONFIG == null ? undefined : (secureJsonParse(env.CODEX_CONFIG) as { model_provider?: string; model_providers?: Record; }); const baseUrl = config?.model_providers?.[config.model_provider ?? '']?.base_url ?? 'https://api.openai.com/v1'; return [ createCredentialRequestTransformation({ matchUrl: baseUrl, matchHeaders: { Authorization: `Bearer ${sandboxCredential}`, }, transformHeaders: { Authorization: `Bearer ${credential}` }, }), ]; }, instructionMapping: { type: 'launch-env-json', variable: 'CODEX_CONFIG', path: ['developer_instructions'], }, permissionModeMapping: { 'allow-reads': null, 'allow-edits': null, 'allow-all': { type: 'session-mode', modeId: 'agent-full-access' }, } as const satisfies ACPPermissionModeMapping, authentication: { methodId: 'api-key', }, providerAuthentication: { gateway: { env: { CODEX_API_KEY: { $source: 'gateway-api-key' }, CODEX_CONFIG: { model: 'openai/gpt-5.6-sol', model_provider: 'ai_gateway', model_providers: { ai_gateway: { name: 'AI Gateway', base_url: { $source: 'gateway-base-url', ensureSuffix: '/v1', }, env_key: 'CODEX_API_KEY', wire_api: 'responses', supports_websockets: false, http_headers: { 'User-Agent': { $source: 'client-app' }, 'x-client-app': { $source: 'client-app' }, }, }, }, model_supports_reasoning_summaries: true, preferred_auth_method: 'apikey', }, }, }, }, }); ``` Codex ACP supports only `permissionMode: 'allow-all'`. Its restrictive session modes enable Codex's internal sandbox, which must not run inside the sandbox already provided to `HarnessAgent`. The `client-app` placeholder resolves to the versioned `ai-sdk-harness-acp/` identifier and sends it as both `User-Agent` and `x-client-app`. Without Gateway credentials, this profile uses direct `CODEX_API_KEY` or `OPENAI_API_KEY` authentication. Gateway `env` values are added to the ACP process only when Gateway is selected, so direct runtime configuration remains unchanged. ### Cursor ACP Cursor exposes ACP through `agent acp` and installs its CLI with a Bash installer rather than an NPM package: ```ts import { createACP } from '@ai-sdk/harness-acp'; export const cursorACPHarness = createACP({ harnessId: 'cursor-acp', nonTextContentTypes: ['image'], source: { type: 'install-command', command: 'curl https://cursor.com/install -fsS | bash', }, executable: 'agent', args: ['--disable-auto-update', 'acp'], modelMapping: { type: 'session-config-option', path: 'model', }, clientCapabilities: { _meta: { parameterizedModelPicker: true }, }, credentialEnv: ['CURSOR_API_KEY'], credentialBrokering: ({ env, sandboxEnv }) => { if (!env.CURSOR_API_KEY || !sandboxEnv?.CURSOR_API_KEY) return []; return [ { match: { host: 'api2.cursor.sh', path: { exact: '/auth/exchange_user_api_key' }, method: ['POST'], headers: [ { key: { exact: 'Authorization' }, value: { exact: `Bearer ${sandboxEnv.CURSOR_API_KEY}` }, }, ], }, transform: { headers: { Authorization: `Bearer ${env.CURSOR_API_KEY}`, }, }, }, ]; }, }); ``` The update flag prevents the cached installation from changing itself at runtime. Credential brokering is limited to Cursor's initial API-key exchange, so subsequent requests keep the short-lived Cursor access token returned by that exchange. Cursor supports AI Gateway. Configure Cursor's OpenAI API key with an AI Gateway credential and set **Override OpenAI Base URL** to `https://ai-gateway.vercel.sh/cursor/v1`. The regular Cursor CLI continues to authenticate to Cursor with `CURSOR_API_KEY`; that login is separate from the model provider and routing configured in the Cursor account. ### Grok Build ACP Grok Build exposes ACP directly through `grok agent stdio`. It does not advertise ACP session modes for its permission behavior, so this profile omits `permissionModeMapping`: ```ts import { createACP } from '@ai-sdk/harness-acp'; import { createCredentialRequestTransformation } from '@ai-sdk/harness/utils'; export const grokBuildACPHarness = createACP({ harnessId: 'acp-grok-build', // Define the runtime's built-in tool names and input schemas to expose // provider-executed calls as typed HarnessAgent tools. // builtinTools: { ... }, source: { type: 'install-command', command: 'curl -fsSL https://x.ai/cli/install.sh | GROK_BIN_DIR="$HOME/.local/bin" bash', }, executable: 'grok', args: ['agent', 'stdio'], modelMapping: { type: 'session-model', path: 'modelId', }, credentialEnv: ['XAI_API_KEY'], credentialBrokering: ({ env, sandboxEnv }) => { if (!env.XAI_API_KEY || !sandboxEnv?.XAI_API_KEY) return []; return [ createCredentialRequestTransformation({ matchUrl: env.GROK_XAI_API_BASE_URL ?? 'https://api.x.ai/v1', matchHeaders: { Authorization: `Bearer ${sandboxEnv.XAI_API_KEY}`, }, transformHeaders: { Authorization: `Bearer ${env.XAI_API_KEY}`, }, }), ]; }, instructionMapping: { type: 'filesystem', path: '.grok/AGENTS.md', }, providerAuthentication: { gateway: { env: { GROK_CLIENT_NAME: { $source: 'client-app-name' }, GROK_CLIENT_VERSION: { $source: 'client-app-version' }, XAI_API_KEY: { $source: 'gateway-api-key' }, GROK_XAI_API_BASE_URL: { $source: 'gateway-base-url', ensureSuffix: '/v1', }, GROK_MODELS_BASE_URL: { $source: 'gateway-base-url', ensureSuffix: '/v1', }, }, }, }, }); ``` Without Gateway credentials, this profile uses direct `XAI_API_KEY` authentication. The Gateway environment supplies client name and version attribution. Grok Build handles its built-in safe operations internally. For permission requests sent through ACP, the adapter applies the configured Harness `permissionMode` by tool kind and requests explicit approval for the rest. ## Known Limitations - ACP v1 does not expose model-step boundaries or per-step usage. The adapter infers boundaries and reports unknown per-step usage; terminal prompt usage supplies the turn total. - Standard ACP v1 has no portable manual compaction or mid-turn steering API. - ACP v1 has no portable built-in tool filtering API. You can still use `activeTools` and `inactiveTools` to filter host-executed tools, but filtering ACP built-ins will throw. - A changed host-tool catalog requires the ACP implementation to refresh its MCP tool list. Implementations that retain stale tools fail explicitly. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Claude Code description: Learn how to use the Claude Code harness adapter. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/claude-code" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The Claude Code harness adapter connects `HarnessAgent` to Claude Code through `@anthropic-ai/claude-agent-sdk`. The adapter runs a bridge inside the sandbox and streams Claude Code events back to the host over a sandbox-exposed WebSocket. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## Setup ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-claude-code @ai-sdk/sandbox-vercel ``` The adapter bootstraps the Claude Code bridge dependencies inside the sandbox when the first session starts. ## Import ```ts import { claudeCode, createClaudeCode } from '@ai-sdk/harness-claude-code'; ``` `claudeCode` is equivalent to `createClaudeCode()` with its default configuration. ## Basic Usage ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { claudeCode } from '@ai-sdk/harness-claude-code'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const agent = new HarnessAgent({ harness: claudeCode, model: 'claude-sonnet-4-6', }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); let exitCode = 0; try { const result = await agent.stream({ session, prompt: 'Check the test failures and fix the production code.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); await sandboxSession.destroy(); process.exit(exitCode); } ``` To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for Vercel Sandbox, and one of the variables listed under [authentication](#authentication) for Claude Code. ## Adapter Settings Use `createClaudeCode()` to configure the runtime: ```ts const harness = createClaudeCode({ maxTurns: 10, env: { DEPLOYMENT_ENV: 'staging', }, thinking: { type: 'adaptive', display: 'summarized', }, }); ``` Settings: - `auth`: authentication mode (`auto`, `direct`, or `ai-gateway`) or an isolated authentication environment. - `credentialForwarding`: optional synchronous or asynchronous callback that customizes each credential immediately before the harness adapter forwards it into a sandbox process. It receives the credential value that would otherwise be forwarded (either the real credential or a masked value) and the environment variable name used to expose it. This callback only controls the value forwarded into the sandbox process. It does not restrict which credentials the harness adapter can discover, read, or otherwise access in the host process. - `mcpServers`: MCP server definitions keyed by server name. - `maxTurns`: maximum internal turns before yielding. - `agentProgressSummaries`: enables periodic AI-generated summaries for running sub-agents. Summaries are available on raw `task_progress` stream parts. - `forwardSubagentText`: forwards sub-agent text and thinking messages as raw stream parts, in addition to the tool activity forwarded by default. - `env`: environment variables for the Claude Code process. Values are merged over the sandbox bridge process environment and take precedence. - `thinking`: extended-thinking configuration. `type` can be `enabled`, `disabled`, or `adaptive`. For enabled or adaptive thinking, `display` can be `summarized` or `omitted`. Defaults to `{ type: 'adaptive', display: 'summarized' }`. - `port`: bridge port override. - `startupTimeoutMs`: maximum time to wait for the bridge to start. - `reconnect`: reconnect timing after an established bridge WebSocket connection drops. `maxElapsedMs` controls the total retry window, including connection establishment and backoff delays, and defaults to 30 seconds. `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to 2 seconds. These retries use exponential backoff and are separate from `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or bridge endpoint is permanently unavailable. - `mintBridgeToken`: synchronous function that receives the sandbox id and returns the bridge authentication token. By default, the adapter generates a random 32-byte token. Custom implementations must return a suitably secret token. ## Structured Output Claude Code supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output). The adapter passes the JSON Schema through the Agent SDK's native `outputFormat` option and returns its `structured_output` value as JSON text. ## Authentication The `auth` setting selects how credentials are resolved from the host environment: - `auto` (default): use AI Gateway credentials when available, then fall back to direct Anthropic credentials. - `direct`: use Anthropic credentials. - `ai-gateway`: use AI Gateway credentials. When the sandbox supports additive request transformations, the bridge receives placeholders and the adapter injects credentials into matching outbound requests. Sandboxes without that capability retain direct credential forwarding. Supported environment variables: - `VERCEL_OIDC_TOKEN` - `AI_GATEWAY_API_KEY` - `AI_GATEWAY_BASE_URL` - `ANTHROPIC_API_KEY` - `ANTHROPIC_AUTH_TOKEN` - `ANTHROPIC_BASE_URL` If no applicable credential environment variable is set, the adapter attempts to resolve a native subscription from the host system unless AI Gateway authentication is selected. Select a specific authentication mode when you do not want automatic detection: ```ts const directHarness = createClaudeCode({ auth: 'direct' }); const gatewayHarness = createClaudeCode({ auth: 'ai-gateway' }); ``` Pass an authentication environment to use programmatically resolved credentials without reading `process.env`: ```ts const harness = createClaudeCode({ auth: { ANTHROPIC_API_KEY: await resolveAnthropicToken() }, }); ``` The supplied record replaces the host environment for authentication discovery. Only recognized authentication variables are forwarded. ## Sandbox Claude Code requires a network sandbox with at least one exposed port, e.g. `@ai-sdk/sandbox-vercel`: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); ``` ## Built-in Tools The adapter exposes these common Claude Code built-ins through `agent.tools`: - `read` - `write` - `edit` - `bash` - `glob` - `grep` - `webSearch` Additional Claude Code built-ins may also appear in `agent.tools` when they do not fit a common tool shape. Claude Code supports built-in tool approval requests when `permissionMode` is `allow-reads` or `allow-edits`. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Cline description: Learn how to use the Cline harness adapter. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/cline" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The Cline harness adapter connects `HarnessAgent` to the [Cline SDK](https://docs.cline.bot/sdk/overview) agent runtime (`@cline/agents`). The runtime runs as an in-process Node library on the host — no bridge — while its built-in tools execute against the session sandbox, so the workspace the model reasons about lives entirely inside the sandbox. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## Setup ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-cline @ai-sdk/sandbox-vercel ``` The Cline runtime has no bootstrap step: nothing is installed inside the sandbox when a session starts. ## Import ```ts import { cline, createCline } from '@ai-sdk/harness-cline'; ``` `cline` is equivalent to `createCline()` with its default configuration. ## Basic Usage ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { cline } from '@ai-sdk/harness-cline'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const agent = new HarnessAgent({ harness: cline, model: 'anthropic/claude-opus-5', }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); let exitCode = 0; try { const result = await agent.stream({ session, prompt: 'Check the test failures and fix the production code.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); await sandboxSession.destroy(); process.exit(exitCode); } ``` To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for Vercel Sandbox. The same token authenticates model calls through AI Gateway when the default `auto` authentication mode is used. ## Adapter Settings Use `createCline()` to configure the runtime: ```ts const harness = createCline({ auth: 'direct', }); ``` Settings: - `auth`: authentication mode (`auto`, `direct`, or `ai-gateway`) or an isolated authentication environment. - `mcpServers`: MCP server definitions keyed by server name. - `providerId`: Cline LLM provider id (e.g. `anthropic`, `openai`, `gemini`). When omitted, direct authentication uses the Cline backend. Explicit custom providers apply only to direct authentication. - `apiKey`: provider API key. When omitted, the Cline gateway falls back to the configured provider's environment variable. - `baseUrl`: custom provider endpoint. - `headers`: extra headers sent to the provider. - `reasoningEffort`: reasoning effort for reasoning-capable models. Supports `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. `none` disables reasoning; every other value enables reasoning at that effort. When omitted, the Cline SDK selects the reasoning behavior. - `maxIterations`: safety cap on agent-loop iterations per turn. To provide additional operating guidance, use the `instructions` setting on `HarnessAgent`. The adapter appends it to Cline's system prompt. ## Structured Output Cline supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output) by requiring a terminal tool whose argument uses the requested JSON Schema. This requires a provider/model route with external tool support; `providerId: 'openai-codex-cli'` throws `HarnessCapabilityUnsupportedError`. ## Authentication The `auth` setting selects which credentials Cline reads from the host environment: - `auto` (default): use AI Gateway credentials when available, otherwise use direct Cline credentials. - `direct`: use `CLINE_API_KEY` and the optional `CLINE_API_BASE_URL`. - `ai-gateway`: use `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` and the optional `AI_GATEWAY_BASE_URL`. If no applicable credential environment variable is set, the adapter attempts to resolve a native subscription from the host system unless AI Gateway authentication is selected. ```ts const harness = createCline({ auth: 'ai-gateway' }); ``` Pass an authentication environment when the host resolves credentials at runtime: ```ts const harness = createCline({ auth: { AI_GATEWAY_API_KEY: await resolveGatewayToken(), AI_GATEWAY_BASE_URL: 'https://ai-gateway.vercel.sh', }, }); ``` The supplied record replaces the host environment for authentication discovery, so it can also select direct authentication with `CLINE_API_KEY`. `CLINE_API_BASE_URL` is Cline's backend root URL; the SDK's `/api/v1` provider path is appended to it. For AI Gateway, the adapter instead overrides Cline's in-process provider configuration with the Gateway credential and its `/v1` endpoint. Direct Cline credentials are never used as a fallback in explicit Gateway mode or when a supplied authentication environment selects Gateway. ## Sandbox The Cline runtime runs on the host and does not need sandbox ports, so any network sandbox works — including port-less configurations: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', template: await agent.getSandboxTemplate(), }); ``` All built-in tools operate on the sandbox filesystem through the sandbox session's file and exec surface. ## Built-in Tools The adapter exposes these built-ins through `agent.tools`: - `read` - `write` - `edit` - `bash` - `grep` - `glob` - `ls` All built-ins are sandbox-backed: `bash` runs in the session working directory, and relative file paths resolve against it. Cline supports built-in tool approval requests when `permissionMode` is `allow-reads` or `allow-edits`, native built-in tool filtering via `activeTools`/`inactiveTools`, and host-executed AI SDK tool approvals. ## Skills Harness-provided skills are written into the sandbox HOME (`~/.agents/skills//SKILL.md`) and advertised to the model through a system prompt section; the model loads a skill's full content with the `read` tool when the task calls for it. ## Session Lifecycle Conversation state lives in the host-process runtime. On `detach`/`stop` the adapter persists the conversation history into the sandbox workspace (`.cline-harness/history.json` in the session working directory), so a future process can resume the session after the caller reattaches the sandbox. Because the runtime is host-resident, suspended turns are rerun-continued from the persisted history rather than losslessly attached (same trade-off as the [Pi adapter](/providers/ai-sdk-harnesses/pi)). Manual compaction (`doCompact`) is not supported by the standalone Cline runtime and throws `HarnessCapabilityUnsupportedError`. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Codex description: Learn how to use the Codex harness adapter. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/codex" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The Codex harness adapter connects `HarnessAgent` to the Codex app-server. The adapter runs a bridge inside the sandbox and streams Codex thread events back to the host over a sandbox-exposed WebSocket. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## Setup ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-codex @ai-sdk/sandbox-vercel ``` The adapter bootstraps the Codex bridge dependencies inside the sandbox when the first session starts. ## Import ```ts import { codex, createCodex } from '@ai-sdk/harness-codex'; ``` `codex` is equivalent to `createCodex()` with its default configuration. ## Basic Usage ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { codex } from '@ai-sdk/harness-codex'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const agent = new HarnessAgent({ harness: codex, model: 'gpt-5.6-luna', }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); let exitCode = 0; try { const result = await agent.stream({ session, prompt: 'Check the test failures and fix the production code.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); await sandboxSession.destroy(); process.exit(exitCode); } ``` To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for Vercel Sandbox, and one of the variables listed under [authentication](#authentication) for Codex. ## Adapter Settings Use `createCodex()` to configure the runtime: ```ts const harness = createCodex({ reasoningEffort: 'high', webSearch: true, codexConfig: { model_verbosity: 'low', }, }); ``` Settings: - `auth`: authentication mode (`auto`, `direct`, or `ai-gateway`) or an isolated authentication environment. - `credentialForwarding`: optional synchronous or asynchronous callback that customizes each credential immediately before the harness adapter forwards it into a sandbox process. It receives the credential value that would otherwise be forwarded (either the real credential or a masked value) and the environment variable name used to expose it. This callback only controls the value forwarded into the sandbox process. It does not restrict which credentials the harness adapter can discover, read, or otherwise access in the host process. - `codexConfig`: additional native Codex configuration. Values pass through as provided, so use the snake\_case keys from Codex's `config.toml` reference. The adapter's managed values take precedence over conflicting entries. - `mcpServers`: MCP server definitions keyed by server name. - `reasoningEffort`: `low`, `medium`, `high`, `xhigh`, or `max`. - `webSearch`: allow live web search. - `port`: bridge port override. - `startupTimeoutMs`: maximum time to wait for the bridge to start. - `reconnect`: reconnect timing after an established bridge WebSocket connection drops. `maxElapsedMs` controls the total retry window, including connection establishment and backoff delays, and defaults to 30 seconds. `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to 2 seconds. These retries use exponential backoff and are separate from `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or bridge endpoint is permanently unavailable. - `mintBridgeToken`: synchronous function that receives the sandbox id and returns the bridge authentication token. By default, the adapter generates a random 32-byte token. Custom implementations must return a suitably secret token. ## Built-in Tool Filtering Codex supports `activeTools` and `inactiveTools` for `bash`, `webSearch`, `apply_patch`, and `view_image`. Host tools are filtered before they are sent to Codex. Native Codex configuration disables `bash`, `view_image`, and `webSearch` individually. When `bash`, `apply_patch`, and `view_image` are all inactive, the adapter disables Codex environments and explicitly disables `bash` and `view_image`. When `apply_patch` is inactive but `bash` or `view_image` remains active, the adapter installs a Codex `PreToolUse` hook that denies patch execution. Before starting each turn, the adapter checks that Codex loaded and trusted the hook and fails the turn if it did not. Including `webSearch` in `activeTools` does not enable it on its own; set `webSearch: true` on `createCodex()` to allow live search. ```ts const agent = new HarnessAgent({ harness: createCodex(), sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000] }), inactiveTools: ['apply_patch'], }); ``` ## Structured Output Codex supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output). The adapter passes the JSON Schema through the Codex app-server's `turn/start` `outputSchema` parameter. ## Authentication The `auth` setting selects how credentials are resolved from the host environment: - `auto` (default): use AI Gateway credentials when available, then fall back to direct OpenAI credentials. - `direct`: use OpenAI credentials. - `ai-gateway`: use AI Gateway credentials. When the sandbox supports additive request transformations, the bridge receives placeholders and the adapter injects credentials into matching outbound requests. Sandboxes without that capability retain direct credential forwarding. Supported environment variables: - `VERCEL_OIDC_TOKEN` - `AI_GATEWAY_API_KEY` - `AI_GATEWAY_BASE_URL` - `OPENAI_API_KEY` - `CODEX_API_KEY` - `OPENAI_BASE_URL` - `OPENAI_ORGANIZATION` - `OPENAI_PROJECT` If no applicable credential environment variable is set, the adapter attempts to resolve a native subscription from the host system unless AI Gateway authentication is selected. Select a specific authentication mode when you do not want automatic detection: ```ts const directHarness = createCodex({ auth: 'direct' }); const gatewayHarness = createCodex({ auth: 'ai-gateway' }); ``` Pass an authentication environment to use programmatically resolved credentials without reading `process.env`: ```ts const harness = createCodex({ auth: { OPENAI_API_KEY: await resolveOpenAIToken() }, }); ``` The supplied record replaces the host environment for authentication discovery. Only recognized authentication variables are forwarded. For OpenAI-compatible endpoints, select `direct` and set `OPENAI_BASE_URL`. ## Sandbox Codex requires a network sandbox with at least one exposed port, e.g. `@ai-sdk/sandbox-vercel`: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); ``` ## Built-in Tools The adapter exposes these Codex built-ins through `agent.tools`: - `bash` and `webSearch` use the common cross-harness tool names. - `apply_patch` accepts Codex's freeform patch string. - `view_image` accepts a local image `path`, with optional `detail` and `environment_id` fields. Codex file changes may also appear as dynamic `fileChange` tool parts because some Codex file mutations do not originate from a visible model-callable tool. ## Known Limitations Codex does not currently support built-in tool approval requests. Use `permissionMode: 'allow-all'` with this adapter. Host-executed AI SDK tool approvals still work. When the app-server must cold-resume a thread, Codex does not provide raw events for `apply_patch` and `view_image`, so those tool calls and results cannot be surfaced for that resumed thread. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Cursor description: Learn how to use the Cursor harness adapter. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/cursor" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The Cursor harness adapter connects `HarnessAgent` to [Cursor CLI](https://cursor.com/cli) through the Agent Client Protocol (ACP). The adapter delegates ACP sessions, streaming, tools, and lifecycle management to `@ai-sdk/harness-acp`. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## Setup ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-cursor @ai-sdk/sandbox-vercel ``` Create a Cursor user API key and set `CURSOR_API_KEY`. The key authenticates Cursor CLI in the sandbox regardless of how Cursor authenticates to the model provider. The adapter installs Cursor CLI inside the sandbox with the official Cursor install command when the first session starts. ## Import ```ts import { createCursor, cursor } from '@ai-sdk/harness-cursor'; ``` `cursor` is equivalent to `createCursor()` with its default configuration. ## Basic Usage ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { cursor } from '@ai-sdk/harness-cursor'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const agent = new HarnessAgent({ harness: cursor, model: 'gpt-5.6-luna', }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); try { const result = await agent.generate({ session, prompt: 'Inspect this project and summarize its purpose.', }); console.log(result.text); } finally { await session.destroy(); await sandboxSession.destroy(); } ``` To use this agent with Vercel Sandbox, provide `VERCEL_OIDC_TOKEN` and `CURSOR_API_KEY` in the host environment. ## Adapter Settings Use `createCursor()` to configure the runtime: ```ts const harness = createCursor({ auth: 'ai-gateway', port: 4001, startupTimeoutMs: 180_000, }); ``` Settings: - `auth`: accepts the shared ACP modes `auto`, `direct`, or `ai-gateway`, or an isolated authentication environment for `CURSOR_API_KEY`. This cannot change Cursor's provider authentication route. Explicit `direct` and `ai-gateway` values emit a configuration reminder; `auto` does not because it does not declare an expected route. - `credentialForwarding`: optional synchronous or asynchronous callback that customizes each credential immediately before the harness adapter forwards it into a sandbox process. It receives the credential value that would otherwise be forwarded (either the real credential or a masked value) and the environment variable name used to expose it. This callback only controls the value forwarded into the sandbox process. It does not restrict which credentials the harness adapter can discover, read, or otherwise access in the host process. - `mcpServers`: MCP server definitions keyed by server name. - `port`: ACP bridge port override. - `startupTimeoutMs`: maximum time to wait for the ACP bridge to start. - `reconnect`: reconnect timing after an established bridge WebSocket connection drops. `maxElapsedMs` controls the total retry window, including connection establishment and backoff delays, and defaults to 30 seconds. `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to 2 seconds. These retries use exponential backoff and are separate from `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or bridge endpoint is permanently unavailable. - `mintBridgeToken`: synchronous function that receives the sandbox id and returns the ACP bridge authentication token. By default, the adapter generates a random 32-byte token. Custom implementations must return a suitably secret token. The adapter fixes the install command and ACP launch command. These implementation details cannot be overridden through `createCursor()`. ## Authentication Cursor has two independent authentication layers: 1. `CURSOR_API_KEY` authenticates Cursor CLI to the Cursor account. The harness requires this key for every `auth` mode. 2. Cursor's account settings determine how Cursor authenticates to the model provider. The harness cannot read or change this setting. If no applicable credential environment variable is set, the adapter attempts to resolve a native subscription from the host system unless AI Gateway authentication is selected. For direct routing, configure the model provider in Cursor. For AI Gateway, configure Cursor's OpenAI API key with an AI Gateway API key and set **Override OpenAI Base URL** to `https://ai-gateway.vercel.sh/cursor/v1` in Cursor settings. Cursor resolves that credential from its account configuration; the harness does not use `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` for model-provider authentication. Explicit `direct` and `ai-gateway` values emit a warning that the selected route must be configured in Cursor. `auto` is accepted without a warning: ```ts const automaticHarness = createCursor({ auth: 'auto' }); const directHarness = createCursor({ auth: 'direct' }); const gatewayHarness = createCursor({ auth: 'ai-gateway' }); ``` Supply a programmatically resolved Cursor account credential without mutating or reading `process.env`: ```ts const harness = createCursor({ auth: { CURSOR_API_KEY: await resolveCursorToken() }, }); ``` This record configures Cursor CLI authentication only. Cursor's account settings still control model-provider routing. ## Sandbox Cursor runs inside the sandbox through `@ai-sdk/harness-acp`. It requires a network sandbox with at least one exposed port, such as `@ai-sdk/sandbox-vercel`: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); ``` The first session requires network egress to run Cursor's official installer. ## Built-in Tools The adapter maps Cursor's terminal, glob, and grep tools to the common `bash`, `glob`, and `grep` tools. It also exposes Cursor's remaining built-ins under stable names, including `read`, `edit`, `ls`, `semanticSearch`, `webSearch`, `task`, and the Cursor planning, MCP, browser, and environment tools. Cursor host-tool calls use ACP MCP transport. The adapter recognizes Cursor's MCP payload and correlates the call with the host-side tool execution. ## Known Limitations - Cursor's model-provider authentication route must be configured in Cursor. The `auth` adapter setting cannot switch it programmatically. - ACP v1 does not expose model-step boundaries or per-step usage. The adapter infers boundaries and reports unknown per-step usage when Cursor does not provide totals. - ACP v1 has no portable manual compaction or mid-turn steering API. - ACP v1 has no portable built-in tool filtering API. Filtering host tools is supported, but filtering Cursor built-ins throws an unsupported-capability error. - Cursor does not currently support built-in tool approval requests. Use `permissionMode: 'allow-all'` with this adapter. Host-executed AI SDK tool approvals still work. - Cursor ACP does not expose a structured-output metadata mapping, so schema-backed structured output is unsupported. - Custom `headers` are not natively supported and only applied via sandbox-external request transformations. When a sandbox without that capability is provided, custom `headers` therefore cannot be passed and are ignored. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) - [Agent Client Protocol](/providers/ai-sdk-harnesses/acp) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Deep Agents description: Learn how to use the Deep Agents harness adapter. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/deepagents" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The Deep Agents harness adapter connects `HarnessAgent` to [Deep Agents](https://github.com/langchain-ai/deepagentsjs), a LangGraph-based agent runtime. The adapter runs a Node bridge inside the sandbox that drives the `deepagents` package and streams its `streamEvents` output back to the host over a sandbox-exposed WebSocket. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## Setup ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-deepagents @ai-sdk/sandbox-vercel ``` The adapter bootstraps the bridge's Node dependencies (the `deepagents` package and LangChain) inside the sandbox via `pnpm` when the first session starts. ## Import ```ts import { deepAgents, createDeepAgents } from '@ai-sdk/harness-deepagents'; ``` `deepAgents` is equivalent to `createDeepAgents()` with its default configuration. ## Basic Usage ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { deepAgents } from '@ai-sdk/harness-deepagents'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const agent = new HarnessAgent({ harness: deepAgents, model: 'anthropic/claude-sonnet-4-6', }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); let exitCode = 0; try { const result = await agent.stream({ session, prompt: 'Analyze this codebase and suggest improvements.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); await sandboxSession.destroy(); process.exit(exitCode); } ``` To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for Vercel Sandbox, and one of the variables listed under [authentication](#authentication) for the model provider. ## Adapter Settings Use `createDeepAgents()` to configure the runtime: ```ts const harness = createDeepAgents({ recursionLimit: 100 }); ``` Settings: - `auth`: authentication mode (`auto`, `anthropic`, or `ai-gateway`) or an isolated authentication environment. - `credentialForwarding`: optional synchronous or asynchronous callback that customizes each credential immediately before the harness adapter forwards it into a sandbox process. It receives the credential value that would otherwise be forwarded (either the real credential or a masked value) and the environment variable name used to expose it. This callback only controls the value forwarded into the sandbox process. It does not restrict which credentials the harness adapter can discover, read, or otherwise access in the host process. - `mcpServers`: MCP server definitions keyed by server name. - `port`: bridge port override. - `recursionLimit`: maximum LangGraph super-steps per turn. When omitted, the Deep Agents default applies. - `startupTimeoutMs`: maximum time to wait for the bridge to start. - `reconnect`: reconnect timing after an established bridge WebSocket connection drops. `maxElapsedMs` controls the total retry window, including connection establishment and backoff delays, and defaults to 30 seconds. `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to 2 seconds. These retries use exponential backoff and are separate from `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or bridge endpoint is permanently unavailable. - `mintBridgeToken`: synchronous function that receives the sandbox id and returns the bridge authentication token. By default, the adapter generates a random 32-byte token. Custom implementations must return a suitably secret token. ## Structured Output Deep Agents supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output). The adapter applies a per-turn LangChain tool strategy and returns the graph's validated `structuredResponse` as JSON text. ## Authentication Deep Agents always drives the Anthropic client. Non-Anthropic models reach it through AI Gateway's Anthropic-compatible endpoint, which translates to any model (Gemini, OpenAI, etc.), tool calls included. The `auth` setting selects how credentials are resolved from the host environment: - `auto` (default): use AI Gateway credentials when available, then fall back to Anthropic credentials. - `anthropic`: use Anthropic credentials. - `ai-gateway`: use AI Gateway credentials. When the sandbox supports additive request transformations, the bridge receives placeholders and the adapter injects credentials into matching outbound requests. Sandboxes without that capability retain direct credential forwarding. Supported environment variables: - `AI_GATEWAY_API_KEY` - `VERCEL_OIDC_TOKEN` - `AI_GATEWAY_BASE_URL` - `ANTHROPIC_API_KEY` - `ANTHROPIC_AUTH_TOKEN` - `ANTHROPIC_BASE_URL` To run a non-Anthropic model, select `ai-gateway`: ```ts const harness = createDeepAgents({ auth: 'ai-gateway' }); const agent = new HarnessAgent({ harness, model: 'google/gemini-2.5-flash', sandbox, }); ``` Pass an authentication environment to use programmatically resolved credentials without reading `process.env`: ```ts const harness = createDeepAgents({ auth: { ANTHROPIC_API_KEY: await resolveAnthropicToken() }, }); ``` The supplied record replaces the host environment for authentication discovery. Only recognized authentication variables are forwarded. ## Sandbox Deep Agents requires a network sandbox with at least one exposed port, e.g. `@ai-sdk/sandbox-vercel`: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); ``` ## Skills Skills passed to the session are materialized as native Deep Agents skill folders (`/SKILL.md` plus any attached files) under `$HOME/.agents/skills/` in the sandbox (outside the work dir, so they can't clash with cloned code), and loaded via Deep Agents' `skills` option — so the agent loads them on demand and skill file references resolve. Skills already present under `/.agents/skills/` (e.g. in a cloned repo) are also discovered. ## Built-in Tools The adapter exposes these Deep Agents built-ins through `agent.tools`: - `read` - `write` - `edit` - `bash` - `grep` - `glob` - `ls` - `task` - `write_todos` ## Known Limitations - **Resuming a stopped session's conversation** is not supported — after `session.stop()`, Deep Agents' in-memory conversation state (LangGraph `MemorySaver`) is gone; only the sandbox workspace persists via its snapshot. Use `session.detach()` for cross-process handoff or `session.suspendTurn()` for turn continuation while keeping the live bridge running. - **Manual compaction** is not supported. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: fx description: Learn how to use the fx harness adapter. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/fx" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The fx harness adapter connects `HarnessAgent` to [fx](https://fx.sh) using [libfx](https://www.npmjs.com/package/libfx). The runtime runs on the host, while built-in filesystem and shell tools operate exclusively on the sandbox. No bridge process runs inside the sandbox. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## Setup ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-fx @ai-sdk/sandbox-vercel ``` libfx is installed with the adapter. No fx executable is downloaded or installed inside the sandbox when a session starts. ## Import ```ts import { createFx, fx } from '@ai-sdk/harness-fx'; ``` `fx` is equivalent to `createFx()` with its default configuration. ## Basic Usage ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { fx } from '@ai-sdk/harness-fx'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const agent = new HarnessAgent({ harness: fx, model: 'openai/gpt-5.6-luna', }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); let exitCode = 0; try { const result = await agent.stream({ session, prompt: 'Check the test failures and fix the production code.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); await sandboxSession.destroy(); process.exit(exitCode); } ``` ## Adapter Settings Use `createFx()` to configure the runtime: ```ts const harness = createFx({ auth: 'ai-gateway', effort: 'low', serviceTier: 'default', }); ``` Settings: - `auth`: selects `auto`, `direct`, or `ai-gateway` authentication, or accepts an authentication environment for programmatically resolved credentials. `auto` prefers Gateway credentials, `direct` prefers subscription credentials with Gateway fallback, and `ai-gateway` uses Gateway only. - `mcpServers`: MCP server definitions keyed by server name. Supports HTTP and SSE servers, with `url`, optional `type`, `headers`, and `disabled` fields. Connections run on the host; command/stdio servers are unsupported. - `headers`: extra headers sent with AI Gateway requests from the host. - `effort`: reasoning effort for models that support it. - `serviceTier`: selects `default`, `fast`, or `ultrafast`. `default` is equivalent to omitting the setting and leaves libfx's defaults unchanged. Fast and Ultra require a model that supports the selected tier. - `backend`: libfx runtime backend (`auto`, `native`, or `wasm`). The default is `auto`, which prefers the native backend. The WASM backend requires JavaScript Promise Integration (JSPI) support. ## Authentication fx supports [Vercel AI Gateway authentication](https://fx.sh/docs/getting-started/authentication) and Codex or Grok subscriptions. For Gateway authentication, set one of these environment variables: - `VERCEL_OIDC_TOKEN` - `AI_GATEWAY_API_KEY` The adapter prefers `AI_GATEWAY_API_KEY` when both are available. `AI_GATEWAY_BASE_URL` optionally overrides the Gateway endpoint. Credentials are used on the host, not forwarded into the sandbox. When Gateway credentials are absent, `auto` discovers subscription credentials in this order: 1. fx Codex: `~/.fx/chatgpt-auth.json`. 2. fx Grok: `~/.fx/grok-auth.json`. 3. Native Codex: the credential store under `CODEX_HOME` or `~/.codex`, respecting `cli_auth_credentials_store` and the native keyring settings. 4. Native Grok: the xAI OAuth record in `GROK_HOME/auth.json` or `~/.grok/auth.json`. libfx refreshes subscription tokens near expiry and after an authentication rejection. The adapter saves rotated credentials back to the selected store. Failures after selection do not silently switch subscriptions or fall back to Gateway. Credentials are never included in persisted lifecycle state. Pass an authentication environment when the host resolves credentials at runtime instead of exposing them through `process.env`: ```ts const gatewayHarness = createFx({ auth: { AI_GATEWAY_API_KEY: await resolveGatewayToken(), AI_GATEWAY_BASE_URL: 'https://ai-gateway.vercel.sh', }, }); ``` The supplied record replaces the host environment for authentication discovery. The adapter does not add its credentials to `process.env` or include their values in persisted lifecycle state. Use `direct` to try subscriptions before Gateway, or `ai-gateway` to disable subscription discovery: ```ts const directHarness = createFx({ auth: 'direct' }); const gatewayHarness = createFx({ auth: 'ai-gateway' }); ``` An explicit authentication environment does not inspect ambient subscription stores. Resumed sessions keep their original provider and require compatible credentials for that provider. ## Sandbox fx runs on the host and accepts either a restricted sandbox session or a network sandbox session. No exposed sandbox ports are required: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', template: await agent.getSandboxTemplate(), }); ``` The host needs network access for AI Gateway and configured MCP servers. The sandbox does not need network access for model requests. Built-in tools use the sandbox's file and command-execution APIs; the sandbox must provide Bash and GNU command-line utilities such as `find`, `grep`, `realpath`, and `base64`. ## Built-in Tools The adapter exposes `read`, `write`, `edit`, `bash`, `grep`, `glob`, `ls`, and `askUserQuestions`. Filesystem and shell operations run in the session sandbox. The adapter does not expose the fx CLI's other built-in tools. Built-in tool filtering is supported through `activeTools` and `inactiveTools`. With `permissionMode: 'allow-reads'`, file edits and shell commands require approval. With `allow-edits`, only shell commands require approval. `allow-all` runs these tools without approval requests. ## Known Limitations - Manual compaction is unsupported by stable libfx and throws `HarnessCapabilityUnsupportedError`. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: GitHub Copilot description: Learn how to use the GitHub Copilot harness adapter. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/github-copilot" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The GitHub Copilot harness adapter connects `HarnessAgent` to [GitHub Copilot CLI](https://github.com/github/copilot-cli) through the Agent Client Protocol (ACP). The adapter delegates ACP installation, sessions, streaming, tools, approvals, and lifecycle management to `@ai-sdk/harness-acp`. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## Setup ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-github-copilot @ai-sdk/sandbox-vercel ``` The ACP harness installs the pinned GitHub Copilot CLI inside the sandbox when the first session starts. It never uses a host or globally installed Copilot CLI. The launch command disables GitHub Copilot's automatic updates so the installed version remains fixed for that bootstrap. ## Import ```ts import { createGitHubCopilot, githubCopilot, } from '@ai-sdk/harness-github-copilot'; ``` `githubCopilot` is equivalent to `createGitHubCopilot()` with its default configuration. ## Basic Usage ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { githubCopilot } from '@ai-sdk/harness-github-copilot'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const agent = new HarnessAgent({ harness: githubCopilot, model: 'gpt-5.5', }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); let exitCode = 0; try { const result = await agent.stream({ session, prompt: 'Check the test failures and fix the production code.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); await sandboxSession.destroy(); process.exit(exitCode); } ``` To use this agent with Vercel Sandbox, provide `VERCEL_OIDC_TOKEN` and one of the variables listed under [authentication](#authentication) in the host environment. Sessions support multiple turns, attach and detach, cold stop and resume, turn suspension and continuation, and `stopWhen` slicing through the shared ACP bridge lifecycle. ## Adapter Settings Use `createGitHubCopilot()` to configure the runtime: ```ts const harness = createGitHubCopilot({ auth: 'ai-gateway', reasoningEffort: 'high', port: 4001, startupTimeoutMs: 180_000, }); ``` Settings: - `auth`: selects `auto`, `direct`, or `ai-gateway` authentication, or accepts an isolated authentication environment. The default is `auto`, which selects AI Gateway when Gateway credentials are present and direct GitHub Copilot authentication otherwise. - `credentialForwarding`: optional synchronous or asynchronous callback that customizes each credential immediately before the harness adapter forwards it into a sandbox process. It receives the credential value that would otherwise be forwarded (either the real credential or a masked value) and the environment variable name used to expose it. This callback only controls the value forwarded into the sandbox process. It does not restrict which credentials the harness adapter can discover, read, or otherwise access in the host process. - `reasoningEffort`: reasoning effort for reasoning-capable models. Supported values are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. When omitted, GitHub Copilot uses its configured default. - `mcpServers`: ACP-native stdio, HTTP, or SSE MCP server definitions keyed by server name. - `port`: ACP bridge port override. - `portEndpoint`: host endpoint for the ACP bridge when the sandbox session cannot expose ports directly. - `startupTimeoutMs`: maximum time to wait for the ACP bridge to start. - `reconnect`: reconnect timing after an established bridge WebSocket connection drops. `maxElapsedMs` controls the total retry window, including connection establishment and backoff delays, and defaults to 30 seconds. `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to 2 seconds. These retries use exponential backoff and are separate from `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or bridge endpoint is permanently unavailable. - `mintBridgeToken`: synchronous function that receives the sandbox id and returns the ACP bridge authentication token. By default, the adapter generates a random 32-byte token. Custom implementations must return a suitably secret token. `reasoningEffort` is fixed for the launched GitHub Copilot server and applies to every session it creates. The adapter pins the GitHub Copilot CLI, executable, launch command, and ACP version. These implementation details cannot be overridden through `createGitHubCopilot()`. ## Authentication GitHub Copilot supports direct GitHub authentication and AI Gateway authentication. Set one or more of these environment variables: - `COPILOT_GITHUB_TOKEN` - `GH_TOKEN` - `GITHUB_TOKEN` - `VERCEL_OIDC_TOKEN` - `AI_GATEWAY_API_KEY` - `AI_GATEWAY_BASE_URL` If no applicable credential environment variable is set, the adapter attempts to resolve a native subscription from the host system unless AI Gateway authentication is selected. For direct authentication, GitHub Copilot checks the three GitHub token variables in the listed order. Fine-grained personal access tokens require the **Copilot Requests** permission; classic personal access tokens are unsupported. For AI Gateway, the adapter uses `VERCEL_OIDC_TOKEN` or `AI_GATEWAY_API_KEY` and honors `AI_GATEWAY_BASE_URL`. When the sandbox supports request transformations, the adapter brokers each credential only to matching outbound requests. Other sandboxes retain direct credential forwarding after applying `credentialForwarding`. Pass an authentication environment when the host resolves credentials at runtime: ```ts const gatewayHarness = createGitHubCopilot({ auth: { AI_GATEWAY_API_KEY: await resolveGatewayToken(), AI_GATEWAY_BASE_URL: 'https://ai-gateway.vercel.sh', }, }); ``` The supplied record replaces the host environment for authentication discovery. The adapter does not add its credentials to `process.env` or include their values in persisted ACP lifecycle identity. Force a specific authentication route when both kinds of credentials are available: ```ts const directHarness = createGitHubCopilot({ auth: 'direct' }); const gatewayHarness = createGitHubCopilot({ auth: 'ai-gateway' }); ``` AI Gateway model requests do not require GitHub authentication, but built-in GitHub MCP capabilities do. ## Sandbox GitHub Copilot runs inside the sandbox through `@ai-sdk/harness-acp`. It requires a network sandbox with at least one exposed port: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); ``` The first session requires network egress to install GitHub Copilot CLI. Subsequent model, GitHub, web, and MCP requests also require network access. ## Built-in Tools The adapter maps `bash`, `grep`, and `glob` to the corresponding common harness tools. Other tools remain available under their native GitHub Copilot names, including `read_bash`, `stop_bash`, `list_bash`, `view`, `create`, `edit`, `web_fetch`, `skill`, `sql`, agent tools, and `task`. GitHub and user-configured MCP tools remain dynamic. The shared ACP harness applies the configured Harness `permissionMode` when GitHub Copilot sends a permission request. `allow-reads` approves read operations, `allow-edits` also approves file mutations, and `allow-all` approves every request. Shell execution still requires approval under `allow-edits`. ## Known Limitations - ACP v1 does not expose a stable programmatic name for every native tool event. The adapter uses standard title, kind, and schema matching and leaves unmatched tools dynamic. - ACP v1 does not expose model-step boundaries or per-step usage. The adapter infers boundaries and reports unknown per-step usage when GitHub Copilot does not provide totals. - ACP v1 has no portable manual compaction or mid-turn steering API. - ACP v1 has no portable built-in tool filtering API. Filtering host tools is supported, but filtering GitHub Copilot built-ins throws an unsupported-capability error. - GitHub Copilot does not currently support built-in tool approval requests. Use `permissionMode: 'allow-all'` with this adapter. Host-executed AI SDK tool approvals still work. - GitHub Copilot ACP does not expose a structured-output metadata mapping, so schema-backed structured output is unsupported. - GitHub Copilot ACP does not expose any question tool, so `askUserQuestions` is unsupported. - GitHub Copilot CLI does not surface reasoning content over ACP for any model. `reasoningEffort` controls reasoning depth, not visibility: emitting a reasoning summary requires a parameter that ACP's `session/new` and `session/set_config_option` (limited to `mode`, `model`, `reasoning_effort`, `allow_all`, and `agent`) never expose to a client. The only code path that sets it lives in GitHub Copilot's interactive terminal UI, which does not run in headless `--acp --stdio` mode. - Custom `headers` are not natively supported and only applied via sandbox-external request transformations. When a sandbox without that capability is provided, custom `headers` therefore cannot be passed and are ignored. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) - [Agent Client Protocol](/providers/ai-sdk-harnesses/acp) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Grok Build description: Learn how to use the Grok Build harness adapter. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/grok-build" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The Grok Build harness adapter connects `HarnessAgent` to the [Grok Build CLI](https://x.ai/cli) through the Agent Client Protocol (ACP). The adapter delegates ACP installation, sessions, streaming, tools, and lifecycle management to `@ai-sdk/harness-acp`. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## Setup ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-grok-build @ai-sdk/sandbox-vercel ``` The ACP harness installs the pinned Grok Build CLI inside the sandbox when the first session starts. ## Import ```ts import { createGrokBuild, grokBuild } from '@ai-sdk/harness-grok-build'; ``` `grokBuild` is equivalent to `createGrokBuild()` with its default configuration. ## Basic Usage ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { grokBuild } from '@ai-sdk/harness-grok-build'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const agent = new HarnessAgent({ harness: grokBuild, model: 'grok-build-0.1', }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); let exitCode = 0; try { const result = await agent.stream({ session, prompt: 'Check the test failures and fix the production code.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); await sandboxSession.destroy(); process.exit(exitCode); } ``` To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for Vercel Sandbox and one of the variables listed under [authentication](#authentication) for Grok Build. ## Adapter Settings Use `createGrokBuild()` to configure the runtime: ```ts const harness = createGrokBuild({ auth: 'ai-gateway', reasoningEffort: 'high', port: 4001, startupTimeoutMs: 180_000, }); ``` Settings: - `auth`: selects `auto`, `direct`, or `ai-gateway` authentication, or accepts an isolated authentication environment. The default is `auto`, which selects AI Gateway when Gateway credentials are present and direct xAI authentication otherwise. - `credentialForwarding`: optional synchronous or asynchronous callback that customizes each credential immediately before the harness adapter forwards it into a sandbox process. It receives the credential value that would otherwise be forwarded (either the real credential or a masked value) and the environment variable name used to expose it. This callback only controls the value forwarded into the sandbox process. It does not restrict which credentials the harness adapter can discover, read, or otherwise access in the host process. - `reasoningEffort`: reasoning effort for reasoning-capable models. Supported values are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. When omitted, Grok Build uses its configured default. - `mcpServers`: MCP server definitions keyed by server name. - `port`: ACP bridge port override. - `startupTimeoutMs`: maximum time to wait for the ACP bridge to start. - `reconnect`: reconnect timing after an established bridge WebSocket connection drops. `maxElapsedMs` controls the total retry window, including connection establishment and backoff delays, and defaults to 30 seconds. `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to 2 seconds. These retries use exponential backoff and are separate from `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or bridge endpoint is permanently unavailable. - `mintBridgeToken`: synchronous function that receives the sandbox id and returns the ACP bridge authentication token. By default, the adapter generates a random 32-byte token. Custom implementations must return a suitably secret token. The adapter pins the Grok Build CLI and ACP launch command. These implementation details cannot be overridden through `createGrokBuild()`. ## Structured Output Grok Build supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output). Its profile maps the JSON Schema to Grok Build's private ACP prompt metadata, which the runtime enforces through its provider structured-output mechanism. ## Authentication By default, authentication is resolved from the host environment. When the sandbox supports additive request transformations, Grok Build receives a placeholder and the adapter injects the credential into matching outbound requests. Other sandboxes retain direct credential forwarding. Supported environment variables: - `VERCEL_OIDC_TOKEN` - `AI_GATEWAY_API_KEY` - `AI_GATEWAY_BASE_URL` - `XAI_API_KEY` If no applicable credential environment variable is set, the adapter attempts to resolve a native subscription from the host system unless AI Gateway authentication is selected. With direct authentication, the adapter uses `XAI_API_KEY`. With AI Gateway, it supplies the Gateway credential as `XAI_API_KEY`, maps the Gateway base URL ending in `/v1` to `GROK_XAI_API_BASE_URL` and `GROK_MODELS_BASE_URL`, and sets `GROK_CLIENT_NAME` and `GROK_CLIENT_VERSION` for client attribution. Force a specific authentication route when both kinds of credentials are available: ```ts const directHarness = createGrokBuild({ auth: 'direct' }); const gatewayHarness = createGrokBuild({ auth: 'ai-gateway' }); ``` Pass an authentication environment when the host resolves credentials at runtime: ```ts const gatewayHarness = createGrokBuild({ auth: { AI_GATEWAY_API_KEY: await resolveGatewayToken(), AI_GATEWAY_BASE_URL: 'https://ai-gateway.vercel.sh', }, }); ``` The supplied record replaces the host environment for authentication discovery, so it can also select direct authentication with `XAI_API_KEY`. ## Sandbox Grok Build runs inside the sandbox through `@ai-sdk/harness-acp`. It requires a network sandbox with at least one exposed port, such as `@ai-sdk/sandbox-vercel`: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); ``` The first session requires network egress so the ACP harness can install `@xai-official/grok@1.0.5` inside the sandbox. ## Built-in Tools The adapter maps these Grok Build built-ins to common harness tools: - `bash` (`run_terminal_command`) - `edit` (`search_replace`) - `grep` - `webSearch` (`web_search`) - `write` Other Grok Build tools remain available under their native names, including `read_file`, `list_dir`, `todo_write`, `spawn_subagent`, `monitor`, workflow and scheduler tools, and image-generation tools. Grok Build does not advertise ACP session modes for its permission behavior. When Grok sends an ACP permission request, the ACP harness applies the configured Harness `permissionMode` based on the tool kind. Grok may handle safe built-in operations internally without sending a permission request. ## Known Limitations - ACP v1 does not expose model-step boundaries or per-step usage. The adapter infers boundaries and reports unknown per-step usage when Grok does not provide totals. - ACP v1 has no portable manual compaction or mid-turn steering API. - ACP v1 has no portable built-in tool filtering API. Filtering host tools is supported, but filtering Grok built-ins throws an unsupported-capability error. - Grok Build does not currently support built-in tool approval requests. Use `permissionMode: 'allow-all'` with this adapter. Host-executed AI SDK tool approvals still work. - A changed host-tool catalog requires Grok Build to refresh its ACP MCP tool list. If the agent retains stale tools, the turn fails explicitly. - Custom `headers` are not natively supported and only applied via sandbox-external request transformations. When a sandbox without that capability is provided, custom `headers` therefore cannot be passed and are ignored. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) - [Agent Client Protocol](/providers/ai-sdk-harnesses/acp) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI SDK Harnesses description: Learn how to use AI SDK harness adapters. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). AI SDK harness adapters connect `HarnessAgent` to established agent runtimes. They are separate from model providers, but they expose AI SDK-compatible stream and response primitives. #### [Claude Code](/providers/ai-sdk-harnesses/claude-code) Use Claude Code through the AI SDK harness abstraction. #### [Cline](/providers/ai-sdk-harnesses/cline) Use the Cline SDK through the AI SDK harness abstraction. #### [Codex](/providers/ai-sdk-harnesses/codex) Use Codex through the AI SDK harness abstraction. #### [Cursor](/providers/ai-sdk-harnesses/cursor) Use Cursor through the AI SDK harness abstraction. #### [fx](/providers/ai-sdk-harnesses/fx) Use fx through the AI SDK harness abstraction. #### [GitHub Copilot](/providers/ai-sdk-harnesses/github-copilot) Use GitHub Copilot through the AI SDK harness abstraction. #### [Grok Build](/providers/ai-sdk-harnesses/grok-build) Use Grok Build through the AI SDK harness abstraction. #### [OpenCode](/providers/ai-sdk-harnesses/opencode) Use OpenCode through the AI SDK harness abstraction. #### [Pi](/providers/ai-sdk-harnesses/pi) Use Pi through the AI SDK harness abstraction. #### [Agent Client Protocol](/providers/ai-sdk-harnesses/acp) Use any ACP compatible harness through the AI SDK harness abstraction. ## Usage All harness adapters are used with `HarnessAgent`: ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { codex } from '@ai-sdk/harness-codex'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const agent = new HarnessAgent({ harness: codex, model: 'gpt-5.6-luna', }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); try { console.log( (await agent.generate({ session, prompt: 'Inspect this project.' })).text, ); } finally { await session.destroy(); await sandboxSession.destroy(); } ``` Read the [Harnesses](/docs/ai-sdk-harnesses) documentation for sessions, tools, UI, and terminal usage. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: OpenCode description: Learn how to use the OpenCode harness adapter. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/opencode" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The OpenCode harness adapter connects `HarnessAgent` to OpenCode through `@opencode-ai/sdk`. The adapter runs a bridge inside the sandbox, starts an OpenCode server in that sandbox, and streams OpenCode session events back to the host over a sandbox-exposed WebSocket. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## Setup ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-opencode @ai-sdk/sandbox-vercel ``` The adapter bootstraps the OpenCode bridge dependencies inside the sandbox when the first session starts. The bridge package depends on `@opencode-ai/sdk` and `opencode-ai`. ## Import ```ts import { openCode, createOpenCode } from '@ai-sdk/harness-opencode'; ``` `openCode` is equivalent to `createOpenCode()` with its default configuration. ## Basic Usage ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { openCode } from '@ai-sdk/harness-opencode'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const agent = new HarnessAgent({ harness: openCode, model: 'anthropic/claude-sonnet-4-6', }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); let exitCode = 0; try { const result = await agent.stream({ session, prompt: 'Check the test failures and fix the production code.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); await sandboxSession.destroy(); process.exit(exitCode); } ``` To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for Vercel Sandbox, and one of the variables listed under [authentication](#authentication) for OpenCode. ## Adapter Settings Use `createOpenCode()` to configure the runtime: ```ts const harness = createOpenCode({ reasoningVariant: 'high', openCodeConfig: { agent: { general: { model: 'openai/gpt-5.4-mini', }, }, }, }); ``` Settings: - `auth`: authentication mode (`auto`, `anthropic`, `openai`, or `ai-gateway`) or an isolated authentication environment. - `credentialForwarding`: optional synchronous or asynchronous callback that customizes each credential immediately before the harness adapter forwards it into a sandbox process. It receives the credential value that would otherwise be forwarded (either the real credential or a masked value) and the environment variable name used to expose it. This callback only controls the value forwarded into the sandbox process. It does not restrict which credentials the harness adapter can discover, read, or otherwise access in the host process. - `mcpServers`: MCP server definitions keyed by server name. - `openCodeConfig`: additional native OpenCode configuration. Adapter-managed settings take precedence. Agent-local `permission` and deprecated `tools` settings are ignored so they cannot bypass harness permissions or built-in tool filtering. - `provider`: provider id to use when `model` on `HarnessAgent` is unprefixed. - `reasoningVariant`: OpenCode reasoning/thinking variant for supported models, such as `low`, `medium`, or `high`. - `port`: bridge port override. - `startupTimeoutMs`: maximum time to wait for the bridge to start. - `reconnect`: reconnect timing after an established bridge WebSocket connection drops. `maxElapsedMs` controls the total retry window, including connection establishment and backoff delays, and defaults to 30 seconds. `initialDelayMs` defaults to 50 milliseconds, and `maxDelayMs` defaults to 2 seconds. These retries use exponential backoff and are separate from `startupTimeoutMs`. They cannot recover when the sandbox, bridge process, or bridge endpoint is permanently unavailable. - `mintBridgeToken`: synchronous function that receives the sandbox id and returns the bridge authentication token. By default, the adapter generates a random 32-byte token. Custom implementations must return a suitably secret token. ## Structured Output OpenCode supports schema-backed [`HarnessAgent` structured output](/docs/ai-sdk-harnesses/harness-agent#generate-structured-output). The adapter uses OpenCode's `json_schema` prompt format and returns the validated `structured` result as JSON text. ## Authentication The `auth` setting selects how credentials are resolved from the host environment: - `auto` (default): use AI Gateway credentials when available, then use the credentials for the selected model provider. - `anthropic`: use Anthropic credentials. - `openai`: use OpenAI credentials. - `ai-gateway`: use AI Gateway credentials. When the sandbox supports additive request transformations, the bridge receives placeholders and the adapter injects credentials into matching outbound requests. Sandboxes without that capability retain direct credential forwarding. Supported environment variables: - `AI_GATEWAY_API_KEY` - `AI_GATEWAY_BASE_URL` - `VERCEL_OIDC_TOKEN` - `ANTHROPIC_API_KEY` - `ANTHROPIC_AUTH_TOKEN` - `ANTHROPIC_BASE_URL` - `OPENAI_API_KEY` - `OPENAI_BASE_URL` - `OPENAI_ORGANIZATION` - `OPENAI_PROJECT` If no applicable credential environment variable is set, the adapter attempts to resolve a native subscription from the host system unless AI Gateway authentication is selected. Select a specific authentication mode when you do not want automatic detection: ```ts const anthropicHarness = createOpenCode({ auth: 'anthropic' }); const openAIHarness = createOpenCode({ auth: 'openai' }); const gatewayHarness = createOpenCode({ auth: 'ai-gateway' }); ``` Pass an authentication environment to use programmatically resolved credentials without reading `process.env`: ```ts const harness = createOpenCode({ auth: { OPENAI_API_KEY: await resolveOpenAIToken() }, provider: 'openai', }); ``` The supplied record replaces the host environment for authentication discovery. Only recognized authentication variables are forwarded. For OpenAI-compatible endpoints, select `openai` and set `OPENAI_BASE_URL`. ## Sandbox OpenCode requires a network sandbox with at least one exposed port, e.g. `@ai-sdk/sandbox-vercel`: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', ports: [4000], template: await agent.getSandboxTemplate(), }); ``` ## Built-in Tools The adapter exposes these common OpenCode built-ins through `agent.tools`: - `read` - `write` - `edit` - `bash` - `glob` - `grep` - `ls` - `webfetch` - `skill` - `todowrite` - `agent` Additional OpenCode built-ins may also appear in `agent.tools` when they do not fit a common tool shape. OpenCode supports built-in tool approval requests when `permissionMode` is `allow-reads` or `allow-edits`. Host-executed AI SDK tool approvals also work. ## Built-in Tool Filtering The adapter supports `activeTools` and `inactiveTools` for OpenCode built-in tools. It uses OpenCode's native `permission: 'deny'` rules, which keep inactive built-ins out of the model-facing tool list. OpenCode groups its native `write` and `edit` tools under the same `edit` permission. Keep the Harness `write` and `edit` tools in the same active or inactive state. Filtering only one of them causes session startup to throw a `HarnessCapabilityUnsupportedError`. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Pi description: Learn how to use the Pi harness adapter. url: "https://ai-sdk.dev/providers/ai-sdk-harnesses/pi" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The Pi harness adapter connects `HarnessAgent` to `@earendil-works/pi-coding-agent`. Pi runs in the host Node.js process and uses the sandbox as a remote filesystem and shell. It does not install a bridge inside the sandbox. Harness packages are **experimental**. Expect breaking changes between releases as this early API gets further refined. ## Setup ```bash pnpm add @ai-sdk/harness @ai-sdk/harness-pi @ai-sdk/sandbox-vercel ``` ## Import ```ts import { pi, createPi } from '@ai-sdk/harness-pi'; ``` `pi` is equivalent to `createPi()` with its default configuration. ## Basic Usage ```ts import { HarnessAgent } from '@ai-sdk/harness/agent'; import { pi } from '@ai-sdk/harness-pi'; import { createVercelNetworkSandboxSession } from '@ai-sdk/sandbox-vercel'; const agent = new HarnessAgent({ harness: pi, model: 'anthropic/claude-sonnet-4.6', }); const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', template: await agent.getSandboxTemplate(), }); const session = await agent.createSession({ sandboxSession }); let exitCode = 0; try { const result = await agent.stream({ session, prompt: 'Check the test failures and fix the production code.', }); for await (const part of result.stream) { if (part.type === 'text-delta') { process.stdout.write(part.text); } } } catch (err) { exitCode = 1; console.error(err); } finally { await session.destroy(); await sandboxSession.destroy(); process.exit(exitCode); } ``` To use this agent, ensure environment variables include `VERCEL_OIDC_TOKEN` for Vercel Sandbox, and one of the variables listed under [authentication](#authentication) for Pi. ## Adapter Settings Use `createPi()` to configure the runtime: ```ts const harness = createPi({ thinkingLevel: 'medium', }); ``` Settings: - `auth`: authentication mode (`auto`, `openai`, `anthropic`, `custom`, or `ai-gateway`) or an isolated authentication environment. - `cacheRetention`: default prompt cache retention for model requests (`none`, `short`, or `long`). For Anthropic models, `long` uses the 1-hour cache instead of the 5-minute default. - `credentials`: application-owned Pi credential storage. This replaces the file-backed `auth.json` store and supports persistent database-backed credentials. - `extensionFactories`: trusted inline Pi extension factories that run in the host Node.js process. - `fileToolPathPolicy`: extra sandbox roots Pi's native file tools can read or write, and roots they must refuse. See [File Tool Paths](#file-tool-paths). - `mcpServers`: MCP servers keyed by server name, served by Pi's native MCP support. Each entry is Pi's MCP server configuration, either stdio (`command`, `args`, `env`, `cwd`) or streamable HTTP (`url`, `headers`). Tools are named `mcp____`, with `-` in either name replaced by `_`. `exposure` defaults to `direct`, which declares a server's tools to the model; with `deferred`, the model finds them through Pi's `tool_search` tool first. Pi's `codemode` exposure is not supported. An entry that uses it, or that Pi rejects, fails session start. Pi truncates text results over 20 KB and writes the full text to the host temp directory. The harness has no sign-in flow, so an HTTP server that requires authentication must carry an `Authorization` header or `auth.provider`. - `mcpSettings`: deprecated. Pi's native MCP has no tool prefix or output guard settings, so a `toolPrefix` other than `mcp`, or `outputGuard: false`, fails session start. - `providers`: explicit Pi provider configurations for custom models. - `reattachInProcess`: whether a suspended turn can reuse its live Pi session in the current process. Defaults to `true`. - `thinkingLevel`: Pi thinking level (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`). ## File Tool Paths By default, Pi's native file tools (`read`, `write`, `edit`, `ls`, `find`, and `grep`) are limited to the session workspace, and the read-only tools can also read the harness skill directory. Use `fileToolPathPolicy` to change what they can reach: ```ts const harness = createPi({ fileToolPathPolicy: { readableRoots: ['/home/vercel-sandbox'], writableRoots: ['/tmp', '/mnt/artifacts'], deniedRoots: ['/home/vercel-sandbox/.credentials'], }, }); ``` - `readableRoots`: absolute sandbox directories that `read`, `ls`, `find`, and `grep` can reach in addition to the defaults. They do not become writable. - `writableRoots`: absolute sandbox directories that `write` and `edit` can reach in addition to the workspace. Writable roots are also readable. - `deniedRoots`: absolute sandbox directories that every native file tool refuses, even inside the workspace or a readable root. Both requested paths and denied roots are resolved in the sandbox, so symlinks pointing to or from a denied root are refused. Configured roots are resolved again for each file operation; canonical denied roots captured at session startup remain denied. `find` and `grep` prune exact denied paths without following directory symlinks, and `ls` hides directly denied entries. Other directories with the same name remain accessible. Every configured root must be an absolute sandbox path. Denied roots take precedence over the workspace, harness skill directory, and allowed roots. Writes outside the workspace do not emit workspace `file-change` events. When the setting is present, a leading `~` in a tool path expands to the sandbox home directory. The policy applies to Pi's native file tools only. The `bash` tool is not restricted by it, so do not treat `deniedRoots` as a security boundary for commands the agent runs. ## Inline Extensions Use `extensionFactories` to load trusted inline Pi extensions for each harness session: ```ts const harness = createPi({ extensionFactories: [ pi => { pi.on('agent_start', () => { console.log('Pi agent started'); }); }, ], }); ``` Each factory also receives the harness session it runs in as a second argument: - `sandboxSession`: the session's sandbox, restricted to the operations harness tools may use. - `sessionWorkDir`: the sandbox directory the session works in. - `instructions()`: the instructions of the current turn. Instructions arrive with each turn, so call it while a turn runs, not when the factory runs. ```ts const harness = createPi({ extensionFactories: [ (pi, session) => { pi.on('agent_start', async () => { const { stdout } = await session.sandboxSession.run({ command: `git -C ${session.sessionWorkDir} status --short`, }); console.log(session.instructions(), stdout); }); }, ], }); ``` Factories that take only the Pi API keep working unchanged. Routine resource refreshes between turns reuse the active extension runtime and do not reinitialize factories. If the underlying Pi session is rebuilt, the factories initialize again for the new Pi runtime. Extension factories execute in the host Node.js process with access to the host environment, so only pass factories you trust. This setting enables only the factories you explicitly provide. Filesystem extension discovery remains disabled, including user, project, and settings-based extensions. Themes and prompt templates also remain disabled. ## Session Reattachment By default, Pi keeps a suspended turn alive for an efficient continuation in the same process when the incoming request has compatible settings and runtime resources. Changed request-scoped settings or sandbox handles cause a cold restore from persisted lifecycle state. For stateless or multi-replica applications, disable in-process reattachment so every continuation restores the persisted lifecycle state with the current request's settings: ```ts const harness = createPi({ reattachInProcess: false, }); ``` An incoming completed `resume-session` state always takes precedence over an older live suspended turn in the process. ## Authentication The `auth` setting selects which credentials Pi reads from the host environment: - `auto` (default): use AI Gateway credentials when available, then fall back to all provider credentials found in the environment. - `openai`: use `OPENAI_API_KEY` and the optional `OPENAI_BASE_URL`. - `anthropic`: use `ANTHROPIC_API_KEY` and the optional `ANTHROPIC_AUTH_TOKEN` and `ANTHROPIC_BASE_URL`. - `custom`: register all providers configured through environment variables. - `ai-gateway`: use `AI_GATEWAY_API_KEY` or `VERCEL_OIDC_TOKEN` and the optional `AI_GATEWAY_BASE_URL`. If no applicable credential environment variable is set, the adapter attempts to resolve a native subscription from the host system unless AI Gateway authentication is selected. ```ts const harness = createPi({ auth: 'ai-gateway' }); ``` Pass an authentication environment to use programmatically resolved credentials without reading `process.env`: ```ts const harness = createPi({ auth: { MISTRAL_API_KEY: await resolveMistralToken(), MISTRAL_BASE_URL: 'https://api.mistral.ai', }, }); ``` The supplied record replaces the host environment for authentication discovery. Pi parses provider API keys and base URLs from that record. For credentials managed outside environment variables or files, inject an application-owned credential store. Pi performs credential reads, refreshes, and updates through this interface: ```ts import { createPi, type PiCredentialStore } from '@ai-sdk/harness-pi'; declare const credentials: PiCredentialStore; const harness = createPi({ credentials, reattachInProcess: false, }); ``` `credentials` replaces Pi's `auth.json` storage. In request-scoped or multi-replica deployments, pair it with `reattachInProcess: false` so each continuation constructs a model runtime from the current request. With `custom`, standard providers use environment variables such as `OPENAI_API_KEY`, `OPENAI_BASE_URL`, `ANTHROPIC_API_KEY`, and `ANTHROPIC_BASE_URL`. Other providers use a `_API_KEY` and matching `_BASE_URL` pair. Authentication variables do not identify a provider's API protocol or models. Register that metadata explicitly when using a custom model: ```ts const harness = createPi({ auth: { MYPROVIDER_API_KEY: await resolveMyProviderToken(), MYPROVIDER_BASE_URL: 'https://api.example.com/v1', }, providers: { myprovider: { api: 'openai-completions', models: [ { id: 'my-custom-model', name: 'My Custom Model', reasoning: false, input: ['text'], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128_000, maxTokens: 16_384, }, ], }, }, }); ``` ## Sandbox Pi needs a sandbox session but does not require exposed ports. You can create a Vercel network session or a local just-bash session: ```ts const sandboxSession = await createVercelNetworkSandboxSession({ runtime: 'node24', template: await agent.getSandboxTemplate(), }); ``` ## Built-in Tools The adapter exposes these common Pi built-ins through `agent.tools`: - `read` - `write` - `edit` - `bash` - `grep` - `glob` - `ls` Additional Pi built-ins may also appear in `agent.tools` when they do not fit a common tool shape. Pi supports built-in tool approval requests when `permissionMode` is `allow-reads` or `allow-edits`. ## Known Limitations Pi does not support structured output. Supplying `output` to `HarnessAgent` causes the turn to throw `HarnessCapabilityUnsupportedError`. Pi does not support user messages with text parts after image parts; all text must appear before the first image. ## Related - [HarnessAgent](/docs/ai-sdk-harnesses/harness-agent) - [Harness tools](/docs/ai-sdk-harnesses/tools) - [Harness adapters](/docs/ai-sdk-harnesses/harness-adapters) --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AI Gateway description: Learn how to use the AI Gateway provider with the AI SDK. url: "https://ai-sdk.dev/providers/ai-sdk-providers/ai-gateway" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [AI Gateway](https://vercel.com/docs/ai-gateway) provider connects you to models from multiple AI providers through a single interface. Instead of integrating with each provider separately, you can access OpenAI, Anthropic, Google, Meta, xAI, and other providers and their models. ## Features - Access models from multiple providers without having to install additional provider modules/dependencies - Use the same code structure across different AI providers - Switch between models and providers easily - Automatic authentication when deployed on Vercel - View pricing information across providers - Observability for AI model usage through the Vercel dashboard ## Setup The Vercel AI Gateway provider is part of the AI SDK. ## Basic Usage For most use cases, you can use the AI Gateway directly with a model string: ```ts // use plain model string with global provider import { generateText } from 'ai'; const { text } = await generateText({ model: 'openai/gpt-6-astra', prompt: 'Hello world', }); ``` ```ts // use provider instance (requires version 5.0.36 or later) import { generateText, gateway } from 'ai'; const { text } = await generateText({ model: gateway('openai/gpt-6-astra'), prompt: 'Hello world', }); ``` The AI SDK automatically uses the AI Gateway when you pass a model string in the `creator/model-name` format. ## Provider Instance The `gateway` provider instance is available from the `ai` package in version 5.0.36 and later. You can also import the default provider instance `gateway` from `ai`: ```ts import { gateway } from 'ai'; ``` You may want to create a custom provider instance when you need to: - Set custom configuration options (API key, Vercel access token, base URL, headers) - Use the provider in a [provider registry](/docs/ai-sdk-core/provider-management) - Wrap the provider with [middleware](/docs/ai-sdk-core/middleware) - Use different settings for different parts of your application To create a custom provider instance, import `createGateway` from `ai`: ```ts import { createGateway } from 'ai'; const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '', }); ``` You can use the following optional settings to customize the AI Gateway provider instance: - **batchResultDownloads** *object* Settings for downloading JSON Lines batch results. - **maxLineBytes** *number* Maximum UTF-8 bytes per row, excluding the LF delimiter. Defaults to 64 MiB (67,108,864 bytes). Must be a positive safe integer. Oversized rows throw a `DownloadError` and cancel the download. - **baseURL** *string* Use a different URL prefix for API calls. The default prefix is `https://ai-gateway.vercel.sh/v4/ai`. - **apiKey** *string* API key or Vercel access token that is being sent using the `Authorization` header. It defaults to the `AI_GATEWAY_API_KEY` environment variable. Supports AI Gateway API keys, Vercel personal access tokens, and Vercel app access tokens. - **teamIdOrSlug** *string* Vercel team ID or slug used to scope model requests when authenticating with multi-team Vercel access tokens. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. - **metadataCacheRefreshMillis** *number* How frequently to refresh the metadata cache in milliseconds. Defaults to 5 minutes (300,000ms). ## Authentication The Gateway provider supports the following authentication methods: ### API Key Authentication Set your API key via environment variable: ```bash AI_GATEWAY_API_KEY=your_api_key_here ``` Or pass it directly to the provider: ```ts import { createGateway } from 'ai'; const gateway = createGateway({ apiKey: 'your_api_key_here', }); ``` ### Vercel Access Token Authentication for Model Requests For dynamic runtime authentication of model requests, pass a Vercel personal access token or Vercel app access token to a custom Gateway provider instance: ```ts import { createGateway } from 'ai'; const gateway = createGateway({ apiKey: 'your_vercel_access_token_here', teamIdOrSlug: 'your-team', // required for multi-team tokens }); ``` `apiKey` takes precedence over `AI_GATEWAY_API_KEY` and OIDC. Vercel access tokens with `teamIdOrSlug` are supported for model requests. Gateway helper methods such as `getCredits`, `getSpendReport`, and `getGenerationInfo` require an AI Gateway API key or OIDC authentication. ### OIDC Authentication (Vercel Deployments) When deployed to Vercel, the AI Gateway provider supports authenticating model requests using [OIDC (OpenID Connect) tokens](https://vercel.com/docs/oidc) without API Keys. #### How OIDC Authentication Works 1. **In Production/Preview Deployments**: - OIDC authentication is automatically handled - No manual configuration needed - Tokens are automatically obtained and refreshed 2. **In Local Development**: - First, install and authenticate with the [Vercel CLI](https://vercel.com/docs/cli) - Run `vercel env pull` to download your project's OIDC token locally - For automatic token management: - Use `vercel dev` to start your development server - this will handle token refreshing automatically - For manual token management: - If not using `vercel dev`, note that OIDC tokens expire after 12 hours - You'll need to run `vercel env pull` again to refresh the token before it expires If an API key or Vercel access token is present (either passed directly or via environment), it will always be used instead of OIDC, even if invalid. Read more about using OIDC tokens in the [Vercel AI Gateway docs](https://vercel.com/docs/ai-gateway#using-the-ai-gateway-with-a-vercel-oidc-token). ## Bring Your Own Key (BYOK) You can connect your own provider credentials to use with Vercel AI Gateway. This lets you use your existing provider accounts and access private resources. To set up BYOK, add your provider credentials in your Vercel team's AI Gateway settings. Once configured, AI Gateway automatically uses your credentials. No code changes are needed. For providers like Azure where you can use custom deployment names, you can configure model mappings to map gateway model slugs to your deployment names. See [model mappings](https://vercel.com/docs/ai-gateway/byok#model-mappings) for details. Learn more in the [BYOK documentation](https://vercel.com/docs/ai-gateway/byok). ## Language Models You can create language models using a provider instance. The first argument is the model ID in the format `creator/model-name`: ```ts import { generateText } from 'ai'; const { text } = await generateText({ model: 'openai/gpt-6-astra', prompt: 'Explain quantum computing in simple terms', }); ``` AI Gateway language models can also be used in the `streamText` function and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output) (see [AI SDK Core](/docs/ai-sdk-core)). When an upstream provider reports a well-formed error after streaming has started, AI SDK Core exposes it as a [`StreamProviderError`](/docs/reference/ai-sdk-errors/ai-stream-provider-error). Use its `type`, `code`, `statusCode`, and `isRetryable` metadata to classify the failure without inspecting the Gateway's serialized error payload. ## Batch Batch support is experimental and the API may change in patch releases. AI Gateway supports durable text batches through `experimental_startBatch`, `experimental_getBatchStatus`, `experimental_getBatchResults`, and `experimental_cancelBatch`. See [Batch](/docs/ai-sdk-core/batch) for the provider-agnostic workflow, and the [AI Gateway batch processing guide](https://vercel.com/docs/ai-gateway/models-and-providers/batch-processing) for supported models, limits, and Gateway-specific behavior. Pass a publicly reachable HTTPS `webhookUrl` when starting a batch to receive a terminal notification instead of polling. AI Gateway sends `batch.completed`, `batch.failed`, or `batch.cancelled`. The event contains terminal status information but not batch results. Completion webhooks are an AI Gateway capability; direct Anthropic and OpenAI batch providers return an unsupported warning when this option is provided. ```ts title="start-batch.ts" import { randomUUID } from 'node:crypto'; import { experimental_startBatch as startBatch, type Experimental_BatchReference as BatchReference, type GatewayProviderMetadata, } from 'ai'; // The receiver uses this token to look up the expected batch and signing // secret before trusting anything in the webhook body. const token = randomUUID(); const model = 'anthropic/claude-haiku-5.5' as const; // Reserve the token before starting so an early delivery finds a retryable // pending record rather than an unknown callback. await reserveBatchWebhook(token); const batch = await startBatch({ requests: [ { id: 'france', type: 'text', model, prompt: 'What is the capital of France?', }, { id: 'germany', type: 'text', model, prompt: 'What is the capital of Germany?', }, ], providerOptions: { gateway: { idempotencyKey: token }, }, webhookUrl: `https://example.com/api/batch-webhook?token=${token}`, }); const gatewayMetadata = batch.providerMetadata?.gateway as | GatewayProviderMetadata | undefined; const signingSecret = gatewayMetadata?.asyncJob?.webhookSigningSecret; if (typeof signingSecret !== 'string') { throw new Error('AI Gateway did not return a webhook signing secret.'); } // Keep sensitive provider metadata out of the receiver queue and logs. const batchReference = { version: batch.version, id: batch.id, provider: batch.provider, } satisfies BatchReference; await completeBatchWebhookReservation(token, { batch: batchReference, signingSecret, }); ``` The signing secret is returned only in the start response. Reserve the callback token before submitting the batch, then persist the secret and minimal `batch` reference before the process exits. The stable `providerOptions.gateway.idempotencyKey` makes retrying an ambiguous start safe. Reuse the reserved token when retrying; do not generate a new one. Delete the reservation after a definitive start failure. While the reservation is still pending, the receiver should return a retryable response so an early delivery is sent again after setup completes. The receiver must verify the `x-ai-gateway-signature` header against a bounded raw request body before trusting the event: ```ts title="app/api/batch-webhook/route.ts" import { createHmac, timingSafeEqual } from 'node:crypto'; const MAX_AGE_SECONDS = 5 * 60; const MAX_BODY_BYTES = 64 * 1024; export async function POST(request: Request) { const token = new URL(request.url).searchParams.get('token'); if (token == null) return new Response(null, { status: 401 }); const record = await loadBatchWebhook(token); if (record == null) return new Response(null, { status: 401 }); if (record.state !== 'ready') return new Response(null, { status: 503 }); const declaredLength = request.headers.get('content-length'); if (declaredLength != null && Number(declaredLength) > MAX_BODY_BYTES) { return new Response(null, { status: 413 }); } const rawBody = await readBodyWithLimit(request, MAX_BODY_BYTES); if (rawBody == null) return new Response(null, { status: 413 }); const signature = request.headers.get('x-ai-gateway-signature'); if ( signature == null || !verifySignature({ header: signature, rawBody, secret: record.signingSecret, }) ) { return new Response(null, { status: 401 }); } let event: { data?: { jobId?: string; status?: string } }; try { event = JSON.parse(rawBody); } catch { return new Response(null, { status: 400 }); } const jobId = event.data?.jobId; const status = event.data?.status; if ( jobId !== record.batch.id || (status !== 'cancelled' && status !== 'completed' && status !== 'failed') ) { return new Response(null, { status: 401 }); } // The header is not signed, so require it to match the signed event body. const deliveryId = `${jobId}-${status}`; if (request.headers.get('x-ai-gateway-idempotency-key') !== deliveryId) { return new Response(null, { status: 401 }); } await enqueueBatchResultFetch({ batch: record.batch, deliveryId, }); return new Response(null, { status: 204 }); } async function readBodyWithLimit(request: Request, maxBytes: number) { if (request.body == null) return ''; const reader = request.body.getReader(); const decoder = new TextDecoder(); let body = ''; let size = 0; try { while (true) { const { done, value } = await reader.read(); if (done) return body + decoder.decode(); size += value.byteLength; if (size > maxBytes) { await reader.cancel(); return undefined; } body += decoder.decode(value, { stream: true }); } } finally { reader.releaseLock(); } } function verifySignature({ header, rawBody, secret, }: { header: string; rawBody: string; secret: string; }) { const timestamp = header.match(/(?:^|,)t=(\d+)/)?.[1]; const digest = header.match(/(?:^|,)v1=([0-9a-f]{64})(?:,|$)/)?.[1]; if (timestamp == null || digest == null) return false; const expected = createHmac('sha256', secret) .update(`${timestamp}.${rawBody}`, 'utf8') .digest('hex'); const provided = Buffer.from(digest, 'hex'); const computed = Buffer.from(expected, 'hex'); if (provided.length !== computed.length) return false; if (!timingSafeEqual(provided, computed)) return false; return Math.abs(Date.now() / 1000 - Number(timestamp)) <= MAX_AGE_SECONDS; } ``` The signature header has the form `t=,v1=`, where `v1` is the HMAC-SHA256 digest of `"."`. Verify a size-limited raw request body rather than a re-serialized object, use a timing-safe comparison, reject stale timestamps, and reject events whose `data.jobId` does not match the persisted `batch.id`. AI Gateway expects a 2xx response within 10 seconds and retries failed deliveries. It does not follow redirects, so the callback endpoint itself must return the 2xx response. Retries carry the same `x-ai-gateway-idempotency-key` value (`-`), which receivers can use for deduplication. After verification, pass the persisted `batch` reference to `experimental_getBatchStatus` and `experimental_getBatchResults`. Delivery is best-effort, so periodically reconcile persisted batches by status as a fallback for a notification that exhausts its retries. Each request specifies its `type` and `model`. AI Gateway currently requires every text request in a batch to use the same model and throws before submission when the models differ. ### Cancelling a batch Pass the batch reference returned by `experimental_startBatch` to request cancellation: ```ts import { gateway } from '@ai-sdk/gateway'; import { experimental_cancelBatch as cancelBatch } from 'ai'; await cancelBatch({ provider: gateway, batch }); ``` Cancellation is asynchronous. Continue checking `experimental_getBatchStatus` until the batch is terminal, then read `experimental_getBatchResults` for any partial results. A batch may finish before cancellation takes effect, and successful requests remain billable. Cancellation returns acknowledgement metadata, not confirmation that provider execution has stopped. ## Reranking Models You can create reranking models using the `rerankingModel` method on the provider instance: ```ts import { rerank } from 'ai'; import { gateway } from '@ai-sdk/gateway'; const { ranking } = await rerank({ model: gateway.rerankingModel('cohere/rerank-v3.5'), query: 'What is the capital of France?', documents: [ 'Paris is the capital of France.', 'Berlin is the capital of Germany.', 'Madrid is the capital of Spain.', ], topN: 2, }); console.log(ranking); // [ // { originalIndex: 0, score: 0.89, document: 'Paris is the capital of France.' }, // { originalIndex: 2, score: 0.15, document: 'Madrid is the capital of Spain.' }, // ] ``` Reranking models are useful for improving search results in retrieval-augmented generation (RAG) pipelines by re-scoring candidate documents after an initial retrieval step. ## Realtime Models Realtime support is experimental and the API may change in patch releases. You can create realtime models for bidirectional audio/text WebSocket sessions using the `experimental_realtime` method on the provider instance. The first argument is the model ID in the format `creator/model-name`: ```ts import { gateway } from '@ai-sdk/gateway'; const model = gateway.experimental_realtime('openai/gpt-realtime-2'); ``` The Gateway normalizes realtime the same way it normalizes every other modality: your client speaks the [normalized AI SDK realtime protocol](/docs/ai-sdk-core/realtime) and the Gateway translates to and from the upstream provider server-side. This means the same client code works regardless of which provider backs the model. Realtime sessions run in the browser and require a short-lived Gateway client secret created on your server with `gateway.experimental_realtime.getToken()`. The method uses your Gateway credential (`apiKey`, `AI_GATEWAY_API_KEY`, or Vercel OIDC token) to mint a `vcst_` client secret and returns the WebSocket URL for the model. ```ts title='app/api/realtime/setup/route.ts' import { gateway } from 'ai'; export async function POST() { const token = await gateway.experimental_realtime.getToken({ model: 'openai/gpt-realtime-2', expiresAfterSeconds: 60 * 10, }); return Response.json(token); } ``` Use the matching Gateway realtime model in the browser. Creating the model is safe in browser code; only `getToken()` is server-side. ```tsx title='app/realtime/page.tsx' 'use client'; import { experimental_useRealtime } from '@ai-sdk/react'; import { gateway } from 'ai'; export default function RealtimePage() { const realtime = experimental_useRealtime({ model: gateway.experimental_realtime('openai/gpt-realtime-2'), api: { token: '/api/realtime/setup', }, }); // ... } ``` Do not expose your Gateway API key or OIDC token to browser clients. Only return the short-lived setup response from `getToken()`. The Gateway WebSocket route transports the short-lived auth token via the versioned `Sec-WebSocket-Protocol` subprotocol and the model id via the `?ai-model-id=` query - the WebSocket transport of the `Authorization` and `ai-model-id` headers the HTTP routes use. Subprotocol values must fit the WebSocket token grammar, and the complete `Sec-WebSocket-Protocol` header should stay compact (under an 8 KiB safe header budget). ### Provider Options Gateway [provider options](#gateway-provider-options) — `tags`, `user`, `byok`, compliance flags, and so on — are set under `providerOptions.gateway` in the **session configuration**, mirroring how they ride the request body on the non-realtime routes. Include them in the session configuration you send to the Gateway, and the Gateway applies them server-side: ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; const gatewayOptions: GatewayProviderOptions = { tags: ['cooking-coach', 'v2'], user: 'user-123', }; const sessionConfig = { instructions: 'You are a concise voice assistant.', providerOptions: { gateway: gatewayOptions }, }; ``` The `GatewayProviderOptions` type is exported from `@ai-sdk/gateway` for stable client-facing fields. The type also accepts service-owned option keys so the Gateway service can add or change server-side options without requiring an SDK release for every schema change. Runtime validation is owned by the Gateway service. Provider options are sent in the initial session update after the socket opens, so connect-time options such as `byok` and quota selection require a Gateway that resolves them from that update. Routing knobs like `order` / `only` do not apply to realtime, where the Gateway selects the WebSocket-capable provider. ## Available Models The AI Gateway supports models from OpenAI, Anthropic, Google, Meta, xAI, Mistral, DeepSeek, Amazon Bedrock, Cohere, Perplexity, Alibaba, and other providers. For the complete list of available models, see the [AI Gateway documentation](https://vercel.com/docs/ai-gateway). ## Dynamic Model Discovery You can discover available models programmatically: ```ts import { gateway, generateText } from 'ai'; const availableModels = await gateway.getAvailableModels(); // List all available models availableModels.models.forEach(model => { console.log(`${model.id}: ${model.name}`); if (model.description) { console.log(` Description: ${model.description}`); } if (model.pricing) { console.log(` Input: $${model.pricing.input}/token`); console.log(` Output: $${model.pricing.output}/token`); if (model.pricing.cachedInputTokens) { console.log( ` Cached input (read): $${model.pricing.cachedInputTokens}/token`, ); } if (model.pricing.cacheCreationInputTokens) { console.log( ` Cache creation (write): $${model.pricing.cacheCreationInputTokens}/token`, ); } } }); // Use any discovered model with plain string const { text } = await generateText({ model: availableModels.models[0].id, // e.g., 'openai/gpt-6-astra' prompt: 'Hello world', }); ``` ## Credit Usage You can check your team's current credit balance and usage: ```ts import { gateway } from 'ai'; const credits = await gateway.getCredits(); console.log(`Team balance: ${credits.balance} credits`); console.log(`Team total used: ${credits.total_used} credits`); ``` The `getCredits()` method returns your team's credit information based on the authenticated API key or OIDC token: - **balance** *number* - Your team's current available credit balance - **total\_used** *number* - Total credits consumed by your team ## Generation Lookup Look up detailed information about a specific generation by its ID, including cost, token usage, latency, and provider details. Generation IDs are available in `providerMetadata.gateway.generationId` on both `generateText` and `streamText` responses. When streaming, the generation ID is injected on the first content chunk, so you can capture it early in the stream without waiting for completion. This is especially useful in cases where a network interruption or mid-stream error could prevent you from receiving the final response — since the gateway records the final status server-side, you can use the generation ID to look up the results (including cost, token usage, and finish reason) later via `getGenerationInfo()`. ```ts import { gateway, generateText } from 'ai'; // Make a request const result = await generateText({ model: gateway('anthropic/claude-sonnet-5.5'), prompt: 'Explain quantum entanglement briefly', }); // Get the generation ID from provider metadata const generationId = result.providerMetadata?.gateway?.generationId as | string | undefined; // Look up detailed generation info if (generationId) { const generation = await gateway.getGenerationInfo({ id: generationId }); console.log(`Model: ${generation.model}`); console.log(`Cost: $${generation.totalCost.toFixed(6)}`); console.log(`Latency: ${generation.latency}ms`); console.log(`Prompt tokens: ${generation.promptTokens}`); console.log(`Completion tokens: ${generation.completionTokens}`); } ``` With `streamText`, you can capture the generation ID from the first chunk via `stream`: ```ts import { gateway, streamText } from 'ai'; const result = streamText({ model: gateway('anthropic/claude-sonnet-5.5'), prompt: 'Explain quantum entanglement briefly', }); let generationId: string | undefined; for await (const part of result.stream) { if (!generationId && part.providerMetadata?.gateway?.generationId) { generationId = part.providerMetadata.gateway.generationId as string; console.log(`Generation ID (early): ${generationId}`); } } // Look up cost and usage after the stream completes if (generationId) { const generation = await gateway.getGenerationInfo({ id: generationId }); console.log(`Cost: $${generation.totalCost.toFixed(6)}`); console.log(`Finish reason: ${generation.finishReason}`); } ``` You can also capture the generation ID for every completed model call with `onLanguageModelCallEnd`. The callback runs before client-side tools execute, so the metadata remains available even if the operation is later aborted while a tool is running: ```ts import { gateway, generateText } from 'ai'; await generateText({ model: gateway('anthropic/claude-sonnet-5.5'), prompt: 'Explain quantum entanglement briefly', onLanguageModelCallEnd({ providerMetadata }) { const generationId = providerMetadata?.gateway?.generationId as | string | undefined; if (generationId) { console.log(`Completed Gateway generation: ${generationId}`); } }, }); ``` The `getGenerationInfo()` method accepts: - **id** *string* - The generation ID to look up (format: `gen_`, required) It returns a `GatewayGenerationInfo` object with the following fields: - **id** *string* - The generation ID - **totalCost** *number* - Total cost in USD - **upstreamInferenceCost** *number* - Upstream inference cost in USD (relevant for BYOK) - **usage** *number* - Usage cost in USD (same as totalCost) - **createdAt** *string* - ISO 8601 timestamp when the generation was created - **model** *string* - Model identifier used - **isByok** *boolean* - Whether Bring Your Own Key credentials were used - **providerName** *string* - The provider that served this generation - **streamed** *boolean* - Whether streaming was used - **finishReason** *string* - Finish reason (e.g. `'stop'`) - **latency** *number* - Time to first token in milliseconds - **generationTime** *number* - Total generation time in milliseconds - **promptTokens** *number* - Number of prompt tokens - **completionTokens** *number* - Number of completion tokens - **reasoningTokens** *number* - Reasoning tokens used (if applicable) - **cachedTokens** *number* - Cached tokens used (if applicable) - **cacheCreationTokens** *number* - Cache creation input tokens - **billableWebSearchCalls** *number* - Number of billable web search calls ## Examples ### Basic Text Generation ```ts import { generateText } from 'ai'; const { text } = await generateText({ model: 'anthropic/claude-sonnet-5.5', prompt: 'Write a haiku about programming', }); console.log(text); ``` ### Streaming ```ts import { streamText } from 'ai'; const { textStream } = await streamText({ model: 'openai/gpt-6-astra', prompt: 'Explain the benefits of serverless architecture', }); for await (const textPart of textStream) { process.stdout.write(textPart); } ``` ### Tool Usage ```ts import { generateText, tool } from 'ai'; import { z } from 'zod'; const { text } = await generateText({ model: 'spacexai/grok-4.7', prompt: 'What is the weather like in San Francisco?', tools: { getWeather: tool({ description: 'Get the current weather for a location', inputSchema: z.object({ location: z.string().describe('The location to get weather for'), }), execute: async ({ location }) => { // Your weather API call here return `It's sunny in ${location}`; }, }), }, }); ``` ### Provider-Executed Tools Some providers offer tools that are executed by the provider itself, such as [OpenAI's web search tool](/providers/ai-sdk-providers/openai#web-search-tool). To use these tools through AI Gateway, import the provider to access the tool definitions: ```ts import { generateText, isStepCount } from 'ai'; import { openai } from '@ai-sdk/openai'; const result = await generateText({ model: 'openai/gpt-6-luna', prompt: 'What is the Vercel AI Gateway?', stopWhen: isStepCount(10), tools: { web_search: openai.tools.webSearch({}), }, }); console.dir(result.text); ``` Some provider-executed tools require account-specific configuration (such as Claude Agent Skills) and may not work through AI Gateway. To use these tools, you must bring your own key (BYOK) directly to the provider. ### Gateway Tools The AI Gateway provider includes built-in tools that are executed by the gateway itself. These tools can be used with any model through the gateway. #### Perplexity Search The Perplexity Search tool enables models to search the web using [Perplexity's search API](https://docs.perplexity.ai/guides/search-quickstart). This tool is executed by the AI Gateway and returns web search results that the model can use to provide up-to-date information. ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.4-nano', prompt: 'Search for news about AI regulations in January 2025.', tools: { perplexity_search: gateway.tools.perplexitySearch(), }, }); console.log(result.text); console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2)); console.log('Tool results:', JSON.stringify(result.toolResults, null, 2)); ``` You can also configure the search with optional parameters: ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.4-nano', prompt: 'Search for news about AI regulations from the first week of January 2025.', tools: { perplexity_search: gateway.tools.perplexitySearch({ maxResults: 5, searchLanguageFilter: ['en'], country: 'US', searchDomainFilter: ['reuters.com', 'bbc.com', 'nytimes.com'], }), }, }); console.log(result.text); console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2)); console.log('Tool results:', JSON.stringify(result.toolResults, null, 2)); ``` The Perplexity Search tool supports the following optional configuration options: - **maxResults** *number* The maximum number of search results to return (1-20, default: 10). - **maxTokensPerPage** *number* The maximum number of tokens to extract per search result page (256-2048, default: 2048). - **maxTokens** *number* The maximum total tokens across all search results (default: 25000, max: 1000000). - **searchLanguageFilter** *string\[]* Filter search results by language using ISO 639-1 language codes (e.g., `['en']` for English, `['en', 'es']` for English and Spanish). - **country** *string* Filter search results by country using ISO 3166-1 alpha-2 country codes (e.g., `'US'` for United States, `'GB'` for United Kingdom). - **searchDomainFilter** *string\[]* Limit search results to specific domains (e.g., `['reuters.com', 'bbc.com']`). This is useful for restricting results to trusted sources. - **searchRecencyFilter** *'day' | 'week' | 'month' | 'year'* Filter search results by relative time period. Useful for always getting recent results (e.g., 'week' for results from the last week). The tool works with both `generateText` and `streamText`: ```ts import { gateway, streamText } from 'ai'; const result = streamText({ model: 'openai/gpt-5.4-nano', prompt: 'Search for the latest news about AI regulations.', tools: { perplexity_search: gateway.tools.perplexitySearch(), }, }); for await (const part of result.stream) { switch (part.type) { case 'text-delta': process.stdout.write(part.text); break; case 'tool-call': console.log('\nTool call:', JSON.stringify(part, null, 2)); break; case 'tool-result': console.log('\nTool result:', JSON.stringify(part, null, 2)); break; } } ``` #### Exa Search The Exa Search tool enables models to search the web using [Exa's Search API](https://exa.ai/docs/reference/search-api-guide-for-coding-agents). This tool is executed by the AI Gateway and returns token-efficient web excerpts for agent workflows. ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.4-nano', prompt: 'Find the latest AI regulation updates and summarize the key changes.', tools: { exa_search: gateway.tools.exaSearch(), }, }); console.log(result.text); console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2)); console.log('Tool results:', JSON.stringify(result.toolResults, null, 2)); ``` You can also configure the search with optional parameters: ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.4-nano', prompt: 'Find recent AI regulation news from trusted sources.', tools: { exa_search: gateway.tools.exaSearch({ type: 'fast', numResults: 5, category: 'news', includeDomains: ['reuters.com', 'bbc.com', 'nytimes.com'], contents: { highlights: true, maxAgeHours: 24, }, }), }, }); console.log(result.text); console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2)); console.log('Tool results:', JSON.stringify(result.toolResults, null, 2)); ``` The Exa Search tool supports the following optional configuration options: - **type** *'auto' | 'fast' | 'instant'* Search mode. `'auto'` is the default balance of speed and quality. - **numResults** *number* Maximum number of results to return (1-100, default: 10). - **category** *'company' | 'people' | 'research paper' | 'news' | 'personal site' | 'financial report'* Focus results on a specific content type. - **includeDomains** / **excludeDomains** *string\[]* Restrict or exclude search results by domain. - **startPublishedDate** / **endPublishedDate** *string* Filter results by ISO 8601 publication date. - **contents** *object* Control result extraction and freshness: - `text` - Return full page text, optionally capped with `maxCharacters` - `highlights` - Return token-efficient excerpts - `maxAgeHours` - Control cached content freshness - `livecrawlTimeout` - Livecrawl timeout in milliseconds - `subpages` / `subpageTarget` - Crawl related subpages - `extras.links` / `extras.imageLinks` - Extract links from pages The tool works with both `generateText` and `streamText`. This initial Gateway integration supports Exa's plain Search modes and token-efficient content controls. Deep synthesis modes and generated summaries are intentionally not exposed yet because they have separate pricing from standard Search. #### Browserbase Search The Browserbase Search tool enables models to search the web using [Browserbase's Search API](https://docs.browserbase.com/reference/api/web-search). It is executed by AI Gateway and returns fast, structured search results without opening a browser session. ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.6-luna', prompt: 'Find official guidance on traveling with power banks on U.S. flights. Return the most relevant page titles and URLs.', tools: { browserbase_search: gateway.tools.browserbaseSearch(), }, }); console.log(result.text); console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2)); console.log('Tool results:', JSON.stringify(result.toolResults, null, 2)); ``` You can configure the maximum number of results returned by each search: ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.6-luna', prompt: 'Find three recent reviews comparing e-readers for outdoor reading. Return their titles and URLs.', tools: { browserbase_search: gateway.tools.browserbaseSearch({ numResults: 3, }), }, }); console.log(result.text); ``` The Browserbase Search tool supports this optional configuration option: - **numResults** *number* Maximum number of search results to return (1-25, default: 10). The tool works with both `generateText` and `streamText`. #### Browserbase Fetch The Browserbase Fetch tool enables models to retrieve page content using [Browserbase's Fetch API](https://docs.browserbase.com/platform/fetch/overview). It is a lightweight option for pages that do not require JavaScript execution or browser interaction, and it supports raw, Markdown, and schema-driven JSON output. ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.6-luna', prompt: 'Fetch https://www.nps.gov/yose/planyourvisit/halfdome.htm and summarize the permit requirements and main safety warnings.', tools: { browserbase_fetch: gateway.tools.browserbaseFetch(), }, }); console.log(result.text); console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2)); console.log('Tool results:', JSON.stringify(result.toolResults, null, 2)); ``` You can configure the output format and request behavior. For example, use JSON extraction to return content matching a JSON Schema: ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.6-luna', prompt: 'Fetch https://www.nps.gov/yose/planyourvisit/halfdome.htm and extract its page title, whether a permit is required, and three safety tips.', tools: { browserbase_fetch: gateway.tools.browserbaseFetch({ allowRedirects: true, format: 'json', schema: { type: 'object', properties: { pageTitle: { type: 'string' }, permitRequired: { type: 'boolean' }, safetyTips: { type: 'array', items: { type: 'string' }, maxItems: 3, }, }, required: ['pageTitle', 'permitRequired', 'safetyTips'], }, }), }, }); console.log(result.text); ``` The Browserbase Fetch tool supports these optional configuration options: - **format** *'raw' | 'markdown' | 'json'* Output format for the fetched content. `raw` returns the upstream response body unchanged and is the default. `markdown` converts the page to Markdown. `json` performs structured extraction and requires `schema`. - **schema** *object* JSON Schema describing the desired structured content. Only used when `format` is `json`. - **allowRedirects** *boolean* Follow HTTP redirects. Defaults to `false`. - **proxies** *boolean* Route the request through Browserbase's proxy network. Defaults to `false`. - **allowInsecureSsl** *boolean* Bypass TLS certificate verification. Defaults to `false`; only enable it for trusted hosts. The Fetch API does not execute JavaScript. Use a browser session for interactive or JavaScript-rendered pages. The tool works with both `generateText` and `streamText`. #### Tako Search The Tako Search tool enables models to search the web and Tako's curated knowledge graph in a single call using [Tako's Search API](https://docs.tako.com/documentation/integrating-tako/search/overview). This tool is executed by the AI Gateway and returns token-efficient web excerpts, plus knowledge-graph results that carry structured data, premium source attribution, and an embed-ready visualization. ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.4-nano', prompt: 'How has Nvidia quarterly revenue changed over the last two years?', tools: { tako_search: gateway.tools.takoSearch(), }, }); console.log(result.text); console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2)); console.log('Tool results:', JSON.stringify(result.toolResults, null, 2)); ``` Configure the search with effort, source selection, and localization defaults. These defaults override values the model includes in a tool call: ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.4-nano', prompt: 'AMD vs. Nvidia headcount since 2013', tools: { tako_search: gateway.tools.takoSearch({ effort: 'fast', sources: { data: { contentFormat: 'json_compact', includeContents: true, maxRows: 100, }, web: { count: 3, }, }, countryCode: 'US', locale: 'en-US', }), }, }); console.log(result.text); ``` The Tako Search tool supports these optional configuration options: - **effort** *'fast' | 'instant' | 'deep'* - Search effort. `'fast'` is the balanced default, `'instant'` favors cached low-latency results, and `'deep'` broadens retrieval with reranking at higher cost and latency. - **sources** *object* - Omit to search both curated Tako data and the live web. When set, only source keys present are searched. - **sources.web** *object* - Configure web results: - **count** *number* - Maximum web results, 1-20. - **category** *'finance' | 'news' | 'sports'* - Restrict web results to one category. - **includeDomains** / **excludeDomains** *string\[]* - Only return, or drop, results from these bare domains. Up to 20 each. - **publishedAfter** / **publishedBefore** *string* - Keep results published on or after, or on or before, this `YYYY-MM-DD` date. - **snippetMaxChars** *number* - Character cap on each result's text excerpt. Defaults to 4,000; maximum 20,000. - **highlights** *boolean* - Return query-relevant passages as each result's snippet instead of the opening text of the page. The snippet can hold multiple passages joined by `' ... '`, and a page with no highlight returns `snippet: null`. Defaults to `true` in the AI Gateway. - **includeContents** *boolean* - Inline each page's extracted full text in `content`. **articleContentMaxChars** caps it, defaulting to 30,000 characters (maximum 1,000,000). - **sources.data** *object* - Configure data results: - **count** *number* - Maximum data results, 1-20. Defaults to 5. - **includeContents** *boolean* - Inline each card's underlying rows in `content.dataset` as typed, unit-labeled columns. This adds a data surcharge based on the row count and dataset source. To estimate cost, first search with `includeContents: false` and inspect `cards.content.export_pricing`. Because this applies to every returned card, use `count` and `maxRows` to control cost. - **contentFormat** *'json\_compact' | 'json\_records' | 'csv' | 'card\_json'* - Serialization for inlined card data. Defaults to `'json_compact'`. - **maxRows** *number* - Row cap per result. Omit to use the allowance in `cards.content.export_pricing`. A data surcharge applies per 1,000 exported rows; lower values reduce cost. - **nodeIds** *string\[]* - Data Graph node IDs to prioritize. Up to 20. - **strict** *boolean* - Only return cards matching `nodeIds`. Requires at least one `nodeIds` value. - **location** *object* - End-user `{ latitude, longitude }` coordinates for localized results. - **countryCode** *string* - ISO 3166-1 alpha-2 country code, such as `'US'`. - **locale** *string* - BCP-47 locale, such as `'en-US'`. - **timezone** *string* - IANA timezone, such as `'America/New_York'`. - **outputSettings** *object* - Card rendering controls. `forceRefresh` is available only with `'instant'` effort. - **includeRelated** *number* - Number of related search suggestions to include (1-20). For precise entity matching, quote a phrase in the query and optionally append a label: `"Tesla":PRODUCT price`. Tako treats the quoted phrase as one entity before searching. The tool works with both `generateText` and `streamText`: ```ts import { gateway, streamText } from 'ai'; const result = streamText({ model: 'openai/gpt-5.4-nano', prompt: 'What is the latest US inflation rate?', tools: { tako_search: gateway.tools.takoSearch(), }, }); for await (const part of result.stream) { switch (part.type) { case 'text-delta': process.stdout.write(part.text); break; case 'tool-call': console.log('\nTool call:', JSON.stringify(part, null, 2)); break; case 'tool-result': console.log('\nTool result:', JSON.stringify(part, null, 2)); break; } } ``` #### Parallel Search The Parallel Search tool enables models to search the web using [Parallel AI's Search API](https://docs.parallel.ai/api-reference/search/search). The model provides a research objective and a few keyword queries, and the tool returns relevant excerpts from web pages, optimized for LLM consumption. ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.4-nano', prompt: 'Research the latest developments in quantum computing.', tools: { parallel_search: gateway.tools.parallelSearch(), }, }); console.log(result.text); console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2)); console.log('Tool results:', JSON.stringify(result.toolResults, null, 2)); ``` You can also configure the search with optional parameters: ```ts import { gateway, generateText } from 'ai'; const result = await generateText({ model: 'openai/gpt-5.4-nano', prompt: 'Find detailed information about TypeScript 5.0 features.', tools: { parallel_search: gateway.tools.parallelSearch({ mode: 'advanced', location: 'us', maxResults: 5, sourcePolicy: { includeDomains: ['typescriptlang.org', 'github.com'], }, excerpts: { maxCharsPerResult: 8000, }, }), }, }); console.log(result.text); console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2)); console.log('Tool results:', JSON.stringify(result.toolResults, null, 2)); ``` The Parallel Search tool supports the following optional configuration options: - **mode** *'turbo' | 'fast' | 'basic' | 'advanced'* Search mode. Defaults to `'basic'`. See the [AI Gateway web search docs](https://vercel.com/docs/ai-gateway/models-and-providers/web-search#using-parallel-search) for pricing. - `'turbo'` - Lowest latency, shorter excerpts. Supports English and Japanese only. - `'fast'` - Low latency, shorter excerpts. - `'basic'` - Balanced latency and excerpt depth (default). - `'advanced'` - Most thorough retrieval, higher latency. `'one-shot'` and `'agentic'` are deprecated but still accepted: `'one-shot'` runs as `'basic'` and `'agentic'` runs as `'advanced'`. - **maxResults** *number* Maximum number of results to return (1-20). Defaults to 10 if not specified. - **sourcePolicy** *object* Source policy for controlling which domains to include/exclude: - `includeDomains` - Domains or domain/path prefixes to include, such as `'wikipedia.org'` or `'docs.python.org/3'`. When set, `excludeDomains` is ignored. - `excludeDomains` - Domains or domain/path prefixes to exclude - `afterDate` - Only include results published on or after this date (`YYYY-MM-DD`) The include and exclude lists can hold up to 200 entries combined. - **maxCharsTotal** *number* Maximum total characters across all result excerpts. - **excerpts** *object* Excerpt configuration for controlling result length: - `maxCharsPerResult` - Maximum characters per result - `maxCharsTotal` - Deprecated; use the top-level `maxCharsTotal` - **fetchPolicy** *object* Fetch policy for controlling content freshness: - `maxAgeSeconds` - Maximum age of cached content, in seconds, before a live fetch is attempted. Minimum 600; lower values are raised to 600. - `timeoutSeconds` - Timeout for a live fetch, in seconds. The gateway caps it to stay within its own tool timeout. - `disableCacheFallback` - Return an error instead of older cached content when a live fetch fails. Defaults to `false`. - **location** *string* ISO 3166-1 alpha-2 country code to localize results, such as `'us'`. Invalid codes are ignored with a warning. Each tool result contains a `search_id` and `results`, ordered by decreasing relevance, and may include a `session_id`. Each result has a `url`, a `title`, a `publish_date` (both may be `null`), and `excerpts` (an array of markdown strings). The result can also include `warnings`, for example when an invalid `location` is ignored. The tool works with both `generateText` and `streamText`: ```ts import { gateway, streamText } from 'ai'; const result = streamText({ model: 'openai/gpt-5.4-nano', prompt: 'Research the latest AI safety guidelines.', tools: { parallel_search: gateway.tools.parallelSearch(), }, }); for await (const part of result.stream) { switch (part.type) { case 'text-delta': process.stdout.write(part.text); break; case 'tool-call': console.log('\nTool call:', JSON.stringify(part, null, 2)); break; case 'tool-result': console.log('\nTool result:', JSON.stringify(part, null, 2)); break; } } ``` ### Custom Reporting Track usage per end-user and categorize requests with tags, then query the data through the reporting API. #### Usage Tracking with User and Tags ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; const { text } = await generateText({ model: 'openai/gpt-6-astra', prompt: 'Summarize this document...', providerOptions: { gateway: { user: 'user-abc-123', // Track usage for this specific end-user tags: ['document-summary', 'premium-feature'], // Categorize for reporting } satisfies GatewayProviderOptions, }, }); ``` This allows you to: - View usage and costs broken down by end-user in your analytics - Filter and analyze spending by feature or use case using tags - Track which users or features are driving the most AI usage #### Querying Spend Reports Use the `getSpendReport()` method to query usage data programmatically. The reporting API is only available for Vercel Pro and Enterprise plans. For pricing, see the [Custom Reporting docs](https://vercel.com/docs/ai-gateway/capabilities/custom-reporting). ```ts import { gateway } from 'ai'; const report = await gateway.getSpendReport({ startDate: '2026-03-01', endDate: '2026-03-25', groupBy: 'model', }); for (const row of report.results) { console.log(`${row.model}: $${row.totalCost.toFixed(4)}`); } ``` The `getSpendReport()` method accepts the following parameters: - **startDate** *string* - Start date in `YYYY-MM-DD` format (inclusive, required) - **endDate** *string* - End date in `YYYY-MM-DD` format (inclusive, required) - **groupBy** *string* - Aggregation dimension: `'day'` (default), `'user'`, `'model'`, `'tag'`, `'provider'`, or `'credential_type'` - **datePart** *string* - Time granularity when `groupBy` is `'day'`: `'day'` or `'hour'` - **userId** *string* - Filter to a specific user - **model** *string* - Filter to a specific model (e.g. `'anthropic/claude-sonnet-5.5'`) - **provider** *string* - Filter to a specific provider (e.g. `'anthropic'`) - **credentialType** *string* - Filter by `'byok'` or `'system'` credentials - **tags** *string\[]* - Filter to requests matching these tags Each row in `results` contains a grouping field (matching your `groupBy` choice) and metrics: - **totalCost** *number* - Total cost in USD - **marketCost** *number* - Market cost in USD - **inputTokens** *number* - Number of input tokens - **outputTokens** *number* - Number of output tokens - **cachedInputTokens** *number* - Number of cached input tokens - **cacheCreationInputTokens** *number* - Number of cache creation input tokens - **reasoningTokens** *number* - Number of reasoning tokens - **requestCount** *number* - Number of requests You can combine tracking and querying to analyze spend by tags you defined: ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { gateway, streamText } from 'ai'; // 1. Make requests with tags const result = streamText({ model: gateway('anthropic/claude-haiku-5.5'), prompt: 'Summarize this quarter's results', providerOptions: { gateway: { tags: ['team:finance', 'feature:summaries'], } satisfies GatewayProviderOptions, }, }); // 2. Later, query spend filtered by those tags const report = await gateway.getSpendReport({ startDate: '2026-03-01', endDate: '2026-03-31', groupBy: 'tag', tags: ['team:finance'], }); for (const row of report.results) { console.log(`${row.tag}: $${row.totalCost.toFixed(4)} (${row.requestCount} requests)`); } ``` ## Provider Options The AI Gateway provider accepts provider options that control routing behavior and provider-specific configurations. ### Gateway Provider Options You can use the `gateway` key in `providerOptions` to control how AI Gateway routes requests: ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; const { text } = await generateText({ model: 'anthropic/claude-sonnet-5.5', prompt: 'Explain quantum computing', providerOptions: { gateway: { order: ['vertex', 'anthropic'], // Try Vertex AI first, then Anthropic only: ['vertex', 'anthropic'], // Only use these providers } satisfies GatewayProviderOptions, }, }); ``` The following gateway provider options are available: - **order** *string\[]* Specifies the sequence of providers to attempt when routing requests. The gateway will try providers in the order specified. If a provider fails or is unavailable, it will move to the next provider in the list. Example: `order: ['bedrock', 'anthropic']` will attempt Amazon Bedrock first, then fall back to Anthropic. - **only** *string\[]* Restricts routing to only the specified providers. When set, the gateway will never route to providers not in this list, even if they would otherwise be available. Example: `only: ['anthropic', 'vertex']` will only allow routing to Anthropic or Vertex AI. - **sort** *'cost' | 'ttft' | 'tps'* Sorts available providers by a performance or cost metric before routing. The gateway will try the best-scoring provider first and fall back through the rest in sorted order. If unspecified, providers are ordered using the gateway's default system ranking. - `'cost'` — lowest cost first - `'ttft'` — lowest time-to-first-token first - `'tps'` — highest tokens-per-second first When combined with `order`, the user-specified providers are promoted to the front while remaining providers follow the sorted order. Example: `sort: 'ttft'` will route to the provider with the fastest time-to-first-token. When `sort` is active, the response's `providerMetadata.gateway.routing.sort` object contains the sort option used, the resulting execution order, per-provider metric values, and any providers that were deprioritized. - **models** *string\[] | \[GatewayModelFallback, ...string\[]]* Specifies fallback models in attempt order. String entries keep their existing behavior: the gateway tries them when the primary model fails or is unavailable. Decision requests can also start the list with one conditional `{ model, when }` entry to retry a successful but uncertain decision with another model. Only the first entry can be conditional, and only one conditional entry is allowed. If the primary model fails, the gateway also advances to the conditional entry under its existing error fallback behavior. Conditional entries are rejected on non-decision requests. Example: `models: ['openai/gpt-5.4-nano', 'google/gemini-3.8-flash']` will try the fallback models in order if the primary model fails. A direct condition uses `{ question, confidenceBelow }` for Choice and Score questions, or `{ question, probabilityBetween: [minimum, maximum] }` for Boolean questions. Leave out `question` to check every question of that type: `{ confidenceBelow: 0.6 }` matches when any Choice or Score answer is below 0.6, and a request with no Choice or Score questions never matches it. Boolean probability is P(true), not confidence in the selected Boolean outcome. Bounds are inclusive, finite, ordered, and within `[0, 1]`. Combine conditions with `{ any: [...] }`, `{ all: [...] }`, or `{ atLeast: { count, conditions: [...] } }`. Each condition list holds 1 to 20 conditions. `atLeast.count` must be an integer from `1` through the number of conditions. Conditions nest at most 5 levels deep. Question IDs are 1 to 256 characters, and the conditional `model` must not be empty. The SDK checks these bounds before sending the request, and the gateway also checks that each question exists and has a kind that matches its condition. String error fallbacks may follow the conditional entry. - **user** *string* Optional identifier for the end user on whose behalf the request is being made. This is used for spend tracking and attribution purposes, allowing you to track usage per end-user in your application. Example: `user: 'user-123'` will associate this request with end-user ID "user-123" in usage reports. - **tags** *string\[]* Optional array of tags for categorizing and filtering usage in reports. Useful for tracking spend by feature, prompt version, or any other dimension relevant to your application. Example: `tags: ['chat', 'v2']` will tag this request with "chat" and "v2" for filtering in usage analytics. - **byok** *Record\>>* Request-scoped BYOK (Bring Your Own Key) credentials to use for this request. When provided, any cached BYOK credentials configured in the gateway system are not considered. Requests may still fall back to use system credentials if the provided credentials fail. Each provider can have multiple credentials (tried in order). The structure is a record where keys are provider slugs and values are arrays of credential objects. Each credential can optionally include a `modelMappings` array to map AI Gateway model slugs to your deployment names (for example, custom Azure deployment names). If a BYOK request fails, the gateway falls back to system credentials using the default model name. Examples: - Single provider: `byok: { 'anthropic': [{ apiKey: 'sk-ant-...' }] }` - Multiple credentials: `byok: { 'vertex': [{ project: 'proj-1', googleCredentials: { privateKey: '...', clientEmail: '...' } }, { project: 'proj-2', googleCredentials: { privateKey: '...', clientEmail: '...' } }] }` - Multiple providers: `byok: { 'anthropic': [{ apiKey: '...' }], 'bedrock': [{ accessKeyId: '...', secretAccessKey: '...' }] }` - With model mappings: `byok: { 'azure': [{ apiKey: '...', resourceName: '...', modelMappings: [{ gatewayModelSlug: 'openai/gpt-5.4-nano', customModelId: 'my-deployment' }] }] }` - **zeroDataRetention** *boolean* Restricts routing to providers with zero data retention agreements with Vercel for AI Gateway. BYOK credentials are skipped by default, since your provider agreements differ from Vercel's. When this filter is on, AI Gateway routes only to providers Vercel has ZDR agreements with for the model. If you have BYOK keys marked as ZDR, those keys are tried first, then AI Gateway falls back to its system credentials. You are responsible for the accuracy of that marking. Applies to both account-wide and request-level ZDR. The request fails if no ZDR-eligible credentials are available. Request-level ZDR is only available for Vercel Pro and Enterprise plans. - **disallowPromptTraining** *boolean* Restricts routing to providers that have agreements with Vercel for AI Gateway to not use prompts for model training. When using BYOK credentials, this filter is not applied. If BYOK credentials fail and the request falls back to system credentials, only providers that do not train on prompt data will be used. If there are no providers available for the model that disallow prompt training, the request will fail. - **quotaEntityId** *string* The unique identifier for the entity against which quota is tracked. Used for quota management and enforcement purposes. - **has** *Array\<'implicit-caching' | 'reasoning' | 'structured-output' | 'tool-use' | 'vision' | `quantization:${string}` | `!quantization:${string}`>* Restricts routing to provider models that have all of the specified capabilities. Applies to both BYOK and system credentials, since the capability is a property of the model rather than the credential. If no provider model for the requested model satisfies the capabilities, the request fails. Unsupported values are rejected. Supported capabilities: - `'implicit-caching'` — models that perform automatic (implicit) prompt caching. - `'reasoning'` — models that support reasoning. - `'structured-output'` — models that support schema-constrained output. - `'tool-use'` — models that support tool calling. - `'vision'` — models that accept image input. Weight-format conditions route on the serving provider's recorded weight format: `'quantization:fp8'` requires a provider serving fp8 weights, while `'!quantization:fp8'` excludes fp8 providers. A negated condition also matches providers whose weight format is not recorded, so exclusions remain usable while the catalog is populated. Example: `has: ['implicit-caching']` will only route to models that support implicit caching. Example: `has: ['vision']` will only route to models that accept image input. Example: `has: ['tool-use']` will only route to models that support tool calling. Example: `has: ['!quantization:fp8']` will only route to providers that do not serve fp8 weights. - **providerTimeouts** *object* Per-provider timeouts for BYOK credentials in milliseconds. Controls how long to wait for a provider to start responding before falling back to the next available provider. Example: `providerTimeouts: { byok: { openai: 5000, anthropic: 2000 } }` For full details, see [Provider Timeouts](https://vercel.com/docs/ai-gateway/models-and-providers/provider-timeouts). - **serviceTier** *'flex' | 'priority'* Unified service tier intent. The gateway translates it into whichever per-provider option each provider expects, and overrides any tier also set in the per-provider options. Leave unset for provider default. See the [OpenAI](/providers/ai-sdk-providers/openai), [Google Generative AI](/providers/ai-sdk-providers/google), and [Google Vertex AI](/providers/ai-sdk-providers/google-vertex) provider docs for tier semantics, model availability, and graceful downgrade behavior on each provider. ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; const { text } = await generateText({ model: 'openai/gpt-6-luna', prompt: 'Hello', providerOptions: { gateway: { serviceTier: 'priority', } satisfies GatewayProviderOptions, }, }); ``` You can combine these options to have fine-grained control over routing and tracking: ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; const { text } = await generateText({ model: 'anthropic/claude-sonnet-5.5', prompt: 'Write a haiku about programming', providerOptions: { gateway: { order: ['vertex'], // Prefer Vertex AI only: ['anthropic', 'vertex'], // Only allow these providers } satisfies GatewayProviderOptions, }, }); ``` #### Model Fallbacks Example The `models` option enables automatic fallback to alternative models when the primary model fails: ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; const { text } = await generateText({ model: 'openai/gpt-6-astra', // Primary model prompt: 'Write a TypeScript haiku', providerOptions: { gateway: { models: ['openai/gpt-5.4-nano', 'google/gemini-3.8-flash'], // Fallback models } satisfies GatewayProviderOptions, }, }); // This will: // 1. Try openai/gpt-6-astra first // 2. If it fails, try openai/gpt-5.4-nano // 3. If that fails, try google/gemini-3.8-flash // 4. Return the result from the first model that succeeds ``` #### Conditional Decision Fallbacks Example Conditional model entries are available only for decision requests. Keep the configuration under `providerOptions.gateway.models`. `experimental_decide` does not have a top-level fallback option. ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { experimental_decide } from 'ai'; const questions = { department: { type: 'choice', instructions: 'Which team should handle this?', criteria: { billing: 'Charges and refunds', support: 'Other requests' }, }, requestsRefund: { type: 'boolean', instructions: 'Is the customer requesting money back?', }, } as const; const result = await experimental_decide({ model: 'typesafe-ai/jev', state: 'I was charged twice. Please refund the duplicate.', questions, providerOptions: { gateway: { models: [ { model: 'openai/gpt-5.6-sol', when: { any: [ { question: 'department', confidenceBelow: 0.6 }, { question: 'requestsRefund', probabilityBetween: [0.4, 0.6], }, ], }, }, ], } satisfies GatewayProviderOptions, }, }); // The model that produced the answers, the fallback when the condition matched. console.log(result.response.modelId); console.log(JSON.stringify(result.providerMetadata?.gateway?.routing, null, 2)); ``` When the condition matches, `result.response.modelId` names the fallback model, and `providerMetadata.gateway.routing.modelAttempts` lists the primary and fallback attempts. The successful attempt of each stage carries its own `generationId`, `usage`, and cost fields, and the first fallback attempt lists the questions that triggered it in `triggeredBy`. #### Zero Data Retention Example Set `zeroDataRetention` to true to route requests to providers with zero data retention agreements with Vercel for AI Gateway. BYOK credentials are skipped by default, since your provider agreements differ from Vercel's. When this filter is on, AI Gateway routes only to providers Vercel has ZDR agreements with for the model. If you have BYOK keys marked as ZDR, those keys are tried first, then AI Gateway falls back to its system credentials. You are responsible for the accuracy of that marking. Applies to both account-wide and request-level ZDR. The request fails if no ZDR-eligible credentials are available. When `zeroDataRetention` is `false` or not specified, there is no enforcement of restricting routing. Request-level ZDR is only available for Vercel Pro and Enterprise plans. ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; const { text } = await generateText({ model: 'anthropic/claude-sonnet-5.5', prompt: 'Analyze this sensitive document...', providerOptions: { gateway: { zeroDataRetention: true, } satisfies GatewayProviderOptions, }, }); ``` #### Disallow Prompt Training Example Set `disallowPromptTraining` to true to route requests to providers that have agreements with Vercel for AI Gateway to not use prompts for model training. When using BYOK credentials, this filter is not applied. If BYOK credentials fail and the request falls back to system credentials, only providers that do not train on prompt data will be used. If there are no providers available for the model that disallow prompt training, the request will fail. When `disallowPromptTraining` is `false` or not specified, there is no enforcement of restricting routing. ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; const { text } = await generateText({ model: 'anthropic/claude-sonnet-5.5', prompt: 'Analyze this proprietary business data...', providerOptions: { gateway: { disallowPromptTraining: true, } satisfies GatewayProviderOptions, }, }); ``` #### Quota Entity ID Example Set `quotaEntityId` to track and enforce quota against a specific entity. This is useful for multi-tenant applications where you need to manage quota at the entity level (e.g., per organization or team). ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; const { text } = await generateText({ model: 'anthropic/claude-sonnet-5.5', prompt: 'Summarize this report...', providerOptions: { gateway: { quotaEntityId: 'org-123', } satisfies GatewayProviderOptions, }, }); ``` #### Filtering by Model Capability Set `has` to restrict routing to provider models that have the specified capabilities. This applies to both BYOK and system credentials, since the capability is a property of the model rather than the credential. `'implicit-caching'` limits routing to models that perform automatic prompt caching, `'vision'` limits routing to models that accept image input, `'reasoning'` limits routing to models that support reasoning, `'structured-output'` limits routing to models that support schema-constrained output, and `'tool-use'` limits routing to models that support tool calling. Weight-format conditions (`'quantization:fp8'` to require, `'!quantization:fp8'` to exclude) limit routing by the serving provider's recorded weight format. If no provider model for the requested model satisfies the capabilities, the request fails. ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; const { text } = await generateText({ model: 'openai/gpt-6-astra', prompt: 'Summarize this report...', providerOptions: { gateway: { has: ['implicit-caching'], } satisfies GatewayProviderOptions, }, }); ``` Use `'vision'` when the request sends image input, so the gateway only routes to provider models that can accept it: ```ts import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; import fs from 'node:fs'; const { text } = await generateText({ model: 'anthropic/claude-sonnet-5.5', messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe the image in detail.' }, { type: 'file', mediaType: 'image/png', data: fs.readFileSync('./data/comic-cat.png'), }, ], }, ], providerOptions: { gateway: { has: ['vision'], } satisfies GatewayProviderOptions, }, }); ``` ### Provider-Specific Options When using provider-specific options through AI Gateway, use the actual provider name (e.g. `anthropic`, `openai`, not `gateway`) as the key: ```ts import type { AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import type { GatewayProviderOptions } from '@ai-sdk/gateway'; import { generateText } from 'ai'; const { text } = await generateText({ model: 'anthropic/claude-sonnet-4.6', prompt: 'Explain quantum computing', providerOptions: { gateway: { order: ['vertex', 'anthropic'], } satisfies GatewayProviderOptions, anthropic: { thinking: { type: 'enabled', budgetTokens: 12000 }, } satisfies AnthropicLanguageModelOptions, }, }); ``` This works with any provider supported by AI Gateway. Each provider has its own set of options - see the individual [provider documentation pages](/providers/ai-sdk-providers) for details on provider-specific options. ### Available Providers AI Gateway supports routing to 20+ providers. For a complete list of available providers and their slugs, see the [AI Gateway documentation](https://vercel.com/docs/ai-gateway/provider-options#available-providers). ## Model Capabilities Model capabilities depend on the specific provider and model you're using. For detailed capability information, see: - [AI Gateway provider options](https://vercel.com/docs/ai-gateway/provider-options#available-providers) for an overview of available providers - Individual [AI SDK provider pages](/providers/ai-sdk-providers) for specific model capabilities and features --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Alibaba description: Learn how to use Alibaba with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/alibaba" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [Alibaba Cloud Model Studio](https://modelstudio.console.alibabacloud.com/) provides access to the Qwen model series, including advanced reasoning capabilities. You can use Alibaba models through [AI Gateway](/providers/ai-sdk-providers/ai-gateway) (recommended) or connect directly with `@ai-sdk/alibaba`. ```ts provider="alibaba" model="alibaba/qwen3.7-max" import { generateText } from 'ai'; const { text } = await generateText({ model: "alibaba/qwen3.7-max", prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. Gateway feature and provider-option support depends on the model and the provider serving the request. See [Gateway provider options](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options). ## Using `@ai-sdk/alibaba` For direct access to Alibaba, install `ai` and `@ai-sdk/alibaba`: ```bash pnpm add ai @ai-sdk/alibaba ``` Set your Alibaba API credential in the `ALIBABA_API_KEY` environment variable: ```bash export ALIBABA_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/alibaba`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/alibaba` provider, including its model factories and settings. Gateway model and feature availability can differ. API keys can be obtained from the [Console](https://modelstudio.console.alibabacloud.com/). ## Provider Instance You can import the default provider instance `alibaba` from `@ai-sdk/alibaba`: ```ts import { alibaba } from '@ai-sdk/alibaba'; ``` For custom configuration, you can import `createAlibaba` and create a provider instance with your settings: ```ts import { createAlibaba } from '@ai-sdk/alibaba'; const alibaba = createAlibaba({ apiKey: process.env.ALIBABA_API_KEY ?? '', }); ``` You can use the following optional settings to customize the Alibaba provider instance: - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use proxy servers or regional endpoints. The default prefix is `https://dashscope-intl.aliyuncs.com/compatible-mode/v1`. - **videoBaseURL** *string* Use a different URL prefix for video generation API calls. The video API uses the DashScope native endpoint (not the OpenAI-compatible endpoint). The default prefix is `https://dashscope-intl.aliyuncs.com`. - **embeddingBaseURL** *string* Use a different URL prefix for embedding API calls. The embedding API uses the DashScope native endpoint (not the OpenAI-compatible endpoint). The default prefix is `https://dashscope-intl.aliyuncs.com/api/v1`. - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `ALIBABA_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. - **includeUsage** *boolean* Include usage information in streaming responses. When enabled, token usage will be included in the final chunk. Defaults to `true`. ## Language Models You can create language models using a provider instance: ```ts import { alibaba } from '@ai-sdk/alibaba'; import { generateText } from 'ai'; const { text } = await generateText({ model: alibaba('qwen-plus'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` You can also use the `.chatModel()` or `.languageModel()` factory methods: ```ts const model = alibaba.chatModel('qwen-plus'); // or const model = alibaba.languageModel('qwen-plus'); ``` Alibaba language models can be used in the `streamText` function (see [AI SDK Core](/docs/ai-sdk-core)). The following optional provider options are available for Alibaba models: - **enableThinking** *boolean* Enable thinking/reasoning mode for supported models. When enabled, the model generates reasoning content before the response. Defaults to `false`. - **thinkingBudget** *number* Maximum number of reasoning tokens to generate. Limits the length of thinking content. - **preserveThinking** *boolean* Preserve reasoning from previous assistant messages for supported models. When enabled, reasoning is sent separately as Alibaba `reasoning_content` and `preserve_thinking` is enabled. Defaults to `true` for models that support preserved thinking; set to `false` to opt out. Append AI SDK `responseMessages` unchanged when continuing the conversation. See Alibaba's [preserved-thinking documentation](https://docs.qwencloud.com/developer-guides/text-generation/thinking#preserve-thinking-in-multi-turn) for the list of supported models. - **parallelToolCalls** *boolean* Whether to enable parallel function calling during tool use. Defaults to `true`. ### Thinking Mode Alibaba's Qwen models support thinking/reasoning mode for complex problem-solving: ```ts import { alibaba, type AlibabaLanguageModelChatOptions } from '@ai-sdk/alibaba'; import { generateText } from 'ai'; const { text, reasoning } = await generateText({ model: alibaba('qwen3-max'), providerOptions: { alibaba: { enableThinking: true, thinkingBudget: 2048, } satisfies AlibabaLanguageModelChatOptions, }, prompt: 'How many "r"s are in the word "strawberry"?', }); console.log('Reasoning:', reasoning); console.log('Answer:', text); ``` For models that are thinking-only (like `qwen3-235b-a22b-thinking-2507`), thinking mode is enabled by default. ### Preserved Thinking in Multi-Turn Conversations For models that support preserved thinking, the AI SDK replays reasoning from previous assistant messages as Alibaba `reasoning_content` by default, so the model can build on its earlier thought process: ```ts import { alibaba, type AlibabaLanguageModelChatOptions } from '@ai-sdk/alibaba'; import { generateText, type ModelMessage } from 'ai'; const providerOptions = { alibaba: { enableThinking: true, thinkingBudget: 2048, } satisfies AlibabaLanguageModelChatOptions, }; const opening: ModelMessage = { role: 'user', content: 'Is Kafka or RocketMQ a better fit for transactional messages?', }; const first = await generateText({ model: alibaba('qwen3.7-max'), messages: [opening], providerOptions, }); const second = await generateText({ model: alibaba('qwen3.7-max'), messages: [ opening, ...first.responseMessages, // append unchanged to keep the reasoning parts { role: 'user', content: 'Which tradeoff mattered most?' }, ], providerOptions, }); ``` When continuing the conversation, append `responseMessages` unchanged. If you rebuild assistant history from visible text only, the reasoning parts are discarded before the provider can serialize them as `reasoning_content`. Set `preserveThinking: false` to opt out of replay, and note the following caveats: - `preserveThinking` does not enable thinking by itself. Pair it with `enableThinking`/`thinkingBudget` or the top-level `reasoning` option. - It is enabled by default only for models that Alibaba documents as supporting preserved thinking; for other models, `preserve_thinking` is not sent unless you set the option explicitly. See Alibaba's [preserved-thinking documentation](https://docs.qwencloud.com/developer-guides/text-generation/thinking#preserve-thinking-in-multi-turn) for the current list of supported models. - Reasoning from the current tool-call round (after the last user message) is always sent back with tool results, as Alibaba recommends; the option only controls reasoning from earlier rounds. - Preserved reasoning increases input token usage and billing. - Historical reasoning remains separate from visible assistant text; it is never merged into `content`. ### Tool Calling Alibaba models support tool calling with parallel execution: ```ts import { alibaba } from '@ai-sdk/alibaba'; import { generateText, tool } from 'ai'; import { z } from 'zod'; const { text } = await generateText({ model: alibaba('qwen-plus'), tools: { weather: tool({ description: 'Get the weather in a location', inputSchema: z.object({ location: z.string().describe('The location to get the weather for'), }), execute: async ({ location }) => ({ location, temperature: 72 + Math.floor(Math.random() * 21) - 10, }), }), }, prompt: 'What is the weather in San Francisco?', }); ``` ### Prompt Caching Alibaba supports both implicit and explicit prompt caching to reduce costs for repeated prompts. **Implicit caching** works automatically - the provider caches appropriate content without any configuration. For more control, you can use **explicit caching** by marking specific messages with `cacheControl`: ### Single message cache control ```ts import { alibaba } from '@ai-sdk/alibaba'; import { generateText } from 'ai'; const { text, usage } = await generateText({ model: alibaba('qwen-plus'), messages: [ { role: 'system', content: 'You are a helpful assistant. [... long system prompt ...]', providerOptions: { alibaba: { cacheControl: { type: 'ephemeral' }, }, }, }, ], }); ``` ### Multi-part message cache control ```ts import { alibaba } from '@ai-sdk/alibaba'; import { generateText } from 'ai'; const longDocument = '... large document content ...'; const { text, usage } = await generateText({ model: alibaba('qwen-plus'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Context: Please analyze this document.', }, { type: 'text', text: longDocument, providerOptions: { alibaba: { cacheControl: { type: 'ephemeral' }, }, }, }, ], }, ], }); ``` **Note:** The minimum content length for a cache block is 1,024 tokens. ## Embedding Models You can create text embedding models using the `.embedding()` factory method. For more on embeddings with the AI SDK see [`embed()`](/docs/reference/ai-sdk-core/embed) and [`embedMany()`](/docs/reference/ai-sdk-core/embed-many). ```ts const model = alibaba.embedding('text-embedding-v4'); ``` You can use Alibaba embedding models to generate embeddings with the `embed` function: ```ts import { alibaba, type AlibabaEmbeddingModelOptions } from '@ai-sdk/alibaba'; import { embed } from 'ai'; const { embedding, usage } = await embed({ model: alibaba.embedding('text-embedding-v4'), value: 'sunny day at the beach', providerOptions: { alibaba: { textType: 'document', dimension: 1024, outputType: 'dense', } satisfies AlibabaEmbeddingModelOptions, }, }); ``` Use `embedMany` to embed multiple text values. Alibaba text embedding models support up to 10 values per API call; larger batches are split automatically by `embedMany`. ```ts import { alibaba, type AlibabaEmbeddingModelOptions } from '@ai-sdk/alibaba'; import { embedMany } from 'ai'; const { embeddings } = await embedMany({ model: alibaba.embedding('text-embedding-v4'), values: [ 'sunny day at the beach', 'rainy afternoon in the city', 'snowy night in the mountains', ], providerOptions: { alibaba: { textType: 'document', dimension: 1024, } satisfies AlibabaEmbeddingModelOptions, }, }); ``` Alibaba embedding models support additional provider options that can be passed via `providerOptions.alibaba`: - **textType** *'query' | 'document'* Differentiates query text from document text for asymmetric retrieval tasks. Defaults to `document`. - **dimension** *number* The dimension of the output embedding vectors. Defaults to 1024. `text-embedding-v4` also supports 1536 and 2048 dimensions. - **outputType** *'dense' | 'sparse' | 'dense\&sparse'* Specifies the output vector type. Defaults to `dense`. The AI SDK embedding interface returns dense `number[]` vectors, so sparse-only output is not supported and will throw. Use `dense&sparse` to receive dense embeddings in `embedding`/`embeddings` and sparse vectors in `providerMetadata.alibaba.sparseEmbeddings`. ### Embedding Model Capabilities | Model | Default Dimensions | Flexible Dimensions | Max Values per Call | | ------------------- | ------------------ | ---------------------------------------- | ------------------- | | `text-embedding-v4` | 1024 | 64, 128, 256, 512, 768, 1024, 1536, 2048 | 10 | | `text-embedding-v3` | 1024 | 512, 768, 1024 | 10 | The tables above list currently documented Alibaba text embedding models. You can also pass any available provider model ID as a string if needed. ## Video Models You can create [Wan](https://www.alibabacloud.com/help/en/model-studio/use-video-generation) video models that call the Alibaba Cloud DashScope API using the `.video()` factory method. For more on video generation with the AI SDK see [generateVideo()](/docs/reference/ai-sdk-core/generate-video). Alibaba supports three video generation modes: text-to-video, image-to-video (first frame), and reference-to-video. ### Text-to-Video Generate videos from text prompts: ```ts import { alibaba, type AlibabaVideoModelOptions } from '@ai-sdk/alibaba'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: alibaba.video('wan2.6-t2v'), prompt: 'A serene mountain lake at sunset with gentle ripples on the water.', resolution: '1280x720', duration: 5, providerOptions: { alibaba: { promptExtend: true, pollTimeoutMs: 600000, // 10 minutes } satisfies AlibabaVideoModelOptions, }, }); ``` `wan2.7` models additionally support the `aspectRatio` option (mapped to the API's `ratio` parameter) and always generate audio. Multi-shot structure is described directly in the prompt instead of the `shotType` option: ```ts const { video } = await generateVideo({ model: alibaba.video('wan2.7-t2v'), prompt: 'A serene mountain lake at sunset. The camera pans across the water, then cuts to a close-up of ripples catching the light.', resolution: '1920x1080', aspectRatio: '16:9', duration: 5, }); ``` ### Image-to-Video Generate videos from a first-frame image and optional text prompt: ```ts import { alibaba, type AlibabaVideoModelOptions } from '@ai-sdk/alibaba'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: alibaba.video('wan2.6-i2v'), prompt: { image: 'https://example.com/landscape.jpg', text: 'Camera slowly pans across the landscape', }, duration: 5, providerOptions: { alibaba: { pollTimeoutMs: 600000, // 10 minutes } satisfies AlibabaVideoModelOptions, }, }); ``` You can also pass the first frame using the top-level `frameImages` option: ```ts import { alibaba, type AlibabaVideoModelOptions } from '@ai-sdk/alibaba'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: alibaba.video('wan2.6-i2v'), prompt: 'Camera slowly pans across the landscape', frameImages: [ { image: 'https://example.com/landscape.jpg', frameType: 'first_frame', }, ], duration: 5, providerOptions: { alibaba: { pollTimeoutMs: 600000, // 10 minutes } satisfies AlibabaVideoModelOptions, }, }); ``` ### Reference-to-Video Generate videos using reference images and/or videos for character consistency. For `wan2.6` models, use character identifiers (`character1`, `character2`, etc.) in your prompt to reference them. References must be public URLs: ```ts import { alibaba, type AlibabaVideoModelOptions } from '@ai-sdk/alibaba'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: alibaba.video('wan2.6-r2v-flash'), prompt: 'character1 walks through a beautiful garden and waves at the camera', resolution: '1280x720', duration: 5, inputReferences: ['https://example.com/character-reference.jpg'], providerOptions: { alibaba: { pollTimeoutMs: 600000, // 10 minutes } satisfies AlibabaVideoModelOptions, }, }); ``` For `wan2.7` models, use `Image 1`, `Image 2`, `Video 1`, etc. in your prompt (images and videos are counted separately, in reference order). Image references can be public URLs or inline file data; video references must be public URLs: ```ts import { alibaba, type AlibabaVideoModelOptions } from '@ai-sdk/alibaba'; import { experimental_generateVideo as generateVideo } from 'ai'; import { readFile } from 'node:fs/promises'; const imageBytes = await readFile('./character.png'); const { video } = await generateVideo({ model: alibaba.video('wan2.7-r2v'), prompt: 'Image 1 walks through the scene shown in Image 2.', resolution: '1920x1080', duration: 5, inputReferences: [ imageBytes, // inline image data, sent as base64 data URI 'https://example.com/background.png', ], providerOptions: { alibaba: { ratio: '16:9', pollTimeoutMs: 600000, // 10 minutes } satisfies AlibabaVideoModelOptions, }, }); ``` For full control over the `wan2.7` media array (e.g. voice references or explicit media types), use the `media` provider option, which overrides the automatic mapping from `inputReferences` and `frameImages`: ```ts const { video } = await generateVideo({ model: alibaba.video('wan2.7-r2v'), prompt: 'Video 1 walks into the room. Image 1 looks up and says hello.', providerOptions: { alibaba: { media: [ { type: 'reference_video', url: 'https://example.com/character.mp4', referenceVoice: 'https://example.com/voice.mp3', }, { type: 'reference_image', url: 'https://example.com/scene.png' }, { type: 'first_frame', url: 'https://example.com/opening-frame.png' }, ], } satisfies AlibabaVideoModelOptions, }, }); ``` ### Video Provider Options The following provider options are available via `providerOptions.alibaba`: - **negativePrompt** *string* A description of what to avoid in the generated video (max 500 characters). - **audioUrl** *string* URL to an audio file for audio-video sync (WAV/MP3, 3-30 seconds, max 15MB). - **promptExtend** *boolean* Enable prompt extension/rewriting for better generation quality. Defaults to `true`. - **shotType** `'single'` | `'multi'` Shot type for video generation. `'multi'` enables multi-shot cinematic narrative (wan2.6 models only). - **watermark** *boolean* Whether to add a watermark to the generated video. Defaults to `false`. - **audio** *boolean* Whether to generate audio (for `wan2.6` I2V and R2V models; `wan2.7` models always generate audio). - **referenceUrls** *string\[]* Array of reference image/video URLs for reference-to-video mode (`wan2.6` models). Supports 0-5 images and 0-3 videos, max 5 total. Prefer the top-level `inputReferences` option when passing reference images. - **media** *Array\<\{ type, url, referenceVoice? }>* Explicit media array for reference-to-video mode (`wan2.7` models). Each item has a `type` (`'reference_image'`, `'reference_video'`, or `'first_frame'`), a `url` (public URL, or a `data:{mime};base64,{data}` URI for images), and an optional `referenceVoice` audio URL. Overrides the automatic mapping from `inputReferences` and `frameImages`. - **ratio** `'16:9'` | `'9:16'` | `'1:1'` | `'4:3'` | `'3:4'` Aspect ratio (`wan2.7` text-to-video and reference-to-video models). The top-level `aspectRatio` option is mapped to this parameter automatically. - **pollIntervalMs** *number* Polling interval in milliseconds for checking task status. Defaults to 5000. - **pollTimeoutMs** *number* Maximum wait time in milliseconds for video generation. Defaults to 600000 (10 minutes). Video generation is an asynchronous process that can take several minutes. Consider setting `pollTimeoutMs` to at least 10 minutes (600000ms) for reliable operation. ### Video Model Capabilities #### Text-to-Video | Model | Audio | Resolution | Duration | | ----------------------- | ----- | ----------------- | -------- | | `wan2.6-t2v` | Yes | 720P, 1080P | 2-15s | | `wan2.5-t2v-preview` | Yes | 480P, 720P, 1080P | 5s, 10s | | `wan2.7-t2v` | Yes | 720P, 1080P | 2-15s | | `wan2.7-t2v-2026-06-12` | Yes | 720P, 1080P | 2-15s | #### Image-to-Video (First Frame) | Model | Audio | Resolution | Duration | | ------------------ | -------- | ----------- | -------- | | `wan2.6-i2v-flash` | Optional | 720P, 1080P | 2-15s | | `wan2.6-i2v` | Yes | 720P, 1080P | 2-15s | #### Reference-to-Video | Model | Audio | Resolution | Duration | | ----------------------- | -------- | ----------- | ----------------------------- | | `wan2.6-r2v-flash` | Optional | 720P, 1080P | 2-10s | | `wan2.6-r2v` | Yes | 720P, 1080P | 2-10s | | `wan2.7-r2v` | Yes | 720P, 1080P | 2-10s (with video ref), 2-15s | | `wan2.7-r2v-2026-06-12` | Yes | 720P, 1080P | 2-10s (with video ref), 2-15s | The tables above list models available in the Singapore International region. You can also pass any available provider model ID as a string if needed. ## Model Capabilities Please see the [Alibaba Cloud Model Studio docs](https://www.alibabacloud.com/help/en/model-studio/models) for a full list of available models. You can also pass any available provider model ID as a string if needed. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Amazon Bedrock description: Learn how to use Amazon Bedrock with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/amazon-bedrock" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The Amazon Bedrock provider for the [AI SDK](/docs) contains language model support for the [Amazon Bedrock](https://aws.amazon.com/bedrock) APIs. Start with [AI Gateway](/providers/ai-sdk-providers/ai-gateway) for models in its catalog (recommended), or use `@ai-sdk/amazon-bedrock` to connect directly to Amazon Bedrock. ```ts provider="amazon" model="amazon/nova-pro" import { generateText } from 'ai'; const { text } = await generateText({ model: "amazon/nova-pro", prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { gateway: { order: ['bedrock'], }, }, }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. The example sets `providerOptions.gateway.order` to `['bedrock']`. This sends the request to Amazon Bedrock first, then falls back to other available providers of the same model if Amazon Bedrock is unavailable or the request fails. `order` sets a preference; it does not restrict fallback to Amazon Bedrock. See [provider ordering](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). The dropdown lists models served by Amazon Bedrock through Gateway with a corresponding direct-provider example. Provider availability depends on the model and your team's [Provider Allowlist](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). ## Using `@ai-sdk/amazon-bedrock` For direct access to Amazon Bedrock, install `ai` and `@ai-sdk/amazon-bedrock`: ```bash pnpm add ai @ai-sdk/amazon-bedrock ``` Set your Amazon Bedrock API credential in the `AWS_BEARER_TOKEN_BEDROCK` environment variable: ```bash export AWS_BEARER_TOKEN_BEDROCK=your_api_key_here export AWS_REGION=us-east-1 ``` Use the direct provider for AWS authentication, account-specific inference profiles, or Bedrock-specific APIs. Gateway and Bedrock use different model IDs. The following sections document the direct `@ai-sdk/amazon-bedrock` provider, including its model factories and settings. Gateway model and feature availability can differ. ### Prerequisites Access to Amazon Bedrock foundation models isn't granted by default. In order to gain access to a foundation model, an IAM user with sufficient permissions needs to request access to it through the console. Once access is provided to a model, it is available for all users in the account. See the [Model Access Docs](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) for more information. ### Authentication The Amazon Bedrock provider supports two authentication methods with automatic fallback: API key authentication (recommended) and AWS SigV4 authentication using IAM credentials or the AWS SDK credentials chain. #### Using an API Key (Recommended) API key authentication provides a simpler setup compared to AWS SigV4 authentication. When an API key is supplied, it takes precedence over any SigV4 credentials. Generate a bedrock API key from the [AWS console](https://docs.aws.amazon.com/bedrock/latest/userguide/api-keys.html), then either set it as the `AWS_BEARER_TOKEN_BEDROCK` environment variable: ```bash AWS_BEARER_TOKEN_BEDROCK=your-api-key-here ``` or pass it directly to `createAmazonBedrock`: ```ts import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'; const amazonBedrock = createAmazonBedrock({ apiKey: 'your-api-key-here', region: 'us-east-1', }); ``` When `apiKey` is omitted, the provider falls back to `AWS_BEARER_TOKEN_BEDROCK`, and then to SigV4 authentication if neither is set. #### Using IAM Access Key and Secret Key **Step 1: Creating AWS Access Key and Secret Key** To get started, you'll need to create an AWS access key and secret key. Here's how: **Login to AWS Management Console** - Go to the [AWS Management Console](https://console.aws.amazon.com/) and log in with your AWS account credentials. **Create an IAM User** - Navigate to the [IAM dashboard](https://console.aws.amazon.com/iam/home) and click on "Users" in the left-hand navigation menu. - Click on "Create user" and fill in the required details to create a new IAM user. - Make sure to select "Programmatic access" as the access type. - The user account needs the `AmazonBedrockFullAccess` policy attached to it. **Create Access Key** - Click on the "Security credentials" tab and then click on "Create access key". - Click "Create access key" to generate a new access key pair. - Download the `.csv` file containing the access key ID and secret access key. **Step 2: Configuring the Access Key and Secret Key** Within your project add a `.env` file if you don't already have one. This file will be used to set the access key and secret key as environment variables. Add the following lines to the `.env` file: ```makefile AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY=YOUR_SECRET_ACCESS_KEY AWS_REGION=YOUR_REGION ``` Many frameworks such as [Next.js](https://nextjs.org/) load the `.env` file automatically. If you're using a different framework, you may need to load the `.env` file manually using a package like [`dotenv`](https://github.com/motdotla/dotenv). Remember to replace `YOUR_ACCESS_KEY_ID`, `YOUR_SECRET_ACCESS_KEY`, and `YOUR_REGION` with the actual values from your AWS account. #### Using AWS SDK Credentials Chain (instance profiles, instance roles, ECS roles, EKS Service Accounts, etc.) When using AWS SDK, the SDK will automatically use the credentials chain to determine the credentials to use. This includes instance profiles, instance roles, ECS roles, EKS Service Accounts, etc. A similar behavior is possible using the AI SDK by not specifying the `accessKeyId` and `secretAccessKey`, `sessionToken` properties in the provider settings and instead passing a `credentialProvider` property. *Usage:* `@aws-sdk/credential-providers` package provides a set of credential providers that can be used to create a credential provider chain. ```bash pnpm add @aws-sdk/credential-providers ``` ```ts import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'; import { fromNodeProviderChain } from '@aws-sdk/credential-providers'; const amazonBedrock = createAmazonBedrock({ region: 'us-east-1', credentialProvider: fromNodeProviderChain(), }); ``` ## Provider Instance You can import the default provider instance `amazonBedrock` from `@ai-sdk/amazon-bedrock`: ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; ``` If you need a customized setup, you can import `createAmazonBedrock` from `@ai-sdk/amazon-bedrock` and create a provider instance with your settings: ```ts import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'; const amazonBedrock = createAmazonBedrock({ region: 'us-east-1', accessKeyId: 'xxxxxxxxx', secretAccessKey: 'xxxxxxxxx', sessionToken: 'xxxxxxxxx', }); ``` Omitted options use the environment variables below. When **both** `accessKeyId` and `secretAccessKey` are strings, SigV4 uses a **`sessionToken` only if you pass one** - not `AWS_SESSION_TOKEN` from the environment - so static keys are not mixed with workload tokens (e.g. EKS IRSA). You can use the following optional settings to customize the Amazon Bedrock provider instance: - **region** *string* The AWS region that you want to use for the API calls. It uses the `AWS_REGION` environment variable by default. - **accessKeyId** *string* The AWS access key ID that you want to use for the API calls. It uses the `AWS_ACCESS_KEY_ID` environment variable by default. - **secretAccessKey** *string* The AWS secret access key that you want to use for the API calls. It uses the `AWS_SECRET_ACCESS_KEY` environment variable by default. - **sessionToken** *string* Optional. For temporary credentials. With **both** access keys set as strings, pass the token here if needed; `AWS_SESSION_TOKEN` from the environment is not used. If either key is taken from the environment, omitting `sessionToken` allows `AWS_SESSION_TOKEN`. - **credentialProvider** *() => Promise\<\{ accessKeyId: string; secretAccessKey: string; sessionToken?: string; }>* Optional. The AWS credential provider chain that you want to use for the API calls. It uses the specified credentials by default. - **apiKey** *string* Optional. API key for authenticating requests using Bearer token authentication. When provided, this will be used instead of AWS SigV4 authentication. It uses the `AWS_BEARER_TOKEN_BEDROCK` environment variable by default. - **baseURL** *string* Optional. Base URL for the Bedrock API calls. Useful for custom endpoints or proxy configurations. When omitted, the provider also supports the service-specific `AWS_ENDPOINT_URL_BEDROCK_RUNTIME` and `AWS_ENDPOINT_URL_BEDROCK_AGENT_RUNTIME` environment variables, which take precedence over the global `AWS_ENDPOINT_URL` environment variable. - **headers** *Record\* Optional. Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Optional. Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. ## Language Models You can create models that call the Bedrock API using the provider instance. The first argument is the model id, e.g. `meta.llama3-70b-instruct-v1:0`. ```ts const model = amazonBedrock('meta.llama3-70b-instruct-v1:0'); ``` Amazon Bedrock models also support some model specific provider options that are not part of the [standard call settings](/docs/ai-sdk-core/settings). You can pass them in the `providerOptions` argument: ```ts const model = amazonBedrock('anthropic.claude-3-sonnet-20240229-v1:0'); await generateText({ model, providerOptions: { anthropic: { additionalModelRequestFields: { top_k: 350 }, }, }, }); ``` Documentation for additional settings based on the selected model can be found within the [Amazon Bedrock Inference Parameter Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html). You can use Amazon Bedrock language models to generate text with the `generateText` function: ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { generateText } from 'ai'; const { text } = await generateText({ model: amazonBedrock('meta.llama3-70b-instruct-v1:0'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` Amazon Bedrock language models can also be used in the `streamText` function (see [AI SDK Core](/docs/ai-sdk-core)). ### Structured Outputs For Anthropic models, you can use the `structuredOutputMode` provider option to choose how Amazon Bedrock generates structured outputs: - `"outputFormat"`: Use Bedrock's native `output_config.format` parameter. - `"jsonTool"`: Use a special `json` tool with required tool choice. - `"auto"`: Use native structured output when supported and otherwise fall back to the JSON tool. This is the default. In `"auto"` mode, Claude Sonnet 4.6 and Claude Haiku 4.5 use the JSON tool fallback because native structured output can be unreliable across workloads and Bedrock accounts. You can set `"outputFormat"` to opt into native structured output when it works for your account and workload, or use `"jsonTool"` to force the fallback for any model: ```ts import { amazonBedrock, type AmazonBedrockLanguageModelChatOptions, } from '@ai-sdk/amazon-bedrock'; import { generateText, Output } from 'ai'; import { z } from 'zod'; const { output } = await generateText({ model: amazonBedrock('eu.anthropic.claude-sonnet-4-6'), output: Output.object({ schema: z.object({ summary: z.string(), findings: z.array(z.string()), }), }), prompt: 'Summarize the inspection report.', providerOptions: { amazonBedrock: { structuredOutputMode: 'jsonTool', } satisfies AmazonBedrockLanguageModelChatOptions, }, }); ``` The legacy `providerOptions.bedrock` key remains supported. For parity with the Anthropic provider, `providerOptions.anthropic.structuredOutputMode` is also honored for Anthropic models on Bedrock. When both are provided, the `amazonBedrock` or legacy `bedrock` value takes precedence. For an Anthropic application inference profile, prefer the Bedrock Anthropic provider. It uses Bedrock's native InvokeModel API, which accepts application inference profile ARNs, and identifies the model as Anthropic automatically: ```ts import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic'; const profileArn = 'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/qibm5eutlkcy'; const model = bedrockAnthropic(profileArn); ``` Use `modelFamily: 'anthropic'` only when you need to use the generic `@ai-sdk/amazon-bedrock` provider, which uses Bedrock's Converse API (for example, when IAM permits Converse but not InvokeModel). Application inference profile ARNs do not identify their underlying model, so declaring the family allows Anthropic-specific features, including native structured output: ```ts import { amazonBedrock, type AmazonBedrockChatModelSettings, } from '@ai-sdk/amazon-bedrock'; const profileArn = 'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/qibm5eutlkcy'; const model = amazonBedrock(profileArn, { modelFamily: 'anthropic', } satisfies AmazonBedrockChatModelSettings); ``` For an underlying model that does not reliably support native structured output, set `structuredOutputMode` to `"jsonTool"`. ### File Inputs Amazon Bedrock supports file inputs in combination with specific models, e.g. `anthropic.claude-3-haiku-20240307-v1:0`. The Amazon Bedrock provider supports file inputs, e.g. PDF files. ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { generateText } from 'ai'; const result = await generateText({ model: amazonBedrock('us.anthropic.claude-haiku-5-5'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe the pdf in detail.' }, { type: 'file', data: readFileSync('./data/ai.pdf'), mediaType: 'application/pdf', }, ], }, ], }); ``` ### Guardrails You can use the `bedrock` provider options to utilize [Amazon Bedrock Guardrails](https://aws.amazon.com/bedrock/guardrails/): ```ts import { type AmazonBedrockLanguageModelChatOptions } from '@ai-sdk/amazon-bedrock'; const result = await generateText({ model: amazonBedrock('anthropic.claude-3-sonnet-20240229-v1:0'), prompt: 'Write a story about space exploration.', providerOptions: { bedrock: { guardrailConfig: { guardrailIdentifier: '1abcd2ef34gh', guardrailVersion: '1', trace: 'enabled' as const, streamProcessingMode: 'async', }, } satisfies AmazonBedrockLanguageModelChatOptions, }, }); ``` Tracing information will be returned in the provider metadata if you have tracing enabled. ```ts if (result.providerMetadata?.bedrock.trace) { // ... } ``` See the [Amazon Bedrock Guardrails documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html) for more information. #### Guard Content You can mark individual text or image parts as guard content using `providerOptions` on the part itself. This wraps the part in a [`guardContent`](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_GuardrailConverseContentBlock.html) block, which tells Bedrock Guardrails to evaluate that content specifically. For text parts, you can also specify `guardContentQualifiers` to indicate how the content should be treated by the guardrail (e.g. as a grounding source, a query, or guard content): ```ts const result = await generateText({ model: bedrock('anthropic.claude-3-sonnet-20240229-v1:0'), providerOptions: { bedrock: { guardrailConfig: { guardrailIdentifier: '1abcd2ef34gh', guardrailVersion: '1', }, }, }, messages: [ { role: 'user', content: [ { type: 'text', text: 'London is the capital of UK. Tokyo is the capital of Japan.', providerOptions: { bedrock: { guardContent: true, guardContentQualifiers: ['grounding_source'], }, }, }, { type: 'text', text: 'Some additional background information.', }, { type: 'text', text: 'What is the capital of Japan?', providerOptions: { bedrock: { guardContent: true, guardContentQualifiers: ['query'], }, }, }, ], }, ], }); ``` Image parts can also be marked as guard content: ```ts { type: 'file', data: { type: 'data', data: imageBase64, }, mediaType: 'image/png', providerOptions: { bedrock: { guardContent: true, }, }, } ``` The available `guardContentQualifiers` are: - `'grounding_source'` — content used as a grounding source for the guardrail - `'query'` — content treated as a query to evaluate - `'guard_content'` — content to be evaluated by the guardrail See the [Amazon Bedrock Guardrails with Converse API documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-use-converse-api.html) for more information. ### Citations Amazon Bedrock supports citations for document-based inputs across compatible models. When enabled: - Some models can read documents with visual understanding, not just extracting text - Models can cite specific parts of documents you provide, making it easier to trace information back to its source (the AI SDK does not return citation data in results yet) ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { generateText, Output } from 'ai'; import { z } from 'zod'; import fs from 'fs'; const result = await generateText({ model: amazonBedrock('apac.anthropic.claude-sonnet-4-20250514-v1:0'), output: Output.object({ schema: z.object({ summary: z.string().describe('Summary of the PDF document'), keyPoints: z.array(z.string()).describe('Key points from the PDF'), }), }), messages: [ { role: 'user', content: [ { type: 'text', text: 'Summarize this PDF and provide key points.', }, { type: 'file', data: readFileSync('./document.pdf'), mediaType: 'application/pdf', providerOptions: { bedrock: { citations: { enabled: true }, }, }, }, ], }, ], }); console.log('Response:', result.output); ``` ### Cache Points Amazon Bedrock prompt caching is currently in preview release. To request access, visit the [Amazon Bedrock prompt caching page](https://aws.amazon.com/bedrock/prompt-caching/). In messages, you can use the `providerOptions` property to set cache points. Set the `bedrock` property in the `providerOptions` object to `{ cachePoint: { type: 'default' } }` to create a cache point. You can also specify a TTL (time-to-live) for cache points using the `ttl` property. Supported values are `'5m'` (5 minutes, default) and `'1h'` (1 hour). The 1-hour TTL is only supported by Claude Opus 4.5, Claude Haiku 4.5, and Claude Sonnet 4.5. ```ts providerOptions: { bedrock: { cachePoint: { type: 'default', ttl: '1h' } }, } ``` When using multiple cache points with different TTLs, cache entries with longer TTL must appear before shorter TTLs (i.e., 1-hour cache entries must come before 5-minute cache entries). Cache usage information is returned in the `providerMetadata` object. See examples below. Cache points have model-specific token minimums and limits. For example, Claude 3.5 Sonnet v2 requires at least 1,024 tokens for a cache point and allows up to 4 cache points. See the [Amazon Bedrock prompt caching documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html) for details on supported models, regions, and limits. ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { generateText } from 'ai'; const cyberpunkAnalysis = '... literary analysis of cyberpunk themes and concepts ...'; const result = await generateText({ model: amazonBedrock('us.anthropic.claude-sonnet-5-5'), messages: [ { role: 'system', content: `You are an expert on William Gibson's cyberpunk literature and themes. You have access to the following academic analysis: ${cyberpunkAnalysis}`, providerOptions: { bedrock: { cachePoint: { type: 'default' } }, }, }, { role: 'user', content: 'What are the key cyberpunk themes that Gibson explores in Neuromancer?', }, ], }); console.log(result.text); console.log(result.providerMetadata?.bedrock?.usage); // Shows cache read/write token usage, e.g.: // { // cacheReadInputTokens: 1337, // cacheWriteInputTokens: 42, // } ``` Cache points also work with streaming responses: ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { streamText } from 'ai'; const cyberpunkAnalysis = '... literary analysis of cyberpunk themes and concepts ...'; const result = streamText({ model: amazonBedrock('us.anthropic.claude-sonnet-5-5'), messages: [ { role: 'assistant', content: [ { type: 'text', text: 'You are an expert on cyberpunk literature.' }, { type: 'text', text: `Academic analysis: ${cyberpunkAnalysis}` }, ], providerOptions: { bedrock: { cachePoint: { type: 'default' } } }, }, { role: 'user', content: 'How does Gibson explore the relationship between humanity and technology?', }, ], }); for await (const textPart of result.textStream) { process.stdout.write(textPart); } console.log( 'Cache token usage:', (await result.providerMetadata)?.bedrock?.usage, ); // Shows cache read/write token usage, e.g.: // { // cacheReadInputTokens: 1337, // cacheWriteInputTokens: 42, // } ``` ### Provider Metadata The following Bedrock-specific metadata may be returned in `providerMetadata.bedrock`: - **trace** *(optional)* Guardrail tracing information (when tracing is enabled). - **performanceConfig** *(optional)* Performance configuration, e.g. `{ latency: 'optimized' }`. - **serviceTier** *(optional)* Service tier information, e.g. `{ type: 'on-demand' }`. - **usage** *(optional)* Cache token usage details including `cacheWriteInputTokens` and `cacheDetails`. - **stopSequence** *string | null* The stop sequence that triggered the stop, if any. ## Reasoning Amazon Bedrock supports model creator-specific reasoning features: - Anthropic (e.g. `claude-sonnet-4-5-20250929`): enable via the `reasoningConfig` provider option and specifying a thinking budget in tokens (minimum: `1024`, maximum: `64000`). - Amazon (e.g. `us.amazon.nova-2-lite-v1:0`): enable via the `reasoningConfig` provider option and specifying a maximum reasoning effort level (`'low' | 'medium' | 'high'`). You can also use the top-level `reasoning` parameter. For Amazon Nova 2, `'xhigh'` and `'max'` are mapped to `'high'` with a compatibility warning. Anthropic and OpenAI effort mappings preserve `'max'`. Bedrock combines partial `providerOptions.amazonBedrock.reasoningConfig` with portable reasoning, preserving explicit values and deriving missing fields for the model's reasoning API. An explicit `maxReasoningEffort` replaces the effort mapping without emitting a coercion warning for the overridden value. For budget-based Anthropic models, `{ budgetTokens: 5000 }` preserves that budget and uses portable reasoning to fill a missing `type: 'enabled'`. Type-only, display-only, and empty configurations also retain portable reasoning. Effort-based Anthropic models can combine portable effort with an explicit thinking configuration. A disabled type prevents effort/budget derivation; portable `reasoning: 'none'` disables thinking on Anthropic models even when an enabled type or budget is supplied. Omitting portable reasoning or using `'provider-default'` leaves the provider configuration unchanged. The legacy `bedrock` provider options key follows the same rules. See the [reasoning guide](/docs/ai-sdk-core/reasoning#precedence-rules) for precedence details. ```ts import { amazonBedrock, type AmazonBedrockLanguageModelChatOptions, } from '@ai-sdk/amazon-bedrock'; import { generateText } from 'ai'; // Anthropic example const anthropicResult = await generateText({ model: amazonBedrock('us.anthropic.claude-sonnet-4-5-20250929-v1:0'), prompt: 'How many people will live in the world in 2040?', providerOptions: { bedrock: { reasoningConfig: { type: 'enabled', budgetTokens: 1024 }, } satisfies AmazonBedrockLanguageModelChatOptions, }, }); console.log(anthropicResult.reasoningText); // reasoning text console.log(anthropicResult.text); // text response // Nova 2 example const amazonResult = await generateText({ model: amazonBedrock('us.amazon.nova-2-lite-v1:0'), prompt: 'How many people will live in the world in 2040?', providerOptions: { bedrock: { reasoningConfig: { type: 'enabled', maxReasoningEffort: 'medium' }, } satisfies AmazonBedrockLanguageModelChatOptions, }, }); console.log(amazonResult.reasoningText); // reasoning text console.log(amazonResult.text); // text response ``` For Anthropic models, Bedrock's `maxTokens` limit includes both thinking and final text. The provider therefore adds `budgetTokens` to `maxOutputTokens` when setting `maxTokens`. When `maxOutputTokens` is omitted, the provider adds `budgetTokens` to the default of `4096`. See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details on how to integrate reasoning into your chatbot. ## Service Tiers Amazon Bedrock supports selecting an inference service tier per request via the `serviceTier` provider option. ```ts import { amazonBedrock, type AmazonBedrockLanguageModelChatOptions, } from '@ai-sdk/amazon-bedrock'; import { generateText } from 'ai'; const result = await generateText({ model: amazonBedrock('us.anthropic.claude-sonnet-4-20250514-v1:0'), prompt: 'Summarize this support ticket backlog.', providerOptions: { bedrock: { serviceTier: 'priority', } satisfies AmazonBedrockLanguageModelChatOptions, }, }); ``` Supported values are: - `reserved` - `priority` - `default` - `flex` See the [Amazon Bedrock service tiers documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/service-tiers-inference.html) for model availability and behavior. ## Extended Context Window Claude Sonnet 4 models on Amazon Bedrock support an extended context window of up to 1 million tokens when using the `context-1m-2025-08-07` beta feature. ```ts import { amazonBedrock, type AmazonBedrockLanguageModelChatOptions, } from '@ai-sdk/amazon-bedrock'; import { generateText } from 'ai'; const result = await generateText({ model: amazonBedrock('us.anthropic.claude-sonnet-4-20250514-v1:0'), prompt: 'analyze this large document...', providerOptions: { bedrock: { anthropicBeta: ['context-1m-2025-08-07'], } satisfies AmazonBedrockLanguageModelChatOptions, }, }); ``` ## Computer Use Via Anthropic, Amazon Bedrock provides three provider-defined tools that can be used to interact with external systems: 1. **Bash Tool**: Allows running bash commands. 2. **Text Editor Tool**: Provides functionality for viewing and editing text files. 3. **Computer Tool**: Enables control of keyboard and mouse actions on a computer. They are available via the `tools` property of the provider instance. Amazon Bedrock does not support strict mode on tool definitions. Setting `strict: true` on a tool will be ignored and a warning will be emitted. ### Bash Tool The Bash Tool allows running bash commands. Here's how to create and use it: ```ts const bashTool = amazonBedrock.tools.bash_20241022({ execute: async ({ command, restart }) => { // Implement your bash command execution logic here // Return the result of the command execution }, }); ``` Parameters: - `command` (string): The bash command to run. Required unless the tool is being restarted. - `restart` (boolean, optional): Specifying true will restart this tool. ### Text Editor Tool The Text Editor Tool provides functionality for viewing and editing text files. **For Claude 4 models (Opus & Sonnet):** ```ts const textEditorTool = amazonBedrock.tools.textEditor_20250429({ execute: async ({ command, path, file_text, insert_line, new_str, insert_text, old_str, view_range, }) => { // Implement your text editing logic here // Return the result of the text editing operation }, }); ``` **For Claude 3.5 Sonnet and earlier models:** ```ts const textEditorTool = amazonBedrock.tools.textEditor_20241022({ execute: async ({ command, path, file_text, insert_line, new_str, insert_text, old_str, view_range, }) => { // Implement your text editing logic here // Return the result of the text editing operation }, }); ``` Parameters: - `command` ('view' | 'create' | 'str\_replace' | 'insert' | 'undo\_edit'): The command to run. Note: `undo_edit` is only available in Claude 3.5 Sonnet and earlier models. - `path` (string): Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`. - `file_text` (string, optional): Required for `create` command, with the content of the file to be created. - `insert_line` (number, optional): Required for `insert` command. The line number after which to insert the new string. - `new_str` (string, optional): New string for `str_replace` command. - `insert_text` (string, optional): Required for `insert` command, containing the text to insert. - `old_str` (string, optional): Required for `str_replace` command, containing the string to replace. - `view_range` (number\[], optional): Optional for `view` command to specify line range to show. When using the Text Editor Tool, make sure to name the key in the tools object correctly: - **Claude 4 models**: Use `str_replace_based_edit_tool` - **Claude 3.5 Sonnet and earlier**: Use `str_replace_editor` ```ts // For Claude 4 models const response = await generateText({ model: amazonBedrock('us.anthropic.claude-sonnet-4-20250514-v1:0'), prompt: "Create a new file called example.txt, write 'Hello World' to it, and run 'cat example.txt' in the terminal", tools: { str_replace_based_edit_tool: textEditorTool, // Claude 4 tool name }, }); // For Claude 3.5 Sonnet and earlier const response = await generateText({ model: amazonBedrock('anthropic.claude-3-5-sonnet-20241022-v2:0'), prompt: "Create a new file called example.txt, write 'Hello World' to it, and run 'cat example.txt' in the terminal", tools: { str_replace_editor: textEditorTool, // Earlier models tool name }, }); ``` ### Computer Tool The Computer Tool enables control of keyboard and mouse actions on a computer: ```ts const computerTool = amazonBedrock.tools.computer_20241022({ displayWidthPx: 1920, displayHeightPx: 1080, displayNumber: 0, // Optional, for X11 environments execute: async ({ action, coordinate, text }) => { // Implement your computer control logic here // Return the result of the action // Example code: switch (action) { case 'screenshot': { // multipart result: return { type: 'image', data: fs .readFileSync('./data/screenshot-editor.png') .toString('base64'), }; } default: { console.log('Action:', action); console.log('Coordinate:', coordinate); console.log('Text:', text); return `executed ${action}`; } } }, // map to tool result content for LLM consumption: toModelOutput({ output }) { return typeof output === 'string' ? [{ type: 'text', text: output }] : [{ type: 'file-data', data: output.data, mediaType: 'image/png' }]; }, }); ``` Parameters: - `action` ('key' | 'type' | 'mouse\_move' | 'left\_click' | 'left\_click\_drag' | 'right\_click' | 'middle\_click' | 'double\_click' | 'screenshot' | 'cursor\_position'): The action to perform. - `coordinate` (number\[], optional): Required for `mouse_move` and `left_click_drag` actions. Specifies the (x, y) coordinates. - `text` (string, optional): Required for `type` and `key` actions. These tools can be used in conjunction with the `anthropic.claude-3-5-sonnet-20240620-v1:0` model to enable more complex interactions and tasks. ### Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | | ---------------------------------------------- | ----------- | ----------------- | ---------- | -------------- | | `amazon.titan-tg1-large` | ✗ | ✗ | ✗ | ✗ | | `amazon.titan-text-express-v1` | ✗ | ✗ | ✗ | ✗ | | `amazon.titan-text-lite-v1` | ✗ | ✗ | ✗ | ✗ | | `us.amazon.nova-premier-v1:0` | ✓ | ✓ | ✓ | ✓ | | `us.amazon.nova-pro-v1:0` | ✓ | ✓ | ✓ | ✓ | | `us.amazon.nova-lite-v1:0` | ✓ | ✓ | ✓ | ✓ | | `us.amazon.nova-micro-v1:0` | ✗ | ✓ | ✓ | ✓ | | `anthropic.claude-haiku-4-5-20251001-v1:0` | ✓ | ✓ | ✓ | ✓ | | `anthropic.claude-sonnet-4-20250514-v1:0` | ✓ | ✓ | ✓ | ✓ | | `anthropic.claude-sonnet-4-5-20250929-v1:0` | ✓ | ✓ | ✓ | ✓ | | `anthropic.claude-opus-4-20250514-v1:0` | ✓ | ✓ | ✓ | ✓ | | `anthropic.claude-opus-4-1-20250805-v1:0` | ✓ | ✓ | ✓ | ✓ | | `anthropic.claude-3-5-sonnet-20241022-v2:0` | ✓ | ✓ | ✓ | ✓ | | `anthropic.claude-3-5-sonnet-20240620-v1:0` | ✓ | ✓ | ✓ | ✓ | | `anthropic.claude-3-opus-20240229-v1:0` | ✓ | ✓ | ✓ | ✓ | | `anthropic.claude-3-sonnet-20240229-v1:0` | ✓ | ✓ | ✓ | ✓ | | `anthropic.claude-3-haiku-20240307-v1:0` | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-sonnet-4-20250514-v1:0` | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-sonnet-4-5-20250929-v1:0` | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-opus-4-20250514-v1:0` | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-opus-4-1-20250805-v1:0` | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-3-5-sonnet-20241022-v2:0` | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-3-5-sonnet-20240620-v1:0` | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-3-sonnet-20240229-v1:0` | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-3-opus-20240229-v1:0` | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-3-haiku-20240307-v1:0` | ✓ | ✓ | ✓ | ✓ | | `anthropic.claude-v2` | ✗ | ✗ | ✗ | ✗ | | `anthropic.claude-v2:1` | ✗ | ✗ | ✗ | ✗ | | `anthropic.claude-instant-v1` | ✗ | ✗ | ✗ | ✗ | | `cohere.command-text-v14` | ✗ | ✗ | ✗ | ✗ | | `cohere.command-light-text-v14` | ✗ | ✗ | ✗ | ✗ | | `cohere.command-r-v1:0` | ✗ | ✗ | ✓ | ✗ | | `cohere.command-r-plus-v1:0` | ✗ | ✗ | ✓ | ✗ | | `us.deepseek.r1-v1:0` | ✗ | ✓ | ✓ | ✓ | | `meta.llama3-8b-instruct-v1:0` | ✗ | ✗ | ✗ | ✗ | | `meta.llama3-70b-instruct-v1:0` | ✗ | ✗ | ✗ | ✗ | | `meta.llama3-1-8b-instruct-v1:0` | ✗ | ✗ | ✗ | ✗ | | `meta.llama3-1-70b-instruct-v1:0` | ✗ | ✗ | ✗ | ✗ | | `meta.llama3-1-405b-instruct-v1:0` | ✗ | ✗ | ✗ | ✗ | | `meta.llama3-2-1b-instruct-v1:0` | ✗ | ✗ | ✗ | ✗ | | `meta.llama3-2-3b-instruct-v1:0` | ✗ | ✗ | ✗ | ✗ | | `meta.llama3-2-11b-instruct-v1:0` | ✗ | ✗ | ✗ | ✗ | | `meta.llama3-2-90b-instruct-v1:0` | ✗ | ✗ | ✗ | ✗ | | `us.meta.llama3-2-1b-instruct-v1:0` | ✗ | ✓ | ✓ | ✓ | | `us.meta.llama3-2-3b-instruct-v1:0` | ✗ | ✓ | ✓ | ✓ | | `us.meta.llama3-2-11b-instruct-v1:0` | ✗ | ✓ | ✓ | ✓ | | `us.meta.llama3-2-90b-instruct-v1:0` | ✗ | ✓ | ✓ | ✓ | | `us.meta.llama3-1-8b-instruct-v1:0` | ✗ | ✓ | ✓ | ✓ | | `us.meta.llama3-1-70b-instruct-v1:0` | ✗ | ✓ | ✓ | ✓ | | `us.meta.llama3-3-70b-instruct-v1:0` | ✗ | ✓ | ✓ | ✓ | | `us.meta.llama4-scout-17b-instruct-v1:0` | ✗ | ✓ | ✓ | ✓ | | `us.meta.llama4-maverick-17b-instruct-v1:0` | ✗ | ✓ | ✓ | ✓ | | `mistral.mistral-7b-instruct-v0:2` | ✗ | ✗ | ✗ | ✗ | | `mistral.mixtral-8x7b-instruct-v0:1` | ✗ | ✗ | ✗ | ✗ | | `mistral.mistral-large-2402-v1:0` | ✗ | ✗ | ✗ | ✗ | | `mistral.mistral-small-2402-v1:0` | ✗ | ✗ | ✗ | ✗ | | `us.mistral.pixtral-large-2502-v1:0` | ✓ | ✓ | ✓ | ✓ | | `openai.gpt-oss-120b-1:0` | ✗ | ✓ | ✓ | ✓ | | `openai.gpt-oss-20b-1:0` | ✗ | ✓ | ✓ | ✓ | The table above lists popular models. Please see the [Amazon Bedrock docs](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-supported-models-features.html) for a full list of available models. You can also pass any available provider model ID as a string if needed. ## Embedding Models You can create models that call the Bedrock API [Bedrock API](https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html) using the `.embedding()` factory method. ```ts const model = amazonBedrock.embedding('amazon.titan-embed-text-v1'); ``` Bedrock Titan embedding model amazon.titan-embed-text-v2:0 supports several additional settings. You can pass them as an options argument: ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { type AmazonBedrockEmbeddingModelOptions } from '@ai-sdk/amazon-bedrock'; import { embed } from 'ai'; const model = amazonBedrock.embedding('amazon.titan-embed-text-v2:0'); const { embedding } = await embed({ model, value: 'sunny day at the beach', providerOptions: { bedrock: { dimensions: 512, // optional, number of dimensions for the embedding normalize: true, // optional, normalize the output embeddings } satisfies AmazonBedrockEmbeddingModelOptions, }, }); ``` The following optional provider options are available for Bedrock Titan embedding models: - **dimensions**: *number* The number of dimensions the output embeddings should have. The following values are accepted: 1024 (default), 512, 256. - **normalize** *boolean* Flag indicating whether or not to normalize the output embeddings. Defaults to true. ### Nova Embedding Models Amazon Nova embedding models support additional provider options: ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { type AmazonBedrockEmbeddingModelOptions } from '@ai-sdk/amazon-bedrock'; import { embed } from 'ai'; const { embedding } = await embed({ model: amazonBedrock.embedding('amazon.nova-embed-text-v2:0'), value: 'sunny day at the beach', providerOptions: { bedrock: { embeddingDimension: 1024, // optional, number of dimensions embeddingPurpose: 'TEXT_RETRIEVAL', // optional, purpose of embedding truncate: 'END', // optional, truncation behavior } satisfies AmazonBedrockEmbeddingModelOptions, }, }); ``` The following optional provider options are available for Nova embedding models: - **embeddingDimension** *number* The number of dimensions for the output embeddings. Supported values: 256, 384, 1024 (default), 3072. - **embeddingPurpose** *string* The purpose of the embedding. Accepts: `GENERIC_INDEX` (default), `TEXT_RETRIEVAL`, `IMAGE_RETRIEVAL`, `VIDEO_RETRIEVAL`, `DOCUMENT_RETRIEVAL`, `AUDIO_RETRIEVAL`, `GENERIC_RETRIEVAL`, `CLASSIFICATION`, `CLUSTERING`. - **truncate** *string* Truncation behavior when input exceeds the model's context length. Accepts: `NONE`, `START`, `END` (default). ### Cohere Embedding Models Cohere embedding models on Bedrock require an `inputType` and support truncation: ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { type AmazonBedrockEmbeddingModelOptions } from '@ai-sdk/amazon-bedrock'; import { embed } from 'ai'; const { embedding } = await embed({ model: amazonBedrock.embedding('cohere.embed-english-v3'), value: 'sunny day at the beach', providerOptions: { bedrock: { inputType: 'search_document', // required for Cohere truncate: 'END', // optional, truncation behavior } satisfies AmazonBedrockEmbeddingModelOptions, }, }); ``` The following provider options are available for Cohere embedding models: - **inputType** *string* Input type for Cohere embedding models. Accepts: `search_document`, `search_query` (default), `classification`, `clustering`. - **truncate** *string* Truncation behavior when input exceeds the model's context length. Accepts: `NONE`, `START`, `END`. ### Application Inference Profiles Application inference profile ARNs do not identify their underlying model. Pass the model family when creating an embedding model so the provider can select the correct request format and batch size: ```ts import { amazonBedrock, type AmazonBedrockEmbeddingModelSettings, } from '@ai-sdk/amazon-bedrock'; import { embedMany } from 'ai'; const profileArn = 'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/qibm5eutlkcy'; const { embeddings } = await embedMany({ model: amazonBedrock.embedding(profileArn, { modelFamily: 'cohere', } satisfies AmazonBedrockEmbeddingModelSettings), values: ['hello', 'world'], }); ``` ### Model Capabilities | Model | Default Dimensions | Custom Dimensions | | ------------------------------ | ------------------ | ----------------- | | `amazon.titan-embed-text-v1` | 1536 | ✗ | | `amazon.titan-embed-text-v2:0` | 1024 | ✓ | | `amazon.nova-embed-text-v2:0` | 1024 | ✓ | | `cohere.embed-english-v3` | 1024 | ✗ | | `cohere.embed-multilingual-v3` | 1024 | ✗ | ## Reranking Models You can create models that call the [Bedrock Rerank API](https://docs.aws.amazon.com/bedrock/latest/userguide/rerank-api.html) using the `.reranking()` factory method. ```ts const model = amazonBedrock.reranking('cohere.rerank-v3-5:0'); ``` You can use Amazon Bedrock reranking models to rerank documents with the `rerank` function: ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { rerank } from 'ai'; const documents = [ 'sunny day at the beach', 'rainy afternoon in the city', 'snowy night in the mountains', ]; const { ranking } = await rerank({ model: amazonBedrock.reranking('cohere.rerank-v3-5:0'), documents, query: 'talk about rain', topN: 2, }); console.log(ranking); // [ // { originalIndex: 1, score: 0.9, document: 'rainy afternoon in the city' }, // { originalIndex: 0, score: 0.3, document: 'sunny day at the beach' } // ] ``` Amazon Bedrock reranking models support additional provider options that can be passed via `providerOptions.bedrock`: ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { rerank } from 'ai'; const { ranking } = await rerank({ model: amazonBedrock.reranking('cohere.rerank-v3-5:0'), documents: ['sunny day at the beach', 'rainy afternoon in the city'], query: 'talk about rain', providerOptions: { bedrock: { nextToken: 'pagination_token_here', }, }, }); ``` The following provider options are available: - **nextToken** *string* Token for pagination of results. - **additionalModelRequestFields** *Record\* Additional model-specific request fields. ### Model Capabilities | Model | | ---------------------- | | `amazon.rerank-v1:0` | | `cohere.rerank-v3-5:0` | ## Image Models You can create models that call the Bedrock API [Bedrock API](https://docs.aws.amazon.com/nova/latest/userguide/image-generation.html) using the `.image()` factory method. For more on the Amazon Nova Canvas image model, see the [Nova Canvas Overview](https://docs.aws.amazon.com/ai/responsible-ai/nova-canvas/overview.html). The `amazon.nova-canvas-v1:0` model is available in the `us-east-1`, `eu-west-1`, and `ap-northeast-1` regions. ```ts const model = amazonBedrock.image('amazon.nova-canvas-v1:0'); ``` You can then generate images with the `generateImage` function: ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: amazonBedrock.image('amazon.nova-canvas-v1:0'), prompt: 'A beautiful sunset over a calm ocean', size: '512x512', seed: 42, }); ``` You can also pass the `providerOptions` object to the `generateImage` function to customize the generation behavior. Use the `AmazonBedrockImageModelOptions` type to validate Amazon Bedrock image provider options. The preferred provider option key is `providerOptions.amazonBedrock`; the legacy `providerOptions.bedrock` key remains supported for backward compatibility: ```ts import { amazonBedrock, type AmazonBedrockImageModelOptions, } from '@ai-sdk/amazon-bedrock'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: amazonBedrock.image('amazon.nova-canvas-v1:0'), prompt: 'A beautiful sunset over a calm ocean', size: '512x512', seed: 42, providerOptions: { amazonBedrock: { quality: 'premium', negativeText: 'blurry, low quality', cfgScale: 7.5, style: 'PHOTOREALISM', } satisfies AmazonBedrockImageModelOptions, }, }); ``` The following optional provider options are available for Amazon Nova Canvas: - **quality** *string* The quality level for image generation. Accepts `'standard'` or `'premium'`. - **negativeText** *string* Text describing what you don't want in the generated image. - **cfgScale** *number* Controls how closely the generated image adheres to the prompt. Higher values result in images that are more closely aligned to the prompt. - **style** *string* Predefined visual style for image generation. Accepts one of: `3D_ANIMATED_FAMILY_FILM` · `DESIGN_SKETCH` · `FLAT_VECTOR_ILLUSTRATION` · `GRAPHIC_NOVEL_ILLUSTRATION` · `MAXIMALISM` · `MIDCENTURY_RETRO` · `PHOTOREALISM` · `SOFT_DIGITAL_PAINTING`. Documentation for additional settings can be found within the [Amazon Bedrock User Guide for Amazon Nova Documentation](https://docs.aws.amazon.com/nova/latest/userguide/image-gen-req-resp-structure.html). ### Image Editing Amazon Nova Canvas supports several image editing task types. When you provide input images via `prompt.images`, the model automatically detects the appropriate editing mode, or you can explicitly specify the `taskType` in provider options. #### Image Variation Create variations of an existing image while maintaining its core characteristics: ```ts const imageBuffer = readFileSync('./input-image.png'); const { images } = await generateImage({ model: amazonBedrock.image('amazon.nova-canvas-v1:0'), prompt: { text: 'Modernize the style, photo-realistic, 8k, hdr', images: [imageBuffer], }, providerOptions: { amazonBedrock: { taskType: 'IMAGE_VARIATION', similarityStrength: 0.7, // 0.2-1.0, higher = closer to original negativeText: 'bad quality, low resolution', } satisfies AmazonBedrockImageModelOptions, }, }); ``` - **similarityStrength** *number* Controls how similar the output is to the input image. Values range from 0.2 to 1.0, where higher values produce results closer to the original. #### Inpainting Edit specific parts of an image. You can define the area to modify using either a mask image or a text prompt: **Using a mask prompt (text-based selection):** ```ts const imageBuffer = readFileSync('./input-image.png'); const { images } = await generateImage({ model: amazonBedrock.image('amazon.nova-canvas-v1:0'), prompt: { text: 'a cute corgi dog in the same style', images: [imageBuffer], }, providerOptions: { amazonBedrock: { maskPrompt: 'cat', // Describe what to replace } satisfies AmazonBedrockImageModelOptions, }, seed: 42, }); ``` **Using a mask image:** ```ts const image = readFileSync('./input-image.png'); const mask = readFileSync('./mask.png'); // White pixels = area to change const { images } = await generateImage({ model: amazonBedrock.image('amazon.nova-canvas-v1:0'), prompt: { text: 'A sunlit indoor lounge area with a pool containing a flamingo', images: [image], mask: mask, }, }); ``` - **maskPrompt** *string* A text description of the area to modify. The model will automatically identify and mask the described region. #### Outpainting Extend an image beyond its original boundaries: ```ts const imageBuffer = readFileSync('./input-image.png'); const { images } = await generateImage({ model: amazonBedrock.image('amazon.nova-canvas-v1:0'), prompt: { text: 'A beautiful sunset landscape with mountains', images: [imageBuffer], }, providerOptions: { amazonBedrock: { taskType: 'OUTPAINTING', maskPrompt: 'background', outPaintingMode: 'DEFAULT', // or 'PRECISE' } satisfies AmazonBedrockImageModelOptions, }, }); ``` - **outPaintingMode** *string* Controls how the outpainting is performed. Accepts `'DEFAULT'` or `'PRECISE'`. #### Background Removal Remove the background from an image: ```ts const imageBuffer = readFileSync('./input-image.png'); const { images } = await generateImage({ model: amazonBedrock.image('amazon.nova-canvas-v1:0'), prompt: { images: [imageBuffer], }, providerOptions: { amazonBedrock: { taskType: 'BACKGROUND_REMOVAL', } satisfies AmazonBedrockImageModelOptions, }, }); ``` Background removal does not require a text prompt - only the input image is needed. #### Image Editing Provider Options The following additional provider options are available for image editing: - **taskType** *string* Explicitly set the editing task type. Accepts `'TEXT_IMAGE'` (default for text-only), `'IMAGE_VARIATION'`, `'INPAINTING'`, `'OUTPAINTING'`, or `'BACKGROUND_REMOVAL'`. When images are provided without an explicit taskType, the model defaults to `'IMAGE_VARIATION'` (or `'INPAINTING'` if a mask is provided). - **maskPrompt** *string* Text description of the area to modify (for inpainting/outpainting). Alternative to providing a mask image. - **similarityStrength** *number* For `IMAGE_VARIATION`: Controls similarity to the original (0.2-1.0). - **outPaintingMode** *string* For `OUTPAINTING`: Controls the outpainting behavior (`'DEFAULT'` or `'PRECISE'`). ### Image Model Settings You can customize the generation behavior with optional options: ```ts await generateImage({ model: amazonBedrock.image('amazon.nova-canvas-v1:0'), prompt: 'A beautiful sunset over a calm ocean', size: '512x512', seed: 42, maxImagesPerCall: 1, // Maximum number of images to generate per API call }); ``` - **maxImagesPerCall** *number* Override the maximum number of images generated per API call. Default can vary by model, with 5 as a common default. ### Model Capabilities The Amazon Nova Canvas model supports custom sizes with constraints as follows: - Each side must be between 320-4096 pixels, inclusive. - Each side must be evenly divisible by 16. - The aspect ratio must be between 1:4 and 4:1. That is, one side can't be more than 4 times longer than the other side. - The total pixel count must be less than 4,194,304. For more, see [Image generation access and usage](https://docs.aws.amazon.com/nova/latest/userguide/image-gen-access.html). | Model | Sizes | | ------------------------- | ----------------------------------------------------------------------------------------------------- | | `amazon.nova-canvas-v1:0` | Custom sizes: 320-4096px per side (must be divisible by 16), aspect ratio 1:4 to 4:1, max 4.2M pixels | ## Response Headers The Amazon Bedrock provider will return the response headers associated with network requests made of the Bedrock servers. ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { generateText } from 'ai'; const result = await generateText({ model: amazonBedrock('meta.llama3-70b-instruct-v1:0'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); console.log(result.response.headers); ``` Below is sample output where you can see the `x-amzn-requestid` header. This can be useful for correlating Bedrock API calls with requests made by the AI SDK: ```js {6} { connection: 'keep-alive', 'content-length': '2399', 'content-type': 'application/json', date: 'Fri, 07 Feb 2025 04:28:30 GMT', 'x-amzn-requestid': 'c9f3ace4-dd5d-49e5-9807-39aedfa47c8e' } ``` This information is also available with `streamText`: ```ts import { amazonBedrock } from '@ai-sdk/amazon-bedrock'; import { streamText } from 'ai'; const result = streamText({ model: amazonBedrock('meta.llama3-70b-instruct-v1:0'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); for await (const textPart of result.textStream) { process.stdout.write(textPart); } console.log('Response headers:', (await result.response).headers); ``` With sample output as: ```js {6} { connection: 'keep-alive', 'content-type': 'application/vnd.amazon.eventstream', date: 'Fri, 07 Feb 2025 04:33:37 GMT', 'transfer-encoding': 'chunked', 'x-amzn-requestid': 'a976e3fc-0e45-4241-9954-b9bdd80ab407' } ``` ## Bedrock Anthropic Provider Usage The Bedrock Anthropic provider offers support for Anthropic's Claude models through Amazon Bedrock's native InvokeModel API. This provides full feature parity with the [Anthropic API](https://platform.claude.com/docs/en/build-with-claude/overview), including features that may not be available through the Converse API (such as `stop_sequence` in streaming responses). For more information on Claude models available on Amazon Bedrock, see [Claude on Amazon Bedrock](https://platform.claude.com/docs/en/build-with-claude/claude-on-amazon-bedrock). ### Provider Instance You can import the default provider instance `bedrockAnthropic` from `@ai-sdk/amazon-bedrock/anthropic`: ```typescript import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic'; ``` If you need a customized setup, you can import `createBedrockAnthropic` from `@ai-sdk/amazon-bedrock/anthropic` and create a provider instance with your settings: ```typescript import { createBedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic'; const bedrockAnthropic = createBedrockAnthropic({ region: 'us-east-1', // optional accessKeyId: 'xxxxxxxxx', // optional secretAccessKey: 'xxxxxxxxx', // optional sessionToken: 'xxxxxxxxx', // optional }); ``` #### Provider Settings You can use the following optional settings to customize the Bedrock Anthropic provider instance: - **region** *string* The AWS region that you want to use for the API calls. It uses the `AWS_REGION` environment variable by default. - **accessKeyId** *string* The AWS access key ID that you want to use for the API calls. It uses the `AWS_ACCESS_KEY_ID` environment variable by default. - **secretAccessKey** *string* The AWS secret access key that you want to use for the API calls. It uses the `AWS_SECRET_ACCESS_KEY` environment variable by default. - **sessionToken** *string* Optional. For temporary credentials. With **both** access keys set as strings, pass the token here if needed; `AWS_SESSION_TOKEN` from the environment is not used. If either key is taken from the environment, omitting `sessionToken` allows `AWS_SESSION_TOKEN`. - **apiKey** *string* API key for authenticating requests using Bearer token authentication. When provided, this will be used instead of AWS SigV4 authentication. It uses the `AWS_BEARER_TOKEN_BEDROCK` environment variable by default. - **baseURL** *string* Base URL for the Bedrock API calls. Useful for custom endpoints or proxy configurations. When omitted, the provider also supports the service-specific `AWS_ENDPOINT_URL_BEDROCK_RUNTIME` environment variable, which takes precedence over the global `AWS_ENDPOINT_URL` environment variable. - **headers** *Resolvable\>* Headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. - **credentialProvider** *() => PromiseLike\* The AWS credential provider to use for the Bedrock provider to get dynamic credentials similar to the AWS SDK. Setting a provider here will cause its credential values to be used instead of the `accessKeyId`, `secretAccessKey`, and `sessionToken` settings. ### Language Models You can create models that call the [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post) using the provider instance. The first argument is the model id, e.g. `us.anthropic.claude-sonnet-5-5`. ```ts const model = bedrockAnthropic('us.anthropic.claude-sonnet-5-5'); ``` You can use Bedrock Anthropic language models to generate text with the `generateText` function: ```ts import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic'; import { generateText } from 'ai'; const { text } = await generateText({ model: bedrockAnthropic('us.anthropic.claude-sonnet-5-5'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` ### Provider Options The following optional provider options are available for Bedrock Anthropic models: - `metadata` *object* Optional. Metadata to include with the request. See the [Anthropic API documentation](https://platform.claude.com/docs/en/api/messages/create) for details. - `userId` *string* - An external identifier for the end-user. ### Cache Control In the messages and message parts, you can use the `providerOptions` property to set cache control breakpoints. You need to set the `anthropic` property in the `providerOptions` object to `{ cacheControl: { type: 'ephemeral' } }` to set a cache control breakpoint. ```ts import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic'; import { generateText } from 'ai'; const result = await generateText({ model: bedrockAnthropic('us.anthropic.claude-sonnet-5-5'), messages: [ { role: 'system', content: 'You are an expert assistant.', providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } }, }, }, { role: 'user', content: 'Explain quantum computing.', }, ], }); ``` Cache control requires a minimum of 1024 tokens before the cache checkpoint. See the [Amazon Bedrock prompt caching documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html) for details on supported models and limits. ### Computer Use The Bedrock Anthropic provider supports Anthropic's computer use tools: 1. **Bash Tool**: Allows running bash commands. 2. **Text Editor Tool**: Provides functionality for viewing and editing text files. 3. **Computer Tool**: Enables control of keyboard and mouse actions on a computer. They are available via the `tools` property of the provider instance. Computer use tools require Claude 3.7 Sonnet or newer models. Claude 3.5 Sonnet v2 does not support these tools. #### Bash Tool ```ts import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic'; import { generateText, isStepCount } from 'ai'; const result = await generateText({ model: bedrockAnthropic('us.anthropic.claude-sonnet-4-5-20250929-v1:0'), tools: { bash: bedrockAnthropic.tools.bash_20241022({ execute: async ({ command }) => { // Implement your bash command execution logic here return [{ type: 'text', text: `Executed: ${command}` }]; }, }), }, prompt: 'List the files in my directory.', stopWhen: isStepCount(2), }); ``` #### Text Editor Tool ```ts import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic'; import { generateText, isStepCount } from 'ai'; const result = await generateText({ model: bedrockAnthropic('us.anthropic.claude-sonnet-4-5-20250929-v1:0'), tools: { str_replace_editor: bedrockAnthropic.tools.textEditor_20241022({ execute: async ({ command, path, old_str, new_str, insert_text }) => { // Implement your text editing logic here return 'File updated successfully'; }, }), }, prompt: 'Update my README file.', stopWhen: isStepCount(5), }); ``` #### Computer Tool ```ts import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic'; import { generateText, isStepCount } from 'ai'; import fs from 'fs'; const result = await generateText({ model: bedrockAnthropic('us.anthropic.claude-sonnet-4-5-20250929-v1:0'), tools: { computer: bedrockAnthropic.tools.computer_20241022({ displayWidthPx: 1024, displayHeightPx: 768, execute: async ({ action, coordinate, text }) => { if (action === 'screenshot') { return { type: 'image', data: fs.readFileSync('./screenshot.png').toString('base64'), }; } return `executed ${action}`; }, toModelOutput({ output }) { return { type: 'content', value: [ typeof output === 'string' ? { type: 'text', text: output } : { type: 'image-data', data: output.data, mediaType: 'image/png', }, ], }; }, }), }, prompt: 'Take a screenshot.', stopWhen: isStepCount(3), }); ``` ### Reasoning Anthropic has reasoning support for Claude 3.7 and Claude 4 models on Bedrock, including: - `us.anthropic.claude-fable-5-1` - `us.anthropic.claude-sonnet-5-5` - `us.anthropic.claude-sonnet-5` - `us.anthropic.claude-fable-5` - `us.anthropic.claude-opus-4-8` - `us.anthropic.claude-opus-4-7` - `us.anthropic.claude-opus-4-6-v1` - `us.anthropic.claude-opus-4-5-20251101-v1:0` - `us.anthropic.claude-sonnet-4-5-20250929-v1:0` - `us.anthropic.claude-opus-4-20250514-v1:0` - `us.anthropic.claude-sonnet-4-20250514-v1:0` - `us.anthropic.claude-opus-4-1-20250805-v1:0` - `us.anthropic.claude-haiku-4-5-20251001-v1:0` You can enable it using the `thinking` provider option and specifying a thinking budget in tokens. ```ts import { bedrockAnthropic } from '@ai-sdk/amazon-bedrock/anthropic'; import { generateText } from 'ai'; const { text, reasoningText, reasoning } = await generateText({ model: bedrockAnthropic('us.anthropic.claude-sonnet-4-5-20250929-v1:0'), prompt: 'How many people will live in the world in 2040?', providerOptions: { anthropic: { thinking: { type: 'enabled', budgetTokens: 12000 }, }, }, }); console.log(reasoningText); // reasoning text console.log(reasoning); // reasoning details including redacted reasoning console.log(text); // text response ``` Anthropic's `max_tokens` limit includes both thinking and final text. To reserve room for both, the provider adds `budgetTokens` to `maxOutputTokens` when setting `max_tokens`. For known models, the combined value is capped at the model's maximum output token limit. See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details on how to integrate reasoning into your chatbot. ### Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Computer Use | Reasoning | | ---------------------------------------------- | ----------- | ----------------- | ---------- | ------------ | --------- | | `us.anthropic.claude-fable-5-1` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-sonnet-5-5` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-sonnet-5` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-haiku-5-5` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-fable-5` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-opus-4-8` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-opus-4-7` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-opus-4-6-v1` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-opus-4-5-20251101-v1:0` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-sonnet-4-5-20250929-v1:0` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-opus-4-20250514-v1:0` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-sonnet-4-20250514-v1:0` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-opus-4-1-20250805-v1:0` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-haiku-4-5-20251001-v1:0` | ✓ | ✓ | ✓ | ✓ | ✓ | | `us.anthropic.claude-3-5-sonnet-20241022-v2:0` | ✓ | ✓ | ✓ | ✗ | ✗ | The Bedrock Anthropic provider uses the native InvokeModel API and supports all features available in the Anthropic API, except for the Files API and MCP Connector which are not supported on Bedrock. ## Bedrock Mantle Provider Usage The Bedrock Mantle provider offers access to OpenAI-compatible models through Amazon Bedrock's Mantle API. It automatically selects the `/v1` or `/openai/v1` route based on the model. These endpoints support the Chat Completions API and the Responses API, and provide access to models that may only be available through the Mantle endpoint (such as `openai.gpt-oss-120b`). ### Provider Instance You can import the default provider instance `bedrockMantle` from `@ai-sdk/amazon-bedrock/mantle`: ```typescript import { bedrockMantle } from '@ai-sdk/amazon-bedrock/mantle'; ``` If you need a customized setup, you can import `createBedrockMantle` from `@ai-sdk/amazon-bedrock/mantle` and create a provider instance with your settings: ```typescript import { createBedrockMantle } from '@ai-sdk/amazon-bedrock/mantle'; const bedrockMantle = createBedrockMantle({ region: 'us-east-1', // optional accessKeyId: 'xxxxxxxxx', // optional secretAccessKey: 'xxxxxxxxx', // optional sessionToken: 'xxxxxxxxx', // optional }); ``` #### Provider Settings You can use the following optional settings to customize the Bedrock Mantle provider instance: - **region** *string* The AWS region that you want to use for the API calls. It uses the `AWS_REGION` environment variable by default. - **accessKeyId** *string* The AWS access key ID that you want to use for the API calls. It uses the `AWS_ACCESS_KEY_ID` environment variable by default. - **secretAccessKey** *string* The AWS secret access key that you want to use for the API calls. It uses the `AWS_SECRET_ACCESS_KEY` environment variable by default. - **sessionToken** *string* Optional. The AWS session token that you want to use for the API calls. It uses the `AWS_SESSION_TOKEN` environment variable by default. - **apiKey** *string* API key for authenticating requests using Bearer token authentication. When provided, this will be used instead of AWS SigV4 authentication. It uses the `AWS_BEARER_TOKEN_BEDROCK` environment variable by default. - **baseURL** *string* Base URL for the Bedrock Mantle API calls. By default, the provider selects either `https://bedrock-mantle.{region}.api.aws/v1` or `https://bedrock-mantle.{region}.api.aws/openai/v1` based on the model. - **headers** *Record\* Headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. - **credentialProvider** *() => PromiseLike\* The AWS credential provider to use for the Bedrock provider to get dynamic credentials similar to the AWS SDK. Setting a provider here will cause its credential values to be used instead of the `accessKeyId`, `secretAccessKey`, and `sessionToken` settings. ### Language Models You can create models using the Mantle provider instance. By default, the provider creates models using the Chat Completions API, which has the broadest model support on Mantle: ```ts const model = bedrockMantle('openai.gpt-oss-120b'); ``` You can also explicitly select the Chat Completions API or the Responses API: ```ts const chatModel = bedrockMantle.chat('openai.gpt-oss-120b'); const responsesModel = bedrockMantle.responses('openai.gpt-oss-120b'); ``` Note that not all Mantle models support the Responses API. See the model capabilities table below. You can use Bedrock Mantle language models to generate text with the `generateText` function: ```ts import { bedrockMantle } from '@ai-sdk/amazon-bedrock/mantle'; import { generateText } from 'ai'; const { text } = await generateText({ model: bedrockMantle('openai.gpt-oss-120b'), prompt: 'Invent a new holiday and describe its traditions.', }); ``` Or stream text with the `streamText` function: ```ts import { bedrockMantle } from '@ai-sdk/amazon-bedrock/mantle'; import { streamText } from 'ai'; const result = streamText({ model: bedrockMantle('openai.gpt-oss-120b'), prompt: 'Invent a new holiday and describe its traditions.', }); for await (const textPart of result.textStream) { process.stdout.write(textPart); } ``` ### Provider Options Since the Mantle API is OpenAI-compatible, provider options are passed under the `openai` namespace: ```ts import { bedrockMantle } from '@ai-sdk/amazon-bedrock/mantle'; import { generateText } from 'ai'; const { text } = await generateText({ model: bedrockMantle('openai.gpt-oss-120b'), prompt: 'Hello!', providerOptions: { openai: { // OpenAI-compatible provider options }, }, }); ``` ### Model Capabilities | Model | Chat Completions | Responses API | | ------------------------------- | ---------------- | ------------- | | `openai.gpt-oss-20b` | ✓ | ✓ | | `openai.gpt-oss-120b` | ✓ | ✓ | | `openai.gpt-oss-safeguard-20b` | ✓ | ✗ | | `openai.gpt-oss-safeguard-120b` | ✓ | ✗ | The Bedrock Mantle provider uses the OpenAI-compatible API format. Models available through the Mantle endpoint may differ from those available through the Bedrock Converse API. Use the Mantle endpoint's model listing API to discover all available models. ## Migrating to `@ai-sdk/amazon-bedrock` 2.x The Amazon Bedrock provider was rewritten in version 2.x to remove the dependency on the `@aws-sdk/client-bedrock-runtime` package. The `bedrockOptions` provider setting previously available has been removed. If you were using the `bedrockOptions` object, you should now use the `region`, `accessKeyId`, `secretAccessKey`, and `sessionToken` settings directly instead. Static IAM user keys do not require `sessionToken`. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Claude Platform on AWS description: Learn how to use Claude Platform on AWS with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/anthropic-aws" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The Claude Platform on AWS provider for the [AI SDK](/docs) gives you access to the Anthropic Messages API hosted inside your AWS environment. Anthropic operates the stack, so you get the same API surface and feature set as the first-party [Anthropic provider](/providers/ai-sdk-providers/anthropic) — including streaming, prompt caching, tool use, computer use, Agent Skills, and `anthropic-beta` headers — with AWS-native authentication and AWS Marketplace billing. Start with [AI Gateway](/providers/ai-sdk-providers/ai-gateway) for models in its catalog (recommended), or use `@ai-sdk/anthropic-aws` to connect directly to Claude Platform on AWS. ```ts provider="anthropic-aws" model="anthropic/claude-sonnet-5.5" import { generateText } from 'ai'; const { text } = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { gateway: { order: ['claudeaws'], }, }, }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. The example sets `providerOptions.gateway.order` to `['claudeaws']`. This sends the request to Claude Platform on AWS first, then falls back to other available providers of the same model if Claude Platform on AWS is unavailable or the request fails. `order` sets a preference; it does not restrict fallback to Claude Platform on AWS. See [provider ordering](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). The dropdown lists models served by Claude Platform on AWS through Gateway with a corresponding direct-provider example. Provider availability depends on the model and your team's [Provider Allowlist](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). Gateway uses the `claudeaws` route for Claude Platform on AWS. Enable `claudeaws` in your team's Gateway Provider Allowlist if it is disabled; otherwise `order` allows Gateway to fall back to another provider. It authenticates the upstream request using Gateway-managed credentials; your AWS workspace and AWS Marketplace billing are configured separately when using the direct provider. ## Using `@ai-sdk/anthropic-aws` For direct access to Claude Platform on AWS, install `ai` and `@ai-sdk/anthropic-aws`: ```bash pnpm add ai @ai-sdk/anthropic-aws ``` Set your Claude Platform on AWS API credential in the `ANTHROPIC_AWS_API_KEY` environment variable: ```bash export ANTHROPIC_AWS_API_KEY=your_api_key_here export AWS_REGION=us-west-2 export ANTHROPIC_AWS_WORKSPACE_ID=your_workspace_id ``` Use the direct provider when you need AWS-native authentication, AWS Marketplace billing, or access to your Claude Platform workspace. Gateway requests do not use your workspace configuration. The following sections document the direct `@ai-sdk/anthropic-aws` provider, including its model factories and settings. Gateway model and feature availability can differ. This differs from [Amazon Bedrock](/providers/ai-sdk-providers/amazon-bedrock) in two important ways: Claude Platform on AWS uses Anthropic's Messages API directly (not Bedrock's `Converse`/`InvokeModel`), and new features are available the same day they launch on the first-party Claude API (no AWS integration delay). ### Prerequisites Before you can use the provider, your AWS account must be subscribed to Claude Platform on AWS through the AWS Marketplace, and outbound web identity federation must be enabled on the account (a one-time setup step): ```bash aws iam enable-outbound-web-identity-federation ``` Without this, every request returns `Outbound web identity federation is disabled for your account`. This is the most common setup error. You'll also need your workspace ID. When you subscribe, AWS provisions an initial workspace for your account in the selected region. You can find the ID in the Claude Console under **Workspaces** (accessed from the AWS Console via the Claude Platform on AWS service page). ### Authentication The provider supports two authentication methods: #### Using AWS SigV4 (recommended for production) SigV4 integrates with your existing AWS IAM policies, roles, and auditing. Configure AWS credentials using any method supported by the AWS default credential provider chain — environment variables, shared credentials file, web identity (IRSA), ECS container credentials, or EC2 instance metadata. ```bash AWS_REGION=us-west-2 ANTHROPIC_AWS_WORKSPACE_ID=wrkspc_… AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=… # AWS_SESSION_TOKEN=… # only for temporary credentials (SSO, STS, assumed role) ``` Then use the default provider instance: ```ts import { anthropicAws } from '@ai-sdk/anthropic-aws'; ``` Or instantiate explicitly: ```ts import { createAnthropicAws } from '@ai-sdk/anthropic-aws'; const anthropicAws = createAnthropicAws({ region: 'us-west-2', workspaceId: 'wrkspc_…', }); ``` For dynamic credentials (e.g., assuming a role at request time): ```ts const anthropicAws = createAnthropicAws({ region: 'us-west-2', workspaceId: 'wrkspc_…', credentialProvider: async () => fetchCredentialsFromSTS(), }); ``` #### Using an API key For simpler integration paths (local development, scripts, migration from the first-party Claude API), you can authenticate with an API key instead of SigV4. Your Anthropic account representative provisions API keys for Claude Platform on AWS. ```bash ANTHROPIC_AWS_API_KEY=sk-… ``` When `apiKey` is set, it takes precedence over any SigV4 credentials in the environment. ```ts import { createAnthropicAws } from '@ai-sdk/anthropic-aws'; const anthropicAws = createAnthropicAws({ region: 'us-west-2', workspaceId: 'wrkspc_…', apiKey: 'sk-…', }); ``` ### Provider Settings | Setting | Description | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `region` | AWS region for the Claude Platform on AWS endpoint. Reads from `AWS_REGION` if omitted. Required — no fallback default. | | `workspaceId` | Anthropic workspace ID for this AWS account. Sent on every request via the `anthropic-workspace-id` header. Reads from `ANTHROPIC_AWS_WORKSPACE_ID` if omitted. | | `apiKey` | API key for `x-api-key` authentication. When provided, used instead of SigV4. Reads from `ANTHROPIC_AWS_API_KEY` if omitted. | | `accessKeyId` | AWS access key ID for SigV4. Reads from `AWS_ACCESS_KEY_ID`. | | `secretAccessKey` | AWS secret access key for SigV4. Reads from `AWS_SECRET_ACCESS_KEY`. | | `sessionToken` | AWS session token for SigV4 (temporary credentials only). Reads from `AWS_SESSION_TOKEN`. | | `baseURL` | Base URL override. Defaults to `https://aws-external-anthropic.{region}.api.aws/v1`. | | `headers` | Custom headers to include on every request. | | `fetch` | Custom fetch implementation for testing or middleware. | | `credentialProvider` | Function returning dynamic AWS credentials. Overrides `accessKeyId`, `secretAccessKey`, and `sessionToken`. | ## Language Models You can create models that call the Anthropic Messages API on AWS using the provider instance: ```ts import { anthropicAws } from '@ai-sdk/anthropic-aws'; const model = anthropicAws('claude-sonnet-5-5'); ``` Model IDs are identical to the first-party Anthropic API. See the [Anthropic provider docs](/providers/ai-sdk-providers/anthropic#language-models) for the full list of language model options, prompt caching, computer use, web search, code execution, and Agent Skills. All of these work identically here because Claude Platform on AWS uses Anthropic's runtime directly. ### Example ```ts import { anthropicAws } from '@ai-sdk/anthropic-aws'; import { generateText } from 'ai'; const { text } = await generateText({ model: anthropicAws('claude-sonnet-5-5'), prompt: 'Invent a new holiday and describe its traditions.', }); ``` ## IAM permissions Your IAM principal needs permission to call the Claude Platform on AWS actions on your workspace. AWS provides three managed policies: - `AnthropicFullAccess` — grants `aws-external-anthropic:*` on all resources. - `AnthropicInferenceAccess` — grants read actions plus `CreateInference`, `CreateBatchInference`, `CancelBatchInference`, `DeleteBatchInference`, and `CountTokens` on all workspaces. **This is the minimum for calling models.** - `AnthropicReadOnlyAccess` — grants `Get*`, `List*`, and `CallWithBearerToken` on all workspaces. Insufficient for inference. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Anthropic description: Learn how to use Anthropic with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/anthropic" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [Anthropic](https://www.anthropic.com/) provider contains language model support for the [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post). You can use Anthropic models through [AI Gateway](/providers/ai-sdk-providers/ai-gateway) (recommended) or connect directly with `@ai-sdk/anthropic`. ```ts provider="anthropic" model="anthropic/claude-sonnet-5.5" import { generateText } from 'ai'; const { text } = await generateText({ model: "anthropic/claude-sonnet-5.5", prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. Gateway feature and provider-option support depends on the model and the provider serving the request. See [Gateway provider options](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options). ## Using `@ai-sdk/anthropic` For direct access to Anthropic, install `ai` and `@ai-sdk/anthropic`: ```bash pnpm add ai @ai-sdk/anthropic ``` Set your Anthropic API credential in the `ANTHROPIC_API_KEY` environment variable: ```bash export ANTHROPIC_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/anthropic`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/anthropic` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `anthropic` from `@ai-sdk/anthropic`: ```ts import { anthropic } from '@ai-sdk/anthropic'; ``` If you need a customized setup, you can import `createAnthropic` from `@ai-sdk/anthropic` and create a provider instance with your settings: ```ts import { createAnthropic } from '@ai-sdk/anthropic'; const anthropic = createAnthropic({ // custom settings }); ``` You can use the following optional settings to customize the Anthropic provider instance: - **batchResultDownloads** *object* Settings for downloading JSON Lines batch results. - **maxLineBytes** *number* Maximum UTF-8 bytes per row, excluding the LF delimiter. Defaults to 64 MiB (67,108,864 bytes). Must be a positive safe integer. Oversized rows throw a `DownloadError` and cancel the download. - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use proxy servers. The default prefix is `https://api.anthropic.com/v1`. - **apiKey** *string* API key that is being sent using the `x-api-key` header. It defaults to the `ANTHROPIC_API_KEY` environment variable. Only one of `apiKey` or `authToken` is required. - **authToken** *string* Auth token that is being sent using the `Authorization: Bearer` header. It defaults to the `ANTHROPIC_AUTH_TOKEN` environment variable. Only one of `apiKey` or `authToken` is required. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. ## Language Models You can create models that call the [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post) using the provider instance. The first argument is the model id, e.g. `claude-haiku-5-5`. Some models have multi-modal capabilities. ```ts const model = anthropic('claude-haiku-5-5'); ``` You can also use the following aliases for model creation: - `anthropic.languageModel('claude-haiku-5-5')` - Creates a language model - `anthropic.chat('claude-haiku-5-5')` - Alias for `languageModel` - `anthropic.messages('claude-haiku-5-5')` - Alias for `languageModel` You can use Anthropic language models to generate text with the `generateText` function: ```ts import { anthropic } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text } = await generateText({ model: anthropic('claude-haiku-5-5'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` Anthropic language models can also be used in the `streamText` function and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output) (see [AI SDK Core](/docs/ai-sdk-core)). ### Message-start events During streaming, each Anthropic `message_start` event is exposed as an `anthropic.message_start` custom stream part containing the message ID, model, and raw initial token usage. No provider option is required. ```ts import { anthropic } from '@ai-sdk/anthropic'; import { streamText } from 'ai'; const result = streamText({ model: anthropic('claude-haiku-5-5'), prompt: 'Say hi in one word.', }); for await (const part of result.fullStream) { if (part.type === 'custom' && part.kind === 'anthropic.message_start') { console.log(part.providerMetadata?.anthropic); } } ``` Initial usage may be incomplete; use `result.usage` for final token counts. Like other custom parts, this event is included in response content and forwarded to UI message streams. It is ignored when replaying conversation history to Anthropic. ### Provider options The following optional provider options are available for Anthropic models: - `disableParallelToolUse` *boolean* Optional. Disables the use of parallel tool calls. Defaults to `false`. When set to `true`, the model will only call one tool at a time instead of potentially calling multiple tools in parallel. - `sendReasoning` *boolean* Optional. Include reasoning content in requests sent to the model. Defaults to `true`. If you are experiencing issues with the model handling requests involving reasoning content, you can set this to `false` to omit them from the request. - `effort` *"low" | "medium" | "high" | "xhigh" | "max"* Optional. See [Effort section](#effort) for more details. - `taskBudget` *object* Optional. See [Task Budgets section](#task-budgets) for more details. - `speed` *"fast" | "standard"* Optional. See [Fast Mode section](#fast-mode) for more details. - `inferenceGeo` *"us" | "global"* Optional. See [Data Residency section](#data-residency) for more details. - `thinking` *object* Optional. See [Reasoning section](#reasoning) for more details. - `toolStreaming` *boolean* Whether to enable tool streaming (and structured output streaming). Default to `true`. - `structuredOutputMode` *"outputFormat" | "jsonTool" | "auto"* Determines how structured outputs are generated. Optional. - `"outputFormat"`: Use the `output_format` parameter to specify the structured output format. - `"jsonTool"`: Use a special `"json"` tool to specify the structured output format. - `"auto"`: Use `"outputFormat"` when supported, otherwise fall back to `"jsonTool"` (default). - `metadata` *object* Optional. Metadata to include with the request. See the [Anthropic API documentation](https://platform.claude.com/docs/en/api/messages/create) for details. - `userId` *string* - An external identifier for the end-user. Should be a UUID, hash, or other opaque identifier. Must not contain PII. ### Batch Batch support is experimental and the API may change in patch releases. Anthropic language models support asynchronous text generation through the [Message Batches API](https://platform.claude.com/docs/en/build-with-claude/batch-processing). Pass the Anthropic provider to the AI SDK's [Batch](/docs/ai-sdk-core/batch) API for the complete workflow, including polling, persistence, and result handling. Each request specifies its `type` and `model`. Anthropic supports using different text models within the same batch. #### Batch Limitations Anthropic Message Batches do not support the `speed` option or completion webhooks. Explicit `anthropicBeta` values must be configured when starting the batch rather than on an individual request. When you provide a `webhookUrl`, the provider returns an unsupported warning and starts the batch without a webhook. ### Structured Outputs and Tool Input Streaming Tool call streaming is enabled by default. You can opt out by setting the `toolStreaming` provider option to `false`. ```ts import { anthropic } from '@ai-sdk/anthropic'; import { streamText, tool } from 'ai'; import { z } from 'zod'; const result = streamText({ model: anthropic('claude-sonnet-5-5'), tools: { writeFile: tool({ description: 'Write content to a file', inputSchema: z.object({ path: z.string(), content: z.string(), }), execute: async ({ path, content }) => { // Implementation return { success: true }; }, }), }, prompt: 'Write a short story to story.txt', }); ``` For `claude-sonnet-5-5`, `claude-opus-5-5`, and `claude-fable-5-1`, the default `"auto"` mode uses native structured outputs through `output_config.format`. These models reject forced tool use, so the `"jsonTool"` mode cannot be used with them. If you request `"jsonTool"` anyway, the provider falls back to `"outputFormat"` and emits a warning. Likewise, a `required` or named `toolChoice` is downgraded to `auto` with a warning; instruct the model to use the tool in your prompt and verify that a tool call was made. ### Effort Anthropic introduced an `effort` option with `claude-opus-4-5` that affects thinking, text responses, and function calls. Effort defaults to `high` and you can set it to `medium` or `low` to save tokens and to lower time-to-last-token latency (TTLT). `claude-opus-4-7`, `claude-opus-4-8`, `claude-opus-5`, `claude-opus-5-5`, `claude-fable-5`, `claude-fable-5-1`, `claude-sonnet-5`, `claude-sonnet-5-5`, and `claude-haiku-5-5` additionally support `xhigh` for maximum reasoning effort. On `claude-opus-5` and `claude-haiku-5-5`, thinking can only be disabled at effort levels up to and including `high`. When you combine `thinking: { type: 'disabled' }` with `effort: 'xhigh'` or `effort: 'max'`, the AI SDK lowers the effort to `high` and emits a warning instead of sending a request that the API would reject. `claude-haiku-5-5` is the first Haiku model with effort levels and supports all five of them. It uses adaptive thinking and does not support thinking token budgets: the AI SDK converts `thinking: { type: 'enabled', budgetTokens }` to `{ type: 'adaptive' }` and emits a warning. Use `effort` to control how much it thinks. Unlike `claude-sonnet-5-5`, thinking can still be turned off with `thinking: { type: 'disabled' }`, but only at `low`, `medium`, and `high` effort. Start at `medium` effort and test other levels rather than carrying over the settings you used on `claude-haiku-4-5`. On `claude-opus-5-5`, thinking is always adaptive and cannot be disabled, so `effort` is the main control for latency and cost. The API default effort for `claude-opus-5-5` is `medium`. Start there and test other levels rather than carrying over the setting you used on `claude-opus-5`. Set `maxOutputTokens` with room for thinking as well as the reply; thinking counts toward the limit even when its content is not returned. On `claude-sonnet-5-5`, thinking cannot be turned off either, and the API default effort is `high`. Its effort levels are not equivalent to those of `claude-sonnet-5`, so test several levels rather than carrying over your current setting. For agentic coding and other multi-step tool use, start at `medium` for well-specified tasks and move to `high` for harder ones. The lowest thinking setting, [`between_tools`](#between-tools-thinking), is only accepted at `low`, `medium`, and `high` effort. When you combine it with `effort: 'xhigh'` or `effort: 'max'`, the AI SDK lowers the effort to `high` and emits a warning. ```ts {8-10} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text, usage } = await generateText({ model: anthropic('claude-opus-5-5'), prompt: 'How many people will live in the world in 2040?', providerOptions: { anthropic: { effort: 'low', } satisfies AnthropicLanguageModelOptions, }, }); console.log(text); // resulting text console.log(usage); // token usage ``` ### Fast Mode Anthropic supports a [`speed` option](https://code.claude.com/docs/en/fast-mode) for `claude-opus-4-6` that enables faster inference with approximately 2.5x faster output token speeds. ```ts {8-10} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text } = await generateText({ model: anthropic('claude-opus-4-6'), prompt: 'Write a short poem about the sea.', providerOptions: { anthropic: { speed: 'fast', } satisfies AnthropicLanguageModelOptions, }, }); ``` The `speed` option accepts `'fast'` or `'standard'` (default behavior). ### Task Budgets `claude-opus-4-7` supports a `taskBudget` option that informs the model of the total token budget available for an agentic turn. The model uses this information to prioritize work, plan ahead, and wind down gracefully as the budget is consumed. Task budgets are advisory — they do not enforce a hard token limit. The model will attempt to stay within budget, but actual usage may vary. ```ts {8-13} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text } = await generateText({ model: anthropic('claude-opus-5-5'), prompt: 'Research the pros and cons of Rust vs Go for building CLI tools.', providerOptions: { anthropic: { taskBudget: { type: 'tokens', total: 400000, }, } satisfies AnthropicLanguageModelOptions, }, }); ``` For long-running agents that compact and restart context, you can carry the remaining budget forward using the `remaining` field: ```ts taskBudget: { type: 'tokens', total: 400000, remaining: 215000, // budget left after prior compacted-away contexts } ``` The `taskBudget` object accepts: - `type` *"tokens"* - Budget type. Currently only `"tokens"` is supported. - `total` *number* - Total task budget for the agentic turn. Minimum 20,000. - `remaining` *number* - Budget left after prior compacted-away contexts. Must be between 0 and `total`. Defaults to `total` if omitted. ### Data Residency Anthropic supports an [`inferenceGeo` option](https://platform.claude.com/docs/en/build-with-claude/data-residency) that controls where model inference runs for a request. ```ts {8-10} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text } = await generateText({ model: anthropic('claude-opus-5-5'), prompt: 'Summarize the key points of this document.', providerOptions: { anthropic: { inferenceGeo: 'us', } satisfies AnthropicLanguageModelOptions, }, }); ``` The `inferenceGeo` option accepts `'us'` (US-only infrastructure) or `'global'` (default, any available geography). ### Safety Classifiers and Fallbacks Claude Fable 5 has safeguards that limit its performance in certain areas like cybersecurity, biology, and chemistry, and automated safety checks are run on every request. Claude Haiku 5.5 has the same biology safeguards as Claude Sonnet 5 and 5.5, plus safeguards that prevent cyber misuse. Claude Sonnet 5.5 also has safety classifiers enabled, including for cybersecurity, biology, and reasoning extraction. Requests that ask the model to reproduce its internal reasoning in the response text can be declined with the `reasoning_extraction` category; use `thinking: { type: 'adaptive', display: 'summarized' }` and read the reasoning parts instead. When one of these checks blocks a request, the API does not answer it. Instead it returns a classifier block: a `200` response with a `refusal` stop reason and an optional `stop_details` object describing the category that triggered the block. The AI SDK surfaces this as a `content-filter` finish reason, with the details available on `providerMetadata.anthropic.stopDetails`. A classifier block looks like this: ```json { "type": "message", "model": "claude-fable-5", "content": [], "stop_reason": "refusal", "stop_details": { "type": "refusal", "category": "cyber", "explanation": "This request triggered restrictions on violative cyber content and was blocked under Anthropic's Usage Policy." } } ``` Branch on the finish reason rather than on the presence of `stop_details` — the API may return a refusal with no details at all. To avoid receiving a classifier block, pass the `fallbacks` option. When the primary model's classifiers block a turn, the API automatically retries it server-side on a fallback model and returns that model's answer. The required beta header is added for you. The recommended configuration is `fallbacks: 'default'`, which lets the API route the retry to Anthropic's recommended fallback model based on the refusal category and removes the need to migrate when a fallback model is deprecated: ```ts {8-10} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text } = await generateText({ model: anthropic('claude-fable-5-1'), prompt: 'Explain the history of cryptography.', providerOptions: { anthropic: { fallbacks: 'default', } satisfies AnthropicLanguageModelOptions, }, }); ``` You can also pin an explicit fallback chain: ```ts {8-10} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text } = await generateText({ model: anthropic('claude-fable-5-1'), prompt: 'Explain the history of cryptography.', providerOptions: { anthropic: { fallbacks: [{ model: 'claude-fable-5-1' }], } satisfies AnthropicLanguageModelOptions, }, }); ``` Each fallback entry requires a `model` and may additionally override `max_tokens`, `thinking`, `output_config`, and `speed` for that attempt only. When a fallback serves the turn, it is recorded in the per-model usage breakdown (`usage.iterations`) as a `fallback_message` entry. The AI SDK exposes this breakdown on `providerMetadata.anthropic.iterations`, so you can detect whether a fallback ran: ```ts // set up `result` with a `streamText` call passing `fallbacks` as shown // above, then consume the stream const { iterations } = (await result.finalStep).providerMetadata?.anthropic ?? {}; const servedByFallback = iterations?.some( iteration => iteration.type === 'fallback_message', ); console.log('Served by fallback:', servedByFallback); ``` ### Dangerous Tool Use Safeguard The `safeguards` option asks the API to run additional server-side checks as part of the request. The `dangerous_tool_use` safeguard classifies every `tool_use` block in the response for dangerous actions, such as destructive shell commands or data exfiltration, and returns a verdict per tool call. This is the check that Claude Code's auto mode relies on. The `dangerous-tool-use-2026-09-03` beta header is added for you. ```ts {9-16} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText, tool } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: anthropic('claude-sonnet-5-5'), prompt: 'Use the bash tool to run: echo hello', tools: { bash: tool({ inputSchema: z.object({ command: z.string() }) }) }, providerOptions: { anthropic: { safeguards: [ { type: 'dangerous_tool_use', classifierContext: { v: 1, permission_mode: 'auto' }, }, ], } satisfies AnthropicLanguageModelOptions, }, }); console.log(result.providerMetadata?.anthropic?.safeguardResults); ``` The verdicts are available on `providerMetadata.anthropic.safeguardResults`, one entry per requested safeguard, in the API's wire shape: ```json [ { "type": "dangerous_tool_use", "status": { "type": "available", "tool_uses": { "toolu_01Ti4QpUfhLV6QqCTFVY8C7w": { "type": "evaluated", "outcome": "not_flagged" } } } } ] ``` `status.type` is `available` when the classifier ran; `unsupported` means the API key is not enabled for the beta. Inside `status.tool_uses`, each tool call id maps to `evaluated` (with an `outcome` of `not_flagged` or `flagged`, the latter with an `explanation` such as `[Data Exfiltration]`), `skipped`, or `unavailable` (a transient classifier failure). When streaming, the verdicts arrive on the final `message_delta` event and are exposed on the `finish` part's provider metadata. Anthropic's platform reference does not document the beta yet; the feature is described from the client side in the Claude Code [auto mode classifier](https://code.claude.com/docs/en/auto-mode-classifier-billing) and [gateway compatibility](https://code.claude.com/docs/en/llm-gateway-protocol#feature-pass-through) guides. ### Reasoning Anthropic models support extended thinking, where Claude shows its reasoning process before providing a final answer. #### Adaptive Thinking For newer models (`claude-sonnet-4-6`, `claude-opus-4-6`, and later), use adaptive thinking. Claude automatically determines how much reasoning to use based on the complexity of the prompt. ```ts {4,8-10} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text, reasoningText, reasoning } = await generateText({ model: anthropic('claude-opus-4-6'), prompt: 'How many people will live in the world in 2040?', providerOptions: { anthropic: { thinking: { type: 'adaptive' }, } satisfies AnthropicLanguageModelOptions, }, }); console.log(reasoningText); // reasoning text console.log(reasoning); // reasoning details including redacted reasoning console.log(text); // text response ``` You can combine adaptive thinking with the `effort` option to control how much reasoning Claude uses: ```ts {6-8} const { text } = await generateText({ model: anthropic('claude-opus-4-6'), prompt: 'Invent a new holiday and describe its traditions.', providerOptions: { anthropic: { thinking: { type: 'adaptive' }, effort: 'max', // 'low' | 'medium' | 'high' | 'max' } satisfies AnthropicLanguageModelOptions, }, }); ``` ##### Adaptive-Only Models `claude-sonnet-5-5`, `claude-opus-5-5`, `claude-fable-5`, and `claude-fable-5-1` always run adaptive thinking. The API rejects `thinking: { type: 'disabled' }` and budget-based `thinking: { type: 'enabled' }` for these models. To keep existing code working, the provider: - removes `thinking: { type: 'disabled' }` and emits a warning, - converts `thinking: { type: 'enabled', budgetTokens }` to `{ type: 'adaptive' }` and emits a warning, - maps the top-level `reasoning: 'none'` option to `effort: 'low'` and emits a warning. On `claude-sonnet-5-5`, which supports [`between_tools` thinking](#between-tools-thinking), the provider instead replaces `thinking: { type: 'disabled' }` with `thinking: { type: 'between_tools' }` (with a warning) and maps `reasoning: 'none'` to `between_tools` thinking. Use `effort` to control how much these models think. On `claude-opus-5-5`, set `effort: 'low'` when time to first token matters, and move to `medium` if quality drops: ```ts {6-9} const { text } = await generateText({ model: anthropic('claude-opus-5-5'), prompt: 'Summarize this ticket in one sentence.', providerOptions: { anthropic: { // thinking stays adaptive; effort controls how much the model thinks effort: 'low', } satisfies AnthropicLanguageModelOptions, }, }); ``` Responses from adaptive-only models can begin with a thinking block. Read the response by content type (`text`, `reasoning`, `tool-call`) rather than assuming the first part is text. Under the default `display: 'omitted'` setting, thinking blocks are returned with empty text. ##### Between-Tools Thinking `claude-sonnet-5-5` supports `thinking: { type: 'between_tools' }`, its lowest thinking setting. The model does no upfront thinking, but the short progress notes it writes between tool calls are still returned as thinking blocks, each with a short summary. They are available as reasoning parts and are sent back unchanged on the next step. `between_tools` accepts no other thinking options (such as `display` or `blockBinding`) and is only accepted at `low`, `medium`, and `high` effort. Per-message effort changes via [mid-conversation system messages](#mid-conversation-system-controls) are not supported with `between_tools`. ```ts {16-17} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText, isStepCount, tool } from 'ai'; import { z } from 'zod'; const { text, steps } = await generateText({ model: anthropic('claude-sonnet-5-5'), stopWhen: isStepCount(5), tools: { weather: tool({ inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => ({ city, temperature: 72 }), }), }, providerOptions: { anthropic: { thinking: { type: 'between_tools' }, effort: 'low', } satisfies AnthropicLanguageModelOptions, }, prompt: 'Compare the weather in San Francisco and New York.', }); ``` ##### Thinking Display (Opus 4.7+) Starting with `claude-opus-4-7`, thinking content is omitted from the response by default — thinking blocks are present in the stream but their text is empty. To receive reasoning output, set `display: 'summarized'`: ```ts {5} const { text, reasoningText } = await generateText({ model: anthropic('claude-opus-4-8'), providerOptions: { anthropic: { thinking: { type: 'adaptive', display: 'summarized' }, } satisfies AnthropicLanguageModelOptions, }, prompt: 'How many people will live in the world in 2040?', }); console.log(reasoningText); // reasoning text (empty without display: 'summarized') console.log(text); ``` If you stream reasoning to users with `claude-opus-4-7`, the default `"omitted"` display will cause a long pause before output begins. Set `display: "summarized"` to restore visible progress during thinking. ##### Thinking Updates Use `display: 'updates'` with `claude-sonnet-5-5`, `claude-opus-5-5`, or `claude-fable-5-1` to stream thinking summaries between tool calls. These models return progress notes between tool calls as thinking blocks rather than text, so without this setting a long tool-calling turn can look silent. The provider adds the required `thinking-display-updates-2026-08-18` beta header automatically: ```ts {12-16} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { streamText, tool } from 'ai'; import { z } from 'zod'; const result = streamText({ model: anthropic('claude-fable-5-1'), tools: { weather: tool({ inputSchema: z.object({ city: z.string() }), }), }, providerOptions: { anthropic: { thinking: { type: 'adaptive', display: 'updates' }, } satisfies AnthropicLanguageModelOptions, }, prompt: 'Compare the weather in San Francisco and New York.', }); ``` ##### Thinking Binding Controls Fable 5.1, Opus 5.5, and Sonnet 5.5 can recover from a thinking-block prefix mismatch by dropping the mismatched block. Set `blockBinding.prefixMismatchBehavior` to `drop_block`, or set it to `error` to reject the request. You can provide block binding by itself to preserve the model's default thinking mode, or combine it with adaptive thinking. Block binding is not available with `between_tools` thinking; keep the message history append-only instead. ```ts {7-11} const result = await generateText({ model: anthropic('claude-fable-5-1'), prompt: 'Continue from this conversation.', providerOptions: { anthropic: { thinking: { blockBinding: { prefixMismatchBehavior: 'drop_block', }, }, } satisfies AnthropicLanguageModelOptions, }, }); console.log(result.providerMetadata?.anthropic?.inputTransformations); ``` Dropped blocks are reported in `providerMetadata.anthropic.inputTransformations` with their `type`, `path`, and `reason`. #### Budget-Based Thinking For earlier models (`claude-opus-4-20250514`, `claude-sonnet-4-20250514`, `claude-sonnet-4-5-20250929`), use `type: 'enabled'` with an explicit token budget: ```ts {4,8-10} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const { text, reasoningText, reasoning } = await generateText({ model: anthropic('claude-sonnet-4-5-20250929'), prompt: 'How many people will live in the world in 2040?', providerOptions: { anthropic: { thinking: { type: 'enabled', budgetTokens: 12000 }, } satisfies AnthropicLanguageModelOptions, }, }); console.log(reasoningText); // reasoning text console.log(reasoning); // reasoning details including redacted reasoning console.log(text); // text response ``` Anthropic's `max_tokens` limit includes both thinking and final text. To reserve room for both, the provider adds `budgetTokens` to `maxOutputTokens` when setting `max_tokens`. For known models, the combined value is capped at the model's maximum output token limit. With manual thinking (`thinking.type: 'enabled'`), a `required` or named `toolChoice` falls back to `auto` with a warning. For a named tool choice, only the selected tool is sent. This also applies when the top-level `reasoning` option enables manual thinking, including when structured output uses the internal JSON response tool. Tool use is no longer guaranteed: instruct the model to use the tool in your prompt and verify that a tool call was made. See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details on how to integrate reasoning into your chatbot. ### Context Management Anthropic's Context Management feature allows you to automatically manage conversation context by clearing tool uses or thinking content when certain conditions are met. This helps optimize token usage and manage long conversations more efficiently. You can configure context management using the `contextManagement` provider option: ```ts {7-20} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const result = await generateText({ model: anthropic('claude-sonnet-4-5-20250929'), prompt: 'Continue our conversation...', providerOptions: { anthropic: { contextManagement: { edits: [ { type: 'clear_tool_uses_20250919', trigger: { type: 'input_tokens', value: 10000 }, keep: { type: 'tool_uses', value: 5 }, clearAtLeast: { type: 'input_tokens', value: 1000 }, clearToolInputs: true, excludeTools: ['important_tool'], }, ], }, } satisfies AnthropicLanguageModelOptions, }, }); // Check what was cleared console.log(result.providerMetadata?.anthropic?.contextManagement); ``` #### Context Editing Context editing strategies selectively remove specific content types from earlier in the conversation to reduce token usage without losing the overall conversation flow. ##### Clear Tool Uses The `clear_tool_uses_20250919` edit type removes old tool call/result pairs from the conversation history: - **trigger** - Condition that triggers the clearing (e.g., `{ type: 'input_tokens', value: 10000 }` or `{ type: 'tool_uses', value: 10 }`) - **keep** - How many recent tool uses to preserve (e.g., `{ type: 'tool_uses', value: 5 }`) - **clearAtLeast** - Minimum amount to clear (e.g., `{ type: 'input_tokens', value: 1000 }`) - **clearToolInputs** - Whether to clear tool input parameters (boolean) - **excludeTools** - Array of tool names to never clear ##### Clear Thinking The `clear_thinking_20251015` edit type removes thinking/reasoning blocks from earlier turns, keeping only the most recent ones: - **keep** - How many recent thinking turns to preserve (e.g., `{ type: 'thinking_turns', value: 2 }`) or `'all'` to keep everything ```ts const result = await generateText({ model: anthropic('claude-opus-4-20250514'), prompt: 'Continue reasoning...', providerOptions: { anthropic: { thinking: { type: 'enabled', budgetTokens: 12000 }, contextManagement: { edits: [ { type: 'clear_thinking_20251015', keep: { type: 'thinking_turns', value: 2 }, }, ], }, } satisfies AnthropicLanguageModelOptions, }, }); ``` #### On-Demand Compaction Use the `compaction` provider option to ask Anthropic to summarize the supplied conversation immediately. The provider automatically adds the `compact-2026-09-04` beta header. ```ts {10-15,21-22} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText, ModelMessage } from 'ai'; const history: ModelMessage[] = [ { role: 'user', content: 'Help me design a recipe application.' }, { role: 'assistant', content: 'Start with Recipe and Ingredient.' }, { role: 'user', content: 'Recipe also needs a title and servings.' }, ]; const summary = await generateText({ model: anthropic('claude-opus-5'), messages: history, maxOutputTokens: 4096, providerOptions: { anthropic: { compaction: { type: 'summarize', instructions: 'Preserve all agreed entities and fields.', }, } satisfies AnthropicLanguageModelOptions, }, }); if (summary.rawFinishReason === 'compaction') { const [compactionMessage] = summary.finalStep.response.messages; const continuation = await generateText({ model: anthropic('claude-opus-5'), messages: [ compactionMessage, { role: 'user', content: 'Which fields should be required?' }, ], }); console.log(continuation.text); } ``` Anthropic returns the summary as a text part whose `providerMetadata.anthropic.type` is `compaction`. Its opaque signature is available as `providerMetadata.anthropic.signature`. Response messages copy that metadata to `providerOptions`, so passing `summary.finalStep.response.messages` back to the AI SDK preserves the signed block and automatically adds the beta header on the continuation request. Check `rawFinishReason` before reading the response block. Anthropic can return a successful HTTP response without a summary when summarization ends for another reason, such as `max_tokens`, `model_context_window_exceeded`, `tool_use`, `refusal`, or `end_turn`. In that case, keep the existing history and retry later; the unsuccessful summarization is still included in `providerMetadata.anthropic.iterations`. Send exactly one signed compaction block on continuation requests. It must be the first content block in `messages` and must be replayed without changing its text or signature. Remove the messages it summarizes before continuing. Keep the same system prompt and tool definitions on the compaction request that you use for the conversation, especially when preserving later thinking blocks. Before requesting compaction, resolve any unfinished tool call from the last assistant turn. Do not combine `compaction` with `contextManagement`, `stopSequences`, structured output, a required or named `toolChoice`, or a `taskBudget.remaining` value. Also omit `taskBudget.remaining` from later requests that carry the signed block. On-demand compaction usage is reported in `providerMetadata.anthropic.iterations`. Sum the iteration usage when calculating the tokens consumed by the summarization request. On-demand compaction can also be initiated in a Message Batch. Configure the `compaction` provider option on the individual batch request; the provider automatically adds `compact-2026-09-04` to the batch beta header. For more details, see [Anthropic's on-demand compaction documentation](https://platform.claude.com/docs/en/build-with-claude/compaction-on-demand). #### Threshold Compaction The `compact_20260112` edit type automatically summarizes earlier conversation context when token limits are reached. This is useful for long-running conversations where you want to preserve the essence of earlier exchanges while staying within token limits. ```ts {7-19} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { streamText } from 'ai'; const result = streamText({ model: anthropic('claude-opus-5-5'), messages: conversationHistory, providerOptions: { anthropic: { contextManagement: { edits: [ { type: 'compact_20260112', trigger: { type: 'input_tokens', value: 50000, // trigger compaction when input exceeds 50k tokens }, instructions: 'Summarize the conversation concisely, preserving key decisions and context.', pauseAfterCompaction: false, }, ], }, } satisfies AnthropicLanguageModelOptions, }, }); ``` **Configuration:** - **trigger** - Condition that triggers compaction (e.g., `{ type: 'input_tokens', value: 50000 }`) - **instructions** - Custom instructions for how the model should summarize the conversation. Use this to guide the compaction summary towards specific aspects of the conversation you want to preserve. - **pauseAfterCompaction** - When `true`, the model will pause after generating the compaction summary, allowing you to inspect or process it before continuing. Defaults to `false`. When compaction occurs, the model generates a summary of the earlier context. This summary appears as a text block with special provider metadata. ##### Detecting Compaction in Streams When using `streamText`, you can detect compaction summaries by checking the `providerMetadata` on `text-start` events: ```ts for await (const part of result.stream) { switch (part.type) { case 'text-start': { const isCompaction = part.providerMetadata?.anthropic?.type === 'compaction'; if (isCompaction) { console.log('[COMPACTION SUMMARY START]'); } break; } case 'text-delta': { process.stdout.write(part.text); break; } } } ``` ##### Compaction in UI Applications When using `useChat` or other UI hooks, compaction summaries appear as regular text parts with `providerMetadata`. You can style them differently in your UI: ```tsx { message.parts.map((part, index) => { if (part.type === 'text') { const isCompaction = (part.providerMetadata?.anthropic as { type?: string } | undefined) ?.type === 'compaction'; if (isCompaction) { return (
[Compaction Summary]
{part.text}
); } return
{part.text}
; } }); } ``` #### Applied Edits Metadata After generation, you can check which edits were applied in the provider metadata: ```ts const metadata = result.providerMetadata?.anthropic?.contextManagement; if (metadata?.appliedEdits) { metadata.appliedEdits.forEach(edit => { if (edit.type === 'clear_tool_uses_20250919') { console.log(`Cleared ${edit.clearedToolUses} tool uses`); console.log(`Freed ${edit.clearedInputTokens} tokens`); } else if (edit.type === 'clear_thinking_20251015') { console.log(`Cleared ${edit.clearedThinkingTurns} thinking turns`); console.log(`Freed ${edit.clearedInputTokens} tokens`); } else if (edit.type === 'compact_20260112') { console.log('Compaction was applied'); } }); } ``` For more details, see [Anthropic's Context Management documentation](https://docs.anthropic.com/en/docs/build-with-claude/context-management). ### Cache Control In the messages and message parts, you can use the `providerOptions` property to set cache control breakpoints. You need to set the `anthropic` property in the `providerOptions` object to `{ cacheControl: { type: 'ephemeral' } }` to set a cache control breakpoint. Cache read and cache write (creation) token counts are returned on the standard `usage` object for both `generateText` and `streamText`. You can access them at `result.usage.inputTokenDetails.cacheReadTokens` and `result.usage.inputTokenDetails.cacheWriteTokens`. ```ts {8,18-20,29-32} import { anthropic } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const errorMessage = '... long error message ...'; const result = await generateText({ model: anthropic('claude-sonnet-5-5'), messages: [ { role: 'user', content: [ { type: 'text', text: 'You are a JavaScript expert.' }, { type: 'text', text: `Error message: ${errorMessage}`, providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } }, }, }, { type: 'text', text: 'Explain the error message.' }, ], }, ], }); console.log(result.text); console.log( 'Cache read tokens:', result.usage.inputTokenDetails.cacheReadTokens, ); console.log( 'Cache write tokens:', result.usage.inputTokenDetails.cacheWriteTokens, ); ``` You can also use cache control on system messages by providing multiple system messages at the head of your messages array: ```ts {3,7-9} const result = await generateText({ model: anthropic('claude-sonnet-5-5'), messages: [ { role: 'system', content: 'Cached system message part', providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } }, }, }, { role: 'system', content: 'Uncached system message part', }, { role: 'user', content: 'User prompt', }, ], }); ``` Cache control for tools: ```ts const result = await generateText({ model: anthropic('claude-haiku-4-5'), tools: { cityAttractions: tool({ inputSchema: z.object({ city: z.string() }), providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' }, }, }, }), }, messages: [ { role: 'user', content: 'User prompt', }, ], }); ``` #### Longer cache TTL Anthropic also supports a longer 1-hour cache duration. Here's an example: ```ts const result = await generateText({ model: anthropic('claude-haiku-4-5'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Long cached message', providerOptions: { anthropic: { cacheControl: { type: 'ephemeral', ttl: '1h' }, }, }, }, ], }, ], }); ``` #### Limitations The minimum cacheable prompt length is: - 4096 tokens for Claude Opus 4.5 - 1024 tokens for Claude Opus 4.1, Claude Opus 4, Claude Sonnet 4.5, Claude Sonnet 4, Claude Sonnet 3.7, and Claude Opus 3 - 4096 tokens for Claude Haiku 4.5 - 2048 tokens for Claude Haiku 3.5 and Claude Haiku 3 Shorter prompts cannot be cached, even if marked with `cacheControl`. Any requests to cache fewer than this number of tokens will be processed without caching. For more on prompt caching with Anthropic, see [Anthropic's Cache Control documentation](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching). Because the `UIMessage` type (used by AI SDK UI hooks like `useChat`) does not support the `providerOptions` property, you can use `convertToModelMessages` first before passing the messages to functions like `generateText` or `streamText`. For more details on `providerOptions` usage, see [here](/docs/foundations/prompts#provider-options). ### Bash Tool The Bash Tool allows running bash commands. By default, the tool executes commands through the `experimental_sandbox` that you pass to `generateText` or `streamText`: ```ts import { anthropic } from '@ai-sdk/anthropic'; import { generateText, isStepCount } from 'ai'; const result = await generateText({ model: anthropic('claude-opus-4-8'), tools: { bash: anthropic.tools.bash_20250124(), }, experimental_sandbox: { description: 'A sandboxed shell environment.', run: async ({ command }) => { // Run the command in your sandbox and return the result. return { exitCode: 0, stdout: `Executed: ${command}`, stderr: '', }; }, }, stopWhen: isStepCount(2), prompt: 'List the files in the current directory.', }); ``` Parameters: - `command` (string): The bash command to run. Required unless the tool is being restarted. - `restart` (boolean, optional): Specifying true will restart this tool. Two versions are available: `bash_20250124` (recommended) and `bash_20241022`. Only certain Claude versions are supported. You can also provide a custom `execute` function when you want to handle bash execution directly instead of using the request experimental sandbox. ### Memory Tool The [Memory Tool](https://docs.claude.com/en/docs/agents-and-tools/tool-use/memory-tool) allows Claude to use a local memory, e.g. in the filesystem. Here's how to create it: ```ts const memory = anthropic.tools.memory_20250818({ execute: async action => { // Implement your memory command execution logic here // Return the result of the command execution }, }); ``` Only certain Claude versions are supported. ### Text Editor Tool The Text Editor Tool provides functionality for viewing and editing text files. ```ts const tools = { str_replace_based_edit_tool: anthropic.tools.textEditor_20250728({ maxCharacters: 10000, // optional async execute({ command, path, old_str, new_str, insert_text }) { // ... }, }), } satisfies ToolSet; ``` Different models support different versions of the tool: - `textEditor_20250728` - For Claude Sonnet 4, Opus 4, and Opus 4.1 (recommended) - `textEditor_20250124` - For Claude Sonnet 3.7 - `textEditor_20241022` - For Claude Sonnet 3.5 Note: `textEditor_20250429` is deprecated. Use `textEditor_20250728` instead. Parameters: - `command` ('view' | 'create' | 'str\_replace' | 'insert' | 'undo\_edit'): The command to run. Note: `undo_edit` is only available in Claude 3.5 Sonnet and earlier models. - `path` (string): Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`. - `file_text` (string, optional): Required for `create` command, with the content of the file to be created. - `insert_line` (number, optional): Required for `insert` command. The line number after which to insert the new string. - `new_str` (string, optional): New string for `str_replace` command. - `insert_text` (string, optional): Required for `insert` command, containing the text to insert. - `old_str` (string, optional): Required for `str_replace` command, containing the string to replace. - `view_range` (number\[], optional): Optional for `view` command to specify line range to show. ### Computer Tool The Computer Tool enables control of keyboard and mouse actions on a computer: ```ts const computerTool = anthropic.tools.computer_20251124({ displayWidthPx: 1920, displayHeightPx: 1080, displayNumber: 0, // Optional, for X11 environments enableZoom: true, // Optional, enables the zoom action execute: async ({ action, coordinate, text, region }) => { // Implement your computer control logic here // Return the result of the action // Example code: switch (action) { case 'screenshot': { // multipart result: return { type: 'image', data: fs .readFileSync('./data/screenshot-editor.png') .toString('base64'), }; } case 'zoom': { // region is [x1, y1, x2, y2] defining the area to zoom into return { type: 'image', data: fs.readFileSync('./data/zoomed-region.png').toString('base64'), }; } default: { console.log('Action:', action); console.log('Coordinate:', coordinate); console.log('Text:', text); return `executed ${action}`; } } }, // map to tool result content for LLM consumption: toModelOutput({ output }) { return typeof output === 'string' ? [{ type: 'text', text: output }] : [{ type: 'file-data', data: output.data, mediaType: 'image/png' }]; }, }); ``` Use `computerToolset_20260801` for Claude Sonnet 5.5 and Opus 5.5 (required), Opus 5, Sonnet 5, Fable 5, Fable 5.1, and Opus 4.8. Use `computer_20251124` for Claude Opus 4.5 through Opus 4.7, which support the zoom action. Use `computer_20250124` for Claude Sonnet 4.5, Haiku 4.5, Opus 4.1, Sonnet 4, Opus 4, and Sonnet 3.7. #### Computer Toolset Newer models use the computer toolset instead of a versioned computer tool. `claude-sonnet-5-5` and `claude-opus-5-5` accept computer use only through the toolset and reject the older `computer_*` tool types with a 400. The toolset does not require a beta header and has no display size parameters: coordinates are always in the pixel space of the screenshots you return. Zoom is enabled by default, and Claude can return several actions in one turn, each as its own tool call. The API returns each action as a separate `tool_use` block with `toolset_name: 'computer'`. The AI SDK maps every action to the toolset tool and passes the action name as `action`, so `execute` receives the same input shape as the older computer tools: ```ts const computerTool = anthropic.tools.computerToolset_20260801({ // optional: turn individual actions on or off configs: { zoom: { enabled: false }, }, execute: async ({ action, coordinate, text, region }) => { switch (action) { case 'screenshot': { return { type: 'image', data: fs.readFileSync('./data/screenshot.png').toString('base64'), }; } default: { console.log('Action:', action, coordinate, text, region); return `executed ${action}`; } } }, toModelOutput({ output }) { return typeof output === 'string' ? [{ type: 'text', text: output }] : [{ type: 'file-data', data: output.data, mediaType: 'image/png' }]; }, }); ``` Parameters: - `action` ('screenshot' | 'zoom' | 'left\_click' | 'right\_click' | 'middle\_click' | 'double\_click' | 'triple\_click' | 'left\_click\_drag' | 'mouse\_move' | 'left\_mouse\_down' | 'left\_mouse\_up' | 'cursor\_position' | 'scroll' | 'type' | 'key' | 'hold\_key' | 'wait'): The member tool that Claude invoked. - `coordinate` (number\[], optional): The (x, y) pixel coordinates for click, move, and scroll actions. - `start_coordinate` (number\[], optional): Where a `left_click_drag` starts. - `region` (number\[], optional): `[x1, y1, x2, y2]` for the `zoom` action. - `text` (string, optional): Text to type, the key combination for `key` and `hold_key`, or modifier keys to hold during click and scroll actions. - `repeat` (number, optional): How many times to press the key for `key`. - `duration` (number, optional): Seconds for `hold_key` and `wait`. - `scroll_direction` ('up' | 'down' | 'left' | 'right', optional) and `scroll_amount` (number, optional): For the `scroll` action. - `configs` (object, optional): Per-action settings keyed by action name. Each entry accepts `enabled` (default `true`) and `deferLoading` (default `false`, for tool search). Tool calls and results of the toolset are serialized with `toolset_name` when you pass the message history back. Keep the toolset tool in `tools` on follow-up requests, or the provider falls back to the `toolsetName` provider metadata on the tool calls of previous responses. Do not declare the toolset together with an older `computer_*` tool. Parameters: - `action` ('key' | 'type' | 'mouse\_move' | 'left\_click' | 'left\_click\_drag' | 'right\_click' | 'middle\_click' | 'double\_click' | 'screenshot' | 'cursor\_position' | 'zoom'): The action to perform. For versioned computer tools, the `zoom` action is only available with `computer_20251124`. It is also available with `computerToolset_20260801`, where it is enabled by default. - `coordinate` (number\[], optional): Required for `mouse_move` and `left_click_drag` actions. Specifies the (x, y) coordinates. - `text` (string, optional): Required for `type` and `key` actions. - `region` (number\[], optional): Required for `zoom` action. Specifies `[x1, y1, x2, y2]` coordinates for the area to inspect. - `displayWidthPx` (number): The width of the display in pixels. - `displayHeightPx` (number): The height of the display in pixels. - `displayNumber` (number, optional): The display number for X11 environments. - `enableZoom` (boolean, optional): Enable the zoom action. Only available with `computer_20251124`. Default: `false`. ### Web Search Tool Anthropic provides a provider-defined web search tool that gives Claude direct access to real-time web content, allowing it to answer questions with up-to-date information beyond its knowledge cutoff. You can enable web search using the provider-defined web search tool: ```ts import { anthropic } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const webSearchTool = anthropic.tools.webSearch_20260318({ maxUses: 5, responseInclusion: 'excluded', }); const result = await generateText({ model: anthropic('claude-opus-5-5'), prompt: 'What are the latest developments in AI?', tools: { web_search: webSearchTool, }, }); ``` Web search must be enabled in your organization's [Console settings](https://console.anthropic.com/settings/privacy). #### Sources and Citations `result.sources` and source stream events expose web-search results. Inline references are attached separately to generated text as `part.citations`, and to streamed text as `text-end.citations`. This shared citation representation includes the referenced URL or document and its supporting passage. If web-search results are unavailable, web citations also provide source references for compatibility. Indexed document references retain their existing document source parts when the original prompt supplies document information. Raw citations remain available as `text.providerMetadata.anthropic.citations`, including on streamed `text-end` events. Import `AnthropicTextProviderMetadata` and `AnthropicCitation` for typed access. `AnthropicCitation` is a union of `web_search_result_location`, `search_result_location`, `page_location`, `char_location`, and `content_block_location`. Document page, character, and block locations refer to the source document, rather than the generated answer. ```ts import type { AnthropicTextProviderMetadata } from '@ai-sdk/anthropic'; for (const part of result.content) { if (part.type === 'text') { console.log(part.citations); const metadata = part.providerMetadata as | AnthropicTextProviderMetadata | undefined; for (const citation of metadata?.anthropic.citations ?? []) { console.log(citation.type, citation.cited_text); } } } ``` #### Configuration Options Use `webSearch_20260318` for dynamic filtering and response inclusion control. The older `webSearch_20260209` and `webSearch_20250305` versions remain available for applications that need their earlier behavior. The web search tool supports several configuration options: - **maxUses** *number* Maximum number of web searches Claude can perform during the conversation. - **allowedDomains** *string\[]* Optional list of domains that Claude is allowed to search. If provided, searches will be restricted to these domains. - **blockedDomains** *string\[]* Optional list of domains that Claude should avoid when searching. - **userLocation** *object* Optional user location information to provide geographically relevant search results. - **responseInclusion** *'full' | 'excluded'* Available with `webSearch_20260318`. Controls whether search result blocks consumed by a completed code execution call in the same turn are included in the API response. The default is `'full'`. Use `'excluded'` to omit those nested call/result pairs and reduce output token usage. ```ts const webSearchTool = anthropic.tools.webSearch_20260318({ maxUses: 3, allowedDomains: ['techcrunch.com', 'wired.com'], userLocation: { type: 'approximate', country: 'US', region: 'California', city: 'San Francisco', timezone: 'America/Los_Angeles', }, responseInclusion: 'excluded', }); const result = await generateText({ model: anthropic('claude-opus-5-5'), prompt: 'Find local news about technology', tools: { web_search: webSearchTool, }, }); ``` `responseInclusion: 'excluded'` only omits results consumed by code execution calls that complete in the same turn. Results from direct calls and from code execution calls that pause before completing are always returned in full so they can be sent back on the next turn. ### Web Fetch Tool Anthropic provides a provider-defined web fetch tool that allows Claude to retrieve content from specific URLs. This is useful when you want Claude to analyze or reference content from a particular webpage or document. You can enable web fetch using the provider-defined web fetch tool: ```ts import { anthropic } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const result = await generateText({ model: anthropic('claude-opus-5-5'), prompt: 'What is this page about? https://en.wikipedia.org/wiki/Maglemosian_culture', tools: { web_fetch: anthropic.tools.webFetch_20260318({ maxUses: 1, responseInclusion: 'excluded', }), }, }); ``` ### Advisor Tool Anthropic provides a provider-executed advisor tool that lets an executor model consult a stronger advisor model during generation. This is useful for long-running coding, research, and agentic tasks where a stronger model can provide planning or course correction while the main response is generated by a faster or lower-cost model. You can enable the advisor tool using `anthropic.tools.advisor_20260301`: ```ts import { anthropic } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const result = await generateText({ model: anthropic('claude-sonnet-4-6'), instructions: 'You have access to an `advisor` tool backed by a stronger reviewer model.', prompt: 'Build a concurrent worker pool in Go with graceful shutdown. Outline the design first.', tools: { advisor: anthropic.tools.advisor_20260301({ model: 'claude-opus-4-8', maxUses: 3, maxTokens: 2048, }), }, }); ``` The AI SDK automatically sends the required `advisor-tool-2026-03-01` beta header when you include this tool. #### Configuration Options The advisor tool supports the following configuration options: - **model** *string* Required. The advisor model ID, such as `claude-opus-4-8`. The advisor model must be at least as capable as the executor model. - **maxUses** *number* Optional. Maximum number of advisor calls allowed in a single request. Once the executor reaches this cap, further advisor calls return an `advisor_tool_result_error` with `errorCode: 'max_uses_exceeded'`, and the executor continues without further advice. - **maxTokens** *number* Optional. Maximum number of tokens the advisor can generate per call, including thinking and text. The minimum is `1024`, and Anthropic recommends starting with `2048`. This limit applies independently to each advisor call and is separate from the request-level `maxOutputTokens`, which controls executor output. Values above the selected advisor model's output limit result in an Anthropic `400` error. When configured, successful advisor results include `stopReason`; check for `stopReason === 'max_tokens'` to detect truncated advice. The field is available on both plaintext and redacted advisor result variants and is preserved across follow-up turns. - **caching** *object* Optional. Enables prompt caching for the advisor's own transcript across calls within a conversation. Use `{ type: 'ephemeral', ttl: '5m' | '1h' }`. This is most useful for longer agent loops where you expect at least three advisor calls. #### How It Works The advisor tool is provider-executed, so you do not provide an `execute` function. When the executor model calls `advisor`, Anthropic runs a separate server-side inference with the advisor model. The advisor sees the transcript and returns guidance to the executor, which then continues generating within the same request. The advisor tool input is always empty. Anthropic constructs the advisor's view from the full transcript automatically. For multi-turn conversations, pass the full assistant content, including advisor tool results, back on subsequent turns. If you remove the advisor tool from a later request, also remove previous advisor tool results from the message history; otherwise the Anthropic API returns an invalid request error. #### Streaming Advisor sub-inferences do not stream. When using `streamText`, the executor stream pauses while the advisor runs, then the advisor result arrives as a single tool result before executor output resumes. ### Tool Search Anthropic provides provider-defined tool search tools that enable Claude to work with hundreds or thousands of tools by dynamically discovering and loading them on-demand. Instead of loading all tool definitions into the context window upfront, Claude searches your tool catalog and loads only the tools it needs. There are two variants: - **BM25 Search** - Uses natural language queries to find tools - **Regex Search** - Uses regex patterns (Python `re.search()` syntax) to find tools #### Basic Usage ```ts import { anthropic } from '@ai-sdk/anthropic'; import { generateText, tool } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: anthropic('claude-sonnet-5-5'), prompt: 'What is the weather in San Francisco?', tools: { toolSearch: anthropic.tools.toolSearchBm25_20251119(), get_weather: tool({ description: 'Get the current weather at a specific location', inputSchema: z.object({ location: z.string().describe('The city and state'), }), execute: async ({ location }) => ({ location, temperature: 72, condition: 'Sunny', }), // Defer tool here - Claude discovers these via the tool search tool providerOptions: { anthropic: { deferLoading: true }, }, }), }, }); ``` #### Using Regex Search For more precise tool matching, you can use the regex variant: ```ts const result = await generateText({ model: anthropic('claude-sonnet-5-5'), prompt: 'Get the weather data', tools: { toolSearch: anthropic.tools.toolSearchRegex_20251119(), // ... deferred tools }, }); ``` Claude will construct regex patterns like `weather|temperature|forecast` to find matching tools. #### Custom Tool Search You can implement your own tool search logic (e.g., using embeddings or semantic search) by returning `tool-reference` content blocks via `toModelOutput`: ```ts import { anthropic } from '@ai-sdk/anthropic'; import { generateText, tool } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: anthropic('claude-sonnet-5-5'), prompt: 'What is the weather in San Francisco?', tools: { // Custom search tool searchTools: tool({ description: 'Search for tools by keyword', inputSchema: z.object({ query: z.string() }), execute: async ({ query }) => { // Your custom search logic (embeddings, fuzzy match, etc.) const allTools = ['get_weather', 'get_forecast', 'get_temperature']; return allTools.filter(name => name.includes(query.toLowerCase())); }, toModelOutput: ({ output }) => ({ type: 'content', value: (output as string[]).map(toolName => ({ type: 'custom' as const, providerOptions: { anthropic: { type: 'tool-reference', toolName, }, }, })), }), }), // Deferred tools get_weather: tool({ description: 'Get the current weather', inputSchema: z.object({ location: z.string() }), execute: async ({ location }) => ({ location, temperature: 72 }), providerOptions: { anthropic: { deferLoading: true }, }, }), }, }); ``` This sends `tool_reference` blocks to Anthropic, which loads the corresponding deferred tool schemas into Claude's context. ### Mid-Conversation System Controls With `claude-sonnet-5-5`, `claude-opus-5-5`, and `claude-fable-5-1`, use an empty system message to change effort from the next user turn. A separate system message containing text can provide a turn-scoped reminder with `clearAt: 'next_user_message'`: its text applies until the next user message. Keep the reminder after a user turn and before an assistant turn, or at the end of the messages array. The provider adds the required `mid-conversation-system-clear-at-2026-08-21` and `mid-conversation-output-config-2026-07-01` beta headers automatically. ```ts {16-24,29-37} import { anthropic } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const result = await generateText({ model: anthropic('claude-fable-5-1'), allowSystemInMessages: true, messages: [ { role: 'user', content: 'Draft a migration plan.', }, { role: 'assistant', content: 'First, inventory the public API.', }, { role: 'system', content: '', providerOptions: { anthropic: { effort: 'high', }, }, }, { role: 'user', content: 'Complete the plan.', }, { role: 'system', content: 'For this turn, verify every compatibility claim.', providerOptions: { anthropic: { clearAt: 'next_user_message', }, }, }, ], }); ``` An effort-only system message can use an empty content array at the API level; in an AI SDK prompt, set `content: ''`. The provider serializes it as `content: []`. These effort-only messages can appear first or later in the conversation, including consecutively. The provider preserves their order. The new level applies from the next user turn until a later message changes it. Initial instruction text is sent in the top-level `system` field. Consecutive inline system messages are evaluated as a group. If an effort-only message is adjacent to a system message containing text, the whole group must follow Anthropic's [content placement rules](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#limitations). Turn-scoped messages with `clearAt` support text only. Put effort updates in a separate message, as in the example above. `clearAt` applies only to mid-conversation system messages. On initial system messages, the provider ignores `clearAt` and effort attached to non-empty content and emits a warning. Use an empty system message for an initial per-message effort setting, or the top-level `effort` provider option to configure effort for the full request. ### Mid-Conversation Tool Changes With `claude-opus-4-8`, you can add or remove tools between turns of a conversation without invalidating the prompt cache. Attach `toolChanges` to a system message that appears mid-conversation (right before an assistant message or at the end of the messages). The required `mid-conversation-tool-changes-2026-07-01` beta header is added automatically. Tools referenced by a `tool_addition` must be declared in the `tools` option — typically with `deferLoading: true`, so they are not loaded into context until the addition surfaces them. A `tool_removal` removes a previously available tool from the conversation going forward. ```ts import { anthropic } from '@ai-sdk/anthropic'; import { generateText, tool } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: anthropic('claude-opus-4-8'), // required for system messages inside `messages`: allowSystemInMessages: true, tools: { get_weather: tool({ description: 'Get weather', inputSchema: z.object({ city: z.string() }), }), get_forecast: tool({ description: 'Get 5-day forecast', inputSchema: z.object({ city: z.string() }), providerOptions: { // declared up front but not loaded until a tool_addition surfaces it anthropic: { deferLoading: true }, }, }), }, messages: [ { role: 'user', content: 'What tools do you have for weather in Paris?' }, { role: 'system', content: '', providerOptions: { anthropic: { toolChanges: [ { type: 'tool_addition', toolName: 'get_forecast' }, { type: 'tool_removal', toolName: 'get_weather' }, ], }, }, }, ], }); ``` To change a tool's definition, remove the old definition at the end of one request, then carry the conversation forward with the updated definition in `tools` on the next request. Tool changes are not supported on the initial system message. The AI SDK ignores them there and emits a warning — configure the initial tool set via the `tools` option instead. ### MCP Connectors Anthropic supports connecting to [MCP servers](https://docs.claude.com/en/docs/agents-and-tools/mcp-connector) as part of their execution. You can enable this feature with the `mcpServers` provider option: ```ts import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const result = await generateText({ model: anthropic('claude-sonnet-5-5'), prompt: `Call the echo tool with "hello world". what does it respond with back?`, providerOptions: { anthropic: { mcpServers: [ { type: 'url', name: 'echo', url: 'https://echo.mcp.inevitable.fyi/mcp', // optional: authorization token authorizationToken: mcpAuthToken, // optional: tool configuration toolConfiguration: { enabled: true, allowedTools: ['echo'], }, }, ], } satisfies AnthropicLanguageModelOptions, }, }); ``` The tool calls and results are dynamic, i.e. the input and output schemas are not known. #### Configuration Options The web fetch tool supports several configuration options: - **maxUses** *number* The maxUses parameter limits the number of web fetches performed. - **allowedDomains** *string\[]* Only fetch from these domains. - **blockedDomains** *string\[]* Never fetch from these domains. - **citations** *object* Unlike web search where citations are always enabled, citations are optional for web fetch. Set `"citations": {"enabled": true}` to enable Claude to cite specific passages from fetched documents. - **maxContentTokens** *number* The maxContentTokens parameter limits the amount of content that will be included in the context. - **useCache** *boolean* Available with `webFetch_20260318`. Set to `false` to bypass cached content and fetch fresh content. The default is `true`; disabling the cache can increase latency. - **responseInclusion** *'full' | 'excluded'* Available with `webFetch_20260318`. Controls whether fetch result blocks consumed by a completed code execution call in the same turn are included in the API response. The default is `'full'`. Direct results and results from code execution calls that pause before completing are always returned in full. #### Error Handling Web search errors are handled differently depending on whether you're using streaming or non-streaming: **Non-streaming (`generateText`):** Web search errors throw exceptions that you can catch: ```ts try { const result = await generateText({ model: anthropic('claude-opus-5-5'), prompt: 'Search for something', tools: { web_search: webSearchTool, }, }); } catch (error) { if (error.message.includes('Web search failed')) { console.log('Search error:', error.message); // Handle search error appropriately } } ``` **Streaming (`streamText`):** Web search errors are delivered as error parts in the stream: ```ts const result = await streamText({ model: anthropic('claude-opus-5-5'), prompt: 'Search for something', tools: { web_search: webSearchTool, }, }); for await (const part of result.textStream) { if (part.type === 'error') { console.log('Search error:', part.error); // Handle search error appropriately } } ``` ## Code Execution Anthropic provides a provider-defined code execution tool that gives Claude direct access to a real Python environment allowing it to execute code to inform its responses. You can enable code execution using the provider-defined code execution tool: ```ts import { anthropic } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const codeExecutionTool = anthropic.tools.codeExecution_20260120(); const result = await generateText({ model: anthropic('claude-opus-4-20250514'), prompt: 'Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]', tools: { code_execution: codeExecutionTool, }, }); ``` Three versions are available: `codeExecution_20260120` (recommended, does not require a beta header, supports Claude Opus 4.6, Sonnet 4.6, Sonnet 4.5, and Opus 4.5), `codeExecution_20250825` (supports Python and Bash with enhanced file operations), and `codeExecution_20250522` (supports Bash only). #### Uploading Files for Code Execution You can upload files via the [Files API](https://docs.anthropic.com/en/docs/build-with-claude/files) and make them available inside the code execution sandbox. First upload the file, then reference it in your message using `containerUpload: true` in the provider options: ```ts import { anthropic } from '@ai-sdk/anthropic'; import { generateText, uploadFile } from 'ai'; import * as fs from 'fs'; const { providerReference } = await uploadFile({ api: anthropic.files(), data: fs.readFileSync('./data.csv'), filename: 'data.csv', mediaType: 'text/csv', }); const result = await generateText({ model: anthropic('claude-sonnet-4-5'), tools: { code_execution: anthropic.tools.codeExecution_20250825(), }, messages: [ { role: 'user', content: [ { type: 'text', text: 'Analyze this CSV data' }, { type: 'file', mediaType: 'text/csv', data: { type: 'reference', reference: providerReference }, providerOptions: { anthropic: { containerUpload: true }, }, }, ], }, ], }); ``` Supported file types include CSV, Excel, JSON, XML, images (JPEG, PNG, GIF, WebP), and text files. #### Error Handling Code execution errors are handled differently depending on whether you're using streaming or non-streaming: **Non-streaming (`generateText`):** Code execution errors are delivered as tool result parts in the response: ```ts const result = await generateText({ model: anthropic('claude-opus-5-5'), prompt: 'Execute some Python script', tools: { code_execution: codeExecutionTool, }, }); const toolErrors = result.content?.filter( content => content.type === 'tool-error', ); toolErrors?.forEach(error => { console.error('Tool execution error:', { toolName: error.toolName, toolCallId: error.toolCallId, error: error.error, }); }); ``` **Streaming (`streamText`):** Code execution errors are delivered as error parts in the stream: ```ts const result = await streamText({ model: anthropic('claude-opus-5-5'), prompt: 'Execute some Python script', tools: { code_execution: codeExecutionTool, }, }); for await (const part of result.textStream) { if (part.type === 'error') { console.log('Code execution error:', part.error); // Handle code execution error appropriately } } ``` ### Programmatic Tool Calling [Programmatic Tool Calling](https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/programmatic-tool-calling) allows Claude to write code that calls your tools programmatically within a code execution container, rather than requiring round trips through the model for each tool invocation. This reduces latency for multi-tool workflows and decreases token consumption. To enable programmatic tool calling, use the `allowedCallers` provider option on tools that you want to be callable from within code execution: ```ts {13-17} import { anthropic, forwardAnthropicContainerIdFromLastStep, } from '@ai-sdk/anthropic'; import { generateText, tool, isStepCount } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: anthropic('claude-sonnet-4-5'), stopWhen: isStepCount(10), prompt: 'Get the weather for Tokyo, Sydney, and London, then calculate the average temperature.', tools: { code_execution: anthropic.tools.codeExecution_20260120(), getWeather: tool({ description: 'Get current weather data for a city.', inputSchema: z.object({ city: z.string().describe('Name of the city'), }), execute: async ({ city }) => { // Your weather API implementation return { temp: 22, condition: 'Sunny' }; }, // Enable this tool to be called from within code execution providerOptions: { anthropic: { allowedCallers: ['code_execution_20260120'], }, }, }), }, // Propagate container ID between steps for code execution continuity prepareStep: forwardAnthropicContainerIdFromLastStep, }); ``` In this flow: 1. Claude writes Python code that calls your `getWeather` tool multiple times in parallel 2. The SDK automatically executes your tool and returns results to the code execution container 3. Claude processes the results in code and generates the final response Programmatic tool calling requires `claude-sonnet-4-6`, `claude-sonnet-4-5`, `claude-opus-4-6`, or `claude-opus-4-5` models and uses the `code_execution_20260120` or `code_execution_20250825` tool. #### Pruning Programmatic Tool History When pruning removes a code execution call but keeps a tool call that references it through `caller.toolId`, the provider omits the dangling caller metadata from history before a subsequent user message and emits a warning. The retained tool calls and results are still sent to Anthropic; the removed code execution history is not restored. Tool-result messages do not end a turn. Caller metadata in an active tool continuation is preserved, so keep the source code execution call when returning programmatic tool results. #### Container Persistence When using programmatic tool calling across multiple steps, you need to preserve the container ID between steps using `prepareStep`. You can use the `forwardAnthropicContainerIdFromLastStep` helper function to do this automatically. The container ID is available in `providerMetadata.anthropic.container.id` after each step completes. ## Agent Skills [Anthropic Agent Skills](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview) enable Claude to perform specialized tasks like document processing (PPTX, DOCX, PDF, XLSX) and data analysis. Skills run in a sandboxed container and require the code execution tool to be enabled. ### Using Built-in Skills Anthropic provides several built-in skills: - **pptx** - Create and edit PowerPoint presentations - **docx** - Create and edit Word documents - **pdf** - Process and analyze PDF files - **xlsx** - Work with Excel spreadsheets To use skills, you need to: 1. Enable the code execution tool 2. Specify the container with skills in `providerOptions` ```ts {6-8,10-23} import { anthropic, AnthropicLanguageModelOptions } from '@ai-sdk/anthropic'; import { generateText } from 'ai'; const result = await generateText({ model: anthropic('claude-sonnet-4-5'), tools: { code_execution: anthropic.tools.codeExecution_20260120(), }, prompt: 'Create a presentation about renewable energy with 5 slides', providerOptions: { anthropic: { container: { skills: [ { type: 'anthropic', skillId: 'pptx', version: 'latest', // optional }, ], }, } satisfies AnthropicLanguageModelOptions, }, }); ``` ### Custom Skills You can also use custom skills by specifying `type: 'custom'`: ```ts {11-15} const result = await generateText({ model: anthropic('claude-sonnet-4-5'), tools: { code_execution: anthropic.tools.codeExecution_20260120(), }, prompt: 'Use my custom skill to process this data', providerOptions: { anthropic: { container: { skills: [ { type: 'custom', skillId: 'my-custom-skill-id', version: '1.0', // optional }, ], }, } satisfies AnthropicLanguageModelOptions, }, }); ``` Skills use progressive context loading and execute within a sandboxed container with code execution capabilities. ### PDF support Anthropic Claude models support reading PDF files. You can pass PDF files as part of the message content using the `file` type: Option 1: URL-based PDF document ```ts const result = await generateText({ model: anthropic('claude-sonnet-5-5'), messages: [ { role: 'user', content: [ { type: 'text', text: 'What is an embedding model according to this document?', }, { type: 'file', data: new URL( 'https://github.com/vercel/ai/blob/main/examples/ai-functions/data/ai.pdf?raw=true', ), mimeType: 'application/pdf', }, ], }, ], }); ``` Option 2: Base64-encoded PDF document ```ts const result = await generateText({ model: anthropic('claude-sonnet-5-5'), messages: [ { role: 'user', content: [ { type: 'text', text: 'What is an embedding model according to this document?', }, { type: 'file', data: fs.readFileSync('./data/ai.pdf'), mediaType: 'application/pdf', }, ], }, ], }); ``` The model will have access to the contents of the PDF file and respond to questions about it. The PDF file should be passed using the `data` field, and the `mediaType` should be set to `'application/pdf'`. ### Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Computer Use | Web Search | Tool Search | Compaction | | ------------------- | ----------- | ----------------- | ---------- | ------------ | ---------- | ----------- | ---------- | | `claude-opus-5-5` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-opus-5` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-sonnet-5-5` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-sonnet-5` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-haiku-5-5` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-fable-5-1` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-fable-5` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-opus-4-8` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-opus-4-7` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-opus-4-6` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-sonnet-4-6` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | `claude-opus-4-5` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | `claude-haiku-4-5` | ✓ | ✓ | ✓ | ✓ | ✓ | | | | `claude-sonnet-4-5` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | `claude-opus-4-1` | ✓ | ✓ | ✓ | ✓ | ✓ | | | | `claude-opus-4-0` | ✓ | ✓ | ✓ | ✓ | ✓ | | | | `claude-sonnet-4-0` | ✓ | ✓ | ✓ | ✓ | ✓ | | | The table above lists popular models. Please see the [Anthropic docs](https://docs.anthropic.com/en/docs/about-claude/models) for a full list of available models. The table above lists popular models. You can also pass any available provider model ID as a string if needed. ## Decision Models Create an experimental decision model with `anthropic.decisionModel(modelId)`. It uses the Messages API's structured-output support for Choice, Score, and Boolean questions. `claude-haiku-4-5-20251001` is one supported model. ```ts import { anthropic } from '@ai-sdk/anthropic'; import { experimental_decide } from 'ai'; const { answers } = await experimental_decide({ model: anthropic.decisionModel('claude-haiku-4-5-20251001'), state: 'I was charged twice.', questions: { requestsRefund: { type: 'boolean', instructions: 'Is the customer requesting money back?', }, department: { type: 'choice', instructions: 'Which team should handle this?', criteria: { billing: 'Charges and refunds', support: 'Other requests' }, }, }, }); ``` Choice labels are preserved exactly, and Scores are finite fractional positions on the ordered rubric. Anthropic does not support numeric bounds in its native output schema, so the adapter describes them in the prompt and validates them after parsing. Invalid answers, refusals, and truncation fail the entire call. The adapter returns no probability distributions for Choice or Score. Boolean answers contain prompted estimates of P(true), validated to be finite and in `[0, 1]`. These estimates are not guaranteed to be calibrated. Apply thresholds in application code, for example `answers.requestsRefund.probability >= 0.5`. It respects `createAnthropic` configuration and forwards `providerOptions.anthropic`, including `structuredOutputMode`. Supported models use native structured output by default; `jsonTool` uses the existing Messages JSON-tool fallback. Usage, warnings, response information, and provider metadata are preserved. See [Decisions](/docs/ai-sdk-core/decisions). Decision models can also be accessed through `customProvider` aliases or `createProviderRegistry().decisionModel('provider:model')`. Direct string IDs use Gateway by default, or an explicitly configured default provider with an `decisionModel` method. See [model aliases and registries](/docs/ai-sdk-core/decisions#model-aliases-and-registries). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: AssemblyAI description: Learn how to set up the AssemblyAI provider and check AI Gateway availability. url: "https://ai-sdk.dev/providers/ai-sdk-providers/assemblyai" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [AssemblyAI](https://assemblyai.com/) provider contains transcription model support for the AssemblyAI transcription API. AssemblyAI models are not currently available through [AI Gateway](https://vercel.com/ai-gateway/models), so the opening example uses `@ai-sdk/assemblyai` directly. See [Using AI Gateway](#using-ai-gateway) below for supported alternatives. ```ts import { transcribe } from 'ai'; import { assemblyai } from '@ai-sdk/assemblyai'; import { readFile } from 'node:fs/promises'; const { text } = await transcribe({ model: assemblyai.transcription('universal-3-5-pro'), audio: await readFile('audio.mp3'), }); console.log(text); ``` The example transcribes an `audio.mp3` file from your project. ## Using AI Gateway The current [AI Gateway model catalog](https://vercel.com/ai-gateway/models) does not list AssemblyAI models. Use `@ai-sdk/assemblyai` for the APIs on this page. To use other transcription models through Gateway, install `ai` and configure a Gateway API key: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [Gateway documentation](/providers/ai-sdk-providers/ai-gateway) for supported models and APIs. ## Using `@ai-sdk/assemblyai` For direct access to AssemblyAI, install `ai` and `@ai-sdk/assemblyai`: ```bash pnpm add ai @ai-sdk/assemblyai ``` Set your AssemblyAI API credential in the `ASSEMBLYAI_API_KEY` environment variable: ```bash export ASSEMBLYAI_API_KEY=your_api_key_here ``` The following sections document the direct `@ai-sdk/assemblyai` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `assemblyai` from `@ai-sdk/assemblyai`: ```ts import { assemblyai } from '@ai-sdk/assemblyai'; ``` If you need a customized setup, you can import `createAssemblyAI` from `@ai-sdk/assemblyai` and create a provider instance with your settings: ```ts import { createAssemblyAI } from '@ai-sdk/assemblyai'; const assemblyai = createAssemblyAI({ // custom settings, e.g. fetch: customFetch, }); ``` You can use the following optional settings to customize the AssemblyAI provider instance: - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `ASSEMBLYAI_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. ## Transcription Models You can create models that call the [AssemblyAI transcription API](https://www.assemblyai.com/docs/getting-started/transcribe-an-audio-file/typescript) using the `.transcription()` factory method. The first argument is the model id, e.g. `universal-3-5-pro`. ```ts const model = assemblyai.transcription('universal-3-5-pro'); ``` The `best` model is a **deprecated** legacy model, sent using AssemblyAI's deprecated `speech_model` request parameter. It still works, but using it emits a deprecation warning — prefer `universal-3-5-pro`. The older `nano` model has been removed by AssemblyAI and is no longer available. All newer models are sent using the [`speech_models`](https://www.assemblyai.com/docs/pre-recorded-audio/select-the-speech-model) request parameter, which the provider selects automatically based on the model id. `universal-3-pro` and `universal-2` are fully supported and continue to work, but using them emits an informational warning (not a deprecation) suggesting `universal-3-5-pro`, AssemblyAI's latest flagship model. You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the `contentSafety` option will enable content safety filtering. ```ts {7} import { transcribe } from 'ai'; import { assemblyai } from '@ai-sdk/assemblyai'; import { type AssemblyAITranscriptionModelOptions } from '@ai-sdk/assemblyai'; import { readFile } from 'fs/promises'; const result = await transcribe({ model: assemblyai.transcription('universal-3-5-pro'), audio: await readFile('audio.mp3'), providerOptions: { assemblyai: { contentSafety: true, } satisfies AssemblyAITranscriptionModelOptions, }, }); ``` The following provider options are available: - **audioEndAt** *number* End time of the audio in milliseconds. Optional. - **audioStartFrom** *number* Start time of the audio in milliseconds. Optional. - **autoChapters** *boolean* Whether to automatically generate chapters for the transcription. Optional. - **autoHighlights** *boolean* Whether to automatically generate highlights for the transcription. Optional. - **boostParam** *enum* Boost parameter for `wordBoost`. Allowed values: `'low'`, `'default'`, `'high'`. **Deprecated** — only applies to the deprecated `wordBoost`; use `keytermsPrompt` instead. Optional. - **contentSafety** *boolean* Whether to enable content safety filtering. Optional. - **contentSafetyConfidence** *number* Confidence threshold for content safety filtering (25-100). Optional. - **customSpelling** *array of objects* Custom spelling rules for the transcription. Each object has `from` (array of strings) and `to` (string) properties. Optional. - **disfluencies** *boolean* Whether to include disfluencies (um, uh, etc.) in the transcription. Optional. - **domain** *string* Enable a domain-specific model for specialized terminology. Currently supports `'medical-v1'` (Medical Mode). Optional. - **entityDetection** *boolean* Whether to detect entities in the transcription. Optional. - **filterProfanity** *boolean* Whether to filter profanity in the transcription. Optional. - **formatText** *boolean* Whether to format the text in the transcription. Optional. - **iabCategories** *boolean* Whether to include IAB categories in the transcription. Optional. - **keytermsPrompt** *array of strings* Domain-specific keyterms to boost recognition for (max 6 words per phrase). Replaces `wordBoost` for newer models — supported by `universal-3-pro`, `universal-3-5-pro`, and `slam-1` (and `universal-2` when enabled). Optional. - **languageCode** *string* Language code for the audio. Supports numerous ISO-639-1 and ISO-639-3 language codes. Optional. - **languageConfidenceThreshold** *number* Confidence threshold for language detection. Optional. - **languageDetection** *boolean* Whether to enable language detection. Optional. - **languageDetectionOptions** *object* Options for automatic language detection: `expectedLanguages` (array of strings), `fallbackLanguage` (string), `codeSwitching` (boolean), `codeSwitchingConfidenceThreshold` (number, 0-1). Optional. - **multichannel** *boolean* Whether to process multiple audio channels separately. Optional. - **prompt** *string* Natural-language context (up to 1,500 words) to steer the model. Only supported by `universal-3-pro`, `universal-3-5-pro`, and `slam-1`. Optional. - **punctuate** *boolean* Whether to add punctuation to the transcription. Optional. - **redactPii** *boolean* Whether to redact personally identifiable information. Optional. - **redactPiiAudio** *boolean* Whether to redact PII in the audio file. Optional. - **redactPiiAudioOptions** *object* Options for PII-redacted audio: `returnRedactedNoSpeechAudio` (boolean), `overrideAudioRedactionMethod` (`'silence'`). Requires `redactPiiAudio`. Optional. - **redactPiiAudioQuality** *enum* Quality of the redacted audio file. Allowed values: `'mp3'`, `'wav'`. Optional. - **redactPiiPolicies** *array of enums* Policies for PII redaction, specifying which types of information to redact. Supports numerous types like `'person_name'`, `'phone_number'`, etc. Optional. - **redactPiiReturnUnredacted** *boolean* Return the original unredacted transcript alongside the redacted one. Requires `redactPii`. Optional. - **redactPiiSub** *enum* Substitution method for redacted PII. Allowed values: `'entity_name'`, `'hash'`. Optional. - **redactStaticEntities** *object* Map of user-defined labels to exact terms to redact, e.g. `{ INTERNAL_TOOL: ['Bearclaw'] }`. Applied on top of standard PII redaction. Requires `redactPii`. Optional. - **removeAudioTags** *enum* Remove inline annotations from rich transcripts. Allowed values: `'all'` (all annotations), `'speaker'` (speaker cues only). Universal-3 Pro models. Optional. - **sentimentAnalysis** *boolean* Whether to perform sentiment analysis on the transcription. Optional. - **speakerLabels** *boolean* Whether to label different speakers in the transcription. Optional. - **speakerOptions** *object* Options for speaker diarization: `minSpeakersExpected` (number), `maxSpeakersExpected` (number). Optional. - **speakersExpected** *number* Expected number of speakers in the audio. Optional. - **speechThreshold** *number* Threshold for speech detection (0-1). Optional. - **summarization** *boolean* Whether to generate a summary of the transcription. Optional. - **summaryModel** *enum* Model to use for summarization. Allowed values: `'informative'`, `'conversational'`, `'catchy'`. Optional. - **summaryType** *enum* Type of summary to generate. Allowed values: `'bullets'`, `'bullets_verbose'`, `'gist'`, `'headline'`, `'paragraph'`. Optional. - **temperature** *number* Sampling temperature (0-1) controlling randomness. Universal-3 Pro models. Optional. - **webhookAuthHeaderName** *string* Name of the authentication header for webhook requests. Optional. - **webhookAuthHeaderValue** *string* Value of the authentication header for webhook requests. Optional. - **webhookUrl** *string* URL to send webhook notifications to. Optional. - **wordBoost** *array of strings* List of words to boost in the transcription. **Deprecated** — rejected by `universal-3-pro`, `universal-3-5-pro`, and `slam-1` (works only on `universal-2`/`best`); use `keytermsPrompt` instead. Optional. ### Speaker diarization and audio-intelligence results The AI SDK's `transcribe` result exposes `text`, `segments`, `language`, and `durationInSeconds`. AssemblyAI's richer results — speaker diarization and audio-intelligence features — don't fit that shape, so they are surfaced in two places: - **`providerMetadata.assemblyai`** — structured results for the features you enabled: `utterances` (speaker-diarized segments, when `speakerLabels` is set), `entities`, `sentimentAnalysisResults`, `contentSafetyLabels`, `iabCategoriesResult`, and `autoHighlightsResult`. - **`response.body`** — the complete, raw AssemblyAI transcript response, so any field not surfaced above (e.g. `chapters`, word-level `speaker` labels) is still available. Note: timings inside `providerMetadata` and `response.body` (e.g. `utterances[].start`) are in **milliseconds**, matching the AssemblyAI API — whereas the top-level `segments` use **seconds**. ```ts import { transcribe } from 'ai'; import { assemblyai } from '@ai-sdk/assemblyai'; import { readFile } from 'fs/promises'; const result = await transcribe({ model: assemblyai.transcription('universal-3-5-pro'), audio: await readFile('audio.mp3'), providerOptions: { assemblyai: { speakerLabels: true, entityDetection: true, }, }, }); const { utterances, entities } = result.providerMetadata?.assemblyai ?? {}; // utterances: [{ speaker: 'A', text: '…', start, end, … }, …] (start/end in ms) ``` The following AssemblyAI features are **deprecated** by the API and not surfaced in `providerMetadata` (their output remains on the raw `response.body` if enabled): Summarization, Auto Chapters, and Custom Topics. Note also that some features are language-gated (e.g. sentiment analysis is English-centric); see AssemblyAI's documentation for per-language availability. ### Model Capabilities | Model | Transcription | Duration | Segments | Language | | ------------------- | ------------- | -------- | -------- | -------- | | `universal-3-5-pro` | ✓ | ✓ | ✓ | ✓ | | `universal-3-pro` | ✓ | ✓ | ✓ | ✓ | | `universal-2` | ✓ | ✓ | ✓ | ✓ | | `best` | ✓ | ✓ | ✓ | ✓ | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Azure OpenAI description: Learn how to use Azure OpenAI with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/azure" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) provider contains language model support for the Azure OpenAI chat API. Start with [AI Gateway](/providers/ai-sdk-providers/ai-gateway) for models in its catalog (recommended), or use `@ai-sdk/azure` to connect directly to Azure OpenAI. ```ts provider="azure" model="openai/gpt-6-astra" import { generateText } from 'ai'; const { text } = await generateText({ model: "openai/gpt-6-astra", prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { gateway: { order: ['azure'], }, }, }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. The example sets `providerOptions.gateway.order` to `['azure']`. This sends the request to Azure OpenAI first, then falls back to other available providers of the same model if Azure OpenAI is unavailable or the request fails. `order` sets a preference; it does not restrict fallback to Azure OpenAI. See [provider ordering](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). The dropdown lists models served by Azure OpenAI through Gateway with a corresponding direct-provider example. Provider availability depends on the model and your team's [Provider Allowlist](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). ## Using `@ai-sdk/azure` For direct access to Azure OpenAI, install `ai` and `@ai-sdk/azure`: ```bash pnpm add ai @ai-sdk/azure ``` Set your Azure API key and resource name: ```bash export AZURE_API_KEY=your_api_key_here export AZURE_RESOURCE_NAME=your_resource_name ``` The direct example uses an Azure deployment name. Replace it with the deployment name configured in your resource. Use the direct provider for your Azure deployments, Microsoft Entra ID authentication, or Azure-specific APIs. Replace the deployment name in the Azure example with one from your resource. The following sections document the direct `@ai-sdk/azure` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `azure` from `@ai-sdk/azure`: ```ts import { azure } from '@ai-sdk/azure'; ``` If you need a customized setup, you can import `createAzure` from `@ai-sdk/azure` and create a provider instance with your settings: ```ts import { createAzure } from '@ai-sdk/azure'; const azure = createAzure({ resourceName: 'your-resource-name', // Azure resource name apiKey: 'your-api-key', }); ``` You can also use a complete [Microsoft Foundry OpenAI v1 base URL](https://learn.microsoft.com/en-us/azure/foundry/openai/api-version-lifecycle#model-support): ```ts const azure = createAzure({ baseURL: 'https://your-resource.services.ai.azure.com/openai/v1', apiKey: 'your-api-key', }); ``` For Microsoft Entra ID authentication, you can provide a token provider. Install `@azure/identity` separately if you want to use its credential helpers: ```ts import { createAzure } from '@ai-sdk/azure'; import { DefaultAzureCredential, getBearerTokenProvider, } from '@azure/identity'; const credential = new DefaultAzureCredential(); const scope = 'https://cognitiveservices.azure.com/.default'; const tokenProvider = getBearerTokenProvider(credential, scope); const azure = createAzure({ resourceName: 'your-resource-name', tokenProvider, }); ``` You can use the following optional settings to customize the OpenAI provider instance: - **resourceName** *string* Azure resource name. It defaults to the `AZURE_RESOURCE_NAME` environment variable. The resource name is used in the assembled URL: `https://{resourceName}.openai.azure.com/openai/v1{path}`. It must be a single DNS label (letters, digits, and hyphens). You can use `baseURL` instead to specify the URL prefix. - **apiKey** *string* API key that is being sent using the `api-key` header. It defaults to the `AZURE_API_KEY` environment variable. - **tokenProvider** *() => Promise\* Microsoft Entra ID token provider that returns an access token to be sent using the `Authorization` header. When provided, this is used instead of `apiKey`. - **apiVersion** *string* Sets a custom [api version](https://learn.microsoft.com/en-us/azure/ai-services/openai/api-version-deprecation). Defaults to `v1`. This setting is applied when the provider constructs the v1 path or uses deployment-based URLs. Complete v1 base URLs are used as-is. - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use proxy servers. Either this or `resourceName` can be used. When a baseURL is provided, the resourceName is ignored. For Azure-hosted URLs ending in `.openai.azure.com`, `.services.ai.azure.com`, or `.cognitiveservices.azure.com`, you can pass either an unversioned OpenAI prefix such as `https://your-resource.services.ai.azure.com/openai` or a complete v1 base URL such as `https://your-resource.services.ai.azure.com/openai/v1`. The provider appends `/v1` and the configured `api-version` to unversioned resource URLs. Complete v1 URLs are used as-is. Complete Microsoft Foundry project v1 URLs, such as `https://your-resource.services.ai.azure.com/api/projects/your-project/openai/v1`, are also used as-is. If `/v1` is omitted from a Foundry project URL, the provider appends it without adding an `api-version` query parameter. With a non-Azure custom gateway baseURL, the resolved URL is `{baseURL}{path}`; the SDK does not append `/v1` or an `api-version` query parameter in this mode. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. - **useDeploymentBasedUrls** *boolean* Use deployment-based URLs for API calls. Set to `true` to use the legacy deployment format: `{baseURL}/deployments/{deploymentId}{path}?api-version={apiVersion}` instead of `{baseURL}/v1{path}?api-version={apiVersion}`. Defaults to `false`. This option is useful for compatibility with certain Azure OpenAI models or deployments that require the legacy endpoint format. - **speechBaseURL** *string* URL prefix for Azure Speech requests (MAI-Transcribe transcription and MAI-Voice speech generation), for example a regional endpoint such as `https://eastus.api.cognitive.microsoft.com`. Defaults to `https://{resourceName}.cognitiveservices.azure.com`. Speech requests do not use `baseURL` or `apiVersion`. - **maiBaseURL** *string* URL prefix for MAI requests (MAI-Image and MAI-Transcribe-2-Streaming). Defaults to `https://{resourceName}.services.ai.azure.com/mai/v1`. - **webSocket** *WebSocketConstructor* Custom WebSocket implementation for MAI streaming transcription, for example the `WebSocket` export of the `ws` package in Node.js. With a custom implementation the API key is sent in the `api-key` header; with the native `WebSocket` it is sent as the `api-key` query parameter. Microsoft Entra ID (`tokenProvider`) authentication requires a custom implementation; without one, streaming fails with an `InvalidArgumentError` before connecting. ## Language Models The Azure OpenAI provider instance is a function that you can invoke to create a language model: ```ts const model = azure('your-deployment-name'); ``` You need to pass your deployment name as the first argument. You can optionally pass the underlying model ID in a settings object: ```ts const model = azure('production-chat', { modelId: 'gpt-5' }); ``` The SDK uses the model ID as the model identity, including in telemetry, and to detect model capabilities such as reasoning support. API requests still use the deployment name. The model ID returned by Azure in response metadata is preserved. If omitted, the deployment name is used for model identity and capability detection. Keep the supplied model ID aligned with the model running in your deployment. ### Reasoning Models Azure exposes the thinking of `DeepSeek-R1` in the generated text using the `` tag. You can use the `extractReasoningMiddleware` to extract this reasoning and expose it as a `reasoning` property on the result: ```ts import { azure } from '@ai-sdk/azure'; import { wrapLanguageModel, extractReasoningMiddleware } from 'ai'; const enhancedModel = wrapLanguageModel({ model: azure('your-deepseek-r1-deployment-name'), middleware: extractReasoningMiddleware({ tagName: 'think' }), }); ``` You can then use that enhanced model in functions like `generateText` and `streamText`. The Azure provider calls the Responses API by default (unless you specify e.g. `azure.chat`). ### Example You can use OpenAI language models to generate text with the `generateText` function: ```ts import { azure } from '@ai-sdk/azure'; import { generateText } from 'ai'; const { text } = await generateText({ model: azure('your-deployment-name'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` OpenAI language models can also be used in the `streamText` function and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output) (see [AI SDK Core](/docs/ai-sdk-core)). Azure OpenAI sends larger chunks than OpenAI. This can lead to the perception that the response is slower. See [Troubleshooting: Azure OpenAI Slow To Stream](/docs/troubleshooting/azure-stream-slow) ### Provider Options When using OpenAI language models on Azure, you can configure provider-specific options using `providerOptions.openai`. More information on available configuration options are on [the OpenAI provider page](/providers/ai-sdk-providers/openai#language-models). ```ts {12-14,24-26} import { azure, type OpenAILanguageModelResponsesOptions } from '@ai-sdk/azure'; const messages = [ { role: 'user', content: [ { type: 'text', text: 'What is the capital of the moon?', }, { type: 'file', mediaType: 'image', data: 'https://example.com/image.png', providerOptions: { openai: { imageDetail: 'low' }, }, }, ], }, ]; const { text } = await generateText({ model: azure('your-deployment-name'), providerOptions: { openai: { reasoningEffort: 'low', } satisfies OpenAILanguageModelResponsesOptions, }, }); ``` ### Chat Models The URL for calling Azure chat models will be constructed as follows: `https://RESOURCE_NAME.openai.azure.com/openai/v1/chat/completions?api-version=v1` You can create models that call the Azure OpenAI chat completions API using the `.chat()` factory method: ```ts const model = azure.chat('your-deployment-name'); ``` Azure OpenAI chat models support also some model specific settings that are not part of the [standard call settings](/docs/ai-sdk-core/settings). You can pass them as an options argument: ```ts import { azure, type OpenAILanguageModelChatOptions } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = await generateText({ model: azure.chat('your-deployment-name'), prompt: 'Write a short story about a robot.', providerOptions: { openai: { logitBias: { // optional likelihood for specific tokens '50256': -100, }, user: 'test-user', // optional unique user identifier } satisfies OpenAILanguageModelChatOptions, }, }); ``` The following optional provider options are available for OpenAI chat models: - **logitBias** *Record\* Modifies the likelihood of specified tokens appearing in the completion. Accepts a JSON object that maps tokens (specified by their token ID in the GPT tokenizer) to an associated bias value from -100 to 100. You can use this tokenizer tool to convert text to token IDs. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token. As an example, you can pass `{"50256": -100}` to prevent the token from being generated. - **logprobs** *boolean | number* Return the log probabilities of the tokens. Including logprobs will increase the response size and can slow down response times. However, it can be useful to better understand how the model is behaving. Setting to true will return the log probabilities of the tokens that were generated. Setting to a number will return the log probabilities of the top n tokens that were generated. - **parallelToolCalls** *boolean* Whether to enable parallel function calling during tool use. Default to true. - **user** *string* A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. Learn more. ### DeepSeek Chat Models You can create Azure-hosted DeepSeek chat models using the `.deepseek()` factory method: ```ts const model = azure.deepseek('your-deepseek-deployment-name'); ``` Use this factory for Azure DeepSeek models that support DeepSeek's chat reasoning fields, such as `deepseek-v4-pro` and `deepseek-v4-flash`. The factory maps top-level `reasoning` to DeepSeek `reasoning_effort` request settings, and parses streamed `reasoning_content` as AI SDK reasoning parts. ```ts import { azure, type AzureDeepSeekLanguageModelOptions } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = generateText({ model: azure.deepseek('your-deepseek-deployment-name'), prompt: 'How many "r"s are in the word "strawberry"?', reasoning: 'high', }); ``` ### Responses Models Azure OpenAI uses responses API as default with the `azure(deploymentName)` factory method. ```ts const model = azure('your-deployment-name'); ``` Further configuration can be done using OpenAI provider options. You can validate the provider options using the `OpenAILanguageModelResponsesOptions` type. In the Responses API, use `azure` as the provider name in `providerOptions` instead of `openai`. The `openai` key is still supported for `providerOptions` input. ```ts import { azure, OpenAILanguageModelResponsesOptions } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = await generateText({ model: azure('your-deployment-name'), providerOptions: { azure: { parallelToolCalls: false, store: false, user: 'user_123', // ... } satisfies OpenAILanguageModelResponsesOptions, }, // ... }); ``` The following provider options are available: - **parallelToolCalls** *boolean* Whether to use parallel tool calls. Defaults to `true`. - **store** *boolean* Whether to store the generation. Defaults to `true`. - **metadata** *Record\* Additional metadata to store with the generation. - **previousResponseId** *string* The ID of the previous response. You can use it to continue a conversation. Defaults to `undefined`. - **instructions** *string* Instructions for the model. They can be used to change the system or developer message when continuing a conversation using the `previousResponseId` option. Defaults to `undefined`. - **user** *string* A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. Defaults to `undefined`. - **reasoningEffort** *'low' | 'medium' | 'high'* Reasoning effort for reasoning models. Defaults to `medium`. If you use `providerOptions` to set the `reasoningEffort` option, this model setting will be ignored. - **strictJsonSchema** *boolean* Whether to use strict JSON schema validation. Defaults to `false`. The Azure OpenAI provider also returns provider-specific metadata: For Responses models (`azure(deploymentName)`), you can type this metadata using `AzureResponsesProviderMetadata`: ```ts import { azure, type AzureResponsesProviderMetadata } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = await generateText({ model: azure('your-deployment-name'), }); const providerMetadata = result.providerMetadata as | AzureResponsesProviderMetadata | undefined; const { responseId, logprobs, serviceTier } = providerMetadata?.azure ?? {}; // responseId can be used to continue a conversation (previousResponseId). console.log(responseId); ``` The following Azure-specific metadata may be returned: - **responseId** *string | null | undefined* The ID of the response. Can be used to continue a conversation. - **logprobs** *(optional)* Log probabilities of output tokens (when enabled). - **serviceTier** *(optional)* Service tier information returned by the API. The providerMetadata is only returned with the default responses API, and is not supported when using 'azure.chat' or 'azure.completion' #### Changing Reasoning Effort Mid-Conversation Azure Responses models support message-level reasoning effort updates through `providerOptions.azure.reasoningEffortUpdate`. The `openai` namespace is also accepted when the message has no `azure` options. Use the `OpenAIResponsesSystemMessageOptions` type exported from `@ai-sdk/azure` to validate these options: ```ts import { azure, type OpenAIResponsesSystemMessageOptions } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = await generateText({ // Use the name of your GPT-6 deployment. model: azure.responses('gpt-6-astra'), reasoning: 'low', allowSystemInMessages: true, messages: [ { role: 'system', content: '', providerOptions: { azure: { reasoningEffortUpdate: 'high', } satisfies OpenAIResponsesSystemMessageOptions, }, }, { role: 'user', content: 'Analyze the failure modes of this migration.' }, ], }); ``` Keep the request-level `reasoning` unchanged throughout the conversation and retain earlier effort updates when resending message history. These updates also work with `streamText`. See the [OpenAI reasoning effort guide](/providers/ai-sdk-providers/openai#changing-reasoning-effort-mid-conversation) for continuation, compaction, and configuration restrictions. The SDK infers support for effort updates from the optional `modelId` setting passed to `azure.responses()`, falling back to the deployment name. The ID must identify a supported GPT-6-or-later model family, such as `gpt-6-astra`. A custom name such as `production` is not recognized, even when the underlying deployment runs GPT-6. Pass the underlying model ID, for example `azure.responses('production', { modelId: 'gpt-6-astra' })`, to enable detection for a custom deployment name. #### Web Search Tool The Azure OpenAI responses API supports web search through the `azure.tools.webSearch` tool. `azure.tools.webSearchPreview()` is deprecated. Use `azure.tools.webSearch()` instead. The existing `searchContextSize` and `userLocation` options are supported by both tools. ```ts const result = await generateText({ model: azure('gpt-5'), prompt: 'What happened in San Francisco last week?', tools: { web_search: azure.tools.webSearch({ // optional configuration: searchContextSize: 'high', userLocation: { type: 'approximate', city: 'San Francisco', region: 'California', }, filters: { allowedDomains: ['sfchronicle.com', 'sfgate.com'], blockedDomains: ['example.com'], }, }), }, // Force web search tool (optional): toolChoice: { type: 'tool', toolName: 'web_search' }, }); // URL sources directly from `results` const sources = result.sources; // Or access sources from tool results for (const toolResult of result.toolResults) { if (toolResult.toolName === 'web_search') { console.log('Query:', toolResult.output.action.query); console.log('Sources:', toolResult.output.sources); // `sources` is an array of object: { type: 'url', url: string } } } ``` The web search tool supports the following configuration options: - **externalWebAccess** *boolean* - Whether to use external web access for fetching live content. Defaults to `true`. - **searchContextSize** *'low' | 'medium' | 'high'* - Controls the amount of context used for the search. Higher values provide more comprehensive results but may have higher latency and cost. - **userLocation** - Optional location information to provide geographically relevant results. Includes `type` (always `'approximate'`), `country`, `city`, `region`, and `timezone`. - **filters** - Optional filter configuration to restrict search results. - **allowedDomains** *string\[]* - Up to 100 allowed domains for the search. - **blockedDomains** *string\[]* - Up to 100 blocked domains for the search. Omit the HTTP or HTTPS prefix from domain filters. Subdomains of configured domains are automatically included or excluded. For detailed information on configuration options see the [Azure OpenAI Web Search Tool documentation](https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/web-search). On Microsoft Foundry environment , Live internet access is not supported. The parameter `externalWebAccess` if passed will be ignored. #### File Search Tool The Azure OpenAI provider supports file search through the `azure.tools.fileSearch` tool. You can force the use of the file search tool by setting the `toolChoice` parameter to `{ type: 'tool', toolName: 'file_search' }`. ```ts const result = await generateText({ model: azure('gpt-5'), prompt: 'What does the document say about user authentication?', tools: { file_search: azure.tools.fileSearch({ // optional configuration: vectorStoreIds: ['vs_123', 'vs_456'], maxNumResults: 10, ranking: { ranker: 'auto', }, }), }, // Force file search tool: toolChoice: { type: 'tool', toolName: 'file_search' }, }); ``` The tool must be named `file_search` when using Azure OpenAI's file search functionality. This name is required by Azure OpenAI's API specification and cannot be customized. The 'file\_search' tool is only supported with the default responses API, and is not supported when using 'azure.chat' or 'azure.completion' #### Image Generation Tool Azure OpenAI's Responses API supports multi-modal image generation as a provider-defined tool. Availability is restricted to specific models (for example, `gpt-5` variants). ```ts import { createAzure } from '@ai-sdk/azure'; import { generateText } from 'ai'; const azure = createAzure({ headers: { 'x-ms-oai-image-generation-deployment': 'gpt-image-2', // use your own image model deployment }, }); const result = await generateText({ model: azure('gpt-5'), prompt: 'Generate an image of an echidna swimming across the Mozambique channel.', tools: { image_generation: azure.tools.imageGeneration({ outputFormat: 'png' }), }, }); for (const toolResult of result.staticToolResults) { if (toolResult.toolName === 'image_generation') { const base64Image = toolResult.output.result; } } ``` The tool must be named `image_generation` when using Azure OpenAI's image generation functionality. This name is required by Azure OpenAI's API specification and cannot be customized. The 'image\_generation' tool is only supported with the default responses API, and is not supported when using 'azure.chat' or 'azure.completion' To use image\_generation, you must first create an image generation model. You must add a deployment specification to the header `x-ms-oai-image-generation-deployment`. Please note that the Responses API model and the image generation model must be in the same resource. When you set `store: false`, then previously generated images will not be accessible by the model. We recommend using the image generation tool without setting `store: false`. #### Code Interpreter Tool The Azure OpenAI provider supports the code interpreter tool through the `azure.tools.codeInterpreter` tool. This allows models to write and execute Python code. ```ts import { azure } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = await generateText({ model: azure('gpt-5'), prompt: 'Write and run Python code to calculate the factorial of 10', tools: { code_interpreter: azure.tools.codeInterpreter({ // optional configuration: container: { fileIds: ['assistant-123', 'assistant-456'], // optional file IDs to make available }, }), }, }); ``` The code interpreter tool can be configured with: - **container**: Either a container ID string or an object with `fileIds` to specify uploaded files that should be available to the code interpreter The tool must be named `code_interpreter` when using Azure OpenAI's code interpreter functionality. This name is required by Azure OpenAI's API specification and cannot be customized. The 'code\_interpreter' tool is only supported with the default responses API, and is not supported when using 'azure.chat' or 'azure.completion' When working with files generated by the Code Interpreter, reference information can be obtained from both [annotations in Text Parts](#typed-providermetadata-in-text-parts) and [`providerMetadata` in Source Document Parts](#typed-providermetadata-in-source-document-parts). #### PDF support The Azure OpenAI provider supports reading PDF files. You can pass PDF files as part of the message content using the `file` type: ```ts const result = await generateText({ model: azure('your-deployment-name'), messages: [ { role: 'user', content: [ { type: 'text', text: 'What is an embedding model?', }, { type: 'file', data: fs.readFileSync('./data/ai.pdf'), mediaType: 'application/pdf', filename: 'ai.pdf', // optional }, ], }, ], }); ``` The model will have access to the contents of the PDF file and respond to questions about it. The PDF file should be passed using the `data` field, and the `mediaType` should be set to `'application/pdf'`. Reading PDF files are only supported with the default responses API, and is not supported when using 'azure.chat' or 'azure.completion' #### Typed providerMetadata in Text Parts When using the Azure OpenAI Responses API, the SDK attaches Azure OpenAI-specific metadata to output parts via `providerMetadata`. This metadata can be used on the client side for tasks such as rendering citations or downloading files generated by the Code Interpreter. To enable type-safe handling of this metadata, the AI SDK exports dedicated TypeScript types. For text parts, when `part.type === 'text'`, the `providerMetadata` is provided in the form of `AzureResponsesTextProviderMetadata`. This metadata includes the following fields: - `itemId`\ The ID of the output item in the Responses API. - `annotations` (optional) An array of annotation objects generated by the model. If no annotations are present, this property itself may be omitted (`undefined`). Each element in `annotations` is a discriminated union with a required `type` field. Supported types include, for example: - `url_citation` - `file_citation` - `container_file_citation` - `file_path` These annotations directly correspond to the annotation objects defined by the Responses API and can be used for inline reference rendering or output analysis. For details, see the official OpenAI documentation: [Responses API – output text annotations](https://platform.openai.com/docs/api-reference/responses/object?lang=javascript#responses-object-output-output_message-content-output_text-annotations). ```ts import { azure, type AzureResponsesTextProviderMetadata } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = await generateText({ model: azure('gpt-4.1-mini'), prompt: 'Create a program that generates five random numbers between 1 and 100 with two decimal places, and show me the execution results. Also save the result to a file.', tools: { code_interpreter: azure.tools.codeInterpreter(), web_search: azure.tools.webSearch({}), file_search: azure.tools.fileSearch({ vectorStoreIds: ['vs_1234'] }), // requires a configured vector store }, }); for (const part of result.content) { if (part.type === 'text') { const providerMetadata = part.providerMetadata as | AzureResponsesTextProviderMetadata | undefined; if (!providerMetadata) continue; const { itemId: _itemId, annotations } = providerMetadata.azure; if (!annotations) continue; for (const annotation of annotations) { switch (annotation.type) { case 'url_citation': // url_citation is returned from web_search and provides: // properties: type, url, title, start_index and end_index break; case 'file_citation': // file_citation is returned from file_search and provides: // properties: type, file_id, filename and index break; case 'container_file_citation': // container_file_citation is returned from code_interpreter and provides: // properties: type, container_id, file_id, filename, start_index and end_index break; case 'file_path': // file_path provides: // properties: type, file_id and index break; default: { const _exhaustiveCheck: never = annotation; throw new Error( `Unhandled annotation: ${JSON.stringify(_exhaustiveCheck)}`, ); } } } } } ``` When implementing file downloads for files generated by the Code Interpreter, the `container_id` and `file_id` available in `providerMetadata` can be used to retrieve the file content. For details, see the [Retrieve container file content](https://platform.openai.com/docs/api-reference/container-files/retrieveContainerFileContent) API. #### Typed providerMetadata in Reasoning Parts When using the Azure OpenAI Responses API, reasoning output parts can include provider metadata. To handle this metadata in a type-safe way, use `AzureResponsesReasoningProviderMetadata`. For reasoning parts, when `part.type === 'reasoning'`, the `providerMetadata` is provided in the form of `AzureResponsesReasoningProviderMetadata`. This metadata includes the following fields: - `itemId`\ The ID of the reasoning item in the Responses API. - `reasoningEncryptedContent` (optional)\ Encrypted reasoning content (only returned when requested via `include: ['reasoning.encrypted_content']`). ```ts import { azure, type AzureResponsesReasoningProviderMetadata, type OpenAILanguageModelResponsesOptions, } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = await generateText({ model: azure('your-deployment-name'), prompt: 'How many "r"s are in the word "strawberry"?', providerOptions: { azure: { store: false, include: ['reasoning.encrypted_content'], } satisfies OpenAILanguageModelResponsesOptions, }, }); for (const part of result.content) { if (part.type === 'reasoning') { const providerMetadata = part.providerMetadata as | AzureResponsesReasoningProviderMetadata | undefined; const { itemId, reasoningEncryptedContent } = providerMetadata?.azure ?? {}; console.log(itemId, reasoningEncryptedContent); } } ``` #### Typed providerMetadata in Source Document Parts For source document parts, when `part.type === 'source'` and `sourceType === 'document'`, the `providerMetadata` is provided as `AzureResponsesSourceDocumentProviderMetadata`. This metadata is also a discriminated union with a required `type` field. Supported types include: - `file_citation` - `container_file_citation` - `file_path` Each type includes the identifiers required to work with the referenced resource, such as `fileId` and `containerId`. ```ts import { azure, type AzureResponsesSourceDocumentProviderMetadata, } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = await generateText({ model: azure('gpt-4.1-mini'), prompt: 'Create a program that generates five random numbers between 1 and 100 with two decimal places, and show me the execution results. Also save the result to a file.', tools: { code_interpreter: azure.tools.codeInterpreter(), web_search: azure.tools.webSearch({}), file_search: azure.tools.fileSearch({ vectorStoreIds: ['vs_1234'] }), // requires a configured vector store }, }); for (const part of result.content) { if (part.type === 'source') { if (part.sourceType === 'document') { const providerMetadata = part.providerMetadata as | AzureResponsesSourceDocumentProviderMetadata | undefined; if (!providerMetadata) continue; const annotation = providerMetadata.azure; switch (annotation.type) { case 'file_citation': // file_citation is returned from file_search and provides: // properties: type, fileId and index // The filename can be accessed via part.filename. break; case 'container_file_citation': // container_file_citation is returned from code_interpreter and provides: // properties: type, containerId and fileId // The filename can be accessed via part.filename. break; case 'file_path': // file_path provides: // properties: type, fileId and index break; default: { const _exhaustiveCheck: never = annotation; throw new Error( `Unhandled annotation: ${JSON.stringify(_exhaustiveCheck)}`, ); } } } } } ``` Annotations in text parts follow the OpenAI Responses API specification and therefore use snake\_case properties (e.g. `file_id`, `container_id`). In contrast, `providerMetadata` for source document parts is normalized by the SDK to camelCase (e.g. `fileId`, `containerId`). Fields that depend on the original text content, such as `start_index` and `end_index`, are omitted, as are fields like `filename` that are directly available on the source object. ### Completion Models You can create models that call the completions API using the `.completion()` factory method. The first argument is the model id. Currently only `gpt-35-turbo-instruct` is supported. ```ts const model = azure.completion('your-gpt-35-turbo-instruct-deployment'); ``` OpenAI completion models support also some model specific settings that are not part of the [standard call settings](/docs/ai-sdk-core/settings). You can pass them as an options argument: ```ts import { azure, type OpenAILanguageModelCompletionOptions, } from '@ai-sdk/azure'; import { generateText } from 'ai'; const result = await generateText({ model: azure.completion('your-gpt-35-turbo-instruct-deployment'), prompt: 'Write a haiku about coding.', providerOptions: { openai: { echo: true, // optional, echo the prompt in addition to the completion logitBias: { // optional likelihood for specific tokens '50256': -100, }, suffix: 'some text', // optional suffix that comes after a completion of inserted text user: 'test-user', // optional unique user identifier } satisfies OpenAILanguageModelCompletionOptions, }, }); ``` The following optional provider options are available for Azure OpenAI completion models: - **echo**: *boolean* Echo back the prompt in addition to the completion. - **logitBias** *Record\* Modifies the likelihood of specified tokens appearing in the completion. Accepts a JSON object that maps tokens (specified by their token ID in the GPT tokenizer) to an associated bias value from -100 to 100. You can use this tokenizer tool to convert text to token IDs. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token. As an example, you can pass `{"50256": -100}` to prevent the \<|endoftext|> token from being generated. - **logprobs** *boolean | number* Return the log probabilities of the tokens. Including logprobs will increase the response size and can slow down response times. However, it can be useful to better understand how the model is behaving. Setting to true will return the log probabilities of the tokens that were generated. Setting to a number will return the log probabilities of the top n tokens that were generated. - **suffix** *string* The suffix that comes after a completion of inserted text. - **user** *string* A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. Learn more. ## Embedding Models You can create models that call the Azure OpenAI embeddings API using the `.embedding()` factory method. ```ts const model = azure.embedding('your-embedding-deployment'); ``` Azure OpenAI embedding models support several additional settings. You can pass them as an options argument: ```ts import { azure, type OpenAIEmbeddingModelOptions } from '@ai-sdk/azure'; import { embed } from 'ai'; const { embedding } = await embed({ model: azure.embedding('your-embedding-deployment'), value: 'sunny day at the beach', providerOptions: { openai: { dimensions: 512, // optional, number of dimensions for the embedding user: 'test-user', // optional unique user identifier } satisfies OpenAIEmbeddingModelOptions, }, }); ``` The following optional provider options are available for Azure OpenAI embedding models: - **dimensions**: *number* The number of dimensions the resulting output embeddings should have. Only supported in text-embedding-3 and later models. - **user** *string* A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. Learn more. ## Image Models You can create models that call the Azure OpenAI image generation API using the `.image()` factory method. The first argument is your deployment name. MAI-Image models use the [MAI image API](#mai-image) instead. ```ts const model = azure.image('your-dalle-deployment-name'); ``` Azure OpenAI image models support several additional settings. You can pass them as `providerOptions.openai` when generating the image: ```ts import { azure } from '@ai-sdk/azure'; import type { OpenAIImageModelGenerationOptions } from '@ai-sdk/openai'; import { generateImage } from 'ai'; await generateImage({ model: azure.image('your-dalle-deployment-name'), prompt: 'A photorealistic image of a cat astronaut floating in space', size: '1024x1024', // '1024x1024', '1792x1024', or '1024x1792' for DALL-E 3 providerOptions: { openai: { user: 'test-user', // optional unique user identifier } satisfies OpenAIImageModelGenerationOptions, }, }); ``` ### Example You can use Azure OpenAI image models to generate images with the `generateImage` function: ```ts import { azure } from '@ai-sdk/azure'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: azure.image('your-dalle-deployment-name'), prompt: 'A photorealistic image of a cat astronaut floating in space', size: '1024x1024', // '1024x1024', '1792x1024', or '1024x1792' for DALL-E 3 }); // image contains the URL or base64 data of the generated image console.log(image); ``` ### Model Capabilities Azure OpenAI supports DALL-E 2 and DALL-E 3 models through deployments. The capabilities depend on which model version your deployment is using: | Model Version | Sizes | | ------------- | ------------------------------- | | DALL-E 3 | 1024x1024, 1792x1024, 1024x1792 | | DALL-E 2 | 256x256, 512x512, 1024x1024 | Azure deployment names are user-defined and do not identify the backing model. Consequently, `supportsFileInputs` and `supportsMaskInputs` are `undefined` (unknown) for Azure image model instances. If your application knows the capabilities of a deployment, you can advertise them with the `overrideSupportsFileInputs` and `overrideSupportsMaskInputs` options of [`wrapImageModel`](/docs/reference/ai-sdk-core/wrap-image-model). DALL-E models do not support the `aspectRatio` parameter. Use the `size` parameter instead. When creating your Azure OpenAI deployment, make sure to set the DALL-E model version you want to use. ### MAI-Image MAI-Image models (`MAI-Image-2.6`, `MAI-Image-2.6-Flash`, `MAI-Image-2.5`, `MAI-Image-2.5-Flash`, `MAI-Image-2.5-Pro`) use the MAI image API at `https://{resourceName}.services.ai.azure.com/mai/v1` (override with `maiBaseURL`). Deployments named after the model are detected automatically, case-insensitively. For other deployment names, set `providerOptions.azure.api` to `'mai'`. ```ts import { azure, type AzureImageModelOptions } from '@ai-sdk/azure'; import { generateImage } from 'ai'; const { image, usage } = await generateImage({ model: azure.image('MAI-Image-2.6'), prompt: 'A watercolor of three flowers on a blue background', size: '1536x1024', providerOptions: { azure: { webGrounding: true, } satisfies AzureImageModelOptions, }, }); ``` Pass reference images to edit or combine them (up to five JPEG or PNG images). Masks are not supported. ```ts const { image } = await generateImage({ model: azure.image('MAI-Image-2.6-Flash'), prompt: { text: 'Put the product from the first image into the scene from the second', images: [productImage, sceneImage], }, }); ``` Each request returns one PNG image; `generateImage` sends one request per image. `size` is sent as `width` and `height`. Each side must be at least 768 pixels, and the total pixel count at most 2,359,296 for MAI-Image-2.6 models and 1,048,576 for MAI-Image-2.5 models. Without `size`, `aspectRatio` is mapped to dimensions of about one megapixel; without either, the model returns 1024x1024. `seed` is not supported. Token usage is returned in `usage`, with the input text and image token split in `providerMetadata.azure.images`. The following provider options are available: - **api** *'mai' | 'openai'* API to use. Defaults to `'mai'` for MAI-Image models, `'openai'` otherwise. - **autoAspectRatio** *boolean* Let the model choose the output aspect ratio from the prompt and reference images. MAI-Image-2.6 models only. - **webGrounding** *boolean* Ground generation in Bing web search results. MAI-Image-2.6 models only. ## Transcription Models You can create models that call the Azure OpenAI or Azure Speech transcription API using the `.transcription()` factory method. The first argument is the model id e.g. `whisper-1`. ```ts const model = azure.transcription('whisper-1'); ``` ### MAI-Transcribe MAI-Transcribe models are multilingual speech-to-text models from Microsoft AI, served through Azure. The following model IDs use the Azure Speech API. They are matched case-insensitively and sent to Azure under their Azure model names: | Model ID | Azure model | Notes | | -------------------- | -------------------- | ------------------------------------ | | `mai-transcribe-2` | `MAI-Transcribe-2` | Supports every option below. | | `mai-transcribe-1.5` | `MAI-Transcribe-1.5` | Supports `locales` and `phraseList`. | `mai-transcribe-2-streaming` uses the MAI realtime API; see [MAI-Transcribe-2-Streaming](#mai-transcribe-2-streaming). All other IDs use the OpenAI API by default, including custom deployment names. ```ts import { azure, type AzureTranscriptionModelOptions } from '@ai-sdk/azure'; import { transcribe } from 'ai'; import { readFile } from 'fs/promises'; const result = await transcribe({ model: azure.transcription('mai-transcribe-2'), audio: await readFile('audio.wav'), providerOptions: { azure: { timestamps: 'word', diarization: { enabled: true }, transcribeStyle: 'clean', } satisfies AzureTranscriptionModelOptions, }, }); ``` MAI-Transcribe models require a Microsoft Foundry (Speech) resource in a region where they are available: `centralindia`, `eastus`, `northeurope`, `southeastasia`, `westus`, or `westus2` (see [Speech service regions](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/regions?tabs=llmspeech)). Resources in other regions reject the request. Set `AZURE_RESOURCE_NAME` and `AZURE_API_KEY` to that resource's name and key, or pass `resourceName` and `apiKey` to `createAzure`. `tokenProvider`, custom headers, and custom fetch are also supported. Keys are sent as `Ocp-Apim-Subscription-Key`. Speech requests use `https://{resourceName}.cognitiveservices.azure.com/speechtotext/transcriptions:transcribe` with API version `2025-10-15`. Use `speechBaseURL` to supply a different Speech URL prefix (without `/speechtotext/transcriptions:transcribe`), for example a regional endpoint: ```ts import { createAzure } from '@ai-sdk/azure'; const azure = createAzure({ speechBaseURL: 'https://eastus.api.cognitive.microsoft.com', apiKey: process.env.AZURE_SPEECH_API_KEY, }); ``` Speech requests do not use `baseURL`, `apiVersion`, or `useDeploymentBasedUrls`; those settings only apply to Azure OpenAI models. The following options are available through `providerOptions.azure`: - **api** *'openai' | 'speech'* Overrides model-based API selection for this request. Set `openai` for an OpenAI deployment named after one of the model IDs above, or `speech` to send another model ID to Speech. Other Speech model IDs are sent unchanged. - **timestamps** *'word' | 'segment' | 'none'* Timing granularity. For MAI-Transcribe-2 the AI SDK defaults to `segment` so that results include timed segments (Azure's own default is `none`). `segment` returns one segment per sentence or speaker turn, `word` additionally returns word timing in provider metadata, and `none` returns a single segment that spans the whole audio. MAI-Transcribe-1.5 only supports `none`, so the AI SDK does not send a default for it. - **transcribeStyle** *'verbatim' | 'clean'* `verbatim` keeps fillers and false starts, `clean` removes them. Defaults to `verbatim`. `clean` requires MAI-Transcribe-2. - **locales** *string\[]* Exactly one language code, such as `['en']`. This is a strong hint; omit it for automatic language detection and code switching. - **diarization** *object* Set `enabled` to `true` to identify speakers. Speaker IDs are returned in provider metadata. Requires MAI-Transcribe-2. `maxSpeakers` is not supported. - **phraseList** *object* Set `phrases` to a list of names and domain terms to bias recognition toward them. Explicit `api` takes precedence over the model ID. Existing OpenAI transcription options remain under `providerOptions.openai`. Speech-only Azure options produce warnings when used with the OpenAI API. Unknown `providerOptions.azure` keys (for example `diarization.maxSpeakers`) are rejected before the request is sent. Options that a model does not support, such as `timestamps: 'word'` with MAI-Transcribe-1.5, are sent unchanged, and Azure rejects the request with an error that names the option. Results include transcript text, timed segments, duration, and an ISO-639-1 language code when the reported phrase locales identify one language. Mixed-language results, and languages without an ISO-639-1 code such as Filipino (`fil`) or Cantonese (`yue`), leave `language` undefined. Speaker IDs, exact locales, and word timing are preserved in `providerMetadata.azure.phrases`; use the exported `AzureTranscriptionProviderMetadata` type to access them. Phrase timing is in milliseconds in metadata and seconds in normalized `segments`. MAI-Transcribe models are file transcription models: they accept WAV, MP3, or FLAC files shorter than 2 hours and smaller than 250 MB, and do not support streaming transcription; use [MAI-Transcribe-2-Streaming](#mai-transcribe-2-streaming) to stream. The models are in public preview. See [Azure's MAI-Transcribe guide](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/mai-transcribe) for supported languages and diarization limits. AI Gateway forwards these provider options, including `azure.api`. ### MAI-Transcribe-2-Streaming MAI-Transcribe-2-Streaming is a low-latency streaming speech-to-text model from Microsoft AI. `azure.transcription('mai-transcribe-2-streaming')` streams audio to the MAI realtime API (`wss://{resourceName}.services.ai.azure.com/mai/v1/realtime`) with [`experimental_streamTranscribe`](/docs/ai-sdk-core/transcription#streaming-transcription). The model ID is sent to Azure as the Foundry deployment name, so deploy the model as `mai-transcribe-2-streaming`, or set `api: 'mai'` for other deployment names. ```ts import { createAzure, type AzureOpenAIProviderSettings, type AzureTranscriptionModelOptions, } from '@ai-sdk/azure'; import { experimental_streamTranscribe as streamTranscribe } from 'ai'; import { WebSocket } from 'ws'; const azure = createAzure({ // `ws`'s WebSocket type differs from the DOM-style WebSocketConstructor. webSocket: WebSocket as unknown as AzureOpenAIProviderSettings['webSocket'], }); const result = streamTranscribe({ model: azure.transcription('mai-transcribe-2-streaming'), audio, // ReadableStream of 16-bit mono PCM chunks inputAudioFormat: { type: 'audio/pcm', rate: 24000 }, providerOptions: { azure: { language: 'en' } satisfies AzureTranscriptionModelOptions, }, }); for await (const part of result.fullStream) { if (part.type === 'transcript-delta') { process.stdout.write(part.delta); } } ``` The model requires a Microsoft Foundry resource with a MAI-Transcribe-2-Streaming deployment in a supported region, such as `centralus`. Input audio must be 16-bit mono PCM (`audio/pcm`) at 16000 or 24000 Hz; the default is 24000 Hz. The stream emits `transcript-delta` parts for finalized text and `transcript-partial` parts for the provisional text that follows the latest delta. MAI does not detect pauses on the server, so the stream commits once when the input audio ends and then emits a `transcript-final` part and the finished transcript. `durationInSeconds` is computed from the streamed audio. The following options are available through `providerOptions.azure`: - **api** *'openai' | 'speech' | 'mai'* Set `mai` to stream with a deployment that has a different name. - **language** *string* Language hint such as `en`. Omit for automatic language detection. File transcription options such as `timestamps` produce warnings when streaming. MAI-Transcribe-2-Streaming does not support file transcription. ### OpenAI transcription If you encounter a "DeploymentNotFound" error with transcription models, try enabling deployment-based URLs: ```ts const azure = createAzure({ useDeploymentBasedUrls: true, apiVersion: '2025-04-01-preview', }); ``` This uses the legacy endpoint format which may be required for certain Azure OpenAI deployments. When using useDeploymentBasedUrls, the default api-version is not valid. You must set it to `2025-04-01-preview` or an earlier value. You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the input language in ISO-639-1 (e.g. `en`) format will improve accuracy and latency. ```ts {6} import { transcribe } from 'ai'; import { azure, type OpenAITranscriptionModelOptions } from '@ai-sdk/azure'; import { readFile } from 'fs/promises'; const result = await transcribe({ model: azure.transcription('whisper-1'), audio: await readFile('audio.mp3'), providerOptions: { openai: { language: 'en', } satisfies OpenAITranscriptionModelOptions, }, }); ``` The following provider options are available: - **timestampGranularities** *string\[]* The granularity of the timestamps in the transcription. Defaults to `['segment']`. Possible values are `['word']`, `['segment']`, and `['word', 'segment']`. Note: There is no additional latency for segment timestamps, but generating word timestamps incurs additional latency. - **language** *string* The language of the input audio. Supplying the input language in ISO-639-1 format (e.g. 'en') will improve accuracy and latency. Optional. - **prompt** *string* An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language. Optional. - **temperature** *number* The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit. Defaults to 0. Optional. - **include** *string\[]* Additional information to include in the transcription response. ### Model Capabilities | Model | Transcription | Duration | Segments | Language | | ---------------------------- | ------------- | -------- | -------- | -------- | | `whisper-1` | ✓ | ✓ | ✓ | ✓ | | `gpt-4o-mini-transcribe` | ✓ | ✗ | ✗ | ✗ | | `gpt-4o-transcribe` | ✓ | ✗ | ✗ | ✗ | | `mai-transcribe-2` | ✓ | ✓ | ✓ | ✓ | | `mai-transcribe-1.5` | ✓ | ✓ | ✓ | ✓ | | `mai-transcribe-2-streaming` | ✓ (streaming) | ✓ | ✗ | ✗ | ## Speech Models You can create models that call the Azure OpenAI speech API or Azure Speech text to speech using the `.speech()` factory method. The first argument is your deployment name for the text-to-speech model (e.g., `tts-1`), or a [MAI-Voice](#mai-voice) model ID. ```ts const model = azure.speech('your-tts-deployment-name'); ``` ### Example ```ts import { azure } from '@ai-sdk/azure'; import { generateSpeech } from 'ai'; const result = await generateSpeech({ model: azure.speech('your-tts-deployment-name'), text: 'Hello, world!', voice: 'alloy', // OpenAI voice ID }); ``` You can also pass additional provider-specific options using the `providerOptions` argument: ```ts import { azure, type OpenAISpeechModelOptions } from '@ai-sdk/azure'; import { generateSpeech } from 'ai'; const result = await generateSpeech({ model: azure.speech('your-tts-deployment-name'), text: 'Hello, world!', voice: 'alloy', providerOptions: { openai: { speed: 1.2, } satisfies OpenAISpeechModelOptions, }, }); ``` The following provider options are available: - **instructions** *string* Control the voice of your generated audio with additional instructions e.g. "Speak in a slow and steady tone". Does not work with `tts-1` or `tts-1-hd`. Optional. - **speed** *number* The speed of the generated audio. Select a value from 0.25 to 4.0. Defaults to 1.0. Optional. ### Model Capabilities Azure OpenAI supports TTS models through deployments. The capabilities depend on which model version your deployment is using: | Model Version | Instructions | | ----------------- | ------------ | | `tts-1` | ✗ | | `tts-1-hd` | ✗ | | `gpt-4o-mini-tts` | ✓ | ### MAI-Voice MAI-Voice models are multilingual text-to-speech models from Microsoft AI, served through Azure. The following model IDs use the Azure Speech API: | Model ID | Notes | | --------------------- | --------------------------------------------- | | `mai-voice-2.1-flash` | Low latency, for voice agents and assistants. | | `mai-voice-2.1` | Highest fidelity, for long-form narration. | | `mai-voice-2-flash` | Low latency, for voice agents and assistants. | | `mai-voice-2` | Highest fidelity, for long-form narration. | All other IDs use the OpenAI API by default, including custom deployment names. ```ts import { azure, type AzureSpeechModelOptions } from '@ai-sdk/azure'; import { generateSpeech } from 'ai'; const result = await generateSpeech({ model: azure.speech('mai-voice-2-flash'), text: 'Thanks for calling! Your order shipped this morning.', voice: 'en-US-Ethan', providerOptions: { azure: { style: 'excited' } satisfies AzureSpeechModelOptions, }, }); ``` MAI-Voice models require a Microsoft Foundry (Speech) resource in a region where they are available (see [Speech service regions](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/regions)). They use the same settings as [MAI-Transcribe](#mai-transcribe): `resourceName` (or `AZURE_RESOURCE_NAME`), `apiKey` (or `AZURE_API_KEY`), `tokenProvider`, and `speechBaseURL`. - **voice** is a prebuilt voice ID that includes its locale, such as `en-US-Harper` (the default), `de-DE-Mia`, or `es-MX-Valeria`. The voice locale selects the language. Full Azure voice names such as `es-MX-Valeria:MAI-Voice-2` are also accepted. Available voices vary by model; see [Azure's MAI-Voice guide](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/mai-voices). - **language** picks a default voice when `voice` is not set (see the table below). Other languages and `auto` use `en-US-Harper` with a warning. When `voice` is set, its locale selects the language, and a conflicting `language` produces a warning. - **outputFormat** accepts `mp3` (default, 24 kHz), `wav`, `pcm` (headerless 24 kHz, 16-bit, mono), `opus` (Ogg), or any Azure `X-Microsoft-OutputFormat` value such as `audio-48khz-192kbitrate-mono-mp3`. Other values fall back to `mp3` with a warning. Azure labels raw formats such as `pcm` as `audio/basic`. - **speed** is a rate multiplier, for example `0.5` for half speed or `2` for double speed. - `instructions` is not supported and produces a warning. | Language | Default voice | Language | Default voice | | -------- | --------------- | -------- | ------------- | | `de` | `de-DE-Mia` | `nl` | `nl-NL-Fleur` | | `en` | `en-US-Harper` | `pt` | `pt-BR-Luana` | | `es` | `es-MX-Valeria` | `ro` | `ro-RO-Elena` | | `fr` | `fr-FR-Soleil` | `ru` | `ru-RU-Masha` | | `hi` | `hi-IN-Kavya` | `th` | `th-TH-Krit` | | `hu` | `hu-HU-Lilla` | `tr` | `tr-TR-Elif` | | `it` | `it-IT-Rosa` | `zh` | `zh-CN-Mei` | | `ko` | `ko-KR-Haena` | | | The following options are available through `providerOptions.azure`: - **api** *'openai' | 'speech'* Overrides model-based API selection for this request. Set `openai` for an OpenAI deployment named after one of the model IDs above, or `speech` to send another model ID to Speech. - **style** *string* Speaking style, such as `excited` or `whispering`. Supported styles vary by voice and model. - **styleDegree** *number* Style intensity from 0.01 to 2. Azure defaults to 1. Requires `style`. Text is always spoken as plain text; SSML input is not supported. The models are in public preview. AI Gateway forwards these provider options, including `azure.api`. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Baseten description: Learn how to use Baseten with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/baseten" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [Baseten](https://baseten.co/) is an inference platform for serving frontier, enterprise-grade opensource AI models via their [API](https://docs.baseten.co/overview). Start with [AI Gateway](/providers/ai-sdk-providers/ai-gateway) for models in its catalog (recommended), or use `@ai-sdk/baseten` to connect directly to Baseten. ```ts provider="baseten" model="openai/gpt-oss-120b" import { generateText } from 'ai'; const { text } = await generateText({ model: "openai/gpt-oss-120b", prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { gateway: { order: ['baseten'], }, }, }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. The example sets `providerOptions.gateway.order` to `['baseten']`. This sends the request to Baseten first, then falls back to other available providers of the same model if Baseten is unavailable or the request fails. `order` sets a preference; it does not restrict fallback to Baseten. See [provider ordering](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). The dropdown lists models served by Baseten through Gateway with a corresponding direct-provider example. Provider availability depends on the model and your team's [Provider Allowlist](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). ## Using `@ai-sdk/baseten` For direct access to Baseten, install `ai` and `@ai-sdk/baseten`: ```bash pnpm add ai @ai-sdk/baseten ``` Set your Baseten API credential in the `BASETEN_API_KEY` environment variable: ```bash export BASETEN_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/baseten`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/baseten` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `baseten` from `@ai-sdk/baseten`: ```ts import { baseten } from '@ai-sdk/baseten'; ``` If you need a customized setup, you can import `createBaseten` from `@ai-sdk/baseten` and create a provider instance with your settings: ```ts import { createBaseten } from '@ai-sdk/baseten'; const baseten = createBaseten({ apiKey: process.env.BASETEN_API_KEY ?? '', }); ``` You can use the following optional settings to customize the Baseten provider instance: - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use proxy servers. The default prefix is `https://inference.baseten.co/v1`. - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `BASETEN_API_KEY` environment variable. It is recommended you set the environment variable using `export` so you do not need to include the field every time. You can grab your Baseten API Key [here](https://app.baseten.co/settings/api_keys) - **modelURL** *string* Custom model URL for specific models (chat or embeddings). If not provided, the default Model APIs will be used. - **headers** *Record\* Custom headers to include in the requests. - **performanceClient** *PerformanceClient constructor* Opt in to Baseten's native performance client for embeddings, for client-side batching and request hedging. Pass the `PerformanceClient` constructor from `@basetenlabs/performance-client`, which you install yourself. When omitted, embeddings use plain HTTP. See [Native performance client](#native-performance-client-optional). - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. ## Model APIs You can select [Baseten models](https://www.baseten.co/products/model-apis/) using a provider instance. The first argument is the model id, e.g. `'moonshotai/Kimi-K2-Instruct-0905'`: The complete supported models under Model APIs can be found [here](https://docs.baseten.co/development/model-apis/overview#supported-models). ```ts const model = baseten('moonshotai/Kimi-K2-Instruct-0905'); ``` ### Example You can use Baseten language models to generate text with the `generateText` function: ```ts import { baseten } from '@ai-sdk/baseten'; import { generateText } from 'ai'; const { text } = await generateText({ model: baseten('moonshotai/Kimi-K2-Instruct-0905'), prompt: 'What is the meaning of life? Answer in one sentence.', }); ``` Baseten language models can also be used in the `streamText` function (see [AI SDK Core](/docs/ai-sdk-core)). ## Dedicated Models Baseten supports dedicated model URLs for both chat and embedding models. You have to specify a `modelURL` when creating the provider: ### OpenAI-Compatible Endpoints (`/sync/v1`) For models deployed with Baseten's OpenAI-compatible endpoints: ```ts import { createBaseten } from '@ai-sdk/baseten'; const baseten = createBaseten({ modelURL: 'https://model-{MODEL_ID}.api.baseten.co/sync/v1', }); // No modelId is needed because we specified modelURL const model = baseten(); const { text } = await generateText({ model: model, prompt: 'Say hello from a Baseten chat model!', }); ``` ### `/predict` Endpoints `/predict` endpoints are currently NOT supported for chat models. You must use `/sync/v1` endpoints for chat functionality. ## Embedding Models You can create models that call the Baseten embeddings API using the `.embeddingModel()` factory method. Baseten Embeddings Inference deployments are OpenAI-compatible, so embeddings use plain HTTP by default with no extra dependencies. **Important:** Embedding models require a dedicated deployment with a custom `modelURL`. Unlike chat models, embeddings cannot use Baseten's default Model APIs and must specify a dedicated model endpoint. ```ts import { createBaseten } from '@ai-sdk/baseten'; import { embed, embedMany } from 'ai'; const baseten = createBaseten({ modelURL: 'https://model-{MODEL_ID}.api.baseten.co/sync', }); const embeddingModel = baseten.embeddingModel(); // Single embedding const { embedding } = await embed({ model: embeddingModel, value: 'sunny day at the beach', }); // Batch embeddings const { embeddings } = await embedMany({ model: embeddingModel, values: [ 'sunny day at the beach', 'rainy afternoon in the city', 'snowy mountain peak', ], }); ``` Each request sends at most 128 values. `embedMany` splits larger inputs into chunks of that size and runs them in parallel, so you can pass as many values as you like. ### Endpoint Support for Embeddings **Supported:** - `/sync` endpoints (`/v1/embeddings` is appended for you) - `/sync/v1` endpoints **Not Supported:** - `/predict` endpoints ### Native performance client (optional) Baseten also publishes `@basetenlabs/performance-client`, a native client that adds client-side batching and request hedging on top of the server-side dynamic batching your deployment already does. It is **not** installed by default: it is a native addon, so it cannot load in edge runtimes and bundlers cannot resolve its platform binaries. To use it, install it yourself and pass the constructor: ```bash npm i @basetenlabs/performance-client ``` ```ts import { createBaseten } from '@ai-sdk/baseten'; import { PerformanceClient } from '@basetenlabs/performance-client'; const baseten = createBaseten({ modelURL: 'https://model-{MODEL_ID}.api.baseten.co/environments/production/sync', performanceClient: PerformanceClient, }); ``` When you opt in, the client handles batching itself, so values are sent in a single call rather than being split at 128. ## Error Handling The Baseten provider includes built-in error handling for common API errors: ```ts import { baseten } from '@ai-sdk/baseten'; import { generateText } from 'ai'; try { const { text } = await generateText({ model: baseten('moonshotai/Kimi-K2-Instruct-0905'), prompt: 'Hello, world!', }); } catch (error) { console.error('Baseten API error:', error.message); } ``` ### Common Error Scenarios ```ts // Embeddings require a modelURL try { baseten.embeddingModel(); } catch (error) { // Error: "No model URL provided for embeddings. Please set modelURL option for embeddings." } // /predict endpoints are not supported for chat models try { const baseten = createBaseten({ modelURL: 'https://model-{MODEL_ID}.api.baseten.co/environments/production/predict', }); baseten(); // This will throw an error } catch (error) { // Error: "Not supported. You must use a /sync/v1 endpoint for chat models." } // /sync/v1 endpoints are now supported for embeddings const baseten = createBaseten({ modelURL: 'https://model-{MODEL_ID}.api.baseten.co/environments/production/sync/v1', }); const embeddingModel = baseten.embeddingModel(); // This works fine! // /predict endpoints are not supported for embeddings try { const baseten = createBaseten({ modelURL: 'https://model-{MODEL_ID}.api.baseten.co/environments/production/predict', }); baseten.embeddingModel(); // This will throw an error } catch (error) { // Error: "Not supported. You must use a /sync or /sync/v1 endpoint for embeddings." } // Image models are not supported try { baseten.imageModel('test-model'); } catch (error) { // Error: NoSuchModelError for imageModel } ``` For more information about Baseten models and deployment options, see the [Baseten documentation](https://docs.baseten.co/). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Black Forest Labs description: Learn how to use Black Forest Labs with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/black-forest-labs" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [Black Forest Labs](https://bfl.ai/) provides a generative media platform for developers with FLUX-based models. Their platform offers fast, high quality, and in-context image generation and editing, plus video generation with FLUX 3. You can use Black Forest Labs models through [AI Gateway](/providers/ai-sdk-providers/ai-gateway) (recommended) or connect directly with `@ai-sdk/black-forest-labs`. ```ts provider="bfl" model="bfl/flux-2-pro" import { generateImage } from 'ai'; const { image } = await generateImage({ model: "bfl/flux-2-pro", prompt: 'A sunlit mountain village in watercolor.', }); console.log(image.base64); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. Gateway feature and provider-option support depends on the model and the provider serving the request. See [Gateway provider options](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options). ## Using `@ai-sdk/black-forest-labs` For direct access to Black Forest Labs, install `ai` and `@ai-sdk/black-forest-labs`: ```bash pnpm add ai @ai-sdk/black-forest-labs ``` Set your Black Forest Labs API credential in the `BFL_API_KEY` environment variable: ```bash export BFL_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/black-forest-labs`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/black-forest-labs` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `blackForestLabs` from `@ai-sdk/black-forest-labs`: ```ts import { blackForestLabs } from '@ai-sdk/black-forest-labs'; ``` If you need a customized setup, you can import `createBlackForestLabs` and create a provider instance with your settings: ```ts import { createBlackForestLabs } from '@ai-sdk/black-forest-labs'; const blackForestLabs = createBlackForestLabs({ apiKey: 'your-api-key', // optional, defaults to BFL_API_KEY environment variable baseURL: 'custom-url', // optional headers: { /* custom headers */ }, // optional }); ``` You can use the following optional settings to customize the Black Forest Labs provider instance: - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use a regional endpoint. The default prefix is `https://api.bfl.ai/v1`. - **apiKey** *string* API key that is being sent using the `x-key` header. It defaults to the `BFL_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. - **pollIntervalMillis** *number* Interval in milliseconds between polling attempts when waiting for generation to complete. Defaults to 500ms for image models and 2000ms for video models. - **pollTimeoutMillis** *number* Overall timeout in milliseconds for polling before giving up. Defaults to 60000ms (60 seconds) for image models and 600000ms (10 minutes) for video models. ## Image Models You can create Black Forest Labs image models using the `.image()` factory method. For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image). ### Basic Usage ```ts import { writeFileSync } from 'node:fs'; import { blackForestLabs } from '@ai-sdk/black-forest-labs'; import { generateImage } from 'ai'; const { image, providerMetadata } = await generateImage({ model: blackForestLabs.image('flux-pro-1.1'), prompt: 'A serene mountain landscape at sunset', }); const filename = `image-${Date.now()}.png`; writeFileSync(filename, image.uint8Array); console.log(`Image saved to ${filename}`); ``` ### Model Capabilities Black Forest Labs offers many models optimized for different use cases. Here are a few popular examples. For a full list of models, see the [Black Forest Labs Models Page](https://bfl.ai/models). | Model | Description | | -------------------- | --------------------------------------------------------------------------------------------------------------------------- | | `flux-3-image` | FLUX 3 for text-to-image and editing with up to ten reference images and optional web grounding | | `flux-3-image-klein` | FLUX 3 Image Klein for fast generation and editing, with open weights and exact output dimensions | | `flux-2-pro` | FLUX.2 \[pro] for text-to-image and reference-based editing in one endpoint | | `flux-2-max` | FLUX.2 \[max] for the highest prompt adherence and detail in the FLUX.2 family | | `flux-2-flex` | FLUX.2 \[flex] exposes `steps` and `guidance` so you can trade speed against quality | | `flux-2-klein-9b` | FLUX.2 \[klein] 9B, an open-weight distilled model | | `flux-2-klein-4b` | FLUX.2 \[klein] 4B, the smallest and cheapest open-weight FLUX.2 model | | `flux-kontext-pro` | FLUX.1 Kontext \[pro] handles both text and reference images as inputs, enabling targeted edits and complex transformations | | `flux-kontext-max` | FLUX.1 Kontext \[max] with improved prompt adherence and typography generation | | `flux-pro-1.1-ultra` | Ultra-fast, ultra high-resolution image creation | | `flux-pro-1.1` | Fast, high-quality image generation from text. | | `flux-pro-1.0-fill` | Inpainting model for filling masked regions of images with new content | Unknown image model IDs use the latest supported image API, currently FLUX 3 Image Klein's request format and options. Known older families (`flux-pro-*`, `flux-kontext-*`, `flux-1-*`, and `flux-2-*`) retain the legacy format. The original `flux-3-image` retains its distinct grounding and version controls. FLUX.1 and FLUX.2 models support aspect ratios from 3:7 (portrait) to 7:3 (landscape). FLUX 3 accepts only `21:9`, `2:1`, `16:9`, `3:2`, `7:5`, `4:3`, `5:4`, `1:1`, `4:5`, `3:4`, `5:7`, `2:3`, `9:16`, `1:2`, and `9:21`. Omit `aspectRatio` to use the endpoint's `auto` default. Unsupported ratios are omitted with a warning. FLUX.2 models take reference images on the same `prompt.images` field as the Kontext models, so a single model ID covers both text-to-image and editing. See [Image Editing](#image-editing) below. FLUX.2 generations can take longer than the 60 second default polling timeout, especially with reference images. Raise `pollTimeoutMillis` when a request reports `Black Forest Labs generation timed out.` ### FLUX 3 Image Generation and Editing The `flux-3-image` and `flux-3-image-klein` models generate images from text and edit reference images through their respective endpoints. Pass reference images through `prompt.images` and select the output resolution with `providerOptions.blackForestLabs.resolution`: ```ts import { blackForestLabs, type BlackForestLabsImageModelOptions, } from '@ai-sdk/black-forest-labs'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: blackForestLabs.image('flux-3-image'), prompt: 'A watercolor illustration of a coastal village at dawn', aspectRatio: '16:9', providerOptions: { blackForestLabs: { resolution: '2k', grounding: false, version: 'latest', pollTimeoutMillis: 240000, } satisfies BlackForestLabsImageModelOptions, }, }); ``` For editing, use a prompt such as: ```ts const { image } = await generateImage({ model: blackForestLabs.image('flux-3-image'), prompt: { text: 'Change the sky to sunset while preserving the buildings', images: ['https://example.com/village.png'], }, }); ``` Both FLUX 3 Image models accept up to ten reference images as URLs or base64-encoded data. For `flux-3-image`, each reference image must be at least 256 × 256 pixels and no larger than 16 megapixels. Omitting `aspectRatio` lets the endpoint choose the ratio from the prompt or the first reference image. Text-to-image generation defaults to `1k`. Klein edits default to the first reference image's size. Web grounding is available only on `flux-3-image` and is enabled by default. The `flux-3-image` model does not accept `seed`, masks, explicit `width`/`height`, or the legacy image options such as `outputFormat`, `steps`, and `promptUpsampling`. A `size` is used only to derive a supported aspect ratio; ratios outside the list above are omitted with a warning. Use `resolution` to select the pixel-area tier. Unsupported options are omitted with warnings; masks are rejected. #### FLUX 3 Image Klein Klein uses the same `resolution`, `aspectRatio`, and `prompt.images` inputs as `flux-3-image`. It also accepts `seed`, `outputFormat` (`'jpeg'` or `'png'`), and `steps` (1–16, default 16): ```ts const { image } = await generateImage({ model: blackForestLabs.image('flux-3-image-klein'), prompt: { text: 'Change the sky to sunset while preserving the buildings', images: ['https://example.com/village.png'], }, aspectRatio: '16:9', seed: 42, providerOptions: { blackForestLabs: { resolution: '2k', outputFormat: 'png', steps: 16, pollTimeoutMillis: 240000, } satisfies BlackForestLabsImageModelOptions, }, }); ``` To request exact dimensions, pass `size` (for example, `'2048x2048'`) or set both `providerOptions.blackForestLabs.width` and `height`. Each dimension must be an integer from 64 to 6256, and their product must not exceed 16,822,272 pixels. Provider dimensions override `size`; exact dimensions override `resolution` and `aspectRatio`. Klein does not accept masks, `grounding`, or `version`. The provider polls through `Pending`, `Reasoning`, and `Generating` until the image is ready. Increase `pollTimeoutMillis` for requests that exceed the default 60 second polling timeout. It stops on moderation or task failures and downloads the signed image URL without sending API credentials. ### Image Editing Black Forest Labs Kontext models support powerful image editing capabilities using reference images. Pass input images via `prompt.images` to transform, combine, or edit existing images. #### Single Image Editing Transform an existing image using text prompts: ```ts import { blackForestLabs, BlackForestLabsImageModelOptions, } from '@ai-sdk/black-forest-labs'; import { generateImage } from 'ai'; const { images } = await generateImage({ model: blackForestLabs.image('flux-kontext-pro'), prompt: { text: 'A baby elephant with a shirt that has the logo from the input image.', images: [ 'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png', ], }, providerOptions: { blackForestLabs: { width: 1024, height: 768, } satisfies BlackForestLabsImageModelOptions, }, }); ``` #### Multi-Reference Editing Combine multiple reference images for complex transformations. Black Forest Labs supports up to 10 input images: ```ts import { blackForestLabs } from '@ai-sdk/black-forest-labs'; import { generateImage } from 'ai'; const { images } = await generateImage({ model: blackForestLabs.image('flux-kontext-pro'), prompt: { text: 'Combine the style of image 1 with the subject of image 2', images: [ 'https://example.com/style-reference.jpg', 'https://example.com/subject-reference.jpg', ], }, }); ``` Input images can be provided as URLs or base64-encoded strings. They support up to 20MB or 20 megapixels per image. #### FLUX.2 Editing FLUX.2 models accept reference images on the same `prompt.images` field, so no separate editing model ID is needed: ```ts import { blackForestLabs } from '@ai-sdk/black-forest-labs'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: blackForestLabs.image('flux-2-pro'), prompt: { text: 'Place this logo on a plain ceramic mug.', images: ['https://example.com/logo.png'], }, providerOptions: { blackForestLabs: { pollTimeoutMillis: 240000, }, }, }); ``` `flux-2-flex` additionally accepts `steps` and `guidance`, which let you trade generation time against quality: ```ts import { blackForestLabs } from '@ai-sdk/black-forest-labs'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: blackForestLabs.image('flux-2-flex'), prompt: 'A detailed macro shot of a dew-covered leaf', providerOptions: { blackForestLabs: { steps: 50, guidance: 4, pollTimeoutMillis: 240000, }, }, }); ``` #### Inpainting The `flux-pro-1.0-fill` model supports inpainting, which allows you to fill masked regions of an image with new content. Pass the source image via `prompt.images` and a mask image via `prompt.mask`: ```ts import { blackForestLabs } from '@ai-sdk/black-forest-labs'; import { generateImage } from 'ai'; const { images } = await generateImage({ model: blackForestLabs.image('flux-pro-1.0-fill'), prompt: { text: 'A beautiful garden with flowers', images: ['https://example.com/source-image.jpg'], mask: 'https://example.com/mask-image.png', }, }); ``` The mask image should be a grayscale image where white areas indicate regions to be filled and black areas indicate regions to preserve. ### Provider Options Black Forest Labs image models support flexible provider options through the `providerOptions.blackForestLabs` object. The supported parameters depend on the used model ID: - **resolution** *"768sq" | "1k" | "1.5k" | "2k" | "4k"* - FLUX 3 Image output resolution class. Defaults to `"1k"` for generation; Klein edits default to the first reference image's size. Exact dimensions vary with aspect ratio. - **grounding** *boolean* - `flux-3-image` web and image search grounding. Defaults to `true`; unavailable on Klein. - **version** *"latest"* - `flux-3-image` endpoint version. Only `"latest"` is currently available; unavailable on Klein. - **width** *number* - Output width in pixels (256–1920 for legacy models, 64–6256 for Klein; unsupported on `flux-3-image`). When set, this overrides any width derived from `size`. - **height** *number* - Output height in pixels (256–1920 for legacy models, 64–6256 for Klein; unsupported on `flux-3-image`). When set, this overrides any height derived from `size`. - **outputFormat** *string* - Desired format of the output image (`"jpeg"` or `"png"`). - **steps** *number* - Number of inference steps. Klein accepts 1–16 (default 16). Higher values may improve quality but increase generation time. - **guidance** *number* - Guidance scale for generation. Higher values follow the prompt more closely. - **imagePrompt** *string* - Base64-encoded image to use as additional visual context for generation. - **imagePromptStrength** *number* - Strength of the image prompt influence on generation (0.0 to 1.0). - **promptUpsampling** *boolean* - If true, performs upsampling on the prompt. - **raw** *boolean* - Enable raw mode for more natural, authentic aesthetics. - **safetyTolerance** *number* - Moderation level for inputs and outputs (0 = most strict). FLUX 3 Image models accept 0–7 and default to 2; the endpoint and organization determine the approved maximum. Legacy models accept 0–6. - **pollIntervalMillis** *number* - Interval in milliseconds between polling attempts (default 500ms). - **pollTimeoutMillis** *number* - Overall timeout in milliseconds for polling before timing out (default 60s). - **webhookUrl** *string* - URL for asynchronous completion notification. Must be a valid HTTP/HTTPS URL. - **webhookSecret** *string* - Secret for webhook signature verification, sent in the `X-Webhook-Secret` header. To pass reference images for editing, use `prompt.images` instead of provider options. This supports up to 10 images as URLs or base64-encoded strings. ### Provider Metadata The `generateImage` response includes provider-specific metadata in `providerMetadata.blackForestLabs.images[]`. Each image object may contain the following properties: - **seed** *number* - The seed used for generation. Useful for reproducing results. - **start\_time** *number* - Unix timestamp when generation started. - **end\_time** *number* - Unix timestamp when generation completed. - **duration** *number* - Generation duration in seconds. - **cost** *number* - Cost of the generation request. - **inputMegapixels** *number* - Input image size in megapixels. - **outputMegapixels** *number* - Output image size in megapixels. ```ts import { blackForestLabs } from '@ai-sdk/black-forest-labs'; import { generateImage } from 'ai'; const { image, providerMetadata } = await generateImage({ model: blackForestLabs.image('flux-pro-1.1'), prompt: 'A serene mountain landscape at sunset', }); // Access provider metadata const metadata = providerMetadata?.blackForestLabs?.images?.[0]; console.log('Seed:', metadata?.seed); console.log('Cost:', metadata?.cost); console.log('Duration:', metadata?.duration); ``` ### Regional Endpoints By default, requests are sent to `https://api.bfl.ai/v1`. You can select a [regional endpoint](https://docs.bfl.ai/api_integration/integration_guidelines#regional-endpoints) by setting `baseURL` when creating the provider instance: ```ts import { createBlackForestLabs } from '@ai-sdk/black-forest-labs'; const blackForestLabs = createBlackForestLabs({ baseURL: 'https://api.eu.bfl.ai/v1', // or https://api.us.bfl.ai/v1 }); ``` ## Video Models You can generate videos with the FLUX 3 video model using the [`experimental_generateVideo`](/docs/reference/ai-sdk-core/generate-video) function: ```ts import { blackForestLabs, type BlackForestLabsVideoModelOptions, } from '@ai-sdk/black-forest-labs'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: blackForestLabs.video('flux-3-video'), prompt: 'A white kitten chases a butterfly across a sunlit garden.', aspectRatio: '16:9', duration: 8, poll: { intervalMs: 2000, timeoutMs: 600000, // 10 minutes }, providerOptions: { blackForestLabs: { resolution: 'fhd', } satisfies BlackForestLabsVideoModelOptions, }, }); ``` FLUX 3 generates one video per call. Generation is asynchronous — the model submits a job and polls until it completes, then passes the signed MP4 URL to AI SDK Core. `generateVideo` downloads it before resolving, so `video` is a `GeneratedFile`. The signed URL remains available in `providerMetadata.blackForestLabs.videos[0].videoUrl`. Use the top-level `poll` option to have AI SDK Core orchestrate polling and set the interval and timeout. The FLUX 3 video API does not currently expose a webhook callback input, so a top-level `webhook` falls back to polling. `duration` accepts a whole number of seconds from 5 to 20. A fractional value is rounded and an out-of-range value is clamped, each with a warning. Omit it to let FLUX 3 fit the duration to the content. Audio is generated by default, so `generateAudio` only has to be set to turn it off. `fps` and `seed` are not supported by the API and are reported as warnings. The URL in provider metadata is time-limited. Persist the returned `video` bytes to your own storage. ### Resolution and aspect ratio The API takes a named resolution tier, `hd` or `fhd` (the default is `hd`), where `fhd` is finished by the video upsampler. The exact frame size depends on the aspect ratio. The top-level `resolution` option is `{width}x{height}`, so it is mapped onto a tier by its shorter side — `1280x720` becomes `hd` and `1920x1080` becomes `fhd`. Other values map to `hd` when the shorter side is at most 720 pixels, and to `fhd` above 720, with a warning reporting the mapping. Use `providerOptions.blackForestLabs.resolution` to set the tier directly. `aspectRatio` accepts `21:9`, `2:1`, `16:9`, `4:3`, `1:1`, `3:4`, and `9:16`. The API default is `auto`, which infers the ratio from the prompt and any conditioning media; because the top-level option must be `{width}:{height}`, `auto` can only be requested through `providerOptions.blackForestLabs.aspectRatio`. ### Generation modes FLUX 3 is a single endpoint with a `mode` discriminator. The mode is inferred from the inputs you pass: - **Text-to-video** — `prompt` only. - **Image-to-video** — pass `image` (or a `frameImages` entry with `frameType: 'first_frame'`) to animate a starting image. Adding a `last_frame` closes the clip on that image. A `last_frame` without a `first_frame` cannot be expressed, and is dropped with a warning. - **Keyframes** — for more than two images, or images pinned to a specific second, pass `providerOptions.blackForestLabs.keyframes`. It takes precedence over `image` and `frameImages`. - **Video continuation** — pass a video-typed entry in `inputReferences` to continue from the final frames of an existing MP4. FLUX 3 accepts a single video; keyframes and continuation are mutually exclusive. - **Draft enhance** — pass `providerOptions.blackForestLabs.draftCache`. See [Draft mode](#draft-mode). FLUX 3 has no reference-image input, so an image passed in `inputReferences` is ignored with a warning pointing at `image`, `frameImages`, or `keyframes`. ### Keyframes Keyframes are positional: one image opens the clip, two open and close it, and with more the first and last are the endpoints while the others are spaced evenly between them. Each image is an `http(s)` URL or a base64 string, and a request accepts 1 to 10 of them. Three or more untimed keyframes require an explicit `duration`; the provider reports a warning when that combination is sent, because the API rejects it. Pass `[seconds, image]` pairs in chronological order to pin each image to a second of the clip instead: ```ts import { blackForestLabs, type BlackForestLabsVideoModelOptions, } from '@ai-sdk/black-forest-labs'; import { experimental_generateVideo as generateVideo } from 'ai'; import { readFileSync } from 'node:fs'; const asBase64 = (file: string) => readFileSync(file).toString('base64'); const { video } = await generateVideo({ model: blackForestLabs.video('flux-3-video'), prompt: 'The cat, then the dog, then the owl each take a turn in the room.', duration: 12, providerOptions: { blackForestLabs: { keyframes: [ [0, asBase64('cat.png')], [4.5, asBase64('dog.png')], [9, asBase64('owl.png')], ], } satisfies BlackForestLabsVideoModelOptions, }, }); ``` ### Draft mode Setting `draft: true` renders a fast, lower-quality preview and leaves behind an encrypted bundle, reported as `providerMetadata.blackForestLabs.videos[0].draftCache`. Passing that bundle back as `draftCache` reproduces the same generation at full quality. The bundle pins the original mode, prompt, seed, and conditioning media, so an enhance request accepts nothing besides `safetyTolerance`. Any other option set on the call is reported as an `unsupported` warning. `generateVideo` requires a `prompt` argument, so pass an empty string to say there is nothing to add. ```ts import { blackForestLabs, type BlackForestLabsVideoModelOptions, } from '@ai-sdk/black-forest-labs'; import { experimental_generateVideo as generateVideo } from 'ai'; const draft = await generateVideo({ model: blackForestLabs.video('flux-3-video'), prompt: 'A white kitten chases a butterfly across a sunlit garden.', duration: 6, providerOptions: { blackForestLabs: { draft: true } satisfies BlackForestLabsVideoModelOptions, }, }); const draftCacheUrl = ( draft.providerMetadata.blackForestLabs?.videos as | Array<{ draftCache?: string }> | undefined )?.[0]?.draftCache; // The download URL expires; sending the base64 `.bin` is the durable path, // though the URL itself also works while the link is still valid. const response = await fetch(draftCacheUrl!); const arrayBuffer = await response.arrayBuffer(); const enhanced = await generateVideo({ model: blackForestLabs.video('flux-3-video'), prompt: '', providerOptions: { blackForestLabs: { draftCache: Buffer.from(arrayBuffer).toString('base64'), } satisfies BlackForestLabsVideoModelOptions, }, }); ``` A draft and its enhance are two separate generations, and each is billed separately. ### Video Provider Options The following optional provider options are available for FLUX 3 video: - **resolution** *'hd' | 'fhd'* Output resolution tier. Takes precedence over the top-level `resolution`. - **aspectRatio** *'21:9' | '2:1' | '16:9' | '4:3' | '1:1' | '3:4' | '9:16' | 'auto'* Aspect ratio of the generated video. Takes precedence over the top-level `aspectRatio`, and unlike it can be set to `auto`. - **keyframes** *Array\* Keyframes for image-to-video generation, for the shapes `image` and `frameImages` cannot express: more than two images, or images pinned to a specific second. Takes precedence over both. - **safetyTolerance** *number* Moderation strictness from 0 (strictest) to 4. Defaults to 2. Sexual content is capped at 3 and hate content at 2 regardless of the request, and any request carrying conditioning media is capped at 2. - **draft** *boolean* Render a fast, lower-quality preview instead of the finished video. Defaults to `false`. - **draftCache** *string* Encrypted draft-cache bundle from a prior `draft` generation, which switches the request to draft-enhance mode. - **version** *string* Model version to pin. Only `latest` is available today. ### Video Provider Metadata FLUX 3 video results include `providerMetadata.blackForestLabs.videos[]`. Each video object may contain the following properties: - **id** *string* - ID of the Black Forest Labs generation request. - **videoUrl** *string* - The signed MP4 URL (the same URL as `video`). Time-limited. - **draftCache** *string* - Download URL for the draft bundle. Present only on a `draft` generation. - **seed** *number* - The seed used for generation, when reported by the API. - **start\_time** *number* - Unix timestamp when generation started. - **end\_time** *number* - Unix timestamp when generation completed. - **duration** *number* - Duration reported by the API, in seconds. - **cost** *number* - Cost of the generation request in credits. - **inputMegapixels** *number* - Input size in megapixels. - **outputMegapixels** *number* - Output size in megapixels. ### Video Model Capabilities | Model | Description | | -------------- | -------------------------------------------------------------------------------------------------------- | | `flux-3-video` | Up to full-HD and 20 seconds, with synchronized audio. Text-to-video, keyframes, and video continuation. | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: ByteDance description: Learn how to use ByteDance with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/bytedance" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [ByteDance](https://www.bytedance.com/) provider contains support for the Seedance family of video generation models and the Seedream family of image generation models through the [BytePlus ModelArk](https://docs.byteplus.com/en/docs/ModelArk/) platform. Seedance provides high-quality text-to-video and image-to-video generation capabilities, including audio-video synchronization, first-and-last frame control, and multi-reference image generation (see the [video generation API](https://docs.byteplus.com/en/docs/ModelArk/Video_Generation_API)). Seedream provides text-to-image and image-to-image generation, including multi-image blending and batch image generation (see the [image generation API](https://docs.byteplus.com/en/docs/ModelArk/1824690)). You can use ByteDance models through [AI Gateway](/providers/ai-sdk-providers/ai-gateway) (recommended) or connect directly with `@ai-sdk/bytedance`. ```ts provider="bytedance" model="bytedance/seedream-5.0-pro" import { generateImage } from 'ai'; const { image } = await generateImage({ model: "bytedance/seedream-5.0-pro", prompt: 'A sunlit mountain village in watercolor.', }); console.log(image.base64); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. Gateway feature and provider-option support depends on the model and the provider serving the request. See [Gateway provider options](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options). ## Using `@ai-sdk/bytedance` For direct access to ByteDance, install `ai` and `@ai-sdk/bytedance`: ```bash pnpm add ai @ai-sdk/bytedance ``` Set your ByteDance API credential in the `ARK_API_KEY` environment variable: ```bash export ARK_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/bytedance`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/bytedance` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `byteDance` from `@ai-sdk/bytedance`: ```ts import { byteDance } from '@ai-sdk/bytedance'; ``` If you need a customized setup, you can import `createByteDance` and create a provider instance with your settings: ```ts import { createByteDance } from '@ai-sdk/bytedance'; const byteDance = createByteDance({ apiKey: 'your-api-key', // optional, defaults to ARK_API_KEY environment variable baseURL: 'custom-url', // optional headers: { /* custom headers */ }, // optional }); ``` You can use the following optional settings to customize the ByteDance provider instance: - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use proxy servers. The default prefix is `https://ark.ap-southeast.bytepluses.com/api/v3`. - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `ARK_API_KEY` environment variable. You can [obtain an API key](https://console.byteplus.com/ark/apiKey) from the BytePlus console. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. ## Image Models You can create ByteDance Seedream image models using the `.image()` factory method. For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image). ### Text-to-Image Generate images from text prompts: ```ts import { byteDance, type ByteDanceImageModelOptions } from '@ai-sdk/bytedance'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: byteDance.image('seedream-5-0-260128'), prompt: 'A salamander in a forest pond at dusk surrounded by fireflies', size: '2048x2048', providerOptions: { bytedance: { watermark: false, } satisfies ByteDanceImageModelOptions, }, }); ``` ByteDance identifies Seedream models by their ModelArk model id (e.g. `seedream-5-0-260128`) or an account-specific endpoint id (e.g. `ep-...`). The `size` parameter accepts pixel dimensions (`{width}x{height}`); resolution levels such as `2K` can be passed via `providerOptions.bytedance.size`. ### Image Editing Pass input images via `prompt.images` to transform an existing image (image-to-image): ```ts import { readFileSync } from 'node:fs'; import { byteDance, type ByteDanceImageModelOptions } from '@ai-sdk/bytedance'; import { generateImage } from 'ai'; const inputImage = readFileSync('./input-image.png'); const { image } = await generateImage({ model: byteDance.image('seedream-5-0-260128'), prompt: { text: 'Change the salamander to a snow weasel', images: [inputImage], }, providerOptions: { bytedance: { watermark: false, } satisfies ByteDanceImageModelOptions, }, }); ``` You can also pass multiple images to blend styles and elements from several references into a single output (multi-image blending). Images may be provided as binary data, base64 strings, or URLs: ```ts import { byteDance, type ByteDanceImageModelOptions } from '@ai-sdk/bytedance'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: byteDance.image('seedream-5-0-260128'), prompt: { text: 'Replace the clothing in image 1 with the outfit from image 2', images: ['https://example.com/model.png', 'https://example.com/outfit.png'], }, providerOptions: { bytedance: { watermark: false, } satisfies ByteDanceImageModelOptions, }, }); ``` Seedream does not support mask-based inpainting. Describe the edit in the prompt, optionally with markers drawn on the input image (interactive editing, supported by `dola-seedream-5-0-pro-260628`). ### Image Model Options The following options are available via `providerOptions.bytedance`. You can type them with `ByteDanceImageModelOptions`. - **watermark** *boolean* Whether to add an "AI generated" watermark to the bottom-right corner of the output image. - **outputFormat** *'png' | 'jpeg'* Format of the generated image file. Supported by `seedream-5-0` and `dola-seedream-5-0-pro`; `seedream-4-5` / `seedream-4-0` always return `jpeg`. - **size** *string* A resolution level (e.g. `1K`, `2K`, `3K`, `4K`) as an alternative to passing pixel dimensions via the top-level `size` parameter. When set, this overrides the top-level `size`. Available levels vary by model. - **sequentialImageGeneration** *'auto' | 'disabled'* Set to `'auto'` to generate a batch of related images (e.g. storyboards or brand visuals). Defaults to `'disabled'` (single image). - **maxImages** *number* Maximum number of images to generate when `sequentialImageGeneration` is `'auto'`. The number of input reference images plus generated images must not exceed the model's limit. - **optimizePromptMode** *'standard' | 'fast'* Prompt optimization mode. `seedream-4-0` supports both `standard` and `fast`; other models support `standard` only. Additional ModelArk fields can also be passed through `providerOptions.bytedance` and are validated by ModelArk. ### Image Model Capabilities | Model | Model ID | Capabilities | | ----------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | Seedream 5.0 Pro | `dola-seedream-5-0-pro-260628` | Text-to-image, single/multi image-to-image, interactive editing (markers). Sizes: 1K, 2K. Formats: png, jpeg. Up to 10 references. | | Seedream 5.0 Lite | `seedream-5-0-260128` (alias `seedream-5-0-lite-260128`) | Text-to-image, single/multi image-to-image, batch generation. Sizes: 2K, 3K, 4K. Formats: png, jpeg. Up to 14 references. | | Seedream 4.5 | `seedream-4-5-251128` | Text-to-image, single/multi image-to-image, batch generation. Sizes: 2K, 4K. Format: jpeg. Up to 14 references. | | Seedream 4.0 | `seedream-4-0-250828` | Text-to-image, single/multi image-to-image, batch generation, fast prompt mode. Sizes: 1K, 2K, 4K. Format: jpeg. Up to 14 references. | You can also pass any model or endpoint id string if needed, e.g. for future models not yet listed here. Streaming image output is not currently supported through the AI SDK integration. ## Video Models You can create ByteDance video models using the `.video()` factory method. For more on video generation with the AI SDK see [generateVideo()](/docs/reference/ai-sdk-core/generate-video). ### Text-to-Video Generate videos from text prompts: ```ts import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: byteDance.video('seedance-1-0-pro-250528'), prompt: 'Photorealistic style: Under a clear blue sky, a vast expanse of white daisy fields stretches out. The camera gradually zooms in and fixates on a close-up of a single daisy.', aspectRatio: '16:9', duration: 5, providerOptions: { bytedance: { watermark: false, } satisfies ByteDanceVideoModelOptions, }, }); console.log(video.url); ``` ### Image-to-Video Generate videos from a first-frame image with an optional text prompt: ```ts import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: byteDance.video('seedance-1-5-pro-251215'), prompt: { image: 'https://example.com/first-frame.png', text: 'The cat slowly turns its head and blinks', }, duration: 5, providerOptions: { bytedance: { watermark: false, } satisfies ByteDanceVideoModelOptions, }, }); ``` When the output ratio is dictated by an input — first-frame or first-and-last-frame image-to-video, video editing, and video extension — pass `aspectRatio: 'adaptive'` or omit `aspectRatio` entirely. Newer Seedance models reject an explicit ratio on those paths, because the output inherits the ratio of the input media. ### Image-to-Video with Audio Seedance 1.5 Pro supports generating synchronized audio alongside the video: ```ts import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: byteDance.video('seedance-1-5-pro-251215'), prompt: { image: 'https://example.com/pianist.png', text: 'A young man sits at a piano, playing calmly. Gentle piano music plays in sync with his movements.', }, duration: 5, providerOptions: { bytedance: { generateAudio: true, watermark: false, } satisfies ByteDanceVideoModelOptions, }, }); ``` ### First-and-Last Frame Video Generate smooth transitions between a starting and ending keyframe image: ```ts import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: byteDance.video('seedance-1-5-pro-251215'), prompt: { image: 'https://example.com/first-frame.jpg', text: 'Create a 360-degree orbiting camera shot based on this photo', }, duration: 5, providerOptions: { bytedance: { lastFrameImage: 'https://example.com/last-frame.jpg', generateAudio: true, watermark: false, } satisfies ByteDanceVideoModelOptions, }, }); ``` ### Multi-Reference Image-to-Video Using the Seedance 1.0 Lite I2V model, you can provide multiple reference images (1-4) that the model uses to faithfully reproduce object shapes, colors, and textures: ```ts import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: byteDance.video('seedance-1-0-lite-i2v-250428'), prompt: 'A boy wearing glasses and a blue T-shirt from [Image 1] and a corgi dog from [Image 2], sitting on the lawn from [Image 3], in 3D cartoon style', aspectRatio: '16:9', duration: 5, providerOptions: { bytedance: { referenceImages: [ 'https://example.com/boy.png', 'https://example.com/corgi.png', 'https://example.com/lawn.png', ], watermark: false, } satisfies ByteDanceVideoModelOptions, }, }); ``` ### Reference Video Seedance 2.0 supports reference videos that guide the style, motion, or composition of the generated video: ```ts import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: byteDance.video('dreamina-seedance-2-0-260128'), prompt: 'First-person perspective promotional ad, using the composition and camera movement from the reference video', aspectRatio: '16:9', duration: 4, providerOptions: { bytedance: { referenceVideos: ['https://example.com/reference-video.mp4'], watermark: false, } satisfies ByteDanceVideoModelOptions, }, }); ``` ### Reference Audio Seedance 2.0 supports reference audio that is used as background music or sound for the generated video: ```ts import { byteDance, type ByteDanceVideoModelOptions } from '@ai-sdk/bytedance'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: byteDance.video('dreamina-seedance-2-0-260128'), prompt: 'A serene mountain landscape at sunrise with gentle camera movement', aspectRatio: '16:9', duration: 4, providerOptions: { bytedance: { referenceAudio: ['https://example.com/background-music.mp3'], generateAudio: true, watermark: false, } satisfies ByteDanceVideoModelOptions, }, }); ``` ### Caller-Managed Callbacks Use `experimental_startVideo({ model, prompt, webhookUrl })` to register an application-owned callback receiver. The SDK forwards `webhookUrl` as `callback_url`, taking precedence over `providerOptions.bytedance.callback_url`. When `webhookUrl` is omitted, a raw `callback_url` is passed through unchanged. The [BytePlus callback protocol](https://docs.byteplus.com/en/docs/ModelArk/1520757) sends progress notifications (`'queued'` or `'running'`) as well as terminal notifications (`'succeeded'`, `'failed'`, or `'expired'`). Your receiver must filter progress notifications and correlate callbacks with the task. Use `experimental_getVideoStatus(model, { operation })` with the operation returned by `experimental_startVideo` to check status and retrieve the result after a terminal notification. Expired tasks return an error status with the provider's diagnostic details. These models deliberately do not expose `handleWebhookOption` because their callback protocol requires a progress-aware receiver. Core [`generateVideo({ webhook })`](/docs/reference/ai-sdk-core/generate-video) falls back to polling without calling the webhook factory. Plain `generateVideo()` also polls. Workflow's existing native webhook capability check rejects these direct provider models for webhook-based generation; use the explicit start/status flow for caller-managed callbacks. ### Video Model Options The following options are available via `providerOptions.bytedance`. You can type them with `ByteDanceVideoModelOptions`. #### Generation Options - **watermark** *boolean* Whether to add a watermark to the generated video. - **generateAudio** *boolean* Whether to generate synchronized audio for the video. Supported by Seedance 1.5 Pro and Seedance 2.0. - **cameraFixed** *boolean* Whether to fix the camera during generation. - **returnLastFrame** *boolean* Whether to return the last frame of the generated video. Useful for chaining consecutive videos. - **serviceTier** *'default' | 'flex'* Inference tier. `'default'` for online inference. `'flex'` for offline inference at 50% of the price, with higher latency (response times on the order of hours). - **draft** *boolean* Enable draft sample mode for low-cost preview generation. Only supported by Seedance 1.5 Pro. Generates a 480p preview video for rapid iteration before committing to a full-quality generation. #### Image Input Options - **lastFrameImage** *string* URL of the last frame image for first-and-last frame video generation. The model generates smooth transitions between the first frame (provided via the `image` prompt) and this last frame. Supported by Seedance 1.5 Pro, 1.0 Pro, and 1.0 Lite I2V. - **referenceImages** *string\[]* Array of reference image URLs for multi-reference image-to-video generation. The model extracts key features from each image and reproduces them in the video. Use `[Image 1]`, `[Image 2]`, etc. in your prompt to reference specific images. #### Media Reference Options - **referenceVideos** *string\[]* Array of reference video URLs (up to 3 videos, max 15 seconds each) for reference-guided video generation. The model uses the referenced videos to guide style, motion, or composition. Supported by Seedance 2.0. - **referenceAudio** *string\[]* Array of reference audio URLs (up to 3, max 15 seconds each) for audio-guided video generation. The model uses the referenced audio as background music or synchronized sound. Supports data URIs (e.g., `data:audio/wav;base64,...`). Supported by Seedance 2.0. #### Polling Options ByteDance video generation is task-based: the provider creates a task and the AI SDK polls it until it completes. Configure polling with the top-level `poll` option of [`generateVideo()`](/docs/reference/ai-sdk-core/generate-video): The configured ByteDance API origin is trusted for the first status request, while every redirect to another origin is validated before it is followed. ```ts const { video } = await generateVideo({ model: byteDance.video('seedance-1-0-pro-250528'), prompt: 'A futuristic city with flying cars', poll: { intervalMs: 2000, // how often to check the task (default: 5000) timeoutMs: 900000, // give up after 15 minutes (default: 600000) }, }); ``` The `pollIntervalMs` and `pollTimeoutMs` provider options are deprecated and ignored. Passing either emits a warning. Video generation can take several minutes, so raise `poll.timeoutMs` above the 10 minute default for long jobs. ### Video Model Capabilities | Model | Model ID | Capabilities | | ----------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | Seedance 2.0 | `dreamina-seedance-2-0-260128` | T2V, I2V, reference videos (up to 3), reference audio (up to 3), audio-video sync. Duration: 4-15s. Resolution: 480p, 720p. | | Seedance 2.0 Fast | `dreamina-seedance-2-0-fast-260128` | T2V, I2V, reference videos (up to 3), reference audio (up to 3), audio-video sync. Optimized for speed. Duration: 4-15s. Resolution: 480p, 720p. | | Seedance 1.5 Pro | `seedance-1-5-pro-251215` | T2V, I2V (first frame), I2V (first+last frame), audio-video sync, draft mode. Duration: 4-12s. Resolution: 480p, 720p, 1080p. | | Seedance 1.0 Pro | `seedance-1-0-pro-250528` | T2V, I2V (first frame), I2V (first+last frame). Duration: 2-12s. Resolution: 480p, 720p, 1080p. | | Seedance 1.0 Pro Fast | `seedance-1-0-pro-fast-251015` | T2V, I2V (first frame). Optimized for speed and cost. Duration: 2-12s. | | Seedance 1.0 Lite (T2V) | `seedance-1-0-lite-t2v-250428` | Text-to-video only. Duration: 2-12s. Resolution: 480p, 720p, 1080p. | | Seedance 1.0 Lite (I2V) | `seedance-1-0-lite-i2v-250428` | I2V (first frame), I2V (first+last frame), multi-reference images (1-4). Duration: 2-12s. Resolution: 480p, 720p. | Supported aspect ratios: `16:9`, `4:3`, `1:1`, `3:4`, `9:16`, `21:9`, `adaptive` (image-to-video only). All models output MP4 video at 24 fps. You can also pass any model ID string if needed, e.g. for future models not yet listed here. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Cartesia description: Learn how to set up the Cartesia provider and check AI Gateway availability. url: "https://ai-sdk.dev/providers/ai-sdk-providers/cartesia" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [Cartesia](https://cartesia.ai/) provider contains Sonic speech generation, Ink-Whisper batch transcription, and Ink 2 realtime and streaming transcription support. Cartesia models are not currently available through [AI Gateway](https://vercel.com/ai-gateway/models), so the opening example uses `@ai-sdk/cartesia` directly. See [Using AI Gateway](#using-ai-gateway) below for supported alternatives. ```ts import { generateSpeech } from 'ai'; import { cartesia } from '@ai-sdk/cartesia'; import { writeFile } from 'node:fs/promises'; const { audio } = await generateSpeech({ model: cartesia.speech('sonic-3.5'), text: 'Hello, world!', outputFormat: 'mp3', voice: '694f9389-aac1-45b6-b726-9d9369183238', }); await writeFile('speech.mp3', audio.uint8Array); ``` ## Using AI Gateway The current [AI Gateway model catalog](https://vercel.com/ai-gateway/models) does not list Cartesia models. Use `@ai-sdk/cartesia` for the APIs on this page. To use other speech models through Gateway, install `ai` and configure a Gateway API key: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [Gateway documentation](/providers/ai-sdk-providers/ai-gateway) for supported models and APIs. ## Using `@ai-sdk/cartesia` For direct access to Cartesia, install `ai` and `@ai-sdk/cartesia`: ```bash pnpm add ai @ai-sdk/cartesia ``` Set your Cartesia API credential in the `CARTESIA_API_KEY` environment variable: ```bash export CARTESIA_API_KEY=your_api_key_here ``` The following sections document the direct `@ai-sdk/cartesia` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `cartesia` from `@ai-sdk/cartesia`: ```ts import { cartesia } from '@ai-sdk/cartesia'; ``` If you need a customized setup, you can import `createCartesia` from `@ai-sdk/cartesia` and create a provider instance with your settings: ```ts import { createCartesia } from '@ai-sdk/cartesia'; const cartesia = createCartesia({ // custom settings, e.g. fetch: customFetch, }); ``` You can use the following optional settings to customize the Cartesia provider instance: - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `CARTESIA_API_KEY` environment variable. - **version** *string* The Cartesia API version to use (sent via the `Cartesia-Version` header). - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. - **webSocket** *WebSocketConstructor* Custom WebSocket implementation for Ink 2 streaming transcription. Defaults to the global `WebSocket` constructor. ## Speech Models You can create models that call the [Cartesia text-to-speech API](https://docs.cartesia.ai/api-reference/tts/bytes) using the `.speech()` factory method. The first argument is the model id, e.g. `sonic-3.5`. ```ts const model = cartesia.speech('sonic-3.5'); ``` You can use the model with the `generateSpeech` function. Cartesia requires a `voice` id: ```ts import { generateSpeech } from 'ai'; import { cartesia } from '@ai-sdk/cartesia'; const result = await generateSpeech({ model: cartesia.speech('sonic-3.5'), text: 'Hello, world!', voice: '694f9389-aac1-45b6-b726-9d9369183238', }); ``` You can also pass additional provider-specific options using the `providerOptions` argument: ```ts {7-12} import { generateSpeech } from 'ai'; import { cartesia, type CartesiaSpeechModelOptions } from '@ai-sdk/cartesia'; const result = await generateSpeech({ model: cartesia.speech('sonic-3.5'), text: 'Hello, world!', voice: '694f9389-aac1-45b6-b726-9d9369183238', providerOptions: { cartesia: { container: 'wav', encoding: 'pcm_s16le', sampleRate: 24000, } satisfies CartesiaSpeechModelOptions, }, }); ``` The following provider options are available: - **container** *string* Container format for the output audio. Supported values: `'raw'`, `'wav'`, `'mp3'`. Optional. - **encoding** *string* Encoding type for the audio output. Supported values: `'pcm_f32le'`, `'pcm_s16le'`, `'pcm_mulaw'`, `'pcm_alaw'`. Optional. - **sampleRate** *number* Sample rate for the output audio in Hz (e.g. `8000`, `16000`, `22050`, `24000`, `44100`, `48000`). Optional. - **bitRate** *number* Bitrate for `mp3` output in bits per second (e.g. `32000`, `64000`, `128000`, `192000`). Optional. - **speed** *number* Controls the speed of the generated speech between `0.6` and `1.5`. Optional. - **language** *string* The language to generate speech in (ISO 639-1 code). Optional. ### Model Capabilities | Model | | -------------- | | `sonic-3.5` | | `sonic-3` | | `sonic-2` | | `sonic-turbo` | | `sonic-latest` | ## Realtime Transcription Models Realtime is an experimental feature. You can create a model for Cartesia's realtime [Ink 2](https://docs.cartesia.ai/build-with-cartesia/stt/latest) API using the `.experimental_realtime()` factory method: ```ts import { cartesia } from '@ai-sdk/cartesia'; const model = cartesia.experimental_realtime('ink-2'); ``` Realtime sessions run in the browser and require a short-lived access token created on your server with `cartesia.experimental_realtime.getToken()`: ```ts const token = await cartesia.experimental_realtime.getToken({ model: 'ink-2', sessionConfig: { inputAudioFormat: { type: 'audio/pcm', rate: 24000 }, inputAudioTranscription: { language: 'en' }, turnDetection: { type: 'server-vad' }, }, }); ``` Ink 2 produces input transcription events and supports English audio. Turn detection is enabled by default. To use manual finalization instead, set `turnDetection` to `null` in the session config and call `commitAudio()` when the current input is complete. See [Realtime](/docs/ai-sdk-core/realtime) for the complete browser session setup. ### Model Capabilities | Model | Streaming Transcription | Turn Detection | | ------- | ----------------------- | -------------- | | `ink-2` | ✓ | ✓ | ## Streaming Transcription Models Streaming transcription is an experimental feature. Ink 2 is also available through the transcription streaming API. Create the model with `.transcription()` and pass it to `experimental_streamTranscribe`: ```ts import { cartesia } from '@ai-sdk/cartesia'; import { experimental_streamTranscribe as streamTranscribe } from 'ai'; const result = streamTranscribe({ model: cartesia.transcription('ink-2'), audio, // ReadableStream containing raw audio chunks inputAudioFormat: { type: 'audio/pcm', rate: 24000 }, providerOptions: { cartesia: { language: 'en', }, }, }); for await (const part of result.fullStream) { if (part.type === 'transcript-partial') { console.log('partial:', part.text); } if (part.type === 'transcript-final') { console.log('final:', part.text); } } ``` The provider creates a short-lived, STT-scoped Cartesia access token before opening the WebSocket, so the API key is not included in the WebSocket URL. Send audio chunks at approximately their playback rate; Cartesia's realtime endpoint is intended for live audio rather than bulk file upload. Ink 2 supports English audio and uses Cartesia's native turn detection by default. To finalize only when the input audio stream ends, pass `providerOptions.cartesia.streaming.turnDetection: false`. By default, the provider maps `audio/pcm`, `audio/pcmu`, and `audio/pcma` to 16-bit PCM, G.711 μ-law, and G.711 A-law respectively. To stream another raw PCM representation, set `providerOptions.cartesia.streaming.encoding` to `pcm_s32le`, `pcm_f16le`, or `pcm_f32le`. For example: ```ts const result = streamTranscribe({ model: cartesia.transcription('ink-2'), audio, inputAudioFormat: { type: 'audio/pcm', rate: 48000 }, providerOptions: { cartesia: { streaming: { encoding: 'pcm_f32le', }, }, }, }); ``` See [Transcription](/docs/ai-sdk-core/transcription) for more information about streaming transcription. ### Model Capabilities | Model | Streaming Transcription | Turn Detection | | ------- | ----------------------- | -------------- | | `ink-2` | ✓ | ✓ | ## Batch Transcription Models You can create models that call the [Cartesia transcription API](https://docs.cartesia.ai/api-reference/stt/transcribe) using the `.transcription()` factory method. The first argument is the model id e.g. `ink-whisper`. ```ts const model = cartesia.transcription('ink-whisper'); ``` You can use the model with the `transcribe` function: ```ts import { transcribe } from 'ai'; import { cartesia } from '@ai-sdk/cartesia'; import { readFile } from 'fs/promises'; const result = await transcribe({ model: cartesia.transcription('ink-whisper'), audio: await readFile('audio.mp3'), }); ``` You can also pass additional provider-specific options using the `providerOptions` argument: ```ts {11-15} import { transcribe } from 'ai'; import { cartesia, type CartesiaTranscriptionModelOptions, } from '@ai-sdk/cartesia'; import { readFile } from 'fs/promises'; const result = await transcribe({ model: cartesia.transcription('ink-whisper'), audio: await readFile('audio.mp3'), providerOptions: { cartesia: { language: 'en', } satisfies CartesiaTranscriptionModelOptions, }, }); ``` The following provider options are available: - **language** *string* The language of the audio (ISO 639-1 code). If not specified, it defaults to English. Optional. - **timestampGranularities** *array of strings* The timestamp granularities to populate. Currently only `'word'` is supported. Optional. ### Model Capabilities | Model | Transcription | Duration | Segments | Language | | ------------- | ------------- | -------- | -------- | -------- | | `ink-whisper` | ✓ | ✓ | ✓ | ✓ | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Cerebras description: Learn how to use Cerebras with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/cerebras" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [Cerebras](https://cerebras.ai) provider offers access to powerful language models through the Cerebras API, including their high-speed inference capabilities powered by Wafer-Scale Engines and CS-3 systems. Start with [AI Gateway](/providers/ai-sdk-providers/ai-gateway) for models in its catalog (recommended), or use `@ai-sdk/cerebras` to connect directly to Cerebras. ```ts provider="cerebras" model="openai/gpt-oss-120b" import { generateText } from 'ai'; const { text } = await generateText({ model: "openai/gpt-oss-120b", prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { gateway: { order: ['cerebras'], }, }, }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. The example sets `providerOptions.gateway.order` to `['cerebras']`. This sends the request to Cerebras first, then falls back to other available providers of the same model if Cerebras is unavailable or the request fails. `order` sets a preference; it does not restrict fallback to Cerebras. See [provider ordering](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). The dropdown lists models served by Cerebras through Gateway with a corresponding direct-provider example. Provider availability depends on the model and your team's [Provider Allowlist](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). ## Using `@ai-sdk/cerebras` For direct access to Cerebras, install `ai` and `@ai-sdk/cerebras`: ```bash pnpm add ai @ai-sdk/cerebras ``` Set your Cerebras API credential in the `CEREBRAS_API_KEY` environment variable: ```bash export CEREBRAS_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/cerebras`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/cerebras` provider, including its model factories and settings. Gateway model and feature availability can differ. API keys can be obtained from the [Cerebras Platform](https://cloud.cerebras.ai). ## Provider Instance You can import the default provider instance `cerebras` from `@ai-sdk/cerebras`: ```ts import { cerebras } from '@ai-sdk/cerebras'; ``` For custom configuration, you can import `createCerebras` and create a provider instance with your settings: ```ts import { createCerebras } from '@ai-sdk/cerebras'; const cerebras = createCerebras({ apiKey: process.env.CEREBRAS_API_KEY ?? '', }); ``` You can use the following optional settings to customize the Cerebras provider instance: - **baseURL** *string* Use a different URL prefix for API calls. The default prefix is `https://api.cerebras.ai/v1`. - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `CEREBRAS_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. ## Language Models You can create language models using a provider instance: ```ts import { cerebras } from '@ai-sdk/cerebras'; import { generateText } from 'ai'; const { text } = await generateText({ model: cerebras('gpt-oss-120b'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` Cerebras language models can be used in the `streamText` function (see [AI SDK Core](/docs/ai-sdk-core)). You can create Cerebras language models using a provider instance. The first argument is the model ID, e.g. `gpt-oss-120b`: ```ts const model = cerebras('gpt-oss-120b'); ``` You can also use the `.languageModel()` and `.chat()` methods: ```ts const languageModel = cerebras.languageModel('gpt-oss-120b'); const chatModel = cerebras.chat('gpt-oss-120b'); ``` ### Reasoning Models Cerebras offers reasoning models including `gpt-oss-120b` and `gemma-4-31b` that generate intermediate thinking tokens before their final response. The reasoning output is streamed through the standard AI SDK reasoning parts. For `gpt-oss-120b`, you can control the reasoning depth using the `reasoningEffort` provider option: ```ts import { cerebras } from '@ai-sdk/cerebras'; import { streamText } from 'ai'; const result = streamText({ model: cerebras('gpt-oss-120b'), providerOptions: { cerebras: { reasoningEffort: 'medium', }, }, prompt: 'How many "r"s are in the word "strawberry"?', }); for await (const part of result.stream) { if (part.type === 'reasoning') { console.log('Reasoning:', part.text); } else if (part.type === 'text-delta') { process.stdout.write(part.textDelta); } } ``` See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details on how to integrate reasoning into your chatbot. ### Provider Options The following optional provider options are available for Cerebras language models: - **parallelToolCalls** *boolean* Whether to enable parallel function calling during tool use. Defaults to `true`. - **logprobs** *boolean* Whether to return log probabilities for generated tokens. Defaults to `false`. - **topLogprobs** *number* Number of most likely tokens to return at each token position. Accepts values from `0` through `20`. Requires `logprobs` to be `true`. - **logitBias** *Record\* Maps token IDs to bias values from `-100` through `100`. - **serviceTier** *'auto' | 'default' | 'flex' | 'priority'* Controls request priority. Availability depends on your account and endpoint. - **reasoningEffort** *'none' | 'low' | 'medium' | 'high'* Controls the amount of reasoning performed by supported models. Supported values and defaults depend on the model. - **reasoningFormat** *'none' | 'parsed' | 'text\_parsed' | 'raw' | 'hidden'* Controls how reasoning content appears in the response. Format support depends on the model. - **prediction** *\{ type: 'content'; content: string | Array\<\{ type: 'text'; text: string }> }* Supplies predicted output that can accelerate requests where most of the response is already known. - **promptCacheKey** *string* Routes related requests to the same prompt cache. The value can contain up to 1024 characters and requires account-level enablement. - **user** *string* A unique identifier representing your end-user, which can help with monitoring and abuse detection. - **strictJsonSchema** *boolean* Whether to use strict JSON schema validation. When `true`, the model uses constrained decoding to guarantee schema compliance. Defaults to `true`. For example: ```ts import { cerebras, type CerebrasLanguageModelChatOptions, } from '@ai-sdk/cerebras'; import { generateText } from 'ai'; const result = await generateText({ model: cerebras('gpt-oss-120b'), prompt: 'Explain why the sky is blue.', providerOptions: { cerebras: { reasoningEffort: 'low', reasoningFormat: 'parsed', promptCacheKey: 'conversation-123', } satisfies CerebrasLanguageModelChatOptions, }, }); ``` ## Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | Reasoning | | -------------- | ----------- | ----------------- | ---------- | -------------- | --------- | | `gpt-oss-120b` | ✗ | ✓ | ✓ | ✓ | ✓ | | `gemma-4-31b` | ✓ | ✓ | ✓ | ✓ | ✓ | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Cohere description: Learn how to use Cohere with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/cohere" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [Cohere](https://cohere.com/) provider contains language and embedding model support for the Cohere chat API. You can use Cohere models through [AI Gateway](/providers/ai-sdk-providers/ai-gateway) (recommended) or connect directly with `@ai-sdk/cohere`. ```ts provider="cohere" model="cohere/command-a" import { generateText } from 'ai'; const { text } = await generateText({ model: "cohere/command-a", prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. Gateway feature and provider-option support depends on the model and the provider serving the request. See [Gateway provider options](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options). ## Using `@ai-sdk/cohere` For direct access to Cohere, install `ai` and `@ai-sdk/cohere`: ```bash pnpm add ai @ai-sdk/cohere ``` Set your Cohere API credential in the `COHERE_API_KEY` environment variable: ```bash export COHERE_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/cohere`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/cohere` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `cohere` from `@ai-sdk/cohere`: ```ts import { cohere } from '@ai-sdk/cohere'; ``` If you need a customized setup, you can import `createCohere` from `@ai-sdk/cohere` and create a provider instance with your settings: ```ts import { createCohere } from '@ai-sdk/cohere'; const cohere = createCohere({ // custom settings }); ``` You can use the following optional settings to customize the Cohere provider instance: - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use proxy servers. The default prefix is `https://api.cohere.com/v2`. - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `COHERE_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. - **generateId** *() => string* Optional function to generate unique IDs for each request. Defaults to the SDK's built-in ID generator. ## Language Models You can create models that call the [Cohere chat API](https://docs.cohere.com/v2/docs/chat-api) using a provider instance. The first argument is the model id, e.g. `command-a-03-2025`. Some Cohere chat models support tool calls. ```ts const model = cohere('command-a-03-2025'); ``` ### Example You can use Cohere language models to generate text with the `generateText` function: ```ts import { cohere } from '@ai-sdk/cohere'; import { generateText } from 'ai'; const { text } = await generateText({ model: cohere('command-a-03-2025'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` Cohere language models can also be used in the `streamText` function and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output) (see [AI SDK Core](/docs/ai-sdk-core)). ### Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | | ----------------------------- | ----------- | ----------------- | ---------- | -------------- | | `command-a-03-2025` | ✗ | ✓ | ✓ | ✓ | | `command-a-reasoning-08-2025` | ✗ | ✓ | ✓ | ✓ | | `command-a-vision-07-2025` | ✓ | ✓ | ✓ | ✓ | | `command-r7b-12-2024` | ✗ | ✓ | ✓ | ✓ | | `command-r-plus-04-2024` | ✗ | ✓ | ✓ | ✓ | | `command-r-plus` | ✗ | ✓ | ✓ | ✓ | | `command-r-08-2024` | ✗ | ✓ | ✓ | ✓ | | `command-r-03-2024` | ✗ | ✓ | ✓ | ✓ | | `command-r` | ✗ | ✓ | ✓ | ✓ | | `command` | ✗ | ✗ | ✗ | ✗ | | `command-nightly` | ✗ | ✗ | ✗ | ✗ | | `command-light` | ✗ | ✗ | ✗ | ✗ | | `command-light-nightly` | ✗ | ✗ | ✗ | ✗ | The table above lists popular models. Please see the [Cohere docs](https://docs.cohere.com/v2/docs/models#command) for a full list of available models. You can also pass any available provider model ID as a string if needed. #### Image Inputs Vision-capable Cohere models such as `command-a-vision-07-2025` accept image inputs as part of the message content. You can pass images as raw bytes, base64-encoded strings, or URLs: ```ts import { cohere } from '@ai-sdk/cohere'; import { generateText } from 'ai'; import { readFileSync } from 'node:fs'; const { text } = await generateText({ model: cohere('command-a-vision-07-2025'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe the image in detail.' }, { type: 'file', mediaType: 'image', data: readFileSync('./data/comic-cat.png'), }, ], }, ], }); ``` You can use the `cohere` provider option on a file part to set the [image input detail](https://docs.cohere.com/docs/image-inputs) to `high`, `low`, or `auto`: ```ts {2,16-18} import { cohere } from '@ai-sdk/cohere'; import { generateText } from 'ai'; const { text } = await generateText({ model: cohere('command-a-vision-07-2025'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe the image in detail.' }, { type: 'file', mediaType: 'image', data: 'https://github.com/vercel/ai/blob/main/examples/ai-functions/data/comic-cat.png?raw=true', // Cohere specific options - image detail: providerOptions: { cohere: { detail: 'high' }, }, }, ], }, ], }); ``` #### Reasoning Cohere has introduced reasoning with the `command-a-reasoning-08-2025` model. You can learn more at [https://docs.cohere.com/docs/reasoning](https://docs.cohere.com/docs/reasoning). ```ts import { cohere, type CohereLanguageModelChatOptions } from '@ai-sdk/cohere'; import { generateText } from 'ai'; async function main() { const { text, reasoning } = await generateText({ model: cohere('command-a-reasoning-08-2025'), prompt: "Alice has 3 brothers and she also has 2 sisters. How many sisters does Alice's brother have?", // optional: reasoning options providerOptions: { cohere: { thinking: { type: 'enabled', tokenBudget: 100, }, } satisfies CohereLanguageModelChatOptions, }, }); console.log(reasoning); console.log(text); } main().catch(console.error); ``` ## Embedding Models You can create models that call the [Cohere embed API](https://docs.cohere.com/v2/reference/embed) using the `.embedding()` factory method. ```ts const model = cohere.embedding('embed-v4.0'); ``` You can use Cohere embedding models to generate embeddings with the `embed` function: ```ts import { cohere, type CohereEmbeddingModelOptions } from '@ai-sdk/cohere'; import { embed } from 'ai'; const { embedding } = await embed({ model: cohere.embedding('embed-v4.0'), value: 'sunny day at the beach', providerOptions: { cohere: { inputType: 'search_document', } satisfies CohereEmbeddingModelOptions, }, }); ``` Cohere embedding models support additional provider options that can be passed via `providerOptions.cohere`: ```ts import { cohere, type CohereEmbeddingModelOptions } from '@ai-sdk/cohere'; import { embed } from 'ai'; const { embedding } = await embed({ model: cohere.embedding('embed-v4.0'), value: 'sunny day at the beach', providerOptions: { cohere: { inputType: 'search_document', truncate: 'END', } satisfies CohereEmbeddingModelOptions, }, }); ``` The following provider options are available: - **embeddingType** *'float' | 'int8' | 'uint8' | 'binary' | 'ubinary'* Selects one embedding format per call. Defaults to `float`, preserving the existing behavior. Non-float formats require Embed v3.0 or newer. For example: ```ts const { embedding } = await embed({ model: cohere.embedding('embed-v4.0'), value: 'sunny day at the beach', providerOptions: { cohere: { embeddingType: 'int8', } satisfies CohereEmbeddingModelOptions, }, }); ``` The selected format is returned in `embedding` (or `embeddings` for `embedMany`) as number arrays. The provider returns the values without converting or unpacking them. `binary` and `ubinary` contain packed bytes: each number represents eight dimensions. Use a binary-aware vector database or unpack the bits before comparing vectors; do not pass packed bytes directly to `cosineSimilarity`. Compressed formats can affect retrieval quality, and compact storage requires an appropriate database representation. - **inputType** *'search\_document' | 'search\_query' | 'classification' | 'clustering'* Specifies the type of input passed to the model. Default is `search_query`. - `search_document`: Used for embeddings stored in a vector database for search use-cases. - `search_query`: Used for embeddings of search queries run against a vector DB to find relevant documents. - `classification`: Used for embeddings passed through a text classifier. - `clustering`: Used for embeddings run through a clustering algorithm. - **outputDimension** *256 | 512 | 768 | 1024 | 1536 | 2048* The number of dimensions of the output embedding. Available for Embed v4.0 and newer models. Embed v4.0 supports `256`, `512`, `1024`, and `1536` (default). Embed v5.0 Pro and Fast also support `768` and `2048` (default). When omitted, the API uses the model's default dimension. - **truncate** *'NONE' | 'START' | 'END'* Specifies how the API will handle inputs longer than the maximum token length. Default is `END`. - `NONE`: If selected, when the input exceeds the maximum input token length will return an error. - `START`: Will discard the start of the input until the remaining input is exactly the maximum input token length for the model. - `END`: Will discard the end of the input until the remaining input is exactly the maximum input token length for the model. ### Model Capabilities | Model | Embedding Dimensions | | ------------------------------- | ----------------------------------------- | | `embed-v5.0-pro` | 256, 512, 768, 1024, 1536, 2048 (default) | | `embed-v5.0-fast` | 256, 512, 768, 1024, 1536, 2048 (default) | | `embed-v4.0` | 256, 512, 1024, 1536 (default) | | `embed-english-v3.0` | 1024 | | `embed-multilingual-v3.0` | 1024 | | `embed-english-light-v3.0` | 384 | | `embed-multilingual-light-v3.0` | 384 | | `embed-english-v2.0` | 4096 | | `embed-english-light-v2.0` | 1024 | | `embed-multilingual-v2.0` | 768 | ## Reranking Models You can create models that call the [Cohere rerank API](https://docs.cohere.com/v2/reference/rerank) using the `.reranking()` factory method. ```ts const model = cohere.reranking('rerank-v4.0-pro'); ``` You can use Cohere reranking models to rerank documents with the `rerank` function: ```ts import { cohere } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const documents = [ 'sunny day at the beach', 'rainy afternoon in the city', 'snowy night in the mountains', ]; const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents, query: 'talk about rain', topN: 2, }); console.log(ranking); // [ // { originalIndex: 1, score: 0.9, document: 'rainy afternoon in the city' }, // { originalIndex: 0, score: 0.3, document: 'sunny day at the beach' } // ] ``` Cohere reranking models support additional provider options that can be passed via `providerOptions.cohere`: ```ts import { cohere, type CohereRerankingModelOptions } from '@ai-sdk/cohere'; import { rerank } from 'ai'; const { ranking } = await rerank({ model: cohere.reranking('rerank-v4.0-pro'), documents: ['sunny day at the beach', 'rainy afternoon in the city'], query: 'talk about rain', providerOptions: { cohere: { maxTokensPerDoc: 1000, priority: 1, } satisfies CohereRerankingModelOptions, }, }); ``` The following provider options are available: - **maxTokensPerDoc** *number* Maximum number of tokens per document. Default is `4096`. - **priority** *number* Priority of the request. Default is `0`. ### Model Capabilities | Model | | -------------------------- | | `rerank-v4.0-pro` | | `rerank-v4.0-fast` | | `rerank-v3.5` | | `rerank-english-v3.0` | | `rerank-multilingual-v3.0` | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Deepgram description: Learn how to set up the Deepgram provider and check AI Gateway availability. url: "https://ai-sdk.dev/providers/ai-sdk-providers/deepgram" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [Deepgram](https://deepgram.com/) provider contains language model support for the Deepgram transcription and speech generation APIs. Deepgram models are not currently available through [AI Gateway](https://vercel.com/ai-gateway/models), so the opening example uses `@ai-sdk/deepgram` directly. See [Using AI Gateway](#using-ai-gateway) below for supported alternatives. ```ts import { generateSpeech } from 'ai'; import { deepgram } from '@ai-sdk/deepgram'; import { writeFile } from 'node:fs/promises'; const { audio } = await generateSpeech({ model: deepgram.speech('aura-2'), text: 'Hello, world!', outputFormat: 'mp3', voice: 'thalia', language: 'en', }); await writeFile('speech.mp3', audio.uint8Array); ``` ## Using AI Gateway The current [AI Gateway model catalog](https://vercel.com/ai-gateway/models) does not list Deepgram models. Use `@ai-sdk/deepgram` for the APIs on this page. To use other speech models through Gateway, install `ai` and configure a Gateway API key: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [Gateway documentation](/providers/ai-sdk-providers/ai-gateway) for supported models and APIs. ## Using `@ai-sdk/deepgram` For direct access to Deepgram, install `ai` and `@ai-sdk/deepgram`: ```bash pnpm add ai @ai-sdk/deepgram ``` Set your Deepgram API credential in the `DEEPGRAM_API_KEY` environment variable: ```bash export DEEPGRAM_API_KEY=your_api_key_here ``` The following sections document the direct `@ai-sdk/deepgram` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `deepgram` from `@ai-sdk/deepgram`: ```ts import { deepgram } from '@ai-sdk/deepgram'; ``` If you need a customized setup, you can import `createDeepgram` from `@ai-sdk/deepgram` and create a provider instance with your settings: ```ts import { createDeepgram } from '@ai-sdk/deepgram'; const deepgram = createDeepgram({ // custom settings, e.g. fetch: customFetch, }); ``` You can use the following optional settings to customize the Deepgram provider instance: - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `DEEPGRAM_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. ## Speech Models You can create models that call the [Deepgram text-to-speech API](https://developers.deepgram.com/docs/text-to-speech) using the `.speech()` factory method. The first argument is the voice family ID: `aura-2` (current generation) or `aura` (Aura-1). The voice and language are selected with the `generateSpeech` `voice` and `language` options — the provider composes the upstream Deepgram model ID as `--` (language defaults to `en`): ```ts import { generateSpeech } from 'ai'; import { deepgram } from '@ai-sdk/deepgram'; const result = await generateSpeech({ model: deepgram.speech('aura-2'), voice: 'thalia', language: 'en', text: 'Hello, world!', }); ``` Full voice model IDs (e.g. `deepgram.speech('aura-2-helena-en')`) still pass through, but the family ID + `voice`/`language` form above is the recommended path — it matches voice selection in the other speech providers. You can also pass additional provider-specific options using the `providerOptions` argument: ```ts {7-11} import { generateSpeech } from 'ai'; import { deepgram, type DeepgramSpeechModelOptions } from '@ai-sdk/deepgram'; const result = await generateSpeech({ model: deepgram.speech('aura-2'), voice: 'helena', text: 'Hello, world!', providerOptions: { deepgram: { encoding: 'linear16', sampleRate: 24000, } satisfies DeepgramSpeechModelOptions, }, }); ``` The following provider options are available: - **encoding** *string* Encoding type for the audio output. Supported values: `'linear16'`, `'mulaw'`, `'alaw'`, `'mp3'`, `'opus'`, `'flac'`, `'aac'`. Optional. - **container** *string* Container format for the output audio. Supported values: `'wav'`, `'ogg'`, `'none'`. Optional. - **sampleRate** *number* Sample rate for the output audio in Hz. Supported values depend on the encoding: `8000`, `16000`, `24000`, `32000`, `48000`. Optional. - **bitRate** *number | string* Bitrate of the audio in bits per second. For `mp3`: `32000` or `48000`. For `opus`: `4000` to `650000`. For `aac`: `4000` to `192000`. Optional. - **callback** *string* URL to which Deepgram will make a callback request with the audio. Optional. - **callbackMethod** *enum* HTTP method for the callback request. Allowed values: `'POST'`, `'PUT'`. Optional. - **mipOptOut** *boolean* Opts out requests from the Deepgram Model Improvement Program. Optional. - **tag** *string | array of strings* Label your requests for identification during usage reporting. Optional. Speech results include Deepgram response details in `providerMetadata.deepgram`: `modelName` (the resolved upstream model), `modelUuid`, `additionalModelUuids`, `charCount` (the billed character count), `breaksApplied`, `pronunciationsApplied`, `pronunciationWarnings` (when present), and `requestId`. The `generateSpeech` `speed` option is passed through to Deepgram's `speed` parameter. Deepgram accepts speeds in the 0.7–1.5 range and rejects out-of-range values with a 400 error; speed is not supported for all languages. `instructions` is not supported and is ignored with a warning. ### Model Capabilities | Family | Voices (selected via the `voice` option) | | -------- | --------------------------------------------------------------------------- | | `aura-2` | 41 English, 17 Spanish, 9 Dutch, 7 German, 10 Italian, 5 Japanese, 2 French | | `aura` | 12 English (Aura-1) | See the [full voice list](https://developers.deepgram.com/docs/tts-models) for all voice names and accents. ## Transcription Models You can create models that call the [Deepgram transcription API](https://developers.deepgram.com/docs/pre-recorded-audio) using the `.transcription()` factory method. The first argument is the model id e.g. `nova-3`. ```ts const model = deepgram.transcription('nova-3'); ``` You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the `summarize` option will enable summaries for sections of content. ```ts {11-15} import { transcribe } from 'ai'; import { deepgram, type DeepgramTranscriptionModelOptions, } from '@ai-sdk/deepgram'; import { readFile } from 'fs/promises'; const result = await transcribe({ model: deepgram.transcription('nova-3'), audio: await readFile('audio.mp3'), providerOptions: { deepgram: { summarize: 'v2', } satisfies DeepgramTranscriptionModelOptions, }, }); ``` The following provider options are available: - **language** *string* Language code for the audio. Supports numerous ISO-639-1 and ISO-639-3 language codes. Optional. - **detectLanguage** *boolean* Whether to enable automatic language detection. When true, Deepgram will detect the language of the audio. Optional. - **smartFormat** *boolean* Whether to apply smart formatting to the transcription. Optional. - **punctuate** *boolean* Whether to add punctuation to the transcription. Optional. - **summarize** *enum | boolean* Whether to generate a summary of the transcription. Allowed values: `'v2'`, `false`. Optional. - **topics** *boolean* Whether to detect topics in the transcription. Optional. - **detectEntities** *boolean* Whether to detect entities in the transcription. Optional. - **redact** *string | array of strings* Specifies what content to redact from the transcription. Optional. - **search** *string* Search term to find in the transcription. Optional. - **diarize** *boolean* Whether to identify different speakers in the transcription. Defaults to `false`. Note that Deepgram bills speaker diarization as a per-minute add-on. Optional. - **utterances** *boolean* Whether to segment the transcription into utterances. Optional. - **uttSplit** *number* Threshold for splitting utterances. Optional. - **fillerWords** *boolean* Whether to include filler words (um, uh, etc.) in the transcription. Optional. - **paragraphs** *boolean* Whether to format the transcription into paragraphs. Optional. - **intents** *boolean* Whether to detect intents in the transcription. Optional. - **sentiment** *boolean* Whether to analyze sentiment in the transcription. Optional. - **keyterm** *string* Key term to improve recognition accuracy for. Optional. - **replace** *string* String to replace redacted content with. Optional. ### Model Capabilities | Model | Transcription | Duration | Segments | Language | | -------------------------------------------------------------------------------------------------- | ------------- | -------- | -------- | -------- | | `nova-3` (+ [variants](https://developers.deepgram.com/docs/models-languages-overview#nova-3)) | ✓ | ✓ | ✓ | ✗ | | `nova-2` (+ [variants](https://developers.deepgram.com/docs/models-languages-overview#nova-2)) | ✓ | ✓ | ✓ | ✗ | | `nova` (+ [variants](https://developers.deepgram.com/docs/models-languages-overview#nova)) | ✓ | ✓ | ✓ | ✗ | | `enhanced` (+ [variants](https://developers.deepgram.com/docs/models-languages-overview#enhanced)) | ✓ | ✓ | ✓ | ✗ | | `base` (+ [variants](https://developers.deepgram.com/docs/models-languages-overview#base)) | ✓ | ✓ | ✓ | ✗ | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: DeepInfra description: Learn how to use DeepInfra with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/deepinfra" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [DeepInfra](https://deepinfra.com) provider contains support for state-of-the-art models through the DeepInfra API, including Llama 3, Mixtral, Qwen, and many other popular open-source models. Start with [AI Gateway](/providers/ai-sdk-providers/ai-gateway) for models in its catalog (recommended), or use `@ai-sdk/deepinfra` to connect directly to DeepInfra. ```ts provider="deepinfra" model="meta/llama-4-scout" import { generateText } from 'ai'; const { text } = await generateText({ model: "meta/llama-4-scout", prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { gateway: { order: ['deepinfra'], }, }, }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. The example sets `providerOptions.gateway.order` to `['deepinfra']`. This sends the request to DeepInfra first, then falls back to other available providers of the same model if DeepInfra is unavailable or the request fails. `order` sets a preference; it does not restrict fallback to DeepInfra. See [provider ordering](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). The dropdown lists models served by DeepInfra through Gateway with a corresponding direct-provider example. Provider availability depends on the model and your team's [Provider Allowlist](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). ## Using `@ai-sdk/deepinfra` For direct access to DeepInfra, install `ai` and `@ai-sdk/deepinfra`: ```bash pnpm add ai @ai-sdk/deepinfra ``` Set your DeepInfra API credential in the `DEEPINFRA_API_KEY` environment variable: ```bash export DEEPINFRA_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/deepinfra`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/deepinfra` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `deepInfra` from `@ai-sdk/deepinfra`: ```ts import { deepInfra } from '@ai-sdk/deepinfra'; ``` If you need a customized setup, you can import `createDeepInfra` from `@ai-sdk/deepinfra` and create a provider instance with your settings: ```ts import { createDeepInfra } from '@ai-sdk/deepinfra'; const deepInfra = createDeepInfra({ apiKey: process.env.DEEPINFRA_API_KEY ?? '', }); ``` You can use the following optional settings to customize the DeepInfra provider instance: - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use proxy servers. The default prefix is `https://api.deepinfra.com/v1`. Note: Language models and embeddings use OpenAI-compatible endpoints at `{baseURL}/openai`, while image models use `{baseURL}/inference`. - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `DEEPINFRA_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. ## Language Models You can create language models using a provider instance. The first argument is the model ID, for example: ```ts import { deepInfra } from '@ai-sdk/deepinfra'; import { generateText } from 'ai'; const { text } = await generateText({ model: deepInfra('meta-llama/Meta-Llama-3.1-70B-Instruct'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` DeepInfra language models can also be used in the `streamText` function (see [AI SDK Core](/docs/ai-sdk-core)). ## Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | | --------------------------------------------------- | ----------- | ----------------- | ---------- | -------------- | | `meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | ✓ | ✗ | ✗ | ✗ | | `meta-llama/Llama-4-Scout-17B-16E-Instruct` | ✓ | ✗ | ✗ | ✗ | | `meta-llama/Llama-3.3-70B-Instruct-Turbo` | ✗ | ✓ | ✓ | ✓ | | `meta-llama/Llama-3.3-70B-Instruct` | ✗ | ✓ | ✓ | ✓ | | `meta-llama/Meta-Llama-3.1-405B-Instruct` | ✗ | ✓ | ✓ | ✓ | | `meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo` | ✗ | ✓ | ✓ | ✓ | | `meta-llama/Meta-Llama-3.1-70B-Instruct` | ✗ | ✓ | ✓ | ✓ | | `meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo` | ✗ | ✓ | ✓ | ✗ | | `meta-llama/Meta-Llama-3.1-8B-Instruct` | ✗ | ✓ | ✓ | ✓ | | `meta-llama/Llama-3.2-11B-Vision-Instruct` | ✓ | ✓ | ✗ | ✗ | | `meta-llama/Llama-3.2-90B-Vision-Instruct` | ✓ | ✓ | ✗ | ✗ | | `mistralai/Mixtral-8x7B-Instruct-v0.1` | ✗ | ✓ | ✓ | ✗ | | `deepseek-ai/DeepSeek-V3` | ✗ | ✓ | ✓ | ✓ | | `deepseek-ai/DeepSeek-R1` | ✗ | ✗ | ✗ | ✗ | | `deepseek-ai/DeepSeek-R1-Distill-Llama-70B` | ✗ | ✗ | ✗ | ✗ | | `deepseek-ai/DeepSeek-R1-Turbo` | ✗ | ✗ | ✗ | ✗ | | `nvidia/Llama-3.1-Nemotron-70B-Instruct` | ✗ | ✓ | ✓ | ✗ | | `Qwen/Qwen2-7B-Instruct` | ✗ | ✓ | ✗ | ✗ | | `Qwen/Qwen2.5-72B-Instruct` | ✗ | ✓ | ✓ | ✓ | | `Qwen/Qwen2.5-Coder-32B-Instruct` | ✗ | ✓ | ✗ | ✗ | | `Qwen/QwQ-32B-Preview` | ✗ | ✓ | ✗ | ✗ | | `google/codegemma-7b-it` | ✗ | ✗ | ✗ | ✗ | | `google/gemma-2-9b-it` | ✗ | ✗ | ✗ | ✗ | | `microsoft/WizardLM-2-8x22B` | ✗ | ✗ | ✗ | ✗ | The table above lists popular models. Please see the [DeepInfra docs](https://deepinfra.com) for a full list of available models. You can also pass any available provider model ID as a string if needed. ## Image Models You can create DeepInfra image models using the `.image()` factory method. For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image). ```ts import { deepInfra, type DeepInfraImageModelOptions } from '@ai-sdk/deepinfra'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: deepInfra.image('stabilityai/sd3.5'), prompt: 'A futuristic cityscape at sunset', aspectRatio: '16:9', }); ``` Model support for `size` and `aspectRatio` parameters varies by model. Please check the individual model documentation on [DeepInfra's models page](https://deepinfra.com/models/text-to-image) for supported options and additional parameters. ### Model-specific options You can pass model-specific parameters using the `providerOptions.deepinfra` field: ```ts import { deepInfra } from '@ai-sdk/deepinfra'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: deepInfra.image('stabilityai/sd3.5'), prompt: 'A futuristic cityscape at sunset', aspectRatio: '16:9', providerOptions: { deepinfra: { num_inference_steps: 30, } satisfies DeepInfraImageModelOptions, }, }); ``` The following options are available via `providerOptions.deepinfra`. You can type them with `DeepInfraImageModelOptions`. - **negative\_prompt** *string* Text description of what to avoid in the generated image. - **num\_inference\_steps** *number* Number of denoising steps for models that expose this option. - **guidance\_scale** *number* Guidance scale for models that expose this option. - **guidance** *number* Guidance value for image edit models that expose this option. - **response\_format** *'b64\_json'* Format for OpenAI-compatible image responses. DeepInfra currently supports `b64_json`. - **quality** *string* OpenAI-compatible image quality option. - **style** *string* OpenAI-compatible image style option. - **user** *string* Unique identifier for your end user. DeepInfra image generation models expose model-specific parameters. Additional fields can be passed through `providerOptions.deepinfra` and are validated by DeepInfra. ### Image Editing DeepInfra supports image editing through models like `Qwen/Qwen-Image-Edit`. Pass input images via `prompt.images` to transform or edit existing images. #### Basic Image Editing Transform an existing image using text prompts: ```ts const imageBuffer = readFileSync('./input-image.png'); const { images } = await generateImage({ model: deepInfra.image('Qwen/Qwen-Image-Edit'), prompt: { text: 'Turn the cat into a golden retriever dog', images: [imageBuffer], }, size: '1024x1024', }); ``` #### Inpainting with Mask Edit specific parts of an image using a mask. Transparent areas in the mask indicate where the image should be edited: ```ts const image = readFileSync('./input-image.png'); const mask = readFileSync('./mask.png'); const { images } = await generateImage({ model: deepInfra.image('Qwen/Qwen-Image-Edit'), prompt: { text: 'A sunlit indoor lounge area with a pool containing a flamingo', images: [image], mask: mask, }, }); ``` #### Multi-Image Combining Combine multiple reference images into a single output: ```ts const cat = readFileSync('./cat.png'); const dog = readFileSync('./dog.png'); const { images } = await generateImage({ model: deepInfra.image('Qwen/Qwen-Image-Edit'), prompt: { text: 'Create a scene with both animals together, playing as friends', images: [cat, dog], }, }); ``` Input images can be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`, or base64-encoded strings. DeepInfra uses an OpenAI-compatible image editing API at `https://api.deepinfra.com/v1/openai/images/edits`. ### Model Capabilities For models supporting aspect ratios, the following ratios are typically supported: `1:1 (default), 16:9, 1:9, 3:2, 2:3, 4:5, 5:4, 9:16, 9:21` For models supporting size parameters, dimensions must typically be: - Multiples of 32 - Width and height between 256 and 1440 pixels - Default size is 1024x1024 | Model | Dimensions Specification | Notes | | -------------------------------------- | ------------------------ | -------------------------------------------------------- | | `stabilityai/sd3.5` | Aspect Ratio | Premium quality base model, 8B parameters | | `black-forest-labs/FLUX-1.1-pro` | Size | Latest state-of-art model with superior prompt following | | `black-forest-labs/FLUX-1-schnell` | Size | Fast generation in 1-4 steps | | `black-forest-labs/FLUX-1-dev` | Size | Optimized for anatomical accuracy | | `black-forest-labs/FLUX-pro` | Size | Flagship Flux model | | `black-forest-labs/FLUX.1-Kontext-dev` | Size | Image editing and transformation model | | `black-forest-labs/FLUX.1-Kontext-pro` | Size | Professional image editing and transformation | | `stabilityai/sd3.5-medium` | Aspect Ratio | Balanced 2.5B parameter model | | `stabilityai/sdxl-turbo` | Aspect Ratio | Optimized for fast generation | For more details and pricing information, see the [DeepInfra text-to-image models page](https://deepinfra.com/models/text-to-image). ## Embedding Models You can create DeepInfra embedding models using the `.embeddingModel()` factory method. For more on embedding models with the AI SDK see [embed()](/docs/reference/ai-sdk-core/embed). ```ts import { deepInfra } from '@ai-sdk/deepinfra'; import { embed } from 'ai'; const { embedding } = await embed({ model: deepInfra.embeddingModel('BAAI/bge-large-en-v1.5'), value: 'sunny day at the beach', }); ``` ### Model Capabilities | Model | Dimensions | Max Tokens | | ----------------------------------------------------- | ---------- | ---------- | | `BAAI/bge-base-en-v1.5` | 768 | 512 | | `BAAI/bge-large-en-v1.5` | 1024 | 512 | | `BAAI/bge-m3` | 1024 | 8192 | | `intfloat/e5-base-v2` | 768 | 512 | | `intfloat/e5-large-v2` | 1024 | 512 | | `intfloat/multilingual-e5-large` | 1024 | 512 | | `sentence-transformers/all-MiniLM-L12-v2` | 384 | 256 | | `sentence-transformers/all-MiniLM-L6-v2` | 384 | 256 | | `sentence-transformers/all-mpnet-base-v2` | 768 | 384 | | `sentence-transformers/clip-ViT-B-32` | 512 | 77 | | `sentence-transformers/clip-ViT-B-32-multilingual-v1` | 512 | 77 | | `sentence-transformers/multi-qa-mpnet-base-dot-v1` | 768 | 512 | | `sentence-transformers/paraphrase-MiniLM-L6-v2` | 384 | 128 | | `shibing624/text2vec-base-chinese` | 768 | 512 | | `thenlper/gte-base` | 768 | 512 | | `thenlper/gte-large` | 1024 | 512 | For a complete list of available embedding models, see the [DeepInfra embeddings page](https://deepinfra.com/models/embeddings). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: DeepSeek description: Learn how to use DeepSeek with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/deepseek" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [DeepSeek](https://www.deepseek.com) provider offers access to powerful language models through the DeepSeek API. You can use DeepSeek models through [AI Gateway](/providers/ai-sdk-providers/ai-gateway) (recommended) or connect directly with `@ai-sdk/deepseek`. ```ts provider="deepseek" model="deepseek/deepseek-v4-flash" import { generateText } from 'ai'; const { text } = await generateText({ model: "deepseek/deepseek-v4-flash", prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. Gateway feature and provider-option support depends on the model and the provider serving the request. See [Gateway provider options](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options). ## Using `@ai-sdk/deepseek` For direct access to DeepSeek, install `ai` and `@ai-sdk/deepseek`: ```bash pnpm add ai @ai-sdk/deepseek ``` Set your DeepSeek API credential in the `DEEPSEEK_API_KEY` environment variable: ```bash export DEEPSEEK_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/deepseek`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/deepseek` provider, including its model factories and settings. Gateway model and feature availability can differ. API keys can be obtained from the [DeepSeek Platform](https://platform.deepseek.com/api_keys). ## Provider Instance You can import the default provider instance `deepSeek` from `@ai-sdk/deepseek`: ```ts import { deepSeek } from '@ai-sdk/deepseek'; ``` For custom configuration, you can import `createDeepSeek` and create a provider instance with your settings: ```ts import { createDeepSeek } from '@ai-sdk/deepseek'; const deepSeek = createDeepSeek({ apiKey: process.env.DEEPSEEK_API_KEY ?? '', }); ``` You can use the following optional settings to customize the DeepSeek provider instance: - **baseURL** *string* Use a different URL prefix for API calls. The default prefix is `https://api.deepseek.com`. - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `DEEPSEEK_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. ## Language Models You can create language models using a provider instance: ```ts import { deepSeek } from '@ai-sdk/deepseek'; import { generateText } from 'ai'; const { text } = await generateText({ model: deepSeek('deepseek-v4-flash'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` You can also use the `.chat()` or `.languageModel()` factory methods: ```ts const model = deepSeek.chat('deepseek-v4-flash'); // or const model = deepSeek.languageModel('deepseek-v4-flash'); ``` DeepSeek language models can be used in the `streamText` function (see [AI SDK Core](/docs/ai-sdk-core)). DeepSeek retired the `deepseek-chat` and `deepseek-reasoner` aliases on July 24, 2026. Use `deepseek-flash` (the alias for the current V4.x Flash release), `deepseek-v4-flash`, or `deepseek-v4-pro` for the current API. Custom and legacy model IDs remain accepted as strings for compatibility with custom endpoints. The following optional provider options are available for DeepSeek models: - `logprobs` *boolean* Optional. Returns log probabilities for generated content and reasoning tokens in `providerMetadata.deepseek.logprobs`. - `topLogprobs` *number* Optional. Returns the specified number of most likely tokens at each token position. Accepts values from `0` through `20` and automatically enables `logprobs`. - `userId` *string* Optional. An opaque end-user identifier that DeepSeek uses for content-safety tracing, KV-cache isolation, and scheduling isolation. The value must match `^[a-zA-Z0-9_-]+$` and contain at most 512 characters. Do not include names, email addresses, or other private user information. - `thinking` *object* Optional. Controls thinking mode (chain-of-thought reasoning) for DeepSeek V4 models. - `type`: `'enabled' | 'disabled'` - Enable or disable thinking mode. See [DeepSeek's thinking mode docs](https://api-docs.deepseek.com/guides/thinking_mode). - `reasoningEffort` *'low' | 'high' | 'max'* Optional. Controls thinking strength for DeepSeek V4 reasoning models. When using the top-level `reasoning` setting, `minimal` is sent as `low`, `medium` is sent as `high`, `xhigh` is sent as `max`, and `max` is sent directly. A compatibility warning is returned whenever the requested value is mapped. For backwards compatibility, legacy provider options supplied at runtime are also mapped to documented values: `thinking.type: 'adaptive'` becomes `'enabled'`, `reasoningEffort: 'medium'` becomes `'high'`, and `reasoningEffort: 'xhigh'` becomes `'max'`. Each mapping returns a compatibility warning so callers can migrate to a canonical value. DeepSeek has deprecated the top-level `frequencyPenalty` and `presencePenalty` settings. The provider omits these settings and returns a deprecation warning when they are used. `temperature` and `topP` have no effect while thinking is enabled, including the default thinking mode for DeepSeek V4 models, so the provider omits them with an unsupported warning. Explicitly set `thinking.type` to `'disabled'` to use `temperature` and `topP`. ```ts {7-15} import { deepSeek, type DeepSeekLanguageModelChatOptions, } from '@ai-sdk/deepseek'; import { generateText } from 'ai'; const { text, reasoning } = await generateText({ model: deepSeek('deepseek-v4-flash'), prompt: 'How many "r"s are in the word "strawberry"?', providerOptions: { deepseek: { userId: 'tenant_123-user', thinking: { type: 'enabled' }, reasoningEffort: 'high', } satisfies DeepSeekLanguageModelChatOptions, }, }); ``` ### Message Names DeepSeek supports optional participant names on system, user, and assistant messages. Set `providerOptions.deepseek.name` on each message that should include a name: ```ts import { deepSeek, type DeepSeekMessageProviderOptions, } from '@ai-sdk/deepseek'; import { generateText } from 'ai'; const { text } = await generateText({ model: deepSeek('deepseek-flash'), instructions: { role: 'system', content: 'Help the customer plan a short trip.', providerOptions: { deepseek: { name: 'travel_planner', } satisfies DeepSeekMessageProviderOptions, }, }, messages: [ { role: 'user', content: 'I want to visit Lisbon for a weekend.', providerOptions: { deepseek: { name: 'customer', } satisfies DeepSeekMessageProviderOptions, }, }, { role: 'assistant', content: 'What kinds of activities do you enjoy?', providerOptions: { deepseek: { name: 'travel_planner', } satisfies DeepSeekMessageProviderOptions, }, }, { role: 'user', content: 'Food, architecture, and walking.', providerOptions: { deepseek: { name: 'customer', } satisfies DeepSeekMessageProviderOptions, }, }, ], }); ``` The same message option works with `streamText`: ```ts import { deepSeek, type DeepSeekMessageProviderOptions, } from '@ai-sdk/deepseek'; import { streamText } from 'ai'; const result = streamText({ model: deepSeek('deepseek-flash'), messages: [ { role: 'user', content: 'Suggest a name for my neighborhood book club.', providerOptions: { deepseek: { name: 'organizer', } satisfies DeepSeekMessageProviderOptions, }, }, ], }); for await (const textPart of result.textStream) { process.stdout.write(textPart); } ``` Names are omitted when the option is not set. The `name` value must be a string. DeepSeek does not support names on tool messages, so the provider ignores that placement and returns an unsupported-feature warning. Avoid including unnecessary personal or identifying information in message names. ### Reasoning DeepSeek V4 models support reasoning. The reasoning is exposed through streaming: ```ts import { deepSeek } from '@ai-sdk/deepseek'; import { streamText } from 'ai'; const result = streamText({ model: deepSeek('deepseek-v4-pro'), prompt: 'How many "r"s are in the word "strawberry"?', }); for await (const part of result.stream) { if (part.type === 'reasoning') { // This is the reasoning text console.log('Reasoning:', part.text); } else if (part.type === 'text') { // This is the final answer console.log('Answer:', part.text); } } ``` See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details on how to integrate reasoning into your chatbot. ### Chat Prefix Completion DeepSeek's beta [chat prefix completion](https://api-docs.deepseek.com/guides/chat_prefix_completion/) continues the content of a final assistant message. Create a provider with a beta base URL and set `prefix: true` in that assistant message's provider options: ```ts {8,20-26} import { createDeepSeek, type DeepSeekAssistantMessageProviderOptions, } from '@ai-sdk/deepseek'; import { generateText } from 'ai'; const deepSeek = createDeepSeek({ baseURL: 'https://api.deepseek.com/beta', }); const { text } = await generateText({ model: deepSeek('deepseek-v4-flash'), messages: [ { role: 'user', content: 'Write a short sentence about the color of the sky.', }, { role: 'assistant', content: 'The sky is', providerOptions: { deepseek: { prefix: true, } satisfies DeepSeekAssistantMessageProviderOptions, }, }, ], }); ``` The prefixed message must be an assistant message and the final message in the prompt. The configured `baseURL` must end in `/beta`, including when using a proxy. Invalid placement or a non-beta base URL causes the request to fail before it is sent. Chat prefix completion is a DeepSeek beta feature and its behavior may change. ### Strict Tool Calls DeepSeek's strict tool-call mode is a beta feature. Create the provider with a beta base URL and set `strict: true` on every function tool in the request: ```ts {5,14} import { createDeepSeek } from '@ai-sdk/deepseek'; import { generateText, tool } from 'ai'; import { z } from 'zod'; const deepSeek = createDeepSeek({ baseURL: 'https://api.deepseek.com/beta', }); const result = await generateText({ model: deepSeek('deepseek-flash'), prompt: 'What is the weather in San Francisco?', tools: { weather: tool({ description: 'Get the weather for a location.', inputSchema: z.object({ location: z.string() }), strict: true, execute: async ({ location }) => ({ location, temperature: 18 }), }), }, }); ``` Strict tools fail locally when the base URL does not end in `/beta`. When any function tool is strict, every function tool in the same request must set `strict: true`. ### Provider Metadata DeepSeek exposes the response system fingerprint and context cache usage through the `providerMetadata` property: ```ts import { deepSeek } from '@ai-sdk/deepseek'; import { generateText } from 'ai'; const result = await generateText({ model: deepSeek('deepseek-v4-flash'), prompt: 'Your prompt here', }); console.log(result.providerMetadata); // Example output: // { // deepseek: { // systemFingerprint: 'fp_eaab8d114b_prod0820_fp8_kvcache', // promptCacheHitTokens: 1856, // promptCacheMissTokens: 5, // }, // } ``` The metadata includes: - `systemFingerprint`: The backend configuration fingerprint for the response - `promptCacheHitTokens`: Number of input tokens that were cached - `promptCacheMissTokens`: Number of input tokens that were not cached For streamed responses, the latest non-null fingerprint from the response chunks is returned. For more details about DeepSeek's caching system, see the [DeepSeek caching documentation](https://api-docs.deepseek.com/guides/kv_cache#checking-cache-hit-status). ### Chat Response Metadata DeepSeek preserves provider-specific response fields in `providerMetadata.deepseek` for generated and streamed responses: - `responseObject`: `chat.completion` or `chat.completion.chunk` - `choiceIndex`: the selected response choice index - `messageRole`: the response message role, when supplied - `toolCallTypes`: the tool-call type for each returned call These fields remain in provider metadata because they are specific to DeepSeek's Chat Completions response rather than shared AI SDK result fields. ### File Uploads For inline images or image URLs, use file-part provider options to select the image processing detail. DeepSeek supports `low`, `high`, `original`, and `auto`: ```ts {2,13-17} import { deepSeek, type DeepSeekFilePartProviderOptions, } from '@ai-sdk/deepseek'; import { generateText } from 'ai'; const { text } = await generateText({ model: deepSeek('deepseek-v4-flash-vision-exp'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe this image.' }, { type: 'file', data: new URL('https://example.com/image.webp'), mediaType: 'image/webp', providerOptions: { deepseek: { imageDetail: 'low', } satisfies DeepSeekFilePartProviderOptions, }, }, ], }, ], }); ``` Set `fileData: true` on an inline image file part to use DeepSeek's `file_data` content-part representation. This preserves the file part's `filename`. `fileData` cannot be used with image URLs or `imageDetail`. DeepSeek accepts JPEG, PNG, GIF, and WebP image inputs. HTTP image URLs can be at most 8,192 characters. You can upload images using the [DeepSeek Files API](https://api-docs.deepseek.com/guides/files_api) and pass the returned provider reference to `deepseek-v4-flash-vision-exp`. This avoids sending the image bytes again in each request. ```ts import { deepSeek, type DeepSeekFilesOptions } from '@ai-sdk/deepseek'; import { generateText, uploadFile } from 'ai'; import { readFile } from 'node:fs/promises'; const { providerReference, mediaType } = await uploadFile({ api: deepSeek.files(), data: await readFile('./image.png'), filename: 'image.png', providerOptions: { deepseek: { expiresAfter: 3600, } satisfies DeepSeekFilesOptions, }, }); const { text } = await generateText({ model: deepSeek('deepseek-v4-flash-vision-exp'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe this image.' }, { type: 'file', mediaType: mediaType ?? 'image/png', data: providerReference, }, ], }, ], }); ``` The optional `expiresAfter` setting specifies the lifetime in seconds. DeepSeek accepts values from 3,600 seconds (one hour) through 2,592,000 seconds (30 days). Files are permanent when no expiration is specified. The provider requires a valid file ID in successful upload responses. It also validates returned `object` and `purpose` discriminators and numeric metadata. Other response metadata remains optional for compatibility with incomplete responses; omitted values are not included in `providerMetadata`, and an omitted response filename falls back to the filename supplied to `uploadFile`. DeepSeek file uploads support JPEG (`.jpg` and `.jpeg`), PNG, GIF, and WebP images. Each file can be at most 64 MiB, and filenames can contain at most 512 characters. The AI SDK validates these constraints before sending the upload request. The `image/jpg` media type alias is accepted, and a supported filename extension is used as a fallback when the media type is generic (for example, `application/octet-stream`). Recognizable non-image content is rejected even when its declared media type or filename indicates a supported image format. ## Model Capabilities | Model | Text Generation | Object Generation | Image Input | Tool Usage | Tool Streaming | | ------------------------------ | --------------- | ----------------- | ----------- | ---------- | -------------- | | `deepseek-flash` | ✓ | ✓ | ✗ | ✓ | ✓ | | `deepseek-v4-flash` | ✓ | ✓ | ✗ | ✓ | ✓ | | `deepseek-v4-pro` | ✓ | ✓ | ✗ | ✓ | ✓ | | `deepseek-v4-flash-vision-exp` | ✓ | ✓ | ✓ | ✓ | ✓ | Please see the [DeepSeek docs](https://api-docs.deepseek.com/quick_start/pricing) for a full list of available models. You can also pass any available provider model ID as a string if needed. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: ElevenLabs description: Learn how to set up the ElevenLabs provider and check AI Gateway availability. url: "https://ai-sdk.dev/providers/ai-sdk-providers/elevenlabs" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [ElevenLabs](https://elevenlabs.io/) provider contains language model support for the ElevenLabs transcription and speech generation APIs. ElevenLabs models are not currently available through [AI Gateway](https://vercel.com/ai-gateway/models), so the opening example uses `@ai-sdk/elevenlabs` directly. See [Using AI Gateway](#using-ai-gateway) below for supported alternatives. ```ts import { generateSpeech } from 'ai'; import { elevenLabs } from '@ai-sdk/elevenlabs'; import { writeFile } from 'node:fs/promises'; const { audio } = await generateSpeech({ model: elevenLabs.speech('eleven_multilingual_v2'), text: 'Hello, world!', outputFormat: 'mp3', voice: '21m00Tcm4TlvDq8ikWAM', }); await writeFile('speech.mp3', audio.uint8Array); ``` ## Using AI Gateway The current [AI Gateway model catalog](https://vercel.com/ai-gateway/models) does not list ElevenLabs models. Use `@ai-sdk/elevenlabs` for the APIs on this page. To use other speech models through Gateway, install `ai` and configure a Gateway API key: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [Gateway documentation](/providers/ai-sdk-providers/ai-gateway) for supported models and APIs. ## Using `@ai-sdk/elevenlabs` For direct access to ElevenLabs, install `ai` and `@ai-sdk/elevenlabs`: ```bash pnpm add ai @ai-sdk/elevenlabs ``` Set your ElevenLabs API credential in the `ELEVENLABS_API_KEY` environment variable: ```bash export ELEVENLABS_API_KEY=your_api_key_here ``` The following sections document the direct `@ai-sdk/elevenlabs` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `elevenLabs` from `@ai-sdk/elevenlabs`: ```ts import { elevenLabs } from '@ai-sdk/elevenlabs'; ``` If you need a customized setup, you can import `createElevenLabs` from `@ai-sdk/elevenlabs` and create a provider instance with your settings: ```ts import { createElevenLabs } from '@ai-sdk/elevenlabs'; const elevenLabs = createElevenLabs({ // custom settings, e.g. fetch: customFetch, }); ``` You can use the following optional settings to customize the ElevenLabs provider instance: - **apiKey** *string* API key that is being sent using the `xi-api-key` header. It defaults to the `ELEVENLABS_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. - **webSocket** *WebSocketConstructor* Custom WebSocket implementation for realtime transcription. A header-capable implementation is required in runtimes whose native WebSocket constructor cannot send the `xi-api-key` authentication header. ## Speech Models You can create models that call the [ElevenLabs speech API](https://elevenlabs.io/text-to-speech) using the `.speech()` factory method. The first argument is the model id e.g. `eleven_multilingual_v2`. ```ts const model = elevenLabs.speech('eleven_multilingual_v2'); ``` The `voice` argument can be set to a voice ID from the [ElevenLabs Voice Library](https://elevenlabs.io/app/voice-library). You can find voice IDs by selecting a voice in the library and copying its ID. ```ts {6} import { generateSpeech } from 'ai'; import { elevenLabs } from '@ai-sdk/elevenlabs'; const result = await generateSpeech({ model: elevenLabs.speech('eleven_multilingual_v2'), text: 'Hello, world!', voice: '21m00Tcm4TlvDq8ikWAM', // Rachel voice }); ``` You can also pass additional provider-specific options using the `providerOptions` argument: ```ts {7-9} import { generateSpeech } from 'ai'; import { elevenLabs, type ElevenLabsSpeechModelOptions, } from '@ai-sdk/elevenlabs'; const result = await generateSpeech({ model: elevenLabs.speech('eleven_multilingual_v2'), text: 'Hello, world!', voice: '21m00Tcm4TlvDq8ikWAM', providerOptions: { elevenlabs: { voiceSettings: { stability: 0.5, similarityBoost: 0.75, }, } satisfies ElevenLabsSpeechModelOptions, }, }); ``` - **languageCode** *string or null*\ Optional. Language code (ISO 639-1) used to enforce a language for the model. Currently, only Turbo v2.5 and Flash v2.5 support language enforcement. For other models, providing a language code will result in an error. - **voiceSettings** *object or null*\ Optional. Voice settings that override stored settings for the given voice. These are applied only to the current request. - **stability** *double or null*\ Optional. Determines how stable the voice is and the randomness between each generation. Lower values introduce broader emotional range; higher values result in a more monotonous voice. - **useSpeakerBoost** *boolean or null*\ Optional. Boosts similarity to the original speaker. Increases computational load and latency. - **similarityBoost** *double or null*\ Optional. Controls how closely the AI should adhere to the original voice. - **style** *double or null*\ Optional. Amplifies the style of the original speaker. May increase latency if set above 0. - **pronunciationDictionaryLocators** *array of objects or null*\ Optional. A list of pronunciation dictionary locators to apply to the text, in order. Up to 3 locators per request.\ Each locator object: - **pronunciationDictionaryId** *string* (required)\ The ID of the pronunciation dictionary. - **versionId** *string or null* (optional)\ The version ID of the dictionary. If not provided, the latest version is used. - **seed** *integer or null*\ Optional. If specified, the system will attempt to sample deterministically. Must be between 0 and 4294967295. Determinism is not guaranteed. - **previousText** *string or null*\ Optional. The text that came before the current request's text. Can improve continuity when concatenating generations or influence current generation continuity. - **nextText** *string or null*\ Optional. The text that comes after the current request's text. Can improve continuity when concatenating generations or influence current generation continuity. - **previousRequestIds** *array of strings or null*\ Optional. List of request IDs for samples generated before this one. Improves continuity when splitting large tasks. Max 3 IDs. If both `previousText` and `previousRequestIds` are sent, `previousText` is ignored. - **nextRequestIds** *array of strings or null*\ Optional. List of request IDs for samples generated after this one. Useful for maintaining continuity when regenerating a sample. Max 3 IDs. If both `nextText` and `nextRequestIds` are sent, `nextText` is ignored. - **applyTextNormalization** *enum*\ Optional. Controls text normalization.\ Allowed values: `'auto'` (default), `'on'`, `'off'`. - `'auto'`: System decides whether to apply normalization (e.g., spelling out numbers). - `'on'`: Always apply normalization. - `'off'`: Never apply normalization.\ For `eleven_turbo_v2_5` and `eleven_flash_v2_5`, can only be enabled with Enterprise plans. - **applyLanguageTextNormalization** *boolean*\ Optional. Defaults to `false`. Controls language text normalization, which helps with proper pronunciation in some supported languages (currently only Japanese). May significantly increase latency. - **enableLogging** *boolean*\ Optional. Whether to enable request logging for this API call. Defaults to the account-level setting. ### Model Capabilities | Model | Instructions | | ------------------------ | ------------ | | `eleven_v3` | ✓ | | `eleven_multilingual_v2` | ✓ | | `eleven_flash_v2_5` | ✓ | | `eleven_flash_v2` | ✓ | | `eleven_turbo_v2_5` | ✓ | | `eleven_turbo_v2` | ✓ | | `eleven_monolingual_v1` | ✓ | | `eleven_multilingual_v1` | ✓ | ## Transcription Models You can create models that call the [ElevenLabs transcription API](https://elevenlabs.io/speech-to-text) using the `.transcription()` factory method. The first argument is the model id e.g. `scribe_v2`. ```ts const model = elevenLabs.transcription('scribe_v2'); ``` You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the input language in ISO-639-1 (e.g. `en`) format can sometimes improve transcription performance if known beforehand. ```ts {10-14} import { transcribe } from 'ai'; import { elevenLabs, type ElevenLabsTranscriptionModelOptions, } from '@ai-sdk/elevenlabs'; const result = await transcribe({ model: elevenLabs.transcription('scribe_v2'), audio: new Uint8Array([1, 2, 3, 4]), providerOptions: { elevenlabs: { languageCode: 'en', } satisfies ElevenLabsTranscriptionModelOptions, }, }); ``` The following provider options are available: - **languageCode** *string* An ISO-639-1 or ISO-639-3 language code corresponding to the language of the audio file. Can sometimes improve transcription performance if known beforehand. Defaults to `null`, in which case the language is predicted automatically. - **tagAudioEvents** *boolean* Whether to tag audio events like (laughter), (footsteps), etc. in the transcription. Defaults to `true`. - **numSpeakers** *integer* The maximum amount of speakers talking in the uploaded file. Can help with predicting who speaks when. The maximum amount of speakers that can be predicted is 32. Defaults to `null`, in which case the amount of speakers is set to the maximum value the model supports. - **timestampsGranularity** *enum* The granularity of the timestamps in the transcription. Defaults to `'word'`. Allowed values: `'none'`, `'word'`, `'character'`. - **diarize** *boolean* Whether to annotate which speaker is currently talking in the uploaded file. Defaults to `true`. - **fileFormat** *enum* The format of input audio. Defaults to `'other'`. Allowed values: `'pcm_s16le_16'`, `'other'`. For `'pcm_s16le_16'`, the input audio must be 16-bit PCM at a 16kHz sample rate, single channel (mono), and little-endian byte order. Latency will be lower than with passing an encoded waveform. ### Model Capabilities | Model | Transcription | Duration | Segments | Language | | ------------------------ | ------------- | -------- | -------- | -------- | | `scribe_v1` | ✓ | ✓ | ✓ | ✓ | | `scribe_v1_experimental` | ✓ | ✓ | ✓ | ✓ | | `scribe_v2` | ✓ | ✓ | ✓ | ✓ | ## Streaming Transcription Models Streaming transcription is an experimental feature. Scribe v2 Realtime supports live transcription through `experimental_streamTranscribe`. It accepts raw PCM audio at 8, 16, 22.05, 24, 44.1, or 48 kHz, or 8 kHz mu-law audio. ```ts import { createElevenLabs, type ElevenLabsProviderSettings, } from '@ai-sdk/elevenlabs'; import { experimental_streamTranscribe as streamTranscribe } from 'ai'; import { WebSocket } from 'ws'; const elevenLabs = createElevenLabs({ webSocket: WebSocket as unknown as ElevenLabsProviderSettings['webSocket'], }); const result = streamTranscribe({ model: elevenLabs.transcription('scribe_v2_realtime'), audio, inputAudioFormat: { type: 'audio/pcm', rate: 16000 }, providerOptions: { elevenlabs: { languageCode: 'en', streaming: { includeLanguageDetection: true, includeTimestamps: true, }, }, }, }); for await (const part of result.fullStream) { if (part.type === 'transcript-partial') { console.log('partial:', part.text); } if (part.type === 'transcript-final') { console.log('final:', part.text); } } ``` The ElevenLabs realtime endpoint authenticates with a WebSocket header. Use a header-capable WebSocket implementation, such as `ws`, in runtimes whose native WebSocket constructor cannot send headers. The `streaming` provider options mirror ElevenLabs' realtime transcription settings: `commitStrategy`, `enableLogging`, `filterBackgroundAudio`, `includeLanguageDetection`, `includeTimestamps`, `keyterms`, `minSilenceDurationMs`, `minSpeechDurationMs`, `noVerbatim`, `previousText`, `secondaryLanguages`, `vadSilenceThresholdSecs`, and `vadThreshold`. ElevenLabs delivers detected language on its timestamp-bearing committed event. When `includeLanguageDetection` is enabled, the provider requests that event internally, but only returns timestamp segments when `includeTimestamps` is also enabled. `filterBackgroundAudio` cannot be combined with either option. See [Transcription](/docs/ai-sdk-core/transcription) for more information about streaming transcription. ### Model Capabilities | Model | Streaming Transcription | Timestamps | Language Detection | | -------------------- | ----------------------- | ---------- | ------------------ | | `scribe_v2_realtime` | ✓ | ✓ | ✓ | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Fal description: Learn how to use Fal with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/fal" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [Fal AI](https://fal.ai/) provides a generative media platform for developers with lightning-fast inference capabilities. Their platform offers optimized performance for running diffusion models, with speeds up to 4x faster than alternatives. Use [AI Gateway](/providers/ai-sdk-providers/ai-gateway) for the shared models shown below, served by Gateway's supported providers, or use `@ai-sdk/fal` to send requests directly to Fal. Gateway does not currently route requests to Fal. ```ts provider="fal" model="bfl/flux-pro-1.1" import { generateImage } from 'ai'; const { image } = await generateImage({ model: "bfl/flux-pro-1.1", prompt: 'A sunlit mountain village in watercolor.', }); console.log(image.base64); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. The current [Gateway provider list](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options#available-providers) does not include Fal as an inference provider. The Gateway tab calls models also available from Fal, but Gateway routes them through its supported inference providers. It does not send the request to Fal. Use the direct tab to call Fal; do not set `providerOptions.gateway.order` to an unsupported provider slug. ## Using `@ai-sdk/fal` For direct access to Fal, install `ai` and `@ai-sdk/fal`: ```bash pnpm add ai @ai-sdk/fal ``` Set your Fal API credential in the `FAL_API_KEY` environment variable: ```bash export FAL_API_KEY=your_api_key_here ``` Use `@ai-sdk/fal` to authenticate with your Fal account and call its platform APIs directly. The Gateway alternative above uses a supported inference provider instead. The following sections document the direct `@ai-sdk/fal` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `fal` from `@ai-sdk/fal`: ```ts import { fal } from '@ai-sdk/fal'; ``` If you need a customized setup, you can import `createFal` and create a provider instance with your settings: ```ts import { createFal } from '@ai-sdk/fal'; const fal = createFal({ apiKey: 'your-api-key', // optional, defaults to FAL_API_KEY environment variable, falling back to FAL_KEY baseURL: 'custom-url', // optional headers: { /* custom headers */ }, // optional }); ``` You can use the following optional settings to customize the Fal provider instance: - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use proxy servers. The default prefix is `https://fal.run`. - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `FAL_API_KEY` environment variable, falling back to `FAL_KEY`. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. ## Image Models You can create Fal image models using the `.image()` factory method. For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image). ### Basic Usage ```ts import { fal } from '@ai-sdk/fal'; import { generateImage } from 'ai'; import fs from 'fs'; const { image, providerMetadata } = await generateImage({ model: fal.image('fal-ai/flux/dev'), prompt: 'A serene mountain landscape at sunset', }); const filename = `image-${Date.now()}.png`; fs.writeFileSync(filename, image.uint8Array); console.log(`Image saved to ${filename}`); ``` Fal image models may return additional information for the images and the request. Here are some examples of properties that may be set for each image ```js providerMetadata.fal.images[0].nsfw; // boolean, image is not safe for work providerMetadata.fal.images[0].width; // number, image width providerMetadata.fal.images[0].height; // number, image height providerMetadata.fal.images[0].contentType; // string, mime type of the image ``` ### Model Capabilities Fal offers many models optimized for different use cases. Here are a few popular examples. For a full list of models, see the [Fal AI Search Page](https://fal.ai/explore/search). | Model | Description | | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | | `fal-ai/flux/dev` | FLUX.1 \[dev] model for high-quality image generation | | `fal-ai/flux-pro/kontext` | FLUX.1 Kontext \[pro] handles both text and reference images as inputs, enabling targeted edits and complex transformations | | `fal-ai/flux-pro/kontext/max` | FLUX.1 Kontext \[max] with improved prompt adherence and typography generation | | `fal-ai/flux-lora` | Super fast endpoint for FLUX.1 with LoRA support | | `fal-ai/ideogram/character` | Generate consistent character appearances across multiple images. Maintain facial features, proportions, and distinctive traits | | `fal-ai/qwen-image` | Qwen-Image foundation model with significant advances in complex text rendering and precise image editing | | `fal-ai/omnigen-v2` | Unified image generation model for Image Editing, Personalized Image Generation, Virtual Try-On, Multi Person Generation and more | | `fal-ai/bytedance/dreamina/v3.1/text-to-image` | Dreamina showcases superior picture effects with improvements in aesthetics, precise and diverse styles, and rich details | | `fal-ai/recraft/v3/text-to-image` | SOTA in image generation with vector art and brand style capabilities | | `fal-ai/wan/v2.2-a14b/text-to-image` | High-resolution, photorealistic images with fine-grained detail | Fal models support the following aspect ratios: - 1:1 (square HD) - 16:9 (landscape) - 9:16 (portrait) - 4:3 (landscape) - 3:4 (portrait) - 16:10 (1280x800) - 10:16 (800x1280) - 21:9 (2560x1080) - 9:21 (1080x2560) Key features of Fal models include: - Up to 4x faster inference speeds compared to alternatives - Optimized by the Fal Inference Engine™ - Support for real-time infrastructure - Cost-effective scaling with pay-per-use pricing - LoRA training capabilities for model personalization #### Modify Image Transform existing images using text prompts. ```ts await generateImage({ model: fal.image('fal-ai/flux-pro/kontext/max'), prompt: { text: 'Put a donut next to the flour.', images: [ 'https://v3.fal.media/files/rabbit/rmgBxhwGYb2d3pl3x9sKf_output.png', ], }, }); ``` Images can also be passed as base64-encoded string, a `Uint8Array`, an `ArrayBuffer`, or a `Buffer`. A mask can be passed as well ```ts await generateImage({ model: fal.image('fal-ai/flux-general/inpainting'), prompt: { text: 'Put a donut next to the flour.', images: [imageBuffer], mask: maskBuffer, }, }); ``` ### Provider Options Fal image models support flexible provider options through the `providerOptions.fal` object. You can pass any parameters supported by the specific Fal model's API. Common options include: - **imageUrl** - Reference image URL for image-to-image generation (deprecated, use `prompt.images` instead) - **strength** - Controls how much the output differs from the input image - **guidanceScale** - Controls adherence to the prompt (range: 1-20) - **numInferenceSteps** - Number of denoising steps (range: 1-50) - **enableSafetyChecker** - Enable/disable safety filtering - **outputFormat** - Output format: 'jpeg' or 'png' - **syncMode** - Wait for completion before returning response - **acceleration** - Speed of generation: 'none', 'regular', or 'high' - **safetyTolerance** - Content safety filtering level (1-6, where 1 is strictest) - **useMultipleImages** - When true, converts multiple input images to `image_urls` array for models that support multiple images (e.g., fal-ai/flux-2/edit) **Deprecation Notice**: snake\_case parameter names (e.g., `image_url`, `guidance_scale`) are deprecated and will be removed in a future version. Please use camelCase names (e.g., `imageUrl`, `guidanceScale`) instead. Refer to the [Fal AI model documentation](https://fal.ai/models) for model-specific parameters. ### Advanced Features Fal's platform offers several advanced capabilities: - **Private Model Inference**: Run your own diffusion transformer models with up to 50% faster inference - **LoRA Training**: Train and personalize models in under 5 minutes - **Real-time Infrastructure**: Enable new user experiences with fast inference times - **Scalable Architecture**: Scale to thousands of GPUs when needed For more details about Fal's capabilities and features, visit the [Fal AI documentation](https://fal.ai/docs). ## Transcription Models You can create models that call the [Fal transcription API](https://docs.fal.ai/guides/convert-speech-to-text) using the `.transcription()` factory method. The first argument is the model id without the `fal-ai/` prefix e.g. `wizper`. ```ts const model = fal.transcription('wizper'); ``` You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the `batchSize` option will increase the number of audio chunks processed in parallel. ```ts {6} import { transcribe } from 'ai'; import { fal, type FalTranscriptionModelOptions } from '@ai-sdk/fal'; import { readFile } from 'fs/promises'; const result = await transcribe({ model: fal.transcription('wizper'), audio: await readFile('audio.mp3'), providerOptions: { fal: { batchSize: 10 } satisfies FalTranscriptionModelOptions, }, }); ``` The following provider options are available: - **language** *string | null* Language of the audio file. Defaults to 'en'. If set to null, the language will be automatically detected. Accepts ISO language codes like 'en', 'fr', 'zh', etc. Optional. - **diarize** *boolean* Whether to diarize the audio file (identify different speakers). Defaults to true. Optional. - **chunkLevel** *string* Level of the chunks to return. Either 'segment' or 'word'. Default value: "segment" Optional. - **version** *string* Version of the model to use. All models are Whisper large variants. Default value: "3" Optional. - **batchSize** *number* Batch size for processing. Default value: 64 Optional. - **numSpeakers** *number* Number of speakers in the audio file. If not provided, the number of speakers will be automatically detected. Optional. ### Model Capabilities | Model | Transcription | Duration | Segments | Language | | --------- | ------------- | -------- | -------- | -------- | | `whisper` | ✓ | ✓ | ✓ | ✓ | | `wizper` | ✓ | ✓ | ✓ | ✓ | ## Speech Models You can create models that call Fal text-to-speech endpoints using the `.speech()` factory method. ### Basic Usage ```ts import { generateSpeech } from 'ai'; import { fal } from '@ai-sdk/fal'; const result = await generateSpeech({ model: fal.speech('fal-ai/minimax/speech-02-hd'), text: 'Hello from the AI SDK!', }); ``` ### Model Capabilities | Model | Description | | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `fal-ai/minimax/voice-clone` | Clone a voice from a sample audio and generate speech from text prompts | | `fal-ai/minimax/voice-design` | Design a personalized voice from a text description and generate speech from text prompts | | `fal-ai/dia-tts/voice-clone` | Clone dialog voices from a sample audio and generate dialogs from text prompts | | `fal-ai/minimax/speech-02-hd` | Generate speech from text prompts and different voices | | `fal-ai/minimax/speech-02-turbo` | Generate fast speech from text prompts and different voices | | `fal-ai/dia-tts` | Directly generates realistic dialogue from transcripts with audio conditioning for emotion control. Produces natural nonverbals like laughter and throat clearing | | `resemble-ai/chatterboxhd/text-to-speech` | Generate expressive, natural speech with Resemble AI's Chatterbox. Features unique emotion control, instant voice cloning from short audio, and built-in watermarking | ### Provider Options Pass provider-specific options via `providerOptions.fal` depending on the model: - **voice\_setting** *object* - `voice_id` (string): predefined voice ID - `speed` (number): 0.5–2.0 - `vol` (number): 0–10 - `pitch` (number): -12–12 - `emotion` (enum): happy | sad | angry | fearful | disgusted | surprised | neutral - `english_normalization` (boolean) - **audio\_setting** *object* Audio configuration settings specific to the model. - **language\_boost** *enum* Chinese | Chinese,Yue | English | Arabic | Russian | Spanish | French | Portuguese | German | Turkish | Dutch | Ukrainian | Vietnamese | Indonesian | Japanese | Italian | Korean | Thai | Polish | Romanian | Greek | Czech | Finnish | Hindi | auto - **pronunciation\_dict** *object* Custom pronunciation dictionary for specific words. Model-specific parameters (e.g., `audio_url`, `prompt`, `preview_text`, `ref_audio_url`, `ref_text`) can be passed directly under `providerOptions.fal` and will be forwarded to the Fal API. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Fireworks description: Learn how to use Fireworks with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/fireworks" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). [Fireworks](https://fireworks.ai/) is a platform for running and testing LLMs through their [API](https://readme.fireworks.ai/). Start with [AI Gateway](/providers/ai-sdk-providers/ai-gateway) for models in its catalog (recommended), or use `@ai-sdk/fireworks` to connect directly to Fireworks. ```ts provider="fireworks" model="openai/gpt-oss-120b" import { generateText } from 'ai'; const { text } = await generateText({ model: "openai/gpt-oss-120b", prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { gateway: { order: ['fireworks'], }, }, }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. The example sets `providerOptions.gateway.order` to `['fireworks']`. This sends the request to Fireworks first, then falls back to other available providers of the same model if Fireworks is unavailable or the request fails. `order` sets a preference; it does not restrict fallback to Fireworks. See [provider ordering](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). The dropdown lists models served by Fireworks through Gateway with a corresponding direct-provider example. Provider availability depends on the model and your team's [Provider Allowlist](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). ## Using `@ai-sdk/fireworks` For direct access to Fireworks, install `ai` and `@ai-sdk/fireworks`: ```bash pnpm add ai @ai-sdk/fireworks ``` Set your Fireworks API credential in the `FIREWORKS_API_KEY` environment variable: ```bash export FIREWORKS_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/fireworks`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/fireworks` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `fireworks` from `@ai-sdk/fireworks`: ```ts import { fireworks } from '@ai-sdk/fireworks'; ``` If you need a customized setup, you can import `createFireworks` from `@ai-sdk/fireworks` and create a provider instance with your settings: ```ts import { createFireworks } from '@ai-sdk/fireworks'; const fireworks = createFireworks({ apiKey: process.env.FIREWORKS_API_KEY ?? '', }); ``` You can use the following optional settings to customize the Fireworks provider instance: - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use proxy servers. The default prefix is `https://api.fireworks.ai/inference/v1`. - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `FIREWORKS_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. ## Language Models You can create [Fireworks models](https://fireworks.ai/models) using a provider instance. The first argument is the model id, e.g. `accounts/fireworks/models/firefunction-v1`: ```ts const model = fireworks('accounts/fireworks/models/firefunction-v1'); ``` ### Reasoning Models For models that support reasoning effort, use the top-level `reasoning` parameter. Fireworks maps `'minimal'` to `'low'` and `'xhigh'` or `'max'` to `'high'`, returning a compatibility warning whenever it coerces the effort. Streaming calls include the warning in the `stream-start` event. Explicit `providerOptions.fireworks.reasoningEffort` takes precedence over the top-level setting. Fireworks exposes the thinking of `deepseek-r1` in the generated text using the `` tag. You can use the `extractReasoningMiddleware` to extract this reasoning and expose it as a `reasoning` property on the result: ```ts import { fireworks } from '@ai-sdk/fireworks'; import { wrapLanguageModel, extractReasoningMiddleware } from 'ai'; const enhancedModel = wrapLanguageModel({ model: fireworks('accounts/fireworks/models/deepseek-r1'), middleware: extractReasoningMiddleware({ tagName: 'think' }), }); ``` You can then use that enhanced model in functions like `generateText` and `streamText`. ### Example You can use Fireworks language models to generate text with the `generateText` function: ```ts import { fireworks } from '@ai-sdk/fireworks'; import { generateText } from 'ai'; const { text } = await generateText({ model: fireworks('accounts/fireworks/models/firefunction-v1'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` Fireworks language models can also be used in the `streamText` function (see [AI SDK Core](/docs/ai-sdk-core)). ### Provider Options Fireworks chat models support additional provider options that are not part of the [standard call settings](/docs/ai-sdk-core/settings). You can pass them in the `providerOptions` argument: ```ts import { fireworks, type FireworksLanguageModelOptions, } from '@ai-sdk/fireworks'; import { generateText } from 'ai'; const { text, reasoningText } = await generateText({ model: fireworks('accounts/fireworks/models/kimi-k2p6'), providerOptions: { fireworks: { thinking: { type: 'enabled', budgetTokens: 4096 }, reasoningHistory: 'interleaved', } satisfies FireworksLanguageModelOptions, }, prompt: 'How many "r"s are in the word "strawberry"?', }); ``` The following optional provider options are available for Fireworks chat models: - **promptCacheKey** *string* A stable, opaque key that improves prompt cache hit rates by routing requests with shared prompt prefixes to the same Fireworks replica. Reuse the same key for all steps and calls in one conversation, and use different keys for unrelated conversations. - **serviceTier** *'priority'* Uses the Fireworks Priority serving path for higher reliability during peak traffic. The provider sends it as Fireworks' `service_tier` request field. - **thinking** *object* Configuration for thinking/reasoning models like Kimi K2.6. - **type** *'enabled' | 'disabled'* Whether to enable thinking mode. - **budgetTokens** *number* Maximum number of tokens for thinking (minimum 1024). - **reasoningHistory** *'disabled' | 'interleaved' | 'preserved'* Controls how reasoning history is handled in multi-turn conversations: - `'disabled'`: Remove reasoning from history - `'interleaved'`: Include reasoning between tool calls within a single turn - `'preserved'`: Keep all reasoning in history ### Prompt Cache Affinity [Fireworks prompt caching](https://docs.fireworks.ai/guides/prompt-caching) is automatic, but cached prefixes are local to a replica. Set `promptCacheKey` to an opaque session or conversation identifier to improve cache affinity. The provider sends it as Fireworks' [`prompt_cache_key`](https://docs.fireworks.ai/api-reference/post-chatcompletions) request field. The AI SDK reuses the same provider options for every model step in a multi-step `generateText` or `streamText` call, so one key covers the complete tool loop: ```ts import { fireworks, type FireworksLanguageModelOptions, } from '@ai-sdk/fireworks'; import { generateText, isStepCount, tool } from 'ai'; import { z } from 'zod'; const sessionId = 'conversation-123'; const result = await generateText({ model: fireworks('accounts/fireworks/models/kimi-k2p6'), providerOptions: { fireworks: { promptCacheKey: sessionId, } satisfies FireworksLanguageModelOptions, }, tools: { weather: tool({ description: 'Get the weather for a city.', inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => `It is sunny in ${city}.`, }), }, stopWhen: isStepCount(5), prompt: 'What is the weather in San Francisco?', }); console.log(result.usage.inputTokenDetails.cacheReadTokens); ``` For a reusable `ToolLoopAgent`, use call options to supply a key for each conversation. Pass the same key again for later agent calls that continue that conversation: ```ts import { fireworks, type FireworksLanguageModelOptions, } from '@ai-sdk/fireworks'; import { ToolLoopAgent } from 'ai'; import { z } from 'zod'; import { weatherTool } from './weather-tool'; const agent = new ToolLoopAgent({ model: fireworks('accounts/fireworks/models/kimi-k2p6'), callOptionsSchema: z.object({ sessionId: z.string(), }), prepareCall: ({ options, ...settings }) => ({ ...settings, providerOptions: { fireworks: { promptCacheKey: options.sessionId, } satisfies FireworksLanguageModelOptions, }, }), tools: { weather: weatherTool }, }); const result = await agent.generate({ prompt: 'What is the weather in San Francisco?', options: { sessionId: 'conversation-123', }, }); ``` Use non-identifying values for cache keys rather than email addresses or other personal information. ### Priority Service Tier [Fireworks Priority tier](https://docs.fireworks.ai/serverless/serving-paths) prioritizes supported models above Standard traffic. Set `serviceTier` to `'priority'` to send Fireworks' `service_tier` request field: ```ts import { fireworks, type FireworksLanguageModelOptions, } from '@ai-sdk/fireworks'; import { generateText } from 'ai'; const result = await generateText({ model: fireworks('accounts/fireworks/models/glm-5p2'), providerOptions: { fireworks: { serviceTier: 'priority', } satisfies FireworksLanguageModelOptions, }, prompt: 'Write a haiku about reliable inference.', }); ``` ### Completion Models You can create models that call the Fireworks completions API using the `.completionModel()` factory method: ```ts const model = fireworks.completionModel( 'accounts/fireworks/models/firefunction-v1', ); ``` ### Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | | ---------------------------------------------------------- | ----------- | ----------------- | ---------- | -------------- | | `accounts/fireworks/models/firefunction-v1` | ✗ | ✓ | ✓ | ✓ | | `accounts/fireworks/models/deepseek-r1` | ✗ | ✓ | ✗ | ✗ | | `accounts/fireworks/models/deepseek-v3` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/llama-v3p1-405b-instruct` | ✗ | ✓ | ✓ | ✓ | | `accounts/fireworks/models/llama-v3p1-8b-instruct` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/llama-v3p2-3b-instruct` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/llama-v3p3-70b-instruct` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/mixtral-8x7b-instruct` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/mixtral-8x7b-instruct-hf` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/mixtral-8x22b-instruct` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/qwen2p5-coder-32b-instruct` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/qwen2p5-72b-instruct` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/qwen-qwq-32b-preview` | ✗ | ✓ | ✗ | ✗ | | `accounts/fireworks/models/qwen2-vl-72b-instruct` | ✓ | ✓ | ✗ | ✗ | | `accounts/fireworks/models/llama-v3p2-11b-vision-instruct` | ✓ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/qwq-32b` | ✗ | ✓ | ✗ | ✗ | | `accounts/fireworks/models/yi-large` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/kimi-k2-instruct` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/kimi-k2-thinking` | ✗ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/kimi-k2p6` | ✓ | ✓ | ✓ | ✗ | | `accounts/fireworks/models/minimax-m2` | ✗ | ✓ | ✓ | ✗ | The table above lists popular models. Please see the [Fireworks models page](https://fireworks.ai/models) for a full list of available models. ## Embedding Models You can create models that call the Fireworks embeddings API using the `.embeddingModel()` factory method: ```ts const model = fireworks.embeddingModel('nomic-ai/nomic-embed-text-v1.5'); ``` You can use Fireworks embedding models to generate embeddings with the `embed` function: ```ts import { fireworks } from '@ai-sdk/fireworks'; import { embed } from 'ai'; const { embedding } = await embed({ model: fireworks.embeddingModel('nomic-ai/nomic-embed-text-v1.5'), value: 'sunny day at the beach', }); ``` ### Model Capabilities | Model | Dimensions | Max Tokens | | -------------------------------- | ---------- | ---------- | | `nomic-ai/nomic-embed-text-v1.5` | 768 | 8192 | For more embedding models, see the [Fireworks models page](https://fireworks.ai/models) for a full list of available models. ## Image Models You can create Fireworks image models using the `.image()` factory method. For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image). ```ts import { fireworks, type FireworksImageModelOptions } from '@ai-sdk/fireworks'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: fireworks.image('accounts/fireworks/models/flux-1-dev-fp8'), prompt: 'A futuristic cityscape at sunset', aspectRatio: '16:9', providerOptions: { fireworks: { guidance_scale: 4.5, num_inference_steps: 8, } satisfies FireworksImageModelOptions, }, }); ``` Model support for `size` and `aspectRatio` parameters varies. See the [Model Capabilities](#model-capabilities-1) section below for supported dimensions, or check the model's documentation on [Fireworks models page](https://fireworks.ai/models) for more details. ### Provider Options Fireworks image models support flexible provider options through the `providerOptions.fireworks` object. Use the `FireworksImageModelOptions` type to validate known Fireworks image options while still allowing model-specific options to pass through. The following typed provider options are available: - **guidance\_scale** *number* Classifier-free guidance scale for the image diffusion process. - **num\_inference\_steps** *number* Number of denoising steps for FLUX workflow image generation. - **output\_format** *'jpeg' | 'png'* Desired output format for FLUX Kontext image generation and editing. - **prompt\_upsampling** *boolean* Whether Fireworks should automatically modify the prompt for more creative generation. - **safety\_tolerance** *number* Moderation tolerance level for inputs and outputs, from `0` to `6`. Fireworks limits image-to-image requests to `2`. - **webhook\_url** *string* URL to receive webhook notifications for async Kontext requests. - **webhook\_secret** *string* Secret for webhook signature verification. - **cfg\_scale** *number* Guidance scale for legacy `image_generation` models. - **steps** *number* Number of generation steps for legacy `image_generation` models. ### Image Editing Fireworks supports image editing through FLUX Kontext models (`flux-kontext-pro` and `flux-kontext-max`). Pass input images via `prompt.images` to transform or edit existing images. Fireworks Kontext models do not support explicit masks. Editing is prompt-driven — describe what you want to change in the text prompt. #### Basic Image Editing Transform an existing image using text prompts: ```ts const imageBuffer = readFileSync('./input-image.png'); const { images } = await generateImage({ model: fireworks.image('accounts/fireworks/models/flux-kontext-pro'), prompt: { text: 'Turn the cat into a golden retriever dog', images: [imageBuffer], }, providerOptions: { fireworks: { output_format: 'jpeg', safety_tolerance: 2, } satisfies FireworksImageModelOptions, }, }); ``` #### Style Transfer Apply artistic styles to an image: ```ts const imageBuffer = readFileSync('./input-image.png'); const { images } = await generateImage({ model: fireworks.image('accounts/fireworks/models/flux-kontext-pro'), prompt: { text: 'Transform this into a watercolor painting style', images: [imageBuffer], }, aspectRatio: '1:1', }); ``` Input images can be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`, or base64-encoded strings. Fireworks only supports a single input image per request. ### Model Capabilities For all models supporting aspect ratios, the following aspect ratios are supported: `1:1 (default), 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9` For all models supporting size, the following sizes are supported: `640 x 1536, 768 x 1344, 832 x 1216, 896 x 1152, 1024x1024 (default), 1152 x 896, 1216 x 832, 1344 x 768, 1536 x 640` | Model | Dimensions Specification | Image Editing | | ------------------------------------------------------------ | ------------------------ | ------------- | | `accounts/fireworks/models/flux-kontext-pro` | Aspect Ratio | ✓ | | `accounts/fireworks/models/flux-kontext-max` | Aspect Ratio | ✓ | | `accounts/fireworks/models/flux-1-dev-fp8` | Aspect Ratio | ✗ | | `accounts/fireworks/models/flux-1-schnell-fp8` | Aspect Ratio | ✗ | | `accounts/fireworks/models/playground-v2-5-1024px-aesthetic` | Size | ✗ | | `accounts/fireworks/models/japanese-stable-diffusion-xl` | Size | ✗ | | `accounts/fireworks/models/playground-v2-1024px-aesthetic` | Size | ✗ | | `accounts/fireworks/models/SSD-1B` | Size | ✗ | | `accounts/fireworks/models/stable-diffusion-xl-1024-v1-0` | Size | ✗ | For more details, see the [Fireworks models page](https://fireworks.ai/models). #### Stability AI Models Fireworks also presents several Stability AI models backed by Stability AI API keys and endpoint. The AI SDK Fireworks provider does not currently include support for these models: | Model ID | | -------------------------------------- | | `accounts/stability/models/sd3-turbo` | | `accounts/stability/models/sd3-medium` | | `accounts/stability/models/sd3` | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Fish Audio description: Learn how to use Fish Audio with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/fish-audio" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [Fish Audio](https://fish.audio/) provider contains speech generation (S1 and S2 models) and speech-to-text transcription support. You can use Fish Audio models through [AI Gateway](/providers/ai-sdk-providers/ai-gateway) (recommended) or connect directly with `@ai-sdk/fish-audio`. ```ts provider="fish-audio" model="fish-audio/s2.1-pro" import { generateSpeech } from 'ai'; import { writeFile } from 'node:fs/promises'; const { audio } = await generateSpeech({ model: "fish-audio/s2.1-pro", text: 'Hello, world!', outputFormat: 'mp3', }); await writeFile('speech.mp3', audio.uint8Array); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. Gateway feature and provider-option support depends on the model and the provider serving the request. See [Gateway provider options](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options). ## Using `@ai-sdk/fish-audio` For direct access to Fish Audio, install `ai` and `@ai-sdk/fish-audio`: ```bash pnpm add ai @ai-sdk/fish-audio ``` Set your Fish Audio API credential in the `FISH_AUDIO_API_KEY` environment variable: ```bash export FISH_AUDIO_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/fish-audio`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/fish-audio` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `fishAudio` from `@ai-sdk/fish-audio`: ```ts import { fishAudio } from '@ai-sdk/fish-audio'; ``` If you need a customized setup, you can import `createFishAudio` from `@ai-sdk/fish-audio` and create a provider instance with your settings: ```ts import { createFishAudio } from '@ai-sdk/fish-audio'; const fishAudio = createFishAudio({ // custom settings, e.g. fetch: customFetch, }); ``` You can use the following optional settings to customize the Fish Audio provider instance: - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `FISH_AUDIO_API_KEY` environment variable. - **baseURL** *string* Base URL for the API calls. Defaults to `https://api.fish.audio`. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. ## Speech Models You can create models that call the [Fish Audio text-to-speech API](https://docs.fish.audio/api-reference/endpoint/openapi-v1/text-to-speech) using the `.speech()` factory method. ```ts import { fishAudio } from '@ai-sdk/fish-audio'; import { generateSpeech } from 'ai'; const { audio } = await generateSpeech({ model: fishAudio.speech('s1'), text: 'Hello from Fish Audio!', }); ``` The `voice` option selects a Fish Audio voice model ID (`reference_id`), either from the [Fish Audio voice library](https://fish.audio/) or one of your own uploaded models. Omit it to use the default voice. ```ts const { audio } = await generateSpeech({ model: fishAudio.speech('s1'), text: 'Hello from Fish Audio!', voice: '933563129e564b19a115bedd57b7406a', outputFormat: 'opus', speed: 1.1, }); ``` Voice listing is not part of the AI SDK speech model specification, so browse voices with the [Fish Audio list-models endpoint](https://docs.fish.audio/api-reference/endpoint/openapi-v1/list-models) directly: ```ts const response = await fetch( 'https://api.fish.audio/model?page_size=20&sort_by=task_count', { headers: { Authorization: `Bearer ${process.env.FISH_AUDIO_API_KEY}` } }, ); const { items } = await response.json(); // Each item's `_id` is a value you can pass as `voice`. ``` Add `self=true` to list only your own uploaded models, or `language=en` / `tag=narration` to filter. ### Provider Options The following provider options are available: - **referenceId** *string | array of strings* Voice model ID(s). A single ID selects one speaker; an array enables multi-speaker dialogue (S2-Pro models). Takes precedence over the top-level `voice` option. Optional. - **sampleRate** *number* Output sample rate in Hz. Falls back to the format default when unset (44100 Hz for `wav`/`pcm`/`mp3`, 48000 Hz for `opus`). Optional. - **mp3Bitrate** *64 | 128 | 192* Bitrate in kbps for `mp3` output. Ignored for other formats. Optional. - **opusBitrate** *-1000 | 24000 | 32000 | 48000 | 64000* Bitrate in bps for `opus` output, where `-1000` selects automatic. Ignored for other formats. Optional. - **latency** *'low' | 'normal' | 'balanced'* Latency/quality tradeoff. `normal` gives the best quality, `balanced` reduces latency, and `low` is the fastest. Optional. - **volume** *number* Volume offset in dB. Negative values are quieter. Optional. - **normalizeLoudness** *boolean* Loudness normalization. Supported by the S2 family (`s2-pro` and `s2.1-pro`). Fish Audio accepts it on `s1` but ignores it, so the provider drops it and emits a warning in that case. Optional. - **temperature** *number* Governs expressiveness (0 to 1). Higher values are more varied. Optional. - **topP** *number* Controls diversity via nucleus sampling (0 to 1). Optional. - **chunkLength** *number* Text segment size for processing (100 to 300). Optional. - **minChunkLength** *number* Minimum characters before splitting into a new chunk (0 to 100). Optional. - **normalize** *boolean* Text normalization for English and Chinese. Helps stability with numbers. Optional. - **maxNewTokens** *number* Maximum audio tokens to generate per text chunk. Optional. - **repetitionPenalty** *number* Values above 1.0 discourage repeated audio patterns. Optional. - **conditionOnPreviousChunks** *boolean* Reuse prior audio as context for voice consistency across chunks. Optional. - **earlyStopThreshold** *number* Early-stop threshold used in batch processing (0 to 1). Optional. - **features** *array of strings* Request-scoped flags passed through to the inference backend, e.g. `['quality-guard']`. Optional. ### Multi-Speaker Dialogue S2-Pro models support multi-speaker dialogue. Pass an array of voice model IDs via `referenceId` and mark turns in the text with `<|speaker:N|>`, where `N` indexes into that array. ```ts const { audio } = await generateSpeech({ model: fishAudio.speech('s2-pro'), text: '<|speaker:0|>Hello!<|speaker:1|>Hi there!', providerOptions: { fishAudio: { referenceId: [ '933563129e564b19a115bedd57b7406a', 'bf322df2096a46f18c579d0baa36f41d', ], }, }, }); ``` ### Output Formats Fish Audio supports the `wav`, `pcm`, `mp3`, and `opus` output formats. Any other value falls back to `mp3` and produces a warning. Fish Audio infers the language from the input text and the selected voice, and has no language parameter. The `language` and `instructions` options are not supported and produce warnings. ### Model Capabilities | Model | Multi-Speaker | Notes | | --------------- | ------------- | ------------------------------------------------------------------------- | | `s1` | | Ignores `normalizeLoudness` | | `s2-pro` | ✓ | Supports `normalizeLoudness` | | `s2.1-pro` | ✓ | Recommended default; supports `normalizeLoudness` | | `s2.1-pro-free` | | Free developer tier; no time-to-first-audio or data-processing guarantees | Streaming text-to-speech (Fish Audio's TTS-live WebSocket and timestamped streaming endpoints) is not currently supported, and neither is inline zero-shot voice cloning via `references`, which requires a MessagePack request body. Upload reference audio to Fish Audio and pass its `reference_id` via `voice` or `referenceId` instead. ## Transcription Models You can create models that call the [Fish Audio speech-to-text API](https://docs.fish.audio/api-reference/endpoint/openapi-v1/speech-to-text) using the `.transcription()` factory method. ```ts import { fishAudio } from '@ai-sdk/fish-audio'; import { transcribe } from 'ai'; import { readFile } from 'node:fs/promises'; const result = await transcribe({ model: fishAudio.transcription(), audio: await readFile('audio.mp3'), }); ``` The Fish Audio speech-to-text endpoint currently exposes no model selector and serves a single model, so the model ID is optional and defaults to `'transcribe-1'`. It is a routing label and is not sent to the API. Fish Audio expects to add more ASR models and to select them with the `model` HTTP header, matching the text-to-speech endpoint. ### Provider Options The following provider options are available: - **language** *string* Language of the audio. A hint only: Fish Audio passes it to the model, but auto-detection is authoritative and overrides it, so it changes neither the transcript nor the reported language. Optional. - **ignoreTimestamps** *boolean* Whether to skip precise timestamps. Mirrors the Fish Audio `ignore_timestamps` parameter, whose API default is `true`. This provider defaults it to `false` so that `segments` is populated. Fish Audio documents an added latency cost for audio shorter than 30 seconds; set this to `true` to trade segments for that latency. Optional. ```ts const result = await transcribe({ model: fishAudio.transcription(), audio: await readFile('audio.mp3'), providerOptions: { fishAudio: { language: 'en', ignoreTimestamps: false, }, }, }); ``` `result.language` reports the detected language as an ISO-639-1 code (e.g. `en`). It is always a two-letter code, never a locale such as `en-US`, and is `undefined` when Fish Audio detects no language. The human-readable language name (e.g. `English`) is available as provider metadata: ```ts console.log(result.language); // 'en' console.log(result.providerMetadata?.fishAudio?.language); // 'English' ``` The provider metadata `language` is a display name intended for presentation. Its exact form is not guaranteed, so avoid matching or branching on it — use `result.language` for anything programmatic. Setting `ignoreTimestamps` to `true` makes Fish Audio return an empty `segments` array. The provider therefore requests timestamps by default. ### Model Capabilities | Model | Transcription | Duration | Segments | Language | | -------------- | ------------- | -------- | -------- | -------- | | `transcribe-1` | ✓ | ✓ | ✓ | ✓ | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Gladia description: Learn how to set up the Gladia provider and check AI Gateway availability. url: "https://ai-sdk.dev/providers/ai-sdk-providers/gladia" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [Gladia](https://gladia.io/) provider contains language model support for the Gladia transcription API. Gladia models are not currently available through [AI Gateway](https://vercel.com/ai-gateway/models), so the opening example uses `@ai-sdk/gladia` directly. See [Using AI Gateway](#using-ai-gateway) below for supported alternatives. ```ts import { transcribe } from 'ai'; import { gladia } from '@ai-sdk/gladia'; import { readFile } from 'node:fs/promises'; const { text } = await transcribe({ model: gladia.transcription(), audio: await readFile('audio.mp3'), }); console.log(text); ``` The example transcribes an `audio.mp3` file from your project. ## Using AI Gateway The current [AI Gateway model catalog](https://vercel.com/ai-gateway/models) does not list Gladia models. Use `@ai-sdk/gladia` for the APIs on this page. To use other transcription models through Gateway, install `ai` and configure a Gateway API key: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [Gateway documentation](/providers/ai-sdk-providers/ai-gateway) for supported models and APIs. ## Using `@ai-sdk/gladia` For direct access to Gladia, install `ai` and `@ai-sdk/gladia`: ```bash pnpm add ai @ai-sdk/gladia ``` Set your Gladia API credential in the `GLADIA_API_KEY` environment variable: ```bash export GLADIA_API_KEY=your_api_key_here ``` The following sections document the direct `@ai-sdk/gladia` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `gladia` from `@ai-sdk/gladia`: ```ts import { gladia } from '@ai-sdk/gladia'; ``` If you need a customized setup, you can import `createGladia` from `@ai-sdk/gladia` and create a provider instance with your settings: ```ts import { createGladia } from '@ai-sdk/gladia'; const gladia = createGladia({ // custom settings, e.g. fetch: customFetch, }); ``` You can use the following optional settings to customize the Gladia provider instance: - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `GLADIA_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. ## Transcription Models You can create models that call the [Gladia transcription API](https://docs.gladia.io/chapters/pre-recorded-stt/getting-started) using the `.transcription()` factory method. ```ts const model = gladia.transcription(); ``` You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the `summarization` option will enable summaries for sections of content. ```ts {7} import { transcribe } from 'ai'; import { gladia } from '@ai-sdk/gladia'; import { type GladiaTranscriptionModelOptions } from '@ai-sdk/gladia'; import { readFile } from 'fs/promises'; const result = await transcribe({ model: gladia.transcription(), audio: await readFile('audio.mp3'), providerOptions: { gladia: { summarization: true, } satisfies GladiaTranscriptionModelOptions, }, }); ``` Gladia does not have various models, so you can omit the standard `model` id parameter. The following provider options are available: - **contextPrompt** *string* Context to feed the transcription model with for possible better accuracy. Optional. - **customVocabulary** *boolean | any\[]* Custom vocabulary to improve transcription accuracy. Optional. - **customVocabularyConfig** *object* Configuration for custom vocabulary. Optional. - **vocabulary** *Array\* - **defaultIntensity** *number* - **detectLanguage** *boolean* Whether to automatically detect the language. Optional. - **enableCodeSwitching** *boolean* Enable code switching for multilingual audio. Optional. - **codeSwitchingConfig** *object* Configuration for code switching. Optional. - **languages** *string\[]* - **language** *string* Specify the language of the audio. Optional. - **callback** *boolean* Enable callback when transcription is complete. Optional. - **callbackConfig** *object* Configuration for callback. Optional. - **url** *string* - **method** *'POST' | 'PUT'* - **subtitles** *boolean* Generate subtitles from the transcription. Optional. - **subtitlesConfig** *object* Configuration for subtitles. Optional. - **formats** *Array\<'srt' | 'vtt'>* - **minimumDuration** *number* - **maximumDuration** *number* - **maximumCharactersPerRow** *number* - **maximumRowsPerCaption** *number* - **style** *'default' | 'compliance'* - **diarization** *boolean* Enable speaker diarization. Optional. - **diarizationConfig** *object* Configuration for diarization. Optional. - **numberOfSpeakers** *number* - **minSpeakers** *number* - **maxSpeakers** *number* - **enhanced** *boolean* - **translation** *boolean* Enable translation of the transcription. Optional. - **translationConfig** *object* Configuration for translation. Optional. - **targetLanguages** *string\[]* - **model** *'base' | 'enhanced'* - **matchOriginalUtterances** *boolean* - **summarization** *boolean* Enable summarization of the transcription. Optional. - **summarizationConfig** *object* Configuration for summarization. Optional. - **type** *'general' | 'bullet\_points' | 'concise'* - **moderation** *boolean* Enable content moderation. Optional. - **namedEntityRecognition** *boolean* Enable named entity recognition. Optional. - **chapterization** *boolean* Enable chapterization of the transcription. Optional. - **nameConsistency** *boolean* Enable name consistency in the transcription. Optional. - **customSpelling** *boolean* Enable custom spelling. Optional. - **customSpellingConfig** *object* Configuration for custom spelling. Optional. - **spellingDictionary** *Record\* - **structuredDataExtraction** *boolean* Enable structured data extraction. Optional. - **structuredDataExtractionConfig** *object* Configuration for structured data extraction. Optional. - **classes** *string\[]* - **sentimentAnalysis** *boolean* Enable sentiment analysis. Optional. - **audioToLlm** *boolean* Enable audio to LLM processing. Optional. - **audioToLlmConfig** *object* Configuration for audio to LLM. Optional. - **prompts** *string\[]* - **customMetadata** *Record\* Custom metadata to include with the request. Optional. - **sentences** *boolean* Enable sentence detection. Optional. - **displayMode** *boolean* Enable display mode. Optional. - **punctuationEnhanced** *boolean* Enable enhanced punctuation. Optional. ### Model Capabilities | Model | Transcription | Duration | Segments | Language | | --------- | ------------- | -------- | -------- | -------- | | `Default` | ✓ | ✓ | ✓ | ✓ | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: GMI Cloud description: Learn how to use GMI Cloud with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/gmicloud" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [GMI Cloud](https://www.gmicloud.ai) provider offers access to open-weight language models through GMI Cloud's GPU inference platform, over an OpenAI-compatible API. Start with [AI Gateway](/providers/ai-sdk-providers/ai-gateway) for models in its catalog (recommended), or use `@ai-sdk/gmicloud` to connect directly to GMI Cloud. ```ts provider="gmicloud" model="deepseek/deepseek-v4-flash-0731" import { generateText } from 'ai'; const { text } = await generateText({ model: "deepseek/deepseek-v4-flash-0731", prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { gateway: { order: ['gmicloud'], }, }, }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. The example sets `providerOptions.gateway.order` to `['gmicloud']`. This sends the request to GMI Cloud first, then falls back to other available providers of the same model if GMI Cloud is unavailable or the request fails. `order` sets a preference; it does not restrict fallback to GMI Cloud. See [provider ordering](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). The dropdown lists models served by GMI Cloud through Gateway with a corresponding direct-provider example. Provider availability depends on the model and your team's [Provider Allowlist](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). ## Using `@ai-sdk/gmicloud` For direct access to GMI Cloud, install `ai` and `@ai-sdk/gmicloud`: ```bash pnpm add ai @ai-sdk/gmicloud ``` Set your GMI Cloud API credential in the `GMI_CLOUD_APIKEY` environment variable: ```bash export GMI_CLOUD_APIKEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/gmicloud`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/gmicloud` provider, including its model factories and settings. Gateway model and feature availability can differ. API keys can be obtained from the [GMI Cloud console](https://console.gmicloud.ai). ## Provider Instance You can import the default provider instance `gmicloud` from `@ai-sdk/gmicloud`: ```ts import { gmicloud } from '@ai-sdk/gmicloud'; ``` For custom configuration, you can import `createGmicloud` and create a provider instance with your settings: ```ts import { createGmicloud } from '@ai-sdk/gmicloud'; const gmicloud = createGmicloud({ apiKey: process.env.GMI_CLOUD_APIKEY ?? '', }); ``` You can use the following optional settings to customize the GMI Cloud provider instance: - **baseURL** *string* Use a different URL prefix for API calls. The default prefix is `https://api.gmi-serving.com/v1`. - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `GMI_CLOUD_APIKEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *FetchFunction* Custom fetch implementation. ## Language Models ```ts import { gmicloud } from '@ai-sdk/gmicloud'; import { generateText } from 'ai'; const { text } = await generateText({ model: gmicloud('deepseek-ai/DeepSeek-V4-Flash-0731'), prompt: 'What is the capital of France?', }); ``` GMI Cloud serves an evolving catalog of open-weight models, so model ids are typed as `string`. Embedding and image models are not supported. ## Error diagnostics GMI Cloud's edge reports a generic banner in `error.message` on rejections and nests the backend engine's diagnostic in `error.details`. This provider unwraps the nested diagnostic, so `AI_APICallError.message` carries the engine's reason (e.g. `The request is invalid: Invalid max_tokens value, the valid range of max_tokens is [1, 393216].`) instead of `Backend request failed with status 400`. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Google Vertex AI description: Learn how to use Google Vertex AI with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/google-vertex" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The Google Vertex provider for the [AI SDK](/docs) contains language model support for the [Google Vertex AI](https://cloud.google.com/vertex-ai) APIs. This includes support for [Google's Gemini models](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models), [Anthropic's Claude partner models](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude), [xAI's Grok partner models](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/grok), and [MaaS (Model as a Service) open models](https://cloud.google.com/vertex-ai/generative-ai/docs/maas/use-open-models). Start with [AI Gateway](/providers/ai-sdk-providers/ai-gateway) for models in its catalog (recommended), or use `@ai-sdk/google-vertex` to connect directly to Google Vertex AI. ```ts provider="google-vertex" model="google/gemini-4-argon" import { generateText } from 'ai'; const { text } = await generateText({ model: "google/gemini-4-argon", prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { gateway: { order: ['vertex'], }, }, }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. The example sets `providerOptions.gateway.order` to `['vertex']`. This sends the request to Google Vertex AI first, then falls back to other available providers of the same model if Google Vertex AI is unavailable or the request fails. `order` sets a preference; it does not restrict fallback to Google Vertex AI. See [provider ordering](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). The dropdown lists models served by Google Vertex AI through Gateway with a corresponding direct-provider example. Provider availability depends on the model and your team's [Provider Allowlist](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). Gateway uses the `vertex` route for Gemini on Google Vertex AI and handles Google Cloud authentication. The example does not use your `GOOGLE_VERTEX_PROJECT`, location, or service-account credentials; use the direct provider to connect to your own project. ## Using `@ai-sdk/google-vertex` For direct access to Google Vertex AI, install `ai` and `@ai-sdk/google-vertex`: ```bash pnpm add ai @ai-sdk/google-vertex ``` Set your Google Cloud project, location, and the path to your service-account credentials file: ```bash export GOOGLE_VERTEX_PROJECT=your_project_id export GOOGLE_VERTEX_LOCATION=us-central1 export GOOGLE_APPLICATION_CREDENTIALS=/path/to/credentials.json ``` The Node.js provider uses Google Application Default Credentials. You can also authenticate locally with `gcloud auth application-default login`. See the runtime sections below for Edge and Express Mode authentication. Use the direct provider for Google Cloud authentication, Vertex deployments, partner models, or Vertex-specific APIs. Gateway requests do not use your project configuration. The following sections document the direct `@ai-sdk/google-vertex` provider, including its model factories and settings. Gateway model and feature availability can differ. The Google Vertex provider is compatible with both Node.js and Edge runtimes. The Edge runtime is supported through the `@ai-sdk/google-vertex/edge` sub-module. More details can be found in the [Google Vertex Edge Runtime](#google-vertex-edge-runtime), [Google Vertex Anthropic Edge Runtime](#google-vertex-anthropic-edge-runtime), and [Google Vertex MaaS Edge Runtime](#google-vertex-maas-edge-runtime) sections below. ## Google Vertex Provider Usage The Google Vertex provider instance is used to create model instances that call the Vertex AI API. The models available with this provider include [Google's Gemini models](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models). If you're looking to use [Anthropic's Claude models](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude), see the [Google Vertex Anthropic Provider](#google-vertex-anthropic-provider-usage) section below. Vertex Gemini models share the Google provider's forward-compatible model classification. Unrecognized `gemini-*` IDs and `-latest` aliases use the newest supported Gemini request behavior, while known legacy IDs keep their generation-specific tool, reasoning, function-response, and thought-signature handling. The selected Vertex model must still support each requested feature. ### Provider Instance You can import the default provider instance `googleVertex` from `@ai-sdk/google-vertex`: ```ts import { googleVertex } from '@ai-sdk/google-vertex'; ``` If you need a customized setup, you can import `createGoogleVertex` from `@ai-sdk/google-vertex` and create a provider instance with your settings: ```ts import { createGoogleVertex } from '@ai-sdk/google-vertex'; const googleVertex = createGoogleVertex({ project: 'my-project', // optional location: 'us-central1', // optional }); ``` Google Vertex supports multiple authentication methods depending on your runtime environment and requirements. #### Node.js Runtime The Node.js runtime is the default runtime supported by the AI SDK. It supports all standard Google Cloud authentication options through the [`google-auth-library`](https://github.com/googleapis/google-auth-library-nodejs?tab=readme-ov-file#ways-to-authenticate). Typical use involves setting a path to a json credentials file in the `GOOGLE_APPLICATION_CREDENTIALS` environment variable. The credentials file can be obtained from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials). If you want to customize the Google authentication options you can pass them as options to the `createGoogleVertex` function, for example: ```ts import { createGoogleVertex } from '@ai-sdk/google-vertex'; const googleVertex = createGoogleVertex({ googleAuthOptions: { credentials: { client_email: 'my-email', private_key: 'my-private-key', }, }, }); ``` ##### Optional Provider Settings You can use the following optional settings to customize the provider instance: - **project** *string* The Google Cloud project ID that you want to use for the API calls. It uses the `GOOGLE_VERTEX_PROJECT` environment variable by default. - **location** *string* The Google Cloud location that you want to use for the API calls, e.g. `us-central1`. It uses the `GOOGLE_VERTEX_LOCATION` environment variable by default. - **googleAuthOptions** *object* Optional. The Authentication options used by the [Google Auth Library](https://github.com/googleapis/google-auth-library-nodejs/). See also the [GoogleAuthOptions](https://github.com/googleapis/google-auth-library-nodejs/blob/08978822e1b7b5961f0e355df51d738e012be392/src/auth/googleauth.ts#L87C18-L87C35) interface. - **authClient** *object* An `AuthClient` to use. - **keyFilename** *string* Path to a .json, .pem, or .p12 key file. - **keyFile** *string* Path to a .json, .pem, or .p12 key file. - **credentials** *object* Object containing client\_email and private\_key properties, or the external account client options. - **clientOptions** *object* Options object passed to the constructor of the client. - **scopes** *string | string\[]* Required scopes for the desired API request. - **projectId** *string* Your project ID. - **universeDomain** *string* The default service domain for a given Cloud universe. - **headers** *Resolvable\>* Headers to include in the requests. Can be provided in multiple formats: - A record of header key-value pairs: `Record` - A function that returns headers: `() => Record` - An async function that returns headers: `async () => Record` - A promise that resolves to headers: `Promise>` - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. - **baseURL** *string* Optional. Base URL for the Google Vertex API calls e.g. to use proxy servers. By default, it is constructed using the location and project: `https://${location}-aiplatform.googleapis.com/v1/projects/${project}/locations/${location}/publishers/google` - **toolResultDownloads** *object* Settings for downloading remote files in tool results before sending them to Vertex as inline data. Supported Google Cloud Storage URLs are forwarded directly for Gemini 3 and later models. See [Tool Result Files](#tool-result-files) for the supported MIME types. - **maxBytes** *number* Maximum size in bytes for each downloaded file. Defaults to 7 MiB. #### Edge Runtime Edge runtimes (like Cloudflare Workers) are lightweight JavaScript environments that run closer to users at the network edge. They only provide a subset of the standard Node.js APIs. For example, direct file system access is not available, and many Node.js-specific libraries (including the standard Google Auth library) are not compatible. The Edge runtime version of the Google Vertex provider supports Google's [Application Default Credentials](https://github.com/googleapis/google-auth-library-nodejs?tab=readme-ov-file#application-default-credentials) through environment variables. The values can be obtained from a json credentials file from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials). You can import the default provider instance `googleVertex` from `@ai-sdk/google-vertex/edge`: ```ts import { googleVertex } from '@ai-sdk/google-vertex/edge'; ``` The `/edge` sub-module is included in the `@ai-sdk/google-vertex` package, so you don't need to install it separately. You must import from `@ai-sdk/google-vertex/edge` to differentiate it from the Node.js provider. If you need a customized setup, you can import `createGoogleVertex` from `@ai-sdk/google-vertex/edge` and create a provider instance with your settings: ```ts import { createGoogleVertex } from '@ai-sdk/google-vertex/edge'; const googleVertex = createGoogleVertex({ project: 'my-project', // optional location: 'us-central1', // optional }); ``` For Edge runtime authentication, you'll need to set these environment variables from your Google Default Application Credentials JSON file: - `GOOGLE_CLIENT_EMAIL` - `GOOGLE_PRIVATE_KEY` - `GOOGLE_PRIVATE_KEY_ID` (optional) These values can be obtained from a service account JSON file from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials). ##### Optional Provider Settings You can use the following optional settings to customize the provider instance: - **project** *string* The Google Cloud project ID that you want to use for the API calls. It uses the `GOOGLE_VERTEX_PROJECT` environment variable by default. - **location** *string* The Google Cloud location that you want to use for the API calls, e.g. `us-central1`. It uses the `GOOGLE_VERTEX_LOCATION` environment variable by default. - **googleCredentials** *object* Optional. The credentials used by the Edge provider for authentication. These credentials are typically set through environment variables and are derived from a service account JSON file. - **clientEmail** *string* The client email from the service account JSON file. Defaults to the contents of the `GOOGLE_CLIENT_EMAIL` environment variable. - **privateKey** *string* The private key from the service account JSON file. Defaults to the contents of the `GOOGLE_PRIVATE_KEY` environment variable. - **privateKeyId** *string* The private key ID from the service account JSON file (optional). Defaults to the contents of the `GOOGLE_PRIVATE_KEY_ID` environment variable. - **headers** *Resolvable\>* Headers to include in the requests. Can be provided in multiple formats: - A record of header key-value pairs: `Record` - A function that returns headers: `() => Record` - An async function that returns headers: `async () => Record` - A promise that resolves to headers: `Promise>` - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. #### Express Mode Express mode provides a simplified authentication method using an API key instead of OAuth or service account credentials. When using express mode, the `project` and `location` settings are not required. ```ts import { createGoogleVertex } from '@ai-sdk/google-vertex'; const googleVertex = createGoogleVertex({ apiKey: process.env.GOOGLE_VERTEX_API_KEY, }); ``` ##### Optional Provider Settings - **apiKey** *string* The API key for Google Vertex AI. When provided, the provider uses express mode with API key authentication instead of OAuth. It uses the `GOOGLE_VERTEX_API_KEY` environment variable by default. ### Language Models You can create models that call the Vertex API using the provider instance. The first argument is the model id, e.g. `gemini-3.8-flash`. ```ts const model = googleVertex('gemini-3.8-flash'); ``` If you are using [your own models](https://cloud.google.com/vertex-ai/docs/training-overview), the name of your model needs to start with `projects/`. Google Vertex models support also some model specific settings that are not part of the [standard call settings](/docs/ai-sdk-core/settings). You can pass them as an options argument: ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { type GoogleLanguageModelOptions } from '@ai-sdk/google'; const model = googleVertex('gemini-3.1-pro-preview'); await generateText({ model, providerOptions: { vertex: { safetySettings: [ { category: 'HARM_CATEGORY_UNSPECIFIED', threshold: 'BLOCK_LOW_AND_ABOVE', }, ], } satisfies GoogleLanguageModelOptions, }, }); ``` The following optional provider options are available for Google Vertex models: - **cachedContent** *string* Optional. The name of the cached content used as context to serve the prediction. Format: projects/\{project}/locations/\{location}/cachedContents/\{cachedContent} - **structuredOutputs** *boolean* Optional. Enable structured output. Default is true. This is useful when the JSON Schema contains elements that are not supported by the OpenAPI schema version that Google Vertex uses. You can use this to disable structured outputs if you need to. See [Troubleshooting: Schema Limitations](#schema-limitations) for more details. - **safetySettings** *Array\<\{ category: string; threshold: string }>* Optional. Safety settings for the model. - **category** *string* The category of the safety setting. Can be one of the following: - `HARM_CATEGORY_UNSPECIFIED` - `HARM_CATEGORY_HATE_SPEECH` - `HARM_CATEGORY_DANGEROUS_CONTENT` - `HARM_CATEGORY_HARASSMENT` - `HARM_CATEGORY_SEXUALLY_EXPLICIT` - `HARM_CATEGORY_CIVIC_INTEGRITY` - **threshold** *string* The threshold of the safety setting. Can be one of the following: - `HARM_BLOCK_THRESHOLD_UNSPECIFIED` - `BLOCK_LOW_AND_ABOVE` - `BLOCK_MEDIUM_AND_ABOVE` - `BLOCK_ONLY_HIGH` - `BLOCK_NONE` - **audioTimestamp** *boolean* Optional. Enables timestamp understanding for audio files. Defaults to false. This is useful for generating transcripts with accurate timestamps. Consult [Google's Documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/audio-understanding) for usage details. - **labels** *object* Optional. Defines labels used in billing reports. Consult [Google's Documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/add-labels-to-api-calls) for usage details. - **imageConfig** *object* Optional. Configuration for image generation. Only supported by Gemini image models. See [Google's GenerationConfig documentation](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/reference/rest/v1/GenerationConfig) for usage details. - **aspectRatio** *string* Optional. The aspect ratio of generated images. Defaults to 1:1 squares, or to matching the output image size to that of an input image. Can be one of the following: - 1:1 - 2:3 - 3:2 - 3:4 - 4:3 - 4:5 - 5:4 - 9:16 - 16:9 - 21:9 - **imageSize** *string* Optional. Controls the output image resolution. Defaults to 1K. Can be one of the following: - 1K - 2K - 4K - **personGeneration** *string* Optional. Controls the generation of people in images. Can be one of the following: - `PERSON_GENERATION_UNSPECIFIED` - `ALLOW_ALL` - `ALLOW_ADULT` - `ALLOW_NONE` - **prominentPeople** *string* Optional. Controls whether generation of prominent people (celebrities) is allowed. When set together with `personGeneration`, `personGeneration` takes precedence. Can be one of the following: - `PROMINENT_PEOPLE_UNSPECIFIED` - `ALLOW_PROMINENT_PEOPLE` - `BLOCK_PROMINENT_PEOPLE` - **imageOutputOptions** *\{ mimeType?: 'image/jpeg' | 'image/png', compressionQuality?: number }* Optional. The image output format for generated images. - **streamFunctionCallArguments** *boolean* Optional. When set to true, function call arguments will be streamed incrementally in streaming responses. This enables `tool-input-delta` events to arrive as the model generates function call arguments, reducing perceived latency for tool calls. Defaults to `false`. Only supported on the Vertex AI API (not the Gemini API) with Gemini 3+ models. Consult [Google's Documentation](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#streaming-fc) for details. - **sharedRequestType** *'priority' | 'flex' | 'standard'* Optional. Selects a pay-as-you-go (PayGo) tier by setting the `X-Vertex-AI-LLM-Shared-Request-Type` request header. Use `'priority'` for consistent low-latency performance at a premium, or `'flex'` for a 50% discount with longer expected latency. Both are supported only on the `global` endpoint and on a subset of Gemini models. By default — with Provisioned Throughput allocated and `requestType` unset — the request consumes Provisioned Throughput quota first and only falls back to the chosen shared tier if PT capacity is exhausted. To bypass Provisioned Throughput entirely, also set `requestType: 'shared'`. The served tier is reported back on `result.providerMetadata.googleVertex.usageMetadata.trafficType` as `ON_DEMAND_PRIORITY`, `ON_DEMAND_FLEX`, or (if downgraded under load) plain `ON_DEMAND`. See [Priority PayGo](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/priority-paygo) and [Flex PayGo](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/flex-paygo) for supported models, ramp limits, and downgrade behavior. - **requestType** *'shared'* Optional. Sets the `X-Vertex-AI-LLM-Request-Type` request header. Combine with `sharedRequestType` to skip Provisioned Throughput entirely and route the request through shared PayGo capacity. See [Priority PayGo](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/priority-paygo). You can use Google Vertex language models to generate text with the `generateText` function: ```ts {1,4} import { googleVertex } from '@ai-sdk/google-vertex'; import { generateText } from 'ai'; const { text } = await generateText({ model: googleVertex('gemini-3.1-pro-preview'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` Google Vertex language models can also be used in the `streamText` function (see [AI SDK Core](/docs/ai-sdk-core)). #### Tuned Models [Tuned models](https://cloud.google.com/vertex-ai/generative-ai/docs/deploy/overview) are served from a deployed endpoint and are addressed by their endpoint id rather than a published model name. Pass the endpoint resource with the `endpoints/` prefix and the provider will route the request to the deployed endpoint (`.../locations/{location}/endpoints/{ENDPOINT_ID}`) instead of the base model path (`.../publishers/google/models/{MODEL_ID}`): ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { generateText } from 'ai'; const { text } = await generateText({ model: googleVertex('endpoints/YOUR_ENDPOINT_ID'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` Use the deployed **endpoint** id, not the tuned model id (a tuned model has both a `models/...` resource and a separate `endpoints/...` resource; only the endpoint is callable for inference). The request and response format is identical to the base model, so all standard call settings and provider options continue to work. Tuned models require standard Google Cloud credentials and the project/location of the deployed endpoint (via `GOOGLE_VERTEX_PROJECT` / `GOOGLE_VERTEX_LOCATION` or `createGoogleVertex`). [Express Mode](#express-mode) API keys are not supported for tuned models. #### Code Execution With [Code Execution](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/code-execution), certain Gemini models on Vertex AI can generate and execute Python code. This allows the model to perform calculations, data manipulation, and other programmatic tasks to enhance its responses. You can enable code execution by adding the `code_execution` tool to your request. ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { generateText } from 'ai'; const result = await generateText({ model: googleVertex('gemini-3.1-pro-preview'), tools: { code_execution: googleVertex.tools.codeExecution({}) }, prompt: 'Use python to calculate 20th fibonacci number. Then find the nearest palindrome to it.', }); ``` The response will contain `tool-call` and `tool-result` parts for the executed code. #### URL Context URL Context allows Gemini models to retrieve and analyze content from URLs. Supported models: Gemini 2.0 and newer. ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { generateText } from 'ai'; const result = await generateText({ model: googleVertex('gemini-3.1-pro-preview'), tools: { url_context: googleVertex.tools.urlContext({}) }, prompt: 'What are the key points from https://example.com/article?', }); ``` #### Google Search Google Search enables Gemini models to access real-time web information. Supported models: Gemini 2.0 and newer. ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { generateText } from 'ai'; const result = await generateText({ model: googleVertex('gemini-3.1-pro-preview'), tools: { google_search: googleVertex.tools.googleSearch({}) }, prompt: 'What are the latest developments in AI?', }); ``` #### Enterprise Web Search [Enterprise Web Search](https://cloud.google.com/vertex-ai/generative-ai/docs/grounding/web-grounding-enterprise) provides grounding using a compliance-focused web index designed for highly-regulated industries such as finance, healthcare, and the public sector. Unlike standard Google Search grounding, Enterprise Web Search does not log customer data and supports VPC service controls. Supported models: Gemini 2.0 and newer. ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { generateText } from 'ai'; const result = await generateText({ model: googleVertex('gemini-3.8-flash'), tools: { enterprise_web_search: googleVertex.tools.enterpriseWebSearch({}), }, prompt: 'What are the latest FDA regulations for clinical trials?', }); ``` #### Google Maps Google Maps grounding enables Gemini models to access Google Maps data for location-aware responses. Supported models: Gemini 2.0 and newer. ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { type GoogleLanguageModelOptions } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: googleVertex('gemini-3.8-flash'), tools: { google_maps: googleVertex.tools.googleMaps({}), }, providerOptions: { vertex: { retrievalConfig: { latLng: { latitude: 34.090199, longitude: -117.881081 }, }, } satisfies GoogleLanguageModelOptions, }, prompt: 'What are the best Italian restaurants nearby?', }); ``` The optional `retrievalConfig.latLng` provider option provides location context for queries about nearby places. This configuration applies to any grounding tools that support location context. #### Streaming Function Call Arguments For Gemini 3 Pro and later models on Vertex AI, you can stream function call arguments as they are generated by setting `streamFunctionCallArguments` to `true`. This reduces perceived latency when functions need to be called, as `tool-input-delta` events arrive incrementally instead of waiting for the complete arguments. This option defaults to `false`. ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { type GoogleLanguageModelOptions } from '@ai-sdk/google'; import { streamText } from 'ai'; import { z } from 'zod'; const result = streamText({ model: googleVertex('gemini-3.1-pro-preview'), prompt: 'What is the weather in Boston and San Francisco?', tools: { getWeather: { description: 'Get the current weather in a given location', inputSchema: z.object({ location: z.string().describe('City name'), }), }, }, providerOptions: { vertex: { streamFunctionCallArguments: true, } satisfies GoogleLanguageModelOptions, }, }); for await (const part of result.stream) { switch (part.type) { case 'tool-input-start': console.log(`Tool call started: ${part.toolName}`); break; case 'tool-input-delta': process.stdout.write(part.delta); break; case 'tool-call': console.log(`Tool call complete: ${part.toolName}`, part.input); break; } } ``` This feature is only available on the Vertex AI API. It is not supported on the Gemini API. When used with the Google provider, a warning will be emitted and the option will be ignored. #### Reasoning (Thinking Tokens) Google Vertex AI, through its support for Gemini models, can also emit "thinking" tokens, representing the model's reasoning process. The AI SDK exposes these as reasoning information. To enable thinking tokens for compatible Gemini models via Vertex, set `includeThoughts: true` in the `thinkingConfig` provider option. These options are passed through `providerOptions.vertex`: ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { type GoogleLanguageModelOptions } from '@ai-sdk/google'; import { generateText, streamText } from 'ai'; // For generateText: const { text, reasoningText, reasoning } = await generateText({ model: googleVertex('gemini-3.8-flash'), // Or other supported model via Vertex providerOptions: { vertex: { thinkingConfig: { includeThoughts: true, // thinkingLevel: 'high', // Optional }, } satisfies GoogleLanguageModelOptions, }, prompt: 'Explain quantum computing in simple terms.', }); console.log('Reasoning:', reasoningText); console.log('Reasoning Details:', reasoning); console.log('Final Text:', text); // For streamText: const result = streamText({ model: googleVertex('gemini-3.8-flash'), // Or other supported model via Vertex providerOptions: { vertex: { thinkingConfig: { includeThoughts: true, // thinkingLevel: 'high', // Optional }, } satisfies GoogleLanguageModelOptions, }, prompt: 'Explain quantum computing in simple terms.', }); for await (const part of result.stream) { if (part.type === 'reasoning') { process.stdout.write(`THOUGHT: ${part.textDelta}\n`); } else if (part.type === 'text-delta') { process.stdout.write(part.textDelta); } } ``` When `includeThoughts` is true, parts of the API response marked with `thought: true` will be processed as reasoning. - In `generateText`, these contribute to the `reasoningText` (string) and `reasoning` (array) fields. - In `streamText`, these are emitted as `reasoning` stream parts. Refer to the [Google Vertex AI documentation on "thinking"](https://cloud.google.com/vertex-ai/generative-ai/docs/thinking) for model compatibility and further details. #### File Inputs The Google Vertex provider supports file inputs, e.g. PDF files. ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { generateText } from 'ai'; const { text } = await generateText({ model: googleVertex('gemini-3.1-pro-preview'), messages: [ { role: 'user', content: [ { type: 'text', text: 'What is an embedding model according to this document?', }, { type: 'file', data: fs.readFileSync('./data/ai.pdf'), mediaType: 'application/pdf', }, ], }, ], }); ``` The AI SDK will automatically download URLs if you pass them as data, except for `gs://` URLs. You can use the Google Cloud Storage API to upload larger files to that location. See [File Parts](/docs/foundations/prompts#file-parts) for details on how to use files in prompts. ### Long decode Gemini 4 models such as `gemini-4-argon` support long decode on Vertex AI the same way as on the Gemini API: set `maxOutputTokens` above the per-request cap (up to 1M minus input) and the provider continues the response automatically. See [Long decode](/providers/ai-sdk-providers/google#long-decode) for details on budgets, usage across requests, and resuming with a continuation token. The Vertex AI Interactions API (preview) does not support long decode. ### Tool Result Files For Gemini 3 and later models, tool results can include Google Cloud Storage (`gs://`) file URLs. Vertex receives these references directly as `functionResponse.parts[].fileData`, without downloading the files into the SDK process. The supported MIME types are `image/png`, `image/jpeg`, `image/webp`, `application/pdf`, and `text/plain`. Specify the full MIME type; a top-level type such as `image` is not sufficient for GCS tool results. Use a file part in the tool's `toModelOutput` result: ```ts toModelOutput: ({ output }) => ({ type: 'content', value: [ { type: 'file', mediaType: 'image/png', data: { type: 'url', url: new URL(output.gcsUri), originalUrl: output.gcsUri, }, }, ], }), ``` Include `originalUrl` when URL parsing changes an opaque GCS URI, such as a path containing spaces, so Vertex receives the exact object reference. The Vertex caller must have access to the referenced object. HTTP(S) tool-result files are downloaded and sent as inline data, subject to `toolResultDownloads.maxBytes`. Direct GCS tool-result forwarding is not supported for older Gemini models or MIME types outside the list above. ### Cached Content Google Vertex AI supports both explicit and implicit caching to help reduce costs on repetitive content. #### Implicit Caching ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { generateText } from 'ai'; // Structure prompts with consistent content at the beginning const baseContext = 'You are a cooking assistant with expertise in Italian cuisine. Here are 1000 lasagna recipes for reference...'; const { text: veggieLasagna } = await generateText({ model: googleVertex('gemini-3.1-pro-preview'), prompt: `${baseContext}\n\nWrite a vegetarian lasagna recipe for 4 people.`, }); // Second request with same prefix - eligible for cache hit const { text: meatLasagna, providerMetadata } = await generateText({ model: googleVertex('gemini-3.1-pro-preview'), prompt: `${baseContext}\n\nWrite a meat lasagna recipe for 12 people.`, }); // Check cached token count in usage metadata console.log('Cached tokens:', providerMetadata.vertex); // e.g. // { // groundingMetadata: null, // safetyRatings: null, // usageMetadata: { // cachedContentTokenCount: 2027, // thoughtsTokenCount: 702, // promptTokenCount: 2152, // candidatesTokenCount: 710, // totalTokenCount: 3564 // } // } ``` #### Explicit Caching You can use explicit caching with Gemini models. See the [Vertex AI context caching documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/context-cache/context-cache-overview) to check if caching is supported for your model. First, create a cache using the Google GenAI SDK with Vertex mode enabled: ```ts import { GoogleGenAI } from '@google/genai'; const ai = new GoogleGenAI({ vertexai: true, project: process.env.GOOGLE_VERTEX_PROJECT, location: process.env.GOOGLE_VERTEX_LOCATION, }); const model = 'gemini-3.1-pro-preview'; // Create a cache with the content you want to reuse const cache = await ai.caches.create({ model, config: { contents: [ { role: 'user', parts: [{ text: '1000 Lasagna Recipes...' }], }, ], ttl: '300s', // Cache expires after 5 minutes }, }); console.log('Cache created:', cache.name); // e.g. projects/my-project/locations/us-central1/cachedContents/abc123 ``` Then use the cache with the AI SDK: ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { type GoogleLanguageModelOptions } from '@ai-sdk/google'; import { generateText } from 'ai'; const { text: veggieLasagnaRecipe } = await generateText({ model: googleVertex('gemini-3.1-pro-preview'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { vertex: { cachedContent: cache.name, } satisfies GoogleLanguageModelOptions, }, }); const { text: meatLasagnaRecipe } = await generateText({ model: googleVertex('gemini-3.1-pro-preview'), prompt: 'Write a meat lasagna recipe for 12 people.', providerOptions: { vertex: { cachedContent: cache.name, } satisfies GoogleLanguageModelOptions, }, }); ``` ### Safety Ratings The safety ratings provide insight into the safety of the model's response. See [Google Vertex AI documentation on configuring safety filters](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/configure-safety-filters). Example response excerpt: ```json { "safetyRatings": [ { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE", "probabilityScore": 0.11027937, "severity": "HARM_SEVERITY_LOW", "severityScore": 0.28487435 }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "HIGH", "blocked": true, "probabilityScore": 0.95422274, "severity": "HARM_SEVERITY_MEDIUM", "severityScore": 0.43398145 }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE", "probabilityScore": 0.11085559, "severity": "HARM_SEVERITY_NEGLIGIBLE", "severityScore": 0.19027223 }, { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE", "probabilityScore": 0.22901751, "severity": "HARM_SEVERITY_NEGLIGIBLE", "severityScore": 0.09089675 } ] } ``` For more details, see the [Google Vertex AI documentation on grounding with Google Search](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/ground-gemini#ground-to-search). ### Troubleshooting #### Schema Limitations The Google Vertex API uses a subset of the OpenAPI 3.0 schema, which does not support features such as unions. The errors that you get in this case look like this: `GenerateContentRequest.generation_config.response_schema.properties[occupation].type: must be specified` By default, structured outputs are enabled (and for tool calling they are required). You can disable structured outputs for object generation as a workaround: ```ts {7,12} import { googleVertex } from '@ai-sdk/google-vertex'; import { type GoogleLanguageModelOptions } from '@ai-sdk/google'; import { generateText, Output } from 'ai'; const result = await generateText({ model: googleVertex('gemini-3.1-pro-preview'), providerOptions: { vertex: { structuredOutputs: false, } satisfies GoogleLanguageModelOptions, }, output: Output.object({ schema: z.object({ name: z.string(), age: z.number(), contact: z.union([ z.object({ type: z.literal('email'), value: z.string(), }), z.object({ type: z.literal('phone'), value: z.string(), }), ]), }), }), prompt: 'Generate an example person for testing.', }); ``` The following Zod features are known to not work with Google Vertex: - `z.union` - `z.record` ### Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | | ------------------------ | ----------- | ----------------- | ---------- | -------------- | | `gemini-nano-banana-2.1` | ✓ | ✗ | ✗ | ✗ | | `gemini-4-argon` | ✓ | ✓ | ✓ | ✓ | | `gemini-3.8-flash` | ✓ | ✓ | ✓ | ✓ | | `gemini-3.7-flash` | ✓ | ✓ | ✓ | ✓ | | `gemini-3.6-flash` | ✓ | ✓ | ✓ | ✓ | | `gemini-3.5-flash` | ✓ | ✓ | ✓ | ✓ | | `gemini-3.5-flash-lite` | ✓ | ✓ | ✓ | ✓ | | `gemini-3-pro-preview` | ✓ | ✓ | ✓ | ✓ | | `gemini-2.5-pro` | ✓ | ✓ | ✓ | ✓ | | `gemini-2.5-flash` | ✓ | ✓ | ✓ | ✓ | | `gemini-2.0-flash-001` | ✓ | ✓ | ✓ | ✓ | The table above lists popular models. Please see the [Google Vertex AI docs](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference#supported-models) for a full list of available models. The table above lists popular models. You can also pass any available provider model ID as a string if needed. ### Embedding Models You can create models that call the Google Vertex AI embeddings API using the `.embeddingModel()` factory method: ```ts const model = googleVertex.embeddingModel('text-embedding-005'); ``` Google Vertex AI embedding models support additional settings. You can pass them as an options argument: ```ts import { googleVertex, type GoogleVertexEmbeddingModelOptions, } from '@ai-sdk/google-vertex'; import { embed } from 'ai'; const model = googleVertex.embeddingModel('text-embedding-005'); const { embedding } = await embed({ model, value: 'sunny day at the beach', providerOptions: { vertex: { outputDimensionality: 512, // optional, number of dimensions for the embedding taskType: 'SEMANTIC_SIMILARITY', // optional, specifies the task type for generating embeddings autoTruncate: false, // optional } satisfies GoogleVertexEmbeddingModelOptions, }, }); ``` The following optional provider options are available for Google Vertex AI embedding models: - **outputDimensionality**: *number* Optional reduced dimension for the output embedding. If set, excessive values in the output embedding are truncated from the end. - **taskType**: *string* Optional. Specifies the task type for generating embeddings. Supported task types include: - `SEMANTIC_SIMILARITY`: Optimized for text similarity. - `CLASSIFICATION`: Optimized for text classification. - `CLUSTERING`: Optimized for clustering texts based on similarity. - `RETRIEVAL_DOCUMENT`: Optimized for document retrieval. - `RETRIEVAL_QUERY`: Optimized for query-based retrieval. - `QUESTION_ANSWERING`: Optimized for answering questions. - `FACT_VERIFICATION`: Optimized for verifying factual information. - `CODE_RETRIEVAL_QUERY`: Optimized for retrieving code blocks based on natural language queries. - **title**: *string* Optional. The title of the document being embedded. This helps the model produce better embeddings by providing additional context. Only valid when `taskType` is set to `'RETRIEVAL_DOCUMENT'`. - **autoTruncate**: *boolean* Optional. When set to `true`, input text will be truncated if it exceeds the maximum length. When set to `false`, an error is returned if the input text is too long. Defaults to `true`. #### Model Capabilities | Model | Max Values Per Call | Parallel Calls | Multimodal | | ---------------------------- | ------------------- | -------------- | ---------- | | `text-embedding-005` | 2048 | ✓ | ✗ | | `gemini-embedding-2` | 2048 | ✓ | ✓ | | `gemini-embedding-2-preview` | 2048 | ✓ | ✓ | The table above lists popular models. You can also pass any available provider model ID as a string if needed. ### Image Models You can create Gemini image models using the `.image()` factory method. These models provide image output through the language model `:generateContent` API. For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image). #### Gemini Image Models [Gemini image models](https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-flash-image) (e.g. `gemini-3.1-flash-image-preview`) are multimodal output language models that can be used with `generateImage()` for a simpler image generation experience. Internally, the provider calls the language model API with `responseModalities: ['IMAGE']`. ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: googleVertex.image('gemini-3.1-flash-image-preview'), prompt: 'A photorealistic image of a cat wearing a wizard hat', aspectRatio: '1:1', }); ``` Gemini image models also support image editing by providing input images: ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { generateImage } from 'ai'; import fs from 'node:fs'; const sourceImage = fs.readFileSync('./cat.png'); const { image } = await generateImage({ model: googleVertex.image('gemini-3.1-flash-image-preview'), prompt: { text: 'Add a small wizard hat to this cat', images: [sourceImage], }, }); ``` Input images must be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`, or base64-encoded strings. URL inputs, including HTTP and `gs://` URLs, are not supported by `generateImage()` with Google Vertex image models. Gemini image models do not support the `size` or `n` parameters. Use `aspectRatio` instead of `size`. Mask-based inpainting is also not supported. Gemini image models are multimodal output models that can generate both text and images. For more advanced use cases where you need both text and image outputs, or want more control over the generation process, you can use them directly with `generateText()`. ##### Gemini Image Model Capabilities | Model | Image Generation | Image Editing | Aspect Ratios | | -------------------------------- | ---------------- | ------------- | ----------------------------------------------------------------------- | | `gemini-nano-banana-2.1` | ✓ | ✓ | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9, 1:8, 8:1, 1:4, 4:1 | | `gemini-3.1-flash-image-preview` | ✓ | ✓ | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | | `gemini-3-pro-image-preview` | ✓ | ✓ | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | | `gemini-2.5-flash-image` | ✓ | ✓ | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | `gemini-3-pro-image-preview` supports additional features including up to 14 reference images for editing (6 objects, 5 humans), resolution options (1K, 2K, 4K via `providerOptions.vertex.imageConfig.imageSize`). Use the `GoogleVertexImageModelOptions` type to validate Gemini provider options. ### Video Models You can create [Veo](https://cloud.google.com/vertex-ai/generative-ai/docs/video/overview) video models that call the Vertex AI API using the `.video()` factory method. For more on video generation with the AI SDK see [generateVideo()](/docs/reference/ai-sdk-core/generate-video). ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: googleVertex.video('veo-3.1-generate-001'), prompt: 'A pangolin curled on a mossy stone in a glowing bioluminescent forest', aspectRatio: '16:9', }); ``` You can configure resolution and duration: ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: googleVertex.video('veo-3.1-generate-001'), prompt: 'A serene mountain landscape at sunset', aspectRatio: '16:9', resolution: '1920x1080', duration: 8, }); ``` #### First and Last Frame Veo supports first-last-frame generation via the top-level `frameImages` option: ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: googleVertex.video('veo-3.1-generate-001'), prompt: 'A hand reaches in and places a glass of milk next to the plate of cookies', frameImages: [ { image: 'gs://cloud-samples-data/generative-ai/image/cookies.png', frameType: 'first_frame', }, { image: 'gs://cloud-samples-data/generative-ai/image/cookies-milk.png', frameType: 'last_frame', }, ], duration: 8, }); ``` #### Reference Images Veo 3.1 supports reference-to-video generation via the top-level `inputReferences` option: ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: googleVertex.video('veo-3.1-generate-001'), prompt: 'The video opens with a medium shot of a woman in a high-fashion flamingo dress walking through a lagoon', inputReferences: [ 'gs://bucket/dress.png', 'gs://bucket/glasses.png', 'gs://bucket/woman.png', ], duration: 8, }); ``` #### Provider Options Further configuration can be done using Google Vertex provider options. You can validate the provider options using the `GoogleVertexVideoModelOptions` type. ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { GoogleVertexVideoModelOptions } from '@ai-sdk/google-vertex'; import { experimental_generateVideo as generateVideo } from 'ai'; const { video } = await generateVideo({ model: googleVertex.video('veo-3.1-generate-001'), prompt: 'A serene mountain landscape at sunset', aspectRatio: '16:9', providerOptions: { vertex: { generateAudio: true, personGeneration: 'allow_adult', } satisfies GoogleVertexVideoModelOptions, }, }); ``` The following provider options are available: - **generateAudio** *boolean* Whether to generate audio along with the video. - **personGeneration** `'dont_allow'` | `'allow_adult'` | `'allow_all'` Whether to allow person generation in the video. - **negativePrompt** *string* A description of what to discourage in the generated video. - **gcsOutputDirectory** *string* Cloud Storage URI to store the generated videos. - **referenceImages** *Array\<\{ bytesBase64Encoded?: string; gcsUri?: string }>* Reference images for style or asset guidance. - **pollIntervalMs** *number* Polling interval in milliseconds for checking task status. - **pollTimeoutMs** *number* Maximum wait time in milliseconds for video generation. Video generation is an asynchronous process that can take several minutes. For longer videos or higher resolutions, consider setting `pollTimeoutMs` to at least 10 minutes (600000ms). #### Model Capabilities | Model | Audio Support | | --------------------------- | ------------- | | `veo-3.1-generate-001` | Yes | | `veo-3.1-fast-generate-001` | Yes | | `veo-3.0-generate-001` | Yes | | `veo-3.0-fast-generate-001` | Yes | | `veo-2.0-generate-001` | No | The table above lists popular models. You can also pass any available provider model ID as a string if needed. ### Speech Models You can create [Gemini text-to-speech](https://docs.cloud.google.com/text-to-speech/docs/gemini-tts) models that call the Vertex AI API using the `.speech()` factory method. For more on speech generation with the AI SDK see [generateSpeech()](/docs/reference/ai-sdk-core/generate-speech). ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { generateSpeech } from 'ai'; const result = await generateSpeech({ model: googleVertex.speech('gemini-2.5-flash-tts'), text: 'Hello, world!', voice: 'Kore', // Gemini voice name }); ``` The `voice` argument accepts one of Gemini's [30 prebuilt voices](https://ai.google.dev/gemini-api/docs/speech-generation#voices) (e.g. `Kore`, `Puck`, `Zephyr`); it defaults to `Kore`. Multi-speaker dialogue is available via `providerOptions.googleVertex.multiSpeakerVoiceConfig`. By default the audio is returned as a playable WAV (Gemini returns raw PCM, which the provider wraps). Set `outputFormat: 'pcm'` for the raw signed 16-bit little-endian mono bytes; the sample rate is reported in `result.providerMetadata.google.sampleRate`. #### Speech Model Capabilities | Model | Multi-speaker | Style via instructions | | ----------------------------------- | ------------- | ---------------------- | | `gemini-2.5-flash-tts` | ✓ | ✓ | | `gemini-2.5-pro-tts` | ✓ | ✓ | | `gemini-2.5-flash-lite-preview-tts` | ✓ | ✓ | | `gemini-3.1-flash-tts-preview` | ✓ | ✓ | ### Transcription Models You can transcribe audio with Google Cloud Speech-to-Text models using the `.transcription()` factory method together with [`transcribe()`](/docs/reference/ai-sdk-core/transcribe). ```ts import { googleVertex } from '@ai-sdk/google-vertex'; import { transcribe } from 'ai'; import { readFile } from 'fs/promises'; const result = await transcribe({ model: googleVertex.transcription('chirp_2'), audio: await readFile('audio.wav'), }); ``` The provider supports [Chirp](https://docs.cloud.google.com/speech-to-text/docs/models/chirp-3) models `chirp_2` and `chirp_3`, plus `telephony` for phone-call audio. Speech-to-Text uses standard Google Cloud credentials (OAuth, Application Default Credentials, or a service account) and calls the Cloud Speech-to-Text API. Express Mode API keys are not supported for transcription models. Set `GOOGLE_VERTEX_LOCATION` (or `providerOptions.googleVertex.region`) to a Speech-to-Text region. For Chirp, `chirp_2` is available in `us-central1`, `europe-west4`, and `asia-southeast1`; `chirp_3` in the `us` and `eu` multi-regions. Chirp is not available in the `global` Speech-to-Text location, and these regions differ from Vertex AI regions. `telephony` availability depends on the selected Speech-to-Text region and language. The synchronous API transcribes audio up to one minute or 10 MB, whichever is reached first. The spoken language is auto-detected by default; pass `languageCodes` to restrict it. For `telephony`, pass a supported language code such as `['en-US']`. ```ts const result = await transcribe({ model: googleVertex.transcription('chirp_3'), audio: await readFile('audio.wav'), providerOptions: { googleVertex: { region: 'us', languageCodes: ['en-US'], }, }, }); ``` The following provider options are available: - **languageCodes** *string\[]* BCP-47 language codes to recognize, or `['auto']` to detect the spoken language. Defaults to `['auto']`. Multiple explicit language codes require a multi-region Speech-to-Text endpoint such as `us` or `eu`. - **enableAutomaticPunctuation** *boolean* Whether to add punctuation to the transcript. Defaults to `true`. - **enableWordTimeOffsets** *boolean* Whether to include word-level timestamps in `result.segments`. Defaults to `true`. Google notes that enabling word-level timestamps can reduce transcription quality and speed. - **region** *string* The Speech-to-Text region for the request. Defaults to the provider `location`. #### Transcription Model Capabilities | Model | Word timestamps | Language detection | | ----------- | --------------------------------------------------------- | ------------------------------------------------------------------------------ | | `chirp_2` | Available with a potential quality and speed tradeoff | Auto detection with `['auto']` | | `chirp_3` | Available with a potential transcription quality tradeoff | Auto detection with `['auto']` | | `telephony` | Available | Explicit supported language codes, with alternative language detection support | ## Google Vertex Anthropic Provider Usage The Google Vertex Anthropic provider for the [AI SDK](/docs) offers support for Anthropic's Claude models through the Google Vertex AI APIs. This section provides details on how to set up and use the Google Vertex Anthropic provider. ### Provider Instance You can import the default provider instance `vertexAnthropic` from `@ai-sdk/google-vertex/anthropic`: ```typescript import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic'; ``` If you need a customized setup, you can import `createVertexAnthropic` from `@ai-sdk/google-vertex/anthropic` and create a provider instance with your settings: ```typescript import { createVertexAnthropic } from '@ai-sdk/google-vertex/anthropic'; const vertexAnthropic = createVertexAnthropic({ project: 'my-project', // optional location: 'us-central1', // optional }); ``` #### Node.js Runtime For Node.js environments, the Google Vertex Anthropic provider supports all standard Google Cloud authentication options through the `google-auth-library`. You can customize the authentication options by passing them to the `createVertexAnthropic` function: ```typescript import { createVertexAnthropic } from '@ai-sdk/google-vertex/anthropic'; const vertexAnthropic = createVertexAnthropic({ googleAuthOptions: { credentials: { client_email: 'my-email', private_key: 'my-private-key', }, }, }); ``` ##### Optional Provider Settings You can use the following optional settings to customize the Google Vertex Anthropic provider instance: - **project** *string* The Google Cloud project ID that you want to use for the API calls. It uses the `GOOGLE_VERTEX_PROJECT` environment variable by default. - **location** *string* The Google Cloud location that you want to use for the API calls, e.g. `us-central1`. It uses the `GOOGLE_VERTEX_LOCATION` environment variable by default. - **googleAuthOptions** *object* Optional. The Authentication options used by the [Google Auth Library](https://github.com/googleapis/google-auth-library-nodejs/). See also the [GoogleAuthOptions](https://github.com/googleapis/google-auth-library-nodejs/blob/08978822e1b7b5961f0e355df51d738e012be392/src/auth/googleauth.ts#L87C18-L87C35) interface. - **authClient** *object* An `AuthClient` to use. - **keyFilename** *string* Path to a .json, .pem, or .p12 key file. - **keyFile** *string* Path to a .json, .pem, or .p12 key file. - **credentials** *object* Object containing client\_email and private\_key properties, or the external account client options. - **clientOptions** *object* Options object passed to the constructor of the client. - **scopes** *string | string\[]* Required scopes for the desired API request. - **projectId** *string* Your project ID. - **universeDomain** *string* The default service domain for a given Cloud universe. - **headers** *Resolvable\>* Headers to include in the requests. Can be provided in multiple formats: - A record of header key-value pairs: `Record` - A function that returns headers: `() => Record` - An async function that returns headers: `async () => Record` - A promise that resolves to headers: `Promise>` - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. #### Edge Runtime Edge runtimes (like Cloudflare Workers) are lightweight JavaScript environments that run closer to users at the network edge. They only provide a subset of the standard Node.js APIs. For example, direct file system access is not available, and many Node.js-specific libraries (including the standard Google Auth library) are not compatible. The Edge runtime version of the Google Vertex Anthropic provider supports Google's [Application Default Credentials](https://github.com/googleapis/google-auth-library-nodejs?tab=readme-ov-file#application-default-credentials) through environment variables. The values can be obtained from a json credentials file from the [Google Cloud Console](https://console.cloud.google.com/apis/credentials). For Edge runtimes, you can import the provider instance from `@ai-sdk/google-vertex/anthropic/edge`: ```typescript import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic/edge'; ``` To customize the setup, use `createVertexAnthropic` from the same module: ```typescript import { createVertexAnthropic } from '@ai-sdk/google-vertex/anthropic/edge'; const vertexAnthropic = createVertexAnthropic({ project: 'my-project', // optional location: 'us-central1', // optional }); ``` For Edge runtime authentication, set these environment variables from your Google Default Application Credentials JSON file: - `GOOGLE_CLIENT_EMAIL` - `GOOGLE_PRIVATE_KEY` - `GOOGLE_PRIVATE_KEY_ID` (optional) ##### Optional Provider Settings You can use the following optional settings to customize the provider instance: - **project** *string* The Google Cloud project ID that you want to use for the API calls. It uses the `GOOGLE_VERTEX_PROJECT` environment variable by default. - **location** *string* The Google Cloud location that you want to use for the API calls, e.g. `us-central1`. It uses the `GOOGLE_VERTEX_LOCATION` environment variable by default. - **googleCredentials** *object* Optional. The credentials used by the Edge provider for authentication. These credentials are typically set through environment variables and are derived from a service account JSON file. - **clientEmail** *string* The client email from the service account JSON file. Defaults to the contents of the `GOOGLE_CLIENT_EMAIL` environment variable. - **privateKey** *string* The private key from the service account JSON file. Defaults to the contents of the `GOOGLE_PRIVATE_KEY` environment variable. - **privateKeyId** *string* The private key ID from the service account JSON file (optional). Defaults to the contents of the `GOOGLE_PRIVATE_KEY_ID` environment variable. - **headers** *Resolvable\>* Headers to include in the requests. Can be provided in multiple formats: - A record of header key-value pairs: `Record` - A function that returns headers: `() => Record` - An async function that returns headers: `async () => Record` - A promise that resolves to headers: `Promise>` - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. ### Language Models You can create models that call the [Anthropic Messages API](https://docs.anthropic.com/claude/reference/messages_post) using the provider instance. The first argument is the model id, e.g. `claude-sonnet-5-5`. Some models have multi-modal capabilities. ```ts const model = vertexAnthropic('claude-sonnet-5-5'); ``` You can use Anthropic language models to generate text with the `generateText` function: ```ts import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic'; import { generateText } from 'ai'; const { text } = await generateText({ model: vertexAnthropic('claude-sonnet-5-5'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` Anthropic language models can also be used in the `streamText` function and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output) (see [AI SDK Core](/docs/ai-sdk-core)). The Anthropic API returns streaming tool calls all at once after a delay. This causes structured output generation to complete fully after a delay instead of streaming incrementally. The following optional provider options are available for Anthropic models: - `sendReasoning` *boolean* Optional. Include reasoning content in requests sent to the model. Defaults to `true`. If you are experiencing issues with the model handling requests involving reasoning content, you can set this to `false` to omit them from the request. - `thinking` *object* Optional. See [Reasoning section](#reasoning) for more details. - `metadata` *object* Optional. Metadata to include with the request. See the [Anthropic API documentation](https://platform.claude.com/docs/en/api/messages/create) for details. - `userId` *string* - An external identifier for the end-user. ### Billing Labels Pass billing labels for Claude requests using `providerOptions.vertex.labels`. Labels are a map of string keys to string values and work with both `generateText` and `streamText`: ```ts import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic'; import { generateText } from 'ai'; const { text } = await generateText({ model: vertexAnthropic('claude-sonnet-4-6@default'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { vertex: { labels: { team: 'data-platform', feature: 'my-app' }, }, }, }); ``` The provider sends the labels as base64-encoded UTF-8 JSON in the `X-Vertex-AI-Labels` header. An explicitly supplied labels header takes precedence over `providerOptions.vertex.labels`. See [Google's billing labels documentation](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/multimodal/add-labels-to-api-calls#add_a_label_to_a_partner_model_api_call) for label requirements and supported billing options. ### Reasoning Anthropic has reasoning support for the `claude-3-7-sonnet@20250219` model. You can enable it using the `thinking` provider option and specifying a thinking budget in tokens. ```ts import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic'; import { generateText } from 'ai'; const { text, reasoningText, reasoning } = await generateText({ model: vertexAnthropic('claude-3-7-sonnet@20250219'), prompt: 'How many people will live in the world in 2040?', providerOptions: { anthropic: { thinking: { type: 'enabled', budgetTokens: 12000 }, }, }, }); console.log(reasoningText); // reasoning text console.log(reasoning); // reasoning details including redacted reasoning console.log(text); // text response ``` See [AI SDK UI: Chatbot](/docs/ai-sdk-ui/chatbot#reasoning) for more details on how to integrate reasoning into your chatbot. #### Cache Control Anthropic cache control is in a Pre-Generally Available (GA) state on Google Vertex. For more see [Google Vertex Anthropic cache control documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude-prompt-caching). In the messages and message parts, you can use the `providerOptions` property to set cache control breakpoints. You need to set the `anthropic` property in the `providerOptions` object to `{ cacheControl: { type: 'ephemeral' } }` to set a cache control breakpoint. Cache read and cache write (creation) token counts are returned on the standard `usage` object for both `generateText` and `streamText`. You can access them at `result.usage.inputTokenDetails.cacheReadTokens` and `result.usage.inputTokenDetails.cacheWriteTokens`. ```ts {8,16-18,27-31} import { vertexAnthropic } from '@ai-sdk/google-vertex/anthropic'; import { generateText } from 'ai'; const errorMessage = '... long error message ...'; const result = await generateText({ model: vertexAnthropic('claude-sonnet-5-5'), messages: [ { role: 'user', content: [ { type: 'text', text: 'You are a JavaScript expert.' }, { type: 'text', text: `Error message: ${errorMessage}`, providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } }, }, }, { type: 'text', text: 'Explain the error message.' }, ], }, ], }); console.log(result.text); console.log( 'Cache read tokens:', result.usage.inputTokenDetails.cacheReadTokens, ); console.log( 'Cache write tokens:', result.usage.inputTokenDetails.cacheWriteTokens, ); ``` You can also use cache control on system messages by providing multiple system messages at the head of your messages array: ```ts {3,7-9} const result = await generateText({ model: vertexAnthropic('claude-sonnet-5-5'), messages: [ { role: 'system', content: 'Cached system message part', providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } }, }, }, { role: 'system', content: 'Uncached system message part', }, { role: 'user', content: 'User prompt', }, ], }); ``` For more on prompt caching with Anthropic, see [Google Vertex AI's Claude prompt caching documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/claude-prompt-caching) and [Anthropic's Cache Control documentation](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching). ### Tools Google Vertex Anthropic supports a subset of Anthropic's built-in tools. The following tools are available via the `tools` property of the provider instance: 1. **Bash Tool**: Allows running bash commands. 2. **Text Editor Tool**: Provides functionality for viewing and editing text files. 3. **Computer Tool**: Enables control of keyboard and mouse actions on a computer. 4. **Web Search Tool**: Provides access to real-time web content. Only a subset of Anthropic tools are supported on Google Vertex. Tools like Code Execution, Memory, and Web Fetch are not available. Use the regular `@ai-sdk/anthropic` provider if you need access to all Anthropic tools. Google Vertex Anthropic does not support strict mode on tool definitions. Setting `strict: true` on a tool will be ignored and a warning will be emitted. For more background on Anthropic tools, see [Anthropic's documentation](https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview). #### Bash Tool The Bash Tool allows running bash commands. Here's how to create and use it: ```ts const bashTool = vertexAnthropic.tools.bash_20250124({ execute: async ({ command, restart }) => { // Implement your bash command execution logic here // Return the result of the command execution }, }); ``` Parameters: - `command` (string): The bash command to run. Required unless the tool is being restarted. - `restart` (boolean, optional): Specifying true will restart this tool. #### Text Editor Tool The Text Editor Tool provides functionality for viewing and editing text files: ```ts const textEditorTool = vertexAnthropic.tools.textEditor_20250124({ execute: async ({ command, path, file_text, insert_line, new_str, insert_text, old_str, view_range, }) => { // Implement your text editing logic here // Return the result of the text editing operation }, }); ``` Parameters: - `command` ('view' | 'create' | 'str\_replace' | 'insert' | 'undo\_edit'): The command to run. Note: `undo_edit` is not supported in `textEditor_20250429` and `textEditor_20250728`. - `path` (string): Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`. - `file_text` (string, optional): Required for `create` command, with the content of the file to be created. - `insert_line` (number, optional): Required for `insert` command. The line number after which to insert the new string. - `new_str` (string, optional): New string for `str_replace` command. - `insert_text` (string, optional): Required for `insert` command, containing the text to insert. - `old_str` (string, optional): Required for `str_replace` command, containing the string to replace. - `view_range` (number\[], optional): Optional for `view` command to specify line range to show. - `max_characters` (number, optional): Optional maximum number of characters to view in the file (only available in `textEditor_20250728`). #### Computer Tool The Computer Tool enables control of keyboard and mouse actions on a computer: ```ts const computerTool = vertexAnthropic.tools.computer_20241022({ displayWidthPx: 1920, displayHeightPx: 1080, displayNumber: 0, // Optional, for X11 environments execute: async ({ action, coordinate, text }) => { // Implement your computer control logic here // Return the result of the action // Example code: switch (action) { case 'screenshot': { // multipart result: return { type: 'image', data: fs .readFileSync('./data/screenshot-editor.png') .toString('base64'), }; } default: { console.log('Action:', action); console.log('Coordinate:', coordinate); console.log('Text:', text); return `executed ${action}`; } } }, // map to tool result content for LLM consumption: toModelOutput({ output }) { return typeof output === 'string' ? [{ type: 'text', text: output }] : [{ type: 'file-data', data: output.data, mediaType: 'image/png' }]; }, }); ``` Parameters: - `action` ('key' | 'type' | 'mouse\_move' | 'left\_click' | 'left\_click\_drag' | 'right\_click' | 'middle\_click' | 'double\_click' | 'screenshot' | 'cursor\_position'): The action to perform. - `coordinate` (number\[], optional): Required for `mouse_move` and `left_click_drag` actions. Specifies the (x, y) coordinates. - `text` (string, optional): Required for `type` and `key` actions. #### Web Search Tool The Web Search Tool provides Claude with direct access to real-time web content: ```ts const webSearchTool = vertexAnthropic.tools.webSearch_20250305({ maxUses: 5, // Optional: Maximum number of web searches Claude can perform allowedDomains: ['example.com'], // Optional: Only search these domains blockedDomains: ['spam.com'], // Optional: Never search these domains userLocation: { // Optional: Provide location for geographically relevant results type: 'approximate', city: 'San Francisco', region: 'CA', country: 'US', timezone: 'America/Los_Angeles', }, }); ``` Parameters: - `maxUses` (number, optional): Maximum number of web searches Claude can perform during the conversation. - `allowedDomains` (string\[], optional): Optional list of domains that Claude is allowed to search. - `blockedDomains` (string\[], optional): Optional list of domains that Claude should avoid when searching. - `userLocation` (object, optional): Optional user location information to provide geographically relevant search results. - `type` ('approximate'): The type of location (must be approximate). - `city` (string, optional): The city name. - `region` (string, optional): The region or state. - `country` (string, optional): The country. - `timezone` (string, optional): The IANA timezone ID. These tools can be used in conjunction with supported Claude models to enable more complex interactions and tasks. ### Model Capabilities The latest Anthropic model list on Vertex AI is available [here](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-claude#model-list). See also [Anthropic Model Comparison](https://docs.anthropic.com/en/docs/about-claude/models#model-comparison). | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | Computer Use | | ------------------------------- | ----------- | ----------------- | ---------- | -------------- | ------------ | | `claude-fable-5-1` | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-3-7-sonnet@20250219` | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-3-5-sonnet-v2@20241022` | ✓ | ✓ | ✓ | ✓ | ✓ | | `claude-3-5-sonnet@20240620` | ✓ | ✓ | ✓ | ✓ | ✗ | | `claude-3-5-haiku@20241022` | ✗ | ✓ | ✓ | ✓ | ✗ | | `claude-3-sonnet@20240229` | ✓ | ✓ | ✓ | ✓ | ✗ | | `claude-3-haiku@20240307` | ✓ | ✓ | ✓ | ✓ | ✗ | | `claude-3-opus@20240229` | ✓ | ✓ | ✓ | ✓ | ✗ | The table above lists popular models. You can also pass any available provider model ID as a string if needed. ## Google Vertex xAI Provider Usage The Google Vertex xAI provider offers support for xAI's Grok partner models through the Google Vertex AI OpenAI-compatible Chat Completions API. For more information, see the [Vertex AI Grok documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/grok). ### Provider Instance You can import the default provider instance `googleVertexXai` from `@ai-sdk/google-vertex/xai`: ```typescript import { googleVertexXai } from '@ai-sdk/google-vertex/xai'; ``` If you need a customized setup, you can import `createGoogleVertexXai` from `@ai-sdk/google-vertex/xai` and create a provider instance with your settings: ```typescript import { createGoogleVertexXai } from '@ai-sdk/google-vertex/xai'; const googleVertexXai = createGoogleVertexXai({ project: 'my-project', // optional location: 'global', // optional, defaults to 'global' }); ``` #### Node.js Runtime For Node.js environments, the Google Vertex xAI provider supports all standard Google Cloud authentication options through the `google-auth-library`: ```typescript import { createGoogleVertexXai } from '@ai-sdk/google-vertex/xai'; const googleVertexXai = createGoogleVertexXai({ googleAuthOptions: { credentials: { client_email: 'my-email', private_key: 'my-private-key', }, }, }); ``` ##### Optional Provider Settings - **project** *string* The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable. - **location** *string* The Google Cloud location. Grok models are available on the global endpoint. Defaults to the `GOOGLE_VERTEX_LOCATION` environment variable. If not set, defaults to `global`. - **googleAuthOptions** *object* Optional. The Authentication options used by the [Google Auth Library](https://github.com/googleapis/google-auth-library-nodejs/). - **headers** *Resolvable\>* Headers to include in requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. #### Edge Runtime For Edge runtimes, import from `@ai-sdk/google-vertex/xai/edge`: ```typescript import { googleVertexXai } from '@ai-sdk/google-vertex/xai/edge'; ``` ```typescript import { createGoogleVertexXai } from '@ai-sdk/google-vertex/xai/edge'; const googleVertexXai = createGoogleVertexXai({ project: 'my-project', location: 'global', }); ``` For Edge runtime authentication, set these environment variables: - `GOOGLE_CLIENT_EMAIL` - `GOOGLE_PRIVATE_KEY` - `GOOGLE_PRIVATE_KEY_ID` (optional) ### Language Models You can create models using the provider instance. The first argument is the model ID: ```ts import { googleVertexXai } from '@ai-sdk/google-vertex/xai'; import { generateText } from 'ai'; const { text } = await generateText({ model: googleVertexXai('xai/grok-4.1-fast-reasoning'), prompt: 'Invent a new holiday and describe its traditions.', }); ``` Streaming is also supported: ```ts import { googleVertexXai } from '@ai-sdk/google-vertex/xai'; import { streamText } from 'ai'; const result = streamText({ model: googleVertexXai('xai/grok-4.1-fast-reasoning'), prompt: 'Invent a new holiday and describe its traditions.', }); for await (const textPart of result.textStream) { process.stdout.write(textPart); } ``` ### Function Calling Grok models on Vertex support OpenAI-compatible function calling. You can use AI SDK tools as usual: ```ts import { googleVertexXai } from '@ai-sdk/google-vertex/xai'; import { generateText, tool } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: googleVertexXai('xai/grok-4.1-fast-reasoning'), tools: { weather: tool({ description: 'Get the weather in a city', inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => `The weather in ${city} is sunny.`, }), }, prompt: 'What is the weather in San Francisco?', }); ``` ### Structured Outputs Grok models on Vertex support JSON mode and schema-backed structured outputs: ```ts import { googleVertexXai } from '@ai-sdk/google-vertex/xai'; import { generateText, Output } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: googleVertexXai('xai/grok-4.1-fast-reasoning'), output: Output.object({ schema: z.object({ name: z.string(), date: z.string(), participants: z.array(z.string()), }), }), prompt: 'Alice and Bob are going to a science fair on Friday.', }); ``` ### Available Models The following models are available through the Google Vertex xAI provider. You can also pass any valid model ID as a string. | Model ID | Reasoning | | --------------------------------- | --------- | | `xai/grok-4.20-reasoning` | Yes | | `xai/grok-4.20-non-reasoning` | No | | `xai/grok-4.1-fast-reasoning` | Yes | | `xai/grok-4.1-fast-non-reasoning` | No | Grok reasoning models on Vertex report reasoning token counts in usage metadata. They do not support the `reasoning_effort` request parameter. ## Google Vertex MaaS Provider Usage The Google Vertex MaaS (Model as a Service) provider offers access to partner and open models hosted on Vertex AI through an OpenAI-compatible Chat Completions API. This includes models from DeepSeek, Qwen, Meta, MiniMax, Moonshot, and OpenAI. For more information, see the [Vertex AI MaaS documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/maas/use-open-models). ### Provider Instance You can import the default provider instance `vertexMaas` from `@ai-sdk/google-vertex/maas`: ```typescript import { vertexMaas } from '@ai-sdk/google-vertex/maas'; ``` If you need a customized setup, you can import `createVertexMaas` from `@ai-sdk/google-vertex/maas` and create a provider instance with your settings: ```typescript import { createVertexMaas } from '@ai-sdk/google-vertex/maas'; const vertexMaas = createVertexMaas({ project: 'my-project', // optional location: 'us-east5', // optional, defaults to 'global' }); ``` #### Node.js Runtime For Node.js environments, the Google Vertex MaaS provider supports all standard Google Cloud authentication options through the `google-auth-library`: ```typescript import { createVertexMaas } from '@ai-sdk/google-vertex/maas'; const vertexMaas = createVertexMaas({ googleAuthOptions: { credentials: { client_email: 'my-email', private_key: 'my-private-key', }, }, }); ``` ##### Optional Provider Settings - **project** *string* The Google Cloud project ID. Defaults to the `GOOGLE_VERTEX_PROJECT` environment variable. - **location** *string* The Google Cloud location, e.g. `us-east5` or `global`. Defaults to the `GOOGLE_VERTEX_LOCATION` environment variable. If not set, defaults to `global`. - **googleAuthOptions** *object* Optional. The Authentication options used by the [Google Auth Library](https://github.com/googleapis/google-auth-library-nodejs/). - **headers** *Resolvable\>* Headers to include in requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. #### Edge Runtime For Edge runtimes, import from `@ai-sdk/google-vertex/maas/edge`: ```typescript import { vertexMaas } from '@ai-sdk/google-vertex/maas/edge'; ``` ```typescript import { createVertexMaas } from '@ai-sdk/google-vertex/maas/edge'; const vertexMaas = createVertexMaas({ project: 'my-project', location: 'us-east5', }); ``` For Edge runtime authentication, set these environment variables: - `GOOGLE_CLIENT_EMAIL` - `GOOGLE_PRIVATE_KEY` - `GOOGLE_PRIVATE_KEY_ID` (optional) ### Language Models You can create models using the provider instance. The first argument is the model ID: ```ts import { vertexMaas } from '@ai-sdk/google-vertex/maas'; import { generateText } from 'ai'; const { text } = await generateText({ model: vertexMaas('deepseek-ai/deepseek-v3.2-maas'), prompt: 'Invent a new holiday and describe its traditions.', }); ``` Streaming is also supported: ```ts import { vertexMaas } from '@ai-sdk/google-vertex/maas'; import { streamText } from 'ai'; const result = streamText({ model: vertexMaas('deepseek-ai/deepseek-v3.2-maas'), prompt: 'Invent a new holiday and describe its traditions.', }); for await (const textPart of result.textStream) { process.stdout.write(textPart); } ``` ### Available Models The following models are available through the MaaS provider. You can also pass any valid model ID as a string. | Model ID | Provider | | ---------------------------------------------- | -------- | | `deepseek-ai/deepseek-r1-0528-maas` | DeepSeek | | `deepseek-ai/deepseek-v3.1-maas` | DeepSeek | | `deepseek-ai/deepseek-v3.2-maas` | DeepSeek | | `openai/gpt-oss-120b-maas` | OpenAI | | `openai/gpt-oss-20b-maas` | OpenAI | | `meta/llama-4-maverick-17b-128e-instruct-maas` | Meta | | `meta/llama-4-scout-17b-16e-instruct-maas` | Meta | | `minimax/minimax-m2-maas` | MiniMax | | `qwen/qwen3-coder-480b-a35b-instruct-maas` | Qwen | | `qwen/qwen3-next-80b-a3b-instruct-maas` | Qwen | | `qwen/qwen3-next-80b-a3b-thinking-maas` | Qwen | | `moonshotai/kimi-k2-thinking-maas` | Moonshot | Llama 4 MaaS models default to their 8,192-token output limit when `maxOutputTokens` is not specified. An explicit `maxOutputTokens` value takes precedence. Model availability depends on your Google Cloud project and region. Check the [Vertex AI Model Garden](https://console.cloud.google.com/vertex-ai/model-garden) for the latest available models. --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Google description: Learn how to use Google models through AI Gateway or the direct Google provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/google" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). You can use [Google](https://ai.google.dev) models through [AI Gateway](/providers/ai-sdk-providers/ai-gateway) (recommended) or connect directly to the [Google APIs](https://ai.google.dev/api/rest) with `@ai-sdk/google`. ```ts provider="google" model="google/gemini-4-argon" {5} import { generateText } from 'ai'; const { text } = await generateText({ model: "google/gemini-4-argon", prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. AI Gateway provides access to Google and other providers through a single API key, with usage and cost tracking. Install the `ai` package: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models?search=google) for available Google models. Google model options can be passed through `providerOptions.google` when using AI Gateway. Support depends on the model and the provider serving the request; see [Gateway provider options](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options). ## Using `@ai-sdk/google` For direct access to the Google APIs, install `ai` and `@ai-sdk/google`: ```bash pnpm add ai @ai-sdk/google ``` Set your [Google API key](https://aistudio.google.com/apikey) in the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable: ```bash export GOOGLE_GENERATIVE_AI_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/google`. Use the direct provider for Google-specific APIs such as [`google.interactions(...)`](#interactions-api), or when you need to customize the Google connection with [`createGoogle`](#provider-instance). The following sections document the direct `@ai-sdk/google` provider, including its model factories, tools, and settings. Gateway model and feature availability can differ from the direct provider. ## Provider Instance You can import the default provider instance `google` from `@ai-sdk/google`: ```ts import { google } from '@ai-sdk/google'; ``` If you need a customized setup, you can import `createGoogle` from `@ai-sdk/google` and create a provider instance with your settings: ```ts import { createGoogle } from '@ai-sdk/google'; const google = createGoogle({ // custom settings }); ``` You can use the following optional settings to customize the Google provider instance: - **batchResultDownloads** *object* Settings for downloading file-based JSON Lines batch results. - **maxLineBytes** *number* Maximum UTF-8 bytes per row, excluding the LF delimiter. Defaults to 64 MiB (67,108,864 bytes). Must be a positive safe integer. Oversized rows throw a `DownloadError` and cancel the download. - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use proxy servers. The default prefix is `https://generativelanguage.googleapis.com/v1beta`. - **apiKey** *string* API key that is being sent using the `x-goog-api-key` header. It defaults to the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. - **generateId** *() => string* Optional function to generate unique IDs for each request. Defaults to the SDK's built-in ID generator. - **name** *string* Custom provider name. Defaults to `'google.generative-ai'`. ## Language Models You can create models that call the [Google Generative AI API](https://ai.google.dev/api/rest) using the provider instance. The first argument is the model id, e.g. `gemini-4-argon`. The models support tool calls and some have multi-modal capabilities. ```ts const model = google('gemini-4-argon'); ``` You can use Google language models to generate text with the `generateText` function: ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const { text } = await generateText({ model: google('gemini-4-argon'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` Google language models can also be used in the `streamText` function and support structured data generation with [`Output`](/docs/reference/ai-sdk-core/output) (see [AI SDK Core](/docs/ai-sdk-core)). To remain forward-compatible, the provider treats unrecognized `gemini-*` model IDs and `-latest` aliases like the newest supported Gemini generation. Currently, this means Gemini 3 request behavior for provider-defined and mixed tools, `thinkingLevel` reasoning, multimodal function responses, and thought signatures. Known legacy Gemini model IDs keep their generation-specific request behavior. This SDK default controls request serialization; the selected model must still support each feature in the Google API. Google also supports some model specific settings that are not part of the [standard call settings](/docs/ai-sdk-core/settings). You can pass them as an options argument: ```ts import { google, type GoogleLanguageModelOptions } from '@ai-sdk/google'; const model = google('gemini-4-argon'); await generateText({ model, providerOptions: { google: { safetySettings: [ { category: 'HARM_CATEGORY_UNSPECIFIED', threshold: 'BLOCK_LOW_AND_ABOVE', }, ], } satisfies GoogleLanguageModelOptions, }, }); ``` The following optional provider options are available for Google models: - **cachedContent** *string* Optional. The name of the cached content used as context to serve the prediction. Format: cachedContents/\{cachedContent} - **structuredOutputs** *boolean* Optional. Enable structured output. Default is true. You can disable structured outputs when a schema is not supported or exceeds Google's schema complexity limits. The model will still be instructed to return JSON, but the schema will not be sent to Google. See [Troubleshooting: Schema Limitations](#schema-limitations) for more details. - **safetySettings** *Array\<\{ category: string; threshold: string }>* Optional. Safety settings for the model. - **category** *string* The category of the safety setting. Can be one of the following: - `HARM_CATEGORY_UNSPECIFIED` - `HARM_CATEGORY_HATE_SPEECH` - `HARM_CATEGORY_DANGEROUS_CONTENT` - `HARM_CATEGORY_HARASSMENT` - `HARM_CATEGORY_SEXUALLY_EXPLICIT` - `HARM_CATEGORY_CIVIC_INTEGRITY` - **threshold** *string* The threshold of the safety setting. Can be one of the following: - `HARM_BLOCK_THRESHOLD_UNSPECIFIED` - `BLOCK_LOW_AND_ABOVE` - `BLOCK_MEDIUM_AND_ABOVE` - `BLOCK_ONLY_HIGH` - `BLOCK_NONE` - `OFF` - **responseModalities** *string\[]* The modalities to use for the response. Valid values are `TEXT`, `IMAGE`, and `AUDIO`, but support is model-dependent. When not defined or empty, the model defaults to returning only text. - **thinkingConfig** *\{ thinkingLevel?: 'minimal' | 'low' | 'medium' | 'high'; thinkingBudget?: number; includeThoughts?: boolean }* Optional. Configuration for the model's thinking process. Only supported by specific [Google models](https://ai.google.dev/gemini-api/docs/thinking). - **thinkingLevel** *'minimal' | 'low' | 'medium' | 'high'* Optional. Controls the thinking depth for Gemini 3 and later models. Gemini 3.1 Pro supports 'low', 'medium', and 'high', Gemini 3 Pro supports 'low' and 'high', while Gemini 3 Flash supports all four levels: 'minimal', 'low', 'medium', and 'high'. - **thinkingBudget** *number* Optional. Gives the model guidance on the number of thinking tokens it can use when generating a response. Setting it to 0 disables thinking, if the model supports it. For more information about the possible value ranges for each model see [Google thinking documentation](https://ai.google.dev/gemini-api/docs/thinking#set-budget). This option is for Gemini 2.5 models. Gemini 3 and later models should use `thinkingLevel` instead. - **includeThoughts** *boolean* Optional. If set to true, thought summaries are returned, which are synthesized versions of the model's raw thoughts and offer insights into the model's internal reasoning process. - **imageConfig** *\{ aspectRatio?: string, imageSize?: string }* Optional. Configuration for the models image generation. Only supported by specific [Google models](https://ai.google.dev/gemini-api/docs/image-generation). - **aspectRatio** *string* Model defaults to generate 1:1 squares, or to matching the output image size to that of your input image. Can be one of the following: - 1:1 - 2:3 - 3:2 - 3:4 - 4:3 - 4:5 - 5:4 - 9:16 - 16:9 - 21:9 - **imageSize** *string* Controls the output image resolution. Defaults to 1K. Can be one of the following: - 1K - 2K - 4K - **audioTimestamp** *boolean* Optional. Enables timestamp understanding for audio-only files. See [Google Cloud audio understanding documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/audio-understanding). - **mediaResolution** *string* Optional. If specified, the media resolution specified will be used. Can be one of the following: - `MEDIA_RESOLUTION_UNSPECIFIED` - `MEDIA_RESOLUTION_LOW` - `MEDIA_RESOLUTION_MEDIUM` - `MEDIA_RESOLUTION_HIGH` See [Google API MediaResolution documentation](https://ai.google.dev/api/generate-content#MediaResolution). - **labels** *Record\* Optional. Defines labels used in billing reports. Available on Vertex AI only. See [Google Cloud labels documentation](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/add-labels-to-api-calls). - **serviceTier** *'standard' | 'flex' | 'priority'* Optional. The service tier to use for the request. Set to `'flex'` for 50% cheaper processing at the cost of increased latency. Set to `'priority'` for ultra-low latency at a 75-100% price premium over `'standard'`. Because Priority can be gracefully downgraded to Standard under load, the tier the request actually ran on is surfaced on `result.providerMetadata.google.serviceTier`. See [Priority inference](https://ai.google.dev/gemini-api/docs/priority-inference) and [Flex inference](https://ai.google.dev/gemini-api/docs/flex-inference). - **continuationToken** *string* Optional. Resumes a response that stopped because it used up its `maxOutputTokens` budget (Gemini 4 long decode). Send it with the original request and a higher `maxOutputTokens`. See [Long decode](#long-decode). - **threshold** *string* Optional. Standalone threshold setting that can be used independently of `safetySettings`. Uses the same values as the `safetySettings` threshold. ### Thinking The Gemini 2.5 and Gemini 3-and-later series models use an internal "thinking process" that significantly improves their reasoning and multi-step planning abilities, making them highly effective for complex tasks such as coding, advanced mathematics, and data analysis. For more information see [Google thinking documentation](https://ai.google.dev/gemini-api/docs/thinking). #### Gemini 3 and Later Models For Gemini 3 and later models, use the `thinkingLevel` parameter to control the depth of reasoning: ```ts import { google, GoogleLanguageModelOptions } from '@ai-sdk/google'; import { generateText } from 'ai'; const model = google('gemini-4-argon'); const { text, reasoning } = await generateText({ model: model, prompt: 'What is the sum of the first 10 prime numbers?', providerOptions: { google: { thinkingConfig: { thinkingLevel: 'high', includeThoughts: true, }, } satisfies GoogleLanguageModelOptions, }, }); console.log(text); console.log(reasoning); // Reasoning summary ``` #### Gemini 2.5 Models For Gemini 2.5 models, use the `thinkingBudget` parameter to control the number of thinking tokens: ```ts import { google, GoogleLanguageModelOptions } from '@ai-sdk/google'; import { generateText } from 'ai'; const model = google('gemini-2.5-flash'); const { text, reasoning } = await generateText({ model: model, prompt: 'What is the sum of the first 10 prime numbers?', providerOptions: { google: { thinkingConfig: { thinkingBudget: 8192, includeThoughts: true, }, } satisfies GoogleLanguageModelOptions, }, }); console.log(text); console.log(reasoning); // Reasoning summary ``` ### Long decode Gemini 4 models such as `gemini-4-argon` can keep thinking and writing past a single request's limits, up to the shared 1M token context window (input, thinking, and output tokens together). Use it for hard math, science, proofs, and long coding or agentic tasks. - A single request generates at most 32,768 tokens without streaming and 65,536 tokens with streaming (`streamText`). Thinking tokens count towards this cap. - `maxOutputTokens` is a **cumulative budget across requests**, not a target length. When a request reaches the per-request cap while budget remains, the provider automatically sends the same request again with a continuation token and combines the results into one response. Set `maxOutputTokens` above the per-request cap (up to 1M minus the input tokens) to enable this. If you do not set it, the call ends after one request. - A lower `maxOutputTokens` does not make the model think less, it ends the response early (possibly before any answer) and you are still charged for the tokens. Use `thinkingConfig.thinkingLevel` to control how much the model thinks. `thinkingBudget` is not supported by these models. - Complex requests can run for several minutes. Make sure your HTTP client allows read timeouts of at least 15 to 20 minutes. **Grounding across requests.** When a response is continued automatically, `providerMetadata.google.groundingMetadataPerRequest` contains the grounding metadata from each request, in order. An entry is `null` if that request did not return grounding metadata. This preserves earlier Search results even if the final request returns none, and keeps citation indices scoped to their original request. The existing `groundingMetadata` field keeps its current behavior; use the per-request array when inspecting a continued response. **Usage across requests.** Each continued request re-sends the prompt plus all output so far as input, so its prompt token count includes everything generated before it. `usage` is the sum of all requests, which matches what Google bills (re-sent tokens are billed at a discount through implicit caching). Do not use the summed input tokens for context window checks or compaction: they can be several times larger than the real context. When a response spanned more than one request, `providerMetadata.google.initialPromptTokenCount` holds the prompt token count of the first request (the google-genai SDKs expose the same value as `initial_prompt_token_count`): ```ts const result = await generateText({ model: google('gemini-4-argon'), prompt, maxOutputTokens: 500_000, }); // Billing: summed across all requests console.log(result.usage.inputTokens.total); // Context window: the first request's prompt plus the generated output const promptTokens = result.finalStep.providerMetadata?.google?.initialPromptTokenCount ?? result.usage.inputTokens.total; const contextTokens = promptTokens + (result.usage.outputTokens.total ?? 0); ``` **Resuming after the budget is used up.** When the cumulative generated tokens reach `maxOutputTokens`, the finish reason is `length` and the provider returns a continuation token on `result.providerMetadata.google.continuationToken`. To continue, send the **original request unchanged** (same prompt, tools, and settings; do not append the partial output as a message) together with the token as `providerOptions.google.continuationToken` and a **higher** `maxOutputTokens`. Resending with the same `maxOutputTokens` fails because the budget is used up. Each response contains only the newly generated content. ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const prompt = 'Prove that sqrt(2) is irrational, in detail.'; const result = await generateText({ model: google('gemini-4-argon'), prompt, maxOutputTokens: 128_000, }); const continuationToken = result.finalStep.providerMetadata?.google?.continuationToken; if (result.finishReason === 'length' && typeof continuationToken === 'string') { // The budget was used up. Raise it to keep going. const continued = await generateText({ model: google('gemini-4-argon'), prompt, maxOutputTokens: 256_000, providerOptions: { google: { continuationToken } }, }); console.log(result.text + continued.text); } ``` Things to keep in mind: - **Dropped streams.** Streamed parts are forwarded as they arrive and are not buffered between the server's checkpoints, so a stream that drops mid-request is not resumed automatically. Retry the request from the start. - **Provider metadata is per call.** If you store the conversation history, combine the content of all calls into one model message only after the final call finishes. Keep thought signatures unchanged, including the ones on empty trailing text parts. - **Zero Data Retention.** Continuation works with `store: false` because the state travels encrypted inside the token. #### Long decode with the Interactions API With the [Interactions API](#interactions-api), long decode requires `background: true`. The server runs every request itself, so no client-side continuation is needed, and `usage` covers all requests. Set an explicit `maxOutputTokens` above 65,536 (without it, you get a single 64K request) and keep `store` enabled (the default). The provider polls the interaction (or streams it, reconnecting when the connection drops) until it finishes. ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google.interactions('gemini-4-argon'), prompt: 'Prove that sqrt(2) is irrational, in detail.', maxOutputTokens: 500_000, providerOptions: { google: { background: true, pollingTimeoutMs: 60 * 60 * 1000, // default: 30 minutes }, }, }); ``` Without `background: true`, an interaction generates at most 65,536 tokens. Requests with a higher `maxOutputTokens` are rejected by the API, and an interaction that reaches the cap finishes with `length`. Long decode is not available with the Vertex AI Interactions API. ### File Inputs The Google provider supports file inputs, e.g. PDF files. ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google('gemini-4-argon'), messages: [ { role: 'user', content: [ { type: 'text', text: 'What is an embedding model according to this document?', }, { type: 'file', data: fs.readFileSync('./data/ai.pdf'), mediaType: 'application/pdf', }, ], }, ], }); ``` You can also use YouTube URLs directly: ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google('gemini-4-argon'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Summarize this video', }, { type: 'file', data: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', mediaType: 'video/mp4', }, ], }, ], }); ``` The AI SDK will automatically download URLs if you pass them as data, except for `https://generativelanguage.googleapis.com/v1beta/files/` and YouTube URLs. You can use the Google Generative AI Files API to upload larger files to that location. YouTube URLs (public or unlisted videos) are supported directly - you can specify one YouTube video URL per request. See [File Parts](/docs/foundations/prompts#file-parts) for details on how to use files in prompts. ### Cached Content Google supports both explicit and implicit caching to help reduce costs on repetitive content. #### Implicit Caching Gemini models automatically provide cache cost savings without needing to create an explicit cache. When you send requests that share common prefixes with previous requests, you can receive discounted pricing on cached input tokens. To maximize cache hits with implicit caching: - Keep content at the beginning of requests consistent - Add variable content (like user questions) at the end of prompts - Ensure requests meet minimum token requirements: - Gemini 3 series: 4096 tokens minimum - Gemini 2.5 series: 2048 tokens minimum ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; // Structure prompts with consistent content at the beginning const baseContext = 'You are a cooking assistant with expertise in Italian cuisine. Here are 1000 lasagna recipes for reference...'; const { text: veggieLasagna } = await generateText({ model: google('gemini-4-argon'), prompt: `${baseContext}\n\nWrite a vegetarian lasagna recipe for 4 people.`, }); // Second request with same prefix - eligible for cache hit const { text: meatLasagna, providerMetadata } = await generateText({ model: google('gemini-4-argon'), prompt: `${baseContext}\n\nWrite a meat lasagna recipe for 12 people.`, }); // Check cached token count in usage metadata console.log('Cached tokens:', providerMetadata.google); // e.g. // { // groundingMetadata: null, // safetyRatings: null, // usageMetadata: { // cachedContentTokenCount: 5027, // thoughtsTokenCount: 702, // promptTokenCount: 5152, // candidatesTokenCount: 710, // totalTokenCount: 6564 // } // } ``` Usage metadata was added to `providerMetadata` in `@ai-sdk/google@1.2.23`. If you are using an older version, usage metadata is available in the raw HTTP `response` body returned as part of the return value from `generateText`. #### Explicit Caching For guaranteed cost savings, you can still use explicit caching with Gemini 2.5 and 3 series models. Gemini 4 Argon supports implicit caching only, so this example uses `gemini-3.8-flash`. See the [models page](https://ai.google.dev/gemini-api/docs/models) to check if caching is supported for the used model: ```ts import { google, type GoogleLanguageModelOptions } from '@ai-sdk/google'; import { GoogleGenAI } from '@google/genai'; import { generateText } from 'ai'; const ai = new GoogleGenAI({ apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY, }); const model = 'gemini-3.8-flash'; // Create a cache with the content you want to reuse const cache = await ai.caches.create({ model, config: { contents: [ { role: 'user', parts: [{ text: '1000 Lasagna Recipes...' }], }, ], ttl: '300s', // Cache expires after 5 minutes }, }); const { text: veggieLasagnaRecipe } = await generateText({ model: google(model), prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { google: { cachedContent: cache.name, } satisfies GoogleLanguageModelOptions, }, }); const { text: meatLasagnaRecipe } = await generateText({ model: google(model), prompt: 'Write a meat lasagna recipe for 12 people.', providerOptions: { google: { cachedContent: cache.name, } satisfies GoogleLanguageModelOptions, }, }); ``` ### Code Execution With [Code Execution](https://ai.google.dev/gemini-api/docs/code-execution), certain models can generate and execute Python code to perform calculations, solve problems, or provide more accurate information. You can enable code execution by adding the `code_execution` tool to your request. ```ts import { google } from '@ai-sdk/google'; import { googleTools } from '@ai-sdk/google/internal'; import { generateText } from 'ai'; const { text, toolCalls, toolResults } = await generateText({ model: google('gemini-4-argon'), tools: { code_execution: google.tools.codeExecution({}) }, prompt: 'Use python to calculate the 20th fibonacci number.', }); ``` The response will contain the tool calls and results from the code execution. ### Google Search With [Google Search grounding](https://ai.google.dev/gemini-api/docs/google-search), the model has access to the latest information using Google Search. ```ts {8,17-19} import { google } from '@ai-sdk/google'; import { GoogleProviderMetadata } from '@ai-sdk/google'; import { generateText } from 'ai'; const { text, sources, providerMetadata } = await generateText({ model: google('gemini-4-argon'), tools: { google_search: google.tools.googleSearch({}), }, prompt: 'List the top 5 San Francisco news from the past week.' + 'You must include the date of each article.', }); // access the grounding metadata. Casting to the provider metadata type // is optional but provides autocomplete and type safety. const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined; const groundingMetadata = metadata?.groundingMetadata; const safetyRatings = metadata?.safetyRatings; ``` The `googleSearch` tool accepts the following optional configuration options: - **searchTypes** *object* Enables specific search types. Both can be combined. - `webSearch`: Enable web search grounding (pass `{}` to enable). This is the default. - `imageSearch`: Enable [image search grounding](https://ai.google.dev/gemini-api/docs/image-generation#image-search) (pass `{}` to enable). - **timeRangeFilter** *object* Restricts search results to a specific time range. Both `startTime` and `endTime` are required. - `startTime`: Start time in ISO 8601 format (e.g. `'2025-01-01T00:00:00Z'`). - `endTime`: End time in ISO 8601 format (e.g. `'2025-12-31T23:59:59Z'`). ```ts google.tools.googleSearch({ searchTypes: { webSearch: {} }, timeRangeFilter: { startTime: '2025-01-01T00:00:00Z', endTime: '2025-12-31T23:59:59Z', }, }); ``` When Google Search grounding is enabled, the model will include sources in the response. Additionally, the grounding metadata includes detailed information about how search results were used to ground the model's response. Here are the available fields: - **`webSearchQueries`** (`string[] | null`) - Array of search queries used to retrieve information - Example: `["What's the weather in Chicago this weekend?"]` - **`searchEntryPoint`** (`{ renderedContent: string } | null`) - Contains the main search result content used as an entry point - The `renderedContent` field contains the formatted content - **`groundingSupports`** (Array of support objects | null) - Contains details about how specific response parts are supported by search results - Each support object includes: - **`segment`**: Information about the grounded text segment - `text`: The actual text segment - `startIndex`: Starting position in the response - `endIndex`: Ending position in the response - **`groundingChunkIndices`**: References to supporting search result chunks - **`confidenceScores`**: Confidence scores (0-1) for each supporting chunk Example response: ```json { "groundingMetadata": { "webSearchQueries": ["What's the weather in Chicago this weekend?"], "searchEntryPoint": { "renderedContent": "..." }, "groundingSupports": [ { "segment": { "startIndex": 0, "endIndex": 65, "text": "Chicago weather changes rapidly, so layers let you adjust easily." }, "groundingChunkIndices": [0], "confidenceScores": [0.99] } ] } } ``` ### Enterprise Web Search With [Enterprise Web Search](https://cloud.google.com/vertex-ai/generative-ai/docs/grounding/web-grounding-enterprise), the model has access to a compliance-focused web index designed for highly-regulated industries such as finance, healthcare, and public sector. Enterprise Web Search is only available on Vertex AI. You must use the Google Vertex provider (`@ai-sdk/google-vertex`) instead of the standard Google provider (`@ai-sdk/google`) to use this feature. Requires Gemini 2.0 or newer models. ```ts import { createGoogleVertex } from '@ai-sdk/google-vertex'; import { generateText } from 'ai'; const vertex = createGoogleVertex({ project: 'my-project', location: 'us-central1', }); const { text, sources, providerMetadata } = await generateText({ model: vertex('gemini-4-argon'), tools: { enterprise_web_search: vertex.tools.enterpriseWebSearch({}), }, prompt: 'What are the latest regulatory updates for financial services?', }); ``` Enterprise Web Search provides the following benefits: - Does not log customer data - Supports VPC service controls - Compliance-focused web index for regulated industries ### File Search The [File Search tool](https://ai.google.dev/gemini-api/docs/file-search) lets Gemini retrieve context from your own documents that you have indexed in File Search stores. The provider enables this tool for Gemini 2.5 and Gemini 3-and-later model IDs, including unrecognized future Gemini IDs. ```ts {9-13} import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const { text, sources, providerMetadata } = await generateText({ model: google('gemini-4-argon'), tools: { file_search: google.tools.fileSearch({ fileSearchStoreNames: [ 'projects/my-project/locations/us/fileSearchStores/my-store', ], metadataFilter: 'author = "Robert Graves"', topK: 8, }), }, prompt: "Summarise the key themes of 'I, Claudius'.", }); ``` File Search responses include citations via the normal `sources` field and expose raw [grounding metadata](#google-search) in `providerMetadata.google.groundingMetadata`. Custom metadata attached to the indexed documents is available in `groundingMetadata.groundingChunks[].retrievedContext.customMetadata`. ### URL Context Google provides a provider-defined URL context tool. The URL context tool allows you to provide specific URLs that you want the model to analyze directly in from the prompt. ```ts {9,13-15} import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const { text, sources, providerMetadata } = await generateText({ model: google('gemini-4-argon'), prompt: `Based on the document: https://ai.google.dev/gemini-api/docs/url-context. Answer this question: How many links can we consume in one request?`, tools: { url_context: google.tools.urlContext({}), }, }); const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined; const groundingMetadata = metadata?.groundingMetadata; const urlContextMetadata = metadata?.urlContextMetadata; ``` The URL context metadata includes detailed information about how the model used the URL context to generate the response. Here are the available fields: - **`urlMetadata`** (`{ retrievedUrl: string; urlRetrievalStatus: string; }[] | null`) - Array of URL context metadata - Each object includes: - **`retrievedUrl`**: The URL of the context - **`urlRetrievalStatus`**: The status of the URL retrieval Example response: ```json { "urlMetadata": [ { "retrievedUrl": "https://ai-sdk.dev/providers/ai-sdk-providers/google", "urlRetrievalStatus": "URL_RETRIEVAL_STATUS_SUCCESS" } ] } ``` With the URL context tool, you will also get the `groundingMetadata`. ```json "groundingMetadata": { "groundingChunks": [ { "web": { "uri": "https://ai-sdk.dev/providers/ai-sdk-providers/google", "title": "Google - AI SDK Providers" } } ], "groundingSupports": [ { "segment": { "startIndex": 67, "endIndex": 157, "text": "**Installation**: Install the `@ai-sdk/google` module using your preferred package manager" }, "groundingChunkIndices": [ 0 ] } ] } ``` You can add up to 20 URLs per request. The URL context tool is only supported for Gemini 2.0 Flash models and above. Check the [supported models for URL context tool](https://ai.google.dev/gemini-api/docs/url-context#supported-models). #### Combine URL Context with Search Grounding You can combine the URL context tool with search grounding to provide the model with the latest information from the web. ```ts {9-10} import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const { text, sources, providerMetadata } = await generateText({ model: google('gemini-4-argon'), prompt: `Based on this context: https://ai-sdk.dev/providers/ai-sdk-providers/google, tell me how to use Gemini with AI SDK. Also, provide the latest news about AI SDK V5.`, tools: { google_search: google.tools.googleSearch({}), url_context: google.tools.urlContext({}), }, }); const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined; const groundingMetadata = metadata?.groundingMetadata; const urlContextMetadata = metadata?.urlContextMetadata; ``` ### Google Maps Grounding With [Google Maps grounding](https://ai.google.dev/gemini-api/docs/maps-grounding), the model has access to Google Maps data for location-aware responses. This enables providing local data and geospatial context, such as finding nearby restaurants. ```ts {7-16} import { google, type GoogleLanguageModelOptions } from '@ai-sdk/google'; import { GoogleProviderMetadata } from '@ai-sdk/google'; import { generateText } from 'ai'; const { text, sources, providerMetadata } = await generateText({ model: google('gemini-4-argon'), tools: { google_maps: google.tools.googleMaps({}), }, providerOptions: { google: { retrievalConfig: { latLng: { latitude: 34.090199, longitude: -117.881081 }, }, } satisfies GoogleLanguageModelOptions, }, prompt: 'What are the best Italian restaurants within a 15-minute walk from here?', }); const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined; const groundingMetadata = metadata?.groundingMetadata; ``` The optional `retrievalConfig.latLng` provider option provides location context for queries about nearby places. This configuration applies to any grounding tools that support location context, including Google Maps and Google Search. When Google Maps grounding is enabled, the model's response will include sources pointing to Google Maps URLs. The grounding metadata includes `maps` chunks with place information: ```json { "groundingMetadata": { "groundingChunks": [ { "maps": { "uri": "https://maps.google.com/?cid=12345", "title": "Restaurant Name", "placeId": "places/ChIJ..." } } ] } } ``` Google Maps grounding is supported on Gemini 2.0 and newer models. ### RAG Engine Grounding With [RAG Engine Grounding](https://cloud.google.com/vertex-ai/generative-ai/docs/rag-engine/use-vertexai-search#generate-content-using-gemini-api), the model has access to your custom knowledge base using the Vertex RAG Engine. This enables the model to provide answers based on your specific data sources and documents. RAG Engine Grounding is only supported with Vertex Gemini models. You must use the Google Vertex provider (`@ai-sdk/google-vertex`) instead of the standard Google provider (`@ai-sdk/google`) to use this feature. ```ts {5-8,13-17,25-27} import { createGoogleVertex } from '@ai-sdk/google-vertex'; import { GoogleProviderMetadata } from '@ai-sdk/google'; import { generateText } from 'ai'; const vertex = createGoogleVertex({ project: 'my-project', location: 'us-central1', }); const { text, sources, providerMetadata } = await generateText({ model: vertex('gemini-4-argon'), tools: { vertex_rag_store: vertex.tools.vertexRagStore({ ragCorpus: 'projects/my-project/locations/us-central1/ragCorpora/my-rag-corpus', topK: 5, }), }, prompt: 'What are the key features of our product according to our documentation?', }); // access the grounding metadata. Casting to the provider metadata type // is optional but provides autocomplete and type safety. const metadata = providerMetadata?.google as GoogleProviderMetadata | undefined; const groundingMetadata = metadata?.groundingMetadata; const safetyRatings = metadata?.safetyRatings; ``` When RAG Engine Grounding is enabled, the model will include sources from your RAG corpus in the response. Additionally, the grounding metadata includes detailed information about how RAG results were used to ground the model's response. Here are the available fields: - **`groundingChunks`** (Array of chunk objects | null) - Contains the retrieved context chunks from your RAG corpus - Each chunk includes: - **`retrievedContext`**: Information about the retrieved context - `uri`: The URI or identifier of the source document - `title`: The title of the source document (optional) - `text`: The actual text content of the chunk - **`groundingSupports`** (Array of support objects | null) - Contains details about how specific response parts are supported by RAG results - Each support object includes: - **`segment`**: Information about the grounded text segment - `text`: The actual text segment - `startIndex`: Starting position in the response - `endIndex`: Ending position in the response - **`groundingChunkIndices`**: References to supporting RAG result chunks - **`confidenceScores`**: Confidence scores (0-1) for each supporting chunk Example response: ```json { "groundingMetadata": { "groundingChunks": [ { "retrievedContext": { "uri": "gs://my-bucket/docs/product-guide.pdf", "title": "Product User Guide", "text": "Our product includes advanced AI capabilities, real-time processing, and enterprise-grade security features." } } ], "groundingSupports": [ { "segment": { "startIndex": 0, "endIndex": 45, "text": "Our product includes advanced AI capabilities and real-time processing." }, "groundingChunkIndices": [0], "confidenceScores": [0.95] } ] } } ``` #### Configuration Options The `vertexRagStore` tool accepts the following configuration options: - **`ragCorpus`** (`string`, required) - The RagCorpus resource name in the format: `projects/{project}/locations/{location}/ragCorpora/{rag_corpus}` - This identifies your specific RAG corpus to search against - **`topK`** (`number`, optional) - The number of top contexts to retrieve from your RAG corpus - Defaults to the corpus configuration if not specified ### Image Outputs Gemini models with image generation capabilities (e.g. `gemini-3.1-flash-image-preview`) support generating images as part of a multimodal response. Images are exposed as files in the response. ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google('gemini-3.1-flash-image-preview'), prompt: 'Create a picture of a nano banana dish in a fancy restaurant with a Gemini theme', }); for (const file of result.files) { if (file.mediaType.startsWith('image/')) { console.log('Generated image:', file); } } ``` If you primarily want to generate images without text output, you can also use Gemini image models with the `generateImage()` function. See [Gemini Image Models](#gemini-image-models) for details. ### Safety Ratings The safety ratings provide insight into the safety of the model's response. See [Google AI documentation on safety settings](https://ai.google.dev/gemini-api/docs/safety-settings). Example response excerpt: ```json { "safetyRatings": [ { "category": "HARM_CATEGORY_HATE_SPEECH", "probability": "NEGLIGIBLE", "probabilityScore": 0.11027937, "severity": "HARM_SEVERITY_LOW", "severityScore": 0.28487435 }, { "category": "HARM_CATEGORY_DANGEROUS_CONTENT", "probability": "HIGH", "blocked": true, "probabilityScore": 0.95422274, "severity": "HARM_SEVERITY_MEDIUM", "severityScore": 0.43398145 }, { "category": "HARM_CATEGORY_HARASSMENT", "probability": "NEGLIGIBLE", "probabilityScore": 0.11085559, "severity": "HARM_SEVERITY_NEGLIGIBLE", "severityScore": 0.19027223 }, { "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "probability": "NEGLIGIBLE", "probabilityScore": 0.22901751, "severity": "HARM_SEVERITY_NEGLIGIBLE", "severityScore": 0.09089675 } ] } ``` ### Troubleshooting #### Schema Limitations The Google Generative AI API supports a subset of JSON Schema. Google notes that [very large or deeply nested schemas may be rejected](https://ai.google.dev/gemini-api/docs/structured-output#limitations). When a schema exceeds Google's complexity limit, the API may return the following generic error: `Request contains an invalid argument.` You can work around this by simplifying the schema sent to Google. For example, remove unnecessary constraints or reduce nesting while preserving the fields and types the model needs to generate. If you need stricter client-side validation than the schema sent to Google, use [`jsonSchema`](/docs/reference/ai-sdk-core/json-schema) with a custom validation function or validate the generated output separately. By default, structured outputs are enabled (and for tool calling they are required). You can also disable provider-enforced structured outputs for object generation: ```ts {3,8} const { output } = await generateText({ model: google('gemini-4-argon'), providerOptions: { google: { structuredOutputs: false, } satisfies GoogleLanguageModelOptions, }, output: Output.object({ schema: z.object({ name: z.string(), age: z.number(), contact: z.union([ z.object({ type: z.literal('email'), value: z.string(), }), z.object({ type: z.literal('phone'), value: z.string(), }), ]), }), }), prompt: 'Generate an example person for testing.', }); ``` When `structuredOutputs` is `false`, Google still receives the JSON response MIME type but not the schema. Describe the required schema in your prompt. AI SDK will still validate the response against the schema in `Output.object`. ### Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | Google Search | URL Context | | ------------------------------------- | ----------- | ----------------- | ---------- | -------------- | ------------- | ----------- | | `gemini-nano-banana-2.1` | ✓ | ✗ | ✗ | ✗ | ✓ | ✗ | | `gemini-4-argon` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-3.8-flash` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-3.7-flash` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-3.6-flash` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-3.5-flash` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-3.5-flash-lite` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-3.1-pro-preview` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-3.1-flash-image-preview` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-3.1-flash-lite-preview` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-3-pro-preview` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-3-pro-image-preview` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-3-flash-preview` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-2.5-pro` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-2.5-flash` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-2.5-flash-lite` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-2.5-flash-lite-preview-06-17` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `gemini-2.0-flash` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | The table above lists popular models. Please see the [Google Generative AI docs](https://ai.google.dev/gemini-api/docs/models/) for a full list of available models. The table above lists popular models. You can also pass any available provider model ID as a string if needed. ### Batch Batch support is experimental and the API may change in patch releases. The Google provider supports asynchronous text generation through the [Gemini Batch API](https://ai.google.dev/gemini-api/docs/batch-api). Pass the Google provider to the AI SDK's [Batch](/docs/ai-sdk-core/batch) API for the complete workflow, including polling, persistence, and result handling. Each request specifies its `type` and `model`. Google requires every text request in a batch to use the same model and throws before submission when the models differ. #### Webhooks You can pass a `webhookUrl` to receive a notification when the batch reaches a terminal state: ```ts import { google } from '@ai-sdk/google'; import { experimental_startBatch as startBatch } from 'ai'; const model = 'gemini-4-argon'; const batch = await startBatch({ provider: google, requests: [ { id: 'capital-france', type: 'text', model, prompt: 'What is the capital of France?', }, { id: 'capital-germany', type: 'text', model, prompt: 'What is the capital of Germany?', }, ], webhookUrl: 'https://example.com/api/google-batch-webhook', }); ``` Google sends a thin event to the webhook URL. After receiving and verifying the event, use the persisted batch reference with `getBatchStatus` or `getBatchResults`. Your application is responsible for handling the webhook and [verifying Google's dynamic webhook signature](https://ai.google.dev/gemini-api/docs/webhooks#verify_dynamic_signatures_jwks). #### Large Batches When the serialized batch creation body is under 20 MB, the provider sends the requests inline. At 20 MB or more, it uploads a JSONL input file through the Gemini Files API. ## Realtime Models Realtime is an experimental feature. You can create models that call the [Gemini Live API](https://ai.google.dev/gemini-api/docs/live) using the `.experimental_realtime()` factory method. ```ts import { google } from '@ai-sdk/google'; const model = google.experimental_realtime('gemini-3.1-flash-live-preview'); ``` Realtime sessions run in the browser and require a short-lived token created on your server with `google.experimental_realtime.getToken()`: ```ts const token = await google.experimental_realtime.getToken({ model: 'gemini-3.1-flash-live-preview', }); ``` Google realtime models may require provider-specific audio formats, depending on the model and modality. See [Realtime](/docs/ai-sdk-core/realtime) for the complete setup and tool calling pattern. Gemini lifecycle signals that do not have provider-neutral equivalents are emitted as custom events. Inspect `rawType` for `goAway`, `sessionResumptionUpdate`, and `generationComplete` and use the event's `raw` payload for provider-specific details. With input audio transcription enabled, `parseServerEvent` emits `input-transcription-completed` events with a stable `itemId` for each user utterance. Each `transcript` contains the accumulated text so far. If you consume these events directly, replace the text for that `itemId` with `transcript` on each event. The realtime session handles this automatically. Google's `finished` transcription marker ends an utterance even when the marker contains no text. For payloads without this marker, response completion and interruption provide fallback boundaries. These fallback boundaries cannot unambiguously associate all delayed transcription fragments. Input item IDs use their own sequence and need not match the model's response IDs. ### Live Translation Use `gemini-3.5-live-translate-preview` for low-latency speech-to-speech translation. Configure the target language with `providerOptions.google.translationConfig` in the realtime session config you use when creating the token: ```ts import { google, type Experimental_GoogleRealtimeModelOptions as GoogleRealtimeModelOptions, } from '@ai-sdk/google'; const token = await google.experimental_realtime.getToken({ model: 'gemini-3.5-live-translate-preview', sessionConfig: { outputModalities: ['audio'], inputAudioTranscription: {}, outputAudioTranscription: {}, providerOptions: { google: { translationConfig: { targetLanguageCode: 'pl', echoTargetLanguage: true, }, } satisfies GoogleRealtimeModelOptions, }, }, }); ``` Set `targetLanguageCode` to a BCP-47 language code (defaults to `en`). When `echoTargetLanguage` is `true`, input audio already in the target language is echoed instead of producing silence. Gemini Live Translation accepts audio input and produces translated audio output. Text input, tools, and custom instructions are not supported by this model. ### Gemini 3.8 Live `gemini-3.8-live` and `gemini-3.8-live-extended-thinking` are audio-only realtime models: request `outputModalities: ['audio']`, and add `outputAudioTranscription: {}` if you want the assistant's transcript as `audio-transcript-delta` events. Server-side voice activity detection and proactive audio are always on. `gemini-3.8-live-extended-thinking` reasons in the background while streaming audio. Google requires exactly one of `thinkingLevel` (`low`, `medium`, `high`) or `thinkingBudget` for it; the provider sends `thinkingLevel: 'low'` when you set neither. Override it through `providerOptions.google.thinkingConfig`: ```ts import { google, type Experimental_GoogleRealtimeModelOptions as GoogleRealtimeModelOptions, } from '@ai-sdk/google'; const token = await google.experimental_realtime.getToken({ model: 'gemini-3.8-live-extended-thinking', sessionConfig: { outputModalities: ['audio'], outputAudioTranscription: {}, providerOptions: { google: { thinkingConfig: { thinkingLevel: 'high' }, } satisfies GoogleRealtimeModelOptions, }, }, }); ``` Tools declared in the session config run asynchronously (`NON_BLOCKING`) by default on these models, so the model keeps talking while a tool call is in flight. Set `providerOptions.google.defaultToolBehavior: 'BLOCKING'` on `gemini-3.8-live` to make it wait for the result instead. On the extended thinking model, `turnComplete` (the `response-done` event) does not mean the model is idle. Watch the `interactionStatus` custom event: its `raw.interactionStatus` is `IN_PROGRESS`, `IDLE`, or `WAITING_FOR_INPUT`. A `waitingForInput` custom event is emitted when the model has stopped and needs user input to continue. ## Translation Models Speech translation is an experimental feature. For server-side streaming translation, you can create models that call the [Gemini Live API](https://ai.google.dev/gemini-api/docs/live) using the `.translation()` factory method. Translation models are streaming-only and are used with [`experimental_streamTranslate`](/docs/reference/ai-sdk-core/stream-translate). The first argument is the model id e.g. `gemini-3.5-live-translate-preview`. ```ts const model = google.translation('gemini-3.5-live-translate-preview'); ``` ```ts import { experimental_streamTranslate as streamTranslate } from 'ai'; import { google } from '@ai-sdk/google'; const result = streamTranslate({ model: google.translation('gemini-3.5-live-translate-preview'), audio: audioStream, // ReadableStream inputAudioFormat: { type: 'audio/pcm', rate: 16000 }, targetLanguage: 'es', }); for await (const part of result.fullStream) { if (part.type === 'output-text-delta') { process.stdout.write(part.delta); } } ``` The Gemini Live API auto-detects the source language (`sourceLanguage` is not supported) and always outputs 24kHz 16-bit PCM audio (`outputAudioFormat` is not supported). Unsupported settings surface as call warnings. The following provider options are available: - **echoTargetLanguage** *boolean* Whether input audio already in the target language should be echoed instead of producing silence. Optional. ### Model Capabilities | Model | Translated Audio | Translated Text | Source Transcript | | ----------------------------------- | ---------------- | --------------- | ----------------- | | `gemini-3.5-live-translate-preview` | ✓ | ✓ | ✓ | ## Interactions API The [Gemini Interactions API](https://ai.google.dev/gemini-api/docs/interactions) (`POST /v1beta/interactions`) is a separate Google endpoint with server-side state, unified content blocks, first-class built-in tools, agent presets, managed agents that run in a sandboxed Linux environment, and native multimodal image and video output. It is reached via the `google.interactions(...)` factory: ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const { text } = await generateText({ model: google.interactions('gemini-4-argon'), prompt: 'Hello, how are you?', }); ``` `google.interactions(...)` accepts a model ID string (e.g. `'gemini-4-argon'`, `'gemini-3.1-pro-preview'`), `{ agent: }` to use a Gemini [agent preset](#agent-presets), or `{ managedAgent: }` to invoke a [managed agent](#managed-agents) you created on Google's side. The returned model can be passed to `generateText` and `streamText` like any other AI SDK language model. Use `google(...)` for the standard `:generateContent` / `:streamGenerateContent` endpoints, and `google.interactions(...)` for the new Interactions endpoint. Pick one per model instance — they target different request bodies and SSE event vocabularies. ### Provider Options The Interactions model reads its options from the shared `providerOptions.google.*` namespace. Validate them with the `GoogleLanguageModelInteractionsOptions` type: ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText } from 'ai'; await generateText({ model: google.interactions('gemini-4-argon'), prompt: 'What color is the sky in one word?', providerOptions: { google: { serviceTier: 'priority', } satisfies GoogleLanguageModelInteractionsOptions, }, }); ``` The following optional provider options are available: - **previousInteractionId** *string* Server-side interaction id from a prior turn. When set, the server pulls prior context from its own state and only the new user message is sent on the wire. Pair with the default `store: true` to chain stateful conversations. See [Stateful chaining](#stateful-chaining). - **store** *boolean* Whether the server should persist the interaction. Defaults to `true`. Set to `false` for stateless multi-turn conversations where the full message history is re-sent on every turn. - **agent** *string* Name of a Gemini agent preset (e.g. `'deep-research-pro-preview-12-2025'`). Prefer the factory form `google.interactions({ agent: '...' })` over setting `agent` in provider options — the factory is type-checked against the supported agent names. - **agentConfig** *object* Per-agent configuration. Currently supports `{ type: 'dynamic' }` and `{ type: 'deep-research', thinkingSummaries?, visualization?, collaborativePlanning? }`. - **thinkingLevel** *'minimal' | 'low' | 'medium' | 'high'* Controls reasoning depth for thinking-enabled models. Mapped onto the Interactions request's `thinking_level`. - **thinkingSummaries** *'auto' | 'none'* Whether the model returns synthesized thought summaries on reasoning parts. Defaults to the API default. - **responseFormat** *Array\* Output-format entries that map directly to the API's `response_format` array. Entries have the following shape: ```ts type ResponseFormatEntry = | { type: 'text'; mimeType?: string; schema?: unknown } | { type: 'image'; mimeType?: string; aspectRatio?: string; imageSize?: '1K' | '2K' | '4K' | '512'; } | { type: 'audio'; mimeType?: string } | { type: 'video'; aspectRatio?: '16:9' | '9:16'; resolution?: '360p' | '720p' | '1080p' | '4k'; duration?: string; delivery?: 'inline' | 'uri'; gcsUri?: string; }; ``` Use this for fine-grained control over image, audio, video, or non-JSON text outputs. Video entries can control aspect ratio, resolution, duration, and inline or URI delivery; set `gcsUri` when delivering to Google Cloud Storage. Inline videos are returned in `result.files` by `generateText` and emitted as `file` parts by `streamText`. The AI SDK call-level `responseFormat: { type: 'json', schema }` still drives JSON-mode automatically and prepends a matching text entry; entries listed here are appended. For image entries, `aspectRatio` accepts `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9`, `1:8`, `8:1`, `1:4`, `4:1`. - **imageConfig** *\{ aspectRatio?: string; imageSize?: '1K' | '2K' | '4K' | '512' }* (deprecated) Use **responseFormat** with a `{ type: 'image', ... }` entry instead. Retained for backwards compatibility; the SDK translates `imageConfig` into a matching `response_format` image entry and emits a warning when set. Ignored when `responseFormat` already supplies an image entry. - **mediaResolution** *'low' | 'medium' | 'high' | 'ultra\_high'* Media resolution applied to image inputs / outputs. - **serviceTier** *'flex' | 'standard' | 'priority'* Service tier for the request. Mirrored back on `result.providerMetadata.google.serviceTier` for observability. - **systemInstruction** *string* Alternative to the AI SDK `system` message. If both are set, the AI SDK `system` message wins and a warning is emitted. - **background** *boolean* Run the interaction in the background. Required for agents whose server-side workflow cannot complete within a single request/response; rejected by agents that only support synchronous calls. When `true`, the POST returns a non-terminal status and the SDK polls `GET /interactions/{id}` until the work completes. - **environment** *string | object* Sandbox environment configuration for [managed agents](#managed-agents). Pass `'remote'` to provision a fresh sandbox, an `environment_id` string to reuse an existing one, or an object of the form `{ type: 'remote', sources?, network? }` to preload files and/or constrain outbound traffic. Only applies to agent calls. - **pollingTimeoutMs** *number* Maximum time, in milliseconds, to poll a background interaction before giving up. Defaults to 30 minutes (1,800,000 ms). Long-running agents may need longer. ### Provider Metadata `result.providerMetadata.google` (typed via `GoogleInteractionsProviderMetadata`) exposes: - **interactionId** *string* Server-side interaction id. Pass this back as `previousInteractionId` on the next turn to chain. - **serviceTier** *string* Service tier the request actually ran on. - **signature** *string* Per-block signature hash, set by the SDK on output reasoning and tool-call or agentic video processing parts. Round-tripped automatically on the next turn. Agentic video custom parts additionally expose: - **processingId** *string* ID of an agentic video `processing_call`. Present on `google.processing_call` custom parts. - **processingCallId** *string* ID of the matching agentic video processing call. Present on `google.processing_result` custom parts. ### Stateful chaining With the default `store: true`, the server retains the prior turn so the next request only needs to send the new user message and the `previousInteractionId`: ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText } from 'ai'; const turn1 = await generateText({ model: google.interactions('gemini-4-argon'), prompt: 'What are the three largest cities in Spain?', }); const interactionId = turn1.providerMetadata?.google?.interactionId as | string | undefined; const turn2 = await generateText({ model: google.interactions('gemini-4-argon'), prompt: 'What is the most famous landmark in the second one?', providerOptions: { google: { previousInteractionId: interactionId, } satisfies GoogleLanguageModelInteractionsOptions, }, }); ``` For stateless multi-turn conversations, set `store: false` and re-send the full message history on every turn (no `previousInteractionId`): ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText, type ModelMessage } from 'ai'; const messages: Array = [ { role: 'user', content: 'What are the three largest cities in Spain?' }, ]; const turn1 = await generateText({ model: google.interactions('gemini-4-argon'), messages, providerOptions: { google: { store: false } satisfies GoogleLanguageModelInteractionsOptions, }, }); messages.push(...turn1.responseMessages); messages.push({ role: 'user', content: 'What is the most famous landmark in the second one?', }); const turn2 = await generateText({ model: google.interactions('gemini-4-argon'), messages, providerOptions: { google: { store: false } satisfies GoogleLanguageModelInteractionsOptions, }, }); ``` ### Agentic video understanding Gemini 4 Argon, 3.7 Flash, 3.6 Flash, and 3.5 Flash Lite support agentic video understanding through the Interactions API. Instead of sampling the entire video at a fixed frame rate, the model dynamically navigates the timeline and loads the transcripts, frames, or audio needed for the prompt. Set `processing: 'agentic'` in the Google provider options of an individual video file part. You can validate the per-file options with `GoogleInteractionsVideoOptions`: ```ts import { google, type GoogleInteractionsVideoOptions } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google.interactions('gemini-4-argon'), messages: [ { role: 'user', content: [ { type: 'file', data: 'https://www.youtube.com/watch?v=9hE5-98ZeCg', mediaType: 'video', providerOptions: { google: { processing: 'agentic', } satisfies GoogleInteractionsVideoOptions, }, }, { type: 'text', text: 'Identify the three most important moments and include timestamps.', }, ], }, ], providerOptions: { google: { thinkingSummaries: 'auto', }, }, }); const processingSteps = result.content.filter( part => part.type === 'custom' && (part.kind === 'google.processing_call' || part.kind === 'google.processing_result'), ); ``` Agentic navigation is exposed as `custom` content parts: - `google.processing_call` marks a request by the model to inspect part of the video. Its `providerMetadata.google.processingId` contains the processing ID. - `google.processing_result` marks completion of that request. Its `providerMetadata.google.processingCallId` links it to the processing call. These parts are informational and do not require a tool response. They are available from both `generateText` and `streamText`. For static processing, use `'static'` or provide clipping and frame-rate options. `startOffset` and `endOffset` are measured in seconds: ```ts const video = { type: 'file' as const, data: videoBytes, mediaType: 'video/mp4', providerOptions: { google: { processing: { type: 'static', startOffset: 0, endOffset: 10.5, fps: 0.5, }, } satisfies GoogleInteractionsVideoOptions, }, }; ``` With stateful conversations, pass the returned `interactionId` as `previousInteractionId`; Gemini retains the video context server-side. With `store: false`, append `result.responseMessages` and re-send the full history. The processing custom parts and their signatures are then round-tripped as native `processing_call` and `processing_result` steps so the video context is preserved. ### Built-in Tools The Interactions API ships a built-in tool catalog. The provider-defined tools under `google.tools.*` map onto Interactions tool descriptors: | AI SDK tool | Interactions tool type | Notes | | ------------------------------------- | ---------------------- | ----------------------------------------- | | `google.tools.googleSearch` | `google_search` | Web / image search grounding. | | `google.tools.codeExecution` | `code_execution` | Server-side Python execution. | | `google.tools.urlContext` | `url_context` | Fetch URLs referenced in the prompt. | | `google.tools.fileSearch` | `file_search` | Retrieval from File Search stores. | | `google.tools.googleMaps` | `google_maps` | Maps grounding for nearby-places queries. | | *provider tool* `google.computer_use` | `computer_use` | Computer use (browser environment). | | *provider tool* `google.mcp_server` | `mcp_server` | Remote MCP server passthrough. | | *provider tool* `google.retrieval` | `retrieval` | Vertex AI Search retrieval. | Function tools (`type: 'function'`) defined with the AI SDK `tool(...)` helper are translated to Interactions `function` tool descriptors. Other tool kinds emit a warning and are dropped. ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const { text, sources } = await generateText({ model: google.interactions('gemini-4-argon'), tools: { google_search: google.tools.googleSearch({}), }, prompt: "What's a notable AI development from this past week? " + 'Include the date for each item you mention.', }); ``` Function tools work the same way as on the standard provider: ```ts import { google } from '@ai-sdk/google'; import { generateText, isStepCount, tool } from 'ai'; import { z } from 'zod'; const weatherTool = tool({ description: 'Get the weather for a city.', inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => `It is sunny in ${city}.`, }); const { text, toolCalls } = await generateText({ model: google.interactions('gemini-4-argon'), tools: { getWeather: weatherTool }, stopWhen: isStepCount(5), prompt: 'What is the weather in San Francisco right now?', }); ``` ### Image output via Interactions Add a `{ type: 'image' }` entry to `responseFormat` on a Gemini image-capable model to get images as `LanguageModelV4FilePart` files in the response. No tool wrapping is required, and the entry doubles as the place to set `aspectRatio`, `imageSize`, and `mimeType`. ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google.interactions('gemini-3-pro-image-preview'), prompt: 'Generate an image of a comic cat in a spaceship.', providerOptions: { google: { responseFormat: [{ type: 'image' }], } satisfies GoogleLanguageModelInteractionsOptions, }, }); for (const file of result.files) { if (file.mediaType.startsWith('image/')) { // file.uint8Array | file.base64 | file.mediaType } } ``` To control aspect ratio, image size, or output mime type, add those fields to the same image entry: ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google.interactions('gemini-3-pro-image-preview'), prompt: 'Generate a high-quality landscape photo of mountains at sunset.', providerOptions: { google: { responseFormat: [ { type: 'image', aspectRatio: '16:9', imageSize: '4K', }, ], } satisfies GoogleLanguageModelInteractionsOptions, }, }); ``` For multimodal output, list one entry per modality. The model returns text in `result.text` and the accompanying image(s) in `result.files`: ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google.interactions('gemini-3.1-flash-image-preview'), prompt: 'Tell me a three sentence bedtime story about a unicorn, accompanied by a suitable illustration.', providerOptions: { google: { responseFormat: [ { type: 'text' }, { type: 'image', aspectRatio: '16:9' }, ], } satisfies GoogleLanguageModelInteractionsOptions, }, }); console.log(result.text); const images = result.files.filter(file => file.mediaType.startsWith('image/')); // images[0].uint8Array | images[0].base64 | images[0].mediaType ``` Iterative image editing pairs naturally with stateful chaining — keep `previousInteractionId` set across turns and the model edits its prior output: ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText } from 'ai'; const model = google.interactions('gemini-3-pro-image-preview'); const turn1 = await generateText({ model, prompt: 'Generate an image of a comic cat in a spaceship.', providerOptions: { google: { responseFormat: [{ type: 'image' }], } satisfies GoogleLanguageModelInteractionsOptions, }, }); const interactionId = turn1.providerMetadata?.google?.interactionId as | string | undefined; const turn2 = await generateText({ model, prompt: 'now make the cat red', providerOptions: { google: { responseFormat: [{ type: 'image' }], previousInteractionId: interactionId, } satisfies GoogleLanguageModelInteractionsOptions, }, }); ``` ### Agent presets Pass `{ agent: }` to target a Gemini agent preset. The factory type-checks the agent name against the supported set: ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google.interactions({ agent: 'deep-research-pro-preview-12-2025', }), prompt: 'Briefly summarize the most-cited papers on retrieval-augmented generation since 2024 (2-3 sentences).', providerOptions: { google: { background: true, } satisfies GoogleLanguageModelInteractionsOptions, }, }); ``` Whether an agent runs synchronously or in the background depends on the agent. Long-running presets (such as the `deep-research-*` family) require `background: true` — the POST returns a non-terminal status and the SDK polls `GET /interactions/{id}` internally until the interaction completes. Other agents accept synchronous calls only and will reject `background: true`. Set the flag explicitly via `providerOptions.google.background`. The default polling timeout is 30 minutes; raise it via `pollingTimeoutMs` for slower agents: ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText } from 'ai'; await generateText({ model: google.interactions({ agent: 'deep-research-max-preview-04-2026' }), prompt: 'Produce a long-form research brief on ...', providerOptions: { google: { background: true, pollingTimeoutMs: 60 * 60 * 1000, // 1 hour } satisfies GoogleLanguageModelInteractionsOptions, }, }); ``` Agents also chain through `previousInteractionId` like model-id calls. ### Managed Agents [Managed agents](https://ai.google.dev/gemini-api/docs/agents) run inside a sandboxed Linux environment provisioned per interaction. Pass the `environment` provider option to control how the sandbox is set up; the option is only accepted on agent calls. The simplest form provisions a fresh sandbox: ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google.interactions({ agent: 'antigravity-preview-05-2026' }), prompt: 'What is 2 + 2?', providerOptions: { google: { environment: 'remote', } satisfies GoogleLanguageModelInteractionsOptions, }, }); ``` `environment` accepts three shapes: - `'remote'` — provision a fresh sandbox for this call. - any other string — an `environment_id` to reuse, forking the previous sandbox so its filesystem and installed packages persist. - an object — provision a fresh sandbox and optionally preload `sources` and/or constrain outbound traffic via `network`: ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText } from 'ai'; await generateText({ model: google.interactions({ agent: 'antigravity-preview-05-2026' }), prompt: 'Read the file at /data/note.txt and tell me exactly what it contains.', providerOptions: { google: { environment: { type: 'remote', sources: [ { type: 'inline', content: 'hello from the AI SDK example\n', target: '/data/note.txt', }, ], }, } satisfies GoogleLanguageModelInteractionsOptions, }, }); ``` Three source types are supported: `inline` (write a string into the sandbox at `target`), `repository` (clone a git repository — pass the URL as `source`), and `gcs` (mount a Google Cloud Storage prefix). The `network` field accepts the string `'disabled'` to block all outbound traffic, or an object with an `allowlist` array whose entries each carry a `domain` plus an optional `transform` array of header objects to inject into matching requests. #### Custom managed agents For user-defined agents that you created on Google's side via the Gemini API's `/v1beta/agents` endpoint, pass the agent's name through the dedicated `managedAgent` factory shape instead of `agent` (which only accepts known preset names): ```ts import { google, type GoogleLanguageModelInteractionsOptions, } from '@ai-sdk/google'; import { generateText } from 'ai'; const result = await generateText({ model: google.interactions({ managedAgent: 'my-custom-agent' }), prompt: 'Hello!', providerOptions: { google: { environment: 'remote', } satisfies GoogleLanguageModelInteractionsOptions, }, }); ``` ### Streaming `streamText` is supported. The stream's `finish` part exposes `interactionId` on `providerMetadata.google` so callers can chain. ```ts import { google } from '@ai-sdk/google'; import { streamText } from 'ai'; const result = streamText({ model: google.interactions('gemini-4-argon'), prompt: 'Hello, how are you?', }); for await (const textPart of result.textStream) { process.stdout.write(textPart); } const googleMetadata = (await result.providerMetadata)?.google; console.log('Interaction id:', googleMetadata?.interactionId); ``` ## Gemma Models You can use [Gemma models](https://deepmind.google/models/gemma/) with the Google Generative AI API. The following Gemma models are available: - `gemma-3-27b-it` - `gemma-3-12b-it` Gemma models don't natively support the `systemInstruction` parameter, but the provider automatically handles system instructions by prepending them to the first user message. This allows you to use system instructions with Gemma models seamlessly: ```ts import { google } from '@ai-sdk/google'; import { generateText } from 'ai'; const { text } = await generateText({ model: google('gemma-3-27b-it'), instructions: 'You are a helpful assistant that responds concisely.', prompt: 'What is machine learning?', }); ``` The system instruction is automatically formatted and included in the conversation, so Gemma models can follow the guidance without any additional configuration. ## Embedding Models You can create models that call the [Google Generative AI embeddings API](https://ai.google.dev/gemini-api/docs/embeddings) using the `.embedding()` factory method. ```ts const model = google.embedding('gemini-embedding-2'); ``` The Google provider sends API calls to the right endpoint based on the type of embedding: - **Single embeddings**: When embedding a single value with `embed()`, the provider uses the single `:embedContent` endpoint, which typically has higher rate limits compared to the batch endpoint. - **Batch embeddings**: When embedding multiple values with `embedMany()` or multiple values in `embed()`, the provider uses the `:batchEmbedContents` endpoint. Google embedding models support additional settings. You can pass them as an options argument: ```ts import { google, type GoogleEmbeddingModelOptions } from '@ai-sdk/google'; import { embed } from 'ai'; const model = google.embedding('gemini-embedding-001'); const { embedding } = await embed({ model, value: 'sunny day at the beach', providerOptions: { google: { outputDimensionality: 512, // optional, number of dimensions for the embedding taskType: 'SEMANTIC_SIMILARITY', // optional, specifies the task type for generating embeddings content: [[{ text: 'additional context' }]], // optional, per-value multimodal content (only 1 here, since `value` is only a single one) } satisfies GoogleEmbeddingModelOptions, }, }); ``` When using `embedMany`, provide per-value multimodal content via the `content` option. Each entry corresponds to a value at the same index; use `null` for text-only entries: ```ts import { google, type GoogleEmbeddingModelOptions } from '@ai-sdk/google'; import { embedMany } from 'ai'; const { embeddings } = await embedMany({ model: google.embedding('gemini-embedding-2'), values: ['sunny day at the beach', 'rainy afternoon in the city'], providerOptions: { google: { // content array must have the same length as values content: [ [{ inlineData: { mimeType: 'image/png', data: '' } }], // pairs with values[0] null, // text-only, pairs with values[1] ], } satisfies GoogleEmbeddingModelOptions, }, }); ``` The following optional provider options are available for Google embedding models: - **outputDimensionality**: *number* Optional reduced dimension for the output embedding. If set, excessive values in the output embedding are truncated from the end. - **taskType**: *string* Optional. Specifies the task type for generating embeddings. Supported task types include: - `SEMANTIC_SIMILARITY`: Optimized for text similarity. - `CLASSIFICATION`: Optimized for text classification. - `CLUSTERING`: Optimized for clustering texts based on similarity. - `RETRIEVAL_DOCUMENT`: Optimized for document retrieval. - `RETRIEVAL_QUERY`: Optimized for query-based retrieval. - `QUESTION_ANSWERING`: Optimized for answering questions. - `FACT_VERIFICATION`: Optimized for verifying factual information. - `CODE_RETRIEVAL_QUERY`: Optimized for retrieving code blocks based on natural language queries. - **content**: *array* Optional. Per-value multimodal content parts for embedding non-text content (images, video, PDF, audio). Each entry corresponds to the embedding value at the same index — its parts are merged with the text value in the request. Use `null` for entries that are text-only. The array length must match the number of values being embedded. Each non-null entry is an array of parts, where each part can be `{ text: string }`, `{ inlineData: { mimeType: string, data: string } }` for inline base64 data, or `{ fileData: { fileUri: string, mimeType: string } }` to reference remote content via HTTP URL or Google Cloud Storage URI (`gs://...`). Supported by `gemini-embedding-2-preview`. ### Model Capabilities | Model | Default Dimensions | Custom Dimensions | Multimodal | | ---------------------------- | ------------------ | ----------------- | ---------- | | `gemini-embedding-001` | 3072 | ✓ | ✗ | | `gemini-embedding-2` | 3072 | ✓ | ✓ | | `gemini-embedding-2-preview` | 3072 | ✓ | ✓ | ## Image Models You can create image models that call the Google Generative AI API using the `.image()` factory method. For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image). The `.image()` factory supports Gemini multimodal language models with image output capabilities through the `:generateContent` API. ### Gemini Image Models [Gemini image models](https://ai.google.dev/gemini-api/docs/image-generation) (e.g. `gemini-3.1-flash-image-preview`) are technically multimodal output language models, but they can be used with the `generateImage()` function for a simpler image generation experience. Internally, the provider calls the language model API with `responseModalities: ['IMAGE']`. ```ts import { google } from '@ai-sdk/google'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: google.image('gemini-3.1-flash-image-preview'), prompt: 'A photorealistic image of a cat wearing a wizard hat', aspectRatio: '1:1', }); ``` Gemini image models also support image editing by providing input images: ```ts import { google } from '@ai-sdk/google'; import { generateImage } from 'ai'; import fs from 'node:fs'; const sourceImage = fs.readFileSync('./cat.png'); const { image } = await generateImage({ model: google.image('gemini-3.1-flash-image-preview'), prompt: { text: 'Add a small wizard hat to this cat', images: [sourceImage], }, }); ``` You can also use URLs for input images: ```ts import { google } from '@ai-sdk/google'; import { generateImage } from 'ai'; const { image } = await generateImage({ model: google.image('gemini-3.1-flash-image-preview'), prompt: { text: 'Add a small wizard hat to this cat', images: ['https://example.com/cat.png'], }, }); ``` Gemini image models do not support the `size` or `n` parameters. Use `aspectRatio` instead of `size`. Mask-based inpainting is also not supported. For more advanced use cases where you need both text and image outputs, or want more control over the generation process, you can use Gemini image models directly with `generateText()`. See [Image Outputs](#image-outputs) for details. #### Google Search Grounding Gemini image models support [Google Search grounding](#google-search) through `providerOptions.google.googleSearch`. The value matches the args of `google.tools.googleSearch(...)`; pass `{}` to enable with defaults, or `{ searchTypes: { imageSearch: {} } }` to ground on reference photos. ```ts import { google, type GoogleImageModelOptions } from '@ai-sdk/google'; import { generateImage } from 'ai'; const result = await generateImage({ model: google.image('gemini-3.1-flash-image-preview'), prompt: 'Search for live footage of the 2026 Super Bowl halftime show artist, then generate a close-up in space.', providerOptions: { google: { googleSearch: { searchTypes: { imageSearch: {} } }, } satisfies GoogleImageModelOptions, }, }); // Grounding metadata is forwarded onto the image result: console.log(result.providerMetadata?.google?.groundingMetadata); ``` #### Gemini Image Model Capabilities | Model | Image Generation | Image Editing | Aspect Ratios | | -------------------------------- | ---------------- | ------------- | ----------------------------------------------------------------------- | | `gemini-2.5-flash-image` | ✓ | ✓ | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | | `gemini-3-pro-image-preview` | ✓ | ✓ | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | | `gemini-3.1-flash-image-preview` | ✓ | ✓ | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9 | | `gemini-nano-banana-2.1` | ✓ | ✓ | 1:1, 2:3, 3:2, 3:4, 4:3, 4:5, 5:4, 9:16, 16:9, 21:9, 1:8, 8:1, 1:4, 4:1 | `gemini-3-pro-image-preview` supports additional features including up to 14 reference images for editing (6 objects, 5 humans), resolution options (1K, 2K, 4K via `providerOptions.google.imageConfig.imageSize`), and Google Search grounding. ## Speech Models You can create models that call the [Gemini text-to-speech API](https://ai.google.dev/gemini-api/docs/speech-generation) using the `.speech()` factory method. The first argument is the model ID, such as `gemini-3.8-flash-tts` or `gemini-3.8-flash-lite-tts`. Speech generation uses the `generateContent` API. ```ts const model = google.speech('gemini-3.8-flash-tts'); ``` The `voice` argument can be set to one of Gemini's [30 prebuilt voices](https://ai.google.dev/gemini-api/docs/speech-generation#voices) e.g. `Kore`, `Puck`, `Zephyr`, or `Charon`. Voice names are case-sensitive. The default voice is `Kore`. Custom voice IDs and replication keys are not supported. ```ts {6} import { generateSpeech } from 'ai'; import { google } from '@ai-sdk/google'; const result = await generateSpeech({ model: google.speech('gemini-3.8-flash-tts'), text: 'Hello, world!', voice: 'Kore', // Gemini voice name }); ``` By default the generated audio is returned as a playable WAV file (`result.audio.mediaType` is `audio/wav`). Gemini 3.8 returns a complete WAV file; the provider passes those bytes through unchanged. Older models return raw PCM, which the provider wraps in a WAV container. Write `result.audio.uint8Array` directly to disk without adding another WAV header. Gemini 3.8 supports the following `outputFormat` values: | Format | Values | Result | | ------ | ---------------------- | ---------------------------- | | WAV | `wav`, `audio/wav` | WAV with a RIFF header | | PCM | `pcm`, `audio/l16` | Headerless signed 16-bit PCM | | Mu-law | `mulaw`, `audio/mulaw` | Headerless mu-law audio | | A-law | `alaw`, `audio/alaw` | Headerless A-law audio | These map to `generationConfig.responseFormat.audio.mimeType` in `generateContent`. Older models support `wav` and `pcm`. The source MIME type and sample rate are available in `result.providerMetadata.google`. Gemini 2.5 and 3.1 model IDs retain the legacy request format. Other model IDs, including custom aliases, use the structured speech format. ### Speech directions in Gemini 3.8 Gemini 3.8 treats text as a verbatim transcript. The provider sends `instructions` as `speechMetadata.style`, leaving the text unchanged. You can also supply `providerOptions.google.speechMetadata` with `style` and `speaker` fields. An explicit style overrides `instructions`, including an empty style string. ```ts const result = await generateSpeech({ model: google.speech('gemini-3.8-flash-tts'), text: 'Welcome back. It is good to see you. ', instructions: 'Warm, relaxed, and speaking slowly', }); ``` Put sustained delivery directions such as whispering, speaking slowly, or being out of breath in metadata. Keep momentary vocal events and pauses such as ``, ``, ``, ``, and `` in the transcript. Avoid sound-effect tags such as applause or thuds. The provider preserves inline tags as written. For Gemini 3.1 and earlier, the provider retains the existing behavior of prepending `instructions` to single-speaker text. It ignores instructions with a warning for older multi-speaker requests. Structured `turns` and `speechMetadata` require Gemini 3.8. ### Multi-speaker audio For Gemini 3.8 dialogue, pass `multiSpeakerVoiceConfig` and `turns` through `providerOptions.google`. Every turn must include `speechMetadata.speaker` matching a configured speaker; missing or unknown speakers produce an error before the request is sent. Speaker labels belong in metadata, not the text. The `turns` array replaces the top-level text, so pass `text: ''`. The combined turn text must be non-empty. Passing nonempty top-level text with turns produces a warning. Put metadata on each turn instead of combining turns with top-level `speechMetadata`. Each turn's style overrides `instructions`; turns without a style inherit `instructions`. `multiSpeakerVoiceConfig` overrides the top-level `voice`. Each `voiceConfig` uses `prebuiltVoiceConfig: { voiceName: 'Kore' }`. Google supports up to two prebuilt voices in a multi-speaker request. ```ts import { generateSpeech } from 'ai'; import { google, type GoogleSpeechModelOptions } from '@ai-sdk/google'; const result = await generateSpeech({ model: google.speech('gemini-3.8-flash-lite-tts'), text: '', providerOptions: { google: { multiSpeakerVoiceConfig: { speakerVoiceConfigs: [ { speaker: 'Joe', voiceConfig: { prebuiltVoiceConfig: { voiceName: 'Kore' } }, }, { speaker: 'Jane', voiceConfig: { prebuiltVoiceConfig: { voiceName: 'Puck' } }, }, ], }, turns: [ { text: 'How are you?', speechMetadata: { speaker: 'Joe', style: 'curious' }, }, { text: ' Doing great, thanks!', speechMetadata: { speaker: 'Jane', style: 'cheerful' }, }, ], } satisfies GoogleSpeechModelOptions, }, }); ``` Older models continue to use labelled transcripts, such as `text: 'Joe: How are you? Jane: Doing great, thanks!'`, with `multiSpeakerVoiceConfig` and without `turns`. Gemini TTS models do not support the `speed` or `language` options; passing them adds a warning to `result.warnings`. Language is detected automatically from the input text. ### Model Capabilities | Model | Multi-speaker | Style via instructions | | ------------------------------ | ------------- | ---------------------- | | `gemini-2.5-flash-preview-tts` | ✓ | ✓ | | `gemini-2.5-pro-preview-tts` | ✓ | ✓ | | `gemini-3.1-flash-tts-preview` | ✓ | ✓ | | `gemini-3.8-flash-tts` | ✓ | ✓ | | `gemini-3.8-flash-lite-tts` | ✓ | ✓ | ## Decision Models Create an experimental decision model with `google.decisionModel(modelId)`. It uses Gemini structured output for Choice, Score, and Boolean questions. Choose a model that supports structured output, such as `gemini-4-argon`. ```ts import { google } from '@ai-sdk/google'; import { experimental_decide } from 'ai'; const { answers } = await experimental_decide({ model: google.decisionModel('gemini-4-argon'), state: 'I was charged twice.', questions: { requestsRefund: { type: 'boolean', instructions: 'Is the customer requesting money back?', }, department: { type: 'choice', instructions: 'Which team should handle this?', criteria: { billing: 'Charges and refunds', support: 'Other requests' }, }, }, providerOptions: { google: { thinkingConfig: { thinkingLevel: 'low' } }, }, }); ``` The factory respects `createGoogle` settings and forwards `providerOptions.google`, including thinking configuration. Thinking controls depend on the selected model; use options supported by that model. The existing Gemini implementation sends the answer schema as `generationConfig.responseJsonSchema`. Choice labels are returned exactly, and Scores are finite fractional positions within the ordered rubric. The adapter validates complete answers after parsing and rejects safety-blocked, truncated, or invalid output. It preserves usage (including reasoning tokens), warnings, response data, and provider metadata. Choice and Score answers do not include probability distributions. Boolean answers contain prompted estimates of P(true), validated to be finite and in `[0, 1]`. These estimates are not guaranteed to be calibrated. Apply thresholds in application code, for example `answers.requestsRefund.probability >= 0.5`. See [Decisions](/docs/ai-sdk-core/decisions). Decision models can also be accessed through `customProvider` aliases or `createProviderRegistry().decisionModel('provider:model')`. Direct string IDs use Gateway by default, or an explicitly configured default provider with an `decisionModel` method. See [model aliases and registries](/docs/ai-sdk-core/decisions#model-aliases-and-registries). --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: Groq description: Learn how to use Groq with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/groq" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [Groq](https://groq.com/) provider contains language model support for the Groq API. Start with [AI Gateway](/providers/ai-sdk-providers/ai-gateway) for models in its catalog (recommended), or use `@ai-sdk/groq` to connect directly to Groq. ```ts provider="groq" model="openai/gpt-oss-120b" import { generateText } from 'ai'; const { text } = await generateText({ model: "openai/gpt-oss-120b", prompt: 'Write a vegetarian lasagna recipe for 4 people.', providerOptions: { gateway: { order: ['groq'], }, }, }); console.log(text); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. The example sets `providerOptions.gateway.order` to `['groq']`. This sends the request to Groq first, then falls back to other available providers of the same model if Groq is unavailable or the request fails. `order` sets a preference; it does not restrict fallback to Groq. See [provider ordering](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). The dropdown lists models served by Groq through Gateway with a corresponding direct-provider example. Provider availability depends on the model and your team's [Provider Allowlist](https://vercel.com/docs/ai-gateway/models-and-providers/provider-filtering-and-ordering). ## Using `@ai-sdk/groq` For direct access to Groq, install `ai` and `@ai-sdk/groq`: ```bash pnpm add ai @ai-sdk/groq ``` Set your Groq API credential in the `GROQ_API_KEY` environment variable: ```bash export GROQ_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/groq`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/groq` provider, including its model factories and settings. Gateway model and feature availability can differ. ## Provider Instance You can import the default provider instance `groq` from `@ai-sdk/groq`: ```ts import { groq } from '@ai-sdk/groq'; ``` If you need a customized setup, you can import `createGroq` from `@ai-sdk/groq` and create a provider instance with your settings: ```ts import { createGroq } from '@ai-sdk/groq'; const groq = createGroq({ // custom settings }); ``` You can use the following optional settings to customize the Groq provider instance: - **baseURL** *string* Use a different URL prefix for API calls, e.g. to use proxy servers. The default prefix is `https://api.groq.com/openai/v1`. - **apiKey** *string* API key that is being sent using the `Authorization` header. It defaults to the `GROQ_API_KEY` environment variable. - **headers** *Record\* Custom headers to include in the requests. - **fetch** *(input: RequestInfo, init?: RequestInit) => Promise\* Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation. Defaults to the global `fetch` function. You can use it as a middleware to intercept requests, or to provide a custom fetch implementation for e.g. testing. ## Language Models You can create [Groq models](https://console.groq.com/docs/models) using a provider instance. The first argument is the model id, e.g. `llama-3.1-8b-instant`. ```ts const model = groq('llama-3.1-8b-instant'); ``` ### Reasoning Models Groq offers reasoning models such as `qwen/qwen3.6-27b` and `openai/gpt-oss-120b`. You can configure how the reasoning is exposed in the generated text by using the `reasoningFormat` option. It supports the options `parsed`, `hidden`, and `raw`. ```ts import { groq, type GroqLanguageModelChatOptions } from '@ai-sdk/groq'; import { generateText } from 'ai'; const result = await generateText({ model: groq('qwen/qwen3.6-27b'), providerOptions: { groq: { reasoningFormat: 'parsed', reasoningEffort: 'default', parallelToolCalls: true, // Enable parallel function calling (default: true) user: 'user-123', // Unique identifier for end-user (optional) serviceTier: 'flex', // Use flex tier for higher throughput (optional) } satisfies GroqLanguageModelChatOptions, }, prompt: 'How many "r"s are in the word "strawberry"?', }); ``` The following optional provider options are available for Groq language models: - **reasoningFormat** *'parsed' | 'raw' | 'hidden'* Controls how reasoning is exposed in the generated text. Supported by reasoning models such as `qwen/qwen3.6-27b`; available formats depend on the model. For a complete list of reasoning models and their capabilities, see [Groq's reasoning models documentation](https://console.groq.com/docs/reasoning). - **reasoningEffort** *'low' | 'medium' | 'high' | 'none' | 'default'* Controls the level of effort the model will put into reasoning. - `qwen/qwen3.6-27b` - Supported values: - `none`: Disable reasoning. The model will not use any reasoning tokens. - `default`: Enable reasoning. - `openai/gpt-oss-20b` and `openai/gpt-oss-120b` - Supported values: - `low`: Use a low level of reasoning effort. - `medium`: Use a medium level of reasoning effort. - `high`: Use a high level of reasoning effort. Defaults to `default` for `qwen/qwen3.6-27b`. - **structuredOutputs** *boolean* Whether to use structured outputs. Defaults to `true`. When enabled, object generation will use the `json_schema` format instead of `json_object` format, providing more reliable structured outputs. - **strictJsonSchema** *boolean* Whether to use strict JSON schema validation. When `true`, the model uses constrained decoding to guarantee schema compliance. Defaults to `true`. Only used when `structuredOutputs` is enabled and a schema is provided. See [Groq's Structured Outputs documentation](https://console.groq.com/docs/structured-outputs) for details on strict mode limitations. - **parallelToolCalls** *boolean* Whether to enable parallel function calling during tool use. Defaults to `true`. - **user** *string* A unique identifier representing your end-user, which can help with monitoring and abuse detection. - **serviceTier** *'on\_demand' | 'performance' | 'flex' | 'auto'* Service tier for the request. Defaults to `'on_demand'`. - `'on_demand'`: Default tier with consistent performance and fairness - `'performance'`: Prioritized tier for latency-sensitive workloads - `'flex'`: Higher throughput tier (10x rate limits) optimized for workloads that can handle occasional request failures - `'auto'`: Uses on\_demand rate limits first, then falls back to flex tier if exceeded For more details about service tiers and their benefits, see [Groq's service tiers documentation](https://console.groq.com/docs/service-tiers). Only Groq reasoning models support the `reasoningFormat` option. #### Structured Outputs Structured outputs are enabled by default for Groq models. You can disable them by setting the `structuredOutputs` option to `false`. ```ts import { groq } from '@ai-sdk/groq'; import { generateText, Output } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: groq('moonshotai/kimi-k2-instruct-0905'), output: Output.object({ schema: z.object({ recipe: z.object({ name: z.string(), ingredients: z.array(z.string()), instructions: z.array(z.string()), }), }), }), prompt: 'Generate a simple pasta recipe.', }); console.log(JSON.stringify(result.output, null, 2)); ``` You can disable structured outputs for models that don't support them: ```ts {9} import { groq, type GroqLanguageModelChatOptions } from '@ai-sdk/groq'; import { generateText, Output } from 'ai'; import { z } from 'zod'; const result = await generateText({ model: groq('llama-3.1-8b-instant'), providerOptions: { groq: { structuredOutputs: false, } satisfies GroqLanguageModelChatOptions, }, output: Output.object({ schema: z.object({ recipe: z.object({ name: z.string(), ingredients: z.array(z.string()), instructions: z.array(z.string()), }), }), }), prompt: 'Generate a simple pasta recipe in JSON format.', }); console.log(JSON.stringify(result.output, null, 2)); ``` Structured outputs are only supported by newer Groq models like `moonshotai/kimi-k2-instruct-0905`. For unsupported models, you can disable structured outputs by setting `structuredOutputs: false`. When disabled, Groq uses the `json_object` format which requires the word "JSON" to be included in your messages. ### Example You can use Groq language models to generate text with the `generateText` function: ```ts import { groq } from '@ai-sdk/groq'; import { generateText } from 'ai'; const { text } = await generateText({ model: groq('llama-3.1-8b-instant'), prompt: 'Write a vegetarian lasagna recipe for 4 people.', }); ``` ### Image Input Groq's multi-modal models like `meta-llama/llama-4-scout-17b-16e-instruct` support image inputs. You can include images in your messages using either URLs or base64-encoded data: ```ts import { groq } from '@ai-sdk/groq'; import { generateText } from 'ai'; const { text } = await generateText({ model: groq('meta-llama/llama-4-scout-17b-16e-instruct'), messages: [ { role: 'user', content: [ { type: 'text', text: 'What do you see in this image?' }, { type: 'file', mediaType: 'image', data: 'https://example.com/image.jpg', }, ], }, ], }); ``` You can also use base64-encoded images: ```ts import { groq } from '@ai-sdk/groq'; import { generateText } from 'ai'; import { readFileSync } from 'fs'; const imageData = readFileSync('path/to/image.jpg', 'base64'); const { text } = await generateText({ model: groq('meta-llama/llama-4-scout-17b-16e-instruct'), messages: [ { role: 'user', content: [ { type: 'text', text: 'Describe this image in detail.' }, { type: 'file', mediaType: 'image', data: `data:image/jpeg;base64,${imageData}`, }, ], }, ], }); ``` ## Model Capabilities | Model | Image Input | Object Generation | Tool Usage | Tool Streaming | | ----------------------------------------------- | ----------- | ----------------- | ---------- | -------------- | | `gemma2-9b-it` | ✗ | ✓ | ✓ | ✓ | | `llama-3.1-8b-instant` | ✗ | ✓ | ✓ | ✓ | | `llama-3.3-70b-versatile` | ✗ | ✓ | ✓ | ✓ | | `meta-llama/llama-guard-4-12b` | ✓ | ✓ | ✗ | ✗ | | `deepseek-r1-distill-llama-70b` | ✗ | ✓ | ✓ | ✓ | | `meta-llama/llama-4-maverick-17b-128e-instruct` | ✓ | ✓ | ✓ | ✓ | | `meta-llama/llama-4-scout-17b-16e-instruct` | ✓ | ✓ | ✓ | ✓ | | `meta-llama/llama-prompt-guard-2-22m` | ✗ | ✓ | ✗ | ✗ | | `meta-llama/llama-prompt-guard-2-86m` | ✗ | ✓ | ✗ | ✗ | | `moonshotai/kimi-k2-instruct-0905` | ✗ | ✓ | ✓ | ✓ | | `qwen/qwen3.6-27b` | ✗ | ✓ | ✓ | ✓ | | `llama-guard-3-8b` | ✗ | ✓ | ✓ | ✓ | | `llama3-70b-8192` | ✗ | ✓ | ✓ | ✓ | | `llama3-8b-8192` | ✗ | ✓ | ✓ | ✓ | | `mixtral-8x7b-32768` | ✗ | ✓ | ✓ | ✓ | | `qwen-qwq-32b` | ✗ | ✓ | ✓ | ✓ | | `qwen-2.5-32b` | ✗ | ✓ | ✓ | ✓ | | `deepseek-r1-distill-qwen-32b` | ✗ | ✓ | ✓ | ✓ | | `openai/gpt-oss-20b` | ✗ | ✓ | ✓ | ✓ | | `openai/gpt-oss-120b` | ✗ | ✓ | ✓ | ✓ | The tables above list the most commonly used models. Please see the [Groq docs](https://console.groq.com/docs/models) for a complete list of available models. You can also pass any available provider model ID as a string if needed. ## Browser Search Tool Groq provides a browser search tool that offers interactive web browsing capabilities. Unlike traditional web search, browser search navigates websites interactively, providing more detailed and comprehensive results. ### Supported Models Browser search is available on Groq's GPT-OSS models, for example: - `openai/gpt-oss-20b` - `openai/gpt-oss-120b` Known model IDs without browser search support (such as the Llama, Qwen, and Kimi models) will generate a warning and the tool will be ignored. Unknown model IDs are forwarded with the tool, and the Groq API decides whether it is supported. ### Basic Usage ```ts import { groq } from '@ai-sdk/groq'; import { generateText } from 'ai'; const result = await generateText({ model: groq('openai/gpt-oss-120b'), // Must use supported model prompt: 'What are the latest developments in AI? Please search for recent news.', tools: { browser_search: groq.tools.browserSearch({}), }, toolChoice: 'required', // Ensure the tool is used }); console.log(result.text); ``` ### Streaming Example ```ts import { groq } from '@ai-sdk/groq'; import { streamText } from 'ai'; const result = streamText({ model: groq('openai/gpt-oss-120b'), prompt: 'Search for the latest tech news and summarize it.', tools: { browser_search: groq.tools.browserSearch({}), }, toolChoice: 'required', }); for await (const delta of result.stream) { if (delta.type === 'text-delta') { process.stdout.write(delta.text); } } ``` ### Key Features - **Interactive Browsing**: Navigates websites like a human user - **Comprehensive Results**: More detailed than traditional search snippets - **Server-side Execution**: Runs on Groq's infrastructure, no setup required - **Powered by Exa**: Uses Exa search engine for optimal results - **Currently Free**: Available at no additional charge during beta ### Best Practices - Use `toolChoice: 'required'` to ensure the browser search is activated - Use a model that supports browser search, such as `openai/gpt-oss-20b` or `openai/gpt-oss-120b` - The tool works automatically - no configuration parameters needed - Server-side execution means no additional API keys or setup required ### Model Validation The provider ignores the tool for known model IDs without browser search support: ```ts // ✅ Supported - will work const result = await generateText({ model: groq('openai/gpt-oss-120b'), tools: { browser_search: groq.tools.browserSearch({}) }, }); // ❌ Unsupported - will show warning and ignore tool const result = await generateText({ model: groq('llama-3.1-8b-instant'), tools: { browser_search: groq.tools.browserSearch({}) }, }); // Warning: "Browser search is not supported on model llama-3.1-8b-instant." ``` For more details about browser search capabilities and limitations, see the [Groq Browser Search Documentation](https://console.groq.com/docs/browser-search). ## Transcription Models You can create models that call the [Groq transcription API](https://console.groq.com/docs/speech-to-text) using the `.transcription()` factory method. The first argument is the model id e.g. `whisper-large-v3`. ```ts const model = groq.transcription('whisper-large-v3'); ``` You can also pass additional provider-specific options using the `providerOptions` argument. For example, supplying the input language in ISO-639-1 (e.g. `en`) format will improve accuracy and latency. ```ts {6} import { transcribe } from 'ai'; import { groq, type GroqTranscriptionModelOptions } from '@ai-sdk/groq'; import { readFile } from 'fs/promises'; const result = await transcribe({ model: groq.transcription('whisper-large-v3'), audio: await readFile('audio.mp3'), providerOptions: { groq: { language: 'en' } satisfies GroqTranscriptionModelOptions, }, }); ``` The following provider options are available: - **timestampGranularities** *string\[]* The granularity of the timestamps in the transcription. Defaults to `['segment']`. Possible values are `['word']`, `['segment']`, and `['word', 'segment']`. Note: There is no additional latency for segment timestamps, but generating word timestamps incurs additional latency. **Important:** Requires `responseFormat` to be set to `'verbose_json'`. - **responseFormat** *string* The format of the response. Set to `'verbose_json'` to receive timestamps for audio segments and enable `timestampGranularities`. Set to `'text'` to return only the transcribed text. Optional. - **language** *string* The language of the input audio. Supplying the input language in ISO-639-1 format (e.g. 'en') will improve accuracy and latency. Optional. - **prompt** *string* An optional text to guide the model's style or continue a previous audio segment. The prompt should match the audio language. Optional. - **temperature** *number* The sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit. Defaults to 0. Optional. ### Model Capabilities | Model | Transcription | Duration | Segments | Language | | ------------------------ | ------------- | -------- | -------- | -------- | | `whisper-large-v3` | ✓ | ✓ | ✓ | ✓ | | `whisper-large-v3-turbo` | ✓ | ✓ | ✓ | ✓ | --- For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md) For an index of all available documentation, see [/llms.txt](/llms.txt) For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md) --- title: HeyGen description: Learn how to use HeyGen with AI Gateway and the direct provider. url: "https://ai-sdk.dev/providers/ai-sdk-providers/heygen" docs_index: /llms.txt --- > For an index of all documentation, see [/llms.txt](/llms.txt). The [HeyGen](https://developers.heygen.com/docs/models/heygen-video) provider supports `heygen-video-1`, a video generation model with text-to-video, image-to-video, and reference-to-video modes. Generated clips include audio. You can use HeyGen models through [AI Gateway](/providers/ai-sdk-providers/ai-gateway) (recommended) or connect directly with `@ai-sdk/heygen`. ```ts provider="heygen" model="heygen/heygen-video-1" import { experimental_generateVideo as generateVideo } from 'ai'; import { writeFile } from 'node:fs/promises'; const { video } = await generateVideo({ model: "heygen/heygen-video-1", prompt: 'A paper boat drifting across a calm lake at sunrise.', duration: 5, aspectRatio: '16:9', }); await writeFile('video.mp4', video.uint8Array); ``` ## Using AI Gateway The AI SDK uses AI Gateway by default for model strings. Gateway provides access to models across providers through a single API key, with usage and cost tracking. Install `ai`: ```bash pnpm add ai ``` Set an [AI Gateway API key](https://vercel.com/docs/ai-gateway/getting-started) in your environment: ```bash export AI_GATEWAY_API_KEY=your_api_key_here ``` Vercel deployments can also use [OIDC authentication](/providers/ai-sdk-providers/ai-gateway#oidc-authentication-vercel-deployments). See the [AI Gateway provider documentation](/providers/ai-sdk-providers/ai-gateway) for configuration and the [model catalog](https://vercel.com/ai-gateway/models) for available models. Gateway feature and provider-option support depends on the model and the provider serving the request. See [Gateway provider options](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options). ## Using `@ai-sdk/heygen` For direct access to HeyGen, install `ai` and `@ai-sdk/heygen`: ```bash pnpm add ai @ai-sdk/heygen ``` Set your HeyGen API credential in the `HEYGEN_API_KEY` environment variable: ```bash export HEYGEN_API_KEY=your_api_key_here ``` AI Gateway does not expose every API in `@ai-sdk/heygen`. Use the direct provider for provider-specific APIs or custom connection settings. The following sections document the direct `@ai-sdk/heygen` provider, including its model factories and settings. Gateway model and feature availability can differ. This provider supports video generation only. HeyGen's avatar, voice, translation, and video editing APIs are not included. ## Provider Instance Import the default instance: ```ts import { heygen } from '@ai-sdk/heygen'; ``` Or create a customized instance: ```ts import { createHeyGen } from '@ai-sdk/heygen'; const heygen = createHeyGen({ apiKey: process.env.HEYGEN_API_KEY, }); ``` The following settings are optional: - **apiKey** *string*: The key sent in the `x-api-key` header. Defaults to the `HEYGEN_API_KEY` environment variable. - **baseURL** *string*: The API URL prefix. Defaults to `https://api.heygen.com`. - **headers** *Record\*: Additional request headers. - **fetch** *typeof fetch*: A custom fetch implementation. ## Video Models Create a model with `heygen.video('heygen-video-1')` or `heygen.videoModel('heygen-video-1')`. ### Text to Video ```ts import { heygen } from '@ai-sdk/heygen'; import { experimental_generateVideo as generateVideo } from 'ai'; const { videos } = await generateVideo({ model: heygen.video('heygen-video-1'), prompt: 'A paper boat floats down a quiet stream. Static camera, soft morning light, the sound of flowing water. No music.', duration: 5, aspectRatio: '16:9', }); ``` ### Image to Video Pass an image in the prompt to use it as the first frame. The video follows the image's aspect ratio; a supplied `aspectRatio` is ignored with a warning. ```ts const { videos } = await generateVideo({ model: heygen.video('heygen-video-1'), prompt: { text: 'The subject moves slowly. Preserve the composition and natural lighting.', image: 'https://example.com/first-frame.jpg', }, duration: 5, }); ``` A single `first_frame` in `frameImages` is also supported. Last-frame conditioning and multiple first frames are rejected. ### Reference to Video Use `inputReferences` to condition a new scene on images or videos. Include a `mediaType` for explicit URL references so the provider can select the reference kind. ```ts const { videos } = await generateVideo({ model: heygen.video('heygen-video-1'), prompt: 'The product in sits on a desk beside a sunlit window.', inputReferences: [ { data: 'https://example.com/product.jpg', mediaType: 'image/jpeg', }, ], duration: 5, aspectRatio: '16:9', }); ``` References are grouped by media type, preserving order within each group. Address them in the prompt as ``, `