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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ Where upstreams are discovered from (read-only):

### Generated files (what gets written — last resort only, #535)

The launcher prefers file-free injection (env vars > CLI flags/extension APIs > generated files; see README, “Injection priority” section). Where a file is unavoidable it is a **copy** — the real config is never edited:
The launcher prefers file-free injection (env vars > CLI flags/extension APIs > generated files; see TECHNICAL-NOTES.md, “Injection priority” section). Where a file is unavoidable it is a **copy** — the real config is never edited:

- **pi / omp** — nothing is written (#535): provider baseUrls ride the `BILI_PROVIDER_REWRITES` env manifest consumed by the bili extension at load (`registerProvider`), and auto native compaction is cancelled in-extension (`session_before_compact`; omp distinguishes auto vs manual via the `auto_compaction_start` announcement, #851) — manual `/compact` stays user-owned. The real `~/.pi` / `~/.omp` homes are untouched.
- **opencode** — a temp `opencode.json` pointed at by `OPENCODE_CONFIG` (removed when the client exits), with `/bili/`-rewritten plaintext baseURLs **plus the thin `/acp` plugin appended**. On OpenCode 1.x the `opencode-acp` entries are stripped from the clone (the host must not load it armed) and the thin plugin imports that same package as a library instead, gated on legacy sessions; the first stripped spec rides along via `BILI_OPENCODE_ACP_SPEC` so the bridge imports the exact copy the host would have loaded (#920). Relative local plugin specs (`./x`, `../x`) are re-anchored to absolute paths in the clone — opencode resolves them against the declaring config file's dir, which the clone no longer is (#826).
Expand Down
2 changes: 1 addition & 1 deletion CONFIGURATION.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ Claude Code 的 undici fetch 忽略 `HTTPS_PROXY`,所以证书 MITM 拦不到

### 生成文件(写了什么 —— 最后手段,#535)

启动器优先零文件注入(env > CLI 参数/扩展 API > 生成文件;见 [README —— 注入优先级](README.zh-CN.md))。确实绕不开文件时写的都是**副本** —— 真实配置绝不编辑:
启动器优先零文件注入(env > CLI 参数/扩展 API > 生成文件;见 [TECHNICAL-NOTES.zh-CN.md —— 注入优先级](TECHNICAL-NOTES.zh-CN.md))。确实绕不开文件时写的都是**副本** —— 真实配置绝不编辑:

- **pi / omp** —— 不写任何文件(#535):provider baseUrl 走 `BILI_PROVIDER_REWRITES` env 清单,由 bili 扩展加载时消费(`registerProvider`);自动原生压缩改由扩展内取消(`session_before_compact`,omp 按 `auto_compaction_start` 预告区分自动/手动,#851),手动 `/compact` 保持用户所有。真实 `~/.pi` / `~/.omp` 主目录原样不动。
- **opencode** —— 临时 `opencode.json`(由 `OPENCODE_CONFIG` 指向,客户端退出时删除),明文 `baseURL` 重写为 `/bili/` 形式,**并追加了薄 `/acp` 插件**。OpenCode 1.x 下 `opencode-acp` 条目会从副本中移除(主机不得以激活状态加载它),改由薄插件把同一个包作为库导入、仅对 legacy 会话生效;首个被移除的 spec 经 `BILI_OPENCODE_ACP_SPEC` 传递,保证 bridge 导入的正是主机本会加载的那份拷贝(#920)。
Expand Down
136 changes: 40 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ Three ways to use it — pick one:
- **URL change (persistent):** prefix your client's baseURL with the proxy
origin + `/bili/`.

Mechanism details behind these three options (plugin lifecycle, runtime-info
protocol, injection priority) live in [TECHNICAL-NOTES.md](TECHNICAL-NOTES.md).

### Option 1 — Native plugin (`bili plugin install pi` / `omp` / `opencode` / `dsh` / `kimi`)

The proxy lives inside the client: install once, then start the client
Expand Down Expand Up @@ -241,52 +244,22 @@ mixing *writers* is what the guard forbids. `bili plugin update [client]`
is the one command that drives every lane through its own owner and prints
the per-lane update path (`bili plugin list` shows the same per-lane channel).

At load the plugin **spawns its own proxy** (or attaches to a healthy
running one — a parent-pid watchdog tears it down when the client exits),
At load the plugin **spawns its own proxy** (attaches to a healthy running
one if present; a parent-pid watchdog tears it down when the client exits),
rewrites model traffic to `<proxy>/bili/<upstream-url>`, registers
`compress` / `decompress` / `acp_status` as native client tools (plugin
mode), and binds the `/acp` panel to the current session. It also reports
the client's **own model config** to the proxy (runtime-info protocol,
#955) so compression budgets use the real window instead of a registry
guess. Opt-out envs: `BILI_NATIVE_PI=0`, `BILI_NATIVE_OMP=0`,
`BILI_NATIVE_OPENCODE=0`, `BILI_NATIVE_DSH=0`, `BILI_NATIVE_KIMI=0`.

#### Runtime-info protocol (#955)

A native plugin lives inside the client process, so it can read the model
config the client itself will use. It pushes that truth to the proxy on two
channels, and the proxy prefers it over the models.dev registry / built-in
table in the context-window chain:

| Channel | When | Fields |
|---|---|---|
| Per-request headers (gated on `x-bili-plugin`) | every model request | `x-bili-plugin-context-window`, `x-bili-plugin-max-output`, `x-bili-plugin-model` |
| `POST /__bili/plugin/runtime-info` (loopback) | plugin bootstrap + model switch | `{agent, model, contextWindow?, maxOutput?, baseURL?, source}` |

Resolution order for the window: `anthropic-beta` negotiation > per-request
plugin header > runtime-info table (agent+model must match) > launcher
env > route config > models.dev registry > built-in table. A reported
`maxOutput` only stands in when the request body carries no output budget
of its own. Implementations: `src/agent/pi.ts` (covers pi and omp),
`src/agent/opencode-native.ts` (v1), `src/agent/opencode-v2.ts`,
`src/agent/dsh-native.ts`, `src/kimi/native-mcp.ts` (bootstrap-time report
only — kimi's provider `custom_headers` are static, so per-request headers
would go stale on model switch) — other client integrations should follow
the same protocol.

The launcher env tier covers pure-proxy clients (no in-process plugin):
`bili <client>` reads the client's own model config at launch
(`model_context_window` / `model_max_output_tokens` for codex,
`contextWindow` / `maxTokens` for pi / omp, `limit.context` / `limit.output`
for opencode, `maxInputTokens` / `maxOutputTokens` for codebuddy) and hands
it to the proxy via `BILI_LAUNCHER_MODEL_WINDOWS` / `BILI_LAUNCHER_MODEL_MAX_OUTPUTS`
(#971). A plugin report — when present — always outranks it.

Before the first model request there is no session yet, so the `/acp` panel
probes `GET /__bili/plugin/status?conversationId=<agent>&fallback=latest`,
which answers from the runtime table (`phase: "pre-first-request"`) instead
of 404ing — the reported config is visible immediately, and the real session
takes over once traffic lands.
mode), and reports the client's **own model config** to the proxy so
compression budgets use the real window instead of a registry guess.
Opt-out envs: `BILI_NATIVE_PI=0`, `BILI_NATIVE_OMP=0`,
`BILI_NATIVE_OPENCODE=0`, `BILI_NATIVE_DSH=0`, `BILI_NATIVE_KIMI=0`. Full
mechanics: [TECHNICAL-NOTES.md](TECHNICAL-NOTES.md).

**Runtime-info protocol (#955).** A native plugin reads the model config
the client itself will use and pushes it to the proxy (per-request headers
+ bootstrap report); the proxy prefers that truth over the models.dev
registry / built-in table when resolving the context window. Protocol
details, resolution order, and implementations:
[TECHNICAL-NOTES.md](TECHNICAL-NOTES.md).

Notes:

Expand All @@ -302,39 +275,12 @@ Notes:
mechanics in the "Kimi Code" section below.
- `codex` has a companion install too (an MCP shell), but it needs a running
proxy — it is not native mode.
- `claude` also has a **native posture** (#964, hybrid): Claude Code has no
in-process extension point, so `bili plugin install claude` writes a
managed block into `~/.claude/settings.json` (env
`ANTHROPIC_BASE_URL=http://127.0.0.1:48787/bili/<upstream>`,
`DISABLE_AUTO_COMPACT=1`, and a `SessionStart` hook) plus the same
user-scope MCP shell as before, now pinned to that stable port. The hook
(fired before claude's first model request) attaches to a healthy proxy
on the port or spawns one whose pid watchdog tracks claude itself, so the
proxy lives and dies with the session. Port override:
`BILI_CLAUDE_NATIVE_PORT` > config `claude.nativePort` > 48787; upstream
override: `BILI_CLAUDE_UPSTREAM` (or the existing `claude.anthropicBaseUrl`
config). Opt out with `BILI_NATIVE_CLAUDE=0` — the hook then brings up a
**passthrough** proxy on the same port (verbatim forward, compression
off) so claude keeps working. The block is pure JSON merge/strip: foreign
keys are never touched, `bili plugin remove claude` restores exactly.
`bili claude` still works on a machine with the native block installed —
it overrides the static URL with its own ephemeral proxy and the hook
stays dormant.

### Injection priority — no files unless unavoidable (#535)

bili never owns user data: every launched client runs on its **real home**, so
runtime writes land where the user expects them. When pointing a client at the
proxy, the launcher picks by priority — **env vars first** (proxy/CA envs for
hermes/dsh/codex; the `BILI_PROVIDER_REWRITES` URL manifest for pi/omp,
consumed by their extension's `registerProvider` at load), then **CLI flags or
extension APIs** (codex `-c key=value`, opencode plugin), and **generated files
last** — today only opencode's temp `opencode.json` (deleted on exit) and dsh's
loopback exception: dsh's fetch stack bypasses proxy envs for loopback targets
unconditionally, so local upstreams keep the persistent `~/.dsh-bili` overlay
rewrite until dsh gains a settings-path env or an upstream loopback opt-out.
Overlay dirs created by older versions are left in place and never merged back
into the real home.
- `claude` also has a **native posture** (#964): `bili plugin install
claude` writes a managed settings block (static `/bili/` URL +
`SessionStart` hook) plus an MCP shell pinned to a stable port — the
proxy lives and dies with the session. Opt out with
`BILI_NATIVE_CLAUDE=0` (passthrough). Mechanics:
[TECHNICAL-NOTES.md](TECHNICAL-NOTES.md).

### 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`)

Expand Down Expand Up @@ -388,6 +334,23 @@ automatically.
For per-client configuration examples (OpenCode, Codex, Pi, login-client
MITM, …) see the web UI guide at [http://localhost:8787](http://localhost:8787).

**Verify.** With the proxy running and your config saved, check it answers
and that your first real request shows compression activity in the log:

```bash
# Health check (proxy up + where it forwards)
curl -s http://localhost:8787/__bili/health
# → {"ok":true,"upstream":"https://api.anthropic.com"}

# Live session stats (after a real request)
curl -s http://localhost:8787/__bili/stats
```

Then send one message from your client and watch the log
(`~/.local/state/billion-context/bili.log`, also printed to stderr). You
should see a `processTurn` line per request, and once the conversation grows,
`[acp-usage] round N input=X cached=Y (cache hit Z%)` + a `compress` event.

### dsh (deepseek-harness)

Two lanes, same plugin (#941):
Expand Down Expand Up @@ -486,25 +449,6 @@ small node scripts that do the work around the client:
NOT pushed out — ACP compression simply fires first, as in launcher mode.
Opt-out: `BILI_NATIVE_KIMI=0`.

### Verify

With the proxy running and your config saved, check it answers and that your
first real request shows compression activity in the log:

```bash
# Health check (proxy up + where it forwards)
curl -s http://localhost:8787/__bili/health
# → {"ok":true,"upstream":"https://api.anthropic.com"}

# Live session stats (after a real request)
curl -s http://localhost:8787/__bili/stats
```

Then send one message from your client and watch the log
(`~/.local/state/billion-context/bili.log`, also printed to stderr). You
should see a `processTurn` line per request, and once the conversation grows,
`[acp-usage] round N input=X cached=Y (cache hit Z%)` + a `compress` event.

### Client uses `http.proxy` (CONNECT) but nothing compresses

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).
Expand Down
Loading
Loading