1+ // Package creator provides functionality to create agent configurations interactively.
2+ // It generates a special agent that helps users build their own agent YAML files.
13package creator
24
35import (
79 "fmt"
810 "strings"
911
12+ "github.com/goccy/go-yaml"
13+
1014 "github.com/docker/cagent/pkg/config"
1115 "github.com/docker/cagent/pkg/config/latest"
1216 "github.com/docker/cagent/pkg/team"
@@ -18,95 +22,153 @@ import (
1822//go:embed instructions.txt
1923var agentBuilderInstructions string
2024
21- type fsToolset struct {
22- tools.ToolSet
23- originalWriteFileHandler tools.ToolHandler
24- path string
25- }
26-
27- func (f * fsToolset ) Tools (ctx context.Context ) ([]tools.Tool , error ) {
28- innerTools , err := f .ToolSet .Tools (ctx )
29- if err != nil {
30- return nil , err
31- }
32-
33- for i , tool := range innerTools {
34- if tool .Name == builtin .ToolNameWriteFile {
35- f .originalWriteFileHandler = tool .Handler
36- innerTools [i ].Handler = f .customWriteFileHandler
37- }
38- }
25+ // Constants for the creator agent configuration.
26+ const (
27+ creatorAgentName = "root"
28+ creatorAgentModel = "auto"
29+ creatorWelcomeMessage = "Hello! I'm here to create agents for you.\n \n Can you explain to me what the agent will be used for?"
30+ )
3931
40- return innerTools , nil
41- }
32+ // Agent creates and returns a team configured for the agent builder functionality.
33+ // The agent builder helps users create their own agent configurations interactively.
34+ //
35+ // Parameters:
36+ // - ctx: Context for the operation
37+ // - runConfig: Runtime configuration including working directory and environment
38+ // - modelNameOverride: Optional model override (empty string uses auto-selection)
39+ //
40+ // Returns the configured team or an error if configuration fails.
41+ func Agent (ctx context.Context , runConfig * config.RuntimeConfig , modelNameOverride string ) (* team.Team , error ) {
42+ instructions := buildInstructions (ctx , runConfig )
4243
43- func (f * fsToolset ) customWriteFileHandler (ctx context.Context , toolCall tools.ToolCall ) (* tools.ToolCallResult , error ) {
44- var args struct {
45- Path string `json:"path"`
46- Content string `json:"content"`
47- }
48- if err := json .Unmarshal ([]byte (toolCall .Function .Arguments ), & args ); err != nil {
49- return nil , fmt .Errorf ("failed to parse arguments: %w" , err )
44+ configYAML , err := buildCreatorConfigYAML (instructions )
45+ if err != nil {
46+ return nil , fmt .Errorf ("building creator config: %w" , err )
5047 }
5148
52- f . path = args . Path
49+ registry := createToolsetRegistry ( runConfig . WorkingDir )
5350
54- return f .originalWriteFileHandler (ctx , toolCall )
51+ return teamloader .Load (
52+ ctx ,
53+ config .NewBytesSource ("creator" , configYAML ),
54+ runConfig ,
55+ teamloader .WithModelOverrides ([]string {modelNameOverride }),
56+ teamloader .WithToolsetRegistry (registry ),
57+ )
5558}
5659
57- func Agent (ctx context.Context , runConfig * config.RuntimeConfig , modelNameOverride string ) (* team.Team , error ) {
60+ // buildInstructions creates the full instruction set for the creator agent,
61+ // including provider-specific model configuration examples.
62+ func buildInstructions (ctx context.Context , runConfig * config.RuntimeConfig ) string {
5863 usableProviders := config .AvailableProviders (ctx , runConfig .ModelsGateway , runConfig .EnvProvider ())
5964
60- // Provide soft guidance to prefer the selected providers
61- instructions := agentBuilderInstructions
62- instructions += "\n \n Preferred model providers to use: " + strings .Join (usableProviders , ", " )
63- instructions += ". You must always use one or more of the following model configurations: \n "
65+ var b strings.Builder
66+ b .WriteString (agentBuilderInstructions )
67+ b .WriteString ("\n \n Preferred model providers to use: " )
68+ b .WriteString (strings .Join (usableProviders , ", " ))
69+ b .WriteString (". You must always use one or more of the following model configurations: \n " )
6470
6571 for _ , provider := range usableProviders {
6672 model := config .DefaultModels [provider ]
6773 maxTokens := config .PreferredMaxTokens (provider )
68- instructions += fmt .Sprintf ( `
74+ fmt .Fprintf ( & b , `
6975 models:
7076 %s:
7177 provider: %s
7278 model: %s
73- max_tokens: %d\n` , provider , provider , model , maxTokens )
79+ max_tokens: %d
80+ ` , provider , provider , model , * maxTokens )
7481 }
7582
76- // Define a new agent configuration
77- newAgentConfig := latest.Config {
78- Agents : []latest.AgentConfig {{
79- Name : "root" ,
80- WelcomeMessage : "Hello! I'm here to create agents for you.\n \n Can you explain to me what the agent will be used for?" ,
81- Instruction : instructions ,
82- Model : "auto" ,
83- Toolsets : []latest.Toolset {
84- {Type : "shell" },
85- {Type : "filesystem" },
86- },
87- }},
83+ return b .String ()
84+ }
85+
86+ // buildCreatorConfigYAML generates the YAML configuration for the creator agent.
87+ // It uses yaml.MapSlice to ensure proper indentation of multi-line strings.
88+ func buildCreatorConfigYAML (instructions string ) ([]byte , error ) {
89+ // Define available toolsets for the creator agent
90+ toolsets := []map [string ]any {
91+ {"type" : "shell" },
92+ {"type" : "filesystem" },
8893 }
8994
90- configAsJSON , err := json .Marshal (newAgentConfig )
91- if err != nil {
92- return nil , fmt .Errorf ("marshalling config: %w" , err )
95+ // Build the root agent configuration
96+ rootAgent := yaml.MapSlice {
97+ {Key : "model" , Value : creatorAgentModel },
98+ {Key : "welcome_message" , Value : creatorWelcomeMessage },
99+ {Key : "instruction" , Value : instructions },
100+ {Key : "toolsets" , Value : toolsets },
93101 }
94102
95- // Custom tool registry to include fsToolset
96- fsToolset := fsToolset {
97- ToolSet : builtin .NewFilesystemTool (runConfig .WorkingDir ),
103+ // Build the full config structure
104+ agentsConfig := yaml.MapSlice {
105+ {Key : creatorAgentName , Value : rootAgent },
106+ }
107+
108+ fullConfig := yaml.MapSlice {
109+ {Key : "agents" , Value : agentsConfig },
110+ }
111+
112+ return yaml .Marshal (fullConfig )
113+ }
114+
115+ // createToolsetRegistry creates a custom toolset registry that wraps the filesystem
116+ // toolset to track file paths written by the agent.
117+ func createToolsetRegistry (workingDir string ) * teamloader.ToolsetRegistry {
118+ tracker := & fileWriteTracker {
119+ ToolSet : builtin .NewFilesystemTool (workingDir ),
98120 }
99121
100122 registry := teamloader .NewDefaultToolsetRegistry ()
101123 registry .Register ("filesystem" , func (context.Context , latest.Toolset , string , * config.RuntimeConfig ) (tools.ToolSet , error ) {
102- return & fsToolset , nil
124+ return tracker , nil
103125 })
104126
105- return teamloader .Load (
106- ctx ,
107- config .NewBytesSource ("creator" , configAsJSON ),
108- runConfig ,
109- teamloader .WithModelOverrides ([]string {modelNameOverride }),
110- teamloader .WithToolsetRegistry (registry ),
111- )
127+ return registry
128+ }
129+
130+ // fileWriteTracker wraps a filesystem toolset to track files written by the agent.
131+ // This allows the creator to know what files were created during the session.
132+ type fileWriteTracker struct {
133+ tools.ToolSet
134+ originalWriteFileHandler tools.ToolHandler
135+ path string
136+ }
137+
138+ // Tools returns the available tools, wrapping the write_file tool to track paths.
139+ func (t * fileWriteTracker ) Tools (ctx context.Context ) ([]tools.Tool , error ) {
140+ innerTools , err := t .ToolSet .Tools (ctx )
141+ if err != nil {
142+ return nil , err
143+ }
144+
145+ for i , tool := range innerTools {
146+ if tool .Name == builtin .ToolNameWriteFile {
147+ t .originalWriteFileHandler = tool .Handler
148+ innerTools [i ].Handler = t .trackWriteFile
149+ }
150+ }
151+
152+ return innerTools , nil
153+ }
154+
155+ // trackWriteFile intercepts write_file calls to track the path being written.
156+ func (t * fileWriteTracker ) trackWriteFile (ctx context.Context , toolCall tools.ToolCall ) (* tools.ToolCallResult , error ) {
157+ var args struct {
158+ Path string `json:"path"`
159+ Content string `json:"content"`
160+ }
161+ if err := json .Unmarshal ([]byte (toolCall .Function .Arguments ), & args ); err != nil {
162+ return nil , fmt .Errorf ("failed to parse write_file arguments: %w" , err )
163+ }
164+
165+ t .path = args .Path
166+
167+ return t .originalWriteFileHandler (ctx , toolCall )
168+ }
169+
170+ // LastWrittenPath returns the path of the last file written by the agent.
171+ // Returns an empty string if no file has been written yet.
172+ func (t * fileWriteTracker ) LastWrittenPath () string {
173+ return t .path
112174}
0 commit comments