@alexkroman1/aai 1.7.0 → 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 +16 -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 +628 -642
- 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 +56 -53
- 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/s2s.test.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { silentLogger } from "./_test-utils.ts";
|
|
|
3
3
|
import type { S2sCallbacks, S2sWebSocket } from "./s2s.ts";
|
|
4
4
|
import { connectS2s } from "./s2s.ts";
|
|
5
5
|
|
|
6
|
-
/** EventTarget-based WebSocket stub (standard API, no `.on()` adapter needed). */
|
|
7
6
|
function createWebSocketStub() {
|
|
8
7
|
const target = new EventTarget();
|
|
9
8
|
return Object.assign(target, {
|
|
@@ -11,7 +10,6 @@ function createWebSocketStub() {
|
|
|
11
10
|
send: vi.fn(),
|
|
12
11
|
close: vi.fn(),
|
|
13
12
|
addEventListener: target.addEventListener.bind(target) as S2sWebSocket["addEventListener"],
|
|
14
|
-
/** Simulate a server-side event for testing. */
|
|
15
13
|
emit(event: string, ...args: unknown[]) {
|
|
16
14
|
const builders: Record<string, () => Event> = {
|
|
17
15
|
open: () => new Event("open"),
|
|
@@ -79,6 +77,20 @@ async function setupHandle(callbacks?: S2sCallbacks) {
|
|
|
79
77
|
return { raw, handle, logger };
|
|
80
78
|
}
|
|
81
79
|
|
|
80
|
+
type WebSocketStub = ReturnType<typeof createWebSocketStub>;
|
|
81
|
+
|
|
82
|
+
function emitMessage(raw: WebSocketStub, payload: unknown): void {
|
|
83
|
+
raw.emit("message", Buffer.from(JSON.stringify(payload)));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function lastSent(raw: WebSocketStub): Record<string, unknown> {
|
|
87
|
+
return JSON.parse(raw.send.mock.calls[0]?.[0] as string);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function errorArg(callbacks: S2sCallbacks): Error {
|
|
91
|
+
return (callbacks.onError as ReturnType<typeof vi.fn>).mock.calls[0]?.[0];
|
|
92
|
+
}
|
|
93
|
+
|
|
82
94
|
describe("connectS2s", () => {
|
|
83
95
|
test("resolves with handle after open", async () => {
|
|
84
96
|
const { handle } = await setupHandle();
|
|
@@ -114,17 +126,15 @@ describe("connectS2s", () => {
|
|
|
114
126
|
).rejects.toThrow("connection refused");
|
|
115
127
|
});
|
|
116
128
|
|
|
117
|
-
// ─── Handle methods ────────────────────────────────────────────────────
|
|
118
|
-
|
|
119
129
|
test("updateSession sends session.update message", async () => {
|
|
120
130
|
const { raw, handle } = await setupHandle();
|
|
121
131
|
|
|
122
132
|
handle.updateSession({ systemPrompt: "test", tools: [] });
|
|
123
133
|
|
|
124
134
|
expect(raw.send).toHaveBeenCalledOnce();
|
|
125
|
-
const sent =
|
|
135
|
+
const sent = lastSent(raw) as { type: string; session: { system_prompt: string } };
|
|
126
136
|
expect(sent.type).toBe("session.update");
|
|
127
|
-
expect(sent.session.system_prompt).toBe("test");
|
|
137
|
+
expect(sent.session.system_prompt).toBe("test");
|
|
128
138
|
});
|
|
129
139
|
|
|
130
140
|
test("sendAudio sends base64-encoded audio when open", async () => {
|
|
@@ -133,14 +143,14 @@ describe("connectS2s", () => {
|
|
|
133
143
|
handle.sendAudio(new Uint8Array([1, 2, 3, 4]));
|
|
134
144
|
|
|
135
145
|
expect(raw.send).toHaveBeenCalledOnce();
|
|
136
|
-
const sent =
|
|
146
|
+
const sent = lastSent(raw);
|
|
137
147
|
expect(sent.type).toBe("input.audio");
|
|
138
|
-
expect(typeof sent.audio).toBe("string");
|
|
148
|
+
expect(typeof sent.audio).toBe("string");
|
|
139
149
|
});
|
|
140
150
|
|
|
141
151
|
test("sendAudio is no-op when ws is not open", async () => {
|
|
142
152
|
const { raw, handle } = await setupHandle();
|
|
143
|
-
raw.readyState = 3;
|
|
153
|
+
raw.readyState = 3;
|
|
144
154
|
|
|
145
155
|
handle.sendAudio(new Uint8Array([1, 2, 3, 4]));
|
|
146
156
|
expect(raw.send).not.toHaveBeenCalled();
|
|
@@ -158,7 +168,7 @@ describe("connectS2s", () => {
|
|
|
158
168
|
|
|
159
169
|
test("sendAudioRaw is no-op when ws is not open", async () => {
|
|
160
170
|
const { raw, handle } = await setupHandle();
|
|
161
|
-
raw.readyState = 3;
|
|
171
|
+
raw.readyState = 3;
|
|
162
172
|
|
|
163
173
|
handle.sendAudioRaw('{"type":"input.audio","audio":"abc"}');
|
|
164
174
|
expect(raw.send).not.toHaveBeenCalled();
|
|
@@ -170,7 +180,7 @@ describe("connectS2s", () => {
|
|
|
170
180
|
handle.sendToolResult("call-123", "result-text");
|
|
171
181
|
|
|
172
182
|
expect(raw.send).toHaveBeenCalledOnce();
|
|
173
|
-
const sent =
|
|
183
|
+
const sent = lastSent(raw);
|
|
174
184
|
expect(sent.type).toBe("tool.result");
|
|
175
185
|
expect(sent.call_id).toBe("call-123");
|
|
176
186
|
expect(sent.result).toBe("result-text");
|
|
@@ -182,7 +192,7 @@ describe("connectS2s", () => {
|
|
|
182
192
|
handle.resumeSession("session-abc");
|
|
183
193
|
|
|
184
194
|
expect(raw.send).toHaveBeenCalledOnce();
|
|
185
|
-
const sent =
|
|
195
|
+
const sent = lastSent(raw);
|
|
186
196
|
expect(sent.type).toBe("session.resume");
|
|
187
197
|
expect(sent.session_id).toBe("session-abc");
|
|
188
198
|
});
|
|
@@ -196,27 +206,17 @@ describe("connectS2s", () => {
|
|
|
196
206
|
|
|
197
207
|
test("send is no-op when ws is not open", async () => {
|
|
198
208
|
const { raw, handle } = await setupHandle();
|
|
199
|
-
raw.readyState = 3;
|
|
209
|
+
raw.readyState = 3;
|
|
200
210
|
|
|
201
211
|
handle.updateSession({ systemPrompt: "test", tools: [] });
|
|
202
212
|
expect(raw.send).not.toHaveBeenCalled();
|
|
203
213
|
});
|
|
204
214
|
|
|
205
|
-
// ─── Message dispatch ──────────────────────────────────────────────────
|
|
206
|
-
|
|
207
215
|
test("session.ready dispatches 'onSessionReady' callback", async () => {
|
|
208
216
|
const callbacks = makeMockCallbacks();
|
|
209
217
|
const { raw } = await setupHandle(callbacks);
|
|
210
218
|
|
|
211
|
-
raw.
|
|
212
|
-
"message",
|
|
213
|
-
Buffer.from(
|
|
214
|
-
JSON.stringify({
|
|
215
|
-
type: "session.ready",
|
|
216
|
-
session_id: "s123",
|
|
217
|
-
}),
|
|
218
|
-
),
|
|
219
|
-
);
|
|
219
|
+
emitMessage(raw, { type: "session.ready", session_id: "s123" });
|
|
220
220
|
|
|
221
221
|
expect(callbacks.onSessionReady).toHaveBeenCalledOnce();
|
|
222
222
|
expect(callbacks.onSessionReady).toHaveBeenCalledWith("s123");
|
|
@@ -226,7 +226,7 @@ describe("connectS2s", () => {
|
|
|
226
226
|
const callbacks = makeMockCallbacks();
|
|
227
227
|
const { raw } = await setupHandle(callbacks);
|
|
228
228
|
|
|
229
|
-
raw
|
|
229
|
+
emitMessage(raw, { type: "input.speech.started" });
|
|
230
230
|
|
|
231
231
|
expect(callbacks.onSpeechStarted).toHaveBeenCalledOnce();
|
|
232
232
|
});
|
|
@@ -235,9 +235,9 @@ describe("connectS2s", () => {
|
|
|
235
235
|
const callbacks = makeMockCallbacks();
|
|
236
236
|
const { raw } = await setupHandle(callbacks);
|
|
237
237
|
|
|
238
|
-
//
|
|
239
|
-
raw
|
|
240
|
-
raw
|
|
238
|
+
// speech_stopped is only forwarded after a speech_started primes VAD state.
|
|
239
|
+
emitMessage(raw, { type: "input.speech.started" });
|
|
240
|
+
emitMessage(raw, { type: "input.speech.stopped" });
|
|
241
241
|
|
|
242
242
|
expect(callbacks.onSpeechStarted).toHaveBeenCalledOnce();
|
|
243
243
|
expect(callbacks.onSpeechStopped).toHaveBeenCalledOnce();
|
|
@@ -247,9 +247,9 @@ describe("connectS2s", () => {
|
|
|
247
247
|
const callbacks = makeMockCallbacks();
|
|
248
248
|
const { raw } = await setupHandle(callbacks);
|
|
249
249
|
|
|
250
|
-
raw
|
|
251
|
-
raw
|
|
252
|
-
raw
|
|
250
|
+
emitMessage(raw, { type: "input.speech.started" });
|
|
251
|
+
emitMessage(raw, { type: "input.speech.stopped" });
|
|
252
|
+
emitMessage(raw, { type: "input.speech.stopped" });
|
|
253
253
|
|
|
254
254
|
expect(callbacks.onSpeechStopped).toHaveBeenCalledOnce();
|
|
255
255
|
});
|
|
@@ -258,16 +258,7 @@ describe("connectS2s", () => {
|
|
|
258
258
|
const callbacks = makeMockCallbacks();
|
|
259
259
|
const { raw } = await setupHandle(callbacks);
|
|
260
260
|
|
|
261
|
-
raw.
|
|
262
|
-
"message",
|
|
263
|
-
Buffer.from(
|
|
264
|
-
JSON.stringify({
|
|
265
|
-
type: "transcript.user",
|
|
266
|
-
item_id: "item-1",
|
|
267
|
-
text: "Hello world",
|
|
268
|
-
}),
|
|
269
|
-
),
|
|
270
|
-
);
|
|
261
|
+
emitMessage(raw, { type: "transcript.user", item_id: "item-1", text: "Hello world" });
|
|
271
262
|
|
|
272
263
|
expect(callbacks.onUserTranscript).toHaveBeenCalledOnce();
|
|
273
264
|
expect(callbacks.onUserTranscript).toHaveBeenCalledWith("Hello world");
|
|
@@ -277,15 +268,7 @@ describe("connectS2s", () => {
|
|
|
277
268
|
const callbacks = makeMockCallbacks();
|
|
278
269
|
const { raw } = await setupHandle(callbacks);
|
|
279
270
|
|
|
280
|
-
raw.
|
|
281
|
-
"message",
|
|
282
|
-
Buffer.from(
|
|
283
|
-
JSON.stringify({
|
|
284
|
-
type: "reply.started",
|
|
285
|
-
reply_id: "r1",
|
|
286
|
-
}),
|
|
287
|
-
),
|
|
288
|
-
);
|
|
271
|
+
emitMessage(raw, { type: "reply.started", reply_id: "r1" });
|
|
289
272
|
|
|
290
273
|
expect(callbacks.onReplyStarted).toHaveBeenCalledOnce();
|
|
291
274
|
expect(callbacks.onReplyStarted).toHaveBeenCalledWith("r1");
|
|
@@ -295,18 +278,13 @@ describe("connectS2s", () => {
|
|
|
295
278
|
const callbacks = makeMockCallbacks();
|
|
296
279
|
const { raw } = await setupHandle(callbacks);
|
|
297
280
|
|
|
298
|
-
raw
|
|
299
|
-
"
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
item_id: "i1",
|
|
306
|
-
interrupted: false,
|
|
307
|
-
}),
|
|
308
|
-
),
|
|
309
|
-
);
|
|
281
|
+
emitMessage(raw, {
|
|
282
|
+
type: "transcript.agent",
|
|
283
|
+
text: "Full response",
|
|
284
|
+
reply_id: "r1",
|
|
285
|
+
item_id: "i1",
|
|
286
|
+
interrupted: false,
|
|
287
|
+
});
|
|
310
288
|
|
|
311
289
|
expect(callbacks.onAgentTranscript).toHaveBeenCalledOnce();
|
|
312
290
|
expect(callbacks.onAgentTranscript).toHaveBeenCalledWith("Full response", false);
|
|
@@ -316,10 +294,7 @@ describe("connectS2s", () => {
|
|
|
316
294
|
const callbacks = makeMockCallbacks();
|
|
317
295
|
const { raw } = await setupHandle(callbacks);
|
|
318
296
|
|
|
319
|
-
raw.
|
|
320
|
-
"message",
|
|
321
|
-
Buffer.from(JSON.stringify({ type: "transcript.agent", text: "response" })),
|
|
322
|
-
);
|
|
297
|
+
emitMessage(raw, { type: "transcript.agent", text: "response" });
|
|
323
298
|
|
|
324
299
|
expect(callbacks.onAgentTranscript).toHaveBeenCalledWith("response", false);
|
|
325
300
|
});
|
|
@@ -328,16 +303,11 @@ describe("connectS2s", () => {
|
|
|
328
303
|
const callbacks = makeMockCallbacks();
|
|
329
304
|
const { raw } = await setupHandle(callbacks);
|
|
330
305
|
|
|
331
|
-
raw
|
|
332
|
-
"
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
text: "Interrupted response",
|
|
337
|
-
interrupted: true,
|
|
338
|
-
}),
|
|
339
|
-
),
|
|
340
|
-
);
|
|
306
|
+
emitMessage(raw, {
|
|
307
|
+
type: "transcript.agent",
|
|
308
|
+
text: "Interrupted response",
|
|
309
|
+
interrupted: true,
|
|
310
|
+
});
|
|
341
311
|
|
|
342
312
|
expect(callbacks.onAgentTranscript).toHaveBeenCalledWith("Interrupted response", true);
|
|
343
313
|
});
|
|
@@ -346,17 +316,12 @@ describe("connectS2s", () => {
|
|
|
346
316
|
const callbacks = makeMockCallbacks();
|
|
347
317
|
const { raw } = await setupHandle(callbacks);
|
|
348
318
|
|
|
349
|
-
raw
|
|
350
|
-
"
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
name: "web_search",
|
|
356
|
-
args: { query: "test" },
|
|
357
|
-
}),
|
|
358
|
-
),
|
|
359
|
-
);
|
|
319
|
+
emitMessage(raw, {
|
|
320
|
+
type: "tool.call",
|
|
321
|
+
call_id: "c1",
|
|
322
|
+
name: "web_search",
|
|
323
|
+
args: { query: "test" },
|
|
324
|
+
});
|
|
360
325
|
|
|
361
326
|
expect(callbacks.onToolCall).toHaveBeenCalledOnce();
|
|
362
327
|
expect(callbacks.onToolCall).toHaveBeenCalledWith("c1", "web_search", { query: "test" });
|
|
@@ -366,15 +331,7 @@ describe("connectS2s", () => {
|
|
|
366
331
|
const callbacks = makeMockCallbacks();
|
|
367
332
|
const { raw } = await setupHandle(callbacks);
|
|
368
333
|
|
|
369
|
-
raw.
|
|
370
|
-
"message",
|
|
371
|
-
Buffer.from(
|
|
372
|
-
JSON.stringify({
|
|
373
|
-
type: "reply.done",
|
|
374
|
-
status: "completed",
|
|
375
|
-
}),
|
|
376
|
-
),
|
|
377
|
-
);
|
|
334
|
+
emitMessage(raw, { type: "reply.done", status: "completed" });
|
|
378
335
|
|
|
379
336
|
expect(callbacks.onReplyDone).toHaveBeenCalledOnce();
|
|
380
337
|
expect(callbacks.onCancelled).not.toHaveBeenCalled();
|
|
@@ -384,15 +341,7 @@ describe("connectS2s", () => {
|
|
|
384
341
|
const callbacks = makeMockCallbacks();
|
|
385
342
|
const { raw } = await setupHandle(callbacks);
|
|
386
343
|
|
|
387
|
-
raw.
|
|
388
|
-
"message",
|
|
389
|
-
Buffer.from(
|
|
390
|
-
JSON.stringify({
|
|
391
|
-
type: "reply.done",
|
|
392
|
-
status: "interrupted",
|
|
393
|
-
}),
|
|
394
|
-
),
|
|
395
|
-
);
|
|
344
|
+
emitMessage(raw, { type: "reply.done", status: "interrupted" });
|
|
396
345
|
|
|
397
346
|
expect(callbacks.onCancelled).toHaveBeenCalledOnce();
|
|
398
347
|
expect(callbacks.onReplyDone).not.toHaveBeenCalled();
|
|
@@ -411,7 +360,7 @@ describe("connectS2s", () => {
|
|
|
411
360
|
sid: "sess-abc",
|
|
412
361
|
});
|
|
413
362
|
|
|
414
|
-
raw
|
|
363
|
+
emitMessage(raw, { type: "reply.done", status: "completed" });
|
|
415
364
|
|
|
416
365
|
const arrivalCall = infoSpy.mock.calls.find((c) => c[0] === "S2S << reply.done");
|
|
417
366
|
expect(arrivalCall).toBeDefined();
|
|
@@ -422,16 +371,11 @@ describe("connectS2s", () => {
|
|
|
422
371
|
const callbacks = makeMockCallbacks();
|
|
423
372
|
const { raw } = await setupHandle(callbacks);
|
|
424
373
|
|
|
425
|
-
raw
|
|
426
|
-
"
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
code: "session_not_found",
|
|
431
|
-
message: "Session not found",
|
|
432
|
-
}),
|
|
433
|
-
),
|
|
434
|
-
);
|
|
374
|
+
emitMessage(raw, {
|
|
375
|
+
type: "session.error",
|
|
376
|
+
code: "session_not_found",
|
|
377
|
+
message: "Session not found",
|
|
378
|
+
});
|
|
435
379
|
|
|
436
380
|
expect(callbacks.onSessionExpired).toHaveBeenCalledOnce();
|
|
437
381
|
});
|
|
@@ -440,16 +384,11 @@ describe("connectS2s", () => {
|
|
|
440
384
|
const callbacks = makeMockCallbacks();
|
|
441
385
|
const { raw } = await setupHandle(callbacks);
|
|
442
386
|
|
|
443
|
-
raw
|
|
444
|
-
"
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
code: "session_forbidden",
|
|
449
|
-
message: "Forbidden",
|
|
450
|
-
}),
|
|
451
|
-
),
|
|
452
|
-
);
|
|
387
|
+
emitMessage(raw, {
|
|
388
|
+
type: "session.error",
|
|
389
|
+
code: "session_forbidden",
|
|
390
|
+
message: "Forbidden",
|
|
391
|
+
});
|
|
453
392
|
|
|
454
393
|
expect(callbacks.onSessionExpired).toHaveBeenCalledOnce();
|
|
455
394
|
});
|
|
@@ -458,19 +397,14 @@ describe("connectS2s", () => {
|
|
|
458
397
|
const callbacks = makeMockCallbacks();
|
|
459
398
|
const { raw } = await setupHandle(callbacks);
|
|
460
399
|
|
|
461
|
-
raw
|
|
462
|
-
"
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
code: "rate_limit",
|
|
467
|
-
message: "Too many requests",
|
|
468
|
-
}),
|
|
469
|
-
),
|
|
470
|
-
);
|
|
400
|
+
emitMessage(raw, {
|
|
401
|
+
type: "session.error",
|
|
402
|
+
code: "rate_limit",
|
|
403
|
+
message: "Too many requests",
|
|
404
|
+
});
|
|
471
405
|
|
|
472
406
|
expect(callbacks.onError).toHaveBeenCalledOnce();
|
|
473
|
-
const err = (callbacks
|
|
407
|
+
const err = errorArg(callbacks);
|
|
474
408
|
expect(err).toBeInstanceOf(Error);
|
|
475
409
|
expect(err.message).toBe("Too many requests");
|
|
476
410
|
});
|
|
@@ -479,24 +413,14 @@ describe("connectS2s", () => {
|
|
|
479
413
|
const callbacks = makeMockCallbacks();
|
|
480
414
|
const { raw } = await setupHandle(callbacks);
|
|
481
415
|
|
|
482
|
-
raw
|
|
483
|
-
"message",
|
|
484
|
-
Buffer.from(
|
|
485
|
-
JSON.stringify({
|
|
486
|
-
type: "error",
|
|
487
|
-
message: "Bad gateway",
|
|
488
|
-
}),
|
|
489
|
-
),
|
|
490
|
-
);
|
|
416
|
+
emitMessage(raw, { type: "error", message: "Bad gateway" });
|
|
491
417
|
|
|
492
418
|
expect(callbacks.onError).toHaveBeenCalledOnce();
|
|
493
|
-
const err = (callbacks
|
|
419
|
+
const err = errorArg(callbacks);
|
|
494
420
|
expect(err).toBeInstanceOf(Error);
|
|
495
421
|
expect(err.message).toBe("Bad gateway");
|
|
496
422
|
});
|
|
497
423
|
|
|
498
|
-
// ─── Audio fast path ───────────────────────────────────────────────────
|
|
499
|
-
|
|
500
424
|
test("reply.audio dispatches 'onAudio' callback with decoded Uint8Array", async () => {
|
|
501
425
|
const callbacks = makeMockCallbacks();
|
|
502
426
|
const { raw } = await setupHandle(callbacks);
|
|
@@ -504,15 +428,7 @@ describe("connectS2s", () => {
|
|
|
504
428
|
const audioBytes = new Uint8Array([10, 20, 30, 40]);
|
|
505
429
|
const base64 = Buffer.from(audioBytes).toString("base64");
|
|
506
430
|
|
|
507
|
-
raw.
|
|
508
|
-
"message",
|
|
509
|
-
Buffer.from(
|
|
510
|
-
JSON.stringify({
|
|
511
|
-
type: "reply.audio",
|
|
512
|
-
data: base64,
|
|
513
|
-
}),
|
|
514
|
-
),
|
|
515
|
-
);
|
|
431
|
+
emitMessage(raw, { type: "reply.audio", data: base64 });
|
|
516
432
|
|
|
517
433
|
expect(callbacks.onAudio).toHaveBeenCalledOnce();
|
|
518
434
|
const payload = (callbacks.onAudio as ReturnType<typeof vi.fn>).mock.calls[0]?.[0];
|
|
@@ -520,8 +436,6 @@ describe("connectS2s", () => {
|
|
|
520
436
|
expect(Array.from(payload)).toEqual([10, 20, 30, 40]);
|
|
521
437
|
});
|
|
522
438
|
|
|
523
|
-
// ─── Edge cases ────────────────────────────────────────────────────────
|
|
524
|
-
|
|
525
439
|
test("invalid JSON message is logged and ignored", async () => {
|
|
526
440
|
const { raw, logger } = await setupHandle();
|
|
527
441
|
|
|
@@ -533,47 +447,23 @@ describe("connectS2s", () => {
|
|
|
533
447
|
test("unrecognized message type is logged and ignored", async () => {
|
|
534
448
|
const { raw, logger } = await setupHandle();
|
|
535
449
|
|
|
536
|
-
raw.
|
|
537
|
-
"message",
|
|
538
|
-
Buffer.from(
|
|
539
|
-
JSON.stringify({
|
|
540
|
-
type: "totally.unknown.type",
|
|
541
|
-
}),
|
|
542
|
-
),
|
|
543
|
-
);
|
|
450
|
+
emitMessage(raw, { type: "totally.unknown.type" });
|
|
544
451
|
|
|
545
452
|
expect(logger.warn).toHaveBeenCalled();
|
|
546
453
|
});
|
|
547
454
|
|
|
548
455
|
test("reply.content_part events are silently ignored (no dispatch)", async () => {
|
|
549
456
|
const { raw } = await setupHandle();
|
|
550
|
-
|
|
551
|
-
raw.
|
|
552
|
-
"message",
|
|
553
|
-
Buffer.from(
|
|
554
|
-
JSON.stringify({
|
|
555
|
-
type: "reply.content_part.started",
|
|
556
|
-
}),
|
|
557
|
-
),
|
|
558
|
-
);
|
|
559
|
-
raw.emit(
|
|
560
|
-
"message",
|
|
561
|
-
Buffer.from(
|
|
562
|
-
JSON.stringify({
|
|
563
|
-
type: "reply.content_part.done",
|
|
564
|
-
}),
|
|
565
|
-
),
|
|
566
|
-
);
|
|
567
|
-
// No error thrown = pass
|
|
457
|
+
emitMessage(raw, { type: "reply.content_part.started" });
|
|
458
|
+
emitMessage(raw, { type: "reply.content_part.done" });
|
|
568
459
|
});
|
|
569
460
|
|
|
570
|
-
test("session.updated is silently ignored (no dispatch)", async () => {
|
|
461
|
+
test("session.updated without config.id is silently ignored (no dispatch)", async () => {
|
|
571
462
|
const callbacks = makeMockCallbacks();
|
|
572
463
|
const { raw } = await setupHandle(callbacks);
|
|
573
464
|
|
|
574
|
-
raw
|
|
465
|
+
emitMessage(raw, { type: "session.updated" });
|
|
575
466
|
|
|
576
|
-
// session.updated is dropped — no callbacks fired
|
|
577
467
|
expect(callbacks.onSessionReady).not.toHaveBeenCalled();
|
|
578
468
|
expect(callbacks.onReplyStarted).not.toHaveBeenCalled();
|
|
579
469
|
expect(callbacks.onReplyDone).not.toHaveBeenCalled();
|
|
@@ -581,7 +471,18 @@ describe("connectS2s", () => {
|
|
|
581
471
|
expect(callbacks.onSpeechStopped).not.toHaveBeenCalled();
|
|
582
472
|
});
|
|
583
473
|
|
|
584
|
-
|
|
474
|
+
test("session.updated with config.id dispatches 'onSessionReady' callback", async () => {
|
|
475
|
+
const callbacks = makeMockCallbacks();
|
|
476
|
+
const { raw } = await setupHandle(callbacks);
|
|
477
|
+
|
|
478
|
+
emitMessage(raw, {
|
|
479
|
+
type: "session.updated",
|
|
480
|
+
config: { id: "sess_from_updated", system_prompt: "x", tools: [] },
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
expect(callbacks.onSessionReady).toHaveBeenCalledOnce();
|
|
484
|
+
expect(callbacks.onSessionReady).toHaveBeenCalledWith("sess_from_updated");
|
|
485
|
+
});
|
|
585
486
|
|
|
586
487
|
test("close event dispatches 'onClose' callback with code and reason", async () => {
|
|
587
488
|
const callbacks = makeMockCallbacks();
|
|
@@ -600,7 +501,7 @@ describe("connectS2s", () => {
|
|
|
600
501
|
raw.emit("error", new Error("ws transport error"));
|
|
601
502
|
|
|
602
503
|
expect(callbacks.onError).toHaveBeenCalledOnce();
|
|
603
|
-
const err = (callbacks
|
|
504
|
+
const err = errorArg(callbacks);
|
|
604
505
|
expect(err).toBeInstanceOf(Error);
|
|
605
506
|
expect(err.message).toBe("ws transport error");
|
|
606
507
|
});
|