zeitlich 0.2.48 → 0.2.50

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 (126) hide show
  1. package/README.md +26 -23
  2. package/dist/{activities-DCaIPQBT.d.ts → activities-IuOIvPHO.d.ts} +6 -6
  3. package/dist/{activities-BlQR5gX4.d.cts → activities-cIlq1y1y.d.cts} +6 -6
  4. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  5. package/dist/adapters/sandbox/daytona/index.d.cts +3 -3
  6. package/dist/adapters/sandbox/daytona/index.d.ts +3 -3
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  8. package/dist/adapters/sandbox/daytona/workflow.d.cts +2 -2
  9. package/dist/adapters/sandbox/daytona/workflow.d.ts +2 -2
  10. package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
  11. package/dist/adapters/sandbox/e2b/index.d.cts +1 -1
  12. package/dist/adapters/sandbox/e2b/index.d.ts +1 -1
  13. package/dist/adapters/sandbox/e2b/index.js.map +1 -1
  14. package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
  15. package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
  16. package/dist/adapters/thread/anthropic/index.cjs +45 -42
  17. package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
  18. package/dist/adapters/thread/anthropic/index.d.cts +10 -10
  19. package/dist/adapters/thread/anthropic/index.d.ts +10 -10
  20. package/dist/adapters/thread/anthropic/index.js +45 -42
  21. package/dist/adapters/thread/anthropic/index.js.map +1 -1
  22. package/dist/adapters/thread/anthropic/workflow.d.cts +7 -7
  23. package/dist/adapters/thread/anthropic/workflow.d.ts +7 -7
  24. package/dist/adapters/thread/google-genai/index.cjs +117 -54
  25. package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
  26. package/dist/adapters/thread/google-genai/index.d.cts +27 -23
  27. package/dist/adapters/thread/google-genai/index.d.ts +27 -23
  28. package/dist/adapters/thread/google-genai/index.js +117 -54
  29. package/dist/adapters/thread/google-genai/index.js.map +1 -1
  30. package/dist/adapters/thread/google-genai/workflow.d.cts +8 -8
  31. package/dist/adapters/thread/google-genai/workflow.d.ts +8 -8
  32. package/dist/adapters/thread/langchain/index.cjs +45 -42
  33. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  34. package/dist/adapters/thread/langchain/index.d.cts +10 -10
  35. package/dist/adapters/thread/langchain/index.d.ts +10 -10
  36. package/dist/adapters/thread/langchain/index.js +45 -42
  37. package/dist/adapters/thread/langchain/index.js.map +1 -1
  38. package/dist/adapters/thread/langchain/workflow.d.cts +7 -7
  39. package/dist/adapters/thread/langchain/workflow.d.ts +7 -7
  40. package/dist/{cold-store-UL13Sstw.d.cts → cold-store-C0uvYTSi.d.cts} +1 -1
  41. package/dist/{cold-store-aD4TSKlU.d.ts → cold-store-CCnZYWjx.d.ts} +1 -1
  42. package/dist/index.cjs +15063 -405
  43. package/dist/index.cjs.map +1 -1
  44. package/dist/index.d.cts +79 -83
  45. package/dist/index.d.ts +79 -83
  46. package/dist/index.js +15064 -402
  47. package/dist/index.js.map +1 -1
  48. package/dist/{proxy-BAty3CWM.d.cts → proxy-BVznA2_p.d.cts} +1 -1
  49. package/dist/{proxy-mbnwBhHw.d.ts → proxy-C4J1pNUk.d.ts} +1 -1
  50. package/dist/{thread-manager-CICj68PI.d.ts → thread-manager-BqjzWsP7.d.ts} +4 -4
  51. package/dist/{thread-manager-R6c3lnJy.d.cts → thread-manager-CzIs47uG.d.cts} +4 -4
  52. package/dist/{thread-manager-DsXvJ5cJ.d.cts → thread-manager-Dzl1fHhV.d.cts} +4 -4
  53. package/dist/{thread-manager-DtEtbUkp.d.ts → thread-manager-SkSWRPRc.d.ts} +4 -4
  54. package/dist/{types-gVa5XCWD.d.ts → types-BQvXWcft.d.ts} +1 -1
  55. package/dist/{types-DF4wzWQG.d.ts → types-CbPnU4RM.d.ts} +3 -3
  56. package/dist/{types-CJ7tCdl6.d.cts → types-D8W5TnSa.d.cts} +3 -3
  57. package/dist/{types-CJ7tCdl6.d.ts → types-D8W5TnSa.d.ts} +3 -3
  58. package/dist/{types-DwBYd0ij.d.ts → types-DZnUqCAP.d.cts} +709 -686
  59. package/dist/{types-CjY93AWZ.d.cts → types-OEN1xrFg.d.cts} +1 -1
  60. package/dist/{types-DWeyCTYK.d.cts → types-YNesmGKV.d.ts} +709 -686
  61. package/dist/{types-DDLPnxBh.d.cts → types-d2RvEP6v.d.cts} +3 -3
  62. package/dist/{workflow-DdaU7_j4.d.ts → workflow-B3oTe2_D.d.cts} +34 -3
  63. package/dist/{workflow-DVNPR7eX.d.cts → workflow-Bkzg0cjB.d.ts} +34 -3
  64. package/dist/workflow.cjs +15021 -362
  65. package/dist/workflow.cjs.map +1 -1
  66. package/dist/workflow.d.cts +3 -3
  67. package/dist/workflow.d.ts +3 -3
  68. package/dist/workflow.js +15022 -359
  69. package/dist/workflow.js.map +1 -1
  70. package/package.json +10 -37
  71. package/src/adapters/thread/anthropic/activities.ts +1 -1
  72. package/src/adapters/thread/anthropic/fork-transform.test.ts +17 -11
  73. package/src/adapters/thread/anthropic/model-invoker.test.ts +4 -3
  74. package/src/adapters/thread/anthropic/model-invoker.ts +1 -1
  75. package/src/adapters/thread/anthropic/thread-manager.test.ts +2 -2
  76. package/src/adapters/thread/anthropic/thread-manager.ts +1 -1
  77. package/src/adapters/thread/google-genai/activities.ts +1 -1
  78. package/src/adapters/thread/google-genai/fork-transform.test.ts +17 -11
  79. package/src/adapters/thread/google-genai/model-invoker.test.ts +337 -0
  80. package/src/adapters/thread/google-genai/model-invoker.ts +107 -23
  81. package/src/adapters/thread/google-genai/thread-manager.test.ts +2 -2
  82. package/src/adapters/thread/google-genai/thread-manager.ts +1 -1
  83. package/src/adapters/thread/langchain/activities.ts +1 -1
  84. package/src/adapters/thread/langchain/fork-transform.test.ts +17 -11
  85. package/src/adapters/thread/langchain/model-invoker.ts +1 -1
  86. package/src/adapters/thread/langchain/thread-manager.test.ts +2 -2
  87. package/src/adapters/thread/langchain/thread-manager.ts +1 -1
  88. package/src/index.ts +2 -2
  89. package/src/lib/sandbox/capability-types.test.ts +2 -2
  90. package/src/lib/sandbox/manager.ts +2 -6
  91. package/src/lib/sandbox/sandbox.test.ts +1 -1
  92. package/src/lib/sandbox/types.ts +2 -2
  93. package/src/lib/session/session.integration.test.ts +92 -0
  94. package/src/lib/session/session.ts +23 -0
  95. package/src/lib/subagent/handler.ts +23 -0
  96. package/src/lib/subagent/subagent.integration.test.ts +198 -0
  97. package/src/lib/thread/keys.test.ts +9 -9
  98. package/src/lib/thread/keys.ts +1 -1
  99. package/src/lib/thread/manager.test.ts +24 -14
  100. package/src/lib/thread/manager.ts +19 -23
  101. package/src/lib/thread/snapshot.test.ts +51 -43
  102. package/src/lib/thread/snapshot.ts +54 -32
  103. package/src/lib/thread/test-utils.ts +106 -59
  104. package/src/lib/thread/tiered.test.ts +1 -1
  105. package/src/lib/thread/types.ts +2 -2
  106. package/src/lib/tool-router/router.integration.test.ts +44 -0
  107. package/src/lib/tool-router/router.ts +149 -33
  108. package/src/lib/tool-router/types.ts +23 -0
  109. package/src/lib/workflow.ts +49 -0
  110. package/src/{adapters/sandbox/inmemory/proxy.ts → test-utils/in-memory-sandbox-proxy.ts} +5 -16
  111. package/src/{adapters/sandbox/inmemory/index.ts → test-utils/in-memory-sandbox.ts} +11 -3
  112. package/src/tools/bash/bash.test.ts +1 -1
  113. package/src/tools/edit/handler.test.ts +1 -1
  114. package/tsup.config.ts +2 -4
  115. package/dist/adapters/sandbox/inmemory/index.cjs +0 -214
  116. package/dist/adapters/sandbox/inmemory/index.cjs.map +0 -1
  117. package/dist/adapters/sandbox/inmemory/index.d.cts +0 -40
  118. package/dist/adapters/sandbox/inmemory/index.d.ts +0 -40
  119. package/dist/adapters/sandbox/inmemory/index.js +0 -211
  120. package/dist/adapters/sandbox/inmemory/index.js.map +0 -1
  121. package/dist/adapters/sandbox/inmemory/workflow.cjs +0 -36
  122. package/dist/adapters/sandbox/inmemory/workflow.cjs.map +0 -1
  123. package/dist/adapters/sandbox/inmemory/workflow.d.cts +0 -27
  124. package/dist/adapters/sandbox/inmemory/workflow.d.ts +0 -27
  125. package/dist/adapters/sandbox/inmemory/workflow.js +0 -34
  126. package/dist/adapters/sandbox/inmemory/workflow.js.map +0 -1
@@ -1,838 +1,861 @@
1
- import { ActivityFunctionWithOptions, QueryDefinition, ChildWorkflowOptions, ActivityInterfaceFor } from '@temporalio/workflow';
1
+ import { QueryDefinition, ActivityFunctionWithOptions, ChildWorkflowOptions, ActivityInterfaceFor } from '@temporalio/workflow';
2
2
  import { UpdateDefinition } from '@temporalio/common/lib/interfaces';
3
3
  import { z } from 'zod';
4
- import { g as SandboxSnapshot, c as SandboxFileSystem, F as FileStat, D as DirentEntry, a as SandboxCreateOptions, S as SandboxOps, h as SandboxCapability } from './types-CJ7tCdl6.cjs';
4
+ import { d as SandboxFileSystem, F as FileStat, D as DirentEntry, g as SandboxSnapshot, f as SandboxCreateOptions, c as SandboxOps, h as SandboxCapability } from './types-D8W5TnSa.js';
5
5
 
6
6
  /**
7
- * A tool definition with a name, description, and Zod schema for arguments.
8
- * Does not include a handler - use ToolWithHandler for tools with handlers.
7
+ * JSON primitive types that Temporal can serialize
9
8
  */
10
- interface ToolDefinition<TName extends string = string, TSchema extends z.ZodType = z.ZodType> {
11
- name: TName;
12
- description: string;
13
- schema: TSchema;
14
- strict?: boolean;
15
- max_uses?: number;
16
- }
9
+ type JsonPrimitive = string | number | boolean | null | undefined;
17
10
  /**
18
- * A tool definition with an integrated handler function.
19
- * This is the primary type for defining tools in the router.
11
+ * JSON-serializable value (recursive type for Temporal compatibility)
20
12
  */
