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 {