Back to course
1 / 1·25m

Module 3 · Lesson 1 · 25m

AI SDK 7: What's New

A tour of the new primitives in AI SDK 7 that change how you build production agents, durable workflows, and sandboxed harnesses.

The crash course and production controls lessons cover the contract that everything else in the AI SDK sits on top of: generateText, streamText, tools, stop conditions, and the three boundaries that keep a chat feature sane.

AI SDK 7 adds a second layer for the cases where a simple request/response loop is not enough. This lesson is a map of that layer. It is not a deep implementation of every feature, but it will tell you which door to open when your product starts asking for long-running agents, sandboxed coding assistants, or richer tool UIs.

Reasoning control

Reasoning is no longer something you configure through provider-specific options alone. AI SDK 7 exposes a provider-agnostic reasoning option on generateText and streamText.

const result = await generateText({
  model: openai('gpt-5.5'),
  prompt: 'Plan a careful migration.',
  reasoning: 'high',
});

If you also pass reasoning settings through providerOptions, the provider-specific settings win. Remove the overlap so the top-level option actually controls the behavior.

Tool context and approval

In v7, the context you pass to tools is scoped and typed.

  • runtimeContext holds shared state for the whole generation or agent run.
  • toolsContext holds per-tool values, keyed by tool name.
  • A tool declares what it needs with contextSchema, so the tool callback receives only the fields it asked for.
const weather = tool({
  inputSchema: z.object({ location: z.string() }),
  contextSchema: z.object({ apiKey: z.string() }),
  execute: async ({ location }, { context: { apiKey } }) => {
    return getWeather(location, apiKey);
  },
});

await streamText({
  model,
  tools: { weather },
  runtimeContext: { requestId: 'req-123' },
  toolsContext: { weather: { apiKey: process.env.WEATHER_API_KEY! } },
});

Approval also moves out of the tool definition. Instead of needsApproval on tool(), you set toolApproval on the call or agent.

await streamText({
  model,
  tools: { deleteFile },
  toolApproval: {
    deleteFile: async ({ path }) =>
      path.startsWith('/tmp/') ? undefined : 'user-approval',
  },
});

This keeps the tool itself small and testable, and puts the gating decision where the request is actually configured.

Files, skills, and message parts

User-facing image parts are deprecated. Use file parts with an image mediaType.

{ type: 'file', mediaType: 'image/png', data: bytes }

Tool result content also collapses into the same file shape, with a tagged data union for inline data, URLs, and provider references.

Skills are reusable instruction bundles. They matter most when you start using harnesses, where a skill is a named package of instructions, tools, and behavior that you can attach to an agent.

MCP Apps

@ai-sdk/mcp lets an MCP server expose tools that are model-visible, app-visible, or both. The split is handled by splitMCPAppTools, and the app metadata stays attached to the tool UI part so your React renderer can decide whether to draw a custom UI for it.

This is the bridge between "the model can call a tool" and "the tool needs its own frontend."

Durable workflows with WorkflowAgent

A ToolLoopAgent lives in memory. If the process crashes, the loop is gone. WorkflowAgent, from @ai-sdk/workflow, runs each step as a durable workflow step.

import { WorkflowAgent, type ModelCallStreamPart } from '@ai-sdk/workflow';
import { convertToModelMessages, type UIMessage } from 'ai';
import { getWritable } from 'workflow';

export async function chat(messages: UIMessage[]) {
  'use workflow';
  const agent = new WorkflowAgent({
    model: 'anthropic/claude-sonnet-4-6',
    instructions: 'You are a booking assistant.',
    tools: { searchFlights, bookFlight },
  });
  const result = await agent.stream({
    messages: await convertToModelMessages(messages),
    writable: getWritable<ModelCallStreamPart>(),
  });
  return { messages: result.messages };
}

The route starts the workflow and returns a resumable stream.

import { createModelCallToUIChunkTransform } from '@ai-sdk/workflow';
import { createUIMessageStreamResponse, type UIMessage } from 'ai';
import { start } from 'workflow/api';

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 },
  });
}

On the client, WorkflowChatTransport reconnects automatically when the stream is interrupted by a timeout, a deploy, or a page refresh.

Harnesses and sandbox support

A harness is a complete agent runtime, not just a model. AI SDK 7 provides HarnessAgent and adapters for Claude Code, Codex, and Pi, each running inside a sandbox.

import { HarnessAgent } from '@ai-sdk/harness/agent';
import { codex } from '@ai-sdk/harness-codex';
import { createVercelSandbox } from '@ai-sdk/sandbox-vercel';

const agent = new HarnessAgent({
  harness: codex,
  sandbox: createVercelSandbox({ runtime: 'node24', ports: [4000] }),
});

const session = await agent.createSession();
try {
  const result = await agent.stream({ session, prompt: 'Fix the tests' });
  for await (const part of result.stream) {
    if (part.type === 'text-delta') process.stdout.write(part.text);
  }
} finally {
  await session.destroy();
}

The harness runtime owns workspace state, built-in tools, and permission flows. AI SDK translates its events into the same stream and response shapes you already use, so a harness stream can feed the same UI boundary as a regular model stream.

Terminal UI

@ai-sdk/tui renders harness or agent streams in the terminal. It handles tool calls, reasoning sections, and approval prompts. This is useful for local tooling, CLI agents, and debugging before you wire the harness into a web UI.

Telemetry

Telemetry graduates out of the experimental namespace. In v7, you register a telemetry integration once, and every AI SDK call is traced by default.

import { registerTelemetry } from 'ai';
import { OpenTelemetry } from '@ai-sdk/otel';

registerTelemetry(new OpenTelemetry());

Opt out per call with telemetry: { isEnabled: false }. The per-call option is now just telemetry, not experimental_telemetry.

Lifecycle events

The lifecycle callbacks are renamed and stabilized:

  • onStart
  • onStepStart
  • onToolExecutionStart
  • onToolExecutionEnd
  • onStepEnd
  • onEnd

These work on generateText, streamText, and agents, and they are the right place for logging, tracing, and telemetry.

What to reach for next

ProblemReach for
One-shot text or structured outputgenerateText / streamText
Multi-step tool loop in memoryToolLoopAgent
Multi-step loop that must survive restartsWorkflowAgent
Use an existing coding agent runtimeHarnessAgent
Tool needs its own custom UIMCP Apps
Local debugging of an agent loop@ai-sdk/tui or DevTools
Observability in production@ai-sdk/otel + registerTelemetry

These are not separate universes. They all speak the same message-part, tool, and stream contracts you already learned. The only thing that changes is how long the loop is allowed to live, and how much of the world it is allowed to touch.