@alexkroman1/aai 1.7.1 → 1.8.1
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 +23 -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 +670 -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 +45 -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 +139 -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 +439 -0
- package/host/transports/openai-realtime-transport.ts +371 -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
|
@@ -15,6 +15,9 @@ import type { TransportCallbacks } from "./types.ts";
|
|
|
15
15
|
|
|
16
16
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
|
+
type SttFake = ReturnType<typeof createFakeSttProvider>;
|
|
19
|
+
type TtsFake = ReturnType<typeof createFakeTtsProvider>;
|
|
20
|
+
|
|
18
21
|
function makeCallbacks(): TransportCallbacks {
|
|
19
22
|
return {
|
|
20
23
|
onReplyStarted: vi.fn(),
|
|
@@ -38,15 +41,11 @@ function makeOpts(
|
|
|
38
41
|
stt = createFakeSttProvider(),
|
|
39
42
|
tts = createFakeTtsProvider(),
|
|
40
43
|
callbacks = makeCallbacks(),
|
|
41
|
-
}: {
|
|
42
|
-
stt?: ReturnType<typeof createFakeSttProvider>;
|
|
43
|
-
tts?: ReturnType<typeof createFakeTtsProvider>;
|
|
44
|
-
callbacks?: TransportCallbacks;
|
|
45
|
-
} = {},
|
|
44
|
+
}: { stt?: SttFake; tts?: TtsFake; callbacks?: TransportCallbacks } = {},
|
|
46
45
|
): {
|
|
47
46
|
opts: PipelineTransportOptions;
|
|
48
|
-
stt:
|
|
49
|
-
tts:
|
|
47
|
+
stt: SttFake;
|
|
48
|
+
tts: TtsFake;
|
|
50
49
|
callbacks: TransportCallbacks;
|
|
51
50
|
} {
|
|
52
51
|
const opts: PipelineTransportOptions = {
|
|
@@ -56,10 +55,7 @@ function makeOpts(
|
|
|
56
55
|
llm: createFakeLanguageModel({ script: [] }),
|
|
57
56
|
tts,
|
|
58
57
|
callbacks,
|
|
59
|
-
sessionConfig: {
|
|
60
|
-
systemPrompt: "You are a test assistant.",
|
|
61
|
-
greeting: "",
|
|
62
|
-
},
|
|
58
|
+
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
63
59
|
providerKeys: { stt: "stt-key", tts: "tts-key" },
|
|
64
60
|
logger: silentLogger,
|
|
65
61
|
...overrides,
|
|
@@ -67,12 +63,24 @@ function makeOpts(
|
|
|
67
63
|
return { opts, stt, tts, callbacks };
|
|
68
64
|
}
|
|
69
65
|
|
|
66
|
+
function firstCallArg<T>(fn: unknown): T {
|
|
67
|
+
// biome-ignore lint/style/noNonNullAssertion: caller asserts the spy was invoked
|
|
68
|
+
return (fn as ReturnType<typeof vi.fn>).mock.calls[0]![0] as T;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const noopToolSchema = {
|
|
72
|
+
type: "function" as const,
|
|
73
|
+
name: "lookup",
|
|
74
|
+
description: "Look something up.",
|
|
75
|
+
parameters: { type: "object" as const, properties: {}, required: [] },
|
|
76
|
+
};
|
|
77
|
+
|
|
70
78
|
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
71
79
|
|
|
72
80
|
describe("PipelineTransport", () => {
|
|
73
81
|
describe("start()", () => {
|
|
74
82
|
test("opens both STT and TTS sessions", async () => {
|
|
75
|
-
const { opts, stt, tts } = makeOpts(
|
|
83
|
+
const { opts, stt, tts } = makeOpts();
|
|
76
84
|
const t = createPipelineTransport(opts);
|
|
77
85
|
await t.start();
|
|
78
86
|
expect(stt.last()).toBeDefined();
|
|
@@ -81,17 +89,11 @@ describe("PipelineTransport", () => {
|
|
|
81
89
|
});
|
|
82
90
|
|
|
83
91
|
test("passes correct keys and sample rate to STT opener", async () => {
|
|
84
|
-
const stt =
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
sttSampleRate: 8000,
|
|
90
|
-
sttPrompt: "be brief",
|
|
91
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
92
|
-
},
|
|
93
|
-
{ stt },
|
|
94
|
-
);
|
|
92
|
+
const { opts, stt } = makeOpts({
|
|
93
|
+
providerKeys: { stt: "MY_STT_KEY", tts: "t" },
|
|
94
|
+
sttSampleRate: 8000,
|
|
95
|
+
sttPrompt: "be brief",
|
|
96
|
+
});
|
|
95
97
|
const t = createPipelineTransport(opts);
|
|
96
98
|
await t.start();
|
|
97
99
|
expect(stt.last()?.opts.sampleRate).toBe(8000);
|
|
@@ -101,7 +103,7 @@ describe("PipelineTransport", () => {
|
|
|
101
103
|
});
|
|
102
104
|
|
|
103
105
|
test("fires onSessionReady with the sid", async () => {
|
|
104
|
-
const { opts, callbacks } = makeOpts(
|
|
106
|
+
const { opts, callbacks } = makeOpts();
|
|
105
107
|
const t = createPipelineTransport(opts);
|
|
106
108
|
await t.start();
|
|
107
109
|
expect(callbacks.onSessionReady).toHaveBeenCalledWith("test-sid");
|
|
@@ -111,38 +113,27 @@ describe("PipelineTransport", () => {
|
|
|
111
113
|
|
|
112
114
|
describe("greeting", () => {
|
|
113
115
|
test("sends greeting via ttsSession.sendText and fires onReplyStarted + onAgentTranscript + onReplyDone", async () => {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const { opts } = makeOpts(
|
|
118
|
-
{ sessionConfig: { systemPrompt: "s", greeting: "Hi there!" } },
|
|
119
|
-
{ stt, tts, callbacks },
|
|
120
|
-
);
|
|
116
|
+
const { opts, tts, callbacks } = makeOpts({
|
|
117
|
+
sessionConfig: { systemPrompt: "s", greeting: "Hi there!" },
|
|
118
|
+
});
|
|
121
119
|
const t = createPipelineTransport(opts);
|
|
122
120
|
await t.start();
|
|
123
|
-
// Greeting runs as a chained turn — waitFor covers the async flush.
|
|
124
121
|
await vi.waitFor(() => {
|
|
125
122
|
expect(callbacks.onReplyDone).toHaveBeenCalledOnce();
|
|
126
123
|
});
|
|
127
124
|
expect(tts.last()?.textChunks).toContain("Hi there!");
|
|
128
125
|
expect(callbacks.onReplyStarted).toHaveBeenCalledWith(expect.stringContaining("greeting"));
|
|
129
126
|
expect(callbacks.onAgentTranscript).toHaveBeenCalledWith("Hi there!", false);
|
|
130
|
-
// onAudioDone is
|
|
131
|
-
// (triggered by onReplyDone) owns the audioDone + replyDone pairing.
|
|
127
|
+
// onAudioDone is owned by session-core's flushReply, not the transport.
|
|
132
128
|
expect(callbacks.onAudioDone).not.toHaveBeenCalled();
|
|
133
129
|
await t.stop();
|
|
134
130
|
});
|
|
135
131
|
|
|
136
132
|
test("skipGreeting suppresses the greeting turn", async () => {
|
|
137
|
-
const tts =
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
skipGreeting: true,
|
|
142
|
-
sessionConfig: { systemPrompt: "s", greeting: "Hello!" },
|
|
143
|
-
},
|
|
144
|
-
{ tts, callbacks },
|
|
145
|
-
);
|
|
133
|
+
const { opts, tts, callbacks } = makeOpts({
|
|
134
|
+
skipGreeting: true,
|
|
135
|
+
sessionConfig: { systemPrompt: "s", greeting: "Hello!" },
|
|
136
|
+
});
|
|
146
137
|
const t = createPipelineTransport(opts);
|
|
147
138
|
await t.start();
|
|
148
139
|
await new Promise((r) => setTimeout(r, 20));
|
|
@@ -154,12 +145,7 @@ describe("PipelineTransport", () => {
|
|
|
154
145
|
|
|
155
146
|
describe("STT → LLM turn", () => {
|
|
156
147
|
test("final STT event fires onUserTranscript and onReplyStarted", async () => {
|
|
157
|
-
const stt =
|
|
158
|
-
const callbacks = makeCallbacks();
|
|
159
|
-
const { opts } = makeOpts(
|
|
160
|
-
{ sessionConfig: { systemPrompt: "s", greeting: "" } },
|
|
161
|
-
{ stt, callbacks },
|
|
162
|
-
);
|
|
148
|
+
const { opts, stt, callbacks } = makeOpts();
|
|
163
149
|
const t = createPipelineTransport(opts);
|
|
164
150
|
await t.start();
|
|
165
151
|
stt.last()?.fireFinal("Hello agent");
|
|
@@ -171,12 +157,7 @@ describe("PipelineTransport", () => {
|
|
|
171
157
|
});
|
|
172
158
|
|
|
173
159
|
test("empty / whitespace-only final is ignored", async () => {
|
|
174
|
-
const stt =
|
|
175
|
-
const callbacks = makeCallbacks();
|
|
176
|
-
const { opts } = makeOpts(
|
|
177
|
-
{ sessionConfig: { systemPrompt: "s", greeting: "" } },
|
|
178
|
-
{ stt, callbacks },
|
|
179
|
-
);
|
|
160
|
+
const { opts, stt, callbacks } = makeOpts();
|
|
180
161
|
const t = createPipelineTransport(opts);
|
|
181
162
|
await t.start();
|
|
182
163
|
stt.last()?.fireFinal(" ");
|
|
@@ -191,15 +172,7 @@ describe("PipelineTransport", () => {
|
|
|
191
172
|
{ type: "text", text: "I am " },
|
|
192
173
|
{ type: "text", text: "the answer" },
|
|
193
174
|
];
|
|
194
|
-
const stt =
|
|
195
|
-
const tts = createFakeTtsProvider();
|
|
196
|
-
const { opts } = makeOpts(
|
|
197
|
-
{
|
|
198
|
-
llm: createFakeLanguageModel({ script }),
|
|
199
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
200
|
-
},
|
|
201
|
-
{ stt, tts },
|
|
202
|
-
);
|
|
175
|
+
const { opts, stt, tts } = makeOpts({ llm: createFakeLanguageModel({ script }) });
|
|
203
176
|
const t = createPipelineTransport(opts);
|
|
204
177
|
await t.start();
|
|
205
178
|
stt.last()?.fireFinal("what is the answer?");
|
|
@@ -211,37 +184,20 @@ describe("PipelineTransport", () => {
|
|
|
211
184
|
});
|
|
212
185
|
|
|
213
186
|
test("inserts a separator between text segments split by a mid-turn tool call", async () => {
|
|
214
|
-
// Multi-step turn:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const { opts } = makeOpts(
|
|
222
|
-
{
|
|
223
|
-
llm: createFakeLanguageModel({
|
|
224
|
-
steps: [
|
|
225
|
-
[
|
|
226
|
-
{ type: "text", text: "Let me look that up." },
|
|
227
|
-
{ type: "tool-call", toolCallId: "tc-1", toolName: "lookup", input: "{}" },
|
|
228
|
-
],
|
|
229
|
-
[{ type: "text", text: "Got it. Here's the answer." }],
|
|
187
|
+
// Multi-step turn: without the separator fix, deltas fuse into "...up.Got it".
|
|
188
|
+
const { opts, stt, tts, callbacks } = makeOpts({
|
|
189
|
+
llm: createFakeLanguageModel({
|
|
190
|
+
steps: [
|
|
191
|
+
[
|
|
192
|
+
{ type: "text", text: "Let me look that up." },
|
|
193
|
+
{ type: "tool-call", toolCallId: "tc-1", toolName: "lookup", input: "{}" },
|
|
230
194
|
],
|
|
231
|
-
|
|
232
|
-
executeTool,
|
|
233
|
-
toolSchemas: [
|
|
234
|
-
{
|
|
235
|
-
type: "function" as const,
|
|
236
|
-
name: "lookup",
|
|
237
|
-
description: "Look something up.",
|
|
238
|
-
parameters: { type: "object" as const, properties: {}, required: [] },
|
|
239
|
-
},
|
|
195
|
+
[{ type: "text", text: "Got it. Here's the answer." }],
|
|
240
196
|
],
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
);
|
|
197
|
+
}),
|
|
198
|
+
executeTool: vi.fn(async () => "result"),
|
|
199
|
+
toolSchemas: [noopToolSchema],
|
|
200
|
+
});
|
|
245
201
|
const t = createPipelineTransport(opts);
|
|
246
202
|
await t.start();
|
|
247
203
|
stt.last()?.fireFinal("look it up");
|
|
@@ -259,35 +215,19 @@ describe("PipelineTransport", () => {
|
|
|
259
215
|
});
|
|
260
216
|
|
|
261
217
|
test("does not double-space when a segment boundary already carries whitespace", async () => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
{
|
|
269
|
-
llm: createFakeLanguageModel({
|
|
270
|
-
steps: [
|
|
271
|
-
[
|
|
272
|
-
{ type: "text", text: "First sentence. " },
|
|
273
|
-
{ type: "tool-call", toolCallId: "tc-1", toolName: "lookup", input: "{}" },
|
|
274
|
-
],
|
|
275
|
-
[{ type: "text", text: "Second sentence." }],
|
|
218
|
+
const { opts, stt, callbacks } = makeOpts({
|
|
219
|
+
llm: createFakeLanguageModel({
|
|
220
|
+
steps: [
|
|
221
|
+
[
|
|
222
|
+
{ type: "text", text: "First sentence. " },
|
|
223
|
+
{ type: "tool-call", toolCallId: "tc-1", toolName: "lookup", input: "{}" },
|
|
276
224
|
],
|
|
277
|
-
|
|
278
|
-
executeTool,
|
|
279
|
-
toolSchemas: [
|
|
280
|
-
{
|
|
281
|
-
type: "function" as const,
|
|
282
|
-
name: "lookup",
|
|
283
|
-
description: "Look something up.",
|
|
284
|
-
parameters: { type: "object" as const, properties: {}, required: [] },
|
|
285
|
-
},
|
|
225
|
+
[{ type: "text", text: "Second sentence." }],
|
|
286
226
|
],
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
);
|
|
227
|
+
}),
|
|
228
|
+
executeTool: vi.fn(async () => "result"),
|
|
229
|
+
toolSchemas: [noopToolSchema],
|
|
230
|
+
});
|
|
291
231
|
const t = createPipelineTransport(opts);
|
|
292
232
|
await t.start();
|
|
293
233
|
stt.last()?.fireFinal("look it up");
|
|
@@ -302,38 +242,22 @@ describe("PipelineTransport", () => {
|
|
|
302
242
|
});
|
|
303
243
|
|
|
304
244
|
test("TTS audio event is forwarded to callbacks.onAudioChunk as Uint8Array", async () => {
|
|
305
|
-
const
|
|
306
|
-
const tts = createFakeTtsProvider();
|
|
307
|
-
const callbacks = makeCallbacks();
|
|
308
|
-
const { opts } = makeOpts(
|
|
309
|
-
{ sessionConfig: { systemPrompt: "s", greeting: "" } },
|
|
310
|
-
{ stt, tts, callbacks },
|
|
311
|
-
);
|
|
245
|
+
const { opts, tts, callbacks } = makeOpts();
|
|
312
246
|
const t = createPipelineTransport(opts);
|
|
313
247
|
await t.start();
|
|
314
248
|
const pcm = new Int16Array([100, 200, 300]);
|
|
315
249
|
tts.last()?.fireAudio(pcm);
|
|
316
250
|
expect(callbacks.onAudioChunk).toHaveBeenCalledOnce();
|
|
317
|
-
|
|
318
|
-
const arg = (callbacks.onAudioChunk as ReturnType<typeof vi.fn>).mock
|
|
319
|
-
.calls[0]![0] as Uint8Array;
|
|
251
|
+
const arg = firstCallArg<Uint8Array>(callbacks.onAudioChunk);
|
|
320
252
|
expect(arg).toBeInstanceOf(Uint8Array);
|
|
321
253
|
expect(arg.byteLength).toBe(pcm.byteLength);
|
|
322
254
|
await t.stop();
|
|
323
255
|
});
|
|
324
256
|
|
|
325
257
|
test("full turn: onUserTranscript → onReplyStarted → onAgentTranscript → onReplyDone (no transport-level onAudioDone)", async () => {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const callbacks = makeCallbacks();
|
|
330
|
-
const { opts } = makeOpts(
|
|
331
|
-
{
|
|
332
|
-
llm: createFakeLanguageModel({ script }),
|
|
333
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
334
|
-
},
|
|
335
|
-
{ stt, tts, callbacks },
|
|
336
|
-
);
|
|
258
|
+
const { opts, stt, callbacks } = makeOpts({
|
|
259
|
+
llm: createFakeLanguageModel({ script: [{ type: "text", text: "Sure!" }] }),
|
|
260
|
+
});
|
|
337
261
|
const t = createPipelineTransport(opts);
|
|
338
262
|
await t.start();
|
|
339
263
|
stt.last()?.fireFinal("test question");
|
|
@@ -343,23 +267,15 @@ describe("PipelineTransport", () => {
|
|
|
343
267
|
expect(callbacks.onUserTranscript).toHaveBeenCalledWith("test question");
|
|
344
268
|
expect(callbacks.onReplyStarted).toHaveBeenCalled();
|
|
345
269
|
expect(callbacks.onAgentTranscript).toHaveBeenCalledWith("Sure!", false);
|
|
346
|
-
// onAudioDone is
|
|
347
|
-
// (triggered by onReplyDone) owns the audioDone + replyDone pairing.
|
|
270
|
+
// onAudioDone is owned by session-core's flushReply, not the transport.
|
|
348
271
|
expect(callbacks.onAudioDone).not.toHaveBeenCalled();
|
|
349
272
|
await t.stop();
|
|
350
273
|
});
|
|
351
274
|
|
|
352
275
|
test("TTS flush is called after LLM stream finishes", async () => {
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const { opts } = makeOpts(
|
|
357
|
-
{
|
|
358
|
-
llm: createFakeLanguageModel({ script }),
|
|
359
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
360
|
-
},
|
|
361
|
-
{ stt, tts },
|
|
362
|
-
);
|
|
276
|
+
const { opts, stt, tts } = makeOpts({
|
|
277
|
+
llm: createFakeLanguageModel({ script: [{ type: "text", text: "hi" }] }),
|
|
278
|
+
});
|
|
363
279
|
const t = createPipelineTransport(opts);
|
|
364
280
|
await t.start();
|
|
365
281
|
stt.last()?.fireFinal("go");
|
|
@@ -382,17 +298,12 @@ describe("PipelineTransport", () => {
|
|
|
382
298
|
const dummyExecuteTool = async () => "{}";
|
|
383
299
|
|
|
384
300
|
test("forwards toolChoice to doStream (default 'auto' when omitted)", async () => {
|
|
385
|
-
const stt = createFakeSttProvider();
|
|
386
301
|
const llm = createFakeLanguageModel({ script: [{ type: "text", text: "ok" }] });
|
|
387
|
-
const { opts } = makeOpts(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
393
|
-
},
|
|
394
|
-
{ stt },
|
|
395
|
-
);
|
|
302
|
+
const { opts, stt } = makeOpts({
|
|
303
|
+
llm,
|
|
304
|
+
toolSchemas: dummyToolSchemas,
|
|
305
|
+
executeTool: dummyExecuteTool,
|
|
306
|
+
});
|
|
396
307
|
const t = createPipelineTransport(opts);
|
|
397
308
|
await t.start();
|
|
398
309
|
stt.last()?.fireFinal("hi");
|
|
@@ -404,18 +315,13 @@ describe("PipelineTransport", () => {
|
|
|
404
315
|
});
|
|
405
316
|
|
|
406
317
|
test("forwards explicit toolChoice='required' to doStream", async () => {
|
|
407
|
-
const stt = createFakeSttProvider();
|
|
408
318
|
const llm = createFakeLanguageModel({ script: [{ type: "text", text: "ok" }] });
|
|
409
|
-
const { opts } = makeOpts(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
416
|
-
},
|
|
417
|
-
{ stt },
|
|
418
|
-
);
|
|
319
|
+
const { opts, stt } = makeOpts({
|
|
320
|
+
llm,
|
|
321
|
+
toolChoice: "required",
|
|
322
|
+
toolSchemas: dummyToolSchemas,
|
|
323
|
+
executeTool: dummyExecuteTool,
|
|
324
|
+
});
|
|
419
325
|
const t = createPipelineTransport(opts);
|
|
420
326
|
await t.start();
|
|
421
327
|
stt.last()?.fireFinal("hi");
|
|
@@ -427,28 +333,17 @@ describe("PipelineTransport", () => {
|
|
|
427
333
|
});
|
|
428
334
|
|
|
429
335
|
test("maxSteps caps the doStream loop", async () => {
|
|
430
|
-
//
|
|
431
|
-
// first step should run; without plumbing it would default to 5 and both
|
|
432
|
-
// would fire.
|
|
433
|
-
const stt = createFakeSttProvider();
|
|
336
|
+
// Two scripted steps; maxSteps=1 must stop after the first (default would be 5).
|
|
434
337
|
const llm = createFakeLanguageModel({
|
|
435
338
|
steps: [[{ type: "text", text: "step1" }], [{ type: "text", text: "step2" }]],
|
|
436
339
|
});
|
|
437
|
-
const { opts } = makeOpts(
|
|
438
|
-
{
|
|
439
|
-
llm,
|
|
440
|
-
maxSteps: 1,
|
|
441
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
442
|
-
},
|
|
443
|
-
{ stt },
|
|
444
|
-
);
|
|
340
|
+
const { opts, stt } = makeOpts({ llm, maxSteps: 1 });
|
|
445
341
|
const t = createPipelineTransport(opts);
|
|
446
342
|
await t.start();
|
|
447
343
|
stt.last()?.fireFinal("hi");
|
|
448
344
|
await vi.waitFor(() => {
|
|
449
345
|
expect(llm.calls.length).toBeGreaterThanOrEqual(1);
|
|
450
346
|
});
|
|
451
|
-
// Let any extra step have a chance to run.
|
|
452
347
|
await new Promise((r) => setTimeout(r, 20));
|
|
453
348
|
expect(llm.calls.length).toBe(1);
|
|
454
349
|
await t.stop();
|
|
@@ -462,26 +357,17 @@ describe("PipelineTransport", () => {
|
|
|
462
357
|
{ type: "text", text: "how can " },
|
|
463
358
|
{ type: "text", text: "I help?" },
|
|
464
359
|
];
|
|
465
|
-
const stt =
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const { opts } = makeOpts(
|
|
469
|
-
{
|
|
470
|
-
llm: createFakeLanguageModel({ script, delayMs: 20 }),
|
|
471
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
472
|
-
},
|
|
473
|
-
{ stt, tts, callbacks },
|
|
474
|
-
);
|
|
360
|
+
const { opts, stt, tts, callbacks } = makeOpts({
|
|
361
|
+
llm: createFakeLanguageModel({ script, delayMs: 20 }),
|
|
362
|
+
});
|
|
475
363
|
const t = createPipelineTransport(opts);
|
|
476
364
|
await t.start();
|
|
477
365
|
|
|
478
|
-
// Start a turn, wait until TTS is receiving text (deep in AGENT_REPLYING).
|
|
479
366
|
stt.last()?.fireFinal("hi there");
|
|
480
367
|
await vi.waitFor(() => {
|
|
481
368
|
expect(tts.last()?.textChunks.length).toBeGreaterThan(0);
|
|
482
369
|
});
|
|
483
370
|
|
|
484
|
-
// Fire barge-in partial.
|
|
485
371
|
stt.last()?.firePartial("wait");
|
|
486
372
|
expect(callbacks.onCancelled).toHaveBeenCalled();
|
|
487
373
|
expect(tts.last()?.cancel).toHaveBeenCalled();
|
|
@@ -493,16 +379,9 @@ describe("PipelineTransport", () => {
|
|
|
493
379
|
{ type: "text", text: "some " },
|
|
494
380
|
{ type: "text", text: "reply" },
|
|
495
381
|
];
|
|
496
|
-
const stt =
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const { opts } = makeOpts(
|
|
500
|
-
{
|
|
501
|
-
llm: createFakeLanguageModel({ script, delayMs: 20 }),
|
|
502
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
503
|
-
},
|
|
504
|
-
{ stt, tts, callbacks },
|
|
505
|
-
);
|
|
382
|
+
const { opts, stt, tts, callbacks } = makeOpts({
|
|
383
|
+
llm: createFakeLanguageModel({ script, delayMs: 20 }),
|
|
384
|
+
});
|
|
506
385
|
const t = createPipelineTransport(opts);
|
|
507
386
|
await t.start();
|
|
508
387
|
|
|
@@ -513,9 +392,8 @@ describe("PipelineTransport", () => {
|
|
|
513
392
|
|
|
514
393
|
t.cancelReply();
|
|
515
394
|
expect(tts.last()?.cancel).toHaveBeenCalled();
|
|
516
|
-
// cancelReply()
|
|
517
|
-
// client.
|
|
518
|
-
// onCancelled is only fired from within the transport for barge-in (STT partial).
|
|
395
|
+
// cancelReply() doesn't fire onCancelled — session-core calls client.cancelled()
|
|
396
|
+
// itself for client-originated cancels. onCancelled fires only for STT-partial barge-in.
|
|
519
397
|
expect(callbacks.onCancelled).not.toHaveBeenCalled();
|
|
520
398
|
await t.stop();
|
|
521
399
|
});
|
|
@@ -523,12 +401,7 @@ describe("PipelineTransport", () => {
|
|
|
523
401
|
|
|
524
402
|
describe("stop()", () => {
|
|
525
403
|
test("closes both STT and TTS sessions", async () => {
|
|
526
|
-
const stt =
|
|
527
|
-
const tts = createFakeTtsProvider();
|
|
528
|
-
const { opts } = makeOpts(
|
|
529
|
-
{ sessionConfig: { systemPrompt: "s", greeting: "" } },
|
|
530
|
-
{ stt, tts },
|
|
531
|
-
);
|
|
404
|
+
const { opts, stt, tts } = makeOpts();
|
|
532
405
|
const t = createPipelineTransport(opts);
|
|
533
406
|
await t.start();
|
|
534
407
|
await t.stop();
|
|
@@ -537,50 +410,37 @@ describe("PipelineTransport", () => {
|
|
|
537
410
|
});
|
|
538
411
|
|
|
539
412
|
test("stop() is idempotent", async () => {
|
|
540
|
-
const stt =
|
|
541
|
-
const tts = createFakeTtsProvider();
|
|
542
|
-
const { opts } = makeOpts(
|
|
543
|
-
{ sessionConfig: { systemPrompt: "s", greeting: "" } },
|
|
544
|
-
{ stt, tts },
|
|
545
|
-
);
|
|
413
|
+
const { opts, stt } = makeOpts();
|
|
546
414
|
const t = createPipelineTransport(opts);
|
|
547
415
|
await t.start();
|
|
548
416
|
await t.stop();
|
|
549
|
-
await t.stop();
|
|
417
|
+
await t.stop();
|
|
550
418
|
expect(stt.last()?.closed.value).toBe(true);
|
|
551
419
|
});
|
|
552
420
|
});
|
|
553
421
|
|
|
554
422
|
describe("sendUserAudio()", () => {
|
|
555
423
|
test("converts aligned Uint8Array to Int16Array and calls sttSession.sendAudio", async () => {
|
|
556
|
-
const stt =
|
|
557
|
-
const { opts } = makeOpts({ sessionConfig: { systemPrompt: "s", greeting: "" } }, { stt });
|
|
424
|
+
const { opts, stt } = makeOpts();
|
|
558
425
|
const t = createPipelineTransport(opts);
|
|
559
426
|
await t.start();
|
|
560
|
-
const
|
|
561
|
-
const bytes = new Uint8Array(buf);
|
|
562
|
-
bytes.set([0x01, 0x02, 0x03, 0x04]);
|
|
427
|
+
const bytes = new Uint8Array([0x01, 0x02, 0x03, 0x04]);
|
|
563
428
|
t.sendUserAudio(bytes);
|
|
564
429
|
const sttSession = stt.last();
|
|
565
430
|
expect(sttSession?.sendAudio).toHaveBeenCalledOnce();
|
|
566
|
-
|
|
567
|
-
const pcm = (sttSession?.sendAudio as ReturnType<typeof vi.fn>).mock
|
|
568
|
-
.calls[0]![0] as Int16Array;
|
|
431
|
+
const pcm = firstCallArg<Int16Array>(sttSession?.sendAudio);
|
|
569
432
|
expect(pcm).toBeInstanceOf(Int16Array);
|
|
570
433
|
expect(pcm.length).toBe(2);
|
|
571
434
|
await t.stop();
|
|
572
435
|
});
|
|
573
436
|
|
|
574
437
|
test("handles odd-length Uint8Array by copying and truncating", async () => {
|
|
575
|
-
const stt =
|
|
576
|
-
const { opts } = makeOpts({ sessionConfig: { systemPrompt: "s", greeting: "" } }, { stt });
|
|
438
|
+
const { opts, stt } = makeOpts();
|
|
577
439
|
const t = createPipelineTransport(opts);
|
|
578
440
|
await t.start();
|
|
579
|
-
|
|
580
|
-
t.sendUserAudio(
|
|
581
|
-
|
|
582
|
-
const pcm = (stt.last()?.sendAudio as ReturnType<typeof vi.fn>).mock
|
|
583
|
-
.calls[0]![0] as Int16Array;
|
|
441
|
+
// 3 bytes → 1 sample (truncates the trailing odd byte).
|
|
442
|
+
t.sendUserAudio(new Uint8Array([1, 2, 3]));
|
|
443
|
+
const pcm = firstCallArg<Int16Array>(stt.last()?.sendAudio);
|
|
584
444
|
expect(pcm.length).toBe(1);
|
|
585
445
|
await t.stop();
|
|
586
446
|
});
|
|
@@ -588,7 +448,7 @@ describe("PipelineTransport", () => {
|
|
|
588
448
|
|
|
589
449
|
describe("sendToolResult()", () => {
|
|
590
450
|
test("is a no-op (Option A: inline tool execution)", async () => {
|
|
591
|
-
const { opts } = makeOpts(
|
|
451
|
+
const { opts } = makeOpts();
|
|
592
452
|
const t = createPipelineTransport(opts);
|
|
593
453
|
await t.start();
|
|
594
454
|
expect(() => t.sendToolResult("call-1", "result")).not.toThrow();
|
|
@@ -598,7 +458,6 @@ describe("PipelineTransport", () => {
|
|
|
598
458
|
|
|
599
459
|
describe("tool observability", () => {
|
|
600
460
|
test("callbacks.onToolCall fires for each tool-call stream part", async () => {
|
|
601
|
-
const executeTool = vi.fn(async () => "sunny");
|
|
602
461
|
const script: ScriptedPart[] = [
|
|
603
462
|
{
|
|
604
463
|
type: "tool-call",
|
|
@@ -609,36 +468,28 @@ describe("PipelineTransport", () => {
|
|
|
609
468
|
{ type: "tool-result", toolCallId: "tc-1", toolName: "get_weather", result: "sunny" },
|
|
610
469
|
{ type: "text", text: "It's sunny." },
|
|
611
470
|
];
|
|
612
|
-
const stt =
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
{
|
|
621
|
-
type: "
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
parameters: {
|
|
625
|
-
type: "object" as const,
|
|
626
|
-
properties: { city: { type: "string" } },
|
|
627
|
-
required: ["city"],
|
|
628
|
-
},
|
|
471
|
+
const { opts, stt, callbacks } = makeOpts({
|
|
472
|
+
llm: createFakeLanguageModel({ script }),
|
|
473
|
+
executeTool: vi.fn(async () => "sunny"),
|
|
474
|
+
toolSchemas: [
|
|
475
|
+
{
|
|
476
|
+
type: "function" as const,
|
|
477
|
+
name: "get_weather",
|
|
478
|
+
description: "Look up the weather.",
|
|
479
|
+
parameters: {
|
|
480
|
+
type: "object" as const,
|
|
481
|
+
properties: { city: { type: "string" } },
|
|
482
|
+
required: ["city"],
|
|
629
483
|
},
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
{ stt, tts, callbacks },
|
|
634
|
-
);
|
|
484
|
+
},
|
|
485
|
+
],
|
|
486
|
+
});
|
|
635
487
|
const t = createPipelineTransport(opts);
|
|
636
488
|
await t.start();
|
|
637
489
|
stt.last()?.fireFinal("how's the weather?");
|
|
638
490
|
await vi.waitFor(() => {
|
|
639
491
|
expect(callbacks.onReplyDone).toHaveBeenCalled();
|
|
640
492
|
});
|
|
641
|
-
// onToolCall fires for observability (Option A).
|
|
642
493
|
expect(callbacks.onToolCall).toHaveBeenCalledWith("tc-1", "get_weather", expect.any(Object));
|
|
643
494
|
await t.stop();
|
|
644
495
|
});
|
|
@@ -646,12 +497,7 @@ describe("PipelineTransport", () => {
|
|
|
646
497
|
|
|
647
498
|
describe("provider errors", () => {
|
|
648
499
|
test("STT error fires onError('stt', ...) and terminates transport", async () => {
|
|
649
|
-
const stt =
|
|
650
|
-
const callbacks = makeCallbacks();
|
|
651
|
-
const { opts } = makeOpts(
|
|
652
|
-
{ sessionConfig: { systemPrompt: "s", greeting: "" } },
|
|
653
|
-
{ stt, callbacks },
|
|
654
|
-
);
|
|
500
|
+
const { opts, stt, callbacks } = makeOpts();
|
|
655
501
|
const t = createPipelineTransport(opts);
|
|
656
502
|
await t.start();
|
|
657
503
|
stt.last()?.fireError("stt_stream_error", "stt failed");
|
|
@@ -660,12 +506,7 @@ describe("PipelineTransport", () => {
|
|
|
660
506
|
});
|
|
661
507
|
|
|
662
508
|
test("TTS error fires onError('tts', ...) and terminates transport", async () => {
|
|
663
|
-
const tts =
|
|
664
|
-
const callbacks = makeCallbacks();
|
|
665
|
-
const { opts } = makeOpts(
|
|
666
|
-
{ sessionConfig: { systemPrompt: "s", greeting: "" } },
|
|
667
|
-
{ tts, callbacks },
|
|
668
|
-
);
|
|
509
|
+
const { opts, tts, callbacks } = makeOpts();
|
|
669
510
|
const t = createPipelineTransport(opts);
|
|
670
511
|
await t.start();
|
|
671
512
|
tts.last()?.fireError("tts_stream_error", "tts failed");
|
|
@@ -674,14 +515,9 @@ describe("PipelineTransport", () => {
|
|
|
674
515
|
});
|
|
675
516
|
|
|
676
517
|
test("STT open failure fires onError('stt', ...) via reportOpenRejection", async () => {
|
|
677
|
-
const callbacks =
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
stt: createFailingSttProvider("stt_connect_failed", "connect failed"),
|
|
681
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
682
|
-
},
|
|
683
|
-
{ callbacks },
|
|
684
|
-
);
|
|
518
|
+
const { opts, callbacks } = makeOpts({
|
|
519
|
+
stt: createFailingSttProvider("stt_connect_failed", "connect failed"),
|
|
520
|
+
});
|
|
685
521
|
const t = createPipelineTransport(opts);
|
|
686
522
|
await t.start();
|
|
687
523
|
expect(callbacks.onError).toHaveBeenCalledWith("stt", "connect failed");
|
|
@@ -689,14 +525,9 @@ describe("PipelineTransport", () => {
|
|
|
689
525
|
});
|
|
690
526
|
|
|
691
527
|
test("TTS open failure fires onError('tts', ...) via reportOpenRejection", async () => {
|
|
692
|
-
const callbacks =
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
tts: createFailingTtsProvider("tts_connect_failed", "tts connect failed"),
|
|
696
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
697
|
-
},
|
|
698
|
-
{ callbacks },
|
|
699
|
-
);
|
|
528
|
+
const { opts, callbacks } = makeOpts({
|
|
529
|
+
tts: createFailingTtsProvider("tts_connect_failed", "tts connect failed"),
|
|
530
|
+
});
|
|
700
531
|
const t = createPipelineTransport(opts);
|
|
701
532
|
await t.start();
|
|
702
533
|
expect(callbacks.onError).toHaveBeenCalledWith("tts", "tts connect failed");
|
|
@@ -709,13 +540,12 @@ describe("PipelineTransport", () => {
|
|
|
709
540
|
{
|
|
710
541
|
stt: createFailingSttProvider("stt_connect_failed", "bad key"),
|
|
711
542
|
tts,
|
|
712
|
-
sessionConfig: { systemPrompt: "s", greeting: "" },
|
|
713
543
|
},
|
|
714
544
|
{ tts },
|
|
715
545
|
);
|
|
716
546
|
const t = createPipelineTransport(opts);
|
|
717
547
|
await t.start();
|
|
718
|
-
//
|
|
548
|
+
// Promise.allSettled opens both concurrently; STT failure then closes TTS.
|
|
719
549
|
expect(tts.last()?.closed.value).toBe(true);
|
|
720
550
|
await t.stop();
|
|
721
551
|
});
|
|
@@ -723,9 +553,6 @@ describe("PipelineTransport", () => {
|
|
|
723
553
|
|
|
724
554
|
describe("history seeding", () => {
|
|
725
555
|
test("sessionConfig.history is used as initial conversation messages", async () => {
|
|
726
|
-
// History seeding is internal — we verify it indirectly by checking
|
|
727
|
-
// that the LLM receives the correct message array.
|
|
728
|
-
// For this test we just ensure start() doesn't throw when history is set.
|
|
729
556
|
const { opts } = makeOpts({
|
|
730
557
|
sessionConfig: {
|
|
731
558
|
systemPrompt: "s",
|