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
package/src/lib/tool-router.ts
DELETED
|
@@ -1,977 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ToolMessageContent,
|
|
3
|
-
Hooks,
|
|
4
|
-
PostToolUseFailureHookResult,
|
|
5
|
-
PreToolUseHookResult,
|
|
6
|
-
SubagentConfig,
|
|
7
|
-
SubagentHooks,
|
|
8
|
-
TokenUsage,
|
|
9
|
-
ToolHooks,
|
|
10
|
-
ToolResultConfig,
|
|
11
|
-
} from "./types";
|
|
12
|
-
import type { Skill } from "./skills/types";
|
|
13
|
-
import type { SubagentArgs } from "../tools/subagent/tool";
|
|
14
|
-
|
|
15
|
-
import type { z } from "zod";
|
|
16
|
-
import { createSubagentTool, SUBAGENT_TOOL_NAME } from "../tools/subagent/tool";
|
|
17
|
-
import { createSubagentHandler } from "../tools/subagent/handler";
|
|
18
|
-
import {
|
|
19
|
-
createReadSkillTool,
|
|
20
|
-
READ_SKILL_TOOL_NAME,
|
|
21
|
-
} from "../tools/read-skill/tool";
|
|
22
|
-
import { createReadSkillHandler } from "../tools/read-skill/handler";
|
|
23
|
-
import { ApplicationFailure } from "@temporalio/workflow";
|
|
24
|
-
|
|
25
|
-
// ============================================================================
|
|
26
|
-
// Tool Definition Types (merged from tool-registry.ts)
|
|
27
|
-
// ============================================================================
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* A tool definition with a name, description, and Zod schema for arguments.
|
|
31
|
-
* Does not include a handler - use ToolWithHandler for tools with handlers.
|
|
32
|
-
*/
|
|
33
|
-
export interface ToolDefinition<
|
|
34
|
-
TName extends string = string,
|
|
35
|
-
TSchema extends z.ZodType = z.ZodType,
|
|
36
|
-
> {
|
|
37
|
-
name: TName;
|
|
38
|
-
description: string;
|
|
39
|
-
schema: TSchema;
|
|
40
|
-
strict?: boolean;
|
|
41
|
-
max_uses?: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* A tool definition with an integrated handler function.
|
|
46
|
-
* This is the primary type for defining tools in the router.
|
|
47
|
-
*/
|
|
48
|
-
export interface ToolWithHandler<
|
|
49
|
-
TName extends string = string,
|
|
50
|
-
TSchema extends z.ZodType = z.ZodType,
|
|
51
|
-
TResult = unknown,
|
|
52
|
-
TContext = ToolHandlerContext,
|
|
53
|
-
> {
|
|
54
|
-
name: TName;
|
|
55
|
-
description: string;
|
|
56
|
-
schema: TSchema;
|
|
57
|
-
handler: ToolHandler<z.infer<TSchema>, TResult, TContext>;
|
|
58
|
-
strict?: boolean;
|
|
59
|
-
max_uses?: number;
|
|
60
|
-
/** Whether this tool is available to the agent (default: true). Disabled tools are excluded from definitions and rejected at parse time. */
|
|
61
|
-
enabled?: boolean;
|
|
62
|
-
/** Per-tool lifecycle hooks (run in addition to global hooks) */
|
|
63
|
-
hooks?: ToolHooks<z.infer<TSchema>, TResult>;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* A map of tool keys to tool definitions with handlers.
|
|
68
|
-
*
|
|
69
|
-
* Handler uses `any` intentionally — this is a type-system boundary where heterogeneous
|
|
70
|
-
* tool types are stored together. Type safety for individual tools is enforced by
|
|
71
|
-
* `defineTool()` at the definition site and generic inference utilities like
|
|
72
|
-
* `InferToolResults<T>` at the consumption site.
|
|
73
|
-
*/
|
|
74
|
-
export type ToolMap = Record<
|
|
75
|
-
string,
|
|
76
|
-
{
|
|
77
|
-
name: string;
|
|
78
|
-
description: string;
|
|
79
|
-
schema: z.ZodType;
|
|
80
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
81
|
-
handler: ToolHandler<any, any, any>;
|
|
82
|
-
strict?: boolean;
|
|
83
|
-
max_uses?: number;
|
|
84
|
-
enabled?: boolean;
|
|
85
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
-
hooks?: ToolHooks<any, any>;
|
|
87
|
-
}
|
|
88
|
-
>;
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Extract the tool names from a tool map (uses the tool's name property, not the key).
|
|
92
|
-
*/
|
|
93
|
-
export type ToolNames<T extends ToolMap> = T[keyof T]["name"];
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* A raw tool call as received from the LLM before parsing.
|
|
97
|
-
*/
|
|
98
|
-
export interface RawToolCall {
|
|
99
|
-
id?: string;
|
|
100
|
-
name: string;
|
|
101
|
-
args: unknown;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* A parsed tool call with validated arguments for a specific tool.
|
|
106
|
-
*/
|
|
107
|
-
export interface ParsedToolCall<
|
|
108
|
-
TName extends string = string,
|
|
109
|
-
TArgs = unknown,
|
|
110
|
-
> {
|
|
111
|
-
id: string;
|
|
112
|
-
name: TName;
|
|
113
|
-
args: TArgs;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Union type of all possible parsed tool calls from a tool map.
|
|
118
|
-
*/
|
|
119
|
-
export type ParsedToolCallUnion<T extends ToolMap> = {
|
|
120
|
-
[K in keyof T]: ParsedToolCall<T[K]["name"], z.infer<T[K]["schema"]>>;
|
|
121
|
-
}[keyof T];
|
|
122
|
-
|
|
123
|
-
// ============================================================================
|
|
124
|
-
// Handler Types
|
|
125
|
-
// ============================================================================
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Function signature for appending tool results to a thread.
|
|
129
|
-
*/
|
|
130
|
-
export type AppendToolResultFn = (config: ToolResultConfig) => Promise<void>;
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* The response from a tool handler.
|
|
134
|
-
* Contains the content for the tool message and the result to return from processToolCalls.
|
|
135
|
-
*
|
|
136
|
-
* Tools that don't return additional data should use `data: null` (TResult defaults to null).
|
|
137
|
-
* Tools that may fail to produce data should type TResult as `SomeType | null`.
|
|
138
|
-
*/
|
|
139
|
-
export interface ToolHandlerResponse<TResult = null> {
|
|
140
|
-
/** Content sent back to the LLM as the tool call response */
|
|
141
|
-
toolResponse: ToolMessageContent;
|
|
142
|
-
/** Data returned to the workflow and hooks for further processing */
|
|
143
|
-
data: TResult;
|
|
144
|
-
/**
|
|
145
|
-
* When true, the tool result has already been appended to the thread
|
|
146
|
-
* by the handler itself (e.g. via `withAutoAppend`), so the router
|
|
147
|
-
* will skip the `appendToolResult` call. This avoids sending large
|
|
148
|
-
* payloads through Temporal's activity payload limit.
|
|
149
|
-
*/
|
|
150
|
-
resultAppended?: boolean;
|
|
151
|
-
/** Token usage from the tool execution (e.g. child agent invocations) */
|
|
152
|
-
usage?: TokenUsage;
|
|
153
|
-
/** Thread ID used by the handler (surfaced to the LLM for subagent thread continuation) */
|
|
154
|
-
threadId?: string;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Context passed to tool handlers for additional data beyond tool args.
|
|
159
|
-
* Use this to pass workflow state like file trees, user context, etc.
|
|
160
|
-
* Generic so callers can type the context shape, e.g. ToolHandlerContext<ControlTestFsParams>.
|
|
161
|
-
*/
|
|
162
|
-
export type ToolHandlerContext<T = Record<string, unknown>> = T;
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* A handler function for a specific tool.
|
|
166
|
-
* Receives the parsed args and context, returns a response with content and result.
|
|
167
|
-
* Context always has a value (defaults to empty object if not provided).
|
|
168
|
-
*/
|
|
169
|
-
export type ToolHandler<TArgs, TResult, TContext = ToolHandlerContext> = (
|
|
170
|
-
args: TArgs,
|
|
171
|
-
context: TContext
|
|
172
|
-
) => ToolHandlerResponse<TResult> | Promise<ToolHandlerResponse<TResult>>;
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Activity-compatible tool handler that always returns a Promise.
|
|
176
|
-
* Use this for tool handlers registered as Temporal activities.
|
|
177
|
-
* Context always has a value (defaults to empty object if not provided).
|
|
178
|
-
*
|
|
179
|
-
* @example
|
|
180
|
-
* ```typescript
|
|
181
|
-
* // Filesystem handler with context
|
|
182
|
-
* const readHandler: ActivityToolHandler<
|
|
183
|
-
* FileReadArgs,
|
|
184
|
-
* ReadResult,
|
|
185
|
-
* { scopedNodes: FileNode[]; provider: FileSystemProvider }
|
|
186
|
-
* > = async (args, context) => {
|
|
187
|
-
* return readHandler(args, context.scopedNodes, context.provider);
|
|
188
|
-
* };
|
|
189
|
-
* ```
|
|
190
|
-
*/
|
|
191
|
-
export type ActivityToolHandler<
|
|
192
|
-
TArgs,
|
|
193
|
-
TResult,
|
|
194
|
-
TContext = ToolHandlerContext,
|
|
195
|
-
> = (args: TArgs, context: TContext) => Promise<ToolHandlerResponse<TResult>>;
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Extract the args type for a specific tool name from a tool map.
|
|
199
|
-
*/
|
|
200
|
-
export type ToolArgs<T extends ToolMap, TName extends ToolNames<T>> = z.infer<
|
|
201
|
-
Extract<T[keyof T], { name: TName }>["schema"]
|
|
202
|
-
>;
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Extract the result type for a specific tool name from a tool map.
|
|
206
|
-
*/
|
|
207
|
-
export type ToolResult<T extends ToolMap, TName extends ToolNames<T>> =
|
|
208
|
-
Extract<T[keyof T], { name: TName }>["handler"] extends ToolHandler<
|
|
209
|
-
unknown,
|
|
210
|
-
infer R,
|
|
211
|
-
unknown
|
|
212
|
-
>
|
|
213
|
-
? Awaited<R>
|
|
214
|
-
: never;
|
|
215
|
-
|
|
216
|
-
// ============================================================================
|
|
217
|
-
// Tool Call Result Types
|
|
218
|
-
// ============================================================================
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* The result of processing a tool call.
|
|
222
|
-
*/
|
|
223
|
-
export interface ToolCallResult<
|
|
224
|
-
TName extends string = string,
|
|
225
|
-
TResult = unknown,
|
|
226
|
-
> {
|
|
227
|
-
toolCallId: string;
|
|
228
|
-
name: TName;
|
|
229
|
-
data: TResult;
|
|
230
|
-
usage?: TokenUsage;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Options for creating a tool router.
|
|
235
|
-
*/
|
|
236
|
-
export interface ToolRouterOptions<T extends ToolMap> {
|
|
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
|
-
/** Skills available to the agent (auto-adds ReadSkill tool when non-empty) */
|
|
250
|
-
skills?: Skill[];
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Infer result types from a tool map based on handler return types.
|
|
255
|
-
*/
|
|
256
|
-
export type InferToolResults<T extends ToolMap> = {
|
|
257
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
258
|
-
[K in keyof T as T[K]["name"]]: T[K]["handler"] extends ToolHandler<
|
|
259
|
-
any,
|
|
260
|
-
infer R,
|
|
261
|
-
any
|
|
262
|
-
>
|
|
263
|
-
? /* eslint-enable @typescript-eslint/no-explicit-any */
|
|
264
|
-
Awaited<R>
|
|
265
|
-
: never;
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Union of all possible tool call results based on handler return types.
|
|
270
|
-
*/
|
|
271
|
-
export type ToolCallResultUnion<TResults extends Record<string, unknown>> = {
|
|
272
|
-
[TName in keyof TResults & string]: ToolCallResult<TName, TResults[TName]>;
|
|
273
|
-
}[keyof TResults & string];
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Context passed to processToolCalls for hook execution and handler invocation
|
|
277
|
-
*/
|
|
278
|
-
export interface ProcessToolCallsContext<THandlerContext = ToolHandlerContext> {
|
|
279
|
-
/** Current turn number (for hooks) */
|
|
280
|
-
turn?: number;
|
|
281
|
-
/** Context passed to each tool handler (scopedNodes, provider, etc.) */
|
|
282
|
-
handlerContext?: THandlerContext;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ============================================================================
|
|
286
|
-
// Router Interface
|
|
287
|
-
// ============================================================================
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* The tool router interface with full type inference for both args and results.
|
|
291
|
-
*/
|
|
292
|
-
export interface ToolRouter<T extends ToolMap> {
|
|
293
|
-
/** Check if the router has any tools */
|
|
294
|
-
hasTools(): boolean;
|
|
295
|
-
// --- Methods from registry ---
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Parse and validate a raw tool call against the router's tools.
|
|
299
|
-
* Returns a typed tool call with validated arguments.
|
|
300
|
-
*/
|
|
301
|
-
parseToolCall(toolCall: RawToolCall): ParsedToolCallUnion<T>;
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Check if a tool with the given name exists in the router.
|
|
305
|
-
*/
|
|
306
|
-
hasTool(name: string): boolean;
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Get all tool names in the router.
|
|
310
|
-
*/
|
|
311
|
-
getToolNames(): ToolNames<T>[];
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Get all tool definitions (without handlers) for passing to LLM.
|
|
315
|
-
*/
|
|
316
|
-
getToolDefinitions(): ToolDefinition[];
|
|
317
|
-
|
|
318
|
-
// --- Methods for processing tool calls ---
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Process all tool calls using the registered handlers.
|
|
322
|
-
* Returns typed results based on handler return types.
|
|
323
|
-
* @param toolCalls - Array of parsed tool calls to process
|
|
324
|
-
* @param context - Optional context including turn number for hooks
|
|
325
|
-
*/
|
|
326
|
-
processToolCalls(
|
|
327
|
-
toolCalls: ParsedToolCallUnion<T>[],
|
|
328
|
-
context?: ProcessToolCallsContext
|
|
329
|
-
): Promise<ToolCallResultUnion<InferToolResults<T>>[]>;
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Process tool calls matching a specific name with a custom handler.
|
|
333
|
-
* Useful for overriding the default handler for specific cases.
|
|
334
|
-
*/
|
|
335
|
-
processToolCallsByName<
|
|
336
|
-
TName extends ToolNames<T>,
|
|
337
|
-
TResult,
|
|
338
|
-
TContext = ToolHandlerContext,
|
|
339
|
-
>(
|
|
340
|
-
toolCalls: ParsedToolCallUnion<T>[],
|
|
341
|
-
toolName: TName,
|
|
342
|
-
handler: ToolHandler<ToolArgs<T, TName>, TResult, TContext>,
|
|
343
|
-
context?: ProcessToolCallsContext<TContext>
|
|
344
|
-
): Promise<ToolCallResult<TName, TResult>[]>;
|
|
345
|
-
|
|
346
|
-
// --- Utility methods ---
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Filter tool calls by name.
|
|
350
|
-
*/
|
|
351
|
-
filterByName<TName extends ToolNames<T>>(
|
|
352
|
-
toolCalls: ParsedToolCallUnion<T>[],
|
|
353
|
-
name: TName
|
|
354
|
-
): ParsedToolCall<TName, ToolArgs<T, TName>>[];
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Check if any tool call matches the given name.
|
|
358
|
-
*/
|
|
359
|
-
hasToolCall(toolCalls: ParsedToolCallUnion<T>[], name: ToolNames<T>): boolean;
|
|
360
|
-
|
|
361
|
-
/**
|
|
362
|
-
* Filter results by tool name.
|
|
363
|
-
*/
|
|
364
|
-
getResultsByName<TName extends ToolNames<T>>(
|
|
365
|
-
results: ToolCallResultUnion<InferToolResults<T>>[],
|
|
366
|
-
name: TName
|
|
367
|
-
): ToolCallResult<TName, ToolResult<T, TName>>[];
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// ============================================================================
|
|
371
|
-
// Router Factory
|
|
372
|
-
// ============================================================================
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Creates a tool router for declarative tool call processing.
|
|
376
|
-
* Combines tool definitions with handlers in a single API.
|
|
377
|
-
*
|
|
378
|
-
* @example
|
|
379
|
-
* ```typescript
|
|
380
|
-
* const router = createToolRouter({
|
|
381
|
-
* threadId,
|
|
382
|
-
* tools: {
|
|
383
|
-
* Read: {
|
|
384
|
-
* name: "FileRead",
|
|
385
|
-
* description: "Read file contents",
|
|
386
|
-
* schema: z.object({ path: z.string() }),
|
|
387
|
-
* handler: async (args, ctx) => ({
|
|
388
|
-
* content: `Read ${args.path}`,
|
|
389
|
-
* result: { path: args.path, content: "..." },
|
|
390
|
-
* }),
|
|
391
|
-
* },
|
|
392
|
-
* },
|
|
393
|
-
* hooks: { onPreToolUse, onPostToolUse },
|
|
394
|
-
* });
|
|
395
|
-
*
|
|
396
|
-
* // Parse raw tool calls from LLM
|
|
397
|
-
* const parsed = router.parseToolCall(rawToolCall);
|
|
398
|
-
*
|
|
399
|
-
* // Process tool calls
|
|
400
|
-
* const results = await router.processToolCalls([parsed]);
|
|
401
|
-
* ```
|
|
402
|
-
*/
|
|
403
|
-
export function createToolRouter<T extends ToolMap>(
|
|
404
|
-
options: ToolRouterOptions<T>
|
|
405
|
-
): ToolRouter<T> {
|
|
406
|
-
const { appendToolResult } = options;
|
|
407
|
-
type TResults = InferToolResults<T>;
|
|
408
|
-
|
|
409
|
-
// Build internal lookup map by tool name
|
|
410
|
-
// Use ToolMap's value type to allow both user tools and the dynamic Task tool
|
|
411
|
-
const toolMap = new Map<string, ToolMap[string]>();
|
|
412
|
-
for (const [_key, tool] of Object.entries(options.tools)) {
|
|
413
|
-
toolMap.set(tool.name, tool as T[keyof T]);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
/** Check if a tool is enabled (defaults to true when not specified) */
|
|
417
|
-
const isEnabled = (tool: ToolMap[string] | SubagentConfig): boolean =>
|
|
418
|
-
tool.enabled ?? true;
|
|
419
|
-
|
|
420
|
-
if (options.subagents) {
|
|
421
|
-
if (options.subagents.length > 0) {
|
|
422
|
-
// Build per-subagent hook dispatcher keyed by subagent name
|
|
423
|
-
const subagentHooksMap = new Map<string, SubagentHooks>();
|
|
424
|
-
for (const s of options.subagents) {
|
|
425
|
-
if (s.hooks) subagentHooksMap.set(s.agentName, s.hooks);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const resolveSubagentName = (args: unknown): string =>
|
|
429
|
-
(args as SubagentArgs).subagent;
|
|
430
|
-
|
|
431
|
-
toolMap.set(SUBAGENT_TOOL_NAME, {
|
|
432
|
-
...createSubagentTool(options.subagents),
|
|
433
|
-
handler: createSubagentHandler(options.subagents),
|
|
434
|
-
...(subagentHooksMap.size > 0 && {
|
|
435
|
-
hooks: {
|
|
436
|
-
onPreToolUse: async (ctx): Promise<PreToolUseHookResult> => {
|
|
437
|
-
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
438
|
-
return hooks?.onPreExecution?.(ctx) ?? {};
|
|
439
|
-
},
|
|
440
|
-
onPostToolUse: async (ctx): Promise<void> => {
|
|
441
|
-
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
442
|
-
await hooks?.onPostExecution?.(ctx);
|
|
443
|
-
},
|
|
444
|
-
onPostToolUseFailure: async (
|
|
445
|
-
ctx
|
|
446
|
-
): Promise<PostToolUseFailureHookResult> => {
|
|
447
|
-
const hooks = subagentHooksMap.get(resolveSubagentName(ctx.args));
|
|
448
|
-
return hooks?.onExecutionFailure?.(ctx) ?? {};
|
|
449
|
-
},
|
|
450
|
-
} satisfies ToolHooks,
|
|
451
|
-
}),
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (options.skills && options.skills.length > 0) {
|
|
457
|
-
toolMap.set(READ_SKILL_TOOL_NAME, {
|
|
458
|
-
...createReadSkillTool(options.skills),
|
|
459
|
-
handler: createReadSkillHandler(options.skills),
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
async function processToolCall(
|
|
464
|
-
toolCall: ParsedToolCallUnion<T>,
|
|
465
|
-
turn: number,
|
|
466
|
-
handlerContext?: ToolHandlerContext
|
|
467
|
-
): Promise<ToolCallResultUnion<TResults> | null> {
|
|
468
|
-
const startTime = Date.now();
|
|
469
|
-
const tool = toolMap.get(toolCall.name);
|
|
470
|
-
const toolHooks = tool?.hooks;
|
|
471
|
-
|
|
472
|
-
// --- PreToolUse: global then per-tool ---
|
|
473
|
-
let effectiveArgs: unknown = toolCall.args;
|
|
474
|
-
if (options.hooks?.onPreToolUse) {
|
|
475
|
-
const preResult = await options.hooks.onPreToolUse({
|
|
476
|
-
toolCall,
|
|
477
|
-
threadId: options.threadId,
|
|
478
|
-
turn,
|
|
479
|
-
});
|
|
480
|
-
if (preResult?.skip) {
|
|
481
|
-
await appendToolResult({
|
|
482
|
-
threadId: options.threadId,
|
|
483
|
-
toolCallId: toolCall.id,
|
|
484
|
-
toolName: toolCall.name,
|
|
485
|
-
content: JSON.stringify({
|
|
486
|
-
skipped: true,
|
|
487
|
-
reason: "Skipped by PreToolUse hook",
|
|
488
|
-
}),
|
|
489
|
-
});
|
|
490
|
-
return null;
|
|
491
|
-
}
|
|
492
|
-
if (preResult?.modifiedArgs !== undefined) {
|
|
493
|
-
effectiveArgs = preResult.modifiedArgs;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
if (toolHooks?.onPreToolUse) {
|
|
497
|
-
const preResult = await toolHooks.onPreToolUse({
|
|
498
|
-
args: effectiveArgs,
|
|
499
|
-
threadId: options.threadId,
|
|
500
|
-
turn,
|
|
501
|
-
});
|
|
502
|
-
if (preResult?.skip) {
|
|
503
|
-
await appendToolResult({
|
|
504
|
-
threadId: options.threadId,
|
|
505
|
-
toolCallId: toolCall.id,
|
|
506
|
-
toolName: toolCall.name,
|
|
507
|
-
content: JSON.stringify({
|
|
508
|
-
skipped: true,
|
|
509
|
-
reason: "Skipped by tool PreToolUse hook",
|
|
510
|
-
}),
|
|
511
|
-
});
|
|
512
|
-
return null;
|
|
513
|
-
}
|
|
514
|
-
if (preResult?.modifiedArgs !== undefined) {
|
|
515
|
-
effectiveArgs = preResult.modifiedArgs;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// --- Execute handler ---
|
|
520
|
-
let result: unknown;
|
|
521
|
-
let content!: ToolMessageContent;
|
|
522
|
-
let resultAppended = false;
|
|
523
|
-
|
|
524
|
-
try {
|
|
525
|
-
if (tool) {
|
|
526
|
-
const enrichedContext = {
|
|
527
|
-
...(handlerContext ?? {}),
|
|
528
|
-
threadId: options.threadId,
|
|
529
|
-
toolCallId: toolCall.id,
|
|
530
|
-
toolName: toolCall.name,
|
|
531
|
-
};
|
|
532
|
-
const response = await tool.handler(
|
|
533
|
-
effectiveArgs as Parameters<typeof tool.handler>[0],
|
|
534
|
-
enrichedContext as Parameters<typeof tool.handler>[1]
|
|
535
|
-
);
|
|
536
|
-
result = response.data;
|
|
537
|
-
content = response.toolResponse;
|
|
538
|
-
resultAppended = response.resultAppended === true;
|
|
539
|
-
} else {
|
|
540
|
-
result = { error: `Unknown tool: ${toolCall.name}` };
|
|
541
|
-
content = JSON.stringify(result, null, 2);
|
|
542
|
-
}
|
|
543
|
-
} catch (error) {
|
|
544
|
-
// --- PostToolUseFailure: per-tool then global ---
|
|
545
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
546
|
-
let recovered = false;
|
|
547
|
-
|
|
548
|
-
if (toolHooks?.onPostToolUseFailure) {
|
|
549
|
-
const failureResult = await toolHooks.onPostToolUseFailure({
|
|
550
|
-
args: effectiveArgs,
|
|
551
|
-
error: err,
|
|
552
|
-
threadId: options.threadId,
|
|
553
|
-
turn,
|
|
554
|
-
});
|
|
555
|
-
if (failureResult?.fallbackContent !== undefined) {
|
|
556
|
-
content = failureResult.fallbackContent;
|
|
557
|
-
result = { error: String(error), recovered: true };
|
|
558
|
-
recovered = true;
|
|
559
|
-
} else if (failureResult?.suppress) {
|
|
560
|
-
content = JSON.stringify({ error: String(error), suppressed: true });
|
|
561
|
-
result = { error: String(error), suppressed: true };
|
|
562
|
-
recovered = true;
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
if (!recovered && options.hooks?.onPostToolUseFailure) {
|
|
567
|
-
const failureResult = await options.hooks.onPostToolUseFailure({
|
|
568
|
-
toolCall,
|
|
569
|
-
error: err,
|
|
570
|
-
threadId: options.threadId,
|
|
571
|
-
turn,
|
|
572
|
-
});
|
|
573
|
-
if (failureResult?.fallbackContent !== undefined) {
|
|
574
|
-
content = failureResult.fallbackContent;
|
|
575
|
-
result = { error: String(error), recovered: true };
|
|
576
|
-
recovered = true;
|
|
577
|
-
} else if (failureResult?.suppress) {
|
|
578
|
-
content = JSON.stringify({ error: String(error), suppressed: true });
|
|
579
|
-
result = { error: String(error), suppressed: true };
|
|
580
|
-
recovered = true;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
if (!recovered) {
|
|
585
|
-
throw ApplicationFailure.fromError(error, {
|
|
586
|
-
nonRetryable: true,
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// Automatically append tool result to thread (unless handler already did)
|
|
592
|
-
if (!resultAppended) {
|
|
593
|
-
await appendToolResult({
|
|
594
|
-
threadId: options.threadId,
|
|
595
|
-
toolCallId: toolCall.id,
|
|
596
|
-
toolName: toolCall.name,
|
|
597
|
-
content,
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
const toolResult = {
|
|
602
|
-
toolCallId: toolCall.id,
|
|
603
|
-
name: toolCall.name,
|
|
604
|
-
data: result,
|
|
605
|
-
} as ToolCallResultUnion<TResults>;
|
|
606
|
-
|
|
607
|
-
// --- PostToolUse: per-tool then global ---
|
|
608
|
-
const durationMs = Date.now() - startTime;
|
|
609
|
-
if (toolHooks?.onPostToolUse) {
|
|
610
|
-
await toolHooks.onPostToolUse({
|
|
611
|
-
args: effectiveArgs,
|
|
612
|
-
result: result,
|
|
613
|
-
threadId: options.threadId,
|
|
614
|
-
turn,
|
|
615
|
-
durationMs,
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
if (options.hooks?.onPostToolUse) {
|
|
619
|
-
await options.hooks.onPostToolUse({
|
|
620
|
-
toolCall,
|
|
621
|
-
result: toolResult,
|
|
622
|
-
threadId: options.threadId,
|
|
623
|
-
turn,
|
|
624
|
-
durationMs,
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
return toolResult;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
return {
|
|
632
|
-
// --- Methods from registry ---
|
|
633
|
-
|
|
634
|
-
hasTools(): boolean {
|
|
635
|
-
return Array.from(toolMap.values()).some(isEnabled);
|
|
636
|
-
},
|
|
637
|
-
|
|
638
|
-
parseToolCall(toolCall: RawToolCall): ParsedToolCallUnion<T> {
|
|
639
|
-
const tool = toolMap.get(toolCall.name);
|
|
640
|
-
|
|
641
|
-
if (!tool || !isEnabled(tool)) {
|
|
642
|
-
throw new Error(`Tool ${toolCall.name} not found`);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
// Parse and validate args using the tool's schema
|
|
646
|
-
const parsedArgs = tool.schema.parse(toolCall.args);
|
|
647
|
-
|
|
648
|
-
return {
|
|
649
|
-
id: toolCall.id ?? "",
|
|
650
|
-
name: toolCall.name,
|
|
651
|
-
args: parsedArgs,
|
|
652
|
-
} as ParsedToolCallUnion<T>;
|
|
653
|
-
},
|
|
654
|
-
|
|
655
|
-
hasTool(name: string): boolean {
|
|
656
|
-
const tool = toolMap.get(name);
|
|
657
|
-
return tool !== undefined && isEnabled(tool);
|
|
658
|
-
},
|
|
659
|
-
|
|
660
|
-
getToolNames(): ToolNames<T>[] {
|
|
661
|
-
return Array.from(toolMap.entries())
|
|
662
|
-
.filter(([, tool]) => isEnabled(tool))
|
|
663
|
-
.map(([name]) => name) as ToolNames<T>[];
|
|
664
|
-
},
|
|
665
|
-
|
|
666
|
-
getToolDefinitions(): ToolDefinition[] {
|
|
667
|
-
const activeSubagents =
|
|
668
|
-
options.subagents?.filter((subagent) => isEnabled(subagent)) ?? [];
|
|
669
|
-
const activeSkills = options.skills ?? [];
|
|
670
|
-
|
|
671
|
-
return [
|
|
672
|
-
...Array.from(toolMap)
|
|
673
|
-
.filter(
|
|
674
|
-
([, tool]) =>
|
|
675
|
-
isEnabled(tool) &&
|
|
676
|
-
tool.name !== SUBAGENT_TOOL_NAME &&
|
|
677
|
-
tool.name !== READ_SKILL_TOOL_NAME
|
|
678
|
-
)
|
|
679
|
-
.map(([name, tool]) => ({
|
|
680
|
-
name,
|
|
681
|
-
description: tool.description,
|
|
682
|
-
schema: tool.schema,
|
|
683
|
-
strict: tool.strict,
|
|
684
|
-
max_uses: tool.max_uses,
|
|
685
|
-
})),
|
|
686
|
-
...(activeSubagents.length > 0
|
|
687
|
-
? [createSubagentTool(activeSubagents)]
|
|
688
|
-
: []),
|
|
689
|
-
...(activeSkills.length > 0 ? [createReadSkillTool(activeSkills)] : []),
|
|
690
|
-
];
|
|
691
|
-
},
|
|
692
|
-
|
|
693
|
-
// --- Methods for processing tool calls ---
|
|
694
|
-
|
|
695
|
-
async processToolCalls(
|
|
696
|
-
toolCalls: ParsedToolCallUnion<T>[],
|
|
697
|
-
context?: ProcessToolCallsContext
|
|
698
|
-
): Promise<ToolCallResultUnion<TResults>[]> {
|
|
699
|
-
if (toolCalls.length === 0) {
|
|
700
|
-
return [];
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
const turn = context?.turn ?? 0;
|
|
704
|
-
const handlerContext = context?.handlerContext;
|
|
705
|
-
|
|
706
|
-
if (options.parallel) {
|
|
707
|
-
const results = await Promise.all(
|
|
708
|
-
toolCalls.map((tc) => processToolCall(tc, turn, handlerContext))
|
|
709
|
-
);
|
|
710
|
-
// Filter out null results (skipped tool calls)
|
|
711
|
-
return results.filter(
|
|
712
|
-
(r): r is NonNullable<typeof r> => r !== null
|
|
713
|
-
) as ToolCallResultUnion<TResults>[];
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// Sequential processing
|
|
717
|
-
const results: ToolCallResultUnion<TResults>[] = [];
|
|
718
|
-
for (const toolCall of toolCalls) {
|
|
719
|
-
const result = await processToolCall(toolCall, turn, handlerContext);
|
|
720
|
-
if (result !== null) {
|
|
721
|
-
results.push(result);
|
|
722
|
-
}
|
|
723
|
-
}
|
|
724
|
-
return results;
|
|
725
|
-
},
|
|
726
|
-
|
|
727
|
-
async processToolCallsByName<
|
|
728
|
-
TName extends ToolNames<T>,
|
|
729
|
-
TResult,
|
|
730
|
-
TContext = ToolHandlerContext,
|
|
731
|
-
>(
|
|
732
|
-
toolCalls: ParsedToolCallUnion<T>[],
|
|
733
|
-
toolName: TName,
|
|
734
|
-
handler: ToolHandler<ToolArgs<T, TName>, TResult, TContext>,
|
|
735
|
-
context?: ProcessToolCallsContext<TContext>
|
|
736
|
-
): Promise<ToolCallResult<TName, TResult>[]> {
|
|
737
|
-
const matchingCalls = toolCalls.filter((tc) => tc.name === toolName);
|
|
738
|
-
|
|
739
|
-
if (matchingCalls.length === 0) {
|
|
740
|
-
return [];
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
const handlerContext = (context?.handlerContext ?? {}) as TContext;
|
|
744
|
-
|
|
745
|
-
const processOne = async (
|
|
746
|
-
toolCall: ParsedToolCallUnion<T>
|
|
747
|
-
): Promise<ToolCallResult<TName, TResult>> => {
|
|
748
|
-
const enrichedContext = {
|
|
749
|
-
...(handlerContext ?? {}),
|
|
750
|
-
threadId: options.threadId,
|
|
751
|
-
toolCallId: toolCall.id,
|
|
752
|
-
toolName: toolCall.name as TName,
|
|
753
|
-
} as TContext;
|
|
754
|
-
const response = await handler(
|
|
755
|
-
toolCall.args as ToolArgs<T, TName>,
|
|
756
|
-
enrichedContext
|
|
757
|
-
);
|
|
758
|
-
|
|
759
|
-
// Automatically append tool result to thread (unless handler already did)
|
|
760
|
-
if (!response.resultAppended) {
|
|
761
|
-
await appendToolResult({
|
|
762
|
-
threadId: options.threadId,
|
|
763
|
-
toolCallId: toolCall.id,
|
|
764
|
-
toolName: toolCall.name,
|
|
765
|
-
content: response.toolResponse,
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
return {
|
|
770
|
-
toolCallId: toolCall.id,
|
|
771
|
-
name: toolCall.name as TName,
|
|
772
|
-
data: response.data,
|
|
773
|
-
};
|
|
774
|
-
};
|
|
775
|
-
|
|
776
|
-
if (options.parallel) {
|
|
777
|
-
return Promise.all(matchingCalls.map(processOne));
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
const results: ToolCallResult<TName, TResult>[] = [];
|
|
781
|
-
for (const toolCall of matchingCalls) {
|
|
782
|
-
results.push(await processOne(toolCall));
|
|
783
|
-
}
|
|
784
|
-
return results;
|
|
785
|
-
},
|
|
786
|
-
|
|
787
|
-
// --- Utility methods ---
|
|
788
|
-
|
|
789
|
-
filterByName<TName extends ToolNames<T>>(
|
|
790
|
-
toolCalls: ParsedToolCallUnion<T>[],
|
|
791
|
-
name: TName
|
|
792
|
-
): ParsedToolCall<TName, ToolArgs<T, TName>>[] {
|
|
793
|
-
return toolCalls.filter(
|
|
794
|
-
(tc): tc is ParsedToolCall<TName, ToolArgs<T, TName>> =>
|
|
795
|
-
tc.name === name
|
|
796
|
-
);
|
|
797
|
-
},
|
|
798
|
-
|
|
799
|
-
hasToolCall(
|
|
800
|
-
toolCalls: ParsedToolCallUnion<T>[],
|
|
801
|
-
name: ToolNames<T>
|
|
802
|
-
): boolean {
|
|
803
|
-
return toolCalls.some((tc) => tc.name === name);
|
|
804
|
-
},
|
|
805
|
-
|
|
806
|
-
getResultsByName<TName extends ToolNames<T>>(
|
|
807
|
-
results: ToolCallResultUnion<TResults>[],
|
|
808
|
-
name: TName
|
|
809
|
-
): ToolCallResult<TName, ToolResult<T, TName>>[] {
|
|
810
|
-
return results.filter((r) => r.name === name) as ToolCallResult<
|
|
811
|
-
TName,
|
|
812
|
-
ToolResult<T, TName>
|
|
813
|
-
>[];
|
|
814
|
-
},
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
/**
|
|
819
|
-
* Wraps a tool handler to automatically append its result directly to the
|
|
820
|
-
* thread and sets `resultAppended: true` on the response.
|
|
821
|
-
*
|
|
822
|
-
* Use this for tools whose responses may exceed Temporal's activity payload
|
|
823
|
-
* limit. The wrapper appends to the thread inside the activity (where Redis
|
|
824
|
-
* is available), then replaces `toolResponse` with an empty string so the
|
|
825
|
-
* large payload never travels through the Temporal workflow boundary.
|
|
826
|
-
*
|
|
827
|
-
* @param getThread - Factory that returns a thread manager for the given threadId
|
|
828
|
-
* @param handler - The original tool handler
|
|
829
|
-
* @returns A wrapped handler that auto-appends and flags the response
|
|
830
|
-
*
|
|
831
|
-
* @example
|
|
832
|
-
* ```typescript
|
|
833
|
-
* import { withAutoAppend } from '@bead-ai/zeitlich/workflow';
|
|
834
|
-
* import { createThreadManager } from '@bead-ai/zeitlich';
|
|
835
|
-
*
|
|
836
|
-
* const handler = withAutoAppend(
|
|
837
|
-
* (threadId) => createThreadManager({ redis, threadId }),
|
|
838
|
-
* async (args, ctx) => ({
|
|
839
|
-
* toolResponse: JSON.stringify(largeResult), // appended directly to Redis
|
|
840
|
-
* data: { summary: "..." }, // small data for workflow
|
|
841
|
-
* }),
|
|
842
|
-
* );
|
|
843
|
-
* ```
|
|
844
|
-
*/
|
|
845
|
-
export function withAutoAppend<
|
|
846
|
-
TArgs,
|
|
847
|
-
TResult,
|
|
848
|
-
TContext extends ToolHandlerContext = ToolHandlerContext,
|
|
849
|
-
>(
|
|
850
|
-
threadHandler: (config: ToolResultConfig) => Promise<void>,
|
|
851
|
-
handler: ActivityToolHandler<TArgs, TResult, TContext>
|
|
852
|
-
): ActivityToolHandler<TArgs, TResult, TContext> {
|
|
853
|
-
return async (args: TArgs, context: TContext) => {
|
|
854
|
-
const response = await handler(args, context);
|
|
855
|
-
const threadId = (context as Record<string, unknown>).threadId as string;
|
|
856
|
-
const toolCallId = (context as Record<string, unknown>)
|
|
857
|
-
.toolCallId as string;
|
|
858
|
-
const toolName = (context as Record<string, unknown>).toolName as string;
|
|
859
|
-
|
|
860
|
-
// Append directly (inside the activity, bypassing Temporal payload)
|
|
861
|
-
await threadHandler({
|
|
862
|
-
threadId,
|
|
863
|
-
toolCallId,
|
|
864
|
-
toolName,
|
|
865
|
-
content: response.toolResponse,
|
|
866
|
-
});
|
|
867
|
-
|
|
868
|
-
// Return with empty toolResponse to keep the Temporal payload small
|
|
869
|
-
return {
|
|
870
|
-
toolResponse: "Response appended via withAutoAppend",
|
|
871
|
-
data: response.data,
|
|
872
|
-
resultAppended: true,
|
|
873
|
-
};
|
|
874
|
-
};
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
/**
|
|
878
|
-
* Identity function that creates a generic inference context for a tool definition.
|
|
879
|
-
* TypeScript infers TResult from the handler and flows it to hooks automatically.
|
|
880
|
-
*
|
|
881
|
-
* @example
|
|
882
|
-
* ```typescript
|
|
883
|
-
* tools: {
|
|
884
|
-
* AskUser: defineTool({
|
|
885
|
-
* ...askUserTool,
|
|
886
|
-
* handler: handleAskUser,
|
|
887
|
-
* hooks: {
|
|
888
|
-
* onPostToolUse: ({ result }) => {
|
|
889
|
-
* // result is correctly typed as the handler's return data type
|
|
890
|
-
* },
|
|
891
|
-
* },
|
|
892
|
-
* }),
|
|
893
|
-
* }
|
|
894
|
-
* ```
|
|
895
|
-
*/
|
|
896
|
-
export function defineTool<
|
|
897
|
-
TName extends string,
|
|
898
|
-
TSchema extends z.ZodType,
|
|
899
|
-
TResult,
|
|
900
|
-
TContext = ToolHandlerContext,
|
|
901
|
-
>(
|
|
902
|
-
tool: ToolWithHandler<TName, TSchema, TResult, TContext>
|
|
903
|
-
): ToolWithHandler<TName, TSchema, TResult, TContext> {
|
|
904
|
-
return tool;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* Identity function that provides full type inference for subagent configurations.
|
|
909
|
-
* Verifies the workflow function's input parameters match the configured context,
|
|
910
|
-
* and properly types the lifecycle hooks with Task tool args and inferred result type.
|
|
911
|
-
*
|
|
912
|
-
* @example
|
|
913
|
-
* ```ts
|
|
914
|
-
* // With typed context — workflow must accept { prompt, context }
|
|
915
|
-
* const researcher = defineSubagent({
|
|
916
|
-
* name: "researcher",
|
|
917
|
-
* description: "Researches topics",
|
|
918
|
-
* workflow: researcherWorkflow, // (input: { prompt: string; context: { apiKey: string } }) => Promise<...>
|
|
919
|
-
* context: { apiKey: "..." },
|
|
920
|
-
* resultSchema: z.object({ findings: z.string() }),
|
|
921
|
-
* hooks: {
|
|
922
|
-
* onPostExecution: ({ result }) => {
|
|
923
|
-
* // result is typed as { findings: string }
|
|
924
|
-
* },
|
|
925
|
-
* },
|
|
926
|
-
* });
|
|
927
|
-
*
|
|
928
|
-
* // Without context — workflow only needs { prompt }
|
|
929
|
-
* const writer = defineSubagent({
|
|
930
|
-
* name: "writer",
|
|
931
|
-
* description: "Writes content",
|
|
932
|
-
* workflow: writerWorkflow, // (input: { prompt: string }) => Promise<...>
|
|
933
|
-
* resultSchema: z.object({ content: z.string() }),
|
|
934
|
-
* });
|
|
935
|
-
* ```
|
|
936
|
-
*/
|
|
937
|
-
// With context — verifies workflow accepts { prompt, context: TContext }
|
|
938
|
-
export function defineSubagent<
|
|
939
|
-
TResult extends z.ZodType = z.ZodType,
|
|
940
|
-
TContext extends Record<string, unknown> = Record<string, unknown>,
|
|
941
|
-
>(
|
|
942
|
-
config: Omit<SubagentConfig<TResult>, "hooks" | "workflow" | "context"> & {
|
|
943
|
-
workflow:
|
|
944
|
-
| string
|
|
945
|
-
| ((input: {
|
|
946
|
-
prompt: string;
|
|
947
|
-
context: TContext;
|
|
948
|
-
}) => Promise<ToolHandlerResponse<z.infer<TResult> | null>>);
|
|
949
|
-
context: TContext;
|
|
950
|
-
hooks?: SubagentHooks<SubagentArgs, z.infer<TResult>>;
|
|
951
|
-
}
|
|
952
|
-
): SubagentConfig<TResult>;
|
|
953
|
-
// Without context — verifies workflow accepts { prompt }
|
|
954
|
-
export function defineSubagent<TResult extends z.ZodType = z.ZodType>(
|
|
955
|
-
config: Omit<SubagentConfig<TResult>, "hooks" | "workflow"> & {
|
|
956
|
-
workflow:
|
|
957
|
-
| string
|
|
958
|
-
| ((input: {
|
|
959
|
-
prompt: string;
|
|
960
|
-
}) => Promise<ToolHandlerResponse<z.infer<TResult> | null>>);
|
|
961
|
-
hooks?: SubagentHooks<SubagentArgs, z.infer<TResult>>;
|
|
962
|
-
}
|
|
963
|
-
): SubagentConfig<TResult>;
|
|
964
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
965
|
-
export function defineSubagent(config: any): SubagentConfig {
|
|
966
|
-
return config;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
/**
|
|
970
|
-
* Utility to check if there were no tool calls besides a specific one
|
|
971
|
-
*/
|
|
972
|
-
export function hasNoOtherToolCalls<T extends ToolMap>(
|
|
973
|
-
toolCalls: ParsedToolCallUnion<T>[],
|
|
974
|
-
excludeName: ToolNames<T>
|
|
975
|
-
): boolean {
|
|
976
|
-
return toolCalls.filter((tc) => tc.name !== excludeName).length === 0;
|
|
977
|
-
}
|