f235b8ce90
- Move cmd/agentu/main.go → main.go (project root) - Move internal/llm/ → pkg/llm/ (leaf package, no internal deps) - Move internal/config/ → pkg/config/ (leaf package, no internal deps) - Update all import paths: agentu/internal/llm → agentu/pkg/llm (14 files) - Update all import paths: agentu/internal/config → agentu/pkg/config (3 files) - Update README: build/run commands, CLI flags section - agent/session/tools/tui stay in internal/ (app-specific business logic) Build: go build . | Test: go test ./... | Vet: go vet ./...
81 lines
2.0 KiB
Go
81 lines
2.0 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"agentu/pkg/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
|
|
}
|