billion-context 0.1.83 → 0.1.84-pr.370.354
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 +130 -32
- 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
|
@@ -49370,6 +49370,7 @@ function subagentNamespace(identityValue, instructions) {
|
|
|
49370
49370
|
|
|
49371
49371
|
// src/persist.ts
|
|
49372
49372
|
import { createHash as createHash3 } from "crypto";
|
|
49373
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
49373
49374
|
import { rm } from "fs/promises";
|
|
49374
49375
|
import * as path5 from "path";
|
|
49375
49376
|
|
|
@@ -49806,6 +49807,7 @@ function rel(p2, base) {
|
|
|
49806
49807
|
|
|
49807
49808
|
// src/persist.ts
|
|
49808
49809
|
var PERSIST_VERSION = 3;
|
|
49810
|
+
var MIGRATION_MARKER = ".bili-migration-286.done";
|
|
49809
49811
|
function mergeState(parsed) {
|
|
49810
49812
|
const fresh = createInitialState();
|
|
49811
49813
|
return {
|
|
@@ -49865,6 +49867,21 @@ var SessionStore = class {
|
|
|
49865
49867
|
}
|
|
49866
49868
|
return out;
|
|
49867
49869
|
}
|
|
49870
|
+
/** Single-pass boot (#401): ONE loadAll walk+parse, then the #286
|
|
49871
|
+
* identity migration over the SAME parsed map (no extra directory
|
|
49872
|
+
* walk), then hydration into Sessions. initSessions calls this instead
|
|
49873
|
+
* of migrateLegacyIds()+loadAll(), which walked and parsed the whole
|
|
49874
|
+
* tree twice per start. */
|
|
49875
|
+
async boot() {
|
|
49876
|
+
if (!this.enabled) return /* @__PURE__ */ new Map();
|
|
49877
|
+
const loaded = await this.store.loadAll();
|
|
49878
|
+
await this.applyLegacyMigration(loaded);
|
|
49879
|
+
const out = /* @__PURE__ */ new Map();
|
|
49880
|
+
for (const [id, envelope] of loaded) {
|
|
49881
|
+
out.set(id, buildSession(envelope.payload));
|
|
49882
|
+
}
|
|
49883
|
+
return out;
|
|
49884
|
+
}
|
|
49868
49885
|
/** One-time migration for the #286 identity change: sessions persisted
|
|
49869
49886
|
* under the old derived hash id are re-keyed to the client-provided
|
|
49870
49887
|
* conversation value stored in meta.label (which is now the session id
|
|
@@ -49873,10 +49890,21 @@ var SessionStore = class {
|
|
|
49873
49890
|
* session, are deleted. Records without a label cannot be mapped and are
|
|
49874
49891
|
* left in place (they load under their old id but are never requested
|
|
49875
49892
|
* again — the new proxy 400s anonymous requests). Self-terminating:
|
|
49876
|
-
* after one pass no loaded id differs from its label.
|
|
49893
|
+
* after one pass no loaded id differs from its label. A completion
|
|
49894
|
+
* marker makes it run ONCE EVER (#401): the old code re-scanned the tree
|
|
49895
|
+
* on every boot because unlabeled files are intentionally kept, so the
|
|
49896
|
+
* "one-time" log line repeated forever. */
|
|
49877
49897
|
async migrateLegacyIds() {
|
|
49878
49898
|
if (!this.enabled) return;
|
|
49899
|
+
if (existsSync3(this.markerPath())) return;
|
|
49879
49900
|
const loaded = await this.store.loadAll();
|
|
49901
|
+
await this.applyLegacyMigration(loaded);
|
|
49902
|
+
}
|
|
49903
|
+
markerPath() {
|
|
49904
|
+
return path5.join(this.dir, MIGRATION_MARKER);
|
|
49905
|
+
}
|
|
49906
|
+
async applyLegacyMigration(loaded) {
|
|
49907
|
+
if (existsSync3(this.markerPath())) return;
|
|
49880
49908
|
const claimed = /* @__PURE__ */ new Set();
|
|
49881
49909
|
const byLabel = /* @__PURE__ */ new Map();
|
|
49882
49910
|
let unlabeled = 0;
|
|
@@ -49894,24 +49922,39 @@ var SessionStore = class {
|
|
|
49894
49922
|
}
|
|
49895
49923
|
const prev = byLabel.get(label);
|
|
49896
49924
|
if (!prev || envelope.savedAt >= prev.savedAt) {
|
|
49897
|
-
if (prev)
|
|
49925
|
+
if (prev) {
|
|
49926
|
+
await this.removeLegacyFile(prev.id, prev.session);
|
|
49927
|
+
loaded.delete(prev.id);
|
|
49928
|
+
}
|
|
49898
49929
|
byLabel.set(label, { id, savedAt: envelope.savedAt, session });
|
|
49899
49930
|
} else {
|
|
49900
49931
|
await this.removeLegacyFile(id, session);
|
|
49932
|
+
loaded.delete(id);
|
|
49901
49933
|
}
|
|
49902
49934
|
}
|
|
49903
49935
|
let rekeyed = 0;
|
|
49904
49936
|
for (const [label, { id, session }] of byLabel) {
|
|
49905
49937
|
if (claimed.has(label)) {
|
|
49906
49938
|
await this.removeLegacyFile(id, session);
|
|
49939
|
+
loaded.delete(id);
|
|
49907
49940
|
continue;
|
|
49908
49941
|
}
|
|
49909
49942
|
session.id = label;
|
|
49910
49943
|
await this.store.writeNow(label, () => buildRecord(session));
|
|
49911
49944
|
await this.removeLegacyFile(id, session);
|
|
49945
|
+
const envelope = loaded.get(id);
|
|
49946
|
+
if (envelope) {
|
|
49947
|
+
loaded.set(label, { ...envelope, id: label, payload: { ...envelope.payload, id: label } });
|
|
49948
|
+
}
|
|
49949
|
+
loaded.delete(id);
|
|
49912
49950
|
claimed.add(label);
|
|
49913
49951
|
rekeyed++;
|
|
49914
49952
|
}
|
|
49953
|
+
try {
|
|
49954
|
+
mkdirSync3(this.dir, { recursive: true });
|
|
49955
|
+
writeFileSync2(this.markerPath(), String(Date.now()), "utf8");
|
|
49956
|
+
} catch {
|
|
49957
|
+
}
|
|
49915
49958
|
if (rekeyed || unlabeled) {
|
|
49916
49959
|
log("info", `[persist] one-time migration (#286): rekeyed ${rekeyed} legacy session(s), left ${unlabeled} unlabeled legacy file(s) in place`);
|
|
49917
49960
|
}
|
|
@@ -49997,10 +50040,21 @@ function buildRecord(session) {
|
|
|
49997
50040
|
createdAt: session.createdAt
|
|
49998
50041
|
};
|
|
49999
50042
|
}
|
|
50043
|
+
function isBlockView(v2) {
|
|
50044
|
+
return !!v2 && typeof v2 === "object" && typeof v2.text === "string" && typeof v2.count === "number";
|
|
50045
|
+
}
|
|
50000
50046
|
function buildSession(parsed) {
|
|
50001
50047
|
const blockContents = /* @__PURE__ */ new Map();
|
|
50002
50048
|
for (const [bid, content] of Object.entries(parsed.blockContents ?? {})) {
|
|
50003
|
-
if (content
|
|
50049
|
+
if (!content || typeof content !== "object") continue;
|
|
50050
|
+
const full = content.full;
|
|
50051
|
+
if (!isBlockView(full)) continue;
|
|
50052
|
+
const one = content.one;
|
|
50053
|
+
const oneView = isBlockView(one) ? one : null;
|
|
50054
|
+
blockContents.set(bid, {
|
|
50055
|
+
one: oneView && !(oneView.text === full.text && oneView.count === full.count) ? oneView : null,
|
|
50056
|
+
full
|
|
50057
|
+
});
|
|
50004
50058
|
}
|
|
50005
50059
|
const meta = parsed.meta ?? {};
|
|
50006
50060
|
const stats = parsed.stats ?? {};
|
|
@@ -50085,8 +50139,7 @@ async function initSessions() {
|
|
|
50085
50139
|
initialized = true;
|
|
50086
50140
|
const store = getStore();
|
|
50087
50141
|
if (!store.enabled) return;
|
|
50088
|
-
await store.
|
|
50089
|
-
const loaded = await store.loadAll();
|
|
50142
|
+
const loaded = await store.boot();
|
|
50090
50143
|
if (loaded.size > MAX_SESSIONS) {
|
|
50091
50144
|
const freshness = (s3) => Math.max(s3.createdAt ?? 0, s3.lastSeen ?? 0);
|
|
50092
50145
|
const entries = [...loaded.entries()].sort((a, b2) => freshness(b2[1]) - freshness(a[1]));
|
|
@@ -50292,7 +50345,7 @@ function withStagedCompressGuidance(text) {
|
|
|
50292
50345
|
}
|
|
50293
50346
|
|
|
50294
50347
|
// src/decompress-shared.ts
|
|
50295
|
-
import { mkdirSync as
|
|
50348
|
+
import { mkdirSync as mkdirSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
50296
50349
|
import { dirname as dirname2, join as join2 } from "path";
|
|
50297
50350
|
import { tmpdir } from "os";
|
|
50298
50351
|
var trackedTempFiles = [];
|
|
@@ -50339,7 +50392,7 @@ function resolveDecompress(args, ctx) {
|
|
|
50339
50392
|
let body;
|
|
50340
50393
|
let count;
|
|
50341
50394
|
if (cached) {
|
|
50342
|
-
const view = full ? cached.full : cached.one;
|
|
50395
|
+
const view = full ? cached.full : cached.one ?? cached.full;
|
|
50343
50396
|
body = view.text;
|
|
50344
50397
|
count = view.count;
|
|
50345
50398
|
} else {
|
|
@@ -50352,8 +50405,8 @@ function resolveDecompress(args, ctx) {
|
|
|
50352
50405
|
const outPath = body.length > 1e4 ? join2(tmpdir(), `acp-decompress-${safeBlockId}-${Date.now()}.txt`) : null;
|
|
50353
50406
|
if (outPath) {
|
|
50354
50407
|
try {
|
|
50355
|
-
|
|
50356
|
-
|
|
50408
|
+
mkdirSync4(dirname2(outPath), { recursive: true });
|
|
50409
|
+
writeFileSync3(outPath, body, "utf8");
|
|
50357
50410
|
trackedTempFiles.push({ path: outPath, mtimeMs: Date.now() });
|
|
50358
50411
|
reapTempFiles();
|
|
50359
50412
|
return `${header}
|
|
@@ -50649,8 +50702,9 @@ ${reasons.map((r) => `- ${r}`).join("\n")}` : "";
|
|
|
50649
50702
|
const full = collectBlockContent(res.state, b2, ctx.messages, { full: true });
|
|
50650
50703
|
const one = collectBlockContent(res.state, b2, ctx.messages, { full: false });
|
|
50651
50704
|
if (full.count > 0 || one.count > 0) {
|
|
50705
|
+
const sameView = one.text === full.text && one.count === full.count;
|
|
50652
50706
|
cacheBlockContent(ctx.session, b2.blockId, {
|
|
50653
|
-
one: { text: one.text, count: one.count },
|
|
50707
|
+
one: sameView ? null : { text: one.text, count: one.count },
|
|
50654
50708
|
full: { text: full.text, count: full.count }
|
|
50655
50709
|
});
|
|
50656
50710
|
}
|
|
@@ -51032,7 +51086,7 @@ import { dirname as dirname4, join as join3 } from "path";
|
|
|
51032
51086
|
import { fileURLToPath } from "url";
|
|
51033
51087
|
|
|
51034
51088
|
// src/web/page.ts
|
|
51035
|
-
import { existsSync as
|
|
51089
|
+
import { existsSync as existsSync4 } from "fs";
|
|
51036
51090
|
|
|
51037
51091
|
// src/ca.ts
|
|
51038
51092
|
var import_node_forge = __toESM(require_lib(), 1);
|
|
@@ -51216,7 +51270,7 @@ function escapeHtml(value) {
|
|
|
51216
51270
|
function renderPage(origin, version2) {
|
|
51217
51271
|
const caPath = rootCaPath();
|
|
51218
51272
|
const caPathEsc = escapeHtml(caPath);
|
|
51219
|
-
const caReady =
|
|
51273
|
+
const caReady = existsSync4(caPath);
|
|
51220
51274
|
return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>billion-context</title><style>${WEB_STYLES}</style></head><body>
|
|
51221
51275
|
<div class="app"><aside class="sidebar"><div class="brand"><div class="brand-title">billion<span>-context</span></div><div class="version">v${version2}</div></div><a class="fork-ribbon" href="https://github.com/ranxianglei/billion-context" target="_blank" rel="noopener">\u2605 Fork me on GitHub</a><nav class="nav"><button class="active" data-page="overview">\u6982\u89C8</button><button data-page="routing">\u63A5\u5165</button><button data-page="upstream">\u4E0A\u6E38</button><button data-page="sessions">\u4F1A\u8BDD</button><button data-page="settings">\u8BBE\u7F6E</button></nav></aside>
|
|
51222
51276
|
<main class="main"><section id="page-overview" class="page active"><h1>\u6982\u89C8</h1><p class="lead">\u672C\u5730\u8DEF\u7531\u4E0E\u4E0A\u6E38\u7F51\u7EDC\u76F8\u4E92\u72EC\u7ACB\uFF0CACP \u538B\u7F29\u8FD0\u884C\u5728\u4E24\u8005\u4E4B\u95F4\u3002</p><div class="grid"><div class="card"><h2>\u670D\u52A1\u5730\u5740</h2><p class="mono">${origin}</p></div><div class="card"><h2>\u5BA2\u6237\u7AEF\u63A5\u5165</h2><p class="status">\u5728"\u63A5\u5165"\u9875\u5C06\u5404\u5BA2\u6237\u7AEF\uFF08Codex / Claude Code / OpenCode / Pi \u7B49\uFF09\u7684\u6D41\u91CF\u8DEF\u7531\u5230 bili\u3002</p></div></div></section>
|
|
@@ -51238,7 +51292,7 @@ function renderPage(origin, version2) {
|
|
|
51238
51292
|
}
|
|
51239
51293
|
|
|
51240
51294
|
// src/web/api.ts
|
|
51241
|
-
import { closeSync, existsSync as
|
|
51295
|
+
import { closeSync, existsSync as existsSync5, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
51242
51296
|
import { dirname as dirname3 } from "path";
|
|
51243
51297
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
51244
51298
|
function readConfig() {
|
|
@@ -51246,7 +51300,7 @@ function readConfig() {
|
|
|
51246
51300
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
51247
51301
|
}
|
|
51248
51302
|
function configParseError() {
|
|
51249
|
-
if (!
|
|
51303
|
+
if (!existsSync5(configFile())) return null;
|
|
51250
51304
|
const parsed = safeReadJson(configFile());
|
|
51251
51305
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return null;
|
|
51252
51306
|
return `config file is not valid JSON: ${configFile()}`;
|
|
@@ -51264,12 +51318,12 @@ function readUpstreamSettings() {
|
|
|
51264
51318
|
}
|
|
51265
51319
|
function atomicWriteConfig(config) {
|
|
51266
51320
|
const filePath = configFile();
|
|
51267
|
-
|
|
51321
|
+
mkdirSync5(dirname3(filePath), { recursive: true });
|
|
51268
51322
|
const tempPath = `${filePath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
51269
51323
|
let descriptor;
|
|
51270
51324
|
try {
|
|
51271
51325
|
descriptor = openSync(tempPath, "wx", 384);
|
|
51272
|
-
|
|
51326
|
+
writeFileSync4(descriptor, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
51273
51327
|
fsyncSync(descriptor);
|
|
51274
51328
|
closeSync(descriptor);
|
|
51275
51329
|
descriptor = void 0;
|
|
@@ -51699,6 +51753,23 @@ function isLoopThinking(m2) {
|
|
|
51699
51753
|
function stripLoopThinking(messages) {
|
|
51700
51754
|
return messages.filter((m2) => !isLoopThinking(m2));
|
|
51701
51755
|
}
|
|
51756
|
+
function canonicalArgs(args) {
|
|
51757
|
+
try {
|
|
51758
|
+
return JSON.stringify(sortKeys(JSON.parse(args)));
|
|
51759
|
+
} catch {
|
|
51760
|
+
return args;
|
|
51761
|
+
}
|
|
51762
|
+
}
|
|
51763
|
+
function sortKeys(v2) {
|
|
51764
|
+
if (Array.isArray(v2)) return v2.map(sortKeys);
|
|
51765
|
+
if (v2 !== null && typeof v2 === "object") {
|
|
51766
|
+
const src = v2;
|
|
51767
|
+
const out = {};
|
|
51768
|
+
for (const k2 of Object.keys(src).sort()) out[k2] = sortKeys(src[k2]);
|
|
51769
|
+
return out;
|
|
51770
|
+
}
|
|
51771
|
+
return v2;
|
|
51772
|
+
}
|
|
51702
51773
|
function executeProxyTool(toolName, args, ctx, callId) {
|
|
51703
51774
|
if (toolName === "compress") {
|
|
51704
51775
|
return applyRanges(parseCompressInput(args, callId), ctx);
|
|
@@ -51801,6 +51872,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
51801
51872
|
log("warn", `[acp-loop] upstream rejected replay (HTTP ${info.status}); retrying in ${info.delayMs}ms (attempt ${info.attempt}/${info.maxAttempts})${lc}`);
|
|
51802
51873
|
}
|
|
51803
51874
|
);
|
|
51875
|
+
const failedSignatures = /* @__PURE__ */ new Set();
|
|
51804
51876
|
try {
|
|
51805
51877
|
for (let round = 1; round <= MAX_LOOP_ROUNDS; round++) {
|
|
51806
51878
|
if (signal?.aborted) break;
|
|
@@ -52055,6 +52127,19 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
|
|
|
52055
52127
|
yield adapter.emitCompletion({ finishReason: "length", usage });
|
|
52056
52128
|
return;
|
|
52057
52129
|
}
|
|
52130
|
+
const failedMutating = proxyResults.filter(
|
|
52131
|
+
(pr2) => (pr2.name === "compress" || pr2.name === "decompress") && pr2.result.includes("FAILED")
|
|
52132
|
+
);
|
|
52133
|
+
if (reRequest && failedMutating.length > 0 && failedMutating.length === proxyResults.length) {
|
|
52134
|
+
const repeated = failedMutating.some((pr2) => failedSignatures.has(`${pr2.name}\0${canonicalArgs(pr2.arguments)}`));
|
|
52135
|
+
if (repeated) {
|
|
52136
|
+
ctx.log(`[acp-loop] round ${round}: model re-submitted an already-failed compress with identical arguments; stopping after ${round} round(s) instead of ${MAX_LOOP_ROUNDS}`);
|
|
52137
|
+
log("warn", `[acp-loop] repeated identical compress failure at round ${round}; breaking early (#156)`);
|
|
52138
|
+
yield adapter.emitCompletion({ finishReason: "length", usage });
|
|
52139
|
+
return;
|
|
52140
|
+
}
|
|
52141
|
+
}
|
|
52142
|
+
for (const pr2 of failedMutating) failedSignatures.add(`${pr2.name}\0${canonicalArgs(pr2.arguments)}`);
|
|
52058
52143
|
ctx.log(`[acp-loop] round ${round}: proxy tool executed; re-requesting so the model sees the result`);
|
|
52059
52144
|
if (signal?.aborted) break;
|
|
52060
52145
|
let newBody = adapter.buildRequest(coreMessages, systemPrompt, requestBody);
|
|
@@ -54087,14 +54172,14 @@ var MIN_TAIL_MATCH = 3;
|
|
|
54087
54172
|
var MAX_STORED_ITEMS = 128;
|
|
54088
54173
|
var MIN_FORK_PREFIX = 3;
|
|
54089
54174
|
function stableStringify(value) {
|
|
54090
|
-
return JSON.stringify(
|
|
54175
|
+
return JSON.stringify(sortKeys2(value));
|
|
54091
54176
|
}
|
|
54092
|
-
function
|
|
54093
|
-
if (Array.isArray(value)) return value.map(
|
|
54177
|
+
function sortKeys2(value) {
|
|
54178
|
+
if (Array.isArray(value)) return value.map(sortKeys2);
|
|
54094
54179
|
if (value && typeof value === "object") {
|
|
54095
54180
|
const record = value;
|
|
54096
54181
|
const out = {};
|
|
54097
|
-
for (const key of Object.keys(record).sort()) out[key] =
|
|
54182
|
+
for (const key of Object.keys(record).sort()) out[key] = sortKeys2(record[key]);
|
|
54098
54183
|
return out;
|
|
54099
54184
|
}
|
|
54100
54185
|
return value;
|
|
@@ -56964,7 +57049,7 @@ Content-Length: ${Buffer.byteLength(body)}\r
|
|
|
56964
57049
|
);
|
|
56965
57050
|
}
|
|
56966
57051
|
if (opts.debug) {
|
|
56967
|
-
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)`);
|
|
57052
|
+
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)`);
|
|
56968
57053
|
}
|
|
56969
57054
|
};
|
|
56970
57055
|
const attemptListen = (port) => {
|
|
@@ -58640,10 +58725,10 @@ async function pipeThrough(stream2, res) {
|
|
|
58640
58725
|
}
|
|
58641
58726
|
}
|
|
58642
58727
|
async function dumpStreamToFile(stream2, dir, name) {
|
|
58643
|
-
const { mkdirSync:
|
|
58728
|
+
const { mkdirSync: mkdirSync7, createWriteStream: createWriteStream2 } = await import("fs");
|
|
58644
58729
|
const { join: join4 } = await import("path");
|
|
58645
58730
|
try {
|
|
58646
|
-
|
|
58731
|
+
mkdirSync7(dir, { recursive: true });
|
|
58647
58732
|
const ws2 = createWriteStream2(join4(dir, name));
|
|
58648
58733
|
ws2.on("error", (e) => {
|
|
58649
58734
|
logDumpFailure("SSE stream dump", e);
|
|
@@ -62704,7 +62789,6 @@ function selfDistFile(name) {
|
|
|
62704
62789
|
return path15.join(selfPackageRoot(), "dist", name);
|
|
62705
62790
|
}
|
|
62706
62791
|
var LAUNCHER_DEFAULT_HOST = "127.0.0.1";
|
|
62707
|
-
var LAUNCHER_DEFAULT_PORT = 8787;
|
|
62708
62792
|
var LAUNCH_CLIENTS = ["pi", "codex", "claude", "omp", "opencode", "hermes", "dsh", "pi-test"];
|
|
62709
62793
|
var HEALTH_PATH = "/__bili/health";
|
|
62710
62794
|
var HEALTH_POLL_INTERVAL_MS = 200;
|
|
@@ -63878,6 +63962,19 @@ async function probeExistingInstance(readInstance, fetchHealthInfo) {
|
|
|
63878
63962
|
if (health.instanceId !== void 0 && health.instanceId !== inst.instanceId) return void 0;
|
|
63879
63963
|
return inst;
|
|
63880
63964
|
}
|
|
63965
|
+
function pickEphemeralPort(host = LAUNCHER_DEFAULT_HOST) {
|
|
63966
|
+
return new Promise((resolve, reject) => {
|
|
63967
|
+
const srv = net2.createServer();
|
|
63968
|
+
srv.once("error", reject);
|
|
63969
|
+
srv.listen(0, host, () => {
|
|
63970
|
+
const addr = srv.address();
|
|
63971
|
+
srv.close(() => {
|
|
63972
|
+
if (addr && typeof addr === "object") resolve(addr.port);
|
|
63973
|
+
else reject(new Error("could not allocate a free port"));
|
|
63974
|
+
});
|
|
63975
|
+
});
|
|
63976
|
+
});
|
|
63977
|
+
}
|
|
63881
63978
|
var INHERITED_PROXY_VARS = ["http_proxy", "https_proxy", "all_proxy", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"];
|
|
63882
63979
|
function stripInheritedProxy(env) {
|
|
63883
63980
|
const cleaned = { ...env };
|
|
@@ -63903,7 +64000,7 @@ async function ensureProxyRunning(opts, deps = {}) {
|
|
|
63903
64000
|
return { origin: existing.origin, port: existing.port, attached: true };
|
|
63904
64001
|
}
|
|
63905
64002
|
const launchToken = randomUUID5();
|
|
63906
|
-
const port = opts.port;
|
|
64003
|
+
const port = opts.port > 0 ? opts.port : await pickEphemeralPort(opts.host);
|
|
63907
64004
|
const script = process.argv[1];
|
|
63908
64005
|
if (!script) throw new Error("bili: cannot resolve launcher script path");
|
|
63909
64006
|
const logPath2 = path15.join(os5.tmpdir(), `bili-proxy-${port}.log`);
|
|
@@ -63912,7 +64009,7 @@ async function ensureProxyRunning(opts, deps = {}) {
|
|
|
63912
64009
|
try {
|
|
63913
64010
|
child = spawnImpl(
|
|
63914
64011
|
process.execPath,
|
|
63915
|
-
[script, ...proxyStartArgs(opts)],
|
|
64012
|
+
[script, ...proxyStartArgs({ ...opts, port })],
|
|
63916
64013
|
{
|
|
63917
64014
|
detached: true,
|
|
63918
64015
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -64019,8 +64116,9 @@ function resolveClientCommand(client, env) {
|
|
|
64019
64116
|
return { command: resolved ?? client, prefixArgs: [] };
|
|
64020
64117
|
}
|
|
64021
64118
|
function parsePort(raw) {
|
|
64022
|
-
|
|
64023
|
-
|
|
64119
|
+
if (!raw || !raw.trim()) return 0;
|
|
64120
|
+
const port = parseInt(raw, 10);
|
|
64121
|
+
if (!Number.isFinite(port) || port < 1 || port > 65535) {
|
|
64024
64122
|
console.error(`bili: invalid --port "${raw}"`);
|
|
64025
64123
|
process.exit(2);
|
|
64026
64124
|
}
|
|
@@ -64248,7 +64346,7 @@ async function runTestPi(params, deps = {}) {
|
|
|
64248
64346
|
}
|
|
64249
64347
|
|
|
64250
64348
|
// src/export.ts
|
|
64251
|
-
import { mkdirSync as
|
|
64349
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
64252
64350
|
import path16 from "path";
|
|
64253
64351
|
function fmtDate(ms2) {
|
|
64254
64352
|
return ms2 ? new Date(ms2).toISOString().replace("T", " ").slice(0, 19) + " UTC" : "\u2014";
|
|
@@ -64381,8 +64479,8 @@ async function exportSession(selector, opts = {}) {
|
|
|
64381
64479
|
}
|
|
64382
64480
|
const markdown = renderHandoff(matches[0], opts.full ?? false);
|
|
64383
64481
|
if (opts.output) {
|
|
64384
|
-
|
|
64385
|
-
|
|
64482
|
+
mkdirSync6(path16.dirname(path16.resolve(opts.output)), { recursive: true });
|
|
64483
|
+
writeFileSync5(opts.output, markdown, "utf8");
|
|
64386
64484
|
return `written to ${opts.output}`;
|
|
64387
64485
|
}
|
|
64388
64486
|
return markdown;
|
|
@@ -64459,7 +64557,7 @@ Launcher (bili pi / bili codex / bili claude / bili omp / bili opencode / bili h
|
|
|
64459
64557
|
Options (override config file / env):
|
|
64460
64558
|
-F <url> upstream proxy to forward through (gost-style;
|
|
64461
64559
|
http://host:port; must precede the client name)
|
|
64462
|
-
--port <N> listen port (default
|
|
64560
|
+
--port <N> listen port (start: 8787; launcher default: random free port)
|
|
64463
64561
|
--host <ADDR> listen host (default 127.0.0.1)
|
|
64464
64562
|
--mitm-domain <domain> extra MITM domain (repeatable; launcher only)
|
|
64465
64563
|
--config <FILE> path to config JSON (default: XDG location)
|