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 +359 -79
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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:
|
|
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.
|
|
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
|
|
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
|
|
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 =
|
|
54977
|
-
|
|
54978
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
55731
|
+
if (typeof body === "string" && (opts.debug || bodyDumpEnabled())) {
|
|
55548
55732
|
try {
|
|
55549
55733
|
const parsed = JSON.parse(body);
|
|
55550
|
-
|
|
55551
|
-
const
|
|
55552
|
-
|
|
55553
|
-
|
|
55554
|
-
|
|
55555
|
-
|
|
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")
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
59496
|
+
await rm2(tmpFile, { force: true });
|
|
59309
59497
|
}
|
|
59310
59498
|
const backupDir = path10.join(cacheDir(), `.update-backup-${version2}`);
|
|
59311
59499
|
try {
|
|
59312
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
60693
|
-
console.error(
|
|
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) {
|