a431d1ec95
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.
123 lines
4.5 KiB
Go
123 lines
4.5 KiB
Go
package tools
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"agentu/internal/llm"
|
|
)
|
|
|
|
const MaxToolOutputBytes = 64 * 1024
|
|
|
|
type Tool interface {
|
|
Definition() llm.Tool
|
|
Execute(ctx context.Context, args json.RawMessage) (string, error)
|
|
}
|
|
|
|
type Registry struct {
|
|
tools map[string]Tool
|
|
primary []string
|
|
}
|
|
|
|
func NewRegistry(items ...Tool) *Registry {
|
|
r := &Registry{tools: make(map[string]Tool, len(items))}
|
|
for _, item := range items {
|
|
r.Register(item)
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (r *Registry) Register(tool Tool) {
|
|
name := tool.Definition().Function.Name
|
|
if _, exists := r.tools[name]; !exists {
|
|
r.primary = append(r.primary, name)
|
|
}
|
|
r.tools[name] = tool
|
|
}
|
|
|
|
func (r *Registry) RegisterAlias(alias string, tool Tool) {
|
|
r.tools[alias] = tool
|
|
}
|
|
|
|
func (r *Registry) Get(name string) (Tool, bool) {
|
|
tool, ok := r.tools[name]
|
|
return tool, ok
|
|
}
|
|
|
|
func (r *Registry) Definitions() []llm.Tool {
|
|
defs := make([]llm.Tool, 0, len(r.primary))
|
|
for _, name := range r.primary {
|
|
tool := r.tools[name]
|
|
defs = append(defs, tool.Definition())
|
|
}
|
|
sort.Slice(defs, func(i, j int) bool {
|
|
return defs[i].Function.Name < defs[j].Function.Name
|
|
})
|
|
return defs
|
|
}
|
|
|
|
func ReadOnlyBuiltins(workingDir string) *Registry {
|
|
fileRead := NewFileReadTool(workingDir)
|
|
fileList := NewFileListTool(workingDir)
|
|
fileSearch := NewFileSearchTool(workingDir)
|
|
|
|
registry := NewRegistry(fileRead, fileList, fileSearch)
|
|
registry.RegisterAlias("file.read", fileRead)
|
|
registry.RegisterAlias("file.list", fileList)
|
|
registry.RegisterAlias("file.search", fileSearch)
|
|
return registry
|
|
}
|
|
|
|
func Builtins(workingDir string) *Registry {
|
|
registry := ReadOnlyBuiltins(workingDir)
|
|
fileWrite := NewFileWriteTool(workingDir)
|
|
shellRun := NewShellRunTool(workingDir)
|
|
registry.Register(fileWrite)
|
|
registry.Register(shellRun)
|
|
registry.RegisterAlias("file.write", fileWrite)
|
|
registry.RegisterAlias("shell.run", shellRun)
|
|
return registry
|
|
}
|
|
|
|
func AgentInstructions(workingDir string, yolo bool) string {
|
|
if yolo {
|
|
return fmt.Sprintf(`Local project context:
|
|
- The project working directory is %q.
|
|
- Tools are available, but use them only when needed to satisfy the user's request.
|
|
- Do not call tools for greetings, small talk, simple Q&A, or general questions that do not require local project or command context.
|
|
- Do not explore the project preemptively. Do not call file_list just to discover context unless the user asks about project files or the answer truly depends on local files.
|
|
- When the user asks to inspect, list, search, or read project files, use file_list, file_search, and file_read instead of saying you cannot access local files.
|
|
- When the user asks to run, execute, test, check, debug, or inspect a shell command, use shell_run. This includes local commands written in backticks, such as go, make, git, pwd, ls, ps, and similar terminal commands.
|
|
- Do not use file tools as a substitute for a command execution request.
|
|
- Do not start interactive or long-running sessions. Prefer bounded, non-interactive local commands with clear completion conditions.
|
|
- Tool paths should normally be relative to the project working directory.
|
|
- Available tools: file_list, file_read, file_search, file_write, shell_run.`, workingDir)
|
|
}
|
|
return fmt.Sprintf(`Local project context:
|
|
- The project working directory is %q.
|
|
- Read-only project file tools are available, but use them only when needed to satisfy the user's request.
|
|
- Do not call tools for greetings, small talk, simple Q&A, or general questions that do not require local project context.
|
|
- Do not explore the project preemptively. Do not call file_list just to discover context unless the user asks about project files or the answer truly depends on local files.
|
|
- When the user asks to inspect, list, search, or read project files, use file_list, file_search, and file_read instead of saying you cannot access local files.
|
|
- Shell command execution is disabled in this mode. When the user asks to run, execute, test, check, debug, or inspect a shell command, do not use file tools as a substitute; explain that they need to restart agentu with --yolo to enable shell_run.
|
|
- Tool paths should normally be relative to the project working directory.
|
|
- Available tools: file_list, file_read, file_search.`, workingDir)
|
|
}
|
|
|
|
func JSONSchema(schema string) json.RawMessage {
|
|
return json.RawMessage(schema)
|
|
}
|
|
|
|
func FormatResult(output string) string {
|
|
if len(output) <= MaxToolOutputBytes {
|
|
return output
|
|
}
|
|
return output[:MaxToolOutputBytes] + fmt.Sprintf("\n\n[truncated after %d bytes]", MaxToolOutputBytes)
|
|
}
|
|
|
|
func FormatError(err error) string {
|
|
return "error: " + err.Error()
|
|
}
|