acp-kernel 0.0.27 → 0.0.29

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 CHANGED
@@ -85,6 +85,14 @@ processTurn({ messages, state, config, tokenCount, renderTags: "none" });
85
85
  `renderTags` is optional and defaults to `"all"`, so existing call sites keep
86
86
  working unchanged.
87
87
 
88
+ **Token counts in rendered tags are snapshots.** Each message's
89
+ `<acp tokens="N">` attribute is frozen at the tag's first render (the
90
+ per-message `tokenSnapshot` in state) and is not recomputed when the message
91
+ text is later filtered, truncated, or edited by the host — the number always
92
+ describes what the model originally saw. Nudge/pressure *decisions* are
93
+ unaffected: they recount live text every turn. If you need the legacy
94
+ live-recomputed tags, use `renderVisibleRefs` directly.
95
+
88
96
  ### Standalone modules
89
97
 
90
98
  | Module | Purpose |
@@ -95,6 +103,7 @@ working unchanged.
95
103
  | `mergeMarkedBlocks` / `collectOldGenBlocks` | Batch merge old-gen blocks into one summary |
96
104
  | `rebuildCompressionState` | Fork-recovery: replay historical compress calls |
97
105
  | `applyMessageFilters` | Pluggable message-filter framework |
106
+ | `resolveTransformChannel` | Channel-selection policy: an explicit preference wins; the default is the wire channel only when the caller reports it viable |
98
107
 
99
108
  ### Nudge system
100
109
 
@@ -106,9 +115,22 @@ The nudge system tells the model *when* to compress. It implements:
106
115
  - **Compressible-range computation**: reports the actual compressible ranges (excluding covered + preserved-recent messages) so the model knows what to target.
107
116
  - **Baseline reset on compress**: `applyCompression` clears the growth baseline on success, preventing the feedback-loop bug where the nudge re-fires post-compress.
108
117
 
118
+ ## Wire codec (`acp-kernel/wire`)
119
+
120
+ The `acp-kernel/wire` subpath ships the provider wire-body codecs (lossless
121
+ round-trip via the `BiliMessage` sidecar): `anthropicToCore`/`coreToAnthropic`,
122
+ `openaiToCore`/`coreToOpenai`, `responsesToCore`/`coreToResponses`, plus
123
+ `deriveMessageId` (content-hash message identity), conversation signals and the
124
+ subagent-namespace helpers. `WIRE_FORMATS` / `detectWireFormat` classify a
125
+ request body by the codec that can parse it — `undefined` means the body is
126
+ unparseable and must be passed through untransformed. Adapters map their
127
+ host's model/API ids to formats and use `resolveTransformChannel` (with
128
+ `wireViable` = body parseable AND the host applies the payload replacement)
129
+ to decide between message-level and wire-level surgery.
130
+
109
131
  ## Status
110
132
 
111
- ✅ **Engine complete** — 23 source modules, 167 tests, typecheck + build clean. 3-tier compression, growth-gated nudges, emergency truncation,, fork-recovery, batch merge, composable node pipeline. Ready for adapter authoring.
133
+ ✅ **Engine complete** — 23 source modules, full suite green (`npm test`), typecheck + build clean. 3-tier compression, growth-gated nudges, emergency truncation, fork-recovery, batch merge, composable node pipeline. Ready for adapter authoring.
112
134
 
113
135
  > **Protected tool messages:** protected tool calls (per `config.protectedTools`) and their paired tool-results are hard-excluded from compression — they are dropped from the compressible set and from the new block's `effectiveMessageIds`, so they stay fully visible and are never folded into a summary. This matches opencode-acp's Bug 39 fix. The soft-protected recent zone (`preserveRecentMessages` / last user message) is handled separately: messages there are excluded from the range but do not fail it (an entirely-protected range fails with a clear error).
114
136
 