21
- interface ToolWithHandler<TName extends string = string, TSchema extends z.ZodType = z.ZodType, TResult = unknown, TContext extends RouterContext = RouterContext, TToolResponse = JsonValue> {
22
- name: TName;
23
- description: string;
24
- schema: TSchema;
25
- handler: ToolHandler<z.infer<TSchema>, TResult, TContext, TToolResponse>;
26
- strict?: boolean;
27
- max_uses?: number;
28
- /** Whether this tool is available to the agent (default: true). Disabled tools are excluded from definitions and rejected at parse time. */
29
- enabled?: boolean | (() => boolean);
30
- /** Per-tool lifecycle hooks (run in addition to global hooks) */
31
- hooks?: ToolHooks<z.infer<TSchema>, TResult>;
32
- }
13
+ type JsonValue = JsonPrimitive | JsonValue[] | {
14
+ [key: string]: JsonValue;
15
+ };
33
16
  /**
34
- * A map of tool keys to tool definitions with handlers.
17
+ * Type constraint ensuring T only contains JSON-serializable values.
18
+ * Use this for custom state to ensure Temporal workflow compatibility.
35
19
  *
36
- * Handler uses `any` intentionally this is a type-system boundary where heterogeneous
37
- * tool types are stored together. Type safety for individual tools is enforced by
38
- * `defineTool()` at the definition site and generic inference utilities like
39
- * `InferToolResults<T>` at the consumption site.
20
+ * Allows: primitives, arrays, plain objects, and JsonValue
21
+ * Rejects: functions, symbols, undefined, class instances with methods
40
22
  */
41
- type ToolMap = Record<string, {
42
- name: string;
43
- description: string | (() => string);
44
- schema: z.ZodType | (() => z.ZodType);
45
- handler: ToolHandler<any, any, any, any>;
46
- strict?: boolean;
47
- max_uses?: number;
48
- enabled?: boolean | (() => boolean);
49
- hooks?: ToolHooks<any, any>;
50
- }>;
23
+ type JsonSerializable<T> = {
24
+ [K in keyof T]: T[K] extends JsonValue ? T[K] : T[K] extends JsonPrimitive ? T[K] : T[K] extends (infer U)[] ? U extends JsonValue ? T[K] : JsonSerializable<U>[] : T[K] extends object ? JsonSerializable<T[K]> : never;
25
+ };
51
26
  /**
52
- * Extract the tool names from a tool map (uses the tool's name property, not the key).
27
+ * Full state type combining base state with custom state
53
28
  */
54
- type ToolNames<T extends ToolMap> = T[keyof T]["name"];
29
+ type AgentState<TCustom extends JsonSerializable<TCustom>> = BaseAgentState & TCustom;
55
30
  /**
56
- * A raw tool call as received from the LLM before parsing.
31
+ * The slice of agent state that is persisted alongside the thread in the
32
+ * thread store (e.g. Redis) so that a workflow can terminate, store its
33
+ * state, and be continued or forked later with that state rehydrated.
34
+ *
35
+ * Only fields that make sense to carry across workflow runs belong here.
36
+ * Runtime bookkeeping like status, version, turns, tools, fileTree, token
37
+ * counters, and the system prompt is intentionally NOT persisted — each run
38
+ * rebuilds those from scratch.
57
39
  */
