billion-context 0.1.63 → 0.1.64-pr.360.210

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/index.js CHANGED
@@ -47708,8 +47708,9 @@ function loadOptions(env = process.env) {
47708
47708
  const webProxy = nonEmpty(fileConfig.upstreamProxy);
47709
47709
  const configProxy = nonEmpty(fileConfig.proxy);
47710
47710
  const rawProxyMode = env.BILI_UPSTREAM_PROXY_MODE ?? fileConfig.upstreamProxyMode ?? (webProxy ? "manual" : void 0);
47711
- const proxyMode = parseUpstreamProxyMode(rawProxyMode);
47712
- const explicitDirect = proxyMode === "direct" && rawProxyMode === "direct";
47711
+ const effectiveMode = rawProxyMode ?? "direct";
47712
+ const proxyMode = parseUpstreamProxyMode(effectiveMode);
47713
+ const explicitDirect = proxyMode === "direct" && !biliProxy;
47713
47714
  const proxy = biliProxy ?? (proxyMode === "direct" ? "" : proxyMode === "manual" ? webProxy ?? configProxy : configProxy);
47714
47715
  const proxySource = biliProxy ? "bili-env" : proxyMode === "direct" ? "direct" : proxyMode === "manual" && webProxy ? "web-manual" : configProxy ? "config" : "auto";
47715
47716
  const httpProxy = nonEmpty(env.HTTP_PROXY ?? env.http_proxy);
@@ -50625,6 +50626,153 @@ import { readFileSync as readFileSync3 } from "fs";
50625
50626
  import { dirname as dirname4, join as join3 } from "path";
50626
50627
  import { fileURLToPath } from "url";
50627
50628
 
50629
+ // src/web/page.ts
50630
+ import { existsSync as existsSync3 } from "fs";
50631
+
50632
+ // src/ca.ts
50633
+ var import_node_forge = __toESM(require_lib(), 1);
50634
+ import fs2 from "fs";
50635
+ import path6 from "path";
50636
+ import tls2 from "tls";
50637
+ var ROOT_CERT_FILE = "root-ca.pem";
50638
+ var ROOT_KEY_FILE = "root-ca-key.pem";
50639
+ var COMBINED_CA_FILE = "combined-ca.pem";
50640
+ var ROOT_CN = "billion-context MITM Root CA";
50641
+ var PLATFORM_CA_CANDIDATES = process.platform === "darwin" ? ["/etc/ssl/cert.pem", "/private/etc/ssl/cert.pem"] : [
50642
+ "/etc/ssl/certs/ca-certificates.crt",
50643
+ "/etc/pki/tls/certs/ca-bundle.crt",
50644
+ "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
50645
+ "/etc/ssl/ca-bundle.pem",
50646
+ "/etc/ssl/cert.pem"
50647
+ ];
50648
+ var rootCertPem;
50649
+ var rootKeyPem;
50650
+ var rootCert;
50651
+ var rootKey;
50652
+ var secureContextCache = /* @__PURE__ */ new Map();
50653
+ var SECURE_CONTEXT_CACHE_MAX = 64;
50654
+ function rootCaPath() {
50655
+ return path6.join(caDir(), ROOT_CERT_FILE);
50656
+ }
50657
+ function collectSystemCaPems(env = process.env) {
50658
+ const pems = [];
50659
+ const seen = /* @__PURE__ */ new Set();
50660
+ const pushFile = (file) => {
50661
+ try {
50662
+ const text = fs2.readFileSync(file, "utf8");
50663
+ if (!text.includes("BEGIN CERTIFICATE") || seen.has(text)) return false;
50664
+ seen.add(text);
50665
+ pems.push(text);
50666
+ return true;
50667
+ } catch {
50668
+ }
50669
+ return false;
50670
+ };
50671
+ const userBundle = env.SSL_CERT_FILE?.trim();
50672
+ if (userBundle && pushFile(userBundle)) return pems;
50673
+ for (const candidate of PLATFORM_CA_CANDIDATES) {
50674
+ if (pushFile(candidate)) break;
50675
+ }
50676
+ return pems;
50677
+ }
50678
+ function writeCombinedBundle() {
50679
+ const certs = /* @__PURE__ */ new Set();
50680
+ for (const pem of collectSystemCaPems()) certs.add(pem.trim());
50681
+ for (const pem of tls2.rootCertificates) certs.add(pem.trim());
50682
+ certs.add(rootCertPem.trim());
50683
+ const body = [...certs].map((pem) => pem.endsWith("\n") ? pem : pem + "\n").join("");
50684
+ fs2.writeFileSync(path6.join(caDir(), COMBINED_CA_FILE), body, { mode: 420 });
50685
+ }
50686
+ function generateRootCA() {
50687
+ const keys = import_node_forge.default.pki.rsa.generateKeyPair({ bits: 2048 });
50688
+ const cert = import_node_forge.default.pki.createCertificate();
50689
+ cert.publicKey = keys.publicKey;
50690
+ cert.serialNumber = "01";
50691
+ cert.validity.notBefore = /* @__PURE__ */ new Date();
50692
+ cert.validity.notAfter = /* @__PURE__ */ new Date();
50693
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10);
50694
+ const attrs = [
50695
+ { name: "commonName", value: ROOT_CN },
50696
+ { name: "organizationName", value: "billion-context" }
50697
+ ];
50698
+ cert.setSubject(attrs);
50699
+ cert.setIssuer(attrs);
50700
+ cert.setExtensions([
50701
+ { name: "basicConstraints", cA: true },
50702
+ { name: "keyUsage", keyCertSign: true, cRLSign: true, digitalSignature: true },
50703
+ { name: "subjectKeyIdentifier" }
50704
+ ]);
50705
+ cert.sign(keys.privateKey, import_node_forge.default.md.sha256.create());
50706
+ return {
50707
+ cert: import_node_forge.default.pki.certificateToPem(cert),
50708
+ key: import_node_forge.default.pki.privateKeyToPem(keys.privateKey)
50709
+ };
50710
+ }
50711
+ function ensureRootCA() {
50712
+ if (rootCertPem && rootKeyPem) {
50713
+ writeCombinedBundle();
50714
+ return;
50715
+ }
50716
+ const dir = caDir();
50717
+ fs2.mkdirSync(dir, { recursive: true });
50718
+ const certPath = path6.join(dir, ROOT_CERT_FILE);
50719
+ const keyPath = path6.join(dir, ROOT_KEY_FILE);
50720
+ if (fs2.existsSync(certPath) && fs2.existsSync(keyPath)) {
50721
+ rootCertPem = fs2.readFileSync(certPath, "utf8");
50722
+ rootKeyPem = fs2.readFileSync(keyPath, "utf8");
50723
+ rootCert = import_node_forge.default.pki.certificateFromPem(rootCertPem);
50724
+ rootKey = import_node_forge.default.pki.privateKeyFromPem(rootKeyPem);
50725
+ writeCombinedBundle();
50726
+ return;
50727
+ }
50728
+ const { cert, key } = generateRootCA();
50729
+ fs2.writeFileSync(certPath, cert, { mode: 420 });
50730
+ fs2.writeFileSync(keyPath, key, { mode: 384 });
50731
+ rootCertPem = cert;
50732
+ rootKeyPem = key;
50733
+ rootCert = import_node_forge.default.pki.certificateFromPem(cert);
50734
+ rootKey = import_node_forge.default.pki.privateKeyFromPem(key);
50735
+ writeCombinedBundle();
50736
+ }
50737
+ function getSecureContext(host) {
50738
+ if (!rootCertPem || !rootKeyPem || !rootCert || !rootKey) {
50739
+ throw new Error("CA not initialized \u2014 call ensureRootCA() first");
50740
+ }
50741
+ const cached = secureContextCache.get(host);
50742
+ if (cached) {
50743
+ secureContextCache.delete(host);
50744
+ secureContextCache.set(host, cached);
50745
+ return cached;
50746
+ }
50747
+ const keys = import_node_forge.default.pki.rsa.generateKeyPair({ bits: 2048 });
50748
+ const cert = import_node_forge.default.pki.createCertificate();
50749
+ cert.publicKey = keys.publicKey;
50750
+ cert.serialNumber = Date.now().toString() + Math.floor(Math.random() * 1e6).toString();
50751
+ cert.validity.notBefore = /* @__PURE__ */ new Date();
50752
+ cert.validity.notAfter = /* @__PURE__ */ new Date();
50753
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 2);
50754
+ cert.setSubject([{ name: "commonName", value: host }]);
50755
+ cert.setIssuer(rootCert.subject.attributes);
50756
+ cert.setExtensions([
50757
+ { name: "basicConstraints", cA: false },
50758
+ { name: "keyUsage", digitalSignature: true, keyEncipherment: true },
50759
+ { name: "extKeyUsage", serverAuth: true },
50760
+ { name: "subjectAltName", altNames: [{ type: 2, value: host }] }
50761
+ ]);
50762
+ cert.sign(rootKey, import_node_forge.default.md.sha256.create());
50763
+ const ctx = tls2.createSecureContext({
50764
+ cert: import_node_forge.default.pki.certificateToPem(cert),
50765
+ key: import_node_forge.default.pki.privateKeyToPem(keys.privateKey),
50766
+ ca: rootCertPem
50767
+ });
50768
+ secureContextCache.set(host, ctx);
50769
+ if (secureContextCache.size > SECURE_CONTEXT_CACHE_MAX) {
50770
+ const oldest = secureContextCache.keys().next().value;
50771
+ if (oldest !== void 0) secureContextCache.delete(oldest);
50772
+ }
50773
+ return ctx;
50774
+ }
50775
+
50628
50776
  // src/web/client.ts
