feat: split LLM Gateway into two provider catalogs - #4011
Conversation
|
Note on the failing |
Renames the existing llmgateway provider to "DevPass (LLM Gateway)" (id
and models unchanged: the aggregated, auto-routed root-model catalog) and
adds llmgateway-providers ("LLM Gateway"): one entry per upstream
provider mapping, addressed as provider/model-id, synced from
/v1/models?mapped=true. The catalog starts empty and is populated by the
scheduled sync automation; the sync refuses to run against a deployment
without the mapped view so it fails loudly instead of syncing wrong ids.
Claude-Session: https://claude.ai/code/session_017pReWhniXJcDL9aiQHqoFQ
afe85a0 to
fb0d722
Compare
|
Restructured to sync-code only (7 files, matching how #2773 introduced the original llmgateway sync): dropped the 359 generated catalog files — the scheduled sync automation will populate |
Action items
|
Addresses the PR review: brand-new factored mapped entries now carry the mapping's own capability flags (attachment/tool_call/reasoning and structured_output) as overrides, translate the deployment's declared reasoning_efforts into reasoning_options instead of stamping [], prefer the gateway's served max_output over inherited/authored output limits, and only fall back to context when the base metadata declares no output. Adds unit tests for mapped factoring, capability overrides, max_output preference, and the unprefixed-id refusal guard. Claude-Session: https://claude.ai/code/session_017pReWhniXJcDL9aiQHqoFQ
|
Addressed the review items in f5678d9:
Verified by regenerating the full catalog locally against the gateway build: 359 files, |
Action items
|
…ass-split # Conflicts: # package.json
The dev branch now rejects providers with zero models, so the empty .gitkeep-anchored catalog no longer validates. Seed it with a small representative set generated by the sync (factored, full, duplicate deployments of one model, capability deltas); the scheduled sync fills in the rest once the gateway's mapped view is live. Claude-Session: https://claude.ai/code/session_017pReWhniXJcDL9aiQHqoFQ
Action items
|
Round 2 of review feedback: - Factored resyncs no longer stamp context as limit.output when the gateway omits max_output and the base declares an output to inherit; the served max_output still wins whenever reported (creates and resyncs), and reasoning_options now refresh from deployment efforts. - A deployment whose only accepted effort is "none" is a plain on/off switch, so it translates to a toggle (matches the lab's control). - When a deployment declares no efforts, mapped entries reuse the aggregated llmgateway catalog's curated reasoning_options for the same root model instead of ending up with []; a curated [] counts as unknown so a bad first stamp is not sticky. The runner also stops stamping [] onto factored reasoners whose base metadata already declares reasoning_options (it would shadow the base's controls). - perplexity added to the canonical prefixes so Sonar models factor against their lab metadata; the sonar-pro seed is now override-only. Claude-Session: https://claude.ai/code/session_017pReWhniXJcDL9aiQHqoFQ
|
Round 2 addressed in 48bf376:
Verified with a full local regeneration (359 files, |
Action items
|
Round 3 of review feedback: - Both LLM Gateway syncs now reject an empty (or fully filtered) response instead of authoritatively deleting the catalog through the delete-missing pass; the every() prefix guard alone passed on []. - A vision-less deployment also overrides modalities on factored creates, so attachment=false can no longer coexist with inherited image input (sonar-pro seed regenerated accordingly). - Mapped entries copy the interleaved reasoning side-channel from the aggregated llmgateway catalog when the deployment reasons (same wire surface); glm-5.1 and kimi-k2.6 seeds now carry it. - Toggle seeds carry the required leading wire-path comment. - gpt-5.5 seeds author the 272k context pricing tier so resync preserves it, matching the first-party and aggregated entries. Claude-Session: https://claude.ai/code/session_017pReWhniXJcDL9aiQHqoFQ
|
Round 3 addressed in 545d19a:
|
Action items
|
Round 4 of review feedback: - A missing/zero context_length is no longer written as limit.context=0: factored entries leave context unset and inherit the base, and unfactored creates without a positive served context are skipped (reported via sourceID) instead of publishing unusable limits. Applies to both the aggregated and mapped builders. - vision=false now forces non-image input modalities from the mapping itself instead of trusting the model-level architecture, on both the factored and unfactored create paths (and the existing-full fallback). Claude-Session: https://claude.ai/code/session_017pReWhniXJcDL9aiQHqoFQ
|
Round 4 addressed in 7ddf9e9:
Also verified end-to-end: running the sync over the committed seed files leaves all 7 byte-identical (leading toggle headers and the authored |
|
These are the provider names you want to go with? They seem a bit interesting.... Not intuitive. |
We want to have two providers:
one is model IDs only and the other has models mapped by providers. |
…ass-split # Conflicts: # packages/core/test/sync.test.ts
Action items
|
Review round 5: drop the fixed width/height from the new provider logo (AGENTS.md blocker), and fail the mapped sync loudly when a kept model does not carry exactly one providers[] mapping instead of letting the builder silently fall back to noisy supported_parameters defaults. Claude-Session: https://claude.ai/code/session_0131ZfUnTfJrCzygw3wE4bNF
Action items
|
Review round 6: mapped factored resyncs no longer stamp a synthesized describeModel blurb as a sticky description override (unset keeps inheriting the lab text, matching merge-gateway/cortecs), and mapped sync writes now author the required leading wire-path comment on files that carry a toggle reasoning control via a new optional header on the translateModel result (an existing on-disk header always wins). Claude-Session: https://claude.ai/code/session_0131ZfUnTfJrCzygw3wE4bNF
Action items
|
Review round 7: mapped existing-entry resyncs (factored and full) now apply the deployment mapping's reasoning/vision/tools/structured-output flags with the same authority as creates, so the written booleans and the reasoning_options derived from them always move together and drift self-heals hourly; prior curation only fills in where the mapping is silent. Also documents in the together-ai/kimi-k2.6 seed header why that pin is intentionally weaker than Together's first-party row (the gateway serves it with tools/JSON off and a 32k output cap per its own e2e'd catalog mapping). Claude-Session: https://claude.ai/code/session_0131ZfUnTfJrCzygw3wE4bNF
Action items
|
Review round 8: mapped resyncs no longer keep a stale text-only modalities override once the deployment's vision returns — a declared vision=true clears the override on factored entries (base image/pdf inputs inherit again) and recomputes from the served architecture on full entries, mirroring how vision=false already strips them; only a silent mapping leaves curated modalities untouched. Claude-Session: https://claude.ai/code/session_0131ZfUnTfJrCzygw3wE4bNF
Action items
|
Review round 9: drop the perplexity entry from the shared CANONICAL_PROVIDER_PREFIXES (it would silently start factoring other hosts' standalone perplexity files) — the llmgateway sync now resolves lab IDs through resolveModelMetadataBaseModel, whose exact models/ path match covers perplexity without touching other providers. Full-row resyncs in both builders no longer fall back to the zero/absent reported context: authored limits only ever carry known-positive values, an authored 0 on disk counts as unusable, and a full row with no usable context anywhere fails loudly (skipping would hand the file to the delete-missing pass). Claude-Session: https://claude.ai/code/session_0131ZfUnTfJrCzygw3wE4bNF
Action items
|
Review round 10: deployment reasoning_efforts now own only the effort/toggle surface — curated non-effort controls such as budget_tokens (the same host's $.reasoning.max_tokens path, mirroring DigitalOcean's sync) survive from the existing file or the aggregated sibling instead of being wiped on every resync. Mapped creates also seed cost.tiers from the aggregated sibling's curated tiers, since the gateway API exposes none and the bulk sync would otherwise author tiered models at flat long-context rates; authored tiers still win on resync. Claude-Session: https://claude.ai/code/session_0131ZfUnTfJrCzygw3wE4bNF
|
I've finished reviewing the split provider, sync module, seeds, logo, and reasoning-option handling against the project rules. |
|
Should be ready now @rekram1-node |
What
LLM Gateway is splitting its opencode-facing catalog into two providers served by the same endpoint and API key:
llmgateway→ renamed to "DevPass (LLM Gateway)" — everything else is unchanged: same provider id, same aggregated root-model catalog (claude-sonnet-5,gpt-5.5, …) auto-routed by the gateway, so existing user configs keep working as-is.llmgateway-providers("LLM Gateway") — new — one entry per upstream provider mapping, addressed the way the gateway accepts provider-pinned requests:provider/model-id(anthropic/claude-sonnet-5,azure/gpt-5.5, …), each with that specific deployment's pricing, context/output limits, and capabilities. Names carry the serving provider ("GPT-5.5 (Azure)") so duplicate deployments of the same model stay distinguishable.Following the pattern of the original llmgateway sync (#2773), this PR ships the sync code plus a small seed catalog (7 representative generated entries covering the factored, full, duplicate-deployment, and capability-delta paths —
devnow rejects providers with zero models, so the catalog can't start empty). The scheduled sync automation fills in the rest (~359 entries) once the gateway change is live.Sync
The new catalog is synced by a second module in
sync/providers/llmgateway.tsfromGET /v1/models?mapped=true(gateway PR):base_modelfrom the root model id +family(the serving provider prefix is unrelated to the originating lab). In a local run against the gateway build, 283 of 359 generated files factor against existingmodels/metadata; the disambiguating name override is deliberately kept on factored files and carried forward on resyncs.vision/tools/reasoningflags describe that specific deployment; those flags drive capability fields (as overrides on factored entries, with values equal to the base dropped), the deployment's declaredreasoning_effortstranslate intoreasoning_options, and the gateway's servedmax_outputtakes precedence over inherited/authored output limits.parseModelsthrows in that case instead of syncing wrong ids into the provider-pinned catalog. Until the gateway PR deploys, a scheduled sync ofllmgateway-providersfails loudly rather than producing a wrong PR.llmgateway/custom(BYO-model placeholder) andllmgateway/auto(the auto-router, already covered by the DevPass catalog) are excluded.context_lengthis optional in the gateway response (absent on pseudo-models and some non-text mappings), so the shared Zod schema and both builders now tolerate that.Verification
bun validatepasses with the seed catalog.max_output,max_outputprecedence on resyncs, and the unprefixed-id refusal guard.bun validatestill passes.