zeitlich 0.2.15 → 0.2.17

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 (78) hide show
  1. package/README.md +125 -64
  2. package/dist/adapters/sandbox/daytona/index.cjs +52 -23
  3. package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
  4. package/dist/adapters/sandbox/daytona/index.d.cts +10 -2
  5. package/dist/adapters/sandbox/daytona/index.d.ts +10 -2
  6. package/dist/adapters/sandbox/daytona/index.js +52 -23
  7. package/dist/adapters/sandbox/daytona/index.js.map +1 -1
  8. package/dist/adapters/sandbox/inmemory/index.cjs +21 -16
  9. package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
  10. package/dist/adapters/sandbox/inmemory/index.d.cts +1 -1
  11. package/dist/adapters/sandbox/inmemory/index.d.ts +1 -1
  12. package/dist/adapters/sandbox/inmemory/index.js +21 -16
  13. package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
  14. package/dist/adapters/sandbox/virtual/index.cjs +83 -38
  15. package/dist/adapters/sandbox/virtual/index.cjs.map +1 -1
  16. package/dist/adapters/sandbox/virtual/index.d.cts +6 -6
  17. package/dist/adapters/sandbox/virtual/index.d.ts +6 -6
  18. package/dist/adapters/sandbox/virtual/index.js +80 -38
  19. package/dist/adapters/sandbox/virtual/index.js.map +1 -1
  20. package/dist/adapters/thread/google-genai/index.d.cts +2 -2
  21. package/dist/adapters/thread/google-genai/index.d.ts +2 -2
  22. package/dist/adapters/thread/langchain/index.cjs +2 -2
  23. package/dist/adapters/thread/langchain/index.cjs.map +1 -1
  24. package/dist/adapters/thread/langchain/index.d.cts +2 -2
  25. package/dist/adapters/thread/langchain/index.d.ts +2 -2
  26. package/dist/adapters/thread/langchain/index.js +2 -2
  27. package/dist/adapters/thread/langchain/index.js.map +1 -1
  28. package/dist/index.cjs +102 -10
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +6 -6
  31. package/dist/index.d.ts +6 -6
  32. package/dist/index.js +98 -11
  33. package/dist/index.js.map +1 -1
  34. package/dist/{types-CwwgQ_9H.d.ts → queries-BlC1I3DK.d.ts} +48 -3
  35. package/dist/{types-BVP87m_W.d.cts → queries-DlJ3jE48.d.cts} +48 -3
  36. package/dist/{types-CDubRtad.d.cts → types-BMRzfELQ.d.cts} +2 -0
  37. package/dist/{types-CDubRtad.d.ts → types-BMRzfELQ.d.ts} +2 -0
  38. package/dist/{types-Dje1TdH6.d.cts → types-Bh-BbfCp.d.cts} +31 -12
  39. package/dist/{types-BWvIYK28.d.ts → types-NkiAxU4t.d.ts} +31 -12
  40. package/dist/workflow.cjs +102 -10
  41. package/dist/workflow.cjs.map +1 -1
  42. package/dist/workflow.d.cts +114 -40
  43. package/dist/workflow.d.ts +114 -40
  44. package/dist/workflow.js +98 -11
  45. package/dist/workflow.js.map +1 -1
  46. package/package.json +1 -1
  47. package/src/adapters/sandbox/daytona/filesystem.ts +43 -19
  48. package/src/adapters/sandbox/daytona/index.ts +16 -3
  49. package/src/adapters/sandbox/daytona/types.ts +4 -0
  50. package/src/adapters/sandbox/inmemory/index.ts +22 -16
  51. package/src/adapters/sandbox/virtual/filesystem.ts +29 -31
  52. package/src/adapters/sandbox/virtual/index.ts +7 -3
  53. package/src/adapters/sandbox/virtual/provider.ts +5 -2
  54. package/src/adapters/sandbox/virtual/queries.ts +97 -0
  55. package/src/adapters/sandbox/virtual/types.ts +3 -0
  56. package/src/adapters/sandbox/virtual/with-virtual-sandbox.ts +4 -3
  57. package/src/adapters/thread/langchain/activities.ts +7 -5
  58. package/src/lib/sandbox/tree.integration.test.ts +153 -0
  59. package/src/lib/sandbox/types.ts +2 -0
  60. package/src/lib/session/session-edge-cases.integration.test.ts +962 -0
  61. package/src/lib/session/session.integration.test.ts +853 -0
  62. package/src/lib/session/session.ts +5 -4
  63. package/src/lib/skills/skills.integration.test.ts +308 -0
  64. package/src/lib/state/manager.integration.test.ts +342 -0
  65. package/src/lib/subagent/define.ts +34 -47
  66. package/src/lib/subagent/handler.ts +9 -6
  67. package/src/lib/subagent/index.ts +4 -1
  68. package/src/lib/subagent/subagent.integration.test.ts +573 -0
  69. package/src/lib/subagent/types.ts +40 -10
  70. package/src/lib/subagent/workflow.ts +114 -0
  71. package/src/lib/thread/id.test.ts +50 -0
  72. package/src/lib/tool-router/auto-append-sandbox.integration.test.ts +344 -0
  73. package/src/lib/tool-router/router-edge-cases.integration.test.ts +623 -0
  74. package/src/lib/tool-router/router.integration.test.ts +699 -0
  75. package/src/lib/types.test.ts +29 -0
  76. package/src/lib/workflow.test.ts +131 -0
  77. package/src/lib/workflow.ts +45 -0
  78. package/src/workflow.ts +12 -2
@@ -0,0 +1,573 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { z } from "zod";
3
+
4
+ vi.mock("@temporalio/workflow", () => {
5
+ let counter = 0;
6
+ return {
7
+ workflowInfo: () => ({ taskQueue: "default-queue" }),
8
+ executeChild: vi.fn(async (_workflow: unknown, opts: { args: unknown[] }) => {
9
+ const prompt = (opts.args as [string])[0];
10
+ return {
11
+ toolResponse: `Response to: ${prompt}`,
12
+ data: { result: "child-data" },
13
+ threadId: "child-thread-1",
14
+ usage: { inputTokens: 100, outputTokens: 50 },
15
+ };
16
+ }),
17
+ uuid4: () => {
18
+ counter++;
19
+ const bytes = Array.from({ length: 16 }, (_, i) =>
20
+ ((counter * 31 + i * 7) & 0xff).toString(16).padStart(2, "0"),
21
+ ).join("");
22
+ return `${bytes.slice(0, 8)}-${bytes.slice(8, 12)}-${bytes.slice(12, 16)}-${bytes.slice(16, 20)}-${bytes.slice(20, 32)}`;
23
+ },
24
+ };
25
+ });
26
+
27
+ import { createSubagentTool, SUBAGENT_TOOL_NAME } from "./tool";
28
+ import { createSubagentHandler } from "./handler";
29
+ import { buildSubagentRegistration } from "./register";
30
+ import { defineSubagentWorkflow } from "./workflow";
31
+ import type {
32
+ SubagentConfig,
33
+ SubagentSessionInput,
34
+ SubagentWorkflowInput,
35
+ } from "./types";
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // createSubagentTool
39
+ // ---------------------------------------------------------------------------
40
+
41
+ describe("createSubagentTool", () => {
42
+ it("creates tool with correct name and schema for single subagent", () => {
43
+ const tool = createSubagentTool([
44
+ {
45
+ agentName: "researcher",
46
+ description: "Researches topics",
47
+ workflow: "researcherWorkflow",
48
+ },
49
+ ]);
50
+
51
+ expect(tool.name).toBe(SUBAGENT_TOOL_NAME);
52
+ expect(tool.description).toContain("researcher");
53
+ expect(tool.description).toContain("Researches topics");
54
+
55
+ const valid = tool.schema.safeParse({
56
+ subagent: "researcher",
57
+ description: "Research something",
58
+ prompt: "Find info about X",
59
+ });
60
+ expect(valid.success).toBe(true);
61
+ });
62
+
63
+ it("creates enum schema for multiple subagents", () => {
64
+ const tool = createSubagentTool([
65
+ {
66
+ agentName: "researcher",
67
+ description: "Researches",
68
+ workflow: "researcherWorkflow",
69
+ },
70
+ {
71
+ agentName: "writer",
72
+ description: "Writes",
73
+ workflow: "writerWorkflow",
74
+ },
75
+ ]);
76
+
77
+ const validResearcher = tool.schema.safeParse({
78
+ subagent: "researcher",
79
+ description: "desc",
80
+ prompt: "prompt",
81
+ });
82
+ expect(validResearcher.success).toBe(true);
83
+
84
+ const validWriter = tool.schema.safeParse({
85
+ subagent: "writer",
86
+ description: "desc",
87
+ prompt: "prompt",
88
+ });
89
+ expect(validWriter.success).toBe(true);
90
+
91
+ const invalidAgent = tool.schema.safeParse({
92
+ subagent: "nonexistent",
93
+ description: "desc",
94
+ prompt: "prompt",
95
+ });
96
+ expect(invalidAgent.success).toBe(false);
97
+ });
98
+
99
+ it("adds threadId field when allowThreadContinuation is set", () => {
100
+ const tool = createSubagentTool([
101
+ {
102
+ agentName: "agent",
103
+ description: "supports continuation",
104
+ workflow: "workflow",
105
+ allowThreadContinuation: true,
106
+ },
107
+ ]);
108
+
109
+ const withThread = tool.schema.safeParse({
110
+ subagent: "agent",
111
+ description: "desc",
112
+ prompt: "prompt",
113
+ threadId: "some-thread",
114
+ });
115
+ expect(withThread.success).toBe(true);
116
+
117
+ const withNull = tool.schema.safeParse({
118
+ subagent: "agent",
119
+ description: "desc",
120
+ prompt: "prompt",
121
+ threadId: null,
122
+ });
123
+ expect(withNull.success).toBe(true);
124
+ });
125
+
126
+ it("does not include threadId field when no subagent has allowThreadContinuation", () => {
127
+ const tool = createSubagentTool([
128
+ {
129
+ agentName: "basic",
130
+ description: "basic agent",
131
+ workflow: "workflow",
132
+ },
133
+ ]);
134
+
135
+ const result = tool.schema.safeParse({
136
+ subagent: "basic",
137
+ description: "desc",
138
+ prompt: "prompt",
139
+ threadId: "should-strip",
140
+ });
141
+ expect(result.success).toBe(true);
142
+ if (result.success) {
143
+ expect(result.data).not.toHaveProperty("threadId");
144
+ }
145
+ });
146
+
147
+ it("throws when no subagents are provided", () => {
148
+ expect(() => createSubagentTool([])).toThrow(
149
+ "createSubagentTool requires at least one subagent",
150
+ );
151
+ });
152
+
153
+ it("includes thread continuation note in description", () => {
154
+ const tool = createSubagentTool([
155
+ {
156
+ agentName: "cont-agent",
157
+ description: "Supports continuation",
158
+ workflow: "workflow",
159
+ allowThreadContinuation: true,
160
+ },
161
+ ]);
162
+
163
+ expect(tool.description).toContain("thread continuation");
164
+ });
165
+ });
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // createSubagentHandler
169
+ // ---------------------------------------------------------------------------
170
+
171
+ describe("createSubagentHandler", () => {
172
+ const basicSubagent: SubagentConfig = {
173
+ agentName: "researcher",
174
+ description: "Researches topics",
175
+ workflow: "researcherWorkflow",
176
+ };
177
+
178
+ it("executes child workflow and returns response", async () => {
179
+ const handler = createSubagentHandler([basicSubagent]);
180
+
181
+ const result = await handler(
182
+ { subagent: "researcher", description: "test", prompt: "Find info" },
183
+ { threadId: "parent-thread", toolCallId: "tc-1", toolName: "Subagent" },
184
+ );
185
+
186
+ expect(result.toolResponse).toContain("Response to: Find info");
187
+ expect(result.data).toEqual({ result: "child-data" });
188
+ });
189
+
190
+ it("throws for unknown subagent name", async () => {
191
+ const handler = createSubagentHandler([basicSubagent]);
192
+
193
+ await expect(
194
+ handler(
195
+ { subagent: "nonexistent", description: "test", prompt: "test" },
196
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent" },
197
+ ),
198
+ ).rejects.toThrow("Unknown subagent: nonexistent");
199
+ });
200
+
201
+ it("includes available subagent names in error message", async () => {
202
+ const handler = createSubagentHandler([
203
+ basicSubagent,
204
+ {
205
+ agentName: "writer",
206
+ description: "Writes",
207
+ workflow: "writerWorkflow",
208
+ },
209
+ ]);
210
+
211
+ await expect(
212
+ handler(
213
+ { subagent: "bad", description: "test", prompt: "test" },
214
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent" },
215
+ ),
216
+ ).rejects.toThrow(/researcher.*writer/);
217
+ });
218
+
219
+ it("validates result against resultSchema", async () => {
220
+ const { executeChild } = await import("@temporalio/workflow");
221
+ (executeChild as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
222
+ toolResponse: "result",
223
+ data: { invalid: "data" },
224
+ threadId: "child-t",
225
+ });
226
+
227
+ const validatedSubagent: SubagentConfig = {
228
+ agentName: "validated",
229
+ description: "Has validation",
230
+ workflow: "workflow",
231
+ resultSchema: z.object({ expected: z.string() }),
232
+ };
233
+
234
+ const handler = createSubagentHandler([validatedSubagent]);
235
+
236
+ const result = await handler(
237
+ { subagent: "validated", description: "test", prompt: "test" },
238
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent" },
239
+ );
240
+
241
+ expect(result.toolResponse).toContain("invalid data");
242
+ expect(result.data).toBeNull();
243
+ });
244
+
245
+ it("appends thread ID when allowThreadContinuation is set", async () => {
246
+ const { executeChild } = await import("@temporalio/workflow");
247
+ (executeChild as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
248
+ toolResponse: "Some response",
249
+ data: null,
250
+ threadId: "child-thread-99",
251
+ });
252
+
253
+ const contSubagent: SubagentConfig = {
254
+ agentName: "cont",
255
+ description: "Continues threads",
256
+ workflow: "workflow",
257
+ allowThreadContinuation: true,
258
+ };
259
+
260
+ const handler = createSubagentHandler([contSubagent]);
261
+
262
+ const result = await handler(
263
+ { subagent: "cont", description: "test", prompt: "test" },
264
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent" },
265
+ );
266
+
267
+ expect(result.toolResponse).toContain("Thread ID: child-thread-99");
268
+ });
269
+
270
+ it("returns fallback when child workflow returns no toolResponse", async () => {
271
+ const { executeChild } = await import("@temporalio/workflow");
272
+ (executeChild as ReturnType<typeof vi.fn>).mockResolvedValueOnce({
273
+ toolResponse: null,
274
+ data: null,
275
+ threadId: "child-t",
276
+ });
277
+
278
+ const handler = createSubagentHandler([basicSubagent]);
279
+
280
+ const result = await handler(
281
+ { subagent: "researcher", description: "test", prompt: "test" },
282
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent" },
283
+ );
284
+
285
+ expect(result.toolResponse).toContain("no response");
286
+ expect(result.data).toBeNull();
287
+ });
288
+
289
+ it("passes sandboxId to child when sandbox is inherit", async () => {
290
+ const { executeChild } = await import("@temporalio/workflow");
291
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
292
+ execMock.mockResolvedValueOnce({
293
+ toolResponse: "ok",
294
+ data: null,
295
+ threadId: "child-t",
296
+ });
297
+
298
+ const inheritSubagent: SubagentConfig = {
299
+ agentName: "inherit-agent",
300
+ description: "Inherits sandbox",
301
+ workflow: "workflow",
302
+ sandbox: "inherit",
303
+ };
304
+
305
+ const handler = createSubagentHandler([inheritSubagent]);
306
+
307
+ await handler(
308
+ { subagent: "inherit-agent", description: "test", prompt: "test" },
309
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent", sandboxId: "parent-sb" },
310
+ );
311
+
312
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
313
+ if (!lastCall) throw new Error("expected exec call");
314
+ const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
315
+ expect(workflowInput.sandboxId).toBe("parent-sb");
316
+ });
317
+
318
+ it("does not pass sandboxId when sandbox is own", async () => {
319
+ const { executeChild } = await import("@temporalio/workflow");
320
+ const execMock = executeChild as ReturnType<typeof vi.fn>;
321
+ execMock.mockResolvedValueOnce({
322
+ toolResponse: "ok",
323
+ data: null,
324
+ threadId: "child-t",
325
+ });
326
+
327
+ const ownSubagent: SubagentConfig = {
328
+ agentName: "own-agent",
329
+ description: "Own sandbox",
330
+ workflow: "workflow",
331
+ sandbox: "own",
332
+ };
333
+
334
+ const handler = createSubagentHandler([ownSubagent]);
335
+
336
+ await handler(
337
+ { subagent: "own-agent", description: "test", prompt: "test" },
338
+ { threadId: "t", toolCallId: "tc", toolName: "Subagent", sandboxId: "parent-sb" },
339
+ );
340
+
341
+ const lastCall = execMock.mock.calls[execMock.mock.calls.length - 1];
342
+ if (!lastCall) throw new Error("expected exec call");
343
+ const workflowInput = lastCall[1].args[1] as SubagentWorkflowInput;
344
+ expect(workflowInput.sandboxId).toBeUndefined();
345
+ });
346
+ });
347
+
348
+ // ---------------------------------------------------------------------------
349
+ // buildSubagentRegistration
350
+ // ---------------------------------------------------------------------------
351
+
352
+ describe("buildSubagentRegistration", () => {
353
+ it("returns null for empty array", () => {
354
+ expect(buildSubagentRegistration([])).toBeNull();
355
+ });
356
+
357
+ it("creates registration with correct tool name", () => {
358
+ const reg = buildSubagentRegistration([
359
+ {
360
+ agentName: "agent",
361
+ description: "An agent",
362
+ workflow: "workflow",
363
+ },
364
+ ]);
365
+
366
+ expect(reg).not.toBeNull();
367
+ expect(reg).toBeDefined();
368
+ if (reg) {
369
+ expect(reg.name).toBe(SUBAGENT_TOOL_NAME);
370
+ expect(typeof reg.handler).toBe("function");
371
+ }
372
+ });
373
+
374
+ it("enabled getter reflects dynamic subagent state", () => {
375
+ const config: SubagentConfig = {
376
+ agentName: "toggle",
377
+ description: "Toggleable",
378
+ workflow: "workflow",
379
+ enabled: true,
380
+ };
381
+
382
+ const reg = buildSubagentRegistration([config]);
383
+ expect(reg).toBeDefined();
384
+ if (!reg) return;
385
+ expect(reg.enabled).toBe(true);
386
+
387
+ config.enabled = false;
388
+ expect(reg.enabled).toBe(false);
389
+ });
390
+
391
+ it("disabled when all subagents are disabled", () => {
392
+ const reg = buildSubagentRegistration([
393
+ {
394
+ agentName: "off",
395
+ description: "Disabled",
396
+ workflow: "workflow",
397
+ enabled: false,
398
+ },
399
+ ]);
400
+
401
+ expect(reg).toBeDefined();
402
+ if (reg) {
403
+ expect(reg.enabled).toBe(false);
404
+ }
405
+ });
406
+
407
+ it("includes hooks when subagents have hooks configured", () => {
408
+ const hookSpy = vi.fn(async () => ({}));
409
+
410
+ const reg = buildSubagentRegistration([
411
+ {
412
+ agentName: "hooked",
413
+ description: "Has hooks",
414
+ workflow: "workflow",
415
+ hooks: {
416
+ onPreExecution: hookSpy,
417
+ },
418
+ },
419
+ ]);
420
+
421
+ expect(reg).toBeDefined();
422
+ if (reg) {
423
+ expect(reg.hooks).toBeDefined();
424
+ if (reg.hooks) {
425
+ expect(reg.hooks.onPreToolUse).toBeDefined();
426
+ }
427
+ }
428
+ });
429
+
430
+ it("does not include hooks when no subagents have hooks", () => {
431
+ const reg = buildSubagentRegistration([
432
+ {
433
+ agentName: "plain",
434
+ description: "No hooks",
435
+ workflow: "workflow",
436
+ },
437
+ ]);
438
+
439
+ expect(reg).toBeDefined();
440
+ if (reg) {
441
+ expect(reg.hooks).toBeUndefined();
442
+ }
443
+ });
444
+
445
+ it("dynamic schema/description updates when subagents change enabled state", () => {
446
+ const config1: SubagentConfig = {
447
+ agentName: "a",
448
+ description: "Agent A",
449
+ workflow: "workflow",
450
+ enabled: true,
451
+ };
452
+ const config2: SubagentConfig = {
453
+ agentName: "b",
454
+ description: "Agent B",
455
+ workflow: "workflow",
456
+ enabled: true,
457
+ };
458
+
459
+ const reg = buildSubagentRegistration([config1, config2]);
460
+
461
+ expect(reg).toBeDefined();
462
+ if (reg) {
463
+ expect(reg.description).toContain("Agent A");
464
+ expect(reg.description).toContain("Agent B");
465
+
466
+ config2.enabled = false;
467
+
468
+ expect(reg.description).toContain("Agent A");
469
+ expect(reg.description).not.toContain("Agent B");
470
+ }
471
+ });
472
+ });
473
+
474
+ // ---------------------------------------------------------------------------
475
+ // defineSubagentWorkflow
476
+ // ---------------------------------------------------------------------------
477
+
478
+ describe("defineSubagentWorkflow", () => {
479
+ it("maps previousThreadId to threadId + continueThread", async () => {
480
+ let capturedPrompt: string | undefined;
481
+ let capturedSession: SubagentSessionInput | undefined;
482
+
483
+ const workflow = defineSubagentWorkflow(
484
+ { name: "test", description: "test agent" },
485
+ async (prompt, sessionInput) => {
486
+ capturedPrompt = prompt;
487
+ capturedSession = sessionInput;
488
+ return { toolResponse: "ok", data: null, threadId: "t" };
489
+ },
490
+ );
491
+
492
+ await workflow("go", { previousThreadId: "prev-42" });
493
+
494
+ expect(capturedPrompt).toBe("go");
495
+ expect(capturedSession).toEqual({
496
+ agentName: "test",
497
+ threadId: "prev-42",
498
+ continueThread: true,
499
+ });
500
+ });
501
+
502
+ it("maps sandboxId", async () => {
503
+ let capturedSession: SubagentSessionInput | undefined;
504
+ const workflow = defineSubagentWorkflow(
505
+ { name: "test", description: "test agent" },
506
+ async (_prompt, sessionInput) => {
507
+ capturedSession = sessionInput;
508
+ return { toolResponse: "ok", data: null, threadId: "t" };
509
+ },
510
+ );
511
+
512
+ await workflow("go", { sandboxId: "sb-123" });
513
+ expect(capturedSession).toEqual({ agentName: "test", sandboxId: "sb-123" });
514
+ });
515
+
516
+ it("passes context as optional third argument", async () => {
517
+ let capturedContext: Record<string, unknown> | undefined;
518
+ const workflow = defineSubagentWorkflow(
519
+ { name: "test", description: "test agent" },
520
+ async (_prompt, _sessionInput, context) => {
521
+ capturedContext = context;
522
+ return { toolResponse: "ok", data: null, threadId: "t" };
523
+ },
524
+ );
525
+
526
+ await workflow("go", {}, { key: "val" });
527
+
528
+ expect(capturedContext).toEqual({ key: "val" });
529
+ });
530
+
531
+ it("supports omitted context", async () => {
532
+ let capturedContext: Record<string, unknown> | undefined;
533
+ const workflow = defineSubagentWorkflow(
534
+ { name: "test", description: "test agent" },
535
+ async (_prompt, _sessionInput, context) => {
536
+ capturedContext = context;
537
+ return { toolResponse: "ok", data: null, threadId: "t" };
538
+ },
539
+ );
540
+
541
+ await workflow("go", { sandboxId: "sb" });
542
+ expect(capturedContext).toBeUndefined();
543
+ });
544
+
545
+ it("returns the handler response unchanged", async () => {
546
+ const workflow = defineSubagentWorkflow(
547
+ { name: "test", description: "test agent", resultSchema: z.object({ count: z.number() }) },
548
+ async () => ({
549
+ toolResponse: "result text",
550
+ data: { count: 42 },
551
+ threadId: "child-thread",
552
+ }),
553
+ );
554
+
555
+ const result = await workflow("go", {});
556
+
557
+ expect(result.toolResponse).toBe("result text");
558
+ expect(result.data).toEqual({ count: 42 });
559
+ expect(result.threadId).toBe("child-thread");
560
+ });
561
+
562
+ it("attaches metadata to the returned workflow function", () => {
563
+ const schema = z.object({ findings: z.string() });
564
+ const workflow = defineSubagentWorkflow(
565
+ { name: "researcher", description: "Researches topics", resultSchema: schema },
566
+ async () => ({ toolResponse: "ok", data: null, threadId: "t" }),
567
+ );
568
+
569
+ expect(workflow.agentName).toBe("researcher");
570
+ expect(workflow.description).toBe("Researches topics");
571
+ expect(workflow.resultSchema).toBe(schema);
572
+ });
573
+ });
@@ -9,10 +9,40 @@ import type {
9
9
  export type SubagentHandlerResponse<TResult = null> =
10
10
  ToolHandlerResponse<TResult> & { threadId: string };
11
11
 
12
+ /**
13
+ * Raw workflow input fields passed from parent to child workflow.
14
+ * `defineSubagentWorkflow` maps this into `SubagentSessionInput`.
15
+ */
16
+ export interface SubagentWorkflowInput {
17
+ /** Thread ID from parent for continuation */
18
+ previousThreadId?: string;
19
+ /** Sandbox ID inherited from parent */
20
+ sandboxId?: string;
21
+ }
22
+
12
23
  export type SubagentWorkflow<TResult extends z.ZodType = z.ZodType> = (
13
- input: SubagentInput
24
+ prompt: string,
25
+ workflowInput: SubagentWorkflowInput,
26
+ context?: Record<string, unknown>,
14
27
  ) => Promise<SubagentHandlerResponse<z.infer<TResult> | null>>;
15
28
 
29
+ /**
30
+ * A subagent workflow with embedded metadata (name, description, resultSchema).
31
+ * Created by `defineSubagentWorkflow` — pass directly to `defineSubagent`.
32
+ */
33
+ export type SubagentDefinition<
34
+ TResult extends z.ZodType = z.ZodType,
35
+ TContext extends Record<string, unknown> = Record<string, unknown>,
36
+ > = ((
37
+ prompt: string,
38
+ workflowInput: SubagentWorkflowInput,
39
+ context?: TContext,
40
+ ) => Promise<SubagentHandlerResponse<z.infer<TResult> | null>>) & {
41
+ readonly agentName: string;
42
+ readonly description: string;
43
+ readonly resultSchema?: TResult;
44
+ };
45
+
16
46
  /** Infer the z.infer'd result type from a SubagentConfig, or null if no schema */
17
47
  export type InferSubagentResult<T extends SubagentConfig> =
18
48
  T extends SubagentConfig<infer S> ? z.infer<S> : null;
@@ -78,15 +108,15 @@ export interface SubagentHooks<TArgs = unknown, TResult = unknown> {
78
108
  }
79
109
 
80
110
  /**
81
- * Input passed to child workflows when spawned as subagents
111
+ * Session config fields passed from parent to child workflow.
82
112
  */
83
- export interface SubagentInput {
84
- /** The prompt/task from the parent agent */
85
- prompt: string;
86
- /** Optional context parameters passed from the parent agent */
87
- context?: Record<string, unknown>;
88
- /** When set, the subagent should continue this thread instead of starting a new one */
89
- previousThreadId?: string;
90
- /** Sandbox ID inherited from the parent agent (when SubagentConfig.sandbox is 'inherit') */
113
+ export interface SubagentSessionInput {
114
+ /** Agent name spread directly into `createSession` */
115
+ agentName: string;
116
+ /** Thread ID to continue from */
117
+ threadId?: string;
118
+ /** Whether to continue an existing thread */
119
+ continueThread?: boolean;
120
+ /** Sandbox ID inherited from the parent agent */
91
121
  sandboxId?: string;
92
122
  }