zeitlich 0.2.6 → 0.2.8

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.
@@ -1,6 +1,6 @@
1
1
  import type Redis from "ioredis";
2
2
  import { createThreadManager } from "./thread-manager";
3
- import type { AgentResponse } from "./types";
3
+ import type { AgentResponse, BaseAgentState } from "./types";
4
4
  import { Context } from "@temporalio/activity";
5
5
  import type { WorkflowClient } from "@temporalio/client";
6
6
  import { mapStoredMessagesToChatMessages } from "@langchain/core/messages";
@@ -10,7 +10,6 @@ import type {
10
10
  BaseChatModelCallOptions,
11
11
  BindToolsInput,
12
12
  } from "@langchain/core/language_models/chat_models";
13
- import { getStateQuery } from "./state-manager";
14
13
 
15
14
  /**
16
15
  * Configuration for invoking the model
@@ -49,7 +48,7 @@ export async function invokeModel({
49
48
  const parentRunId = info.workflowExecution.runId;
50
49
 
51
50
  const handle = client.getHandle(parentWorkflowId, parentRunId);
52
- const { tools } = await handle.query(getStateQuery);
51
+ const { tools } = await handle.query<BaseAgentState>(`get${agentName}State`);
53
52
 
54
53
  const messages = await thread.load();
55
54
  const response = await model.invoke(
@@ -74,9 +73,13 @@ export async function invokeModel({
74
73
  args: tc.args,
75
74
  })),
76
75
  usage: {
77
- input_tokens: response.usage_metadata?.input_tokens,
78
- output_tokens: response.usage_metadata?.output_tokens,
79
- total_tokens: response.usage_metadata?.total_tokens,
76
+ inputTokens: response.usage_metadata?.input_tokens,
77
+ outputTokens: response.usage_metadata?.output_tokens,
78
+ reasonTokens: response.usage_metadata?.output_token_details?.reasoning,
79
+ cachedWriteTokens:
80
+ response.usage_metadata?.input_token_details?.cache_creation,
81
+ cachedReadTokens:
82
+ response.usage_metadata?.input_token_details?.cache_read,
80
83
  },
81
84
  };
82
85
  }
@@ -1,11 +1,17 @@
1
- import { proxyActivities } from "@temporalio/workflow";
1
+ import {
2
+ proxyActivities,
3
+ condition,
4
+ defineUpdate,
5
+ setHandler,
6
+ } from "@temporalio/workflow";
2
7
  import type { ZeitlichSharedActivities } from "../activities";
3
8
  import type {
4
9
  ThreadOps,
5
- ZeitlichAgentConfig,
10
+ AgentConfig,
6
11
  SessionStartHook,
7
12
  SessionEndHook,
8
13
  SessionExitReason,
14
+ SessionConfig,
9
15
  } from "./types";
10
16
  import { type AgentStateManager, type JsonSerializable } from "./state-manager";
11
17
  import {
@@ -13,11 +19,16 @@ import {
13
19
  type ParsedToolCallUnion,
14
20
  type ToolMap,
15
21
  } from "./tool-router";
22
+ import type { MessageContent } from "@langchain/core/messages";
16
23
 
17
24
  export interface ZeitlichSession<M = unknown> {
18
25
  runSession<T extends JsonSerializable<T>>(args: {
19
26
  stateManager: AgentStateManager<T>;
20
- }): Promise<M | null>;
27
+ }): Promise<{
28
+ finalMessage: M | null;
29
+ exitReason: SessionExitReason;
30
+ usage: ReturnType<AgentStateManager<T>["getTotalUsage"]>;
31
+ }>;
21
32
  }
22
33
 
23
34
  /**
@@ -33,6 +44,7 @@ export interface SessionLifecycleHooks {
33
44
  export const createSession = async <T extends ToolMap, M = unknown>({
34
45
  threadId,
35
46
  agentName,
47
+ description,
36
48
  maxTurns = 50,
37
49
  metadata = {},
38
50
  runAgent,
@@ -44,13 +56,15 @@ export const createSession = async <T extends ToolMap, M = unknown>({
44
56
  hooks = {},
45
57
  appendSystemPrompt = true,
46
58
  systemPrompt,
47
- }: ZeitlichAgentConfig<T, M>): Promise<ZeitlichSession<M>> => {
59
+ waitForInputTimeout = "48h",
60
+ }: SessionConfig<T, M> & AgentConfig): Promise<ZeitlichSession<M>> => {
48
61
  const {
49
62
  appendToolResult,
50
63
  appendHumanMessage,
51
64
  initializeThread,
52
65
  appendSystemMessage,
53
66
  } = threadOps ?? proxyDefaultThreadOps();
67
+
54
68
  const toolRouter = createToolRouter({
55
69
  tools,
56
70
  appendToolResult,
@@ -77,7 +91,33 @@ export const createSession = async <T extends ToolMap, M = unknown>({
77
91
  };
78
92
 
79
93
  return {
80
- runSession: async ({ stateManager }): Promise<M | null> => {
94
+ runSession: async ({
95
+ stateManager,
96
+ }): Promise<{
97
+ finalMessage: M | null;
98
+ exitReason: SessionExitReason;
99
+ usage: ReturnType<typeof stateManager.getTotalUsage>;
100
+ }> => {
101
+ setHandler(
102
+ defineUpdate<unknown, [MessageContent]>(`add${agentName}Message`),
103
+ async (message: MessageContent) => {
104
+ if (hooks.onPreHumanMessageAppend) {
105
+ await hooks.onPreHumanMessageAppend({
106
+ message,
107
+ threadId,
108
+ });
109
+ }
110
+ await appendHumanMessage(threadId, message);
111
+ if (hooks.onPostHumanMessageAppend) {
112
+ await hooks.onPostHumanMessageAppend({
113
+ message,
114
+ threadId,
115
+ });
116
+ }
117
+ stateManager.run();
118
+ }
119
+ );
120
+
81
121
  if (hooks.onSessionStart) {
82
122
  await hooks.onSessionStart({
83
123
  threadId,
@@ -86,8 +126,6 @@ export const createSession = async <T extends ToolMap, M = unknown>({
86
126
  });
87
127
  }
88
128
 
89
- stateManager.setTools(toolRouter.getToolDefinitions());
90
-
91
129
  await initializeThread(threadId);
92
130
  if (appendSystemPrompt && systemPrompt && systemPrompt.trim() !== "") {
93
131
  await appendSystemMessage(threadId, systemPrompt);
@@ -105,17 +143,29 @@ export const createSession = async <T extends ToolMap, M = unknown>({
105
143
  stateManager.incrementTurns();
106
144
  const currentTurn = stateManager.getTurns();
107
145
 
108
- const { message, rawToolCalls } = await runAgent({
146
+ stateManager.setTools(toolRouter.getToolDefinitions());
147
+
148
+ const { message, rawToolCalls, usage } = await runAgent({
109
149
  threadId,
110
150
  agentName,
111
151
  metadata,
152
+ systemPrompt,
153
+ description,
112
154
  });
113
155
 
156
+ if (usage) {
157
+ stateManager.updateUsage(usage);
158
+ }
159
+
114
160
  // No tools configured - treat any non-end_turn as completed
115
161
  if (!toolRouter.hasTools() || rawToolCalls.length === 0) {
116
162
  stateManager.complete();
117
163
  exitReason = "completed";
118
- return message;
164
+ return {
165
+ finalMessage: message,
166
+ exitReason,
167
+ usage: stateManager.getTotalUsage(),
168
+ };
119
169
  }
120
170
 
121
171
  // Parse all tool calls uniformly through the router
@@ -136,13 +186,30 @@ export const createSession = async <T extends ToolMap, M = unknown>({
136
186
  }
137
187
 
138
188
  // Hooks can call stateManager.waitForInput() to pause the session
139
- await toolRouter.processToolCalls(parsedToolCalls, {
140
- turn: currentTurn,
141
- });
189
+ const toolCallResults = await toolRouter.processToolCalls(
190
+ parsedToolCalls,
191
+ {
192
+ turn: currentTurn,
193
+ }
194
+ );
195
+
196
+ for (const result of toolCallResults) {
197
+ if (result.usage) {
198
+ stateManager.updateUsage(result.usage);
199
+ }
200
+ }
142
201
 
143
202
  if (stateManager.getStatus() === "WAITING_FOR_INPUT") {
144
- exitReason = "waiting_for_input";
145
- break;
203
+ const conditionMet = await condition(
204
+ () => stateManager.getStatus() === "RUNNING",
205
+ waitForInputTimeout
206
+ );
207
+ if (!conditionMet) {
208
+ stateManager.cancel();
209
+ // Wait briefly to allow pending waitForStateChange handlers to complete
210
+ await condition(() => false, "2s");
211
+ break;
212
+ }
146
213
  }
147
214
  }
148
215
 
@@ -158,7 +225,11 @@ export const createSession = async <T extends ToolMap, M = unknown>({
158
225
  await callSessionEnd(exitReason, stateManager.getTurns());
159
226
  }
160
227
 
161
- return null;
228
+ return {
229
+ finalMessage: null,
230
+ exitReason,
231
+ usage: stateManager.getTotalUsage(),
232
+ };
162
233
  },
163
234
  };
164
235
  };
@@ -1,5 +1,11 @@
1
- import { defineQuery, setHandler } from "@temporalio/workflow";
2
1
  import {
2
+ condition,
3
+ defineQuery,
4
+ defineUpdate,
5
+ setHandler,
6
+ } from "@temporalio/workflow";
7
+ import {
8
+ type AgentConfig,
3
9
  type AgentStatus,
4
10
  type BaseAgentState,
5
11
  type WorkflowTask,
@@ -108,9 +114,26 @@ export interface AgentStateManager<TCustom extends JsonSerializable<TCustom>> {
108
114
 
109
115
  /** Set the tools (converts Zod schemas to JSON Schema for serialization) */
