mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-24 02:39:42 +02:00
Follow-up to #5188 addressing the second-round review. - ValidateThinkingLevel now fails an empty codex model closed instead of borrowing the flagged Default (gpt-5.6-sol). An empty model follows config.toml, which can resolve to any installed model; Sol alone advertises `ultra`, so the old borrow green-lit levels Luna / gpt-5.5 don't support and Codex doesn't reject. Checked before ListModels so a discovery error can't fail it open. Frontend pickModelEntry mirrors this (no per-model effort preview for an empty codex model); the persisted-orphan clear path stays. - parseCodexDebugModels drops efforts without a known label so the picker never advertises a level the Create/Update enum gate would 400 on save; the contract test now drives the real parser with an unknown effort instead of comparing two hand-written maps. - gpt-5.6 price aliases anchor to a literal dot (not the [.-] class), so dashed variants like gpt-5-6-luna surface as unmapped on both backend and frontend rather than silently borrowing a tier. Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
97 lines
3.2 KiB
Go
97 lines
3.2 KiB
Go
package metrics
|
|
|
|
import "testing"
|
|
|
|
func TestPriceForModelAliasAnthropicFableAndOpus48(t *testing.T) {
|
|
cases := []struct {
|
|
model string
|
|
want ModelPrice
|
|
}{
|
|
{
|
|
model: "claude-sonnet-5",
|
|
want: ModelPrice{Provider: "anthropic", Model: "claude-sonnet-5", InputPerM: 2, CacheReadPerM: 0.2, CacheWritePerM: 2.5, OutputPerM: 10},
|
|
},
|
|
{
|
|
model: "anthropic:claude-sonnet-5",
|
|
want: ModelPrice{Provider: "anthropic", Model: "claude-sonnet-5", InputPerM: 2, CacheReadPerM: 0.2, CacheWritePerM: 2.5, OutputPerM: 10},
|
|
},
|
|
{
|
|
model: "claude-5-sonnet",
|
|
want: ModelPrice{Provider: "anthropic", Model: "claude-sonnet-5", InputPerM: 2, CacheReadPerM: 0.2, CacheWritePerM: 2.5, OutputPerM: 10},
|
|
},
|
|
{
|
|
model: "claude-fable-5",
|
|
want: ModelPrice{Provider: "anthropic", Model: "claude-fable-5", InputPerM: 10, CacheReadPerM: 1, CacheWritePerM: 12.5, OutputPerM: 50},
|
|
},
|
|
{
|
|
model: "anthropic/claude-fable-5",
|
|
want: ModelPrice{Provider: "anthropic", Model: "claude-fable-5", InputPerM: 10, CacheReadPerM: 1, CacheWritePerM: 12.5, OutputPerM: 50},
|
|
},
|
|
{
|
|
model: "claude-opus-4-8",
|
|
want: ModelPrice{Provider: "anthropic", Model: "claude-opus-4.8", InputPerM: 5, CacheReadPerM: 0.5, CacheWritePerM: 6.25, OutputPerM: 25},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
got, ok := PriceForModelAlias(tc.model)
|
|
if !ok {
|
|
t.Fatalf("PriceForModelAlias(%q) did not resolve", tc.model)
|
|
}
|
|
if got != tc.want {
|
|
t.Fatalf("PriceForModelAlias(%q) = %+v, want %+v", tc.model, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPriceForModelAliasCodexGPT56(t *testing.T) {
|
|
// Official rates from OpenAI's GPT-5.6 announcement: cache read = 0.1x
|
|
// input (90% cached-input discount), cache write = 1.25x input.
|
|
cases := []struct {
|
|
model string
|
|
want ModelPrice
|
|
}{
|
|
{
|
|
model: "gpt-5.6-sol",
|
|
want: ModelPrice{Provider: "openai", Model: "gpt-5.6-sol", InputPerM: 5, CacheReadPerM: 0.5, CacheWritePerM: 6.25, OutputPerM: 30},
|
|
},
|
|
{
|
|
model: "openai:gpt-5.6-terra",
|
|
want: ModelPrice{Provider: "openai", Model: "gpt-5.6-terra", InputPerM: 2.5, CacheReadPerM: 0.25, CacheWritePerM: 3.125, OutputPerM: 15},
|
|
},
|
|
{
|
|
model: "openai/gpt-5.6-luna",
|
|
want: ModelPrice{Provider: "openai", Model: "gpt-5.6-luna", InputPerM: 1, CacheReadPerM: 0.1, CacheWritePerM: 1.25, OutputPerM: 6},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
got, ok := PriceForModelAlias(tc.model)
|
|
if !ok {
|
|
t.Fatalf("PriceForModelAlias(%q) did not resolve", tc.model)
|
|
}
|
|
if got != tc.want {
|
|
t.Fatalf("PriceForModelAlias(%q) = %+v, want %+v", tc.model, got, tc.want)
|
|
}
|
|
}
|
|
|
|
// Unknown suffixed variants must NOT borrow a 5.6 tier — the alias is an
|
|
// anchored exact match, mirroring the frontend's exact-match resolver.
|
|
// The dash-normalized ids (`gpt-5-6-luna`) must also miss: the real Codex
|
|
// slug is always dotted and the frontend does not dash-normalize, so both
|
|
// sides surface these as unmapped instead of silently pricing them.
|
|
for _, model := range []string{
|
|
"gpt-5.6-luna-pro",
|
|
"gpt-5.6-luna/unknown",
|
|
"gpt-5.6-sol-high",
|
|
"gpt-5.6-mini",
|
|
"gpt-5-6-luna",
|
|
"gpt-5-6-sol",
|
|
"gpt-5-6-terra",
|
|
} {
|
|
if got, ok := PriceForModelAlias(model); ok {
|
|
t.Fatalf("PriceForModelAlias(%q) unexpectedly resolved to %+v; want unmapped", model, got)
|
|
}
|
|
}
|
|
}
|