billion-context 0.1.82 → 0.1.83-pr.512.341
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 +12 -1
- package/README.zh-CN.md +12 -1
- package/dist/index.js +103 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,6 +112,17 @@ 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?
|
|
116
|
+
|
|
117
|
+
Pick by your client:
|
|
118
|
+
|
|
119
|
+
| Client | Use |
|
|
120
|
+
|---|---|
|
|
121
|
+
| **pi** | [`billion-context-pi`](https://github.com/ranxianglei/billion-context-pi) (in-process extension) |
|
|
122
|
+
| **opencode** | [`opencode-acp`](https://github.com/ranxianglei/opencode-acp) (in-process extension) |
|
|
123
|
+
| **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context) via `bili omp` (built-in plugin) |
|
|
124
|
+
| **everything else** (no context hook) | [`billion-context`](https://github.com/ranxianglei/billion-context) — `bili <client>` (launcher, preferred) or `/bili/` prefix |
|
|
125
|
+
|
|
115
126
|
## Install
|
|
116
127
|
|
|
117
128
|
```bash
|
|
@@ -382,7 +393,7 @@ pass an explicit `x-acp-session` header per conversation to avoid collisions.
|
|
|
382
393
|
|
|
383
394
|
Early. Protocol handling and compression work against mock tests (500+ passing). Real-model integration testing is the next milestone. Expect rough edges.
|
|
384
395
|
|
|
385
|
-
|
|
396
|
+
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.
|
|
386
397
|
|
|
387
398
|
## License
|
|
388
399
|
|
package/README.zh-CN.md
CHANGED
|
@@ -50,6 +50,17 @@ AI 编程助手的<strong>通用上下文压缩代理</strong>
|
|
|
50
50
|
|
|
51
51
|
代理向对话注入四个上下文管理工具(`compress`、`decompress`、`search_context`、`acp_status`)。模型在对话增长时调用 `compress`,代理在服务端执行 —— 压缩后的范围在下一轮之前折叠进对话历史。
|
|
52
52
|
|
|
53
|
+
## 该选哪个?
|
|
54
|
+
|
|
55
|
+
按客户端选:
|
|
56
|
+
|
|
57
|
+
| 客户端 | 用这个 |
|
|
58
|
+
|---|---|
|
|
59
|
+
| **pi** | [`billion-context-pi`](https://github.com/ranxianglei/billion-context-pi)(进程内扩展) |
|
|
60
|
+
| **opencode** | [`opencode-acp`](https://github.com/ranxianglei/opencode-acp)(进程内扩展) |
|
|
61
|
+
| **omp** | [`billion-context`](https://github.com/ranxianglei/billion-context),`bili omp`(内置插件) |
|
|
62
|
+
| **其余所有**(没有上下文 hook) | [`billion-context`](https://github.com/ranxianglei/billion-context) —— `bili <client>`(启动器,优先)或 `/bili/` 前缀 |
|
|
63
|
+
|
|
53
64
|
## 安装
|
|
54
65
|
|
|
55
66
|
```bash
|
|
@@ -258,7 +269,7 @@ Windows 下会自动发现常见 Clash/Mihomo 静态系统代理;Web UI 会显
|
|
|
258
269
|
|
|
259
270
|
早期。协议处理和压缩已通过 mock 测试(500+ 项通过)。真实模型集成测试是下一里程碑。预期会有粗糙的地方。
|
|
260
271
|
|
|
261
|
-
pi
|
|
272
|
+
针对 pi / omp / opencode 的客户端插件随 `billion-context` 一起发布(`dist/agent/*.js`),用于协作代理路径。三者(`billion-context`、独立的 `billion-context-pi`、`opencode-acp`)如何取舍,见上文「该选哪个?」一节。
|
|
262
273
|
|
|
263
274
|
## 许可证
|
|
264
275
|
|
package/dist/index.js
CHANGED
|
@@ -48360,33 +48360,64 @@ 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
|
+
}
|
|
48363
48390
|
function registryLookup(reg, model, host) {
|
|
48364
48391
|
if (!reg || !model) return void 0;
|
|
48365
48392
|
const provider = host ? providerFromHost(host) : void 0;
|
|
48366
|
-
const
|
|
48367
|
-
|
|
48368
|
-
const
|
|
48369
|
-
|
|
48370
|
-
|
|
48371
|
-
|
|
48372
|
-
|
|
48373
|
-
|
|
48374
|
-
|
|
48375
|
-
|
|
48376
|
-
|
|
48377
|
-
|
|
48378
|
-
|
|
48379
|
-
|
|
48380
|
-
|
|
48381
|
-
|
|
48382
|
-
|
|
48383
|
-
|
|
48384
|
-
|
|
48385
|
-
|
|
48386
|
-
|
|
48387
|
-
|
|
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
|
+
}
|
|
48388
48420
|
}
|
|
48389
|
-
return max;
|
|
48390
48421
|
}
|
|
48391
48422
|
return void 0;
|
|
48392
48423
|
}
|
|
@@ -54705,7 +54736,7 @@ function handlePluginStatus(conversationId2, res, fallbackLatest = false) {
|
|
|
54705
54736
|
if (entry) entry.lastSeen = Date.now();
|
|
54706
54737
|
const limit = session.metadata.effectiveContextLimit;
|
|
54707
54738
|
const mem = remembered.get(session.id);
|
|
54708
|
-
const modelContextLimit = typeof limit === "number" && limit > 0 ? limit :
|
|
54739
|
+
const modelContextLimit = typeof limit === "number" && limit > 0 ? limit : 0;
|
|
54709
54740
|
let panel;
|
|
54710
54741
|
try {
|
|
54711
54742
|
panel = buildStatusPanel({
|
|
@@ -56729,6 +56760,9 @@ function logUnrecognizedPath(log2, url) {
|
|
|
56729
56760
|
log2("info", `unrecognized path ${key}: forwarding unchanged; further occurrences suppressed`);
|
|
56730
56761
|
}
|
|
56731
56762
|
}
|
|
56763
|
+
function isModelDiscoveryPath(path18) {
|
|
56764
|
+
return path18.replace(/\/+$/, "").endsWith("/models");
|
|
56765
|
+
}
|
|
56732
56766
|
var BILI_HOP_HEADER = "x-bili-hop";
|
|
56733
56767
|
function parseLauncherModelWindows(raw) {
|
|
56734
56768
|
if (!raw) return {};
|
|
@@ -56930,7 +56964,7 @@ Content-Length: ${Buffer.byteLength(body)}\r
|
|
|
56930
56964
|
);
|
|
56931
56965
|
}
|
|
56932
56966
|
if (opts.debug) {
|
|
56933
|
-
log2("info", `[debug] build features: raw-HTTP-capture(on) | remote_compaction_v2-strip(on) | cert-MITM-launcher(on) | strip-acp-summary(on) \u2014 seeing this line confirms the launcher build (not registry 0.1.34)`);
|
|
56967
|
+
log2("info", `[debug] build features: raw-HTTP-capture(${bodyDumpEnabled() ? "on" : "off"}) | remote_compaction_v2-strip(on) | cert-MITM-launcher(on) | strip-acp-summary(on) \u2014 seeing this line confirms the launcher build (not registry 0.1.34)`);
|
|
56934
56968
|
}
|
|
56935
56969
|
};
|
|
56936
56970
|
const attemptListen = (port) => {
|
|
@@ -56998,6 +57032,13 @@ Content-Length: ${Buffer.byteLength(body)}\r
|
|
|
56998
57032
|
}
|
|
56999
57033
|
return server;
|
|
57000
57034
|
}
|
|
57035
|
+
var SIDE_REQUEST_MAX_TOKENS = 200;
|
|
57036
|
+
function isSideRequest(parsed) {
|
|
57037
|
+
if (!parsed || typeof parsed !== "object") return false;
|
|
57038
|
+
const p2 = parsed;
|
|
57039
|
+
const raw = p2.max_tokens ?? p2.max_completion_tokens ?? p2.max_output_tokens;
|
|
57040
|
+
return typeof raw === "number" && raw > 0 && raw <= SIDE_REQUEST_MAX_TOKENS;
|
|
57041
|
+
}
|
|
57001
57042
|
function isTrustedAdminOrigin(origin, host, trustedHosts) {
|
|
57002
57043
|
if (!host || !trustedHosts.has(host.toLowerCase())) return false;
|
|
57003
57044
|
if (!origin) return true;
|
|
@@ -57274,11 +57315,11 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57274
57315
|
const pluginWindow = pluginReportedContextWindow(req.headers);
|
|
57275
57316
|
const launcherWindow = launcherContextWindow(model);
|
|
57276
57317
|
const configuredWindow = resolveConfiguredContextLimit(opts.routes, embeddedUrl, model);
|
|
57277
|
-
const peekWindow =
|
|
57318
|
+
const peekWindow = peekRegistryContext(model, host);
|
|
57278
57319
|
let native = betaWindow ?? pluginWindow ?? launcherWindow ?? configuredWindow ?? peekWindow ?? lookupContextLimit(model);
|
|
57279
57320
|
const operatorWindowTuned = resolveCompress(opts.routes, embeddedUrl, model, opts.compress).modelContextLimit !== void 0;
|
|
57280
57321
|
nativeFromFallback = !betaWindow && !pluginWindow && !launcherWindow && !peekWindow && !configuredWindow && !operatorWindowTuned;
|
|
57281
|
-
if (!native
|
|
57322
|
+
if (!native) {
|
|
57282
57323
|
native = await contextFromRegistry(model, host);
|
|
57283
57324
|
if (native) nativeFromFallback = false;
|
|
57284
57325
|
}
|
|
@@ -57379,7 +57420,6 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57379
57420
|
if (pluginAgent && !pluginConversation) pluginConversation = conversation;
|
|
57380
57421
|
if (pluginAgent) {
|
|
57381
57422
|
if (session.metadata.pluginAgent !== pluginAgent) session.metadata.pluginAgent = pluginAgent;
|
|
57382
|
-
session.metadata.effectiveContextLimit = reqConfig.modelContextLimit;
|
|
57383
57423
|
recordPluginSession(pluginConversation ?? conversation, session.id);
|
|
57384
57424
|
}
|
|
57385
57425
|
if (protocol === "responses" || protocol === "openai" || protocol === "anthropic") {
|
|
@@ -57389,6 +57429,21 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57389
57429
|
}
|
|
57390
57430
|
}
|
|
57391
57431
|
const pluginMode = pluginAgent !== void 0;
|
|
57432
|
+
if (!countTokens && !responsesCompact && protocol !== null && isSideRequest(parsed)) {
|
|
57433
|
+
log2("info", `[${session.id}] side request (max_tokens<=${SIDE_REQUEST_MAX_TOKENS}) \u2192 passthrough + tag strip only, kernel state untouched`);
|
|
57434
|
+
const sidePrepared = {
|
|
57435
|
+
body: bodyBuffer,
|
|
57436
|
+
session,
|
|
57437
|
+
processedMessages: [],
|
|
57438
|
+
originalMessages: [],
|
|
57439
|
+
protocol,
|
|
57440
|
+
stream: parsed.stream === true,
|
|
57441
|
+
compressInjected: false,
|
|
57442
|
+
sidePassthrough: true
|
|
57443
|
+
};
|
|
57444
|
+
await forward(req, res, opts, bodyBuffer, sidePrepared, core, reqConfig, log2, route, instanceId, affinity);
|
|
57445
|
+
return;
|
|
57446
|
+
}
|
|
57392
57447
|
const reqModel = parsed.model;
|
|
57393
57448
|
const learnedMap = session.metadata.learnedContextLimits;
|
|
57394
57449
|
const learnedLimit = (reqModel && learnedMap ? learnedMap[reqModel] : void 0) ?? session.metadata.learnedContextLimit;
|
|
@@ -57397,6 +57452,22 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57397
57452
|
reqConfig = { ...reqConfig, modelContextLimit: learnedLimit };
|
|
57398
57453
|
nativeFromFallback = false;
|
|
57399
57454
|
log2("info", `[${session.id}] self-healed context window: ${resolved} \u2192 ${learnedLimit} (learned from an upstream overflow)`);
|
|
57455
|
+
} else if (nativeFromFallback && reqModel) {
|
|
57456
|
+
const prevInput = session.stats.lastInputTokens ?? 0;
|
|
57457
|
+
const prevWindow = session.metadata.lastTurnWindow;
|
|
57458
|
+
const resolved = reqConfig.modelContextLimit;
|
|
57459
|
+
if (prevWindow !== void 0 && prevInput > prevWindow && prevInput > resolved && prevInput >= 1e3) {
|
|
57460
|
+
const map = session.metadata.learnedContextLimits ?? {};
|
|
57461
|
+
const prev = map[reqModel];
|
|
57462
|
+
if (prev === void 0 || prevInput > prev) {
|
|
57463
|
+
map[reqModel] = prevInput;
|
|
57464
|
+
session.metadata.learnedContextLimits = map;
|
|
57465
|
+
markDirty(session);
|
|
57466
|
+
}
|
|
57467
|
+
reqConfig = { ...reqConfig, modelContextLimit: prevInput };
|
|
57468
|
+
nativeFromFallback = false;
|
|
57469
|
+
log2("info", `[${session.id}] self-healed context window upward: ${resolved} \u2192 ${prevInput} (a prior turn used ${prevInput} input tokens, exceeding the fallback window)`);
|
|
57470
|
+
}
|
|
57400
57471
|
}
|
|
57401
57472
|
const nativeWindow = reqConfig.modelContextLimit;
|
|
57402
57473
|
if (shouldReserveOutputHeadroom(protocol)) {
|
|
@@ -57410,6 +57481,8 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57410
57481
|
}
|
|
57411
57482
|
if (reserved !== reqConfig.modelContextLimit) reqConfig = { ...reqConfig, modelContextLimit: reserved };
|
|
57412
57483
|
}
|
|
57484
|
+
session.metadata.effectiveContextLimit = reqConfig.modelContextLimit;
|
|
57485
|
+
session.metadata.lastTurnWindow = reqConfig.modelContextLimit;
|
|
57413
57486
|
acquireInFlight(session);
|
|
57414
57487
|
try {
|
|
57415
57488
|
await withSessionLock(session, async () => {
|
|
@@ -57474,7 +57547,7 @@ ${bodyBuffer.toString("utf8")}`);
|
|
|
57474
57547
|
}
|
|
57475
57548
|
}
|
|
57476
57549
|
if (!prepared) {
|
|
57477
|
-
if (protocol === null && !opts.passthrough) {
|
|
57550
|
+
if (protocol === null && !opts.passthrough && !isModelDiscoveryPath(urlPath)) {
|
|
57478
57551
|
logUnrecognizedPath(log2, req.url ?? "");
|
|
57479
57552
|
}
|
|
57480
57553
|
await forward(req, res, opts, bodyBuffer, null, core, reqConfig, log2, route, instanceId, void 0);
|
|
@@ -58311,7 +58384,7 @@ ${hdrText}
|
|
|
58311
58384
|
}
|
|
58312
58385
|
let responseBody = upstream.body;
|
|
58313
58386
|
try {
|
|
58314
|
-
if (prepared !== null && prepared.stream && maxFakeCompletionRetries() > 0) {
|
|
58387
|
+
if (prepared !== null && prepared.stream && !prepared.sidePassthrough && maxFakeCompletionRetries() > 0) {
|
|
58315
58388
|
const resolvedBuf = await resolveFakeCompletion(upstream.body, {
|
|
58316
58389
|
protocol: prepared.protocol,
|
|
58317
58390
|
body,
|