Repository navigation
Description
Problem
Provider HTTP calls are hand-written fetch clients (merged in #1973). Each package keeps its own request types, URL map, auth, and body encoding. Updating a provider means editing that client by hand.
modelschemas now publishes a generation-only OpenAPI document per provider:
GET https://modelschemas.com/v1/openapi/{provider}
with Accept: application/openapi+json.
That document is the source we should generate from. A script must be able to refetch it and show whether the committed client is stale.
This is not #1788 (that issue only moves the model catalog off models.dev). It is not #619 / PR #622 (a separate @tanstack/ai-schemas validation package, closed, not merged). Leave World Labs on its existing hey-api client.
Generate with Kubb
Use Kubb v5 @kubb/plugin-ts and @kubb/plugin-fetch. One compact client per provider that has a document, not one file per schema.
import { defineConfig } from 'kubb/config'
import { pluginTs } from '@kubb/plugin-ts'
import { pluginFetch } from '@kubb/plugin-fetch'
export default defineConfig({
input: './openapi.json',
output: { path: './src/gen', clean: true, barrel: false, format: 'oxfmt' },
plugins: [
pluginTs({
output: { path: 'types.ts', barrel: false },
operations: 'inline',
}),
pluginFetch({
output: { path: 'sdk.ts', barrel: false },
sdk: { name: 'OpenAI', mode: 'flat' },
}),
],
})A trial of the live OpenAI document (38 operations) wrote 6 files: types.ts, sdk.ts, and a .kubb fetch runtime. output.format: 'oxfmt' runs after generation. This repo already depends on oxfmt.
Share one .kubb runtime across packages with pluginFetch({ importPath }) when that stays simpler than copying the runtime into every package.
What the scripts must do
- List providers from modelschemas.
- Fetch each generation OpenAPI document.
- Skip a provider when the document is not available (
missing_connect,no_endpoints,unknown_provider). Do not fail the whole run for those. - Run Kubb, then oxfmt.
- A check command (used in CI and locally) refetches the documents and fails when the generated client would change. Follow the drift style of
.github/workflows/sync-models.yml, but keep this pipeline separate from model-id sync.
Commit the generated client. Do not commit a second hand-written copy of the same calls.
Spec shape
Keep one path and one method. A call that returns JSON or text/event-stream lists both content types on that operation. Do not add a #stream path. # is a fragment, fetch drops it, and it is not a valid OpenAPI path.
Fal stays one queue client from the untyped queue document (POST /{endpoint_id} on https://queue.fal.run). ?category= is not a selector. Do not generate one client per Fal model. Providers that still require ?model= (Reactor, Cloudflare AI Gateway) stay out of this pass until they have a provider-level document.
Known generator limits
Kubb does not turn a combined JSON plus event-stream operation into both a JSON method and a parsed SSE loop. The body field stream: true selects streaming. The runtime returns response.body for text/event-stream and does not parse event: / data: lines.
audio/* is not mapped to a blob (only image/* and application/octet-stream are), so speech audio can fall through to JSON.parse.
Keep a small hand-written layer for those cases: stream: true, SSE parsing, and audio bytes. Do not use Stainless. Do not switch this work to hey-api.
Auth that a generated client cannot perform (AWS SigV4) stays hand-written. Bearer tokens and the Azure api-key header can go through the generated client.
Acceptance
pnpmscript generates every available provider client from modelschemas with Kubb, in the compact layout above, formatted with oxfmt.- A check script exits non-zero when a live document would change committed output.
- Provider adapters call the generated client instead of the hand-written fetch maps from perf(providers): call provider APIs with fetch instead of the SDK runtime #1973, except for the hand-written stream, audio, and SigV4 cases above.
- Packages that only speak another provider's API reuse that generated client instead of generating a duplicate.
- Unit tests cover generation drift detection and one JSON call plus one streaming call per generated provider that has both.
- Docs and a changeset ship with the change if a published package's public behavior changes.
Activity