billion-context 0.1.65 → 0.1.66-pr.376.226

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
@@ -46551,9 +46551,9 @@ function parseObjectValue(value, callId, diag) {
46551
46551
  const content = value["content"];
46552
46552
  if (content === void 0) {
46553
46553
  const single = validateEntry(value, callId);
46554
- if (single) {
46554
+ if ("range" in single) {
46555
46555
  diag.kind = "ok";
46556
- return finish([single], diag);
46556
+ return finish([single.range], diag);
46557
46557
  }
46558
46558
  diag.kind = "missing-content";
46559
46559
  return finish([], diag);
@@ -46574,7 +46574,7 @@ function parseObjectValue(value, callId, diag) {
46574
46574
  diag.kind = "content-not-array";
46575
46575
  return finish([], diag);
46576
46576
  }
46577
- const { ranges, invalid } = validateEntries(entries, callId);
46577
+ const { ranges, invalid, reasons } = validateEntries(entries, callId);
46578
46578
  const topTopic = stringOr(value["topic"]);
46579
46579
  const topMaxChars = value["summaryMaxChars"];
46580
46580
  const hasTopMaxChars = typeof topMaxChars === "number" && Number.isFinite(topMaxChars);
@@ -46585,6 +46585,7 @@ function parseObjectValue(value, callId, diag) {
46585
46585
  }
46586
46586
  }
46587
46587
  diag.invalidItems = invalid;
46588
+ if (reasons.length > 0) diag.invalidReasons = reasons;
46588
46589
  diag.kind = salvaged ? "truncated" : ranges.length > 0 ? "ok" : "no-valid-ranges";
46589
46590
  return finish(ranges, diag);
46590
46591
  }
@@ -46608,36 +46609,43 @@ function finish(ranges, diag) {
46608
46609
  return { ranges, diagnostics: diag };
46609
46610
  }
46610
46611
  function finishSalvage(entries, callId, diag, truncatedShape) {
46611
- const { ranges, invalid } = validateEntries(entries, callId);
46612
+ const { ranges, invalid, reasons } = validateEntries(entries, callId);
46612
46613
  diag.invalidItems = invalid;
46614
+ if (reasons.length > 0) diag.invalidReasons = reasons;
46613
46615
  diag.kind = entries.length > 0 || truncatedShape ? "truncated" : "malformed-json";
46614
46616
  return finish(ranges, diag);
46615
46617
  }
46616
46618
  function validateEntries(entries, callId) {
46617
46619
  const ranges = [];
46620
+ const reasons = [];
46618
46621
  let invalid = 0;
46619
- for (const entry of entries) {
46620
- const range = validateEntry(entry, callId);
46621
- if (range !== null) ranges.push(range);
46622
- else invalid++;
46622
+ for (let i = 0; i < entries.length; i++) {
46623
+ const outcome = validateEntry(entries[i], callId);
46624
+ if ("range" in outcome) ranges.push(outcome.range);
46625
+ else {
46626
+ invalid++;
46627
+ reasons.push(`entry ${i}: ${outcome.reason}`);
46628
+ }
46623
46629
  }
46624
- return { ranges, invalid };
46630
+ return { ranges, invalid, reasons };
46625
46631
  }
46626
46632
  function validateEntry(entry, callId) {
46627
- if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
46633
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return { reason: "not an object" };
46628
46634
  const e = entry;
46629
46635
  const start = stringOr(e["startRef"]) ?? stringOr(e["startId"]) ?? stringOr(e["messageId"]);
46630
46636
  const end = stringOr(e["endRef"]) ?? stringOr(e["endId"]) ?? stringOr(e["messageId"]);
46631
- if (start === void 0 || end === void 0) return null;
46637
+ if (start === void 0 || end === void 0) {
46638
+ return { reason: "missing range bounds (need startRef/startId and endRef/endId)" };
46639
+ }
46632
46640
  const summary = stringOr(e["summary"]);
46633
- if (summary === void 0) return null;
46641
+ if (summary === void 0) return { reason: "missing summary" };
46634
46642
  const range = { startRef: start, endRef: end, summary };
46635
46643
  const topic = stringOr(e["topic"]);
46636
46644
  if (topic !== void 0) range.topic = topic;
46637
46645
  const maxChars = e["summaryMaxChars"];
46638
46646
  if (typeof maxChars === "number" && Number.isFinite(maxChars)) range.summaryMaxChars = maxChars;
46639
46647
  if (callId !== void 0) range.compressCallId = callId;
46640
- return range;
46648
+ return { range };
46641
46649
  }
46642
46650
  function stringOr(value) {
46643
46651
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -49260,11 +49268,16 @@ var StateStore = class {
49260
49268
  legacyFn;
49261
49269
  relPathFn;
49262
49270
  validateFn;
49271
+ retryAttempts;
49272
+ retryBaseMs;
49273
+ retryMaxMs;
49263
49274
  timers = /* @__PURE__ */ new Map();
49264
49275
  pending = /* @__PURE__ */ new Map();
49265
49276
  writeChains = /* @__PURE__ */ new Map();
49266
49277
  /** id → absolute path, populated by writes and loadAll. */
49267
49278
  discovered = /* @__PURE__ */ new Map();
49279
+ /** id → cumulative write-failure count, for rate-limited alerting. */
49280
+ failCounts = /* @__PURE__ */ new Map();
49268
49281
  /** Monotonic counter for unique temp filenames within a process. */
49269
49282
  tmpSeq = 0;
49270
49283
  constructor(opts) {
@@ -49277,6 +49290,9 @@ var StateStore = class {
49277
49290
  this.relPathFn = opts.relPath;
49278
49291
  this.legacyFn = opts.legacy;
49279
49292
  this.validateFn = opts.validate ?? defaultValidate;
49293
+ this.retryAttempts = Math.max(1, opts.retryAttempts ?? 6);
49294
+ this.retryBaseMs = Math.max(1, opts.retryBaseMs ?? 50);
49295
+ this.retryMaxMs = Math.max(this.retryBaseMs, opts.retryMaxMs ?? 1600);
49280
49296
  }
49281
49297
  /** Debounced save. Coalesces bursts; the builder runs at write time, so
49282
49298
  * the freshest state is always persisted. Never throws. */
@@ -49336,32 +49352,42 @@ var StateStore = class {
49336
49352
  } catch (e) {
49337
49353
  this.log("warn", `[persist] could not create dir ${path4.dirname(file)}: ${errText(e)}`);
49338
49354
  }
49339
- const tmp = this.tempPath(file);
49340
- try {
49341
- fs.writeFileSync(tmp, data, "utf8");
49342
- let lastErr;
49343
- for (let attempt = 0; attempt < 3; attempt++) {
49355
+ let lastErr;
49356
+ for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
49357
+ const tmp = this.tempPath(file);
49358
+ try {
49359
+ fs.writeFileSync(tmp, data, "utf8");
49360
+ fs.renameSync(tmp, file);
49361
+ lastErr = void 0;
49362
+ break;
49363
+ } catch (e) {
49364
+ lastErr = e;
49344
49365
  try {
49345
- fs.renameSync(tmp, file);
49346
- lastErr = void 0;
49347
- break;
49348
- } catch (e) {
49349
- lastErr = e;
49350
- const code = e.code;
49351
- if (code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") break;
49352
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 20 * (attempt + 1));
49366
+ fs.unlinkSync(tmp);
49367
+ } catch {
49353
49368
  }
49369
+ const code = e.code;
49370
+ if (code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") break;
49371
+ if (attempt === this.retryAttempts - 1) break;
49372
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, this.backoffMs(attempt));
49354
49373
  }
49355
- if (lastErr) throw lastErr;
49374
+ }
49375
+ if (!lastErr) {
49376
+ this.discovered.set(id, file);
49377
+ this.clearFailure(id);
49378
+ this.removeSpillSync(file);
49356
49379
  return true;
49357
- } catch (e) {
49358
- this.log("error", `[persist] flushSync failed for ${id}: ${errText(e)}`);
49359
- try {
49360
- fs.unlinkSync(tmp);
49361
- } catch {
49362
- }
49363
- return false;
49364
49380
  }
49381
+ let spillPath = null;
49382
+ try {
49383
+ const spill = this.spillPathFor(file);
49384
+ fs.writeFileSync(spill, data, "utf8");
49385
+ spillPath = spill;
49386
+ this.discovered.set(id, spill);
49387
+ } catch {
49388
+ }
49389
+ this.recordFailure(id, lastErr, spillPath);
49390
+ return spillPath !== null;
49365
49391
  }
49366
49392
  /** Load one record. Checks the discovered path (from a prior
49367
49393
  * write/loadAll), an optional relative-path hint, and the flat default
@@ -49395,13 +49421,27 @@ var StateStore = class {
49395
49421
  for (const file of files) {
49396
49422
  const envelope = this.readEnvelope(file);
49397
49423
  if (!envelope) continue;
49398
- const expected = path4.basename(this.relPathOf(envelope.id, envelope.payload)) === path4.basename(file) || flatFileNameFor(envelope.id) === path4.basename(file);
49399
- if (!expected) {
49424
+ const base = path4.basename(file);
49425
+ const relBase = path4.basename(this.relPathOf(envelope.id, envelope.payload));
49426
+ const flatBase = flatFileNameFor(envelope.id);
49427
+ let owner = null;
49428
+ if (base === relBase || base === flatBase) {
49429
+ owner = envelope.id;
49430
+ } else if (base.endsWith(".fb.json")) {
49431
+ const canonicalBase = `${base.slice(0, -".fb.json".length)}.json`;
49432
+ if (canonicalBase === relBase || canonicalBase === flatBase) {
49433
+ owner = envelope.id;
49434
+ }
49435
+ }
49436
+ if (owner === null) {
49400
49437
  this.log("warn", `[persist] skipping ${rel(file, this.dir)}: filename does not match record id`);
49401
49438
  continue;
49402
49439
  }
49403
- this.discovered.set(envelope.id, file);
49404
- out.set(envelope.id, envelope);
49440
+ const existing = out.get(owner);
49441
+ if (!existing || envelope.savedAt >= existing.savedAt) {
49442
+ out.set(owner, envelope);
49443
+ this.discovered.set(owner, file);
49444
+ }
49405
49445
  }
49406
49446
  return out;
49407
49447
  }
@@ -49455,14 +49495,25 @@ var StateStore = class {
49455
49495
  this.log("warn", `[persist] could not create dir ${path4.dirname(file)}: ${errText(e)}`);
49456
49496
  }
49457
49497
  const tmp = this.tempPath(file);
49498
+ const env = this.envelope(id, payload);
49458
49499
  try {
49459
- await fsp.writeFile(tmp, JSON.stringify(this.envelope(id, payload)), "utf8");
49460
- await renameWithRetry(tmp, file);
49500
+ await fsp.writeFile(tmp, JSON.stringify(env), "utf8");
49501
+ await this.renameWithRetry(tmp, file);
49461
49502
  this.discovered.set(id, file);
49503
+ this.clearFailure(id);
49504
+ await this.removeSpill(file);
49462
49505
  } catch (e) {
49463
49506
  await fsp.unlink(tmp).catch(() => {
49464
49507
  });
49465
- this.log("error", `[persist] write failed for ${id}: ${errText(e)}`);
49508
+ let spillPath = null;
49509
+ try {
49510
+ const spill = this.spillPathFor(file);
49511
+ await fsp.writeFile(spill, JSON.stringify(env), "utf8");
49512
+ spillPath = spill;
49513
+ this.discovered.set(id, spill);
49514
+ } catch {
49515
+ }
49516
+ this.recordFailure(id, e, spillPath);
49466
49517
  throw e;
49467
49518
  }
49468
49519
  }
@@ -49490,6 +49541,67 @@ var StateStore = class {
49490
49541
  const seq = this.tmpSeq++;
49491
49542
  return path4.join(path4.dirname(dest), `.tmp-${path4.basename(dest, ".json")}-${process.pid}-${seq}`);
49492
49543
  }
49544
+ backoffMs(attempt) {
49545
+ return Math.min(this.retryBaseMs * 2 ** attempt, this.retryMaxMs);
49546
+ }
49547
+ /** Side file for a record whose canonical write keeps failing: the
49548
+ * canonical name with a `.fb` (fallback) infix, e.g. `a.json` →
49549
+ * `a.fb.json`. One slot per id, overwritten on each spill, so a stuck
49550
+ * lock never accumulates files. Ends in `.json` so loadAll discovers it. */
49551
+ spillPathFor(file) {
49552
+ const base = path4.basename(file);
49553
+ const dot = base.lastIndexOf(".");
49554
+ const stem2 = dot > 0 ? base.slice(0, dot) : base;
49555
+ const ext = dot > 0 ? base.slice(dot) : ".json";
49556
+ return path4.join(path4.dirname(file), `${stem2}.fb${ext}`);
49557
+ }
49558
+ /** Remove a stale spill after a successful canonical write (best-effort). */
49559
+ async removeSpill(file) {
49560
+ await fsp.unlink(this.spillPathFor(file)).catch(() => {
49561
+ });
49562
+ }
49563
+ removeSpillSync(file) {
49564
+ try {
49565
+ fs.unlinkSync(this.spillPathFor(file));
49566
+ } catch {
49567
+ }
49568
+ }
49569
+ /** Rate-limited failure alerting: log on the first failure and at each
49570
+ * power-of-two count (1,2,4,8,…), so a long lock yields ~log2(N) lines
49571
+ * instead of one per write. Includes the spill path so the operator can
49572
+ * see where the data landed. */
49573
+ recordFailure(id, err2, spillPath) {
49574
+ const count = (this.failCounts.get(id) ?? 0) + 1;
49575
+ this.failCounts.set(id, count);
49576
+ const alertAt = count === 1 || count >= 2 && (count & count - 1) === 0;
49577
+ if (!alertAt) return;
49578
+ const where = spillPath ? `; data spilled to ${rel(spillPath, this.dir)}` : "; SPILLOVER ALSO FAILED \u2014 data at risk";
49579
+ this.log(count === 1 ? "error" : "warn", `[persist] write failed for ${id} (total ${count}x): ${errText(err2)}${where}`);
49580
+ }
49581
+ clearFailure(id) {
49582
+ this.failCounts.delete(id);
49583
+ }
49584
+ /** fs.rename with exponential-backoff retries on Windows transient locks
49585
+ * (EPERM/EBUSY/EACCES from AV scan, search indexer, SMB). Other errors
49586
+ * are real and rethrown immediately. */
49587
+ async renameWithRetry(src, dest) {
49588
+ let lastErr;
49589
+ for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
49590
+ try {
49591
+ await fsp.rename(src, dest);
49592
+ return;
49593
+ } catch (e) {
49594
+ lastErr = e;
49595
+ const code = e.code;
49596
+ if (code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
49597
+ if (attempt === this.retryAttempts - 1) throw e;
49598
+ const { promise, resolve } = Promise.withResolvers();
49599
+ setTimeout(resolve, this.backoffMs(attempt));
49600
+ await promise;
49601
+ }
49602
+ }
49603
+ throw lastErr;
49604
+ }
49493
49605
  /** Parse + validate one file. Corrupt or invalid records return null
49494
49606
  * (logged) instead of throwing — load paths must never block boot. */
49495
49607
  readEnvelope(file) {
@@ -49565,23 +49677,6 @@ function isEnvelopeLike(value) {
49565
49677
  function flatFileNameFor(id) {
49566
49678
  return createHash2("sha256").update(id, "utf8").digest("hex").slice(0, 24) + ".json";
49567
49679
  }
49568
- async function renameWithRetry(src, dest) {
49569
- let lastErr;
49570
- for (let attempt = 0; attempt < 3; attempt++) {
49571
- try {
49572
- await fsp.rename(src, dest);
49573
- return;
49574
- } catch (e) {
49575
- lastErr = e;
49576
- const code = e.code;
49577
- if (code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
49578
- const { promise, resolve } = Promise.withResolvers();
49579
- setTimeout(resolve, 20 * (attempt + 1));
49580
- await promise;
49581
- }
49582
- }
49583
- throw lastErr;
49584
- }
49585
49680
  function errText(e) {
49586
49681
  return e instanceof Error ? e.message : String(e);
49587
49682
  }
@@ -50005,9 +50100,9 @@ async function flushAllSessions() {
50005
50100
  function parseCompressInput(input, callId) {
50006
50101
  const { ranges, diagnostics } = parseCompressArgs(input, { callId });
50007
50102
  if (!diagnostics.ok && diagnostics.kind !== "ok") {
50008
- log("warn", `[acp-compress-input] rejected: kind=${diagnostics.kind} invalidItems=${diagnostics.invalidItems}${diagnostics.keys ? ` keys=[${diagnostics.keys.join(",")}]` : ""}${diagnostics.length !== void 0 ? ` len=${diagnostics.length}` : ""}`);
50103
+ log("warn", `[acp-compress-input] rejected: kind=${diagnostics.kind} invalidItems=${diagnostics.invalidItems}${diagnostics.keys ? ` keys=[${diagnostics.keys.join(",")}]` : ""}${diagnostics.length !== void 0 ? ` len=${diagnostics.length}` : ""}${diagnostics.invalidReasons && diagnostics.invalidReasons.length > 0 ? ` reasons=[${diagnostics.invalidReasons.join(" | ")}]` : ""}`);
50009
50104
  }
50010
- return ranges;
50105
+ return { ranges, diagnostics };
50011
50106
  }
50012
50107
  var STAGED_COMPRESS_GUIDANCE = "\n\n[Smooth-transition guidance: when you compress, prefer a SMALLER, TAIL-biased range \u2014 compress the most recent large content and keep the stable prefix (the earliest messages) intact. A large single rewrite changes the request shape sharply and can trip provider risk-control; smaller tail-biased folds keep the prefix cache alive and the transition gentle.]";
50013
50108
  function withStagedCompressGuidance(text) {
@@ -50103,17 +50198,23 @@ function normalizeSseLineEndings(buf) {
50103
50198
  var PAIRED = /\x3cacp\s[^<>]{0,256}>([^<>]{0,64})<\/acp>/;
50104
50199
  var LONE_OPEN = /\x3cacp(?:\s[^<>]{0,256})?>/;
50105
50200
  var LONE_CLOSE = /<\/acp(?=[\s>])[^<>]{0,32}>/;
50106
- var PARTIAL_TAIL = /(\x3cacp\s[^<>]*|<\/?a?c?p?)$/;
50201
+ var PARTIAL_TAIL = /(\x3cacp\s[^<>]*|\x3c\/acp(?:\s[^<>]*)?|<\/?a?c?p?)$/;
50107
50202
  var TRUNC_OPEN = /\x3cacp\s[^<>]*$/;
50203
+ var TRUNC_CLOSE = /\x3c\/acp(?:\s[^<>]*)?$/;
50108
50204
  var CLOSE_TAG = "</acp";
50109
50205
  var HOLD_LIMIT = 128;
50110
50206
  var TAG_OPEN_CAP = 4096;
50111
50207
  var SWALLOW_CAP = 80;
50112
50208
  function stripAcpTags(text) {
50113
- return text.replace(new RegExp(PAIRED.source, "g"), "").replace(new RegExp(LONE_OPEN.source, "g"), "").replace(new RegExp(LONE_CLOSE.source, "g"), "").replace(new RegExp(TRUNC_OPEN.source), "");
50209
+ return text.replace(new RegExp(PAIRED.source, "g"), "").replace(new RegExp(LONE_OPEN.source, "g"), "").replace(new RegExp(LONE_CLOSE.source, "g"), "").replace(new RegExp(TRUNC_OPEN.source), "").replace(new RegExp(TRUNC_CLOSE.source), "");
50114
50210
  }
50211
+ var RENDER_TAG_DETECT = /\x3c\/?acp(?=[\s>])|\\u003c\/?acp(?=[\s>\\])/;
50115
50212
  function containsRenderTagText(s3) {
50116
- return /\x3c\/?acp(?=[\s>])/.test(s3) || /\\u003c\/?acp(?=[\s>\\])/.test(s3);
50213
+ return RENDER_TAG_DETECT.test(s3);
50214
+ }
50215
+ var TOOL_CALL_XML = /\x3c\/?(?:antml:)?(invoke|tool_calls|tool_call|parameter|parameters)\b[^<>]*\x3e|\\u003c\/?(?:antml:)?(invoke|tool_calls|tool_call|parameter|parameters)\b|\x3c\/?antml:[a-z_]+/i;
50216
+ function containsToolCallXmlFragment(s3) {
50217
+ return TOOL_CALL_XML.test(s3);
50117
50218
  }
50118
50219
  function createTagEchoFilter(onDrop) {
50119
50220
  let held = "";
@@ -50162,7 +50263,7 @@ function createTagEchoFilter(onDrop) {
50162
50263
  if (!m2) {
50163
50264
  const t = PARTIAL_TAIL.exec(buf);
50164
50265
  if (t) {
50165
- const definite = /^\x3cacp\s/.test(t[0]);
50266
+ const definite = /^\x3cacp\s/.test(t[0]) || /^\x3c\/acp/.test(t[0]);
50166
50267
  const cap = definite ? TAG_OPEN_CAP : HOLD_LIMIT;
50167
50268
  if (t[0].length <= cap) {
50168
50269
  held = t[0];
@@ -50209,6 +50310,11 @@ function createTagEchoFilter(onDrop) {
50209
50310
  drop(t[0]);
50210
50311
  return rest.slice(0, t.index);
50211
50312
  }
50313
+ const tc = new RegExp(TRUNC_CLOSE.source).exec(rest);
50314
+ if (tc) {
50315
+ drop(tc[0]);
50316
+ return rest.slice(0, tc.index);
50317
+ }
50212
50318
  return rest;
50213
50319
  },
50214
50320
  dropped() {
@@ -50291,10 +50397,15 @@ ${lines.join("\n\n")}`;
50291
50397
  function refNum2(ref) {
50292
50398
  return Number(ref.replace(/\D/g, "")) || 0;
50293
50399
  }
50294
- function applyRanges(ranges, ctx) {
50400
+ function applyRanges(parsed, ctx) {
50401
+ const { ranges, diagnostics } = parsed;
50295
50402
  if (ranges.length === 0) {
50296
50403
  ctx.log("[acp-proxy: compress call had no valid ranges; nothing compressed.]");
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.]";
50404
+ const reasons = diagnostics.invalidReasons?.slice(0, 8).map((r) => r.length > 200 ? r.slice(0, 200) + "..." : r) ?? [];
50405
+ const why = reasons.length > 0 ? ` Rejected entries:
50406
+ ${reasons.map((r) => `- ${r}`).join("\n")}` : "";
50407
+ return `[Compression FAILED: no valid ranges parsed (kind=${diagnostics.kind}, dropped=${diagnostics.invalidItems}).${why}
50408
+ 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
50409
  }
50299
50410
  ctx.log(`[acp-proxy: compress requested ${ranges.length} range(s): ${ranges.map((r) => `${r.startRef}\u2013${r.endRef}`).join(", ")}]`);
50300
50411
  ctx.log(`[acp-proxy: ctx has ${ctx.messages.length} message(s), state has ${ctx.session.state.messageRefs?.byRef?.size ?? "?"} ref(s) mapped]`);
@@ -55420,6 +55531,28 @@ async function decodeRequestBody(contentEncoding, input, maxOutputBytes) {
55420
55531
  function bodyDumpEnabled() {
55421
55532
  return process.env.ACP_DUMP_BODY === "1";
55422
55533
  }
55534
+ var dumpFailCount = 0;
55535
+ var lastDumpFailLog = 0;
55536
+ function logDumpFailure(where, err2) {
55537
+ dumpFailCount++;
55538
+ const now = Date.now();
55539
+ if (dumpFailCount === 1 || now - lastDumpFailLog >= 6e4) {
55540
+ lastDumpFailLog = now;
55541
+ const msg = err2 instanceof Error ? err2.message : String(err2);
55542
+ log("warn", `[dump] ${where} failed (total ${dumpFailCount}x): ${msg}`);
55543
+ }
55544
+ }
55545
+ var unrecognizedPathCounts = /* @__PURE__ */ new Map();
55546
+ function logUnrecognizedPath(log2, url) {
55547
+ const key = maskUrlsInText(url.split("?")[0]);
55548
+ const n = (unrecognizedPathCounts.get(key) ?? 0) + 1;
55549
+ unrecognizedPathCounts.set(key, n);
55550
+ if (n <= 3) {
55551
+ log2("warn", `unrecognized path ${key} \u2014 not a known protocol (/chat/completions, /v1/messages, /responses, /responses/compact); forwarding unchanged`);
55552
+ } else if (n === 4) {
55553
+ log2("info", `unrecognized path ${key}: forwarding unchanged; further occurrences suppressed`);
55554
+ }
55555
+ }
55423
55556
  var BILI_HOP_HEADER = "x-bili-hop";
55424
55557
  function parseLauncherModelWindows(raw) {
55425
55558
  if (!raw) return {};
@@ -55867,7 +56000,8 @@ async function handle(req, res, opts, core, config, log2, instanceId) {
55867
56000
  ${hdrText}
55868
56001
 
55869
56002
  ${bodyBuffer.toString("utf8")}`);
55870
- } catch {
56003
+ } catch (err2) {
56004
+ logDumpFailure("INCOMING dump", err2);
55871
56005
  }
55872
56006
  }
55873
56007
  let reqConfig = config;
@@ -56072,7 +56206,7 @@ ${bodyBuffer.toString("utf8")}`);
56072
56206
  }
56073
56207
  if (!prepared) {
56074
56208
  if (protocol === null && !opts.passthrough) {
56075
- log2("warn", `unrecognized path ${maskUrlsInText(req.url ?? "")} \u2014 not a known protocol (/chat/completions, /v1/messages, /responses, /responses/compact); forwarding unchanged`);
56209
+ logUnrecognizedPath(log2, req.url ?? "");
56076
56210
  }
56077
56211
  await forward(req, res, opts, bodyBuffer, null, core, reqConfig, log2, route, instanceId, void 0);
56078
56212
  }
@@ -56697,7 +56831,8 @@ ${hdrText}
56697
56831
 
56698
56832
  ${bodyText}`);
56699
56833
  log2("info", `[debug] RAW request dump: ${reqPath}`);
56700
- } catch {
56834
+ } catch (err2) {
56835
+ logDumpFailure("REQ dump", err2);
56701
56836
  }
56702
56837
  }
56703
56838
  const dispatcher = proxyDispatcher(proxyUrl);
@@ -56745,7 +56880,8 @@ ${bodyText}`);
56745
56880
  ${hdrText}
56746
56881
  `);
56747
56882
  log2("info", `[debug] RAW response dump: ${resPath}`);
56748
- } catch {
56883
+ } catch (err2) {
56884
+ logDumpFailure("RES dump", err2);
56749
56885
  }
56750
56886
  }
56751
56887
  if (!upstream.ok) {
@@ -56881,12 +57017,16 @@ ${hdrText}
56881
57017
  systemPrompt,
56882
57018
  clientAbort.signal
56883
57019
  );
57020
+ let protocolFragmentWarned = false;
56884
57021
  for await (const chunk of loop) {
56885
57022
  if (res.destroyed || res.writableEnded) break;
56886
57023
  {
56887
57024
  const s3 = chunk.toString("utf8");
56888
57025
  if (s3.includes("<acp ") || s3.includes("</acp")) {
56889
57026
  log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains <acp tag`);
57027
+ } else if (!protocolFragmentWarned && containsToolCallXmlFragment(s3)) {
57028
+ protocolFragmentWarned = true;
57029
+ log2("warn", `[${prepared.session.id}] tag echo: ${prepared.protocol} response stream contains tool-call XML fragment (possible tag echo; not stripped)`);
56890
57030
  }
56891
57031
  }
56892
57032
  res.write(chunk);
@@ -56994,7 +57134,7 @@ async function dumpStreamToFile(stream2, dir, name) {
56994
57134
  mkdirSync6(dir, { recursive: true });
56995
57135
  const ws2 = createWriteStream2(join4(dir, name));
56996
57136
  ws2.on("error", (e) => {
56997
- log("debug", `[dump] write stream error: ${e.message ?? e}`);
57137
+ logDumpFailure("SSE stream dump", e);
56998
57138
  });
56999
57139
  const reader = stream2.getReader();
57000
57140
  try {
@@ -57007,7 +57147,8 @@ async function dumpStreamToFile(stream2, dir, name) {
57007
57147
  reader.releaseLock();
57008
57148
  ws2.end();
57009
57149
  }
57010
- } catch {
57150
+ } catch (err2) {
57151
+ logDumpFailure("SSE stream dump", err2);
57011
57152
  }
57012
57153
  }
57013
57154
  function deriveTitle(messages) {