@specode/pi-antigravity 0.1.0 → 0.1.2

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.
package/README.md CHANGED
@@ -2,84 +2,90 @@
2
2
 
3
3
  [English](./README.md) | [简体中文](./README.zh-CN.md)
4
4
 
5
- An independently maintained Antigravity subscription provider for Pi. It adapts the official **agy CLI HTTP protocol** without running the CLI, an ACP bridge, or another agent.
5
+ A [Pi](https://github.com/earendil-works/pi-mono) extension that registers the Antigravity (`antigravity`) subscription chat provider using the official agy CLI HTTP protocol.
6
6
 
7
- **Status: public-source chat provider, installed and user-tested locally.** Tested against Pi **1.5.2** with synthetic HTTP responses and bounded live diagnostics; see the acceptance limits below.
7
+ It connects directly to Google's Cloud Code API with your Antigravity subscription and reuses Pi's native OAuth storage, model catalog, tool execution, and session management—without running the official CLI binary, an ACP bridge, or a secondary agent loop.
8
8
 
9
9
  ## Installation
10
10
 
11
- Requires Node.js >= 22.19.0 and the Pi 1.5.2 provider APIs used by this implementation.
11
+ Requires Node.js >= 22.19.0 and Pi >= 1.5.2.
12
12
 
13
13
  From npm:
14
14
 
15
- ```sh
15
+ ```bash
16
16
  pi install npm:@specode/pi-antigravity
17
17
  ```
18
18
 
19
- Or from GitHub:
19
+ From GitHub:
20
20
 
21
- ```sh
21
+ ```bash
22
22
  pi install git:github.com/specode/pi-antigravity
23
23
  ```
24
24
 
25
- If the older `pi-antigravity` package is installed, remove it first with `pi remove npm:pi-antigravity`. Never load two implementations of the `antigravity` provider together. Existing Pi OAuth credentials are retained; no CLI credential migration is required.
25
+ From a local directory for development:
26
26
 
27
- Run `/reload` or restart Pi after installation. If needed, use `/login`, choose Antigravity, and paste the complete callback URL when prompted. Select a chat model with `/model`.
28
-
29
- For local development:
30
-
31
- ```sh
27
+ ```bash
32
28
  pi install /path/to/pi-antigravity
33
29
  ```
34
30
 
35
- ## Development
31
+ Run `/reload` or restart Pi after installing.
36
32
 
37
- ```sh
38
- npm ci
39
- npm run check
40
- ```
33
+ If another package registering the `antigravity` provider ID (such as `pi-antigravity`) is installed, remove it first with `pi remove npm:pi-antigravity`. Only one `antigravity` provider should be loaded at a time. Existing Pi OAuth credentials in `~/.pi/agent/auth.json` are reused as-is.
41
34
 
42
- `check` runs strict type checking, offline tests, an isolated real Pi extension-load smoke test, and a package dry run. The smoke test uses a temporary config directory and blocks network fetches; it does not touch your installed extensions or credentials.
35
+ ## Usage
43
36
 
44
- The extension entry is `src/index.ts`. Do not load it alongside another provider registered as `antigravity`. Switching your installed provider, live account testing, and publishing are separate steps—not performed by development tests.
37
+ 1. Run `/login`, choose `Sign in with an account` → `Antigravity` (`Sign in with Google (Antigravity subscription)`), and open the authorization URL in your browser.
38
+ 2. After signing in with Google, click **Copy to Clipboard** on the Antigravity page and paste the authorization code into Pi. Use the code from this login attempt, not the address bar URL—the page removes its query parameters. A complete callback URL containing `code` and `state` is also accepted.
39
+ 3. Run `/model` to select an `antigravity` model and adjust its thinking effort as needed.
45
40
 
46
- ## Publishing
41
+ ### Model Discovery and Routing
47
42
 
48
- The scoped npm package is configured for public access. GitHub Actions validates pushes and pull requests. The first bootstrap publication requires an authenticated npm maintainer; afterward, configure this repository and `npm-publish.yml` as an npm trusted publisher for future GitHub Release publications through OIDC. The workflow requires no long-lived npm token. Publishing and creating releases are external operations and require explicit authorization.
43
+ | Aspect | Behavior |
44
+ | --- | --- |
45
+ | Catalog discovery | Calls `loadCodeAssist` and `fetchAvailableModels`, exposing curated chat models (`agentModelSorts` / `recommended`) and filtering out internal placeholders and image models |
46
+ | Thinking effort | Groups explicit `-low`, `-medium`, and `-high` runtime suffixes under one public model ID and maps the selected effort to `generationConfig.thinkingConfig`; unsupported effort levels fail explicitly without silent downgrades |
47
+ | Offline cache | Saves the last successful catalog per account in Pi's `models-store.json` (4-hour refresh TTL); restores cached models on offline startup and retains them if a refresh fails |
48
+ | Account isolation | Binds cached models to the authenticated account (`email` or derived `ownershipId`), `projectId`, and official endpoint; switching accounts hides the previous catalog |
49
+ | Token usage and cost | Populates prompt, cached, output, and thinking token counts from stream metadata; sets cost fields to `0` (unpriced subscription usage, not free API billing) |
49
50
 
50
- ## Request flow
51
+ ### Companion Extensions
51
52
 
52
- 1. **Authentication:** native Pi OAuth registration, Google manual callback-URL login with PKCE/state validation, and token refresh. Pi owns credential storage and refresh locking. Existing OAuth credentials retain `access`, `refresh`, `expires`, `projectId`, and optional `email`.
53
- 2. **Discovery:** initialize the consumer account and fetch the curated chat catalog. Public model IDs are separate from runtime IDs and explicitly discovered effort variants. Pi's standard model store retains the last successful account-owned snapshot for offline startup; failed refreshes do not erase it.
54
- 3. **Conversion:** reuse Pi's public Google converters and normalized transcript. Rewrite only Pi's exact known opening sentences in the serialized system prompt; preserve rules, tools, user text, and session history.
55
- 4. **Streaming:** one HTTP attempt to an approved official HTTPS origin, incremental SSE parsing, Pi instrumentation hooks, signatures, tool calls, usage, cancellation, separate header/idle deadlines, and one terminal event. No internal retries or hidden endpoint/model failover.
53
+ - **[`@specode/pi-subscription-usage`](https://github.com/specode/pi-subscription-usage)**: Displays Antigravity subscription quota pools (Gemini, Claude & GPT, and Gemini 4 Argon) in the footer status bar and via `/usage`. This provider preserves the `antigravity` provider ID, official Google endpoint, and `{ token, projectId, email }` credential bridge expected by `pi-subscription-usage`.
54
+ - **[`@specode/pi-last-model-effort`](https://github.com/specode/pi-last-model-effort)**: Remembers the thinking effort you last selected for each Antigravity model and restores it across model switches and new TUI sessions.
56
55
 
57
- The default endpoint is `https://daily-cloudcode-pa.googleapis.com`, observed for the consumer account in the handoff. This is not a universal enterprise-routing claim. CLI identity is pinned to **1.5.9 / build 1021519450**, with the actual host platform and architecture.
56
+ ## Protocol and Boundaries
58
57
 
59
- ## Boundaries
58
+ - **Official CLI HTTP alignment**: Targets consumer Antigravity subscriptions (`https://daily-cloudcode-pa.googleapis.com` by default; requires an existing Cloud Code Assist project, with no enterprise routing or automatic GCP project onboarding). Sends chat requests to `/v1internal:streamGenerateContent?alt=sse` with the `antigravity/cli/1.5.9` (build `1021519450`) client identity, host OS/architecture, `agent` request envelope, trajectory labels, and empty-seed 64-bit FNV-1a `sessionId`.
59
+ - **Public Pi converters and tool loop**: Resolves `@earendil-works/pi-ai`'s public Google message and tool converters through host package exports, preserving `thoughtSignature` (including signed empty text parts), paired tool-call IDs, and the verified `gemini-3.8-flash-low` function-response role adjustment. Tool schema types are uppercased only at JSON Schema keyword positions without altering property names or literal values; unsupported `$ref` / `$dynamicRef` schemas or `strict: "require"` contracts fail explicitly instead of silently dropping tools.
60
+ - **Targeted system prompt rewrite**: Rewrites only Pi's exact default two-sentence opening in the serialized request to `You are a coding agent helping the user with their project.` so the backend does not reject the default opening with `429 RESOURCE_EXHAUSTED`. User messages, session files, project rules, and custom prompts are never modified.
61
+ - **Focused scope**: Provides text chat, coding, and tool calling only. Does not include image generation/editing, web search/fetch tools, multi-account rotation, credit overage, background warming, or a built-in quota UI.
62
+ - **Predictable transport**: Makes a single HTTP request per turn with separate response-header (30s) and stream-idle (120s) timeouts. Never performs hidden retries or silent model, effort, or endpoint failover.
60
63
 
61
- - Chat/coding provider only. Antigravity image generation/editing has been cancelled and is not a remaining implementation or testing task.
62
- - Only curated chat models and explicit supported thinking variants are exposed. Unknown thinking contracts are omitted rather than guessed; unsupported requested effort fails visibly.
63
- - Subscription per-token billing is not known. Pi's zero cost metadata means **unpriced**, not free usage or an invoice estimate. Token usage is recorded separately from subscription quota.
64
- - Tool schema names and literal values remain unchanged. Unsupported references or required strict-sampling contracts fail instead of silently removing tools.
65
- - Request UUIDs and transcript counters are provider-local bookkeeping, not a claimed reconstruction of every CLI retry/resume rule. Pi session IDs keep ordinary resumes stable and forks separate; direct calls without a session ID are ephemeral.
66
- - No account switching, credit overage, background warming, quota UI, search/fetch tools, CLI storage migration, or private Pi runtime imports.
64
+ ## Privacy and Safety
67
65
 
68
- ## Companion plugins
66
+ - Credentials are managed solely by Pi's native OAuth store (`auth.json`). The extension never reads, copies, or migrates official CLI credential files or OS keychain entries.
67
+ - Requests are sent only to allowlisted official HTTPS origins (`https://daily-cloudcode-pa.googleapis.com` by default, plus `https://cloudcode-pa.googleapis.com` and `https://daily-cloudcode-pa.sandbox.googleapis.com`) with HTTP redirects disabled (`redirect: "error"`).
68
+ - OAuth sign-in accepts the authorization code copied from the official page and exchanges it using this login attempt's independent S256 PKCE verifier. The copied code contains no `state`, so that path relies on the token endpoint's PKCE validation. Complete `https://antigravity.google/oauth-callback` URLs are also accepted, with strict origin/path checks and constant-time `state` validation; invalid URLs never fall back to bare-code parsing.
69
+ - OAuth and streaming error messages expose only allowlisted error codes or bounded diagnostic metadata and never include access or refresh tokens.
69
70
 
70
- - **[pi-subscription-usage](https://github.com/specode/pi-subscription-usage):** owns quota queries and display. The JSON credential bridge retains `token`, `projectId`, and optional `email`. Email-less accounts add an opaque `ownershipId` (persisted by Pi as `antigravityOwnershipId`) so token rotation cannot break catalog ownership; existing consumers can ignore it. This development work does not modify that plugin.
71
- - **[pi-subscription-image](https://github.com/specode/pi-subscription-image):** its existing Codex/Grok functionality is unchanged. The planned Antigravity backend has been cancelled; this provider registers no image-generation API or tool. Earlier image evidence is retained only as historical research.
71
+ ## Development
72
72
 
73
- ## Evidence and remaining acceptance
73
+ ```bash
74
+ # Install dependencies
75
+ npm ci
74
76
 
75
- The earlier **2026-11-15** protocol investigation covered 17 generation requests, including native read-tool feasibility, strict prompt-opening controls, and one reference-image request. [The evidence manifest](./test/fixtures/agy-cli-1.5.9/manifest.json) distinguishes official captures from direct probes. Redacted records are structural evidence, not replayable requests.
77
+ # Run typecheck, offline tests, isolated packed-extension smoke test, and package check
78
+ npm run check
76
79
 
77
- Current offline tests exercise native Pi model/auth integration and real local Pi read-tool execution against mocked Gemini/Claude responses, plus malformed streams, cancellation, timeouts, signatures, concurrency, and cache isolation. OAuth application parameters were statically checked against the pinned official CLI archive; **this is not a live browser-login test**.
80
+ # Run offline unit and integration tests only
81
+ npm test
78
82
 
79
- Local deployment also verified real account initialization and catalog discovery, persistence of six public models, and offline restoration by the installed CLI. Existing credentials were unchanged.
83
+ # Validate a published npm version in an isolated Pi environment with network blocked
84
+ npm run smoke -- @specode/pi-antigravity@0.1.2
85
+ ```
80
86
 
81
- A separately authorized six-request Argon High diagnosis isolated a 429 to this repository's own injected instructions: they quoted the original Pi opening after the leading prompt had already been adapted. A strict same-request comparison changed only that documentation block to a source reference and returned 429 → 200. The literal example was removed from `AGENTS.md`, not from arbitrary user rules or history; a regression test prevents reintroduction. Small output caps and no tool execution mean this is not full tool-loop acceptance. Reload Pi's project rules before retrying an affected session.
87
+ Verification covers strict type checking, offline unit/integration suites (including local Pi `read`-tool execution against mocked Gemini/Claude streams), static checks against official agy CLI `1.5.9`, and bounded consumer-account testing on Pi `1.5.2`; live browser `/login` and exhaustive model/account combinations have not all been individually verified. See [AGENTS.md](./AGENTS.md) for architecture and verification boundaries, and [`test/fixtures/agy-cli-1.5.9/manifest.json`](./test/fixtures/agy-cli-1.5.9/manifest.json) for redacted protocol research fixtures.
82
88
 
83
- The user subsequently reported successful real usage after the fix. This records local deployment/fix acceptance, not verified coverage of every model, tool continuation, login, resume, or cancellation path.
89
+ ## License
84
90
 
85
- Remaining individually unverified areas: authorized `/login`, complete live tool-loop and session UI acceptance, and broader account/model coverage. Antigravity image generation is out of scope, not unfinished work. See [AGENTS.md](./AGENTS.md), the sole development/handoff entry point, for exact contracts and limitations.
91
+ [MIT](./LICENSE). See [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for protocol references and third-party notices.
package/README.zh-CN.md CHANGED
@@ -2,84 +2,90 @@
2
2
 
3
3
  [English](./README.md) | [简体中文](./README.zh-CN.md)
4
4
 
5
- 独立维护的 Pi Antigravity 订阅 provider。适配官方 **agy CLI 的 HTTP 协议**,运行时不启动官方 CLI、ACP bridge 或另一套 agent。
5
+ 一个 [Pi](https://github.com/earendil-works/pi-mono) 扩展:对齐官方 agy CLI 的 HTTP 协议,在 Pi 中注册 Antigravity(`antigravity`)订阅聊天提供商。
6
6
 
7
- **状态:公开源码的聊天 provider,已安装到本机并获用户实测确认。** 基于 Pi **1.5.2** 完成合成 HTTP 测试和受限真实对照;验收边界见下文。
7
+ 直接使用 Antigravity 订阅连接 Google Cloud Code 接口,复用 Pi 原生的 OAuth 凭据存储、模型目录、工具执行与会话管理,无需运行官方 CLI 二进制、ACP 桥接进程或另一套 Agent 循环。
8
8
 
9
9
  ## 安装
10
10
 
11
- 需要 Node.js >= 22.19.0,以及本实现使用的 Pi 1.5.2 provider API。
11
+ 需要 Node.js >= 22.19.0,以及 Pi >= 1.5.2。
12
12
 
13
13
  从 npm 安装:
14
14
 
15
- ```sh
15
+ ```bash
16
16
  pi install npm:@specode/pi-antigravity
17
17
  ```
18
18
 
19
- 也可以从 GitHub 安装:
19
+ 从 GitHub 安装:
20
20
 
21
- ```sh
21
+ ```bash
22
22
  pi install git:github.com/specode/pi-antigravity
23
23
  ```
24
24
 
25
- 如果已安装旧 `pi-antigravity` 包,先执行 `pi remove npm:pi-antigravity`。不要同时加载两个注册 `antigravity` 的实现。保留现有 Pi OAuth 凭据,无需迁移 CLI 登录存储。
25
+ 从本地目录安装(开发调试):
26
26
 
27
- 安装后执行 `/reload` 或重启 Pi。需要登录时运行 `/login`,选择 Antigravity,并按提示粘贴完整回调 URL;通过 `/model` 选择聊天模型。
28
-
29
- 本地开发也可以直接安装源码目录:
30
-
31
- ```sh
27
+ ```bash
32
28
  pi install /path/to/pi-antigravity
33
29
  ```
34
30
 
35
- ## 开发验证
31
+ 安装后执行 `/reload` 或重启 Pi 生效。
36
32
 
37
- ```sh
38
- npm ci
39
- npm run check
40
- ```
33
+ 如果已安装其他同样注册 `antigravity` provider ID 的包(例如 `pi-antigravity`),请先执行 `pi remove npm:pi-antigravity` 卸载,不要同时加载两个 `antigravity` 提供商。`~/.pi/agent/auth.json` 中已有的 Pi OAuth 登录凭据可直接复用。
41
34
 
42
- `check` 包含严格类型检查、离线测试、真实 Pi 扩展加载的隔离冒烟测试及打包预检。冒烟测试使用临时配置目录并阻止网络 fetch,不触碰用户已安装扩展或凭据。
35
+ ## 使用说明
43
36
 
44
- 扩展入口为 `src/index.ts`。不要与另一个注册 `antigravity` 的 provider 同时加载。切换用户安装、真实账号测试和发布均为独立步骤,不会由开发测试执行。
37
+ 1. 执行 `/login`,依次选择 `Sign in with an account` → `Antigravity`(`Sign in with Google (Antigravity subscription)`),在浏览器中打开终端提示的授权链接。
38
+ 2. 完成 Google 账号授权后,在 Antigravity 页面点击 **Copy to Clipboard**,将授权码粘贴回 Pi。请使用本次登录产生的授权码,不要复制地址栏 URL——页面会主动移除其中的查询参数。同时兼容包含 `code` 和 `state` 的完整回调 URL。
39
+ 3. 执行 `/model` 选择 `antigravity` 下的模型,并按需切换推理档位(thinking effort)。
45
40
 
46
- ## 发布
41
+ ### 模型发现与路由规则
47
42
 
48
- scoped npm 包配置为公开访问。GitHub Actions 验证 push 和 pull request。首次发布需要已认证的 npm 维护者;之后在 npm 为本仓库和 `npm-publish.yml` 配置 trusted publisher,未来创建 GitHub Release 时通过 OIDC 发布,工作流不使用长期 npm token。发布和创建 Release 属于外部操作,仍须明确授权。
43
+ | 环节 | 行为 |
44
+ | --- | --- |
45
+ | 目录拉取 | 调用 `loadCodeAssist` 与 `fetchAvailableModels`,仅公开精选聊天模型(`agentModelSorts` / `recommended`),过滤内部占位符与生图模型 |
46
+ | 推理档位 | 将带 `-low`、`-medium`、`-high` 后缀的运行时模型归并到同一公开模型 ID 下,并映射为 `generationConfig.thinkingConfig`;不支持的档位直接报错,不暗中降级 |
47
+ | 离线缓存 | 按账号将最近一次成功的模型目录保存在 Pi 的 `models-store.json` 中(刷新周期 4 小时);支持离线启动恢复,刷新失败时保留已有目录 |
48
+ | 账号隔离 | 目录缓存绑定当前登录账号(`email` 或派生的 `ownershipId`)、`projectId` 与官方接口地址;切换账号时自动隐藏旧账号目录 |
49
+ | 用量与成本 | 从流式元数据记录输入、缓存命中、输出与思考 token 用量;成本字段固定记为 `0`(表示订阅未按 token 定价,而非免费 API 额度) |
49
50
 
50
- ## 请求主流程
51
+ ### 配套扩展
51
52
 
52
- 1. **认证**:Pi 原生 OAuth 注册,Google 手动粘贴完整回调 URL 登录,校验 PKCE/state,支持 token 刷新。凭据保存和刷新锁由 Pi 管理。保留现有 OAuth 的 `access`、`refresh`、`expires`、`projectId` 及可选 `email`。
53
- 2. **目录**:初始化 consumer 账号并拉取精选聊天目录。公开模型 id 与 runtime id、明确发现的思考档位分离。Pi 标准模型存储保留账号隔离的最后成功快照,支持离线启动;刷新失败不清空快照。
54
- 3. **转换**:复用 Pi 公开 Google 转换器和标准 transcript。仅在序列化系统提示词时精确替换 Pi 已知开头两句,保留规则、工具、用户文本和会话记录。
55
- 4. **流式处理**:只向批准的官方 HTTPS origin 发送一次 HTTP 请求,增量解析 SSE,保留 Pi 观测钩子、签名、工具调用与用量,支持取消、独立的响应头/流空闲超时及唯一终止事件。不内置重试,不暗中切换 endpoint 或模型。
53
+ - **[`@specode/pi-subscription-usage`](https://github.com/specode/pi-subscription-usage)**:在底部状态栏和 `/usage` 中显示 Antigravity 订阅额度池(Gemini、Claude 与 GPT、Gemini 4 Argon)。本扩展保持相同的 `antigravity` provider ID、官方 Google 接口地址与 `{ token, projectId, email }` 凭据桥接格式,无需额外配置即可配合使用。
54
+ - **[`@specode/pi-last-model-effort`](https://github.com/specode/pi-last-model-effort)**:记住每个 Antigravity 模型上次选择的推理档位,并在切换模型或新开 TUI 会话时自动恢复。
56
55
 
57
- 默认 endpoint 为交接中 consumer 账号实际使用的 `https://daily-cloudcode-pa.googleapis.com`,不宣称适用于所有企业账号。CLI 身份固定为 **1.5.9/构建 1021519450**,平台与架构取当前主机实际值。
56
+ ## 协议适配与边界
58
57
 
59
- ## 功能边界
58
+ - **对齐官方 CLI HTTP 协议**:面向个人订阅(默认连接 `https://daily-cloudcode-pa.googleapis.com`;账号需具备已初始化的 Cloud Code Assist 项目,不包含企业版路由或自动 GCP 项目开通)。聊天请求发往 `/v1internal:streamGenerateContent?alt=sse`,携带 `antigravity/cli/1.5.9`(构建号 `1021519450`)客户端标识、当前主机系统平台与架构、`agent` 外层请求结构、会话轨迹标签以及空种子 64 位 FNV-1a `sessionId`。
59
+ - **复用公开转换器与工具闭环**:通过宿主公开的包导出加载 `@earendil-works/pi-ai` 的 Google 消息与工具转换器,完整保留 `thoughtSignature`(含空文本块上的签名)、多工具调用 ID 配对及已验证的 `gemini-3.8-flash-low` 函数结果角色调整;仅在 JSON Schema 关键字位置将类型转为大写,不改动工具属性名或字面值,遇到不支持的 `$ref` / `$dynamicRef` 引用或 `strict: "require"` 约束时直接报错,不静默丢弃工具。
60
+ - **精确改写默认系统提示词开头**:仅在序列化发往后端的请求时,将 Pi 默认的两句固定开头替换为 `You are a coding agent helping the user with their project.`,避免后端对该固定开头返回 `429 RESOURCE_EXHAUSTED`;不修改用户消息、会话文件、项目规则或自定义提示词。
61
+ - **聚焦核心范围**:仅提供文本对话、编码与工具调用能力;不包含图片生成/编辑、网页搜索/抓取工具、多账号轮换、超额 credits、后台预热或内置额度 UI。
62
+ - **单次受控传输**:每轮请求仅发起一次 HTTP 调用,分别设置响应头超时(30 秒)与流空闲超时(120 秒);不内置隐式重试,不在失败或部分输出后暗中切换模型、降级推理档位或切换接口地址。
60
63
 
61
- - 仅维护聊天/编码 provider。Antigravity 生图/编辑计划已取消,不再列为待开发或待测试任务。
62
- - 仅公开精选聊天模型和明确支持的思考变体。不猜测未知思考协议;不支持的显式档位直接报错。
63
- - 尚无订阅按 token 计费依据。Pi 成本元数据中的零表示**未定价**,不表示免费,也不是账单估算。请求 token 用量与订阅额度分开。
64
- - 保留工具 schema 属性名和字面值。不支持的引用或必须严格采样的契约明确失败,不静默删除工具。
65
- - 请求 UUID 与 transcript 计数属于本 provider 的本地记账,不声称还原了 CLI 所有重试/恢复规则。Pi session id 让普通恢复保持标识稳定、fork 相互隔离;无 session id 的直接调用按临时会话处理。
66
- - 不实现账号切换、超额 credits、后台预热、额度 UI、搜索/网页工具、CLI 登录存储迁移或 Pi 私有运行时依赖。
64
+ ## 隐私与安全
67
65
 
68
- ## 独立插件协作
66
+ - 仅通过 Pi 原生的 OAuth 存储(`auth.json`)管理凭据,不读取、复制或迁移官方 CLI 登录文件或系统钥匙串。
67
+ - 请求仅发往白名单内的官方 HTTPS 域名(默认为 `https://daily-cloudcode-pa.googleapis.com`,以及 `https://cloudcode-pa.googleapis.com` 和 `https://daily-cloudcode-pa.sandbox.googleapis.com`),并禁用重定向(`redirect: "error"`)。
68
+ - OAuth 登录接受官方页面复制的授权码,并携带本次登录独立生成的 S256 PKCE verifier 交换令牌。复制的授权码不含 `state`,此路径依靠令牌端点的 PKCE 校验。仍兼容完整的 `https://antigravity.google/oauth-callback` URL,严格校验 origin/path 并进行常量时间的 `state` 校验;无效 URL 不会退回裸授权码解析。
69
+ - OAuth 与流式传输报错仅保留白名单错误码或受限诊断元数据,绝不在异常消息、日志或模型目录缓存中写入访问令牌或刷新令牌。
69
70
 
70
- - **[pi-subscription-usage](https://github.com/specode/pi-subscription-usage)**:负责额度查询和展示。JSON 凭据桥接保留 `token`、`projectId` 及可选 `email`。无 email 的账号额外携带不透明的 `ownershipId`(由 Pi 以 `antigravityOwnershipId` 保存),避免 token 轮换破坏目录归属;既有消费者可忽略此字段。本次开发不修改该插件。
71
- - **[pi-subscription-image](https://github.com/specode/pi-subscription-image)**:已有 Codex/Grok 功能保持不变,原计划的 Antigravity 后端已取消;本 provider 不注册生图 API 或工具。早期生图证据仅作为历史调研记录保留。
71
+ ## 本地开发
72
72
 
73
- ## 证据与待完成验收
73
+ ```bash
74
+ # 安装依赖
75
+ npm ci
74
76
 
75
- 此前 **2026-11-15** 协议调研共执行 17 次生成请求,覆盖原生 read 工具可行性、严格的提示词开头对照及一次参考图请求。[证据清单](./test/fixtures/agy-cli-1.5.9/manifest.json) 区分官方抓包和直接探测。脱敏记录仅用于结构取证,不能重放。
77
+ # 运行类型检查、离线测试、隔离打包加载测试和打包文件检查
78
+ npm run check
76
79
 
77
- 当前离线测试覆盖 Pi 原生模型/认证集成,使用 mock Gemini/Claude 响应执行真实的本地 Pi read 工具,并覆盖异常流、取消、超时、签名、并发和缓存隔离。OAuth 应用参数已对照固定版本官方 CLI 归档静态核实;**这不等于真实浏览器登录已验收**。
80
+ # 仅运行离线单元与集成测试
81
+ npm test
78
82
 
79
- 本机部署另外验证了真实账号初始化和目录发现,6 个公开模型的持久化及已安装 CLI 的离线恢复。既有凭据保持不变。
83
+ # 在禁网的隔离 Pi 环境中验证已发布的 npm 包
84
+ npm run smoke -- @specode/pi-antigravity@0.1.2
85
+ ```
80
86
 
81
- 另经独立授权执行 6 次 Argon High 对照,定位到本仓库自身注入的项目规则:真正的开头虽已适配,文档却又逐字引用了原始 Pi 开头。严格保持请求其他字段相同,仅把该文档段落改为源码引用,结果从 429 变为 200。已移除 `AGENTS.md` 的这一逐字示例并增加回归测试,不改写任意用户规则或历史。测试限制了输出且未执行工具,不能视为完整工具闭环验收。受影响会话应先重载 Pi 的项目规则再重试。
87
+ 当前验证覆盖严格类型检查、离线单元与集成测试(含基于 mock Gemini/Claude 流的本地 Pi `read` 工具闭环)、官方 agy CLI `1.5.9` 参数静态核对,以及基于 Pi `1.5.2` 的个人订阅账号实测;首次浏览器 `/login` 及跨账号/全模型组合尚未逐一单独验收。详细架构说明与验收边界见 [AGENTS.md](./AGENTS.md),脱敏协议研究样本见 [`test/fixtures/agy-cli-1.5.9/manifest.json`](./test/fixtures/agy-cli-1.5.9/manifest.json)。
82
88
 
83
- 用户随后反馈修复后实际测试正常,记为本机部署及该修复的用户验收;不据此宣称所有模型、工具续轮、登录、恢复或取消流程均已验证。
89
+ ## 许可证
84
90
 
85
- 仍未单独验收的范围:经授权的 `/login`、真实完整工具闭环与会话 UI,以及更广泛的账号/模型覆盖。Antigravity 生图不在范围内,不是未完成待办。精确契约和限制统一见唯一开发/交接入口 [AGENTS.md](./AGENTS.md)。
91
+ [MIT](./LICENSE)。协议参考与第三方声明见 [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specode/pi-antigravity",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Independent Antigravity subscription chat provider for Pi, aligned with the official agy CLI HTTP protocol.",
6
6
  "license": "MIT",
package/src/auth.ts CHANGED
@@ -127,8 +127,10 @@ export async function initializeAccount(token: string, options: InitializeAccoun
127
127
  * 0x102b7a400. Google Endpoint has AuthStyleInParams (1). It calls standard
128
128
  * oauth2 Config.Exchange and tokenRefresher.Token. These are public client
129
129
  * application parameters, not user credentials. Static verification is not
130
- * a live /login acceptance test. We require full URLs/state rather than the
131
- * CLI's more permissive bare-code input; no localhost/ACP flow is implemented.
130
+ * a live /login acceptance test. The official callback page copies a bare code
131
+ * and removes query parameters from the address bar. Manual codes rely on the
132
+ * token endpoint's S256 PKCE binding; full URLs additionally validate state.
133
+ * No localhost/ACP flow is implemented.
132
134
  */
133
135
  export const OAUTH_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
134
136
  const OAUTH_CLIENT_SECRET = "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";
@@ -180,6 +182,22 @@ export function parseOAuthCallback(value: string, expectedState: string): string
180
182
  return code[0];
181
183
  }
182
184
 
185
+ /**
186
+ * The official page's Copy to Clipboard button returns a Google code, not a URL
187
+ * or state. Accept its current 4/... format verbatim; never fabricate state or
188
+ * retry a rejected URL as a code. The login must exchange it with its own PKCE
189
+ * verifier. URL input retains the stricter origin/path/code/state checks above.
190
+ */
191
+ export function parseOAuthInput(value: string, expectedState: string): string {
192
+ const input = value.trim();
193
+ if (wireString(input) && /^4\/[A-Za-z0-9_-]+$/.test(input)) return input;
194
+ let url: URL;
195
+ try { url = new URL(input); } catch {
196
+ throw new Error("Paste the authorization code from the Antigravity page's Copy to Clipboard button, or the complete callback URL.");
197
+ }
198
+ return parseOAuthCallback(url.href, expectedState);
199
+ }
200
+
183
201
  async function oauthJson(url: string, init: RequestInit, signal: AbortSignal, options: AuthOptions): Promise<Record<string, unknown>> {
184
202
  signal.throwIfAborted();
185
203
  const bounded = AbortSignal.any([signal, AbortSignal.timeout(options.requestTimeoutMs ?? 30_000)]);
@@ -240,10 +258,10 @@ export async function loginAntigravity(interaction: ProviderAuthInteraction, opt
240
258
  response_type: "code", scope: OAUTH_SCOPES.join(" "), code_challenge: challenge, code_challenge_method: "S256",
241
259
  state, access_type: "offline", prompt: "consent" });
242
260
  interaction.notify({ type: "auth_url", url: `${OAUTH_AUTH_URL}?${params}`,
243
- instructions: "Sign in, then paste the complete final Antigravity callback URL (including code and state)." });
244
- const pasted = await waitForPrompt(interaction.prompt({ type: "manual_code", message: "Paste the complete Antigravity callback URL:", signal }), signal);
261
+ instructions: "Sign in, click Copy to Clipboard on the Antigravity page, then paste the authorization code here. Use the code from this login attempt, not the address bar URL." });
262
+ const pasted = await waitForPrompt(interaction.prompt({ type: "manual_code", message: "Paste the Antigravity authorization code (or complete callback URL):", signal }), signal);
245
263
  signal.throwIfAborted();
246
- const code = parseOAuthCallback(pasted, state);
264
+ const code = parseOAuthInput(pasted, state);
247
265
  const now = (options.now ?? Date.now)();
248
266
  const data = await exchangeToken({ grant_type: "authorization_code", code, code_verifier: verifier, redirect_uri: OAUTH_REDIRECT_URI }, signal, options);
249
267
  const fields = tokenFields(data, now);
@@ -0,0 +1,28 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { findPackageJSON } from "node:module";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ import { getPackageDir } from "@earendil-works/pi-coding-agent";
6
+ import type * as GoogleShared from "@earendil-works/pi-ai/api/google-shared";
7
+
8
+ /**
9
+ * Pi 1.5.2 aliases the pi-ai root, but not its api/* subpaths, for extensions.
10
+ * Resolve the host's public export using public Node/Pi APIs instead of relying
11
+ * on this extension's devDependencies or bundling a second Pi implementation.
12
+ */
13
+ const anchor = join(getPackageDir(), "package.json");
14
+ const manifestPath = findPackageJSON("@earendil-works/pi-ai", pathToFileURL(anchor));
15
+ if (!manifestPath) throw new Error("The Pi host does not provide @earendil-works/pi-ai.");
16
+ const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as {
17
+ exports?: Record<string, string | { import?: string }>;
18
+ };
19
+ const entry = manifest.exports?.["./api/google-shared"] ?? manifest.exports?.["./api/*"];
20
+ const target = typeof entry === "string" ? entry : entry?.import;
21
+ if (typeof target !== "string" || !target.startsWith("./")) {
22
+ throw new Error("The Pi host does not expose the public Google converter API.");
23
+ }
24
+ const googleShared = await import(
25
+ pathToFileURL(resolve(dirname(manifestPath), target.replace("*", "google-shared"))).href
26
+ ) as typeof GoogleShared;
27
+
28
+ export const { convertMessages, convertTools, mapToolChoice } = googleShared;
package/src/request.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import type { Api, Model, SimpleStreamOptions, TranscriptContext } from "@earendil-works/pi-ai";
3
- import { convertMessages, convertTools, mapToolChoice } from "@earendil-works/pi-ai/api/google-shared";
4
- import { getCurrentSystemPrompt, getCurrentTools } from "@earendil-works/pi-ai/utils/transcript";
3
+ import { getCurrentSystemPrompt, getCurrentTools } from "@earendil-works/pi-ai";
4
+ import { convertMessages, convertTools, mapToolChoice } from "./google-shared.ts";
5
5
 
6
6
  const PI_OPENING = "You are a coding assistant running inside pi, a coding agent harness. Help users inspect files, run commands, and modify code.";
7
7
  const CLI_OPENING = "You are a coding agent helping the user with their project.";
package/src/stream.ts CHANGED
@@ -2,8 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import type {
3
3
  Api, AssistantMessage, AssistantMessageEventStream, JsonObject, Model, SimpleStreamOptions, TranscriptContext,
4
4
  } from "@earendil-works/pi-ai";
5
- import { calculateCost } from "@earendil-works/pi-ai/models";
6
- import { createAssistantMessageEventStream } from "@earendil-works/pi-ai/utils/event-stream";
5
+ import { calculateCost, createAssistantMessageEventStream } from "@earendil-works/pi-ai";
7
6
  import { CLIENT_USER_AGENT, DEFAULT_BASE_URL, assertOfficialBaseUrl } from "./client.ts";
8
7
 
9
8
  export interface AntigravityStreamOptions extends SimpleStreamOptions {