Skip to content

Commit 51c318c

Browse files
committed
cagent build
Signed-off-by: David Gageot <david.gageot@docker.com>
1 parent ba031f8 commit 51c318c

2 files changed

Lines changed: 161 additions & 0 deletions

File tree

cmd/root/build.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package root
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"strings"
9+
10+
"github.com/spf13/cobra"
11+
12+
"github.com/docker/cagent/internal/telemetry"
13+
"github.com/docker/cagent/pkg/config"
14+
latest "github.com/docker/cagent/pkg/config/v2"
15+
"github.com/docker/cagent/pkg/model/provider"
16+
)
17+
18+
var push bool
19+
20+
func NewBuildCmd() *cobra.Command {
21+
cmd := &cobra.Command{
22+
Use: "build <agent-file> <image-name>",
23+
Args: cobra.ExactArgs(2),
24+
RunE: runBuildCommand,
25+
Hidden: true,
26+
}
27+
28+
cmd.PersistentFlags().BoolVar(&push, "push", false, "push the image")
29+
30+
return cmd
31+
}
32+
33+
func runBuildCommand(cmd *cobra.Command, args []string) error {
34+
telemetry.TrackCommand("build", args)
35+
36+
fileName := filepath.Base(args[0])
37+
parentDir := filepath.Dir(args[0])
38+
39+
cfg, err := config.LoadConfigSecure(fileName, parentDir)
40+
if err != nil {
41+
return err
42+
}
43+
44+
secrets := gatherRequiredEnv(cfg)
45+
mcpServers := gatherMCPServers(cfg)
46+
47+
tmp, err := os.MkdirTemp("", "build")
48+
if err != nil {
49+
return err
50+
}
51+
defer os.RemoveAll(tmp)
52+
53+
// TODO(dga): set the right entrypoint.
54+
err = os.WriteFile(filepath.Join(tmp, "Dockerfile"), fmt.Appendf(nil, `# syntax=docker/dockerfile:1
55+
FROM alpine:3.22@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
56+
57+
RUN adduser -D cagent
58+
ADD https://github.com/docker/cagent/releases/download/v1.0.9/cagent-linux-arm64 /cagent
59+
RUN chmod +x /cagent
60+
COPY agent.yaml /
61+
RUN chmod 666 /agent.yaml
62+
USER cagent
63+
ENTRYPOINT ["/cagent", "run", "--debug", "--tui=false", "/agent.yaml", "get my username on github"]
64+
65+
LABEL com.docker.agent.packaging.version="v0.0.1"
66+
LABEL com.docker.agent.runtime="cagent"
67+
LABEL org.opencontainers.image.description="%s"
68+
LABEL org.opencontainers.image.licenses="%s"
69+
LABEL com.docker.agent.mcp-servers="%s"
70+
LABEL com.docker.agent.secrets="%s"
71+
`, cfg.Agents["root"].Description, cfg.Metadata.License, strings.Join(mcpServers, ","), strings.Join(secrets, ",")), 0o700)
72+
if err != nil {
73+
return err
74+
}
75+
76+
agentYaml, err := os.ReadFile(args[0])
77+
if err != nil {
78+
return err
79+
}
80+
81+
err = os.WriteFile(filepath.Join(tmp, "agent.yaml"), agentYaml, 0o700)
82+
if err != nil {
83+
return err
84+
}
85+
86+
buildArgs := []string{"build", "-t", args[1]}
87+
if push {
88+
buildArgs = append(buildArgs, "--push")
89+
}
90+
buildArgs = append(buildArgs, tmp)
91+
buildCmd := exec.CommandContext(cmd.Context(), "docker", buildArgs...)
92+
buildCmd.Stdout = os.Stdout
93+
buildCmd.Stderr = os.Stderr
94+
95+
return buildCmd.Run()
96+
}
97+
98+
func gatherRequiredEnv(cfg *latest.Config) []string {
99+
requiredEnv := map[string]bool{}
100+
101+
for name := range cfg.Models {
102+
model := cfg.Models[name]
103+
// Use the token environment variable from the alias if available
104+
if alias, exists := provider.ProviderAliases[model.Provider]; exists {
105+
if alias.TokenEnvVar != "" {
106+
requiredEnv[alias.TokenEnvVar] = true
107+
}
108+
} else {
109+
// Fallback to hardcoded mappings for unknown providers
110+
switch model.Provider {
111+
case "openai":
112+
requiredEnv["OPENAI_API_KEY"] = true
113+
case "anthropic":
114+
requiredEnv["ANTHROPIC_API_KEY"] = true
115+
case "google":
116+
requiredEnv["GOOGLE_API_KEY"] = true
117+
}
118+
}
119+
}
120+
121+
for _, agent := range cfg.Agents {
122+
model := agent.Model
123+
switch {
124+
case strings.HasPrefix(model, "openai/"):
125+
requiredEnv["OPENAI_API_KEY"] = true
126+
case strings.HasPrefix(model, "anthropic/"):
127+
requiredEnv["ANTHROPIC_API_KEY"] = true
128+
case strings.HasPrefix(model, "google/"):
129+
requiredEnv["GOOGLE_API_KEY"] = true
130+
}
131+
}
132+
133+
var requiredEnvList []string
134+
for e := range requiredEnv {
135+
requiredEnvList = append(requiredEnvList, e)
136+
}
137+
138+
return requiredEnvList
139+
}
140+
141+
func gatherMCPServers(cfg *latest.Config) []string {
142+
requiredServers := map[string]bool{}
143+
144+
for _, agent := range cfg.Agents {
145+
for i := range agent.Toolsets {
146+
toolSet := agent.Toolsets[i]
147+
148+
if toolSet.Type == "mcp" && toolSet.Ref != "" {
149+
requiredServers[toolSet.Ref] = true
150+
}
151+
}
152+
}
153+
154+
var requiredServersList []string
155+
for e := range requiredServers {
156+
requiredServersList = append(requiredServersList, e)
157+
}
158+
159+
return requiredServersList
160+
}

cmd/root/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ func NewRootCmd() *cobra.Command {
105105
cmd.AddCommand(NewDebugCmd())
106106
cmd.AddCommand(NewFeedbackCmd())
107107
cmd.AddCommand(NewCatalogCmd())
108+
cmd.AddCommand(NewBuildCmd())
108109

109110
return cmd
110111
}

0 commit comments

Comments
 (0)