@evalops/maestro 0.10.30 → 0.10.31
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/agent/compaction/cut-points.d.ts +157 -0
- package/dist/agent/compaction/cut-points.d.ts.map +1 -0
- package/dist/agent/compaction/cut-points.js +341 -0
- package/dist/agent/compaction/cut-points.js.map +1 -0
- package/dist/agent/compaction.d.ts +4 -144
- package/dist/agent/compaction.d.ts.map +1 -1
- package/dist/agent/compaction.js +5 -340
- package/dist/agent/compaction.js.map +1 -1
- package/dist/cli.js +117 -113
- package/dist/node_modules/@evalops/contracts/package.json +1 -1
- package/dist/node_modules/@evalops/tui/package.json +1 -1
- package/dist/tools/parallel-ripgrep.d.ts.map +1 -1
- package/dist/tools/parallel-ripgrep.js +1 -10
- package/dist/tools/parallel-ripgrep.js.map +1 -1
- package/dist/tools/ripgrep-utils.d.ts +1 -0
- package/dist/tools/ripgrep-utils.d.ts.map +1 -1
- package/dist/tools/ripgrep-utils.js +9 -0
- package/dist/tools/ripgrep-utils.js.map +1 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +4 -4
- package/dist/tools/search.js.map +1 -1
- package/dist/version.json +2 -2
- package/package.json +1 -1
|
@@ -33,31 +33,12 @@
|
|
|
33
33
|
* @module agent/compaction
|
|
34
34
|
*/
|
|
35
35
|
import type { PromptProjectDocManifest } from "../config/index.js";
|
|
36
|
-
import type { SessionEntry } from "../session/types.js";
|
|
37
36
|
import { readTool } from "../tools/read.js";
|
|
38
37
|
import { type CompactionHookContext, type CompactionHookService } from "./compaction-hooks.js";
|
|
39
|
-
import type {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export interface CompactionSettings {
|
|
44
|
-
/** Whether auto-compaction is enabled */
|
|
45
|
-
enabled: boolean;
|
|
46
|
-
/**
|
|
47
|
-
* Tokens to reserve for summary generation and safety margin.
|
|
48
|
-
* Default: 16384 (~13k for summary + ~3k safety margin)
|
|
49
|
-
*/
|
|
50
|
-
reserveTokens: number;
|
|
51
|
-
/**
|
|
52
|
-
* Approximate number of recent tokens to preserve verbatim.
|
|
53
|
-
* Default: 20000 (recent context preserved without summarization)
|
|
54
|
-
*/
|
|
55
|
-
keepRecentTokens: number;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Default compaction settings based on research into Claude Code, Codex, and OpenCode.
|
|
59
|
-
*/
|
|
60
|
-
export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings;
|
|
38
|
+
import type { CompactionSettings } from "./compaction/cut-points.js";
|
|
39
|
+
export { DEFAULT_COMPACTION_SETTINGS, adjustBoundaryForToolResults, calculateContextTokens, calculateUsagePercent, findCutPoint, findCutPointWithSplitInfo, findTurnStartIndex, getLastAssistantUsage, getLastAssistantUsageFromEntries, shouldCompact, } from "./compaction/cut-points.js";
|
|
40
|
+
export type { CompactionSettings, CutPointResult, } from "./compaction/cut-points.js";
|
|
41
|
+
import type { Api, AppMessage, AssistantMessage } from "./types.js";
|
|
61
42
|
/**
|
|
62
43
|
* Internal user prompt appended after compaction so the model resumes from the
|
|
63
44
|
* summarized context on the next turn.
|
|
@@ -78,127 +59,7 @@ export interface CompactionResult {
|
|
|
78
59
|
/** Messages to keep verbatim */
|
|
79
60
|
keptMessages: AppMessage[];
|
|
80
61
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Calculate total context tokens from usage metadata.
|
|
83
|
-
*
|
|
84
|
-
* This includes all token types that contribute to context window consumption:
|
|
85
|
-
* - input: Tokens from user/system messages
|
|
86
|
-
* - output: Tokens generated by the model
|
|
87
|
-
* - cacheRead: Tokens read from prompt cache
|
|
88
|
-
* - cacheWrite: Tokens written to prompt cache
|
|
89
|
-
*
|
|
90
|
-
* @param usage - Usage metadata from an assistant message
|
|
91
|
-
* @returns Total context token count
|
|
92
|
-
*/
|
|
93
|
-
export declare function calculateContextTokens(usage: Usage): number;
|
|
94
62
|
type ReadRestoreExecutor = typeof readTool.execute;
|
|
95
|
-
/**
|
|
96
|
-
* Find the last non-aborted assistant message usage from a message array.
|
|
97
|
-
*
|
|
98
|
-
* Used to determine current context window consumption. Walks backwards
|
|
99
|
-
* to find the most recent valid usage metadata.
|
|
100
|
-
*
|
|
101
|
-
* @param messages - Array of application messages
|
|
102
|
-
* @returns Usage metadata from the last assistant message, or null
|
|
103
|
-
*/
|
|
104
|
-
export declare function getLastAssistantUsage(messages: AppMessage[]): Usage | null;
|
|
105
|
-
/**
|
|
106
|
-
* Find the last non-aborted assistant message usage from session entries.
|
|
107
|
-
*
|
|
108
|
-
* @param entries - Array of session entries
|
|
109
|
-
* @returns Usage metadata from the last assistant message, or null
|
|
110
|
-
*/
|
|
111
|
-
export declare function getLastAssistantUsageFromEntries(entries: SessionEntry[]): Usage | null;
|
|
112
|
-
/**
|
|
113
|
-
* Determine if compaction should trigger based on current context usage.
|
|
114
|
-
*
|
|
115
|
-
* Compaction triggers when context usage exceeds `contextWindow - reserveTokens`,
|
|
116
|
-
* leaving enough room for summary generation plus a safety margin.
|
|
117
|
-
*
|
|
118
|
-
* @param contextTokens - Current context window usage in tokens
|
|
119
|
-
* @param contextWindow - Model's total context window size
|
|
120
|
-
* @param settings - Compaction configuration
|
|
121
|
-
* @returns true if compaction should trigger
|
|
122
|
-
*/
|
|
123
|
-
export declare function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean;
|
|
124
|
-
/**
|
|
125
|
-
* Calculate context usage percentage.
|
|
126
|
-
*
|
|
127
|
-
* @param contextTokens - Current context window usage in tokens
|
|
128
|
-
* @param contextWindow - Model's total context window size
|
|
129
|
-
* @returns Percentage of context window used (0-100)
|
|
130
|
-
*/
|
|
131
|
-
export declare function calculateUsagePercent(contextTokens: number, contextWindow: number): number;
|
|
132
|
-
/**
|
|
133
|
-
* Result of finding a cut point, including split turn information.
|
|
134
|
-
*/
|
|
135
|
-
export interface CutPointResult {
|
|
136
|
-
/** Index of first message to keep (messages before this are summarized) */
|
|
137
|
-
firstKeptIndex: number;
|
|
138
|
-
/** Index of user message that starts the turn being split, or -1 if not splitting */
|
|
139
|
-
turnStartIndex: number;
|
|
140
|
-
/** Whether this cut splits a turn (cut point is not a user message) */
|
|
141
|
-
isSplitTurn: boolean;
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Find the user message that starts the turn containing the given index.
|
|
145
|
-
*
|
|
146
|
-
* @param messages - Array of messages
|
|
147
|
-
* @param entryIndex - Index of the message to find the turn start for
|
|
148
|
-
* @param startIndex - First index to consider (inclusive)
|
|
149
|
-
* @returns Index of the user message that starts this turn, or -1 if not found
|
|
150
|
-
*/
|
|
151
|
-
export declare function findTurnStartIndex(messages: AppMessage[], entryIndex: number, startIndex: number): number;
|
|
152
|
-
/**
|
|
153
|
-
* Find the optimal cut point in messages that preserves `keepRecentTokens`.
|
|
154
|
-
*
|
|
155
|
-
* The algorithm:
|
|
156
|
-
* 1. Find all turn boundaries (user message indices)
|
|
157
|
-
* 2. Walk backwards collecting assistant token usage
|
|
158
|
-
* 3. Find where cumulative token difference exceeds keepRecentTokens
|
|
159
|
-
* 4. Return the nearest turn boundary at or before that point
|
|
160
|
-
*
|
|
161
|
-
* This ensures:
|
|
162
|
-
* - Recent context is preserved based on actual token usage, not message count
|
|
163
|
-
* - Turn integrity is maintained (user/assistant/toolResult groups stay together)
|
|
164
|
-
* - More context is kept when messages are small, less when large
|
|
165
|
-
*
|
|
166
|
-
* @param messages - Array of messages to analyze
|
|
167
|
-
* @param startIndex - First index to consider for cutting
|
|
168
|
-
* @param endIndex - Last index to consider (exclusive)
|
|
169
|
-
* @param keepRecentTokens - Approximate tokens to preserve
|
|
170
|
-
* @returns Index of first message to keep (messages before this are summarized)
|
|
171
|
-
*/
|
|
172
|
-
export declare function findCutPoint(messages: AppMessage[], startIndex: number, endIndex: number, keepRecentTokens: number): number;
|
|
173
|
-
/**
|
|
174
|
-
* Find the optimal cut point with split turn information.
|
|
175
|
-
*
|
|
176
|
-
* Similar to findCutPoint but:
|
|
177
|
-
* - Can cut at assistant messages (not just user messages)
|
|
178
|
-
* - Returns information about whether we're splitting a turn
|
|
179
|
-
*
|
|
180
|
-
* This allows for more aggressive compaction when turns are very large,
|
|
181
|
-
* while providing the context needed to summarize the split turn prefix.
|
|
182
|
-
*
|
|
183
|
-
* @param messages - Array of messages to analyze
|
|
184
|
-
* @param startIndex - First index to consider for cutting
|
|
185
|
-
* @param endIndex - Last index to consider (exclusive)
|
|
186
|
-
* @param keepRecentTokens - Approximate tokens to preserve
|
|
187
|
-
* @returns CutPointResult with index and split turn information
|
|
188
|
-
*/
|
|
189
|
-
export declare function findCutPointWithSplitInfo(messages: AppMessage[], startIndex: number, endIndex: number, keepRecentTokens: number): CutPointResult;
|
|
190
|
-
/**
|
|
191
|
-
* Adjust a cut boundary to preserve tool call/result integrity.
|
|
192
|
-
*
|
|
193
|
-
* Tool results must stay paired with their originating assistant message.
|
|
194
|
-
* If a toolResult would be kept but its toolCall would be cut, this
|
|
195
|
-
* moves the boundary back to include the toolCall.
|
|
196
|
-
*
|
|
197
|
-
* @param messages - Array of messages
|
|
198
|
-
* @param boundary - Initial cut point
|
|
199
|
-
* @returns Adjusted boundary that preserves tool integrity
|
|
200
|
-
*/
|
|
201
|
-
export declare function adjustBoundaryForToolResults(messages: AppMessage[], boundary: number): number;
|
|
202
63
|
/**
|
|
203
64
|
* Default prompt for generating compaction summaries.
|
|
204
65
|
*
|
|
@@ -383,5 +244,4 @@ export declare function performCompaction(params: {
|
|
|
383
244
|
renderSummaryText?: (message: AssistantMessage) => string;
|
|
384
245
|
readRestoreExecute?: ReadRestoreExecutor;
|
|
385
246
|
}): Promise<PerformCompactionResult>;
|
|
386
|
-
export {};
|
|
387
247
|
//# sourceMappingURL=compaction.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../src/agent/compaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAQH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../src/agent/compaction.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/compaction.js
CHANGED
|
@@ -41,20 +41,17 @@ import { createLogger } from "../utils/logger.js";
|
|
|
41
41
|
import { expandUserPath } from "../utils/path-validation.js";
|
|
42
42
|
import { runPostCompactionCleanup } from "./compaction-cleanup.js";
|
|
43
43
|
import { createCompactionHookService, } from "./compaction-hooks.js";
|
|
44
|
+
import { DEFAULT_COMPACTION_SETTINGS, adjustBoundaryForToolResults, calculateContextTokens, findCutPoint, findTurnBoundaries, getLastAssistantUsage, } from "./compaction/cut-points.js";
|
|
45
|
+
export { DEFAULT_COMPACTION_SETTINGS, adjustBoundaryForToolResults, calculateContextTokens, calculateUsagePercent, findCutPoint, findCutPointWithSplitInfo, findTurnStartIndex, getLastAssistantUsage, getLastAssistantUsageFromEntries, shouldCompact, } from "./compaction/cut-points.js";
|
|
44
46
|
import { BACKGROUND_TASKS_COMPACTION_CUSTOM_TYPE, HEADLESS_CLIENT_REQUESTS_COMPACTION_CUSTOM_TYPE, MCP_SERVERS_COMPACTION_CUSTOM_TYPE, PLAN_FILE_COMPACTION_CUSTOM_TYPE, PLAN_MODE_COMPACTION_CUSTOM_TYPE, } from "./compaction-restoration.js";
|
|
45
47
|
import { isContextOverflow as isCompactionOverflowMessage, isOverflowErrorMessage, parseOverflowDetails, } from "./context-overflow.js";
|
|
46
48
|
import { convertAppMessageToLlm, createHookMessage, } from "./custom-messages.js";
|
|
47
49
|
import { getPlanFilePathForCompactionRestore } from "./plan-mode.js";
|
|
48
50
|
import { SESSION_START_INITIAL_USER_METADATA_KIND } from "./session-start-metadata.js";
|
|
49
51
|
const logger = createLogger("agent:compaction");
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
export const DEFAULT_COMPACTION_SETTINGS = {
|
|
54
|
-
enabled: true,
|
|
55
|
-
reserveTokens: 16384,
|
|
56
|
-
keepRecentTokens: 20000,
|
|
57
|
-
};
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// Types
|
|
54
|
+
// ============================================================================
|
|
58
55
|
/**
|
|
59
56
|
* Internal user prompt appended after compaction so the model resumes from the
|
|
60
57
|
* summarized context on the next turn.
|
|
@@ -74,38 +71,6 @@ const SKILL_RESTORE_TRUNCATION_MARKER = "\n\n[... restored skill truncated for c
|
|
|
74
71
|
// ============================================================================
|
|
75
72
|
// Token Calculation
|
|
76
73
|
// ============================================================================
|
|
77
|
-
/**
|
|
78
|
-
* Calculate total context tokens from usage metadata.
|
|
79
|
-
*
|
|
80
|
-
* This includes all token types that contribute to context window consumption:
|
|
81
|
-
* - input: Tokens from user/system messages
|
|
82
|
-
* - output: Tokens generated by the model
|
|
83
|
-
* - cacheRead: Tokens read from prompt cache
|
|
84
|
-
* - cacheWrite: Tokens written to prompt cache
|
|
85
|
-
*
|
|
86
|
-
* @param usage - Usage metadata from an assistant message
|
|
87
|
-
* @returns Total context token count
|
|
88
|
-
*/
|
|
89
|
-
export function calculateContextTokens(usage) {
|
|
90
|
-
return usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Extract usage metadata from a message if it's a non-aborted assistant message.
|
|
94
|
-
*
|
|
95
|
-
* @param msg - Any application message
|
|
96
|
-
* @returns Usage metadata or null if not available/applicable
|
|
97
|
-
*/
|
|
98
|
-
function getAssistantUsage(msg) {
|
|
99
|
-
if (msg.role === "assistant") {
|
|
100
|
-
const assistantMsg = msg;
|
|
101
|
-
if (assistantMsg.stopReason !== "aborted" &&
|
|
102
|
-
assistantMsg.stopReason !== "error" &&
|
|
103
|
-
assistantMsg.usage) {
|
|
104
|
-
return assistantMsg.usage;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return null;
|
|
108
|
-
}
|
|
109
74
|
function shouldSkipAssistantCompactionMessage(message) {
|
|
110
75
|
return (message.role === "assistant" &&
|
|
111
76
|
(message.stopReason === "aborted" || message.stopReason === "error"));
|
|
@@ -952,306 +917,6 @@ function truncateSummaryInputForOverflowRetry(messages, overflowErrorMessage) {
|
|
|
952
917
|
? [preamble, markerMessage, ...truncatedBody]
|
|
953
918
|
: [markerMessage, ...truncatedBody];
|
|
954
919
|
}
|
|
955
|
-
/**
|
|
956
|
-
* Find the last non-aborted assistant message usage from a message array.
|
|
957
|
-
*
|
|
958
|
-
* Used to determine current context window consumption. Walks backwards
|
|
959
|
-
* to find the most recent valid usage metadata.
|
|
960
|
-
*
|
|
961
|
-
* @param messages - Array of application messages
|
|
962
|
-
* @returns Usage metadata from the last assistant message, or null
|
|
963
|
-
*/
|
|
964
|
-
export function getLastAssistantUsage(messages) {
|
|
965
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
966
|
-
const usage = getAssistantUsage(messages[i]);
|
|
967
|
-
if (usage)
|
|
968
|
-
return usage;
|
|
969
|
-
}
|
|
970
|
-
return null;
|
|
971
|
-
}
|
|
972
|
-
/**
|
|
973
|
-
* Find the last non-aborted assistant message usage from session entries.
|
|
974
|
-
*
|
|
975
|
-
* @param entries - Array of session entries
|
|
976
|
-
* @returns Usage metadata from the last assistant message, or null
|
|
977
|
-
*/
|
|
978
|
-
export function getLastAssistantUsageFromEntries(entries) {
|
|
979
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
980
|
-
const entry = entries[i];
|
|
981
|
-
if (!entry)
|
|
982
|
-
continue;
|
|
983
|
-
if (entry.type === "message") {
|
|
984
|
-
const usage = getAssistantUsage(entry.message);
|
|
985
|
-
if (usage)
|
|
986
|
-
return usage;
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
return null;
|
|
990
|
-
}
|
|
991
|
-
// ============================================================================
|
|
992
|
-
// Compaction Trigger Detection
|
|
993
|
-
// ============================================================================
|
|
994
|
-
/**
|
|
995
|
-
* Determine if compaction should trigger based on current context usage.
|
|
996
|
-
*
|
|
997
|
-
* Compaction triggers when context usage exceeds `contextWindow - reserveTokens`,
|
|
998
|
-
* leaving enough room for summary generation plus a safety margin.
|
|
999
|
-
*
|
|
1000
|
-
* @param contextTokens - Current context window usage in tokens
|
|
1001
|
-
* @param contextWindow - Model's total context window size
|
|
1002
|
-
* @param settings - Compaction configuration
|
|
1003
|
-
* @returns true if compaction should trigger
|
|
1004
|
-
*/
|
|
1005
|
-
export function shouldCompact(contextTokens, contextWindow, settings) {
|
|
1006
|
-
if (!settings.enabled)
|
|
1007
|
-
return false;
|
|
1008
|
-
return contextTokens > contextWindow - settings.reserveTokens;
|
|
1009
|
-
}
|
|
1010
|
-
/**
|
|
1011
|
-
* Calculate context usage percentage.
|
|
1012
|
-
*
|
|
1013
|
-
* @param contextTokens - Current context window usage in tokens
|
|
1014
|
-
* @param contextWindow - Model's total context window size
|
|
1015
|
-
* @returns Percentage of context window used (0-100)
|
|
1016
|
-
*/
|
|
1017
|
-
export function calculateUsagePercent(contextTokens, contextWindow) {
|
|
1018
|
-
if (contextWindow <= 0)
|
|
1019
|
-
return 0;
|
|
1020
|
-
return (contextTokens / contextWindow) * 100;
|
|
1021
|
-
}
|
|
1022
|
-
/**
|
|
1023
|
-
* Find indices of user messages (turn boundaries) in a message array.
|
|
1024
|
-
*
|
|
1025
|
-
* Turn boundaries are important because we never want to split a turn
|
|
1026
|
-
* (user message + assistant response + tool results). Cutting at a user
|
|
1027
|
-
* message ensures turn integrity.
|
|
1028
|
-
*
|
|
1029
|
-
* @param messages - Array of messages to scan
|
|
1030
|
-
* @param startIndex - First index to consider (inclusive)
|
|
1031
|
-
* @param endIndex - Last index to consider (exclusive)
|
|
1032
|
-
* @returns Array of indices where user messages appear
|
|
1033
|
-
*/
|
|
1034
|
-
function findTurnBoundaries(messages, startIndex, endIndex) {
|
|
1035
|
-
const boundaries = [];
|
|
1036
|
-
for (let i = startIndex; i < endIndex; i++) {
|
|
1037
|
-
if (messages[i].role === "user") {
|
|
1038
|
-
boundaries.push(i);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
return boundaries;
|
|
1042
|
-
}
|
|
1043
|
-
/**
|
|
1044
|
-
* Find the user message that starts the turn containing the given index.
|
|
1045
|
-
*
|
|
1046
|
-
* @param messages - Array of messages
|
|
1047
|
-
* @param entryIndex - Index of the message to find the turn start for
|
|
1048
|
-
* @param startIndex - First index to consider (inclusive)
|
|
1049
|
-
* @returns Index of the user message that starts this turn, or -1 if not found
|
|
1050
|
-
*/
|
|
1051
|
-
export function findTurnStartIndex(messages, entryIndex, startIndex) {
|
|
1052
|
-
for (let i = entryIndex; i >= startIndex; i--) {
|
|
1053
|
-
if (messages[i].role === "user") {
|
|
1054
|
-
return i;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
return -1;
|
|
1058
|
-
}
|
|
1059
|
-
/**
|
|
1060
|
-
* Find the optimal cut point in messages that preserves `keepRecentTokens`.
|
|
1061
|
-
*
|
|
1062
|
-
* The algorithm:
|
|
1063
|
-
* 1. Find all turn boundaries (user message indices)
|
|
1064
|
-
* 2. Walk backwards collecting assistant token usage
|
|
1065
|
-
* 3. Find where cumulative token difference exceeds keepRecentTokens
|
|
1066
|
-
* 4. Return the nearest turn boundary at or before that point
|
|
1067
|
-
*
|
|
1068
|
-
* This ensures:
|
|
1069
|
-
* - Recent context is preserved based on actual token usage, not message count
|
|
1070
|
-
* - Turn integrity is maintained (user/assistant/toolResult groups stay together)
|
|
1071
|
-
* - More context is kept when messages are small, less when large
|
|
1072
|
-
*
|
|
1073
|
-
* @param messages - Array of messages to analyze
|
|
1074
|
-
* @param startIndex - First index to consider for cutting
|
|
1075
|
-
* @param endIndex - Last index to consider (exclusive)
|
|
1076
|
-
* @param keepRecentTokens - Approximate tokens to preserve
|
|
1077
|
-
* @returns Index of first message to keep (messages before this are summarized)
|
|
1078
|
-
*/
|
|
1079
|
-
export function findCutPoint(messages, startIndex, endIndex, keepRecentTokens) {
|
|
1080
|
-
const boundaries = findTurnBoundaries(messages, startIndex, endIndex);
|
|
1081
|
-
if (boundaries.length === 0) {
|
|
1082
|
-
return startIndex; // No user messages, keep everything in range
|
|
1083
|
-
}
|
|
1084
|
-
// Collect assistant usages walking backwards from endIndex
|
|
1085
|
-
const assistantUsages = [];
|
|
1086
|
-
for (let i = endIndex - 1; i >= startIndex; i--) {
|
|
1087
|
-
const usage = getAssistantUsage(messages[i]);
|
|
1088
|
-
if (usage) {
|
|
1089
|
-
assistantUsages.push({
|
|
1090
|
-
index: i,
|
|
1091
|
-
tokens: calculateContextTokens(usage),
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
if (assistantUsages.length === 0) {
|
|
1096
|
-
// No usage info, keep last turn only
|
|
1097
|
-
return boundaries[boundaries.length - 1];
|
|
1098
|
-
}
|
|
1099
|
-
// Walk through and find where cumulative token difference exceeds keepRecentTokens
|
|
1100
|
-
const newestTokens = assistantUsages[0].tokens;
|
|
1101
|
-
let cutIndex = startIndex; // Default: keep everything in range
|
|
1102
|
-
for (let i = 1; i < assistantUsages.length; i++) {
|
|
1103
|
-
const tokenDiff = newestTokens - assistantUsages[i].tokens;
|
|
1104
|
-
if (tokenDiff >= keepRecentTokens) {
|
|
1105
|
-
// Find the turn boundary at or before the assistant we want to keep
|
|
1106
|
-
const lastKeptAssistantIndex = assistantUsages[i - 1].index;
|
|
1107
|
-
for (let b = boundaries.length - 1; b >= 0; b--) {
|
|
1108
|
-
const boundary = boundaries[b];
|
|
1109
|
-
if (boundary !== undefined && boundary <= lastKeptAssistantIndex) {
|
|
1110
|
-
cutIndex = boundary;
|
|
1111
|
-
break;
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
break;
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
return cutIndex;
|
|
1118
|
-
}
|
|
1119
|
-
/**
|
|
1120
|
-
* Find valid cut points (user or assistant messages, not tool results).
|
|
1121
|
-
* Tool results must stay with their preceding tool call.
|
|
1122
|
-
*
|
|
1123
|
-
* @param messages - Array of messages to scan
|
|
1124
|
-
* @param startIndex - First index to consider (inclusive)
|
|
1125
|
-
* @param endIndex - Last index to consider (exclusive)
|
|
1126
|
-
* @returns Array of indices where valid cut points exist
|
|
1127
|
-
*/
|
|
1128
|
-
function findValidCutPoints(messages, startIndex, endIndex) {
|
|
1129
|
-
const cutPoints = [];
|
|
1130
|
-
for (let i = startIndex; i < endIndex; i++) {
|
|
1131
|
-
const role = messages[i].role;
|
|
1132
|
-
// user and assistant are valid cut points
|
|
1133
|
-
// toolResult must stay with its preceding tool call
|
|
1134
|
-
if (role === "user" || role === "assistant") {
|
|
1135
|
-
cutPoints.push(i);
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
return cutPoints;
|
|
1139
|
-
}
|
|
1140
|
-
/**
|
|
1141
|
-
* Find the optimal cut point with split turn information.
|
|
1142
|
-
*
|
|
1143
|
-
* Similar to findCutPoint but:
|
|
1144
|
-
* - Can cut at assistant messages (not just user messages)
|
|
1145
|
-
* - Returns information about whether we're splitting a turn
|
|
1146
|
-
*
|
|
1147
|
-
* This allows for more aggressive compaction when turns are very large,
|
|
1148
|
-
* while providing the context needed to summarize the split turn prefix.
|
|
1149
|
-
*
|
|
1150
|
-
* @param messages - Array of messages to analyze
|
|
1151
|
-
* @param startIndex - First index to consider for cutting
|
|
1152
|
-
* @param endIndex - Last index to consider (exclusive)
|
|
1153
|
-
* @param keepRecentTokens - Approximate tokens to preserve
|
|
1154
|
-
* @returns CutPointResult with index and split turn information
|
|
1155
|
-
*/
|
|
1156
|
-
export function findCutPointWithSplitInfo(messages, startIndex, endIndex, keepRecentTokens) {
|
|
1157
|
-
const cutPoints = findValidCutPoints(messages, startIndex, endIndex);
|
|
1158
|
-
if (cutPoints.length === 0) {
|
|
1159
|
-
return {
|
|
1160
|
-
firstKeptIndex: startIndex,
|
|
1161
|
-
turnStartIndex: -1,
|
|
1162
|
-
isSplitTurn: false,
|
|
1163
|
-
};
|
|
1164
|
-
}
|
|
1165
|
-
// Estimate message sizes for token-based cutting
|
|
1166
|
-
let accumulatedTokens = 0;
|
|
1167
|
-
let cutIndex = startIndex;
|
|
1168
|
-
for (let i = endIndex - 1; i >= startIndex; i--) {
|
|
1169
|
-
const usage = getAssistantUsage(messages[i]);
|
|
1170
|
-
if (usage) {
|
|
1171
|
-
accumulatedTokens = calculateContextTokens(usage);
|
|
1172
|
-
// Using cumulative tokens from the last assistant message
|
|
1173
|
-
if (accumulatedTokens >= keepRecentTokens) {
|
|
1174
|
-
// Find the closest valid cut point at or after this index
|
|
1175
|
-
for (const cp of cutPoints) {
|
|
1176
|
-
if (cp >= i) {
|
|
1177
|
-
cutIndex = cp;
|
|
1178
|
-
break;
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
break;
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
// If no cut point found via tokens, use last assistant's token-based cut
|
|
1186
|
-
if (cutIndex === startIndex) {
|
|
1187
|
-
// Fall back to original algorithm
|
|
1188
|
-
cutIndex = findCutPoint(messages, startIndex, endIndex, keepRecentTokens);
|
|
1189
|
-
}
|
|
1190
|
-
// Determine if this is a split turn
|
|
1191
|
-
const isUserMessage = messages[cutIndex]?.role === "user";
|
|
1192
|
-
const turnStartIndex = isUserMessage
|
|
1193
|
-
? -1
|
|
1194
|
-
: findTurnStartIndex(messages, cutIndex, startIndex);
|
|
1195
|
-
return {
|
|
1196
|
-
firstKeptIndex: cutIndex,
|
|
1197
|
-
turnStartIndex,
|
|
1198
|
-
isSplitTurn: !isUserMessage && turnStartIndex !== -1,
|
|
1199
|
-
};
|
|
1200
|
-
}
|
|
1201
|
-
/**
|
|
1202
|
-
* Adjust a cut boundary to preserve tool call/result integrity.
|
|
1203
|
-
*
|
|
1204
|
-
* Tool results must stay paired with their originating assistant message.
|
|
1205
|
-
* If a toolResult would be kept but its toolCall would be cut, this
|
|
1206
|
-
* moves the boundary back to include the toolCall.
|
|
1207
|
-
*
|
|
1208
|
-
* @param messages - Array of messages
|
|
1209
|
-
* @param boundary - Initial cut point
|
|
1210
|
-
* @returns Adjusted boundary that preserves tool integrity
|
|
1211
|
-
*/
|
|
1212
|
-
export function adjustBoundaryForToolResults(messages, boundary) {
|
|
1213
|
-
let adjusted = boundary;
|
|
1214
|
-
const seenToolCalls = new Set();
|
|
1215
|
-
const missingToolCalls = new Set();
|
|
1216
|
-
// Process assistant message to collect tool calls
|
|
1217
|
-
const processAssistantMessage = (message) => {
|
|
1218
|
-
if (message.role !== "assistant")
|
|
1219
|
-
return;
|
|
1220
|
-
const content = message.content;
|
|
1221
|
-
if (!content)
|
|
1222
|
-
return;
|
|
1223
|
-
for (const part of content) {
|
|
1224
|
-
if (part?.type === "toolCall") {
|
|
1225
|
-
seenToolCalls.add(part.id);
|
|
1226
|
-
if (missingToolCalls.has(part.id)) {
|
|
1227
|
-
missingToolCalls.delete(part.id);
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
};
|
|
1232
|
-
// Process tool result to check for missing tool calls
|
|
1233
|
-
const processToolResultMessage = (message) => {
|
|
1234
|
-
if (message.role !== "toolResult")
|
|
1235
|
-
return;
|
|
1236
|
-
const toolCallId = message.toolCallId;
|
|
1237
|
-
if (!seenToolCalls.has(toolCallId)) {
|
|
1238
|
-
missingToolCalls.add(toolCallId);
|
|
1239
|
-
}
|
|
1240
|
-
};
|
|
1241
|
-
// First pass: process messages we're keeping
|
|
1242
|
-
for (const message of messages.slice(adjusted)) {
|
|
1243
|
-
processAssistantMessage(message);
|
|
1244
|
-
processToolResultMessage(message);
|
|
1245
|
-
}
|
|
1246
|
-
// Walk backwards to find missing tool calls
|
|
1247
|
-
while (missingToolCalls.size > 0 && adjusted > 0) {
|
|
1248
|
-
adjusted -= 1;
|
|
1249
|
-
const candidate = messages[adjusted];
|
|
1250
|
-
processAssistantMessage(candidate);
|
|
1251
|
-
processToolResultMessage(candidate);
|
|
1252
|
-
}
|
|
1253
|
-
return adjusted;
|
|
1254
|
-
}
|
|
1255
920
|
// ============================================================================
|
|
1256
921
|
// Summarization
|
|
1257
922
|
// ============================================================================
|