billion-context 0.1.85 → 0.1.86-pr.561.380

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
@@ -47078,7 +47078,7 @@ function caDir() {
47078
47078
  }
47079
47079
 
47080
47080
  // src/logger.ts
47081
- import { createWriteStream, fstatSync, mkdirSync, statSync, renameSync } from "fs";
47081
+ import { createWriteStream, fstatSync, mkdirSync, statSync, renameSync, unlinkSync } from "fs";
47082
47082
  import path2 from "path";
47083
47083
  var MAX_BYTES = 10 * 1024 * 1024;
47084
47084
  var stream;
@@ -47094,6 +47094,10 @@ function openStream(file) {
47094
47094
  existingSize = statSync(file).size;
47095
47095
  if (existingSize >= MAX_BYTES) {
47096
47096
  try {
47097
+ try {
47098
+ unlinkSync(file + ".old");
47099
+ } catch {
47100
+ }
47097
47101
  renameSync(file, file + ".old");
47098
47102
  existingSize = 0;
47099
47103
  } catch {
@@ -47738,6 +47742,11 @@ function loadRoutes(env = process.env) {
47738
47742
  }
47739
47743
  return routes;
47740
47744
  }
47745
+ function passthroughState(env) {
47746
+ const filePassthrough = loadConfigFile().passthrough === true;
47747
+ if (env.ACP_PASSTHROUGH !== void 0) return { enabled: env.ACP_PASSTHROUGH === "1", source: "env" };
47748
+ return { enabled: filePassthrough, source: filePassthrough ? "file" : null };
47749
+ }
47741
47750
  function loadOptions(env = process.env) {
47742
47751
  const fileConfig = loadConfigFile();
47743
47752
  const port = parseInt(env.ACP_PORT ?? env.PORT ?? `${fileConfig.port ?? 8787}`, 10);
@@ -47748,6 +47757,7 @@ function loadOptions(env = process.env) {
47748
47757
  const host = rawHost === "localhost" ? "127.0.0.1" : rawHost;
47749
47758
  const upstream = (env.ACP_UPSTREAM ?? fileConfig.upstream ?? "https://api.anthropic.com").replace(/\/$/, "");
47750
47759
  const routes = loadRoutes(env);
47760
+ const passthrough = passthroughState(env);
47751
47761
  const modelContextLimit = parseInt(env.ACP_MODEL_CONTEXT_LIMIT ?? `${fileConfig.modelContextLimit ?? 2e5}`, 10);
47752
47762
  const biliProxy = nonEmpty(env.BILI_UPSTREAM_PROXY);
47753
47763
  const webProxy = nonEmpty(fileConfig.upstreamProxy);
@@ -47802,7 +47812,8 @@ function loadOptions(env = process.env) {
47802
47812
  log: env.ACP_LOG !== "0" && fileConfig.log !== false,
47803
47813
  debug: (env.ACP_DEBUG ?? (fileConfig.debug ? "1" : "0")) === "1",
47804
47814
  dumpSse: env.ACP_DUMP_SSE || fileConfig.dumpSse || void 0,
47805
- passthrough: (env.ACP_PASSTHROUGH ?? (fileConfig.passthrough ? "1" : "0")) === "1",
47815
+ passthrough: passthrough.enabled,
47816
+ passthroughSource: passthrough.source,
47806
47817
  autoUpdate: (env.ACP_AUTO_UPDATE ?? (fileConfig.autoUpdate === false ? "0" : "1")) !== "0",
47807
47818
  logFile: env.ACP_LOG_FILE !== void 0 ? env.ACP_LOG_FILE || void 0 : fileConfig.logFile,
47808
47819
  mitm: {
@@ -49475,15 +49486,11 @@ var StateStore = class {
49475
49486
  }
49476
49487
  const file = this.resolvePath(id, payload);
49477
49488
  const data = JSON.stringify(this.envelope(id, payload));
49478
- try {
49479
- fs.mkdirSync(path4.dirname(file), { recursive: true });
49480
- } catch (e) {
49481
- this.log("warn", `[persist] could not create dir ${path4.dirname(file)}: ${errText(e)}`);
49482
- }
49483
49489
  let lastErr;
49484
49490
  for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
49485
49491
  const tmp = this.tempPath(file);
49486
49492
  try {
49493
+ fs.mkdirSync(path4.dirname(file), { recursive: true });
49487
49494
  fs.writeFileSync(tmp, data, "utf8");
49488
49495
  fs.renameSync(tmp, file);
49489
49496
  lastErr = void 0;
@@ -49494,9 +49501,7 @@ var StateStore = class {
49494
49501
  fs.unlinkSync(tmp);
49495
49502
  } catch {
49496
49503
  }
49497
- const code = e.code;
49498
- if (code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") break;
49499
- if (attempt === this.retryAttempts - 1) break;
49504
+ if (!isTransientFsError(e) || attempt === this.retryAttempts - 1) break;
49500
49505
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, this.backoffMs(attempt));
49501
49506
  }
49502
49507
  }
@@ -49506,14 +49511,19 @@ var StateStore = class {
49506
49511
  this.removeSpillSync(file);
49507
49512
  return true;
49508
49513
  }
49514
+ const spill = this.spillPathFor(file);
49509
49515
  let spillPath = null;
49510
- try {
49511
- const spill = this.spillPathFor(file);
49512
- fs.writeFileSync(spill, data, "utf8");
49513
- spillPath = spill;
49514
- this.discovered.set(id, spill);
49515
- } catch {
49516
+ for (let attempt = 0; attempt < this.retryAttempts && spillPath === null; attempt++) {
49517
+ try {
49518
+ fs.mkdirSync(path4.dirname(spill), { recursive: true });
49519
+ fs.writeFileSync(spill, data, "utf8");
49520
+ spillPath = spill;
49521
+ } catch (e) {
49522
+ if (!isTransientFsError(e) || attempt === this.retryAttempts - 1) break;
49523
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, this.backoffMs(attempt));
49524
+ }
49516
49525
  }
49526
+ if (spillPath) this.discovered.set(id, spillPath);
49517
49527
  this.recordFailure(id, lastErr, spillPath);
49518
49528
  return spillPath !== null;
49519
49529
  }
@@ -49617,33 +49627,45 @@ var StateStore = class {
49617
49627
  throw e;
49618
49628
  }
49619
49629
  const file = this.resolvePath(id, payload);
49620
- try {
49621
- await fsp.mkdir(path4.dirname(file), { recursive: true });
49622
- } catch (e) {
49623
- this.log("warn", `[persist] could not create dir ${path4.dirname(file)}: ${errText(e)}`);
49630
+ const data = JSON.stringify(this.envelope(id, payload));
49631
+ let lastErr;
49632
+ for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
49633
+ const tmp = this.tempPath(file);
49634
+ try {
49635
+ await fsp.mkdir(path4.dirname(file), { recursive: true });
49636
+ await fsp.writeFile(tmp, data, "utf8");
49637
+ await fsp.rename(tmp, file);
49638
+ this.discovered.set(id, file);
49639
+ this.clearFailure(id);
49640
+ await this.removeSpill(file);
49641
+ return;
49642
+ } catch (e) {
49643
+ lastErr = e;
49644
+ await fsp.unlink(tmp).catch(() => {
49645
+ });
49646
+ if (!isTransientFsError(e) || attempt === this.retryAttempts - 1) break;
49647
+ const { promise, resolve } = Promise.withResolvers();
49648
+ setTimeout(resolve, this.backoffMs(attempt));
49649
+ await promise;
49650
+ }
49624
49651
  }
49625
- const tmp = this.tempPath(file);
49626
- const env = this.envelope(id, payload);
49627
- try {
49628
- await fsp.writeFile(tmp, JSON.stringify(env), "utf8");
49629
- await this.renameWithRetry(tmp, file);
49630
- this.discovered.set(id, file);
49631
- this.clearFailure(id);
49632
- await this.removeSpill(file);
49633
- } catch (e) {
49634
- await fsp.unlink(tmp).catch(() => {
49635
- });
49636
- let spillPath = null;
49652
+ const spill = this.spillPathFor(file);
49653
+ let spillPath = null;
49654
+ for (let attempt = 0; attempt < this.retryAttempts && spillPath === null; attempt++) {
49637
49655
  try {
49638
- const spill = this.spillPathFor(file);
49639
- await fsp.writeFile(spill, JSON.stringify(env), "utf8");
49656
+ await fsp.mkdir(path4.dirname(spill), { recursive: true });
49657
+ await fsp.writeFile(spill, data, "utf8");
49640
49658
  spillPath = spill;
49641
- this.discovered.set(id, spill);
49642
- } catch {
49659
+ } catch (e) {
49660
+ if (!isTransientFsError(e) || attempt === this.retryAttempts - 1) break;
49661
+ const { promise, resolve } = Promise.withResolvers();
49662
+ setTimeout(resolve, this.backoffMs(attempt));
49663
+ await promise;
49643
49664
  }
49644
- this.recordFailure(id, e, spillPath);
49645
- throw e;
49646
49665
  }
49666
+ if (spillPath) this.discovered.set(id, spillPath);
49667
+ this.recordFailure(id, lastErr, spillPath);
49668
+ throw lastErr;
49647
49669
  }
49648
49670
  envelope(id, payload) {
49649
49671
  return { version: this.version, savedAt: Date.now(), id, payload };
@@ -49709,27 +49731,6 @@ var StateStore = class {
49709
49731
  clearFailure(id) {
49710
49732
  this.failCounts.delete(id);
49711
49733
  }
49712
- /** fs.rename with exponential-backoff retries on Windows transient locks
49713
- * (EPERM/EBUSY/EACCES from AV scan, search indexer, SMB). Other errors
49714
- * are real and rethrown immediately. */
49715
- async renameWithRetry(src, dest) {
49716
- let lastErr;
49717
- for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
49718
- try {
49719
- await fsp.rename(src, dest);
49720
- return;
49721
- } catch (e) {
49722
- lastErr = e;
49723
- const code = e.code;
49724
- if (code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
49725
- if (attempt === this.retryAttempts - 1) throw e;
49726
- const { promise, resolve } = Promise.withResolvers();
49727
- setTimeout(resolve, this.backoffMs(attempt));
49728
- await promise;
49729
- }
49730
- }
49731
- throw lastErr;
49732
- }
49733
49734
  /** Parse + validate one file. Corrupt or invalid records return null
49734
49735
  * (logged) instead of throwing — load paths must never block boot. */
49735
49736
  readEnvelope(file) {
@@ -49805,6 +49806,10 @@ function isEnvelopeLike(value) {
49805
49806
  function flatFileNameFor(id) {
49806
49807
  return createHash2("sha256").update(id, "utf8").digest("hex").slice(0, 24) + ".json";
49807
49808
  }
49809
+ function isTransientFsError(e) {
49810
+ const code = e.code;
49811
+ return code === "EPERM" || code === "EBUSY" || code === "EACCES" || code === "ENOENT" || code === "ENOTDIR";
49812
+ }
49808
49813
  function errText(e) {
49809
49814
  return e instanceof Error ? e.message : String(e);
49810
49815
  }
@@ -50353,7 +50358,7 @@ function withStagedCompressGuidance(text) {
50353
50358
  }
50354
50359
 
50355
50360
  // src/decompress-shared.ts
50356
- import { mkdirSync as mkdirSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
50361
+ import { mkdirSync as mkdirSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
50357
50362
  import { dirname as dirname2, join as join2 } from "path";
50358
50363
  import { tmpdir } from "os";
50359
50364
  var trackedTempFiles = [];
@@ -50369,7 +50374,7 @@ function reapTempFiles() {
50369
50374
  const oldest = trackedTempFiles.shift();
50370
50375
  if (!oldest) break;
50371
50376
  try {
50372
- unlinkSync(oldest.path);
50377
+ unlinkSync2(oldest.path);
50373
50378
  } catch {
50374
50379
  }
50375
50380
  }
@@ -50377,7 +50382,7 @@ function reapTempFiles() {
50377
50382
  process.on("beforeExit", () => {
50378
50383
  for (const f2 of trackedTempFiles) {
50379
50384
  try {
50380
- unlinkSync(f2.path);
50385
+ unlinkSync2(f2.path);
50381
50386
  } catch {
50382
50387
  }
50383
50388
  }
@@ -51262,7 +51267,7 @@ function busy(button,on,label){if(!button)return;if(on){button.dataset.label=but
51262
51267
  async function json(url,options){const response=await fetch(url,options);const data=await response.json().catch(()=>({}));if(!response.ok)throw new Error(data.error||data.detail||("HTTP "+response.status));return data}
51263
51268
  function showPage(name){document.querySelectorAll(".page").forEach((node)=>node.classList.toggle("active",node.id==="page-"+name));document.querySelectorAll(".nav button").forEach((node)=>node.classList.toggle("active",node.dataset.page===name));if(name==="sessions")loadSessions();if(name==="upstream"){loadUpstream();loadOverrides()}}
51264
51269
  document.querySelectorAll(".nav button").forEach((button)=>button.addEventListener("click",()=>showPage(button.dataset.page)));
51265
- async function loadConfig(){const data=await json("/__bili/config");if(data.parseError)toast(String(data.parseError),true);byId("providers-json").value=JSON.stringify(data.providers||{},null,2);byId("proxy-url").value=data.upstreamProxy||"";byId("compress-json").value=data.compress?JSON.stringify(data.compress,null,2):"";document.querySelectorAll('input[name="proxy-mode"]').forEach((input)=>input.checked=input.value===(data.upstreamProxyMode||"direct"));return data}
51270
+ async function loadConfig(){const data=await json("/__bili/config");if(data.parseError)toast(String(data.parseError),true);byId("providers-json").value=JSON.stringify(data.providers||{},null,2);byId("proxy-url").value=data.upstreamProxy||"";byId("compress-json").value=data.compress?JSON.stringify(data.compress,null,2):"";document.querySelectorAll('input[name="proxy-mode"]').forEach((input)=>input.checked=input.value===(data.upstreamProxyMode||"direct"));const pt=data.passthrough||{enabled:false};const banner=byId("passthrough-banner");if(banner){if(pt.enabled){banner.hidden=false;byId("passthrough-source").textContent=pt.source==="env"?"\u73AF\u5883\u53D8\u91CF ACP_PASSTHROUGH\uFF08\u6216 --passthrough \u53C2\u6570\uFF09":"\u914D\u7F6E\u6587\u4EF6 passthrough: true";const btn=byId("clear-passthrough");btn.disabled=pt.source==="env";btn.title=pt.source==="env"?"\u9700\u5148 unset ACP_PASSTHROUGH \u5E76\u91CD\u542F":""}else{banner.hidden=true}}return data}
51266
51271
  async function loadUpstream(){try{const data=await json("/__bili/upstream");const labels={"provider":"Provider \u5355\u72EC\u4EE3\u7406","provider-direct":"Provider \u76F4\u8FDE","bili-env":"BILI_UPSTREAM_PROXY","web-manual":"Web \u624B\u52A8\u4EE3\u7406","config":"\u914D\u7F6E\u6587\u4EF6\u4EE3\u7406","HTTPS_PROXY":"HTTPS_PROXY","HTTP_PROXY":"HTTP_PROXY","ALL_PROXY":"ALL_PROXY","windows-system":"Windows \u7CFB\u7EDF\u4EE3\u7406","windows-bypass":"Windows \u7ED5\u8FC7\u5217\u8868","no-proxy":"NO_PROXY","direct":"\u76F4\u8FDE"};byId("upstream-source").textContent=labels[data.source]||data.source||"\u76F4\u8FDE";byId("upstream-effective").textContent=data.proxy||"direct";byId("upstream-pac").textContent=data.autoConfigUrl||"\u2014";const state=byId("upstream-state");state.className="status "+(data.connected===true?"ok":data.connected===false?"err":"");state.textContent=data.connected===true?"CONNECT \u6B63\u5E38":data.connected===false?(data.error||"\u8FDE\u63A5\u5931\u8D25"):"\u5C1A\u672A\u89C2\u5BDF\u5230\u4E0A\u6E38\u8FDE\u63A5"}catch(error){byId("upstream-state").textContent=String(error)}}
51267
51272
  async function saveUpstream(){const button=byId("save-upstream");busy(button,true,"\u4FDD\u5B58\u4E2D\u2026");const mode=document.querySelector('input[name="proxy-mode"]:checked').value;try{await json("/__bili/config",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({upstreamProxyMode:mode,upstreamProxy:byId("proxy-url").value.trim()||null})});toast("\u4E0A\u6E38\u8BBE\u7F6E\u5DF2\u70ED\u66F4\u65B0");await loadUpstream()}catch(error){toast(String(error),true)}finally{busy(button,false)}}
51268
51273
  async function testUpstream(){const button=byId("test-upstream");busy(button,true,"\u6D4B\u8BD5\u4E2D\u2026");try{const data=await json("/__bili/upstream/test",{method:"POST"});toast("\u8FDE\u63A5\u6210\u529F\uFF0CHTTP "+data.status);await loadUpstream()}catch(error){toast(String(error),true);await loadUpstream()}finally{busy(button,false)}}
@@ -51272,7 +51277,9 @@ async function loadOverrides(){try{const data=await loadConfig();const providers
51272
51277
  async function saveOverrides(){const button=byId("save-overrides");busy(button,true,"\u4FDD\u5B58\u4E2D\u2026");try{const data=await loadConfig();const providers=data.providers||{};document.querySelectorAll(".override-proxy").forEach((input)=>{const url=input.dataset.url;if(!url)return;if(!providers[url])providers[url]={};const val=input.value.trim();if(val)providers[url].proxy=val;else delete providers[url].proxy});await json("/__bili/config",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({providers})});await json("/__bili/config/reload",{method:"POST"});toast("\u6309 URL \u8986\u76D6\u5DF2\u70ED\u66F4\u65B0");await loadOverrides()}catch(error){toast(String(error),true)}finally{busy(button,false)}}
51273
51278
  async function loadSessions(){try{const data=await json("/__bili/stats");const rows=(data.sessions||[]).map((item)=>"<tr><td>"+escapeHtml(item.title||"\u2014")+"</td><td>"+escapeHtml(item.protocol||"\u2014")+"</td><td class=mono>"+escapeHtml(item.label||"\u2014")+"</td><td>"+escapeHtml(item.requests)+"</td><td>"+escapeHtml(item.contextTokens)+"</td><td>"+escapeHtml(new Date(item.lastSeen).toLocaleString())+(item.restored?" <span class=muted>(\u6062\u590D\uFF0C\u5F00\u673A\u540E\u672A\u6D3B\u52A8)</span>":"")+"</td></tr>").join("");byId("sessions-body").innerHTML=rows||'<tr><td colspan="6">\u6682\u65E0\u4F1A\u8BDD</td></tr>'}catch(error){toast(String(error),true);throw error}}
51274
51279
  async function refreshSessions(){const button=byId("refresh-sessions");busy(button,true,"\u5237\u65B0\u4E2D\u2026");try{await loadSessions();toast("\u4F1A\u8BDD\u5217\u8868\u5DF2\u5237\u65B0")}catch{}finally{busy(button,false)}}
51275
- document.querySelectorAll(".copy-btn").forEach((btn)=>btn.addEventListener("click",async()=>{busy(btn,true,"\u590D\u5236\u4E2D\u2026");try{await navigator.clipboard.writeText(btn.dataset.copy||"");toast("\u5DF2\u590D\u5236")}catch(e){toast(String(e),true)}finally{busy(btn,false)}}));byId("save-upstream").addEventListener("click",saveUpstream);byId("test-upstream").addEventListener("click",testUpstream);byId("save-providers").addEventListener("click",saveProviders);byId("save-compress").addEventListener("click",saveCompress);byId("save-overrides").addEventListener("click",saveOverrides);byId("refresh-sessions").addEventListener("click",refreshSessions);
51280
+ document.querySelectorAll(".copy-btn").forEach((btn)=>btn.addEventListener("click",async()=>{busy(btn,true,"\u590D\u5236\u4E2D\u2026");try{await navigator.clipboard.writeText(btn.dataset.copy||"");toast("\u5DF2\u590D\u5236")}catch(e){toast(String(e),true)}finally{busy(btn,false)}}));async function clearPassthrough(){const button=byId("clear-passthrough");busy(button,true,"\u5904\u7406\u4E2D\u2026");try{await json("/__bili/config",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({passthrough:null})});toast("\u5DF2\u6062\u590D\u538B\u7F29");await loadConfig()}catch(error){toast(String(error),true)}finally{busy(button,false)}}
51281
+ byId("clear-passthrough").addEventListener("click",clearPassthrough);
51282
+ byId("save-upstream").addEventListener("click",saveUpstream);byId("test-upstream").addEventListener("click",testUpstream);byId("save-providers").addEventListener("click",saveProviders);byId("save-compress").addEventListener("click",saveCompress);byId("save-overrides").addEventListener("click",saveOverrides);byId("refresh-sessions").addEventListener("click",refreshSessions);
51276
51283
  Promise.all([loadConfig(),loadUpstream()]).catch((error)=>toast(String(error),true));setInterval(()=>{if(byId("page-sessions").classList.contains("active"))loadSessions().catch(()=>{})},5000);
51277
51284
  `;
51278
51285
 
@@ -51294,7 +51301,7 @@ function renderPage(origin, version2) {
51294
51301
  const caReady = existsSync4(caPath);
51295
51302
  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>
51296
51303
  <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>
51297
- <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>
51304
+ <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 id="passthrough-banner" class="notice" hidden>\u26A0\uFE0F <strong>Passthrough \u5DF2\u542F\u7528</strong>\uFF1A\u6240\u6709\u8BF7\u6C42\u539F\u6837\u8F6C\u53D1\uFF0C\u538B\u7F29\u5B8C\u5168\u5173\u95ED\uFF0C\u4E0D\u4F1A\u8282\u7701\u4EFB\u4F55 token\u3002\u6765\u6E90\uFF1A<span id="passthrough-source" class="mono"></span>\u3002 <button id="clear-passthrough" class="btn">\u6062\u590D\u538B\u7F29</button></div><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>
51298
51305
  <section id="page-routing" class="page"><h1>\u5BA2\u6237\u7AEF\u63A5\u5165</h1><p class="lead">\u4E24\u7C7B\u63A5\u5165\uFF1AA. \u63A8\u8350\u63A5\u5165\u65B9\u5F0F\uFF08<span class="mono">/bili/</span> \u524D\u7F00\uFF0C\u96F6\u914D\u7F6E\uFF09\uFF1BB. \u8BC1\u4E66\u63A5\u5165\u65B9\u5F0F\uFF08MITM\uFF0C\u9700\u4FE1\u4EFB CA \u8BC1\u4E66\uFF09\u3002</p>
51299
51306
  <h2 class="section-title">A. \u63A8\u8350\u63A5\u5165\u65B9\u5F0F\uFF08/bili/ \u524D\u7F00\uFF09</h2>
51300
51307
  <p class="hint">\u5728\u5BA2\u6237\u7AEF\u914D\u7F6E\u91CC\u628A\u539F baseURL \u524D\u52A0\u4E0A <span class="mono">${origin}/bili/</span> \u5373\u53EF\uFF0C\u5176\u4F59\u4E0D\u53D8\u3002</p>
@@ -51313,7 +51320,7 @@ function renderPage(origin, version2) {
51313
51320
  }
51314
51321
 
51315
51322
  // src/web/api.ts
51316
- import { closeSync, existsSync as existsSync5, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
51323
+ import { closeSync, existsSync as existsSync5, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
51317
51324
  import { dirname as dirname3 } from "path";
51318
51325
  import { randomUUID as randomUUID2 } from "crypto";
51319
51326
  function readConfig() {
@@ -51352,7 +51359,7 @@ function atomicWriteConfig(config) {
51352
51359
  } catch (error) {
51353
51360
  if (descriptor !== void 0) closeSync(descriptor);
51354
51361
  try {
51355
- unlinkSync2(tempPath);
51362
+ unlinkSync3(tempPath);
51356
51363
  } catch {
51357
51364
  }
51358
51365
  throw error;
@@ -51370,6 +51377,7 @@ async function handleConfigGet(res) {
51370
51377
  upstreamProxy: upstream.proxy ?? null,
51371
51378
  upstreamProxyMode: upstream.mode,
51372
51379
  compress: config.compress ?? null,
51380
+ passthrough: passthroughState(process.env),
51373
51381
  ...parseError ? { parseError } : {}
51374
51382
  }, null, 2));
51375
51383
  }
@@ -51383,7 +51391,8 @@ async function handleConfigPut(req, res, onChanged, biliPort = 8787) {
51383
51391
  const hasProxy = Object.prototype.hasOwnProperty.call(body, "upstreamProxy");
51384
51392
  const hasMode = Object.prototype.hasOwnProperty.call(body, "upstreamProxyMode");
51385
51393
  const hasCompress = Object.prototype.hasOwnProperty.call(body, "compress");
51386
- if (!hasProviders && !hasProxy && !hasMode && !hasCompress) return sendError(res, 400, "expected providers, upstream proxy, or compress settings");
51394
+ const hasPassthrough = Object.prototype.hasOwnProperty.call(body, "passthrough");
51395
+ if (!hasProviders && !hasProxy && !hasMode && !hasCompress && !hasPassthrough) return sendError(res, 400, "expected providers, upstream proxy, compress, or passthrough settings");
51387
51396
  const routes = {};
51388
51397
  if (hasProviders) {
51389
51398
  if (!body.providers || typeof body.providers !== "object" || Array.isArray(body.providers)) {
@@ -51427,6 +51436,14 @@ async function handleConfigPut(req, res, onChanged, biliPort = 8787) {
51427
51436
  compress = body.compress === null ? {} : parseCompressSettings(body.compress);
51428
51437
  if (compress === void 0) return sendError(res, 400, "invalid compress settings");
51429
51438
  }
51439
+ if (hasPassthrough) {
51440
+ if (body.passthrough !== null && typeof body.passthrough !== "boolean") {
51441
+ return sendError(res, 400, "passthrough must be a boolean or null");
51442
+ }
51443
+ if (passthroughState(process.env).source === "env") {
51444
+ return sendError(res, 409, "passthrough is forced by the ACP_PASSTHROUGH environment variable (or --passthrough flag); unset it and restart to change here");
51445
+ }
51446
+ }
51430
51447
  const config = readConfig();
51431
51448
  if (hasProviders) config.providers = routes;
51432
51449
  if (hasProxy) {
@@ -51438,6 +51455,10 @@ async function handleConfigPut(req, res, onChanged, biliPort = 8787) {
51438
51455
  if (compress && Object.keys(compress).length > 0) config.compress = compress;
51439
51456
  else delete config.compress;
51440
51457
  }
51458
+ if (hasPassthrough) {
51459
+ if (body.passthrough === true) config.passthrough = true;
51460
+ else delete config.passthrough;
51461
+ }
51441
51462
  try {
51442
51463
  atomicWriteConfig(config);
51443
51464
  onChanged?.();
@@ -51448,6 +51469,7 @@ async function handleConfigPut(req, res, onChanged, biliPort = 8787) {
51448
51469
  if (hasProviders) changed.push(`${Object.keys(routes).length} routes`);
51449
51470
  if (hasProxy || hasMode) changed.push("network");
51450
51471
  if (hasCompress) changed.push("compress");
51472
+ if (hasPassthrough) changed.push("passthrough");
51451
51473
  log("info", `[acp-web] configuration updated (${changed.join(", ") || "none"})`);
51452
51474
  res.writeHead(200, { "content-type": "application/json" });
51453
51475
  res.end(JSON.stringify({ ok: true, providers: hasProviders ? Object.keys(routes).length : void 0 }));
@@ -57119,8 +57141,23 @@ Content-Length: ${Buffer.byteLength(body)}\r
57119
57141
  const nOverrides = Object.keys(opts.routes).length;
57120
57142
  log2(
57121
57143
  "info",
57122
- `acp-proxy listening on http://${displayHost}:${actualPort} \u2014 web UI: http://${displayHost}:${actualPort}/__bili/ \u2014 zero-config: prefix any baseURL with http://${displayHost}:${actualPort}/bili/` + (nOverrides ? ` \u2014 context overrides for ${nOverrides} upstream URL(s)` : "") + (opts.mitm.enabled ? ` \u2014 MITM proxy on (whitelist)${opts.mitm.domains.length ? ` +${opts.mitm.domains.join(",")}` : ""}` : "")
57144
+ `acp-proxy listening on http://${displayHost}:${actualPort} \u2014 web UI: http://${displayHost}:${actualPort}/__bili/ \u2014 zero-config: prefix any baseURL with http://${displayHost}:${actualPort}/bili/` + (nOverrides ? ` \u2014 context overrides for ${nOverrides} upstream URL(s)` : "") + (opts.mitm.enabled ? ` \u2014 MITM proxy on (whitelist)${opts.mitm.domains.length ? ` +${opts.mitm.domains.join(",")}` : ""}` : "") + (opts.passthrough ? " \u2014 PASSTHROUGH (compression OFF)" : "")
57123
57145
  );
57146
+ if (opts.passthrough) {
57147
+ log2(
57148
+ "warn",
57149
+ `[passthrough] compression is OFF \u2014 every request is forwarded verbatim, no tokens are saved (source: ${opts.passthroughSource === "env" ? "ACP_PASSTHROUGH env var or --passthrough flag" : `config file ${configFile()}`}). ` + (opts.passthroughSource === "env" ? "Unset ACP_PASSTHROUGH (or drop --passthrough) and restart to re-enable compression." : 'Clear it in the web UI (\u6982\u89C8 page) or remove "passthrough": true from the config file to re-enable compression.')
57150
+ );
57151
+ }
57152
+ const envKnobs = [];
57153
+ if (process.env.ACP_PASSTHROUGH !== void 0) envKnobs.push(`ACP_PASSTHROUGH=${process.env.ACP_PASSTHROUGH}`);
57154
+ if (process.env.ACP_MODEL_CONTEXT_LIMIT !== void 0) envKnobs.push(`ACP_MODEL_CONTEXT_LIMIT=${process.env.ACP_MODEL_CONTEXT_LIMIT}`);
57155
+ if (process.env.ACP_COMPRESS_TOOL !== void 0) envKnobs.push(`ACP_COMPRESS_TOOL=${process.env.ACP_COMPRESS_TOOL}`);
57156
+ if (process.env.ACP_COMPRESS_NUDGE !== void 0) envKnobs.push(`ACP_COMPRESS_NUDGE=${process.env.ACP_COMPRESS_NUDGE}`);
57157
+ if (process.env.BILI_PERSIST !== void 0) envKnobs.push(`BILI_PERSIST=${process.env.BILI_PERSIST}`);
57158
+ if (envKnobs.length > 0) {
57159
+ log2("info", `[config] env overrides active (win over the config file): ${envKnobs.join(", ")}`);
57160
+ }
57124
57161
  if (nonLoopbackBind) {
57125
57162
  log2(
57126
57163
  "warn",
@@ -57200,9 +57237,34 @@ var SIDE_REQUEST_MAX_TOKENS = 200;
57200
57237
  function isSideRequest(parsed) {
57201
57238
  if (!parsed || typeof parsed !== "object") return false;
57202
57239
  const p2 = parsed;
57240
+ if (Array.isArray(p2.tools) && p2.tools.length > 0) return false;
57203
57241
  const raw = p2.max_tokens ?? p2.max_completion_tokens ?? p2.max_output_tokens;
57204
57242
  return typeof raw === "number" && raw > 0 && raw <= SIDE_REQUEST_MAX_TOKENS;
57205
57243
  }
57244
+ function outputBudgetField(parsed) {
57245
+ if (!parsed || typeof parsed !== "object") return null;
57246
+ const p2 = parsed;
57247
+ for (const field of ["max_tokens", "max_completion_tokens", "max_output_tokens"]) {
57248
+ if (typeof p2[field] === "number" && p2[field] > 0) return field;
57249
+ }
57250
+ return null;
57251
+ }
57252
+ function restoreOutputBudget(parsed, session, log2) {
57253
+ const field = outputBudgetField(parsed);
57254
+ if (!field) return;
57255
+ const p2 = parsed;
57256
+ const value = p2[field];
57257
+ if (value > SIDE_REQUEST_MAX_TOKENS) {
57258
+ session.metadata.outputBudgetHighWater = value;
57259
+ return;
57260
+ }
57261
+ if (!Array.isArray(p2.tools) || p2.tools.length === 0) return;
57262
+ const highWater = session.metadata.outputBudgetHighWater;
57263
+ if (typeof highWater === "number" && highWater > SIDE_REQUEST_MAX_TOKENS) {
57264
+ p2[field] = highWater;
57265
+ log2("info", `[${session.id}] output budget restored ${value} -> ${highWater} (#546: client shrank it from its raw-history estimate)`);
57266
+ }
57267
+ }
57206
57268
  function isTrustedAdminOrigin(origin, host, trustedHosts) {
57207
57269
  if (!host || !trustedHosts.has(host.toLowerCase())) return false;
57208
57270
  if (!origin) return true;
@@ -57271,6 +57333,14 @@ async function handle(req, res, opts, core, config, log2, instanceId, instanceSt
57271
57333
  if (req.method === "PUT" && req.url === "/__bili/config") {
57272
57334
  return handleConfigPut(req, res, () => {
57273
57335
  const fresh = loadOptions();
57336
+ if (fresh.passthrough !== opts.passthrough) {
57337
+ log2(
57338
+ fresh.passthrough ? "warn" : "info",
57339
+ `[passthrough] ${fresh.passthrough ? "compression turned OFF via web config \u2014 forwarding verbatim" : "compression re-enabled via web config"}`
57340
+ );
57341
+ }
57342
+ opts.passthrough = fresh.passthrough;
57343
+ opts.passthroughSource = fresh.passthroughSource;
57274
57344
  opts.proxy = fresh.proxy;
57275
57345
  opts.proxyMode = fresh.proxyMode;
57276
57346
  opts.proxySource = fresh.proxySource;
@@ -57593,6 +57663,7 @@ ${bodyBuffer.toString("utf8")}`);
57593
57663
  }
57594
57664
  }
57595
57665
  const pluginMode = pluginAgent !== void 0;
57666
+ restoreOutputBudget(parsed, session, log2);
57596
57667
  if (!countTokens && !responsesCompact && protocol !== null && isSideRequest(parsed)) {
57597
57668
  log2("info", `[${session.id}] side request (max_tokens<=${SIDE_REQUEST_MAX_TOKENS}) \u2192 passthrough + tag strip only, kernel state untouched`);
57598
57669
  const sidePrepared = {