1package tools
2
3import (
4 "context"
5 _ "embed"
6 "fmt"
7
8 "charm.land/fantasy"
9 "git.secluded.site/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}