@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.
Files changed (38) hide show
  1. package/README.md +84 -41
  2. package/dist/agent.d.ts +23 -0
  3. package/dist/agent.js +41 -7
  4. package/dist/anchors/manager.js +3 -2
  5. package/dist/context/delegated-result-store.d.ts +67 -0
  6. package/dist/context/delegated-result-store.js +99 -0
  7. package/dist/context/delegation-types.d.ts +82 -0
  8. package/dist/context/delegation-types.js +18 -0
  9. package/dist/context/index.d.ts +6 -0
  10. package/dist/context/index.js +4 -0
  11. package/dist/context/manager.js +12 -32
  12. package/dist/context/tool-result-delegator.d.ts +63 -0
  13. package/dist/context/tool-result-delegator.js +305 -0
  14. package/dist/index.d.ts +5 -5
  15. package/dist/index.js +9 -3
  16. package/dist/memory/loader.js +2 -1
  17. package/dist/memory/types.d.ts +1 -1
  18. package/dist/providers/claude.d.ts +1 -5
  19. package/dist/providers/claude.js +3 -28
  20. package/dist/providers/gemini-native.d.ts +1 -1
  21. package/dist/providers/gemini-native.js +3 -24
  22. package/dist/providers/mock.d.ts +1 -1
  23. package/dist/providers/mock.js +3 -24
  24. package/dist/providers/openai-compatible.d.ts +1 -5
  25. package/dist/providers/openai-compatible.js +3 -28
  26. package/dist/rate-limit/provider-wrapper.d.ts +1 -1
  27. package/dist/rate-limit/provider-wrapper.js +3 -27
  28. package/dist/tools/builtin/index.d.ts +2 -0
  29. package/dist/tools/builtin/index.js +2 -0
  30. package/dist/tools/builtin/recall-result.d.ts +29 -0
  31. package/dist/tools/builtin/recall-result.js +48 -0
  32. package/dist/tools/index.d.ts +2 -2
  33. package/dist/tools/index.js +2 -0
  34. package/dist/utils/index.d.ts +1 -0
  35. package/dist/utils/index.js +2 -0
  36. package/dist/utils/tokenizer.d.ts +18 -0
  37. package/dist/utils/tokenizer.js +56 -0
  38. package/package.json +8 -7
@@ -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;
@@ -349,35 +350,9 @@ export class OpenAICompatibleProvider {
349
350
  return results;
350
351
  }
351
352
  /**
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.
353
+ * Count tokens in messages using tiktoken (cl100k_base encoding)
357
354
  */
358
355
  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));
356
+ return Promise.resolve(countMessageTokens(messages));
382
357
  }
383
358
  }
@@ -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
+ }
@@ -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,18 @@
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
+ */
11
+ export declare function countTokens(text: string): number;
12
+ /**
13
+ * Count tokens across an array of messages
14
+ *
15
+ * Extracts all text content from messages (text blocks, tool inputs/results,
16
+ * thinking blocks) and counts tokens using tiktoken.
17
+ */
18
+ export declare function countMessageTokens(messages: Message[]): number;
@@ -0,0 +1,56 @@
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 { getEncoding } from 'js-tiktoken';
8
+ // Lazy-initialized encoder (loaded on first use)
9
+ let encoder = null;
10
+ function getEncoder() {
11
+ if (!encoder) {
12
+ encoder = getEncoding('cl100k_base');
13
+ }
14
+ return encoder;
15
+ }
16
+ /**
17
+ * Count tokens in a text string using tiktoken
18
+ */
19
+ export function countTokens(text) {
20
+ if (!text)
21
+ return 0;
22
+ return getEncoder().encode(text).length;
23
+ }
24
+ /**
25
+ * Count tokens across an array of messages
26
+ *
27
+ * Extracts all text content from messages (text blocks, tool inputs/results,
28
+ * thinking blocks) and counts tokens using tiktoken.
29
+ */
30
+ export function countMessageTokens(messages) {
31
+ const parts = [];
32
+ for (const msg of messages) {
33
+ if (typeof msg.content === 'string') {
34
+ parts.push(msg.content);
35
+ }
36
+ else {
37
+ for (const block of msg.content) {
38
+ switch (block.type) {
39
+ case 'text':
40
+ parts.push(block.text);
41
+ break;
42
+ case 'tool_use':
43
+ parts.push(JSON.stringify(block.input));
44
+ break;
45
+ case 'tool_result':
46
+ parts.push(typeof block.content === 'string' ? block.content : JSON.stringify(block.content));
47
+ break;
48
+ case 'thinking':
49
+ parts.push(block.thinking);
50
+ break;
51
+ }
52
+ }
53
+ }
54
+ }
55
+ return countTokens(parts.join(' '));
56
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/agents",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "description": "Lightweight multi-LLM agent library for building CLI AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -52,7 +52,7 @@
52
52
  "node": ">=18.0.0"
53
53
  },
54
54
  "peerDependencies": {
55
- "@anthropic-ai/sdk": "^0.72.1",
55
+ "@anthropic-ai/sdk": ">=0.72.1",
56
56
  "@modelcontextprotocol/sdk": "^1.23.0"
57
57
  },
58
58
  "peerDependenciesMeta": {
@@ -64,20 +64,21 @@
64
64
  }
65
65
  },
66
66
  "devDependencies": {
67
- "@anthropic-ai/sdk": "^0.72.1",
67
+ "@anthropic-ai/sdk": "^0.74.0",
68
68
  "@eslint/js": "^9.39.1",
69
69
  "@modelcontextprotocol/sdk": "^1.23.0",
70
- "@types/node": "^24.10.1",
71
- "@vitest/coverage-v8": "^3.2.4",
70
+ "@types/node": "^25.2.3",
71
+ "@vitest/coverage-v8": "^4.0.18",
72
72
  "dotenv": "^17.2.3",
73
73
  "eslint": "^9.39.1",
74
74
  "prettier": "^3.7.1",
75
75
  "typescript": "^5.3.0",
76
76
  "typescript-eslint": "^8.48.0",
77
- "vitest": "^3.2.4"
77
+ "vitest": "^4.0.18"
78
78
  },
79
79
  "dependencies": {
80
- "@google/genai": "^1.38.0"
80
+ "@google/genai": "^1.38.0",
81
+ "js-tiktoken": "^1.0.21"
81
82
  },
82
83
  "overrides": {
83
84
  "hono": "^4.11.7"