From 17efd415bb9fe0181a6fa4aef96f4e45afd5ca47 Mon Sep 17 00:00:00 2001 From: confusionhill Date: Tue, 10 Mar 2026 16:07:50 +0700 Subject: [PATCH] Fix prevent openrouter provider models from getting cut --- pkg/agent/instance.go | 2 +- pkg/providers/factory_provider.go | 15 ++++++---- pkg/providers/factory_provider_test.go | 31 +++++++++++++++++++- pkg/providers/openai_compat/provider_test.go | 21 +++++++++++++ 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/pkg/agent/instance.go b/pkg/agent/instance.go index 0c7baa1ee..845538b9a 100644 --- a/pkg/agent/instance.go +++ b/pkg/agent/instance.go @@ -183,7 +183,7 @@ func NewAgentInstance( if fullModel == raw { return ensureProtocol(fullModel), true } - _, modelID := providers.ExtractProtocol(fullModel) + _, modelID := providers.ExtractProtocol(fullModel, cfg.GetAPIBase()) if modelID == raw { return ensureProtocol(fullModel), true } diff --git a/pkg/providers/factory_provider.go b/pkg/providers/factory_provider.go index a798154cb..d20298da0 100644 --- a/pkg/providers/factory_provider.go +++ b/pkg/providers/factory_provider.go @@ -38,16 +38,21 @@ func createCodexAuthProvider() (LLMProvider, error) { // ExtractProtocol extracts the protocol prefix and model identifier from a model string. // If no prefix is specified, it defaults to "openai". +// If the model is from OpenRouter, it returns the model as is. // Examples: -// - "openai/gpt-4o" -> ("openai", "gpt-4o") -// - "anthropic/claude-sonnet-4.6" -> ("anthropic", "claude-sonnet-4.6") -// - "gpt-4o" -> ("openai", "gpt-4o") // default protocol -func ExtractProtocol(model string) (protocol, modelID string) { +// - "openai/gpt-4o", "https://openrouter.ai/api/v1" -> ("openai", "gpt-4o") +// - "anthropic/claude-sonnet-4.6", "https://api.anthropic.com/v1" -> ("anthropic", "claude-sonnet-4.6") +// - "gpt-4o", "https://api.openai.com/v1" -> ("openai", "gpt-4o") // default protocol +// - "openrouter/gpt-4o", "https://openrouter.ai/api/v1" -> ("openrouter", "openrouter/gpt-4o") +func ExtractProtocol(model string, url string) (protocol, modelID string) { model = strings.TrimSpace(model) protocol, modelID, found := strings.Cut(model, "/") if !found { return "openai", model } + if strings.Contains(strings.ToLower(url), "openrouter.ai") { + return protocol, model + } return protocol, modelID } @@ -64,7 +69,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err return nil, "", fmt.Errorf("model is required") } - protocol, modelID := ExtractProtocol(cfg.Model) + protocol, modelID := ExtractProtocol(cfg.Model, cfg.APIBase) switch protocol { case "openai": diff --git a/pkg/providers/factory_provider_test.go b/pkg/providers/factory_provider_test.go index 17bc55d25..0da82f0b5 100644 --- a/pkg/providers/factory_provider_test.go +++ b/pkg/providers/factory_provider_test.go @@ -19,6 +19,7 @@ func TestExtractProtocol(t *testing.T) { tests := []struct { name string model string + apiBase string wantProtocol string wantModelID string }{ @@ -64,11 +65,18 @@ func TestExtractProtocol(t *testing.T) { wantProtocol: "nvidia", wantModelID: "meta/llama-3.1-8b", }, + { + name: "open router model", + model: "openrouter/llama-3.1-8b", + wantProtocol: "openrouter", + wantModelID: "openrouter/llama-3.1-8b", + apiBase: "https://openrouter.ai/api/v1", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - protocol, modelID := ExtractProtocol(tt.model) + protocol, modelID := ExtractProtocol(tt.model, tt.apiBase) if protocol != tt.wantProtocol { t.Errorf("ExtractProtocol(%q) protocol = %q, want %q", tt.model, protocol, tt.wantProtocol) } @@ -99,6 +107,27 @@ func TestCreateProviderFromConfig_OpenAI(t *testing.T) { } } +func TestCreateProviderFromConfig_OpenRouter(t *testing.T) { + target := "openai/gpt-4o" + cfg := &config.ModelConfig{ + ModelName: "test-openai", + Model: target, + APIKey: "test-key", + APIBase: "https://openrouter.ai/api/v1", + } + + provider, modelID, err := CreateProviderFromConfig(cfg) + if err != nil { + t.Fatalf("CreateProviderFromConfig() error = %v", err) + } + if provider == nil { + t.Fatal("CreateProviderFromConfig() returned nil provider") + } + if modelID != target { + t.Errorf("modelID = %q, want %q", modelID, target) + } +} + func TestCreateProviderFromConfig_DefaultAPIBase(t *testing.T) { tests := []struct { name string diff --git a/pkg/providers/openai_compat/provider_test.go b/pkg/providers/openai_compat/provider_test.go index 9a3a7acc5..037ac3158 100644 --- a/pkg/providers/openai_compat/provider_test.go +++ b/pkg/providers/openai_compat/provider_test.go @@ -523,6 +523,27 @@ func TestNormalizeModel_UsesAPIBase(t *testing.T) { if got := normalizeModel("vivgrid/auto", "https://api.vivgrid.com/v1"); got != "auto" { t.Fatalf("normalizeModel(vivgrid auto) = %q, want %q", got, "auto") } + + // Bug fix: qwen/qwen3-235b-a22b-2507 sent to openrouter.ai must be preserved in full + if got := normalizeModel("qwen/qwen3-235b-a22b-2507", "https://openrouter.ai/api/v1"); got != "qwen/qwen3-235b-a22b-2507" { + t.Fatalf("normalizeModel(qwen@openrouter) = %q, want %q", got, "qwen/qwen3-235b-a22b-2507") + } + // Same prefix, different (native) provider: strip the prefix + if got := normalizeModel("qwen/qwen3-235b-a22b-2507", "https://dashscope.aliyuncs.com/compatible-mode/v1"); got != "qwen3-235b-a22b-2507" { + t.Fatalf("normalizeModel(qwen@dashscope) = %q, want %q", got, "qwen3-235b-a22b-2507") + } + // openai/ prefix should be stripped for non-openrouter providers + if got := normalizeModel("openai/gpt-4o", "https://api.openai.com/v1"); got != "gpt-4o" { + t.Fatalf("normalizeModel(openai) = %q, want %q", got, "gpt-4o") + } + // anthropic/ prefix should be stripped for non-openrouter providers + if got := normalizeModel("anthropic/claude-sonnet-4.6", "https://api.anthropic.com/v1"); got != "claude-sonnet-4.6" { + t.Fatalf("normalizeModel(anthropic) = %q, want %q", got, "claude-sonnet-4.6") + } + // Models with unknown prefix are left unchanged + if got := normalizeModel("meta-llama/llama-3.1-8b", "https://openrouter.ai/api/v1"); got != "meta-llama/llama-3.1-8b" { + t.Fatalf("normalizeModel(meta-llama@openrouter) = %q, want %q", got, "meta-llama/llama-3.1-8b") + } } func TestProvider_RequestTimeoutDefault(t *testing.T) {