billion-context 0.1.83 → 0.1.84-pr.360.366

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/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) await this.removeLegacyFile(prev.id, prev.session);
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 && typeof content === "object") blockContents.set(bid, 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.migrateLegacyIds();
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 mkdirSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
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
- mkdirSync3(dirname2(outPath), { recursive: true });
50356
- writeFileSync2(outPath, body, "utf8");
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 existsSync3 } from "fs";
51089
+ import { existsSync as existsSync4 } from "fs";
51036
51090
 
51037
51091
  // src/ca.ts
51038
51092
  var import_node_forge = __toESM(require_lib(), 1);
@@ -51103,7 +51157,7 @@ function generateRootCA() {
51103
51157
  cert.setSubject(attrs);
51104
51158
  cert.setIssuer(attrs);
51105
51159
  cert.setExtensions([
51106
- { name: "basicConstraints", cA: true },
51160
+ { name: "basicConstraints", cA: true, critical: true },
51107
51161
  { name: "keyUsage", keyCertSign: true, cRLSign: true, digitalSignature: true },
51108
51162
  { name: "subjectKeyIdentifier" }
51109
51163
  ]);
@@ -51139,16 +51193,10 @@ function ensureRootCA() {
51139
51193
  rootKey = import_node_forge.default.pki.privateKeyFromPem(key);
51140
51194
  writeCombinedBundle();
51141
51195
  }
51142
- function getSecureContext(host) {
51196
+ function mintHostCert(host) {
51143
51197
  if (!rootCertPem || !rootKeyPem || !rootCert || !rootKey) {
51144
51198
  throw new Error("CA not initialized \u2014 call ensureRootCA() first");
51145
51199
  }
51146
- const cached = secureContextCache.get(host);
51147
- if (cached) {
51148
- secureContextCache.delete(host);
51149
- secureContextCache.set(host, cached);
51150
- return cached;
51151
- }
51152
51200
  const keys = import_node_forge.default.pki.rsa.generateKeyPair({ bits: 2048 });
51153
51201
  const cert = import_node_forge.default.pki.createCertificate();
51154
51202
  cert.publicKey = keys.publicKey;
@@ -51158,16 +51206,35 @@ function getSecureContext(host) {
51158
51206
  cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 2);
51159
51207
  cert.setSubject([{ name: "commonName", value: host }]);
51160
51208
  cert.setIssuer(rootCert.subject.attributes);
51209
+ const rootSki = rootCert.generateSubjectKeyIdentifier().getBytes();
51161
51210
  cert.setExtensions([
51162
51211
  { name: "basicConstraints", cA: false },
51163
51212
  { name: "keyUsage", digitalSignature: true, keyEncipherment: true },
51164
51213
  { name: "extKeyUsage", serverAuth: true },
51165
- { name: "subjectAltName", altNames: [{ type: 2, value: host }] }
51214
+ { name: "subjectAltName", altNames: [{ type: 2, value: host }] },
51215
+ { name: "subjectKeyIdentifier" },
51216
+ { name: "authorityKeyIdentifier", keyIdentifier: rootSki }
51166
51217
  ]);
51167
51218
  cert.sign(rootKey, import_node_forge.default.md.sha256.create());
51219
+ return {
51220
+ certPem: import_node_forge.default.pki.certificateToPem(cert),
51221
+ keyPem: import_node_forge.default.pki.privateKeyToPem(keys.privateKey)
51222
+ };
51223
+ }
51224
+ function getSecureContext(host) {
51225
+ if (!rootCertPem || !rootKeyPem || !rootCert || !rootKey) {
51226
+ throw new Error("CA not initialized \u2014 call ensureRootCA() first");
51227
+ }
51228
+ const cached = secureContextCache.get(host);
51229
+ if (cached) {
51230
+ secureContextCache.delete(host);
51231
+ secureContextCache.set(host, cached);
51232
+ return cached;
51233
+ }
51234
+ const { certPem, keyPem } = mintHostCert(host);
51168
51235
  const ctx = tls2.createSecureContext({
51169
- cert: import_node_forge.default.pki.certificateToPem(cert),
51170
- key: import_node_forge.default.pki.privateKeyToPem(keys.privateKey),
51236
+ cert: certPem,
51237
+ key: keyPem,
51171
51238
  ca: rootCertPem
51172
51239
  });
51173
51240
  secureContextCache.set(host, ctx);
@@ -51216,7 +51283,7 @@ function escapeHtml(value) {
51216
51283
  function renderPage(origin, version2) {
51217
51284
  const caPath = rootCaPath();
51218
51285
  const caPathEsc = escapeHtml(caPath);
51219
- const caReady = existsSync3(caPath);
51286
+ const caReady = existsSync4(caPath);
51220
51287
  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
51288
  <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
51289
  <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 +51305,7 @@ function renderPage(origin, version2) {
51238
51305
  }
51239
51306
 
51240
51307
  // src/web/api.ts
51241
- import { closeSync, existsSync as existsSync4, fsyncSync, mkdirSync as mkdirSync4, openSync, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
51308
+ import { closeSync, existsSync as existsSync5, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
51242
51309
  import { dirname as dirname3 } from "path";
51243
51310
  import { randomUUID as randomUUID2 } from "crypto";
51244
51311
  function readConfig() {
@@ -51246,7 +51313,7 @@ function readConfig() {
51246
51313
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
51247
51314
  }
51248
51315
  function configParseError() {
51249
- if (!existsSync4(configFile())) return null;
51316
+ if (!existsSync5(configFile())) return null;
51250
51317
  const parsed = safeReadJson(configFile());
51251
51318
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return null;
51252
51319
  return `config file is not valid JSON: ${configFile()}`;
@@ -51264,12 +51331,12 @@ function readUpstreamSettings() {
51264
51331
  }
51265
51332
  function atomicWriteConfig(config) {
51266
51333
  const filePath = configFile();
51267
- mkdirSync4(dirname3(filePath), { recursive: true });
51334
+ mkdirSync5(dirname3(filePath), { recursive: true });
51268
51335
  const tempPath = `${filePath}.${process.pid}.${randomUUID2()}.tmp`;
51269
51336
  let descriptor;
51270
51337
  try {
51271
51338
  descriptor = openSync(tempPath, "wx", 384);
51272
- writeFileSync3(descriptor, JSON.stringify(config, null, 2) + "\n", "utf8");
51339
+ writeFileSync4(descriptor, JSON.stringify(config, null, 2) + "\n", "utf8");
51273
51340
  fsyncSync(descriptor);
51274
51341
  closeSync(descriptor);
51275
51342
  descriptor = void 0;
@@ -51947,10 +52014,11 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
51947
52014
  if (realCalls > 0) ctx.log(`[acp-loop] round ${round}: ${realCalls} real tool call(s) forwarded to client`);
51948
52015
  }
51949
52016
  if (proxyResults.length > 0) {
52017
+ const requiresThinkingSignature = ctx.protocol === "anthropic";
51950
52018
  if (reasoningSegments.length > 0) {
51951
52019
  for (let i = 0; i < reasoningSegments.length; i++) {
51952
52020
  const seg = reasoningSegments[i];
51953
- if (seg.text.length === 0 || seg.signature.length === 0) continue;
52021
+ if (seg.text.length === 0 || requiresThinkingSignature && seg.signature.length === 0) continue;
51954
52022
  const reasoningMsg = {
51955
52023
  id: i === 0 ? `acp_loop_r${round}_reasoning` : `acp_loop_r${round}_reasoning_${i + 1}`,
51956
52024
  role: "assistant",
@@ -52073,7 +52141,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
52073
52141
  try {
52074
52142
  respResult = await fetchUpstream(newBody);
52075
52143
  } catch (e) {
52076
- if (!(e instanceof UpstreamHttpError) || e.status < 400 || e.status >= 500 || degradedRetried || !coreMessages.some(isLoopThinking)) {
52144
+ if (!(e instanceof UpstreamHttpError) || e.status < 400 || e.status >= 500 || degradedRetried || ctx.protocol !== "anthropic" || !coreMessages.some(isLoopThinking)) {
52077
52145
  throw e;
52078
52146
  }
52079
52147
  degradedRetried = true;
@@ -52295,6 +52363,7 @@ data: ${JSON.stringify(obj)}
52295
52363
  }
52296
52364
  async function* iterSseEvents(stream2) {
52297
52365
  const reader = stream2.getReader();
52366
+ const decoder = new TextDecoder();
52298
52367
  let buf = "";
52299
52368
  try {
52300
52369
  while (true) {
@@ -52306,7 +52375,7 @@ async function* iterSseEvents(stream2) {
52306
52375
  break;
52307
52376
  }
52308
52377
  if (done) break;
52309
- buf += new TextDecoder().decode(value, { stream: true });
52378
+ buf += decoder.decode(value, { stream: true });
52310
52379
  buf = buf.replace(/\r\n|\r/g, "\n");
52311
52380
  let idx;
52312
52381
  while ((idx = buf.indexOf("\n\n")) >= 0) {
@@ -52784,6 +52853,7 @@ function filterRealToolFragments(parsed, realIndexes) {
52784
52853
  }
52785
52854
  async function* iterSseChunks(stream2) {
52786
52855
  const reader = stream2.getReader();
52856
+ const decoder = new TextDecoder();
52787
52857
  let buf = "";
52788
52858
  try {
52789
52859
  while (true) {
@@ -52795,7 +52865,7 @@ async function* iterSseChunks(stream2) {
52795
52865
  break;
52796
52866
  }
52797
52867
  if (done) break;
52798
- buf += new TextDecoder().decode(value, { stream: true });
52868
+ buf += decoder.decode(value, { stream: true });
52799
52869
  buf = buf.replace(/\r\n|\r/g, "\n");
52800
52870
  let idx;
52801
52871
  while ((idx = buf.indexOf("\n\n")) >= 0) {
@@ -53121,6 +53191,7 @@ function createOpenaiAdapter(requestBody, clientSystem) {
53121
53191
  // src/loop/adapter-anthropic.ts
53122
53192
  async function* iterSseEvents2(stream2) {
53123
53193
  const reader = stream2.getReader();
53194
+ const decoder = new TextDecoder();
53124
53195
  let buf = "";
53125
53196
  try {
53126
53197
  while (true) {
@@ -53132,7 +53203,7 @@ async function* iterSseEvents2(stream2) {
53132
53203
  break;
53133
53204
  }
53134
53205
  if (done) break;
53135
- buf += new TextDecoder().decode(value, { stream: true });
53206
+ buf += decoder.decode(value, { stream: true });
53136
53207
  buf = buf.replace(/\r\n|\r/g, "\n");
53137
53208
  let idx;
53138
53209
  while ((idx = buf.indexOf("\n\n")) >= 0) {
@@ -53779,6 +53850,76 @@ ${FAKE_COMPLETION_HINT}`;
53779
53850
  return JSON.stringify(obj);
53780
53851
  }
53781
53852
 
53853
+ // src/acp-panel.ts
53854
+ var PANEL_BOX_TOP = "\u256D";
53855
+ var PANEL_BOX_TITLE = "ACP Context Analysis";
53856
+ var PANEL_BOX_FOOTER = "Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.";
53857
+ var PANEL_FALLBACK_HEADER = "\u{1F4CA} ACP status";
53858
+ function isAcpPanelText(text) {
53859
+ const t = text.trim();
53860
+ if (t.length === 0) return false;
53861
+ return isBoxPanel(t) || isFallbackPanel(t);
53862
+ }
53863
+ function isBoxPanel(t) {
53864
+ return t.startsWith(PANEL_BOX_TOP) && t.includes(PANEL_BOX_TITLE) && t.endsWith(PANEL_BOX_FOOTER);
53865
+ }
53866
+ function isFallbackPanel(t) {
53867
+ const lines = t.split("\n");
53868
+ if (lines[0] !== PANEL_FALLBACK_HEADER) return false;
53869
+ for (let i = 1; i < lines.length; i++) {
53870
+ const line = lines[i];
53871
+ if (line.length > 0 && !line.startsWith(" ")) return false;
53872
+ }
53873
+ return true;
53874
+ }
53875
+ function messageText(content) {
53876
+ if (typeof content === "string") return content;
53877
+ if (Array.isArray(content)) {
53878
+ let joined = "";
53879
+ for (const part of content) {
53880
+ const p2 = part;
53881
+ if (p2 === null || typeof p2 !== "object") return void 0;
53882
+ const pt2 = p2.type;
53883
+ if (pt2 !== void 0 && pt2 !== "text" && pt2 !== "input_text") return void 0;
53884
+ if (typeof p2.text === "string") joined += p2.text;
53885
+ }
53886
+ return joined;
53887
+ }
53888
+ return void 0;
53889
+ }
53890
+ function stripAcpPanelMessages(messages) {
53891
+ if (!Array.isArray(messages)) return 0;
53892
+ let stripped = 0;
53893
+ for (let i = messages.length - 1; i >= 0; i--) {
53894
+ const rec = messages[i];
53895
+ if (rec === null || typeof rec !== "object") continue;
53896
+ if (rec.role !== "user") continue;
53897
+ const text = messageText(rec.content);
53898
+ if (text !== void 0 && isAcpPanelText(text)) {
53899
+ messages.splice(i, 1);
53900
+ stripped++;
53901
+ }
53902
+ }
53903
+ return stripped;
53904
+ }
53905
+ function stripAcpPanelResponsesInput(input) {
53906
+ if (!Array.isArray(input)) return 0;
53907
+ let stripped = 0;
53908
+ for (let i = input.length - 1; i >= 0; i--) {
53909
+ const rec = input[i];
53910
+ if (rec === null || typeof rec !== "object") continue;
53911
+ const type = rec.type;
53912
+ if (type !== "message" && type !== void 0) continue;
53913
+ if (rec.role !== "user") continue;
53914
+ const text = messageText(rec.content);
53915
+ if (text !== void 0 && isAcpPanelText(text)) {
53916
+ input.splice(i, 1);
53917
+ stripped++;
53918
+ }
53919
+ }
53920
+ return stripped;
53921
+ }
53922
+
53782
53923
  // src/stream-openai.ts
53783
53924
  function rewriteOpenaiJsonResponse(body, ctx) {
53784
53925
  if (!body || typeof body !== "object") return body;
@@ -55505,6 +55646,7 @@ function readOmpConfig(ompHome) {
55505
55646
  }
55506
55647
  function parseHermesYaml(text) {
55507
55648
  const result = { providers: {} };
55649
+ const candidates = /* @__PURE__ */ new Map();
55508
55650
  let mode = "none";
55509
55651
  let sectionIndent = -1;
55510
55652
  let entryIndent = -1;
@@ -55549,8 +55691,8 @@ function parseHermesYaml(text) {
55549
55691
  current = m2 ? m2[1] : null;
55550
55692
  if (current && !result.providers[current]) result.providers[current] = {};
55551
55693
  } else if (indent > entryIndent && current) {
55552
- const apiMatch = /^api:\s*(\S+)/.exec(trimmed);
55553
- if (apiMatch) result.providers[current].api = apiMatch[1];
55694
+ const apiMatch = /^(base_url|url|api):\s*(\S+)/.exec(trimmed);
55695
+ if (apiMatch) candidates.set(`${current}\0${apiMatch[1]}`, apiMatch[2]);
55554
55696
  }
55555
55697
  } else {
55556
55698
  const dashMatch = /^-\s+(.*)$/.exec(trimmed);
@@ -55567,6 +55709,10 @@ function parseHermesYaml(text) {
55567
55709
  }
55568
55710
  }
55569
55711
  }
55712
+ for (const name of Object.keys(result.providers)) {
55713
+ const pick = candidates.get(`${name}\0base_url`) ?? candidates.get(`${name}\0url`) ?? candidates.get(`${name}\0api`);
55714
+ if (pick !== void 0) result.providers[name].api = pick;
55715
+ }
55570
55716
  return result;
55571
55717
  }
55572
55718
  function readHermesConfig(hermesHome) {
@@ -56867,6 +57013,16 @@ function resolveUpstream(_opts, reqUrl, req) {
56867
57013
  }
56868
57014
  }
56869
57015
  }
57016
+ if (reqUrl.startsWith("http://") || reqUrl.startsWith("https://")) {
57017
+ try {
57018
+ const u2 = new URL(reqUrl);
57019
+ const ownHost = (req?.headers.host ?? "").toLowerCase();
57020
+ if (u2.host.toLowerCase() !== ownHost) {
57021
+ return { upstream: `${u2.protocol}//${u2.host}`, rewrittenUrl: reqUrl, tunnel: true };
57022
+ }
57023
+ } catch {
57024
+ }
57025
+ }
56870
57026
  return void 0;
56871
57027
  }
56872
57028
  async function startServer(opts) {
@@ -56964,7 +57120,7 @@ Content-Length: ${Buffer.byteLength(body)}\r
56964
57120
  );
56965
57121
  }
56966
57122
  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)`);
57123
+ 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
57124
  }
56969
57125
  };
56970
57126
  const attemptListen = (port) => {
@@ -57604,6 +57760,10 @@ function prepareAnthropic(parsed, req, opts, core, config, prompts, log2, sessio
57604
57760
  let rebuiltMessages = parsed.messages;
57605
57761
  let systemOut = parsed.system;
57606
57762
  let toolsOut = parsed.tools;
57763
+ const strippedPanels = stripAcpPanelMessages(parsed.messages);
57764
+ if (strippedPanels > 0) {
57765
+ log2("info", `[${sessionId}] stripped ${strippedPanels} ACP panel message(s) before projection (UI-only, issue #359)`);
57766
+ }
57607
57767
  try {
57608
57768
  const { msgs, cacheControls } = anthropicToCore(parsed);
57609
57769
  originalMessages = msgs;
@@ -57693,6 +57853,10 @@ function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session,
57693
57853
  const isTitleGen = maxTokens <= 200;
57694
57854
  const shouldInject = opts.compress.injectTool && !isTitleGen;
57695
57855
  const injectTools = shouldInject && !pluginMode;
57856
+ const strippedPanels = stripAcpPanelMessages(parsed.messages);
57857
+ if (strippedPanels > 0) {
57858
+ log2("info", `[${sessionId}] stripped ${strippedPanels} ACP panel message(s) before projection (UI-only, issue #359)`);
57859
+ }
57696
57860
  try {
57697
57861
  const { msgs, systemText } = openaiToCore(parsed);
57698
57862
  openaiSystemText = systemText;
@@ -57778,6 +57942,10 @@ function prepareResponses(parsed, req, opts, core, config, prompts, log2, sessio
57778
57942
  if (droppedEmpty > 0) {
57779
57943
  log2("info", `[${sessionId}] dropped ${droppedEmpty} whitespace-only message item(s) before projection (flattened-turn artifact)`);
57780
57944
  }
57945
+ const strippedPanels = stripAcpPanelResponsesInput(parsed.input);
57946
+ if (strippedPanels > 0) {
57947
+ log2("info", `[${sessionId}] stripped ${strippedPanels} ACP panel message(s) before projection (UI-only, issue #359)`);
57948
+ }
57781
57949
  const shouldInject = opts.compress.injectTool;
57782
57950
  const injectTools = shouldInject && !pluginMode;
57783
57951
  const isCompactionTrigger = hasCompactionTrigger(parsed.input);
@@ -58640,10 +58808,10 @@ async function pipeThrough(stream2, res) {
58640
58808
  }
58641
58809
  }
58642
58810
  async function dumpStreamToFile(stream2, dir, name) {
58643
- const { mkdirSync: mkdirSync6, createWriteStream: createWriteStream2 } = await import("fs");
58811
+ const { mkdirSync: mkdirSync7, createWriteStream: createWriteStream2 } = await import("fs");
58644
58812
  const { join: join4 } = await import("path");
58645
58813
  try {
58646
- mkdirSync6(dir, { recursive: true });
58814
+ mkdirSync7(dir, { recursive: true });
58647
58815
  const ws2 = createWriteStream2(join4(dir, name));
58648
58816
  ws2.on("error", (e) => {
58649
58817
  logDumpFailure("SSE stream dump", e);
@@ -62704,7 +62872,6 @@ function selfDistFile(name) {
62704
62872
  return path15.join(selfPackageRoot(), "dist", name);
62705
62873
  }
62706
62874
  var LAUNCHER_DEFAULT_HOST = "127.0.0.1";
62707
- var LAUNCHER_DEFAULT_PORT = 8787;
62708
62875
  var LAUNCH_CLIENTS = ["pi", "codex", "claude", "omp", "opencode", "hermes", "dsh", "pi-test"];
62709
62876
  var HEALTH_PATH = "/__bili/health";
62710
62877
  var HEALTH_POLL_INTERVAL_MS = 200;
@@ -62809,8 +62976,16 @@ function discoverRoutes(client, config) {
62809
62976
  if (url.protocol !== "http:" && url.protocol !== "https:") continue;
62810
62977
  if (hermesSeen.has(name)) continue;
62811
62978
  hermesSeen.add(name);
62812
- rewriteKeys.add(name);
62813
- httpRewrites.push({ key: name, realUpstream: real });
62979
+ if (url.protocol === "https:") {
62980
+ const host = url.hostname.toLowerCase();
62981
+ if (host && !httpsSeen.has(host)) {
62982
+ httpsSeen.add(host);
62983
+ httpsDomains.push(host);
62984
+ }
62985
+ } else if (!rewriteKeys.has(name)) {
62986
+ rewriteKeys.add(name);
62987
+ httpRewrites.push({ key: name, realUpstream: real });
62988
+ }
62814
62989
  } catch {
62815
62990
  }
62816
62991
  }
@@ -62841,8 +63016,23 @@ function discoverRoutes(client, config) {
62841
63016
  function discoverDomains(client, config) {
62842
63017
  return discoverRoutes(client, config).httpsDomains;
62843
63018
  }
62844
- function buildPiEnv(origin, caPath, baseEnv) {
62845
- return { ...baseEnv, HTTPS_PROXY: origin, NODE_EXTRA_CA_CERTS: caPath, BILLION_CONTEXT_PROXY: origin };
63019
+ function buildPiEnv(origin, caPath, baseEnv, httpRewrites = [], httpsRewrites = []) {
63020
+ const manifest = {};
63021
+ for (const r of httpRewrites) {
63022
+ if (r.key.length === 0 || r.realUpstream.length === 0) continue;
63023
+ manifest[r.key] = wrapUpstream(origin, r.realUpstream);
63024
+ }
63025
+ for (const r of httpsRewrites) {
63026
+ if (r.key.length === 0 || r.realUpstream.length === 0) continue;
63027
+ manifest[r.key] = r.realUpstream;
63028
+ }
63029
+ return {
63030
+ ...baseEnv,
63031
+ HTTPS_PROXY: origin,
63032
+ NODE_EXTRA_CA_CERTS: caPath,
63033
+ BILLION_CONTEXT_PROXY: origin,
63034
+ ...Object.keys(manifest).length > 0 ? { BILI_PROVIDER_REWRITES: JSON.stringify(manifest) } : {}
63035
+ };
62846
63036
  }
62847
63037
  function buildCodexEnv(origin, caPath, baseEnv) {
62848
63038
  return { ...baseEnv, HTTPS_PROXY: origin, SSL_CERT_FILE: caPath, BILLION_CONTEXT_PROXY: origin };
@@ -63373,363 +63563,6 @@ function writeOverlayFileAtomic(overlay, fileName, contents) {
63373
63563
  }
63374
63564
  }
63375
63565
  }
63376
- function writePiCompactionDisabledSettings(piHome, overlay) {
63377
- let settings = {};
63378
- try {
63379
- const parsed = JSON.parse(fs12.readFileSync(path15.join(piHome, "settings.json"), "utf8"));
63380
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
63381
- settings = parsed;
63382
- }
63383
- } catch {
63384
- }
63385
- const compactionRaw = settings.compaction;
63386
- const compaction = compactionRaw && typeof compactionRaw === "object" && !Array.isArray(compactionRaw) ? { ...compactionRaw } : {};
63387
- compaction.enabled = false;
63388
- settings.compaction = compaction;
63389
- writeOverlayFileAtomic(overlay, "settings.json", JSON.stringify(settings, null, 2));
63390
- }
63391
- function preparePiHttpRewrite(piHome, origin, httpRewrites, httpsRewrites) {
63392
- if (!fs12.existsSync(piHome)) return void 0;
63393
- const overlay = `${piHome}-bili`;
63394
- const generated = ["settings.json"];
63395
- let modelsRoot;
63396
- try {
63397
- const parsed = JSON.parse(fs12.readFileSync(path15.join(piHome, "models.json"), "utf8"));
63398
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
63399
- modelsRoot = parsed;
63400
- generated.push("models.json");
63401
- }
63402
- } catch {
63403
- }
63404
- if (!refreshOverlayHome(piHome, overlay, generated)) return void 0;
63405
- if (modelsRoot) {
63406
- const providersVal = modelsRoot.providers;
63407
- if (providersVal && typeof providersVal === "object" && !Array.isArray(providersVal)) {
63408
- const providers = providersVal;
63409
- for (const r of httpRewrites) {
63410
- const prov = providers[r.key];
63411
- if (prov && typeof prov === "object" && !Array.isArray(prov)) {
63412
- const p2 = prov;
63413
- const existing = typeof p2.baseUrl === "string" ? p2.baseUrl : r.realUpstream;
63414
- p2.baseUrl = wrapUpstream(origin, unwrapUpstream2(existing));
63415
- }
63416
- }
63417
- for (const r of httpsRewrites) {
63418
- const prov = providers[r.key];
63419
- if (prov && typeof prov === "object" && !Array.isArray(prov)) {
63420
- const p2 = prov;
63421
- p2.baseUrl = r.realUpstream;
63422
- }
63423
- }
63424
- }
63425
- writeOverlayFileAtomic(overlay, "models.json", JSON.stringify(modelsRoot));
63426
- }
63427
- writePiCompactionDisabledSettings(piHome, overlay);
63428
- return overlay;
63429
- }
63430
- function atomicWriteTextFile(filePath, contents) {
63431
- const draft = `${filePath}.${process.pid}.bili-tmp`;
63432
- try {
63433
- fs12.writeFileSync(draft, contents);
63434
- fs12.renameSync(draft, filePath);
63435
- } catch {
63436
- try {
63437
- fs12.rmSync(draft, { force: true });
63438
- } catch {
63439
- }
63440
- throw new Error(`bili: could not write ${filePath}`);
63441
- }
63442
- }
63443
- function liveProxyPorts() {
63444
- const ports = /* @__PURE__ */ new Set();
63445
- const inst = readProxyInstanceFile();
63446
- if (isProxyInstanceFile(inst)) {
63447
- if (isPidAlive(inst.pid)) ports.add(inst.port);
63448
- } else if (inst) {
63449
- try {
63450
- ports.add(new URL(inst.origin).port === "" ? 80 : Number(new URL(inst.origin).port));
63451
- } catch {
63452
- }
63453
- }
63454
- return ports;
63455
- }
63456
- function unpackDeadProxyUrlsInFile(filePath, livePorts) {
63457
- let txt;
63458
- try {
63459
- txt = fs12.readFileSync(filePath, "utf8");
63460
- } catch {
63461
- return 0;
63462
- }
63463
- const re2 = /https?:\/\/(?:127\.0\.0\.1|localhost|\[::1\]):(\d+)\/bili\/(https?:\/\/\S+)/g;
63464
- let changed = 0;
63465
- const out = txt.replace(re2, (full, portStr, raw) => {
63466
- if (livePorts.has(Number(portStr))) return full;
63467
- changed += 1;
63468
- return raw;
63469
- });
63470
- if (changed === 0) return 0;
63471
- try {
63472
- atomicWriteTextFile(filePath, out);
63473
- } catch {
63474
- return 0;
63475
- }
63476
- return changed;
63477
- }
63478
- function mergeOmpCompactionDisabledYaml(text) {
63479
- const lines = text.split(/\r?\n/);
63480
- let compactionLine = -1;
63481
- for (let i = 0; i < lines.length; i++) {
63482
- const rawLine = lines[i];
63483
- const trimmed = rawLine.trim();
63484
- if (!trimmed || trimmed.startsWith("#")) continue;
63485
- const indent = rawLine.length - rawLine.trimStart().length;
63486
- if (indent === 0 && /^compaction:\s*(#.*)?$/.test(trimmed)) {
63487
- compactionLine = i;
63488
- break;
63489
- }
63490
- }
63491
- if (compactionLine === -1) {
63492
- const base = text === "" || text.endsWith("\n") ? text : `${text}
63493
- `;
63494
- return `${base}compaction:
63495
- enabled: false
63496
- `;
63497
- }
63498
- for (let i = compactionLine + 1; i < lines.length; i++) {
63499
- const rawLine = lines[i];
63500
- const trimmed = rawLine.trim();
63501
- if (!trimmed || trimmed.startsWith("#")) continue;
63502
- const indent = rawLine.length - rawLine.trimStart().length;
63503
- if (indent === 0) break;
63504
- if (/^enabled:/.test(trimmed)) {
63505
- const leading = rawLine.slice(0, rawLine.length - rawLine.trimStart().length);
63506
- const commentMatch = /\s+#.*$/.exec(rawLine);
63507
- const comment = commentMatch ? commentMatch[0] : "";
63508
- lines[i] = `${leading}enabled: false${comment}`;
63509
- return lines.join("\n");
63510
- }
63511
- }
63512
- let childIndent = 2;
63513
- for (let i = compactionLine + 1; i < lines.length; i++) {
63514
- const rawLine = lines[i];
63515
- const trimmed = rawLine.trim();
63516
- if (!trimmed || trimmed.startsWith("#")) continue;
63517
- const indent = rawLine.length - rawLine.trimStart().length;
63518
- if (indent === 0) break;
63519
- childIndent = indent;
63520
- break;
63521
- }
63522
- lines.splice(compactionLine + 1, 0, `${" ".repeat(childIndent)}enabled: false`);
63523
- return lines.join("\n");
63524
- }
63525
- function writeOmpCompactionDisabledConfig(ompHome, overlay) {
63526
- let base;
63527
- let baseIsJson = false;
63528
- for (const name of ["config.yml", "config.yaml"]) {
63529
- try {
63530
- base = fs12.readFileSync(path15.join(ompHome, name), "utf8");
63531
- break;
63532
- } catch {
63533
- }
63534
- }
63535
- if (base === void 0) {
63536
- try {
63537
- base = fs12.readFileSync(path15.join(ompHome, "settings.json"), "utf8");
63538
- baseIsJson = true;
63539
- } catch {
63540
- }
63541
- }
63542
- let merged;
63543
- if (base === void 0) {
63544
- merged = "compaction:\n enabled: false\n";
63545
- } else if (baseIsJson) {
63546
- let settings = {};
63547
- try {
63548
- const parsed = JSON.parse(base);
63549
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
63550
- settings = parsed;
63551
- }
63552
- } catch {
63553
- }
63554
- const compactionRaw = settings.compaction;
63555
- const compaction = compactionRaw && typeof compactionRaw === "object" && !Array.isArray(compactionRaw) ? { ...compactionRaw } : {};
63556
- compaction.enabled = false;
63557
- settings.compaction = compaction;
63558
- merged = JSON.stringify(settings, null, 2);
63559
- } else {
63560
- merged = mergeOmpCompactionDisabledYaml(base);
63561
- }
63562
- writeOverlayFileAtomic(overlay, "config.yml", merged);
63563
- }
63564
- function prepareOmpHttpRewrite(ompHome, origin, httpRewrites, httpsRewrites) {
63565
- if (!fs12.existsSync(ompHome)) return void 0;
63566
- const overlay = `${ompHome}-bili`;
63567
- const generated = ["config.yml"];
63568
- const modelsPath = path15.join(ompHome, "models.yml");
63569
- const unpacked = unpackDeadProxyUrlsInFile(modelsPath, liveProxyPorts());
63570
- if (unpacked > 0) {
63571
- console.error(`bili: unpacked ${unpacked} dead proxy URL(s) from the real ${modelsPath}`);
63572
- }
63573
- let modelsText;
63574
- try {
63575
- modelsText = fs12.readFileSync(modelsPath, "utf8");
63576
- generated.push("models.yml");
63577
- } catch {
63578
- }
63579
- if (!refreshOverlayHome(ompHome, overlay, generated)) return void 0;
63580
- if (modelsText !== void 0) {
63581
- const httpMap = new Map(httpRewrites.map((r) => [r.key, wrapUpstream(origin, r.realUpstream)]));
63582
- const httpsMap = new Map(httpsRewrites.map((r) => [r.key, r.realUpstream]));
63583
- const lines = modelsText.split(/\r?\n/);
63584
- let providersIndent = -1;
63585
- let providerIndent = -1;
63586
- let currentProvider = null;
63587
- for (let i = 0; i < lines.length; i++) {
63588
- const rawLine = lines[i];
63589
- const trimmed = rawLine.trim();
63590
- if (!trimmed || trimmed.startsWith("#")) continue;
63591
- const indent = rawLine.length - rawLine.trimStart().length;
63592
- if (providersIndent === -1) {
63593
- if (/^providers:\s*(#.*)?$/.test(trimmed)) providersIndent = indent;
63594
- continue;
63595
- }
63596
- if (indent <= providersIndent) break;
63597
- if (providerIndent === -1) providerIndent = indent;
63598
- if (indent === providerIndent) {
63599
- const m2 = /^([A-Za-z0-9_.-]+):/.exec(trimmed);
63600
- currentProvider = m2 ? m2[1] : null;
63601
- } else if (indent > providerIndent && currentProvider) {
63602
- const baseMatch = /^(baseUrl:\s*)(\S+)/.exec(trimmed);
63603
- if (baseMatch) {
63604
- const target = httpMap.get(currentProvider) ?? httpsMap.get(currentProvider);
63605
- if (target) {
63606
- const leading = rawLine.slice(0, rawLine.length - rawLine.trimStart().length);
63607
- const commentMatch = /\s+#.*$/.exec(rawLine);
63608
- const comment = commentMatch ? commentMatch[0] : "";
63609
- lines[i] = `${leading}baseUrl: ${target}${comment}`;
63610
- }
63611
- }
63612
- }
63613
- }
63614
- writeOverlayFileAtomic(overlay, "models.yml", lines.join("\n"));
63615
- }
63616
- writeOmpCompactionDisabledConfig(ompHome, overlay);
63617
- return overlay;
63618
- }
63619
- function prepareHermesHome(hermesHome, origin, rewrites) {
63620
- if (rewrites.length === 0) return void 0;
63621
- const cfgPath = path15.join(hermesHome, "config.yaml");
63622
- let txt;
63623
- try {
63624
- txt = fs12.readFileSync(cfgPath, "utf8");
63625
- } catch {
63626
- return void 0;
63627
- }
63628
- const wrapSet = new Set(rewrites.map((r) => r.realUpstream));
63629
- const eol = txt.includes("\r\n") ? "\r\n" : "\n";
63630
- const lines = txt.split(/\r?\n/);
63631
- let changed = false;
63632
- for (let i = 0; i < lines.length; i++) {
63633
- const rawLine = lines[i];
63634
- const m2 = /^(\s*(?:api|base_url|url):\s*)(\S+)(\s+#.*)?$/.exec(rawLine);
63635
- if (!m2) continue;
63636
- const rawUrl = m2[2].replace(/^["']|["']$/g, "");
63637
- if (!/^https?:\/\//i.test(rawUrl)) continue;
63638
- const real = unwrapUpstream2(rawUrl);
63639
- if (!wrapSet.has(real)) continue;
63640
- lines[i] = `${m2[1]}${wrapUpstream(origin, real)}${m2[3] ?? ""}`;
63641
- changed = true;
63642
- }
63643
- if (!changed) return void 0;
63644
- const overlay = `${hermesHome}-bili`;
63645
- if (!refreshOverlayHome(hermesHome, overlay, "config.yaml")) return void 0;
63646
- writeOverlayFileAtomic(overlay, "config.yaml", lines.join(eol));
63647
- return overlay;
63648
- }
63649
- function writeHermesIdentityPlugin(hermesHome, overlay, rewrites) {
63650
- try {
63651
- if (fs12.existsSync(path15.join(hermesHome, "plugins"))) return false;
63652
- } catch {
63653
- return false;
63654
- }
63655
- const dir = path15.join("plugins", "model-providers", "bili-session-identity");
63656
- try {
63657
- fs12.mkdirSync(path15.join(overlay, dir), { recursive: true });
63658
- } catch {
63659
- return false;
63660
- }
63661
- const keys = rewrites.map((r) => r.key);
63662
- const pluginDir = path15.join(overlay, dir);
63663
- try {
63664
- fs12.writeFileSync(path15.join(pluginDir, "__init__.py"), hermesIdentityPluginSource(keys));
63665
- fs12.writeFileSync(path15.join(pluginDir, "plugin.yaml"), [
63666
- "name: bili-session-identity",
63667
- "kind: model-provider",
63668
- "version: 1.0.0",
63669
- "description: billion-context session identity (installed by `bili hermes` \u2014 safe to delete)",
63670
- "author: billion-context",
63671
- ""
63672
- ].join("\n"));
63673
- } catch {
63674
- return false;
63675
- }
63676
- try {
63677
- return fs12.existsSync(path15.join(overlay, dir, "__init__.py"));
63678
- } catch {
63679
- return false;
63680
- }
63681
- }
63682
- function hermesIdentityPluginSource(providerKeys) {
63683
- return [
63684
- '"""Installed by `bili hermes` (billion-context) \u2014 safe to delete.',
63685
- "",
63686
- "Re-registers hermes's `custom` provider profile (plus a `custom:<name>`",
63687
- "profile for every provider the proxy fronts) with one addition: the",
63688
- "REAL hermes session id is sent as `prompt_cache_key` on every request,",
63689
- "which the billion-context proxy uses as the stable conversation",
63690
- "identity for its compression sessions. Removing this file restores",
63691
- 'stock behavior (no prompt_cache_key)."""',
63692
- "from providers import get_provider_profile, register_provider",
63693
- "from providers.base import ProviderProfile",
63694
- "",
63695
- `_PROVIDER_KEYS = ${JSON.stringify(providerKeys)}`,
63696
- "",
63697
- '_custom = get_provider_profile("custom")',
63698
- "_BASE = type(_custom) if _custom is not None else ProviderProfile",
63699
- "",
63700
- "",
63701
- "class _BiliSessionIdentity(_BASE):",
63702
- " def build_api_kwargs_extras(self, *, session_id=None, **ctx):",
63703
- " extra_body, top_level = _BASE.build_api_kwargs_extras(",
63704
- " self, session_id=session_id, **ctx",
63705
- " )",
63706
- ' sid = str(session_id or "").strip()',
63707
- " if sid:",
63708
- ' top_level.setdefault("prompt_cache_key", sid[:64])',
63709
- " return extra_body, top_level",
63710
- "",
63711
- "",
63712
- "def _register(name, template, with_aliases):",
63713
- " fields = {}",
63714
- " if template is not None:",
63715
- " # Aliases are copied ONLY for the canonical custom",
63716
- " # re-registration: copying them onto custom:<name> entries",
63717
- " # would steal the aliases (register_provider re-points each",
63718
- " # alias at the registering name).",
63719
- ' for attr in (("aliases",) if with_aliases else ()) + ("env_vars", "base_url", "default_max_tokens"):',
63720
- " value = getattr(template, attr, None)",
63721
- " if value:",
63722
- " fields[attr] = value",
63723
- " register_provider(_BiliSessionIdentity(name=name, **fields))",
63724
- "",
63725
- "",
63726
- "if _custom is not None:",
63727
- ' _register("custom", _custom, True)',
63728
- "for _key in _PROVIDER_KEYS:",
63729
- ' _register("custom:" + _key, _custom, False)',
63730
- ""
63731
- ].join("\n");
63732
- }
63733
63566
  function prepareDshHome(dshHome, origin, rewrites) {
63734
63567
  const cfgPath = path15.join(dshHome, "settings.yaml");
63735
63568
  let txt;
@@ -63878,7 +63711,29 @@ async function probeExistingInstance(readInstance, fetchHealthInfo) {
63878
63711
  if (health.instanceId !== void 0 && health.instanceId !== inst.instanceId) return void 0;
63879
63712
  return inst;
63880
63713
  }
63881
- var INHERITED_PROXY_VARS = ["http_proxy", "https_proxy", "all_proxy", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY"];
63714
+ function pickEphemeralPort(host = LAUNCHER_DEFAULT_HOST) {
63715
+ return new Promise((resolve, reject) => {
63716
+ const srv = net2.createServer();
63717
+ srv.once("error", reject);
63718
+ srv.listen(0, host, () => {
63719
+ const addr = srv.address();
63720
+ srv.close(() => {
63721
+ if (addr && typeof addr === "object") resolve(addr.port);
63722
+ else reject(new Error("could not allocate a free port"));
63723
+ });
63724
+ });
63725
+ });
63726
+ }
63727
+ var INHERITED_PROXY_VARS = [
63728
+ "http_proxy",
63729
+ "https_proxy",
63730
+ "all_proxy",
63731
+ "HTTP_PROXY",
63732
+ "HTTPS_PROXY",
63733
+ "ALL_PROXY",
63734
+ "no_proxy",
63735
+ "NO_PROXY"
63736
+ ];
63882
63737
  function stripInheritedProxy(env) {
63883
63738
  const cleaned = { ...env };
63884
63739
  for (const key of INHERITED_PROXY_VARS) delete cleaned[key];
@@ -63903,7 +63758,7 @@ async function ensureProxyRunning(opts, deps = {}) {
63903
63758
  return { origin: existing.origin, port: existing.port, attached: true };
63904
63759
  }
63905
63760
  const launchToken = randomUUID5();
63906
- const port = opts.port;
63761
+ const port = opts.port > 0 ? opts.port : await pickEphemeralPort(opts.host);
63907
63762
  const script = process.argv[1];
63908
63763
  if (!script) throw new Error("bili: cannot resolve launcher script path");
63909
63764
  const logPath2 = path15.join(os5.tmpdir(), `bili-proxy-${port}.log`);
@@ -63912,7 +63767,7 @@ async function ensureProxyRunning(opts, deps = {}) {
63912
63767
  try {
63913
63768
  child = spawnImpl(
63914
63769
  process.execPath,
63915
- [script, ...proxyStartArgs(opts)],
63770
+ [script, ...proxyStartArgs({ ...opts, port })],
63916
63771
  {
63917
63772
  detached: true,
63918
63773
  stdio: ["ignore", logFd, logFd],
@@ -64019,8 +63874,9 @@ function resolveClientCommand(client, env) {
64019
63874
  return { command: resolved ?? client, prefixArgs: [] };
64020
63875
  }
64021
63876
  function parsePort(raw) {
64022
- const port = raw && raw.trim() ? parseInt(raw, 10) : LAUNCHER_DEFAULT_PORT;
64023
- if (!Number.isFinite(port) || port <= 0 || port > 65535) {
63877
+ if (!raw || !raw.trim()) return 0;
63878
+ const port = parseInt(raw, 10);
63879
+ if (!Number.isFinite(port) || port < 1 || port > 65535) {
64024
63880
  console.error(`bili: invalid --port "${raw}"`);
64025
63881
  process.exit(2);
64026
63882
  }
@@ -64031,9 +63887,30 @@ async function runLaunch(params, deps = {}) {
64031
63887
  const port = parsePort(params.overrides.ACP_PORT ?? process.env.ACP_PORT);
64032
63888
  const passthrough = params.overrides.ACP_PASSTHROUGH === "1";
64033
63889
  const debug = params.overrides.ACP_DEBUG === "1";
64034
- const config = loadClientConfig(process.env, process.cwd());
64035
63890
  const base = baseClientName(params.client);
63891
+ const discoveryEnv = base === "pi" || base === "omp" ? { ...process.env, PI_CODING_AGENT_DIR: void 0 } : process.env;
63892
+ const config = loadClientConfig(discoveryEnv, process.cwd());
64036
63893
  const routes = discoverRoutes(base, config);
63894
+ const piRealHome = resolvePiHome({ ...process.env, PI_CODING_AGENT_DIR: void 0 });
63895
+ const ompRealHome = resolveOmpHome({ ...process.env, PI_CODING_AGENT_DIR: void 0 });
63896
+ if (base === "pi" && (routes.httpRewrites.length > 0 || routes.httpsRewrites.length > 0)) {
63897
+ const piExt = selfDistFile("agent/pi.js");
63898
+ const extAvailable = piExt !== void 0 && fs12.existsSync(piExt) || piPluginInstalled(piRealHome);
63899
+ if (!extAvailable) {
63900
+ throw new Error(
63901
+ "bili: pi needs provider URL rewrites but the bili extension cannot load (dist/agent/pi.js missing and the plugin is not installed in ~/.pi/agent/settings.json) \u2014 reinstall billion-context or run `bili plugin install pi`"
63902
+ );
63903
+ }
63904
+ }
63905
+ if (base === "omp" && routes.httpRewrites.length > 0) {
63906
+ const ompExt = selfDistFile("agent/omp.js");
63907
+ const extAvailable = ompExt !== void 0 && fs12.existsSync(ompExt) || ompPluginLoadedFrom(ompRealHome);
63908
+ if (!extAvailable) {
63909
+ throw new Error(
63910
+ "bili: omp needs provider URL rewrites but the bili extension cannot load (dist/agent/omp.js missing and the plugin is not installed in ~/.omp/agent/config.yml) \u2014 reinstall billion-context or run `bili plugin install omp`"
63911
+ );
63912
+ }
63913
+ }
64037
63914
  const biliRoutes = loadRoutes(process.env);
64038
63915
  const domains = dedupeInOrder([...routes.httpsDomains, ...params.mitmDomains ?? []]);
64039
63916
  const handle2 = await ensureProxyRunning({ host, port, passthrough, debug, mitmDomains: domains, modelWindows: collectModelWindows(config, base) }, deps);
@@ -64046,10 +63923,7 @@ async function runLaunch(params, deps = {}) {
64046
63923
  const ca = resolveCaCertPath(process.env);
64047
63924
  let env;
64048
63925
  let clientArgs = params.clientArgs;
64049
- let piOverlayHome;
64050
- let ompOverlayHome;
64051
63926
  let opencodeTmpFile;
64052
- let hermesOverlayHome;
64053
63927
  let dshOverlayHome;
64054
63928
  const tmpFiles = [];
64055
63929
  const directUrl = launcherDirectUrl(process.env);
@@ -64077,19 +63951,17 @@ async function runLaunch(params, deps = {}) {
64077
63951
  }
64078
63952
  const origin = handle2.origin;
64079
63953
  if (base === "pi") {
64080
- env = buildPiEnv(origin, ca, process.env);
64081
- piOverlayHome = preparePiHttpRewrite(resolvePiHome(process.env), origin, routes.httpRewrites, routes.httpsRewrites);
64082
- if (piOverlayHome) env.PI_CODING_AGENT_DIR = piOverlayHome;
63954
+ env = buildPiEnv(origin, ca, process.env, routes.httpRewrites, routes.httpsRewrites);
63955
+ delete env.PI_CODING_AGENT_DIR;
64083
63956
  const piExt = selfDistFile("agent/pi.js");
64084
- if (piExt && fs12.existsSync(piExt) && !piPluginInstalled(resolvePiHome(process.env))) {
63957
+ if (piExt && fs12.existsSync(piExt) && !piPluginInstalled(piRealHome)) {
64085
63958
  clientArgs = ["-e", piExt, ...clientArgs];
64086
63959
  }
64087
63960
  } else if (base === "omp") {
64088
- env = buildPiEnv(origin, ca, process.env);
64089
- ompOverlayHome = prepareOmpHttpRewrite(resolveOmpHome(process.env), origin, routes.httpRewrites, routes.httpsRewrites);
64090
- if (ompOverlayHome) env.PI_CODING_AGENT_DIR = ompOverlayHome;
63961
+ env = buildPiEnv(origin, ca, process.env, routes.httpRewrites);
63962
+ delete env.PI_CODING_AGENT_DIR;
64091
63963
  const ompExt = selfDistFile("agent/omp.js");
64092
- if (ompExt && fs12.existsSync(ompExt) && !ompPluginLoadedFrom(resolveOmpHome(process.env))) {
63964
+ if (ompExt && fs12.existsSync(ompExt) && !ompPluginLoadedFrom(ompRealHome)) {
64093
63965
  clientArgs = ["-e", ompExt, ...clientArgs];
64094
63966
  }
64095
63967
  } else if (base === "opencode") {
@@ -64099,18 +63971,12 @@ async function runLaunch(params, deps = {}) {
64099
63971
  opencodeTmpFile = prepareOpencodeHttpRewrite(resolveOpencodeConfigFile(process.env), origin, routes.httpRewrites, routes.httpsRewrites, opencodePluginPath);
64100
63972
  if (opencodeTmpFile) env.OPENCODE_CONFIG = opencodeTmpFile;
64101
63973
  } else if (base === "hermes") {
64102
- env = { ...process.env };
64103
- hermesOverlayHome = prepareHermesHome(resolveHermesHome(process.env), origin, routes.httpRewrites);
64104
- if (hermesOverlayHome) {
64105
- env.HERMES_HOME = hermesOverlayHome;
64106
- if (!writeHermesIdentityPlugin(resolveHermesHome(process.env), hermesOverlayHome, routes.httpRewrites)) {
64107
- console.error(
64108
- "bili: ~/.hermes has its own plugins/ \u2014 skipping the session-identity plugin; hermes requests will be rejected by the proxy without an identity (see #286)."
64109
- );
64110
- }
64111
- } else {
63974
+ env = stripInheritedProxy(process.env);
63975
+ env.HTTPS_PROXY = origin;
63976
+ env.HERMES_CA_BUNDLE = ca;
63977
+ if (routes.httpRewrites.length === 0 && routes.httpsDomains.length === 0) {
64112
63978
  console.error(
64113
- 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."
63979
+ "bili: no hermes providers found in ~/.hermes/config.yaml \u2014 traffic will NOT go through the proxy (configure a provider first)."
64114
63980
  );
64115
63981
  }
64116
63982
  } else if (base === "dsh") {
@@ -64248,7 +64114,7 @@ async function runTestPi(params, deps = {}) {
64248
64114
  }
64249
64115
 
64250
64116
  // src/export.ts
64251
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
64117
+ import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
64252
64118
  import path16 from "path";
64253
64119
  function fmtDate(ms2) {
64254
64120
  return ms2 ? new Date(ms2).toISOString().replace("T", " ").slice(0, 19) + " UTC" : "\u2014";
@@ -64381,8 +64247,8 @@ async function exportSession(selector, opts = {}) {
64381
64247
  }
64382
64248
  const markdown = renderHandoff(matches[0], opts.full ?? false);
64383
64249
  if (opts.output) {
64384
- mkdirSync5(path16.dirname(path16.resolve(opts.output)), { recursive: true });
64385
- writeFileSync4(opts.output, markdown, "utf8");
64250
+ mkdirSync6(path16.dirname(path16.resolve(opts.output)), { recursive: true });
64251
+ writeFileSync5(opts.output, markdown, "utf8");
64386
64252
  return `written to ${opts.output}`;
64387
64253
  }
64388
64254
  return markdown;
@@ -64459,7 +64325,7 @@ Launcher (bili pi / bili codex / bili claude / bili omp / bili opencode / bili h
64459
64325
  Options (override config file / env):
64460
64326
  -F <url> upstream proxy to forward through (gost-style;
64461
64327
  http://host:port; must precede the client name)
64462
- --port <N> listen port (default 8787)
64328
+ --port <N> listen port (start: 8787; launcher default: random free port)
64463
64329
  --host <ADDR> listen host (default 127.0.0.1)
64464
64330
  --mitm-domain <domain> extra MITM domain (repeatable; launcher only)
64465
64331
  --config <FILE> path to config JSON (default: XDG location)