bash_output.go

 1package tools
 2
 3import (
 4	"context"
 5	_ "embed"
 6	"fmt"
 7	"strings"
 8
 9	"charm.land/fantasy"
10	"github.com/charmbracelet/crush/internal/shell"
11)
12
13const (
14	BashOutputToolName = "bash_output"
15)
16
17//go:embed bash_output.md
18var bashOutputDescription []byte
19
20type BashOutputParams struct {
21	ShellID string `json:"shell_id" description:"The ID of the background shell to retrieve output from"`
22}
23
24type BashOutputResponseMetadata struct {
25	ShellID          string `json:"shell_id"`
26	Done             bool   `json:"done"`
27	WorkingDirectory string `json:"working_directory"`
28}
29
30func NewBashOutputTool() fantasy.AgentTool {
31	return fantasy.NewAgentTool(
32		BashOutputToolName,
33		string(bashOutputDescription),
34		func(ctx context.Context, params BashOutputParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
35			if params.ShellID == "" {
36				return fantasy.NewTextErrorResponse("missing shell_id"), nil
37			}
38
39			bgManager := shell.GetBackgroundShellManager()
40			bgShell, ok := bgManager.Get(params.ShellID)
41			if !ok {
42				return fantasy.NewTextErrorResponse(fmt.Sprintf("background shell not found: %s", params.ShellID)), nil
43			}
44
45			stdout, stderr, done, err := bgShell.GetOutput()
46
47			var outputParts []string
48			if stdout != "" {
49				outputParts = append(outputParts, stdout)
50			}
51			if stderr != "" {
52				outputParts = append(outputParts, stderr)
53			}
54
55			status := "running"
56			if done {
57				status = "completed"
58				if err != nil {
59					exitCode := shell.ExitCode(err)
60					if exitCode != 0 {
61						outputParts = append(outputParts, fmt.Sprintf("Exit code %d", exitCode))
62					}
63				}
64			}
65
66			output := strings.Join(outputParts, "\n")
67
68			metadata := BashOutputResponseMetadata{
69				ShellID:          params.ShellID,
70				Done:             done,
71				WorkingDirectory: bgShell.GetWorkingDir(),
72			}
73
74			if output == "" {
75				output = BashNoOutput
76			}
77
78			result := fmt.Sprintf("Shell ID: %s\nStatus: %s\n\nOutput:\n%s", params.ShellID, status, output)
79			return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result), metadata), nil
80		})
81}