zeitlich 0.2.13 → 0.2.14

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 (135) hide show
  1. package/README.md +49 -38
  2. package/dist/adapters/sandbox/daytona/index.cjs +205 -0
  3. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -0
  4. package/dist/adapters/sandbox/daytona/index.d.cts +86 -0
  5. package/dist/adapters/sandbox/daytona/index.d.ts +86 -0
  6. package/dist/adapters/sandbox/daytona/index.js +202 -0
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -0
  8. package/dist/adapters/sandbox/inmemory/index.cjs +174 -0
  9. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -0
  10. package/dist/adapters/sandbox/inmemory/index.d.cts +28 -0
  11. package/dist/adapters/sandbox/inmemory/index.d.ts +28 -0
  12. package/dist/adapters/sandbox/inmemory/index.js +172 -0
  13. package/dist/adapters/sandbox/inmemory/index.js.map +1 -0
  14. package/dist/adapters/sandbox/virtual/index.cjs +405 -0
  15. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -0
  16. package/dist/adapters/sandbox/virtual/index.d.cts +85 -0
  17. package/dist/adapters/sandbox/virtual/index.d.ts +85 -0
  18. package/dist/adapters/sandbox/virtual/index.js +400 -0
  19. package/dist/adapters/sandbox/virtual/index.js.map +1 -0
  20. package/dist/adapters/thread/google-genai/index.cjs +284 -0
  21. package/dist/adapters/thread/google-genai/index.cjs.map +1 -0
  22. package/dist/adapters/thread/google-genai/index.d.cts +145 -0
  23. package/dist/adapters/thread/google-genai/index.d.ts +145 -0
  24. package/dist/adapters/thread/google-genai/index.js +278 -0
  25. package/dist/adapters/thread/google-genai/index.js.map +1 -0
  26. package/dist/adapters/{langchain → thread/langchain}/index.cjs +7 -9
  27. package/dist/adapters/thread/langchain/index.cjs.map +1 -0
  28. package/dist/adapters/{langchain → thread/langchain}/index.d.cts +17 -21
  29. package/dist/adapters/{langchain → thread/langchain}/index.d.ts +17 -21
  30. package/dist/adapters/{langchain → thread/langchain}/index.js +7 -9
  31. package/dist/adapters/thread/langchain/index.js.map +1 -0
  32. package/dist/index.cjs +816 -545
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +235 -74
  35. package/dist/index.d.ts +235 -74
  36. package/dist/index.js +804 -540
  37. package/dist/index.js.map +1 -1
  38. package/dist/types-B4C9txdq.d.ts +389 -0
  39. package/dist/{thread-manager-qc0g5Rvd.d.cts → types-B9ljZewB.d.cts} +1 -6
  40. package/dist/{thread-manager-qc0g5Rvd.d.ts → types-B9ljZewB.d.ts} +1 -6
  41. package/dist/types-BMXzv7TN.d.cts +476 -0
  42. package/dist/types-BMXzv7TN.d.ts +476 -0
  43. package/dist/types-BVP87m_W.d.cts +121 -0
  44. package/dist/types-CDubRtad.d.cts +115 -0
  45. package/dist/types-CDubRtad.d.ts +115 -0
  46. package/dist/types-CwwgQ_9H.d.ts +121 -0
  47. package/dist/types-GpMU4b0w.d.cts +389 -0
  48. package/dist/workflow.cjs +444 -318
  49. package/dist/workflow.cjs.map +1 -1
  50. package/dist/workflow.d.cts +271 -222
  51. package/dist/workflow.d.ts +271 -222
  52. package/dist/workflow.js +440 -316
  53. package/dist/workflow.js.map +1 -1
  54. package/package.json +59 -6
  55. package/src/adapters/sandbox/daytona/filesystem.ts +136 -0
  56. package/src/adapters/sandbox/daytona/index.ts +149 -0
  57. package/src/adapters/sandbox/daytona/types.ts +34 -0
  58. package/src/adapters/sandbox/inmemory/index.ts +213 -0
  59. package/src/adapters/sandbox/virtual/filesystem.ts +345 -0
  60. package/src/adapters/sandbox/virtual/index.ts +88 -0
  61. package/src/adapters/sandbox/virtual/mutations.ts +38 -0
  62. package/src/adapters/sandbox/virtual/provider.ts +101 -0
  63. package/src/adapters/sandbox/virtual/tree.ts +82 -0
  64. package/src/adapters/sandbox/virtual/types.ts +127 -0
  65. package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +523 -0
  66. package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +91 -0
  67. package/src/adapters/thread/google-genai/activities.ts +121 -0
  68. package/src/adapters/thread/google-genai/index.ts +41 -0
  69. package/src/adapters/thread/google-genai/model-invoker.ts +154 -0
  70. package/src/adapters/thread/google-genai/thread-manager.ts +169 -0
  71. package/src/adapters/{langchain → thread/langchain}/activities.ts +11 -15
  72. package/src/adapters/{langchain → thread/langchain}/index.ts +1 -1
  73. package/src/adapters/{langchain → thread/langchain}/model-invoker.ts +15 -18
  74. package/src/adapters/{langchain → thread/langchain}/thread-manager.ts +1 -1
  75. package/src/index.ts +32 -24
  76. package/src/lib/activity.ts +87 -0
  77. package/src/lib/hooks/index.ts +11 -0
  78. package/src/lib/hooks/types.ts +98 -0
  79. package/src/lib/model/helpers.ts +6 -0
  80. package/src/lib/model/index.ts +13 -0
  81. package/src/lib/{model-invoker.ts → model/types.ts} +18 -1
  82. package/src/lib/sandbox/index.ts +19 -0
  83. package/src/lib/sandbox/manager.ts +76 -0
  84. package/src/lib/sandbox/sandbox.test.ts +158 -0
  85. package/src/lib/{fs.ts → sandbox/tree.ts} +6 -6
  86. package/src/lib/sandbox/types.ts +164 -0
  87. package/src/lib/session/index.ts +11 -0
  88. package/src/lib/{session.ts → session/session.ts} +76 -48
  89. package/src/lib/session/types.ts +93 -0
  90. package/src/lib/skills/fs-provider.ts +16 -15
  91. package/src/lib/skills/handler.ts +31 -0
  92. package/src/lib/skills/index.ts +5 -1
  93. package/src/lib/skills/register.ts +20 -0
  94. package/src/lib/skills/tool.ts +47 -0
  95. package/src/lib/state/index.ts +9 -0
  96. package/src/lib/{state-manager.ts → state/manager.ts} +10 -147
  97. package/src/lib/state/types.ts +134 -0
  98. package/src/lib/subagent/define.ts +71 -0
  99. package/src/lib/subagent/handler.ts +99 -0
  100. package/src/lib/subagent/index.ts +13 -0
  101. package/src/lib/subagent/register.ts +53 -0
  102. package/src/lib/subagent/tool.ts +80 -0
  103. package/src/lib/subagent/types.ts +92 -0
  104. package/src/lib/thread/index.ts +7 -0
  105. package/src/lib/{thread-manager.ts → thread/manager.ts} +1 -33
  106. package/src/lib/thread/types.ts +33 -0
  107. package/src/lib/tool-router/auto-append.ts +55 -0
  108. package/src/lib/tool-router/index.ts +41 -0
  109. package/src/lib/tool-router/router.ts +462 -0
  110. package/src/lib/tool-router/types.ts +478 -0
  111. package/src/lib/tool-router/with-sandbox.ts +70 -0
  112. package/src/lib/types.ts +5 -382
  113. package/src/tools/bash/bash.test.ts +53 -55
  114. package/src/tools/bash/handler.ts +23 -51
  115. package/src/tools/edit/handler.ts +67 -81
  116. package/src/tools/glob/handler.ts +60 -17
  117. package/src/tools/read-file/handler.ts +67 -0
  118. package/src/tools/read-skill/handler.ts +1 -31
  119. package/src/tools/read-skill/tool.ts +5 -47
  120. package/src/tools/subagent/handler.ts +1 -100
  121. package/src/tools/subagent/tool.ts +5 -93
  122. package/src/tools/task-create/handler.ts +1 -1
  123. package/src/tools/task-get/handler.ts +1 -1
  124. package/src/tools/task-list/handler.ts +1 -1
  125. package/src/tools/task-update/handler.ts +1 -1
  126. package/src/tools/write-file/handler.ts +47 -0
  127. package/src/workflow.ts +88 -47
  128. package/tsup.config.ts +8 -1
  129. package/dist/adapters/langchain/index.cjs.map +0 -1
  130. package/dist/adapters/langchain/index.js.map +0 -1
  131. package/dist/model-invoker-y_zlyMqu.d.cts +0 -892
  132. package/dist/model-invoker-y_zlyMqu.d.ts +0 -892
  133. package/src/lib/tool-router.ts +0 -977
  134. package/src/lib/workflow-helpers.ts +0 -50
  135. /package/src/lib/{thread-id.ts → thread/id.ts} +0 -0
