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 {
return ensureProtocol(fullModel), true
}
_, modelID := providers.ExtractProtocol(fullModel)
_, modelID := providers.ExtractProtocol(fullModel, cfg.GetAPIBase())
if modelID == raw {
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.
// 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
}
@ -66,7 +71,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":

View file

@ -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

View file

@ -572,6 +572,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) {