58
- interface RawToolCall {
59
- id?: string;
60
- name: string;
61
- args: unknown;
40
+ interface PersistedThreadState {
41
+ /** Task map serialized as entries so it round-trips through JSON. */
42
+ tasks: [string, WorkflowTask][];
43
+ /** All custom state fields declared by the caller. */
44
+ custom: Record<string, JsonValue>;
62
45
  }
63
46
  /**
64
- * A parsed tool call with validated arguments for a specific tool.
47
+ * Agent state manager interface
48
+ * Note: Temporal handlers must be set up in the workflow file due to
49
+ * Temporal's workflow isolation requirements. This manager provides
50
+ * the state and helpers needed for those handlers.
65
51
  */
66
- interface ParsedToolCall<TName extends string = string, TArgs = unknown> {
67
- id: string;
68
- name: TName;
69
- args: TArgs;
52
+ interface AgentStateManager<TCustom extends JsonSerializable<TCustom>> {
53
+ /** Typed query definition registered for this agent's state */
54
+ readonly stateQuery: QueryDefinition<AgentState<TCustom>>;
55
+ /** Typed update definition registered for waiting on this agent's state change */
56
+ readonly stateChangeUpdate: UpdateDefinition<AgentState<TCustom>, [number]>;
57
+ /** Get current status */
58
+ getStatus(): AgentStatus;
59
+ /** Check if agent is running */
60
+ isRunning(): boolean;
61
+ /** Check if agent is in terminal state */
62
+ isTerminal(): boolean;
63
+ /** Get current state version */
64
+ getVersion(): number;
65
+ /** Set status to RUNNING */
66
+ run(): void;
67
+ /** Set status to WAITING_FOR_INPUT */
68
+ waitForInput(): void;
69
+ /** Set status to COMPLETED */
70
+ complete(): void;
71
+ /** Set status to FAILED */
72
+ fail(): void;
73
+ /** Set status to CANCELLED */
74
+ cancel(): void;
75
+ /** Increment state version (call after state changes) */
76
+ incrementVersion(): void;
77
+ /** Increment turns (call after each turn) */
78
+ incrementTurns(): void;
79
+ /** Get current turns */
80
+ getTurns(): number;
81
+ /** Get the system prompt */
82
+ getSystemPrompt(): unknown;
83
+ /** Set the system prompt */
84
+ setSystemPrompt(newSystemPrompt: unknown): void;
85
+ /** Get a custom state value by key */
86
+ get<K extends keyof TCustom>(key: K): TCustom[K];
87
+ /** Set a custom state value by key */
88
+ set<K extends keyof TCustom>(key: K, value: TCustom[K]): void;
89
+ /** Bulk-merge a partial update into custom state */
90
+ mergeUpdate(update: Partial<AgentState<TCustom>>): void;
91
+ /** Get full state for query handler */
92
+ getCurrentState(): AgentState<TCustom>;
93
+ /** Check if should return from waitForStateChange */
94
+ shouldReturnFromWait(lastKnownVersion: number): boolean;
95
+ /** Get all tasks */
96
+ getTasks(): WorkflowTask[];
97
+ /** Get a task by ID */
98
+ getTask(id: string): WorkflowTask | undefined;
99
+ /** Add or update a task */
100
+ setTask(task: WorkflowTask): void;
101
+ /** Delete a task by ID */
102
+ deleteTask(id: string): boolean;
103
+ /** Set the tools (converts Zod schemas to JSON Schema for serialization) */
104
+ setTools(newTools: ToolDefinition[]): void;
105
+ /**
106
+ * Snapshot the fields that should survive across workflow runs
107
+ * (tasks + all custom state). Safe to pass directly to
108
+ * {@link ThreadOps.saveThreadState}. Rehydrate on the next run with
109
+ * `mergeUpdate({ tasks: new Map(slice.tasks), ...slice.custom })`.
110
+ */
111
+ getPersistedSlice(): PersistedThreadState;
112
+ /** Update the usage */
113
+ updateUsage(usage: TokenUsage): void;
114
+ /** Get the total usage */
115
+ getTotalUsage(): {
116
+ totalInputTokens: number;
117
+ totalOutputTokens: number;
118
+ totalCachedWriteTokens: number;
119
+ totalCachedReadTokens: number;
120
+ totalReasonTokens: number;
121
+ turns: number;
122
+ };
70
123
  }
124
+
71
125
  /**
72
- * Union type of all possible parsed tool calls from a tool map.
73
- */
74
- type ParsedToolCallUnion<T extends ToolMap> = {
75
- [K in keyof T]: ParsedToolCall<T[K]["name"], z.infer<T[K]["schema"]>>;
76
- }[keyof T];
77
- /**
78
- * Function signature for appending tool results to a thread.
79
- */
80
- type AppendToolResultFn = ActivityFunctionWithOptions<(id: string, config: ToolResultConfig) => Promise<void>>;
81
- /**
82
- * The response from a tool handler.
83
- * Contains the content for the tool message and the result to return from processToolCalls.
126
+ * Ephemeral virtual filesystem backed by a {@link FileResolver}.
84
127
  *
85
- * Tools that don't return additional data should use `data: null` (TResult defaults to null).
86
- * Tools that may fail to produce data should type TResult as `SomeType | null`.
128
+ * Created fresh for each tool invocation from the current workflow file tree.
129
+ * Directory structure is inferred from file paths. All mutations are tracked
130
+ * and can be retrieved via {@link getMutations} after the handler completes.
87
131
  */
88
- interface ToolHandlerResponse<TResult = null, TToolResponse = JsonValue> {
89
- /** Content sent back to the LLM as the tool call response */
90
- toolResponse: TToolResponse;
91
- /** Data returned to the workflow and hooks for further processing */
92
- data: TResult;
93
- /**
94
- * When true, the tool result has already been appended to the thread
95
- * by the handler itself (e.g. via `withAutoAppend`), so the router
96
- * will skip the `appendToolResult` call. This avoids sending large
97
- * payloads through Temporal's activity payload limit.
98
- */
99
- resultAppended?: boolean;
132
+ declare class VirtualFileSystem<TCtx = unknown, TMeta = FileEntryMetadata> implements SandboxFileSystem {
133
+ private resolver;
134
+ private ctx;
135
+ readonly workspaceBase: string;
136
+ private entries;
137
+ private directories;
138
+ private mutations;
139
+ private inlineFiles;
140
+ constructor(tree: FileEntry<TMeta>[], resolver: FileResolver<TCtx, TMeta>, ctx: TCtx, workspaceBase?: string, inlineFiles?: Record<string, string>);
141
+ /** Return all mutations accumulated during this invocation. */
142
+ getMutations(): TreeMutation<TMeta>[];
143
+ /** Look up a file entry by virtual path. */
144
+ getEntry(path: string): FileEntry<TMeta> | undefined;
145
+ readFile(path: string): Promise<string>;
146
+ readFileBuffer(path: string): Promise<Uint8Array>;
100
147
  /**
101
- * When true, the session will rewind: any in-flight parallel tool
102
- * calls are cancelled and the LLM call is retried. The session reuses
103
- * the same `assistantMessageId` for the retry; the next `runAgent`
104
- * activity truncates the thread from that id on entry, wiping the
105
- * triggering assistant message and any tool results already appended
106
- * before re-invoking the LLM.
107
- *
108
- * The `toolResponse` for a rewinding tool call is ignored (never
109
- * appended) since the thread is rewound back to the pre-assistant
110
- * state on the next invocation.
148
+ * Resolve the string content for an entry, preferring inline content
149
+ * carried on the entry itself before consulting the resolver. Used by
150
+ * `readFile`, `appendFile`, and `cp` so all read paths agree on the
151
+ * lookup precedence.
111
152
  */
112
- rewind?: boolean;
113
- /** Token usage from the tool execution (e.g. child agent invocations) */
114
- usage?: TokenUsage;
115
- /** Thread ID used by the handler (surfaced to the LLM for subagent thread continuation) */
116
- threadId?: string;
117
- /** Sandbox ID created or used by the handler (e.g. child agent sandbox) */
118
- sandboxId?: string;
119
- /** Snapshot captured on exit when `sandboxShutdown === "snapshot"`. */
120
- snapshot?: SandboxSnapshot;
121
- /** Snapshot captured immediately after sandbox seeding (before the agent loop starts) when `sandbox.mode === "new"` and `sandboxShutdown === "snapshot"`. Intended as a reusable "base" for new threads that want to skip re-seeding. */
122
- baseSnapshot?: SandboxSnapshot;
123
- /** Unvalidated metadata passthrough from handler to hooks (e.g. infrastructure state) */
124
- metadata?: Record<string, unknown>;
153
+ private readEntryContent;
154
+ exists(path: string): Promise<boolean>;
155
+ stat(path: string): Promise<FileStat>;
156
+ readdir(path: string): Promise<string[]>;
157
+ readdirWithFileTypes(path: string): Promise<DirentEntry[]>;
158
+ writeFile(path: string, content: string | Uint8Array): Promise<void>;
159
+ appendFile(path: string, content: string | Uint8Array): Promise<void>;
160
+ mkdir(_path: string, _options?: {
161
+ recursive?: boolean;
162
+ }): Promise<void>;
163
+ rm(path: string, options?: {
164
+ recursive?: boolean;
165
+ force?: boolean;
166
+ }): Promise<void>;
167
+ cp(src: string, dest: string, _options?: {
168
+ recursive?: boolean;
169
+ }): Promise<void>;
170
+ mv(src: string, dest: string): Promise<void>;
171
+ readlink(_path: string): Promise<string>;
172
+ resolvePath(base: string, path: string): string;
173
+ private addParentDirectories;
125
174
  }
126
- /**
127
- * Base context the router always injects into every handler invocation.
128
- * Handlers can rely on these fields being present without casting.
129
- */
130
- interface RouterContext {
131
- threadId: string;
132
- /** Redis key suffix for thread storage. Defaults to 'messages'. */
133
- threadKey?: string;
134
- toolCallId: string;
135
- toolName: string;
136
- sandboxId?: string;
175
+
176
+ /** Allowed value types for file-entry metadata. */
177
+ type FileEntryMetadata = Record<string, string | number | boolean | null>;
178
+ /** JSON-serializable metadata for a single file in the virtual tree. */
179
+ interface FileEntry<TMeta = FileEntryMetadata> {
180
+ id: string;
181
+ /** Virtual path, e.g. "/src/index.ts" */
182
+ path: string;
183
+ size: number;
184
+ /** ISO-8601 date string (JSON-safe) */
185
+ mtime: string;
186
+ metadata: TMeta;
137
187
  /**
138
- * Id of the assistant message that issued this tool call (the message
139
- * the session passed as `assistantMessageId` into `runAgent`). Present
140
- * for any tool call processed through `processToolCalls` from a
141
- * session; may be absent when the router is driven manually (e.g.
142
- * tests, custom orchestrators).
188
+ * Optional inline content carried directly on the entry. When present the
189
+ * {@link VirtualFileSystem} returns this string from `readFile` /
190
+ * `readFileBuffer` (and uses it as the source for `cp` / `appendFile`)
191
+ * without consulting the resolver.
143
192
  *
144
- * Subagent handlers that fork the parent's thread mid-call use this
145
- * to truncate the orphan trailing assistant message from the forked
146
- * thread so the child's first model call sees a well-formed history.
193
+ * Use this for files that exist purely in workflow state and have no
194
+ * backing in the consumer's data layer (e.g. skill resources bundled at
195
+ * session creation time). Because the content travels with the entry,
196
+ * any tool handler that constructs a `VirtualFileSystem` from `fileTree`
197
+ * sees the same content — no separate `inlineFiles` plumbing required.
198
+ *
199
+ * Read-only: entries with `inlineContent` reject in-place mutations
200
+ * (`writeFile`, `appendFile`, `rm`, `mv` of the entry, `cp` over the
201
+ * entry as destination). The resolver has no contract for the synthetic
202
+ * `id` these entries carry, so attempting a mutation throws an `EROFS`
203
+ * error instead of silently routing a doomed call through the resolver.
147
204
  */
148
- assistantMessageId?: string;
205
+ inlineContent?: string;
149
206
  }
150
207
  /**
151
- * A handler function for a specific tool.
152
- * Receives the parsed args and a context that always includes {@link RouterContext}
153
- * fields, plus any additional properties when TContext extends RouterContext.
208
+ * Flat list of file entries.
209
+ * Directories are inferred from file paths at runtime.
154
210
  */
155
- type ToolHandler<TArgs, TResult, TContext extends RouterContext = RouterContext, TToolResponse = JsonValue> = (args: TArgs, context: TContext) => ToolHandlerResponse<TResult, TToolResponse> | Promise<ToolHandlerResponse<TResult, TToolResponse>>;
211
+ type VirtualFileTree<TMeta = FileEntryMetadata> = FileEntry<TMeta>[];
212
+ type TreeMutation<TMeta = FileEntryMetadata> = {
213
+ type: "add";
214
+ entry: FileEntry<TMeta>;
215
+ } | {
216
+ type: "remove";
217
+ path: string;
218
+ } | {
219
+ type: "update";
220
+ path: string;
221
+ entry: Partial<FileEntry<TMeta>>;
222
+ };
156
223
  /**
157
- * Activity-compatible tool handler that always returns a Promise.
158
- * Use this for tool handlers registered as Temporal activities.
224
+ * Consumer-provided bridge to the existing DB / S3 / CRUD layer.
159
225
  *
160
- * @example
161
- * ```typescript
162
- * const readHandler: ActivityToolHandler<
163
- * FileReadArgs,
164
- * ReadResult,
165
- * RouterContext & { scopedNodes: FileNode[]; provider: FileSystemProvider }
166
- * > = async (args, context) => {
167
- * // context.threadId, context.sandboxId etc. are always available
168
- * return readHandler(args, context.scopedNodes, context.provider);
169
- * };
170
- * ```
171
- */
172
- type ActivityToolHandler<TArgs, TResult, TContext extends RouterContext = RouterContext, TToolResponse = JsonValue> = (args: TArgs, context: TContext) => Promise<ToolHandlerResponse<TResult, TToolResponse>>;
173
- /**
174
- * Extract the args type for a specific tool name from a tool map.
175
- */
176
- type ToolArgs<T extends ToolMap, TName extends ToolNames<T>> = z.infer<Extract<T[keyof T], {
177
- name: TName;
178
- }>["schema"]>;
179
- /**
180
- * Extract the result type for a specific tool name from a tool map.
181
- */
182
- type ToolResult<T extends ToolMap, TName extends ToolNames<T>> = Extract<T[keyof T], {
183
- name: TName;
184
- }>["handler"] extends ToolHandler<unknown, infer R, RouterContext, any> ? Awaited<R> : never;
185
- /**
186
- * The result of processing a tool call.
226
+ * Generic over `TCtx` so every call receives workflow-level context
227
+ * (e.g. `{ projectId: string }`) without the resolver holding state.
228
+ *
229
+ * Generic over `TMeta` so resolved entries carry typed metadata.
187
230
  */
188
- interface ToolCallResult<TName extends string = string, TResult = unknown> {
189
- toolCallId: string;
190
- name: TName;
191
- data: TResult;
192
- usage?: TokenUsage;
193
- /** Unvalidated metadata passthrough from handler to hooks (e.g. infrastructure state) */
194
- metadata?: Record<string, unknown>;
231
+ interface FileResolver<TCtx = unknown, TMeta = FileEntryMetadata> {
232
+ resolveEntries(ctx: TCtx): Promise<FileEntry<TMeta>[]>;
233
+ readFile(id: string, ctx: TCtx, metadata: TMeta): Promise<string>;
234
+ readFileBuffer(id: string, ctx: TCtx, metadata: TMeta): Promise<Uint8Array>;
235
+ writeFile(id: string, content: string | Uint8Array, ctx: TCtx, metadata: TMeta): Promise<void>;
236
+ createFile(path: string, content: string | Uint8Array, ctx: TCtx): Promise<FileEntry<TMeta>>;
237
+ deleteFile(id: string, ctx: TCtx, metadata: TMeta): Promise<void>;
195
238
  }
196
239
  /**
197
- * Infer result types from a tool map based on handler return types.
240
+ * Workflow-side operations for the virtual filesystem.
241
+ *
242
+ * Unlike {@link SandboxOps}, this only exposes what is actually needed:
243
+ * resolving the initial file tree from the consumer's data layer.
198
244
  */
199
- type InferToolResults<T extends ToolMap> = {
200
- [K in keyof T as T[K]["name"]]: T[K]["handler"] extends ToolHandler<any, infer R, any, any> ? Awaited<R> : never;
201
- };
245
+ interface VirtualFsOps<TCtx = unknown, TMeta = FileEntryMetadata> {
246
+ resolveFileTree(ctx: TCtx): Promise<{
247
+ fileTree: FileEntry<TMeta>[];
248
+ }>;
249
+ }
202
250
  /**
203
- * Union of all possible tool call results based on handler return types.
251
+ * Maps generic {@link VirtualFsOps} method names to scope-prefixed names.
252
+ *
253
+ * @example
254
+ * ```typescript
255
+ * type Ops = PrefixedVirtualFsOps<"codingAgent">;
256
+ * // → { virtualFsCodingAgentResolveFileTree: ... }
257
+ * ```
204
258
  */
205
- type ToolCallResultUnion<TResults extends Record<string, unknown>> = {
206
- [TName in keyof TResults & string]: ToolCallResult<TName, TResults[TName]>;
207
- }[keyof TResults & string];
259
+ type PrefixedVirtualFsOps<TPrefix extends string, TCtx = unknown, TMeta = FileEntryMetadata> = {
260
+ [K in keyof VirtualFsOps<TCtx, TMeta> as `virtualFs${Capitalize<TPrefix>}${Capitalize<K & string>}`]: VirtualFsOps<TCtx, TMeta>[K];
261
+ };
208
262
  /**
209
- * Context passed to processToolCalls for hook execution and handler invocation
263
+ * The portion of workflow `AgentState` that the virtual filesystem reads via
264
+ * {@link queryParentWorkflowState}. Populated automatically by the session
265
+ * when `virtualFs` config is provided.
210
266
  */
211
- interface ProcessToolCallsContext {
212
- /** Current turn number (for hooks) */
213
- turn?: number;
214
- /** Active sandbox ID (when a sandbox is configured for this session) */
215
- sandboxId?: string;
216
- /**
217
- * Id of the assistant message that produced these tool calls. The
218
- * router forwards it into every handler's {@link RouterContext} so
219
- * handlers can reference the message they were issued from (e.g.
220
- * subagent forks that need to truncate the orphan assistant message
221
- * out of a parent-forked thread).
222
- */
223
- assistantMessageId?: string;
267
+ interface VirtualFsState<TCtx = unknown, TMeta = FileEntryMetadata> {
268
+ fileTree: FileEntry<TMeta>[];
269
+ virtualFsCtx: TCtx;
270
+ /** In-memory file contents keyed by path, bypassing the resolver (e.g. skill resources). */
271
+ inlineFiles?: Record<string, string>;
224
272
  }
225
273
  /**
226
- * Signal that a tool handler requested a rewind. Attached to the
227
- * {@link ProcessToolCallsResult} so the session can reuse the same
228
- * `assistantMessageId` for the retry; the next `runAgent` activity
229
- * then truncates the thread from that id on entry.
274
+ * Extended router context injected by {@link withVirtualFs}.
275
+ * Guarantees a live (ephemeral) virtual filesystem built from the workflow
276
+ * file tree.
230
277
  */
231
- interface RewindSignal {
232
- toolCallId: string;
233
- toolName: string;
278
+ interface VirtualFsContext<TCtx = unknown, TMeta = FileEntryMetadata> extends RouterContext {
279
+ virtualFs: VirtualFileSystem<TCtx, TMeta>;
234
280
  }
281
+
235
282
  /**
236
- * Result returned by {@link ToolRouter.processToolCalls}.
237
- *
238
- * The object is a standard array of tool call results for successful
239
- * tool calls (cancelled or rewinding siblings are omitted), extended
240
- * with a `rewind` property when any tool in the batch requested a
241
- * rewind. Using an array-with-property lets existing code that treats
242
- * the return value as `ToolCallResultUnion[]` continue to work.
283
+ * Agent execution status
243
284
  */
244
- type ProcessToolCallsResult<TResults extends Record<string, unknown>> = ToolCallResultUnion<TResults>[] & {
245
- rewind?: RewindSignal;
246
- };
285
+ type AgentStatus = "RUNNING" | "WAITING_FOR_INPUT" | "COMPLETED" | "FAILED" | "CANCELLED";
247
286
  /**
248
- * Result from PreToolUse hook - can block or modify execution
287
+ * Base state that all agents must have
249
288
  */
250
- interface PreToolUseHookResult {
251
- /** Skip this tool call entirely */
252
- skip?: boolean;
253
- /** Modified args to use instead (must match schema) */
254
- modifiedArgs?: unknown;
289
+ interface BaseAgentState {
290
+ tools: SerializableToolDefinition[];
291
+ status: AgentStatus;
292
+ version: number;
293
+ turns: number;
294
+ tasks: Map<string, WorkflowTask>;
295
+ fileTree: FileEntry[];
296
+ /** In-memory file contents keyed by path, bypassing the resolver (e.g. skill resources). */
297
+ inlineFiles?: Record<string, string>;
298
+ virtualFsCtx?: unknown;
299
+ systemPrompt?: unknown;
300
+ totalInputTokens: number;
301
+ totalOutputTokens: number;
302
+ cachedWriteTokens: number;
303
+ cachedReadTokens: number;
255
304
  }
256
305
  /**
257
- * Result from PostToolUseFailure hook - can recover from errors
306
+ * File representation for agent workflows
258
307
  */
259
- interface PostToolUseFailureHookResult {
260
- /** Provide a fallback result instead of throwing */
261
- fallbackContent?: JsonValue;
262
- /** Whether to suppress the error (still logs, but continues) */
263
- suppress?: boolean;
308
+ interface AgentFile {
309
+ /** Database/S3 file ID */
310
+ id: string;
311
+ /** Virtual path for agent (e.g., "evidence/invoice.pdf") */
312
+ path: string;
313
+ /** Original filename */
314
+ filename: string;
315
+ /** Generic description for prompt */
316
+ description?: string;
317
+ /** MIME type of the file */
318
+ mimeType?: string;
319
+ }
320
+ interface TokenUsage {
321
+ inputTokens?: number;
322
+ outputTokens?: number;
323
+ cachedWriteTokens?: number;
324
+ cachedReadTokens?: number;
325
+ reasonTokens?: number;
264
326
  }
265
327
  /**
266
- * Per-tool lifecycle hooks - defined directly on a tool definition.
267
- * Runs in addition to global hooks (global pre → tool pre → execute → tool post → global post).
328
+ * Configuration for a Zeitlich agent
268
329
  */
269
- interface ToolHooks<TArgs = unknown, TResult = unknown> {
270
- /** Called before this tool executes - can skip or modify args */
271
- onPreToolUse?: (ctx: {
272
- args: TArgs;
273
- threadId: string;
274
- turn: number;
275
- }) => PreToolUseHookResult | Promise<PreToolUseHookResult>;
276
- /** Called after this tool executes successfully */
277
- onPostToolUse?: (ctx: {
278
- args: TArgs;
279
- result: TResult;
280
- threadId: string;
281
- turn: number;
282
- durationMs: number;
283
- metadata?: Record<string, unknown>;
284
- }) => void | Promise<void>;
285
- /** Called when this tool execution fails */
286
- onPostToolUseFailure?: (ctx: {
287
- args: TArgs;
288
- error: Error;
289
- threadId: string;
290
- turn: number;
291
- }) => PostToolUseFailureHookResult | Promise<PostToolUseFailureHookResult>;
330
+ interface AgentConfig {
331
+ /** The name of the agent, should be unique within the workflows, ideally Pascal Case */
332
+ agentName: string;
333
+ /** Description, used for sub agents */
334
+ description?: string;
292
335
  }
293
336
  /**
294
- * Context for PreToolUse hook - called before tool execution
337
+ * A JSON-serializable tool definition for state storage.
338
+ * Uses a plain JSON Schema object instead of a live Zod instance,
339
+ * so it survives Temporal serialization without losing constraints (min, max, etc.).
295
340
  */
296
- interface PreToolUseHookContext<T extends ToolMap> {
297
- /** The tool call about to be executed */
298
- toolCall: ParsedToolCallUnion<T>;
299
- /** Thread identifier */
300
- threadId: string;
301
- /** Current turn number */
302
- turn: number;
341
+ interface SerializableToolDefinition {
342
+ name: string;
343
+ description: string;
344
+ schema: Record<string, unknown>;
345
+ strict?: boolean;
346
+ max_uses?: number;
303
347
  }
304
348
  /**
305
- * PreToolUse hook - called before tool execution, can block or modify
349
+ * Configuration passed to runAgent activity
306
350
  */
307
- type PreToolUseHook<T extends ToolMap> = (ctx: PreToolUseHookContext<T>) => PreToolUseHookResult | Promise<PreToolUseHookResult>;
351
+ interface RunAgentConfig extends AgentConfig {
352
+ /** The thread ID to use for the session */
353
+ threadId: string;
354
+ /** Redis key suffix for thread storage. Defaults to 'messages'. */
355
+ threadKey?: string;
356
+ /** Metadata for the session */
357
+ metadata?: Record<string, unknown>;
358
+ /**
359
+ * The id under which the assistant message produced by this call will
360
+ * be appended. The activity truncates the thread from this id on
361
+ * entry (no-op on the first attempt) so that:
362
+ *
363
+ * - Rewind retries can reuse the same id and the previous (bad)
364
+ * assistant + its tool results are wiped before the retry LLM call.
365
+ * - Resetting the Temporal workflow to this activity restores the
366
+ * pre-call thread state: replay re-truncates, re-invokes, and
367
+ * appends under the same id.
368
+ */
369
+ assistantMessageId: string;
370
+ }
308
371
  /**
309
- * Context for PostToolUse hook - called after successful tool execution
372
+ * Configuration for appending a tool result
310
373
  */
311
- interface PostToolUseHookContext<T extends ToolMap, TResult = unknown> {
312
- /** The tool call that was executed */
313
- toolCall: ParsedToolCallUnion<T>;
314
- /** The result from the tool handler */
315
- result: TResult;
316
- /** Thread identifier */
374
+ interface ToolResultConfig {
317
375
  threadId: string;
318
- /** Current turn number */
319
- turn: number;
320
- /** Execution duration in milliseconds */
321
- durationMs: number;
376
+ /** Redis key suffix for thread storage. Defaults to 'messages'. */
377
+ threadKey?: string;
378
+ toolCallId: string;
379
+ /** The name of the tool that produced this result */
380
+ toolName: string;
381
+ /** Content for the tool result — string, object, or array. The adapter converts to its SDK-native format. */
382
+ content: JsonValue;
322
383
  }
323
384
  /**
324
- * PostToolUse hook - called after successful tool execution
385
+ * Status of a workflow task
325
386
  */
326
- type PostToolUseHook<T extends ToolMap, TResult = unknown> = (ctx: PostToolUseHookContext<T, TResult>) => void | Promise<void>;
387
+ type TaskStatus = "pending" | "in_progress" | "completed";
327
388
  /**
328
- * Context for PostToolUseFailure hook - called when tool execution fails
389
+ * A task managed within a workflow for tracking work items
329
390
  */
330
- interface PostToolUseFailureHookContext<T extends ToolMap> {
331
- /** The tool call that failed */
332
- toolCall: ParsedToolCallUnion<T>;
333
- /** The error that occurred */
334
- error: Error;
335
- /** Thread identifier */
336
- threadId: string;
337
- /** Current turn number */
338
- turn: number;
391
+ interface WorkflowTask {
392
+ /** Unique task identifier */
393
+ id: string;
394
+ /** Brief, actionable title in imperative form */
395
+ subject: string;
396
+ /** Detailed description of what needs to be done */
397
+ description: string;
398
+ /** Present continuous form shown in spinner when in_progress */
399
+ activeForm: string;
400
+ /** Current status of the task */
401
+ status: TaskStatus;
402
+ /** Arbitrary key-value pairs for tracking */
403
+ metadata: Record<string, string>;
404
+ /** IDs of tasks that must complete before this one can start */
405
+ blockedBy: string[];
406
+ /** IDs of tasks that are waiting for this one to complete */
407
+ blocks: string[];
339
408
  }
340
409
  /**
341
- * PostToolUseFailure hook - called when tool execution fails
410
+ * Exit reasons for session termination
342
411
  */
343
- type PostToolUseFailureHook<T extends ToolMap> = (ctx: PostToolUseFailureHookContext<T>) => PostToolUseFailureHookResult | Promise<PostToolUseFailureHookResult>;
412
+ type SessionExitReason = "completed" | "max_turns" | "waiting_for_input" | "failed" | "cancelled";
344
413
  /**
345
- * Tool execution hooks the subset of hooks consumed by the tool router.
346
- * Session/message lifecycle hooks live in lib/hooks/types.ts.
414
+ * Helper to check if status is terminal
347
415
  */
348
- interface ToolRouterHooks<T extends ToolMap, TResult = unknown> {
349
- /** Called before each tool execution - can block or modify */
350
- onPreToolUse?: PreToolUseHook<T>;
351
- /** Called after each successful tool execution */
352
- onPostToolUse?: PostToolUseHook<T, TResult>;
353
- /** Called when tool execution fails */
354
- onPostToolUseFailure?: PostToolUseFailureHook<T>;
355
- }
416
+ declare function isTerminalStatus(status: AgentStatus): boolean;
417
+
356
418
  /**
357
- * Options for creating a tool router.
419
+ * A tool definition with a name, description, and Zod schema for arguments.
420
+ * Does not include a handler - use ToolWithHandler for tools with handlers.
358
421
  */
359
- interface ToolRouterOptions<T extends ToolMap> {
360
- /** Map of tools with their handlers */
361
- tools: T;
362
- /** Thread ID for appending tool results */
363
- threadId: string;
364
- /** Redis key suffix for thread storage. Defaults to 'messages'. */
365
- threadKey?: string;
366
- /** Function to append tool results to the thread (called automatically after each handler).
367
- * Accepts a Temporal activity proxy with {@link ActivityFunctionWithOptions}. */
368
- appendToolResult: AppendToolResultFn;
369
- /** Whether to process tools in parallel (default: true) */
370
- parallel?: boolean;
371
- /** Tool execution lifecycle hooks (pre/post tool use) */
372
- hooks?: ToolRouterHooks<T, ToolCallResultUnion<InferToolResults<T>>>;
373
- /** Additional tools to auto-register (e.g. subagent, skill tools built by register helpers) */
374
- plugins?: ToolMap[string][];
422
+ interface ToolDefinition<TName extends string = string, TSchema extends z.ZodType = z.ZodType> {
423
+ name: TName;
424
+ description: string;
425
+ schema: TSchema;
426
+ strict?: boolean;
427
+ max_uses?: number;
375
428
  }
376
429
  /**
377
- * The tool router interface with full type inference for both args and results.
430
+ * A tool definition with an integrated handler function.
431
+ * This is the primary type for defining tools in the router.
378
432
  */
379
- interface ToolRouter<T extends ToolMap> {
380
- /** Check if the router has any tools */
381
- hasTools(): boolean;
382
- /**
383
- * Parse and validate a raw tool call against the router's tools.
384
- * Returns a typed tool call with validated arguments.
385
- */
386
- parseToolCall(toolCall: RawToolCall): ParsedToolCallUnion<T>;
387
- /**
388
- * Check if a tool with the given name exists in the router.
389
- */
390
- hasTool(name: string): boolean;
391
- /**
392
- * Get all tool names in the router.
393
- */
394
- getToolNames(): ToolNames<T>[];
395
- /**
396
- * Get all tool definitions (without handlers) for passing to LLM.
397
- */
398
- getToolDefinitions(): ToolDefinition[];
399
- /**
400
- * Process all tool calls using the registered handlers.
401
- * Returns typed results based on handler return types.
402
- * @param toolCalls - Array of parsed tool calls to process
403
- * @param context - Optional context including turn number for hooks
404
- */
405
- processToolCalls(toolCalls: ParsedToolCallUnion<T>[], context?: ProcessToolCallsContext): Promise<ProcessToolCallsResult<InferToolResults<T>>>;
406
- /**
407
- * Process tool calls matching a specific name with a custom handler.
408
- * Useful for overriding the default handler for specific cases.
409
- */
410
- processToolCallsByName<TName extends ToolNames<T>, TResult>(toolCalls: ParsedToolCallUnion<T>[], toolName: TName, handler: ToolHandler<ToolArgs<T, TName>, TResult>, context?: ProcessToolCallsContext): Promise<ToolCallResult<TName, TResult>[]>;
411
- /**
412
- * Filter tool calls by name.
413
- */
414
- filterByName<TName extends ToolNames<T>>(toolCalls: ParsedToolCallUnion<T>[], name: TName): ParsedToolCall<TName, ToolArgs<T, TName>>[];
415
- /**
416
- * Check if any tool call matches the given name.
417
- */
418
- hasToolCall(toolCalls: ParsedToolCallUnion<T>[], name: ToolNames<T>): boolean;
419
- /**
420
- * Filter results by tool name.
421
- */
422
- getResultsByName<TName extends ToolNames<T>>(results: ToolCallResultUnion<InferToolResults<T>>[], name: TName): ToolCallResult<TName, ToolResult<T, TName>>[];
433
+ interface ToolWithHandler<TName extends string = string, TSchema extends z.ZodType = z.ZodType, TResult = unknown, TContext extends RouterContext = RouterContext, TToolResponse = JsonValue> {
434
+ name: TName;
435
+ description: string;
436
+ schema: TSchema;
437
+ handler: ToolHandler<z.infer<TSchema>, TResult, TContext, TToolResponse>;
438
+ strict?: boolean;
439
+ max_uses?: number;
440
+ /** Whether this tool is available to the agent (default: true). Disabled tools are excluded from definitions and rejected at parse time. */
441
+ enabled?: boolean | (() => boolean);
442
+ /** Per-tool lifecycle hooks (run in addition to global hooks) */
443
+ hooks?: ToolHooks<z.infer<TSchema>, TResult>;
423
444
  }
424
-
425
445
  /**
426
- * JSON primitive types that Temporal can serialize
446
+ * A map of tool keys to tool definitions with handlers.
447
+ *
448
+ * Handler uses `any` intentionally — this is a type-system boundary where heterogeneous
449
+ * tool types are stored together. Type safety for individual tools is enforced by
450
+ * `defineTool()` at the definition site and generic inference utilities like
451
+ * `InferToolResults<T>` at the consumption site.
427
452
  */
428
- type JsonPrimitive = string | number | boolean | null | undefined;
453
+ type ToolMap = Record<string, {
454
+ name: string;
455
+ description: string | (() => string);
456
+ schema: z.ZodType | (() => z.ZodType);
457
+ handler: ToolHandler<any, any, any, any>;
458
+ strict?: boolean;
459
+ max_uses?: number;
460
+ enabled?: boolean | (() => boolean);
461
+ hooks?: ToolHooks<any, any>;
462
+ }>;
429
463
  /**
430
- * JSON-serializable value (recursive type for Temporal compatibility)
464
+ * Extract the tool names from a tool map (uses the tool's name property, not the key).
431
465
  */
432
- type JsonValue = JsonPrimitive | JsonValue[] | {
433
- [key: string]: JsonValue;
434
- };
466
+ type ToolNames<T extends ToolMap> = T[keyof T]["name"];
435
467
  /**
436
- * Type constraint ensuring T only contains JSON-serializable values.
437
- * Use this for custom state to ensure Temporal workflow compatibility.
438
- *
439
- * Allows: primitives, arrays, plain objects, and JsonValue
440
- * Rejects: functions, symbols, undefined, class instances with methods
468
+ * A raw tool call as received from the LLM before parsing.
441
469
  */
442
- type JsonSerializable<T> = {
443
- [K in keyof T]: T[K] extends JsonValue ? T[K] : T[K] extends JsonPrimitive ? T[K] : T[K] extends (infer U)[] ? U extends JsonValue ? T[K] : JsonSerializable<U>[] : T[K] extends object ? JsonSerializable<T[K]> : never;
444
- };
470
+ interface RawToolCall {
471
+ id?: string;
472
+ name: string;
473
+ args: unknown;
474
+ }
445
475
  /**
446
- * Full state type combining base state with custom state
476
+ * A parsed tool call with validated arguments for a specific tool.
447
477
  */
448
- type AgentState<TCustom extends JsonSerializable<TCustom>> = BaseAgentState & TCustom;
478
+ interface ParsedToolCall<TName extends string = string, TArgs = unknown> {
479
+ id: string;
480
+ name: TName;
481
+ args: TArgs;
482
+ }
449
483
  /**
450
- * The slice of agent state that is persisted alongside the thread in the
451
- * thread store (e.g. Redis) so that a workflow can terminate, store its
452
- * state, and be continued or forked later with that state rehydrated.
453
- *
454
- * Only fields that make sense to carry across workflow runs belong here.
455
- * Runtime bookkeeping like status, version, turns, tools, fileTree, token
456
- * counters, and the system prompt is intentionally NOT persisted — each run
457
- * rebuilds those from scratch.
484
+ * Union type of all possible parsed tool calls from a tool map.
458
485
  */
459
- interface PersistedThreadState {
460
- /** Task map serialized as entries so it round-trips through JSON. */
461
- tasks: [string, WorkflowTask][];
462
- /** All custom state fields declared by the caller. */
463
- custom: Record<string, JsonValue>;
464
- }
486
+ type ParsedToolCallUnion<T extends ToolMap> = {
487
+ [K in keyof T]: ParsedToolCall<T[K]["name"], z.infer<T[K]["schema"]>>;
488
+ }[keyof T];
465
489
  /**
466
- * Agent state manager interface
467
- * Note: Temporal handlers must be set up in the workflow file due to
468
- * Temporal's workflow isolation requirements. This manager provides
469
- * the state and helpers needed for those handlers.
490
+ * Function signature for appending tool results to a thread.
470
491
  */
471
- interface AgentStateManager<TCustom extends JsonSerializable<TCustom>> {
472
- /** Typed query definition registered for this agent's state */
473
- readonly stateQuery: QueryDefinition<AgentState<TCustom>>;
474
- /** Typed update definition registered for waiting on this agent's state change */
475
- readonly stateChangeUpdate: UpdateDefinition<AgentState<TCustom>, [number]>;
476
- /** Get current status */
477
- getStatus(): AgentStatus;
478
- /** Check if agent is running */
479
- isRunning(): boolean;
480
- /** Check if agent is in terminal state */
481
- isTerminal(): boolean;
482
- /** Get current state version */
483
- getVersion(): number;
484
- /** Set status to RUNNING */
485
- run(): void;
486
- /** Set status to WAITING_FOR_INPUT */
487
- waitForInput(): void;
488
- /** Set status to COMPLETED */
489
- complete(): void;
490
- /** Set status to FAILED */
491
- fail(): void;
492
- /** Set status to CANCELLED */
493
- cancel(): void;
494
- /** Increment state version (call after state changes) */
495
- incrementVersion(): void;
496
- /** Increment turns (call after each turn) */
497
- incrementTurns(): void;
498
- /** Get current turns */
499
- getTurns(): number;
500
- /** Get the system prompt */
501
- getSystemPrompt(): unknown;
502
- /** Set the system prompt */
503
- setSystemPrompt(newSystemPrompt: unknown): void;
504
- /** Get a custom state value by key */
505
- get<K extends keyof TCustom>(key: K): TCustom[K];
506
- /** Set a custom state value by key */
507
- set<K extends keyof TCustom>(key: K, value: TCustom[K]): void;
508
- /** Bulk-merge a partial update into custom state */
509
- mergeUpdate(update: Partial<AgentState<TCustom>>): void;
510
- /** Get full state for query handler */
511
- getCurrentState(): AgentState<TCustom>;
512
- /** Check if should return from waitForStateChange */
513
- shouldReturnFromWait(lastKnownVersion: number): boolean;
514
- /** Get all tasks */
515
- getTasks(): WorkflowTask[];
516
- /** Get a task by ID */
517
- getTask(id: string): WorkflowTask | undefined;
518
- /** Add or update a task */
519
- setTask(task: WorkflowTask): void;
520
- /** Delete a task by ID */
521
- deleteTask(id: string): boolean;
522
- /** Set the tools (converts Zod schemas to JSON Schema for serialization) */
523
- setTools(newTools: ToolDefinition[]): void;
492
+ type AppendToolResultFn = ActivityFunctionWithOptions<(id: string, config: ToolResultConfig) => Promise<void>>;
493
+ /**
494
+ * The response from a tool handler.
495
+ * Contains the content for the tool message and the result to return from processToolCalls.
496
+ *
497
+ * Tools that don't return additional data should use `data: null` (TResult defaults to null).
498
+ * Tools that may fail to produce data should type TResult as `SomeType | null`.
499
+ */
500
+ interface ToolHandlerResponse<TResult = null, TToolResponse = JsonValue> {
501
+ /** Content sent back to the LLM as the tool call response */
502
+ toolResponse: TToolResponse;
503
+ /** Data returned to the workflow and hooks for further processing */
504
+ data: TResult;
524
505
  /**
525
- * Snapshot the fields that should survive across workflow runs
526
- * (tasks + all custom state). Safe to pass directly to
527
- * {@link ThreadOps.saveThreadState}. Rehydrate on the next run with
528
- * `mergeUpdate({ tasks: new Map(slice.tasks), ...slice.custom })`.
506
+ * When true, the tool result has already been appended to the thread
507
+ * by the handler itself (e.g. via `withAutoAppend`), so the router
508
+ * will skip the `appendToolResult` call. This avoids sending large
509
+ * payloads through Temporal's activity payload limit.
510
+ */
511
+ resultAppended?: boolean;
512
+ /**
513
+ * When true, the session will rewind: any in-flight parallel tool
514
+ * calls are cancelled and the LLM call is retried. The session reuses
515
+ * the same `assistantMessageId` for the retry; the next `runAgent`
516
+ * activity truncates the thread from that id on entry, wiping the
517
+ * triggering assistant message and any tool results already appended
518
+ * before re-invoking the LLM.
519
+ *
520
+ * The `toolResponse` for a rewinding tool call is ignored (never
521
+ * appended) since the thread is rewound back to the pre-assistant
522
+ * state on the next invocation.
529
523
  */
530
- getPersistedSlice(): PersistedThreadState;
531
- /** Update the usage */
532
- updateUsage(usage: TokenUsage): void;
533
- /** Get the total usage */
534
- getTotalUsage(): {
535
- totalInputTokens: number;
536
- totalOutputTokens: number;
537
- totalCachedWriteTokens: number;
538
- totalCachedReadTokens: number;
539
- totalReasonTokens: number;
540
- turns: number;
541
- };
524
+ rewind?: boolean;
525
+ /** Token usage from the tool execution (e.g. child agent invocations) */
526
+ usage?: TokenUsage;
527
+ /** Thread ID used by the handler (surfaced to the LLM for subagent thread continuation) */
528
+ threadId?: string;
529
+ /** Sandbox ID created or used by the handler (e.g. child agent sandbox) */
530
+ sandboxId?: string;
531
+ /** Snapshot captured on exit when `sandboxShutdown === "snapshot"`. */
532
+ snapshot?: SandboxSnapshot;
533
+ /** Snapshot captured immediately after sandbox seeding (before the agent loop starts) when `sandbox.mode === "new"` and `sandboxShutdown === "snapshot"`. Intended as a reusable "base" for new threads that want to skip re-seeding. */
534
+ baseSnapshot?: SandboxSnapshot;
535
+ /** Unvalidated metadata passthrough from handler to hooks (e.g. infrastructure state) */
536
+ metadata?: Record<string, unknown>;
542
537
  }
543
-
544
538
  /**
545
- * Ephemeral virtual filesystem backed by a {@link FileResolver}.
546
- *
547
- * Created fresh for each tool invocation from the current workflow file tree.
548
- * Directory structure is inferred from file paths. All mutations are tracked
549
- * and can be retrieved via {@link getMutations} after the handler completes.
539
+ * Base context the router always injects into every handler invocation.
540
+ * Handlers can rely on these fields being present without casting.
550
541
  */
551
- declare class VirtualFileSystem<TCtx = unknown, TMeta = FileEntryMetadata> implements SandboxFileSystem {
552
- private resolver;
553
- private ctx;
554
- readonly workspaceBase: string;
555
- private entries;
556
- private directories;
557
- private mutations;
558
- private inlineFiles;
559
- constructor(tree: FileEntry<TMeta>[], resolver: FileResolver<TCtx, TMeta>, ctx: TCtx, workspaceBase?: string, inlineFiles?: Record<string, string>);
560
- /** Return all mutations accumulated during this invocation. */
561
- getMutations(): TreeMutation<TMeta>[];
562
- /** Look up a file entry by virtual path. */
563
- getEntry(path: string): FileEntry<TMeta> | undefined;
564
- readFile(path: string): Promise<string>;
565
- readFileBuffer(path: string): Promise<Uint8Array>;
542
+ interface RouterContext {
543
+ threadId: string;
544
+ /** Redis key suffix for thread storage. Defaults to 'messages'. */
545
+ threadKey?: string;
546
+ toolCallId: string;
547
+ toolName: string;
548
+ sandboxId?: string;
566
549
  /**
567
- * Resolve the string content for an entry, preferring inline content
568
- * carried on the entry itself before consulting the resolver. Used by
569
- * `readFile`, `appendFile`, and `cp` so all read paths agree on the
570
- * lookup precedence.
550
+ * Id of the assistant message that issued this tool call (the message
551
+ * the session passed as `assistantMessageId` into `runAgent`). Present
552
+ * for any tool call processed through `processToolCalls` from a
553
+ * session; may be absent when the router is driven manually (e.g.
554
+ * tests, custom orchestrators).
555
+ *
556
+ * Subagent handlers that fork the parent's thread mid-call use this
557
+ * to truncate the orphan trailing assistant message from the forked
558
+ * thread so the child's first model call sees a well-formed history.
571
559
  */
572
- private readEntryContent;
573
- exists(path: string): Promise<boolean>;
574
- stat(path: string): Promise<FileStat>;
575
- readdir(path: string): Promise<string[]>;
576
- readdirWithFileTypes(path: string): Promise<DirentEntry[]>;
577
- writeFile(path: string, content: string | Uint8Array): Promise<void>;
578
- appendFile(path: string, content: string | Uint8Array): Promise<void>;
579
- mkdir(_path: string, _options?: {
580
- recursive?: boolean;
581
- }): Promise<void>;
582
- rm(path: string, options?: {
583
- recursive?: boolean;
584
- force?: boolean;
585
- }): Promise<void>;
586
- cp(src: string, dest: string, _options?: {
587
- recursive?: boolean;
588
- }): Promise<void>;
589
- mv(src: string, dest: string): Promise<void>;
590
- readlink(_path: string): Promise<string>;
591
- resolvePath(base: string, path: string): string;
592
- private addParentDirectories;
593
- }
594
-
595
- /** Allowed value types for file-entry metadata. */
596
- type FileEntryMetadata = Record<string, string | number | boolean | null>;
597
- /** JSON-serializable metadata for a single file in the virtual tree. */
598
- interface FileEntry<TMeta = FileEntryMetadata> {
599
- id: string;
600
- /** Virtual path, e.g. "/src/index.ts" */
601
- path: string;
602
- size: number;
603
- /** ISO-8601 date string (JSON-safe) */
604
- mtime: string;
605
- metadata: TMeta;
560
+ assistantMessageId?: string;
606
561
  /**
607
- * Optional inline content carried directly on the entry. When present the
608
- * {@link VirtualFileSystem} returns this string from `readFile` /
609
- * `readFileBuffer` (and uses it as the source for `cp` / `appendFile`)
610
- * without consulting the resolver.
611
- *
612
- * Use this for files that exist purely in workflow state and have no
613
- * backing in the consumer's data layer (e.g. skill resources bundled at
614
- * session creation time). Because the content travels with the entry,
615
- * any tool handler that constructs a `VirtualFileSystem` from `fileTree`
616
- * sees the same content — no separate `inlineFiles` plumbing required.
562
+ * Persist the parent session's current `PersistedThreadState` slice
563
+ * (tasks + custom state) to the durable thread store. Wired up by
564
+ * the session absent for manually-driven routers (tests, custom
565
+ * orchestrators).
617
566
  *
618
- * Read-only: entries with `inlineContent` reject in-place mutations
619
- * (`writeFile`, `appendFile`, `rm`, `mv` of the entry, `cp` over the
620
- * entry as destination). The resolver has no contract for the synthetic
621
- * `id` these entries carry, so attempting a mutation throws an `EROFS`
622
- * error instead of silently routing a doomed call through the resolver.
567
+ * Subagent handlers invoke this before spawning a child that will
568
+ * read the parent's thread (`newThreadSource: "from-parent"` or an
569
+ * explicit parent threadId): the parent's slice otherwise only
570
+ * lands in storage at session-exit time, so the child would load a
571
+ * stale (or empty) snapshot. Best-effort failures are logged by
572
+ * the session but never thrown.
623
573
  */
624
- inlineContent?: string;
574
+ persistThreadState?: () => Promise<void>;
625
575
  }
626
576
  /**
627
- * Flat list of file entries.
628
- * Directories are inferred from file paths at runtime.
577
+ * A handler function for a specific tool.
578
+ * Receives the parsed args and a context that always includes {@link RouterContext}
579
+ * fields, plus any additional properties when TContext extends RouterContext.
629
580
  */
630
- type VirtualFileTree<TMeta = FileEntryMetadata> = FileEntry<TMeta>[];
631
- type TreeMutation<TMeta = FileEntryMetadata> = {
632
- type: "add";
633
- entry: FileEntry<TMeta>;
634
- } | {
635
- type: "remove";
636
- path: string;
637
- } | {
638
- type: "update";
639
- path: string;
640
- entry: Partial<FileEntry<TMeta>>;
641
- };
581
+ type ToolHandler<TArgs, TResult, TContext extends RouterContext = RouterContext, TToolResponse = JsonValue> = (args: TArgs, context: TContext) => ToolHandlerResponse<TResult, TToolResponse> | Promise<ToolHandlerResponse<TResult, TToolResponse>>;
642
582
  /**
643
- * Consumer-provided bridge to the existing DB / S3 / CRUD layer.
644
- *
645
- * Generic over `TCtx` so every call receives workflow-level context
646
- * (e.g. `{ projectId: string }`) without the resolver holding state.
583
+ * Activity-compatible tool handler that always returns a Promise.
584
+ * Use this for tool handlers registered as Temporal activities.
647
585
  *
648
- * Generic over `TMeta` so resolved entries carry typed metadata.
586
+ * @example
587
+ * ```typescript
588
+ * const readHandler: ActivityToolHandler<
589
+ * FileReadArgs,
590
+ * ReadResult,
591
+ * RouterContext & { scopedNodes: FileNode[]; provider: FileSystemProvider }
592
+ * > = async (args, context) => {
593
+ * // context.threadId, context.sandboxId etc. are always available
594
+ * return readHandler(args, context.scopedNodes, context.provider);
595
+ * };
596
+ * ```
649
597
  */
650
- interface FileResolver<TCtx = unknown, TMeta = FileEntryMetadata> {
651
- resolveEntries(ctx: TCtx): Promise<FileEntry<TMeta>[]>;
652
- readFile(id: string, ctx: TCtx, metadata: TMeta): Promise<string>;
653
- readFileBuffer(id: string, ctx: TCtx, metadata: TMeta): Promise<Uint8Array>;
654
- writeFile(id: string, content: string | Uint8Array, ctx: TCtx, metadata: TMeta): Promise<void>;
655
- createFile(path: string, content: string | Uint8Array, ctx: TCtx): Promise<FileEntry<TMeta>>;
656
- deleteFile(id: string, ctx: TCtx, metadata: TMeta): Promise<void>;
657
- }
598
+ type ActivityToolHandler<TArgs, TResult, TContext extends RouterContext = RouterContext, TToolResponse = JsonValue> = (args: TArgs, context: TContext) => Promise<ToolHandlerResponse<TResult, TToolResponse>>;
658
599
  /**
659
- * Workflow-side operations for the virtual filesystem.
660
- *
661
- * Unlike {@link SandboxOps}, this only exposes what is actually needed:
662
- * resolving the initial file tree from the consumer's data layer.
600
+ * Extract the args type for a specific tool name from a tool map.
663
601
  */
664
- interface VirtualFsOps<TCtx = unknown, TMeta = FileEntryMetadata> {
665
- resolveFileTree(ctx: TCtx): Promise<{
666
- fileTree: FileEntry<TMeta>[];
667
- }>;
602
+ type ToolArgs<T extends ToolMap, TName extends ToolNames<T>> = z.infer<Extract<T[keyof T], {
603
+ name: TName;
604
+ }>["schema"]>;
605
+ /**
606
+ * Extract the result type for a specific tool name from a tool map.
607
+ */
608
+ type ToolResult<T extends ToolMap, TName extends ToolNames<T>> = Extract<T[keyof T], {
609
+ name: TName;
610
+ }>["handler"] extends ToolHandler<unknown, infer R, RouterContext, any> ? Awaited<R> : never;
611
+ /**
612
+ * The result of processing a tool call.
613
+ */
614
+ interface ToolCallResult<TName extends string = string, TResult = unknown> {
615
+ toolCallId: string;
616
+ name: TName;
617
+ data: TResult;
618
+ usage?: TokenUsage;
619
+ /** Unvalidated metadata passthrough from handler to hooks (e.g. infrastructure state) */
620
+ metadata?: Record<string, unknown>;
668
621
  }
669
622
  /**
670
- * Maps generic {@link VirtualFsOps} method names to scope-prefixed names.
671
- *
672
- * @example
673
- * ```typescript
674
- * type Ops = PrefixedVirtualFsOps<"codingAgent">;
675
- * // → { virtualFsCodingAgentResolveFileTree: ... }
676
- * ```
623
+ * Infer result types from a tool map based on handler return types.
677
624
  */
678
- type PrefixedVirtualFsOps<TPrefix extends string, TCtx = unknown, TMeta = FileEntryMetadata> = {
679
- [K in keyof VirtualFsOps<TCtx, TMeta> as `virtualFs${Capitalize<TPrefix>}${Capitalize<K & string>}`]: VirtualFsOps<TCtx, TMeta>[K];
625
+ type InferToolResults<T extends ToolMap> = {
626
+ [K in keyof T as T[K]["name"]]: T[K]["handler"] extends ToolHandler<any, infer R, any, any> ? Awaited<R> : never;
680
627
  };
681
628
  /**
682
- * The portion of workflow `AgentState` that the virtual filesystem reads via
683
- * {@link queryParentWorkflowState}. Populated automatically by the session
684
- * when `virtualFs` config is provided.
629
+ * Union of all possible tool call results based on handler return types.
685
630
  */
686
- interface VirtualFsState<TCtx = unknown, TMeta = FileEntryMetadata> {
687
- fileTree: FileEntry<TMeta>[];
688
- virtualFsCtx: TCtx;
689
- /** In-memory file contents keyed by path, bypassing the resolver (e.g. skill resources). */
690
- inlineFiles?: Record<string, string>;
631
+ type ToolCallResultUnion<TResults extends Record<string, unknown>> = {
632
+ [TName in keyof TResults & string]: ToolCallResult<TName, TResults[TName]>;
633
+ }[keyof TResults & string];
634
+ /**
635
+ * Context passed to processToolCalls for hook execution and handler invocation
636
+ */
637
+ interface ProcessToolCallsContext {
638
+ /** Current turn number (for hooks) */
639
+ turn?: number;
640
+ /** Active sandbox ID (when a sandbox is configured for this session) */
641
+ sandboxId?: string;
642
+ /**
643
+ * Id of the assistant message that produced these tool calls. The
644
+ * router forwards it into every handler's {@link RouterContext} so
645
+ * handlers can reference the message they were issued from (e.g.
646
+ * subagent forks that need to truncate the orphan assistant message
647
+ * out of a parent-forked thread).
648
+ */
649
+ assistantMessageId?: string;
650
+ /**
651
+ * Optional callback that flushes the session's in-memory
652
+ * `PersistedThreadState` slice to the durable thread store. The
653
+ * router forwards it into every handler's {@link RouterContext}
654
+ * verbatim. The session uses this to let mid-loop tool handlers
655
+ * (notably subagents that fork or continue the parent's thread)
656
+ * persist the parent's slice before the child reads it.
657
+ */
658
+ persistThreadState?: () => Promise<void>;
691
659
  }
692
660
  /**
693
- * Extended router context injected by {@link withVirtualFs}.
694
- * Guarantees a live (ephemeral) virtual filesystem built from the workflow
695
- * file tree.
661
+ * Signal that a tool handler requested a rewind. Attached to the
662
+ * {@link ProcessToolCallsResult} so the session can reuse the same
663
+ * `assistantMessageId` for the retry; the next `runAgent` activity
664
+ * then truncates the thread from that id on entry.
696
665
  */
697
- interface VirtualFsContext<TCtx = unknown, TMeta = FileEntryMetadata> extends RouterContext {
698
- virtualFs: VirtualFileSystem<TCtx, TMeta>;
666
+ interface RewindSignal {
667
+ toolCallId: string;
668
+ toolName: string;
699
669
  }
700
-
701
670
  /**
702
- * Agent execution status
671
+ * Result returned by {@link ToolRouter.processToolCalls}.
672
+ *
673
+ * The object is a standard array of tool call results for successful
674
+ * tool calls (cancelled or rewinding siblings are omitted), extended
675
+ * with a `rewind` property when any tool in the batch requested a
676
+ * rewind. Using an array-with-property lets existing code that treats
677
+ * the return value as `ToolCallResultUnion[]` continue to work.
703
678
  */
704
- type AgentStatus = "RUNNING" | "WAITING_FOR_INPUT" | "COMPLETED" | "FAILED" | "CANCELLED";
679
+ type ProcessToolCallsResult<TResults extends Record<string, unknown>> = ToolCallResultUnion<TResults>[] & {
680
+ rewind?: RewindSignal;
681
+ };
705
682
  /**
706
- * Base state that all agents must have
683
+ * Result from PreToolUse hook - can block or modify execution
707
684
  */
708
- interface BaseAgentState {
709
- tools: SerializableToolDefinition[];
710
- status: AgentStatus;
711
- version: number;
712
- turns: number;
713
- tasks: Map<string, WorkflowTask>;
714
- fileTree: FileEntry[];
715
- /** In-memory file contents keyed by path, bypassing the resolver (e.g. skill resources). */
716
- inlineFiles?: Record<string, string>;
717
- virtualFsCtx?: unknown;
718
- systemPrompt?: unknown;
719
- totalInputTokens: number;
720
- totalOutputTokens: number;
721
- cachedWriteTokens: number;
722
- cachedReadTokens: number;
685
+ interface PreToolUseHookResult {
686
+ /** Skip this tool call entirely */
687
+ skip?: boolean;
688
+ /** Modified args to use instead (must match schema) */
689
+ modifiedArgs?: unknown;
723
690
  }
724
691
  /**
725
- * File representation for agent workflows
692
+ * Result from PostToolUseFailure hook - can recover from errors
726
693
  */
727
- interface AgentFile {
728
- /** Database/S3 file ID */
729
- id: string;
730
- /** Virtual path for agent (e.g., "evidence/invoice.pdf") */
731
- path: string;
732
- /** Original filename */
733
- filename: string;
734
- /** Generic description for prompt */
735
- description?: string;
736
- /** MIME type of the file */
737
- mimeType?: string;
738
- }
739
- interface TokenUsage {
740
- inputTokens?: number;
741
- outputTokens?: number;
742
- cachedWriteTokens?: number;
743
- cachedReadTokens?: number;
744
- reasonTokens?: number;
694
+ interface PostToolUseFailureHookResult {
695
+ /** Provide a fallback result instead of throwing */
696
+ fallbackContent?: JsonValue;
697
+ /** Whether to suppress the error (still logs, but continues) */
698
+ suppress?: boolean;
745
699
  }
746
700
  /**
747
- * Configuration for a Zeitlich agent
701
+ * Per-tool lifecycle hooks - defined directly on a tool definition.
702
+ * Runs in addition to global hooks (global pre → tool pre → execute → tool post → global post).
748
703
  */
749
- interface AgentConfig {
750
- /** The name of the agent, should be unique within the workflows, ideally Pascal Case */
751
- agentName: string;
752
- /** Description, used for sub agents */
753
- description?: string;
704
+ interface ToolHooks<TArgs = unknown, TResult = unknown> {
705
+ /** Called before this tool executes - can skip or modify args */
706
+ onPreToolUse?: (ctx: {
707
+ args: TArgs;
708
+ threadId: string;
709
+ turn: number;
710
+ }) => PreToolUseHookResult | Promise<PreToolUseHookResult>;
711
+ /** Called after this tool executes successfully */
712
+ onPostToolUse?: (ctx: {
713
+ args: TArgs;
714
+ result: TResult;
715
+ threadId: string;
716
+ turn: number;
717
+ durationMs: number;
718
+ metadata?: Record<string, unknown>;
719
+ }) => void | Promise<void>;
720
+ /** Called when this tool execution fails */
721
+ onPostToolUseFailure?: (ctx: {
722
+ args: TArgs;
723
+ error: Error;
724
+ threadId: string;
725
+ turn: number;
726
+ }) => PostToolUseFailureHookResult | Promise<PostToolUseFailureHookResult>;
754
727
  }
755
728
  /**
756
- * A JSON-serializable tool definition for state storage.
757
- * Uses a plain JSON Schema object instead of a live Zod instance,
758
- * so it survives Temporal serialization without losing constraints (min, max, etc.).
729
+ * Context for PreToolUse hook - called before tool execution
759
730
  */
760
- interface SerializableToolDefinition {
761
- name: string;
762
- description: string;
763
- schema: Record<string, unknown>;
764
- strict?: boolean;
765
- max_uses?: number;
731
+ interface PreToolUseHookContext<T extends ToolMap> {
732
+ /** The tool call about to be executed */
733
+ toolCall: ParsedToolCallUnion<T>;
734
+ /** Thread identifier */
735
+ threadId: string;
736
+ /** Current turn number */
737
+ turn: number;
766
738
  }
767
739
  /**
768
- * Configuration passed to runAgent activity
740
+ * PreToolUse hook - called before tool execution, can block or modify
769
741
  */
770
- interface RunAgentConfig extends AgentConfig {
771
- /** The thread ID to use for the session */
742
+ type PreToolUseHook<T extends ToolMap> = (ctx: PreToolUseHookContext<T>) => PreToolUseHookResult | Promise<PreToolUseHookResult>;
743
+ /**
744
+ * Context for PostToolUse hook - called after successful tool execution
745
+ */
746
+ interface PostToolUseHookContext<T extends ToolMap, TResult = unknown> {
747
+ /** The tool call that was executed */
748
+ toolCall: ParsedToolCallUnion<T>;
749
+ /** The result from the tool handler */
750
+ result: TResult;
751
+ /** Thread identifier */
772
752
  threadId: string;
773
- /** Redis key suffix for thread storage. Defaults to 'messages'. */
774
- threadKey?: string;
775
- /** Metadata for the session */
776
- metadata?: Record<string, unknown>;
777
- /**
778
- * The id under which the assistant message produced by this call will
779
- * be appended. The activity truncates the thread from this id on
780
- * entry (no-op on the first attempt) so that:
781
- *
782
- * - Rewind retries can reuse the same id and the previous (bad)
783
- * assistant + its tool results are wiped before the retry LLM call.
784
- * - Resetting the Temporal workflow to this activity restores the
785
- * pre-call thread state: replay re-truncates, re-invokes, and
786
- * appends under the same id.
787
- */
788
- assistantMessageId: string;
753
+ /** Current turn number */
754
+ turn: number;
755
+ /** Execution duration in milliseconds */
756
+ durationMs: number;
789
757
  }
790
758
  /**
791
- * Configuration for appending a tool result
759
+ * PostToolUse hook - called after successful tool execution
792
760
  */
793
- interface ToolResultConfig {
761
+ type PostToolUseHook<T extends ToolMap, TResult = unknown> = (ctx: PostToolUseHookContext<T, TResult>) => void | Promise<void>;
762
+ /**
763
+ * Context for PostToolUseFailure hook - called when tool execution fails
764
+ */
765
+ interface PostToolUseFailureHookContext<T extends ToolMap> {
766
+ /** The tool call that failed */
767
+ toolCall: ParsedToolCallUnion<T>;
768
+ /** The error that occurred */
769
+ error: Error;
770
+ /** Thread identifier */
794
771
  threadId: string;
795
- /** Redis key suffix for thread storage. Defaults to 'messages'. */
796
- threadKey?: string;
797
- toolCallId: string;
798
- /** The name of the tool that produced this result */
799
- toolName: string;
800
- /** Content for the tool result — string, object, or array. The adapter converts to its SDK-native format. */
801
- content: JsonValue;
772
+ /** Current turn number */
773
+ turn: number;
802
774
  }
803
775
  /**
804
- * Status of a workflow task
776
+ * PostToolUseFailure hook - called when tool execution fails
805
777
  */
806
- type TaskStatus = "pending" | "in_progress" | "completed";
778
+ type PostToolUseFailureHook<T extends ToolMap> = (ctx: PostToolUseFailureHookContext<T>) => PostToolUseFailureHookResult | Promise<PostToolUseFailureHookResult>;
807
779
  /**
808
- * A task managed within a workflow for tracking work items
780
+ * Tool execution hooks the subset of hooks consumed by the tool router.
781
+ * Session/message lifecycle hooks live in lib/hooks/types.ts.
809
782
  */
810
- interface WorkflowTask {
811
- /** Unique task identifier */
812
- id: string;
813
- /** Brief, actionable title in imperative form */
814
- subject: string;
815
- /** Detailed description of what needs to be done */
816
- description: string;
817
- /** Present continuous form shown in spinner when in_progress */
818
- activeForm: string;
819
- /** Current status of the task */
820
- status: TaskStatus;
821
- /** Arbitrary key-value pairs for tracking */
822
- metadata: Record<string, string>;
823
- /** IDs of tasks that must complete before this one can start */
824
- blockedBy: string[];
825
- /** IDs of tasks that are waiting for this one to complete */
826
- blocks: string[];
783
+ interface ToolRouterHooks<T extends ToolMap, TResult = unknown> {
784
+ /** Called before each tool execution - can block or modify */
785
+ onPreToolUse?: PreToolUseHook<T>;
786
+ /** Called after each successful tool execution */
787
+ onPostToolUse?: PostToolUseHook<T, TResult>;
788
+ /** Called when tool execution fails */
789
+ onPostToolUseFailure?: PostToolUseFailureHook<T>;
827
790
  }
828
791
  /**
829
- * Exit reasons for session termination
792
+ * Options for creating a tool router.
830
793
  */
831
- type SessionExitReason = "completed" | "max_turns" | "waiting_for_input" | "failed" | "cancelled";
794
+ interface ToolRouterOptions<T extends ToolMap> {
795
+ /** Map of tools with their handlers */
796
+ tools: T;
797
+ /** Thread ID for appending tool results */
798
+ threadId: string;
799
+ /** Redis key suffix for thread storage. Defaults to 'messages'. */
800
+ threadKey?: string;
801
+ /** Function to append tool results to the thread (called automatically after each handler).
802
+ * Accepts a Temporal activity proxy with {@link ActivityFunctionWithOptions}. */
803
+ appendToolResult: AppendToolResultFn;
804
+ /** Whether to process tools in parallel (default: true) */
805
+ parallel?: boolean;
806
+ /** Tool execution lifecycle hooks (pre/post tool use) */
807
+ hooks?: ToolRouterHooks<T, ToolCallResultUnion<InferToolResults<T>>>;
808
+ /** Additional tools to auto-register (e.g. subagent, skill tools built by register helpers) */
809
+ plugins?: ToolMap[string][];
810
+ }
832
811
  /**
833
- * Helper to check if status is terminal
812
+ * The tool router interface with full type inference for both args and results.
834
813
  */
835
- declare function isTerminalStatus(status: AgentStatus): boolean;
814
+ interface ToolRouter<T extends ToolMap> {
815
+ /** Check if the router has any tools */
816
+ hasTools(): boolean;
817
+ /**
818
+ * Parse and validate a raw tool call against the router's tools.
819
+ * Returns a typed tool call with validated arguments.
820
+ */
821
+ parseToolCall(toolCall: RawToolCall): ParsedToolCallUnion<T>;
822
+ /**
823
+ * Check if a tool with the given name exists in the router.
824
+ */
825
+ hasTool(name: string): boolean;
826
+ /**
827
+ * Get all tool names in the router.
828
+ */
829
+ getToolNames(): ToolNames<T>[];
830
+ /**
831
+ * Get all tool definitions (without handlers) for passing to LLM.
832
+ */
833
+ getToolDefinitions(): ToolDefinition[];
834
+ /**
835
+ * Process all tool calls using the registered handlers.
836
+ * Returns typed results based on handler return types.
837
+ * @param toolCalls - Array of parsed tool calls to process
838
+ * @param context - Optional context including turn number for hooks
839
+ */
840
+ processToolCalls(toolCalls: ParsedToolCallUnion<T>[], context?: ProcessToolCallsContext): Promise<ProcessToolCallsResult<InferToolResults<T>>>;
841
+ /**
842
+ * Process tool calls matching a specific name with a custom handler.
843
+ * Useful for overriding the default handler for specific cases.
844
+ */
845
+ processToolCallsByName<TName extends ToolNames<T>, TResult>(toolCalls: ParsedToolCallUnion<T>[], toolName: TName, handler: ToolHandler<ToolArgs<T, TName>, TResult>, context?: ProcessToolCallsContext): Promise<ToolCallResult<TName, TResult>[]>;
846
+ /**
847
+ * Filter tool calls by name.
848
+ */
849
+ filterByName<TName extends ToolNames<T>>(toolCalls: ParsedToolCallUnion<T>[], name: TName): ParsedToolCall<TName, ToolArgs<T, TName>>[];
850
+ /**
851
+ * Check if any tool call matches the given name.
852
+ */
853
+ hasToolCall(toolCalls: ParsedToolCallUnion<T>[], name: ToolNames<T>): boolean;
854
+ /**
855
+ * Filter results by tool name.
856
+ */
857
+ getResultsByName<TName extends ToolNames<T>>(results: ToolCallResultUnion<InferToolResults<T>>[], name: TName): ToolCallResult<TName, ToolResult<T, TName>>[];
858
+ }
836
859
 
837
860
  /**
838
861
  * Context for SessionStart hook - called when session begins
@@ -1843,4 +1866,4 @@ interface ZeitlichSession<M = unknown, HasSandbox extends boolean = boolean> {
1843
1866
  }): Promise<SessionResult<M, T, HasSandbox>>;
1844
1867
  }
1845
1868
 
1846
- export { type SandboxShutdown as $, type AgentResponse as A, type BaseAgentState as B, type PostToolUseFailureHookContext as C, type PostToolUseFailureHookResult as D, type PostToolUseHook as E, type FileEntryMetadata as F, type PostToolUseHookContext as G, type Hooks as H, type InferToolResults as I, type JsonValue as J, type PreHumanMessageAppendHook as K, type PreHumanMessageAppendHookContext as L, type ModelInvokerConfig as M, type PreToolUseHook as N, type PreToolUseHookContext as O, type PersistedThreadState as P, type PreToolUseHookResult as Q, type RouterContext as R, type ScopedPrefix as S, type ThreadOps as T, type ProcessToolCallsContext as U, type VirtualFsContext as V, type ProcessToolCallsResult as W, type RawToolCall as X, type RewindSignal as Y, type RunAgentActivity as Z, type SandboxInit as _, type ModelInvoker as a, type SerializableToolDefinition as a0, type SessionConfig as a1, type SessionEndHook as a2, type SessionEndHookContext as a3, type SessionExitReason as a4, type SessionRequiredCaps as a5, type SessionResult as a6, type SessionStartHook as a7, type SessionStartHookContext as a8, type SubagentChildWorkflowOptions as a9, VirtualFileSystem as aA, type VirtualFileTree as aB, type VirtualFsOps as aC, type VirtualFsState as aD, type WorkflowTask as aE, type ZeitlichSession as aF, isTerminalStatus as aG, type ToolRouterOptions as aH, type SubagentConfig as aa, type SubagentContinuationCaps as ab, type SubagentDefinition as ac, type SubagentFnResult as ad, type SubagentHandlerResponse as ae, type SubagentHooks as af, type SubagentSandboxConfig as ag, type SubagentSandboxShutdown as ah, type SubagentSessionInput as ai, type SubagentWorkflow as aj, type SubagentWorkflowInput as ak, type TaskStatus as al, type ThreadInit as am, type TokenUsage as an, type ToolArgs as ao, type ToolCallResult as ap, type ToolCallResultUnion as aq, type ToolDefinition as ar, type ToolHandler as as, type ToolHooks as at, type ToolMap as au, type ToolNames as av, type ToolResult as aw, type ToolRouter as ax, type ToolRouterHooks as ay, type ToolWithHandler as az, type PrefixedThreadOps as b, type ToolHandlerResponse as c, type ActivityToolHandler as d, type ToolResultConfig as e, type RunAgentConfig as f, type SkillProvider as g, type SkillMetadata as h, type Skill as i, type FileResolver as j, type TreeMutation as k, type PrefixedVirtualFsOps as l, type AgentConfig as m, type AgentFile as n, type AgentState as o, type AgentStateManager as p, type AgentStatus as q, type AppendToolResultFn as r, type FileEntry as s, type JsonPrimitive as t, type JsonSerializable as u, type ParsedToolCall as v, type ParsedToolCallUnion as w, type PostHumanMessageAppendHook as x, type PostHumanMessageAppendHookContext as y, type PostToolUseFailureHook as z };
1869
+ export { type SandboxShutdown as $, type AgentResponse as A, type BaseAgentState as B, type PostToolUseFailureHookContext as C, type PostToolUseFailureHookResult as D, type PostToolUseHook as E, type FileEntryMetadata as F, type PostToolUseHookContext as G, type Hooks as H, type InferToolResults as I, type JsonValue as J, type PreHumanMessageAppendHook as K, type PreHumanMessageAppendHookContext as L, type ModelInvokerConfig as M, type PreToolUseHook as N, type PreToolUseHookContext as O, type PersistedThreadState as P, type PreToolUseHookResult as Q, type RouterContext as R, type ScopedPrefix as S, type ThreadOps as T, type ProcessToolCallsContext as U, type VirtualFsContext as V, type ProcessToolCallsResult as W, type RawToolCall as X, type RewindSignal as Y, type RunAgentActivity as Z, type SandboxInit as _, type ModelInvoker as a, type SerializableToolDefinition as a0, type SessionConfig as a1, type SessionEndHook as a2, type SessionEndHookContext as a3, type SessionExitReason as a4, type SessionRequiredCaps as a5, type SessionResult as a6, type SessionStartHook as a7, type SessionStartHookContext as a8, type SubagentChildWorkflowOptions as a9, VirtualFileSystem as aA, type VirtualFileTree as aB, type VirtualFsOps as aC, type VirtualFsState as aD, type WorkflowTask as aE, type ZeitlichSession as aF, isTerminalStatus as aG, type ToolRouterOptions as aH, type SubagentConfig as aa, type SubagentContinuationCaps as ab, type SubagentDefinition as ac, type SubagentFnResult as ad, type SubagentHandlerResponse as ae, type SubagentHooks as af, type SubagentSandboxConfig as ag, type SubagentSandboxShutdown as ah, type SubagentSessionInput as ai, type SubagentWorkflow as aj, type SubagentWorkflowInput as ak, type TaskStatus as al, type ThreadInit as am, type TokenUsage as an, type ToolArgs as ao, type ToolCallResult as ap, type ToolCallResultUnion as aq, type ToolDefinition as ar, type ToolHandler as as, type ToolHooks as at, type ToolMap as au, type ToolNames as av, type ToolResult as aw, type ToolRouter as ax, type ToolRouterHooks as ay, type ToolWithHandler as az, type PrefixedThreadOps as b, type ToolHandlerResponse as c, type ActivityToolHandler as d, type RunAgentConfig as e, type ToolResultConfig as f, type SkillProvider as g, type SkillMetadata as h, type Skill as i, type FileResolver as j, type TreeMutation as k, type PrefixedVirtualFsOps as l, type AgentConfig as m, type AgentFile as n, type AgentState as o, type AgentStateManager as p, type AgentStatus as q, type AppendToolResultFn as r, type FileEntry as s, type JsonPrimitive as t, type JsonSerializable as u, type ParsedToolCall as v, type ParsedToolCallUnion as w, type PostHumanMessageAppendHook as x, type PostHumanMessageAppendHookContext as y, type PostToolUseFailureHook as z };