billion-context 0.1.68 → 0.1.69-pr.443.269
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 +61 -0
- package/dist/index.js +94 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,67 @@ Agent (Claude Code / Codex / Cursor / Aider ...)
|
|
|
50
50
|
|
|
51
51
|
The proxy injects four context-management tools (`compress`, `decompress`, `search_context`, `acp_status`) into the conversation. The model calls `compress` when the conversation grows, and the proxy executes it server-side — the compressed ranges are folded into the conversation history before the next turn.
|
|
52
52
|
|
|
53
|
+
### Two compression modes — who executes `compress`
|
|
54
|
+
|
|
55
|
+
The proxy runs in one of two modes, and **the mode decides who executes
|
|
56
|
+
`compress`, which in turn decides how the summary travels to the model** (the
|
|
57
|
+
"carrier"). This distinction is the root of #377.
|
|
58
|
+
|
|
59
|
+
| | **Launcher / plugin mode** (`bili pi`, `bili codex`, …) | **Proxy mode** (plain client → `/bili/`) |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| Client | ACP-native agent with the bili extension (pi/omp) | Any OpenAI/Anthropic client, no extension |
|
|
62
|
+
| Who executes `compress` | **The agent** (pi runs it locally) | **The proxy** (server-side compress loop) |
|
|
63
|
+
| `compress` tool call in the re-sent history? | Yes — part of the agent's own conversation | No — ephemeral proxy-loop traffic |
|
|
64
|
+
| Preflight blocks (no tool call)? | No — overflow forces the model to call `compress` | Yes — `src/preflight.ts` compresses behind the client's back |
|
|
65
|
+
| **Summary carrier on the wire** | **the `compress` tool call** | **an `acp_summary` user message** |
|
|
66
|
+
| System messages on the wire | always exactly 1 (client + prompt) | always exactly 1 (client + prompt) — summaries ride on user messages |
|
|
67
|
+
| SGLang "single system" 400 (#377) | cannot happen | cannot happen (summaries are user messages, not system) |
|
|
68
|
+
| Proxy-injected tools + nudge | none (agent owns compression) | the 4 context tools + nudge (when enabled) |
|
|
69
|
+
|
|
70
|
+
**Why the carriers differ.** In plugin mode the agent owns compression: the
|
|
71
|
+
`compress` call + result live in the agent's own history and are re-sent every
|
|
72
|
+
turn, so the summary rides on the tool call and the agent's view never renders
|
|
73
|
+
the kernel's `acp_summary` fallback (`billion-context-pi` `src/messages.ts`
|
|
74
|
+
skips `acp_summary_*`). In proxy mode the client is not ACP-native, so the
|
|
75
|
+
proxy executes `compress` server-side; the tool call never enters the client's
|
|
76
|
+
history, and preflight blocks have no tool call at all — so the kernel's
|
|
77
|
+
`acp_summary` message is the only carrier. The kernel renders it as role
|
|
78
|
+
`system`, but strict OpenAI-compatible backends (SGLang) require exactly one
|
|
79
|
+
system message at index 0, so `systemToUser` (`src/util.ts`) re-voices it as a
|
|
80
|
+
`user` message, leaving it at its anchor position. This keeps the head system
|
|
81
|
+
message (the prefix-cache anchor) byte-stable across compress turns, so a new
|
|
82
|
+
block does not invalidate the whole-conversation prefix.
|
|
83
|
+
|
|
84
|
+
**Why `user`, not `system` or a forged tool call.** A mid-stream `system`
|
|
85
|
+
message is what SGLang rejects (#377). A forged `compress` tool call would be
|
|
86
|
+
the "pure" carrier, but in proxy mode it requires fabricating an
|
|
87
|
+
assistant `tool_calls` + `user` `tool_result` pair by id, declaring the tool in
|
|
88
|
+
the request, and handling preflight blocks that have no authentic call — far
|
|
89
|
+
more invasive than re-voicing a standalone note. A `user` message is allowed
|
|
90
|
+
anywhere in the conversation, so it is the minimal change that satisfies both
|
|
91
|
+
SGLang's one-system rule and prefix-cache stability. The accepted trade-off:
|
|
92
|
+
a summary is a stand-in for the folded history, and re-voicing it as a user
|
|
93
|
+
turn is a semantic mismatch the model tolerates (it is clearly marked
|
|
94
|
+
`[Compressed conversation section]`).
|
|
95
|
+
|
|
96
|
+
**Do the two modes coexist?**
|
|
97
|
+
|
|
98
|
+
- **Same proxy instance: yes, by design.** One proxy serves plugin and plain
|
|
99
|
+
clients at once; `pluginMode` is decided per request (`x-bili-plugin` header)
|
|
100
|
+
and bound per session (`session.metadata.pluginAgent`). The launcher reuses a
|
|
101
|
+
running proxy.
|
|
102
|
+
- **Same session: the mode is sticky.** A session created in plugin mode stays
|
|
103
|
+
plugin mode (metadata inheritance); a plain session can only be *upgraded* to
|
|
104
|
+
plugin mode if a plugin request arrives with a matching conversation id (the
|
|
105
|
+
header outranks) — and never downgraded. In practice a plain→plugin upgrade
|
|
106
|
+
requires the plugin client's conversation id to match an existing plain
|
|
107
|
+
session id, which doesn't happen (each client generates its own id).
|
|
108
|
+
- **Cross-mode block hazard: theoretical only.** It would require the same
|
|
109
|
+
conversation id to span a mode switch. plugin→proxy is safe (the tool call is
|
|
110
|
+
in the shared history); proxy→plugin could orphan proxy-created block
|
|
111
|
+
summaries (their tool call isn't in the agent's history and the agent's view
|
|
112
|
+
skips `acp_summary`) — but that needs the id match above, which doesn't occur.
|
|
113
|
+
|
|
53
114
|
## Install
|
|
54
115
|
|
|
55
116
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -19050,7 +19050,7 @@ var require_response = __commonJS({
|
|
|
19050
19050
|
var assert = __require("assert");
|
|
19051
19051
|
var { isomorphicEncode, serializeJavascriptValueToJSONString } = require_infra();
|
|
19052
19052
|
var textEncoder = new TextEncoder("utf-8");
|
|
19053
|
-
var
|
|
19053
|
+
var Response2 = class _Response {
|
|
19054
19054
|
/** @type {Headers} */
|
|
19055
19055
|
#headers;
|
|
19056
19056
|
#state;
|
|
@@ -19222,13 +19222,13 @@ var require_response = __commonJS({
|
|
|
19222
19222
|
response.#state = newState;
|
|
19223
19223
|
}
|
|
19224
19224
|
};
|
|
19225
|
-
var { getResponseHeaders, setResponseHeaders, getResponseState, setResponseState } =
|
|
19226
|
-
Reflect.deleteProperty(
|
|
19227
|
-
Reflect.deleteProperty(
|
|
19228
|
-
Reflect.deleteProperty(
|
|
19229
|
-
Reflect.deleteProperty(
|
|
19230
|
-
mixinBody(
|
|
19231
|
-
Object.defineProperties(
|
|
19225
|
+
var { getResponseHeaders, setResponseHeaders, getResponseState, setResponseState } = Response2;
|
|
19226
|
+
Reflect.deleteProperty(Response2, "getResponseHeaders");
|
|
19227
|
+
Reflect.deleteProperty(Response2, "setResponseHeaders");
|
|
19228
|
+
Reflect.deleteProperty(Response2, "getResponseState");
|
|
19229
|
+
Reflect.deleteProperty(Response2, "setResponseState");
|
|
19230
|
+
mixinBody(Response2, getResponseState);
|
|
19231
|
+
Object.defineProperties(Response2.prototype, {
|
|
19232
19232
|
type: kEnumerableProperty,
|
|
19233
19233
|
url: kEnumerableProperty,
|
|
19234
19234
|
status: kEnumerableProperty,
|
|
@@ -19244,7 +19244,7 @@ var require_response = __commonJS({
|
|
|
19244
19244
|
configurable: true
|
|
19245
19245
|
}
|
|
19246
19246
|
});
|
|
19247
|
-
Object.defineProperties(
|
|
19247
|
+
Object.defineProperties(Response2, {
|
|
19248
19248
|
json: kEnumerableProperty,
|
|
19249
19249
|
redirect: kEnumerableProperty,
|
|
19250
19250
|
error: kEnumerableProperty
|
|
@@ -19377,7 +19377,7 @@ var require_response = __commonJS({
|
|
|
19377
19377
|
}
|
|
19378
19378
|
}
|
|
19379
19379
|
function fromInnerResponse(innerResponse, guard) {
|
|
19380
|
-
const response = new
|
|
19380
|
+
const response = new Response2(kConstruct);
|
|
19381
19381
|
setResponseState(response, innerResponse);
|
|
19382
19382
|
const headers = new Headers(kConstruct);
|
|
19383
19383
|
setResponseHeaders(response, headers);
|
|
@@ -19431,14 +19431,14 @@ var require_response = __commonJS({
|
|
|
19431
19431
|
converter: webidl.converters.HeadersInit
|
|
19432
19432
|
}
|
|
19433
19433
|
]);
|
|
19434
|
-
webidl.is.Response = webidl.util.MakeTypeAssertion(
|
|
19434
|
+
webidl.is.Response = webidl.util.MakeTypeAssertion(Response2);
|
|
19435
19435
|
module.exports = {
|
|
19436
19436
|
isNetworkError,
|
|
19437
19437
|
makeNetworkError,
|
|
19438
19438
|
makeResponse,
|
|
19439
19439
|
makeAppropriateNetworkError,
|
|
19440
19440
|
filterResponse,
|
|
19441
|
-
Response,
|
|
19441
|
+
Response: Response2,
|
|
19442
19442
|
cloneResponse,
|
|
19443
19443
|
fromInnerResponse,
|
|
19444
19444
|
getResponseState
|
|
@@ -48010,7 +48010,11 @@ var MAX_REQUEST_BYTES = 100 * 1024 * 1024;
|
|
|
48010
48010
|
var UPSTREAM_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
48011
48011
|
async function fetchWithTimeout(url, opts, timeoutMs = UPSTREAM_TIMEOUT_MS, externalSignal) {
|
|
48012
48012
|
const controller = new AbortController();
|
|
48013
|
-
|
|
48013
|
+
let timer2 = setTimeout(() => controller.abort(), timeoutMs);
|
|
48014
|
+
const rearm = () => {
|
|
48015
|
+
clearTimeout(timer2);
|
|
48016
|
+
timer2 = setTimeout(() => controller.abort(), timeoutMs);
|
|
48017
|
+
};
|
|
48014
48018
|
let onExternalAbort = null;
|
|
48015
48019
|
if (externalSignal) {
|
|
48016
48020
|
if (externalSignal.aborted) controller.abort();
|
|
@@ -48019,22 +48023,54 @@ async function fetchWithTimeout(url, opts, timeoutMs = UPSTREAM_TIMEOUT_MS, exte
|
|
|
48019
48023
|
externalSignal.addEventListener("abort", onExternalAbort, { once: true });
|
|
48020
48024
|
}
|
|
48021
48025
|
}
|
|
48026
|
+
const cleanup = () => {
|
|
48027
|
+
clearTimeout(timer2);
|
|
48028
|
+
if (onExternalAbort && externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
|
|
48029
|
+
};
|
|
48022
48030
|
try {
|
|
48023
48031
|
const finalOpts = { ...opts, signal: controller.signal };
|
|
48024
|
-
const
|
|
48025
|
-
|
|
48026
|
-
|
|
48027
|
-
|
|
48028
|
-
|
|
48029
|
-
|
|
48030
|
-
|
|
48031
|
-
|
|
48032
|
+
const raw = await fetch(url, finalOpts);
|
|
48033
|
+
if (raw.body) {
|
|
48034
|
+
const wrapped = armIdleBody(raw.body, rearm);
|
|
48035
|
+
return {
|
|
48036
|
+
response: new Response(wrapped, {
|
|
48037
|
+
status: raw.status,
|
|
48038
|
+
statusText: raw.statusText,
|
|
48039
|
+
headers: raw.headers
|
|
48040
|
+
}),
|
|
48041
|
+
clearTimer: cleanup
|
|
48042
|
+
};
|
|
48043
|
+
}
|
|
48044
|
+
return { response: raw, clearTimer: cleanup };
|
|
48032
48045
|
} catch (e) {
|
|
48033
|
-
|
|
48034
|
-
if (onExternalAbort && externalSignal) externalSignal.removeEventListener("abort", onExternalAbort);
|
|
48046
|
+
cleanup();
|
|
48035
48047
|
throw e;
|
|
48036
48048
|
}
|
|
48037
48049
|
}
|
|
48050
|
+
function armIdleBody(body, rearm) {
|
|
48051
|
+
const reader = body.getReader();
|
|
48052
|
+
return new ReadableStream({
|
|
48053
|
+
async pull(controller) {
|
|
48054
|
+
try {
|
|
48055
|
+
const result = await reader.read();
|
|
48056
|
+
if (result.done) {
|
|
48057
|
+
controller.close();
|
|
48058
|
+
return;
|
|
48059
|
+
}
|
|
48060
|
+
rearm();
|
|
48061
|
+
controller.enqueue(result.value);
|
|
48062
|
+
} catch (e) {
|
|
48063
|
+
controller.error(e);
|
|
48064
|
+
}
|
|
48065
|
+
},
|
|
48066
|
+
async cancel(reason) {
|
|
48067
|
+
try {
|
|
48068
|
+
await reader.cancel(reason);
|
|
48069
|
+
} catch {
|
|
48070
|
+
}
|
|
48071
|
+
}
|
|
48072
|
+
});
|
|
48073
|
+
}
|
|
48038
48074
|
var UpstreamHttpError = class extends Error {
|
|
48039
48075
|
status;
|
|
48040
48076
|
body;
|
|
@@ -51718,25 +51754,18 @@ function reserveOutputHeadroom(window2, maxOutput) {
|
|
|
51718
51754
|
}
|
|
51719
51755
|
return window2;
|
|
51720
51756
|
}
|
|
51721
|
-
function
|
|
51722
|
-
let
|
|
51723
|
-
|
|
51724
|
-
|
|
51725
|
-
|
|
51726
|
-
|
|
51727
|
-
|
|
51728
|
-
|
|
51729
|
-
|
|
51730
|
-
|
|
51731
|
-
|
|
51732
|
-
|
|
51733
|
-
const rest = [];
|
|
51734
|
-
for (let i = lead; i < messages.length; i++) {
|
|
51735
|
-
const r = messages[i].role;
|
|
51736
|
-
if (r === "system" || r === "developer") mid.push(messages[i]);
|
|
51737
|
-
else rest.push(messages[i]);
|
|
51738
|
-
}
|
|
51739
|
-
return [...head, ...mid, ...rest];
|
|
51757
|
+
function systemToUser(messages) {
|
|
51758
|
+
let hasSys = false;
|
|
51759
|
+
for (const m2 of messages) {
|
|
51760
|
+
if (m2.role === "system" || m2.role === "developer") {
|
|
51761
|
+
hasSys = true;
|
|
51762
|
+
break;
|
|
51763
|
+
}
|
|
51764
|
+
}
|
|
51765
|
+
if (!hasSys) return messages;
|
|
51766
|
+
return messages.map(
|
|
51767
|
+
(m2) => m2.role === "system" || m2.role === "developer" ? { ...m2, role: "user" } : m2
|
|
51768
|
+
);
|
|
51740
51769
|
}
|
|
51741
51770
|
function shouldReserveOutputHeadroom(protocol) {
|
|
51742
51771
|
return protocol !== "anthropic";
|
|
@@ -52330,6 +52359,20 @@ function rewriteContentChunk(parsed, content) {
|
|
|
52330
52359
|
|
|
52331
52360
|
`, "utf8");
|
|
52332
52361
|
}
|
|
52362
|
+
function stripFinishReasonChunk(buf) {
|
|
52363
|
+
const m2 = /^data: (.*)\n\n$/s.exec(buf.toString("utf8"));
|
|
52364
|
+
if (!m2) return buf;
|
|
52365
|
+
try {
|
|
52366
|
+
const parsed = JSON.parse(m2[1]);
|
|
52367
|
+
if (!Array.isArray(parsed.choices)) return buf;
|
|
52368
|
+
parsed.choices = parsed.choices.map((c) => ({ ...c, finish_reason: null }));
|
|
52369
|
+
return Buffer.from(`data: ${JSON.stringify(parsed)}
|
|
52370
|
+
|
|
52371
|
+
`, "utf8");
|
|
52372
|
+
} catch {
|
|
52373
|
+
return buf;
|
|
52374
|
+
}
|
|
52375
|
+
}
|
|
52333
52376
|
function createOpenaiAdapter(requestBody, clientSystem) {
|
|
52334
52377
|
const model = requestBody.model ?? "unknown";
|
|
52335
52378
|
let responseId = `chatcmpl-proxy-${Date.now()}`;
|
|
@@ -52399,7 +52442,7 @@ function createOpenaiAdapter(requestBody, clientSystem) {
|
|
|
52399
52442
|
);
|
|
52400
52443
|
return {
|
|
52401
52444
|
buildRequest(coreMessages, systemPrompt, body) {
|
|
52402
|
-
const messages =
|
|
52445
|
+
const messages = systemToUser(coreToOpenai(coreMessages));
|
|
52403
52446
|
const withSys = injectOpenaiSystem(messages, [clientSystem, systemPrompt].filter((p2) => typeof p2 === "string" && p2.length > 0));
|
|
52404
52447
|
return { ...body, messages: withSys };
|
|
52405
52448
|
},
|
|
@@ -52526,6 +52569,7 @@ function createOpenaiAdapter(requestBody, clientSystem) {
|
|
|
52526
52569
|
if (sawRealToolCall) {
|
|
52527
52570
|
yield { kind: "meta", chunk: rawBuf };
|
|
52528
52571
|
yield { kind: "done", finishReason, suppressCompletion: true };
|
|
52572
|
+
continue;
|
|
52529
52573
|
} else {
|
|
52530
52574
|
yield {
|
|
52531
52575
|
kind: "done",
|
|
@@ -52535,7 +52579,7 @@ function createOpenaiAdapter(requestBody, clientSystem) {
|
|
|
52535
52579
|
}
|
|
52536
52580
|
if (!delta) continue;
|
|
52537
52581
|
if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
|
|
52538
|
-
yield { kind: "reasoning", delta: delta.reasoning_content, raw: rawBuf };
|
|
52582
|
+
yield { kind: "reasoning", delta: delta.reasoning_content, raw: finishReason ? stripFinishReasonChunk(rawBuf) : rawBuf };
|
|
52539
52583
|
}
|
|
52540
52584
|
if (delta.tool_calls) {
|
|
52541
52585
|
const tcs = delta.tool_calls;
|
|
@@ -52570,10 +52614,10 @@ function createOpenaiAdapter(requestBody, clientSystem) {
|
|
|
52570
52614
|
const clean = tagFilter.push(delta.content);
|
|
52571
52615
|
if (clean.length > 0) {
|
|
52572
52616
|
const raw = clean === delta.content ? rawBuf : rewriteContentChunk(parsed, clean);
|
|
52573
|
-
yield { kind: "text", delta: clean, ...hasReasoning ? {} : { raw } };
|
|
52617
|
+
yield { kind: "text", delta: clean, ...hasReasoning ? {} : { raw: finishReason ? stripFinishReasonChunk(raw) : raw } };
|
|
52574
52618
|
}
|
|
52575
52619
|
} else if (!hasReasoning && (delta.role || Object.keys(delta).length === 0 && !finishReason)) {
|
|
52576
|
-
yield { kind: "meta", chunk: rawBuf, firstRoundOnly: true };
|
|
52620
|
+
yield { kind: "meta", chunk: finishReason ? stripFinishReasonChunk(rawBuf) : rawBuf, firstRoundOnly: true };
|
|
52577
52621
|
}
|
|
52578
52622
|
}
|
|
52579
52623
|
},
|
|
@@ -56593,7 +56637,7 @@ function prepareAnthropic(parsed, req, opts, core, config, prompts, log2, sessio
|
|
|
56593
56637
|
if (injectTools) {
|
|
56594
56638
|
toolsOut = injectTool(parsed.tools);
|
|
56595
56639
|
}
|
|
56596
|
-
if (opts.compress.injectNudge && turn.nudge?.shouldInject) {
|
|
56640
|
+
if (opts.compress.injectNudge && !pluginMode && turn.nudge?.shouldInject) {
|
|
56597
56641
|
try {
|
|
56598
56642
|
const rendered = renderNudgeText(turn.nudge, prompts);
|
|
56599
56643
|
if (rendered.text) {
|
|
@@ -56645,7 +56689,7 @@ function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session,
|
|
|
56645
56689
|
log2("info", diagNudge(turn, sessionId, tokenCount, config.modelContextLimit, parsed.model));
|
|
56646
56690
|
processedMessages = stripKernelSummaries(turn.messages, turn.state);
|
|
56647
56691
|
reapOrphanBlocks(session, msgs, deactivateBlock);
|
|
56648
|
-
rebuiltMessages =
|
|
56692
|
+
rebuiltMessages = systemToUser(coreToOpenai(processedMessages));
|
|
56649
56693
|
const sysParts = [];
|
|
56650
56694
|
if (systemText) sysParts.push(systemText);
|
|
56651
56695
|
if (shouldInject) sysParts.push(buildCompressSystemPrompt(prompts));
|
|
@@ -56653,7 +56697,7 @@ function prepareOpenai(parsed, req, opts, core, config, prompts, log2, session,
|
|
|
56653
56697
|
if (injectTools) {
|
|
56654
56698
|
toolsOut = injectOpenaiTool(parsed.tools);
|
|
56655
56699
|
}
|
|
56656
|
-
if (opts.compress.injectNudge && turn.nudge?.shouldInject && shouldInject) {
|
|
56700
|
+
if (opts.compress.injectNudge && !pluginMode && turn.nudge?.shouldInject && shouldInject) {
|
|
56657
56701
|
try {
|
|
56658
56702
|
const rendered = renderNudgeText(turn.nudge, prompts);
|
|
56659
56703
|
if (rendered.text) {
|
|
@@ -56747,7 +56791,7 @@ function prepareResponses(parsed, req, opts, core, config, prompts, log2, sessio
|
|
|
56747
56791
|
const devContent = [...projection.systemParts, ...forgedSummaries].join("\n\n---\n\n");
|
|
56748
56792
|
rebuiltInput = injectResponsesDeveloperMessage(rebuiltInput, devContent);
|
|
56749
56793
|
}
|
|
56750
|
-
if (opts.compress.injectNudge && turn.nudge?.shouldInject && shouldInject && !isCompactionTrigger) {
|
|
56794
|
+
if (opts.compress.injectNudge && !pluginMode && turn.nudge?.shouldInject && shouldInject && !isCompactionTrigger) {
|
|
56751
56795
|
try {
|
|
56752
56796
|
const rendered = renderNudgeText(turn.nudge, prompts);
|
|
56753
56797
|
if (rendered.text) {
|