@earendil-works/pi-coding-agent 0.80.6 → 0.80.8

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.
Files changed (172) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +9 -7
  3. package/dist/bun/cli.d.ts.map +1 -1
  4. package/dist/bun/cli.js +2 -0
  5. package/dist/bun/cli.js.map +1 -1
  6. package/dist/cli/args.d.ts.map +1 -1
  7. package/dist/cli/args.js +1 -1
  8. package/dist/cli/args.js.map +1 -1
  9. package/dist/cli/list-models.d.ts +2 -2
  10. package/dist/cli/list-models.d.ts.map +1 -1
  11. package/dist/cli/list-models.js +3 -3
  12. package/dist/cli/list-models.js.map +1 -1
  13. package/dist/core/agent-session-services.d.ts +3 -6
  14. package/dist/core/agent-session-services.d.ts.map +1 -1
  15. package/dist/core/agent-session-services.js +10 -9
  16. package/dist/core/agent-session-services.js.map +1 -1
  17. package/dist/core/agent-session.d.ts +6 -7
  18. package/dist/core/agent-session.d.ts.map +1 -1
  19. package/dist/core/agent-session.js +61 -34
  20. package/dist/core/agent-session.js.map +1 -1
  21. package/dist/core/auth-storage.d.ts +15 -99
  22. package/dist/core/auth-storage.d.ts.map +1 -1
  23. package/dist/core/auth-storage.js +46 -259
  24. package/dist/core/auth-storage.js.map +1 -1
  25. package/dist/core/cache-stats.d.ts +2 -2
  26. package/dist/core/cache-stats.d.ts.map +1 -1
  27. package/dist/core/cache-stats.js +1 -1
  28. package/dist/core/cache-stats.js.map +1 -1
  29. package/dist/core/compaction/branch-summarization.d.ts +1 -1
  30. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  31. package/dist/core/compaction/branch-summarization.js.map +1 -1
  32. package/dist/core/extensions/loader.d.ts.map +1 -1
  33. package/dist/core/extensions/loader.js +8 -2
  34. package/dist/core/extensions/loader.js.map +1 -1
  35. package/dist/core/extensions/runner.d.ts +2 -0
  36. package/dist/core/extensions/runner.d.ts.map +1 -1
  37. package/dist/core/extensions/runner.js +8 -0
  38. package/dist/core/extensions/runner.js.map +1 -1
  39. package/dist/core/extensions/types.d.ts +9 -2
  40. package/dist/core/extensions/types.d.ts.map +1 -1
  41. package/dist/core/extensions/types.js.map +1 -1
  42. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  43. package/dist/core/extensions/wrapper.js +22 -3
  44. package/dist/core/extensions/wrapper.js.map +1 -1
  45. package/dist/core/keybindings.d.ts +8 -3
  46. package/dist/core/keybindings.d.ts.map +1 -1
  47. package/dist/core/keybindings.js +7 -3
  48. package/dist/core/keybindings.js.map +1 -1
  49. package/dist/core/model-config.d.ts +507 -0
  50. package/dist/core/model-config.d.ts.map +1 -0
  51. package/dist/core/model-config.js +242 -0
  52. package/dist/core/model-config.js.map +1 -0
  53. package/dist/core/model-registry.d.ts +13 -123
  54. package/dist/core/model-registry.d.ts.map +1 -1
  55. package/dist/core/model-registry.js +44 -731
  56. package/dist/core/model-registry.js.map +1 -1
  57. package/dist/core/model-resolver.d.ts +6 -6
  58. package/dist/core/model-resolver.d.ts.map +1 -1
  59. package/dist/core/model-resolver.js +18 -17
  60. package/dist/core/model-resolver.js.map +1 -1
  61. package/dist/core/model-runtime.d.ts +77 -0
  62. package/dist/core/model-runtime.d.ts.map +1 -0
  63. package/dist/core/model-runtime.js +418 -0
  64. package/dist/core/model-runtime.js.map +1 -0
  65. package/dist/core/models-store.d.ts +17 -0
  66. package/dist/core/models-store.d.ts.map +1 -0
  67. package/dist/core/models-store.js +45 -0
  68. package/dist/core/models-store.js.map +1 -0
  69. package/dist/core/package-manager.d.ts.map +1 -1
  70. package/dist/core/package-manager.js +7 -2
  71. package/dist/core/package-manager.js.map +1 -1
  72. package/dist/core/provider-composer.d.ts +55 -0
  73. package/dist/core/provider-composer.d.ts.map +1 -0
  74. package/dist/core/provider-composer.js +375 -0
  75. package/dist/core/provider-composer.js.map +1 -0
  76. package/dist/core/radius.d.ts +2 -0
  77. package/dist/core/radius.d.ts.map +1 -0
  78. package/dist/core/radius.js +2 -0
  79. package/dist/core/radius.js.map +1 -0
  80. package/dist/core/remote-catalog-provider.d.ts +5 -0
  81. package/dist/core/remote-catalog-provider.d.ts.map +1 -0
  82. package/dist/core/remote-catalog-provider.js +83 -0
  83. package/dist/core/remote-catalog-provider.js.map +1 -0
  84. package/dist/core/runtime-credentials.d.ts +15 -0
  85. package/dist/core/runtime-credentials.d.ts.map +1 -0
  86. package/dist/core/runtime-credentials.js +36 -0
  87. package/dist/core/runtime-credentials.js.map +1 -0
  88. package/dist/core/sdk.d.ts +3 -6
  89. package/dist/core/sdk.d.ts.map +1 -1
  90. package/dist/core/sdk.js +14 -25
  91. package/dist/core/sdk.js.map +1 -1
  92. package/dist/core/system-prompt.d.ts.map +1 -1
  93. package/dist/core/system-prompt.js +1 -11
  94. package/dist/core/system-prompt.js.map +1 -1
  95. package/dist/index.d.ts +2 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +2 -2
  98. package/dist/index.js.map +1 -1
  99. package/dist/main.d.ts.map +1 -1
  100. package/dist/main.js +11 -13
  101. package/dist/main.js.map +1 -1
  102. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  103. package/dist/modes/interactive/components/assistant-message.js +22 -10
  104. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  105. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  106. package/dist/modes/interactive/components/custom-editor.js +1 -1
  107. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  108. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  109. package/dist/modes/interactive/components/footer.js +1 -1
  110. package/dist/modes/interactive/components/footer.js.map +1 -1
  111. package/dist/modes/interactive/components/login-dialog.d.ts +5 -5
  112. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  113. package/dist/modes/interactive/components/login-dialog.js +17 -8
  114. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  115. package/dist/modes/interactive/components/model-selector.d.ts +11 -4
  116. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  117. package/dist/modes/interactive/components/model-selector.js +72 -41
  118. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  119. package/dist/modes/interactive/components/oauth-selector.d.ts +4 -4
  120. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  121. package/dist/modes/interactive/components/oauth-selector.js +14 -27
  122. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  123. package/dist/modes/interactive/components/tree-selector.d.ts +5 -0
  124. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  125. package/dist/modes/interactive/components/tree-selector.js +49 -12
  126. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  127. package/dist/modes/interactive/interactive-mode.d.ts +7 -5
  128. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  129. package/dist/modes/interactive/interactive-mode.js +187 -171
  130. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  131. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  132. package/dist/modes/rpc/rpc-mode.js +2 -2
  133. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  134. package/dist/package-manager-cli.d.ts.map +1 -1
  135. package/dist/package-manager-cli.js +67 -5
  136. package/dist/package-manager-cli.js.map +1 -1
  137. package/dist/utils/clipboard-native.d.ts +1 -0
  138. package/dist/utils/clipboard-native.d.ts.map +1 -1
  139. package/dist/utils/clipboard-native.js.map +1 -1
  140. package/dist/utils/clipboard.d.ts +2 -0
  141. package/dist/utils/clipboard.d.ts.map +1 -1
  142. package/dist/utils/clipboard.js +13 -0
  143. package/dist/utils/clipboard.js.map +1 -1
  144. package/docs/custom-provider.md +8 -11
  145. package/docs/extensions.md +160 -1
  146. package/docs/keybindings.md +1 -0
  147. package/docs/models.md +3 -0
  148. package/docs/packages.md +1 -0
  149. package/docs/providers.md +16 -1
  150. package/docs/quickstart.md +1 -1
  151. package/docs/sdk.md +40 -50
  152. package/docs/usage.md +2 -0
  153. package/examples/extensions/custom-provider-anthropic/index.ts +1 -1
  154. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  155. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  156. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  157. package/examples/extensions/gondolin/package-lock.json +2 -2
  158. package/examples/extensions/gondolin/package.json +1 -1
  159. package/examples/extensions/sandbox/package-lock.json +2 -2
  160. package/examples/extensions/sandbox/package.json +1 -1
  161. package/examples/extensions/with-deps/package-lock.json +2 -2
  162. package/examples/extensions/with-deps/package.json +1 -1
  163. package/examples/sdk/02-custom-model.ts +5 -8
  164. package/examples/sdk/09-api-keys-and-oauth.ts +13 -31
  165. package/examples/sdk/12-full-control.ts +7 -12
  166. package/examples/sdk/README.md +14 -18
  167. package/npm-shrinkwrap.json +12 -12
  168. package/package.json +4 -4
  169. package/dist/core/provider-display-names.d.ts +0 -2
  170. package/dist/core/provider-display-names.d.ts.map +0 -1
  171. package/dist/core/provider-display-names.js +0 -36
  172. package/dist/core/provider-display-names.js.map +0 -1