110
116
  setTools(newTools: ToolDefinition[]): void;
111
- }
112
117
 
113
- export const getStateQuery = defineQuery<BaseAgentState>("getState");
118
+ /** Update the usage */
119
+ updateUsage(usage: {
120
+ inputTokens?: number;
121
+ outputTokens?: number;
122
+ cachedWriteTokens?: number;
123
+ cachedReadTokens?: number;
124
+ reasonTokens?: number;
125
+ }): void;
126
+
127
+ /** Get the total usage */
128
+ getTotalUsage(): {
129
+ totalInputTokens: number;
130
+ totalOutputTokens: number;
131
+ totalCachedWriteTokens: number;
132
+ totalCachedReadTokens: number;
133
+ totalReasonTokens: number;
134
+ turns: number;
135
+ };
136
+ }
114
137
 
115
138
  /**
116
139
  * Creates an agent state manager for tracking workflow state.
@@ -124,14 +147,23 @@ export const getStateQuery = defineQuery<BaseAgentState>("getState");
124
147
  */
125
148
  export function createAgentStateManager<
126
149
  TCustom extends JsonSerializable<TCustom> = Record<string, never>,
127
- >(
128
- initialState?: Partial<BaseAgentState> & TCustom
129
- ): AgentStateManager<TCustom> {
150
+ >({
151
+ initialState,
152
+ agentConfig,
153
+ }: {
154
+ initialState?: Partial<BaseAgentState> & TCustom;
155
+ agentConfig: AgentConfig;
156
+ }): AgentStateManager<TCustom> {
130
157
  // Default state (BaseAgentState fields)
131
158
  let status: AgentStatus = initialState?.status ?? "RUNNING";
132
159
  let version = initialState?.version ?? 0;
133
160
  let turns = initialState?.turns ?? 0;
134
161
  let tools = initialState?.tools ?? [];
162
+ let totalInputTokens = 0;
163
+ let totalOutputTokens = 0;
164
+ let totalCachedWriteTokens = 0;
165
+ let totalCachedReadTokens = 0;
166
+ let totalReasonTokens = 0;
135
167
 
136
168
  // Tasks state
137
169
  const tasks = new Map<string, WorkflowTask>(initialState?.tasks);
@@ -157,10 +189,23 @@ export function createAgentStateManager<
157
189
  } as AgentState<TCustom>;
158
190
  }
