fix(providers): route Anthropic API key to native ClaudeProvider instead of HTTPProvider
When using `provider: "anthropic"` with a plain `api_key` (no `auth_method`), the factory incorrectly left `providerType` as `providerTypeHTTPCompat`, causing requests to be sent via the OpenAI-compatible HTTP provider. This resulted in 404 errors because the Anthropic API does not have a `/v1/chat/completions` endpoint and requires different headers/body format. Add a new `providerTypeClaudeAPIKey` that routes to `NewClaudeProviderWithBaseURL`, which uses the native Anthropic SDK with the correct `/v1/messages` endpoint and `x-api-key` header. Fix both the explicit provider path and the model-name fallback detection path. Closes #269
This commit is contained in:
parent
3bb4f4ecc6
commit
6c065d2852
4 changed files with 61 additions and 8 deletions
|
|
@ -17,6 +17,7 @@ type providerType int
|
|||
const (
|
||||
providerTypeHTTPCompat providerType = iota
|
||||
providerTypeClaudeAuth
|
||||
providerTypeClaudeAPIKey
|
||||
providerTypeCodexAuth
|
||||
providerTypeCodexCLIToken
|
||||
providerTypeClaudeCLI
|
||||
|
|
@ -91,6 +92,8 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) {
|
|||
if sel.apiBase == "" {
|
||||
sel.apiBase = defaultAnthropicAPIBase
|
||||
}
|
||||
sel.providerType = providerTypeClaudeAPIKey
|
||||
return sel, nil
|
||||
}
|
||||
case "openrouter":
|
||||
if cfg.Providers.OpenRouter.APIKey != "" {
|
||||
|
|
@ -241,6 +244,8 @@ func resolveProviderSelection(cfg *config.Config) (providerSelection, error) {
|
|||
if sel.apiBase == "" {
|
||||
sel.apiBase = defaultAnthropicAPIBase
|
||||
}
|
||||
sel.providerType = providerTypeClaudeAPIKey
|
||||
return sel, nil
|
||||
case (strings.Contains(lowerModel, "gpt") || strings.HasPrefix(model, "openai/")) &&
|
||||
(cfg.Providers.OpenAI.APIKey != "" || cfg.Providers.OpenAI.AuthMethod != ""):
|
||||
sel.enableWebSearch = cfg.Providers.OpenAI.WebSearch
|
||||
|
|
|
|||
|
|
@ -120,7 +120,9 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
|
|||
}
|
||||
return provider, modelID, nil
|
||||
}
|
||||
// Use API key with HTTP API
|
||||
// Use API key with native Claude provider (not OpenAI-compatible HTTP).
|
||||
// The Anthropic API uses /v1/messages (not /v1/chat/completions) and
|
||||
// requires x-api-key header, so HTTPProvider would produce 404 errors.
|
||||
apiBase := cfg.APIBase
|
||||
if apiBase == "" {
|
||||
apiBase = "https://api.anthropic.com/v1"
|
||||
|
|
@ -128,13 +130,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
|
|||
if cfg.APIKey == "" {
|
||||
return nil, "", fmt.Errorf("api_key is required for anthropic protocol (model: %s)", cfg.Model)
|
||||
}
|
||||
return NewHTTPProviderWithMaxTokensFieldAndRequestTimeout(
|
||||
cfg.APIKey,
|
||||
apiBase,
|
||||
cfg.Proxy,
|
||||
cfg.MaxTokensField,
|
||||
cfg.RequestTimeout,
|
||||
), modelID, nil
|
||||
return NewClaudeProviderWithBaseURL(cfg.APIKey, apiBase), modelID, nil
|
||||
|
||||
case "antigravity":
|
||||
return NewAntigravityProvider(), modelID, nil
|
||||
|
|
|
|||
|
|
@ -178,6 +178,12 @@ func TestCreateProviderFromConfig_Anthropic(t *testing.T) {
|
|||
if modelID != "claude-sonnet-4.6" {
|
||||
t.Errorf("modelID = %q, want %q", modelID, "claude-sonnet-4.6")
|
||||
}
|
||||
// Anthropic API key must route to native ClaudeProvider (not HTTPProvider).
|
||||
// The Anthropic API uses /v1/messages and x-api-key header, which are
|
||||
// incompatible with the OpenAI-compatible HTTPProvider.
|
||||
if _, ok := provider.(*ClaudeProvider); !ok {
|
||||
t.Errorf("expected *ClaudeProvider for anthropic API key, got %T", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateProviderFromConfig_Antigravity(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,36 @@ func TestResolveProviderSelection(t *testing.T) {
|
|||
},
|
||||
wantType: providerTypeClaudeAuth,
|
||||
},
|
||||
{
|
||||
name: "explicit anthropic provider with api key routes to claude api key provider",
|
||||
setup: func(cfg *config.Config) {
|
||||
cfg.Agents.Defaults.Provider = "anthropic"
|
||||
cfg.Agents.Defaults.Model = "claude-opus-4-6"
|
||||
cfg.Providers.Anthropic.APIKey = "sk-ant-api03-test"
|
||||
},
|
||||
wantType: providerTypeClaudeAPIKey,
|
||||
wantAPIBase: "https://api.anthropic.com/v1",
|
||||
},
|
||||
{
|
||||
name: "explicit claude provider with api key and custom base routes to claude api key provider",
|
||||
setup: func(cfg *config.Config) {
|
||||
cfg.Agents.Defaults.Provider = "claude"
|
||||
cfg.Agents.Defaults.Model = "claude-opus-4-6"
|
||||
cfg.Providers.Anthropic.APIKey = "sk-ant-api03-test"
|
||||
cfg.Providers.Anthropic.APIBase = "https://proxy.example.com/v1"
|
||||
},
|
||||
wantType: providerTypeClaudeAPIKey,
|
||||
wantAPIBase: "https://proxy.example.com/v1",
|
||||
},
|
||||
{
|
||||
name: "claude model name with api key infers claude api key provider",
|
||||
setup: func(cfg *config.Config) {
|
||||
cfg.Agents.Defaults.Model = "claude-opus-4-6"
|
||||
cfg.Providers.Anthropic.APIKey = "sk-ant-api03-test"
|
||||
},
|
||||
wantType: providerTypeClaudeAPIKey,
|
||||
wantAPIBase: "https://api.anthropic.com/v1",
|
||||
},
|
||||
{
|
||||
name: "openai oauth routes to codex auth provider",
|
||||
setup: func(cfg *config.Config) {
|
||||
|
|
@ -313,6 +343,22 @@ func TestCreateProviderReturnsClaudeProviderForAnthropicOAuth(t *testing.T) {
|
|||
// TODO: Test custom APIBase when createClaudeAuthProvider supports it
|
||||
}
|
||||
|
||||
func TestCreateProviderReturnsClaudeProviderForAnthropicAPIKey(t *testing.T) {
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Agents.Defaults.Provider = "anthropic"
|
||||
cfg.Agents.Defaults.Model = "claude-opus-4-6"
|
||||
cfg.Providers.Anthropic.APIKey = "sk-ant-api03-test"
|
||||
|
||||
provider, err := CreateProvider(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateProvider() error = %v", err)
|
||||
}
|
||||
|
||||
if _, ok := provider.(*ClaudeProvider); !ok {
|
||||
t.Fatalf("provider type = %T, want *ClaudeProvider", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateProviderReturnsCodexProviderForOpenAIOAuth(t *testing.T) {
|
||||
// TODO: This test requires openai protocol to support auth_method: "oauth"
|
||||
// which is not yet implemented in the new factory_provider.go
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue