Repository navigation
feat(providers): generate type-checked provider HTTP clients from modelschemas with Kubb - #2105
tombeckenham wants to merge 6 commits into
Conversation
…h Kubb Adds scripts/provider-clients (generate + --check drift mode), the shared Kubb fetch runtime at @tanstack/ai-utils/kubb with sendWith(), the generated clients for openai, anthropic, gemini, mistral, openrouter, elevenlabs and fal, and routes the openai-base client through the generated OpenAI operations.
…labs and fal through the generated clients Each client sends the operations that its modelschemas OpenAPI document describes through the generated Kubb functions. Auth, retries, errors, SSE parsing and audio bytes stay hand-written, as do calls the documents do not have. The shared runtime now merges every provider's Kubb runtime, so the SSE helpers that ElevenLabs needs are exported.
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:sherif,test:knip,tes... |
❌ Failed | 7m 8s | View ↗ |
nx run-many --targets=test:coverage --projects=... |
✅ Succeeded | 5s | View ↗ |
nx run-many --targets=build --exclude=examples/... |
✅ Succeeded | 37s | View ↗ |
☁️ Nx Cloud last updated this comment at 2026-11-09 05:27:52 UTC
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
🦋 Changeset detectedLatest commit: d5d430d The changes in this PR will be included in the next version bump. This PR includes changesets to release 8 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Coverage❌ Coverage fell under 60% in 1 package(s). Each package is measured twice in this job — on this PR and on its merge-base with
|
The generator fetches each provider's ?activity= documents. An activity
with per-model schemas gets a types-only src/generated/{activity} with a
{Provider}{Activity}Endpoints map of model id to { input, output }.
Only fal has per-model schemas today.
@tanstack/ai
@tanstack/ai-acp
@tanstack/ai-angular
@tanstack/ai-anthropic
@tanstack/ai-bedrock
@tanstack/ai-byteplus
@tanstack/ai-claude-code
@tanstack/ai-client
@tanstack/ai-cloudflare
@tanstack/ai-code-mode
@tanstack/ai-code-mode-snippets
@tanstack/ai-codex
@tanstack/ai-cohere
@tanstack/ai-compaction
@tanstack/ai-computer-local
@tanstack/ai-computer-playwright
@tanstack/ai-computer-sandbox
@tanstack/ai-computer-use
@tanstack/ai-devtools-core
@tanstack/ai-durable-stream
@tanstack/ai-effect
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-grok-build
@tanstack/ai-groq
@tanstack/ai-harness
@tanstack/ai-harness-cli
@tanstack/ai-isolate-cloudflare
@tanstack/ai-isolate-daytona
@tanstack/ai-isolate-e2b
@tanstack/ai-isolate-node
@tanstack/ai-isolate-quickjs
@tanstack/ai-isolate-quickjs-bun
@tanstack/ai-llmgateway
@tanstack/ai-lovable
@tanstack/ai-mcp
@tanstack/ai-memory
@tanstack/ai-mistral
@tanstack/ai-neon
@tanstack/ai-octane
@tanstack/ai-ollama
@tanstack/ai-ollaya
@tanstack/ai-openai
@tanstack/ai-opencode
@tanstack/ai-openrouter
@tanstack/ai-perplexity
@tanstack/ai-persistence
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-reactor
@tanstack/ai-remix
@tanstack/ai-sandbox
@tanstack/ai-sandbox-blaxel
@tanstack/ai-sandbox-boxd
@tanstack/ai-sandbox-cloudflare
@tanstack/ai-sandbox-daytona
@tanstack/ai-sandbox-docker
@tanstack/ai-sandbox-e2b
@tanstack/ai-sandbox-local-process
@tanstack/ai-sandbox-railway
@tanstack/ai-sandbox-sprites
@tanstack/ai-sandbox-upstash-box
@tanstack/ai-sandbox-vercel
@tanstack/ai-skills
@tanstack/ai-solid
@tanstack/ai-svelte
@tanstack/ai-typesafe
@tanstack/ai-utils
@tanstack/ai-vercel-gateway
@tanstack/ai-vertex
@tanstack/ai-vue
@tanstack/ai-worldlabs
@tanstack/openai-base
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
@tanstack/svelte-ai-devtools
commit: |
…types Adapters and clients build each body as the generated modelschemas type, with no casts. Lines where the document or a public SDK type does not fit carry a @ts-expect-error that names the gap, so the build fails once it is fixed. - openai-base: OpenAIClientLike takes the generated bodies (breaking: an SDK instance no longer fits; use createOpenAIClient). - generator: drop format: byte, which Kubb types as Blob instead of base64. - mistral: stream json_schema response format in snake_case. - elevenlabs: send string entityDetection/entityRedaction.

