@animalabs/context-manager 0.4.0 → 0.5.1
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/src/adaptive/chunker.d.ts +70 -0
- package/dist/src/adaptive/chunker.d.ts.map +1 -0
- package/dist/src/adaptive/chunker.js +233 -0
- package/dist/src/adaptive/chunker.js.map +1 -0
- package/dist/src/adaptive/folding-strategy.d.ts +118 -0
- package/dist/src/adaptive/folding-strategy.d.ts.map +1 -0
- package/dist/src/adaptive/folding-strategy.js +12 -0
- package/dist/src/adaptive/folding-strategy.js.map +1 -0
- package/dist/src/adaptive/index.d.ts +17 -0
- package/dist/src/adaptive/index.d.ts.map +1 -0
- package/dist/src/adaptive/index.js +23 -0
- package/dist/src/adaptive/index.js.map +1 -0
- package/dist/src/adaptive/picker.d.ts +103 -0
- package/dist/src/adaptive/picker.d.ts.map +1 -0
- package/dist/src/adaptive/picker.js +357 -0
- package/dist/src/adaptive/picker.js.map +1 -0
- package/dist/src/adaptive/render.d.ts +39 -0
- package/dist/src/adaptive/render.d.ts.map +1 -0
- package/dist/src/adaptive/render.js +104 -0
- package/dist/src/adaptive/render.js.map +1 -0
- package/dist/src/adaptive/strategies/flat-profile.d.ts +19 -0
- package/dist/src/adaptive/strategies/flat-profile.d.ts.map +1 -0
- package/dist/src/adaptive/strategies/flat-profile.js +86 -0
- package/dist/src/adaptive/strategies/flat-profile.js.map +1 -0
- package/dist/src/adaptive/strategies/oldest-first.d.ts +19 -0
- package/dist/src/adaptive/strategies/oldest-first.d.ts.map +1 -0
- package/dist/src/adaptive/strategies/oldest-first.js +39 -0
- package/dist/src/adaptive/strategies/oldest-first.js.map +1 -0
- package/dist/src/context-manager.d.ts +5 -0
- package/dist/src/context-manager.d.ts.map +1 -1
- package/dist/src/context-manager.js +22 -0
- package/dist/src/context-manager.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/message-store.d.ts +36 -1
- package/dist/src/message-store.d.ts.map +1 -1
- package/dist/src/message-store.js +134 -8
- package/dist/src/message-store.js.map +1 -1
- package/dist/src/strategies/autobiographical.d.ts +217 -2
- package/dist/src/strategies/autobiographical.d.ts.map +1 -1
- package/dist/src/strategies/autobiographical.js +1525 -37
- package/dist/src/strategies/autobiographical.js.map +1 -1
- package/dist/src/strategies/index.d.ts +1 -1
- package/dist/src/strategies/index.d.ts.map +1 -1
- package/dist/src/strategies/index.js.map +1 -1
- package/dist/src/types/message.d.ts +38 -0
- package/dist/src/types/message.d.ts.map +1 -1
- package/dist/src/types/strategy.d.ts +106 -10
- package/dist/src/types/strategy.d.ts.map +1 -1
- package/dist/src/types/strategy.js +7 -0
- package/dist/src/types/strategy.js.map +1 -1
- package/dist/test/adaptive/branching.test.d.ts +20 -0
- package/dist/test/adaptive/branching.test.d.ts.map +1 -0
- package/dist/test/adaptive/branching.test.js +176 -0
- package/dist/test/adaptive/branching.test.js.map +1 -0
- package/dist/test/adaptive/chunker.test.d.ts +2 -0
- package/dist/test/adaptive/chunker.test.d.ts.map +1 -0
- package/dist/test/adaptive/chunker.test.js +120 -0
- package/dist/test/adaptive/chunker.test.js.map +1 -0
- package/dist/test/adaptive/deep-levels.test.d.ts +10 -0
- package/dist/test/adaptive/deep-levels.test.d.ts.map +1 -0
- package/dist/test/adaptive/deep-levels.test.js +167 -0
- package/dist/test/adaptive/deep-levels.test.js.map +1 -0
- package/dist/test/adaptive/doc-plus-chat.test.d.ts +10 -0
- package/dist/test/adaptive/doc-plus-chat.test.d.ts.map +1 -0
- package/dist/test/adaptive/doc-plus-chat.test.js +194 -0
- package/dist/test/adaptive/doc-plus-chat.test.js.map +1 -0
- package/dist/test/adaptive/hard-fail.test.d.ts +9 -0
- package/dist/test/adaptive/hard-fail.test.d.ts.map +1 -0
- package/dist/test/adaptive/hard-fail.test.js +154 -0
- package/dist/test/adaptive/hard-fail.test.js.map +1 -0
- package/dist/test/adaptive/harness.d.ts +109 -0
- package/dist/test/adaptive/harness.d.ts.map +1 -0
- package/dist/test/adaptive/harness.js +263 -0
- package/dist/test/adaptive/harness.js.map +1 -0
- package/dist/test/adaptive/ingestion.test.d.ts +13 -0
- package/dist/test/adaptive/ingestion.test.d.ts.map +1 -0
- package/dist/test/adaptive/ingestion.test.js +306 -0
- package/dist/test/adaptive/ingestion.test.js.map +1 -0
- package/dist/test/adaptive/integration.test.d.ts +12 -0
- package/dist/test/adaptive/integration.test.d.ts.map +1 -0
- package/dist/test/adaptive/integration.test.js +247 -0
- package/dist/test/adaptive/integration.test.js.map +1 -0
- package/dist/test/adaptive/long-chronicle.test.d.ts +13 -0
- package/dist/test/adaptive/long-chronicle.test.d.ts.map +1 -0
- package/dist/test/adaptive/long-chronicle.test.js +186 -0
- package/dist/test/adaptive/long-chronicle.test.js.map +1 -0
- package/dist/test/adaptive/persistence.test.d.ts +11 -0
- package/dist/test/adaptive/persistence.test.d.ts.map +1 -0
- package/dist/test/adaptive/persistence.test.js +200 -0
- package/dist/test/adaptive/persistence.test.js.map +1 -0
- package/dist/test/adaptive/picker.test.d.ts +2 -0
- package/dist/test/adaptive/picker.test.d.ts.map +1 -0
- package/dist/test/adaptive/picker.test.js +333 -0
- package/dist/test/adaptive/picker.test.js.map +1 -0
- package/dist/test/adaptive/render.test.d.ts +2 -0
- package/dist/test/adaptive/render.test.d.ts.map +1 -0
- package/dist/test/adaptive/render.test.js +109 -0
- package/dist/test/adaptive/render.test.js.map +1 -0
- package/dist/test/adaptive/shard-immutability.test.d.ts +14 -0
- package/dist/test/adaptive/shard-immutability.test.d.ts.map +1 -0
- package/dist/test/adaptive/shard-immutability.test.js +184 -0
- package/dist/test/adaptive/shard-immutability.test.js.map +1 -0
- package/dist/test/adaptive/strategy-integration.test.d.ts +9 -0
- package/dist/test/adaptive/strategy-integration.test.d.ts.map +1 -0
- package/dist/test/adaptive/strategy-integration.test.js +293 -0
- package/dist/test/adaptive/strategy-integration.test.js.map +1 -0
- package/dist/test/message-store-sequence.test.d.ts +25 -0
- package/dist/test/message-store-sequence.test.d.ts.map +1 -0
- package/dist/test/message-store-sequence.test.js +157 -0
- package/dist/test/message-store-sequence.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/adaptive/chunker.ts +323 -0
- package/src/adaptive/folding-strategy.ts +129 -0
- package/src/adaptive/index.ts +51 -0
- package/src/adaptive/picker.ts +442 -0
- package/src/adaptive/render.ts +115 -0
- package/src/adaptive/strategies/flat-profile.ts +96 -0
- package/src/adaptive/strategies/oldest-first.ts +48 -0
- package/src/context-manager.ts +32 -0
- package/src/index.ts +1 -1
- package/src/message-store.ts +147 -8
- package/src/strategies/autobiographical.ts +1635 -38
- package/src/strategies/index.ts +1 -1
- package/src/types/message.ts +43 -0
- package/src/types/strategy.ts +122 -10
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic chunker for messages that exceed `chunkThreshold` tokens.
|
|
3
|
+
*
|
|
4
|
+
* Splits a message body into shards such that concatenating the shards in
|
|
5
|
+
* order reproduces the original body byte-for-byte. Each shard is identified
|
|
6
|
+
* by a stable `sourceHash` so re-ingesting the same content produces
|
|
7
|
+
* identical shard boundaries (and reuses any existing L_k summaries via the
|
|
8
|
+
* archive's hash-keyed lookup).
|
|
9
|
+
*
|
|
10
|
+
* Strategy:
|
|
11
|
+
* 1. Structural pass — split at markdown headings, code-fence boundaries,
|
|
12
|
+
* blank-line paragraph breaks. Prefer larger structural units when they
|
|
13
|
+
* fit under `chunkSize`.
|
|
14
|
+
* 2. Token-bucket fallback — when a structural unit alone exceeds
|
|
15
|
+
* `chunkSize`, split it at paragraph or line boundaries until each
|
|
16
|
+
* resulting piece fits.
|
|
17
|
+
* 3. Last-resort hard split — only when no structural seam exists in the
|
|
18
|
+
* piece (e.g., one very long line). Splits at the largest UTF-8-safe
|
|
19
|
+
* position under the threshold.
|
|
20
|
+
*
|
|
21
|
+
* See `docs/adaptive-resolution-design.md` §3.6.
|
|
22
|
+
*/
|
|
23
|
+
export interface ChunkerOptions {
|
|
24
|
+
/**
|
|
25
|
+
* Messages with token count strictly greater than this are chunked.
|
|
26
|
+
* Messages at or below are passed through unchanged.
|
|
27
|
+
* Default: 8192.
|
|
28
|
+
*/
|
|
29
|
+
chunkThreshold: number;
|
|
30
|
+
/**
|
|
31
|
+
* Target token count per shard. The chunker aims for shards close to
|
|
32
|
+
* but not exceeding this size. Default: 4096.
|
|
33
|
+
*/
|
|
34
|
+
chunkSize: number;
|
|
35
|
+
/**
|
|
36
|
+
* Approximate characters per token. Used for size estimation since we
|
|
37
|
+
* can't easily run a tokenizer here. Default: 4.0. The picker uses
|
|
38
|
+
* cached real token counts; the chunker only needs approximate sizing.
|
|
39
|
+
*/
|
|
40
|
+
charsPerToken: number;
|
|
41
|
+
}
|
|
42
|
+
export declare const DEFAULT_CHUNKER_OPTIONS: ChunkerOptions;
|
|
43
|
+
export interface Shard {
|
|
44
|
+
/** Order of this shard within its bodyGroup, starting at 0. */
|
|
45
|
+
index: number;
|
|
46
|
+
/** Byte offsets into the original body. [start, end) — UTF-8 byte positions. */
|
|
47
|
+
range: {
|
|
48
|
+
startByte: number;
|
|
49
|
+
endByte: number;
|
|
50
|
+
};
|
|
51
|
+
/** Shard content (a substring of the original body). */
|
|
52
|
+
content: string;
|
|
53
|
+
/** SHA-256 of the shard content. */
|
|
54
|
+
sourceHash: string;
|
|
55
|
+
/** Approximate token count (chars / charsPerToken, rounded). */
|
|
56
|
+
approxTokens: number;
|
|
57
|
+
}
|
|
58
|
+
export interface ShardingResult {
|
|
59
|
+
/** True if the message was split into multiple shards. */
|
|
60
|
+
wasSharded: boolean;
|
|
61
|
+
/** The shards. If wasSharded is false, exactly one shard containing the entire body. */
|
|
62
|
+
shards: Shard[];
|
|
63
|
+
/** The stable group id for these shards (sha256 of the full original body, prefixed). */
|
|
64
|
+
bodyGroupId: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Chunk a message body. Pure function — no side effects.
|
|
68
|
+
*/
|
|
69
|
+
export declare function chunkMessage(body: string, options?: Partial<ChunkerOptions>): ShardingResult;
|
|
70
|
+
//# sourceMappingURL=chunker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker.d.ts","sourceRoot":"","sources":["../../../src/adaptive/chunker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,uBAAuB,EAAE,cAIrC,CAAC;AAEF,MAAM,WAAW,KAAK;IACpB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,UAAU,EAAE,OAAO,CAAC;IACpB,wFAAwF;IACxF,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,yFAAyF;IACzF,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,CAAC,cAAc,CAAM,GAAG,cAAc,CAqFhG"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic chunker for messages that exceed `chunkThreshold` tokens.
|
|
3
|
+
*
|
|
4
|
+
* Splits a message body into shards such that concatenating the shards in
|
|
5
|
+
* order reproduces the original body byte-for-byte. Each shard is identified
|
|
6
|
+
* by a stable `sourceHash` so re-ingesting the same content produces
|
|
7
|
+
* identical shard boundaries (and reuses any existing L_k summaries via the
|
|
8
|
+
* archive's hash-keyed lookup).
|
|
9
|
+
*
|
|
10
|
+
* Strategy:
|
|
11
|
+
* 1. Structural pass — split at markdown headings, code-fence boundaries,
|
|
12
|
+
* blank-line paragraph breaks. Prefer larger structural units when they
|
|
13
|
+
* fit under `chunkSize`.
|
|
14
|
+
* 2. Token-bucket fallback — when a structural unit alone exceeds
|
|
15
|
+
* `chunkSize`, split it at paragraph or line boundaries until each
|
|
16
|
+
* resulting piece fits.
|
|
17
|
+
* 3. Last-resort hard split — only when no structural seam exists in the
|
|
18
|
+
* piece (e.g., one very long line). Splits at the largest UTF-8-safe
|
|
19
|
+
* position under the threshold.
|
|
20
|
+
*
|
|
21
|
+
* See `docs/adaptive-resolution-design.md` §3.6.
|
|
22
|
+
*/
|
|
23
|
+
import { createHash } from 'node:crypto';
|
|
24
|
+
export const DEFAULT_CHUNKER_OPTIONS = {
|
|
25
|
+
chunkThreshold: 8192,
|
|
26
|
+
chunkSize: 4096,
|
|
27
|
+
charsPerToken: 4.0,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Chunk a message body. Pure function — no side effects.
|
|
31
|
+
*/
|
|
32
|
+
export function chunkMessage(body, options = {}) {
|
|
33
|
+
const opts = { ...DEFAULT_CHUNKER_OPTIONS, ...options };
|
|
34
|
+
const totalApproxTokens = Math.ceil(body.length / opts.charsPerToken);
|
|
35
|
+
const bodyHash = hashOf(body);
|
|
36
|
+
const bodyGroupId = `bg-${bodyHash.slice(0, 16)}`;
|
|
37
|
+
if (totalApproxTokens <= opts.chunkThreshold) {
|
|
38
|
+
return {
|
|
39
|
+
wasSharded: false,
|
|
40
|
+
bodyGroupId,
|
|
41
|
+
shards: [
|
|
42
|
+
{
|
|
43
|
+
index: 0,
|
|
44
|
+
range: { startByte: 0, endByte: byteLength(body) },
|
|
45
|
+
content: body,
|
|
46
|
+
sourceHash: bodyHash,
|
|
47
|
+
approxTokens: totalApproxTokens,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Build a list of split points — preferred seams in priority order.
|
|
53
|
+
// Each seam is a byte offset into the body. The chunker walks the body
|
|
54
|
+
// and emits shards by greedily accumulating content up to chunkSize at
|
|
55
|
+
// the highest-quality seam available.
|
|
56
|
+
const seams = findSeams(body);
|
|
57
|
+
const targetChars = opts.chunkSize * opts.charsPerToken;
|
|
58
|
+
const maxChars = opts.chunkSize * opts.charsPerToken * 1.25; // 25% overflow tolerance to land on a clean seam
|
|
59
|
+
const shards = [];
|
|
60
|
+
let cursor = 0;
|
|
61
|
+
let index = 0;
|
|
62
|
+
const bodyByteLen = byteLength(body);
|
|
63
|
+
while (cursor < body.length) {
|
|
64
|
+
// Find the best seam to split at, given the [cursor, cursor + maxChars] window.
|
|
65
|
+
const windowEnd = Math.min(cursor + maxChars, body.length);
|
|
66
|
+
const targetEnd = Math.min(cursor + targetChars, body.length);
|
|
67
|
+
let splitAt;
|
|
68
|
+
if (windowEnd >= body.length) {
|
|
69
|
+
splitAt = body.length;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
splitAt = bestSeamIn(seams, cursor, targetEnd, windowEnd, body);
|
|
73
|
+
}
|
|
74
|
+
// Ensure forward progress: if no seam found and we'd produce an empty
|
|
75
|
+
// shard, take a hard split at the targetEnd.
|
|
76
|
+
if (splitAt <= cursor) {
|
|
77
|
+
splitAt = safeCharBoundary(body, targetEnd);
|
|
78
|
+
}
|
|
79
|
+
const content = body.slice(cursor, splitAt);
|
|
80
|
+
const startByte = byteLengthOf(body, 0, cursor);
|
|
81
|
+
const endByte = startByte + byteLength(content);
|
|
82
|
+
shards.push({
|
|
83
|
+
index,
|
|
84
|
+
range: { startByte, endByte },
|
|
85
|
+
content,
|
|
86
|
+
sourceHash: hashOf(content),
|
|
87
|
+
approxTokens: Math.ceil(content.length / opts.charsPerToken),
|
|
88
|
+
});
|
|
89
|
+
cursor = splitAt;
|
|
90
|
+
index += 1;
|
|
91
|
+
}
|
|
92
|
+
// Sanity: byte-faithful reassembly
|
|
93
|
+
const reassembled = shards.map((s) => s.content).join('');
|
|
94
|
+
if (reassembled !== body) {
|
|
95
|
+
throw new Error(`chunker: shard reassembly mismatch (original=${body.length} chars, reassembled=${reassembled.length} chars)`);
|
|
96
|
+
}
|
|
97
|
+
if (shards[shards.length - 1].range.endByte !== bodyByteLen) {
|
|
98
|
+
throw new Error(`chunker: byte-range mismatch (expected end=${bodyByteLen}, got=${shards[shards.length - 1].range.endByte})`);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
wasSharded: true,
|
|
102
|
+
bodyGroupId,
|
|
103
|
+
shards,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function findSeams(body) {
|
|
107
|
+
const seams = [];
|
|
108
|
+
// Markdown headings: ^(#+)\s
|
|
109
|
+
// Match at line starts.
|
|
110
|
+
const headingRe = /(?:^|\n)(#{1,6})\s[^\n]*\n/g;
|
|
111
|
+
let m;
|
|
112
|
+
while ((m = headingRe.exec(body)) !== null) {
|
|
113
|
+
const hashes = m[1];
|
|
114
|
+
// Position is just before the heading's newline so the heading itself
|
|
115
|
+
// starts the next shard (cleaner visual boundary).
|
|
116
|
+
const lineStart = m.index === 0 ? 0 : m.index + 1;
|
|
117
|
+
if (lineStart === 0)
|
|
118
|
+
continue; // can't split at the start
|
|
119
|
+
seams.push({
|
|
120
|
+
offset: lineStart,
|
|
121
|
+
priority: hashes.length <= 2 ? 100 : 90,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Code-fence boundaries.
|
|
125
|
+
const fenceRe = /\n```[^\n]*\n/g;
|
|
126
|
+
while ((m = fenceRe.exec(body)) !== null) {
|
|
127
|
+
seams.push({ offset: m.index + 1, priority: 80 });
|
|
128
|
+
}
|
|
129
|
+
// Blank lines (paragraph breaks): \n\n
|
|
130
|
+
const blankRe = /\n\n+/g;
|
|
131
|
+
while ((m = blankRe.exec(body)) !== null) {
|
|
132
|
+
// Position is just after the last \n in the sequence, so the next
|
|
133
|
+
// shard begins at the non-blank content.
|
|
134
|
+
seams.push({ offset: m.index + m[0].length, priority: 70 });
|
|
135
|
+
}
|
|
136
|
+
// Single newlines.
|
|
137
|
+
const nlRe = /\n/g;
|
|
138
|
+
while ((m = nlRe.exec(body)) !== null) {
|
|
139
|
+
seams.push({ offset: m.index + 1, priority: 50 });
|
|
140
|
+
}
|
|
141
|
+
// Sentence ends.
|
|
142
|
+
const sentRe = /[.!?]\s+/g;
|
|
143
|
+
while ((m = sentRe.exec(body)) !== null) {
|
|
144
|
+
seams.push({ offset: m.index + m[0].length, priority: 30 });
|
|
145
|
+
}
|
|
146
|
+
// Word boundaries (any whitespace).
|
|
147
|
+
const wsRe = /\s+/g;
|
|
148
|
+
while ((m = wsRe.exec(body)) !== null) {
|
|
149
|
+
seams.push({ offset: m.index + m[0].length, priority: 10 });
|
|
150
|
+
}
|
|
151
|
+
// Deduplicate by offset, keeping highest priority.
|
|
152
|
+
const byOffset = new Map();
|
|
153
|
+
for (const s of seams) {
|
|
154
|
+
const prev = byOffset.get(s.offset);
|
|
155
|
+
if (prev === undefined || prev < s.priority)
|
|
156
|
+
byOffset.set(s.offset, s.priority);
|
|
157
|
+
}
|
|
158
|
+
return Array.from(byOffset.entries())
|
|
159
|
+
.map(([offset, priority]) => ({ offset, priority }))
|
|
160
|
+
.sort((a, b) => a.offset - b.offset);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Find the highest-priority seam in (cursor, windowEnd] that falls at or
|
|
164
|
+
* before targetEnd if possible, otherwise the closest after targetEnd up to
|
|
165
|
+
* windowEnd. Returns the chosen split offset.
|
|
166
|
+
*/
|
|
167
|
+
function bestSeamIn(seams, cursor, targetEnd, windowEnd, body) {
|
|
168
|
+
// Binary search for the first seam strictly > cursor.
|
|
169
|
+
let lo = 0;
|
|
170
|
+
let hi = seams.length;
|
|
171
|
+
while (lo < hi) {
|
|
172
|
+
const mid = (lo + hi) >>> 1;
|
|
173
|
+
if (seams[mid].offset <= cursor)
|
|
174
|
+
lo = mid + 1;
|
|
175
|
+
else
|
|
176
|
+
hi = mid;
|
|
177
|
+
}
|
|
178
|
+
let bestPreTarget = null;
|
|
179
|
+
let bestPostTarget = null;
|
|
180
|
+
for (let i = lo; i < seams.length; i++) {
|
|
181
|
+
const s = seams[i];
|
|
182
|
+
if (s.offset > windowEnd)
|
|
183
|
+
break;
|
|
184
|
+
if (s.offset <= targetEnd) {
|
|
185
|
+
if (!bestPreTarget || s.priority > bestPreTarget.priority || (s.priority === bestPreTarget.priority && s.offset > bestPreTarget.offset)) {
|
|
186
|
+
bestPreTarget = s;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
if (!bestPostTarget || s.priority > bestPostTarget.priority || (s.priority === bestPostTarget.priority && s.offset < bestPostTarget.offset)) {
|
|
191
|
+
bestPostTarget = s;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Prefer the best seam at or before target.
|
|
196
|
+
if (bestPreTarget)
|
|
197
|
+
return bestPreTarget.offset;
|
|
198
|
+
if (bestPostTarget)
|
|
199
|
+
return bestPostTarget.offset;
|
|
200
|
+
// No seams in window — hard split at a UTF-8-safe boundary near target.
|
|
201
|
+
return safeCharBoundary(body, targetEnd);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Return an offset ≤ target that doesn't fall inside a UTF-16 surrogate pair.
|
|
205
|
+
* The chunker operates on JS strings (UTF-16); splitting inside a surrogate
|
|
206
|
+
* pair would corrupt the output.
|
|
207
|
+
*/
|
|
208
|
+
function safeCharBoundary(body, target) {
|
|
209
|
+
if (target >= body.length)
|
|
210
|
+
return body.length;
|
|
211
|
+
if (target <= 0)
|
|
212
|
+
return 0;
|
|
213
|
+
const code = body.charCodeAt(target);
|
|
214
|
+
// If we'd split before a low surrogate, back up one.
|
|
215
|
+
if (code >= 0xdc00 && code <= 0xdfff) {
|
|
216
|
+
return target - 1;
|
|
217
|
+
}
|
|
218
|
+
return target;
|
|
219
|
+
}
|
|
220
|
+
/** UTF-8 byte length of a JS string. */
|
|
221
|
+
function byteLength(s) {
|
|
222
|
+
return Buffer.byteLength(s, 'utf8');
|
|
223
|
+
}
|
|
224
|
+
/** UTF-8 byte length of body[start..end] without allocating the slice when possible. */
|
|
225
|
+
function byteLengthOf(body, start, end) {
|
|
226
|
+
if (start === 0 && end === 0)
|
|
227
|
+
return 0;
|
|
228
|
+
return Buffer.byteLength(body.slice(start, end), 'utf8');
|
|
229
|
+
}
|
|
230
|
+
function hashOf(s) {
|
|
231
|
+
return createHash('sha256').update(s, 'utf8').digest('hex');
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=chunker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker.js","sourceRoot":"","sources":["../../../src/adaptive/chunker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable folding strategy interface for the adaptive-resolution picker.
|
|
3
|
+
*
|
|
4
|
+
* The picker is a generic orchestrator; the policy (which group to raise next,
|
|
5
|
+
* whether to lower, whether to request a missing summary) lives in a strategy
|
|
6
|
+
* object. V1 ships `FlatProfileStrategy` (default, level-equalizing) and
|
|
7
|
+
* `OldestFirstStrategy` (chronological, matches the rev-1 design).
|
|
8
|
+
*
|
|
9
|
+
* See `docs/adaptive-resolution-design.md` §3.5.
|
|
10
|
+
*/
|
|
11
|
+
import type { MessageId } from '../types/message.js';
|
|
12
|
+
import type { SummaryEntry } from '../types/strategy.js';
|
|
13
|
+
/** Identifier of a summary in the archive (e.g., "L2-15"). */
|
|
14
|
+
export type SummaryId = string;
|
|
15
|
+
/** Identifier of a chunk — for now equivalent to a MessageId. */
|
|
16
|
+
export type ChunkId = MessageId;
|
|
17
|
+
/** Half-open range of chunks, identified by message IDs. */
|
|
18
|
+
export interface ChunkRange {
|
|
19
|
+
firstChunkId: ChunkId;
|
|
20
|
+
lastChunkId: ChunkId;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* A view of a single chunk for strategy decisions.
|
|
24
|
+
*
|
|
25
|
+
* Strategies should treat ChunkView as read-only. Use the picker's
|
|
26
|
+
* apply() to commit fold operations rather than mutating fields directly.
|
|
27
|
+
*/
|
|
28
|
+
export interface ChunkView {
|
|
29
|
+
id: ChunkId;
|
|
30
|
+
/** Position in source order (lower = older). */
|
|
31
|
+
sequence: number;
|
|
32
|
+
/** Approximate tokens of raw content. */
|
|
33
|
+
rawTokens: number;
|
|
34
|
+
/** Current display resolution. 0 = raw, k>0 = L_k summary. */
|
|
35
|
+
currentResolution: number;
|
|
36
|
+
/** Whether the picker is forbidden from changing currentResolution. */
|
|
37
|
+
lockedByAgent: boolean;
|
|
38
|
+
/** If this chunk is a shard of a larger logical message, the group id. */
|
|
39
|
+
bodyGroupId?: string;
|
|
40
|
+
/** True if this chunk is in the head window (kept raw). */
|
|
41
|
+
inHead: boolean;
|
|
42
|
+
/** True if this chunk is in the tail window (kept raw). */
|
|
43
|
+
inTail: boolean;
|
|
44
|
+
/** True if this chunk is pinned. */
|
|
45
|
+
pinned: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* The chunk's ancestor at level k in the summary tree, if one exists.
|
|
48
|
+
* Returns null if the level has not been produced for this chunk's range.
|
|
49
|
+
*/
|
|
50
|
+
ancestorAt(level: number): SummaryEntry | null;
|
|
51
|
+
/** The highest level k for which a summary exists covering this chunk. */
|
|
52
|
+
maxAvailableLevel(): number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Read-only view passed to a FoldingStrategy.
|
|
56
|
+
*/
|
|
57
|
+
export interface FoldingState {
|
|
58
|
+
/** All chunks in source order. */
|
|
59
|
+
chunks(): readonly ChunkView[];
|
|
60
|
+
/** Chunks that are foldable — not in head/tail, not pinned, not locked. */
|
|
61
|
+
foldableMiddle(): readonly ChunkView[];
|
|
62
|
+
/** Look up a summary by id. */
|
|
63
|
+
getSummary(id: SummaryId): SummaryEntry | null;
|
|
64
|
+
/** All leaf chunks under a given summary (recursively walks the tree). */
|
|
65
|
+
leavesUnder(groupRoot: SummaryId): readonly ChunkView[];
|
|
66
|
+
/**
|
|
67
|
+
* Estimate of total tokens that would be rendered with the current
|
|
68
|
+
* per-chunk resolutions. Cached; recompute via recomputeTokens() after
|
|
69
|
+
* applying a fold op.
|
|
70
|
+
*/
|
|
71
|
+
tokenCount(): number;
|
|
72
|
+
}
|
|
73
|
+
/** A fold operation returned by FoldingStrategy.selectNextFold. */
|
|
74
|
+
export type FoldOp = {
|
|
75
|
+
kind: 'raise';
|
|
76
|
+
/** L_{k+1} summary id that becomes the new visible level for the group. */
|
|
77
|
+
groupRoot: SummaryId;
|
|
78
|
+
} | {
|
|
79
|
+
kind: 'lower';
|
|
80
|
+
/** L_k summary id whose group is being lowered to (k-1). V2 only. */
|
|
81
|
+
groupRoot: SummaryId;
|
|
82
|
+
} | {
|
|
83
|
+
kind: 'produce';
|
|
84
|
+
/** Level to produce (e.g., 2 for L2). */
|
|
85
|
+
level: number;
|
|
86
|
+
/** Range of chunks the produced summary should cover. */
|
|
87
|
+
range: ChunkRange;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Budget context for the strategy.
|
|
91
|
+
*/
|
|
92
|
+
export interface FoldingBudget {
|
|
93
|
+
/** Hard maximum tokens (model context - response reserve). */
|
|
94
|
+
totalBudget: number;
|
|
95
|
+
/** Soft target tokens (totalBudget * (1 - slack)). Strategies fold until at or below. */
|
|
96
|
+
targetBudget: number;
|
|
97
|
+
/** Slack ratio (e.g., 0.1 for 10%). */
|
|
98
|
+
slack: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Pluggable strategy interface.
|
|
102
|
+
*
|
|
103
|
+
* The picker calls selectNextFold() repeatedly until it returns null. On
|
|
104
|
+
* each call, the strategy inspects current state and returns one operation:
|
|
105
|
+
* - 'raise' — fold a group up one level (the picker applies it and re-asks)
|
|
106
|
+
* - 'lower' — refold down one level (V2; default V1 strategies don't emit this)
|
|
107
|
+
* - 'produce' — request lazy summary production (the picker enqueues it
|
|
108
|
+
* and stops; the next compile will retry)
|
|
109
|
+
*
|
|
110
|
+
* A strategy MUST converge: it must either eventually return null, or emit
|
|
111
|
+
* a 'produce' op that doesn't loop indefinitely. The picker has its own
|
|
112
|
+
* loop-bound safeguard, but well-behaved strategies make it unnecessary.
|
|
113
|
+
*/
|
|
114
|
+
export interface FoldingStrategy {
|
|
115
|
+
readonly name: string;
|
|
116
|
+
selectNextFold(state: FoldingState, budget: FoldingBudget): FoldOp | null;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=folding-strategy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folding-strategy.d.ts","sourceRoot":"","sources":["../../../src/adaptive/folding-strategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,8DAA8D;AAC9D,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,iEAAiE;AACjE,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC;AAEhC,4DAA4D;AAC5D,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uEAAuE;IACvE,aAAa,EAAE,OAAO,CAAC;IACvB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,MAAM,EAAE,OAAO,CAAC;IAChB,2DAA2D;IAC3D,MAAM,EAAE,OAAO,CAAC;IAChB,oCAAoC;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAAC;IAC/C,0EAA0E;IAC1E,iBAAiB,IAAI,MAAM,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,MAAM,IAAI,SAAS,SAAS,EAAE,CAAC;IAC/B,2EAA2E;IAC3E,cAAc,IAAI,SAAS,SAAS,EAAE,CAAC;IACvC,+BAA+B;IAC/B,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,YAAY,GAAG,IAAI,CAAC;IAC/C,0EAA0E;IAC1E,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,SAAS,EAAE,CAAC;IACxD;;;;OAIG;IACH,UAAU,IAAI,MAAM,CAAC;CACtB;AAED,mEAAmE;AACnE,MAAM,MAAM,MAAM,GACd;IACE,IAAI,EAAE,OAAO,CAAC;IACd,2EAA2E;IAC3E,SAAS,EAAE,SAAS,CAAC;CACtB,GACD;IACE,IAAI,EAAE,OAAO,CAAC;IACd,qEAAqE;IACrE,SAAS,EAAE,SAAS,CAAC;CACtB,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,KAAK,EAAE,UAAU,CAAC;CACnB,CAAC;AAEN;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,yFAAyF;IACzF,YAAY,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAAC;CAC3E"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable folding strategy interface for the adaptive-resolution picker.
|
|
3
|
+
*
|
|
4
|
+
* The picker is a generic orchestrator; the policy (which group to raise next,
|
|
5
|
+
* whether to lower, whether to request a missing summary) lives in a strategy
|
|
6
|
+
* object. V1 ships `FlatProfileStrategy` (default, level-equalizing) and
|
|
7
|
+
* `OldestFirstStrategy` (chronological, matches the rev-1 design).
|
|
8
|
+
*
|
|
9
|
+
* See `docs/adaptive-resolution-design.md` §3.5.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=folding-strategy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folding-strategy.js","sourceRoot":"","sources":["../../../src/adaptive/folding-strategy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive-resolution context management subsystem.
|
|
3
|
+
*
|
|
4
|
+
* See `docs/adaptive-resolution-design.md` for the architectural overview.
|
|
5
|
+
*
|
|
6
|
+
* Status: V1 implementation, not yet wired into `AutobiographicalStrategy`.
|
|
7
|
+
* Modules in this directory are exercised via the test harness in
|
|
8
|
+
* `test/adaptive/` and will be integrated into the strategy in a follow-up
|
|
9
|
+
* once the design is validated against real workloads.
|
|
10
|
+
*/
|
|
11
|
+
export { chunkMessage, DEFAULT_CHUNKER_OPTIONS, type ChunkerOptions, type Shard, type ShardingResult, } from './chunker.js';
|
|
12
|
+
export type { FoldingStrategy, FoldingState, FoldingBudget, FoldOp, ChunkView, ChunkId, ChunkRange, SummaryId, } from './folding-strategy.js';
|
|
13
|
+
export { FlatProfileStrategy } from './strategies/flat-profile.js';
|
|
14
|
+
export { OldestFirstStrategy } from './strategies/oldest-first.js';
|
|
15
|
+
export { Picker, OverBudgetError, type PickerInputs, type PickerResult, type PickerChunk, } from './picker.js';
|
|
16
|
+
export { concatBodyGroups, placeholderRecallText } from './render.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adaptive/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EACL,YAAY,EACZ,uBAAuB,EACvB,KAAK,cAAc,EACnB,KAAK,KAAK,EACV,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AAGtB,YAAY,EACV,eAAe,EACf,YAAY,EACZ,aAAa,EACb,MAAM,EACN,SAAS,EACT,OAAO,EACP,UAAU,EACV,SAAS,GACV,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAGnE,OAAO,EACL,MAAM,EACN,eAAe,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive-resolution context management subsystem.
|
|
3
|
+
*
|
|
4
|
+
* See `docs/adaptive-resolution-design.md` for the architectural overview.
|
|
5
|
+
*
|
|
6
|
+
* Status: V1 implementation, not yet wired into `AutobiographicalStrategy`.
|
|
7
|
+
* Modules in this directory are exercised via the test harness in
|
|
8
|
+
* `test/adaptive/` and will be integrated into the strategy in a follow-up
|
|
9
|
+
* once the design is validated against real workloads.
|
|
10
|
+
*/
|
|
11
|
+
// Chunker
|
|
12
|
+
export { chunkMessage, DEFAULT_CHUNKER_OPTIONS, } from './chunker.js';
|
|
13
|
+
// Concrete strategies
|
|
14
|
+
export { FlatProfileStrategy } from './strategies/flat-profile.js';
|
|
15
|
+
export { OldestFirstStrategy } from './strategies/oldest-first.js';
|
|
16
|
+
// Picker
|
|
17
|
+
export { Picker, OverBudgetError, } from './picker.js';
|
|
18
|
+
// Render helpers
|
|
19
|
+
export { concatBodyGroups, placeholderRecallText } from './render.js';
|
|
20
|
+
// Chunk locking is exposed as a method on `AutobiographicalStrategy`
|
|
21
|
+
// (`lockChunk(id)` / `unlockChunk(id)`); there is no standalone lock API at
|
|
22
|
+
// the adaptive layer. The picker honors `PickerChunk.lockedByAgent`.
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adaptive/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,UAAU;AACV,OAAO,EACL,YAAY,EACZ,uBAAuB,GAIxB,MAAM,cAAc,CAAC;AActB,sBAAsB;AACtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAEnE,SAAS;AACT,OAAO,EACL,MAAM,EACN,eAAe,GAIhB,MAAM,aAAa,CAAC;AAErB,iBAAiB;AACjB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEtE,qEAAqE;AACrE,4EAA4E;AAC5E,qEAAqE"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picker orchestrator for the adaptive-resolution design.
|
|
3
|
+
*
|
|
4
|
+
* The picker is a thin loop: build a FoldingState, call strategy.selectNextFold
|
|
5
|
+
* until it returns null, applying 'raise'/'lower' ops as they come and recording
|
|
6
|
+
* 'produce' ops for the caller to enqueue.
|
|
7
|
+
*
|
|
8
|
+
* The picker is stateless across runs — all persistent state lives on the
|
|
9
|
+
* per-chunk fields. Each run builds a fresh FoldingState from the inputs.
|
|
10
|
+
*
|
|
11
|
+
* See `docs/adaptive-resolution-design.md` §5.
|
|
12
|
+
*/
|
|
13
|
+
import type { FoldingStrategy, FoldingBudget, FoldOp, ChunkId, SummaryId } from './folding-strategy.js';
|
|
14
|
+
import type { SummaryEntry } from '../types/strategy.js';
|
|
15
|
+
/**
|
|
16
|
+
* Error raised by the strategy when the picker has folded everything it can
|
|
17
|
+
* and the resulting context still exceeds the hard token budget.
|
|
18
|
+
*
|
|
19
|
+
* The strategy throws this rather than silently dropping entries. The host
|
|
20
|
+
* application decides how to respond — typical responses: raise the budget,
|
|
21
|
+
* switch to a larger-context model, drop the head/tail windows explicitly,
|
|
22
|
+
* or surface a "context too large" error to the user.
|
|
23
|
+
*
|
|
24
|
+
* See `docs/adaptive-resolution-design.md` §3.10.
|
|
25
|
+
*/
|
|
26
|
+
export declare class OverBudgetError extends Error {
|
|
27
|
+
/** The hard budget the strategy was trying to fit under. */
|
|
28
|
+
readonly budget: number;
|
|
29
|
+
/** The token count the strategy could not reduce below `budget`. */
|
|
30
|
+
readonly actual: number;
|
|
31
|
+
/** Diagnostic snapshot of the picker's final state. */
|
|
32
|
+
readonly diagnostics: {
|
|
33
|
+
headTokens: number;
|
|
34
|
+
tailTokens: number;
|
|
35
|
+
middleTokens: number;
|
|
36
|
+
middleChunkCount: number;
|
|
37
|
+
deepestLevel: number;
|
|
38
|
+
};
|
|
39
|
+
constructor(opts: {
|
|
40
|
+
budget: number;
|
|
41
|
+
actual: number;
|
|
42
|
+
diagnostics: OverBudgetError['diagnostics'];
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Minimal chunk representation used by the picker. Real callers will adapt
|
|
47
|
+
* their `StoredMessage` instances to this shape.
|
|
48
|
+
*/
|
|
49
|
+
export interface PickerChunk {
|
|
50
|
+
id: ChunkId;
|
|
51
|
+
sequence: number;
|
|
52
|
+
rawTokens: number;
|
|
53
|
+
currentResolution: number;
|
|
54
|
+
lockedByAgent: boolean;
|
|
55
|
+
bodyGroupId?: string;
|
|
56
|
+
pinned: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* The L1 summary covering this chunk, if any. Higher levels are derived
|
|
59
|
+
* by walking parentId pointers in the summary tree.
|
|
60
|
+
*/
|
|
61
|
+
l1Id?: SummaryId;
|
|
62
|
+
}
|
|
63
|
+
export interface PickerInputs {
|
|
64
|
+
/** All chunks in source order (oldest first). */
|
|
65
|
+
chunks: PickerChunk[];
|
|
66
|
+
/** All summaries, indexed by id. */
|
|
67
|
+
summaries: ReadonlyMap<SummaryId, SummaryEntry>;
|
|
68
|
+
/**
|
|
69
|
+
* Token count for each summary's recall pair. Keyed by summary id.
|
|
70
|
+
* If missing, falls back to SummaryEntry.tokens.
|
|
71
|
+
*/
|
|
72
|
+
recallPairTokens?: ReadonlyMap<SummaryId, number>;
|
|
73
|
+
/** Tokens consumed by the head window (fixed, not foldable). */
|
|
74
|
+
headTokens: number;
|
|
75
|
+
/** Tokens consumed by the tail window (fixed, not foldable). */
|
|
76
|
+
tailTokens: number;
|
|
77
|
+
/** Indices into chunks[] that are inside the head window. */
|
|
78
|
+
headChunkIds: ReadonlySet<ChunkId>;
|
|
79
|
+
/** Indices into chunks[] that are inside the tail window. */
|
|
80
|
+
tailChunkIds: ReadonlySet<ChunkId>;
|
|
81
|
+
}
|
|
82
|
+
export interface PickerResult {
|
|
83
|
+
/** Final resolution state per chunk after all applied ops. */
|
|
84
|
+
finalResolutions: ReadonlyMap<ChunkId, number>;
|
|
85
|
+
/** Ops applied this run, in order. */
|
|
86
|
+
applied: FoldOp[];
|
|
87
|
+
/** Pending production requests the picker emitted. */
|
|
88
|
+
produced: FoldOp[];
|
|
89
|
+
/** Final token count after applying all ops. */
|
|
90
|
+
finalTokens: number;
|
|
91
|
+
/** True if the picker stopped because budget was met. */
|
|
92
|
+
budgetMet: boolean;
|
|
93
|
+
/** True if the picker stopped because no further folds were possible. */
|
|
94
|
+
exhausted: boolean;
|
|
95
|
+
/** Number of iterations the picker performed. */
|
|
96
|
+
iterations: number;
|
|
97
|
+
}
|
|
98
|
+
export declare class Picker {
|
|
99
|
+
private readonly strategy;
|
|
100
|
+
constructor(strategy: FoldingStrategy);
|
|
101
|
+
run(inputs: PickerInputs, budget: FoldingBudget): PickerResult;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=picker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picker.d.ts","sourceRoot":"","sources":["../../../src/adaptive/picker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,eAAe,EAEf,aAAa,EACb,MAAM,EAEN,OAAO,EACP,SAAS,EAEV,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGzD;;;;;;;;;;GAUG;AACH,qBAAa,eAAgB,SAAQ,KAAK;IACxC,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,QAAQ,CAAC,WAAW,EAAE;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;gBAEU,IAAI,EAAE;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC;KAC7C;CAcF;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,oCAAoC;IACpC,SAAS,EAAE,WAAW,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;IAChD;;;OAGG;IACH,gBAAgB,CAAC,EAAE,WAAW,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAClD,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,6DAA6D;IAC7D,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IACnC,6DAA6D;IAC7D,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,8DAA8D;IAC9D,gBAAgB,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC/C,sCAAsC;IACtC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gDAAgD;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,yDAAyD;IACzD,SAAS,EAAE,OAAO,CAAC;IACnB,yEAAyE;IACzE,SAAS,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;CACpB;AAuBD,qBAAa,MAAM;IACL,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,eAAe;IAEtD,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,GAAG,YAAY;CA0C/D"}
|