1package tools
 2
 3import (
 4	"context"
 5	_ "embed"
 6	"fmt"
 7
 8	"charm.land/fantasy"
 9	"github.com/charmbracelet/crush/internal/shell"
10)
11
12const (
13	JobKillToolName = "job_kill"
14)
15
16//go:embed job_kill.md
17var jobKillDescription []byte
18
19type JobKillParams struct {
20	ShellID string `json:"shell_id" description:"The ID of the background shell to terminate"`
21}
22
23type JobKillResponseMetadata struct {
24	ShellID     string `json:"shell_id"`
25	Command     string `json:"command"`
26	Description string `json:"description"`
27}
28
29func NewJobKillTool() fantasy.AgentTool {
30	return fantasy.NewAgentTool(
31		JobKillToolName,
32		string(jobKillDescription),
33		func(ctx context.Context, params JobKillParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
34			if params.ShellID == "" {
35				return fantasy.NewTextErrorResponse("missing shell_id"), nil
36			}
37
38			bgManager := shell.GetBackgroundShellManager()
39
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			metadata := JobKillResponseMetadata{
46				ShellID:     params.ShellID,
47				Command:     bgShell.Command,
48				Description: bgShell.Description,
49			}
50
51			err := bgManager.Kill(params.ShellID)
52			if err != nil {
53				return fantasy.NewTextErrorResponse(err.Error()), nil
54			}
55
56			result := fmt.Sprintf("Background shell %s terminated successfully", params.ShellID)
57			return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result), metadata), nil
58		})
59}