@aliou/pi-dev-kit 0.4.9 → 0.5.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/README.md +60 -0
- package/package.json +59 -2
- package/src/commands/index.ts +6 -0
- package/src/commands/update.ts +144 -0
- package/src/index.ts +8 -0
- package/src/prompts/setup-demo.md +35 -0
- package/src/skills/demo-setup/SKILL.md +217 -0
- package/src/skills/pi-extension/SKILL.md +140 -0
- package/src/skills/pi-extension/references/additional-apis.md +264 -0
- package/src/skills/pi-extension/references/commands.md +100 -0
- package/src/skills/pi-extension/references/components.md +166 -0
- package/src/skills/pi-extension/references/documentation.md +54 -0
- package/src/skills/pi-extension/references/hooks.md +244 -0
- package/src/skills/pi-extension/references/messages.md +169 -0
- package/src/skills/pi-extension/references/modes.md +156 -0
- package/src/skills/pi-extension/references/providers.md +134 -0
- package/src/skills/pi-extension/references/publish.md +139 -0
- package/src/skills/pi-extension/references/state.md +56 -0
- package/src/skills/pi-extension/references/structure.md +408 -0
- package/src/skills/pi-extension/references/testing.md +54 -0
- package/src/skills/pi-extension/references/tools.md +430 -0
- package/src/tools/changelog-tool.ts +596 -0
- package/src/tools/docs-tool.ts +240 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/package-manager-tool.ts +223 -0
- package/src/tools/utils.ts +62 -0
- package/src/tools/version-tool.ts +77 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Providers
|
|
2
|
+
|
|
3
|
+
Providers add LLM backends to pi. They connect pi to model APIs (OpenAI-compatible or custom).
|
|
4
|
+
|
|
5
|
+
## Registration
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Type, type ExtensionAPI, type ProviderDefinition } from "@mariozechner/pi-coding-agent";
|
|
9
|
+
|
|
10
|
+
const myProvider: ProviderDefinition = {
|
|
11
|
+
name: "my-provider",
|
|
12
|
+
models: () => {
|
|
13
|
+
const apiKey = process.env.MY_API_KEY;
|
|
14
|
+
if (!apiKey) return [];
|
|
15
|
+
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
id: "my-provider/model-name",
|
|
19
|
+
name: "Model Name",
|
|
20
|
+
provider: "my-provider",
|
|
21
|
+
canStream: true,
|
|
22
|
+
contextLength: 128000,
|
|
23
|
+
maxOutputTokens: 8192,
|
|
24
|
+
pricing: { inputPerMillion: 3.0, outputPerMillion: 15.0 },
|
|
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",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default function (pi: ExtensionAPI) {
|
|
38
|
+
pi.registerProvider(myProvider);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Provider Definition
|
|
43
|
+
|
|
44
|
+
| Field | Type | Description |
|
|
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. |
|
|
50
|
+
|
|
51
|
+
The `models` function is the right place to check for API key presence. If the key is missing, return an empty array and the provider will appear registered but offer no models.
|
|
52
|
+
|
|
53
|
+
## Model Definition
|
|
54
|
+
|
|
55
|
+
| Field | Type | Description |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `id` | `string` | Unique model ID. Convention: `provider/model-name`. |
|
|
58
|
+
| `name` | `string` | Display name shown in model picker. |
|
|
59
|
+
| `provider` | `string` | Must match the provider's `name`. |
|
|
60
|
+
| `canStream` | `boolean` | Whether the model supports streaming responses. |
|
|
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. |
|
|
65
|
+
|
|
66
|
+
## Compat Field
|
|
67
|
+
|
|
68
|
+
The `compat` field tells pi how to talk to the model's API. Most third-party APIs are OpenAI-compatible but differ in which features they support.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
compat: {
|
|
72
|
+
type: "openai-completions",
|
|
73
|
+
|
|
74
|
+
// Which field name the API uses for max output tokens
|
|
75
|
+
maxTokensField: "max_tokens" | "max_completion_tokens",
|
|
76
|
+
|
|
77
|
+
// Whether the API supports the 'developer' role (vs 'system')
|
|
78
|
+
supportsDeveloperRole: boolean,
|
|
79
|
+
|
|
80
|
+
// Whether the API supports the 'store' parameter
|
|
81
|
+
supportsStore: boolean,
|
|
82
|
+
|
|
83
|
+
// Whether the API supports reasoning_effort parameter
|
|
84
|
+
supportsReasoningEffort: boolean,
|
|
85
|
+
|
|
86
|
+
// Whether usage stats are included in streaming responses
|
|
87
|
+
supportsUsageInStreaming: boolean,
|
|
88
|
+
|
|
89
|
+
// Whether tool results must include a 'name' field
|
|
90
|
+
requiresToolResultName: boolean,
|
|
91
|
+
|
|
92
|
+
// Whether an assistant message is required after tool results
|
|
93
|
+
requiresAssistantAfterToolResult: boolean,
|
|
94
|
+
|
|
95
|
+
// Whether thinking/reasoning must be sent as text content
|
|
96
|
+
requiresThinkingAsText: boolean,
|
|
97
|
+
|
|
98
|
+
// Mistral-specific tool ID requirements
|
|
99
|
+
requiresMistralToolIds: boolean,
|
|
100
|
+
|
|
101
|
+
// Format for thinking/reasoning blocks
|
|
102
|
+
thinkingFormat: "openai" | "zai" | "qwen",
|
|
103
|
+
|
|
104
|
+
// OpenRouter-specific routing hints
|
|
105
|
+
openRouterRouting: object,
|
|
106
|
+
|
|
107
|
+
// Vercel AI Gateway routing
|
|
108
|
+
vercelGatewayRouting: object,
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
All fields in `compat` are optional except `type`. Start with the minimum and add fields as needed based on API behavior.
|
|
113
|
+
|
|
114
|
+
There is also `type: "openai-responses"` for providers using the OpenAI Responses API, which currently has no additional compat fields.
|
|
115
|
+
|
|
116
|
+
## Provider with API Key Gate
|
|
117
|
+
|
|
118
|
+
Register the provider unconditionally but gate tools/commands on the API key:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
export default function (pi: ExtensionAPI) {
|
|
122
|
+
// Provider always registered -- models() returns [] if no key
|
|
123
|
+
pi.registerProvider(myProvider);
|
|
124
|
+
|
|
125
|
+
const apiKey = process.env.MY_API_KEY;
|
|
126
|
+
if (!apiKey) return;
|
|
127
|
+
|
|
128
|
+
// Only register tools that need the key
|
|
129
|
+
pi.registerTool(createSearchTool(apiKey));
|
|
130
|
+
pi.registerCommand(createQuotasCommand(apiKey));
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This way the provider appears in pi's provider list even without a key, and users see a clear "no models available" state rather than a missing provider.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Publishing
|
|
2
|
+
|
|
3
|
+
Extensions are published to npm and installed with `pi install`.
|
|
4
|
+
|
|
5
|
+
## Package Setup
|
|
6
|
+
|
|
7
|
+
The `package.json` must have the `pi` key declaring extension resources. See `references/structure.md` for the full template.
|
|
8
|
+
|
|
9
|
+
Key fields for publishing:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"name": "@scope/pi-my-extension",
|
|
14
|
+
"version": "0.1.0",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"private": false,
|
|
18
|
+
"publishConfig": { "access": "public" },
|
|
19
|
+
"files": ["src", "README.md"],
|
|
20
|
+
"pi": {
|
|
21
|
+
"extensions": ["./src/index.ts"]
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@mariozechner/pi-coding-agent": ">=0.51.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Versioning with Changesets
|
|
30
|
+
|
|
31
|
+
Use [changesets](https://github.com/changesets/changesets) for versioning and changelogs.
|
|
32
|
+
|
|
33
|
+
### `.changeset/config.json`
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
|
|
38
|
+
"changelog": "@changesets/cli/changelog",
|
|
39
|
+
"commit": false,
|
|
40
|
+
"fixed": [],
|
|
41
|
+
"linked": [],
|
|
42
|
+
"access": "public",
|
|
43
|
+
"baseBranch": "main",
|
|
44
|
+
"updateInternalDependencies": "patch",
|
|
45
|
+
"ignore": []
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Creating a changeset
|
|
50
|
+
|
|
51
|
+
The interactive CLI (`pnpm changeset`) is not available in headless environments. Write the file directly instead. Create `.changeset/<descriptive-name>.md`:
|
|
52
|
+
|
|
53
|
+
```md
|
|
54
|
+
---
|
|
55
|
+
"@scope/pi-my-extension": patch
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
Description of what changed and why it matters to users.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Bump types: `patch` for fixes and internal changes, `minor` for new user-facing features, `major` for breaking changes.
|
|
62
|
+
|
|
63
|
+
Commit the changeset file alongside the changes it describes. Multiple changeset files can coexist — they are all consumed together on the next release.
|
|
64
|
+
|
|
65
|
+
### Releasing manually (no CI)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pnpm changeset version # consumes .changeset/*.md, bumps version, updates CHANGELOG.md
|
|
69
|
+
pnpm changeset publish # publishes to npm
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## GitHub Actions Automation
|
|
73
|
+
|
|
74
|
+
The recommended setup uses a single `publish.yml` workflow that runs on every push to `main`. It handles two cases automatically:
|
|
75
|
+
|
|
76
|
+
- **Pending changesets present**: opens (or updates) a version PR titled `Updating @scope/pi-my-extension to version X.Y.Z`.
|
|
77
|
+
- **Version PR merged**: publishes the package to npm and creates a GitHub release with a matching git tag.
|
|
78
|
+
|
|
79
|
+
Copy `.github/workflows/publish.yml` from `pi-extension-template` into the new repo. It uses `changesets/action@v1` under the hood.
|
|
80
|
+
|
|
81
|
+
The workflow requires two secrets, configured in the repo's GitHub settings under **Settings → Secrets and variables → Actions**:
|
|
82
|
+
|
|
83
|
+
- `GITHUB_TOKEN` — automatically provided by GitHub Actions, no setup needed.
|
|
84
|
+
- `NPM_TOKEN` — an npm automation token with publish access to the `@scope` org. Create one at npmjs.com under **Access Tokens → Generate New Token → Automation**. Add it as a repository secret named `NPM_TOKEN`. Without this, the publish step will fail silently on the version PR merge.
|
|
85
|
+
|
|
86
|
+
The workflow also sets `NPM_CONFIG_PROVENANCE=true`, which links the published package to the GitHub Actions run for supply chain transparency (requires the `id-token: write` permission, already included in the template).
|
|
87
|
+
|
|
88
|
+
## First-time Setup for a New Package
|
|
89
|
+
|
|
90
|
+
Before the workflow can publish a package that has never been on npm:
|
|
91
|
+
|
|
92
|
+
1. Make sure `"private": false` and `"publishConfig": { "access": "public" }` are in `package.json`.
|
|
93
|
+
2. Add the `NPM_TOKEN` secret to the repo (see above).
|
|
94
|
+
3. The first time the version PR is merged, the workflow publishes the package. npm will create the package entry automatically — no manual `npm publish` needed.
|
|
95
|
+
|
|
96
|
+
If the package name is scoped (e.g., `@aliou/pi-my-extension`) and the scope is new to your npm account, you may need to create the scope first at npmjs.com or run `npm publish --access public` once manually to register it.
|
|
97
|
+
|
|
98
|
+
## Installation
|
|
99
|
+
|
|
100
|
+
Users install extensions with:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pi install @scope/pi-my-extension
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Pi reads the `pi` key from the package's `package.json` to discover extensions, skills, themes, and prompts.
|
|
107
|
+
|
|
108
|
+
## Dependency Management in Monorepos
|
|
109
|
+
|
|
110
|
+
If publishing from a monorepo that contains both public and private packages:
|
|
111
|
+
|
|
112
|
+
**Critical rule**: Public packages cannot depend on private workspace packages. This will break when users try to install your package from npm.
|
|
113
|
+
|
|
114
|
+
In the pi-extensions monorepo, this is enforced by:
|
|
115
|
+
- Pre-commit hook that blocks commits with invalid dependencies
|
|
116
|
+
- CI check that prevents merging bad dependencies
|
|
117
|
+
- `pnpm run check:public-deps` validates all dependencies
|
|
118
|
+
|
|
119
|
+
When adding a workspace dependency to a `package.json`:
|
|
120
|
+
1. Check if the dependency is public (`"private": false` or `"publishConfig": { "access": "public" }`).
|
|
121
|
+
2. If the dependency is private, either make it public, make your package private, or remove the dependency.
|
|
122
|
+
|
|
123
|
+
## Pre-publish Checklist
|
|
124
|
+
|
|
125
|
+
- [ ] `"private": false` is set.
|
|
126
|
+
- [ ] `"publishConfig": { "access": "public" }` is set.
|
|
127
|
+
- [ ] `"files"` lists only what users need (`["src", "README.md"]`).
|
|
128
|
+
- [ ] `peerDependencies` version range is correct (`>=` minimum supported version).
|
|
129
|
+
- [ ] `@mariozechner/pi-tui` is in `peerDependencies` with `optional: true` in `peerDependenciesMeta` if imported at runtime.
|
|
130
|
+
- [ ] `prepare` script is `[ -d .git ] && husky || true`, not bare `husky`.
|
|
131
|
+
- [ ] `check:lockfile` script is present.
|
|
132
|
+
- [ ] `description` is clear and concise.
|
|
133
|
+
- [ ] `pi.extensions` paths are correct.
|
|
134
|
+
- [ ] `NPM_TOKEN` secret is set on the GitHub repo.
|
|
135
|
+
- [ ] `.github/workflows/publish.yml` is present.
|
|
136
|
+
- [ ] If in a monorepo: no dependency on private workspace packages (`pnpm run check:public-deps` if available).
|
|
137
|
+
- [ ] README documents what the extension does, required environment variables, and available tools/commands.
|
|
138
|
+
- [ ] If wrapping a third-party API: extension handles missing API key gracefully (notification, not crash).
|
|
139
|
+
- [ ] `pnpm typecheck` and `pnpm lint` pass.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# State Management
|
|
2
|
+
|
|
3
|
+
Extensions can persist state in the session history using `appendEntry`. State is reconstructed by replaying entries when a session is loaded.
|
|
4
|
+
|
|
5
|
+
## appendEntry
|
|
6
|
+
|
|
7
|
+
Adds an entry to the session conversation. Unlike `sendMessage`, entries from `appendEntry` are explicitly for state tracking and are rendered via the tool result rendering system.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
pi.appendEntry({
|
|
11
|
+
toolName: "todo",
|
|
12
|
+
toolCallId: `todo-${Date.now()}`,
|
|
13
|
+
input: { action: "add", text: "Buy groceries" },
|
|
14
|
+
output: "Added: Buy groceries",
|
|
15
|
+
display: true,
|
|
16
|
+
details: { items: ["Buy groceries"] },
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Field | Type | Description |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `toolName` | `string` | Which tool this entry is associated with. Used for rendering. |
|
|
23
|
+
| `toolCallId` | `string` | Unique ID for this entry. |
|
|
24
|
+
| `input` | `object` | The "input" shown in the entry (as if the tool was called with these params). |
|
|
25
|
+
| `output` | `string` | The text output (what the LLM sees). |
|
|
26
|
+
| `display` | `boolean` | Whether to show in TUI. |
|
|
27
|
+
| `details` | `object` | Rich data for the tool's `renderResult`. |
|
|
28
|
+
|
|
29
|
+
## Reconstructing State from Session
|
|
30
|
+
|
|
31
|
+
When a session loads, you can reconstruct state by iterating over existing entries. This is typically done in a `session_start` or `session_switch` handler:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
35
|
+
// Rebuild state from session entries
|
|
36
|
+
const entries = ctx.getEntries();
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
if (entry.toolName === "todo" && entry.details) {
|
|
39
|
+
todoItems = entry.details.items;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This pattern makes state survive session reloads, forks, and compactions (as long as the entries are included in the compaction summary).
|
|
46
|
+
|
|
47
|
+
## When to Use appendEntry vs sendMessage
|
|
48
|
+
|
|
49
|
+
| | `appendEntry` | `sendMessage` |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| Rendered as | Tool call/result pair | Assistant message |
|
|
52
|
+
| Custom renderer | Tool's `renderResult` | `registerMessageRenderer` |
|
|
53
|
+
| Use for | State changes, action logs | Information display, command results |
|
|
54
|
+
| LLM sees | The `output` field | The `content` field |
|
|
55
|
+
|
|
56
|
+
Use `appendEntry` when you are tracking state changes that need to be replayed. Use `sendMessage` when you are displaying a one-time result.
|