fix(coding-agent): support dynamic tool registration and tool prompt snippets closes #1720

This commit is contained in:
Mario Zechner 2026-03-02 22:32:07 +01:00
parent ca5510158d
commit bc2fa8d6d0
12 changed files with 285 additions and 47 deletions

View file

@ -880,6 +880,12 @@ Subscribe to events. See [Events](#events) for event types and return values.
Register a custom tool callable by the LLM. See [Custom Tools](#custom-tools) for full details.
`pi.registerTool()` works both during extension load and after startup. You can call it inside `session_start`, command handlers, or other event handlers. New tools are refreshed immediately in the same session, so they appear in `pi.getAllTools()` and are callable by the LLM without `/reload`.
Use `pi.setActiveTools()` to enable or disable tools (including dynamically added tools) at runtime.
See [dynamic-tools.ts](../examples/extensions/dynamic-tools.ts) for a full example.
```typescript
import { Type } from "@sinclair/typebox";
import { StringEnum } from "@mariozechner/pi-ai";
@ -888,6 +894,7 @@ pi.registerTool({
name: "my_tool",
label: "My Tool",
description: "What this tool does",
promptSnippet: "Summarize or transform text according to action",
parameters: Type.Object({
action: StringEnum(["list", "add"] as const),
text: Type.Optional(Type.String()),
@ -1116,7 +1123,7 @@ const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
### pi.getActiveTools() / pi.getAllTools() / pi.setActiveTools(names)
Manage active tools.
Manage active tools. This works for both built-in tools and dynamically registered tools.
```typescript
const active = pi.getActiveTools(); // ["read", "bash", "edit", "write"]
@ -1276,6 +1283,8 @@ export default function (pi: ExtensionAPI) {
Register tools the LLM can call via `pi.registerTool()`. Tools appear in the system prompt and can have custom rendering.
Use `promptSnippet` for a short one-line entry in the `Available tools` section in the default system prompt. If omitted, pi falls back to `description`.
Note: Some models are idiots and include the @ prefix in tool path arguments. Built-in tools strip a leading @ before resolving paths. If your custom tool accepts a path, normalize a leading @ as well.
### Tool Definition
@ -1289,6 +1298,7 @@ pi.registerTool({
name: "my_tool",
label: "My Tool",
description: "What this tool does (shown to LLM)",
promptSnippet: "List or add items in the project todo list",
parameters: Type.Object({
action: StringEnum(["list", "add"] as const), // Use StringEnum for Google compatibility
text: Type.Optional(Type.String()),
@ -1886,6 +1896,7 @@ All examples in [examples/extensions/](../examples/extensions/).
| `question.ts` | Tool with user interaction | `registerTool`, `ui.select` |
| `questionnaire.ts` | Multi-step wizard tool | `registerTool`, `ui.custom` |
| `todo.ts` | Stateful tool with persistence | `registerTool`, `appendEntry`, `renderResult`, session events |
| `dynamic-tools.ts` | Register tools after startup and during commands | `registerTool`, `session_start`, `registerCommand` |
| `truncated-tool.ts` | Output truncation example | `registerTool`, `truncateHead` |
| `tool-override.ts` | Override built-in read tool | `registerTool` (same name as built-in) |
| **Commands** |||