@oh-my-pi/pi-coding-agent 3.20.1 → 3.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -8
- package/docs/custom-tools.md +3 -3
- package/docs/extensions.md +226 -220
- package/docs/hooks.md +2 -2
- package/docs/sdk.md +50 -53
- package/examples/custom-tools/README.md +2 -17
- package/examples/extensions/README.md +76 -74
- package/examples/extensions/todo.ts +2 -5
- package/examples/hooks/custom-compaction.ts +2 -4
- package/examples/hooks/handoff.ts +1 -1
- package/examples/hooks/qna.ts +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/README.md +7 -11
- package/package.json +6 -6
- package/src/cli/args.ts +9 -6
- package/src/cli/file-processor.ts +1 -1
- package/src/cli/list-models.ts +1 -1
- package/src/core/agent-session.ts +16 -5
- package/src/core/auth-storage.ts +1 -1
- package/src/core/compaction/branch-summarization.ts +2 -2
- package/src/core/compaction/compaction.ts +2 -2
- package/src/core/compaction/utils.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/custom-tools/wrapper.ts +0 -1
- package/src/core/extensions/index.ts +1 -6
- package/src/core/extensions/runner.ts +1 -1
- package/src/core/extensions/types.ts +1 -1
- package/src/core/extensions/wrapper.ts +1 -8
- package/src/core/file-mentions.ts +5 -8
- package/src/core/hooks/runner.ts +2 -2
- package/src/core/hooks/types.ts +1 -1
- package/src/core/messages.ts +1 -1
- package/src/core/model-registry.ts +1 -1
- package/src/core/model-resolver.ts +1 -1
- package/src/core/sdk.ts +64 -105
- package/src/core/session-manager.ts +18 -22
- package/src/core/settings-manager.ts +66 -1
- package/src/core/slash-commands.ts +12 -5
- package/src/core/system-prompt.ts +49 -36
- package/src/core/title-generator.ts +2 -2
- package/src/core/tools/ask.ts +98 -4
- package/src/core/tools/bash-interceptor.ts +11 -4
- package/src/core/tools/bash.ts +121 -5
- package/src/core/tools/context.ts +7 -0
- package/src/core/tools/edit-diff.ts +73 -24
- package/src/core/tools/edit.ts +221 -34
- package/src/core/tools/exa/render.ts +4 -16
- package/src/core/tools/find.ts +149 -5
- package/src/core/tools/gemini-image.ts +279 -56
- package/src/core/tools/git.ts +17 -3
- package/src/core/tools/grep.ts +185 -5
- package/src/core/tools/index.test.ts +180 -0
- package/src/core/tools/index.ts +96 -242
- package/src/core/tools/ls.ts +133 -5
- package/src/core/tools/lsp/index.ts +32 -29
- package/src/core/tools/lsp/render.ts +21 -22
- package/src/core/tools/notebook.ts +112 -4
- package/src/core/tools/output.ts +175 -15
- package/src/core/tools/read.ts +127 -25
- package/src/core/tools/render-utils.ts +241 -0
- package/src/core/tools/renderers.ts +40 -828
- package/src/core/tools/review.ts +26 -25
- package/src/core/tools/rulebook.ts +11 -3
- package/src/core/tools/task/agents.ts +28 -7
- package/src/core/tools/task/discovery.ts +0 -6
- package/src/core/tools/task/executor.ts +264 -254
- package/src/core/tools/task/index.ts +48 -208
- package/src/core/tools/task/render.ts +26 -11
- package/src/core/tools/task/types.ts +7 -12
- package/src/core/tools/task/worker-protocol.ts +17 -0
- package/src/core/tools/task/worker.ts +238 -0
- package/src/core/tools/truncate.ts +27 -1
- package/src/core/tools/web-fetch.ts +25 -49
- package/src/core/tools/web-search/index.ts +132 -46
- package/src/core/tools/web-search/providers/anthropic.ts +7 -2
- package/src/core/tools/web-search/providers/exa.ts +2 -1
- package/src/core/tools/web-search/providers/perplexity.ts +6 -1
- package/src/core/tools/web-search/render.ts +6 -4
- package/src/core/tools/web-search/types.ts +13 -0
- package/src/core/tools/write.ts +96 -14
- package/src/core/voice.ts +1 -1
- package/src/discovery/helpers.test.ts +1 -1
- package/src/index.ts +5 -16
- package/src/main.ts +5 -5
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/custom-message.ts +1 -1
- package/src/modes/interactive/components/extensions/inspector-panel.ts +25 -22
- package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
- package/src/modes/interactive/components/footer.ts +1 -1
- package/src/modes/interactive/components/hook-message.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +1 -1
- package/src/modes/interactive/components/oauth-selector.ts +1 -1
- package/src/modes/interactive/components/settings-defs.ts +49 -0
- package/src/modes/interactive/components/status-line.ts +1 -1
- package/src/modes/interactive/components/tool-execution.ts +93 -538
- package/src/modes/interactive/interactive-mode.ts +19 -7
- package/src/modes/interactive/theme/theme.ts +4 -4
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/prompts/system-prompt.md +4 -0
- package/src/prompts/task.md +0 -7
- package/src/prompts/tools/gemini-image.md +5 -1
- package/src/prompts/tools/output.md +6 -2
- package/src/prompts/tools/task.md +68 -0
- package/src/prompts/tools/web-fetch.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/utils/image-convert.ts +8 -2
- package/src/utils/image-magick.ts +247 -0
- package/src/utils/image-resize.ts +53 -13
- package/examples/custom-tools/question/index.ts +0 -84
- package/examples/custom-tools/subagent/README.md +0 -172
- package/examples/custom-tools/subagent/agents/planner.md +0 -37
- package/examples/custom-tools/subagent/agents/scout.md +0 -50
- package/examples/custom-tools/subagent/agents/worker.md +0 -24
- package/examples/custom-tools/subagent/agents.ts +0 -156
- package/examples/custom-tools/subagent/commands/implement-and-review.md +0 -10
- package/examples/custom-tools/subagent/commands/implement.md +0 -10
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +0 -9
- package/examples/custom-tools/subagent/index.ts +0 -1002
- package/examples/sdk/05-tools.ts +0 -94
- package/examples/sdk/12-full-control.ts +0 -95
- package/src/prompts/browser.md +0 -71
package/docs/extensions.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
Extensions are TypeScript modules that extend pi's behavior. They can subscribe to lifecycle events, register custom tools callable by the LLM, add commands, and more.
|
|
6
6
|
|
|
7
7
|
**Key capabilities:**
|
|
8
|
+
|
|
8
9
|
- **Custom tools** - Register tools the LLM can call via `pi.registerTool()`
|
|
9
10
|
- **Event interception** - Block or modify tool calls, inject context, customize compaction
|
|
10
11
|
- **User interaction** - Prompt users via `ctx.ui` (select, confirm, input, notify)
|
|
@@ -14,6 +15,7 @@ Extensions are TypeScript modules that extend pi's behavior. They can subscribe
|
|
|
14
15
|
- **Custom rendering** - Control how tool calls/results and messages appear in TUI
|
|
15
16
|
|
|
16
17
|
**Example use cases:**
|
|
18
|
+
|
|
17
19
|
- Permission gates (confirm before `rm -rf`, `sudo`, etc.)
|
|
18
20
|
- Git checkpointing (stash at each turn, restore on branch)
|
|
19
21
|
- Path protection (block writes to `.env`, `node_modules/`)
|
|
@@ -55,41 +57,41 @@ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
|
55
57
|
import { Type } from "@sinclair/typebox";
|
|
56
58
|
|
|
57
59
|
export default function (pi: ExtensionAPI) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
60
|
+
// React to events
|
|
61
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
62
|
+
ctx.ui.notify("Extension loaded!", "info");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
66
|
+
if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
|
|
67
|
+
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
|
|
68
|
+
if (!ok) return { block: true, reason: "Blocked by user" };
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Register a custom tool
|
|
73
|
+
pi.registerTool({
|
|
74
|
+
name: "greet",
|
|
75
|
+
label: "Greet",
|
|
76
|
+
description: "Greet someone by name",
|
|
77
|
+
parameters: Type.Object({
|
|
78
|
+
name: Type.String({ description: "Name to greet" }),
|
|
79
|
+
}),
|
|
80
|
+
async execute(toolCallId, params, onUpdate, ctx, signal) {
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: "text", text: `Hello, ${params.name}!` }],
|
|
83
|
+
details: {},
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Register a command
|
|
89
|
+
pi.registerCommand("hello", {
|
|
90
|
+
description: "Say hello",
|
|
91
|
+
handler: async (args, ctx) => {
|
|
92
|
+
ctx.ui.notify(`Hello ${args || "world"}!`, "info");
|
|
93
|
+
},
|
|
94
|
+
});
|
|
93
95
|
}
|
|
94
96
|
```
|
|
95
97
|
|
|
@@ -103,12 +105,12 @@ pi -e ./my-extension.ts
|
|
|
103
105
|
|
|
104
106
|
Extensions are auto-discovered from:
|
|
105
107
|
|
|
106
|
-
| Location
|
|
107
|
-
|
|
108
|
-
| `~/.omp/agent/extensions/*.ts`
|
|
109
|
-
| `~/.omp/agent/extensions/*/index.ts` | Global (subdirectory)
|
|
110
|
-
| `.omp/extensions/*.ts`
|
|
111
|
-
| `.omp/extensions/*/index.ts`
|
|
108
|
+
| Location | Scope |
|
|
109
|
+
| ------------------------------------ | ---------------------------- |
|
|
110
|
+
| `~/.omp/agent/extensions/*.ts` | Global (all projects) |
|
|
111
|
+
| `~/.omp/agent/extensions/*/index.ts` | Global (subdirectory) |
|
|
112
|
+
| `.omp/extensions/*.ts` | Project-local |
|
|
113
|
+
| `.omp/extensions/*/index.ts` | Project-local (subdirectory) |
|
|
112
114
|
|
|
113
115
|
Legacy `.pi` directories are supported as aliases for the `.omp` paths above.
|
|
114
116
|
|
|
@@ -116,7 +118,7 @@ Additional paths via `settings.json`:
|
|
|
116
118
|
|
|
117
119
|
```json
|
|
118
120
|
{
|
|
119
|
-
|
|
121
|
+
"extensions": ["/path/to/extension.ts", "/path/to/extension/dir"]
|
|
120
122
|
}
|
|
121
123
|
```
|
|
122
124
|
|
|
@@ -142,17 +144,18 @@ Additional paths via `settings.json`:
|
|
|
142
144
|
```json
|
|
143
145
|
// my-extension-pack/package.json
|
|
144
146
|
{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
147
|
+
"name": "my-extension-pack",
|
|
148
|
+
"dependencies": {
|
|
149
|
+
"zod": "^3.0.0"
|
|
150
|
+
},
|
|
151
|
+
"omp": {
|
|
152
|
+
"extensions": ["./src/safety-gates.ts", "./src/custom-tools.ts"]
|
|
153
|
+
}
|
|
152
154
|
}
|
|
153
155
|
```
|
|
154
156
|
|
|
155
157
|
The `package.json` approach enables:
|
|
158
|
+
|
|
156
159
|
- Multiple extensions from one package
|
|
157
160
|
- Third-party npm dependencies (resolved via jiti)
|
|
158
161
|
- Nested source structure (no depth limit within the package)
|
|
@@ -160,12 +163,12 @@ The `package.json` approach enables:
|
|
|
160
163
|
|
|
161
164
|
## Available Imports
|
|
162
165
|
|
|
163
|
-
| Package
|
|
164
|
-
|
|
166
|
+
| Package | Purpose |
|
|
167
|
+
| --------------------------- | ------------------------------------------------------------ |
|
|
165
168
|
| `@oh-my-pi/pi-coding-agent` | Extension types (`ExtensionAPI`, `ExtensionContext`, events) |
|
|
166
|
-
| `@sinclair/typebox`
|
|
167
|
-
| `@
|
|
168
|
-
| `@oh-my-pi/pi-tui`
|
|
169
|
+
| `@sinclair/typebox` | Schema definitions for tool parameters |
|
|
170
|
+
| `@mariozechner/pi-ai` | AI utilities (`StringEnum` for Google-compatible enums) |
|
|
171
|
+
| `@oh-my-pi/pi-tui` | TUI components for custom rendering |
|
|
169
172
|
|
|
170
173
|
npm dependencies work too. Add a `package.json` next to your extension (or in a parent directory), run `npm install`, and imports from `node_modules/` are resolved automatically.
|
|
171
174
|
|
|
@@ -232,14 +235,14 @@ Extensions are loaded via [jiti](https://github.com/unjs/jiti), so TypeScript wo
|
|
|
232
235
|
```json
|
|
233
236
|
// package.json
|
|
234
237
|
{
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
238
|
+
"name": "my-extension",
|
|
239
|
+
"dependencies": {
|
|
240
|
+
"zod": "^3.0.0",
|
|
241
|
+
"chalk": "^5.0.0"
|
|
242
|
+
},
|
|
243
|
+
"pi": {
|
|
244
|
+
"extensions": ["./src/index.ts"]
|
|
245
|
+
}
|
|
243
246
|
}
|
|
244
247
|
```
|
|
245
248
|
|
|
@@ -304,7 +307,7 @@ Fired on initial session load.
|
|
|
304
307
|
|
|
305
308
|
```typescript
|
|
306
309
|
pi.on("session_start", async (_event, ctx) => {
|
|
307
|
-
|
|
310
|
+
ctx.ui.notify(`Session: ${ctx.sessionManager.getSessionFile() ?? "ephemeral"}`, "info");
|
|
308
311
|
});
|
|
309
312
|
```
|
|
310
313
|
|
|
@@ -314,18 +317,18 @@ Fired when starting a new session (`/new`) or switching sessions (`/resume`).
|
|
|
314
317
|
|
|
315
318
|
```typescript
|
|
316
319
|
pi.on("session_before_switch", async (event, ctx) => {
|
|
317
|
-
|
|
318
|
-
|
|
320
|
+
// event.reason - "new" or "resume"
|
|
321
|
+
// event.targetSessionFile - session we're switching to (only for "resume")
|
|
319
322
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
323
|
+
if (event.reason === "new") {
|
|
324
|
+
const ok = await ctx.ui.confirm("Clear?", "Delete all messages?");
|
|
325
|
+
if (!ok) return { cancel: true };
|
|
326
|
+
}
|
|
324
327
|
});
|
|
325
328
|
|
|
326
329
|
pi.on("session_switch", async (event, ctx) => {
|
|
327
|
-
|
|
328
|
-
|
|
330
|
+
// event.reason - "new" or "resume"
|
|
331
|
+
// event.previousSessionFile - session we came from
|
|
329
332
|
});
|
|
330
333
|
```
|
|
331
334
|
|
|
@@ -335,14 +338,14 @@ Fired when branching via `/branch`.
|
|
|
335
338
|
|
|
336
339
|
```typescript
|
|
337
340
|
pi.on("session_before_branch", async (event, ctx) => {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
341
|
+
// event.entryId - ID of the entry being branched from
|
|
342
|
+
return { cancel: true }; // Cancel branch
|
|
343
|
+
// OR
|
|
344
|
+
return { skipConversationRestore: true }; // Branch but don't rewind messages
|
|
342
345
|
});
|
|
343
346
|
|
|
344
347
|
pi.on("session_branch", async (event, ctx) => {
|
|
345
|
-
|
|
348
|
+
// event.previousSessionFile - previous session file
|
|
346
349
|
});
|
|
347
350
|
```
|
|
348
351
|
|
|
@@ -352,24 +355,24 @@ Fired on compaction. See [compaction.md](compaction.md) for details.
|
|
|
352
355
|
|
|
353
356
|
```typescript
|
|
354
357
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
358
|
+
const { preparation, branchEntries, customInstructions, signal } = event;
|
|
359
|
+
|
|
360
|
+
// Cancel:
|
|
361
|
+
return { cancel: true };
|
|
362
|
+
|
|
363
|
+
// Custom summary:
|
|
364
|
+
return {
|
|
365
|
+
compaction: {
|
|
366
|
+
summary: "...",
|
|
367
|
+
firstKeptEntryId: preparation.firstKeptEntryId,
|
|
368
|
+
tokensBefore: preparation.tokensBefore,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
368
371
|
});
|
|
369
372
|
|
|
370
373
|
pi.on("session_compact", async (event, ctx) => {
|
|
371
|
-
|
|
372
|
-
|
|
374
|
+
// event.compactionEntry - the saved compaction
|
|
375
|
+
// event.fromExtension - whether extension provided it
|
|
373
376
|
});
|
|
374
377
|
```
|
|
375
378
|
|
|
@@ -379,14 +382,14 @@ Fired on `/tree` navigation.
|
|
|
379
382
|
|
|
380
383
|
```typescript
|
|
381
384
|
pi.on("session_before_tree", async (event, ctx) => {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
385
|
+
const { preparation, signal } = event;
|
|
386
|
+
return { cancel: true };
|
|
387
|
+
// OR provide custom summary:
|
|
388
|
+
return { summary: { summary: "...", details: {} } };
|
|
386
389
|
});
|
|
387
390
|
|
|
388
391
|
pi.on("session_tree", async (event, ctx) => {
|
|
389
|
-
|
|
392
|
+
// event.newLeafId, oldLeafId, summaryEntry, fromExtension
|
|
390
393
|
});
|
|
391
394
|
```
|
|
392
395
|
|
|
@@ -396,7 +399,7 @@ Fired on exit (Ctrl+C, Ctrl+D, SIGTERM).
|
|
|
396
399
|
|
|
397
400
|
```typescript
|
|
398
401
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
399
|
-
|
|
402
|
+
// Cleanup, save state, etc.
|
|
400
403
|
});
|
|
401
404
|
```
|
|
402
405
|
|
|
@@ -408,19 +411,19 @@ Fired after user submits prompt, before agent loop. Can inject a message and/or
|
|
|
408
411
|
|
|
409
412
|
```typescript
|
|
410
413
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
414
|
+
// event.prompt - user's prompt text
|
|
415
|
+
// event.images - attached images (if any)
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
// Inject a persistent message (stored in session, sent to LLM)
|
|
419
|
+
message: {
|
|
420
|
+
customType: "my-extension",
|
|
421
|
+
content: "Additional context for the LLM",
|
|
422
|
+
display: true,
|
|
423
|
+
},
|
|
424
|
+
// Append to system prompt for this turn only
|
|
425
|
+
systemPromptAppend: "Extra instructions for this turn...",
|
|
426
|
+
};
|
|
424
427
|
});
|
|
425
428
|
```
|
|
426
429
|
|
|
@@ -432,7 +435,7 @@ Fired once per user prompt.
|
|
|
432
435
|
pi.on("agent_start", async (_event, ctx) => {});
|
|
433
436
|
|
|
434
437
|
pi.on("agent_end", async (event, ctx) => {
|
|
435
|
-
|
|
438
|
+
// event.messages - messages from this prompt
|
|
436
439
|
});
|
|
437
440
|
```
|
|
438
441
|
|
|
@@ -442,11 +445,11 @@ Fired for each turn (one LLM response + tool calls).
|
|
|
442
445
|
|
|
443
446
|
```typescript
|
|
444
447
|
pi.on("turn_start", async (event, ctx) => {
|
|
445
|
-
|
|
448
|
+
// event.turnIndex, event.timestamp
|
|
446
449
|
});
|
|
447
450
|
|
|
448
451
|
pi.on("turn_end", async (event, ctx) => {
|
|
449
|
-
|
|
452
|
+
// event.turnIndex, event.message, event.toolResults
|
|
450
453
|
});
|
|
451
454
|
```
|
|
452
455
|
|
|
@@ -456,9 +459,9 @@ Fired before each LLM call. Modify messages non-destructively.
|
|
|
456
459
|
|
|
457
460
|
```typescript
|
|
458
461
|
pi.on("context", async (event, ctx) => {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
+
// event.messages - deep copy, safe to modify
|
|
463
|
+
const filtered = event.messages.filter((m) => !shouldPrune(m));
|
|
464
|
+
return { messages: filtered };
|
|
462
465
|
});
|
|
463
466
|
```
|
|
464
467
|
|
|
@@ -470,13 +473,13 @@ Fired before tool executes. **Can block.**
|
|
|
470
473
|
|
|
471
474
|
```typescript
|
|
472
475
|
pi.on("tool_call", async (event, ctx) => {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
+
// event.toolName - "bash", "read", "write", "edit", etc.
|
|
477
|
+
// event.toolCallId
|
|
478
|
+
// event.input - tool parameters
|
|
476
479
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
+
if (shouldBlock(event)) {
|
|
481
|
+
return { block: true, reason: "Not allowed" };
|
|
482
|
+
}
|
|
480
483
|
});
|
|
481
484
|
```
|
|
482
485
|
|
|
@@ -521,9 +524,9 @@ Current working directory.
|
|
|
521
524
|
Read-only access to session state:
|
|
522
525
|
|
|
523
526
|
```typescript
|
|
524
|
-
ctx.sessionManager.getEntries()
|
|
525
|
-
ctx.sessionManager.getBranch()
|
|
526
|
-
ctx.sessionManager.getLeafId()
|
|
527
|
+
ctx.sessionManager.getEntries(); // All entries
|
|
528
|
+
ctx.sessionManager.getBranch(); // Current branch
|
|
529
|
+
ctx.sessionManager.getLeafId(); // Current leaf entry ID
|
|
527
530
|
```
|
|
528
531
|
|
|
529
532
|
### ctx.modelRegistry / ctx.model
|
|
@@ -544,10 +547,10 @@ Wait for the agent to finish streaming:
|
|
|
544
547
|
|
|
545
548
|
```typescript
|
|
546
549
|
pi.registerCommand("my-cmd", {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
550
|
+
handler: async (args, ctx) => {
|
|
551
|
+
await ctx.waitForIdle();
|
|
552
|
+
// Agent is now idle, safe to modify session
|
|
553
|
+
},
|
|
551
554
|
});
|
|
552
555
|
```
|
|
553
556
|
|
|
@@ -557,18 +560,18 @@ Create a new session:
|
|
|
557
560
|
|
|
558
561
|
```typescript
|
|
559
562
|
const result = await ctx.newSession({
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
563
|
+
parentSession: ctx.sessionManager.getSessionFile(),
|
|
564
|
+
setup: async (sm) => {
|
|
565
|
+
sm.appendMessage({
|
|
566
|
+
role: "user",
|
|
567
|
+
content: [{ type: "text", text: "Context from previous session..." }],
|
|
568
|
+
timestamp: Date.now(),
|
|
569
|
+
});
|
|
570
|
+
},
|
|
568
571
|
});
|
|
569
572
|
|
|
570
573
|
if (result.cancelled) {
|
|
571
|
-
|
|
574
|
+
// An extension cancelled the new session
|
|
572
575
|
}
|
|
573
576
|
```
|
|
574
577
|
|
|
@@ -579,7 +582,7 @@ Branch from a specific entry:
|
|
|
579
582
|
```typescript
|
|
580
583
|
const result = await ctx.branch("entry-id-123");
|
|
581
584
|
if (!result.cancelled) {
|
|
582
|
-
|
|
585
|
+
// Now in the branched session
|
|
583
586
|
}
|
|
584
587
|
```
|
|
585
588
|
|
|
@@ -589,7 +592,7 @@ Navigate to a different point in the session tree:
|
|
|
589
592
|
|
|
590
593
|
```typescript
|
|
591
594
|
const result = await ctx.navigateTree("entry-id-456", {
|
|
592
|
-
|
|
595
|
+
summarize: true,
|
|
593
596
|
});
|
|
594
597
|
```
|
|
595
598
|
|
|
@@ -605,7 +608,7 @@ Register a custom tool callable by the LLM. See [Custom Tools](#custom-tools) fo
|
|
|
605
608
|
|
|
606
609
|
```typescript
|
|
607
610
|
import { Type } from "@sinclair/typebox";
|
|
608
|
-
import { StringEnum } from "@
|
|
611
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
609
612
|
|
|
610
613
|
pi.registerTool({
|
|
611
614
|
name: "my_tool",
|
|
@@ -649,6 +652,7 @@ pi.sendMessage({
|
|
|
649
652
|
```
|
|
650
653
|
|
|
651
654
|
**Options:**
|
|
655
|
+
|
|
652
656
|
- `deliverAs` - Delivery mode:
|
|
653
657
|
- `"steer"` (default) - Interrupts streaming. Delivered after current tool finishes, remaining tools skipped.
|
|
654
658
|
- `"followUp"` - Waits for agent to finish. Delivered only when agent has no more tool calls.
|
|
@@ -664,11 +668,11 @@ pi.appendEntry("my-state", { count: 42 });
|
|
|
664
668
|
|
|
665
669
|
// Restore on reload
|
|
666
670
|
pi.on("session_start", async (_event, ctx) => {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
671
|
+
for (const entry of ctx.sessionManager.getEntries()) {
|
|
672
|
+
if (entry.type === "custom" && entry.customType === "my-state") {
|
|
673
|
+
// Reconstruct from entry.data
|
|
674
|
+
}
|
|
675
|
+
}
|
|
672
676
|
});
|
|
673
677
|
```
|
|
674
678
|
|
|
@@ -678,11 +682,11 @@ Register a command:
|
|
|
678
682
|
|
|
679
683
|
```typescript
|
|
680
684
|
pi.registerCommand("stats", {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
685
|
+
description: "Show session statistics",
|
|
686
|
+
handler: async (args, ctx) => {
|
|
687
|
+
const count = ctx.sessionManager.getEntries().length;
|
|
688
|
+
ctx.ui.notify(`${count} entries`, "info");
|
|
689
|
+
},
|
|
686
690
|
});
|
|
687
691
|
```
|
|
688
692
|
|
|
@@ -696,10 +700,10 @@ Register a keyboard shortcut:
|
|
|
696
700
|
|
|
697
701
|
```typescript
|
|
698
702
|
pi.registerShortcut("ctrl+shift+p", {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
+
description: "Toggle plan mode",
|
|
704
|
+
handler: async (ctx) => {
|
|
705
|
+
ctx.ui.notify("Toggled!");
|
|
706
|
+
},
|
|
703
707
|
});
|
|
704
708
|
```
|
|
705
709
|
|
|
@@ -709,14 +713,14 @@ Register a CLI flag:
|
|
|
709
713
|
|
|
710
714
|
```typescript
|
|
711
715
|
pi.registerFlag("--plan", {
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
716
|
+
description: "Start in plan mode",
|
|
717
|
+
type: "boolean",
|
|
718
|
+
default: false,
|
|
715
719
|
});
|
|
716
720
|
|
|
717
721
|
// Check value
|
|
718
722
|
if (pi.getFlag("--plan")) {
|
|
719
|
-
|
|
723
|
+
// Plan mode enabled
|
|
720
724
|
}
|
|
721
725
|
```
|
|
722
726
|
|
|
@@ -734,7 +738,7 @@ const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
|
|
|
734
738
|
Manage active tools:
|
|
735
739
|
|
|
736
740
|
```typescript
|
|
737
|
-
const active = pi.getActiveTools();
|
|
741
|
+
const active = pi.getActiveTools(); // ["read", "bash", "edit", "write"]
|
|
738
742
|
pi.setActiveTools(["read", "bash"]); // Switch to read-only
|
|
739
743
|
```
|
|
740
744
|
|
|
@@ -753,31 +757,31 @@ Extensions with state should store it in tool result `details` for proper branch
|
|
|
753
757
|
|
|
754
758
|
```typescript
|
|
755
759
|
export default function (pi: ExtensionAPI) {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
760
|
+
let items: string[] = [];
|
|
761
|
+
|
|
762
|
+
// Reconstruct state from session
|
|
763
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
764
|
+
items = [];
|
|
765
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
766
|
+
if (entry.type === "message" && entry.message.role === "toolResult") {
|
|
767
|
+
if (entry.message.toolName === "my_tool") {
|
|
768
|
+
items = entry.message.details?.items ?? [];
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
pi.registerTool({
|
|
775
|
+
name: "my_tool",
|
|
776
|
+
// ...
|
|
777
|
+
async execute(toolCallId, params, onUpdate, ctx, signal) {
|
|
778
|
+
items.push("new item");
|
|
779
|
+
return {
|
|
780
|
+
content: [{ type: "text", text: "Added" }],
|
|
781
|
+
details: { items: [...items] }, // Store for reconstruction
|
|
782
|
+
};
|
|
783
|
+
},
|
|
784
|
+
});
|
|
781
785
|
}
|
|
782
786
|
```
|
|
783
787
|
|
|
@@ -789,7 +793,7 @@ Register tools the LLM can call via `pi.registerTool()`. Tools appear in the sys
|
|
|
789
793
|
|
|
790
794
|
```typescript
|
|
791
795
|
import { Type } from "@sinclair/typebox";
|
|
792
|
-
import { StringEnum } from "@
|
|
796
|
+
import { StringEnum } from "@mariozechner/pi-ai";
|
|
793
797
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
794
798
|
|
|
795
799
|
pi.registerTool({
|
|
@@ -829,7 +833,7 @@ pi.registerTool({
|
|
|
829
833
|
});
|
|
830
834
|
```
|
|
831
835
|
|
|
832
|
-
**Important:** Use `StringEnum` from `@
|
|
836
|
+
**Important:** Use `StringEnum` from `@mariozechner/pi-ai` for string enums. `Type.Union`/`Type.Literal` doesn't work with Google's API.
|
|
833
837
|
|
|
834
838
|
### Multiple Tools
|
|
835
839
|
|
|
@@ -910,6 +914,7 @@ renderResult(result, { expanded, isPartial }, theme) {
|
|
|
910
914
|
#### Fallback
|
|
911
915
|
|
|
912
916
|
If `renderCall`/`renderResult` is not defined or throws:
|
|
917
|
+
|
|
913
918
|
- `renderCall`: Shows tool name
|
|
914
919
|
- `renderResult`: Shows raw text from `content`
|
|
915
920
|
|
|
@@ -933,7 +938,7 @@ const name = await ctx.ui.input("Name:", "placeholder");
|
|
|
933
938
|
const text = await ctx.ui.editor("Edit:", "prefilled text");
|
|
934
939
|
|
|
935
940
|
// Notification (non-blocking)
|
|
936
|
-
ctx.ui.notify("Done!", "info");
|
|
941
|
+
ctx.ui.notify("Done!", "info"); // "info" | "warning" | "error"
|
|
937
942
|
```
|
|
938
943
|
|
|
939
944
|
### Widgets and Status
|
|
@@ -941,12 +946,12 @@ ctx.ui.notify("Done!", "info"); // "info" | "warning" | "error"
|
|
|
941
946
|
```typescript
|
|
942
947
|
// Status in footer (persistent until cleared)
|
|
943
948
|
ctx.ui.setStatus("my-ext", "Processing...");
|
|
944
|
-
ctx.ui.setStatus("my-ext", undefined);
|
|
949
|
+
ctx.ui.setStatus("my-ext", undefined); // Clear
|
|
945
950
|
|
|
946
951
|
// Widget above editor (string array or factory function)
|
|
947
952
|
ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]);
|
|
948
953
|
ctx.ui.setWidget("my-widget", (tui, theme) => new Text(theme.fg("accent", "Custom"), 0, 0));
|
|
949
|
-
ctx.ui.setWidget("my-widget", undefined);
|
|
954
|
+
ctx.ui.setWidget("my-widget", undefined); // Clear
|
|
950
955
|
|
|
951
956
|
// Terminal title
|
|
952
957
|
ctx.ui.setTitle("pi - my-project");
|
|
@@ -964,23 +969,24 @@ For complex UI, use `ctx.ui.custom()`. This temporarily replaces the editor with
|
|
|
964
969
|
import { Text, Component } from "@oh-my-pi/pi-tui";
|
|
965
970
|
|
|
966
971
|
const result = await ctx.ui.custom<boolean>((tui, theme, done) => {
|
|
967
|
-
|
|
972
|
+
const text = new Text("Press Enter to confirm, Escape to cancel", 1, 1);
|
|
968
973
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
+
text.onKey = (key) => {
|
|
975
|
+
if (key === "return") done(true);
|
|
976
|
+
if (key === "escape") done(false);
|
|
977
|
+
return true;
|
|
978
|
+
};
|
|
974
979
|
|
|
975
|
-
|
|
980
|
+
return text;
|
|
976
981
|
});
|
|
977
982
|
|
|
978
983
|
if (result) {
|
|
979
|
-
|
|
984
|
+
// User pressed Enter
|
|
980
985
|
}
|
|
981
986
|
```
|
|
982
987
|
|
|
983
988
|
The callback receives:
|
|
989
|
+
|
|
984
990
|
- `tui` - TUI instance (for screen dimensions, focus management)
|
|
985
991
|
- `theme` - Current theme for styling
|
|
986
992
|
- `done(value)` - Call to close component and return value
|
|
@@ -995,15 +1001,15 @@ Register a custom renderer for messages with your `customType`:
|
|
|
995
1001
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
996
1002
|
|
|
997
1003
|
pi.registerMessageRenderer("my-extension", (message, options, theme) => {
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1004
|
+
const { expanded } = options;
|
|
1005
|
+
let text = theme.fg("accent", `[${message.customType}] `);
|
|
1006
|
+
text += message.content;
|
|
1001
1007
|
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1008
|
+
if (expanded && message.details) {
|
|
1009
|
+
text += "\n" + theme.fg("dim", JSON.stringify(message.details, null, 2));
|
|
1010
|
+
}
|
|
1005
1011
|
|
|
1006
|
-
|
|
1012
|
+
return new Text(text, 0, 0);
|
|
1007
1013
|
});
|
|
1008
1014
|
```
|
|
1009
1015
|
|
|
@@ -1024,18 +1030,18 @@ All render functions receive a `theme` object:
|
|
|
1024
1030
|
|
|
1025
1031
|
```typescript
|
|
1026
1032
|
// Foreground colors
|
|
1027
|
-
theme.fg("toolTitle", text)
|
|
1028
|
-
theme.fg("accent", text)
|
|
1029
|
-
theme.fg("success", text)
|
|
1030
|
-
theme.fg("error", text)
|
|
1031
|
-
theme.fg("warning", text)
|
|
1032
|
-
theme.fg("muted", text)
|
|
1033
|
-
theme.fg("dim", text)
|
|
1033
|
+
theme.fg("toolTitle", text); // Tool names
|
|
1034
|
+
theme.fg("accent", text); // Highlights
|
|
1035
|
+
theme.fg("success", text); // Success (green)
|
|
1036
|
+
theme.fg("error", text); // Errors (red)
|
|
1037
|
+
theme.fg("warning", text); // Warnings (yellow)
|
|
1038
|
+
theme.fg("muted", text); // Secondary text
|
|
1039
|
+
theme.fg("dim", text); // Tertiary text
|
|
1034
1040
|
|
|
1035
1041
|
// Text styles
|
|
1036
|
-
theme.bold(text)
|
|
1037
|
-
theme.italic(text)
|
|
1038
|
-
theme.strikethrough(text)
|
|
1042
|
+
theme.bold(text);
|
|
1043
|
+
theme.italic(text);
|
|
1044
|
+
theme.strikethrough(text);
|
|
1039
1045
|
```
|
|
1040
1046
|
|
|
1041
1047
|
## Error Handling
|
|
@@ -1046,10 +1052,10 @@ theme.strikethrough(text)
|
|
|
1046
1052
|
|
|
1047
1053
|
## Mode Behavior
|
|
1048
1054
|
|
|
1049
|
-
| Mode
|
|
1050
|
-
|
|
1051
|
-
| Interactive
|
|
1052
|
-
| RPC
|
|
1053
|
-
| Print (`-p`) | No-op
|
|
1055
|
+
| Mode | UI Methods | Notes |
|
|
1056
|
+
| ------------ | ------------- | ------------------------------- |
|
|
1057
|
+
| Interactive | Full TUI | Normal operation |
|
|
1058
|
+
| RPC | JSON protocol | Host handles UI |
|
|
1059
|
+
| Print (`-p`) | No-op | Extensions run but can't prompt |
|
|
1054
1060
|
|
|
1055
1061
|
In print mode, check `ctx.hasUI` before using UI methods.
|