From 86eb07752dcf158af3fb5961eef3a43bd3612e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=BE=99=200668001470?= Date: Mon, 16 Mar 2026 11:47:43 +0800 Subject: [PATCH] fix(provider): normalize anthropic model ids for API key path --- pkg/providers/openai_compat/provider.go | 14 ++++-- pkg/providers/openai_compat/provider_test.go | 51 ++++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/pkg/providers/openai_compat/provider.go b/pkg/providers/openai_compat/provider.go index 0e8db7409..99d653353 100644 --- a/pkg/providers/openai_compat/provider.go +++ b/pkg/providers/openai_compat/provider.go @@ -428,12 +428,20 @@ func serializeMessages(messages []Message) []any { } func normalizeModel(model, apiBase string) string { - before, after, ok := strings.Cut(model, "/") - if !ok { + lowerBase := strings.ToLower(apiBase) + if strings.Contains(lowerBase, "openrouter.ai") { return model } - if strings.Contains(strings.ToLower(apiBase), "openrouter.ai") { + if strings.Contains(lowerBase, "api.anthropic.com") { + if _, after, ok := strings.Cut(model, "/"); ok { + model = after + } + return strings.ReplaceAll(model, ".", "-") + } + + before, after, ok := strings.Cut(model, "/") + if !ok { return model } diff --git a/pkg/providers/openai_compat/provider_test.go b/pkg/providers/openai_compat/provider_test.go index 9a3a7acc5..b8f663855 100644 --- a/pkg/providers/openai_compat/provider_test.go +++ b/pkg/providers/openai_compat/provider_test.go @@ -514,6 +514,12 @@ func TestNormalizeModel_UsesAPIBase(t *testing.T) { if got := normalizeModel("deepseek/deepseek-chat", "https://api.deepseek.com/v1"); got != "deepseek-chat" { t.Fatalf("normalizeModel(deepseek) = %q, want %q", got, "deepseek-chat") } + if got := normalizeModel("claude-sonnet-4.6", "https://api.anthropic.com/v1"); got != "claude-sonnet-4-6" { + t.Fatalf("normalizeModel(anthropic plain) = %q, want %q", got, "claude-sonnet-4-6") + } + if got := normalizeModel("anthropic/claude-sonnet-4.6", "https://api.anthropic.com/v1"); got != "claude-sonnet-4-6" { + t.Fatalf("normalizeModel(anthropic prefixed) = %q, want %q", got, "claude-sonnet-4-6") + } if got := normalizeModel("openrouter/auto", "https://openrouter.ai/api/v1"); got != "openrouter/auto" { t.Fatalf("normalizeModel(openrouter) = %q, want %q", got, "openrouter/auto") } @@ -525,6 +531,51 @@ func TestNormalizeModel_UsesAPIBase(t *testing.T) { } } +func TestProviderChat_NormalizesAnthropicModelForAnthropicAPIBase(t *testing.T) { + var requestBody map[string]any + + p := NewProvider("key", "https://api.anthropic.com/v1", "") + p.httpClient.Transport = roundTripperFunc(func(r *http.Request) (*http.Response, error) { + if r.URL.String() != "https://api.anthropic.com/v1/chat/completions" { + t.Fatalf("request URL = %q, want %q", r.URL.String(), "https://api.anthropic.com/v1/chat/completions") + } + if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil { + t.Fatalf("decode request body: %v", err) + } + resp := map[string]any{ + "choices": []map[string]any{ + { + "message": map[string]any{"content": "ok"}, + "finish_reason": "stop", + }, + }, + } + data, err := json.Marshal(resp) + if err != nil { + t.Fatalf("marshal response: %v", err) + } + return &http.Response{ + StatusCode: http.StatusOK, + Header: http.Header{"Content-Type": []string{"application/json"}}, + Body: io.NopCloser(bytes.NewReader(data)), + }, nil + }) + + _, err := p.Chat( + t.Context(), + []Message{{Role: "user", Content: "hi"}}, + nil, + "claude-sonnet-4.6", + nil, + ) + if err != nil { + t.Fatalf("Chat() error = %v", err) + } + if got := requestBody["model"]; got != "claude-sonnet-4-6" { + t.Fatalf("request model = %v, want %q", got, "claude-sonnet-4-6") + } +} + func TestProvider_RequestTimeoutDefault(t *testing.T) { p := NewProviderWithMaxTokensFieldAndTimeout("key", "https://example.com/v1", "", "", 0) if p.httpClient.Timeout != defaultRequestTimeout {