billion-context 0.1.165 → 0.1.166
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 +112 -983
- package/README.zh-CN.md +67 -304
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
|
+
> **Cache health at a glance:** a healthy session keeps a **95–97%** prefix-cache hit rate — compression itself costs ≤2%. Sustained lower? Check attribution with `/acp` or `/acp-cache` (see [FAQ](#faq)); usual causes, in order: upstream cache TTL expiry · model switch · a bili bug (please report) · other/unknown.
|
|
46
|
+
|
|
45
47
|
## Community
|
|
46
48
|
|
|
47
49
|
QQ Group:
|
|
@@ -86,91 +88,14 @@ Agent (Claude Code / Codex / Cursor / Aider ...)
|
|
|
86
88
|
real model API (Anthropic / OpenAI / compatible)
|
|
87
89
|
```
|
|
88
90
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
An opt-in fifth tool, `absorb` (`compress.absorb.enabled: true` — see [CONFIGURATION.md](CONFIGURATION.md)), compresses **individual tool results the moment they arrive**: large results (builds, logs, greps) get a forced absorb instruction, the model distills each into a compact summary, and the original pair is hidden from the wire from the next turn on — keeping mid-session pressure lower between fold rounds (#605).
|
|
92
|
-
|
|
93
|
-
The sixth tool, `acp_rule` (opt-in via `compress.rules: true` — see [CONFIGURATION.md](CONFIGURATION.md); once enabled the model has full rights over session rules and may call it unprompted, #1399), records **persistent principle-level reminders**: a short rule recorded by the model (user-emphasized lessons, behaviors to remember, major pitfalls hit) is hard-protected from compression — the call and its result stay in context across every fold — and omitting the argument lists the recorded rules; passing `delete` with a rule id (e.g. `"rule3"`) removes one rule and `clear: true` removes all of them ([ranxianglei/billion-context-pi#433](https://github.com/ranxianglei/billion-context-pi/issues/433)).
|
|
94
|
-
|
|
95
|
-
The seventh tool, `acp_retrieve` (opt-in on every lane — set `compress.ccr.enabled: true` at any level, after local verification; plugin lanes require the explicit global `true` so the manifest advertises the tool, #1271/#1273 — see [CONFIGURATION.md](CONFIGURATION.md)), backs the **content-addressed message store** (built-in CCR, #1097/#1179): oversized tool results are **ID-referenced at arrival instead of force-distilled** — the wire keeps a byte-stable placeholder and the original goes into a per-session content-store envelope (hash-deduped), retrievable on demand via one cheap tool call. V2 makes folds lossless too: covered originals are stored when a fold lands, `decompress` restores ranges (`startId`/`endId` refs) instead of whole blocks, and `search_context` hits carry the covered `mNNNNN` refs so you can fetch exactly what you need. Lossless by default: a retrieve not made costs nothing but the call; a detail distilled away by absorb is gone for good. Scope: proxy mode, plus plugin lanes on the anthropic + openai wires when explicitly enabled (`acp_retrieve` is advertised in the plugin manifest then, #1271); responses marker/text routes and google in plugin mode stay disarmed because no request-only round-trip channel exists there (silent loss, #1097).
|
|
96
|
-
|
|
97
|
-
An opt-in tool, `image_full` (`compress.imageCompression.enabled: true` — see [CONFIGURATION.md](CONFIGURATION.md)), backs **image pre-compression** (#1095): screenshot-like images in tool results are downscaled once at arrival — the kernel decides routing and recipe, the host encodes via optional `sharp` — cutting billed pixels before they enter the wire (providers bill by pixel area; halving dimensions cuts billed tokens ~4×). Non-screenshot images pass through byte-identical. Lossy by nature: when the model can't read details it calls `image_full` with the message's ref to restore the original resolution for the rest of the session — no proxy-side storage needed, since the client's own history still carries the original bytes (it never saw the shrunk form). Default off.
|
|
91
|
+
### Context-management tools
|
|
98
92
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
### Two compression modes — who executes `compress`
|
|
102
|
-
|
|
103
|
-
The proxy runs in one of two modes, and **the mode decides who executes
|
|
104
|
-
`compress`, which in turn decides how the summary travels to the model** (the
|
|
105
|
-
"carrier"). This distinction is the root of #377.
|
|
106
|
-
|
|
107
|
-
| | **Launcher / plugin mode** (`bili pi`, `bili codex`, …) | **Proxy mode** (plain client → `/bili/`) |
|
|
108
|
-
|---|---|---|
|
|
109
|
-
| Client | ACP-native agent with the bili extension (pi/omp) | Any OpenAI/Anthropic client, no extension |
|
|
110
|
-
| Who executes `compress` | **The agent** (pi runs it locally) | **The proxy** (server-side compress loop) |
|
|
111
|
-
| `compress` tool call in the re-sent history? | Yes — part of the agent's own conversation | No — ephemeral proxy-loop traffic |
|
|
112
|
-
| Preflight blocks (no tool call)? | Last-resort backstop — the agent normally compresses on its own `compress` calls, but `src/preflight.ts` still fires (in both modes) when the input alone exceeds the window (#470) | Yes — `src/preflight.ts` compresses behind the client's back |
|
|
113
|
-
| **Summary carrier on the wire** | **the `compress` tool call** | **an `acp_summary` user message** |
|
|
114
|
-
| System messages on the wire | always exactly 1 (client + prompt) | always exactly 1 (client + prompt) — summaries ride on user messages |
|
|
115
|
-
| SGLang "single system" 400 (#377) | cannot happen | cannot happen (summaries are user messages, not system) |
|
|
116
|
-
| Proxy-injected `compress` tools | none — the agent registers the 4 ACP tools natively | the 4 context tools (when enabled) |
|
|
117
|
-
| Proxy-injected nudge | **yes** — the agent has no nudge channel of its own, so the proxy-side nudge is the proactive compression trigger (preflight alone only fires at the hard limit; #451) | yes (when enabled) |
|
|
118
|
-
|
|
119
|
-
**Why the carriers differ.** In plugin mode the agent owns compression: the
|
|
120
|
-
`compress` call + result live in the agent's own history and are re-sent every
|
|
121
|
-
turn, so the summary rides on the tool call and the agent's view never renders
|
|
122
|
-
the kernel's `acp_summary` fallback (`billion-context-pi` `src/messages.ts`
|
|
123
|
-
skips `acp_summary_*`). In proxy mode the client is not ACP-native, so the
|
|
124
|
-
proxy executes `compress` server-side; the tool call never enters the client's
|
|
125
|
-
history, and preflight blocks have no tool call at all — so the kernel's
|
|
126
|
-
`acp_summary` message is the only carrier. The kernel renders it as role
|
|
127
|
-
`system`, but strict OpenAI-compatible backends (SGLang) require exactly one
|
|
128
|
-
system message at index 0, so `systemToUser` (`src/util.ts`) re-voices it as a
|
|
129
|
-
`user` message, leaving it at its anchor position. This keeps the head system
|
|
130
|
-
message (the prefix-cache anchor) byte-stable across compress turns, so a new
|
|
131
|
-
block does not invalidate the whole-conversation prefix.
|
|
132
|
-
|
|
133
|
-
**Why `user`, not `system` or a forged tool call.** A mid-stream `system`
|
|
134
|
-
message is what SGLang rejects (#377). A forged `compress` tool call would be
|
|
135
|
-
the "pure" carrier, but in proxy mode it requires fabricating an
|
|
136
|
-
assistant `tool_calls` + `user` `tool_result` pair by id, declaring the tool in
|
|
137
|
-
the request, and handling preflight blocks that have no authentic call — far
|
|
138
|
-
more invasive than re-voicing a standalone note. A `user` message is allowed
|
|
139
|
-
anywhere in the conversation, so it is the minimal change that satisfies both
|
|
140
|
-
SGLang's one-system rule and prefix-cache stability. The accepted trade-off:
|
|
141
|
-
a summary is a stand-in for the folded history, and re-voicing it as a user
|
|
142
|
-
turn is a semantic mismatch the model tolerates (it is clearly marked
|
|
143
|
-
`[Compressed conversation section]`).
|
|
144
|
-
|
|
145
|
-
**Do the two modes coexist?**
|
|
146
|
-
|
|
147
|
-
- **Same proxy instance: yes, by design.** One proxy serves plugin and plain
|
|
148
|
-
clients at once; `pluginMode` is decided per request (`x-bili-plugin` header)
|
|
149
|
-
and bound per session (`session.metadata.pluginAgent`). The launcher reuses a
|
|
150
|
-
running proxy.
|
|
151
|
-
- **Same session: the mode is sticky.** A session created in plugin mode stays
|
|
152
|
-
plugin mode (metadata inheritance); a plain session can only be *upgraded* to
|
|
153
|
-
plugin mode if a plugin request arrives with a matching conversation id (the
|
|
154
|
-
header outranks) — and never downgraded. In practice a plain→plugin upgrade
|
|
155
|
-
requires the plugin client's conversation id to match an existing plain
|
|
156
|
-
session id, which doesn't happen (each client generates its own id).
|
|
157
|
-
- **Cross-mode block hazard: theoretical only.** It would require the same
|
|
158
|
-
conversation id to span a mode switch. plugin→proxy is safe (the tool call is
|
|
159
|
-
in the shared history); proxy→plugin could orphan proxy-created block
|
|
160
|
-
summaries (their tool call isn't in the agent's history and the agent's view
|
|
161
|
-
skips `acp_summary`) — but that needs the id match above, which doesn't occur.
|
|
162
|
-
|
|
163
|
-
**Verifying that a compression actually landed.** After executing `compress`,
|
|
164
|
-
the proxy emits a confirmation marker (`📦 [ACP] Compressed …`) as plain
|
|
165
|
-
assistant text — but under sustained context pressure a model was observed
|
|
166
|
-
*writing that marker format itself* without ever calling the tool (#717): 17
|
|
167
|
-
fake "compressions" over ~2 hours while real usage climbed to 89%. A marker
|
|
168
|
-
line visible in the transcript is therefore not proof of persistence — verify
|
|
169
|
-
with `acp_status` (block count increased, compressible-range start advanced)
|
|
170
|
-
before trusting it. As a backstop, the proxy strips any marker-shaped line the
|
|
171
|
-
model emits on its own and logs a `[marker-echo]` warning, and both the nudge
|
|
172
|
-
and the injected prompt state explicitly that markers are proxy-emitted only.
|
|
93
|
+
The proxy injects four context-management tools into the conversation; the model calls them itself as context grows, and the proxy executes `compress` server-side so folded ranges stay summarized in history until restored:
|
|
173
94
|
|
|
95
|
+
- **`compress`** — fold a message range into a detailed summary.
|
|
96
|
+
- **`decompress`** — restore a compressed range when exact details are needed again.
|
|
97
|
+
- **`search_context`** — keyword search over compressed summaries and visible messages.
|
|
98
|
+
- **`acp_status`** — context-usage overview plus which ranges are still compressible.
|
|
174
99
|
|
|
175
100
|
## Which do I need?
|
|
176
101
|
|
|
@@ -179,25 +104,25 @@ Pick by your client:
|
|
|
179
104
|
| Client | Use |
|
|
180
105
|
|---|---|
|
|
181
106
|
| **pi** | [`billion-context-pi`](https://github.com/ranxianglei/billion-context-pi) (in-process extension) |
|
|
182
|
-
| **opencode** (1.x / 2.x) | [`billion-context`](https://github.com/ranxianglei/billion-context) — `bili opencode` (launcher) or `bili plugin install opencode` (native
|
|
107
|
+
| **opencode** (1.x / 2.x) | [`billion-context`](https://github.com/ranxianglei/billion-context) — `bili opencode` (launcher) or `bili plugin install opencode` (native); standalone [`opencode-acp`](https://github.com/ranxianglei/opencode-acp) remains usable on 1.x — full guide: [OpenCode](CLIENTS.md#opencode) |
|
|
183
108
|
| **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili omp` (built-in plugin) or `bili plugin install omp` (self-spawning native plugin, no launcher) |
|
|
184
|
-
| **dsh** | `bili dsh` (launcher — full native plugin via `--patch
|
|
185
|
-
| **kimi** | `bili plugin install kimi` (self-spawning native
|
|
186
|
-
| **hermes** | `bili plugin install hermes` (self-spawning native
|
|
187
|
-
| **zcode** (Z.ai / bigmodel coding plan) | `bili plugin install zcode` (self-spawning native
|
|
109
|
+
| **dsh** | `bili dsh` (launcher — full native plugin via `--patch`) or `bili plugin install dsh` ≡ `dsh plugin --profile <name> add billion-context` (one unified lane) — details: [CLIENTS.md](CLIENTS.md) |
|
|
110
|
+
| **kimi** | `bili plugin install kimi` (self-spawning native, Kimi Code ≥ 2.0.0) or `bili kimi` (cert-MITM) or `/bili/` prefix — details: [CLIENTS.md](CLIENTS.md) |
|
|
111
|
+
| **hermes** | `bili plugin install hermes` (self-spawning native, Python plugin #958) or `bili hermes` (cert-MITM) |
|
|
112
|
+
| **zcode** (Z.ai / bigmodel coding plan) | `bili plugin install zcode` (self-spawning native, #1145) or cert-MITM via the GUI's Settings → Network or `/bili/` prefix — details: [CLIENTS.md](CLIENTS.md) |
|
|
188
113
|
| **claude** | `bili claude` (launcher) or `bili plugin install claude` (native posture, #964 — managed settings block + session-owned proxy; see the notes below) |
|
|
189
|
-
| **jcode** | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili jcode` (
|
|
190
|
-
| **gemini** (Gemini CLI) | `bili gemini` (launcher, `GOOGLE_GEMINI_BASE_URL` `/bili/` rewrite) or `/bili/` prefix — launcher-only
|
|
114
|
+
| **jcode** | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili jcode` (cert-MITM) or `/bili/` prefix — no native mode (compiled Rust binary, no plugin seam, [#962](https://github.com/ranxianglei/billion-context/issues/962)) |
|
|
115
|
+
| **gemini** (Gemini CLI) | `bili gemini` (launcher, `GOOGLE_GEMINI_BASE_URL` `/bili/` rewrite) or `/bili/` prefix — launcher-only (no in-loop tool seam, #1043) |
|
|
191
116
|
| **iflow** (iFlow CLI) | `bili iflow` (launcher, `IFLOW_BASE_URL` `/bili/` rewrite) or `/bili/` prefix |
|
|
192
117
|
| **qwen** (Qwen Code) | `bili qwen` (launcher, cert-MITM) or `/bili/` prefix |
|
|
193
|
-
| **mcode** (MiniMax Code) | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili mcode` (
|
|
194
|
-
| **aider** | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili aider` (
|
|
195
|
-
| **copilot** (GitHub Copilot CLI) | `bili copilot` (launcher, cert-MITM) — closed Go binary, no plugin seam
|
|
196
|
-
| **amp** (Amp CLI) | `bili amp` (launcher, cert-MITM) — closed Go binary, no plugin seam
|
|
197
|
-
| **goose** (Goose CLI) | `bili goose` (launcher) — rustls
|
|
118
|
+
| **mcode** (MiniMax Code) | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili mcode` (cert-MITM) or `/bili/` prefix — no native mode (event hooks only, no model-request seam, [#1050](https://github.com/ranxianglei/billion-context/issues/1050)) |
|
|
119
|
+
| **aider** | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili aider` (cert-MITM) or `/bili/` prefix — no native mode (shell-command-only hooks, no tool-injection seam, [#1048](https://github.com/ranxianglei/billion-context/issues/1048)) |
|
|
120
|
+
| **copilot** (GitHub Copilot CLI) | `bili copilot` (launcher, cert-MITM) — closed Go binary, no plugin seam (#1049) |
|
|
121
|
+
| **amp** (Amp CLI) | `bili amp` (launcher, cert-MITM) — closed Go binary, no plugin seam (#1049) |
|
|
122
|
+
| **goose** (Goose CLI) | `bili goose` (launcher) — rustls trusts no CA file, so no cert-MITM: openai/anthropic legs via `OPENAI_HOST`/`ANTHROPIC_HOST`, custom providers via a regenerated `GOOSE_PATH_ROOT` overlay (#1049) |
|
|
198
123
|
| **everything else** (no context hook) | [`billion-context`](https://github.com/ranxianglei/billion-context) — `bili <client>` (launcher, preferred) or `/bili/` prefix |
|
|
199
124
|
|
|
200
|
-
**Native mode vs standalone extensions.** The host-native plugins (`bili plugin install
|
|
125
|
+
**Native mode vs standalone extensions.** The host-native plugins (`bili plugin install …`) and the standalone in-process extensions (`billion-context-pi`, `opencode-acp`) are **mutually exclusive** — both active means double compression. The installer makes the switch: it replaces the legacy entries (bare name, `npm:` alias, versioned, path form; array or object shape) and snapshots the original config to `.bili-bak` once; a **project-local** install is not touched — remove that one by hand. As a runtime safety net for manual installs, the native entries set `BILLION_CONTEXT_NATIVE=<host>` synchronously at load so a standalone extension can stand down at action time. On the pi side the marker needs `billion-context-pi` **0.1.72+**, and the pi-native entry scans both pi settings files once its proxy is up and warns loudly when it spots a co-resident legacy entry the installer never saw — that warning is the only visible signal while an old `billion-context-pi` silently double-compresses.
|
|
201
126
|
|
|
202
127
|
|
|
203
128
|
## Install
|
|
@@ -212,11 +137,11 @@ This installs the `bili` command (`bili-proxy` is kept as an alias).
|
|
|
212
137
|
|
|
213
138
|
Three ways to use it — pick one:
|
|
214
139
|
|
|
215
|
-
- **Native plugin (
|
|
140
|
+
- **Native plugin (most native):** `bili plugin install <client>` — bili
|
|
216
141
|
becomes a plugin inside the client; start the client as usual.
|
|
217
|
-
- **Launcher (
|
|
142
|
+
- **Launcher (config-free):** one `bili <client>` command brings up the proxy and
|
|
218
143
|
the client together — no real config file is ever touched.
|
|
219
|
-
- **URL change (
|
|
144
|
+
- **URL change (most universal):** prefix your client's baseURL with the proxy
|
|
220
145
|
origin + `/bili/`.
|
|
221
146
|
|
|
222
147
|
Mechanism details behind these three options (plugin lifecycle, runtime-info
|
|
@@ -238,7 +163,6 @@ bili plugin install kimi # writes $KIMI_CODE_HOME/plugins/managed/billion
|
|
|
238
163
|
bili plugin install hermes # copies the Python plugin into ~/.hermes/plugins/billion-context/ (+ machine-owned bili.json sidecar) and enables it via `hermes plugins enable billion-context`
|
|
239
164
|
bili plugin install zcode # writes hooks.enabled + a SessionStart hook + mcp.servers.bili into ~/.zcode/cli/config.json; per-session routing lands in the bigmodel provider store on first start
|
|
240
165
|
bili plugin remove <client> # undo (dsh removes through the same channel; config snapshots go to .bili-bak)
|
|
241
|
-
bili plugin update [client] # bring every lane's bili presence up to date, each through its own owner (see below)
|
|
242
166
|
```
|
|
243
167
|
|
|
244
168
|
Where a client has its own plugin channel you can also install natively,
|
|
@@ -262,150 +186,17 @@ For pi / omp / kimi / claude there is no client-side channel — `bili plugin
|
|
|
262
186
|
install <client>` writes their config entries for you (kimi's declarative
|
|
263
187
|
`kimi.plugin.json` + registry record, claude's managed settings block, …).
|
|
264
188
|
|
|
265
|
-
#### Single-writer: who owns which copy (#991)
|
|
266
|
-
|
|
267
|
-
Every bili presence on a machine has exactly **one writer** — the thing
|
|
268
|
-
that installed it is the thing that updates it, and nothing else ever
|
|
269
|
-
overwrites that copy in place:
|
|
270
|
-
|
|
271
|
-
| Lane | Copy lives in | Updated by |
|
|
272
|
-
|------|---------------|------------|
|
|
273
|
-
| global `bili` | npm global (`npm i -g billion-context`) | `bili update` / background auto-update |
|
|
274
|
-
| **pi** | pi's package manager (npm form) | **`pi update`** — bili never overwrites it |
|
|
275
|
-
| **opencode** | opencode's plugin dir | **opencode's plugin manager** — bili never overwrites it |
|
|
276
|
-
| **dsh** | each profile's pnpm store | a periodic check re-runs dsh's plugin channel per profile — driven by the global bili self-update **or by the profile copy's own proxy** when the global isn't running (dsh-market installs, #1196); manual: `dsh plugin add billion-context@latest`. pnpm's hardlinked store must never be copied over in place |
|
|
277
|
-
| omp / claude / codex / kimi / zcode | no copy — entries point at the global bili install | they update together with the global copy |
|
|
278
|
-
| **hermes** | `~/.hermes/plugins/billion-context/` (copied files + `bili.json` sidecar pointing at the global dist) | **`bili plugin update hermes`** re-copies the files; the sidecar tracks the global install |
|
|
279
|
-
|
|
280
|
-
This is enforced in code, not just convention: the self-updater
|
|
281
|
-
(`src/update.ts` → `hostManagedInstall`) detects install dirs under a pnpm
|
|
282
|
-
virtual store (`.pnpm`) or a host agent tree (pi / opencode / dsh / kimi /
|
|
283
|
-
omp homes) and **skips** them; `installViaTarball` refuses them structurally
|
|
284
|
-
so direct callers cannot corrupt a store either. Mixing *commands* is fine
|
|
285
|
-
(`dsh plugin add` ≡ `bili plugin install dsh` — same channel, same records);
|
|
286
|
-
mixing *writers* is what the guard forbids. `bili plugin update [client]`
|
|
287
|
-
is the one command that drives every lane through its own owner and prints
|
|
288
|
-
the per-lane update path (`bili plugin list` shows the same per-lane channel).
|
|
289
|
-
|
|
290
|
-
At load the plugin **spawns its own proxy** (attaches to a healthy running
|
|
291
|
-
one only when it passes the attach gate below; a parent-pid watchdog tears
|
|
292
|
-
it down when the client exits),
|
|
293
|
-
rewrites model traffic to `<proxy>/bili/<upstream-url>`, registers
|
|
294
|
-
`compress` / `decompress` / `acp_status` as native client tools (plugin
|
|
295
|
-
mode), and reports the client's **own model config** to the proxy so
|
|
296
|
-
compression budgets use the real window instead of a registry guess.
|
|
297
|
-
Opt-out envs: `BILI_NATIVE_PI=0`, `BILI_NATIVE_OMP=0`,
|
|
298
|
-
`BILI_NATIVE_OPENCODE=0`, `BILI_NATIVE_DSH=0`, `BILI_NATIVE_KIMI=0`,
|
|
299
|
-
`BILI_NATIVE_HERMES=0`, `BILI_NATIVE_ZCODE=0`. Full
|
|
300
|
-
mechanics: [TECHNICAL-NOTES.md](TECHNICAL-NOTES.md).
|
|
301
|
-
|
|
302
|
-
Reuse is identity-based (#1225) **and lifecycle-gated (#1335)**: an existing
|
|
303
|
-
proxy is attached only when it runs the **same code** (sha256 of the entry
|
|
304
|
-
script, recorded in the instance file), its **lane is compatible** — each
|
|
305
|
-
launcher declares its client's lane, two *different declared* lanes never
|
|
306
|
-
share — **and it owns a session lifecycle**: its health endpoint reports an
|
|
307
|
-
armed parent-pid watchdog (`watchdog.armed == true`), i.e. it was spawned by
|
|
308
|
-
a launcher with a parent pid and dies when the last attached session dies.
|
|
309
|
-
An instance without a declared lane is wildcard-compatible on the lane axis,
|
|
310
|
-
but that alone no longer makes it attachable (see the gate below).
|
|
311
|
-
Instances written before #1225 carry no code fingerprint and are therefore
|
|
312
|
-
never attached: a rebuilt or updated install always starts a fresh proxy on
|
|
313
|
-
the next launch, so fixes take effect immediately instead of silently
|
|
314
|
-
serving stale code.
|
|
315
|
-
|
|
316
|
-
**The attach gate (#1335).** A native hook attaches to whatever answers on
|
|
317
|
-
the port, so the three listener kinds get different treatment (TS lanes and
|
|
318
|
-
the hermes Python plugin's discovery path alike, #1338):
|
|
319
|
-
|
|
320
|
-
| Listener | Lifecycle owner | Attach? |
|
|
321
|
-
|---|---|---|
|
|
322
|
-
| Its own session-spawned proxy | armed from birth | ✅ yes |
|
|
323
|
-
| Another session's armed proxy (shared, watcher set #1186) | watcher set | ✅ yes — sharing stays the design |
|
|
324
|
-
| Manually started `bili start` daemon | **none** — refuses watchers, never dies with sessions, often an older build | ❌ not by default |
|
|
325
|
-
|
|
326
|
-
The hook probes the candidate's `/__bili/health` for `watchdog.armed` before
|
|
327
|
-
attaching. Armed → attach + register a watcher (current behavior, README
|
|
328
|
-
lifecycle contract holds). Unarmed — or a pre-#1330 build that reports no
|
|
329
|
-
`watchdog` field at all (unverifiable, treated as unarmed) → **do not
|
|
330
|
-
attach**; the hook spawns its own session-owned proxy (ephemeral port, armed
|
|
331
|
-
from birth, dies with the last session). This also fixes version skew: every
|
|
332
|
-
session now runs the **currently installed** bili instead of whatever a
|
|
333
|
-
stale resident daemon happens to carry. The trade-off is one extra short-lived
|
|
334
|
-
proxy process per session when no armed proxy exists (session state is shared
|
|
335
|
-
on disk, so compression continuity is unaffected); the multi-instance warning
|
|
336
|
-
(#394) becomes correspondingly more common. **Escape hatch:** deliberately
|
|
337
|
-
run a resident daemon for your hooks to ride on → set
|
|
338
|
-
`native.attachExternal: true` in the config file or
|
|
339
|
-
`BILI_NATIVE_ATTACH_EXTERNAL=1`. That restores attaching to any compatible
|
|
340
|
-
listener regardless of watchdog state — you then own the daemon's lifetime
|
|
341
|
-
and version yourself. Explicit user-directed attaches (`BILLION_CONTEXT_ATTACH`
|
|
342
|
-
/ preset `BILLION_CONTEXT_PROXY` for kimi/dsh) bypass discovery entirely and
|
|
343
|
-
are exempt by construction.
|
|
344
|
-
|
|
345
|
-
Attach discovery is lane-aware across **all** live instances (#1232): the
|
|
346
|
-
launcher probes every live entry in the instance registry, not just the
|
|
347
|
-
single instance file (last-writer-wins — under concurrent multi-client use
|
|
348
|
-
it can point at another client's proxy), and applies the gate above to every
|
|
349
|
-
candidate. Among compatible candidates the newest instance with the launcher's
|
|
350
|
-
own declared lane wins; an instance without a lane is wildcard-compatible on
|
|
351
|
-
the lane axis (still subject to the gate). The `another bili instance is
|
|
352
|
-
running` warning (#394) is lane-aware too: it fires for same-lane or lane-less
|
|
353
|
-
coexistence, but stays silent between two *different* declared lanes, whose
|
|
354
|
-
session files are disjoint.
|
|
355
|
-
|
|
356
|
-
**Runtime-info protocol (#955).** A native plugin reads the model config
|
|
357
|
-
the client itself will use and pushes it to the proxy (per-request headers
|
|
358
|
-
+ bootstrap report); the proxy prefers that truth over the models.dev
|
|
359
|
-
registry / built-in table when resolving the context window. Protocol
|
|
360
|
-
details, resolution order, and implementations:
|
|
361
|
-
[TECHNICAL-NOTES.md](TECHNICAL-NOTES.md).
|
|
362
|
-
|
|
363
189
|
Notes:
|
|
364
190
|
|
|
365
|
-
- Native mode is **mutually exclusive** with the standalone in-process
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
-
|
|
371
|
-
- `
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
mechanics in the "Kimi Code" section below.
|
|
375
|
-
- `hermes`'s native plugin is Python (its CLI agent's plugin API is
|
|
376
|
-
Python-only) — instead of patching fetch it points hermes' httpx stack at
|
|
377
|
-
the proxy via env vars after a health check, and stamps per-request headers
|
|
378
|
-
through an `llm_request` middleware; full mechanics in the "Hermes" section
|
|
379
|
-
below.
|
|
380
|
-
- `codex` has a companion install too (an MCP shell), but it needs a running
|
|
381
|
-
proxy — it is not native mode.
|
|
382
|
-
- `claude` also has a **native posture** (#964): `bili plugin install
|
|
383
|
-
claude` writes a managed settings block (static `/bili/` URL +
|
|
384
|
-
`SessionStart` hook) plus an MCP shell pinned to a stable port — the
|
|
385
|
-
proxy lives and dies with the session. Opt out with
|
|
386
|
-
`BILI_NATIVE_CLAUDE=0` (passthrough). Mechanics:
|
|
387
|
-
[TECHNICAL-NOTES.md](TECHNICAL-NOTES.md).
|
|
388
|
-
- `zcode` also has a **native posture** (#1145): `bili plugin install
|
|
389
|
-
zcode` writes `~/.zcode/cli/config.json` (`hooks.enabled` +
|
|
390
|
-
`SessionStart` hook + stdio MCP server) and rewrites the bigmodel
|
|
391
|
-
coding-plan provider's `baseURL` to `<proxy>/bili/<upstream>` per
|
|
392
|
-
session (both store generations: legacy `v2/config.json` and v3.14+
|
|
393
|
-
`provider_config.json`) — full mechanics in the "ZCode" section below.
|
|
394
|
-
- `jcode` has no native mode at all: it is a compiled Rust binary with no
|
|
395
|
-
plugin or extension seam, its only per-provider request surface is a static
|
|
396
|
-
TOML header table applied verbatim to every request, and its MCP servers
|
|
397
|
-
run in a global pool shared across all sessions — so there is neither a
|
|
398
|
-
way to rewrite model traffic in-process nor one to stamp the per-request
|
|
399
|
-
headers plugin mode requires (`x-bili-plugin`, conversation id,
|
|
400
|
-
runtime-info). Full source-level analysis: [#962](https://github.com/ranxianglei/billion-context/issues/962)
|
|
401
|
-
(closed wontfix). Use `bili jcode`.
|
|
402
|
-
- `aider` has no native mode either: it is a Python script structure whose
|
|
403
|
-
hook surface is limited to shell commands around file edits and idle
|
|
404
|
-
notifications (`--git-commit-verify`, `--notifications-command`) — there is
|
|
405
|
-
no plugin or extension API and no MCP client, so there is no
|
|
406
|
-
tool-injection seam for plugin mode. Use `bili aider`
|
|
407
|
-
([#1048](https://github.com/ranxianglei/billion-context/issues/1048)).
|
|
408
|
-
- `copilot`, `amp` and `goose` are launcher-only (#1049): none exposes a tool-injection seam, so there is no native mode (a codex-style MCP-shell companion remains possible for amp/goose but is not shipped). Goose additionally cannot be cert-MITMed — its release builds run rustls/webpki and trust no CA file — so it rides plain-HTTP base-URL redirects instead of proxy envs.
|
|
191
|
+
- Native mode is **mutually exclusive** with the standalone in-process extensions (`billion-context-pi`, `opencode-acp`) — the installer swaps the entries and snapshots the original config (`.bili-bak`).
|
|
192
|
+
- OpenCode legacy sessions, V1/V2 shapes and caveats: [OpenCode](CLIENTS.md#opencode).
|
|
193
|
+
- `kimi` reports runtime-info at bootstrap only (static headers can't carry per-request window/model values) and binds subagents by per-call `conversation_id`.
|
|
194
|
+
- `hermes`'s native plugin is Python: it points hermes' httpx stack at the proxy via env vars after a health check and stamps per-request headers through an `llm_request` middleware.
|
|
195
|
+
- `codex` has a companion MCP-shell install too, but it needs a running proxy — not native mode.
|
|
196
|
+
- `claude` has a native posture (#964): managed settings block + `SessionStart` hook + MCP shell on a stable port; opt out with `BILI_NATIVE_CLAUDE=0` (passthrough). Mechanics: [TECHNICAL-NOTES.md](TECHNICAL-NOTES.md).
|
|
197
|
+
- `zcode` has a native posture (#1145): managed `~/.zcode/cli/config.json` block + per-session provider `baseURL` rewrite. Full mechanics: [CLIENTS.md](CLIENTS.md).
|
|
198
|
+
- `jcode` and `aider` have no native mode (no plugin/MCP/tool-injection seam: #962, #1048) — use `bili jcode` / `bili aider`.
|
|
199
|
+
- `copilot`, `amp` and `goose` are launcher-only (#1049); goose cannot be cert-MITMed (rustls trusts no CA file) and rides plain-HTTP base-URL redirects instead.
|
|
409
200
|
|
|
410
201
|
### Option 2 — Launcher (`bili pi` / `bili codex` / `bili claude` / `bili omp` / `bili opencode` / `bili hermes` / `bili dsh` / `bili codebuddy` / `bili qoder` / `bili trae` / `bili jcode` / `bili kimi` / `bili gemini` / `bili iflow` / `bili qwen` / `bili mcode` / `bili aider` / `bili copilot` / `bili amp` / `bili goose`)
|
|
411
202
|
|
|
@@ -423,22 +214,22 @@ bili pi # launch pi through the proxy — file-fre
|
|
|
423
214
|
bili codex # launch codex through the proxy
|
|
424
215
|
bili claude # launch claude through the proxy
|
|
425
216
|
bili omp # pi-style, file-free (#535): env + extension registerProvider + compaction cancel, real ~/.omp untouched
|
|
426
|
-
bili opencode # OpenCode (1.x & 2.x): full guide in the [OpenCode](#opencode) section below
|
|
217
|
+
bili opencode # OpenCode (1.x & 2.x): full guide in the [OpenCode](CLIENTS.md#opencode) section below
|
|
427
218
|
bili hermes # file-free (#535): hermes proxy env (HTTPS_PROXY + combined CA bundle via SSL_CERT_FILE) — https via CONNECT MITM, http via absolute-form forward proxy; real ~/.hermes untouched
|
|
428
|
-
bili dsh # deepseek-harness: full native plugin injected via --patch (#941) —
|
|
219
|
+
bili dsh # deepseek-harness: full native plugin injected via --patch (#941) — real dsh tools, session-bound /acp + /acp-cache (plugin mode); non-loopback upstreams ride proxy envs, loopback keeps the overlay DSH_HOME rewrite (#535); dsh auto-compaction off
|
|
429
220
|
bili codebuddy # Tencent CodeBuddy Code CLI: CODEBUDDY_BASE_URL /bili/ rewrite (OpenAI chat completions wire), budget aligned via CODEBUDDY_AUTO_COMPACT_WINDOW; real ~/.codebuddy untouched
|
|
430
221
|
bili qoder # qoder: model endpoint is hardcoded https (no /bili/ rewrite possible) — cert-MITM via HTTPS_PROXY + NODE_EXTRA_CA_CERTS, default model hosts whitelisted (#653)
|
|
431
222
|
bili trae # Trae CLI (ByteDance, closed Go binary, no base-URL override) — cert-MITM via HTTPS_PROXY + SSL_CERT_FILE, model host from TRAE_CLI_API_HOST or the default enterprise gateway (#655)
|
|
432
223
|
bili jcode # jcode (Rust agent harness) — env-only cert-MITM launch: HTTPS_PROXY + SSL_CERT_FILE, model host api.z.ai whitelisted, local loopback providers stay direct via NO_PROXY
|
|
433
|
-
bili kimi # Kimi Code CLI (Moonshot):
|
|
224
|
+
bili kimi # Kimi Code CLI (Moonshot): standard proxy envs except an unconditional loopback bypass — https via cert-MITM, http via absolute-form forward proxy; loopback endpoints inventoried with a manual /bili/ hint (#757)
|
|
434
225
|
bili gemini # Gemini CLI (Google): GOOGLE_GEMINI_BASE_URL /bili/ rewrite to generativelanguage.googleapis.com (Google native wire), real ~/.gemini untouched
|
|
435
226
|
bili iflow # iFlow CLI: IFLOW_BASE_URL /bili/ rewrite to apis.iflow.cn/v1 (OpenAI chat-completions wire), real ~/.iflow untouched
|
|
436
227
|
bili qwen # Qwen Code (multi-protocol gemini-cli fork, no base-URL hook): cert-MITM via HTTPS_PROXY + NODE_EXTRA_CA_CERTS, default DashScope/Qwen model hosts whitelisted, custom relays via --mitm-domain
|
|
437
|
-
bili mcode # MiniMax Code CLI:
|
|
438
|
-
bili aider # Aider (Python pair programmer): cert-MITM via HTTPS_PROXY + SSL_CERT_FILE/REQUESTS_CA_BUNDLE; endpoint from OPENAI_API_BASE / ANTHROPIC_BASE_URL
|
|
228
|
+
bili mcode # MiniMax Code CLI: same shape as kimi (proxy envs, unconditional loopback bypass, cert-MITM/absolute-form); session bound via X-Mavis-Session-Id (#1050)
|
|
229
|
+
bili aider # Aider (Python pair programmer): cert-MITM via HTTPS_PROXY + SSL_CERT_FILE/REQUESTS_CA_BUNDLE; endpoint from OPENAI_API_BASE / ANTHROPIC_BASE_URL / --openai-api-base / .aider.conf.yml (#1048)
|
|
439
230
|
bili copilot # Copilot CLI (GitHub, closed Go binary) — cert-MITM via HTTPS_PROXY + SSL_CERT_FILE, api.githubcopilot.com + per-plan subdomains whitelisted (#1049)
|
|
440
231
|
bili amp # Amp CLI (Sourcegraph, closed Go binary) — cert-MITM via HTTPS_PROXY + SSL_CERT_FILE, ampcode.com whitelisted (#1049)
|
|
441
|
-
bili goose # Goose (Block, Rust/reqwest): rustls release builds trust no CA file —
|
|
232
|
+
bili goose # Goose (Block, Rust/reqwest): rustls release builds trust no CA file — openai/anthropic legs via OPENAI_HOST/ANTHROPIC_HOST, custom providers via a regenerated GOOSE_PATH_ROOT overlay (/bili/ rewrites, real config untouched) (#1049)
|
|
442
233
|
bili pi --mitm-domain api.foo.com # add a domain to the MITM whitelist
|
|
443
234
|
```
|
|
444
235
|
|
|
@@ -459,13 +250,7 @@ client baseURL before: https://api.openai.com/v1
|
|
|
459
250
|
client baseURL after: http://localhost:8787/bili/https://api.openai.com/v1
|
|
460
251
|
```
|
|
461
252
|
|
|
462
|
-
|
|
463
|
-
passes it through untouched). Context windows (gpt-5.1-codex=400K,
|
|
464
|
-
glm-5.2=1M, claude-opus-4=200K, …) are looked up from models.dev
|
|
465
|
-
automatically.
|
|
466
|
-
|
|
467
|
-
For per-client configuration examples (OpenCode, Codex, Pi, login-client
|
|
468
|
-
MITM, …) see the web UI guide at [http://localhost:8787](http://localhost:8787).
|
|
253
|
+
For more per-client configuration examples, see the web UI guide at [http://localhost:8787](http://localhost:8787).
|
|
469
254
|
|
|
470
255
|
**Verify.** With the proxy running and your config saved, check it answers
|
|
471
256
|
and that your first real request shows compression activity in the log:
|
|
@@ -484,464 +269,60 @@ Then send one message from your client and watch the log
|
|
|
484
269
|
should see a `processTurn` line per request, and once the conversation grows,
|
|
485
270
|
`[acp-usage] round N input=X cached=Y (cache hit Z%)` + a `compress` event.
|
|
486
271
|
|
|
487
|
-
###
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
the attribution gate lets through unproxied (once per process). Reliable
|
|
542
|
-
workaround meanwhile: launch through `bili dsh` instead — the launcher's
|
|
543
|
-
settings overlay rewrites those providers' `baseURL`s to `/bili/` URLs, so
|
|
544
|
-
the traffic reaches the proxy regardless of which fetch the transport uses
|
|
545
|
-
or what the attribution state is.
|
|
546
|
-
|
|
547
|
-
Under a `bili dsh` launch the plugin ATTACHES to the launcher's proxy (no
|
|
548
|
-
second spawn). Raw upstream URLs rewrite to `<proxy>/bili/<url>` like
|
|
549
|
-
spawn mode (a loopback proxy target is never proxied, so the MITM envs are
|
|
550
|
-
simply bypassed); already-routed `/bili/`-prefixed requests pass through
|
|
551
|
-
untouched except for header stamping. Known limitation: manual
|
|
552
|
-
`/compact` has no dsh-side event hook, so its boundary is left to the
|
|
553
|
-
kernel's natural ingest diff (auto-compaction is off, so this is rare).
|
|
554
|
-
|
|
555
|
-
### Kimi Code (Moonshot)
|
|
556
|
-
|
|
557
|
-
Three aligned modes: `bili kimi` (launcher, cert-MITM — Option 2), `/bili/`
|
|
558
|
-
URL prefix, and native plugin mode (`bili plugin install kimi`, #963). Kimi
|
|
559
|
-
Code v2's plugin system is declarative only (`kimi.plugin.json`: MCP servers,
|
|
560
|
-
hooks, skills — no in-process JS execution), so bili cannot patch the client's
|
|
561
|
-
fetch stack like it does for pi/opencode/dsh. Instead the plugin ships two
|
|
562
|
-
small node scripts that do the work around the client:
|
|
563
|
-
|
|
564
|
-
- **Install:** `bili plugin install kimi` writes
|
|
565
|
-
`$KIMI_CODE_HOME/plugins/managed/billion-context/kimi.plugin.json`
|
|
566
|
-
declaring a stdio MCP server (`node <root>/dist/kimi/native-mcp.js`) plus a
|
|
567
|
-
`SessionStart` hook (`node <root>/dist/kimi/bootstrap-hook.js`, 30 s
|
|
568
|
-
timeout), and registers the plugin in
|
|
569
|
-
`$KIMI_CODE_HOME/plugins/installed.json`. The installer requires
|
|
570
|
-
`kimi --version` ≥ 2.0.0 and refuses below that (the launcher still works
|
|
571
|
-
either way). Remove with `bili plugin remove kimi` (managed dir + registry
|
|
572
|
-
record + config restore).
|
|
573
|
-
- **Per-session bootstrap:** kimi spawns the MCP server as a direct child for
|
|
574
|
-
each session; at startup it attaches to a healthy proxy
|
|
575
|
-
(`BILLION_CONTEXT_PROXY`) or spawns its own on an ephemeral port, then
|
|
576
|
-
rewrites the client's routing with an idempotent, line-surgical managed
|
|
577
|
-
block in `~/.kimi-code/config.toml`: an own provider `[providers.bili]`
|
|
578
|
-
(`base_url = http://127.0.0.1:<port>/bili/<upstream>`, cloning the active
|
|
579
|
-
provider's `oauth` / `api_key` reference verbatim), a `[models.bili-kimi]`
|
|
580
|
-
alias, and a top-level `default_model` redirect with the previous value
|
|
581
|
-
recorded inside the block. The original file is snapshotted to
|
|
582
|
-
`config.toml.bili-bak` once; every write happens under a mkdir lockfile and
|
|
583
|
-
user content outside the block is never touched. Kimi's config hot-reload
|
|
584
|
-
applies the change to live sessions. The `SessionStart` hook runs the same
|
|
585
|
-
bootstrap opportunistically (attach-only — it never spawns); its
|
|
586
|
-
non-blocking race is tolerated by design: round 1 may ride direct/wire mode,
|
|
587
|
-
and the invariant is never pointing `base_url` at a dead port.
|
|
588
|
-
- **Plugin-mode stamping:** the block gains
|
|
589
|
-
`custom_headers = { x-bili-plugin = "kimi" }` ONLY after the ACP tool list
|
|
590
|
-
has been verified against the live proxy manifest — until then traffic rides
|
|
591
|
-
wire mode. Because `custom_headers` are static per provider they cannot
|
|
592
|
-
carry per-request window/model headers without going stale on model switch;
|
|
593
|
-
the runtime-info report therefore happens at bootstrap only (model + context
|
|
594
|
-
window + max output from the client's own config whenever present).
|
|
595
|
-
- **Watchdog & lifecycle:** the MCP child probes the proxy every 30 s. In
|
|
596
|
-
attach mode it waits forever (it never touches a user-owned proxy); in spawn
|
|
597
|
-
mode a dead proxy is respawned and the routing rewritten to the new origin.
|
|
598
|
-
If recovery fails, the managed block is removed so traffic degrades back to
|
|
599
|
-
direct upstream rather than hitting a dead port. When a session ends, kimi
|
|
600
|
-
kills the MCP child and the parent-pid watchdog tears down the spawned
|
|
601
|
-
proxy. Multiple concurrent TUIs share the first-spawned proxy; when it goes
|
|
602
|
-
away the remaining sessions respawn and re-route automatically.
|
|
603
|
-
- **Known limitations:** subagent conversations get their own derived proxy
|
|
604
|
-
sessions (kimi exposes no stable session id; tool calls bind via the
|
|
605
|
-
per-call `conversation_id` argument), and kimi's native auto-compaction is
|
|
606
|
-
NOT pushed out — ACP compression simply fires first, as in launcher mode.
|
|
607
|
-
Opt-out: `BILI_NATIVE_KIMI=0`.
|
|
608
|
-
|
|
609
|
-
### Hermes (Nous Research)
|
|
610
|
-
|
|
611
|
-
Three aligned modes: `bili hermes` (launcher, cert-MITM — Option 2), `/bili/`
|
|
612
|
-
URL prefix, and native plugin mode (`bili plugin install hermes`, #958). The
|
|
613
|
-
hermes CLI agent's plugin API is Python-only (the `desktop/plugin.js` SDK
|
|
614
|
-
belongs to the separate Desktop app), so the native plugin is a small
|
|
615
|
-
pure-stdlib Python module shipped inside the npm package:
|
|
616
|
-
|
|
617
|
-
- **Install:** `bili plugin install hermes` copies `plugin.yaml` +
|
|
618
|
-
`__init__.py` into `~/.hermes/plugins/billion-context/`, writes a
|
|
619
|
-
machine-owned `bili.json` sidecar pointing at the global bili install
|
|
620
|
-
(`dist/index.js` + node path), and enables the plugin through hermes' own
|
|
621
|
-
channel (`hermes plugins enable billion-context` — if the CLI isn't on PATH
|
|
622
|
-
the same command is printed instead). Start a new hermes session to
|
|
623
|
-
activate. Remove with `bili plugin remove hermes`; refresh with
|
|
624
|
-
`bili plugin update hermes` after a global update.
|
|
625
|
-
- **Lifecycle:** at load the plugin attaches to a healthy running proxy or
|
|
626
|
-
spawns its own on an ephemeral port (parent-pid watchdog tears it down when
|
|
627
|
-
hermes exits; concurrent starts arbitrate through the same starting-marker
|
|
628
|
-
protocol the launcher uses). Only once the proxy is verified healthy does it
|
|
629
|
-
point hermes' httpx stack at it via `HTTPS_PROXY` / `https_proxy` +
|
|
630
|
-
`SSL_CERT_FILE` (bili's combined CA bundle — current hermes resolves ambient
|
|
631
|
-
trust there; `HERMES_CA_BUNDLE` stays set for older builds) —
|
|
632
|
-
`~/.hermes/config.yaml` is never touched. Provider https hosts are read from hermes' config and whitelisted
|
|
633
|
-
for MITM; everything else blind-tunnels exactly like launcher mode. If no
|
|
634
|
-
proxy can be made healthy, the plugin stands down silently and traffic goes
|
|
635
|
-
direct (no compression, no dead port).
|
|
636
|
-
- **Plugin-mode stamping:** an `llm_request` middleware stamps
|
|
637
|
-
`x-bili-plugin: hermes` + conversation id (= the hermes session id, so
|
|
638
|
-
gateway multi-session stays safe) + model, and `x-bili-plugin-max-output`
|
|
639
|
-
once known — ONLY after the ACP tools are registered against the live proxy
|
|
640
|
-
manifest; round 1 rides wire mode. A `pre_api_request` hook captures the
|
|
641
|
-
effective `max_tokens` and pushes runtime-info (model + max output) to the
|
|
642
|
-
proxy. `compress` / `decompress` / `acp_status` are registered as real
|
|
643
|
-
hermes tools served by the proxy's existing plugin endpoints.
|
|
644
|
-
- **Known limitations:** requests going out hermes' Codex-wire transport may
|
|
645
|
-
drop the per-request header surface, so such setups stay in wire mode until
|
|
646
|
-
that transport exposes headers. Inert when `BILLION_CONTEXT_PROXY` is set
|
|
647
|
-
(the launcher owns the proxy) or `BILI_PROVIDER_REWRITES` is defined.
|
|
648
|
-
Opt-out: `BILI_NATIVE_HERMES=0`.
|
|
649
|
-
|
|
650
|
-
### ZCode (Z.ai / bigmodel coding plan)
|
|
651
|
-
|
|
652
|
-
Three aligned modes: `/bili/` URL prefix, cert-MITM through the GUI's
|
|
653
|
-
Settings → Network (HTTP proxy + root-CA path), and native plugin mode
|
|
654
|
-
(`bili plugin install zcode`, #1145). ZCode's extension surface is
|
|
655
|
-
Claude-Code-shaped but declarative: user-level hooks and stdio MCP servers in
|
|
656
|
-
`~/.zcode/cli/config.json`, no in-process JS seam. So the native lane ships
|
|
657
|
-
two small node scripts that do the work around the client:
|
|
658
|
-
|
|
659
|
-
- **Install:** `bili plugin install zcode` writes `~/.zcode/cli/config.json`:
|
|
660
|
-
sets `hooks.enabled = true`, appends a `SessionStart` process hook
|
|
661
|
-
(`node <root>/dist/zcode/bootstrap-hook.js`) and registers a stdio MCP
|
|
662
|
-
server `mcp.servers.bili` (`node <root>/dist/zcode/mcp-entry.js`). A
|
|
663
|
-
pre-existing user-owned `mcp.servers.bili` entry is never overwritten — the
|
|
664
|
-
installer refuses loudly instead. No URL is frozen at install time; routing
|
|
665
|
-
happens per session. Remove with `bili plugin remove zcode` (strips only
|
|
666
|
-
bili's entries, reverts `hooks.enabled` when it was the one to enable it,
|
|
667
|
-
and restores the provider store from its snapshot).
|
|
668
|
-
- **Per-session bootstrap:** each ZCode session spawns the MCP child as a
|
|
669
|
-
direct process; at startup it attaches to a healthy proxy
|
|
670
|
-
(`BILLION_CONTEXT_PROXY`) or spawns its own on an ephemeral port, then
|
|
671
|
-
rewrites the active provider store with idempotent JSON surgery under a
|
|
672
|
-
mkdir lockfile: the bigmodel coding-plan provider entries' `baseURL` becomes
|
|
673
|
-
`http://127.0.0.1:<port>/bili/<upstream>` (the builtin default upstream is
|
|
674
|
-
`https://open.bigmodel.cn/api/anthropic`; any custom baseURL you set is
|
|
675
|
-
preserved verbatim behind the wrapper). Both store generations are handled:
|
|
676
|
-
legacy `~/.zcode/v2/config.json` (`provider.<id>.options.baseURL`) and the
|
|
677
|
-
v3.14+ personal store `~/.zcode/v2/provider_config.json`
|
|
678
|
-
(`config.providerConfigRules.providerRules[].config.api.baseUrl`) — when
|
|
679
|
-
both exist, the new store wins. The original file is snapshotted to
|
|
680
|
-
`<file>.bili-bak` once per user edit (the snapshot always reflects your last
|
|
681
|
-
real state, never bili's own writes); every other key is preserved
|
|
682
|
-
byte-for-byte. Legacy-generation clients load provider config at startup —
|
|
683
|
-
restart ZCode once after installing; newer builds pick up routing changes
|
|
684
|
-
mid-session (~1 s polling). The `SessionStart` hook runs the same bootstrap
|
|
685
|
-
opportunistically (attach-only — it never spawns); its non-blocking race is
|
|
686
|
-
tolerated by design: round 1 may ride wire mode, and the invariant is never
|
|
687
|
-
pointing `baseURL` at a dead port.
|
|
688
|
-
- **Plugin-mode stamping:** once the MCP child verifies the ACP tool list
|
|
689
|
-
against the live proxy manifest, the routed entries gain
|
|
690
|
-
`headers["x-bili-plugin"] = "zcode"` — until then traffic rides wire mode.
|
|
691
|
-
Tool calls bind via the per-call `conversation_id` argument (#760).
|
|
692
|
-
- **Watchdog & lifecycle:** the MCP child probes the proxy every 30 s. In
|
|
693
|
-
attach mode it waits forever (it never touches a user-owned proxy); in spawn
|
|
694
|
-
mode a dead proxy is respawned and the routing rewritten to the new origin.
|
|
695
|
-
If recovery fails, the managed rewrite is removed so traffic degrades back
|
|
696
|
-
to direct upstream rather than hitting a dead port. When a session ends,
|
|
697
|
-
ZCode kills the MCP child and the parent-pid watchdog tears down the spawned
|
|
698
|
-
proxy. Concurrent sessions share the first-spawned proxy; when it goes away
|
|
699
|
-
the remaining sessions respawn and re-route automatically.
|
|
700
|
-
- **Known limitations:** ZCode's anti-fraud fingerprinting (#661) applies to
|
|
701
|
-
MITM-rebuilt bodies on `zcode.z.ai` login traffic — native mode does not
|
|
702
|
-
touch that surface (model traffic flows through the provider store, not the
|
|
703
|
-
GUI proxy); if you also run the GUI-proxy/MITM setup, keep the
|
|
704
|
-
`"mitm://zcode.z.ai": { "passthrough": true }` route. Inert when
|
|
705
|
-
`BILLION_CONTEXT_PROXY` is set (attach mode owns the proxy) or
|
|
706
|
-
`BILI_PROVIDER_REWRITES` is defined. Opt-out: `BILI_NATIVE_ZCODE=0`.
|
|
707
|
-
|
|
708
|
-
### Gemini family (Gemini CLI / iFlow CLI / Qwen Code)
|
|
709
|
-
|
|
710
|
-
Three launchers for the gemini-cli architecture family (#1043 tier 1). Two of
|
|
711
|
-
the three have a base-URL env hook; one doesn't:
|
|
712
|
-
|
|
713
|
-
- **`bili gemini`** — Gemini CLI (`@google/gemini-cli`). Sets
|
|
714
|
-
`GOOGLE_GEMINI_BASE_URL=<proxy>/bili/<upstream>` (default upstream
|
|
715
|
-
`https://generativelanguage.googleapis.com`; if you export your own
|
|
716
|
-
`GOOGLE_GEMINI_BASE_URL`, that value is relayed through the proxy instead).
|
|
717
|
-
The client switches to its `gateway` auth mode and sends Google-native-wire
|
|
718
|
-
requests straight to the loopback proxy — no MITM, no CA install, and
|
|
719
|
-
`~/.gemini` is never touched. The proxy speaks this wire natively (model
|
|
720
|
-
name rides in the URL path). Limitations: headless `-p` runs need a saved
|
|
721
|
-
auth selection (e.g. `security.auth.selectedType = "gemini-api-key"` in
|
|
722
|
-
settings + `GEMINI_API_KEY`) because gemini-cli rejects purely-env-derived
|
|
723
|
-
gateway auth in non-interactive mode; users on an OAuth personal login
|
|
724
|
-
(CodeAssist) are not covered by this route at all — that path ignores the
|
|
725
|
-
base-URL hook.
|
|
726
|
-
- **`bili iflow`** — iFlow CLI (`@iflow-ai/iflow-cli`). Same pattern via
|
|
727
|
-
`IFLOW_BASE_URL` (default `https://apis.iflow.cn/v1`, relayed when you set
|
|
728
|
-
it); OpenAI chat-completions wire.
|
|
729
|
-
- **`bili qwen`** — Qwen Code (`QwenLM/qwen-code`). This fork dropped the
|
|
730
|
-
base-URL hook (`DASHSCOPE_PROXY_BASE_URL` is a header-tuning knob, not
|
|
731
|
-
routing), but it honors standard proxy envs, so the launcher uses cert-MITM:
|
|
732
|
-
`HTTPS_PROXY=<proxy>` + `NODE_EXTRA_CA_CERTS=<bili CA>` with a static
|
|
733
|
-
whitelist of the default model hosts (DashScope / Qwen gateway / common
|
|
734
|
-
third-party endpoints). Custom relay hosts: add them with
|
|
735
|
-
`--mitm-domain <host>`. Best-effort route — a `BLIND TUNNEL WARNING` in the
|
|
736
|
-
log means a host is missing from the whitelist.
|
|
737
|
-
|
|
738
|
-
None of the three has a native mode: none exposes an in-loop tool injection
|
|
739
|
-
seam (gemini-cli extensions reach custom commands only; the forks inherit
|
|
740
|
-
that surface). Launcher-only by design.
|
|
741
|
-
|
|
742
|
-
### Client uses `http.proxy` (CONNECT) but nothing compresses
|
|
743
|
-
|
|
744
|
-
Some clients (VS Code-based IDEs: CodeBuddy, Cursor, Windsurf, …) only offer an HTTP **proxy** setting (`http.proxy`, `codingcopilot.httpProxyURL`, …) — no model base-URL to rewrite. Such clients send `CONNECT <model-host>:443` through the proxy instead of plain `/bili/…` requests. That path is only decrypted when the model host is on bili's **MITM whitelist**; otherwise bili blind-tunnels the TLS bytes (opaque relay) and can never see — or compress — the model requests (#897).
|
|
745
|
-
|
|
746
|
-
This failure mode is now loud instead of silent:
|
|
747
|
-
|
|
748
|
-
- a one-time `BLIND TUNNEL WARNING` per target host in the log, with the fix steps;
|
|
749
|
-
- `blindTunnels` (count + exact target hosts) in `curl -s http://localhost:8787/__bili/health` and `/__bili/stats` (loopback-only);
|
|
750
|
-
- an `UNDECRYPTED TRAFFIC (instance-level)` section in `acp_status` output while such tunnels exist.
|
|
751
|
-
|
|
752
|
-
To actually compress such a client: add its model domain to `"mitm".domains` in `billion-context.json` (e.g. `"mitm": { "domains": ["copilot.tencent.com"] }`) or via `BILI_MITM_DOMAINS`, restart bili, and make the client trust bili's root CA (`NODE_EXTRA_CA_CERTS=~/.local/share/billion-context/ca/root-ca.pem` for Node-based clients, or the client's own CA-path setting). The `/bili/` prefix trick does not apply here — there is no URL to change. Details: [CONFIGURATION.md → MITM](CONFIGURATION.md#mitm-transparent-proxy-login-clients).
|
|
753
|
-
|
|
754
|
-
### An unrecognized endpoint goes direct and nothing compresses (#1290)
|
|
755
|
-
|
|
756
|
-
bili only compresses requests whose path matches a known wire protocol (`/chat/completions`, `/llm_raw_chat`, `/v1/messages`, `/responses`, …). A request to any other path — e.g. a third-party plugin's **custom wire** such as Command Code's Go plan posting to `/alpha/generate` — is relayed byte-for-byte and **never compressed**. There is no config seam to declare an arbitrary new wire today; adding one is a separate feature, not a switch you can flip.
|
|
757
|
-
|
|
758
|
-
That outcome is now loud instead of silent (#1290):
|
|
759
|
-
|
|
760
|
-
- the client-side fetch hook logs each distinct unrouted endpoint once per process (`…is not a recognized model endpoint, so bili did not route it through the proxy…`);
|
|
761
|
-
- `unrecognizedPaths` (per-path counts) in `curl -s http://localhost:8787/__bili/stats` (loopback-only);
|
|
762
|
-
- an `UNRECOGNIZED PATHS (instance-level)` section in `acp_status` output while such requests exist.
|
|
763
|
-
|
|
764
|
-
If you expected compression at such an endpoint, use the provider's standard protocol endpoint instead (Command Code's Provider plan posts to `/provider/v1/chat/completions`, which bili does compress); a genuinely custom wire needs its own support.
|
|
765
|
-
|
|
766
|
-
## OpenCode
|
|
767
|
-
|
|
768
|
-
One bundled plugin serves **both** OpenCode generations: the agent file keeps
|
|
769
|
-
the V1 `server()` export alongside the V2 `setup()`, so hosts ≥ 1.18.29 load
|
|
770
|
-
the V1 shape and 2.x hosts load the V2 `setup()`. The standalone
|
|
771
|
-
[`opencode-acp`](https://github.com/ranxianglei/opencode-acp) extension is
|
|
772
|
-
V1-only and does **not** load under 2.x — for OpenCode 2.x, billion-context
|
|
773
|
-
is the recommended context manager. Everything below is verified end-to-end
|
|
774
|
-
on `@opencode/cli` 2.0.3 (V1 lane: 1.14.46 and 1.18.31).
|
|
775
|
-
|
|
776
|
-
| Path | Command | When |
|
|
777
|
-
|---|---|---|
|
|
778
|
-
| Launcher (easiest) | `bili opencode` | one command brings up proxy + client; real config untouched |
|
|
779
|
-
| Native (no launcher) | `bili plugin install opencode` | self-spawning plugin in your real config; start `opencode` as usual |
|
|
780
|
-
| Pure proxy (fallback) | baseURL `/bili/` prefix | no plugin — wire-level tool injection |
|
|
781
|
-
|
|
782
|
-
### Launcher — `bili opencode`
|
|
783
|
-
|
|
784
|
-
HTTPS rides cert-MITM, HTTP a temp `opencode.json` clone with `/bili/`
|
|
785
|
-
(JSONC comments accepted, merged the way opencode itself merges them;
|
|
786
|
-
relative local plugin specs re-anchored to absolute paths in the clone —
|
|
787
|
-
opencode resolves them against the declaring config file's dir, #826). Host
|
|
788
|
-
generation is detected with a `--version` probe (failed probe defaults to
|
|
789
|
-
1.x): on a **2.x** host the built-in V2 plugin (`dist/agent/opencode.js`) is
|
|
790
|
-
injected as a temp wrapper directory whose `index.js` re-exports the plugin
|
|
791
|
-
file (2.x rejects bare file paths in the config `plugin` array); **1.x**
|
|
792
|
-
hosts get the bare file path.
|
|
793
|
-
|
|
794
|
-
What the plugin does (both generations): registers the bili tools natively
|
|
795
|
-
in-host — compress / decompress / search_context / acp_status (+ absorb) —
|
|
796
|
-
and stamps the proxy headers on every outgoing provider request, including
|
|
797
|
-
context-window / max-output read from the host's own model catalog
|
|
798
|
-
(`ctx.catalog.model.list()`, refreshed every 60s) and reported to the proxy
|
|
799
|
-
as runtime-info (#955) — compression runs in plugin mode with **no**
|
|
800
|
-
wire-level tool injection. Native auto-compaction is disabled automatically
|
|
801
|
-
(`compaction.auto: false`). Every registration is defensive (optional
|
|
802
|
-
chaining): on any 2.x build where a seam is missing or never fires, the
|
|
803
|
-
plugin stays inert and the session transparently runs in plain proxy mode
|
|
804
|
-
instead of breaking — observed across adjacent `dev` builds whose API
|
|
805
|
-
surfaces differ from each other (#754 review probes).
|
|
806
|
-
|
|
807
|
-
1.x specifics (verified 1.14.46 + 1.18.31): the V1 `.server()` hooks rewrite
|
|
808
|
-
every provider `options.baseURL` to `<proxy>/bili/…` in-process and set
|
|
809
|
-
`compaction.auto: false`; `chat.headers` stamps the plugin headers per
|
|
810
|
-
request; `tool` registers the bili tools with real zod shapes (zod is a
|
|
811
|
-
runtime dependency — when it cannot be resolved the plugin degrades to
|
|
812
|
-
rewrite-only). Providers **without** an explicit `baseURL` (SDK defaults,
|
|
813
|
-
e.g. bare `@ai-sdk/openai` → api.openai.com) are caught by a global `fetch`
|
|
814
|
-
patch (log: `v1: fetch patch installed`) — idempotent, passes
|
|
815
|
-
`/bili/`-wrapped URLs through untouched; verified including the OpenAI
|
|
816
|
-
Responses endpoint.
|
|
817
|
-
|
|
818
|
-
### Native (no launcher) — `bili plugin install opencode`
|
|
819
|
-
|
|
820
|
-
Registers a self-spawning plugin in your real opencode config and sets
|
|
821
|
-
`compaction.auto: false`; afterwards plain `opencode` works as-is. No MCP
|
|
822
|
-
face is added by default (the native plugin already provides the bili tools,
|
|
823
|
-
session-bound); pass `--with-mcp` to add one — the entry then carries no
|
|
824
|
-
origin pin, so it survives the plugin's ephemeral-port proxy restarts (#926).
|
|
825
|
-
Entry form depends on how THIS bili was installed: an **npm install** writes
|
|
826
|
-
the bare package name (`"plugin": ["billion-context"]`) — the package
|
|
827
|
-
publishes `exports["./server"]` → `dist/agent/opencode-native.js`, so
|
|
828
|
-
opencode loads it through its own Npm.add machinery; zero absolute paths, portable. (That exact bare-name entry doubles as a hand-install without bili — see Option 1.) A **git checkout / dev build** falls back to a local shim dir
|
|
829
|
-
(`<configDir>/plugins/billion-context/index.js` → this checkout's
|
|
830
|
-
`dist/agent/opencode-native.js`) — machine-local by construction; re-running
|
|
831
|
-
install from an npm install migrates the entry back to the bare name.
|
|
832
|
-
|
|
833
|
-
At load the plugin bootstraps its own proxy (attaches to a healthy instance
|
|
834
|
-
instead of doubling; parent-pid watchdog kills it when opencode exits),
|
|
835
|
-
routes model-API traffic to `<proxy>/bili/<upstream-url>`, and exposes the
|
|
836
|
-
same native bili tools as launcher mode — no fixed port, no env var, no
|
|
837
|
-
launcher. Opt-out: `BILI_NATIVE_OPENCODE=0`. If no proxy can be made
|
|
838
|
-
healthy, requests go direct (uncompressed) with a one-time warning and
|
|
839
|
-
recover automatically. Under a `bili opencode` launch this entry is skipped
|
|
840
|
-
entirely (the launcher owns the proxy).
|
|
841
|
-
|
|
842
|
-
### Pure proxy (no plugin)
|
|
843
|
-
|
|
844
|
-
Point the provider baseURL at the proxy like any other client:
|
|
845
|
-
|
|
846
|
-
```json
|
|
847
|
-
{
|
|
848
|
-
"provider": {
|
|
849
|
-
"myprovider": {
|
|
850
|
-
"npm": "@ai-sdk/openai-compatible",
|
|
851
|
-
"options": {
|
|
852
|
-
"baseURL": "http://localhost:8787/bili/http://upstream.example/v1",
|
|
853
|
-
"apiKey": "sk-any"
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
```
|
|
859
|
-
|
|
860
|
-
Note: 2.0 AI-SDK providers require an `apiKey` field even for local
|
|
861
|
-
endpoints that never check it — set any non-empty value.
|
|
862
|
-
|
|
863
|
-
### Status: `/acp` and `acp_status`
|
|
864
|
-
|
|
865
|
-
The `/acp` panel is session-bound in all modes, and the `acp_status` tool is
|
|
866
|
-
its in-host equivalent everywhere. On 2.0.x stable, where the command editor
|
|
867
|
-
supports adding entries (`editor.add`), the V2 plugin additionally registers
|
|
868
|
-
an `/acp` slash command — rendered as a synthetic non-model message,
|
|
869
|
-
panel-first like the `acp_status` tool; on older shapes the registration
|
|
870
|
-
stays inert. Note `opencode run` mode dispatches no slash commands at all
|
|
871
|
-
(they pass through to the model) — use the TUI.
|
|
872
|
-
|
|
873
|
-
The same seam carries `/acp-cache` (#1146) — the human entry point to the
|
|
874
|
-
prompt-cache reconciliation report (identical output to the `acp_cache` tool):
|
|
875
|
-
pi/omp register it natively (`/acp-cache [full]` for the every-line listing);
|
|
876
|
-
opencode V1 renders it as an ignored message the proxy strips from model
|
|
877
|
-
context before it reaches the wire; opencode V2 as a synthetic message (report
|
|
878
|
-
visible up to ~8 KB); dsh (both lanes) shows the default summary ledger — dsh's
|
|
879
|
-
command API passes no arguments, so there is no `full`. Legacy opencode-acp
|
|
880
|
-
sessions (#920) get an explicit unavailable notice instead (their traffic
|
|
881
|
-
bypasses this proxy's compression state). Claude Code has no in-process command
|
|
882
|
-
API: `bili plugin install claude` writes a model-mediated
|
|
883
|
-
`commands/acp-cache.md` markdown command whose prompt drives the `acp_cache`
|
|
884
|
-
MCP tool and pastes the report back verbatim. codex/kimi/hermes expose no
|
|
885
|
-
user-typable command seam — ask the model to call its `acp_cache` tool directly.
|
|
886
|
-
Per-fold P&L verdicts are priced by the optional `compress.priceProfile`
|
|
887
|
-
(normalized multipliers over the input-token unit); when no level sets it, the
|
|
888
|
-
request model's models.dev price row applies in absolute $/Mtok (kernel ratio
|
|
889
|
-
defaults only for unresolvable models) — breakeven/PAID BACK therefore reflect
|
|
890
|
-
your upstream's actual economics out of the box; override per provider for
|
|
891
|
-
relays with custom markup (CONFIGURATION.md, #1279).
|
|
892
|
-
|
|
893
|
-
The same seam carries `/acp-rule` (#1251/#1399) — the human entry point to
|
|
894
|
-
the persistent-rules feature (identical output to the `acp_rule` tool):
|
|
895
|
-
pi/omp register it natively with the tool's full operation set — bare
|
|
896
|
-
`/acp-rule` lists every recorded rule, `/acp-rule <text>` records one directly
|
|
897
|
-
(as if the model had called it), `/acp-rule remove <id>` deletes one, and bare
|
|
898
|
-
`/acp-rule clear` wipes all recorded rules (`clear <text>` records instead of
|
|
899
|
-
wiping — a typo must not destroy every rule). The wrapped transcript message
|
|
900
|
-
is stripped from model context by content signature like the cache report —
|
|
901
|
-
recorded rules reach the model every turn via the system-prompt injection
|
|
902
|
-
anyway.
|
|
903
|
-
|
|
904
|
-
### Legacy opencode-acp sessions (#920)
|
|
905
|
-
|
|
906
|
-
On 1.x hosts, pre-migration [`opencode-acp`](https://github.com/ranxianglei/opencode-acp)
|
|
907
|
-
sessions keep working under both lanes: the launcher strips the
|
|
908
|
-
`opencode-acp` entry from its temp config clone (the host never loads it
|
|
909
|
-
armed), and each lane absorbs the installed package (imported directly from
|
|
910
|
-
`node_modules` — `.opencode/node_modules`, project `node_modules`, global npm
|
|
911
|
-
root, or opencode's config-scope modules, first hit wins). A session is
|
|
912
|
-
legacy iff opencode-acp's persisted state file exists
|
|
913
|
-
(`<XDG_DATA_HOME>/opencode/storage/plugin/acp/<sessionID>.json`, or the dir
|
|
914
|
-
from `storagePath` in `acp.jsonc`):
|
|
915
|
-
|
|
916
|
-
- **Legacy session** — compression runs through the absorbed opencode-acp
|
|
917
|
-
(its own refs and block store keep working: `compress` / `decompress` /
|
|
918
|
-
`search_context` / `acp_status` / `acp_context_recap` all execute in it).
|
|
919
|
-
Its model requests carry `x-bili-plugin-bypass: 1`; the proxy forwards
|
|
920
|
-
them VERBATIM — no wire injection, no nudge, no session binding.
|
|
921
|
-
- **New session** — bili owns it: tool calls forward to the proxy's plugin
|
|
922
|
-
endpoints (plugin mode). The executor routes by session lane, so a new
|
|
923
|
-
session's `compress` reaches the proxy while a legacy session's reaches
|
|
924
|
-
opencode-acp. `acp_context_recap` has no proxy counterpart — on new
|
|
925
|
-
sessions the proxy answers with its unknown-tool message.
|
|
926
|
-
|
|
927
|
-
`/acp` and `/dcp` route the same way. Adoption of new sessions into
|
|
928
|
-
opencode-acp's registry is prevented by gating its transforms (system /
|
|
929
|
-
messages / text.complete) on the legacy predicate. Degradation: when the
|
|
930
|
-
package is absent or fails to import (or isn't v1), bili runs alone and
|
|
931
|
-
legacy sessions behave as read-only archives (old tags render, `decompress`
|
|
932
|
-
returns `[Block … not found]`, new refs restart from m00001).
|
|
933
|
-
|
|
934
|
-
### Caveats
|
|
935
|
-
|
|
936
|
-
- The 2.x line publishes as npm package `@opencode/cli`, and its plugin API
|
|
937
|
-
surface is still moving between builds (adjacent `dev`-channel builds
|
|
938
|
-
expose different `ctx` shapes) — the hook/tool details above are
|
|
939
|
-
version-specific observations, not a stable contract.
|
|
940
|
-
- Design note: the V2 plugin is a thin protocol client (no acp-kernel
|
|
941
|
-
inside) because the proxy stays the single compression authority — that
|
|
942
|
-
eliminates kernel-version drift between agent and proxy; it does not rely
|
|
943
|
-
on the plugin API being unable to mutate context (that capability varies
|
|
944
|
-
by 2.x build).
|
|
272
|
+
### Client deep dives
|
|
273
|
+
|
|
274
|
+
Everything that doesn't fit in one Quickstart line — how each client's lanes attach, what gets written where, and known limitations — lives in **[CLIENTS.md](CLIENTS.md)**: dsh · Kimi Code · Hermes · ZCode · Gemini family (Gemini CLI / iFlow CLI / Qwen Code) · cert-MITM clients that never compress (CONNECT blind tunnels, #897) · unrecognized endpoints going direct (#1290) · OpenCode (launcher / native / pure proxy, `/acp` status & rules, legacy opencode-acp sessions #920).
|
|
275
|
+
|
|
276
|
+
## FAQ
|
|
277
|
+
|
|
278
|
+
**How do I check my cache hit rate?** Don't dig through logs — `/acp-cache`
|
|
279
|
+
prints a **text summary report right in the client**, headed by a clickable
|
|
280
|
+
**Web UI link**: open it for the web version of the session page — the cache
|
|
281
|
+
hit-rate **line chart** plus per-break **attribution**:
|
|
282
|
+
|
|
283
|
+

|
|
284
|
+
The report has four
|
|
285
|
+
blocks that pin things down at a glance: **GRAND LEDGER** (totals + hit% with
|
|
286
|
+
an explicit `HEALTHY` verdict; misses decomposed into new content /
|
|
287
|
+
compress re-pay / upstream-ttl-or-client-rewrite) · **FOLD ECONOMICS** (per-fold
|
|
288
|
+
economics: net tokens saved, paid-back verdicts) · **LINE ITEMS** (anomalies
|
|
289
|
+
only: hit<85% or miss≥5000). Rule of thumb: **compression itself costs ≤2%** —
|
|
290
|
+
a healthy session sits at **95–97%**. When you see less, the attribution tells
|
|
291
|
+
you which of the usual suspects it was, in this order: ① upstream cache TTL
|
|
292
|
+
expiry (shows up as stable-prefix misses — the top-spikes line names idle
|
|
293
|
+
times), ② a model switch, ③ a bili bug (report it with the page attached),
|
|
294
|
+
④ other/unknown. `/acp-cache [full]` lists every fold & line; same report over
|
|
295
|
+
HTTP: `GET /__bili/cache-report`; the raw per-request `[acp-usage]` lines still
|
|
296
|
+
land in the log file for deep dives.
|
|
297
|
+
|
|
298
|
+
**What does `/acp` show?** In clients with the native plugin (opencode, dsh),
|
|
299
|
+
`/acp` renders the ACP status panel of the current conversation straight from
|
|
300
|
+
the proxy (session, blocks, compressible ranges, usage); before the first model
|
|
301
|
+
request it shows an idle notice instead. `/acp-cache [full]` prints the cache
|
|
302
|
+
report above.
|
|
303
|
+
|
|
304
|
+
**Can I query sessions and config over the web?** Yes — open
|
|
305
|
+
[http://localhost:8787](http://localhost:8787): an overview dashboard, session
|
|
306
|
+
list with per-session detail, live logs, a config editor, and an upstream
|
|
307
|
+
connectivity test. Everything is also plain JSON for scripting
|
|
308
|
+
(`/__bili/stats`, `/__bili/sessions`, `/__bili/config`, …).
|
|
309
|
+
|
|
310
|
+
**When does compression happen?** It is model-driven: the injected context
|
|
311
|
+
tools are called by the model as context grows, gentle growth nudges
|
|
312
|
+
(~50K-token steps by design, adjustable via `compress.nudgeGrowthTokens`)
|
|
313
|
+
prompt it along the way, and preflight fires as a hard backstop when the input
|
|
314
|
+
alone exceeds the window (#470). Watch it live with `/acp` or the web UI.
|
|
315
|
+
|
|
316
|
+
**Is bili transparent? How do I turn it off?** Unrecognized endpoints forward
|
|
317
|
+
unchanged ([CLIENTS.md](CLIENTS.md)), and every mode reverses cleanly:
|
|
318
|
+
`bili plugin remove <client>` for native installs, stop using the launcher
|
|
319
|
+
command / env vars / `/bili/` prefix for the other two — traffic goes direct
|
|
320
|
+
again immediately.
|
|
321
|
+
|
|
322
|
+
**Where are logs and session data stored?** Log:
|
|
323
|
+
`~/.local/state/billion-context/bili.log` (also mirrored to stderr); session
|
|
324
|
+
state: `~/.local/share/billion-context/` (XDG-overridable; Windows AV exclusion
|
|
325
|
+
#362, opt-in cleanup #1082) — full paths in [CONFIGURATION.md](CONFIGURATION.md).
|
|
945
326
|
|
|
946
327
|
## Running the proxy
|
|
947
328
|
|
|
@@ -962,94 +343,25 @@ Flags override env vars and the config file. `bili --help` lists them all.
|
|
|
962
343
|
|
|
963
344
|
### Remote agents (`--host`)
|
|
964
345
|
|
|
965
|
-
By default the proxy binds `127.0.0.1` and only accepts loopback
|
|
966
|
-
connections. To serve agents on other machines, bind a non-loopback host:
|
|
346
|
+
By default the proxy binds `127.0.0.1` and only accepts loopback connections. To serve agents on other machines: `bili --host 0.0.0.0` (or your LAN IP); remote agents point their model `baseURL` at `http://<this-host>:<port>/bili/…`.
|
|
967
347
|
|
|
968
|
-
|
|
969
|
-
bili
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
- Remote agents point their model `baseURL` at `http://<this-host>:<port>/bili/…`.
|
|
973
|
-
- MITM-mode `CONNECT` then also accepts remote clients — for **whitelisted
|
|
974
|
-
model hosts only**. Blind tunnels to arbitrary hosts stay loopback-only, so
|
|
975
|
-
the proxy can never be used as an open relay.
|
|
976
|
-
- The `/bili/<absolute-url>` tunnel has destination admission (#409): the
|
|
977
|
-
proxy itself and link-local/metadata addresses are **always denied**;
|
|
978
|
-
loopback/private destinations are allowed for local clients (self-hosted
|
|
979
|
-
upstreams) and **denied for remote clients** unless listed in
|
|
980
|
-
`BILI_TUNNEL_ALLOWED_HOSTS` (`host` or `host:port`, comma-separated) — a
|
|
981
|
-
remote peer must not use the proxy as an SSRF pivot into your LAN, and the
|
|
982
|
-
management plane is unreachable through the tunnel even via NAT hairpin
|
|
983
|
-
(tunneled requests carry an internal `x-bili-tunnel` marker that `/__bili/`
|
|
984
|
-
rejects). One exception (#1073): a **local** client relaying a management
|
|
985
|
-
path (`/__bili/*`, `/__acp/*`) to a **loopback IP-literal** destination is
|
|
986
|
-
forwarded unmarked — any same-machine process can already reach that port
|
|
987
|
-
directly, so the marker would add no protection while breaking legitimate
|
|
988
|
-
inter-instance health probes. Remote peers and hostname destinations keep
|
|
989
|
-
the marker unconditionally. An absolute-form request addressed to the
|
|
990
|
-
instance's **own** endpoint on a management path is served locally instead
|
|
991
|
-
of tunneled (a forward-proxy-style health probe gets a real answer).
|
|
992
|
-
- There is **no authentication**: only do this on a trusted LAN or behind a
|
|
993
|
-
firewall. The `/__bili/` management endpoints remain loopback-only.
|
|
994
|
-
- A startup `[security]` warning reminds you of the above.
|
|
348
|
+
- MITM-mode `CONNECT` then also accepts remote clients — for **whitelisted model hosts only**. Blind tunnels to arbitrary hosts stay loopback-only, so the proxy can never be used as an open relay.
|
|
349
|
+
- `/bili/<absolute-url>` destination admission (#409): the proxy itself and link-local/metadata addresses are always denied; loopback/private destinations are allowed for local clients (self-hosted upstreams) and denied for remote clients unless listed in `BILI_TUNNEL_ALLOWED_HOSTS` (`host` or `host:port`, comma-separated). One exception (#1073): a **local** client relaying a management path (`/__bili/*`, `/__acp/*`) to a **loopback IP-literal** destination is forwarded unmarked; remote peers and hostname destinations keep the internal tunnel marker unconditionally, and management paths stay unreachable through the tunnel even via NAT hairpin. An absolute-form request addressed to the instance's own endpoint on a management path is served locally instead of tunneled (a forward-proxy-style health probe gets a real answer).
|
|
350
|
+
- There is **no authentication**: only do this on a trusted LAN or behind a firewall. The `/__bili/` management endpoints remain loopback-only. A startup `[security]` warning reminds you of the above.
|
|
995
351
|
|
|
996
352
|
### Debugging
|
|
997
353
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
1. **CLI flag** (quickest): `bili --debug`
|
|
1001
|
-
2. **Env var**: `ACP_DEBUG=1 bili`
|
|
1002
|
-
3. **Config file**: `"debug": true` in `billion-context.json`
|
|
1003
|
-
|
|
1004
|
-
Verbose mode logs every `processTurn` (tag counts, token usage), the nudge
|
|
1005
|
-
decision (growth/usage/pendingT1/shouldInject), client headers, and SSE
|
|
1006
|
-
rewrites.
|
|
354
|
+
`bili --debug` (or env `ACP_DEBUG=1`, or `"debug": true` in config — flag > env > config) logs every `processTurn` (tag counts, token usage), the nudge decision (growth/usage/pendingT1/shouldInject), client headers, and SSE rewrites.
|
|
1007
355
|
|
|
1008
356
|
### Log file
|
|
1009
357
|
|
|
1010
|
-
All logs
|
|
1011
|
-
(XDG state dir). They also still print to stderr so a foreground `bili start`
|
|
1012
|
-
shows them in the terminal.
|
|
1013
|
-
|
|
1014
|
-
```bash
|
|
1015
|
-
# Config: "logFile": "/custom/path.log"
|
|
1016
|
-
# Env: ACP_LOG_FILE=/custom/path.log (or ACP_LOG_FILE=off to disable the file)
|
|
1017
|
-
```
|
|
1018
|
-
|
|
1019
|
-
The file auto-rotates at 10 MB (renamed to `bili.log.old`). Cache-hit stats
|
|
1020
|
-
per request are logged as `[acp-usage] round N input=X cached=Y (cache hit Z%)`
|
|
1021
|
-
so you can measure prefix-cache health directly from the log.
|
|
358
|
+
All logs tee to `~/.local/state/billion-context/bili.log` by default (XDG state dir) and still print to stderr. Override with `"logFile"` in config or `ACP_LOG_FILE` (`off` disables the file). Auto-rotates at 10 MB (`bili.log.old`). Per-request cache-hit stats log as `[acp-usage] round N input=X cached=Y (cache hit Z%)` so you can measure prefix-cache health directly from the log.
|
|
1022
359
|
|
|
1023
360
|
### Self-update
|
|
1024
361
|
|
|
1025
|
-
The proxy checks npm
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
While the running process is behind the on-disk install ("stale"), the state is
|
|
1030
|
-
visible without digging through logs:
|
|
1031
|
-
|
|
1032
|
-
- The web UI (`/__bili/`) shows a banner on the overview page: which version is
|
|
1033
|
-
running vs installed, and whether auto-restart is enabled.
|
|
1034
|
-
- `GET /__bili/status` returns `{version, diskVersion, stale,
|
|
1035
|
-
autoRestartOnUpdate, inFlight}` for scripting.
|
|
1036
|
-
- A one-time `[update] … restart bili to activate` warning per version pair
|
|
1037
|
-
stays in the log.
|
|
1038
|
-
|
|
1039
|
-
**Opt-in self-restart.** With `--auto-restart-on-update` (or env
|
|
1040
|
-
`ACP_AUTO_RESTART_ON_UPDATE=1`, or `"autoRestartOnUpdate": true` in the config
|
|
1041
|
-
file — default OFF) the proxy re-execs itself instead of waiting for a human:
|
|
1042
|
-
when the on-disk version is newer and there are **zero in-flight requests**, it
|
|
1043
|
-
verifies the new install, stops accepting connections, drains, spawns a
|
|
1044
|
-
replacement process on the same port, waits until it accepts connections, then
|
|
1045
|
-
exits. Clients reconnect to the same port automatically; session state survives
|
|
1046
|
-
(persisted on disk). Safety gates: zero in-flight at decision time *and*
|
|
1047
|
-
through the drain window; an install sanity check before re-exec; a 10-minute
|
|
1048
|
-
cooldown marker so a flapping version can never loop-restart. Any failure
|
|
1049
|
-
resumes the original listener and falls back to the plain reminder.
|
|
1050
|
-
|
|
1051
|
-
Disable permanently via config (`"autoUpdate": false`) or env
|
|
1052
|
-
(`ACP_AUTO_UPDATE=0`).
|
|
362
|
+
The proxy checks npm on startup and every 3 minutes; a newer version is installed in place and a notice is logged — **restart `bili` to pick it up**, unless you enable opt-in self-restart (`--auto-restart-on-update` flag, env `ACP_AUTO_RESTART_ON_UPDATE=1`, or `"autoRestartOnUpdate": true` in config — default OFF): with zero in-flight requests it verifies the new install, stops accepting connections, drains, spawns a replacement on the same port, and exits once it accepts connections (clients reconnect automatically; session state survives on disk). Safety gates: zero in-flight through the drain window, an install sanity check before re-exec, and a 10-minute cooldown marker so a flapping version can never loop-restart; any failure resumes the original listener and falls back to the plain reminder.
|
|
363
|
+
|
|
364
|
+
While the running process is behind the on-disk install ("stale"), the web UI shows a banner (running vs installed version, auto-restart state) and `GET /__bili/status` returns `{version, diskVersion, stale, autoRestartOnUpdate, inFlight}` for scripting. Disable permanently via `"autoUpdate": false` in config or `ACP_AUTO_UPDATE=0`.
|
|
1053
365
|
|
|
1054
366
|
## Configuration
|
|
1055
367
|
|
|
@@ -1057,114 +369,14 @@ The full configuration reference — config file location, top-level keys,
|
|
|
1057
369
|
providers, compression tuning, environment variables — lives in
|
|
1058
370
|
**[CONFIGURATION.md](CONFIGURATION.md)**.
|
|
1059
371
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
If the proxy's own outbound connections to a model provider are blocked
|
|
1063
|
-
(e.g. `api.openai.com` from inside the GFW), configure an **upstream proxy**
|
|
1064
|
-
(the local v2rayA / clash HTTP port) so the proxy reaches the provider:
|
|
1065
|
-
|
|
1066
|
-
```jsonc
|
|
1067
|
-
{
|
|
1068
|
-
// Global default: ALL providers route through this proxy
|
|
1069
|
-
"proxy": "http://127.0.0.1:20172",
|
|
1070
|
-
"providers": {
|
|
1071
|
-
"https://api.openai.com/v1": {
|
|
1072
|
-
// Per-URL overrides global (use a different proxy for this host)
|
|
1073
|
-
"proxy": "http://127.0.0.1:20173",
|
|
1074
|
-
"models": { "gpt-5": { "context": 400000 } }
|
|
1075
|
-
},
|
|
1076
|
-
"https://open.bigmodel.cn/api/anthropic": {
|
|
1077
|
-
// Empty string = explicitly DIRECT, overriding the global proxy
|
|
1078
|
-
"proxy": "",
|
|
1079
|
-
"models": { "glm-5.2": { "context": 1000000 } }
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
```
|
|
1084
|
-
|
|
1085
|
-
Rules:
|
|
1086
|
-
- **Per-URL `proxy`** has the highest priority for its matching provider URL.
|
|
1087
|
-
- Remaining priority is `BILI_UPSTREAM_PROXY` → Web UI manual proxy → top-level
|
|
1088
|
-
`proxy` → `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` → Windows system proxy
|
|
1089
|
-
→ direct.
|
|
1090
|
-
- Empty string `""` means **explicitly direct** (override-and-disable).
|
|
1091
|
-
- Auto mode honors `NO_PROXY` and the Windows proxy bypass list for
|
|
1092
|
-
environment/system fallbacks. A proxy pointing back to bili's own local port
|
|
1093
|
-
is ignored or rejected to prevent a loop.
|
|
1094
|
-
- HTTP and HTTPS proxy origins are supported. SOCKS5 (`socks5`/`socks5h`) is
|
|
1095
|
-
not supported: an explicit `BILI_UPSTREAM_PROXY` / config `proxy` with such
|
|
1096
|
-
a scheme fails startup with an actionable error, while env/system proxies
|
|
1097
|
-
(`HTTPS_PROXY`, …) with such a scheme are ignored with a one-time warning
|
|
1098
|
-
(traffic then falls through to direct). For Clash/mihomo, point bili at the
|
|
1099
|
-
same mixed port over `http://` (e.g. `http://127.0.0.1:7890`).
|
|
1100
|
-
- Both outbound paths are covered: `/bili/` path-mode (fetch) AND MITM CONNECT
|
|
1101
|
-
tunnels (the proxy's connection to the real upstream goes through the HTTP
|
|
1102
|
-
CONNECT proxy).
|
|
1103
|
-
- The auto-updater's own egress (npm registry check + tarball download) uses
|
|
1104
|
-
the same decision for its hosts, so `bili update` and auto-update work on
|
|
1105
|
-
hosts where npm is only reachable through the proxy (#609).
|
|
1106
|
-
|
|
1107
|
-
Env override: `BILI_UPSTREAM_PROXY=http://127.0.0.1:20172` (higher priority than
|
|
1108
|
-
the config file). On Windows, common Clash/Mihomo static system proxies are
|
|
1109
|
-
discovered automatically; the Web UI shows the effective source and any PAC
|
|
1110
|
-
URL detected in Internet Settings.
|
|
1111
|
-
|
|
1112
|
-
**MITM vs `/bili/` — distinguishing the key scheme.** A login client
|
|
1113
|
-
(ZCode via MITM) and an API-key client can both hit the same host
|
|
1114
|
-
(`open.bigmodel.cn`). To let their config differ, MITM traffic uses a
|
|
1115
|
-
`mitm://` scheme in the lookup key while `/bili/` traffic uses the real
|
|
1116
|
-
`https://`:
|
|
1117
|
-
|
|
1118
|
-
| Client | Lookup key example |
|
|
1119
|
-
|---|---|
|
|
1120
|
-
| ZCode (MITM, login) | `mitm://open.bigmodel.cn` |
|
|
1121
|
-
| API-key client (`/bili/`) | `https://open.bigmodel.cn/api/anthropic` |
|
|
1122
|
-
|
|
1123
|
-
So you can give ZCode its own proxy without affecting API-key clients:
|
|
1124
|
-
```jsonc
|
|
1125
|
-
{
|
|
1126
|
-
"providers": {
|
|
1127
|
-
"mitm://open.bigmodel.cn": { "proxy": "http://127.0.0.1:20173" },
|
|
1128
|
-
"https://open.bigmodel.cn/api/anthropic": { "proxy": "http://127.0.0.1:20172" }
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
```
|
|
372
|
+
Two knobs people look for first:
|
|
1132
373
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
Some upstreams reject the `developer` role newer codex clients send on the
|
|
1136
|
-
Responses API (`400 Invalid role: developer`). `compat.roles` maps roles to
|
|
1137
|
-
what the upstream accepts — applied at the forward boundary to the final
|
|
1138
|
-
`openai`/`responses` body (client-sent roles **and** bili's own injected
|
|
1139
|
-
prompt alike), global or per-provider, default off = byte-for-byte:
|
|
1140
|
-
|
|
1141
|
-
```jsonc
|
|
1142
|
-
{
|
|
1143
|
-
"compat": { "roles": { "developer": "system" } },
|
|
1144
|
-
"providers": {
|
|
1145
|
-
"https://picky.example.com": { "compat": { "roles": { "developer": "user" } } }
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
|
-
```
|
|
1149
|
-
|
|
1150
|
-
**No configuration needed for the common case.** When an upstream answers a
|
|
1151
|
-
request with `400 Invalid role: …`, bili auto-rewrites the offending role to
|
|
1152
|
-
`system`, retries the request once, and — if the retry succeeds — remembers
|
|
1153
|
-
the mapping **for that session only** (nothing is written to your config).
|
|
1154
|
-
Later requests in the session skip the 400 round-trip. The log line printed
|
|
1155
|
-
when the auto-fix fires includes a copy-paste per-provider snippet if you
|
|
1156
|
-
want the mapping permanently.
|
|
374
|
+
- **Upstream proxy (firewall/GFW)** — routing the proxy's own outbound traffic through v2rayA/clash: full resolution order, empty-string = explicit direct, SOCKS5 rejection, both egress paths, and the `mitm://` vs `https://` key schemes live in [CONFIGURATION.md](CONFIGURATION.md) (Server Settings → `proxy`; Providers → key schemes).
|
|
375
|
+
- **Wire-compat role rewrite (`compat.roles`)** — an upstream that rejects the `developer` role? Covered by [CONFIGURATION.md](CONFIGURATION.md) (Server Settings → `compat`) — including the learn-on-failure auto-fix that needs no configuration at all.
|
|
1157
376
|
|
|
1158
377
|
## How sessions work
|
|
1159
378
|
|
|
1160
|
-
The proxy
|
|
1161
|
-
state across concurrent users/accounts. It uses **the conversation value the
|
|
1162
|
-
client itself provides, verbatim** (`src/session-id.ts`) — no hashing and no
|
|
1163
|
-
protocol/upstream/API-key dimensions. Those dimensions were mutable mid-
|
|
1164
|
-
conversation (credentials rotate, users switch relays, the wire protocol can
|
|
1165
|
-
change), so keying on them orphaned state exactly when the user kept talking
|
|
1166
|
-
(#280, #286). The id is used only inside the proxy (state store, persistence,
|
|
1167
|
-
UI label); it is never sent upstream.
|
|
379
|
+
The proxy keys compression state on **the conversation value the client itself provides, verbatim** (`src/session-id.ts`) — no hashing, no protocol/upstream/API-key dimensions (those mutate mid-conversation and orphaned state exactly when users kept talking, #280/#286). The id stays inside the proxy (state store, persistence, UI label); it is never sent upstream.
|
|
1168
380
|
|
|
1169
381
|
Where the value comes from, first hit wins: the plugin's
|
|
1170
382
|
`x-bili-plugin-conversation` (honored only alongside the `x-bili-plugin`
|
|
@@ -1184,22 +396,7 @@ Responses/OpenAI/Anthropic wires.
|
|
|
1184
396
|
| **omp** (via plugin) | ✅ yes | `prompt_cache_key` promoted over any fingerprint (#268) |
|
|
1185
397
|
| **pi** (bare) | ❌ no | nothing → anonymous prefix affinity below |
|
|
1186
398
|
|
|
1187
|
-
**Header-less clients (pi-like): anonymous prefix affinity.**
|
|
1188
|
-
sends no conversation signal at all, the proxy resolves the session from the
|
|
1189
|
-
replayed history itself (`src/prefix-affinity.ts`, #309): an incoming request
|
|
1190
|
-
reattaches to a stored session only when its history reproduces that
|
|
1191
|
-
session's message chain byte-exactly from position 0; otherwise it gets its
|
|
1192
|
-
own deterministic `pfa-…` session. Consequences for the failure modes this
|
|
1193
|
-
section used to warn about (#1262):
|
|
1194
|
-
|
|
1195
|
-
- A **resumed** conversation reattaches to its own session — including after
|
|
1196
|
-
a proxy restart (#499).
|
|
1197
|
-
- A **new task with an identical opener does NOT inherit** another
|
|
1198
|
-
conversation's blocks or protected zone: it mints a fresh session, and once
|
|
1199
|
-
its history diverges it is fully separate (fork lineage is recorded for
|
|
1200
|
-
debugging).
|
|
1201
|
-
- A request with no usable signal at all is rejected with an explicit 400
|
|
1202
|
-
instead of silently colliding with something else's state.
|
|
399
|
+
**Header-less clients (pi-like): anonymous prefix affinity.** With no conversation signal at all, the proxy resolves the session from the replayed history itself (`src/prefix-affinity.ts`, #309): a request reattaches to a stored session only when its history reproduces that session's message chain byte-exactly from position 0; otherwise it gets its own deterministic `pfa-…` session. Consequences (#1262): a **resumed** conversation reattaches to its own session (even after a proxy restart, #499); a **new task with an identical opener does NOT inherit** another conversation's blocks — it mints a fresh, fully separate session; a request with no usable signal at all gets an explicit 400 instead of silently colliding.
|
|
1203
400
|
|
|
1204
401
|
Design record and threat model: [SESSION-IDENTITY.md](SESSION-IDENTITY.md). The
|
|
1205
402
|
message-granularity sibling (why identity is content-derived, not an
|
|
@@ -1209,87 +406,19 @@ For upstream sticky-routing, the proxy forwards only identity values the
|
|
|
1209
406
|
client already supplied (e.g. a body `session_id` is forwarded upstream as
|
|
1210
407
|
`x-session-id`); it never synthesizes one itself.
|
|
1211
408
|
|
|
1212
|
-
**Recommendation:** clients
|
|
1213
|
-
concurrent conversations through the proxy. For header-less multi-agent use,
|
|
1214
|
-
prefer the client plugin (the omp/pi plugins stamp a stable id per
|
|
1215
|
-
conversation); otherwise pass an explicit `x-acp-session` header per
|
|
1216
|
-
conversation. Even without either, prefix affinity keeps distinct tasks apart
|
|
1217
|
-
— the cost of a diverged fork is one raw resend plus a compression-ladder
|
|
1218
|
-
restart.
|
|
409
|
+
**Recommendation:** explicit-id clients are safe to run concurrently. For header-less multi-agent use, prefer the client plugin (stamps a stable id per conversation) or pass an explicit `x-acp-session` header — otherwise prefix affinity still keeps distinct tasks apart (a diverged fork costs one raw resend plus a compression-ladder restart).
|
|
1219
410
|
|
|
1220
411
|
### Derived (child) sessions inherit the parent's compressed context (#1333, #1362)
|
|
1221
412
|
|
|
1222
|
-
|
|
1223
|
-
empty history instead of resending the parent's conversation — that child
|
|
1224
|
-
could not previously `decompress` or `search_context` content that was folded
|
|
1225
|
-
away in the parent. Each lane now reports the lineage at birth: its identity
|
|
1226
|
-
registration carries the parent's conversation id (`parentConversationId`),
|
|
1227
|
-
and the proxy records a read-only link (`derivedFrom`) on the child session.
|
|
1228
|
-
From then on:
|
|
1229
|
-
|
|
1230
|
-
- `decompress` / `search_context` fall back along the parent chain for
|
|
1231
|
-
content the child never saw itself (resident or on-disk parents,
|
|
1232
|
-
cycle-guarded, depth cap 8);
|
|
1233
|
-
- nothing is copied into the child's state and the parent is never modified —
|
|
1234
|
-
fallback hits are read-only, so a child can never clobber what the parent
|
|
1235
|
-
still owns;
|
|
1236
|
-
- if the parent is unknown to the proxy when the link is recorded, the child
|
|
1237
|
-
simply starts fresh.
|
|
1238
|
-
|
|
1239
|
-
| Lane | Parent signal |
|
|
1240
|
-
|---|---|
|
|
1241
|
-
| **pi** RLM inline spawn | `parentSession` in the session header (path to the parent session file, resolved to its session id) |
|
|
1242
|
-
| **omp** fork / newSession | `parentSession` in the session header (bare session id or file path — both accepted) |
|
|
1243
|
-
| **OpenCode V1** (native plugin) | SDK session info `parentID` (resolved once per session, cached) |
|
|
1244
|
-
| **OpenCode V2** (native plugin) | `session.created` event `data.parentID` |
|
|
1245
|
-
|
|
1246
|
-
claude/codex/dsh need nothing here: they share one session id across
|
|
1247
|
-
subagents or have no child-session concept at all.
|
|
413
|
+
A child session (subagent/fork starting from an empty history) reports its lineage at birth — its identity registration carries `parentConversationId` and the proxy records a read-only `derivedFrom` link — so `decompress` / `search_context` fall back along the parent chain for content the child never saw itself (cycle-guarded, depth cap 8). Nothing is copied into the child, the parent is never modified; if the parent is unknown the child simply starts fresh. Per-lane parent signals (pi/omp `parentSession` header, OpenCode V1/V2 `parentID`): [SESSION-IDENTITY.md](SESSION-IDENTITY.md#derived-child-sessions-lineage-at-birth-1333-1362). claude/codex/dsh need nothing here: they share one session id across subagents or have no child-session concept at all.
|
|
1248
414
|
|
|
1249
415
|
### Windows: exclude the sessions dir from antivirus (#362)
|
|
1250
416
|
|
|
1251
|
-
The proxy
|
|
1252
|
-
(`%USERPROFILE%\.local\share\billion-context\` by default) and rewrites the
|
|
1253
|
-
file every turn of a long session. Persisted per session: the compression
|
|
1254
|
-
state (block summaries), the compressed originals cache (`blockContents`,
|
|
1255
|
-
what `bili export --full` recovers), and a bounded folded-view snapshot of
|
|
1256
|
-
the recent conversation (newest `BILI_PERSIST_TAIL_TOKENS` tokens, default
|
|
1257
|
-
16k) — the raw full history is never duplicated on disk (#401). On
|
|
1258
|
-
Windows, real-time antivirus (Windows
|
|
1259
|
-
Defender), the search indexer, or a sync tool (OneDrive) can lock that
|
|
1260
|
-
directory mid-write, so the rename fails with `EPERM` and every persist for
|
|
1261
|
-
that session fails until the lock clears.
|
|
1262
|
-
|
|
1263
|
-
When the same session fails N consecutive writes (default `5`), the proxy
|
|
1264
|
-
logs a one-time, actionable alert naming the directory to exclude. To fix it
|
|
1265
|
-
at the root: add `%USERPROFILE%\.local\share\billion-context\` to your
|
|
1266
|
-
antivirus **exclusions** (Windows Defender: Settings → Virus & threat
|
|
1267
|
-
protection → Manage settings → Exclusions → Add an exclusion → Folder) and
|
|
1268
|
-
make sure no sync tool (OneDrive / Dropbox / …) is syncing that path. Full
|
|
1269
|
-
steps in [CONFIGURATION.md](CONFIGURATION.md#windows-exclude-the-sessions-dir-from-antivirus-362).
|
|
417
|
+
The proxy rewrites each session's state file every turn of a long session; on Windows, real-time AV (Defender), the search indexer, or a sync tool (OneDrive) can lock the sessions dir mid-write, so persists fail with `EPERM` until the lock clears. Fix at the root: add `%USERPROFILE%\.local\share\billion-context\` to your antivirus exclusions (Defender steps included) and keep sync tools off that path — [CONFIGURATION.md](CONFIGURATION.md#windows-exclude-the-sessions-dir-from-antivirus-362).
|
|
1270
418
|
|
|
1271
419
|
### Session file cleanup (#1082)
|
|
1272
420
|
|
|
1273
|
-
Short-lived sessions leave small state files behind that are never resumed.
|
|
1274
|
-
Cleanup is **opt-in** — set `BILI_SESSION_GC=1` to enable it (off by default:
|
|
1275
|
-
session files are user data, so there is no silent deletion policy). When
|
|
1276
|
-
enabled and persistence is on, bili sweeps the sessions dir at boot and
|
|
1277
|
-
hourly, and deletes a file only when BOTH hold: it is older than
|
|
1278
|
-
`BILI_SESSION_GC_MAX_AGE_DAYS` (default 7 days), AND the session was never
|
|
1279
|
-
compressed (no folded blocks) with its newest request body ≤
|
|
1280
|
-
`BILI_SESSION_GC_MAX_TOKENS` tokens (default 1M; unrecorded legacy files use
|
|
1281
|
-
`contextTokens`) — so deletion loses nothing but bytes: resuming rebuilds the
|
|
1282
|
-
context from the client's own history at the cost of one cold rebuild.
|
|
1283
|
-
CCR content stores (#1097) live next to their session file as
|
|
1284
|
-
`<hash>.content-store.json` and follow the same lifecycle (#1180): a store is
|
|
1285
|
-
deleted together with its session file, an orphaned store (session file
|
|
1286
|
-
already gone) is swept once past the age gate, and the store's token footprint
|
|
1287
|
-
(unique-content counted with the kernel's CJK-aware `defaultCountTokens` — the
|
|
1288
|
-
same estimator as `rawInputTokens`) counts toward the size ceiling above. Compressed
|
|
1289
|
-
sessions are never deleted (their summaries cannot be rebuilt losslessly). Every deletion is audit-logged individually, plus one summary
|
|
1290
|
-
line per non-empty sweep. Live sessions, unreadable files, and encrypted
|
|
1291
|
-
files are handled conservatively (decoded via `BILI_ENCRYPTION_KEY` before
|
|
1292
|
-
judging). Details in [CONFIGURATION.md](CONFIGURATION.md#environment-variables).
|
|
421
|
+
Short-lived sessions leave small state files behind that are never resumed. Cleanup is **opt-in** (`BILI_SESSION_GC=1`; off by default — session files are user data, no silent deletion policy): when enabled, bili sweeps the sessions dir at boot and hourly and deletes a file only when BOTH hold — older than `BILI_SESSION_GC_MAX_AGE_DAYS` (default 7 days) AND never compressed with its newest request body ≤ `BILI_SESSION_GC_MAX_TOKENS` tokens (default 1M) — so deletion loses nothing but bytes. CCR content stores follow their session file's lifecycle; compressed sessions are never deleted; every deletion is audit-logged. Full policy: [CONFIGURATION.md](CONFIGURATION.md#environment-variables).
|
|
1293
422
|
|
|
1294
423
|
## Status
|
|
1295
424
|
|