summaryrefslogtreecommitdiff
path: root/internal/executor/localtools.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/executor/localtools.go')
-rw-r--r--internal/executor/localtools.go279
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)
- }
-}