159
191
 
160
- setHandler(getStateQuery, () => {
192
+ setHandler(defineQuery(`get${agentConfig.agentName}State`), () => {
161
193
  return buildState();
162
194
  });
163
195
 
196
+ setHandler(
197
+ defineUpdate<AgentState<TCustom>, [number]>(
198
+ `waitFor${agentConfig.agentName}StateChange`
199
+ ),
200
+ async (lastKnownVersion: number) => {
201
+ await condition(
202
+ () => version > lastKnownVersion || isTerminalStatus(status),
203
+ "55s"
204
+ );
205
+ return buildState();
206
+ }
207
+ );
208
+
164
209
  return {
165
210
  getStatus(): AgentStatus {
166
211
  return status;
@@ -262,6 +307,38 @@ export function createAgentStateManager<
262
307
  }
263
308
  return deleted;
264
309
  },
310
+
311
+ updateUsage(usage: {
312
+ inputTokens?: number;
313
+ outputTokens?: number;
314
+ cachedWriteTokens?: number;
315
+ cachedReadTokens?: number;
316
+ reasonTokens?: number;
317
+ }): void {
318
+ totalInputTokens += usage.inputTokens ?? 0;
319
+ totalOutputTokens += usage.outputTokens ?? 0;
320
+ totalCachedWriteTokens += usage.cachedWriteTokens ?? 0;
321
+ totalCachedReadTokens += usage.cachedReadTokens ?? 0;
322
+ totalReasonTokens += usage.reasonTokens ?? 0;
323
+ },
324
+
325
+ getTotalUsage(): {
326
+ totalInputTokens: number;
327
+ totalOutputTokens: number;
328
+ totalCachedWriteTokens: number;
329
+ totalCachedReadTokens: number;
330
+ totalReasonTokens: number;
331
+ turns: number;
332
+ } {
333
+ return {
334
+ totalInputTokens,
335
+ totalOutputTokens,
336
+ totalCachedWriteTokens,
337
+ totalCachedReadTokens,
338
+ totalReasonTokens,
339
+ turns,
340
+ };
341
+ },
265
342
  };
266
343
  }
