billion-context 0.1.53 → 0.1.54-pr.245.89
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 +17 -0
- package/README.zh-CN.md +15 -0
- package/dist/index.js +116 -80
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -148,6 +148,23 @@ bili --no-auto-update # disable self-update for this run
|
|
|
148
148
|
|
|
149
149
|
Flags override env vars and the config file. `bili --help` lists them all.
|
|
150
150
|
|
|
151
|
+
### Remote agents (`--host`)
|
|
152
|
+
|
|
153
|
+
By default the proxy binds `127.0.0.1` and only accepts loopback
|
|
154
|
+
connections. To serve agents on other machines, bind a non-loopback host:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
bili --host 0.0.0.0 # all interfaces (or use your LAN IP)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- Remote agents point their model `baseURL` at `http://<this-host>:<port>/bili/…`.
|
|
161
|
+
- MITM-mode `CONNECT` then also accepts remote clients — for **whitelisted
|
|
162
|
+
model hosts only**. Blind tunnels to arbitrary hosts stay loopback-only, so
|
|
163
|
+
the proxy can never be used as an open relay.
|
|
164
|
+
- There is **no authentication**: only do this on a trusted LAN or behind a
|
|
165
|
+
firewall. The `/__bili/` management endpoints remain loopback-only.
|
|
166
|
+
- A startup `[security]` warning reminds you of the above.
|
|
167
|
+
|
|
151
168
|
### Debugging
|
|
152
169
|
|
|
153
170
|
Three ways to enable verbose logging (priority: flag > env > config):
|
package/README.zh-CN.md
CHANGED
|
@@ -133,6 +133,21 @@ bili --no-auto-update # 本次启动禁用自动更新
|
|
|
133
133
|
|
|
134
134
|
参数优先级高于环境变量和配置文件。`bili --help` 列出全部。
|
|
135
135
|
|
|
136
|
+
### 远程 agent(`--host`)
|
|
137
|
+
|
|
138
|
+
默认绑定 `127.0.0.1`,只接受本机连接。要给其他机器上的 agent 用,绑定非 loopback 地址:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
bili --host 0.0.0.0 # 所有网卡(或直接用局域网 IP)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- 远程 agent 把模型 `baseURL` 指向 `http://<本机IP>:<端口>/bili/…`。
|
|
145
|
+
- MITM 模式的 `CONNECT` 也会接受远程客户端 —— 但仅限**白名单内的模型域名**;
|
|
146
|
+
到任意主机的盲隧道仍仅限本机,代理不会沦为开放中继。
|
|
147
|
+
- **没有任何鉴权**: 只应在可信局域网或防火墙内使用。`/__bili/` 管理
|
|
148
|
+
端点仍仅限本机访问。
|
|
149
|
+
- 启动时的 `[security]` 警告会提醒上述事项。
|
|
150
|
+
|
|
136
151
|
### 调试
|
|
137
152
|
|
|
138
153
|
三种方式打开详细日志(优先级:参数 > 环境变量 > 配置):
|
package/dist/index.js
CHANGED
|
@@ -50295,6 +50295,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
50295
50295
|
const calls = [];
|
|
50296
50296
|
let usage = {};
|
|
50297
50297
|
let finishReason;
|
|
50298
|
+
let sawDone = false;
|
|
50298
50299
|
let suppressCompletion = false;
|
|
50299
50300
|
for await (const ev of adapter.parseStream(currentUpstream, round)) {
|
|
50300
50301
|
if (signal?.aborted) break;
|
|
@@ -50332,6 +50333,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
50332
50333
|
cachedTokens: ev.cachedTokens
|
|
50333
50334
|
};
|
|
50334
50335
|
} else if (ev.kind === "done") {
|
|
50336
|
+
sawDone = true;
|
|
50335
50337
|
finishReason = ev.finishReason;
|
|
50336
50338
|
suppressCompletion = ev.suppressCompletion === true;
|
|
50337
50339
|
} else if (ev.kind === "meta") {
|
|
@@ -50453,6 +50455,15 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
50453
50455
|
}
|
|
50454
50456
|
const reRequest = proxyResults.length > 0 && realCalls === 0;
|
|
50455
50457
|
if (!reRequest) {
|
|
50458
|
+
if (!sawDone) {
|
|
50459
|
+
const partialText = assistantText.length;
|
|
50460
|
+
const partialReasoning = assistantReasoning.length;
|
|
50461
|
+
const msg = `upstream stream truncated (no completion event; round ${round}, ${partialText} text chars + ${partialReasoning} reasoning chars received)`;
|
|
50462
|
+
ctx.log(`[acp-loop] round ${round}: ${msg}`);
|
|
50463
|
+
log("error", `[acp-loop] ${msg}`);
|
|
50464
|
+
yield adapter.emitError(msg);
|
|
50465
|
+
return;
|
|
50466
|
+
}
|
|
50456
50467
|
if (!suppressCompletion) {
|
|
50457
50468
|
yield adapter.emitCompletion({ finishReason, usage });
|
|
50458
50469
|
}
|
|
@@ -50534,7 +50545,91 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
50534
50545
|
}
|
|
50535
50546
|
}
|
|
50536
50547
|
|
|
50548
|
+
// src/util.ts
|
|
50549
|
+
import { createHash as createHash4 } from "crypto";
|
|
50550
|
+
function hashId2(s3) {
|
|
50551
|
+
return createHash4("sha256").update(s3, "utf8").digest("hex").slice(0, 16);
|
|
50552
|
+
}
|
|
50553
|
+
function isLoopbackAddress(addr) {
|
|
50554
|
+
return !!addr && (addr.startsWith("127.") || addr === "::1" || addr.startsWith("::ffff:127."));
|
|
50555
|
+
}
|
|
50556
|
+
function usageTotals(protocol, usage) {
|
|
50557
|
+
const num3 = (v2) => typeof v2 === "number" && Number.isFinite(v2) ? v2 : void 0;
|
|
50558
|
+
if (protocol === "anthropic") {
|
|
50559
|
+
const fresh = num3(usage["input_tokens"]);
|
|
50560
|
+
const read = num3(usage["cache_read_input_tokens"]);
|
|
50561
|
+
const creation = num3(usage["cache_creation_input_tokens"]);
|
|
50562
|
+
const any = fresh !== void 0 || read !== void 0 || creation !== void 0;
|
|
50563
|
+
return {
|
|
50564
|
+
total: any ? (fresh ?? 0) + (read ?? 0) + (creation ?? 0) : void 0,
|
|
50565
|
+
cached: read
|
|
50566
|
+
};
|
|
50567
|
+
}
|
|
50568
|
+
if (protocol === "openai") {
|
|
50569
|
+
return {
|
|
50570
|
+
total: num3(usage["prompt_tokens"]),
|
|
50571
|
+
cached: num3(usage["prompt_tokens_details"]?.["cached_tokens"])
|
|
50572
|
+
};
|
|
50573
|
+
}
|
|
50574
|
+
return {
|
|
50575
|
+
total: num3(usage["input_tokens"]),
|
|
50576
|
+
cached: num3(usage["input_tokens_details"]?.["cached_tokens"])
|
|
50577
|
+
};
|
|
50578
|
+
}
|
|
50579
|
+
var CONTEXT_OVERFLOW_PATTERNS = [
|
|
50580
|
+
/context_length_exceeded/i,
|
|
50581
|
+
/context length exceeded/i,
|
|
50582
|
+
/maximum context length/i,
|
|
50583
|
+
/max context length/i,
|
|
50584
|
+
/maximum context size/i,
|
|
50585
|
+
/exceeds the context window/i,
|
|
50586
|
+
/exceeded model token limit/i,
|
|
50587
|
+
/prompt is too long/i,
|
|
50588
|
+
/prompt_too_long/i,
|
|
50589
|
+
/prompt_is_too_long/i,
|
|
50590
|
+
/request_too_large/i,
|
|
50591
|
+
/token limit exceeded/i
|
|
50592
|
+
];
|
|
50593
|
+
function toTokenNumber(s3) {
|
|
50594
|
+
const n = parseInt(s3.replace(/,/g, ""), 10);
|
|
50595
|
+
return Number.isFinite(n) && n >= 1e3 ? n : void 0;
|
|
50596
|
+
}
|
|
50597
|
+
function parseOverflowWindow(text) {
|
|
50598
|
+
let m2 = text.match(/>\s*(\d[\d,]*)\s*maximum/i);
|
|
50599
|
+
if (m2) return toTokenNumber(m2[1]);
|
|
50600
|
+
m2 = text.match(/maximum context length is (\d[\d,]*)/i) ?? text.match(/maximum context length of (\d[\d,]*)/i) ?? text.match(/maximum context size (?:is|of) (\d[\d,]*)/i) ?? text.match(/(?:maximum|max)\s+(?:context\s+)?length\s+(?:is\s+)?(\d[\d,]*)/i) ?? text.match(/limit of (\d[\d,]*)\s*token/i) ?? text.match(/(\d[\d,]*)\s*maximum\b/i);
|
|
50601
|
+
if (m2) return toTokenNumber(m2[1]);
|
|
50602
|
+
return void 0;
|
|
50603
|
+
}
|
|
50604
|
+
function inspectContextOverflow(status, bodyText) {
|
|
50605
|
+
const message = (bodyText ?? "").slice(0, 300);
|
|
50606
|
+
if (status !== 400 && status !== 413) return { isOverflow: false, message };
|
|
50607
|
+
if (!bodyText) return { isOverflow: false, message };
|
|
50608
|
+
const isOverflow = CONTEXT_OVERFLOW_PATTERNS.some((p2) => p2.test(bodyText));
|
|
50609
|
+
if (!isOverflow) return { isOverflow: false, message };
|
|
50610
|
+
return { isOverflow: true, window: parseOverflowWindow(bodyText), message };
|
|
50611
|
+
}
|
|
50612
|
+
function reserveOutputHeadroom(window2, maxOutput) {
|
|
50613
|
+
if (Number.isFinite(window2) && window2 > 0 && Number.isFinite(maxOutput) && maxOutput > 0 && maxOutput < window2) {
|
|
50614
|
+
return window2 - maxOutput;
|
|
50615
|
+
}
|
|
50616
|
+
return window2;
|
|
50617
|
+
}
|
|
50618
|
+
function shouldReserveOutputHeadroom(protocol) {
|
|
50619
|
+
return protocol !== "anthropic";
|
|
50620
|
+
}
|
|
50621
|
+
|
|
50537
50622
|
// src/loop/adapter-responses.ts
|
|
50623
|
+
var RESPONSES_ITEM_ID_MAX = 64;
|
|
50624
|
+
function sanitizeResponsesInputIds(input) {
|
|
50625
|
+
if (!Array.isArray(input)) return;
|
|
50626
|
+
for (const item of input) {
|
|
50627
|
+
const rec = item;
|
|
50628
|
+
if (typeof rec?.id === "string" && rec.id.length > RESPONSES_ITEM_ID_MAX) {
|
|
50629
|
+
rec.id = `msg-fix-${hashId2(rec.id)}`;
|
|
50630
|
+
}
|
|
50631
|
+
}
|
|
50632
|
+
}
|
|
50538
50633
|
function rewriteItemEvent(type, obj, mapped) {
|
|
50539
50634
|
const item = { ...obj.item ?? {}, id: mapped.id };
|
|
50540
50635
|
return Buffer.from(`event: ${type}
|
|
@@ -50766,7 +50861,7 @@ function createResponsesAdapter(textProtocol, projection) {
|
|
|
50766
50861
|
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: false };
|
|
50767
50862
|
} else if (item?.type === "message" && round > 1 && !suppressTextLifecycle) {
|
|
50768
50863
|
const origId = typeof item.id === "string" ? item.id : "";
|
|
50769
|
-
const mapped = { id: `msg-proxy-${round}-${origId || outputIndex}`, index: outputIndex++ };
|
|
50864
|
+
const mapped = { id: `msg-proxy-${round}-${hashId2(origId || String(outputIndex))}`, index: outputIndex++ };
|
|
50770
50865
|
if (origId) remapped.set(origId, mapped);
|
|
50771
50866
|
yield { kind: "meta", chunk: rewriteItemEvent(type, obj, mapped), firstRoundOnly: false };
|
|
50772
50867
|
} else if (item?.type !== "message" || !suppressTextLifecycle) {
|
|
@@ -52017,80 +52112,6 @@ data: ${JSON.stringify({ type: "message_stop" })}
|
|
|
52017
52112
|
}
|
|
52018
52113
|
}
|
|
52019
52114
|
|
|
52020
|
-
// src/util.ts
|
|
52021
|
-
import { createHash as createHash4 } from "crypto";
|
|
52022
|
-
function hashId2(s3) {
|
|
52023
|
-
return createHash4("sha256").update(s3, "utf8").digest("hex").slice(0, 16);
|
|
52024
|
-
}
|
|
52025
|
-
function isLoopbackAddress(addr) {
|
|
52026
|
-
return !!addr && (addr.startsWith("127.") || addr === "::1" || addr.startsWith("::ffff:127."));
|
|
52027
|
-
}
|
|
52028
|
-
function usageTotals(protocol, usage) {
|
|
52029
|
-
const num3 = (v2) => typeof v2 === "number" && Number.isFinite(v2) ? v2 : void 0;
|
|
52030
|
-
if (protocol === "anthropic") {
|
|
52031
|
-
const fresh = num3(usage["input_tokens"]);
|
|
52032
|
-
const read = num3(usage["cache_read_input_tokens"]);
|
|
52033
|
-
const creation = num3(usage["cache_creation_input_tokens"]);
|
|
52034
|
-
const any = fresh !== void 0 || read !== void 0 || creation !== void 0;
|
|
52035
|
-
return {
|
|
52036
|
-
total: any ? (fresh ?? 0) + (read ?? 0) + (creation ?? 0) : void 0,
|
|
52037
|
-
cached: read
|
|
52038
|
-
};
|
|
52039
|
-
}
|
|
52040
|
-
if (protocol === "openai") {
|
|
52041
|
-
return {
|
|
52042
|
-
total: num3(usage["prompt_tokens"]),
|
|
52043
|
-
cached: num3(usage["prompt_tokens_details"]?.["cached_tokens"])
|
|
52044
|
-
};
|
|
52045
|
-
}
|
|
52046
|
-
return {
|
|
52047
|
-
total: num3(usage["input_tokens"]),
|
|
52048
|
-
cached: num3(usage["input_tokens_details"]?.["cached_tokens"])
|
|
52049
|
-
};
|
|
52050
|
-
}
|
|
52051
|
-
var CONTEXT_OVERFLOW_PATTERNS = [
|
|
52052
|
-
/context_length_exceeded/i,
|
|
52053
|
-
/context length exceeded/i,
|
|
52054
|
-
/maximum context length/i,
|
|
52055
|
-
/max context length/i,
|
|
52056
|
-
/maximum context size/i,
|
|
52057
|
-
/exceeds the context window/i,
|
|
52058
|
-
/exceeded model token limit/i,
|
|
52059
|
-
/prompt is too long/i,
|
|
52060
|
-
/prompt_too_long/i,
|
|
52061
|
-
/prompt_is_too_long/i,
|
|
52062
|
-
/request_too_large/i,
|
|
52063
|
-
/token limit exceeded/i
|
|
52064
|
-
];
|
|
52065
|
-
function toTokenNumber(s3) {
|
|
52066
|
-
const n = parseInt(s3.replace(/,/g, ""), 10);
|
|
52067
|
-
return Number.isFinite(n) && n >= 1e3 ? n : void 0;
|
|
52068
|
-
}
|
|
52069
|
-
function parseOverflowWindow(text) {
|
|
52070
|
-
let m2 = text.match(/>\s*(\d[\d,]*)\s*maximum/i);
|
|
52071
|
-
if (m2) return toTokenNumber(m2[1]);
|
|
52072
|
-
m2 = text.match(/maximum context length is (\d[\d,]*)/i) ?? text.match(/maximum context length of (\d[\d,]*)/i) ?? text.match(/maximum context size (?:is|of) (\d[\d,]*)/i) ?? text.match(/(?:maximum|max)\s+(?:context\s+)?length\s+(?:is\s+)?(\d[\d,]*)/i) ?? text.match(/limit of (\d[\d,]*)\s*token/i) ?? text.match(/(\d[\d,]*)\s*maximum\b/i);
|
|
52073
|
-
if (m2) return toTokenNumber(m2[1]);
|
|
52074
|
-
return void 0;
|
|
52075
|
-
}
|
|
52076
|
-
function inspectContextOverflow(status, bodyText) {
|
|
52077
|
-
const message = (bodyText ?? "").slice(0, 300);
|
|
52078
|
-
if (status !== 400 && status !== 413) return { isOverflow: false, message };
|
|
52079
|
-
if (!bodyText) return { isOverflow: false, message };
|
|
52080
|
-
const isOverflow = CONTEXT_OVERFLOW_PATTERNS.some((p2) => p2.test(bodyText));
|
|
52081
|
-
if (!isOverflow) return { isOverflow: false, message };
|
|
52082
|
-
return { isOverflow: true, window: parseOverflowWindow(bodyText), message };
|
|
52083
|
-
}
|
|
52084
|
-
function reserveOutputHeadroom(window2, maxOutput) {
|
|
52085
|
-
if (Number.isFinite(window2) && window2 > 0 && Number.isFinite(maxOutput) && maxOutput > 0 && maxOutput < window2) {
|
|
52086
|
-
return window2 - maxOutput;
|
|
52087
|
-
}
|
|
52088
|
-
return window2;
|
|
52089
|
-
}
|
|
52090
|
-
function shouldReserveOutputHeadroom(protocol) {
|
|
52091
|
-
return protocol !== "anthropic";
|
|
52092
|
-
}
|
|
52093
|
-
|
|
52094
52115
|
// src/session-id.ts
|
|
52095
52116
|
function extractKey(headers) {
|
|
52096
52117
|
const auth = headers["authorization"];
|
|
@@ -53217,17 +53238,23 @@ function isMitmHost(host, extraDomains = []) {
|
|
|
53217
53238
|
return all.some((d) => h === d || h.endsWith("." + d));
|
|
53218
53239
|
}
|
|
53219
53240
|
function setupMitm(server, extraDomains = [], log2 = () => {
|
|
53220
|
-
}, resolveProxyUrl) {
|
|
53241
|
+
}, resolveProxyUrl, allowRemoteClients = false) {
|
|
53221
53242
|
ensureRootCA();
|
|
53222
53243
|
server.on("connect", (req, clientSocket, head) => {
|
|
53223
|
-
|
|
53224
|
-
|
|
53244
|
+
const remote = !isLoopbackAddress(clientSocket.remoteAddress);
|
|
53245
|
+
if (remote && !allowRemoteClients) {
|
|
53246
|
+
log2(`CONNECT ${req.url} rejected: non-loopback client ${clientSocket.remoteAddress} (bind a non-loopback --host to allow remote clients)`);
|
|
53225
53247
|
clientSocket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
53226
53248
|
return;
|
|
53227
53249
|
}
|
|
53228
53250
|
const { hostname, port } = parseHostPort(req.url ?? "");
|
|
53229
53251
|
const targetPort = port || 443;
|
|
53230
53252
|
if (!isMitmHost(hostname, extraDomains)) {
|
|
53253
|
+
if (remote) {
|
|
53254
|
+
log2(`CONNECT ${req.url} rejected: remote client ${clientSocket.remoteAddress} tunneling non-whitelisted host`);
|
|
53255
|
+
clientSocket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
53256
|
+
return;
|
|
53257
|
+
}
|
|
53231
53258
|
const proxyUrl = resolveProxyUrl?.(hostname);
|
|
53232
53259
|
tunnelThrough(clientSocket, hostname, targetPort, head, log2, proxyUrl);
|
|
53233
53260
|
return;
|
|
@@ -54147,10 +54174,12 @@ Content-Length: ${Buffer.byteLength(body)}\r
|
|
|
54147
54174
|
);
|
|
54148
54175
|
});
|
|
54149
54176
|
if (opts.mitm.enabled) {
|
|
54150
|
-
|
|
54177
|
+
const allowRemoteConnect = opts.host === "0.0.0.0" || opts.host === "::" || !isLoopbackAddress(opts.host);
|
|
54178
|
+
setupMitm(server, opts.mitm.domains, (msg) => log2("info", msg), (host) => resolveProxy(opts.routes, opts.proxy, `https://${host}`, opts.proxyFallback), allowRemoteConnect);
|
|
54151
54179
|
}
|
|
54152
54180
|
server.listen(opts.port, opts.host, () => {
|
|
54153
|
-
const
|
|
54181
|
+
const nonLoopbackBind = opts.host === "0.0.0.0" || opts.host === "::" || !isLoopbackAddress(opts.host);
|
|
54182
|
+
const displayHost = nonLoopbackBind ? opts.host : opts.host === "0.0.0.0" ? "localhost" : opts.host;
|
|
54154
54183
|
try {
|
|
54155
54184
|
fs6.mkdirSync(stateDir(), { recursive: true });
|
|
54156
54185
|
const originHost = opts.host === "0.0.0.0" || opts.host === "::" || opts.host === "localhost" ? "127.0.0.1" : opts.host.includes(":") && !opts.host.startsWith("[") ? `[${opts.host}]` : opts.host;
|
|
@@ -54163,6 +54192,12 @@ Content-Length: ${Buffer.byteLength(body)}\r
|
|
|
54163
54192
|
"info",
|
|
54164
54193
|
`acp-proxy listening on http://${displayHost}:${opts.port} \u2014 web UI: http://${displayHost}:${opts.port}/__bili/ \u2014 zero-config: prefix any baseURL with http://${displayHost}:${opts.port}/bili/` + (nOverrides ? ` \u2014 context overrides for ${nOverrides} upstream URL(s)` : "") + (opts.mitm.enabled ? ` \u2014 MITM proxy on (whitelist)${opts.mitm.domains.length ? ` +${opts.mitm.domains.join(",")}` : ""}` : "")
|
|
54165
54194
|
);
|
|
54195
|
+
if (nonLoopbackBind) {
|
|
54196
|
+
log2(
|
|
54197
|
+
"warn",
|
|
54198
|
+
`[security] bound to ${opts.host} \u2014 proxy endpoints (/bili/, CONNECT for whitelisted model hosts) are reachable from the network with NO authentication; /__bili/ management endpoints stay loopback-only. Restrict access with a firewall on untrusted networks. Remote agents: point baseURL at http://<this-host>:${opts.port}/bili/`
|
|
54199
|
+
);
|
|
54200
|
+
}
|
|
54166
54201
|
if (opts.debug) {
|
|
54167
54202
|
log2("info", `[debug] build features: raw-HTTP-capture(on) | remote_compaction_v2-strip(on) | cert-MITM-launcher(on) | strip-acp-summary(on) \u2014 seeing this line confirms the launcher build (not registry 0.1.34)`);
|
|
54168
54203
|
}
|
|
@@ -54684,6 +54719,7 @@ function prepareResponses(parsed, req, opts, core, config, prompts, log2, sessio
|
|
|
54684
54719
|
let responsesProjection;
|
|
54685
54720
|
let rebuiltInput = parsed.input;
|
|
54686
54721
|
let toolsOut = parsed.tools;
|
|
54722
|
+
sanitizeResponsesInputIds(parsed.input);
|
|
54687
54723
|
const shouldInject = opts.compress.injectTool;
|
|
54688
54724
|
const injectTools = shouldInject && !pluginMode;
|
|
54689
54725
|
const responsesTextProtocol = FORCE_TEXT_PROTOCOL || resolveCompressProtocol(opts.routes, session.meta.upstreamOrigin) === "marker";
|