billion-context 0.1.57 → 0.1.58

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
@@ -47137,6 +47137,81 @@ var import_undici = __toESM(require_undici(), 1);
47137
47137
  import { execFileSync } from "child_process";
47138
47138
  import net from "net";
47139
47139
  import tls from "tls";
47140
+
47141
+ // src/log-mask.ts
47142
+ var PRIVATE_HOST = "<private-host>";
47143
+ var PUBLIC_HOST_SUFFIXES = [
47144
+ "openai.com",
47145
+ "chatgpt.com",
47146
+ "anthropic.com",
47147
+ "deepseek.com",
47148
+ "googleapis.com",
47149
+ "azure.com",
47150
+ "amazonaws.com",
47151
+ "mistral.ai",
47152
+ "groq.com",
47153
+ "cohere.com",
47154
+ "together.ai",
47155
+ "fireworks.ai",
47156
+ "x.ai",
47157
+ "openrouter.ai",
47158
+ "huggingface.co",
47159
+ "moonshot.ai",
47160
+ "zhipuai.com",
47161
+ "volcengine.com",
47162
+ "aliyuncs.com",
47163
+ "baidu.com",
47164
+ "baidubce.com",
47165
+ "minimax.io"
47166
+ ];
47167
+ var CREDENTIAL_HEADER_RE = /key|auth|token|cookie/i;
47168
+ function isPublicApiHost(host) {
47169
+ const h = host.replace(/^\[|\]$/g, "").toLowerCase();
47170
+ return PUBLIC_HOST_SUFFIXES.some((s3) => h === s3 || h.endsWith(`.${s3}`));
47171
+ }
47172
+ function maskHostForLog(host) {
47173
+ return isPublicApiHost(host) ? host : PRIVATE_HOST;
47174
+ }
47175
+ function maskUrlForLog(url) {
47176
+ let u2;
47177
+ try {
47178
+ u2 = new URL(url);
47179
+ } catch {
47180
+ return "<unparseable-url>";
47181
+ }
47182
+ const host = isPublicApiHost(u2.hostname) ? u2.host : `${PRIVATE_HOST}${u2.port ? `:${u2.port}` : ""}`;
47183
+ return `${u2.protocol}//${host}${u2.pathname}`;
47184
+ }
47185
+ function maskUrlsInText(text) {
47186
+ return text.replace(/https?:\/\/[^\s"'<>]+/gi, (m2) => maskUrlForLog(m2));
47187
+ }
47188
+ function maskHostPortForLog(target) {
47189
+ const i = target.lastIndexOf(":");
47190
+ if (i <= 0) return maskHostForLog(target);
47191
+ return `${maskHostForLog(target.slice(0, i))}${target.slice(i)}`;
47192
+ }
47193
+ function maskHeaderForLog(name, value) {
47194
+ if (name.toLowerCase() === "host") return maskHostPortForLog(value);
47195
+ if (CREDENTIAL_HEADER_RE.test(name)) return `<masked ${value.length} chars>`;
47196
+ return value;
47197
+ }
47198
+ function maskHeadersForLog(headers) {
47199
+ const out = {};
47200
+ for (const [k2, v2] of Object.entries(headers)) out[k2] = maskHeaderForLog(k2, v2);
47201
+ return out;
47202
+ }
47203
+ function maskHostInText(text, host) {
47204
+ if (!host || isPublicApiHost(host)) return text;
47205
+ const bare = host.replace(/^\[|\]$/g, "");
47206
+ const forms = /* @__PURE__ */ new Set([host, bare]);
47207
+ if (bare.includes(":")) forms.add(`[${bare}]`);
47208
+ for (const form of forms) {
47209
+ if (form.length > 0) text = text.split(form).join(PRIVATE_HOST);
47210
+ }
47211
+ return text;
47212
+ }
47213
+
47214
+ // src/upstream-proxy.ts
47140
47215
  var dispatcherCache = /* @__PURE__ */ new Map();
47141
47216
  var lastConnection = {};
47142
47217
  var windowsProxyCache;
@@ -47460,13 +47535,20 @@ function formatUpstreamError(error, url, proxyUrl) {
47460
47535
  const chain = errorChain(error);
47461
47536
  const fields = ["code", "errno", "syscall", "address", "port"];
47462
47537
  const parts = [];
47538
+ const maskHostIn = (s3) => {
47539
+ try {
47540
+ return maskHostInText(s3, new URL(url).hostname);
47541
+ } catch {
47542
+ }
47543
+ return s3;
47544
+ };
47463
47545
  for (const field of fields) {
47464
47546
  const value = chain.find((entry) => entry[field] !== void 0)?.[field];
47465
- if (value !== void 0) parts.push(`${field}=${String(value)}`);
47547
+ if (value !== void 0) parts.push(`${field}=${maskHostIn(String(value))}`);
47466
47548
  }
47467
- const messages = chain.map((entry) => String(entry.message ?? "")).filter(Boolean);
47549
+ const messages = chain.map((entry) => maskHostIn(String(entry.message ?? ""))).filter(Boolean);
47468
47550
  if (messages.length > 0) parts.push(`message=${messages.join(" <- ")}`);
47469
- parts.push(`url=${url}`);
47551
+ parts.push(`url=${maskUrlForLog(url)}`);
47470
47552
  parts.push(`proxy=${redactProxyUrl(proxyUrl) ?? "direct"}`);
47471
47553
  return parts.join(" ");
47472
47554
  }
@@ -47520,9 +47602,7 @@ function lookupContextLimit(model) {
47520
47602
  }
47521
47603
  return void 0;
47522
47604
  }
47523
- function resolveContextLimit(routes, upstreamUrl, model) {
47524
- return resolveConfiguredContextLimit(routes, upstreamUrl, model) ?? lookupContextLimit(model);
47525
- }
47605
+ var FALLBACK_EFFECTIVE_WINDOW_FLOOR = 1e5;
47526
47606
  function findRoute(routes, upstreamUrl) {
47527
47607
  if (!upstreamUrl) return void 0;
47528
47608
  let bestKey = "";
@@ -48149,6 +48229,18 @@ function registryLookup(reg, model, host) {
48149
48229
  const ctx = entry?.limit?.context;
48150
48230
  if (typeof ctx === "number" && ctx > 0) return ctx;
48151
48231
  }
48232
+ if (provider === void 0) {
48233
+ const suffix = `/${model}`;
48234
+ let agreed;
48235
+ for (const key of Object.keys(reg)) {
48236
+ if (!key.endsWith(suffix)) continue;
48237
+ const ctx = reg[key].limit?.context;
48238
+ if (typeof ctx !== "number" || ctx <= 0) continue;
48239
+ if (agreed === void 0) agreed = ctx;
48240
+ else if (agreed !== ctx) return void 0;
48241
+ }
48242
+ return agreed;
48243
+ }
48152
48244
  return void 0;
48153
48245
  }
48154
48246
 
@@ -48890,6 +48982,7 @@ function subagentNamespace(identityValue, instructions) {
48890
48982
 
48891
48983
  // src/persist.ts
48892
48984
  import { createHash as createHash3 } from "crypto";
48985
+ import { rm } from "fs/promises";
48893
48986
  import * as path5 from "path";
48894
48987
 
48895
48988
  // node_modules/acp-kernel/dist/persist/index.js
@@ -49266,12 +49359,14 @@ function relPathFor(id, protocol, upstreamOrigin) {
49266
49359
  }
49267
49360
  var SessionStore = class {
49268
49361
  enabled;
49362
+ dir;
49269
49363
  store;
49270
49364
  constructor(opts) {
49271
49365
  const debounceMs = opts?.debounceMs ?? defaultDebounce();
49272
49366
  this.enabled = (opts?.enabled ?? true) && debounceMs >= 0;
49367
+ this.dir = opts?.dir ?? defaultDir();
49273
49368
  this.store = new StateStore({
49274
- dir: opts?.dir ?? defaultDir(),
49369
+ dir: this.dir,
49275
49370
  version: PERSIST_VERSION,
49276
49371
  debounceMs: Math.max(0, debounceMs),
49277
49372
  enabled: this.enabled,
@@ -49295,6 +49390,71 @@ var SessionStore = class {
49295
49390
  }
49296
49391
  return out;
49297
49392
  }
49393
+ /** One-time migration for the #286 identity change: sessions persisted
49394
+ * under the old derived hash id are re-keyed to the client-provided
49395
+ * conversation value stored in meta.label (which is now the session id
49396
+ * itself). Collisions on the same label keep the most recently saved
49397
+ * record; the losers, and any label already claimed by a new-format
49398
+ * session, are deleted. Records without a label cannot be mapped and are
49399
+ * left in place (they load under their old id but are never requested
49400
+ * again — the new proxy 400s anonymous requests). Self-terminating:
49401
+ * after one pass no loaded id differs from its label. */
49402
+ async migrateLegacyIds() {
49403
+ if (!this.enabled) return;
49404
+ const loaded = await this.store.loadAll();
49405
+ const claimed = /* @__PURE__ */ new Set();
49406
+ const byLabel = /* @__PURE__ */ new Map();
49407
+ let unlabeled = 0;
49408
+ for (const [id, envelope] of loaded) {
49409
+ const session = buildSession(envelope.payload);
49410
+ const label = session.meta.label;
49411
+ if (!label) {
49412
+ unlabeled++;
49413
+ continue;
49414
+ }
49415
+ if (label === id) {
49416
+ claimed.add(id);
49417
+ continue;
49418
+ }
49419
+ const prev = byLabel.get(label);
49420
+ if (!prev || envelope.savedAt >= prev.savedAt) {
49421
+ if (prev) await this.removeLegacyFile(prev.id, prev.session);
49422
+ byLabel.set(label, { id, savedAt: envelope.savedAt, session });
49423
+ } else {
49424
+ await this.removeLegacyFile(id, session);
49425
+ }
49426
+ }
49427
+ let rekeyed = 0;
49428
+ for (const [label, { id, session }] of byLabel) {
49429
+ if (claimed.has(label)) {
49430
+ await this.removeLegacyFile(id, session);
49431
+ continue;
49432
+ }
49433
+ session.id = label;
49434
+ await this.store.writeNow(label, () => buildRecord(session));
49435
+ await this.removeLegacyFile(id, session);
49436
+ claimed.add(label);
49437
+ rekeyed++;
49438
+ }
49439
+ if (rekeyed || unlabeled) {
49440
+ log("info", `[persist] one-time migration (#286): rekeyed ${rekeyed} legacy session(s), left ${unlabeled} unlabeled legacy file(s) in place`);
49441
+ }
49442
+ }
49443
+ /** Remove a legacy session file. The kernel store never deletes (cleanup
49444
+ * is downstream policy), so the path is recomputed here: the namespaced
49445
+ * layout for v2+/v3 files, the _unknown/ fallback, and the flat default
49446
+ * name for pre-envelope v1 files. */
49447
+ async removeLegacyFile(id, session) {
49448
+ const candidates = /* @__PURE__ */ new Set([
49449
+ relPathFor(id, session.meta.protocol, session.meta.upstreamOrigin),
49450
+ relPathFor(id),
49451
+ flatFileNameFor(id)
49452
+ ]);
49453
+ for (const rel2 of candidates) {
49454
+ await rm(path5.join(this.dir, rel2), { force: true }).catch(() => {
49455
+ });
49456
+ }
49457
+ }
49298
49458
  /** Synchronous reload of a single session. Used on a memory miss (after
49299
49459
  * LRU eviction). Sync fs is acceptable here because a miss is rare and
49300
49460
  * reads a single small file (~1ms). Returns null if missing/corrupt or the
@@ -49439,6 +49599,7 @@ async function initSessions() {
49439
49599
  initialized = true;
49440
49600
  const store = getStore();
49441
49601
  if (!store.enabled) return;
49602
+ await store.migrateLegacyIds();
49442
49603
  const loaded = await store.loadAll();
49443
49604
  if (loaded.size > MAX_SESSIONS) {
49444
49605
  const entries = [...loaded.entries()].sort((a, b2) => (b2[1].createdAt ?? 0) - (a[1].createdAt ?? 0));
@@ -50707,7 +50868,7 @@ async function* runCompressLoop(upstream, ctx, requestBody, requestOptions, adap
50707
50868
  }
50708
50869
  );
50709
50870
  let newBody = adapter.buildRequest(coreMessages, systemPrompt, requestBody);
50710
- if (process.env.ACP_DUMP_REQ !== "0" && ctx.debug) {
50871
+ if (process.env.ACP_DUMP_BODY === "1") {
50711
50872
  try {
50712
50873
  const fs11 = await import("fs");
50713
50874
  const dumpDir = process.env.ACP_DUMP_DIR || `${process.env.HOME}/.local/state/billion-context/dumps`;
@@ -52383,13 +52544,6 @@ data: ${JSON.stringify({ type: "message_stop" })}
52383
52544
  }
52384
52545
 
52385
52546
  // src/session-id.ts
52386
- function extractKey(headers) {
52387
- const auth = headers["authorization"];
52388
- if (typeof auth === "string" && auth.length > 0) return auth.trim();
52389
- const apiKey = headers["x-api-key"];
52390
- if (typeof apiKey === "string" && apiKey.length > 0) return `key:${apiKey.trim()}`;
52391
- return "(no-key)";
52392
- }
52393
52547
  function clientConversationHeader(headers) {
52394
52548
  const pluginMarker = typeof headers["x-bili-plugin"] === "string";
52395
52549
  const names = ["x-bili-plugin-conversation", "x-claude-code-session-id", "x-session-affinity", "x-acp-session", "x-session-id", "x-opencode-session", "session-id", "session_id"];
@@ -52400,11 +52554,6 @@ function clientConversationHeader(headers) {
52400
52554
  }
52401
52555
  return void 0;
52402
52556
  }
52403
- function deriveSessionId(headers, protocol, upstream, conversation) {
52404
- if (!conversation) throw new Error("deriveSessionId: conversation dimension is required (pass the conversationSignal* output)");
52405
- const key = extractKey(headers);
52406
- return hashId2(`${protocol}|${upstream}|${key}|${conversation}`);
52407
- }
52408
52557
  function affinityToken(identity) {
52409
52558
  return identity.clientProvided ? identity.value : void 0;
52410
52559
  }
@@ -53746,7 +53895,7 @@ function setupMitm(server, extraDomains = [], log2 = () => {
53746
53895
  server.on("connect", (req, clientSocket, head) => {
53747
53896
  const remote = !isLoopbackAddress(clientSocket.remoteAddress);
53748
53897
  if (remote && !allowRemoteClients) {
53749
- log2(`CONNECT ${req.url} rejected: non-loopback client ${clientSocket.remoteAddress} (bind a non-loopback --host to allow remote clients)`);
53898
+ log2(`CONNECT ${maskHostPortForLog(req.url ?? "")} rejected: non-loopback client ${clientSocket.remoteAddress} (bind a non-loopback --host to allow remote clients)`);
53750
53899
  clientSocket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
53751
53900
  return;
53752
53901
  }
@@ -53754,7 +53903,7 @@ function setupMitm(server, extraDomains = [], log2 = () => {
53754
53903
  const targetPort = port || 443;
53755
53904
  if (!isMitmHost(hostname, extraDomains)) {
53756
53905
  if (remote) {
53757
- log2(`CONNECT ${req.url} rejected: remote client ${clientSocket.remoteAddress} tunneling non-whitelisted host`);
53906
+ log2(`CONNECT ${maskHostPortForLog(req.url ?? "")} rejected: remote client ${clientSocket.remoteAddress} tunneling non-whitelisted host`);
53758
53907
  clientSocket.end("HTTP/1.1 403 Forbidden\r\n\r\n");
53759
53908
  return;
53760
53909
  }
@@ -53779,7 +53928,7 @@ function tunnelThrough(clientSocket, host, port, head, log2, proxyUrl) {
53779
53928
  const connectTimer = setTimeout(() => {
53780
53929
  if (!established) {
53781
53930
  aborted = true;
53782
- log2(`tunnel ${host}:${port} connect timeout`);
53931
+ log2(`tunnel ${maskHostForLog(host)}:${port} connect timeout`);
53783
53932
  clientSocket.write("HTTP/1.1 504 Gateway Timeout\r\n\r\n");
53784
53933
  clientSocket.destroy();
53785
53934
  }
@@ -53796,7 +53945,7 @@ function tunnelThrough(clientSocket, host, port, head, log2, proxyUrl) {
53796
53945
  upstream.pipe(clientSocket);
53797
53946
  clientSocket.pipe(upstream);
53798
53947
  const cleanup = (where, err2) => {
53799
- log2(`tunnel ${host}:${port} ${where} closed: ${err2.message}`);
53948
+ log2(`tunnel ${maskHostForLog(host)}:${port} ${where} closed: ${maskHostInText(err2.message, host)}`);
53800
53949
  upstream.destroy();
53801
53950
  clientSocket.destroy();
53802
53951
  };
@@ -53805,7 +53954,7 @@ function tunnelThrough(clientSocket, host, port, head, log2, proxyUrl) {
53805
53954
  }).catch((err2) => {
53806
53955
  if (aborted) return;
53807
53956
  clearTimeout(connectTimer);
53808
- log2(`tunnel ${host}:${port} connect failed: ${err2.message}`);
53957
+ log2(`tunnel ${maskHostForLog(host)}:${port} connect failed: ${maskHostInText(err2.message, host)}`);
53809
53958
  clientSocket.write("HTTP/1.1 502 Bad Gateway\r\n\r\n");
53810
53959
  clientSocket.destroy();
53811
53960
  });
@@ -53823,18 +53972,18 @@ function doMitm(server, clientSocket, host, port, head, log2) {
53823
53972
  ALPNProtocols: ["http/1.1"]
53824
53973
  });
53825
53974
  } catch (e) {
53826
- log2(`mitm ${host}:${port} TLS setup failed: ${e.message}`);
53975
+ log2(`mitm ${maskHostForLog(host)}:${port} TLS setup failed: ${e.message}`);
53827
53976
  clientSocket.destroy();
53828
53977
  return;
53829
53978
  }
53830
53979
  tlsSocket[MITM_UPSTREAM_KEY] = `https://${host}`;
53831
53980
  tlsSocket.on("error", (err2) => {
53832
- log2(`mitm ${host}:${port} TLS error: ${err2.message}`);
53981
+ log2(`mitm ${maskHostForLog(host)}:${port} TLS error: ${err2.message}`);
53833
53982
  tlsSocket.destroy();
53834
53983
  clientSocket.destroy();
53835
53984
  });
53836
53985
  const handshakeTimer = setTimeout(() => {
53837
- log2(`mitm ${host}:${port} TLS handshake timeout`);
53986
+ log2(`mitm ${maskHostForLog(host)}:${port} TLS handshake timeout`);
53838
53987
  tlsSocket.destroy();
53839
53988
  clientSocket.destroy();
53840
53989
  }, mitmHandshakeTimeoutMs());
@@ -53842,7 +53991,7 @@ function doMitm(server, clientSocket, host, port, head, log2) {
53842
53991
  tlsSocket.once("close", () => clearTimeout(handshakeTimer));
53843
53992
  tlsSocket.once("error", () => clearTimeout(handshakeTimer));
53844
53993
  server.emit("connection", tlsSocket);
53845
- log2(`mitm ${host}:${port} tunnel established (TLS terminated locally)`);
53994
+ log2(`mitm ${maskHostForLog(host)}:${port} tunnel established (TLS terminated locally)`);
53846
53995
  }
53847
53996
  function readMitmUpstream(socket) {
53848
53997
  if (!socket) return void 0;
@@ -54567,6 +54716,9 @@ async function decodeRequestBody(contentEncoding, input, maxOutputBytes) {
54567
54716
  }
54568
54717
 
54569
54718
  // src/server.ts
54719
+ function bodyDumpEnabled() {
54720
+ return process.env.ACP_DUMP_BODY === "1";
54721
+ }
54570
54722
  function parseLauncherModelWindows(raw) {
54571
54723
  if (!raw) return {};
54572
54724
  try {
@@ -54585,6 +54737,10 @@ var LAUNCHER_MODEL_WINDOWS = parseLauncherModelWindows(process.env.BILI_LAUNCHER
54585
54737
  function launcherContextWindow(model) {
54586
54738
  return LAUNCHER_MODEL_WINDOWS[model];
54587
54739
  }
54740
+ function safeSessionId(id) {
54741
+ return (id ?? "unknown").replace(/[^a-zA-Z0-9._-]/g, "_");
54742
+ }
54743
+ var NO_IDENTITY_MESSAGE = "Missing stable conversation identity. Send one of the headers: x-session-id, x-session-affinity, x-acp-session, x-opencode-session, x-claude-code-session-id, session-id \u2014 or body session_id / prompt_cache_key (responses/openai). Content-fingerprint sessions are disabled (#286).";
54588
54744
  var UPSTREAM_HOP_HEADERS = /* @__PURE__ */ new Set([
54589
54745
  "host",
54590
54746
  "content-length",
@@ -54681,7 +54837,7 @@ async function startServer(opts) {
54681
54837
  }
54682
54838
  });
54683
54839
  server.on("upgrade", (req, socket) => {
54684
- log2("info", `[ws] rejected ${req.method} ${req.url ?? ""} host=${req.headers.host ?? "?"} with 426`);
54840
+ log2("info", `[ws] rejected ${req.method} ${maskUrlsInText(req.url ?? "")} host=${req.headers.host ? maskHostPortForLog(req.headers.host) : "?"} with 426`);
54685
54841
  socket.on("error", () => {
54686
54842
  });
54687
54843
  const body = JSON.stringify({ error: "WebSocket upgrades are not supported; use HTTP POST" });
@@ -54963,25 +55119,31 @@ async function handle(req, res, opts, core, config, log2) {
54963
55119
  }
54964
55120
  }
54965
55121
  if (opts.debug && parsed && typeof parsed === "object") {
55122
+ const p2 = parsed;
55123
+ const hasPrev = p2.previous_response_id !== void 0;
55124
+ const inLen = Array.isArray(p2.input) ? p2.input.length : 0;
55125
+ log2("info", `[debug] INCOMING previous_response_id=${hasPrev ? String(p2.previous_response_id).slice(0, 16) : "absent"} input_items=${inLen} instructions=${p2.instructions !== void 0 ? "present" : "absent"}`);
55126
+ }
55127
+ if (bodyDumpEnabled() && parsed && typeof parsed === "object") {
54966
55128
  try {
54967
- const p2 = parsed;
54968
- const hasPrev = p2.previous_response_id !== void 0;
54969
- const inLen = Array.isArray(p2.input) ? p2.input.length : 0;
54970
- log2("info", `[debug] INCOMING previous_response_id=${hasPrev ? String(p2.previous_response_id).slice(0, 16) : "absent"} input_items=${inLen} instructions=${p2.instructions !== void 0 ? "present" : "absent"}`);
54971
- const rawDir = `${stateDir()}/raw`;
55129
+ const rawDir = process.env.ACP_RAW_DUMP_DIR || `${stateDir()}/raw`;
54972
55130
  try {
54973
55131
  fs6.mkdirSync(rawDir, { recursive: true });
54974
55132
  } catch {
54975
55133
  }
54976
- const hdrs = Object.entries(req.headers).filter(([k2]) => !/authorization|x-api-key|cookie/i.test(k2)).map(([k2, v2]) => `${k2}: ${Array.isArray(v2) ? v2.join(",") : v2}`).join("\n");
54977
- fs6.writeFileSync(`${rawDir}/${Date.now()}-INCOMING.txt`, `${req.method} ${req.url}
54978
- ${hdrs}
55134
+ const hdrs = maskHeadersForLog(
55135
+ Object.fromEntries(Object.entries(req.headers).map(([k2, v2]) => [k2, Array.isArray(v2) ? v2.join(",") : String(v2)]))
55136
+ );
55137
+ const hdrText = Object.entries(hdrs).map(([k2, v2]) => `${k2}: ${v2}`).join("\n");
55138
+ fs6.writeFileSync(`${rawDir}/${Date.now()}-INCOMING.txt`, `${req.method} ${maskUrlsInText(req.url ?? "")}
55139
+ ${hdrText}
54979
55140
 
54980
55141
  ${bodyBuffer.toString("utf8")}`);
54981
55142
  } catch {
54982
55143
  }
54983
55144
  }
54984
55145
  let reqConfig = config;
55146
+ let nativeFromFallback = false;
54985
55147
  let reqPrompts = defaultPrompts;
54986
55148
  if (parsed && typeof parsed === "object") {
54987
55149
  const model = parsed.model;
@@ -54994,9 +55156,16 @@ ${bodyBuffer.toString("utf8")}`);
54994
55156
  return void 0;
54995
55157
  }
54996
55158
  })();
54997
- let native = pluginReportedContextWindow(req.headers) ?? launcherContextWindow(model) ?? (host ? peekRegistryContext(model, host) : void 0) ?? resolveContextLimit(opts.routes, embeddedUrl, model);
55159
+ const pluginWindow = pluginReportedContextWindow(req.headers);
55160
+ const launcherWindow = launcherContextWindow(model);
55161
+ const peekWindow = host ? peekRegistryContext(model, host) : void 0;
55162
+ const configuredWindow = resolveConfiguredContextLimit(opts.routes, embeddedUrl, model);
55163
+ let native = pluginWindow ?? launcherWindow ?? peekWindow ?? configuredWindow ?? lookupContextLimit(model);
55164
+ const operatorWindowTuned = resolveCompress(opts.routes, embeddedUrl, model, opts.compress).modelContextLimit !== void 0;
55165
+ nativeFromFallback = !pluginWindow && !launcherWindow && !peekWindow && !configuredWindow && !operatorWindowTuned;
54998
55166
  if (!native && host) {
54999
55167
  native = await contextFromRegistry(model, host);
55168
+ if (native) nativeFromFallback = false;
55000
55169
  }
55001
55170
  reqConfig = resolveRequestConfig(config, opts.routes, embeddedUrl, model, native, opts.compress);
55002
55171
  reqPrompts = resolveCompressPrompts(resolveCompress(opts.routes, embeddedUrl, model, opts.compress));
@@ -55022,7 +55191,14 @@ ${bodyBuffer.toString("utf8")}`);
55022
55191
  responsesIdentity?.value ?? conversationSignalResponses(parsed, convHeader),
55023
55192
  parsed.instructions
55024
55193
  );
55025
- const sessionId = deriveSessionId(req.headers, protocol, upstreamOrigin, conversation);
55194
+ const clientProvided = protocol === "responses" ? responsesIdentity?.clientProvided ?? false : protocol === "openai" ? openaiIdentity?.clientProvided ?? false : !!convHeader;
55195
+ if (!clientProvided) {
55196
+ log2("warn", `400: no stable conversation identity on ${protocol} request \u2192 ${upstreamOrigin}; refusing to create a content-fingerprint session (#286)`);
55197
+ res.writeHead(400, { "content-type": "application/json" });
55198
+ res.end(JSON.stringify(protocol === "anthropic" ? { type: "error", error: { type: "invalid_request_error", message: NO_IDENTITY_MESSAGE } } : { error: { type: "invalid_request_error", message: NO_IDENTITY_MESSAGE } }));
55199
+ return;
55200
+ }
55201
+ const sessionId = conversation;
55026
55202
  const bodyIdentity = responsesIdentity ?? openaiIdentity;
55027
55203
  const affinity = affinityToken(bodyIdentity ?? {
55028
55204
  value: clientConv ?? conversation,
@@ -55065,19 +55241,24 @@ ${bodyBuffer.toString("utf8")}`);
55065
55241
  if (learnedLimit && learnedLimit > 0 && learnedLimit < reqConfig.modelContextLimit) {
55066
55242
  const resolved = reqConfig.modelContextLimit;
55067
55243
  reqConfig = { ...reqConfig, modelContextLimit: learnedLimit };
55244
+ nativeFromFallback = false;
55068
55245
  log2("info", `[${session.id}] self-healed context window: ${resolved} \u2192 ${learnedLimit} (learned from an upstream overflow)`);
55069
55246
  }
55070
55247
  if (shouldReserveOutputHeadroom(protocol)) {
55071
55248
  const p2 = parsed;
55072
55249
  const rawMax = p2.max_tokens ?? p2.max_completion_tokens ?? p2.max_output_tokens;
55073
55250
  const maxOutput = typeof rawMax === "number" ? rawMax : 0;
55074
- const reserved = reserveOutputHeadroom(reqConfig.modelContextLimit, maxOutput);
55251
+ let reserved = reserveOutputHeadroom(reqConfig.modelContextLimit, maxOutput);
55252
+ if (nativeFromFallback && reserved < FALLBACK_EFFECTIVE_WINDOW_FLOOR) {
55253
+ log2("info", `[${session.id}] fallback context window floored: ${reserved} \u2192 ${FALLBACK_EFFECTIVE_WINDOW_FLOOR} (model=${String(p2.model ?? "?")} not authoritatively identified; self-heal corrects it if the real window is smaller)`);
55254
+ reserved = FALLBACK_EFFECTIVE_WINDOW_FLOOR;
55255
+ }
55075
55256
  if (reserved !== reqConfig.modelContextLimit) reqConfig = { ...reqConfig, modelContextLimit: reserved };
55076
55257
  }
55077
55258
  acquireInFlight(session);
55078
55259
  try {
55079
55260
  await withSessionLock(session, async () => {
55080
- const runPrepare = () => countTokens ? prepareCountTokens(parsed, core, reqConfig, log2, session) : protocol === "anthropic" ? prepareAnthropic(parsed, req, opts, core, reqConfig, reqPrompts, log2, session, pluginMode) : protocol === "openai" ? prepareOpenai(parsed, req, opts, core, reqConfig, reqPrompts, log2, session, pluginMode) : responsesCompact ? prepareResponsesCompact(bodyBuffer, parsed, session) : prepareResponses(parsed, req, opts, core, reqConfig, reqPrompts, log2, session, responsesIdentity, pluginMode);
55261
+ const runPrepare = () => countTokens ? prepareCountTokens(parsed, core, reqConfig, log2, session) : protocol === "anthropic" ? prepareAnthropic(parsed, req, opts, core, reqConfig, reqPrompts, log2, session, pluginMode) : protocol === "openai" ? prepareOpenai(parsed, req, opts, core, reqConfig, reqPrompts, log2, session, pluginMode) : responsesCompact ? prepareResponsesCompact(bodyBuffer, parsed, session) : prepareResponses(parsed, req, opts, core, reqConfig, reqPrompts, log2, session, responsesIdentity, pluginMode, upstreamOrigin);
55081
55262
  prepared = runPrepare();
55082
55263
  if (!countTokens && !responsesCompact) {
55083
55264
  prepared = await preflightCompressIfNeeded(
@@ -55105,7 +55286,7 @@ ${bodyBuffer.toString("utf8")}`);
55105
55286
  }
55106
55287
  if (!prepared) {
55107
55288
  if (protocol === null && !opts.passthrough) {
55108
- log2("warn", `unrecognized path ${req.url ?? ""} \u2014 not a known protocol (/chat/completions, /v1/messages, /responses, /responses/compact); forwarding unchanged`);
55289
+ log2("warn", `unrecognized path ${maskUrlsInText(req.url ?? "")} \u2014 not a known protocol (/chat/completions, /v1/messages, /responses, /responses/compact); forwarding unchanged`);
55109
55290
  }
55110
55291
  await forward(req, res, opts, bodyBuffer, null, core, reqConfig, log2, route, void 0);
55111
55292
  }
@@ -55254,6 +55435,7 @@ function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session,
55254
55435
  processedMessages = [];
55255
55436
  }
55256
55437
  const rebuilt = { ...parsed, messages: rebuiltMessages, tools: toolsOut };
55438
+ delete rebuilt.prompt_cache_retention;
55257
55439
  if (stream2 && rebuilt.stream_options === void 0) {
55258
55440
  rebuilt.stream_options = { include_usage: true };
55259
55441
  }
@@ -55261,7 +55443,7 @@ function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session,
55261
55443
  markDirty(session);
55262
55444
  return { body: JSON.stringify(rebuilt), session, processedMessages, originalMessages, protocol: "openai", stream: stream2, compressInjected: injectTools, pluginMode, nudge, prompts, openaiSystemText };
55263
55445
  }
55264
- function prepareResponses(parsed, req, opts, core, config, prompts, log2, session, identity, pluginMode) {
55446
+ function prepareResponses(parsed, req, opts, core, config, prompts, log2, session, identity, pluginMode, upstreamOrigin) {
55265
55447
  const sessionId = session.id;
55266
55448
  const stream2 = parsed.stream === true;
55267
55449
  ++session.stats.requests;
@@ -55285,7 +55467,7 @@ function prepareResponses(parsed, req, opts, core, config, prompts, log2, sessio
55285
55467
  }
55286
55468
  const shouldInject = opts.compress.injectTool;
55287
55469
  const injectTools = shouldInject && !pluginMode;
55288
- const responsesTextProtocol = FORCE_TEXT_PROTOCOL || resolveCompressProtocol(opts.routes, session.meta.upstreamOrigin) === "marker";
55470
+ const responsesTextProtocol = FORCE_TEXT_PROTOCOL || resolveCompressProtocol(opts.routes, upstreamOrigin) === "marker";
55289
55471
  try {
55290
55472
  const projection = responsesToCore(parsed);
55291
55473
  responsesProjection = projection;
@@ -55320,7 +55502,8 @@ function prepareResponses(parsed, req, opts, core, config, prompts, log2, sessio
55320
55502
  } else if (projection.systemParts.length > 0) {
55321
55503
  rebuiltInput = injectResponsesDeveloperMessage(rebuiltInput, projection.systemParts.join("\n\n---\n\n"));
55322
55504
  }
55323
- if (opts.compress.injectNudge && turn.nudge?.shouldInject && shouldInject) {
55505
+ const triggerFinal = Array.isArray(rebuiltInput) && rebuiltInput.length > 0 && rebuiltInput[rebuiltInput.length - 1].type === "compaction_trigger";
55506
+ if (opts.compress.injectNudge && turn.nudge?.shouldInject && shouldInject && !triggerFinal) {
55324
55507
  try {
55325
55508
  const rendered = renderNudgeText(turn.nudge, prompts);
55326
55509
  if (rendered.text) {
@@ -55340,11 +55523,12 @@ function prepareResponses(parsed, req, opts, core, config, prompts, log2, sessio
55340
55523
  rebuilt.prompt_cache_key,
55341
55524
  identity,
55342
55525
  opts.promptCache.routing,
55343
- session.meta.upstreamOrigin
55526
+ upstreamOrigin
55344
55527
  );
55345
55528
  if (promptCacheKey && !rebuilt.prompt_cache_key) rebuilt.prompt_cache_key = promptCacheKey;
55346
55529
  if (process.env.ACP_KEEP_RESPONSE_ID !== "1") delete rebuilt.previous_response_id;
55347
55530
  delete rebuilt.instructions;
55531
+ delete rebuilt.prompt_cache_retention;
55348
55532
  if (process.env.ACP_DEBUG) {
55349
55533
  const fwdTools = (Array.isArray(toolsOut) ? toolsOut : []).map((t) => {
55350
55534
  const r = t;
@@ -55530,7 +55714,7 @@ async function preflightCompressIfNeeded(prepared, runPrepare, req, res, opts, c
55530
55714
  }
55531
55715
  async function forward(req, res, opts, body, prepared, core, config, log2, route, affinity) {
55532
55716
  const { upstreamUrl, headers, proxyUrl } = buildForwardTarget(req, opts, route, affinity);
55533
- log2("info", `forward ${req.method} \u2192 ${upstreamUrl}`);
55717
+ log2("info", `forward ${req.method} \u2192 ${maskUrlForLog(upstreamUrl)}`);
55534
55718
  if (process.env.ACP_DEBUG && prepared) {
55535
55719
  const sid = prepared.session.id;
55536
55720
  const hdrKeys = Object.keys(req.headers);
@@ -55544,22 +55728,24 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
55544
55728
  }
55545
55729
  }
55546
55730
  }
55547
- if (opts.debug && typeof body === "string") {
55731
+ if (typeof body === "string" && (opts.debug || bodyDumpEnabled())) {
55548
55732
  try {
55549
55733
  const parsed = JSON.parse(body);
55550
- const toolNames = (parsed.tools ?? []).map((t) => {
55551
- const fn = t.function;
55552
- return fn?.name ?? t.name ?? "?";
55553
- });
55554
- log2("info", `[debug] tools=[${toolNames.join(",")}] msgs=${parsed.messages?.length ?? 0} stream=${parsed.stream ?? false} system_len=${JSON.stringify(parsed.messages?.find((m2) => m2.role === "system")?.content ?? "").length}`);
55555
- if (process.env.ACP_DUMP_REQ !== "0") {
55734
+ if (opts.debug) {
55735
+ const toolNames = (parsed.tools ?? []).map((t) => {
55736
+ const fn = t.function;
55737
+ return fn?.name ?? t.name ?? "?";
55738
+ });
55739
+ log2("info", `[debug] tools=[${toolNames.join(",")}] msgs=${parsed.messages?.length ?? 0} stream=${parsed.stream ?? false} system_len=${JSON.stringify(parsed.messages?.find((m2) => m2.role === "system")?.content ?? "").length}`);
55740
+ }
55741
+ if (bodyDumpEnabled() && process.env.ACP_DUMP_REQ !== "0") {
55556
55742
  const dumpDir = process.env.ACP_DUMP_DIR || `${stateDir()}/dumps`;
55557
55743
  try {
55558
55744
  fs6.mkdirSync(dumpDir, { recursive: true });
55559
55745
  } catch {
55560
55746
  }
55561
55747
  const sid = prepared?.session.id ?? "unknown";
55562
- const out = `${dumpDir}/req-${Date.now()}-${sid}.json`;
55748
+ const out = `${dumpDir}/req-${Date.now()}-${safeSessionId(sid)}.json`;
55563
55749
  try {
55564
55750
  const pretty = JSON.stringify(JSON.parse(body), null, 2);
55565
55751
  fs6.writeFileSync(out, pretty);
@@ -55574,26 +55760,28 @@ async function forward(req, res, opts, body, prepared, core, config, log2, route
55574
55760
  if (opts.debug) {
55575
55761
  const hdrLog = {};
55576
55762
  for (const [hk, hv] of Object.entries(headers)) {
55577
- if (typeof hv === "string") hdrLog[hk] = hv.length > 200 ? hv.slice(0, 200) + "..." : hv;
55763
+ if (typeof hv === "string") {
55764
+ const masked = maskHeaderForLog(hk, hv);
55765
+ hdrLog[hk] = masked.length > 200 ? masked.slice(0, 200) + "..." : masked;
55766
+ }
55578
55767
  }
55579
55768
  log2("info", `[${prepared?.session.id ?? "unknown"}] \u2192 upstream headers: ${JSON.stringify(hdrLog)}`);
55580
55769
  }
55581
- const rawBase = opts.debug ? (() => {
55770
+ const rawBase = bodyDumpEnabled() ? (() => {
55582
55771
  try {
55583
55772
  const rawDir = process.env.ACP_RAW_DUMP_DIR || `${stateDir()}/raw`;
55584
55773
  fs6.mkdirSync(rawDir, { recursive: true });
55585
- return `${rawDir}/${Date.now()}-${prepared?.session.id ?? "unknown"}`;
55774
+ return `${rawDir}/${Date.now()}-${safeSessionId(prepared?.session.id)}`;
55586
55775
  } catch {
55587
55776
  return "";
55588
55777
  }
55589
55778
  })() : "";
55590
55779
  if (rawBase) {
55591
55780
  try {
55592
- const maskHdr = (k2, v2) => /key|auth|token/i.test(k2) ? `<masked ${v2.length} chars>` : v2;
55593
- const hdrText = Object.entries(headers).map(([k2, v2]) => `${k2}: ${maskHdr(k2, String(v2))}`).join("\n");
55781
+ const hdrText = Object.entries(maskHeadersForLog(headers)).map(([k2, v2]) => `${k2}: ${v2}`).join("\n");
55594
55782
  const bodyText = req.method === "GET" || req.method === "HEAD" ? "" : typeof body === "string" ? body : Buffer.from(body).toString("utf8");
55595
55783
  const reqPath = `${rawBase}-REQ.txt`;
55596
- fs6.writeFileSync(reqPath, `${req.method ?? "POST"} ${upstreamUrl}
55784
+ fs6.writeFileSync(reqPath, `${req.method ?? "POST"} ${maskUrlForLog(upstreamUrl)}
55597
55785
  ${hdrText}
55598
55786
 
55599
55787
  ${bodyText}`);
@@ -55633,14 +55821,14 @@ ${bodyText}`);
55633
55821
  upstream.headers.forEach((v2, k2) => {
55634
55822
  const lower = k2.toLowerCase();
55635
55823
  if (UPSTREAM_HOP_HEADERS.has(lower) || respConnNamed.has(lower)) return;
55636
- respLog[k2] = v2.length > 300 ? v2.slice(0, 300) + "..." : v2;
55824
+ const masked = maskHeaderForLog(k2, v2);
55825
+ respLog[k2] = masked.length > 300 ? masked.slice(0, 300) + "..." : masked;
55637
55826
  });
55638
55827
  log2("info", `[${prepared?.session.id ?? "unknown"}] \u2190 upstream response headers: ${JSON.stringify(respLog)}`);
55639
55828
  }
55640
55829
  if (rawBase) {
55641
55830
  try {
55642
- const maskHdr = (k2, v2) => /key|auth|token/i.test(k2) ? `<masked ${v2.length} chars>` : v2;
55643
- const hdrText = Object.entries(respHeaders).map(([k2, v2]) => `${k2}: ${maskHdr(k2, v2)}`).join("\n");
55831
+ const hdrText = Object.entries(maskHeadersForLog(respHeaders)).map(([k2, v2]) => `${k2}: ${v2}`).join("\n");
55644
55832
  const resPath = `${rawBase}-RES.txt`;
55645
55833
  fs6.writeFileSync(resPath, `${upstream.status}
55646
55834
  ${hdrText}
@@ -55747,7 +55935,7 @@ ${hdrText}
55747
55935
  if (opts.dumpSse) {
55748
55936
  const [a, b2] = upstream.body.tee();
55749
55937
  streamToRead = a;
55750
- dumpRaw = dumpStreamToFile(b2, opts.dumpSse, `${Date.now()}-${prepared.session.id}-raw.sse`);
55938
+ dumpRaw = dumpStreamToFile(b2, opts.dumpSse, `${Date.now()}-${safeSessionId(prepared.session.id)}-raw.sse`);
55751
55939
  }
55752
55940
  try {
55753
55941
  const parsedReq = JSON.parse(typeof body === "string" ? body : body.toString("utf8"));
@@ -55976,7 +56164,7 @@ function logMsg(opts, level, msg) {
55976
56164
  }
55977
56165
 
55978
56166
  // src/update.ts
55979
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, access, constants, rm, cp, unlink } from "fs/promises";
56167
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, access, constants, rm as rm2, cp, unlink } from "fs/promises";
55980
56168
  import { execFile } from "child_process";
55981
56169
  import crypto from "crypto";
55982
56170
 
@@ -59055,7 +59243,7 @@ async function syntaxCheckEntry(entryAbs) {
59055
59243
  return null;
59056
59244
  } finally {
59057
59245
  try {
59058
- await rm(tmpCheck, { force: true });
59246
+ await rm2(tmpCheck, { force: true });
59059
59247
  } catch {
59060
59248
  }
59061
59249
  }
@@ -59140,7 +59328,7 @@ async function tryAcquireLock() {
59140
59328
  release: async () => {
59141
59329
  const current = await readLock();
59142
59330
  if (current?.pid === pid) {
59143
- await rm(LOCK_FILE, { force: true }).catch(() => {
59331
+ await rm2(LOCK_FILE, { force: true }).catch(() => {
59144
59332
  });
59145
59333
  }
59146
59334
  }
@@ -59287,7 +59475,7 @@ async function installViaTarball(version2, tarballUrl, installDir, integrity, sh
59287
59475
  }
59288
59476
  const stagingDir = path10.join(cacheDir(), `.update-staging-${version2}`);
59289
59477
  try {
59290
- await rm(stagingDir, { recursive: true, force: true });
59478
+ await rm2(stagingDir, { recursive: true, force: true });
59291
59479
  await mkdir2(stagingDir, { recursive: true });
59292
59480
  await So({
59293
59481
  file: tmpFile,
@@ -59305,18 +59493,18 @@ async function installViaTarball(version2, tarballUrl, installDir, integrity, sh
59305
59493
  } catch (e) {
59306
59494
  return { ok: false, error: `extraction failed: ${String(e)}` };
59307
59495
  } finally {
59308
- await rm(tmpFile, { force: true });
59496
+ await rm2(tmpFile, { force: true });
59309
59497
  }
59310
59498
  const backupDir = path10.join(cacheDir(), `.update-backup-${version2}`);
59311
59499
  try {
59312
- await rm(backupDir, { recursive: true, force: true });
59500
+ await rm2(backupDir, { recursive: true, force: true });
59313
59501
  await cp(installDir, backupDir, { recursive: true, force: true });
59314
59502
  } catch (e) {
59315
59503
  return { ok: false, error: `backup of current install failed (install left untouched): ${String(e)}` };
59316
59504
  }
59317
59505
  const restoreFromBackup = async () => {
59318
59506
  try {
59319
- await rm(installDir, { recursive: true, force: true });
59507
+ await rm2(installDir, { recursive: true, force: true });
59320
59508
  await cp(backupDir, installDir, { recursive: true, force: true });
59321
59509
  return null;
59322
59510
  } catch (e) {
@@ -59329,7 +59517,7 @@ async function installViaTarball(version2, tarballUrl, installDir, integrity, sh
59329
59517
  } catch (e) {
59330
59518
  copyError = `failed to copy to install dir: ${String(e)}`;
59331
59519
  } finally {
59332
- await rm(stagingDir, { recursive: true, force: true });
59520
+ await rm2(stagingDir, { recursive: true, force: true });
59333
59521
  }
59334
59522
  if (copyError !== null) {
59335
59523
  const rb2 = await restoreFromBackup();
@@ -59348,7 +59536,7 @@ async function installViaTarball(version2, tarballUrl, installDir, integrity, sh
59348
59536
  const rb2 = await restoreFromBackup();
59349
59537
  return { ok: false, error: rb2 ?? postEntryErr };
59350
59538
  }
59351
- await rm(backupDir, { recursive: true, force: true });
59539
+ await rm2(backupDir, { recursive: true, force: true });
59352
59540
  return { ok: true };
59353
59541
  }
59354
59542
  function startAutoUpdate(opts) {
@@ -60033,7 +60221,7 @@ function launcherDirectUrl(env) {
60033
60221
  return env.BILI_LAUNCHER_DIRECT === "1";
60034
60222
  }
60035
60223
  function launcherInjectMcp(env, base) {
60036
- return base !== "pi" && base !== "omp" && base !== "opencode" && base !== "hermes" && base !== "dsh" && env.BILI_LAUNCHER_PLUGIN === "1";
60224
+ return base !== "pi" && base !== "omp" && base !== "opencode" && base !== "hermes" && base !== "dsh" && env.BILI_LAUNCHER_PLUGIN !== "0";
60037
60225
  }
60038
60226
  function buildMcpConfig(origin) {
60039
60227
  const script = selfDistFile("mcp.js");
@@ -60369,7 +60557,7 @@ function prepareHermesHome(hermesHome, origin, rewrites) {
60369
60557
  const rawLine = lines[i];
60370
60558
  const m2 = /^(\s*(?:api|base_url|url):\s*)(\S+)(\s+#.*)?$/.exec(rawLine);
60371
60559
  if (!m2) continue;
60372
- const rawUrl = m2[2];
60560
+ const rawUrl = m2[2].replace(/^["']|["']$/g, "");
60373
60561
  if (!/^https?:\/\//i.test(rawUrl)) continue;
60374
60562
  const real = unwrapUpstream2(rawUrl);
60375
60563
  if (!wrapSet.has(real)) continue;
@@ -60382,6 +60570,90 @@ function prepareHermesHome(hermesHome, origin, rewrites) {
60382
60570
  writeOverlayFileAtomic(overlay, "config.yaml", lines.join(eol));
60383
60571
  return overlay;
60384
60572
  }
60573
+ function writeHermesIdentityPlugin(hermesHome, overlay, rewrites) {
60574
+ try {
60575
+ if (fs10.existsSync(path13.join(hermesHome, "plugins"))) return false;
60576
+ } catch {
60577
+ return false;
60578
+ }
60579
+ const dir = path13.join("plugins", "model-providers", "bili-session-identity");
60580
+ try {
60581
+ fs10.mkdirSync(path13.join(overlay, dir), { recursive: true });
60582
+ } catch {
60583
+ return false;
60584
+ }
60585
+ const keys = rewrites.map((r) => r.key);
60586
+ const pluginDir = path13.join(overlay, dir);
60587
+ try {
60588
+ fs10.writeFileSync(path13.join(pluginDir, "__init__.py"), hermesIdentityPluginSource(keys));
60589
+ fs10.writeFileSync(path13.join(pluginDir, "plugin.yaml"), [
60590
+ "name: bili-session-identity",
60591
+ "kind: model-provider",
60592
+ "version: 1.0.0",
60593
+ "description: billion-context session identity (installed by `bili hermes` \u2014 safe to delete)",
60594
+ "author: billion-context",
60595
+ ""
60596
+ ].join("\n"));
60597
+ } catch {
60598
+ return false;
60599
+ }
60600
+ try {
60601
+ return fs10.existsSync(path13.join(overlay, dir, "__init__.py"));
60602
+ } catch {
60603
+ return false;
60604
+ }
60605
+ }
60606
+ function hermesIdentityPluginSource(providerKeys) {
60607
+ return [
60608
+ '"""Installed by `bili hermes` (billion-context) \u2014 safe to delete.',
60609
+ "",
60610
+ "Re-registers hermes's `custom` provider profile (plus a `custom:<name>`",
60611
+ "profile for every provider the proxy fronts) with one addition: the",
60612
+ "REAL hermes session id is sent as `prompt_cache_key` on every request,",
60613
+ "which the billion-context proxy uses as the stable conversation",
60614
+ "identity for its compression sessions. Removing this file restores",
60615
+ 'stock behavior (no prompt_cache_key)."""',
60616
+ "from providers import get_provider_profile, register_provider",
60617
+ "from providers.base import ProviderProfile",
60618
+ "",
60619
+ `_PROVIDER_KEYS = ${JSON.stringify(providerKeys)}`,
60620
+ "",
60621
+ '_custom = get_provider_profile("custom")',
60622
+ "_BASE = type(_custom) if _custom is not None else ProviderProfile",
60623
+ "",
60624
+ "",
60625
+ "class _BiliSessionIdentity(_BASE):",
60626
+ " def build_api_kwargs_extras(self, *, session_id=None, **ctx):",
60627
+ " extra_body, top_level = _BASE.build_api_kwargs_extras(",
60628
+ " self, session_id=session_id, **ctx",
60629
+ " )",
60630
+ ' sid = str(session_id or "").strip()',
60631
+ " if sid:",
60632
+ ' top_level.setdefault("prompt_cache_key", sid[:64])',
60633
+ " return extra_body, top_level",
60634
+ "",
60635
+ "",
60636
+ "def _register(name, template, with_aliases):",
60637
+ " fields = {}",
60638
+ " if template is not None:",
60639
+ " # Aliases are copied ONLY for the canonical custom",
60640
+ " # re-registration: copying them onto custom:<name> entries",
60641
+ " # would steal the aliases (register_provider re-points each",
60642
+ " # alias at the registering name).",
60643
+ ' for attr in (("aliases",) if with_aliases else ()) + ("env_vars", "base_url", "default_max_tokens"):',
60644
+ " value = getattr(template, attr, None)",
60645
+ " if value:",
60646
+ " fields[attr] = value",
60647
+ " register_provider(_BiliSessionIdentity(name=name, **fields))",
60648
+ "",
60649
+ "",
60650
+ "if _custom is not None:",
60651
+ ' _register("custom", _custom, True)',
60652
+ "for _key in _PROVIDER_KEYS:",
60653
+ ' _register("custom:" + _key, _custom, False)',
60654
+ ""
60655
+ ].join("\n");
60656
+ }
60385
60657
  function prepareDshHome(dshHome, origin, rewrites) {
60386
60658
  const cfgPath = path13.join(dshHome, "settings.yaml");
60387
60659
  let txt;
@@ -60689,8 +60961,10 @@ async function runLaunch(params, deps = {}) {
60689
60961
  }
60690
60962
  }
60691
60963
  const injectMcp = launcherInjectMcp(process.env, base);
60692
- if (!injectMcp && (base === "claude" || base === "codex")) {
60693
- console.error("bili: plugin mode off \u2014 set BILI_LAUNCHER_PLUGIN=1 to enable native MCP tools (experimental; verified with claude 2.1.227 / codex 0.147.0).");
60964
+ if (injectMcp) {
60965
+ console.error(`bili: injecting native bili MCP tools for ${base} (disable with BILI_LAUNCHER_PLUGIN=0).`);
60966
+ } else if (base === "claude" || base === "codex") {
60967
+ console.error("bili: native MCP tools disabled (BILI_LAUNCHER_PLUGIN=0) \u2014 running in pure wire mode.");
60694
60968
  }
60695
60969
  const origin = handle2.origin;
60696
60970
  if (base === "pi") {
@@ -60720,6 +60994,11 @@ async function runLaunch(params, deps = {}) {
60720
60994
  hermesOverlayHome = prepareHermesHome(resolveHermesHome(process.env), origin, routes.httpRewrites);
60721
60995
  if (hermesOverlayHome) {
60722
60996
  env.HERMES_HOME = hermesOverlayHome;
60997
+ if (!writeHermesIdentityPlugin(resolveHermesHome(process.env), hermesOverlayHome, routes.httpRewrites)) {
60998
+ console.error(
60999
+ "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)."
61000
+ );
61001
+ }
60723
61002
  } else {
60724
61003
  console.error(
60725
61004
  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."
@@ -60728,6 +61007,7 @@ async function runLaunch(params, deps = {}) {
60728
61007
  } else if (base === "dsh") {
60729
61008
  env = { ...process.env, BILLION_CONTEXT_PROXY: origin };
60730
61009
  env.DEEPSEEK_BASE_URL = wrapUpstream(origin, "https://api.deepseek.com");
61010
+ env.PI_CACHE_RETENTION = "long";
60731
61011
  const dshHomeDir = resolveDshHome(process.env);
60732
61012
  dshOverlayHome = prepareDshHome(dshHomeDir, origin, routes.httpRewrites);
60733
61013
  if (dshOverlayHome) {