452 lines · 12.4 KB · Go
RawDownload
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:          "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" {					t.Errorf("thinking model %q with effort %q resolved to bare UID %q without effort suffix", baseID, eff, resolved)				}			}		}	}}