Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@ This index is generated from `website/config-reference/*.yaml` — edit the seed
| `BILI_PARENT_PID` | number | unset | — | Spawn channel: parent process id for lifecycle supervision. |
| `BILI_STRICT_PORT` | number | unset | — | Spawn channel: exact-port requirement (squatters refused loudly instead of hopping). |
| `BILI_UPGRADE_REPLACEMENT` | boolean-ish | unset | — | Spawn channel: marks a self-restart replacement — it must own exactly the freed port or exit without hopping (#3044). |
| `BILI_PLUGIN_BYPASS` | string | unset (auto) | — | Force switch for the x-bili-plugin-bypass wire decision (#6220). Unset/auto = the inbound per-request header decides (legacy opencode acp sessions bypass, everyone else is compressed — zero behavior change). 1/on = FORCE bypass for ALL inbound traffic, raw passthrough with no compression or session state: the escape valve when an in-process compressor is loaded but bili still manages those sessions; if nothing in-process (e.g. opencode acp) is actually compressing, traffic reaches the model UNCOMPRESSED. 0/off = ignore any inbound bypass header, including opencode legacy ones: bili takes over those sessions, but their pre-existing ref space lives in the in-process acp store, so their history degrades to a read-only archive. Each forced state emits one loud startup-of-traffic warn; unrecognized values are treated as unset (auto) with one warning. Env-only by design (owner decision in #6220). |
| `BILI_OPENCODE_ACP_SPEC` | string | unset | — | Spawn channel: ACP spec marker for opencode-native lanes. |
| `BILI_LAUNCHER_MODEL_WINDOWS` | string | unset | — | Launcher channel: model-window overrides passed into spawned clients. |
| `BILI_LAUNCHER_LANE` | string | unset | — | Launcher channel: lane identity of the spawning bili process. |
Expand Down
1 change: 1 addition & 0 deletions docs/configuration.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,7 @@
| `BILI_PARENT_PID` | number | unset | — | 派生通道:父进程 id,用于生命周期监管。 |
| `BILI_STRICT_PORT` | number | unset | — | 派生通道:精确端口要求(被占用时响亮拒绝而非跳端口)。 |
| `BILI_UPGRADE_REPLACEMENT` | boolean-ish | unset | — | 派生通道:标记自重启替换进程——必须独占被释放的精确端口,否则直接退出而非跳端口(#3044)。 |
| `BILI_PLUGIN_BYPASS` | string | unset (auto) | — | x-bili-plugin-bypass 透传判决的强制开关(#6220)。缺省/auto = 由每请求头决定(opencode 旧版 acp 会话透传、其余压缩,行为零变化)。1/on = 强制所有流量透传、不压缩不记状态:用于“进程内已有压缩器但 bili 仍在管理这些会话”的逃生阀;若进程内实际没有压缩器(如 opencode acp),流量会以**未压缩**状态直达模型。0/off = 无视一切入站透传头(含 opencode 旧版):bili 接管这些会话,但其既有引用空间在进程内 acp 存储里,历史降级为只读归档。每种强制态首次命中时打一条醒目警告;无法识别的取值按缺省(auto)处理并警告一次。按 owner 决定(#6220)env-only。 |
| `BILI_OPENCODE_ACP_SPEC` | string | unset | — | 派生通道:opencode 原生车道的 ACP 规格标记。 |
| `BILI_LAUNCHER_MODEL_WINDOWS` | string | unset | — | 启动器通道:传入被派生客户端的模型窗口覆盖。 |
| `BILI_LAUNCHER_LANE` | string | unset | — | 启动器通道:派生方 bili 进程的车道身份。 |
Expand Down
1 change: 1 addition & 0 deletions docs/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@
| `BILI_AUTO_MODE_FORGE` | autoModeForge.enabled,默认 on——auto-mode 分类判定本地 forge,零出账/计费(`=0` 恢复纯透传,留给付真实判定费的用户,#2740 owner 决定) |
| `BILI_DSH_CONTEXT_TAB` | dsh.contextTab 的 env 层(#4237):`=0` 隐藏 DSH 顶栏「上下文」标签页(压缩及其余入口全部不受影响);其他值强制开并覆盖文件层;缺省按文件层(默认 true);下次页面加载 / dsh 重启生效 |
| `BILI_DSH_MAX_TESTED_VERSION` | dsh native lane「高于已测最高版」启动提示的上限(#4545):从注入包上方最近的 node_modules/@deepseek-ai/dsh/package.json 读运行中的构建,**严格高于**上限才弹一条启动提示,建议先回滚;默认 0.2.0-rc.2(CI e2e 锚点),新版本验证通过后抬高;空串 = 关闭检查;只提示不拦截 |
| `BILI_PLUGIN_BYPASS` | x-bili-plugin-bypass 判决的强制开关(#6220):缺省/auto 按每请求头走(零行为变化);`1` 强制全流量原始透传(逃生阀,无进程内压缩器时流量**未压缩**直达模型);`0` 无视一切 bypass 头(opencode 旧版会话被 bili 接管,既有历史降级只读归档);无法识别的取值按缺省处理 + 一次警告;每种强制态首次命中打一条醒目 warn;env-only(owner 在 #6220 明确拍板,无文件键) |

## 3. 非环境变量的 BILI_* 标识符(勿与上表混淆)

Expand Down
20 changes: 20 additions & 0 deletions src/knobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1068,3 +1068,23 @@ export function resolveStorageWalJournalSizeLimit(env: NodeJS.ProcessEnv = proce
return WAL_JOURNAL_SIZE_LIMIT;
}

/** BILI_PLUGIN_BYPASS — #6220: tri-state force switch for the
* x-bili-plugin-bypass wire lane (decision site: src/server/handle.ts).
* "on" (1/true/on) — force bypass: EVERY inbound model request is raw
* passthrough, all lanes, no compression, no state.
* "off" (0/false/off) — never bypass: ignore any inbound
* x-bili-plugin-bypass header (incl. opencode legacy).
* "auto" (unset/empty/auto) — honor the header per request (today's behavior).
* "invalid" — set to an unrecognized value: behaves as "auto"; the read site
* warns once so a typo can't silently sit on the surface.
* Env-only by owner ruling in the #6220 thread (explicit escape valve, not a
* per-machine tunable — no config-file twin, per reference/env-vars.md hard
* rule 1). Pure: env in, decision out (never throws). */
export function resolvePluginBypass(env: NodeJS.ProcessEnv = process.env): "on" | "off" | "auto" | "invalid" {
const raw = env.BILI_PLUGIN_BYPASS?.trim().toLowerCase();
if (raw === "1" || raw === "true" || raw === "on") return "on";
if (raw === "0" || raw === "false" || raw === "off") return "off";
if (raw === undefined || raw === "" || raw === "auto") return "auto";
return "invalid";
}

31 changes: 29 additions & 2 deletions src/server/handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ import { resolveCompatDropFields } from "../compat-drop.js";
import { unwrapCommandcodeBody, type CommandcodeEnvelopeMeta } from "../commandcode-wire.js";
import { DecompressedTooLargeError, decodeRequestBody, IngestBodyTooLargeError } from "../content-encoding.js";
import { matchModelEndpoint } from "../model-endpoints.js";
import { autoModeForgeEnabled, dshAuthoredCompactionEnabled, imageRescaleEnabled, limitsDecodedBytes as knobLimitsDecodedBytes, limitsIngestBytes as knobLimitsIngestBytes, noInjectTool as knobNoInjectTool, rawDumpDir as knobRawDumpDir } from "../knobs.js";
import { autoModeForgeEnabled, dshAuthoredCompactionEnabled, imageRescaleEnabled, limitsDecodedBytes as knobLimitsDecodedBytes, limitsIngestBytes as knobLimitsIngestBytes, noInjectTool as knobNoInjectTool, rawDumpDir as knobRawDumpDir, resolvePluginBypass } from "../knobs.js";
import { buildForgeResponse, classifierForgeVariant, estimateCjkTokens, isAutoModeClassifier, requestText, summarizeAction } from "../auto-mode-forge.js";
import { extractConversationId, isV1BackgroundSideRequest, isV1InternalRequest, unwrapV1Internal } from "../v1internal.js";
import { anthropicBetaContextWindow, BILI_HOP_HEADER, capRegistryWindowByStandard, expandedContextSuffixWindow, launcherContextWindow, launcherMaxOutput, noteUnresolvedModelSession, UNRESOLVED_MODEL_SESSION_THRESHOLD, windowSourceLogged } from "./context-window.js";
Expand Down Expand Up @@ -114,6 +114,17 @@ function earlyHoldCredHeaders(req: http.IncomingMessage): Record<string, string>
// window sizing falls back to config.toml model_context_window.
const codexConfigWindowWarned = new Set<string>();

// #6220: warn-once per forced state for BILI_PLUGIN_BYPASS ("on" / "off" /
// "invalid") — the first request that sees a forced state gets the loud risk
// line, later requests stay at debug level.
const pluginBypassForceWarned = new Set<string>();

/** Test seam: clear the #6220 warn-once set so a suite can assert the warns
* from a clean slate (same pattern as _resetNoOutputCeilingWarningsForTest). */
export function _resetPluginBypassForceWarnForTest(): void {
pluginBypassForceWarned.clear();
}

// #5608 v3: weakest VERIFIED common-prefix ratio (verifiedPrefix / chainDepth)
// at which a byte-exact MISS may still weakly adopt the closest tracked chain.
// Floor set just below the lowest production telemetry reading in #5608's
Expand Down Expand Up @@ -637,7 +648,23 @@ async function handleRequest(
// plugin stamps this header per request for sessions with acp state on
// disk; acp owns their context in-process, so binding/injecting/compressing
// here would double-manage it. Raw forward, zero state touched.
if (headerValue(req, BILI_PLUGIN_BYPASS_HEADER) === "1") {
// #6220: BILI_PLUGIN_BYPASS force switch (env, tri-state) overrides the
// per-request header decision: "on" force-bypasses ALL inbound traffic
// (escape valve); "off" ignores the header everywhere; unset/auto = the
// header-based behavior below.
const bypassMode = resolvePluginBypass();
if (bypassMode === "on" && !pluginBypassForceWarned.has("on")) {
pluginBypassForceWarned.add("on");
log("warn", `BILI_PLUGIN_BYPASS=on — forced: EVERY inbound model request is now raw passthrough (no compression, no session state, any lane). If nothing in-process (e.g. opencode acp) is actually compressing, your traffic reaches the model UNCOMPRESSED. Unset BILI_PLUGIN_BYPASS to restore per-request header behavior (#6220).`);
} else if (bypassMode === "off" && !pluginBypassForceWarned.has("off")) {
pluginBypassForceWarned.add("off");
log("warn", `BILI_PLUGIN_BYPASS=off — forced: the inbound ${BILI_PLUGIN_BYPASS_HEADER} header is ignored on every request; opencode legacy sessions will now be managed by bili, but their pre-existing ref space lives in the in-process acp store, so their history degrades to a read-only archive. Unset BILI_PLUGIN_BYPASS to restore per-request header behavior (#6220).`);
} else if (bypassMode === "invalid" && !pluginBypassForceWarned.has("invalid")) {
pluginBypassForceWarned.add("invalid");
log("warn", `BILI_PLUGIN_BYPASS is set to an unrecognized value — treating it as unset (auto). Use 1 (force bypass all traffic), 0 (never bypass) or auto/unset (#6220).`);
}
const bypass = bypassMode === "on" || (bypassMode !== "off" && headerValue(req, BILI_PLUGIN_BYPASS_HEADER) === "1");
if (bypass) {
log("debug", `bypass: ${req.method ?? "?"} ${maskUrlForLog(req.url ?? "")} — raw passthrough (legacy in-process compression)`);
await forward(req, res, opts, scrubCompatDrop(scrubAnthropicPck(protocol, bodyBuffer, log), compatDropPaths, log), null, core, config, log, route, instanceId, undefined, undefined, undefined, googleModel ?? vertexModel);
return;
Expand Down
96 changes: 95 additions & 1 deletion tests/plugin-bypass.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import type { ProxyOptions } from "../src/config.ts";
import { SessionStore, _setStoreForTest } from "../src/persist.ts";
import { _setForTest as setRegistryForTest } from "../src/registry.ts";
import { _resetPluginStateForTest } from "../src/plugin.ts";
import { resolvePluginBypass } from "../src/knobs.ts";
import { setLogCapture } from "../src/logger.ts";
import { _resetPluginBypassForceWarnForTest } from "../src/server/handle.ts";
import { closeServers } from "./server-close-guard.ts";

interface Harness {
Expand Down Expand Up @@ -49,7 +52,11 @@ async function startHarness(): Promise<Harness> {
compress: { injectTool: true, injectNudge: true },
promptCache: { routing: "auto" },
sessionHeader: "x-acp-session",
log: false,
// log: true + logFile: "off" (established pattern, e.g. tests/codex-responses-ws.test.ts):
// engine logging live (engine log is a no-op when log: false) but no log file
// opened; the warn-capture test observes the stream via setLogCapture.
log: true,
logFile: "off",
debug: false,
passthrough: false,
autoUpdate: false,
Expand Down Expand Up @@ -81,10 +88,33 @@ beforeEach(async () => {
h = await startHarness();
});
afterEach(async () => {
delete process.env["BILI_PLUGIN_BYPASS"];
setLogCapture(null);
await h?.close();
h = undefined;
});

async function post(model: string, withHeader = false) {
const before = h!.captured.length;
const headers: Record<string, string> = { "content-type": "application/json" };
if (withHeader) headers["x-bili-plugin-bypass"] = "1";
const body = { model, stream: false, messages: [{ role: "user", content: "env switch probe" }] };
const resp = await fetch(`http://127.0.0.1:${h!.proxyPort}/bili/http://127.0.0.1:${h!.upstreamPort}/v1/chat/completions`, {
method: "POST",
headers,
body: JSON.stringify(body),
});
assert.equal(resp.status, 200);
assert.equal(h!.captured.length, before + 1, `exactly one upstream request per post (before=${before} after=${h!.captured.length})`);
const entry = h!.captured[before];
if (!entry) throw new Error(`captured[${before}] missing; length=${h!.captured.length}`);
return JSON.parse(entry.body) as { tools?: unknown[] };
}

function toolNames(seen: { tools?: unknown[] }): string[] {
return (seen.tools ?? []).map((t) => (t as { function?: { name?: string } }).function?.name ?? "?");
}

describe("plugin bypass header (#920)", () => {
it("bypassed requests are forwarded verbatim — no tool injection, no nudge", { timeout: 60_000 }, async () => {
const body = { model: "test-model", stream: false, messages: [{ role: "user", content: "hello legacy session" }] };
Expand Down Expand Up @@ -114,3 +144,67 @@ describe("plugin bypass header (#920)", () => {
assert.ok(names.includes("compress"), `expected compress in ${JSON.stringify(names)}`);
});
});

describe("BILI_PLUGIN_BYPASS force switch (#6220)", () => {
beforeEach(() => {
// the warn-once set is process-global (first request that sees a
// forced state pays the loud line) — start each test clean
_resetPluginBypassForceWarnForTest();
});
it("resolver: tri-state + invalid classification", () => {
assert.equal(resolvePluginBypass({}), "auto");
assert.equal(resolvePluginBypass({ BILI_PLUGIN_BYPASS: "" }), "auto");
assert.equal(resolvePluginBypass({ BILI_PLUGIN_BYPASS: "auto" }), "auto");
for (const v of ["1", "true", "on", " ON ", "On"]) assert.equal(resolvePluginBypass({ BILI_PLUGIN_BYPASS: v }), "on");
for (const v of ["0", "false", "off", " OFF ", "Off"]) assert.equal(resolvePluginBypass({ BILI_PLUGIN_BYPASS: v }), "off");
for (const v of ["banana", "2", "yes", "bypass"] ) assert.equal(resolvePluginBypass({ BILI_PLUGIN_BYPASS: v }), "invalid");
});

it("=1 forces bypass even WITHOUT the header (raw passthrough, no tools)", { timeout: 60_000 }, async () => {
process.env["BILI_PLUGIN_BYPASS"] = "1";
const seen = await post("test-model");
assert.equal(seen.tools, undefined);
});

it("=0 ignores the header — plugin pipeline runs (tools injected)", { timeout: 60_000 }, async () => {
process.env["BILI_PLUGIN_BYPASS"] = "0";
const seen = await post("test-model", true);
assert.ok(toolNames(seen).includes("compress"), `expected compress in ${JSON.stringify(toolNames(seen))}`);
});

it("=auto and unrecognized values keep per-request header behavior", { timeout: 120_000 }, async () => {
process.env["BILI_PLUGIN_BYPASS"] = "auto";
let seen = await post("test-model");
assert.ok(toolNames(seen).includes("compress"), "no header + auto => no bypass, pipeline should run");
seen = await post("test-model", true);
assert.equal(seen.tools, undefined, "header + auto => bypass");

// unrecognized value behaves like auto: header still decides
process.env["BILI_PLUGIN_BYPASS"] = "banana";
seen = await post("test-model", true);
assert.equal(seen.tools, undefined, "header + invalid env => bypass (auto semantics)");
});

it("forced states warn loudly, once each; invalid warns about the typo", { timeout: 120_000 }, async () => {
const lines: { level: string; msg: string }[] = [];
setLogCapture((level, msg) => { lines.push({ level, msg }); });

process.env["BILI_PLUGIN_BYPASS"] = "1";
await post("test-model");
await post("test-model"); // second request: must not repeat the warn
const onWarns = lines.filter((l) => l.level === "warn" && l.msg.includes("BILI_PLUGIN_BYPASS=on"));
assert.equal(onWarns.length, 1, `expected exactly one forced-on warn, got ${JSON.stringify(onWarns)}`);
assert.ok(onWarns[0]!.msg.includes("UNCOMPRESSED"), "forced-on warn must name the no-compression risk");

process.env["BILI_PLUGIN_BYPASS"] = "0";
await post("test-model", true);
const offWarns = lines.filter((l) => l.level === "warn" && l.msg.includes("BILI_PLUGIN_BYPASS=off"));
assert.equal(offWarns.length, 1);
assert.ok(offWarns[0]!.msg.includes("read-only archive"), "forced-off warn must name the legacy-history risk");

process.env["BILI_PLUGIN_BYPASS"] = "banana";
await post("test-model", true);
const invalidWarns = lines.filter((l) => l.level === "warn" && l.msg.includes("unrecognized value"));
assert.equal(invalidWarns.length, 1);
});
});
8 changes: 8 additions & 0 deletions website/config-reference/env-only.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,14 @@ entries:
group: envonly
desc_en: "Spawn channel: marks a self-restart replacement — it must own exactly the freed port or exit without hopping (#3044)."
desc_zh: "派生通道:标记自重启替换进程——必须独占被释放的精确端口,否则直接退出而非跳端口(#3044)。"
- key: BILI_PLUGIN_BYPASS
type: string
default: "unset (auto)"
kind: env-only
audience: user
group: envonly
desc_en: "Force switch for the x-bili-plugin-bypass wire decision (#6220). Unset/auto = the inbound per-request header decides (legacy opencode acp sessions bypass, everyone else is compressed — zero behavior change). 1/on = FORCE bypass for ALL inbound traffic, raw passthrough with no compression or session state: the escape valve when an in-process compressor is loaded but bili still manages those sessions; if nothing in-process (e.g. opencode acp) is actually compressing, traffic reaches the model UNCOMPRESSED. 0/off = ignore any inbound bypass header, including opencode legacy ones: bili takes over those sessions, but their pre-existing ref space lives in the in-process acp store, so their history degrades to a read-only archive. Each forced state emits one loud startup-of-traffic warn; unrecognized values are treated as unset (auto) with one warning. Env-only by design (owner decision in #6220)."
desc_zh: "x-bili-plugin-bypass 透传判决的强制开关(#6220)。缺省/auto = 由每请求头决定(opencode 旧版 acp 会话透传、其余压缩,行为零变化)。1/on = 强制所有流量透传、不压缩不记状态:用于“进程内已有压缩器但 bili 仍在管理这些会话”的逃生阀;若进程内实际没有压缩器(如 opencode acp),流量会以**未压缩**状态直达模型。0/off = 无视一切入站透传头(含 opencode 旧版):bili 接管这些会话,但其既有引用空间在进程内 acp 存储里,历史降级为只读归档。每种强制态首次命中时打一条醒目警告;无法识别的取值按缺省(auto)处理并警告一次。按 owner 决定(#6220)env-only。"
- key: BILI_OPENCODE_ACP_SPEC
type: string
default: "unset"
Expand Down
Loading