SYS/2026.Q1Agentic SEO audits delivered in 72 hoursSee how →
DevelopmentMigration14 min readPublished May 15, 2026

useChat changes, tool-call streaming differences, provider-adapter contracts shifting — the v6 changes that touch every page you've built.

Vercel AI SDK v5 to v6 Migration Playbook: 2026

Vercel AI SDK v6 lands four breaking-change axes at once — the useChat message-parts model, tool-call streaming lifecycle, provider-adapter contracts, and the streaming wire format. This playbook covers what each axis means in production code, what the official codemods cover, and the phased rollout that gets you to v6 without a long-tail incident week.

DA
Digital Applied Team
Senior engineers · Published May 2026
PublishedMay 15, 2026
Read time14 min
SourcesAI SDK 6 release notes
Breaking-change axes
4
useChat · tools · providers · streaming
Codemod coverage
70–80%
mechanical changes only
Typical migration duration
1–2 wk
phased rollout, mid-size app
Streaming latency improvement
15–25%
first-token, p50

The Vercel AI SDK v5 to v6 migration is the largest breaking-change release the library has shipped since the AI SDK 3.0 split — four independent axes of change land in the same major: the useChat hook moves to a message-parts model, the tool-call streaming lifecycle picks up explicit run-call hooks, provider adapters break across all four mainstream vendors, and the streaming wire format gets a 15–25% first-token latency improvement that pays for the disruption.

What's at stake. The AI SDK powers the chat surface, the tool-augmented agent, and the multi-provider routing layer in most modern Next.js AI products. A four-axis breaking release touches every one of those surfaces simultaneously, which means a single careless cut-over produces a multi-symptom outage that's painful to triage in production. The cost of getting this wrong is measured in incident hours, not codemod minutes.

