Skip to content

docs(custom providers): fix references to nonexistent error and serialization APIs - #21997

Open
jayfeng7 wants to merge 5 commits into
vercel:mainfrom
jayfeng7:docs/custom-provider-real-apis
Open

jayfeng7 wants to merge 5 commits into
vercel:mainfrom
jayfeng7:docs/custom-provider-real-apis

Conversation

@jayfeng7

@jayfeng7 jayfeng7 commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

What

  • Step 5 (Handle Errors): drop the nonexistent TooManyRequestsError import and the this.getRetryAfter(error) call, throw APICallError for the failed response, and fill in the required message / url / requestBodyValues fields so the sample type-checks. The explicit isRetryable override is removed so APICallError's default retryable behavior (408/409/429/5xx) applies.
  • Step 6 (Workflow serialization): replace the nonexistent serializeModel / deserializeModel helpers with serializeModelOptions plus a hand-written [WORKFLOW_DESERIALIZE], matching how provider packages actually implement workflow serialization (e.g. packages/deepinfra/src/deepinfra-chat-language-model.ts, packages/provider-utils/src/evaluation-language-model.ts).

Why

  • TooManyRequestsError, serializeModel, deserializeModel, and getRetryAfter do not exist anywhere in the packages, so readers copying the tutorial hit import errors and type errors.
  • The original sample's explicit isRetryable: status >= 500 && status < 600 excludes 429 from retryability, which contradicts APICallError's documented default (packages/provider/src/errors/api-call-error.ts treats 408/409/429/5xx as retryable).