50629
50777
  var WEB_CLIENT = `
50630
50778
  const byId=(id)=>document.getElementById(id);
@@ -50657,7 +50805,13 @@ var WEB_STYLES = `
50657
50805
  `;
50658
50806
 
50659
50807
  // src/web/page.ts
50808
+ function escapeHtml(value) {
50809
+ return value.replace(/[&<>"']/g, (char) => char === "&" ? "&amp;" : char === "<" ? "&lt;" : char === ">" ? "&gt;" : char === '"' ? "&quot;" : "&#39;");
50810
+ }
50660
50811
  function renderPage(origin, version2) {
50812
+ const caPath = rootCaPath();
50813
+ const caPathEsc = escapeHtml(caPath);
50814
+ const caReady = existsSync3(caPath);
50661
50815
  return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>billion-context</title><style>${WEB_STYLES}</style></head><body>
50662
50816
  <div class="app"><aside class="sidebar"><div class="brand"><div class="brand-title">billion<span>-context</span></div><div class="version">v${version2}</div></div><a class="fork-ribbon" href="https://github.com/ranxianglei/billion-context" target="_blank" rel="noopener">\u2605 Fork me on GitHub</a><nav class="nav"><button class="active" data-page="overview">\u6982\u89C8</button><button data-page="routing">\u63A5\u5165</button><button data-page="upstream">\u4E0A\u6E38</button><button data-page="sessions">\u4F1A\u8BDD</button><button data-page="settings">\u8BBE\u7F6E</button></nav></aside>
50663
50817
  <main class="main"><section id="page-overview" class="page active"><h1>\u6982\u89C8</h1><p class="lead">\u672C\u5730\u8DEF\u7531\u4E0E\u4E0A\u6E38\u7F51\u7EDC\u76F8\u4E92\u72EC\u7ACB\uFF0CACP \u538B\u7F29\u8FD0\u884C\u5728\u4E24\u8005\u4E4B\u95F4\u3002</p><div class="grid"><div class="card"><h2>\u670D\u52A1\u5730\u5740</h2><p class="mono">${origin}</p></div><div class="card"><h2>\u5BA2\u6237\u7AEF\u63A5\u5165</h2><p class="status">\u5728"\u63A5\u5165"\u9875\u5C06\u5404\u5BA2\u6237\u7AEF\uFF08Codex / Claude Code / OpenCode / Pi \u7B49\uFF09\u7684\u6D41\u91CF\u8DEF\u7531\u5230 bili\u3002</p></div></div></section>
@@ -50672,14 +50826,14 @@ function renderPage(origin, version2) {
50672
50826
  <div class="card"><div class="card-head"><h2>\u5176\u4ED6\uFF08Cursor / Aider / Continue\uFF09</h2><button class="btn small copy-btn" data-copy="${origin}/bili/">\u590D\u5236</button></div><dl class="kv"><dt>\u89C4\u5219</dt><dd>\u4EFB\u610F baseURL \u524D\u52A0 <span class="mono">${origin}/bili/</span></dd><dt>\u793A\u4F8B</dt><dd class="mono">${origin}/bili/https://api.openai.com/v1</dd></dl></div>
50673
50827
  <h2 class="section-title">B. \u8BC1\u4E66\u63A5\u5165\u65B9\u5F0F\uFF08zcode \u7B49\u7C7B\u578B\uFF09</h2>
50674
50828
  <p class="hint">\u4E0D\u652F\u6301\u6539 URL \u7684\u5BA2\u6237\u7AEF\uFF08endpoint \u786C\u7F16\u7801\uFF09\u9700 MITM \u900F\u660E\u4EE3\u7406\uFF0C\u8BBE HTTPS_PROXY + \u4FE1\u4EFB CA \u8BC1\u4E66\u3002</p>
50675
- <div class="card"><div class="card-head"><h2>ZCode\uFF08\u7F16\u7A0B\u5957\u9910\uFF09</h2><span class="badge">MITM</span></div><dl class="kv"><dt>\u65B9\u5F0F</dt><dd>MITM \u900F\u660E\u4EE3\u7406</dd><dt>\u8BBE\u7F6E</dt><dd>Settings \u2192 Network \u2192 HTTP Proxy = <span class="mono">${origin}</span></dd><dt>CA \u8BC1\u4E66</dt><dd class="mono">~/.local/share/billion-context/ca/root-ca.pem</dd></dl></div></section>
50829
+ <div class="card"><div class="card-head"><h2>ZCode\uFF08\u7F16\u7A0B\u5957\u9910\uFF09</h2><span class="badge">MITM</span><button class="btn small copy-btn" data-copy="${caPathEsc}">\u590D\u5236 CA \u8DEF\u5F84</button></div><dl class="kv"><dt>\u65B9\u5F0F</dt><dd>MITM \u900F\u660E\u4EE3\u7406</dd><dt>\u8BBE\u7F6E</dt><dd>Settings \u2192 Network \u2192 HTTP Proxy = <span class="mono">${origin}</span></dd><dt>CA \u8BC1\u4E66</dt><dd class="mono">${caPathEsc}${caReady ? "" : "\uFF08\u5C1A\u672A\u751F\u6210\uFF0C\u542F\u52A8\u4EE3\u7406\u540E\u81EA\u52A8\u521B\u5EFA\uFF09"}</dd></dl><p class="hint">CA \u8DEF\u5F84\u662F\u672C\u673A bili \u5B9E\u9645\u751F\u6210\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u70B9"\u590D\u5236 CA \u8DEF\u5F84"\u540E\u76F4\u63A5\u7C98\u8D34\u8FDB ZCode\u3002\u26A0\uFE0F Windows \u4E0A ZCode \u4E0D\u4F1A\u5C55\u5F00 <span class="mono">~</span>\uFF0C\u8BF7\u52FF\u624B\u586B <span class="mono">~/\u2026</span> \u5F62\u5F0F\u7684\u8DEF\u5F84\u3002</p></div></section>
50676
50830
  <section id="page-upstream" class="page"><h1>\u4E0A\u6E38\u7F51\u7EDC</h1><p class="lead">\u63A7\u5236 bili \u5982\u4F55\u8BBF\u95EE\u771F\u5B9E Provider\uFF0C\u4E0D\u4F1A\u6539\u53D8\u5BA2\u6237\u7AEF\u7684\u672C\u5730\u8DEF\u7531\u5730\u5740\u3002</p><div class="card"><div class="card-head"><h2>\u5168\u5C40\u4EE3\u7406</h2></div><div class="modes"><label><input type="radio" name="proxy-mode" value="direct" checked>\u76F4\u8FDE\uFF08\u9ED8\u8BA4\uFF09</label><label><input type="radio" name="proxy-mode" value="manual">\u624B\u52A8\u4EE3\u7406</label><label><input type="radio" name="proxy-mode" value="auto">\u81EA\u52A8\uFF08\u8DDF\u968F\u7CFB\u7EDF\uFF09</label></div><div class="field"><label>HTTP / HTTPS Proxy</label><input id="proxy-url" class="mono" placeholder="http://127.0.0.1:7897"></div><dl class="kv"><dt>\u5F53\u524D\u6765\u6E90</dt><dd id="upstream-source">\u76F4\u8FDE</dd><dt>\u6709\u6548\u4EE3\u7406</dt><dd id="upstream-effective" class="mono">direct</dd><dt>\u7CFB\u7EDF PAC</dt><dd id="upstream-pac" class="mono">\u2014</dd><dt>\u72B6\u6001</dt><dd id="upstream-state" class="status">\u5C1A\u672A\u6D4B\u8BD5</dd></dl><div class="actions"><button id="save-upstream" class="btn primary">\u4FDD\u5B58\u5E76\u70ED\u66F4\u65B0</button><button id="test-upstream" class="btn">\u6D4B\u8BD5\u8FDE\u63A5</button></div></div><div class="card"><div class="card-head"><h2>\u6309 URL \u8986\u76D6</h2></div><p class="status">\u4E3A\u7279\u5B9A Provider \u5355\u72EC\u8BBE\u7F6E\u4E0A\u6E38\u4EE3\u7406\uFF0C\u8986\u76D6\u5168\u5C40\u8BBE\u7F6E\u3002\u7559\u7A7A = \u7EE7\u627F\u5168\u5C40\u3002</p><div id="upstream-overrides" class="status">\u52A0\u8F7D\u4E2D</div><div class="actions"><button id="save-overrides" class="btn primary">\u4FDD\u5B58\u8986\u76D6\u5E76\u70ED\u66F4\u65B0</button></div></div></section>
50677
50831
  <section id="page-sessions" class="page"><div class="card-head"><div><h1>\u4F1A\u8BDD</h1><p class="lead">ACP \u538B\u7F29\u72B6\u6001\u4E0E\u4E0A\u6E38\u7528\u91CF\u3002</p></div><button id="refresh-sessions" class="btn">\u5237\u65B0</button></div><div class="card"><table><thead><tr><th>\u6807\u9898</th><th>\u534F\u8BAE</th><th>\u6807\u8BC6</th><th>\u8BF7\u6C42</th><th>\u4E0A\u4E0B\u6587</th><th>\u6700\u540E\u6D3B\u52A8</th></tr></thead><tbody id="sessions-body"></tbody></table></div></section>
50678
50832
  <section id="page-settings" class="page"><h1>\u9AD8\u7EA7\u8BBE\u7F6E</h1><p class="lead">\u76F4\u63A5\u7F16\u8F91 providers JSON\uFF08\u6DFB\u52A0\u65B0 Provider\u3001\u914D\u7F6E\u6A21\u578B\u4E0A\u4E0B\u6587\u7A97\u53E3\u3001\u6309 URL \u4EE3\u7406\u7B49\uFF09\uFF1B\u4FDD\u5B58\u540E\u7ACB\u5373\u70ED\u66F4\u65B0\u3002</p><div class="card"><div class="field"><label>providers JSON</label><textarea id="providers-json">{}</textarea></div><div class="actions"><button id="save-providers" class="btn primary">\u4FDD\u5B58\u5E76\u5E94\u7528</button></div></div><div class="card"><div class="card-head"><h2>\u538B\u7F29\u53C2\u6570\uFF08\u5168\u5C40\uFF09</h2></div><p class="status">\u5168\u5C40 compress \u914D\u7F6E\uFF08modelContextLimit / maxContextLimit / emergencyThresholdPercent / nudgeGrowthTokens / preserveRecentMessages / preserveRecentTokens / minCompressRange / tiers / injectTool / injectNudge\uFF09\u3002\u4FDD\u5B58\u540E\u70ED\u66F4\u65B0\uFF0C\u65E0\u9700\u91CD\u542F\u3002\u7F6E\u7A7A = \u6062\u590D\u9ED8\u8BA4\u3002</p><div class="field"><label>compress JSON</label><textarea id="compress-json" placeholder='{"modelContextLimit": 200000}'></textarea></div><div class="actions"><button id="save-compress" class="btn primary">\u4FDD\u5B58\u5E76\u70ED\u66F4\u65B0</button></div></div></section></main></div><div id="toast" class="toast"></div><script>${WEB_CLIENT}</script></body></html>`;