Review
packages/openai-base/src/client/client.tsfirst. The other six clients copy its pattern. The provider clients now build their requests with code that Kubb generates from the modelschemas OpenAPI documents. TypeScript checks every request body against the generated types.pnpm generate:provider-clients:checkfails when a live document would change the committed code.🎯 Changes
scripts/provider-clients/lists the providers on modelschemas, fetches each OpenAPI document, and runs Kubb and oxfmt.missing_connect,no_endpoints,unknown_provider).--checkcompares the output with the committed files.format: bytebefore Kubb runs. Kubb typesbyteasBlob, but the APIs take a base64 string.openai-base), Anthropic, Gemini, Mistral, OpenRouter, ElevenLabs, and Fal each getsrc/generated/client/{sdk,types}.ts. Groq, Grok, Azure, Bedrock, and the gateways use the OpenAI client.@ts-expect-errorthat names the mismatch. The build fails when the document is fixed, so the marker cannot go stale.@tanstack/ai-utils/kubbholds the Kubb fetch runtime andsendWith().sendWith()sends a generated request through the provider's own code (auth, retries, errors, SSE, audio).sync-provider-clients.ymlruns the check, regenerates, and opens a PR.Bugs fixed on the way:
json_schemaresponse format in camelCase. It now sends snake_case.entityDetectionorentityRedaction. It now sends it.Hand-written code that stays: calls that the documents do not have (files, job status, model lists, rerank), SigV4 signing, and the Vertex and Enterprise paths of Gemini.
Differences from the plan in the issue:
@kubb/plugin-client, notplugin-fetch. Onlyplugin-clientacceptsimportPath, so there is one runtime, not one per package. Its plain functions also tree-shake.operations: 'export', not'inline'. In Kubb 5.16,'inline'writes Gemini types that do not compile.x-modelschemas-encode: raw, so Fal passes its own path serializer. The per-model Fal types wait on fal: ?activity= OpenAPI documents drop indexed endpoints (veo3.1, kling, sora-2) modelschemas#605.Document gaps found
These lines carry a
modelschemas gapmarker. Each one needs an upstream fix in TanStack/modelschemas.temperature,top_kortop_p. Gemini has noGoogleMaps.authConfigand no videowebhookConfig. ElevenLabs operations have no query parameters.top_logprobs, and OpenRouteruser_locationand reasoningcontent, do not acceptnull.text.verbosityhas no'max', and its responsesinputhas noopenrouter:mcpitem.✅ Checklist
pnpm run test:pr, or these tests do not apply to this pull request.docs/for this change, or this change is not user-facing.pnpm changeset), or this PR does not change a published package.🚀 Release Impact
Testing
Commands run
tsc --noEmit,oxlint src --type-aware, andvitest runin the 17 changed packages. All pass, and the SDK parity tests are green.pnpm test:kiirapasses (2149 doc snippets).vitest run scripts/provider-clientspasses (12 tests).pnpm test:prand the E2E suite. CI runs them.Manual test
pnpm generate:provider-clients:check. It exits 0.packages/ai-fal/src/generated/client/sdk.ts. Run the check again. It names the file and exits 1.pnpm generate:provider-clients. The edit is gone.packages/ai-mistral/src/adapters/text.ts, remove a@ts-expect-error modelschemas gapline.pnpm exec tsc --noEmit -p packages/ai-mistralfails on that line.How this PR makes testing easy
Each migrated package has
tests/generated-client.test.ts. It injectsfetchand checks one JSON call and one streaming or audio call.scripts/provider-clients/files.test.tscovers the drift check and thebytefix.Linked issues
Closes #2104
Risk / rollback
new OpenAI()to anopenai-baseadapter no longer compiles. The runtime is the same.Public API change
OpenAIClientLikein@tanstack/openai-basetakes the generated body types, so an SDK instance no longer fits it.@tanstack/openai-baseexports the generated body types.@tanstack/ai-utilshas a new@tanstack/ai-utils/kubbentry point, andcreateElevenLabsClient()accepts an optionalfetch.Before
After