diff --git a/packages/pi-memory/package.json b/packages/pi-memory/package.json index a9f8af2..47aa6b7 100644 --- a/packages/pi-memory/package.json +++ b/packages/pi-memory/package.json @@ -44,8 +44,8 @@ "pack:check": "pnpm pack --pack-destination ../../.pack" }, "devDependencies": { - "@earendil-works/pi-ai": "^0.80.0", - "@earendil-works/pi-coding-agent": "^0.80.0", + "@earendil-works/pi-ai": "^0.80.9", + "@earendil-works/pi-coding-agent": "^0.80.9", "@types/node": "^25.5.0", "markdownlint-cli2": "^0.18.1", "oxfmt": "^0.42.0", diff --git a/packages/pi-web-search/package.json b/packages/pi-web-search/package.json index 3ec0fcb..dc849f4 100644 --- a/packages/pi-web-search/package.json +++ b/packages/pi-web-search/package.json @@ -48,8 +48,8 @@ "pack:check": "pnpm pack --pack-destination ../../.pack" }, "devDependencies": { - "@earendil-works/pi-ai": "^0.80.0", - "@earendil-works/pi-coding-agent": "^0.80.0", + "@earendil-works/pi-ai": "^0.80.9", + "@earendil-works/pi-coding-agent": "^0.80.9", "@types/node": "^25.5.0", "oxfmt": "^0.42.0", "oxlint": "^1.57.0", diff --git a/packages/skill-pi/CHANGELOG.md b/packages/skill-pi/CHANGELOG.md index 20ca783..1865640 100644 --- a/packages/skill-pi/CHANGELOG.md +++ b/packages/skill-pi/CHANGELOG.md @@ -1,5 +1,38 @@ # @counterposition/skill-pi +## 0.80.9 + +### Minor Changes + +- Sync the Pi skill with Pi 0.80.9, covering changes across 0.80.4–0.80.9. + - SDK (breaking in Pi 0.80.8): rewrite `references/sdk.md` around `ModelRuntime`, which + replaces the removed `AuthStorage`/`ModelRegistry` SDK surface and the + `authStorage`/`modelRegistry` options on `createAgentSession()`. Document + `ModelRuntime.create()` (custom paths, injected pi-ai `CredentialStore`), + auth resolution priority, `getAuth()`, `checkAuth()`, `readStoredCredential()`, + the `resolveCliModel`/`resolveModelScopeWithDiagnostics` helpers, named + `InlineExtension` factories, and the `agent_settled` session/RPC event with + `willRetry` on `agent_end`. + - Extensions: document dynamic tool loading (Pi 0.80.7) — additive + `pi.setActiveTools()` during execution, native deferred loading on Anthropic + and OpenAI Responses models, Kimi `deferredToolsMode` (Pi 0.80.9), compat + flags, and cache guidance. Add the `agent_settled` and + `before_provider_headers` hooks, `pi.registerEntryRenderer()` for TUI-only + custom entries, provider `refreshModels(context)` discovery, the async + `ModelRegistry.refresh()`, and `sessionManager.buildContextEntries()`. + - Providers: xAI subscription login and Radius gateway (Pi 0.80.8), Bedrock + `/login` API key (Pi 0.80.7), `/login ` autocomplete, live model + catalog refresh via `/model`, `pi update --models`, and `models-store.json`. + - models.json: replace the removed `compat.sendSessionIdHeader` with + `compat.sessionAffinityFormat` (breaking in Pi 0.80.7), add request-wide + input pricing `cost.tiers` (Pi 0.80.6), `max` in `thinkingLevelMap` with + hole semantics, and expanded `modelOverrides` (extension-registered models, + `thinkingLevelMap`). + - Settings: the `max` thinking level (Pi 0.80.6) across settings and CLI, + `showCacheMissNotices` (Pi 0.80.4), and `~` expansion for `shellPath`. + - Packages: `pi update --models`, `pi config -l` with Tab scope switching + (Pi 0.80.4), and `autoload: false` project-entry delta semantics. + ## 0.80.3 ### Minor Changes diff --git a/packages/skill-pi/package.json b/packages/skill-pi/package.json index c3faa4f..17f9382 100644 --- a/packages/skill-pi/package.json +++ b/packages/skill-pi/package.json @@ -1,6 +1,6 @@ { "name": "@counterposition/skill-pi", - "version": "0.80.3", + "version": "0.80.9", "description": "Pi coding agent reference skill — extensions, settings, providers, SDK/RPC, packages, and .pi/ config", "keywords": [ "ai-skill", diff --git a/packages/skill-pi/skills/pi/references/extensions.md b/packages/skill-pi/skills/pi/references/extensions.md index 43f462c..065dab2 100644 --- a/packages/skill-pi/skills/pi/references/extensions.md +++ b/packages/skill-pi/skills/pi/references/extensions.md @@ -90,13 +90,15 @@ Useful agent events: - `input` — carries `event.streamingBehavior` (`"steer" | "followUp" | undefined`) so handlers can distinguish idle prompts, mid-stream steers, and queued follow-ups; return `{ action: "transform" | "handled" | "continue" }` - `before_agent_start` — receives `event.systemPromptOptions` (a `BuildSystemPromptOptions`) so handlers can inspect the structured inputs feeding the system prompt - `agent_start` -- `agent_end` +- `agent_end` — one low-level run; Pi may still auto-retry, auto-compact and retry, or continue with queued follow-ups +- `agent_settled` (Pi 0.80.4) — fired when no retry/compaction/follow-up remains; use for status integrations that need to know Pi will not continue automatically (`ctx.isIdle()` is true here unless another extension started a new run) - `turn_start` - `turn_end` - `message_start` - `message_update` - `message_end` — return a replacement message to override usage/cost or rewrite the finalized assistant message - `context` +- `before_provider_headers` (Pi 0.80.4) — mutate `event.headers` in place after outgoing HTTP headers are assembled: set a key to a string to add/override, `null` to delete. Runs once per provider request; retries reuse the same headers - `before_provider_request` - `after_provider_response` — inspect the provider HTTP status and headers before stream consumption - `model_select` @@ -121,8 +123,8 @@ Tool results may include `terminate: true` to end the current tool batch without - `ctx.ui` for interactive UI hooks - `ctx.mode` — `"tui" | "rpc" | "json" | "print"`; gate terminal-only features on `ctx.mode === "tui"` - `ctx.cwd` -- `ctx.sessionManager` (read-only) -- `ctx.modelRegistry` / `ctx.model` +- `ctx.sessionManager` (read-only; `buildContextEntries()` returns active-branch entries with compaction applied, Pi 0.80.4) +- `ctx.modelRegistry` / `ctx.model` — `ctx.modelRegistry` is the synchronous extension-facing facade; its `refresh()` became `Promise` in Pi 0.80.8 (await it before synchronous registry reads) - `ctx.hasUI` — `true` in TUI and RPC modes - `ctx.signal` — the active agent abort signal (or `undefined` when idle); pass it to `fetch`/model calls for abort-aware nested work - `ctx.isIdle()` / `ctx.hasPendingMessages()` @@ -171,6 +173,19 @@ Important rules: 4. Use the file-mutation queue for write/edit style tools. 5. Put reconstructable state in `details`; it persists in session history. +## Dynamic Tool Loading + +Pi 0.80.7 lets an extension register many tools while keeping only a small initial set active, then add more during execution — cache-friendly on models with native deferred loading. Lifecycle: + +1. Register every tool with `pi.registerTool()` (all appear in `pi.getAllTools()`). +2. Keep a loader tool (e.g. `search_tools`) active; leave searchable tools inactive — e.g. on `session_start`, `pi.setActiveTools()` to the filtered set. +3. During loader execution, call `pi.setActiveTools([...pi.getActiveTools(), ...matches])`. The change must be purely additive; unknown names are ignored. +4. Pi records the added tools on that tool result and exposes their definitions before the next model response. + +Native deferred loading preserves the cached prompt prefix on Anthropic Sonnet/Opus/Fable ≥ 4.5 (not Haiku) and OpenAI `gpt-5.4`+; Kimi K3 works via `compat.deferredToolsMode: "kimi"` (Pi 0.80.9). For verified custom models/proxies, enable `compat.supportsToolReferences: true` (`anthropic-messages`) or `compat.supportsToolSearch: true` (`openai-responses`/`openai-codex-responses`). All other models fall back to sending the full active tool list on the next request — activation still works, but may invalidate the provider's cached prefix. Non-additive changes (removals/replacements) always use the fallback. + +Cache tips: keep the loader active for the whole session; add rather than replace. Activating a tool that has `promptSnippet`/`promptGuidelines` rebuilds the system prompt and can invalidate the prefix even with native support — lazily loaded tools should rely on their `description` alone. + ## UI Methods Check `ctx.hasUI` first. Non-interactive modes may not support UI. @@ -240,6 +255,15 @@ Store extension state with custom session entries: pi.appendEntry("my-extension-state", { key: "value" }); ``` +Custom entries never participate in LLM context. Since Pi 0.80.4 they can also render in the interactive transcript via `pi.registerEntryRenderer(customType, renderer)` — the TUI-only counterpart to `pi.registerMessageRenderer()` (whose custom messages DO enter LLM context via `pi.sendMessage()`): + +```typescript +pi.registerEntryRenderer("status-card", (entry, { expanded }, theme) => { + return new Text(theme.fg("accent", JSON.stringify(entry.data))); +}); +pi.appendEntry("status-card", { title: "Indexed files", count: 17 }); +``` + Use lifecycle hooks to restore it: ```typescript @@ -280,12 +304,16 @@ pi.registerProvider("anthropic", { baseUrl: "https://proxy.example.com" }); pi.unregisterProvider("my-provider"); ``` +Dynamic providers can implement `refreshModels(context)` (Pi 0.80.8) for model discovery: Pi calls it during catalog refresh (`/model`, `pi update --models`) and publishes the returned list; its models replace extension-provided `models`. Persist results through the scoped `context.store` only when they should survive restarts — live servers like llama.cpp can ignore it. + If you need auth for a specific model request, use: ```typescript const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model); ``` +(Still supported for extensions in Pi 0.80.8+; SDK code should use `ModelRuntime.getAuth()` — see `references/sdk.md`.) + Pass `shouldStopAfterTurn` via the SDK to exit the agent loop gracefully after a completed turn. See `references/sdk.md` and `references/providers.md` for the full provider/model schema. ## Handy Patterns diff --git a/packages/skill-pi/skills/pi/references/packages.md b/packages/skill-pi/skills/pi/references/packages.md index 1c4d640..9e2abbd 100644 --- a/packages/skill-pi/skills/pi/references/packages.md +++ b/packages/skill-pi/skills/pi/references/packages.md @@ -19,9 +19,11 @@ pi list pi update # update pi only (changed in Pi 0.79.7) pi update --all # update pi + packages, reconcile pinned git refs pi update --extensions # update packages only +pi update --models # refresh model catalogs only (Pi 0.80.8) pi update --self # update pi only pi update # update one package -pi config +pi config # starts in global settings; Tab switches scope +pi config -l # start in project overrides (.pi/settings.json) ``` By default, `install` and `remove` write to user settings (`~/.pi/agent/settings.json`). Use `-l` to write to project settings (`.pi/settings.json`) instead. `pi update` installs the exact version returned by the update check. @@ -129,6 +131,6 @@ Rules: ## Scope and Deduplication -- Project settings override global settings for the same package identity +- Project settings override global settings for the same package identity — unless the project entry has `autoload: false`, in which case it is applied as a delta over the global entry (Pi 0.80.4) - Identity is package name for npm, repo URL for git, and resolved absolute path for local sources -- `pi config` can enable or disable package resources after installation +- `pi config` can enable or disable package resources after installation; since Pi 0.80.4 it manages global vs project-local scopes (Tab to switch, `pi config -l` to start in project mode with inherited global resources dimmed) diff --git a/packages/skill-pi/skills/pi/references/providers.md b/packages/skill-pi/skills/pi/references/providers.md index bb75638..f6f5970 100644 --- a/packages/skill-pi/skills/pi/references/providers.md +++ b/packages/skill-pi/skills/pi/references/providers.md @@ -9,8 +9,12 @@ Use `/login` in interactive mode, then select a provider. The `/login` selector - Anthropic Claude Pro / Max — third-party usage draws from extra usage and is billed per token (suppress the warning via `warnings.anthropicExtraUsage`) - OpenAI ChatGPT Plus / Pro (Codex) — `/login` defaults to browser auth but can use a device-code flow for headless environments - GitHub Copilot +- xAI (Grok/X subscription, Pi 0.80.8) — `/login xai` then **Use a subscription** (device-code OAuth); `XAI_API_KEY` remains available via **Use an API key** +- Radius (Pi 0.80.8) — a dynamic `pi-messages` gateway; `/login radius` stores OAuth tokens, and custom Radius gateways can be declared in `models.json` with `"oauth": "radius"` plus a gateway `baseUrl` -Use `/logout` to clear stored OAuth credentials. Pi 0.71.0 removed built-in Google Gemini CLI and Google Antigravity providers. +`/login ` with autocomplete works since Pi 0.80.4, and login methods are provider-owned since 0.80.8 (registered pi-ai providers expose their own auth options and status in `/login`). Use `/logout` to clear stored OAuth credentials. Pi 0.71.0 removed built-in Google Gemini CLI and Google Antigravity providers. + +Since Pi 0.80.8, built-in catalogs are complemented by dynamic ones: `/model` refreshes configured providers in the background, `pi update --models` forces an immediate refresh, and refreshed catalogs are cached in `~/.pi/agent/models-store.json` for offline use. ## API Key Providers @@ -35,6 +39,8 @@ Use `/logout` to clear stored OAuth credentials. Pi 0.71.0 removed built-in Goog | ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` | `zai-coding-cn` | | OpenCode Zen | `OPENCODE_API_KEY` | `opencode` | | OpenCode Go | `OPENCODE_API_KEY` | `opencode-go` | +| Amazon Bedrock | `AWS_BEARER_TOKEN_BEDROCK` | `amazon-bedrock` | +| Radius | `RADIUS_API_KEY` | `radius` | | Hugging Face | `HF_TOKEN` | `huggingface` | | Fireworks | `FIREWORKS_API_KEY` | `fireworks` | | Together AI | `TOGETHER_API_KEY` | `together` | @@ -99,7 +105,7 @@ export AZURE_OPENAI_DEPLOYMENT_NAME_MAP=gpt-4o=my-gpt4o ### Amazon Bedrock -Pi supports standard AWS auth flows: +`/login amazon-bedrock` stores a Bedrock API key (Pi 0.80.7). Ambient AWS credential flows also work — these keep using SigV4 authentication: - `AWS_PROFILE` - `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` @@ -182,10 +188,14 @@ Common `api` values: ### Model & Compatibility Knobs -- `thinkingLevelMap` (Pi 0.72) replaces `compat.reasoningEffortMap`. Map pi levels (`off`/`minimal`/`low`/`medium`/`high`/`xhigh`) to provider values; use `null` to hide a level. +- `thinkingLevelMap` (Pi 0.72) replaces `compat.reasoningEffortMap`. Map pi levels (`off`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`) to provider values; use `null` to hide a level. Maps may contain holes (Pi 0.80.6) — e.g. expose `high` and `max` without `xhigh`. When a key is omitted, standard levels through `high` use the provider default mapping, but the extended `xhigh`/`max` levels are unsupported. +- `cost` supports request-wide input pricing tiers (Pi 0.80.6): a `tiers` array where each tier supplies a complete alternate rate set and applies to the whole request when total input usage (`input + cacheRead + cacheWrite`) exceeds `inputTokensAbove`; the highest matching threshold wins. Also usable in `modelOverrides` and extension-registered providers. +- `modelOverrides` applies to built-in and (since Pi 0.80.4) extension-registered provider models; per-model fields: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `headers`, `compat`. - `openRouterRouting` is forwarded as-is in the OpenRouter `provider` field (fallbacks, ZDR, ignore lists, throughput/latency). - `compat.thinkingFormat` supports OpenAI-compatible reasoning variants: `openrouter` sends `reasoning: { effort }`, `together` sends `reasoning: { enabled }` plus `reasoning_effort` when supported, `qwen-chat-template` targets local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking`, and `chat-template` (Pi 0.79.9) sends configurable `chat_template_kwargs` via `compat.chatTemplateKwargs` — e.g. `{ "thinking": { "$var": "thinking.enabled" } }` for DeepSeek models behind vLLM/Hugging Face chat templates (`"$var"` accepts `"thinking.enabled"` or `"thinking.effort"`). -- Advanced `compat` flags exist for proxy quirks (`cacheControlFormat`, `supportsReasoningEffort`, `supportsLongCacheRetention`, `supportsEagerToolInputStreaming`, `sendSessionIdHeader`, `sendSessionAffinityHeaders`). See [Pi `docs/models.md`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/models.md) when a proxy rejects pi's defaults. +- **Breaking (Pi 0.80.7):** `compat.sendSessionIdHeader` was removed. Session affinity is now controlled by `compat.sessionAffinityFormat` (`"openai"` sends `session_id`/`x-client-request-id`, `"openai-nosession"` omits the underscore-containing `session_id` header, `"openrouter"` sends `x-session-id`; default auto-detected). Replace `sendSessionIdHeader: false` with `sessionAffinityFormat: "openai-nosession"`. `sendSessionAffinityHeaders` still gates the behavior for `openai-completions`. +- `compat.deferredToolsMode: "kimi"` (Pi 0.80.9) enables Kimi's deferred tool serialization; `compat.supportsToolReferences` / `compat.supportsToolSearch` enable native dynamic tool loading on verified custom endpoints (see `references/extensions.md`). +- Advanced `compat` flags exist for proxy quirks (`cacheControlFormat`, `supportsReasoningEffort`, `supportsLongCacheRetention`, `supportsEagerToolInputStreaming`, `supportsStrictMode`). See [Pi `docs/models.md`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/models.md) when a proxy rejects pi's defaults. ### Context Overflow Recovery @@ -219,7 +229,7 @@ pi.registerProvider("anthropic", { baseUrl: "https://proxy.example.com" }); ## SDK / Extension Auth Lookup -If extension or SDK code needs auth for a specific model request, use `getApiKeyAndHeaders(model)` rather than the removed `getApiKey(model)`: +If extension code needs auth for a specific model request, use `getApiKeyAndHeaders(model)` rather than the removed `getApiKey(model)`: ```typescript const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model); @@ -228,4 +238,4 @@ if (!auth.ok) throw new Error(auth.error); const { apiKey, headers } = auth; ``` -This matters for providers whose headers or auth values resolve dynamically on every request. +This matters for providers whose headers or auth values resolve dynamically on every request. In SDK code, use `ModelRuntime.getAuth(providerOrModel)` instead (Pi 0.80.8) — passing a model also resolves built-in, `models.json`, and extension model headers; see `references/sdk.md`. diff --git a/packages/skill-pi/skills/pi/references/sdk.md b/packages/skill-pi/skills/pi/references/sdk.md index d8834f2..9479c7c 100644 --- a/packages/skill-pi/skills/pi/references/sdk.md +++ b/packages/skill-pi/skills/pi/references/sdk.md @@ -12,19 +12,16 @@ npm install @earendil-works/pi-coding-agent ```typescript import { - AuthStorage, createAgentSession, - ModelRegistry, + ModelRuntime, SessionManager, } from "@earendil-works/pi-coding-agent"; -const authStorage = AuthStorage.create(); -const modelRegistry = ModelRegistry.create(authStorage); +const modelRuntime = await ModelRuntime.create(); const { session } = await createAgentSession({ sessionManager: SessionManager.inMemory(), - authStorage, - modelRegistry, + modelRuntime, }); session.subscribe((event) => { @@ -38,17 +35,16 @@ await session.prompt("What files are in the current directory?"); Run with `npx tsx my-script.ts`. -**Important:** `ModelRegistry` no longer has a public constructor. Use `ModelRegistry.create(authStorage, modelsJsonPath?)` for file-backed registries or `ModelRegistry.inMemory(authStorage)` for built-in models only. +**Breaking (Pi 0.80.8):** `ModelRuntime` replaced the old `AuthStorage`/`ModelRegistry` pair as the SDK model/auth facade. `CreateAgentSessionOptions.authStorage` and `modelRegistry` are gone — pass the async `modelRuntime` instead. `AuthStorage` is no longer exported; use `ModelRuntime` (or a custom pi-ai `CredentialStore`), or `readStoredCredential()` for one-off reads of `auth.json`. `ModelRegistry` still exists only as the synchronous extension-facing compatibility facade, and its `refresh()` is now `Promise`. ## Key Imports ```typescript import { - AuthStorage, createAgentSession, DefaultResourceLoader, defineTool, - ModelRegistry, + ModelRuntime, SessionManager, } from "@earendil-works/pi-coding-agent"; import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all"; @@ -60,16 +56,14 @@ import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all"; ```typescript const cwd = "/path/to/project"; -const authStorage = AuthStorage.create(); -const modelRegistry = ModelRegistry.create(authStorage); +const modelRuntime = await ModelRuntime.create(); const { session } = await createAgentSession({ cwd, agentDir: "~/.pi/agent", - authStorage, - modelRegistry, + modelRuntime, model: getBuiltinModel("anthropic", "claude-opus-4-8"), - thinkingLevel: "medium", + thinkingLevel: "medium", // off | minimal | low | medium | high | xhigh | max scopedModels: [ { model: getBuiltinModel("anthropic", "claude-opus-4-8"), thinkingLevel: "high" }, ], @@ -90,6 +84,50 @@ Notes: - `DefaultResourceLoader` loads extensions, skills, prompt templates, themes, and context files. Replace it to drive resource discovery from custom sources (and it must implement `loadProjectContextFiles()` if you want `AGENTS.md`/`CLAUDE.md` discovery; that helper is also exported standalone). - Pass `shouldStopAfterTurn(state) => boolean` (Pi 0.72.0) to exit the agent loop gracefully after a completed turn. +## Models & Auth (`ModelRuntime`) + +`ModelRuntime` (Pi 0.80.8) implements the pi-ai `Models` interface and owns credential storage. Auth resolution priority: runtime overrides (`setRuntimeApiKey`, not persisted) → stored credentials in `auth.json` → environment variables → fallback resolver for `models.json` keys. + +```typescript +import { InMemoryCredentialStore } from "@earendil-works/pi-ai"; +import { ModelRuntime } from "@earendil-works/pi-coding-agent"; + +// Default: ~/.pi/agent/auth.json and ~/.pi/agent/models.json +const modelRuntime = await ModelRuntime.create(); + +// Model lookup (use pi-ai Models methods directly; the old +// getAll()/find()/getSnapshot() projections were removed) +const model = modelRuntime.getModel("my-provider", "my-model"); +const available = await modelRuntime.getAvailable(); // only models with valid auth + +// Provider-owned auth methods and current status +for (const provider of modelRuntime.getProviders()) { + const status = await modelRuntime.checkAuth(provider.id); +} + +modelRuntime.setRuntimeApiKey("anthropic", "sk-my-temp-key"); + +// Custom locations, or inject any pi-ai CredentialStore +const custom = await ModelRuntime.create({ authPath: "/my/auth.json", modelsPath: "/my/models.json" }); +const inMemory = await ModelRuntime.create({ credentials: new InMemoryCredentialStore() }); +``` + +`ModelRuntime.getAuth(providerOrModel)` assembles final request auth (replacing `ModelRegistry.getApiKeyAndHeaders()` on the SDK side); passing a model also resolves built-in, `models.json`, and extension model headers. Dynamic provider catalogs refresh via async `ModelRuntime.refresh()` and are cached in `~/.pi/agent/models-store.json`. + +To match CLI model parsing, use the exported resolver helpers (Pi 0.80.4): + +```typescript +import { resolveCliModel, resolveModelScopeWithDiagnostics } from "@earendil-works/pi-coding-agent"; + +const cliModel = resolveCliModel({ cliModel: "anthropic/claude-opus-4-8:high", modelRuntime }); +const { scopedModels, diagnostics } = await resolveModelScopeWithDiagnostics( + ["anthropic/*:high", "gpt-5"], + modelRuntime, +); +``` + +`resolveCliModel()` resolves against all registered models (so `--api-key`-style first-time setup works before stored auth exists); `resolveModelScopeWithDiagnostics()` matches `--models`/`enabledModels` semantics and returns warnings instead of printing. + ## Prompting & Queueing ```typescript @@ -193,7 +231,8 @@ session.subscribe((event) => { case "tool_execution_update": case "tool_execution_end": case "agent_start": - case "agent_end": + case "agent_end": // one low-level run; may be followed by retry/compaction/queued follow-ups + case "agent_settled": // fully settled — no automatic continuation left (Pi 0.80.4) case "turn_start": case "turn_end": case "queue_update": @@ -212,6 +251,7 @@ session.subscribe((event) => { `pi --mode rpc` speaks newline-delimited JSON over stdio. Additions since Pi 0.79: +- `agent_settled` event (Pi 0.80.4) fires when a run is fully settled — no automatic retry, compaction retry, or queued continuation remains; `agent_end` now carries `willRetry`. `set_thinking_level` accepts `"max"` where the model supports it. - `get_entries` / `get_tree` (Pi 0.80.3) read session entries and tree snapshots over RPC. - `@earendil-works/pi-coding-agent/rpc-entry` (Pi 0.80.3) launches Pi directly in RPC mode from an importing process. - RPC extension UI request/response types are exported from the public API (Pi 0.79.0). @@ -227,6 +267,20 @@ session.subscribe((event) => { - Add custom skills or prompt templates without touching disk - Share an event bus between the host app and loaded extensions +To name an inline factory in the startup Extensions list (instead of ``), wrap it in an `InlineExtension` (Pi 0.80.4): + +```typescript +import type { InlineExtension } from "@earendil-works/pi-coding-agent"; + +const myProvider: InlineExtension = { + name: "my-provider", + factory: (pi) => { /* ... */ }, +}; +const loader = new DefaultResourceLoader({ extensionFactories: [myProvider] }); +``` + +Bare factory functions are still accepted. + ## Standalone Custom Tools Use `defineTool()` when you want a reusable custom tool definition outside `pi.registerTool(...)`: diff --git a/packages/skill-pi/skills/pi/references/settings.md b/packages/skill-pi/skills/pi/references/settings.md index b57900e..f220b0d 100644 --- a/packages/skill-pi/skills/pi/references/settings.md +++ b/packages/skill-pi/skills/pi/references/settings.md @@ -17,6 +17,7 @@ Edit JSON directly or use `/settings` for common interactive options. "defaultModel": "claude-sonnet-4-20250514", "defaultThinkingLevel": "medium", "hideThinkingBlock": false, + "showCacheMissNotices": false, "thinkingBudgets": { "minimal": 1024, "low": 4096, @@ -26,8 +27,9 @@ Edit JSON directly or use `/settings` for common interactive options. } ``` -- Thinking levels: `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"` +- Thinking levels: `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"` — `max` (Pi 0.80.6) is an opt-in level above `xhigh`, exposed only when the selected model supports it (some models, e.g. GPT-5.6, expose both). Custom themes can define `thinkingMax`; existing themes fall back to `thinkingXhigh`. - `hideThinkingBlock` hides visible thinking output in the UI +- `showCacheMissNotices` (Pi 0.80.4) shows transcript notices for significant prompt-cache misses ## UI & Display @@ -125,6 +127,7 @@ Notes: } ``` +- `shellPath` supports a leading `~` for the home directory (Pi 0.80.6). - `npmCommand` is argv-style and is used for npm lookup/install operations, including git-package installs. User-scoped npm packages install under `~/.pi/agent/npm/`; project-scoped npm packages install under `.pi/npm/`. - `terminal.showTerminalProgress` (default `false` since Pi 0.70.0) toggles OSC 9;4 progress reporting in supporting terminals (iTerm2, WezTerm, Windows Terminal, Kitty). - `terminal.imageWidthCells` (Pi 0.68.1) caps inline tool-output image width in terminal cells. @@ -236,7 +239,7 @@ pi [options] [@files...] [messages...] --provider e.g. anthropic, openai, google --model supports provider/id and optional : --api-key ---thinking off|minimal|low|medium|high|xhigh +--thinking off|minimal|low|medium|high|xhigh|max --models Ctrl+P cycling allowlist --list-models [search] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0701ba4..e44185b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,11 +33,11 @@ importers: packages/pi-memory: devDependencies: '@earendil-works/pi-ai': - specifier: ^0.80.0 - version: 0.80.3(ws@8.20.0)(zod@4.3.6) + specifier: ^0.80.9 + version: 0.80.9(ws@8.20.0)(zod@4.3.6) '@earendil-works/pi-coding-agent': - specifier: ^0.80.0 - version: 0.80.3(ws@8.20.0)(zod@4.3.6) + specifier: ^0.80.9 + version: 0.80.9(ws@8.20.0)(zod@4.3.6) '@types/node': specifier: ^25.5.0 version: 25.5.0 @@ -63,11 +63,11 @@ importers: packages/pi-web-search: devDependencies: '@earendil-works/pi-ai': - specifier: ^0.80.0 - version: 0.80.3(ws@8.20.0)(zod@4.3.6) + specifier: ^0.80.9 + version: 0.80.9(ws@8.20.0)(zod@4.3.6) '@earendil-works/pi-coding-agent': - specifier: ^0.80.0 - version: 0.80.3(ws@8.20.0)(zod@4.3.6) + specifier: ^0.80.9 + version: 0.80.9(ws@8.20.0)(zod@4.3.6) '@types/node': specifier: ^25.5.0 version: 25.5.0 @@ -260,22 +260,22 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} - '@earendil-works/pi-agent-core@0.80.3': - resolution: {integrity: sha512-3qw0/GeRQBU/nlGjDe5Yb7ePKTmoxefx2YxyKMFAviFUMXpFexBG/hS7mBtwFahFvzrrTPPoRT6sFIDjwoDWPQ==} + '@earendil-works/pi-agent-core@0.80.9': + resolution: {integrity: sha512-tObjeOLiw1kYUciBi9R+rRyc4QGK+1akbLLQHvzsn2JrrV2btUdDncJ7jMIR5TKvOYKzKxAwQSl/5k7h3Tjrrg==} engines: {node: '>=22.19.0'} - '@earendil-works/pi-ai@0.80.3': - resolution: {integrity: sha512-jPZLMeGL5kkMSEAwAklfXTMHqZvfhsJtCCpKGIr5Duk7mc0n4skjB1dugk7y0z3z8ZHIUCmPAWHdyDqgUz5vdA==} + '@earendil-works/pi-ai@0.80.9': + resolution: {integrity: sha512-kHsH5nO4FU7mbKnskK0BVPVuWzNb2DrZtiN1fb6LamP+6BMI8xEZiAOw2fqs4VudvlMQgOLjtbgErv+kNJRPIg==} engines: {node: '>=22.19.0'} hasBin: true - '@earendil-works/pi-coding-agent@0.80.3': - resolution: {integrity: sha512-TIggw9gCXpA+Ph7OjdTA7ka2NPwTVuPmy39KDSyUzaKq8VvHfMGR7vtRz4JB7Um/RMRblmzhu4p9tUCk6MTgGA==} + '@earendil-works/pi-coding-agent@0.80.9': + resolution: {integrity: sha512-Clgx2Bg5NbMcCpGxusSDQwE+GC0g/d6sCBluE9aypPgSgtJ6n8VmZIIT6auXObMskpRgkr+XZ77wG5hf+cSDtg==} engines: {node: '>=22.19.0'} hasBin: true - '@earendil-works/pi-tui@0.80.3': - resolution: {integrity: sha512-2BJI6qwRQfnM0Q7seL1+SbacU/jRRjBnN7Hu3n9BjAn7/s5FaBNnvdD1qBQYRsFTHfjqMaDsjYqanPyqwXj99w==} + '@earendil-works/pi-tui@0.80.9': + resolution: {integrity: sha512-unPTW8hRgIHEGjV8mJJ2jqm+fzgnRubes6V2FPk9ay1W9ZLofcpYQ3NDfrODXSci+oKbBpX9JyYUMfQV6jCA/A==} engines: {node: '>=22.19.0'} '@emnapi/core@1.9.1': @@ -2270,9 +2270,9 @@ snapshots: human-id: 4.1.3 prettier: 2.8.8 - '@earendil-works/pi-agent-core@0.80.3(ws@8.20.0)(zod@4.3.6)': + '@earendil-works/pi-agent-core@0.80.9(ws@8.20.0)(zod@4.3.6)': dependencies: - '@earendil-works/pi-ai': 0.80.3(ws@8.20.0)(zod@4.3.6) + '@earendil-works/pi-ai': 0.80.9(ws@8.20.0)(zod@4.3.6) ignore: 7.0.5 typebox: 1.1.38 yaml: 2.9.0 @@ -2284,7 +2284,7 @@ snapshots: - ws - zod - '@earendil-works/pi-ai@0.80.3(ws@8.20.0)(zod@4.3.6)': + '@earendil-works/pi-ai@0.80.9(ws@8.20.0)(zod@4.3.6)': dependencies: '@anthropic-ai/sdk': 0.91.1(zod@4.3.6) '@aws-sdk/client-bedrock-runtime': 3.1048.0 @@ -2305,11 +2305,11 @@ snapshots: - ws - zod - '@earendil-works/pi-coding-agent@0.80.3(ws@8.20.0)(zod@4.3.6)': + '@earendil-works/pi-coding-agent@0.80.9(ws@8.20.0)(zod@4.3.6)': dependencies: - '@earendil-works/pi-agent-core': 0.80.3(ws@8.20.0)(zod@4.3.6) - '@earendil-works/pi-ai': 0.80.3(ws@8.20.0)(zod@4.3.6) - '@earendil-works/pi-tui': 0.80.3 + '@earendil-works/pi-agent-core': 0.80.9(ws@8.20.0)(zod@4.3.6) + '@earendil-works/pi-ai': 0.80.9(ws@8.20.0)(zod@4.3.6) + '@earendil-works/pi-tui': 0.80.9 '@silvia-odwyer/photon-node': 0.3.4 chalk: 5.6.2 cross-spawn: 7.0.6 @@ -2335,7 +2335,7 @@ snapshots: - ws - zod - '@earendil-works/pi-tui@0.80.3': + '@earendil-works/pi-tui@0.80.9': dependencies: get-east-asian-width: 1.6.0 marked: 18.0.5 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 924b55f..4976c8d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,5 @@ packages: - packages/* +# Keep pnpm's release quarantine, but track same-day Pi releases. +minimumReleaseAgeExclude: + - '@earendil-works/*' diff --git a/skills/pi/references/extensions.md b/skills/pi/references/extensions.md index 43f462c..065dab2 100644 --- a/skills/pi/references/extensions.md +++ b/skills/pi/references/extensions.md @@ -90,13 +90,15 @@ Useful agent events: - `input` — carries `event.streamingBehavior` (`"steer" | "followUp" | undefined`) so handlers can distinguish idle prompts, mid-stream steers, and queued follow-ups; return `{ action: "transform" | "handled" | "continue" }` - `before_agent_start` — receives `event.systemPromptOptions` (a `BuildSystemPromptOptions`) so handlers can inspect the structured inputs feeding the system prompt - `agent_start` -- `agent_end` +- `agent_end` — one low-level run; Pi may still auto-retry, auto-compact and retry, or continue with queued follow-ups +- `agent_settled` (Pi 0.80.4) — fired when no retry/compaction/follow-up remains; use for status integrations that need to know Pi will not continue automatically (`ctx.isIdle()` is true here unless another extension started a new run) - `turn_start` - `turn_end` - `message_start` - `message_update` - `message_end` — return a replacement message to override usage/cost or rewrite the finalized assistant message - `context` +- `before_provider_headers` (Pi 0.80.4) — mutate `event.headers` in place after outgoing HTTP headers are assembled: set a key to a string to add/override, `null` to delete. Runs once per provider request; retries reuse the same headers - `before_provider_request` - `after_provider_response` — inspect the provider HTTP status and headers before stream consumption - `model_select` @@ -121,8 +123,8 @@ Tool results may include `terminate: true` to end the current tool batch without - `ctx.ui` for interactive UI hooks - `ctx.mode` — `"tui" | "rpc" | "json" | "print"`; gate terminal-only features on `ctx.mode === "tui"` - `ctx.cwd` -- `ctx.sessionManager` (read-only) -- `ctx.modelRegistry` / `ctx.model` +- `ctx.sessionManager` (read-only; `buildContextEntries()` returns active-branch entries with compaction applied, Pi 0.80.4) +- `ctx.modelRegistry` / `ctx.model` — `ctx.modelRegistry` is the synchronous extension-facing facade; its `refresh()` became `Promise` in Pi 0.80.8 (await it before synchronous registry reads) - `ctx.hasUI` — `true` in TUI and RPC modes - `ctx.signal` — the active agent abort signal (or `undefined` when idle); pass it to `fetch`/model calls for abort-aware nested work - `ctx.isIdle()` / `ctx.hasPendingMessages()` @@ -171,6 +173,19 @@ Important rules: 4. Use the file-mutation queue for write/edit style tools. 5. Put reconstructable state in `details`; it persists in session history. +## Dynamic Tool Loading + +Pi 0.80.7 lets an extension register many tools while keeping only a small initial set active, then add more during execution — cache-friendly on models with native deferred loading. Lifecycle: + +1. Register every tool with `pi.registerTool()` (all appear in `pi.getAllTools()`). +2. Keep a loader tool (e.g. `search_tools`) active; leave searchable tools inactive — e.g. on `session_start`, `pi.setActiveTools()` to the filtered set. +3. During loader execution, call `pi.setActiveTools([...pi.getActiveTools(), ...matches])`. The change must be purely additive; unknown names are ignored. +4. Pi records the added tools on that tool result and exposes their definitions before the next model response. + +Native deferred loading preserves the cached prompt prefix on Anthropic Sonnet/Opus/Fable ≥ 4.5 (not Haiku) and OpenAI `gpt-5.4`+; Kimi K3 works via `compat.deferredToolsMode: "kimi"` (Pi 0.80.9). For verified custom models/proxies, enable `compat.supportsToolReferences: true` (`anthropic-messages`) or `compat.supportsToolSearch: true` (`openai-responses`/`openai-codex-responses`). All other models fall back to sending the full active tool list on the next request — activation still works, but may invalidate the provider's cached prefix. Non-additive changes (removals/replacements) always use the fallback. + +Cache tips: keep the loader active for the whole session; add rather than replace. Activating a tool that has `promptSnippet`/`promptGuidelines` rebuilds the system prompt and can invalidate the prefix even with native support — lazily loaded tools should rely on their `description` alone. + ## UI Methods Check `ctx.hasUI` first. Non-interactive modes may not support UI. @@ -240,6 +255,15 @@ Store extension state with custom session entries: pi.appendEntry("my-extension-state", { key: "value" }); ``` +Custom entries never participate in LLM context. Since Pi 0.80.4 they can also render in the interactive transcript via `pi.registerEntryRenderer(customType, renderer)` — the TUI-only counterpart to `pi.registerMessageRenderer()` (whose custom messages DO enter LLM context via `pi.sendMessage()`): + +```typescript +pi.registerEntryRenderer("status-card", (entry, { expanded }, theme) => { + return new Text(theme.fg("accent", JSON.stringify(entry.data))); +}); +pi.appendEntry("status-card", { title: "Indexed files", count: 17 }); +``` + Use lifecycle hooks to restore it: ```typescript @@ -280,12 +304,16 @@ pi.registerProvider("anthropic", { baseUrl: "https://proxy.example.com" }); pi.unregisterProvider("my-provider"); ``` +Dynamic providers can implement `refreshModels(context)` (Pi 0.80.8) for model discovery: Pi calls it during catalog refresh (`/model`, `pi update --models`) and publishes the returned list; its models replace extension-provided `models`. Persist results through the scoped `context.store` only when they should survive restarts — live servers like llama.cpp can ignore it. + If you need auth for a specific model request, use: ```typescript const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model); ``` +(Still supported for extensions in Pi 0.80.8+; SDK code should use `ModelRuntime.getAuth()` — see `references/sdk.md`.) + Pass `shouldStopAfterTurn` via the SDK to exit the agent loop gracefully after a completed turn. See `references/sdk.md` and `references/providers.md` for the full provider/model schema. ## Handy Patterns diff --git a/skills/pi/references/packages.md b/skills/pi/references/packages.md index 1c4d640..9e2abbd 100644 --- a/skills/pi/references/packages.md +++ b/skills/pi/references/packages.md @@ -19,9 +19,11 @@ pi list pi update # update pi only (changed in Pi 0.79.7) pi update --all # update pi + packages, reconcile pinned git refs pi update --extensions # update packages only +pi update --models # refresh model catalogs only (Pi 0.80.8) pi update --self # update pi only pi update # update one package -pi config +pi config # starts in global settings; Tab switches scope +pi config -l # start in project overrides (.pi/settings.json) ``` By default, `install` and `remove` write to user settings (`~/.pi/agent/settings.json`). Use `-l` to write to project settings (`.pi/settings.json`) instead. `pi update` installs the exact version returned by the update check. @@ -129,6 +131,6 @@ Rules: ## Scope and Deduplication -- Project settings override global settings for the same package identity +- Project settings override global settings for the same package identity — unless the project entry has `autoload: false`, in which case it is applied as a delta over the global entry (Pi 0.80.4) - Identity is package name for npm, repo URL for git, and resolved absolute path for local sources -- `pi config` can enable or disable package resources after installation +- `pi config` can enable or disable package resources after installation; since Pi 0.80.4 it manages global vs project-local scopes (Tab to switch, `pi config -l` to start in project mode with inherited global resources dimmed) diff --git a/skills/pi/references/providers.md b/skills/pi/references/providers.md index bb75638..f6f5970 100644 --- a/skills/pi/references/providers.md +++ b/skills/pi/references/providers.md @@ -9,8 +9,12 @@ Use `/login` in interactive mode, then select a provider. The `/login` selector - Anthropic Claude Pro / Max — third-party usage draws from extra usage and is billed per token (suppress the warning via `warnings.anthropicExtraUsage`) - OpenAI ChatGPT Plus / Pro (Codex) — `/login` defaults to browser auth but can use a device-code flow for headless environments - GitHub Copilot +- xAI (Grok/X subscription, Pi 0.80.8) — `/login xai` then **Use a subscription** (device-code OAuth); `XAI_API_KEY` remains available via **Use an API key** +- Radius (Pi 0.80.8) — a dynamic `pi-messages` gateway; `/login radius` stores OAuth tokens, and custom Radius gateways can be declared in `models.json` with `"oauth": "radius"` plus a gateway `baseUrl` -Use `/logout` to clear stored OAuth credentials. Pi 0.71.0 removed built-in Google Gemini CLI and Google Antigravity providers. +`/login ` with autocomplete works since Pi 0.80.4, and login methods are provider-owned since 0.80.8 (registered pi-ai providers expose their own auth options and status in `/login`). Use `/logout` to clear stored OAuth credentials. Pi 0.71.0 removed built-in Google Gemini CLI and Google Antigravity providers. + +Since Pi 0.80.8, built-in catalogs are complemented by dynamic ones: `/model` refreshes configured providers in the background, `pi update --models` forces an immediate refresh, and refreshed catalogs are cached in `~/.pi/agent/models-store.json` for offline use. ## API Key Providers @@ -35,6 +39,8 @@ Use `/logout` to clear stored OAuth credentials. Pi 0.71.0 removed built-in Goog | ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` | `zai-coding-cn` | | OpenCode Zen | `OPENCODE_API_KEY` | `opencode` | | OpenCode Go | `OPENCODE_API_KEY` | `opencode-go` | +| Amazon Bedrock | `AWS_BEARER_TOKEN_BEDROCK` | `amazon-bedrock` | +| Radius | `RADIUS_API_KEY` | `radius` | | Hugging Face | `HF_TOKEN` | `huggingface` | | Fireworks | `FIREWORKS_API_KEY` | `fireworks` | | Together AI | `TOGETHER_API_KEY` | `together` | @@ -99,7 +105,7 @@ export AZURE_OPENAI_DEPLOYMENT_NAME_MAP=gpt-4o=my-gpt4o ### Amazon Bedrock -Pi supports standard AWS auth flows: +`/login amazon-bedrock` stores a Bedrock API key (Pi 0.80.7). Ambient AWS credential flows also work — these keep using SigV4 authentication: - `AWS_PROFILE` - `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` @@ -182,10 +188,14 @@ Common `api` values: ### Model & Compatibility Knobs -- `thinkingLevelMap` (Pi 0.72) replaces `compat.reasoningEffortMap`. Map pi levels (`off`/`minimal`/`low`/`medium`/`high`/`xhigh`) to provider values; use `null` to hide a level. +- `thinkingLevelMap` (Pi 0.72) replaces `compat.reasoningEffortMap`. Map pi levels (`off`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`) to provider values; use `null` to hide a level. Maps may contain holes (Pi 0.80.6) — e.g. expose `high` and `max` without `xhigh`. When a key is omitted, standard levels through `high` use the provider default mapping, but the extended `xhigh`/`max` levels are unsupported. +- `cost` supports request-wide input pricing tiers (Pi 0.80.6): a `tiers` array where each tier supplies a complete alternate rate set and applies to the whole request when total input usage (`input + cacheRead + cacheWrite`) exceeds `inputTokensAbove`; the highest matching threshold wins. Also usable in `modelOverrides` and extension-registered providers. +- `modelOverrides` applies to built-in and (since Pi 0.80.4) extension-registered provider models; per-model fields: `name`, `reasoning`, `thinkingLevelMap`, `input`, `cost` (partial), `contextWindow`, `maxTokens`, `headers`, `compat`. - `openRouterRouting` is forwarded as-is in the OpenRouter `provider` field (fallbacks, ZDR, ignore lists, throughput/latency). - `compat.thinkingFormat` supports OpenAI-compatible reasoning variants: `openrouter` sends `reasoning: { effort }`, `together` sends `reasoning: { enabled }` plus `reasoning_effort` when supported, `qwen-chat-template` targets local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking`, and `chat-template` (Pi 0.79.9) sends configurable `chat_template_kwargs` via `compat.chatTemplateKwargs` — e.g. `{ "thinking": { "$var": "thinking.enabled" } }` for DeepSeek models behind vLLM/Hugging Face chat templates (`"$var"` accepts `"thinking.enabled"` or `"thinking.effort"`). -- Advanced `compat` flags exist for proxy quirks (`cacheControlFormat`, `supportsReasoningEffort`, `supportsLongCacheRetention`, `supportsEagerToolInputStreaming`, `sendSessionIdHeader`, `sendSessionAffinityHeaders`). See [Pi `docs/models.md`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/models.md) when a proxy rejects pi's defaults. +- **Breaking (Pi 0.80.7):** `compat.sendSessionIdHeader` was removed. Session affinity is now controlled by `compat.sessionAffinityFormat` (`"openai"` sends `session_id`/`x-client-request-id`, `"openai-nosession"` omits the underscore-containing `session_id` header, `"openrouter"` sends `x-session-id`; default auto-detected). Replace `sendSessionIdHeader: false` with `sessionAffinityFormat: "openai-nosession"`. `sendSessionAffinityHeaders` still gates the behavior for `openai-completions`. +- `compat.deferredToolsMode: "kimi"` (Pi 0.80.9) enables Kimi's deferred tool serialization; `compat.supportsToolReferences` / `compat.supportsToolSearch` enable native dynamic tool loading on verified custom endpoints (see `references/extensions.md`). +- Advanced `compat` flags exist for proxy quirks (`cacheControlFormat`, `supportsReasoningEffort`, `supportsLongCacheRetention`, `supportsEagerToolInputStreaming`, `supportsStrictMode`). See [Pi `docs/models.md`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/models.md) when a proxy rejects pi's defaults. ### Context Overflow Recovery @@ -219,7 +229,7 @@ pi.registerProvider("anthropic", { baseUrl: "https://proxy.example.com" }); ## SDK / Extension Auth Lookup -If extension or SDK code needs auth for a specific model request, use `getApiKeyAndHeaders(model)` rather than the removed `getApiKey(model)`: +If extension code needs auth for a specific model request, use `getApiKeyAndHeaders(model)` rather than the removed `getApiKey(model)`: ```typescript const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model); @@ -228,4 +238,4 @@ if (!auth.ok) throw new Error(auth.error); const { apiKey, headers } = auth; ``` -This matters for providers whose headers or auth values resolve dynamically on every request. +This matters for providers whose headers or auth values resolve dynamically on every request. In SDK code, use `ModelRuntime.getAuth(providerOrModel)` instead (Pi 0.80.8) — passing a model also resolves built-in, `models.json`, and extension model headers; see `references/sdk.md`. diff --git a/skills/pi/references/sdk.md b/skills/pi/references/sdk.md index d8834f2..9479c7c 100644 --- a/skills/pi/references/sdk.md +++ b/skills/pi/references/sdk.md @@ -12,19 +12,16 @@ npm install @earendil-works/pi-coding-agent ```typescript import { - AuthStorage, createAgentSession, - ModelRegistry, + ModelRuntime, SessionManager, } from "@earendil-works/pi-coding-agent"; -const authStorage = AuthStorage.create(); -const modelRegistry = ModelRegistry.create(authStorage); +const modelRuntime = await ModelRuntime.create(); const { session } = await createAgentSession({ sessionManager: SessionManager.inMemory(), - authStorage, - modelRegistry, + modelRuntime, }); session.subscribe((event) => { @@ -38,17 +35,16 @@ await session.prompt("What files are in the current directory?"); Run with `npx tsx my-script.ts`. -**Important:** `ModelRegistry` no longer has a public constructor. Use `ModelRegistry.create(authStorage, modelsJsonPath?)` for file-backed registries or `ModelRegistry.inMemory(authStorage)` for built-in models only. +**Breaking (Pi 0.80.8):** `ModelRuntime` replaced the old `AuthStorage`/`ModelRegistry` pair as the SDK model/auth facade. `CreateAgentSessionOptions.authStorage` and `modelRegistry` are gone — pass the async `modelRuntime` instead. `AuthStorage` is no longer exported; use `ModelRuntime` (or a custom pi-ai `CredentialStore`), or `readStoredCredential()` for one-off reads of `auth.json`. `ModelRegistry` still exists only as the synchronous extension-facing compatibility facade, and its `refresh()` is now `Promise`. ## Key Imports ```typescript import { - AuthStorage, createAgentSession, DefaultResourceLoader, defineTool, - ModelRegistry, + ModelRuntime, SessionManager, } from "@earendil-works/pi-coding-agent"; import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all"; @@ -60,16 +56,14 @@ import { getBuiltinModel } from "@earendil-works/pi-ai/providers/all"; ```typescript const cwd = "/path/to/project"; -const authStorage = AuthStorage.create(); -const modelRegistry = ModelRegistry.create(authStorage); +const modelRuntime = await ModelRuntime.create(); const { session } = await createAgentSession({ cwd, agentDir: "~/.pi/agent", - authStorage, - modelRegistry, + modelRuntime, model: getBuiltinModel("anthropic", "claude-opus-4-8"), - thinkingLevel: "medium", + thinkingLevel: "medium", // off | minimal | low | medium | high | xhigh | max scopedModels: [ { model: getBuiltinModel("anthropic", "claude-opus-4-8"), thinkingLevel: "high" }, ], @@ -90,6 +84,50 @@ Notes: - `DefaultResourceLoader` loads extensions, skills, prompt templates, themes, and context files. Replace it to drive resource discovery from custom sources (and it must implement `loadProjectContextFiles()` if you want `AGENTS.md`/`CLAUDE.md` discovery; that helper is also exported standalone). - Pass `shouldStopAfterTurn(state) => boolean` (Pi 0.72.0) to exit the agent loop gracefully after a completed turn. +## Models & Auth (`ModelRuntime`) + +`ModelRuntime` (Pi 0.80.8) implements the pi-ai `Models` interface and owns credential storage. Auth resolution priority: runtime overrides (`setRuntimeApiKey`, not persisted) → stored credentials in `auth.json` → environment variables → fallback resolver for `models.json` keys. + +```typescript +import { InMemoryCredentialStore } from "@earendil-works/pi-ai"; +import { ModelRuntime } from "@earendil-works/pi-coding-agent"; + +// Default: ~/.pi/agent/auth.json and ~/.pi/agent/models.json +const modelRuntime = await ModelRuntime.create(); + +// Model lookup (use pi-ai Models methods directly; the old +// getAll()/find()/getSnapshot() projections were removed) +const model = modelRuntime.getModel("my-provider", "my-model"); +const available = await modelRuntime.getAvailable(); // only models with valid auth + +// Provider-owned auth methods and current status +for (const provider of modelRuntime.getProviders()) { + const status = await modelRuntime.checkAuth(provider.id); +} + +modelRuntime.setRuntimeApiKey("anthropic", "sk-my-temp-key"); + +// Custom locations, or inject any pi-ai CredentialStore +const custom = await ModelRuntime.create({ authPath: "/my/auth.json", modelsPath: "/my/models.json" }); +const inMemory = await ModelRuntime.create({ credentials: new InMemoryCredentialStore() }); +``` + +`ModelRuntime.getAuth(providerOrModel)` assembles final request auth (replacing `ModelRegistry.getApiKeyAndHeaders()` on the SDK side); passing a model also resolves built-in, `models.json`, and extension model headers. Dynamic provider catalogs refresh via async `ModelRuntime.refresh()` and are cached in `~/.pi/agent/models-store.json`. + +To match CLI model parsing, use the exported resolver helpers (Pi 0.80.4): + +```typescript +import { resolveCliModel, resolveModelScopeWithDiagnostics } from "@earendil-works/pi-coding-agent"; + +const cliModel = resolveCliModel({ cliModel: "anthropic/claude-opus-4-8:high", modelRuntime }); +const { scopedModels, diagnostics } = await resolveModelScopeWithDiagnostics( + ["anthropic/*:high", "gpt-5"], + modelRuntime, +); +``` + +`resolveCliModel()` resolves against all registered models (so `--api-key`-style first-time setup works before stored auth exists); `resolveModelScopeWithDiagnostics()` matches `--models`/`enabledModels` semantics and returns warnings instead of printing. + ## Prompting & Queueing ```typescript @@ -193,7 +231,8 @@ session.subscribe((event) => { case "tool_execution_update": case "tool_execution_end": case "agent_start": - case "agent_end": + case "agent_end": // one low-level run; may be followed by retry/compaction/queued follow-ups + case "agent_settled": // fully settled — no automatic continuation left (Pi 0.80.4) case "turn_start": case "turn_end": case "queue_update": @@ -212,6 +251,7 @@ session.subscribe((event) => { `pi --mode rpc` speaks newline-delimited JSON over stdio. Additions since Pi 0.79: +- `agent_settled` event (Pi 0.80.4) fires when a run is fully settled — no automatic retry, compaction retry, or queued continuation remains; `agent_end` now carries `willRetry`. `set_thinking_level` accepts `"max"` where the model supports it. - `get_entries` / `get_tree` (Pi 0.80.3) read session entries and tree snapshots over RPC. - `@earendil-works/pi-coding-agent/rpc-entry` (Pi 0.80.3) launches Pi directly in RPC mode from an importing process. - RPC extension UI request/response types are exported from the public API (Pi 0.79.0). @@ -227,6 +267,20 @@ session.subscribe((event) => { - Add custom skills or prompt templates without touching disk - Share an event bus between the host app and loaded extensions +To name an inline factory in the startup Extensions list (instead of ``), wrap it in an `InlineExtension` (Pi 0.80.4): + +```typescript +import type { InlineExtension } from "@earendil-works/pi-coding-agent"; + +const myProvider: InlineExtension = { + name: "my-provider", + factory: (pi) => { /* ... */ }, +}; +const loader = new DefaultResourceLoader({ extensionFactories: [myProvider] }); +``` + +Bare factory functions are still accepted. + ## Standalone Custom Tools Use `defineTool()` when you want a reusable custom tool definition outside `pi.registerTool(...)`: diff --git a/skills/pi/references/settings.md b/skills/pi/references/settings.md index b57900e..f220b0d 100644 --- a/skills/pi/references/settings.md +++ b/skills/pi/references/settings.md @@ -17,6 +17,7 @@ Edit JSON directly or use `/settings` for common interactive options. "defaultModel": "claude-sonnet-4-20250514", "defaultThinkingLevel": "medium", "hideThinkingBlock": false, + "showCacheMissNotices": false, "thinkingBudgets": { "minimal": 1024, "low": 4096, @@ -26,8 +27,9 @@ Edit JSON directly or use `/settings` for common interactive options. } ``` -- Thinking levels: `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"` +- Thinking levels: `"off"`, `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`, `"max"` — `max` (Pi 0.80.6) is an opt-in level above `xhigh`, exposed only when the selected model supports it (some models, e.g. GPT-5.6, expose both). Custom themes can define `thinkingMax`; existing themes fall back to `thinkingXhigh`. - `hideThinkingBlock` hides visible thinking output in the UI +- `showCacheMissNotices` (Pi 0.80.4) shows transcript notices for significant prompt-cache misses ## UI & Display @@ -125,6 +127,7 @@ Notes: } ``` +- `shellPath` supports a leading `~` for the home directory (Pi 0.80.6). - `npmCommand` is argv-style and is used for npm lookup/install operations, including git-package installs. User-scoped npm packages install under `~/.pi/agent/npm/`; project-scoped npm packages install under `.pi/npm/`. - `terminal.showTerminalProgress` (default `false` since Pi 0.70.0) toggles OSC 9;4 progress reporting in supporting terminals (iTerm2, WezTerm, Windows Terminal, Kitty). - `terminal.imageWidthCells` (Pi 0.68.1) caps inline tool-output image width in terminal cells. @@ -236,7 +239,7 @@ pi [options] [@files...] [messages...] --provider e.g. anthropic, openai, google --model supports provider/id and optional : --api-key ---thinking off|minimal|low|medium|high|xhigh +--thinking off|minimal|low|medium|high|xhigh|max --models Ctrl+P cycling allowlist --list-models [search]