@compilr-dev/agents 0.3.10 → 0.3.12
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/README.md +84 -41
- package/dist/agent.d.ts +23 -0
- package/dist/agent.js +41 -7
- package/dist/anchors/manager.js +3 -2
- package/dist/context/delegated-result-store.d.ts +67 -0
- package/dist/context/delegated-result-store.js +99 -0
- package/dist/context/delegation-types.d.ts +82 -0
- package/dist/context/delegation-types.js +18 -0
- package/dist/context/index.d.ts +6 -0
- package/dist/context/index.js +4 -0
- package/dist/context/manager.js +12 -32
- package/dist/context/tool-result-delegator.d.ts +63 -0
- package/dist/context/tool-result-delegator.js +305 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +9 -3
- package/dist/memory/loader.js +2 -1
- package/dist/memory/types.d.ts +1 -1
- package/dist/providers/claude.d.ts +1 -5
- package/dist/providers/claude.js +3 -28
- package/dist/providers/gemini-native.d.ts +1 -1
- package/dist/providers/gemini-native.js +3 -24
- package/dist/providers/mock.d.ts +1 -1
- package/dist/providers/mock.js +3 -24
- package/dist/providers/openai-compatible.d.ts +1 -5
- package/dist/providers/openai-compatible.js +3 -28
- package/dist/rate-limit/provider-wrapper.d.ts +1 -1
- package/dist/rate-limit/provider-wrapper.js +3 -27
- package/dist/tools/builtin/index.d.ts +2 -0
- package/dist/tools/builtin/index.js +2 -0
- package/dist/tools/builtin/recall-result.d.ts +29 -0
- package/dist/tools/builtin/recall-result.js +48 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +2 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/tokenizer.d.ts +18 -0
- package/dist/utils/tokenizer.js +56 -0
- package/package.json +8 -7
package/dist/context/manager.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
import { repairToolPairing } from '../messages/index.js';
|
|
21
|
+
import { countTokens, countMessageTokens } from '../utils/tokenizer.js';
|
|
21
22
|
/**
|
|
22
23
|
* Default budget allocation
|
|
23
24
|
*/
|
|
@@ -122,29 +123,8 @@ export class ContextManager {
|
|
|
122
123
|
if (this.provider.countTokens) {
|
|
123
124
|
return this.provider.countTokens(messages);
|
|
124
125
|
}
|
|
125
|
-
// Fallback:
|
|
126
|
-
|
|
127
|
-
for (const msg of messages) {
|
|
128
|
-
if (typeof msg.content === 'string') {
|
|
129
|
-
charCount += msg.content.length;
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
for (const block of msg.content) {
|
|
133
|
-
switch (block.type) {
|
|
134
|
-
case 'text':
|
|
135
|
-
charCount += block.text.length;
|
|
136
|
-
break;
|
|
137
|
-
case 'tool_use':
|
|
138
|
-
charCount += JSON.stringify(block.input).length;
|
|
139
|
-
break;
|
|
140
|
-
case 'tool_result':
|
|
141
|
-
charCount += block.content.length;
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return Math.ceil(charCount / 4);
|
|
126
|
+
// Fallback: count tokens using tiktoken
|
|
127
|
+
return countMessageTokens(messages);
|
|
148
128
|
}
|
|
149
129
|
/**
|
|
150
130
|
* Update token count for messages
|
|
@@ -351,7 +331,7 @@ export class ContextManager {
|
|
|
351
331
|
}
|
|
352
332
|
// Compact this message
|
|
353
333
|
if (typeof msg.content === 'string') {
|
|
354
|
-
const tokens =
|
|
334
|
+
const tokens = countTokens(msg.content);
|
|
355
335
|
if (tokens >= this.config.compaction.minTokensToCompact) {
|
|
356
336
|
const filePath = await saveToFile(msg.content, compactedIndex++);
|
|
357
337
|
filesCreated.push(filePath);
|
|
@@ -369,7 +349,7 @@ export class ContextManager {
|
|
|
369
349
|
const compactedBlocks = [];
|
|
370
350
|
for (const block of msg.content) {
|
|
371
351
|
if (block.type === 'tool_result' && category === 'toolResults') {
|
|
372
|
-
const tokens =
|
|
352
|
+
const tokens = countTokens(block.content);
|
|
373
353
|
if (tokens >= this.config.compaction.minTokensToCompact) {
|
|
374
354
|
const filePath = await saveToFile(block.content, compactedIndex++);
|
|
375
355
|
filesCreated.push(filePath);
|
|
@@ -702,7 +682,7 @@ export class ContextManager {
|
|
|
702
682
|
* Estimate tokens for a string content
|
|
703
683
|
*/
|
|
704
684
|
estimateTokens(content) {
|
|
705
|
-
return
|
|
685
|
+
return countTokens(content);
|
|
706
686
|
}
|
|
707
687
|
/**
|
|
708
688
|
* Check if content can be added to a category
|
|
@@ -822,13 +802,13 @@ export class ContextManager {
|
|
|
822
802
|
maxLines = this.config.filtering.maxErrorLines;
|
|
823
803
|
break;
|
|
824
804
|
default: {
|
|
825
|
-
// For tool results, check token count
|
|
826
|
-
const estimatedTokens =
|
|
805
|
+
// For tool results, check token count
|
|
806
|
+
const estimatedTokens = countTokens(content);
|
|
827
807
|
if (estimatedTokens <= this.config.filtering.maxToolResultTokens) {
|
|
828
808
|
return { content, filtered: false, originalLength };
|
|
829
809
|
}
|
|
830
|
-
// Truncate to roughly maxToolResultTokens
|
|
831
|
-
const maxChars = this.config.filtering.maxToolResultTokens *
|
|
810
|
+
// Truncate to roughly maxToolResultTokens (~3 chars per token)
|
|
811
|
+
const maxChars = this.config.filtering.maxToolResultTokens * 3;
|
|
832
812
|
const truncated = content.slice(0, maxChars);
|
|
833
813
|
return {
|
|
834
814
|
content: truncated + '\n\n[Content truncated - see file for full output]',
|
|
@@ -877,7 +857,7 @@ export class ContextManager {
|
|
|
877
857
|
for (const msg of oldMessages) {
|
|
878
858
|
if (typeof msg.content === 'string') {
|
|
879
859
|
// Check if string content is large enough to compact
|
|
880
|
-
const tokens =
|
|
860
|
+
const tokens = countTokens(msg.content);
|
|
881
861
|
if (tokens >= this.config.compaction.minTokensToCompact) {
|
|
882
862
|
const filePath = await saveToFile(msg.content, compactedIndex++);
|
|
883
863
|
filesCreated.push(filePath);
|
|
@@ -895,7 +875,7 @@ export class ContextManager {
|
|
|
895
875
|
const compactedBlocks = [];
|
|
896
876
|
for (const block of msg.content) {
|
|
897
877
|
if (block.type === 'tool_result') {
|
|
898
|
-
const tokens =
|
|
878
|
+
const tokens = countTokens(block.content);
|
|
899
879
|
if (tokens >= this.config.compaction.minTokensToCompact) {
|
|
900
880
|
const filePath = await saveToFile(block.content, compactedIndex++);
|
|
901
881
|
filesCreated.push(filePath);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Result Delegator
|
|
3
|
+
*
|
|
4
|
+
* Intercepts large tool results via the AfterToolHook mechanism,
|
|
5
|
+
* stores the full result for optional recall, and replaces it
|
|
6
|
+
* with a compact summary to conserve context tokens.
|
|
7
|
+
*/
|
|
8
|
+
import type { LLMProvider } from '../providers/types.js';
|
|
9
|
+
import type { AfterToolHook } from '../hooks/types.js';
|
|
10
|
+
import type { DelegationConfig, DelegationEvent } from './delegation-types.js';
|
|
11
|
+
import { DelegatedResultStore } from './delegated-result-store.js';
|
|
12
|
+
/**
|
|
13
|
+
* Options for creating a ToolResultDelegator.
|
|
14
|
+
*/
|
|
15
|
+
export interface ToolResultDelegatorOptions {
|
|
16
|
+
/** LLM provider for summarization (small/medium tier preferred) */
|
|
17
|
+
provider: LLMProvider;
|
|
18
|
+
/** Delegation configuration (merged with defaults) */
|
|
19
|
+
config?: Partial<DelegationConfig>;
|
|
20
|
+
/** Event callback for delegation lifecycle events */
|
|
21
|
+
onEvent?: (event: DelegationEvent) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Core class that creates an AfterToolHook for auto-delegating large tool results.
|
|
25
|
+
*/
|
|
26
|
+
export declare class ToolResultDelegator {
|
|
27
|
+
private readonly store;
|
|
28
|
+
private readonly config;
|
|
29
|
+
private readonly provider;
|
|
30
|
+
private readonly onEvent?;
|
|
31
|
+
constructor(options: ToolResultDelegatorOptions);
|
|
32
|
+
/**
|
|
33
|
+
* Returns an AfterToolHook to register with HooksManager.
|
|
34
|
+
*/
|
|
35
|
+
createHook(): AfterToolHook;
|
|
36
|
+
/**
|
|
37
|
+
* Perform the actual delegation (async path).
|
|
38
|
+
*/
|
|
39
|
+
private delegateResult;
|
|
40
|
+
/**
|
|
41
|
+
* Access the store (needed by recall_full_result tool).
|
|
42
|
+
*/
|
|
43
|
+
getStore(): DelegatedResultStore;
|
|
44
|
+
/**
|
|
45
|
+
* Get the resolved config for a specific tool.
|
|
46
|
+
*/
|
|
47
|
+
private getToolConfig;
|
|
48
|
+
/**
|
|
49
|
+
* Extractive summarization: first/last lines + prioritized structural markers.
|
|
50
|
+
* No LLM cost, fast, preserves actual code signatures.
|
|
51
|
+
*/
|
|
52
|
+
private summarizeExtractive;
|
|
53
|
+
/**
|
|
54
|
+
* LLM-based summarization using the provider.
|
|
55
|
+
* Returns null if the LLM call fails.
|
|
56
|
+
*/
|
|
57
|
+
private summarizeLLM;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* System prompt addition when delegation is enabled.
|
|
61
|
+
* Append to the agent's system prompt.
|
|
62
|
+
*/
|
|
63
|
+
export declare const DELEGATION_SYSTEM_PROMPT: string;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Result Delegator
|
|
3
|
+
*
|
|
4
|
+
* Intercepts large tool results via the AfterToolHook mechanism,
|
|
5
|
+
* stores the full result for optional recall, and replaces it
|
|
6
|
+
* with a compact summary to conserve context tokens.
|
|
7
|
+
*/
|
|
8
|
+
import { DEFAULT_DELEGATION_CONFIG } from './delegation-types.js';
|
|
9
|
+
import { DelegatedResultStore } from './delegated-result-store.js';
|
|
10
|
+
import { countTokens } from '../utils/tokenizer.js';
|
|
11
|
+
/**
|
|
12
|
+
* System prompt for LLM-based summarization.
|
|
13
|
+
*/
|
|
14
|
+
const SUMMARIZATION_SYSTEM_PROMPT = 'You are a tool result summarizer. Compress this tool output preserving:\n' +
|
|
15
|
+
'- ALL errors/warnings verbatim\n' +
|
|
16
|
+
'- File paths, function names, line numbers, counts\n' +
|
|
17
|
+
'- Overall structure (sections, item counts)\n' +
|
|
18
|
+
'- First few items of lists verbatim\n' +
|
|
19
|
+
'Drop: verbose repetition, successful routine output, function bodies.\n' +
|
|
20
|
+
'Never add information not in the original.\n' +
|
|
21
|
+
'Respond with ONLY the summary, no preamble.';
|
|
22
|
+
/**
|
|
23
|
+
* Core class that creates an AfterToolHook for auto-delegating large tool results.
|
|
24
|
+
*/
|
|
25
|
+
export class ToolResultDelegator {
|
|
26
|
+
store;
|
|
27
|
+
config;
|
|
28
|
+
provider;
|
|
29
|
+
onEvent;
|
|
30
|
+
constructor(options) {
|
|
31
|
+
this.provider = options.provider;
|
|
32
|
+
this.config = { ...DEFAULT_DELEGATION_CONFIG, ...options.config };
|
|
33
|
+
this.onEvent = options.onEvent;
|
|
34
|
+
this.store = new DelegatedResultStore({
|
|
35
|
+
maxSize: this.config.maxStoredResults,
|
|
36
|
+
defaultTTL: this.config.resultTTL,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns an AfterToolHook to register with HooksManager.
|
|
41
|
+
*/
|
|
42
|
+
createHook() {
|
|
43
|
+
const getToolConfig = this.getToolConfig.bind(this);
|
|
44
|
+
const delegateResult = this.delegateResult.bind(this);
|
|
45
|
+
return (context) => {
|
|
46
|
+
// Skip failed results — errors should always pass through
|
|
47
|
+
if (!context.result.success)
|
|
48
|
+
return undefined;
|
|
49
|
+
// Never re-delegate recall_full_result — that would create a loop
|
|
50
|
+
if (context.toolName === 'recall_full_result')
|
|
51
|
+
return undefined;
|
|
52
|
+
// Check if delegation is enabled for this tool
|
|
53
|
+
const toolConfig = getToolConfig(context.toolName);
|
|
54
|
+
if (!toolConfig.enabled)
|
|
55
|
+
return undefined;
|
|
56
|
+
// Serialize the result content
|
|
57
|
+
const content = typeof context.result.result === 'string'
|
|
58
|
+
? context.result.result
|
|
59
|
+
: JSON.stringify(context.result.result);
|
|
60
|
+
// Count tokens
|
|
61
|
+
const tokens = countTokens(content);
|
|
62
|
+
// Below threshold — pass through unchanged
|
|
63
|
+
if (tokens <= toolConfig.threshold)
|
|
64
|
+
return undefined;
|
|
65
|
+
// Delegate asynchronously
|
|
66
|
+
return delegateResult(context, content, tokens, toolConfig);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Perform the actual delegation (async path).
|
|
71
|
+
*/
|
|
72
|
+
async delegateResult(context, content, tokens, toolConfig) {
|
|
73
|
+
// Generate delegation ID
|
|
74
|
+
const id = this.store.generateId();
|
|
75
|
+
this.onEvent?.({
|
|
76
|
+
type: 'delegation:started',
|
|
77
|
+
toolName: context.toolName,
|
|
78
|
+
originalTokens: tokens,
|
|
79
|
+
delegationId: id,
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
// Determine strategy
|
|
83
|
+
const strategy = toolConfig.strategy === 'auto' ? 'extractive' : toolConfig.strategy;
|
|
84
|
+
let summary;
|
|
85
|
+
let usedStrategy = strategy;
|
|
86
|
+
if (strategy === 'llm') {
|
|
87
|
+
const llmSummary = await this.summarizeLLM(content, context.toolName);
|
|
88
|
+
if (llmSummary !== null) {
|
|
89
|
+
summary = llmSummary;
|
|
90
|
+
usedStrategy = 'llm';
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// LLM failed, fall back to extractive
|
|
94
|
+
summary = this.summarizeExtractive(content);
|
|
95
|
+
usedStrategy = 'extractive';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else if (toolConfig.strategy === 'auto') {
|
|
99
|
+
// Auto: try LLM first, fall back to extractive
|
|
100
|
+
const llmSummary = await this.summarizeLLM(content, context.toolName);
|
|
101
|
+
if (llmSummary !== null) {
|
|
102
|
+
summary = llmSummary;
|
|
103
|
+
usedStrategy = 'llm';
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
summary = this.summarizeExtractive(content);
|
|
107
|
+
usedStrategy = 'extractive';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
summary = this.summarizeExtractive(content);
|
|
112
|
+
usedStrategy = 'extractive';
|
|
113
|
+
}
|
|
114
|
+
const summaryTokens = countTokens(summary);
|
|
115
|
+
// Store the full result
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
const stored = {
|
|
118
|
+
id,
|
|
119
|
+
toolName: context.toolName,
|
|
120
|
+
toolInput: context.input,
|
|
121
|
+
fullContent: content,
|
|
122
|
+
fullTokens: tokens,
|
|
123
|
+
summary,
|
|
124
|
+
summaryTokens,
|
|
125
|
+
storedAt: now,
|
|
126
|
+
expiresAt: now + this.config.resultTTL,
|
|
127
|
+
};
|
|
128
|
+
this.store.add(stored);
|
|
129
|
+
this.onEvent?.({
|
|
130
|
+
type: 'delegation:completed',
|
|
131
|
+
toolName: context.toolName,
|
|
132
|
+
originalTokens: tokens,
|
|
133
|
+
summaryTokens,
|
|
134
|
+
delegationId: id,
|
|
135
|
+
strategy: usedStrategy,
|
|
136
|
+
});
|
|
137
|
+
// Return modified result with summary
|
|
138
|
+
// The format must be unmistakable so the LLM never confuses
|
|
139
|
+
// a delegated summary with the actual tool output.
|
|
140
|
+
return {
|
|
141
|
+
result: {
|
|
142
|
+
success: true,
|
|
143
|
+
result: {
|
|
144
|
+
_delegated: true,
|
|
145
|
+
_delegationId: id,
|
|
146
|
+
_originalTokens: tokens,
|
|
147
|
+
_summaryTokens: summaryTokens,
|
|
148
|
+
_warning: 'THIS IS A SUMMARY, NOT THE FULL RESULT. You did not see the full output.',
|
|
149
|
+
summary,
|
|
150
|
+
recall: `To get the full output, call recall_full_result with id="${id}"`,
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
this.onEvent?.({
|
|
157
|
+
type: 'delegation:failed',
|
|
158
|
+
toolName: context.toolName,
|
|
159
|
+
error: error instanceof Error ? error.message : String(error),
|
|
160
|
+
});
|
|
161
|
+
// On failure, return original result unchanged
|
|
162
|
+
return { result: context.result };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Access the store (needed by recall_full_result tool).
|
|
167
|
+
*/
|
|
168
|
+
getStore() {
|
|
169
|
+
return this.store;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get the resolved config for a specific tool.
|
|
173
|
+
*/
|
|
174
|
+
getToolConfig(toolName) {
|
|
175
|
+
const override = this.config.toolOverrides?.[toolName];
|
|
176
|
+
return {
|
|
177
|
+
enabled: override?.enabled ?? this.config.enabled,
|
|
178
|
+
threshold: override?.threshold ?? this.config.delegationThreshold,
|
|
179
|
+
strategy: override?.strategy ?? this.config.strategy,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Extractive summarization: first/last lines + prioritized structural markers.
|
|
184
|
+
* No LLM cost, fast, preserves actual code signatures.
|
|
185
|
+
*/
|
|
186
|
+
summarizeExtractive(content) {
|
|
187
|
+
const lines = content.split('\n');
|
|
188
|
+
const totalLines = lines.length;
|
|
189
|
+
if (totalLines <= 40) {
|
|
190
|
+
// Small enough to include mostly as-is
|
|
191
|
+
return content;
|
|
192
|
+
}
|
|
193
|
+
const parts = [];
|
|
194
|
+
// Header: makes it impossible to confuse with full content
|
|
195
|
+
parts.push(`[SUMMARIZED — showing first 20 + last 10 of ${String(totalLines)} lines, plus key signatures. Use recall_full_result for complete output.]`);
|
|
196
|
+
parts.push('');
|
|
197
|
+
// First 20 lines
|
|
198
|
+
parts.push(...lines.slice(0, 20));
|
|
199
|
+
// Extract structural markers from the middle, prioritized by importance
|
|
200
|
+
const middleLines = lines.slice(20, -10);
|
|
201
|
+
// High priority: exports, class/interface/function declarations with signatures
|
|
202
|
+
const highPriority = /^\s*export\s+(default\s+)?(async\s+)?(function|class|interface|type|const|enum)\s/;
|
|
203
|
+
// Medium priority: non-exported declarations, method signatures
|
|
204
|
+
const medPriority = /^\s*(async\s+)?(function|class|interface|type|const|enum)\s|^\s+(async\s+)?(\w+)\s*\([^)]*\)\s*[:{]/;
|
|
205
|
+
// Low priority: errors, headings, section markers
|
|
206
|
+
const lowPriority = /^\s*(ERROR|WARN|FAIL|error|warning|#{1,6}\s|---{3,}|\*{3,}|={3,}|\/\/\s*={3,})/;
|
|
207
|
+
const high = [];
|
|
208
|
+
const med = [];
|
|
209
|
+
const low = [];
|
|
210
|
+
for (const line of middleLines) {
|
|
211
|
+
if (highPriority.test(line)) {
|
|
212
|
+
high.push(line);
|
|
213
|
+
}
|
|
214
|
+
else if (medPriority.test(line)) {
|
|
215
|
+
med.push(line);
|
|
216
|
+
}
|
|
217
|
+
else if (lowPriority.test(line)) {
|
|
218
|
+
low.push(line);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Budget: scale markers with file size, cap at 40
|
|
222
|
+
const totalMarkers = high.length + med.length + low.length;
|
|
223
|
+
const budget = Math.min(40, Math.max(15, Math.floor(totalLines / 80)));
|
|
224
|
+
// Fill budget: all high, then medium, then low
|
|
225
|
+
const selected = [];
|
|
226
|
+
for (const line of high) {
|
|
227
|
+
if (selected.length >= budget)
|
|
228
|
+
break;
|
|
229
|
+
selected.push(line);
|
|
230
|
+
}
|
|
231
|
+
for (const line of med) {
|
|
232
|
+
if (selected.length >= budget)
|
|
233
|
+
break;
|
|
234
|
+
selected.push(line);
|
|
235
|
+
}
|
|
236
|
+
for (const line of low) {
|
|
237
|
+
if (selected.length >= budget)
|
|
238
|
+
break;
|
|
239
|
+
selected.push(line);
|
|
240
|
+
}
|
|
241
|
+
if (selected.length > 0) {
|
|
242
|
+
parts.push('');
|
|
243
|
+
parts.push(`... (${String(middleLines.length)} lines omitted, ${String(totalMarkers)} structural markers found, showing ${String(selected.length)} key signatures) ...`);
|
|
244
|
+
parts.push(...selected);
|
|
245
|
+
if (totalMarkers > selected.length) {
|
|
246
|
+
parts.push(`... (${String(totalMarkers - selected.length)} more markers omitted) ...`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
parts.push('');
|
|
251
|
+
parts.push(`... (${String(middleLines.length)} lines omitted) ...`);
|
|
252
|
+
}
|
|
253
|
+
// Last 10 lines
|
|
254
|
+
parts.push('');
|
|
255
|
+
parts.push(...lines.slice(-10));
|
|
256
|
+
parts.push('');
|
|
257
|
+
parts.push(`[Total: ${String(totalLines)} lines]`);
|
|
258
|
+
return parts.join('\n');
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* LLM-based summarization using the provider.
|
|
262
|
+
* Returns null if the LLM call fails.
|
|
263
|
+
*/
|
|
264
|
+
async summarizeLLM(content, toolName) {
|
|
265
|
+
try {
|
|
266
|
+
const messages = [
|
|
267
|
+
{
|
|
268
|
+
role: 'system',
|
|
269
|
+
content: SUMMARIZATION_SYSTEM_PROMPT,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
role: 'user',
|
|
273
|
+
content: `Summarize this ${toolName} tool result (keep under ${String(this.config.summaryMaxTokens)} tokens):\n\n${content}`,
|
|
274
|
+
},
|
|
275
|
+
];
|
|
276
|
+
let text = '';
|
|
277
|
+
for await (const chunk of this.provider.chat(messages, {
|
|
278
|
+
maxTokens: this.config.summaryMaxTokens,
|
|
279
|
+
temperature: 0,
|
|
280
|
+
model: undefined, // Use the provider's default model
|
|
281
|
+
})) {
|
|
282
|
+
if (chunk.type === 'text' && chunk.text) {
|
|
283
|
+
text += chunk.text;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return text.trim() || null;
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* System prompt addition when delegation is enabled.
|
|
295
|
+
* Append to the agent's system prompt.
|
|
296
|
+
*/
|
|
297
|
+
export const DELEGATION_SYSTEM_PROMPT = '\n\n## Tool Result Delegation\n' +
|
|
298
|
+
'Some tool results are automatically summarized to conserve context. When a result has `_delegated: true`, ' +
|
|
299
|
+
'you received a SUMMARY, not the full output. You MUST acknowledge this — never claim you read the full content.\n' +
|
|
300
|
+
'Delegated results include:\n' +
|
|
301
|
+
'- `_warning`: confirms this is a summary\n' +
|
|
302
|
+
'- `summary`: extracted key information\n' +
|
|
303
|
+
'- `recall`: how to get the full output\n' +
|
|
304
|
+
'Use `recall_full_result` with the delegation ID to get the full output when the summary is insufficient.\n' +
|
|
305
|
+
'Only recall when you need specific code, exact values, or the summary indicates omitted content.\n';
|
package/dist/index.d.ts
CHANGED
|
@@ -31,16 +31,16 @@ export { PerplexityProvider, createPerplexityProvider } from './providers/index.
|
|
|
31
31
|
export type { PerplexityProviderConfig } from './providers/index.js';
|
|
32
32
|
export { OpenRouterProvider, createOpenRouterProvider } from './providers/index.js';
|
|
33
33
|
export type { OpenRouterProviderConfig } from './providers/index.js';
|
|
34
|
-
export type { Tool, ToolHandler, ToolRegistry, ToolInputSchema, ToolExecutionResult, ToolRegistryOptions, ToolFallbackHandler, 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';
|
|
34
|
+
export type { Tool, ToolHandler, ToolRegistry, ToolInputSchema, ToolExecutionResult, ToolRegistryOptions, ToolFallbackHandler, DefineToolOptions, ReadFileInput, WriteFileInput, BashInput, BashResult, FifoDetectionResult, GrepInput, GlobInput, EditInput, TodoWriteInput, TodoReadInput, TodoItem, TodoStatus, TodoContextCleanupOptions, TaskInput, TaskResult, AgentTypeConfig, TaskToolOptions, ContextMode, ThoroughnessLevel, SubAgentEventInfo, SuggestInput, SuggestToolOptions, RecallResultInput, RecallResultToolOptions, } from './tools/index.js';
|
|
35
35
|
export { defineTool, createSuccessResult, createErrorResult, wrapToolExecute, DefaultToolRegistry, createToolRegistry, } from './tools/index.js';
|
|
36
|
-
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';
|
|
36
|
+
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, createRecallResultTool, builtinTools, allBuiltinTools, TOOL_NAMES, TOOL_SETS, } from './tools/index.js';
|
|
37
37
|
export { userMessage, assistantMessage, systemMessage, textBlock, toolUseBlock, toolResultBlock, getTextContent, getToolUses, getToolResults, hasToolUses, validateToolUseResultPairing, repairToolPairing, ensureMessageContent, normalizeMessages, } from './messages/index.js';
|
|
38
38
|
export type { ToolPairingValidation } from './messages/index.js';
|
|
39
|
-
export { generateId, sleep, retry, truncate, withRetryGenerator, calculateBackoffDelay, DEFAULT_RETRY_CONFIG, } from './utils/index.js';
|
|
39
|
+
export { generateId, sleep, retry, truncate, withRetryGenerator, calculateBackoffDelay, DEFAULT_RETRY_CONFIG, countTokens, countMessageTokens, } from './utils/index.js';
|
|
40
40
|
export type { RetryConfig as LLMRetryConfig, WithRetryOptions } from './utils/index.js';
|
|
41
41
|
export { AgentError, ProviderError, ToolError, ToolTimeoutError, ToolLoopError, ValidationError, MaxIterationsError, AbortError, ContextOverflowError, isAgentError, isProviderError, isToolError, isToolTimeoutError, isToolLoopError, isContextOverflowError, wrapError, } from './errors.js';
|
|
42
|
-
export { ContextManager, DEFAULT_CONTEXT_CONFIG, FileAccessTracker, createFileTrackingHook, TRACKED_TOOLS, } from './context/index.js';
|
|
43
|
-
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';
|
|
42
|
+
export { ContextManager, DEFAULT_CONTEXT_CONFIG, FileAccessTracker, createFileTrackingHook, TRACKED_TOOLS, DelegatedResultStore, ToolResultDelegator, DELEGATION_SYSTEM_PROMPT, DEFAULT_DELEGATION_CONFIG, } from './context/index.js';
|
|
43
|
+
export type { ContextManagerOptions, ContextCategory, BudgetAllocation, CategoryBudgetInfo, PreflightResult, VerbosityLevel, VerbosityConfig, ContextConfig, FilteringConfig, CompactionConfig, SummarizationConfig, CompactionResult, SummarizationResult, FilteringResult, ContextEvent, ContextEventHandler, ContextStats, FileAccessType, FileAccess, FileAccessTrackerOptions, FormatHintsOptions, FileAccessStats, DelegatedResultStoreStats, ToolResultDelegatorOptions, DelegationConfig, StoredResult, DelegationEvent, } from './context/index.js';
|
|
44
44
|
export { SkillRegistry, defineSkill, createSkillRegistry, builtinSkills, getDefaultSkillRegistry, resetDefaultSkillRegistry, } from './skills/index.js';
|
|
45
45
|
export type { Skill, SkillInvocationResult, SkillInvokeOptions } from './skills/index.js';
|
|
46
46
|
export { JsonSerializer, CompactJsonSerializer, defaultSerializer, MemoryCheckpointer, FileCheckpointer, StateError, StateErrorCode, CURRENT_STATE_VERSION, } from './state/index.js';
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,9 @@ export { readFileTool, createReadFileTool, writeFileTool, createWriteFileTool, b
|
|
|
29
29
|
// Task tool (sub-agent spawning)
|
|
30
30
|
createTaskTool, defaultAgentTypes,
|
|
31
31
|
// Suggest tool (next action suggestions)
|
|
32
|
-
suggestTool, createSuggestTool,
|
|
32
|
+
suggestTool, createSuggestTool,
|
|
33
|
+
// Recall result tool (delegation)
|
|
34
|
+
createRecallResultTool, builtinTools, allBuiltinTools,
|
|
33
35
|
// Tool names - single source of truth
|
|
34
36
|
TOOL_NAMES, TOOL_SETS, } from './tools/index.js';
|
|
35
37
|
// Message utilities
|
|
@@ -37,11 +39,15 @@ export { userMessage, assistantMessage, systemMessage, textBlock, toolUseBlock,
|
|
|
37
39
|
// Utilities
|
|
38
40
|
export { generateId, sleep,
|
|
39
41
|
// eslint-disable-next-line @typescript-eslint/no-deprecated -- Kept for backward compatibility
|
|
40
|
-
retry, truncate, withRetryGenerator, calculateBackoffDelay, DEFAULT_RETRY_CONFIG,
|
|
42
|
+
retry, truncate, withRetryGenerator, calculateBackoffDelay, DEFAULT_RETRY_CONFIG,
|
|
43
|
+
// Token counting (tiktoken-based)
|
|
44
|
+
countTokens, countMessageTokens, } from './utils/index.js';
|
|
41
45
|
// Errors
|
|
42
46
|
export { AgentError, ProviderError, ToolError, ToolTimeoutError, ToolLoopError, ValidationError, MaxIterationsError, AbortError, ContextOverflowError, isAgentError, isProviderError, isToolError, isToolTimeoutError, isToolLoopError, isContextOverflowError, wrapError, } from './errors.js';
|
|
43
47
|
// Context management
|
|
44
|
-
export { ContextManager, DEFAULT_CONTEXT_CONFIG, FileAccessTracker, createFileTrackingHook, TRACKED_TOOLS,
|
|
48
|
+
export { ContextManager, DEFAULT_CONTEXT_CONFIG, FileAccessTracker, createFileTrackingHook, TRACKED_TOOLS,
|
|
49
|
+
// Tool result delegation
|
|
50
|
+
DelegatedResultStore, ToolResultDelegator, DELEGATION_SYSTEM_PROMPT, DEFAULT_DELEGATION_CONFIG, } from './context/index.js';
|
|
45
51
|
// Skills system
|
|
46
52
|
export { SkillRegistry, defineSkill, createSkillRegistry, builtinSkills, getDefaultSkillRegistry, resetDefaultSkillRegistry, } from './skills/index.js';
|
|
47
53
|
// State management
|
package/dist/memory/loader.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import * as fs from 'fs/promises';
|
|
27
27
|
import * as path from 'path';
|
|
28
|
+
import { countTokens } from '../utils/tokenizer.js';
|
|
28
29
|
/**
|
|
29
30
|
* Built-in patterns for various LLM providers
|
|
30
31
|
*/
|
|
@@ -268,7 +269,7 @@ export class ProjectMemoryLoader {
|
|
|
268
269
|
files,
|
|
269
270
|
content,
|
|
270
271
|
rootDir: absoluteRoot,
|
|
271
|
-
estimatedTokens:
|
|
272
|
+
estimatedTokens: countTokens(content),
|
|
272
273
|
};
|
|
273
274
|
this.emit({ type: 'memory:search_complete', memory });
|
|
274
275
|
return memory;
|
package/dist/memory/types.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface ProjectMemory {
|
|
|
41
41
|
content: string;
|
|
42
42
|
/** The root directory where search started */
|
|
43
43
|
rootDir: string;
|
|
44
|
-
/** Total token estimate (
|
|
44
|
+
/** Total token estimate (tiktoken cl100k_base) */
|
|
45
45
|
estimatedTokens: number;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
@@ -61,11 +61,7 @@ export declare class ClaudeProvider implements LLMProvider {
|
|
|
61
61
|
*/
|
|
62
62
|
chat(messages: Message[], options?: ChatOptions): AsyncIterable<StreamChunk>;
|
|
63
63
|
/**
|
|
64
|
-
* Count tokens in messages
|
|
65
|
-
*
|
|
66
|
-
* Note: Token counting API is available via Anthropic's beta endpoints
|
|
67
|
-
* but not yet exposed in the stable SDK. For now, this provides an
|
|
68
|
-
* approximation based on character count.
|
|
64
|
+
* Count tokens in messages using tiktoken (cl100k_base encoding)
|
|
69
65
|
*/
|
|
70
66
|
countTokens(messages: Message[]): Promise<number>;
|
|
71
67
|
/**
|
package/dist/providers/claude.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* ```
|
|
12
12
|
*/
|
|
13
13
|
import Anthropic from '@anthropic-ai/sdk';
|
|
14
|
+
import { countMessageTokens } from '../utils/tokenizer.js';
|
|
14
15
|
import { ProviderError } from '../errors.js';
|
|
15
16
|
/**
|
|
16
17
|
* Default model for Claude API
|
|
@@ -122,36 +123,10 @@ export class ClaudeProvider {
|
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
/**
|
|
125
|
-
* Count tokens in messages
|
|
126
|
-
*
|
|
127
|
-
* Note: Token counting API is available via Anthropic's beta endpoints
|
|
128
|
-
* but not yet exposed in the stable SDK. For now, this provides an
|
|
129
|
-
* approximation based on character count.
|
|
126
|
+
* Count tokens in messages using tiktoken (cl100k_base encoding)
|
|
130
127
|
*/
|
|
131
128
|
countTokens(messages) {
|
|
132
|
-
|
|
133
|
-
let charCount = 0;
|
|
134
|
-
for (const msg of messages) {
|
|
135
|
-
if (typeof msg.content === 'string') {
|
|
136
|
-
charCount += msg.content.length;
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
for (const block of msg.content) {
|
|
140
|
-
switch (block.type) {
|
|
141
|
-
case 'text':
|
|
142
|
-
charCount += block.text.length;
|
|
143
|
-
break;
|
|
144
|
-
case 'tool_use':
|
|
145
|
-
charCount += JSON.stringify(block.input).length;
|
|
146
|
-
break;
|
|
147
|
-
case 'tool_result':
|
|
148
|
-
charCount += block.content.length;
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return Promise.resolve(Math.ceil(charCount / 4));
|
|
129
|
+
return Promise.resolve(countMessageTokens(messages));
|
|
155
130
|
}
|
|
156
131
|
/**
|
|
157
132
|
* Convert our Message format to Anthropic's format
|
|
@@ -52,7 +52,7 @@ export declare class GeminiNativeProvider implements LLMProvider {
|
|
|
52
52
|
*/
|
|
53
53
|
chat(messages: Message[], options?: ChatOptions): AsyncIterable<StreamChunk>;
|
|
54
54
|
/**
|
|
55
|
-
* Count tokens in messages (
|
|
55
|
+
* Count tokens in messages using tiktoken (cl100k_base encoding)
|
|
56
56
|
*/
|
|
57
57
|
countTokens(messages: Message[]): Promise<number>;
|
|
58
58
|
/**
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { GoogleGenAI } from '@google/genai';
|
|
17
17
|
import { ProviderError } from '../errors.js';
|
|
18
|
+
import { countMessageTokens } from '../utils/tokenizer.js';
|
|
18
19
|
/**
|
|
19
20
|
* Default model for Gemini API
|
|
20
21
|
*/
|
|
@@ -137,32 +138,10 @@ export class GeminiNativeProvider {
|
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
/**
|
|
140
|
-
* Count tokens in messages (
|
|
141
|
+
* Count tokens in messages using tiktoken (cl100k_base encoding)
|
|
141
142
|
*/
|
|
142
143
|
countTokens(messages) {
|
|
143
|
-
|
|
144
|
-
let charCount = 0;
|
|
145
|
-
for (const msg of messages) {
|
|
146
|
-
if (typeof msg.content === 'string') {
|
|
147
|
-
charCount += msg.content.length;
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
for (const block of msg.content) {
|
|
151
|
-
switch (block.type) {
|
|
152
|
-
case 'text':
|
|
153
|
-
charCount += block.text.length;
|
|
154
|
-
break;
|
|
155
|
-
case 'tool_use':
|
|
156
|
-
charCount += JSON.stringify(block.input).length;
|
|
157
|
-
break;
|
|
158
|
-
case 'tool_result':
|
|
159
|
-
charCount += block.content.length;
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return Promise.resolve(Math.ceil(charCount / 4));
|
|
144
|
+
return Promise.resolve(countMessageTokens(messages));
|
|
166
145
|
}
|
|
167
146
|
/**
|
|
168
147
|
* Convert our Message format to Google's format
|