diff --git a/Makefile b/Makefile index ba95c72a..ec0d4906 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) LDFLAGS = -s -w -X github.com/nextlevelbuilder/goclaw/cmd.Version=$(VERSION) BINARY = goclaw -.PHONY: build run clean version +.PHONY: build run clean version up down logs build: CGO_ENABLED=0 go build -ldflags="$(LDFLAGS)" -o $(BINARY) . @@ -15,3 +15,14 @@ clean: version: @echo $(VERSION) + +COMPOSE = docker compose -f docker-compose.yml -f docker-compose.managed.yml -f docker-compose.selfservice.yml + +up: + $(COMPOSE) up -d --build + +down: + $(COMPOSE) down + +logs: + $(COMPOSE) logs -f goclaw diff --git a/cmd/gateway_providers.go b/cmd/gateway_providers.go index 3bd1469a..9e2a69a1 100644 --- a/cmd/gateway_providers.go +++ b/cmd/gateway_providers.go @@ -11,7 +11,8 @@ import ( func registerProviders(registry *providers.Registry, cfg *config.Config) { if cfg.Providers.Anthropic.APIKey != "" { - registry.Register(providers.NewAnthropicProvider(cfg.Providers.Anthropic.APIKey)) + registry.Register(providers.NewAnthropicProvider(cfg.Providers.Anthropic.APIKey, + providers.WithAnthropicBaseURL(cfg.Providers.Anthropic.APIBase))) slog.Info("registered provider", "name", "anthropic") } @@ -81,7 +82,8 @@ func registerProvidersFromDB(registry *providers.Registry, provStore store.Provi continue } if p.ProviderType == store.ProviderAnthropicNative { - registry.Register(providers.NewAnthropicProvider(p.APIKey)) + registry.Register(providers.NewAnthropicProvider(p.APIKey, + providers.WithAnthropicBaseURL(p.APIBase))) } else { prov := providers.NewOpenAIProvider(p.Name, p.APIKey, p.APIBase, "") if p.ProviderType == store.ProviderMiniMax { diff --git a/docker-compose.yml b/docker-compose.yml index 6eb19bc7..191cc388 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,6 +33,7 @@ services: - GOCLAW_MODEL=${GOCLAW_MODEL:-} - GOCLAW_OPENROUTER_API_KEY=${GOCLAW_OPENROUTER_API_KEY:-} - GOCLAW_ANTHROPIC_API_KEY=${GOCLAW_ANTHROPIC_API_KEY:-} + - GOCLAW_ANTHROPIC_BASE_URL=${GOCLAW_ANTHROPIC_BASE_URL:-} - GOCLAW_OPENAI_API_KEY=${GOCLAW_OPENAI_API_KEY:-} - GOCLAW_MINIMAX_API_KEY=${GOCLAW_MINIMAX_API_KEY:-} - GOCLAW_GROQ_API_KEY=${GOCLAW_GROQ_API_KEY:-} diff --git a/internal/config/config_load.go b/internal/config/config_load.go index 9cf1a297..09eaa8cc 100644 --- a/internal/config/config_load.go +++ b/internal/config/config_load.go @@ -94,6 +94,7 @@ func (c *Config) applyEnvOverrides() { } } envStr("GOCLAW_ANTHROPIC_API_KEY", &c.Providers.Anthropic.APIKey) + envStr("GOCLAW_ANTHROPIC_BASE_URL", &c.Providers.Anthropic.APIBase) envStr("GOCLAW_OPENAI_API_KEY", &c.Providers.OpenAI.APIKey) envStr("GOCLAW_OPENROUTER_API_KEY", &c.Providers.OpenRouter.APIKey) envStr("GOCLAW_GROQ_API_KEY", &c.Providers.Groq.APIKey) diff --git a/internal/http/providers.go b/internal/http/providers.go index 378d5b17..f316640e 100644 --- a/internal/http/providers.go +++ b/internal/http/providers.go @@ -65,7 +65,8 @@ func (h *ProvidersHandler) registerInMemory(p *store.LLMProviderData) { return } if p.ProviderType == store.ProviderAnthropicNative { - h.providerReg.Register(providers.NewAnthropicProvider(p.APIKey)) + h.providerReg.Register(providers.NewAnthropicProvider(p.APIKey, + providers.WithAnthropicBaseURL(p.APIBase))) } else { prov := providers.NewOpenAIProvider(p.Name, p.APIKey, p.APIBase, "") if p.ProviderType == store.ProviderMiniMax { diff --git a/internal/providers/anthropic.go b/internal/providers/anthropic.go index 6f1f001e..9e417ec9 100644 --- a/internal/providers/anthropic.go +++ b/internal/providers/anthropic.go @@ -21,6 +21,7 @@ const ( // AnthropicProvider implements Provider using the Anthropic Claude API via net/http. type AnthropicProvider struct { apiKey string + baseURL string defaultModel string client *http.Client retryConfig RetryConfig @@ -30,6 +31,7 @@ type AnthropicProvider struct { func NewAnthropicProvider(apiKey string, opts ...AnthropicOption) *AnthropicProvider { p := &AnthropicProvider{ apiKey: apiKey, + baseURL: anthropicAPIBase, defaultModel: defaultClaudeModel, client: &http.Client{Timeout: 120 * time.Second}, retryConfig: DefaultRetryConfig(), @@ -46,6 +48,14 @@ func WithAnthropicModel(model string) AnthropicOption { return func(p *AnthropicProvider) { p.defaultModel = model } } +func WithAnthropicBaseURL(baseURL string) AnthropicOption { + return func(p *AnthropicProvider) { + if baseURL != "" { + p.baseURL = strings.TrimRight(baseURL, "/") + } + } +} + func (p *AnthropicProvider) Name() string { return "anthropic" } func (p *AnthropicProvider) DefaultModel() string { return p.defaultModel } @@ -331,7 +341,7 @@ func (p *AnthropicProvider) doRequest(ctx context.Context, body interface{}) (io return nil, fmt.Errorf("anthropic: marshal request: %w", err) } - httpReq, err := http.NewRequestWithContext(ctx, "POST", anthropicAPIBase+"/messages", bytes.NewReader(data)) + httpReq, err := http.NewRequestWithContext(ctx, "POST", p.baseURL+"/messages", bytes.NewReader(data)) if err != nil { return nil, fmt.Errorf("anthropic: create request: %w", err) }