@counterposition/skill-pi 0.1.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/LICENSE.md +675 -0
- package/README.md +17 -0
- package/package.json +42 -0
- package/skills/pi/LICENSE.md +675 -0
- package/skills/pi/README.md +38 -0
- package/skills/pi/SKILL.md +170 -0
- package/skills/pi/evals/evals.json +50 -0
- package/skills/pi/references/extensions.md +343 -0
- package/skills/pi/references/packages.md +97 -0
- package/skills/pi/references/patterns.md +314 -0
- package/skills/pi/references/providers.md +212 -0
- package/skills/pi/references/sdk.md +177 -0
- package/skills/pi/references/settings.md +170 -0
- package/skills/pi/references/skills.md +111 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Pi Packages
|
|
2
|
+
|
|
3
|
+
Packages bundle extensions, skills, prompt templates, and themes for sharing via npm, git, or local paths.
|
|
4
|
+
|
|
5
|
+
**Security warning:** Packages run with full system access. Extensions execute arbitrary code, skills can instruct the model to perform any action. Review source before installing third-party packages.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# From npm
|
|
11
|
+
pi install npm:@foo/pi-extensions@1.0.0
|
|
12
|
+
|
|
13
|
+
# From git
|
|
14
|
+
pi install git:github.com/user/repo@v1
|
|
15
|
+
pi install https://github.com/user/repo.git
|
|
16
|
+
pi install git@github.com:user/repo.git
|
|
17
|
+
|
|
18
|
+
# From local path
|
|
19
|
+
pi install ./my-package
|
|
20
|
+
pi install /absolute/path/to/package
|
|
21
|
+
|
|
22
|
+
# Project-local install (writes to .pi/settings.json for team sharing)
|
|
23
|
+
pi install -l npm:@foo/pi-extensions
|
|
24
|
+
|
|
25
|
+
# Temporary trial (loads for current session, doesn't install)
|
|
26
|
+
pi -e npm:@foo/pi-extensions
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Management Commands
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pi list # List installed packages
|
|
33
|
+
pi update # Update non-pinned packages
|
|
34
|
+
pi remove <pkg> # Remove a package
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Package Structure
|
|
38
|
+
|
|
39
|
+
### Convention-Based (Auto-Discovery)
|
|
40
|
+
|
|
41
|
+
Place files in standard directories — Pi discovers them automatically:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
my-pi-package/
|
|
45
|
+
├── package.json # npm package metadata + optional pi manifest
|
|
46
|
+
├── extensions/ # .ts and .js files → loaded as extensions
|
|
47
|
+
│ └── my-ext.ts
|
|
48
|
+
├── skills/ # Directories with SKILL.md → loaded as skills
|
|
49
|
+
│ └── my-skill/
|
|
50
|
+
│ └── SKILL.md
|
|
51
|
+
├── prompts/ # .md files → loaded as prompt templates
|
|
52
|
+
│ └── review.md
|
|
53
|
+
└── themes/ # .json files → loaded as themes
|
|
54
|
+
└── my-theme.json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Manifest-Based (Explicit)
|
|
58
|
+
|
|
59
|
+
Declare resources in `package.json` under the `pi` key:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"name": "@foo/pi-extensions",
|
|
64
|
+
"version": "1.0.0",
|
|
65
|
+
"pi": {
|
|
66
|
+
"extensions": ["src/ext1.ts", "src/ext2.ts"],
|
|
67
|
+
"skills": ["skills/my-skill"],
|
|
68
|
+
"prompts": ["prompts/"],
|
|
69
|
+
"themes": ["themes/custom.json"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Both approaches can be combined. Explicit paths in the `pi` manifest take precedence.
|
|
75
|
+
|
|
76
|
+
## Dependencies
|
|
77
|
+
|
|
78
|
+
- **Standard npm dependencies:** Declare in `package.json` → `dependencies` as usual. They're installed with the package.
|
|
79
|
+
- **Pi peer dependencies** (`@mariozechner/pi-coding-agent`, `@mariozechner/pi-ai`, etc.): Use `"*"` as the version range in `peerDependencies`. Do NOT bundle these.
|
|
80
|
+
- **Other Pi packages:** If your package depends on another Pi package, it must be bundled explicitly.
|
|
81
|
+
|
|
82
|
+
## Publishing to npm
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cd my-pi-package
|
|
86
|
+
npm publish
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Users install with `pi install npm:@foo/pi-extensions`.
|
|
90
|
+
|
|
91
|
+
## Deduplication
|
|
92
|
+
|
|
93
|
+
When the same resource name appears from multiple sources:
|
|
94
|
+
|
|
95
|
+
- Project-level packages override global ones
|
|
96
|
+
- First discovery wins for same-level conflicts
|
|
97
|
+
- Pi warns about collisions
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Common Patterns & Recipes
|
|
2
|
+
|
|
3
|
+
Practical recipes for extending Pi. Each pattern is a self-contained example.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [Permission Gate](#permission-gate)
|
|
8
|
+
2. [Protected Paths](#protected-paths)
|
|
9
|
+
3. [Preset System](#preset-system)
|
|
10
|
+
4. [Sub-agents](#sub-agents)
|
|
11
|
+
5. [Git Checkpoint & Restore](#git-checkpoint--restore)
|
|
12
|
+
6. [Auto-Commit on Exit](#auto-commit-on-exit)
|
|
13
|
+
7. [Custom Compaction](#custom-compaction)
|
|
14
|
+
8. [Plan Mode (Read-Only)](#plan-mode-read-only)
|
|
15
|
+
9. [Claude Rules Integration](#claude-rules-integration)
|
|
16
|
+
10. [Interactive Shell](#interactive-shell)
|
|
17
|
+
11. [TUI Components](#tui-components)
|
|
18
|
+
12. [Status Line & Widgets](#status-line--widgets)
|
|
19
|
+
13. [File Watcher / Trigger](#file-watcher--trigger)
|
|
20
|
+
14. [Input Transform](#input-transform)
|
|
21
|
+
15. [Tool Override](#tool-override)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Permission Gate
|
|
26
|
+
|
|
27
|
+
Block dangerous bash commands with user confirmation:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
31
|
+
|
|
32
|
+
export default function (pi: ExtensionAPI) {
|
|
33
|
+
const dangerous = [/\brm\s+(-rf?|--recursive)/i, /\bsudo\b/i, /\b(chmod|chown)\b.*777/i];
|
|
34
|
+
|
|
35
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
36
|
+
if (event.toolName !== "bash") return;
|
|
37
|
+
const cmd = event.input.command as string;
|
|
38
|
+
if (!dangerous.some(p => p.test(cmd))) return;
|
|
39
|
+
if (!ctx.hasUI) return { block: true, reason: "Dangerous command blocked (no UI)" };
|
|
40
|
+
const ok = await ctx.ui.select(`⚠️ ${cmd}\nAllow?`, ["Yes", "No"]);
|
|
41
|
+
if (ok !== "Yes") return { block: true, reason: "Blocked by user" };
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Protected Paths
|
|
47
|
+
|
|
48
|
+
Block writes to sensitive files:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
export default function (pi: ExtensionAPI) {
|
|
52
|
+
const protected = [".env", ".git/", "node_modules/", "*.pem", "*.key"];
|
|
53
|
+
|
|
54
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
55
|
+
if (!["write", "edit"].includes(event.toolName)) return;
|
|
56
|
+
const path = (event.input.file_path || event.input.path) as string;
|
|
57
|
+
if (protected.some(p => path.includes(p))) {
|
|
58
|
+
return { block: true, reason: `Protected path: ${path}` };
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Preset System
|
|
65
|
+
|
|
66
|
+
Named configurations (model + thinking + tools + instructions):
|
|
67
|
+
|
|
68
|
+
**`~/.pi/agent/presets.json`:**
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"plan": {
|
|
73
|
+
"model": "claude-sonnet-4-20250514",
|
|
74
|
+
"thinkingLevel": "high",
|
|
75
|
+
"tools": ["read", "grep", "find", "ls"],
|
|
76
|
+
"instructions": "Analyze the codebase and propose a plan. Do not modify any files."
|
|
77
|
+
},
|
|
78
|
+
"implement": {
|
|
79
|
+
"model": "claude-sonnet-4-20250514",
|
|
80
|
+
"thinkingLevel": "medium",
|
|
81
|
+
"tools": ["read", "bash", "edit", "write"],
|
|
82
|
+
"instructions": "Implement the requested changes with minimal, focused edits."
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The preset extension (from Pi examples) loads these and exposes `/preset [name]` + `Ctrl+Shift+U` cycling.
|
|
88
|
+
|
|
89
|
+
## Sub-agents
|
|
90
|
+
|
|
91
|
+
Delegate tasks to isolated Pi processes. The subagent extension (from Pi examples) provides:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
// Single agent
|
|
95
|
+
{ agent: "researcher", task: "Find all API endpoints in this codebase" }
|
|
96
|
+
|
|
97
|
+
// Parallel execution
|
|
98
|
+
{ tasks: [
|
|
99
|
+
{ agent: "researcher", task: "Analyze auth module" },
|
|
100
|
+
{ agent: "researcher", task: "Analyze database module" }
|
|
101
|
+
]}
|
|
102
|
+
|
|
103
|
+
// Sequential chain with context passing
|
|
104
|
+
{ chain: [
|
|
105
|
+
{ agent: "researcher", task: "Find all TODO comments" },
|
|
106
|
+
{ agent: "writer", task: "Write a summary of: {previous}" }
|
|
107
|
+
]}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Agent definitions live in `~/.pi/agent/agents/` or `.pi/agents/`. Each is a directory with a config file specifying model, tools, and system prompt.
|
|
111
|
+
|
|
112
|
+
## Git Checkpoint & Restore
|
|
113
|
+
|
|
114
|
+
Save code state at each turn, offer restore on fork:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
export default function (pi: ExtensionAPI) {
|
|
118
|
+
const checkpoints = new Map<string, string>();
|
|
119
|
+
let currentEntryId: string | undefined;
|
|
120
|
+
|
|
121
|
+
pi.on("tool_result", async (_event, ctx) => {
|
|
122
|
+
const leaf = ctx.sessionManager.getLeafEntry();
|
|
123
|
+
if (leaf) currentEntryId = leaf.id;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
pi.on("turn_start", async () => {
|
|
127
|
+
const { stdout } = await pi.exec("git", ["stash", "create"]);
|
|
128
|
+
if (stdout.trim() && currentEntryId) checkpoints.set(currentEntryId, stdout.trim());
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
pi.on("session_before_fork", async (event, ctx) => {
|
|
132
|
+
const ref = checkpoints.get(event.entryId);
|
|
133
|
+
if (!ref || !ctx.hasUI) return;
|
|
134
|
+
const ok = await ctx.ui.select("Restore code?", ["Yes", "No"]);
|
|
135
|
+
if (ok?.startsWith("Yes")) await pi.exec("git", ["stash", "apply", ref]);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Auto-Commit on Exit
|
|
141
|
+
|
|
142
|
+
Stage and commit all changes when session ends:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
export default function (pi: ExtensionAPI) {
|
|
146
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
147
|
+
const { stdout } = await pi.exec("git", ["status", "--porcelain"]);
|
|
148
|
+
if (!stdout.trim()) return;
|
|
149
|
+
|
|
150
|
+
const messages = ctx.sessionManager.getBranch();
|
|
151
|
+
const lastAssistant = messages.reverse().find(m => m.role === "assistant");
|
|
152
|
+
const text = lastAssistant?.content?.find(c => c.type === "text")?.text || "pi session changes";
|
|
153
|
+
const firstLine = text.split("\n")[0].slice(0, 50);
|
|
154
|
+
|
|
155
|
+
await pi.exec("git", ["add", "-A"]);
|
|
156
|
+
await pi.exec("git", ["commit", "-m", `[pi] ${firstLine}`]);
|
|
157
|
+
ctx.ui?.notify("Changes committed", "info");
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Custom Compaction
|
|
163
|
+
|
|
164
|
+
Replace default compaction with a custom summary model:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
export default function (pi: ExtensionAPI) {
|
|
168
|
+
pi.on("session_before_compact", async (event, ctx) => {
|
|
169
|
+
const allMessages = [...event.messagesToSummarize, ...event.turnPrefixMessages];
|
|
170
|
+
// Use a cheaper model for summarization
|
|
171
|
+
const summary = await myCustomSummarize(allMessages);
|
|
172
|
+
return {
|
|
173
|
+
summary,
|
|
174
|
+
firstKeptEntryId: event.firstKeptEntryId,
|
|
175
|
+
tokensBefore: event.tokens,
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Plan Mode (Read-Only)
|
|
182
|
+
|
|
183
|
+
Restrict to read-only tools for safe exploration:
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
export default function (pi: ExtensionAPI) {
|
|
187
|
+
let planMode = false;
|
|
188
|
+
|
|
189
|
+
pi.registerCommand("plan", {
|
|
190
|
+
description: "Toggle read-only plan mode",
|
|
191
|
+
async handler(_args, ctx) {
|
|
192
|
+
planMode = !planMode;
|
|
193
|
+
if (planMode) {
|
|
194
|
+
pi.setActiveTools(["read", "bash", "grep", "find", "ls"]);
|
|
195
|
+
ctx.ui.notify("Plan mode ON — read-only tools only", "info");
|
|
196
|
+
} else {
|
|
197
|
+
pi.setActiveTools(["read", "bash", "edit", "write"]);
|
|
198
|
+
ctx.ui.notify("Plan mode OFF — full tools restored", "info");
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
pi.on("tool_call", async (event) => {
|
|
204
|
+
if (!planMode || event.toolName !== "bash") return;
|
|
205
|
+
const cmd = event.input.command as string;
|
|
206
|
+
// Block destructive bash in plan mode
|
|
207
|
+
if (/\b(rm|mv|cp|chmod|chown|git\s+(push|commit|reset))\b/.test(cmd)) {
|
|
208
|
+
return { block: true, reason: "Blocked in plan mode" };
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Claude Rules Integration
|
|
215
|
+
|
|
216
|
+
Scan `.claude/rules/` and inject into system prompt:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
export default function (pi: ExtensionAPI) {
|
|
220
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
221
|
+
const rulesDir = path.join(ctx.cwd, ".claude", "rules");
|
|
222
|
+
if (!fs.existsSync(rulesDir)) return;
|
|
223
|
+
const files = fs.readdirSync(rulesDir, { recursive: true })
|
|
224
|
+
.filter(f => f.toString().endsWith(".md"));
|
|
225
|
+
if (files.length === 0) return;
|
|
226
|
+
const listing = files.map(f => `- .claude/rules/${f}`).join("\n");
|
|
227
|
+
event.systemPrompt += `\n\nProject rules (${files.length} files):\n${listing}\nUse the read tool to load relevant rules.`;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Interactive Shell
|
|
233
|
+
|
|
234
|
+
Keep a persistent shell session across tool calls (rather than spawning a new shell each time):
|
|
235
|
+
|
|
236
|
+
The Pi examples include an `interactive-shell.ts` extension that maintains a persistent bash process, allowing stateful shell operations (cd, environment variables, etc.) across multiple tool invocations.
|
|
237
|
+
|
|
238
|
+
## TUI Components
|
|
239
|
+
|
|
240
|
+
Pi's TUI library (`@mariozechner/pi-tui`) provides:
|
|
241
|
+
|
|
242
|
+
- `Text` — Word-wrapped text
|
|
243
|
+
- `Box` — Padded container with border
|
|
244
|
+
- `Container` — Vertical grouping
|
|
245
|
+
- `Spacer` — Vertical space
|
|
246
|
+
- `Markdown` — Rendered markdown
|
|
247
|
+
- `Image` — Terminal image display
|
|
248
|
+
- `SelectList` — Interactive selection list
|
|
249
|
+
- `BorderedLoader` — Async spinner with cancel
|
|
250
|
+
|
|
251
|
+
All components implement `render(width) → string[]` and optional `handleInput(data)`.
|
|
252
|
+
|
|
253
|
+
## Status Line & Widgets
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// Footer status text
|
|
257
|
+
ctx.ui.setStatus("🔍 Searching...");
|
|
258
|
+
|
|
259
|
+
// Widget above the editor
|
|
260
|
+
ctx.ui.setWidget("above", new Text("Current task: refactoring auth", 0, 0));
|
|
261
|
+
|
|
262
|
+
// Widget below the editor
|
|
263
|
+
ctx.ui.setWidget("below", progressComponent);
|
|
264
|
+
|
|
265
|
+
// Custom footer
|
|
266
|
+
ctx.ui.setFooter(new Text(theme.fg("accent", "My Extension v1.0"), 0, 0));
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## File Watcher / Trigger
|
|
270
|
+
|
|
271
|
+
Run actions when files change:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
export default function (pi: ExtensionAPI) {
|
|
275
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
276
|
+
fs.watch(ctx.cwd, { recursive: true }, (eventType, filename) => {
|
|
277
|
+
if (filename?.endsWith(".test.ts")) {
|
|
278
|
+
ctx.ui.notify(`Test file changed: ${filename}`, "info");
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Input Transform
|
|
286
|
+
|
|
287
|
+
Modify user input before it reaches the LLM:
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
pi.on("input", async (event) => {
|
|
291
|
+
// Expand shorthand
|
|
292
|
+
event.text = event.text.replace(/^fix /, "Find and fix the bug: ");
|
|
293
|
+
// Or block certain inputs
|
|
294
|
+
if (event.text.includes("DELETE EVERYTHING")) {
|
|
295
|
+
return { block: true };
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Tool Override
|
|
301
|
+
|
|
302
|
+
Replace a built-in tool's behavior:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
306
|
+
if (event.toolName === "write") {
|
|
307
|
+
// Add a backup before every write
|
|
308
|
+
const filePath = event.input.file_path as string;
|
|
309
|
+
if (fs.existsSync(filePath)) {
|
|
310
|
+
fs.copyFileSync(filePath, `${filePath}.bak`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
```
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Providers & Custom Models
|
|
2
|
+
|
|
3
|
+
Pi supports 20+ LLM providers out of the box and allows adding custom providers via `models.json` or extensions.
|
|
4
|
+
|
|
5
|
+
## Built-in Providers
|
|
6
|
+
|
|
7
|
+
### Subscription-based (OAuth via `/login`)
|
|
8
|
+
|
|
9
|
+
| Provider | Subscription |
|
|
10
|
+
|----------|-------------|
|
|
11
|
+
| Anthropic | Claude Pro / Max |
|
|
12
|
+
| OpenAI | ChatGPT Plus / Pro |
|
|
13
|
+
| GitHub Copilot | Copilot subscription |
|
|
14
|
+
| Google | Google AI / Vertex |
|
|
15
|
+
|
|
16
|
+
### API Key Providers
|
|
17
|
+
|
|
18
|
+
| Provider | Env Variable | Auth.json Key |
|
|
19
|
+
|----------|-------------|---------------|
|
|
20
|
+
| Anthropic | `ANTHROPIC_API_KEY` | `anthropic` |
|
|
21
|
+
| OpenAI | `OPENAI_API_KEY` | `openai` |
|
|
22
|
+
| Google | `GOOGLE_API_KEY` | `google` |
|
|
23
|
+
| xAI | `XAI_API_KEY` | `xai` |
|
|
24
|
+
| Groq | `GROQ_API_KEY` | `groq` |
|
|
25
|
+
| Cerebras | `CEREBRAS_API_KEY` | `cerebras` |
|
|
26
|
+
| OpenRouter | `OPENROUTER_API_KEY` | `openrouter` |
|
|
27
|
+
| Mistral | `MISTRAL_API_KEY` | `mistral` |
|
|
28
|
+
| Hugging Face | `HF_TOKEN` | `huggingface` |
|
|
29
|
+
| MiniMax | `MINIMAX_API_KEY` | `minimax` |
|
|
30
|
+
| Together | `TOGETHER_API_KEY` | `together` |
|
|
31
|
+
| Fireworks | `FIREWORKS_API_KEY` | `fireworks` |
|
|
32
|
+
| DeepSeek | `DEEPSEEK_API_KEY` | `deepseek` |
|
|
33
|
+
| Sambanova | `SAMBANOVA_API_KEY` | `sambanova` |
|
|
34
|
+
|
|
35
|
+
## Credential Priority
|
|
36
|
+
|
|
37
|
+
1. CLI flags (`--api-key`)
|
|
38
|
+
2. `~/.pi/agent/auth.json` entries
|
|
39
|
+
3. Environment variables
|
|
40
|
+
4. Custom provider config in `models.json`
|
|
41
|
+
|
|
42
|
+
## Auth File Format
|
|
43
|
+
|
|
44
|
+
`~/.pi/agent/auth.json` (permissions: `0600`):
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"anthropic": {
|
|
49
|
+
"key": "sk-ant-..."
|
|
50
|
+
},
|
|
51
|
+
"openai": {
|
|
52
|
+
"key": "!op read 'OpenAI API Key'"
|
|
53
|
+
},
|
|
54
|
+
"google": {
|
|
55
|
+
"key": "GOOGLE_API_KEY"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Key value resolution:
|
|
61
|
+
|
|
62
|
+
- **Literal:** `"sk-ant-..."` — used directly
|
|
63
|
+
- **Shell command:** `"!command"` — executes command, uses stdout
|
|
64
|
+
- **Env variable:** `"ENV_VAR_NAME"` — reads from environment
|
|
65
|
+
|
|
66
|
+
## Custom Models via models.json
|
|
67
|
+
|
|
68
|
+
`~/.pi/agent/models.json`:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"providers": [
|
|
73
|
+
{
|
|
74
|
+
"name": "ollama",
|
|
75
|
+
"baseUrl": "http://localhost:11434/v1",
|
|
76
|
+
"api": "openai-completions",
|
|
77
|
+
"apiKey": "unused",
|
|
78
|
+
"models": [
|
|
79
|
+
{
|
|
80
|
+
"id": "llama3.2",
|
|
81
|
+
"name": "Llama 3.2",
|
|
82
|
+
"contextWindow": 128000,
|
|
83
|
+
"maxTokens": 4096,
|
|
84
|
+
"input": ["text"],
|
|
85
|
+
"reasoning": false
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Supported API Types
|
|
94
|
+
|
|
95
|
+
| API | Value |
|
|
96
|
+
|-----|-------|
|
|
97
|
+
| OpenAI Chat Completions | `"openai-completions"` |
|
|
98
|
+
| OpenAI Responses | `"openai-responses"` |
|
|
99
|
+
| Anthropic Messages | `"anthropic-messages"` |
|
|
100
|
+
| Google Generative AI | `"google-generative-ai"` |
|
|
101
|
+
| Google Vertex AI | `"google-vertex"` |
|
|
102
|
+
| Azure OpenAI | `"azure-openai-responses"` |
|
|
103
|
+
| Mistral | `"mistral-conversations"` |
|
|
104
|
+
| Amazon Bedrock | `"bedrock-converse-stream"` |
|
|
105
|
+
|
|
106
|
+
### Model Fields
|
|
107
|
+
|
|
108
|
+
| Field | Required | Type | Description |
|
|
109
|
+
|-------|----------|------|-------------|
|
|
110
|
+
| `id` | Yes | string | Model ID sent to API |
|
|
111
|
+
| `name` | No | string | Human-readable display name |
|
|
112
|
+
| `reasoning` | No | boolean | Supports extended thinking |
|
|
113
|
+
| `input` | No | string[] | `["text"]` or `["text", "image"]` |
|
|
114
|
+
| `contextWindow` | No | number | Max context tokens |
|
|
115
|
+
| `maxTokens` | No | number | Max output tokens |
|
|
116
|
+
| `cost` | No | object | `{ input, output, cacheRead, cacheWrite }` per million tokens |
|
|
117
|
+
|
|
118
|
+
### Provider Fields
|
|
119
|
+
|
|
120
|
+
| Field | Required | Description |
|
|
121
|
+
|-------|----------|-------------|
|
|
122
|
+
| `name` | Yes | Provider identifier |
|
|
123
|
+
| `baseUrl` | Yes | API endpoint URL |
|
|
124
|
+
| `api` | Yes | API type (see table above) |
|
|
125
|
+
| `apiKey` | Yes | API key (literal, env var, or shell command) |
|
|
126
|
+
| `models` | Yes | Array of model definitions |
|
|
127
|
+
| `headers` | No | Custom HTTP headers (same value resolution as apiKey) |
|
|
128
|
+
|
|
129
|
+
### Compatibility Settings
|
|
130
|
+
|
|
131
|
+
For partially OpenAI-compatible APIs, use the `compat` field on models:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"compat": {
|
|
136
|
+
"supportsDeveloperRole": false,
|
|
137
|
+
"supportsReasoningEffort": false,
|
|
138
|
+
"supportsStreamOptions": true,
|
|
139
|
+
"openRouterProviderOrder": ["anthropic", "google"]
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Cloud Providers
|
|
145
|
+
|
|
146
|
+
### Azure OpenAI
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"name": "azure-openai",
|
|
151
|
+
"baseUrl": "https://myresource.openai.azure.com",
|
|
152
|
+
"api": "azure-openai-responses",
|
|
153
|
+
"apiKey": "AZURE_OPENAI_API_KEY",
|
|
154
|
+
"models": [{ "id": "gpt-4o", "name": "GPT-4o" }]
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Amazon Bedrock
|
|
159
|
+
|
|
160
|
+
Uses AWS credentials (profile, IAM keys, or bearer tokens). Set `AWS_PROFILE` or `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`. Pi auto-enables Bedrock for Claude models when credentials are available.
|
|
161
|
+
|
|
162
|
+
### Google Vertex AI
|
|
163
|
+
|
|
164
|
+
Uses Application Default Credentials: `gcloud auth application-default login`.
|
|
165
|
+
|
|
166
|
+
## Custom Provider via Extension
|
|
167
|
+
|
|
168
|
+
For full control (custom streaming, OAuth, etc.):
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
172
|
+
|
|
173
|
+
export default function (pi: ExtensionAPI) {
|
|
174
|
+
pi.registerProvider("my-provider", {
|
|
175
|
+
baseUrl: "https://api.example.com",
|
|
176
|
+
api: "openai-completions",
|
|
177
|
+
apiKey: "my-key",
|
|
178
|
+
models: [
|
|
179
|
+
{ id: "my-model", name: "My Model", contextWindow: 128000, maxTokens: 4096 }
|
|
180
|
+
],
|
|
181
|
+
// Optional: custom streaming for non-standard APIs
|
|
182
|
+
streamSimple: async (model, context, options) => {
|
|
183
|
+
// Return AsyncIterable<AssistantMessageEvent>
|
|
184
|
+
},
|
|
185
|
+
// Optional: OAuth device flow
|
|
186
|
+
oauth: {
|
|
187
|
+
deviceCodeUrl: "https://auth.example.com/device",
|
|
188
|
+
tokenUrl: "https://auth.example.com/token",
|
|
189
|
+
clientId: "my-client-id",
|
|
190
|
+
scopes: ["chat"],
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Overriding Built-in Providers
|
|
197
|
+
|
|
198
|
+
To route a built-in provider through a proxy while keeping its models:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"providers": [
|
|
203
|
+
{
|
|
204
|
+
"name": "anthropic",
|
|
205
|
+
"baseUrl": "https://my-proxy.example.com/v1",
|
|
206
|
+
"apiKey": "proxy-key"
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Models.json reloads automatically when accessing `/model` — no restart needed.
|