# Technical notes Mechanism-level details behind the three usage options in the README. The README keeps each option concise; the "how it actually works" material lives here instead of between the options. ## Native plugin lifecycle (Option 1) 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), rewrites model traffic to `/bili/`, 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 ` 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=&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. ## Claude native posture (#964) 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/`, `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.