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
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ Pick by your client:
| **pi** | [`billion-context-pi`](https://github.com/ranxianglei/billion-context-pi) (in-process extension) |
| **opencode 1.x** | `bili plugin install opencode` (self-spawning native plugin — pre-migration [`opencode-acp`](https://github.com/ranxianglei/opencode-acp) sessions keep working, see "OpenCode 1.x") or `bili opencode` (launcher) or standalone `opencode-acp` (in-process extension) |
| **opencode 2.0+** | `bili opencode` (built-in V2 plugin — native tools, no separate package) or `bili plugin install opencode` (self-spawning native plugin, no launcher) |
| **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili omp` (built-in plugin) |
| **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) |
| **dsh** | `bili dsh` (launcher — full native plugin via `--patch`: tools, session-bound `/acp`, fetch intercept) or `bili plugin install dsh` ≡ `dsh plugin --profile <name> add billion-context` (one unified lane — pnpm-installs the package into each profile so dsh mounts the bundled patch layer; the bili form just drives dsh's own channel per profile and migrates legacy managed blocks) |
| **claude** | `bili claude` (launcher) or `bili plugin install claude` (native posture, #964 — managed settings block + session-owned proxy; see the notes below) |
| **everything else** (no context hook) | [`billion-context`](https://github.com/ranxianglei/billion-context) — `bili <client>` (launcher, preferred) or `/bili/` prefix |
Expand Down Expand Up @@ -185,15 +185,16 @@ Three ways to use it — pick one:
- **URL change (persistent):** prefix your client's baseURL with the proxy
origin + `/bili/`.

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

The proxy lives inside the client: install once, then start the client
exactly as you always do — no launcher command, no env vars, no fixed port,
no URL edits. Supported today for **pi**, **opencode** (1.x and 2.x) and
**dsh**:
no URL edits. Supported today for **pi**, **omp**, **opencode** (1.x and
2.x) and **dsh**:

```bash
bili plugin install pi # registers a "billion-context" entry in pi's settings (npm form when bili itself was npm-installed)
bili plugin install omp # registers an extensions entry in omp's config.yml (~/.omp/agent/config.yml)
bili plugin install opencode # registers the plugin in opencode's real config + disables native auto-compaction
bili plugin install dsh # runs 'dsh plugin --profile <name> add billion-context' for every existing profile
bili plugin remove <client> # undo (dsh removes through the same channel; config snapshots go to .bili-bak)
Expand All @@ -211,8 +212,8 @@ rewrites model traffic to `<proxy>/bili/<upstream-url>`, registers
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_OPENCODE=0`,
`BILI_NATIVE_DSH=0`.
guess. Opt-out envs: `BILI_NATIVE_PI=0`, `BILI_NATIVE_OMP=0`,
`BILI_NATIVE_OPENCODE=0`, `BILI_NATIVE_DSH=0`.

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

Expand All @@ -230,9 +231,10 @@ 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`, `src/agent/opencode-native.ts`
(v1), `src/agent/opencode-v2.ts`, `src/agent/dsh-native.ts` — other client
integrations should follow the same protocol.
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` — 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
Expand All @@ -257,8 +259,8 @@ Notes:
to stand down cleanly).
- On OpenCode 1.x, pre-migration `opencode-acp` sessions keep working
(v1 lane routing, #920) — see "OpenCode 1.x" below.
- `codex` / `omp` have companion installs too (an MCP shell / a thin
extension), but those need a running proxy — they are not native mode.
- `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
Expand Down
13 changes: 7 additions & 6 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ AI 编程助手的<strong>通用上下文压缩代理</strong>
| **opencode 1.x** | [`billion-context`](https://github.com/ranxianglei/billion-context) —— `bili plugin install opencode`(V1 `.server()` 钩子,与 2.x 同一个包装器)或 `bili opencode`(启动器);[`opencode-acp`](https://github.com/ranxianglei/opencode-acp)(进程内扩展)仍可用 |
| **opencode 2.0+** | `bili opencode`(内置 V2 插件 —— 原生工具,无需另装包)或 `bili plugin install opencode`(自拉起原生插件,免启动器) |
| **opencode** | [`billion-context`](https://github.com/ranxianglei/billion-context),`bili opencode`(新会话走 bili 代理;现有 [`opencode-acp`](https://github.com/ranxianglei/opencode-acp) 会话继续可用,见下文「OpenCode 1.x」) |
| **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context),`bili omp`(内置插件) |
| **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context),`bili omp`(内置插件)或 `bili plugin install omp`(自拉起原生插件,免启动器) |
| **dsh** | [`billion-context`](https://github.com/ranxianglei/billion-context) —— `bili dsh`(启动器,经 `--patch` 注入完整原生插件:工具、会话绑定 `/acp`、fetch 拦截)或 `bili plugin install dsh` ≡ `dsh plugin --profile <name> add billion-context`(统一泳道 —— pnpm 把包装进各 profile、由 dsh 挂载包内 patch 层;bili 形式只是替你按 profile 驱动 dsh 自己的通道,并顺带迁移旧版受管块) |
| **claude** | `bili claude`(启动器)或 `bili plugin install claude`(原生姿态,#964 —— 受管 settings 块 + 会话自管代理;见下方"注意") |
| **其余所有**(没有上下文 hook) | [`billion-context`](https://github.com/ranxianglei/billion-context) —— `bili <client>`(启动器,优先)或 `/bili/` 前缀 |
Expand Down Expand Up @@ -113,20 +113,21 @@ npm install -g billion-context



### 方式 1 —— 原生插件(native,`bili plugin install pi` / `opencode` / `dsh`)
### 方式 1 —— 原生插件(native,`bili plugin install pi` / `omp` / `opencode` / `dsh`)

代理住进客户端:装一次,之后照常启动客户端 —— 不用启动器命令、不用环境变量、不用固定端口、不用改 URL。目前支持 **pi**、**opencode**(1.x 与 2.x)、**dsh**:
代理住进客户端:装一次,之后照常启动客户端 —— 不用启动器命令、不用环境变量、不用固定端口、不用改 URL。目前支持 **pi**、**omp**、**opencode**(1.x 与 2.x)、**dsh**:

```bash
bili plugin install pi # 在 pi 的 settings 里注册 billion-context 条目(bili 自身为 npm 安装时写 npm 条目)
bili plugin install omp # 在 omp 的 config.yml(~/.omp/agent/config.yml)注册 extensions 条目
bili plugin install opencode # 在 opencode 真实配置里注册插件 + 关闭原生自动压缩
bili plugin install dsh # 对每个已存在的 profile 执行 'dsh plugin --profile <name> add billion-context'
bili plugin remove <client> # 卸载(dsh 经同一通道移除;配置快照存 .bili-bak)
```

dsh 用户可以完全不用 bili:`dsh plugin --profile <name> add billion-context` 正是 `bili plugin install dsh` 按 profile 驱动的那条命令 —— 两种走法终态一致(pnpm 装进 profile、patch 层由 dsh 自己挂载)—— 见下文 dsh 段。

插件加载时**自拉起自己的代理**(已有健康实例则直接复用 —— 父进程 pid 看门狗在客户端退出时收掉它),把模型流量改写到 `<proxy>/bili/<上游URL>`,并把 `compress` / `decompress` / `acp_status` 注册为客户端原生工具(plugin 模式),`/acp` 面板绑定当前会话。插件还会把客户端**自己的模型配置**上报给代理(runtime-info 协议,#955),压缩预算用真实窗口而不是注册表猜测。退出开关:`BILI_NATIVE_PI=0`、`BILI_NATIVE_OPENCODE=0`、`BILI_NATIVE_DSH=0`。
插件加载时**自拉起自己的代理**(已有健康实例则直接复用 —— 父进程 pid 看门狗在客户端退出时收掉它),把模型流量改写到 `<proxy>/bili/<上游URL>`,并把 `compress` / `decompress` / `acp_status` 注册为客户端原生工具(plugin 模式),`/acp` 面板绑定当前会话。插件还会把客户端**自己的模型配置**上报给代理(runtime-info 协议,#955),压缩预算用真实窗口而不是注册表猜测。退出开关:`BILI_NATIVE_PI=0`、`BILI_NATIVE_OMP=0`、`BILI_NATIVE_OPENCODE=0`、`BILI_NATIVE_DSH=0`。

#### Runtime-info 协议(#955)

Expand All @@ -137,7 +138,7 @@ dsh 用户可以完全不用 bili:`dsh plugin --profile <name> add billion-con
| 逐请求头(门控在 `x-bili-plugin`) | 每次模型请求 | `x-bili-plugin-context-window`、`x-bili-plugin-max-output`、`x-bili-plugin-model` |
| `POST /__bili/plugin/runtime-info`(回环地址) | 插件自举 + 模型切换 | `{agent, model, contextWindow?, maxOutput?, baseURL?, source}` |

窗口解析顺序:`anthropic-beta` 协商 > 逐请求 plugin 头 > runtime-info 表(agent+model 必须匹配) > launcher 环境变量 > 路由配置 > models.dev 注册表 > 内置表。上报的 `maxOutput` 仅在请求体自带输出预算缺席时兜底。现有实现:`src/agent/pi.ts`、`src/agent/opencode-native.ts`(v1)、`src/agent/opencode-v2.ts`、`src/agent/dsh-native.ts` —— 其他客户端接入请遵循同一协议。
窗口解析顺序:`anthropic-beta` 协商 > 逐请求 plugin 头 > runtime-info 表(agent+model 必须匹配) > launcher 环境变量 > 路由配置 > models.dev 注册表 > 内置表。上报的 `maxOutput` 仅在请求体自带输出预算缺席时兜底。现有实现:`src/agent/pi.ts`(覆盖 pi 与 omp)、`src/agent/opencode-native.ts`(v1)、`src/agent/opencode-v2.ts`、`src/agent/dsh-native.ts` —— 其他客户端接入请遵循同一协议。

launcher 环境变量这档覆盖纯代理客户端(无进程内插件):`bili <client>` 启动时读客户端自己的模型配置(codex 的 `model_context_window` / `model_max_output_tokens`,pi / omp 的 `contextWindow` / `maxTokens`,opencode 的 `limit.context` / `limit.output`,codebuddy 的 `maxInputTokens` / `maxOutputTokens`),经 `BILI_LAUNCHER_MODEL_WINDOWS` / `BILI_LAUNCHER_MODEL_MAX_OUTPUTS` 交给代理(#971)。插件上报 —— 若存在 —— 永远优先于它。

Expand All @@ -147,7 +148,7 @@ launcher 环境变量这档覆盖纯代理客户端(无进程内插件):`bili <c

- 原生模式与独立进程内扩展(`billion-context-pi`、`opencode-acp`)**互斥** —— 安装器负责换条目并把原配置快照(`.bili-bak`);迁移细节见上方客户端表(pi 需 `billion-context-pi` 0.1.72+ 才能干净退让)。
- OpenCode 1.x 上,迁移前的 `opencode-acp` 旧会话继续可用(v1 泳道路由,#920)—— 见下文 "OpenCode 1.x"。
- `codex` / `omp` 也有配套安装(MCP shell 与轻量扩展),但它们需要一个在跑的代理 —— 不属于原生模式。
- `codex` 也有配套安装(MCP shell),但它需要一个在跑的代理 —— 不属于原生模式。
- `claude` 有**原生姿态**(hybrid,#964):Claude Code 没有进程内扩展点,所以 `bili plugin install claude` 往 `~/.claude/settings.json` 写一个受管块(env `ANTHROPIC_BASE_URL=http://127.0.0.1:48787/bili/<upstream>`、`DISABLE_AUTO_COMPACT=1`、`SessionStart` hook),外加同样指向该稳定端口的用户级 MCP shell。hook 在首个模型请求前触发:附着到端口上健康的代理,或拉起一个 pid 看门狗追踪 claude 本身的代理 —— 代理随会话生灭。端口覆盖:`BILI_CLAUDE_NATIVE_PORT` > config `claude.nativePort` > 48787;上游覆盖:`BILI_CLAUDE_UPSTREAM`(或既有 `claude.anthropicBaseUrl`)。`BILI_NATIVE_CLAUDE=0` 退出 —— hook 改为拉起同端口的 **passthrough** 代理(原样转发、关闭压缩)。块是纯 JSON merge/strip:外部键从不触碰,`bili plugin remove claude` 精确还原。装有原生块的机器上 `bili claude` 仍可用 —— 它用自身临时代理覆盖静态 URL,hook 保持休眠。

### 注入优先级 —— 能不写文件就不写(#535)
Expand Down
72 changes: 72 additions & 0 deletions src/agent/omp-native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Native omp mode (#957): the package-installed omp extension bootstraps its
// own proxy — bare `omp` (with this package installed via
// `bili plugin install omp`) gets full plugin-mode compression with NO
// launcher. Same flow as pi-native.ts (#519):
// 1. spawn the package's own proxy (`dist/index.js start`, ephemeral
// port, parent-pid watchdog = this omp process) via ensureProxyRunning —
// a healthy compatible instance is ATTACHED, not doubled;
// 2. patch globalThis.fetch (native-intercept.ts) so model-API requests
// are rewritten to `<proxy>/bili/<full-upstream-url>` (verified
// patchable under Bun — omp runs on a bundled bun);
// 3. set BILLION_CONTEXT_PROXY so the shared plugin (pi.ts) detects the
// proxy through its existing env fallback — tools, /acp, the compaction
// cancel and the prompt_cache_key stamp all reuse the launcher-mode
// code paths. omp never emits before_provider_headers, so its
// runtime-info report (#955) rides before_provider_request instead.
// Skipped when a proxy is already managed (BILLION_CONTEXT_PROXY set by a
// `bili` MITM launch, or BILI_PROVIDER_REWRITES set by a `bili` /bili/
// launch) or opted out (BILI_NATIVE_OMP=0).

import { ensureProxyRunning, LAUNCHER_DEFAULT_HOST } from "../launcher.js";
import { createBiliPlugin } from "./pi.js";
import { markNativeHost, nativeBootstrapGate, nativeProxyScriptPath, singleFlight } from "./native-bootstrap.js";
import { installNativeFetchIntercept, type NativeInterceptState } from "./native-intercept.js";

/** Decides whether the native bootstrap should run in this process. */
export function shouldBootstrapNativeOmp(env: NodeJS.ProcessEnv): boolean {
return nativeBootstrapGate(env, "BILI_NATIVE_OMP");
}

function errMessage(err: unknown): string {
return err instanceof Error ? err.message : String(err);
}

const state: NativeInterceptState = { origin: undefined, ready: Promise.resolve(undefined) };

async function bootstrap(): Promise<string | undefined> {
try {
const handle = await ensureProxyRunning(
{ host: LAUNCHER_DEFAULT_HOST, port: 0, passthrough: false, debug: false },
{ scriptPath: nativeProxyScriptPath() },
);
const origin = handle.origin;
state.origin = origin;
process.env.BILLION_CONTEXT_PROXY = origin;
return origin;
} catch (err) {
console.error(`bili-native: proxy bootstrap failed — model traffic goes direct (uncompressed): ${errMessage(err)}`);
return undefined;
}
}

const nativeActive = shouldBootstrapNativeOmp(process.env);
if (nativeActive) markNativeHost(process.env, "omp");

// node:test imports this module for shouldBootstrapNativeOmp — never
// bootstrap a real proxy from inside a test run.
if (process.env.NODE_TEST_CONTEXT === undefined && nativeActive) {
const start = singleFlight(bootstrap);
state.respawn = start;
state.onGiveUp = () => {
// We wrote BILLION_CONTEXT_PROXY at successful bootstrap. If the proxy
// dies mid-session and the respawn fails, traffic goes direct — clear
// the env so event-time ownership checks (session_before_compact
// cancel, prompt_cache_key stamping) stop claiming compression
// ownership and native compaction comes back with the direct traffic.
delete process.env.BILLION_CONTEXT_PROXY;
};
state.ready = start();
installNativeFetchIntercept(state);
}

export default createBiliPlugin("omp");
14 changes: 14 additions & 0 deletions src/agent/pi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,11 +459,25 @@ export function createBiliPlugin(agentOverride?: string, opts?: { retryIntervalM
}
void registerTools(pi, ctx, state, agent).catch((err: unknown) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));
});
// omp never emits before_provider_headers — where pi stamps the
// x-bili-plugin-* headers and reports runtime info (#955) — so omp's
// report rides this per-request event instead: POST only, deduped per
// model switch, gated on toolsReady like pi's header path (ownership
// claim = ACP tools registered; round 1 rides wire mode).
function reportOmpRuntimeInfo(ctx: Ctx): void {
if (state.toolsReady !== true) return;
const modelId = ctx.model?.id;
if (typeof modelId !== "string" || modelId.length === 0) return;
const window = ctx.model?.contextWindow;
const maxOut = (ctx.model as { maxTokens?: unknown } | undefined)?.maxTokens;
reportRuntimeInfoOnChange(proxyBaseForCtx(ctx), { agent, model: modelId, contextWindow: typeof window === "number" && window > 0 ? Math.floor(window) : undefined, maxOutput: typeof maxOut === "number" && maxOut > 0 ? Math.floor(maxOut) : undefined, baseURL: ctx.model?.baseUrl, source: "client-config" });
}
pi.on("before_provider_request", (event, ctx) => {
// omp emits this per model request (but never before_provider_headers);
// it doubles as the retry driver when the session_start manifest
// fetch raced the proxy startup. Cached by sid, throttled by retryAt.
void registerTools(pi, ctx, state, agent).catch((err: unknown) => console.error(`bili-plugin(${agent}): ${err instanceof Error ? err.message : String(err)}`));
if (agent === "omp") reportOmpRuntimeInfo(ctx);
return stampPromptCacheKey(event, ctx, agent);
});
pi.on("session_start", (_event, ctx) => {
Expand Down
Loading
Loading