billion-context 0.1.132 → 0.1.133

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/README.md CHANGED
@@ -810,6 +810,30 @@ The file auto-rotates at 10 MB (renamed to `bili.log.old`). Cache-hit stats
810
810
  per request are logged as `[acp-usage] round N input=X cached=Y (cache hit Z%)`
811
811
  so you can measure prefix-cache health directly from the log.
812
812
 
813
+ ### Chaining bili instances (bili → bili)
814
+
815
+ When one bili's upstream is another bili (`client → A → B → LLM`), only the
816
+ instance closest to the client should run the compression pipeline — a second
817
+ pass would double-compress and corrupt session state. Any of three signals on
818
+ the inbound request makes the downstream instance pass it through **verbatim**
819
+ (no tool/tag injection, no acp-loop, no session state):
820
+
821
+ - `x-bili-hop` — stamped with the processing instance's id on every processed
822
+ emission (#300). Clients never send this header, so its presence always means
823
+ "came from a bili".
824
+ - `x-bili-sig` — sha256 digest of the exact emitted body bytes (#1078). Proves
825
+ no middlebox rewrote the request in transit; on mismatch the downstream
826
+ instance passes through anyway, because it must not process content it did
827
+ not produce.
828
+ - ACP artifacts in the body (render tags / context-management tool names) —
829
+ fallback for relays that strip bili's headers.
830
+
831
+ Every detection logs a prominent `[chain]` line (to `bili.log` and stderr)
832
+ naming the signal that fired. Older versions that stamp only `x-bili-hop` keep
833
+ working unchanged. If a third-party relay in your chain rewrites or strips
834
+ request bodies/headers, a `[chain]` warning on the downstream instance is the
835
+ safety mechanism working, not an error.
836
+
813
837
  ### Self-update
814
838
 
815
839
  The proxy checks npm for a newer version on startup and every 3 minutes. When a
package/README.zh-CN.md CHANGED
@@ -402,6 +402,16 @@ bili --host 0.0.0.0 # 所有网卡(或直接用局域网 IP)
402
402
 
403
403
  文件超过 10 MB 自动轮转(重命名为 `bili.log.old`)。每个请求的缓存命中统计会以 `[acp-usage] round N input=X cached=Y (cache hit Z%)` 打印,可直接从日志衡量前缀缓存健康度。
404
404
 
405
+ ### 串联多个 bili(bili → bili)
406
+
407
+ 当一个 bili 的 upstream 指向另一个 bili(`客户端 → A → B → LLM`)时,只有离客户端最近的那个实例应该执行压缩管线——二次处理会重复压缩并破坏会话状态。入站请求上任意一个信号命中,下游实例都会**原样透传**该请求(不注入工具/标签、不跑 acp-loop、不建会话状态):
408
+
409
+ - `x-bili-hop` —— 处理方实例在每次处理后的转发上盖自己的实例 id(#300)。客户端永远不会发这个头,所以它的出现一定意味着"来自某个 bili"。
410
+ - `x-bili-sig` —— 转发体字节的 sha256 摘要(#1078)。用来证明请求在途中没有被中间设备改写;摘要不符时下游同样直接透传,因为它不能处理自己没有产出的内容。
411
+ - 请求体中的 ACP 产物(render 标签 / 上下文管理工具名)—— 针对会剥掉 bili 请求头的中转站的兜底检测。
412
+
413
+ 每次命中都会在 `bili.log`(及 stderr)打一条醒目的 `[chain]` 日志,标明触发信号。只盖 `x-bili-hop` 的旧版本行为不变。如果你的链路里有第三方中转站会改写或剥离请求体/头,下游实例出现的 `[chain]` 警告是安全机制在工作,不是错误。
414
+
405
415
  ### 自动更新
406
416
 
407
417
  代理启动时和每 3 分钟检查 npm 是否有新版本。发现新版本就全局安装(`npm install -g`)并打印通知 —— **重启 `bili` 才能生效**。
package/dist/index.js CHANGED
@@ -50569,7 +50569,7 @@ function rejectLegacyRoute(key, value) {
50569
50569
  import http from "http";
50570
50570
  import fs11 from "fs";
50571
50571
  import path18 from "path";
50572
- import { createHash as createHash8, randomUUID as randomUUID4 } from "crypto";
50572
+ import { createHash as createHash9, randomUUID as randomUUID4 } from "crypto";
50573
50573
  import { performance as performance2 } from "perf_hooks";
50574
50574
 
50575
50575
  // src/compress-settings.ts
@@ -70492,6 +70492,35 @@ function anthropicBetaContextWindow(headers) {
70492
70492
  return best;
70493
70493
  }
70494
70494
 
70495
+ // src/server/chain-sig.ts
70496
+ import { createHash as createHash8, timingSafeEqual } from "crypto";
70497
+ var BILI_SIG_HEADER = "x-bili-sig";
70498
+ var SIG_FORMAT = /^sha256:([0-9a-f]{64})$/;
70499
+ function sha256Hex(body) {
70500
+ return createHash8("sha256").update(typeof body === "string" ? Buffer.from(body, "utf8") : body).digest("hex");
70501
+ }
70502
+ function stampChainSig(headers, body) {
70503
+ headers[BILI_SIG_HEADER] = `sha256:${sha256Hex(body)}`;
70504
+ }
70505
+ function verifyChainSig(header, rawBody) {
70506
+ if (header === void 0) return "absent";
70507
+ const m2 = SIG_FORMAT.exec(header);
70508
+ if (!m2) return "mismatch";
70509
+ const expected = Buffer.from(m2[1], "hex");
70510
+ const actual = createHash8("sha256").update(rawBody).digest();
70511
+ return timingSafeEqual(expected, actual) ? "verified" : "mismatch";
70512
+ }
70513
+ var ACP_TAG_RE = /\x3cacp\s+tokens=\\?"[0-9]+(?:\.[0-9]+)?K?\\?"\s+type=\\?"[^\\"]*\\?"\s*\x3em[0-9]{1,8}\x3c\/acp\x3e/;
70514
+ function detectAcpArtifacts(body) {
70515
+ if (body.length === 0) return false;
70516
+ const tagSeed = body.includes("<acp ");
70517
+ const toolSeed = body.includes('"acp_status"') && body.includes('"search_context"');
70518
+ if (!tagSeed && !toolSeed) return false;
70519
+ const text = body.toString("utf8");
70520
+ if (tagSeed && ACP_TAG_RE.test(text)) return true;
70521
+ return toolSeed;
70522
+ }
70523
+
70495
70524
  // src/server/headers.ts
70496
70525
  function safeSessionId(id) {
70497
70526
  return (id ?? "unknown").replace(/[^a-zA-Z0-9._-]/g, "_");
@@ -71236,6 +71265,20 @@ async function handle(req, res, opts, core, config, log2, instanceId, instanceSt
71236
71265
  const selfLoop = hopMarker === instanceId;
71237
71266
  log2("warn", selfLoop ? `[chain] inbound request carries THIS instance's ${BILI_HOP_HEADER} marker (${hopMarker}) \u2014 self-loop detected. Passing through without processing; check your upstream config (it may point back to this instance).` : `[chain] inbound request carries ${BILI_HOP_HEADER} from another bili instance (${hopMarker}) \u2014 bili\u2192bili chain detected. Passing through without processing to avoid double compression; keep only one bili instance in the chain.`);
71238
71267
  }
71268
+ const chainSig = headerValue2(req, BILI_SIG_HEADER);
71269
+ const hasAcpArtifacts = hopMarker === void 0 && chainSig === void 0 && bodyBuffer.length > 0 && detectAcpArtifacts(bodyBuffer);
71270
+ if (hopMarker !== void 0 && chainSig !== void 0) {
71271
+ if (verifyChainSig(chainSig, bodyBuffer) === "mismatch") {
71272
+ log2("warn", `[chain] ${BILI_HOP_HEADER} (${hopMarker}) present but ${BILI_SIG_HEADER} does not match the received body \u2014 a middlebox rewrote the request in transit. Passing through without processing.`);
71273
+ } else {
71274
+ log2("debug", `[chain] ${BILI_SIG_HEADER} verified for ${BILI_HOP_HEADER}=${hopMarker}`);
71275
+ }
71276
+ } else if (hopMarker === void 0 && chainSig !== void 0) {
71277
+ log2("warn", `[chain] inbound request carries ${BILI_SIG_HEADER} but no ${BILI_HOP_HEADER} \u2014 a middlebox likely stripped the hop header. Passing through without processing.`);
71278
+ } else if (hasAcpArtifacts) {
71279
+ log2("warn", `[chain] inbound request carries ACP compression artifacts but neither ${BILI_HOP_HEADER} nor ${BILI_SIG_HEADER} \u2014 a middlebox likely stripped bili's headers. Passing through without processing.`);
71280
+ }
71281
+ const chainDetected = hopMarker !== void 0 || chainSig !== void 0 || hasAcpArtifacts;
71239
71282
  if (headerValue2(req, BILI_PLUGIN_BYPASS_HEADER) === "1") {
71240
71283
  log2("debug", `bypass: ${req.method ?? "?"} ${maskUrlForLog(req.url ?? "")} \u2014 raw passthrough (legacy in-process compression)`);
71241
71284
  await forward(req, res, opts, bodyBuffer, null, core, config, log2, route, instanceId, void 0);
@@ -71358,10 +71401,10 @@ ${bodyBuffer.toString("utf8")}`);
71358
71401
  let prepared = null;
71359
71402
  let forwarded = false;
71360
71403
  const routePassthrough = !opts.passthrough && findRoute(opts.routes, route?.rewrittenUrl)?.passthrough === true;
71361
- if (routePassthrough && hopMarker === void 0 && protocol && parsed) {
71404
+ if (routePassthrough && !chainDetected && protocol && parsed) {
71362
71405
  log2("info", `[route-passthrough] ${maskUrlsInText(route?.rewrittenUrl ?? "")} matches a passthrough route \u2014 forwarding verbatim, kernel bypassed`);
71363
71406
  }
71364
- if (!opts.passthrough && !routePassthrough && hopMarker === void 0 && protocol && parsed && typeof parsed === "object") {
71407
+ if (!opts.passthrough && !routePassthrough && !chainDetected && protocol && parsed && typeof parsed === "object") {
71365
71408
  const sessionHeader = headerValue2(req, opts.sessionHeader);
71366
71409
  let pluginAgent = pluginAgentHeader(req.headers);
71367
71410
  let pluginConversation = pluginConversationHeader(req.headers);
@@ -72656,7 +72699,7 @@ async function preflightCompressIfNeeded(prepared, runPrepare, req, inboundBody,
72656
72699
  return failFast(502, "no part of the conversation is compressible (nothing left to fold)", false);
72657
72700
  }
72658
72701
  }
72659
- const deadEndKey = `${model}\0${limit}\0${createHash8("sha256").update(inboundBody).digest("hex")}`;
72702
+ const deadEndKey = `${model}\0${limit}\0${createHash9("sha256").update(inboundBody).digest("hex")}`;
72660
72703
  const deadEnd = session.metadata.preflightDeadEnd;
72661
72704
  if (deadEnd && typeof deadEnd === "object") {
72662
72705
  const de2 = deadEnd;
@@ -72783,6 +72826,9 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
72783
72826
  }
72784
72827
  }
72785
72828
  }
72829
+ if (prepared !== null && req.method !== "GET" && req.method !== "HEAD") {
72830
+ stampChainSig(headers, wireBody);
72831
+ }
72786
72832
  const wireTransform = compatProtocol ? (b2) => {
72787
72833
  if (compatRoles) applyCompatRolesJson(b2, compatProtocol, compatRoles);
72788
72834
  return b2;
@@ -72920,7 +72966,9 @@ ${bodyText}`);
72920
72966
  if (fixed.rewritten === 0) return "other";
72921
72967
  let r;
72922
72968
  try {
72923
- r = await fetchWithTimeout(upstreamUrl, { ...init, body: fixed.body }, void 0, clientAbort.signal);
72969
+ const retryHeaders = prepared !== null ? { ...headers } : headers;
72970
+ if (prepared !== null) stampChainSig(retryHeaders, fixed.body);
72971
+ r = await fetchWithTimeout(upstreamUrl, { ...init, headers: retryHeaders, body: fixed.body }, void 0, clientAbort.signal);
72924
72972
  } catch {
72925
72973
  return "other";
72926
72974
  }
@@ -73411,11 +73459,13 @@ async function resolveFakeCompletion(stream2, opts) {
73411
73459
  opts.log("warn", `[${sid}] fake completion (tool-call XML, no tool block); retry ${attempt}/${max} with corrective hint`);
73412
73460
  let r;
73413
73461
  try {
73462
+ const retryHeaders = { ...opts.reqHeaders };
73463
+ stampChainSig(retryHeaders, hinted);
73414
73464
  r = await fetchWithTimeout(
73415
73465
  opts.upstreamUrl,
73416
73466
  {
73417
73467
  method: "POST",
73418
- headers: opts.reqHeaders,
73468
+ headers: retryHeaders,
73419
73469
  body: hinted,
73420
73470
  ...opts.proxyUrl ? { dispatcher: proxyDispatcher(opts.proxyUrl) } : {}
73421
73471
  },