package cli import ( "log/slog" "net/http" "time" "github.com/thepeterstone/claudomator/internal/config" "github.com/thepeterstone/claudomator/internal/executor" "github.com/thepeterstone/claudomator/internal/llm" "github.com/thepeterstone/claudomator/internal/provider" "github.com/thepeterstone/claudomator/internal/provider/anthropic" "github.com/thepeterstone/claudomator/internal/provider/google" "github.com/thepeterstone/claudomator/internal/provider/openaicompat" ) // cloudProviderSpec describes how to construct a provider.Provider for one // cloud provider entry in cfg.Providers, and which RunnersConfig field gates // it. anthropic/google use their native wire-format adapters; groq/ // openrouter/openai are OpenAI-wire-compatible and reuse the Phase 1 // openaicompat adapter — no dedicated adapter package needed for those three // (see docs/api-keys-setup.md). type cloudProviderSpec struct { name string enabled func(config.RunnersConfig) bool build func(pc config.ProviderConfig, timeout time.Duration) provider.Provider } func openaiCompatBuild(pc config.ProviderConfig, timeout time.Duration) provider.Provider { return openaicompat.New(&llm.Client{ Endpoint: pc.Endpoint, Model: pc.DefaultModel, APIKey: pc.APIKey, HTTPClient: &http.Client{Timeout: timeout}, }) } var cloudProviderSpecs = []cloudProviderSpec{ { name: "anthropic", enabled: config.RunnersConfig.AnthropicEnabled, build: func(pc config.ProviderConfig, timeout time.Duration) provider.Provider { return anthropic.New(pc.APIKey, pc.Endpoint, timeout) }, }, { name: "google", enabled: config.RunnersConfig.GoogleEnabled, build: func(pc config.ProviderConfig, timeout time.Duration) provider.Provider { return google.New(pc.APIKey, pc.Endpoint, timeout) }, }, {name: "groq", enabled: config.RunnersConfig.GroqEnabled, build: openaiCompatBuild}, {name: "openrouter", enabled: config.RunnersConfig.OpenRouterEnabled, build: openaiCompatBuild}, {name: "openai", enabled: config.RunnersConfig.OpenAIEnabled, build: openaiCompatBuild}, } // registerCloudRunners constructs and adds a NativeRunner (SandboxKind: // "docker", matching the original anthropic/google-only wiring — cloud // providers get real container isolation, not the weaker HostSandbox local // runners use) into runners for every cloud provider in cloudProviderSpecs // that has a non-empty cfg.Providers[name].APIKey and is enabled via its // RunnersConfig.Enabled() gate. Called identically from both `serve` // and `run` — the only difference between them (logDir) is a parameter, and // neither command does anything provider-specific beyond this. func registerCloudRunners(runners map[string]executor.Runner, cfg *config.Config, logger *slog.Logger, logDir string) { for _, spec := range cloudProviderSpecs { pc, ok := cfg.Providers[spec.name] if !ok || pc.APIKey == "" || !spec.enabled(cfg.Runners) { continue } timeout := time.Duration(0) if pc.TimeoutSeconds > 0 { timeout = time.Duration(pc.TimeoutSeconds) * time.Second } runners[spec.name] = &executor.NativeRunner{ Provider: spec.build(pc, timeout), Logger: logger, LogDir: logDir, DefaultModel: pc.DefaultModel, // Native cloud providers get real container isolation // (sandbox.DockerSandbox) rather than HostSandbox's host-side // path-prefix confinement — see NativeRunner.SandboxKind doc. SandboxKind: "docker", SandboxImage: cfg.SandboxImage, } if logger != nil { logger.Info(spec.name+" runner registered", "default_model", pc.DefaultModel, "sandbox", "docker") } } }