From ddfa437f9ce6ac9c6f7bd16eca6a3d415bf338bb Mon Sep 17 00:00:00 2001 From: ZaneTung Date: Wed, 11 Mar 2026 11:08:47 +0800 Subject: [PATCH] fix(providers): address review comments in anthropic-messages provider - fix normalizeBaseURL edge case that incorrectly appends /v1 to URLs already containing /v1 path (e.g., https://api.example.com/v1/proxy) - remove dead code for apiBase empty check as normalizeBaseURL() always provides a default value - update test to use proper constructor instead of direct struct initialization - add detailed comments explaining the URL normalization logic Resolves review comments on PR #1284 --- pkg/providers/anthropic_messages/provider.go | 23 ++++++++++++------- .../anthropic_messages/provider_test.go | 19 ++++----------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/pkg/providers/anthropic_messages/provider.go b/pkg/providers/anthropic_messages/provider.go index 0f362dd39..6ef74a5b1 100644 --- a/pkg/providers/anthropic_messages/provider.go +++ b/pkg/providers/anthropic_messages/provider.go @@ -73,10 +73,6 @@ func (p *Provider) Chat( model string, options map[string]any, ) (*LLMResponse, error) { - if p.apiBase == "" { - return nil, fmt.Errorf("API base not configured") - } - if p.apiKey == "" { return nil, fmt.Errorf("API key not configured") } @@ -323,19 +319,30 @@ func parseResponseBody(body []byte) (*LLMResponse, error) { } // normalizeBaseURL ensures the base URL is properly formatted. +// It removes /v1 suffix if present (to avoid duplication) and always appends /v1. +// This handles edge cases like "https://api.example.com/v1/proxy" correctly. func normalizeBaseURL(apiBase string) string { base := strings.TrimSpace(apiBase) if base == "" { return defaultBaseURL } + // Remove trailing slashes base = strings.TrimRight(base, "/") - // Add /v1 if not present - if !strings.HasSuffix(base, "/v1") { - base = base + "/v1" + + // Remove /v1 suffix if present (will be re-added) + // This prevents duplication for URLs like "https://api.example.com/v1/proxy" + if before, ok := strings.CutSuffix(base, "/v1"); ok { + base = before } - return base + // Ensure we don't have an empty string after cutting + if base == "" { + return defaultBaseURL + } + + // Add /v1 suffix (required by Anthropic Messages API) + return base + "/v1" } // Helper functions for type conversion diff --git a/pkg/providers/anthropic_messages/provider_test.go b/pkg/providers/anthropic_messages/provider_test.go index 36a582412..3fa062360 100644 --- a/pkg/providers/anthropic_messages/provider_test.go +++ b/pkg/providers/anthropic_messages/provider_test.go @@ -413,26 +413,18 @@ func TestGetDefaultModel(t *testing.T) { } } -// Mock HTTP server test for integration testing +// TestProviderChatErrors tests error handling in Chat. +// Note: apiBase check removed as it's dead code - normalizeBaseURL() always provides a default. func TestProviderChatErrors(t *testing.T) { tests := []struct { name string apiKey string - apiBase string messages []Message wantErrMsg string }{ - { - name: "missing API base", - apiKey: "test-key", - apiBase: "", - messages: []Message{{Role: "user", Content: "Test"}}, - wantErrMsg: "API base not configured", - }, { name: "missing API key", apiKey: "", - apiBase: "https://api.example.com", messages: []Message{{Role: "user", Content: "Test"}}, wantErrMsg: "API key not configured", }, @@ -440,11 +432,8 @@ func TestProviderChatErrors(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // Create provider with empty apiBase to trigger error - provider := &Provider{ - apiKey: tt.apiKey, - apiBase: tt.apiBase, - } + // Create provider using constructor to ensure proper initialization + provider := NewProvider(tt.apiKey, "https://api.example.com") _, err := provider.Chat(context.Background(), tt.messages, nil, "test-model", nil) if err == nil {