@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 +50 -44
- package/README.zh-CN.md +50 -44
- package/package.json +1 -1
- package/src/auth.ts +23 -5
- package/src/google-shared.ts +28 -0
- package/src/request.ts +2 -2
- package/src/stream.ts +1 -2
package/README.md
CHANGED
|
@@ -2,84 +2,90 @@
|
|
|
2
2
|
|
|
3
3
|
[English](./README.md) | [简体中文](./README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
|
11
|
+
Requires Node.js >= 22.19.0 and Pi >= 1.5.2.
|
|
12
12
|
|
|
13
13
|
From npm:
|
|
14
14
|
|
|
15
|
-
```
|
|
15
|
+
```bash
|
|
16
16
|
pi install npm:@specode/pi-antigravity
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
From GitHub:
|
|
20
20
|
|
|
21
|
-
```
|
|
21
|
+
```bash
|
|
22
22
|
pi install git:github.com/specode/pi-antigravity
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
From a local directory for development:
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
For local development:
|
|
30
|
-
|
|
31
|
-
```sh
|
|
27
|
+
```bash
|
|
32
28
|
pi install /path/to/pi-antigravity
|
|
33
29
|
```
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
Run `/reload` or restart Pi after installing.
|
|
36
32
|
|
|
37
|
-
|
|
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
|
-
|
|
35
|
+
## Usage
|
|
43
36
|
|
|
44
|
-
|
|
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
|
-
|
|
41
|
+
### Model Discovery and Routing
|
|
47
42
|
|
|
48
|
-
|
|
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
|
-
|
|
51
|
+
### Companion Extensions
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
56
|
+
## Protocol and Boundaries
|
|
58
57
|
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
+
```bash
|
|
74
|
+
# Install dependencies
|
|
75
|
+
npm ci
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
# Run typecheck, offline tests, isolated packed-extension smoke test, and package check
|
|
78
|
+
npm run check
|
|
76
79
|
|
|
77
|
-
|
|
80
|
+
# Run offline unit and integration tests only
|
|
81
|
+
npm test
|
|
78
82
|
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
+
## License
|
|
84
90
|
|
|
85
|
-
|
|
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
|
-
|
|
5
|
+
一个 [Pi](https://github.com/earendil-works/pi-mono) 扩展:对齐官方 agy CLI 的 HTTP 协议,在 Pi 中注册 Antigravity(`antigravity`)订阅聊天提供商。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
直接使用 Antigravity 订阅连接 Google Cloud Code 接口,复用 Pi 原生的 OAuth 凭据存储、模型目录、工具执行与会话管理,无需运行官方 CLI 二进制、ACP 桥接进程或另一套 Agent 循环。
|
|
8
8
|
|
|
9
9
|
## 安装
|
|
10
10
|
|
|
11
|
-
需要 Node.js >= 22.19.0
|
|
11
|
+
需要 Node.js >= 22.19.0,以及 Pi >= 1.5.2。
|
|
12
12
|
|
|
13
13
|
从 npm 安装:
|
|
14
14
|
|
|
15
|
-
```
|
|
15
|
+
```bash
|
|
16
16
|
pi install npm:@specode/pi-antigravity
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
从 GitHub 安装:
|
|
20
20
|
|
|
21
|
-
```
|
|
21
|
+
```bash
|
|
22
22
|
pi install git:github.com/specode/pi-antigravity
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
从本地目录安装(开发调试):
|
|
26
26
|
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
+
## 使用说明
|
|
43
36
|
|
|
44
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
+
# 运行类型检查、离线测试、隔离打包加载测试和打包文件检查
|
|
78
|
+
npm run check
|
|
76
79
|
|
|
77
|
-
|
|
80
|
+
# 仅运行离线单元与集成测试
|
|
81
|
+
npm test
|
|
78
82
|
|
|
79
|
-
|
|
83
|
+
# 在禁网的隔离 Pi 环境中验证已发布的 npm 包
|
|
84
|
+
npm run smoke -- @specode/pi-antigravity@0.1.2
|
|
85
|
+
```
|
|
80
86
|
|
|
81
|
-
|
|
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
|
-
|
|
91
|
+
[MIT](./LICENSE)。协议参考与第三方声明见 [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md)。
|
package/package.json
CHANGED
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.
|
|
131
|
-
*
|
|
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
|
|
244
|
-
const pasted = await waitForPrompt(interaction.prompt({ type: "manual_code", message: "Paste the complete
|
|
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 =
|
|
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 {
|
|
4
|
-
import {
|
|
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
|
|
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 {
|