@compilr-dev/agents 0.3.11 → 0.3.13

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.
Files changed (42) hide show
  1. package/dist/agent.d.ts +42 -1
  2. package/dist/agent.js +116 -17
  3. package/dist/anchors/manager.js +3 -2
  4. package/dist/context/delegated-result-store.d.ts +67 -0
  5. package/dist/context/delegated-result-store.js +99 -0
  6. package/dist/context/delegation-types.d.ts +82 -0
  7. package/dist/context/delegation-types.js +18 -0
  8. package/dist/context/file-tracker.d.ts +59 -1
  9. package/dist/context/file-tracker.js +96 -1
  10. package/dist/context/file-tracking-hook.js +9 -4
  11. package/dist/context/index.d.ts +7 -1
  12. package/dist/context/index.js +4 -0
  13. package/dist/context/manager.js +12 -32
  14. package/dist/context/tool-result-delegator.d.ts +63 -0
  15. package/dist/context/tool-result-delegator.js +314 -0
  16. package/dist/index.d.ts +5 -5
  17. package/dist/index.js +9 -3
  18. package/dist/memory/loader.js +2 -1
  19. package/dist/memory/types.d.ts +1 -1
  20. package/dist/providers/claude.d.ts +1 -5
  21. package/dist/providers/claude.js +6 -29
  22. package/dist/providers/gemini-native.d.ts +1 -1
  23. package/dist/providers/gemini-native.js +10 -24
  24. package/dist/providers/mock.d.ts +1 -1
  25. package/dist/providers/mock.js +3 -24
  26. package/dist/providers/openai-compatible.d.ts +1 -5
  27. package/dist/providers/openai-compatible.js +14 -28
  28. package/dist/providers/types.d.ts +5 -0
  29. package/dist/rate-limit/provider-wrapper.d.ts +1 -1
  30. package/dist/rate-limit/provider-wrapper.js +3 -27
  31. package/dist/tools/builtin/index.d.ts +2 -0
  32. package/dist/tools/builtin/index.js +2 -0
  33. package/dist/tools/builtin/recall-result.d.ts +29 -0
  34. package/dist/tools/builtin/recall-result.js +48 -0
  35. package/dist/tools/builtin/task.js +1 -0
  36. package/dist/tools/index.d.ts +2 -2
  37. package/dist/tools/index.js +2 -0
  38. package/dist/utils/index.d.ts +1 -0
  39. package/dist/utils/index.js +2 -0
  40. package/dist/utils/tokenizer.d.ts +19 -0
  41. package/dist/utils/tokenizer.js +93 -0
  42. package/package.json +3 -2
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, RestorationHintMessage, 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, builtinTools, allBuiltinTools,
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, } from './utils/index.js';
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, } from './context/index.js';
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
@@ -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: Math.ceil(content.length / 4),
272
+ estimatedTokens: countTokens(content),
272
273
  };
273
274
  this.emit({ type: 'memory:search_complete', memory });
274
275
  return memory;
@@ -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 (rough: chars / 4) */
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 (not yet available in SDK v0.30)
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
  /**
@@ -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
@@ -73,7 +74,9 @@ export class ClaudeProvider {
73
74
  if (thinking) {
74
75
  Object.assign(params, { thinking });
75
76
  }
76
- const stream = this.client.messages.stream(params);
77
+ // Pass abort signal to SDK for immediate cancellation
78
+ const requestOptions = options?.signal ? { signal: options.signal } : undefined;
79
+ const stream = this.client.messages.stream(params, requestOptions);
77
80
  const model = options?.model ?? this.defaultModel;
78
81
  let currentToolId = '';
79
82
  let currentToolName = '';
@@ -122,36 +125,10 @@ export class ClaudeProvider {
122
125
  }
123
126
  }
124
127
  /**
125
- * Count tokens in messages (not yet available in SDK v0.30)
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.
128
+ * Count tokens in messages using tiktoken (cl100k_base encoding)
130
129
  */
131
130
  countTokens(messages) {
132
- // Approximation: ~4 characters per token (rough estimate)
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));
131
+ return Promise.resolve(countMessageTokens(messages));
155
132
  }
156
133
  /**
157
134
  * 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 (approximation)
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
  */