267
344
 
@@ -6,6 +6,7 @@ import type {
6
6
  PreToolUseHookResult,
7
7
  SubagentConfig,
8
8
  SubagentHooks,
9
+ TokenUsage,
9
10
  ToolHooks,
10
11
  ToolResultConfig,
11
12
  } from "./types";
@@ -53,7 +54,7 @@ export interface ToolWithHandler<
53
54
  strict?: boolean;
54
55
  max_uses?: number;
55
56
  /** Whether this tool is available to the agent (default: true). Disabled tools are excluded from definitions and rejected at parse time. */
56
- enabled?: boolean;
57
+ enabled?: () => boolean;
57
58
  /** Per-tool lifecycle hooks (run in addition to global hooks) */
58
59
  hooks?: ToolHooks<z.infer<TSchema>, TResult>;
59
60
  }
@@ -76,7 +77,7 @@ export type ToolMap = Record<
76
77
  handler: ToolHandler<any, any, any>;
77
78
  strict?: boolean;
78
79
  max_uses?: number;
79
- enabled?: boolean;
80
+ enabled?: () => boolean;
80
81
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
82
  hooks?: ToolHooks<any, any>;
82
83
  }
@@ -153,6 +154,8 @@ export interface ToolHandlerResponse<TResult = null> {
153
154
  * payloads through Temporal's activity payload limit.
154
155
  */
155
156
  resultAppended?: boolean;
157
+ /** Token usage from the tool execution (e.g. child agent invocations) */
158
+ usage?: TokenUsage;
156
159
  }
157
160
 
158
161
  /**
@@ -228,6 +231,7 @@ export interface ToolCallResult<
228
231
  toolCallId: string;
229
232
  name: TName;
230
233
  data: TResult;
234
+ usage?: TokenUsage;
231
235
  }
232
236
 
233
237
  /**
@@ -412,16 +416,14 @@ export function createToolRouter<T extends ToolMap>(
412
416
  }
413
417
 
414
418
  /** Check if a tool is enabled (defaults to true when not specified) */
