billion-context 0.1.64 → 0.1.65

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
@@ -50294,7 +50294,7 @@ function refNum2(ref) {
50294
50294
  function applyRanges(ranges, ctx) {
50295
50295
  if (ranges.length === 0) {
50296
50296
  ctx.log("[acp-proxy: compress call had no valid ranges; nothing compressed.]");
50297
- return "[Compression FAILED: no valid ranges parsed from the tool call. Check your startId/endId parameters.]";
50297
+ return "[Compression FAILED: no valid ranges parsed. compress requires a non-empty 'content' array of {startId, endId, summary} ranges, where startId/endId are mNNNNN message refs from the conversation. Re-issue the compress call with a valid content array.]";
50298
50298
  }
50299
50299
  ctx.log(`[acp-proxy: compress requested ${ranges.length} range(s): ${ranges.map((r) => `${r.startRef}\u2013${r.endRef}`).join(", ")}]`);
50300
50300
  ctx.log(`[acp-proxy: ctx has ${ctx.messages.length} message(s), state has ${ctx.session.state.messageRefs?.byRef?.size ?? "?"} ref(s) mapped]`);
@@ -50626,6 +50626,153 @@ import { readFileSync as readFileSync3 } from "fs";
50626
50626
  import { dirname as dirname4, join as join3 } from "path";
50627
50627
  import { fileURLToPath } from "url";
50628
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
+
50629
50776
  // src/web/client.ts
50630
50777
  var WEB_CLIENT = `
50631
50778
  const byId=(id)=>document.getElementById(id);
@@ -50658,7 +50805,13 @@ var WEB_STYLES = `
50658
50805
  `;
50659
50806
 
50660
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
+ }
50661
50811
  function renderPage(origin, version2) {
50812
+ const caPath = rootCaPath();
50813
+ const caPathEsc = escapeHtml(caPath);
50814
+ const caReady = existsSync3(caPath);
50662
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>
50663
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>
50664
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>
@@ -50673,14 +50826,14 @@ function renderPage(origin, version2) {
50673
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>
50674
50827
  <h2 class="section-title">B. \u8BC1\u4E66\u63A5\u5165\u65B9\u5F0F\uFF08zcode \u7B49\u7C7B\u578B\uFF09</h2>
50675
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>
50676
- <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>
50677
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>
50678
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>
50679
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>`;
50680
50833
  }
50681
50834
 
50682
50835
  // src/web/api.ts
50683
- 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";
50684
50837
  import { dirname as dirname3 } from "path";
50685
50838
  import { randomUUID as randomUUID2 } from "crypto";
50686
50839
  function readConfig() {
@@ -50688,7 +50841,7 @@ function readConfig() {
50688
50841
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
50689
50842
  }
50690
50843
  function configParseError() {
50691
- if (!existsSync3(configFile())) return null;
50844
+ if (!existsSync4(configFile())) return null;
50692
50845
  const parsed = safeReadJson(configFile());
50693
50846
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return null;
50694
50847
  return `config file is not valid JSON: ${configFile()}`;
@@ -51327,6 +51480,26 @@ function reserveOutputHeadroom(window2, maxOutput) {
51327
51480
  }
51328
51481
  return window2;
51329
51482
  }
51483
+ function hoistMidSystemMessages(messages) {
51484
+ let lead = 0;
51485
+ while (lead < messages.length && (messages[lead].role === "system" || messages[lead].role === "developer")) lead++;
51486
+ if (lead === messages.length) return messages;
51487
+ let midCount = 0;
51488
+ for (let i = lead; i < messages.length; i++) {
51489
+ const r = messages[i].role;
51490
+ if (r === "system" || r === "developer") midCount++;
51491
+ }
51492
+ if (midCount === 0) return messages;
51493
+ const head = messages.slice(0, lead);
51494
+ const mid = [];
51495
+ const rest = [];
51496
+ for (let i = lead; i < messages.length; i++) {
51497
+ const r = messages[i].role;
51498
+ if (r === "system" || r === "developer") mid.push(messages[i]);
51499
+ else rest.push(messages[i]);
51500
+ }
51501
+ return [...head, ...mid, ...rest];
51502
+ }
51330
51503
  function shouldReserveOutputHeadroom(protocol) {
51331
51504
  return protocol !== "anthropic";
51332
51505
  }
@@ -51988,7 +52161,7 @@ function createOpenaiAdapter(requestBody, clientSystem) {
51988
52161
  );
51989
52162
  return {
51990
52163
  buildRequest(coreMessages, systemPrompt, body) {
51991
- const messages = coreToOpenai(coreMessages);
52164
+ const messages = hoistMidSystemMessages(coreToOpenai(coreMessages));
51992
52165
  const withSys = injectOpenaiSystem(messages, [clientSystem, systemPrompt].filter((p2) => typeof p2 === "string" && p2.length > 0));
51993
52166
  return { ...body, messages: withSys };
51994
52167
  },
@@ -52134,7 +52307,8 @@ function createOpenaiAdapter(requestBody, clientSystem) {
52134
52307
  const fn = tc.function;
52135
52308
  const name = typeof fn?.name === "string" ? fn.name : "";
52136
52309
  const id = typeof tc.id === "string" ? tc.id : "";
52137
- const args = typeof fn?.arguments === "string" ? fn.arguments : "";
52310
+ const rawArgs = fn?.arguments;
52311
+ const args = typeof rawArgs === "string" ? rawArgs : rawArgs !== null && typeof rawArgs === "object" ? JSON.stringify(rawArgs) : "";
52138
52312
  let buf = pending.get(idx);
52139
52313
  if (!buf) {
52140
52314
  buf = { index: idx, id, name, arguments: args };
@@ -53345,13 +53519,13 @@ function buildStatusPanel(input) {
53345
53519
 
53346
53520
  // src/plugin.ts
53347
53521
  import { fileURLToPath as fileURLToPath2 } from "url";
53348
- import fs2 from "fs";
53349
- import path6 from "path";
53522
+ import fs3 from "fs";
53523
+ import path7 from "path";
53350
53524
  var PROXY_VERSION = (() => {
53351
53525
  try {
53352
53526
  const here = fileURLToPath2(import.meta.url);
53353
- const pkg = path6.join(path6.dirname(here), "..", "package.json");
53354
- return JSON.parse(fs2.readFileSync(pkg, "utf8")).version ?? "dev";
53527
+ const pkg = path7.join(path7.dirname(here), "..", "package.json");
53528
+ return JSON.parse(fs3.readFileSync(pkg, "utf8")).version ?? "dev";
53355
53529
  } catch {
53356
53530
  return "dev";
53357
53531
  }
@@ -53363,8 +53537,8 @@ var PLUGIN_PROTOCOL_VERSION = 1;
53363
53537
  var VERSION = (() => {
53364
53538
  try {
53365
53539
  const here = fileURLToPath2(import.meta.url);
53366
- const pkg = path6.join(path6.dirname(here), "..", "package.json");
53367
- return JSON.parse(fs2.readFileSync(pkg, "utf8")).version ?? "dev";
53540
+ const pkg = path7.join(path7.dirname(here), "..", "package.json");
53541
+ return JSON.parse(fs3.readFileSync(pkg, "utf8")).version ?? "dev";
53368
53542
  } catch {
53369
53543
  return "dev";
53370
53544
  }
@@ -53393,14 +53567,14 @@ function pluginReportedContextWindow(headers) {
53393
53567
  var MAX_PLUGIN_CONVERSATIONS = 1024;
53394
53568
  var conversations = /* @__PURE__ */ new Map();
53395
53569
  var remembered = /* @__PURE__ */ new Map();
53396
- var conversationsFile = () => path6.join(stateDir(), "plugin-conversations.json");
53570
+ var conversationsFile = () => path7.join(stateDir(), "plugin-conversations.json");
53397
53571
  var conversationsSaveTimer;
53398
53572
  function writeConversationsFile() {
53399
53573
  try {
53400
53574
  const obj = {};
53401
53575
  for (const [k2, v2] of conversations) obj[k2] = v2;
53402
- fs2.mkdirSync(stateDir(), { recursive: true });
53403
- fs2.writeFileSync(conversationsFile(), JSON.stringify(obj));
53576
+ fs3.mkdirSync(stateDir(), { recursive: true });
53577
+ fs3.writeFileSync(conversationsFile(), JSON.stringify(obj));
53404
53578
  } catch {
53405
53579
  }
53406
53580
  }
@@ -53420,7 +53594,7 @@ function flushConversations() {
53420
53594
  }
53421
53595
  function loadConversations() {
53422
53596
  try {
53423
- const raw = fs2.readFileSync(conversationsFile(), "utf8");
53597
+ const raw = fs3.readFileSync(conversationsFile(), "utf8");
53424
53598
  const obj = JSON.parse(raw);
53425
53599
  for (const [k2, v2] of Object.entries(obj)) {
53426
53600
  if (v2 && typeof v2.sessionId === "string" && v2.sessionId.length > 0) {
@@ -53881,147 +54055,6 @@ async function pipePluginJson(stream2, res, session, protocol) {
53881
54055
  // src/mitm.ts
53882
54056
  import tls3 from "tls";
53883
54057
 
53884
- // src/ca.ts
53885
- var import_node_forge = __toESM(require_lib(), 1);
53886
- import fs3 from "fs";
53887
- import path7 from "path";
53888
- import tls2 from "tls";
53889
- var ROOT_CERT_FILE = "root-ca.pem";
53890
- var ROOT_KEY_FILE = "root-ca-key.pem";
53891
- var COMBINED_CA_FILE = "combined-ca.pem";
53892
- var ROOT_CN = "billion-context MITM Root CA";
53893
- var PLATFORM_CA_CANDIDATES = process.platform === "darwin" ? ["/etc/ssl/cert.pem", "/private/etc/ssl/cert.pem"] : [
53894
- "/etc/ssl/certs/ca-certificates.crt",
53895
- "/etc/pki/tls/certs/ca-bundle.crt",
53896
- "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
53897
- "/etc/ssl/ca-bundle.pem",
53898
- "/etc/ssl/cert.pem"
53899
- ];
53900
- var rootCertPem;
53901
- var rootKeyPem;
53902
- var rootCert;
53903
- var rootKey;
53904
- var secureContextCache = /* @__PURE__ */ new Map();
53905
- var SECURE_CONTEXT_CACHE_MAX = 64;
53906
- function collectSystemCaPems(env = process.env) {
53907
- const pems = [];
53908
- const seen = /* @__PURE__ */ new Set();
53909
- const pushFile = (file) => {
53910
- try {
53911
- const text = fs3.readFileSync(file, "utf8");
53912
- if (!text.includes("BEGIN CERTIFICATE") || seen.has(text)) return false;
53913
- seen.add(text);
53914
- pems.push(text);
53915
- return true;
53916
- } catch {
53917
- }
53918
- return false;
53919
- };
53920
- const userBundle = env.SSL_CERT_FILE?.trim();
53921
- if (userBundle && pushFile(userBundle)) return pems;
53922
- for (const candidate of PLATFORM_CA_CANDIDATES) {
53923
- if (pushFile(candidate)) break;
53924
- }
53925
- return pems;
53926
- }
53927
- function writeCombinedBundle() {
53928
- const certs = /* @__PURE__ */ new Set();
53929
- for (const pem of collectSystemCaPems()) certs.add(pem.trim());
53930
- for (const pem of tls2.rootCertificates) certs.add(pem.trim());
53931
- certs.add(rootCertPem.trim());
53932
- const body = [...certs].map((pem) => pem.endsWith("\n") ? pem : pem + "\n").join("");
53933
- fs3.writeFileSync(path7.join(caDir(), COMBINED_CA_FILE), body, { mode: 420 });
53934
- }
53935
- function generateRootCA() {
53936
- const keys = import_node_forge.default.pki.rsa.generateKeyPair({ bits: 2048 });
53937
- const cert = import_node_forge.default.pki.createCertificate();
53938
- cert.publicKey = keys.publicKey;
53939
- cert.serialNumber = "01";
53940
- cert.validity.notBefore = /* @__PURE__ */ new Date();
53941
- cert.validity.notAfter = /* @__PURE__ */ new Date();
53942
- cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10);
53943
- const attrs = [
53944
- { name: "commonName", value: ROOT_CN },
53945
- { name: "organizationName", value: "billion-context" }
53946
- ];
53947
- cert.setSubject(attrs);
53948
- cert.setIssuer(attrs);
53949
- cert.setExtensions([
53950
- { name: "basicConstraints", cA: true },
53951
- { name: "keyUsage", keyCertSign: true, cRLSign: true, digitalSignature: true },
53952
- { name: "subjectKeyIdentifier" }
53953
- ]);
53954
- cert.sign(keys.privateKey, import_node_forge.default.md.sha256.create());
53955
- return {
53956
- cert: import_node_forge.default.pki.certificateToPem(cert),
53957
- key: import_node_forge.default.pki.privateKeyToPem(keys.privateKey)
53958
- };
53959
- }
53960
- function ensureRootCA() {
53961
- if (rootCertPem && rootKeyPem) {
53962
- writeCombinedBundle();
53963
- return;
53964
- }
53965
- const dir = caDir();
53966
- fs3.mkdirSync(dir, { recursive: true });
53967
- const certPath = path7.join(dir, ROOT_CERT_FILE);
53968
- const keyPath = path7.join(dir, ROOT_KEY_FILE);
53969
- if (fs3.existsSync(certPath) && fs3.existsSync(keyPath)) {
53970
- rootCertPem = fs3.readFileSync(certPath, "utf8");
53971
- rootKeyPem = fs3.readFileSync(keyPath, "utf8");
53972
- rootCert = import_node_forge.default.pki.certificateFromPem(rootCertPem);
53973
- rootKey = import_node_forge.default.pki.privateKeyFromPem(rootKeyPem);
53974
- writeCombinedBundle();
53975
- return;
53976
- }
53977
- const { cert, key } = generateRootCA();
53978
- fs3.writeFileSync(certPath, cert, { mode: 420 });
53979
- fs3.writeFileSync(keyPath, key, { mode: 384 });
53980
- rootCertPem = cert;
53981
- rootKeyPem = key;
53982
- rootCert = import_node_forge.default.pki.certificateFromPem(cert);
53983
- rootKey = import_node_forge.default.pki.privateKeyFromPem(key);
53984
- writeCombinedBundle();
53985
- }
53986
- function getSecureContext(host) {
53987
- if (!rootCertPem || !rootKeyPem || !rootCert || !rootKey) {
53988
- throw new Error("CA not initialized \u2014 call ensureRootCA() first");
53989
- }
53990
- const cached = secureContextCache.get(host);
53991
- if (cached) {
53992
- secureContextCache.delete(host);
53993
- secureContextCache.set(host, cached);
53994
- return cached;
53995
- }
53996
- const keys = import_node_forge.default.pki.rsa.generateKeyPair({ bits: 2048 });
53997
- const cert = import_node_forge.default.pki.createCertificate();
53998
- cert.publicKey = keys.publicKey;
53999
- cert.serialNumber = Date.now().toString() + Math.floor(Math.random() * 1e6).toString();
54000
- cert.validity.notBefore = /* @__PURE__ */ new Date();
54001
- cert.validity.notAfter = /* @__PURE__ */ new Date();
54002
- cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 2);
54003
- cert.setSubject([{ name: "commonName", value: host }]);
54004
- cert.setIssuer(rootCert.subject.attributes);
54005
- cert.setExtensions([
54006
- { name: "basicConstraints", cA: false },
54007
- { name: "keyUsage", digitalSignature: true, keyEncipherment: true },
54008
- { name: "extKeyUsage", serverAuth: true },
54009
- { name: "subjectAltName", altNames: [{ type: 2, value: host }] }
54010
- ]);
54011
- cert.sign(rootKey, import_node_forge.default.md.sha256.create());
54012
- const ctx = tls2.createSecureContext({
54013
- cert: import_node_forge.default.pki.certificateToPem(cert),
54014
- key: import_node_forge.default.pki.privateKeyToPem(keys.privateKey),
54015
- ca: rootCertPem
54016
- });
54017
- secureContextCache.set(host, ctx);
54018
- if (secureContextCache.size > SECURE_CONTEXT_CACHE_MAX) {
54019
- const oldest = secureContextCache.keys().next().value;
54020
- if (oldest !== void 0) secureContextCache.delete(oldest);
54021
- }
54022
- return ctx;
54023
- }
54024
-
54025
54058
  // src/discover.ts
54026
54059
  import fs5 from "fs";
54027
54060
  import os2 from "os";
@@ -54537,6 +54570,15 @@ var DEFAULT_MITM_DOMAINS = [
54537
54570
  "chatgpt.com"
54538
54571
  ];
54539
54572
  var MITM_UPSTREAM_KEY = "__biliMitmUpstream";
54573
+ var warnedCertRejected = false;
54574
+ var CERT_REJECT_RE = /alert (?:certificate unknown|unknown ca|bad certificate|certificate expired|certificate revoked|unsupported certificate)/i;
54575
+ function noteMitmTlsError(host, port, message, log2) {
54576
+ log2(`mitm ${maskHostForLog(host)}:${port} TLS error: ${message}`);
54577
+ if (CERT_REJECT_RE.test(message) && !warnedCertRejected) {
54578
+ warnedCertRejected = true;
54579
+ log2(`mitm ${maskHostForLog(host)}:${port} client REJECTED the MITM certificate \u2014 it does not trust bili's root CA. Install it in the client's/system trust store, then restart the client. CA: ${rootCaPath()}`);
54580
+ }
54581
+ }
54540
54582
  var MITM_HANDSHAKE_TIMEOUT_MS_DEFAULT = 1e4;
54541
54583
  function mitmHandshakeTimeoutMs() {
54542
54584
  const v2 = Number.parseInt(process.env.BILI_MITM_HANDSHAKE_TIMEOUT_MS ?? "", 10);
@@ -54602,6 +54644,7 @@ function tunnelThrough(clientSocket, host, port, head, log2, proxyUrl) {
54602
54644
  if (head.length > 0) upstream.write(head);
54603
54645
  upstream.pipe(clientSocket);
54604
54646
  clientSocket.pipe(upstream);
54647
+ log2(`tunnel ${maskHostForLog(host)}:${port} established (blind TCP, not decrypted)`);
54605
54648
  const cleanup = (where, err2) => {
54606
54649
  log2(`tunnel ${maskHostForLog(host)}:${port} ${where} closed: ${maskHostInText(err2.message, host)}`);
54607
54650
  upstream.destroy();
@@ -54636,7 +54679,7 @@ function doMitm(server, clientSocket, host, port, head, log2) {
54636
54679
  }
54637
54680
  tlsSocket[MITM_UPSTREAM_KEY] = `https://${host}`;
54638
54681
  tlsSocket.on("error", (err2) => {
54639
- log2(`mitm ${maskHostForLog(host)}:${port} TLS error: ${err2.message}`);
54682
+ noteMitmTlsError(host, port, err2.message, log2);
54640
54683
  tlsSocket.destroy();
54641
54684
  clientSocket.destroy();
54642
54685
  });
@@ -55414,7 +55457,7 @@ function anthropicBetaContextWindow(headers) {
55414
55457
  function safeSessionId(id) {
55415
55458
  return (id ?? "unknown").replace(/[^a-zA-Z0-9._-]/g, "_");
55416
55459
  }
55417
- var NO_IDENTITY_MESSAGE = "Missing stable conversation identity. Send one of the headers: x-session-id, x-session-affinity, x-acp-session, x-opencode-session, x-claude-code-session-id, session-id \u2014 or body session_id / prompt_cache_key (responses/openai). Requests replaying a conversation history are matched by content prefix affinity (#309); this one carries no usable history signal.";
55460
+ var NO_IDENTITY_MESSAGE = "Missing stable conversation identity. Send one of the headers: x-session-id, x-session-affinity, x-acp-session, x-opencode-session, x-claude-code-session-id, session-id \u2014 or body session_id / prompt_cache_key (responses/openai/anthropic). Requests replaying a conversation history are matched by content prefix affinity (#309); this one carries no usable history signal.";
55418
55461
  var UPSTREAM_HOP_HEADERS = /* @__PURE__ */ new Set([
55419
55462
  "host",
55420
55463
  "content-length",
@@ -55753,6 +55796,11 @@ async function handle(req, res, opts, core, config, log2, instanceId) {
55753
55796
  return;
55754
55797
  }
55755
55798
  }
55799
+ if (isAdminPath) {
55800
+ res.writeHead(404, { "content-type": "application/json" });
55801
+ res.end(JSON.stringify({ error: { type: "not_found", message: "no such management endpoint" } }));
55802
+ return;
55803
+ }
55756
55804
  let bodyBuffer;
55757
55805
  let urlPath;
55758
55806
  let responsesCompact;
@@ -55839,9 +55887,9 @@ ${bodyBuffer.toString("utf8")}`);
55839
55887
  const betaWindow = anthropicBetaContextWindow(req.headers);
55840
55888
  const pluginWindow = pluginReportedContextWindow(req.headers);
55841
55889
  const launcherWindow = launcherContextWindow(model);
55842
- const peekWindow = host ? peekRegistryContext(model, host) : void 0;
55843
55890
  const configuredWindow = resolveConfiguredContextLimit(opts.routes, embeddedUrl, model);
55844
- let native = betaWindow ?? pluginWindow ?? launcherWindow ?? peekWindow ?? configuredWindow ?? lookupContextLimit(model);
55891
+ const peekWindow = host ? peekRegistryContext(model, host) : void 0;
55892
+ let native = betaWindow ?? pluginWindow ?? launcherWindow ?? configuredWindow ?? peekWindow ?? lookupContextLimit(model);
55845
55893
  const operatorWindowTuned = resolveCompress(opts.routes, embeddedUrl, model, opts.compress).modelContextLimit !== void 0;
55846
55894
  nativeFromFallback = !betaWindow && !pluginWindow && !launcherWindow && !peekWindow && !configuredWindow && !operatorWindowTuned;
55847
55895
  if (!native && host) {
@@ -55876,11 +55924,16 @@ ${bodyBuffer.toString("utf8")}`);
55876
55924
  convHeader ? { value: openaiSignal, source: "header", clientProvided: true } : { value: openaiSignal, source: "content-fingerprint", clientProvided: false },
55877
55925
  parsed
55878
55926
  ) : void 0;
55879
- const conversation = protocol === "anthropic" ? conversationSignalAnthropic(parsed, convHeader) : protocol === "openai" ? openaiIdentity?.value ?? openaiSignal : codexTurn ? codexTurn.value : subagentNamespace(
55927
+ const anthropicSignal = protocol === "anthropic" ? conversationSignalAnthropic(parsed, convHeader) : "";
55928
+ const anthropicIdentity = protocol === "anthropic" ? preferPromptCacheKeyIdentity(
55929
+ convHeader ? { value: anthropicSignal, source: "header", clientProvided: true } : { value: anthropicSignal, source: "content-fingerprint", clientProvided: false },
55930
+ parsed
55931
+ ) : void 0;
55932
+ const conversation = protocol === "anthropic" ? anthropicIdentity?.value ?? anthropicSignal : protocol === "openai" ? openaiIdentity?.value ?? openaiSignal : codexTurn ? codexTurn.value : subagentNamespace(
55880
55933
  responsesIdentity?.value ?? conversationSignalResponses(parsed, convHeader),
55881
55934
  parsed.instructions
55882
55935
  );
55883
- const clientProvided = protocol === "responses" ? responsesIdentity?.clientProvided ?? false : protocol === "openai" ? openaiIdentity?.clientProvided ?? false : !!convHeader;
55936
+ const clientProvided = protocol === "responses" ? responsesIdentity?.clientProvided ?? false : protocol === "openai" ? openaiIdentity?.clientProvided ?? false : protocol === "anthropic" ? anthropicIdentity?.clientProvided ?? false : !!convHeader;
55884
55937
  let anonAffinity = null;
55885
55938
  if (!clientProvided) {
55886
55939
  const anonMessages = protocol === "responses" ? parsed.input ?? [] : parsed.messages ?? [];
@@ -55904,7 +55957,7 @@ ${bodyBuffer.toString("utf8")}`);
55904
55957
  }
55905
55958
  }
55906
55959
  const sessionId = anonAffinity ? anonAffinity.sessionId : conversation;
55907
- const bodyIdentity = responsesIdentity ?? openaiIdentity;
55960
+ const bodyIdentity = responsesIdentity ?? openaiIdentity ?? anthropicIdentity;
55908
55961
  const affinity = affinityToken(bodyIdentity ?? {
55909
55962
  value: clientConv ?? conversation,
55910
55963
  source: clientConv ? "header" : "generated",
@@ -55942,7 +55995,7 @@ ${bodyBuffer.toString("utf8")}`);
55942
55995
  session.metadata.effectiveContextLimit = reqConfig.modelContextLimit;
55943
55996
  recordPluginSession(pluginConversation ?? conversation, session.id);
55944
55997
  }
55945
- if (protocol === "responses" || protocol === "openai") {
55998
+ if (protocol === "responses" || protocol === "openai" || protocol === "anthropic") {
55946
55999
  const pck = parsed.prompt_cache_key;
55947
56000
  if (typeof pck === "string" && pck.trim().length > 0) {
55948
56001
  recordPluginSession(pck.trim(), session.id);
@@ -56065,6 +56118,10 @@ function prepareAnthropic(parsed, req, opts, core, config, prompts, log2, sessio
56065
56118
  const stream2 = parsed.stream === true;
56066
56119
  ++session.stats.requests;
56067
56120
  const injectTools = opts.compress.injectTool && !pluginMode;
56121
+ if (isAutoModeClassifier(parsed)) {
56122
+ log2("info", `[${sessionId}] auto-mode classifier passthrough (skipping compress injection)`);
56123
+ return { body: JSON.stringify(parsed), session, processedMessages: [], originalMessages: [], anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: false, pluginMode, nudge: void 0, prompts };
56124
+ }
56068
56125
  let processedMessages = [];
56069
56126
  let originalMessages = [];
56070
56127
  let nudge;
@@ -56111,6 +56168,7 @@ function prepareAnthropic(parsed, req, opts, core, config, prompts, log2, sessio
56111
56168
  snapshotMessages(session, originalMessages);
56112
56169
  markDirty(session);
56113
56170
  const rebuilt = { ...parsed, messages: rebuiltMessages, system: systemOut, tools: toolsOut };
56171
+ delete rebuilt.prompt_cache_key;
56114
56172
  return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, anthropicSystem: parsed.system, protocol: "anthropic", stream: stream2, compressInjected: injectTools, pluginMode, nudge, prompts };
56115
56173
  }
56116
56174
  function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session, pluginMode) {
@@ -56146,7 +56204,7 @@ function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session,
56146
56204
  log2("info", diagNudge(turn, sessionId, tokenCount, config.modelContextLimit, parsed.model));
56147
56205
  processedMessages = stripKernelSummaries(turn.messages, turn.state);
56148
56206
  reapOrphanBlocks(session, msgs, deactivateBlock);
56149
- rebuiltMessages = coreToOpenai(processedMessages);
56207
+ rebuiltMessages = hoistMidSystemMessages(coreToOpenai(processedMessages));
56150
56208
  const sysParts = [];
56151
56209
  if (systemText) sysParts.push(systemText);
56152
56210
  if (shouldInject) sysParts.push(buildCompressSystemPrompt(prompts));
@@ -56330,8 +56388,10 @@ function prepareCountTokens(parsed, core, config, log2, session) {
56330
56388
  const stripped = stripKernelSummaries(turn.messages, turn.state);
56331
56389
  const rebuiltMessages = coreToAnthropic(stripped, cacheControls);
56332
56390
  log2("info", `[${sessionId}] count_tokens pruned: ${msgs.length} \u2192 ${stripped.length} msgs`);
56391
+ const rebuilt = { ...parsed, messages: rebuiltMessages };
56392
+ delete rebuilt.prompt_cache_key;
56333
56393
  return {
56334
- body: JSON.stringify({ ...parsed, messages: rebuiltMessages }),
56394
+ body: JSON.stringify(rebuilt),
56335
56395
  session,
56336
56396
  processedMessages: [],
56337
56397
  originalMessages: msgs,
@@ -56341,8 +56401,10 @@ function prepareCountTokens(parsed, core, config, log2, session) {
56341
56401
  };
56342
56402
  } catch (err2) {
56343
56403
  log2("warn", `[${sessionId}] count_tokens prune failed, forwarding unchanged: ${String(err2)}`);
56404
+ const fallback = { ...parsed };
56405
+ delete fallback.prompt_cache_key;
56344
56406
  return {
56345
- body: JSON.stringify(parsed),
56407
+ body: JSON.stringify(fallback),
56346
56408
  session,
56347
56409
  processedMessages: [],
56348
56410
  originalMessages: [],
@@ -56411,6 +56473,12 @@ function resolvePromptCacheKey(explicit, identity, routing, upstream) {
56411
56473
  if (!identity.clientProvided || !shouldInjectPromptCacheKey(routing, upstream)) return void 0;
56412
56474
  return identity.value;
56413
56475
  }
56476
+ var AUTO_MODE_CLASSIFIER_STOPS = /* @__PURE__ */ new Set(["</severity>", "</block>"]);
56477
+ function isAutoModeClassifier(parsed) {
56478
+ const stops = parsed.stop_sequences;
56479
+ if (!Array.isArray(stops)) return false;
56480
+ return stops.some((s3) => typeof s3 === "string" && AUTO_MODE_CLASSIFIER_STOPS.has(s3));
56481
+ }
56414
56482
  function injectSystem(parsed, opts, prompts = defaultPrompts) {
56415
56483
  const baseText = extractSystem(parsed.system);
56416
56484
  const parts = [];