@@ -1 +1 @@
1
- {"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAapB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AASvD,OAAO,EAGL,KAAK,YAAY,EAElB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,EAEN,WAAW,EAGX,iBAAiB,EAEjB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,KAAK;IACpB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACxD,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,CAAC;IACvE,YAAY,IAAI,YAAY,EAAE,CAAC;IAC/B,UAAU,CACR,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,gBAAgB,GACtB,gBAAgB,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAAC;IACnE,MAAM,CACJ,KAAK,EAAE,gBAAgB,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,YAAY,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;IACJ,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnC;AAsBD,wBAAgB,UAAU,CAAC,KAAK,GAAE,KAAU,GAAG,eAAe,CAwQ7D;AAkxBD,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
1
+ {"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAapB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AASvD,OAAO,EAGL,KAAK,YAAY,EAElB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,EAEN,WAAW,EAGX,iBAAiB,EAEjB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,KAAK;IACpB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACxD,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,CAAC;IACvE,YAAY,IAAI,YAAY,EAAE,CAAC;IAC/B,UAAU,CACR,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,gBAAgB,GACtB,gBAAgB,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAAC;IACnE,MAAM,CACJ,KAAK,EAAE,gBAAgB,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,YAAY,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;IACJ,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnC;AAsBD,wBAAgB,UAAU,CAAC,KAAK,GAAE,KAAU,GAAG,eAAe,CAwQ7D;AAqxBD,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
package/dist/index.d.ts CHANGED
@@ -26,7 +26,10 @@ export { rebuildCompressionState } from "./rebuild.js";
26
26
  export type { RebuildResult, RebuildPorts } from "./rebuild.js";
27
27
  export { renderVisibleRefs, renderRefsNode, createRenderRefsNode } from "./render-refs.js";
28
28
  export type { RenderStrategy } from "./render-refs.js";
29
+ export { resolveTransformChannel } from "./transform-channel.js";
30
+ export type { TransformChannel } from "./transform-channel.js";
29
31
  export { searchBlocks, searchBlocksAsync, blockDocs, messageDocs } from "./search.js";
32
+ export { clearDocFeatures, docCacheInfo, docFeatures, setDocCacheCap } from "./search.js";
30
33
  export type { SearchResult, SearchOptions, SearchAlgorithm, AsyncSearchAlgorithm, AnySearchAlgorithm, SearchDoc, SearchDocKind, ScoredBlock, MessageRole, RoleWeights, MessageInput } from "./search.js";
31
34
  export { DEFAULT_ALGORITHM, DEFAULT_ROLE_WEIGHTS, registerSearchAlgorithm, getSearchAlgorithm, listSearchAlgorithms } from "./search.js";
32
35
  export { isMessageProtected, matchToolPattern } from "./protected.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1050,6 +1050,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
1050
1050
  ref,
1051
1051
  refNum: rn,
1052
1052
  tokens: countTokens(msg.text ?? ""),
1053
+ chars: (msg.text ?? "").length,
1053
1054
  isTool: isToolMessage(msg),
1054
1055
  isUser: msg.role === "user"
1055
1056
  });
@@ -1070,6 +1071,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
1070
1071
  endRef: info.ref,
1071
1072
  count: 1,
1072
1073
  tokens: info.tokens,
1074
+ chars: info.chars,
1073
1075
  toolPct: info.isTool ? 100 : 0,
1074
1076
  textPct: info.isTool ? 0 : 100
1075
1077
  };
@@ -1077,6 +1079,7 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
1077
1079
  cur.endRef = info.ref;
1078
1080
  cur.count++;
1079
1081
  cur.tokens += info.tokens;
