billion-context 0.1.82-pr.512.336 → 0.1.82

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 CHANGED
@@ -112,63 +112,6 @@ turn is a semantic mismatch the model tolerates (it is clearly marked
112
112
  summaries (their tool call isn't in the agent's history and the agent's view
113
113
  skips `acp_summary`) — but that needs the id match above, which doesn't occur.
114
114
 
115
- ## Which do I need? — billion-context vs billion-context-pi vs opencode-acp
116
-
117
- Three names keep appearing around this project. They are **different layers of
118
- the same idea** — context compression for coding agents — not three competing
119
- products. The deciding question is whether your client has a **"context hook"**
120
- (a place to host an in-process extension that registers the `compress` tool and
121
- runs compression locally):
122
-
123
- | Name | What it is | Where it runs | Pick it when |
124
- |---|---|---|---|
125
- | **billion-context-pi** | Standalone **in-process pi extension** (pure-extension mode — acp-kernel integrated directly into pi; the reference implementation). No external proxy needed. | Inside the pi process | **Preferred for pi.** Active, not superseded. |
126
- | **opencode-acp** | Standalone **in-process opencode extension** (same idea, for opencode). No external proxy needed. | Inside the opencode process | Preferred for opencode when you want pure in-process, no proxy. |
127
- | **billion-context** (`bili`) | The external **compression proxy** + launchers + built-in client-side plugins. **Mostly for clients that have no context hook.** | A local proxy process | Default for hookless clients (Claude Code, Codex, Cursor, Aider, ZCode…). Also how you drive **omp** (no standalone omp extension exists — it uses `bili`'s built-in plugin). |
128
-
129
- ```
130
- Does your client have a "context hook" (a spot for an in-process extension)?
131
-
132
- ├─ yes → prefer an in-process extension (no external proxy running)
133
- │ • pi → billion-context-pi ← preferred
134
- │ • opencode → opencode-acp
135
- │ • omp → billion-context (built-in omp plugin; no standalone omp pkg)
136
- │
137
- └─ no → the billion-context proxy (bili)
138
- Claude Code / Codex / Cursor / Aider / ZCode …
139
- two ways (prefer the launcher):
140
- ✅ bili <client> launcher mode ← preferred
141
- ○ /bili/ prefix plain proxy mode
142
- ```
143
-
144
- **When to pick which:**
145
-
146
- - **Client has a context hook** → prefer the in-process extension (tightest
147
- integration, no separate proxy process):
148
- - **pi** → **`billion-context-pi`** — the reference implementation and the
149
- primary way to run pi with compression; acp-kernel runs *inside* pi.
150
- - **opencode** → **`opencode-acp`** — the same idea for opencode.
151
- - **omp** → **`billion-context`** — omp has no standalone extension of its own,
152
- so it rides `billion-context`'s built-in omp plugin (`bili omp`).
153
- - **Client has no context hook** → **`billion-context`** (`bili`), the external
154
- proxy — its main job. Anything that can set a base URL but can't host an
155
- in-process extension: Claude Code, Codex, Cursor, Aider, ZCode, etc. Two ways,
156
- **prefer the launcher**:
157
- - `bili <client>` — **launcher mode** (spawns a proxy and wires the client up
158
- automatically; recommended).
159
- - `/bili/` base-URL prefix — plain proxy mode (point the client at a running
160
- proxy yourself).
161
-
162
- > Note: `billion-context` also ships thin built-in plugins for pi / omp /
163
- > opencode, so those hooked clients *can* optionally route through one shared
164
- > proxy (`bili pi` / `bili opencode`). In that case any standalone extension
165
- > (e.g. `billion-context-pi`) self-disables via `BILLION_CONTEXT_PROXY` to avoid
166
- > double compression. For pi, the standalone `billion-context-pi` remains the
167
- > preferred path; the proxy is the fallback / unification option.
168
-
169
- **TL;DR:** pi → `billion-context-pi`. opencode → `opencode-acp`. omp and every
170
- hookless client → `billion-context` (`bili`), launcher mode first.
171
-
172
115
  ## Install
173
116
 
174
117
  ```bash
@@ -439,7 +382,7 @@ pass an explicit `x-acp-session` header per conversation to avoid collisions.
439
382
 
440
383
  Early. Protocol handling and compression work against mock tests (500+ passing). Real-model integration testing is the next milestone. Expect rough edges.
441
384
 
442
- Client-side plugins for pi / omp / opencode ship inside `billion-context` (`dist/agent/*.js`) for the cooperative-proxy path. See the **"Which do I need?"** section above for how `billion-context`, the standalone `billion-context-pi`, and `opencode-acp` relate.
385
+ See [billion-context-pi](https://github.com/ranxianglei/billion-context-pi) for the pi-extension mode (in-process, tighter integration, the reference implementation).
443
386
 
444
387
  ## License
445
388
 
package/README.zh-CN.md CHANGED
@@ -50,45 +50,6 @@ AI 编程助手的<strong>通用上下文压缩代理</strong>
50
50
 
51
51
  代理向对话注入四个上下文管理工具(`compress`、`decompress`、`search_context`、`acp_status`)。模型在对话增长时调用 `compress`,代理在服务端执行 —— 压缩后的范围在下一轮之前折叠进对话历史。
52
52
 
53
- ## 该选哪个?—— billion-context / billion-context-pi / opencode-acp
54
-
55
- 围绕这个项目经常会出现三个名字。它们是<strong>同一件事(编程助手的上下文压缩)的不同层次</strong>,<em>不是三选一的对立产品</em>。关键看你的客户端有没有<strong>「上下文 hook」</strong>(能装一个进程内扩展、把 `compress` 工具注册进去并在本地跑压缩的地方):
56
-
57
- | 名字 | 是什么 | 跑在哪 | 什么时候选它 |
58
- |---|---|---|---|
59
- | **billion-context-pi** | 独立的<strong>进程内 pi 扩展</strong>(纯扩展模式 —— acp-kernel 直接集成进 pi;参考实现)。不需要外部代理。 | pi 进程内 | <strong>pi 首选。</strong>活跃,并未被取代。 |
60
- | **opencode-acp** | 独立的<strong>进程内 opencode 扩展</strong>(同样思路,面向 opencode)。不需要外部代理。 | opencode 进程内 | 想要纯进程内、不跑代理时,opencode 首选。 |
61
- | **billion-context**(`bili`) | 外部<strong>压缩代理</strong> + 启动器 + 内置客户端插件。<strong>主要面向没有上下文 hook 的客户端。</strong> | 本地代理进程 | 无 hook 客户端(Claude Code、Codex、Cursor、Aider、ZCode…)的默认选择;也是驱动 **omp** 的方式(没有独立的 omp 扩展包 —— 用 `bili` 内置插件)。 |
62
-
63
- ```
64
- 你的客户端有没有「上下文 hook」(能装进程内扩展的地方)?
65
-
66
- ├─ 有 → 优先进程内扩展(不跑外部代理)
67
- │ • pi → billion-context-pi ← 首选
68
- │ • opencode → opencode-acp
69
- │ • omp → billion-context(内置 omp 插件;无独立 omp 包)
70
- │
71
- └─ 没有 → billion-context 外部代理(bili)
72
- Claude Code / Codex / Cursor / Aider / ZCode …
73
- 两种方式(优先启动器):
74
- ✅ bili <client> 启动器模式 ← 首选
75
- ○ /bili/ 前缀 纯代理模式
76
- ```
77
-
78
- <strong>什么时候选哪个:</strong>
79
-
80
- - <strong>客户端有上下文 hook</strong> → 优先用进程内扩展(集成最紧、不额外跑一个代理进程):
81
- - <strong>pi</strong> → <strong>`billion-context-pi`</strong> —— 参考实现,也是 pi 跑压缩的主要方式,acp-kernel 直接在 pi 进程里跑。
82
- - <strong>opencode</strong> → <strong>`opencode-acp`</strong> —— opencode 的同款思路。
83
- - <strong>omp</strong> → <strong>`billion-context`</strong> —— omp 没有自己的独立扩展包,所以走 `billion-context` 内置的 omp 插件(`bili omp`)。
84
- - <strong>客户端没有上下文 hook</strong> → <strong>`billion-context`</strong>(`bili`)外部代理 —— 这是它的主场。任何能设 base URL、但装不了进程内扩展的客户端:Claude Code、Codex、Cursor、Aider、ZCode 等。两种方式,<strong>优先启动器</strong>:
85
- - `bili <client>` —— <strong>启动器模式</strong>(自动拉起代理并接好客户端;推荐)。
86
- - `/bili/` base-URL 前缀 —— 纯代理模式(你自己把客户端指向已运行的代理)。
87
-
88
- > 说明:`billion-context` 也自带针对 pi / omp / opencode 的轻量内置插件,让这些有 hook 的客户端<strong>可以</strong>选择性走同一个共享代理(`bili pi` / `bili opencode`)。此时任何独立扩展(如 `billion-context-pi`)会经 `BILLION_CONTEXT_PROXY` 自禁用,避免双层压缩。对 pi 而言,独立的 `billion-context-pi` 仍是首选路径;代理是兜底 / 统一入口的选项。
89
-
90
- <strong>一句话总结:</strong>pi → `billion-context-pi`;opencode → `opencode-acp`;omp 和所有无 hook 客户端 → `billion-context`(`bili`),优先启动器模式。
91
-
92
53
  ## 安装
93
54
 
94
55
  ```bash
@@ -297,7 +258,7 @@ Windows 下会自动发现常见 Clash/Mihomo 静态系统代理;Web UI 会显
297
258
 
298
259
  早期。协议处理和压缩已通过 mock 测试(500+ 项通过)。真实模型集成测试是下一里程碑。预期会有粗糙的地方。
299
260
 
300
- 针对 pi / omp / opencode 的客户端插件随 `billion-context` 一起发布(`dist/agent/*.js`),用于协作代理路径。三者(`billion-context`、独立的 `billion-context-pi`、`opencode-acp`)如何取舍,见上文「该选哪个?」一节。
261
+ pi 扩展模式(进程内、更紧密集成、参考实现)见 [billion-context-pi](https://github.com/ranxianglei/billion-context-pi)。
301
262
 
302
263
  ## 许可证
303
264
 
package/dist/index.js CHANGED
@@ -48360,64 +48360,33 @@ async function contextFromRegistry(model, host) {
48360
48360
  function peekRegistryContext(model, host) {
48361
48361
  return registryLookup(cache2, model, host);
48362
48362
  }
48363
- var VARIANT_SUFFIXES = [
48364
- "-interleaved-thinking",
48365
- "-thinking",
48366
- "-reasoning",
48367
- "-extended",
48368
- "-fast",
48369
- "-high",
48370
- "-medium",
48371
- "-low"
48372
- ];
48373
- function modelVariants(name) {
48374
- const variants = [name];
48375
- let current = name;
48376
- for (; ; ) {
48377
- let stripped = false;
48378
- for (const suffix of VARIANT_SUFFIXES) {
48379
- if (current.length > suffix.length && current.endsWith(suffix)) {
48380
- current = current.slice(0, -suffix.length);
48381
- variants.push(current);
48382
- stripped = true;
48383
- break;
48384
- }
48385
- }
48386
- if (!stripped) break;
48387
- }
48388
- return variants;
48389
- }
48390
48363
  function registryLookup(reg, model, host) {
48391
48364
  if (!reg || !model) return void 0;
48392
48365
  const provider = host ? providerFromHost(host) : void 0;
48393
- for (const name of modelVariants(model)) {
48394
- const candidates = provider ? [`${provider}/${name}`, name] : [name];
48395
- for (const key of candidates) {
48396
- const entry = reg[key];
48397
- const ctx = entry?.limit?.context;
48398
- if (typeof ctx === "number" && ctx > 0) return ctx;
48399
- }
48400
- if (provider === void 0) {
48401
- const suffix = `/${name}`;
48402
- let max;
48403
- const distinct = /* @__PURE__ */ new Set();
48404
- const parts = [];
48405
- for (const key of Object.keys(reg)) {
48406
- if (!key.endsWith(suffix)) continue;
48407
- const ctx = reg[key].limit?.context;
48408
- if (typeof ctx !== "number" || ctx <= 0) continue;
48409
- if (max === void 0 || ctx > max) max = ctx;
48410
- distinct.add(ctx);
48411
- parts.push(`${key}=${ctx}`);
48412
- }
48413
- if (max !== void 0) {
48414
- if (distinct.size > 1 && !warnedConflicts.has(name)) {
48415
- warnedConflicts.add(name);
48416
- log("warn", `[acp-registry] conflicting context windows for "${name}" (${parts.join(", ")}) \u2014 using max ${max}`);
48417
- }
48418
- return max;
48419
- }
48366
+ const candidates = provider ? [`${provider}/${model}`, model] : [model];
48367
+ for (const key of candidates) {
48368
+ const entry = reg[key];
48369
+ const ctx = entry?.limit?.context;
48370
+ if (typeof ctx === "number" && ctx > 0) return ctx;
48371
+ }
48372
+ if (provider === void 0) {
48373
+ const suffix = `/${model}`;
48374
+ let max;
48375
+ const distinct = /* @__PURE__ */ new Set();
48376
+ const parts = [];
48377
+ for (const key of Object.keys(reg)) {
48378
+ if (!key.endsWith(suffix)) continue;
48379
+ const ctx = reg[key].limit?.context;
48380
+ if (typeof ctx !== "number" || ctx <= 0) continue;
48381
+ if (max === void 0 || ctx > max) max = ctx;
48382
+ distinct.add(ctx);
48383
+ parts.push(`${key}=${ctx}`);
48384
+ }
48385
+ if (max !== void 0 && distinct.size > 1 && !warnedConflicts.has(model)) {
48386
+ warnedConflicts.add(model);
48387
+ log("warn", `[acp-registry] conflicting context windows for "${model}" (${parts.join(", ")}) \u2014 using max ${max}`);
48420
48388
  }
48389
+ return max;
48421
48390
  }
48422
48391
  return void 0;
48423
48392
  }
@@ -54736,7 +54705,7 @@ function handlePluginStatus(conversationId2, res, fallbackLatest = false) {
54736
54705
  if (entry) entry.lastSeen = Date.now();
54737
54706
  const limit = session.metadata.effectiveContextLimit;
54738
54707
  const mem = remembered.get(session.id);
54739
- const modelContextLimit = typeof limit === "number" && limit > 0 ? limit : 0;
54708
+ const modelContextLimit = typeof limit === "number" && limit > 0 ? limit : 2e5;
54740
54709
  let panel;
54741
54710
  try {
54742
54711
  panel = buildStatusPanel({
@@ -56760,9 +56729,6 @@ function logUnrecognizedPath(log2, url) {
56760
56729
  log2("info", `unrecognized path ${key}: forwarding unchanged; further occurrences suppressed`);
56761
56730
  }
56762
56731
  }
56763
- function isModelDiscoveryPath(path18) {
56764
- return path18.replace(/\/+$/, "").endsWith("/models");
56765
- }
56766
56732
  var BILI_HOP_HEADER = "x-bili-hop";
56767
56733
  function parseLauncherModelWindows(raw) {
56768
56734
  if (!raw) return {};
@@ -57308,11 +57274,11 @@ ${bodyBuffer.toString("utf8")}`);
57308
57274
  const pluginWindow = pluginReportedContextWindow(req.headers);
57309
57275
  const launcherWindow = launcherContextWindow(model);
57310
57276
  const configuredWindow = resolveConfiguredContextLimit(opts.routes, embeddedUrl, model);
57311
- const peekWindow = peekRegistryContext(model, host);
57277
+ const peekWindow = host ? peekRegistryContext(model, host) : void 0;
57312
57278
  let native = betaWindow ?? pluginWindow ?? launcherWindow ?? configuredWindow ?? peekWindow ?? lookupContextLimit(model);
57313
57279
  const operatorWindowTuned = resolveCompress(opts.routes, embeddedUrl, model, opts.compress).modelContextLimit !== void 0;
57314
57280
  nativeFromFallback = !betaWindow && !pluginWindow && !launcherWindow && !peekWindow && !configuredWindow && !operatorWindowTuned;
57315
- if (!native) {
57281
+ if (!native && host) {
57316
57282
  native = await contextFromRegistry(model, host);
57317
57283
  if (native) nativeFromFallback = false;
57318
57284
  }
@@ -57413,6 +57379,7 @@ ${bodyBuffer.toString("utf8")}`);
57413
57379
  if (pluginAgent && !pluginConversation) pluginConversation = conversation;
57414
57380
  if (pluginAgent) {
57415
57381
  if (session.metadata.pluginAgent !== pluginAgent) session.metadata.pluginAgent = pluginAgent;
57382
+ session.metadata.effectiveContextLimit = reqConfig.modelContextLimit;
57416
57383
  recordPluginSession(pluginConversation ?? conversation, session.id);
57417
57384
  }
57418
57385
  if (protocol === "responses" || protocol === "openai" || protocol === "anthropic") {
@@ -57430,22 +57397,6 @@ ${bodyBuffer.toString("utf8")}`);
57430
57397
  reqConfig = { ...reqConfig, modelContextLimit: learnedLimit };
57431
57398
  nativeFromFallback = false;
57432
57399
  log2("info", `[${session.id}] self-healed context window: ${resolved} \u2192 ${learnedLimit} (learned from an upstream overflow)`);
57433
- } else if (nativeFromFallback && reqModel) {
57434
- const prevInput = session.stats.lastInputTokens ?? 0;
57435
- const prevWindow = session.metadata.lastTurnWindow;
57436
- const resolved = reqConfig.modelContextLimit;
57437
- if (prevWindow !== void 0 && prevInput > prevWindow && prevInput > resolved && prevInput >= 1e3) {
57438
- const map = session.metadata.learnedContextLimits ?? {};
57439
- const prev = map[reqModel];
57440
- if (prev === void 0 || prevInput > prev) {
57441
- map[reqModel] = prevInput;
57442
- session.metadata.learnedContextLimits = map;
57443
- markDirty(session);
57444
- }
57445
- reqConfig = { ...reqConfig, modelContextLimit: prevInput };
57446
- nativeFromFallback = false;
57447
- log2("info", `[${session.id}] self-healed context window upward: ${resolved} \u2192 ${prevInput} (a prior turn used ${prevInput} input tokens, exceeding the fallback window)`);
57448
- }
57449
57400
  }
57450
57401
  const nativeWindow = reqConfig.modelContextLimit;
57451
57402
  if (shouldReserveOutputHeadroom(protocol)) {
@@ -57459,8 +57410,6 @@ ${bodyBuffer.toString("utf8")}`);
57459
57410
  }
57460
57411
  if (reserved !== reqConfig.modelContextLimit) reqConfig = { ...reqConfig, modelContextLimit: reserved };
57461
57412
  }
57462
- session.metadata.effectiveContextLimit = reqConfig.modelContextLimit;
57463
- session.metadata.lastTurnWindow = reqConfig.modelContextLimit;
57464
57413
  acquireInFlight(session);
57465
57414
  try {
57466
57415
  await withSessionLock(session, async () => {
@@ -57525,7 +57474,7 @@ ${bodyBuffer.toString("utf8")}`);
57525
57474
  }
57526
57475
  }
57527
57476
  if (!prepared) {
57528
- if (protocol === null && !opts.passthrough && !isModelDiscoveryPath(urlPath)) {
57477
+ if (protocol === null && !opts.passthrough) {
57529
57478
  logUnrecognizedPath(log2, req.url ?? "");
57530
57479
  }
57531
57480
  await forward(req, res, opts, bodyBuffer, null, core, reqConfig, log2, route, instanceId, void 0);