zeitlich 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -180
- package/dist/index.cjs +1314 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +128 -0
- package/dist/index.d.ts +51 -75
- package/dist/index.js +741 -1091
- package/dist/index.js.map +1 -1
- package/dist/workflow-uVNF7zoe.d.cts +941 -0
- package/dist/workflow-uVNF7zoe.d.ts +941 -0
- package/dist/workflow.cjs +914 -0
- package/dist/workflow.cjs.map +1 -0
- package/dist/workflow.d.cts +5 -0
- package/dist/workflow.d.ts +2 -1
- package/dist/workflow.js +543 -423
- package/dist/workflow.js.map +1 -1
- package/package.json +19 -17
- package/src/activities.ts +112 -0
- package/src/index.ts +49 -0
- package/src/lib/fs.ts +80 -0
- package/src/lib/model-invoker.ts +75 -0
- package/src/lib/session.ts +216 -0
- package/src/lib/state-manager.ts +268 -0
- package/src/lib/thread-manager.ts +169 -0
- package/src/lib/tool-router.ts +717 -0
- package/src/lib/types.ts +354 -0
- package/src/plugin.ts +28 -0
- package/src/tools/ask-user-question/handler.ts +25 -0
- package/src/tools/ask-user-question/tool.ts +46 -0
- package/src/tools/bash/bash.test.ts +104 -0
- package/src/tools/bash/handler.ts +36 -0
- package/src/tools/bash/tool.ts +20 -0
- package/src/tools/edit/handler.ts +156 -0
- package/src/tools/edit/tool.ts +39 -0
- package/src/tools/glob/handler.ts +62 -0
- package/src/tools/glob/tool.ts +27 -0
- package/src/tools/grep/tool.ts +45 -0
- package/src/tools/read/tool.ts +33 -0
- package/src/tools/task/handler.ts +75 -0
- package/src/tools/task/tool.ts +96 -0
- package/src/tools/task-create/handler.ts +49 -0
- package/src/tools/task-create/tool.ts +66 -0
- package/src/tools/task-get/handler.ts +38 -0
- package/src/tools/task-get/tool.ts +11 -0
- package/src/tools/task-list/handler.ts +33 -0
- package/src/tools/task-list/tool.ts +9 -0
- package/src/tools/task-update/handler.ts +79 -0
- package/src/tools/task-update/tool.ts +20 -0
- package/src/tools/write/tool.ts +26 -0
- package/src/workflow.ts +138 -0
- package/tsup.config.ts +20 -0
- package/dist/index.d.mts +0 -152
- package/dist/index.mjs +0 -1587
- package/dist/index.mjs.map +0 -1
- package/dist/workflow-7_MT-5-w.d.mts +0 -1203
- package/dist/workflow-7_MT-5-w.d.ts +0 -1203
- package/dist/workflow.d.mts +0 -4
- package/dist/workflow.mjs +0 -739
- package/dist/workflow.mjs.map +0 -1
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
import type { MessageToolDefinition } from "@langchain/core/messages";
|
|
2
|
+
import type { ToolMessageContent } from "./thread-manager";
|
|
3
|
+
import type { Hooks, SubagentConfig, ToolResultConfig } from "./types";
|
|
4
|
+
|
|
5
|
+
import type { z } from "zod";
|
|
6
|
+
import { proxyActivities } from "@temporalio/workflow";
|
|
7
|
+
import type { ZeitlichSharedActivities } from "../activities";
|
|
8
|
+
import { createTaskTool } from "../tools/task/tool";
|
|
9
|
+
import { createTaskHandler } from "../tools/task/handler";
|
|
10
|
+
import type { createTaskCreateHandler } from "../tools/task-create/handler";
|
|
11
|
+
import { bashTool, createBashToolDescription } from "../tools/bash/tool";
|
|
12
|
+
import { taskCreateTool } from "../tools/task-create/tool";
|
|
13
|
+
import type { handleBashTool } from "../tools/bash/handler";
|
|
14
|
+
|
|
15
|
+
export type { ToolMessageContent };
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Tool Definition Types (merged from tool-registry.ts)
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A tool definition with a name, description, and Zod schema for arguments.
|
|
23
|
+
* Does not include a handler - use ToolWithHandler for tools with handlers.
|
|
24
|
+
*/
|
|
25
|
+
export interface ToolDefinition<
|
|
26
|
+
TName extends string = string,
|
|
27
|
+
TSchema extends z.ZodType = z.ZodType,
|
|
28
|
+
> {
|
|
29
|
+
name: TName;
|
|
30
|
+
description: string;
|
|
31
|
+
schema: TSchema;
|
|
32
|
+
strict?: boolean;
|
|
33
|
+
max_uses?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A tool definition with an integrated handler function.
|
|
38
|
+
* This is the primary type for defining tools in the router.
|
|
39
|
+
*/
|
|
40
|
+
export interface ToolWithHandler<
|
|
41
|
+
TName extends string = string,
|
|
42
|
+
TSchema extends z.ZodType = z.ZodType,
|
|
43
|
+
TResult = unknown,
|
|
44
|
+
TContext = ToolHandlerContext,
|
|
45
|
+
> {
|
|
46
|
+
name: TName;
|
|
47
|
+
description: string;
|
|
48
|
+
schema: TSchema;
|
|
49
|
+
handler: ToolHandler<z.infer<TSchema>, TResult, TContext>;
|
|
50
|
+
strict?: boolean;
|
|
51
|
+
max_uses?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A map of tool keys to tool definitions with handlers.
|
|
56
|
+
*/
|
|
57
|
+
export type ToolMap = Record<
|
|
58
|
+
string,
|
|
59
|
+
{
|
|
60
|
+
name: string;
|
|
61
|
+
description: string;
|
|
62
|
+
schema: z.ZodType;
|
|
63
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
64
|
+
handler: (
|
|
65
|
+
args: any,
|
|
66
|
+
context: any
|
|
67
|
+
) => ToolHandlerResponse<any> | Promise<ToolHandlerResponse<any>>;
|
|
68
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
69
|
+
strict?: boolean;
|
|
70
|
+
max_uses?: number;
|
|
71
|
+
}
|
|
72
|
+
>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Converts a ToolMap to MessageStructure-compatible tools type.
|
|
76
|
+
* Maps each tool's name to a MessageToolDefinition with inferred input type from the schema.
|
|
77
|
+
*/
|
|
78
|
+
export type ToolMapToMessageTools<T extends ToolMap> = {
|
|
79
|
+
[K in keyof T as T[K]["name"]]: MessageToolDefinition<
|
|
80
|
+
z.infer<T[K]["schema"]>
|
|
81
|
+
>;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Extract the tool names from a tool map (uses the tool's name property, not the key).
|
|
86
|
+
*/
|
|
87
|
+
export type ToolNames<T extends ToolMap> = T[keyof T]["name"];
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* A raw tool call as received from the LLM before parsing.
|
|
91
|
+
*/
|
|
92
|
+
export interface RawToolCall {
|
|
93
|
+
id?: string;
|
|
94
|
+
name: string;
|
|
95
|
+
args: unknown;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A parsed tool call with validated arguments for a specific tool.
|
|
100
|
+
*/
|
|
101
|
+
export interface ParsedToolCall<
|
|
102
|
+
TName extends string = string,
|
|
103
|
+
TArgs = unknown,
|
|
104
|
+
> {
|
|
105
|
+
id: string;
|
|
106
|
+
name: TName;
|
|
107
|
+
args: TArgs;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Union type of all possible parsed tool calls from a tool map.
|
|
112
|
+
*/
|
|
113
|
+
export type ParsedToolCallUnion<T extends ToolMap> = {
|
|
114
|
+
[K in keyof T]: ParsedToolCall<T[K]["name"], z.infer<T[K]["schema"]>>;
|
|
115
|
+
}[keyof T];
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Handler Types
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Function signature for appending tool results to a thread.
|
|
123
|
+
*/
|
|
124
|
+
export type AppendToolResultFn = (config: ToolResultConfig) => Promise<void>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The response from a tool handler.
|
|
128
|
+
* Contains the content for the tool message and the result to return from processToolCalls.
|
|
129
|
+
*/
|
|
130
|
+
export interface ToolHandlerResponse<TResult> {
|
|
131
|
+
/** Content for the tool message added to the thread */
|
|
132
|
+
content: ToolMessageContent;
|
|
133
|
+
/** Result returned from processToolCalls */
|
|
134
|
+
result: TResult;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Context passed to tool handlers for additional data beyond tool args.
|
|
139
|
+
* Use this to pass workflow state like file trees, user context, etc.
|
|
140
|
+
*/
|
|
141
|
+
export interface ToolHandlerContext {
|
|
142
|
+
/** Additional context data - define your own shape */
|
|
143
|
+
[key: string]: unknown;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface BuildInToolDefinitions {
|
|
147
|
+
[bashTool.name]: typeof bashTool & {
|
|
148
|
+
handler: ReturnType<typeof handleBashTool>;
|
|
149
|
+
};
|
|
150
|
+
[taskCreateTool.name]: typeof taskCreateTool & {
|
|
151
|
+
handler: ReturnType<typeof createTaskCreateHandler>;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export const buildIntoolDefinitions: Record<
|
|
156
|
+
keyof BuildInToolDefinitions,
|
|
157
|
+
ToolDefinition
|
|
158
|
+
> = {
|
|
159
|
+
[bashTool.name]: bashTool,
|
|
160
|
+
[taskCreateTool.name]: taskCreateTool,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A handler function for a specific tool.
|
|
165
|
+
* Receives the parsed args and context, returns a response with content and result.
|
|
166
|
+
* Context always has a value (defaults to empty object if not provided).
|
|
167
|
+
*/
|
|
168
|
+
export type ToolHandler<TArgs, TResult, TContext = ToolHandlerContext> = (
|
|
169
|
+
args: TArgs,
|
|
170
|
+
context: TContext
|
|
171
|
+
) => ToolHandlerResponse<TResult> | Promise<ToolHandlerResponse<TResult>>;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Activity-compatible tool handler that always returns a Promise.
|
|
175
|
+
* Use this for tool handlers registered as Temporal activities.
|
|
176
|
+
* Context always has a value (defaults to empty object if not provided).
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```typescript
|
|
180
|
+
* // Filesystem handler with context
|
|
181
|
+
* const readHandler: ActivityToolHandler<
|
|
182
|
+
* ReadToolSchemaType,
|
|
183
|
+
* ReadResult,
|
|
184
|
+
* { scopedNodes: FileNode[]; provider: FileSystemProvider }
|
|
185
|
+
* > = async (args, context) => {
|
|
186
|
+
* return readHandler(args, context.scopedNodes, context.provider);
|
|
187
|
+
* };
|
|
188
|
+
* ```
|
|
189
|
+
*/
|
|
190
|
+
export type ActivityToolHandler<
|
|
191
|
+
TArgs,
|
|
192
|
+
TResult,
|
|
193
|
+
TContext = ToolHandlerContext,
|
|
194
|
+
> = (args: TArgs, context: TContext) => Promise<ToolHandlerResponse<TResult>>;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Extract the args type for a specific tool name from a tool map.
|
|
198
|
+
*/
|
|
199
|
+
export type ToolArgs<T extends ToolMap, TName extends ToolNames<T>> = z.infer<
|
|
200
|
+
Extract<T[keyof T], { name: TName }>["schema"]
|
|
201
|
+
>;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Extract the result type for a specific tool name from a tool map.
|
|
205
|
+
*/
|
|
206
|
+
export type ToolResult<T extends ToolMap, TName extends ToolNames<T>> =
|
|
207
|
+
Extract<T[keyof T], { name: TName }>["handler"] extends ToolHandler<
|
|
208
|
+
unknown,
|
|
209
|
+
infer R,
|
|
210
|
+
unknown
|
|
211
|
+
>
|
|
212
|
+
? Awaited<R>
|
|
213
|
+
: never;
|
|
214
|
+
|
|
215
|
+
// ============================================================================
|
|
216
|
+
// Tool Call Result Types
|
|
217
|
+
// ============================================================================
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The result of processing a tool call.
|
|
221
|
+
*/
|
|
222
|
+
export interface ToolCallResult<
|
|
223
|
+
TName extends string = string,
|
|
224
|
+
TResult = unknown,
|
|
225
|
+
> {
|
|
226
|
+
toolCallId: string;
|
|
227
|
+
name: TName;
|
|
228
|
+
result: TResult;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Options for creating a tool router.
|
|
233
|
+
*/
|
|
234
|
+
export interface ToolRouterOptions<T extends ToolMap> {
|
|
235
|
+
/** File tree for the agent */
|
|
236
|
+
fileTree: string;
|
|
237
|
+
/** Map of tools with their handlers */
|
|
238
|
+
tools: T;
|
|
239
|
+
/** Thread ID for appending tool results */
|
|
240
|
+
threadId: string;
|
|
241
|
+
/** Function to append tool results to the thread (called automatically after each handler) */
|
|
242
|
+
appendToolResult: AppendToolResultFn;
|
|
243
|
+
/** Whether to process tools in parallel (default: true) */
|
|
244
|
+
parallel?: boolean;
|
|
245
|
+
/** Lifecycle hooks for tool execution */
|
|
246
|
+
hooks?: Hooks<T, ToolCallResultUnion<InferToolResults<T>>>;
|
|
247
|
+
/** Subagent configurations */
|
|
248
|
+
subagents?: SubagentConfig[];
|
|
249
|
+
/** Build in tools - accepts raw handlers or proxied activities */
|
|
250
|
+
buildInTools?: {
|
|
251
|
+
[K in keyof BuildInToolDefinitions]?: BuildInToolDefinitions[K]["handler"];
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Infer result types from a tool map based on handler return types.
|
|
257
|
+
*/
|
|
258
|
+
export type InferToolResults<T extends ToolMap> = {
|
|
259
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
260
|
+
[K in keyof T as T[K]["name"]]: T[K]["handler"] extends ToolHandler<
|
|
261
|
+
any,
|
|
262
|
+
infer R,
|
|
263
|
+
any
|
|
264
|
+
>
|
|
265
|
+
? /* eslint-enable @typescript-eslint/no-explicit-any */
|
|
266
|
+
Awaited<R>
|
|
267
|
+
: never;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Union of all possible tool call results based on handler return types.
|
|
272
|
+
*/
|
|
273
|
+
export type ToolCallResultUnion<TResults extends Record<string, unknown>> = {
|
|
274
|
+
[TName in keyof TResults & string]: ToolCallResult<TName, TResults[TName]>;
|
|
275
|
+
}[keyof TResults & string];
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Context passed to processToolCalls for hook execution and handler invocation
|
|
279
|
+
*/
|
|
280
|
+
export interface ProcessToolCallsContext<THandlerContext = ToolHandlerContext> {
|
|
281
|
+
/** Current turn number (for hooks) */
|
|
282
|
+
turn?: number;
|
|
283
|
+
/** Context passed to each tool handler (scopedNodes, provider, etc.) */
|
|
284
|
+
handlerContext?: THandlerContext;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// Router Interface
|
|
289
|
+
// ============================================================================
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* The tool router interface with full type inference for both args and results.
|
|
293
|
+
*/
|
|
294
|
+
export interface ToolRouter<T extends ToolMap> {
|
|
295
|
+
/** Check if the router has any tools */
|
|
296
|
+
hasTools(): boolean;
|
|
297
|
+
// --- Methods from registry ---
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Parse and validate a raw tool call against the router's tools.
|
|
301
|
+
* Returns a typed tool call with validated arguments.
|
|
302
|
+
*/
|
|
303
|
+
parseToolCall(toolCall: RawToolCall): ParsedToolCallUnion<T>;
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Check if a tool with the given name exists in the router.
|
|
307
|
+
*/
|
|
308
|
+
hasTool(name: string): boolean;
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Get all tool names in the router.
|
|
312
|
+
*/
|
|
313
|
+
getToolNames(): ToolNames<T>[];
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Get all tool definitions (without handlers) for passing to LLM.
|
|
317
|
+
*/
|
|
318
|
+
getToolDefinitions(): ToolDefinition[];
|
|
319
|
+
|
|
320
|
+
// --- Methods for processing tool calls ---
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Process all tool calls using the registered handlers.
|
|
324
|
+
* Returns typed results based on handler return types.
|
|
325
|
+
* @param toolCalls - Array of parsed tool calls to process
|
|
326
|
+
* @param context - Optional context including turn number for hooks
|
|
327
|
+
*/
|
|
328
|
+
processToolCalls(
|
|
329
|
+
toolCalls: ParsedToolCallUnion<T>[],
|
|
330
|
+
context?: ProcessToolCallsContext
|
|
331
|
+
): Promise<ToolCallResultUnion<InferToolResults<T>>[]>;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Process tool calls matching a specific name with a custom handler.
|
|
335
|
+
* Useful for overriding the default handler for specific cases.
|
|
336
|
+
*/
|
|
337
|
+
processToolCallsByName<
|
|
338
|
+
TName extends ToolNames<T>,
|
|
339
|
+
TResult,
|
|
340
|
+
TContext = ToolHandlerContext,
|
|
341
|
+
>(
|
|
342
|
+
toolCalls: ParsedToolCallUnion<T>[],
|
|
343
|
+
toolName: TName,
|
|
344
|
+
handler: ToolHandler<ToolArgs<T, TName>, TResult, TContext>,
|
|
345
|
+
context?: ProcessToolCallsContext<TContext>
|
|
346
|
+
): Promise<ToolCallResult<TName, TResult>[]>;
|
|
347
|
+
|
|
348
|
+
// --- Utility methods ---
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Filter tool calls by name.
|
|
352
|
+
*/
|
|
353
|
+
filterByName<TName extends ToolNames<T>>(
|
|
354
|
+
toolCalls: ParsedToolCallUnion<T>[],
|
|
355
|
+
name: TName
|
|
356
|
+
): ParsedToolCall<TName, ToolArgs<T, TName>>[];
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Check if any tool call matches the given name.
|
|
360
|
+
*/
|
|
361
|
+
hasToolCall(toolCalls: ParsedToolCallUnion<T>[], name: ToolNames<T>): boolean;
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Filter results by tool name.
|
|
365
|
+
*/
|
|
366
|
+
getResultsByName<TName extends ToolNames<T>>(
|
|
367
|
+
results: ToolCallResultUnion<InferToolResults<T>>[],
|
|
368
|
+
name: TName
|
|
369
|
+
): ToolCallResult<TName, ToolResult<T, TName>>[];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ============================================================================
|
|
373
|
+
// Router Factory
|
|
374
|
+
// ============================================================================
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Creates a tool router for declarative tool call processing.
|
|
378
|
+
* Combines tool definitions with handlers in a single API.
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* const router = createToolRouter({
|
|
383
|
+
* threadId,
|
|
384
|
+
* tools: {
|
|
385
|
+
* Read: {
|
|
386
|
+
* name: "FileRead",
|
|
387
|
+
* description: "Read file contents",
|
|
388
|
+
* schema: z.object({ path: z.string() }),
|
|
389
|
+
* handler: async (args, ctx) => ({
|
|
390
|
+
* content: `Read ${args.path}`,
|
|
391
|
+
* result: { path: args.path, content: "..." },
|
|
392
|
+
* }),
|
|
393
|
+
* },
|
|
394
|
+
* },
|
|
395
|
+
* hooks: { onPreToolUse, onPostToolUse },
|
|
396
|
+
* });
|
|
397
|
+
*
|
|
398
|
+
* // Parse raw tool calls from LLM
|
|
399
|
+
* const parsed = router.parseToolCall(rawToolCall);
|
|
400
|
+
*
|
|
401
|
+
* // Process tool calls
|
|
402
|
+
* const results = await router.processToolCalls([parsed]);
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
export function createToolRouter<T extends ToolMap>(
|
|
406
|
+
options: ToolRouterOptions<T>
|
|
407
|
+
): ToolRouter<T> {
|
|
408
|
+
const { appendToolResult } = proxyActivities<ZeitlichSharedActivities>({
|
|
409
|
+
startToCloseTimeout: "2m",
|
|
410
|
+
retry: {
|
|
411
|
+
maximumAttempts: 3,
|
|
412
|
+
initialInterval: "5s",
|
|
413
|
+
maximumInterval: "15m",
|
|
414
|
+
backoffCoefficient: 4,
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
type TResults = InferToolResults<T>;
|
|
418
|
+
|
|
419
|
+
// Build internal lookup map by tool name
|
|
420
|
+
// Use ToolMap's value type to allow both user tools and the dynamic Task tool
|
|
421
|
+
const toolMap = new Map<string, ToolMap[string]>();
|
|
422
|
+
for (const [_key, tool] of Object.entries(options.tools)) {
|
|
423
|
+
toolMap.set(tool.name, tool as T[keyof T]);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (options.subagents) {
|
|
427
|
+
toolMap.set("Task", {
|
|
428
|
+
...createTaskTool(options.subagents),
|
|
429
|
+
handler: createTaskHandler(options.subagents),
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (options.buildInTools) {
|
|
434
|
+
for (const [key, value] of Object.entries(options.buildInTools)) {
|
|
435
|
+
if (key === bashTool.name) {
|
|
436
|
+
toolMap.set(key, {
|
|
437
|
+
...buildIntoolDefinitions[key as keyof BuildInToolDefinitions],
|
|
438
|
+
description: createBashToolDescription({
|
|
439
|
+
fileTree: options.fileTree,
|
|
440
|
+
}),
|
|
441
|
+
handler: value,
|
|
442
|
+
});
|
|
443
|
+
} else {
|
|
444
|
+
toolMap.set(key, {
|
|
445
|
+
...buildIntoolDefinitions[key as keyof BuildInToolDefinitions],
|
|
446
|
+
handler: value,
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function processToolCall(
|
|
453
|
+
toolCall: ParsedToolCallUnion<T>,
|
|
454
|
+
turn: number,
|
|
455
|
+
handlerContext?: ToolHandlerContext
|
|
456
|
+
): Promise<ToolCallResultUnion<TResults> | null> {
|
|
457
|
+
const startTime = Date.now();
|
|
458
|
+
|
|
459
|
+
// PreToolUse hook - can skip or modify args
|
|
460
|
+
let effectiveArgs: unknown = toolCall.args;
|
|
461
|
+
if (options.hooks?.onPreToolUse) {
|
|
462
|
+
const preResult = await options.hooks.onPreToolUse({
|
|
463
|
+
toolCall,
|
|
464
|
+
threadId: options.threadId,
|
|
465
|
+
turn,
|
|
466
|
+
});
|
|
467
|
+
if (preResult?.skip) {
|
|
468
|
+
// Skip this tool call - append a skip message and return null
|
|
469
|
+
await appendToolResult({
|
|
470
|
+
threadId: options.threadId,
|
|
471
|
+
toolCallId: toolCall.id,
|
|
472
|
+
content: JSON.stringify({
|
|
473
|
+
skipped: true,
|
|
474
|
+
reason: "Skipped by PreToolUse hook",
|
|
475
|
+
}),
|
|
476
|
+
});
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
if (preResult?.modifiedArgs !== undefined) {
|
|
480
|
+
effectiveArgs = preResult.modifiedArgs;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const tool = toolMap.get(toolCall.name);
|
|
485
|
+
let result: unknown;
|
|
486
|
+
let content: ToolMessageContent;
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
if (tool) {
|
|
490
|
+
const response = await tool.handler(
|
|
491
|
+
effectiveArgs as Parameters<typeof tool.handler>[0],
|
|
492
|
+
(handlerContext ?? {}) as Parameters<typeof tool.handler>[1]
|
|
493
|
+
);
|
|
494
|
+
result = response.result;
|
|
495
|
+
content = response.content;
|
|
496
|
+
} else {
|
|
497
|
+
result = { error: `Unknown tool: ${toolCall.name}` };
|
|
498
|
+
content = JSON.stringify(result, null, 2);
|
|
499
|
+
}
|
|
500
|
+
} catch (error) {
|
|
501
|
+
// PostToolUseFailure hook - can recover from errors
|
|
502
|
+
if (options.hooks?.onPostToolUseFailure) {
|
|
503
|
+
const failureResult = await options.hooks.onPostToolUseFailure({
|
|
504
|
+
toolCall,
|
|
505
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
506
|
+
threadId: options.threadId,
|
|
507
|
+
turn,
|
|
508
|
+
});
|
|
509
|
+
if (failureResult?.fallbackContent !== undefined) {
|
|
510
|
+
content = failureResult.fallbackContent;
|
|
511
|
+
result = { error: String(error), recovered: true };
|
|
512
|
+
} else if (failureResult?.suppress) {
|
|
513
|
+
content = JSON.stringify({ error: String(error), suppressed: true });
|
|
514
|
+
result = { error: String(error), suppressed: true };
|
|
515
|
+
} else {
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Automatically append tool result to thread
|
|
524
|
+
await appendToolResult({
|
|
525
|
+
threadId: options.threadId,
|
|
526
|
+
toolCallId: toolCall.id,
|
|
527
|
+
content,
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
const toolResult = {
|
|
531
|
+
toolCallId: toolCall.id,
|
|
532
|
+
name: toolCall.name,
|
|
533
|
+
result,
|
|
534
|
+
} as ToolCallResultUnion<TResults>;
|
|
535
|
+
|
|
536
|
+
// PostToolUse hook - called after successful execution
|
|
537
|
+
if (options.hooks?.onPostToolUse) {
|
|
538
|
+
const durationMs = Date.now() - startTime;
|
|
539
|
+
await options.hooks.onPostToolUse({
|
|
540
|
+
toolCall,
|
|
541
|
+
result: toolResult,
|
|
542
|
+
threadId: options.threadId,
|
|
543
|
+
turn,
|
|
544
|
+
durationMs,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return toolResult;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return {
|
|
552
|
+
// --- Methods from registry ---
|
|
553
|
+
|
|
554
|
+
hasTools(): boolean {
|
|
555
|
+
return toolMap.size > 0;
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
parseToolCall(toolCall: RawToolCall): ParsedToolCallUnion<T> {
|
|
559
|
+
const tool = toolMap.get(toolCall.name);
|
|
560
|
+
|
|
561
|
+
if (!tool) {
|
|
562
|
+
throw new Error(`Tool ${toolCall.name} not found`);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Parse and validate args using the tool's schema
|
|
566
|
+
const parsedArgs = tool.schema.parse(toolCall.args);
|
|
567
|
+
|
|
568
|
+
return {
|
|
569
|
+
id: toolCall.id ?? "",
|
|
570
|
+
name: toolCall.name,
|
|
571
|
+
args: parsedArgs,
|
|
572
|
+
} as ParsedToolCallUnion<T>;
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
hasTool(name: string): boolean {
|
|
576
|
+
return toolMap.has(name);
|
|
577
|
+
},
|
|
578
|
+
|
|
579
|
+
getToolNames(): ToolNames<T>[] {
|
|
580
|
+
return Array.from(toolMap.keys()) as ToolNames<T>[];
|
|
581
|
+
},
|
|
582
|
+
|
|
583
|
+
getToolDefinitions(): ToolDefinition[] {
|
|
584
|
+
return Array.from(toolMap).map(([name, tool]) => ({
|
|
585
|
+
name,
|
|
586
|
+
description: tool.description,
|
|
587
|
+
schema: tool.schema,
|
|
588
|
+
strict: tool.strict,
|
|
589
|
+
max_uses: tool.max_uses,
|
|
590
|
+
}));
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
// --- Methods for processing tool calls ---
|
|
594
|
+
|
|
595
|
+
async processToolCalls(
|
|
596
|
+
toolCalls: ParsedToolCallUnion<T>[],
|
|
597
|
+
context?: ProcessToolCallsContext
|
|
598
|
+
): Promise<ToolCallResultUnion<TResults>[]> {
|
|
599
|
+
if (toolCalls.length === 0) {
|
|
600
|
+
return [];
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const turn = context?.turn ?? 0;
|
|
604
|
+
const handlerContext = context?.handlerContext;
|
|
605
|
+
|
|
606
|
+
if (options.parallel) {
|
|
607
|
+
const results = await Promise.all(
|
|
608
|
+
toolCalls.map((tc) => processToolCall(tc, turn, handlerContext))
|
|
609
|
+
);
|
|
610
|
+
// Filter out null results (skipped tool calls)
|
|
611
|
+
return results.filter(
|
|
612
|
+
(r): r is NonNullable<typeof r> => r !== null
|
|
613
|
+
) as ToolCallResultUnion<TResults>[];
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Sequential processing
|
|
617
|
+
const results: ToolCallResultUnion<TResults>[] = [];
|
|
618
|
+
for (const toolCall of toolCalls) {
|
|
619
|
+
const result = await processToolCall(toolCall, turn, handlerContext);
|
|
620
|
+
if (result !== null) {
|
|
621
|
+
results.push(result);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return results;
|
|
625
|
+
},
|
|
626
|
+
|
|
627
|
+
async processToolCallsByName<
|
|
628
|
+
TName extends ToolNames<T>,
|
|
629
|
+
TResult,
|
|
630
|
+
TContext = ToolHandlerContext,
|
|
631
|
+
>(
|
|
632
|
+
toolCalls: ParsedToolCallUnion<T>[],
|
|
633
|
+
toolName: TName,
|
|
634
|
+
handler: ToolHandler<ToolArgs<T, TName>, TResult, TContext>,
|
|
635
|
+
context?: ProcessToolCallsContext<TContext>
|
|
636
|
+
): Promise<ToolCallResult<TName, TResult>[]> {
|
|
637
|
+
const matchingCalls = toolCalls.filter((tc) => tc.name === toolName);
|
|
638
|
+
|
|
639
|
+
if (matchingCalls.length === 0) {
|
|
640
|
+
return [];
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
const handlerContext = (context?.handlerContext ?? {}) as TContext;
|
|
644
|
+
|
|
645
|
+
const processOne = async (
|
|
646
|
+
toolCall: ParsedToolCallUnion<T>
|
|
647
|
+
): Promise<ToolCallResult<TName, TResult>> => {
|
|
648
|
+
const response = await handler(
|
|
649
|
+
toolCall.args as ToolArgs<T, TName>,
|
|
650
|
+
handlerContext
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
// Automatically append tool result to thread
|
|
654
|
+
await appendToolResult({
|
|
655
|
+
threadId: options.threadId,
|
|
656
|
+
toolCallId: toolCall.id,
|
|
657
|
+
content: response.content,
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
return {
|
|
661
|
+
toolCallId: toolCall.id,
|
|
662
|
+
name: toolCall.name as TName,
|
|
663
|
+
result: response.result,
|
|
664
|
+
};
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
if (options.parallel) {
|
|
668
|
+
return Promise.all(matchingCalls.map(processOne));
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const results: ToolCallResult<TName, TResult>[] = [];
|
|
672
|
+
for (const toolCall of matchingCalls) {
|
|
673
|
+
results.push(await processOne(toolCall));
|
|
674
|
+
}
|
|
675
|
+
return results;
|
|
676
|
+
},
|
|
677
|
+
|
|
678
|
+
// --- Utility methods ---
|
|
679
|
+
|
|
680
|
+
filterByName<TName extends ToolNames<T>>(
|
|
681
|
+
toolCalls: ParsedToolCallUnion<T>[],
|
|
682
|
+
name: TName
|
|
683
|
+
): ParsedToolCall<TName, ToolArgs<T, TName>>[] {
|
|
684
|
+
return toolCalls.filter(
|
|
685
|
+
(tc): tc is ParsedToolCall<TName, ToolArgs<T, TName>> =>
|
|
686
|
+
tc.name === name
|
|
687
|
+
);
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
hasToolCall(
|
|
691
|
+
toolCalls: ParsedToolCallUnion<T>[],
|
|
692
|
+
name: ToolNames<T>
|
|
693
|
+
): boolean {
|
|
694
|
+
return toolCalls.some((tc) => tc.name === name);
|
|
695
|
+
},
|
|
696
|
+
|
|
697
|
+
getResultsByName<TName extends ToolNames<T>>(
|
|
698
|
+
results: ToolCallResultUnion<TResults>[],
|
|
699
|
+
name: TName
|
|
700
|
+
): ToolCallResult<TName, ToolResult<T, TName>>[] {
|
|
701
|
+
return results.filter((r) => r.name === name) as ToolCallResult<
|
|
702
|
+
TName,
|
|
703
|
+
ToolResult<T, TName>
|
|
704
|
+
>[];
|
|
705
|
+
},
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Utility to check if there were no tool calls besides a specific one
|
|
711
|
+
*/
|
|
712
|
+
export function hasNoOtherToolCalls<T extends ToolMap>(
|
|
713
|
+
toolCalls: ParsedToolCallUnion<T>[],
|
|
714
|
+
excludeName: ToolNames<T>
|
|
715
|
+
): boolean {
|
|
716
|
+
return toolCalls.filter((tc) => tc.name !== excludeName).length === 0;
|
|
717
|
+
}
|