@chaoslabs/ai-sdk 0.0.3 → 0.0.5
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/README.md +46 -1
- package/dist/__tests__/http-streaming.test.d.ts +15 -0
- package/dist/__tests__/http-streaming.test.js +1401 -0
- package/dist/__tests__/stream.test.d.ts +1 -0
- package/dist/__tests__/stream.test.js +345 -0
- package/dist/__tests__/trivial.test.d.ts +1 -0
- package/dist/__tests__/trivial.test.js +6 -0
- package/dist/client.d.ts +21 -5
- package/dist/client.js +75 -29
- package/dist/http-streaming.d.ts +55 -0
- package/dist/http-streaming.js +359 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.js +3 -26
- package/dist/schemas.d.ts +97 -405
- package/dist/schemas.js +18 -47
- package/dist/stream.d.ts +32 -0
- package/dist/stream.js +127 -0
- package/dist/types.d.ts +11 -0
- package/package.json +2 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { describe, it, expect } from "bun:test";
|
|
2
|
+
import { isAgentStatusMessage, isAgentMessage, isReportMessage, isFollowUpSuggestions, isUserInputMessage, parseAgentStatus, isTerminalStatus, extractAgentMessageText, extractSuggestions, extractReportBlock, parseStreamLine, parseStreamLines, } from "../stream";
|
|
3
|
+
describe("isAgentStatusMessage", () => {
|
|
4
|
+
it("returns true for agent_status_change message", () => {
|
|
5
|
+
const msg = {
|
|
6
|
+
id: "1",
|
|
7
|
+
type: "agent_status_change",
|
|
8
|
+
timestamp: Date.now(),
|
|
9
|
+
content: { status: "processing" },
|
|
10
|
+
context: {
|
|
11
|
+
sessionId: "session-1",
|
|
12
|
+
artifactId: "artifact-1",
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
expect(isAgentStatusMessage(msg)).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
it("returns false for other message types", () => {
|
|
18
|
+
const msg = {
|
|
19
|
+
id: "2",
|
|
20
|
+
type: "agent_message",
|
|
21
|
+
timestamp: Date.now(),
|
|
22
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
23
|
+
context: {
|
|
24
|
+
sessionId: "session-1",
|
|
25
|
+
artifactId: "artifact-1",
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
expect(isAgentStatusMessage(msg)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe("isAgentMessage", () => {
|
|
32
|
+
it("returns true for agent_message type", () => {
|
|
33
|
+
const msg = {
|
|
34
|
+
id: "1",
|
|
35
|
+
type: "agent_message",
|
|
36
|
+
timestamp: Date.now(),
|
|
37
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
38
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
39
|
+
};
|
|
40
|
+
expect(isAgentMessage(msg)).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
it("returns false for other message types", () => {
|
|
43
|
+
const msg = {
|
|
44
|
+
id: "1",
|
|
45
|
+
type: "report",
|
|
46
|
+
timestamp: Date.now(),
|
|
47
|
+
content: { id: "r1", type: "table", data: {} },
|
|
48
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
49
|
+
};
|
|
50
|
+
expect(isAgentMessage(msg)).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe("isReportMessage", () => {
|
|
54
|
+
it("returns true for report type", () => {
|
|
55
|
+
const msg = {
|
|
56
|
+
id: "1",
|
|
57
|
+
type: "report",
|
|
58
|
+
timestamp: Date.now(),
|
|
59
|
+
content: { id: "r1", type: "table", data: {} },
|
|
60
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
61
|
+
};
|
|
62
|
+
expect(isReportMessage(msg)).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
it("returns false for other message types", () => {
|
|
65
|
+
const msg = {
|
|
66
|
+
id: "1",
|
|
67
|
+
type: "agent_message",
|
|
68
|
+
timestamp: Date.now(),
|
|
69
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
70
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
71
|
+
};
|
|
72
|
+
expect(isReportMessage(msg)).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe("isFollowUpSuggestions", () => {
|
|
76
|
+
it("returns true for follow_up_suggestions type", () => {
|
|
77
|
+
const msg = {
|
|
78
|
+
id: "1",
|
|
79
|
+
type: "follow_up_suggestions",
|
|
80
|
+
timestamp: Date.now(),
|
|
81
|
+
content: { suggestions: ["Question 1?", "Question 2?"] },
|
|
82
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
83
|
+
};
|
|
84
|
+
expect(isFollowUpSuggestions(msg)).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
it("returns false for other message types", () => {
|
|
87
|
+
const msg = {
|
|
88
|
+
id: "1",
|
|
89
|
+
type: "agent_message",
|
|
90
|
+
timestamp: Date.now(),
|
|
91
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
92
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
93
|
+
};
|
|
94
|
+
expect(isFollowUpSuggestions(msg)).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe("isUserInputMessage", () => {
|
|
98
|
+
it("returns true for user_input type", () => {
|
|
99
|
+
const msg = {
|
|
100
|
+
id: "1",
|
|
101
|
+
type: "user_input",
|
|
102
|
+
timestamp: Date.now(),
|
|
103
|
+
content: { query: "What is my balance?" },
|
|
104
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
105
|
+
};
|
|
106
|
+
expect(isUserInputMessage(msg)).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
it("returns false for other message types", () => {
|
|
109
|
+
const msg = {
|
|
110
|
+
id: "1",
|
|
111
|
+
type: "agent_message",
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
114
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
115
|
+
};
|
|
116
|
+
expect(isUserInputMessage(msg)).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
describe("parseAgentStatus", () => {
|
|
120
|
+
it("extracts status from agent_status_change message", () => {
|
|
121
|
+
const msg = {
|
|
122
|
+
id: "1",
|
|
123
|
+
type: "agent_status_change",
|
|
124
|
+
timestamp: Date.now(),
|
|
125
|
+
content: { status: "processing" },
|
|
126
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
127
|
+
};
|
|
128
|
+
expect(parseAgentStatus(msg)).toBe("processing");
|
|
129
|
+
});
|
|
130
|
+
it("extracts done status", () => {
|
|
131
|
+
const msg = {
|
|
132
|
+
id: "1",
|
|
133
|
+
type: "agent_status_change",
|
|
134
|
+
timestamp: Date.now(),
|
|
135
|
+
content: { status: "done" },
|
|
136
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
137
|
+
};
|
|
138
|
+
expect(parseAgentStatus(msg)).toBe("done");
|
|
139
|
+
});
|
|
140
|
+
it("returns null for non-status messages", () => {
|
|
141
|
+
const msg = {
|
|
142
|
+
id: "1",
|
|
143
|
+
type: "agent_message",
|
|
144
|
+
timestamp: Date.now(),
|
|
145
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
146
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
147
|
+
};
|
|
148
|
+
expect(parseAgentStatus(msg)).toBeNull();
|
|
149
|
+
});
|
|
150
|
+
it("returns null for invalid content", () => {
|
|
151
|
+
const msg = {
|
|
152
|
+
id: "1",
|
|
153
|
+
type: "agent_status_change",
|
|
154
|
+
timestamp: Date.now(),
|
|
155
|
+
content: { notStatus: "wrong" },
|
|
156
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
157
|
+
};
|
|
158
|
+
expect(parseAgentStatus(msg)).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
describe("isTerminalStatus", () => {
|
|
162
|
+
it("returns true for done status", () => {
|
|
163
|
+
expect(isTerminalStatus("done")).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
it("returns true for error status", () => {
|
|
166
|
+
expect(isTerminalStatus("error")).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
it("returns true for cancelled status", () => {
|
|
169
|
+
expect(isTerminalStatus("cancelled")).toBe(true);
|
|
170
|
+
});
|
|
171
|
+
it("returns false for processing status", () => {
|
|
172
|
+
expect(isTerminalStatus("processing")).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
describe("extractAgentMessageText", () => {
|
|
176
|
+
it("extracts message text from agent_message", () => {
|
|
177
|
+
const msg = {
|
|
178
|
+
id: "1",
|
|
179
|
+
type: "agent_message",
|
|
180
|
+
timestamp: Date.now(),
|
|
181
|
+
content: { messageId: "m1", data: { message: "Hello world" } },
|
|
182
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
183
|
+
};
|
|
184
|
+
expect(extractAgentMessageText(msg)).toBe("Hello world");
|
|
185
|
+
});
|
|
186
|
+
it("returns null for non-agent_message types", () => {
|
|
187
|
+
const msg = {
|
|
188
|
+
id: "1",
|
|
189
|
+
type: "report",
|
|
190
|
+
timestamp: Date.now(),
|
|
191
|
+
content: { id: "r1", type: "table", data: {} },
|
|
192
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
193
|
+
};
|
|
194
|
+
expect(extractAgentMessageText(msg)).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
it("returns null for invalid content structure", () => {
|
|
197
|
+
const msg = {
|
|
198
|
+
id: "1",
|
|
199
|
+
type: "agent_message",
|
|
200
|
+
timestamp: Date.now(),
|
|
201
|
+
content: { messageId: "m1" }, // missing data.message
|
|
202
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
203
|
+
};
|
|
204
|
+
expect(extractAgentMessageText(msg)).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
describe("extractSuggestions", () => {
|
|
208
|
+
it("extracts suggestions array from follow_up_suggestions", () => {
|
|
209
|
+
const msg = {
|
|
210
|
+
id: "1",
|
|
211
|
+
type: "follow_up_suggestions",
|
|
212
|
+
timestamp: Date.now(),
|
|
213
|
+
content: { suggestions: ["Question 1?", "Question 2?"] },
|
|
214
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
215
|
+
};
|
|
216
|
+
expect(extractSuggestions(msg)).toEqual(["Question 1?", "Question 2?"]);
|
|
217
|
+
});
|
|
218
|
+
it("returns empty array for non-follow_up_suggestions types", () => {
|
|
219
|
+
const msg = {
|
|
220
|
+
id: "1",
|
|
221
|
+
type: "agent_message",
|
|
222
|
+
timestamp: Date.now(),
|
|
223
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
224
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
225
|
+
};
|
|
226
|
+
expect(extractSuggestions(msg)).toEqual([]);
|
|
227
|
+
});
|
|
228
|
+
it("returns empty array for invalid content structure", () => {
|
|
229
|
+
const msg = {
|
|
230
|
+
id: "1",
|
|
231
|
+
type: "follow_up_suggestions",
|
|
232
|
+
timestamp: Date.now(),
|
|
233
|
+
content: { notSuggestions: "wrong" },
|
|
234
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
235
|
+
};
|
|
236
|
+
expect(extractSuggestions(msg)).toEqual([]);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
describe("extractReportBlock", () => {
|
|
240
|
+
it("extracts data from report message", () => {
|
|
241
|
+
const reportData = { columns: ["name"], rows: [["Alice"]] };
|
|
242
|
+
const msg = {
|
|
243
|
+
id: "1",
|
|
244
|
+
type: "report",
|
|
245
|
+
timestamp: Date.now(),
|
|
246
|
+
content: { id: "r1", type: "table", data: reportData },
|
|
247
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
248
|
+
};
|
|
249
|
+
expect(extractReportBlock(msg)).toEqual(reportData);
|
|
250
|
+
});
|
|
251
|
+
it("returns null for non-report types", () => {
|
|
252
|
+
const msg = {
|
|
253
|
+
id: "1",
|
|
254
|
+
type: "agent_message",
|
|
255
|
+
timestamp: Date.now(),
|
|
256
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
257
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
258
|
+
};
|
|
259
|
+
expect(extractReportBlock(msg)).toBeNull();
|
|
260
|
+
});
|
|
261
|
+
it("returns null for invalid content structure", () => {
|
|
262
|
+
const msg = {
|
|
263
|
+
id: "1",
|
|
264
|
+
type: "report",
|
|
265
|
+
timestamp: Date.now(),
|
|
266
|
+
content: { id: "r1", type: "table" }, // missing data
|
|
267
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
268
|
+
};
|
|
269
|
+
expect(extractReportBlock(msg)).toBeNull();
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
describe("parseStreamLine", () => {
|
|
273
|
+
it("parses valid NDJSON line to StreamMessage", () => {
|
|
274
|
+
const msg = {
|
|
275
|
+
id: "1",
|
|
276
|
+
type: "agent_message",
|
|
277
|
+
timestamp: 1234567890,
|
|
278
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
279
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
280
|
+
};
|
|
281
|
+
const line = JSON.stringify(msg);
|
|
282
|
+
const result = parseStreamLine(line);
|
|
283
|
+
expect(result).toEqual(msg);
|
|
284
|
+
});
|
|
285
|
+
it("returns null for empty line", () => {
|
|
286
|
+
expect(parseStreamLine("")).toBeNull();
|
|
287
|
+
});
|
|
288
|
+
it("returns null for whitespace-only line", () => {
|
|
289
|
+
expect(parseStreamLine(" ")).toBeNull();
|
|
290
|
+
});
|
|
291
|
+
it("returns null for invalid JSON", () => {
|
|
292
|
+
expect(parseStreamLine("{not valid json}")).toBeNull();
|
|
293
|
+
});
|
|
294
|
+
it("returns null for missing required fields", () => {
|
|
295
|
+
expect(parseStreamLine('{"id": "1"}')).toBeNull();
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
describe("parseStreamLines", () => {
|
|
299
|
+
it("parses multiple NDJSON lines", () => {
|
|
300
|
+
const msg1 = {
|
|
301
|
+
id: "1",
|
|
302
|
+
type: "agent_status_change",
|
|
303
|
+
timestamp: 1234567890,
|
|
304
|
+
content: { status: "processing" },
|
|
305
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
306
|
+
};
|
|
307
|
+
const msg2 = {
|
|
308
|
+
id: "2",
|
|
309
|
+
type: "agent_message",
|
|
310
|
+
timestamp: 1234567891,
|
|
311
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
312
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
313
|
+
};
|
|
314
|
+
const text = JSON.stringify(msg1) + "\n" + JSON.stringify(msg2);
|
|
315
|
+
const result = parseStreamLines(text);
|
|
316
|
+
expect(result).toEqual([msg1, msg2]);
|
|
317
|
+
});
|
|
318
|
+
it("returns empty array for empty string", () => {
|
|
319
|
+
expect(parseStreamLines("")).toEqual([]);
|
|
320
|
+
});
|
|
321
|
+
it("skips invalid lines and returns valid ones", () => {
|
|
322
|
+
const validMsg = {
|
|
323
|
+
id: "1",
|
|
324
|
+
type: "agent_message",
|
|
325
|
+
timestamp: 1234567890,
|
|
326
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
327
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
328
|
+
};
|
|
329
|
+
const text = "invalid json\n" + JSON.stringify(validMsg) + "\n{broken}";
|
|
330
|
+
const result = parseStreamLines(text);
|
|
331
|
+
expect(result).toEqual([validMsg]);
|
|
332
|
+
});
|
|
333
|
+
it("handles lines with various whitespace", () => {
|
|
334
|
+
const msg = {
|
|
335
|
+
id: "1",
|
|
336
|
+
type: "agent_message",
|
|
337
|
+
timestamp: 1234567890,
|
|
338
|
+
content: { messageId: "m1", data: { message: "Hello" } },
|
|
339
|
+
context: { sessionId: "s1", artifactId: "a1" },
|
|
340
|
+
};
|
|
341
|
+
const text = "\n\n" + JSON.stringify(msg) + "\n\n";
|
|
342
|
+
const result = parseStreamLines(text);
|
|
343
|
+
expect(result).toEqual([msg]);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/client.d.ts
CHANGED
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
import type { ChaosConfig, CreateResponseParams, Response } from './types.js';
|
|
2
2
|
import { WALLET_MODEL, ASK_MODEL } from './request.js';
|
|
3
|
+
import { StreamingHttpClient } from './http-streaming.js';
|
|
3
4
|
export { WALLET_MODEL, ASK_MODEL };
|
|
5
|
+
/**
|
|
6
|
+
* Extended config with useNativeHttp option
|
|
7
|
+
*/
|
|
8
|
+
interface ExtendedChaosConfig extends ChaosConfig {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
timeout: number;
|
|
12
|
+
useNativeHttp: boolean;
|
|
13
|
+
}
|
|
4
14
|
/**
|
|
5
15
|
* The v1 responses API namespace.
|
|
6
16
|
*/
|
|
7
17
|
declare class V1Responses {
|
|
8
18
|
private config;
|
|
9
19
|
private sessionId;
|
|
10
|
-
private
|
|
11
|
-
constructor(config:
|
|
20
|
+
private streamingClient;
|
|
21
|
+
constructor(config: ExtendedChaosConfig);
|
|
12
22
|
private generateId;
|
|
13
23
|
/**
|
|
14
24
|
* Create a response using v1 API.
|
|
15
25
|
*/
|
|
16
26
|
create(params: CreateResponseParams): Promise<Response>;
|
|
17
27
|
/**
|
|
18
|
-
* Create a
|
|
28
|
+
* Create a response using native HTTP streaming.
|
|
19
29
|
*/
|
|
20
|
-
private
|
|
30
|
+
private createWithNativeHttp;
|
|
21
31
|
/**
|
|
22
32
|
* Cancel the current request.
|
|
23
33
|
*/
|
|
@@ -32,7 +42,7 @@ declare class V1Responses {
|
|
|
32
42
|
*/
|
|
33
43
|
declare class V1Chat {
|
|
34
44
|
readonly responses: V1Responses;
|
|
35
|
-
constructor(config:
|
|
45
|
+
constructor(config: ExtendedChaosConfig);
|
|
36
46
|
}
|
|
37
47
|
/**
|
|
38
48
|
* Chaos AI SDK Client - V1 API.
|
|
@@ -64,6 +74,8 @@ declare class V1Chat {
|
|
|
64
74
|
export declare class Chaos {
|
|
65
75
|
readonly chat: V1Chat;
|
|
66
76
|
private config;
|
|
77
|
+
/** Whether to use native http module for streaming */
|
|
78
|
+
readonly useNativeHttp: boolean;
|
|
67
79
|
constructor(config: ChaosConfig);
|
|
68
80
|
/**
|
|
69
81
|
* Get the API key.
|
|
@@ -73,5 +85,9 @@ export declare class Chaos {
|
|
|
73
85
|
* Get the base URL.
|
|
74
86
|
*/
|
|
75
87
|
get baseUrl(): string;
|
|
88
|
+
/**
|
|
89
|
+
* Get a StreamingHttpClient instance for direct streaming access.
|
|
90
|
+
*/
|
|
91
|
+
getStreamingClient(): StreamingHttpClient;
|
|
76
92
|
}
|
|
77
93
|
export default Chaos;
|
package/dist/client.js
CHANGED
|
@@ -3,23 +3,37 @@
|
|
|
3
3
|
// This client talks to the new v1 API endpoint with Bearer auth.
|
|
4
4
|
import { ChaosError, ChaosTimeoutError } from './types.js';
|
|
5
5
|
import { toV1WalletRequest, toV1AskRequest, buildV1Headers, buildV1Endpoint, WALLET_MODEL, ASK_MODEL, } from './request.js';
|
|
6
|
-
import {
|
|
6
|
+
import { toV1Response } from './response.js';
|
|
7
|
+
import { parseStreamLine } from './stream.js';
|
|
8
|
+
import { StreamingHttpClient } from './http-streaming.js';
|
|
9
|
+
/**
|
|
10
|
+
* Build final state from a list of stream events.
|
|
11
|
+
*/
|
|
12
|
+
function buildFinalStateFromEvents(events) {
|
|
13
|
+
const blocks = [];
|
|
14
|
+
for (const event of events) {
|
|
15
|
+
if (event.type === 'report' && event.content) {
|
|
16
|
+
const content = event.content;
|
|
17
|
+
if (content.data) {
|
|
18
|
+
blocks.push(content.data);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { blocks };
|
|
23
|
+
}
|
|
7
24
|
// Export model constants
|
|
8
25
|
export { WALLET_MODEL, ASK_MODEL };
|
|
9
26
|
// ============================================================================
|
|
10
27
|
// Constants
|
|
11
28
|
// ============================================================================
|
|
12
29
|
const DEFAULT_TIMEOUT = 120000; // 2 minutes
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// V1 Responses Class
|
|
15
|
-
// ============================================================================
|
|
16
30
|
/**
|
|
17
31
|
* The v1 responses API namespace.
|
|
18
32
|
*/
|
|
19
33
|
class V1Responses {
|
|
20
34
|
config;
|
|
21
35
|
sessionId;
|
|
22
|
-
|
|
36
|
+
streamingClient = null;
|
|
23
37
|
constructor(config) {
|
|
24
38
|
this.config = config;
|
|
25
39
|
this.sessionId = this.generateId();
|
|
@@ -31,48 +45,63 @@ class V1Responses {
|
|
|
31
45
|
* Create a response using v1 API.
|
|
32
46
|
*/
|
|
33
47
|
async create(params) {
|
|
34
|
-
|
|
48
|
+
// Always use native HTTP for true streaming behavior
|
|
49
|
+
return this.createWithNativeHttp(params);
|
|
35
50
|
}
|
|
36
51
|
/**
|
|
37
|
-
* Create a
|
|
52
|
+
* Create a response using native HTTP streaming.
|
|
38
53
|
*/
|
|
39
|
-
async
|
|
54
|
+
async createWithNativeHttp(params) {
|
|
40
55
|
const responseId = this.generateId();
|
|
41
56
|
const sessionId = params.metadata.session_id || this.sessionId;
|
|
42
|
-
// Create
|
|
43
|
-
this.
|
|
44
|
-
|
|
45
|
-
|
|
57
|
+
// Create streaming client
|
|
58
|
+
this.streamingClient = new StreamingHttpClient({
|
|
59
|
+
baseUrl: this.config.baseUrl,
|
|
60
|
+
timeout: this.config.timeout,
|
|
61
|
+
});
|
|
46
62
|
// Build request
|
|
47
|
-
const endpoint = buildV1Endpoint(this.config.baseUrl);
|
|
48
63
|
const headers = buildV1Headers(this.config.apiKey);
|
|
49
64
|
// Check if wallet or ask mode
|
|
50
65
|
const isWallet = params.model === WALLET_MODEL || params.model.includes('WALLET');
|
|
51
66
|
const requestBody = isWallet
|
|
52
67
|
? toV1WalletRequest(params, sessionId)
|
|
53
68
|
: toV1AskRequest(params, sessionId);
|
|
69
|
+
// Build endpoint path
|
|
70
|
+
const endpoint = buildV1Endpoint(this.config.baseUrl);
|
|
71
|
+
const url = new URL(endpoint);
|
|
72
|
+
const path = url.pathname + url.search;
|
|
54
73
|
try {
|
|
55
|
-
//
|
|
56
|
-
const
|
|
74
|
+
// Collect events for final response
|
|
75
|
+
const events = [];
|
|
76
|
+
// Stream lines using native http
|
|
77
|
+
for await (const line of this.streamingClient.streamLines(path, {
|
|
57
78
|
method: 'POST',
|
|
58
79
|
headers,
|
|
59
80
|
body: JSON.stringify(requestBody),
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
81
|
+
})) {
|
|
82
|
+
try {
|
|
83
|
+
const event = JSON.parse(line);
|
|
84
|
+
events.push(event);
|
|
85
|
+
// Call stream event callback if provided
|
|
86
|
+
if (params.onStreamEvent) {
|
|
87
|
+
const msg = parseStreamLine(line);
|
|
88
|
+
if (msg) {
|
|
89
|
+
params.onStreamEvent(msg);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Skip invalid JSON lines
|
|
95
|
+
}
|
|
64
96
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
// Parse NDJSON stream
|
|
69
|
-
const finalState = await parseV1Stream(response.body);
|
|
97
|
+
// Build final state from events
|
|
98
|
+
const finalState = buildFinalStateFromEvents(events);
|
|
70
99
|
// Convert to Response
|
|
71
100
|
return toV1Response(responseId, params.model, finalState);
|
|
72
101
|
}
|
|
73
102
|
catch (error) {
|
|
74
|
-
if (error instanceof
|
|
75
|
-
throw
|
|
103
|
+
if (error instanceof ChaosTimeoutError) {
|
|
104
|
+
throw error;
|
|
76
105
|
}
|
|
77
106
|
if (error instanceof ChaosError) {
|
|
78
107
|
throw error;
|
|
@@ -80,15 +109,17 @@ class V1Responses {
|
|
|
80
109
|
throw new ChaosError(error instanceof Error ? error.message : String(error));
|
|
81
110
|
}
|
|
82
111
|
finally {
|
|
83
|
-
this.
|
|
112
|
+
this.streamingClient = null;
|
|
84
113
|
}
|
|
85
114
|
}
|
|
86
115
|
/**
|
|
87
116
|
* Cancel the current request.
|
|
88
117
|
*/
|
|
89
118
|
cancel() {
|
|
90
|
-
|
|
91
|
-
this.
|
|
119
|
+
// Cancel native HTTP streaming client
|
|
120
|
+
if (this.streamingClient) {
|
|
121
|
+
this.streamingClient.abort();
|
|
122
|
+
}
|
|
92
123
|
}
|
|
93
124
|
/**
|
|
94
125
|
* Reset the session for a new conversation.
|
|
@@ -143,12 +174,18 @@ class V1Chat {
|
|
|
143
174
|
export class Chaos {
|
|
144
175
|
chat;
|
|
145
176
|
config;
|
|
177
|
+
/** Whether to use native http module for streaming */
|
|
178
|
+
useNativeHttp;
|
|
146
179
|
constructor(config) {
|
|
180
|
+
// Default useNativeHttp to true for proper streaming
|
|
181
|
+
const useNativeHttp = config.useNativeHttp !== false;
|
|
147
182
|
this.config = {
|
|
148
183
|
apiKey: config.apiKey,
|
|
149
184
|
baseUrl: config.baseUrl || 'https://ai-staging.chaoslabs.co',
|
|
150
185
|
timeout: config.timeout || DEFAULT_TIMEOUT,
|
|
186
|
+
useNativeHttp,
|
|
151
187
|
};
|
|
188
|
+
this.useNativeHttp = useNativeHttp;
|
|
152
189
|
this.chat = new V1Chat(this.config);
|
|
153
190
|
}
|
|
154
191
|
/**
|
|
@@ -163,5 +200,14 @@ export class Chaos {
|
|
|
163
200
|
get baseUrl() {
|
|
164
201
|
return this.config.baseUrl;
|
|
165
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* Get a StreamingHttpClient instance for direct streaming access.
|
|
205
|
+
*/
|
|
206
|
+
getStreamingClient() {
|
|
207
|
+
return new StreamingHttpClient({
|
|
208
|
+
baseUrl: this.config.baseUrl,
|
|
209
|
+
timeout: this.config.timeout,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
166
212
|
}
|
|
167
213
|
export default Chaos;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface HttpStreamOptions {
|
|
2
|
+
url: string;
|
|
3
|
+
method: string;
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
body: string;
|
|
6
|
+
timeout: number;
|
|
7
|
+
}
|
|
8
|
+
export interface StreamingHttpClientOptions {
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
timeout: number;
|
|
11
|
+
}
|
|
12
|
+
export interface StreamRequestOptions {
|
|
13
|
+
method: string;
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
body: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Makes an HTTP request using Node's native http/https modules and returns
|
|
19
|
+
* an async iterator that yields chunks as they arrive (true streaming).
|
|
20
|
+
*
|
|
21
|
+
* @param options - Request options
|
|
22
|
+
* @returns AsyncIterable that yields string chunks
|
|
23
|
+
*/
|
|
24
|
+
export declare function httpStreamRequest(options: HttpStreamOptions): AsyncIterable<string>;
|
|
25
|
+
/**
|
|
26
|
+
* High-level HTTP streaming client that provides methods for streaming
|
|
27
|
+
* requests with proper chunk and line parsing.
|
|
28
|
+
*/
|
|
29
|
+
export declare class StreamingHttpClient {
|
|
30
|
+
private baseUrl;
|
|
31
|
+
private timeout;
|
|
32
|
+
private aborted;
|
|
33
|
+
private currentIterator;
|
|
34
|
+
constructor(options: StreamingHttpClientOptions);
|
|
35
|
+
/**
|
|
36
|
+
* Stream raw chunks from a request.
|
|
37
|
+
*
|
|
38
|
+
* @param path - URL path (will be appended to baseUrl)
|
|
39
|
+
* @param options - Request options
|
|
40
|
+
* @returns AsyncIterable of raw string chunks
|
|
41
|
+
*/
|
|
42
|
+
stream(path: string, options: StreamRequestOptions): AsyncIterable<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Stream parsed NDJSON lines from a request.
|
|
45
|
+
*
|
|
46
|
+
* @param path - URL path (will be appended to baseUrl)
|
|
47
|
+
* @param options - Request options
|
|
48
|
+
* @returns AsyncIterable of complete JSON lines (without newline)
|
|
49
|
+
*/
|
|
50
|
+
streamLines(path: string, options: StreamRequestOptions): AsyncIterable<string>;
|
|
51
|
+
/**
|
|
52
|
+
* Abort the current in-progress request.
|
|
53
|
+
*/
|
|
54
|
+
abort(): void;
|
|
55
|
+
}
|