diff --git a/internal/http/providers.go b/internal/http/providers.go index 31dda011..9e803c56 100644 --- a/internal/http/providers.go +++ b/internal/http/providers.go @@ -162,8 +162,10 @@ func (h *ProvidersHandler) registerInMemory(p *store.LLMProviderData) { if cliPath == "" { cliPath = "claude" } - var cliOpts []providers.ClaudeCLIOption - cliOpts = append(cliOpts, providers.WithClaudeCLISecurityHooks("", true)) + cliOpts := []providers.ClaudeCLIOption{ + providers.WithClaudeCLIName(p.Name), + providers.WithClaudeCLISecurityHooks("", true), + } if h.gatewayAddr != "" { mcpData := providers.BuildCLIMCPConfigData(nil, h.gatewayAddr, pkgGatewayToken) mcpData.AgentMCPLookup = h.mcpLookup @@ -197,6 +199,7 @@ func (h *ProvidersHandler) registerInMemory(p *store.LLMProviderData) { h.providerReg.RegisterForTenant(p.TenantID, codex) case store.ProviderAnthropicNative: h.providerReg.RegisterForTenant(p.TenantID, providers.NewAnthropicProvider(p.APIKey, + providers.WithAnthropicName(p.Name), providers.WithAnthropicBaseURL(apiBase))) case store.ProviderDashScope: h.providerReg.RegisterForTenant(p.TenantID, providers.NewDashScopeProvider(p.Name, p.APIKey, apiBase, "")) diff --git a/internal/http/providers_test.go b/internal/http/providers_test.go index 66736d64..ed5252c6 100644 --- a/internal/http/providers_test.go +++ b/internal/http/providers_test.go @@ -57,6 +57,71 @@ func TestProvidersHandlerRegisterInMemoryAppliesCodexPoolDefaults(t *testing.T) } } +// TestProvidersHandlerRegisterInMemoryUsesDBNameForAnthropic guards the onboarding verify flow: +// when an Anthropic provider is created via HTTP with a custom name, the in-memory registry +// must key the provider by that DB name — not the hardcoded "anthropic" default. Otherwise +// handleVerifyProvider's GetForTenant(p.TenantID, p.Name) lookup fails with "provider not registered". +// See commit 7fcf0327 for the matching fix on the startup path (cmd/gateway_providers.go). +func TestProvidersHandlerRegisterInMemoryUsesDBNameForAnthropic(t *testing.T) { + providerReg := providers.NewRegistry(nil) + handler := NewProvidersHandler(newMockProviderStore(), newMockSecretsStore(), providerReg, "") + + provider := &store.LLMProviderData{ + BaseModel: store.BaseModel{ID: uuid.New()}, + TenantID: uuid.New(), + Name: "my-anthropic", + ProviderType: store.ProviderAnthropicNative, + APIKey: "sk-ant-test", + Enabled: true, + } + + handler.registerInMemory(provider) + + got, err := providerReg.GetForTenant(provider.TenantID, provider.Name) + if err != nil { + t.Fatalf("GetForTenant(%q) error = %v, want provider registered under DB name", provider.Name, err) + } + if got.Name() != provider.Name { + t.Fatalf("Name() = %q, want %q", got.Name(), provider.Name) + } + + // Negative: the hardcoded default "anthropic" must NOT be registered when the user chose a different name. + if _, err := providerReg.GetForTenant(provider.TenantID, "anthropic"); err == nil { + t.Fatal("GetForTenant(\"anthropic\") succeeded, want not-found — provider should only live under its DB name") + } +} + +// TestProvidersHandlerRegisterInMemoryUsesDBNameForClaudeCLI mirrors the Anthropic guard for Claude CLI. +// Custom-named CLI providers must be registered under their DB name to be locatable via verify. +func TestProvidersHandlerRegisterInMemoryUsesDBNameForClaudeCLI(t *testing.T) { + providerReg := providers.NewRegistry(nil) + handler := NewProvidersHandler(newMockProviderStore(), newMockSecretsStore(), providerReg, "") + + provider := &store.LLMProviderData{ + BaseModel: store.BaseModel{ID: uuid.New()}, + TenantID: uuid.New(), + Name: "claude-max", + ProviderType: store.ProviderClaudeCLI, + APIBase: "claude", + Enabled: true, + } + + handler.registerInMemory(provider) + + got, err := providerReg.GetForTenant(provider.TenantID, provider.Name) + if err != nil { + t.Fatalf("GetForTenant(%q) error = %v, want provider registered under DB name", provider.Name, err) + } + if got.Name() != provider.Name { + t.Fatalf("Name() = %q, want %q", got.Name(), provider.Name) + } + + // Negative: the hardcoded default "claude-cli" must NOT be registered when the user chose a different name. + if _, err := providerReg.GetForTenant(provider.TenantID, "claude-cli"); err == nil { + t.Fatal("GetForTenant(\"claude-cli\") succeeded, want not-found — provider should only live under its DB name") + } +} + func setupProvidersAdminToken(t *testing.T) string { t.Helper() token := "system-admin-key"