This commit is contained in:
OpenClaw-User 2026-03-14 17:17:37 +08:00
commit e6d306c1ea
4 changed files with 62 additions and 7 deletions

View file

@ -183,7 +183,7 @@ func NewAgentInstance(
if fullModel == raw { if fullModel == raw {
return ensureProtocol(fullModel), true return ensureProtocol(fullModel), true
} }
_, modelID := providers.ExtractProtocol(fullModel) _, modelID := providers.ExtractProtocol(fullModel, cfg.GetAPIBase())
if modelID == raw { if modelID == raw {
return ensureProtocol(fullModel), true return ensureProtocol(fullModel), true
} }

View file

@ -39,16 +39,21 @@ func createCodexAuthProvider() (LLMProvider, error) {
// ExtractProtocol extracts the protocol prefix and model identifier from a model string. // ExtractProtocol extracts the protocol prefix and model identifier from a model string.
// If no prefix is specified, it defaults to "openai". // If no prefix is specified, it defaults to "openai".
// If the model is from OpenRouter, it returns the model as is.
// Examples: // Examples:
// - "openai/gpt-4o" -> ("openai", "gpt-4o") // - "openai/gpt-4o", "https://openrouter.ai/api/v1" -> ("openai", "gpt-4o")
// - "anthropic/claude-sonnet-4.6" -> ("anthropic", "claude-sonnet-4.6") // - "anthropic/claude-sonnet-4.6", "https://api.anthropic.com/v1" -> ("anthropic", "claude-sonnet-4.6")
// - "gpt-4o" -> ("openai", "gpt-4o") // default protocol // - "gpt-4o", "https://api.openai.com/v1" -> ("openai", "gpt-4o") // default protocol
func ExtractProtocol(model string) (protocol, modelID string) { // - "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) model = strings.TrimSpace(model)
protocol, modelID, found := strings.Cut(model, "/") protocol, modelID, found := strings.Cut(model, "/")
if !found { if !found {
return "openai", model return "openai", model
} }
if strings.Contains(strings.ToLower(url), "openrouter.ai") {
return protocol, model
}
return protocol, modelID return protocol, modelID
} }
@ -66,7 +71,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
return nil, "", fmt.Errorf("model is required") return nil, "", fmt.Errorf("model is required")
} }
protocol, modelID := ExtractProtocol(cfg.Model) protocol, modelID := ExtractProtocol(cfg.Model, cfg.APIBase)
switch protocol { switch protocol {
case "openai": case "openai":

View file

@ -19,6 +19,7 @@ func TestExtractProtocol(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
model string model string
apiBase string
wantProtocol string wantProtocol string
wantModelID string wantModelID string
}{ }{
@ -64,11 +65,18 @@ func TestExtractProtocol(t *testing.T) {
wantProtocol: "nvidia", wantProtocol: "nvidia",
wantModelID: "meta/llama-3.1-8b", 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
protocol, modelID := ExtractProtocol(tt.model) protocol, modelID := ExtractProtocol(tt.model, tt.apiBase)
if protocol != tt.wantProtocol { if protocol != tt.wantProtocol {
t.Errorf("ExtractProtocol(%q) protocol = %q, want %q", tt.model, 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) { func TestCreateProviderFromConfig_DefaultAPIBase(t *testing.T) {
tests := []struct { tests := []struct {
name string name string

View file

@ -572,6 +572,27 @@ func TestNormalizeModel_UsesAPIBase(t *testing.T) {
if got := normalizeModel("vivgrid/auto", "https://api.vivgrid.com/v1"); got != "auto" { if got := normalizeModel("vivgrid/auto", "https://api.vivgrid.com/v1"); got != "auto" {
t.Fatalf("normalizeModel(vivgrid auto) = %q, want %q", 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) { func TestProvider_RequestTimeoutDefault(t *testing.T) {