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.
- package/README.md +49 -38
- package/dist/adapters/sandbox/daytona/index.cjs +205 -0
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -0
- package/dist/adapters/sandbox/daytona/index.d.cts +86 -0
- package/dist/adapters/sandbox/daytona/index.d.ts +86 -0
- package/dist/adapters/sandbox/daytona/index.js +202 -0
- package/dist/adapters/sandbox/daytona/index.js.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.cjs +174 -0
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -0
- package/dist/adapters/sandbox/inmemory/index.d.cts +28 -0
- package/dist/adapters/sandbox/inmemory/index.d.ts +28 -0
- package/dist/adapters/sandbox/inmemory/index.js +172 -0
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -0
- package/dist/adapters/sandbox/virtual/index.cjs +405 -0
- package/dist/adapters/sandbox/virtual/index.cjs.map +1 -0
- package/dist/adapters/sandbox/virtual/index.d.cts +85 -0
- package/dist/adapters/sandbox/virtual/index.d.ts +85 -0
- package/dist/adapters/sandbox/virtual/index.js +400 -0
- package/dist/adapters/sandbox/virtual/index.js.map +1 -0
- package/dist/adapters/thread/google-genai/index.cjs +284 -0
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -0
- package/dist/adapters/thread/google-genai/index.d.cts +145 -0
- package/dist/adapters/thread/google-genai/index.d.ts +145 -0
- package/dist/adapters/thread/google-genai/index.js +278 -0
- package/dist/adapters/thread/google-genai/index.js.map +1 -0
- package/dist/adapters/{langchain → thread/langchain}/index.cjs +7 -9
- package/dist/adapters/thread/langchain/index.cjs.map +1 -0
- package/dist/adapters/{langchain → thread/langchain}/index.d.cts +17 -21
- package/dist/adapters/{langchain → thread/langchain}/index.d.ts +17 -21
- package/dist/adapters/{langchain → thread/langchain}/index.js +7 -9
- package/dist/adapters/thread/langchain/index.js.map +1 -0
- package/dist/index.cjs +816 -545
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +235 -74
- package/dist/index.d.ts +235 -74
- package/dist/index.js +804 -540
- package/dist/index.js.map +1 -1
- package/dist/types-B4C9txdq.d.ts +389 -0
- package/dist/{thread-manager-qc0g5Rvd.d.cts → types-B9ljZewB.d.cts} +1 -6
- package/dist/{thread-manager-qc0g5Rvd.d.ts → types-B9ljZewB.d.ts} +1 -6
- package/dist/types-BMXzv7TN.d.cts +476 -0
- package/dist/types-BMXzv7TN.d.ts +476 -0
- package/dist/types-BVP87m_W.d.cts +121 -0
- package/dist/types-CDubRtad.d.cts +115 -0
- package/dist/types-CDubRtad.d.ts +115 -0
- package/dist/types-CwwgQ_9H.d.ts +121 -0
- package/dist/types-GpMU4b0w.d.cts +389 -0
- package/dist/workflow.cjs +444 -318
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +271 -222
- package/dist/workflow.d.ts +271 -222
- package/dist/workflow.js +440 -316
- package/dist/workflow.js.map +1 -1
- package/package.json +59 -6
- package/src/adapters/sandbox/daytona/filesystem.ts +136 -0
- package/src/adapters/sandbox/daytona/index.ts +149 -0
- package/src/adapters/sandbox/daytona/types.ts +34 -0
- package/src/adapters/sandbox/inmemory/index.ts +213 -0
- package/src/adapters/sandbox/virtual/filesystem.ts +345 -0
- package/src/adapters/sandbox/virtual/index.ts +88 -0
- package/src/adapters/sandbox/virtual/mutations.ts +38 -0
- package/src/adapters/sandbox/virtual/provider.ts +101 -0
- package/src/adapters/sandbox/virtual/tree.ts +82 -0
- package/src/adapters/sandbox/virtual/types.ts +127 -0
- package/src/adapters/sandbox/virtual/virtual-sandbox.test.ts +523 -0
- package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +91 -0
- package/src/adapters/thread/google-genai/activities.ts +121 -0
- package/src/adapters/thread/google-genai/index.ts +41 -0
- package/src/adapters/thread/google-genai/model-invoker.ts +154 -0
- package/src/adapters/thread/google-genai/thread-manager.ts +169 -0
- package/src/adapters/{langchain → thread/langchain}/activities.ts +11 -15
- package/src/adapters/{langchain → thread/langchain}/index.ts +1 -1
- package/src/adapters/{langchain → thread/langchain}/model-invoker.ts +15 -18
- package/src/adapters/{langchain → thread/langchain}/thread-manager.ts +1 -1
- package/src/index.ts +32 -24
- package/src/lib/activity.ts +87 -0
- package/src/lib/hooks/index.ts +11 -0
- package/src/lib/hooks/types.ts +98 -0
- package/src/lib/model/helpers.ts +6 -0
- package/src/lib/model/index.ts +13 -0
- package/src/lib/{model-invoker.ts → model/types.ts} +18 -1
- package/src/lib/sandbox/index.ts +19 -0
- package/src/lib/sandbox/manager.ts +76 -0
- package/src/lib/sandbox/sandbox.test.ts +158 -0
- package/src/lib/{fs.ts → sandbox/tree.ts} +6 -6
- package/src/lib/sandbox/types.ts +164 -0
- package/src/lib/session/index.ts +11 -0
- package/src/lib/{session.ts → session/session.ts} +76 -48
- package/src/lib/session/types.ts +93 -0
- package/src/lib/skills/fs-provider.ts +16 -15
- package/src/lib/skills/handler.ts +31 -0
- package/src/lib/skills/index.ts +5 -1
- package/src/lib/skills/register.ts +20 -0
- package/src/lib/skills/tool.ts +47 -0
- package/src/lib/state/index.ts +9 -0
- package/src/lib/{state-manager.ts → state/manager.ts} +10 -147
- package/src/lib/state/types.ts +134 -0
- package/src/lib/subagent/define.ts +71 -0
- package/src/lib/subagent/handler.ts +99 -0
- package/src/lib/subagent/index.ts +13 -0
- package/src/lib/subagent/register.ts +53 -0
- package/src/lib/subagent/tool.ts +80 -0
- package/src/lib/subagent/types.ts +92 -0
- package/src/lib/thread/index.ts +7 -0
- package/src/lib/{thread-manager.ts → thread/manager.ts} +1 -33
- package/src/lib/thread/types.ts +33 -0
- package/src/lib/tool-router/auto-append.ts +55 -0
- package/src/lib/tool-router/index.ts +41 -0
- package/src/lib/tool-router/router.ts +462 -0
- package/src/lib/tool-router/types.ts +478 -0
- package/src/lib/tool-router/with-sandbox.ts +70 -0
- package/src/lib/types.ts +5 -382
- package/src/tools/bash/bash.test.ts +53 -55
- package/src/tools/bash/handler.ts +23 -51
- package/src/tools/edit/handler.ts +67 -81
- package/src/tools/glob/handler.ts +60 -17
- package/src/tools/read-file/handler.ts +67 -0
- package/src/tools/read-skill/handler.ts +1 -31
- package/src/tools/read-skill/tool.ts +5 -47
- package/src/tools/subagent/handler.ts +1 -100
- package/src/tools/subagent/tool.ts +5 -93
- package/src/tools/task-create/handler.ts +1 -1
- package/src/tools/task-get/handler.ts +1 -1
- package/src/tools/task-list/handler.ts +1 -1
- package/src/tools/task-update/handler.ts +1 -1
- package/src/tools/write-file/handler.ts +47 -0
- package/src/workflow.ts +88 -47
- package/tsup.config.ts +8 -1
- package/dist/adapters/langchain/index.cjs.map +0 -1
- package/dist/adapters/langchain/index.js.map +0 -1
- package/dist/model-invoker-y_zlyMqu.d.cts +0 -892
- package/dist/model-invoker-y_zlyMqu.d.ts +0 -892
- package/src/lib/tool-router.ts +0 -977
- package/src/lib/workflow-helpers.ts +0 -50
- /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
|
+
}
|