@@ -66,6 +67,10 @@ export class GeminiNativeProvider {
66
67
  if (tools.length > 0) {
67
68
  config.tools = [{ functionDeclarations: tools }];
68
69
  }
70
+ // Check abort before starting the request
71
+ if (options?.signal?.aborted) {
72
+ throw new Error('Aborted');
73
+ }
69
74
  // Request streaming response
70
75
  const streamResponse = await this.client.models.generateContentStream({
71
76
  model,
@@ -81,6 +86,9 @@ export class GeminiNativeProvider {
81
86
  let lastUsageMetadata;
82
87
  // Process stream chunks
83
88
  for await (const chunk of streamResponse) {
89
+ // Check abort between chunks
90
+ if (options?.signal?.aborted)
91
+ break;
84
92
  const streamChunks = this.processChunk(chunk, currentToolId, currentToolName, toolInputJson, inThinkingBlock);
85
93
  for (const streamChunk of streamChunks) {
86
94
  // Update tracking state
@@ -137,32 +145,10 @@ export class GeminiNativeProvider {
137
145
  }
138
146
  }
139
147
  /**
140
- * Count tokens in messages (approximation)
148
+ * Count tokens in messages using tiktoken (cl100k_base encoding)
141
149
  */
142
150
  countTokens(messages) {
143
- // Approximation: ~4 characters per token
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));
151
+ return Promise.resolve(countMessageTokens(messages));
166
152
  }
167
153
  /**
168
154
  * Convert our Message format to Google's format
@@ -122,7 +122,7 @@ export declare class MockProvider implements LLMProvider {
122
122
  */
123
123
  chat(messages: Message[], options?: ChatOptions): AsyncIterable<StreamChunk>;
124
124
  /**
125
- * Count tokens (mock implementation)
125
+ * Count tokens using tiktoken (cl100k_base encoding)
126
126
  */
127
127
  countTokens(messages: Message[]): Promise<number>;
128
128
  private sleep;
@@ -25,6 +25,7 @@
25
25
  * ```
26
26
  */
27
27
  import { ProviderError } from '../errors.js';
28
+ import { countMessageTokens } from '../utils/tokenizer.js';
28
29
  /**
29
30
  * MockProvider for testing agents without API calls.
30
31
  *
@@ -171,32 +172,10 @@ export class MockProvider {
171
172
  yield { type: 'done' };
172
173
  }
173
174
  /**
174
- * Count tokens (mock implementation)
175
+ * Count tokens using tiktoken (cl100k_base encoding)
175
176
  */
176
177
  countTokens(messages) {
177
- // Simple approximation: ~4 chars per token
178
- let charCount = 0;
179
- for (const msg of messages) {
180
- if (typeof msg.content === 'string') {
181
- charCount += msg.content.length;
182
- }
183
- else {
184
- for (const block of msg.content) {
185
- if (block.type === 'text') {
186
- charCount += block.text.length;
187
- }
188
- else if (block.type === 'tool_result') {
189
- // Count tool result content (always a string per our type definition)
190
- charCount += block.content.length;
191
- }
192
- else if (block.type === 'tool_use') {
193
- // Count tool use input
194
- charCount += JSON.stringify(block.input).length;
195
- }
196
- }
197
- }
198
- }
199
- return Promise.resolve(Math.ceil(charCount / 4));
178
+ return Promise.resolve(countMessageTokens(messages));
200
179
  }
201
180
  sleep(ms) {
202
181
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -186,11 +186,7 @@ export declare abstract class OpenAICompatibleProvider implements LLMProvider {
186
186
  arguments: string;
187
187
  }>): StreamChunk[];
188
188
  /**
189
- * Estimate token count (rough approximation)
190
- *
191
- * @remarks
192
- * Most providers don't have a native token counting endpoint.
193
- * This uses a rough approximation of ~4 characters per token.
189
+ * Count tokens in messages using tiktoken (cl100k_base encoding)
194
190
  */
195
191
  countTokens(messages: Message[]): Promise<number>;
196
192
  }
@@ -22,6 +22,7 @@
22
22
  * ```
23
23
  */
24
24
  import { ProviderError } from '../errors.js';
25
+ import { countMessageTokens } from '../utils/tokenizer.js';
25
26
  // Default configuration
26
27
  const DEFAULT_MAX_TOKENS = 4096;
27
28
  const DEFAULT_TIMEOUT = 120000;
@@ -98,6 +99,17 @@ export class OpenAICompatibleProvider {
98
99
  let usage;
99
100
  try {
100
101
  const controller = new AbortController();
102
+ // Chain user abort signal to our controller
103
+ if (options?.signal) {
104
+ if (options.signal.aborted) {
105
+ controller.abort();
106
+ }
107
+ else {
108
+ options.signal.addEventListener('abort', () => {
109
+ controller.abort();
110
+ }, { once: true });
111
+ }
112
+ }
101
113
  const timeoutId = setTimeout(() => {
102
114
  controller.abort();
103
115
  }, this.timeout);
@@ -349,35 +361,9 @@ export class OpenAICompatibleProvider {
349
361
  return results;
350
362
  }
351
363
  /**
352
- * Estimate token count (rough approximation)
353
- *
354
- * @remarks
355
- * Most providers don't have a native token counting endpoint.
356
- * This uses a rough approximation of ~4 characters per token.
364
+ * Count tokens in messages using tiktoken (cl100k_base encoding)
357
365
  */
358
366
  countTokens(messages) {
359
- let charCount = 0;
360
- for (const msg of messages) {
361
- if (typeof msg.content === 'string') {
362
- charCount += msg.content.length;
363
- }
364
- else if (Array.isArray(msg.content)) {
365
- for (const block of msg.content) {
366
- if (block.type === 'text') {
367
- charCount += block.text.length;
368
- }
369
- else if (block.type === 'tool_use') {
370
- charCount += JSON.stringify(block.input).length;
371
- }
372
- else if (block.type === 'tool_result') {
373
- charCount +=
374
- typeof block.content === 'string'
375
- ? block.content.length
376
- : JSON.stringify(block.content).length;
377
- }
378
- }
379
- }
380
- }
381
- return Promise.resolve(Math.ceil(charCount / 4));
367
+ return Promise.resolve(countMessageTokens(messages));
382
368
  }
383
369
  }
@@ -147,6 +147,11 @@ export interface ChatOptions {
147
147
  * ```
148
148
  */
149
149
  thinking?: ThinkingConfig;
150
+ /**
151
+ * AbortSignal for cancelling the LLM request.
152
+ * When aborted, the provider should stop streaming and throw/return immediately.
153
+ */
154
+ signal?: AbortSignal;
150
155
  /**
151
156
  * Enable prompt caching for system prompt and tools (Claude-specific)
152
157
  *
@@ -45,7 +45,7 @@ export declare class RateLimitedProvider implements LLMProvider {
45
45
  */
46
46
  countTokens(messages: Message[]): Promise<number>;
47
47
  /**
48
- * Estimate tokens from messages (rough approximation)
48
+ * Estimate tokens from messages using tiktoken
49
49
  */
50
50
  private estimateTokens;
51
51
  }
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { createRateLimiter } from './limiter.js';
7
7
  import { withRetry } from './retry.js';
8
+ import { countMessageTokens } from '../utils/tokenizer.js';
8
9
  /**
9
10
  * Wrapper that adds rate limiting and retry to any LLMProvider
10
11
  *
@@ -105,35 +106,10 @@ export class RateLimitedProvider {
105
106
  return withRetry(() => countTokensFn(messages), this.retryConfig);
106
107
  }
107
108
  /**
108
- * Estimate tokens from messages (rough approximation)
109
+ * Estimate tokens from messages using tiktoken
109
110
  */
110
111
  estimateTokens(messages) {
111
- let charCount = 0;
112
- for (const msg of messages) {
113
- if (typeof msg.content === 'string') {
114
- charCount += msg.content.length;
115
- }
116
- else {
117
- for (const block of msg.content) {
118
- switch (block.type) {
119
- case 'text':
120
- charCount += block.text.length;
121
- break;
122
- case 'tool_use':
123
- charCount += JSON.stringify(block.input).length;
124
- break;
125
- case 'tool_result':
126
- charCount += block.content.length;
127
- break;
128
- case 'thinking':
129
- charCount += block.thinking.length;
130
- break;
131
- }
132
- }
133
- }
134
- }
135
- // Rough estimate: 4 characters per token
136
- return Math.ceil(charCount / 4);
112
+ return countMessageTokens(messages);
137
113
  }
138
114
  }
139
115
  /**
@@ -33,6 +33,8 @@ export { askUserTool, createAskUserTool } from './ask-user.js';
33
33
  export type { AskUserInput, AskUserResult, AskUserQuestion, AskUserOption, AskUserToolOptions, } from './ask-user.js';
34
34
  export { askUserSimpleTool, createAskUserSimpleTool } from './ask-user-simple.js';
35
35
  export type { AskUserSimpleInput, AskUserSimpleResult, AskUserSimpleToolOptions, } from './ask-user-simple.js';
36
+ export { createRecallResultTool } from './recall-result.js';
37
+ export type { RecallResultInput, RecallResultToolOptions } from './recall-result.js';
36
38
  export { backlogReadTool, backlogWriteTool, createBacklogTools } from './backlog.js';
37
39
  export type { BacklogItem, BacklogStatus, BacklogItemType, BacklogPriority, BacklogReadInput, BacklogReadResult, BacklogWriteInput, BacklogWriteResult, BacklogToolOptions, } from './backlog.js';
38
40
  export declare const builtinTools: {
@@ -32,6 +32,8 @@ export { suggestTool, createSuggestTool } from './suggest.js';
32
32
  // Ask user tools (user interaction)
33
33
  export { askUserTool, createAskUserTool } from './ask-user.js';
34
34
  export { askUserSimpleTool, createAskUserSimpleTool } from './ask-user-simple.js';
35
+ // Recall result tool (tool result delegation)
36
+ export { createRecallResultTool } from './recall-result.js';
35
37
  // Backlog tools (file-based project backlog)
36
38
  export { backlogReadTool, backlogWriteTool, createBacklogTools } from './backlog.js';
37
39
  /**
@@ -0,0 +1,29 @@
1
+ /**
2
+ * recall_full_result tool
3
+ *
4
+ * Retrieves the full output of a previously delegated tool result.
5
+ * Use when a summary is insufficient and you need the complete data.
6
+ */
7
+ import type { Tool } from '../types.js';
8
+ import type { DelegatedResultStore } from '../../context/delegated-result-store.js';
9
+ import type { DelegationEvent } from '../../context/delegation-types.js';
10
+ /**
11
+ * Input for the recall_full_result tool.
12
+ */
13
+ export interface RecallResultInput {
14
+ /** Delegation ID from a previously delegated result (e.g., "dr_1707900000_0") */
15
+ id: string;
16
+ }
17
+ /**
18
+ * Options for creating the recall_full_result tool.
19
+ */
20
+ export interface RecallResultToolOptions {
21
+ /** The delegation store to retrieve results from */
22
+ store: DelegatedResultStore;
23
+ /** Optional event callback for recall events */
24
+ onEvent?: (event: DelegationEvent) => void;
25
+ }
26
+ /**
27
+ * Create a recall_full_result tool that retrieves delegated results from the store.
28
+ */
29
+ export declare function createRecallResultTool(options: RecallResultToolOptions): Tool<RecallResultInput>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * recall_full_result tool
3
+ *
4
+ * Retrieves the full output of a previously delegated tool result.
5
+ * Use when a summary is insufficient and you need the complete data.
6
+ */
7
+ import { defineTool, createSuccessResult, createErrorResult } from '../define.js';
8
+ /**
9
+ * Create a recall_full_result tool that retrieves delegated results from the store.
10
+ */
11
+ export function createRecallResultTool(options) {
12
+ const { store, onEvent } = options;
13
+ return defineTool({
14
+ name: 'recall_full_result',
15
+ description: 'Retrieve the full output of a previously delegated tool result. ' +
16
+ 'Use when a summary is insufficient and you need the complete data. ' +
17
+ 'Results expire after 10 minutes.',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ id: {
22
+ type: 'string',
23
+ description: 'Delegation ID (e.g., "dr_1707900000_0")',
24
+ },
25
+ },
26
+ required: ['id'],
27
+ },
28
+ execute: (input) => {
29
+ const stored = store.get(input.id);
30
+ onEvent?.({
31
+ type: 'delegation:recall',
32
+ delegationId: input.id,
33
+ found: stored !== undefined,
34
+ });
35
+ if (!stored) {
36
+ return Promise.resolve(createErrorResult('Delegation ID not found or expired. Re-run the original tool to get the result.'));
37
+ }
38
+ return Promise.resolve(createSuccessResult({
39
+ delegationId: stored.id,
40
+ toolName: stored.toolName,
41
+ fullContent: stored.fullContent,
42
+ fullTokens: stored.fullTokens,
43
+ }));
44
+ },
45
+ parallel: true,
46
+ silent: true,
47
+ });
48
+ }
@@ -183,6 +183,7 @@ export function createTaskTool(options) {
183
183
  };
184
184
  }
185
185
  const resultPromise = parentAgent.runSubAgent(subAgentName, prompt, {
186
+ signal: context?.abortSignal,
186
187
  onEvent: eventHandler,
187
188
  });
188
189
  const result = await Promise.race([resultPromise, timeoutPromise]);
@@ -6,5 +6,5 @@ export { defineTool, createSuccessResult, createErrorResult, wrapToolExecute } f
6
6
  export type { DefineToolOptions } from './define.js';
7
7
  export { DefaultToolRegistry, createToolRegistry } from './registry.js';
8
8
  export type { ToolRegistryOptions } from './registry.js';
9
- 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 './builtin/index.js';
10
- export type { ReadFileInput, WriteFileInput, BashInput, BashResult, FifoDetectionResult, BashOutputInput, BashOutputResult, KillShellInput, KillShellResult, ShellStatus, BackgroundShell, ShellOutput, ShellManagerOptions, GrepInput, GlobInput, EditInput, TodoWriteInput, TodoReadInput, TodoItem, TodoStatus, TodoContextCleanupOptions, WebFetchInput, WebFetchResult, WebFetchOptions, TaskInput, TaskResult, AgentTypeConfig, TaskToolOptions, ContextMode, ThoroughnessLevel, SubAgentEventInfo, SuggestInput, SuggestToolOptions, } from './builtin/index.js';
9
+ 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 './builtin/index.js';
10
+ export type { ReadFileInput, WriteFileInput, BashInput, BashResult, FifoDetectionResult, BashOutputInput, BashOutputResult, KillShellInput, KillShellResult, ShellStatus, BackgroundShell, ShellOutput, ShellManagerOptions, GrepInput, GlobInput, EditInput, TodoWriteInput, TodoReadInput, TodoItem, TodoStatus, TodoContextCleanupOptions, WebFetchInput, WebFetchResult, WebFetchOptions, TaskInput, TaskResult, AgentTypeConfig, TaskToolOptions, ContextMode, ThoroughnessLevel, SubAgentEventInfo, SuggestInput, SuggestToolOptions, RecallResultInput, RecallResultToolOptions, } from './builtin/index.js';
@@ -35,6 +35,8 @@ webFetchTool, createWebFetchTool,
35
35
  createTaskTool, defaultAgentTypes,
36
36
  // Suggest (next action suggestions)
37
37
  suggestTool, createSuggestTool,
38
+ // Recall result (delegation)
39
+ createRecallResultTool,
38
40
  // Collections
39
41
  builtinTools, allBuiltinTools,
40
42
  // Tool names - single source of truth
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Utility functions for @compilr-dev/agents
3
3
  */
4
+ export { countTokens, countMessageTokens } from './tokenizer.js';
4
5
  /**
5
6
  * Generate a unique ID for tool uses
6
7
  */
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Utility functions for @compilr-dev/agents
3
3
  */
4
+ // Token counting
5
+ export { countTokens, countMessageTokens } from './tokenizer.js';
4
6
  /**
5
7
  * Generate a unique ID for tool uses
6
8
  */
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Token counting utility using tiktoken (cl100k_base encoding)
3
+ *
4
+ * Replaces the rough `Math.ceil(charCount / 4)` heuristic with actual
5
+ * BPE tokenization for accurate context window management.
6
+ */
7
+ import type { Message } from '../providers/types.js';
8
+ /**
9
+ * Count tokens in a text string using tiktoken.
10
+ * Falls back to chars/4 heuristic for very large or pathologically repetitive strings.
11
+ */
12
+ export declare function countTokens(text: string): number;
13
+ /**
14
+ * Count tokens across an array of messages
15
+ *
16
+ * Extracts all text content from messages (text blocks, tool inputs/results,
17
+ * thinking blocks) and counts tokens using tiktoken.
18
+ */
19
+ export declare function countMessageTokens(messages: Message[]): number;