@@ -5,8 +5,10 @@ import (
55 _ "embed"
66 "fmt"
77 "log/slog"
8+ "maps"
89 "os"
910 "path/filepath"
11+ "slices"
1012 "strings"
1113
1214 "github.com/google/go-containerregistry/pkg/name"
@@ -16,10 +18,10 @@ import (
1618 "github.com/docker/cagent/pkg/userconfig"
1719)
1820
19- //go:embed default-agent .yaml
21+ //go:embed builtin-agents/default .yaml
2022var defaultAgent []byte
2123
22- //go:embed coder-agent .yaml
24+ //go:embed builtin-agents/coder .yaml
2325var coderAgent []byte
2426
2527// builtinAgents maps built-in agent names to their embedded YAML configurations.
@@ -30,11 +32,7 @@ var builtinAgents = map[string][]byte{
3032
3133// BuiltinAgentNames returns the names of all built-in agents.
3234func BuiltinAgentNames () []string {
33- names := make ([]string , 0 , len (builtinAgents ))
34- for name := range builtinAgents {
35- names = append (names , name )
36- }
37- return names
35+ return slices .Sorted (maps .Keys (builtinAgents ))
3836}
3937
4038// ResolveAlias resolves an agent reference and returns the alias if it exists and has options.
@@ -70,61 +68,24 @@ func GetUserSettings() *userconfig.Settings {
7068// when fetching from GitHub URLs.
7169// For OCI references, always checks remote for updates but falls back to local cache if offline.
7270func ResolveSources (agentsPath string , envProvider environment.Provider ) (Sources , error ) {
73- // Handle URL references first (before resolve() which converts to absolute path)
74- if IsURLReference (agentsPath ) {
75- return map [string ]Source {
76- agentsPath : NewURLSource (agentsPath , envProvider ),
77- }, nil
78- }
79-
8071 resolvedPath , err := resolve (agentsPath )
8172 if err != nil {
73+ // resolve() only fails for non-OCI, non-URL, non-builtin references
74+ // that can't be made absolute. Try OCI as last resort.
8275 if IsOCIReference (agentsPath ) {
83- return map [string ]Source {
84- reference .OciRefToFilename (agentsPath ): NewOCISource (agentsPath ),
85- }, nil
76+ return singleSource (reference .OciRefToFilename (agentsPath ), NewOCISource (agentsPath )), nil
8677 }
8778 return nil , err
8879 }
8980
90- if data , ok := builtinAgents [resolvedPath ]; ok {
91- return map [string ]Source {
92- resolvedPath : NewBytesSource (resolvedPath , data ),
93- }, nil
94- }
95-
96- if isLocalFile (resolvedPath ) {
97- return map [string ]Source {
98- fileNameWithoutExt (resolvedPath ): NewFileSource (resolvedPath ),
99- }, nil
100- }
101-
81+ // Only directories need special handling to enumerate YAML files.
10282 if dirExists (resolvedPath ) {
103- sources := make (Sources )
104- entries , err := os .ReadDir (resolvedPath )
105- if err != nil {
106- return nil , fmt .Errorf ("reading agents directory %s: %w" , resolvedPath , err )
107- }
108- for _ , entry := range entries {
109- if entry .IsDir () {
110- continue
111- }
112- ext := strings .ToLower (filepath .Ext (entry .Name ()))
113- if ext != ".yaml" && ext != ".yml" {
114- continue
115- }
116- a := filepath .Join (resolvedPath , entry .Name ())
117- sources [fileNameWithoutExt (a )], err = Resolve (a , envProvider )
118- if err != nil {
119- return nil , err
120- }
121- }
122- return sources , nil
83+ return resolveDirectory (resolvedPath , envProvider )
12384 }
12485
125- return map [ string ] Source {
126- reference . OciRefToFilename ( resolvedPath ): NewOCISource (resolvedPath ),
127- } , nil
86+ // For all other reference types, delegate to resolveOne.
87+ key , source := resolveOne (resolvedPath , envProvider )
88+ return singleSource ( key , source ) , nil
12889}
12990
13091// Resolve resolves an agent file reference (local file, URL, or OCI image) to a source.
@@ -140,19 +101,55 @@ func Resolve(agentFilename string, envProvider environment.Provider) (Source, er
140101 return nil , err
141102 }
142103
143- if data , ok := builtinAgents [resolvedPath ]; ok {
144- return NewBytesSource (resolvedPath , data ), nil
104+ _ , source := resolveOne (resolvedPath , envProvider )
105+ return source , nil
106+ }
107+
108+ // resolveOne maps a resolved path to the appropriate Source and a key for use
109+ // in Sources maps. The path must already be resolved via resolve().
110+ // This is the single place that decides which source type a reference maps to.
111+ // To add a new source type, add a case here.
112+ func resolveOne (resolvedPath string , envProvider environment.Provider ) (string , Source ) {
113+ switch {
114+ case builtinAgents [resolvedPath ] != nil :
115+ return resolvedPath , NewBytesSource (resolvedPath , builtinAgents [resolvedPath ])
116+ case IsURLReference (resolvedPath ):
117+ return resolvedPath , NewURLSource (resolvedPath , envProvider )
118+ case isLocalFile (resolvedPath ):
119+ return fileNameWithoutExt (resolvedPath ), NewFileSource (resolvedPath )
120+ default :
121+ return reference .OciRefToFilename (resolvedPath ), NewOCISource (resolvedPath )
145122 }
123+ }
146124
147- if IsURLReference (resolvedPath ) {
148- return NewURLSource (resolvedPath , envProvider ), nil
125+ // resolveDirectory enumerates YAML files in a directory and resolves each one.
126+ func resolveDirectory (dirPath string , envProvider environment.Provider ) (Sources , error ) {
127+ entries , err := os .ReadDir (dirPath )
128+ if err != nil {
129+ return nil , fmt .Errorf ("reading agents directory %s: %w" , dirPath , err )
149130 }
150131
151- if isLocalFile (resolvedPath ) {
152- return NewFileSource (resolvedPath ), nil
132+ sources := make (Sources )
133+ for _ , entry := range entries {
134+ if entry .IsDir () {
135+ continue
136+ }
137+ ext := strings .ToLower (filepath .Ext (entry .Name ()))
138+ if ext != ".yaml" && ext != ".yml" {
139+ continue
140+ }
141+ a := filepath .Join (dirPath , entry .Name ())
142+ sources [fileNameWithoutExt (a )], err = Resolve (a , envProvider )
143+ if err != nil {
144+ return nil , err
145+ }
153146 }
147+ return sources , nil
148+ }
154149
155- return NewOCISource (resolvedPath ), nil
150+ // singleSource wraps a single source in a Sources map.
151+ func singleSource (key string , source Source ) Sources {
152+ return Sources {key : source }
156153}
157154
158155// resolve resolves an agent reference, handling aliases and defaults
0 commit comments