The ai npm package — the core of the Vercel AI SDK — pulled 22,962,178 downloads in the week of August 18–24, 2026 (per the npm registry’s own download API). Add the @ai-sdk/* provider packages built on top of it and it’s the most widely adopted AI application framework in the JavaScript ecosystem, by a wide margin, in 2026. It also shipped three major, deliberately-breaking versions — v5, v6, v7 — in under two years, each one renaming core functions, restructuring the message type, or replacing an entire generation function with a different pattern. This gallery already tracks the AI SDK’s own AGENTS.md, which documents the repository’s conventions for people contributing to vercel/ai itself. What’s missing everywhere — including here, until now — is a CLAUDE.md for people building on top of the SDK, where the problem isn’t contributor conventions but which of three incompatible API eras an agent’s next suggestion is going to come from.
That’s not a hypothetical. generateObject() and streamObject() were the standard, most-tutorialized way to get structured output from the SDK for years, right up until AI SDK 6.0 replaced them with an output option on generateText()/streamText(). The old functions are still what shows up most often by volume in an agent’s training data, simply because they existed longer and were written about more. Nothing about that code fails to compile in a v7 project if the old functions still resolve — it’s TypeScript, not a hard runtime wall, and the mismatch surfaces as confusing type errors or silently wrong runtime behavior, not a clean failure.
Why Generic TypeScript Rules Don’t Cover This
A CLAUDE.md written for TypeScript in general — strict mode, no any, functional patterns, Zod for validation — says nothing about which of generateText, generateObject, or Output.object() is the currently-correct way to get JSON out of a model call in this specific project. This gallery’s TypeScript guide covers the language-level rules; this one covers the version-specific ones that only apply once you’re building on the AI SDK.
Three things make the AI SDK worth a dedicated section rather than a paragraph inside a general TypeScript or Next.js guide:
The message type was split and renamed in v5, and the split matters semantically, not just syntactically. Message became UIMessage; CoreMessage became ModelMessage; .content (a string) became .parts (a typed array). This isn’t cosmetic — UIMessage is what you persist and render, ModelMessage is what the model actually consumes, and conflating the two (constructing a ModelMessage by hand in UI code, or persisting a ModelMessage as your database’s source of truth) throws away the reasoning parts, tool call/result data, and custom typed data that only UIMessage carries.
The primary structured-output API changed in v6, and both the old and new forms still type-check independently. generateObject()/streamObject() → generateText()/streamText() with output: Output.object({ schema }). An agent that picks the older pattern won’t get an error for it — it’ll get working code that just isn’t what the rest of a v6+/v7 codebase does, which is its own kind of maintenance debt.
v7 (June 2026) added an entirely new capability — running Claude Code itself as an AI SDK Agent — that predates most training data by definition. HarnessAgent didn’t exist before June 2026, so no amount of “the model is smart, it’ll figure it out” applies; if a project uses it, CLAUDE.md is the only way an agent knows it exists at all.
Check the Installed Version Before Applying Any of This
Every rule below is version-specific. The single highest-leverage line in an AI SDK CLAUDE.md is the one that states which major version the project is actually on, because applying v6/v7 rules to a v4 project (or vice versa) is worse than applying no rules — it actively tells the agent to write code that won’t compile.
## AI SDK Version
This project is on AI SDK [X].x (check `"ai"` in package.json — do not
assume). The rules below apply to v6/v7 specifically. If the installed
version is v4 or v5, most of this section does not apply — ask before
"fixing" code that looks outdated; it may just be a different major
version, not a bug.
# Quick version check before trusting any AI SDK-specific rule
cat package.json | grep '"ai":'
npx @ai-sdk/codemod --dry-run # shows what a migration would change, without applying it
The Message Model: UIMessage vs. ModelMessage
## Message Types (v5+)
- `UIMessage` (was `Message` pre-v5): the source of truth. Persist this.
Render this. It carries a `parts` array (text, reasoning, tool-call,
tool-result, file, custom data parts) — not a flat `.content` string.
- `ModelMessage` (was `CoreMessage` pre-v5): the trimmed, token-optimized
form the language model actually receives. Never persist this. Never
construct it by hand for UI-facing state.
- Convert at the boundary, right before the model call:
`convertToModelMessages(uiMessages)` — this is now async, await it.
// v5+ — correct: persist/render UIMessage, convert only at the model boundary
import { convertToModelMessages, streamText } from 'ai';
async function handleChat(uiMessages: UIMessage[]) {
const modelMessages = await convertToModelMessages(uiMessages); // now async
const result = streamText({
model: 'anthropic/claude-sonnet-4-5',
messages: modelMessages,
});
return result.toUIMessageStreamResponse();
}
// Pre-v5 pattern — will not compile against a v5+ Message/UIMessage type
// const result = streamText({ model, messages: coreMessages }); // CoreMessage no longer exported under that name
The parts array is the part of this that’s easy to half-migrate: reasoning content used to live on a top-level reasoning property and is now a { type: 'reasoning', text: '...' } part inside the array; the data role was removed outright in favor of typed custom data parts. Code that reads message.reasoning or checks message.role === 'data' is v4-era and needs updating, not a legitimate alternate pattern.
Structured Output: Output.object(), Not generateObject()
## Structured Output (v6+)
Use `generateText()` / `streamText()` with an `output` option, not the
standalone `generateObject()` / `streamObject()` functions:
const { output } = await generateText({
model,
output: Output.object({ schema: z.object({ ... }) }),
prompt: '...',
});
For streaming, read `result.partialOutputStream` as partial objects
arrive. Do not reach for `generateObject()` — it predates this project's
AI SDK version and duplicates functionality `Output.object()` now covers.
// v6+ — correct
import { generateText, Output } from 'ai';
import { z } from 'zod';
const { output } = await generateText({
model: 'anthropic/claude-sonnet-4-5',
output: Output.object({
schema: z.object({
title: z.string(),
tags: z.array(z.string()),
}),
}),
prompt: 'Summarize this article and extract tags.',
});
// output is typed from the Zod schema, not `any`
// Pre-6.0 pattern — the function may still exist for backward compatibility
// in some provider setups, but is not the pattern to write new code against
// const { object } = await generateObject({ model, schema, prompt });
Renamed Tool and Config Fields (v5)
A cluster of renames landed together in v5 and are easy to half-apply, since half the old names still exist elsewhere in the SDK for unrelated reasons:
## v5 Renames
- `maxTokens` → `maxOutputTokens`
- `providerMetadata` (as an input option) → `providerOptions`
- Tool definitions: `parameters` → `inputSchema`
- Tool call/result objects: `.args` → `.input`, `.result` → `.output`
- `StreamData` class: removed — use UI message streams with typed data
parts instead
- `maxSteps` → `stopWhen` (accepts `isStepCount(n)`, `hasToolCall(name)`,
or a combination via `stepCountIs`)
// v5+ tool definition — inputSchema, not parameters
const weatherTool = tool({
description: 'Get the current weather',
inputSchema: z.object({ location: z.string() }), // was `parameters`
execute: async ({ location }) => fetchWeather(location),
});
// v5+ step control — stopWhen, not maxSteps
import { stepCountIs } from 'ai';
const result = streamText({
model,
messages,
tools: { weatherTool },
stopWhen: stepCountIs(5), // was maxSteps: 5
});
Agent Class: ToolLoopAgent, Not Experimental_Agent
## Agent Construction (v6+)
Use `ToolLoopAgent` with an `instructions` field, not `Experimental_Agent`
with a `system` field — the experimental class is gone in v6+. Default
`stopWhen` for `ToolLoopAgent` is `isStepCount(20)` (was effectively 1 step
under the old default) — set it explicitly if the project needs a lower
step ceiling.
New in v7: HarnessAgent Can Run Claude Code Itself
This is the one AI SDK feature in this guide that’s specifically relevant to a Claude Code-using team, and it’s new enough (June 2026) that it postdates most training data outright:
## Agent Harnesses (v7+, if used)
This project uses `HarnessAgent` to run [Claude Code / Codex / Pi] as the
backing agent runtime inside a sandbox, rather than calling a single model
directly. It exposes the same `generate()`/`stream()` interface as any
other AI SDK Agent — UI code built on `useChat` does not need to change
when swapping which harness backs it.
import { HarnessAgent } from 'ai';
import { claudeCode } from '@ai-sdk/claude-code-harness'; // package name illustrative — confirm against project's actual dependency
const agent = new HarnessAgent({
harness: claudeCode,
sandbox: { /* sandbox provider config */ },
});
const result = await agent.stream({ messages: uiMessages });
v7 also added uploadFile/uploadSkill (upload once, reference across multiple model calls instead of re-sending the same file each request), WorkflowAgent (durable, resumable execution that survives process restarts), and experimental generateVideo and realtime WebSocket sessions via experimental_useRealtime(). None of these existed in v6 — if a project’s CLAUDE.md doesn’t mention them, an agent has no way to know they’re an option and will keep reaching for whatever pre-v7 pattern accomplishes something close to the same goal, less directly.
Provider and Validation Details Worth Stating
## Zod Version
This project uses Zod [3 / 4] — the AI SDK supports both, but import
paths differ between them for schema-related utilities. Check
package.json before assuming either.
## JSON Parsing
Never call `JSON.parse()` directly on model output or tool arguments in
production code. Use `parseJSON` or `safeParseJSON` from
`@ai-sdk/provider-utils` — they handle the partial/malformed JSON a model
can produce mid-stream without throwing an uncaught exception.
## Provider-Specific Notes
- Anthropic: supports a `structuredOutputMode` option (added v6).
- Google Vertex: provider options key is `vertex`, not `google`.
- OpenAI: `strictJsonSchema` defaults to `true` as of v6 (was `false`).
Complete CLAUDE.md Template
# CLAUDE.md
## Project Overview
[Next.js / Node.js / other] app using the Vercel AI SDK for [chat / agent /
structured-extraction] features. Providers: [Anthropic / OpenAI / etc].
## AI SDK Version
This project is on AI SDK [X].x — verify against package.json before
applying any version-specific rule below. Run `npx @ai-sdk/codemod
--dry-run` to preview what a migration would change without applying it.
## Message Types (v5+)
`UIMessage` (not `Message`) is the persisted/rendered source of truth,
carrying a `parts` array — not a `.content` string. `ModelMessage` (not
`CoreMessage`) is model-input-only; never persist it. Convert with
`await convertToModelMessages(uiMessages)` immediately before the model
call, not earlier.
## Structured Output (v6+)
Use `generateText()`/`streamText()` with `output: Output.object({ schema })`.
Do not use `generateObject()`/`streamObject()` for new code.
## Renamed Fields (v5)
`maxTokens` → `maxOutputTokens`. Tool definitions use `inputSchema`, not
`parameters`. Tool call/result objects use `.input`/`.output`, not
`.args`/`.result`. Step control uses `stopWhen` (e.g. `stepCountIs(5)`),
not `maxSteps`.
## Agents (v6+)
`ToolLoopAgent` with `instructions`, not `Experimental_Agent` with
`system`. Default `stopWhen` is `isStepCount(20)` — set explicitly if a
lower ceiling is needed.
## Validation
Zod [3 / 4] — check import paths before assuming either. Never
`JSON.parse()` model/tool output directly; use `parseJSON`/`safeParseJSON`
from `@ai-sdk/provider-utils`.
## What to Avoid
- `generateObject()`/`streamObject()` in new v6+ code — use `Output.object()`.
- `.content` on a `UIMessage`, or `message.role === 'data'` — both are pre-v5.
- `parameters` on a tool definition — use `inputSchema`.
- `maxSteps` — use `stopWhen`.
- `Experimental_Agent` — use `ToolLoopAgent`.
- Persisting a `ModelMessage`, or hand-constructing one in UI code.
- Assuming any of the above without first checking the installed major version.
CLAUDE.md vs AGENTS.md for an AI SDK Project
AGENTS.md holds the tool-agnostic layer: which version the project targets, the message-type and structured-output rules, the renamed fields, the agent-construction pattern. A Cursor or Codex agent generating AI SDK code needs the identical constraints a Claude Code session does — none of it is Claude-specific. CLAUDE.md adds Claude Code’s own operational layer on top: whether to run tsc --noEmit automatically after touching a file that imports from ai, how verbose to be when explaining a version-mismatch fix.
AGENTS.md → target version, message types, structured-output pattern, renamed fields, agent class
CLAUDE.md → "run tsc --noEmit after editing files importing from 'ai'", verbosity preferences
This gallery’s own vercel/ai AGENTS.md entry documents the SDK repository’s contributor conventions (pnpm/Turborepo monorepo layout, Vitest naming, the parseJSON/safeParseJSON security rule) — useful background on how the maintainers themselves write AI SDK code, but it’s aimed at people contributing to vercel/ai, not at a downstream project consuming the package. The rules in this guide are for the latter.
Putting It Together
None of this is the AI SDK being badly maintained — the opposite: shipping Output.object(), HarnessAgent, and WorkflowAgent inside eighteen months is exactly what “most widely adopted AI framework in the JS ecosystem, still moving fast” looks like. But it means an agent’s training data spans at minimum three incompatible API eras for the same package, and nothing about TypeScript’s type system forces old and new patterns to visibly conflict — generateObject() and Output.object() both type-check fine on their own, they just belong to different versions of the same codebase’s conventions.
The fix isn’t complicated: state the installed major version at the top of CLAUDE.md, list the specific renames and replacements that apply to it, and flag the newest capabilities (HarnessAgent above all — it’s the one feature here actually built to run Claude Code) that predate most training data by definition. Confirm the version against package.json before writing any of it, since applying v6/v7 rules to a project still on v4 does more harm than writing no AI SDK-specific rules at all.
Real-world TypeScript AI framework rules in our gallery include the vercel/ai AGENTS.md referenced above and the LangChain CLAUDE.md entry for a Python-side comparison of the same “framework evolves faster than training data” problem.
FAQ
Why does Claude Code write generateObject() in a Vercel AI SDK project that no longer uses it?
AI SDK 6.0 replaced generateObject()/streamObject() with an output option on generateText()/streamText(). The older function is still what shows up most in an agent’s training data by sheer volume — stating the current version and exact replacement syntax in CLAUDE.md is the only reliable fix, since both patterns type-check independently.
What is the difference between UIMessage and ModelMessage in the AI SDK?
UIMessage is what you persist and render — a full parts array with tool calls, reasoning, and custom data. ModelMessage is the trimmed form the model consumes. Convert with convertToModelMessages() right before the model call; never persist a ModelMessage or hand-construct one in UI code.
What is HarnessAgent in the Vercel AI SDK, and how does it relate to Claude Code?
Added in AI SDK 7 (June 2026), HarnessAgent runs a full external agent runtime — Claude Code, Codex, or Pi — inside a sandbox behind the same generate()/stream() interface as any other AI SDK Agent, so it can run Claude Code itself as an application’s tool-calling backend.
Why did maxTokens become maxOutputTokens in the AI SDK?
AI SDK 5 renamed it (along with providerMetadata → providerOptions and parameters → inputSchema) to remove ambiguity now that input and output tokens are billed and capped separately by some providers, and reasoning models can consume a large, separately-tracked budget before visible output appears.
How many major breaking-change versions has the Vercel AI SDK released, and how often?
Three in roughly two years as of August 2026 — v5, v6, and v7 — each renaming core APIs or adding capabilities (like HarnessAgent) that didn’t exist in the previous major version. Each ships an automated codemod, but the codemod only fixes existing code; it doesn’t stop an agent from writing new code in an outdated style from memory.