billion-context-dsh 0.2.1 → 0.2.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 CHANGED
@@ -3,7 +3,7 @@
3
3
  [English](./README.en.md) | [中文](./README.md)
4
4
 
5
5
  > **⚠️ Beta notice — not for production use**
6
- > This project (**v0.2.1**) is a work-in-progress beta. The [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) itself is also in **public beta**. **Do not use either in engineering / production environments** — expect breaking changes and rough edges.
6
+ > This project (**v0.2.2**) is a work-in-progress beta. The [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) itself is also in **public beta**. **Do not use either in engineering / production environments** — expect breaking changes and rough edges.
7
7
 
8
8
  <p align="center">
9
9
  <strong>Built with gratitude on top of these projects</strong> — please give them a ⭐:
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [中文](./README.md) | [English](./README.en.md)
4
4
 
5
5
  > **⚠️ 测试版声明——请勿用于生产环境**
6
- > 本项目(**v0.2.1**)仍处于开发中的测试版。[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 本身也处于**公开测试版**阶段。**请勿将两者用于工程化 / 生产环境**——预期会有破坏性变更与粗糙之处。
6
+ > 本项目(**v0.2.2**)仍处于开发中的测试版。[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 本身也处于**公开测试版**阶段。**请勿将两者用于工程化 / 生产环境**——预期会有破坏性变更与粗糙之处。
7
7
 
8
8
  <p align="center">
9
9
  <strong>衷心感谢以下项目——请给它们一个 ⭐:</strong>
package/dist/index.d.ts CHANGED
@@ -40,7 +40,7 @@ export { makeTools, type ToolEnvironment } from './tools.ts';
40
40
  export { acpCommand } from './commands.ts';
41
41
  export { buildNudge, resolveTokenCount, type NudgeEnvironment, type NudgeOutcome } from './nudge.ts';
42
42
  export { DEFAULT_CONTEXT_WINDOW, detectContextWindow, windowSourceLabel, type AcpWindow, } from './window.ts';
43
- export { AlreadyCompressedRangeError, rebuildBlockLedger, resolveSurfaceRange, runCompactionTransaction, shadowedSeqsOf, findOpenTurn, assertNoActiveCompaction, blockRegistry, blockRefForSummarySeq, compactionIdsOfKernelBlocks, summarySeqOfKernelBlock, expandShadowedSeqs, type AcpBlockLedgerEntry, type CompactionTransactionInput, type ResolvedSurfaceRange, } from './region.ts';
43
+ export { AlreadyCompressedRangeError, rebuildBlockLedger, resolveSurfaceRange, runCompactionTransaction, shadowedSeqsOf, findOpenTurn, assertNoActiveCompaction, blockRegistry, blockRefForSummarySeq, compactionIdsOfKernelBlocks, summarySeqOfKernelBlock, expandShadowedSeqs, hideCompressToolPair, stripOrphanedSurfaceToolMessages, type AcpBlockLedgerEntry, type CompactionTransactionInput, type ResolvedSurfaceRange, } from './region.ts';
44
44
  export { eventsToCoreMessages, projectEvent, surfaceEventsOf, extractEventText } from './messages.ts';
45
45
  export interface AcpConfig {
46
46
  /**
@@ -108,6 +108,8 @@ export declare class AcpCompactionEngine extends CompactionEngine {
108
108
  /** Resolved prompt templates (validated at construction — fail-fast on template typos). */
109
109
  readonly prompts: ResolvedPrompts;
110
110
  private readonly lastNudgeTurn;
111
+ /** Successful compress call ids awaiting their tool/result so the pair can be hidden. */
112
+ private readonly compressCallIdsToHide;
111
113
  /** Per provider/model route the resolved window (probe failures cached too). */
112
114
  private readonly windowCache;
113
115
  constructor(ctx: Context, config?: Partial<AcpConfig>);
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  toolPairingBalancedAfter,
17
17
  toolPairingBalancedBefore
18
18
  } from "@deepseek-ai/dsh-compaction";
19
- import { createUserMessage } from "@deepseek-ai/dsh-llm";
19
+ import { createAssistantMessage, createUserMessage } from "@deepseek-ai/dsh-llm";
20
20
  import { defaultCountTokens } from "acp-kernel";
21
21
 
22
22
  // src/messages.ts
@@ -347,7 +347,198 @@ function isCheckpointNode(event) {
347
347
  const source = event.data.source;
348
348
  return source?.plugin === "compact";
349
349
  }
350
+ function toolCallIdsOfEvent(event) {
351
+ if (event.type !== "assistant/message") return [];
352
+ const content = event.data.message?.content;
353
+ if (!Array.isArray(content)) return [];
354
+ const ids = [];
355
+ for (const block of content) {
356
+ if (block === null || typeof block !== "object") continue;
357
+ const b = block;
358
+ if (b.type === "tool-call" && typeof b.id === "string") ids.push(b.id);
359
+ }
360
+ return ids;
361
+ }
362
+ function toolCallIdOfResultEvent(event) {
363
+ if (event.type !== "tool/result") return null;
364
+ const message = event.data.message;
365
+ const block = Array.isArray(message?.content) ? message.content.find((candidate) => candidate?.type === "tool-result") : void 0;
366
+ const id = block?.toolCallId ?? message?.source?.callId;
367
+ return typeof id === "string" ? id : null;
368
+ }
369
+ function assistantProviderModel(event) {
370
+ if (event.type === "assistant/message") {
371
+ const message = event.data.message;
372
+ return {
373
+ provider: typeof message?.source?.provider === "string" ? message.source.provider : "billion-context-dsh",
374
+ model: typeof message?.source?.model === "string" ? message.source.model : "surface-prune"
375
+ };
376
+ }
377
+ return { provider: "billion-context-dsh", model: "surface-prune" };
378
+ }
379
+ function hideSurfaceSeqs(session, seqs, provider, model, text) {
380
+ if (seqs.length === 0) return;
381
+ const start = seqs[0];
382
+ const end = seqs[seqs.length - 1];
383
+ let shadowedTokenCount = 0;
384
+ for (const seq of seqs) {
385
+ const event = session.events[seq];
386
+ if (event !== void 0) shadowedTokenCount += defaultCountTokens(extractEventText(event));
387
+ }
388
+ session.append("compaction/prune", {
389
+ shadowedRange: { start, end },
390
+ shadowedSeqs: [...seqs],
391
+ shadowedTokenCount
392
+ });
393
+ if (text !== void 0) {
394
+ session.append("user/message", createUserMessage({
395
+ content: [{ type: "text", text }],
396
+ source: { kind: "plugin", plugin: "billion-context-dsh" }
397
+ }), {
398
+ surfaceOp: { op: "replace", start, end },
399
+ sourceEventSeqs: [...seqs]
400
+ });
401
+ return;
402
+ }
403
+ session.append("assistant/message", {
404
+ turn: findOpenTurn(session.events) ?? 0,
405
+ step: 0,
406
+ message: createAssistantMessage({ content: [], source: { provider, model } })
407
+ }, {
408
+ surfaceOp: { op: "replace", start, end },
409
+ sourceEventSeqs: [...seqs]
410
+ });
411
+ }
412
+ function hideCompressToolPair(session, callId, resultSeq) {
413
+ let callSeq = null;
414
+ for (const event of session.events) {
415
+ if (event.type !== "assistant/message") continue;
416
+ if (toolCallIdsOfEvent(event).includes(callId)) {
417
+ callSeq = event.seq;
418
+ break;
419
+ }
420
+ }
421
+ if (callSeq === null) return false;
422
+ const callNodeIds = toolCallIdsOfEvent(session.events[callSeq]);
423
+ if (callNodeIds.length !== 1 || callNodeIds[0] !== callId) return false;
424
+ let resolvedResultSeq = resultSeq ?? null;
425
+ if (resolvedResultSeq === null) {
426
+ for (const event of session.events) {
427
+ if (event.type === "tool/result" && toolCallIdOfResultEvent(event) === callId) {
428
+ resolvedResultSeq = event.seq;
429
+ break;
430
+ }
431
+ }
432
+ }
433
+ if (resolvedResultSeq === null) return false;
434
+ const nodes = session.surface.nodes;
435
+ const startIdx = nodes.indexOf(callSeq);
436
+ const endIdx = nodes.indexOf(resolvedResultSeq);
437
+ if (startIdx < 0 || endIdx < 0 || endIdx - startIdx !== 1) return false;
438
+ const { provider, model } = assistantProviderModel(session.events[callSeq]);
439
+ const resultEvent = session.events[resolvedResultSeq];
440
+ const resultText = resultEvent === void 0 ? "" : extractEventText(resultEvent);
441
+ hideSurfaceSeqs(session, [callSeq, resolvedResultSeq], provider, model, resultText.trim().length > 0 ? resultText : void 0);
442
+ return true;
443
+ }
444
+ function stripOrphanedSurfaceToolMessages(session, inFlightCallIds = /* @__PURE__ */ new Set()) {
445
+ const nodes = session.surface.nodes;
446
+ const callIdsBySeq = /* @__PURE__ */ new Map();
447
+ const open = /* @__PURE__ */ new Map();
448
+ const orphanResultSeqs = [];
449
+ const brokenResults = /* @__PURE__ */ new Map();
450
+ for (let index = 0; index < nodes.length; index += 1) {
451
+ const seq = nodes[index];
452
+ const event = session.events[seq];
453
+ if (event === void 0) continue;
454
+ if (event.type === "assistant/message") {
455
+ const ids = toolCallIdsOfEvent(event);
456
+ if (ids.length === 0) continue;
457
+ callIdsBySeq.set(seq, ids);
458
+ for (const id of ids) {
459
+ if (!open.has(id)) open.set(id, { seq, index });
460
+ }
461
+ } else if (event.type === "tool/result") {
462
+ const id = toolCallIdOfResultEvent(event);
463
+ if (id === null) continue;
464
+ const call = open.get(id);
465
+ if (call === void 0) {
466
+ orphanResultSeqs.push(seq);
467
+ continue;
468
+ }
469
+ const callNodeIds = callIdsBySeq.get(call.seq);
470
+ let adjacent = false;
471
+ if (callNodeIds !== void 0) {
472
+ adjacent = true;
473
+ for (let mid = call.index + 1; mid < index; mid += 1) {
474
+ const midEvent = session.events[nodes[mid]];
475
+ if (midEvent === void 0 || midEvent.type !== "tool/result") {
476
+ adjacent = false;
477
+ break;
478
+ }
479
+ const midId = toolCallIdOfResultEvent(midEvent);
480
+ if (midId === null || !callNodeIds.includes(midId)) {
481
+ adjacent = false;
482
+ break;
483
+ }
484
+ }
485
+ }
486
+ open.delete(id);
487
+ if (!adjacent) brokenResults.set(seq, call.seq);
488
+ }
489
+ }
490
+ const brokenIdsByCallSeq = /* @__PURE__ */ new Map();
491
+ for (const [resultSeq, callSeq] of brokenResults) {
492
+ const id = toolCallIdOfResultEvent(session.events[resultSeq]);
493
+ if (id !== null) {
494
+ const list = brokenIdsByCallSeq.get(callSeq) ?? [];
495
+ list.push(id);
496
+ brokenIdsByCallSeq.set(callSeq, list);
497
+ }
498
+ }
499
+ const hiddenSet = new Set(orphanResultSeqs);
500
+ for (const resultSeq of brokenResults.keys()) hiddenSet.add(resultSeq);
501
+ for (const [callSeq, ids] of callIdsBySeq) {
502
+ const brokenIds = brokenIdsByCallSeq.get(callSeq);
503
+ const allUnpaired = !ids.some((candidate) => inFlightCallIds.has(candidate)) && ids.every((candidate) => open.has(candidate) || brokenIds?.includes(candidate) === true);
504
+ if (allUnpaired) hiddenSet.add(callSeq);
505
+ }
506
+ const hidden = [...hiddenSet].sort((a, b) => a - b);
507
+ let count = 0;
508
+ for (const seq of hidden) {
509
+ const event = session.events[seq];
510
+ if (event === void 0) continue;
511
+ const { provider, model } = assistantProviderModel(event);
512
+ hideSurfaceSeqs(session, [seq], provider, model);
513
+ count += 1;
514
+ }
515
+ return count;
516
+ }
517
+ function openToolCallIds(session) {
518
+ const open = /* @__PURE__ */ new Set();
519
+ for (const seq of session.surface.nodes) {
520
+ const event = session.events[seq];
521
+ if (event === void 0) continue;
522
+ if (event.type === "assistant/message") {
523
+ for (const id of toolCallIdsOfEvent(event)) open.add(id);
524
+ } else if (event.type === "tool/result") {
525
+ const id = toolCallIdOfResultEvent(event);
526
+ if (id !== null) open.delete(id);
527
+ }
528
+ }
529
+ return open;
530
+ }
531
+ function deferCompressPairHide(session, callId, resultSeq, onError) {
532
+ queueMicrotask(() => {
533
+ try {
534
+ hideCompressToolPair(session, callId, resultSeq);
535
+ } catch (error) {
536
+ onError?.(error);
537
+ }
538
+ });
539
+ }
350
540
  function buildCompressibleSeqRanges(session, opts = {}) {
541
+ stripOrphanedSurfaceToolMessages(session);
351
542
  const nodes = session.surface.nodes;
352
543
  const preserve = opts.preserveRecent ?? 5;
353
544
  const protectedSeqs = /* @__PURE__ */ new Set();
@@ -989,6 +1180,7 @@ function unwrapCompressArgs(args) {
989
1180
  async function handleCompress(env, args, exec) {
990
1181
  const agent = requireAgent(exec);
991
1182
  const session = agent.session;
1183
+ stripOrphanedSurfaceToolMessages(session, openToolCallIds(session));
992
1184
  const state = env.store.stateFor(session);
993
1185
  const coreMessages = allLogMessages(session);
994
1186
  const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session));
@@ -1059,10 +1251,13 @@ async function handleCompress(env, args, exec) {
1059
1251
  // non-block-covered messages as the visible feed, so default behavior is
1060
1252
  // preserved. Any 'Excluded N protected message(s)' warning is surfaced.
1061
1253
  });
1062
- if (applied.result.errors.length > 0) {
1254
+ if (applied.result.errors.length > 0 && applied.result.blocksCreated === 0) {
1063
1255
  return { text: `compress failed: ${applied.result.errors.join("; ")}` };
1064
1256
  }
1065
1257
  env.store.set(session, applied.state);
1258
+ if (applied.result.blocksCreated > 0) {
1259
+ env.compressCallIdsToHide?.add(exec.callId);
1260
+ }
1066
1261
  const previousIds = new Set(turn.state.blocks.map((block) => block.blockId));
1067
1262
  const newBlocks = applied.state.blocks.filter((block) => !previousIds.has(block.blockId));
1068
1263
  const blockByRangeKey = new Map(newBlocks.map((block) => [`${block.startRef}::${block.endRef}`, block]));
@@ -1125,8 +1320,9 @@ async function handleCompress(env, args, exec) {
1125
1320
  }
1126
1321
  const summaryLine = `Compressed ${applied.result.blocksCreated} block(s), ~${applied.result.tokensCompressed} tokens reclaimed.`;
1127
1322
  const totalSkipped = skippedRanges + alreadyCompressedNotes.length;
1128
- const warningLines = [...freeWarnings.map((warning) => ` ${warning}`), ...alreadyCompressedNotes, ...lines];
1129
- const footer = totalSkipped > 0 ? ` (${totalSkipped} range(s) skipped \u2014 see warnings above)` : "";
1323
+ const failedLines = applied.result.errors.map((error) => ` ${error}`);
1324
+ const warningLines = [...freeWarnings.map((warning) => ` ${warning}`), ...failedLines, ...alreadyCompressedNotes, ...lines];
1325
+ const footer = totalSkipped > 0 ? ` (${totalSkipped} range(s) skipped or failed \u2014 see above)` : "";
1130
1326
  return { text: `${summaryLine}
1131
1327
  ${[...warningLines, footer].filter((line) => line !== "").join("\n")}` };
1132
1328
  }
@@ -1360,6 +1556,8 @@ var AcpCompactionEngine = class extends CompactionEngine {
1360
1556
  /** Resolved prompt templates (validated at construction — fail-fast on template typos). */
1361
1557
  prompts;
1362
1558
  lastNudgeTurn = /* @__PURE__ */ new Map();
1559
+ /** Successful compress call ids awaiting their tool/result so the pair can be hidden. */
1560
+ compressCallIdsToHide = /* @__PURE__ */ new Set();
1363
1561
  /** Per provider/model route the resolved window (probe failures cached too). */
1364
1562
  windowCache = /* @__PURE__ */ new Map();
1365
1563
  constructor(ctx, config = {}) {
@@ -1379,7 +1577,8 @@ var AcpCompactionEngine = class extends CompactionEngine {
1379
1577
  nudgeEmergencyThresholdPct: this.config.nudgeEmergencyThresholdPct,
1380
1578
  coreOverrides: this.config.coreOverrides,
1381
1579
  windowFor: (agent) => this.windowFor(agent),
1382
- prompts: this.prompts
1580
+ prompts: this.prompts,
1581
+ compressCallIdsToHide: this.compressCallIdsToHide
1383
1582
  };
1384
1583
  const tools = ctx.get("tools");
1385
1584
  if (tools !== void 0) {
@@ -1413,16 +1612,27 @@ var AcpCompactionEngine = class extends CompactionEngine {
1413
1612
  if (name === "commands") registerCommand();
1414
1613
  });
1415
1614
  }
1416
- if (this.config.autoNudge) {
1417
- ctx.on("agent/pre-step", async (payload, next) => {
1418
- const decision = await next();
1419
- if (decision.kind === "reject") return decision;
1420
- const window = await this.windowFor(payload.agent);
1421
- const outcome = buildNudge(payload.agent, { ...env, modelContextLimit: window.limit }, this.lastNudgeTurn);
1422
- if (outcome === null) return decision;
1423
- return { kind: "enter", messages: [...decision.messages, outcome.message] };
1615
+ ctx.on("session/event", (session, event) => {
1616
+ if (event.type !== "tool/result") return;
1617
+ const message = event.data.message;
1618
+ const block = message.content[0];
1619
+ const callId = block?.toolCallId ?? message.source.callId;
1620
+ if (typeof callId !== "string" || !this.compressCallIdsToHide.has(callId)) return;
1621
+ this.compressCallIdsToHide.delete(callId);
1622
+ deferCompressPairHide(session, callId, event.seq, (error) => {
1623
+ ctx.logger.warn(`billion-context-dsh: hide compress call/result pair failed: ${String(error)}`);
1424
1624
  });
1425
- }
1625
+ });
1626
+ ctx.on("agent/pre-step", async (payload, next) => {
1627
+ stripOrphanedSurfaceToolMessages(payload.agent.session);
1628
+ if (!this.config.autoNudge) return next();
1629
+ const decision = await next();
1630
+ if (decision.kind === "reject") return decision;
1631
+ const window = await this.windowFor(payload.agent);
1632
+ const outcome = buildNudge(payload.agent, { ...env, modelContextLimit: window.limit }, this.lastNudgeTurn);
1633
+ if (outcome === null) return decision;
1634
+ return { kind: "enter", messages: [...decision.messages, outcome.message] };
1635
+ });
1426
1636
  const systemPrompt = ctx.get("systemPrompt");
1427
1637
  if (systemPrompt !== void 0) {
1428
1638
  systemPrompt.section({
@@ -1519,6 +1729,7 @@ export {
1519
1729
  expandShadowedSeqs,
1520
1730
  extractEventText,
1521
1731
  findOpenTurn,
1732
+ hideCompressToolPair,
1522
1733
  kernelConfigFor,
1523
1734
  makeTools,
1524
1735
  projectEvent,
@@ -1531,6 +1742,7 @@ export {
1531
1742
  resolveTokenCount,
1532
1743
  runCompactionTransaction,
1533
1744
  shadowedSeqsOf,
1745
+ stripOrphanedSurfaceToolMessages,
1534
1746
  summarySeqOfKernelBlock,
1535
1747
  surfaceEventsOf,
1536
1748
  windowSourceLabel