Repository navigation
Feat: Price IBM Bob in Bobcoins in the built-in local config #1201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ import ( | |
| "encoding/json" | ||
| "encoding/pem" | ||
| "fmt" | ||
| "math" | ||
| "math/big" | ||
| "os" | ||
| "path/filepath" | ||
|
|
@@ -21,6 +22,7 @@ import ( | |
| "time" | ||
|
|
||
| "github.com/rossoctl/cortex/core/config" | ||
| "github.com/rossoctl/cortex/core/cost/pricing" | ||
| "github.com/rossoctl/cortex/core/cost/usage" | ||
| "github.com/rossoctl/cortex/core/session" | ||
| ) | ||
|
|
@@ -635,3 +637,52 @@ func TestBuiltinConfig_SeparatesCodingAgentSessions(t *testing.T) { | |
| t.Errorf("built-in config: id_headers = %v, want it to include %s", cfg.Session.SessionIDHeaders(), session.BobSessionHeader) | ||
| } | ||
| } | ||
|
|
||
| // TestBuiltinConfig_PricesBobInBobcoins: the built-in config prices IBM Bob's gateway at its | ||
| // flat 2 Bobcoins per million tokens on every tier. Without the entry, every Bob request is | ||
| // unpriced (its models — premium-ide, router, openai/gpt-oss-20b — are in no bundled table), and | ||
| // a new install shows "—" for Bob until its user finds docs/pricing.md. The rate is resolved | ||
| // through pricing.Build rather than read off the struct, so a unit or model key the table would | ||
| // not match fails here and not only on a laptop. | ||
| func TestBuiltinConfig_PricesBobInBobcoins(t *testing.T) { | ||
| cortexDir := t.TempDir() | ||
| p, err := writeBuiltinConfig(cortexDir, filepath.Join(cortexDir, "ca")) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| cfg, err := config.Load(p) | ||
| if err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| tab, err := pricing.Build(cfg.Pricing) | ||
| if err != nil { | ||
| t.Fatalf("pricing.Build: %v", err) | ||
| } | ||
|
|
||
| const bob = "api.us-east.bob.ibm.com" | ||
| for _, model := range []string{"premium-ide", "router", "openai/gpt-oss-20b"} { | ||
| rates, prov := tab.Resolve(bob, model, 0) | ||
| if prov != pricing.ProvConfigured { | ||
| t.Errorf("%s on %s: provenance %v, want configured", model, bob, prov) | ||
| } | ||
| // Rates are stored per token; the config states them per million. | ||
| for tier := pricing.TierInput; tier <= pricing.TierOutput; tier++ { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit — this loop is correct today and the "every tier" claim in the doc comment holds: But it reads as "input through output," and it would silently stop covering a tier appended after |
||
| if perMillion := rates.Base[tier] * 1e6; !rates.Set[tier] || math.Abs(perMillion-2) > 1e-9 { | ||
| t.Errorf("%s on %s: tier %d = %v per Mtok (set %v), want 2", model, bob, tier, perMillion, rates.Set[tier]) | ||
| } | ||
| } | ||
| if got := tab.CurrencyFor(bob, model); got != "Bobcoins" { | ||
| t.Errorf("%s on %s: unit %q, want Bobcoins", model, bob, got) | ||
| } | ||
| } | ||
|
|
||
| // The entry is scoped to Bob's host: its "*" model must not reprice anyone else's traffic | ||
| // in Bobcoins. | ||
| const other = "api.anthropic.com" | ||
| if _, prov := tab.Resolve(other, "claude-opus-5-5", 0); prov != pricing.ProvBundled { | ||
| t.Errorf("claude-opus-5-5 on %s: provenance %v, want bundled", other, prov) | ||
| } | ||
| if got := tab.CurrencyFor(other, "claude-opus-5-5"); got != pricing.CurrencyUSD { | ||
| t.Errorf("claude-opus-5-5 on %s: unit %q, want %s", other, got, pricing.CurrencyUSD) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion — this example is now stranded above the block it belongs to.
The comment at 236-237 says "Add it under endpoints below:", but the snippet sits above the new live
pricing:key (251), separated by the Bob paragraph, and it lost the# pricing:/# endpoints:parents it had onmain. Onmainthese four lines were self-contained — uncommenting them produced a valid standalone block. Now, uncommenting just these two lines puts a bare sequence item at document root and the config fails to load:(verified by extracting the template and parsing the result.) Note where that error points: at
pricing:, twelve lines below the lines the user actually edited. The diagnostic actively misdirects.The indent is off by one too — 5 spaces before the dash here vs 4 at line 253 — because the snippet was authored to be read with the
#gutter stripped, not uncommented in place.Suggest moving these two lines to just after 260, inside the live
endpoints:list, at 4-space/6-space indent, so uncommenting yields a valid second list item. The prose can stay where it is. A multiplier-only endpoint is legal once correctly placed, so nothing else needs to change.