@@ -0,0 +1,462 @@
1
+ import type { ToolMessageContent } from "../types";
2
+ import type {
3
+ ToolMap,
4
+ ToolDefinition,
5
+ RouterContext,
6
+ ToolHandler,
7
+ RawToolCall,
8
+ ParsedToolCallUnion,
9
+ ParsedToolCall,
10
+ ToolCallResult,
11
+ ToolCallResultUnion,
12
+ InferToolResults,
13
+ ToolRouterOptions,
14
+ ToolRouter,
15
+ ToolNames,
16
+ ToolArgs,
17
+ ToolResult,
18
+ ProcessToolCallsContext,
19
+ ToolWithHandler,
20
+ } from "./types";
21
+
22
+ import type { z } from "zod";
23
+ import { ApplicationFailure } from "@temporalio/workflow";
24
+
25
+ /**
26
+ * Creates a tool router for declarative tool call processing.
27
+ * Combines tool definitions with handlers in a single API.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const router = createToolRouter({
32
+ * threadId,
33
+ * tools: {
34
+ * Read: {
35
+ * name: "FileRead",
36
+ * description: "Read file contents",
37
+ * schema: z.object({ path: z.string() }),
38
+ * handler: async (args, ctx) => ({
39
+ * content: `Read ${args.path}`,
40
+ * result: { path: args.path, content: "..." },
41
+ * }),
42
+ * },
43
+ * },
44
+ * hooks: { onPreToolUse, onPostToolUse },
45
+ * });
46
+ *
47
+ * // Parse raw tool calls from LLM
48
+ * const parsed = router.parseToolCall(rawToolCall);
49
+ *
50
+ * // Process tool calls
51
+ * const results = await router.processToolCalls([parsed]);
52
+ * ```
53
+ */
54
+ export function createToolRouter<T extends ToolMap>(
55
+ options: ToolRouterOptions<T>
56
+ ): ToolRouter<T> {
57
+ const { appendToolResult } = options;
58
+ type TResults = InferToolResults<T>;
59
+
60
+ // Build internal lookup map by tool name
61
+ const toolMap = new Map<string, ToolMap[string]>();
62
+ for (const [_key, tool] of Object.entries(options.tools)) {
63
+ toolMap.set(tool.name, tool as T[keyof T]);
64
+ }
65
+
66
+ /** Check if a tool is enabled (defaults to true when not specified) */
67
+ const isEnabled = (tool: ToolMap[string]): boolean => tool.enabled ?? true;
68
+
69
+ if (options.plugins) {
70
+ for (const plugin of options.plugins) {
71
+ toolMap.set(plugin.name, plugin);
72
+ }
73
+ }
74
+
75
+ /** Run global → per-tool pre-hooks. Returns null to skip, or the (possibly modified) args. */
76
+ async function runPreHooks(
77
+ toolCall: ParsedToolCallUnion<T>,
78
+ tool: ToolMap[string] | undefined,
79
+ turn: number
80
+ ): Promise<{ skip: true } | { skip: false; args: unknown }> {
81
+ let effectiveArgs: unknown = toolCall.args;
82
+
83
+ if (options.hooks?.onPreToolUse) {
84
+ const preResult = await options.hooks.onPreToolUse({
85
+ toolCall,
86
+ threadId: options.threadId,
87
+ turn,
88
+ });
89
+ if (preResult?.skip) return { skip: true };
90
+ if (preResult?.modifiedArgs !== undefined)
91
+ effectiveArgs = preResult.modifiedArgs;
92
+ }
93
+
94
+ if (tool?.hooks?.onPreToolUse) {
95
+ const preResult = await tool.hooks.onPreToolUse({
96
+ args: effectiveArgs,
97
+ threadId: options.threadId,
98
+ turn,
99
+ });
100
+ if (preResult?.skip) return { skip: true };
101
+ if (preResult?.modifiedArgs !== undefined)
102
+ effectiveArgs = preResult.modifiedArgs;
103
+ }
104
+
105
+ return { skip: false, args: effectiveArgs };
106
+ }
107
+
108
+ /**
109
+ * Run per-tool → global failure hooks. Returns recovery content/result,
110
+ * or throws if no hook recovers.
111
+ */
112
+ async function runFailureHooks(
113
+ toolCall: ParsedToolCallUnion<T>,
114
+ tool: ToolMap[string] | undefined,
115
+ error: unknown,
116
+ effectiveArgs: unknown,
117
+ turn: number
118
+ ): Promise<{ content: ToolMessageContent; result: unknown }> {
119
+ const err = error instanceof Error ? error : new Error(String(error));
120
+ const errorStr = String(error);
121
+
122
+ if (tool?.hooks?.onPostToolUseFailure) {
123
+ const r = await tool.hooks.onPostToolUseFailure({
124
+ args: effectiveArgs,
125
+ error: err,
126
+ threadId: options.threadId,
127
+ turn,
128
+ });
129
+ if (r?.fallbackContent !== undefined)
130
+ return { content: r.fallbackContent, result: { error: errorStr, recovered: true } };
131
+ if (r?.suppress)
132
+ return {
133
+ content: JSON.stringify({ error: errorStr, suppressed: true }),
134
+ result: { error: errorStr, suppressed: true },
135
+ };
136
+ }
137
+
138
+ if (options.hooks?.onPostToolUseFailure) {
139
+ const r = await options.hooks.onPostToolUseFailure({
140
+ toolCall,
141
+ error: err,
142
+ threadId: options.threadId,
143
+ turn,
144
+ });
145
+ if (r?.fallbackContent !== undefined)
146
+ return { content: r.fallbackContent, result: { error: errorStr, recovered: true } };
147
+ if (r?.suppress)
148
+ return {
149
+ content: JSON.stringify({ error: errorStr, suppressed: true }),
150
+ result: { error: errorStr, suppressed: true },
151
+ };
152
+ }
153
+
154
+ throw ApplicationFailure.fromError(error, { nonRetryable: true });
155
+ }
156
+
157
+ /** Run per-tool → global post-hooks. */
158
+ async function runPostHooks(
159
+ toolCall: ParsedToolCallUnion<T>,
160
+ tool: ToolMap[string] | undefined,
161
+ toolResult: ToolCallResultUnion<TResults>,
162
+ effectiveArgs: unknown,
163
+ turn: number,
164
+ durationMs: number
165
+ ): Promise<void> {
166
+ if (tool?.hooks?.onPostToolUse) {
167
+ await tool.hooks.onPostToolUse({
168
+ args: effectiveArgs,
169
+ result: toolResult.data,
170
+ threadId: options.threadId,
171
+ turn,
172
+ durationMs,
173
+ });
174
+ }
175
+ if (options.hooks?.onPostToolUse) {
176
+ await options.hooks.onPostToolUse({
177
+ toolCall,
178
+ result: toolResult,
179
+ threadId: options.threadId,
180
+ turn,
181
+ durationMs,
182
+ });
183
+ }
184
+ }
185
+
186
+ async function processToolCall(
187
+ toolCall: ParsedToolCallUnion<T>,
188
+ turn: number,
189
+ sandboxId?: string
190
+ ): Promise<ToolCallResultUnion<TResults> | null> {
191
+ const startTime = Date.now();
192
+ const tool = toolMap.get(toolCall.name);
193
+
194
+ // --- Pre-hooks: may skip or modify args ---
195
+ const preResult = await runPreHooks(toolCall, tool, turn);
196
+ if (preResult.skip) {
197
+ await appendToolResult({
198
+ threadId: options.threadId,
199
+ toolCallId: toolCall.id,
200
+ toolName: toolCall.name,
201
+ content: JSON.stringify({ skipped: true, reason: "Skipped by PreToolUse hook" }),
202
+ });
203
+ return null;
204
+ }
205
+ const effectiveArgs = preResult.args;
206
+
207
+ // --- Execute handler ---
208
+ let result: unknown;
209
+ let content!: ToolMessageContent;
210
+ let resultAppended = false;
211
+
212
+ try {
213
+ if (tool) {
214
+ const routerContext: RouterContext = {
215
+ threadId: options.threadId,
216
+ toolCallId: toolCall.id,
217
+ toolName: toolCall.name,
218
+ ...(sandboxId !== undefined && { sandboxId }),
219
+ };
220
+ const response = await tool.handler(
221
+ effectiveArgs as Parameters<typeof tool.handler>[0],
222
+ routerContext as Parameters<typeof tool.handler>[1]
223
+ );
224
+ result = response.data;
225
+ content = response.toolResponse;
226
+ resultAppended = response.resultAppended === true;
227
+ } else {
228
+ result = { error: `Unknown tool: ${toolCall.name}` };
229
+ content = JSON.stringify(result, null, 2);
230
+ }
231
+ } catch (error) {
232
+ const recovery = await runFailureHooks(toolCall, tool, error, effectiveArgs, turn);
233
+ result = recovery.result;
234
+ content = recovery.content;
235
+ }
236
+
237
+ // --- Append result to thread (unless handler already did) ---
238
+ if (!resultAppended) {
239
+ await appendToolResult({
240
+ threadId: options.threadId,
241
+ toolCallId: toolCall.id,
242
+ toolName: toolCall.name,
243
+ content,
244
+ });
245
+ }
246
+
247
+ const toolResult = {
248
+ toolCallId: toolCall.id,
249
+ name: toolCall.name,
250
+ data: result,
251
+ } as ToolCallResultUnion<TResults>;
252
+
253
+ // --- Post-hooks ---
254
+ await runPostHooks(toolCall, tool, toolResult, effectiveArgs, turn, Date.now() - startTime);
255
+
256
+ return toolResult;
257
+ }
258
+
259
+ return {
260
+ hasTools(): boolean {
261
+ return Array.from(toolMap.values()).some(isEnabled);
262
+ },
263
+
264
+ parseToolCall(toolCall: RawToolCall): ParsedToolCallUnion<T> {
265
+ const tool = toolMap.get(toolCall.name);
266
+
267
+ if (!tool || !isEnabled(tool)) {
268
+ throw new Error(`Tool ${toolCall.name} not found`);
269
+ }
270
+
271
+ const parsedArgs = tool.schema.parse(toolCall.args);
272
+
273
+ return {
274
+ id: toolCall.id ?? "",
275
+ name: toolCall.name,
276
+ args: parsedArgs,
277
+ } as ParsedToolCallUnion<T>;
278
+ },
279
+
280
+ hasTool(name: string): boolean {
281
+ const tool = toolMap.get(name);
282
+ return tool !== undefined && isEnabled(tool);
283
+ },
284
+
285
+ getToolNames(): ToolNames<T>[] {
286
+ return Array.from(toolMap.entries())
287
+ .filter(([, tool]) => isEnabled(tool))
288
+ .map(([name]) => name) as ToolNames<T>[];
289
+ },
290
+
291
+ getToolDefinitions(): ToolDefinition[] {
292
+ return Array.from(toolMap)
293
+ .filter(([, tool]) => isEnabled(tool))
294
+ .map(([name, tool]) => ({
295
+ name,
296
+ description: tool.description,
297
+ schema: tool.schema,
298
+ strict: tool.strict,
299
+ max_uses: tool.max_uses,
300
+ }));
301
+ },
302
+
303
+ async processToolCalls(
304
+ toolCalls: ParsedToolCallUnion<T>[],
305
+ context?: ProcessToolCallsContext
306
+ ): Promise<ToolCallResultUnion<TResults>[]> {
307
+ if (toolCalls.length === 0) {
308
+ return [];
309
+ }
310
+
311
+ const turn = context?.turn ?? 0;
312
+ const sandboxId = context?.sandboxId;
313
+
314
+ if (options.parallel) {
315
+ const results = await Promise.all(
316
+ toolCalls.map((tc) =>
317
+ processToolCall(tc, turn, sandboxId)
318
+ )
319
+ );
320
+ return results.filter(
321
+ (r): r is NonNullable<typeof r> => r !== null
322
+ ) as ToolCallResultUnion<TResults>[];
323
+ }
324
+
325
+ const results: ToolCallResultUnion<TResults>[] = [];
326
+ for (const toolCall of toolCalls) {
327
+ const result = await processToolCall(
328
+ toolCall,
329
+ turn,
330
+ sandboxId
331
+ );
332
+ if (result !== null) {
333
+ results.push(result);
334
+ }
335
+ }
336
+ return results;
337
+ },
338
+
339
+ async processToolCallsByName<
340
+ TName extends ToolNames<T>,
341
+ TResult,
342
+ >(
343
+ toolCalls: ParsedToolCallUnion<T>[],
344
+ toolName: TName,
345
+ handler: ToolHandler<ToolArgs<T, TName>, TResult>,
346
+ context?: ProcessToolCallsContext
347
+ ): Promise<ToolCallResult<TName, TResult>[]> {
348
+ const matchingCalls = toolCalls.filter((tc) => tc.name === toolName);
349
+
350
+ if (matchingCalls.length === 0) {
351
+ return [];
352
+ }
353
+
354
+ const processOne = async (
355
+ toolCall: ParsedToolCallUnion<T>
356
+ ): Promise<ToolCallResult<TName, TResult>> => {
357
+ const routerContext: RouterContext = {
358
+ threadId: options.threadId,
359
+ toolCallId: toolCall.id,
360
+ toolName: toolCall.name as TName,
361
+ ...(context?.sandboxId !== undefined && { sandboxId: context.sandboxId }),
362
+ };
363
+ const response = await handler(
364
+ toolCall.args as ToolArgs<T, TName>,
365
+ routerContext as Parameters<typeof handler>[1]
366
+ );
367
+
368
+ if (!response.resultAppended) {
369
+ await appendToolResult({
370
+ threadId: options.threadId,
371
+ toolCallId: toolCall.id,
372
+ toolName: toolCall.name,
373
+ content: response.toolResponse,
374
+ });
375
+ }
376
+
377
+ return {
378
+ toolCallId: toolCall.id,
379
+ name: toolCall.name as TName,
380
+ data: response.data,
381
+ };
382
+ };
383
+
384
+ if (options.parallel) {
385
+ return Promise.all(matchingCalls.map(processOne));
386
+ }
387
+
388
+ const results: ToolCallResult<TName, TResult>[] = [];
389
+ for (const toolCall of matchingCalls) {
390
+ results.push(await processOne(toolCall));
391
+ }
392
+ return results;
393
+ },
394
+
395
+ filterByName<TName extends ToolNames<T>>(
396
+ toolCalls: ParsedToolCallUnion<T>[],
397
+ name: TName
398
+ ): ParsedToolCall<TName, ToolArgs<T, TName>>[] {
399
+ return toolCalls.filter(
400
+ (tc): tc is ParsedToolCall<TName, ToolArgs<T, TName>> =>
401
+ tc.name === name
402
+ );
403
+ },
404
+
405
+ hasToolCall(
406
+ toolCalls: ParsedToolCallUnion<T>[],
407
+ name: ToolNames<T>
408
+ ): boolean {
409
+ return toolCalls.some((tc) => tc.name === name);
410
+ },
411
+
412
+ getResultsByName<TName extends ToolNames<T>>(
413
+ results: ToolCallResultUnion<TResults>[],
414
+ name: TName
415
+ ): ToolCallResult<TName, ToolResult<T, TName>>[] {
416
+ return results.filter((r) => r.name === name) as ToolCallResult<
417
+ TName,
418
+ ToolResult<T, TName>
419
+ >[];
420
+ },
421
+ };
422
+ }
423
+
424
+ /**
425
+ * Identity function that creates a generic inference context for a tool definition.
426
+ * TypeScript infers TResult from the handler and flows it to hooks automatically.
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * tools: {
431
+ * AskUser: defineTool({
432
+ * ...askUserTool,
433
+ * handler: handleAskUser,
434
+ * hooks: {
435
+ * onPostToolUse: ({ result }) => {
436
+ * // result is correctly typed as the handler's return data type
437
+ * },
438
+ * },
439
+ * }),
440
+ * }
441
+ * ```
442
+ */
443
+ export function defineTool<
444
+ TName extends string,
445
+ TSchema extends z.ZodType,
446
+ TResult,
447
+ TContext extends RouterContext = RouterContext,
448
+ >(
449
+ tool: ToolWithHandler<TName, TSchema, TResult, TContext>
450
+ ): ToolWithHandler<TName, TSchema, TResult, TContext> {
451
+ return tool;
452
+ }
453
+
454
+ /**
455
+ * Utility to check if there were no tool calls besides a specific one
456
+ */
457
+ export function hasNoOtherToolCalls<T extends ToolMap>(
458
+ toolCalls: ParsedToolCallUnion<T>[],
459
+ excludeName: ToolNames<T>
460
+ ): boolean {
461
+ return toolCalls.filter((tc) => tc.name !== excludeName).length === 0;
462
+ }