Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/desktop/electron/main/pi-model-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export function relayChatMetadata(models: Models, id: string): Model<Api> | unde

/**
* Display-only compatibility data for speech/embedding/image settings rows that
* Pi 1.0.0 does not publish. It has no auth, prices, refresh, routing or dispatch.
* Pi's chat-provider records do not publish. It has no auth, prices, refresh,
* routing or dispatch.
* Chat selection and typed Pi operation lookups never consult this data.
*/
export function settingsOperationMetadata(providerId: string, vendor?: string, id?: string): ModelInfo[] {
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
"electron-updater": "^6.8.9"
},
"devDependencies": {
"@earendil-works/pi-ai": "1.0.0",
"@earendil-works/pi-mcp": "1.0.0",
"@earendil-works/pi-ai": "1.0.1",
"@earendil-works/pi-mcp": "1.0.1",
"@pi-desktop/agent-host": "workspace:*",
"@pi-desktop/agent-runtime": "workspace:*",
"@pi-desktop/host-runtime": "workspace:*",
Expand Down
45 changes: 39 additions & 6 deletions apps/desktop/test/oauth-standalone-bundle.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,28 @@ const exercise = String.raw`
import assert from "node:assert/strict";
import http from "node:http";
import { syncBuiltinESMExports } from "node:module";
http.Server.prototype.listen = function () { throw new Error("mock callback port unavailable"); };
let callbackServer;
const originalAddress = http.Server.prototype.address;
http.Server.prototype.listen = function (port, host, callback) {
assert.equal(port, 1455);
assert.equal(host, "127.0.0.1");
callbackServer = this;
queueMicrotask(() => callback?.());
return this;
};
http.Server.prototype.address = function () {
if (this === callbackServer) return { address: "127.0.0.1", family: "IPv4", port: 1455 };
return originalAddress.call(this);
};
http.Server.prototype.close = function (callback) {
if (this === callbackServer) {
callback?.();
return this;
}
return Reflect.apply(originalClose, this, [callback]);
};
const originalClose = http.Server.prototype.close;
http.Server.prototype.closeAllConnections = function () {};
syncBuiltinESMExports();
const { VendorOAuth, secretRefForProviderOauth } = await import("./oauth.mjs");
const secrets = new Map();
Expand Down Expand Up @@ -107,16 +128,28 @@ const deps = {
if (!url.startsWith("https://auth.openai.com/")) return;
authorization = new URL(url);
hostIds.push(authorization.searchParams.get("ext_agent_host_id"));
const callback = new URL("http://127.0.0.1:1455/auth/callback");
callback.searchParams.set("state", authorization.searchParams.get("state"));
callback.searchParams.set("code", "test-code");
callback.searchParams.set("client_id", "test-issued-client");
const requestHandler = callbackServer?.listeners("request")[0];
assert.equal(typeof requestHandler, "function");
await new Promise((resolve, reject) => {
requestHandler({ method: "GET", url: callback.pathname + callback.search }, {
writeHead(status) {
if (status !== 200) reject(new Error("unexpected OAuth callback response: " + status));
},
end() {
resolve();
},
});
});
},
emit: (event) => {
if (event.kind === "error") failed(new Error(event.message));
if (event.kind === "done") complete(event.providerId);
if (event.kind === "prompt") {
const callback = new URL("http://127.0.0.1:1455/auth/callback");
callback.searchParams.set("state", authorization.searchParams.get("state"));
callback.searchParams.set("code", "test-code");
callback.searchParams.set("client_id", "test-issued-client");
oauth.respond({ loginId: event.loginId, promptId: event.request.promptId, value: callback.href });
assert.equal(event.request.type, "manual_code");
}
},
};
Expand Down
1 change: 1 addition & 0 deletions docs/project/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
- Historical project board (archived; last refreshed 2026-08-11 for the 0.5.x line): [`BOARD.md`](BOARD.md)
- Documentation/code alignment audit: [2026-07-30 audit](2026-07-30-docs-code-audit.md)
- Plan implementation plan: [`plan-mode-implementation-plan.md`](plan-mode-implementation-plan.md)
- Pi 1.0.1 adoption: [`pi-101-adoption.md`](pi-101-adoption.md)
- GitHub Issues + Milestones: repository Issues page
- GitHub Projects: create after adding the `project` token scope
79 changes: 79 additions & 0 deletions docs/project/pi-101-adoption.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Pi 1.0.1 adoption

Status: task candidate commit `2c90e2ebb0598fb2e3d4b31c78265eec8a2e6d71`
on `fix/pi-1.0.1-adaptation`, based on `origin/main` at
`3b036cc7810e18b3ef7689a2b93385125a8d0a3f`.

## Scope

All five direct Pi dependencies used by Agent Runtime and Electron Main are
pinned exactly to `1.0.1`. The three existing Desktop patches were rebased on
the published 1.0.1 package contents; the pnpm lockfile records the resulting
patch hashes and upstream package versions.

| Workspace entry | Direct dependencies |
| --- | --- |
| `packages/agent-runtime` | `@earendil-works/pi-agent-core`, `@earendil-works/pi-ai`, `@earendil-works/pi-coding-agent` |
| `apps/desktop` | `@earendil-works/pi-ai`, `@earendil-works/pi-mcp` |

The 1.0.1 release adds retry classification for model-capacity errors in
`pi-ai`, updates Anthropic request handling and its SDK, and includes fixes for
Bedrock thinking state and OAuth callback port collisions. `pi-agent-core`'
runtime implementation is unchanged. `pi-coding-agent` and its transitive Pi
packages receive the 1.0.1 release updates. `pi-mcp` replaces the
`clientMetadataUrl` option with `clientMetadataDocument`; repository code does
not call that option directly, and the updated runtime consumes the package.

No Host RPC, Plugin SDK, provider persistence, session format, or permission
contract changes. The desktop retry classifier already retries generic
provider failures. A regression contract test now confirms both that
classification and Pi's 1.0.1 native classifier treat “Selected model is at
capacity” as retryable.

## Fresh-release policy

At adoption time, Pi 1.0.1 and seven packages in its release group are inside
the configured minimum-release-age window. `pnpm-workspace.yaml` therefore
contains exact-version exclusions for those eight packages. The dependency
check rejects missing, additional, wildcard, or non-1.0.1 exclusions. Remove
these entries once the release group clears the age window and regenerate the
lockfile; do not broaden them.

## Patch identity

| Package | Patch SHA-256 |
| --- | --- |
| `@earendil-works/pi-agent-core@1.0.1` | `02de513ae53cf7f1e92d0cfc7fce07cf880d31195f5ec621d2f2197ead92a9da` |
| `@earendil-works/pi-ai@1.0.1` | `0c7a4701594d70c49699adfef275d7c1f997d1c55d103ab09f3377523d322981` |
| `@earendil-works/pi-coding-agent@1.0.1` | `ab63d8f7817d606be15d14daedd329df64bb34e73b71c6842b02f67643ba0769` |

The `check:pi-dependencies` and `check:pi-patches` scripts verify exact direct
pins, installed package versions, patch mappings and lockfile identities, and
representative patched source and declaration contracts.

## Validation

| Candidate | `2c90e2ebb0598fb2e3d4b31c78265eec8a2e6d71` |
| --- | --- |
| Base main | `3b036cc7810e18b3ef7689a2b93385125a8d0a3f` |
| E2E suites | Pi Agent Runtime live request; protocol-level `pnpm test:e2e` |
| Result | Both passed; smoke suite 25/25 passed, 0 skipped |
| Environment | `.env`-configured model and host-core built from the candidate source; output redacted API key and endpoint. |

- `pnpm install --frozen-lockfile --ignore-scripts`, `pnpm check:pi-dependencies`,
`pnpm check:pi-patches`, and `pnpm audit --recursive --prod` passed; audit
found no known vulnerabilities.
- `pnpm build:js`, agent-runtime and desktop typechecks, and `pnpm lint` passed.
- Agent Runtime: 93 files / 1,234 tests. Desktop: 3,484 tests; no failures or
skipped tests.
- `pnpm docs:check` and `pnpm check:agent-policy` passed.
- `node scripts/e2e-agent-live.mjs` returned the expected
`hello-from-pi-desktop` response.
- `pnpm test:e2e` passed 25/25, including live completion and streaming.
- `cargo build --locked -p host-core` passed using the shared host Cargo target.

## Rollback

Rollback the dependency manifests, lockfile, release-age entries, patch files,
and version references together. Do not roll back an individual patched Pi
package while retaining the others at 1.0.1.
6 changes: 3 additions & 3 deletions docs/spec/02-architecture/02-tech-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
| Host backend | **Rust** | stable Rust toolchain | tools/plugins/permissions/persistence adapters |
| Rust async | tokio | stable | host services |
| Host RPC | stdio JSON-RPC (NDJSON) | frozen (D001) | Electron main ↔ Rust host |
| Agent engine | `@earendil-works/pi-agent-core` | 1.0.0 | agent loop |
| Model API | `@earendil-works/pi-ai` | 1.0.0 | provider adapters, OAuth, and stream handling |
| Model catalog | pi-ai Providers/Models | pinned 1.0.0 + explicit provider refresh | account-scoped metadata and typed operations |
| Agent engine | `@earendil-works/pi-agent-core` | 1.0.1 | agent loop |
| Model API | `@earendil-works/pi-ai` | 1.0.1 | provider adapters, OAuth, and stream handling |
| Model catalog | pi-ai Providers/Models | pinned 1.0.1 + explicit provider refresh | account-scoped metadata and typed operations |

> pi-ai owns published metadata, native thinking support, transports and auth.
> Host persists Desktop account rows, credentials, and explicit binding overrides.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ boundaries must keep that split clear as upstream packages evolve.
## 2. Current ownership

The sidecar pins `@earendil-works/pi-agent-core`, `pi-ai`, and
`pi-coding-agent` together at exactly `1.0.0`; Desktop's `pi-ai` and `pi-mcp`
`pi-coding-agent` together at exactly `1.0.1`; Desktop's `pi-ai` and `pi-mcp`
development dependencies use the same release. Target-release package patches
remain narrow compatibility deltas and are listed with their audited behavior
in `docs/project/pi-1000-patch-audit.md`.
in `docs/project/pi-101-adoption.md`.

- `@earendil-works/pi-agent-core` supplies the agent loop and its stable agent,
event, and tool types. Agent Runtime owns desktop-specific compaction,
Expand All @@ -31,7 +31,7 @@ in `docs/project/pi-1000-patch-audit.md`.
host-core checkpoint-compaction path, and durable session store do not use
the package's AgentSession.

The extension shim is a versioned compatibility subset, not the full 1.0.0
The extension shim is a versioned compatibility subset, not the full 1.0.1
coding-agent API. Its legacy `VERSION` marker remains sourced from
`TRUSTED_EXTENSION_KERNEL_VERSION` and intentionally identifies the modeled
0.87.1 extension surface.
Expand Down
4 changes: 2 additions & 2 deletions docs/spec/03-runtime/02-agent-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -1650,7 +1650,7 @@ with the original v3 `SessionManager`, Pi `ModelRuntime`, `SettingsManager`, and
leaf, compaction, model/thinking changes, and context-bearing custom messages;
it is never reconstructed from renderer `UiMessage` rows.

The Pi 1.0.0 SDK also applies append-only `context_edit` entries to this model
The Pi 1.0.1 SDK also applies append-only `context_edit` entries to this model
projection. An edit can omit or replace an earlier message for later provider
requests without rewriting its raw JSONL entry or the visible native history.
Native Pi extensions use the SDK's boundary hooks; all entries they append,
Expand Down Expand Up @@ -1720,7 +1720,7 @@ and never triggers a provider transport rebuild. Protocol errors such as
`EPROTO` keep their existing retry behavior. See
[certificate trust ADR](../../adr/provider-system-certificates.md).

## Pi 1.0.0 execution boundary
## Pi 1.0.1 execution boundary

Published model metadata and account entitlement come from one account-scoped
Pi Models collection. Effective binding projection is shared by launch, delegates
Expand Down
2 changes: 1 addition & 1 deletion docs/spec/03-runtime/11-provider-model-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ same vendor key.

### Anthropic token endpoint rate limits

The pinned pi-ai 1.0.0 patch gives Anthropic authorization-code exchange and
The pinned pi-ai 1.0.1 patch gives Anthropic authorization-code exchange and
refresh a shared, bounded token-request policy: retry only an explicit HTTP
429, at most three total requests. Wait at least 1 s then 2 s, or longer when
`Retry-After` gives delta seconds or an HTTP date. A server delay beyond the
Expand Down
2 changes: 1 addition & 1 deletion docs/spec/03-runtime/21-image-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ previews, editing a generated file, collapsed results, and setup navigation.
Live verification is opt-in via `scripts/test-image-generation-live.mjs`, limited
to one generation plus one edit and never a default test command.

## Pi 1.0.0 operation boundary
## Pi 1.0.1 operation boundary

Image generation and edits execute through account-scoped Pi `Models.generateImages`.
Use native OpenRouter images or a registered compatible OpenAI-images adapter,
Expand Down
4 changes: 2 additions & 2 deletions docs/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -3322,7 +3322,7 @@ identify the platform validation still needed.

#### E2E-MCP-pi-client-owner-policy: Pi protocol under existing Desktop owners

- **Preconditions**: Pi 1.0.0 with the pinned host-policy patch; offline stdio and
- **Preconditions**: Pi 1.0.1 with the pinned host-policy patch; offline stdio and
HTTP fixtures, no paid provider or user credentials.
- **Steps**: Run `plugin-mcp.test.mjs`, `user-mcp.test.mjs`,
`mcp-stdio-launch.test.mjs`, `mcp-call-registry.test.mjs`, `mcp-oauth.test.mjs`,
Expand Down Expand Up @@ -16381,7 +16381,7 @@ renderer's durable transcript reads. No real model or provider is contacted.

## E2E-OAUTH-pi-installation-identity-and-standalone-load

- **Preconditions**: Pi 1.0.0; temporary Host secrets and account fixtures;
- **Preconditions**: Pi 1.0.1; temporary Host secrets and account fixtures;
network/browser/callback I/O mocked; no user account or paid service.
- **Steps**: Run `installation-identity.test.mjs`, `vendor-oauth-login.test.mjs`
and `oauth-standalone-bundle.test.mjs` under `apps/desktop/test`.
Expand Down
6 changes: 3 additions & 3 deletions docs/zh-CN/spec/02-architecture/02-tech-stack.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
| 主机后端 | **Rust** | 稳定的 Rust 工具链 | tools/plugins/permissions/persistence 适配器 |
| Rust 异步 | 东京 | 稳定 | 主机服务 |
| 主机 RPC | stdio JSON-RPC (NDJSON) | 冷冻(D001) | Electron 主 ↔ Rust 主机 |
| Agent 引擎 | `@earendil-works/pi-agent-core` | 1.0.0 | Agent 循环及稳定的 agent/event/tool 类型 |
| 模型 API | `@earendil-works/pi-ai` | 1.0.0 | 提供商 |
| Model catalog | pi-ai Providers/Models | pinned 1.0.0 + explicit provider refresh | account-scoped metadata and typed operations |
| Agent 引擎 | `@earendil-works/pi-agent-core` | 1.0.1 | Agent 循环及稳定的 agent/event/tool 类型 |
| 模型 API | `@earendil-works/pi-ai` | 1.0.1 | 提供商 |
| Model catalog | pi-ai Providers/Models | pinned 1.0.1 + explicit provider refresh | account-scoped metadata and typed operations |

> pi-ai 提供已发布的模型元数据、原生思考能力、传输和认证。
> Host 持有 Desktop 账户行、凭据和显式绑定覆盖。
Expand Down
2 changes: 1 addition & 1 deletion docs/zh-CN/spec/03-runtime/02-agent-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -1171,7 +1171,7 @@ System/Direct/Custom 代理路由保持不变。
provider transport 重建。`EPROTO` 等协议错误继续使用原有重试行为。详见
[证书信任 ADR](../../../adr/provider-system-certificates.md)。

## Pi 1.0.0 execution boundary
## Pi 1.0.1 execution boundary

Published model metadata and account entitlement come from one account-scoped
Pi Models collection. Effective binding projection is shared by launch, delegates
Expand Down
4 changes: 2 additions & 2 deletions docs/zh-CN/spec/03-runtime/11-provider-model-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ PI-Desktop 不得把用户永久限制在一份简短的固定模型列表上。

### 6.2 Catalog responsibilities

1. pi-ai 1.0.0 Providers/Models own published metadata, transport, thinking
1. pi-ai 1.0.1 Providers/Models own published metadata, transport, thinking
support and native operation types. Electron's historically named
`ModelsDevCatalog` is an account-aware adapter over this public API.
2. Startup is cache-only and disables ambient environment/file credentials.
Expand Down Expand Up @@ -381,7 +381,7 @@ Codex CLI 版本(`CODEX_MODELS_CLIENT_VERSION`),账户模型缺失时调

### Anthropic token 端点限流

固定版本 pi-ai 1.0.0 的仓库补丁为 Anthropic 授权码交换与刷新提供同一套
固定版本 pi-ai 1.0.1 的仓库补丁为 Anthropic 授权码交换与刷新提供同一套
有限策略:只重试明确的 HTTP 429,最多总共三次请求。先等待至少 1 秒、再
等待至少 2 秒;若 `Retry-After` 给出更长的秒数或 HTTP 日期,则遵守该时间。
服务器要求的等待超出剩余预算时结束本次尝试,不缩短等待后提前重试。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ type RecentModelRef = {

## 6. Refresh behavior

The pinned pi-ai 1.0.0 catalog is the startup baseline. Startup reads no remote
The pinned pi-ai 1.0.1 catalog is the startup baseline. Startup reads no remote
catalog and uses no ambient credentials. `providers.refreshModelCatalog` invokes
Pi's public refresh API; settings metadata lookups themselves are local.

Expand Down
2 changes: 1 addition & 1 deletion docs/zh-CN/spec/03-runtime/21-image-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

`node scripts/e2e-image-chat.mjs` 在隔离桌面中使用本地模型/图片 HTTP 夹具,覆盖相邻默认设置、Composer 提交、批量结果、引用生成文件编辑、收起详情和配置跳转。真实接口验证通过 `scripts/test-image-generation-live.mjs` 显式启用,仅限一次生成和一次编辑,不属于默认测试命令。

## Pi 1.0.0 operation boundary
## Pi 1.0.1 operation boundary

Image generation and edits execute through account-scoped Pi `Models.generateImages`.
Use native OpenRouter images or a registered compatible OpenAI-images adapter,
Expand Down
6 changes: 3 additions & 3 deletions packages/agent-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
"clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true});fs.rmSync('dist-bundle',{recursive:true,force:true})\""
},
"dependencies": {
"@earendil-works/pi-agent-core": "1.0.0",
"@earendil-works/pi-ai": "1.0.0",
"@earendil-works/pi-coding-agent": "1.0.0",
"@earendil-works/pi-agent-core": "1.0.1",
"@earendil-works/pi-ai": "1.0.1",
"@earendil-works/pi-coding-agent": "1.0.1",
"@pi-desktop/shared": "workspace:*",
"jiti": "2.7.0",
"typebox": "1.3.27",
Expand Down
24 changes: 24 additions & 0 deletions packages/agent-runtime/src/pi-upstream-1.0-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { convertResponsesMessages } from "@earendil-works/pi-ai/api/openai-respo
import { retryProviderRequest } from "@earendil-works/pi-ai/utils/provider-retry";
import { isContextOverflow } from "@earendil-works/pi-ai/utils/overflow";
import { normalizeContext, type AssistantMessage, type Model } from "@earendil-works/pi-ai";
import { isRetryableAssistantError } from "@earendil-works/pi-ai/compat";
import { classifyProviderError, isTransientProviderRetryCode } from "./provider-retry.js";

const testModel: Model<"openai-responses"> = {
id: "test-model",
Expand Down Expand Up @@ -80,6 +82,28 @@ describe("Pi 1.0 upstream regression contracts", () => {
}
});

it("keeps model-capacity failures retryable in Pi and Desktop runtimes", () => {
const message: AssistantMessage = {
role: "assistant",
content: [],
api: testModel.api,
provider: testModel.provider,
model: testModel.id,
usage: {
input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "error",
errorMessage: "Selected model is at capacity",
timestamp: 1,
};

expect(isRetryableAssistantError(message)).toBe(true);
const desktopError = classifyProviderError(message);
expect(desktopError).toMatchObject({ code: "PROVIDER_ERROR", retriable: true });
expect(isTransientProviderRetryCode(desktopError.code)).toBe(true);
});

it("recognizes the Z.AI context-overflow finish reason", () => {
const message: AssistantMessage = {
role: "assistant",
Expand Down
Loading
Loading