billion-context 0.1.87 → 0.1.88-pr.592.414
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 +48 -3
- package/README.zh-CN.md +5 -1
- package/dist/agent/omp.js +4 -0
- package/dist/agent/omp.js.map +1 -1
- package/dist/agent/pi.js +4 -0
- package/dist/agent/pi.js.map +1 -1
- package/dist/index.js +1219 -516
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ The proxy runs in one of two modes, and **the mode decides who executes
|
|
|
61
61
|
| Client | ACP-native agent with the bili extension (pi/omp) | Any OpenAI/Anthropic client, no extension |
|
|
62
62
|
| Who executes `compress` | **The agent** (pi runs it locally) | **The proxy** (server-side compress loop) |
|
|
63
63
|
| `compress` tool call in the re-sent history? | Yes — part of the agent's own conversation | No — ephemeral proxy-loop traffic |
|
|
64
|
-
| Preflight blocks (no tool call)? |
|
|
64
|
+
| Preflight blocks (no tool call)? | Last-resort backstop — the agent normally compresses on its own `compress` calls, but `src/preflight.ts` still fires (in both modes) when the input alone exceeds the window (#470) | Yes — `src/preflight.ts` compresses behind the client's back |
|
|
65
65
|
| **Summary carrier on the wire** | **the `compress` tool call** | **an `acp_summary` user message** |
|
|
66
66
|
| System messages on the wire | always exactly 1 (client + prompt) | always exactly 1 (client + prompt) — summaries ride on user messages |
|
|
67
67
|
| SGLang "single system" 400 (#377) | cannot happen | cannot happen (summaries are user messages, not system) |
|
|
@@ -140,6 +140,21 @@ Two ways to use it — pick one:
|
|
|
140
140
|
- **URL change (persistent):** prefix your client's baseURL with the proxy
|
|
141
141
|
origin + `/bili/`.
|
|
142
142
|
|
|
143
|
+
### Injection priority — no files unless unavoidable (#535)
|
|
144
|
+
|
|
145
|
+
bili never owns user data: every launched client runs on its **real home**, so
|
|
146
|
+
runtime writes land where the user expects them. When pointing a client at the
|
|
147
|
+
proxy, the launcher picks by priority — **env vars first** (proxy/CA envs for
|
|
148
|
+
hermes/dsh/codex; the `BILI_PROVIDER_REWRITES` URL manifest for pi/omp,
|
|
149
|
+
consumed by their extension's `registerProvider` at load), then **CLI flags or
|
|
150
|
+
extension APIs** (codex `-c key=value`, opencode plugin), and **generated files
|
|
151
|
+
last** — today only opencode's temp `opencode.json` (deleted on exit) and dsh's
|
|
152
|
+
loopback exception: dsh's fetch stack bypasses proxy envs for loopback targets
|
|
153
|
+
unconditionally, so local upstreams keep the persistent `~/.dsh-bili` overlay
|
|
154
|
+
rewrite until dsh gains a settings-path env or an upstream loopback opt-out.
|
|
155
|
+
Overlay dirs created by older versions are left in place and never merged back
|
|
156
|
+
into the real home.
|
|
157
|
+
|
|
143
158
|
### Option 1 — Launcher (`bili pi` / `bili codex` / `bili claude` / `bili omp` / `bili opencode` / `bili hermes` / `bili dsh`)
|
|
144
159
|
|
|
145
160
|
The launcher wraps a client in one command: it starts a proxy on an
|
|
@@ -158,7 +173,7 @@ bili claude # launch claude through the proxy
|
|
|
158
173
|
bili omp # pi-style, file-free (#535): env + extension registerProvider + compaction cancel, real ~/.omp untouched
|
|
159
174
|
bili opencode # MITM for HTTPS + temp opencode.json (/bili/ for HTTP) + thin /acp plugin
|
|
160
175
|
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
|
|
161
|
-
bili dsh # deepseek-harness:
|
|
176
|
+
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
|
|
162
177
|
bili pi --mitm-domain api.foo.com # add a domain to the MITM whitelist
|
|
163
178
|
```
|
|
164
179
|
|
|
@@ -355,6 +370,31 @@ So you can give ZCode its own proxy without affecting API-key clients:
|
|
|
355
370
|
}
|
|
356
371
|
```
|
|
357
372
|
|
|
373
|
+
### Wire-compat role rewrite (`compat.roles`)
|
|
374
|
+
|
|
375
|
+
Some upstreams reject the `developer` role newer codex clients send on the
|
|
376
|
+
Responses API (`400 Invalid role: developer`). `compat.roles` maps roles to
|
|
377
|
+
what the upstream accepts — applied at the forward boundary to the final
|
|
378
|
+
`openai`/`responses` body (client-sent roles **and** bili's own injected
|
|
379
|
+
prompt alike), global or per-provider, default off = byte-for-byte:
|
|
380
|
+
|
|
381
|
+
```jsonc
|
|
382
|
+
{
|
|
383
|
+
"compat": { "roles": { "developer": "system" } },
|
|
384
|
+
"providers": {
|
|
385
|
+
"https://picky.example.com": { "compat": { "roles": { "developer": "user" } } }
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**No configuration needed for the common case.** When an upstream answers a
|
|
391
|
+
request with `400 Invalid role: …`, bili auto-rewrites the offending role to
|
|
392
|
+
`system`, retries the request once, and — if the retry succeeds — remembers
|
|
393
|
+
the mapping **for that session only** (nothing is written to your config).
|
|
394
|
+
Later requests in the session skip the 400 round-trip. The log line printed
|
|
395
|
+
when the auto-fix fires includes a copy-paste per-provider snippet if you
|
|
396
|
+
want the mapping permanently.
|
|
397
|
+
|
|
358
398
|
## How sessions work
|
|
359
399
|
|
|
360
400
|
The proxy needs a stable per-conversation identifier to isolate compression
|
|
@@ -393,7 +433,12 @@ pass an explicit `x-acp-session` header per conversation to avoid collisions.
|
|
|
393
433
|
|
|
394
434
|
The proxy persists each session's compression state to the sessions dir
|
|
395
435
|
(`%USERPROFILE%\.local\share\billion-context\` by default) and rewrites the
|
|
396
|
-
file every turn of a long session.
|
|
436
|
+
file every turn of a long session. Persisted per session: the compression
|
|
437
|
+
state (block summaries), the compressed originals cache (`blockContents`,
|
|
438
|
+
what `bili export --full` recovers), and a bounded folded-view snapshot of
|
|
439
|
+
the recent conversation (newest `BILI_PERSIST_TAIL_TOKENS` tokens, default
|
|
440
|
+
16k) — the raw full history is never duplicated on disk (#401). On
|
|
441
|
+
Windows, real-time antivirus (Windows
|
|
397
442
|
Defender), the search indexer, or a sync tool (OneDrive) can lock that
|
|
398
443
|
directory mid-write, so the rename fails with `EPERM` and every persist for
|
|
399
444
|
that session fails until the lock clears.
|
package/README.zh-CN.md
CHANGED
|
@@ -78,6 +78,10 @@ npm install -g billion-context
|
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
|
|
81
|
+
### 注入优先级 —— 能不写文件就不写(#535)
|
|
82
|
+
|
|
83
|
+
bili 永不拥有用户数据:每个被启动的客户端都跑在**真实 home** 上,运行期写入落在用户预期的位置。把客户端指向代理时,启动器按优先级选择——**优先 env 变量**(hermes/dsh/codex 的代理/CA env;pi/omp 的 `BILI_PROVIDER_REWRITES` URL 清单,由扩展加载时经 `registerProvider` 消费),其次 **CLI 参数或扩展 API**(codex `-c key=value`、opencode 插件),最后才是**生成文件**——目前仅剩 opencode 的临时 `opencode.json`(退出即删)和 dsh 的回环例外:dsh 的 fetch 栈对回环目标无条件绕过代理 env,所以本地上游保留持久 `~/.dsh-bili` overlay 改写,直到 dsh 提供 settings-path env 或上游支持回环 opt-out。旧版本创建的 overlay 目录原地保留,绝不合并回真实 home。
|
|
84
|
+
|
|
81
85
|
### 方式 1 —— 启动器(`bili pi` / `bili codex` / `bili claude` / `bili omp` / `bili opencode` / `bili hermes` / `bili dsh`)
|
|
82
86
|
|
|
83
87
|
启动器把客户端包进一条命令:在独立端口拉起一个代理(总是全新实例,绝不复用端口),再按客户端支持的机制把它指向代理 —— 能吃代理/CA 环境变量的走**证书 MITM**,不吃的走隔离的**`/bili/` 配置重写**。真实配置文件从不被修改;客户端自己的配置只被**读取**,用来发现它实际连接的 HTTPS 上游主机,把这些主机加入 MITM 白名单 —— 代理只 TLS 终结它们,其余流量盲透传。
|
|
@@ -89,7 +93,7 @@ bili claude # 拉起 claude
|
|
|
89
93
|
bili omp # pi 同款,file-free(#535):环境变量 + 扩展 registerProvider + 压缩取消,真实 ~/.omp 不动
|
|
90
94
|
bili opencode # HTTPS 走 MITM + 临时 opencode.json(HTTP 走 /bili/)+ 轻量 /acp 插件
|
|
91
95
|
bili hermes # file-free(#535):hermes 代理环境变量(HTTPS_PROXY + HERMES_CA_BUNDLE)—— https 走 CONNECT MITM,http 走绝对形式转发;真实 ~/.hermes 不动
|
|
92
|
-
bili dsh # deepseek-harness
|
|
96
|
+
bili dsh # deepseek-harness:非回环上游走代理 env(https MITM、http absolute-form),回环上游保留 overlay DSH_HOME(~/.dsh-bili)改写(#535),内置 deepseek 路由走 DEEPSEEK_BASE_URL,经 --patch 注入原生 /acp 命令
|
|
93
97
|
bili pi --mitm-domain api.foo.com # 向 MITM 白名单追加域名
|
|
94
98
|
```
|
|
95
99
|
|
package/dist/agent/omp.js
CHANGED
|
@@ -140,6 +140,10 @@ function renderAcpStatus(s) {
|
|
|
140
140
|
const pct = contextLimit !== null && contextLimit > 0 ? ` (${(contextTokens / contextLimit * 100).toFixed(1)}%)` : "";
|
|
141
141
|
lines.push(` context: ${fmtTok(contextTokens)}${contextLimit !== null ? ` / ${fmtTok(contextLimit)}` : ""}${pct}`);
|
|
142
142
|
}
|
|
143
|
+
const hostCredit = num(s.hostCredit);
|
|
144
|
+
if (hostCredit !== null && hostCredit > 0) {
|
|
145
|
+
lines.push(` host baseline: uncompressed (proxy backfilled +${fmtTok(hostCredit)} tok)`);
|
|
146
|
+
}
|
|
143
147
|
if (inputTokens !== null || outputTokens !== null || cachedTokens !== null) {
|
|
144
148
|
lines.push(` in/out/cached: ${fmtTok(inputTokens ?? 0)} / ${fmtTok(outputTokens ?? 0)} / ${fmtTok(cachedTokens ?? 0)}`);
|
|
145
149
|
}
|
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): 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 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\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 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's event has no reason field, so omp\n // cancels ALL compaction — under bili, manual native /compact is\n // equally harmful (the native summarizer would destroy the\n // ACP-tagged context), the host shows \"Compaction cancelled\", and\n // the user should reach for /acp instead. Only armed under `bili`\n // launch: plain pi/omp with the plugin installed stays fully native.\n if ((agent === \"pi\" || agent === \"omp\") && process.env.BILLION_CONTEXT_PROXY !== undefined) {\n pi.on(\"session_before_compact\", (event) => {\n if (agent === \"pi\") {\n const reason = (event as unknown as { reason?: unknown }).reason;\n if (reason === \"threshold\" || reason === \"overflow\") return { cancel: true };\n return undefined;\n }\n return { cancel: true };\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 notify(\"bili: no proxy detected (run via `bili <client>` or set a /bili/ baseURL)\", \"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":[]}
|
|
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): 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 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\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. pi's event\n // carries `reason`: cancel only threshold + overflow so manual\n // /compact stays user-owned. omp's event has no reason field, so omp\n // cancels ALL compaction — under bili, manual native /compact is\n // equally harmful (the native summarizer would destroy the\n // ACP-tagged context), the host shows \"Compaction cancelled\", and\n // the user should reach for /acp instead. Only armed under `bili`\n // launch: plain pi/omp with the plugin installed stays fully native.\n if ((agent === \"pi\" || agent === \"omp\") && process.env.BILLION_CONTEXT_PROXY !== undefined) {\n pi.on(\"session_before_compact\", (event) => {\n if (agent === \"pi\") {\n const reason = (event as unknown as { reason?: unknown }).reason;\n if (reason === \"threshold\" || reason === \"overflow\") return { cancel: true };\n return undefined;\n }\n return { cancel: true };\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 notify(\"bili: no proxy detected (run via `bili <client>` or set a /bili/ baseURL)\", \"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":[]}
|
package/dist/agent/pi.js
CHANGED
|
@@ -140,6 +140,10 @@ function renderAcpStatus(s) {
|
|
|
140
140
|
const pct = contextLimit !== null && contextLimit > 0 ? ` (${(contextTokens / contextLimit * 100).toFixed(1)}%)` : "";
|
|
141
141
|
lines.push(` context: ${fmtTok(contextTokens)}${contextLimit !== null ? ` / ${fmtTok(contextLimit)}` : ""}${pct}`);
|
|
142
142
|
}
|
|
143
|
+
const hostCredit = num(s.hostCredit);
|
|
144
|
+
if (hostCredit !== null && hostCredit > 0) {
|
|
145
|
+
lines.push(` host baseline: uncompressed (proxy backfilled +${fmtTok(hostCredit)} tok)`);
|
|
146
|
+
}
|
|
143
147
|
if (inputTokens !== null || outputTokens !== null || cachedTokens !== null) {
|
|
144
148
|
lines.push(` in/out/cached: ${fmtTok(inputTokens ?? 0)} / ${fmtTok(outputTokens ?? 0)} / ${fmtTok(cachedTokens ?? 0)}`);
|
|
145
149
|
}
|
package/dist/agent/pi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/shared.ts","../../src/agent/pi.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): 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 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\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 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's event has no reason field, so omp\n // cancels ALL compaction — under bili, manual native /compact is\n // equally harmful (the native summarizer would destroy the\n // ACP-tagged context), the host shows \"Compaction cancelled\", and\n // the user should reach for /acp instead. Only armed under `bili`\n // launch: plain pi/omp with the plugin installed stays fully native.\n if ((agent === \"pi\" || agent === \"omp\") && process.env.BILLION_CONTEXT_PROXY !== undefined) {\n pi.on(\"session_before_compact\", (event) => {\n if (agent === \"pi\") {\n const reason = (event as unknown as { reason?: unknown }).reason;\n if (reason === \"threshold\" || reason === \"overflow\") return { cancel: true };\n return undefined;\n }\n return { cancel: true };\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 notify(\"bili: no proxy detected (run via `bili <client>` or set a /bili/ baseURL)\", \"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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/agent/shared.ts","../../src/agent/pi.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): 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 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\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. pi's event\n // carries `reason`: cancel only threshold + overflow so manual\n // /compact stays user-owned. omp's event has no reason field, so omp\n // cancels ALL compaction — under bili, manual native /compact is\n // equally harmful (the native summarizer would destroy the\n // ACP-tagged context), the host shows \"Compaction cancelled\", and\n // the user should reach for /acp instead. Only armed under `bili`\n // launch: plain pi/omp with the plugin installed stays fully native.\n if ((agent === \"pi\" || agent === \"omp\") && process.env.BILLION_CONTEXT_PROXY !== undefined) {\n pi.on(\"session_before_compact\", (event) => {\n if (agent === \"pi\") {\n const reason = (event as unknown as { reason?: unknown }).reason;\n if (reason === \"threshold\" || reason === \"overflow\") return { cancel: true };\n return undefined;\n }\n return { cancel: true };\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 notify(\"bili: no proxy detected (run via `bili <client>` or set a /bili/ baseURL)\", \"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"],"mappings":"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","names":[]}
|