billion-context 0.1.85 → 0.1.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/README.zh-CN.md +6 -0
- package/dist/index.js +217 -70
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -389,6 +389,23 @@ recommended** for many concurrent conversations because of the collision
|
|
|
389
389
|
risk — until pi grows its own session-id signal. For pi multi-agent use,
|
|
390
390
|
pass an explicit `x-acp-session` header per conversation to avoid collisions.
|
|
391
391
|
|
|
392
|
+
### Windows: exclude the sessions dir from antivirus (#362)
|
|
393
|
+
|
|
394
|
+
The proxy persists each session's compression state to the sessions dir
|
|
395
|
+
(`%USERPROFILE%\.local\share\billion-context\` by default) and rewrites the
|
|
396
|
+
file every turn of a long session. On Windows, real-time antivirus (Windows
|
|
397
|
+
Defender), the search indexer, or a sync tool (OneDrive) can lock that
|
|
398
|
+
directory mid-write, so the rename fails with `EPERM` and every persist for
|
|
399
|
+
that session fails until the lock clears.
|
|
400
|
+
|
|
401
|
+
When the same session fails N consecutive writes (default `5`), the proxy
|
|
402
|
+
logs a one-time, actionable alert naming the directory to exclude. To fix it
|
|
403
|
+
at the root: add `%USERPROFILE%\.local\share\billion-context\` to your
|
|
404
|
+
antivirus **exclusions** (Windows Defender: Settings → Virus & threat
|
|
405
|
+
protection → Manage settings → Exclusions → Add an exclusion → Folder) and
|
|
406
|
+
make sure no sync tool (OneDrive / Dropbox / …) is syncing that path. Full
|
|
407
|
+
steps in [CONFIGURATION.md](CONFIGURATION.md#windows-exclude-the-sessions-dir-from-antivirus-362).
|
|
408
|
+
|
|
392
409
|
## Status
|
|
393
410
|
|
|
394
411
|
Early. Protocol handling and compression work against mock tests (500+ passing). Real-model integration testing is the next milestone. Expect rough edges.
|
package/README.zh-CN.md
CHANGED
|
@@ -265,6 +265,12 @@ Windows 下会自动发现常见 Clash/Mihomo 静态系统代理;Web UI 会显
|
|
|
265
265
|
|
|
266
266
|
**建议:** Codex 和 OpenCode 可以安全地通过代理并发跑很多会话。pi 单个 agent 没问题,但因碰撞风险**不建议**并发多会话 —— 直到 pi 自己长出 session-id 信号。pi 多 agent 场景下,每个会话发一个显式 `x-acp-session` header 来避免碰撞。
|
|
267
267
|
|
|
268
|
+
### Windows:把会话目录加入杀软排除项(#362)
|
|
269
|
+
|
|
270
|
+
代理把每个会话的压缩状态持久化到会话目录(默认 `%USERPROFILE%\.local\share\billion-context\`),长会话每一轮都会重写该文件。在 Windows 上,实时杀毒(Windows Defender)、搜索索引器或同步工具(OneDrive)可能在写入中途锁住该目录,导致 rename 以 `EPERM` 失败,在锁解除前该会话的每次持久化都会失败。
|
|
271
|
+
|
|
272
|
+
当同一会话连续 N 次写失败(默认 `5`)时,代理会打一条一次性、可操作的告警,明确指出要排除的目录。要从根上修复:把 `%USERPROFILE%\.local\share\billion-context\` 加入杀软**排除项**(Windows Defender:设置 → 病毒和威胁防护 → 管理设置 → 排除项 → 添加排除 → 文件夹),并确认没有同步工具(OneDrive / Dropbox / …)在同步该路径。完整步骤见 [CONFIGURATION.zh-CN.md](CONFIGURATION.zh-CN.md) 的「Windows:把会话目录加入杀软排除项」章节。
|
|
273
|
+
|
|
268
274
|
## 状态
|
|
269
275
|
|
|
270
276
|
早期。协议处理和压缩已通过 mock 测试(500+ 项通过)。真实模型集成测试是下一里程碑。预期会有粗糙的地方。
|
package/dist/index.js
CHANGED
|
@@ -47078,7 +47078,7 @@ function caDir() {
|
|
|
47078
47078
|
}
|
|
47079
47079
|
|
|
47080
47080
|
// src/logger.ts
|
|
47081
|
-
import { createWriteStream, fstatSync, mkdirSync, statSync, renameSync } from "fs";
|
|
47081
|
+
import { createWriteStream, fstatSync, mkdirSync, statSync, renameSync, unlinkSync } from "fs";
|
|
47082
47082
|
import path2 from "path";
|
|
47083
47083
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
47084
47084
|
var stream;
|
|
@@ -47094,6 +47094,10 @@ function openStream(file) {
|
|
|
47094
47094
|
existingSize = statSync(file).size;
|
|
47095
47095
|
if (existingSize >= MAX_BYTES) {
|
|
47096
47096
|
try {
|
|
47097
|
+
try {
|
|
47098
|
+
unlinkSync(file + ".old");
|
|
47099
|
+
} catch {
|
|
47100
|
+
}
|
|
47097
47101
|
renameSync(file, file + ".old");
|
|
47098
47102
|
existingSize = 0;
|
|
47099
47103
|
} catch {
|
|
@@ -47738,6 +47742,11 @@ function loadRoutes(env = process.env) {
|
|
|
47738
47742
|
}
|
|
47739
47743
|
return routes;
|
|
47740
47744
|
}
|
|
47745
|
+
function passthroughState(env) {
|
|
47746
|
+
const filePassthrough = loadConfigFile().passthrough === true;
|
|
47747
|
+
if (env.ACP_PASSTHROUGH !== void 0) return { enabled: env.ACP_PASSTHROUGH === "1", source: "env" };
|
|
47748
|
+
return { enabled: filePassthrough, source: filePassthrough ? "file" : null };
|
|
47749
|
+
}
|
|
47741
47750
|
function loadOptions(env = process.env) {
|
|
47742
47751
|
const fileConfig = loadConfigFile();
|
|
47743
47752
|
const port = parseInt(env.ACP_PORT ?? env.PORT ?? `${fileConfig.port ?? 8787}`, 10);
|
|
@@ -47748,6 +47757,7 @@ function loadOptions(env = process.env) {
|
|
|
47748
47757
|
const host = rawHost === "localhost" ? "127.0.0.1" : rawHost;
|
|
47749
47758
|
const upstream = (env.ACP_UPSTREAM ?? fileConfig.upstream ?? "https://api.anthropic.com").replace(/\/$/, "");
|
|
47750
47759
|
const routes = loadRoutes(env);
|
|
47760
|
+
const passthrough = passthroughState(env);
|
|
47751
47761
|
const modelContextLimit = parseInt(env.ACP_MODEL_CONTEXT_LIMIT ?? `${fileConfig.modelContextLimit ?? 2e5}`, 10);
|
|
47752
47762
|
const biliProxy = nonEmpty(env.BILI_UPSTREAM_PROXY);
|
|
47753
47763
|
const webProxy = nonEmpty(fileConfig.upstreamProxy);
|
|
@@ -47802,7 +47812,8 @@ function loadOptions(env = process.env) {
|
|
|
47802
47812
|
log: env.ACP_LOG !== "0" && fileConfig.log !== false,
|
|
47803
47813
|
debug: (env.ACP_DEBUG ?? (fileConfig.debug ? "1" : "0")) === "1",
|
|
47804
47814
|
dumpSse: env.ACP_DUMP_SSE || fileConfig.dumpSse || void 0,
|
|
47805
|
-
passthrough:
|
|
47815
|
+
passthrough: passthrough.enabled,
|
|
47816
|
+
passthroughSource: passthrough.source,
|
|
47806
47817
|
autoUpdate: (env.ACP_AUTO_UPDATE ?? (fileConfig.autoUpdate === false ? "0" : "1")) !== "0",
|
|
47807
47818
|
logFile: env.ACP_LOG_FILE !== void 0 ? env.ACP_LOG_FILE || void 0 : fileConfig.logFile,
|
|
47808
47819
|
mitm: {
|
|
@@ -49475,15 +49486,11 @@ var StateStore = class {
|
|
|
49475
49486
|
}
|
|
49476
49487
|
const file = this.resolvePath(id, payload);
|
|
49477
49488
|
const data = JSON.stringify(this.envelope(id, payload));
|
|
49478
|
-
try {
|
|
49479
|
-
fs.mkdirSync(path4.dirname(file), { recursive: true });
|
|
49480
|
-
} catch (e) {
|
|
49481
|
-
this.log("warn", `[persist] could not create dir ${path4.dirname(file)}: ${errText(e)}`);
|
|
49482
|
-
}
|
|
49483
49489
|
let lastErr;
|
|
49484
49490
|
for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
|
|
49485
49491
|
const tmp = this.tempPath(file);
|
|
49486
49492
|
try {
|
|
49493
|
+
fs.mkdirSync(path4.dirname(file), { recursive: true });
|
|
49487
49494
|
fs.writeFileSync(tmp, data, "utf8");
|
|
49488
49495
|
fs.renameSync(tmp, file);
|
|
49489
49496
|
lastErr = void 0;
|
|
@@ -49494,9 +49501,7 @@ var StateStore = class {
|
|
|
49494
49501
|
fs.unlinkSync(tmp);
|
|
49495
49502
|
} catch {
|
|
49496
49503
|
}
|
|
49497
|
-
|
|
49498
|
-
if (code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") break;
|
|
49499
|
-
if (attempt === this.retryAttempts - 1) break;
|
|
49504
|
+
if (!isTransientFsError(e) || attempt === this.retryAttempts - 1) break;
|
|
49500
49505
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, this.backoffMs(attempt));
|
|
49501
49506
|
}
|
|
49502
49507
|
}
|
|
@@ -49506,14 +49511,19 @@ var StateStore = class {
|
|
|
49506
49511
|
this.removeSpillSync(file);
|
|
49507
49512
|
return true;
|
|
49508
49513
|
}
|
|
49514
|
+
const spill = this.spillPathFor(file);
|
|
49509
49515
|
let spillPath = null;
|
|
49510
|
-
|
|
49511
|
-
|
|
49512
|
-
|
|
49513
|
-
|
|
49514
|
-
|
|
49515
|
-
|
|
49516
|
+
for (let attempt = 0; attempt < this.retryAttempts && spillPath === null; attempt++) {
|
|
49517
|
+
try {
|
|
49518
|
+
fs.mkdirSync(path4.dirname(spill), { recursive: true });
|
|
49519
|
+
fs.writeFileSync(spill, data, "utf8");
|
|
49520
|
+
spillPath = spill;
|
|
49521
|
+
} catch (e) {
|
|
49522
|
+
if (!isTransientFsError(e) || attempt === this.retryAttempts - 1) break;
|
|
49523
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, this.backoffMs(attempt));
|
|
49524
|
+
}
|
|
49516
49525
|
}
|
|
49526
|
+
if (spillPath) this.discovered.set(id, spillPath);
|
|
49517
49527
|
this.recordFailure(id, lastErr, spillPath);
|
|
49518
49528
|
return spillPath !== null;
|
|
49519
49529
|
}
|
|
@@ -49617,33 +49627,45 @@ var StateStore = class {
|
|
|
49617
49627
|
throw e;
|
|
49618
49628
|
}
|
|
49619
49629
|
const file = this.resolvePath(id, payload);
|
|
49620
|
-
|
|
49621
|
-
|
|
49622
|
-
|
|
49623
|
-
|
|
49630
|
+
const data = JSON.stringify(this.envelope(id, payload));
|
|
49631
|
+
let lastErr;
|
|
49632
|
+
for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
|
|
49633
|
+
const tmp = this.tempPath(file);
|
|
49634
|
+
try {
|
|
49635
|
+
await fsp.mkdir(path4.dirname(file), { recursive: true });
|
|
49636
|
+
await fsp.writeFile(tmp, data, "utf8");
|
|
49637
|
+
await fsp.rename(tmp, file);
|
|
49638
|
+
this.discovered.set(id, file);
|
|
49639
|
+
this.clearFailure(id);
|
|
49640
|
+
await this.removeSpill(file);
|
|
49641
|
+
return;
|
|
49642
|
+
} catch (e) {
|
|
49643
|
+
lastErr = e;
|
|
49644
|
+
await fsp.unlink(tmp).catch(() => {
|
|
49645
|
+
});
|
|
49646
|
+
if (!isTransientFsError(e) || attempt === this.retryAttempts - 1) break;
|
|
49647
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
49648
|
+
setTimeout(resolve, this.backoffMs(attempt));
|
|
49649
|
+
await promise;
|
|
49650
|
+
}
|
|
49624
49651
|
}
|
|
49625
|
-
const
|
|
49626
|
-
|
|
49627
|
-
|
|
49628
|
-
await fsp.writeFile(tmp, JSON.stringify(env), "utf8");
|
|
49629
|
-
await this.renameWithRetry(tmp, file);
|
|
49630
|
-
this.discovered.set(id, file);
|
|
49631
|
-
this.clearFailure(id);
|
|
49632
|
-
await this.removeSpill(file);
|
|
49633
|
-
} catch (e) {
|
|
49634
|
-
await fsp.unlink(tmp).catch(() => {
|
|
49635
|
-
});
|
|
49636
|
-
let spillPath = null;
|
|
49652
|
+
const spill = this.spillPathFor(file);
|
|
49653
|
+
let spillPath = null;
|
|
49654
|
+
for (let attempt = 0; attempt < this.retryAttempts && spillPath === null; attempt++) {
|
|
49637
49655
|
try {
|
|
49638
|
-
|
|
49639
|
-
await fsp.writeFile(spill,
|
|
49656
|
+
await fsp.mkdir(path4.dirname(spill), { recursive: true });
|
|
49657
|
+
await fsp.writeFile(spill, data, "utf8");
|
|
49640
49658
|
spillPath = spill;
|
|
49641
|
-
|
|
49642
|
-
|
|
49659
|
+
} catch (e) {
|
|
49660
|
+
if (!isTransientFsError(e) || attempt === this.retryAttempts - 1) break;
|
|
49661
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
49662
|
+
setTimeout(resolve, this.backoffMs(attempt));
|
|
49663
|
+
await promise;
|
|
49643
49664
|
}
|
|
49644
|
-
this.recordFailure(id, e, spillPath);
|
|
49645
|
-
throw e;
|
|
49646
49665
|
}
|
|
49666
|
+
if (spillPath) this.discovered.set(id, spillPath);
|
|
49667
|
+
this.recordFailure(id, lastErr, spillPath);
|
|
49668
|
+
throw lastErr;
|
|
49647
49669
|
}
|
|
49648
49670
|
envelope(id, payload) {
|
|
49649
49671
|
return { version: this.version, savedAt: Date.now(), id, payload };
|
|
@@ -49709,27 +49731,6 @@ var StateStore = class {
|
|
|
49709
49731
|
clearFailure(id) {
|
|
49710
49732
|
this.failCounts.delete(id);
|
|
49711
49733
|
}
|
|
49712
|
-
/** fs.rename with exponential-backoff retries on Windows transient locks
|
|
49713
|
-
* (EPERM/EBUSY/EACCES from AV scan, search indexer, SMB). Other errors
|
|
49714
|
-
* are real and rethrown immediately. */
|
|
49715
|
-
async renameWithRetry(src, dest) {
|
|
49716
|
-
let lastErr;
|
|
49717
|
-
for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
|
|
49718
|
-
try {
|
|
49719
|
-
await fsp.rename(src, dest);
|
|
49720
|
-
return;
|
|
49721
|
-
} catch (e) {
|
|
49722
|
-
lastErr = e;
|
|
49723
|
-
const code = e.code;
|
|
49724
|
-
if (code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
|
|
49725
|
-
if (attempt === this.retryAttempts - 1) throw e;
|
|
49726
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
49727
|
-
setTimeout(resolve, this.backoffMs(attempt));
|
|
49728
|
-
await promise;
|
|
49729
|
-
}
|
|
49730
|
-
}
|
|
49731
|
-
throw lastErr;
|
|
49732
|
-
}
|
|
49733
49734
|
/** Parse + validate one file. Corrupt or invalid records return null
|
|
49734
49735
|
* (logged) instead of throwing — load paths must never block boot. */
|
|
49735
49736
|
readEnvelope(file) {
|
|
@@ -49805,6 +49806,10 @@ function isEnvelopeLike(value) {
|
|
|
49805
49806
|
function flatFileNameFor(id) {
|
|
49806
49807
|
return createHash2("sha256").update(id, "utf8").digest("hex").slice(0, 24) + ".json";
|
|
49807
49808
|
}
|
|
49809
|
+
function isTransientFsError(e) {
|
|
49810
|
+
const code = e.code;
|
|
49811
|
+
return code === "EPERM" || code === "EBUSY" || code === "EACCES" || code === "ENOENT" || code === "ENOTDIR";
|
|
49812
|
+
}
|
|
49808
49813
|
function errText(e) {
|
|
49809
49814
|
return e instanceof Error ? e.message : String(e);
|
|
49810
49815
|
}
|
|
@@ -49813,6 +49818,57 @@ function rel(p2, base) {
|
|
|
49813
49818
|
return r && !r.startsWith("..") ? r : p2;
|
|
49814
49819
|
}
|
|
49815
49820
|
|
|
49821
|
+
// src/persist-eperm.ts
|
|
49822
|
+
var LOCK_CODES = /\b(EPERM|EBUSY|EACCES)\b/;
|
|
49823
|
+
var WRITE_FAIL_RE = /^\[persist\] write failed for (.+?) \(total (\d+)x\): (.+)$/;
|
|
49824
|
+
var PersistEpermAlert = class {
|
|
49825
|
+
dir;
|
|
49826
|
+
threshold;
|
|
49827
|
+
repeatMs;
|
|
49828
|
+
platform;
|
|
49829
|
+
now;
|
|
49830
|
+
onAlert;
|
|
49831
|
+
hasAlerted = false;
|
|
49832
|
+
alertedAt = 0;
|
|
49833
|
+
constructor(opts) {
|
|
49834
|
+
this.dir = opts.dir;
|
|
49835
|
+
this.threshold = opts.threshold ?? 5;
|
|
49836
|
+
this.repeatMs = opts.repeatMs ?? 0;
|
|
49837
|
+
this.platform = opts.platform ?? process.platform;
|
|
49838
|
+
this.now = opts.now ?? Date.now;
|
|
49839
|
+
this.onAlert = opts.onAlert ?? ((d, c) => log("warn", buildAlertMessage(d, c)));
|
|
49840
|
+
}
|
|
49841
|
+
// Returns true iff an alert was emitted. Runs on every kernel log line, so
|
|
49842
|
+
// the non-matching cases bail before any allocation.
|
|
49843
|
+
observe(level, msg) {
|
|
49844
|
+
if (this.platform !== "win32") return false;
|
|
49845
|
+
if (level !== "error" && level !== "warn") return false;
|
|
49846
|
+
const m2 = WRITE_FAIL_RE.exec(msg);
|
|
49847
|
+
if (!m2) return false;
|
|
49848
|
+
const count = Number(m2[2]);
|
|
49849
|
+
if (count < this.threshold) return false;
|
|
49850
|
+
if (!LOCK_CODES.test(m2[3])) return false;
|
|
49851
|
+
if (!this.shouldAlert()) return false;
|
|
49852
|
+
this.hasAlerted = true;
|
|
49853
|
+
this.alertedAt = this.now();
|
|
49854
|
+
this.onAlert(this.dir, count);
|
|
49855
|
+
return true;
|
|
49856
|
+
}
|
|
49857
|
+
shouldAlert() {
|
|
49858
|
+
if (!this.hasAlerted) return true;
|
|
49859
|
+
if (this.repeatMs <= 0) return false;
|
|
49860
|
+
return this.now() - this.alertedAt >= this.repeatMs;
|
|
49861
|
+
}
|
|
49862
|
+
};
|
|
49863
|
+
function buildAlertMessage(dir, count) {
|
|
49864
|
+
return [
|
|
49865
|
+
`[persist] session dir ${dir} failed ${count} consecutive writes (EPERM \u2014 rename is locked).`,
|
|
49866
|
+
"Likely cause: Windows Defender / antivirus / indexer / a sync tool is locking the directory.",
|
|
49867
|
+
`Fix: add ${dir} to Windows Defender exclusions (Settings \u2192 Virus & threat protection \u2192 Manage settings \u2192 Exclusions \u2192 Add an exclusion),`,
|
|
49868
|
+
"and make sure no OneDrive / sync tool is syncing this path."
|
|
49869
|
+
].join("\n");
|
|
49870
|
+
}
|
|
49871
|
+
|
|
49816
49872
|
// src/persist.ts
|
|
49817
49873
|
var PERSIST_VERSION = 3;
|
|
49818
49874
|
var MIGRATION_MARKER = ".bili-migration-286.done";
|
|
@@ -49850,12 +49906,21 @@ var SessionStore = class {
|
|
|
49850
49906
|
const debounceMs = opts?.debounceMs ?? defaultDebounce();
|
|
49851
49907
|
this.enabled = (opts?.enabled ?? true) && debounceMs >= 0;
|
|
49852
49908
|
this.dir = opts?.dir ?? defaultDir();
|
|
49909
|
+
const baseLog = opts?.log ?? defaultLogger;
|
|
49910
|
+
const epermAlert = new PersistEpermAlert({
|
|
49911
|
+
dir: this.dir,
|
|
49912
|
+
threshold: epermAlertThreshold(),
|
|
49913
|
+
repeatMs: epermAlertRepeatMs()
|
|
49914
|
+
});
|
|
49853
49915
|
this.store = new StateStore({
|
|
49854
49916
|
dir: this.dir,
|
|
49855
49917
|
version: PERSIST_VERSION,
|
|
49856
49918
|
debounceMs: Math.max(0, debounceMs),
|
|
49857
49919
|
enabled: this.enabled,
|
|
49858
|
-
log:
|
|
49920
|
+
log: (level, msg) => {
|
|
49921
|
+
epermAlert.observe(level, msg);
|
|
49922
|
+
baseLog(level, msg);
|
|
49923
|
+
},
|
|
49859
49924
|
relPath: (id, payload) => relPathFor(id, payload.meta?.protocol ?? payload.protocol, payload.meta?.upstreamOrigin ?? payload.upstreamOrigin),
|
|
49860
49925
|
// Adopt the pre-envelope flat format this store itself wrote
|
|
49861
49926
|
// before the kernel extraction (and every v1/v2 file before it).
|
|
@@ -50123,6 +50188,22 @@ function persistEnabled() {
|
|
|
50123
50188
|
if (env === "0" || env === "false") return false;
|
|
50124
50189
|
return true;
|
|
50125
50190
|
}
|
|
50191
|
+
function epermAlertThreshold() {
|
|
50192
|
+
const env = process.env.BILI_PERSIST_EPERM_ALERT_THRESHOLD;
|
|
50193
|
+
if (env) {
|
|
50194
|
+
const n = Number.parseInt(env, 10);
|
|
50195
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
50196
|
+
}
|
|
50197
|
+
return 5;
|
|
50198
|
+
}
|
|
50199
|
+
function epermAlertRepeatMs() {
|
|
50200
|
+
const env = process.env.BILI_PERSIST_EPERM_ALERT_REPEAT_MS;
|
|
50201
|
+
if (env) {
|
|
50202
|
+
const n = Number.parseInt(env, 10);
|
|
50203
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
50204
|
+
}
|
|
50205
|
+
return 0;
|
|
50206
|
+
}
|
|
50126
50207
|
function defaultLogger(level, m2) {
|
|
50127
50208
|
log(level, m2);
|
|
50128
50209
|
}
|
|
@@ -50353,7 +50434,7 @@ function withStagedCompressGuidance(text) {
|
|
|
50353
50434
|
}
|
|
50354
50435
|
|
|
50355
50436
|
// src/decompress-shared.ts
|
|
50356
|
-
import { mkdirSync as mkdirSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
50437
|
+
import { mkdirSync as mkdirSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
50357
50438
|
import { dirname as dirname2, join as join2 } from "path";
|
|
50358
50439
|
import { tmpdir } from "os";
|
|
50359
50440
|
var trackedTempFiles = [];
|
|
@@ -50369,7 +50450,7 @@ function reapTempFiles() {
|
|
|
50369
50450
|
const oldest = trackedTempFiles.shift();
|
|
50370
50451
|
if (!oldest) break;
|
|
50371
50452
|
try {
|
|
50372
|
-
|
|
50453
|
+
unlinkSync2(oldest.path);
|
|
50373
50454
|
} catch {
|
|
50374
50455
|
}
|
|
50375
50456
|
}
|
|
@@ -50377,7 +50458,7 @@ function reapTempFiles() {
|
|
|
50377
50458
|
process.on("beforeExit", () => {
|
|
50378
50459
|
for (const f2 of trackedTempFiles) {
|
|
50379
50460
|
try {
|
|
50380
|
-
|
|
50461
|
+
unlinkSync2(f2.path);
|
|
50381
50462
|
} catch {
|
|
50382
50463
|
}
|
|
50383
50464
|
}
|
|
@@ -51262,7 +51343,7 @@ function busy(button,on,label){if(!button)return;if(on){button.dataset.label=but
|
|
|
51262
51343
|
async function json(url,options){const response=await fetch(url,options);const data=await response.json().catch(()=>({}));if(!response.ok)throw new Error(data.error||data.detail||("HTTP "+response.status));return data}
|
|
51263
51344
|
function showPage(name){document.querySelectorAll(".page").forEach((node)=>node.classList.toggle("active",node.id==="page-"+name));document.querySelectorAll(".nav button").forEach((node)=>node.classList.toggle("active",node.dataset.page===name));if(name==="sessions")loadSessions();if(name==="upstream"){loadUpstream();loadOverrides()}}
|
|
51264
51345
|
document.querySelectorAll(".nav button").forEach((button)=>button.addEventListener("click",()=>showPage(button.dataset.page)));
|
|
51265
|
-
async function loadConfig(){const data=await json("/__bili/config");if(data.parseError)toast(String(data.parseError),true);byId("providers-json").value=JSON.stringify(data.providers||{},null,2);byId("proxy-url").value=data.upstreamProxy||"";byId("compress-json").value=data.compress?JSON.stringify(data.compress,null,2):"";document.querySelectorAll('input[name="proxy-mode"]').forEach((input)=>input.checked=input.value===(data.upstreamProxyMode||"direct"));return data}
|
|
51346
|
+
async function loadConfig(){const data=await json("/__bili/config");if(data.parseError)toast(String(data.parseError),true);byId("providers-json").value=JSON.stringify(data.providers||{},null,2);byId("proxy-url").value=data.upstreamProxy||"";byId("compress-json").value=data.compress?JSON.stringify(data.compress,null,2):"";document.querySelectorAll('input[name="proxy-mode"]').forEach((input)=>input.checked=input.value===(data.upstreamProxyMode||"direct"));const pt=data.passthrough||{enabled:false};const banner=byId("passthrough-banner");if(banner){if(pt.enabled){banner.hidden=false;byId("passthrough-source").textContent=pt.source==="env"?"\u73AF\u5883\u53D8\u91CF ACP_PASSTHROUGH\uFF08\u6216 --passthrough \u53C2\u6570\uFF09":"\u914D\u7F6E\u6587\u4EF6 passthrough: true";const btn=byId("clear-passthrough");btn.disabled=pt.source==="env";btn.title=pt.source==="env"?"\u9700\u5148 unset ACP_PASSTHROUGH \u5E76\u91CD\u542F":""}else{banner.hidden=true}}return data}
|
|
51266
51347
|
async function loadUpstream(){try{const data=await json("/__bili/upstream");const labels={"provider":"Provider \u5355\u72EC\u4EE3\u7406","provider-direct":"Provider \u76F4\u8FDE","bili-env":"BILI_UPSTREAM_PROXY","web-manual":"Web \u624B\u52A8\u4EE3\u7406","config":"\u914D\u7F6E\u6587\u4EF6\u4EE3\u7406","HTTPS_PROXY":"HTTPS_PROXY","HTTP_PROXY":"HTTP_PROXY","ALL_PROXY":"ALL_PROXY","windows-system":"Windows \u7CFB\u7EDF\u4EE3\u7406","windows-bypass":"Windows \u7ED5\u8FC7\u5217\u8868","no-proxy":"NO_PROXY","direct":"\u76F4\u8FDE"};byId("upstream-source").textContent=labels[data.source]||data.source||"\u76F4\u8FDE";byId("upstream-effective").textContent=data.proxy||"direct";byId("upstream-pac").textContent=data.autoConfigUrl||"\u2014";const state=byId("upstream-state");state.className="status "+(data.connected===true?"ok":data.connected===false?"err":"");state.textContent=data.connected===true?"CONNECT \u6B63\u5E38":data.connected===false?(data.error||"\u8FDE\u63A5\u5931\u8D25"):"\u5C1A\u672A\u89C2\u5BDF\u5230\u4E0A\u6E38\u8FDE\u63A5"}catch(error){byId("upstream-state").textContent=String(error)}}
|
|
51267
51348
|
async function saveUpstream(){const button=byId("save-upstream");busy(button,true,"\u4FDD\u5B58\u4E2D\u2026");const mode=document.querySelector('input[name="proxy-mode"]:checked').value;try{await json("/__bili/config",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({upstreamProxyMode:mode,upstreamProxy:byId("proxy-url").value.trim()||null})});toast("\u4E0A\u6E38\u8BBE\u7F6E\u5DF2\u70ED\u66F4\u65B0");await loadUpstream()}catch(error){toast(String(error),true)}finally{busy(button,false)}}
|
|
51268
51349
|
async function testUpstream(){const button=byId("test-upstream");busy(button,true,"\u6D4B\u8BD5\u4E2D\u2026");try{const data=await json("/__bili/upstream/test",{method:"POST"});toast("\u8FDE\u63A5\u6210\u529F\uFF0CHTTP "+data.status);await loadUpstream()}catch(error){toast(String(error),true);await loadUpstream()}finally{busy(button,false)}}
|
|
@@ -51272,7 +51353,9 @@ async function loadOverrides(){try{const data=await loadConfig();const providers
|
|
|
51272
51353
|
async function saveOverrides(){const button=byId("save-overrides");busy(button,true,"\u4FDD\u5B58\u4E2D\u2026");try{const data=await loadConfig();const providers=data.providers||{};document.querySelectorAll(".override-proxy").forEach((input)=>{const url=input.dataset.url;if(!url)return;if(!providers[url])providers[url]={};const val=input.value.trim();if(val)providers[url].proxy=val;else delete providers[url].proxy});await json("/__bili/config",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({providers})});await json("/__bili/config/reload",{method:"POST"});toast("\u6309 URL \u8986\u76D6\u5DF2\u70ED\u66F4\u65B0");await loadOverrides()}catch(error){toast(String(error),true)}finally{busy(button,false)}}
|
|
51273
51354
|
async function loadSessions(){try{const data=await json("/__bili/stats");const rows=(data.sessions||[]).map((item)=>"<tr><td>"+escapeHtml(item.title||"\u2014")+"</td><td>"+escapeHtml(item.protocol||"\u2014")+"</td><td class=mono>"+escapeHtml(item.label||"\u2014")+"</td><td>"+escapeHtml(item.requests)+"</td><td>"+escapeHtml(item.contextTokens)+"</td><td>"+escapeHtml(new Date(item.lastSeen).toLocaleString())+(item.restored?" <span class=muted>(\u6062\u590D\uFF0C\u5F00\u673A\u540E\u672A\u6D3B\u52A8)</span>":"")+"</td></tr>").join("");byId("sessions-body").innerHTML=rows||'<tr><td colspan="6">\u6682\u65E0\u4F1A\u8BDD</td></tr>'}catch(error){toast(String(error),true);throw error}}
|
|
51274
51355
|
async function refreshSessions(){const button=byId("refresh-sessions");busy(button,true,"\u5237\u65B0\u4E2D\u2026");try{await loadSessions();toast("\u4F1A\u8BDD\u5217\u8868\u5DF2\u5237\u65B0")}catch{}finally{busy(button,false)}}
|
|
51275
|
-
document.querySelectorAll(".copy-btn").forEach((btn)=>btn.addEventListener("click",async()=>{busy(btn,true,"\u590D\u5236\u4E2D\u2026");try{await navigator.clipboard.writeText(btn.dataset.copy||"");toast("\u5DF2\u590D\u5236")}catch(e){toast(String(e),true)}finally{busy(btn,false)}}));byId("
|
|
51356
|
+
document.querySelectorAll(".copy-btn").forEach((btn)=>btn.addEventListener("click",async()=>{busy(btn,true,"\u590D\u5236\u4E2D\u2026");try{await navigator.clipboard.writeText(btn.dataset.copy||"");toast("\u5DF2\u590D\u5236")}catch(e){toast(String(e),true)}finally{busy(btn,false)}}));async function clearPassthrough(){const button=byId("clear-passthrough");busy(button,true,"\u5904\u7406\u4E2D\u2026");try{await json("/__bili/config",{method:"PUT",headers:{"content-type":"application/json"},body:JSON.stringify({passthrough:null})});toast("\u5DF2\u6062\u590D\u538B\u7F29");await loadConfig()}catch(error){toast(String(error),true)}finally{busy(button,false)}}
|
|
51357
|
+
byId("clear-passthrough").addEventListener("click",clearPassthrough);
|
|
51358
|
+
byId("save-upstream").addEventListener("click",saveUpstream);byId("test-upstream").addEventListener("click",testUpstream);byId("save-providers").addEventListener("click",saveProviders);byId("save-compress").addEventListener("click",saveCompress);byId("save-overrides").addEventListener("click",saveOverrides);byId("refresh-sessions").addEventListener("click",refreshSessions);
|
|
51276
51359
|
Promise.all([loadConfig(),loadUpstream()]).catch((error)=>toast(String(error),true));setInterval(()=>{if(byId("page-sessions").classList.contains("active"))loadSessions().catch(()=>{})},5000);
|
|
51277
51360
|
`;
|
|
51278
51361
|
|
|
@@ -51294,7 +51377,7 @@ function renderPage(origin, version2) {
|
|
|
51294
51377
|
const caReady = existsSync4(caPath);
|
|
51295
51378
|
return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>billion-context</title><style>${WEB_STYLES}</style></head><body>
|
|
51296
51379
|
<div class="app"><aside class="sidebar"><div class="brand"><div class="brand-title">billion<span>-context</span></div><div class="version">v${version2}</div></div><a class="fork-ribbon" href="https://github.com/ranxianglei/billion-context" target="_blank" rel="noopener">\u2605 Fork me on GitHub</a><nav class="nav"><button class="active" data-page="overview">\u6982\u89C8</button><button data-page="routing">\u63A5\u5165</button><button data-page="upstream">\u4E0A\u6E38</button><button data-page="sessions">\u4F1A\u8BDD</button><button data-page="settings">\u8BBE\u7F6E</button></nav></aside>
|
|
51297
|
-
<main class="main"><section id="page-overview" class="page active"><h1>\u6982\u89C8</h1><p class="lead">\u672C\u5730\u8DEF\u7531\u4E0E\u4E0A\u6E38\u7F51\u7EDC\u76F8\u4E92\u72EC\u7ACB\uFF0CACP \u538B\u7F29\u8FD0\u884C\u5728\u4E24\u8005\u4E4B\u95F4\u3002</p><div class="grid"><div class="card"><h2>\u670D\u52A1\u5730\u5740</h2><p class="mono">${origin}</p></div><div class="card"><h2>\u5BA2\u6237\u7AEF\u63A5\u5165</h2><p class="status">\u5728"\u63A5\u5165"\u9875\u5C06\u5404\u5BA2\u6237\u7AEF\uFF08Codex / Claude Code / OpenCode / Pi \u7B49\uFF09\u7684\u6D41\u91CF\u8DEF\u7531\u5230 bili\u3002</p></div></div></section>
|
|
51380
|
+
<main class="main"><section id="page-overview" class="page active"><h1>\u6982\u89C8</h1><p class="lead">\u672C\u5730\u8DEF\u7531\u4E0E\u4E0A\u6E38\u7F51\u7EDC\u76F8\u4E92\u72EC\u7ACB\uFF0CACP \u538B\u7F29\u8FD0\u884C\u5728\u4E24\u8005\u4E4B\u95F4\u3002</p><div id="passthrough-banner" class="notice" hidden>\u26A0\uFE0F <strong>Passthrough \u5DF2\u542F\u7528</strong>\uFF1A\u6240\u6709\u8BF7\u6C42\u539F\u6837\u8F6C\u53D1\uFF0C\u538B\u7F29\u5B8C\u5168\u5173\u95ED\uFF0C\u4E0D\u4F1A\u8282\u7701\u4EFB\u4F55 token\u3002\u6765\u6E90\uFF1A<span id="passthrough-source" class="mono"></span>\u3002 <button id="clear-passthrough" class="btn">\u6062\u590D\u538B\u7F29</button></div><div class="grid"><div class="card"><h2>\u670D\u52A1\u5730\u5740</h2><p class="mono">${origin}</p></div><div class="card"><h2>\u5BA2\u6237\u7AEF\u63A5\u5165</h2><p class="status">\u5728"\u63A5\u5165"\u9875\u5C06\u5404\u5BA2\u6237\u7AEF\uFF08Codex / Claude Code / OpenCode / Pi \u7B49\uFF09\u7684\u6D41\u91CF\u8DEF\u7531\u5230 bili\u3002</p></div></div></section>
|
|
51298
51381
|
<section id="page-routing" class="page"><h1>\u5BA2\u6237\u7AEF\u63A5\u5165</h1><p class="lead">\u4E24\u7C7B\u63A5\u5165\uFF1AA. \u63A8\u8350\u63A5\u5165\u65B9\u5F0F\uFF08<span class="mono">/bili/</span> \u524D\u7F00\uFF0C\u96F6\u914D\u7F6E\uFF09\uFF1BB. \u8BC1\u4E66\u63A5\u5165\u65B9\u5F0F\uFF08MITM\uFF0C\u9700\u4FE1\u4EFB CA \u8BC1\u4E66\uFF09\u3002</p>
|
|
51299
51382
|
<h2 class="section-title">A. \u63A8\u8350\u63A5\u5165\u65B9\u5F0F\uFF08/bili/ \u524D\u7F00\uFF09</h2>
|
|
51300
51383
|
<p class="hint">\u5728\u5BA2\u6237\u7AEF\u914D\u7F6E\u91CC\u628A\u539F baseURL \u524D\u52A0\u4E0A <span class="mono">${origin}/bili/</span> \u5373\u53EF\uFF0C\u5176\u4F59\u4E0D\u53D8\u3002</p>
|
|
@@ -51313,7 +51396,7 @@ function renderPage(origin, version2) {
|
|
|
51313
51396
|
}
|
|
51314
51397
|
|
|
51315
51398
|
// src/web/api.ts
|
|
51316
|
-
import { closeSync, existsSync as existsSync5, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync2, unlinkSync as
|
|
51399
|
+
import { closeSync, existsSync as existsSync5, fsyncSync, mkdirSync as mkdirSync5, openSync, renameSync as renameSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
51317
51400
|
import { dirname as dirname3 } from "path";
|
|
51318
51401
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
51319
51402
|
function readConfig() {
|
|
@@ -51352,7 +51435,7 @@ function atomicWriteConfig(config) {
|
|
|
51352
51435
|
} catch (error) {
|
|
51353
51436
|
if (descriptor !== void 0) closeSync(descriptor);
|
|
51354
51437
|
try {
|
|
51355
|
-
|
|
51438
|
+
unlinkSync3(tempPath);
|
|
51356
51439
|
} catch {
|
|
51357
51440
|
}
|
|
51358
51441
|
throw error;
|
|
@@ -51370,6 +51453,7 @@ async function handleConfigGet(res) {
|
|
|
51370
51453
|
upstreamProxy: upstream.proxy ?? null,
|
|
51371
51454
|
upstreamProxyMode: upstream.mode,
|
|
51372
51455
|
compress: config.compress ?? null,
|
|
51456
|
+
passthrough: passthroughState(process.env),
|
|
51373
51457
|
...parseError ? { parseError } : {}
|
|
51374
51458
|
}, null, 2));
|
|
51375
51459
|
}
|
|
@@ -51383,7 +51467,8 @@ async function handleConfigPut(req, res, onChanged, biliPort = 8787) {
|
|
|
51383
51467
|
const hasProxy = Object.prototype.hasOwnProperty.call(body, "upstreamProxy");
|
|
51384
51468
|
const hasMode = Object.prototype.hasOwnProperty.call(body, "upstreamProxyMode");
|
|
51385
51469
|
const hasCompress = Object.prototype.hasOwnProperty.call(body, "compress");
|
|
51386
|
-
|
|
51470
|
+
const hasPassthrough = Object.prototype.hasOwnProperty.call(body, "passthrough");
|
|
51471
|
+
if (!hasProviders && !hasProxy && !hasMode && !hasCompress && !hasPassthrough) return sendError(res, 400, "expected providers, upstream proxy, compress, or passthrough settings");
|
|
51387
51472
|
const routes = {};
|
|
51388
51473
|
if (hasProviders) {
|
|
51389
51474
|
if (!body.providers || typeof body.providers !== "object" || Array.isArray(body.providers)) {
|
|
@@ -51427,6 +51512,14 @@ async function handleConfigPut(req, res, onChanged, biliPort = 8787) {
|
|
|
51427
51512
|
compress = body.compress === null ? {} : parseCompressSettings(body.compress);
|
|
51428
51513
|
if (compress === void 0) return sendError(res, 400, "invalid compress settings");
|
|
51429
51514
|
}
|
|
51515
|
+
if (hasPassthrough) {
|
|
51516
|
+
if (body.passthrough !== null && typeof body.passthrough !== "boolean") {
|
|
51517
|
+
return sendError(res, 400, "passthrough must be a boolean or null");
|
|
51518
|
+
}
|
|
51519
|
+
if (passthroughState(process.env).source === "env") {
|
|
51520
|
+
return sendError(res, 409, "passthrough is forced by the ACP_PASSTHROUGH environment variable (or --passthrough flag); unset it and restart to change here");
|
|
51521
|
+
}
|
|
51522
|
+
}
|
|
51430
51523
|
const config = readConfig();
|
|
51431
51524
|
if (hasProviders) config.providers = routes;
|
|
51432
51525
|
if (hasProxy) {
|
|
@@ -51438,6 +51531,10 @@ async function handleConfigPut(req, res, onChanged, biliPort = 8787) {
|
|
|
51438
51531
|
if (compress && Object.keys(compress).length > 0) config.compress = compress;
|
|
51439
51532
|
else delete config.compress;
|
|
51440
51533
|
}
|
|
51534
|
+
if (hasPassthrough) {
|
|
51535
|
+
if (body.passthrough === true) config.passthrough = true;
|
|
51536
|
+
else delete config.passthrough;
|
|
51537
|
+
}
|
|
51441
51538
|
try {
|
|
51442
51539
|
atomicWriteConfig(config);
|
|
51443
51540
|
onChanged?.();
|
|
@@ -51448,6 +51545,7 @@ async function handleConfigPut(req, res, onChanged, biliPort = 8787) {
|
|
|
51448
51545
|
if (hasProviders) changed.push(`${Object.keys(routes).length} routes`);
|
|
51449
51546
|
if (hasProxy || hasMode) changed.push("network");
|
|
51450
51547
|
if (hasCompress) changed.push("compress");
|
|
51548
|
+
if (hasPassthrough) changed.push("passthrough");
|
|
51451
51549
|
log("info", `[acp-web] configuration updated (${changed.join(", ") || "none"})`);
|
|
51452
51550
|
res.writeHead(200, { "content-type": "application/json" });
|
|
51453
51551
|
res.end(JSON.stringify({ ok: true, providers: hasProviders ? Object.keys(routes).length : void 0 }));
|
|
@@ -57119,8 +57217,23 @@ Content-Length: ${Buffer.byteLength(body)}\r
|
|
|
57119
57217
|
const nOverrides = Object.keys(opts.routes).length;
|
|
57120
57218
|
log2(
|
|
57121
57219
|
"info",
|
|
57122
|
-
`acp-proxy listening on http://${displayHost}:${actualPort} \u2014 web UI: http://${displayHost}:${actualPort}/__bili/ \u2014 zero-config: prefix any baseURL with http://${displayHost}:${actualPort}/bili/` + (nOverrides ? ` \u2014 context overrides for ${nOverrides} upstream URL(s)` : "") + (opts.mitm.enabled ? ` \u2014 MITM proxy on (whitelist)${opts.mitm.domains.length ? ` +${opts.mitm.domains.join(",")}` : ""}` : "")
|
|
57220
|
+
`acp-proxy listening on http://${displayHost}:${actualPort} \u2014 web UI: http://${displayHost}:${actualPort}/__bili/ \u2014 zero-config: prefix any baseURL with http://${displayHost}:${actualPort}/bili/` + (nOverrides ? ` \u2014 context overrides for ${nOverrides} upstream URL(s)` : "") + (opts.mitm.enabled ? ` \u2014 MITM proxy on (whitelist)${opts.mitm.domains.length ? ` +${opts.mitm.domains.join(",")}` : ""}` : "") + (opts.passthrough ? " \u2014 PASSTHROUGH (compression OFF)" : "")
|
|
57123
57221
|
);
|
|
57222
|
+
if (opts.passthrough) {
|
|
57223
|
+
log2(
|
|
57224
|
+
"warn",
|
|
57225
|
+
`[passthrough] compression is OFF \u2014 every request is forwarded verbatim, no tokens are saved (source: ${opts.passthroughSource === "env" ? "ACP_PASSTHROUGH env var or --passthrough flag" : `config file ${configFile()}`}). ` + (opts.passthroughSource === "env" ? "Unset ACP_PASSTHROUGH (or drop --passthrough) and restart to re-enable compression." : 'Clear it in the web UI (\u6982\u89C8 page) or remove "passthrough": true from the config file to re-enable compression.')
|
|
57226
|
+
);
|
|
57227
|
+
}
|
|
57228
|
+
const envKnobs = [];
|
|
57229
|
+
if (process.env.ACP_PASSTHROUGH !== void 0) envKnobs.push(`ACP_PASSTHROUGH=${process.env.ACP_PASSTHROUGH}`);
|
|
57230
|
+
if (process.env.ACP_MODEL_CONTEXT_LIMIT !== void 0) envKnobs.push(`ACP_MODEL_CONTEXT_LIMIT=${process.env.ACP_MODEL_CONTEXT_LIMIT}`);
|
|
57231
|
+
if (process.env.ACP_COMPRESS_TOOL !== void 0) envKnobs.push(`ACP_COMPRESS_TOOL=${process.env.ACP_COMPRESS_TOOL}`);
|
|
57232
|
+
if (process.env.ACP_COMPRESS_NUDGE !== void 0) envKnobs.push(`ACP_COMPRESS_NUDGE=${process.env.ACP_COMPRESS_NUDGE}`);
|
|
57233
|
+
if (process.env.BILI_PERSIST !== void 0) envKnobs.push(`BILI_PERSIST=${process.env.BILI_PERSIST}`);
|
|
57234
|
+
if (envKnobs.length > 0) {
|
|
57235
|
+
log2("info", `[config] env overrides active (win over the config file): ${envKnobs.join(", ")}`);
|
|
57236
|
+
}
|
|
57124
57237
|
if (nonLoopbackBind) {
|
|
57125
57238
|
log2(
|
|
57126
57239
|
"warn",
|
|
@@ -57200,9 +57313,34 @@ var SIDE_REQUEST_MAX_TOKENS = 200;
|
|
|
57200
57313
|
function isSideRequest(parsed) {
|
|
57201
57314
|
if (!parsed || typeof parsed !== "object") return false;
|
|
57202
57315
|
const p2 = parsed;
|
|
57316
|
+
if (Array.isArray(p2.tools) && p2.tools.length > 0) return false;
|
|
57203
57317
|
const raw = p2.max_tokens ?? p2.max_completion_tokens ?? p2.max_output_tokens;
|
|
57204
57318
|
return typeof raw === "number" && raw > 0 && raw <= SIDE_REQUEST_MAX_TOKENS;
|
|
57205
57319
|
}
|
|
57320
|
+
function outputBudgetField(parsed) {
|
|
57321
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
57322
|
+
const p2 = parsed;
|
|
57323
|
+
for (const field of ["max_tokens", "max_completion_tokens", "max_output_tokens"]) {
|
|
57324
|
+
if (typeof p2[field] === "number" && p2[field] > 0) return field;
|
|
57325
|
+
}
|
|
57326
|
+
return null;
|
|
57327
|
+
}
|
|
57328
|
+
function restoreOutputBudget(parsed, session, log2) {
|
|
57329
|
+
const field = outputBudgetField(parsed);
|
|
57330
|
+
if (!field) return;
|
|
57331
|
+
const p2 = parsed;
|
|
57332
|
+
const value = p2[field];
|
|
57333
|
+
if (value > SIDE_REQUEST_MAX_TOKENS) {
|
|
57334
|
+
session.metadata.outputBudgetHighWater = value;
|
|
57335
|
+
return;
|
|
57336
|
+
}
|
|
57337
|
+
if (!Array.isArray(p2.tools) || p2.tools.length === 0) return;
|
|
57338
|
+
const highWater = session.metadata.outputBudgetHighWater;
|
|
57339
|
+
if (typeof highWater === "number" && highWater > SIDE_REQUEST_MAX_TOKENS) {
|
|
57340
|
+
p2[field] = highWater;
|
|
57341
|
+
log2("info", `[${session.id}] output budget restored ${value} -> ${highWater} (#546: client shrank it from its raw-history estimate)`);
|
|
57342
|
+
}
|
|
57343
|
+
}
|
|
57206
57344
|
function isTrustedAdminOrigin(origin, host, trustedHosts) {
|
|
57207
57345
|
if (!host || !trustedHosts.has(host.toLowerCase())) return false;
|
|
57208
57346
|
if (!origin) return true;
|
|
@@ -57271,6 +57409,14 @@ async function handle(req, res, opts, core, config, log2, instanceId, instanceSt
|
|
|
57271
57409
|
if (req.method === "PUT" && req.url === "/__bili/config") {
|
|
57272
57410
|
return handleConfigPut(req, res, () => {
|
|
57273
57411
|
const fresh = loadOptions();
|
|
57412
|
+
if (fresh.passthrough !== opts.passthrough) {
|
|
57413
|
+
log2(
|
|
57414
|
+
fresh.passthrough ? "warn" : "info",
|
|
57415
|
+
`[passthrough] ${fresh.passthrough ? "compression turned OFF via web config \u2014 forwarding verbatim" : "compression re-enabled via web config"}`
|
|
57416
|
+
);
|
|
57417
|
+
}
|
|
57418
|
+
opts.passthrough = fresh.passthrough;
|
|
57419
|
+
opts.passthroughSource = fresh.passthroughSource;
|
|
57274
57420
|
opts.proxy = fresh.proxy;
|
|
57275
57421
|
opts.proxyMode = fresh.proxyMode;
|
|
57276
57422
|
opts.proxySource = fresh.proxySource;
|
|
@@ -57593,6 +57739,7 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57593
57739
|
}
|
|
57594
57740
|
}
|
|
57595
57741
|
const pluginMode = pluginAgent !== void 0;
|
|
57742
|
+
restoreOutputBudget(parsed, session, log2);
|
|
57596
57743
|
if (!countTokens && !responsesCompact && protocol !== null && isSideRequest(parsed)) {
|
|
57597
57744
|
log2("info", `[${session.id}] side request (max_tokens<=${SIDE_REQUEST_MAX_TOKENS}) \u2192 passthrough + tag strip only, kernel state untouched`);
|
|
57598
57745
|
const sidePrepared = {
|