zeitlich 0.2.2 → 0.2.4

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 (46) hide show
  1. package/README.md +34 -31
  2. package/dist/index.cjs +330 -399
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +24 -43
  5. package/dist/index.d.ts +24 -43
  6. package/dist/index.js +301 -373
  7. package/dist/index.js.map +1 -1
  8. package/dist/{workflow-BQf5EfNN.d.cts → workflow-PjeURKw4.d.cts} +265 -258
  9. package/dist/{workflow-BQf5EfNN.d.ts → workflow-PjeURKw4.d.ts} +265 -258
  10. package/dist/workflow.cjs +223 -281
  11. package/dist/workflow.cjs.map +1 -1
  12. package/dist/workflow.d.cts +2 -3
  13. package/dist/workflow.d.ts +2 -3
  14. package/dist/workflow.js +198 -258
  15. package/dist/workflow.js.map +1 -1
  16. package/package.json +3 -2
  17. package/src/activities.ts +0 -32
  18. package/src/index.ts +14 -11
  19. package/src/lib/model-invoker.ts +7 -1
  20. package/src/lib/session.ts +54 -109
  21. package/src/lib/thread-manager.ts +45 -37
  22. package/src/lib/tool-router.ts +148 -108
  23. package/src/lib/types.ts +35 -26
  24. package/src/tools/ask-user-question/handler.ts +5 -5
  25. package/src/tools/ask-user-question/tool.ts +3 -2
  26. package/src/tools/bash/bash.test.ts +12 -12
  27. package/src/tools/bash/handler.ts +5 -5
  28. package/src/tools/bash/tool.ts +3 -2
  29. package/src/tools/edit/handler.ts +78 -123
  30. package/src/tools/edit/tool.ts +3 -2
  31. package/src/tools/glob/handler.ts +17 -48
  32. package/src/tools/glob/tool.ts +3 -2
  33. package/src/tools/grep/tool.ts +3 -2
  34. package/src/tools/{read → read-file}/tool.ts +3 -2
  35. package/src/tools/{task → subagent}/handler.ts +18 -31
  36. package/src/tools/{task → subagent}/tool.ts +13 -20
  37. package/src/tools/task-create/handler.ts +5 -11
  38. package/src/tools/task-create/tool.ts +3 -2
  39. package/src/tools/task-get/handler.ts +5 -10
  40. package/src/tools/task-get/tool.ts +3 -2
  41. package/src/tools/task-list/handler.ts +5 -10
  42. package/src/tools/task-list/tool.ts +3 -2
  43. package/src/tools/task-update/handler.ts +5 -12
  44. package/src/tools/task-update/tool.ts +3 -2
  45. package/src/tools/{write → write-file}/tool.ts +5 -6
  46. package/src/workflow.ts +24 -21
@@ -9,17 +9,11 @@ import type {
9
9
  ToolHooks,
10
10
  ToolResultConfig,
11
11
  } from "./types";
12
- import type { GenericTaskToolSchemaType } from "../tools/task/tool";
12
+ import type { SubagentArgs } from "../tools/subagent/tool";
13
13
 
14
14
  import type { z } from "zod";
15
- import { proxyActivities } from "@temporalio/workflow";
16
- import type { ZeitlichSharedActivities } from "../activities";
17
- import { createTaskTool } from "../tools/task/tool";
18
- import { createTaskHandler } from "../tools/task/handler";
19
- import type { createTaskCreateHandler } from "../tools/task-create/handler";
20
- import { bashTool, createBashToolDescription } from "../tools/bash/tool";
21
- import { taskCreateTool } from "../tools/task-create/tool";
22
- import type { handleBashTool } from "../tools/bash/handler";
15
+ import { createSubagentTool } from "../tools/subagent/tool";
16
+ import { createSubagentHandler } from "../tools/subagent/handler";
23
17
 
24
18
  export type { ToolMessageContent };
25
19
 
@@ -58,12 +52,19 @@ export interface ToolWithHandler<
58
52
  handler: ToolHandler<z.infer<TSchema>, TResult, TContext>;
59
53
  strict?: boolean;
60
54
  max_uses?: number;
55
+ /** Whether this tool is available to the agent (default: true). Disabled tools are excluded from definitions and rejected at parse time. */
56
+ enabled?: boolean;
61
57
  /** Per-tool lifecycle hooks (run in addition to global hooks) */