415
- const isEnabled = (tool: ToolMap[string]): boolean => tool.enabled !== false;
419
+ const isEnabled = (tool: ToolMap[string]): boolean =>
420
+ tool.enabled?.() ?? true;
416
421
 
417
422
  if (options.subagents) {
418
- const enabledSubagents = options.subagents.filter(
419
- (s) => s.enabled !== false
420
- );
421
- if (enabledSubagents.length > 0) {
423
+ if (options.subagents.length > 0) {
422
424
  // Build per-subagent hook dispatcher keyed by subagent name
423
425
  const subagentHooksMap = new Map<string, SubagentHooks>();
424
- for (const s of enabledSubagents) {
426
+ for (const s of options.subagents) {
425
427
  if (s.hooks) subagentHooksMap.set(s.agentName, s.hooks);
426
428
  }
427
429
 
@@ -429,8 +431,8 @@ export function createToolRouter<T extends ToolMap>(
429
431
  (args as SubagentArgs).subagent;
430
432
 
431
433
  toolMap.set("Subagent", {
432
- ...createSubagentTool(enabledSubagents),
433
- handler: createSubagentHandler(enabledSubagents),
434
+ ...createSubagentTool(options.subagents),
435
+ handler: createSubagentHandler(options.subagents),
434
436
  ...(subagentHooksMap.size > 0 && {
435
437
  hooks: {
436
438
  onPreToolUse: async (ctx): Promise<PreToolUseHookResult> => {
package/src/lib/types.ts CHANGED
@@ -8,6 +8,7 @@ import type {
8
8
  } from "./tool-router";
9
9
 
10
10
  import type { MessageContent, StoredMessage } from "@langchain/core/messages";
11
+ import type { Duration } from "@temporalio/common";
11
12
  import type { Workflow } from "@temporalio/workflow";
12
13
  import type { z } from "zod";
13
14
 
@@ -31,6 +32,10 @@ export interface BaseAgentState {
31
32
  turns: number;
32
33
  tasks: Map<string, WorkflowTask>;
33
34
  systemPrompt: string;
35
+ totalInputTokens: number;
36
+ totalOutputTokens: number;
37
+ cachedWriteTokens: number;
38
+ cachedReadtTokens: number;
34
39
  }
35
40
 
36
41
  /**
@@ -49,17 +54,21 @@ export interface AgentFile {
49
54
  mimeType?: string;
50
55
  }
51
56
 
57
+ export interface TokenUsage {
58
+ inputTokens?: number;
59
+ outputTokens?: number;
60
+ cachedWriteTokens?: number;
61
+ cachedReadTokens?: number;
62
+ reasonTokens?: number;
63
+ }
64
+
52
65
  /**
53
66
  * Agent response from LLM invocation
54
67
  */
55
68
  export interface AgentResponse<M = StoredMessage> {
56
69
  message: M;
57
70
  rawToolCalls: RawToolCall[];
58
- usage?: {
59
- input_tokens?: number;
60
- output_tokens?: number;
61
- total_tokens?: number;
62
- };
71
+ usage?: TokenUsage;
63
72
  }
64
73
 
65
74
  /**
@@ -82,16 +91,28 @@ export interface ThreadOps {
82
91
  }
83
92
 
84
93
  /**
85
- * Configuration for a Zeitlich agent session
94
+ * Configuration for a Zeitlich agent
86
95
  */
87
- export interface ZeitlichAgentConfig<T extends ToolMap, M = StoredMessage> {
88
- threadId: string;
96
+ export interface AgentConfig {
97
+ /** The name of the agent, should be unique within the workflows, ideally Pascal Case */
89
98
  agentName: string;
90
99
  /** Description, used for sub agents */
91
100
  description?: string;
101
+ /** The system prompt to append to the thread */
92
102
  systemPrompt?: string;
103
+ }
104
+
105
+ /**
106
+ * Configuration for a Zeitlich agent session
107
+ */
108
+ export interface SessionConfig<T extends ToolMap, M = StoredMessage> {
109
+ /** The thread ID to use for the session */
110
+ threadId: string;
111
+ /** Metadata for the session */
93
112
  metadata?: Record<string, unknown>;
113
+ /** Whether to append the system prompt as message to the thread */
94
114
  appendSystemPrompt?: boolean;
115
+ /** How many turns to run the session for */
95
116
  maxTurns?: number;
96
117
  /** Workflow-specific runAgent activity (with tools pre-bound) */
97
118
  runAgent: RunAgentActivity<M>;
@@ -110,6 +131,8 @@ export interface ZeitlichAgentConfig<T extends ToolMap, M = StoredMessage> {
110
131
  * Returns MessageContent array for the initial HumanMessage.
111
132
  */
112
133
  buildContextMessage: () => MessageContent | Promise<MessageContent>;
134
+ /** How long to wait for input before cancelling the workflow */
135
+ waitForInputTimeout?: Duration;
113
136
  }
114
137
 
115
138
  /**
@@ -128,9 +151,10 @@ export interface SerializableToolDefinition {
128
151
  /**
129
152
  * Configuration passed to runAgent activity
130
153
  */
131
- export interface RunAgentConfig {
154
+ export interface RunAgentConfig extends AgentConfig {
155
+ /** The thread ID to use for the session */
132
156
  threadId: string;
133
- agentName: string;
157
+ /** Metadata for the session */
134
158
  metadata?: Record<string, unknown>;
135
159
  }
136
160
 
@@ -140,6 +164,7 @@ export interface RunAgentConfig {
140
164
  export type RunAgentActivity<M = StoredMessage> = (
141
165
  config: RunAgentConfig
142
166
  ) => Promise<AgentResponse<M>>;
167
+
143
168
  /**
144
169
  * Configuration for appending a tool result
145
170
  */
@@ -171,7 +196,7 @@ export interface SubagentConfig<TResult extends z.ZodType = z.ZodType> {
171
196
  /** Description shown to the parent agent explaining what this subagent does */
172
197
  description: string;
173
198
  /** Whether this subagent is available (default: true). Disabled subagents are excluded from the Subagent tool. */
174
- enabled?: boolean;
199
+ enabled?: () => boolean;
175
200
  /** Temporal workflow function or type name (used with executeChild) */
176
201
  workflow: string | Workflow;
177
202
  /** Optional task queue - defaults to parent's queue if not specified */
@@ -369,6 +394,40 @@ export type SessionStartHook = (
369
394
  ctx: SessionStartHookContext
370
395
  ) => void | Promise<void>;
371
396
 
397
+ /**
398
+ * Context for PreHumanMessageAppend hook - called before each human message is appended to the thread
399
+ */
400
+ export interface PreHumanMessageAppendHookContext {
401
+ /** The message about to be appended */
402
+ message: MessageContent;
403
+ /** Thread identifier */
404
+ threadId: string;
405
+ }
406
+
407
+ /**
408
+ * PreHumanMessageAppend hook - called before each human message is appended to the thread
409
+ */
410
+ export type PreHumanMessageAppendHook = (
411
+ ctx: PreHumanMessageAppendHookContext
412
+ ) => void | Promise<void>;
413
+
414
+ /**
415
+ * PostHumanMessageAppend hook - called after each human message is appended to the thread
416
+ */
417
+ export type PostHumanMessageAppendHook = (
418
+ ctx: PostHumanMessageAppendHookContext
419
+ ) => void | Promise<void>;
420
+
421
+ /**
422
+ * Context for PostHumanMessageAppend hook - called after each human message is appended to the thread
423
+ */
424
+ export interface PostHumanMessageAppendHookContext {
425
+ /** The message that was appended */
426
+ message: MessageContent;
427
+ /** Thread identifier */
428
+ threadId: string;
429
+ }
430
+
372
431
  /**
373
432
  * Context for SessionEnd hook - called when session ends
374
433
  */
@@ -424,6 +483,10 @@ export interface ToolHooks<TArgs = unknown, TResult = unknown> {
424
483
  * Combined hooks interface for session lifecycle
425
484
  */
426
485
  export interface Hooks<T extends ToolMap, TResult = unknown> {
486
+ /** Called before each human message is appended to the thread */
487
+ onPreHumanMessageAppend?: PreHumanMessageAppendHook;
488
+ /** Called after each human message is appended to the thread */
489
+ onPostHumanMessageAppend?: PostHumanMessageAppendHook;
427
490
  /** Called before each tool execution - can block or modify */
428
491
  onPreToolUse?: PreToolUseHook<T>;
429
492
  /** Called after each successful tool execution */
@@ -1,25 +1,24 @@
1
- import { AIMessage, type StoredMessage } from "@langchain/core/messages";
2
- import type { ActivityToolHandler } from "../../lib/tool-router";
1
+ import type { ToolHandler } from "../../lib/tool-router";
3
2
  import type { AskUserQuestionArgs } from "./tool";
4
3
 
5
4
  /**
6
5
  * Creates handler for user interaction tool - creates AI messages for display.
7
6
  */
8
- export const createAskUserQuestionHandler = (): ActivityToolHandler<
9
- AskUserQuestionArgs,
10
- { chatMessages: StoredMessage[] }
11
- > => async (args) => {
12
- const messages = args.questions.map(
13
- ({ question, header, options, multiSelect }) =>
14
- new AIMessage({
15
- content: question,
16
- additional_kwargs: {
17
- header,
18
- options,
19
- multiSelect,
20
- },
21
- }).toDict()
22
- );
23
-
24
- return { toolResponse: "Question submitted", data: { chatMessages: messages } };
25
- };
7
+ export const createAskUserQuestionHandler =
8
+ (): ToolHandler<
9
+ AskUserQuestionArgs,
10
+ {
11
+ questions: {
12
+ question: string;
13
+ header: string;
14
+ options: { label: string; description: string }[];
15
+ multiSelect: boolean;
16
+ }[];
17
+ }
18
+ > =>
19
+ (args) => {
20
+ return {
21
+ toolResponse: "Question submitted",
22
+ data: { questions: args.questions },
23
+ };
24
+ };
@@ -53,7 +53,7 @@ export function createSubagentHandler<
53
53
  taskQueue: config.taskQueue ?? parentTaskQueue,
54
54
  };
55
55
 
56
- const { toolResponse, data } =
56
+ const { toolResponse, data, usage } =
57
57
  typeof config.workflow === "string"
58
58
  ? await executeChild(config.workflow, childOpts)
59
59
  : await executeChild(config.workflow, childOpts);
@@ -66,6 +66,7 @@ export function createSubagentHandler<
66
66
  return {
67
67
  toolResponse,
68
68
  data: validated,
69
+ ...(usage && { usage }),
69
70
  };
70
71
  };
71
72
  }
package/src/workflow.ts CHANGED
@@ -74,7 +74,7 @@ export type {
74
74
  AgentFile,
75
75
  AgentResponse,
76
76
  ThreadOps,
77
- ZeitlichAgentConfig,
77
+ AgentConfig,
78
78
  RunAgentConfig,
79
79
  RunAgentActivity,
80
80
  ToolResultConfig,
@@ -109,8 +109,6 @@ export type { SubagentArgs } from "./tools/subagent/tool";
109
109
  export type { ZeitlichSharedActivities } from "./activities";
110
110
 
111
111
  // Tool definitions (schemas only - no handlers)
112
- export { askUserQuestionTool } from "./tools/ask-user-question/tool";
113
- export type { AskUserQuestionArgs } from "./tools/ask-user-question/tool";
114
112
  export { globTool } from "./tools/glob/tool";
115
113
  export type { GlobArgs } from "./tools/glob/tool";
116
114
  export { grepTool } from "./tools/grep/tool";
@@ -141,3 +139,7 @@ export { createTaskUpdateHandler } from "./tools/task-update/handler";
141
139
 
142
140
  export { bashTool, createBashToolDescription } from "./tools/bash/tool";
143
141
  export type { BashArgs } from "./tools/bash/tool";
142
+
143
+ export { askUserQuestionTool } from "./tools/ask-user-question/tool";
144
+ export type { AskUserQuestionArgs } from "./tools/ask-user-question/tool";
145
+ export { createAskUserQuestionHandler } from "./tools/ask-user-question/handler";