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 +24 -0
- package/README.zh-CN.md +10 -0
- package/dist/index.js +56 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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 &&
|
|
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 &&
|
|
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${
|
|
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
|
-
|
|
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:
|
|
73468
|
+
headers: retryHeaders,
|
|
73419
73469
|
body: hinted,
|
|
73420
73470
|
...opts.proxyUrl ? { dispatcher: proxyDispatcher(opts.proxyUrl) } : {}
|
|
73421
73471
|
},
|