Skip to content

fix: declare @ai-sdk/provider and provider-utils as optional peers - #630

Open
mattapperson wants to merge 3 commits into
mainfrom
claude/declare-ai-sdk-type-deps
Open

mattapperson wants to merge 3 commits into
mainfrom
claude/declare-ai-sdk-type-deps

Conversation

@mattapperson

@mattapperson mattapperson commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Fixes #621

dist/index.d.ts and dist/internal/index.d.ts import from @ai-sdk/provider and @ai-sdk/provider-utils, but neither was declared. Under strict layouts (pnpm hoist=false, pnpm global virtual store) the imports failed with TS2307 and provider types degraded to any. Under lenient layouts they bound to whatever copy was hoisted.

Changes

  • peerDependencies (optional): @ai-sdk/provider: ^4.0.0, @ai-sdk/provider-utils: ^5.0.0, both with peerDependenciesMeta.optional: true. They stay in devDependencies at the versions we build against (4.0.16 / 5.0.42), so pnpm-lock.yaml is unchanged from main.
  • Bundling: tsup noExternal for both packages. The runtime JS is the same shape as 3.1.0, with no runtime imports of @ai-sdk/*. Optional peers are not guaranteed to be installed, and the copy ai brings can be older than the version we compiled against.
  • CI guard: scripts/check-consumer-types.mjs (pnpm check:consumer-types) packs the tarball and installs one consumer per (layout, ai version) outside the repo. It runs tsc with skipLibCheck: false on real user code: generateText / streamText with chat, completion, the callable provider, and the internal chat model; wrapLanguageModel; embed / embedMany; createProviderRegistry({ openrouter }); plus IsAny assertions. Then it runs a runtime smoke test that imports both entries and does an end-to-end generateText through the consumer's ai against a stub fetch. Any TS error fails the check (TS2307, TS2322, any), with one exception: @ai-sdk/provider's own undeclared json-schema import (TS7016, global virtual store only). That one is upstream and affects ai itself.
    • Default matrix (run in CI): layouts pnpm-isolated (hoist=false), pnpm-global-virtual-store, pnpm-hoisted, npm × ai 7.0.0 (peer floor), 7.0.103 (devDependency), latest. That's 12 consumers, run 4 at a time, about 10 s locally with a warm cache.
    • Extra layouts on demand: pnpm-isolated-no-auto-peers, npm-legacy-peer-deps. --tarball=<path> checks an arbitrary tarball, for baselines.

Why optional peers: comparison

ai pins exact versions of @ai-sdk/provider / provider-utils (ai@7.0.0 uses provider@4.0.0, 7.0.103 uses 4.0.16, latest uses 4.0.35). If our .d.ts binds to any other copy, user code fails TS2322 once the copies diverge (for example, finishReason.unified gained 'interrupted'). Results from the harness above (tsc result per ai version: 7.0.0 / 7.0.103 / latest; runtime passed everywhere):

Candidate pnpm-isolated pnpm-gvs pnpm-hoisted npm pnpm no-auto-peers npm legacy-peer-deps
Published 3.1.0 (undeclared) TS2307 ×3 TS2307 ok ok TS2307 ×3 ok
Previous PR: caret dependencies, external TS2322 / TS2322 / ok TS2322 / TS2322 / ok TS2322 / TS2322 / ok TS2322 / TS2322 / ok n/a TS2322 / TS2322 / ok
(a) required peers, external ok ok ok TS2322 / TS2322 / ok ok ok
(b) required peers, bundled ok ok ok TS2322 / TS2322 / ok ok ok
(h) optional peers, bundled (this PR) ok ok ok ok ok ok
  • Caret dependencies resolve to the newest version, not ai's pinned copy, so the earlier claim that they'd dedupe with ai was wrong.
  • Required peers: pnpm satisfies the peer from the copy already in the graph (ai's), but npm 7+ auto-installs the newest matching peer at the root and nests ai's pinned copy under ai/, so the types diverge.
  • Optional peers are never auto-installed. pnpm (checked with 9.15, 10.34, and 11.28) links the copy already in the graph, which is ai's, and npm resolves the hoisted copy, which is also ai's. I checked this with npm ls and the pnpm symlinks: on ai@7.0.0, our package links @ai-sdk/provider@4.0.0 / provider-utils@5.0.0.
  • (c), raising the ai floor, wasn't needed.

Runtime duplicates (review finding)

The runtime still bundles a private copy, as in 3.1.0. Cross-copy checks still work because both markers use Symbol.for: AISDKError markers (Symbol.for('vercel.ai.error…')) and the workflow serde symbols from @workflow/serde (Symbol.for('workflow-serialize') / Symbol.for('workflow-deserialize')).

Notes

  • e2e/issues/issue-621-*.test.ts checks the declarations (optional peers, nothing undeclared in src/dist .d.ts, no runtime @ai-sdk/* imports in dist/*.js). It runs only under test:issues. The CI guard is check:consumer-types.
  • Not covered: Yarn PnP. With PnP, optional peers are only visible if the parent provides them.
  • The TS2322 in the issue's Mastra repro comes from @mastra/core pinning its own @ai-sdk/provider. That has to be fixed on Mastra's side.

Checklist

  • I have run pnpm stylecheck and pnpm typecheck
  • I have run pnpm test and all tests pass
  • I have added tests for my changes (if applicable) (consumer type-check matrix + CI step)
  • I have updated documentation (if applicable)

Changeset

  • I have run pnpm changeset to create a changeset file (minor)

devin-ai-integration[bot]

This comment was marked as resolved.

The published .d.ts files import both packages, but they were only
devDependencies, so type resolution depended on the consumer's
node_modules layout (silent any under pnpm GVS, arbitrary hoisted copy
elsewhere). Declare them as dependencies and let tsup externalize them.

Adds scripts/check-consumer-types.mjs and a CI step that packs the
package and type-checks a consumer with skipLibCheck: false under pnpm
isolated (hoist=false), pnpm hoisted, and npm.

Fixes #621
Caret dependencies resolved to the newest @ai-sdk/provider, while ai pins
an exact version, so consumers on older ai 7.x got a second copy and
generateText/streamText/wrapLanguageModel failed with TS2322.

Declare both as optional peerDependencies so the .d.ts imports bind to the
copies the consumer's ai version uses, keep them bundled at runtime (as in
3.1.0), and restore the lockfile to main's.

Rework check-consumer-types to type-check real user code against a matrix
of ai versions (peer floor, devDependency, latest) and package-manager
layouts (pnpm isolated, global virtual store, hoisted, npm), plus an
end-to-end generateText runtime smoke test.
@mattapperson
mattapperson force-pushed the claude/declare-ai-sdk-type-deps branch from d7389a9 to 37d8bba Compare November 4, 2026 14:45
@mattapperson mattapperson changed the title fix: declare @ai-sdk/provider and provider-utils as dependencies fix: declare @ai-sdk/provider and provider-utils as optional peers Nov 4, 2026

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

@@ -0,0 +1,7 @@
---
"@openrouter/ai-sdk-provider": minor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Bug fix receives a minor version bump

For this dependency-resolution fix, the changeset selects a minor release. The repository's release rules reserve minor bumps for new features, so publication increments the wrong version component.

Suggested change
"@openrouter/ai-sdk-provider": minor
"@openrouter/ai-sdk-provider": patch

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Keeping minor on purpose. This changes the published manifest: it adds two new peerDependencies, @ai-sdk/provider and @ai-sdk/provider-utils (both optional), and package managers will now report or check them, for example peer warnings if someone pins an incompatible major. The PR review asked for at least minor so the change is visible in the version number, and the changeset lists the supported ai range. I am leaving this thread open so the maintainer can make the final call on patch vs. minor.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Types import @ai-sdk/provider without declaring it: layout-dependent resolution (silent any under pnpm GVS, TS2322 with Mastra under isolated/flat)

2 participants