job_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	JobOutputToolName = "job_output"
15)
16
17//go:embed job_output.md
18var jobOutputDescription []byte
19
20type JobOutputParams struct {
21	ShellID string `json:"shell_id" description:"The ID of the background shell to retrieve output from"`
22}
23
24type JobOutputResponseMetadata struct {
25	ShellID          string `json:"shell_id"`
26	Command          string `json:"command"`
27	Description      string `json:"description"`
28	Done             bool   `json:"done"`
29	WorkingDirectory string `json:"working_directory"`
30}
31
32func NewJobOutputTool() fantasy.AgentTool {
33	return fantasy.NewAgentTool(
34		JobOutputToolName,
35		string(jobOutputDescription),
36		func(ctx context.Context, params JobOutputParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
37			if params.ShellID == "" {
38				return fantasy.NewTextErrorResponse("missing shell_id"), nil
39			}
40
41			bgManager := shell.GetBackgroundShellManager()
42			bgShell, ok := bgManager.Get(params.ShellID)
43			if !ok {
44				return fantasy.NewTextErrorResponse(fmt.Sprintf("background shell not found: %s", params.ShellID)), nil
45			}
46
47			stdout, stderr, done, err := bgShell.GetOutput()
48
49			var outputParts []string
50			if stdout != "" {
51				outputParts = append(outputParts, stdout)
52			}
53			if stderr != "" {
54				outputParts = append(outputParts, stderr)
55			}
56
57			status := "running"
58			if done {
59				status = "completed"
60				if err != nil {
61					exitCode := shell.ExitCode(err)
62					if exitCode != 0 {
63						outputParts = append(outputParts, fmt.Sprintf("Exit code %d", exitCode))
64					}
65				}
66			}
67
68			output := strings.Join(outputParts, "\n")
69
70			metadata := JobOutputResponseMetadata{
71				ShellID:          params.ShellID,
72				Command:          bgShell.Command,
73				Description:      bgShell.Description,
74				Done:             done,
75				WorkingDirectory: bgShell.WorkingDir,
76			}
77
78			if output == "" {
79				output = BashNoOutput
80			}
81
82			result := fmt.Sprintf("Status: %s\n\n%s", status, output)
83			return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result), metadata), nil
84		})
85}