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")
}
}
}
|