billion-context 0.1.92 → 0.1.93-pr.631.452

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
@@ -5632,7 +5632,7 @@ var require_util2 = __commonJS({
5632
5632
  callback();
5633
5633
  }
5634
5634
  };
5635
- function createInflate(zlibOptions) {
5635
+ function createInflate2(zlibOptions) {
5636
5636
  return new InflateStream(zlibOptions);
5637
5637
  }
5638
5638
  function extractMimeType(headers) {
@@ -5764,7 +5764,7 @@ var require_util2 = __commonJS({
5764
5764
  readAllBytes,
5765
5765
  simpleRangeHeaderValue,
5766
5766
  buildContentRange,
5767
- createInflate,
5767
+ createInflate: createInflate2,
5768
5768
  extractMimeType,
5769
5769
  getDecodeSplit,
5770
5770
  environmentSettingsObject,
@@ -13122,7 +13122,7 @@ var require_mock_interceptor = __commonJS({
13122
13122
  var require_mock_client = __commonJS({
13123
13123
  "node_modules/undici/lib/mock/mock-client.js"(exports, module) {
13124
13124
  "use strict";
13125
- var { promisify: promisify2 } = __require("util");
13125
+ var { promisify } = __require("util");
13126
13126
  var Client = require_client();
13127
13127
  var { buildMockDispatch } = require_mock_utils();
13128
13128
  var {
@@ -13170,7 +13170,7 @@ var require_mock_client = __commonJS({
13170
13170
  this[kDispatches] = [];
13171
13171
  }
13172
13172
  async [kClose]() {
13173
- await promisify2(this[kOriginalClose])();
13173
+ await promisify(this[kOriginalClose])();
13174
13174
  this[kConnected] = 0;
13175
13175
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
13176
13176
  }
@@ -13383,7 +13383,7 @@ var require_mock_call_history = __commonJS({
13383
13383
  var require_mock_pool = __commonJS({
13384
13384
  "node_modules/undici/lib/mock/mock-pool.js"(exports, module) {
13385
13385
  "use strict";
13386
- var { promisify: promisify2 } = __require("util");
13386
+ var { promisify } = __require("util");
13387
13387
  var Pool2 = require_pool();
13388
13388
  var { buildMockDispatch } = require_mock_utils();
13389
13389
  var {
@@ -13431,7 +13431,7 @@ var require_mock_pool = __commonJS({
13431
13431
  this[kDispatches] = [];
13432
13432
  }
13433
13433
  async [kClose]() {
13434
- await promisify2(this[kOriginalClose])();
13434
+ await promisify(this[kOriginalClose])();
13435
13435
  this[kConnected] = 0;
13436
13436
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
13437
13437
  }
@@ -17545,17 +17545,17 @@ var require_cache2 = __commonJS({
17545
17545
  var require_decompress = __commonJS({
17546
17546
  "node_modules/undici/lib/interceptor/decompress.js"(exports, module) {
17547
17547
  "use strict";
17548
- var { createInflate, createGunzip, createBrotliDecompress, createZstdDecompress } = __require("zlib");
17548
+ var { createInflate: createInflate2, createGunzip: createGunzip2, createBrotliDecompress: createBrotliDecompress2, createZstdDecompress } = __require("zlib");
17549
17549
  var { pipeline } = __require("stream");
17550
17550
  var DecoratorHandler = require_decorator_handler();
17551
17551
  var { runtimeFeatures } = require_runtime_features();
17552
17552
  var supportedEncodings = {
17553
- gzip: createGunzip,
17554
- "x-gzip": createGunzip,
17555
- br: createBrotliDecompress,
17556
- deflate: createInflate,
17557
- compress: createInflate,
17558
- "x-compress": createInflate,
17553
+ gzip: createGunzip2,
17554
+ "x-gzip": createGunzip2,
17555
+ br: createBrotliDecompress2,
17556
+ deflate: createInflate2,
17557
+ compress: createInflate2,
17558
+ "x-compress": createInflate2,
17559
17559
  ...runtimeFeatures.has("zstd") ? { zstd: createZstdDecompress } : {}
17560
17560
  };
17561
17561
  var defaultSkipStatusCodes = (
@@ -20384,7 +20384,7 @@ var require_fetch = __commonJS({
20384
20384
  clampAndCoarsenConnectionTimingInfo,
20385
20385
  simpleRangeHeaderValue,
20386
20386
  buildContentRange,
20387
- createInflate,
20387
+ createInflate: createInflate2,
20388
20388
  extractMimeType,
20389
20389
  hasAuthenticationEntry,
20390
20390
  includesCredentials,
@@ -21366,7 +21366,7 @@ var require_fetch = __commonJS({
21366
21366
  finishFlush: zlib.constants.Z_SYNC_FLUSH
21367
21367
  }));
21368
21368
  } else if (coding === "deflate") {
21369
- decoders.push(createInflate({
21369
+ decoders.push(createInflate2({
21370
21370
  flush: zlib.constants.Z_SYNC_FLUSH,
21371
21371
  finishFlush: zlib.constants.Z_SYNC_FLUSH
21372
21372
  }));
@@ -23432,7 +23432,7 @@ var require_connection = __commonJS({
23432
23432
  var require_permessage_deflate = __commonJS({
23433
23433
  "node_modules/undici/lib/web/websocket/permessage-deflate.js"(exports, module) {
23434
23434
  "use strict";
23435
- var { createInflateRaw, Z_DEFAULT_WINDOWBITS } = __require("zlib");
23435
+ var { createInflateRaw: createInflateRaw2, Z_DEFAULT_WINDOWBITS } = __require("zlib");
23436
23436
  var { isValidClientWindowBits } = require_util5();
23437
23437
  var { MessageSizeExceededError } = require_errors();
23438
23438
  var tail = Buffer.from([0, 0, 255, 255]);
@@ -23468,7 +23468,7 @@ var require_permessage_deflate = __commonJS({
23468
23468
  windowBits = Number.parseInt(this.#options.serverMaxWindowBits);
23469
23469
  }
23470
23470
  try {
23471
- this.#inflate = createInflateRaw({ windowBits });
23471
+ this.#inflate = createInflateRaw2({ windowBits });
23472
23472
  } catch (err2) {
23473
23473
  callback(err2);
23474
23474
  return;
@@ -38339,7 +38339,7 @@ var require_tls = __commonJS({
38339
38339
  }
38340
38340
  return rval;
38341
38341
  };
38342
- var inflate2 = function(c, record, s3) {
38342
+ var inflate = function(c, record, s3) {
38343
38343
  var rval = false;
38344
38344
  try {
38345
38345
  var bytes = c.inflate(record.fragment.getBytes());
@@ -39484,7 +39484,7 @@ var require_tls = __commonJS({
39484
39484
  case tls4.CompressionMethod.none:
39485
39485
  break;
39486
39486
  case tls4.CompressionMethod.deflate:
39487
- state.read.compressFunction = inflate2;
39487
+ state.read.compressFunction = inflate;
39488
39488
  state.write.compressFunction = deflate;
39489
39489
  break;
39490
39490
  default:
@@ -46740,6 +46740,74 @@ function renderCompressedDrilldown(blocks, state, sort, limit, countTokens) {
46740
46740
  }
46741
46741
  return lines.join("\n");
46742
46742
  }
46743
+ function renderMessage2(m2) {
46744
+ const parts = [];
46745
+ switch (m2.contentType) {
46746
+ case "text":
46747
+ parts.push(m2.text ?? "");
46748
+ break;
46749
+ case "tool-call":
46750
+ parts.push(`\`${m2.toolName ?? "?"}(${m2.toolCallId ?? ""})\` args: ${m2.text ?? ""}`);
46751
+ break;
46752
+ case "tool-result":
46753
+ parts.push(`\`${m2.toolName ?? "?"}(${m2.toolCallId ?? ""})\` \u2192 ${m2.text ?? ""}`);
46754
+ break;
46755
+ case "reasoning":
46756
+ parts.push(`_reasoning_: ${m2.text ?? ""}`);
46757
+ break;
46758
+ }
46759
+ const body = parts.join("\n").trim();
46760
+ return body === "" ? "_(empty)_" : body + "\n";
46761
+ }
46762
+ function renderHandoff(input) {
46763
+ const { coreMessages, state, full, meta } = input;
46764
+ const lines = [];
46765
+ lines.push("# billion-context session handoff");
46766
+ lines.push("");
46767
+ lines.push(`- title: ${meta.title ?? "(untitled)"}`);
46768
+ if (meta.label) lines.push(`- label: ${meta.label}`);
46769
+ lines.push(`- session id: ${meta.sessionId}`);
46770
+ for (const bullet of meta.extraBullets ?? []) lines.push(bullet);
46771
+ if (meta.contextTokens) lines.push(`- last context tokens: ~${meta.contextTokens}`);
46772
+ lines.push(`- compression blocks: ${state.blocks.length} (active ${state.blocks.filter((b2) => b2.active).length})`);
46773
+ lines.push("");
46774
+ const folded = input.folded === true;
46775
+ const view = full || folded ? coreMessages : prune(coreMessages, state);
46776
+ lines.push(full && !folded ? `## Full conversation (${coreMessages.length} messages)` : folded ? `## Conversation (persisted folded snapshot, ${coreMessages.length} messages)` : `## Conversation (folded view as the model saw it, ${coreMessages.length} client messages)`);
46777
+ lines.push("");
46778
+ if (view.length === 0) {
46779
+ lines.push("No conversation messages to export.");
46780
+ lines.push("");
46781
+ }
46782
+ let lastRole = "";
46783
+ for (const m2 of view) {
46784
+ if (m2.role !== lastRole) {
46785
+ lines.push(`### ${m2.role}`);
46786
+ lines.push("");
46787
+ lastRole = m2.role;
46788
+ }
46789
+ lines.push(renderMessage2(m2));
46790
+ }
46791
+ lines.push("");
46792
+ if (full && folded) {
46793
+ for (const b2 of input.blocksFull ?? []) {
46794
+ lines.push(`## Block ${b2.blockId}${b2.topic ? ` \u2014 ${b2.topic}` : ""}`);
46795
+ lines.push("");
46796
+ lines.push(`### Original messages (${b2.count})`);
46797
+ lines.push("");
46798
+ lines.push(b2.fullText.trim());
46799
+ lines.push("");
46800
+ }
46801
+ }
46802
+ return lines.join("\n");
46803
+ }
46804
+ function matchSession(sessions2, selector, labelOf) {
46805
+ const exact = sessions2.filter((s3) => s3.id === selector);
46806
+ if (exact.length > 0) return exact;
46807
+ const byLabel = sessions2.filter((s3) => labelOf(s3) === selector);
46808
+ if (byLabel.length > 0) return byLabel;
46809
+ return sessions2.filter((s3) => s3.id.startsWith(selector) || (labelOf(s3) ?? "").startsWith(selector));
46810
+ }
46743
46811
  function parseCompressArgs(input, opts) {
46744
46812
  const callId = opts?.callId;
46745
46813
  const diag = { ok: false, kind: "ok", invalidItems: 0 };
@@ -48293,6 +48361,7 @@ function loadOptions(env = process.env) {
48293
48361
  passthrough: passthrough.enabled,
48294
48362
  passthroughSource: passthrough.source,
48295
48363
  autoUpdate: (env.ACP_AUTO_UPDATE ?? (fileConfig.autoUpdate === false ? "0" : "1")) !== "0",
48364
+ updateTag: (env.ACP_UPDATE_TAG ?? fileConfig.updateTag ?? "latest").trim() || "latest",
48296
48365
  logFile: env.ACP_LOG_FILE !== void 0 ? env.ACP_LOG_FILE || void 0 : fileConfig.logFile,
48297
48366
  mitm: {
48298
48367
  enabled: (env.BILI_MITM ?? (fileConfig.mitm?.enabled === false ? "0" : "1")) !== "0",
@@ -50329,6 +50398,7 @@ function buildAlertMessage(dir, count) {
50329
50398
  // src/persist.ts
50330
50399
  var PERSIST_VERSION = 3;
50331
50400
  var MIGRATION_MARKER = ".bili-migration-286.done";
50401
+ var STALE_WARN_THROTTLE_MS = 6e4;
50332
50402
  function mergeState(parsed) {
50333
50403
  const fresh = createInitialState();
50334
50404
  return {
@@ -50362,11 +50432,14 @@ var SessionStore = class {
50362
50432
  enabled;
50363
50433
  dir;
50364
50434
  store;
50435
+ log;
50436
+ staleWarnAt = /* @__PURE__ */ new Map();
50365
50437
  constructor(opts) {
50366
50438
  const debounceMs = opts?.debounceMs ?? defaultDebounce();
50367
50439
  this.enabled = (opts?.enabled ?? true) && debounceMs >= 0;
50368
50440
  this.dir = opts?.dir ?? defaultDir();
50369
50441
  const baseLog = opts?.log ?? defaultLogger;
50442
+ this.log = baseLog;
50370
50443
  const epermAlert = new PersistEpermAlert({
50371
50444
  dir: this.dir,
50372
50445
  threshold: epermAlertThreshold(),
@@ -50516,40 +50589,83 @@ var SessionStore = class {
50516
50589
  });
50517
50590
  }
50518
50591
  }
50592
+ /** Shared envelope probe for the sync read paths: namespaced path
50593
+ * first, then the _unknown/ location, letting the kernel store layer
50594
+ * also check its discovered map and the flat legacy name. */
50595
+ loadEnvelope(id, meta) {
50596
+ const envelopes = [
50597
+ this.store.loadSync(id, relPathFor(id, meta?.protocol, meta?.upstreamOrigin)),
50598
+ meta?.protocol ? this.store.loadSync(id, relPathFor(id)) : null
50599
+ ];
50600
+ for (const envelope of envelopes) {
50601
+ if (envelope) return envelope;
50602
+ }
50603
+ return null;
50604
+ }
50519
50605
  /** Synchronous reload of a single session. Used on a memory miss (after
50520
50606
  * LRU eviction). Sync fs is acceptable here because a miss is rare and
50521
50607
  * reads a single small file (~1ms). Returns null if missing/corrupt or the
50522
50608
  * body id does not match what we asked for. */
50523
50609
  loadSync(id, meta) {
50524
50610
  if (!this.enabled) return null;
50525
- const envelopes = [
50526
- this.store.loadSync(id, relPathFor(id, meta?.protocol, meta?.upstreamOrigin)),
50527
- meta?.protocol ? this.store.loadSync(id, relPathFor(id)) : null
50528
- ];
50529
- for (const envelope of envelopes) {
50530
- if (envelope) {
50531
- const session = buildSession(envelope.payload);
50532
- if (hasNegativePersistedTokens(envelope.payload)) {
50533
- this.scheduleSave(session);
50534
- log("info", `[persist] clamped negative token stats on reload for ${id} (#408)`);
50535
- }
50536
- return session;
50537
- }
50611
+ const envelope = this.loadEnvelope(id, meta);
50612
+ if (!envelope) return null;
50613
+ const session = buildSession(envelope.payload);
50614
+ if (hasNegativePersistedTokens(envelope.payload)) {
50615
+ this.scheduleSave(session);
50616
+ log("info", `[persist] clamped negative token stats on reload for ${id} (#408)`);
50617
+ }
50618
+ return session;
50619
+ }
50620
+ /** #405 fix #4: dual-instance rollback guard. When two proxy processes
50621
+ * share BILI_SESSIONS_DIR, whoever saves last used to win — an instance
50622
+ * holding a STALE in-memory copy would roll counters back (requests:3 →
50623
+ * 2). Both signals below are monotonic per session id, so either being
50624
+ * strictly smaller proves staleness. Returns the fresher-on-disk payload
50625
+ * when the incoming record is stale, else null. */
50626
+ staleDiskPayload(incoming) {
50627
+ const envelope = this.loadEnvelope(incoming.id, incoming.meta);
50628
+ if (!envelope) return null;
50629
+ const disk = envelope.payload;
50630
+ const diskRequests = disk.stats?.requests ?? disk.requests ?? 0;
50631
+ const incRequests = incoming.stats?.requests ?? incoming.requests ?? 0;
50632
+ if (incRequests < diskRequests) return disk;
50633
+ if (incRequests === diskRequests) {
50634
+ const diskBlocks = disk.state?.nextBlockId ?? 0;
50635
+ const incBlocks = incoming.state?.nextBlockId ?? 0;
50636
+ if (incBlocks < diskBlocks) return disk;
50538
50637
  }
50539
50638
  return null;
50540
50639
  }
50640
+ guardedBuild(session) {
50641
+ return () => {
50642
+ const record = buildRecord(session);
50643
+ const disk = this.staleDiskPayload(record);
50644
+ if (disk === null) return record;
50645
+ const now = Date.now();
50646
+ const last = this.staleWarnAt.get(record.id) ?? 0;
50647
+ if (now - last >= STALE_WARN_THROTTLE_MS) {
50648
+ this.staleWarnAt.set(record.id, now);
50649
+ this.log(
50650
+ "warn",
50651
+ `[persist] rejected stale snapshot for session ${record.id}: in-memory copy is older than the on-disk one (another bili instance holds newer state) \u2014 keeping disk state, no rollback (#405)`
50652
+ );
50653
+ }
50654
+ return disk;
50655
+ };
50656
+ }
50541
50657
  /** Schedule a debounced write for a session. Multiple calls within the
50542
50658
  * window coalesce; the record is built at WRITE time, so the freshest
50543
50659
  * session state is persisted. Safe to call on the hot path. No-op if
50544
50660
  * disabled. */
50545
50661
  scheduleSave(session) {
50546
- this.store.scheduleSave(session.id, () => buildRecord(session));
50662
+ this.store.scheduleSave(session.id, this.guardedBuild(session));
50547
50663
  }
50548
50664
  /** Asynchronously persist a session right now (skips the debounce). Throws
50549
50665
  * on write failure so callers can react (e.g. avoid evicting). Serialized
50550
50666
  * per-session by the kernel store's write chains. */
50551
50667
  async writeNow(session) {
50552
- await this.store.writeNow(session.id, () => buildRecord(session));
50668
+ await this.store.writeNow(session.id, this.guardedBuild(session));
50553
50669
  }
50554
50670
  /** Synchronous flush for a single session. Used on memory eviction so a
50555
50671
  * dirty evicted session is not lost. Sync because eviction runs in the
@@ -50557,7 +50673,7 @@ var SessionStore = class {
50557
50673
  * Returns true on success, false on failure (caller must NOT evict on
50558
50674
  * failure for a never-persisted session or it is lost permanently). */
50559
50675
  flushSync(session) {
50560
- return this.store.flushSync(session.id, () => buildRecord(session));
50676
+ return this.store.flushSync(session.id, this.guardedBuild(session));
50561
50677
  }
50562
50678
  /** Flush all dirty sessions with a pending debounce timer. Called on
50563
50679
  * SIGTERM/SIGINT for graceful shutdown. The kernel store flushes its own
@@ -57416,8 +57532,7 @@ function tunnelAllowlistFromEnv(env = process.env) {
57416
57532
  }
57417
57533
 
57418
57534
  // src/content-encoding.ts
57419
- import { promisify } from "util";
57420
- import { brotliDecompress, gunzip, inflate, inflateRaw } from "zlib";
57535
+ import { createBrotliDecompress, createGunzip, createInflate, createInflateRaw } from "zlib";
57421
57536
 
57422
57537
  // node_modules/fzstd/esm/index.mjs
57423
57538
  var ab = ArrayBuffer;
@@ -58090,31 +58205,54 @@ var Decompress = /* @__PURE__ */ (function() {
58090
58205
  })();
58091
58206
 
58092
58207
  // src/content-encoding.ts
58093
- var gunzipAsync = promisify(gunzip);
58094
- var inflateAsync = promisify(inflate);
58095
- var inflateRawAsync = promisify(inflateRaw);
58096
- var brotliAsync = promisify(brotliDecompress);
58097
- async function decodeZstd(input, maxOutputBytes) {
58208
+ var DecompressedTooLargeError = class extends Error {
58209
+ constructor(limit) {
58210
+ super(`decompressed request exceeds ${limit} bytes`);
58211
+ this.limit = limit;
58212
+ this.name = "DecompressedTooLargeError";
58213
+ }
58214
+ limit;
58215
+ };
58216
+ function streamDecode(factory, input, max) {
58217
+ return new Promise((resolve, reject) => {
58218
+ const chunks = [];
58219
+ let size = 0;
58220
+ const decoder = factory();
58221
+ decoder.on("data", (chunk) => {
58222
+ size += chunk.length;
58223
+ if (size > max) {
58224
+ decoder.destroy();
58225
+ reject(new DecompressedTooLargeError(max));
58226
+ return;
58227
+ }
58228
+ chunks.push(chunk);
58229
+ });
58230
+ decoder.on("end", () => resolve(Buffer.concat(chunks, size)));
58231
+ decoder.on("error", (err2) => reject(err2));
58232
+ decoder.end(input);
58233
+ });
58234
+ }
58235
+ async function decodeZstd(input, max) {
58098
58236
  const chunks = [];
58099
58237
  let size = 0;
58100
58238
  const decoder = new Decompress((chunk) => {
58101
58239
  size += chunk.byteLength;
58102
- if (size > maxOutputBytes) throw new Error(`decompressed request exceeds ${maxOutputBytes} bytes`);
58240
+ if (size > max) throw new DecompressedTooLargeError(max);
58103
58241
  chunks.push(Buffer.from(chunk));
58104
58242
  });
58105
58243
  decoder.push(input, true);
58106
58244
  return Buffer.concat(chunks, size);
58107
58245
  }
58108
- async function decodeOne(coding, input, maxOutputBytes) {
58109
- const options = { maxOutputLength: maxOutputBytes };
58110
- if (coding === "gzip" || coding === "x-gzip") return gunzipAsync(input, options);
58111
- if (coding === "br") return brotliAsync(input, options);
58112
- if (coding === "zstd") return decodeZstd(input, maxOutputBytes);
58246
+ async function decodeOne(coding, input, max) {
58247
+ if (coding === "gzip" || coding === "x-gzip") return streamDecode(createGunzip, input, max);
58248
+ if (coding === "br") return streamDecode(createBrotliDecompress, input, max);
58249
+ if (coding === "zstd") return decodeZstd(input, max);
58113
58250
  if (coding === "deflate") {
58114
58251
  try {
58115
- return await inflateAsync(input, options);
58116
- } catch {
58117
- return inflateRawAsync(input, options);
58252
+ return await streamDecode(createInflate, input, max);
58253
+ } catch (err2) {
58254
+ if (err2 instanceof DecompressedTooLargeError) throw err2;
58255
+ return streamDecode(createInflateRaw, input, max);
58118
58256
  }
58119
58257
  }
58120
58258
  throw new Error(`unsupported request content-encoding: ${coding}`);
@@ -58125,9 +58263,7 @@ async function decodeRequestBody(contentEncoding, input, maxOutputBytes) {
58125
58263
  let body = input;
58126
58264
  for (const coding of codings.reverse()) {
58127
58265
  body = await decodeOne(coding, body, maxOutputBytes);
58128
- if (body.byteLength > maxOutputBytes) {
58129
- throw new Error(`decompressed request exceeds ${maxOutputBytes} bytes`);
58130
- }
58266
+ if (body.byteLength > maxOutputBytes) throw new DecompressedTooLargeError(maxOutputBytes);
58131
58267
  }
58132
58268
  return { body, decoded: true };
58133
58269
  }
@@ -58707,18 +58843,24 @@ async function handle(req, res, opts, core, config, log2, instanceId, instanceSt
58707
58843
  upstreamOrigin = route ? route.upstream : /^https?:\/\//i.test(url) ? new URL(url).origin : opts.upstream;
58708
58844
  protocol = route?.explicitProtocol ?? (req.method === "POST" && bodyBuffer.length > 0 ? urlPath.endsWith("/chat/completions") ? "openai" : urlPath.endsWith("/v1/messages") || urlPath.endsWith("/messages") ? "anthropic" : urlPath.endsWith("/responses") || responsesCompact ? "responses" : null : null);
58709
58845
  if (protocol !== null && bodyBuffer.length > 0) {
58710
- const decoded = await decodeRequestBody(headerValue2(req, "content-encoding"), bodyBuffer, MAX_REQUEST_BYTES);
58711
- bodyBuffer = decoded.body;
58712
- if (decoded.decoded) delete req.headers["content-encoding"];
58846
+ try {
58847
+ const decoded = await decodeRequestBody(headerValue2(req, "content-encoding"), bodyBuffer, MAX_REQUEST_BYTES);
58848
+ bodyBuffer = decoded.body;
58849
+ if (decoded.decoded) delete req.headers["content-encoding"];
58850
+ } catch (decErr) {
58851
+ if (decErr instanceof DecompressedTooLargeError) throw decErr;
58852
+ protocol = null;
58853
+ log2("warn", `decode body failed (${String(decErr)}) - forwarding raw body verbatim to ${upstreamOrigin}`);
58854
+ }
58713
58855
  }
58714
58856
  } catch (err2) {
58715
- if (err2 instanceof BodyTooLargeError) {
58857
+ if (err2 instanceof BodyTooLargeError || err2 instanceof DecompressedTooLargeError) {
58716
58858
  log2("warn", `413: request body exceeds ${err2.limit} bytes`);
58717
58859
  res.writeHead(413, { "content-type": "application/json" });
58718
58860
  res.end(JSON.stringify({ error: { type: "request_too_large", message: err2.message } }));
58719
58861
  return;
58720
58862
  }
58721
- log2("warn", `read/decode body failed: ${String(err2)}`);
58863
+ log2("warn", `failed to prepare inbound request (${String(err2)}) - 400`);
58722
58864
  res.writeHead(400, { "content-type": "application/json" });
58723
58865
  res.end(JSON.stringify({ error: { type: "invalid_request", message: String(err2) } }));
58724
58866
  return;
@@ -63583,6 +63725,12 @@ var To = (s3) => {
63583
63725
  import path12 from "path";
63584
63726
  import { fileURLToPath as fileURLToPath3 } from "url";
63585
63727
  var REGISTRY_BASE = "https://registry.npmjs.org";
63728
+ function normalizeUpdateTag(tag) {
63729
+ return (tag ?? "latest").trim() || "latest";
63730
+ }
63731
+ function registryUrlFor(packageName, tag) {
63732
+ return `${REGISTRY_BASE}/${packageName}/${encodeURIComponent(tag)}`;
63733
+ }
63586
63734
  var CHECK_INTERVAL_MS = 3 * 60 * 1e3;
63587
63735
  var THROTTLE_FILE = path12.join(cacheDir(), ".update-check");
63588
63736
  var LOCK_FILE = path12.join(cacheDir(), ".update-lock");
@@ -63593,22 +63741,42 @@ function shouldStealLock(holderAlive, ageMs) {
63593
63741
  var timer2;
63594
63742
  var inFlight = false;
63595
63743
  var firstCheckDone = false;
63596
- function parseVersion(v2) {
63597
- return v2.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
63598
- }
63599
- function isNewer(latest, current) {
63600
- const l = parseVersion(latest);
63601
- const c = parseVersion(current);
63744
+ function isVersionNewer(latest, current) {
63745
+ const next = parseSemVer(latest);
63746
+ const prev = parseSemVer(current);
63747
+ if (!next || !prev) return false;
63602
63748
  for (let i = 0; i < 3; i++) {
63603
- const lv = l[i] ?? 0;
63604
- const cv = c[i] ?? 0;
63605
- if (lv > cv) return true;
63606
- if (lv < cv) return false;
63749
+ const a = next.parts[i] ?? 0;
63750
+ const b2 = prev.parts[i] ?? 0;
63751
+ if (a !== b2) return a > b2;
63752
+ }
63753
+ if (!next.pre.length && prev.pre.length) return true;
63754
+ if (next.pre.length && !prev.pre.length) return false;
63755
+ for (let i = 0; i < Math.max(next.pre.length, prev.pre.length); i++) {
63756
+ const a = next.pre[i];
63757
+ const b2 = prev.pre[i];
63758
+ if (a === void 0) return false;
63759
+ if (b2 === void 0) return true;
63760
+ if (a === b2) continue;
63761
+ const aNumber = /^\d+$/.test(a) ? Number(a) : void 0;
63762
+ const bNumber = /^\d+$/.test(b2) ? Number(b2) : void 0;
63763
+ if (aNumber !== void 0 && bNumber !== void 0) return aNumber > bNumber;
63764
+ if (aNumber !== void 0) return false;
63765
+ if (bNumber !== void 0) return true;
63766
+ return a > b2;
63607
63767
  }
63608
63768
  return false;
63609
63769
  }
63770
+ function parseSemVer(version2) {
63771
+ const match = version2.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+.+)?$/);
63772
+ if (!match) return void 0;
63773
+ return {
63774
+ parts: [Number(match[1]), Number(match[2]), Number(match[3])],
63775
+ pre: match[4]?.split(".") ?? []
63776
+ };
63777
+ }
63610
63778
  function staleInstallStatus(diskVersion, runningVersion) {
63611
- return diskVersion !== void 0 && isNewer(diskVersion, runningVersion) ? "restart" : "current";
63779
+ return diskVersion !== void 0 && isVersionNewer(diskVersion, runningVersion) ? "restart" : "current";
63612
63780
  }
63613
63781
  async function readLastCheck() {
63614
63782
  try {
@@ -63814,7 +63982,8 @@ async function checkForUpdate(opts, force = false) {
63814
63982
  return;
63815
63983
  }
63816
63984
  log("info", `[update] checking npm registry for ${opts.packageName}${sinceLastSec < 0 ? " (startup check)" : sinceLastSec === 0 ? "" : ` (last check ${sinceLastSec}s ago)`}\u2026`);
63817
- const url = `${REGISTRY_BASE}/${opts.packageName}/latest`;
63985
+ const tag = normalizeUpdateTag(opts.updateTag);
63986
+ const url = registryUrlFor(opts.packageName, tag);
63818
63987
  const registryDispatcher = egressDispatcher(opts, url);
63819
63988
  const res = await fetchWithEgress(url, {
63820
63989
  signal: AbortSignal.timeout(5e3),
@@ -63833,11 +64002,11 @@ async function checkForUpdate(opts, force = false) {
63833
64002
  }
63834
64003
  const diskVersion = installDir ? await readDiskVersion(installDir) : void 0;
63835
64004
  const currentVersion = diskVersion ?? opts.currentVersion;
63836
- if (!isNewer(latest, currentVersion)) {
64005
+ if (!isVersionNewer(latest, currentVersion)) {
63837
64006
  if (staleInstallStatus(diskVersion, opts.currentVersion) === "restart") {
63838
64007
  log("warn", `[update] running v${opts.currentVersion} but v${diskVersion} is installed \u2014 restart bili to activate (auto-update replaced the on-disk install; this process is still on the old code)`);
63839
64008
  } else {
63840
- log("info", `[update] current=${currentVersion} latest=${latest} (up to date)`);
64009
+ log("info", `[update] current=${currentVersion} latest=${latest} tag=${tag} (up to date)`);
63841
64010
  }
63842
64011
  return;
63843
64012
  }
@@ -65902,7 +66071,33 @@ function latestBlockTime(s3) {
65902
66071
  for (const b2 of s3.state.blocks) if (b2.createdAt > latest) latest = b2.createdAt;
65903
66072
  return latest;
65904
66073
  }
65905
- function renderHandoff(s3, full) {
66074
+ function renderHandoff2(s3, full) {
66075
+ const messages = s3.lastMessages;
66076
+ if (messages && messages.length > 0) {
66077
+ const folded = s3.lastMessagesFolded === true;
66078
+ const blocksFull = full && folded ? s3.state.blocks.filter((b2) => b2.active).map((b2) => {
66079
+ const content = s3.blockContents.get(b2.blockId);
66080
+ return content ? { blockId: b2.blockId, topic: b2.topic, count: content.full.count, fullText: content.full.text } : void 0;
66081
+ }).filter((x) => x !== void 0) : void 0;
66082
+ return renderHandoff({
66083
+ coreMessages: messages,
66084
+ state: s3.state,
66085
+ full,
66086
+ folded,
66087
+ blocksFull,
66088
+ meta: {
66089
+ title: s3.meta.title,
66090
+ label: s3.meta.label,
66091
+ sessionId: s3.id,
66092
+ contextTokens: s3.stats.contextTokens,
66093
+ extraBullets: [
66094
+ ...s3.meta.protocol ? [`- protocol: ${s3.meta.protocol}`] : [],
66095
+ ...s3.meta.upstreamOrigin ? [`- upstream: ${s3.meta.upstreamOrigin}`] : [],
66096
+ `- requests: ${s3.stats.requests}`
66097
+ ]
66098
+ }
66099
+ });
66100
+ }
65906
66101
  const lines = [];
65907
66102
  lines.push(`# billion-context session handoff`);
65908
66103
  lines.push("");
@@ -65915,38 +66110,6 @@ function renderHandoff(s3, full) {
65915
66110
  if (s3.stats.contextTokens) lines.push(`- last context tokens: ~${s3.stats.contextTokens}`);
65916
66111
  lines.push(`- compression blocks: ${s3.state.blocks.length} (active ${s3.state.blocks.filter((b2) => b2.active).length})`);
65917
66112
  lines.push("");
65918
- const messages = s3.lastMessages;
65919
- if (messages && messages.length > 0) {
65920
- const folded = s3.lastMessagesFolded === true;
65921
- const view = full || folded ? messages : prune(messages, s3.state);
65922
- lines.push(
65923
- full && !folded ? `## Full conversation (${messages.length} messages)` : folded ? `## Conversation (persisted folded snapshot, ${messages.length} messages)` : `## Conversation (folded view as the model saw it, ${messages.length} client messages)`
65924
- );
65925
- lines.push("");
65926
- let lastRole = "";
65927
- for (const m2 of view) {
65928
- if (m2.role !== lastRole) {
65929
- lines.push(`### ${m2.role}`);
65930
- lines.push("");
65931
- lastRole = m2.role;
65932
- }
65933
- lines.push(renderMessage2(m2));
65934
- }
65935
- lines.push("");
65936
- if (full && folded) {
65937
- for (const b2 of s3.state.blocks.filter((x) => x.active)) {
65938
- const content = s3.blockContents.get(b2.blockId);
65939
- if (!content) continue;
65940
- lines.push(`## Block ${b2.blockId}${b2.topic ? ` \u2014 ${b2.topic}` : ""}`);
65941
- lines.push("");
65942
- lines.push(`### Original messages (${content.full.count})`);
65943
- lines.push("");
65944
- lines.push(content.full.text.trim());
65945
- lines.push("");
65946
- }
65947
- }
65948
- return lines.join("\n");
65949
- }
65950
66113
  const active = s3.state.blocks.filter((b2) => b2.active);
65951
66114
  if (active.length === 0) {
65952
66115
  lines.push("No active compression blocks and no persisted conversation snapshot (v2 session file). Original messages are only persisted when they are compressed into a block, so this session's conversation content is not available for export.");
@@ -65975,32 +66138,8 @@ function renderHandoff(s3, full) {
65975
66138
  }
65976
66139
  return lines.join("\n");
65977
66140
  }
65978
- function renderMessage2(m2) {
65979
- const parts = [];
65980
- switch (m2.contentType) {
65981
- case "text":
65982
- parts.push(m2.text ?? "");
65983
- break;
65984
- case "tool-call":
65985
- parts.push(`\`${m2.toolName ?? "?"}(${m2.toolCallId ?? ""})\` args: ${m2.text ?? ""}`);
65986
- break;
65987
- case "tool-result":
65988
- parts.push(`\`${m2.toolName ?? "?"}(${m2.toolCallId ?? ""})\` \u2192 ${m2.text ?? ""}`);
65989
- break;
65990
- case "reasoning":
65991
- parts.push(`_reasoning_: ${m2.text ?? ""}`);
65992
- break;
65993
- }
65994
- const body = parts.join("\n").trim();
65995
- return body === "" ? "_(empty)_" : body + "\n";
65996
- }
65997
- function matchSession(sessions2, selector) {
65998
- const exact = sessions2.filter((s3) => s3.id === selector);
65999
- if (exact.length > 0) return exact;
66000
- const byLabel = sessions2.filter((s3) => s3.meta.label === selector);
66001
- if (byLabel.length > 0) return byLabel;
66002
- const byPrefix = sessions2.filter((s3) => s3.id.startsWith(selector) || (s3.meta.label ?? "").startsWith(selector));
66003
- return byPrefix;
66141
+ function matchSession2(sessions2, selector) {
66142
+ return matchSession(sessions2, selector, (s3) => s3.meta.label);
66004
66143
  }
66005
66144
  async function exportSession(selector, opts = {}) {
66006
66145
  const store = new SessionStore({ dir: opts.dir, enabled: true });
@@ -66015,7 +66154,7 @@ async function exportSession(selector, opts = {}) {
66015
66154
  );
66016
66155
  return ["Persisted sessions:", "", ...rows.map((r) => ` ${r}`), "", "Usage: bili export <session-id|label> [--output handoff.md] [--full]"].join("\n");
66017
66156
  }
66018
- const matches = matchSession(all, selector);
66157
+ const matches = matchSession2(all, selector);
66019
66158
  if (matches.length === 0) {
66020
66159
  throw new Error(`no session matches "${selector}" (run "bili export" to list sessions)`);
66021
66160
  }
@@ -66023,7 +66162,7 @@ async function exportSession(selector, opts = {}) {
66023
66162
  const ids = matches.map((s3) => s3.id).join(", ");
66024
66163
  throw new Error(`selector "${selector}" matches ${matches.length} sessions (${ids}); use the full session id`);
66025
66164
  }
66026
- const markdown = renderHandoff(matches[0], opts.full ?? false);
66165
+ const markdown = renderHandoff2(matches[0], opts.full ?? false);
66027
66166
  if (opts.output) {
66028
66167
  mkdirSync6(path16.dirname(path16.resolve(opts.output)), { recursive: true });
66029
66168
  writeFileSync5(opts.output, markdown, "utf8");
@@ -66348,14 +66487,16 @@ async function main() {
66348
66487
  if (v2 !== void 0) process.env[k2] = v2;
66349
66488
  }
66350
66489
  let updaterResolveProxy;
66490
+ let updateTag;
66351
66491
  try {
66352
66492
  const o = loadOptions();
66353
66493
  updaterResolveProxy = (url) => resolveProxy(o.routes, o.proxy, url, o.proxyFallback);
66494
+ updateTag = o.updateTag;
66354
66495
  } catch (e) {
66355
66496
  console.error(`bili update: config load failed (${String(e)}); updater egress goes direct`);
66356
66497
  }
66357
66498
  await checkForUpdate(
66358
- { packageName: PACKAGE_NAME, currentVersion: VERSION3, autoUpdate: true, resolveProxy: updaterResolveProxy },
66499
+ { packageName: PACKAGE_NAME, currentVersion: VERSION3, autoUpdate: true, resolveProxy: updaterResolveProxy, updateTag },
66359
66500
  true
66360
66501
  );
66361
66502
  return;
@@ -66383,7 +66524,8 @@ async function main() {
66383
66524
  packageName: PACKAGE_NAME,
66384
66525
  currentVersion: VERSION3,
66385
66526
  autoUpdate: true,
66386
- resolveProxy: (url) => resolveProxy(opts.routes, opts.proxy, url, opts.proxyFallback)
66527
+ resolveProxy: (url) => resolveProxy(opts.routes, opts.proxy, url, opts.proxyFallback),
66528
+ updateTag: opts.updateTag
66387
66529
  });
66388
66530
  }
66389
66531
  }