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.
- package/README.md +26 -23
- package/dist/{activities-DCaIPQBT.d.ts → activities-IuOIvPHO.d.ts} +6 -6
- package/dist/{activities-BlQR5gX4.d.cts → activities-cIlq1y1y.d.cts} +6 -6
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +3 -3
- package/dist/adapters/sandbox/daytona/index.d.ts +3 -3
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +2 -2
- package/dist/adapters/sandbox/daytona/workflow.d.ts +2 -2
- package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +1 -1
- package/dist/adapters/sandbox/e2b/index.d.ts +1 -1
- package/dist/adapters/sandbox/e2b/index.js.map +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/e2b/workflow.d.ts +1 -1
- package/dist/adapters/thread/anthropic/index.cjs +45 -42
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +10 -10
- package/dist/adapters/thread/anthropic/index.d.ts +10 -10
- package/dist/adapters/thread/anthropic/index.js +45 -42
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +7 -7
- package/dist/adapters/thread/anthropic/workflow.d.ts +7 -7
- package/dist/adapters/thread/google-genai/index.cjs +117 -54
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +27 -23
- package/dist/adapters/thread/google-genai/index.d.ts +27 -23
- package/dist/adapters/thread/google-genai/index.js +117 -54
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +8 -8
- package/dist/adapters/thread/google-genai/workflow.d.ts +8 -8
- package/dist/adapters/thread/langchain/index.cjs +45 -42
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +10 -10
- package/dist/adapters/thread/langchain/index.d.ts +10 -10
- package/dist/adapters/thread/langchain/index.js +45 -42
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +7 -7
- package/dist/adapters/thread/langchain/workflow.d.ts +7 -7
- package/dist/{cold-store-UL13Sstw.d.cts → cold-store-C0uvYTSi.d.cts} +1 -1
- package/dist/{cold-store-aD4TSKlU.d.ts → cold-store-CCnZYWjx.d.ts} +1 -1
- package/dist/index.cjs +15063 -405
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -83
- package/dist/index.d.ts +79 -83
- package/dist/index.js +15064 -402
- package/dist/index.js.map +1 -1
- package/dist/{proxy-BAty3CWM.d.cts → proxy-BVznA2_p.d.cts} +1 -1
- package/dist/{proxy-mbnwBhHw.d.ts → proxy-C4J1pNUk.d.ts} +1 -1
- package/dist/{thread-manager-CICj68PI.d.ts → thread-manager-BqjzWsP7.d.ts} +4 -4
- package/dist/{thread-manager-R6c3lnJy.d.cts → thread-manager-CzIs47uG.d.cts} +4 -4
- package/dist/{thread-manager-DsXvJ5cJ.d.cts → thread-manager-Dzl1fHhV.d.cts} +4 -4
- package/dist/{thread-manager-DtEtbUkp.d.ts → thread-manager-SkSWRPRc.d.ts} +4 -4
- package/dist/{types-gVa5XCWD.d.ts → types-BQvXWcft.d.ts} +1 -1
- package/dist/{types-DF4wzWQG.d.ts → types-CbPnU4RM.d.ts} +3 -3
- package/dist/{types-CJ7tCdl6.d.cts → types-D8W5TnSa.d.cts} +3 -3
- package/dist/{types-CJ7tCdl6.d.ts → types-D8W5TnSa.d.ts} +3 -3
- package/dist/{types-DwBYd0ij.d.ts → types-DZnUqCAP.d.cts} +709 -686
- package/dist/{types-CjY93AWZ.d.cts → types-OEN1xrFg.d.cts} +1 -1
- package/dist/{types-DWeyCTYK.d.cts → types-YNesmGKV.d.ts} +709 -686
- package/dist/{types-DDLPnxBh.d.cts → types-d2RvEP6v.d.cts} +3 -3
- package/dist/{workflow-DdaU7_j4.d.ts → workflow-B3oTe2_D.d.cts} +34 -3
- package/dist/{workflow-DVNPR7eX.d.cts → workflow-Bkzg0cjB.d.ts} +34 -3
- package/dist/workflow.cjs +15021 -362
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +3 -3
- package/dist/workflow.d.ts +3 -3
- package/dist/workflow.js +15022 -359
- package/dist/workflow.js.map +1 -1
- package/package.json +10 -37
- package/src/adapters/thread/anthropic/activities.ts +1 -1
- package/src/adapters/thread/anthropic/fork-transform.test.ts +17 -11
- package/src/adapters/thread/anthropic/model-invoker.test.ts +4 -3
- package/src/adapters/thread/anthropic/model-invoker.ts +1 -1
- package/src/adapters/thread/anthropic/thread-manager.test.ts +2 -2
- package/src/adapters/thread/anthropic/thread-manager.ts +1 -1
- package/src/adapters/thread/google-genai/activities.ts +1 -1
- package/src/adapters/thread/google-genai/fork-transform.test.ts +17 -11
- package/src/adapters/thread/google-genai/model-invoker.test.ts +337 -0
- package/src/adapters/thread/google-genai/model-invoker.ts +107 -23
- package/src/adapters/thread/google-genai/thread-manager.test.ts +2 -2
- package/src/adapters/thread/google-genai/thread-manager.ts +1 -1
- package/src/adapters/thread/langchain/activities.ts +1 -1
- package/src/adapters/thread/langchain/fork-transform.test.ts +17 -11
- package/src/adapters/thread/langchain/model-invoker.ts +1 -1
- package/src/adapters/thread/langchain/thread-manager.test.ts +2 -2
- package/src/adapters/thread/langchain/thread-manager.ts +1 -1
- package/src/index.ts +2 -2
- package/src/lib/sandbox/capability-types.test.ts +2 -2
- package/src/lib/sandbox/manager.ts +2 -6
- package/src/lib/sandbox/sandbox.test.ts +1 -1
- package/src/lib/sandbox/types.ts +2 -2
- package/src/lib/session/session.integration.test.ts +92 -0
- package/src/lib/session/session.ts +23 -0
- package/src/lib/subagent/handler.ts +23 -0
- package/src/lib/subagent/subagent.integration.test.ts +198 -0
- package/src/lib/thread/keys.test.ts +9 -9
- package/src/lib/thread/keys.ts +1 -1
- package/src/lib/thread/manager.test.ts +24 -14
- package/src/lib/thread/manager.ts +19 -23
- package/src/lib/thread/snapshot.test.ts +51 -43
- package/src/lib/thread/snapshot.ts +54 -32
- package/src/lib/thread/test-utils.ts +106 -59
- package/src/lib/thread/tiered.test.ts +1 -1
- package/src/lib/thread/types.ts +2 -2
- package/src/lib/tool-router/router.integration.test.ts +44 -0
- package/src/lib/tool-router/router.ts +149 -33
- package/src/lib/tool-router/types.ts +23 -0
- package/src/lib/workflow.ts +49 -0
- package/src/{adapters/sandbox/inmemory/proxy.ts → test-utils/in-memory-sandbox-proxy.ts} +5 -16
- package/src/{adapters/sandbox/inmemory/index.ts → test-utils/in-memory-sandbox.ts} +11 -3
- package/src/tools/bash/bash.test.ts +1 -1
- package/src/tools/edit/handler.test.ts +1 -1
- package/tsup.config.ts +2 -4
- package/dist/adapters/sandbox/inmemory/index.cjs +0 -214
- package/dist/adapters/sandbox/inmemory/index.cjs.map +0 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +0 -40
- package/dist/adapters/sandbox/inmemory/index.d.ts +0 -40
- package/dist/adapters/sandbox/inmemory/index.js +0 -211
- package/dist/adapters/sandbox/inmemory/index.js.map +0 -1
- package/dist/adapters/sandbox/inmemory/workflow.cjs +0 -36
- package/dist/adapters/sandbox/inmemory/workflow.cjs.map +0 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +0 -27
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +0 -27
- package/dist/adapters/sandbox/inmemory/workflow.js +0 -34
- package/dist/adapters/sandbox/inmemory/workflow.js.map +0 -1
|
@@ -271,6 +271,50 @@ describe("createToolRouter integration", () => {
|
|
|
271
271
|
expect(order[1]).toBe("start-echo-b");
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
+
it("appends parallel results in original call order", async () => {
|
|
275
|
+
const slowEcho = defineTool({
|
|
276
|
+
name: "Echo" as const,
|
|
277
|
+
description: "slow echo with variable latency",
|
|
278
|
+
schema: z.object({ text: z.string(), delay: z.number() }),
|
|
279
|
+
handler: async (args: { text: string; delay: number }) => {
|
|
280
|
+
await new Promise((r) => setTimeout(r, args.delay));
|
|
281
|
+
return { toolResponse: args.text, data: { echoed: args.text } };
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const router = createToolRouter({
|
|
286
|
+
tools: { Echo: slowEcho, Add: mathTool } as const,
|
|
287
|
+
threadId: "t-1",
|
|
288
|
+
appendToolResult: appendSpy.fn,
|
|
289
|
+
parallel: true,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const calls = [
|
|
293
|
+
router.parseToolCall({
|
|
294
|
+
id: "tc-1",
|
|
295
|
+
name: "Echo",
|
|
296
|
+
args: { text: "first", delay: 30 },
|
|
297
|
+
}),
|
|
298
|
+
router.parseToolCall({
|
|
299
|
+
id: "tc-2",
|
|
300
|
+
name: "Echo",
|
|
301
|
+
args: { text: "second", delay: 0 },
|
|
302
|
+
}),
|
|
303
|
+
router.parseToolCall({
|
|
304
|
+
id: "tc-3",
|
|
305
|
+
name: "Echo",
|
|
306
|
+
args: { text: "third", delay: 15 },
|
|
307
|
+
}),
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
await router.processToolCalls(calls);
|
|
311
|
+
|
|
312
|
+
expect(appendSpy.calls).toHaveLength(3);
|
|
313
|
+
expect(at(appendSpy.calls, 0).toolCallId).toBe("tc-1");
|
|
314
|
+
expect(at(appendSpy.calls, 1).toolCallId).toBe("tc-2");
|
|
315
|
+
expect(at(appendSpy.calls, 2).toolCallId).toBe("tc-3");
|
|
316
|
+
});
|
|
317
|
+
|
|
274
318
|
it("processes multiple tool calls sequentially", async () => {
|
|
275
319
|
const order: string[] = [];
|
|
276
320
|
const slowEcho = defineTool({
|
|
@@ -211,17 +211,29 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
211
211
|
* handler requested a session-level rewind; when present, the result is
|
|
212
212
|
* not appended to the thread and siblings should be cancelled.
|
|
213
213
|
*/
|
|
214
|
+
interface PendingAppend {
|
|
215
|
+
toolCallId: string;
|
|
216
|
+
toolName: string;
|
|
217
|
+
content: JsonValue;
|
|
218
|
+
}
|
|
219
|
+
|
|
214
220
|
type ProcessedToolCall =
|
|
215
|
-
| {
|
|
221
|
+
| {
|
|
222
|
+
kind: "result";
|
|
223
|
+
value: ToolCallResultUnion<TResults>;
|
|
224
|
+
pendingAppend?: PendingAppend;
|
|
225
|
+
}
|
|
216
226
|
| { kind: "rewind"; signal: RewindSignal }
|
|
217
|
-
| { kind: "skipped" };
|
|
227
|
+
| { kind: "skipped"; pendingAppend?: PendingAppend };
|
|
218
228
|
|
|
219
229
|
async function processToolCall(
|
|
220
230
|
toolCall: ParsedToolCallUnion<T>,
|
|
221
231
|
turn: number,
|
|
222
232
|
sandboxId?: string,
|
|
223
233
|
onRewindRequested?: (signal: RewindSignal) => void,
|
|
224
|
-
assistantMessageId?: string
|
|
234
|
+
assistantMessageId?: string,
|
|
235
|
+
persistThreadState?: () => Promise<void>,
|
|
236
|
+
deferAppend?: boolean
|
|
225
237
|
): Promise<ProcessedToolCall> {
|
|
226
238
|
const startTime = Date.now();
|
|
227
239
|
const tool = toolMap.get(toolCall.name);
|
|
@@ -229,15 +241,26 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
229
241
|
// --- Pre-hooks: may skip or modify args ---
|
|
230
242
|
const preResult = await runPreHooks(toolCall, tool, turn);
|
|
231
243
|
if (preResult.skip) {
|
|
244
|
+
const skipContent = JSON.stringify({
|
|
245
|
+
skipped: true,
|
|
246
|
+
reason: "Skipped by PreToolUse hook",
|
|
247
|
+
});
|
|
248
|
+
if (deferAppend) {
|
|
249
|
+
return {
|
|
250
|
+
kind: "skipped",
|
|
251
|
+
pendingAppend: {
|
|
252
|
+
toolCallId: toolCall.id,
|
|
253
|
+
toolName: toolCall.name,
|
|
254
|
+
content: skipContent,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
232
258
|
await appendToolResult(uuid4(), {
|
|
233
259
|
threadId: options.threadId,
|
|
234
260
|
threadKey: options.threadKey,
|
|
235
261
|
toolCallId: toolCall.id,
|
|
236
262
|
toolName: toolCall.name,
|
|
237
|
-
content:
|
|
238
|
-
skipped: true,
|
|
239
|
-
reason: "Skipped by PreToolUse hook",
|
|
240
|
-
}),
|
|
263
|
+
content: skipContent,
|
|
241
264
|
});
|
|
242
265
|
return { kind: "skipped" };
|
|
243
266
|
}
|
|
@@ -265,6 +288,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
265
288
|
toolName: toolCall.name,
|
|
266
289
|
...(sandboxId !== undefined && { sandboxId }),
|
|
267
290
|
...(assistantMessageId !== undefined && { assistantMessageId }),
|
|
291
|
+
...(persistThreadState !== undefined && { persistThreadState }),
|
|
268
292
|
};
|
|
269
293
|
const response = await tool.handler(
|
|
270
294
|
effectiveArgs as Parameters<typeof tool.handler>[0],
|
|
@@ -312,19 +336,22 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
312
336
|
}
|
|
313
337
|
|
|
314
338
|
// --- Append result to thread (unless handler already did) ---
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
threadId: options.threadId,
|
|
318
|
-
threadKey: options.threadKey,
|
|
319
|
-
toolCallId: toolCall.id,
|
|
320
|
-
toolName: toolCall.name,
|
|
321
|
-
content,
|
|
322
|
-
};
|
|
339
|
+
const needsAppend = !resultAppended;
|
|
340
|
+
if (needsAppend && !deferAppend) {
|
|
323
341
|
await appendToolResult.executeWithOptions(
|
|
324
342
|
{
|
|
325
343
|
summary: `Append ${toolCall.name} result`,
|
|
326
344
|
},
|
|
327
|
-
[
|
|
345
|
+
[
|
|
346
|
+
uuid4(),
|
|
347
|
+
{
|
|
348
|
+
threadId: options.threadId,
|
|
349
|
+
threadKey: options.threadKey,
|
|
350
|
+
toolCallId: toolCall.id,
|
|
351
|
+
toolName: toolCall.name,
|
|
352
|
+
content,
|
|
353
|
+
},
|
|
354
|
+
]
|
|
328
355
|
);
|
|
329
356
|
}
|
|
330
357
|
|
|
@@ -354,7 +381,18 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
354
381
|
durationMs
|
|
355
382
|
);
|
|
356
383
|
|
|
357
|
-
return {
|
|
384
|
+
return {
|
|
385
|
+
kind: "result",
|
|
386
|
+
value: toolResult,
|
|
387
|
+
...(needsAppend &&
|
|
388
|
+
deferAppend && {
|
|
389
|
+
pendingAppend: {
|
|
390
|
+
toolCallId: toolCall.id,
|
|
391
|
+
toolName: toolCall.name,
|
|
392
|
+
content,
|
|
393
|
+
},
|
|
394
|
+
}),
|
|
395
|
+
};
|
|
358
396
|
}
|
|
359
397
|
|
|
360
398
|
return {
|
|
@@ -407,7 +445,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
407
445
|
): Promise<ProcessToolCallsResult<TResults>> {
|
|
408
446
|
const attachRewind = (
|
|
409
447
|
arr: ToolCallResultUnion<TResults>[],
|
|
410
|
-
rewind: RewindSignal | undefined
|
|
448
|
+
rewind: RewindSignal | undefined
|
|
411
449
|
): ProcessToolCallsResult<TResults> => {
|
|
412
450
|
if (rewind) {
|
|
413
451
|
(arr as ProcessToolCallsResult<TResults>).rewind = rewind;
|
|
@@ -422,6 +460,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
422
460
|
const turn = context?.turn ?? 0;
|
|
423
461
|
const sandboxId = context?.sandboxId;
|
|
424
462
|
const assistantMessageId = context?.assistantMessageId;
|
|
463
|
+
const persistThreadState = context?.persistThreadState;
|
|
425
464
|
|
|
426
465
|
let rewindSignal: RewindSignal | undefined;
|
|
427
466
|
|
|
@@ -443,19 +482,56 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
443
482
|
turn,
|
|
444
483
|
sandboxId,
|
|
445
484
|
onRewindRequested,
|
|
446
|
-
assistantMessageId
|
|
485
|
+
assistantMessageId,
|
|
486
|
+
persistThreadState,
|
|
487
|
+
true
|
|
447
488
|
)
|
|
448
489
|
)
|
|
449
490
|
)
|
|
450
491
|
);
|
|
451
492
|
|
|
493
|
+
// Fail fast on non-cancellation rejections before appending
|
|
494
|
+
// anything, so the thread stays clean for retry/truncation.
|
|
495
|
+
for (const outcome of outcomes) {
|
|
496
|
+
if (
|
|
497
|
+
outcome.status === "rejected" &&
|
|
498
|
+
!isCancellation(outcome.reason)
|
|
499
|
+
) {
|
|
500
|
+
throw outcome.reason;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Append deferred results in original call order so positional
|
|
505
|
+
// correlation between function calls and responses is preserved.
|
|
506
|
+
if (!rewindSignal) {
|
|
507
|
+
for (const outcome of outcomes) {
|
|
508
|
+
if (
|
|
509
|
+
outcome.status === "fulfilled" &&
|
|
510
|
+
outcome.value.kind !== "rewind" &&
|
|
511
|
+
outcome.value.pendingAppend
|
|
512
|
+
) {
|
|
513
|
+
const pa = outcome.value.pendingAppend;
|
|
514
|
+
await appendToolResult.executeWithOptions(
|
|
515
|
+
{ summary: `Append ${pa.toolName} result` },
|
|
516
|
+
[
|
|
517
|
+
uuid4(),
|
|
518
|
+
{
|
|
519
|
+
threadId: options.threadId,
|
|
520
|
+
threadKey: options.threadKey,
|
|
521
|
+
toolCallId: pa.toolCallId,
|
|
522
|
+
toolName: pa.toolName,
|
|
523
|
+
content: pa.content,
|
|
524
|
+
},
|
|
525
|
+
]
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
452
531
|
const results: ToolCallResultUnion<TResults>[] = [];
|
|
453
532
|
for (const outcome of outcomes) {
|
|
454
533
|
if (outcome.status === "rejected") {
|
|
455
|
-
|
|
456
|
-
continue;
|
|
457
|
-
}
|
|
458
|
-
throw outcome.reason;
|
|
534
|
+
continue;
|
|
459
535
|
}
|
|
460
536
|
if (outcome.value.kind === "result") {
|
|
461
537
|
results.push(outcome.value.value);
|
|
@@ -471,7 +547,8 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
471
547
|
turn,
|
|
472
548
|
sandboxId,
|
|
473
549
|
undefined,
|
|
474
|
-
assistantMessageId
|
|
550
|
+
assistantMessageId,
|
|
551
|
+
persistThreadState
|
|
475
552
|
);
|
|
476
553
|
if (outcome.kind === "rewind") {
|
|
477
554
|
rewindSignal = outcome.signal;
|
|
@@ -497,8 +574,12 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
497
574
|
}
|
|
498
575
|
|
|
499
576
|
const processOne = async (
|
|
500
|
-
toolCall: ParsedToolCallUnion<T
|
|
501
|
-
|
|
577
|
+
toolCall: ParsedToolCallUnion<T>,
|
|
578
|
+
deferAppend?: boolean
|
|
579
|
+
): Promise<{
|
|
580
|
+
result: ToolCallResult<TName, TResult>;
|
|
581
|
+
pendingAppend?: PendingAppend;
|
|
582
|
+
}> => {
|
|
502
583
|
const routerContext: RouterContext = {
|
|
503
584
|
threadId: options.threadId,
|
|
504
585
|
...(options.threadKey && { threadKey: options.threadKey }),
|
|
@@ -510,13 +591,17 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
510
591
|
...(context?.assistantMessageId !== undefined && {
|
|
511
592
|
assistantMessageId: context.assistantMessageId,
|
|
512
593
|
}),
|
|
594
|
+
...(context?.persistThreadState !== undefined && {
|
|
595
|
+
persistThreadState: context.persistThreadState,
|
|
596
|
+
}),
|
|
513
597
|
};
|
|
514
598
|
const response = await handler(
|
|
515
599
|
toolCall.args as ToolArgs<T, TName>,
|
|
516
600
|
routerContext as Parameters<typeof handler>[1]
|
|
517
601
|
);
|
|
518
602
|
|
|
519
|
-
|
|
603
|
+
const needsAppend = !response.resultAppended;
|
|
604
|
+
if (needsAppend && !deferAppend) {
|
|
520
605
|
await appendToolResult.executeWithOptions(
|
|
521
606
|
{
|
|
522
607
|
summary: `Append ${toolCall.name} result`,
|
|
@@ -535,20 +620,51 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
535
620
|
}
|
|
536
621
|
|
|
537
622
|
return {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
623
|
+
result: {
|
|
624
|
+
toolCallId: toolCall.id,
|
|
625
|
+
name: toolCall.name as TName,
|
|
626
|
+
data: response.data,
|
|
627
|
+
...(response.metadata && { metadata: response.metadata }),
|
|
628
|
+
},
|
|
629
|
+
...(needsAppend &&
|
|
630
|
+
deferAppend && {
|
|
631
|
+
pendingAppend: {
|
|
632
|
+
toolCallId: toolCall.id,
|
|
633
|
+
toolName: toolCall.name,
|
|
634
|
+
content: response.toolResponse as JsonValue,
|
|
635
|
+
},
|
|
636
|
+
}),
|
|
542
637
|
};
|
|
543
638
|
};
|
|
544
639
|
|
|
545
640
|
if (options.parallel) {
|
|
546
|
-
|
|
641
|
+
const outcomes = await Promise.all(
|
|
642
|
+
matchingCalls.map((tc) => processOne(tc, true))
|
|
643
|
+
);
|
|
644
|
+
for (const { pendingAppend } of outcomes) {
|
|
645
|
+
if (pendingAppend) {
|
|
646
|
+
await appendToolResult.executeWithOptions(
|
|
647
|
+
{ summary: `Append ${pendingAppend.toolName} result` },
|
|
648
|
+
[
|
|
649
|
+
uuid4(),
|
|
650
|
+
{
|
|
651
|
+
threadId: options.threadId,
|
|
652
|
+
threadKey: options.threadKey,
|
|
653
|
+
toolCallId: pendingAppend.toolCallId,
|
|
654
|
+
toolName: pendingAppend.toolName,
|
|
655
|
+
content: pendingAppend.content,
|
|
656
|
+
},
|
|
657
|
+
]
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return outcomes.map((o) => o.result);
|
|
547
662
|
}
|
|
548
663
|
|
|
549
664
|
const results: ToolCallResult<TName, TResult>[] = [];
|
|
550
665
|
for (const toolCall of matchingCalls) {
|
|
551
|
-
|
|
666
|
+
const { result } = await processOne(toolCall);
|
|
667
|
+
results.push(result);
|
|
552
668
|
}
|
|
553
669
|
return results;
|
|
554
670
|
},
|
|
@@ -190,6 +190,20 @@ export interface RouterContext {
|
|
|
190
190
|
* thread so the child's first model call sees a well-formed history.
|
|
191
191
|
*/
|
|
192
192
|
assistantMessageId?: string;
|
|
193
|
+
/**
|
|
194
|
+
* Persist the parent session's current `PersistedThreadState` slice
|
|
195
|
+
* (tasks + custom state) to the durable thread store. Wired up by
|
|
196
|
+
* the session — absent for manually-driven routers (tests, custom
|
|
197
|
+
* orchestrators).
|
|
198
|
+
*
|
|
199
|
+
* Subagent handlers invoke this before spawning a child that will
|
|
200
|
+
* read the parent's thread (`newThreadSource: "from-parent"` or an
|
|
201
|
+
* explicit parent threadId): the parent's slice otherwise only
|
|
202
|
+
* lands in storage at session-exit time, so the child would load a
|
|
203
|
+
* stale (or empty) snapshot. Best-effort — failures are logged by
|
|
204
|
+
* the session but never thrown.
|
|
205
|
+
*/
|
|
206
|
+
persistThreadState?: () => Promise<void>;
|
|
193
207
|
}
|
|
194
208
|
|
|
195
209
|
/**
|
|
@@ -314,6 +328,15 @@ export interface ProcessToolCallsContext {
|
|
|
314
328
|
* out of a parent-forked thread).
|
|
315
329
|
*/
|
|
316
330
|
assistantMessageId?: string;
|
|
331
|
+
/**
|
|
332
|
+
* Optional callback that flushes the session's in-memory
|
|
333
|
+
* `PersistedThreadState` slice to the durable thread store. The
|
|
334
|
+
* router forwards it into every handler's {@link RouterContext}
|
|
335
|
+
* verbatim. The session uses this to let mid-loop tool handlers
|
|
336
|
+
* (notably subagents that fork or continue the parent's thread)
|
|
337
|
+
* persist the parent's slice before the child reads it.
|
|
338
|
+
*/
|
|
339
|
+
persistThreadState?: () => Promise<void>;
|
|
317
340
|
}
|
|
318
341
|
|
|
319
342
|
/**
|
package/src/lib/workflow.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ThreadInit, SandboxInit, SandboxShutdown } from "./lifecycle";
|
|
2
|
+
import type { SandboxSnapshot } from "./sandbox/types";
|
|
3
|
+
import type { TokenUsage } from "./types";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Session config fields derived from a main workflow input, ready to spread
|
|
@@ -13,6 +15,25 @@ export interface WorkflowSessionInput {
|
|
|
13
15
|
sandbox?: SandboxInit;
|
|
14
16
|
/** Sandbox shutdown policy (default: "destroy") */
|
|
15
17
|
sandboxShutdown?: SandboxShutdown;
|
|
18
|
+
/**
|
|
19
|
+
* Called by the session right before `runSession` returns. Installed by
|
|
20
|
+
* `defineWorkflow` to capture sandbox / thread / usage outputs and forward
|
|
21
|
+
* them to the workflow's `onSessionExit` config hook. Spread into
|
|
22
|
+
* `createSession` via `...sessionInput`.
|
|
23
|
+
*/
|
|
24
|
+
onSessionExit?: (result: {
|
|
25
|
+
sandboxId?: string;
|
|
26
|
+
snapshot?: SandboxSnapshot;
|
|
27
|
+
threadId: string;
|
|
28
|
+
usage: {
|
|
29
|
+
totalInputTokens: number;
|
|
30
|
+
totalOutputTokens: number;
|
|
31
|
+
totalCachedWriteTokens: number;
|
|
32
|
+
totalCachedReadTokens: number;
|
|
33
|
+
totalReasonTokens: number;
|
|
34
|
+
turns: number;
|
|
35
|
+
};
|
|
36
|
+
}) => void;
|
|
16
37
|
}
|
|
17
38
|
|
|
18
39
|
/** Raw workflow input fields that map into `WorkflowSessionInput`. */
|
|
@@ -34,6 +55,18 @@ export interface WorkflowConfig {
|
|
|
34
55
|
* - `"keep"` — leave the sandbox running (no-op on exit).
|
|
35
56
|
*/
|
|
36
57
|
sandboxShutdown?: SandboxShutdown;
|
|
58
|
+
/**
|
|
59
|
+
* Called right before the underlying session exits, with the sandbox /
|
|
60
|
+
* thread outputs and normalized token usage. Mirrors the capture logic in
|
|
61
|
+
* `defineSubagentWorkflow`; useful for emitting metrics or persisting
|
|
62
|
+
* sandbox / thread ids without threading them through the handler result.
|
|
63
|
+
*/
|
|
64
|
+
onSessionExit?: (result: {
|
|
65
|
+
sandboxId?: string;
|
|
66
|
+
snapshot?: SandboxSnapshot;
|
|
67
|
+
threadId: string;
|
|
68
|
+
usage: TokenUsage;
|
|
69
|
+
}) => void;
|
|
37
70
|
}
|
|
38
71
|
|
|
39
72
|
/**
|
|
@@ -59,6 +92,22 @@ export function defineWorkflow<TInput, TResult>(
|
|
|
59
92
|
sandboxShutdown: config.sandboxShutdown ?? "destroy",
|
|
60
93
|
...(workflowInput.thread && { thread: workflowInput.thread }),
|
|
61
94
|
...(workflowInput.sandbox && { sandbox: workflowInput.sandbox }),
|
|
95
|
+
...(config.onSessionExit && {
|
|
96
|
+
onSessionExit: ({ sandboxId, snapshot, threadId, usage }): void => {
|
|
97
|
+
config.onSessionExit?.({
|
|
98
|
+
...(sandboxId !== undefined && { sandboxId }),
|
|
99
|
+
...(snapshot !== undefined && { snapshot }),
|
|
100
|
+
threadId,
|
|
101
|
+
usage: {
|
|
102
|
+
inputTokens: usage.totalInputTokens,
|
|
103
|
+
outputTokens: usage.totalOutputTokens,
|
|
104
|
+
cachedWriteTokens: usage.totalCachedWriteTokens,
|
|
105
|
+
cachedReadTokens: usage.totalCachedReadTokens,
|
|
106
|
+
reasonTokens: usage.totalReasonTokens,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
62
111
|
};
|
|
63
112
|
return fn(input, sessionInput);
|
|
64
113
|
};
|
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Test-only workflow-safe proxy for the in-memory sandbox fixture.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* By default the scope is derived from `workflowInfo().workflowType`,
|
|
8
|
-
* so activities are automatically namespaced per workflow.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```typescript
|
|
12
|
-
* import { proxyInMemorySandboxOps } from 'zeitlich/adapters/sandbox/inmemory/workflow';
|
|
13
|
-
*
|
|
14
|
-
* // Auto-scoped to the current workflow name
|
|
15
|
-
* const sandbox = proxyInMemorySandboxOps();
|
|
16
|
-
* ```
|
|
4
|
+
* Not part of the shipped public surface — used by the capability-type
|
|
5
|
+
* fixtures to exercise the full-capability proxy shape.
|
|
17
6
|
*/
|
|
18
7
|
import { proxyActivities, workflowInfo } from "@temporalio/workflow";
|
|
19
8
|
import type {
|
|
20
9
|
SandboxCreateOptions,
|
|
21
10
|
SandboxOps,
|
|
22
|
-
} from "
|
|
23
|
-
import type { InMemoryCaps } from "./
|
|
11
|
+
} from "../lib/sandbox/types";
|
|
12
|
+
import type { InMemoryCaps } from "./in-memory-sandbox";
|
|
24
13
|
|
|
25
14
|
const ADAPTER_PREFIX = "inMemory";
|
|
26
15
|
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-only in-memory {@link Sandbox} provider backed by `just-bash`.
|
|
3
|
+
*
|
|
4
|
+
* This is **not** part of the shipped public surface — it lives under
|
|
5
|
+
* `src/test-utils` purely so the sandbox manager, tool handlers, and
|
|
6
|
+
* capability-type fixtures have a lightweight, fully-featured backend to
|
|
7
|
+
* exercise against. `just-bash` is a dev dependency for the same reason.
|
|
8
|
+
*/
|
|
1
9
|
import {
|
|
2
10
|
Bash,
|
|
3
11
|
InMemoryFs,
|
|
@@ -18,9 +26,9 @@ import type {
|
|
|
18
26
|
ExecResult,
|
|
19
27
|
DirentEntry,
|
|
20
28
|
FileStat,
|
|
21
|
-
} from "
|
|
22
|
-
import { SandboxNotFoundError } from "
|
|
23
|
-
import { getShortId } from "
|
|
29
|
+
} from "../lib/sandbox/types";
|
|
30
|
+
import { SandboxNotFoundError } from "../lib/sandbox/types";
|
|
31
|
+
import { getShortId } from "../lib/thread/id";
|
|
24
32
|
|
|
25
33
|
// ============================================================================
|
|
26
34
|
// Adapter: IFileSystem → SandboxFileSystem
|
|
@@ -2,7 +2,7 @@ import { describe, expect, it, beforeEach } from "vitest";
|
|
|
2
2
|
import { bashHandler } from "./handler";
|
|
3
3
|
import { withSandbox } from "../../lib/tool-router/with-sandbox";
|
|
4
4
|
import { SandboxManager } from "../../lib/sandbox/manager";
|
|
5
|
-
import { InMemorySandboxProvider } from "../../
|
|
5
|
+
import { InMemorySandboxProvider } from "../../test-utils/in-memory-sandbox";
|
|
6
6
|
import type { RouterContext } from "../../lib/tool-router/types";
|
|
7
7
|
import type { Sandbox, SandboxCreateOptions } from "../../lib/sandbox";
|
|
8
8
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { InMemorySandboxProvider } from "../../
|
|
2
|
+
import { InMemorySandboxProvider } from "../../test-utils/in-memory-sandbox";
|
|
3
3
|
import type { Sandbox, SandboxCreateOptions } from "../../lib/sandbox";
|
|
4
4
|
import { SandboxManager } from "../../lib/sandbox/manager";
|
|
5
5
|
import type { RouterContext } from "../../lib/tool-router/types";
|
package/tsup.config.ts
CHANGED
|
@@ -15,9 +15,6 @@ export default defineConfig({
|
|
|
15
15
|
"adapters/thread/anthropic/index": "src/adapters/thread/anthropic/index.ts",
|
|
16
16
|
"adapters/thread/anthropic/workflow":
|
|
17
17
|
"src/adapters/thread/anthropic/proxy.ts",
|
|
18
|
-
"adapters/sandbox/inmemory/index": "src/adapters/sandbox/inmemory/index.ts",
|
|
19
|
-
"adapters/sandbox/inmemory/workflow":
|
|
20
|
-
"src/adapters/sandbox/inmemory/proxy.ts",
|
|
21
18
|
"adapters/sandbox/daytona/index": "src/adapters/sandbox/daytona/index.ts",
|
|
22
19
|
"adapters/sandbox/daytona/workflow":
|
|
23
20
|
"src/adapters/sandbox/daytona/proxy.ts",
|
|
@@ -38,7 +35,8 @@ export default defineConfig({
|
|
|
38
35
|
/^@anthropic-ai\//,
|
|
39
36
|
/^@daytonaio\//,
|
|
40
37
|
/^@e2b\//,
|
|
41
|
-
"
|
|
38
|
+
"redis",
|
|
39
|
+
/^@redis\//,
|
|
42
40
|
"@mongodb-js/zstd",
|
|
43
41
|
"node-liblzma",
|
|
44
42
|
],
|