Skip to content

Commit 6d0d50a

Browse files
committed
Use the oci source in the server
Also cleanup Signed-off-by: Djordje Lukic <djordje.lukic@docker.com>
1 parent d6299b5 commit 6d0d50a

8 files changed

Lines changed: 150 additions & 1693 deletions

File tree

cmd/root/api.go

Lines changed: 4 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,14 @@ import (
44
"fmt"
55
"log/slog"
66
"os"
7-
"path/filepath"
8-
"time"
97

108
"github.com/spf13/cobra"
119

1210
"github.com/docker/cagent/pkg/agentfile"
1311
"github.com/docker/cagent/pkg/cli"
1412
"github.com/docker/cagent/pkg/config"
15-
"github.com/docker/cagent/pkg/remote"
1613
"github.com/docker/cagent/pkg/server"
1714
"github.com/docker/cagent/pkg/session"
18-
"github.com/docker/cagent/pkg/teamloader"
1915
"github.com/docker/cagent/pkg/telemetry"
2016
)
2117

@@ -73,96 +69,21 @@ func (f *apiFlags) runAPICommand(cmd *cobra.Command, args []string) error {
7369

7470
slog.Debug("Starting server", "agents", agentsPath, "addr", ln.Addr().String())
7571

76-
resolvedPath, err := agentfile.Resolve(ctx, out, agentsPath)
77-
if err != nil {
78-
return err
79-
}
80-
8172
sessionStore, err := session.NewSQLiteSessionStore(f.sessionDB)
8273
if err != nil {
8374
return fmt.Errorf("failed to create session store: %w", err)
8475
}
8576

86-
var opts []server.Opt
87-
88-
if !agentfile.IsOCIReference(agentsPath) {
89-
stat, err := os.Stat(resolvedPath)
90-
if err != nil {
91-
return fmt.Errorf("failed to stat agents path: %w", err)
92-
}
93-
if stat.IsDir() {
94-
// For directories: only set agentsDir, not agentsPath
95-
opts = append(opts, server.WithAgentsDir(resolvedPath))
96-
} else {
97-
opts = append(opts, server.WithAgentsPath(resolvedPath), server.WithAgentsDir(filepath.Dir(resolvedPath)))
98-
}
99-
}
100-
101-
teams, err := teamloader.LoadTeams(ctx, resolvedPath, &f.runConfig)
77+
sources, err := agentfile.ResolveSources(ctx, nil, agentsPath)
10278
if err != nil {
103-
return fmt.Errorf("failed to load teams: %w", err)
104-
}
105-
106-
// For OCI refs: store the reference for later per-session reloading, then clean up temp file
107-
if agentfile.IsOCIReference(agentsPath) {
108-
teamKey := filepath.Base(resolvedPath)
109-
opts = append(opts, server.WithOCIRef(teamKey, agentsPath))
110-
111-
if err := os.Remove(resolvedPath); err != nil {
112-
slog.Warn("Failed to remove temporary OCI file", "path", resolvedPath, "error", err)
113-
} else {
114-
slog.Debug("Cleaned up temporary OCI file", "path", resolvedPath)
115-
}
79+
return fmt.Errorf("failed to resolve agent sources: %w", err)
11680
}
117-
118-
defer func() {
119-
for _, team := range teams {
120-
if err := team.StopToolSets(ctx); err != nil {
121-
slog.Error("Failed to stop tool sets", "error", err)
122-
}
123-
}
124-
}()
125-
126-
s, err := server.New(sessionStore, &f.runConfig, teams, opts...)
81+
s, err := server.New(sessionStore, &f.runConfig, sources)
12782
if err != nil {
12883
return fmt.Errorf("failed to create server: %w", err)
12984
}
13085

131-
// Start background auto-pull for OCI references if enabled
132-
if f.pullIntervalMins > 0 {
133-
go func() {
134-
ticker := time.NewTicker(time.Duration(f.pullIntervalMins) * time.Minute)
135-
defer ticker.Stop()
136-
137-
slog.Info("Auto-pull enabled for OCI reference", "reference", agentsPath, "interval_minutes", f.pullIntervalMins)
138-
139-
for {
140-
select {
141-
case <-ctx.Done():
142-
return
143-
case <-ticker.C:
144-
slog.Info("Auto-pulling OCI reference", "reference", agentsPath)
145-
if _, err := remote.Pull(ctx, agentsPath, false); err != nil {
146-
slog.Error("Failed to auto-pull OCI reference", "reference", agentsPath, "error", err)
147-
continue
148-
}
149-
150-
// Resolve the OCI reference to get the updated file path
151-
newResolvedPath, err := agentfile.Resolve(ctx, out, agentsPath)
152-
if err != nil {
153-
slog.Error("Failed to resolve OCI reference after pull", "reference", agentsPath, "error", err)
154-
continue
155-
}
156-
157-
if err := s.ReloadTeams(ctx, newResolvedPath); err != nil {
158-
slog.Error("Failed to reload teams", "reference", agentsPath, "error", err)
159-
} else {
160-
slog.Info("Successfully reloaded teams from updated OCI reference", "reference", agentsPath)
161-
}
162-
}
163-
}
164-
}()
165-
}
86+
// TODO(rumpl): implement pull interval
16687

16788
return s.Serve(ctx, ln)
16889
}