@@ -47,6 +47,7 @@ See [examples/extensions/](../examples/extensions/) for working implementations.
47
47
  - [ExtensionAPI Methods](#extensionapi-methods)
48
48
  - [State Management](#state-management)
49
49
  - [Custom Tools](#custom-tools)
50
+ - [Dynamic Tool Loading](#dynamic-tool-loading)
50
51
  - [Custom UI](#custom-ui)
51
52
  - [Error Handling](#error-handling)
52
53
  - [Mode Behavior](#mode-behavior)
@@ -1676,7 +1677,7 @@ Register or override a model provider dynamically. Useful for proxies, custom en
1676
1677
 
1677
1678
  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`.
1678
1679
 
1679
- 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`.
1680
+ Dynamic providers can implement `refreshModels`. Pi calls it during model refresh, publishes the returned list synchronously through the provider, and passes the canonical credential/store/network/signal context. The extension decides whether to persist the catalog through `context.store`; live servers such as llama.cpp can ignore it.
1680
1681
 
1681
1682
  ```typescript
1682
1683
  // Register a new provider with custom models
@@ -1698,6 +1699,26 @@ pi.registerProvider("my-proxy", {
1698
1699
  ]
1699
1700
  });
1700
1701
 
1702
+ // Register a live llama.cpp catalog without persisting discovered models
1703
+ pi.registerProvider("llama.cpp", {
1704
+ baseUrl: "http://localhost:8080/v1",
1705
+ apiKey: "local",
1706
+ api: "openai-completions",
1707
+ async refreshModels({ signal }) {
1708
+ const response = await fetch("http://localhost:8080/v1/models", { signal });
1709
+ const { data } = await response.json();
1710
+ return data.map(({ id }) => ({
1711
+ id,
1712
+ name: id,
1713
+ reasoning: false,
1714
+ input: ["text"],
1715
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
1716
+ contextWindow: 128000,
1717
+ maxTokens: 16384
1718
+ }));
1719
+ }
1720
+ });
1721
+
1701
1722
  // Override baseUrl for an existing provider (keeps all models)
1702
1723
  pi.registerProvider("anthropic", {
1703
1724
  baseUrl: "https://proxy.example.com"
@@ -1735,6 +1756,7 @@ pi.registerProvider("corporate-ai", {
1735
1756
  - `headers` - Custom headers to include in requests.
1736
1757
  - `authHeader` - If true, adds `Authorization: Bearer` header automatically.
1737
1758
  - `models` - Array of model definitions. If provided, replaces all existing models for this provider. Model definitions can set `baseUrl` to override the provider endpoint for that model.
1759
+ - `refreshModels` - Async dynamic discovery callback. Its returned models replace extension-provided models. Use the scoped `context.store` only when results should persist.
1738
1760
  - `oauth` - OAuth provider config for `/login` support. When provided, the provider appears in the login menu.
1739
1761
  - `streamSimple` - Custom streaming implementation for non-standard APIs.
1740
1762
 
@@ -2229,6 +2251,143 @@ If a slot renderer is not defined or throws:
2229
2251
  - `renderCall`: Shows the tool name
2230
2252
  - `renderResult`: Shows raw text from `content`
2231
2253
 
2254
+ ### Dynamic Tool Loading
2255
+
2256
+ Extensions can register many tools while keeping only a small initial set active. A tool can then add more tools with `pi.setActiveTools()` during execution. Pi detects purely additive changes, records the newly available tool names on that tool result, and applies the updated active set before the next model request.
2257
+
2258
+ This works with every model. Models with native deferred-loading support preserve the stable prompt prefix and load the new definitions at the tool-result position. Other models use the fallback described below.
2259
+
2260
+ The lifecycle is:
2261
+
2262
+ 1. Register every tool with `pi.registerTool()` so it appears in `pi.getAllTools()`.
2263
+ 2. Keep loader tools, such as `search_tools`, active and leave searchable tools inactive.
2264
+ 3. During loader execution, call `pi.setActiveTools([...currentTools, ...matchingTools])`. The change must be additive: do not remove currently active tools in the same call.
2265
+ 4. Pi records which tools were added on the loader's tool result.
2266
+ 5. Before the next model response, Pi exposes the added definitions using native deferred loading when supported, or the normal active tool list otherwise.
2267
+
2268
+ You do not need to return provider-specific tool references or mark the loader as a special search tool. The active-tool change is the signal. Names passed to `pi.setActiveTools()` must already be registered; unknown names are ignored.
2269
+
2270
+ #### Models with native deferred loading
2271
+
2272
+ - **Anthropic**
2273
+ - **Models:** Sonnet, Opus, Fable version 4.5 or newer (without Haiku)
2274
+ - **Native representation:** Deferred definitions use `defer_loading`; the load point uses `tool_reference` content.
2275
+ - **OpenAI**
2276
+ - **Models:** `gpt-5.4` and newer family
2277
+ - **Native representation:** Pi adds completed client `tool_search_call` and `tool_search_output` items at the load point.
2278
+
2279
+ For a verified custom model or proxy, native handling can be enabled with `compat.supportsToolReferences: true` for `anthropic-messages`, or `compat.supportsToolSearch: true` for `openai-responses` and `openai-codex-responses`. Leave these disabled unless the endpoint and model accept the corresponding native protocol.
2280
+
2281
+ #### Fallback behavior
2282
+
2283
+ For all other models and providers, dynamic activation still works: Pi sends the complete current active tool list normally on the next request. The model can call the newly activated tools, but adding their definitions may invalidate the provider's cached prompt prefix.
2284
+
2285
+ Pi also uses this safe fallback when the active set is not purely additive, such as replacing one group of tools with another. Tool removals therefore work, but they do not use deferred loading.
2286
+
2287
+ For the best cache behavior, keep the loader tool active for the whole session and add tools instead of replacing the active set. Also note that activating a tool with `promptSnippet` or `promptGuidelines` rebuilds the system prompt; that system-prompt change can invalidate the prefix even when the provider supports deferred schemas. Lazily loaded tools should usually rely on their tool `description` and omit active-only prompt metadata.
2288
+
2289
+ #### Search tool example
2290
+
2291
+ The following extension registers two searchable tools, removes them from the initial active set, and keeps only `search_tools` as their loader. The example uses simple keyword matching, but the search implementation could use BM25, embeddings, a remote catalog, or project-specific routing.
2292
+
2293
+ ```typescript
2294
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2295
+ import { Type } from "typebox";
2296
+
2297
+ const SEARCHABLE_TOOL_NAMES = new Set(["lookup_weather", "search_issues"]);
2298
+
2299
+ export default function (pi: ExtensionAPI) {
2300
+ pi.registerTool({
2301
+ name: "lookup_weather",
2302
+ label: "Lookup Weather",
2303
+ description: "Look up the current weather for a city",
2304
+ parameters: Type.Object({ city: Type.String() }),
2305
+ async execute(_toolCallId, params) {
2306
+ return {
2307
+ content: [{ type: "text", text: `Weather for ${params.city}: sunny` }],
2308
+ details: {},
2309
+ };
2310
+ },
2311
+ });
2312
+
2313
+ pi.registerTool({
2314
+ name: "search_issues",
2315
+ label: "Search Issues",
2316
+ description: "Search project issues by keyword",
2317
+ parameters: Type.Object({ query: Type.String() }),
2318
+ async execute(_toolCallId, params) {
2319
+ return {
2320
+ content: [{ type: "text", text: `No open issues matching ${params.query}` }],
2321
+ details: {},
2322
+ };
2323
+ },
2324
+ });
2325
+
2326
+ pi.registerTool({
2327
+ name: "search_tools",
2328
+ label: "Search Tools",
2329
+ description: "Search for and enable tools relevant to a task",
2330
+ promptSnippet: "Search for additional tools when the active tools cannot perform the task",
2331
+ promptGuidelines: [
2332
+ "Use search_tools when a task requires a capability that is not currently available.",
2333
+ ],
2334
+ parameters: Type.Object({
2335
+ query: Type.String({ description: "Capability or task to search for" }),
2336
+ limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 10 })),
2337
+ }),
2338
+ async execute(_toolCallId, params) {
2339
+ const terms = params.query.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
2340
+ const matches = pi.getAllTools()
2341
+ .filter((tool) => SEARCHABLE_TOOL_NAMES.has(tool.name))
2342
+ .map((tool) => ({
2343
+ tool,
2344
+ score: terms.reduce(
2345
+ (score, term) =>
2346
+ score + (`${tool.name} ${tool.description}`.toLowerCase().includes(term) ? 1 : 0),
2347
+ 0,
2348
+ ),
2349
+ }))
2350
+ .filter((match) => match.score > 0)
2351
+ .sort((a, b) => b.score - a.score)
2352
+ .slice(0, params.limit ?? 3)
2353
+ .map((match) => match.tool.name);
2354
+
2355
+ if (matches.length === 0) {
2356
+ return {
2357
+ content: [{ type: "text", text: `No tools found for: ${params.query}` }],
2358
+ details: { matches: [] },
2359
+ };
2360
+ }
2361
+
2362
+ const active = pi.getActiveTools();
2363
+ const added = matches.filter((name) => !active.includes(name));
2364
+ pi.setActiveTools([...new Set([...active, ...added])]);
2365
+
2366
+ return {
2367
+ content: [{
2368
+ type: "text",
2369
+ text: added.length > 0
2370
+ ? `Loaded tools: ${added.join(", ")}`
2371
+ : `Matching tools already active: ${matches.join(", ")}`,
2372
+ }],
2373
+ details: { matches, added },
2374
+ };
2375
+ },
2376
+ });
2377
+
2378
+ pi.on("session_start", () => {
2379
+ // Keep searchable tools registered but initially inactive. Preserve built-ins
2380
+ // and tools owned by other extensions, and keep the loader itself active.
2381
+ const initialTools = pi.getActiveTools().filter(
2382
+ (name) => !SEARCHABLE_TOOL_NAMES.has(name),
2383
+ );
2384
+ pi.setActiveTools([...new Set([...initialTools, "search_tools"])]);
2385
+ });
2386
+ }
2387
+ ```
2388
+
2389
+ When `search_tools` adds a match, the model receives that definition on the immediately following request. On a native-capable model the definition is anchored after the search result without changing the initial tool-schema prefix. On other models it appears in the normal tool list on that same following request.
2390
+
2232
2391
  ## Custom UI
2233
2392
 
2234
2393
  Extensions can interact with users via `ctx.ui` methods and customize how messages/tools render.
@@ -119,6 +119,7 @@ Modifier combinations: `ctrl+shift+x`, `alt+ctrl+x`, `ctrl+shift+alt+x`, `ctrl+1
119
119
  | Keybinding id | Default | Description |
120
120
  |--------|---------|-------------|
121
121
  | `app.tools.expand` | `ctrl+o` | Collapse or expand tool output |
122
+ | `app.message.copy` | `ctrl+x` | Copy the last assistant message, or the selected message in `/tree` |
122
123
  | `app.message.followUp` | `alt+enter` | Queue follow-up message |
123
124
  | `app.message.dequeue` | `alt+up` | Restore queued messages to editor |
124
125
 
package/docs/models.md CHANGED
@@ -136,6 +136,7 @@ Set `api` at provider level (default for all models) or model level (override pe
136
136
  | `baseUrl` | API endpoint URL |
137
137
  | `api` | API type (see above) |
138
138
  | `apiKey` | Optional API key config (see value resolution below). Omit it when auth is provided by `/login`/`auth.json` or CLI `--api-key`. |
139
+ | `oauth` | Dynamic OAuth provider type. Currently supports `"radius"`; requires the gateway `baseUrl`. |
139
140
  | `headers` | Custom headers (see value resolution below) |
140
141
  | `authHeader` | Set `true` to add `Authorization: Bearer <apiKey>` automatically |
141
142
  | `models` | Array of model configurations |
@@ -445,6 +446,8 @@ For providers with partial OpenAI compatibility, use the `compat` field.
445
446
  | `thinkingFormat` | Use `reasoning_effort`, `openrouter`, `deepseek`, `together`, `zai`, `qwen`, `chat-template`, or `qwen-chat-template` thinking parameters |
446
447
  | `chatTemplateKwargs` | `chat_template_kwargs` values for `thinkingFormat: "chat-template"`; use `{ "$var": "thinking.enabled" }` or `{ "$var": "thinking.effort" }` for pi-controlled thinking values |
447
448
  | `cacheControlFormat` | Use Anthropic-style `cache_control` markers on the system prompt, last tool definition, and last user/assistant text content. Currently only `anthropic` is supported. |
449
+ | `sendSessionAffinityHeaders` | For `openai-completions`, send session-affinity headers from the session id when caching is enabled. Default: `false`. |
450
+ | `sessionAffinityFormat` | For `openai-completions` and `openai-responses`, the session-affinity header format: `openai` sends `session_id`/`x-client-request-id` (completions also `x-session-affinity`), `openai-nosession` omits the underscore-containing `session_id` header, `openrouter` sends `x-session-id`. Does not affect the `prompt_cache_key` body param. Default: auto-detected. |
448
451
  | `supportsStrictMode` | Include the `strict` field in tool definitions |
449
452
  | `supportsLongCacheRetention` | Whether the provider accepts long cache retention when cache retention is `long`: `prompt_cache_retention: "24h"` for OpenAI prompt caching, or `cache_control.ttl: "1h"` when `cacheControlFormat` is `anthropic`. Default: `true`. |
450
453
  | `openRouterRouting` | OpenRouter provider routing preferences. This object is sent as-is in the `provider` field of the [OpenRouter API request](https://openrouter.ai/docs/guides/routing/provider-selection). |
package/docs/packages.md CHANGED
@@ -31,6 +31,7 @@ pi list # show installed packages from settings
31
31
  pi update # update pi only
32
32
  pi update --all # update pi, update packages, and reconcile pinned git refs
33
33
  pi update --extensions # update packages and reconcile pinned git refs only
34
+ pi update --models # refresh model catalogs only
34
35
  pi update --self # update pi only
35
36
  pi update --self --force # reinstall pi even if current
36
37
  pi update npm:@foo/bar # update one package
package/docs/providers.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Providers
2
2
 
3
- Pi supports subscription-based providers via OAuth and API key providers via environment variables or auth file. For each provider, pi knows all available models. The list is updated with every pi release.
3
+ Pi supports subscription-based providers via OAuth and API key providers via environment variables or auth file. Built-in catalogs ship with pi; configured providers may refresh newer catalogs and cache them in `~/.pi/agent/models-store.json` for offline use.
4
4
 
5
5
  ## Table of Contents
6
6
 
@@ -18,6 +18,8 @@ Use `/login` in interactive mode, then select a provider:
18
18
  - ChatGPT Plus/Pro (Codex)
19
19
  - Claude Pro/Max
20
20
  - GitHub Copilot
21
+ - xAI (Grok/X subscription)
22
+ - Radius
21
23
 
22
24
  Use `/logout` to clear credentials. Tokens are stored in `~/.pi/agent/auth.json` and auto-refresh when expired.
23
25
 
@@ -35,6 +37,15 @@ Anthropic subscription auth is active for Claude Pro/Max accounts. Third-party h
35
37
  - Press Enter for github.com, or enter your GitHub Enterprise Server domain
36
38
  - If you get "model not supported", enable it in VS Code: Copilot Chat → model selector → select model → "Enable"
37
39
 
40
+ ### xAI (Grok/X subscription)
41
+
42
+ - Run `/login xai`, then select **Use a subscription**
43
+ - `XAI_API_KEY` remains available through **Use an API key**
44
+
45
+ ### Radius
46
+
47
+ Radius is a dynamic `pi-messages` gateway. `/login radius` stores OAuth tokens in `auth.json`; the gateway catalog is refreshed independently and cached in `models-store.json`. Custom Radius gateways can be declared in `models.json` with `"oauth": "radius"` and a gateway `baseUrl`.
48
+
38
49
  ## API Keys
39
50
 
40
51
  ### Environment Variables or Auth File
@@ -55,6 +66,7 @@ pi
55
66
  | DeepSeek | `DEEPSEEK_API_KEY` | `deepseek` |
56
67
  | NVIDIA NIM | `NVIDIA_API_KEY` | `nvidia` |
57
68
  | Google Gemini | `GEMINI_API_KEY` | `google` |
69
+ | Amazon Bedrock | `AWS_BEARER_TOKEN_BEDROCK` | `amazon-bedrock` |
58
70
  | Mistral | `MISTRAL_API_KEY` | `mistral` |
59
71
  | Groq | `GROQ_API_KEY` | `groq` |
60
72
  | Cerebras | `CEREBRAS_API_KEY` | `cerebras` |
@@ -67,6 +79,7 @@ pi
67
79
  | ZAI Coding Plan (China) | `ZAI_CODING_CN_API_KEY` | `zai-coding-cn` |
68
80
  | OpenCode Zen | `OPENCODE_API_KEY` | `opencode` |
69
81
  | OpenCode Go | `OPENCODE_API_KEY` | `opencode-go` |
82
+ | Radius | `RADIUS_API_KEY` | `radius` |
70
83
  | Hugging Face | `HF_TOKEN` | `huggingface` |
71
84
  | Fireworks | `FIREWORKS_API_KEY` | `fireworks` |
72
85
  | Together AI | `TOGETHER_API_KEY` | `together` |
@@ -170,6 +183,8 @@ export AZURE_OPENAI_DEPLOYMENT_NAME_MAP=gpt-4=my-gpt4,gpt-4o=my-gpt4o
170
183
 
171
184
  ### Amazon Bedrock
172
185
 
186
+ Use `/login amazon-bedrock` to store a Bedrock API key, or configure one of the ambient AWS credential sources below:
187
+
173
188
  ```bash
174
189
  # Option 1: AWS Profile
175
190
  export AWS_PROFILE=your-profile
@@ -113,7 +113,7 @@ pi @README.md "Summarize this"
113
113
  pi @src/app.ts @src/app.test.ts "Review these together"
114
114
  ```
115
115
 
116
- Images can be pasted with Ctrl+V (Alt+V on Windows) or dragged into supported terminals.
116
+ Images or text can be pasted with Ctrl+V (Alt+V on Windows); images can also be dragged into supported terminals.
117
117
 
118
118
  ### Run shell commands
119
119
 
package/docs/sdk.md CHANGED
@@ -16,16 +16,12 @@ See [examples/sdk/](../examples/sdk/) for working examples from minimal to full
16
16
  ## Quick Start
17
17
 
18
18
  ```typescript
19
- import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "@earendil-works/pi-coding-agent";
20
-
21
- // Set up credential storage and model registry
22
- const authStorage = AuthStorage.create();
23
- const modelRegistry = ModelRegistry.create(authStorage);
19
+ import { createAgentSession, ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent";
24
20
 
21
+ const modelRuntime = await ModelRuntime.create();
25
22
  const { session } = await createAgentSession({
26
23
  sessionManager: SessionManager.inMemory(),
27
- authStorage,
28
- modelRegistry,
24
+ modelRuntime,
29
25
  });
30
26
 
31
27
  session.subscribe((event) => {
@@ -369,10 +365,9 @@ When you pass a custom `ResourceLoader`, `cwd` and `agentDir` no longer control
369
365
 
370
366
  ```typescript
371
367
  import { getModel } from "@earendil-works/pi-ai";
372
- import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
368
+ import { ModelRuntime } from "@earendil-works/pi-coding-agent";
373
369
 
374
- const authStorage = AuthStorage.create();
375
- const modelRegistry = ModelRegistry.create(authStorage);
370
+ const modelRuntime = await ModelRuntime.create();
376
371
 
377
372
  // Find specific built-in model (doesn't check if API key exists)
378
373
  const opus = getModel("anthropic", "claude-opus-4-5");
@@ -380,10 +375,10 @@ if (!opus) throw new Error("Model not found");
380
375
 
381
376
  // Find any model by provider/id, including custom models from models.json
382
377
  // (doesn't check if API key exists)
383
- const customModel = modelRegistry.find("my-provider", "my-model");
378
+ const customModel = modelRuntime.getModel("my-provider", "my-model");
384
379
 
385
- // Get only models that have valid API keys configured
386
- const available = await modelRegistry.getAvailable();
380
+ // Get only models that have valid authentication configured
381
+ const available = await modelRuntime.getAvailable();
387
382
 
388
383
  const { session } = await createAgentSession({
389
384
  model: opus,
@@ -395,8 +390,7 @@ const { session } = await createAgentSession({
395
390
  { model: haiku, thinkingLevel: "off" },
396
391
  ],
397
392
 
398
- authStorage,
399
- modelRegistry,
393
+ modelRuntime,
400
394
  });
401
395
  ```
402
396
 
@@ -415,14 +409,14 @@ import {
415
409
 
416
410
  const cliModel = resolveCliModel({
417
411
  cliModel: "anthropic/claude-opus-4-5:high",
418
- modelRegistry,
412
+ modelRuntime,
419
413
  });
420
414
  if (cliModel.error) throw new Error(cliModel.error);
421
415
  if (cliModel.warning) console.warn(cliModel.warning);
422
416
 
423
417
  const { scopedModels, diagnostics } = await resolveModelScopeWithDiagnostics(
424
418
  ["anthropic/*:high", "gpt-5"],
425
- modelRegistry,
419
+ modelRuntime,
426
420
  );
427
421
  for (const diagnostic of diagnostics) {
428
422
  console.warn(diagnostic.message);
@@ -435,40 +429,41 @@ for (const diagnostic of diagnostics) {
435
429
 
436
430
  ### API Keys and OAuth
437
431
 
438
- API key resolution priority (handled by AuthStorage):
432
+ Authentication resolution priority (handled by `ModelRuntime`):
439
433
  1. Runtime overrides (via `setRuntimeApiKey`, not persisted)
440
434
  2. Stored credentials in `auth.json` (API keys or OAuth tokens)
441
435
  3. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
442
436
  4. Fallback resolver (for custom provider keys from `models.json`)
443
437
 
444
438
  ```typescript
445
- import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
439
+ import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
440
+ import { createAgentSession, ModelRuntime } from "@earendil-works/pi-coding-agent";
446
441
 
447
442
  // Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json
448
- const authStorage = AuthStorage.create();
449
- const modelRegistry = ModelRegistry.create(authStorage);
443
+ const modelRuntime = await ModelRuntime.create();
450
444
 
451
- const { session } = await createAgentSession({
452
- sessionManager: SessionManager.inMemory(),
453
- authStorage,
454
- modelRegistry,
455
- });
445
+ // Provider-owned auth methods and current status
446
+ for (const provider of modelRuntime.getProviders()) {
447
+ const status = await modelRuntime.checkAuth(provider.id);
448
+ console.log(provider.name, provider.auth, status);
449
+ }
456
450
 
457
451
  // Runtime API key override (not persisted to disk)
458
- authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
452
+ modelRuntime.setRuntimeApiKey("anthropic", "sk-my-temp-key");
453
+
454
+ // Custom credential and model locations
455
+ const customRuntime = await ModelRuntime.create({
456
+ authPath: "/my/app/auth.json",
457
+ modelsPath: "/my/app/models.json",
458
+ });
459
459
 
460
- // Custom auth storage location
461
- const customAuth = AuthStorage.create("/my/app/auth.json");
462
- const customRegistry = ModelRegistry.create(customAuth, "/my/app/models.json");
460
+ // Or inject any pi-ai CredentialStore
461
+ const credentials = new InMemoryCredentialStore();
462
+ const inMemoryRuntime = await ModelRuntime.create({ credentials });
463
463
 
464
464
  const { session } = await createAgentSession({
465
- sessionManager: SessionManager.inMemory(),
466
- authStorage: customAuth,
467
- modelRegistry: customRegistry,
465
+ modelRuntime: customRuntime,
468
466
  });
469
-
470
- // No custom models.json (built-in models only)
471
- const simpleRegistry = ModelRegistry.inMemory(authStorage);
472
467
  ```
473
468
 
474
469
  > See [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)
@@ -927,26 +922,22 @@ interface LoadExtensionsResult {
927
922
  import { getModel } from "@earendil-works/pi-ai";
928
923
  import { Type } from "typebox";
929
924
  import {
930
- AuthStorage,
931
925
  createAgentSession,
932
926
  DefaultResourceLoader,
933
927
  defineTool,
934
- ModelRegistry,
928
+ ModelRuntime,
935
929
  SessionManager,
936
930
  SettingsManager,
937
931
  } from "@earendil-works/pi-coding-agent";
938
932
 
939
- // Set up auth storage (custom location)
940
- const authStorage = AuthStorage.create("/custom/agent/auth.json");
941
-
942
- // Runtime API key override (not persisted)
933
+ const modelRuntime = await ModelRuntime.create({
934
+ authPath: "/custom/agent/auth.json",
935
+ modelsPath: "/custom/agent/models.json",
936
+ });
943
937
  if (process.env.MY_KEY) {
944
- authStorage.setRuntimeApiKey("anthropic", process.env.MY_KEY);
938
+ modelRuntime.setRuntimeApiKey("anthropic", process.env.MY_KEY);
945
939
  }
946
940
 
947
- // Model registry (no custom models.json)
948
- const modelRegistry = ModelRegistry.create(authStorage);
949
-
950
941
  // Inline tool
951
942
  const statusTool = defineTool({
952
943
  name: "status",
@@ -982,8 +973,7 @@ const { session } = await createAgentSession({
982
973
 
983
974
  model,
984
975
  thinkingLevel: "off",
985
- authStorage,
986
- modelRegistry,
976
+ modelRuntime,
987
977
 
988
978
  tools: ["read", "bash", "status"],
989
979
  customTools: [statusTool],
@@ -1149,8 +1139,8 @@ createAgentSessionRuntime
1149
1139
  AgentSessionRuntime
1150
1140
 
1151
1141
  // Auth and Models
1152
- AuthStorage
1153
- ModelRegistry
1142
+ ModelRuntime // implements pi-ai Models and owns credential storage
1143
+ ModelRegistry // synchronous extension compatibility facade
1154
1144
  resolveCliModel
1155
1145
  resolveModelScopeWithDiagnostics
1156
1146
 
package/docs/usage.md CHANGED
@@ -22,6 +22,7 @@ The editor can be replaced temporarily by built-in UI such as `/settings` or by
22
22
  | File reference | Type `@` to fuzzy-search project files |
23
23
  | Path completion | Press Tab to complete paths |
24
24
  | Multi-line input | Shift+Enter, or Ctrl+Enter on Windows Terminal |
25
+ | Copy response | Ctrl+X copies the last assistant message; in `/tree`, it copies the selected message |
25
26
  | Images | Paste with Ctrl+V, Alt+V on Windows, or drag into the terminal |
26
27
  | Shell command | `!command` runs and sends output to the model |
27
28
  | Hidden shell command | `!!command` runs without sending output to the model |
@@ -150,6 +151,7 @@ pi uninstall <source> [-l] # Alias for remove
150
151
  pi update [source|self|pi] # Update pi only, or one package source
151
152
  pi update --all # Update pi and packages; reconcile pinned git refs
152
153
  pi update --extensions # Update packages only; reconcile pinned git refs
154
+ pi update --models # Refresh model catalogs only
153
155
  pi update --self # Update pi only
154
156
  pi update --extension <src> # Update one package
155
157
  pi list # List installed packages
@@ -46,7 +46,7 @@ import {
46
46
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
47
47
 
48
48
  // =============================================================================
49
- // OAuth Implementation (copied from packages/ai/src/utils/oauth/anthropic.ts)
49
+ // OAuth implementation adapted for the legacy extension compatibility interface.
50
50
  // =============================================================================
51
51
 
52
52
  const decode = (s: string) => atob(s);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "0.80.6",
3
+ "version": "0.80.8",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "0.80.6",
9
+ "version": "0.80.8",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.80.6",
4
+ "version": "0.80.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.80.6",
4
+ "version": "0.80.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-gondolin",
3
- "version": "0.80.6",
3
+ "version": "0.80.8",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-gondolin",
9
- "version": "0.80.6",
9
+ "version": "0.80.8",
10
10
  "dependencies": {
11
11
  "@earendil-works/gondolin": "0.12.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-gondolin",
3
3
  "private": true,
4
- "version": "0.80.6",
4
+ "version": "0.80.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
- "version": "1.10.6",
3
+ "version": "1.10.8",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-sandbox",
9
- "version": "1.10.6",
9
+ "version": "1.10.8",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sandbox-runtime": "^0.0.26"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-sandbox",
3
3
  "private": true,
4
- "version": "1.10.6",
4
+ "version": "1.10.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "0.80.6",
3
+ "version": "0.80.8",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "0.80.6",
9
+ "version": "0.80.8",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.80.6",
4
+ "version": "0.80.8",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",