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