pkg/acp/agent.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
type Agent struct {
2323
conn *acp.AgentSideConnection
2424
team *team.Team
25-
agentFilename string
25+
source teamloader.AgentSource
2626
runtimeConfig *config.RuntimeConfig
2727
sessions map[string]*Session
2828
mu sync.Mutex
@@ -39,9 +39,9 @@ type Session struct {
3939
}
4040

4141
// NewAgent creates a new ACP agent
42-
func NewAgent(agentFilename string, runtimeConfig *config.RuntimeConfig) *Agent {
42+
func NewAgent(source teamloader.AgentSource, runtimeConfig *config.RuntimeConfig) *Agent {
4343
return &Agent{
44-
agentFilename: agentFilename,
44+
source: source,
4545
runtimeConfig: runtimeConfig,
4646
sessions: make(map[string]*Session),
4747
}
@@ -69,7 +69,7 @@ func (a *Agent) Initialize(ctx context.Context, params acp.InitializeRequest) (a
6969

7070
a.mu.Lock()
7171
defer a.mu.Unlock()
72-
t, err := teamloader.Load(ctx, a.agentFilename, a.runtimeConfig, teamloader.WithToolsetRegistry(createToolsetRegistry(a)))
72+
t, err := teamloader.LoadFrom(ctx, a.source, a.runtimeConfig, teamloader.WithToolsetRegistry(createToolsetRegistry(a)))
7373
if err != nil {
7474
return acp.InitializeResponse{}, fmt.Errorf("failed to load teams: %w", err)
7575
}

pkg/acp/run.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,15 @@ import (
1111
"github.com/docker/cagent/pkg/config"
1212
)
1313

14-
type discardOutput struct{}
15-
16-
func (d *discardOutput) Printf(string, ...any) {}
17-
1814
func Run(ctx context.Context, agentFilename string, stdin io.Reader, stdout io.Writer, runConfig *config.RuntimeConfig) error {
1915
slog.Debug("Starting ACP server", "agent", agentFilename)
2016

21-
agentFilename, err := agentfile.Resolve(ctx, &discardOutput{}, agentFilename)
17+
source, err := agentfile.ResolveSource(ctx, nil, agentFilename)
2218
if err != nil {
2319
return err
2420
}
2521

26-
acpAgent := NewAgent(agentFilename, runConfig)
22+
acpAgent := NewAgent(source, runConfig)
2723
conn := acpsdk.NewAgentSideConnection(acpAgent, stdout, stdin)
2824
conn.SetLogger(slog.Default())
2925
acpAgent.SetAgentConnection(conn)

pkg/agentfile/resolver.go

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package agentfile
33
import (
44
"context"
55
_ "embed"
6+
"fmt"
67
"log/slog"
78
"os"
89
"path/filepath"
@@ -11,6 +12,7 @@ import (
1112
"github.com/google/go-containerregistry/pkg/name"
1213

1314
"github.com/docker/cagent/pkg/aliases"
15+
"github.com/docker/cagent/pkg/reference"
1416
"github.com/docker/cagent/pkg/teamloader"
1517
)
1618

@@ -21,11 +23,56 @@ type Printer interface {
2123
Printf(format string, a ...any)
2224
}
2325

26+
// ResolveSource resolves an agent file reference (local file or OCI image) to a local file path
27+
// For OCI references, always checks remote for updates but falls back to local cache if offline
28+
func ResolveSources(ctx context.Context, out Printer, agentFilename string) (teamloader.AgentSources, error) {
29+
resolvedPath, err := resolve(agentFilename)
30+
if err != nil {
31+
if IsOCIReference(agentFilename) {
32+
return map[string]teamloader.AgentSource{reference.OciRefToFilename(agentFilename): teamloader.NewOCISource(agentFilename)}, nil
33+
}
34+
return nil, err
35+
}
36+
37+
if resolvedPath == "default" {
38+
return map[string]teamloader.AgentSource{"default": teamloader.NewBytesSource(defaultAgent)}, nil
39+
}
40+
if isLocalFile(resolvedPath) {
41+
return map[string]teamloader.AgentSource{resolvedPath: teamloader.NewFileSource(resolvedPath)}, nil
42+
}
43+
if dirExists(resolvedPath) {
44+
sources := make(teamloader.AgentSources)
45+
entries, err := os.ReadDir(resolvedPath)
46+
if err != nil {
47+
return nil, fmt.Errorf("reading agents directory %s: %w", resolvedPath, err)
48+
}
49+
for _, entry := range entries {
50+
if entry.IsDir() {
51+
continue
52+
}
53+
ext := strings.ToLower(filepath.Ext(entry.Name()))
54+
if ext != ".yaml" && ext != ".yml" {
55+
continue
56+
}
57+
a := filepath.Join(resolvedPath, entry.Name())
58+
sources[a], err = ResolveSource(ctx, out, a)
59+
if err != nil {
60+
return nil, err
61+
}
62+
}
63+
return sources, nil
64+
}
65+
return map[string]teamloader.AgentSource{resolvedPath: teamloader.NewOCISource(agentFilename)}, nil
66+
}
67+
2468
// ResolveSource resolves an agent file reference (local file or OCI image) to a local file path
2569
// For OCI references, always checks remote for updates but falls back to local cache if offline
2670
func ResolveSource(ctx context.Context, out Printer, agentFilename string) (teamloader.AgentSource, error) {
27-
resolvedPath, err := Resolve(ctx, out, agentFilename)
71+
resolvedPath, err := resolve(agentFilename)
2872
if err != nil {
73+
if IsOCIReference(agentFilename) {
74+
return teamloader.NewOCISource(agentFilename), nil
75+
}
2976
return nil, err
3077
}
3178

@@ -35,11 +82,11 @@ func ResolveSource(ctx context.Context, out Printer, agentFilename string) (team
3582
if isLocalFile(resolvedPath) {
3683
return teamloader.NewFileSource(resolvedPath), nil
3784
}
38-
return teamloader.NewOCISource(resolvedPath), nil
85+
return teamloader.NewOCISource(agentFilename), nil
3986
}
4087

41-
// Resolve resolves an agent file reference (local file or OCI image) to a local file path
42-
func Resolve(ctx context.Context, out Printer, agentFilename string) (string, error) {
88+
// resolve resolves an agent reference, handling aliases and defaults
89+
func resolve(agentFilename string) (string, error) {
4390
if agentFilename == "" {
4491
agentFilename = "default"
4592
}
@@ -57,13 +104,25 @@ func Resolve(ctx context.Context, out Printer, agentFilename string) (string, er
57104
return "default", nil
58105
}
59106

60-
return agentFilename, nil
107+
abs, err := filepath.Abs(agentFilename)
108+
if err != nil {
109+
return "", err
110+
}
111+
112+
return abs, nil
61113
}
62114

63115
// fileExists checks if a file exists at the given path
64116
func fileExists(path string) bool {
65-
_, err := os.Stat(path)
66-
exists := err == nil
117+
s, err := os.Stat(path)
118+
exists := err == nil && !s.IsDir()
119+
return exists
120+
}
121+
122+
// fileExists checks if a file exists at the given path
123+
func dirExists(path string) bool {
124+
s, err := os.Stat(path)
125+
exists := err == nil && s.IsDir()
67126
return exists
68127
}
69128

0 commit comments

Comments
 (0)