summaryrefslogtreecommitdiff
path: root/internal/cli/cloudrunners.go
blob: 55b3efb55aade0a631b9663b47df06305c1c3a8e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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.<Name>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")
		}
	}
}