62
58
  hooks?: ToolHooks<z.infer<TSchema>, TResult>;
63
59
  }
64
60
 
65
61
  /**
66
62
  * A map of tool keys to tool definitions with handlers.
63
+ *
64
+ * Handler uses `any` intentionally — this is a type-system boundary where heterogeneous
65
+ * tool types are stored together. Type safety for individual tools is enforced by
66
+ * `defineTool()` at the definition site and generic inference utilities like
67
+ * `InferToolResults<T>` at the consumption site.
67
68
  */
68
69
  export type ToolMap = Record<
69
70
  string,
@@ -71,17 +72,13 @@ export type ToolMap = Record<
71
72
  name: string;
72
73
  description: string;
73
74
  schema: z.ZodType;
74
- /* eslint-disable @typescript-eslint/no-explicit-any */
75
- handler: (
76
- args: any,
77
- context: any
78
- ) => ToolHandlerResponse<any> | Promise<ToolHandlerResponse<any>>;
79
- /* eslint-enable @typescript-eslint/no-explicit-any */
75
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
+ handler: ToolHandler<any, any, any>;
80
77
  strict?: boolean;
81
78
  max_uses?: number;
82
- /* eslint-disable @typescript-eslint/no-explicit-any */
79
+ enabled?: boolean;
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
81
  hooks?: ToolHooks<any, any>;
84
- /* eslint-enable @typescript-eslint/no-explicit-any */
85
82
  }
86
83
  >;
87
84
 
@@ -140,39 +137,30 @@ export type AppendToolResultFn = (config: ToolResultConfig) => Promise<void>;
140
137
  /**
141
138
  * The response from a tool handler.
142
139
  * Contains the content for the tool message and the result to return from processToolCalls.
140
+ *
141
+ * Tools that don't return additional data should use `data: null` (TResult defaults to null).
142
+ * Tools that may fail to produce data should type TResult as `SomeType | null`.
143
143
  */
144
- export interface ToolHandlerResponse<TResult> {
144
+ export interface ToolHandlerResponse<TResult = null> {
145
145
  /** Content sent back to the LLM as the tool call response */
146
146
  toolResponse: ToolMessageContent;
147
147
  /** Data returned to the workflow and hooks for further processing */
148
- data: TResult | null;
148
+ data: TResult;
149
+ /**
150
+ * When true, the tool result has already been appended to the thread
151
+ * by the handler itself (e.g. via `withAutoAppend`), so the router
152
+ * will skip the `appendToolResult` call. This avoids sending large
153
+ * payloads through Temporal's activity payload limit.
154
+ */
155
+ resultAppended?: boolean;
149
156
  }
150
157
 
151
158
  /**
152
159
  * Context passed to tool handlers for additional data beyond tool args.
153
160
  * Use this to pass workflow state like file trees, user context, etc.
161
+ * Generic so callers can type the context shape, e.g. ToolHandlerContext<ControlTestFsParams>.
154
162
  */
155
- export interface ToolHandlerContext {
156
- /** Additional context data - define your own shape */
157
- [key: string]: unknown;
158
- }
159
-
160
- export interface BuildInToolDefinitions {
161
- [bashTool.name]: typeof bashTool & {
162
- handler: ReturnType<typeof handleBashTool>;
163
- };
164
- [taskCreateTool.name]: typeof taskCreateTool & {
165
- handler: ReturnType<typeof createTaskCreateHandler>;
166
- };
167
- }
168
-
169
- export const buildIntoolDefinitions: Record<
170
- keyof BuildInToolDefinitions,
171
- ToolDefinition
172
- > = {
173
- [bashTool.name]: bashTool,
174
- [taskCreateTool.name]: taskCreateTool,
175
- };
163
+ export type ToolHandlerContext<T = Record<string, unknown>> = T;
176
164
 
177
165
  /**
178
166
  * A handler function for a specific tool.
@@ -193,7 +181,7 @@ export type ToolHandler<TArgs, TResult, TContext = ToolHandlerContext> = (
193
181
  * ```typescript
194
182
  * // Filesystem handler with context
195
183
  * const readHandler: ActivityToolHandler<
196
- * ReadToolSchemaType,
184
+ * FileReadArgs,
197
185
  * ReadResult,
198
186
  * { scopedNodes: FileNode[]; provider: FileSystemProvider }
199
187
  * > = async (args, context) => {
@@ -239,15 +227,13 @@ export interface ToolCallResult<
239
227
  > {
240
228
  toolCallId: string;
241
229
  name: TName;
242
- data: TResult | null;
230
+ data: TResult;
243
231
  }
244
232
 
245
233
  /**
246
234
  * Options for creating a tool router.
247
235
  */
248
236
  export interface ToolRouterOptions<T extends ToolMap> {
249
- /** File tree for the agent */
250
- fileTree: string;
251
237
  /** Map of tools with their handlers */
252
238
  tools: T;
253
239
  /** Thread ID for appending tool results */
@@ -260,10 +246,6 @@ export interface ToolRouterOptions<T extends ToolMap> {
260
246
  hooks?: Hooks<T, ToolCallResultUnion<InferToolResults<T>>>;
261
247
  /** Subagent configurations */
262
248
  subagents?: SubagentConfig[];
263
- /** Build in tools - accepts raw handlers or proxied activities */
264
- buildInTools?: {
265
- [K in keyof BuildInToolDefinitions]?: BuildInToolDefinitions[K]["handler"];
266
- };
267
249
  }
268
250
 
269
251
  /**
@@ -419,15 +401,7 @@ export interface ToolRouter<T extends ToolMap> {
419
401
  export function createToolRouter<T extends ToolMap>(
420
402
  options: ToolRouterOptions<T>
421
403
  ): ToolRouter<T> {
422
- const { appendToolResult } = proxyActivities<ZeitlichSharedActivities>({
423
- startToCloseTimeout: "2m",
424
- retry: {
425
- maximumAttempts: 3,
426
- initialInterval: "5s",
427
- maximumInterval: "15m",
428
- backoffCoefficient: 4,
429
- },
430
- });
404
+ const { appendToolResult } = options;
431
405
  type TResults = InferToolResults<T>;
432
406
 
433
407
  // Build internal lookup map by tool name
@@ -437,6 +411,9 @@ export function createToolRouter<T extends ToolMap>(
437
411
  toolMap.set(tool.name, tool as T[keyof T]);
438
412
  }
439
413
 
414
+ /** Check if a tool is enabled (defaults to true when not specified) */
415
+ const isEnabled = (tool: ToolMap[string]): boolean => tool.enabled !== false;
416
+
440
417
  if (options.subagents) {
441
418
  // Build per-subagent hook dispatcher keyed by subagent name
442
419
  const subagentHooksMap = new Map<string, SubagentHooks>();
@@ -445,11 +422,11 @@ export function createToolRouter<T extends ToolMap>(
445
422
  }
446
423
 
447
424
  const resolveSubagentName = (args: unknown): string =>
448
- (args as GenericTaskToolSchemaType).subagent;
425
+ (args as SubagentArgs).subagent;
449
426
 
450
- toolMap.set("Task", {
451
- ...createTaskTool(options.subagents),
452
- handler: createTaskHandler(options.subagents),
427
+ toolMap.set("Subagent", {
428
+ ...createSubagentTool(options.subagents),
429
+ handler: createSubagentHandler(options.subagents),
453
430
  ...(subagentHooksMap.size > 0 && {
454
431
  hooks: {
455
432
  onPreToolUse: async (ctx): Promise<PreToolUseHookResult> => {
@@ -471,25 +448,6 @@ export function createToolRouter<T extends ToolMap>(
471
448
  });
472
449
  }
473
450
 
474
- if (options.buildInTools) {
475
- for (const [key, value] of Object.entries(options.buildInTools)) {
476
- if (key === bashTool.name) {
477
- toolMap.set(key, {
478
- ...buildIntoolDefinitions[key as keyof BuildInToolDefinitions],
479
- description: createBashToolDescription({
480
- fileTree: options.fileTree,
481
- }),
482
- handler: value,
483
- });
484
- } else {
485
- toolMap.set(key, {
486
- ...buildIntoolDefinitions[key as keyof BuildInToolDefinitions],
487
- handler: value,
488
- });
489
- }
490
- }
491
- }
492
-
493
451
  async function processToolCall(
494
452
  toolCall: ParsedToolCallUnion<T>,
495
453
  turn: number,
@@ -511,6 +469,7 @@ export function createToolRouter<T extends ToolMap>(
511
469
  await appendToolResult({
512
470
  threadId: options.threadId,
513
471
  toolCallId: toolCall.id,
472
+ toolName: toolCall.name,
514
473
  content: JSON.stringify({
515
474
  skipped: true,
516
475
  reason: "Skipped by PreToolUse hook",
@@ -532,6 +491,7 @@ export function createToolRouter<T extends ToolMap>(
532
491
  await appendToolResult({
533
492
  threadId: options.threadId,
534
493
  toolCallId: toolCall.id,
494
+ toolName: toolCall.name,
535
495
  content: JSON.stringify({
536
496
  skipped: true,
537
497
  reason: "Skipped by tool PreToolUse hook",
@@ -547,15 +507,23 @@ export function createToolRouter<T extends ToolMap>(
547
507
  // --- Execute handler ---
548
508
  let result: unknown;
549
509
  let content!: ToolMessageContent;
510
+ let resultAppended = false;
550
511
 
551
512
  try {
552
513
  if (tool) {
514
+ const enrichedContext = {
515
+ ...(handlerContext ?? {}),
516
+ threadId: options.threadId,
517
+ toolCallId: toolCall.id,
518
+ toolName: toolCall.name,
519
+ };
553
520
  const response = await tool.handler(
554
521
  effectiveArgs as Parameters<typeof tool.handler>[0],
555
- (handlerContext ?? {}) as Parameters<typeof tool.handler>[1]
522
+ enrichedContext as Parameters<typeof tool.handler>[1]
556
523
  );
557
524
  result = response.data;
558
525
  content = response.toolResponse;
526
+ resultAppended = response.resultAppended === true;
559
527
  } else {
560
528
  result = { error: `Unknown tool: ${toolCall.name}` };
561
529
  content = JSON.stringify(result, null, 2);
@@ -606,12 +574,15 @@ export function createToolRouter<T extends ToolMap>(
606
574
  }
607
575
  }
608
576
 
609
- // Automatically append tool result to thread
610
- await appendToolResult({
611
- threadId: options.threadId,
612
- toolCallId: toolCall.id,
613
- content,
614
- });
577
+ // Automatically append tool result to thread (unless handler already did)
578
+ if (!resultAppended) {
579
+ await appendToolResult({
580
+ threadId: options.threadId,
581
+ toolCallId: toolCall.id,
582
+ toolName: toolCall.name,
583
+ content,
584
+ });
585
+ }
615
586
 
616
587
  const toolResult = {
617
588
  toolCallId: toolCall.id,
@@ -647,13 +618,13 @@ export function createToolRouter<T extends ToolMap>(
647
618
  // --- Methods from registry ---
648
619
 
649
620
  hasTools(): boolean {
650
- return toolMap.size > 0;
621
+ return Array.from(toolMap.values()).some(isEnabled);
651
622
  },
652
623
 
653
624
  parseToolCall(toolCall: RawToolCall): ParsedToolCallUnion<T> {
654
625
  const tool = toolMap.get(toolCall.name);
655
626
 
656
- if (!tool) {
627
+ if (!tool || !isEnabled(tool)) {
657
628
  throw new Error(`Tool ${toolCall.name} not found`);
658
629
  }
659
630
 
@@ -668,21 +639,26 @@ export function createToolRouter<T extends ToolMap>(
668
639
  },
669
640
 
670
641
  hasTool(name: string): boolean {
671
- return toolMap.has(name);
642
+ const tool = toolMap.get(name);
643
+ return tool !== undefined && isEnabled(tool);
672
644
  },
673
645
 
674
646
  getToolNames(): ToolNames<T>[] {
675
- return Array.from(toolMap.keys()) as ToolNames<T>[];
647
+ return Array.from(toolMap.entries())
648
+ .filter(([, tool]) => isEnabled(tool))
649
+ .map(([name]) => name) as ToolNames<T>[];
676
650
  },
677
651
 
678
652
  getToolDefinitions(): ToolDefinition[] {
679
- return Array.from(toolMap).map(([name, tool]) => ({
680
- name,
681
- description: tool.description,
682
- schema: tool.schema,
683
- strict: tool.strict,
684
- max_uses: tool.max_uses,
685
- }));
653
+ return Array.from(toolMap)
654
+ .filter(([, tool]) => isEnabled(tool))
655
+ .map(([name, tool]) => ({
656
+ name,
657
+ description: tool.description,
658
+ schema: tool.schema,
659
+ strict: tool.strict,
660
+ max_uses: tool.max_uses,
661
+ }));
686
662
  },
687
663
 
688
664
  // --- Methods for processing tool calls ---
@@ -740,22 +716,31 @@ export function createToolRouter<T extends ToolMap>(
740
716
  const processOne = async (
741
717
  toolCall: ParsedToolCallUnion<T>
742
718
  ): Promise<ToolCallResult<TName, TResult>> => {
719
+ const enrichedContext = {
720
+ ...(handlerContext ?? {}),
721
+ threadId: options.threadId,
722
+ toolCallId: toolCall.id,
723
+ toolName: toolCall.name as TName,
724
+ } as TContext;
743
725
  const response = await handler(
744
726
  toolCall.args as ToolArgs<T, TName>,
745
- handlerContext
727
+ enrichedContext
746
728
  );
747
729
 
748
- // Automatically append tool result to thread
749
- await appendToolResult({
750
- threadId: options.threadId,
751
- toolCallId: toolCall.id,
752
- content: response.toolResponse,
753
- });
730
+ // Automatically append tool result to thread (unless handler already did)
731
+ if (!response.resultAppended) {
732
+ await appendToolResult({
733
+ threadId: options.threadId,
734
+ toolCallId: toolCall.id,
735
+ toolName: toolCall.name,
736
+ content: response.toolResponse,
737
+ });
738
+ }
754
739
 
755
740
  return {
756
741
  toolCallId: toolCall.id,
757
742
  name: toolCall.name as TName,
758
- data: response.data ?? null,
743
+ data: response.data,
759
744
  };
760
745
  };
761
746
 
@@ -801,6 +786,61 @@ export function createToolRouter<T extends ToolMap>(
801
786
  };
802
787
  }
803
788
 
789
+ /**
790
+ * Wraps a tool handler to automatically append its result directly to the
791
+ * thread and sets `resultAppended: true` on the response.
792
+ *
793
+ * Use this for tools whose responses may exceed Temporal's activity payload
794
+ * limit. The wrapper appends to the thread inside the activity (where Redis
795
+ * is available), then replaces `toolResponse` with an empty string so the
796
+ * large payload never travels through the Temporal workflow boundary.
797
+ *
798
+ * @param getThread - Factory that returns a thread manager for the given threadId
799
+ * @param handler - The original tool handler
800
+ * @returns A wrapped handler that auto-appends and flags the response
801
+ *
802
+ * @example
803
+ * ```typescript
804
+ * import { withAutoAppend } from '@bead-ai/zeitlich/workflow';
805
+ * import { createThreadManager } from '@bead-ai/zeitlich';
806
+ *
807
+ * const handler = withAutoAppend(
808
+ * (threadId) => createThreadManager({ redis, threadId }),
809
+ * async (args, ctx) => ({
810
+ * toolResponse: JSON.stringify(largeResult), // appended directly to Redis
811
+ * data: { summary: "..." }, // small data for workflow
812
+ * }),
813
+ * );
814
+ * ```
815
+ */
816
+ export function withAutoAppend<
817
+ TArgs,
818
+ TResult,
819
+ TContext extends ToolHandlerContext = ToolHandlerContext,
820
+ >(
821
+ threadHandler: (config: ToolResultConfig) => Promise<void>,
822
+ handler: ActivityToolHandler<TArgs, TResult, TContext>
823
+ ): ActivityToolHandler<TArgs, TResult, TContext> {
824
+ return async (args: TArgs, context: TContext) => {
825
+ const response = await handler(args, context);
826
+ const threadId = (context as Record<string, unknown>).threadId as string;
827
+ const toolCallId = (context as Record<string, unknown>)
828
+ .toolCallId as string;
829
+ const toolName = (context as Record<string, unknown>).toolName as string;
830
+
831
+ // Append directly (inside the activity, bypassing Temporal payload)
832
+ await threadHandler({
833
+ threadId,
834
+ toolCallId,
835
+ toolName,
836
+ content: response.toolResponse,
837
+ });
838
+
839
+ // Return with empty toolResponse to keep the Temporal payload small
840
+ return { toolResponse: "", data: response.data, resultAppended: true };
841
+ };
842
+ }
843
+
804
844
  /**
805
845
  * Identity function that creates a generic inference context for a tool definition.
806
846
  * TypeScript infers TResult from the handler and flows it to hooks automatically.
@@ -872,7 +912,7 @@ export function defineSubagent<
872
912
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
873
913
  | ((input: { prompt: string; context: TContext }) => Promise<any>);
874
914
  context: TContext;
875
- hooks?: SubagentHooks<GenericTaskToolSchemaType, z.infer<TResult>>;
915
+ hooks?: SubagentHooks<SubagentArgs, z.infer<TResult>>;
876
916
  }
877
917
  ): SubagentConfig<TResult>;
878
918
  // Without context — verifies workflow accepts { prompt }
@@ -880,7 +920,7 @@ export function defineSubagent<TResult extends z.ZodType = z.ZodType>(
880
920
  config: Omit<SubagentConfig<TResult>, "hooks" | "workflow"> & {
881
921
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
882
922
  workflow: string | ((input: { prompt: string }) => Promise<any>);
883
- hooks?: SubagentHooks<GenericTaskToolSchemaType, z.infer<TResult>>;
923
+ hooks?: SubagentHooks<SubagentArgs, z.infer<TResult>>;
884
924
  }
885
925
  ): SubagentConfig<TResult>;
886
926
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
package/src/lib/types.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { ToolMessageContent } from "./thread-manager";
2
2
  import type {
3
- BuildInToolDefinitions,
4
3
  InferToolResults,
5
4
  ParsedToolCallUnion,
5
+ RawToolCall,
6
6
  ToolCallResultUnion,
7
7
  ToolMap,
8
8
  } from "./tool-router";
@@ -30,6 +30,7 @@ export interface BaseAgentState {
30
30
  version: number;
31
31
  turns: number;
32
32
  tasks: Map<string, WorkflowTask>;
33
+ systemPrompt: string;
33
34
  }
34
35
 
35
36
  /**
@@ -51,9 +52,9 @@ export interface AgentFile {
51
52
  /**
52
53
  * Agent response from LLM invocation
53
54
  */
54
- export interface AgentResponse {
55
- message: StoredMessage;
56
- stopReason: string | null;
55
+ export interface AgentResponse<M = StoredMessage> {
56
+ message: M;
57
+ rawToolCalls: RawToolCall[];
57
58
  usage?: {
58
59
  input_tokens?: number;
59
60
  output_tokens?: number;
@@ -61,17 +62,35 @@ export interface AgentResponse {
61
62
  };
62
63
  }
63
64
 
65
+ /**
66
+ * Thread operations required by a session.
67
+ * Consumers provide these — typically by wrapping Temporal activities.
68
+ * Use `proxyDefaultThreadOps()` for the default StoredMessage implementation.
69
+ */
70
+ export interface ThreadOps {
71
+ /** Initialize an empty thread */
72
+ initializeThread(threadId: string): Promise<void>;
73
+ /** Append a human message to the thread */
74
+ appendHumanMessage(
75
+ threadId: string,
76
+ content: string | MessageContent
77
+ ): Promise<void>;
78
+ /** Append a tool result to the thread */
79
+ appendToolResult(config: ToolResultConfig): Promise<void>;
80
+ }
81
+
64
82
  /**
65
83
  * Configuration for a Zeitlich agent session
66
84
  */
67
- export interface ZeitlichAgentConfig<T extends ToolMap> {
68
- buildFileTree?: () => Promise<string>;
85
+ export interface ZeitlichAgentConfig<T extends ToolMap, M = StoredMessage> {
69
86
  threadId: string;
70
87
  agentName: string;
71
88
  metadata?: Record<string, unknown>;
72
89
  maxTurns?: number;
73
90
  /** Workflow-specific runAgent activity (with tools pre-bound) */
74
- runAgent: RunAgentActivity;
91
+ runAgent: RunAgentActivity<M>;
92
+ /** Thread operations (initialize, append messages, parse tool calls) */
93
+ threadOps: ThreadOps;
75
94
  /** Tool router for processing tool calls (optional if agent has no tools) */
76
95
  tools?: T;
77
96
  /** Subagent configurations */
@@ -80,27 +99,11 @@ export interface ZeitlichAgentConfig<T extends ToolMap> {
80
99
  hooks?: Hooks<T, ToolCallResultUnion<InferToolResults<T>>>;
81
100
  /** Whether to process tools in parallel */
82
101
  processToolsInParallel?: boolean;
83
- /**
84
- * Base system prompt (e.g., Auditron identity).
85
- * Can be a static string or async function.
86
- */
87
- baseSystemPrompt: string | (() => string | Promise<string>);
88
- /**
89
- * Agent-specific instructions prompt.
90
- * Can be a static string or async function.
91
- */
92
- instructionsPrompt: string | (() => string | Promise<string>);
93
102
  /**
94
103
  * Build context message content from agent-specific context.
95
104
  * Returns MessageContent array for the initial HumanMessage.
96
105
  */
97
106
  buildContextMessage: () => MessageContent | Promise<MessageContent>;
98
- /**
99
- * Build in tools - accepts raw handlers or proxied activities
100
- */
101
- buildInTools?: {
102
- [K in keyof BuildInToolDefinitions]?: BuildInToolDefinitions[K]["handler"];
103
- };
104
107
  }
105
108
 
106
109
  /**
@@ -128,15 +131,17 @@ export interface RunAgentConfig {
128
131
  /**
129
132
  * Type signature for workflow-specific runAgent activity
130
133
  */
131
- export type RunAgentActivity = (
134
+ export type RunAgentActivity<M = StoredMessage> = (
132
135
  config: RunAgentConfig
133
- ) => Promise<AgentResponse>;
136
+ ) => Promise<AgentResponse<M>>;
134
137
  /**
135
138
  * Configuration for appending a tool result
136
139
  */
137
140
  export interface ToolResultConfig {
138
141
  threadId: string;
139
142
  toolCallId: string;
143
+ /** The name of the tool that produced this result */
144
+ toolName: string;
140
145
  /** Content for the tool message (string or complex content parts) */
141
146
  content: ToolMessageContent;
142
147
  }
@@ -145,6 +150,10 @@ export interface ToolResultConfig {
145
150
  // Subagent Configuration
146
151
  // ============================================================================
147
152
 
153
+ /** Infer the z.infer'd result type from a SubagentConfig, or null if no schema */
154
+ export type InferSubagentResult<T extends SubagentConfig> =
155
+ T extends SubagentConfig<infer S> ? z.infer<S> : null;
156
+
148
157
  /**
149
158
  * Configuration for a subagent that can be spawned by the parent workflow.
150
159
  *
@@ -389,7 +398,7 @@ export interface ToolHooks<TArgs = unknown, TResult = unknown> {
389
398
  /** Called after this tool executes successfully */
390
399
  onPostToolUse?: (ctx: {
391
400
  args: TArgs;
392
- result: TResult | null;
401
+ result: TResult;
393
402
  threadId: string;
394
403
  turn: number;
395
404
  durationMs: number;
@@ -1,14 +1,14 @@
1
1
  import { AIMessage, type StoredMessage } from "@langchain/core/messages";
2
2
  import type { ActivityToolHandler } from "../../lib/tool-router";
3
- import type { AskUserQuestionToolSchemaType } from "./tool";
3
+ import type { AskUserQuestionArgs } from "./tool";
4
4
 
5
5
  /**
6
- * Handle user interaction tool result - creates AI messages for display.
6
+ * Creates handler for user interaction tool - creates AI messages for display.
7
7
  */
8
- export const handleAskUserQuestionToolResult: ActivityToolHandler<
9
- AskUserQuestionToolSchemaType,
8
+ export const createAskUserQuestionHandler = (): ActivityToolHandler<
9
+ AskUserQuestionArgs,
10
10
  { chatMessages: StoredMessage[] }
11
- > = async (args) => {
11
+ > => async (args) => {
12
12
  const messages = args.questions.map(
13
13
  ({ question, header, options, multiSelect }) =>
14
14
  new AIMessage({
@@ -1,4 +1,5 @@
1
1
  import z from "zod";
2
+ import type { ToolDefinition } from "../../lib/tool-router";
2
3
 
3
4
  export const askUserQuestionTool = {
4
5
  name: "AskUserQuestion" as const,
@@ -39,8 +40,8 @@ Usage notes:
39
40
  ),
40
41
  }),
41
42
  strict: true,
42
- };
43
+ } satisfies ToolDefinition;
43
44
 
44
- export type AskUserQuestionToolSchemaType = z.infer<
45
+ export type AskUserQuestionArgs = z.infer<
45
46
  typeof askUserQuestionTool.schema
46
47
  >;