Files
agentu/internal/tools/shell.go
T
loveuer a431d1ec95 v0.0.1: agentu mvp
Add OpenAI-compatible providers, TUI, slash commands, and model switching.
Add local file tools plus optional yolo shell execution.
Document config, usage, and development workflow.
2026-06-22 22:40:38 +08:00

81 lines
2.0 KiB
Go

package tools
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"agentu/internal/llm"
)
type ShellRunTool struct {
workingDir string
}
func NewShellRunTool(workingDir string) *ShellRunTool {
return &ShellRunTool{workingDir: workingDir}
}
func (t *ShellRunTool) Definition() llm.Tool {
return llm.Tool{
Type: "function",
Function: llm.ToolFunction{
Name: "shell_run",
Description: "Run a non-interactive shell command on the local machine. Use for builds, tests, local status inspection, and local automation. Avoid commands that require an interactive TTY, credentials, or open long-running sessions.",
Parameters: JSONSchema(`{
"type": "object",
"properties": {
"command": {"type": "string", "description": "Command to execute."},
"workdir": {"type": "string", "description": "Optional working directory. Relative paths are resolved from the configured agent working directory."}
},
"required": ["command"],
"additionalProperties": false
}`),
},
}
}
type shellRunArgs struct {
Command string `json:"command"`
Workdir string `json:"workdir"`
}
func (t *ShellRunTool) Execute(ctx context.Context, raw json.RawMessage) (string, error) {
var args shellRunArgs
if err := json.Unmarshal(raw, &args); err != nil {
return "", fmt.Errorf("parse arguments: %w", err)
}
if strings.TrimSpace(args.Command) == "" {
return "", errors.New("command is required")
}
workdir, err := resolvePath(t.workingDir, args.Workdir)
if err != nil {
return "", err
}
shell := os.Getenv("SHELL")
if shell == "" {
shell = "/bin/sh"
}
cmd := exec.CommandContext(ctx, shell, "-lc", args.Command)
cmd.Dir = workdir
output, err := cmd.CombinedOutput()
text := FormatResult(string(output))
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return text, fmt.Errorf("command timed out: %w", ctx.Err())
}
if err != nil {
if text != "" {
return text, fmt.Errorf("command failed: %w", err)
}
return "", fmt.Errorf("command failed: %w", err)
}
return text, nil
}