Merge remote-tracking branch 'origin/main' into add-kimi-deferred-tools
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.80.8] - 2026-07-16
|
||||
|
||||
### New Features
|
||||
|
||||
- **Unified model runtime and provider authentication** — `ModelRuntime` centralizes model configuration, provider-owned `/login`, and dynamic provider catalogs. See [Providers](docs/providers.md).
|
||||
- **Live model catalog refresh** — `/model` refreshes configured providers in the background, and `pi update --models` forces an immediate refresh. See [Install and Manage](docs/packages.md#install-and-manage).
|
||||
- **xAI device-code OAuth and Grok 4.5 Responses support** — Sign in to xAI with a device code and use Grok 4.5 with low, medium, or high thinking. See [xAI](docs/providers.md#xai-grokx-subscription).
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Replaced the SDK's `CreateAgentSessionOptions.authStorage` and `modelRegistry` options with the async `modelRuntime` option. `AuthStorage` and its storage backends are no longer exported; use `ModelRuntime` (or a custom pi-ai `CredentialStore`), or `readStoredCredential()` for one-off reads of auth.json.
|
||||
@@ -16,6 +24,8 @@
|
||||
- Added provider-owned `/login` discovery directly from registered pi-ai providers, including ambient auth status and informational links.
|
||||
- Added file-backed dynamic catalogs in `models-store.json`, per-provider pi.dev catalog overlays, and Radius gateway support including offline migration from legacy credential-cached catalogs.
|
||||
- Added extension provider `refreshModels(context)` support for dynamic model discovery with optional provider-controlled persistence.
|
||||
- Added `pi update --models` to force an immediate model catalog refresh without updating pi or extensions.
|
||||
- Added inherited xAI device-code OAuth login and Grok 4.5 OpenAI Responses support, with low, medium, and high thinking levels ([#6651](https://github.com/earendil-works/pi-mono/pull/6651) by [@Jaaneek](https://github.com/Jaaneek)).
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -27,6 +37,10 @@
|
||||
|
||||
- Fixed configured-provider catalog refresh to parse pi.dev's model-ID keyed responses, throttle checks to once per four hours, send the versioned pi user agent, treat unimplemented routes as unavailable overlays, and show concise refresh status in `/model`.
|
||||
- Fixed adjacent assistant thinking blocks to render as one thinking section.
|
||||
- Fixed inherited OpenAI Codex session IDs longer than 64 characters to meet the API limit ([#6630](https://github.com/earendil-works/pi-mono/issues/6630)).
|
||||
- Fixed inherited terminal output to normalize tab characters consistently ([#6697](https://github.com/earendil-works/pi-mono/pull/6697) by [@xz-dev](https://github.com/xz-dev)).
|
||||
- Fixed the Windows terminal title after checking npm packages ([#6629](https://github.com/earendil-works/pi-mono/issues/6629)).
|
||||
- Fixed Bun standalone binaries to bundle OAuth adapters for interactive logins.
|
||||
|
||||
## [0.80.7] - 2026-07-14
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ Then just talk to pi. By default, pi gives the model four tools: `read`, `write`
|
||||
|
||||
## Providers & Models
|
||||
|
||||
For each built-in provider, pi maintains a list of tool-capable models, updated with every release. Authenticate via subscription (`/login`) or API key, then select any model from that provider via `/model` (or Ctrl+L).
|
||||
For each built-in provider, pi maintains a list of tool-capable models. Configured provider catalogs refresh automatically; run `pi update --models` to force an immediate refresh. Authenticate via subscription (`/login`) or API key, then select any model from that provider via `/model` (or Ctrl+L).
|
||||
|
||||
**Subscriptions:**
|
||||
- Anthropic Claude Pro/Max
|
||||
@@ -421,6 +421,7 @@ pi list
|
||||
pi update # update pi only
|
||||
pi update --all # update pi and packages
|
||||
pi update --extensions # update packages only
|
||||
pi update --models # refresh model catalogs only
|
||||
pi update --self # update pi only
|
||||
pi update --self --force # reinstall pi even if current
|
||||
pi update npm:@foo/pi-tools # update one package
|
||||
@@ -519,6 +520,7 @@ pi uninstall <source> [-l] # Alias for remove
|
||||
pi update [source|self|pi] # Update pi only, or one package source
|
||||
pi update --all # Update pi and packages
|
||||
pi update --extensions # Update packages only
|
||||
pi update --models # Refresh model catalogs only
|
||||
pi update --self # Update pi only
|
||||
pi update --self --force # Reinstall pi even if current
|
||||
pi update --extension <src> # Update one package
|
||||
|
||||
@@ -31,6 +31,7 @@ pi list # show installed packages from settings
|
||||
pi update # update pi only
|
||||
pi update --all # update pi, update packages, and reconcile pinned git refs
|
||||
pi update --extensions # update packages and reconcile pinned git refs only
|
||||
pi update --models # refresh model catalogs only
|
||||
pi update --self # update pi only
|
||||
pi update --self --force # reinstall pi even if current
|
||||
pi update npm:@foo/bar # update one package
|
||||
|
||||
@@ -18,6 +18,7 @@ Use `/login` in interactive mode, then select a provider:
|
||||
- ChatGPT Plus/Pro (Codex)
|
||||
- Claude Pro/Max
|
||||
- GitHub Copilot
|
||||
- xAI (Grok/X subscription)
|
||||
- Radius
|
||||
|
||||
Use `/logout` to clear credentials. Tokens are stored in `~/.pi/agent/auth.json` and auto-refresh when expired.
|
||||
@@ -36,6 +37,11 @@ Anthropic subscription auth is active for Claude Pro/Max accounts. Third-party h
|
||||
- Press Enter for github.com, or enter your GitHub Enterprise Server domain
|
||||
- If you get "model not supported", enable it in VS Code: Copilot Chat → model selector → select model → "Enable"
|
||||
|
||||
### xAI (Grok/X subscription)
|
||||
|
||||
- Run `/login xai`, then select **Use a subscription**
|
||||
- `XAI_API_KEY` remains available through **Use an API key**
|
||||
|
||||
### Radius
|
||||
|
||||
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`.
|
||||
|
||||
@@ -151,6 +151,7 @@ pi uninstall <source> [-l] # Alias for remove
|
||||
pi update [source|self|pi] # Update pi only, or one package source
|
||||
pi update --all # Update pi and packages; reconcile pinned git refs
|
||||
pi update --extensions # Update packages only; reconcile pinned git refs
|
||||
pi update --models # Refresh model catalogs only
|
||||
pi update --self # Update pi only
|
||||
pi update --extension <src> # Update one package
|
||||
pi list # List installed packages
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pi-extension-custom-provider",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pi-extension-custom-provider",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.52.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "pi-extension-custom-provider-anthropic",
|
||||
"private": true,
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"clean": "echo 'nothing to clean'",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "pi-extension-custom-provider-gitlab-duo",
|
||||
"private": true,
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"clean": "echo 'nothing to clean'",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pi-extension-gondolin",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pi-extension-gondolin",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"dependencies": {
|
||||
"@earendil-works/gondolin": "0.12.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "pi-extension-gondolin",
|
||||
"private": true,
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"clean": "echo 'nothing to clean'",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pi-extension-sandbox",
|
||||
"version": "1.10.7",
|
||||
"version": "1.10.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pi-extension-sandbox",
|
||||
"version": "1.10.7",
|
||||
"version": "1.10.8",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sandbox-runtime": "^0.0.26"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "pi-extension-sandbox",
|
||||
"private": true,
|
||||
"version": "1.10.7",
|
||||
"version": "1.10.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"clean": "echo 'nothing to clean'",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pi-extension-with-deps",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pi-extension-with-deps",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "pi-extension-with-deps",
|
||||
"private": true,
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"clean": "echo 'nothing to clean'",
|
||||
|
||||
+15
-15
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@earendil-works/pi-coding-agent-install",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@earendil-works/pi-coding-agent-install",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"dependencies": {
|
||||
"@earendil-works/pi-coding-agent": "0.80.7"
|
||||
"@earendil-works/pi-coding-agent": "0.80.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.19.0"
|
||||
@@ -450,11 +450,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-agent-core": {
|
||||
"version": "0.80.7",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.7.tgz",
|
||||
"version": "0.80.8",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.8.tgz",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@earendil-works/pi-ai": "^0.80.7",
|
||||
"@earendil-works/pi-ai": "^0.80.8",
|
||||
"ignore": "7.0.5",
|
||||
"typebox": "1.1.38",
|
||||
"yaml": "2.9.0"
|
||||
@@ -464,8 +464,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-ai": {
|
||||
"version": "0.80.7",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.7.tgz",
|
||||
"version": "0.80.8",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.8.tgz",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "0.91.1",
|
||||
@@ -488,13 +488,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-coding-agent": {
|
||||
"version": "0.80.7",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.7.tgz",
|
||||
"version": "0.80.8",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.8.tgz",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@earendil-works/pi-agent-core": "^0.80.7",
|
||||
"@earendil-works/pi-ai": "^0.80.7",
|
||||
"@earendil-works/pi-tui": "^0.80.7",
|
||||
"@earendil-works/pi-agent-core": "^0.80.8",
|
||||
"@earendil-works/pi-ai": "^0.80.8",
|
||||
"@earendil-works/pi-tui": "^0.80.8",
|
||||
"@silvia-odwyer/photon-node": "0.3.4",
|
||||
"chalk": "5.6.2",
|
||||
"cross-spawn": "7.0.6",
|
||||
@@ -522,8 +522,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-tui": {
|
||||
"version": "0.80.7",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.7.tgz",
|
||||
"version": "0.80.8",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.8.tgz",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "1.6.0",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "@earendil-works/pi-coding-agent-install",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"private": true,
|
||||
"description": "Lockfile root used by the Pi installer and updater.",
|
||||
"dependencies": {
|
||||
"@earendil-works/pi-coding-agent": "0.80.7"
|
||||
"@earendil-works/pi-coding-agent": "0.80.8"
|
||||
},
|
||||
"overrides": {
|
||||
"rimraf": "6.1.2",
|
||||
|
||||
+12
-12
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@earendil-works/pi-coding-agent",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@earendil-works/pi-coding-agent",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@earendil-works/pi-agent-core": "^0.80.7",
|
||||
"@earendil-works/pi-ai": "^0.80.7",
|
||||
"@earendil-works/pi-tui": "^0.80.7",
|
||||
"@earendil-works/pi-agent-core": "^0.80.8",
|
||||
"@earendil-works/pi-ai": "^0.80.8",
|
||||
"@earendil-works/pi-tui": "^0.80.8",
|
||||
"@silvia-odwyer/photon-node": "0.3.4",
|
||||
"chalk": "5.6.2",
|
||||
"cross-spawn": "7.0.6",
|
||||
@@ -474,11 +474,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-agent-core": {
|
||||
"version": "0.80.7",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.7.tgz",
|
||||
"version": "0.80.8",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.8.tgz",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@earendil-works/pi-ai": "^0.80.7",
|
||||
"@earendil-works/pi-ai": "^0.80.8",
|
||||
"ignore": "7.0.5",
|
||||
"typebox": "1.1.38",
|
||||
"yaml": "2.9.0"
|
||||
@@ -488,8 +488,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-ai": {
|
||||
"version": "0.80.7",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.7.tgz",
|
||||
"version": "0.80.8",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.8.tgz",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "0.91.1",
|
||||
@@ -512,8 +512,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@earendil-works/pi-tui": {
|
||||
"version": "0.80.7",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.7.tgz",
|
||||
"version": "0.80.8",
|
||||
"resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.8.tgz",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "1.6.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@earendil-works/pi-coding-agent",
|
||||
"version": "0.80.7",
|
||||
"version": "0.80.8",
|
||||
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
||||
"type": "module",
|
||||
"piConfig": {
|
||||
@@ -39,9 +39,9 @@
|
||||
"prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
|
||||
},
|
||||
"dependencies": {
|
||||
"@earendil-works/pi-agent-core": "^0.80.7",
|
||||
"@earendil-works/pi-ai": "^0.80.7",
|
||||
"@earendil-works/pi-tui": "^0.80.7",
|
||||
"@earendil-works/pi-agent-core": "^0.80.8",
|
||||
"@earendil-works/pi-ai": "^0.80.8",
|
||||
"@earendil-works/pi-tui": "^0.80.8",
|
||||
"@silvia-odwyer/photon-node": "0.3.4",
|
||||
"chalk": "5.6.2",
|
||||
"cross-spawn": "7.0.6",
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#!/usr/bin/env node
|
||||
import { registerBunOAuthFlows } from "@earendil-works/pi-ai/bun-oauth";
|
||||
import { APP_NAME } from "../config.ts";
|
||||
|
||||
process.title = APP_NAME;
|
||||
process.emitWarning = (() => {}) as typeof process.emitWarning;
|
||||
|
||||
registerBunOAuthFlows();
|
||||
|
||||
import { restoreSandboxEnv } from "./restore-sandbox-env.ts";
|
||||
|
||||
restoreSandboxEnv();
|
||||
|
||||
@@ -229,7 +229,7 @@ ${chalk.bold("Commands:")}
|
||||
${APP_NAME} install <source> [-l] Install extension source and add to settings
|
||||
${APP_NAME} remove <source> [-l] Remove extension source from settings
|
||||
${APP_NAME} uninstall <source> [-l] Alias for remove
|
||||
${APP_NAME} update [source|self|pi] Update pi (use --all for pi and extensions)
|
||||
${APP_NAME} update [source|self|pi] Update pi, extensions, or model catalogs
|
||||
${APP_NAME} list List installed extensions from settings
|
||||
${APP_NAME} config [-l] Open TUI to enable/disable package resources (Tab switches scope)
|
||||
${APP_NAME} <command> --help Show help for install/remove/uninstall/update/list/config
|
||||
|
||||
@@ -44,6 +44,7 @@ export function withRemoteCatalog(provider: Provider, catalogBaseUrl: string = D
|
||||
if (stored) dynamicModels = stored.models.filter((model) => model.provider === provider.id);
|
||||
if (!context.allowNetwork || context.signal?.aborted) return;
|
||||
if (
|
||||
!context.force &&
|
||||
stored?.checkedAt !== undefined &&
|
||||
Date.now() - stored.checkedAt < REMOTE_CATALOG_REFRESH_INTERVAL_MS
|
||||
) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { join } from "node:path";
|
||||
import { Markdown, type MarkdownTheme } from "@earendil-works/pi-tui";
|
||||
import chalk from "chalk";
|
||||
import { selectConfig } from "./cli/config-selector.ts";
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
VERSION,
|
||||
} from "./config.ts";
|
||||
import type { InlineExtension } from "./core/extensions/types.ts";
|
||||
import { ModelRuntime } from "./core/model-runtime.ts";
|
||||
import { DefaultPackageManager } from "./core/package-manager.ts";
|
||||
import { type AppMode, resolveProjectTrusted } from "./core/project-trust.ts";
|
||||
import { DefaultResourceLoader } from "./core/resource-loader.ts";
|
||||
@@ -30,7 +32,7 @@ import {
|
||||
|
||||
export type PackageCommand = "install" | "remove" | "update" | "list";
|
||||
|
||||
type UpdateTarget = { type: "all" } | { type: "self" } | { type: "extensions"; source?: string };
|
||||
type UpdateTarget = { type: "all" } | { type: "self" } | { type: "extensions"; source?: string } | { type: "models" };
|
||||
|
||||
const SELF_UPDATE_NOTE_MARKDOWN_THEME: MarkdownTheme = {
|
||||
heading: (text) => chalk.bold(chalk.yellow(text)),
|
||||
@@ -81,7 +83,7 @@ function getPackageCommandUsage(command: PackageCommand): string {
|
||||
case "remove":
|
||||
return `${APP_NAME} remove <source> [-l] [--approve|--no-approve]`;
|
||||
case "update":
|
||||
return `${APP_NAME} update [source|self|pi] [--self|--extensions|--all] [--extension <source>] [--approve|--no-approve] [--force]`;
|
||||
return `${APP_NAME} update [source|self|pi] [--self|--extensions|--models|--all] [--extension <source>] [--approve|--no-approve] [--force]`;
|
||||
case "list":
|
||||
return `${APP_NAME} list [--approve|--no-approve]`;
|
||||
}
|
||||
@@ -149,11 +151,12 @@ Examples:
|
||||
console.log(`${chalk.bold("Usage:")}
|
||||
${getPackageCommandUsage("update")}
|
||||
|
||||
Update pi and installed packages.
|
||||
Update pi, installed packages, or model catalogs.
|
||||
|
||||
Options:
|
||||
--self Update pi only (default when no target is given)
|
||||
--extensions Update installed packages only
|
||||
--models Refresh model catalogs only
|
||||
--all Update pi and installed packages
|
||||
--extension <source> Update one package only
|
||||
-a, --approve Trust project-local files for this command
|
||||
@@ -163,6 +166,7 @@ Options:
|
||||
Short forms:
|
||||
${APP_NAME} update Update pi only
|
||||
${APP_NAME} update --all Update pi and all extensions
|
||||
${APP_NAME} update --models Refresh model catalogs only
|
||||
${APP_NAME} update <source> Update one package
|
||||
${APP_NAME} update pi Update pi only (self works as alias to pi)
|
||||
`);
|
||||
@@ -205,6 +209,7 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
||||
let source: string | undefined;
|
||||
let selfFlag = false;
|
||||
let extensionsFlag = false;
|
||||
let modelsFlag = false;
|
||||
let allFlag = false;
|
||||
let extensionFlagSource: string | undefined;
|
||||
|
||||
@@ -242,6 +247,15 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--models") {
|
||||
if (command === "update") {
|
||||
modelsFlag = true;
|
||||
} else {
|
||||
invalidOption = invalidOption ?? arg;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--all") {
|
||||
if (command === "update") {
|
||||
allFlag = true;
|
||||
@@ -304,15 +318,24 @@ function parsePackageCommand(args: string[]): PackageCommandOptions | undefined
|
||||
let updateTarget: UpdateTarget | undefined;
|
||||
let showExtensionsSkippedNote = false;
|
||||
if (command === "update") {
|
||||
if (allFlag && (selfFlag || extensionsFlag || extensionFlagSource)) {
|
||||
if (allFlag && (selfFlag || extensionsFlag || modelsFlag || extensionFlagSource)) {
|
||||
conflictingOptions =
|
||||
conflictingOptions ?? "--all cannot be combined with --self, --extensions, or --extension";
|
||||
conflictingOptions ?? "--all cannot be combined with --self, --extensions, --models, or --extension";
|
||||
}
|
||||
if (allFlag && source) {
|
||||
conflictingOptions = conflictingOptions ?? "--all cannot be combined with a positional source";
|
||||
}
|
||||
|
||||
if (extensionFlagSource) {
|
||||
if (modelsFlag) {
|
||||
if (selfFlag || extensionsFlag || allFlag || extensionFlagSource) {
|
||||
conflictingOptions =
|
||||
conflictingOptions ?? "--models cannot be combined with --self, --extensions, --all, or --extension";
|
||||
}
|
||||
if (source) {
|
||||
conflictingOptions = conflictingOptions ?? "--models cannot be combined with a positional source";
|
||||
}
|
||||
updateTarget = { type: "models" };
|
||||
} else if (extensionFlagSource) {
|
||||
if (selfFlag || extensionsFlag || allFlag) {
|
||||
conflictingOptions =
|
||||
conflictingOptions ?? "--extension cannot be combined with --self, --extensions, or --all";
|
||||
@@ -371,6 +394,33 @@ function updateTargetIncludesExtensions(target: UpdateTarget): boolean {
|
||||
return target.type === "all" || target.type === "extensions";
|
||||
}
|
||||
|
||||
async function refreshModelCatalogs(agentDir: string): Promise<void> {
|
||||
const modelRuntime = await ModelRuntime.create({
|
||||
authPath: join(agentDir, "auth.json"),
|
||||
modelsPath: join(agentDir, "models.json"),
|
||||
allowModelNetwork: false,
|
||||
});
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 15_000);
|
||||
try {
|
||||
const result = await modelRuntime.refresh({
|
||||
allowNetwork: true,
|
||||
force: true,
|
||||
signal: controller.signal,
|
||||
});
|
||||
if (result.aborted) {
|
||||
throw new Error("Model catalog refresh timed out.");
|
||||
}
|
||||
if (result.errors.size > 0) {
|
||||
const details = Array.from(result.errors, ([provider, error]) => `${provider}: ${error.message}`).join("; ");
|
||||
throw new Error(`Could not refresh model catalogs: ${details}`);
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
console.log(chalk.green("Model catalogs refreshed"));
|
||||
}
|
||||
|
||||
function printSelfUpdateUnavailable(
|
||||
npmCommand?: string[],
|
||||
updatePackageTarget: SelfUpdatePackageTarget = PACKAGE_NAME,
|
||||
@@ -673,6 +723,17 @@ export async function handlePackageCommand(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (options.command === "update" && options.updateTarget?.type === "models") {
|
||||
try {
|
||||
await refreshModelCatalogs(getAgentDir());
|
||||
} catch (error: unknown) {
|
||||
const message = error instanceof Error ? error.message : "Unknown model catalog refresh error";
|
||||
console.error(chalk.red(`Error: ${message}`));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const cwd = process.cwd();
|
||||
const agentDir = getAgentDir();
|
||||
const writesProjectPackageConfig = (options.command === "install" || options.command === "remove") && options.local;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { tmpdir } from "node:os";
|
||||
import { delimiter, join } from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { ENV_AGENT_DIR, PACKAGE_NAME, VERSION } from "../src/config.ts";
|
||||
import { ModelRuntime } from "../src/core/model-runtime.ts";
|
||||
import type { ResolvedPaths } from "../src/core/package-manager.ts";
|
||||
import { InMemorySettingsStorage, SettingsManager } from "../src/core/settings-manager.ts";
|
||||
import { ProjectTrustStore } from "../src/core/trust-manager.ts";
|
||||
@@ -371,6 +372,42 @@ describe("package commands", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("refreshes only model catalogs with update --models", async () => {
|
||||
const refresh = vi.fn(async () => ({ aborted: false, errors: new Map<string, Error>() }));
|
||||
const create = vi.spyOn(ModelRuntime, "create").mockResolvedValue({ refresh } as unknown as ModelRuntime);
|
||||
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
await expect(runPackageCommandDirectly(["update", "--models"])).resolves.toBeUndefined();
|
||||
|
||||
expect(create).toHaveBeenCalledWith({
|
||||
authPath: join(agentDir, "auth.json"),
|
||||
modelsPath: join(agentDir, "models.json"),
|
||||
allowModelNetwork: false,
|
||||
});
|
||||
expect(refresh).toHaveBeenCalledWith({
|
||||
allowNetwork: true,
|
||||
force: true,
|
||||
signal: expect.any(AbortSignal),
|
||||
});
|
||||
expect(logSpy.mock.calls.map(([message]) => String(message)).join("\n")).toContain("Model catalogs refreshed");
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
expect(process.exitCode).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects update --models combined with another update target", async () => {
|
||||
const create = vi.spyOn(ModelRuntime, "create");
|
||||
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
await expect(runPackageCommandDirectly(["update", "--models", "--self"])).resolves.toBeUndefined();
|
||||
|
||||
expect(create).not.toHaveBeenCalled();
|
||||
expect(errorSpy.mock.calls.map(([message]) => String(message)).join("\n")).toContain(
|
||||
"--models cannot be combined with --self",
|
||||
);
|
||||
expect(process.exitCode).toBe(1);
|
||||
});
|
||||
|
||||
it("cycles project package overrides in config local mode", async () => {
|
||||
const storage = new InMemorySettingsStorage();
|
||||
storage.withLock("global", () => JSON.stringify({ packages: ["npm:pi-tools"] }));
|
||||
|
||||
@@ -21,12 +21,13 @@ function model(id: string): Model<"openai-completions"> {
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
describe("remote catalog provider", () => {
|
||||
it("parses keyed catalogs, sends version headers, and observes the refresh TTL", async () => {
|
||||
const fetchSpy = vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
||||
new Response(JSON.stringify({ dynamic: model("dynamic") }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
it("parses keyed catalogs, sends version headers, observes the refresh TTL, and supports forced refreshes", async () => {
|
||||
const fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(
|
||||
async () =>
|
||||
new Response(JSON.stringify({ dynamic: model("dynamic") }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
);
|
||||
const provider = withRemoteCatalog(
|
||||
createProvider({
|
||||
@@ -62,10 +63,20 @@ describe("remote catalog provider", () => {
|
||||
},
|
||||
allowNetwork: true,
|
||||
});
|
||||
await provider.refreshModels?.({
|
||||
credential: { type: "api_key" },
|
||||
store: {
|
||||
read: () => store.read(provider.id),
|
||||
write: (entry) => store.write(provider.id, entry),
|
||||
delete: () => store.delete(provider.id),
|
||||
},
|
||||
allowNetwork: true,
|
||||
force: true,
|
||||
});
|
||||
|
||||
expect(provider.getModels().map((entry) => entry.id)).toEqual(["static", "dynamic"]);
|
||||
expect((await store.read(provider.id))?.models.map((entry) => entry.id)).toEqual(["dynamic"]);
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
||||
expect(fetchSpy.mock.calls[0]?.[1]?.headers).toMatchObject({
|
||||
"User-Agent": expect.stringContaining(`pi/${VERSION}`),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user