billion-context 0.1.82 → 0.1.83-pr.399.349
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 +12 -1
- package/README.zh-CN.md +12 -1
- package/dist/index.js +125 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,6 +112,17 @@ turn is a semantic mismatch the model tolerates (it is clearly marked
|
|
|
112
112
|
summaries (their tool call isn't in the agent's history and the agent's view
|
|
113
113
|
skips `acp_summary`) — but that needs the id match above, which doesn't occur.
|
|
114
114
|
|
|
115
|
+
## Which do I need?
|
|
116
|
+
|
|
117
|
+
Pick by your client:
|
|
118
|
+
|
|
119
|
+
| Client | Use |
|
|
120
|
+
|---|---|
|
|
121
|
+
| **pi** | [`billion-context-pi`](https://github.com/ranxianglei/billion-context-pi) (in-process extension) |
|
|
122
|
+
| **opencode** | [`opencode-acp`](https://github.com/ranxianglei/opencode-acp) (in-process extension) |
|
|
123
|
+
| **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili omp` (built-in plugin) |
|
|
124
|
+
| **everything else** (no context hook) | [`billion-context`](https://github.com/ranxianglei/billion-context) — `bili <client>` (launcher, preferred) or `/bili/` prefix |
|
|
125
|
+
|
|
115
126
|
## Install
|
|
116
127
|
|
|
117
128
|
```bash
|
|
@@ -382,7 +393,7 @@ pass an explicit `x-acp-session` header per conversation to avoid collisions.
|
|
|
382
393
|
|
|
383
394
|
Early. Protocol handling and compression work against mock tests (500+ passing). Real-model integration testing is the next milestone. Expect rough edges.
|
|
384
395
|
|
|
385
|
-
|
|
396
|
+
Client-side plugins for pi / omp / opencode ship inside `billion-context` (`dist/agent/*.js`) for the cooperative-proxy path. See the **"Which do I need?"** section above for how `billion-context`, the standalone `billion-context-pi`, and `opencode-acp` relate.
|
|
386
397
|
|
|
387
398
|
## License
|
|
388
399
|
|
package/README.zh-CN.md
CHANGED
|
@@ -50,6 +50,17 @@ AI 编程助手的<strong>通用上下文压缩代理</strong>
|
|
|
50
50
|
|
|
51
51
|
代理向对话注入四个上下文管理工具(`compress`、`decompress`、`search_context`、`acp_status`)。模型在对话增长时调用 `compress`,代理在服务端执行 —— 压缩后的范围在下一轮之前折叠进对话历史。
|
|
52
52
|
|
|
53
|
+
## 该选哪个?
|
|
54
|
+
|
|
55
|
+
按客户端选:
|
|
56
|
+
|
|
57
|
+
| 客户端 | 用这个 |
|
|
58
|
+
|---|---|
|
|
59
|
+
| **pi** | [`billion-context-pi`](https://github.com/ranxianglei/billion-context-pi)(进程内扩展) |
|
|
60
|
+
| **opencode** | [`opencode-acp`](https://github.com/ranxianglei/opencode-acp)(进程内扩展) |
|
|
61
|
+
| **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context),`bili omp`(内置插件) |
|
|
62
|
+
| **其余所有**(没有上下文 hook) | [`billion-context`](https://github.com/ranxianglei/billion-context) —— `bili <client>`(启动器,优先)或 `/bili/` 前缀 |
|
|
63
|
+
|
|
53
64
|
## 安装
|
|
54
65
|
|
|
55
66
|
```bash
|
|
@@ -258,7 +269,7 @@ Windows 下会自动发现常见 Clash/Mihomo 静态系统代理;Web UI 会显
|
|
|
258
269
|
|
|
259
270
|
早期。协议处理和压缩已通过 mock 测试(500+ 项通过)。真实模型集成测试是下一里程碑。预期会有粗糙的地方。
|
|
260
271
|
|
|
261
|
-
pi
|
|
272
|
+
针对 pi / omp / opencode 的客户端插件随 `billion-context` 一起发布(`dist/agent/*.js`),用于协作代理路径。三者(`billion-context`、独立的 `billion-context-pi`、`opencode-acp`)如何取舍,见上文「该选哪个?」一节。
|
|
262
273
|
|
|
263
274
|
## 许可证
|
|
264
275
|
|
package/dist/index.js
CHANGED
|
@@ -43476,10 +43476,6 @@ function defaultCountTokens(text) {
|
|
|
43476
43476
|
const cjkCount = cjk?.length ?? 0;
|
|
43477
43477
|
return cjkCount + Math.ceil((text.length - cjkCount) / 4);
|
|
43478
43478
|
}
|
|
43479
|
-
function estimateTokensFast(text) {
|
|
43480
|
-
if (!text) return 0;
|
|
43481
|
-
return Math.ceil(text.length / 4);
|
|
43482
|
-
}
|
|
43483
43479
|
var COMPRESS_PHILOSOPHY = `Compression Philosophy:
|
|
43484
43480
|
- All compression serves the primary task, but be frugal.
|
|
43485
43481
|
- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
|
|
@@ -48360,33 +48356,64 @@ async function contextFromRegistry(model, host) {
|
|
|
48360
48356
|
function peekRegistryContext(model, host) {
|
|
48361
48357
|
return registryLookup(cache2, model, host);
|
|
48362
48358
|
}
|
|
48359
|
+
var VARIANT_SUFFIXES = [
|
|
48360
|
+
"-interleaved-thinking",
|
|
48361
|
+
"-thinking",
|
|
48362
|
+
"-reasoning",
|
|
48363
|
+
"-extended",
|
|
48364
|
+
"-fast",
|
|
48365
|
+
"-high",
|
|
48366
|
+
"-medium",
|
|
48367
|
+
"-low"
|
|
48368
|
+
];
|
|
48369
|
+
function modelVariants(name) {
|
|
48370
|
+
const variants = [name];
|
|
48371
|
+
let current = name;
|
|
48372
|
+
for (; ; ) {
|
|
48373
|
+
let stripped = false;
|
|
48374
|
+
for (const suffix of VARIANT_SUFFIXES) {
|
|
48375
|
+
if (current.length > suffix.length && current.endsWith(suffix)) {
|
|
48376
|
+
current = current.slice(0, -suffix.length);
|
|
48377
|
+
variants.push(current);
|
|
48378
|
+
stripped = true;
|
|
48379
|
+
break;
|
|
48380
|
+
}
|
|
48381
|
+
}
|
|
48382
|
+
if (!stripped) break;
|
|
48383
|
+
}
|
|
48384
|
+
return variants;
|
|
48385
|
+
}
|
|
48363
48386
|
function registryLookup(reg, model, host) {
|
|
48364
48387
|
if (!reg || !model) return void 0;
|
|
48365
48388
|
const provider = host ? providerFromHost(host) : void 0;
|
|
48366
|
-
const
|
|
48367
|
-
|
|
48368
|
-
const
|
|
48369
|
-
|
|
48370
|
-
|
|
48371
|
-
|
|
48372
|
-
|
|
48373
|
-
|
|
48374
|
-
|
|
48375
|
-
|
|
48376
|
-
|
|
48377
|
-
|
|
48378
|
-
|
|
48379
|
-
|
|
48380
|
-
|
|
48381
|
-
|
|
48382
|
-
|
|
48383
|
-
|
|
48384
|
-
|
|
48385
|
-
|
|
48386
|
-
|
|
48387
|
-
|
|
48389
|
+
for (const name of modelVariants(model)) {
|
|
48390
|
+
const candidates = provider ? [`${provider}/${name}`, name] : [name];
|
|
48391
|
+
for (const key of candidates) {
|
|
48392
|
+
const entry = reg[key];
|
|
48393
|
+
const ctx = entry?.limit?.context;
|
|
48394
|
+
if (typeof ctx === "number" && ctx > 0) return ctx;
|
|
48395
|
+
}
|
|
48396
|
+
if (provider === void 0) {
|
|
48397
|
+
const suffix = `/${name}`;
|
|
48398
|
+
let max;
|
|
48399
|
+
const distinct = /* @__PURE__ */ new Set();
|
|
48400
|
+
const parts = [];
|
|
48401
|
+
for (const key of Object.keys(reg)) {
|
|
48402
|
+
if (!key.endsWith(suffix)) continue;
|
|
48403
|
+
const ctx = reg[key].limit?.context;
|
|
48404
|
+
if (typeof ctx !== "number" || ctx <= 0) continue;
|
|
48405
|
+
if (max === void 0 || ctx > max) max = ctx;
|
|
48406
|
+
distinct.add(ctx);
|
|
48407
|
+
parts.push(`${key}=${ctx}`);
|
|
48408
|
+
}
|
|
48409
|
+
if (max !== void 0) {
|
|
48410
|
+
if (distinct.size > 1 && !warnedConflicts.has(name)) {
|
|
48411
|
+
warnedConflicts.add(name);
|
|
48412
|
+
log("warn", `[acp-registry] conflicting context windows for "${name}" (${parts.join(", ")}) \u2014 using max ${max}`);
|
|
48413
|
+
}
|
|
48414
|
+
return max;
|
|
48415
|
+
}
|
|
48388
48416
|
}
|
|
48389
|
-
return max;
|
|
48390
48417
|
}
|
|
48391
48418
|
return void 0;
|
|
48392
48419
|
}
|
|
@@ -50581,7 +50608,7 @@ function executeAnthropicProxyTool(toolName, args, ctx) {
|
|
|
50581
50608
|
${lines.join("\n\n")}`;
|
|
50582
50609
|
}
|
|
50583
50610
|
if (toolName === "acp_status") {
|
|
50584
|
-
return buildStatusReport(ctx.session.state, ctx.messages,
|
|
50611
|
+
return buildStatusReport(ctx.session.state, ctx.messages, defaultCountTokens);
|
|
50585
50612
|
}
|
|
50586
50613
|
return `[Unknown proxy tool: ${toolName}]`;
|
|
50587
50614
|
}
|
|
@@ -51702,7 +51729,7 @@ function handleAcpStatus(args, ctx) {
|
|
|
51702
51729
|
const tool = typeof args.tool === "string" ? args.tool : void 0;
|
|
51703
51730
|
const sort = typeof args.sort === "string" ? args.sort : void 0;
|
|
51704
51731
|
const limit = typeof args.limit === "number" ? args.limit : void 0;
|
|
51705
|
-
const base = buildStatusReport(ctx.session.state, ctx.messages,
|
|
51732
|
+
const base = buildStatusReport(ctx.session.state, ctx.messages, defaultCountTokens, { scope, view, tool, sort, limit });
|
|
51706
51733
|
if (scope) return base;
|
|
51707
51734
|
const nudge = ctx.nudge;
|
|
51708
51735
|
const ranges = nudge?.compressibleRanges ?? [];
|
|
@@ -53547,7 +53574,7 @@ function executeProxyTool2(toolName, args, ctx) {
|
|
|
53547
53574
|
${lines.join("\n\n")}`;
|
|
53548
53575
|
}
|
|
53549
53576
|
if (toolName === "acp_status") {
|
|
53550
|
-
return buildStatusReport(ctx.session.state, ctx.messages,
|
|
53577
|
+
return buildStatusReport(ctx.session.state, ctx.messages, defaultCountTokens);
|
|
53551
53578
|
}
|
|
53552
53579
|
return `[Unknown proxy tool: ${toolName}]`;
|
|
53553
53580
|
}
|
|
@@ -54705,7 +54732,7 @@ function handlePluginStatus(conversationId2, res, fallbackLatest = false) {
|
|
|
54705
54732
|
if (entry) entry.lastSeen = Date.now();
|
|
54706
54733
|
const limit = session.metadata.effectiveContextLimit;
|
|
54707
54734
|
const mem = remembered.get(session.id);
|
|
54708
|
-
const modelContextLimit = typeof limit === "number" && limit > 0 ? limit :
|
|
54735
|
+
const modelContextLimit = typeof limit === "number" && limit > 0 ? limit : 0;
|
|
54709
54736
|
let panel;
|
|
54710
54737
|
try {
|
|
54711
54738
|
panel = buildStatusPanel({
|
|
@@ -56729,6 +56756,9 @@ function logUnrecognizedPath(log2, url) {
|
|
|
56729
56756
|
log2("info", `unrecognized path ${key}: forwarding unchanged; further occurrences suppressed`);
|
|
56730
56757
|
}
|
|
56731
56758
|
}
|
|
56759
|
+
function isModelDiscoveryPath(path18) {
|
|
56760
|
+
return path18.replace(/\/+$/, "").endsWith("/models");
|
|
56761
|
+
}
|
|
56732
56762
|
var BILI_HOP_HEADER = "x-bili-hop";
|
|
56733
56763
|
function parseLauncherModelWindows(raw) {
|
|
56734
56764
|
if (!raw) return {};
|
|
@@ -56930,7 +56960,7 @@ Content-Length: ${Buffer.byteLength(body)}\r
|
|
|
56930
56960
|
);
|
|
56931
56961
|
}
|
|
56932
56962
|
if (opts.debug) {
|
|
56933
|
-
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)`);
|
|
56963
|
+
log2("info", `[debug] build features: raw-HTTP-capture(${bodyDumpEnabled() ? "on" : "off"}) | 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)`);
|
|
56934
56964
|
}
|
|
56935
56965
|
};
|
|
56936
56966
|
const attemptListen = (port) => {
|
|
@@ -56998,6 +57028,13 @@ Content-Length: ${Buffer.byteLength(body)}\r
|
|
|
56998
57028
|
}
|
|
56999
57029
|
return server;
|
|
57000
57030
|
}
|
|
57031
|
+
var SIDE_REQUEST_MAX_TOKENS = 200;
|
|
57032
|
+
function isSideRequest(parsed) {
|
|
57033
|
+
if (!parsed || typeof parsed !== "object") return false;
|
|
57034
|
+
const p2 = parsed;
|
|
57035
|
+
const raw = p2.max_tokens ?? p2.max_completion_tokens ?? p2.max_output_tokens;
|
|
57036
|
+
return typeof raw === "number" && raw > 0 && raw <= SIDE_REQUEST_MAX_TOKENS;
|
|
57037
|
+
}
|
|
57001
57038
|
function isTrustedAdminOrigin(origin, host, trustedHosts) {
|
|
57002
57039
|
if (!host || !trustedHosts.has(host.toLowerCase())) return false;
|
|
57003
57040
|
if (!origin) return true;
|
|
@@ -57274,11 +57311,11 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57274
57311
|
const pluginWindow = pluginReportedContextWindow(req.headers);
|
|
57275
57312
|
const launcherWindow = launcherContextWindow(model);
|
|
57276
57313
|
const configuredWindow = resolveConfiguredContextLimit(opts.routes, embeddedUrl, model);
|
|
57277
|
-
const peekWindow =
|
|
57314
|
+
const peekWindow = peekRegistryContext(model, host);
|
|
57278
57315
|
let native = betaWindow ?? pluginWindow ?? launcherWindow ?? configuredWindow ?? peekWindow ?? lookupContextLimit(model);
|
|
57279
57316
|
const operatorWindowTuned = resolveCompress(opts.routes, embeddedUrl, model, opts.compress).modelContextLimit !== void 0;
|
|
57280
57317
|
nativeFromFallback = !betaWindow && !pluginWindow && !launcherWindow && !peekWindow && !configuredWindow && !operatorWindowTuned;
|
|
57281
|
-
if (!native
|
|
57318
|
+
if (!native) {
|
|
57282
57319
|
native = await contextFromRegistry(model, host);
|
|
57283
57320
|
if (native) nativeFromFallback = false;
|
|
57284
57321
|
}
|
|
@@ -57379,7 +57416,6 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57379
57416
|
if (pluginAgent && !pluginConversation) pluginConversation = conversation;
|
|
57380
57417
|
if (pluginAgent) {
|
|
57381
57418
|
if (session.metadata.pluginAgent !== pluginAgent) session.metadata.pluginAgent = pluginAgent;
|
|
57382
|
-
session.metadata.effectiveContextLimit = reqConfig.modelContextLimit;
|
|
57383
57419
|
recordPluginSession(pluginConversation ?? conversation, session.id);
|
|
57384
57420
|
}
|
|
57385
57421
|
if (protocol === "responses" || protocol === "openai" || protocol === "anthropic") {
|
|
@@ -57389,6 +57425,21 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57389
57425
|
}
|
|
57390
57426
|
}
|
|
57391
57427
|
const pluginMode = pluginAgent !== void 0;
|
|
57428
|
+
if (!countTokens && !responsesCompact && protocol !== null && isSideRequest(parsed)) {
|
|
57429
|
+
log2("info", `[${session.id}] side request (max_tokens<=${SIDE_REQUEST_MAX_TOKENS}) \u2192 passthrough + tag strip only, kernel state untouched`);
|
|
57430
|
+
const sidePrepared = {
|
|
57431
|
+
body: bodyBuffer,
|
|
57432
|
+
session,
|
|
57433
|
+
processedMessages: [],
|
|
57434
|
+
originalMessages: [],
|
|
57435
|
+
protocol,
|
|
57436
|
+
stream: parsed.stream === true,
|
|
57437
|
+
compressInjected: false,
|
|
57438
|
+
sidePassthrough: true
|
|
57439
|
+
};
|
|
57440
|
+
await forward(req, res, opts, bodyBuffer, sidePrepared, core, reqConfig, log2, route, instanceId, affinity);
|
|
57441
|
+
return;
|
|
57442
|
+
}
|
|
57392
57443
|
const reqModel = parsed.model;
|
|
57393
57444
|
const learnedMap = session.metadata.learnedContextLimits;
|
|
57394
57445
|
const learnedLimit = (reqModel && learnedMap ? learnedMap[reqModel] : void 0) ?? session.metadata.learnedContextLimit;
|
|
@@ -57397,6 +57448,22 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57397
57448
|
reqConfig = { ...reqConfig, modelContextLimit: learnedLimit };
|
|
57398
57449
|
nativeFromFallback = false;
|
|
57399
57450
|
log2("info", `[${session.id}] self-healed context window: ${resolved} \u2192 ${learnedLimit} (learned from an upstream overflow)`);
|
|
57451
|
+
} else if (nativeFromFallback && reqModel) {
|
|
57452
|
+
const prevInput = session.stats.lastInputTokens ?? 0;
|
|
57453
|
+
const prevWindow = session.metadata.lastTurnWindow;
|
|
57454
|
+
const resolved = reqConfig.modelContextLimit;
|
|
57455
|
+
if (prevWindow !== void 0 && prevInput > prevWindow && prevInput > resolved && prevInput >= 1e3) {
|
|
57456
|
+
const map = session.metadata.learnedContextLimits ?? {};
|
|
57457
|
+
const prev = map[reqModel];
|
|
57458
|
+
if (prev === void 0 || prevInput > prev) {
|
|
57459
|
+
map[reqModel] = prevInput;
|
|
57460
|
+
session.metadata.learnedContextLimits = map;
|
|
57461
|
+
markDirty(session);
|
|
57462
|
+
}
|
|
57463
|
+
reqConfig = { ...reqConfig, modelContextLimit: prevInput };
|
|
57464
|
+
nativeFromFallback = false;
|
|
57465
|
+
log2("info", `[${session.id}] self-healed context window upward: ${resolved} \u2192 ${prevInput} (a prior turn used ${prevInput} input tokens, exceeding the fallback window)`);
|
|
57466
|
+
}
|
|
57400
57467
|
}
|
|
57401
57468
|
const nativeWindow = reqConfig.modelContextLimit;
|
|
57402
57469
|
if (shouldReserveOutputHeadroom(protocol)) {
|
|
@@ -57410,6 +57477,8 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57410
57477
|
}
|
|
57411
57478
|
if (reserved !== reqConfig.modelContextLimit) reqConfig = { ...reqConfig, modelContextLimit: reserved };
|
|
57412
57479
|
}
|
|
57480
|
+
session.metadata.effectiveContextLimit = reqConfig.modelContextLimit;
|
|
57481
|
+
session.metadata.lastTurnWindow = reqConfig.modelContextLimit;
|
|
57413
57482
|
acquireInFlight(session);
|
|
57414
57483
|
try {
|
|
57415
57484
|
await withSessionLock(session, async () => {
|
|
@@ -57474,7 +57543,7 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57474
57543
|
}
|
|
57475
57544
|
}
|
|
57476
57545
|
if (!prepared) {
|
|
57477
|
-
if (protocol === null && !opts.passthrough) {
|
|
57546
|
+
if (protocol === null && !opts.passthrough && !isModelDiscoveryPath(urlPath)) {
|
|
57478
57547
|
logUnrecognizedPath(log2, req.url ?? "");
|
|
57479
57548
|
}
|
|
57480
57549
|
await forward(req, res, opts, bodyBuffer, null, core, reqConfig, log2, route, instanceId, void 0);
|
|
@@ -58311,7 +58380,7 @@ ${hdrText}
|
|
|
58311
58380
|
}
|
|
58312
58381
|
let responseBody = upstream.body;
|
|
58313
58382
|
try {
|
|
58314
|
-
if (prepared !== null && prepared.stream && maxFakeCompletionRetries() > 0) {
|
|
58383
|
+
if (prepared !== null && prepared.stream && !prepared.sidePassthrough && maxFakeCompletionRetries() > 0) {
|
|
58315
58384
|
const resolvedBuf = await resolveFakeCompletion(upstream.body, {
|
|
58316
58385
|
protocol: prepared.protocol,
|
|
58317
58386
|
body,
|
|
@@ -62631,7 +62700,6 @@ function selfDistFile(name) {
|
|
|
62631
62700
|
return path15.join(selfPackageRoot(), "dist", name);
|
|
62632
62701
|
}
|
|
62633
62702
|
var LAUNCHER_DEFAULT_HOST = "127.0.0.1";
|
|
62634
|
-
var LAUNCHER_DEFAULT_PORT = 8787;
|
|
62635
62703
|
var LAUNCH_CLIENTS = ["pi", "codex", "claude", "omp", "opencode", "hermes", "dsh", "pi-test"];
|
|
62636
62704
|
var HEALTH_PATH = "/__bili/health";
|
|
62637
62705
|
var HEALTH_POLL_INTERVAL_MS = 200;
|
|
@@ -63805,6 +63873,19 @@ async function probeExistingInstance(readInstance, fetchHealthInfo) {
|
|
|
63805
63873
|
if (health.instanceId !== void 0 && health.instanceId !== inst.instanceId) return void 0;
|
|
63806
63874
|
return inst;
|
|
63807
63875
|
}
|
|
63876
|
+
function pickEphemeralPort(host = LAUNCHER_DEFAULT_HOST) {
|
|
63877
|
+
return new Promise((resolve, reject) => {
|
|
63878
|
+
const srv = net2.createServer();
|
|
63879
|
+
srv.once("error", reject);
|
|
63880
|
+
srv.listen(0, host, () => {
|
|
63881
|
+
const addr = srv.address();
|
|
63882
|
+
srv.close(() => {
|
|
63883
|
+
if (addr && typeof addr === "object") resolve(addr.port);
|
|
63884
|
+
else reject(new Error("could not allocate a free port"));
|
|
63885
|
+
});
|
|
63886
|
+
});
|
|
63887
|
+
});
|
|
63888
|
+
}
|
|
63808
63889
|
var INHERITED_PROXY_VARS = ["http_proxy", "https_proxy", "all_proxy", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"];
|
|
63809
63890
|
function stripInheritedProxy(env) {
|
|
63810
63891
|
const cleaned = { ...env };
|
|
@@ -63830,7 +63911,7 @@ async function ensureProxyRunning(opts, deps = {}) {
|
|
|
63830
63911
|
return { origin: existing.origin, port: existing.port, attached: true };
|
|
63831
63912
|
}
|
|
63832
63913
|
const launchToken = randomUUID5();
|
|
63833
|
-
const port = opts.port;
|
|
63914
|
+
const port = opts.port > 0 ? opts.port : await pickEphemeralPort(opts.host);
|
|
63834
63915
|
const script = process.argv[1];
|
|
63835
63916
|
if (!script) throw new Error("bili: cannot resolve launcher script path");
|
|
63836
63917
|
const logPath2 = path15.join(os5.tmpdir(), `bili-proxy-${port}.log`);
|
|
@@ -63839,7 +63920,7 @@ async function ensureProxyRunning(opts, deps = {}) {
|
|
|
63839
63920
|
try {
|
|
63840
63921
|
child = spawnImpl(
|
|
63841
63922
|
process.execPath,
|
|
63842
|
-
[script, ...proxyStartArgs(opts)],
|
|
63923
|
+
[script, ...proxyStartArgs({ ...opts, port })],
|
|
63843
63924
|
{
|
|
63844
63925
|
detached: true,
|
|
63845
63926
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -63946,8 +64027,9 @@ function resolveClientCommand(client, env) {
|
|
|
63946
64027
|
return { command: resolved ?? client, prefixArgs: [] };
|
|
63947
64028
|
}
|
|
63948
64029
|
function parsePort(raw) {
|
|
63949
|
-
|
|
63950
|
-
|
|
64030
|
+
if (!raw || !raw.trim()) return 0;
|
|
64031
|
+
const port = parseInt(raw, 10);
|
|
64032
|
+
if (!Number.isFinite(port) || port < 1 || port > 65535) {
|
|
63951
64033
|
console.error(`bili: invalid --port "${raw}"`);
|
|
63952
64034
|
process.exit(2);
|
|
63953
64035
|
}
|
|
@@ -64386,7 +64468,7 @@ Launcher (bili pi / bili codex / bili claude / bili omp / bili opencode / bili h
|
|
|
64386
64468
|
Options (override config file / env):
|
|
64387
64469
|
-F <url> upstream proxy to forward through (gost-style;
|
|
64388
64470
|
http://host:port; must precede the client name)
|
|
64389
|
-
--port <N> listen port (default
|
|
64471
|
+
--port <N> listen port (start: 8787; launcher default: random free port)
|
|
64390
64472
|
--host <ADDR> listen host (default 127.0.0.1)
|
|
64391
64473
|
--mitm-domain <domain> extra MITM domain (repeatable; launcher only)
|
|
64392
64474
|
--config <FILE> path to config JSON (default: XDG location)
|