docs(coding-agent): document async extension factories closes #3469
This commit is contained in:
@@ -7,6 +7,10 @@
|
|||||||
- Added extension support for customizing the interactive streaming working indicator via `ctx.ui.setWorkingIndicator()`, including custom animated frames, static indicators, hidden indicators, a new `working-indicator.ts` example extension, and updated extension/TUI/RPC docs ([#3413](https://github.com/badlogic/pi-mono/issues/3413))
|
- Added extension support for customizing the interactive streaming working indicator via `ctx.ui.setWorkingIndicator()`, including custom animated frames, static indicators, hidden indicators, a new `working-indicator.ts` example extension, and updated extension/TUI/RPC docs ([#3413](https://github.com/badlogic/pi-mono/issues/3413))
|
||||||
- Added `/clone` to duplicate the current active branch into a new session, while keeping `/fork` focused on forking from a previous user message ([#2962](https://github.com/badlogic/pi-mono/issues/2962))
|
- Added `/clone` to duplicate the current active branch into a new session, while keeping `/fork` focused on forking from a previous user message ([#2962](https://github.com/badlogic/pi-mono/issues/2962))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Documented async extension factory functions in the extensions and custom-provider docs, including startup ordering and dynamic model/provider discovery via async initialization ([#3469](https://github.com/badlogic/pi-mono/issues/3469))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Fixed `pi update` reinstalling npm packages that are already at the latest published version by checking the installed package version before running `npm install <pkg>@latest` ([#3000](https://github.com/badlogic/pi-mono/issues/3000))
|
- Fixed `pi update` reinstalling npm packages that are already at the latest published version by checking the installed package version before running `npm install <pkg>@latest` ([#3000](https://github.com/badlogic/pi-mono/issues/3000))
|
||||||
|
|||||||
@@ -335,6 +335,8 @@ export default function (pi: ExtensionAPI) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The default export can also be `async`. pi waits for async extension factories before startup continues, which is useful for one-time initialization such as fetching remote model lists before calling `pi.registerProvider()`.
|
||||||
|
|
||||||
**What's possible:**
|
**What's possible:**
|
||||||
- Custom tools (or replace built-in tools entirely)
|
- Custom tools (or replace built-in tools entirely)
|
||||||
- Sub-agents and plan mode
|
- Sub-agents and plan mode
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ export default function (pi: ExtensionAPI) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The extension factory can also be `async`. For dynamic model discovery, fetch and register models in the factory instead of `session_start`. pi waits for the factory before startup continues, so the provider is available during interactive startup and to `pi --list-models`.
|
||||||
|
|
||||||
## Override Existing Provider
|
## Override Existing Provider
|
||||||
|
|
||||||
The simplest use case: redirect an existing provider through a proxy.
|
The simplest use case: redirect an existing provider through a proxy.
|
||||||
@@ -91,6 +93,41 @@ When only `baseUrl` and/or `headers` are provided (no `models`), all existing mo
|
|||||||
|
|
||||||
To add a completely new provider, specify `models` along with the required configuration.
|
To add a completely new provider, specify `models` along with the required configuration.
|
||||||
|
|
||||||
|
If the model list comes from a remote endpoint, use an async extension factory:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
export default async function (pi: ExtensionAPI) {
|
||||||
|
const response = await fetch("http://localhost:1234/v1/models");
|
||||||
|
const payload = (await response.json()) as {
|
||||||
|
data: Array<{
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
context_window?: number;
|
||||||
|
max_tokens?: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
pi.registerProvider("local-openai", {
|
||||||
|
baseUrl: "http://localhost:1234/v1",
|
||||||
|
apiKey: "LOCAL_OPENAI_API_KEY",
|
||||||
|
api: "openai-completions",
|
||||||
|
models: payload.data.map((model) => ({
|
||||||
|
id: model.id,
|
||||||
|
name: model.name ?? model.id,
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: model.context_window ?? 128000,
|
||||||
|
maxTokens: model.max_tokens ?? 4096,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This registers the fetched models before startup finishes.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
pi.registerProvider("my-llm", {
|
pi.registerProvider("my-llm", {
|
||||||
baseUrl: "https://api.my-llm.com/v1",
|
baseUrl: "https://api.my-llm.com/v1",
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ Node.js built-ins (`node:fs`, `node:path`, etc.) are also available.
|
|||||||
|
|
||||||
## Writing an Extension
|
## Writing an Extension
|
||||||
|
|
||||||
An extension exports a default function that receives `ExtensionAPI`:
|
An extension exports a default factory function that receives `ExtensionAPI`. The factory can be synchronous or asynchronous:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
@@ -176,6 +176,45 @@ export default function (pi: ExtensionAPI) {
|
|||||||
|
|
||||||
Extensions are loaded via [jiti](https://github.com/unjs/jiti), so TypeScript works without compilation.
|
Extensions are loaded via [jiti](https://github.com/unjs/jiti), so TypeScript works without compilation.
|
||||||
|
|
||||||
|
If the factory returns a `Promise`, pi awaits it before continuing startup. That means async initialization completes before `session_start`, before `resources_discover`, and before provider registrations queued via `pi.registerProvider()` are flushed.
|
||||||
|
|
||||||
|
### Async factory functions
|
||||||
|
|
||||||
|
Use an async factory for one-time startup work such as fetching remote configuration or dynamically discovering available models.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
|
export default async function (pi: ExtensionAPI) {
|
||||||
|
const response = await fetch("http://localhost:1234/v1/models");
|
||||||
|
const payload = (await response.json()) as {
|
||||||
|
data: Array<{
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
context_window?: number;
|
||||||
|
max_tokens?: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
pi.registerProvider("local-openai", {
|
||||||
|
baseUrl: "http://localhost:1234/v1",
|
||||||
|
apiKey: "LOCAL_OPENAI_API_KEY",
|
||||||
|
api: "openai-completions",
|
||||||
|
models: payload.data.map((model) => ({
|
||||||
|
id: model.id,
|
||||||
|
name: model.name ?? model.id,
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: model.context_window ?? 128000,
|
||||||
|
maxTokens: model.max_tokens ?? 4096,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This pattern makes the fetched models available during normal startup and to `pi --list-models`.
|
||||||
|
|
||||||
### Extension Styles
|
### Extension Styles
|
||||||
|
|
||||||
**Single file** - simplest, for small extensions:
|
**Single file** - simplest, for small extensions:
|
||||||
@@ -1371,6 +1410,8 @@ Register or override a model provider dynamically. Useful for proxies, custom en
|
|||||||
|
|
||||||
Calls made during the extension factory function are queued and applied once the runner initialises. Calls made after that — for example from a command handler following a user setup flow — take effect immediately without requiring a `/reload`.
|
Calls made during the extension factory function are queued and applied once the runner initialises. Calls made after that — for example from a command handler following a user setup flow — take effect immediately without requiring a `/reload`.
|
||||||
|
|
||||||
|
If you need to discover models from a remote endpoint, prefer an async extension factory over deferring the fetch to `session_start`. pi waits for the factory before startup continues, so the registered models are available immediately, including to `pi --list-models`.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Register a new provider with custom models
|
// Register a new provider with custom models
|
||||||
pi.registerProvider("my-proxy", {
|
pi.registerProvider("my-proxy", {
|
||||||
|
|||||||
Reference in New Issue
Block a user