diff options
Diffstat (limited to 'internal/executor/localtools.go')
| -rw-r--r-- | internal/executor/localtools.go | 279 |
1 files changed, 0 insertions, 279 deletions
diff --git a/internal/executor/localtools.go b/internal/executor/localtools.go deleted file mode 100644 index 9fd2cfc..0000000 --- a/internal/executor/localtools.go +++ /dev/null @@ -1,279 +0,0 @@ -package executor - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - - "github.com/thepeterstone/claudomator/internal/llm" -) - -// agentToolDefs returns the four agent back-channel tools as OpenAI -// function-calling definitions, for runners that talk to an OpenAI-compatible -// endpoint (LocalRunner). They mirror the MCP tools the container runners expose. -func agentToolDefs() []llm.Tool { - strProp := func(desc string) map[string]any { - return map[string]any{"type": "string", "description": desc} - } - return []llm.Tool{ - {Type: "function", Function: llm.ToolFunction{ - Name: "ask_user", - Description: "Ask the user a question when you genuinely need a decision to proceed. The task pauses until the user answers; do not call other tools after this.", - Parameters: map[string]any{ - "type": "object", - "properties": map[string]any{ - "question": strProp("the question to ask, phrased as a real question"), - "options": map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "description": "optional suggested answer choices"}, - }, - "required": []string{"question"}, - }, - }}, - {Type: "function", Function: llm.ToolFunction{ - Name: "report_summary", - Description: "Record a concise 2-5 sentence summary of what you accomplished. Call this before finishing.", - Parameters: map[string]any{ - "type": "object", - "properties": map[string]any{"summary": strProp("the summary text")}, - "required": []string{"summary"}, - }, - }}, - {Type: "function", Function: llm.ToolFunction{ - Name: "spawn_subtask", - Description: "Create a child task to be executed separately. Use this to break large work into focused pieces.", - Parameters: map[string]any{ - "type": "object", - "properties": map[string]any{ - "name": strProp("short descriptive name for the subtask"), - "instructions": strProp("complete instructions for the subtask agent"), - "model": strProp("optional model override"), - "max_budget_usd": map[string]any{"type": "number", "description": "optional budget cap in USD"}, - }, - "required": []string{"name", "instructions"}, - }, - }}, - {Type: "function", Function: llm.ToolFunction{ - Name: "record_progress", - Description: "Record a short progress note that appears in the task timeline.", - Parameters: map[string]any{ - "type": "object", - "properties": map[string]any{"message": strProp("a short progress note")}, - "required": []string{"message"}, - }, - }}, - {Type: "function", Function: llm.ToolFunction{ - Name: "read_file", - Description: "Read the contents of a file in the sandbox working directory.", - Parameters: map[string]any{ - "type": "object", - "properties": map[string]any{"path": strProp("path to the file, relative or absolute")}, - "required": []string{"path"}, - }, - }}, - {Type: "function", Function: llm.ToolFunction{ - Name: "write_file", - Description: "Write or overwrite a file in the sandbox working directory.", - Parameters: map[string]any{ - "type": "object", - "properties": map[string]any{ - "path": strProp("path to the file, relative or absolute"), - "content": strProp("content to write"), - }, - "required": []string{"path", "content"}, - }, - }}, - {Type: "function", Function: llm.ToolFunction{ - Name: "run_bash", - Description: "Run a shell command in the sandbox working directory. Returns exit code, stdout, and stderr.", - Parameters: map[string]any{ - "type": "object", - "properties": map[string]any{"command": strProp("shell command to execute")}, - "required": []string{"command"}, - }, - }}, - {Type: "function", Function: llm.ToolFunction{ - Name: "glob", - Description: "List files matching a glob pattern relative to the sandbox working directory.", - Parameters: map[string]any{ - "type": "object", - "properties": map[string]any{"pattern": strProp("glob pattern, e.g. **/*.go")}, - "required": []string{"pattern"}, - }, - }}, - } -} - -// dispatchAgentTool invokes one model-requested tool against the AgentChannel. -// workDir is the sandbox working directory; file/bash tools return an error if -// it is empty. It returns the text to feed back to the model as the tool -// result, and a blocked flag set when ask_user could not be answered in-session -// (the run must stop and the task block). -func dispatchAgentTool(ctx context.Context, ch AgentChannel, workDir, name, argsJSON string) (result string, blocked bool, err error) { - switch name { - case "ask_user": - var a struct { - Question string `json:"question"` - Options []string `json:"options"` - } - _ = json.Unmarshal([]byte(argsJSON), &a) - q := map[string]any{"text": a.Question} - if len(a.Options) > 0 { - q["options"] = a.Options - } - payload, _ := json.Marshal(q) - ans, askErr := ch.AskUser(ctx, string(payload)) - if errors.Is(askErr, ErrAgentBlocked) { - return "", true, nil - } - if askErr != nil { - return "", false, askErr - } - return ans, false, nil - - case "report_summary": - var a struct { - Summary string `json:"summary"` - } - _ = json.Unmarshal([]byte(argsJSON), &a) - if rsErr := ch.ReportSummary(ctx, a.Summary); rsErr != nil { - return "", false, rsErr - } - return "Summary recorded.", false, nil - - case "spawn_subtask": - var a struct { - Name string `json:"name"` - Instructions string `json:"instructions"` - Model string `json:"model"` - MaxBudgetUSD float64 `json:"max_budget_usd"` - } - _ = json.Unmarshal([]byte(argsJSON), &a) - id, ssErr := ch.SpawnSubtask(ctx, SubtaskSpec{ - Name: a.Name, - Instructions: a.Instructions, - Model: a.Model, - MaxBudgetUSD: a.MaxBudgetUSD, - }) - if ssErr != nil { - return "", false, ssErr - } - return "Created subtask " + id, false, nil - - case "record_progress": - var a struct { - Message string `json:"message"` - } - _ = json.Unmarshal([]byte(argsJSON), &a) - if rpErr := ch.RecordProgress(ctx, a.Message); rpErr != nil { - return "", false, rpErr - } - return "Noted.", false, nil - - case "read_file": - if workDir == "" { - return "", false, fmt.Errorf("read_file: no sandbox working directory") - } - var a struct { - Path string `json:"path"` - } - _ = json.Unmarshal([]byte(argsJSON), &a) - p := a.Path - if !filepath.IsAbs(p) { - p = filepath.Join(workDir, p) - } - data, readErr := os.ReadFile(p) - if readErr != nil { - return "", false, readErr - } - return string(data), false, nil - - case "write_file": - if workDir == "" { - return "", false, fmt.Errorf("write_file: no sandbox working directory") - } - var a struct { - Path string `json:"path"` - Content string `json:"content"` - } - _ = json.Unmarshal([]byte(argsJSON), &a) - p := a.Path - if !filepath.IsAbs(p) { - p = filepath.Join(workDir, p) - } - if mkErr := os.MkdirAll(filepath.Dir(p), 0o755); mkErr != nil { - return "", false, mkErr - } - if writeErr := os.WriteFile(p, []byte(a.Content), 0o644); writeErr != nil { - return "", false, writeErr - } - return "Written.", false, nil - - case "run_bash": - if workDir == "" { - return "", false, fmt.Errorf("run_bash: no sandbox working directory") - } - var a struct { - Command string `json:"command"` - } - _ = json.Unmarshal([]byte(argsJSON), &a) - cmd := exec.CommandContext(ctx, "sh", "-c", a.Command) - cmd.Dir = workDir - var stdout, stderr bytes.Buffer - cmd.Stdout = &stdout - cmd.Stderr = &stderr - runErr := cmd.Run() - exitCode := 0 - if runErr != nil { - if exitErr, ok := runErr.(*exec.ExitError); ok { - exitCode = exitErr.ExitCode() - } else { - return "", false, runErr - } - } - const maxOutput = 8 * 1024 - out := stdout.String() - errOut := stderr.String() - if len(out) > maxOutput { - out = out[:maxOutput] + "\n[truncated]" - } - if len(errOut) > maxOutput { - errOut = errOut[:maxOutput] + "\n[truncated]" - } - return fmt.Sprintf("exit_code: %d\nstdout:\n%s\nstderr:\n%s", exitCode, out, errOut), false, nil - - case "glob": - if workDir == "" { - return "", false, fmt.Errorf("glob: no sandbox working directory") - } - var a struct { - Pattern string `json:"pattern"` - } - _ = json.Unmarshal([]byte(argsJSON), &a) - pattern := a.Pattern - if !filepath.IsAbs(pattern) { - pattern = filepath.Join(workDir, pattern) - } - matches, globErr := filepath.Glob(pattern) - if globErr != nil { - return "", false, globErr - } - // Return paths relative to workDir for readability. - rel := make([]string, 0, len(matches)) - for _, m := range matches { - r, relErr := filepath.Rel(workDir, m) - if relErr != nil { - r = m - } - rel = append(rel, r) - } - out, _ := json.Marshal(rel) - return string(out), false, nil - - default: - return "", false, fmt.Errorf("unknown tool %q", name) - } -} |
