diff --git a/openspec/changes/agent-tool-use/design.md b/openspec/changes/agent-tool-use/design.md new file mode 100644 index 0000000..5729c5b --- /dev/null +++ b/openspec/changes/agent-tool-use/design.md @@ -0,0 +1,174 @@ +# Design: Agent Tool Use + +## Architecture + +``` +User prompt + ↓ +LLM (with tools in system prompt) + ↓ +Response contains tool_calls? ──yes──→ Execute tools ──→ Feed results back to LLM ──→ loop + │ │ + no ↓ + ↓ Show results in TUI +Final text response +``` + +## Tool Protocol + +Use OpenAI function calling format (compatible with DMR): + +```json +{ + "tools": [ + { + "type": "function", + "function": { + "name": "web_search", + "description": "Search the web for information", + "parameters": { + "type": "object", + "properties": { + "query": { "type": "string", "description": "Search query" } + }, + "required": ["query"] + } + } + } + ] +} +``` + +When the LLM wants to call a tool, it returns: +```json +{ + "choices": [{ + "delta": { + "content": null, + "tool_calls": [{ + "id": "call_abc123", + "type": "function", + "function": { + "name": "web_search", + "arguments": "{\"query\": \"next.js app router setup\"}" + } + }] + } + }] +} +``` + +## Tools + +### Read-only (no confirmation) +- `web_search(query)` — DuckDuckGo Lite HTML scraping (no API key needed) +- `read_file(path)` — Read file contents +- `list_files(path)` — List directory entries +- `get_webpage(url)` — Fetch and extract text from a URL + +### Destructive (require confirmation) +- `write_file(path, content)` — Write/create a file +- `shell_exec(command)` — Execute a shell command + +## Streaming Changes + +Current `streamChat` yields `{ type: "thinking" | "content", text }`. + +New yield type: +```typescript +type StreamChunk = + | { type: "thinking"; text: string } + | { type: "content"; text: string } + | { type: "tool_call"; id: string; name: string; arguments: string } +``` + +Tool calls accumulate across chunks (arguments stream in). When the stream ends with tool calls, the event loop executes them. + +## Event Loop (in tui-main.ts) + +``` +while true: + response = streamChat(messages, model, tools) + if response has tool_calls: + for each tool_call: + show in timeline: "🔧 calling web_search..." + result = executeTool(tool_call) + show in timeline: "✓ result: ..." + messages.push({ role: "tool", tool_call_id, content: result }) + continue loop + else: + show final text response + break +``` + +## Tool Sources + +Tools come from 3 sources (in priority order): + +1. **Bundled** — core tools compiled into the binary (`src/tools/`) +2. **Project** — `.paikea/tools/*.tool.json` files in the project root +3. **User** — `~/.config/paikea/tools/*.tool.json` global tools + +### Custom Tool Format + +`.paikea/tools/` contains JSON files defining tools: + +```json +{ + "name": "search_npm", + "description": "Search npm packages", + "parameters": { + "type": "object", + "properties": { + "query": { "type": "string", "description": "Package name or keywords" } + }, + "required": ["query"] + }, + "handler": "shell", + "command": "npm search {{query}} --json | head -20", + "sandbox": true +} +``` + +Handler types: +- `"shell"` — run `command` with parameter interpolation (`{{param}}`) +- `"http"` — fetch `url` with method/body interpolation +- `"js"` — execute a `.js` file from `.paikea/tools/handlers/` + +### Tool Registry + +``` +src/tools/ +├── registry.ts # loads from bundled + .paikea/tools/ + ~/.config/paikea/tools/ +├── web-search.ts # bundled: web_search +├── file-ops.ts # bundled: read_file, write_file, list_files +├── shell-exec.ts # bundled: shell_exec +└── webpage.ts # bundled: get_webpage +``` + +## Safety Model + +- Read-only tools: execute immediately +- Destructive tools: show confirmation prompt (`@clack/prompts confirm`) +- Shell exec: show command, require Enter to confirm +- Write file: show path + first 20 lines, require Enter to confirm +- Custom tools with `"sandbox": true`: validate paths stay within project root + +## TUI Timeline Changes + +New message types in timeline: +``` +🔧 web_search("next.js app router setup") + → Found 5 results... +📝 write_file("src/app/page.tsx") + → Created (142 lines) +💻 shell_exec("bun install") + → Done (exit 0) +``` + +## Limitations + +- DuckDuckGo scraping may break if they change their HTML +- Shell exec runs in the project directory (sandboxed by CWD) +- No streaming of tool results back to LLM (batch only) +- Tool call arguments may be truncated by context window limits diff --git a/openspec/changes/agent-tool-use/proposal.md b/openspec/changes/agent-tool-use/proposal.md new file mode 100644 index 0000000..4d27d1d --- /dev/null +++ b/openspec/changes/agent-tool-use/proposal.md @@ -0,0 +1,23 @@ +# Agent Tool Use + +## Problem + +Paikea is a TUI client that sends prompts to a local LLM via Docker Model Runner, but it has no ability to take autonomous actions. When the LLM doesn't know how to set up a framework, it can only generate text — it can't search the web, read files, or execute commands to learn and act. + +## Solution + +Add a tool use (function calling) layer that lets the LLM request actions during a conversation. The LLM returns structured tool calls instead of (or alongside) text, paikea executes them, and feeds results back to the LLM in a loop. + +## Scope + +- Tool protocol (OpenAI function calling format, supported by DMR) +- Core tools: web search, file read/write, directory listing, shell execution +- Custom tool loading from `.paikea/tools/*.tool.json` and `~/.config/paikea/tools/` +- Event loop: parse tool calls → execute → return results → continue +- TUI integration: show tool calls/results in timeline +- Safety: confirmation prompt before destructive actions (write, shell exec) + +## Out of scope + +- Multi-agent orchestration +- Persistent memory across sessions diff --git a/openspec/changes/agent-tool-use/tasks.md b/openspec/changes/agent-tool-use/tasks.md new file mode 100644 index 0000000..a3ad26d --- /dev/null +++ b/openspec/changes/agent-tool-use/tasks.md @@ -0,0 +1,52 @@ +# Tasks: Agent Tool Use + +## Phase 1: Tool Infrastructure + +- [ ] Define tool types and interfaces in `src/types.ts` +- [ ] Create `src/services/tool-registry.ts` — tool definitions + JSON schemas +- [ ] Create `src/services/tool-executor.ts` — dispatch tool calls to handlers + +## Phase 2: Core Tools + +- [ ] Implement `web_search` — scrape DuckDuckGo Lite HTML +- [ ] Implement `get_webpage` — fetch URL, extract text (strip HTML) +- [ ] Implement `read_file` — read file with path validation +- [ ] Implement `list_files` — list directory with .gitignore awareness +- [ ] Implement `write_file` — write file with confirmation prompt +- [ ] Implement `shell_exec` — exec with confirmation prompt + timeout + +## Phase 2b: Custom Tool Loading + +- [ ] Create `src/tools/registry.ts` — load from bundled + `.paikea/tools/` + `~/.config/paikea/tools/` +- [ ] Define `.tool.json` schema (name, description, parameters, handler, command/url, sandbox) +- [ ] Implement shell handler — `{{param}}` interpolation + command execution +- [ ] Implement http handler — fetch with method/body interpolation +- [ ] Implement js handler — load `.paikea/tools/handlers/*.js` +- [ ] Path validation for sandboxed tools — reject paths outside project root +- [ ] Tool override priority — project tools override bundled, user tools override project + +## Phase 3: LLM Integration + +- [ ] Update `dmr-client.ts` — pass tools array to API, parse tool_calls in stream +- [ ] Update `StreamChunk` type to include `tool_call` variant +- [ ] Accumulate streamed tool call arguments across chunks + +## Phase 4: Event Loop + +- [ ] Implement tool loop in `tui-main.ts` — detect tool_calls → execute → re-prompt +- [ ] Add max iterations guard (prevent infinite loops, default: 10) +- [ ] Show tool execution progress in TUI timeline + +## Phase 5: TUI & UX + +- [ ] Add tool call rendering in timeline component +- [ ] Add tool result rendering in timeline component +- [ ] Confirmation prompts for destructive tools (write_file, shell_exec) +- [ ] Status bar update: show tool count during execution + +## Phase 6: Safety + +- [ ] Path validation — reject paths outside project root +- [ ] Shell command blocklist — block `rm -rf /`, `sudo`, etc. +- [ ] File write sandboxing — only within project directory +- [ ] Tool call logging to session file