@brunobracaioli/aibrain 0.1.0-beta.1 → 0.3.0-beta.13

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.
@@ -0,0 +1,263 @@
1
+ import { preflightProviderState } from './state-fitting.mjs';
2
+ export const PROVIDER_WINDOW_MAX_PAIRS = 200;
3
+ export const PROVIDER_WINDOW_MAX_MESSAGES = 1_000;
4
+ export const PROVIDER_WINDOW_MAX_REFERENCES = 200;
5
+ export const PROVIDER_WINDOW_MAX_REQUEST_BYTES = 512 * 1024;
6
+ export const PROVIDER_WINDOW_REQUEST_HEADROOM_BYTES = 16 * 1024;
7
+ export const PROVIDER_WINDOW_MAX_SEARCH_ATTEMPTS = 12;
8
+ export const PROVIDER_WINDOW_MAX_STATE_TOKENS = 25_000;
9
+ export const PROVIDER_WINDOW_MAX_REQUEST_TOKENS = 30_000;
10
+ export const PROVIDER_WINDOW_MAX_RESERVED_TOKENS = 120_000;
11
+ export const PROVIDER_WINDOW_OUTPUT_TOKEN_ALLOWANCE = 4_000;
12
+ function utf8Bytes(value) {
13
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength;
14
+ }
15
+ function anchorMessageIds(messages, preserveRecentMessages) {
16
+ return new Set(messages
17
+ .slice(0, 1)
18
+ .concat(messages.slice(Math.max(0, messages.length - preserveRecentMessages)))
19
+ .map((message) => message.id));
20
+ }
21
+ function pairIdsFromMessages(messages) {
22
+ return new Set(messages.flatMap((message) => [...message.callIds, ...message.resultIds]));
23
+ }
24
+ function compareEligible(a, b) {
25
+ return b.resultChars - a.resultChars || a.callMessage - b.callMessage || a.id.localeCompare(b.id);
26
+ }
27
+ function uniqueMarkerId(messages) {
28
+ const ids = new Set(messages.map((message) => message.id));
29
+ let candidate = 'provider-window-omitted';
30
+ let sequence = 0;
31
+ while (ids.has(candidate))
32
+ candidate = `provider-window-omitted-${++sequence}`;
33
+ return candidate;
34
+ }
35
+ function abbreviatedText(text, limit) {
36
+ if (text.length <= limit)
37
+ return text;
38
+ if (limit <= 0)
39
+ return `[historical message omitted; ${text.length} chars]`;
40
+ const head = Math.max(1, Math.floor(limit * 0.65));
41
+ const tail = Math.max(1, limit - head);
42
+ return `${text.slice(0, head)}…[omitted ${text.length - head - tail} chars]…${text.slice(-tail)}`;
43
+ }
44
+ function boundedMessages(messages, selectedIds, anchorIds, maxMessages) {
45
+ const filtered = messages.map((message) => ({
46
+ ...message,
47
+ callIds: message.callIds.filter((id) => selectedIds.has(id)),
48
+ resultIds: message.resultIds.filter((id) => selectedIds.has(id)),
49
+ }));
50
+ if (filtered.length <= maxMessages)
51
+ return filtered;
52
+ const retained = filtered.filter((message) => anchorIds.has(message.id) || message.callIds.length > 0 || message.resultIds.length > 0);
53
+ if (retained.length > maxMessages)
54
+ return null;
55
+ const output = [];
56
+ const usedIds = new Set(filtered.map((message) => message.id));
57
+ let markerSequence = 0;
58
+ let omitted = 0;
59
+ let markerPending = false;
60
+ const flush = () => {
61
+ if (!markerPending)
62
+ return;
63
+ let markerId = uniqueMarkerId(output);
64
+ while (usedIds.has(markerId))
65
+ markerId = `provider-window-omitted-${++markerSequence}`;
66
+ usedIds.add(markerId);
67
+ output.push({
68
+ id: markerId,
69
+ role: 'user',
70
+ text: `[${omitted} historical messages omitted from provider window]`,
71
+ callIds: [],
72
+ resultIds: [],
73
+ });
74
+ omitted = 0;
75
+ markerPending = false;
76
+ };
77
+ for (const message of filtered) {
78
+ if (anchorIds.has(message.id) || message.callIds.length > 0 || message.resultIds.length > 0) {
79
+ flush();
80
+ output.push(message);
81
+ }
82
+ else {
83
+ omitted += 1;
84
+ markerPending = true;
85
+ }
86
+ }
87
+ flush();
88
+ return output.length <= maxMessages ? output : null;
89
+ }
90
+ function transportMessages(messages, anchorIds, textLimit) {
91
+ if (textLimit === null)
92
+ return messages;
93
+ return messages.map((message) => anchorIds.has(message.id)
94
+ ? message
95
+ : { ...message, text: abbreviatedText(message.text, textLimit) });
96
+ }
97
+ function toWireProjection(input, selectedPairs, messages) {
98
+ return {
99
+ goal: input.goal,
100
+ messages: messages.map((message) => ({
101
+ id: message.id,
102
+ role: message.role,
103
+ text: message.text,
104
+ call_ids: [...message.callIds],
105
+ result_ids: [...message.resultIds],
106
+ })),
107
+ pairs: selectedPairs.map((pair) => ({
108
+ id: pair.id,
109
+ tool: pair.tool,
110
+ input_text: pair.inputText,
111
+ result_chars: pair.resultChars,
112
+ result_is_error: pair.resultIsError,
113
+ eligibility: pair.protectedPair ? 'protected' : pair.eligibility,
114
+ pin_reasons: [...pair.pinReasons],
115
+ })),
116
+ protected_item_count: input.protectedItemCount,
117
+ };
118
+ }
119
+ function toFitProjection(input, selectedPairs, messages) {
120
+ return {
121
+ goal: input.goal,
122
+ protectedItemCount: input.protectedItemCount,
123
+ messages,
124
+ pairs: selectedPairs,
125
+ };
126
+ }
127
+ function candidate(input, rankedEligible, mandatoryIds, count, options, fit) {
128
+ const selectedIds = new Set(mandatoryIds);
129
+ rankedEligible.slice(0, count).forEach((pair) => selectedIds.add(pair.id));
130
+ const selectedPairs = input.pairs.filter((pair) => selectedIds.has(pair.id));
131
+ if (selectedPairs.length > options.maxPairs)
132
+ return null;
133
+ const anchorIds = anchorMessageIds(input.messages, options.preserveRecentMessages);
134
+ const messages = boundedMessages(input.messages, selectedIds, anchorIds, options.maxMessages);
135
+ if (messages === null)
136
+ return null;
137
+ if (messages.some((message) => message.callIds.length > options.maxReferencesPerMessage ||
138
+ message.resultIds.length > options.maxReferencesPerMessage))
139
+ return null;
140
+ const eligible = selectedPairs.filter((pair) => !pair.protectedPair);
141
+ if (count === 0 && eligible.length === 0)
142
+ return null;
143
+ const textLimits = [null, 4_096, 1_024, 256, 64, 0];
144
+ for (const textLimit of textLimits) {
145
+ const transportReadyMessages = transportMessages(messages, anchorIds, textLimit);
146
+ const wire = toWireProjection(input, selectedPairs, transportReadyMessages);
147
+ const requestBytes = utf8Bytes({ projection: wire });
148
+ if (requestBytes + options.requestHeadroomBytes > options.maxRequestBytes)
149
+ continue;
150
+ const preflight = fit(toFitProjection(input, selectedPairs, transportReadyMessages), eligible, options);
151
+ if (!preflight.fits)
152
+ return null;
153
+ const maxBatches = options.maxBatchesPerOperation ?? 4;
154
+ if (!Number.isSafeInteger(maxBatches) || maxBatches < 1 || preflight.batchCount > maxBatches)
155
+ return null;
156
+ const outputAllowance = options.outputTokenAllowance ?? PROVIDER_WINDOW_OUTPUT_TOKEN_ALLOWANCE;
157
+ if (!Number.isSafeInteger(outputAllowance) || outputAllowance < 0)
158
+ return null;
159
+ const estimatedReservationTokens = ((preflight.requestTokens ?? Number.POSITIVE_INFINITY) + outputAllowance) *
160
+ preflight.batchCount;
161
+ if (estimatedReservationTokens > options.maxReservedTokens)
162
+ return null;
163
+ return {
164
+ ok: true,
165
+ projection: wire,
166
+ pairs: selectedPairs,
167
+ deferredPairs: input.pairs.filter((pair) => !selectedIds.has(pair.id)),
168
+ preflight: { ...preflight, requestBytes, estimatedReservationTokens },
169
+ attempts: 0,
170
+ anchorPairCount: mandatoryIds.size,
171
+ selectedEligibleCount: eligible.length,
172
+ };
173
+ }
174
+ return null;
175
+ }
176
+ export function prepareProviderWindow(input, options, fit = preflightProviderState) {
177
+ const limits = {
178
+ maxPairs: Math.min(options.maxPairs ?? PROVIDER_WINDOW_MAX_PAIRS, PROVIDER_WINDOW_MAX_PAIRS),
179
+ maxMessages: Math.min(options.maxMessages ?? PROVIDER_WINDOW_MAX_MESSAGES, PROVIDER_WINDOW_MAX_MESSAGES),
180
+ maxReferencesPerMessage: Math.min(options.maxReferencesPerMessage ?? PROVIDER_WINDOW_MAX_REFERENCES, PROVIDER_WINDOW_MAX_REFERENCES),
181
+ maxRequestBytes: Math.min(options.maxRequestBytes ?? PROVIDER_WINDOW_MAX_REQUEST_BYTES, PROVIDER_WINDOW_MAX_REQUEST_BYTES),
182
+ requestHeadroomBytes: Math.max(options.requestHeadroomBytes ?? PROVIDER_WINDOW_REQUEST_HEADROOM_BYTES, PROVIDER_WINDOW_REQUEST_HEADROOM_BYTES),
183
+ maxReservedTokens: Math.min(options.maxReservedTokens ?? PROVIDER_WINDOW_MAX_RESERVED_TOKENS, PROVIDER_WINDOW_MAX_RESERVED_TOKENS),
184
+ };
185
+ const fitOptions = {
186
+ ...options,
187
+ preserveRecentMessages: Math.min(Math.max(1, options.preserveRecentMessages), limits.maxMessages),
188
+ maxStateTokens: Math.min(options.maxStateTokens, PROVIDER_WINDOW_MAX_STATE_TOKENS),
189
+ maxRequestTokens: Math.min(options.maxRequestTokens, PROVIDER_WINDOW_MAX_REQUEST_TOKENS),
190
+ maxBatchesPerOperation: Math.min(Math.max(1, options.maxBatchesPerOperation ?? 4), 4),
191
+ outputTokenAllowance: Math.min(Math.max(0, options.outputTokenAllowance ?? PROVIDER_WINDOW_OUTPUT_TOKEN_ALLOWANCE), PROVIDER_WINDOW_OUTPUT_TOKEN_ALLOWANCE),
192
+ };
193
+ const anchors = anchorMessageIds(input.messages, fitOptions.preserveRecentMessages);
194
+ const mandatoryIds = pairIdsFromMessages(input.messages.filter((message) => anchors.has(message.id)));
195
+ if (mandatoryIds.size > limits.maxPairs)
196
+ return {
197
+ ok: false,
198
+ reason: 'anchor_overflow',
199
+ attempts: 0,
200
+ anchorPairCount: mandatoryIds.size,
201
+ };
202
+ const rankedEligible = input.pairs
203
+ .filter((pair) => !pair.protectedPair && !mandatoryIds.has(pair.id))
204
+ .sort(compareEligible);
205
+ const mandatoryEligibleCount = input.pairs.filter((pair) => mandatoryIds.has(pair.id) && !pair.protectedPair).length;
206
+ if (rankedEligible.length === 0 && mandatoryEligibleCount === 0)
207
+ return {
208
+ ok: false,
209
+ reason: 'no_feasible_window',
210
+ attempts: 0,
211
+ anchorPairCount: mandatoryIds.size,
212
+ };
213
+ const maxCount = Math.min(rankedEligible.length, limits.maxPairs - mandatoryIds.size);
214
+ let low = mandatoryEligibleCount > 0 ? 0 : 1;
215
+ let high = maxCount;
216
+ let best = null;
217
+ let bestCount = 0;
218
+ let attempts = 0;
219
+ while (low <= high && attempts < PROVIDER_WINDOW_MAX_SEARCH_ATTEMPTS) {
220
+ const count = Math.floor((low + high) / 2);
221
+ const result = candidate(input, rankedEligible, mandatoryIds, count, {
222
+ ...limits,
223
+ preserveRecentMessages: fitOptions.preserveRecentMessages,
224
+ maxStateTokens: fitOptions.maxStateTokens,
225
+ maxRequestTokens: fitOptions.maxRequestTokens,
226
+ maxReservedTokens: limits.maxReservedTokens,
227
+ maxBatchesPerOperation: fitOptions.maxBatchesPerOperation,
228
+ outputTokenAllowance: fitOptions.outputTokenAllowance,
229
+ }, fit);
230
+ attempts += 1;
231
+ if (result !== null) {
232
+ best = { ...result, attempts };
233
+ bestCount = count;
234
+ low = count + 1;
235
+ }
236
+ else
237
+ high = count - 1;
238
+ }
239
+ if (best === null)
240
+ return {
241
+ ok: false,
242
+ reason: 'no_feasible_window',
243
+ attempts,
244
+ anchorPairCount: mandatoryIds.size,
245
+ };
246
+ const final = candidate(input, rankedEligible, mandatoryIds, bestCount, {
247
+ ...limits,
248
+ preserveRecentMessages: fitOptions.preserveRecentMessages,
249
+ maxStateTokens: fitOptions.maxStateTokens,
250
+ maxRequestTokens: fitOptions.maxRequestTokens,
251
+ maxReservedTokens: limits.maxReservedTokens,
252
+ maxBatchesPerOperation: fitOptions.maxBatchesPerOperation,
253
+ outputTokenAllowance: fitOptions.outputTokenAllowance,
254
+ }, fit);
255
+ if (final === null)
256
+ return {
257
+ ok: false,
258
+ reason: 'no_feasible_window',
259
+ attempts: attempts + 1,
260
+ anchorPairCount: mandatoryIds.size,
261
+ };
262
+ return { ...final, attempts: attempts + 1 };
263
+ }
@@ -0,0 +1,47 @@
1
+ export type ProviderFitPreflightReason = 'fits' | 'budget_exceeded';
2
+
3
+ export interface ProviderFitPreflight {
4
+ readonly fits: boolean;
5
+ readonly reason: ProviderFitPreflightReason;
6
+ readonly stage: string | null;
7
+ readonly stateTokens: number | null;
8
+ readonly requestTokens: number | null;
9
+ readonly batchCount: number;
10
+ }
11
+
12
+ export interface ProviderFitProjectionMessage {
13
+ readonly id: string;
14
+ readonly role: 'user' | 'assistant';
15
+ readonly text: string;
16
+ readonly callIds: readonly string[];
17
+ readonly resultIds: readonly string[];
18
+ }
19
+
20
+ export interface ProviderFitProjectionPair {
21
+ readonly id: string;
22
+ readonly tool: string;
23
+ readonly inputText: string;
24
+ readonly resultChars: number;
25
+ readonly resultIsError: boolean;
26
+ readonly eligibility: 'reproducible_read' | 'compatible_tool' | 'protected';
27
+ readonly pinReasons: readonly string[];
28
+ }
29
+
30
+ export interface ProviderFitProjection {
31
+ readonly goal: string;
32
+ readonly messages: readonly ProviderFitProjectionMessage[];
33
+ readonly pairs: readonly ProviderFitProjectionPair[];
34
+ readonly protectedItemCount: number;
35
+ }
36
+
37
+ export interface ProviderFitOptions {
38
+ readonly preserveRecentMessages: number;
39
+ readonly maxStateTokens: number;
40
+ readonly maxRequestTokens: number;
41
+ }
42
+
43
+ export function preflightProviderState(
44
+ projection: ProviderFitProjection,
45
+ eligible: readonly ProviderFitProjectionPair[],
46
+ options: ProviderFitOptions,
47
+ ): ProviderFitPreflight;
@@ -0,0 +1,324 @@
1
+ export const UTF8_ESTIMATOR_VERSION = 'utf8-bytes-v1';
2
+ export const FIT_OVERHEAD_TOKENS = 256;
3
+ export const FIT_MAX_BATCHES = 4;
4
+ const textEncoder = typeof TextEncoder === 'function' ? new TextEncoder() : undefined;
5
+ function utf8ByteLength(value) {
6
+ if (textEncoder !== undefined) return textEncoder.encode(value).byteLength;
7
+ const buffer = globalThis.Buffer;
8
+ if (buffer !== undefined) return buffer.byteLength(value, 'utf8');
9
+ throw new Error('utf8_encoder_unavailable');
10
+ }
11
+ export function estimateUtf8Tokens(value) {
12
+ return utf8ByteLength(JSON.stringify(value));
13
+ }
14
+ export function freezeProviderState(value) {
15
+ if (typeof value === 'object' && value !== null) {
16
+ Object.freeze(value);
17
+ for (const nested of Object.values(value)) freezeProviderState(nested);
18
+ }
19
+ return value;
20
+ }
21
+ function marker(text, head, tail = head) {
22
+ const codePoints = Array.from(text);
23
+ if (codePoints.length <= head + tail + 32) return text;
24
+ return `${codePoints.slice(0, head).join('')}…[omitted ${codePoints.length - head - tail} chars]…${codePoints.slice(-tail).join('')}`;
25
+ }
26
+ function takeCodePoints(text, count) {
27
+ return Array.from(text).slice(0, count).join('');
28
+ }
29
+ function codePointLength(text) {
30
+ return Array.from(text).length;
31
+ }
32
+ function stateFor(
33
+ projection,
34
+ pairs,
35
+ messages = projection.messages,
36
+ compactIds = new Set(),
37
+ omittedProtectedPairCount = 0,
38
+ ) {
39
+ return freezeProviderState({
40
+ goal: projection.goal,
41
+ messages: messages.map((message) => ({
42
+ id: message.id,
43
+ role: message.role,
44
+ text: message.text,
45
+ call_ids: [...message.callIds],
46
+ result_ids: [...message.resultIds],
47
+ })),
48
+ pairs: pairs.map((pair) =>
49
+ compactIds.has(pair.id)
50
+ ? {
51
+ id: pair.id,
52
+ compact_record: `${pair.id}|${pair.tool}|${takeCodePoints(pair.inputText, 12)}|result_chars=${pair.resultChars}|error=${pair.resultIsError}`,
53
+ }
54
+ : {
55
+ id: pair.id,
56
+ tool: pair.tool,
57
+ input_text: pair.inputText,
58
+ result_chars: pair.resultChars,
59
+ result_is_error: pair.resultIsError,
60
+ },
61
+ ),
62
+ protected_item_count: projection.protectedItemCount,
63
+ ...(omittedProtectedPairCount > 0
64
+ ? { omitted_protected_pair_count: omittedProtectedPairCount }
65
+ : {}),
66
+ });
67
+ }
68
+ export function compactionQuestions(pairs) {
69
+ return Object.fromEntries(
70
+ pairs.flatMap((pair) => [
71
+ [
72
+ `call_${pair.id}`,
73
+ {
74
+ type: 'noul',
75
+ instructions: `Should the call occurrence and input for pair ${pair.id} (${pair.tool}) remain useful for continuing the approved goal? Answer yes only when retaining the call is needed.`,
76
+ },
77
+ ],
78
+ [
79
+ `result_${pair.id}`,
80
+ {
81
+ type: 'noul',
82
+ instructions: `Should the full result for pair ${pair.id} (${pair.resultChars} characters) remain useful for continuing the approved goal? Answer yes only when the result itself is needed.`,
83
+ },
84
+ ],
85
+ ]),
86
+ );
87
+ }
88
+ export function estimateCompactionQuestions(pairs) {
89
+ return estimateUtf8Tokens(compactionQuestions(pairs));
90
+ }
91
+ function makeBatches(eligible, stateTokens, maxRequestTokens) {
92
+ const available = maxRequestTokens - stateTokens - FIT_OVERHEAD_TOKENS;
93
+ if (available <= 0) return null;
94
+ const batches = [];
95
+ let current = [];
96
+ let currentTokens = 0;
97
+ for (const pair of eligible) {
98
+ const pairTokens = estimateCompactionQuestions([pair]);
99
+ if (pairTokens > available) return null;
100
+ if (current.length > 0 && currentTokens + pairTokens > available) {
101
+ batches.push(current);
102
+ current = [];
103
+ currentTokens = 0;
104
+ }
105
+ current.push(pair);
106
+ currentTokens += pairTokens;
107
+ }
108
+ if (current.length > 0) batches.push(current);
109
+ if (batches.length > FIT_MAX_BATCHES) return null;
110
+ return {
111
+ batches,
112
+ requestTokens: batches.reduce(
113
+ (max, batch) =>
114
+ Math.max(max, stateTokens + FIT_OVERHEAD_TOKENS + estimateCompactionQuestions(batch)),
115
+ 0,
116
+ ),
117
+ };
118
+ }
119
+ function isPinnedMessage(projection, messageIndex, recent, protectedIds, pinnedMessageIds) {
120
+ if (pinnedMessageIds.has(projection.messages[messageIndex]?.id ?? '')) return true;
121
+ return (
122
+ projection.messages[messageIndex]?.callIds.some((id) => protectedIds.has(id)) === true ||
123
+ projection.messages[messageIndex]?.resultIds.some((id) => protectedIds.has(id)) === true
124
+ );
125
+ }
126
+ function stageProjection(projection, eligibleIds, recent, stage, pinnedMessageIds) {
127
+ const protectedIds = new Set(
128
+ projection.pairs.filter((pair) => !eligibleIds.has(pair.id)).map((pair) => pair.id),
129
+ );
130
+ const pairs = projection.pairs.map((pair) => {
131
+ if (!eligibleIds.has(pair.id)) return pair;
132
+ if (stage === 'inputs_1000')
133
+ return { ...pair, inputText: takeCodePoints(pair.inputText, 1_000) };
134
+ if (stage === 'inputs_200') return { ...pair, inputText: takeCodePoints(pair.inputText, 200) };
135
+ if (stage === 'inputs_60') return { ...pair, inputText: takeCodePoints(pair.inputText, 60) };
136
+ if (stage === 'text_head_tail') return { ...pair, inputText: marker(pair.inputText, 30, 30) };
137
+ if (stage === 'text_omitted')
138
+ return codePointLength(pair.inputText) > 60
139
+ ? { ...pair, inputText: `[input omitted; ${codePointLength(pair.inputText)} chars]` }
140
+ : pair;
141
+ if (stage === 'compact_calls')
142
+ return { ...pair, inputText: `${pair.tool}(${marker(pair.inputText, 24, 12)})` };
143
+ return pair;
144
+ });
145
+ let messages = projection.messages;
146
+ if (stage === 'text_head_tail') {
147
+ messages = projection.messages.map((message, index) =>
148
+ isPinnedMessage(projection, index, recent, protectedIds, pinnedMessageIds)
149
+ ? message
150
+ : { ...message, text: marker(message.text, 80, 40) },
151
+ );
152
+ } else if (stage === 'text_omitted') {
153
+ messages = projection.messages.map((message, index) =>
154
+ isPinnedMessage(projection, index, recent, protectedIds, pinnedMessageIds)
155
+ ? message
156
+ : codePointLength(message.text) > 120
157
+ ? { ...message, text: `[message omitted; ${codePointLength(message.text)} chars]` }
158
+ : message,
159
+ );
160
+ } else if (stage === 'compact_calls') {
161
+ messages = projection.messages.map((message, index) =>
162
+ // At this stage pair identity and question binding live in `pairs`. Keep
163
+ // protected message wiring available while abbreviating only its text.
164
+ // A later fitting stage folds this wiring only if the complete state still
165
+ // cannot fit, so ordinary histories retain more useful provider context.
166
+ isPinnedMessage(projection, index, recent, protectedIds, pinnedMessageIds)
167
+ ? message
168
+ : {
169
+ ...message,
170
+ text: `call:${message.callIds.join(',')};result:${message.resultIds.join(',')}`,
171
+ },
172
+ );
173
+ } else if (stage === 'callless_omitted') {
174
+ messages = projection.messages.filter(
175
+ (message, index) =>
176
+ isPinnedMessage(projection, index, recent, protectedIds, pinnedMessageIds) ||
177
+ message.callIds.length > 0 ||
178
+ message.resultIds.length > 0,
179
+ );
180
+ } else if (stage === 'runs_folded') {
181
+ const folded = [];
182
+ const usedIds = new Set(projection.messages.map((message) => message.id));
183
+ let markerSequence = 0;
184
+ let omittedRun;
185
+ let omittedCount = 0;
186
+ const flushOmitted = () => {
187
+ if (omittedRun === undefined) return;
188
+ let id = 'provider-history-omitted';
189
+ while (usedIds.has(id)) id = `provider-history-omitted-${++markerSequence}`;
190
+ usedIds.add(id);
191
+ folded.push({
192
+ id,
193
+ role: omittedRun.role,
194
+ text: `[${omittedCount} historical messages omitted from provider metadata]`,
195
+ callIds: [],
196
+ resultIds: [],
197
+ });
198
+ omittedRun = undefined;
199
+ omittedCount = 0;
200
+ };
201
+ for (let index = 0; index < projection.messages.length; index += 1) {
202
+ const message = projection.messages[index];
203
+ if (message === undefined) continue;
204
+ if (pinnedMessageIds.has(message.id)) {
205
+ flushOmitted();
206
+ folded.push(message);
207
+ } else {
208
+ omittedRun ??= message;
209
+ omittedCount += 1;
210
+ }
211
+ }
212
+ flushOmitted();
213
+ messages = folded;
214
+ }
215
+ return { pairs, messages };
216
+ }
217
+ export function fitProviderState(projection, eligible, options) {
218
+ const beforeState = stateFor(projection, projection.pairs);
219
+ const beforeTokens = estimateUtf8Tokens(beforeState);
220
+ const eligibleIds = new Set(eligible.map((pair) => pair.id));
221
+ const pinnedMessageIds = new Set(
222
+ projection.messages
223
+ .slice(0, 1)
224
+ .concat(
225
+ projection.messages.slice(
226
+ Math.max(0, projection.messages.length - options.preserveRecentMessages),
227
+ ),
228
+ )
229
+ .map((message) => message.id),
230
+ );
231
+ const stages = [
232
+ 'original',
233
+ 'inputs_1000',
234
+ 'inputs_200',
235
+ 'inputs_60',
236
+ 'text_head_tail',
237
+ 'text_omitted',
238
+ 'compact_calls',
239
+ 'callless_omitted',
240
+ 'runs_folded',
241
+ ];
242
+ let current = { pairs: projection.pairs, messages: projection.messages };
243
+ for (const [stageIndex, stage] of stages.entries()) {
244
+ if (stage !== 'original')
245
+ current = stageProjection(
246
+ { ...projection, pairs: current.pairs, messages: current.messages },
247
+ eligibleIds,
248
+ options.preserveRecentMessages,
249
+ stage,
250
+ pinnedMessageIds,
251
+ );
252
+ let transformed = current;
253
+ let omittedProtectedPairCount = 0;
254
+ if (stage === 'runs_folded') {
255
+ const retainedPairIds = new Set(
256
+ transformed.messages.flatMap((message) => [...message.callIds, ...message.resultIds]),
257
+ );
258
+ const retainedPairs = transformed.pairs.filter(
259
+ (pair) => eligibleIds.has(pair.id) || retainedPairIds.has(pair.id),
260
+ );
261
+ omittedProtectedPairCount = transformed.pairs.length - retainedPairs.length;
262
+ transformed = { ...transformed, pairs: retainedPairs };
263
+ }
264
+ // Once we reach the explicit compact-calls stage, abbreviate pair metadata
265
+ // while retaining identity, tool, size and outcome. Eligible inputs have
266
+ // already passed through bounded input stages, and the compact record keeps
267
+ // a short prefix for the provider's final question context.
268
+ const compactIds =
269
+ stageIndex >= stages.indexOf('compact_calls')
270
+ ? new Set(projection.pairs.map((pair) => pair.id))
271
+ : new Set();
272
+ const state = stateFor(
273
+ projection,
274
+ transformed.pairs,
275
+ transformed.messages,
276
+ compactIds,
277
+ omittedProtectedPairCount,
278
+ );
279
+ const stateTokens = estimateUtf8Tokens(state);
280
+ if (stateTokens > options.maxStateTokens) continue;
281
+ const packed = makeBatches(eligible, stateTokens, options.maxRequestTokens);
282
+ if (packed === null) continue;
283
+ return {
284
+ state,
285
+ fitting: {
286
+ version: 'state-fit-v1',
287
+ stage,
288
+ estimator: UTF8_ESTIMATOR_VERSION,
289
+ beforeEstimatedStateTokens: beforeTokens,
290
+ afterEstimatedStateTokens: stateTokens,
291
+ },
292
+ batches: packed.batches,
293
+ stateTokens,
294
+ requestTokens: packed.requestTokens,
295
+ };
296
+ }
297
+ throw new Error('state_fit_budget_exceeded');
298
+ }
299
+ /**
300
+ * Narrow, side-effect-free contract for the Claude provider-window planner.
301
+ * It intentionally does not expose the prepared provider state or transcript.
302
+ */
303
+ export function preflightProviderState(projection, eligible, options) {
304
+ try {
305
+ const fitted = fitProviderState(projection, eligible, options);
306
+ return {
307
+ fits: true,
308
+ reason: 'fits',
309
+ stage: fitted.fitting.stage,
310
+ stateTokens: fitted.stateTokens,
311
+ requestTokens: fitted.requestTokens,
312
+ batchCount: fitted.batches.length,
313
+ };
314
+ } catch {
315
+ return {
316
+ fits: false,
317
+ reason: 'budget_exceeded',
318
+ stage: null,
319
+ stateTokens: null,
320
+ requestTokens: null,
321
+ batchCount: 0,
322
+ };
323
+ }
324
+ }