Skip to content

deps(ai): migrate the Vercel AI SDK from 5 to 6 - #184

Merged
mrsibe merged 1 commit into
mainfrom
deps/ai-sdk-6
Sep 30, 2026
Merged

mrsibe merged 1 commit into
mainfrom
deps/ai-sdk-6

Conversation

@mrsibe

@mrsibe mrsibe commented Sep 30, 2026

Copy link
Copy Markdown
Owner

What

Upgrade the AI SDK from 5 to 6, keeping the runtime behaviour identical.

Package Before After
ai 5.0.265 6.0.297
@ai-sdk/provider 2.0.5 3.0.18
@ai-sdk/openai 2.0.129 3.0.121
@ai-sdk/anthropic 2.0.105 3.0.125
@ai-sdk/google 2.0.99 3.0.129
@ai-sdk/openai-compatible 1.0.56 2.0.80

This is the second step of the two-stage 5 → 6 → 7 plan: it lands the provider-spec V2 → V3 migration on its own, so the semantics change in 7 does not hide inside a double jump.

How

Ran the official codemod npx @ai-sdk/codemod@3.0.8 v6, then fixed the three things it cannot know:

  1. The test fixtures, not just the types. The codemod renamed MockLanguageModelV2 → MockLanguageModelV3 but left the scripted provider chunks as V2 stream parts, so a V3 mock received a string finishReason and flat usage numbers. The suite caught this — 10 tests failed — and the fixtures now build the V3 shape:

    finishReason: { unified, raw }
    usage: { inputTokens: { total, noCache, cacheRead, cacheWrite },
             outputTokens: { total, text, reasoning } }

    This is the part of the migration a type-only change would have shipped silently: the as never[] cast on those fixtures is why it typechecked while being wrong.

  2. Embedding entry points — textEmbedding → embedding, textEmbeddingModel → embeddingModel.

  3. A misleading codemod rename. The local convertToCoreMessages in ModelClient is a plain mapper, not the SDK function the codemod named it after. Renamed to toModelMessages with the stale CoreMessage comment corrected.

The one real behaviour change, and how it is prevented

AI SDK 6 flips OpenAI's strictJsonSchema default from false to true. QuizService, AnkiCardService and MindMapService build schemas with .optional() fields, and OpenAI strict mode requires every property to be listed in required.

I verified both halves of this at the request-body level rather than assuming:

# the zod schema conversion leaves optional fields out of `required`
required at root: [ 'title', 'items' ]      # 'tags', 'meta', 'hint' absent

# the same call, with and without the fix
responses default                -> text.format.strict = true
responses strictJsonSchema:false -> text.format.strict = false

So without a fix, openai-responses would start rejecting the generated quiz / Anki / mind-map schemas. ProtocolAdapter and ModelClient now carry structuredOutputProviderOptions(), the openai-responses adapter answers it, and the three services pass it to streamObject.

Only openai-responses needs it. openai-completions goes through @ai-sdk/openai-compatible, which sends response_format: { type: 'json_object' } unless structuredOutputs is enabled, so its strictJsonSchema never applies — I checked the provider source, not the guide.

Verification (Linux x64, WSL2)

Command Result
npm run typecheck clean
npm test 470/470 pass (469 + the strictness regression test)
npm run build:unpack && npm run smoke:packaged 29 checks pass

The packaged smoke matters here because the AI SDK is a devDependency that rollup bundles into out/main, so a bad upgrade can break the bundle, not just the types.

Deliberately not changed

  • streamObject is deprecated in 6 (in favour of streamText + Output.object) but still works. Rewriting three services does not belong in a version bump; it is the natural content of the 6 → 7 PR.
  • The 'openai-compatible' providerOptions key is deprecated in favour of 'openaiCompatible'. It still works, including [Feat] Reasoning models hit the output ceiling: make truncation recoverable #179's reasoning-effort path, but now logs a warning. Flagged rather than renamed so this PR stays behaviour-identical.

Rollback

Revert the branch. No schema, storage format, or persisted data changes; the provider-spec V2 → V3 change is internal to the SDK.

Stacking

The 6 → 7 PR will stack on this branch (base deps/ai-sdk-6), because the v7 codemod can only run on v6 code.

Version set (all in devDependencies, bundled by rollup into out/main):

  ai                          5.0.265 -> 6.0.297
  @ai-sdk/provider             2.0.5  -> 3.0.18
  @ai-sdk/openai               2.0.129 -> 3.0.121
  @ai-sdk/anthropic            2.0.105 -> 3.0.125
  @ai-sdk/google               2.0.99  -> 3.0.129
  @ai-sdk/openai-compatible    1.0.56  -> 2.0.80

Ran the official v6 codemod (npx @ai-sdk/codemod@3.0.8 v6), then fixed what it
could not:

- Provider spec V2 -> V3 in the tests: MockLanguageModelV2 ->
  MockLanguageModelV3, LanguageModelV2* -> LanguageModelV3*.
- The scripted provider fixtures were still building V2 stream parts, so a V3
  mock received a string `finishReason` and flat usage. The suite caught it -
  10 tests failed - and the fixtures now use the V3 shape:
  `finishReason: { unified, raw }` and nested `inputTokens`/`outputTokens`
  totals. This is the one place a type-only migration was not enough.
- Embedding entry points renamed: textEmbedding -> embedding,
  textEmbeddingModel -> embeddingModel.
- The local `convertToCoreMessages` helper in ModelClient was only a plain
  mapper; the codemod renamed it after the SDK function it is not. It is now
  `toModelMessages`, and the stale `CoreMessage` comment is corrected.

One behavior change had to be actively prevented, not just reviewed:

AI SDK 6 flips OpenAI's `strictJsonSchema` default from false to true.
`QuizService`, `AnkiCardService` and `MindMapService` build schemas with
`.optional()` fields, and OpenAI's strict mode requires every property to be
listed in `required`. Verified at the request-body level:

  responses default                -> text.format.strict = true
  responses strictJsonSchema:false -> text.format.strict = false

and verified that the zod schema conversion leaves optional fields out of
`required`. So without a fix, openai-responses would start rejecting the
generated quiz/Anki/mind-map schemas. The `ProtocolAdapter` interface and
`ModelClient` now carry `structuredOutputProviderOptions()`, the
openai-responses adapter answers it, and the three services pass it to
`streamObject`.

Only openai-responses needs it: openai-completions goes through
`@ai-sdk/openai-compatible`, which sends `json_object` unless
`structuredOutputs` is enabled, so its `strictJsonSchema` never applies.

Verified on Linux x64 (WSL2):

- `npm run typecheck` - clean
- `npm test` - 470/470 pass (469 + the strictness regression test)
- `npm run build:unpack && npm run smoke:packaged` - 29 checks pass

Not changed, deliberately: the app still calls the deprecated `streamObject`
(v6 deprecates it in favour of `streamText` + `Output.object`); it works and
rewriting three services does not belong in a version bump. Likewise the
`'openai-compatible'` providerOptions key is deprecated in favour of
`'openaiCompatible'` and still works, including for #179's reasoning effort;
it now logs a warning.
@github-actions github-actions Bot added the dependencies Dependency updates label Sep 30, 2026
@mrsibe
mrsibe merged commit 92a1770 into main Sep 30, 2026
4 checks passed
@mrsibe
mrsibe deleted the deps/ai-sdk-6 branch September 30, 2026 05:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Dependency updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant