Skip to content

feat: add gpt-5.6 models and generate the known-models catalog from models.dev - #27146

Merged
johnstcn merged 14 commits into
mainfrom
mux/aibridge-gpt56-prices
Jul 14, 2026
Merged

feat: add gpt-5.6 models and generate the known-models catalog from models.dev#27146
johnstcn merged 14 commits into
mainfrom
mux/aibridge-gpt56-prices

Conversation

@johnstcn

@johnstcn johnstcn commented Jul 10, 2026

Copy link
Copy Markdown
Member

Follow-up to #27132 (review thread): the gpt-5.6 models routed correctly but had no price rows, so budgets recorded their usage at NULL cost, and no catalog entries, so they never appeared in the add-model UI.

Changes

  • Regenerate prices.json from models.dev. Adds the gpt-5.6 family and other new models. Delisted rows drop out; the seeder is upsert-only, so existing deployments keep them.
  • Generate the frontend known-models catalog instead of hand-writing it. make gen/aibridge-prices fetches models.dev once, applies overrides.jq (guarded factual corrections: claude-sonnet-4-5 context pinned to the flat-priced 200k tier; claude-mythos-5 injected as a claude-fable-5 copy since upstream omits it), and emits both artifacts from that snapshot. Editorial choices (selection, order, aliases, reasoning defaults) live in curation.json and are validated at generation time. Deletes the hand-written openai.ts and anthropic.ts.
  • Adds golden join tests with one error case per validation, a no-network drift test comparing curation to the checked-in artifact, and pinned invariants for the Anthropic thinking-mode split (the wrong side returns HTTP 400) and the sonnet-4-5 context pin.

Adding a model is now one curation.json entry plus make gen/aibridge-prices.

Note

Upstream changed gpt-3.5-turbo cache_read_price from a bogus 1250000 to 0. The model does not support prompt caching (as far as the resources consulted said), so no cache-read tokens ever accrue and both values are inert.

This PR was authored by Coder Agents on Cian's behalf.

@johnstcn johnstcn changed the title chore(coderd/aibridge/prices): regenerate price seed for gpt-5.6 models feat: add gpt-5.6 models to price seed and known-model catalog Jul 10, 2026
@johnstcn johnstcn changed the title feat: add gpt-5.6 models to price seed and known-model catalog feat: add gpt-5.6 models and generate the known-models catalog from models.dev Jul 10, 2026
johnstcn

This comment was marked as resolved.

Comment on lines -4 to -64
"model": "claude-3-5-haiku-20241022",
"input_price": 800000,
"output_price": 4000000,
"cache_read_price": 80000,
"cache_write_price": 1000000
},
{
"provider": "anthropic",
"model": "claude-3-5-haiku-latest",
"input_price": 800000,
"output_price": 4000000,
"cache_read_price": 80000,
"cache_write_price": 1000000
},
{
"provider": "anthropic",
"model": "claude-3-5-sonnet-20240620",
"input_price": 3000000,
"output_price": 15000000,
"cache_read_price": 300000,
"cache_write_price": 3750000
},
{
"provider": "anthropic",
"model": "claude-3-5-sonnet-20241022",
"input_price": 3000000,
"output_price": 15000000,
"cache_read_price": 300000,
"cache_write_price": 3750000
},
{
"provider": "anthropic",
"model": "claude-3-7-sonnet-20250219",
"input_price": 3000000,
"output_price": 15000000,
"cache_read_price": 300000,
"cache_write_price": 3750000
},
{
"provider": "anthropic",
"model": "claude-3-haiku-20240307",
"input_price": 250000,
"output_price": 1250000,
"cache_read_price": 30000,
"cache_write_price": 300000
},
{
"provider": "anthropic",
"model": "claude-3-opus-20240229",
"input_price": 15000000,
"output_price": 75000000,
"cache_read_price": 1500000,
"cache_write_price": 18750000
},
{
"provider": "anthropic",
"model": "claude-3-sonnet-20240229",
"input_price": 3000000,
"output_price": 15000000,
"cache_read_price": 300000,
"cache_write_price": 300000

This comment was marked as resolved.

This comment was marked as resolved.

@johnstcn

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-07-10 21:07 UTC by @johnstcn
Spend: $63.60 / $100.00

Review history
  • R1 (2026-07-10): 19 reviewers, 3 Nit, 1 Note, 3 P3, COMMENT. Review
  • R2 (2026-07-10): 11 reviewers, 3 Nit, 3 Note, 2 P2, 4 P3, COMMENT. Review

deep-review v0.9.0 | Round 2 | 9af3d31..db13913

Last posted: Round 2, 12 findings (2 P2, 4 P3, 3 Nit, 3 Note), COMMENT. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Author fixed (db1391) overrides.jq:21 claude-mythos-5 injection lacks guard against upstream adding the model R1 Netero Yes
CRF-2 P3 Author fixed (db1391) catalog_test.go:58 Golden test missing thinkingBudgetTokens and DisplayName override paths R1 Bisky P3, Chopper P3, Kite Note Yes
CRF-3 P3 Author fixed (db1391) catalog.go:98 No validation that upstream provides token limits for curated models R1 Knov Yes
CRF-4 Note Author fixed (db1391) catalog_test.go:136 Missing empty-displayName error case in error table R1 Bisky Yes
CRF-5 Nit Author fixed (db1391) catalog.go:15 Comment claims last_updated is joined from upstream; field does not exist R1 Leorio Yes
CRF-6 Nit Author fixed (db1391) catalog_test.go:141 MissingProvider and MissingUpstreamModel error cases share same wantErr substring R1 Chopper Yes
CRF-7 Nit Author fixed (db1391) main.go:1 Package doc describes only prices output; tool now has two formats R1 Leorio Yes
CRF-8 Nit Dropped by orchestrator (pattern-level note in CRF-5 body) catalog.go:12 Comment padding (~40%) in curationJSON doc comment R1 Gon No
CRF-9 Nit Dropped by orchestrator (same pattern as CRF-8, covered by CRF-5 note) catalog.go:39 Comment padding in catalogEntry doc comment R1 Gon No
CRF-10 Nit Dropped by orchestrator (Leorio praises same comment; disagreement resolved in favor of keep) index.ts:7 Comment padding in knownModelsByProvider R1 Gon No
CRF-11 Nit Dropped by orchestrator (trivial five-line pattern, loses type safety if merged) catalog.go:140 writeCatalog duplicates write pattern from main.go R1 Robin No
CRF-12 Nit Dropped by orchestrator (moot if squash-merging) catalog.go:1 Commit scope does not contain all changed files R1 Mafu-san No
CRF-13 P2 Open overrides.jq:13 sonnet-4-5 context pin rests on a false premise (no anthropic tiered pricing); only understates UI badge, inconsistent with siblings R2 Mafuuu Yes
CRF-14 P2 Open main.go:181 Price seed drops context_over_200k tiered pricing for 6 OpenAI 1M-context models, under-recording large-context usage ~2x R2 Mafuuu Yes
CRF-15 P3 Open catalog.go:133 Uniqueness/shadow guards are case-sensitive but frontend alias lookup is case-insensitive; guard does not fully enforce its stated invariant R2 Hisoka P3, Meruem P3 Yes
CRF-16 Note Open catalog.go:105 buildCatalog rejects nil limits but accepts non-positive (zero); emits contextLimit:0 which formatContextBadge throws on R2 Meruem Yes
CRF-17 Note Open catalog.go:102 hasPricing accepts any-one cost (OR) but limits require both (AND); cache-only cost passes with input/output omitted R2 Bisky Yes

Law analysis

Effective LOC: +1066 -608 (16 files). Head SHA: 67f983f. Verdict: Don't split. Enforcement: N/A.

Contested and acknowledged

(none)

Round log

Round 1

Panel (19 reviewers). 3 P3, 1 Note, 4 Nit new. 4 dropped. Reviewed against 9af3d31..67f983f.

Round 2 update

Churn guard PROCEED: CRF-1..CRF-7 all Author fixed (db1391). Panel re-review pending. Reviewed against 9af3d31..db13913.

Round 2

Panel (11 reviewers) + Netero (advisory, no findings). 2 P2, 1 P3, 2 Note new. CRF-1..CRF-7 verified fixed. CRF-13/CRF-14 (tiered pricing) verified against live models.dev by orchestrator. Reviewed against 9af3d31..db13913.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

coder-agents-review[bot]

This comment was marked as resolved.

@johnstcn

Copy link
Copy Markdown
Member Author

/coder-agents-review

@johnstcn
johnstcn marked this pull request as ready for review July 10, 2026 21:20
Copilot AI review requested due to automatic review settings July 10, 2026 21:20

This comment was marked as resolved.

johnstcn and others added 2 commits July 10, 2026 22:28
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@coder-agents-review coder-agents-review 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.

Deep review round 2 (11 panel reviewers + Netero).

All seven round-1 findings (CRF-1..CRF-7) are genuinely fixed in db1391, verified against the code and tests, not just the labels: the overrides.jq mythos guard now errors on both drift directions, the golden test exercises the DisplayName-override and thinkingBudgetTokens paths, buildCatalog fails closed on nil upstream limits, the error table gained MissingUpstreamLimit and EmptyUpstreamName, the stale last_updated comment is gone, the two "missing from upstream" cases assert distinct substrings, and the package doc covers both formats. Clean work.

Round-2 findings: 2 P2, 1 P3, 2 Notes across 5 inline comments.

The two P2s are a linked pair about tiered pricing, and I verified both against a live models.dev fetch. Upstream ships context_over_200k / tiers blocks for the OpenAI 1M-context models (gpt-5.6-sol base input 5 / output 30, over-200k input 10 / output 45) and for none of the Anthropic models. So the handling is backwards: the model that gets the context pin (sonnet-4-5) has no tiered pricing and the pin only makes its UI badge understate a 1M window, while the models that do have tiered pricing (the gpt-5.x family) go unpinned and their price seed silently records base-tier only. Both need a human decision, not a silent default.

The P3 (case-sensitive guard vs case-insensitive consumer) converged from two reviewers independently.

Process notes, none blocking: the buildCatalog validation loop reports errors while ranging a map, so with errors under two providers the surfaced message is nondeterministic (Komugi, developer-ergonomics only). The knownModelsGenerated.test.ts it.each assertions omit the model identifier from failure messages, so a failure names the provider but not the model (Chopper). Several applyKnownModelDefaults logic tests read real catalog entries rather than synthetic fixtures, so a future regeneration can break them for reasons unrelated to the logic (Bisky). And the gpt-5.6 alias on gpt-5.6-sol shadows a distinct bare gpt-5.6 model that also exists upstream; benign today because their pricing is identical, but the alias guard only checks curated identifiers, not the upstream model list (Pariston).

"You built a cage of invariants and dared the input to escape. Most of it holds." (Hisoka)

This review was generated by Coder Agents.


scripts/aibridgepricesgen/main.go:181

P2 [CRF-14] The price seed silently drops context-tiered pricing for the OpenAI 1M-context models, so budgets under-record large-context usage. upstreamCost parses only flat input/output/cache_read/cache_write, and convert writes those base rates into prices.json.

Verified against a live models.dev fetch: gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.5-pro, and gpt-5.4 all carry a context_over_200k block (and a tiers entry at tier.size: 272000) where prices roughly double above 200k. gpt-5.6-sol is input:5, output:30 at base and input:10, output:45 above 200k, but the seed records the base tier only (input_price: 5000000), while the same model advertises contextLimit: 1050000.

This lands on the exact feature the PR exists to fix: for any request whose context exceeds the tier boundary, the cost-control loader records well under half the real input spend, so budgets run too lax for large-context OpenAI usage. Flat-only pricing is a documented Coder constraint for the catalog display (catalog.go comment), but the price-seed path has no equivalent acknowledgement and no test pins the behavior. A human should decide whether flat-only seed pricing is acceptable for these tiered models and, if so, document it where the seed is generated. (Mafuuu)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread scripts/aibridgepricesgen/overrides.jq
}
// An alias resolving to a canonical identifier would make exact-alias
// lookup and canonical-id lookup disagree.
for alias := range seenAliases {

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.

P3 [CRF-15] The uniqueness and shadow guards compare identifiers and aliases with case-sensitive equality, but the frontend resolves aliases case-insensitively, so the invariant the guard exists to protect can still be violated.

The comment states the invariant: "An alias resolving to a canonical identifier would make exact-alias lookup and canonical-id lookup disagree." But the shadow check (seenIdentifiers[alias]) and the alias dedup (seenAliases[alias]) both key on raw curation strings. The consumers disagree on case: findKnownModelByExactAlias compares alias.toLowerCase() === value.toLowerCase() (index.ts), while findKnownModelByCanonicalId compares modelIdentifier === modelId (case-sensitive), and ModelIdentifierField runs canonical-id first, then falls through to exact-alias.

Verified in index.ts. So an alias "Claude-Sonnet-4-5" on model A alongside a modelIdentifier claude-sonnet-4-5 on model B passes every guard (the strings differ by case), yet at runtime findKnownModelByExactAlias("claude-sonnet-4-5") matches A while findKnownModelByCanonicalId("claude-sonnet-4-5") matches B: exactly the disagreement the guard claims to prevent. The same gap lets two aliases differing only in case survive dedup and resolve ambiguously.

Latent today because all curation entries are lowercase, but the guard's whole purpose is to make the bad state impossible for the next curation author. Lowercasing the keys in seenIdentifiers, seenAliases, and the post-loop cross-check matches the generator's uniqueness domain to the consumer's lookup domain. (Hisoka P3, Meruem P3)

🤖

if !m.Cost.hasPricing() {
return nil, xerrors.Errorf("%s/%s: upstream model has no cost block", providerID, c.ModelIdentifier)
}
if m.Limit.Context == nil || m.Limit.Output == nil {

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.

Note [CRF-16] The new limit check rejects nil context/output but accepts non-positive ones. A present-but-zero upstream limit (pointer to 0) passes, and because catalogEntry's *int64 fields use omitempty (which drops nil, not zero), the generator emits "contextLimit": 0. That value is semantically invalid: formatContextBadge throws on contextLimit <= 0 (verified in index.ts), and knownModelsGenerated.test.ts asserts every limit is > 0. CI would catch a zero-limit artifact before merge, so the consequence is bounded, but the generator that owns the join could enforce the invariant it produces by tightening the check to *m.Limit.Context <= 0 || *m.Limit.Output <= 0. (Meruem)

🤖

if !ok {
return nil, xerrors.Errorf("%s/%s: model missing from upstream (patch it in via overrides.jq if intentional)", providerID, c.ModelIdentifier)
}
if !m.Cost.hasPricing() {

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.

Note [CRF-17] buildCatalog requires cost with OR (hasPricing accepts any one of input/output/cache_read/cache_write) but limits with AND (both context and output must be present). A curated model whose upstream cost block carries only cache_read passes validation and emits a catalog entry with inputCost/outputCost omitted. TestBuildCatalog covers the reverse (input+output, no cache) but nothing covers cost-present-yet-input/output-absent. Blast radius is small: this feeds only the frontend pre-fill, not the prices.json budget seed (separate convert path), and curated models always carry input/output pricing today. If the asymmetry is deliberate (costs are optional pre-fills, limits drive badges), a one-line comment would keep the next reader from "fixing" it; otherwise an error-table row asserting a missing input/output price would close it. (Bisky)

🤖

@ssncferreira ssncferreira 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.

LGTM from AI Gateway cost control side of things, just a few suggestions and comment

Comment thread scripts/aibridgepricesgen/overrides.jq Outdated
Comment thread scripts/aibridgepricesgen/main.go Outdated
Comment thread scripts/aibridgepricesgen/overrides.jq Outdated
Comment on lines +9 to +12
# claude-sonnet-4-5: upstream advertises a 1M-token context window, which
# implies tiered context_over_200k pricing. Coder persists flat pricing only,
# so pin the context limit to the flat-priced 200k tier.
if .anthropic.models | has("claude-sonnet-4-5") then

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.

I'm slightly confused about this comment, if I run:

> curl -fsSL https://models.dev/api.json | jq '.anthropic.models."claude-sonnet-4-5"'
{
  "id": "claude-sonnet-4-5",
  "name": "Claude Sonnet 4.5 (latest)",
  "description": "Balanced Claude model for coding, analysis, agent workflows, and cost control",
  "family": "claude-sonnet",
  "attachment": true,
  "reasoning": true,
  "reasoning_options": [
    {
      "type": "budget_tokens",
      "min": 1024
    }
  ],
  "tool_call": true,
  "structured_output": true,
  "temperature": true,
  "knowledge": "2025-07-31",
  "release_date": "2025-09-29",
  "last_updated": "2025-09-29",
  "modalities": {
    "input": [
      "text",
      "image",
      "pdf"
    ],
    "output": [
      "text"
    ]
  },
  "open_weights": false,
  "limit": {
    "context": 1000000,
    "output": 64000
  },
  "cost": {
    "input": 3,
    "output": 15,
    "cache_read": 0.3,
    "cache_write": 3.75
  }
}

So, based on this response, I would assume the cost object (what gets stored in ai_model_prices) is for a context limit of up to a 1M-token context window... but for catalog, we are setting a limit of 200k?

Edit: I just realized this is related to the comment below

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yep, there was an email on May 1st about this but models.dev doesn't seem to have updated.

@johnstcn
johnstcn enabled auto-merge (squash) July 14, 2026 19:29
@johnstcn
johnstcn merged commit 8eaf4f5 into main Jul 14, 2026
28 of 29 checks passed
@johnstcn
johnstcn deleted the mux/aibridge-gpt56-prices branch July 14, 2026 19:36
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 14, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants