billion-context 0.1.117 → 0.1.118-pr.706.765
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 +78 -6
- package/README.zh-CN.md +62 -2
- package/dist/agent/dsh-acp.js.map +1 -1
- package/dist/agent/omp.js +6 -4
- package/dist/agent/omp.js.map +1 -1
- package/dist/agent/opencode-native.js +46033 -0
- package/dist/agent/opencode-native.js.map +1 -0
- package/dist/agent/opencode.js +588 -247
- package/dist/agent/opencode.js.map +1 -1
- package/dist/agent/pi-native.js +44712 -0
- package/dist/agent/pi-native.js.map +1 -0
- package/dist/agent/pi.js +6 -4
- package/dist/agent/pi.js.map +1 -1
- package/dist/index.js +10478 -8301
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +5 -3
- package/dist/mcp.js.map +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -150,11 +150,20 @@ Pick by your client:
|
|
|
150
150
|
| Client | Use |
|
|
151
151
|
|---|---|
|
|
152
152
|
| **pi** | [`billion-context-pi`](https://github.com/ranxianglei/billion-context-pi) (in-process extension) |
|
|
153
|
-
| **opencode 1.x** | [`opencode-acp`](https://github.com/ranxianglei/opencode-acp)
|
|
154
|
-
| **opencode 2.0+** | `bili opencode` (built-in V2 plugin — native tools, no separate package) |
|
|
153
|
+
| **opencode 1.x** | `bili plugin install opencode` (self-spawning native plugin — pre-migration [`opencode-acp`](https://github.com/ranxianglei/opencode-acp) sessions keep working, see "OpenCode 1.x") or `bili opencode` (launcher) or standalone `opencode-acp` (in-process extension) |
|
|
154
|
+
| **opencode 2.0+** | `bili opencode` (built-in V2 plugin — native tools, no separate package) or `bili plugin install opencode` (self-spawning native plugin, no launcher) |
|
|
155
155
|
| **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili omp` (built-in plugin) |
|
|
156
156
|
| **everything else** (no context hook) | [`billion-context`](https://github.com/ranxianglei/billion-context) — `bili <client>` (launcher, preferred) or `/bili/` prefix |
|
|
157
157
|
|
|
158
|
+
**Native mode vs standalone extensions.** The host-native plugins (`bili plugin install pi` / `opencode` — they spawn the proxy inside the host process) and the standalone in-process extensions (`billion-context-pi`, `opencode-acp`) are **mutually exclusive**: both active means double compression. The installer makes the switch: `bili plugin install pi` replaces the legacy `npm:billion-context-pi` entry, and `bili plugin install opencode` strips legacy `opencode-acp` entries from the global opencode.json — bare name, `npm:` alias, versioned (`opencode-acp@stable`), or path form, array or object shape; the original config is snapshotted to `opencode.json.bili-bak`. A **project-local** install (`opencode plugin opencode-acp` writes `<project>/.opencode/opencode.json`, not the global config) is not touched — remove it by hand; the installer note reminds you. As a runtime safety net for manual installs, the native entries set `BILLION_CONTEXT_NATIVE=<host>` synchronously at load so a standalone extension can stand down at action time — its own load-time `BILLION_CONTEXT_PROXY` check cannot see a proxy that native mode spawns asynchronously, and its `/bili/` baseUrl check never sees the fetch-layer rewrite.
|
|
159
|
+
|
|
160
|
+
**Legacy sessions from opencode-acp (v1 lane routing).** On OpenCode 1.x, `bili plugin install opencode` keeps pre-migration sessions WORKING: the native entry absorbs the installed `opencode-acp` package (imported directly from `node_modules` — `.opencode/node_modules`, project `node_modules`, global npm root, or opencode's config-scope modules, first hit wins) and routes per session. A session is legacy iff opencode-acp's persisted state file exists (`<XDG_DATA_HOME>/opencode/storage/plugin/acp/<sessionID>.json`):
|
|
161
|
+
|
|
162
|
+
- **Legacy session** — compression runs through the absorbed opencode-acp (its own `<dcp-message-id>` refs and block store keep working: `compress` / `decompress` / `search_context` / `acp_status` / `acp_context_recap` all execute in it). Its model requests carry `x-bili-plugin-bypass: 1`, and the proxy forwards them VERBATIM — no wire injection, no nudge, no session binding.
|
|
163
|
+
- **New session** — bili owns it: tool calls forward to the proxy's plugin endpoints (plugin mode). The tool slots the model sees carry the DCP schemas (one def per name process-wide on v1), but the executor routes by session lane, so a new session's `compress` reaches the proxy while a legacy session's reaches opencode-acp. `acp_context_recap` has no proxy counterpart — on new sessions the proxy answers it with its unknown-tool message.
|
|
164
|
+
|
|
165
|
+
`/acp` and `/dcp` route the same way. Adoption of new sessions into opencode-acp's registry is prevented by gating its transforms (system / messages / text.complete) on the legacy predicate. Degradation: when the opencode-acp package is absent or fails to import, bili runs alone and legacy sessions behave as read-only archives (old `<acp>` tags render, `decompress` returns `[Block … not found]`, new refs restart from m00001).
|
|
166
|
+
|
|
158
167
|
## Install
|
|
159
168
|
|
|
160
169
|
```bash
|
|
@@ -203,7 +212,7 @@ bili pi # launch pi through the proxy — file-fre
|
|
|
203
212
|
bili codex # launch codex through the proxy
|
|
204
213
|
bili claude # launch claude through the proxy
|
|
205
214
|
bili omp # pi-style, file-free (#535): env + extension registerProvider + compaction cancel, real ~/.omp untouched
|
|
206
|
-
bili opencode # MITM for HTTPS + temp opencode.json (/bili/ for HTTP) + thin /acp plugin; OpenCode 2.0+: built-in V2 plugin with native bili tools, native compaction auto-disabled. Reads the user's opencode.jsonc / opencode.json / config.json (JSONC comments accepted, merged the same way opencode itself merges them); relative local plugin specs (`./x`, `../x`) are re-anchored to absolute paths in the clone — opencode resolves them against the declaring config file's dir (#826)
|
|
215
|
+
bili opencode # MITM for HTTPS + temp opencode.json (/bili/ for HTTP) + thin /acp plugin; OpenCode 1.x: existing opencode-acp sessions keep working (entries stripped from the clone, package imported as a library, #920); OpenCode 2.0+: built-in V2 plugin with native bili tools, native compaction auto-disabled. Reads the user's opencode.jsonc / opencode.json / config.json (JSONC comments accepted, merged the same way opencode itself merges them); relative local plugin specs (`./x`, `../x`) are re-anchored to absolute paths in the clone — opencode resolves them against the declaring config file's dir (#826)
|
|
207
216
|
bili hermes # file-free (#535): hermes proxy env (HTTPS_PROXY + HERMES_CA_BUNDLE) — https via CONNECT MITM, http via absolute-form forward proxy; real ~/.hermes untouched
|
|
208
217
|
bili dsh # deepseek-harness: non-loopback upstreams ride proxy envs (https MITM, http absolute-form), loopback keeps the overlay DSH_HOME (~/.dsh-bili) rewrite (#535), built-in deepseek route via DEEPSEEK_BASE_URL, native /acp command injected via --patch
|
|
209
218
|
bili codebuddy # Tencent CodeBuddy Code CLI: CODEBUDDY_BASE_URL /bili/ rewrite (OpenAI chat completions wire), budget aligned via CODEBUDDY_AUTO_COMPACT_WINDOW; real ~/.codebuddy untouched
|
|
@@ -239,6 +248,29 @@ automatically.
|
|
|
239
248
|
For per-client configuration examples (OpenCode, Codex, Pi, login-client
|
|
240
249
|
MITM, …) see the web UI guide at [http://localhost:8787](http://localhost:8787).
|
|
241
250
|
|
|
251
|
+
### OpenCode 1.x
|
|
252
|
+
|
|
253
|
+
On a 1.x host, `bili opencode` runs **new sessions through the bili proxy** and
|
|
254
|
+
keeps **existing `opencode-acp` ("legacy") sessions working with their own
|
|
255
|
+
machinery** (#920). The launcher strips the `opencode-acp` entry from its temp
|
|
256
|
+
config clone (the host never loads it armed), and the thin bili plugin imports
|
|
257
|
+
the installed package as a library instead:
|
|
258
|
+
|
|
259
|
+
- every acp hook is gated on "an acp store file exists for this session"
|
|
260
|
+
(`~/.local/share/opencode/storage/plugin/acp/<sessionID>.json`, or the dir
|
|
261
|
+
from `storagePath` in `acp.jsonc`) — legacy sessions keep their DCP compress /
|
|
262
|
+
decompress / search_context / acp_status tools and the `/dcp` command; new
|
|
263
|
+
sessions are never adopted and run plain proxy mode.
|
|
264
|
+
- legacy LLM requests are stamped `x-bili-plugin-bypass: 1`, which the proxy
|
|
265
|
+
honors as raw passthrough (no injection, no compression, no session state).
|
|
266
|
+
- in proxy mode the proxy owns the compression tool names: same-named client
|
|
267
|
+
tools in the body are dropped before injection, so upstream sees one
|
|
268
|
+
definition per name.
|
|
269
|
+
|
|
270
|
+
Graceful degradation: if the package can't be found/imported or isn't v1, the
|
|
271
|
+
plugin behaves exactly as before this change — legacy sessions degrade the way
|
|
272
|
+
they did when `opencode-acp` self-disabled on `/bili/` baseURLs.
|
|
273
|
+
|
|
242
274
|
### OpenCode 2.0
|
|
243
275
|
|
|
244
276
|
OpenCode 2.0 ships a new plugin API (`@opencode/plugin`); the standalone
|
|
@@ -263,9 +295,37 @@ contract:
|
|
|
263
295
|
never fires, the plugin stays inert and the session transparently runs in
|
|
264
296
|
plain proxy mode (wire-level tool injection) instead of breaking — observed
|
|
265
297
|
on two adjacent `dev` builds (2026-09-13 / 2026-09-14) whose API surfaces
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
298
|
+
differ from each other (#754 review probes); conversely verified end-to-end
|
|
299
|
+
on `@opencode/cli` 2.0.3 (native `acp_status` executed through the plugin
|
|
300
|
+
endpoint, zero wire-level injection).
|
|
301
|
+
- **Native (no launcher):** with the package installed from npm, run
|
|
302
|
+
`bili plugin install opencode` — it writes a self-spawning plugin into your
|
|
303
|
+
real opencode config (`<configDir>/plugins/billion-context/index.js` →
|
|
304
|
+
`dist/agent/opencode-native.js`) and sets `compaction.auto: false`, after
|
|
305
|
+
which plain `opencode` works as-is. At load the plugin bootstraps its own
|
|
306
|
+
proxy (attaches to a healthy instance instead of doubling; parent-pid
|
|
307
|
+
watchdog kills it when opencode exits), routes model-API traffic to
|
|
308
|
+
`<proxy>/bili/<upstream-url>`, and exposes the same native bili tools as
|
|
309
|
+
launcher mode — no fixed port, no env var, no launcher.
|
|
310
|
+
Opt-out: `BILI_NATIVE_OPENCODE=0`. If no proxy can be made healthy, requests
|
|
311
|
+
go direct (uncompressed) with a one-time warning and recover automatically.
|
|
312
|
+
Under a `bili opencode` launch this entry is skipped entirely (the launcher
|
|
313
|
+
owns the proxy).
|
|
314
|
+
|
|
315
|
+
The same wrapper serves **OpenCode 1.x** through the V1 `.server()` hooks
|
|
316
|
+
(verified on 1.14.46 and 1.18.31): the `config` hook mutates the shared
|
|
317
|
+
config object in-process to rewrite every provider `options.baseURL` to
|
|
318
|
+
`<proxy>/bili/…` and sets `compaction.auto: false`; `chat.headers` stamps
|
|
319
|
+
the plugin headers per request; `tool` registers the bili tools with real
|
|
320
|
+
zod shapes (zod is a runtime dependency — when it cannot be resolved the
|
|
321
|
+
plugin degrades to plain proxy mode: rewrite only, wire-injected tools);
|
|
322
|
+
the `/acp` command renders the same status panel. Providers **without**
|
|
323
|
+
an explicit `baseURL` (SDK defaults, e.g. bare `@ai-sdk/openai` →
|
|
324
|
+
api.openai.com) are caught by a global `fetch` patch (the pi-native
|
|
325
|
+
mechanism) that reroutes model-API calls to the proxy — verified end-to-end
|
|
326
|
+
on 1.14.46 and 1.18.31 (log: `v1: fetch patch installed`), including the
|
|
327
|
+
OpenAI Responses endpoint. The patch is idempotent and passes
|
|
328
|
+
`/bili/`-wrapped URLs through untouched.
|
|
269
329
|
- **Pure proxy:** point the provider baseURL at the proxy like any other
|
|
270
330
|
client:
|
|
271
331
|
|
|
@@ -320,6 +380,18 @@ Then send one message from your client and watch the log
|
|
|
320
380
|
should see a `processTurn` line per request, and once the conversation grows,
|
|
321
381
|
`[acp-usage] round N input=X cached=Y (cache hit Z%)` + a `compress` event.
|
|
322
382
|
|
|
383
|
+
### Client uses `http.proxy` (CONNECT) but nothing compresses
|
|
384
|
+
|
|
385
|
+
Some clients (VS Code-based IDEs: CodeBuddy, Cursor, Windsurf, …) only offer an HTTP **proxy** setting (`http.proxy`, `codingcopilot.httpProxyURL`, …) — no model base-URL to rewrite. Such clients send `CONNECT <model-host>:443` through the proxy instead of plain `/bili/…` requests. That path is only decrypted when the model host is on bili's **MITM whitelist**; otherwise bili blind-tunnels the TLS bytes (opaque relay) and can never see — or compress — the model requests (#897).
|
|
386
|
+
|
|
387
|
+
This failure mode is now loud instead of silent:
|
|
388
|
+
|
|
389
|
+
- a one-time `BLIND TUNNEL WARNING` per target host in the log, with the fix steps;
|
|
390
|
+
- `blindTunnels` (count + exact target hosts) in `curl -s http://localhost:8787/__bili/health` and `/__bili/stats` (loopback-only);
|
|
391
|
+
- an `UNDECRYPTED TRAFFIC (instance-level)` section in `acp_status` output while such tunnels exist.
|
|
392
|
+
|
|
393
|
+
To actually compress such a client: add its model domain to `"mitm".domains` in `billion-context.json` (e.g. `"mitm": { "domains": ["copilot.tencent.com"] }`) or via `BILI_MITM_DOMAINS`, restart bili, and make the client trust bili's root CA (`NODE_EXTRA_CA_CERTS=~/.local/share/billion-context/ca/root-ca.pem` for Node-based clients, or the client's own CA-path setting). The `/bili/` prefix trick does not apply here — there is no URL to change. Details: [CONFIGURATION.md → MITM](CONFIGURATION.md#mitm-transparent-proxy-login-clients).
|
|
394
|
+
|
|
323
395
|
## Running the proxy
|
|
324
396
|
|
|
325
397
|
### Flags
|
package/README.zh-CN.md
CHANGED
|
@@ -78,10 +78,21 @@ AI 编程助手的<strong>通用上下文压缩代理</strong>
|
|
|
78
78
|
| 客户端 | 用这个 |
|
|
79
79
|
|---|---|
|
|
80
80
|
| **pi** | [`billion-context-pi`](https://github.com/ranxianglei/billion-context-pi)(进程内扩展) |
|
|
81
|
-
| **opencode** | [`opencode-acp`](https://github.com/ranxianglei/opencode-acp)(进程内扩展) |
|
|
81
|
+
| **opencode 1.x** | [`billion-context`](https://github.com/ranxianglei/billion-context) —— `bili plugin install opencode`(V1 `.server()` 钩子,与 2.x 同一个包装器)或 `bili opencode`(启动器);[`opencode-acp`](https://github.com/ranxianglei/opencode-acp)(进程内扩展)仍可用 |
|
|
82
|
+
| **opencode 2.0+** | `bili opencode`(内置 V2 插件 —— 原生工具,无需另装包)或 `bili plugin install opencode`(自拉起原生插件,免启动器) |
|
|
83
|
+
| **opencode** | [`billion-context`](https://github.com/ranxianglei/billion-context),`bili opencode`(新会话走 bili 代理;现有 [`opencode-acp`](https://github.com/ranxianglei/opencode-acp) 会话继续可用,见下文「OpenCode 1.x」) |
|
|
82
84
|
| **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context),`bili omp`(内置插件) |
|
|
83
85
|
| **其余所有**(没有上下文 hook) | [`billion-context`](https://github.com/ranxianglei/billion-context) —— `bili <client>`(启动器,优先)或 `/bili/` 前缀 |
|
|
84
86
|
|
|
87
|
+
**原生模式 vs 独立扩展。** 宿主原生插件(`bili plugin install pi` / `opencode` —— 代理在宿主进程内拉起)与独立进程内扩展(`billion-context-pi`、`opencode-acp`)**互斥**:两者同时生效意味着双重压缩。安装器负责切换:`bili plugin install pi` 会替换旧的 `npm:billion-context-pi` 条目;`bili plugin install opencode` 会从全局 opencode.json 里剔除旧的 `opencode-acp` 条目 —— 裸名、`npm:` 别名、带版本号(`opencode-acp@stable`)、路径形式都认,数组/对象两种形态都处理;原配置会快照到 `opencode.json.bili-bak`。**项目级**安装(`opencode plugin opencode-acp` 写的是 `<project>/.opencode/opencode.json`,不是全局配置)不会被碰 —— 需手动移除,安装器输出里会提醒。作为手动安装的运行期安全网,原生入口在加载时同步设置 `BILLION_CONTEXT_NATIVE=<host>`,让独立扩展在动作时自动退出 —— 它自己的加载期 `BILLION_CONTEXT_PROXY` 检查看不见原生模式异步拉起的代理,`/bili/` baseURL 检查也看不见 fetch 层改写。
|
|
88
|
+
|
|
89
|
+
**从 opencode-acp 迁移旧会话(v1 泳道路由)。** 在 OpenCode 1.x 上,`bili plugin install opencode` 让迁移前的旧会话继续可用:原生入口会吸收已安装的 `opencode-acp` 包(直接从 `node_modules` 导入 —— `.opencode/node_modules`、项目 `node_modules`、全局 npm root、opencode 配置级 modules,先到先得),并按会话路由。会话属于 legacy ⟺ opencode-acp 的持久化状态文件存在(`<XDG_DATA_HOME>/opencode/storage/plugin/acp/<sessionID>.json`):
|
|
90
|
+
|
|
91
|
+
- **旧会话** —— 压缩由被吸收的 opencode-acp 执行(它自己的 `<dcp-message-id>` 引用号与块存储照常工作:`compress` / `decompress` / `search_context` / `acp_status` / `acp_context_recap` 全部在它里面执行)。其模型请求带 `x-bili-plugin-bypass: 1`,代理原样转发 —— 不注入 wire 工具、不注 nudge、不绑定会话。
|
|
92
|
+
- **新会话** —— bili 接管:工具调用转发到代理的 plugin 端点(plugin 模式)。模型看到的工具槽位带 DCP schema(v1 进程级每个名字仅一份定义),但执行器按会话分泳道:新会话的 `compress` 发到代理,旧会话的发给 opencode-acp。`acp_context_recap` 没有代理端对应 —— 新会话调用会收到代理的 unknown-tool 消息。
|
|
93
|
+
|
|
94
|
+
`/acp` 与 `/dcp` 同样路由。新会话被 opencode-acp 注册表收编的可能被其 transform 门控(system / messages / text.complete 都过 legacy 谓词)阻断。退化路径:opencode-acp 包缺失或导入失败时 bili 单独运行,旧会话退化为只读存档(旧 `<acp>` 标签照常渲染、`decompress` 返回 `[Block … not found]`、新引用号从 m00001 重新开始)。
|
|
95
|
+
|
|
85
96
|
## 安装
|
|
86
97
|
|
|
87
98
|
```bash
|
|
@@ -112,7 +123,7 @@ bili pi # 拉起 pi,走代理 —— file-free(#53
|
|
|
112
123
|
bili codex # 拉起 codex
|
|
113
124
|
bili claude # 拉起 claude
|
|
114
125
|
bili omp # pi 同款,file-free(#535):环境变量 + 扩展 registerProvider + 压缩取消,真实 ~/.omp 不动
|
|
115
|
-
bili opencode # HTTPS 走 MITM + 临时 opencode.json(HTTP 走 /bili/)+ 轻量 /acp
|
|
126
|
+
bili opencode # HTTPS 走 MITM + 临时 opencode.json(HTTP 走 /bili/)+ 轻量 /acp 插件;OpenCode 2.0+:内置 V2 插件带原生 bili 工具,自动关掉原生 auto-compaction;OpenCode 1.x:旧 opencode-acp 会话继续可用(条目从副本中移除、包以库形式导入,#920)。详见下方「OpenCode 专属说明」
|
|
116
127
|
bili hermes # file-free(#535):hermes 代理环境变量(HTTPS_PROXY + HERMES_CA_BUNDLE)—— https 走 CONNECT MITM,http 走绝对形式转发;真实 ~/.hermes 不动
|
|
117
128
|
bili dsh # deepseek-harness:非回环上游走代理 env(https MITM、http absolute-form),回环上游保留 overlay DSH_HOME(~/.dsh-bili)改写(#535),内置 deepseek 路由走 DEEPSEEK_BASE_URL,经 --patch 注入原生 /acp 命令
|
|
118
129
|
bili codebuddy # Tencent CodeBuddy Code CLI:CODEBUDDY_BASE_URL /bili/ 重写(OpenAI chat completions wire),预算对齐走 CODEBUDDY_AUTO_COMPACT_WINDOW;真实 ~/.codebuddy 不动
|
|
@@ -124,6 +135,16 @@ bili pi --mitm-domain api.foo.com # 向 MITM 白名单追加域名
|
|
|
124
135
|
```
|
|
125
136
|
|
|
126
137
|
|
|
138
|
+
### OpenCode 1.x —— 旧 opencode-acp 会话继续可用(#920)
|
|
139
|
+
|
|
140
|
+
在 1.x 主机上,`bili opencode` 会**通过 bili 代理运行新会话**,同时**让现有 `opencode-acp`("legacy")会话继续使用其自身机制正常工作**(#920)。启动器会从临时配置副本中移除 `opencode-acp` 条目(主机永远不会以激活状态加载它),而轻量的 bili 插件则改为以库的形式导入已安装的包:
|
|
141
|
+
|
|
142
|
+
- 每个 ACP 钩子都以"该会话存在 ACP 存储文件"为门控条件(`~/.local/share/opencode/storage/plugin/acp/<sessionID>.json`,或 `acp.jsonc` 中 `storagePath` 指定的目录)——旧版会话保留其 DCP compress / decompress / search_context / acp_status 工具和 `/dcp` 命令;新会话不会被接管,而是运行纯代理模式。
|
|
143
|
+
- 旧版 LLM 请求会被打上 `x-bili-plugin-bypass: 1` 标记,代理将其视为原始透传(无注入、无压缩、无会话状态)。
|
|
144
|
+
- 在代理模式下,压缩工具的名称由代理独占:请求体中同名的客户端工具会在注入前被丢弃,因此上游对每个名称只看到一个定义。
|
|
145
|
+
|
|
146
|
+
优雅降级:如果包无法找到/导入或不是 v1 版本,插件的行为与此更改之前完全一致——旧版会话的降级方式与 `opencode-acp` 在 `/bili/` baseURL 上自我禁用时的表现相同。
|
|
147
|
+
|
|
127
148
|
### 方式 2 —— 改url(`/bili/` 前缀)
|
|
128
149
|
|
|
129
150
|
启动代理:
|
|
@@ -159,6 +180,45 @@ curl -s http://localhost:8787/__bili/stats
|
|
|
159
180
|
同时也打到 stderr)。每个请求应该看到一行 `processTurn`,等对话变长后
|
|
160
181
|
会出现 `[acp-usage] round N input=X cached=Y (cache hit Z%)` + `compress` 事件。
|
|
161
182
|
|
|
183
|
+
### OpenCode 专属说明
|
|
184
|
+
|
|
185
|
+
OpenCode 2.0 换了新插件 API(`@opencode/plugin`);独立扩展 `opencode-acp` 仅支持 V1,在 2.0 下不加载。bili 的两种模式在 2.0 都能用。2.x 插件 API 面在不同 build 间仍在变动(相邻的 npm `dev` 构建 `ctx` 形状不同),下面列的钩子/工具细节是针对具体版本的观察,不是稳定契约:
|
|
186
|
+
|
|
187
|
+
- **启动器:** `bili opencode` 用法不变。在 2.x 宿主上,它把内置 V2 插件(`dist/agent/opencode.js`)以临时包装目录的形式注入临时配置(目录入口 `index.js` 再 re-export 插件文件 —— 2.x 拒绝配置 `plugin` 数组里的裸文件路径;1.x 宿主用裸文件路径)。宿主代次用 `--version` 探测(探测失败默认 1.x 形状)。V2 插件在宿主内原生注册 bili 工具 —— compress / decompress / search_context / acp_status(另加 absorb),JSON-Schema 入参 —— 并在每个 provider 请求上盖章代理头,压缩走插件模式,不做 wire 级工具注入;原生 auto-compaction 自动关闭(`compaction.auto: false`)。所有注册都是防御式的(可选链):任一 2.x build 上接缝缺失或未触发时,插件保持情性,会话透明回退纯代理模式(wire 级注入)而不是报错 —— 在两个相邻 `dev` build(2026-09-13 / 2026-09-14)上观察到过 API 面不同(#754 评审探针);反之在 `@opencode/cli` 2.0.3 上端到端验证过(原生 `acp_status` 经插件端点执行,零 wire 注入)。
|
|
188
|
+
- **原生(免启动器):** npm 装好包后跑 `bili plugin install opencode` —— 它往真实 opencode 配置写一个自拉起插件(`<configDir>/plugins/billion-context/index.js` → `dist/agent/opencode-native.js`)并设 `compaction.auto: false`,之后直接跑 `opencode` 即可。加载时插件自拉起自己的代理(健康的已有实例直接复用不重复起;父进程 pid 看门狗在 opencode 退出时收掉它),把模型流量路由到 `<proxy>/bili/<upstream-url>`,并暴露与启动器模式相同的原生 bili 工具 —— 无固定端口、无环境变量、免启动器。退出:`BILI_NATIVE_OPENCODE=0`。若没有任何代理能拉到健康状态,请求直连(不压缩)并给一次性告警,之后自动恢复。在 `bili opencode` 启动下该入口整体跳过(代理归启动器管)。
|
|
189
|
+
|
|
190
|
+
同一个包装器也服务 **OpenCode 1.x**(V1 `.server()` 钩子;在 1.14.46 和 1.18.31 上验证):`config` 钩子在进程内直接改共享配置对象,把每个 provider 的 `options.baseURL` 改写为 `<proxy>/bili/…` 并设 `compaction.auto: false`;`chat.headers` 每次请求盖章插件头;`tool` 用真实 zod 形状注册 bili 工具(zod 是运行时依赖 —— 解析不到时插件降级纯代理模式:只改写、wire 注入工具);`/acp` 命令渲染同一张状态面板。**没有显式 `baseURL`** 的 provider(SDK 默认值,如裸 `@ai-sdk/openai` → api.openai.com)由全局 `fetch` 补丁(pi-native 同机制)兑住 —— 模型 API 调用重路由到代理(日志:`v1: fetch patch installed`),在 1.14.46 / 1.18.31 上含 OpenAI Responses 端点端到端验证;补丁幂等,`/bili/` 包装过的 URL 原样直通。
|
|
191
|
+
- **纯代理:** 与其它客户端一样,把 provider baseURL 指向代理:
|
|
192
|
+
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"provider": {
|
|
196
|
+
"myprovider": {
|
|
197
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
198
|
+
"options": {
|
|
199
|
+
"baseURL": "http://localhost:8787/bili/http://upstream.example/v1",
|
|
200
|
+
"apiKey": "sk-any"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
注意:2.0 AI-SDK provider 即使本地端点从不校验也要求 `apiKey` 字段 —— 随便填个非空值。
|
|
208
|
+
|
|
209
|
+
注意事项:2.x 系列以 npm 包 `@opencode/cli` 发布。命令支持随 build 而定:某个预发布版只暴露 list/get/update/remove,而 2.0.x 稳定版允许插件经 `ctx.command.transform((editor) => editor.add(...))` 新增命令 —— TUI 里接受斜杠菜单补全(Tab + Enter)即可调用;注意 `opencode run` 模式完全不派发斜杠命令(它们会透传给模型)。内置插件在两种形状上都刻意不注册命令。
|
|
210
|
+
### 客户端用 `http.proxy`(CONNECT)接入但从不压缩
|
|
211
|
+
|
|
212
|
+
部分客户端(VS Code 系 IDE:CodeBuddy、Cursor、Windsurf……)只提供一个 HTTP **代理**设置(`http.proxy`、`codingcopilot.httpProxyURL` 等),没有可改写的模型 base-URL。这类客户端不走普通的 `/bili/…` 请求,而是把 `CONNECT <模型域名>:443` 发给代理。只有当模型域名在 bili 的 **MITM 白名单**里时这条路径才会被解密;否则 bili 只做盲隧道(不透明转发),永远看不到——也就无法压缩——模型请求(#897)。
|
|
213
|
+
|
|
214
|
+
该失效模式现在不再静默:
|
|
215
|
+
|
|
216
|
+
- 日志里对每个目标域名打一次 `BLIND TUNNEL WARNING`,附修复步骤;
|
|
217
|
+
- `curl -s http://localhost:8787/__bili/health` 与 `/__bili/stats` 输出 `blindTunnels`(计数 + 精确目标域名,仅 loopback);
|
|
218
|
+
- 存在此类隧道时,`acp_status` 输出会多一节 `UNDECRYPTED TRAFFIC (instance-level)`。
|
|
219
|
+
|
|
220
|
+
要真正压缩这类客户端:把它的模型域名加进 `billion-context.json` 的 `"mitm".domains`(如 `"mitm": { "domains": ["copilot.tencent.com"] }`)或环境变量 `BILI_MITM_DOMAINS`,重启 bili,并让客户端信任 bili 的根 CA(Node 系客户端用 `NODE_EXTRA_CA_CERTS=~/.local/share/billion-context/ca/root-ca.pem`,有 CA 路径设置的用其设置)。`/bili/` 前缀方案在这里不适用——没有 URL 可改。详见 [CONFIGURATION.zh-CN.md → MITM](CONFIGURATION.zh-CN.md#mitm-透明代理登录客户端)。
|
|
221
|
+
|
|
162
222
|
## 运行代理
|
|
163
223
|
|
|
164
224
|
### 命令行参数
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/shared.ts","../../src/agent/dsh-acp.ts"],"sourcesContent":["// Shared thin-plugin core for agent-side extensions (\"内外呼应\", issue #1).\n// The agent plugin is a PURE PROTOCOL CLIENT: no acp-kernel import, no\n// compression logic. The proxy stays the single compression authority; the\n// plugin only (1) detects the proxy, (2) fetches the tool manifest (single\n// source of truth), (3) forwards tool executes, (4) reads status. Same\n// package as the proxy ⇒ same version ⇒ no kernel-skew bug class.\n\nexport type ManifestTool = {\n name: string;\n description?: string;\n inputSchema: unknown;\n};\n\nconst MANIFEST_TIMEOUT_MS = 5000;\nconst TOOL_TIMEOUT_MS = 60000;\nconst STATUS_TIMEOUT_MS = 5000;\n\n/** Detect the proxy from a provider baseUrl's `/bili/` zero-config prefix.\n * The real prefix embeds the full upstream URL (`/bili/https://…`), so the\n * check requires `bili` as the first path segment followed by an http(s)\n * URL — a plain `/foo/bili/` path segment is NOT a bili proxy.\n * Returns the proxy origin (scheme//host) the request will actually hit. */\nexport function proxyBaseFromUrl(baseUrl: string | undefined): string | undefined {\n if (!baseUrl) return undefined;\n try {\n const url = new URL(baseUrl);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return undefined;\n const segments = url.pathname.split(\"/\").filter((s) => s.length > 0);\n if (segments[0] !== \"bili\") return undefined;\n const rest = url.pathname.slice(url.pathname.indexOf(\"bili\") + \"bili\".length);\n if (!/^\\/https?:\\/\\//.test(rest)) return undefined;\n return `${url.protocol}//${url.host}`;\n } catch {\n return undefined;\n }\n}\n\n/** MITM transparent mode has no `/bili/` prefix; the proxy's launcher exports\n * BILLION_CONTEXT_PROXY. A stale value surfaces as a tool-forward error. */\nexport function proxyBaseFromEnv(): string | undefined {\n const raw = process.env.BILLION_CONTEXT_PROXY?.trim();\n if (!raw) return undefined;\n try {\n const url = new URL(raw);\n return url.protocol === \"http:\" || url.protocol === \"https:\" ? `${url.protocol}//${url.host}` : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function detectProxyBase(baseUrl: string | undefined): string | undefined {\n if (process.env.BILLION_CONTEXT_PLUGIN === \"0\") return undefined;\n return proxyBaseFromUrl(baseUrl) ?? proxyBaseFromEnv();\n}\n\nasync function fetchJson(url: string, init: RequestInit | undefined, timeoutMs: number, externalSignal?: AbortSignal): Promise<{ ok: boolean; status: number; json: unknown }> {\n const ac = new AbortController();\n // An already-aborted external signal never fires its \"abort\" event, so\n // forward the state directly — otherwise only the timeout could stop\n // the request, turning an instant cancel into a timeout wait.\n if (externalSignal?.aborted) ac.abort();\n const timer = setTimeout(() => ac.abort(), timeoutMs);\n const onExternalAbort = () => ac.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n try {\n let res: Response;\n try {\n res = await fetch(url, { ...init, signal: ac.signal });\n } catch (err) {\n if (ac.signal.aborted && !externalSignal?.aborted) throw new Error(`timeout after ${timeoutMs}ms: ${url}`);\n throw err;\n }\n const text = await res.text();\n let json: unknown = undefined;\n try {\n json = JSON.parse(text);\n } catch {\n json = undefined;\n }\n return { ok: res.ok, status: res.status, json };\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n}\n\nexport async function fetchManifest(proxyBase: string, format: \"anthropic\" | \"openai\" = \"anthropic\"): Promise<ManifestTool[]> {\n const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/manifest`, undefined, MANIFEST_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") throw new Error(`manifest fetch failed: ${status}`);\n if (format === \"openai\") {\n // OpenAI function style: {name, description, parameters} (plain JSON Schema).\n const data = json as { tools?: { openai?: { name?: string; description?: string; parameters?: unknown }[] } };\n const tools = (data.tools?.openai ?? []).filter((t): t is { name: string; description?: string; parameters?: unknown } => typeof t.name === \"string\");\n if (tools.length === 0) throw new Error(\"manifest served no openai tools\");\n return tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.parameters ?? { type: \"object\", properties: {} } }));\n }\n const data = json as { tools?: { anthropic?: { name?: string; description?: string; input_schema?: unknown }[] } };\n const tools = (data.tools?.anthropic ?? []).filter((t): t is { name: string; description?: string; input_schema?: unknown } => typeof t.name === \"string\");\n if (tools.length === 0) throw new Error(\"manifest served no anthropic tools\");\n return tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.input_schema ?? { type: \"object\", properties: {} } }));\n}\n\nconst COMPACT_TIMEOUT_MS = 5000;\n\n/** Report a host-native compaction boundary to the proxy archive (#395):\n * hosts that compact natively (opencode) cannot cancel it, so the proxy\n * marks the boundary and archives unreachable blocks. Fire-and-forget at\n * call sites — a missed report degrades to stale blocks, not broken turns. */\nexport async function reportCompactionBoundary(proxyBase: string, conversationId: string): Promise<void> {\n await fetchJson(`${proxyBase}/__bili/plugin/compact`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId }),\n }, COMPACT_TIMEOUT_MS);\n}\n\nexport async function forwardTool(proxyBase: string, conversationId: string, tool: string, args: unknown, signal?: AbortSignal): Promise<string> {\n const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/tool`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId, tool, args: args ?? {} }),\n }, TOOL_TIMEOUT_MS, signal);\n const data = json as { ok?: boolean; result?: string; error?: string } | undefined;\n if (!ok || !data?.ok) {\n throw new Error(`bili proxy tool ${tool} failed (${status}): ${data?.error ?? \"unknown error\"}`);\n }\n return data.result ?? \"\";\n}\n\n/** Soft-fail by design: the status read is best-effort UI data; undefined\n * means \"no data\" whether the proxy is down or the session is unknown. */\nexport async function fetchStatus(proxyBase: string, conversationId: string): Promise<Record<string, unknown> | undefined> {\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/status?conversationId=${encodeURIComponent(conversationId)}`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n return json as Record<string, unknown>;\n}\n\n/** Wire-mode status read (dsh): those clients carry no per-conversation id\n * the proxy could bind, so ask for the most recently active session instead\n * (fallback=latest). Same soft-fail contract as fetchStatus. */\nexport async function fetchStatusLatest(proxyBase: string): Promise<Record<string, unknown> | undefined> {\n // conversationId must be non-empty (server rejects the empty string), but\n // any unknown id is fine: fallback=latest then resolves the most recently\n // active session.\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/status?conversationId=dsh&fallback=latest`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n return json as Record<string, unknown>;\n}\n\n/** Liveness + version probe for status UIs: same loopback origin as the\n * status endpoint, so a 404 status + a live manifest means \"proxy up,\n * conversation not seen yet\" — an armed-but-idle state, not an error. */\nexport async function fetchProxyVersion(proxyBase: string): Promise<string | undefined> {\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/manifest`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n const version = (json as { version?: unknown }).version;\n return typeof version === \"string\" && version.length > 0 ? version : undefined;\n}\n","// Native dsh (deepseek-harness) cordis plugin: registers the `/acp` command.\n// Injected by the `bili dsh` launcher through a `--patch` overlay that\n// inserts this module (as a file:// URL) into the loader entry tree — the\n// same plugin shape as dsh-command-compact. Pure protocol client, same\n// discipline as the other agent plugins: no acp-kernel import, every byte of\n// displayed data comes from the proxy's HTTP endpoints.\n\nimport { proxyBaseFromEnv, fetchProxyVersion, fetchStatusLatest } from \"./shared.js\";\n\nexport const name = \"bili-acp\";\nexport const inject = [\"commands\"];\n\ntype CommandOutcome = { kind: \"success\" | \"error\"; text: string };\n\ntype CommandsService = {\n register: (command: { name: string; description: string; handler: () => Promise<CommandOutcome> }) => unknown;\n};\n\ntype PluginContext = { commands: CommandsService };\n\n/** One `/acp` invocation: latest session panel, else armed-but-idle, else a\n * reachable-proxy failure. dsh conversations carry no client-side id we can\n * bind to, so the read asks for the most recently active session. */\nasync function statusOutcome(): Promise<CommandOutcome> {\n const base = proxyBaseFromEnv();\n if (!base) {\n return {\n kind: \"error\",\n text: \"bili: no proxy detected — launch dsh through `bili dsh` so /acp can read context status.\",\n };\n }\n const status = await fetchStatusLatest(base);\n const panel = status?.panel;\n if (status && typeof panel === \"string\" && panel.length > 0) {\n return { kind: \"success\", text: panel };\n }\n const version = await fetchProxyVersion(base);\n if (version) {\n return {\n kind: \"success\",\n text: `billion-context@${version} — proxy connected, compression armed. No model request seen yet; send one, then run /acp again.`,\n };\n }\n return {\n kind: \"error\",\n text: `bili: proxy not reachable at ${base} — is the bili proxy still running?`,\n };\n}\n\nexport function apply(ctx: PluginContext): void {\n ctx.commands.register({\n name: \"acp\",\n description: \"Show bili context-compression status\",\n handler: statusOutcome,\n });\n}\n"],"mappings":";;;;AAeA,IAAM,oBAAoB;AAwBnB,SAAS,mBAAuC;AACnD,QAAM,MAAM,QAAQ,IAAI,uBAAuB,KAAK;AACpD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACA,UAAM,MAAM,IAAI,IAAI,GAAG;AACvB,WAAO,IAAI,aAAa,WAAW,IAAI,aAAa,WAAW,GAAG,IAAI,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,EACpG,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAOA,eAAe,UAAU,KAAa,MAA+B,WAAmB,gBAAuF;AAC3K,QAAM,KAAK,IAAI,gBAAgB;AAI/B,MAAI,gBAAgB,QAAS,IAAG,MAAM;AACtC,QAAM,QAAQ,WAAW,MAAM,GAAG,MAAM,GAAG,SAAS;AACpD,QAAM,kBAAkB,MAAM,GAAG,MAAM;AACvC,kBAAgB,iBAAiB,SAAS,iBAAiB,EAAE,MAAM,KAAK,CAAC;AACzE,MAAI;AACA,QAAI;AACJ,QAAI;AACA,YAAM,MAAM,MAAM,KAAK,EAAE,GAAG,MAAM,QAAQ,GAAG,OAAO,CAAC;AAAA,IACzD,SAAS,KAAK;AACV,UAAI,GAAG,OAAO,WAAW,CAAC,gBAAgB,QAAS,OAAM,IAAI,MAAM,iBAAiB,SAAS,OAAO,GAAG,EAAE;AACzG,YAAM;AAAA,IACV;AACA,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,OAAgB;AACpB,QAAI;AACA,aAAO,KAAK,MAAM,IAAI;AAAA,IAC1B,QAAQ;AACJ,aAAO;AAAA,IACX;AACA,WAAO,EAAE,IAAI,IAAI,IAAI,QAAQ,IAAI,QAAQ,KAAK;AAAA,EAClD,UAAE;AACE,iBAAa,KAAK;AAClB,oBAAgB,oBAAoB,SAAS,eAAe;AAAA,EAChE;AACJ;AAwDA,eAAsB,kBAAkB,WAAiE;AAIrG,QAAM,EAAE,IAAI,KAAK,IAAI,MAAM,UAAU,GAAG,SAAS,4DAA4D,QAAW,iBAAiB;AACzI,MAAI,CAAC,MAAM,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AACrD,SAAO;AACX;AAKA,eAAsB,kBAAkB,WAAgD;AACpF,QAAM,EAAE,IAAI,KAAK,IAAI,MAAM,UAAU,GAAG,SAAS,2BAA2B,QAAW,iBAAiB;AACxG,MAAI,CAAC,MAAM,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AACrD,QAAM,UAAW,KAA+B;AAChD,SAAO,OAAO,YAAY,YAAY,QAAQ,SAAS,IAAI,UAAU;AACzE;;;ACpJO,IAAM,OAAO;AACb,IAAM,SAAS,CAAC,UAAU;AAajC,eAAe,gBAAyC;AACpD,QAAM,OAAO,iBAAiB;AAC9B,MAAI,CAAC,MAAM;AACP,WAAO;AAAA,MACH,MAAM;AAAA,MACN,MAAM;AAAA,IACV;AAAA,EACJ;AACA,QAAM,SAAS,MAAM,kBAAkB,IAAI;AAC3C,QAAM,QAAQ,QAAQ;AACtB,MAAI,UAAU,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACzD,WAAO,EAAE,MAAM,WAAW,MAAM,MAAM;AAAA,EAC1C;AACA,QAAM,UAAU,MAAM,kBAAkB,IAAI;AAC5C,MAAI,SAAS;AACT,WAAO;AAAA,MACH,MAAM;AAAA,MACN,MAAM,mBAAmB,OAAO;AAAA,IACpC;AAAA,EACJ;AACA,SAAO;AAAA,IACH,MAAM;AAAA,IACN,MAAM,gCAAgC,IAAI;AAAA,EAC9C;AACJ;AAEO,SAAS,MAAM,KAA0B;AAC5C,MAAI,SAAS,SAAS;AAAA,IAClB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,SAAS;AAAA,EACb,CAAC;AACL;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/agent/shared.ts","../../src/agent/dsh-acp.ts"],"sourcesContent":["// Shared thin-plugin core for agent-side extensions (\"内外呼应\", issue #1).\n// The agent plugin is a PURE PROTOCOL CLIENT: no acp-kernel import, no\n// compression logic. The proxy stays the single compression authority; the\n// plugin only (1) detects the proxy, (2) fetches the tool manifest (single\n// source of truth), (3) forwards tool executes, (4) reads status. Same\n// package as the proxy ⇒ same version ⇒ no kernel-skew bug class.\n\nexport type ManifestTool = {\n name: string;\n description?: string;\n inputSchema: unknown;\n};\n\nconst MANIFEST_TIMEOUT_MS = 5000;\nconst TOOL_TIMEOUT_MS = 60000;\nconst STATUS_TIMEOUT_MS = 5000;\n\n/** Detect the proxy from a provider baseUrl's `/bili/` zero-config prefix.\n * The real prefix embeds the full upstream URL (`/bili/https://…`), so the\n * check requires `bili` as the first path segment followed by an http(s)\n * URL — a plain `/foo/bili/` path segment is NOT a bili proxy.\n * Returns the proxy origin (scheme//host) the request will actually hit. */\nexport function proxyBaseFromUrl(baseUrl: string | undefined): string | undefined {\n if (!baseUrl) return undefined;\n try {\n const url = new URL(baseUrl);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return undefined;\n const segments = url.pathname.split(\"/\").filter((s) => s.length > 0);\n if (segments[0] !== \"bili\") return undefined;\n const rest = url.pathname.slice(url.pathname.indexOf(\"bili\") + \"bili\".length);\n if (!/^\\/https?:\\/\\//.test(rest)) return undefined;\n return `${url.protocol}//${url.host}`;\n } catch {\n return undefined;\n }\n}\n\n/** MITM transparent mode has no `/bili/` prefix; the proxy's launcher exports\n * BILLION_CONTEXT_PROXY. A stale value surfaces as a tool-forward error. */\nexport function proxyBaseFromEnv(): string | undefined {\n const raw = process.env.BILLION_CONTEXT_PROXY?.trim();\n if (!raw) return undefined;\n try {\n const url = new URL(raw);\n return url.protocol === \"http:\" || url.protocol === \"https:\" ? `${url.protocol}//${url.host}` : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function detectProxyBase(baseUrl: string | undefined): string | undefined {\n if (process.env.BILLION_CONTEXT_PLUGIN === \"0\") return undefined;\n return proxyBaseFromUrl(baseUrl) ?? proxyBaseFromEnv();\n}\n\nasync function fetchJson(url: string, init: RequestInit | undefined, timeoutMs: number, externalSignal?: AbortSignal): Promise<{ ok: boolean; status: number; json: unknown }> {\n const ac = new AbortController();\n // An already-aborted external signal never fires its \"abort\" event, so\n // forward the state directly — otherwise only the timeout could stop\n // the request, turning an instant cancel into a timeout wait.\n if (externalSignal?.aborted) ac.abort();\n const timer = setTimeout(() => ac.abort(), timeoutMs);\n const onExternalAbort = () => ac.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n try {\n let res: Response;\n try {\n res = await fetch(url, { ...init, signal: ac.signal });\n } catch (err) {\n if (ac.signal.aborted && !externalSignal?.aborted) throw new Error(`timeout after ${timeoutMs}ms: ${url}`);\n throw err;\n }\n const text = await res.text();\n let json: unknown = undefined;\n try {\n json = JSON.parse(text);\n } catch {\n json = undefined;\n }\n return { ok: res.ok, status: res.status, json };\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n}\n\nexport async function fetchManifest(proxyBase: string, format: \"anthropic\" | \"openai\" = \"anthropic\"): Promise<ManifestTool[]> {\n const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/manifest`, undefined, MANIFEST_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") throw new Error(`manifest fetch failed: ${status}`);\n if (format === \"openai\") {\n // OpenAI function style: {name, description, parameters} (plain JSON Schema).\n const data = json as { tools?: { openai?: { name?: string; description?: string; parameters?: unknown }[] } };\n const tools = (data.tools?.openai ?? []).filter((t): t is { name: string; description?: string; parameters?: unknown } => typeof t.name === \"string\");\n if (tools.length === 0) throw new Error(\"manifest served no openai tools\");\n return tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.parameters ?? { type: \"object\", properties: {} } }));\n }\n const data = json as { tools?: { anthropic?: { name?: string; description?: string; input_schema?: unknown }[] } };\n const tools = (data.tools?.anthropic ?? []).filter((t): t is { name: string; description?: string; input_schema?: unknown } => typeof t.name === \"string\");\n if (tools.length === 0) throw new Error(\"manifest served no anthropic tools\");\n return tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.input_schema ?? { type: \"object\", properties: {} } }));\n}\n\nconst COMPACT_TIMEOUT_MS = 5000;\n\n/** Report a host-native compaction boundary to the proxy archive (#395):\n * hosts that compact natively (opencode) cannot cancel it, so the proxy\n * marks the boundary and archives unreachable blocks. Fire-and-forget at\n * call sites — a missed report degrades to stale blocks, not broken turns. */\nexport async function reportCompactionBoundary(proxyBase: string, conversationId: string): Promise<void> {\n await fetchJson(`${proxyBase}/__bili/plugin/compact`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId }),\n }, COMPACT_TIMEOUT_MS);\n}\n\nexport async function forwardTool(proxyBase: string, conversationId: string, tool: string, args: unknown, signal?: AbortSignal): Promise<string> {\n const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/tool`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId, tool, args: args ?? {} }),\n }, TOOL_TIMEOUT_MS, signal);\n const data = json as { ok?: boolean; result?: string; error?: string } | undefined;\n if (!ok || !data?.ok) {\n throw new Error(`bili proxy tool ${tool} failed (${status}): ${data?.error ?? \"unknown error\"}`);\n }\n return data.result ?? \"\";\n}\n\n/** Soft-fail by design: the status read is best-effort UI data; undefined\n * means \"no data\" whether the proxy is down or the session is unknown. */\nexport async function fetchStatus(proxyBase: string, conversationId: string): Promise<Record<string, unknown> | undefined> {\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/status?conversationId=${encodeURIComponent(conversationId)}`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n return json as Record<string, unknown>;\n}\n\n/** Wire-mode status read (dsh): those clients carry no per-conversation id\n * the proxy could bind, so ask for the most recently active session instead\n * (fallback=latest). Same soft-fail contract as fetchStatus. */\nexport async function fetchStatusLatest(proxyBase: string): Promise<Record<string, unknown> | undefined> {\n // conversationId must be non-empty (server rejects the empty string), but\n // any unknown id is fine: fallback=latest then resolves the most recently\n // active session.\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/status?conversationId=dsh&fallback=latest`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n return json as Record<string, unknown>;\n}\n\n/** Liveness + version probe for status UIs: same loopback origin as the\n * status endpoint, so a 404 status + a live manifest means \"proxy up,\n * conversation not seen yet\" — an armed-but-idle state, not an error. */\nexport async function fetchProxyVersion(proxyBase: string): Promise<string | undefined> {\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/manifest`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n const version = (json as { version?: unknown }).version;\n return typeof version === \"string\" && version.length > 0 ? version : undefined;\n}\n\n// Model-facing body for /acp status renders: the visible panel goes to the synthetic message's\n// description (TUI Notice cap ~1KB); this inert one-liner keeps the model context clean.\nexport const V2_SYNTHETIC_TEXT = \"[bili-acp-status] ACP status panel was displayed in your terminal; this line is not an instruction.\";\n","// Native dsh (deepseek-harness) cordis plugin: registers the `/acp` command.\n// Injected by the `bili dsh` launcher through a `--patch` overlay that\n// inserts this module (as a file:// URL) into the loader entry tree — the\n// same plugin shape as dsh-command-compact. Pure protocol client, same\n// discipline as the other agent plugins: no acp-kernel import, every byte of\n// displayed data comes from the proxy's HTTP endpoints.\n\nimport { proxyBaseFromEnv, fetchProxyVersion, fetchStatusLatest } from \"./shared.js\";\n\nexport const name = \"bili-acp\";\nexport const inject = [\"commands\"];\n\ntype CommandOutcome = { kind: \"success\" | \"error\"; text: string };\n\ntype CommandsService = {\n register: (command: { name: string; description: string; handler: () => Promise<CommandOutcome> }) => unknown;\n};\n\ntype PluginContext = { commands: CommandsService };\n\n/** One `/acp` invocation: latest session panel, else armed-but-idle, else a\n * reachable-proxy failure. dsh conversations carry no client-side id we can\n * bind to, so the read asks for the most recently active session. */\nasync function statusOutcome(): Promise<CommandOutcome> {\n const base = proxyBaseFromEnv();\n if (!base) {\n return {\n kind: \"error\",\n text: \"bili: no proxy detected — launch dsh through `bili dsh` so /acp can read context status.\",\n };\n }\n const status = await fetchStatusLatest(base);\n const panel = status?.panel;\n if (status && typeof panel === \"string\" && panel.length > 0) {\n return { kind: \"success\", text: panel };\n }\n const version = await fetchProxyVersion(base);\n if (version) {\n return {\n kind: \"success\",\n text: `billion-context@${version} — proxy connected, compression armed. No model request seen yet; send one, then run /acp again.`,\n };\n }\n return {\n kind: \"error\",\n text: `bili: proxy not reachable at ${base} — is the bili proxy still running?`,\n };\n}\n\nexport function apply(ctx: PluginContext): void {\n ctx.commands.register({\n name: \"acp\",\n description: \"Show bili context-compression status\",\n handler: statusOutcome,\n });\n}\n"],"mappings":";;;;AAeA,IAAM,oBAAoB;AAwBnB,SAAS,mBAAuC;AACnD,QAAM,MAAM,QAAQ,IAAI,uBAAuB,KAAK;AACpD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACA,UAAM,MAAM,IAAI,IAAI,GAAG;AACvB,WAAO,IAAI,aAAa,WAAW,IAAI,aAAa,WAAW,GAAG,IAAI,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,EACpG,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAOA,eAAe,UAAU,KAAa,MAA+B,WAAmB,gBAAuF;AAC3K,QAAM,KAAK,IAAI,gBAAgB;AAI/B,MAAI,gBAAgB,QAAS,IAAG,MAAM;AACtC,QAAM,QAAQ,WAAW,MAAM,GAAG,MAAM,GAAG,SAAS;AACpD,QAAM,kBAAkB,MAAM,GAAG,MAAM;AACvC,kBAAgB,iBAAiB,SAAS,iBAAiB,EAAE,MAAM,KAAK,CAAC;AACzE,MAAI;AACA,QAAI;AACJ,QAAI;AACA,YAAM,MAAM,MAAM,KAAK,EAAE,GAAG,MAAM,QAAQ,GAAG,OAAO,CAAC;AAAA,IACzD,SAAS,KAAK;AACV,UAAI,GAAG,OAAO,WAAW,CAAC,gBAAgB,QAAS,OAAM,IAAI,MAAM,iBAAiB,SAAS,OAAO,GAAG,EAAE;AACzG,YAAM;AAAA,IACV;AACA,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,OAAgB;AACpB,QAAI;AACA,aAAO,KAAK,MAAM,IAAI;AAAA,IAC1B,QAAQ;AACJ,aAAO;AAAA,IACX;AACA,WAAO,EAAE,IAAI,IAAI,IAAI,QAAQ,IAAI,QAAQ,KAAK;AAAA,EAClD,UAAE;AACE,iBAAa,KAAK;AAClB,oBAAgB,oBAAoB,SAAS,eAAe;AAAA,EAChE;AACJ;AAwDA,eAAsB,kBAAkB,WAAiE;AAIrG,QAAM,EAAE,IAAI,KAAK,IAAI,MAAM,UAAU,GAAG,SAAS,4DAA4D,QAAW,iBAAiB;AACzI,MAAI,CAAC,MAAM,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AACrD,SAAO;AACX;AAKA,eAAsB,kBAAkB,WAAgD;AACpF,QAAM,EAAE,IAAI,KAAK,IAAI,MAAM,UAAU,GAAG,SAAS,2BAA2B,QAAW,iBAAiB;AACxG,MAAI,CAAC,MAAM,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AACrD,QAAM,UAAW,KAA+B;AAChD,SAAO,OAAO,YAAY,YAAY,QAAQ,SAAS,IAAI,UAAU;AACzE;;;ACpJO,IAAM,OAAO;AACb,IAAM,SAAS,CAAC,UAAU;AAajC,eAAe,gBAAyC;AACpD,QAAM,OAAO,iBAAiB;AAC9B,MAAI,CAAC,MAAM;AACP,WAAO;AAAA,MACH,MAAM;AAAA,MACN,MAAM;AAAA,IACV;AAAA,EACJ;AACA,QAAM,SAAS,MAAM,kBAAkB,IAAI;AAC3C,QAAM,QAAQ,QAAQ;AACtB,MAAI,UAAU,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACzD,WAAO,EAAE,MAAM,WAAW,MAAM,MAAM;AAAA,EAC1C;AACA,QAAM,UAAU,MAAM,kBAAkB,IAAI;AAC5C,MAAI,SAAS;AACT,WAAO;AAAA,MACH,MAAM;AAAA,MACN,MAAM,mBAAmB,OAAO;AAAA,IACpC;AAAA,EACJ;AACA,SAAO;AAAA,IACH,MAAM;AAAA,IACN,MAAM,gCAAgC,IAAI;AAAA,EAC9C;AACJ;AAEO,SAAS,MAAM,KAA0B;AAC5C,MAAI,SAAS,SAAS;AAAA,IAClB,MAAM;AAAA,IACN,aAAa;AAAA,IACb,SAAS;AAAA,EACb,CAAC;AACL;","names":[]}
|
package/dist/agent/omp.js
CHANGED
|
@@ -104,7 +104,7 @@ function agentName(override) {
|
|
|
104
104
|
return process.env.BILLION_CONTEXT_PLUGIN_AGENT === "omp" ? "omp" : "pi";
|
|
105
105
|
}
|
|
106
106
|
function proxyBaseForCtx(ctx) {
|
|
107
|
-
return detectProxyBase(ctx
|
|
107
|
+
return detectProxyBase(ctx?.model?.baseUrl);
|
|
108
108
|
}
|
|
109
109
|
function sessionIdOf(ctx) {
|
|
110
110
|
try {
|
|
@@ -269,9 +269,10 @@ function createBiliPlugin(agentOverride, opts) {
|
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
|
-
if (
|
|
272
|
+
if (agent === "pi" || agent === "omp") {
|
|
273
273
|
if (agent === "pi") {
|
|
274
|
-
pi.on("session_before_compact", (event) => {
|
|
274
|
+
pi.on("session_before_compact", (event, ctx) => {
|
|
275
|
+
if (proxyBaseForCtx(ctx) === void 0) return void 0;
|
|
275
276
|
const reason = event.reason;
|
|
276
277
|
if (reason === "threshold" || reason === "overflow") return { cancel: true };
|
|
277
278
|
return void 0;
|
|
@@ -284,7 +285,8 @@ function createBiliPlugin(agentOverride, opts) {
|
|
|
284
285
|
pi.on("auto_compaction_end", () => {
|
|
285
286
|
autoPending = false;
|
|
286
287
|
});
|
|
287
|
-
pi.on("session_before_compact", () => {
|
|
288
|
+
pi.on("session_before_compact", (event, ctx) => {
|
|
289
|
+
if (proxyBaseForCtx(ctx) === void 0) return void 0;
|
|
288
290
|
if (!autoPending) return void 0;
|
|
289
291
|
autoPending = false;
|
|
290
292
|
return { cancel: true };
|
package/dist/agent/omp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/shared.ts","../../src/agent/pi.ts","../../src/agent/omp.ts"],"sourcesContent":["// Shared thin-plugin core for agent-side extensions (\"内外呼应\", issue #1).\n// The agent plugin is a PURE PROTOCOL CLIENT: no acp-kernel import, no\n// compression logic. The proxy stays the single compression authority; the\n// plugin only (1) detects the proxy, (2) fetches the tool manifest (single\n// source of truth), (3) forwards tool executes, (4) reads status. Same\n// package as the proxy ⇒ same version ⇒ no kernel-skew bug class.\n\nexport type ManifestTool = {\n name: string;\n description?: string;\n inputSchema: unknown;\n};\n\nconst MANIFEST_TIMEOUT_MS = 5000;\nconst TOOL_TIMEOUT_MS = 60000;\nconst STATUS_TIMEOUT_MS = 5000;\n\n/** Detect the proxy from a provider baseUrl's `/bili/` zero-config prefix.\n * The real prefix embeds the full upstream URL (`/bili/https://…`), so the\n * check requires `bili` as the first path segment followed by an http(s)\n * URL — a plain `/foo/bili/` path segment is NOT a bili proxy.\n * Returns the proxy origin (scheme//host) the request will actually hit. */\nexport function proxyBaseFromUrl(baseUrl: string | undefined): string | undefined {\n if (!baseUrl) return undefined;\n try {\n const url = new URL(baseUrl);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return undefined;\n const segments = url.pathname.split(\"/\").filter((s) => s.length > 0);\n if (segments[0] !== \"bili\") return undefined;\n const rest = url.pathname.slice(url.pathname.indexOf(\"bili\") + \"bili\".length);\n if (!/^\\/https?:\\/\\//.test(rest)) return undefined;\n return `${url.protocol}//${url.host}`;\n } catch {\n return undefined;\n }\n}\n\n/** MITM transparent mode has no `/bili/` prefix; the proxy's launcher exports\n * BILLION_CONTEXT_PROXY. A stale value surfaces as a tool-forward error. */\nexport function proxyBaseFromEnv(): string | undefined {\n const raw = process.env.BILLION_CONTEXT_PROXY?.trim();\n if (!raw) return undefined;\n try {\n const url = new URL(raw);\n return url.protocol === \"http:\" || url.protocol === \"https:\" ? `${url.protocol}//${url.host}` : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function detectProxyBase(baseUrl: string | undefined): string | undefined {\n if (process.env.BILLION_CONTEXT_PLUGIN === \"0\") return undefined;\n return proxyBaseFromUrl(baseUrl) ?? proxyBaseFromEnv();\n}\n\nasync function fetchJson(url: string, init: RequestInit | undefined, timeoutMs: number, externalSignal?: AbortSignal): Promise<{ ok: boolean; status: number; json: unknown }> {\n const ac = new AbortController();\n // An already-aborted external signal never fires its \"abort\" event, so\n // forward the state directly — otherwise only the timeout could stop\n // the request, turning an instant cancel into a timeout wait.\n if (externalSignal?.aborted) ac.abort();\n const timer = setTimeout(() => ac.abort(), timeoutMs);\n const onExternalAbort = () => ac.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n try {\n let res: Response;\n try {\n res = await fetch(url, { ...init, signal: ac.signal });\n } catch (err) {\n if (ac.signal.aborted && !externalSignal?.aborted) throw new Error(`timeout after ${timeoutMs}ms: ${url}`);\n throw err;\n }\n const text = await res.text();\n let json: unknown = undefined;\n try {\n json = JSON.parse(text);\n } catch {\n json = undefined;\n }\n return { ok: res.ok, status: res.status, json };\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n}\n\nexport async function fetchManifest(proxyBase: string, format: \"anthropic\" | \"openai\" = \"anthropic\"): Promise<ManifestTool[]> {\n const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/manifest`, undefined, MANIFEST_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") throw new Error(`manifest fetch failed: ${status}`);\n if (format === \"openai\") {\n // OpenAI function style: {name, description, parameters} (plain JSON Schema).\n const data = json as { tools?: { openai?: { name?: string; description?: string; parameters?: unknown }[] } };\n const tools = (data.tools?.openai ?? []).filter((t): t is { name: string; description?: string; parameters?: unknown } => typeof t.name === \"string\");\n if (tools.length === 0) throw new Error(\"manifest served no openai tools\");\n return tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.parameters ?? { type: \"object\", properties: {} } }));\n }\n const data = json as { tools?: { anthropic?: { name?: string; description?: string; input_schema?: unknown }[] } };\n const tools = (data.tools?.anthropic ?? []).filter((t): t is { name: string; description?: string; input_schema?: unknown } => typeof t.name === \"string\");\n if (tools.length === 0) throw new Error(\"manifest served no anthropic tools\");\n return tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.input_schema ?? { type: \"object\", properties: {} } }));\n}\n\nconst COMPACT_TIMEOUT_MS = 5000;\n\n/** Report a host-native compaction boundary to the proxy archive (#395):\n * hosts that compact natively (opencode) cannot cancel it, so the proxy\n * marks the boundary and archives unreachable blocks. Fire-and-forget at\n * call sites — a missed report degrades to stale blocks, not broken turns. */\nexport async function reportCompactionBoundary(proxyBase: string, conversationId: string): Promise<void> {\n await fetchJson(`${proxyBase}/__bili/plugin/compact`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId }),\n }, COMPACT_TIMEOUT_MS);\n}\n\nexport async function forwardTool(proxyBase: string, conversationId: string, tool: string, args: unknown, signal?: AbortSignal): Promise<string> {\n const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/tool`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId, tool, args: args ?? {} }),\n }, TOOL_TIMEOUT_MS, signal);\n const data = json as { ok?: boolean; result?: string; error?: string } | undefined;\n if (!ok || !data?.ok) {\n throw new Error(`bili proxy tool ${tool} failed (${status}): ${data?.error ?? \"unknown error\"}`);\n }\n return data.result ?? \"\";\n}\n\n/** Soft-fail by design: the status read is best-effort UI data; undefined\n * means \"no data\" whether the proxy is down or the session is unknown. */\nexport async function fetchStatus(proxyBase: string, conversationId: string): Promise<Record<string, unknown> | undefined> {\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/status?conversationId=${encodeURIComponent(conversationId)}`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n return json as Record<string, unknown>;\n}\n\n/** Wire-mode status read (dsh): those clients carry no per-conversation id\n * the proxy could bind, so ask for the most recently active session instead\n * (fallback=latest). Same soft-fail contract as fetchStatus. */\nexport async function fetchStatusLatest(proxyBase: string): Promise<Record<string, unknown> | undefined> {\n // conversationId must be non-empty (server rejects the empty string), but\n // any unknown id is fine: fallback=latest then resolves the most recently\n // active session.\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/status?conversationId=dsh&fallback=latest`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n return json as Record<string, unknown>;\n}\n\n/** Liveness + version probe for status UIs: same loopback origin as the\n * status endpoint, so a 404 status + a live manifest means \"proxy up,\n * conversation not seen yet\" — an armed-but-idle state, not an error. */\nexport async function fetchProxyVersion(proxyBase: string): Promise<string | undefined> {\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/manifest`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n const version = (json as { version?: unknown }).version;\n return typeof version === \"string\" && version.length > 0 ? version : undefined;\n}\n","// Thin agent extension for pi and omp (\"内外呼应\", issue #1). Loaded by pi\n// via the package.json `pi` manifest (dist/agent/pi.js) or by omp via the\n// config.yml `extensions:` list (dist/agent/omp.js). pi and omp share the\n// ExtensionFactory API shape, so one factory serves both; types below are\n// minimal structural declarations — the bundled artifact imports NOTHING\n// from the host at runtime (the host duck-types us in).\n\nimport { detectProxyBase, fetchManifest, forwardTool, fetchStatus, fetchProxyVersion, type ManifestTool } from \"./shared.js\";\n\ntype Ctx = {\n sessionManager?: { getSessionId?: () => string } | undefined;\n model?: { contextWindow?: number; baseUrl?: string; provider?: string; id?: string; [key: string]: unknown } | undefined;\n cwd?: string;\n};\n\ntype TextBlock = { type: \"text\"; text: string };\ntype ToolResult = { content: TextBlock[]; isError?: boolean };\n\ntype ToolDefinition = {\n name: string;\n description?: string;\n parameters: unknown;\n // omp 17.x mounts extension tools that omit loadMode under xd:// devices\n // (invisible to the main turn's tools array — only title requests see\n // them). Declaring \"essential\" keeps ACP tools top-level; pi upstream\n // ignores the field.\n loadMode?: string;\n execute: (toolCallId: string, params: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: ((u: unknown) => void) | undefined, ctx: Ctx) => Promise<ToolResult>;\n};\n\ntype CommandCtx = {\n sessionManager?: { getSessionId?: () => string } | undefined;\n model?: { contextWindow?: number; baseUrl?: string } | undefined;\n ui?: { notify?: (message: string, type?: string) => void } | undefined;\n};\n\ntype ExtensionAPI = {\n on: (event: string, handler: (event: never, ctx: Ctx) => unknown) => void;\n registerTool: (tool: ToolDefinition) => void;\n registerCommand?: (name: string, options: { description?: string; handler: (args: string, ctx: CommandCtx) => void | Promise<void> }) => void;\n // #535: launcher passes provider URL rewrites via env; the extension\n // overrides each provider's baseUrl at load (file-free routing — no\n // models.json overlay). Optional because older hosts may lack it.\n registerProvider?: (name: string, config: { baseUrl: string }) => void;\n // #535 omp-only: omp pins the session's Model object from the static\n // catalog BEFORE extensions load, and its registerProvider — unlike\n // pi's _refreshCurrentModelFromRegistry — never re-resolves the live\n // session model, so the extension must re-pin it via setModel (see the\n // session_start handler below). Optional because pi hosts lack it.\n setModel?: (model: Record<string, unknown> & { baseUrl?: string }) => Promise<boolean | void> | boolean | void;\n // Persistent transcript output (rendered by TUI and web hosts like\n // pi-web); notify() is a transient toast — only the fallback for hosts\n // without sendMessage (issue #359).\n sendMessage?: (message: { customType: string; content: string; display: boolean }) => void;\n};\n\nfunction agentName(override: string | undefined): string {\n if (override) return override;\n return process.env.BILLION_CONTEXT_PLUGIN_AGENT === \"omp\" ? \"omp\" : \"pi\";\n}\n\nfunction proxyBaseForCtx(ctx: Ctx): string | undefined {\n return detectProxyBase(ctx.model?.baseUrl);\n}\n\nfunction sessionIdOf(ctx: Ctx): string | undefined {\n try {\n const sid = ctx.sessionManager?.getSessionId?.();\n return typeof sid === \"string\" ? sid : undefined;\n } catch {\n return undefined;\n }\n}\n\n// omp's chat-completions payloads carry NO conversation signal (no\n// prompt_cache_key / session / user, and no session header — verified by dump),\n// so the proxy's openai identity falls to a content fingerprint that never\n// matches the session id this plugin registered (the identity register is keyed\n// by the omp session uuid). The before_provider_request return value REPLACES\n// the whole outgoing payload (omp onPayload chain, verified in the omp 17.3.8\n// dist), so stamp prompt_cache_key with the omp session id: the proxy binds\n// pluginMode by that identity and /acp finds the session by it.\n// Chat shape = messages array, no responses `input`, no native prompt_cache_key.\n// max_tokens is NOT a discriminator: omp's openai-compat providers send it in\n// every chat-completions body (maxTokensField:\"max_tokens\") exactly like the\n// anthropic wire — excluding it meant the target shape was never stamped\n// (#268). The anthropic wire gets stamped too: the proxy records the mapping\n// from the body pck there as well and strips the field before forwarding to\n// the real Anthropic. pi is untouched (it stamps x-bili-plugin-conversation in\n// before_provider_headers, which outranks the body field).\nfunction stampPromptCacheKey(event: unknown, ctx: Ctx, agent: string): Record<string, unknown> | undefined {\n if (agent !== \"omp\") return undefined;\n const payload = (event as { payload?: unknown } | undefined)?.payload;\n if (payload === null || typeof payload !== \"object\" || Array.isArray(payload)) return undefined;\n const p = payload as Record<string, unknown>;\n if (!Array.isArray(p.messages)) return undefined;\n if (p.input !== undefined) return undefined;\n if (typeof p.prompt_cache_key === \"string\" && p.prompt_cache_key.trim().length > 0) return undefined;\n const sid = sessionIdOf(ctx);\n if (sid === undefined || sid.length === 0) return undefined;\n return { ...p, prompt_cache_key: sid };\n}\n\nfunction fmtTok(n: number): string {\n if (n < 1000) return String(n);\n if (n < 1_000_000) return `${(n / 1000).toFixed(1)}K`;\n return `${(n / 1_000_000).toFixed(2)}M`;\n}\n\nfunction renderAcpStatus(s: Record<string, unknown>): string {\n const num = (v: unknown): number | null => (typeof v === \"number\" && Number.isFinite(v) ? v : null);\n const contextTokens = num(s.contextTokens);\n const contextLimit = num(s.contextLimit);\n const inputTokens = num(s.inputTokens);\n const outputTokens = num(s.outputTokens);\n const cachedTokens = num(s.cachedTokens);\n const requests = num(s.requests);\n const blocks = Array.isArray(s.blocks) ? (s.blocks as Array<{ tier?: number; active?: boolean }>) : [];\n const activeBlocks = blocks.filter((b) => b.active === true).length;\n const lines: string[] = [\"📊 ACP status\"];\n if (contextTokens !== null) {\n const pct = contextLimit !== null && contextLimit > 0 ? ` (${((contextTokens / contextLimit) * 100).toFixed(1)}%)` : \"\";\n lines.push(` context: ${fmtTok(contextTokens)}${contextLimit !== null ? ` / ${fmtTok(contextLimit)}` : \"\"}${pct}`);\n }\n const hostCredit = num(s.hostCredit);\n if (hostCredit !== null && hostCredit > 0) {\n lines.push(` host baseline: uncompressed (proxy backfilled +${fmtTok(hostCredit)} tok)`);\n }\n if (inputTokens !== null || outputTokens !== null || cachedTokens !== null) {\n lines.push(` in/out/cached: ${fmtTok(inputTokens ?? 0)} / ${fmtTok(outputTokens ?? 0)} / ${fmtTok(cachedTokens ?? 0)}`);\n }\n if (requests !== null) lines.push(` requests: ${requests}`);\n if (blocks.length > 0) lines.push(` blocks: ${blocks.length} (${activeBlocks} active)`);\n return lines.join(\"\\n\");\n}\n\nfunction manifestToTool(proxyBase: string, tool: ManifestTool, agent: string): ToolDefinition {\n return {\n name: tool.name,\n description: tool.description,\n parameters: tool.inputSchema,\n loadMode: \"essential\",\n execute: async (_toolCallId, params, signal, _onUpdate, ctx) => {\n const conversationId = sessionIdOf(ctx) ?? \"unknown\";\n try {\n const output = await forwardTool(proxyBase, conversationId, tool.name, params, signal);\n return { content: [{ type: \"text\", text: output }] };\n } catch (err) {\n return { content: [{ type: \"text\", text: `bili tool error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };\n }\n },\n };\n}\n\nfunction parseProviderRewrites(env: NodeJS.ProcessEnv): Record<string, string> | undefined {\n const raw = env.BILI_PROVIDER_REWRITES;\n if (raw === undefined || raw.trim().length === 0) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n console.error(\"bili-plugin: BILI_PROVIDER_REWRITES is not valid JSON — provider URLs left untouched\");\n return undefined;\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined;\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof value !== \"string\" || !/^https?:\\/\\//i.test(value)) continue;\n out[key] = value;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nconst RETRY_INTERVAL_MS = 10000;\n\ntype RegisterState = { sid?: string; toolsFor?: string; toolsReady?: boolean; pending?: Promise<void>; retryAt?: number; identityAt?: string; retryIntervalMs: number };\n\n// omp never emits before_provider_headers, so the x-bili-plugin marker cannot\n// be stamped per request. Register the conversation id once (after tools are\n// ready): the proxy binds any request carrying that id into plugin mode —\n// same launcher path claude/codex use (#162).\nasync function postIdentityRegister(proxyBase: string, conversationId: string, agent: string): Promise<void> {\n const res = await fetch(`${proxyBase}/__bili/plugin/register`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId, agent, identity: true }),\n signal: AbortSignal.timeout(5000),\n });\n if (!res.ok) throw new Error(`register HTTP ${res.status}`);\n}\n\nasync function registerTools(pi: ExtensionAPI, ctx: Ctx, state: RegisterState, agent: string): Promise<void> {\n const proxyBase = proxyBaseForCtx(ctx);\n if (proxyBase === undefined) return;\n // Cache on the session id; \"\" (host has no sessionManager) still caches,\n // so a successful registration is not re-fetched on every provider\n // request — the manifest is session-independent anyway.\n const sid = sessionIdOf(ctx) ?? \"\";\n if (sid === state.sid) return;\n if (state.pending !== undefined) return state.pending;\n if (state.retryAt !== undefined && Date.now() < state.retryAt) return;\n const wait = state.retryIntervalMs;\n state.pending = (async () => {\n let tools: ManifestTool[];\n try {\n tools = await fetchManifest(proxyBase);\n } catch (err) {\n state.retryAt = Date.now() + wait;\n console.error(`bili-plugin(${agent}): manifest fetch failed: ${err instanceof Error ? err.message : String(err)} — retrying in ${wait / 1000}s`);\n return;\n }\n try {\n // toolsFor (not sid) guards the register loop: a retry after a\n // failed identity register re-fetches the manifest but must NOT\n // re-register the tools (the host may not dedupe by name).\n if (state.toolsFor !== sid) {\n for (const t of tools) pi.registerTool(manifestToTool(proxyBase, t, agent));\n state.toolsFor = sid;\n }\n state.toolsReady = true;\n state.retryAt = undefined;\n if (agent === \"omp\" && sid !== \"\" && state.identityAt !== sid) {\n try {\n await postIdentityRegister(proxyBase, sid, agent);\n state.identityAt = sid;\n } catch (err) {\n // Leave state.sid UNSET so the next per-request event\n // re-enters (throttled by retryAt) and retries ONLY the\n // register — setting sid here would wedge the session in\n // wire mode forever (the early return above blocks every\n // retry).\n state.retryAt = Date.now() + wait;\n console.error(`bili-plugin(${agent}): identity register failed (${err instanceof Error ? err.message : String(err)}) — retrying in ${wait / 1000}s`);\n return;\n }\n }\n state.sid = sid;\n } catch (err) {\n state.sid = undefined;\n state.toolsFor = undefined;\n state.retryAt = Date.now() + wait;\n console.error(`bili-plugin(${agent}): tool registration deferred (${err instanceof Error ? err.message : String(err)}) — retrying in ${wait / 1000}s`);\n }\n })();\n try {\n await state.pending;\n } finally {\n state.pending = undefined;\n }\n}\n\nexport function createBiliPlugin(agentOverride?: string, opts?: { retryIntervalMs?: number }): (pi: ExtensionAPI) => void {\n return function biliPlugin(pi: ExtensionAPI): void {\n const agent = agentName(agentOverride);\n const state: RegisterState = { retryIntervalMs: opts?.retryIntervalMs ?? RETRY_INTERVAL_MS };\n // #535: file-free routing — override provider baseUrls at load from\n // the launcher-passed manifest (see buildPiEnv). registerProvider is\n // queued during initial extension load and applied before any model\n // traffic, so every request (including round 1) rides the proxy.\n const rewrites = parseProviderRewrites(process.env);\n if (rewrites !== undefined && typeof pi.registerProvider !== \"function\") {\n console.error(\n \"bili-plugin: BILI_PROVIDER_REWRITES is set but this pi build has no registerProvider API — \" +\n \"provider traffic goes DIRECT (uncompressed). Update pi, or reinstall the bili plugin: `bili plugin install pi`.\",\n );\n }\n if (rewrites !== undefined && typeof pi.registerProvider === \"function\") {\n for (const [key, url] of Object.entries(rewrites)) {\n try {\n pi.registerProvider(key, { baseUrl: url });\n } catch (err) {\n console.error(`bili-plugin: registerProvider(${key}) failed: ${err instanceof Error ? err.message : String(err)} — traffic for this provider goes direct`);\n }\n }\n }\n // #535: cancel the host's NATIVE compaction so its summarizer never\n // fires alongside bili's ACP compression — the in-extension\n // replacement for the old compaction-off config injection. Only\n // armed under `bili` launch: plain pi/omp with the plugin installed\n // stays fully native.\n // pi: the event carries `reason`; cancel only threshold + overflow so\n // manual /compact stays user-owned.\n // omp (#851): session_before_compact carries NO reason field, so at\n // hook level manual compaction (/compact, plan-mode \"Approve and\n // compact context\") is indistinguishable from auto — but every auto\n // pass announces itself first via auto_compaction_start (reason\n // threshold|overflow|idle|incomplete), which omp emits (awaited)\n // before the hook fires; manual paths never do. Track the\n // announcement: announced passes stay cancelled, unannounced ones\n // are left user-owned. A surviving native compaction is safe: the\n // proxy archives the unreachable blocks on session_compact (#395).\n if ((agent === \"pi\" || agent === \"omp\") && process.env.BILLION_CONTEXT_PROXY !== undefined) {\n if (agent === \"pi\") {\n pi.on(\"session_before_compact\", (event) => {\n const reason = (event as unknown as { reason?: unknown }).reason;\n if (reason === \"threshold\" || reason === \"overflow\") return { cancel: true };\n return undefined;\n });\n } else {\n let autoPending = false;\n pi.on(\"auto_compaction_start\", () => {\n autoPending = true;\n });\n pi.on(\"auto_compaction_end\", () => {\n autoPending = false;\n });\n pi.on(\"session_before_compact\", () => {\n if (!autoPending) return undefined;\n autoPending = false;\n return { cancel: true };\n });\n }\n }\n // #535 omp-only: omp resolves modelRoles.default into options.model\n // from the PRE-extension static catalog (main.ts: \"scope is resolved\n // before extensions register their providers\"), and omp's fork lacks\n // pi's registerProvider → _refreshCurrentModelFromRegistry hop — the\n // registry gets the rewritten baseUrl but the live session keeps the\n // direct one, so every request bypasses the proxy (fetch trace →\n // http://127.0.0.1:8197/v1/responses with zero proxy forwards). Re-pin\n // the session model at load + on every session switch: spread the\n // current model with the rewritten baseUrl through the host setModel\n // (keyed-provider-gated; local providers carry dummy keys). Mid-session\n // /model picks resolve from the already-overridden registry, so only\n // session start/restore need this.\n if (agent === \"omp\" && rewrites !== undefined && typeof pi.setModel === \"function\") {\n const repin = async (ctx: Ctx): Promise<void> => {\n const model = ctx?.model;\n if (model === null || typeof model !== \"object\") return;\n const provider = model.provider;\n if (typeof provider !== \"string\" || provider === \"\") return;\n const rewritten = rewrites[provider];\n if (rewritten === undefined || model.baseUrl === rewritten) return;\n try {\n const switched = await pi.setModel?.({ ...model, baseUrl: rewritten });\n if (switched === false) {\n console.error(`bili-plugin: omp setModel(${provider}/${String(model.id)}) rejected (no API key) — traffic for this provider goes direct`);\n }\n } catch (err) {\n console.error(`bili-plugin: omp setModel failed: ${err instanceof Error ? err.message : String(err)} — traffic goes direct`);\n }\n };\n pi.on(\"session_start\", (_event, ctx) => repin(ctx));\n pi.on(\"session_switch\", (_event, ctx) => repin(ctx));\n }\n if (typeof pi.registerCommand === \"function\") {\n pi.registerCommand(\"acp\", {\n description: \"Show ACP context-compression status for this session\",\n handler: async (_args, ctx) => {\n const notify = (message: string, type?: string): void => {\n try {\n ctx.ui?.notify?.(message, type);\n } catch {\n // host UI unavailable — the command is best-effort\n }\n };\n const proxyBase = detectProxyBase(ctx.model?.baseUrl);\n if (proxyBase === undefined) {\n // #788: neutral wording — the plugin also loads under plain\n // pi/omp launches where the user never intended proxy mode\n // (e.g. they use billion-context-pi in-process instead), so\n // offer both exits instead of assuming proxy intent.\n const removeHint = agent === \"pi\"\n ? \", or remove this plugin (`bili plugin remove pi`) if you use billion-context-pi or don't want a proxy\"\n : agent === \"omp\"\n ? \", or remove this plugin (`bili plugin remove omp`) if you don't want a proxy\"\n : \"\";\n notify(`bili: no proxy detected — run via \\`bili ${agent}\\` (or set a /bili/ baseURL) to use proxy mode${removeHint}`, \"warning\");\n return;\n }\n const conversationId = sessionIdOf(ctx) ?? \"unknown\";\n let status: Record<string, unknown> | undefined;\n try {\n status = await fetchStatus(proxyBase, conversationId);\n } catch (err) {\n notify(`bili: status fetch failed: ${err instanceof Error ? err.message : String(err)}`, \"error\");\n return;\n }\n if (status === undefined) {\n // 404 from a live proxy = this conversation has sent no\n // model request yet (e.g. /acp right after startup).\n // Probe the manifest to confirm liveness + version and\n // show an armed/idle notice instead of a scary warning.\n let version: string | undefined;\n try {\n version = await fetchProxyVersion(proxyBase);\n } catch {\n version = undefined;\n }\n if (version !== undefined) {\n notify(\n `billion-context@${version} — proxy connected, compression armed. No model request in this conversation yet; send one, then run /acp again.`,\n \"info\",\n );\n } else {\n notify(\"bili: no ACP session yet (send a model request first, then run /acp)\", \"warning\");\n }\n return;\n }\n const panel = typeof status.panel === \"string\" ? status.panel : undefined;\n const text = panel ?? renderAcpStatus(status);\n // Persistent transcript output (TUI + web hosts like pi-web).\n // The proxy strips this message from the model context by\n // content signature (src/acp-panel.ts), so it never reaches\n // the LLM; notify() is the fallback for hosts without\n // sendMessage (older pi).\n if (typeof pi.sendMessage === \"function\") {\n try {\n pi.sendMessage({ customType: \"bili-acp-status\", content: text, display: true });\n return;\n } catch (err) {\n console.error(`bili-plugin(${agent}): sendMessage failed (${err instanceof Error ? err.message : String(err)}) — falling back to notify`);\n }\n }\n notify(text, \"info\");\n },\n });\n }\n pi.on(\"before_provider_headers\", (event, ctx) => {\n try {\n if (proxyBaseForCtx(ctx) === undefined) return;\n const headers = (event as unknown as { headers?: Record<string, string> }).headers;\n if (headers === undefined || typeof headers !== \"object\" || Array.isArray(headers)) return;\n // The x-bili-plugin marker tells the proxy \"the client owns the\n // ACP tools natively — skip wire-level injection\". Stamping it\n // before registerTools() finishes would send round 1 out with\n // NO ACP tools (the first provider request races the manifest\n // fetch). Claim ownership only once tools are registered;\n // until then the request rides the proxy's wire mode. A\n // permanently failing manifest fetch keeps us in wire mode —\n // a graceful fallback rather than a tool-less session.\n if (state.toolsReady === true) {\n const sid = sessionIdOf(ctx);\n if (sid !== undefined) headers[\"x-bili-plugin-conversation\"] = sid;\n headers[\"x-bili-plugin\"] = agent;\n const window = ctx.model?.contextWindow;\n if (typeof window === \"number\" && Number.isFinite(window) && window > 0) {\n headers[\"x-bili-plugin-context-window\"] = String(Math.floor(window));\n }\n }\n } catch (err) {\n console.error(`bili-plugin(${agent}): header stamp skipped (${err instanceof Error ? err.message : String(err)})`);\n }\n void registerTools(pi, ctx, state, agent).catch((err: unknown) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));\n });\n pi.on(\"before_provider_request\", (event, ctx) => {\n // omp emits this per model request (but never before_provider_headers);\n // it doubles as the retry driver when the session_start manifest\n // fetch raced the proxy startup. Cached by sid, throttled by retryAt.\n void registerTools(pi, ctx, state, agent).catch((err: unknown) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));\n return stampPromptCacheKey(event, ctx, agent);\n });\n pi.on(\"session_start\", (_event, ctx) => {\n state.sid = undefined;\n void registerTools(pi, ctx, state, agent).catch((err: unknown) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));\n });\n // omp fires session_compact on in-session native compaction (sid does\n // not rotate), so the proxy reuses stale state — notify it to archive\n // the now-unreachable blocks (#395). Fire-and-forget: a failed\n // notification must never break the agent's compaction.\n pi.on(\"session_compact\", (_event, ctx) => {\n const proxyBase = proxyBaseForCtx(ctx);\n if (proxyBase === undefined) return;\n const sid = sessionIdOf(ctx);\n if (sid === undefined || sid.length === 0) return;\n fetch(`${proxyBase}/__bili/plugin/compact`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId: sid }),\n signal: AbortSignal.timeout(5000),\n }).catch(() => {});\n });\n };\n}\n\nexport default createBiliPlugin();\n\nexport { fetchStatus };\n","import { createBiliPlugin } from \"./pi.js\";\n\nexport default createBiliPlugin(\"omp\");\n"],"mappings":"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","names":["data","tools"]}
|
|
1
|
+
{"version":3,"sources":["../../src/agent/shared.ts","../../src/agent/pi.ts","../../src/agent/omp.ts"],"sourcesContent":["// Shared thin-plugin core for agent-side extensions (\"内外呼应\", issue #1).\n// The agent plugin is a PURE PROTOCOL CLIENT: no acp-kernel import, no\n// compression logic. The proxy stays the single compression authority; the\n// plugin only (1) detects the proxy, (2) fetches the tool manifest (single\n// source of truth), (3) forwards tool executes, (4) reads status. Same\n// package as the proxy ⇒ same version ⇒ no kernel-skew bug class.\n\nexport type ManifestTool = {\n name: string;\n description?: string;\n inputSchema: unknown;\n};\n\nconst MANIFEST_TIMEOUT_MS = 5000;\nconst TOOL_TIMEOUT_MS = 60000;\nconst STATUS_TIMEOUT_MS = 5000;\n\n/** Detect the proxy from a provider baseUrl's `/bili/` zero-config prefix.\n * The real prefix embeds the full upstream URL (`/bili/https://…`), so the\n * check requires `bili` as the first path segment followed by an http(s)\n * URL — a plain `/foo/bili/` path segment is NOT a bili proxy.\n * Returns the proxy origin (scheme//host) the request will actually hit. */\nexport function proxyBaseFromUrl(baseUrl: string | undefined): string | undefined {\n if (!baseUrl) return undefined;\n try {\n const url = new URL(baseUrl);\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return undefined;\n const segments = url.pathname.split(\"/\").filter((s) => s.length > 0);\n if (segments[0] !== \"bili\") return undefined;\n const rest = url.pathname.slice(url.pathname.indexOf(\"bili\") + \"bili\".length);\n if (!/^\\/https?:\\/\\//.test(rest)) return undefined;\n return `${url.protocol}//${url.host}`;\n } catch {\n return undefined;\n }\n}\n\n/** MITM transparent mode has no `/bili/` prefix; the proxy's launcher exports\n * BILLION_CONTEXT_PROXY. A stale value surfaces as a tool-forward error. */\nexport function proxyBaseFromEnv(): string | undefined {\n const raw = process.env.BILLION_CONTEXT_PROXY?.trim();\n if (!raw) return undefined;\n try {\n const url = new URL(raw);\n return url.protocol === \"http:\" || url.protocol === \"https:\" ? `${url.protocol}//${url.host}` : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function detectProxyBase(baseUrl: string | undefined): string | undefined {\n if (process.env.BILLION_CONTEXT_PLUGIN === \"0\") return undefined;\n return proxyBaseFromUrl(baseUrl) ?? proxyBaseFromEnv();\n}\n\nasync function fetchJson(url: string, init: RequestInit | undefined, timeoutMs: number, externalSignal?: AbortSignal): Promise<{ ok: boolean; status: number; json: unknown }> {\n const ac = new AbortController();\n // An already-aborted external signal never fires its \"abort\" event, so\n // forward the state directly — otherwise only the timeout could stop\n // the request, turning an instant cancel into a timeout wait.\n if (externalSignal?.aborted) ac.abort();\n const timer = setTimeout(() => ac.abort(), timeoutMs);\n const onExternalAbort = () => ac.abort();\n externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n try {\n let res: Response;\n try {\n res = await fetch(url, { ...init, signal: ac.signal });\n } catch (err) {\n if (ac.signal.aborted && !externalSignal?.aborted) throw new Error(`timeout after ${timeoutMs}ms: ${url}`);\n throw err;\n }\n const text = await res.text();\n let json: unknown = undefined;\n try {\n json = JSON.parse(text);\n } catch {\n json = undefined;\n }\n return { ok: res.ok, status: res.status, json };\n } finally {\n clearTimeout(timer);\n externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n }\n}\n\nexport async function fetchManifest(proxyBase: string, format: \"anthropic\" | \"openai\" = \"anthropic\"): Promise<ManifestTool[]> {\n const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/manifest`, undefined, MANIFEST_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") throw new Error(`manifest fetch failed: ${status}`);\n if (format === \"openai\") {\n // OpenAI function style: {name, description, parameters} (plain JSON Schema).\n const data = json as { tools?: { openai?: { name?: string; description?: string; parameters?: unknown }[] } };\n const tools = (data.tools?.openai ?? []).filter((t): t is { name: string; description?: string; parameters?: unknown } => typeof t.name === \"string\");\n if (tools.length === 0) throw new Error(\"manifest served no openai tools\");\n return tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.parameters ?? { type: \"object\", properties: {} } }));\n }\n const data = json as { tools?: { anthropic?: { name?: string; description?: string; input_schema?: unknown }[] } };\n const tools = (data.tools?.anthropic ?? []).filter((t): t is { name: string; description?: string; input_schema?: unknown } => typeof t.name === \"string\");\n if (tools.length === 0) throw new Error(\"manifest served no anthropic tools\");\n return tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.input_schema ?? { type: \"object\", properties: {} } }));\n}\n\nconst COMPACT_TIMEOUT_MS = 5000;\n\n/** Report a host-native compaction boundary to the proxy archive (#395):\n * hosts that compact natively (opencode) cannot cancel it, so the proxy\n * marks the boundary and archives unreachable blocks. Fire-and-forget at\n * call sites — a missed report degrades to stale blocks, not broken turns. */\nexport async function reportCompactionBoundary(proxyBase: string, conversationId: string): Promise<void> {\n await fetchJson(`${proxyBase}/__bili/plugin/compact`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId }),\n }, COMPACT_TIMEOUT_MS);\n}\n\nexport async function forwardTool(proxyBase: string, conversationId: string, tool: string, args: unknown, signal?: AbortSignal): Promise<string> {\n const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/tool`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId, tool, args: args ?? {} }),\n }, TOOL_TIMEOUT_MS, signal);\n const data = json as { ok?: boolean; result?: string; error?: string } | undefined;\n if (!ok || !data?.ok) {\n throw new Error(`bili proxy tool ${tool} failed (${status}): ${data?.error ?? \"unknown error\"}`);\n }\n return data.result ?? \"\";\n}\n\n/** Soft-fail by design: the status read is best-effort UI data; undefined\n * means \"no data\" whether the proxy is down or the session is unknown. */\nexport async function fetchStatus(proxyBase: string, conversationId: string): Promise<Record<string, unknown> | undefined> {\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/status?conversationId=${encodeURIComponent(conversationId)}`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n return json as Record<string, unknown>;\n}\n\n/** Wire-mode status read (dsh): those clients carry no per-conversation id\n * the proxy could bind, so ask for the most recently active session instead\n * (fallback=latest). Same soft-fail contract as fetchStatus. */\nexport async function fetchStatusLatest(proxyBase: string): Promise<Record<string, unknown> | undefined> {\n // conversationId must be non-empty (server rejects the empty string), but\n // any unknown id is fine: fallback=latest then resolves the most recently\n // active session.\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/status?conversationId=dsh&fallback=latest`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n return json as Record<string, unknown>;\n}\n\n/** Liveness + version probe for status UIs: same loopback origin as the\n * status endpoint, so a 404 status + a live manifest means \"proxy up,\n * conversation not seen yet\" — an armed-but-idle state, not an error. */\nexport async function fetchProxyVersion(proxyBase: string): Promise<string | undefined> {\n const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/manifest`, undefined, STATUS_TIMEOUT_MS);\n if (!ok || !json || typeof json !== \"object\") return undefined;\n const version = (json as { version?: unknown }).version;\n return typeof version === \"string\" && version.length > 0 ? version : undefined;\n}\n\n// Model-facing body for /acp status renders: the visible panel goes to the synthetic message's\n// description (TUI Notice cap ~1KB); this inert one-liner keeps the model context clean.\nexport const V2_SYNTHETIC_TEXT = \"[bili-acp-status] ACP status panel was displayed in your terminal; this line is not an instruction.\";\n","// Thin agent extension for pi and omp (\"内外呼应\", issue #1). Loaded by pi\n// via the package.json `pi` manifest (dist/agent/pi.js) or by omp via the\n// config.yml `extensions:` list (dist/agent/omp.js). pi and omp share the\n// ExtensionFactory API shape, so one factory serves both; types below are\n// minimal structural declarations — the bundled artifact imports NOTHING\n// from the host at runtime (the host duck-types us in).\n\nimport { detectProxyBase, fetchManifest, forwardTool, fetchStatus, fetchProxyVersion, type ManifestTool } from \"./shared.js\";\n\ntype Ctx = {\n sessionManager?: { getSessionId?: () => string } | undefined;\n model?: { contextWindow?: number; baseUrl?: string; provider?: string; id?: string; [key: string]: unknown } | undefined;\n cwd?: string;\n};\n\ntype TextBlock = { type: \"text\"; text: string };\ntype ToolResult = { content: TextBlock[]; isError?: boolean };\n\ntype ToolDefinition = {\n name: string;\n description?: string;\n parameters: unknown;\n // omp 17.x mounts extension tools that omit loadMode under xd:// devices\n // (invisible to the main turn's tools array — only title requests see\n // them). Declaring \"essential\" keeps ACP tools top-level; pi upstream\n // ignores the field.\n loadMode?: string;\n execute: (toolCallId: string, params: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: ((u: unknown) => void) | undefined, ctx: Ctx) => Promise<ToolResult>;\n};\n\ntype CommandCtx = {\n sessionManager?: { getSessionId?: () => string } | undefined;\n model?: { contextWindow?: number; baseUrl?: string } | undefined;\n ui?: { notify?: (message: string, type?: string) => void } | undefined;\n};\n\ntype ExtensionAPI = {\n on: (event: string, handler: (event: never, ctx: Ctx) => unknown) => void;\n registerTool: (tool: ToolDefinition) => void;\n registerCommand?: (name: string, options: { description?: string; handler: (args: string, ctx: CommandCtx) => void | Promise<void> }) => void;\n // #535: launcher passes provider URL rewrites via env; the extension\n // overrides each provider's baseUrl at load (file-free routing — no\n // models.json overlay). Optional because older hosts may lack it.\n registerProvider?: (name: string, config: { baseUrl: string }) => void;\n // #535 omp-only: omp pins the session's Model object from the static\n // catalog BEFORE extensions load, and its registerProvider — unlike\n // pi's _refreshCurrentModelFromRegistry — never re-resolves the live\n // session model, so the extension must re-pin it via setModel (see the\n // session_start handler below). Optional because pi hosts lack it.\n setModel?: (model: Record<string, unknown> & { baseUrl?: string }) => Promise<boolean | void> | boolean | void;\n // Persistent transcript output (rendered by TUI and web hosts like\n // pi-web); notify() is a transient toast — only the fallback for hosts\n // without sendMessage (issue #359).\n sendMessage?: (message: { customType: string; content: string; display: boolean }) => void;\n};\n\nfunction agentName(override: string | undefined): string {\n if (override) return override;\n return process.env.BILLION_CONTEXT_PLUGIN_AGENT === \"omp\" ? \"omp\" : \"pi\";\n}\n\nfunction proxyBaseForCtx(ctx: Ctx | undefined): string | undefined {\n return detectProxyBase(ctx?.model?.baseUrl);\n}\n\nfunction sessionIdOf(ctx: Ctx): string | undefined {\n try {\n const sid = ctx.sessionManager?.getSessionId?.();\n return typeof sid === \"string\" ? sid : undefined;\n } catch {\n return undefined;\n }\n}\n\n// omp's chat-completions payloads carry NO conversation signal (no\n// prompt_cache_key / session / user, and no session header — verified by dump),\n// so the proxy's openai identity falls to a content fingerprint that never\n// matches the session id this plugin registered (the identity register is keyed\n// by the omp session uuid). The before_provider_request return value REPLACES\n// the whole outgoing payload (omp onPayload chain, verified in the omp 17.3.8\n// dist), so stamp prompt_cache_key with the omp session id: the proxy binds\n// pluginMode by that identity and /acp finds the session by it.\n// Chat shape = messages array, no responses `input`, no native prompt_cache_key.\n// max_tokens is NOT a discriminator: omp's openai-compat providers send it in\n// every chat-completions body (maxTokensField:\"max_tokens\") exactly like the\n// anthropic wire — excluding it meant the target shape was never stamped\n// (#268). The anthropic wire gets stamped too: the proxy records the mapping\n// from the body pck there as well and strips the field before forwarding to\n// the real Anthropic. pi is untouched (it stamps x-bili-plugin-conversation in\n// before_provider_headers, which outranks the body field).\nfunction stampPromptCacheKey(event: unknown, ctx: Ctx, agent: string): Record<string, unknown> | undefined {\n if (agent !== \"omp\") return undefined;\n const payload = (event as { payload?: unknown } | undefined)?.payload;\n if (payload === null || typeof payload !== \"object\" || Array.isArray(payload)) return undefined;\n const p = payload as Record<string, unknown>;\n if (!Array.isArray(p.messages)) return undefined;\n if (p.input !== undefined) return undefined;\n if (typeof p.prompt_cache_key === \"string\" && p.prompt_cache_key.trim().length > 0) return undefined;\n const sid = sessionIdOf(ctx);\n if (sid === undefined || sid.length === 0) return undefined;\n return { ...p, prompt_cache_key: sid };\n}\n\nfunction fmtTok(n: number): string {\n if (n < 1000) return String(n);\n if (n < 1_000_000) return `${(n / 1000).toFixed(1)}K`;\n return `${(n / 1_000_000).toFixed(2)}M`;\n}\n\nfunction renderAcpStatus(s: Record<string, unknown>): string {\n const num = (v: unknown): number | null => (typeof v === \"number\" && Number.isFinite(v) ? v : null);\n const contextTokens = num(s.contextTokens);\n const contextLimit = num(s.contextLimit);\n const inputTokens = num(s.inputTokens);\n const outputTokens = num(s.outputTokens);\n const cachedTokens = num(s.cachedTokens);\n const requests = num(s.requests);\n const blocks = Array.isArray(s.blocks) ? (s.blocks as Array<{ tier?: number; active?: boolean }>) : [];\n const activeBlocks = blocks.filter((b) => b.active === true).length;\n const lines: string[] = [\"📊 ACP status\"];\n if (contextTokens !== null) {\n const pct = contextLimit !== null && contextLimit > 0 ? ` (${((contextTokens / contextLimit) * 100).toFixed(1)}%)` : \"\";\n lines.push(` context: ${fmtTok(contextTokens)}${contextLimit !== null ? ` / ${fmtTok(contextLimit)}` : \"\"}${pct}`);\n }\n const hostCredit = num(s.hostCredit);\n if (hostCredit !== null && hostCredit > 0) {\n lines.push(` host baseline: uncompressed (proxy backfilled +${fmtTok(hostCredit)} tok)`);\n }\n if (inputTokens !== null || outputTokens !== null || cachedTokens !== null) {\n lines.push(` in/out/cached: ${fmtTok(inputTokens ?? 0)} / ${fmtTok(outputTokens ?? 0)} / ${fmtTok(cachedTokens ?? 0)}`);\n }\n if (requests !== null) lines.push(` requests: ${requests}`);\n if (blocks.length > 0) lines.push(` blocks: ${blocks.length} (${activeBlocks} active)`);\n return lines.join(\"\\n\");\n}\n\nfunction manifestToTool(proxyBase: string, tool: ManifestTool, agent: string): ToolDefinition {\n return {\n name: tool.name,\n description: tool.description,\n parameters: tool.inputSchema,\n loadMode: \"essential\",\n execute: async (_toolCallId, params, signal, _onUpdate, ctx) => {\n const conversationId = sessionIdOf(ctx) ?? \"unknown\";\n try {\n const output = await forwardTool(proxyBase, conversationId, tool.name, params, signal);\n return { content: [{ type: \"text\", text: output }] };\n } catch (err) {\n return { content: [{ type: \"text\", text: `bili tool error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };\n }\n },\n };\n}\n\nfunction parseProviderRewrites(env: NodeJS.ProcessEnv): Record<string, string> | undefined {\n const raw = env.BILI_PROVIDER_REWRITES;\n if (raw === undefined || raw.trim().length === 0) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n console.error(\"bili-plugin: BILI_PROVIDER_REWRITES is not valid JSON — provider URLs left untouched\");\n return undefined;\n }\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined;\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof value !== \"string\" || !/^https?:\\/\\//i.test(value)) continue;\n out[key] = value;\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\nconst RETRY_INTERVAL_MS = 10000;\n\ntype RegisterState = { sid?: string; toolsFor?: string; toolsReady?: boolean; pending?: Promise<void>; retryAt?: number; identityAt?: string; retryIntervalMs: number };\n\n// omp never emits before_provider_headers, so the x-bili-plugin marker cannot\n// be stamped per request. Register the conversation id once (after tools are\n// ready): the proxy binds any request carrying that id into plugin mode —\n// same launcher path claude/codex use (#162).\nasync function postIdentityRegister(proxyBase: string, conversationId: string, agent: string): Promise<void> {\n const res = await fetch(`${proxyBase}/__bili/plugin/register`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId, agent, identity: true }),\n signal: AbortSignal.timeout(5000),\n });\n if (!res.ok) throw new Error(`register HTTP ${res.status}`);\n}\n\nasync function registerTools(pi: ExtensionAPI, ctx: Ctx, state: RegisterState, agent: string): Promise<void> {\n const proxyBase = proxyBaseForCtx(ctx);\n if (proxyBase === undefined) return;\n // Cache on the session id; \"\" (host has no sessionManager) still caches,\n // so a successful registration is not re-fetched on every provider\n // request — the manifest is session-independent anyway.\n const sid = sessionIdOf(ctx) ?? \"\";\n if (sid === state.sid) return;\n if (state.pending !== undefined) return state.pending;\n if (state.retryAt !== undefined && Date.now() < state.retryAt) return;\n const wait = state.retryIntervalMs;\n state.pending = (async () => {\n let tools: ManifestTool[];\n try {\n tools = await fetchManifest(proxyBase);\n } catch (err) {\n state.retryAt = Date.now() + wait;\n console.error(`bili-plugin(${agent}): manifest fetch failed: ${err instanceof Error ? err.message : String(err)} — retrying in ${wait / 1000}s`);\n return;\n }\n try {\n // toolsFor (not sid) guards the register loop: a retry after a\n // failed identity register re-fetches the manifest but must NOT\n // re-register the tools (the host may not dedupe by name).\n if (state.toolsFor !== sid) {\n for (const t of tools) pi.registerTool(manifestToTool(proxyBase, t, agent));\n state.toolsFor = sid;\n }\n state.toolsReady = true;\n state.retryAt = undefined;\n if (agent === \"omp\" && sid !== \"\" && state.identityAt !== sid) {\n try {\n await postIdentityRegister(proxyBase, sid, agent);\n state.identityAt = sid;\n } catch (err) {\n // Leave state.sid UNSET so the next per-request event\n // re-enters (throttled by retryAt) and retries ONLY the\n // register — setting sid here would wedge the session in\n // wire mode forever (the early return above blocks every\n // retry).\n state.retryAt = Date.now() + wait;\n console.error(`bili-plugin(${agent}): identity register failed (${err instanceof Error ? err.message : String(err)}) — retrying in ${wait / 1000}s`);\n return;\n }\n }\n state.sid = sid;\n } catch (err) {\n state.sid = undefined;\n state.toolsFor = undefined;\n state.retryAt = Date.now() + wait;\n console.error(`bili-plugin(${agent}): tool registration deferred (${err instanceof Error ? err.message : String(err)}) — retrying in ${wait / 1000}s`);\n }\n })();\n try {\n await state.pending;\n } finally {\n state.pending = undefined;\n }\n}\n\nexport function createBiliPlugin(agentOverride?: string, opts?: { retryIntervalMs?: number }): (pi: ExtensionAPI) => void {\n return function biliPlugin(pi: ExtensionAPI): void {\n const agent = agentName(agentOverride);\n const state: RegisterState = { retryIntervalMs: opts?.retryIntervalMs ?? RETRY_INTERVAL_MS };\n // #535: file-free routing — override provider baseUrls at load from\n // the launcher-passed manifest (see buildPiEnv). registerProvider is\n // queued during initial extension load and applied before any model\n // traffic, so every request (including round 1) rides the proxy.\n const rewrites = parseProviderRewrites(process.env);\n if (rewrites !== undefined && typeof pi.registerProvider !== \"function\") {\n console.error(\n \"bili-plugin: BILI_PROVIDER_REWRITES is set but this pi build has no registerProvider API — \" +\n \"provider traffic goes DIRECT (uncompressed). Update pi, or reinstall the bili plugin: `bili plugin install pi`.\",\n );\n }\n if (rewrites !== undefined && typeof pi.registerProvider === \"function\") {\n for (const [key, url] of Object.entries(rewrites)) {\n try {\n pi.registerProvider(key, { baseUrl: url });\n } catch (err) {\n console.error(`bili-plugin: registerProvider(${key}) failed: ${err instanceof Error ? err.message : String(err)} — traffic for this provider goes direct`);\n }\n }\n }\n // #535: cancel the host's NATIVE compaction so its summarizer never\n // fires alongside bili's ACP compression — the in-extension\n // replacement for the old compaction-off config injection. pi's event\n // carries `reason`: cancel only threshold + overflow so manual\n // /compact stays user-owned. omp (#851): session_before_compact\n // carries NO reason field, so at hook level manual compaction\n // (/compact, plan-mode \"Approve and compact context\") is\n // indistinguishable from auto — but every auto pass announces itself\n // first via auto_compaction_start (reason threshold|overflow|idle|\n // incomplete), which omp emits (awaited) before the hook fires;\n // manual paths never do. Track the announcement: announced passes\n // stay cancelled, unannounced ones are left user-owned. A surviving\n // native compaction is safe: the proxy archives the unreachable\n // blocks on session_compact (#395).\n // Whether we own compression is decided at EVENT time, not load time:\n // in native mode (#519) the proxy origin lands in\n // BILLION_CONTEXT_PROXY only after the async bootstrap finishes, so a\n // load-time check would leave the cancel disarmed for the whole\n // session. Plain pi/omp with the plugin installed but NO reachable\n // proxy (incl. a failed bootstrap) stays fully native.\n if (agent === \"pi\" || agent === \"omp\") {\n if (agent === \"pi\") {\n pi.on(\"session_before_compact\", (event, ctx) => {\n if (proxyBaseForCtx(ctx) === undefined) return undefined;\n const reason = (event as unknown as { reason?: unknown }).reason;\n if (reason === \"threshold\" || reason === \"overflow\") return { cancel: true };\n return undefined;\n });\n } else {\n let autoPending = false;\n pi.on(\"auto_compaction_start\", () => {\n autoPending = true;\n });\n pi.on(\"auto_compaction_end\", () => {\n autoPending = false;\n });\n pi.on(\"session_before_compact\", (event, ctx) => {\n if (proxyBaseForCtx(ctx) === undefined) return undefined;\n if (!autoPending) return undefined;\n autoPending = false;\n return { cancel: true };\n });\n }\n }\n // #535 omp-only: omp resolves modelRoles.default into options.model\n // from the PRE-extension static catalog (main.ts: \"scope is resolved\n // before extensions register their providers\"), and omp's fork lacks\n // pi's registerProvider → _refreshCurrentModelFromRegistry hop — the\n // registry gets the rewritten baseUrl but the live session keeps the\n // direct one, so every request bypasses the proxy (fetch trace →\n // http://127.0.0.1:8197/v1/responses with zero proxy forwards). Re-pin\n // the session model at load + on every session switch: spread the\n // current model with the rewritten baseUrl through the host setModel\n // (keyed-provider-gated; local providers carry dummy keys). Mid-session\n // /model picks resolve from the already-overridden registry, so only\n // session start/restore need this.\n if (agent === \"omp\" && rewrites !== undefined && typeof pi.setModel === \"function\") {\n const repin = async (ctx: Ctx): Promise<void> => {\n const model = ctx?.model;\n if (model === null || typeof model !== \"object\") return;\n const provider = model.provider;\n if (typeof provider !== \"string\" || provider === \"\") return;\n const rewritten = rewrites[provider];\n if (rewritten === undefined || model.baseUrl === rewritten) return;\n try {\n const switched = await pi.setModel?.({ ...model, baseUrl: rewritten });\n if (switched === false) {\n console.error(`bili-plugin: omp setModel(${provider}/${String(model.id)}) rejected (no API key) — traffic for this provider goes direct`);\n }\n } catch (err) {\n console.error(`bili-plugin: omp setModel failed: ${err instanceof Error ? err.message : String(err)} — traffic goes direct`);\n }\n };\n pi.on(\"session_start\", (_event, ctx) => repin(ctx));\n pi.on(\"session_switch\", (_event, ctx) => repin(ctx));\n }\n if (typeof pi.registerCommand === \"function\") {\n pi.registerCommand(\"acp\", {\n description: \"Show ACP context-compression status for this session\",\n handler: async (_args, ctx) => {\n const notify = (message: string, type?: string): void => {\n try {\n ctx.ui?.notify?.(message, type);\n } catch {\n // host UI unavailable — the command is best-effort\n }\n };\n const proxyBase = detectProxyBase(ctx.model?.baseUrl);\n if (proxyBase === undefined) {\n // #788: neutral wording — the plugin also loads under plain\n // pi/omp launches where the user never intended proxy mode\n // (e.g. they use billion-context-pi in-process instead), so\n // offer both exits instead of assuming proxy intent.\n const removeHint = agent === \"pi\"\n ? \", or remove this plugin (`bili plugin remove pi`) if you use billion-context-pi or don't want a proxy\"\n : agent === \"omp\"\n ? \", or remove this plugin (`bili plugin remove omp`) if you don't want a proxy\"\n : \"\";\n notify(`bili: no proxy detected — run via \\`bili ${agent}\\` (or set a /bili/ baseURL) to use proxy mode${removeHint}`, \"warning\");\n return;\n }\n const conversationId = sessionIdOf(ctx) ?? \"unknown\";\n let status: Record<string, unknown> | undefined;\n try {\n status = await fetchStatus(proxyBase, conversationId);\n } catch (err) {\n notify(`bili: status fetch failed: ${err instanceof Error ? err.message : String(err)}`, \"error\");\n return;\n }\n if (status === undefined) {\n // 404 from a live proxy = this conversation has sent no\n // model request yet (e.g. /acp right after startup).\n // Probe the manifest to confirm liveness + version and\n // show an armed/idle notice instead of a scary warning.\n let version: string | undefined;\n try {\n version = await fetchProxyVersion(proxyBase);\n } catch {\n version = undefined;\n }\n if (version !== undefined) {\n notify(\n `billion-context@${version} — proxy connected, compression armed. No model request in this conversation yet; send one, then run /acp again.`,\n \"info\",\n );\n } else {\n notify(\"bili: no ACP session yet (send a model request first, then run /acp)\", \"warning\");\n }\n return;\n }\n const panel = typeof status.panel === \"string\" ? status.panel : undefined;\n const text = panel ?? renderAcpStatus(status);\n // Persistent transcript output (TUI + web hosts like pi-web).\n // The proxy strips this message from the model context by\n // content signature (src/acp-panel.ts), so it never reaches\n // the LLM; notify() is the fallback for hosts without\n // sendMessage (older pi).\n if (typeof pi.sendMessage === \"function\") {\n try {\n pi.sendMessage({ customType: \"bili-acp-status\", content: text, display: true });\n return;\n } catch (err) {\n console.error(`bili-plugin(${agent}): sendMessage failed (${err instanceof Error ? err.message : String(err)}) — falling back to notify`);\n }\n }\n notify(text, \"info\");\n },\n });\n }\n pi.on(\"before_provider_headers\", (event, ctx) => {\n try {\n if (proxyBaseForCtx(ctx) === undefined) return;\n const headers = (event as unknown as { headers?: Record<string, string> }).headers;\n if (headers === undefined || typeof headers !== \"object\" || Array.isArray(headers)) return;\n // The x-bili-plugin marker tells the proxy \"the client owns the\n // ACP tools natively — skip wire-level injection\". Stamping it\n // before registerTools() finishes would send round 1 out with\n // NO ACP tools (the first provider request races the manifest\n // fetch). Claim ownership only once tools are registered;\n // until then the request rides the proxy's wire mode. A\n // permanently failing manifest fetch keeps us in wire mode —\n // a graceful fallback rather than a tool-less session.\n if (state.toolsReady === true) {\n const sid = sessionIdOf(ctx);\n if (sid !== undefined) headers[\"x-bili-plugin-conversation\"] = sid;\n headers[\"x-bili-plugin\"] = agent;\n const window = ctx.model?.contextWindow;\n if (typeof window === \"number\" && Number.isFinite(window) && window > 0) {\n headers[\"x-bili-plugin-context-window\"] = String(Math.floor(window));\n }\n }\n } catch (err) {\n console.error(`bili-plugin(${agent}): header stamp skipped (${err instanceof Error ? err.message : String(err)})`);\n }\n void registerTools(pi, ctx, state, agent).catch((err: unknown) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));\n });\n pi.on(\"before_provider_request\", (event, ctx) => {\n // omp emits this per model request (but never before_provider_headers);\n // it doubles as the retry driver when the session_start manifest\n // fetch raced the proxy startup. Cached by sid, throttled by retryAt.\n void registerTools(pi, ctx, state, agent).catch((err: unknown) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));\n return stampPromptCacheKey(event, ctx, agent);\n });\n pi.on(\"session_start\", (_event, ctx) => {\n state.sid = undefined;\n void registerTools(pi, ctx, state, agent).catch((err: unknown) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));\n });\n // omp fires session_compact on in-session native compaction (sid does\n // not rotate), so the proxy reuses stale state — notify it to archive\n // the now-unreachable blocks (#395). Fire-and-forget: a failed\n // notification must never break the agent's compaction.\n pi.on(\"session_compact\", (_event, ctx) => {\n const proxyBase = proxyBaseForCtx(ctx);\n if (proxyBase === undefined) return;\n const sid = sessionIdOf(ctx);\n if (sid === undefined || sid.length === 0) return;\n fetch(`${proxyBase}/__bili/plugin/compact`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ conversationId: sid }),\n signal: AbortSignal.timeout(5000),\n }).catch(() => {});\n });\n };\n}\n\nexport default createBiliPlugin();\n\nexport { fetchStatus };\n","import { createBiliPlugin } from \"./pi.js\";\n\nexport default createBiliPlugin(\"omp\");\n"],"mappings":"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","names":["data","tools"]}
|