Skip to content

Commit 30dd7c3

Browse files
fix(config): let a gateway ANTHROPIC_BASE_URL resolve as anthropic-compatible (#497)
Setting ANTHROPIC_BASE_URL to a proxy/gateway failed setup with "anthropic provider anthropic requires official baseURL https://api.anthropic.com". OPENAI_BASE_URL already auto-promotes a non-official env URL to the openai-compatible kind, but Anthropic had no equivalent, so the universal ANTHROPIC_BASE_URL proxy convention was rejected outright. Mirror the OpenAI behaviour: a non-official ANTHROPIC_BASE_URL from the environment promotes the profile to anthropic-compatible. This stays env-only on purpose -- a custom baseURL in a project config.json is still rejected by Resolve, so an untrusted project can't redirect a first-party key to another host (TestResolveRejectsAnthropicOfficialProviderCustomBaseURL still passes). Fixes #479
1 parent e7d88b4 commit 30dd7c3

2 files changed

Lines changed: 37 additions & 0 deletions

File tree

‎internal/config/resolver.go‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,9 +590,17 @@ func applyProviderEnv(cfg *FileConfig, providerKind ProviderKind, env envProfile
590590
if profile.Name == "" {
591591
profile.Name = string(providerKind)
592592
}
593+
// A non-official baseURL from the environment signals a proxy/gateway, so promote
594+
// the first-party kind to its -compatible transport (which accepts a custom URL).
595+
// Env-only by design: a custom baseURL in a project config.json is left as-is and
596+
// rejected by Resolve, so an untrusted project can't redirect a first-party key to
597+
// another host.
593598
if providerKind == ProviderKindOpenAI && baseURL != "" && !isOfficialOpenAIBaseURL(baseURL) {
594599
profile.ProviderKind = ProviderKindOpenAICompatible
595600
}
601+
if providerKind == ProviderKindAnthropic && baseURL != "" && !isOfficialAnthropicBaseURL(baseURL) {
602+
profile.ProviderKind = ProviderKindAnthropicCompat
603+
}
596604
// When the env supplies only credentials (no baseURL) for a provider name that
597605
// already exists, don't force the standard transport kind — a same-named
598606
// openai-compatible/anthropic-compatible proxy or gateway must keep its kind.

‎internal/config/resolver_test.go‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,6 +617,35 @@ func TestResolveUsesOpenAIEnvFallback(t *testing.T) {
617617
}
618618
}
619619

620+
func TestResolveUsesAnthropicEnvBaseURLAsCompatible(t *testing.T) {
621+
// ANTHROPIC_BASE_URL pointing at a proxy/gateway must resolve to an
622+
// anthropic-compatible provider (mirroring OPENAI_BASE_URL), not the "requires
623+
// official baseURL" rejection — issue #479. The env is user-controlled, so a
624+
// custom URL there is a deliberate gateway choice, unlike a project config.json.
625+
resolved, err := Resolve(ResolveOptions{
626+
Env: map[string]string{
627+
"ANTHROPIC_API_KEY": "sk-ant-env",
628+
"ANTHROPIC_BASE_URL": "https://gateway.example/anthropic",
629+
"ANTHROPIC_MODEL": "claude-custom",
630+
},
631+
})
632+
if err != nil {
633+
t.Fatalf("Resolve() error = %v", err)
634+
}
635+
if resolved.ActiveProvider != "anthropic" {
636+
t.Fatalf("ActiveProvider = %q, want anthropic", resolved.ActiveProvider)
637+
}
638+
if resolved.Provider.ProviderKind != ProviderKindAnthropicCompat {
639+
t.Fatalf("ProviderKind = %q, want anthropic-compatible", resolved.Provider.ProviderKind)
640+
}
641+
if resolved.Provider.BaseURL != "https://gateway.example/anthropic" {
642+
t.Fatalf("BaseURL = %q, want gateway URL", resolved.Provider.BaseURL)
643+
}
644+
if resolved.Provider.APIKey != "sk-ant-env" || resolved.Provider.Model != "claude-custom" {
645+
t.Fatalf("Provider = %#v, want env credentials/model", resolved.Provider)
646+
}
647+
}
648+
620649
func TestResolveUsesOpenAIAPIKeyOnlyWithDefaultModel(t *testing.T) {
621650
resolved, err := Resolve(ResolveOptions{
622651
Env: map[string]string{

0 commit comments

Comments
 (0)