@chaoslabs/ai-sdk 0.0.5 → 0.0.7

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.
@@ -1,1401 +0,0 @@
1
- /**
2
- * HTTP Streaming Tests (TDD - RED Phase)
3
- *
4
- * These tests verify the http-based streaming implementation that will replace fetch.
5
- * Tests are written BEFORE the implementation (TDD RED phase).
6
- *
7
- * The implementation will use Node's native http/https modules for streaming
8
- * instead of fetch to ensure proper incremental streaming behavior.
9
- *
10
- * KEY TESTS THAT MUST FAIL INITIALLY:
11
- * - httpStreamRequest function (doesn't exist yet)
12
- * - StreamingHttpClient class (doesn't exist yet)
13
- * - useNativeHttp option (doesn't exist yet)
14
- */
15
- import { describe, it, expect, beforeAll, afterAll, afterEach } from "bun:test";
16
- import * as http from "node:http";
17
- import { Chaos, WALLET_MODEL } from "../client";
18
- import { ChaosError, ChaosTimeoutError } from "../types";
19
- // ============================================================================
20
- // Dynamic Import for TDD - Module Doesn't Exist Yet
21
- // ============================================================================
22
- // These will be undefined until we implement the http streaming module
23
- let httpStreamRequest;
24
- let StreamingHttpClient;
25
- // Try to import the module - it won't exist until implementation
26
- try {
27
- // @ts-expect-error - Module doesn't exist yet (TDD RED phase)
28
- const httpStreaming = await import("../http-streaming");
29
- httpStreamRequest = httpStreaming.httpStreamRequest;
30
- StreamingHttpClient = httpStreaming.StreamingHttpClient;
31
- }
32
- catch {
33
- // Expected to fail until module is implemented
34
- httpStreamRequest = undefined;
35
- StreamingHttpClient = undefined;
36
- }
37
- // ============================================================================
38
- // Test Utilities
39
- // ============================================================================
40
- /**
41
- * Creates a mock HTTP server that streams NDJSON responses.
42
- */
43
- function createMockServer(handler) {
44
- return http.createServer(handler);
45
- }
46
- /**
47
- * Creates a valid stream message for testing.
48
- */
49
- function createStreamMessage(id, type, content) {
50
- return {
51
- id,
52
- type,
53
- timestamp: Date.now(),
54
- content,
55
- context: {
56
- sessionId: "test-session",
57
- artifactId: "test-artifact",
58
- },
59
- };
60
- }
61
- /**
62
- * Creates a valid NDJSON line from a stream message.
63
- */
64
- function toNDJSON(msg) {
65
- return JSON.stringify(msg) + "\n";
66
- }
67
- // ============================================================================
68
- // Test Suite: Incremental Streaming
69
- // ============================================================================
70
- describe("HTTP Streaming - Incremental Event Delivery", () => {
71
- let server;
72
- let serverPort;
73
- beforeAll(async () => {
74
- server = createMockServer((req, res) => {
75
- res.writeHead(200, {
76
- "Content-Type": "application/x-ndjson",
77
- "Transfer-Encoding": "chunked",
78
- });
79
- // Send messages with delays to simulate real streaming
80
- const messages = [
81
- createStreamMessage("1", "agent_status_change", { status: "processing" }),
82
- createStreamMessage("2", "agent_message", {
83
- messageId: "m1",
84
- data: { message: "First message" },
85
- }),
86
- createStreamMessage("3", "agent_message", {
87
- messageId: "m2",
88
- data: { message: "Second message" },
89
- }),
90
- createStreamMessage("4", "report", {
91
- id: "r1",
92
- type: "table",
93
- data: { blockType: "table", title: "Test", tableHeaders: [], tableRows: [] },
94
- }),
95
- createStreamMessage("5", "agent_status_change", { status: "done" }),
96
- ];
97
- let index = 0;
98
- const sendNext = () => {
99
- if (index < messages.length) {
100
- res.write(toNDJSON(messages[index]));
101
- index++;
102
- setTimeout(sendNext, 50); // 50ms delay between messages
103
- }
104
- else {
105
- res.end();
106
- }
107
- };
108
- sendNext();
109
- });
110
- await new Promise((resolve) => {
111
- server.listen(0, () => {
112
- const addr = server.address();
113
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
114
- resolve();
115
- });
116
- });
117
- });
118
- afterAll(() => {
119
- server.close();
120
- });
121
- it("fires onStreamEvent callback for each message as data arrives", async () => {
122
- const chaos = new Chaos({
123
- apiKey: "test-api-key",
124
- baseUrl: `http://localhost:${serverPort}`,
125
- timeout: 10000,
126
- });
127
- const receivedEvents = [];
128
- const receivedTimestamps = [];
129
- await chaos.chat.responses.create({
130
- model: WALLET_MODEL,
131
- input: [{ type: "message", role: "user", content: "Test query" }],
132
- metadata: {
133
- user_id: "test-user",
134
- session_id: "test-session",
135
- wallet_id: "0x123",
136
- },
137
- onStreamEvent: (event) => {
138
- receivedEvents.push(event);
139
- receivedTimestamps.push(Date.now());
140
- },
141
- });
142
- // Should receive all 5 messages
143
- expect(receivedEvents.length).toBe(5);
144
- // Events should arrive incrementally (with time gaps between them)
145
- // If they arrived all at once, timestamps would be nearly identical
146
- for (let i = 1; i < receivedTimestamps.length; i++) {
147
- const timeDiff = receivedTimestamps[i] - receivedTimestamps[i - 1];
148
- // Should have at least 20ms gap (server sends with 50ms delay)
149
- expect(timeDiff).toBeGreaterThanOrEqual(20);
150
- }
151
- // Verify message types in order
152
- expect(receivedEvents[0].type).toBe("agent_status_change");
153
- expect(receivedEvents[1].type).toBe("agent_message");
154
- expect(receivedEvents[2].type).toBe("agent_message");
155
- expect(receivedEvents[3].type).toBe("report");
156
- expect(receivedEvents[4].type).toBe("agent_status_change");
157
- });
158
- it("does not buffer all events before calling callback", async () => {
159
- const chaos = new Chaos({
160
- apiKey: "test-api-key",
161
- baseUrl: `http://localhost:${serverPort}`,
162
- timeout: 10000,
163
- });
164
- let firstEventTime = null;
165
- let lastEventTime = null;
166
- let eventCount = 0;
167
- const startTime = Date.now();
168
- await chaos.chat.responses.create({
169
- model: WALLET_MODEL,
170
- input: [{ type: "message", role: "user", content: "Test query" }],
171
- metadata: {
172
- user_id: "test-user",
173
- session_id: "test-session",
174
- wallet_id: "0x123",
175
- },
176
- onStreamEvent: () => {
177
- const now = Date.now();
178
- if (firstEventTime === null) {
179
- firstEventTime = now;
180
- }
181
- lastEventTime = now;
182
- eventCount++;
183
- },
184
- });
185
- // Total time should be close to 5 messages * 50ms = 250ms
186
- const totalTime = lastEventTime - firstEventTime;
187
- // If events were buffered, totalTime would be near 0
188
- // With streaming, it should be at least 150ms (accounting for some variance)
189
- expect(totalTime).toBeGreaterThanOrEqual(150);
190
- expect(eventCount).toBe(5);
191
- });
192
- });
193
- // ============================================================================
194
- // Test Suite: Chunked Data Handling
195
- // ============================================================================
196
- describe("HTTP Streaming - Chunked Data Handling", () => {
197
- let server;
198
- let serverPort;
199
- afterEach(() => {
200
- if (server) {
201
- server.close();
202
- }
203
- });
204
- it("handles partial JSON lines split across multiple data events", async () => {
205
- // Create a message that will be split across chunks
206
- const fullMessage = createStreamMessage("1", "agent_message", {
207
- messageId: "m1",
208
- data: { message: "This is a complete message that was split" },
209
- });
210
- const fullLine = JSON.stringify(fullMessage);
211
- // Split the line at various points
212
- const chunk1 = fullLine.substring(0, 20);
213
- const chunk2 = fullLine.substring(20, 50);
214
- const chunk3 = fullLine.substring(50) + "\n";
215
- server = createMockServer((req, res) => {
216
- res.writeHead(200, {
217
- "Content-Type": "application/x-ndjson",
218
- "Transfer-Encoding": "chunked",
219
- });
220
- // Send chunks with delays
221
- res.write(chunk1);
222
- setTimeout(() => res.write(chunk2), 20);
223
- setTimeout(() => res.write(chunk3), 40);
224
- setTimeout(() => res.end(), 60);
225
- });
226
- await new Promise((resolve) => {
227
- server.listen(0, () => {
228
- const addr = server.address();
229
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
230
- resolve();
231
- });
232
- });
233
- const chaos = new Chaos({
234
- apiKey: "test-api-key",
235
- baseUrl: `http://localhost:${serverPort}`,
236
- timeout: 10000,
237
- });
238
- const receivedEvents = [];
239
- await chaos.chat.responses.create({
240
- model: WALLET_MODEL,
241
- input: [{ type: "message", role: "user", content: "Test query" }],
242
- metadata: {
243
- user_id: "test-user",
244
- session_id: "test-session",
245
- wallet_id: "0x123",
246
- },
247
- onStreamEvent: (event) => {
248
- receivedEvents.push(event);
249
- },
250
- });
251
- // Should correctly reassemble the split message
252
- expect(receivedEvents.length).toBe(1);
253
- expect(receivedEvents[0].id).toBe("1");
254
- expect(receivedEvents[0].type).toBe("agent_message");
255
- const content = receivedEvents[0].content;
256
- expect(content.data.message).toBe("This is a complete message that was split");
257
- });
258
- it("handles multiple complete messages in a single chunk", async () => {
259
- const msg1 = createStreamMessage("1", "agent_status_change", { status: "processing" });
260
- const msg2 = createStreamMessage("2", "agent_message", {
261
- messageId: "m1",
262
- data: { message: "Hello" },
263
- });
264
- const msg3 = createStreamMessage("3", "agent_status_change", { status: "done" });
265
- // All messages in one chunk
266
- const allMessages = toNDJSON(msg1) + toNDJSON(msg2) + toNDJSON(msg3);
267
- server = createMockServer((req, res) => {
268
- res.writeHead(200, {
269
- "Content-Type": "application/x-ndjson",
270
- "Transfer-Encoding": "chunked",
271
- });
272
- res.write(allMessages);
273
- res.end();
274
- });
275
- await new Promise((resolve) => {
276
- server.listen(0, () => {
277
- const addr = server.address();
278
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
279
- resolve();
280
- });
281
- });
282
- const chaos = new Chaos({
283
- apiKey: "test-api-key",
284
- baseUrl: `http://localhost:${serverPort}`,
285
- timeout: 10000,
286
- });
287
- const receivedEvents = [];
288
- await chaos.chat.responses.create({
289
- model: WALLET_MODEL,
290
- input: [{ type: "message", role: "user", content: "Test query" }],
291
- metadata: {
292
- user_id: "test-user",
293
- session_id: "test-session",
294
- wallet_id: "0x123",
295
- },
296
- onStreamEvent: (event) => {
297
- receivedEvents.push(event);
298
- },
299
- });
300
- expect(receivedEvents.length).toBe(3);
301
- expect(receivedEvents[0].id).toBe("1");
302
- expect(receivedEvents[1].id).toBe("2");
303
- expect(receivedEvents[2].id).toBe("3");
304
- });
305
- it("handles messages split mid-UTF8 character", async () => {
306
- // Message with unicode characters that might be split mid-character
307
- const fullMessage = createStreamMessage("1", "agent_message", {
308
- messageId: "m1",
309
- data: { message: "Hello \u{1F4B0} and \u{1F680} tokens!" },
310
- });
311
- const fullLine = JSON.stringify(fullMessage) + "\n";
312
- // Convert to buffer and split at byte level (might split UTF-8 sequences)
313
- const buffer = Buffer.from(fullLine, "utf8");
314
- const midPoint = Math.floor(buffer.length / 2);
315
- const chunk1 = buffer.subarray(0, midPoint);
316
- const chunk2 = buffer.subarray(midPoint);
317
- server = createMockServer((req, res) => {
318
- res.writeHead(200, {
319
- "Content-Type": "application/x-ndjson",
320
- "Transfer-Encoding": "chunked",
321
- });
322
- res.write(chunk1);
323
- setTimeout(() => {
324
- res.write(chunk2);
325
- res.end();
326
- }, 30);
327
- });
328
- await new Promise((resolve) => {
329
- server.listen(0, () => {
330
- const addr = server.address();
331
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
332
- resolve();
333
- });
334
- });
335
- const chaos = new Chaos({
336
- apiKey: "test-api-key",
337
- baseUrl: `http://localhost:${serverPort}`,
338
- timeout: 10000,
339
- });
340
- const receivedEvents = [];
341
- await chaos.chat.responses.create({
342
- model: WALLET_MODEL,
343
- input: [{ type: "message", role: "user", content: "Test query" }],
344
- metadata: {
345
- user_id: "test-user",
346
- session_id: "test-session",
347
- wallet_id: "0x123",
348
- },
349
- onStreamEvent: (event) => {
350
- receivedEvents.push(event);
351
- },
352
- });
353
- expect(receivedEvents.length).toBe(1);
354
- const content = receivedEvents[0].content;
355
- expect(content.data.message).toContain("\u{1F4B0}");
356
- expect(content.data.message).toContain("\u{1F680}");
357
- });
358
- });
359
- // ============================================================================
360
- // Test Suite: Timeout Handling
361
- // ============================================================================
362
- describe("HTTP Streaming - Timeout Handling", () => {
363
- let server;
364
- let serverPort;
365
- afterEach(() => {
366
- if (server) {
367
- server.close();
368
- }
369
- });
370
- it("throws ChaosTimeoutError when request exceeds timeout", async () => {
371
- server = createMockServer((req, res) => {
372
- res.writeHead(200, {
373
- "Content-Type": "application/x-ndjson",
374
- "Transfer-Encoding": "chunked",
375
- });
376
- // Send initial data then hang
377
- const msg = createStreamMessage("1", "agent_status_change", { status: "processing" });
378
- res.write(toNDJSON(msg));
379
- // Never send more data - let it timeout
380
- });
381
- await new Promise((resolve) => {
382
- server.listen(0, () => {
383
- const addr = server.address();
384
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
385
- resolve();
386
- });
387
- });
388
- const chaos = new Chaos({
389
- apiKey: "test-api-key",
390
- baseUrl: `http://localhost:${serverPort}`,
391
- timeout: 200, // Very short timeout
392
- });
393
- await expect(chaos.chat.responses.create({
394
- model: WALLET_MODEL,
395
- input: [{ type: "message", role: "user", content: "Test query" }],
396
- metadata: {
397
- user_id: "test-user",
398
- session_id: "test-session",
399
- wallet_id: "0x123",
400
- },
401
- })).rejects.toThrow(ChaosTimeoutError);
402
- });
403
- it("includes timeout duration in error message", async () => {
404
- server = createMockServer((req, res) => {
405
- res.writeHead(200, {
406
- "Content-Type": "application/x-ndjson",
407
- "Transfer-Encoding": "chunked",
408
- });
409
- // Hang indefinitely
410
- });
411
- await new Promise((resolve) => {
412
- server.listen(0, () => {
413
- const addr = server.address();
414
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
415
- resolve();
416
- });
417
- });
418
- const chaos = new Chaos({
419
- apiKey: "test-api-key",
420
- baseUrl: `http://localhost:${serverPort}`,
421
- timeout: 300,
422
- });
423
- try {
424
- await chaos.chat.responses.create({
425
- model: WALLET_MODEL,
426
- input: [{ type: "message", role: "user", content: "Test query" }],
427
- metadata: {
428
- user_id: "test-user",
429
- session_id: "test-session",
430
- wallet_id: "0x123",
431
- },
432
- });
433
- expect.fail("Should have thrown");
434
- }
435
- catch (error) {
436
- expect(error).toBeInstanceOf(ChaosTimeoutError);
437
- expect(error.message).toContain("300");
438
- }
439
- });
440
- it("does not timeout when data arrives within timeout window", async () => {
441
- server = createMockServer((req, res) => {
442
- res.writeHead(200, {
443
- "Content-Type": "application/x-ndjson",
444
- "Transfer-Encoding": "chunked",
445
- });
446
- // Send messages quickly
447
- const msg1 = createStreamMessage("1", "agent_status_change", { status: "processing" });
448
- const msg2 = createStreamMessage("2", "agent_status_change", { status: "done" });
449
- res.write(toNDJSON(msg1));
450
- setTimeout(() => {
451
- res.write(toNDJSON(msg2));
452
- res.end();
453
- }, 50);
454
- });
455
- await new Promise((resolve) => {
456
- server.listen(0, () => {
457
- const addr = server.address();
458
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
459
- resolve();
460
- });
461
- });
462
- const chaos = new Chaos({
463
- apiKey: "test-api-key",
464
- baseUrl: `http://localhost:${serverPort}`,
465
- timeout: 5000,
466
- });
467
- const receivedEvents = [];
468
- // Should complete without throwing
469
- await chaos.chat.responses.create({
470
- model: WALLET_MODEL,
471
- input: [{ type: "message", role: "user", content: "Test query" }],
472
- metadata: {
473
- user_id: "test-user",
474
- session_id: "test-session",
475
- wallet_id: "0x123",
476
- },
477
- onStreamEvent: (event) => {
478
- receivedEvents.push(event);
479
- },
480
- });
481
- expect(receivedEvents.length).toBe(2);
482
- });
483
- });
484
- // ============================================================================
485
- // Test Suite: HTTP Error Handling
486
- // ============================================================================
487
- describe("HTTP Streaming - HTTP Error Handling", () => {
488
- let server;
489
- let serverPort;
490
- afterEach(() => {
491
- if (server) {
492
- server.close();
493
- }
494
- });
495
- it("throws ChaosError with status code for 4xx responses", async () => {
496
- server = createMockServer((req, res) => {
497
- res.writeHead(401, { "Content-Type": "application/json" });
498
- res.end(JSON.stringify({ error: "Unauthorized" }));
499
- });
500
- await new Promise((resolve) => {
501
- server.listen(0, () => {
502
- const addr = server.address();
503
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
504
- resolve();
505
- });
506
- });
507
- const chaos = new Chaos({
508
- apiKey: "invalid-key",
509
- baseUrl: `http://localhost:${serverPort}`,
510
- });
511
- try {
512
- await chaos.chat.responses.create({
513
- model: WALLET_MODEL,
514
- input: [{ type: "message", role: "user", content: "Test query" }],
515
- metadata: {
516
- user_id: "test-user",
517
- session_id: "test-session",
518
- wallet_id: "0x123",
519
- },
520
- });
521
- expect.fail("Should have thrown");
522
- }
523
- catch (error) {
524
- expect(error).toBeInstanceOf(ChaosError);
525
- expect(error.status).toBe(401);
526
- }
527
- });
528
- it("throws ChaosError with status code for 5xx responses", async () => {
529
- server = createMockServer((req, res) => {
530
- res.writeHead(503, { "Content-Type": "application/json" });
531
- res.end(JSON.stringify({ error: "Service Unavailable" }));
532
- });
533
- await new Promise((resolve) => {
534
- server.listen(0, () => {
535
- const addr = server.address();
536
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
537
- resolve();
538
- });
539
- });
540
- const chaos = new Chaos({
541
- apiKey: "test-api-key",
542
- baseUrl: `http://localhost:${serverPort}`,
543
- });
544
- try {
545
- await chaos.chat.responses.create({
546
- model: WALLET_MODEL,
547
- input: [{ type: "message", role: "user", content: "Test query" }],
548
- metadata: {
549
- user_id: "test-user",
550
- session_id: "test-session",
551
- wallet_id: "0x123",
552
- },
553
- });
554
- expect.fail("Should have thrown");
555
- }
556
- catch (error) {
557
- expect(error).toBeInstanceOf(ChaosError);
558
- expect(error.status).toBe(503);
559
- }
560
- });
561
- it("includes HTTP status text in error message", async () => {
562
- server = createMockServer((req, res) => {
563
- res.writeHead(429, "Too Many Requests", { "Content-Type": "application/json" });
564
- res.end(JSON.stringify({ error: "Rate limited" }));
565
- });
566
- await new Promise((resolve) => {
567
- server.listen(0, () => {
568
- const addr = server.address();
569
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
570
- resolve();
571
- });
572
- });
573
- const chaos = new Chaos({
574
- apiKey: "test-api-key",
575
- baseUrl: `http://localhost:${serverPort}`,
576
- });
577
- try {
578
- await chaos.chat.responses.create({
579
- model: WALLET_MODEL,
580
- input: [{ type: "message", role: "user", content: "Test query" }],
581
- metadata: {
582
- user_id: "test-user",
583
- session_id: "test-session",
584
- wallet_id: "0x123",
585
- },
586
- });
587
- expect.fail("Should have thrown");
588
- }
589
- catch (error) {
590
- expect(error).toBeInstanceOf(ChaosError);
591
- expect(error.message).toContain("429");
592
- }
593
- });
594
- });
595
- // ============================================================================
596
- // Test Suite: Connection Error Handling
597
- // ============================================================================
598
- describe("HTTP Streaming - Connection Error Handling", () => {
599
- it("throws ChaosError when server is unreachable", async () => {
600
- const chaos = new Chaos({
601
- apiKey: "test-api-key",
602
- baseUrl: "http://localhost:59999", // Port that is not listening
603
- timeout: 5000,
604
- });
605
- await expect(chaos.chat.responses.create({
606
- model: WALLET_MODEL,
607
- input: [{ type: "message", role: "user", content: "Test query" }],
608
- metadata: {
609
- user_id: "test-user",
610
- session_id: "test-session",
611
- wallet_id: "0x123",
612
- },
613
- })).rejects.toThrow(ChaosError);
614
- });
615
- it("handles connection reset mid-stream", async () => {
616
- const server = createMockServer((req, res) => {
617
- res.writeHead(200, {
618
- "Content-Type": "application/x-ndjson",
619
- "Transfer-Encoding": "chunked",
620
- });
621
- const msg = createStreamMessage("1", "agent_status_change", { status: "processing" });
622
- res.write(toNDJSON(msg));
623
- // Destroy the socket to simulate connection reset
624
- setTimeout(() => {
625
- req.socket?.destroy();
626
- }, 50);
627
- });
628
- await new Promise((resolve) => {
629
- server.listen(0, () => resolve());
630
- });
631
- const addr = server.address();
632
- const serverPort = typeof addr === "object" && addr ? addr.port : 0;
633
- const chaos = new Chaos({
634
- apiKey: "test-api-key",
635
- baseUrl: `http://localhost:${serverPort}`,
636
- timeout: 5000,
637
- });
638
- try {
639
- await chaos.chat.responses.create({
640
- model: WALLET_MODEL,
641
- input: [{ type: "message", role: "user", content: "Test query" }],
642
- metadata: {
643
- user_id: "test-user",
644
- session_id: "test-session",
645
- wallet_id: "0x123",
646
- },
647
- });
648
- expect.fail("Should have thrown");
649
- }
650
- catch (error) {
651
- expect(error).toBeInstanceOf(ChaosError);
652
- }
653
- finally {
654
- server.close();
655
- }
656
- });
657
- it("handles DNS resolution failure", async () => {
658
- const chaos = new Chaos({
659
- apiKey: "test-api-key",
660
- baseUrl: "http://this-domain-does-not-exist-12345.invalid",
661
- timeout: 5000,
662
- });
663
- await expect(chaos.chat.responses.create({
664
- model: WALLET_MODEL,
665
- input: [{ type: "message", role: "user", content: "Test query" }],
666
- metadata: {
667
- user_id: "test-user",
668
- session_id: "test-session",
669
- wallet_id: "0x123",
670
- },
671
- })).rejects.toThrow(ChaosError);
672
- });
673
- });
674
- // ============================================================================
675
- // Test Suite: Protocol Support
676
- // ============================================================================
677
- describe("HTTP Streaming - Protocol Support", () => {
678
- let httpServer;
679
- let httpPort;
680
- afterEach(() => {
681
- if (httpServer) {
682
- httpServer.close();
683
- }
684
- });
685
- it("works with http:// URLs", async () => {
686
- httpServer = createMockServer((req, res) => {
687
- res.writeHead(200, {
688
- "Content-Type": "application/x-ndjson",
689
- "Transfer-Encoding": "chunked",
690
- });
691
- const msg1 = createStreamMessage("1", "agent_status_change", { status: "processing" });
692
- const msg2 = createStreamMessage("2", "agent_status_change", { status: "done" });
693
- res.write(toNDJSON(msg1));
694
- res.write(toNDJSON(msg2));
695
- res.end();
696
- });
697
- await new Promise((resolve) => {
698
- httpServer.listen(0, () => {
699
- const addr = httpServer.address();
700
- httpPort = typeof addr === "object" && addr ? addr.port : 0;
701
- resolve();
702
- });
703
- });
704
- const chaos = new Chaos({
705
- apiKey: "test-api-key",
706
- baseUrl: `http://localhost:${httpPort}`,
707
- });
708
- const receivedEvents = [];
709
- await chaos.chat.responses.create({
710
- model: WALLET_MODEL,
711
- input: [{ type: "message", role: "user", content: "Test query" }],
712
- metadata: {
713
- user_id: "test-user",
714
- session_id: "test-session",
715
- wallet_id: "0x123",
716
- },
717
- onStreamEvent: (event) => {
718
- receivedEvents.push(event);
719
- },
720
- });
721
- expect(receivedEvents.length).toBe(2);
722
- });
723
- it("uses correct protocol module based on URL scheme", async () => {
724
- // This test verifies that http:// URLs use the http module
725
- // and https:// URLs would use the https module
726
- // Since we can't easily test https without certificates in unit tests,
727
- // we verify http works and document that https should work identically
728
- httpServer = createMockServer((req, res) => {
729
- // Verify the request came through correctly
730
- expect(req.method).toBe("POST");
731
- expect(req.headers["content-type"]).toBe("application/json");
732
- expect(req.headers["authorization"]).toContain("Bearer");
733
- res.writeHead(200, {
734
- "Content-Type": "application/x-ndjson",
735
- });
736
- const msg = createStreamMessage("1", "agent_status_change", { status: "done" });
737
- res.write(toNDJSON(msg));
738
- res.end();
739
- });
740
- await new Promise((resolve) => {
741
- httpServer.listen(0, () => {
742
- const addr = httpServer.address();
743
- httpPort = typeof addr === "object" && addr ? addr.port : 0;
744
- resolve();
745
- });
746
- });
747
- const chaos = new Chaos({
748
- apiKey: "test-api-key",
749
- baseUrl: `http://localhost:${httpPort}`,
750
- });
751
- // Should complete without error
752
- const response = await chaos.chat.responses.create({
753
- model: WALLET_MODEL,
754
- input: [{ type: "message", role: "user", content: "Test query" }],
755
- metadata: {
756
- user_id: "test-user",
757
- session_id: "test-session",
758
- wallet_id: "0x123",
759
- },
760
- });
761
- expect(response).toBeDefined();
762
- expect(response.status).toBe("completed");
763
- });
764
- it("handles URL with port in baseUrl", async () => {
765
- httpServer = createMockServer((req, res) => {
766
- res.writeHead(200, {
767
- "Content-Type": "application/x-ndjson",
768
- });
769
- const msg = createStreamMessage("1", "agent_status_change", { status: "done" });
770
- res.write(toNDJSON(msg));
771
- res.end();
772
- });
773
- await new Promise((resolve) => {
774
- httpServer.listen(0, () => {
775
- const addr = httpServer.address();
776
- httpPort = typeof addr === "object" && addr ? addr.port : 0;
777
- resolve();
778
- });
779
- });
780
- const chaos = new Chaos({
781
- apiKey: "test-api-key",
782
- baseUrl: `http://localhost:${httpPort}`,
783
- });
784
- const response = await chaos.chat.responses.create({
785
- model: WALLET_MODEL,
786
- input: [{ type: "message", role: "user", content: "Test query" }],
787
- metadata: {
788
- user_id: "test-user",
789
- session_id: "test-session",
790
- wallet_id: "0x123",
791
- },
792
- });
793
- expect(response).toBeDefined();
794
- });
795
- it("handles URL with path in baseUrl", async () => {
796
- httpServer = createMockServer((req, res) => {
797
- // The path should include the base path plus the API endpoint
798
- expect(req.url).toContain("/api");
799
- res.writeHead(200, {
800
- "Content-Type": "application/x-ndjson",
801
- });
802
- const msg = createStreamMessage("1", "agent_status_change", { status: "done" });
803
- res.write(toNDJSON(msg));
804
- res.end();
805
- });
806
- await new Promise((resolve) => {
807
- httpServer.listen(0, () => {
808
- const addr = httpServer.address();
809
- httpPort = typeof addr === "object" && addr ? addr.port : 0;
810
- resolve();
811
- });
812
- });
813
- const chaos = new Chaos({
814
- apiKey: "test-api-key",
815
- baseUrl: `http://localhost:${httpPort}/api`,
816
- });
817
- const response = await chaos.chat.responses.create({
818
- model: WALLET_MODEL,
819
- input: [{ type: "message", role: "user", content: "Test query" }],
820
- metadata: {
821
- user_id: "test-user",
822
- session_id: "test-session",
823
- wallet_id: "0x123",
824
- },
825
- });
826
- expect(response).toBeDefined();
827
- });
828
- });
829
- // ============================================================================
830
- // Test Suite: Request Cancellation
831
- // ============================================================================
832
- describe("HTTP Streaming - Request Cancellation", () => {
833
- let server;
834
- let serverPort;
835
- afterEach(() => {
836
- if (server) {
837
- server.close();
838
- }
839
- });
840
- it("can cancel an in-progress streaming request", async () => {
841
- let requestReceived = false;
842
- server = createMockServer((req, res) => {
843
- requestReceived = true;
844
- res.writeHead(200, {
845
- "Content-Type": "application/x-ndjson",
846
- "Transfer-Encoding": "chunked",
847
- });
848
- const msg = createStreamMessage("1", "agent_status_change", { status: "processing" });
849
- res.write(toNDJSON(msg));
850
- // Keep sending data slowly
851
- const interval = setInterval(() => {
852
- const msg = createStreamMessage("2", "agent_message", {
853
- messageId: "m1",
854
- data: { message: "Still processing..." },
855
- });
856
- try {
857
- res.write(toNDJSON(msg));
858
- }
859
- catch {
860
- clearInterval(interval);
861
- }
862
- }, 100);
863
- req.on("close", () => {
864
- clearInterval(interval);
865
- });
866
- });
867
- await new Promise((resolve) => {
868
- server.listen(0, () => {
869
- const addr = server.address();
870
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
871
- resolve();
872
- });
873
- });
874
- const chaos = new Chaos({
875
- apiKey: "test-api-key",
876
- baseUrl: `http://localhost:${serverPort}`,
877
- timeout: 10000,
878
- });
879
- const receivedEvents = [];
880
- // Start the request but cancel it after receiving some events
881
- const requestPromise = chaos.chat.responses.create({
882
- model: WALLET_MODEL,
883
- input: [{ type: "message", role: "user", content: "Test query" }],
884
- metadata: {
885
- user_id: "test-user",
886
- session_id: "test-session",
887
- wallet_id: "0x123",
888
- },
889
- onStreamEvent: (event) => {
890
- receivedEvents.push(event);
891
- if (receivedEvents.length >= 2) {
892
- // Cancel after receiving 2 events
893
- chaos.chat.responses.cancel();
894
- }
895
- },
896
- });
897
- // The request should either throw or complete early
898
- try {
899
- await requestPromise;
900
- }
901
- catch (error) {
902
- // Cancellation may throw an error, which is acceptable
903
- expect(error).toBeInstanceOf(Error);
904
- }
905
- expect(requestReceived).toBe(true);
906
- // Should have received some events before cancellation
907
- expect(receivedEvents.length).toBeGreaterThanOrEqual(1);
908
- });
909
- });
910
- // ============================================================================
911
- // Test Suite: Low-Level HTTP Streaming Module (TDD - Will Fail Until Implemented)
912
- // ============================================================================
913
- describe("HTTP Streaming Module - httpStreamRequest Function", () => {
914
- let server;
915
- let serverPort;
916
- afterEach(() => {
917
- if (server) {
918
- server.close();
919
- }
920
- });
921
- it("exports httpStreamRequest function", () => {
922
- // This test will fail because the module doesn't exist yet
923
- expect(typeof httpStreamRequest).toBe("function");
924
- });
925
- it("httpStreamRequest returns an async iterator of chunks", async () => {
926
- server = createMockServer((req, res) => {
927
- res.writeHead(200, {
928
- "Content-Type": "application/x-ndjson",
929
- "Transfer-Encoding": "chunked",
930
- });
931
- const msg = createStreamMessage("1", "agent_status_change", { status: "done" });
932
- res.write(toNDJSON(msg));
933
- res.end();
934
- });
935
- await new Promise((resolve) => {
936
- server.listen(0, () => {
937
- const addr = server.address();
938
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
939
- resolve();
940
- });
941
- });
942
- const chunks = [];
943
- const iterator = httpStreamRequest({
944
- url: `http://localhost:${serverPort}/v1/chat/stream`,
945
- method: "POST",
946
- headers: {
947
- "Content-Type": "application/json",
948
- Authorization: "Bearer test-key",
949
- },
950
- body: JSON.stringify({ query: "test" }),
951
- timeout: 5000,
952
- });
953
- for await (const chunk of iterator) {
954
- chunks.push(chunk);
955
- }
956
- expect(chunks.length).toBeGreaterThan(0);
957
- expect(chunks.join("")).toContain("agent_status_change");
958
- });
959
- it("httpStreamRequest yields chunks as they arrive (not buffered)", async () => {
960
- server = createMockServer((req, res) => {
961
- res.writeHead(200, {
962
- "Content-Type": "application/x-ndjson",
963
- "Transfer-Encoding": "chunked",
964
- });
965
- const messages = [
966
- createStreamMessage("1", "agent_status_change", { status: "processing" }),
967
- createStreamMessage("2", "agent_message", { data: { message: "Hello" } }),
968
- createStreamMessage("3", "agent_status_change", { status: "done" }),
969
- ];
970
- let index = 0;
971
- const sendNext = () => {
972
- if (index < messages.length) {
973
- res.write(toNDJSON(messages[index]));
974
- index++;
975
- setTimeout(sendNext, 100);
976
- }
977
- else {
978
- res.end();
979
- }
980
- };
981
- sendNext();
982
- });
983
- await new Promise((resolve) => {
984
- server.listen(0, () => {
985
- const addr = server.address();
986
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
987
- resolve();
988
- });
989
- });
990
- const chunkTimestamps = [];
991
- const iterator = httpStreamRequest({
992
- url: `http://localhost:${serverPort}/v1/chat/stream`,
993
- method: "POST",
994
- headers: { "Content-Type": "application/json" },
995
- body: "{}",
996
- timeout: 5000,
997
- });
998
- for await (const chunk of iterator) {
999
- chunkTimestamps.push(Date.now());
1000
- }
1001
- // Chunks should arrive with time gaps (streaming, not buffered)
1002
- expect(chunkTimestamps.length).toBeGreaterThanOrEqual(2);
1003
- for (let i = 1; i < chunkTimestamps.length; i++) {
1004
- const gap = chunkTimestamps[i] - chunkTimestamps[i - 1];
1005
- // Should have at least some gap (>50ms) if truly streaming
1006
- expect(gap).toBeGreaterThanOrEqual(50);
1007
- }
1008
- });
1009
- it("httpStreamRequest throws on connection error", async () => {
1010
- await expect((async () => {
1011
- const iterator = httpStreamRequest({
1012
- url: "http://localhost:59999/nonexistent",
1013
- method: "POST",
1014
- headers: {},
1015
- body: "{}",
1016
- timeout: 1000,
1017
- });
1018
- for await (const _ of iterator) {
1019
- // Should not reach here
1020
- }
1021
- })()).rejects.toThrow();
1022
- });
1023
- it("httpStreamRequest throws on HTTP error status", async () => {
1024
- server = createMockServer((req, res) => {
1025
- res.writeHead(500, { "Content-Type": "application/json" });
1026
- res.end(JSON.stringify({ error: "Internal Server Error" }));
1027
- });
1028
- await new Promise((resolve) => {
1029
- server.listen(0, () => {
1030
- const addr = server.address();
1031
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
1032
- resolve();
1033
- });
1034
- });
1035
- await expect((async () => {
1036
- const iterator = httpStreamRequest({
1037
- url: `http://localhost:${serverPort}/v1/chat/stream`,
1038
- method: "POST",
1039
- headers: {},
1040
- body: "{}",
1041
- timeout: 5000,
1042
- });
1043
- for await (const _ of iterator) {
1044
- // Should throw before iterating
1045
- }
1046
- })()).rejects.toThrow();
1047
- });
1048
- it("httpStreamRequest respects timeout", async () => {
1049
- server = createMockServer((req, res) => {
1050
- res.writeHead(200, {
1051
- "Content-Type": "application/x-ndjson",
1052
- "Transfer-Encoding": "chunked",
1053
- });
1054
- // Send one chunk then hang
1055
- res.write('{"partial": true}\n');
1056
- // Never end the response
1057
- });
1058
- await new Promise((resolve) => {
1059
- server.listen(0, () => {
1060
- const addr = server.address();
1061
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
1062
- resolve();
1063
- });
1064
- });
1065
- const startTime = Date.now();
1066
- await expect((async () => {
1067
- const iterator = httpStreamRequest({
1068
- url: `http://localhost:${serverPort}/v1/chat/stream`,
1069
- method: "POST",
1070
- headers: {},
1071
- body: "{}",
1072
- timeout: 300,
1073
- });
1074
- for await (const _ of iterator) {
1075
- // Should timeout
1076
- }
1077
- })()).rejects.toThrow();
1078
- const elapsed = Date.now() - startTime;
1079
- // Should timeout around 300ms, with some tolerance
1080
- expect(elapsed).toBeLessThan(1000);
1081
- expect(elapsed).toBeGreaterThanOrEqual(250);
1082
- });
1083
- });
1084
- describe("HTTP Streaming Module - StreamingHttpClient Class", () => {
1085
- let server;
1086
- let serverPort;
1087
- afterEach(() => {
1088
- if (server) {
1089
- server.close();
1090
- }
1091
- });
1092
- it("exports StreamingHttpClient class", () => {
1093
- expect(typeof StreamingHttpClient).toBe("function");
1094
- });
1095
- it("StreamingHttpClient.stream returns async iterator", async () => {
1096
- server = createMockServer((req, res) => {
1097
- res.writeHead(200, { "Content-Type": "application/x-ndjson" });
1098
- const msg = createStreamMessage("1", "agent_status_change", { status: "done" });
1099
- res.write(toNDJSON(msg));
1100
- res.end();
1101
- });
1102
- await new Promise((resolve) => {
1103
- server.listen(0, () => {
1104
- const addr = server.address();
1105
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
1106
- resolve();
1107
- });
1108
- });
1109
- const client = new StreamingHttpClient({
1110
- baseUrl: `http://localhost:${serverPort}`,
1111
- timeout: 5000,
1112
- });
1113
- const chunks = [];
1114
- for await (const chunk of client.stream("/v1/chat/stream", {
1115
- method: "POST",
1116
- headers: { Authorization: "Bearer test" },
1117
- body: "{}",
1118
- })) {
1119
- chunks.push(chunk);
1120
- }
1121
- expect(chunks.length).toBeGreaterThan(0);
1122
- });
1123
- it("StreamingHttpClient.abort cancels in-progress request", async () => {
1124
- server = createMockServer((req, res) => {
1125
- res.writeHead(200, {
1126
- "Content-Type": "application/x-ndjson",
1127
- "Transfer-Encoding": "chunked",
1128
- });
1129
- const msg = createStreamMessage("1", "agent_status_change", { status: "processing" });
1130
- res.write(toNDJSON(msg));
1131
- // Keep streaming indefinitely
1132
- const interval = setInterval(() => {
1133
- try {
1134
- res.write('{"keep": "alive"}\n');
1135
- }
1136
- catch {
1137
- clearInterval(interval);
1138
- }
1139
- }, 50);
1140
- req.on("close", () => {
1141
- clearInterval(interval);
1142
- });
1143
- });
1144
- await new Promise((resolve) => {
1145
- server.listen(0, () => {
1146
- const addr = server.address();
1147
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
1148
- resolve();
1149
- });
1150
- });
1151
- const client = new StreamingHttpClient({
1152
- baseUrl: `http://localhost:${serverPort}`,
1153
- timeout: 10000,
1154
- });
1155
- let chunkCount = 0;
1156
- const abortPromise = new Promise((resolve) => {
1157
- setTimeout(() => {
1158
- client.abort();
1159
- resolve();
1160
- }, 200);
1161
- });
1162
- try {
1163
- for await (const chunk of client.stream("/v1/chat/stream", {
1164
- method: "POST",
1165
- body: "{}",
1166
- })) {
1167
- chunkCount++;
1168
- if (chunkCount > 10)
1169
- break; // Safety limit
1170
- }
1171
- }
1172
- catch (error) {
1173
- // Abort should cause an error
1174
- expect(error).toBeInstanceOf(Error);
1175
- }
1176
- await abortPromise;
1177
- expect(chunkCount).toBeLessThan(10);
1178
- });
1179
- it("StreamingHttpClient handles https:// URLs", async () => {
1180
- // Note: This test documents the expected behavior for HTTPS
1181
- // In practice, testing HTTPS requires certificates
1182
- const client = new StreamingHttpClient({
1183
- baseUrl: "https://example.com",
1184
- timeout: 5000,
1185
- });
1186
- // The client should use https module for https:// URLs
1187
- // We can verify this by checking internal state or behavior
1188
- expect(client).toBeDefined();
1189
- // The actual HTTPS test would require a real HTTPS server
1190
- // For now, we document that it should work
1191
- });
1192
- it("StreamingHttpClient parses NDJSON lines correctly", async () => {
1193
- server = createMockServer((req, res) => {
1194
- res.writeHead(200, { "Content-Type": "application/x-ndjson" });
1195
- const msg1 = createStreamMessage("1", "agent_status_change", { status: "processing" });
1196
- const msg2 = createStreamMessage("2", "agent_message", {
1197
- data: { message: "Hello world" },
1198
- });
1199
- const msg3 = createStreamMessage("3", "agent_status_change", { status: "done" });
1200
- // Send with slight delays
1201
- res.write(toNDJSON(msg1));
1202
- setTimeout(() => res.write(toNDJSON(msg2)), 30);
1203
- setTimeout(() => {
1204
- res.write(toNDJSON(msg3));
1205
- res.end();
1206
- }, 60);
1207
- });
1208
- await new Promise((resolve) => {
1209
- server.listen(0, () => {
1210
- const addr = server.address();
1211
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
1212
- resolve();
1213
- });
1214
- });
1215
- const client = new StreamingHttpClient({
1216
- baseUrl: `http://localhost:${serverPort}`,
1217
- timeout: 5000,
1218
- });
1219
- const lines = [];
1220
- for await (const line of client.streamLines("/v1/chat/stream", {
1221
- method: "POST",
1222
- body: "{}",
1223
- })) {
1224
- lines.push(line);
1225
- }
1226
- expect(lines.length).toBe(3);
1227
- expect(JSON.parse(lines[0]).id).toBe("1");
1228
- expect(JSON.parse(lines[1]).id).toBe("2");
1229
- expect(JSON.parse(lines[2]).id).toBe("3");
1230
- });
1231
- });
1232
- describe("HTTP Streaming Module - Chaos Client Integration", () => {
1233
- let server;
1234
- let serverPort;
1235
- afterEach(() => {
1236
- if (server) {
1237
- server.close();
1238
- }
1239
- });
1240
- it("Chaos client accepts useNativeHttp option in config", () => {
1241
- // This test will fail until we add the useNativeHttp option
1242
- const chaos = new Chaos({
1243
- apiKey: "test-api-key",
1244
- baseUrl: "http://localhost:3000",
1245
- // @ts-expect-error - Option doesn't exist yet (TDD RED phase)
1246
- useNativeHttp: true,
1247
- });
1248
- // The config should accept this option
1249
- expect(chaos).toBeDefined();
1250
- // @ts-expect-error - Property doesn't exist yet
1251
- expect(chaos.useNativeHttp).toBe(true);
1252
- });
1253
- it("Chaos client uses http module when useNativeHttp is true", async () => {
1254
- let requestMethod;
1255
- let connectionHeader;
1256
- server = createMockServer((req, res) => {
1257
- requestMethod = req.method;
1258
- connectionHeader = req.headers["connection"];
1259
- res.writeHead(200, { "Content-Type": "application/x-ndjson" });
1260
- const msg = createStreamMessage("1", "agent_status_change", { status: "done" });
1261
- res.write(toNDJSON(msg));
1262
- res.end();
1263
- });
1264
- await new Promise((resolve) => {
1265
- server.listen(0, () => {
1266
- const addr = server.address();
1267
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
1268
- resolve();
1269
- });
1270
- });
1271
- const chaos = new Chaos({
1272
- apiKey: "test-api-key",
1273
- baseUrl: `http://localhost:${serverPort}`,
1274
- // @ts-expect-error - Option doesn't exist yet (TDD RED phase)
1275
- useNativeHttp: true,
1276
- });
1277
- await chaos.chat.responses.create({
1278
- model: WALLET_MODEL,
1279
- input: [{ type: "message", role: "user", content: "Test" }],
1280
- metadata: {
1281
- user_id: "test-user",
1282
- session_id: "test-session",
1283
- wallet_id: "0x123",
1284
- },
1285
- });
1286
- expect(requestMethod).toBe("POST");
1287
- // When using native http, we should see the connection header handled differently
1288
- // This is implementation-specific and may need adjustment
1289
- });
1290
- it("getStreamingClient returns StreamingHttpClient instance", () => {
1291
- const chaos = new Chaos({
1292
- apiKey: "test-api-key",
1293
- baseUrl: "http://localhost:3000",
1294
- });
1295
- // @ts-expect-error - Method doesn't exist yet
1296
- const streamingClient = chaos.getStreamingClient();
1297
- expect(streamingClient).toBeInstanceOf(StreamingHttpClient);
1298
- });
1299
- });
1300
- describe("HTTP Streaming Module - Edge Cases", () => {
1301
- let server;
1302
- let serverPort;
1303
- afterEach(() => {
1304
- if (server) {
1305
- server.close();
1306
- }
1307
- });
1308
- it("handles empty response body gracefully", async () => {
1309
- server = createMockServer((req, res) => {
1310
- res.writeHead(200, { "Content-Type": "application/x-ndjson" });
1311
- res.end(); // Empty body
1312
- });
1313
- await new Promise((resolve) => {
1314
- server.listen(0, () => {
1315
- const addr = server.address();
1316
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
1317
- resolve();
1318
- });
1319
- });
1320
- const chunks = [];
1321
- const iterator = httpStreamRequest({
1322
- url: `http://localhost:${serverPort}/v1/chat/stream`,
1323
- method: "POST",
1324
- headers: {},
1325
- body: "{}",
1326
- timeout: 5000,
1327
- });
1328
- for await (const chunk of iterator) {
1329
- chunks.push(chunk);
1330
- }
1331
- // Should complete without error, with zero or minimal chunks
1332
- expect(chunks.length).toBeLessThanOrEqual(1);
1333
- });
1334
- it("handles very large chunks", async () => {
1335
- const largeData = "x".repeat(100000); // 100KB of data
1336
- const largeMessage = createStreamMessage("1", "agent_message", {
1337
- data: { message: largeData },
1338
- });
1339
- server = createMockServer((req, res) => {
1340
- res.writeHead(200, { "Content-Type": "application/x-ndjson" });
1341
- res.write(toNDJSON(largeMessage));
1342
- res.end();
1343
- });
1344
- await new Promise((resolve) => {
1345
- server.listen(0, () => {
1346
- const addr = server.address();
1347
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
1348
- resolve();
1349
- });
1350
- });
1351
- const allData = [];
1352
- const iterator = httpStreamRequest({
1353
- url: `http://localhost:${serverPort}/v1/chat/stream`,
1354
- method: "POST",
1355
- headers: {},
1356
- body: "{}",
1357
- timeout: 10000,
1358
- });
1359
- for await (const chunk of iterator) {
1360
- allData.push(chunk);
1361
- }
1362
- const combined = allData.join("");
1363
- expect(combined).toContain(largeData);
1364
- });
1365
- it("handles rapid successive requests", async () => {
1366
- let requestCount = 0;
1367
- server = createMockServer((req, res) => {
1368
- requestCount++;
1369
- res.writeHead(200, { "Content-Type": "application/x-ndjson" });
1370
- const msg = createStreamMessage(String(requestCount), "agent_status_change", {
1371
- status: "done",
1372
- });
1373
- res.write(toNDJSON(msg));
1374
- res.end();
1375
- });
1376
- await new Promise((resolve) => {
1377
- server.listen(0, () => {
1378
- const addr = server.address();
1379
- serverPort = typeof addr === "object" && addr ? addr.port : 0;
1380
- resolve();
1381
- });
1382
- });
1383
- const client = new StreamingHttpClient({
1384
- baseUrl: `http://localhost:${serverPort}`,
1385
- timeout: 5000,
1386
- });
1387
- // Make 5 rapid requests
1388
- const results = await Promise.all(Array.from({ length: 5 }, async (_, i) => {
1389
- const chunks = [];
1390
- for await (const chunk of client.stream("/v1/chat/stream", {
1391
- method: "POST",
1392
- body: JSON.stringify({ request: i }),
1393
- })) {
1394
- chunks.push(chunk);
1395
- }
1396
- return chunks.join("");
1397
- }));
1398
- expect(results.length).toBe(5);
1399
- expect(requestCount).toBe(5);
1400
- });
1401
- });