@compilr-dev/agents 0.2.1 → 0.3.1

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.
@@ -143,6 +143,10 @@ export interface SubAgentEventInfo {
143
143
  * Type of the sub-agent
144
144
  */
145
145
  agentType: string;
146
+ /**
147
+ * Tool use ID for correlation with parent tool call
148
+ */
149
+ toolUseId?: string;
146
150
  /**
147
151
  * The original event from the sub-agent
148
152
  */
@@ -174,13 +178,19 @@ export interface TaskToolOptions {
174
178
  */
175
179
  defaultTimeout?: number;
176
180
  /**
177
- * Called when a sub-agent is spawned
181
+ * Called when a sub-agent is spawned.
182
+ * @param agentType - The type of agent being spawned
183
+ * @param description - Description of the task
184
+ * @param toolUseId - Tool use ID for correlation (enables parallel tracking)
178
185
  */
179
- onSpawn?: (agentType: string, description: string) => void;
186
+ onSpawn?: (agentType: string, description: string, toolUseId?: string) => void;
180
187
  /**
181
- * Called when a sub-agent completes
188
+ * Called when a sub-agent completes.
189
+ * @param agentType - The type of agent that completed
190
+ * @param result - The task result
191
+ * @param toolUseId - Tool use ID for correlation (enables parallel tracking)
182
192
  */
183
- onComplete?: (agentType: string, result: TaskResult) => void;
193
+ onComplete?: (agentType: string, result: TaskResult, toolUseId?: string) => void;
184
194
  /**
185
195
  * Called when a sub-agent emits an event (for real-time streaming)
186
196
  * Only called if enableEventStreaming is true
@@ -114,7 +114,8 @@ export function createTaskTool(options) {
114
114
  },
115
115
  required: ['description', 'prompt', 'subagent_type'],
116
116
  },
117
- execute: async (input) => {
117
+ execute: async (input, context) => {
118
+ const toolUseId = context?.toolUseId;
118
119
  const { description, prompt, subagent_type, model, context_mode, thoroughness = 'medium', } = input;
119
120
  // Validate agent type exists
120
121
  if (!Object.hasOwn(agentTypes, subagent_type)) {
@@ -124,16 +125,14 @@ export function createTaskTool(options) {
124
125
  const agentConfig = agentTypes[subagent_type];
125
126
  // Check concurrent limit
126
127
  if (activeCount >= maxConcurrent) {
127
- console.error(`[task-tool] BLOCKED: activeCount=${String(activeCount)}, maxConcurrent=${String(maxConcurrent)}`);
128
128
  return createErrorResult(`Maximum concurrent sub-agents (${String(maxConcurrent)}) reached. ` +
129
129
  `Wait for existing tasks to complete.`);
130
130
  }
131
- // Notify spawn
131
+ // Notify spawn (include toolUseId for parallel execution tracking)
132
132
  if (onSpawn) {
133
- onSpawn(subagent_type, description);
133
+ onSpawn(subagent_type, description, toolUseId);
134
134
  }
135
135
  activeCount++;
136
- console.error(`[task-tool] SPAWN: ${subagent_type}, activeCount=${String(activeCount)}`);
137
136
  try {
138
137
  // Note: Sub-agents currently use the parent's provider
139
138
  // Future enhancement: support model switching via providerFactory
@@ -178,6 +177,7 @@ export function createTaskTool(options) {
178
177
  onSubAgentEvent({
179
178
  agentName: subAgentName,
180
179
  agentType: subagent_type,
180
+ toolUseId,
181
181
  event,
182
182
  });
183
183
  };
@@ -194,21 +194,21 @@ export function createTaskTool(options) {
194
194
  iterations: result.iterations,
195
195
  toolCalls: result.toolCalls.length,
196
196
  };
197
- // Notify completion
197
+ // Notify completion (include toolUseId for parallel execution tracking)
198
198
  if (onComplete) {
199
- onComplete(subagent_type, taskResult);
199
+ onComplete(subagent_type, taskResult, toolUseId);
200
200
  }
201
201
  return createSuccessResult(taskResult);
202
202
  }
203
203
  catch (error) {
204
- console.error(`[task-tool] ERROR: ${subagent_type}, error=${error instanceof Error ? error.message : String(error)}`);
205
204
  return createErrorResult(`Sub-agent failed: ${error instanceof Error ? error.message : String(error)}`);
206
205
  }
207
206
  finally {
208
207
  activeCount--;
209
- console.error(`[task-tool] DONE: ${subagent_type}, activeCount=${String(activeCount)}`);
210
208
  }
211
209
  },
210
+ // Task tool can run in parallel since sub-agents are independent
211
+ parallel: true,
212
212
  });
213
213
  }
214
214
  /**
@@ -22,6 +22,13 @@ export interface DefineToolOptions<T extends object> {
22
22
  * Function that executes the tool
23
23
  */
24
24
  execute: (input: T) => Promise<ToolExecutionResult>;
25
+ /**
26
+ * If true, multiple calls to this tool can execute in parallel.
27
+ * When the LLM requests multiple parallel-safe tools in one response,
28
+ * they will be executed concurrently using Promise.all.
29
+ * Default: false (sequential execution)
30
+ */
31
+ parallel?: boolean;
25
32
  }
26
33
  /**
27
34
  * Define a tool with type-safe input handling
@@ -32,6 +32,7 @@ export function defineTool(options) {
32
32
  return {
33
33
  definition,
34
34
  execute: options.execute,
35
+ parallel: options.parallel,
35
36
  };
36
37
  }
37
38
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * ToolRegistry - Manages available tools for an agent
3
3
  */
4
- import type { Tool, ToolDefinition, ToolRegistry as IToolRegistry, ToolExecutionResult } from './types.js';
4
+ import type { Tool, ToolDefinition, ToolRegistry as IToolRegistry, ToolExecutionResult, ToolExecutionContext } from './types.js';
5
5
  /**
6
6
  * Options for creating a DefaultToolRegistry
7
7
  */
@@ -61,9 +61,10 @@ export declare class DefaultToolRegistry implements IToolRegistry {
61
61
  *
62
62
  * @param name - Tool name
63
63
  * @param input - Tool input parameters
64
+ * @param contextOrTimeout - Optional execution context or timeout override
64
65
  * @param timeoutMs - Optional timeout override (uses default if not provided)
65
66
  */
66
- execute(name: string, input: Record<string, unknown>, timeoutMs?: number): Promise<ToolExecutionResult>;
67
+ execute(name: string, input: Record<string, unknown>, contextOrTimeout?: ToolExecutionContext | number, timeoutMs?: number): Promise<ToolExecutionResult>;
67
68
  /**
68
69
  * Execute a tool with a timeout
69
70
  */
@@ -75,9 +75,10 @@ export class DefaultToolRegistry {
75
75
  *
76
76
  * @param name - Tool name
77
77
  * @param input - Tool input parameters
78
+ * @param contextOrTimeout - Optional execution context or timeout override
78
79
  * @param timeoutMs - Optional timeout override (uses default if not provided)
79
80
  */
80
- async execute(name, input, timeoutMs) {
81
+ async execute(name, input, contextOrTimeout, timeoutMs) {
81
82
  const tool = this.tools.get(name);
82
83
  if (!tool) {
83
84
  return {
@@ -85,15 +86,27 @@ export class DefaultToolRegistry {
85
86
  error: `Tool not found: ${name}`,
86
87
  };
87
88
  }
89
+ // Handle backwards compatibility: contextOrTimeout can be number (old API) or context (new API)
90
+ let context;
91
+ let explicitTimeout;
92
+ if (typeof contextOrTimeout === 'number') {
93
+ // Old API: execute(name, input, timeoutMs)
94
+ explicitTimeout = contextOrTimeout;
95
+ }
96
+ else {
97
+ // New API: execute(name, input, context?, timeoutMs?)
98
+ context = contextOrTimeout;
99
+ explicitTimeout = timeoutMs;
100
+ }
88
101
  // Determine timeout: explicit param > per-tool config > default
89
102
  const perToolTimeout = Object.hasOwn(this.toolTimeouts, name)
90
103
  ? this.toolTimeouts[name]
91
104
  : undefined;
92
- const timeout = timeoutMs ?? perToolTimeout ?? this.defaultTimeoutMs;
105
+ const timeout = explicitTimeout ?? perToolTimeout ?? this.defaultTimeoutMs;
93
106
  // If timeout is 0 or negative, execute without timeout
94
107
  if (timeout <= 0) {
95
108
  try {
96
- return await tool.execute(input);
109
+ return await tool.execute(input, context);
97
110
  }
98
111
  catch (error) {
99
112
  return {
@@ -104,7 +117,7 @@ export class DefaultToolRegistry {
104
117
  }
105
118
  // Execute with timeout
106
119
  try {
107
- return await this.executeWithTimeout(tool, name, input, timeout);
120
+ return await this.executeWithTimeout(tool, name, input, timeout, context);
108
121
  }
109
122
  catch (error) {
110
123
  if (error instanceof ToolTimeoutError) {
@@ -122,7 +135,7 @@ export class DefaultToolRegistry {
122
135
  /**
123
136
  * Execute a tool with a timeout
124
137
  */
125
- async executeWithTimeout(tool, name, input, timeoutMs) {
138
+ async executeWithTimeout(tool, name, input, timeoutMs, context) {
126
139
  // Create a promise that rejects after the timeout
127
140
  const timeoutPromise = new Promise((_, reject) => {
128
141
  setTimeout(() => {
@@ -130,7 +143,7 @@ export class DefaultToolRegistry {
130
143
  }, timeoutMs);
131
144
  });
132
145
  // Race the tool execution against the timeout
133
- return Promise.race([tool.execute(input), timeoutPromise]);
146
+ return Promise.race([tool.execute(input, context), timeoutPromise]);
134
147
  }
135
148
  /**
136
149
  * Clear all registered tools
@@ -25,16 +25,40 @@ export interface ToolExecutionResult {
25
25
  result?: unknown;
26
26
  error?: string;
27
27
  }
28
+ /**
29
+ * Context passed to tool execution for streaming output
30
+ */
31
+ export interface ToolExecutionContext {
32
+ /**
33
+ * Callback for streaming output (e.g., bash stdout/stderr)
34
+ * @param output - The output text
35
+ * @param stream - Which stream the output came from
36
+ */
37
+ onOutput?: (output: string, stream?: 'stdout' | 'stderr') => void;
38
+ /**
39
+ * Tool use ID for correlation with events
40
+ */
41
+ toolUseId?: string;
42
+ }
28
43
  /**
29
44
  * Tool handler function type
45
+ * @param input - The tool input parameters
46
+ * @param context - Optional execution context for streaming
30
47
  */
31
- export type ToolHandler<T = object> = (input: T) => Promise<ToolExecutionResult>;
48
+ export type ToolHandler<T = object> = (input: T, context?: ToolExecutionContext) => Promise<ToolExecutionResult>;
32
49
  /**
33
50
  * Tool implementation - combines definition with handler
34
51
  */
35
52
  export interface Tool<T = object> {
36
53
  definition: ToolDefinition;
37
54
  execute: ToolHandler<T>;
55
+ /**
56
+ * If true, multiple calls to this tool can execute in parallel.
57
+ * When the LLM requests multiple parallel-safe tools in one response,
58
+ * they will be executed concurrently using Promise.all.
59
+ * Default: false (sequential execution)
60
+ */
61
+ parallel?: boolean;
38
62
  }
39
63
  /**
40
64
  * Tool registry for managing available tools
@@ -54,6 +78,9 @@ export interface ToolRegistry {
54
78
  getDefinitions(): ToolDefinition[];
55
79
  /**
56
80
  * Execute a tool by name with given input
81
+ * @param name - Tool name
82
+ * @param input - Tool input parameters
83
+ * @param context - Optional execution context for streaming
57
84
  */
58
- execute(name: string, input: Record<string, unknown>): Promise<ToolExecutionResult>;
85
+ execute(name: string, input: Record<string, unknown>, context?: ToolExecutionContext): Promise<ToolExecutionResult>;
59
86
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/agents",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "Lightweight multi-LLM agent library for building CLI AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",