@alexkroman1/aai 1.7.1 → 1.8.0
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/.turbo/turbo-build.log +11 -9
- package/CHANGELOG.md +10 -0
- package/dist/{_internal-types-CrnTi9Ew.js → _internal-types-CfOAbK6V.js} +22 -35
- package/dist/constants-y68COEGj.js +29 -0
- package/dist/host/_base64.d.ts +2 -0
- package/dist/host/_mock-ws.d.ts +0 -61
- package/dist/host/_pipeline-test-fakes.d.ts +7 -4
- package/dist/host/_run-code.d.ts +0 -25
- package/dist/host/_runtime-conformance.d.ts +3 -34
- package/dist/host/memory-vector.d.ts +0 -11
- package/dist/host/providers/resolve-kv.d.ts +0 -7
- package/dist/host/providers/resolve-vector.d.ts +0 -8
- package/dist/host/providers/stt/assemblyai.d.ts +0 -14
- package/dist/host/providers/stt/deepgram.d.ts +2 -14
- package/dist/host/providers/stt/soniox.d.ts +0 -22
- package/dist/host/providers/tts/rime.d.ts +10 -31
- package/dist/host/runtime-barrel.js +619 -630
- package/dist/host/runtime-config.d.ts +9 -6
- package/dist/host/runtime.d.ts +3 -0
- package/dist/host/to-vercel-tools.d.ts +3 -33
- package/dist/host/transports/openai-realtime-transport.d.ts +43 -0
- package/dist/host/unstorage-kv.d.ts +0 -26
- package/dist/index.js +3 -3
- package/dist/openai-realtime-cjPAHMMx.js +10 -0
- package/dist/sdk/_internal-types.d.ts +6 -55
- package/dist/sdk/allowed-hosts.d.ts +4 -3
- package/dist/sdk/constants.d.ts +4 -29
- package/dist/sdk/define.d.ts +7 -4
- package/dist/sdk/kv.d.ts +13 -37
- package/dist/sdk/manifest-barrel.js +1 -1
- package/dist/sdk/manifest.d.ts +8 -2
- package/dist/sdk/protocol.js +1 -1
- package/dist/sdk/providers/s2s/openai-realtime.d.ts +17 -0
- package/dist/sdk/providers/s2s-barrel.d.ts +9 -0
- package/dist/sdk/providers/s2s-barrel.js +2 -0
- package/dist/sdk/providers/tts/rime.d.ts +1 -1
- package/dist/sdk/providers.d.ts +6 -2
- package/dist/sdk/types.d.ts +7 -1
- package/dist/{types-KUgezM6u.js → types-DOWVZhb9.js} +1 -7
- package/dist/{ws-upgrade-BeOQ7fXL.js → ws-upgrade-CG8-by1n.js} +2 -3
- package/host/_base64.ts +9 -0
- package/host/_mock-ws.ts +0 -65
- package/host/_pipeline-test-fakes.ts +19 -31
- package/host/_run-code.ts +10 -53
- package/host/_runtime-conformance.ts +3 -44
- package/host/_test-utils.ts +20 -42
- package/host/builtin-tools.test.ts +127 -222
- package/host/builtin-tools.ts +6 -10
- package/host/cleanup.test.ts +30 -73
- package/host/integration/pipeline-reference.integration.test.ts +12 -17
- package/host/integration.test.ts +0 -7
- package/host/memory-vector.test.ts +3 -1
- package/host/memory-vector.ts +16 -21
- package/host/pinecone-vector.test.ts +14 -17
- package/host/pinecone-vector.ts +10 -19
- package/host/providers/providers.test-d.ts +5 -3
- package/host/providers/resolve-kv.ts +23 -41
- package/host/providers/resolve-vector.ts +3 -12
- package/host/providers/resolve.test.ts +15 -28
- package/host/providers/resolve.ts +24 -24
- package/host/providers/stt/assemblyai.test.ts +2 -14
- package/host/providers/stt/assemblyai.ts +12 -35
- package/host/providers/stt/deepgram.test.ts +23 -83
- package/host/providers/stt/deepgram.ts +15 -40
- package/host/providers/stt/elevenlabs.test.ts +26 -38
- package/host/providers/stt/elevenlabs.ts +10 -9
- package/host/providers/stt/soniox.test.ts +35 -85
- package/host/providers/stt/soniox.ts +8 -53
- package/host/providers/tts/cartesia.test.ts +19 -58
- package/host/providers/tts/cartesia.ts +36 -66
- package/host/providers/tts/rime.test.ts +12 -38
- package/host/providers/tts/rime.ts +23 -86
- package/host/runtime-config.test.ts +9 -9
- package/host/runtime-config.ts +16 -22
- package/host/runtime.test.ts +111 -73
- package/host/runtime.ts +138 -86
- package/host/s2s.test.ts +92 -191
- package/host/s2s.ts +55 -49
- package/host/server-shutdown.test.ts +9 -30
- package/host/server.test.ts +2 -13
- package/host/server.ts +85 -100
- package/host/session-core.test.ts +15 -30
- package/host/session-core.ts +10 -13
- package/host/session-prompt.test.ts +1 -5
- package/host/to-vercel-tools.test.ts +53 -72
- package/host/to-vercel-tools.ts +9 -39
- package/host/tool-executor.test.ts +25 -51
- package/host/tool-executor.ts +18 -12
- package/host/transports/openai-realtime-transport.test.ts +371 -0
- package/host/transports/openai-realtime-transport.ts +319 -0
- package/host/transports/pipeline-transport.test.ts +125 -298
- package/host/transports/pipeline-transport.ts +20 -68
- package/host/transports/s2s-transport-fixtures.test.ts +31 -92
- package/host/transports/s2s-transport.test.ts +65 -134
- package/host/transports/s2s-transport.ts +15 -43
- package/host/transports/types.test.ts +4 -8
- package/host/unstorage-kv.test.ts +3 -2
- package/host/unstorage-kv.ts +5 -35
- package/host/ws-handler.test.ts +72 -176
- package/host/ws-handler.ts +6 -12
- package/package.json +6 -1
- package/sdk/__snapshots__/exports.test.ts.snap +7 -0
- package/sdk/__snapshots__/schema-shapes.test.ts.snap +1 -0
- package/sdk/_internal-types.test.ts +6 -9
- package/sdk/_internal-types.ts +16 -57
- package/sdk/_test-matchers.ts +25 -15
- package/sdk/allowed-hosts.test.ts +50 -114
- package/sdk/allowed-hosts.ts +8 -14
- package/sdk/constants.ts +5 -52
- package/sdk/define.test.ts +7 -6
- package/sdk/define.ts +7 -3
- package/sdk/exports.test.ts +6 -1
- package/sdk/kv.ts +13 -37
- package/sdk/manifest.test-d.ts +5 -0
- package/sdk/manifest.test.ts +61 -9
- package/sdk/manifest.ts +11 -11
- package/sdk/protocol-compat.test.ts +66 -98
- package/sdk/protocol-snapshot.test.ts +2 -16
- package/sdk/protocol.test.ts +13 -22
- package/sdk/providers/s2s/openai-realtime.ts +36 -0
- package/sdk/providers/s2s-barrel.ts +12 -0
- package/sdk/providers/tts/rime.ts +1 -1
- package/sdk/providers.ts +24 -5
- package/sdk/schema-alignment.test.ts +25 -73
- package/sdk/schema-shapes.test.ts +1 -29
- package/sdk/system-prompt.test.ts +0 -1
- package/sdk/system-prompt.ts +17 -19
- package/sdk/types-inference.test.ts +10 -36
- package/sdk/types.ts +7 -0
- package/sdk/ws-upgrade.test.ts +24 -23
- package/sdk/ws-upgrade.ts +2 -3
- package/tsdown.config.ts +8 -11
- package/dist/constants-C2nirZUI.js +0 -54
package/host/session-core.ts
CHANGED
|
@@ -58,12 +58,14 @@ export type SessionCore = {
|
|
|
58
58
|
export function createSessionCore(opts: SessionCoreOptions): SessionCore {
|
|
59
59
|
const log = opts.logger ?? consoleLogger;
|
|
60
60
|
const maxHistory = opts.maxHistory ?? DEFAULT_MAX_HISTORY;
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
return raw === 0 || !Number.isFinite(raw) ? 0 : raw;
|
|
64
|
-
})();
|
|
61
|
+
const rawIdleMs = opts.agentConfig.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
62
|
+
const idleMs = rawIdleMs === 0 || !Number.isFinite(rawIdleMs) ? 0 : rawIdleMs;
|
|
65
63
|
|
|
66
|
-
|
|
64
|
+
function emptyReply(): ReplyState {
|
|
65
|
+
return { currentReplyId: null, pendingTools: [], toolCallCount: 0 };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let reply: ReplyState = emptyReply();
|
|
67
69
|
let history: Message[] = [];
|
|
68
70
|
let turnPromise: Promise<void> | null = null;
|
|
69
71
|
let idleTimer: NodeJS.Timeout | null = null;
|
|
@@ -90,12 +92,12 @@ export function createSessionCore(opts: SessionCoreOptions): SessionCore {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
function beginReply(replyId: string): void {
|
|
93
|
-
reply = { currentReplyId: replyId
|
|
95
|
+
reply = { ...emptyReply(), currentReplyId: replyId };
|
|
94
96
|
turnPromise = null;
|
|
95
97
|
}
|
|
96
98
|
|
|
97
99
|
function cancelReply(): void {
|
|
98
|
-
reply =
|
|
100
|
+
reply = emptyReply();
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
function flushReply(startMs: number, hadTurnPromise: boolean): void {
|
|
@@ -209,12 +211,7 @@ export function createSessionCore(opts: SessionCoreOptions): SessionCore {
|
|
|
209
211
|
},
|
|
210
212
|
|
|
211
213
|
onToolCall(callId, name, args) {
|
|
212
|
-
emit({
|
|
213
|
-
type: "tool_call",
|
|
214
|
-
toolCallId: callId,
|
|
215
|
-
toolName: name,
|
|
216
|
-
args: args as Record<string, unknown>,
|
|
217
|
-
});
|
|
214
|
+
emit({ type: "tool_call", toolCallId: callId, toolName: name, args });
|
|
218
215
|
if (reply.currentReplyId === null) {
|
|
219
216
|
log.warn("tool_call with no active reply", { sid: opts.id, name });
|
|
220
217
|
return;
|
|
@@ -4,13 +4,9 @@ import { DEFAULT_SYSTEM_PROMPT } from "../sdk/types.ts";
|
|
|
4
4
|
import { makeConfig } from "./_test-utils.ts";
|
|
5
5
|
|
|
6
6
|
describe("buildSystemPrompt", () => {
|
|
7
|
-
test("
|
|
7
|
+
test("uses DEFAULT_SYSTEM_PROMPT with no agent-specific section when no custom instructions", () => {
|
|
8
8
|
const result = buildSystemPrompt(makeConfig(), { hasTools: false });
|
|
9
9
|
expect(result.startsWith(DEFAULT_SYSTEM_PROMPT)).toBe(true);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test("does not include agent-specific instructions section for default instructions", () => {
|
|
13
|
-
const result = buildSystemPrompt(makeConfig(), { hasTools: false });
|
|
14
10
|
expect(result).not.toContain("Agent-Specific Instructions:");
|
|
15
11
|
});
|
|
16
12
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright 2025 the AAI authors. MIT license.
|
|
2
|
+
import type { Tool, ToolExecutionOptions } from "ai";
|
|
2
3
|
import { describe, expect, test, vi } from "vitest";
|
|
3
|
-
import type { ExecuteTool, ToolSchema } from "../sdk/_internal-types.ts";
|
|
4
|
+
import type { ExecuteTool, ExecuteToolOptions, ToolSchema } from "../sdk/_internal-types.ts";
|
|
4
5
|
import type { Message } from "../sdk/types.ts";
|
|
5
6
|
import { toVercelTools } from "./to-vercel-tools.ts";
|
|
6
7
|
|
|
@@ -17,6 +18,15 @@ const schemas: ToolSchema[] = [
|
|
|
17
18
|
},
|
|
18
19
|
];
|
|
19
20
|
|
|
21
|
+
function runTool(
|
|
22
|
+
tool: Tool | undefined,
|
|
23
|
+
args: Readonly<Record<string, unknown>>,
|
|
24
|
+
options: ToolExecutionOptions,
|
|
25
|
+
): Promise<unknown> {
|
|
26
|
+
if (!tool?.execute) throw new Error("tool.execute missing");
|
|
27
|
+
return tool.execute(args, options);
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
describe("toVercelTools", () => {
|
|
21
31
|
test("produces one Vercel AI SDK tool per schema, keyed by name", () => {
|
|
22
32
|
const executeTool = vi.fn(async () => "sunny");
|
|
@@ -26,9 +36,7 @@ describe("toVercelTools", () => {
|
|
|
26
36
|
messages: () => [],
|
|
27
37
|
});
|
|
28
38
|
expect(Object.keys(tools)).toEqual(["get_weather"]);
|
|
29
|
-
expect(tools.get_weather).toMatchObject({
|
|
30
|
-
description: "Look up the weather.",
|
|
31
|
-
});
|
|
39
|
+
expect(tools.get_weather).toMatchObject({ description: "Look up the weather." });
|
|
32
40
|
});
|
|
33
41
|
|
|
34
42
|
test("execute delegates to ctx.executeTool with (name, args, sessionId, messages)", async () => {
|
|
@@ -38,9 +46,13 @@ describe("toVercelTools", () => {
|
|
|
38
46
|
sessionId: "sess-42",
|
|
39
47
|
messages: () => [{ role: "user", content: "?" }],
|
|
40
48
|
});
|
|
41
|
-
const result = await
|
|
49
|
+
const result = await runTool(
|
|
50
|
+
tools.get_weather,
|
|
42
51
|
{ city: "SF" },
|
|
43
|
-
{
|
|
52
|
+
{
|
|
53
|
+
toolCallId: "tc-1",
|
|
54
|
+
messages: [],
|
|
55
|
+
},
|
|
44
56
|
);
|
|
45
57
|
expect(executeTool).toHaveBeenCalledWith(
|
|
46
58
|
"get_weather",
|
|
@@ -54,103 +66,77 @@ describe("toVercelTools", () => {
|
|
|
54
66
|
|
|
55
67
|
test("execute passes through abort signal when provided", async () => {
|
|
56
68
|
const controller = new AbortController();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
_m?: readonly unknown[],
|
|
63
|
-
opts?: { signal?: AbortSignal },
|
|
64
|
-
) => {
|
|
65
|
-
expect(opts?.signal).toBe(controller.signal);
|
|
66
|
-
return "ok";
|
|
67
|
-
},
|
|
68
|
-
);
|
|
69
|
+
let received: ExecuteToolOptions | undefined;
|
|
70
|
+
const executeTool: ExecuteTool = async (_n, _a, _s, _m, opts) => {
|
|
71
|
+
received = opts;
|
|
72
|
+
return "ok";
|
|
73
|
+
};
|
|
69
74
|
const tools = toVercelTools(schemas, {
|
|
70
75
|
executeTool,
|
|
71
76
|
sessionId: "s",
|
|
72
77
|
messages: () => [],
|
|
73
78
|
signal: controller.signal,
|
|
74
79
|
});
|
|
75
|
-
await tools.get_weather
|
|
76
|
-
expect(
|
|
80
|
+
await runTool(tools.get_weather, { city: "NY" }, { toolCallId: "tc-2", messages: [] });
|
|
81
|
+
expect(received?.signal).toBe(controller.signal);
|
|
77
82
|
});
|
|
78
83
|
|
|
79
84
|
test("execute prefers options.abortSignal over ctx.signal", async () => {
|
|
80
85
|
const ctxController = new AbortController();
|
|
81
86
|
const callController = new AbortController();
|
|
82
|
-
let
|
|
83
|
-
const executeTool =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
_s?: string,
|
|
88
|
-
_m?: readonly unknown[],
|
|
89
|
-
opts?: { signal?: AbortSignal },
|
|
90
|
-
) => {
|
|
91
|
-
receivedSignal = opts?.signal;
|
|
92
|
-
return "ok";
|
|
93
|
-
},
|
|
94
|
-
);
|
|
87
|
+
let received: ExecuteToolOptions | undefined;
|
|
88
|
+
const executeTool: ExecuteTool = async (_n, _a, _s, _m, opts) => {
|
|
89
|
+
received = opts;
|
|
90
|
+
return "ok";
|
|
91
|
+
};
|
|
95
92
|
const tools = toVercelTools(schemas, {
|
|
96
93
|
executeTool,
|
|
97
94
|
sessionId: "s",
|
|
98
95
|
messages: () => [],
|
|
99
96
|
signal: ctxController.signal,
|
|
100
97
|
});
|
|
101
|
-
await
|
|
98
|
+
await runTool(
|
|
99
|
+
tools.get_weather,
|
|
102
100
|
{ city: "NY" },
|
|
103
|
-
{
|
|
101
|
+
{
|
|
102
|
+
toolCallId: "tc-1",
|
|
103
|
+
messages: [],
|
|
104
|
+
abortSignal: callController.signal,
|
|
105
|
+
},
|
|
104
106
|
);
|
|
105
|
-
expect(
|
|
107
|
+
expect(received?.signal).toBe(callController.signal);
|
|
106
108
|
});
|
|
107
109
|
|
|
108
110
|
test("execute falls back to ctx.signal when options.abortSignal is absent", async () => {
|
|
109
111
|
const ctxController = new AbortController();
|
|
110
|
-
let
|
|
111
|
-
const executeTool =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
_s?: string,
|
|
116
|
-
_m?: readonly unknown[],
|
|
117
|
-
opts?: { signal?: AbortSignal },
|
|
118
|
-
) => {
|
|
119
|
-
receivedSignal = opts?.signal;
|
|
120
|
-
return "ok";
|
|
121
|
-
},
|
|
122
|
-
);
|
|
112
|
+
let received: ExecuteToolOptions | undefined;
|
|
113
|
+
const executeTool: ExecuteTool = async (_n, _a, _s, _m, opts) => {
|
|
114
|
+
received = opts;
|
|
115
|
+
return "ok";
|
|
116
|
+
};
|
|
123
117
|
const tools = toVercelTools(schemas, {
|
|
124
118
|
executeTool,
|
|
125
119
|
sessionId: "s",
|
|
126
120
|
messages: () => [],
|
|
127
121
|
signal: ctxController.signal,
|
|
128
122
|
});
|
|
129
|
-
await tools.get_weather
|
|
130
|
-
expect(
|
|
123
|
+
await runTool(tools.get_weather, { city: "NY" }, { toolCallId: "tc-2", messages: [] });
|
|
124
|
+
expect(received?.signal).toBe(ctxController.signal);
|
|
131
125
|
});
|
|
132
126
|
|
|
133
127
|
test("execute propagates toolCallId from options", async () => {
|
|
134
|
-
let
|
|
135
|
-
const executeTool =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
_s?: string,
|
|
140
|
-
_m?: readonly unknown[],
|
|
141
|
-
opts?: { toolCallId?: string },
|
|
142
|
-
) => {
|
|
143
|
-
receivedCallId = opts?.toolCallId;
|
|
144
|
-
return "ok";
|
|
145
|
-
},
|
|
146
|
-
);
|
|
128
|
+
let received: ExecuteToolOptions | undefined;
|
|
129
|
+
const executeTool: ExecuteTool = async (_n, _a, _s, _m, opts) => {
|
|
130
|
+
received = opts;
|
|
131
|
+
return "ok";
|
|
132
|
+
};
|
|
147
133
|
const tools = toVercelTools(schemas, {
|
|
148
134
|
executeTool,
|
|
149
135
|
sessionId: "s",
|
|
150
136
|
messages: () => [],
|
|
151
137
|
});
|
|
152
|
-
await tools.get_weather
|
|
153
|
-
expect(
|
|
138
|
+
await runTool(tools.get_weather, { city: "NY" }, { toolCallId: "tc-3", messages: [] });
|
|
139
|
+
expect(received?.toolCallId).toBe("tc-3");
|
|
154
140
|
});
|
|
155
141
|
});
|
|
156
142
|
|
|
@@ -161,7 +147,6 @@ describe("toVercelTools — message snapshot isolation", () => {
|
|
|
161
147
|
|
|
162
148
|
const executeTool: ExecuteTool = async (_name, _args, _sid, msgs) => {
|
|
163
149
|
observedInsideExecute = msgs;
|
|
164
|
-
// Mutate the original array; the snapshot we captured must be unaffected.
|
|
165
150
|
messagesBox.messages.push({ role: "user", content: "second" });
|
|
166
151
|
return "ok";
|
|
167
152
|
};
|
|
@@ -182,13 +167,9 @@ describe("toVercelTools — message snapshot isolation", () => {
|
|
|
182
167
|
},
|
|
183
168
|
);
|
|
184
169
|
|
|
185
|
-
|
|
186
|
-
if (!t?.execute) throw new Error("tool.execute missing");
|
|
187
|
-
await t.execute({}, { toolCallId: "c1", messages: [] });
|
|
170
|
+
await runTool(tools.t, {}, { toolCallId: "c1", messages: [] });
|
|
188
171
|
|
|
189
|
-
// The caller-observable messages array has 2 entries after the push.
|
|
190
172
|
expect(messagesBox.messages).toHaveLength(2);
|
|
191
|
-
// But the snapshot the tool executed against was frozen at length 1.
|
|
192
173
|
expect(observedInsideExecute).toHaveLength(1);
|
|
193
174
|
expect(observedInsideExecute?.[0]).toMatchObject({ content: "first" });
|
|
194
175
|
});
|
package/host/to-vercel-tools.ts
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
1
|
// Copyright 2025 the AAI authors. MIT license.
|
|
2
2
|
/**
|
|
3
|
-
* Converts agent {@link ToolSchema}[] to Vercel AI SDK tools
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* The pipeline orchestrator passes the output to `streamText({ tools })`.
|
|
7
|
-
* Each produced tool's `execute` closure calls
|
|
8
|
-
* `ctx.executeTool(name, args, sessionId, messages(), { signal, toolCallId })`,
|
|
9
|
-
* so the existing agent tool infrastructure (argument validation, KV, hooks,
|
|
10
|
-
* timeout) remains the single source of truth for tool behavior.
|
|
11
|
-
*
|
|
12
|
-
* Per-call `options.abortSignal` (forwarded by `streamText` when the
|
|
13
|
-
* outer turn is aborted, e.g. barge-in) takes precedence over the
|
|
14
|
-
* bag-level `ctx.signal` so individual invocations respect streamText
|
|
15
|
-
* aborts.
|
|
3
|
+
* Converts agent {@link ToolSchema}[] to Vercel AI SDK tools, delegating
|
|
4
|
+
* `execute` to the agent's {@link ExecuteTool} so validation, KV, hooks,
|
|
5
|
+
* and timeouts remain the single source of truth for tool behavior.
|
|
16
6
|
*/
|
|
17
7
|
|
|
18
8
|
import { jsonSchema, type Tool, type ToolExecutionOptions, tool } from "ai";
|
|
@@ -20,32 +10,12 @@ import type { ExecuteTool, ExecuteToolOptions, ToolSchema } from "../sdk/_intern
|
|
|
20
10
|
import type { Message } from "../sdk/types.ts";
|
|
21
11
|
|
|
22
12
|
export interface ToVercelToolsContext {
|
|
23
|
-
/** The agent's tool-execution function (from the runtime). */
|
|
24
13
|
executeTool: ExecuteTool;
|
|
25
|
-
/** Session id threaded to {@link executeTool}. */
|
|
26
14
|
sessionId: string;
|
|
27
|
-
/**
|
|
28
|
-
* Returns the current conversation history at call-time. The orchestrator
|
|
29
|
-
* calls this per invocation; `toVercelTools` snapshots the returned array
|
|
30
|
-
* before forwarding to `executeTool` so concurrent mutations cannot leak
|
|
31
|
-
* across tool calls.
|
|
32
|
-
*/
|
|
33
15
|
messages: () => readonly Message[];
|
|
34
|
-
/**
|
|
35
|
-
* Bag-level abort signal. Used as a fallback when the per-call
|
|
36
|
-
* `options.abortSignal` from Vercel's `ToolExecutionOptions` is absent.
|
|
37
|
-
*/
|
|
38
16
|
signal?: AbortSignal;
|
|
39
17
|
}
|
|
40
18
|
|
|
41
|
-
/**
|
|
42
|
-
* Convert an array of {@link ToolSchema} to a Vercel AI SDK `ToolSet`
|
|
43
|
-
* (record keyed by tool name).
|
|
44
|
-
*
|
|
45
|
-
* Uses the v6 `tool()` helper with `inputSchema: jsonSchema(...)` wrapping
|
|
46
|
-
* the agent's JSON Schema `parameters`. Execution is delegated to
|
|
47
|
-
* `ctx.executeTool` so validation, KV, timeouts, and hooks keep working.
|
|
48
|
-
*/
|
|
49
19
|
export function toVercelTools(
|
|
50
20
|
schemas: readonly ToolSchema[],
|
|
51
21
|
ctx: ToVercelToolsContext,
|
|
@@ -57,16 +27,16 @@ export function toVercelTools(
|
|
|
57
27
|
inputSchema: jsonSchema(schema.parameters),
|
|
58
28
|
execute: async (args: unknown, options: ToolExecutionOptions) => {
|
|
59
29
|
const input = (args ?? {}) as Readonly<Record<string, unknown>>;
|
|
60
|
-
//
|
|
61
|
-
//
|
|
30
|
+
// Per-call abortSignal from streamText takes precedence over bag-level
|
|
31
|
+
// ctx.signal so individual invocations respect outer-turn aborts.
|
|
62
32
|
const signal = options.abortSignal ?? ctx.signal;
|
|
63
33
|
const opts: ExecuteToolOptions = {};
|
|
64
34
|
if (signal !== undefined) opts.signal = signal;
|
|
65
35
|
if (options.toolCallId !== undefined) opts.toolCallId = options.toolCallId;
|
|
66
|
-
// Snapshot
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
return ctx.executeTool(schema.name, input, ctx.sessionId,
|
|
36
|
+
// Snapshot history so concurrent mutation from a newer turn can't
|
|
37
|
+
// leak into this tool's view.
|
|
38
|
+
const history = ctx.messages().slice();
|
|
39
|
+
return ctx.executeTool(schema.name, input, ctx.sessionId, history, opts);
|
|
70
40
|
},
|
|
71
41
|
});
|
|
72
42
|
}
|
|
@@ -46,31 +46,21 @@ describe("executeToolCall", () => {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
test("returns error when tool throws", async () => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
throw new Error("boom");
|
|
56
|
-
},
|
|
57
|
-
}),
|
|
58
|
-
),
|
|
59
|
-
).toBe(JSON.stringify({ error: "boom" }));
|
|
49
|
+
const tool = makeTool({
|
|
50
|
+
execute: () => {
|
|
51
|
+
throw new Error("boom");
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
expect(await run("fail", {}, tool)).toBe(JSON.stringify({ error: "boom" }));
|
|
60
55
|
});
|
|
61
56
|
|
|
62
57
|
test("returns error string when tool throws", async () => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
throw new Error("string error");
|
|
70
|
-
},
|
|
71
|
-
}),
|
|
72
|
-
),
|
|
73
|
-
).toBe(JSON.stringify({ error: "string error" }));
|
|
58
|
+
const tool = makeTool({
|
|
59
|
+
execute: () => {
|
|
60
|
+
throw new Error("string error");
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
expect(await run("fail", {}, tool)).toBe(JSON.stringify({ error: "string error" }));
|
|
74
64
|
});
|
|
75
65
|
|
|
76
66
|
test("passes env to tool context", async () => {
|
|
@@ -86,15 +76,11 @@ describe("executeToolCall", () => {
|
|
|
86
76
|
test("kv throws when not provided", async () => {
|
|
87
77
|
const tool = makeTool({
|
|
88
78
|
execute: (_args, ctx) => {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return "no error";
|
|
92
|
-
} catch (e) {
|
|
93
|
-
return (e as Error).message;
|
|
94
|
-
}
|
|
79
|
+
void ctx.kv;
|
|
80
|
+
return "no error";
|
|
95
81
|
},
|
|
96
82
|
});
|
|
97
|
-
expect(await run("test", {}, tool)).toBe("KV not available");
|
|
83
|
+
expect(await run("test", {}, tool)).toBe(JSON.stringify({ error: "KV not available" }));
|
|
98
84
|
});
|
|
99
85
|
|
|
100
86
|
test("handles async tool execution", async () => {
|
|
@@ -111,50 +97,38 @@ describe("executeToolCall", () => {
|
|
|
111
97
|
vi.useFakeTimers();
|
|
112
98
|
const tool = makeTool({
|
|
113
99
|
execute: () =>
|
|
114
|
-
new Promise(() => {
|
|
100
|
+
new Promise<never>(() => {
|
|
115
101
|
/* never resolves */
|
|
116
102
|
}),
|
|
117
103
|
});
|
|
118
104
|
const promise = run("slow", {}, tool);
|
|
119
105
|
await vi.advanceTimersByTimeAsync(30_000);
|
|
120
|
-
|
|
121
|
-
expect(result).toBe(JSON.stringify({ error: 'Tool "slow" timed out after 30000ms' }));
|
|
106
|
+
expect(await promise).toBe(JSON.stringify({ error: 'Tool "slow" timed out after 30000ms' }));
|
|
122
107
|
vi.useRealTimers();
|
|
123
108
|
});
|
|
124
109
|
|
|
125
110
|
test("ctx.send calls the send callback", async () => {
|
|
126
111
|
const sends: Array<{ event: string; data: unknown }> = [];
|
|
127
|
-
const tool
|
|
128
|
-
description: "sends an event",
|
|
129
|
-
parameters: z.object({}),
|
|
112
|
+
const tool = makeTool({
|
|
130
113
|
execute: (_args, ctx) => {
|
|
131
114
|
ctx.send("game_state", { hp: 10 });
|
|
132
115
|
return "ok";
|
|
133
116
|
},
|
|
134
|
-
};
|
|
135
|
-
const result = await
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
{
|
|
139
|
-
tool,
|
|
140
|
-
env: {},
|
|
141
|
-
send: (event, data) => sends.push({ event, data }),
|
|
142
|
-
},
|
|
143
|
-
);
|
|
117
|
+
});
|
|
118
|
+
const result = await run("sender", {}, tool, {
|
|
119
|
+
send: (event: string, data: unknown) => sends.push({ event, data }),
|
|
120
|
+
});
|
|
144
121
|
expect(result).toBe("ok");
|
|
145
122
|
expect(sends).toEqual([{ event: "game_state", data: { hp: 10 } }]);
|
|
146
123
|
});
|
|
147
124
|
|
|
148
125
|
test("ctx.send is a no-op when no send callback provided", async () => {
|
|
149
|
-
const tool
|
|
150
|
-
description: "sends an event",
|
|
151
|
-
parameters: z.object({}),
|
|
126
|
+
const tool = makeTool({
|
|
152
127
|
execute: (_args, ctx) => {
|
|
153
128
|
ctx.send("test", {});
|
|
154
129
|
return "ok";
|
|
155
130
|
},
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
expect(result).toBe("ok");
|
|
131
|
+
});
|
|
132
|
+
expect(await run("sender", {}, tool)).toBe("ok");
|
|
159
133
|
});
|
|
160
134
|
});
|
package/host/tool-executor.ts
CHANGED
|
@@ -33,7 +33,7 @@ export type ExecuteToolCallOptions = {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
function buildToolContext(opts: ExecuteToolCallOptions): ToolContext {
|
|
36
|
-
const { env, state, kv, vector, messages, sessionId } = opts;
|
|
36
|
+
const { env, state, kv, vector, messages, sessionId, send } = opts;
|
|
37
37
|
return {
|
|
38
38
|
env,
|
|
39
39
|
state: state ?? {},
|
|
@@ -48,24 +48,32 @@ function buildToolContext(opts: ExecuteToolCallOptions): ToolContext {
|
|
|
48
48
|
messages: messages ?? [],
|
|
49
49
|
sessionId: sessionId ?? "",
|
|
50
50
|
send(event: string, data: unknown): void {
|
|
51
|
-
|
|
51
|
+
send?.(event, data);
|
|
52
52
|
},
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
function formatZodIssues(error: z.ZodError | undefined): string {
|
|
57
|
+
return (error?.issues ?? [])
|
|
58
|
+
.map((i: z.ZodIssue) => `${i.path.map(String).join(".")}: ${i.message}`)
|
|
59
|
+
.join(", ");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function stringifyResult(result: unknown): string {
|
|
63
|
+
if (result == null) return "null";
|
|
64
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
65
|
+
}
|
|
66
|
+
|
|
56
67
|
export async function executeToolCall(
|
|
57
68
|
name: string,
|
|
58
69
|
args: Readonly<Record<string, unknown>>,
|
|
59
70
|
options: ExecuteToolCallOptions,
|
|
60
71
|
): Promise<string> {
|
|
61
|
-
const { tool } = options;
|
|
72
|
+
const { tool, logger } = options;
|
|
62
73
|
const schema = tool.parameters ?? EMPTY_PARAMS;
|
|
63
74
|
const parsed = schema.safeParse(args);
|
|
64
75
|
if (!parsed.success) {
|
|
65
|
-
|
|
66
|
-
.map((i: z.ZodIssue) => `${i.path.map(String).join(".")}: ${i.message}`)
|
|
67
|
-
.join(", ");
|
|
68
|
-
return toolError(`Invalid arguments for tool "${name}": ${issues}`);
|
|
76
|
+
return toolError(`Invalid arguments for tool "${name}": ${formatZodIssues(parsed.error)}`);
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
try {
|
|
@@ -76,12 +84,10 @@ export async function executeToolCall(
|
|
|
76
84
|
message: `Tool "${name}" timed out after ${TOOL_EXECUTION_TIMEOUT_MS}ms`,
|
|
77
85
|
});
|
|
78
86
|
await yieldTick();
|
|
79
|
-
|
|
80
|
-
return typeof result === "string" ? result : JSON.stringify(result);
|
|
87
|
+
return stringifyResult(result);
|
|
81
88
|
} catch (err: unknown) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
log.warn("Tool execution failed", { tool: name, error: errorDetail(err) });
|
|
89
|
+
if (logger) {
|
|
90
|
+
logger.warn("Tool execution failed", { tool: name, error: errorDetail(err) });
|
|
85
91
|
} else {
|
|
86
92
|
console.warn(`[tool-executor] Tool execution failed: ${name}`, err);
|
|
87
93
|
}
|