50679
50833
  }
50680
50834
 
50681
50835
  // src/web/api.ts
50682
- import { closeSync, existsSync as existsSync3, fsyncSync, mkdirSync as mkdirSync4, openSync, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
50836
+ import { closeSync, existsSync as existsSync4, fsyncSync, mkdirSync as mkdirSync4, openSync, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
50683
50837
  import { dirname as dirname3 } from "path";
50684
50838
  import { randomUUID as randomUUID2 } from "crypto";
50685
50839
  function readConfig() {
@@ -50687,7 +50841,7 @@ function readConfig() {
50687
50841
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
50688
50842
  }
50689
50843
  function configParseError() {
50690
- if (!existsSync3(configFile())) return null;
50844
+ if (!existsSync4(configFile())) return null;
50691
50845
  const parsed = safeReadJson(configFile());
50692
50846
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return null;
50693
50847
  return `config file is not valid JSON: ${configFile()}`;
@@ -52736,6 +52890,65 @@ async function compressLoopResponsesJson(initialResponse, ctx, requestBody, requ
52736
52890
  return current;
52737
52891
  }
52738
52892
 
52893
+ // src/acp-panel.ts
52894
+ var PANEL_BOX_TOP = "\u256D";
52895
+ var PANEL_BOX_TITLE = "ACP Context Analysis";
52896
+ var PANEL_FALLBACK_HEADER = "\u{1F4CA} ACP status";
52897
+ function isAcpPanelText(text) {
52898
+ const t = text.trim();
52899
+ if (t.length === 0) return false;
52900
+ if (t.startsWith(PANEL_BOX_TOP) && t.includes(PANEL_BOX_TITLE)) return true;
52901
+ if (t.startsWith(PANEL_FALLBACK_HEADER)) return true;
52902
+ return false;
52903
+ }
52904
+ function messageText(content) {
52905
+ if (typeof content === "string") return content;
52906
+ if (Array.isArray(content)) {
52907
+ let joined = "";
52908
+ for (const part of content) {
52909
+ const p2 = part;
52910
+ if (p2 === null || typeof p2 !== "object") return void 0;
52911
+ const pt2 = p2.type;
52912
+ if (pt2 !== void 0 && pt2 !== "text" && pt2 !== "input_text" && pt2 !== "output_text") return void 0;
52913
+ if (typeof p2.text === "string") joined += p2.text;
52914
+ }
52915
+ return joined;
52916
+ }
52917
+ return void 0;
52918
+ }
52919
+ function stripAcpPanelMessages(messages) {
52920
+ if (!Array.isArray(messages)) return 0;
52921
+ let stripped = 0;
52922
+ for (let i = messages.length - 1; i >= 0; i--) {
52923
+ const rec = messages[i];
52924
+ if (rec === null || typeof rec !== "object") continue;
52925
+ if (rec.role !== "user") continue;
52926
+ const text = messageText(rec.content);
52927
+ if (text !== void 0 && isAcpPanelText(text)) {
52928
+ messages.splice(i, 1);
52929
+ stripped++;
52930
+ }
52931
+ }
52932
+ return stripped;
52933
+ }
52934
+ function stripAcpPanelResponsesInput(input) {
52935
+ if (!Array.isArray(input)) return 0;
52936
+ let stripped = 0;
52937
+ for (let i = input.length - 1; i >= 0; i--) {
52938
+ const rec = input[i];
52939
+ if (rec === null || typeof rec !== "object") continue;
52940
+ const type = rec.type;
52941
+ if (type !== "message" && type !== void 0) continue;
52942
+ if (rec.role !== "user") continue;
52943
+ const text = messageText(rec.content);
52944
+ if (text !== void 0 && isAcpPanelText(text)) {
52945
+ input.splice(i, 1);
52946
+ stripped++;
52947
+ }
52948
+ }
52949
+ return stripped;
52950
+ }
52951
+
52739
52952
  // src/stream-openai.ts
52740
52953
  function rewriteOpenaiJsonResponse(body, ctx) {
52741
52954
  if (!body || typeof body !== "object") return body;
@@ -53344,13 +53557,13 @@ function buildStatusPanel(input) {
53344
53557
 
53345
53558
  // src/plugin.ts
53346
53559
  import { fileURLToPath as fileURLToPath2 } from "url";
53347
- import fs2 from "fs";
53348
- import path6 from "path";
53560
+ import fs3 from "fs";
53561
+ import path7 from "path";
53349
53562
  var PROXY_VERSION = (() => {
53350
53563
  try {
53351
53564
  const here = fileURLToPath2(import.meta.url);
53352
- const pkg = path6.join(path6.dirname(here), "..", "package.json");
53353
- return JSON.parse(fs2.readFileSync(pkg, "utf8")).version ?? "dev";
53565
+ const pkg = path7.join(path7.dirname(here), "..", "package.json");
53566
+ return JSON.parse(fs3.readFileSync(pkg, "utf8")).version ?? "dev";
53354
53567
  } catch {
53355
53568
  return "dev";
53356
53569
  }
@@ -53362,8 +53575,8 @@ var PLUGIN_PROTOCOL_VERSION = 1;
53362
53575
  var VERSION = (() => {
53363
53576
  try {
53364
53577
  const here = fileURLToPath2(import.meta.url);
53365
- const pkg = path6.join(path6.dirname(here), "..", "package.json");
53366
- return JSON.parse(fs2.readFileSync(pkg, "utf8")).version ?? "dev";
53578
+ const pkg = path7.join(path7.dirname(here), "..", "package.json");
53579
+ return JSON.parse(fs3.readFileSync(pkg, "utf8")).version ?? "dev";
53367
53580
  } catch {
53368
53581
  return "dev";
53369
53582
  }
@@ -53392,14 +53605,14 @@ function pluginReportedContextWindow(headers) {
53392
53605
  var MAX_PLUGIN_CONVERSATIONS = 1024;
53393
53606
  var conversations = /* @__PURE__ */ new Map();
53394
53607
  var remembered = /* @__PURE__ */ new Map();
53395
- var conversationsFile = () => path6.join(stateDir(), "plugin-conversations.json");
53608
+ var conversationsFile = () => path7.join(stateDir(), "plugin-conversations.json");
53396
53609
  var conversationsSaveTimer;
53397
53610
  function writeConversationsFile() {
53398
53611
  try {
53399
53612
  const obj = {};
53400
53613
  for (const [k2, v2] of conversations) obj[k2] = v2;
53401
- fs2.mkdirSync(stateDir(), { recursive: true });
53402
- fs2.writeFileSync(conversationsFile(), JSON.stringify(obj));
53614
+ fs3.mkdirSync(stateDir(), { recursive: true });
53615
+ fs3.writeFileSync(conversationsFile(), JSON.stringify(obj));
53403
53616
  } catch {
53404
53617
  }
53405
53618
  }
@@ -53419,7 +53632,7 @@ function flushConversations() {
53419
53632
  }
53420
53633
  function loadConversations() {
53421
53634
  try {
53422
- const raw = fs2.readFileSync(conversationsFile(), "utf8");
53635
+ const raw = fs3.readFileSync(conversationsFile(), "utf8");
53423
53636
  const obj = JSON.parse(raw);
53424
53637
  for (const [k2, v2] of Object.entries(obj)) {
53425
53638
  if (v2 && typeof v2.sessionId === "string" && v2.sessionId.length > 0) {
@@ -53880,147 +54093,6 @@ async function pipePluginJson(stream2, res, session, protocol) {
53880
54093
  // src/mitm.ts
53881
54094
  import tls3 from "tls";
53882
54095
 
53883
- // src/ca.ts
53884
- var import_node_forge = __toESM(require_lib(), 1);
53885
- import fs3 from "fs";
53886
- import path7 from "path";
53887
- import tls2 from "tls";
53888
- var ROOT_CERT_FILE = "root-ca.pem";
53889
- var ROOT_KEY_FILE = "root-ca-key.pem";
53890
- var COMBINED_CA_FILE = "combined-ca.pem";
53891
- var ROOT_CN = "billion-context MITM Root CA";
53892
- var PLATFORM_CA_CANDIDATES = process.platform === "darwin" ? ["/etc/ssl/cert.pem", "/private/etc/ssl/cert.pem"] : [
53893
- "/etc/ssl/certs/ca-certificates.crt",
53894
- "/etc/pki/tls/certs/ca-bundle.crt",
53895
- "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
53896
- "/etc/ssl/ca-bundle.pem",
53897
- "/etc/ssl/cert.pem"
53898
- ];
53899
- var rootCertPem;
53900
- var rootKeyPem;
53901
- var rootCert;
53902
- var rootKey;
53903
- var secureContextCache = /* @__PURE__ */ new Map();
53904
- var SECURE_CONTEXT_CACHE_MAX = 64;
53905
- function collectSystemCaPems(env = process.env) {
53906
- const pems = [];
53907
- const seen = /* @__PURE__ */ new Set();
53908
- const pushFile = (file) => {
53909
- try {
53910
- const text = fs3.readFileSync(file, "utf8");
53911
- if (!text.includes("BEGIN CERTIFICATE") || seen.has(text)) return false;
53912
- seen.add(text);
53913
- pems.push(text);
53914
- return true;
53915
- } catch {
53916
- }
53917
- return false;
53918
- };
53919
- const userBundle = env.SSL_CERT_FILE?.trim();
53920
- if (userBundle && pushFile(userBundle)) return pems;
53921
- for (const candidate of PLATFORM_CA_CANDIDATES) {
53922
- if (pushFile(candidate)) break;
53923
- }
53924
- return pems;
53925
- }
53926
- function writeCombinedBundle() {
53927
- const certs = /* @__PURE__ */ new Set();
53928
- for (const pem of collectSystemCaPems()) certs.add(pem.trim());
53929
- for (const pem of tls2.rootCertificates) certs.add(pem.trim());
53930
- certs.add(rootCertPem.trim());
53931
- const body = [...certs].map((pem) => pem.endsWith("\n") ? pem : pem + "\n").join("");
53932
- fs3.writeFileSync(path7.join(caDir(), COMBINED_CA_FILE), body, { mode: 420 });
53933
- }
53934
- function generateRootCA() {
53935
- const keys = import_node_forge.default.pki.rsa.generateKeyPair({ bits: 2048 });
53936
- const cert = import_node_forge.default.pki.createCertificate();
53937
- cert.publicKey = keys.publicKey;
53938
- cert.serialNumber = "01";
53939
- cert.validity.notBefore = /* @__PURE__ */ new Date();
53940
- cert.validity.notAfter = /* @__PURE__ */ new Date();
53941
- cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10);
53942
- const attrs = [
53943
- { name: "commonName", value: ROOT_CN },
53944
- { name: "organizationName", value: "billion-context" }
53945
- ];
53946
- cert.setSubject(attrs);
53947
- cert.setIssuer(attrs);
53948
- cert.setExtensions([
53949
- { name: "basicConstraints", cA: true },
53950
- { name: "keyUsage", keyCertSign: true, cRLSign: true, digitalSignature: true },
53951
- { name: "subjectKeyIdentifier" }
53952
- ]);
53953
- cert.sign(keys.privateKey, import_node_forge.default.md.sha256.create());
53954
- return {
53955
- cert: import_node_forge.default.pki.certificateToPem(cert),
53956
- key: import_node_forge.default.pki.privateKeyToPem(keys.privateKey)
53957
- };
53958
- }
53959
- function ensureRootCA() {
53960
- if (rootCertPem && rootKeyPem) {
53961
- writeCombinedBundle();
53962
- return;
53963
- }
53964
- const dir = caDir();
53965
- fs3.mkdirSync(dir, { recursive: true });
53966
- const certPath = path7.join(dir, ROOT_CERT_FILE);
53967
- const keyPath = path7.join(dir, ROOT_KEY_FILE);
53968
- if (fs3.existsSync(certPath) && fs3.existsSync(keyPath)) {
53969
- rootCertPem = fs3.readFileSync(certPath, "utf8");
53970
- rootKeyPem = fs3.readFileSync(keyPath, "utf8");
53971
- rootCert = import_node_forge.default.pki.certificateFromPem(rootCertPem);
53972
- rootKey = import_node_forge.default.pki.privateKeyFromPem(rootKeyPem);
53973
- writeCombinedBundle();
53974
- return;
53975
- }
53976
- const { cert, key } = generateRootCA();
53977
- fs3.writeFileSync(certPath, cert, { mode: 420 });
53978
- fs3.writeFileSync(keyPath, key, { mode: 384 });
53979
- rootCertPem = cert;
53980
- rootKeyPem = key;
53981
- rootCert = import_node_forge.default.pki.certificateFromPem(cert);
53982
- rootKey = import_node_forge.default.pki.privateKeyFromPem(key);
53983
- writeCombinedBundle();
53984
- }
53985
- function getSecureContext(host) {
53986
- if (!rootCertPem || !rootKeyPem || !rootCert || !rootKey) {
53987
- throw new Error("CA not initialized \u2014 call ensureRootCA() first");
53988
- }
53989
- const cached = secureContextCache.get(host);
53990
- if (cached) {
53991
- secureContextCache.delete(host);
53992
- secureContextCache.set(host, cached);
53993
- return cached;
53994
- }
53995
- const keys = import_node_forge.default.pki.rsa.generateKeyPair({ bits: 2048 });
53996
- const cert = import_node_forge.default.pki.createCertificate();
53997
- cert.publicKey = keys.publicKey;
53998
- cert.serialNumber = Date.now().toString() + Math.floor(Math.random() * 1e6).toString();
53999
- cert.validity.notBefore = /* @__PURE__ */ new Date();
54000
- cert.validity.notAfter = /* @__PURE__ */ new Date();
54001
- cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 2);
54002
- cert.setSubject([{ name: "commonName", value: host }]);
54003
- cert.setIssuer(rootCert.subject.attributes);
54004
- cert.setExtensions([
54005
- { name: "basicConstraints", cA: false },
54006
- { name: "keyUsage", digitalSignature: true, keyEncipherment: true },
54007
- { name: "extKeyUsage", serverAuth: true },
54008
- { name: "subjectAltName", altNames: [{ type: 2, value: host }] }
54009
- ]);
54010
- cert.sign(rootKey, import_node_forge.default.md.sha256.create());
54011
- const ctx = tls2.createSecureContext({
54012
- cert: import_node_forge.default.pki.certificateToPem(cert),
54013
- key: import_node_forge.default.pki.privateKeyToPem(keys.privateKey),
54014
- ca: rootCertPem
54015
- });
54016
- secureContextCache.set(host, ctx);
54017
- if (secureContextCache.size > SECURE_CONTEXT_CACHE_MAX) {
54018
- const oldest = secureContextCache.keys().next().value;
54019
- if (oldest !== void 0) secureContextCache.delete(oldest);
54020
- }
54021
- return ctx;
54022
- }
54023
-
54024
54096
  // src/discover.ts
54025
54097
  import fs5 from "fs";
54026
54098
  import os2 from "os";
@@ -56070,6 +56142,10 @@ function prepareAnthropic(parsed, req, opts, core, config, prompts, log2, sessio
56070
56142
  let rebuiltMessages = parsed.messages;
56071
56143
  let systemOut = parsed.system;
56072
56144
  let toolsOut = parsed.tools;
56145
+ const strippedPanels = stripAcpPanelMessages(parsed.messages);
56146
+ if (strippedPanels > 0) {
56147
+ log2("info", `[${sessionId}] stripped ${strippedPanels} ACP panel message(s) before projection (UI-only, issue #359)`);
56148
+ }
56073
56149
  try {
56074
56150
  const { msgs, cacheControls } = anthropicToCore(parsed);
56075
56151
  originalMessages = msgs;
@@ -56126,6 +56202,10 @@ function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session,
56126
56202
  const isTitleGen = maxTokens <= 200;
56127
56203
  const shouldInject = opts.compress.injectTool && !isTitleGen;
56128
56204
  const injectTools = shouldInject && !pluginMode;
56205
+ const strippedPanels = stripAcpPanelMessages(parsed.messages);
56206
+ if (strippedPanels > 0) {
56207
+ log2("info", `[${sessionId}] stripped ${strippedPanels} ACP panel message(s) before projection (UI-only, issue #359)`);
56208
+ }
56129
56209
  try {
56130
56210
  const { msgs, systemText } = openaiToCore(parsed);
56131
56211
  openaiSystemText = systemText;
@@ -56207,6 +56287,10 @@ function prepareResponses(parsed, req, opts, core, config, prompts, log2, sessio
56207
56287
  if (droppedEmpty > 0) {
56208
56288
  log2("info", `[${sessionId}] dropped ${droppedEmpty} whitespace-only message item(s) before projection (flattened-turn artifact)`);
56209
56289
  }
56290
+ const strippedPanels = stripAcpPanelResponsesInput(parsed.input);
56291
+ if (strippedPanels > 0) {
56292
+ log2("info", `[${sessionId}] stripped ${strippedPanels} ACP panel message(s) before projection (UI-only, issue #359)`);
56293
+ }
56210
56294
  const shouldInject = opts.compress.injectTool;
56211
56295
  const injectTools = shouldInject && !pluginMode;
56212
56296
  const isCompactionTrigger = hasCompactionTrigger(parsed.input);
@@ -56445,6 +56529,20 @@ function injectResponsesTool(tools, toolsToAdd = ACP_TOOLS_RESPONSES) {
56445
56529
  const additions = toolsToAdd.filter((t) => !present.has(t.name));
56446
56530
  return [...tools, ...additions];
56447
56531
  }
56532
+ var loggedUpstreamProxyDecisions = /* @__PURE__ */ new Set();
56533
+ function logUpstreamProxyDecision(opts, upstreamUrl, decision) {
56534
+ if (!upstreamUrl) return;
56535
+ let host = upstreamUrl;
56536
+ try {
56537
+ host = new URL(upstreamUrl).host;
56538
+ } catch {
56539
+ }
56540
+ const key = `${host}|${decision.proxy ?? ""}|${decision.source}`;
56541
+ if (loggedUpstreamProxyDecisions.has(key)) return;
56542
+ loggedUpstreamProxyDecisions.add(key);
56543
+ const via = decision.proxy ? `via ${maskUrlForLog(decision.proxy)}` : "direct";
56544
+ logMsg(opts, "info", `[upstream-proxy] ${maskHostPortForLog(host)} ${via} (source=${decision.source})`);
56545
+ }
56448
56546
  function buildForwardTarget(req, opts, route, affinity, hopMarker) {
56449
56547
  const reqUrl = req.url ?? "";
56450
56548
  const isAbsoluteUrl = /^https?:\/\//i.test(reqUrl);
@@ -56468,8 +56566,9 @@ function buildForwardTarget(req, opts, route, affinity, hopMarker) {
56468
56566
  if (affinity && !clientConversationHeader(req.headers)) {
56469
56567
  headers["x-session-id"] = affinity;
56470
56568
  }
56471
- const proxyUrl = resolveProxy(opts.routes, opts.proxy, route?.rewrittenUrl ?? upstreamUrl, opts.proxyFallback);
56472
- return { upstreamUrl, headers, proxyUrl };
56569
+ const decision = resolveProxyDecision(opts.routes, opts.proxy, route?.rewrittenUrl ?? upstreamUrl, opts.proxyFallback);
56570
+ logUpstreamProxyDecision(opts, upstreamUrl, decision);
56571
+ return { upstreamUrl, headers, proxyUrl: decision.proxy };
56473
56572
  }
56474
56573
  function isPreflightFailFast(outcome) {
56475
56574
  return "failFast" in outcome;
@@ -62263,6 +62362,8 @@ Launcher (bili pi / bili codex / bili claude / bili omp / bili opencode / bili h
62263
62362
  auto-whitelisted for MITM so the proxy TLS-terminates exactly the hosts the
62264
62363
  client uses; HTTP / localhost providers go direct. pi/claude trust the CA
62265
62364
  via NODE_EXTRA_CA_CERTS, codex via SSL_CERT_FILE. Proxy killed on client exit.
62365
+ bili flags (-F, --mitm-domain, --port, ...) must precede the client name;
62366
+ everything after the client name is passed through to the client.
62266
62367
  bili pi # launch pi through the proxy
62267
62368
  bili pi -- print "hi" # args after the client are passed through
62268
62369
  bili pi-test # pi through the proxy with extensions off (proxy owns compression)
@@ -62272,9 +62373,12 @@ Launcher (bili pi / bili codex / bili claude / bili omp / bili opencode / bili h
62272
62373
  bili hermes # launch hermes-agent through the proxy (/bili/ rewrite of ~/.hermes/config.yaml)
62273
62374
  bili dsh --profile web "task" # launch deepseek-harness through the proxy (/bili/ rewrite of ~/.dsh/settings.yaml)
62274
62375
  bili test pi # quick end-to-end check of the pi path
62275
- bili pi --mitm-domain api.foo.com # add a domain to the MITM whitelist
62376
+ bili --mitm-domain api.foo.com pi # add a domain to the MITM whitelist (flags precede the client)
62377
+ bili -F http://127.0.0.1:7897 codex # route bili's upstream through a proxy (gost-style -F)
62276
62378
 
62277
62379
  Options (override config file / env):
62380
+ -F <url> upstream proxy to forward through (gost-style;
62381
+ http://host:port; must precede the client name)
62278
62382
  --port <N> listen port (default 8787)
62279
62383
  --host <ADDR> listen host (default 127.0.0.1)
62280
62384
  --mitm-domain <domain> extra MITM domain (repeatable; launcher only)
@@ -62353,6 +62457,7 @@ function parseArgs(argv) {
62353
62457
  exportOutput = val;
62354
62458
  break;
62355
62459
  }
62460
+ case "-F":
62356
62461
  case "--port":
62357
62462
  case "--host":
62358
62463
  case "--config":
@@ -62369,6 +62474,7 @@ function parseArgs(argv) {
62369
62474
  else if (a === "--config") overrides.BILI_CONFIG_FILE = val;
62370
62475
  else if (a === "--origin") overrides.BILI_MCP_PROXY = val;
62371
62476
  else if (a === "--bin") process.env.BILI_CLIENT_BIN = val;
62477
+ else if (a === "-F") overrides.BILI_UPSTREAM_PROXY = val;
62372
62478
  else overrides.BILI_PLUGIN_AGENT = val;
62373
62479
  break;
62374
62480
  }