zeitlich 0.2.19 → 0.2.21
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/adapters/sandbox/daytona/index.cjs +25 -10
- package/dist/adapters/sandbox/daytona/index.cjs.map +1 -1
- package/dist/adapters/sandbox/daytona/index.d.cts +4 -1
- package/dist/adapters/sandbox/daytona/index.d.ts +4 -1
- package/dist/adapters/sandbox/daytona/index.js +25 -10
- package/dist/adapters/sandbox/daytona/index.js.map +1 -1
- package/dist/adapters/sandbox/virtual/index.d.cts +4 -3
- package/dist/adapters/sandbox/virtual/index.d.ts +4 -3
- package/dist/adapters/thread/google-genai/index.cjs +69 -24
- package/dist/adapters/thread/google-genai/index.cjs.map +1 -1
- package/dist/adapters/thread/google-genai/index.d.cts +10 -10
- package/dist/adapters/thread/google-genai/index.d.ts +10 -10
- package/dist/adapters/thread/google-genai/index.js +69 -24
- package/dist/adapters/thread/google-genai/index.js.map +1 -1
- package/dist/adapters/thread/langchain/index.cjs +76 -69
- package/dist/adapters/thread/langchain/index.cjs.map +1 -1
- package/dist/adapters/thread/langchain/index.d.cts +11 -11
- package/dist/adapters/thread/langchain/index.d.ts +11 -11
- package/dist/adapters/thread/langchain/index.js +76 -69
- package/dist/adapters/thread/langchain/index.js.map +1 -1
- package/dist/index.cjs +198 -118
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -14
- package/dist/index.d.ts +14 -14
- package/dist/index.js +198 -118
- package/dist/index.js.map +1 -1
- package/dist/{queries-D8T4pEeu.d.ts → queries-6Avfh74U.d.ts} +1 -1
- package/dist/{queries-D22uWTOb.d.cts → queries-CHa2iv_I.d.cts} +1 -1
- package/dist/{types-CxWLeJTB.d.ts → types-BkAYmc96.d.ts} +6 -6
- package/dist/{types-CCfJb5Jl.d.cts → types-CES_30qx.d.cts} +6 -6
- package/dist/{types-DjT78Sdp.d.cts → types-YbL7JpEA.d.cts} +4 -2
- package/dist/{types-DjT78Sdp.d.ts → types-YbL7JpEA.d.ts} +4 -2
- package/dist/workflow.cjs +68 -34
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +16 -12
- package/dist/workflow.d.ts +16 -12
- package/dist/workflow.js +68 -34
- package/dist/workflow.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/sandbox/daytona/filesystem.ts +21 -12
- package/src/adapters/sandbox/daytona/index.ts +24 -23
- package/src/adapters/thread/google-genai/activities.ts +11 -9
- package/src/adapters/thread/google-genai/model-invoker.ts +6 -11
- package/src/adapters/thread/google-genai/thread-manager.ts +44 -29
- package/src/adapters/thread/langchain/activities.ts +6 -4
- package/src/adapters/thread/langchain/thread-manager.ts +55 -27
- package/src/lib/session/session-edge-cases.integration.test.ts +20 -2
- package/src/lib/session/session.integration.test.ts +16 -2
- package/src/lib/session/session.ts +7 -5
- package/src/lib/session/types.ts +9 -3
- package/src/lib/subagent/handler.ts +1 -1
- package/src/lib/subagent/subagent.integration.test.ts +5 -4
- package/src/lib/subagent/tool.ts +1 -1
- package/src/lib/thread/index.ts +0 -1
- package/src/lib/tool-router/auto-append-sandbox.integration.test.ts +20 -21
- package/src/lib/tool-router/auto-append.ts +3 -2
- package/src/lib/tool-router/router-edge-cases.integration.test.ts +64 -23
- package/src/lib/tool-router/router.integration.test.ts +60 -23
- package/src/lib/tool-router/router.ts +58 -29
- package/src/lib/tool-router/types.ts +12 -7
- package/src/lib/workflow.test.ts +18 -6
- package/src/lib/workflow.ts +13 -3
- package/src/tools/task-create/handler.ts +3 -6
- package/src/workflow.ts +2 -2
|
@@ -5,6 +5,7 @@ import type { ThreadOps } from "./types";
|
|
|
5
5
|
import type { RunAgentActivity } from "../model/types";
|
|
6
6
|
import type { RawToolCall } from "../tool-router/types";
|
|
7
7
|
import type { SandboxOps } from "../sandbox/types";
|
|
8
|
+
import type { ActivityInterfaceFor } from "@temporalio/workflow";
|
|
8
9
|
|
|
9
10
|
let idCounter = 0;
|
|
10
11
|
|
|
@@ -51,9 +52,26 @@ type TurnScript = {
|
|
|
51
52
|
usage?: TokenUsage;
|
|
52
53
|
};
|
|
53
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Wraps every method on a ThreadOps object so it also has `.executeWithOptions()`,
|
|
57
|
+
* matching Temporal's `ActivityInterfaceFor<ThreadOps>` shape.
|
|
58
|
+
*/
|
|
59
|
+
function toActivityInterface(
|
|
60
|
+
raw: ThreadOps,
|
|
61
|
+
): ActivityInterfaceFor<ThreadOps> {
|
|
62
|
+
const result = {} as Record<string, unknown>;
|
|
63
|
+
for (const [key, fn] of Object.entries(raw)) {
|
|
64
|
+
const wrapped = (...args: unknown[]) => (fn as (...a: unknown[]) => unknown)(...args);
|
|
65
|
+
wrapped.executeWithOptions = (_opts: unknown, args: unknown[]) =>
|
|
66
|
+
(fn as (...a: unknown[]) => unknown)(...args);
|
|
67
|
+
result[key] = wrapped;
|
|
68
|
+
}
|
|
69
|
+
return result as ActivityInterfaceFor<ThreadOps>;
|
|
70
|
+
}
|
|
71
|
+
|
|
54
72
|
function createMockThreadOps() {
|
|
55
73
|
const log: { op: string; args: unknown[] }[] = [];
|
|
56
|
-
const ops
|
|
74
|
+
const ops = toActivityInterface({
|
|
57
75
|
initializeThread: async (threadId) => {
|
|
58
76
|
log.push({ op: "initializeThread", args: [threadId] });
|
|
59
77
|
},
|
|
@@ -69,7 +87,7 @@ function createMockThreadOps() {
|
|
|
69
87
|
forkThread: async (source, target) => {
|
|
70
88
|
log.push({ op: "forkThread", args: [source, target] });
|
|
71
89
|
},
|
|
72
|
-
};
|
|
90
|
+
});
|
|
73
91
|
return { ops, log };
|
|
74
92
|
}
|
|
75
93
|
|
|
@@ -5,6 +5,7 @@ import type { ThreadOps } from "./types";
|
|
|
5
5
|
import type { RunAgentActivity } from "../model/types";
|
|
6
6
|
import type { RawToolCall } from "../tool-router/types";
|
|
7
7
|
import type { SandboxOps } from "../sandbox/types";
|
|
8
|
+
import type { ActivityInterfaceFor } from "@temporalio/workflow";
|
|
8
9
|
|
|
9
10
|
// ---------------------------------------------------------------------------
|
|
10
11
|
// Mock @temporalio/workflow
|
|
@@ -58,10 +59,23 @@ function at<T>(arr: T[], index: number): T {
|
|
|
58
59
|
return val;
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
function toActivityInterface(
|
|
63
|
+
raw: ThreadOps,
|
|
64
|
+
): ActivityInterfaceFor<ThreadOps> {
|
|
65
|
+
const result = {} as Record<string, unknown>;
|
|
66
|
+
for (const [key, fn] of Object.entries(raw)) {
|
|
67
|
+
const wrapped = (...args: unknown[]) => (fn as (...a: unknown[]) => unknown)(...args);
|
|
68
|
+
wrapped.executeWithOptions = (_opts: unknown, args: unknown[]) =>
|
|
69
|
+
(fn as (...a: unknown[]) => unknown)(...args);
|
|
70
|
+
result[key] = wrapped;
|
|
71
|
+
}
|
|
72
|
+
return result as ActivityInterfaceFor<ThreadOps>;
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
function createMockThreadOps() {
|
|
62
76
|
const log: { op: string; args: unknown[] }[] = [];
|
|
63
77
|
|
|
64
|
-
const ops
|
|
78
|
+
const ops = toActivityInterface({
|
|
65
79
|
initializeThread: async (threadId) => {
|
|
66
80
|
log.push({ op: "initializeThread", args: [threadId] });
|
|
67
81
|
},
|
|
@@ -77,7 +91,7 @@ function createMockThreadOps() {
|
|
|
77
91
|
forkThread: async (source, target) => {
|
|
78
92
|
log.push({ op: "forkThread", args: [source, target] });
|
|
79
93
|
},
|
|
80
|
-
};
|
|
94
|
+
});
|
|
81
95
|
|
|
82
96
|
return { ops, log };
|
|
83
97
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
defineUpdate,
|
|
5
5
|
setHandler,
|
|
6
6
|
ApplicationFailure,
|
|
7
|
+
type ActivityInterfaceFor,
|
|
7
8
|
} from "@temporalio/workflow";
|
|
8
9
|
import type { SessionExitReason, MessageContent } from "../types";
|
|
9
10
|
import type { ThreadOps, SessionConfig, ZeitlichSession } from "./types";
|
|
@@ -14,6 +15,7 @@ import type { ParsedToolCallUnion, ToolMap } from "../tool-router/types";
|
|
|
14
15
|
import { getShortId } from "../thread/id";
|
|
15
16
|
import { buildSubagentRegistration } from "../subagent/register";
|
|
16
17
|
import { buildSkillRegistration } from "../skills/register";
|
|
18
|
+
import { uuid4 } from "@temporalio/workflow";
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Creates an agent session that manages the agent loop: LLM invocation,
|
|
@@ -133,7 +135,7 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
133
135
|
threadId,
|
|
134
136
|
});
|
|
135
137
|
}
|
|
136
|
-
await appendHumanMessage(threadId, message);
|
|
138
|
+
await appendHumanMessage(threadId, uuid4(), message);
|
|
137
139
|
if (hooks.onPostHumanMessageAppend) {
|
|
138
140
|
await hooks.onPostHumanMessageAppend({
|
|
139
141
|
message,
|
|
@@ -175,12 +177,12 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
175
177
|
nonRetryable: true,
|
|
176
178
|
});
|
|
177
179
|
}
|
|
178
|
-
await appendSystemMessage(threadId, systemPrompt);
|
|
180
|
+
await appendSystemMessage(threadId, uuid4(), systemPrompt);
|
|
179
181
|
} else {
|
|
180
182
|
await initializeThread(threadId);
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
|
-
await appendHumanMessage(threadId, await buildContextMessage());
|
|
185
|
+
await appendHumanMessage(threadId, uuid4(), await buildContextMessage());
|
|
184
186
|
|
|
185
187
|
let exitReason: SessionExitReason = "completed";
|
|
186
188
|
|
|
@@ -221,7 +223,7 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
221
223
|
try {
|
|
222
224
|
parsedToolCalls.push(toolRouter.parseToolCall(tc));
|
|
223
225
|
} catch (error) {
|
|
224
|
-
await appendToolResult({
|
|
226
|
+
await appendToolResult(uuid4(), {
|
|
225
227
|
threadId,
|
|
226
228
|
toolCallId: tc.id ?? "",
|
|
227
229
|
toolName: tc.name,
|
|
@@ -299,7 +301,7 @@ export const createSession = async <T extends ToolMap, M = unknown>({
|
|
|
299
301
|
*/
|
|
300
302
|
export function proxyDefaultThreadOps(
|
|
301
303
|
options?: Parameters<typeof proxyActivities>[0]
|
|
302
|
-
): ThreadOps {
|
|
304
|
+
): ActivityInterfaceFor<ThreadOps> {
|
|
303
305
|
return proxyActivities<ThreadOps>(
|
|
304
306
|
options ?? {
|
|
305
307
|
startToCloseTimeout: "10s",
|
package/src/lib/session/types.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type { Skill } from "../skills/types";
|
|
|
15
15
|
import type { SandboxOps } from "../sandbox/types";
|
|
16
16
|
import type { RunAgentActivity } from "../model/types";
|
|
17
17
|
import type { AgentStateManager, JsonSerializable } from "../state/types";
|
|
18
|
+
import type { ActivityInterfaceFor } from "@temporalio/workflow";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Thread operations required by a session.
|
|
@@ -26,12 +27,17 @@ export interface ThreadOps {
|
|
|
26
27
|
/** Append a human message to the thread */
|
|
27
28
|
appendHumanMessage(
|
|
28
29
|
threadId: string,
|
|
30
|
+
id: string,
|
|
29
31
|
content: string | MessageContent
|
|
30
32
|
): Promise<void>;
|
|
31
33
|
/** Append a tool result to the thread */
|
|
32
|
-
appendToolResult(config: ToolResultConfig): Promise<void>;
|
|
34
|
+
appendToolResult(id: string, config: ToolResultConfig): Promise<void>;
|
|
33
35
|
/** Append a system message to the thread */
|
|
34
|
-
appendSystemMessage(
|
|
36
|
+
appendSystemMessage(
|
|
37
|
+
threadId: string,
|
|
38
|
+
id: string,
|
|
39
|
+
content: string
|
|
40
|
+
): Promise<void>;
|
|
35
41
|
/** Copy all messages from sourceThreadId into a new thread at targetThreadId */
|
|
36
42
|
forkThread(sourceThreadId: string, targetThreadId: string): Promise<void>;
|
|
37
43
|
}
|
|
@@ -53,7 +59,7 @@ export interface SessionConfig<T extends ToolMap, M = unknown> {
|
|
|
53
59
|
/** Workflow-specific runAgent activity (with tools pre-bound) */
|
|
54
60
|
runAgent: RunAgentActivity<M>;
|
|
55
61
|
/** Thread operations (initialize, append messages, parse tool calls) */
|
|
56
|
-
threadOps?: ThreadOps
|
|
62
|
+
threadOps?: ActivityInterfaceFor<ThreadOps>;
|
|
57
63
|
/** Tool router for processing tool calls (optional if agent has no tools) */
|
|
58
64
|
tools?: T;
|
|
59
65
|
/** Subagent configurations */
|
|
@@ -89,7 +89,7 @@ export function createSubagentHandler<
|
|
|
89
89
|
if (config.allowThreadContinuation && childThreadId) {
|
|
90
90
|
finalToolResponse =
|
|
91
91
|
typeof toolResponse === "string"
|
|
92
|
-
? `${toolResponse}\n\n[Thread ID: ${childThreadId}]`
|
|
92
|
+
? `${toolResponse}\n\n[${config.agentName} Thread ID: ${childThreadId}]`
|
|
93
93
|
: toolResponse;
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -476,13 +476,14 @@ describe("buildSubagentRegistration", () => {
|
|
|
476
476
|
|
|
477
477
|
expect(reg).toBeDefined();
|
|
478
478
|
if (reg) {
|
|
479
|
-
|
|
480
|
-
expect(
|
|
479
|
+
const desc = reg.description as () => string;
|
|
480
|
+
expect(desc()).toContain("Agent A");
|
|
481
|
+
expect(desc()).toContain("Agent B");
|
|
481
482
|
|
|
482
483
|
bEnabled = false;
|
|
483
484
|
|
|
484
|
-
expect(
|
|
485
|
-
expect(
|
|
485
|
+
expect(desc()).toContain("Agent A");
|
|
486
|
+
expect(desc()).not.toContain("Agent B");
|
|
486
487
|
}
|
|
487
488
|
});
|
|
488
489
|
});
|
package/src/lib/subagent/tool.ts
CHANGED
|
@@ -57,7 +57,7 @@ export function createSubagentTool<T extends SubagentConfig[]>(
|
|
|
57
57
|
.string()
|
|
58
58
|
.nullable()
|
|
59
59
|
.describe(
|
|
60
|
-
"Thread ID to continue an existing conversation, or null to start a new one"
|
|
60
|
+
"Thread ID to continue an existing conversation from the same subagent, or null to start a new one"
|
|
61
61
|
),
|
|
62
62
|
})
|
|
63
63
|
: z.object(baseFields);
|
package/src/lib/thread/index.ts
CHANGED
|
@@ -12,13 +12,13 @@ import type { Sandbox } from "../sandbox/types";
|
|
|
12
12
|
describe("withAutoAppend", () => {
|
|
13
13
|
it("appends tool result via threadHandler and sets resultAppended", async () => {
|
|
14
14
|
const appended: ToolResultConfig[] = [];
|
|
15
|
-
const threadHandler = async (config: ToolResultConfig) => {
|
|
15
|
+
const threadHandler = async (_id: string, config: ToolResultConfig) => {
|
|
16
16
|
appended.push(config);
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
const innerHandler = async (
|
|
20
20
|
args: { text: string },
|
|
21
|
-
_ctx: RouterContext
|
|
21
|
+
_ctx: RouterContext
|
|
22
22
|
): Promise<ToolHandlerResponse<{ echoed: string }>> => ({
|
|
23
23
|
toolResponse: `Echo: ${args.text}`,
|
|
24
24
|
data: { echoed: args.text },
|
|
@@ -32,7 +32,7 @@ describe("withAutoAppend", () => {
|
|
|
32
32
|
threadId: "thread-1",
|
|
33
33
|
toolCallId: "tc-1",
|
|
34
34
|
toolName: "Echo",
|
|
35
|
-
}
|
|
35
|
+
}
|
|
36
36
|
);
|
|
37
37
|
|
|
38
38
|
expect(result.resultAppended).toBe(true);
|
|
@@ -62,7 +62,7 @@ describe("withAutoAppend", () => {
|
|
|
62
62
|
|
|
63
63
|
const result = await wrapped(
|
|
64
64
|
{},
|
|
65
|
-
{ threadId: "t", toolCallId: "tc", toolName: "BigTool" }
|
|
65
|
+
{ threadId: "t", toolCallId: "tc", toolName: "BigTool" }
|
|
66
66
|
);
|
|
67
67
|
|
|
68
68
|
expect(result.toolResponse).toBe("Response appended via withAutoAppend");
|
|
@@ -81,10 +81,7 @@ describe("withAutoAppend", () => {
|
|
|
81
81
|
const wrapped = withAutoAppend(threadHandler, innerHandler);
|
|
82
82
|
|
|
83
83
|
await expect(
|
|
84
|
-
wrapped(
|
|
85
|
-
{},
|
|
86
|
-
{ threadId: "t", toolCallId: "tc", toolName: "Fail" },
|
|
87
|
-
),
|
|
84
|
+
wrapped({}, { threadId: "t", toolCallId: "tc", toolName: "Fail" })
|
|
88
85
|
).rejects.toThrow("handler failed");
|
|
89
86
|
|
|
90
87
|
expect(appendSpy).not.toHaveBeenCalled();
|
|
@@ -92,7 +89,7 @@ describe("withAutoAppend", () => {
|
|
|
92
89
|
|
|
93
90
|
it("uses correct context fields for thread handler config", async () => {
|
|
94
91
|
let capturedConfig: ToolResultConfig | null = null;
|
|
95
|
-
const threadHandler = async (config: ToolResultConfig) => {
|
|
92
|
+
const threadHandler = async (_id: string, config: ToolResultConfig) => {
|
|
96
93
|
capturedConfig = config;
|
|
97
94
|
};
|
|
98
95
|
|
|
@@ -110,7 +107,7 @@ describe("withAutoAppend", () => {
|
|
|
110
107
|
toolCallId: "my-tc",
|
|
111
108
|
toolName: "MyTool",
|
|
112
109
|
sandboxId: "sb-1",
|
|
113
|
-
}
|
|
110
|
+
}
|
|
114
111
|
);
|
|
115
112
|
|
|
116
113
|
expect(capturedConfig).toEqual({
|
|
@@ -177,7 +174,7 @@ describe("withSandbox", () => {
|
|
|
177
174
|
|
|
178
175
|
const handler = async (
|
|
179
176
|
_args: { text: string },
|
|
180
|
-
ctx: RouterContext & { sandbox: Sandbox; sandboxId: string }
|
|
177
|
+
ctx: RouterContext & { sandbox: Sandbox; sandboxId: string }
|
|
181
178
|
): Promise<ToolHandlerResponse<null>> => {
|
|
182
179
|
capturedSandbox = ctx.sandbox;
|
|
183
180
|
capturedSandboxId = ctx.sandboxId;
|
|
@@ -193,7 +190,7 @@ describe("withSandbox", () => {
|
|
|
193
190
|
toolCallId: "tc-1",
|
|
194
191
|
toolName: "Test",
|
|
195
192
|
sandboxId: "sb-42",
|
|
196
|
-
}
|
|
193
|
+
}
|
|
197
194
|
);
|
|
198
195
|
|
|
199
196
|
expect(result.toolResponse).toBe("ok");
|
|
@@ -219,7 +216,7 @@ describe("withSandbox", () => {
|
|
|
219
216
|
threadId: "thread-1",
|
|
220
217
|
toolCallId: "tc-1",
|
|
221
218
|
toolName: "Bash",
|
|
222
|
-
}
|
|
219
|
+
}
|
|
223
220
|
);
|
|
224
221
|
|
|
225
222
|
expect(result.toolResponse).toContain("No sandbox configured");
|
|
@@ -247,7 +244,7 @@ describe("withSandbox", () => {
|
|
|
247
244
|
toolCallId: "tc",
|
|
248
245
|
toolName: "Grep",
|
|
249
246
|
sandboxId: undefined,
|
|
250
|
-
}
|
|
247
|
+
}
|
|
251
248
|
);
|
|
252
249
|
|
|
253
250
|
expect(result.toolResponse).toContain("No sandbox configured");
|
|
@@ -276,8 +273,8 @@ describe("withSandbox", () => {
|
|
|
276
273
|
toolCallId: "tc",
|
|
277
274
|
toolName: "Test",
|
|
278
275
|
sandboxId: "sb-missing",
|
|
279
|
-
}
|
|
280
|
-
)
|
|
276
|
+
}
|
|
277
|
+
)
|
|
281
278
|
).rejects.toThrow("sandbox not found");
|
|
282
279
|
});
|
|
283
280
|
|
|
@@ -285,11 +282,13 @@ describe("withSandbox", () => {
|
|
|
285
282
|
const mockSandbox = createMockSandbox();
|
|
286
283
|
const manager = { getSandbox: async () => mockSandbox };
|
|
287
284
|
|
|
288
|
-
let capturedCtx:
|
|
285
|
+
let capturedCtx:
|
|
286
|
+
| (RouterContext & { sandbox: Sandbox; sandboxId: string })
|
|
287
|
+
| null = null;
|
|
289
288
|
|
|
290
289
|
const handler = async (
|
|
291
290
|
_args: unknown,
|
|
292
|
-
ctx: RouterContext & { sandbox: Sandbox; sandboxId: string }
|
|
291
|
+
ctx: RouterContext & { sandbox: Sandbox; sandboxId: string }
|
|
293
292
|
): Promise<ToolHandlerResponse<null>> => {
|
|
294
293
|
capturedCtx = ctx;
|
|
295
294
|
return { toolResponse: "ok", data: null };
|
|
@@ -304,7 +303,7 @@ describe("withSandbox", () => {
|
|
|
304
303
|
toolCallId: "my-tc",
|
|
305
304
|
toolName: "MyTool",
|
|
306
305
|
sandboxId: "my-sandbox",
|
|
307
|
-
}
|
|
306
|
+
}
|
|
308
307
|
);
|
|
309
308
|
|
|
310
309
|
expect(capturedCtx).toEqual(
|
|
@@ -314,7 +313,7 @@ describe("withSandbox", () => {
|
|
|
314
313
|
toolName: "MyTool",
|
|
315
314
|
sandboxId: "my-sandbox",
|
|
316
315
|
sandbox: mockSandbox,
|
|
317
|
-
})
|
|
316
|
+
})
|
|
318
317
|
);
|
|
319
318
|
});
|
|
320
319
|
|
|
@@ -335,7 +334,7 @@ describe("withSandbox", () => {
|
|
|
335
334
|
toolCallId: "tc",
|
|
336
335
|
toolName: "Test",
|
|
337
336
|
sandboxId: "",
|
|
338
|
-
}
|
|
337
|
+
}
|
|
339
338
|
);
|
|
340
339
|
|
|
341
340
|
expect(result.toolResponse).toContain("No sandbox configured");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ToolResultConfig } from "../types";
|
|
2
2
|
import type { ActivityToolHandler, RouterContext } from "./types";
|
|
3
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Wraps a tool handler to automatically append its result directly to the
|
|
@@ -33,13 +34,13 @@ export function withAutoAppend<
|
|
|
33
34
|
TResult,
|
|
34
35
|
TContext extends RouterContext = RouterContext,
|
|
35
36
|
>(
|
|
36
|
-
threadHandler: (config: ToolResultConfig) => Promise<void>,
|
|
37
|
+
threadHandler: (id: string, config: ToolResultConfig) => Promise<void>,
|
|
37
38
|
handler: ActivityToolHandler<TArgs, TResult, TContext>
|
|
38
39
|
): ActivityToolHandler<TArgs, TResult, TContext> {
|
|
39
40
|
return async (args: TArgs, context: TContext) => {
|
|
40
41
|
const response = await handler(args, context);
|
|
41
42
|
|
|
42
|
-
await threadHandler({
|
|
43
|
+
await threadHandler(uuidv4(), {
|
|
43
44
|
threadId: context.threadId,
|
|
44
45
|
toolCallId: context.toolCallId,
|
|
45
46
|
toolName: context.toolName,
|
|
@@ -15,10 +15,7 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
15
15
|
err.nonRetryable = nonRetryable;
|
|
16
16
|
return err;
|
|
17
17
|
}
|
|
18
|
-
static fromError(
|
|
19
|
-
error: unknown,
|
|
20
|
-
options?: { nonRetryable?: boolean },
|
|
21
|
-
) {
|
|
18
|
+
static fromError(error: unknown, options?: { nonRetryable?: boolean }) {
|
|
22
19
|
const src = error instanceof Error ? error : new Error(String(error));
|
|
23
20
|
const err = new MockApplicationFailure(src.message);
|
|
24
21
|
err.nonRetryable = options?.nonRetryable;
|
|
@@ -29,18 +26,25 @@ vi.mock("@temporalio/workflow", () => {
|
|
|
29
26
|
});
|
|
30
27
|
|
|
31
28
|
import { createToolRouter, defineTool, hasNoOtherToolCalls } from "./router";
|
|
32
|
-
import type {
|
|
33
|
-
ToolMap,
|
|
34
|
-
ToolHandlerResponse,
|
|
35
|
-
AppendToolResultFn,
|
|
36
|
-
} from "./types";
|
|
29
|
+
import type { ToolMap, ToolHandlerResponse, AppendToolResultFn } from "./types";
|
|
37
30
|
import type { ToolResultConfig } from "../types";
|
|
38
31
|
|
|
39
32
|
function createAppendSpy() {
|
|
40
33
|
const calls: ToolResultConfig[] = [];
|
|
41
|
-
const fn
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
const fn = Object.assign(
|
|
35
|
+
async (_id: string, config: ToolResultConfig) => {
|
|
36
|
+
calls.push(config);
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
executeWithOptions: (
|
|
40
|
+
_opts: unknown,
|
|
41
|
+
[, config]: [string, ToolResultConfig]
|
|
42
|
+
) => {
|
|
43
|
+
calls.push(config);
|
|
44
|
+
return Promise.resolve();
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
) as AppendToolResultFn;
|
|
44
48
|
return { fn, calls };
|
|
45
49
|
}
|
|
46
50
|
|
|
@@ -146,7 +150,11 @@ describe("createToolRouter edge cases", () => {
|
|
|
146
150
|
},
|
|
147
151
|
});
|
|
148
152
|
|
|
149
|
-
const parsed = router.parseToolCall({
|
|
153
|
+
const parsed = router.parseToolCall({
|
|
154
|
+
id: "tc-1",
|
|
155
|
+
name: "Hooked",
|
|
156
|
+
args: {},
|
|
157
|
+
});
|
|
150
158
|
await router.processToolCalls([parsed], { turn: 1 });
|
|
151
159
|
|
|
152
160
|
expect(order).toEqual(["global-pre", "tool-pre", "handler"]);
|
|
@@ -185,7 +193,11 @@ describe("createToolRouter edge cases", () => {
|
|
|
185
193
|
},
|
|
186
194
|
});
|
|
187
195
|
|
|
188
|
-
const parsed = router.parseToolCall({
|
|
196
|
+
const parsed = router.parseToolCall({
|
|
197
|
+
id: "tc-1",
|
|
198
|
+
name: "Hooked",
|
|
199
|
+
args: {},
|
|
200
|
+
});
|
|
189
201
|
await router.processToolCalls([parsed], { turn: 1 });
|
|
190
202
|
|
|
191
203
|
expect(order).toEqual(["global-pre-skip"]);
|
|
@@ -219,7 +231,11 @@ describe("createToolRouter edge cases", () => {
|
|
|
219
231
|
},
|
|
220
232
|
});
|
|
221
233
|
|
|
222
|
-
const parsed = router.parseToolCall({
|
|
234
|
+
const parsed = router.parseToolCall({
|
|
235
|
+
id: "tc-1",
|
|
236
|
+
name: "Hooked",
|
|
237
|
+
args: {},
|
|
238
|
+
});
|
|
223
239
|
await router.processToolCalls([parsed], { turn: 1 });
|
|
224
240
|
|
|
225
241
|
expect(order).toEqual(["tool-post", "global-post"]);
|
|
@@ -259,7 +275,10 @@ describe("createToolRouter edge cases", () => {
|
|
|
259
275
|
const results = await router.processToolCalls([parsed], { turn: 1 });
|
|
260
276
|
|
|
261
277
|
expect(at(appendSpy.calls, 0).content).toBe("tool-level recovery");
|
|
262
|
-
expect(at(results, 0).data).toEqual({
|
|
278
|
+
expect(at(results, 0).data).toEqual({
|
|
279
|
+
error: "Error: boom",
|
|
280
|
+
recovered: true,
|
|
281
|
+
});
|
|
263
282
|
expect(globalHookSpy).not.toHaveBeenCalled();
|
|
264
283
|
});
|
|
265
284
|
|
|
@@ -365,7 +384,11 @@ describe("createToolRouter edge cases", () => {
|
|
|
365
384
|
plugins: [pluginTool],
|
|
366
385
|
});
|
|
367
386
|
|
|
368
|
-
const parsed = router.parseToolCall({
|
|
387
|
+
const parsed = router.parseToolCall({
|
|
388
|
+
id: "tc-1",
|
|
389
|
+
name: "MyTool",
|
|
390
|
+
args: {},
|
|
391
|
+
});
|
|
369
392
|
const results = await router.processToolCalls([parsed]);
|
|
370
393
|
|
|
371
394
|
expect(at(results, 0).data).toEqual({ source: "plugin" });
|
|
@@ -393,7 +416,7 @@ describe("createToolRouter edge cases", () => {
|
|
|
393
416
|
const results = await router.processToolCallsByName(
|
|
394
417
|
[],
|
|
395
418
|
"Echo",
|
|
396
|
-
async () => ({ toolResponse: "ok", data: null })
|
|
419
|
+
async () => ({ toolResponse: "ok", data: null })
|
|
397
420
|
);
|
|
398
421
|
|
|
399
422
|
expect(results).toEqual([]);
|
|
@@ -421,7 +444,11 @@ describe("createToolRouter edge cases", () => {
|
|
|
421
444
|
appendToolResult: appendSpy.fn,
|
|
422
445
|
});
|
|
423
446
|
|
|
424
|
-
const parsed = router.parseToolCall({
|
|
447
|
+
const parsed = router.parseToolCall({
|
|
448
|
+
id: "tc-1",
|
|
449
|
+
name: "Complex",
|
|
450
|
+
args: {},
|
|
451
|
+
});
|
|
425
452
|
await router.processToolCalls([parsed]);
|
|
426
453
|
|
|
427
454
|
const appended = at(appendSpy.calls, 0);
|
|
@@ -435,7 +462,9 @@ describe("createToolRouter edge cases", () => {
|
|
|
435
462
|
name: "Sync" as const,
|
|
436
463
|
description: "sync handler",
|
|
437
464
|
schema: z.object({ n: z.number() }),
|
|
438
|
-
handler: (args: {
|
|
465
|
+
handler: (args: {
|
|
466
|
+
n: number;
|
|
467
|
+
}): ToolHandlerResponse<{ doubled: number }> => ({
|
|
439
468
|
toolResponse: `${args.n * 2}`,
|
|
440
469
|
data: { doubled: args.n * 2 },
|
|
441
470
|
}),
|
|
@@ -447,7 +476,11 @@ describe("createToolRouter edge cases", () => {
|
|
|
447
476
|
appendToolResult: appendSpy.fn,
|
|
448
477
|
});
|
|
449
478
|
|
|
450
|
-
const parsed = router.parseToolCall({
|
|
479
|
+
const parsed = router.parseToolCall({
|
|
480
|
+
id: "tc-1",
|
|
481
|
+
name: "Sync",
|
|
482
|
+
args: { n: 5 },
|
|
483
|
+
});
|
|
451
484
|
const results = await router.processToolCalls([parsed]);
|
|
452
485
|
|
|
453
486
|
expect(at(results, 0).data).toEqual({ doubled: 10 });
|
|
@@ -504,7 +537,11 @@ describe("createToolRouter edge cases", () => {
|
|
|
504
537
|
appendToolResult: appendSpy.fn,
|
|
505
538
|
});
|
|
506
539
|
|
|
507
|
-
const parsed = router.parseToolCall({
|
|
540
|
+
const parsed = router.parseToolCall({
|
|
541
|
+
id: "tc-1",
|
|
542
|
+
name: "Suppress",
|
|
543
|
+
args: {},
|
|
544
|
+
});
|
|
508
545
|
const results = await router.processToolCalls([parsed], { turn: 1 });
|
|
509
546
|
|
|
510
547
|
expect(at(results, 0).data).toEqual({
|
|
@@ -590,7 +627,11 @@ describe("createToolRouter edge cases", () => {
|
|
|
590
627
|
},
|
|
591
628
|
});
|
|
592
629
|
|
|
593
|
-
const parsed = router.parseToolCall({
|
|
630
|
+
const parsed = router.parseToolCall({
|
|
631
|
+
id: "tc-1",
|
|
632
|
+
name: "ThrowString",
|
|
633
|
+
args: {},
|
|
634
|
+
});
|
|
594
635
|
const results = await router.processToolCalls([parsed], { turn: 1 });
|
|
595
636
|
|
|
596
637
|
expect(at(results, 0).data).toEqual({
|