feat(providers): support custom base URL for Anthropic provider (#16)

Allow overriding the Anthropic API base URL via GOCLAW_ANTHROPIC_BASE_URL
env var, config JSON, or DB provider record. Enables use of Anthropic-
compatible proxies and custom endpoints.

Also adds Makefile shortcuts for docker compose (up/down/logs).
This commit is contained in:
Duc Nguyen authored and GitHub committed 2026-02-28 11:50:00 +07:00
1 parent 7450e7014a
commit 4c67dff24d
6 files changed
+31 -5

No files matched your search

+12 -1
View File
@@ -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
+4 -2
View File
@@ -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 {
+1
View File
@@ -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:-}
+1
View File
@@ -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)
+2 -1
View File
@@ -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 {
+11 -1
View File
@@ -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)
}