billion-context 0.1.43 → 0.1.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,9 +61,49 @@ Three ways to use it — pick one:
61
61
  or when you want to pin an exact value. Routing is the same `/bili/` prefix
62
62
  either way — the config only changes which context window the proxy uses.
63
63
 
64
+ ### Agent plugin mode (内外呼应)
65
+
66
+ For a native-plugin experience, an agent can run a small cooperative plugin
67
+ alongside the proxy: the plugin registers the four ACP tools
68
+ (`compress` / `decompress` / `search_context` / `acp_status`) natively with
69
+ the agent and drives the agent's own tool loop, while the proxy stays the
70
+ compression authority (state, history folding, philosophy prompt, nudges).
71
+ Tool schemas are served by the proxy itself (`GET /__bili/plugin/manifest`),
72
+ so plugin and proxy can never drift. See [PLUGIN.md](PLUGIN.md) for the
73
+ protocol spec. Plugin-equipped sessions are detected automatically via
74
+ request headers — wire-level tool injection is then suppressed for them
75
+ (no double compression, native tool UX). Works in both proxy modes: the
76
+ `/bili/` prefix baseURL **and MITM transparent mode** — the launcher
77
+ (`bili pi` / `bili codex` / `bili claude`) exports `BILLION_CONTEXT_PROXY`
78
+ for plugins to detect; the plugin can also report the agent's own model
79
+ context window (`x-bili-plugin-context-window`) and read live context
80
+ usage via `GET /__bili/plugin/status`.
81
+
64
82
  Compression is injected automatically — you only configure routing, never
65
83
  compression itself.
66
84
 
85
+ For pi and omp, the plugin ships **inside billion-context itself** — no
86
+ separate package to install:
87
+
88
+ ```bash
89
+ bili plugin install pi # adds this billion-context install to pi's settings.json (packages)
90
+ bili plugin install omp # same for omp (config.yml extensions)
91
+ bili plugin install claude # registers the bili MCP server (claude mcp add, user scope)
92
+ bili plugin install codex # appends [mcp_servers.bili] to ~/.codex/config.toml
93
+ bili plugin install opencode # adds mcp.bili to ~/.config/opencode/opencode.json
94
+ bili plugin list # install status for every supported host
95
+ bili plugin remove pi # undo (original files backed up to *.bili-bak once)
96
+ ```
97
+
98
+ The installed plugin is a **thin** one (~5 KB, zero runtime deps): it just
99
+ detects the proxy (from the `/bili/` baseURL or `BILLION_CONTEXT_PROXY`),
100
+ fetches tool schemas from the proxy, registers native tools, and forwards
101
+ executions — the proxy remains the single compression authority, so plugin
102
+ and proxy always match versions. Killing it entirely: `BILLION_CONTEXT_PLUGIN=0`.
103
+
104
+ Hosts without a plugin API (claude, codex, opencode) install the MCP
105
+ bridge instead (`dist/mcp.js`), same protocol underneath.
106
+
67
107
  ### Option 0 — Launcher (`bili pi` / `bili codex` / `bili claude`)
68
108
 
69
109
  The launcher wraps a client in one command: it starts a proxy on an
@@ -83,6 +123,35 @@ bili test pi # quick end-to-end smoke test of the pi pa
83
123
  bili pi --mitm-domain api.foo.com # add a domain to the MITM whitelist
