feat(cloudflare-ai-gateway): derive models from live catalog + schema + docs - #4922
Conversation
Replace the legacy 4-script bash pipeline with a single catalog-aware reconciler. It sources canonical dotted model ids and pricing from Cloudflare's ai/catalog/models (proxied) and ai/models/search (hosted @cf/*), and isolates all human curation in a committed overrides.toml. - add packages/core/script/generate-cloudflare-ai-gateway.ts (fetch with fixture fallback, catalog/manual cost derivation, tiered pricing, --check mode; hard-fails on an unmapped Text-Generation catalog id) - add providers/cloudflare-ai-gateway/overrides.toml (67 model entries + 33-id skip list) as the sole hand-authored curation layer - wire cloudflare-ai-gateway:generate; rewrite the provider README - regenerate all 67 model stubs via the shared formatToml serializer; resolved model JSON is unchanged (compare:migrations clean, validate passes) - remove the legacy scripts/ pipeline and stale data/ JSON Leading source-citation comments on hand-authored files are dropped by regeneration for now; modeling them as a structured notes field is a documented follow-up.
… + docs Replace overrides.toml with a 3-source reconciler: catalog for cost/limits, per-model schema and Workers-AI docs for reasoning_options, curation.toml only for base_model maps, live-tested structured_output, and native-provider reasoning shapes. Inherit name/description from base_model.
Action items
|
…d output limits per lab/peer baselines The generator was deriving reasoning_options straight from catalog/docs schema enums instead of matching each host's real native controls, silently dropping interleaved fields and toggle wire comments on every regen, and writing limit.output from a catalog field that's verified wrong against lab/first-party for several models (gpt-5.5, claude-haiku-4.5, gpt-oss-20b, and more). - curation.toml gains interleaved and note (leading wire-path comment) fields; both proxied and hosted loops now consult them - proxied/hosted limit.output no longer derives from an unreliable field — curated only, so it correctly inherits from base_model when not host-specific - restored the hand-verified reasoning_options/interleaved/output/wire-comments that existed before this rework for gpt-oss-20b/120b, gemma-4-26b-a4b-it, kimi-k2.6, nemotron-3-120b-a12b, glm-4.7-flash, glm-5.2, and backfilled limit.output for every other previously-verified @cf model - fixed proxied deepseek-v4-pro, qwen3.7-plus, and grok-4.6 to match their lab/first-party reasoning controls instead of a generic schema-derived effort enum Verified live against Cloudflare (bun run cloudflare-ai-gateway:generate --check passes, bun validate passes). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressed the action items from the bot review in 4672743. Verified everything against
Diff: 63 files, mostly proxied |
Action items
|
…t lab/peer baselines Second pass on bot review feedback: several proxied and hosted models were still using schema-derived L/M/H effort instead of matching their real native controls. - alibaba/qwen3.5-397b-a17b, qwen3.7-max, qwen3.8-max: toggle + budget_tokens (qwen3.8-max also gets low|medium|xhigh effort + interleaved), matching first-party Alibaba shapes - google/gemini-3.5-flash-lite, gemini-3.6-flash, gemini-3.7-flash: restored graded lab effort instead of a toggle-only/[] shape copied from merge-gateway without host-specific evidence - moonshotai/kimi-k3 (proxied), workers-ai/@cf/deepseek-ai/deepseek-v4-flash-0731, workers-ai/@cf/qwen/qwen3.8-27b: corrected effort values to match lab/peer instead of a generic enum (deepseek-v4-flash-0731 matches its same-@cf-id cloudflare-workers-ai peer, which is host-specific evidence trumping the general lab set) - workers-ai/@cf/moonshotai/kimi-k2.7-code, xai/grok-4.20-0309-reasoning: restored reasoning_options = [] (always-on, no caller control) per first-party lab files - workers-ai/@cf/zai-org/glm-5.2: effort high|max only (no toggle) + interleaved, matching lab; glm-4.7-flash kept its existing toggle+effort shape but gained the wire note, since its cloudflare-workers-ai peer cites docs-verified evidence for that host-specific delta - added missing toggle wire-path notes (claude-sonnet-5, gemini-2.5-flash/-lite budget models) Left providers/cloudflare-ai-gateway/models/openai/gpt-5.6-sol.toml unchanged: its cheaper non-tiered pricing is live and correct — Cloudflare's catalog carries an active "50% off through Sept 18" promotional banner for this model, confirmed via direct API query. bun run cloudflare-ai-gateway:generate --check and bun validate both pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressed the second round of action items in c960980.
Also proactively fixed one more instance of the same defect I found while verifying #3's neighbor: kimi-k2.7-code (hosted) had the same schema-derived |
Cloudflare fronts Anthropic and OpenAI with native passthrough routes (the Messages and Responses APIs), not the gateway's generic openai-compatible transform. Emit [provider] npm per model for those two families so consumers route to the endpoint that serves them best instead of inheriting the provider default (ai-gateway-provider). Workers AI and the compat-only third parties keep the default. Restores the @ai-sdk/anthropic signal the generator rewrite dropped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Action items
|
…imit deltas Curation could not author tool_call/attachment/modalities, so several Workers AI @cf entries silently re-inherited lab capabilities the same @cf peer marks false. Extend the curation schema + hosted generation to carry those host disables, and restore them for the six affected models (matching cloudflare-workers-ai peers). Align thinkingmachines/inkling reasoning_options with the lab toggle+effort shape (with the required wire-path note), and curate its served output ceiling (65_536) instead of inheriting the lab canon's 1M. Proxied limit now merges catalog context with curated fields so context stays catalog-derived. Regenerated live; gpt-5.5's flat catalog pricing (no cache/tier) confirmed as what Cloudflare's catalog returns, not dropped data. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Addressed the action items in 384873b (regenerated live against Cloudflare;
|
Action items
|
The gateway entries don't support tiered pricing, so keep only the base/default- context rate and drop the higher-context [[cost.tiers]] bands (google gemini-3.x flash, xai grok-4.5/4.6). The lower band was already folded into the flat rate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Action items
|
Workers AI (@cf/...) is a different pathway from the proxied third-party models - hosted on Cloudflare, CF-token auth, per-model agreements - and already has its own provider (cloudflare-workers-ai). Mirroring it here mixed two pathways in one provider for no benefit, so drop the hosted @cf set and the generator's hosted path (search API, cloudflare-docs fetch, hostedCost/hostedProp). The provider is now proxied third-party models only. Also skip thinkingmachines/inkling: it is BYOK-only on the gateway (not available via unified billing), so it can't be invoked the way the other proxied models are. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Action items
|
grok-4.6's reasoning_effort was curated as low|medium|high|xhigh from the first-party xAI API, but the Cloudflare gateway route rejects xhigh (verified live: code 7003, 'expected one of low|medium|high'). Match what the CF route actually accepts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
For traceability — the opencode side that makes this catalog usable in the CLI:
Both were validated by exercising every model in this provider (base + each reasoning-effort variant) through opencode against a live Cloudflare gateway. That pass also caught a curation bug fixed here in bbb7276: Cloudflare's grok-4.6 route rejects |
Action items
|
Gemini's API rejects any request whose message list ends on an assistant turn (verified live: gemini-3.7-flash returns code 7003, gemini-3.5-flash returns 2002), which is exactly what opencode's agent loop sends when checking for further tool calls. Every Gemini model fails under that condition, so skip the whole google/* set until the client sends Gemini-compatible message sequences. Routing itself is fine — this is a message-format incompatibility, not a gateway issue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Action items
|
|
Follow-up (Google Gemini): the Root cause: Gemini rejects any request whose message list ends on an assistant turn. Verified live against the gateway:
This affects every Gemini model uniformly (not just the newest), via any route — it is not specific to Cloudflare or the REST endpoint. It surfaced because opencode's agent loop round-trips the model's own reply back as the trailing message when checking for further tool calls; most providers tolerate that, Gemini does not. To reinstate: once the client normalizes Gemini message sequences (never end a request on an assistant turn — e.g. coalesce/trim trailing assistant turns for Gemini), drop the Minimal repro: |
The cloudflare-ai-gateway model files used to come from a committed catalog snapshot plus a hand-maintained overrides.toml. That snapshot was ~8 months stale and the overrides restated a lot the APIs already know. This reworks the generator to derive everything it can from Cloudflare's own live sources and shrink human curation to the few things those sources genuinely can't express.
Three live sources now drive generation:
curation.toml replaces overrides.toml and holds only the irreducible: hosted base_model maps, live-tested structured_output (schema acceptance != conformance), reasoning_options for native-format providers the schema doesn't expose, and a skip list.
Net model delta: +24 new, -14 phantom OpenAI ids absent from the live catalog, 52 updated (77 total: 58 proxied + 29 hosted). bun validate passes; live --check matches committed output with zero drift.