@dexto/core 1.5.3 → 1.5.5
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/DextoAgent.cjs +190 -1
- package/dist/agent/DextoAgent.d.ts +71 -0
- package/dist/agent/DextoAgent.d.ts.map +1 -1
- package/dist/agent/DextoAgent.js +181 -1
- package/dist/agent/schemas.d.ts +51 -21
- package/dist/agent/schemas.d.ts.map +1 -1
- package/dist/context/compaction/overflow.cjs +6 -10
- package/dist/context/compaction/overflow.d.ts +14 -11
- package/dist/context/compaction/overflow.d.ts.map +1 -1
- package/dist/context/compaction/overflow.js +6 -10
- package/dist/context/compaction/providers/reactive-overflow-provider.cjs +15 -0
- package/dist/context/compaction/providers/reactive-overflow-provider.d.ts +15 -0
- package/dist/context/compaction/providers/reactive-overflow-provider.d.ts.map +1 -1
- package/dist/context/compaction/providers/reactive-overflow-provider.js +15 -0
- package/dist/context/compaction/schemas.cjs +22 -2
- package/dist/context/compaction/schemas.d.ts +45 -0
- package/dist/context/compaction/schemas.d.ts.map +1 -1
- package/dist/context/compaction/schemas.js +22 -2
- package/dist/context/compaction/strategies/reactive-overflow.cjs +168 -26
- package/dist/context/compaction/strategies/reactive-overflow.d.ts +22 -0
- package/dist/context/compaction/strategies/reactive-overflow.d.ts.map +1 -1
- package/dist/context/compaction/strategies/reactive-overflow.js +168 -26
- package/dist/context/compaction/types.d.ts +13 -1
- package/dist/context/compaction/types.d.ts.map +1 -1
- package/dist/context/manager.cjs +278 -31
- package/dist/context/manager.d.ts +192 -5
- package/dist/context/manager.d.ts.map +1 -1
- package/dist/context/manager.js +285 -32
- package/dist/context/types.d.ts +6 -0
- package/dist/context/types.d.ts.map +1 -1
- package/dist/context/utils.cjs +77 -11
- package/dist/context/utils.d.ts +86 -8
- package/dist/context/utils.d.ts.map +1 -1
- package/dist/context/utils.js +71 -11
- package/dist/errors/types.cjs +0 -2
- package/dist/errors/types.d.ts +1 -5
- package/dist/errors/types.d.ts.map +1 -1
- package/dist/errors/types.js +0 -2
- package/dist/events/index.cjs +2 -0
- package/dist/events/index.d.ts +21 -6
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +2 -0
- package/dist/llm/executor/stream-processor.cjs +104 -28
- package/dist/llm/executor/stream-processor.d.ts +7 -0
- package/dist/llm/executor/stream-processor.d.ts.map +1 -1
- package/dist/llm/executor/stream-processor.js +104 -28
- package/dist/llm/executor/turn-executor.cjs +147 -30
- package/dist/llm/executor/turn-executor.d.ts +28 -10
- package/dist/llm/executor/turn-executor.d.ts.map +1 -1
- package/dist/llm/executor/turn-executor.js +147 -30
- package/dist/llm/formatters/vercel.cjs +36 -28
- package/dist/llm/formatters/vercel.d.ts.map +1 -1
- package/dist/llm/formatters/vercel.js +36 -28
- package/dist/llm/services/factory.cjs +3 -2
- package/dist/llm/services/factory.d.ts +3 -1
- package/dist/llm/services/factory.d.ts.map +1 -1
- package/dist/llm/services/factory.js +3 -2
- package/dist/llm/services/vercel.cjs +31 -6
- package/dist/llm/services/vercel.d.ts +18 -3
- package/dist/llm/services/vercel.d.ts.map +1 -1
- package/dist/llm/services/vercel.js +31 -6
- package/dist/session/chat-session.cjs +29 -13
- package/dist/session/chat-session.d.ts +6 -4
- package/dist/session/chat-session.d.ts.map +1 -1
- package/dist/session/chat-session.js +29 -13
- package/dist/session/session-manager.cjs +11 -0
- package/dist/session/session-manager.d.ts +7 -0
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +11 -0
- package/dist/session/title-generator.cjs +2 -2
- package/dist/session/title-generator.js +2 -2
- package/dist/systemPrompt/in-built-prompts.cjs +36 -0
- package/dist/systemPrompt/in-built-prompts.d.ts +18 -1
- package/dist/systemPrompt/in-built-prompts.d.ts.map +1 -1
- package/dist/systemPrompt/in-built-prompts.js +25 -0
- package/dist/systemPrompt/manager.cjs +22 -0
- package/dist/systemPrompt/manager.d.ts +10 -0
- package/dist/systemPrompt/manager.d.ts.map +1 -1
- package/dist/systemPrompt/manager.js +22 -0
- package/dist/systemPrompt/registry.cjs +2 -1
- package/dist/systemPrompt/registry.d.ts +1 -1
- package/dist/systemPrompt/registry.d.ts.map +1 -1
- package/dist/systemPrompt/registry.js +2 -1
- package/dist/systemPrompt/schemas.cjs +7 -0
- package/dist/systemPrompt/schemas.d.ts +13 -13
- package/dist/systemPrompt/schemas.d.ts.map +1 -1
- package/dist/systemPrompt/schemas.js +7 -0
- package/dist/telemetry/telemetry.cjs +12 -5
- package/dist/telemetry/telemetry.d.ts.map +1 -1
- package/dist/telemetry/telemetry.js +12 -5
- package/dist/utils/index.cjs +3 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/package.json +15 -5
- package/dist/filesystem/error-codes.cjs +0 -53
- package/dist/filesystem/error-codes.d.ts +0 -31
- package/dist/filesystem/error-codes.d.ts.map +0 -1
- package/dist/filesystem/error-codes.js +0 -30
- package/dist/filesystem/errors.cjs +0 -303
- package/dist/filesystem/errors.d.ts +0 -109
- package/dist/filesystem/errors.d.ts.map +0 -1
- package/dist/filesystem/errors.js +0 -280
- package/dist/filesystem/filesystem-service.cjs +0 -534
- package/dist/filesystem/filesystem-service.d.ts +0 -97
- package/dist/filesystem/filesystem-service.d.ts.map +0 -1
- package/dist/filesystem/filesystem-service.js +0 -501
- package/dist/filesystem/index.cjs +0 -37
- package/dist/filesystem/index.d.ts +0 -11
- package/dist/filesystem/index.d.ts.map +0 -1
- package/dist/filesystem/index.js +0 -11
- package/dist/filesystem/path-validator.cjs +0 -250
- package/dist/filesystem/path-validator.d.ts +0 -103
- package/dist/filesystem/path-validator.d.ts.map +0 -1
- package/dist/filesystem/path-validator.js +0 -217
- package/dist/filesystem/types.cjs +0 -16
- package/dist/filesystem/types.d.ts +0 -175
- package/dist/filesystem/types.d.ts.map +0 -1
- package/dist/filesystem/types.js +0 -0
- package/dist/process/command-validator.cjs +0 -554
- package/dist/process/command-validator.d.ts +0 -49
- package/dist/process/command-validator.d.ts.map +0 -1
- package/dist/process/command-validator.js +0 -531
- package/dist/process/error-codes.cjs +0 -47
- package/dist/process/error-codes.d.ts +0 -25
- package/dist/process/error-codes.d.ts.map +0 -1
- package/dist/process/error-codes.js +0 -24
- package/dist/process/errors.cjs +0 -244
- package/dist/process/errors.d.ts +0 -87
- package/dist/process/errors.d.ts.map +0 -1
- package/dist/process/errors.js +0 -221
- package/dist/process/index.cjs +0 -37
- package/dist/process/index.d.ts +0 -11
- package/dist/process/index.d.ts.map +0 -1
- package/dist/process/index.js +0 -11
- package/dist/process/process-service.cjs +0 -497
- package/dist/process/process-service.d.ts +0 -69
- package/dist/process/process-service.d.ts.map +0 -1
- package/dist/process/process-service.js +0 -464
- package/dist/process/types.cjs +0 -16
- package/dist/process/types.d.ts +0 -107
- package/dist/process/types.d.ts.map +0 -1
- package/dist/process/types.js +0 -0
|
@@ -2,11 +2,31 @@ import "../../chunk-PTJYTZNU.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
const CompactionConfigSchema = z.object({
|
|
4
4
|
type: z.string().describe("Compaction provider type"),
|
|
5
|
-
enabled: z.boolean().default(true).describe("Enable or disable compaction")
|
|
5
|
+
enabled: z.boolean().default(true).describe("Enable or disable compaction"),
|
|
6
|
+
/**
|
|
7
|
+
* Maximum context tokens before compaction triggers.
|
|
8
|
+
* When set, overrides the model's context window for compaction threshold.
|
|
9
|
+
* Useful for capping context size below the model's maximum limit.
|
|
10
|
+
* Example: Set to 50000 to trigger compaction at 50K tokens even if
|
|
11
|
+
* the model supports 200K tokens.
|
|
12
|
+
*/
|
|
13
|
+
maxContextTokens: z.number().positive().optional().describe(
|
|
14
|
+
"Maximum context tokens before compaction triggers. Overrides model context window when set."
|
|
15
|
+
),
|
|
16
|
+
/**
|
|
17
|
+
* Percentage of context window that triggers compaction (0.0 to 1.0).
|
|
18
|
+
* Default is 0.9 (90%), leaving a 10% buffer to avoid context degradation.
|
|
19
|
+
* Set lower values to trigger compaction earlier.
|
|
20
|
+
* Example: 0.8 triggers compaction when 80% of context is used.
|
|
21
|
+
*/
|
|
22
|
+
thresholdPercent: z.number().min(0.1).max(1).default(0.9).describe(
|
|
23
|
+
"Percentage of context window that triggers compaction (0.1 to 1.0, default 0.9)"
|
|
24
|
+
)
|
|
6
25
|
}).passthrough().describe("Context compaction configuration");
|
|
7
26
|
const DEFAULT_COMPACTION_CONFIG = {
|
|
8
27
|
type: "reactive-overflow",
|
|
9
|
-
enabled: true
|
|
28
|
+
enabled: true,
|
|
29
|
+
thresholdPercent: 0.9
|
|
10
30
|
};
|
|
11
31
|
export {
|
|
12
32
|
CompactionConfigSchema,
|
|
@@ -26,15 +26,36 @@ var import_types = require("../../types.js");
|
|
|
26
26
|
const DEFAULT_OPTIONS = {
|
|
27
27
|
preserveLastNTurns: 2,
|
|
28
28
|
maxSummaryTokens: 2e3,
|
|
29
|
-
summaryPrompt: `You are a conversation summarizer
|
|
30
|
-
- What tasks were attempted and their outcomes
|
|
31
|
-
- Current state and context the assistant needs to remember
|
|
32
|
-
- Any important decisions or information discovered
|
|
33
|
-
- What the user was trying to accomplish
|
|
29
|
+
summaryPrompt: `You are a conversation summarizer creating a structured summary for session continuation.
|
|
34
30
|
|
|
35
|
-
|
|
31
|
+
Analyze the conversation and produce a summary in the following XML format:
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
<session_compaction>
|
|
34
|
+
<conversation_history>
|
|
35
|
+
A concise summary of what happened in the conversation:
|
|
36
|
+
- Tasks attempted and their outcomes (success/failure/in-progress)
|
|
37
|
+
- Important decisions made
|
|
38
|
+
- Key information discovered (file paths, configurations, errors encountered)
|
|
39
|
+
- Tools used and their results
|
|
40
|
+
</conversation_history>
|
|
41
|
+
|
|
42
|
+
<current_task>
|
|
43
|
+
The most recent task or instruction the user requested that may still be in progress.
|
|
44
|
+
Be specific - include the exact request and current status.
|
|
45
|
+
</current_task>
|
|
46
|
+
|
|
47
|
+
<important_context>
|
|
48
|
+
Critical state that must be preserved:
|
|
49
|
+
- File paths being worked on
|
|
50
|
+
- Variable values or configurations
|
|
51
|
+
- Error messages that need addressing
|
|
52
|
+
- Any pending actions or next steps
|
|
53
|
+
</important_context>
|
|
54
|
+
</session_compaction>
|
|
55
|
+
|
|
56
|
+
IMPORTANT: The assistant will continue working based on this summary. Ensure the current_task section clearly states what needs to be done next.
|
|
57
|
+
|
|
58
|
+
Conversation to summarize:
|
|
38
59
|
{conversation}`
|
|
39
60
|
};
|
|
40
61
|
class ReactiveOverflowStrategy {
|
|
@@ -63,15 +84,73 @@ class ReactiveOverflowStrategy {
|
|
|
63
84
|
this.logger.debug("ReactiveOverflowStrategy: History too short, skipping compaction");
|
|
64
85
|
return [];
|
|
65
86
|
}
|
|
87
|
+
let existingSummaryIndex = -1;
|
|
88
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
89
|
+
const msg = history[i];
|
|
90
|
+
if (msg?.metadata?.isSummary === true || msg?.metadata?.isSessionSummary === true) {
|
|
91
|
+
existingSummaryIndex = i;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (existingSummaryIndex !== -1) {
|
|
96
|
+
const messagesAfterSummary = history.slice(existingSummaryIndex + 1);
|
|
97
|
+
if (messagesAfterSummary.length <= 4) {
|
|
98
|
+
this.logger.debug(
|
|
99
|
+
`ReactiveOverflowStrategy: Only ${messagesAfterSummary.length} messages after existing summary, skipping re-compaction`
|
|
100
|
+
);
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
this.logger.info(
|
|
104
|
+
`ReactiveOverflowStrategy: Found existing summary at index ${existingSummaryIndex}, working with ${messagesAfterSummary.length} messages after it`
|
|
105
|
+
);
|
|
106
|
+
return this.compactSubset(messagesAfterSummary, history, existingSummaryIndex);
|
|
107
|
+
}
|
|
66
108
|
const { toSummarize, toKeep } = this.splitHistory(history);
|
|
67
109
|
if (toSummarize.length === 0) {
|
|
68
110
|
this.logger.debug("ReactiveOverflowStrategy: No messages to summarize");
|
|
69
111
|
return [];
|
|
70
112
|
}
|
|
113
|
+
const currentTaskMessage = this.findCurrentTaskMessage(history);
|
|
71
114
|
this.logger.info(
|
|
72
115
|
`ReactiveOverflowStrategy: Summarizing ${toSummarize.length} messages, keeping ${toKeep.length}`
|
|
73
116
|
);
|
|
74
|
-
const summary = await this.generateSummary(toSummarize);
|
|
117
|
+
const summary = await this.generateSummary(toSummarize, currentTaskMessage);
|
|
118
|
+
const summaryMessage = {
|
|
119
|
+
role: "assistant",
|
|
120
|
+
content: [{ type: "text", text: summary }],
|
|
121
|
+
timestamp: Date.now(),
|
|
122
|
+
metadata: {
|
|
123
|
+
isSummary: true,
|
|
124
|
+
summarizedAt: Date.now(),
|
|
125
|
+
originalMessageCount: toSummarize.length,
|
|
126
|
+
originalFirstTimestamp: toSummarize[0]?.timestamp,
|
|
127
|
+
originalLastTimestamp: toSummarize[toSummarize.length - 1]?.timestamp
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
return [summaryMessage];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Handle re-compaction when there's already a summary in history.
|
|
134
|
+
* Only summarizes messages AFTER the existing summary, preventing
|
|
135
|
+
* cascading summaries of summaries.
|
|
136
|
+
*
|
|
137
|
+
* @param messagesAfterSummary Messages after the existing summary
|
|
138
|
+
* @param fullHistory The complete history (for current task detection)
|
|
139
|
+
* @param existingSummaryIndex Index of the existing summary in fullHistory
|
|
140
|
+
* @returns Array with single summary message, or empty if nothing to summarize
|
|
141
|
+
*/
|
|
142
|
+
async compactSubset(messagesAfterSummary, fullHistory, existingSummaryIndex) {
|
|
143
|
+
const { toSummarize, toKeep } = this.splitHistory(messagesAfterSummary);
|
|
144
|
+
if (toSummarize.length === 0) {
|
|
145
|
+
this.logger.debug("ReactiveOverflowStrategy: No messages to summarize in subset");
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
const currentTaskMessage = this.findCurrentTaskMessage(fullHistory);
|
|
149
|
+
this.logger.info(
|
|
150
|
+
`ReactiveOverflowStrategy (re-compact): Summarizing ${toSummarize.length} messages after existing summary, keeping ${toKeep.length}`
|
|
151
|
+
);
|
|
152
|
+
const summary = await this.generateSummary(toSummarize, currentTaskMessage);
|
|
153
|
+
const absoluteOriginalMessageCount = existingSummaryIndex + 1 + toSummarize.length;
|
|
75
154
|
const summaryMessage = {
|
|
76
155
|
role: "assistant",
|
|
77
156
|
content: [{ type: "text", text: summary }],
|
|
@@ -79,16 +158,43 @@ class ReactiveOverflowStrategy {
|
|
|
79
158
|
metadata: {
|
|
80
159
|
isSummary: true,
|
|
81
160
|
summarizedAt: Date.now(),
|
|
82
|
-
|
|
161
|
+
originalMessageCount: absoluteOriginalMessageCount,
|
|
162
|
+
isRecompaction: true,
|
|
163
|
+
// Mark that this is a re-compaction
|
|
83
164
|
originalFirstTimestamp: toSummarize[0]?.timestamp,
|
|
84
165
|
originalLastTimestamp: toSummarize[toSummarize.length - 1]?.timestamp
|
|
85
166
|
}
|
|
86
167
|
};
|
|
87
168
|
return [summaryMessage];
|
|
88
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Find the most recent user message that represents the current task.
|
|
172
|
+
* This helps preserve context about what the user is currently asking for.
|
|
173
|
+
*/
|
|
174
|
+
findCurrentTaskMessage(history) {
|
|
175
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
176
|
+
const msg = history[i];
|
|
177
|
+
if (msg?.role === "user") {
|
|
178
|
+
if (typeof msg.content === "string") {
|
|
179
|
+
return msg.content;
|
|
180
|
+
} else if (Array.isArray(msg.content)) {
|
|
181
|
+
const textParts = msg.content.filter(
|
|
182
|
+
(part) => part.type === "text"
|
|
183
|
+
).map((part) => part.text).join("\n");
|
|
184
|
+
if (textParts.length > 0) {
|
|
185
|
+
return textParts;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
89
192
|
/**
|
|
90
193
|
* Split history into messages to summarize and messages to keep.
|
|
91
194
|
* Keeps the last N turns (user + assistant pairs) intact.
|
|
195
|
+
*
|
|
196
|
+
* For long agentic conversations with many tool calls, this also ensures
|
|
197
|
+
* we don't try to keep too many messages even within preserved turns.
|
|
92
198
|
*/
|
|
93
199
|
splitHistory(history) {
|
|
94
200
|
const turnsToKeep = this.options.preserveLastNTurns;
|
|
@@ -103,20 +209,25 @@ class ReactiveOverflowStrategy {
|
|
|
103
209
|
}
|
|
104
210
|
if (userMessageIndices.length > 0) {
|
|
105
211
|
const splitIndex = userMessageIndices[0];
|
|
106
|
-
if (splitIndex !== void 0) {
|
|
107
|
-
if (splitIndex === 0) {
|
|
108
|
-
return {
|
|
109
|
-
toSummarize: [],
|
|
110
|
-
toKeep: history
|
|
111
|
-
};
|
|
112
|
-
}
|
|
212
|
+
if (splitIndex !== void 0 && splitIndex > 0) {
|
|
113
213
|
return {
|
|
114
214
|
toSummarize: history.slice(0, splitIndex),
|
|
115
215
|
toKeep: history.slice(splitIndex)
|
|
116
216
|
};
|
|
117
217
|
}
|
|
118
218
|
}
|
|
119
|
-
const
|
|
219
|
+
const minKeep = 3;
|
|
220
|
+
const maxKeepPercent = 0.2;
|
|
221
|
+
const keepCount = Math.max(minKeep, Math.floor(history.length * maxKeepPercent));
|
|
222
|
+
if (keepCount >= history.length) {
|
|
223
|
+
return {
|
|
224
|
+
toSummarize: [],
|
|
225
|
+
toKeep: history
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
this.logger.debug(
|
|
229
|
+
`splitHistory: Using fallback - keeping last ${keepCount} of ${history.length} messages`
|
|
230
|
+
);
|
|
120
231
|
return {
|
|
121
232
|
toSummarize: history.slice(0, -keepCount),
|
|
122
233
|
toKeep: history.slice(-keepCount)
|
|
@@ -124,21 +235,36 @@ class ReactiveOverflowStrategy {
|
|
|
124
235
|
}
|
|
125
236
|
/**
|
|
126
237
|
* Generate an LLM summary of the messages.
|
|
238
|
+
*
|
|
239
|
+
* @param messages Messages to summarize
|
|
240
|
+
* @param currentTask The most recent user message (current task context)
|
|
127
241
|
*/
|
|
128
|
-
async generateSummary(messages) {
|
|
242
|
+
async generateSummary(messages, currentTask) {
|
|
129
243
|
const formattedConversation = this.formatMessagesForSummary(messages);
|
|
130
|
-
|
|
244
|
+
let conversationWithContext = formattedConversation;
|
|
245
|
+
if (currentTask) {
|
|
246
|
+
conversationWithContext += `
|
|
247
|
+
|
|
248
|
+
--- CURRENT TASK (most recent user request) ---
|
|
249
|
+
${currentTask}`;
|
|
250
|
+
}
|
|
251
|
+
const prompt = this.options.summaryPrompt.replace(
|
|
252
|
+
"{conversation}",
|
|
253
|
+
conversationWithContext
|
|
254
|
+
);
|
|
131
255
|
try {
|
|
132
256
|
const result = await (0, import_ai.generateText)({
|
|
133
257
|
model: this.model,
|
|
134
258
|
prompt,
|
|
135
259
|
maxOutputTokens: this.options.maxSummaryTokens
|
|
136
260
|
});
|
|
137
|
-
return `[
|
|
261
|
+
return `[Session Compaction Summary]
|
|
138
262
|
${result.text}`;
|
|
139
263
|
} catch (error) {
|
|
140
|
-
this.logger.error(
|
|
141
|
-
|
|
264
|
+
this.logger.error(
|
|
265
|
+
`ReactiveOverflowStrategy: Failed to generate summary - ${error instanceof Error ? error.message : String(error)}`
|
|
266
|
+
);
|
|
267
|
+
return this.createFallbackSummary(messages, currentTask);
|
|
142
268
|
}
|
|
143
269
|
}
|
|
144
270
|
/**
|
|
@@ -174,7 +300,7 @@ ${result.text}`;
|
|
|
174
300
|
/**
|
|
175
301
|
* Create a fallback summary if LLM call fails.
|
|
176
302
|
*/
|
|
177
|
-
createFallbackSummary(messages) {
|
|
303
|
+
createFallbackSummary(messages, currentTask) {
|
|
178
304
|
const userMessages = messages.filter((m) => m.role === "user");
|
|
179
305
|
const assistantWithTools = messages.filter(
|
|
180
306
|
(m) => (0, import_types.isAssistantMessage)(m) && !!m.toolCalls && m.toolCalls.length > 0
|
|
@@ -190,9 +316,25 @@ ${result.text}`;
|
|
|
190
316
|
assistantWithTools.flatMap((m) => m.toolCalls.map((tc) => tc.function.name))
|
|
191
317
|
)
|
|
192
318
|
].join(", ");
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
319
|
+
let fallback = `[Session Compaction Summary - Fallback]
|
|
320
|
+
<session_compaction>
|
|
321
|
+
<conversation_history>
|
|
322
|
+
User discussed: ${userTopics || "various topics"}
|
|
323
|
+
Tools used: ${toolsUsed || "none"}
|
|
324
|
+
Messages summarized: ${messages.length}
|
|
325
|
+
</conversation_history>`;
|
|
326
|
+
if (currentTask) {
|
|
327
|
+
fallback += `
|
|
328
|
+
<current_task>
|
|
329
|
+
${currentTask.slice(0, 500)}${currentTask.length > 500 ? "..." : ""}
|
|
330
|
+
</current_task>`;
|
|
331
|
+
}
|
|
332
|
+
fallback += `
|
|
333
|
+
<important_context>
|
|
334
|
+
Note: This is a fallback summary due to LLM error. Context may be incomplete.
|
|
335
|
+
</important_context>
|
|
336
|
+
</session_compaction>`;
|
|
337
|
+
return fallback;
|
|
196
338
|
}
|
|
197
339
|
}
|
|
198
340
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -60,13 +60,35 @@ export declare class ReactiveOverflowStrategy implements ICompactionStrategy {
|
|
|
60
60
|
* @returns Array with single summary message to add, or empty if nothing to summarize
|
|
61
61
|
*/
|
|
62
62
|
compact(history: readonly InternalMessage[]): Promise<InternalMessage[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Handle re-compaction when there's already a summary in history.
|
|
65
|
+
* Only summarizes messages AFTER the existing summary, preventing
|
|
66
|
+
* cascading summaries of summaries.
|
|
67
|
+
*
|
|
68
|
+
* @param messagesAfterSummary Messages after the existing summary
|
|
69
|
+
* @param fullHistory The complete history (for current task detection)
|
|
70
|
+
* @param existingSummaryIndex Index of the existing summary in fullHistory
|
|
71
|
+
* @returns Array with single summary message, or empty if nothing to summarize
|
|
72
|
+
*/
|
|
73
|
+
private compactSubset;
|
|
74
|
+
/**
|
|
75
|
+
* Find the most recent user message that represents the current task.
|
|
76
|
+
* This helps preserve context about what the user is currently asking for.
|
|
77
|
+
*/
|
|
78
|
+
private findCurrentTaskMessage;
|
|
63
79
|
/**
|
|
64
80
|
* Split history into messages to summarize and messages to keep.
|
|
65
81
|
* Keeps the last N turns (user + assistant pairs) intact.
|
|
82
|
+
*
|
|
83
|
+
* For long agentic conversations with many tool calls, this also ensures
|
|
84
|
+
* we don't try to keep too many messages even within preserved turns.
|
|
66
85
|
*/
|
|
67
86
|
private splitHistory;
|
|
68
87
|
/**
|
|
69
88
|
* Generate an LLM summary of the messages.
|
|
89
|
+
*
|
|
90
|
+
* @param messages Messages to summarize
|
|
91
|
+
* @param currentTask The most recent user message (current task context)
|
|
70
92
|
*/
|
|
71
93
|
private generateSummary;
|
|
72
94
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reactive-overflow.d.ts","sourceRoot":"","sources":["../../../../src/context/compaction/strategies/reactive-overflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,IAAI,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAY,MAAM,gBAAgB,CAAC;AAEhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"reactive-overflow.d.ts","sourceRoot":"","sources":["../../../../src/context/compaction/strategies/reactive-overflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,IAAI,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAY,MAAM,gBAAgB,CAAC;AAEhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAsCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,wBAAyB,YAAW,mBAAmB;IAChE,QAAQ,CAAC,IAAI,uBAAuB;IAEpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,uBAAuB,YAAK,EAAE,MAAM,EAAE,YAAY;IAM7F;;;;;;;;;;OAUG;IACG,OAAO,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAiF9E;;;;;;;;;OASG;YACW,aAAa;IAkD3B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAuB9B;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAwDpB;;;;;OAKG;YACW,eAAe;IAmC7B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA2ChC;;OAEG;IACH,OAAO,CAAC,qBAAqB;CA0DhC"}
|
|
@@ -4,15 +4,36 @@ import { isAssistantMessage, isToolMessage } from "../../types.js";
|
|
|
4
4
|
const DEFAULT_OPTIONS = {
|
|
5
5
|
preserveLastNTurns: 2,
|
|
6
6
|
maxSummaryTokens: 2e3,
|
|
7
|
-
summaryPrompt: `You are a conversation summarizer
|
|
8
|
-
- What tasks were attempted and their outcomes
|
|
9
|
-
- Current state and context the assistant needs to remember
|
|
10
|
-
- Any important decisions or information discovered
|
|
11
|
-
- What the user was trying to accomplish
|
|
7
|
+
summaryPrompt: `You are a conversation summarizer creating a structured summary for session continuation.
|
|
12
8
|
|
|
13
|
-
|
|
9
|
+
Analyze the conversation and produce a summary in the following XML format:
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
<session_compaction>
|
|
12
|
+
<conversation_history>
|
|
13
|
+
A concise summary of what happened in the conversation:
|
|
14
|
+
- Tasks attempted and their outcomes (success/failure/in-progress)
|
|
15
|
+
- Important decisions made
|
|
16
|
+
- Key information discovered (file paths, configurations, errors encountered)
|
|
17
|
+
- Tools used and their results
|
|
18
|
+
</conversation_history>
|
|
19
|
+
|
|
20
|
+
<current_task>
|
|
21
|
+
The most recent task or instruction the user requested that may still be in progress.
|
|
22
|
+
Be specific - include the exact request and current status.
|
|
23
|
+
</current_task>
|
|
24
|
+
|
|
25
|
+
<important_context>
|
|
26
|
+
Critical state that must be preserved:
|
|
27
|
+
- File paths being worked on
|
|
28
|
+
- Variable values or configurations
|
|
29
|
+
- Error messages that need addressing
|
|
30
|
+
- Any pending actions or next steps
|
|
31
|
+
</important_context>
|
|
32
|
+
</session_compaction>
|
|
33
|
+
|
|
34
|
+
IMPORTANT: The assistant will continue working based on this summary. Ensure the current_task section clearly states what needs to be done next.
|
|
35
|
+
|
|
36
|
+
Conversation to summarize:
|
|
16
37
|
{conversation}`
|
|
17
38
|
};
|
|
18
39
|
class ReactiveOverflowStrategy {
|
|
@@ -41,15 +62,73 @@ class ReactiveOverflowStrategy {
|
|
|
41
62
|
this.logger.debug("ReactiveOverflowStrategy: History too short, skipping compaction");
|
|
42
63
|
return [];
|
|
43
64
|
}
|
|
65
|
+
let existingSummaryIndex = -1;
|
|
66
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
67
|
+
const msg = history[i];
|
|
68
|
+
if (msg?.metadata?.isSummary === true || msg?.metadata?.isSessionSummary === true) {
|
|
69
|
+
existingSummaryIndex = i;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (existingSummaryIndex !== -1) {
|
|
74
|
+
const messagesAfterSummary = history.slice(existingSummaryIndex + 1);
|
|
75
|
+
if (messagesAfterSummary.length <= 4) {
|
|
76
|
+
this.logger.debug(
|
|
77
|
+
`ReactiveOverflowStrategy: Only ${messagesAfterSummary.length} messages after existing summary, skipping re-compaction`
|
|
78
|
+
);
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
this.logger.info(
|
|
82
|
+
`ReactiveOverflowStrategy: Found existing summary at index ${existingSummaryIndex}, working with ${messagesAfterSummary.length} messages after it`
|
|
83
|
+
);
|
|
84
|
+
return this.compactSubset(messagesAfterSummary, history, existingSummaryIndex);
|
|
85
|
+
}
|
|
44
86
|
const { toSummarize, toKeep } = this.splitHistory(history);
|
|
45
87
|
if (toSummarize.length === 0) {
|
|
46
88
|
this.logger.debug("ReactiveOverflowStrategy: No messages to summarize");
|
|
47
89
|
return [];
|
|
48
90
|
}
|
|
91
|
+
const currentTaskMessage = this.findCurrentTaskMessage(history);
|
|
49
92
|
this.logger.info(
|
|
50
93
|
`ReactiveOverflowStrategy: Summarizing ${toSummarize.length} messages, keeping ${toKeep.length}`
|
|
51
94
|
);
|
|
52
|
-
const summary = await this.generateSummary(toSummarize);
|
|
95
|
+
const summary = await this.generateSummary(toSummarize, currentTaskMessage);
|
|
96
|
+
const summaryMessage = {
|
|
97
|
+
role: "assistant",
|
|
98
|
+
content: [{ type: "text", text: summary }],
|
|
99
|
+
timestamp: Date.now(),
|
|
100
|
+
metadata: {
|
|
101
|
+
isSummary: true,
|
|
102
|
+
summarizedAt: Date.now(),
|
|
103
|
+
originalMessageCount: toSummarize.length,
|
|
104
|
+
originalFirstTimestamp: toSummarize[0]?.timestamp,
|
|
105
|
+
originalLastTimestamp: toSummarize[toSummarize.length - 1]?.timestamp
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
return [summaryMessage];
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Handle re-compaction when there's already a summary in history.
|
|
112
|
+
* Only summarizes messages AFTER the existing summary, preventing
|
|
113
|
+
* cascading summaries of summaries.
|
|
114
|
+
*
|
|
115
|
+
* @param messagesAfterSummary Messages after the existing summary
|
|
116
|
+
* @param fullHistory The complete history (for current task detection)
|
|
117
|
+
* @param existingSummaryIndex Index of the existing summary in fullHistory
|
|
118
|
+
* @returns Array with single summary message, or empty if nothing to summarize
|
|
119
|
+
*/
|
|
120
|
+
async compactSubset(messagesAfterSummary, fullHistory, existingSummaryIndex) {
|
|
121
|
+
const { toSummarize, toKeep } = this.splitHistory(messagesAfterSummary);
|
|
122
|
+
if (toSummarize.length === 0) {
|
|
123
|
+
this.logger.debug("ReactiveOverflowStrategy: No messages to summarize in subset");
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
const currentTaskMessage = this.findCurrentTaskMessage(fullHistory);
|
|
127
|
+
this.logger.info(
|
|
128
|
+
`ReactiveOverflowStrategy (re-compact): Summarizing ${toSummarize.length} messages after existing summary, keeping ${toKeep.length}`
|
|
129
|
+
);
|
|
130
|
+
const summary = await this.generateSummary(toSummarize, currentTaskMessage);
|
|
131
|
+
const absoluteOriginalMessageCount = existingSummaryIndex + 1 + toSummarize.length;
|
|
53
132
|
const summaryMessage = {
|
|
54
133
|
role: "assistant",
|
|
55
134
|
content: [{ type: "text", text: summary }],
|
|
@@ -57,16 +136,43 @@ class ReactiveOverflowStrategy {
|
|
|
57
136
|
metadata: {
|
|
58
137
|
isSummary: true,
|
|
59
138
|
summarizedAt: Date.now(),
|
|
60
|
-
|
|
139
|
+
originalMessageCount: absoluteOriginalMessageCount,
|
|
140
|
+
isRecompaction: true,
|
|
141
|
+
// Mark that this is a re-compaction
|
|
61
142
|
originalFirstTimestamp: toSummarize[0]?.timestamp,
|
|
62
143
|
originalLastTimestamp: toSummarize[toSummarize.length - 1]?.timestamp
|
|
63
144
|
}
|
|
64
145
|
};
|
|
65
146
|
return [summaryMessage];
|
|
66
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Find the most recent user message that represents the current task.
|
|
150
|
+
* This helps preserve context about what the user is currently asking for.
|
|
151
|
+
*/
|
|
152
|
+
findCurrentTaskMessage(history) {
|
|
153
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
154
|
+
const msg = history[i];
|
|
155
|
+
if (msg?.role === "user") {
|
|
156
|
+
if (typeof msg.content === "string") {
|
|
157
|
+
return msg.content;
|
|
158
|
+
} else if (Array.isArray(msg.content)) {
|
|
159
|
+
const textParts = msg.content.filter(
|
|
160
|
+
(part) => part.type === "text"
|
|
161
|
+
).map((part) => part.text).join("\n");
|
|
162
|
+
if (textParts.length > 0) {
|
|
163
|
+
return textParts;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
67
170
|
/**
|
|
68
171
|
* Split history into messages to summarize and messages to keep.
|
|
69
172
|
* Keeps the last N turns (user + assistant pairs) intact.
|
|
173
|
+
*
|
|
174
|
+
* For long agentic conversations with many tool calls, this also ensures
|
|
175
|
+
* we don't try to keep too many messages even within preserved turns.
|
|
70
176
|
*/
|
|
71
177
|
splitHistory(history) {
|
|
72
178
|
const turnsToKeep = this.options.preserveLastNTurns;
|
|
@@ -81,20 +187,25 @@ class ReactiveOverflowStrategy {
|
|
|
81
187
|
}
|
|
82
188
|
if (userMessageIndices.length > 0) {
|
|
83
189
|
const splitIndex = userMessageIndices[0];
|
|
84
|
-
if (splitIndex !== void 0) {
|
|
85
|
-
if (splitIndex === 0) {
|
|
86
|
-
return {
|
|
87
|
-
toSummarize: [],
|
|
88
|
-
toKeep: history
|
|
89
|
-
};
|
|
90
|
-
}
|
|
190
|
+
if (splitIndex !== void 0 && splitIndex > 0) {
|
|
91
191
|
return {
|
|
92
192
|
toSummarize: history.slice(0, splitIndex),
|
|
93
193
|
toKeep: history.slice(splitIndex)
|
|
94
194
|
};
|
|
95
195
|
}
|
|
96
196
|
}
|
|
97
|
-
const
|
|
197
|
+
const minKeep = 3;
|
|
198
|
+
const maxKeepPercent = 0.2;
|
|
199
|
+
const keepCount = Math.max(minKeep, Math.floor(history.length * maxKeepPercent));
|
|
200
|
+
if (keepCount >= history.length) {
|
|
201
|
+
return {
|
|
202
|
+
toSummarize: [],
|
|
203
|
+
toKeep: history
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
this.logger.debug(
|
|
207
|
+
`splitHistory: Using fallback - keeping last ${keepCount} of ${history.length} messages`
|
|
208
|
+
);
|
|
98
209
|
return {
|
|
99
210
|
toSummarize: history.slice(0, -keepCount),
|
|
100
211
|
toKeep: history.slice(-keepCount)
|
|
@@ -102,21 +213,36 @@ class ReactiveOverflowStrategy {
|
|
|
102
213
|
}
|
|
103
214
|
/**
|
|
104
215
|
* Generate an LLM summary of the messages.
|
|
216
|
+
*
|
|
217
|
+
* @param messages Messages to summarize
|
|
218
|
+
* @param currentTask The most recent user message (current task context)
|
|
105
219
|
*/
|
|
106
|
-
async generateSummary(messages) {
|
|
220
|
+
async generateSummary(messages, currentTask) {
|
|
107
221
|
const formattedConversation = this.formatMessagesForSummary(messages);
|
|
108
|
-
|
|
222
|
+
let conversationWithContext = formattedConversation;
|
|
223
|
+
if (currentTask) {
|
|
224
|
+
conversationWithContext += `
|
|
225
|
+
|
|
226
|
+
--- CURRENT TASK (most recent user request) ---
|
|
227
|
+
${currentTask}`;
|
|
228
|
+
}
|
|
229
|
+
const prompt = this.options.summaryPrompt.replace(
|
|
230
|
+
"{conversation}",
|
|
231
|
+
conversationWithContext
|
|
232
|
+
);
|
|
109
233
|
try {
|
|
110
234
|
const result = await generateText({
|
|
111
235
|
model: this.model,
|
|
112
236
|
prompt,
|
|
113
237
|
maxOutputTokens: this.options.maxSummaryTokens
|
|
114
238
|
});
|
|
115
|
-
return `[
|
|
239
|
+
return `[Session Compaction Summary]
|
|
116
240
|
${result.text}`;
|
|
117
241
|
} catch (error) {
|
|
118
|
-
this.logger.error(
|
|
119
|
-
|
|
242
|
+
this.logger.error(
|
|
243
|
+
`ReactiveOverflowStrategy: Failed to generate summary - ${error instanceof Error ? error.message : String(error)}`
|
|
244
|
+
);
|
|
245
|
+
return this.createFallbackSummary(messages, currentTask);
|
|
120
246
|
}
|
|
121
247
|
}
|
|
122
248
|
/**
|
|
@@ -152,7 +278,7 @@ ${result.text}`;
|
|
|
152
278
|
/**
|
|
153
279
|
* Create a fallback summary if LLM call fails.
|
|
154
280
|
*/
|
|
155
|
-
createFallbackSummary(messages) {
|
|
281
|
+
createFallbackSummary(messages, currentTask) {
|
|
156
282
|
const userMessages = messages.filter((m) => m.role === "user");
|
|
157
283
|
const assistantWithTools = messages.filter(
|
|
158
284
|
(m) => isAssistantMessage(m) && !!m.toolCalls && m.toolCalls.length > 0
|
|
@@ -168,9 +294,25 @@ ${result.text}`;
|
|
|
168
294
|
assistantWithTools.flatMap((m) => m.toolCalls.map((tc) => tc.function.name))
|
|
169
295
|
)
|
|
170
296
|
].join(", ");
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
297
|
+
let fallback = `[Session Compaction Summary - Fallback]
|
|
298
|
+
<session_compaction>
|
|
299
|
+
<conversation_history>
|
|
300
|
+
User discussed: ${userTopics || "various topics"}
|
|
301
|
+
Tools used: ${toolsUsed || "none"}
|
|
302
|
+
Messages summarized: ${messages.length}
|
|
303
|
+
</conversation_history>`;
|
|
304
|
+
if (currentTask) {
|
|
305
|
+
fallback += `
|
|
306
|
+
<current_task>
|
|
307
|
+
${currentTask.slice(0, 500)}${currentTask.length > 500 ? "..." : ""}
|
|
308
|
+
</current_task>`;
|
|
309
|
+
}
|
|
310
|
+
fallback += `
|
|
311
|
+
<important_context>
|
|
312
|
+
Note: This is a fallback summary due to LLM error. Context may be incomplete.
|
|
313
|
+
</important_context>
|
|
314
|
+
</session_compaction>`;
|
|
315
|
+
return fallback;
|
|
174
316
|
}
|
|
175
317
|
}
|
|
176
318
|
export {
|
|
@@ -12,8 +12,20 @@ export interface ICompactionStrategy {
|
|
|
12
12
|
/**
|
|
13
13
|
* Compacts the provided message history.
|
|
14
14
|
*
|
|
15
|
+
* The returned summary messages MUST include specific metadata fields for
|
|
16
|
+
* `filterCompacted()` to correctly exclude pre-summary messages at read-time:
|
|
17
|
+
*
|
|
18
|
+
* Required metadata:
|
|
19
|
+
* - `isSummary: true` - Marks the message as a compaction summary
|
|
20
|
+
* - `originalMessageCount: number` - Count of messages that were summarized
|
|
21
|
+
* (used by filterCompacted to determine which messages to exclude)
|
|
22
|
+
*
|
|
23
|
+
* Optional metadata:
|
|
24
|
+
* - `isRecompaction: true` - Set when re-compacting after a previous summary
|
|
25
|
+
* - `isSessionSummary: true` - Alternative to isSummary for session-level summaries
|
|
26
|
+
*
|
|
15
27
|
* @param history The current conversation history.
|
|
16
|
-
* @returns Summary messages to add to history
|
|
28
|
+
* @returns Summary messages to add to history. Empty array if nothing to compact.
|
|
17
29
|
*/
|
|
18
30
|
compact(history: readonly InternalMessage[]): Promise<InternalMessage[]> | InternalMessage[];
|
|
19
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/context/compaction/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAChC,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/context/compaction/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAChC,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,GAAG,eAAe,EAAE,CAAC;CAChG"}
|