Files
CLIProxyAPI/internal/runtime/executor/helps/devin_models_test.go
2026-09-16 16:49:49 +08:00

477 lines
13 KiB
Go

package helps
import (
"strings"
"testing"
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
)
func TestResolveDevinChatModelUID(t *testing.T) {
tests := []struct {
name string
rawModel string
thinkingLevel string
budgetTokens int
want string
}{
{
name: "direct full UID unchanged",
rawModel: "claude-fable-5-1-max",
want: "claude-fable-5-1-max",
},
{
name: "direct swe-2-high unchanged",
rawModel: "swe-2-high",
want: "swe-2-high",
},
{
name: "swe-2 default to high when no effort",
rawModel: "swe-2",
thinkingLevel: "",
want: "swe-2-high",
},
{
name: "swe-2 clamp minimal to medium",
rawModel: "swe-2",
thinkingLevel: "minimal",
want: "swe-2-medium",
},
{
name: "swe-2 clamp low to medium",
rawModel: "swe-2",
thinkingLevel: "low",
want: "swe-2-medium",
},
{
name: "swe-2 with xhigh maps to max",
rawModel: "swe-2",
thinkingLevel: "xhigh",
want: "swe-2-max",
},
{
name: "swe-2 request max",
rawModel: "swe-2",
thinkingLevel: "max",
want: "swe-2-max",
},
{
name: "swe-2 suffix overrides body thinkingLevel",
rawModel: "swe-2(max)",
thinkingLevel: "medium",
want: "swe-2-max",
},
{
name: "fable-5-1 budget tokens maps to max",
rawModel: "claude-fable-5-1",
budgetTokens: 64000,
want: "claude-fable-5-1-max",
},
{
name: "fable-5-1 budget tokens maps to low",
rawModel: "claude-fable-5-1",
budgetTokens: 2048,
want: "claude-fable-5-1-low",
},
{
name: "fable-5-1 with xhigh",
rawModel: "claude-fable-5-1",
thinkingLevel: "xhigh",
want: "claude-fable-5-1-xhigh",
},
{
name: "astra with suffix in parenthesis",
rawModel: "gpt-6-astra(high)",
thinkingLevel: "",
want: "gpt-6-astra-high",
},
{
name: "glm-5-3 clamp medium to high",
rawModel: "glm-5-3",
thinkingLevel: "medium",
want: "glm-5-3-high",
},
{
name: "devin prefix glm-5-3-flash default high",
rawModel: "devin/glm-5-3-flash",
thinkingLevel: "",
want: "glm-5-3-flash-high",
},
{
name: "devin prefix glm-5-3-flash with low suffix",
rawModel: "devin/glm-5-3-flash:low",
thinkingLevel: "",
want: "glm-5-3-flash-low",
},
{
name: "devin prefix glm-5-3-flash with max suffix",
rawModel: "devin/glm-5-3-flash(max)",
thinkingLevel: "",
want: "glm-5-3-flash-max",
},
{
name: "devin prefix gpt-5-6-sol with none suffix",
rawModel: "devin/gpt-5-6-sol:none",
thinkingLevel: "",
want: "gpt-5-6-sol-none",
},
{
name: "devin prefix gpt-5-6-sol with body none effort",
rawModel: "devin/gpt-5-6-sol",
thinkingLevel: "none",
want: "gpt-5-6-sol-none",
},
{
name: "devin prefix gpt-5-6-terra with none suffix",
rawModel: "devin/gpt-5-6-terra:none",
thinkingLevel: "",
want: "gpt-5-6-terra-none",
},
{
name: "devin prefix nemotron-3-ultra with none suffix",
rawModel: "devin/nemotron-3-ultra:none",
thinkingLevel: "",
want: "nemotron-3-ultra-none",
},
{
name: "glm-5-2 default maps to glm-5-2",
rawModel: "devin/glm-5-2",
thinkingLevel: "high",
want: "glm-5-2",
},
{
name: "glm-5-2 with none effort maps to glm-5-2-none",
rawModel: "devin/glm-5-2:none",
thinkingLevel: "",
want: "glm-5-2-none",
},
{
name: "glm-5-2 suffix max maps to max",
rawModel: "devin/glm-5-2(max)",
thinkingLevel: "",
want: "glm-5-2-max",
},
{
name: "devin prefix lowercase swe-2",
rawModel: "devin/swe-2",
thinkingLevel: "",
want: "swe-2-high",
},
{
name: "Devin prefix capitalized swe-2 with suffix",
rawModel: "Devin/swe-2(max)",
thinkingLevel: "",
want: "swe-2-max",
},
{
name: "devin prefix claude-fable-5-1",
rawModel: "devin/claude-fable-5-1",
thinkingLevel: "",
want: "claude-fable-5-1-medium",
},
{
name: "Devin prefix gpt-6-astra with suffix",
rawModel: "Devin/gpt-6-astra(high)",
thinkingLevel: "",
want: "gpt-6-astra-high",
},
{
name: "devin prefix direct effort UID",
rawModel: "devin/swe-2-high",
thinkingLevel: "",
want: "swe-2-high",
},
{
name: "devin prefix gemini-3-8-flash default high",
rawModel: "devin/gemini-3-8-flash",
thinkingLevel: "",
want: "gemini-3-8-flash-high",
},
{
name: "devin prefix gemini-3.8-flash with low suffix",
rawModel: "devin/gemini-3.8-flash(low)",
thinkingLevel: "",
want: "gemini-3-8-flash-low",
},
{
name: "devin prefix grok-4-6 default high",
rawModel: "devin/grok-4-6",
thinkingLevel: "",
want: "grok-4-6-high",
},
{
name: "devin prefix grok-4.6 with xhigh suffix",
rawModel: "devin/grok-4.6:xhigh",
thinkingLevel: "",
want: "grok-4-6-xhigh",
},
{
name: "devin prefix deepseek-v4-flash default high",
rawModel: "devin/deepseek-v4-flash",
thinkingLevel: "",
want: "deepseek-v4-flash-high",
},
{
name: "devin prefix deepseek-v4.1-flash with max suffix",
rawModel: "devin/deepseek-v4.1-flash(max)",
thinkingLevel: "",
want: "deepseek-v4-1-flash-max",
},
{
name: "swe-1-7 default maps to swe-1-7",
rawModel: "devin/swe-1-7",
thinkingLevel: "",
want: "swe-1-7",
},
{
name: "swe-1-7 with medium maps to swe-1-7-medium",
rawModel: "devin/swe-1-7:medium",
thinkingLevel: "",
want: "swe-1-7-medium",
},
{
name: "claude-haiku-4-5 maps to MODEL_PRIVATE_11",
rawModel: "devin/claude-haiku-4-5",
thinkingLevel: "",
want: "MODEL_PRIVATE_11",
},
{
name: "claude-sonnet-4-5 non-thinking maps to MODEL_PRIVATE_2",
rawModel: "devin/claude-sonnet-4-5",
thinkingLevel: "",
want: "MODEL_PRIVATE_2",
},
{
name: "claude-sonnet-4-5 thinking maps to MODEL_PRIVATE_3",
rawModel: "devin/claude-sonnet-4-5:high",
thinkingLevel: "",
want: "MODEL_PRIVATE_3",
},
{
name: "gpt-4-1 maps to MODEL_CHAT_GPT_4_1_2025_04_14",
rawModel: "devin/gpt-4-1",
thinkingLevel: "",
want: "MODEL_CHAT_GPT_4_1_2025_04_14",
},
{
name: "gemini-3-flash alias maps to gemini-3-8-flash-high",
rawModel: "devin/gemini-3-flash",
thinkingLevel: "",
want: "gemini-3-8-flash-high",
},
{
name: "gpt-5-6-luna default maps to low",
rawModel: "devin/gpt-5-6-luna",
thinkingLevel: "",
want: "gpt-5-6-luna-low",
},
{
name: "gpt-5-6-luna with high suffix",
rawModel: "devin/gpt-5-6-luna(high)",
thinkingLevel: "",
want: "gpt-5-6-luna-high",
},
{
name: "gpt-5-6-luna with none suffix",
rawModel: "devin/gpt-5-6-luna:none",
thinkingLevel: "",
want: "gpt-5-6-luna-none",
},
{
name: "gpt-5-6-sol default maps to low",
rawModel: "devin/gpt-5-6-sol",
thinkingLevel: "",
want: "gpt-5-6-sol-low",
},
{
name: "gpt-5-6-terra default maps to low",
rawModel: "devin/gpt-5-6-terra",
thinkingLevel: "",
want: "gpt-5-6-terra-low",
},
{
name: "gpt-5-5 default maps to low",
rawModel: "devin/gpt-5-5",
thinkingLevel: "",
want: "gpt-5-5-low",
},
{
name: "gpt-5-4 default maps to low",
rawModel: "devin/gpt-5-4",
thinkingLevel: "",
want: "gpt-5-4-low",
},
{
name: "gpt-5-4-mini default maps to medium",
rawModel: "devin/gpt-5-4-mini",
thinkingLevel: "",
want: "gpt-5-4-mini-medium",
},
{
name: "gpt-5-3-codex default maps to medium",
rawModel: "devin/gpt-5-3-codex",
thinkingLevel: "",
want: "gpt-5-3-codex-medium",
},
{
name: "claude-opus-5 default maps to medium",
rawModel: "devin/claude-opus-5",
thinkingLevel: "",
want: "claude-opus-5-medium",
},
{
name: "claude-opus-4-8 default maps to medium",
rawModel: "devin/claude-opus-4-8",
thinkingLevel: "",
want: "claude-opus-4-8-medium",
},
{
name: "claude-opus-4-7 default maps to medium",
rawModel: "devin/claude-opus-4-7",
thinkingLevel: "",
want: "claude-opus-4-7-medium",
},
{
name: "claude-sonnet-5 default maps to medium",
rawModel: "devin/claude-sonnet-5",
thinkingLevel: "",
want: "claude-sonnet-5-medium",
},
{
name: "gemini-3-7-flash default maps to high",
rawModel: "devin/gemini-3-7-flash",
thinkingLevel: "",
want: "gemini-3-7-flash-high",
},
{
name: "gemini-3-6-flash default maps to high",
rawModel: "devin/gemini-3-6-flash",
thinkingLevel: "",
want: "gemini-3-6-flash-high",
},
{
name: "gemini-3-5-flash default maps to high",
rawModel: "devin/gemini-3-5-flash",
thinkingLevel: "",
want: "gemini-3-5-flash-high",
},
{
name: "deepseek-v4-pro default maps to high",
rawModel: "devin/deepseek-v4-pro",
thinkingLevel: "",
want: "deepseek-v4-pro-high",
},
{
name: "grok-4-5 default maps to high",
rawModel: "devin/grok-4-5",
thinkingLevel: "",
want: "grok-4-5-high",
},
{
name: "kimi-k3 default maps to high",
rawModel: "devin/kimi-k3",
thinkingLevel: "",
want: "kimi-k3-high",
},
{
name: "nemotron-3-ultra default maps to high",
rawModel: "devin/nemotron-3-ultra",
thinkingLevel: "",
want: "nemotron-3-ultra-high",
},
{
name: "swe-1-6 default maps to swe-1-6",
rawModel: "devin/swe-1-6",
thinkingLevel: "",
want: "swe-1-6",
},
{
name: "swe-1-6 with fast maps to swe-1-6-fast",
rawModel: "devin/swe-1-6:fast",
thinkingLevel: "",
want: "swe-1-6-fast",
},
{
name: "swe-1-6-slow default maps to swe-1-6-slow",
rawModel: "devin/swe-1-6-slow",
thinkingLevel: "",
want: "swe-1-6-slow",
},
{
name: "swe-1-6-slow without prefix maps to swe-1-6-slow",
rawModel: "swe-1-6-slow",
thinkingLevel: "",
want: "swe-1-6-slow",
},
{
name: "swe-1-6-slow with low maps to swe-1-6-slow",
rawModel: "devin/swe-1-6-slow:low",
thinkingLevel: "",
want: "swe-1-6-slow",
},
{
name: "swe-1-6-slow with high maps to swe-1-6-slow",
rawModel: "devin/swe-1-6-slow:high",
thinkingLevel: "",
want: "swe-1-6-slow",
},
{
name: "kimi-k2-6 default maps to kimi-k2-6",
rawModel: "devin/kimi-k2-6",
thinkingLevel: "",
want: "kimi-k2-6",
},
{
name: "kimi-k2-7 default maps to kimi-k2-7",
rawModel: "devin/kimi-k2-7",
thinkingLevel: "",
want: "kimi-k2-7",
},
{
name: "claude-opus-4-6 default maps to claude-opus-4-6",
rawModel: "devin/claude-opus-4-6",
thinkingLevel: "",
want: "claude-opus-4-6",
},
{
name: "claude-sonnet-4-6 default maps to claude-sonnet-4-6",
rawModel: "devin/claude-sonnet-4-6",
thinkingLevel: "",
want: "claude-sonnet-4-6",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ResolveDevinChatModelUID(tt.rawModel, tt.thinkingLevel, tt.budgetTokens)
if got != tt.want {
t.Errorf("ResolveDevinChatModelUID(%q, %q, %d) = %q, want %q", tt.rawModel, tt.thinkingLevel, tt.budgetTokens, got, tt.want)
}
})
}
}
func TestResolveDevinChatModelUID_AllCatalogModels(t *testing.T) {
models := registry.GetDevinModels()
if len(models) == 0 {
t.Fatal("GetDevinModels() returned empty list")
}
testEfforts := []string{"", "none", "low", "medium", "high", "xhigh", "max"}
for _, m := range models {
baseID := strings.TrimPrefix(m.ID, "devin/")
for _, eff := range testEfforts {
resolved := ResolveDevinChatModelUID("devin/"+baseID, eff, 0)
if resolved == "" {
t.Errorf("model %q with effort %q resolved to empty string", baseID, eff)
}
// If model defines thinking levels, resolved UID must have an effort suffix
if m.Thinking != nil && len(m.Thinking.Levels) > 0 {
if !HasDevinEffortSuffix(resolved) && baseID != "swe-1-7" && baseID != "glm-5-2" && baseID != "swe-1-6-slow" {
t.Errorf("thinking model %q with effort %q resolved to bare UID %q without effort suffix", baseID, eff, resolved)
}
}
}
}
}