billion-context 0.1.53 → 0.1.54-pr.246.90
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 +19 -1
- package/README.zh-CN.md +17 -1
- package/dist/index.js +211 -85
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ Two ways to use it — pick one:
|
|
|
67
67
|
- **URL change (persistent):** prefix your client's baseURL with the proxy
|
|
68
68
|
origin + `/bili/`.
|
|
69
69
|
|
|
70
|
-
### Option 1 — Launcher (`bili pi` / `bili codex` / `bili claude` / `bili omp` / `bili opencode` / `bili hermes`)
|
|
70
|
+
### Option 1 — Launcher (`bili pi` / `bili codex` / `bili claude` / `bili omp` / `bili opencode` / `bili hermes` / `bili dsh`)
|
|
71
71
|
|
|
72
72
|
The launcher wraps a client in one command: it starts a proxy on an
|
|
73
73
|
independent port (a fresh instance is always spawned — a port is never
|
|
@@ -85,6 +85,7 @@ bili claude # launch claude through the proxy
|
|
|
85
85
|
bili omp # pi-style: MITM env + persistent overlay home (~/.omp/agent-bili, real config untouched)
|
|
86
86
|
bili opencode # MITM for HTTPS + temp opencode.json (/bili/ for HTTP) + thin /acp plugin
|
|
87
87
|
bili hermes # no MITM possible (certifi CA) — persistent overlay home (~/.hermes-bili), all traffic /bili/
|
|
88
|
+
bili dsh # deepseek-harness: built-in deepseek route via DEEPSEEK_BASE_URL + overlay DSH_HOME (~/.dsh-bili), all traffic /bili/
|
|
88
89
|
bili pi --mitm-domain api.foo.com # add a domain to the MITM whitelist
|
|
89
90
|
```
|
|
90
91
|
|
|
@@ -148,6 +149,23 @@ bili --no-auto-update # disable self-update for this run
|
|
|
148
149
|
|
|
149
150
|
Flags override env vars and the config file. `bili --help` lists them all.
|
|
150
151
|
|
|
152
|
+
### Remote agents (`--host`)
|
|
153
|
+
|
|
154
|
+
By default the proxy binds `127.0.0.1` and only accepts loopback
|
|
155
|
+
connections. To serve agents on other machines, bind a non-loopback host:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
bili --host 0.0.0.0 # all interfaces (or use your LAN IP)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- Remote agents point their model `baseURL` at `http://<this-host>:<port>/bili/…`.
|
|
162
|
+
- MITM-mode `CONNECT` then also accepts remote clients — for **whitelisted
|
|
163
|
+
model hosts only**. Blind tunnels to arbitrary hosts stay loopback-only, so
|
|
164
|
+
the proxy can never be used as an open relay.
|
|
165
|
+
- There is **no authentication**: only do this on a trusted LAN or behind a
|
|
166
|
+
firewall. The `/__bili/` management endpoints remain loopback-only.
|
|
167
|
+
- A startup `[security]` warning reminds you of the above.
|
|
168
|
+
|
|
151
169
|
### Debugging
|
|
152
170
|
|
|
153
171
|
Three ways to enable verbose logging (priority: flag > env > config):
|
package/README.zh-CN.md
CHANGED
|
@@ -67,7 +67,7 @@ npm install -g billion-context
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
### 方式 1 —— 启动器(`bili pi` / `bili codex` / `bili claude` / `bili omp` / `bili opencode` / `bili hermes`)
|
|
70
|
+
### 方式 1 —— 启动器(`bili pi` / `bili codex` / `bili claude` / `bili omp` / `bili opencode` / `bili hermes` / `bili dsh`)
|
|
71
71
|
|
|
72
72
|
启动器把客户端包进一条命令:在独立端口拉起一个代理(总是全新实例,绝不复用端口),再按客户端支持的机制把它指向代理 —— 能吃代理/CA 环境变量的走**证书 MITM**,不吃的走隔离的**`/bili/` 配置重写**。真实配置文件从不被修改;客户端自己的配置只被**读取**,用来发现它实际连接的 HTTPS 上游主机,把这些主机加入 MITM 白名单 —— 代理只 TLS 终结它们,其余流量盲透传。
|
|
73
73
|
|
|
@@ -78,6 +78,7 @@ bili claude # 拉起 claude
|
|
|
78
78
|
bili omp # pi 同款 MITM 环境变量 + 隔离临时 models.yml
|
|
79
79
|
bili opencode # HTTPS 走 MITM + 临时 opencode.json(HTTP 走 /bili/)+ 轻量 /acp 插件
|
|
80
80
|
bili hermes # 无法 MITM(certifi CA)—— 隔离 HERMES_HOME,全部流量 /bili/
|
|
81
|
+
bili dsh # deepseek-harness:内置 deepseek 路由走 DEEPSEEK_BASE_URL + 隔离 DSH_HOME(~/.dsh-bili),全部流量 /bili/
|
|
81
82
|
bili pi --mitm-domain api.foo.com # 向 MITM 白名单追加域名
|
|
82
83
|
```
|
|
83
84
|
|
|
@@ -133,6 +134,21 @@ bili --no-auto-update # 本次启动禁用自动更新
|
|
|
133
134
|
|
|
134
135
|
参数优先级高于环境变量和配置文件。`bili --help` 列出全部。
|
|
135
136
|
|
|
137
|
+
### 远程 agent(`--host`)
|
|
138
|
+
|
|
139
|
+
默认绑定 `127.0.0.1`,只接受本机连接。要给其他机器上的 agent 用,绑定非 loopback 地址:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
bili --host 0.0.0.0 # 所有网卡(或直接用局域网 IP)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- 远程 agent 把模型 `baseURL` 指向 `http://<本机IP>:<端口>/bili/…`。
|
|
146
|
+
- MITM 模式的 `CONNECT` 也会接受远程客户端 —— 但仅限**白名单内的模型域名**;
|
|
147
|
+
到任意主机的盲隧道仍仅限本机,代理不会沦为开放中继。
|
|
148
|
+
- **没有任何鉴权**: 只应在可信局域网或防火墙内使用。`/__bili/` 管理
|
|
149
|
+
端点仍仅限本机访问。
|
|
150
|
+
- 启动时的 `[security]` 警告会提醒上述事项。
|
|
151
|
+
|
|
136
152
|
### 调试
|
|
137
153
|
|
|
138
154
|
三种方式打开详细日志(优先级:参数 > 环境变量 > 配置):
|
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) {
|
|
@@ -51317,8 +51412,8 @@ function createOpenaiAdapter(requestBody, clientSystem) {
|
|
|
51317
51412
|
emitCompletion(opts) {
|
|
51318
51413
|
const finishReason = opts?.finishReason ?? "stop";
|
|
51319
51414
|
const usage = opts?.usage ? {
|
|
51320
|
-
prompt_tokens: opts.usage.inputTokens,
|
|
51321
|
-
completion_tokens: opts.usage.outputTokens,
|
|
51415
|
+
prompt_tokens: opts.usage.inputTokens ?? 0,
|
|
51416
|
+
completion_tokens: opts.usage.outputTokens ?? 0,
|
|
51322
51417
|
total_tokens: (opts.usage.inputTokens ?? 0) + (opts.usage.outputTokens ?? 0),
|
|
51323
51418
|
...typeof opts.usage.cachedTokens === "number" ? { prompt_tokens_details: { cached_tokens: opts.usage.cachedTokens } } : {}
|
|
51324
51419
|
} : null;
|
|
@@ -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"];
|
|
@@ -52846,6 +52867,33 @@ function resolveHermesHome(env) {
|
|
|
52846
52867
|
const h = os.homedir();
|
|
52847
52868
|
return nonEmpty2(env.HERMES_HOME) ? env.HERMES_HOME : path8.join(h, ".hermes");
|
|
52848
52869
|
}
|
|
52870
|
+
function resolveDshHome(env) {
|
|
52871
|
+
const h = os.homedir();
|
|
52872
|
+
return nonEmpty2(env.DSH_HOME) ? env.DSH_HOME : path8.join(h, ".dsh");
|
|
52873
|
+
}
|
|
52874
|
+
function parseDshSettingsYaml(text) {
|
|
52875
|
+
const out = [];
|
|
52876
|
+
const seen = /* @__PURE__ */ new Set();
|
|
52877
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
52878
|
+
const m2 = /^\s*(?:baseURL|baseUrl|base_url):\s*(\S+)(?:\s+#.*)?$/.exec(rawLine);
|
|
52879
|
+
if (!m2) continue;
|
|
52880
|
+
const url = m2[1].replace(/^["']|["']$/g, "");
|
|
52881
|
+
if (!/^https?:\/\//i.test(url)) continue;
|
|
52882
|
+
if (seen.has(url)) continue;
|
|
52883
|
+
seen.add(url);
|
|
52884
|
+
out.push(url);
|
|
52885
|
+
}
|
|
52886
|
+
return out;
|
|
52887
|
+
}
|
|
52888
|
+
function readDshConfig(dshHome) {
|
|
52889
|
+
let text;
|
|
52890
|
+
try {
|
|
52891
|
+
text = fs4.readFileSync(path8.join(dshHome, "settings.yaml"), "utf8");
|
|
52892
|
+
} catch {
|
|
52893
|
+
return { baseUrls: [] };
|
|
52894
|
+
}
|
|
52895
|
+
return { baseUrls: parseDshSettingsYaml(text) };
|
|
52896
|
+
}
|
|
52849
52897
|
function readClaudeSettings(homeDir, cwd, env = process.env) {
|
|
52850
52898
|
const files = [
|
|
52851
52899
|
path8.join(homeDir, ".claude", "settings.json"),
|
|
@@ -53112,6 +53160,7 @@ function loadClientConfig(env, cwd) {
|
|
|
53112
53160
|
config.omp = readOmpConfig(resolveOmpHome(env));
|
|
53113
53161
|
config.opencode = readOpencodeConfig(resolveOpencodeConfigFile(env));
|
|
53114
53162
|
config.hermes = readHermesConfig(resolveHermesHome(env));
|
|
53163
|
+
config.dsh = readDshConfig(resolveDshHome(env));
|
|
53115
53164
|
return config;
|
|
53116
53165
|
}
|
|
53117
53166
|
|
|
@@ -53217,17 +53266,23 @@ function isMitmHost(host, extraDomains = []) {
|
|
|
53217
53266
|
return all.some((d) => h === d || h.endsWith("." + d));
|
|
53218
53267
|
}
|
|
53219
53268
|
function setupMitm(server, extraDomains = [], log2 = () => {
|
|
53220
|
-
}, resolveProxyUrl) {
|
|
53269
|
+
}, resolveProxyUrl, allowRemoteClients = false) {
|
|
53221
53270
|
ensureRootCA();
|
|
53222
53271
|
server.on("connect", (req, clientSocket, head) => {
|
|
53223
|
-
|
|
53224
|
-
|
|
53272
|
+
const remote = !isLoopbackAddress(clientSocket.remoteAddress);
|
|
53273
|
+
if (remote && !allowRemoteClients) {
|
|
53274
|
+
log2(`CONNECT ${req.url} rejected: non-loopback client ${clientSocket.remoteAddress} (bind a non-loopback --host to allow remote clients)`);
|
|
53225
53275
|
clientSocket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
53226
53276
|
return;
|
|
53227
53277
|
}
|
|
53228
53278
|
const { hostname, port } = parseHostPort(req.url ?? "");
|
|
53229
53279
|
const targetPort = port || 443;
|
|
53230
53280
|
if (!isMitmHost(hostname, extraDomains)) {
|
|
53281
|
+
if (remote) {
|
|
53282
|
+
log2(`CONNECT ${req.url} rejected: remote client ${clientSocket.remoteAddress} tunneling non-whitelisted host`);
|
|
53283
|
+
clientSocket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
|
|
53284
|
+
return;
|
|
53285
|
+
}
|
|
53231
53286
|
const proxyUrl = resolveProxyUrl?.(hostname);
|
|
53232
53287
|
tunnelThrough(clientSocket, hostname, targetPort, head, log2, proxyUrl);
|
|
53233
53288
|
return;
|
|
@@ -54147,10 +54202,12 @@ Content-Length: ${Buffer.byteLength(body)}\r
|
|
|
54147
54202
|
);
|
|
54148
54203
|
});
|
|
54149
54204
|
if (opts.mitm.enabled) {
|
|
54150
|
-
|
|
54205
|
+
const allowRemoteConnect = opts.host === "0.0.0.0" || opts.host === "::" || !isLoopbackAddress(opts.host);
|
|
54206
|
+
setupMitm(server, opts.mitm.domains, (msg) => log2("info", msg), (host) => resolveProxy(opts.routes, opts.proxy, `https://${host}`, opts.proxyFallback), allowRemoteConnect);
|
|
54151
54207
|
}
|
|
54152
54208
|
server.listen(opts.port, opts.host, () => {
|
|
54153
|
-
const
|
|
54209
|
+
const nonLoopbackBind = opts.host === "0.0.0.0" || opts.host === "::" || !isLoopbackAddress(opts.host);
|
|
54210
|
+
const displayHost = nonLoopbackBind ? opts.host : opts.host === "0.0.0.0" ? "localhost" : opts.host;
|
|
54154
54211
|
try {
|
|
54155
54212
|
fs6.mkdirSync(stateDir(), { recursive: true });
|
|
54156
54213
|
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 +54220,12 @@ Content-Length: ${Buffer.byteLength(body)}\r
|
|
|
54163
54220
|
"info",
|
|
54164
54221
|
`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
54222
|
);
|
|
54223
|
+
if (nonLoopbackBind) {
|
|
54224
|
+
log2(
|
|
54225
|
+
"warn",
|
|
54226
|
+
`[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/`
|
|
54227
|
+
);
|
|
54228
|
+
}
|
|
54166
54229
|
if (opts.debug) {
|
|
54167
54230
|
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
54231
|
}
|
|
@@ -54684,6 +54747,7 @@ function prepareResponses(parsed, req, opts, core, config, prompts, log2, sessio
|
|
|
54684
54747
|
let responsesProjection;
|
|
54685
54748
|
let rebuiltInput = parsed.input;
|
|
54686
54749
|
let toolsOut = parsed.tools;
|
|
54750
|
+
sanitizeResponsesInputIds(parsed.input);
|
|
54687
54751
|
const shouldInject = opts.compress.injectTool;
|
|
54688
54752
|
const injectTools = shouldInject && !pluginMode;
|
|
54689
54753
|
const responsesTextProtocol = FORCE_TEXT_PROTOCOL || resolveCompressProtocol(opts.routes, session.meta.upstreamOrigin) === "marker";
|
|
@@ -59207,7 +59271,7 @@ function selfDistFile(name) {
|
|
|
59207
59271
|
}
|
|
59208
59272
|
var LAUNCHER_DEFAULT_HOST = "127.0.0.1";
|
|
59209
59273
|
var LAUNCHER_DEFAULT_PORT = 8787;
|
|
59210
|
-
var LAUNCH_CLIENTS = ["pi", "codex", "claude", "omp", "opencode", "hermes", "pi-test"];
|
|
59274
|
+
var LAUNCH_CLIENTS = ["pi", "codex", "claude", "omp", "opencode", "hermes", "dsh", "pi-test"];
|
|
59211
59275
|
var HEALTH_PATH = "/__bili/health";
|
|
59212
59276
|
var HEALTH_POLL_INTERVAL_MS = 200;
|
|
59213
59277
|
var SPAWN_WAIT_MS = 2e4;
|
|
@@ -59316,6 +59380,22 @@ function discoverRoutes(client, config) {
|
|
|
59316
59380
|
} catch {
|
|
59317
59381
|
}
|
|
59318
59382
|
}
|
|
59383
|
+
} else if (client === "dsh") {
|
|
59384
|
+
const dshSeen = /* @__PURE__ */ new Set();
|
|
59385
|
+
let anon = 0;
|
|
59386
|
+
for (const raw of config.dsh?.baseUrls ?? []) {
|
|
59387
|
+
const real = unwrapUpstream2(raw);
|
|
59388
|
+
try {
|
|
59389
|
+
const url = new URL(real);
|
|
59390
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") continue;
|
|
59391
|
+
if (dshSeen.has(real)) continue;
|
|
59392
|
+
dshSeen.add(real);
|
|
59393
|
+
anon += 1;
|
|
59394
|
+
rewriteKeys.add(`dsh-${anon}`);
|
|
59395
|
+
httpRewrites.push({ key: `dsh-${anon}`, realUpstream: real });
|
|
59396
|
+
} catch {
|
|
59397
|
+
}
|
|
59398
|
+
}
|
|
59319
59399
|
} else {
|
|
59320
59400
|
for (const [name, prov] of Object.entries(config.codex?.providers ?? {})) {
|
|
59321
59401
|
classify(prov.baseUrl, `model_providers.${name}.base_url`);
|
|
@@ -59356,7 +59436,7 @@ function launcherDirectUrl(env) {
|
|
|
59356
59436
|
return env.BILI_LAUNCHER_DIRECT === "1";
|
|
59357
59437
|
}
|
|
59358
59438
|
function launcherInjectMcp(env, base) {
|
|
59359
|
-
return base !== "pi" && base !== "omp" && base !== "opencode" && base !== "hermes" && env.BILI_LAUNCHER_PLUGIN === "1";
|
|
59439
|
+
return base !== "pi" && base !== "omp" && base !== "opencode" && base !== "hermes" && base !== "dsh" && env.BILI_LAUNCHER_PLUGIN === "1";
|
|
59360
59440
|
}
|
|
59361
59441
|
function buildMcpConfig(origin) {
|
|
59362
59442
|
const script = selfDistFile("mcp.js");
|
|
@@ -59705,6 +59785,34 @@ function prepareHermesHome(hermesHome, origin, rewrites) {
|
|
|
59705
59785
|
writeOverlayFileAtomic(overlay, "config.yaml", lines.join(eol));
|
|
59706
59786
|
return overlay;
|
|
59707
59787
|
}
|
|
59788
|
+
function prepareDshHome(dshHome, origin, rewrites) {
|
|
59789
|
+
const cfgPath = path13.join(dshHome, "settings.yaml");
|
|
59790
|
+
let txt;
|
|
59791
|
+
try {
|
|
59792
|
+
txt = fs10.readFileSync(cfgPath, "utf8");
|
|
59793
|
+
} catch {
|
|
59794
|
+
return void 0;
|
|
59795
|
+
}
|
|
59796
|
+
const wrapSet = new Set(rewrites.map((r) => r.realUpstream));
|
|
59797
|
+
const eol = txt.includes("\r\n") ? "\r\n" : "\n";
|
|
59798
|
+
const lines = txt.split(/\r?\n/);
|
|
59799
|
+
let changed = false;
|
|
59800
|
+
for (let i = 0; i < lines.length; i++) {
|
|
59801
|
+
const m2 = /^(\s*(?:baseURL|baseUrl|base_url):\s*)(\S+)(\s+#.*)?$/.exec(lines[i]);
|
|
59802
|
+
if (!m2) continue;
|
|
59803
|
+
const rawUrl = m2[2].replace(/^["']|["']$/g, "");
|
|
59804
|
+
if (!/^https?:\/\//i.test(rawUrl)) continue;
|
|
59805
|
+
const real = unwrapUpstream2(rawUrl);
|
|
59806
|
+
if (!wrapSet.has(real)) continue;
|
|
59807
|
+
lines[i] = `${m2[1]}${wrapUpstream(origin, real)}${m2[3] ?? ""}`;
|
|
59808
|
+
changed = true;
|
|
59809
|
+
}
|
|
59810
|
+
if (!changed) return void 0;
|
|
59811
|
+
const overlay = `${dshHome}-bili`;
|
|
59812
|
+
if (!refreshOverlayHome(dshHome, overlay, "settings.yaml")) return void 0;
|
|
59813
|
+
writeOverlayFileAtomic(overlay, "settings.yaml", lines.join(eol));
|
|
59814
|
+
return overlay;
|
|
59815
|
+
}
|
|
59708
59816
|
function prepareOpencodeHttpRewrite(configFile2, origin, httpRewrites, httpsRewrites, pluginPath) {
|
|
59709
59817
|
if (httpRewrites.length === 0 && httpsRewrites.length === 0 && !pluginPath) return void 0;
|
|
59710
59818
|
let root = {};
|
|
@@ -59944,6 +60052,7 @@ async function runLaunch(params, deps = {}) {
|
|
|
59944
60052
|
let ompOverlayHome;
|
|
59945
60053
|
let opencodeTmpFile;
|
|
59946
60054
|
let hermesOverlayHome;
|
|
60055
|
+
let dshOverlayHome;
|
|
59947
60056
|
const tmpFiles = [];
|
|
59948
60057
|
const directUrl = launcherDirectUrl(process.env);
|
|
59949
60058
|
if (directUrl) {
|
|
@@ -59990,6 +60099,21 @@ async function runLaunch(params, deps = {}) {
|
|
|
59990
60099
|
routes.httpRewrites.length === 0 ? "bili: no hermes providers found in ~/.hermes/config.yaml \u2014 traffic will NOT go through the proxy (configure a provider first)." : "bili: hermes config.yaml could not be rewritten (unreadable or no matching provider endpoints) \u2014 traffic will NOT go through the proxy."
|
|
59991
60100
|
);
|
|
59992
60101
|
}
|
|
60102
|
+
} else if (base === "dsh") {
|
|
60103
|
+
env = { ...process.env, BILLION_CONTEXT_PROXY: origin };
|
|
60104
|
+
env.DEEPSEEK_BASE_URL = wrapUpstream(origin, "https://api.deepseek.com");
|
|
60105
|
+
dshOverlayHome = prepareDshHome(resolveDshHome(process.env), origin, routes.httpRewrites);
|
|
60106
|
+
if (dshOverlayHome) {
|
|
60107
|
+
env.DSH_HOME = dshOverlayHome;
|
|
60108
|
+
} else if (routes.httpRewrites.length > 0) {
|
|
60109
|
+
console.error(
|
|
60110
|
+
"bili: dsh settings.yaml could not be rewritten (unreadable or no matching endpoints) \u2014 custom providers will NOT go through the proxy; the built-in deepseek route still does."
|
|
60111
|
+
);
|
|
60112
|
+
} else {
|
|
60113
|
+
console.error(
|
|
60114
|
+
"bili: no custom providers found in ~/.dsh/settings.yaml \u2014 proxying the built-in deepseek route via DEEPSEEK_BASE_URL only."
|
|
60115
|
+
);
|
|
60116
|
+
}
|
|
59993
60117
|
} else if (base === "codex") {
|
|
59994
60118
|
const codexConversationId = injectMcp ? randomUUID2() : void 0;
|
|
59995
60119
|
if (directUrl) {
|
|
@@ -60257,6 +60381,7 @@ Usage:
|
|
|
60257
60381
|
bili omp [opts --] [args] start a proxy + launch omp against it (cert-MITM)
|
|
60258
60382
|
bili opencode [opts --] [args] start a proxy + launch opencode against it (cert-MITM)
|
|
60259
60383
|
bili hermes [opts --] [args] start a proxy + launch hermes-agent against it (/bili/ rewrite)
|
|
60384
|
+
bili dsh [opts --] [args] start a proxy + launch deepseek-harness against it (/bili/ rewrite)
|
|
60260
60385
|
bili test pi non-polluting pi smoke test through the proxy
|
|
60261
60386
|
bili export [session] [--full] list sessions / export one as a Markdown handoff
|
|
60262
60387
|
(--full includes original messages; --output FILE)
|
|
@@ -60271,7 +60396,7 @@ Usage:
|
|
|
60271
60396
|
bili --version print version
|
|
60272
60397
|
bili --help show this help
|
|
60273
60398
|
|
|
60274
|
-
Launcher (bili pi / bili codex / bili claude / bili omp / bili opencode / bili hermes):
|
|
60399
|
+
Launcher (bili pi / bili codex / bili claude / bili omp / bili opencode / bili hermes / bili dsh):
|
|
60275
60400
|
Brings up a proxy on an independent port (a fresh instance every launch), then runs the client pointed at it via HTTPS_PROXY + the proxy's
|
|
60276
60401
|
MITM CA \u2014 no config-file edits. Discovered HTTPS upstream domains are
|
|
60277
60402
|
auto-whitelisted for MITM so the proxy TLS-terminates exactly the hosts the
|
|
@@ -60284,6 +60409,7 @@ Launcher (bili pi / bili codex / bili claude / bili omp / bili opencode / bili h
|
|
|
60284
60409
|
bili claude # launch claude through the proxy
|
|
60285
60410
|
bili omp # launch omp through the proxy (pi-based; /bili/ rewrite)
|
|
60286
60411
|
bili hermes # launch hermes-agent through the proxy (/bili/ rewrite of ~/.hermes/config.yaml)
|
|
60412
|
+
bili dsh --profile web "task" # launch deepseek-harness through the proxy (/bili/ rewrite of ~/.dsh/settings.yaml)
|
|
60287
60413
|
bili test pi # quick end-to-end check of the pi path
|
|
60288
60414
|
bili pi --mitm-domain api.foo.com # add a domain to the MITM whitelist
|
|
60289
60415
|
|