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 +1 -58
- package/README.zh-CN.md +1 -40
- package/dist/index.js +28 -79
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
48394
|
-
|
|
48395
|
-
|
|
48396
|
-
|
|
48397
|
-
|
|
48398
|
-
|
|
48399
|
-
|
|
48400
|
-
|
|
48401
|
-
|
|
48402
|
-
|
|
48403
|
-
|
|
48404
|
-
|
|
48405
|
-
|
|
48406
|
-
|
|
48407
|
-
|
|
48408
|
-
|
|
48409
|
-
|
|
48410
|
-
|
|
48411
|
-
|
|
48412
|
-
|
|
48413
|
-
|
|
48414
|
-
|
|
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 :
|
|
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
|
|
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);
|