84
124
  ```
85
125
 
126
+ **Plugin-in-launcher mode (#162)** — for `claude` and `codex` the launcher
127
+ additionally injects a single `bili` MCP server (`--mcp-config` for claude,
128
+ `-c mcp_servers.bili.*` for codex — both ephemeral, nothing written to host
129
+ config). The traffic route is **unchanged by default** — the transparent-
130
+ MITM proxy as before — so existing setups keep working exactly as they did
131
+ (OAuth-subscription traffic, custom relay endpoints). Opt in to **direct URL
132
+ routing** (`BILI_LAUNCHER_DIRECT=1`) to drop MITM/CA trust entirely (claude's
133
+ `ANTHROPIC_BASE_URL` / codex's provider `base_url` pointing at the `/bili/`
134
+ prefix); the launcher prints a warning when direct mode changes your traffic
135
+ route. The result is the native-plugin experience of [PLUGIN.md](PLUGIN.md)
136
+ with zero setup: the four ACP tools appear as native MCP tools, executed on
137
+ the proxy under the session lock, while the proxy keeps state, folding,
138
+ philosophy prompt and nudges. Session binding is automatic — claude passes
139
+ its session id to MCP children and on every request; codex spawns bind on
140
+ first sight. Plugin mode is opt-in while host-flag compatibility soaks: set
141
+ `BILI_LAUNCHER_PLUGIN=1` to enable the native MCP tools (verified with
142
+ claude 2.1.227 / codex 0.147.0; `BILI_LAUNCHER_PLUGIN=0` forces the plain
143
+ wire-injected launcher once the default flips on).
144
+ `BILI_LAUNCHER_DIRECT=1` is an opt-in, not an opt-out.
145
+
146
+ Mode matrix:
147
+
148
+ | Mode | Tools surface | Setup | When |
149
+ |------|--------------|-------|------|
150
+ | Launcher + MCP (`BILI_LAUNCHER_PLUGIN=1`) | native MCP tools | one env var | best UX; opt-in while soaking |
151
+ | Launcher wire mode (default for claude/codex) | proxy-injected wire tools | none — just `bili claude` / `bili codex` | default until plugin mode soaks |
152
+ | Manual plugin (pi etc.) | agent-side plugin | install plugin | hosts with plugin APIs |
153
+ | Manual baseURL | proxy-injected wire tools | edit client config | exotic hosts |
154
+
86
155
  How the client is pointed at the proxy (set automatically in the child env):
87
156
 
88
157
  | Client | Proxy redirect | CA trust env var |
package/README.zh-CN.md CHANGED
@@ -47,6 +47,21 @@ npm install -g billion-context
47
47
  - **零配置(最简单):** 在客户端 baseURL 前面加上代理地址 + `/bili/`。无需配置文件 —— context 窗口自动从 [models.dev](https://models.dev) registry 查询。`/bili/` 前缀还是个自检测信号:billion-context 的客户端扩展(billion-context-pi / opencode-acp)能在自己的 baseUrl 里认出它并自禁用,避免双层压缩。
48
48
  - **显式 context 窗口覆盖:** 在配置文件(或网页)里按 URL 声明 context 窗口,用于 registry 不认识的端点,或想钉死一个精确值的场景。两种方式路由都是同一个 `/bili/` 前缀 —— 配置只改变代理用哪个 context 窗口。
49
49
 
50
+ ### Agent 插件模式(内外呼应)
51
+
52
+ 想要原生插件体验,可以在 agent 内部装一个配合代理的插件:插件把四个
53
+ ACP 工具(`compress` / `decompress` / `search_context` / `acp_status`)
54
+ 原生注册进 agent、由 agent 自己的工具循环驱动,而代理仍然是压缩引擎
55
+ (状态、历史折叠、压缩哲学 prompt、nudge 全归代理)。工具 schema 由
56
+ 代理统一下发(`GET /__bili/plugin/manifest`),插件与代理永远不会版本
57
+ 漂移。协议规范见 [PLUGIN.md](PLUGIN.md)。带插件的会话通过请求头自动
58
+ 识别 —— 该会话的 wire 层工具注入自动关闭(不会双重压缩,工具体验原生)。
59
+ 两种代理模式都支持:`/bili/` 前缀 baseURL **和 MITM 透明模式** ——
60
+ launcher(`bili pi` / `bili codex` / `bili claude`)会导出
61
+ `BILLION_CONTEXT_PROXY` 供插件检测;插件还可以上报 agent 自己的模型
62
+ 上下文窗口(`x-bili-plugin-context-window`),并通过
63
+ `GET /__bili/plugin/status` 读取实时上下文水位。
64
+
50
65
  压缩是自动注入的 —— 你只需配置路由,无需配置压缩本身。
51
66
 
52
67
  ### 方式 A —— 零配置(`/bili/` 前缀)
@@ -0,0 +1,182 @@
1
+ import { createRequire as __biliCreateRequire } from 'node:module';
2
+ const require = __biliCreateRequire(import.meta.url);
3
+
4
+ // src/agent/shared.ts
5
+ var MANIFEST_TIMEOUT_MS = 5e3;
6
+ var TOOL_TIMEOUT_MS = 6e4;
7
+ function proxyBaseFromUrl(baseUrl) {
8
+ if (!baseUrl) return void 0;
9
+ try {
10
+ const url = new URL(baseUrl);
11
+ if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
12
+ const segments = url.pathname.split("/").filter((s) => s.length > 0);
13
+ if (segments[0] !== "bili") return void 0;
14
+ const rest = url.pathname.slice(url.pathname.indexOf("bili") + "bili".length);
15
+ if (!/^\/https?:\/\//.test(rest)) return void 0;
16
+ return `${url.protocol}//${url.host}`;
17
+ } catch {
18
+ return void 0;
19
+ }
20
+ }
21
+ function proxyBaseFromEnv() {
22
+ const raw = process.env.BILLION_CONTEXT_PROXY?.trim();
23
+ if (!raw) return void 0;
24
+ try {
25
+ const url = new URL(raw);
26
+ return url.protocol === "http:" || url.protocol === "https:" ? `${url.protocol}//${url.host}` : void 0;
27
+ } catch {
28
+ return void 0;
29
+ }
30
+ }
31
+ function detectProxyBase(baseUrl) {
32
+ if (process.env.BILLION_CONTEXT_PLUGIN === "0") return void 0;
33
+ return proxyBaseFromUrl(baseUrl) ?? proxyBaseFromEnv();
34
+ }
35
+ async function fetchJson(url, init, timeoutMs, externalSignal) {
36
+ const ac = new AbortController();
37
+ if (externalSignal?.aborted) ac.abort();
38
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
39
+ const onExternalAbort = () => ac.abort();
40
+ externalSignal?.addEventListener("abort", onExternalAbort, { once: true });
41
+ try {
42
+ let res;
43
+ try {
44
+ res = await fetch(url, { ...init, signal: ac.signal });
45
+ } catch (err) {
46
+ if (ac.signal.aborted && !externalSignal?.aborted) throw new Error(`timeout after ${timeoutMs}ms: ${url}`);
47
+ throw err;
48
+ }
49
+ const text = await res.text();
50
+ let json = void 0;
51
+ try {
52
+ json = JSON.parse(text);
53
+ } catch {
54
+ json = void 0;
55
+ }
56
+ return { ok: res.ok, status: res.status, json };
57
+ } finally {
58
+ clearTimeout(timer);
59
+ externalSignal?.removeEventListener("abort", onExternalAbort);
60
+ }
61
+ }
62
+ async function fetchManifest(proxyBase) {
63
+ const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/manifest`, void 0, MANIFEST_TIMEOUT_MS);
64
+ if (!ok || !json || typeof json !== "object") throw new Error(`manifest fetch failed: ${status}`);
65
+ const data = json;
66
+ const tools = (data.tools?.anthropic ?? []).filter((t) => typeof t.name === "string");
67
+ if (tools.length === 0) throw new Error("manifest served no anthropic tools");
68
+ return tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.input_schema ?? { type: "object", properties: {} } }));
69
+ }
70
+ async function forwardTool(proxyBase, conversationId, tool, args, signal) {
71
+ const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/tool`, {
72
+ method: "POST",
73
+ headers: { "content-type": "application/json" },
74
+ body: JSON.stringify({ conversationId, tool, args: args ?? {} })
75
+ }, TOOL_TIMEOUT_MS, signal);
76
+ const data = json;
77
+ if (!ok || !data?.ok) {
78
+ throw new Error(`bili proxy tool ${tool} failed (${status}): ${data?.error ?? "unknown error"}`);
79
+ }
80
+ return data.result ?? "";
81
+ }
82
+
83
+ // src/agent/pi.ts
84
+ function agentName(override) {
85
+ if (override) return override;
86
+ return process.env.BILLION_CONTEXT_PLUGIN_AGENT === "omp" ? "omp" : "pi";
87
+ }
88
+ function proxyBaseForCtx(ctx) {
89
+ return detectProxyBase(ctx.model?.baseUrl);
90
+ }
91
+ function sessionIdOf(ctx) {
92
+ try {
93
+ const sid = ctx.sessionManager?.getSessionId?.();
94
+ return typeof sid === "string" ? sid : void 0;
95
+ } catch {
96
+ return void 0;
97
+ }
98
+ }
99
+ function manifestToTool(proxyBase, tool, agent) {
100
+ return {
101
+ name: tool.name,
102
+ description: tool.description,
103
+ parameters: tool.inputSchema,
104
+ execute: async (_toolCallId, params, signal, _onUpdate, ctx) => {
105
+ const conversationId = sessionIdOf(ctx) ?? "unknown";
106
+ try {
107
+ const output = await forwardTool(proxyBase, conversationId, tool.name, params, signal);
108
+ return { content: [{ type: "text", text: output }] };
109
+ } catch (err) {
110
+ return { content: [{ type: "text", text: `bili tool error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
111
+ }
112
+ }
113
+ };
114
+ }
115
+ var RETRY_INTERVAL_MS = 1e4;
116
+ async function registerTools(pi, ctx, state, agent) {
117
+ const proxyBase = proxyBaseForCtx(ctx);
118
+ if (proxyBase === void 0) return;
119
+ const sid = sessionIdOf(ctx) ?? "";
120
+ if (sid === state.sid) return;
121
+ if (state.pending !== void 0) return state.pending;
122
+ if (state.retryAt !== void 0 && Date.now() < state.retryAt) return;
123
+ state.pending = (async () => {
124
+ let tools;
125
+ try {
126
+ tools = await fetchManifest(proxyBase);
127
+ } catch (err) {
128
+ state.retryAt = Date.now() + RETRY_INTERVAL_MS;
129
+ console.error(`bili-plugin(${agent}): manifest fetch failed: ${err instanceof Error ? err.message : String(err)} \u2014 retrying in ${RETRY_INTERVAL_MS / 1e3}s`);
130
+ return;
131
+ }
132
+ try {
133
+ for (const t of tools) pi.registerTool(manifestToTool(proxyBase, t, agent));
134
+ state.sid = sid;
135
+ state.retryAt = void 0;
136
+ } catch (err) {
137
+ state.sid = void 0;
138
+ state.retryAt = Date.now() + RETRY_INTERVAL_MS;
139
+ console.error(`bili-plugin(${agent}): tool registration deferred (${err instanceof Error ? err.message : String(err)}) \u2014 retrying in ${RETRY_INTERVAL_MS / 1e3}s`);
140
+ }
141
+ })();
142
+ try {
143
+ await state.pending;
144
+ } finally {
145
+ state.pending = void 0;
146
+ }
147
+ }
148
+ function createBiliPlugin(agentOverride) {
149
+ return function biliPlugin(pi) {
150
+ const agent = agentName(agentOverride);
151
+ const state = {};
152
+ pi.on("before_provider_headers", (event, ctx) => {
153
+ try {
154
+ if (proxyBaseForCtx(ctx) === void 0) return;
155
+ const headers = event.headers;
156
+ if (headers === void 0 || typeof headers !== "object" || Array.isArray(headers)) return;
157
+ const sid = sessionIdOf(ctx);
158
+ if (sid !== void 0) headers["x-bili-plugin-conversation"] = sid;
159
+ headers["x-bili-plugin"] = agent;
160
+ const window = ctx.model?.contextWindow;
161
+ if (typeof window === "number" && Number.isFinite(window) && window > 0) {
162
+ headers["x-bili-plugin-context-window"] = String(Math.floor(window));
163
+ }
164
+ } catch (err) {
165
+ console.error(`bili-plugin(${agent}): header stamp skipped (${err instanceof Error ? err.message : String(err)})`);
166
+ }
167
+ void registerTools(pi, ctx, state, agent).catch((err) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));
168
+ });
169
+ pi.on("session_start", (_event, ctx) => {
170
+ state.sid = void 0;
171
+ void registerTools(pi, ctx, state, agent).catch((err) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));
172
+ });
173
+ };
174
+ }
175
+ var pi_default = createBiliPlugin();
176
+
177
+ // src/agent/omp.ts
178
+ var omp_default = createBiliPlugin("omp");
179
+ export {
180
+ omp_default as default
181
+ };
182
+ //# sourceMappingURL=omp.js.map
@@ -0,0 +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","// 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, type ManifestTool } from \"./shared.js\";\n\ntype Ctx = {\n sessionManager?: { getSessionId?: () => string } | undefined;\n model?: { contextWindow?: number; baseUrl?: string } | 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 execute: (toolCallId: string, params: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: ((u: unknown) => void) | undefined, ctx: Ctx) => Promise<ToolResult>;\n};\n\ntype ExtensionAPI = {\n on: (event: string, handler: (event: never, ctx: Ctx) => unknown) => void;\n registerTool: (tool: ToolDefinition) => 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\nfunction manifestToTool(proxyBase: string, tool: ManifestTool, agent: string): ToolDefinition {\n return {\n name: tool.name,\n description: tool.description,\n parameters: tool.inputSchema,\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\nconst RETRY_INTERVAL_MS = 10000;\n\ntype RegisterState = { sid?: string; pending?: Promise<void>; retryAt?: number };\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 state.pending = (async () => {\n let tools: ManifestTool[];\n try {\n tools = await fetchManifest(proxyBase);\n } catch (err) {\n state.retryAt = Date.now() + RETRY_INTERVAL_MS;\n console.error(`bili-plugin(${agent}): manifest fetch failed: ${err instanceof Error ? err.message : String(err)} — retrying in ${RETRY_INTERVAL_MS / 1000}s`);\n return;\n }\n try {\n for (const t of tools) pi.registerTool(manifestToTool(proxyBase, t, agent));\n state.sid = sid;\n state.retryAt = undefined;\n } catch (err) {\n state.sid = undefined;\n state.retryAt = Date.now() + RETRY_INTERVAL_MS;\n console.error(`bili-plugin(${agent}): tool registration deferred (${err instanceof Error ? err.message : String(err)}) — retrying in ${RETRY_INTERVAL_MS / 1000}s`);\n }\n })();\n try {\n await state.pending;\n } finally {\n state.pending = undefined;\n }\n}\n\nexport function createBiliPlugin(agentOverride?: string): (pi: ExtensionAPI) => void {\n return function biliPlugin(pi: ExtensionAPI): void {\n const agent = agentName(agentOverride);\n const state: RegisterState = {};\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 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 } 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(\"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 };\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":[]}
@@ -0,0 +1,187 @@
1
+ import { createRequire as __biliCreateRequire } from 'node:module';
2
+ const require = __biliCreateRequire(import.meta.url);
3
+
4
+ // src/agent/shared.ts
5
+ var MANIFEST_TIMEOUT_MS = 5e3;
6
+ var TOOL_TIMEOUT_MS = 6e4;
7
+ var STATUS_TIMEOUT_MS = 5e3;
8
+ function proxyBaseFromUrl(baseUrl) {
9
+ if (!baseUrl) return void 0;
10
+ try {
11
+ const url = new URL(baseUrl);
12
+ if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
13
+ const segments = url.pathname.split("/").filter((s) => s.length > 0);
14
+ if (segments[0] !== "bili") return void 0;
15
+ const rest = url.pathname.slice(url.pathname.indexOf("bili") + "bili".length);
16
+ if (!/^\/https?:\/\//.test(rest)) return void 0;
17
+ return `${url.protocol}//${url.host}`;
18
+ } catch {
19
+ return void 0;
20
+ }
21
+ }
22
+ function proxyBaseFromEnv() {
23
+ const raw = process.env.BILLION_CONTEXT_PROXY?.trim();
24
+ if (!raw) return void 0;
25
+ try {
26
+ const url = new URL(raw);
27
+ return url.protocol === "http:" || url.protocol === "https:" ? `${url.protocol}//${url.host}` : void 0;
28
+ } catch {
29
+ return void 0;
30
+ }
31
+ }
32
+ function detectProxyBase(baseUrl) {
33
+ if (process.env.BILLION_CONTEXT_PLUGIN === "0") return void 0;
34
+ return proxyBaseFromUrl(baseUrl) ?? proxyBaseFromEnv();
35
+ }
36
+ async function fetchJson(url, init, timeoutMs, externalSignal) {
37
+ const ac = new AbortController();
38
+ if (externalSignal?.aborted) ac.abort();
39
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
40
+ const onExternalAbort = () => ac.abort();
41
+ externalSignal?.addEventListener("abort", onExternalAbort, { once: true });
42
+ try {
43
+ let res;
44
+ try {
45
+ res = await fetch(url, { ...init, signal: ac.signal });
46
+ } catch (err) {
47
+ if (ac.signal.aborted && !externalSignal?.aborted) throw new Error(`timeout after ${timeoutMs}ms: ${url}`);
48
+ throw err;
49
+ }
50
+ const text = await res.text();
51
+ let json = void 0;
52
+ try {
53
+ json = JSON.parse(text);
54
+ } catch {
55
+ json = void 0;
56
+ }
57
+ return { ok: res.ok, status: res.status, json };
58
+ } finally {
59
+ clearTimeout(timer);
60
+ externalSignal?.removeEventListener("abort", onExternalAbort);
61
+ }
62
+ }
63
+ async function fetchManifest(proxyBase) {
64
+ const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/manifest`, void 0, MANIFEST_TIMEOUT_MS);
65
+ if (!ok || !json || typeof json !== "object") throw new Error(`manifest fetch failed: ${status}`);
66
+ const data = json;
67
+ const tools = (data.tools?.anthropic ?? []).filter((t) => typeof t.name === "string");
68
+ if (tools.length === 0) throw new Error("manifest served no anthropic tools");
69
+ return tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.input_schema ?? { type: "object", properties: {} } }));
70
+ }
71
+ async function forwardTool(proxyBase, conversationId, tool, args, signal) {
72
+ const { ok, status, json } = await fetchJson(`${proxyBase}/__bili/plugin/tool`, {
73
+ method: "POST",
74
+ headers: { "content-type": "application/json" },
75
+ body: JSON.stringify({ conversationId, tool, args: args ?? {} })
76
+ }, TOOL_TIMEOUT_MS, signal);
77
+ const data = json;
78
+ if (!ok || !data?.ok) {
79
+ throw new Error(`bili proxy tool ${tool} failed (${status}): ${data?.error ?? "unknown error"}`);
80
+ }
81
+ return data.result ?? "";
82
+ }
83
+ async function fetchStatus(proxyBase, conversationId) {
84
+ const { ok, json } = await fetchJson(`${proxyBase}/__bili/plugin/status?conversationId=${encodeURIComponent(conversationId)}`, void 0, STATUS_TIMEOUT_MS);
85
+ if (!ok || !json || typeof json !== "object") return void 0;
86
+ return json;
87
+ }
88
+
89
+ // src/agent/pi.ts
90
+ function agentName(override) {
91
+ if (override) return override;
92
+ return process.env.BILLION_CONTEXT_PLUGIN_AGENT === "omp" ? "omp" : "pi";
93
+ }
94
+ function proxyBaseForCtx(ctx) {
95
+ return detectProxyBase(ctx.model?.baseUrl);
96
+ }
97
+ function sessionIdOf(ctx) {
98
+ try {
99
+ const sid = ctx.sessionManager?.getSessionId?.();
100
+ return typeof sid === "string" ? sid : void 0;
101
+ } catch {
102
+ return void 0;
103
+ }
104
+ }
105
+ function manifestToTool(proxyBase, tool, agent) {
106
+ return {
107
+ name: tool.name,
108
+ description: tool.description,
109
+ parameters: tool.inputSchema,
110
+ execute: async (_toolCallId, params, signal, _onUpdate, ctx) => {
111
+ const conversationId = sessionIdOf(ctx) ?? "unknown";
112
+ try {
113
+ const output = await forwardTool(proxyBase, conversationId, tool.name, params, signal);
114
+ return { content: [{ type: "text", text: output }] };
115
+ } catch (err) {
116
+ return { content: [{ type: "text", text: `bili tool error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
117
+ }
118
+ }
119
+ };
120
+ }
121
+ var RETRY_INTERVAL_MS = 1e4;
122
+ async function registerTools(pi, ctx, state, agent) {
123
+ const proxyBase = proxyBaseForCtx(ctx);
124
+ if (proxyBase === void 0) return;
125
+ const sid = sessionIdOf(ctx) ?? "";
126
+ if (sid === state.sid) return;
127
+ if (state.pending !== void 0) return state.pending;
128
+ if (state.retryAt !== void 0 && Date.now() < state.retryAt) return;
129
+ state.pending = (async () => {
130
+ let tools;
131
+ try {
132
+ tools = await fetchManifest(proxyBase);
133
+ } catch (err) {
134
+ state.retryAt = Date.now() + RETRY_INTERVAL_MS;
135
+ console.error(`bili-plugin(${agent}): manifest fetch failed: ${err instanceof Error ? err.message : String(err)} \u2014 retrying in ${RETRY_INTERVAL_MS / 1e3}s`);
136
+ return;
137
+ }
138
+ try {
139
+ for (const t of tools) pi.registerTool(manifestToTool(proxyBase, t, agent));
140
+ state.sid = sid;
141
+ state.retryAt = void 0;
142
+ } catch (err) {
143
+ state.sid = void 0;
144
+ state.retryAt = Date.now() + RETRY_INTERVAL_MS;
145
+ console.error(`bili-plugin(${agent}): tool registration deferred (${err instanceof Error ? err.message : String(err)}) \u2014 retrying in ${RETRY_INTERVAL_MS / 1e3}s`);
146
+ }
147
+ })();
148
+ try {
149
+ await state.pending;
150
+ } finally {
151
+ state.pending = void 0;
152
+ }
153
+ }
154
+ function createBiliPlugin(agentOverride) {
155
+ return function biliPlugin(pi) {
156
+ const agent = agentName(agentOverride);
157
+ const state = {};
158
+ pi.on("before_provider_headers", (event, ctx) => {
159
+ try {
160
+ if (proxyBaseForCtx(ctx) === void 0) return;
161
+ const headers = event.headers;
162
+ if (headers === void 0 || typeof headers !== "object" || Array.isArray(headers)) return;
163
+ const sid = sessionIdOf(ctx);
164
+ if (sid !== void 0) headers["x-bili-plugin-conversation"] = sid;
165
+ headers["x-bili-plugin"] = agent;
166
+ const window = ctx.model?.contextWindow;
167
+ if (typeof window === "number" && Number.isFinite(window) && window > 0) {
168
+ headers["x-bili-plugin-context-window"] = String(Math.floor(window));
169
+ }
170
+ } catch (err) {
171
+ console.error(`bili-plugin(${agent}): header stamp skipped (${err instanceof Error ? err.message : String(err)})`);
172
+ }
173
+ void registerTools(pi, ctx, state, agent).catch((err) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));
174
+ });
175
+ pi.on("session_start", (_event, ctx) => {
176
+ state.sid = void 0;
177
+ void registerTools(pi, ctx, state, agent).catch((err) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));
178
+ });
179
+ };
180
+ }
181
+ var pi_default = createBiliPlugin();
182
+ export {
183
+ createBiliPlugin,
184
+ pi_default as default,
185
+ fetchStatus
186
+ };
187
+ //# sourceMappingURL=pi.js.map
@@ -0,0 +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","// 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, type ManifestTool } from \"./shared.js\";\n\ntype Ctx = {\n sessionManager?: { getSessionId?: () => string } | undefined;\n model?: { contextWindow?: number; baseUrl?: string } | 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 execute: (toolCallId: string, params: Record<string, unknown>, signal: AbortSignal | undefined, onUpdate: ((u: unknown) => void) | undefined, ctx: Ctx) => Promise<ToolResult>;\n};\n\ntype ExtensionAPI = {\n on: (event: string, handler: (event: never, ctx: Ctx) => unknown) => void;\n registerTool: (tool: ToolDefinition) => 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\nfunction manifestToTool(proxyBase: string, tool: ManifestTool, agent: string): ToolDefinition {\n return {\n name: tool.name,\n description: tool.description,\n parameters: tool.inputSchema,\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\nconst RETRY_INTERVAL_MS = 10000;\n\ntype RegisterState = { sid?: string; pending?: Promise<void>; retryAt?: number };\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 state.pending = (async () => {\n let tools: ManifestTool[];\n try {\n tools = await fetchManifest(proxyBase);\n } catch (err) {\n state.retryAt = Date.now() + RETRY_INTERVAL_MS;\n console.error(`bili-plugin(${agent}): manifest fetch failed: ${err instanceof Error ? err.message : String(err)} — retrying in ${RETRY_INTERVAL_MS / 1000}s`);\n return;\n }\n try {\n for (const t of tools) pi.registerTool(manifestToTool(proxyBase, t, agent));\n state.sid = sid;\n state.retryAt = undefined;\n } catch (err) {\n state.sid = undefined;\n state.retryAt = Date.now() + RETRY_INTERVAL_MS;\n console.error(`bili-plugin(${agent}): tool registration deferred (${err instanceof Error ? err.message : String(err)}) — retrying in ${RETRY_INTERVAL_MS / 1000}s`);\n }\n })();\n try {\n await state.pending;\n } finally {\n state.pending = undefined;\n }\n}\n\nexport function createBiliPlugin(agentOverride?: string): (pi: ExtensionAPI) => void {\n return function biliPlugin(pi: ExtensionAPI): void {\n const agent = agentName(agentOverride);\n const state: RegisterState = {};\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 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 } 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(\"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 };\n}\n\nexport default createBiliPlugin();\n\nexport { fetchStatus };\n"],"mappings":"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","names":[]}