What this guide covers. The four breaking axes laid out plainly, the old-versus-new shape of useChatso you can recognize the changes in your own code, the tool-call streaming lifecycle and how to render the new parts, the provider-adapter contract shift across Anthropic / OpenAI / Google, what the official codemods cover (and the 20–30% they don't), a phased rollout in five stages, and the four pitfalls that surface in production once the wrap-shim comes off. Everything below assumes you're on a recent v5.x line and shipping to Vercel.

Key takeaways
  1. 01
    The useChat message-parts model is a meaningful UX upgrade.Adopt it fully, not partially. The new parts array carries text, tool calls, tool results, and reasoning traces as first-class entries — render every part type or you'll silently drop tool-call UX the moment a tool is invoked.
  2. 02
    Tool-call streaming changes the perceived intelligence of agents.Use the new run-call lifecycle hooks (onToolCall, onStepFinish) to render skeleton states during tool execution and structured tool-result cards on completion. The lifecycle is what makes the agent feel deliberate instead of opaque.
  3. 03
    Provider-adapter contract shifts affect provider-switching logic.Wrap before cutting over. Anthropic, OpenAI, and Google all changed their factory signatures and provider-options shapes in lockstep with the core release — a single thin lib/model.ts shim isolates the change from every call site.
  4. 04
    Codemods cover 70-80% of mechanical changes.The rest is judgment. Vercel ships codemods for the import-path rewrites, the useChat hook shape, and the tool() helper signature, but the routing layer, custom tool-result rendering, and any non-trivial provider-options usage are hand work.
  5. 05
    Streaming latency improves by 15-25% in v6.Measure your specific endpoints. The wire-format change reduces per-token overhead, but the gain depends on your model, provider, and region. Benchmark p50 first-token latency before and after — and verify the gain offsets the rollout cost.

01What's Newv6 ships in four axes — useChat, tools, providers, streaming.

The AI SDK v6 release is unusual in modern library practice because it bundles four independently-motivated breaking changes into one major version rather than spreading them across three consecutive majors. The bundling is deliberate — each axis depends on the others — but it means migration cost concentrates into one window instead of amortizing across the year.

The four axes, in the order they show up when you upgrade. First, the useChat hook moves from a flat content string per message to a structured parts array — every message becomes a sequence of typed parts (text, tool-call, tool-result, reasoning) that render-loops iterate over. Second, tool-call streaming picks up an explicit run-call lifecycle with new hooks (onToolCall, onStepFinish, onFinish) and a richer streamed-part contract. Third, every provider adapter — Anthropic, OpenAI, Google, xAI — changes its factory signature and provider-options shape in lockstep. Fourth, the streaming wire format updates to reduce per-token overhead, which is where the 15–25% first-token latency improvement comes from.

Why ship all four together. The streaming-format change required a new part-typed protocol, which required the message-parts model on the client, which required tool calls to flow through the same channel as text, which required provider adapters to expose tool-call streaming uniformly. Each axis is a strict prerequisite for the next. Splitting them across majors would have meant either shipping incomplete intermediate states or carrying two protocols in parallel for a year — neither was tolerable.

Axis 1
useChat message parts
messages[].parts[] · typed entries

Each message is now an ordered parts array — text, tool-call, tool-result, reasoning, and image parts as first-class entries. Render loops match on part.type rather than reading a flat content string. The biggest source of silent regressions.

Client-side · render every part
Axis 2
Tool-call lifecycle
onToolCall · onStepFinish · onFinish

Tool execution is now a streamed lifecycle: tool-call part flushes pre-execution (skeleton render), tool-result part flushes post-execution, multi-step agent loops surface step boundaries. Replaces v5's all-or-nothing tool-execution batch.

Route-handler + client
Axis 3
Provider adapters
@ai-sdk/{anthropic,openai,google,xai}@^6

Factory signatures change across all mainstream vendors. providerOptions replaces inline model params; native tool-output streaming added for Anthropic and OpenAI. Wrap with a thin getModel() helper before cutting over.

Server-side · all providers
Axis 4
Streaming wire format
toUIMessageStreamResponse() · part-typed SSE

New typed-event SSE protocol with reduced per-token overhead. Source of the 15-25% first-token latency improvement. Mixed v5/v6 across the wire produces silent stream-parse failures — pin client and server together.

Protocol-level · pin together
The pin-together rule
The single most important deploy-time rule for this migration: pin ai@^6 and @ai-sdk/react@^6 as a matched set, and make sure every route handler in the deployment ships at the same major as the client bundle that calls it. Mixed v5/v6 across the wire produces silent stream-parse failures — the request completes, the response stream opens, and the client renders nothing because it can't parse the event types. Bisecting this in production after a partial rollout is the worst-case scenario for this migration.

The release-note framing from Vercel is honest about scope. v6 is not a polish release — it's a structural one. The payoff is the part-typed protocol (a meaningful UX upgrade for tool-augmented agents) and the latency improvement; the cost is one focused migration sprint. Most mid-sized AI products land in the 1–2 week window covered by the phased rollout in Section 06.

02useChat ChangesMessage-parts model and streamlined errors.

The useChathook is the surface area most v5 codebases touch most often, so it's also where the migration produces the most visible breakage. The headline change: every message is now an ordered parts array of typed entries, not a flat content string. The shape is identical for user and assistantroles; the difference is what entries you'll see inside each.

Why the change matters. In v5, a single assistant turn could carry text plus tool-call metadata, but rendering it required inspecting auxiliary arrays on the message object — toolInvocations, experimental_attachments — that lived alongside content. The v6 parts array unifies that into a single ordered sequence, which means a render loop that handles every part type renders the full turn in the correct order without any auxiliary look-up. The cost of the migration is that any render loop reading m.content directly silently drops everything tool- related the moment a tool is invoked.

Old shape vs new shape — side by side

The choice matrix below shows the contract change in the four most-touched places: the message render loop, the input submission helper, the streaming status flag, and the error surface. Every codebase touches at least three of these.

Render loop
Message shape

v5: m.content is a flat string; tool calls live on m.toolInvocations. v6: m.parts is an ordered array of { type: 'text' | 'tool-call' | 'tool-result' | 'reasoning', ... }. Render must iterate parts, not read content.

v6 parts array
Submission
Helper signature

v5: append({ role: 'user', content: text }) or handleSubmit(e). v6: sendMessage({ text }) for the common case; sendMessage({ parts: [...] }) for attachments and structured inputs. handleSubmit is gone — wire the form yourself.

sendMessage({ text })
Status flag
Streaming state

v5: isLoading boolean. v6: status enum — 'ready' | 'submitted' | 'streaming' | 'error'. The 'submitted' state covers the request-in-flight-but-no-tokens-yet window that v5 conflated with isLoading. Use it for the optimistic-spinner UX.

status enum
Error surface
Error handling

v5: error object surfaced asynchronously, sometimes silently. v6: useChat returns error directly; route-handler errors flow through the stream as a typed error part and surface in the onError callback with a structured payload. Easier to log, harder to ignore.

structured error part

Two implementation rules worth internalizing during the mechanical sweep. First, never flatten parts to a string in a render helper — tool-call parts have to render as their own component (skeleton during pending, structured card when complete), and reasoning parts get folded by default. A naive parts.map(p => p.text ?? '').join('') silently drops every part that isn't text. Second, treat the messages array as read-only in render code; the hook owns the array and exposes setMessages only for advanced cases (initial-history hydration, server-side persistence rehydration).

"The parts array is the contract change that pays for the rest of the migration. Render every part type or the agent looks broken the moment a tool fires."— Our reading of the AI SDK 6 migration notes, May 2026

On the submission side, the loss of handleSubmit is mostly an ergonomic regression — you wire onSubmit on the form yourself and call sendMessage({ text: input }). The upside is that attachments and structured inputs now flow through the same sendMessage entry point — pass a parts array to send a user message that includes an image or a structured form payload. v5 required a separate attachments API; v6 collapses them into one surface.

The new status enum is the single biggest UX quality-of-life upgrade in the hook. The submitted state — request in flight but the first token has not arrived — is the right time to show a chat-input spinner; the streaming state is the right time to show a tail-cursor on the assistant placeholder; the ready state is the right time to re-enable the input. v5 conflated all three into isLoading, which made it impossible to differentiate the right loading-state UX without timing heuristics.

03Tool-Call StreamingStreaming parts and the run-call lifecycle.

Tool calls are the second axis of breakage and the one most likely to surface in production rather than in development. In v5, a tool call was effectively atomic from the client's perspective — the assistant message arrived with a fully- populated toolInvocationsarray after the tool executed server-side. In v6, the tool-call lifecycle is streamed as discrete parts and lifecycle events, which lets you render the "agent is currently searching for X" UX in real time instead of waiting for the round trip to finish.

The lifecycle in v6 has four phases per tool invocation: a tool-call part flushes the moment the model emits structured tool-call output, an onToolCall callback fires server-side at the same moment, the execute function runs to completion, and a tool-result part flushes with the structured result. For multi-step agent loops, onStepFinish fires at each step boundary and onFinish fires once when the model emits a final text response.

Tool-call lifecycle · per-invocation event order

Source: AI SDK 6 streaming protocol reference
tool-call part flushesClient renders 'Searching for <query>…' skeleton
t=0
onToolCall fires server-sideLogging / telemetry hook · pre-execution
t≈0
execute() runsYour tool function · variable duration
t = exec
tool-result part flushesClient swaps skeleton for structured result card
t = exec+1
onStepFinish firesPer-step usage metrics · routing-aware loops
per step
onFinish firesTotal usage breakdown · cost calc · DB persistence
turn end

The practical impact on the client is small if you already render every part type from Section 02 — the new tool-call and tool-result parts arrive in the same parts array as text, just at the right timestamps. The practical impact on the server is bigger: onToolCall and onStepFinish are the right hooks for the observability layer (token counts per step, tool-call latency per tool, structured cost calculation), and onFinishreplaces v5's pattern of inspecting the response object after streamText resolved.

The streaming-aware render pattern
The new lifecycle only pays off if the render loop matches on part.type === 'tool-call' and part.type === 'tool-result' as distinct cases. The first renders a skeleton card ("Searching for <query>…") while the tool is executing; the second renders the structured result with click-through. If both cases collapse into a generic "tool used" chip, you lose half the perceived-quality gain of v6 and the migration looks like it didn't do anything.

One operational note on the lifecycle: stopWhen replaces v5's maxToolRoundtrips as the multi-step-loop cap. The helper stepCountIs(n) is the direct equivalent; richer stop conditions (hasToolCall(name), custom predicates) are now first-class. For chat-style agents, stepCountIs(5) is a reasonable default — high enough to handle "search, fetch one result, answer" chains, low enough to bound cost on a runaway loop.

04Provider AdapterAnthropic, OpenAI, Google — contract shifts.

Every mainstream provider adapter changes shape in v6, and the changes are not uniform across vendors. The core abstraction survives — each provider still exports a factory that returns a model object — but the factory signatures and the provider-options shapes diverge. The right move is to isolate the provider call behind a thin lib/model.ts wrapper so the rest of the route handler is portable across providers and the adapter changes hit one file, not every route.

The pattern across vendors. Each provider factory now takes a model ID plus an optional providerOptionsobject instead of inline parameters; Anthropic adds native streaming for tool outputs; OpenAI's factory exposes structured- output JSON schemas via generateObjectwith tightened type inference; Google's Gemini factory normalizes its tool-call format to match the rest of the SDK instead of carrying a vendor-specific shape. xAI's Grok adapter has the smallest contract delta.

Anthropic
+native
Tool-output streaming

anthropic('claude-sonnet-4-7') now streams tool outputs as they generate, not after they complete. providerOptions takes thinking config, cache control, and beta flags. Most material upgrade in the adapter set.

@ai-sdk/anthropic@^6
OpenAI
+JSON
Structured outputs

openai('gpt-5-5') exposes JSON-schema-mode via generateObject with tighter type inference. providerOptions takes reasoning effort, response format, and audio config. Tool-call surface harmonized with Anthropic.

@ai-sdk/openai@^6
Google
+norm
Tool-call normalization

google('gemini-3.1-pro') drops the vendor-specific tool-call shape and matches the rest of the SDK's tool() contract. providerOptions takes safety settings and grounding config. Long-context cache cost picked up.

@ai-sdk/google@^6
The wrap-shim pattern
The shortest path through the provider migration is to wrap before cutting over. Create lib/model.ts that exports a single getModel() function — every route handler imports getModel()instead of calling the vendor factory directly. The shim isolates the v5-to-v6 contract change to one file; the rest of the codebase doesn't know there's a migration happening. When the dust settles, you can remove the wrap or keep it — either way the migration cost is bounded.

One practical implication of the lockstep provider release: the cross-provider switch in Section 06 of our Next.js 16 AI chatbot tutorial needs a v6 sweep at the same time as the core migration. The adapter shape changes are uniform enough that a single getModel()helper covers all four providers, but you can't leave one provider on v5 while migrating the others — they ship together. Plan the codemod sweep against every adapter in a single commit.

Provider-options is the second piece worth pinning down. In v5, model parameters (temperature, max tokens, stop sequences) lived inline at the call site; in v6 they move to a structured providerOptionsobject on the call. The change is mostly mechanical for common parameters, but vendor-specific knobs (Anthropic's thinking config, OpenAI's reasoning effort, Google's safety settings) now live in vendor-specific sub-objects under providerOptions — codemods catch the import-path rewrites but not the provider-options reshaping.

05CodemodsWhat Vercel ships, and what they miss.

The official codemod surface is the right starting point for the migration — run it before any hand work — but the coverage is roughly 70 to 80 percent of mechanical changes, not 100 percent of the migration. Knowing where the codemods stop is the difference between a one-day mechanical sweep and a one-week production headache.

Run the official sweep with npx @ai-sdk/codemod upgrade at the repo root after bumping ai and the provider packages to ^6. The codemod rewrites import paths, converts append and handleSubmit usage to sendMessage, flips isLoading to the new status enum where the rewrite is safe, and updates the tool() helper signature.

Covered
Import-path rewrites

Every import from 'ai' and '@ai-sdk/*' is rewritten to v6 paths. Symbol renames (e.g. UIMessage shape, tool helper) are handled. Safe to run blind on a clean working tree; produces a single commit.

Run first · codemod owns it
Covered
useChat mechanical

append → sendMessage and handleSubmit → sendMessage rewrites land cleanly. isLoading → status === 'streaming' is conservative — the codemod flags the call sites where 'submitted' might also apply but leaves the judgment to you.

Codemod + spot-check
Missed
Render-loop parts

Any render code reading m.content directly stays unchanged — the codemod can't infer your render intent. Hand work. Sweep with a grep for m.content and m.toolInvocations after the codemod completes.

Hand work · grep + rewrite
Missed
Provider-options reshape

Vendor-specific provider-options (Anthropic thinking, OpenAI reasoning effort, Google safety) get partially rewritten but the structural reshape to providerOptions sub-objects is hand work. Read the adapter changelog and sweep manually.

Hand work · per-vendor

The four places to grep after the codemod completes: m.content (any direct content reads in render), toolInvocations (v5 auxiliary array, now empty), isLoading (any uses the codemod flagged for manual review), and experimental_ (v5 experimental APIs renamed or removed in v6). A single rg sweep on those four substrings catches most of the remaining hand work.

The codemod is intentionally conservative on judgment calls — it will leave ambiguous rewrites in place with a comment marker rather than ship a wrong rewrite silently. Treat the codemod output as a starting commit, not a finished migration. The spot-check pass that follows is where the production-quality migration actually happens.

06Phased RolloutAssess → codemod → wrap → cut over → retire.

The phased rollout below is the shape that gets a mid-sized AI product from v5 to v6 in one to two weeks without a long-tail incident week. Each phase has a single primary deliverable and a single rollback condition — keeping phases narrow is what makes the rollout debuggable when something fails.

Phase 1
Assess
1–2 days · read-only

Inventory every useChat call site, every streamText route handler, every provider factory call. Capture current p50 first-token latency per endpoint as a baseline. Read the v6 release notes end-to-end. No code changes.

Deliverable: migration scope doc
Phase 2
Codemod
1 day · mechanical sweep

Bump ai, @ai-sdk/react, and every provider adapter to ^6 on a feature branch. Run the official codemod. Spot-check the four grep targets from Section 05. Commit as a single 'v6 codemod' commit so the diff is reviewable.

Deliverable: codemod commit
Phase 3
Wrap
1–2 days · shim layer

Create lib/model.ts with getModel(). Add a thin renderParts(parts) helper to centralize the new render loop. Wrap every route handler and every render site behind the shims. Build and test green.

Deliverable: shimmed dev build
Phase 4
Cut over
1 day · feature-flag ramp

Deploy v6 behind a feature flag (Edge Config or a Postgres row). Ramp 5% → 25% → 50% → 100% over four hours with latency and error-rate dashboards open. Roll back via flag, not redeploy, if either metric regresses.

Deliverable: 100% v6 in prod
Phase 5
Retire
2–3 days · cleanup

Remove the wrap shim (or keep it — both are defensible). Delete v5 fallback code paths. Update the team's internal docs. Capture the post-migration p50 latency and confirm the 15-25% improvement; if not, investigate before declaring done.

Deliverable: v5 paths removed
"Phased rollouts are predictable. Single-commit migrations are not. The phase boundaries are where you measure, where you decide, and where you roll back."— Internal playbook for major-version SDK migrations

The feature-flag step in Phase 4 is non-negotiable for any chatbot serving more than a small beta. The flag check runs at the top of the route handler — if disabled, the route serves the v5 wrapped path; if enabled, it serves the v6 native path. The shim layer from Phase 3 is what makes this dual-path feasible without a fork. Ramp slowly enough to see at least 500 turns at each percentage step before bumping; ramp fast enough to be done inside one working day.

One scope-discipline note. The temptation during a v6 migration is to also bump Next.js, refresh shadcn, and modernize the tool-call rendering all in the same window. Don't. The v6 migration is its own concern; combining it with other changes multiplies the surface area you need to debug if something regresses. Ship v6 clean, then ship the next concern as its own cycle. Our web development team runs these migrations as single-concern engagements for exactly this reason.

07Common PitfallsFour ways the migration surfaces in production.

The four pitfalls below are the production-surfacing failures most v5-to-v6 migrations hit at least one of in the first week after cut-over. None are exotic; all are preventable; most are invisible in development because the trigger condition only shows up at real traffic mix.

Pitfall 1
Tool-call silent drop

Render loop matches only on part.type === 'text'. Tool calls and tool results render as nothing; the assistant appears to invoke tools silently and produce a final answer with no visible intermediate work. Production-visible the moment a real user triggers a tool path.

Render every part type
Pitfall 2
Mixed v5/v6 wire

Client bundle ships at v6 but a route handler ships at v5 (or vice versa). Stream opens, parses fail silently, client renders nothing. Most common during partial rollouts where one route gets missed. Pin client and server together; verify via build-time check.

Pin the wire format
Pitfall 3
Provider-options drift

Codemod doesn't reshape vendor-specific provider-options. Anthropic thinking config, OpenAI reasoning effort, and Google safety settings end up in the wrong sub-object and silently fall back to defaults. Symptom: model behavior shifts after migration despite identical prompts.

Audit per-vendor manually
Pitfall 4
Status enum half-adopted

Codemod converted isLoading to status === 'streaming' but the 'submitted' state isn't surfaced anywhere in UI. Users experience a dead-feeling input between submit and first token. Not broken, but worse UX than v5. Add the submitted-state spinner during Phase 3.

Surface 'submitted' in UI

The diagnostic flow for each pitfall is roughly the same: catch it in staging by replaying production prompt traffic against the migrated build, surface it through structured logging (the new onFinish hook is the right place to log token usage, tool-call counts, and latency per turn), and tune via the same feature-flag ramp from Phase 4. None of the pitfalls require a code rollback — all four are recoverable by patching forward inside the v6 codebase.

For teams thinking about how this migration sits relative to the broader AI-stack roadmap, the wire-format change in v6 is the foundation for several capabilities Vercel has been signposting for the second half of 2026 — streamed structured outputs, multi-modal part types, and richer agent-loop observability. Landing on v6 cleanly now means picking up those capabilities as non-breaking minor bumps rather than another major. The same logic applies to building the cross-platform agent surfaces in our Slack bot event-subscriptions tutorial — every adapter built on AI SDK 6 inherits the new part-typed protocol by default.

Conclusion

AI SDK migrations are predictable when codemods, wrap-shims, and shadow-tests all run before cut-over.

The v5 to v6 migration is the biggest breaking-change release the AI SDK has shipped since the 3.0 split. Four independent axes — the useChatmessage-parts model, the tool-call streaming lifecycle, provider-adapter contracts, and the streaming wire format — all change at once, and that concentration of breakage is what makes the migration feel larger than it is. In practice, a mid-sized AI product clears the migration in one to two weeks of focused work, with the heaviest cost in Phase 2's codemod sweep and Phase 3's shim layer.

The payoff is real and measurable. The streaming wire format change buys 15 to 25 percent first-token latency improvement measured at the p50 — a quality-of-life win that compounds across every chat turn the product serves. The new tool-call lifecycle materially improves the perceived intelligence of tool-augmented agents because the user sees the agent working in real time rather than waiting for a round trip to finish. The unified parts array on useChat simplifies client-side render code once the hand-sweep is complete. None of these are theoretical; all show up in production within the first week post-cut-over.

What to internalize for the next major. The wrap-shim pattern from Phase 3 is the durable lesson here — wrapping provider factory calls and render loops behind a thin shim layer isolates breaking changes to a single file regardless of which major lands next. Teams that built that habit during the v5 to v6 sweep will spend a fraction of the migration budget when v7 lands. Teams that didn't will re-learn the same lesson on a future major. The migration cost is bounded the second time only if you keep the shim.

Migrate the AI SDK cleanly

AI SDK migrations are predictable — when phased with codemods, wraps, and shadows.

Our team executes Vercel AI SDK migrations — codemod sweep, provider-adapter wrap, tool-call streaming adoption — with measurable rollout and rollback.

Free consultationExpert guidanceTailored solutions
What we ship

AI SDK migration engagements

  • Codemod sweep and audit
  • Provider-adapter wrap-compatibility shim
  • Tool-call streaming adoption
  • Streaming-latency benchmarking
  • Rollback procedures for the first 24 hours
FAQ · AI SDK v6

The questions teams ask before the SDK bump.

The official @ai-sdk/codemod sweep covers roughly 70 to 80 percent of mechanical changes — every import-path rewrite, the useChat hook signature shift from append/handleSubmit to sendMessage, the tool() helper signature update, and the conservative isLoading-to-status rewrite. The 20 to 30 percent it doesn't cover is the render-loop work (any code reading m.content directly stays unchanged), the provider-options structural reshape across vendors, and any custom tool-result rendering. Treat the codemod commit as a starting point, then sweep with rg for m.content, m.toolInvocations, isLoading, and experimental_ to surface the remaining hand work. Plan for one focused day of hand-sweep after the codemod runs.