zeitlich 0.2.37 → 0.2.38
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/dist/{activities-Bb-nAjwQ.d.ts → activities-BKhMtKDd.d.ts} +4 -2
- package/dist/{activities-vkI4_3CC.d.cts → activities-CDcwkRZs.d.cts} +4 -2
- package/dist/adapters/sandbox/bedrock/index.cjs +3 -3
- package/dist/adapters/sandbox/bedrock/index.cjs.map +1 -1
- package/dist/adapters/sandbox/bedrock/index.d.cts +6 -6
- package/dist/adapters/sandbox/bedrock/index.d.ts +6 -6
- package/dist/adapters/sandbox/bedrock/index.js +3 -3
- package/dist/adapters/sandbox/bedrock/index.js.map +1 -1
- package/dist/adapters/sandbox/bedrock/workflow.d.cts +2 -2
- package/dist/adapters/sandbox/bedrock/workflow.d.ts +2 -2
- package/dist/adapters/sandbox/daytona/index.cjs +3 -3
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +4 -4
- package/dist/adapters/sandbox/daytona/index.d.ts +4 -4
- package/dist/adapters/sandbox/daytona/index.js +3 -3
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/daytona/workflow.d.ts +1 -1
- package/dist/adapters/sandbox/e2b/index.cjs +26 -14
- package/dist/adapters/sandbox/e2b/index.cjs.map +1 -1
- package/dist/adapters/sandbox/e2b/index.d.cts +24 -4
- package/dist/adapters/sandbox/e2b/index.d.ts +24 -4
- package/dist/adapters/sandbox/e2b/index.js +26 -14
- 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/sandbox/inmemory/index.cjs +3 -3
- package/dist/adapters/sandbox/inmemory/index.cjs.map +1 -1
- package/dist/adapters/sandbox/inmemory/index.d.cts +4 -4
- package/dist/adapters/sandbox/inmemory/index.d.ts +4 -4
- package/dist/adapters/sandbox/inmemory/index.js +3 -3
- package/dist/adapters/sandbox/inmemory/index.js.map +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.cts +1 -1
- package/dist/adapters/sandbox/inmemory/workflow.d.ts +1 -1
- package/dist/adapters/thread/anthropic/index.cjs +23 -3
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +5 -5
- package/dist/adapters/thread/anthropic/index.d.ts +5 -5
- package/dist/adapters/thread/anthropic/index.js +23 -3
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.cjs +2 -1
- package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +5 -5
- package/dist/adapters/thread/anthropic/workflow.d.ts +5 -5
- package/dist/adapters/thread/anthropic/workflow.js +2 -1
- package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.cjs +27 -3
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +5 -5
- package/dist/adapters/thread/google-genai/index.d.ts +5 -5
- package/dist/adapters/thread/google-genai/index.js +27 -3
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +2 -1
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +5 -5
- package/dist/adapters/thread/google-genai/workflow.d.ts +5 -5
- package/dist/adapters/thread/google-genai/workflow.js +2 -1
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
- package/dist/adapters/thread/langchain/index.cjs +23 -3
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +5 -5
- package/dist/adapters/thread/langchain/index.d.ts +5 -5
- package/dist/adapters/thread/langchain/index.js +23 -3
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +2 -1
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +5 -5
- package/dist/adapters/thread/langchain/workflow.d.ts +5 -5
- package/dist/adapters/thread/langchain/workflow.js +2 -1
- package/dist/adapters/thread/langchain/workflow.js.map +1 -1
- package/dist/index.cjs +120 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +121 -31
- package/dist/index.js.map +1 -1
- package/dist/{proxy-0smGKvx8.d.ts → proxy-CUlKSvZS.d.ts} +1 -1
- package/dist/{proxy-DEtowJyd.d.cts → proxy-D_3x7RN4.d.cts} +1 -1
- package/dist/{thread-manager-C-C4pI2z.d.ts → thread-manager-CVu7o2cs.d.ts} +4 -2
- package/dist/{thread-manager-D4vgzYrh.d.cts → thread-manager-HSwyh28L.d.cts} +4 -2
- package/dist/{thread-manager-3fszQih4.d.ts → thread-manager-c1gPopAG.d.ts} +4 -2
- package/dist/{thread-manager-CzYln2OC.d.cts → thread-manager-wGi-LqIP.d.cts} +4 -2
- package/dist/{types-B37hKoWA.d.ts → types-BH_IRryz.d.ts} +10 -1
- package/dist/{types-D08CXPh8.d.cts → types-BaOw4hKI.d.cts} +10 -1
- package/dist/{types-CPKDl-y_.d.ts → types-C06FwR96.d.cts} +59 -4
- package/dist/{types-CNuWnvy9.d.ts → types-DAsQ21Rt.d.ts} +1 -1
- package/dist/{types-BO7Yju20.d.cts → types-DNr31FzL.d.ts} +59 -4
- package/dist/{types-DWEUmYAJ.d.cts → types-lm8tMNJQ.d.cts} +1 -1
- package/dist/{types-tQL9njTu.d.cts → types-yx0LzPGn.d.cts} +21 -7
- package/dist/{types-tQL9njTu.d.ts → types-yx0LzPGn.d.ts} +21 -7
- package/dist/{workflow-CjXHbZZc.d.ts → workflow-CSCkpwAL.d.ts} +2 -2
- package/dist/{workflow-Do_lzJpT.d.cts → workflow-DuvMZ8Vm.d.cts} +2 -2
- package/dist/workflow.cjs +94 -18
- 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 +95 -19
- package/dist/workflow.js.map +1 -1
- package/package.json +2 -2
- package/src/adapters/sandbox/bedrock/index.ts +12 -3
- package/src/adapters/sandbox/daytona/index.ts +12 -3
- package/src/adapters/sandbox/e2b/index.ts +36 -14
- package/src/adapters/sandbox/e2b/types.ts +16 -0
- package/src/adapters/sandbox/inmemory/index.ts +12 -3
- package/src/adapters/thread/anthropic/activities.ts +9 -0
- package/src/adapters/thread/anthropic/model-invoker.ts +3 -1
- package/src/adapters/thread/anthropic/thread-manager.ts +3 -0
- package/src/adapters/thread/google-genai/activities.ts +13 -0
- package/src/adapters/thread/google-genai/model-invoker.ts +3 -1
- package/src/adapters/thread/google-genai/thread-manager.ts +3 -0
- package/src/adapters/thread/langchain/activities.ts +9 -0
- package/src/adapters/thread/langchain/model-invoker.ts +2 -1
- package/src/adapters/thread/langchain/thread-manager.ts +3 -0
- package/src/lib/lifecycle.ts +11 -4
- package/src/lib/model/types.ts +10 -0
- package/src/lib/sandbox/manager.ts +26 -18
- package/src/lib/sandbox/types.ts +27 -7
- package/src/lib/session/session-edge-cases.integration.test.ts +265 -1
- package/src/lib/session/session.integration.test.ts +22 -1
- package/src/lib/session/session.ts +61 -7
- package/src/lib/session/types.ts +12 -0
- package/src/lib/subagent/subagent.integration.test.ts +100 -104
- package/src/lib/thread/manager.ts +18 -0
- package/src/lib/thread/proxy.ts +1 -0
- package/src/lib/thread/types.ts +9 -0
- package/src/lib/tool-router/index.ts +2 -0
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +92 -0
- package/src/lib/tool-router/router.integration.test.ts +12 -0
- package/src/lib/tool-router/router.ts +89 -16
- package/src/lib/tool-router/types.ts +34 -1
- package/src/workflow.ts +2 -0
|
@@ -23,8 +23,20 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
const noop = () => {};
|
|
26
|
+
class MockCancellationScope {
|
|
27
|
+
cancellable: boolean;
|
|
28
|
+
constructor(opts?: { cancellable?: boolean }) {
|
|
29
|
+
this.cancellable = opts?.cancellable ?? true;
|
|
30
|
+
}
|
|
31
|
+
async run<T>(fn: () => Promise<T>): Promise<T> {
|
|
32
|
+
return fn();
|
|
33
|
+
}
|
|
34
|
+
cancel(): void {}
|
|
35
|
+
}
|
|
26
36
|
return {
|
|
27
37
|
ApplicationFailure: MockApplicationFailure,
|
|
38
|
+
CancellationScope: MockCancellationScope,
|
|
39
|
+
isCancellation: (_err: unknown) => false,
|
|
28
40
|
uuid4: () => "00000000-0000-0000-0000-000000000000",
|
|
29
41
|
log: { trace: noop, debug: noop, info: noop, warn: noop, error: noop },
|
|
30
42
|
};
|
|
@@ -648,6 +660,86 @@ describe("createToolRouter edge cases", () => {
|
|
|
648
660
|
recovered: true,
|
|
649
661
|
});
|
|
650
662
|
});
|
|
663
|
+
|
|
664
|
+
// --- Rewind signal -------------------------------------------------------
|
|
665
|
+
|
|
666
|
+
it("attaches a rewind signal and skips result append when handler returns rewind:true", async () => {
|
|
667
|
+
const rewindTool = defineTool({
|
|
668
|
+
name: "Rewind" as const,
|
|
669
|
+
description: "rewinds",
|
|
670
|
+
schema: z.object({}),
|
|
671
|
+
handler: async () => ({
|
|
672
|
+
toolResponse: "ignored",
|
|
673
|
+
data: null,
|
|
674
|
+
rewind: true,
|
|
675
|
+
}),
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
const router = createToolRouter({
|
|
679
|
+
tools: { Rewind: rewindTool } as const,
|
|
680
|
+
threadId: "t-1",
|
|
681
|
+
appendToolResult: appendSpy.fn,
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
const parsed = router.parseToolCall({
|
|
685
|
+
id: "tc-1",
|
|
686
|
+
name: "Rewind",
|
|
687
|
+
args: {},
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
const results = await router.processToolCalls([parsed]);
|
|
691
|
+
|
|
692
|
+
expect(results).toHaveLength(0);
|
|
693
|
+
expect(results.rewind).toEqual({
|
|
694
|
+
toolCallId: "tc-1",
|
|
695
|
+
toolName: "Rewind",
|
|
696
|
+
});
|
|
697
|
+
expect(appendSpy.calls).toHaveLength(0);
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
it("short-circuits further sequential tool calls when one requests rewind", async () => {
|
|
701
|
+
let laterCalled = false;
|
|
702
|
+
const laterTool = defineTool({
|
|
703
|
+
name: "Later" as const,
|
|
704
|
+
description: "runs after rewind",
|
|
705
|
+
schema: z.object({}),
|
|
706
|
+
handler: async () => {
|
|
707
|
+
laterCalled = true;
|
|
708
|
+
return { toolResponse: "ok", data: null };
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
const rewindTool = defineTool({
|
|
712
|
+
name: "Rewind" as const,
|
|
713
|
+
description: "rewinds",
|
|
714
|
+
schema: z.object({}),
|
|
715
|
+
handler: async () => ({
|
|
716
|
+
toolResponse: "ignored",
|
|
717
|
+
data: null,
|
|
718
|
+
rewind: true,
|
|
719
|
+
}),
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
const router = createToolRouter({
|
|
723
|
+
tools: { Rewind: rewindTool, Later: laterTool } as const,
|
|
724
|
+
threadId: "t-1",
|
|
725
|
+
appendToolResult: appendSpy.fn,
|
|
726
|
+
parallel: false,
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
const calls = [
|
|
730
|
+
router.parseToolCall({ id: "tc-1", name: "Rewind", args: {} }),
|
|
731
|
+
router.parseToolCall({ id: "tc-2", name: "Later", args: {} }),
|
|
732
|
+
];
|
|
733
|
+
|
|
734
|
+
const results = await router.processToolCalls(calls);
|
|
735
|
+
|
|
736
|
+
expect(results).toHaveLength(0);
|
|
737
|
+
expect(results.rewind).toEqual({
|
|
738
|
+
toolCallId: "tc-1",
|
|
739
|
+
toolName: "Rewind",
|
|
740
|
+
});
|
|
741
|
+
expect(laterCalled).toBe(false);
|
|
742
|
+
});
|
|
651
743
|
});
|
|
652
744
|
|
|
653
745
|
describe("hasNoOtherToolCalls", () => {
|
|
@@ -23,8 +23,20 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
const noop = () => {};
|
|
26
|
+
class MockCancellationScope {
|
|
27
|
+
cancellable: boolean;
|
|
28
|
+
constructor(opts?: { cancellable?: boolean }) {
|
|
29
|
+
this.cancellable = opts?.cancellable ?? true;
|
|
30
|
+
}
|
|
31
|
+
async run<T>(fn: () => Promise<T>): Promise<T> {
|
|
32
|
+
return fn();
|
|
33
|
+
}
|
|
34
|
+
cancel(): void {}
|
|
35
|
+
}
|
|
26
36
|
return {
|
|
27
37
|
ApplicationFailure: MockApplicationFailure,
|
|
38
|
+
CancellationScope: MockCancellationScope,
|
|
39
|
+
isCancellation: (_err: unknown) => false,
|
|
28
40
|
uuid4: () => "00000000-0000-0000-0000-000000000000",
|
|
29
41
|
log: { trace: noop, debug: noop, info: noop, warn: noop, error: noop },
|
|
30
42
|
};
|
|
@@ -15,12 +15,19 @@ import type {
|
|
|
15
15
|
ToolArgs,
|
|
16
16
|
ToolResult,
|
|
17
17
|
ProcessToolCallsContext,
|
|
18
|
+
ProcessToolCallsResult,
|
|
19
|
+
RewindSignal,
|
|
18
20
|
ToolWithHandler,
|
|
19
21
|
} from "./types";
|
|
20
22
|
|
|
21
23
|
import type { JsonValue } from "../state/types";
|
|
22
24
|
import type { z } from "zod";
|
|
23
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
uuid4,
|
|
27
|
+
log,
|
|
28
|
+
CancellationScope,
|
|
29
|
+
isCancellation,
|
|
30
|
+
} from "@temporalio/workflow";
|
|
24
31
|
|
|
25
32
|
/**
|
|
26
33
|
* Creates a tool router for declarative tool call processing.
|
|
@@ -199,11 +206,22 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
199
206
|
}
|
|
200
207
|
}
|
|
201
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Internal per-tool-call outcome. `rewind` signals the caller that the
|
|
211
|
+
* handler requested a session-level rewind; when present, the result is
|
|
212
|
+
* not appended to the thread and siblings should be cancelled.
|
|
213
|
+
*/
|
|
214
|
+
type ProcessedToolCall =
|
|
215
|
+
| { kind: "result"; value: ToolCallResultUnion<TResults> }
|
|
216
|
+
| { kind: "rewind"; signal: RewindSignal }
|
|
217
|
+
| { kind: "skipped" };
|
|
218
|
+
|
|
202
219
|
async function processToolCall(
|
|
203
220
|
toolCall: ParsedToolCallUnion<T>,
|
|
204
221
|
turn: number,
|
|
205
|
-
sandboxId?: string
|
|
206
|
-
|
|
222
|
+
sandboxId?: string,
|
|
223
|
+
onRewindRequested?: (signal: RewindSignal) => void
|
|
224
|
+
): Promise<ProcessedToolCall> {
|
|
207
225
|
const startTime = Date.now();
|
|
208
226
|
const tool = toolMap.get(toolCall.name);
|
|
209
227
|
|
|
@@ -220,7 +238,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
220
238
|
reason: "Skipped by PreToolUse hook",
|
|
221
239
|
}),
|
|
222
240
|
});
|
|
223
|
-
return
|
|
241
|
+
return { kind: "skipped" };
|
|
224
242
|
}
|
|
225
243
|
const effectiveArgs = preResult.args;
|
|
226
244
|
|
|
@@ -235,6 +253,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
235
253
|
let content!: JsonValue;
|
|
236
254
|
let resultAppended = false;
|
|
237
255
|
let metadata: Record<string, unknown> | undefined;
|
|
256
|
+
let rewindRequested = false;
|
|
238
257
|
|
|
239
258
|
try {
|
|
240
259
|
if (tool) {
|
|
@@ -253,11 +272,15 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
253
272
|
content = response.toolResponse as JsonValue;
|
|
254
273
|
resultAppended = response.resultAppended === true;
|
|
255
274
|
metadata = response.metadata;
|
|
275
|
+
rewindRequested = response.rewind === true;
|
|
256
276
|
} else {
|
|
257
277
|
result = { error: `Unknown tool: ${toolCall.name}` };
|
|
258
278
|
content = JSON.stringify(result, null, 2);
|
|
259
279
|
}
|
|
260
280
|
} catch (error) {
|
|
281
|
+
if (isCancellation(error)) {
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
261
284
|
log.warn("tool call failed", {
|
|
262
285
|
toolName: toolCall.name,
|
|
263
286
|
toolCallId: toolCall.id,
|
|
@@ -276,6 +299,16 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
276
299
|
content = recovery.content;
|
|
277
300
|
}
|
|
278
301
|
|
|
302
|
+
if (rewindRequested) {
|
|
303
|
+
const signal: RewindSignal = {
|
|
304
|
+
toolCallId: toolCall.id,
|
|
305
|
+
toolName: toolCall.name,
|
|
306
|
+
};
|
|
307
|
+
log.info("tool requested rewind", { ...signal });
|
|
308
|
+
onRewindRequested?.(signal);
|
|
309
|
+
return { kind: "rewind", signal };
|
|
310
|
+
}
|
|
311
|
+
|
|
279
312
|
// --- Append result to thread (unless handler already did) ---
|
|
280
313
|
if (!resultAppended) {
|
|
281
314
|
const config = {
|
|
@@ -319,7 +352,7 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
319
352
|
durationMs
|
|
320
353
|
);
|
|
321
354
|
|
|
322
|
-
return toolResult;
|
|
355
|
+
return { kind: "result", value: toolResult };
|
|
323
356
|
}
|
|
324
357
|
|
|
325
358
|
return {
|
|
@@ -369,31 +402,71 @@ export function createToolRouter<T extends ToolMap>(
|
|
|
369
402
|
async processToolCalls(
|
|
370
403
|
toolCalls: ParsedToolCallUnion<T>[],
|
|
371
404
|
context?: ProcessToolCallsContext
|
|
372
|
-
): Promise<
|
|
405
|
+
): Promise<ProcessToolCallsResult<TResults>> {
|
|
406
|
+
const attachRewind = (
|
|
407
|
+
arr: ToolCallResultUnion<TResults>[],
|
|
408
|
+
rewind: RewindSignal | undefined,
|
|
409
|
+
): ProcessToolCallsResult<TResults> => {
|
|
410
|
+
if (rewind) {
|
|
411
|
+
(arr as ProcessToolCallsResult<TResults>).rewind = rewind;
|
|
412
|
+
}
|
|
413
|
+
return arr as ProcessToolCallsResult<TResults>;
|
|
414
|
+
};
|
|
415
|
+
|
|
373
416
|
if (toolCalls.length === 0) {
|
|
374
|
-
return [];
|
|
417
|
+
return attachRewind([], undefined);
|
|
375
418
|
}
|
|
376
419
|
|
|
377
420
|
const turn = context?.turn ?? 0;
|
|
378
421
|
const sandboxId = context?.sandboxId;
|
|
379
422
|
|
|
423
|
+
let rewindSignal: RewindSignal | undefined;
|
|
424
|
+
|
|
380
425
|
if (options.parallel) {
|
|
381
|
-
const
|
|
382
|
-
|
|
426
|
+
const scope = new CancellationScope({ cancellable: true });
|
|
427
|
+
const onRewindRequested = (signal: RewindSignal): void => {
|
|
428
|
+
if (!rewindSignal) {
|
|
429
|
+
rewindSignal = signal;
|
|
430
|
+
// Cancel all other in-flight tool calls in this batch.
|
|
431
|
+
scope.cancel();
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
const outcomes = await scope.run(async () =>
|
|
436
|
+
Promise.allSettled(
|
|
437
|
+
toolCalls.map((tc) =>
|
|
438
|
+
processToolCall(tc, turn, sandboxId, onRewindRequested)
|
|
439
|
+
)
|
|
440
|
+
)
|
|
383
441
|
);
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
)
|
|
442
|
+
|
|
443
|
+
const results: ToolCallResultUnion<TResults>[] = [];
|
|
444
|
+
for (const outcome of outcomes) {
|
|
445
|
+
if (outcome.status === "rejected") {
|
|
446
|
+
if (isCancellation(outcome.reason)) {
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
throw outcome.reason;
|
|
450
|
+
}
|
|
451
|
+
if (outcome.value.kind === "result") {
|
|
452
|
+
results.push(outcome.value.value);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return attachRewind(results, rewindSignal);
|
|
387
456
|
}
|
|
388
457
|
|
|
389
458
|
const results: ToolCallResultUnion<TResults>[] = [];
|
|
390
459
|
for (const toolCall of toolCalls) {
|
|
391
|
-
const
|
|
392
|
-
if (
|
|
393
|
-
|
|
460
|
+
const outcome = await processToolCall(toolCall, turn, sandboxId);
|
|
461
|
+
if (outcome.kind === "rewind") {
|
|
462
|
+
rewindSignal = outcome.signal;
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
if (outcome.kind === "result") {
|
|
466
|
+
results.push(outcome.value);
|
|
394
467
|
}
|
|
395
468
|
}
|
|
396
|
-
return results;
|
|
469
|
+
return attachRewind(results, rewindSignal);
|
|
397
470
|
},
|
|
398
471
|
|
|
399
472
|
async processToolCallsByName<TName extends ToolNames<T>, TResult>(
|
|
@@ -139,6 +139,17 @@ export interface ToolHandlerResponse<
|
|
|
139
139
|
* payloads through Temporal's activity payload limit.
|
|
140
140
|
*/
|
|
141
141
|
resultAppended?: boolean;
|
|
142
|
+
/**
|
|
143
|
+
* When true, the session will rewind: any in-flight parallel tool calls
|
|
144
|
+
* are cancelled, previously appended tool results and the triggering
|
|
145
|
+
* assistant message are removed from the thread, and the LLM call that
|
|
146
|
+
* produced the tool calls is retried.
|
|
147
|
+
*
|
|
148
|
+
* The `toolResponse` for a rewinding tool call is ignored (never
|
|
149
|
+
* appended) since the session truncates the thread back to the
|
|
150
|
+
* pre-invocation state.
|
|
151
|
+
*/
|
|
152
|
+
rewind?: boolean;
|
|
142
153
|
/** Token usage from the tool execution (e.g. child agent invocations) */
|
|
143
154
|
usage?: TokenUsage;
|
|
144
155
|
/** Thread ID used by the handler (surfaced to the LLM for subagent thread continuation) */
|
|
@@ -278,6 +289,28 @@ export interface ProcessToolCallsContext {
|
|
|
278
289
|
sandboxId?: string;
|
|
279
290
|
}
|
|
280
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Signal that a tool handler requested a rewind. Attached to the
|
|
294
|
+
* {@link ProcessToolCallsResult} so the session can roll the thread
|
|
295
|
+
* back to the pre-invocation snapshot and retry the LLM call.
|
|
296
|
+
*/
|
|
297
|
+
export interface RewindSignal {
|
|
298
|
+
toolCallId: string;
|
|
299
|
+
toolName: string;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Result returned by {@link ToolRouter.processToolCalls}.
|
|
304
|
+
*
|
|
305
|
+
* The object is a standard array of tool call results for successful
|
|
306
|
+
* tool calls (cancelled or rewinding siblings are omitted), extended
|
|
307
|
+
* with a `rewind` property when any tool in the batch requested a
|
|
308
|
+
* rewind. Using an array-with-property lets existing code that treats
|
|
309
|
+
* the return value as `ToolCallResultUnion[]` continue to work.
|
|
310
|
+
*/
|
|
311
|
+
export type ProcessToolCallsResult<TResults extends Record<string, unknown>> =
|
|
312
|
+
ToolCallResultUnion<TResults>[] & { rewind?: RewindSignal };
|
|
313
|
+
|
|
281
314
|
// ============================================================================
|
|
282
315
|
// Hook Types
|
|
283
316
|
// ============================================================================
|
|
@@ -469,7 +502,7 @@ export interface ToolRouter<T extends ToolMap> {
|
|
|
469
502
|
processToolCalls(
|
|
470
503
|
toolCalls: ParsedToolCallUnion<T>[],
|
|
471
504
|
context?: ProcessToolCallsContext
|
|
472
|
-
): Promise<
|
|
505
|
+
): Promise<ProcessToolCallsResult<InferToolResults<T>>>;
|
|
473
506
|
|
|
474
507
|
/**
|
|
475
508
|
* Process tool calls matching a specific name with a custom handler.
|