1082
+ cur.chars = (cur.chars ?? 0) + info.chars;
1080
1083
  if (info.isTool) {
1081
1084
  cur.toolPct = Math.round((cur.toolPct * (cur.count - 1) + 100) / cur.count);
1082
1085
  } else {
@@ -1124,6 +1127,7 @@ function mergeBatch(batch) {
1124
1127
  const last = batch[batch.length - 1];
1125
1128
  const count = batch.reduce((s, r) => s + r.count, 0);
1126
1129
  const tokens = batch.reduce((s, r) => s + r.tokens, 0);
1130
+ const chars = batch.reduce((s, r) => s + rangeChars(r), 0);
1127
1131
  const toolPct = Math.round(
1128
1132
  batch.reduce((s, r) => s + r.toolPct * r.count, 0) / count
1129
1133
  );
@@ -1132,6 +1136,7 @@ function mergeBatch(batch) {
1132
1136
  endRef: last.endRef,
1133
1137
  count,
1134
1138
  tokens,
1139
+ chars,
1135
1140
  toolPct,
1136
1141
  textPct: 100 - toolPct
1137
1142
  };
@@ -1140,16 +1145,21 @@ function mergeBatch(batch) {
1140
1145
  }
1141
1146
  return merged;
1142
1147
  }
1148
+ function rangeChars(r) {
1149
+ return r.chars ?? r.tokens * 4;
1150
+ }
1143
1151
  function mergeRangesToThreshold(ranges, minChars) {
1144
1152
  if (minChars <= 0 || ranges.length === 0) return ranges;
1145
1153
  const result = [];
1146
1154
  let batch = [];
1155
+ let batchChars = 0;
1147
1156
  for (const r of ranges) {
1148
1157
  batch.push(r);
1149
- const batchTokens = batch.reduce((s, x) => s + x.tokens, 0);
1150
- if (batchTokens * 4 >= minChars) {
1158
+ batchChars += rangeChars(r);
1159
+ if (batchChars >= minChars) {
1151
1160
  result.push(mergeBatch(batch));
1152
1161
  batch = [];
1162
+ batchChars = 0;
1153
1163
  }
1154
1164
  }
1155
1165
  if (batch.length > 0) {
@@ -1744,7 +1754,7 @@ function resolveAdaptiveGrowth(modelContextLimit, nudge) {
1744
1754
  function pendingByTier(state, recommendation, countTokens, minCompressRange) {
1745
1755
  const out = {};
1746
1756
  const merged = recommendation?.recommendedRanges ?? [];
1747
- const effective = minCompressRange > 0 ? merged.filter((r) => r.tokens * 4 >= minCompressRange) : merged;
1757
+ const effective = minCompressRange > 0 ? merged.filter((r) => (r.chars ?? r.tokens * 4) >= minCompressRange) : merged;
1748
1758
  out[1] = { pending: effective.reduce((s, r) => s + r.tokens, 0), targetBlocks: [] };
1749
1759
  const active = activeBlocks(state);
1750
1760
  const t1 = active.filter((b) => b.tier === 1);
@@ -2690,24 +2700,9 @@ function extractRanges(input, callId) {
2690
2700
  return ranges;
2691
2701
  }
2692
2702
 
2693
- // src/search/algorithms/substring.ts
2694
- var substringAlgorithm = {
2695
- name: "substring",
2696
- description: "Exact substring counting (original baseline). Predictable, no normalization.",
2697
- score(docs, query) {
2698
- const terms = query.toLowerCase().trim().split(/\s+/).filter((t) => t.length > 0);
2699
- if (terms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
2700
- return docs.map((d) => {
2701
- const haystack = d.text.toLowerCase();
2702
- let score = 0;
2703
- for (const term of terms) score += countOccurrences2(haystack, term);
2704
- return { ref: d.ref, score };
2705
- });
2706
- }
2707
- };
2708
- function countOccurrences2(haystack, needle) {
2709
- if (!needle) return 0;
2710
- return haystack.split(needle).length - 1;
2703
+ // src/transform-channel.ts
2704
+ function resolveTransformChannel(explicit, wireViable) {
2705
+ return explicit ?? (wireViable ? "wire" : "message");
2711
2706
  }
2712
2707
 
2713
2708
  // src/search/stemmer.ts
@@ -2731,8 +2726,17 @@ function stem(word) {
2731
2726
 
2732
2727
  // src/search/tokenizer.ts
2733
2728
  var CJK = /[\u3400-\u9fff\uf900-\ufaff\u3040-\u30ff\uac00-\ud7af]/;
2734
- var CJK_RUN = new RegExp(`${CJK.source}+`, "g");
2735
2729
  var LATIN_WORD = /[a-z][a-z0-9_]*[a-z0-9]|[a-z0-9]/g;
2730
+ var cjkSegmenter = new Intl.Segmenter("zh", { granularity: "word" });
2731
+ function cjkRunTokens(segs) {
2732
+ const words = segs.filter((w) => w.length >= 2);
2733
+ if (words.length > 0) return words;
2734
+ const run = segs.join("");
2735
+ const out = [];
2736
+ for (let i = 0; i < run.length - 1; i++) out.push(run.slice(i, i + 2));
2737
+ for (const ch of run) out.push(ch);
2738
+ return out;
2739
+ }
2736
2740
  function tokenize(text, opts = {}) {
2737
2741
  const lower = text.toLowerCase();
2738
2742
  const tokens = [];
@@ -2743,15 +2747,23 @@ function tokenize(text, opts = {}) {
2743
2747
  tokens.push(w);
2744
2748
  }
2745
2749
  }
2746
- const cjkRuns = lower.match(CJK_RUN) ?? [];
2747
- for (const run of cjkRuns) {
2748
- if (run.length === 1) {
2749
- tokens.push(run);
2750
- } else {
2751
- for (let i = 0; i < run.length - 1; i++) tokens.push(run.slice(i, i + 2));
2752
- for (const ch of run) tokens.push(ch);
2750
+ if (!CJK.test(lower)) return tokens;
2751
+ const runSegs = [];
2752
+ let cur = null;
2753
+ for (const s of cjkSegmenter.segment(lower)) {
2754
+ const t = s.segment;
2755
+ if (t.length === 0) continue;
2756
+ if (CJK.test(t)) {
2757
+ (cur ??= []).push(t);
2758
+ } else if (cur) {
2759
+ runSegs.push(cur);
2760
+ cur = null;
2753
2761
  }
2754
2762
  }
2763
+ if (cur) runSegs.push(cur);
2764
+ for (const segs of runSegs) {
2765
+ tokens.push(...cjkRunTokens(segs));
2766
+ }
2755
2767
  return tokens;
2756
2768
  }
2757
2769
  function charBigrams(text) {
@@ -2768,6 +2780,69 @@ function tfMap(text, stem2) {
2768
2780
  return m;
2769
2781
  }
2770
2782
 
2783
+ // src/search/doc-cache.ts
2784
+ var DEFAULT_CAP_CHARS = 8 * 1024 * 1024;
2785
+ var capChars = DEFAULT_CAP_CHARS;
2786
+ var cache = /* @__PURE__ */ new Map();
2787
+ var cachedChars = 0;
2788
+ function build(text) {
2789
+ const tf = tfMap(text, true);
2790
+ let len = 0;
2791
+ for (const v of tf.values()) len += v;
2792
+ const lower = text.toLowerCase();
2793
+ return { tf, len, lower, grams: new Set(charBigrams(lower)) };
2794
+ }
2795
+ function docFeatures(text) {
2796
+ const hit = cache.get(text);
2797
+ if (hit) return hit;
2798
+ const f = build(text);
2799
+ if (text.length > 0 && text.length <= capChars) {
2800
+ while (cachedChars + text.length > capChars && cache.size > 0) {
2801
+ const k = cache.keys().next().value;
2802
+ cachedChars -= k.length;
2803
+ cache.delete(k);
2804
+ }
2805
+ cache.set(text, f);
2806
+ cachedChars += text.length;
2807
+ }
2808
+ return f;
2809
+ }
2810
+ function clearDocFeatures() {
2811
+ cache.clear();
2812
+ cachedChars = 0;
2813
+ }
2814
+ function setDocCacheCap(chars) {
2815
+ capChars = Math.max(1, chars);
2816
+ while (cachedChars > capChars && cache.size > 0) {
2817
+ const k = cache.keys().next().value;
2818
+ cachedChars -= k.length;
2819
+ cache.delete(k);
2820
+ }
2821
+ }
2822
+ function docCacheInfo() {
2823
+ return { entries: cache.size, chars: cachedChars };
2824
+ }
2825
+
2826
+ // src/search/algorithms/substring.ts
2827
+ var substringAlgorithm = {
2828
+ name: "substring",
2829
+ description: "Exact substring counting (original baseline). Predictable, no normalization.",
2830
+ score(docs, query) {
2831
+ const terms = query.toLowerCase().trim().split(/\s+/).filter((t) => t.length > 0);
2832
+ if (terms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
2833
+ return docs.map((d) => {
2834
+ const haystack = docFeatures(d.text).lower;
2835
+ let score = 0;
2836
+ for (const term of terms) score += countOccurrences2(haystack, term);
2837
+ return { ref: d.ref, score };
2838
+ });
2839
+ }
2840
+ };
2841
+ function countOccurrences2(haystack, needle) {
2842
+ if (!needle) return 0;
2843
+ return haystack.split(needle).length - 1;
2844
+ }
2845
+
2771
2846
  // src/search/algorithms/bm25.ts
2772
2847
  var bm25Algorithm = {
2773
2848
  name: "bm25",
@@ -2777,11 +2852,8 @@ var bm25Algorithm = {
2777
2852
  const k1 = 1.2;
2778
2853
  const b = 0.75;
2779
2854
  const parsed = docs.map((d) => {
2780
- const text = d.text;
2781
- const tf = tfMap(text, true);
2782
- let len = 0;
2783
- for (const v of tf.values()) len += v;
2784
- return { id: d.ref, tf, len };
2855
+ const f = docFeatures(d.text);
2856
+ return { id: d.ref, tf: f.tf, len: f.len };
2785
2857
  });
2786
2858
  const avgdl = parsed.reduce((s, d) => s + d.len, 0) / (N || 1);
2787
2859
  const qTerms = tokenize(query, { stem: true });
@@ -2810,14 +2882,13 @@ var fuzzyAlgorithm = {
2810
2882
  name: "fuzzy",
2811
2883
  description: "Character bigram overlap. Typo-tolerant, script-agnostic, high recall.",
2812
2884
  score(docs, query) {
2813
- const qTokens = query.toLowerCase().split(/[\s,]+/).filter((t) => t.length >= 4);
2885
+ const qTokens = query.toLowerCase().split(/[\s,]+/).filter((t) => t.length >= 4 || t.length >= 2 && CJK.test(t));
2814
2886
  if (qTokens.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
2815
2887
  const qGrams = /* @__PURE__ */ new Set();
2816
2888
  for (const t of qTokens) for (const g of charBigrams(t)) qGrams.add(g);
2817
2889
  if (qGrams.size === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));
2818
2890
  return docs.map((d) => {
2819
- const haystack = d.text.toLowerCase();
2820
- const docGrams = new Set(charBigrams(haystack));
2891
+ const docGrams = docFeatures(d.text).grams;
2821
2892
  let hits = 0;
2822
2893
  for (const g of qGrams) if (docGrams.has(g)) hits++;
2823
2894
  return { ref: d.ref, score: hits / qGrams.size };
@@ -2991,6 +3062,7 @@ export {
2991
3062
  buildRecap,
2992
3063
  buildRestoredContentPreview,
2993
3064
  buildStatusReport,
3065
+ clearDocFeatures,
2994
3066
  clearMessageFilters,
2995
3067
  collectBlockContent,
2996
3068
  coveredMessageIds,
@@ -3002,6 +3074,8 @@ export {
3002
3074
  defaultConfig,
3003
3075
  defaultCountTokens,
3004
3076
  defaultPrompts,
3077
+ docCacheInfo,
3078
+ docFeatures,
3005
3079
  emptyRefMap,
3006
3080
  estimateTokensFast,
3007
3081
  findActiveAncestor,
@@ -3033,9 +3107,11 @@ export {
3033
3107
  renderVisibleRefs,
3034
3108
  resolveBoundaries,
3035
3109
  resolvePrompts,
3110
+ resolveTransformChannel,
3036
3111
  runPipeline,
3037
3112
  searchBlocks,
3038
3113
  searchBlocksAsync,
3114
+ setDocCacheCap,
3039
3115
  syncBlocks,
3040
3116
  truncateLargeToolOutputs,
3041
3117
  validateConfig