catalog: a model a relay renamed (claude-deepseek-v4.1-flash[1M]) gets its levels, window, images, name and price from the model it is (#1383, uWydnA)
Relays rename models for Claude Code with a "claude-" prefix and/or a
window mark ("[1M]", "[200K]"). Every capability getter looked the bare id
up exactly, so such a model had an empty reasoning menu, no images (the
gateway's vision stand-in described pictures to a model that sees them)
and a 0 window.
Now every getter shares one lookup (lookup -> found -> renamed):
- found is the chain EffortsOf and ContextOf already had (exact, a Bedrock
geography, "(variant)", ":tag"), now also used by Thinks, SeesImages,
NameOf and OutputOf, which matched the exact id only.
- renamed applies only when models.dev lists no model by the id at all
(a new `listed` set of every bare id, whatever it says of it): a window
mark comes off first, and if what is left is listed it is the model
(Claude Code's claude-opus-5[1m] stays claude-opus-5, never "opus-5");
else a "claude-" prefix comes off when the rest is listed and names a
version, so the reporter's counter-example claude-auto[1M] is not
models.dev's router "auto".
- ContextOf gives a mark's window (what the relay serves it at, as Claude
Code takes [1m] for 1M); NameOf keeps the mark ("DeepSeek V4.1 Flash
[1M]") so two models apart by window alone read apart.
- Siblings: PricedBy (last, after the dot/dash match) and ListedBy take
the rename too; Knows follows from ContextOf/EffortsOf/Thinks.
Ran: the reporter's TestRelayRenamedIDLosesCapabilities verbatim, plus
tests for every getter on the renamed forms, Claude [1m] ids, a listed
claude-glm-5 keeping its own (empty) levels over glm-5's, near misses
(claude-auto, -flashy, [fast], [infm], anthropic- prefix) matching
nothing, gpt-5.5/auto/opus-5/glm-5 unchanged. Broken by hand seven ways
(no rename, no listed gate, no version check, mark window ignored, prefix
before the mark-stripped id, PricedBy or ListedBy without the rename):
each FAILs naming the getter. Against today's models.dev api.json:
claude-deepseek-v4.1-flash[1M] -> low,high,max, 1M, images; claude-auto[1M]
-> nothing; claude-code (listed) as before. gofmt -l clean; go vet,
GOOS=linux go vet -tags nogui, GOOS=windows go vet ./...; go build for
darwin, linux (nogui), windows; go test -tags nogui ./... in a temp HOME
all ok.
This commit is contained in:
1 parent
4cbde14cea
commit
1069785dc7
2 files changed
+332
-33
No files matched your search
+143
-33
@@ -13,6 +13,7 @@ import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -338,6 +339,9 @@ var (
|
||||
// names are the models' names, by bare id, as most of the providers
|
||||
// serving them give it
|
||||
names map[string]string
|
||||
// listed are the bare ids of every model models.dev lists, whatever it
|
||||
// says of them: an id listed is never taken for another (renamed)
|
||||
listed map[string]bool
|
||||
|
||||
syncMu sync.Mutex
|
||||
)
|
||||
@@ -383,8 +387,10 @@ func load() map[string]mdProvider {
|
||||
// (llmgateway's deepinfra/…, xiaomi/…) votes once, not once
|
||||
// a host
|
||||
voted := map[string]bool{}
|
||||
listed = map[string]bool{}
|
||||
for pid, p := range m {
|
||||
for id, x := range p.Models {
|
||||
listed[bareID(id)] = true
|
||||
OneHourFor(id, x.Cost)
|
||||
if x.Name != "" && x.Name != id && x.Name != bareID(id) {
|
||||
if named[bareID(id)] == nil {
|
||||
@@ -467,7 +473,7 @@ func Reset() {
|
||||
loadMu.Lock()
|
||||
defer loadMu.Unlock()
|
||||
loaded = false
|
||||
mdev, images, windows, outputs, efforts, thinks, names = nil, nil, nil, nil, nil, nil, nil
|
||||
mdev, images, windows, outputs, efforts, thinks, names, listed = nil, nil, nil, nil, nil, nil, nil, nil
|
||||
}
|
||||
|
||||
// Sync downloads the models.dev catalog into CachePath. It serializes with
|
||||
@@ -575,6 +581,11 @@ func PricedBy(providers []string, id string) (Price, bool) {
|
||||
return *all[pid].Models[keys[0]].Cost, true
|
||||
}
|
||||
}
|
||||
// and a relay's rename of a model none of them lists as given
|
||||
// (claude-deepseek-v4.1-flash[1M] is deepseek-v4.1-flash, #1383)
|
||||
if r, _, ok := renamed(b); ok {
|
||||
return PricedBy(providers, r)
|
||||
}
|
||||
return Price{}, false
|
||||
}
|
||||
|
||||
@@ -641,7 +652,8 @@ func ProviderName(id string) string {
|
||||
// others.
|
||||
func Thinks(id string) bool {
|
||||
load()
|
||||
return thinks[bareID(id)]
|
||||
v, _ := lookup(id, thinks)
|
||||
return v
|
||||
}
|
||||
|
||||
// NameOf is the name models.dev gives a model of this id, as most of the
|
||||
@@ -650,7 +662,21 @@ func Thinks(id string) bool {
|
||||
// other than by its id.
|
||||
func NameOf(id string) string {
|
||||
load()
|
||||
return names[bareID(id)]
|
||||
b := bareID(id)
|
||||
if n, ok := found(b, names); ok {
|
||||
return n
|
||||
}
|
||||
r, w, ok := renamed(b)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
n, _ := found(r, names)
|
||||
if n != "" && w > 0 {
|
||||
// two of a relay's models apart by their window alone read apart:
|
||||
// "DeepSeek V4.1 Flash [1M]", the mark as the id writes it
|
||||
n += " " + id[strings.LastIndexByte(id, '['):]
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Named gives a model the list names by its id alone the name models.dev
|
||||
@@ -682,7 +708,8 @@ func Knows(id string) bool {
|
||||
// glm-5.3).
|
||||
func SeesImages(id string) bool {
|
||||
load()
|
||||
return images[bareID(id)]
|
||||
v, _ := lookup(id, images)
|
||||
return v
|
||||
}
|
||||
|
||||
// ContextOf is the context window models.dev gives a model of this id, as
|
||||
@@ -691,23 +718,22 @@ func SeesImages(id string) bool {
|
||||
// proxy serving "openai/gpt-5.4", or "gpt-5.4(high)" with the reasoning
|
||||
// level CLIProxyAPI takes in the id, which a static model list would
|
||||
// otherwise leave at the agent's default.
|
||||
//
|
||||
// A relay's rename of a model it knows (renamed) that names its window
|
||||
// ("[1M]", "[200K]", Claude Code's "[1m]") has the window it names: what
|
||||
// the relay serves it at, as Claude Code takes claude-opus-5[1m] for 1M.
|
||||
func ContextOf(id string) int {
|
||||
load()
|
||||
b := bareID(id)
|
||||
if w, ok := windows[b]; ok {
|
||||
if w, ok := found(b, windows); ok {
|
||||
return w
|
||||
}
|
||||
if r, ok := unprofiled(b); ok {
|
||||
return ContextOf(r)
|
||||
}
|
||||
if i := strings.IndexByte(b, '('); i > 0 && strings.HasSuffix(b, ")") {
|
||||
b = b[:i]
|
||||
if w, ok := windows[b]; ok {
|
||||
if r, w, ok := renamed(b); ok {
|
||||
if w > 0 {
|
||||
return w
|
||||
}
|
||||
}
|
||||
if i := strings.IndexByte(b, ':'); i > 0 { // ":free", ":batch"
|
||||
return windows[b[:i]]
|
||||
w, _ = found(r, windows)
|
||||
return w
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -716,17 +742,8 @@ func ContextOf(id string) int {
|
||||
// most of the providers serving it give it, or 0 when not known.
|
||||
func OutputOf(id string) int {
|
||||
load()
|
||||
b := bareID(id)
|
||||
if o, ok := outputs[b]; ok {
|
||||
return o
|
||||
}
|
||||
if r, ok := unprofiled(b); ok {
|
||||
return OutputOf(r)
|
||||
}
|
||||
if i := strings.IndexAny(b, "(:"); i > 0 {
|
||||
return outputs[b[:i]]
|
||||
}
|
||||
return 0
|
||||
o, _ := lookup(id, outputs)
|
||||
return o
|
||||
}
|
||||
|
||||
// EffortsOf is the reasoning levels models.dev gives a model of this id, as
|
||||
@@ -737,23 +754,113 @@ func OutputOf(id string) int {
|
||||
// reason, and an agent offer no levels for it.
|
||||
func EffortsOf(id string) []string {
|
||||
load()
|
||||
e, _ := lookup(id, efforts)
|
||||
return slices.Clone(e)
|
||||
}
|
||||
|
||||
// lookup is what table holds for a model of this id: as found finds it,
|
||||
// else as the relay's rename of a model found finds (renamed). Every
|
||||
// capability is looked up the same way, so a model the catalog knows has
|
||||
// all of them or none.
|
||||
func lookup[T any](id string, table map[string]T) (T, bool) {
|
||||
b := bareID(id)
|
||||
if e, ok := efforts[b]; ok {
|
||||
return slices.Clone(e)
|
||||
if v, ok := found(b, table); ok {
|
||||
return v, true
|
||||
}
|
||||
if r, _, ok := renamed(b); ok {
|
||||
return found(r, table)
|
||||
}
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
|
||||
// found is what table holds for a bare id: by the id itself, a Bedrock
|
||||
// profile's without its geography, "gpt-5.4(high)" (CLIProxyAPI's level in
|
||||
// the id) as gpt-5.4, or "glm-5:free" (":batch", ":thinking") as glm-5.
|
||||
func found[T any](b string, table map[string]T) (T, bool) {
|
||||
if v, ok := table[b]; ok {
|
||||
return v, true
|
||||
}
|
||||
if r, ok := unprofiled(b); ok {
|
||||
return EffortsOf(r)
|
||||
return found(r, table)
|
||||
}
|
||||
if i := strings.IndexByte(b, '('); i > 0 && strings.HasSuffix(b, ")") {
|
||||
if v, ok := table[b[:i]]; ok {
|
||||
return v, true
|
||||
}
|
||||
b = b[:i]
|
||||
if e, ok := efforts[b]; ok {
|
||||
return slices.Clone(e)
|
||||
}
|
||||
if i := strings.IndexByte(b, ':'); i > 0 {
|
||||
v, ok := table[b[:i]]
|
||||
return v, ok
|
||||
}
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
|
||||
// relayPrefixes are what relays put before a model's id to rename it, so
|
||||
// Claude Code, which takes a model for a Claude one by its name, serves it:
|
||||
// claude-deepseek-v4.1-flash (#1383).
|
||||
var relayPrefixes = []string{"claude-"}
|
||||
|
||||
// renamed is the model a relay renamed to b, when models.dev lists none
|
||||
// by b (found), and the window the rename names, if any: a window mark
|
||||
// taken off ("deepseek-v4.1-flash[1m]", "[200k]", and Claude Code's
|
||||
// claude-opus-5[1m], which is claude-opus-5), then a relay's prefix
|
||||
// ("claude-deepseek-v4.1-flash"). An id models.dev lists is always its own
|
||||
// model, never renamed; a prefix comes off only when what is left is
|
||||
// listed, and names a version ("claude-auto" is not the router "auto").
|
||||
func renamed(b string) (string, int, bool) {
|
||||
if _, ok := found(b, listed); ok {
|
||||
return "", 0, false
|
||||
}
|
||||
r, w := unmarked(b)
|
||||
if r != b {
|
||||
if _, ok := found(r, listed); ok {
|
||||
return r, w, true
|
||||
}
|
||||
}
|
||||
if i := strings.IndexByte(b, ':'); i > 0 { // ":free", ":thinking"
|
||||
return slices.Clone(efforts[b[:i]])
|
||||
for _, pre := range relayPrefixes {
|
||||
rest, ok := strings.CutPrefix(r, pre)
|
||||
if !ok || !strings.ContainsAny(rest, "0123456789") {
|
||||
continue
|
||||
}
|
||||
if _, ok := found(rest, listed); ok {
|
||||
return rest, w, true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return "", 0, false
|
||||
}
|
||||
|
||||
// unmarked is a bare id without a window mark at its end ("[1m]",
|
||||
// "[200k]", "[1.5m]") and the window it names, or the id and 0 without one.
|
||||
func unmarked(b string) (string, int) {
|
||||
if !strings.HasSuffix(b, "]") {
|
||||
return b, 0
|
||||
}
|
||||
i := strings.LastIndexByte(b, '[')
|
||||
if i <= 0 {
|
||||
return b, 0
|
||||
}
|
||||
mark := b[i+1 : len(b)-1]
|
||||
unit := 0
|
||||
switch {
|
||||
case strings.HasSuffix(mark, "k"):
|
||||
unit = 1_000
|
||||
case strings.HasSuffix(mark, "m"):
|
||||
unit = 1_000_000
|
||||
default:
|
||||
return b, 0
|
||||
}
|
||||
num := mark[:len(mark)-1]
|
||||
if strings.Trim(num, "0123456789.") != "" {
|
||||
return b, 0
|
||||
}
|
||||
n, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil || n <= 0 {
|
||||
return b, 0
|
||||
}
|
||||
return b[:i], int(n * float64(unit))
|
||||
}
|
||||
|
||||
// ListedBy is the reasoning levels the first of providers (models.dev
|
||||
@@ -768,6 +875,9 @@ func EffortsOf(id string) []string {
|
||||
func ListedBy(providers []string, id string) ([]string, bool) {
|
||||
all := load()
|
||||
b := bareID(id)
|
||||
if r, _, ok := renamed(b); ok {
|
||||
b = r
|
||||
}
|
||||
if r, ok := unprofiled(b); ok {
|
||||
b = r
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package catalog
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// The reporter's own test (#1383, uWydnA): a relay renames
|
||||
// deepseek-v4.1-flash claude-deepseek-v4.1-flash[1M], and the renamed
|
||||
// model had no levels, no window and no images.
|
||||
func TestRelayRenamedIDLosesCapabilities(t *testing.T) {
|
||||
writeCatalog(t, `{
|
||||
"deepseek": {"models": {"deepseek-v4.1-flash": {"id":"deepseek-v4.1-flash",
|
||||
"reasoning_options":[{"type":"effort","values":["low","high","max"]}],
|
||||
"modalities":{"input":["text","image"],"output":["text"]},
|
||||
"limit":{"context":1000000,"output":65536}}}}}`)
|
||||
|
||||
for _, id := range []string{"deepseek-v4.1-flash", "claude-deepseek-v4.1-flash[1M]"} {
|
||||
t.Run(id, func(t *testing.T) {
|
||||
if got := strings.Join(EffortsOf(id), ","); got != "low,high,max" {
|
||||
t.Errorf("EffortsOf(%q) = %q, want low,high,max", id, got)
|
||||
}
|
||||
if got := ContextOf(id); got != 1000000 {
|
||||
t.Errorf("ContextOf(%q) = %d, want 1000000", id, got)
|
||||
}
|
||||
if !SeesImages(id) {
|
||||
t.Errorf("SeesImages(%q) = false, want true", id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const renamedCatalog = `{
|
||||
"deepseek": {"name":"DeepSeek","models": {
|
||||
"deepseek-v4.1-flash": {"id":"deepseek-v4.1-flash","name":"DeepSeek V4.1 Flash",
|
||||
"reasoning_options":[{"type":"effort","values":["low","high","max"]}],
|
||||
"modalities":{"input":["text","image"],"output":["text"]},
|
||||
"cost":{"input":0.3,"output":1.2},
|
||||
"limit":{"context":1000000,"output":65536}}}},
|
||||
"anthropic": {"name":"Anthropic","models": {
|
||||
"claude-opus-5": {"id":"claude-opus-5","name":"Claude Opus 5","reasoning":true,
|
||||
"reasoning_options":[{"type":"effort","values":["low","medium","high"]}],
|
||||
"modalities":{"input":["text","image"],"output":["text"]},
|
||||
"cost":{"input":5,"output":25},
|
||||
"limit":{"context":200000,"output":64000}}}},
|
||||
"elsewhere": {"name":"Elsewhere","models": {
|
||||
"opus-5": {"id":"opus-5","name":"Not Claude",
|
||||
"reasoning_options":[{"type":"effort","values":["minimal"]}],
|
||||
"limit":{"context":32000,"output":4000}},
|
||||
"glm-5": {"id":"glm-5","name":"GLM-5","reasoning":true,
|
||||
"reasoning_options":[{"type":"effort","values":["low","high"]}],
|
||||
"modalities":{"input":["text","image"],"output":["text"]},
|
||||
"limit":{"context":200000,"output":8000}},
|
||||
"claude-glm-5": {"id":"claude-glm-5","name":"Claude GLM 5",
|
||||
"modalities":{"input":["text"],"output":["text"]},
|
||||
"limit":{"context":64000,"output":4000}}}},
|
||||
"model-oracle-ai": {"name":"Oracle","models": {
|
||||
"model-oracle-ai/auto": {"id":"model-oracle-ai/auto","name":"Auto Router",
|
||||
"reasoning_options":[{"type":"effort","values":["none","minimal","low","medium","high","xhigh","max"]}],
|
||||
"modalities":{"input":["text","image"],"output":["text"]},
|
||||
"limit":{"context":2000000,"output":100000}}}},
|
||||
"openai": {"name":"OpenAI","models": {
|
||||
"gpt-5.5": {"id":"gpt-5.5","name":"GPT-5.5",
|
||||
"reasoning_options":[{"type":"effort","values":["low","medium","high","xhigh"]}],
|
||||
"modalities":{"input":["text","image"],"output":["text"]},
|
||||
"cost":{"input":1.25,"output":10},
|
||||
"limit":{"context":400000,"output":128000}}}}
|
||||
}`
|
||||
|
||||
// A relay's rename is matched by every getter, each of its forms, and a
|
||||
// window mark names the window the relay serves it at.
|
||||
func TestRelayRenameReachesEveryGetter(t *testing.T) {
|
||||
writeCatalog(t, renamedCatalog)
|
||||
|
||||
for id, window := range map[string]int{
|
||||
"claude-deepseek-v4.1-flash[1M]": 1_000_000,
|
||||
"claude-deepseek-v4.1-flash[200K]": 200_000,
|
||||
"claude-deepseek-v4.1-flash": 1_000_000,
|
||||
"deepseek-v4.1-flash[1M]": 1_000_000,
|
||||
"relay/claude-DeepSeek-V4.1-Flash": 1_000_000,
|
||||
} {
|
||||
t.Run(id, func(t *testing.T) {
|
||||
if got := strings.Join(EffortsOf(id), ","); got != "low,high,max" {
|
||||
t.Errorf("EffortsOf = %q", got)
|
||||
}
|
||||
if got := ContextOf(id); got != window {
|
||||
t.Errorf("ContextOf = %d, want %d", got, window)
|
||||
}
|
||||
if got := OutputOf(id); got != 65536 {
|
||||
t.Errorf("OutputOf = %d", got)
|
||||
}
|
||||
if !SeesImages(id) || !Thinks(id) || !Knows(id) {
|
||||
t.Errorf("SeesImages %v, Thinks %v, Knows %v", SeesImages(id), Thinks(id), Knows(id))
|
||||
}
|
||||
if p, ok := PricedBy([]string{"deepseek"}, id); !ok || p.Input != 0.3 {
|
||||
t.Errorf("PricedBy = %+v, %v", p, ok)
|
||||
}
|
||||
if e, ok := ListedBy([]string{"deepseek"}, id); !ok || strings.Join(e, ",") != "low,high,max" {
|
||||
t.Errorf("ListedBy = %v, %v", e, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
// the name keeps the mark, so two of a relay's models apart by their
|
||||
// window alone read apart
|
||||
if got := NameOf("claude-deepseek-v4.1-flash[1M]"); got != "DeepSeek V4.1 Flash [1M]" {
|
||||
t.Errorf("NameOf marked = %q", got)
|
||||
}
|
||||
if got := NameOf("claude-deepseek-v4.1-flash"); got != "DeepSeek V4.1 Flash" {
|
||||
t.Errorf("NameOf = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Claude Code's own [1m] on a Claude model is that model at 1M: the mark
|
||||
// comes off, the "claude-" stays (claude-opus-5[1m] is not "opus-5").
|
||||
func TestClaude1MMarkIsTheClaudeModel(t *testing.T) {
|
||||
writeCatalog(t, renamedCatalog)
|
||||
|
||||
if got := strings.Join(EffortsOf("claude-opus-5[1m]"), ","); got != "low,medium,high" {
|
||||
t.Errorf("EffortsOf = %q", got)
|
||||
}
|
||||
if got := ContextOf("claude-opus-5[1m]"); got != 1_000_000 {
|
||||
t.Errorf("ContextOf [1m] = %d", got)
|
||||
}
|
||||
if got := ContextOf("claude-opus-5"); got != 200_000 {
|
||||
t.Errorf("ContextOf = %d", got)
|
||||
}
|
||||
if got := OutputOf("claude-opus-5[1m]"); got != 64000 {
|
||||
t.Errorf("OutputOf = %d", got)
|
||||
}
|
||||
if p, ok := PricedBy([]string{"anthropic"}, "claude-opus-5[1m]"); !ok || p.Input != 5 {
|
||||
t.Errorf("PricedBy = %+v, %v", p, ok)
|
||||
}
|
||||
if got := NameOf("claude-opus-5[1m]"); got != "Claude Opus 5 [1m]" {
|
||||
t.Errorf("NameOf = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// A listed id is always its own model, and a near miss is no model.
|
||||
func TestRenameNeverTakesAModelForAnother(t *testing.T) {
|
||||
writeCatalog(t, renamedCatalog)
|
||||
|
||||
// claude-glm-5 is listed: no levels, no images, its own window, though
|
||||
// glm-5 has them
|
||||
if got := EffortsOf("claude-glm-5"); got != nil {
|
||||
t.Errorf("EffortsOf(claude-glm-5) = %v", got)
|
||||
}
|
||||
if SeesImages("claude-glm-5") || Thinks("claude-glm-5") {
|
||||
t.Error("claude-glm-5 took glm-5's images or reasoning")
|
||||
}
|
||||
if got := ContextOf("claude-glm-5"); got != 64000 {
|
||||
t.Errorf("ContextOf(claude-glm-5) = %d", got)
|
||||
}
|
||||
if got := NameOf("claude-glm-5"); got != "Claude GLM 5" {
|
||||
t.Errorf("NameOf(claude-glm-5) = %q", got)
|
||||
}
|
||||
// the relay's own router is not models.dev's "auto" (the reporter's
|
||||
// counter-example), and ids that only look like a known one match none
|
||||
for _, id := range []string{
|
||||
"claude-auto[1M]", "claude-auto",
|
||||
"claude-deepseek-v4.1-flashy[1M]", "claude-deepseek-v4.1",
|
||||
"deepseek-v4.1-flash[fast]", "deepseek-v4.1-flash[1M]x", "deepseek-v4.1-flash[infm]",
|
||||
"anthropic-deepseek-v4.1-flash", "claude-claude-opus-5x",
|
||||
"[1m]", "claude-[1m]",
|
||||
} {
|
||||
if e, c, i, n := EffortsOf(id), ContextOf(id), SeesImages(id), NameOf(id); e != nil || c != 0 || i || n != "" || Knows(id) {
|
||||
t.Errorf("%q matched: efforts %v, context %d, images %v, name %q", id, e, c, i, n)
|
||||
}
|
||||
if p, ok := PricedBy([]string{"deepseek", "anthropic", "model-oracle-ai"}, id); ok {
|
||||
t.Errorf("%q priced %+v", id, p)
|
||||
}
|
||||
}
|
||||
// other vendors' known models are as they were
|
||||
if got := strings.Join(EffortsOf("gpt-5.5"), ","); got != "low,medium,high,xhigh" {
|
||||
t.Errorf("EffortsOf(gpt-5.5) = %q", got)
|
||||
}
|
||||
if ContextOf("openai/gpt-5.5") != 400000 || OutputOf("gpt-5.5") != 128000 || !SeesImages("gpt-5.5") {
|
||||
t.Error("gpt-5.5 changed")
|
||||
}
|
||||
if got := EffortsOf("auto"); !slices.Contains(got, "xhigh") {
|
||||
t.Errorf("EffortsOf(auto) = %v", got)
|
||||
}
|
||||
if got := strings.Join(EffortsOf("opus-5"), ","); got != "minimal" {
|
||||
t.Errorf("EffortsOf(opus-5) = %q", got)
|
||||
}
|
||||
if got := ContextOf("glm-5"); got != 200000 {
|
||||
t.Errorf("ContextOf(glm-5) = %d", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user