billion-context 0.1.47 → 0.1.48-pr.218.33

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/dist/agent/omp.js CHANGED
@@ -165,6 +165,7 @@ async function registerTools(pi, ctx, state, agent) {
165
165
  try {
166
166
  for (const t of tools) pi.registerTool(manifestToTool(proxyBase, t, agent));
167
167
  state.sid = sid;
168
+ state.toolsReady = true;
168
169
  state.retryAt = void 0;
169
170
  } catch (err) {
170
171
  state.sid = void 0;
@@ -219,12 +220,14 @@ function createBiliPlugin(agentOverride) {
219
220
  if (proxyBaseForCtx(ctx) === void 0) return;
220
221
  const headers = event.headers;
221
222
  if (headers === void 0 || typeof headers !== "object" || Array.isArray(headers)) return;
222
- const sid = sessionIdOf(ctx);
223
- if (sid !== void 0) headers["x-bili-plugin-conversation"] = sid;
224
- headers["x-bili-plugin"] = agent;
225
- const window = ctx.model?.contextWindow;
226
- if (typeof window === "number" && Number.isFinite(window) && window > 0) {
227
- headers["x-bili-plugin-context-window"] = String(Math.floor(window));
223
+ if (state.toolsReady === true) {
224
+ const sid = sessionIdOf(ctx);
225
+ if (sid !== void 0) headers["x-bili-plugin-conversation"] = sid;
226
+ headers["x-bili-plugin"] = agent;
227
+ const window = ctx.model?.contextWindow;
228
+ if (typeof window === "number" && Number.isFinite(window) && window > 0) {
229
+ headers["x-bili-plugin-context-window"] = String(Math.floor(window));
230
+ }
228
231
  }
229
232
  } catch (err) {
230
233
  console.error(`bili-plugin(${agent}): header stamp skipped (${err instanceof Error ? err.message : String(err)})`);
@@ -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","// 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 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};\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 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 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 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 notify(\"bili: no ACP session yet (send a model request first, then run /acp)\", \"warning\");\n return;\n }\n const panel = typeof status.panel === \"string\" ? status.panel : undefined;\n notify(panel ?? renderAcpStatus(status), \"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 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":[]}
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 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};\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 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 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; toolsReady?: boolean; 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.toolsReady = true;\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 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 notify(\"bili: no ACP session yet (send a model request first, then run /acp)\", \"warning\");\n return;\n }\n const panel = typeof status.panel === \"string\" ? status.panel : undefined;\n notify(panel ?? renderAcpStatus(status), \"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(\"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":[]}
package/dist/agent/pi.js CHANGED
@@ -165,6 +165,7 @@ async function registerTools(pi, ctx, state, agent) {
165
165
  try {
166
166
  for (const t of tools) pi.registerTool(manifestToTool(proxyBase, t, agent));
167
167
  state.sid = sid;
168
+ state.toolsReady = true;
168
169
  state.retryAt = void 0;
169
170
  } catch (err) {
170
171
  state.sid = void 0;
@@ -219,12 +220,14 @@ function createBiliPlugin(agentOverride) {
219
220
  if (proxyBaseForCtx(ctx) === void 0) return;
220
221
  const headers = event.headers;
221
222
  if (headers === void 0 || typeof headers !== "object" || Array.isArray(headers)) return;
222
- const sid = sessionIdOf(ctx);
223
- if (sid !== void 0) headers["x-bili-plugin-conversation"] = sid;
224
- headers["x-bili-plugin"] = agent;
225
- const window = ctx.model?.contextWindow;
226
- if (typeof window === "number" && Number.isFinite(window) && window > 0) {
227
- headers["x-bili-plugin-context-window"] = String(Math.floor(window));
223
+ if (state.toolsReady === true) {
224
+ const sid = sessionIdOf(ctx);
225
+ if (sid !== void 0) headers["x-bili-plugin-conversation"] = sid;
226
+ headers["x-bili-plugin"] = agent;
227
+ const window = ctx.model?.contextWindow;
228
+ if (typeof window === "number" && Number.isFinite(window) && window > 0) {
229
+ headers["x-bili-plugin-context-window"] = String(Math.floor(window));
230
+ }
228
231
  }
229
232
  } catch (err) {
230
233
  console.error(`bili-plugin(${agent}): header stamp skipped (${err instanceof Error ? err.message : String(err)})`);
@@ -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","// 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 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};\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 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 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 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 notify(\"bili: no ACP session yet (send a model request first, then run /acp)\", \"warning\");\n return;\n }\n const panel = typeof status.panel === \"string\" ? status.panel : undefined;\n notify(panel ?? renderAcpStatus(status), \"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 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":[]}
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 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};\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 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 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; toolsReady?: boolean; 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.toolsReady = true;\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 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 notify(\"bili: no ACP session yet (send a model request first, then run /acp)\", \"warning\");\n return;\n }\n const panel = typeof status.panel === \"string\" ? status.panel : undefined;\n notify(panel ?? renderAcpStatus(status), \"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(\"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":[]}
package/dist/index.js CHANGED
@@ -50048,6 +50048,19 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
50048
50048
  }
50049
50049
 
50050
50050
  // src/loop/adapter-responses.ts
50051
+ function rewriteItemEvent(type, obj, mapped) {
50052
+ const item = { ...obj.item ?? {}, id: mapped.id };
50053
+ return Buffer.from(`event: ${type}
50054
+ data: ${JSON.stringify({ ...obj, output_index: mapped.index, item })}
50055
+
50056
+ `, "utf8");
50057
+ }
50058
+ function rewriteRefEvent(type, obj, mapped) {
50059
+ return Buffer.from(`event: ${type}
50060
+ data: ${JSON.stringify({ ...obj, item_id: mapped.id, output_index: mapped.index })}
50061
+
50062
+ `, "utf8");
50063
+ }
50051
50064
  async function* iterSseEvents(stream2) {
50052
50065
  const reader = stream2.getReader();
50053
50066
  let buf = "";
@@ -50213,6 +50226,7 @@ function createResponsesAdapter(textProtocol, projection) {
50213
50226
  },
50214
50227
  async *parseStream(upstream, round) {
50215
50228
  const pending = /* @__PURE__ */ new Map();
50229
+ const remapped = /* @__PURE__ */ new Map();
50216
50230
  for await (const eventStr of iterSseEvents(upstream)) {
50217
50231
  const type = extractEventType(eventStr);
50218
50232
  const dataLine = extractDataLine(eventStr);
@@ -50246,17 +50260,32 @@ function createResponsesAdapter(textProtocol, projection) {
50246
50260
  }
50247
50261
  } else if (item?.type === "custom_tool_call") {
50248
50262
  yield { kind: "meta", chunk: rawBuf, firstRoundOnly: false };
50263
+ } else if (item?.type === "message" && round > 1 && !suppressTextLifecycle) {
50264
+ const origId = typeof item.id === "string" ? item.id : "";
50265
+ const mapped = { id: `msg-proxy-${round}-${origId || outputIndex}`, index: outputIndex++ };
50266
+ if (origId) remapped.set(origId, mapped);
50267
+ yield { kind: "meta", chunk: rewriteItemEvent(type, obj, mapped), firstRoundOnly: false };
50249
50268
  } else if (item?.type !== "message" || !suppressTextLifecycle) {
50250
50269
  yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50251
50270
  }
50252
50271
  } else if (type === "response.content_part.added" || type === "response.content_part.done" || type === "response.output_text.done") {
50253
- if (!suppressTextLifecycle) {
50272
+ const mapped = remapped.get(typeof obj.item_id === "string" ? obj.item_id : "");
50273
+ if (mapped) {
50274
+ yield { kind: "meta", chunk: rewriteRefEvent(type, obj, mapped), firstRoundOnly: false };
50275
+ } else if (!suppressTextLifecycle) {
50254
50276
  yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50255
50277
  }
50256
50278
  } else if (type === "response.output_text.delta") {
50257
50279
  const delta = typeof obj.delta === "string" ? obj.delta : "";
50258
50280
  if (delta.length > 0) {
50259
- yield { kind: "text", delta, raw: rawBuf };
50281
+ const mapped = remapped.get(typeof obj.item_id === "string" ? obj.item_id : "");
50282
+ if (mapped) {
50283
+ yield { kind: "text", delta, raw: rewriteRefEvent(type, obj, mapped) };
50284
+ } else if (round === 1) {
50285
+ yield { kind: "text", delta, raw: rawBuf };
50286
+ } else {
50287
+ yield { kind: "text", delta };
50288
+ }
50260
50289
  }
50261
50290
  } else if (type === "response.function_call_arguments.delta") {
50262
50291
  const itemId = typeof obj.item_id === "string" ? obj.item_id : "";
@@ -50296,6 +50325,15 @@ function createResponsesAdapter(textProtocol, projection) {
50296
50325
  }
50297
50326
  } else if (item?.type === "custom_tool_call") {
50298
50327
  yield { kind: "meta", chunk: rawBuf, firstRoundOnly: false };
50328
+ } else if (item?.type === "message") {
50329
+ const origId = typeof item.id === "string" ? item.id : "";
50330
+ const mapped = remapped.get(origId);
50331
+ if (mapped) {
50332
+ remapped.delete(origId);
50333
+ yield { kind: "meta", chunk: rewriteItemEvent(type, obj, mapped), firstRoundOnly: false };
50334
+ } else if (!suppressTextLifecycle) {
50335
+ yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50336
+ }
50299
50337
  } else if (item?.type !== "message" || !suppressTextLifecycle) {
50300
50338
  yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
50301
50339
  }
@@ -58402,14 +58440,6 @@ var HEALTH_POLL_INTERVAL_MS = 200;
58402
58440
  var SPAWN_WAIT_MS = 2e4;
58403
58441
  var PROBE_TIMEOUT_MS = 1500;
58404
58442
  var DEFAULT_MITM_DOMAIN_SET = new Set(DEFAULT_MITM_DOMAINS.map((d) => d.toLowerCase()));
58405
- function coveredByDefaultMitm(host) {
58406
- const h = host.toLowerCase();
58407
- return DEFAULT_MITM_DOMAINS.some((d) => h === d || h.endsWith("." + d));
58408
- }
58409
- function domainsNeedFreshProxy(domains) {
58410
- if (!domains || domains.length === 0) return false;
58411
- return domains.some((d) => !coveredByDefaultMitm(d));
58412
- }
58413
58443
  function isLaunchClient(value) {
58414
58444
  return LAUNCH_CLIENTS.includes(value);
58415
58445
  }
@@ -58783,10 +58813,6 @@ async function ensureProxyRunning(opts, deps = {}) {
58783
58813
  const spawnImpl = deps.spawnImpl ?? spawn;
58784
58814
  const now = deps.now ?? Date.now;
58785
58815
  const sleepImpl = deps.sleep ?? ((ms2) => new Promise((r) => setTimeout(r, ms2)));
58786
- const preferredOrigin = proxyOrigin(opts.host, opts.port);
58787
- if (!domainsNeedFreshProxy(opts.mitmDomains) && await probeHealth(preferredOrigin, fetchImpl)) {
58788
- return { origin: preferredOrigin, port: opts.port, reused: true, child: null };
58789
- }
58790
58816
  const port = await findFreePort(opts.port, opts.host);
58791
58817
  const spawnedOrigin = proxyOrigin(opts.host, port);
58792
58818
  const script = process.argv[1];
@@ -58821,7 +58847,7 @@ async function ensureProxyRunning(opts, deps = {}) {
58821
58847
  while (now() < deadline) {
58822
58848
  await sleepImpl(HEALTH_POLL_INTERVAL_MS);
58823
58849
  if (await probeHealth(spawnedOrigin, fetchImpl)) {
58824
- return { origin: spawnedOrigin, port, reused: false, child, logPath: logPath2 };
58850
+ return { origin: spawnedOrigin, port, child, logPath: logPath2 };
58825
58851
  }
58826
58852
  }
58827
58853
  throw new Error(`bili: proxy did not become healthy at ${spawnedOrigin} within ${SPAWN_WAIT_MS}ms`);
@@ -58907,7 +58933,7 @@ async function runLaunch(params, deps = {}) {
58907
58933
  const domains = dedupeInOrder([...routes.httpsDomains, ...params.mitmDomains ?? []]);
58908
58934
  const handle2 = await ensureProxyRunning({ host, port, passthrough, debug, mitmDomains: domains }, deps);
58909
58935
  console.error(
58910
- `bili: ${handle2.reused ? "reusing existing" : "started"} proxy at ${handle2.origin} (MITM domains: ${domains.length ? domains.join(", ") : "defaults"})` + (routes.httpRewrites.length > 0 ? ` (HTTP /bili/ rewrites: ${routes.httpRewrites.length})` : "") + (routes.httpsRewrites.length > 0 ? ` (HTTPS cert rewrites: ${routes.httpsRewrites.length})` : "") + (params.client === "pi-test" ? " (no extensions)" : "")
58936
+ `bili: started proxy at ${handle2.origin} (MITM domains: ${domains.length ? domains.join(", ") : "defaults"})` + (routes.httpRewrites.length > 0 ? ` (HTTP /bili/ rewrites: ${routes.httpRewrites.length})` : "") + (routes.httpsRewrites.length > 0 ? ` (HTTPS cert rewrites: ${routes.httpsRewrites.length})` : "") + (params.client === "pi-test" ? " (no extensions)" : "")
58911
58937
  );
58912
58938
  if (handle2.logPath) {
58913
58939
  console.error(`bili: proxy log: ${handle2.logPath}`);
@@ -58981,7 +59007,7 @@ async function runLaunch(params, deps = {}) {
58981
59007
  console.error(`bili: failed to launch ${params.client}: ${err2 instanceof Error ? err2.message : String(err2)}`);
58982
59008
  code = 1;
58983
59009
  } finally {
58984
- if (!handle2.reused) stopProxy(handle2);
59010
+ stopProxy(handle2);
58985
59011
  if (piTmpHome) {
58986
59012
  try {
58987
59013
  fs10.rmSync(piTmpHome, { recursive: true, force: true });
@@ -59021,7 +59047,7 @@ async function runTestPi(params, deps = {}) {
59021
59047
  ]);
59022
59048
  const handle2 = await ensureProxyRunning({ host, port, passthrough, debug, mitmDomains: domains }, deps);
59023
59049
  console.error(
59024
- `bili: ${handle2.reused ? "reusing existing" : "started"} proxy at ${handle2.origin} (MITM domains: ${domains.length ? domains.join(", ") : "defaults"})`
59050
+ `bili: started proxy at ${handle2.origin} (MITM domains: ${domains.length ? domains.join(", ") : "defaults"})`
59025
59051
  );
59026
59052
  if (handle2.logPath) {
59027
59053
  console.error(`bili: proxy log: ${handle2.logPath}`);
@@ -59050,7 +59076,7 @@ async function runTestPi(params, deps = {}) {
59050
59076
  console.error(`bili: pi test failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
59051
59077
  code = 1;
59052
59078
  } finally {
59053
- if (!handle2.reused) stopProxy(handle2);
59079
+ stopProxy(handle2);
59054
59080
  }
59055
59081
  process.exit(code ?? 0);
59056
59082
  }
@@ -59243,8 +59269,7 @@ Usage:
59243
59269
  bili --help show this help
59244
59270
 
59245
59271
  Launcher (bili pi / bili codex / bili claude / bili omp / bili opencode):
59246
- Brings up a proxy on an independent port (reusing one already running on
59247
- that port), then runs the client pointed at it via HTTPS_PROXY + the proxy's
59272
+ Brings up a proxy on an independent port (a fresh instance every launch), then runs the client pointed at it via HTTPS_PROXY + the proxy's
59248
59273
  MITM CA \u2014 no config-file edits. Discovered HTTPS upstream domains are
59249
59274
  auto-whitelisted for MITM so the proxy TLS-terminates exactly the hosts the
59250
59275
  client uses; HTTP / localhost providers go direct. pi/claude trust the CA