billion-context-pi 0.1.30 → 0.1.32

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/dist/index.js CHANGED
@@ -463,14 +463,49 @@ function truncateLargeToolOutputs(messages, tokenCount, config, countTokens, opt
463
463
  return { messages: updated, truncatedCount, savedTokens };
464
464
  }
465
465
  var KEEP_LAST_ORPHANED = 0;
466
+ function rangeKey(startRef, endRef) {
467
+ return `${startRef}::${endRef}`;
468
+ }
469
+ function rewriteCompressText(text, liveKeys) {
470
+ let parsed;
471
+ try {
472
+ parsed = JSON.parse(text ?? "");
473
+ } catch {
474
+ return null;
475
+ }
476
+ if (!parsed || typeof parsed !== "object") return null;
477
+ const obj = parsed;
478
+ const content = obj.content;
479
+ if (!Array.isArray(content) || content.length === 0) return null;
480
+ const kept = content.filter((entry) => {
481
+ if (!entry || typeof entry !== "object") return false;
482
+ const s = typeof entry.startId === "string" ? entry.startId : typeof entry.messageId === "string" ? entry.messageId : "";
483
+ const e = typeof entry.endId === "string" ? entry.endId : typeof entry.messageId === "string" ? entry.messageId : "";
484
+ return liveKeys.has(rangeKey(s, e));
485
+ });
486
+ if (kept.length === content.length || kept.length === 0) return null;
487
+ return JSON.stringify({ ...obj, content: kept });
488
+ }
466
489
  function hideConsumedCompressCalls(state, messages) {
467
- const activeCallIds = /* @__PURE__ */ new Set();
468
490
  const allBlockCallIds = /* @__PURE__ */ new Set();
491
+ const activeCallIds = /* @__PURE__ */ new Set();
492
+ const liveRangeKeysByCallId = /* @__PURE__ */ new Map();
493
+ const legacyLiveByCallId = /* @__PURE__ */ new Set();
469
494
  for (const block of state.blocks) {
470
- if (block.compressCallId) {
471
- allBlockCallIds.add(block.compressCallId);
472
- if (block.active) activeCallIds.add(block.compressCallId);
495
+ if (!block.compressCallId) continue;
496
+ allBlockCallIds.add(block.compressCallId);
497
+ if (!block.active) continue;
498
+ activeCallIds.add(block.compressCallId);
499
+ if (block.startRef === void 0 || block.endRef === void 0) {
500
+ legacyLiveByCallId.add(block.compressCallId);
501
+ continue;
502
+ }
503
+ let keys = liveRangeKeysByCallId.get(block.compressCallId);
504
+ if (!keys) {
505
+ keys = /* @__PURE__ */ new Set();
506
+ liveRangeKeysByCallId.set(block.compressCallId, keys);
473
507
  }
508
+ keys.add(rangeKey(block.startRef, block.endRef));
474
509
  }
475
510
  const lastOrphanedCallIds = [];
476
511
  for (let i = messages.length - 1; i >= 0 && lastOrphanedCallIds.length < KEEP_LAST_ORPHANED; i--) {
@@ -499,6 +534,16 @@ function hideConsumedCompressCalls(state, messages) {
499
534
  hidden++;
500
535
  continue;
501
536
  }
537
+ if (message.toolName === "compress" && message.contentType === "tool-call" && message.toolCallId && keepCallIds.has(message.toolCallId)) {
538
+ const liveKeys = liveRangeKeysByCallId.get(message.toolCallId);
539
+ if (liveKeys && liveKeys.size > 0 && !legacyLiveByCallId.has(message.toolCallId)) {
540
+ const rewritten = rewriteCompressText(message.text, liveKeys);
541
+ if (rewritten !== null) {
542
+ result.push({ ...message, text: rewritten });
543
+ continue;
544
+ }
545
+ }
546
+ }
502
547
  result.push(message);
503
548
  }
504
549
  return { messages: result, hidden };
@@ -732,8 +777,8 @@ function refNum(ref) {
732
777
  const n = parseInt(ref.slice(1), 10);
733
778
  return Number.isNaN(n) ? -1 : n;
734
779
  }
735
- function estimateMessageTokens(message) {
736
- return Math.ceil((message.text ?? "").length / 4);
780
+ function estimateTextTokens(text) {
781
+ return Math.ceil(text.length / 4);
737
782
  }
738
783
  function isToolMessage(message) {
739
784
  return message.contentType === "tool-call" || message.contentType === "tool-result";
@@ -745,7 +790,7 @@ function isSyntheticOrPruned(message, state) {
745
790
  }
746
791
  return false;
747
792
  }
748
- function computeProtectedRefs(messages, state, config) {
793
+ function computeProtectedRefs(messages, state, config, countTokens = estimateTextTokens) {
749
794
  const preserveN = config.preserveRecentMessages;
750
795
  const preserveTokens = config.preserveRecentTokens;
751
796
  const result = /* @__PURE__ */ new Set();
@@ -755,7 +800,7 @@ function computeProtectedRefs(messages, state, config) {
755
800
  if (isNeverPreserveRecent(msg)) continue;
756
801
  const ref = state.messageRefs.byRaw[msg.id];
757
802
  if (!ref || ref === "BLOCKED") continue;
758
- visible.push({ ref, tokens: estimateMessageTokens(msg) });
803
+ visible.push({ ref, tokens: countTokens(msg.text ?? "") });
759
804
  }
760
805
  if (preserveN > 0) {
761
806
  for (const m of visible.slice(-preserveN)) {
@@ -780,7 +825,7 @@ function computeProtectedRefs(messages, state, config) {
780
825
  }
781
826
  return result;
782
827
  }
783
- function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
828
+ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, countTokens = estimateTextTokens) {
784
829
  const compressibleMsgs = [];
785
830
  const protectedMsgs = [];
786
831
  const protectedCallIds = collectProtectedToolCallIds(messages, config);
@@ -793,7 +838,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
793
838
  protectedMsgs.push({
794
839
  ref,
795
840
  refNum: rn,
796
- tokens: estimateMessageTokens(msg),
841
+ tokens: countTokens(msg.text ?? ""),
797
842
  tools: msg.toolName ? [msg.toolName] : []
798
843
  });
799
844
  continue;
@@ -804,7 +849,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs) {
804
849
  compressibleMsgs.push({
805
850
  ref,
806
851
  refNum: rn,
807
- tokens: estimateMessageTokens(msg),
852
+ tokens: countTokens(msg.text ?? ""),
808
853
  isTool: isToolMessage(msg),
809
854
  isUser: msg.role === "user"
810
855
  });
@@ -891,7 +936,7 @@ function createCore(ports = {}) {
891
936
  let tokensCompressed = 0;
892
937
  const errors = [];
893
938
  const warnings = [];
894
- const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(input.messages, input.state, input.config);
939
+ const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(input.messages, input.state, input.config, countTokens);
895
940
  const preExistingCoverage = collectCoverage(state);
896
941
  const rangeIndexSets = [];
897
942
  for (const spec of input.ranges) {
@@ -916,29 +961,25 @@ function createCore(ports = {}) {
916
961
  const bMin = b.indices.length > 0 ? Math.min(...b.indices) : Infinity;
917
962
  return aMin - bMin;
918
963
  });
919
- for (let i = 1; i < sortedRanges.length; i++) {
920
- const prev = sortedRanges[i - 1];
921
- const curr = sortedRanges[i];
922
- const prevMax = prev.indices.length > 0 ? Math.max(...prev.indices) : -1;
923
- const currMin = curr.indices.length > 0 ? Math.min(...curr.indices) : -1;
924
- if (prevMax >= currMin && prevMax >= 0) {
925
- return {
926
- state: input.state,
927
- result: {
928
- blocksCreated: 0,
929
- tokensCompressed: 0,
930
- errors: [
931
- `content: range (${prev.spec.startRef}..${prev.spec.endRef}) overlaps (${curr.spec.startRef}..${curr.spec.endRef}). Overlapping ranges cannot be compressed in the same batch.`
932
- ],
933
- warnings: []
934
- }
935
- };
964
+ const skipSpecs = /* @__PURE__ */ new Set();
965
+ let acceptedMaxIndex = -1;
966
+ for (const entry of sortedRanges) {
967
+ const entryMax = entry.indices.length > 0 ? Math.max(...entry.indices) : -1;
968
+ const entryMin = entry.indices.length > 0 ? Math.min(...entry.indices) : -1;
969
+ if (entryMin >= 0 && entryMin <= acceptedMaxIndex) {
970
+ skipSpecs.add(entry.spec);
971
+ warnings.push(
972
+ `Skipped range (${entry.spec.startRef}..${entry.spec.endRef}) \u2014 overlaps an earlier range in the batch; the earlier range takes precedence. Keep ranges disjoint.`
973
+ );
974
+ continue;
936
975
  }
976
+ if (entryMax > acceptedMaxIndex) acceptedMaxIndex = entryMax;
937
977
  }
938
978
  if (input.config.compress.minCompressRange > 0 && input.ranges.length > 0) {
939
979
  let totalRangeChars = 0;
940
980
  let hasBlockBoundaryRange = false;
941
981
  for (const spec of input.ranges) {
982
+ if (skipSpecs.has(spec)) continue;
942
983
  let resolved;
943
984
  try {
944
985
  resolved = resolveBoundaries({
@@ -974,6 +1015,7 @@ function createCore(ports = {}) {
974
1015
  }
975
1016
  }
976
1017
  for (const spec of input.ranges) {
1018
+ if (skipSpecs.has(spec)) continue;
977
1019
  try {
978
1020
  const outcome = applySingleRange({
979
1021
  spec,
@@ -1114,13 +1156,15 @@ var recommendNode = {
1114
1156
  const protectedRefs = computeProtectedRefs(
1115
1157
  io.messages,
1116
1158
  io.state,
1117
- ctx.config
1159
+ ctx.config,
1160
+ ctx.countTokens
1118
1161
  );
1119
1162
  const contextRanges = buildCompressibleRanges(
1120
1163
  io.messages,
1121
1164
  io.state,
1122
1165
  ctx.config,
1123
- protectedRefs
1166
+ protectedRefs,
1167
+ ctx.countTokens
1124
1168
  );
1125
1169
  const nothingToCompress = contextRanges.compressible.length === 0;
1126
1170
  const recommendation = {
@@ -1298,7 +1342,9 @@ function applySingleRange(input) {
1298
1342
  survivedCount: 0,
1299
1343
  generation: "young",
1300
1344
  active: true,
1301
- compressCallId: input.spec.compressCallId
1345
+ compressCallId: input.spec.compressCallId,
1346
+ startRef: input.spec.startRef,
1347
+ endRef: input.spec.endRef
1302
1348
  };
1303
1349
  input.state.blocks.push(block);
1304
1350
  for (const consumedId of consumedBlockIds) {
@@ -1789,7 +1835,7 @@ function renderNudgeText(decision) {
1789
1835
  breakdownStr,
1790
1836
  "",
1791
1837
  `[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`,
1792
- isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries.`,
1838
+ isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,
1793
1839
  blockList,
1794
1840
  `Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
1795
1841
  "",
@@ -2537,6 +2583,15 @@ function patchRefTag(original, core) {
2537
2583
  if (!tag) return original;
2538
2584
  const base = original;
2539
2585
  if (base.role === "assistant") return original;
2586
+ const tagCore = tag.replace(/\s+$/, "");
2587
+ let bodyStart = tagCore.length;
2588
+ if (core.text && core.text.charAt(bodyStart) === "\n") bodyStart += 1;
2589
+ const coreBody = core.text ? core.text.slice(bodyStart) : "";
2590
+ const originalBody = extractText(base.content);
2591
+ const trimEnd = (s) => s.replace(/\s+$/, "");
2592
+ if (coreBody && trimEnd(coreBody) !== trimEnd(originalBody)) {
2593
+ return rebuildBodyFromCore(original, coreBody, tag);
2594
+ }
2540
2595
  const rawBlocks = Array.isArray(base.content) ? base.content : typeof base.content === "string" ? [{ type: "text", text: base.content }] : [];
2541
2596
  const peeled = peelRefTagBlocks(rawBlocks);
2542
2597
  const newBlocks = [...peeled];
@@ -2560,6 +2615,23 @@ ${tag}` : tag };
2560
2615
  content: [...peeled, { type: "text", text: tag }]
2561
2616
  };
2562
2617
  }
2618
+ function rebuildBodyFromCore(original, coreBody, tag) {
2619
+ const base = original;
2620
+ const text = `${coreBody.replace(/\s+$/, "")}
2621
+
2622
+ ${tag}`;
2623
+ if (typeof base.content === "string") {
2624
+ return { ...original, content: text };
2625
+ }
2626
+ if (Array.isArray(base.content)) {
2627
+ const nonText = base.content.filter((b) => b.type !== "text");
2628
+ return {
2629
+ ...original,
2630
+ content: [...nonText, { type: "text", text }]
2631
+ };
2632
+ }
2633
+ return { ...original, content: [{ type: "text", text }] };
2634
+ }
2563
2635
  function peelRefTagBlocks(blocks) {
2564
2636
  const out = [];
2565
2637
  for (const block of blocks) {
@@ -8718,7 +8790,7 @@ async function statusReport(runtime, ctx) {
8718
8790
  const activeBlocksList = state.blocks.filter((b) => b.active);
8719
8791
  const totalBlocksList = state.blocks;
8720
8792
  const lines = [];
8721
- const versionStr = "0.1.30" ? `billion-context-pi@${"0.1.30"}` : "";
8793
+ const versionStr = "0.1.32" ? `billion-context-pi@${"0.1.32"}` : "";
8722
8794
  lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
8723
8795
  lines.push("\u2502 ACP Context Analysis \u2502");
8724
8796
  lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
@@ -9084,7 +9156,7 @@ async function checkForUpdate(autoUpdate, notify) {
9084
9156
  const data = await res.json();
9085
9157
  const latest = data.version;
9086
9158
  if (!latest) return;
9087
- const current = runtimeVersion ?? "0.1.30";
9159
+ const current = runtimeVersion ?? "0.1.32";
9088
9160
  const hasUpdate = isNewer(latest, current);
9089
9161
  debug.event("update-check", {
9090
9162
  current,
@@ -9323,7 +9395,7 @@ function wireSessionLifecycle(pi, runtime) {
9323
9395
  runtime.store.invalidate();
9324
9396
  runtime.clearNudgeTracking();
9325
9397
  const sid = ctx.sessionManager.getSessionId();
9326
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.30" : null });
9398
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.32" : null });
9327
9399
  try {
9328
9400
  const user = await loadUserConfig(ctx.cwd);
9329
9401
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));