@aliou/pi-dev-kit 0.6.1 → 0.6.3
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/README.md +3 -1
- package/package.json +1 -1
- package/src/skills/pi-extension/SKILL.md +8 -5
- package/src/skills/pi-extension/references/additional-apis.md +5 -3
- package/src/skills/pi-extension/references/providers.md +118 -93
- package/src/skills/pi-extension/references/structure.md +5 -2
- package/src/skills/pi-extension/references/tools.md +94 -9
- package/src/tools/changelog-tool.ts +2 -2
- package/src/tools/docs-tool.ts +2 -2
- package/src/tools/package-manager-tool.ts +2 -2
- package/src/tools/version-tool.ts +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
1
3
|
# Pi Dev Kit
|
|
2
4
|
|
|
3
5
|
Tools and commands for building, maintaining, and updating Pi extensions.
|
|
@@ -57,4 +59,4 @@ Parses the Pi changelog and returns entries for a specific version (or the lates
|
|
|
57
59
|
|
|
58
60
|
## Compatibility
|
|
59
61
|
|
|
60
|
-
Compatible with Pi 0.50.x and 0.51.0+. Tools that need the extension context use a runtime shim to handle the execute signature difference between versions.
|
|
62
|
+
Compatible with Pi 0.50.x and 0.51.0+. Tools that need the extension context use a runtime shim to handle the execute signature difference between versions.
|
package/package.json
CHANGED
|
@@ -53,7 +53,7 @@ import { Container, Markdown, Text } from "@mariozechner/pi-tui";
|
|
|
53
53
|
6. If the extension tracks state: Read `references/state.md`.
|
|
54
54
|
7. For less common APIs: Read `references/additional-apis.md`.
|
|
55
55
|
8. If the extension has user-configurable settings: Use `registerSettingsCommand` from `@aliou/pi-utils-settings`. Read `references/structure.md` for settings command and auth wizard patterns.
|
|
56
|
-
9. If the extension adds a tool that competes with a natural bash fallback: use `promptSnippet` and `promptGuidelines` on the tool definition for simple guidance. Use system prompt hooks only for complex cross-tool orchestration. Read the **Guidance** section in `references/additional-apis.md`.
|
|
56
|
+
9. If the extension adds a tool that competes with a natural bash fallback: use `promptSnippet` and `promptGuidelines` on the tool definition for simple guidance. Write `promptGuidelines` as standalone bullets that name the exact tool, because pi injects them verbatim into the shared global `Guidelines` section. Use system prompt hooks only for complex cross-tool orchestration. Read the **Guidance** section in `references/additional-apis.md`.
|
|
57
57
|
10. Before publishing: Read `references/publish.md` and `references/documentation.md`.
|
|
58
58
|
|
|
59
59
|
### Modifying an Existing Extension
|
|
@@ -68,11 +68,11 @@ import { Container, Markdown, Text } from "@mariozechner/pi-tui";
|
|
|
68
68
|
| File | Content |
|
|
69
69
|
|---|---|
|
|
70
70
|
| `references/structure.md` | Project layout, package.json, tsconfig, biome.json, config.ts, entry point patterns (including acceptable exceptions), API key pattern, imports |
|
|
71
|
-
| `references/tools.md` | Tool registration, execute signature, parameters, streaming, rendering, naming, renderCall/renderResult UI guidelines |
|
|
71
|
+
| `references/tools.md` | Tool registration, execute signature, parameters, `prepareArguments`, path normalization, file mutation queueing, streaming, rendering, naming, renderCall/renderResult UI guidelines |
|
|
72
72
|
| `references/hooks.md` | Events, blocking/cancelling, input transformation, system prompt modification, bash spawn hooks (command rewriting) |
|
|
73
73
|
| `references/commands.md` | Command registration, three-tier pattern, component extraction |
|
|
74
74
|
| `references/components.md` | TUI components (pi-tui + pi-coding-agent), custom(), theme styling, keyboard handling |
|
|
75
|
-
| `references/providers.md` |
|
|
75
|
+
| `references/providers.md` | Current `pi.registerProvider(name, config)` API, model definition, provider override/registration patterns, API key gating |
|
|
76
76
|
| `references/modes.md` | Mode behavior matrix, ctx.hasUI, dialog vs fire-and-forget, three-tier pattern |
|
|
77
77
|
| `references/messages.md` | sendMessage, registerMessageRenderer, notify, when to use each |
|
|
78
78
|
| `references/state.md` | appendEntry, state reconstruction, appendEntry vs sendMessage |
|
|
@@ -105,7 +105,7 @@ When implementing, look at these existing extensions for patterns:
|
|
|
105
105
|
9. **Long args placement**: Put long prompt/task/question/context strings on following lines. Keep first line scannable.
|
|
106
106
|
10. **Result layout**: In `renderResult(result, options, theme)`, handle `isPartial` first with a stable tool-scoped message. Detect errors by checking for missing expected fields in `details` (framework sets `details: {}` on throw). Use `ToolBody` from `@aliou/pi-utils-ui` with `showCollapsed` fields. Use `ToolFooter` conditionally (omit when empty). Use `Container`/`Markdown` for rich content.
|
|
107
107
|
11. **Typed param alias**: Define `type MyToolParams = Static<typeof parameters>` at the top of each tool file. Use it everywhere instead of repeating `Static<typeof parameters>`.
|
|
108
|
-
12. **Tool metadata**: Every tool must have `label` (required). Add `promptSnippet` for system prompt tool listing. Add `promptGuidelines` for usage instructions. These replace system-prompt hooks for simple tools.
|
|
108
|
+
12. **Tool metadata**: Every tool must have `label` (required). Add `promptSnippet` for system prompt tool listing. Add `promptGuidelines` for usage instructions, but write them as standalone global bullets that name the exact tool. These replace system-prompt hooks for simple tools.
|
|
109
109
|
13. **Output truncation**: For tools returning large text, use `truncateHead()` from `@mariozechner/pi-coding-agent`. Write full content to temp file. Append footer with line/byte counts and temp file path.
|
|
110
110
|
14. **Core/lib pattern**: Extract domain logic into modules (`client.ts`, `manager.ts`) that don't import from Pi. Tools are thin wrappers. Core modules are unit-testable with vitest.
|
|
111
111
|
15. **Humanize messages**: Show display names first, IDs in dim/parens. `"Started \"backend\" (proc_42)"` not `"Started proc_42"`.
|
|
@@ -133,9 +133,12 @@ Before considering an extension complete:
|
|
|
133
133
|
- [ ] `renderResult` uses `ToolBody` with `showCollapsed` fields.
|
|
134
134
|
- [ ] `renderResult` uses `ToolFooter` conditionally (omits when empty).
|
|
135
135
|
- [ ] Every tool has `label` field.
|
|
136
|
-
- [ ] Tools have `promptSnippet` and/or `promptGuidelines` when appropriate
|
|
136
|
+
- [ ] Tools have `promptSnippet` and/or `promptGuidelines` when appropriate, and `promptGuidelines` bullets name the exact tool instead of saying `this tool`.
|
|
137
137
|
- [ ] Large output tools use `truncateHead()` + temp file pattern.
|
|
138
138
|
- [ ] Domain logic is extracted to testable core modules.
|
|
139
|
+
- [ ] File-mutating custom tools use `withFileMutationQueue()` for the full read-modify-write window.
|
|
140
|
+
- [ ] Path-taking custom tools normalize a leading `@` before resolving paths.
|
|
141
|
+
- [ ] Tool overrides re-declare `promptSnippet`/`promptGuidelines` if they need inherited prompt behavior.
|
|
139
142
|
- [ ] `ctx.ui.custom()` calls have RPC fallback, and interactive close/cancel paths do not rely on `done(undefined)` when fallback detection uses `result === undefined`.
|
|
140
143
|
- [ ] `tool_call` hooks check `ctx.hasUI` before dialog methods.
|
|
141
144
|
- [ ] Fire-and-forget methods (notify, setStatus, etc.) are used without hasUI guards.
|
|
@@ -136,7 +136,7 @@ There are two ways to inject guidance, depending on complexity:
|
|
|
136
136
|
For most tools, use the SDK-level `promptSnippet` and `promptGuidelines` fields directly on the tool definition. No hook is needed.
|
|
137
137
|
|
|
138
138
|
- **`promptSnippet`** — Injected into the "Available tools" system prompt section. Use for a concise (1–2 sentence) description of when to prefer this tool.
|
|
139
|
-
- **`promptGuidelines`** — Appended to the "Guidelines" section. Use for a short list of usage rules.
|
|
139
|
+
- **`promptGuidelines`** — Appended verbatim to the global "Guidelines" section. Use for a short list of usage rules that still make sense without extra tool-local context.
|
|
140
140
|
|
|
141
141
|
```typescript
|
|
142
142
|
const myTool = {
|
|
@@ -145,8 +145,8 @@ const myTool = {
|
|
|
145
145
|
description: "...",
|
|
146
146
|
promptSnippet: "Manage background processes without blocking the conversation.",
|
|
147
147
|
promptGuidelines: [
|
|
148
|
-
"Use
|
|
149
|
-
"After starting
|
|
148
|
+
"Use my_tool for long-running commands instead of bash.",
|
|
149
|
+
"After starting my_tool, continue other work instead of waiting.",
|
|
150
150
|
],
|
|
151
151
|
parameters: ...,
|
|
152
152
|
execute: ...,
|
|
@@ -155,6 +155,8 @@ const myTool = {
|
|
|
155
155
|
|
|
156
156
|
This is the simplest approach and works well when guidance is specific to a single tool.
|
|
157
157
|
|
|
158
|
+
Because these bullets are merged into the shared global `Guidelines` section, avoid vague phrasing like `Use this tool...`. Name the exact tool (`my_tool`, `process`, `linkup_web_search`) so the bullet remains clear after injection.
|
|
159
|
+
|
|
158
160
|
#### Tier 2: System Prompt Hook (For Complex Cross-Tool Orchestration)
|
|
159
161
|
|
|
160
162
|
Use the `before_agent_start` hook when:
|
|
@@ -1,134 +1,159 @@
|
|
|
1
1
|
# Providers
|
|
2
2
|
|
|
3
|
-
Providers add LLM backends to pi. They connect pi to model APIs
|
|
3
|
+
Providers add LLM backends to pi. They connect pi to model APIs, proxies, gateways, and custom streaming implementations.
|
|
4
|
+
|
|
5
|
+
This reference tracks the current `pi.registerProvider(name, config)` API from pi-mono. For full provider details and advanced examples, also read pi-mono `packages/coding-agent/docs/custom-provider.md`.
|
|
4
6
|
|
|
5
7
|
## Registration
|
|
6
8
|
|
|
7
9
|
```typescript
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
compat: {
|
|
26
|
-
type: "openai-completions",
|
|
27
|
-
maxTokensField: "max_tokens",
|
|
28
|
-
supportsDeveloperRole: false,
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
];
|
|
32
|
-
},
|
|
33
|
-
apiKey: () => process.env.MY_API_KEY,
|
|
34
|
-
baseUrl: () => "https://api.my-provider.com/v1",
|
|
10
|
+
import type { ExtensionAPI, ProviderConfig } from "@mariozechner/pi-coding-agent";
|
|
11
|
+
|
|
12
|
+
const myProviderConfig: ProviderConfig = {
|
|
13
|
+
baseUrl: "https://api.example.com/v1",
|
|
14
|
+
apiKey: "MY_API_KEY",
|
|
15
|
+
api: "openai-completions",
|
|
16
|
+
models: [
|
|
17
|
+
{
|
|
18
|
+
id: "my-model",
|
|
19
|
+
name: "My Model",
|
|
20
|
+
reasoning: false,
|
|
21
|
+
input: ["text", "image"],
|
|
22
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
23
|
+
contextWindow: 128000,
|
|
24
|
+
maxTokens: 4096,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
35
27
|
};
|
|
36
28
|
|
|
37
29
|
export default function (pi: ExtensionAPI) {
|
|
38
|
-
pi.registerProvider(
|
|
30
|
+
pi.registerProvider("my-provider", myProviderConfig);
|
|
39
31
|
}
|
|
40
32
|
```
|
|
41
33
|
|
|
42
|
-
##
|
|
34
|
+
## Common Registration Patterns
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|---|---|---|
|
|
46
|
-
| `name` | `string` | Unique provider identifier. Used as prefix in model IDs. |
|
|
47
|
-
| `models` | `() => ProviderModelConfig[]` | Returns available models. Called when pi needs the model list. Return `[]` if the API key is missing. |
|
|
48
|
-
| `apiKey` | `() => string \| undefined` | Returns the API key. Pi calls this when making requests. |
|
|
49
|
-
| `baseUrl` | `() => string \| undefined` | Returns the base URL for the API. |
|
|
36
|
+
### Override an existing provider
|
|
50
37
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
## Model Definition
|
|
38
|
+
```typescript
|
|
39
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
54
40
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
| `contextLength` | `number` | Maximum context window in tokens. |
|
|
62
|
-
| `maxOutputTokens` | `number` | Maximum output tokens per response. |
|
|
63
|
-
| `pricing` | `object` | `{ inputPerMillion, outputPerMillion }` in USD. Used for cost display. |
|
|
64
|
-
| `compat` | `object` | OpenAI compatibility settings. See below. |
|
|
41
|
+
export default function (pi: ExtensionAPI) {
|
|
42
|
+
pi.registerProvider("anthropic", {
|
|
43
|
+
baseUrl: "https://proxy.example.com",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
```
|
|
65
47
|
|
|
66
|
-
|
|
48
|
+
Use this when you want to keep the built-in provider and model list, but change the endpoint and/or headers.
|
|
67
49
|
|
|
68
|
-
|
|
50
|
+
### Register a new provider
|
|
69
51
|
|
|
70
52
|
```typescript
|
|
71
|
-
|
|
72
|
-
type: "openai-completions",
|
|
73
|
-
|
|
74
|
-
// Which field name the API uses for max output tokens
|
|
75
|
-
maxTokensField: "max_tokens" | "max_completion_tokens",
|
|
53
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
76
54
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
55
|
+
export default function (pi: ExtensionAPI) {
|
|
56
|
+
pi.registerProvider("my-provider", {
|
|
57
|
+
baseUrl: "https://api.example.com/v1",
|
|
58
|
+
apiKey: "MY_API_KEY",
|
|
59
|
+
api: "openai-completions",
|
|
60
|
+
models: [
|
|
61
|
+
{
|
|
62
|
+
id: "my-model",
|
|
63
|
+
name: "My Model",
|
|
64
|
+
reasoning: false,
|
|
65
|
+
input: ["text"],
|
|
66
|
+
cost: { input: 0.5, output: 1.5, cacheRead: 0, cacheWrite: 0 },
|
|
67
|
+
contextWindow: 128000,
|
|
68
|
+
maxTokens: 8192,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
```
|
|
82
74
|
|
|
83
|
-
|
|
84
|
-
supportsReasoningEffort: boolean,
|
|
75
|
+
### Unregister a provider
|
|
85
76
|
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
```typescript
|
|
78
|
+
pi.unregisterProvider("my-provider");
|
|
79
|
+
```
|
|
88
80
|
|
|
89
|
-
|
|
90
|
-
requiresToolResultName: boolean,
|
|
81
|
+
This takes effect immediately at runtime.
|
|
91
82
|
|
|
92
|
-
|
|
93
|
-
requiresAssistantAfterToolResult: boolean,
|
|
83
|
+
## ProviderConfig Fields
|
|
94
84
|
|
|
95
|
-
|
|
96
|
-
|
|
85
|
+
| Field | Type | Description |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| `baseUrl` | `string` | Base URL for the provider or proxy. |
|
|
88
|
+
| `headers` | `Record<string, string>` | Optional static headers to add to requests. |
|
|
89
|
+
| `apiKey` | `string` | Environment variable name containing the API key. |
|
|
90
|
+
| `api` | `"openai-completions" \| "openai-responses"` | Compatibility mode for request/response handling. |
|
|
91
|
+
| `models` | `ProviderModelConfig[]` | Model definitions exposed by this provider. |
|
|
92
|
+
| `streamSimple` | `function` | Optional custom streaming implementation for non-standard APIs. |
|
|
93
|
+
| `oauth` | `object` | Optional OAuth config for providers that need browser-based auth. |
|
|
97
94
|
|
|
98
|
-
|
|
99
|
-
requiresMistralToolIds: boolean,
|
|
95
|
+
Use the built-in OpenAI-compatible path when possible. Reach for `streamSimple` only when the upstream API is not compatible enough.
|
|
100
96
|
|
|
101
|
-
|
|
102
|
-
thinkingFormat: "openai" | "zai" | "qwen",
|
|
97
|
+
## Model Definition
|
|
103
98
|
|
|
104
|
-
|
|
105
|
-
openRouterRouting: object,
|
|
99
|
+
The exact model type has more fields, but these are the ones you will usually need:
|
|
106
100
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
| Field | Type | Description |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `id` | `string` | Model identifier within the provider config. |
|
|
104
|
+
| `name` | `string` | Display name shown in model selection UI. |
|
|
105
|
+
| `reasoning` | `boolean` | Whether the model is a reasoning model. |
|
|
106
|
+
| `input` | `Array<"text" \| "image" \| "audio" \| "pdf">` | Input modalities supported by the model. |
|
|
107
|
+
| `cost` | `object` | `{ input, output, cacheRead, cacheWrite }` cost values. |
|
|
108
|
+
| `contextWindow` | `number` | Maximum context window. |
|
|
109
|
+
| `maxTokens` | `number` | Maximum output tokens. |
|
|
111
110
|
|
|
112
|
-
|
|
111
|
+
## API Key Gating
|
|
113
112
|
|
|
114
|
-
|
|
113
|
+
Provider registration and extension tool registration are separate concerns.
|
|
115
114
|
|
|
116
|
-
|
|
115
|
+
For providers:
|
|
116
|
+
- Register the provider with `pi.registerProvider(name, config)`.
|
|
117
|
+
- Point `apiKey` at the environment variable name that holds the credential.
|
|
118
|
+
- If the provider should exist even when tools are disabled, still register it.
|
|
117
119
|
|
|
118
|
-
|
|
120
|
+
For tools and commands that require the same credential:
|
|
121
|
+
- Gate those registrations separately in your extension entry point.
|
|
119
122
|
|
|
120
123
|
```typescript
|
|
121
124
|
export default function (pi: ExtensionAPI) {
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
pi.registerProvider("my-provider", {
|
|
126
|
+
baseUrl: "https://api.example.com/v1",
|
|
127
|
+
apiKey: "MY_API_KEY",
|
|
128
|
+
api: "openai-completions",
|
|
129
|
+
models: [
|
|
130
|
+
{
|
|
131
|
+
id: "my-model",
|
|
132
|
+
name: "My Model",
|
|
133
|
+
reasoning: false,
|
|
134
|
+
input: ["text"],
|
|
135
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
136
|
+
contextWindow: 128000,
|
|
137
|
+
maxTokens: 4096,
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
});
|
|
124
141
|
|
|
125
|
-
|
|
126
|
-
if (!apiKey) return;
|
|
142
|
+
if (!process.env.MY_API_KEY) return;
|
|
127
143
|
|
|
128
|
-
|
|
129
|
-
pi.
|
|
130
|
-
pi.registerCommand(createQuotasCommand(apiKey));
|
|
144
|
+
pi.registerTool(mySearchTool);
|
|
145
|
+
pi.registerCommand("quota", { handler: showQuota });
|
|
131
146
|
}
|
|
132
147
|
```
|
|
133
148
|
|
|
134
|
-
|
|
149
|
+
That pattern keeps provider setup accurate while still hiding tools that cannot work without credentials.
|
|
150
|
+
|
|
151
|
+
## When to read the upstream docs
|
|
152
|
+
|
|
153
|
+
Also read pi-mono `packages/coding-agent/docs/custom-provider.md` when you need:
|
|
154
|
+
- custom streaming via `streamSimple`
|
|
155
|
+
- OAuth support
|
|
156
|
+
- proxying existing providers
|
|
157
|
+
- header injection
|
|
158
|
+
- provider teardown with `pi.unregisterProvider()`
|
|
159
|
+
- advanced model config details
|
|
@@ -79,12 +79,14 @@ Not every extension needs every directory. A simple extension with one tool migh
|
|
|
79
79
|
"peerDependencies": {
|
|
80
80
|
"@mariozechner/pi-coding-agent": ">=CURRENT_VERSION",
|
|
81
81
|
"@mariozechner/pi-ai": ">=CURRENT_VERSION",
|
|
82
|
-
"@mariozechner/pi-tui": ">=CURRENT_VERSION"
|
|
82
|
+
"@mariozechner/pi-tui": ">=CURRENT_VERSION",
|
|
83
|
+
"@sinclair/typebox": ">=0.34.0"
|
|
83
84
|
},
|
|
84
85
|
"peerDependenciesMeta": {
|
|
85
86
|
"@mariozechner/pi-coding-agent": { "optional": true },
|
|
86
87
|
"@mariozechner/pi-ai": { "optional": true },
|
|
87
|
-
"@mariozechner/pi-tui": { "optional": true }
|
|
88
|
+
"@mariozechner/pi-tui": { "optional": true },
|
|
89
|
+
"@sinclair/typebox": { "optional": true }
|
|
88
90
|
},
|
|
89
91
|
"devDependencies": {
|
|
90
92
|
"@aliou/biome-plugins": "^0.3.0",
|
|
@@ -93,6 +95,7 @@ Not every extension needs every directory. A simple extension with one tool migh
|
|
|
93
95
|
"@mariozechner/pi-ai": "CURRENT_VERSION",
|
|
94
96
|
"@mariozechner/pi-coding-agent": "CURRENT_VERSION",
|
|
95
97
|
"@mariozechner/pi-tui": "CURRENT_VERSION",
|
|
98
|
+
"@sinclair/typebox": "0.34.41",
|
|
96
99
|
"@types/node": "^25.0.0",
|
|
97
100
|
"husky": "^9.0.0",
|
|
98
101
|
"typescript": "^5.8.0"
|
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
} from "@mariozechner/pi-coding-agent";
|
|
19
19
|
import { getMarkdownTheme, keyHint, truncateHead, formatSize } from "@mariozechner/pi-coding-agent";
|
|
20
20
|
import { Container, Markdown, Text } from "@mariozechner/pi-tui";
|
|
21
|
-
import { type Static, Type } from "@
|
|
21
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
## Registration
|
|
@@ -29,9 +29,9 @@ const myTool = {
|
|
|
29
29
|
label: "My Tool", // Required: human-readable name for UI
|
|
30
30
|
description: "What this tool does. The LLM reads this to decide when to call it.",
|
|
31
31
|
promptSnippet: "Search for items by query", // One-liner for "Available tools" system prompt
|
|
32
|
-
promptGuidelines: [ // Guideline bullets
|
|
33
|
-
"Use
|
|
34
|
-
"Prefer specific queries over broad ones",
|
|
32
|
+
promptGuidelines: [ // Guideline bullets appended verbatim to the global "Guidelines" section when this tool is active
|
|
33
|
+
"Use my_tool when the user asks about search.",
|
|
34
|
+
"Prefer specific queries over broad ones when calling my_tool.",
|
|
35
35
|
],
|
|
36
36
|
parameters: Type.Object({
|
|
37
37
|
query: Type.String({ description: "Search query" }),
|
|
@@ -73,7 +73,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
73
73
|
| `description` | `string` | Yes | What the tool does (LLM reads this) |
|
|
74
74
|
| `parameters` | `TSchema` | Yes | TypeBox schema for arguments |
|
|
75
75
|
| `promptSnippet` | `string` | No | One-liner injected into "Available tools" system prompt. Custom tools without this are omitted from that section. |
|
|
76
|
-
| `promptGuidelines` | `string[]` | No | Guideline bullets appended to "Guidelines" system prompt when this tool is active |
|
|
76
|
+
| `promptGuidelines` | `string[]` | No | Guideline bullets appended verbatim to the global "Guidelines" system prompt section when this tool is active. Write each bullet so it still makes sense standalone. |
|
|
77
77
|
| `execute` | `function` | Yes | Implementation |
|
|
78
78
|
| `renderCall` | `function` | No | Custom call rendering |
|
|
79
79
|
| `renderResult` | `function` | No | Custom result rendering |
|
|
@@ -118,6 +118,8 @@ The `onUpdate` parameter can be `undefined`. Calling it without optional chainin
|
|
|
118
118
|
|
|
119
119
|
If you override a built-in tool or wrap another tool, audit any delegated `tool.execute(...)` calls during upgrades. These forwarders often pass through `signal`, `onUpdate`, or `ctx` and can silently break when the execute signature changes. Always recheck the delegate call parameter order and include optional parameters that the target tool expects.
|
|
120
120
|
|
|
121
|
+
Prompt metadata is not inherited automatically when you override a built-in tool. If the original tool had `promptSnippet` or `promptGuidelines` and you still want that system prompt behavior, define those fields explicitly on the override.
|
|
122
|
+
|
|
121
123
|
## Return Value
|
|
122
124
|
|
|
123
125
|
```typescript
|
|
@@ -222,7 +224,7 @@ Both approaches work. Approach 1 is more common in published extensions. Approac
|
|
|
222
224
|
Use TypeBox (`Type.*`) for parameter schemas. The LLM sees the schema to know what arguments to provide.
|
|
223
225
|
|
|
224
226
|
```typescript
|
|
225
|
-
import { Type } from "@
|
|
227
|
+
import { Type } from "@sinclair/typebox";
|
|
226
228
|
|
|
227
229
|
// Required string
|
|
228
230
|
Type.String({ description: "File path to read" })
|
|
@@ -248,6 +250,89 @@ Type.Array(Type.String(), { description: "List of tags" })
|
|
|
248
250
|
|
|
249
251
|
Always provide `description` on parameters. The LLM uses these to understand what to pass.
|
|
250
252
|
|
|
253
|
+
## Prompt Metadata
|
|
254
|
+
|
|
255
|
+
`promptSnippet` and `promptGuidelines` affect different parts of the default system prompt:
|
|
256
|
+
|
|
257
|
+
- `promptSnippet` adds a one-line entry to `Available tools`.
|
|
258
|
+
- `promptGuidelines` appends raw bullets to the global `Guidelines` section.
|
|
259
|
+
|
|
260
|
+
Important implications:
|
|
261
|
+
|
|
262
|
+
- `promptGuidelines` bullets are not wrapped with the tool name.
|
|
263
|
+
- Write bullets so they still make sense when read out of context.
|
|
264
|
+
- Prefer explicit tool names over phrases like `this tool`.
|
|
265
|
+
|
|
266
|
+
Good:
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
promptGuidelines: [
|
|
270
|
+
"Use my_tool to search project docs before broader web research.",
|
|
271
|
+
"Prefer specific queries when calling my_tool.",
|
|
272
|
+
]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Weak:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
promptGuidelines: [
|
|
279
|
+
"Use this tool for docs.",
|
|
280
|
+
"Prefer specific queries.",
|
|
281
|
+
]
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Use `promptGuidelines` for short, tool-local rules. If the guidance needs cross-tool sequencing, comparisons against several tools, or dynamic config context, use a `before_agent_start` hook instead.
|
|
285
|
+
|
|
286
|
+
## Argument Compatibility and Path Handling
|
|
287
|
+
|
|
288
|
+
Use `prepareArguments(args)` when you need a compatibility shim before schema validation, for example to support an old parameter shape during a migration.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
prepareArguments(args) {
|
|
292
|
+
if (!args || typeof args !== "object") return args;
|
|
293
|
+
const input = args as { action?: string; oldAction?: string };
|
|
294
|
+
if (typeof input.oldAction === "string" && input.action === undefined) {
|
|
295
|
+
return { ...input, action: input.oldAction };
|
|
296
|
+
}
|
|
297
|
+
return args;
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
If your custom tool accepts filesystem paths, normalize a leading `@` before resolving the path. Some models include `@` in path arguments, and the built-in file tools already strip it.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
const normalizedPath = params.path.startsWith("@") ? params.path.slice(1) : params.path;
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## File-Mutating Tools and Concurrency
|
|
308
|
+
|
|
309
|
+
Tool calls can run in parallel. If your custom tool mutates files, use `withFileMutationQueue()` so it participates in the same per-file queue as built-in `edit` and `write`.
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
import { withFileMutationQueue } from "@mariozechner/pi-coding-agent";
|
|
313
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
314
|
+
import { dirname, resolve } from "node:path";
|
|
315
|
+
|
|
316
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
317
|
+
const normalizedPath = params.path.startsWith("@") ? params.path.slice(1) : params.path;
|
|
318
|
+
const absolutePath = resolve(ctx.cwd, normalizedPath);
|
|
319
|
+
|
|
320
|
+
return withFileMutationQueue(absolutePath, async () => {
|
|
321
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
322
|
+
const current = await readFile(absolutePath, "utf8");
|
|
323
|
+
const next = current.replace(params.oldText, params.newText);
|
|
324
|
+
await writeFile(absolutePath, next, "utf8");
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
content: [{ type: "text", text: `Updated ${normalizedPath}` }],
|
|
328
|
+
details: {},
|
|
329
|
+
};
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Queue the whole read-modify-write window, not just the final write.
|
|
335
|
+
|
|
251
336
|
## Streaming Updates
|
|
252
337
|
|
|
253
338
|
Use `onUpdate` to stream partial results while the tool executes. This gives the user feedback during long operations.
|
|
@@ -753,7 +838,7 @@ import type {
|
|
|
753
838
|
} from "@mariozechner/pi-coding-agent";
|
|
754
839
|
import { keyHint, formatSize } from "@mariozechner/pi-coding-agent";
|
|
755
840
|
import { Container, Text } from "@mariozechner/pi-tui";
|
|
756
|
-
import { type Static, Type } from "@
|
|
841
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
757
842
|
|
|
758
843
|
// Schema
|
|
759
844
|
const parameters = Type.Object({
|
|
@@ -780,8 +865,8 @@ const repoTreeTool = {
|
|
|
780
865
|
description: "List files and directories in a GitHub repository.",
|
|
781
866
|
promptSnippet: "Browse repository file structure",
|
|
782
867
|
promptGuidelines: [
|
|
783
|
-
"Use
|
|
784
|
-
"Start
|
|
868
|
+
"Use repo_tree to explore repository structure before reading files.",
|
|
869
|
+
"Start repo_tree at the root path, then drill down into directories.",
|
|
785
870
|
],
|
|
786
871
|
parameters,
|
|
787
872
|
|
|
@@ -244,9 +244,9 @@ export function setupChangelogTool(pi: ExtensionAPI) {
|
|
|
244
244
|
promptSnippet: `pi_changelog version="1.2.3" // Get changelog for specific version
|
|
245
245
|
pi_changelog // Get latest changelog`,
|
|
246
246
|
promptGuidelines: [
|
|
247
|
-
"Use
|
|
247
|
+
"Use pi_changelog to check what's new in a Pi version",
|
|
248
248
|
"Use pi_changelog_versions first to list available versions",
|
|
249
|
-
"Leave version empty to get the latest changelog",
|
|
249
|
+
"Leave version empty for pi_changelog to get the latest changelog",
|
|
250
250
|
],
|
|
251
251
|
|
|
252
252
|
parameters: ChangelogParamsSchema,
|
package/src/tools/docs-tool.ts
CHANGED
|
@@ -47,8 +47,8 @@ export function setupDocsTool(pi: ExtensionAPI) {
|
|
|
47
47
|
|
|
48
48
|
promptSnippet: "List Pi documentation files",
|
|
49
49
|
promptGuidelines: [
|
|
50
|
-
"Use to discover available Pi documentation",
|
|
51
|
-
"
|
|
50
|
+
"Use pi_docs to discover available Pi documentation",
|
|
51
|
+
"pi_docs returns markdown files from README.md, docs/, and examples/",
|
|
52
52
|
],
|
|
53
53
|
|
|
54
54
|
parameters: DocsParamsSchema,
|
|
@@ -46,8 +46,8 @@ export function setupPackageManagerTool(pi: ExtensionAPI) {
|
|
|
46
46
|
"Detect the package manager used in the current project by checking lockfiles and package.json",
|
|
47
47
|
promptSnippet: "Detect the package manager for this project",
|
|
48
48
|
promptGuidelines: [
|
|
49
|
-
"Use when you need to know which package manager (npm, yarn, pnpm, bun) the project uses",
|
|
50
|
-
"
|
|
49
|
+
"Use detect_package_manager when you need to know which package manager (npm, yarn, pnpm, bun) the project uses",
|
|
50
|
+
"detect_package_manager is helpful before running install commands or scripts",
|
|
51
51
|
],
|
|
52
52
|
|
|
53
53
|
parameters: Params,
|
|
@@ -24,7 +24,7 @@ export function setupVersionTool(pi: ExtensionAPI) {
|
|
|
24
24
|
description: "Get the version of the currently running Pi instance",
|
|
25
25
|
promptSnippet: "Check the current Pi version.",
|
|
26
26
|
promptGuidelines: [
|
|
27
|
-
"Use when the user asks about the Pi version or when a task depends on knowing the installed version.",
|
|
27
|
+
"Use pi_version when the user asks about the Pi version or when a task depends on knowing the installed version.",
|
|
28
28
|
],
|
|
29
29
|
|
|
30
30
|
parameters: VersionParams,
|