How checked

  • Grepped all packages/* for TooManyRequestsError, serializeModel, deserializeModel: zero matches; serializeModelOptions, WORKFLOW_SERIALIZE, WORKFLOW_DESERIALIZE are exported from @ai-sdk/provider-utils (packages/provider-utils/src/index.ts).
  • The replacement serialize/deserialize bodies mirror the canonical in-repo usage verbatim (deepinfra chat model).
  • Also searched open PRs for existing work on these names before opening.

@jayfeng7
jayfeng7 force-pushed the docs/custom-provider-real-apis branch from 83b65e1 to ac65fbc Compare October 3, 2026 08:22
@jayfeng7
jayfeng7 force-pushed the docs/custom-provider-real-apis branch from ac65fbc to e22e892 Compare October 3, 2026 08:31
@jayfeng7

jayfeng7 commented Oct 5, 2026

Copy link
Copy Markdown
Contributor Author

Friendly ping — same situation as #21996: docs-only fix removing references to error/serialization APIs that don't exist in the current SDK. Bot checks are green and CI is pending approval; an approval whenever convenient would be much appreciated.

molebox added a commit that referenced this pull request Oct 5, 2026
## Summary
Fix 19 still-present copied documentation defects in owning MDX source:
telemetry opt-out scope, MCP options nesting, optional streamUI
response, pruneMessages template literal notation, Slackbot configured
model prose, retired Sonnet example labeling, isLoading name,
passenger-context timing, image middleware version, HarnessAgent session
scope, Expo model ID explanation, temperature conversion direction,
canonical inline tool-result files, onToolCall await warning scope,
computer-toolset zoom scope, fal language nullability, custom-provider
interface count, LM Studio HTTP examples, and AssistantResponse callback
argument migration wording.

No generated docsgraph output, ledger changes, or runtime changes.

## Verification and limitations
Scope is only the copied 31 open contradiction findings for site ai-sdk
across 25 pages. Copy built 2026-10-05T05:45:06.658Z at provenance
16f4e7370f24f2848dbc1808b87a32596ec46298. Owning source default branch
main checked at 5440b3a. Inspected
quote locations, surrounding prose/code, implementation signatures and
model placeholder rendering.

Reviewed 100 newest open PR titles available from GitHubPullRequestRead,
and patches for relevant documentation PRs #21997 (same custom-provider
page, disjoint error/serialization edits), #21972 (same Gateway page,
disjoint upstream-endpoint edits), #22003 (observability index, not
telemetry page). No duplicate found in available results; older open PRs
could not be exhaustively checked because list_pull_requests exposes no
pagination.

Direct live-page verification is BLOCKED: ai-sdk.dev cannot resolve in
the sandbox (`curl: (6) Could not resolve host: ai-sdk.dev`).
KnowledgeSearch returned indexed documentation, not proof of current
deployment. Fixed means proposed source correction, not deployed. Nine
source-already-corrected findings stay open pending deployment
verification.

Checks passed: MDX compilation for all 19 changed files with
@mdx-js/mdx; repository PropertiesTable validation; git diff --check.
Full website build not run.

## Finding dispositions
Keys below retain the exact copied site/path/hash; all have prefix
`contradiction ai-sdk`.

| key | disposition | change | note |
| --- | --- | --- | --- |
| contradiction ai-sdk /docs/ai-sdk-core/telemetry 922cc8263159 | fixed
| This PR | |
| contradiction ai-sdk /docs/reference/ai-sdk-core/create-mcp-client
3d1fbeb59048 | fixed | This PR | |
| contradiction ai-sdk /docs/reference/ai-sdk-rsc/stream-ui 40f33af154fc
| fixed | This PR | |
| contradiction ai-sdk /docs/reference/ai-sdk-ui/prune-messages
9891262bc76c | fixed | This PR | |
| contradiction ai-sdk /cookbook/guides/slackbot 2699d257387c | fixed |
This PR | |
| contradiction ai-sdk /cookbook/guides/sonnet-3-7 f2b899889277 | fixed
| This PR | |
| contradiction ai-sdk /cookbook/next/stream-object 6d5012684477 | fixed
| This PR | |
| contradiction ai-sdk /docs/advanced/multistep-interfaces d29c23c9edef
| fixed | This PR | |
| contradiction ai-sdk /docs/ai-sdk-core/image-generation 8ae62667617a |
fixed | This PR | |
| contradiction ai-sdk /docs/ai-sdk-harnesses/terminal-ui 3a1df8ca81a0 |
fixed | This PR | |
| contradiction ai-sdk /docs/getting-started/expo 1cb5a064177b | fixed |
This PR | |
| contradiction ai-sdk /docs/getting-started/nextjs-pages-router
bbc2eb7ee2a0 | fixed | This PR | |
| contradiction ai-sdk /docs/migration-guides/migration-guide-7-0
f38c873e332e | fixed | This PR | |
| contradiction ai-sdk
/docs/reference/ai-sdk-errors/ai-message-conversion-error f5201a99e7d8 |
dismissed | | Runtime name AI_MessageConversionError and exported class
MessageConversionError intentionally differ;
packages/ai/src/prompt/message-conversion-error.ts. Verdict command
below. |
| contradiction ai-sdk
/docs/reference/ai-sdk-errors/ai-ui-message-stream-error 14e8170da778 |
dismissed | | Runtime name AI_UIMessageStreamError and exported class
UIMessageStreamError intentionally differ;
packages/ai/src/error/ui-message-stream-error.ts. Verdict command below.
|
| contradiction ai-sdk
/docs/troubleshooting/tool-invocation-missing-result 28da3ccb0e39 |
fixed | This PR | |
| contradiction ai-sdk /providers/ai-sdk-providers/anthropic
33ee3e7c11cb | fixed | This PR | |
| contradiction ai-sdk /providers/ai-sdk-providers/fal f07f206e67d3 |
fixed | This PR | |
| contradiction ai-sdk /providers/community-providers/custom-providers
7e7c79892b53 | fixed | This PR | |
| contradiction ai-sdk /providers/openai-compatible-providers/lmstudio
f35444eeabdf | fixed | This PR | |
| contradiction ai-sdk /providers/ai-sdk-providers/ai-gateway
4d3c55b7a277 | left open | | Main already matches primary model and
comment (openai/gpt-6-astra); live deployment unverified. |
| contradiction ai-sdk /providers/ai-sdk-providers/ai-gateway
ff8d7e073b93 | left open | | Main already matches fallback and comment
(google/gemini-3.8-flash); live deployment unverified. |
| contradiction ai-sdk /providers/ai-sdk-providers/amazon-bedrock
9d7c5208485b | left open | | Main already distinguishes Bedrock
citations support from SDK citation result availability; live deployment
unverified. |
| contradiction ai-sdk /providers/ai-sdk-providers/amazon-bedrock
890f1f3133bc | left open | | Duplicate underlying finding of
9d7c5208485b; main already clarified; live deployment unverified. |
| contradiction ai-sdk /providers/ai-sdk-providers/google-vertex
1d1894389ca4 | left open | | Main now lists Gemini 2.0 and newer for
Search, matching sample; live deployment unverified. |
| contradiction ai-sdk /providers/ai-sdk-providers/google-vertex
4997f0ea4c14 | left open | | Main now lists Gemini 2.0 and newer for
Maps, matching sample; live deployment unverified. |
| contradiction ai-sdk /providers/ai-sdk-providers/google-vertex
8042d0cc0730 | dismissed | |
packages/google/src/google-language-model.ts writes both vertex and
googleVertex metadata keys for backward compatibility. Verdict command
below. |
| contradiction ai-sdk /providers/ai-sdk-providers/google-vertex
c8036eff8129 | left open | | Main now lists Gemini 2.0 and newer for URL
Context, matching sample; live deployment unverified. |
| contradiction ai-sdk /providers/ai-sdk-providers/openai 5178e580cf3c |
left open | | Main already includes null in reasoningSummary type; live
deployment unverified. |
| contradiction ai-sdk /providers/ai-sdk-providers/openai b0b5cd763b94 |
left open | | Main already describes file type with image media type,
matching sample; live deployment unverified. |
| contradiction ai-sdk /docs/migration-guides/migration-guide-4-0
3ef9eb2e1ea5 | fixed | This PR | |

## Unpersisted false-positive verdicts
The copy did not identify the docsgraph GitHub owner. Attempted
vercel/docsgraph checkout was denied repo_not_allowed. No verdicts were
written. Run these in the actual docsgraph checkout and commit
data/verdicts.ndjson, never ledger.json:

```sh
pnpm docsgraph audit-verdict --consumer page-defects --key "contradiction ai-sdk /docs/reference/ai-sdk-errors/ai-message-conversion-error f5201a99e7d8" --verdict wrong --reviewer vercel-agent --note "AI_MessageConversionError is the runtime error name; MessageConversionError is its exported class. packages/ai/src/prompt/message-conversion-error.ts defines both intentionally."
pnpm docsgraph audit-verdict --consumer page-defects --key "contradiction ai-sdk /docs/reference/ai-sdk-errors/ai-ui-message-stream-error 14e8170da778" --verdict wrong --reviewer vercel-agent --note "AI_UIMessageStreamError is the runtime error name; UIMessageStreamError is its exported class. packages/ai/src/error/ui-message-stream-error.ts defines both intentionally."
pnpm docsgraph audit-verdict --consumer page-defects --key "contradiction ai-sdk /providers/ai-sdk-providers/google-vertex 8042d0cc0730" --verdict wrong --reviewer vercel-agent --note "Both vertex and googleVertex metadata keys are written for backward compatibility in packages/google/src/google-language-model.ts."
```

Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Rich Haines <22930449+molebox@users.noreply.github.com>

@gr2m gr2m left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still relevant, but the remaining issue below prevents a confident merge.


static [WORKFLOW_SERIALIZE](model: CustomChatLanguageModel) {
return serializeModel(model);
return serializeModelOptions({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep deserialized headers compatible with the model config. serializeModelOptions resolves a function-valued headers property and serializes the returned object. After this new deserializer restores that config, the following this.config.headers?.() example calls a plain object and throws TypeError. Update the header config/access pattern to support resolved headers, and correct the accompanying claims that headers are omitted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 136ef31.

You are right, and the old shape was worse than a doc typo: it typechecked. Under headers?: () => Record<string, string | undefined>, this.config.headers?.() produces zero tsc errors while throwing at runtime on the model the deserializer restores — I reproduced both halves against the real signatures:

old config type + `headers?.()`   -> tsc: 0 errors
deserialized config               -> TypeError: this.config.headers is not a function

Three changes, all in this page:

Config type accepts what the deserializer restores.

import type { Resolvable } from '@ai-sdk/provider-utils';

type CustomChatConfig = {
  provider: string;
  baseURL: string;
  headers?: Resolvable<Record<string, string | undefined>>;
  fetch?: FetchFunction;
  generateId?: () => string;
};

Both request paths resolve instead of call. The postJsonToApi call in doGenerate and the fetch call in doStream use

this.config.headers ? await resolve(this.config.headers) : undefined

wrapped in combineHeaders(..., options.headers). The ternary is not defensive noise: headers is optional and resolve() does not accept undefined, so without it the fixed snippet does not compile. This is the same shape amazon-bedrock and anthropic already use, and Resolvable is the type their configs declare.

The claims are corrected. headers is no longer described as filtered out — it is the one special case, resolved during serialization and kept, and only fetch/generateId are dropped. The note no longer says a deserialized model has no auth headers (it does); it now says the resolved map travels with the payload, which is the consequence a reader actually needs to know about, and points at options.headers if a key must not be serialized.

Reading serializeModelOptions to make sure the page matches it: resolveSync calls a function-valued headers and throws SerializationError on a promise, then the resolved object is kept only when isJSONSerializable accepts it. Both facts are stated on the page now, because a page that says headers are omitted makes the sync requirement invisible.

I also replaced the combineHeaders Before/After with the resolved form, so the "after" is copy-pasteable rather than a sketch. If you would rather the page keep the un-combined this.config.headers() from Step 2 and only show resolve there, say so — I kept combineHeaders because that is what the request-level options.headers merge needs and it was already in the snippet you reviewed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the thorough review — this is addressed in the current head. Commit 136ef31 ("docs(custom providers): keep deserialized headers compatible with the config") replaces this.config.headers?.() with await resolve(this.config.headers) from @ai-sdk/provider-utils at both call sites (doGenerate and doStream), and adds a Note explaining why optional chaining is not a safe substitute.

The Resolvable<...> type is also introduced in the config so deserialized models carry the resolved header map, and the docs now state that the function must be synchronous (async headers throws SerializationError).

The diff between your reviewed head (e22e8929) and the current head (30d2c0f6) is: git diff e22e8929..30d2c0f6 -- content/providers/05-community-providers/01-custom-providers.mdx — the resolve pattern and the Note block are the only new changes to this file.

… config

`serializeModelOptions()` resolves a function-valued `headers` during
serialization and keeps the object it returns, so a model that crosses a
workflow step boundary has a plain object where this page told you to expect a
function. Following the old guidance — `this.config.headers?.()` — throws
`TypeError` on exactly that model, because optional chaining guards `undefined`
rather than a call on an object.

Three changes:

- The config type declares `headers?: Resolvable<Record<string, string |
  undefined>>`, so the value the deserializer restores typechecks.
- Both request paths resolve it: `this.config.headers ? await
  resolve(this.config.headers) : undefined`. The guard stays because `headers`
  is optional and `resolve()` does not accept `undefined`.
- The claims are corrected. `headers` is not omitted from the serialized config:
  it is resolved and kept, and only `fetch` and `generateId` are dropped. The
  note no longer says a deserialized model has no auth headers — it says the
  resolved map travels with the payload, which is why a secret in `headers` ends
  up in the workflow payload.

Checked against the implementation rather than assumed: `serializeModelOptions`
resolves headers synchronously (`SerializationError` on a promise) and keeps the
result only when it is JSON-serializable. The old shape typechecked and failed at
runtime; the new one typechecks and passes both shapes through.
The second paragraph of a multi-paragraph `<Note>` is not indented after the
blank line, which is how oxfmt formats every other multi-paragraph Note on the
site. Without it the page fails `ultracite check`.
@jayfeng7

Copy link
Copy Markdown
Contributor Author

The [P2] fix is in 136ef31, plus one formatting follow-up in a0d924d.

One thing about the red Lint & Format check, so it is not read as fallout from this PR: it fails on three files this PR does not touch, and it fails on main too.

main @ a1db2092  Lint & Format  failure
  content/docs/03-ai-sdk-core/41-skill-uploads.mdx
  content/docs/06-advanced/01-prompt-engineering.mdx
  content/docs/07-reference/01-ai-sdk-core/14-upload-skill.mdx
  Format issues found in above 3 files.

That list comes from the job log of main's own run, not from this PR's. This page is clean under the repo's pinned oxfmt@0.41.0 on an LF checkout:

$ oxfmt --check content/providers/05-community-providers/01-custom-providers.mdx
All matched files use the correct format.

while the same command flags exactly those three files at a1db2092, so the regression came in with the docs sweep on main. I did introduce one issue of my own here — a two-paragraph <Note> whose second paragraph was indented, which oxfmt de-indents — and fixed it in a0d924d. I left the other three alone rather than widen a docs-accuracy PR; say the word and I will send them as a separate PR.

@jayfeng7

Copy link
Copy Markdown
Contributor Author

Following up on my earlier note about the red Lint & Format check, since I now have the full picture rather than a guess.

The current head (a0d924d) fails on exactly three files, and 01-custom-providers.mdx is not one of them:

content/docs/03-ai-sdk-core/41-skill-uploads.mdx (49ms)
content/docs/06-advanced/01-prompt-engineering.mdx (31ms)
content/docs/07-reference/01-ai-sdk-core/14-upload-skill.mdx (14ms)

Format issues found in above 3 files.

All three are unformatted on main itself at a1db2092, which is why every pull request inherits the failure — the job checks the merge with main, not just the diff. I reproduced it on an LF checkout with the pinned oxfmt@0.41.0: those three files report format issues at a1db2092, and oxfmt --check over the whole tree is clean once they are formatted.

So rather than keep asking for an exception, I opened #23601, which applies oxfmt to exactly those three files and nothing else. If it lands, this PR's check should go green on the next run without any further change here — the remaining red is entirely upstream.

The one format issue that was mine — a two-paragraph <Note> whose second paragraph was indented — is fixed in a0d924d, and this page is clean under oxfmt@0.41.0 on an LF checkout.

@jayfeng7

Copy link
Copy Markdown
Contributor Author

Merged main into the branch (a0d924d → 30d2c0f6) to clear the last red check.

Lint & Format was failing on three files this PR does not touch (41-skill-uploads.mdx, 01-prompt-engineering.mdx, 14-upload-skill.mdx). Main has since fixed them in #23609 (f2876d0b48), so the merge carries the fix in: Lint & Format now passes on the new head and the diff is still just the one custom-providers page. Nothing else changed — remaining checks are still running, and the PR is down to your re-review.

@jayfeng7

Copy link
Copy Markdown
Contributor Author

@gr2m — the P2 you flagged (deserialized headers calling this.config.headers?.() on a plain object) is addressed in the current head.

What changed since your review (commit 136ef319, pushed 49 minutes after your review landed):

  1. this.config.headers?.() is replaced with await resolve(this.config.headers) from @ai-sdk/provider-utils at both call sites (doGenerate and doStream) — resolve() passes a plain object through and calls a function, so a deserialized model with a resolved header map works correctly.
  2. The config type now uses Resolvable<Record<string, string | undefined>> from @ai-sdk/provider-utils, matching the pattern used by the AI SDK's own providers.
  3. The accompanying claims that headers are "not serialized" are corrected: serializeModelOptions resolves a function-valued headers and keeps the returned object, so a deserialized model carries its header map.
  4. A Note block is added explaining why optional chaining is not a safe substitute.

Diff since your review: git diff e22e8929..30d2c0f6 -- content/providers/05-community-providers/01-custom-providers.mdx

Could you re-review when you have a moment? The changes are confined to this one file.

1 similar comment
@jayfeng7

Copy link
Copy Markdown
Contributor Author

@gr2m — the P2 you flagged (deserialized headers calling this.config.headers?.() on a plain object) is addressed in the current head.

What changed since your review (commit 136ef319, pushed 49 minutes after your review landed):

  1. this.config.headers?.() is replaced with await resolve(this.config.headers) from @ai-sdk/provider-utils at both call sites (doGenerate and doStream) — resolve() passes a plain object through and calls a function, so a deserialized model with a resolved header map works correctly.
  2. The config type now uses Resolvable<Record<string, string | undefined>> from @ai-sdk/provider-utils, matching the pattern used by the AI SDK's own providers.
  3. The accompanying claims that headers are "not serialized" are corrected: serializeModelOptions resolves a function-valued headers and keeps the returned object, so a deserialized model carries its header map.
  4. A Note block is added explaining why optional chaining is not a safe substitute.

Diff since your review: git diff e22e8929..30d2c0f6 -- content/providers/05-community-providers/01-custom-providers.mdx

Could you re-review when you have a moment? The changes are confined to this one file.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants