zeitlich 0.2.37 → 0.2.39
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 +18 -0
- package/dist/{activities-Bb-nAjwQ.d.ts → activities-Bmu7XnaG.d.ts} +4 -4
- package/dist/{activities-vkI4_3CC.d.cts → activities-ByBFLvm2.d.cts} +4 -4
- package/dist/adapter-id-BB-mmrts.d.cts +17 -0
- package/dist/adapter-id-BB-mmrts.d.ts +17 -0
- package/dist/adapter-id-CMwVrVqv.d.cts +17 -0
- package/dist/adapter-id-CMwVrVqv.d.ts +17 -0
- package/dist/adapter-id-CbY2zeSt.d.cts +17 -0
- package/dist/adapter-id-CbY2zeSt.d.ts +17 -0
- 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 +150 -13
- package/dist/adapters/thread/anthropic/index.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/index.d.cts +9 -8
- package/dist/adapters/thread/anthropic/index.d.ts +9 -8
- package/dist/adapters/thread/anthropic/index.js +150 -14
- package/dist/adapters/thread/anthropic/index.js.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.cjs +9 -3
- package/dist/adapters/thread/anthropic/workflow.cjs.map +1 -1
- package/dist/adapters/thread/anthropic/workflow.d.cts +6 -5
- package/dist/adapters/thread/anthropic/workflow.d.ts +6 -5
- package/dist/adapters/thread/anthropic/workflow.js +9 -4
- package/dist/adapters/thread/anthropic/workflow.js.map +1 -1
- package/dist/adapters/thread/google-genai/index.cjs +154 -13
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +6 -5
- package/dist/adapters/thread/google-genai/index.d.ts +6 -5
- package/dist/adapters/thread/google-genai/index.js +154 -14
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.cjs +9 -3
- package/dist/adapters/thread/google-genai/workflow.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/workflow.d.cts +6 -5
- package/dist/adapters/thread/google-genai/workflow.d.ts +6 -5
- package/dist/adapters/thread/google-genai/workflow.js +9 -4
- package/dist/adapters/thread/google-genai/workflow.js.map +1 -1
- package/dist/adapters/thread/index.cjs +16 -0
- package/dist/adapters/thread/index.cjs.map +1 -0
- package/dist/adapters/thread/index.d.cts +34 -0
- package/dist/adapters/thread/index.d.ts +34 -0
- package/dist/adapters/thread/index.js +12 -0
- package/dist/adapters/thread/index.js.map +1 -0
- package/dist/adapters/thread/langchain/index.cjs +149 -14
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +9 -8
- package/dist/adapters/thread/langchain/index.d.ts +9 -8
- package/dist/adapters/thread/langchain/index.js +149 -15
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/adapters/thread/langchain/workflow.cjs +9 -3
- package/dist/adapters/thread/langchain/workflow.cjs.map +1 -1
- package/dist/adapters/thread/langchain/workflow.d.cts +6 -5
- package/dist/adapters/thread/langchain/workflow.d.ts +6 -5
- package/dist/adapters/thread/langchain/workflow.js +9 -4
- package/dist/adapters/thread/langchain/workflow.js.map +1 -1
- package/dist/index.cjs +367 -59
- 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 +365 -61
- package/dist/index.js.map +1 -1
- package/dist/{proxy-DEtowJyd.d.cts → proxy-BAKzNGRq.d.cts} +1 -1
- package/dist/{proxy-0smGKvx8.d.ts → proxy-DO_MXbY4.d.ts} +1 -1
- package/dist/{thread-manager-C-C4pI2z.d.ts → thread-manager-CcRXasqs.d.ts} +2 -2
- package/dist/{thread-manager-D4vgzYrh.d.cts → thread-manager-ClwSaUnj.d.cts} +2 -2
- package/dist/{thread-manager-3fszQih4.d.ts → thread-manager-D-7lp1JK.d.ts} +2 -2
- package/dist/{thread-manager-CzYln2OC.d.cts → thread-manager-Y8Ucf0Tf.d.cts} +2 -2
- package/dist/{types-CPKDl-y_.d.ts → types-Bcbiq8iv.d.cts} +195 -22
- package/dist/{types-CNuWnvy9.d.ts → types-DAsQ21Rt.d.ts} +1 -1
- package/dist/{types-B37hKoWA.d.ts → types-DpHTX-iO.d.ts} +58 -1
- package/dist/{types-BO7Yju20.d.cts → types-Dt8-HBBT.d.ts} +195 -22
- package/dist/{types-D08CXPh8.d.cts → types-hFFi-Zd9.d.cts} +58 -1
- 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-Bmf9EtDW.d.ts} +83 -3
- package/dist/{workflow-Do_lzJpT.d.cts → workflow-Bx9utBwb.d.cts} +83 -3
- package/dist/workflow.cjs +266 -39
- 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 +264 -41
- package/dist/workflow.js.map +1 -1
- package/package.json +12 -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/adapter-id.test.ts +42 -0
- package/src/adapters/thread/anthropic/activities.ts +40 -5
- package/src/adapters/thread/anthropic/adapter-id.ts +16 -0
- package/src/adapters/thread/anthropic/fork-transform.test.ts +291 -0
- package/src/adapters/thread/anthropic/index.ts +3 -0
- package/src/adapters/thread/anthropic/model-invoker.ts +7 -1
- package/src/adapters/thread/anthropic/proxy.ts +3 -2
- package/src/adapters/thread/anthropic/thread-manager.ts +27 -1
- package/src/adapters/thread/google-genai/activities.ts +44 -5
- package/src/adapters/thread/google-genai/adapter-id.ts +16 -0
- package/src/adapters/thread/google-genai/fork-transform.test.ts +149 -0
- package/src/adapters/thread/google-genai/index.ts +3 -0
- package/src/adapters/thread/google-genai/model-invoker.ts +8 -2
- package/src/adapters/thread/google-genai/proxy.ts +3 -2
- package/src/adapters/thread/google-genai/thread-manager.ts +27 -1
- package/src/adapters/thread/index.ts +39 -0
- package/src/adapters/thread/langchain/activities.ts +40 -5
- package/src/adapters/thread/langchain/adapter-id.ts +16 -0
- package/src/adapters/thread/langchain/fork-transform.test.ts +142 -0
- package/src/adapters/thread/langchain/index.ts +3 -0
- package/src/adapters/thread/langchain/model-invoker.ts +7 -1
- package/src/adapters/thread/langchain/proxy.ts +3 -2
- package/src/adapters/thread/langchain/thread-manager.ts +27 -1
- package/src/lib/lifecycle.ts +14 -5
- package/src/lib/model/types.ts +7 -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 +336 -4
- package/src/lib/session/session.integration.test.ts +192 -2
- package/src/lib/session/session.ts +102 -8
- package/src/lib/session/types.ts +66 -3
- package/src/lib/state/index.ts +1 -0
- package/src/lib/state/manager.integration.test.ts +109 -0
- package/src/lib/state/manager.ts +38 -8
- package/src/lib/state/types.ts +25 -0
- package/src/lib/subagent/handler.ts +124 -11
- package/src/lib/subagent/index.ts +5 -1
- package/src/lib/subagent/subagent.integration.test.ts +628 -104
- package/src/lib/subagent/types.ts +63 -14
- package/src/lib/subagent/workflow.ts +29 -2
- package/src/lib/thread/index.ts +5 -0
- package/src/lib/thread/keys.test.ts +101 -0
- package/src/lib/thread/keys.ts +94 -0
- package/src/lib/thread/manager.test.ts +139 -0
- package/src/lib/thread/manager.ts +105 -9
- package/src/lib/thread/proxy.ts +3 -0
- package/src/lib/thread/types.ts +64 -1
- 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 +42 -1
- package/src/lib/types.ts +12 -0
- package/src/workflow.ts +14 -1
- package/tsup.config.ts +1 -0
package/src/lib/thread/types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type Redis from "ioredis";
|
|
2
|
-
import type { JsonValue } from "../state/types";
|
|
2
|
+
import type { JsonValue, PersistedThreadState } from "../state/types";
|
|
3
3
|
export interface ThreadManagerConfig<T> {
|
|
4
4
|
redis: Redis;
|
|
5
5
|
threadId: string;
|
|
@@ -34,8 +34,50 @@ export interface BaseThreadManager<T> {
|
|
|
34
34
|
* forks — each call creates an independent copy.
|
|
35
35
|
*/
|
|
36
36
|
fork(newThreadId: string): Promise<BaseThreadManager<T>>;
|
|
37
|
+
/**
|
|
38
|
+
* Atomically replace the entire contents of the thread with `messages`.
|
|
39
|
+
* The existing list is cleared, the new messages are appended in order,
|
|
40
|
+
* and dedup markers from prior appends are cleared so future idempotent
|
|
41
|
+
* appends with ids that were removed aren't silently skipped.
|
|
42
|
+
*
|
|
43
|
+
* Requires the thread manager to be configured with `idOf`.
|
|
44
|
+
*/
|
|
45
|
+
replaceAll(messages: T[]): Promise<void>;
|
|
37
46
|
/** Delete the thread */
|
|
38
47
|
delete(): Promise<void>;
|
|
48
|
+
/** Get the number of stored messages currently in the thread */
|
|
49
|
+
length(): Promise<number>;
|
|
50
|
+
/**
|
|
51
|
+
* Truncate the thread starting at the message with id `messageId`.
|
|
52
|
+
* That message and every message after it are removed. If `messageId`
|
|
53
|
+
* is not present in the thread this is a no-op — useful as the
|
|
54
|
+
* "truncate on entry" step of the `runAgent` activity, which becomes a
|
|
55
|
+
* no-op on the first attempt and a cleanup on Temporal workflow reset
|
|
56
|
+
* or in-workflow rewind retries.
|
|
57
|
+
*
|
|
58
|
+
* Dedup markers for removed single-message appends are also cleared so
|
|
59
|
+
* that appending the same id again (e.g. the same assistant message id
|
|
60
|
+
* on a rewind retry) is not silently skipped.
|
|
61
|
+
*
|
|
62
|
+
* Requires the thread manager to be configured with `idOf`.
|
|
63
|
+
*/
|
|
64
|
+
truncateFromId(messageId: string): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Load the persisted state slice associated with this thread, or
|
|
67
|
+
* `null` if none has been saved yet. Safe to call on any thread —
|
|
68
|
+
* treats a missing slice as a non-error.
|
|
69
|
+
*/
|
|
70
|
+
loadState(): Promise<PersistedThreadState | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Overwrite the persisted state slice for this thread. The thread
|
|
73
|
+
* itself must already exist (same TTL as the message list).
|
|
74
|
+
*
|
|
75
|
+
* Note: {@link BaseThreadManager.fork} already copies the slice to
|
|
76
|
+
* the new thread, so there's no separate `forkState` method.
|
|
77
|
+
*/
|
|
78
|
+
saveState(state: PersistedThreadState): Promise<void>;
|
|
79
|
+
/** Delete just the persisted state slice, leaving messages intact. */
|
|
80
|
+
deleteState(): Promise<void>;
|
|
39
81
|
}
|
|
40
82
|
|
|
41
83
|
/**
|
|
@@ -72,6 +114,27 @@ export interface ThreadManagerHooks<TStored, TPrepared = TStored> {
|
|
|
72
114
|
index: number,
|
|
73
115
|
messages: readonly TPrepared[]
|
|
74
116
|
) => TPrepared;
|
|
117
|
+
/**
|
|
118
|
+
* One-shot list-level pre-pass applied once when a thread is forked with
|
|
119
|
+
* `transform: true`. Runs before {@link onForkTransform}. May filter,
|
|
120
|
+
* compact, prepend, or otherwise rewrite the whole forked thread — so the
|
|
121
|
+
* returned length need not match the input length. Async, so implementations
|
|
122
|
+
* may call an LLM or other I/O.
|
|
123
|
+
*/
|
|
124
|
+
onForkPrepareThread?: (
|
|
125
|
+
messages: readonly TStored[]
|
|
126
|
+
) => TStored[] | Promise<TStored[]>;
|
|
127
|
+
/**
|
|
128
|
+
* Per-message final pass applied once when a thread is forked with
|
|
129
|
+
* `transform: true`. Runs after {@link onForkPrepareThread}. Pure 1:1 map —
|
|
130
|
+
* must return a value for every input message; length cannot change. Same
|
|
131
|
+
* shape as {@link onPreparedMessage}.
|
|
132
|
+
*/
|
|
133
|
+
onForkTransform?: (
|
|
134
|
+
message: TStored,
|
|
135
|
+
index: number,
|
|
136
|
+
messages: readonly TStored[]
|
|
137
|
+
) => TStored;
|
|
75
138
|
}
|
|
76
139
|
|
|
77
140
|
export interface ProviderThreadManager<
|
|
@@ -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>(
|
|
@@ -2,6 +2,7 @@ import type { TokenUsage, ToolResultConfig } from "../types";
|
|
|
2
2
|
import type { JsonValue } from "../state/types";
|
|
3
3
|
import type { z } from "zod";
|
|
4
4
|
import type { ActivityFunctionWithOptions } from "@temporalio/workflow";
|
|
5
|
+
import type { SandboxSnapshot } from "../sandbox/types";
|
|
5
6
|
|
|
6
7
|
// ============================================================================
|
|
7
8
|
// Tool Definition Types
|
|
@@ -139,12 +140,29 @@ export interface ToolHandlerResponse<
|
|
|
139
140
|
* payloads through Temporal's activity payload limit.
|
|
140
141
|
*/
|
|
141
142
|
resultAppended?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* When true, the session will rewind: any in-flight parallel tool
|
|
145
|
+
* calls are cancelled and the LLM call is retried. The session reuses
|
|
146
|
+
* the same `assistantMessageId` for the retry; the next `runAgent`
|
|
147
|
+
* activity truncates the thread from that id on entry, wiping the
|
|
148
|
+
* triggering assistant message and any tool results already appended
|
|
149
|
+
* before re-invoking the LLM.
|
|
150
|
+
*
|
|
151
|
+
* The `toolResponse` for a rewinding tool call is ignored (never
|
|
152
|
+
* appended) since the thread is rewound back to the pre-assistant
|
|
153
|
+
* state on the next invocation.
|
|
154
|
+
*/
|
|
155
|
+
rewind?: boolean;
|
|
142
156
|
/** Token usage from the tool execution (e.g. child agent invocations) */
|
|
143
157
|
usage?: TokenUsage;
|
|
144
158
|
/** Thread ID used by the handler (surfaced to the LLM for subagent thread continuation) */
|
|
145
159
|
threadId?: string;
|
|
146
160
|
/** Sandbox ID created or used by the handler (e.g. child agent sandbox) */
|
|
147
161
|
sandboxId?: string;
|
|
162
|
+
/** Snapshot captured on exit when `sandboxShutdown === "snapshot"`. */
|
|
163
|
+
snapshot?: SandboxSnapshot;
|
|
164
|
+
/** Snapshot captured immediately after sandbox seeding (before the agent loop starts) when `sandbox.mode === "new"` and `sandboxShutdown === "snapshot"`. Intended as a reusable "base" for new threads that want to skip re-seeding. */
|
|
165
|
+
baseSnapshot?: SandboxSnapshot;
|
|
148
166
|
/** Unvalidated metadata passthrough from handler to hooks (e.g. infrastructure state) */
|
|
149
167
|
metadata?: Record<string, unknown>;
|
|
150
168
|
}
|
|
@@ -278,6 +296,29 @@ export interface ProcessToolCallsContext {
|
|
|
278
296
|
sandboxId?: string;
|
|
279
297
|
}
|
|
280
298
|
|
|
299
|
+
/**
|
|
300
|
+
* Signal that a tool handler requested a rewind. Attached to the
|
|
301
|
+
* {@link ProcessToolCallsResult} so the session can reuse the same
|
|
302
|
+
* `assistantMessageId` for the retry; the next `runAgent` activity
|
|
303
|
+
* then truncates the thread from that id on entry.
|
|
304
|
+
*/
|
|
305
|
+
export interface RewindSignal {
|
|
306
|
+
toolCallId: string;
|
|
307
|
+
toolName: string;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Result returned by {@link ToolRouter.processToolCalls}.
|
|
312
|
+
*
|
|
313
|
+
* The object is a standard array of tool call results for successful
|
|
314
|
+
* tool calls (cancelled or rewinding siblings are omitted), extended
|
|
315
|
+
* with a `rewind` property when any tool in the batch requested a
|
|
316
|
+
* rewind. Using an array-with-property lets existing code that treats
|
|
317
|
+
* the return value as `ToolCallResultUnion[]` continue to work.
|
|
318
|
+
*/
|
|
319
|
+
export type ProcessToolCallsResult<TResults extends Record<string, unknown>> =
|
|
320
|
+
ToolCallResultUnion<TResults>[] & { rewind?: RewindSignal };
|
|
321
|
+
|
|
281
322
|
// ============================================================================
|
|
282
323
|
// Hook Types
|
|
283
324
|
// ============================================================================
|
|
@@ -469,7 +510,7 @@ export interface ToolRouter<T extends ToolMap> {
|
|
|
469
510
|
processToolCalls(
|
|
470
511
|
toolCalls: ParsedToolCallUnion<T>[],
|
|
471
512
|
context?: ProcessToolCallsContext
|
|
472
|
-
): Promise<
|
|
513
|
+
): Promise<ProcessToolCallsResult<InferToolResults<T>>>;
|
|
473
514
|
|
|
474
515
|
/**
|
|
475
516
|
* Process tool calls matching a specific name with a custom handler.
|
package/src/lib/types.ts
CHANGED
|
@@ -92,6 +92,18 @@ export interface RunAgentConfig extends AgentConfig {
|
|
|
92
92
|
threadKey?: string;
|
|
93
93
|
/** Metadata for the session */
|
|
94
94
|
metadata?: Record<string, unknown>;
|
|
95
|
+
/**
|
|
96
|
+
* The id under which the assistant message produced by this call will
|
|
97
|
+
* be appended. The activity truncates the thread from this id on
|
|
98
|
+
* entry (no-op on the first attempt) so that:
|
|
99
|
+
*
|
|
100
|
+
* - Rewind retries can reuse the same id and the previous (bad)
|
|
101
|
+
* assistant + its tool results are wiped before the retry LLM call.
|
|
102
|
+
* - Resetting the Temporal workflow to this activity restores the
|
|
103
|
+
* pre-call thread state: replay re-truncates, re-invokes, and
|
|
104
|
+
* appends under the same id.
|
|
105
|
+
*/
|
|
106
|
+
assistantMessageId: string;
|
|
95
107
|
}
|
|
96
108
|
|
|
97
109
|
/**
|
package/src/workflow.ts
CHANGED
|
@@ -40,6 +40,11 @@ export type {
|
|
|
40
40
|
|
|
41
41
|
// Thread utilities
|
|
42
42
|
export { getShortId } from "./lib/thread/id";
|
|
43
|
+
export {
|
|
44
|
+
THREAD_TTL_SECONDS,
|
|
45
|
+
getThreadListKey,
|
|
46
|
+
getThreadMetaKey,
|
|
47
|
+
} from "./lib/thread/keys";
|
|
43
48
|
|
|
44
49
|
// State management
|
|
45
50
|
export { createAgentStateManager } from "./lib/state";
|
|
@@ -49,6 +54,7 @@ export type {
|
|
|
49
54
|
JsonSerializable,
|
|
50
55
|
JsonValue,
|
|
51
56
|
JsonPrimitive,
|
|
57
|
+
PersistedThreadState,
|
|
52
58
|
} from "./lib/state";
|
|
53
59
|
|
|
54
60
|
// Tool router (includes registry functionality)
|
|
@@ -57,7 +63,11 @@ export {
|
|
|
57
63
|
hasNoOtherToolCalls,
|
|
58
64
|
defineTool,
|
|
59
65
|
} from "./lib/tool-router";
|
|
60
|
-
export {
|
|
66
|
+
export {
|
|
67
|
+
defineSubagent,
|
|
68
|
+
defineSubagentWorkflow,
|
|
69
|
+
DEFAULT_SUBAGENT_WORKFLOW_RUN_TIMEOUT,
|
|
70
|
+
} from "./lib/subagent";
|
|
61
71
|
export type {
|
|
62
72
|
// Tool definition types
|
|
63
73
|
ToolDefinition,
|
|
@@ -94,6 +104,8 @@ export type {
|
|
|
94
104
|
// Other
|
|
95
105
|
AppendToolResultFn,
|
|
96
106
|
ProcessToolCallsContext,
|
|
107
|
+
ProcessToolCallsResult,
|
|
108
|
+
RewindSignal,
|
|
97
109
|
} from "./lib/tool-router";
|
|
98
110
|
|
|
99
111
|
// Session & message lifecycle hooks
|
|
@@ -151,6 +163,7 @@ export { proxyRunAgent } from "./lib/model/proxy";
|
|
|
151
163
|
// Subagent types
|
|
152
164
|
export type {
|
|
153
165
|
SubagentConfig,
|
|
166
|
+
SubagentChildWorkflowOptions,
|
|
154
167
|
SubagentDefinition,
|
|
155
168
|
SubagentFnResult,
|
|
156
169
|
SubagentHooks,
|
package/tsup.config.ts
CHANGED
|
@@ -4,6 +4,7 @@ export default defineConfig({
|
|
|
4
4
|
entry: {
|
|
5
5
|
index: "src/index.ts",
|
|
6
6
|
workflow: "src/workflow.ts",
|
|
7
|
+
"adapters/thread/index": "src/adapters/thread/index.ts",
|
|
7
8
|
"adapters/thread/langchain/index": "src/adapters/thread/langchain/index.ts",
|
|
8
9
|
"adapters/thread/langchain/workflow":
|
|
9
10
|
"src/adapters/thread/langchain/proxy.ts",
|