billion-context 0.1.75 → 0.1.76

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
@@ -45360,6 +45360,26 @@ function numericBlockId(id) {
45360
45360
  const parsed = /^b(\d+)$/.exec(id);
45361
45361
  return parsed ? Number(parsed[1]) : 0;
45362
45362
  }
45363
+ function refGateDiagnostics(state, requestedRanges, unknownCount) {
45364
+ const highest = highestUsedIndex(state.messageRefs);
45365
+ const highestRef = highest > 0 ? indexToRef(highest) : "none";
45366
+ return `[diagnostics: session highest ref=${highestRef}, unknown ranges in request=${unknownCount}/${requestedRanges}, session history=${state.stats.compressionCount} compression(s), ${state.blocks.length} block(s)]`;
45367
+ }
45368
+ function danglingMessageRefs(state, messages, spec) {
45369
+ const visible = new Set(messages.map((m2) => m2.id));
45370
+ const dangling = [];
45371
+ for (const ref of [spec.startRef, spec.endRef]) {
45372
+ const parsed = parseBoundary(ref);
45373
+ if (!parsed || parsed.kind !== "message") continue;
45374
+ const rawId = state.messageRefs.byRef[parsed.raw] ?? state.messageRefs.byRef[indexToRef(parsed.numericId)];
45375
+ if (!rawId || visible.has(rawId)) continue;
45376
+ const covered = state.blocks.some(
45377
+ (block) => block.active && block.effectiveMessageIds.includes(rawId)
45378
+ );
45379
+ if (!covered) dangling.push(parsed.raw);
45380
+ }
45381
+ return dangling;
45382
+ }
45363
45383
  function createCore(ports = {}) {
45364
45384
  const countTokens = ports.countTokens ?? defaultCountTokens;
45365
45385
  function applyCompression(input) {
@@ -45460,7 +45480,15 @@ function createCore(ports = {}) {
45460
45480
  if (!hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {
45461
45481
  const live = activeBlocks(state).map((b2) => b2.blockId).sort((x, y) => numericBlockId(x) - numericBlockId(y));
45462
45482
  const liveHint = live.length > 0 ? ` Current active blocks span ${live[0]}..${live[live.length - 1]} \u2014 retry with startId/endId set to active block IDs in that span.` : "";
45463
- const gateMessage = resolvableCount === 0 && consumedRanges.length === 0 && unknownCount > 0 ? `None of the ${input.ranges.length} requested range(s) resolved \u2014 every ref failed with "does not exist in this session". Refs recorded before an earlier compress are stale: each successful compress renumbers the remaining refs. Run acp_status, then call the compress tool again using only the refs it reports.` : consumedRanges.length > 0 ? `Requested range(s) already compressed (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}) \u2014 your refs are stale: a prior compress renumbered the remaining refs, so this range now falls inside an active block. Run acp_status, then call the compress tool again using only the CURRENT compressible ranges it reports.${liveHint}` : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;
45483
+ const diagnostics = refGateDiagnostics(
45484
+ state,
45485
+ input.ranges.length,
45486
+ unknownCount
45487
+ );
45488
+ const danglingRefs = consumedRanges.flatMap(
45489
+ (spec) => danglingMessageRefs(state, input.messages, spec)
45490
+ );
45491
+ const gateMessage = resolvableCount === 0 && consumedRanges.length === 0 && unknownCount > 0 ? `None of the ${input.ranges.length} requested range(s) resolved \u2014 every ref is unknown to this session. Refs are per-session snapshots, assigned once when a message is first rendered; no compress reassigns them, so unknown refs cannot come from an earlier compress in this session. They come from a different generation: a previous session instance (switching model or upstream mid-conversation starts a fresh session whose refs restart at m00001), the generation before a native-compaction rebase (which also resets refs to m00001), or a typo. ${diagnostics} Run acp_status, then call the compress tool again using only the refs it reports.` : consumedRanges.length > 0 ? danglingRefs.length > 0 ? `Requested range(s) cannot be anchored (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}) \u2014 the refs exist in this session's ref map, but the messages they point to are no longer in the visible context and no active block covers them: the message content changed (or the message was filtered out of the view) and now carries a new ref, leaving your old refs dangling. ${diagnostics} Run acp_status, then call the compress tool again using only the refs it reports.` : `Requested range(s) already compressed (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}) \u2014 those refs no longer point to directly compressible content: the range is covered by active block(s) or the block ref(s) are stale (distilled or consumed). ${diagnostics} Run acp_status, then call the compress tool again using only the CURRENT compressible ranges it reports.${liveHint}` : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;
45464
45492
  return {
45465
45493
  state: input.state,
45466
45494
  result: {
@@ -47856,7 +47884,7 @@ function parseCompressSettings(v2) {
47856
47884
  }
47857
47885
  out[key] = typeof obj[key] === "string" ? obj[key].trim() : obj[key];
47858
47886
  }
47859
- for (const key of ["nudgeGrowthTokens", "preserveRecentMessages", "preserveRecentTokens", "minCompressRange"]) {
47887
+ for (const key of ["nudgeGrowthTokens", "preserveRecentMessages", "preserveRecentTokens", "minCompressRange", "minCompressRangeChars"]) {
47860
47888
  takeNumber(key);
47861
47889
  }
47862
47890
  if ("tiers" in obj) {
@@ -47922,6 +47950,7 @@ function resolveContextLimitValue(raw, nativeLimit) {
47922
47950
  function mergeCompress(global2, provider, model) {
47923
47951
  const pick = (k2) => model?.[k2] ?? provider?.[k2] ?? global2?.[k2];
47924
47952
  const promptLevels = [global2?.prompts, provider?.prompts, model?.prompts].filter(Boolean);
47953
+ const rangeOf = (s3) => s3?.minCompressRangeChars ?? s3?.minCompressRange;
47925
47954
  return {
47926
47955
  modelContextLimit: pick("modelContextLimit"),
47927
47956
  maxContextLimit: pick("maxContextLimit"),
@@ -47929,7 +47958,7 @@ function mergeCompress(global2, provider, model) {
47929
47958
  nudgeGrowthTokens: pick("nudgeGrowthTokens"),
47930
47959
  preserveRecentMessages: pick("preserveRecentMessages"),
47931
47960
  preserveRecentTokens: pick("preserveRecentTokens"),
47932
- minCompressRange: pick("minCompressRange"),
47961
+ minCompressRangeChars: rangeOf(model) ?? rangeOf(provider) ?? rangeOf(global2),
47933
47962
  tiers: pick("tiers"),
47934
47963
  prompts: promptLevels.length > 0 ? Object.assign({}, ...promptLevels) : void 0,
47935
47964
  acknowledgePromptsRisk: pick("acknowledgePromptsRisk")
@@ -47983,7 +48012,7 @@ function applyCompressSettings(base, limit, s3) {
47983
48012
  preserveRecentTokens: s3.preserveRecentTokens ?? base.preserveRecentTokens,
47984
48013
  compress: {
47985
48014
  ...base.compress,
47986
- minCompressRange: s3.minCompressRange ?? base.compress.minCompressRange
48015
+ minCompressRange: s3.minCompressRangeChars ?? s3.minCompressRange ?? base.compress.minCompressRange
47987
48016
  }
47988
48017
  };
47989
48018
  }
@@ -48008,12 +48037,22 @@ import path3 from "path";
48008
48037
  // src/fetch-util.ts
48009
48038
  var MAX_REQUEST_BYTES = 100 * 1024 * 1024;
48010
48039
  var UPSTREAM_TIMEOUT_MS = 10 * 60 * 1e3;
48040
+ var liveUpstreamTimers = /* @__PURE__ */ new Set();
48011
48041
  async function fetchWithTimeout(url, opts, timeoutMs = UPSTREAM_TIMEOUT_MS, externalSignal) {
48012
48042
  const controller = new AbortController();
48013
- let timer2 = setTimeout(() => controller.abort(), timeoutMs);
48043
+ const armTimer = () => {
48044
+ const t = setTimeout(() => {
48045
+ liveUpstreamTimers.delete(t);
48046
+ controller.abort();
48047
+ }, timeoutMs);
48048
+ liveUpstreamTimers.add(t);
48049
+ return t;
48050
+ };
48051
+ let timer2 = armTimer();
48014
48052
  const rearm = () => {
48015
48053
  clearTimeout(timer2);
48016
- timer2 = setTimeout(() => controller.abort(), timeoutMs);
48054
+ liveUpstreamTimers.delete(timer2);
48055
+ timer2 = armTimer();
48017
48056
  };
48018
48057
  let onExternalAbort = null;
48019
48058
  if (externalSignal) {
@@ -48025,6 +48064,7 @@ async function fetchWithTimeout(url, opts, timeoutMs = UPSTREAM_TIMEOUT_MS, exte
48025
48064
  }
48026
48065
  const cleanup = () => {
48027
48066
  clearTimeout(timer2);
48067
+ liveUpstreamTimers.delete(timer2);
48028
48068
  if (onExternalAbort && externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
48029
48069
  };
48030
48070
  try {
@@ -48154,6 +48194,7 @@ var CACHE_FILE = path3.join(cacheDir(), "models-dev.json");
48154
48194
  var TTL_MS = 24 * 60 * 60 * 1e3;
48155
48195
  var cache2 = null;
48156
48196
  var loading = null;
48197
+ var warnedConflicts = /* @__PURE__ */ new Set();
48157
48198
  var snapshotReg = null;
48158
48199
  var snapshotMs = 0;
48159
48200
  function bundledSnapshotRegistry() {
@@ -48326,15 +48367,22 @@ function registryLookup(reg, model, host) {
48326
48367
  }
48327
48368
  if (provider === void 0) {
48328
48369
  const suffix = `/${model}`;
48329
- let agreed;
48370
+ let max;
48371
+ const distinct = /* @__PURE__ */ new Set();
48372
+ const parts = [];
48330
48373
  for (const key of Object.keys(reg)) {
48331
48374
  if (!key.endsWith(suffix)) continue;
48332
48375
  const ctx = reg[key].limit?.context;
48333
48376
  if (typeof ctx !== "number" || ctx <= 0) continue;
48334
- if (agreed === void 0) agreed = ctx;
48335
- else if (agreed !== ctx) return void 0;
48377
+ if (max === void 0 || ctx > max) max = ctx;
48378
+ distinct.add(ctx);
48379
+ parts.push(`${key}=${ctx}`);
48336
48380
  }
48337
- return agreed;
48381
+ if (max !== void 0 && distinct.size > 1 && !warnedConflicts.has(model)) {
48382
+ warnedConflicts.add(model);
48383
+ log("warn", `[acp-registry] conflicting context windows for "${model}" (${parts.join(", ")}) \u2014 using max ${max}`);
48384
+ }
48385
+ return max;
48338
48386
  }
48339
48387
  return void 0;
48340
48388
  }
@@ -48459,11 +48507,14 @@ ${summary}` }]
48459
48507
  }
48460
48508
  return { items, replaced, dropped };
48461
48509
  }
48462
- function codexCompactGate(session, effectiveLimit, transformOk) {
48463
- if (!transformOk || effectiveLimit <= 0) return false;
48510
+ function codexCompactGatePre(session, effectiveLimit) {
48511
+ if (effectiveLimit <= 0) return false;
48464
48512
  if (session.stats.lastInputTokens >= effectiveLimit * 0.9) return false;
48465
48513
  return session.state.blocks.some((b2) => b2.active);
48466
48514
  }
48515
+ function codexCompactGate(session, effectiveLimit, transformOk) {
48516
+ return transformOk && codexCompactGatePre(session, effectiveLimit);
48517
+ }
48467
48518
  function buildTriggerForgeBody(summaryText, usage, stream2) {
48468
48519
  const compactionItem = {
48469
48520
  type: "compaction",
@@ -49940,7 +49991,13 @@ function buildSession(parsed) {
49940
49991
  metadata: parsed.metadata ?? {},
49941
49992
  state: mergeState(parsed.state),
49942
49993
  createdAt: parsed.createdAt ?? Date.now(),
49943
- lastSeen: Date.now(),
49994
+ // #404: lastSeen reflects the on-disk savedAt (the last real
49995
+ // activity), NOT the restore moment — a restart must not fabricate
49996
+ // activity for every session (broke fallback=latest ties, panel
49997
+ // freshness, and eviction ordering). Consumers that need "has this
49998
+ // session been used since boot" read the restored flag.
49999
+ lastSeen: parsed.savedAt ?? Date.now(),
50000
+ restored: true,
49944
50001
  blockContents,
49945
50002
  lastMessages: Array.isArray(parsed.messages) ? parsed.messages : void 0,
49946
50003
  inFlight: 0,
@@ -49995,7 +50052,8 @@ async function initSessions() {
49995
50052
  await store.migrateLegacyIds();
49996
50053
  const loaded = await store.loadAll();
49997
50054
  if (loaded.size > MAX_SESSIONS) {
49998
- const entries = [...loaded.entries()].sort((a, b2) => (b2[1].createdAt ?? 0) - (a[1].createdAt ?? 0));
50055
+ const freshness = (s3) => Math.max(s3.createdAt ?? 0, s3.lastSeen ?? 0);
50056
+ const entries = [...loaded.entries()].sort((a, b2) => freshness(b2[1]) - freshness(a[1]));
49999
50057
  for (const [id, s3] of entries) {
50000
50058
  if (sessions.size >= MAX_SESSIONS) break;
50001
50059
  sessions.set(id, s3);
@@ -50008,6 +50066,7 @@ function getSession(id, meta) {
50008
50066
  const existing = sessions.get(id);
50009
50067
  if (existing) {
50010
50068
  existing.lastSeen = Date.now();
50069
+ existing.restored = false;
50011
50070
  if (meta?.protocol && !existing.meta.protocol) existing.meta.protocol = meta.protocol;
50012
50071
  if (meta?.upstreamOrigin && !existing.meta.upstreamOrigin) existing.meta.upstreamOrigin = meta.upstreamOrigin;
50013
50072
  if (meta?.label && !existing.meta.label) existing.meta.label = meta.label;
@@ -50017,6 +50076,7 @@ function getSession(id, meta) {
50017
50076
  const reloaded = store.loadSync(id, meta);
50018
50077
  if (reloaded) {
50019
50078
  reloaded.lastSeen = Date.now();
50079
+ reloaded.restored = false;
50020
50080
  reloaded.persisted = true;
50021
50081
  sessions.set(id, reloaded);
50022
50082
  return reloaded;
@@ -51027,7 +51087,7 @@ async function saveProviders(){const button=byId("save-providers");busy(button,t
51027
51087
  async function saveCompress(){const button=byId("save-compress");busy(button,true,"\u4FDD\u5B58\u4E2D\u2026");try{const text=byId("compress-json").value.trim();const compress=text?JSON.parse(text):null;await json("/__bili/config",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({compress})});toast("\u538B\u7F29\u53C2\u6570\u5DF2\u70ED\u66F4\u65B0")}catch(error){toast(String(error),true)}finally{busy(button,false)}}
51028
51088
  async function loadOverrides(){try{const data=await loadConfig();const providers=data.providers||{};const box=byId("upstream-overrides");const entries=Object.entries(providers);if(entries.length===0){box.innerHTML='<p class="status">\u6682\u65E0 Provider\u3002\u5728"\u9AD8\u7EA7\u8BBE\u7F6E"\u9875\u6DFB\u52A0 Provider \u540E\u53EF\u5728\u6B64\u6309 URL \u914D\u7F6E\u4EE3\u7406\u3002</p>';return}box.innerHTML='<table><thead><tr><th>Provider URL</th><th>\u4E0A\u6E38\u4EE3\u7406\uFF08\u7A7A=\u7EE7\u627F\u5168\u5C40\uFF09</th></tr></thead><tbody>'+entries.map(([url,route])=>'<tr><td class="mono">'+escapeHtml(url)+'</td><td><input class="mono override-proxy" data-url="'+escapeHtml(url)+'" value="'+escapeHtml((route&&route.proxy)||"")+'" placeholder="\u7EE7\u627F\u5168\u5C40"></td></tr>').join("")+'</tbody></table>'}catch(error){byId("upstream-overrides").textContent=String(error)}}
51029
51089
  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)}}
51030
- 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())+"</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}}
51090
+ 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}}
51031
51091
  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)}}
51032
51092
  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);
51033
51093
  Promise.all([loadConfig(),loadUpstream()]).catch((error)=>toast(String(error),true));setInterval(()=>{if(byId("page-sessions").classList.contains("active"))loadSessions().catch(()=>{})},5000);
@@ -51037,7 +51097,7 @@ Promise.all([loadConfig(),loadUpstream()]).catch((error)=>toast(String(error),tr
51037
51097
  var WEB_STYLES = `
51038
51098
  :root{--bg:#0f1115;--panel:#171a21;--panel2:#1e222c;--line:#2b313d;--text:#e7eaf0;--muted:#8f98a8;--blue:#6ea8fe;--green:#55c98b;--yellow:#e9b949;--red:#ef6b73;--radius:10px}
51039
51099
  *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;min-height:100vh}.app{display:grid;grid-template-columns:220px 1fr;min-height:100vh}.sidebar{border-right:1px solid var(--line);padding:22px 14px;background:#12151b;position:sticky;top:0;height:100vh}.brand{font-size:18px;font-weight:700;padding:0 10px 22px}.brand span{color:var(--blue)}.version{display:block;color:var(--muted);font-size:11px;font-weight:400;margin-top:2px}.nav{display:grid;gap:4px}.nav button{appearance:none;border:0;background:transparent;color:var(--muted);text-align:left;padding:10px 12px;border-radius:7px;cursor:pointer;font:inherit}.nav button:hover,.nav button.active{background:var(--panel2);color:var(--text)}.main{padding:32px;max-width:1050px;width:100%}.page{display:none}.page.active{display:block}h1{font-size:23px;margin:0 0 4px}h2{font-size:15px;margin:0}.lead{color:var(--muted);margin:0 0 24px}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:18px;margin-bottom:14px}.card-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.badge{border-radius:99px;padding:3px 9px;font-size:12px;background:var(--panel2);color:var(--muted)}.badge.ok{color:var(--green)}.badge.warn{color:var(--yellow)}.badge.err{color:var(--red)}.kv{display:grid;grid-template-columns:135px 1fr;gap:10px 14px;margin:0}.kv dt{color:var(--muted)}.kv dd{margin:0;word-break:break-all}.mono{font-family:"Cascadia Code",Consolas,monospace;font-size:12px}.actions{display:flex;gap:9px;flex-wrap:wrap;margin-top:18px}.btn{border:1px solid var(--line);background:var(--panel2);color:var(--text);border-radius:7px;padding:8px 13px;cursor:pointer;font:inherit}.btn:hover{border-color:#4b5668}.btn.primary{background:var(--blue);border-color:var(--blue);color:#0b1220;font-weight:600}.btn.danger{color:var(--red)}.btn:disabled{opacity:.55;cursor:wait}.field{display:grid;gap:6px;margin:12px 0}.field label{color:var(--muted);font-size:12px}input,textarea,select{width:100%;background:#101319;color:var(--text);border:1px solid var(--line);border-radius:7px;padding:9px 10px;font:inherit}textarea{min-height:250px;font-family:"Cascadia Code",Consolas,monospace;font-size:12px;resize:vertical}.modes{display:flex;gap:18px;margin:12px 0}.modes label{display:flex;gap:7px;align-items:center;color:var(--muted)}.modes input{width:auto}.notice{border-left:3px solid var(--yellow);padding:10px 12px;background:rgba(233,185,73,.08);color:#d8c185;margin-top:14px}.status{color:var(--muted);white-space:pre-wrap}.status.ok{color:var(--green)}.status.err{color:var(--red)}table{width:100%;border-collapse:collapse}th,td{text-align:left;border-bottom:1px solid var(--line);padding:9px 8px;font-size:12px}th{color:var(--muted);font-weight:500}.toast{position:fixed;right:24px;bottom:24px;background:var(--green);color:#07140d;padding:10px 15px;border-radius:8px;opacity:0;transform:translateY(8px);transition:.2s;pointer-events:none}.toast.show{opacity:1;transform:none}.toast.err{background:var(--red);color:#1b0608}
51040
- .section-title{font-size:17px;margin:28px 0 4px}.hint{color:var(--muted);margin:0 0 14px;font-size:13px}.btn.small{padding:4px 10px;font-size:12px}.config-block{background:var(--panel2);border:1px solid var(--line);border-radius:6px;padding:10px 14px;margin:8px 0 12px;overflow-x:auto;font-size:13px}.config-block code{font-family:"Cascadia Code",Consolas,monospace;color:var(--text)}.fork-ribbon{position:fixed;top:0;right:0;background:#1a1a2e;color:#fff;padding:6px 16px;font-size:12px;text-decoration:none;border-radius:0 0 0 8px;z-index:100;opacity:.8;transition:.2s}.fork-ribbon:hover{opacity:1}
51100
+ .section-title{font-size:17px;margin:28px 0 4px}.hint{color:var(--muted);margin:0 0 14px;font-size:13px}.muted{color:var(--muted);font-size:11px}.btn.small{padding:4px 10px;font-size:12px}.config-block{background:var(--panel2);border:1px solid var(--line);border-radius:6px;padding:10px 14px;margin:8px 0 12px;overflow-x:auto;font-size:13px}.config-block code{font-family:"Cascadia Code",Consolas,monospace;color:var(--text)}.fork-ribbon{position:fixed;top:0;right:0;background:#1a1a2e;color:#fff;padding:6px 16px;font-size:12px;text-decoration:none;border-radius:0 0 0 8px;z-index:100;opacity:.8;transition:.2s}.fork-ribbon:hover{opacity:1}
51041
51101
  @media(max-width:760px){.app{grid-template-columns:1fr}.sidebar{height:auto;position:static;border-right:0;border-bottom:1px solid var(--line)}.nav{display:flex;overflow:auto}.main{padding:20px}.grid{grid-template-columns:1fr}.kv{grid-template-columns:110px 1fr}}
51042
51102
  `;
51043
51103
 
@@ -51066,7 +51126,7 @@ function renderPage(origin, version2) {
51066
51126
  <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>
51067
51127
  <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>
51068
51128
  <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>
51069
- <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>`;
51129
+ <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 / minCompressRangeChars\uFF08\u5355\u4F4D\uFF1A\u5B57\u7B26\uFF0C\u65E7\u540D minCompressRange \u517C\u5BB9\uFF09 / 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>`;
51070
51130
  }
51071
51131
 
51072
51132
  // src/web/api.ts
@@ -54353,10 +54413,14 @@ function handlePluginStatus(conversationId2, res, fallbackLatest = false) {
54353
54413
  let session = entry ? peekSession(entry.sessionId) : void 0;
54354
54414
  let viaFallback = false;
54355
54415
  if ((!entry || !session) && fallbackLatest) {
54356
- const latest = listSessions().filter((s3) => typeof s3.lastSeen === "number").sort((a, b2) => (b2.lastSeen ?? 0) - (a.lastSeen ?? 0))[0];
54416
+ const latest = listSessions().filter((s3) => s3.restored !== true).sort((a, b2) => (b2.lastSeen ?? 0) - (a.lastSeen ?? 0))[0];
54357
54417
  if (latest) {
54358
54418
  session = latest;
54359
54419
  viaFallback = true;
54420
+ } else {
54421
+ res.writeHead(404, { "content-type": "application/json" });
54422
+ res.end(JSON.stringify({ ok: false, error: "no session with activity since boot \u2014 issue a model request or pass the conversation id" }));
54423
+ return;
54360
54424
  }
54361
54425
  }
54362
54426
  if (!session) {
@@ -54563,6 +54627,12 @@ data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: delt
54563
54627
  return new Promise((r) => res.once("drain", () => r()));
54564
54628
  }
54565
54629
  };
54630
+ const settleUsage = () => {
54631
+ if (session && (acc.inputTokens !== void 0 || acc.outputTokens !== void 0 || acc.cachedTokens !== void 0)) {
54632
+ applyUsageSample(session, acc, protocol);
54633
+ markDirty(session);
54634
+ }
54635
+ };
54566
54636
  const pushField = (field, index, text) => {
54567
54637
  const s3 = filterFor(field, index);
54568
54638
  const clean = s3.filter.push(text);
@@ -54668,10 +54738,14 @@ data: ${JSON.stringify({ type: "content_block_delta", index, delta: { type: delt
54668
54738
  }
54669
54739
  const rest = flushTails();
54670
54740
  if (rest.length > 0 && !res.destroyed && !res.writableEnded) await write(rest);
54671
- if (session && (acc.inputTokens !== void 0 || acc.outputTokens !== void 0 || acc.cachedTokens !== void 0)) {
54672
- applyUsageSample(session, acc, protocol);
54673
- markDirty(session);
54741
+ settleUsage();
54742
+ } catch (e) {
54743
+ settleUsage();
54744
+ if (res.destroyed || res.writableEnded) {
54745
+ log2?.("client aborted mid-stream");
54746
+ return;
54674
54747
  }
54748
+ throw e;
54675
54749
  } finally {
54676
54750
  reader.releaseLock();
54677
54751
  res.end();
@@ -54705,6 +54779,12 @@ async function pipePluginResponsesWithStrip(stream2, res, session, log2) {
54705
54779
  return new Promise((r) => res.once("drain", () => r()));
54706
54780
  }
54707
54781
  };
54782
+ const settleUsage = () => {
54783
+ if (session && (acc.inputTokens !== void 0 || acc.outputTokens !== void 0 || acc.cachedTokens !== void 0)) {
54784
+ applyUsageSample(session, acc, "responses");
54785
+ markDirty(session);
54786
+ }
54787
+ };
54708
54788
  let lastDeltaMeta = null;
54709
54789
  const flushTail = (after) => {
54710
54790
  const tail = tagFilter.flush();
@@ -54777,10 +54857,14 @@ async function pipePluginResponsesWithStrip(stream2, res, session, log2) {
54777
54857
  const rest = flushTail("");
54778
54858
  if (rest.length > 0) await write(rest);
54779
54859
  }
54780
- if (session && (acc.inputTokens !== void 0 || acc.outputTokens !== void 0 || acc.cachedTokens !== void 0)) {
54781
- applyUsageSample(session, acc, "responses");
54782
- markDirty(session);
54860
+ settleUsage();
54861
+ } catch (e) {
54862
+ settleUsage();
54863
+ if (res.destroyed || res.writableEnded) {
54864
+ log2?.("client aborted mid-stream");
54865
+ return;
54783
54866
  }
54867
+ throw e;
54784
54868
  } finally {
54785
54869
  reader.releaseLock();
54786
54870
  res.end();
@@ -54806,10 +54890,16 @@ function rebuildEvent(rawEvent, ev) {
54806
54890
  async function pipePluginJson(stream2, res, session, protocol) {
54807
54891
  const reader = stream2.getReader();
54808
54892
  const chunks = [];
54809
- for (; ; ) {
54810
- const { done, value } = await reader.read();
54811
- if (done) break;
54812
- if (value && value.length > 0) chunks.push(Buffer.from(value));
54893
+ try {
54894
+ for (; ; ) {
54895
+ const { done, value } = await reader.read();
54896
+ if (done) break;
54897
+ if (value && value.length > 0) chunks.push(Buffer.from(value));
54898
+ }
54899
+ } catch (e) {
54900
+ if (!res.writableEnded) res.end();
54901
+ if (res.destroyed || res.writableEnded) return;
54902
+ throw e;
54813
54903
  }
54814
54904
  reader.releaseLock();
54815
54905
  const text = Buffer.concat(chunks).toString("utf8");
@@ -56460,7 +56550,10 @@ async function startServer(opts) {
56460
56550
  await handle(req, res, opts, core, config, log2, instanceId, instanceStartedAt);
56461
56551
  } catch (err2) {
56462
56552
  const msg = String(err2);
56463
- log2("error", msg);
56553
+ const e = err2;
56554
+ const clientAbort = (e?.name === "AbortError" || /abort/i.test(String(e?.message ?? ""))) && (res.destroyed || res.writableEnded);
56555
+ if (clientAbort) log2("info", `client aborted mid-stream: ${msg}`);
56556
+ else log2("error", msg);
56464
56557
  if (!res.headersSent) {
56465
56558
  const status = msg.includes("exceeds") ? 413 : 502;
56466
56559
  res.writeHead(status, { "content-type": "application/json" });
@@ -57012,6 +57105,21 @@ ${bodyBuffer.toString("utf8")}`);
57012
57105
  try {
57013
57106
  await withSessionLock(session, async () => {
57014
57107
  const runPrepare = () => countTokens ? prepareCountTokens(parsed, core, reqConfig, log2, session) : protocol === "anthropic" ? prepareAnthropic(parsed, req, opts, core, reqConfig, reqPrompts, log2, session, pluginMode) : protocol === "openai" ? prepareOpenai(parsed, req, opts, core, reqConfig, reqPrompts, log2, session, pluginMode, nativeWindow) : responsesCompact ? prepareResponsesCompact(bodyBuffer, parsed, session, req, core, reqConfig, log2) : prepareResponses(parsed, req, opts, core, reqConfig, reqPrompts, log2, session, responsesIdentity, pluginMode, upstreamOrigin, nativeWindow);
57108
+ const isCodexCompactTrigger = protocol === "responses" && !responsesCompact && isCodexClient(req.headers) && hasCompactionTrigger(parsed.input);
57109
+ if (isCodexCompactTrigger) {
57110
+ const mode = codexCompactMode();
57111
+ const gatePre = codexCompactGatePre(session, reqConfig.modelContextLimit);
57112
+ if (mode === "intercept" && gatePre) prepared = runPrepare();
57113
+ if (prepared?.codexForge) {
57114
+ await forward(req, res, opts, prepared.body, prepared, core, reqConfig, log2, route, instanceId, affinity);
57115
+ rememberPluginMessages(sessionId, prepared.processedMessages, prepared.originalMessages, prepared.nudge);
57116
+ return;
57117
+ }
57118
+ const why = mode !== "intercept" ? "BILI_CODEX_COMPACT=pass" : !gatePre ? "gate preconditions not met" : "transform/forge failed";
57119
+ log2("info", `[${session.id}] codex compaction_trigger request not intercepted (${why}) \u2014 forwarding verbatim (no preflight, no rebuild, no window clamp)`);
57120
+ await forward(req, res, opts, bodyBuffer, null, core, reqConfig, log2, route, instanceId, affinity);
57121
+ return;
57122
+ }
57015
57123
  prepared = runPrepare();
57016
57124
  if (!countTokens && !responsesCompact) {
57017
57125
  const outcome = await preflightCompressIfNeeded(
@@ -57457,7 +57565,7 @@ function prepareResponsesCompact(body, parsed, session, req, core, config, log2)
57457
57565
  compressInjected: false,
57458
57566
  resetAfterSuccess: true
57459
57567
  };
57460
- if (codexCompactMode() !== "intercept" || !isCodexClient(req.headers) || !Array.isArray(parsed.input)) {
57568
+ if (codexCompactMode() !== "intercept" || !isCodexClient(req.headers) || !Array.isArray(parsed.input) || !codexCompactGatePre(session, config.modelContextLimit)) {
57461
57569
  return base;
57462
57570
  }
57463
57571
  const prevState = session.state;
@@ -57849,187 +57957,205 @@ ${hdrText}
57849
57957
  return;
57850
57958
  }
57851
57959
  if (prepared?.pluginMode) {
57852
- if (prepared.stream) {
57853
- if (prepared.protocol === "responses") {
57854
- await pipePluginResponsesWithStrip(upstream.body, res, prepared.session, (msg) => log2("info", `[${prepared.session.id}] ${msg}`));
57855
- } else {
57856
- await pipePluginChatWithStrip(upstream.body, res, prepared.protocol, prepared.session, (msg) => log2("info", `[${prepared.session.id}] ${msg}`));
57857
- }
57858
- } else {
57859
- await pipePluginJson(upstream.body, res, prepared.session, prepared.protocol);
57860
- }
57861
- clearUpstreamTimer();
57862
- return;
57863
- }
57864
- let responseBody = upstream.body;
57865
- if (prepared !== null && prepared.stream && maxFakeCompletionRetries() > 0) {
57866
- const resolvedBuf = await resolveFakeCompletion(upstream.body, {
57867
- protocol: prepared.protocol,
57868
- body,
57869
- upstreamUrl,
57870
- reqHeaders: buildForwardHeaders(headers),
57871
- proxyUrl,
57872
- signal: clientAbort.signal,
57873
- session: prepared.session,
57874
- log: log2
57875
- });
57876
- responseBody = bufferToStream(resolvedBuf);
57877
- }
57878
- const useRewriter = prepared !== null && prepared.compressInjected && prepared.processedMessages.length > 0;
57879
- if (!useRewriter || prepared === null) {
57880
- if (prepared && prepared.resetAfterSuccess) {
57881
- const [toClient, toObserve] = responseBody.tee();
57882
- const observed = observeResponsesTerminalState(toObserve, prepared.stream);
57883
- const tagLog = (msg) => log2("info", `[${prepared.session.id}] ${msg}`);
57884
- if ((upstream.headers.get("content-type") ?? "").includes("text/event-stream")) {
57885
- await pipePluginResponsesWithStrip(toClient, res, void 0, tagLog);
57886
- } else {
57887
- await pipeThrough(toClient, res);
57888
- }
57889
- clearUpstreamTimer();
57890
- const terminal = await observed;
57891
- if (terminal === "completed") {
57892
- markNativeCompactionBoundary(prepared.session);
57893
- log2("info", `[${prepared.session.id}] native Responses compact completed; rebase scheduled for next Responses turn`);
57894
- } else {
57895
- log2("warn", `[${prepared.session.id}] native compact response terminal=${terminal}; rebase NOT scheduled`);
57960
+ try {
57961
+ let pluginBody = upstream.body;
57962
+ if (prepared.stream && maxFakeCompletionRetries() > 0) {
57963
+ const resolvedBuf = await resolveFakeCompletion(pluginBody, {
57964
+ protocol: prepared.protocol,
57965
+ body,
57966
+ upstreamUrl,
57967
+ reqHeaders: buildForwardHeaders(headers),
57968
+ proxyUrl,
57969
+ signal: clientAbort.signal,
57970
+ session: prepared.session,
57971
+ log: log2
57972
+ });
57973
+ pluginBody = bufferToStream(resolvedBuf);
57896
57974
  }
57897
- } else if (prepared && prepared.stream && (upstream.headers.get("content-type") ?? "").includes("text/event-stream")) {
57898
- const p2 = prepared;
57899
- const tagLog = (msg) => log2("info", `[${p2.session.id}] ${msg}`);
57900
- if (p2.protocol === "responses") {
57901
- await pipePluginResponsesWithStrip(responseBody, res, void 0, tagLog);
57975
+ if (prepared.stream) {
57976
+ if (prepared.protocol === "responses") {
57977
+ await pipePluginResponsesWithStrip(pluginBody, res, prepared.session, (msg) => log2("info", `[${prepared.session.id}] ${msg}`));
57978
+ } else {
57979
+ await pipePluginChatWithStrip(pluginBody, res, prepared.protocol, prepared.session, (msg) => log2("info", `[${prepared.session.id}] ${msg}`));
57980
+ }
57902
57981
  } else {
57903
- await pipePluginChatWithStrip(responseBody, res, p2.protocol, void 0, tagLog);
57982
+ await pipePluginJson(pluginBody, res, prepared.session, prepared.protocol);
57904
57983
  }
57905
- clearUpstreamTimer();
57906
- } else if (prepared && (upstream.headers.get("content-type") ?? "").includes("application/json")) {
57907
- await pipePluginJson(responseBody, res, void 0, prepared.protocol);
57908
- clearUpstreamTimer();
57909
- } else {
57910
- await pipeThrough(responseBody, res);
57984
+ } finally {
57911
57985
  clearUpstreamTimer();
57912
57986
  }
57913
57987
  return;
57914
57988
  }
57915
- const ctx = {
57916
- core,
57917
- config,
57918
- messages: prepared.originalMessages,
57919
- session: prepared.session,
57920
- log: (msg) => log2("info", `[${prepared.session.id}] ${msg}`),
57921
- debug: opts.debug
57922
- };
57923
- if (prepared.stream) {
57924
- let streamToRead = responseBody;
57925
- let dumpRaw;
57926
- if (opts.dumpSse) {
57927
- const [a, b2] = responseBody.tee();
57928
- streamToRead = a;
57929
- dumpRaw = dumpStreamToFile(b2, opts.dumpSse, `${Date.now()}-${safeSessionId(prepared.session.id)}-raw.sse`);
57930
- }
57931
- try {
57932
- const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
57933
- const reqHeaders = buildForwardHeaders(headers);
57934
- const textProtocol = prepared.protocol === "responses" && !!prepared.responsesTextProtocol;
57935
- const systemPrompt = textProtocol ? buildCompressHybridSystemPrompt(prepared.prompts ?? defaultPrompts) : buildCompressSystemPrompt(prepared.prompts ?? defaultPrompts);
57936
- const adapter = pickAdapter(prepared.protocol, parsedReq, textProtocol, prepared.responsesProjection, prepared.anthropicSystem, prepared.openaiSystemText);
57937
- const refreshFolded = (current) => {
57938
- const turn = core.processTurn({
57939
- messages: prepared.originalMessages,
57940
- state: prepared.session.state,
57941
- config,
57942
- tokenCount: prepared.session.stats.lastInputTokens,
57943
- renderTags: prepared.renderTags ?? "text-only"
57944
- });
57945
- prepared.session.state = turn.state;
57946
- const records = current.filter((m2) => typeof m2.id === "string" && m2.id.startsWith("acp_loop_"));
57947
- return stripKernelSummaries([...turn.messages, ...records], turn.state);
57948
- };
57949
- const loop = runCompressLoop(
57950
- streamToRead,
57951
- { core, config, messages: prepared.processedMessages.length > 0 ? prepared.processedMessages : prepared.originalMessages, compressMessages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, protocol: prepared.protocol, textProtocol, debug: opts.debug, nudge: prepared.nudge, refreshFolded },
57952
- parsedReq,
57953
- { url: upstreamUrl, headers: reqHeaders },
57954
- adapter,
57955
- systemPrompt,
57956
- clientAbort.signal
57957
- );
57958
- let protocolFragmentWarned = false;
57959
- for await (const chunk of loop) {
57960
- if (res.destroyed || res.writableEnded) break;
57961
- {
57962
- const s3 = chunk.toString("utf8");
57963
- if (s3.includes("<acp ") || s3.includes("</acp")) {
57964
- log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains <acp tag`);
57965
- } else if (!protocolFragmentWarned && containsToolCallXmlFragment(s3)) {
57966
- protocolFragmentWarned = true;
57967
- log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains tool-call XML fragment (possible tag echo; not stripped)`);
57968
- }
57969
- }
57970
- res.write(chunk);
57971
- if (res.writableNeedDrain) {
57972
- await Promise.race([
57973
- new Promise((r) => res.once("drain", () => r())),
57974
- new Promise((r) => res.once("close", () => r()))
57975
- ]);
57989
+ let responseBody = upstream.body;
57990
+ try {
57991
+ if (prepared !== null && prepared.stream && maxFakeCompletionRetries() > 0) {
57992
+ const resolvedBuf = await resolveFakeCompletion(upstream.body, {
57993
+ protocol: prepared.protocol,
57994
+ body,
57995
+ upstreamUrl,
57996
+ reqHeaders: buildForwardHeaders(headers),
57997
+ proxyUrl,
57998
+ signal: clientAbort.signal,
57999
+ session: prepared.session,
58000
+ log: log2
58001
+ });
58002
+ responseBody = bufferToStream(resolvedBuf);
58003
+ }
58004
+ const useRewriter = prepared !== null && prepared.compressInjected && prepared.processedMessages.length > 0;
58005
+ if (!useRewriter || prepared === null) {
58006
+ if (prepared && prepared.resetAfterSuccess) {
58007
+ const [toClient, toObserve] = responseBody.tee();
58008
+ const observed = observeResponsesTerminalState(toObserve, prepared.stream);
58009
+ const tagLog = (msg) => log2("info", `[${prepared.session.id}] ${msg}`);
58010
+ if ((upstream.headers.get("content-type") ?? "").includes("text/event-stream")) {
58011
+ await pipePluginResponsesWithStrip(toClient, res, void 0, tagLog);
58012
+ } else {
58013
+ await pipeThrough(toClient, res);
57976
58014
  }
57977
- if (res.destroyed || res.writableEnded) break;
58015
+ const terminal = await observed;
58016
+ if (terminal === "completed") {
58017
+ markNativeCompactionBoundary(prepared.session);
58018
+ log2("info", `[${prepared.session.id}] native Responses compact completed; rebase scheduled for next Responses turn`);
58019
+ } else {
58020
+ log2("warn", `[${prepared.session.id}] native compact response terminal=${terminal}; rebase NOT scheduled`);
58021
+ }
58022
+ } else if (prepared && prepared.stream && (upstream.headers.get("content-type") ?? "").includes("text/event-stream")) {
58023
+ const p2 = prepared;
58024
+ const tagLog = (msg) => log2("info", `[${p2.session.id}] ${msg}`);
58025
+ if (p2.protocol === "responses") {
58026
+ await pipePluginResponsesWithStrip(responseBody, res, void 0, tagLog);
58027
+ } else {
58028
+ await pipePluginChatWithStrip(responseBody, res, p2.protocol, void 0, tagLog);
58029
+ }
58030
+ } else if (prepared && (upstream.headers.get("content-type") ?? "").includes("application/json")) {
58031
+ await pipePluginJson(responseBody, res, void 0, prepared.protocol);
58032
+ } else {
58033
+ await pipeThrough(responseBody, res);
57978
58034
  }
57979
- res.end();
57980
- } catch (e) {
57981
- emitStreamError(res, prepared.protocol, e?.message ?? String(e), (m2) => log2("error", `[${prepared.session.id}] ${m2}`));
57982
- } finally {
57983
- clearUpstreamTimer();
57984
- if (dumpRaw) await dumpRaw;
58035
+ return;
57985
58036
  }
57986
- } else {
57987
- try {
57988
- const buf = await upstream.arrayBuffer();
57989
- const text = Buffer.from(buf).toString("utf8");
58037
+ const ctx = {
58038
+ core,
58039
+ config,
58040
+ messages: prepared.originalMessages,
58041
+ session: prepared.session,
58042
+ log: (msg) => log2("info", `[${prepared.session.id}] ${msg}`),
58043
+ debug: opts.debug
58044
+ };
58045
+ if (prepared.stream) {
58046
+ let streamToRead = responseBody;
58047
+ let dumpRaw;
58048
+ if (opts.dumpSse) {
58049
+ const [a, b2] = responseBody.tee();
58050
+ streamToRead = a;
58051
+ dumpRaw = dumpStreamToFile(b2, opts.dumpSse, `${Date.now()}-${safeSessionId(prepared.session.id)}-raw.sse`);
58052
+ }
57990
58053
  try {
57991
- let json = JSON.parse(text);
57992
- if (prepared.protocol === "responses" && prepared.responsesTextProtocol) {
57993
- const requestBody = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
57994
- const requestHeaders = buildForwardHeaders(headers);
57995
- json = await compressLoopResponsesJson(
57996
- json,
57997
- { core, config, messages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol: true },
57998
- requestBody,
57999
- { url: upstreamUrl, headers: requestHeaders }
58000
- );
58001
- }
58002
- const u2 = json.usage ?? {};
58003
- const { total, cached } = usageTotals(prepared.protocol, u2);
58004
- if (typeof total === "number") {
58005
- prepared.session.stats.inputTokens += total;
58006
- prepared.session.stats.lastInputTokens = Math.max(
58007
- 0,
58008
- total - (prepared.session.stats.compressCreditTokens ?? 0)
58009
- );
58010
- if (typeof cached === "number") {
58011
- prepared.session.stats.cachedTokens += cached;
58012
- prepared.session.stats.cacheSamples += 1;
58054
+ const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
58055
+ const reqHeaders = buildForwardHeaders(headers);
58056
+ const textProtocol = prepared.protocol === "responses" && !!prepared.responsesTextProtocol;
58057
+ const systemPrompt = textProtocol ? buildCompressHybridSystemPrompt(prepared.prompts ?? defaultPrompts) : buildCompressSystemPrompt(prepared.prompts ?? defaultPrompts);
58058
+ const adapter = pickAdapter(prepared.protocol, parsedReq, textProtocol, prepared.responsesProjection, prepared.anthropicSystem, prepared.openaiSystemText);
58059
+ const refreshFolded = (current) => {
58060
+ const turn = core.processTurn({
58061
+ messages: prepared.originalMessages,
58062
+ state: prepared.session.state,
58063
+ config,
58064
+ tokenCount: prepared.session.stats.lastInputTokens,
58065
+ renderTags: prepared.renderTags ?? "text-only"
58066
+ });
58067
+ prepared.session.state = turn.state;
58068
+ const records = current.filter((m2) => typeof m2.id === "string" && m2.id.startsWith("acp_loop_"));
58069
+ return stripKernelSummaries([...turn.messages, ...records], turn.state);
58070
+ };
58071
+ const loop = runCompressLoop(
58072
+ streamToRead,
58073
+ { core, config, messages: prepared.processedMessages.length > 0 ? prepared.processedMessages : prepared.originalMessages, compressMessages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, protocol: prepared.protocol, textProtocol, debug: opts.debug, nudge: prepared.nudge, refreshFolded },
58074
+ parsedReq,
58075
+ { url: upstreamUrl, headers: reqHeaders },
58076
+ adapter,
58077
+ systemPrompt,
58078
+ clientAbort.signal
58079
+ );
58080
+ let protocolFragmentWarned = false;
58081
+ for await (const chunk of loop) {
58082
+ if (res.destroyed || res.writableEnded) break;
58083
+ {
58084
+ const s3 = chunk.toString("utf8");
58085
+ if (s3.includes("<acp ") || s3.includes("</acp")) {
58086
+ log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains <acp tag`);
58087
+ } else if (!protocolFragmentWarned && containsToolCallXmlFragment(s3)) {
58088
+ protocolFragmentWarned = true;
58089
+ log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains tool-call XML fragment (possible tag echo; not stripped)`);
58090
+ }
58091
+ }
58092
+ res.write(chunk);
58093
+ if (res.writableNeedDrain) {
58094
+ await Promise.race([
58095
+ new Promise((r) => res.once("drain", () => r())),
58096
+ new Promise((r) => res.once("close", () => r()))
58097
+ ]);
58013
58098
  }
58014
- const out = u2.completion_tokens ?? u2.output_tokens;
58015
- if (typeof out === "number") prepared.session.stats.outputTokens += out;
58099
+ if (res.destroyed || res.writableEnded) break;
58016
58100
  }
58017
- if (prepared.protocol === "openai") {
58018
- rewriteOpenaiJsonResponse(json, ctx);
58019
- } else if (prepared.protocol === "responses") {
58020
- rewriteResponsesJsonResponse(json, ctx);
58021
- } else {
58022
- rewriteJsonResponse(json, ctx);
58101
+ res.end();
58102
+ } catch (e) {
58103
+ emitStreamError(res, prepared.protocol, e?.message ?? String(e), (m2) => log2("error", `[${prepared.session.id}] ${m2}`));
58104
+ } finally {
58105
+ clearUpstreamTimer();
58106
+ if (dumpRaw) await dumpRaw;
58107
+ markDirty(prepared.session);
58108
+ }
58109
+ } else {
58110
+ try {
58111
+ const buf = await upstream.arrayBuffer();
58112
+ const text = Buffer.from(buf).toString("utf8");
58113
+ try {
58114
+ let json = JSON.parse(text);
58115
+ if (prepared.protocol === "responses" && prepared.responsesTextProtocol) {
58116
+ const requestBody = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
58117
+ const requestHeaders = buildForwardHeaders(headers);
58118
+ json = await compressLoopResponsesJson(
58119
+ json,
58120
+ { core, config, messages: prepared.originalMessages, session: prepared.session, log: ctx.log, proxyUrl, textProtocol: true },
58121
+ requestBody,
58122
+ { url: upstreamUrl, headers: requestHeaders }
58123
+ );
58124
+ }
58125
+ const u2 = json.usage ?? {};
58126
+ const { total, cached } = usageTotals(prepared.protocol, u2);
58127
+ if (typeof total === "number") {
58128
+ prepared.session.stats.inputTokens += total;
58129
+ prepared.session.stats.lastInputTokens = Math.max(
58130
+ 0,
58131
+ total - (prepared.session.stats.compressCreditTokens ?? 0)
58132
+ );
58133
+ if (typeof cached === "number") {
58134
+ prepared.session.stats.cachedTokens += cached;
58135
+ prepared.session.stats.cacheSamples += 1;
58136
+ }
58137
+ const out = u2.completion_tokens ?? u2.output_tokens;
58138
+ if (typeof out === "number") prepared.session.stats.outputTokens += out;
58139
+ }
58140
+ if (prepared.protocol === "openai") {
58141
+ rewriteOpenaiJsonResponse(json, ctx);
58142
+ } else if (prepared.protocol === "responses") {
58143
+ rewriteResponsesJsonResponse(json, ctx);
58144
+ } else {
58145
+ rewriteJsonResponse(json, ctx);
58146
+ }
58147
+ res.end(JSON.stringify(json));
58148
+ } catch {
58149
+ res.end(text);
58023
58150
  }
58024
- res.end(JSON.stringify(json));
58025
- } catch {
58026
- res.end(text);
58151
+ } finally {
58152
+ clearUpstreamTimer();
58153
+ markDirty(prepared.session);
58027
58154
  }
58028
- } finally {
58029
- clearUpstreamTimer();
58030
58155
  }
58156
+ } finally {
58157
+ clearUpstreamTimer();
58031
58158
  }
58032
- markDirty(prepared.session);
58033
58159
  }
58034
58160
  async function readStreamToBuffer(stream2, maxBytes = 1 << 20) {
58035
58161
  const reader = stream2.getReader();
@@ -58174,7 +58300,8 @@ function sendStats(res) {
58174
58300
  outputTokens: s3.stats.outputTokens,
58175
58301
  cacheSamples: s3.stats.cacheSamples,
58176
58302
  cacheHitPct: s3.stats.cacheSamples > 0 && s3.stats.inputTokens > 0 ? Math.round(s3.stats.cachedTokens / s3.stats.inputTokens * 100) : null,
58177
- lastSeen: new Date(s3.lastSeen).toISOString()
58303
+ lastSeen: new Date(s3.lastSeen).toISOString(),
58304
+ restored: s3.restored === true
58178
58305
  }));
58179
58306
  res.writeHead(200, { "content-type": "application/json" });
58180
58307
  res.end(JSON.stringify({ sessions: sessions2 }, null, 2));