@compilr-dev/agents 0.1.0 → 0.2.0
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.d.ts +168 -1
- package/dist/agent.js +268 -14
- package/dist/context/file-tracker.d.ts +156 -0
- package/dist/context/file-tracker.js +358 -0
- package/dist/context/file-tracking-hook.d.ts +29 -0
- package/dist/context/file-tracking-hook.js +103 -0
- package/dist/context/index.d.ts +5 -1
- package/dist/context/index.js +3 -0
- package/dist/context/manager.d.ts +69 -1
- package/dist/context/manager.js +304 -0
- package/dist/context/types.d.ts +95 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +7 -3
- package/dist/messages/index.d.ts +13 -0
- package/dist/messages/index.js +51 -0
- package/dist/permissions/manager.js +6 -1
- package/dist/providers/gemini.js +1 -3
- package/dist/providers/mock.js +8 -0
- package/dist/providers/openai-compatible.js +1 -3
- package/dist/skills/index.js +691 -0
- package/dist/tools/builtin/index.d.ts +6 -1
- package/dist/tools/builtin/index.js +7 -0
- package/dist/tools/builtin/suggest.d.ts +57 -0
- package/dist/tools/builtin/suggest.js +99 -0
- package/dist/tools/builtin/task.js +13 -8
- package/dist/tools/builtin/tool-names.d.ts +44 -0
- package/dist/tools/builtin/tool-names.js +51 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +5 -1
- package/dist/tools/registry.d.ts +4 -0
- package/dist/tools/registry.js +9 -0
- package/package.json +1 -1
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
import type { Message, LLMProvider } from '../providers/types.js';
|
|
21
|
-
import type { ContextConfig, ContextStats, CompactionResult, SummarizationResult, ContextEventHandler, ContextCategory, CategoryBudgetInfo, BudgetAllocation, PreflightResult, VerbosityLevel } from './types.js';
|
|
21
|
+
import type { ContextConfig, ContextStats, CompactionResult, SummarizationResult, ContextEventHandler, ContextCategory, CategoryBudgetInfo, BudgetAllocation, PreflightResult, VerbosityLevel, CategorizedMessages, SmartCompactOptions, SmartCompactionResult } from './types.js';
|
|
22
|
+
import type { FileAccessTracker } from './file-tracker.js';
|
|
22
23
|
/**
|
|
23
24
|
* Default budget allocation
|
|
24
25
|
*/
|
|
@@ -43,6 +44,12 @@ export interface ContextManagerOptions {
|
|
|
43
44
|
* Event handler for context events
|
|
44
45
|
*/
|
|
45
46
|
onEvent?: ContextEventHandler;
|
|
47
|
+
/**
|
|
48
|
+
* File access tracker for context restoration hints.
|
|
49
|
+
* When provided, compaction/summarization will inject hints
|
|
50
|
+
* about previously accessed files.
|
|
51
|
+
*/
|
|
52
|
+
fileTracker?: FileAccessTracker;
|
|
46
53
|
}
|
|
47
54
|
/**
|
|
48
55
|
* ContextManager tracks and manages context window usage
|
|
@@ -51,6 +58,7 @@ export declare class ContextManager {
|
|
|
51
58
|
private readonly provider;
|
|
52
59
|
private readonly config;
|
|
53
60
|
private readonly onEvent?;
|
|
61
|
+
private readonly fileTracker?;
|
|
54
62
|
private cachedTokenCount;
|
|
55
63
|
private turnCount;
|
|
56
64
|
private compactionCount;
|
|
@@ -110,6 +118,23 @@ export declare class ContextManager {
|
|
|
110
118
|
* Get the current configuration
|
|
111
119
|
*/
|
|
112
120
|
getConfig(): ContextConfig;
|
|
121
|
+
/**
|
|
122
|
+
* Get the file access tracker (if configured)
|
|
123
|
+
*/
|
|
124
|
+
getFileTracker(): FileAccessTracker | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* Format context restoration hints based on current verbosity level.
|
|
127
|
+
* Returns empty string if no file tracker is configured or no files have been accessed.
|
|
128
|
+
*/
|
|
129
|
+
formatRestorationHints(): string;
|
|
130
|
+
/**
|
|
131
|
+
* Create a system message with context restoration hints.
|
|
132
|
+
* Returns undefined if no hints are available.
|
|
133
|
+
*
|
|
134
|
+
* The caller (Agent) is responsible for injecting this message
|
|
135
|
+
* after compaction or summarization.
|
|
136
|
+
*/
|
|
137
|
+
createRestorationHintMessage(): Message | undefined;
|
|
113
138
|
/**
|
|
114
139
|
* Get budget information for a specific category
|
|
115
140
|
*/
|
|
@@ -144,6 +169,49 @@ export declare class ContextManager {
|
|
|
144
169
|
* Determine which category a message belongs to
|
|
145
170
|
*/
|
|
146
171
|
private categorizeMessage;
|
|
172
|
+
/**
|
|
173
|
+
* Categorize all messages by type for smart compaction
|
|
174
|
+
*
|
|
175
|
+
* This method:
|
|
176
|
+
* 1. Identifies system messages
|
|
177
|
+
* 2. Separates recent messages (last N turns) that should never be compacted
|
|
178
|
+
* 3. Identifies tool results in older messages
|
|
179
|
+
* 4. Classifies remaining older messages as history
|
|
180
|
+
*
|
|
181
|
+
* @param messages All messages to categorize
|
|
182
|
+
* @param preserveRecentTurns Number of recent turns to preserve (default: config value)
|
|
183
|
+
*/
|
|
184
|
+
categorizeMessages(messages: Message[], preserveRecentTurns?: number): Promise<CategorizedMessages>;
|
|
185
|
+
/**
|
|
186
|
+
* Find which categories are over their budget allocation
|
|
187
|
+
*
|
|
188
|
+
* @param categorized Categorized messages from categorizeMessages()
|
|
189
|
+
* @returns Array of categories that exceed their budget, sorted by how much they exceed
|
|
190
|
+
*/
|
|
191
|
+
findOverBudgetCategories(categorized: CategorizedMessages): ContextCategory[];
|
|
192
|
+
/**
|
|
193
|
+
* Smart compaction that respects category budgets
|
|
194
|
+
*
|
|
195
|
+
* Strategy:
|
|
196
|
+
* 1. System messages: Never compacted (critical for agent behavior)
|
|
197
|
+
* 2. Recent messages: Never compacted (needed for conversation continuity)
|
|
198
|
+
* 3. Tool results: Save large results to files, replace with references
|
|
199
|
+
* 4. History: Summarize with LLM
|
|
200
|
+
*
|
|
201
|
+
* The method compacts categories in order of how much they exceed their budget.
|
|
202
|
+
*/
|
|
203
|
+
smartCompact(messages: Message[], options: SmartCompactOptions): Promise<{
|
|
204
|
+
messages: Message[];
|
|
205
|
+
result: SmartCompactionResult;
|
|
206
|
+
}>;
|
|
207
|
+
/**
|
|
208
|
+
* Compact tool results by saving large ones to files
|
|
209
|
+
*/
|
|
210
|
+
private compactToolResults;
|
|
211
|
+
/**
|
|
212
|
+
* Calculate utilization given a token count
|
|
213
|
+
*/
|
|
214
|
+
private calculateUtilization;
|
|
147
215
|
/**
|
|
148
216
|
* Estimate tokens for a string content
|
|
149
217
|
*/
|
package/dist/context/manager.js
CHANGED
|
@@ -66,6 +66,7 @@ export class ContextManager {
|
|
|
66
66
|
provider;
|
|
67
67
|
config;
|
|
68
68
|
onEvent;
|
|
69
|
+
fileTracker;
|
|
69
70
|
cachedTokenCount = 0;
|
|
70
71
|
turnCount = 0;
|
|
71
72
|
compactionCount = 0;
|
|
@@ -82,6 +83,7 @@ export class ContextManager {
|
|
|
82
83
|
this.provider = options.provider;
|
|
83
84
|
this.config = this.mergeConfig(options.config);
|
|
84
85
|
this.onEvent = options.onEvent;
|
|
86
|
+
this.fileTracker = options.fileTracker;
|
|
85
87
|
}
|
|
86
88
|
/**
|
|
87
89
|
* Merge partial config with defaults
|
|
@@ -221,6 +223,41 @@ export class ContextManager {
|
|
|
221
223
|
getConfig() {
|
|
222
224
|
return { ...this.config };
|
|
223
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Get the file access tracker (if configured)
|
|
228
|
+
*/
|
|
229
|
+
getFileTracker() {
|
|
230
|
+
return this.fileTracker;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Format context restoration hints based on current verbosity level.
|
|
234
|
+
* Returns empty string if no file tracker is configured or no files have been accessed.
|
|
235
|
+
*/
|
|
236
|
+
formatRestorationHints() {
|
|
237
|
+
if (!this.fileTracker || this.fileTracker.size === 0) {
|
|
238
|
+
return '';
|
|
239
|
+
}
|
|
240
|
+
return this.fileTracker.formatRestorationHints({
|
|
241
|
+
verbosityLevel: this.getVerbosityLevel(),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Create a system message with context restoration hints.
|
|
246
|
+
* Returns undefined if no hints are available.
|
|
247
|
+
*
|
|
248
|
+
* The caller (Agent) is responsible for injecting this message
|
|
249
|
+
* after compaction or summarization.
|
|
250
|
+
*/
|
|
251
|
+
createRestorationHintMessage() {
|
|
252
|
+
const hints = this.formatRestorationHints();
|
|
253
|
+
if (!hints) {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
role: 'user',
|
|
258
|
+
content: hints,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
224
261
|
// ==========================================================================
|
|
225
262
|
// Budget System (Novel Technique #1)
|
|
226
263
|
// ==========================================================================
|
|
@@ -371,6 +408,273 @@ export class ContextManager {
|
|
|
371
408
|
return 'recentMessages';
|
|
372
409
|
}
|
|
373
410
|
// ==========================================================================
|
|
411
|
+
// Smart Compaction (Phase 1)
|
|
412
|
+
// ==========================================================================
|
|
413
|
+
/**
|
|
414
|
+
* Categorize all messages by type for smart compaction
|
|
415
|
+
*
|
|
416
|
+
* This method:
|
|
417
|
+
* 1. Identifies system messages
|
|
418
|
+
* 2. Separates recent messages (last N turns) that should never be compacted
|
|
419
|
+
* 3. Identifies tool results in older messages
|
|
420
|
+
* 4. Classifies remaining older messages as history
|
|
421
|
+
*
|
|
422
|
+
* @param messages All messages to categorize
|
|
423
|
+
* @param preserveRecentTurns Number of recent turns to preserve (default: config value)
|
|
424
|
+
*/
|
|
425
|
+
async categorizeMessages(messages, preserveRecentTurns) {
|
|
426
|
+
const recentTurns = preserveRecentTurns ?? this.config.compaction.preserveRecentTurns;
|
|
427
|
+
const preserveCount = recentTurns * 2; // 2 messages per turn (user + assistant)
|
|
428
|
+
const result = {
|
|
429
|
+
system: [],
|
|
430
|
+
recentMessages: [],
|
|
431
|
+
toolResults: [],
|
|
432
|
+
history: [],
|
|
433
|
+
tokenCounts: {
|
|
434
|
+
system: 0,
|
|
435
|
+
recentMessages: 0,
|
|
436
|
+
toolResults: 0,
|
|
437
|
+
history: 0,
|
|
438
|
+
},
|
|
439
|
+
originalIndices: new Map(),
|
|
440
|
+
};
|
|
441
|
+
// First pass: separate system messages and track indices
|
|
442
|
+
const nonSystemMessages = [];
|
|
443
|
+
for (let i = 0; i < messages.length; i++) {
|
|
444
|
+
const msg = messages[i];
|
|
445
|
+
result.originalIndices.set(msg, i);
|
|
446
|
+
if (msg.role === 'system') {
|
|
447
|
+
result.system.push(msg);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
nonSystemMessages.push(msg);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// Second pass: separate recent vs older messages
|
|
454
|
+
const recentStartIndex = Math.max(0, nonSystemMessages.length - preserveCount);
|
|
455
|
+
const recentMessages = nonSystemMessages.slice(recentStartIndex);
|
|
456
|
+
const olderMessages = nonSystemMessages.slice(0, recentStartIndex);
|
|
457
|
+
result.recentMessages = recentMessages;
|
|
458
|
+
// Third pass: categorize older messages
|
|
459
|
+
for (const msg of olderMessages) {
|
|
460
|
+
const category = this.categorizeMessage(msg);
|
|
461
|
+
if (category === 'toolResults') {
|
|
462
|
+
result.toolResults.push(msg);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
result.history.push(msg);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// Count tokens for each category
|
|
469
|
+
if (result.system.length > 0) {
|
|
470
|
+
result.tokenCounts.system = await this.countTokens(result.system);
|
|
471
|
+
}
|
|
472
|
+
if (result.recentMessages.length > 0) {
|
|
473
|
+
result.tokenCounts.recentMessages = await this.countTokens(result.recentMessages);
|
|
474
|
+
}
|
|
475
|
+
if (result.toolResults.length > 0) {
|
|
476
|
+
result.tokenCounts.toolResults = await this.countTokens(result.toolResults);
|
|
477
|
+
}
|
|
478
|
+
if (result.history.length > 0) {
|
|
479
|
+
result.tokenCounts.history = await this.countTokens(result.history);
|
|
480
|
+
}
|
|
481
|
+
return result;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Find which categories are over their budget allocation
|
|
485
|
+
*
|
|
486
|
+
* @param categorized Categorized messages from categorizeMessages()
|
|
487
|
+
* @returns Array of categories that exceed their budget, sorted by how much they exceed
|
|
488
|
+
*/
|
|
489
|
+
findOverBudgetCategories(categorized) {
|
|
490
|
+
const overBudget = [];
|
|
491
|
+
for (const category of ['system', 'toolResults', 'history']) {
|
|
492
|
+
// Skip recentMessages - they're never compacted
|
|
493
|
+
const budget = this.getCategoryBudget(category);
|
|
494
|
+
const used = categorized.tokenCounts[category];
|
|
495
|
+
const allocated = budget.allocatedTokens;
|
|
496
|
+
if (used > allocated) {
|
|
497
|
+
overBudget.push({
|
|
498
|
+
category,
|
|
499
|
+
excess: used - allocated,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// Sort by excess (most over-budget first)
|
|
504
|
+
overBudget.sort((a, b) => b.excess - a.excess);
|
|
505
|
+
return overBudget.map((item) => item.category);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Smart compaction that respects category budgets
|
|
509
|
+
*
|
|
510
|
+
* Strategy:
|
|
511
|
+
* 1. System messages: Never compacted (critical for agent behavior)
|
|
512
|
+
* 2. Recent messages: Never compacted (needed for conversation continuity)
|
|
513
|
+
* 3. Tool results: Save large results to files, replace with references
|
|
514
|
+
* 4. History: Summarize with LLM
|
|
515
|
+
*
|
|
516
|
+
* The method compacts categories in order of how much they exceed their budget.
|
|
517
|
+
*/
|
|
518
|
+
async smartCompact(messages, options) {
|
|
519
|
+
const emergency = options.emergency ?? this.needsEmergencySummarization();
|
|
520
|
+
const targetUtilization = options.targetUtilization ?? this.config.summarization.targetUtilization;
|
|
521
|
+
const preserveRecentTurns = options.preserveRecentTurns ?? this.config.compaction.preserveRecentTurns;
|
|
522
|
+
// Categorize all messages
|
|
523
|
+
const categorized = await this.categorizeMessages(messages, preserveRecentTurns);
|
|
524
|
+
const tokensBefore = categorized.tokenCounts.system +
|
|
525
|
+
categorized.tokenCounts.recentMessages +
|
|
526
|
+
categorized.tokenCounts.toolResults +
|
|
527
|
+
categorized.tokenCounts.history;
|
|
528
|
+
const filesCreated = [];
|
|
529
|
+
let fileIndex = 0;
|
|
530
|
+
let summarizationRounds = 0;
|
|
531
|
+
let summary;
|
|
532
|
+
// Initialize category stats
|
|
533
|
+
const categoryStats = {
|
|
534
|
+
system: {
|
|
535
|
+
tokensBefore: categorized.tokenCounts.system,
|
|
536
|
+
tokensAfter: categorized.tokenCounts.system,
|
|
537
|
+
action: 'preserved',
|
|
538
|
+
},
|
|
539
|
+
recentMessages: {
|
|
540
|
+
tokensBefore: categorized.tokenCounts.recentMessages,
|
|
541
|
+
tokensAfter: categorized.tokenCounts.recentMessages,
|
|
542
|
+
action: 'preserved',
|
|
543
|
+
},
|
|
544
|
+
toolResults: {
|
|
545
|
+
tokensBefore: categorized.tokenCounts.toolResults,
|
|
546
|
+
tokensAfter: categorized.tokenCounts.toolResults,
|
|
547
|
+
action: 'preserved',
|
|
548
|
+
},
|
|
549
|
+
history: {
|
|
550
|
+
tokensBefore: categorized.tokenCounts.history,
|
|
551
|
+
tokensAfter: categorized.tokenCounts.history,
|
|
552
|
+
action: 'preserved',
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
// Find which categories need compaction
|
|
556
|
+
const overBudgetCategories = this.findOverBudgetCategories(categorized);
|
|
557
|
+
// Process tool results if over budget
|
|
558
|
+
let compactedToolResults = categorized.toolResults;
|
|
559
|
+
if (overBudgetCategories.includes('toolResults') && categorized.toolResults.length > 0) {
|
|
560
|
+
const { messages: compacted, files } = await this.compactToolResults(categorized.toolResults, options.saveToFile, fileIndex);
|
|
561
|
+
compactedToolResults = compacted;
|
|
562
|
+
filesCreated.push(...files);
|
|
563
|
+
fileIndex += files.length;
|
|
564
|
+
const tokensAfter = await this.countTokens(compactedToolResults);
|
|
565
|
+
categoryStats.toolResults.tokensAfter = tokensAfter;
|
|
566
|
+
categoryStats.toolResults.action = 'compacted';
|
|
567
|
+
}
|
|
568
|
+
// Process history if over budget or if we need to reach target utilization
|
|
569
|
+
let compactedHistory = categorized.history;
|
|
570
|
+
const currentUtilization = this.calculateUtilization(categoryStats.system.tokensAfter +
|
|
571
|
+
categoryStats.recentMessages.tokensAfter +
|
|
572
|
+
categoryStats.toolResults.tokensAfter +
|
|
573
|
+
categorized.tokenCounts.history);
|
|
574
|
+
if ((overBudgetCategories.includes('history') || currentUtilization > targetUtilization) &&
|
|
575
|
+
categorized.history.length > 0) {
|
|
576
|
+
// Summarize history
|
|
577
|
+
summary = await options.generateSummary(categorized.history);
|
|
578
|
+
summarizationRounds = 1;
|
|
579
|
+
// Create summary message
|
|
580
|
+
const summaryMessage = {
|
|
581
|
+
role: 'user',
|
|
582
|
+
content: `[Previous conversation summary]\n\n${summary}\n\n[End of summary]`,
|
|
583
|
+
};
|
|
584
|
+
compactedHistory = [summaryMessage];
|
|
585
|
+
const tokensAfter = await this.countTokens(compactedHistory);
|
|
586
|
+
categoryStats.history.tokensAfter = tokensAfter;
|
|
587
|
+
categoryStats.history.action = 'summarized';
|
|
588
|
+
}
|
|
589
|
+
// Reconstruct messages in original order
|
|
590
|
+
// System messages come first, then summary (if any), then tool results, then recent
|
|
591
|
+
const finalMessages = [
|
|
592
|
+
...categorized.system,
|
|
593
|
+
...compactedHistory,
|
|
594
|
+
...compactedToolResults,
|
|
595
|
+
...categorized.recentMessages,
|
|
596
|
+
];
|
|
597
|
+
// If history was summarized, add assistant acknowledgment after summary
|
|
598
|
+
if (categoryStats.history.action === 'summarized' && compactedHistory.length > 0) {
|
|
599
|
+
// Insert assistant acknowledgment after the summary message
|
|
600
|
+
const summaryIndex = categorized.system.length + 1; // After system + summary
|
|
601
|
+
finalMessages.splice(summaryIndex, 0, {
|
|
602
|
+
role: 'assistant',
|
|
603
|
+
content: 'I understand. I have the context from our previous conversation.',
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
const tokensAfter = await this.countTokens(finalMessages);
|
|
607
|
+
this.cachedTokenCount = tokensAfter;
|
|
608
|
+
this.compactionCount++;
|
|
609
|
+
this.lastCompactionTurn = this.turnCount;
|
|
610
|
+
const result = {
|
|
611
|
+
tokensBefore,
|
|
612
|
+
tokensAfter,
|
|
613
|
+
categoryStats,
|
|
614
|
+
filesCreated,
|
|
615
|
+
summary,
|
|
616
|
+
summarizationRounds,
|
|
617
|
+
emergency,
|
|
618
|
+
};
|
|
619
|
+
return { messages: finalMessages, result };
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Compact tool results by saving large ones to files
|
|
623
|
+
*/
|
|
624
|
+
async compactToolResults(messages, saveToFile, startIndex) {
|
|
625
|
+
const compactedMessages = [];
|
|
626
|
+
const files = [];
|
|
627
|
+
let fileIndex = startIndex;
|
|
628
|
+
for (const msg of messages) {
|
|
629
|
+
if (typeof msg.content === 'string') {
|
|
630
|
+
// String content - check if large enough to compact
|
|
631
|
+
const tokens = this.estimateTokens(msg.content);
|
|
632
|
+
if (tokens >= this.config.compaction.minTokensToCompact) {
|
|
633
|
+
const filePath = await saveToFile(msg.content, fileIndex++);
|
|
634
|
+
files.push(filePath);
|
|
635
|
+
compactedMessages.push({
|
|
636
|
+
...msg,
|
|
637
|
+
content: `[Content saved to ${filePath}]`,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
compactedMessages.push(msg);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
// Content blocks - compact large tool_results
|
|
646
|
+
const compactedBlocks = [];
|
|
647
|
+
for (const block of msg.content) {
|
|
648
|
+
if (block.type === 'tool_result') {
|
|
649
|
+
const tokens = this.estimateTokens(block.content);
|
|
650
|
+
if (tokens >= this.config.compaction.minTokensToCompact) {
|
|
651
|
+
const filePath = await saveToFile(block.content, fileIndex++);
|
|
652
|
+
files.push(filePath);
|
|
653
|
+
compactedBlocks.push({
|
|
654
|
+
...block,
|
|
655
|
+
content: `[Content saved to ${filePath}]`,
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
compactedBlocks.push(block);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
compactedBlocks.push(block);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
compactedMessages.push({ ...msg, content: compactedBlocks });
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return { messages: compactedMessages, files };
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Calculate utilization given a token count
|
|
673
|
+
*/
|
|
674
|
+
calculateUtilization(tokens) {
|
|
675
|
+
return tokens / this.config.maxContextTokens;
|
|
676
|
+
}
|
|
677
|
+
// ==========================================================================
|
|
374
678
|
// Pre-flight Checks (Novel Technique #2)
|
|
375
679
|
// ==========================================================================
|
|
376
680
|
/**
|
package/dist/context/types.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Types for managing agent context windows, including token tracking,
|
|
5
5
|
* compaction, summarization, and filtering.
|
|
6
6
|
*/
|
|
7
|
+
import type { Message } from '../providers/types.js';
|
|
7
8
|
/**
|
|
8
9
|
* Context categories for budget allocation
|
|
9
10
|
*
|
|
@@ -342,6 +343,100 @@ export type ContextEvent = {
|
|
|
342
343
|
* Context event handler
|
|
343
344
|
*/
|
|
344
345
|
export type ContextEventHandler = (event: ContextEvent) => void;
|
|
346
|
+
/**
|
|
347
|
+
* Categorized messages for smart compaction
|
|
348
|
+
*/
|
|
349
|
+
export interface CategorizedMessages {
|
|
350
|
+
/**
|
|
351
|
+
* System prompt message(s)
|
|
352
|
+
*/
|
|
353
|
+
system: Message[];
|
|
354
|
+
/**
|
|
355
|
+
* Recent messages (last N turns, never compacted)
|
|
356
|
+
*/
|
|
357
|
+
recentMessages: Message[];
|
|
358
|
+
/**
|
|
359
|
+
* Messages containing tool results
|
|
360
|
+
*/
|
|
361
|
+
toolResults: Message[];
|
|
362
|
+
/**
|
|
363
|
+
* Older conversation history
|
|
364
|
+
*/
|
|
365
|
+
history: Message[];
|
|
366
|
+
/**
|
|
367
|
+
* Token counts per category
|
|
368
|
+
*/
|
|
369
|
+
tokenCounts: Record<ContextCategory, number>;
|
|
370
|
+
/**
|
|
371
|
+
* Original message indices for reconstruction
|
|
372
|
+
*/
|
|
373
|
+
originalIndices: Map<Message, number>;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Options for smart compaction
|
|
377
|
+
*/
|
|
378
|
+
export interface SmartCompactOptions {
|
|
379
|
+
/**
|
|
380
|
+
* Function to generate LLM summary for history messages
|
|
381
|
+
*/
|
|
382
|
+
generateSummary: (messages: Message[]) => Promise<string>;
|
|
383
|
+
/**
|
|
384
|
+
* Function to save content to a file (for tool results)
|
|
385
|
+
*/
|
|
386
|
+
saveToFile: (content: string, index: number) => Promise<string>;
|
|
387
|
+
/**
|
|
388
|
+
* Number of recent turns to preserve (not compact)
|
|
389
|
+
* @default from config.compaction.preserveRecentTurns
|
|
390
|
+
*/
|
|
391
|
+
preserveRecentTurns?: number;
|
|
392
|
+
/**
|
|
393
|
+
* Target utilization after compaction (0-1)
|
|
394
|
+
* @default from config.summarization.targetUtilization
|
|
395
|
+
*/
|
|
396
|
+
targetUtilization?: number;
|
|
397
|
+
/**
|
|
398
|
+
* Whether to use emergency mode (more aggressive)
|
|
399
|
+
* @default auto-detect based on utilization
|
|
400
|
+
*/
|
|
401
|
+
emergency?: boolean;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Result of smart compaction
|
|
405
|
+
*/
|
|
406
|
+
export interface SmartCompactionResult {
|
|
407
|
+
/**
|
|
408
|
+
* Total tokens before compaction
|
|
409
|
+
*/
|
|
410
|
+
tokensBefore: number;
|
|
411
|
+
/**
|
|
412
|
+
* Total tokens after compaction
|
|
413
|
+
*/
|
|
414
|
+
tokensAfter: number;
|
|
415
|
+
/**
|
|
416
|
+
* Per-category statistics
|
|
417
|
+
*/
|
|
418
|
+
categoryStats: Record<ContextCategory, {
|
|
419
|
+
tokensBefore: number;
|
|
420
|
+
tokensAfter: number;
|
|
421
|
+
action: 'preserved' | 'compacted' | 'summarized';
|
|
422
|
+
}>;
|
|
423
|
+
/**
|
|
424
|
+
* Files created during tool result compaction
|
|
425
|
+
*/
|
|
426
|
+
filesCreated: string[];
|
|
427
|
+
/**
|
|
428
|
+
* Generated summary (for history)
|
|
429
|
+
*/
|
|
430
|
+
summary?: string;
|
|
431
|
+
/**
|
|
432
|
+
* Number of summarization rounds performed
|
|
433
|
+
*/
|
|
434
|
+
summarizationRounds: number;
|
|
435
|
+
/**
|
|
436
|
+
* Whether emergency mode was used
|
|
437
|
+
*/
|
|
438
|
+
emergency: boolean;
|
|
439
|
+
}
|
|
345
440
|
/**
|
|
346
441
|
* Context statistics
|
|
347
442
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -17,15 +17,15 @@ export { GeminiProvider, createGeminiProvider } from './providers/index.js';
|
|
|
17
17
|
export type { GeminiProviderConfig } from './providers/index.js';
|
|
18
18
|
export { OpenAICompatibleProvider } from './providers/index.js';
|
|
19
19
|
export type { OpenAICompatibleConfig } from './providers/index.js';
|
|
20
|
-
export type { Tool, ToolHandler, ToolRegistry, ToolInputSchema, ToolExecutionResult, ToolRegistryOptions, DefineToolOptions, ReadFileInput, WriteFileInput, BashInput, BashResult, FifoDetectionResult, GrepInput, GlobInput, EditInput, TodoWriteInput, TodoReadInput, TodoItem, TodoStatus, TodoContextCleanupOptions, TaskInput, TaskResult, AgentTypeConfig, TaskToolOptions, ContextMode, ThoroughnessLevel, SubAgentEventInfo, } from './tools/index.js';
|
|
20
|
+
export type { Tool, ToolHandler, ToolRegistry, ToolInputSchema, ToolExecutionResult, ToolRegistryOptions, DefineToolOptions, ReadFileInput, WriteFileInput, BashInput, BashResult, FifoDetectionResult, GrepInput, GlobInput, EditInput, TodoWriteInput, TodoReadInput, TodoItem, TodoStatus, TodoContextCleanupOptions, TaskInput, TaskResult, AgentTypeConfig, TaskToolOptions, ContextMode, ThoroughnessLevel, SubAgentEventInfo, SuggestInput, SuggestToolOptions, } from './tools/index.js';
|
|
21
21
|
export { defineTool, createSuccessResult, createErrorResult, wrapToolExecute, DefaultToolRegistry, createToolRegistry, } from './tools/index.js';
|
|
22
|
-
export { readFileTool, createReadFileTool, writeFileTool, createWriteFileTool, bashTool, createBashTool, execStream, detectFifoUsage, bashOutputTool, createBashOutputTool, killShellTool, createKillShellTool, ShellManager, getDefaultShellManager, setDefaultShellManager, grepTool, createGrepTool, globTool, createGlobTool, editTool, createEditTool, todoWriteTool, todoReadTool, createTodoTools, TodoStore, resetDefaultTodoStore, getDefaultTodoStore, createIsolatedTodoStore, cleanupTodoContextMessages, getTodoContextStats, webFetchTool, createWebFetchTool, createTaskTool, defaultAgentTypes, builtinTools, allBuiltinTools, } from './tools/index.js';
|
|
23
|
-
export { userMessage, assistantMessage, systemMessage, textBlock, toolUseBlock, toolResultBlock, getTextContent, getToolUses, getToolResults, hasToolUses, validateToolUseResultPairing, ensureMessageContent, normalizeMessages, } from './messages/index.js';
|
|
22
|
+
export { readFileTool, createReadFileTool, writeFileTool, createWriteFileTool, bashTool, createBashTool, execStream, detectFifoUsage, bashOutputTool, createBashOutputTool, killShellTool, createKillShellTool, ShellManager, getDefaultShellManager, setDefaultShellManager, grepTool, createGrepTool, globTool, createGlobTool, editTool, createEditTool, todoWriteTool, todoReadTool, createTodoTools, TodoStore, resetDefaultTodoStore, getDefaultTodoStore, createIsolatedTodoStore, cleanupTodoContextMessages, getTodoContextStats, webFetchTool, createWebFetchTool, createTaskTool, defaultAgentTypes, suggestTool, createSuggestTool, builtinTools, allBuiltinTools, TOOL_NAMES, TOOL_SETS, } from './tools/index.js';
|
|
23
|
+
export { userMessage, assistantMessage, systemMessage, textBlock, toolUseBlock, toolResultBlock, getTextContent, getToolUses, getToolResults, hasToolUses, validateToolUseResultPairing, repairToolPairing, ensureMessageContent, normalizeMessages, } from './messages/index.js';
|
|
24
24
|
export type { ToolPairingValidation } from './messages/index.js';
|
|
25
25
|
export { generateId, sleep, retry, truncate } from './utils/index.js';
|
|
26
26
|
export { AgentError, ProviderError, ToolError, ToolTimeoutError, ToolLoopError, ValidationError, MaxIterationsError, AbortError, ContextOverflowError, isAgentError, isProviderError, isToolError, isToolTimeoutError, isToolLoopError, isContextOverflowError, wrapError, } from './errors.js';
|
|
27
|
-
export { ContextManager, DEFAULT_CONTEXT_CONFIG } from './context/index.js';
|
|
28
|
-
export type { ContextManagerOptions, ContextCategory, BudgetAllocation, CategoryBudgetInfo, PreflightResult, VerbosityLevel, VerbosityConfig, ContextConfig, FilteringConfig, CompactionConfig, SummarizationConfig, CompactionResult, SummarizationResult, FilteringResult, ContextEvent, ContextEventHandler, ContextStats, } from './context/index.js';
|
|
27
|
+
export { ContextManager, DEFAULT_CONTEXT_CONFIG, FileAccessTracker, createFileTrackingHook, TRACKED_TOOLS, } from './context/index.js';
|
|
28
|
+
export type { ContextManagerOptions, ContextCategory, BudgetAllocation, CategoryBudgetInfo, PreflightResult, VerbosityLevel, VerbosityConfig, ContextConfig, FilteringConfig, CompactionConfig, SummarizationConfig, CompactionResult, SummarizationResult, FilteringResult, ContextEvent, ContextEventHandler, ContextStats, FileAccessType, FileAccess, FileAccessTrackerOptions, FormatHintsOptions, FileAccessStats, } from './context/index.js';
|
|
29
29
|
export { SkillRegistry, defineSkill, createSkillRegistry, builtinSkills, getDefaultSkillRegistry, resetDefaultSkillRegistry, } from './skills/index.js';
|
|
30
30
|
export type { Skill, SkillInvocationResult, SkillInvokeOptions } from './skills/index.js';
|
|
31
31
|
export { JsonSerializer, CompactJsonSerializer, defaultSerializer, MemoryCheckpointer, FileCheckpointer, StateError, StateErrorCode, CURRENT_STATE_VERSION, } from './state/index.js';
|
package/dist/index.js
CHANGED
|
@@ -17,15 +17,19 @@ export { defineTool, createSuccessResult, createErrorResult, wrapToolExecute, De
|
|
|
17
17
|
// Built-in tools
|
|
18
18
|
export { readFileTool, createReadFileTool, writeFileTool, createWriteFileTool, bashTool, createBashTool, execStream, detectFifoUsage, bashOutputTool, createBashOutputTool, killShellTool, createKillShellTool, ShellManager, getDefaultShellManager, setDefaultShellManager, grepTool, createGrepTool, globTool, createGlobTool, editTool, createEditTool, todoWriteTool, todoReadTool, createTodoTools, TodoStore, resetDefaultTodoStore, getDefaultTodoStore, createIsolatedTodoStore, cleanupTodoContextMessages, getTodoContextStats, webFetchTool, createWebFetchTool,
|
|
19
19
|
// Task tool (sub-agent spawning)
|
|
20
|
-
createTaskTool, defaultAgentTypes,
|
|
20
|
+
createTaskTool, defaultAgentTypes,
|
|
21
|
+
// Suggest tool (next action suggestions)
|
|
22
|
+
suggestTool, createSuggestTool, builtinTools, allBuiltinTools,
|
|
23
|
+
// Tool names - single source of truth
|
|
24
|
+
TOOL_NAMES, TOOL_SETS, } from './tools/index.js';
|
|
21
25
|
// Message utilities
|
|
22
|
-
export { userMessage, assistantMessage, systemMessage, textBlock, toolUseBlock, toolResultBlock, getTextContent, getToolUses, getToolResults, hasToolUses, validateToolUseResultPairing, ensureMessageContent, normalizeMessages, } from './messages/index.js';
|
|
26
|
+
export { userMessage, assistantMessage, systemMessage, textBlock, toolUseBlock, toolResultBlock, getTextContent, getToolUses, getToolResults, hasToolUses, validateToolUseResultPairing, repairToolPairing, ensureMessageContent, normalizeMessages, } from './messages/index.js';
|
|
23
27
|
// Utilities
|
|
24
28
|
export { generateId, sleep, retry, truncate } from './utils/index.js';
|
|
25
29
|
// Errors
|
|
26
30
|
export { AgentError, ProviderError, ToolError, ToolTimeoutError, ToolLoopError, ValidationError, MaxIterationsError, AbortError, ContextOverflowError, isAgentError, isProviderError, isToolError, isToolTimeoutError, isToolLoopError, isContextOverflowError, wrapError, } from './errors.js';
|
|
27
31
|
// Context management
|
|
28
|
-
export { ContextManager, DEFAULT_CONTEXT_CONFIG } from './context/index.js';
|
|
32
|
+
export { ContextManager, DEFAULT_CONTEXT_CONFIG, FileAccessTracker, createFileTrackingHook, TRACKED_TOOLS, } from './context/index.js';
|
|
29
33
|
// Skills system
|
|
30
34
|
export { SkillRegistry, defineSkill, createSkillRegistry, builtinSkills, getDefaultSkillRegistry, resetDefaultSkillRegistry, } from './skills/index.js';
|
|
31
35
|
// State management
|
package/dist/messages/index.d.ts
CHANGED
|
@@ -80,3 +80,16 @@ export declare function ensureMessageContent(message: Message | {
|
|
|
80
80
|
* @returns Normalized messages
|
|
81
81
|
*/
|
|
82
82
|
export declare function normalizeMessages(messages: Message[]): Message[];
|
|
83
|
+
/**
|
|
84
|
+
* Repair tool use/result pairing issues in a message array.
|
|
85
|
+
*
|
|
86
|
+
* This function removes orphaned tool_result blocks (those without a matching tool_use)
|
|
87
|
+
* which can cause API errors like "function_response.name: Name cannot be empty" in Gemini.
|
|
88
|
+
*
|
|
89
|
+
* This is particularly useful after context compaction/summarization, which may remove
|
|
90
|
+
* tool_use messages while preserving tool_result messages in the "recent" window.
|
|
91
|
+
*
|
|
92
|
+
* @param messages - Array of messages to repair
|
|
93
|
+
* @returns New array with orphaned tool_results removed
|
|
94
|
+
*/
|
|
95
|
+
export declare function repairToolPairing(messages: Message[]): Message[];
|
package/dist/messages/index.js
CHANGED
|
@@ -153,3 +153,54 @@ export function ensureMessageContent(message) {
|
|
|
153
153
|
export function normalizeMessages(messages) {
|
|
154
154
|
return messages.map(ensureMessageContent);
|
|
155
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Repair tool use/result pairing issues in a message array.
|
|
158
|
+
*
|
|
159
|
+
* This function removes orphaned tool_result blocks (those without a matching tool_use)
|
|
160
|
+
* which can cause API errors like "function_response.name: Name cannot be empty" in Gemini.
|
|
161
|
+
*
|
|
162
|
+
* This is particularly useful after context compaction/summarization, which may remove
|
|
163
|
+
* tool_use messages while preserving tool_result messages in the "recent" window.
|
|
164
|
+
*
|
|
165
|
+
* @param messages - Array of messages to repair
|
|
166
|
+
* @returns New array with orphaned tool_results removed
|
|
167
|
+
*/
|
|
168
|
+
export function repairToolPairing(messages) {
|
|
169
|
+
// First, collect all tool_use IDs
|
|
170
|
+
const toolUseIds = new Set();
|
|
171
|
+
for (const message of messages) {
|
|
172
|
+
if (typeof message.content === 'string')
|
|
173
|
+
continue;
|
|
174
|
+
for (const block of message.content) {
|
|
175
|
+
if (block.type === 'tool_use') {
|
|
176
|
+
toolUseIds.add(block.id);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Now filter out orphaned tool_results
|
|
181
|
+
const repairedMessages = [];
|
|
182
|
+
for (const message of messages) {
|
|
183
|
+
if (typeof message.content === 'string') {
|
|
184
|
+
repairedMessages.push(message);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
// Filter content blocks to remove orphaned tool_results
|
|
188
|
+
const filteredBlocks = message.content.filter((block) => {
|
|
189
|
+
if (block.type === 'tool_result') {
|
|
190
|
+
// Only keep if there's a matching tool_use
|
|
191
|
+
return toolUseIds.has(block.toolUseId);
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
});
|
|
195
|
+
// If all blocks were removed, skip this message entirely
|
|
196
|
+
if (filteredBlocks.length === 0) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
// If some blocks remain, include the message with filtered content
|
|
200
|
+
repairedMessages.push({
|
|
201
|
+
...message,
|
|
202
|
+
content: filteredBlocks,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return repairedMessages;
|
|
206
|
+
}
|
|
@@ -243,7 +243,12 @@ export class PermissionManager {
|
|
|
243
243
|
};
|
|
244
244
|
}
|
|
245
245
|
case 'once': {
|
|
246
|
-
//
|
|
246
|
+
// Check if user explicitly granted session permission (via 'allow-always')
|
|
247
|
+
if (this.sessionGrants.has(toolName)) {
|
|
248
|
+
this.emit({ type: 'permission:granted', toolName, level, input, rule });
|
|
249
|
+
return { allowed: true, level, askedUser: false, rule };
|
|
250
|
+
}
|
|
251
|
+
// Ask for permission each time (unless session-granted above)
|
|
247
252
|
const allowed = await this.askPermission(toolName, input, level, rule);
|
|
248
253
|
if (allowed) {
|
|
249
254
|
this.emit({ type: 'permission:granted', toolName, level, input, rule });
|