diff --git a/internal/ai/supervisor.go b/internal/ai/supervisor.go index 9c15f6e5..314b6ee7 100644 --- a/internal/ai/supervisor.go +++ b/internal/ai/supervisor.go @@ -6,7 +6,6 @@ import ( "os" "github.com/anthropics/anthropic-sdk-go" - "github.com/anthropics/anthropic-sdk-go/option" "github.com/steveyegge/vc/internal/storage" "github.com/steveyegge/vc/internal/types" "golang.org/x/sync/semaphore" @@ -122,7 +121,7 @@ func NewSupervisor(cfg *Config) (*Supervisor, error) { retry = DefaultRetryConfig() } - client := anthropic.NewClient(option.WithAPIKey(apiKey)) + client := NewAnthropicClient(apiKey) // Initialize circuit breaker if enabled var circuitBreaker *CircuitBreaker diff --git a/internal/ai/utils.go b/internal/ai/utils.go index d03b83c0..efe8115a 100644 --- a/internal/ai/utils.go +++ b/internal/ai/utils.go @@ -9,6 +9,7 @@ import ( "unicode/utf8" "github.com/anthropics/anthropic-sdk-go" + "github.com/anthropics/anthropic-sdk-go/option" "github.com/steveyegge/vc/internal/types" ) @@ -329,3 +330,20 @@ func safeTruncateString(s string, maxLen int) string { // Return empty string rather than corrupted data return "" } + +// NewAnthropicClient creates an Anthropic client with support for ANTHROPIC_BASE_URL +// It uses the ANTHROPIC_API_KEY environment variable for authentication +// and optionally ANTHROPIC_BASE_URL environment variable for custom endpoint +func NewAnthropicClient(apiKey string) anthropic.Client { + // Prepare client options + opts := []option.RequestOption{ + option.WithAPIKey(apiKey), + } + + // Add base URL if provided + if baseURL := os.Getenv("ANTHROPIC_BASE_URL"); baseURL != "" { + opts = append(opts, option.WithBaseURL(baseURL)) + } + + return anthropic.NewClient(opts...) +} diff --git a/internal/discovery/sdk/ai.go b/internal/discovery/sdk/ai.go index b6395f1b..0a736311 100644 --- a/internal/discovery/sdk/ai.go +++ b/internal/discovery/sdk/ai.go @@ -6,7 +6,7 @@ import ( "os" "github.com/anthropics/anthropic-sdk-go" - "github.com/anthropics/anthropic-sdk-go/option" + "github.com/steveyegge/vc/internal/ai" ) // AIRequest represents a request to the AI supervisor. @@ -79,7 +79,7 @@ func CallAI(ctx context.Context, req AIRequest) (*AIResponse, error) { } // Create client - client := anthropic.NewClient(option.WithAPIKey(apiKey)) + client := ai.NewAnthropicClient(apiKey) // Build messages messages := []anthropic.MessageParam{ diff --git a/internal/executor/agent.go b/internal/executor/agent.go index f0bfbf0f..e1f47157 100644 --- a/internal/executor/agent.go +++ b/internal/executor/agent.go @@ -14,8 +14,8 @@ import ( "time" "github.com/anthropics/anthropic-sdk-go" - "github.com/anthropics/anthropic-sdk-go/option" "github.com/google/uuid" + "github.com/steveyegge/vc/internal/ai" "github.com/steveyegge/vc/internal/events" "github.com/steveyegge/vc/internal/sandbox" "github.com/steveyegge/vc/internal/storage" @@ -1180,7 +1180,7 @@ Only say stuck=true if you're confident (>0.8) this is a loop.`, summary) checkCtx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() - client := anthropic.NewClient(option.WithAPIKey(apiKey)) + client := ai.NewAnthropicClient(apiKey) resp, err := client.Messages.New(checkCtx, anthropic.MessageNewParams{ Model: anthropic.Model("claude-3-5-haiku-20241022"), // Haiku for speed/cost diff --git a/internal/executor/executor.go b/internal/executor/executor.go index e0b987d7..eb06feb5 100644 --- a/internal/executor/executor.go +++ b/internal/executor/executor.go @@ -11,8 +11,6 @@ import ( "sync" "time" - "github.com/anthropics/anthropic-sdk-go" - "github.com/anthropics/anthropic-sdk-go/option" "github.com/google/uuid" "github.com/steveyegge/vc/internal/ai" "github.com/steveyegge/vc/internal/config" @@ -658,7 +656,7 @@ func New(cfg *Config) (*Executor, error) { apiKey := os.Getenv("ANTHROPIC_API_KEY") if apiKey != "" { // Create Anthropic client for message generation (vc-35: using Haiku for cost efficiency) - client := anthropic.NewClient(option.WithAPIKey(apiKey)) + client := ai.NewAnthropicClient(apiKey) e.messageGen = git.NewMessageGenerator(&client, ai.GetSimpleTaskModel()) } else { fmt.Fprintf(os.Stderr, "Warning: ANTHROPIC_API_KEY not set (auto-commit message generation disabled)\n") diff --git a/internal/health/model_cost_test.go b/internal/health/model_cost_test.go index 9bbda5c5..6500e4de 100644 --- a/internal/health/model_cost_test.go +++ b/internal/health/model_cost_test.go @@ -10,7 +10,6 @@ import ( "time" "github.com/anthropics/anthropic-sdk-go" - "github.com/anthropics/anthropic-sdk-go/option" "github.com/steveyegge/vc/internal/ai" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -79,7 +78,7 @@ func TestModelCost_CruftDetector(t *testing.T) { for _, model := range []string{ai.ModelSonnet, ai.ModelHaiku} { t.Run(model, func(t *testing.T) { // Create cost-tracking supervisor - client := anthropic.NewClient(option.WithAPIKey(apiKey)) + client := ai.NewAnthropicClient(apiKey) supervisor := &costTrackingSupervisor{ client: &client, model: model, @@ -153,7 +152,7 @@ func TestModelCost_FileSizeMonitor(t *testing.T) { for _, model := range []string{ai.ModelSonnet, ai.ModelHaiku} { t.Run(model, func(t *testing.T) { - client := anthropic.NewClient(option.WithAPIKey(apiKey)) + client := ai.NewAnthropicClient(apiKey) supervisor := &costTrackingSupervisor{ client: &client, model: model, @@ -262,7 +261,7 @@ func TestModelCost_GitignoreDetector(t *testing.T) { for _, model := range []string{ai.ModelSonnet, ai.ModelHaiku} { t.Run(model, func(t *testing.T) { - client := anthropic.NewClient(option.WithAPIKey(apiKey)) + client := ai.NewAnthropicClient(apiKey) supervisor := &costTrackingSupervisor{ client: &client, model: model, diff --git a/internal/health/model_quality_test.go b/internal/health/model_quality_test.go index 9bdd7fbd..8a35f0af 100644 --- a/internal/health/model_quality_test.go +++ b/internal/health/model_quality_test.go @@ -11,7 +11,6 @@ import ( "time" "github.com/anthropics/anthropic-sdk-go" - "github.com/anthropics/anthropic-sdk-go/option" "github.com/steveyegge/vc/internal/ai" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -56,7 +55,7 @@ func TestModelQuality_CruftDetector(t *testing.T) { for _, model := range []string{ai.ModelSonnet, ai.ModelHaiku} { t.Run(model, func(t *testing.T) { // Create supervisor with specific model - client := anthropic.NewClient(option.WithAPIKey(apiKey)) + client := ai.NewAnthropicClient(apiKey) supervisor := &realAISupervisor{ client: &client, model: model, @@ -165,7 +164,7 @@ func TestModelQuality_FileSizeMonitor(t *testing.T) { for _, model := range []string{ai.ModelSonnet, ai.ModelHaiku} { t.Run(model, func(t *testing.T) { - client := anthropic.NewClient(option.WithAPIKey(apiKey)) + client := ai.NewAnthropicClient(apiKey) supervisor := &realAISupervisor{ client: &client, model: model, @@ -298,7 +297,7 @@ func TestModelQuality_GitignoreDetector(t *testing.T) { for _, model := range []string{ai.ModelSonnet, ai.ModelHaiku} { t.Run(model, func(t *testing.T) { - client := anthropic.NewClient(option.WithAPIKey(apiKey)) + client := ai.NewAnthropicClient(apiKey) supervisor := &realAISupervisor{ client: &client, model: model, diff --git a/internal/repl/conversation_state.go b/internal/repl/conversation_state.go index 74b50848..60deb2cf 100644 --- a/internal/repl/conversation_state.go +++ b/internal/repl/conversation_state.go @@ -6,7 +6,7 @@ import ( "os" "github.com/anthropics/anthropic-sdk-go" - "github.com/anthropics/anthropic-sdk-go/option" + "github.com/steveyegge/vc/internal/ai" "github.com/steveyegge/vc/internal/storage" ) @@ -37,7 +37,7 @@ func NewConversationHandler(store storage.Storage, actor string) (*ConversationH actor = "user" } - client := anthropic.NewClient(option.WithAPIKey(apiKey)) + client := ai.NewAnthropicClient(apiKey) return &ConversationHandler{ client: &client,