erosolar-cli 1.7.14 → 1.7.16
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/core/responseVerifier.d.ts +79 -0
- package/dist/core/responseVerifier.d.ts.map +1 -0
- package/dist/core/responseVerifier.js +443 -0
- package/dist/core/responseVerifier.js.map +1 -0
- package/dist/shell/interactiveShell.d.ts +10 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +80 -0
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/ui/ShellUIAdapter.d.ts +3 -0
- package/dist/ui/ShellUIAdapter.d.ts.map +1 -1
- package/dist/ui/ShellUIAdapter.js +4 -10
- package/dist/ui/ShellUIAdapter.js.map +1 -1
- package/dist/ui/persistentPrompt.d.ts +4 -0
- package/dist/ui/persistentPrompt.d.ts.map +1 -1
- package/dist/ui/persistentPrompt.js +10 -11
- package/dist/ui/persistentPrompt.js.map +1 -1
- package/package.json +1 -1
- package/dist/bin/core/agent.js +0 -362
- package/dist/bin/core/agentProfileManifest.js +0 -187
- package/dist/bin/core/agentProfiles.js +0 -34
- package/dist/bin/core/agentRulebook.js +0 -135
- package/dist/bin/core/agentSchemaLoader.js +0 -233
- package/dist/bin/core/contextManager.js +0 -412
- package/dist/bin/core/contextWindow.js +0 -122
- package/dist/bin/core/customCommands.js +0 -80
- package/dist/bin/core/errors/apiKeyErrors.js +0 -114
- package/dist/bin/core/errors/errorTypes.js +0 -340
- package/dist/bin/core/errors/safetyValidator.js +0 -304
- package/dist/bin/core/errors.js +0 -32
- package/dist/bin/core/modelDiscovery.js +0 -755
- package/dist/bin/core/preferences.js +0 -224
- package/dist/bin/core/schemaValidator.js +0 -92
- package/dist/bin/core/secretStore.js +0 -199
- package/dist/bin/core/sessionStore.js +0 -187
- package/dist/bin/core/toolRuntime.js +0 -290
- package/dist/bin/core/types.js +0 -1
- package/dist/bin/shell/bracketedPasteManager.js +0 -350
- package/dist/bin/shell/fileChangeTracker.js +0 -65
- package/dist/bin/shell/interactiveShell.js +0 -2908
- package/dist/bin/shell/liveStatus.js +0 -78
- package/dist/bin/shell/shellApp.js +0 -290
- package/dist/bin/shell/systemPrompt.js +0 -60
- package/dist/bin/shell/updateManager.js +0 -108
- package/dist/bin/ui/ShellUIAdapter.js +0 -459
- package/dist/bin/ui/UnifiedUIController.js +0 -183
- package/dist/bin/ui/animation/AnimationScheduler.js +0 -430
- package/dist/bin/ui/codeHighlighter.js +0 -854
- package/dist/bin/ui/designSystem.js +0 -121
- package/dist/bin/ui/display.js +0 -1222
- package/dist/bin/ui/interrupts/InterruptManager.js +0 -437
- package/dist/bin/ui/layout.js +0 -139
- package/dist/bin/ui/orchestration/StatusOrchestrator.js +0 -403
- package/dist/bin/ui/outputMode.js +0 -38
- package/dist/bin/ui/persistentPrompt.js +0 -183
- package/dist/bin/ui/richText.js +0 -338
- package/dist/bin/ui/shortcutsHelp.js +0 -87
- package/dist/bin/ui/telemetry/UITelemetry.js +0 -443
- package/dist/bin/ui/textHighlighter.js +0 -210
- package/dist/bin/ui/theme.js +0 -116
- package/dist/bin/ui/toolDisplay.js +0 -423
- package/dist/bin/ui/toolDisplayAdapter.js +0 -357
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ContextManager - Manages conversation context to prevent token limit leaks
|
|
3
|
-
*
|
|
4
|
-
* Responsibilities:
|
|
5
|
-
* - Truncate tool outputs intelligently
|
|
6
|
-
* - Prune old conversation history with LLM summarization
|
|
7
|
-
* - Track and estimate token usage
|
|
8
|
-
* - Keep conversation within budget based on model context windows
|
|
9
|
-
* - Proactively shrink context before hitting limits
|
|
10
|
-
*/
|
|
11
|
-
import { calculateContextThresholds } from './contextWindow.js';
|
|
12
|
-
/**
|
|
13
|
-
* Summarization prompt template
|
|
14
|
-
*/
|
|
15
|
-
export const SUMMARIZATION_PROMPT = `You are a context summarization assistant. Your task is to create a concise but comprehensive summary of the earlier conversation history.
|
|
16
|
-
|
|
17
|
-
IMPORTANT GUIDELINES:
|
|
18
|
-
1. Preserve KEY INFORMATION that may be needed for future reference:
|
|
19
|
-
- File paths and locations mentioned
|
|
20
|
-
- Variable names, function names, class names
|
|
21
|
-
- Error messages and their solutions
|
|
22
|
-
- User preferences and decisions
|
|
23
|
-
- Important code snippets (keep brief)
|
|
24
|
-
- Task progress and what was accomplished
|
|
25
|
-
|
|
26
|
-
2. Keep the summary CONCISE but COMPLETE:
|
|
27
|
-
- Focus on facts and outcomes, not back-and-forth dialogue
|
|
28
|
-
- Use bullet points for clarity
|
|
29
|
-
- Group related information together
|
|
30
|
-
- Skip pleasantries and redundant confirmations
|
|
31
|
-
|
|
32
|
-
3. Format the summary as:
|
|
33
|
-
## Key Context
|
|
34
|
-
- [Important decisions and preferences]
|
|
35
|
-
|
|
36
|
-
## Work Completed
|
|
37
|
-
- [What was done, files modified, etc.]
|
|
38
|
-
|
|
39
|
-
## Important Details
|
|
40
|
-
- [File paths, function names, error solutions, etc.]
|
|
41
|
-
|
|
42
|
-
Summarize the following conversation history:
|
|
43
|
-
|
|
44
|
-
{conversation}`;
|
|
45
|
-
export class ContextManager {
|
|
46
|
-
constructor(config = {}) {
|
|
47
|
-
this.config = {
|
|
48
|
-
maxTokens: 130000, // Leave room below 131072 limit
|
|
49
|
-
targetTokens: 100000, // Target to trigger pruning
|
|
50
|
-
maxToolOutputLength: 10000, // 10k chars max per tool output
|
|
51
|
-
preserveRecentMessages: 10, // Keep last 10 user/assistant exchanges
|
|
52
|
-
estimatedCharsPerToken: 4,
|
|
53
|
-
...config,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Truncate tool output intelligently
|
|
58
|
-
*/
|
|
59
|
-
truncateToolOutput(output, toolName) {
|
|
60
|
-
const originalLength = output.length;
|
|
61
|
-
if (originalLength <= this.config.maxToolOutputLength) {
|
|
62
|
-
return {
|
|
63
|
-
content: output,
|
|
64
|
-
wasTruncated: false,
|
|
65
|
-
originalLength,
|
|
66
|
-
truncatedLength: originalLength,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
// Intelligent truncation based on tool type
|
|
70
|
-
let truncated = this.intelligentTruncate(output, toolName);
|
|
71
|
-
const truncatedLength = truncated.length;
|
|
72
|
-
return {
|
|
73
|
-
content: truncated,
|
|
74
|
-
wasTruncated: true,
|
|
75
|
-
originalLength,
|
|
76
|
-
truncatedLength,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Intelligent truncation based on tool type
|
|
81
|
-
*/
|
|
82
|
-
intelligentTruncate(output, toolName) {
|
|
83
|
-
const maxLength = this.config.maxToolOutputLength;
|
|
84
|
-
// For file reads, show beginning and end
|
|
85
|
-
if (toolName === 'Read' || toolName === 'read_file') {
|
|
86
|
-
return this.truncateFileOutput(output, maxLength);
|
|
87
|
-
}
|
|
88
|
-
// For search results, keep first N results
|
|
89
|
-
if (toolName === 'Grep' || toolName === 'grep_search' || toolName === 'Glob') {
|
|
90
|
-
return this.truncateSearchOutput(output, maxLength);
|
|
91
|
-
}
|
|
92
|
-
// For bash/command output, keep end (usually most relevant)
|
|
93
|
-
if (toolName === 'Bash' || toolName === 'bash' || toolName === 'execute_bash') {
|
|
94
|
-
return this.truncateBashOutput(output, maxLength);
|
|
95
|
-
}
|
|
96
|
-
// Default: show beginning with truncation notice
|
|
97
|
-
return this.truncateDefault(output, maxLength);
|
|
98
|
-
}
|
|
99
|
-
truncateFileOutput(output, maxLength) {
|
|
100
|
-
const lines = output.split('\n');
|
|
101
|
-
if (lines.length <= 100) {
|
|
102
|
-
// For small files, just truncate text
|
|
103
|
-
return this.truncateDefault(output, maxLength);
|
|
104
|
-
}
|
|
105
|
-
// Show first 50 and last 50 lines
|
|
106
|
-
const keepLines = Math.floor(maxLength / 100); // Rough estimate
|
|
107
|
-
const headLines = lines.slice(0, keepLines);
|
|
108
|
-
const tailLines = lines.slice(-keepLines);
|
|
109
|
-
const truncatedCount = lines.length - (keepLines * 2);
|
|
110
|
-
return [
|
|
111
|
-
...headLines,
|
|
112
|
-
`\n... [${truncatedCount} lines truncated for context management] ...\n`,
|
|
113
|
-
...tailLines,
|
|
114
|
-
].join('\n');
|
|
115
|
-
}
|
|
116
|
-
truncateSearchOutput(output, maxLength) {
|
|
117
|
-
const lines = output.split('\n');
|
|
118
|
-
const keepLines = Math.floor(maxLength / 80); // Rough average line length
|
|
119
|
-
if (lines.length <= keepLines) {
|
|
120
|
-
return output;
|
|
121
|
-
}
|
|
122
|
-
const truncatedCount = lines.length - keepLines;
|
|
123
|
-
return [
|
|
124
|
-
...lines.slice(0, keepLines),
|
|
125
|
-
`\n... [${truncatedCount} more results truncated for context management] ...`,
|
|
126
|
-
].join('\n');
|
|
127
|
-
}
|
|
128
|
-
truncateBashOutput(output, maxLength) {
|
|
129
|
-
if (output.length <= maxLength) {
|
|
130
|
-
return output;
|
|
131
|
-
}
|
|
132
|
-
// For command output, the end is usually most important (errors, final status)
|
|
133
|
-
const keepChars = Math.floor(maxLength * 0.8); // 80% at end
|
|
134
|
-
const prefixChars = maxLength - keepChars - 100; // Small prefix
|
|
135
|
-
const prefix = output.slice(0, prefixChars);
|
|
136
|
-
const suffix = output.slice(-keepChars);
|
|
137
|
-
const truncatedChars = output.length - prefixChars - keepChars;
|
|
138
|
-
return `${prefix}\n\n... [${truncatedChars} characters truncated for context management] ...\n\n${suffix}`;
|
|
139
|
-
}
|
|
140
|
-
truncateDefault(output, maxLength) {
|
|
141
|
-
if (output.length <= maxLength) {
|
|
142
|
-
return output;
|
|
143
|
-
}
|
|
144
|
-
const truncatedChars = output.length - maxLength + 100; // Account for notice
|
|
145
|
-
return `${output.slice(0, maxLength - 100)}\n\n... [${truncatedChars} characters truncated for context management] ...`;
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Estimate tokens in a message
|
|
149
|
-
*/
|
|
150
|
-
estimateTokens(message) {
|
|
151
|
-
let charCount = 0;
|
|
152
|
-
if (message.content) {
|
|
153
|
-
charCount += message.content.length;
|
|
154
|
-
}
|
|
155
|
-
if (message.role === 'assistant' && message.toolCalls) {
|
|
156
|
-
// Tool calls add overhead
|
|
157
|
-
for (const call of message.toolCalls) {
|
|
158
|
-
charCount += call.name.length;
|
|
159
|
-
charCount += JSON.stringify(call.arguments).length;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return Math.ceil(charCount / this.config.estimatedCharsPerToken);
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Estimate total tokens in conversation
|
|
166
|
-
*/
|
|
167
|
-
estimateTotalTokens(messages) {
|
|
168
|
-
return messages.reduce((sum, msg) => sum + this.estimateTokens(msg), 0);
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Prune old messages when approaching limit
|
|
172
|
-
*
|
|
173
|
-
* Synchronously removes old messages to stay within budget.
|
|
174
|
-
* If LLM summarization is available and enabled, this method will be async.
|
|
175
|
-
*/
|
|
176
|
-
pruneMessages(messages) {
|
|
177
|
-
const totalTokens = this.estimateTotalTokens(messages);
|
|
178
|
-
// Only prune if we're above target
|
|
179
|
-
if (totalTokens < this.config.targetTokens) {
|
|
180
|
-
return { pruned: messages, removed: 0 };
|
|
181
|
-
}
|
|
182
|
-
// Always keep system message (first)
|
|
183
|
-
const firstMessage = messages[0];
|
|
184
|
-
const systemMessage = firstMessage?.role === 'system' ? firstMessage : null;
|
|
185
|
-
const conversationMessages = systemMessage ? messages.slice(1) : messages;
|
|
186
|
-
// Keep recent messages based on preserveRecentMessages
|
|
187
|
-
// Count user/assistant pairs
|
|
188
|
-
const recentMessages = [];
|
|
189
|
-
let exchangeCount = 0;
|
|
190
|
-
for (let i = conversationMessages.length - 1; i >= 0; i--) {
|
|
191
|
-
const msg = conversationMessages[i];
|
|
192
|
-
if (!msg)
|
|
193
|
-
continue;
|
|
194
|
-
recentMessages.unshift(msg);
|
|
195
|
-
if (msg.role === 'user') {
|
|
196
|
-
exchangeCount++;
|
|
197
|
-
if (exchangeCount >= this.config.preserveRecentMessages) {
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
// Build pruned message list
|
|
203
|
-
const pruned = [];
|
|
204
|
-
if (systemMessage) {
|
|
205
|
-
pruned.push(systemMessage);
|
|
206
|
-
}
|
|
207
|
-
// Add a context summary message if we removed messages
|
|
208
|
-
const removedCount = conversationMessages.length - recentMessages.length;
|
|
209
|
-
if (removedCount > 0) {
|
|
210
|
-
pruned.push({
|
|
211
|
-
role: 'system',
|
|
212
|
-
content: `[Context Manager: Removed ${removedCount} old messages to stay within token budget. Recent conversation history preserved.]`,
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
pruned.push(...recentMessages);
|
|
216
|
-
return {
|
|
217
|
-
pruned,
|
|
218
|
-
removed: removedCount,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* Prune messages with LLM-based summarization
|
|
223
|
-
*
|
|
224
|
-
* This is an async version that uses the LLM to create intelligent summaries
|
|
225
|
-
* instead of just removing old messages. Should be called BEFORE generation.
|
|
226
|
-
*/
|
|
227
|
-
async pruneMessagesWithSummary(messages) {
|
|
228
|
-
const totalTokens = this.estimateTotalTokens(messages);
|
|
229
|
-
// Only prune if we're above target
|
|
230
|
-
if (totalTokens < this.config.targetTokens) {
|
|
231
|
-
return { pruned: messages, removed: 0, summarized: false };
|
|
232
|
-
}
|
|
233
|
-
// If no summarization callback or disabled, fall back to simple pruning
|
|
234
|
-
if (!this.config.summarizationCallback || !this.config.useLLMSummarization) {
|
|
235
|
-
const result = this.pruneMessages(messages);
|
|
236
|
-
return { ...result, summarized: false };
|
|
237
|
-
}
|
|
238
|
-
// Partition messages
|
|
239
|
-
const firstMessage = messages[0];
|
|
240
|
-
const systemMessage = firstMessage?.role === 'system' ? firstMessage : null;
|
|
241
|
-
const conversationMessages = systemMessage ? messages.slice(1) : messages;
|
|
242
|
-
// Keep recent messages
|
|
243
|
-
const recentMessages = [];
|
|
244
|
-
let exchangeCount = 0;
|
|
245
|
-
for (let i = conversationMessages.length - 1; i >= 0; i--) {
|
|
246
|
-
const msg = conversationMessages[i];
|
|
247
|
-
if (!msg)
|
|
248
|
-
continue;
|
|
249
|
-
recentMessages.unshift(msg);
|
|
250
|
-
if (msg.role === 'user') {
|
|
251
|
-
exchangeCount++;
|
|
252
|
-
if (exchangeCount >= this.config.preserveRecentMessages) {
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
const toSummarize = conversationMessages.slice(0, conversationMessages.length - recentMessages.length);
|
|
258
|
-
// If nothing to summarize, return as-is
|
|
259
|
-
if (toSummarize.length === 0) {
|
|
260
|
-
return { pruned: messages, removed: 0, summarized: false };
|
|
261
|
-
}
|
|
262
|
-
try {
|
|
263
|
-
// Call the LLM to summarize old messages
|
|
264
|
-
const summary = await this.config.summarizationCallback(toSummarize);
|
|
265
|
-
// Build pruned message list with summary
|
|
266
|
-
const pruned = [];
|
|
267
|
-
if (systemMessage) {
|
|
268
|
-
pruned.push(systemMessage);
|
|
269
|
-
}
|
|
270
|
-
// Add intelligent summary
|
|
271
|
-
pruned.push({
|
|
272
|
-
role: 'system',
|
|
273
|
-
content: [
|
|
274
|
-
'=== Context Summary (Auto-generated) ===',
|
|
275
|
-
summary.trim(),
|
|
276
|
-
'',
|
|
277
|
-
`[Summarized ${toSummarize.length} earlier messages. Recent ${recentMessages.length} messages preserved below.]`,
|
|
278
|
-
].join('\n'),
|
|
279
|
-
});
|
|
280
|
-
pruned.push(...recentMessages);
|
|
281
|
-
return {
|
|
282
|
-
pruned,
|
|
283
|
-
removed: toSummarize.length,
|
|
284
|
-
summarized: true,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
catch (error) {
|
|
288
|
-
// If summarization fails, fall back to simple pruning
|
|
289
|
-
const result = this.pruneMessages(messages);
|
|
290
|
-
return { ...result, summarized: false };
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* Check if we're approaching the limit
|
|
295
|
-
*/
|
|
296
|
-
isApproachingLimit(messages) {
|
|
297
|
-
const totalTokens = this.estimateTotalTokens(messages);
|
|
298
|
-
return totalTokens >= this.config.targetTokens;
|
|
299
|
-
}
|
|
300
|
-
/**
|
|
301
|
-
* Get warning level for current context usage
|
|
302
|
-
* Returns: null (no warning), 'info' (<70%), 'warning' (70-90%), 'danger' (>90%)
|
|
303
|
-
*/
|
|
304
|
-
getWarningLevel(messages) {
|
|
305
|
-
const totalTokens = this.estimateTotalTokens(messages);
|
|
306
|
-
const percentage = (totalTokens / this.config.maxTokens) * 100;
|
|
307
|
-
if (percentage > 90) {
|
|
308
|
-
return 'danger';
|
|
309
|
-
}
|
|
310
|
-
else if (percentage > 70) {
|
|
311
|
-
return 'warning';
|
|
312
|
-
}
|
|
313
|
-
else if (percentage > 50) {
|
|
314
|
-
return 'info';
|
|
315
|
-
}
|
|
316
|
-
return null;
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Get a human-readable warning message
|
|
320
|
-
*/
|
|
321
|
-
getWarningMessage(messages) {
|
|
322
|
-
const stats = this.getStats(messages);
|
|
323
|
-
const warningLevel = this.getWarningLevel(messages);
|
|
324
|
-
if (warningLevel === 'danger') {
|
|
325
|
-
return `⚠️ Context usage critical (${stats.percentage}%). Consider starting a new session or the next request may fail.`;
|
|
326
|
-
}
|
|
327
|
-
else if (warningLevel === 'warning') {
|
|
328
|
-
return `Context usage high (${stats.percentage}%). Automatic cleanup will occur soon.`;
|
|
329
|
-
}
|
|
330
|
-
return null;
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Get context stats
|
|
334
|
-
*/
|
|
335
|
-
getStats(messages) {
|
|
336
|
-
const totalTokens = this.estimateTotalTokens(messages);
|
|
337
|
-
const percentage = Math.round((totalTokens / this.config.maxTokens) * 100);
|
|
338
|
-
return {
|
|
339
|
-
totalTokens,
|
|
340
|
-
percentage,
|
|
341
|
-
isOverLimit: totalTokens >= this.config.maxTokens,
|
|
342
|
-
isApproachingLimit: totalTokens >= this.config.targetTokens,
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Update configuration
|
|
347
|
-
*/
|
|
348
|
-
updateConfig(config) {
|
|
349
|
-
this.config = { ...this.config, ...config };
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
/**
|
|
353
|
-
* Create a default context manager instance with model-aware limits
|
|
354
|
-
*/
|
|
355
|
-
export function createDefaultContextManager(overrides, model) {
|
|
356
|
-
// Get model-specific thresholds
|
|
357
|
-
const thresholds = calculateContextThresholds(model);
|
|
358
|
-
return new ContextManager({
|
|
359
|
-
maxTokens: thresholds.maxTokens,
|
|
360
|
-
targetTokens: thresholds.targetTokens, // Start pruning at 60%
|
|
361
|
-
warningTokens: thresholds.warningTokens, // Warn at 50%
|
|
362
|
-
criticalTokens: thresholds.criticalTokens, // Critical at 75%
|
|
363
|
-
maxToolOutputLength: 5000, // 5k chars max per tool (reduced for safety)
|
|
364
|
-
preserveRecentMessages: 5, // Keep last 5 exchanges
|
|
365
|
-
estimatedCharsPerToken: 3.5, // More aggressive estimate (accounts for special tokens, JSON overhead)
|
|
366
|
-
useLLMSummarization: true, // Enable LLM summarization by default
|
|
367
|
-
model,
|
|
368
|
-
...overrides,
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
/**
|
|
372
|
-
* Format conversation messages into readable text for summarization
|
|
373
|
-
*/
|
|
374
|
-
export function formatMessagesForSummary(messages) {
|
|
375
|
-
const lines = [];
|
|
376
|
-
for (const msg of messages) {
|
|
377
|
-
if (msg.role === 'user') {
|
|
378
|
-
lines.push(`USER: ${msg.content}`);
|
|
379
|
-
}
|
|
380
|
-
else if (msg.role === 'assistant') {
|
|
381
|
-
let content = msg.content || '';
|
|
382
|
-
if (msg.toolCalls && msg.toolCalls.length > 0) {
|
|
383
|
-
const toolNames = msg.toolCalls.map(tc => tc.name);
|
|
384
|
-
content += ` [Called tools: ${toolNames.join(', ')}]`;
|
|
385
|
-
}
|
|
386
|
-
lines.push(`ASSISTANT: ${content}`);
|
|
387
|
-
}
|
|
388
|
-
else if (msg.role === 'tool') {
|
|
389
|
-
// Truncate long tool outputs for summarization
|
|
390
|
-
const output = msg.content.length > 500
|
|
391
|
-
? msg.content.slice(0, 500) + '...'
|
|
392
|
-
: msg.content;
|
|
393
|
-
lines.push(`TOOL (${msg.name}): ${output}`);
|
|
394
|
-
}
|
|
395
|
-
// Skip system messages in summary input
|
|
396
|
-
}
|
|
397
|
-
return lines.join('\n\n');
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* Create a summarization callback using the given provider
|
|
401
|
-
*/
|
|
402
|
-
export function createSummarizationCallback(provider) {
|
|
403
|
-
return async (messages) => {
|
|
404
|
-
// Format messages into readable conversation
|
|
405
|
-
const conversationText = formatMessagesForSummary(messages);
|
|
406
|
-
// Create summarization prompt
|
|
407
|
-
const prompt = SUMMARIZATION_PROMPT.replace('{conversation}', conversationText);
|
|
408
|
-
// Call provider to generate summary (no tools needed)
|
|
409
|
-
const response = await provider.generate([{ role: 'user', content: prompt }], []);
|
|
410
|
-
return response.content || '';
|
|
411
|
-
};
|
|
412
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Model Context Window Management
|
|
3
|
-
*
|
|
4
|
-
* Maps models to their context window sizes and provides utilities
|
|
5
|
-
* for dynamic context limit configuration.
|
|
6
|
-
*/
|
|
7
|
-
const MODEL_CONTEXT_WINDOWS = [
|
|
8
|
-
// OpenAI GPT-5.x series (200K context)
|
|
9
|
-
{ pattern: /^gpt-5\.1-?codex/i, contextWindow: 200000, targetTokens: 140000 },
|
|
10
|
-
{ pattern: /^gpt-5(?:\.1|-?pro|-?mini|-?nano|-?max)/i, contextWindow: 200000, targetTokens: 140000 },
|
|
11
|
-
{ pattern: /^gpt-5$/i, contextWindow: 200000, targetTokens: 140000 },
|
|
12
|
-
// OpenAI GPT-4o series (128K context)
|
|
13
|
-
{ pattern: /^gpt-4o/i, contextWindow: 128000, targetTokens: 90000 },
|
|
14
|
-
{ pattern: /^gpt-4-turbo/i, contextWindow: 128000, targetTokens: 90000 },
|
|
15
|
-
{ pattern: /^gpt-4-(?:0125|1106)/i, contextWindow: 128000, targetTokens: 90000 },
|
|
16
|
-
// OpenAI GPT-4 (8K-32K context)
|
|
17
|
-
{ pattern: /^gpt-4-32k/i, contextWindow: 32000, targetTokens: 24000 },
|
|
18
|
-
{ pattern: /^gpt-4(?![o-])/i, contextWindow: 8192, targetTokens: 6000 },
|
|
19
|
-
// OpenAI o1/o3 reasoning models (200K context)
|
|
20
|
-
{ pattern: /^o1(?:-pro|-mini)?/i, contextWindow: 200000, targetTokens: 140000 },
|
|
21
|
-
{ pattern: /^o3(?:-pro|-mini)?/i, contextWindow: 200000, targetTokens: 140000 },
|
|
22
|
-
// OpenAI GPT-3.5 (16K context)
|
|
23
|
-
{ pattern: /^gpt-3\.5-turbo-16k/i, contextWindow: 16000, targetTokens: 12000 },
|
|
24
|
-
{ pattern: /^gpt-3\.5/i, contextWindow: 4096, targetTokens: 3000 },
|
|
25
|
-
// Anthropic Claude 4.x series (200K context)
|
|
26
|
-
{ pattern: /^claude-(?:sonnet|opus|haiku)-4/i, contextWindow: 200000, targetTokens: 140000 },
|
|
27
|
-
{ pattern: /^(?:sonnet|opus|haiku)-4/i, contextWindow: 200000, targetTokens: 140000 },
|
|
28
|
-
// Anthropic Claude 3.x series (200K context)
|
|
29
|
-
{ pattern: /^claude-3/i, contextWindow: 200000, targetTokens: 140000 },
|
|
30
|
-
// Anthropic Claude 2.x (100K context)
|
|
31
|
-
{ pattern: /^claude-2/i, contextWindow: 100000, targetTokens: 70000 },
|
|
32
|
-
// Google Gemini Pro (1M+ context, capped at 200K for safety)
|
|
33
|
-
{ pattern: /^gemini-(?:1\.5|2\.0)-(?:pro|flash)/i, contextWindow: 1000000, targetTokens: 200000 },
|
|
34
|
-
{ pattern: /^gemini-pro/i, contextWindow: 32000, targetTokens: 24000 },
|
|
35
|
-
// DeepSeek (64K-128K context)
|
|
36
|
-
{ pattern: /^deepseek-(?:chat|coder|reasoner)/i, contextWindow: 128000, targetTokens: 90000 },
|
|
37
|
-
{ pattern: /^deepseek/i, contextWindow: 64000, targetTokens: 45000 },
|
|
38
|
-
// xAI Grok (128K context)
|
|
39
|
-
{ pattern: /^grok/i, contextWindow: 128000, targetTokens: 90000 },
|
|
40
|
-
// Ollama/Local models
|
|
41
|
-
{ pattern: /^llama-?3\.?[12]?:?(?:70b|405b)/i, contextWindow: 128000, targetTokens: 90000 },
|
|
42
|
-
{ pattern: /^llama-?3/i, contextWindow: 8192, targetTokens: 6000 },
|
|
43
|
-
{ pattern: /^llama-?2/i, contextWindow: 4096, targetTokens: 3000 },
|
|
44
|
-
{ pattern: /^mistral/i, contextWindow: 32000, targetTokens: 24000 },
|
|
45
|
-
{ pattern: /^mixtral/i, contextWindow: 32000, targetTokens: 24000 },
|
|
46
|
-
{ pattern: /^codellama/i, contextWindow: 16000, targetTokens: 12000 },
|
|
47
|
-
{ pattern: /^qwen/i, contextWindow: 32000, targetTokens: 24000 },
|
|
48
|
-
{ pattern: /^phi/i, contextWindow: 4096, targetTokens: 3000 },
|
|
49
|
-
];
|
|
50
|
-
// Default fallback values
|
|
51
|
-
const DEFAULT_CONTEXT_WINDOW = 128000;
|
|
52
|
-
const DEFAULT_TARGET_TOKENS = 90000;
|
|
53
|
-
/**
|
|
54
|
-
* Get context window information for a model.
|
|
55
|
-
*/
|
|
56
|
-
export function getModelContextInfo(model) {
|
|
57
|
-
if (!model) {
|
|
58
|
-
return {
|
|
59
|
-
model: 'unknown',
|
|
60
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
61
|
-
targetTokens: DEFAULT_TARGET_TOKENS,
|
|
62
|
-
isDefault: true,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
const normalized = model.trim();
|
|
66
|
-
for (const entry of MODEL_CONTEXT_WINDOWS) {
|
|
67
|
-
if (entry.pattern.test(normalized)) {
|
|
68
|
-
return {
|
|
69
|
-
model,
|
|
70
|
-
contextWindow: entry.contextWindow,
|
|
71
|
-
targetTokens: entry.targetTokens,
|
|
72
|
-
isDefault: false,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
model,
|
|
78
|
-
contextWindow: DEFAULT_CONTEXT_WINDOW,
|
|
79
|
-
targetTokens: DEFAULT_TARGET_TOKENS,
|
|
80
|
-
isDefault: true,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Returns the approximate context window (in tokens) for the provided model id.
|
|
85
|
-
* Falls back to null when the model is unknown so callers can handle gracefully.
|
|
86
|
-
*/
|
|
87
|
-
export function getContextWindowTokens(model) {
|
|
88
|
-
if (!model) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
const info = getModelContextInfo(model);
|
|
92
|
-
return info.isDefault ? null : info.contextWindow;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Get safe target token count for a model.
|
|
96
|
-
* This is the threshold at which context pruning should begin.
|
|
97
|
-
*/
|
|
98
|
-
export function getSafeTargetTokens(model) {
|
|
99
|
-
const info = getModelContextInfo(model);
|
|
100
|
-
return info.targetTokens;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Calculate all context management thresholds for a model.
|
|
104
|
-
*
|
|
105
|
-
* Thresholds are set conservatively to prevent context overflow errors:
|
|
106
|
-
* - targetTokens: Start proactive pruning at 60% to leave ample room
|
|
107
|
-
* - warningTokens: Show warning at 50% so user is aware
|
|
108
|
-
* - criticalTokens: Aggressive pruning at 75%
|
|
109
|
-
* - safetyBuffer: Reserve 5% for API overhead and response tokens
|
|
110
|
-
*/
|
|
111
|
-
export function calculateContextThresholds(model) {
|
|
112
|
-
const info = getModelContextInfo(model);
|
|
113
|
-
const contextWindow = info.contextWindow;
|
|
114
|
-
// Apply 5% safety buffer to account for API overhead
|
|
115
|
-
const effectiveMax = Math.floor(contextWindow * 0.95);
|
|
116
|
-
return {
|
|
117
|
-
maxTokens: effectiveMax,
|
|
118
|
-
targetTokens: Math.floor(contextWindow * 0.60), // Start pruning at 60% (more aggressive)
|
|
119
|
-
warningTokens: Math.floor(contextWindow * 0.50), // Warn at 50%
|
|
120
|
-
criticalTokens: Math.floor(contextWindow * 0.75), // Critical at 75%
|
|
121
|
-
};
|
|
122
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
const dataRoot = process.env['EROSOLAR_DATA_DIR']?.trim() || join(homedir(), '.erosolar');
|
|
5
|
-
const defaultCommandsDir = process.env['EROSOLAR_COMMANDS_DIR']?.trim() || join(dataRoot, 'commands');
|
|
6
|
-
export function loadCustomSlashCommands(dir = defaultCommandsDir) {
|
|
7
|
-
if (!existsSync(dir)) {
|
|
8
|
-
return [];
|
|
9
|
-
}
|
|
10
|
-
const files = readdirSync(dir).filter((file) => file.toLowerCase().endsWith('.json'));
|
|
11
|
-
const commands = [];
|
|
12
|
-
const seen = new Set();
|
|
13
|
-
for (const file of files) {
|
|
14
|
-
const filePath = join(dir, file);
|
|
15
|
-
try {
|
|
16
|
-
const raw = readFileSync(filePath, 'utf8');
|
|
17
|
-
const parsed = JSON.parse(raw);
|
|
18
|
-
if (Array.isArray(parsed)) {
|
|
19
|
-
for (const entry of parsed) {
|
|
20
|
-
const cmd = normalizeCommand(entry, filePath);
|
|
21
|
-
if (cmd && !seen.has(cmd.command)) {
|
|
22
|
-
seen.add(cmd.command);
|
|
23
|
-
commands.push(cmd);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
const cmd = normalizeCommand(parsed, filePath);
|
|
29
|
-
if (cmd && !seen.has(cmd.command)) {
|
|
30
|
-
seen.add(cmd.command);
|
|
31
|
-
commands.push(cmd);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
console.warn(`[custom commands] Failed to load ${filePath}:`, error);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return commands;
|
|
40
|
-
}
|
|
41
|
-
export function buildCustomCommandPrompt(command, input, context) {
|
|
42
|
-
const replacements = {
|
|
43
|
-
input,
|
|
44
|
-
workspace: context.workspace,
|
|
45
|
-
profile: context.profile,
|
|
46
|
-
provider: context.provider,
|
|
47
|
-
model: context.model,
|
|
48
|
-
};
|
|
49
|
-
return command.template.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => {
|
|
50
|
-
const normalized = key.toLowerCase();
|
|
51
|
-
return replacements[normalized] ?? replacements[key] ?? '';
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
function normalizeCommand(entry, source) {
|
|
55
|
-
if (!entry || typeof entry !== 'object') {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
const record = entry;
|
|
59
|
-
const rawCommandValue = record['command'];
|
|
60
|
-
const rawCommand = typeof rawCommandValue === 'string' ? rawCommandValue.trim() : '';
|
|
61
|
-
const command = rawCommand ? (rawCommand.startsWith('/') ? rawCommand : `/${rawCommand}`) : '';
|
|
62
|
-
const descriptionValue = record['description'];
|
|
63
|
-
const description = typeof descriptionValue === 'string' && descriptionValue.trim()
|
|
64
|
-
? descriptionValue.trim()
|
|
65
|
-
: 'Custom command';
|
|
66
|
-
const templateEntry = record['template'] ?? record['prompt'];
|
|
67
|
-
const templateValue = typeof templateEntry === 'string' ? templateEntry : '';
|
|
68
|
-
if (!command || !templateValue.trim()) {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
const requireInputEntry = record['requireInput'] ?? record['inputRequired'];
|
|
72
|
-
const requireInput = typeof requireInputEntry === 'boolean' ? requireInputEntry : false;
|
|
73
|
-
return {
|
|
74
|
-
command,
|
|
75
|
-
description,
|
|
76
|
-
template: templateValue,
|
|
77
|
-
requireInput,
|
|
78
|
-
source,
|
|
79
|
-
};
|
|
80
|
-
}
|