billion-context-dsh 0.1.0 → 0.1.2
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.en.md +179 -0
- package/README.md +132 -67
- package/dist/index.js +144 -40
- package/dist/index.js.map +1 -1
- package/dist/nudge.d.ts +6 -0
- package/dist/region.d.ts +27 -6
- package/package.json +3 -2
- package/README.zh-CN.md +0 -116
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ var AcpStateStore = class {
|
|
|
28
28
|
|
|
29
29
|
// src/tools.ts
|
|
30
30
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
31
|
-
import {
|
|
31
|
+
import { defaultCountTokens as defaultCountTokens2 } from "acp-kernel";
|
|
32
32
|
|
|
33
33
|
// src/config.ts
|
|
34
34
|
import { defaultConfig } from "acp-kernel";
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
toolPairingBalancedBefore
|
|
54
54
|
} from "@deepseek-ai/dsh-compaction";
|
|
55
55
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
56
|
+
import { defaultCountTokens } from "acp-kernel";
|
|
56
57
|
|
|
57
58
|
// src/messages.ts
|
|
58
59
|
function extractText(content) {
|
|
@@ -176,28 +177,62 @@ function assertNoActiveCompaction(events) {
|
|
|
176
177
|
throw new Error("billion-context-dsh: another compaction is already active for this session");
|
|
177
178
|
}
|
|
178
179
|
}
|
|
180
|
+
function hasPlainRef(session, seq) {
|
|
181
|
+
const event = session.events[seq];
|
|
182
|
+
if (event === void 0) return false;
|
|
183
|
+
switch (event.type) {
|
|
184
|
+
case "user/message":
|
|
185
|
+
case "tool/result":
|
|
186
|
+
return extractEventText(event).trim().length > 0;
|
|
187
|
+
case "assistant/message": {
|
|
188
|
+
const content = event.data.message?.content;
|
|
189
|
+
const calls = Array.isArray(content) ? content.filter(
|
|
190
|
+
(block) => block !== null && typeof block === "object" && block.type === "tool-call"
|
|
191
|
+
) : [];
|
|
192
|
+
if (calls.length > 1) return false;
|
|
193
|
+
return calls.length === 1 || extractEventText(event).trim().length > 0;
|
|
194
|
+
}
|
|
195
|
+
default:
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
179
199
|
function resolveSurfaceRange(session, start, end) {
|
|
180
200
|
const nodes = session.surface.nodes;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (
|
|
201
|
+
const requestedStartIdx = nodes.indexOf(start);
|
|
202
|
+
const requestedEndIdx = nodes.indexOf(end);
|
|
203
|
+
if (requestedStartIdx < 0 || requestedEndIdx < 0) {
|
|
184
204
|
throw new Error(`billion-context-dsh: seq ${start}..${end} not in the current surface`);
|
|
185
205
|
}
|
|
186
|
-
if (
|
|
206
|
+
if (requestedStartIdx > requestedEndIdx) {
|
|
187
207
|
throw new Error(`billion-context-dsh: reversed range ${start}..${end}`);
|
|
188
208
|
}
|
|
189
|
-
|
|
209
|
+
const cleanBefore = (index) => toolPairingBalancedBefore(session, nodes[index]) && hasPlainRef(session, nodes[index]);
|
|
210
|
+
const cleanAfter = (index) => toolPairingBalancedAfter(session, nodes[index]) && hasPlainRef(session, nodes[index]);
|
|
211
|
+
let startIdx = requestedStartIdx;
|
|
212
|
+
let endIdx = requestedEndIdx;
|
|
213
|
+
while (startIdx <= endIdx && !cleanBefore(startIdx)) {
|
|
190
214
|
startIdx += 1;
|
|
191
215
|
}
|
|
192
|
-
while (endIdx >= startIdx && !
|
|
216
|
+
while (endIdx >= startIdx && !cleanAfter(endIdx)) {
|
|
193
217
|
endIdx -= 1;
|
|
194
218
|
}
|
|
195
|
-
if (startIdx
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
if (startIdx <= endIdx) {
|
|
220
|
+
return { start: nodes[startIdx], end: nodes[endIdx] };
|
|
221
|
+
}
|
|
222
|
+
startIdx = requestedStartIdx;
|
|
223
|
+
endIdx = requestedEndIdx;
|
|
224
|
+
while (startIdx > 0 && !cleanBefore(startIdx)) {
|
|
225
|
+
startIdx -= 1;
|
|
226
|
+
}
|
|
227
|
+
while (endIdx < nodes.length - 1 && !cleanAfter(endIdx)) {
|
|
228
|
+
endIdx += 1;
|
|
199
229
|
}
|
|
200
|
-
|
|
230
|
+
if (cleanBefore(startIdx) && cleanAfter(endIdx)) {
|
|
231
|
+
return { start: nodes[startIdx], end: nodes[endIdx] };
|
|
232
|
+
}
|
|
233
|
+
throw new Error(
|
|
234
|
+
`billion-context-dsh: no tool-pairing-balanced range around seq ${start}..${end} \u2014 narrow the range or consult acp_status for the current surface`
|
|
235
|
+
);
|
|
201
236
|
}
|
|
202
237
|
function shadowedSeqsOf(session, start, end) {
|
|
203
238
|
const nodes = session.surface.nodes;
|
|
@@ -236,17 +271,73 @@ function rebuildBlockLedger(events) {
|
|
|
236
271
|
for (const event of events) {
|
|
237
272
|
if (event.type !== "compaction/summary") continue;
|
|
238
273
|
const data = event.data;
|
|
274
|
+
let shadowedTokenCount = data.shadowedTokenCount;
|
|
275
|
+
if (shadowedTokenCount === 0) {
|
|
276
|
+
shadowedTokenCount = 0;
|
|
277
|
+
for (const seq of data.shadowedSeqs) {
|
|
278
|
+
const original = events[seq];
|
|
279
|
+
if (original !== void 0) shadowedTokenCount += defaultCountTokens(extractEventText(original));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
239
282
|
ledger.push({
|
|
240
283
|
blockId: data.compactionId,
|
|
241
284
|
summary: extractText(data.summary),
|
|
242
285
|
shadowedSeqs: [...data.shadowedSeqs],
|
|
243
|
-
shadowedTokenCount
|
|
286
|
+
shadowedTokenCount,
|
|
244
287
|
start: data.shadowedRange.start,
|
|
245
288
|
end: data.shadowedRange.end
|
|
246
289
|
});
|
|
247
290
|
}
|
|
248
291
|
return ledger;
|
|
249
292
|
}
|
|
293
|
+
function isCheckpointNode(event) {
|
|
294
|
+
if (event.type !== "user/message") return false;
|
|
295
|
+
const source = event.data.source;
|
|
296
|
+
return source?.plugin === "compact";
|
|
297
|
+
}
|
|
298
|
+
function buildCompressibleSeqRanges(session, opts = {}) {
|
|
299
|
+
const nodes = session.surface.nodes;
|
|
300
|
+
const preserve = opts.preserveRecent ?? 5;
|
|
301
|
+
const protectedSeqs = /* @__PURE__ */ new Set();
|
|
302
|
+
for (const seq of nodes.slice(-preserve)) protectedSeqs.add(seq);
|
|
303
|
+
for (let index = nodes.length - 1; index >= 0; index -= 1) {
|
|
304
|
+
const event = session.events[nodes[index]];
|
|
305
|
+
if (event?.type === "user/message" && !isCheckpointNode(event)) {
|
|
306
|
+
protectedSeqs.add(nodes[index]);
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const raw = [];
|
|
311
|
+
let cur = null;
|
|
312
|
+
const flush = () => {
|
|
313
|
+
if (cur !== null) raw.push(cur);
|
|
314
|
+
cur = null;
|
|
315
|
+
};
|
|
316
|
+
for (const seq of nodes) {
|
|
317
|
+
const event = session.events[seq];
|
|
318
|
+
if (event === void 0 || protectedSeqs.has(seq) || isCheckpointNode(event)) {
|
|
319
|
+
flush();
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
const tokens = defaultCountTokens(extractEventText(event));
|
|
323
|
+
if (cur === null) {
|
|
324
|
+
cur = { start: seq, end: seq, count: 1, tokens };
|
|
325
|
+
} else {
|
|
326
|
+
cur = { start: cur.start, end: seq, count: cur.count + 1, tokens: cur.tokens + tokens };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
flush();
|
|
330
|
+
const out = [];
|
|
331
|
+
for (const range of raw) {
|
|
332
|
+
try {
|
|
333
|
+
const { start, end } = resolveSurfaceRange(session, range.start, range.end);
|
|
334
|
+
const count = range.count;
|
|
335
|
+
out.push({ start, end, count, tokens: range.tokens });
|
|
336
|
+
} catch {
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return out.sort((a, b) => b.tokens - a.tokens);
|
|
340
|
+
}
|
|
250
341
|
|
|
251
342
|
// src/tools.ts
|
|
252
343
|
function textOutput() {
|
|
@@ -306,7 +397,7 @@ async function handleCompress(env, args, exec) {
|
|
|
306
397
|
const session = agent.session;
|
|
307
398
|
const state = env.store.stateFor(session);
|
|
308
399
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
309
|
-
const tokenCount = coreMessages.reduce((sum, message) => sum +
|
|
400
|
+
const tokenCount = coreMessages.reduce((sum, message) => sum + defaultCountTokens2(message.text ?? ""), 0);
|
|
310
401
|
const config = kernelConfigFor(env);
|
|
311
402
|
const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
312
403
|
env.store.set(session, turn.state);
|
|
@@ -314,16 +405,19 @@ async function handleCompress(env, args, exec) {
|
|
|
314
405
|
const ranges = args.content.map((range) => {
|
|
315
406
|
const startSeq = parseSeq(range.startSeq);
|
|
316
407
|
const endSeq = parseSeq(range.endSeq);
|
|
317
|
-
const
|
|
318
|
-
const
|
|
408
|
+
const { start, end } = resolveSurfaceRange(session, startSeq, endSeq);
|
|
409
|
+
const startRef = byRaw[String(start)];
|
|
410
|
+
const endRef = byRaw[String(end)];
|
|
319
411
|
if (startRef === void 0 || endRef === void 0) {
|
|
320
412
|
throw new Error(
|
|
321
|
-
`billion-context-dsh: seq ${
|
|
413
|
+
`billion-context-dsh: seq ${start}..${end} has no assigned ref \u2014 the range must be on the current surface (run acp_status for the live seq list)`
|
|
322
414
|
);
|
|
323
415
|
}
|
|
324
416
|
return {
|
|
325
417
|
startSeq,
|
|
326
418
|
endSeq,
|
|
419
|
+
start,
|
|
420
|
+
end,
|
|
327
421
|
startRef,
|
|
328
422
|
endRef,
|
|
329
423
|
summary: range.summary,
|
|
@@ -344,14 +438,19 @@ async function handleCompress(env, args, exec) {
|
|
|
344
438
|
for (let index = 0; index < ranges.length; index += 1) {
|
|
345
439
|
const range = ranges[index];
|
|
346
440
|
const original = args.content[index];
|
|
347
|
-
const { start, end } =
|
|
441
|
+
const { start, end } = range;
|
|
348
442
|
const shadowed = shadowedSeqsOf(session, start, end);
|
|
443
|
+
let shadowedTokens = 0;
|
|
444
|
+
for (const seq of shadowed) {
|
|
445
|
+
const event = session.events[seq];
|
|
446
|
+
if (event !== void 0) shadowedTokens += defaultCountTokens2(extractEventText(event));
|
|
447
|
+
}
|
|
349
448
|
const { compactionId } = runCompactionTransaction(session, {
|
|
350
449
|
start,
|
|
351
450
|
end,
|
|
352
451
|
shadowedSeqs: shadowed,
|
|
353
452
|
summary: [{ type: "text", text: original.summary }],
|
|
354
|
-
shadowedTokenCount:
|
|
453
|
+
shadowedTokenCount: shadowedTokens,
|
|
355
454
|
provider: agent.options.provider ?? "",
|
|
356
455
|
model: agent.options.model ?? ""
|
|
357
456
|
});
|
|
@@ -418,7 +517,7 @@ function handleStatus(env, _args, exec) {
|
|
|
418
517
|
const ledger = rebuildBlockLedger(session.events);
|
|
419
518
|
const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0);
|
|
420
519
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
421
|
-
const estimated = coreMessages.reduce((sum, message) => sum +
|
|
520
|
+
const estimated = coreMessages.reduce((sum, message) => sum + defaultCountTokens2(message.text ?? ""), 0);
|
|
422
521
|
const limit = env.modelContextLimit;
|
|
423
522
|
const lines = [
|
|
424
523
|
`ACP status \u2014 session ${session.id}`,
|
|
@@ -473,13 +572,13 @@ function makeTools(env) {
|
|
|
473
572
|
}
|
|
474
573
|
|
|
475
574
|
// src/commands.ts
|
|
476
|
-
import {
|
|
575
|
+
import { defaultCountTokens as defaultCountTokens3 } from "acp-kernel";
|
|
477
576
|
function statusText(env, agent) {
|
|
478
577
|
const session = agent.session;
|
|
479
578
|
const ledger = rebuildBlockLedger(session.events);
|
|
480
579
|
const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0);
|
|
481
580
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
482
|
-
const estimated = coreMessages.reduce((sum, message) => sum +
|
|
581
|
+
const estimated = coreMessages.reduce((sum, message) => sum + defaultCountTokens3(message.text ?? ""), 0);
|
|
483
582
|
const limit = env.modelContextLimit;
|
|
484
583
|
const lines = [
|
|
485
584
|
`ACP status \u2014 session ${session.id}`,
|
|
@@ -505,12 +604,17 @@ function compressText(env, agent, args) {
|
|
|
505
604
|
const session = agent.session;
|
|
506
605
|
const { start, end } = resolveSurfaceRange(session, startSeq, endSeq);
|
|
507
606
|
const shadowed = shadowedSeqsOf(session, startSeq, endSeq);
|
|
607
|
+
let shadowedTokens = 0;
|
|
608
|
+
for (const seq of shadowed) {
|
|
609
|
+
const event = session.events[seq];
|
|
610
|
+
if (event !== void 0) shadowedTokens += defaultCountTokens3(extractEventText(event));
|
|
611
|
+
}
|
|
508
612
|
const { compactionId } = runCompactionTransaction(session, {
|
|
509
613
|
start,
|
|
510
614
|
end,
|
|
511
615
|
shadowedSeqs: shadowed,
|
|
512
616
|
summary: [{ type: "text", text: summary }],
|
|
513
|
-
shadowedTokenCount:
|
|
617
|
+
shadowedTokenCount: shadowedTokens,
|
|
514
618
|
provider: agent.options.provider ?? "",
|
|
515
619
|
model: agent.options.model ?? ""
|
|
516
620
|
});
|
|
@@ -549,31 +653,31 @@ function acpCommand(env) {
|
|
|
549
653
|
|
|
550
654
|
// src/nudge.ts
|
|
551
655
|
import {
|
|
552
|
-
|
|
656
|
+
defaultCountTokens as defaultCountTokens4
|
|
553
657
|
} from "acp-kernel";
|
|
554
658
|
import { createUserMessage as createUserMessage2 } from "@deepseek-ai/dsh-llm";
|
|
555
|
-
function rangeTable(
|
|
556
|
-
const
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const endRaw = byRef[range.endRef];
|
|
560
|
-
if (startRaw === void 0 || endRaw === void 0) return null;
|
|
561
|
-
return ` - seq ${startRaw}..${endRaw} \u2014 ${range.count} messages, ~${range.tokens} tokens`;
|
|
562
|
-
});
|
|
563
|
-
const visible = lines.filter((line) => line !== null);
|
|
564
|
-
if (visible.length === 0) return "";
|
|
659
|
+
function rangeTable(session) {
|
|
660
|
+
const ranges = buildCompressibleSeqRanges(session).slice(0, 6);
|
|
661
|
+
if (ranges.length === 0) return "";
|
|
662
|
+
const lines = ranges.map((range) => ` - seq ${range.start}..${range.end} \u2014 ${range.count} messages, ~${range.tokens} tokens`);
|
|
565
663
|
return [
|
|
566
664
|
"",
|
|
567
665
|
"Compressible ranges (refs are surface seqs):",
|
|
568
|
-
...
|
|
666
|
+
...lines,
|
|
569
667
|
"Compress with: compress({ content: [{ startSeq, endSeq, summary }] })"
|
|
570
668
|
].join("\n");
|
|
571
669
|
}
|
|
670
|
+
function measuredTokenCount(agent, coreMessages) {
|
|
671
|
+
const meter = agent.ctx?.get?.("tokenMeter");
|
|
672
|
+
const surface = meter?.measure?.(agent.session)?.surfaceTokens;
|
|
673
|
+
if (typeof surface === "number" && surface > 0) return surface;
|
|
674
|
+
return coreMessages.reduce((sum, message) => sum + defaultCountTokens4(message.text ?? ""), 0);
|
|
675
|
+
}
|
|
572
676
|
function buildNudge(agent, env, lastNudgeTurn) {
|
|
573
677
|
const session = agent.session;
|
|
574
678
|
const state = env.store.stateFor(session);
|
|
575
679
|
const coreMessages = eventsToCoreMessages(surfaceEventsOf(session));
|
|
576
|
-
const tokenCount =
|
|
680
|
+
const tokenCount = measuredTokenCount(agent, coreMessages);
|
|
577
681
|
const config = kernelConfigFor(env);
|
|
578
682
|
const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount });
|
|
579
683
|
env.store.set(session, turn.state);
|
|
@@ -584,18 +688,18 @@ function buildNudge(agent, env, lastNudgeTurn) {
|
|
|
584
688
|
const alreadyShown = !emergency && lastNudgeTurn.get(session.id) === turnNumber;
|
|
585
689
|
if (alreadyShown) return null;
|
|
586
690
|
lastNudgeTurn.set(session.id, turnNumber);
|
|
587
|
-
const text = buildNudgeText(nudge, emergency,
|
|
691
|
+
const text = buildNudgeText(nudge, emergency, session);
|
|
588
692
|
const message = createUserMessage2({
|
|
589
693
|
content: [{ type: "text", text }],
|
|
590
694
|
source: { kind: "plugin", plugin: "acp-nudge" }
|
|
591
695
|
});
|
|
592
696
|
return { message, emergency };
|
|
593
697
|
}
|
|
594
|
-
function buildNudgeText(nudge, emergency,
|
|
595
|
-
const pct = Math.round(nudge.contextUsage * 100);
|
|
698
|
+
function buildNudgeText(nudge, emergency, session) {
|
|
699
|
+
const pct = Math.round(Math.min(nudge.contextUsage, 1) * 100);
|
|
596
700
|
const frame = emergency ? `\u26A0\uFE0F Context usage is at ${pct}% of the window \u2014 nearly full. Consider compressing consumed ranges soon so working context stays available; the choice and timing are yours.` : `Context usage is at ${pct}%. This is a suggestion, not a requirement \u2014 you decide whether and when to compress.`;
|
|
597
701
|
const guidance = "Compress by need, not by percentage: replace only ranges you have genuinely consumed, with dense self-contained summaries.";
|
|
598
|
-
return [frame, "", guidance, rangeTable(
|
|
702
|
+
return [frame, "", guidance, rangeTable(session)].join("\n");
|
|
599
703
|
}
|
|
600
704
|
|
|
601
705
|
// src/system-prompt.ts
|