bash_output.go

 1package tools
 2
 3import (
 4	"context"
 5	"fmt"
 6	"strings"
 7
 8	"charm.land/fantasy"
 9	"github.com/charmbracelet/crush/internal/shell"
10)
11
12const (
13	BashOutputToolName = "bash_output"
14)
15
16type BashOutputParams struct {
17	ShellID string `json:"shell_id" description:"The ID of the background shell to retrieve output from"`
18}
19
20type BashOutputResponseMetadata struct {
21	ShellID          string `json:"shell_id"`
22	Done             bool   `json:"done"`
23	WorkingDirectory string `json:"working_directory"`
24}
25
26const bashOutputDescription = `Retrieves the current output from a background shell.
27
28<usage>
29- Provide the shell ID returned from a background bash execution
30- Returns the current stdout and stderr output
31- Indicates whether the shell has completed execution
32</usage>
33
34<features>
35- View output from running background processes
36- Check if background process has completed
37- Get cumulative output from process start
38</features>
39
40<tips>
41- Use this to monitor long-running processes
42- Check the 'done' status to see if process completed
43- Can be called multiple times to view incremental output
44</tips>
45`
46
47func NewBashOutputTool() fantasy.AgentTool {
48	return fantasy.NewAgentTool(
49		BashOutputToolName,
50		bashOutputDescription,
51		func(ctx context.Context, params BashOutputParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
52			if params.ShellID == "" {
53				return fantasy.NewTextErrorResponse("missing shell_id"), nil
54			}
55
56			bgManager := shell.GetBackgroundShellManager()
57			bgShell, ok := bgManager.Get(params.ShellID)
58			if !ok {
59				return fantasy.NewTextErrorResponse(fmt.Sprintf("background shell not found: %s", params.ShellID)), nil
60			}
61
62			stdout, stderr, done, err := bgShell.GetOutput()
63
64			var outputParts []string
65			if stdout != "" {
66				outputParts = append(outputParts, stdout)
67			}
68			if stderr != "" {
69				outputParts = append(outputParts, stderr)
70			}
71
72			status := "running"
73			if done {
74				status = "completed"
75				if err != nil {
76					exitCode := shell.ExitCode(err)
77					if exitCode != 0 {
78						outputParts = append(outputParts, fmt.Sprintf("Exit code %d", exitCode))
79					}
80				}
81			}
82
83			output := strings.Join(outputParts, "\n")
84
85			metadata := BashOutputResponseMetadata{
86				ShellID:          params.ShellID,
87				Done:             done,
88				WorkingDirectory: bgShell.GetWorkingDir(),
89			}
90
91			if output == "" {
92				output = BashNoOutput
93			}
94
95			result := fmt.Sprintf("Shell ID: %s\nStatus: %s\n\nOutput:\n%s", params.ShellID, status, output)
96			return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result), metadata), nil
97		})
98}