assistant-cloud 0.1.40 → 0.1.42
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 +3 -3
- package/dist/AssistantCloud.js +2 -2
- package/dist/AssistantCloud.js.map +1 -1
- package/dist/AssistantCloudAPI.d.ts.map +1 -1
- package/dist/AssistantCloudAPI.js +5 -5
- package/dist/AssistantCloudAPI.js.map +1 -1
- package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
- package/dist/AssistantCloudAuthStrategy.js +35 -12
- package/dist/AssistantCloudAuthStrategy.js.map +1 -1
- package/dist/AssistantCloudAuthTokens.d.ts.map +1 -1
- package/dist/AssistantCloudAuthTokens.js +3 -1
- package/dist/AssistantCloudAuthTokens.js.map +1 -1
- package/dist/AssistantCloudFiles.d.ts.map +1 -1
- package/dist/AssistantCloudFiles.js +16 -4
- package/dist/AssistantCloudFiles.js.map +1 -1
- package/dist/AssistantCloudRuns.d.ts +3 -13
- package/dist/AssistantCloudRuns.d.ts.map +1 -1
- package/dist/AssistantCloudRuns.js +10 -2
- package/dist/AssistantCloudRuns.js.map +1 -1
- package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
- package/dist/AssistantCloudThreadMessages.js +3 -2
- package/dist/AssistantCloudThreadMessages.js.map +1 -1
- package/dist/AssistantCloudThreads.d.ts.map +1 -1
- package/dist/AssistantCloudThreads.js +3 -2
- package/dist/AssistantCloudThreads.js.map +1 -1
- package/dist/generateThreadTitle.d.ts +15 -0
- package/dist/generateThreadTitle.d.ts.map +1 -0
- package/dist/generateThreadTitle.js +25 -0
- package/dist/generateThreadTitle.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/runTelemetry.d.ts +54 -0
- package/dist/runTelemetry.d.ts.map +1 -0
- package/dist/runTelemetry.js +79 -0
- package/dist/runTelemetry.js.map +1 -0
- package/package.json +5 -5
- package/src/AssistantCloud.ts +1 -1
- package/src/AssistantCloudAPI.ts +9 -8
- package/src/AssistantCloudAuthStrategy.ts +49 -12
- package/src/AssistantCloudAuthTokens.test.ts +30 -0
- package/src/AssistantCloudAuthTokens.ts +7 -1
- package/src/AssistantCloudFiles.test.ts +71 -0
- package/src/AssistantCloudFiles.ts +56 -10
- package/src/AssistantCloudRuns.ts +37 -15
- package/src/AssistantCloudThreadMessages.test.ts +20 -0
- package/src/AssistantCloudThreadMessages.ts +10 -3
- package/src/AssistantCloudThreads.test.ts +17 -0
- package/src/AssistantCloudThreads.ts +6 -1
- package/src/generateThreadTitle.test.ts +71 -0
- package/src/generateThreadTitle.ts +38 -0
- package/src/index.ts +10 -0
- package/src/runTelemetry.test.ts +130 -0
- package/src/runTelemetry.ts +140 -0
- package/src/tests/AssistantCloud.test.ts +39 -0
- package/src/tests/AssistantCloudAPI.test.ts +25 -0
- package/src/tests/AssistantCloudAuthStrategy.test.ts +278 -5
- package/src/tests/AssistantCloudRuns.test.ts +115 -0
|
@@ -11,6 +11,7 @@ const refreshToken = {
|
|
|
11
11
|
token: "r1",
|
|
12
12
|
expires_at: "2099-01-01",
|
|
13
13
|
};
|
|
14
|
+
const refreshTokenKey = `aui:refresh_token:${baseUrl}`;
|
|
14
15
|
|
|
15
16
|
let originalLocalStorageDescriptor: PropertyDescriptor | undefined;
|
|
16
17
|
|
|
@@ -79,9 +80,7 @@ describe("AssistantCloudAnonymousAuthStrategy", () => {
|
|
|
79
80
|
await expect(strategy.getAuthHeaders()).resolves.toEqual({
|
|
80
81
|
Authorization: `Bearer ${accessToken}`,
|
|
81
82
|
});
|
|
82
|
-
expect(values.get(
|
|
83
|
-
JSON.stringify(nextRefreshToken),
|
|
84
|
-
);
|
|
83
|
+
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(nextRefreshToken));
|
|
85
84
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
86
85
|
`${baseUrl}/v1/auth/tokens/anonymous`,
|
|
87
86
|
{ method: "POST" },
|
|
@@ -107,6 +106,183 @@ describe("AssistantCloudAnonymousAuthStrategy", () => {
|
|
|
107
106
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
108
107
|
});
|
|
109
108
|
|
|
109
|
+
it("scopes anonymous refresh tokens by backend", async () => {
|
|
110
|
+
const secondBaseUrl = "https://other.example.com";
|
|
111
|
+
const values = new Map<string, string>();
|
|
112
|
+
installLocalStorage({
|
|
113
|
+
getItem: (key) => values.get(key) ?? null,
|
|
114
|
+
setItem: (key, value) => {
|
|
115
|
+
values.set(key, value);
|
|
116
|
+
},
|
|
117
|
+
removeItem: (key) => {
|
|
118
|
+
values.delete(key);
|
|
119
|
+
},
|
|
120
|
+
} as Storage);
|
|
121
|
+
const secondRefreshToken = {
|
|
122
|
+
token: "r2",
|
|
123
|
+
expires_at: "2099-01-01",
|
|
124
|
+
};
|
|
125
|
+
const fetchMock = vi
|
|
126
|
+
.fn()
|
|
127
|
+
.mockResolvedValueOnce({
|
|
128
|
+
ok: true,
|
|
129
|
+
json: vi.fn().mockResolvedValue({
|
|
130
|
+
access_token: accessToken,
|
|
131
|
+
refresh_token: refreshToken,
|
|
132
|
+
}),
|
|
133
|
+
})
|
|
134
|
+
.mockResolvedValueOnce({
|
|
135
|
+
ok: true,
|
|
136
|
+
json: vi.fn().mockResolvedValue({
|
|
137
|
+
access_token: accessToken,
|
|
138
|
+
refresh_token: secondRefreshToken,
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
142
|
+
|
|
143
|
+
await new AssistantCloudAnonymousAuthStrategy(baseUrl).getAuthHeaders();
|
|
144
|
+
await new AssistantCloudAnonymousAuthStrategy(
|
|
145
|
+
secondBaseUrl,
|
|
146
|
+
).getAuthHeaders();
|
|
147
|
+
|
|
148
|
+
expect(fetchMock).toHaveBeenNthCalledWith(
|
|
149
|
+
1,
|
|
150
|
+
`${baseUrl}/v1/auth/tokens/anonymous`,
|
|
151
|
+
{ method: "POST" },
|
|
152
|
+
);
|
|
153
|
+
expect(fetchMock).toHaveBeenNthCalledWith(
|
|
154
|
+
2,
|
|
155
|
+
`${secondBaseUrl}/v1/auth/tokens/anonymous`,
|
|
156
|
+
{ method: "POST" },
|
|
157
|
+
);
|
|
158
|
+
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
|
|
159
|
+
expect(values.get(`aui:refresh_token:${secondBaseUrl}`)).toBe(
|
|
160
|
+
JSON.stringify(secondRefreshToken),
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("migrates the legacy refresh token to the first backend", async () => {
|
|
165
|
+
const secondBaseUrl = "https://other.example.com";
|
|
166
|
+
const secondRefreshToken = {
|
|
167
|
+
token: "r2",
|
|
168
|
+
expires_at: "2099-01-01",
|
|
169
|
+
};
|
|
170
|
+
const values = new Map([
|
|
171
|
+
["aui:refresh_token", JSON.stringify(refreshToken)],
|
|
172
|
+
]);
|
|
173
|
+
installLocalStorage({
|
|
174
|
+
getItem: (key) => values.get(key) ?? null,
|
|
175
|
+
setItem: (key, value) => {
|
|
176
|
+
values.set(key, value);
|
|
177
|
+
},
|
|
178
|
+
removeItem: (key) => {
|
|
179
|
+
values.delete(key);
|
|
180
|
+
},
|
|
181
|
+
} as Storage);
|
|
182
|
+
const fetchMock = vi
|
|
183
|
+
.fn()
|
|
184
|
+
.mockResolvedValueOnce({
|
|
185
|
+
ok: true,
|
|
186
|
+
json: vi.fn().mockResolvedValue({ access_token: accessToken }),
|
|
187
|
+
})
|
|
188
|
+
.mockResolvedValueOnce({
|
|
189
|
+
ok: true,
|
|
190
|
+
json: vi.fn().mockResolvedValue({
|
|
191
|
+
access_token: accessToken,
|
|
192
|
+
refresh_token: secondRefreshToken,
|
|
193
|
+
}),
|
|
194
|
+
});
|
|
195
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
196
|
+
|
|
197
|
+
await new AssistantCloudAnonymousAuthStrategy(baseUrl).getAuthHeaders();
|
|
198
|
+
await new AssistantCloudAnonymousAuthStrategy(
|
|
199
|
+
secondBaseUrl,
|
|
200
|
+
).getAuthHeaders();
|
|
201
|
+
|
|
202
|
+
expect(fetchMock).toHaveBeenNthCalledWith(
|
|
203
|
+
1,
|
|
204
|
+
`${baseUrl}/v1/auth/tokens/refresh`,
|
|
205
|
+
{
|
|
206
|
+
method: "POST",
|
|
207
|
+
headers: { "Content-Type": "application/json" },
|
|
208
|
+
body: JSON.stringify({ refresh_token: refreshToken.token }),
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
expect(fetchMock).toHaveBeenNthCalledWith(
|
|
212
|
+
2,
|
|
213
|
+
`${secondBaseUrl}/v1/auth/tokens/anonymous`,
|
|
214
|
+
{ method: "POST" },
|
|
215
|
+
);
|
|
216
|
+
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
|
|
217
|
+
expect(values.get(`aui:refresh_token:${secondBaseUrl}`)).toBe(
|
|
218
|
+
JSON.stringify(secondRefreshToken),
|
|
219
|
+
);
|
|
220
|
+
expect(values.has("aui:refresh_token")).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("retires the legacy refresh token when a scoped token exists", async () => {
|
|
224
|
+
const secondBaseUrl = "https://other.example.com";
|
|
225
|
+
const scopedRefreshToken = {
|
|
226
|
+
token: "scoped-r1",
|
|
227
|
+
expires_at: "2099-01-01",
|
|
228
|
+
};
|
|
229
|
+
const secondRefreshToken = {
|
|
230
|
+
token: "r2",
|
|
231
|
+
expires_at: "2099-01-01",
|
|
232
|
+
};
|
|
233
|
+
const values = new Map([
|
|
234
|
+
["aui:refresh_token", JSON.stringify(refreshToken)],
|
|
235
|
+
[refreshTokenKey, JSON.stringify(scopedRefreshToken)],
|
|
236
|
+
]);
|
|
237
|
+
installLocalStorage({
|
|
238
|
+
getItem: (key) => values.get(key) ?? null,
|
|
239
|
+
setItem: (key, value) => {
|
|
240
|
+
values.set(key, value);
|
|
241
|
+
},
|
|
242
|
+
removeItem: (key) => {
|
|
243
|
+
values.delete(key);
|
|
244
|
+
},
|
|
245
|
+
} as Storage);
|
|
246
|
+
const fetchMock = vi
|
|
247
|
+
.fn()
|
|
248
|
+
.mockResolvedValueOnce({
|
|
249
|
+
ok: true,
|
|
250
|
+
json: vi.fn().mockResolvedValue({ access_token: accessToken }),
|
|
251
|
+
})
|
|
252
|
+
.mockResolvedValueOnce({
|
|
253
|
+
ok: true,
|
|
254
|
+
json: vi.fn().mockResolvedValue({
|
|
255
|
+
access_token: accessToken,
|
|
256
|
+
refresh_token: secondRefreshToken,
|
|
257
|
+
}),
|
|
258
|
+
});
|
|
259
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
260
|
+
|
|
261
|
+
await new AssistantCloudAnonymousAuthStrategy(baseUrl).getAuthHeaders();
|
|
262
|
+
await new AssistantCloudAnonymousAuthStrategy(
|
|
263
|
+
secondBaseUrl,
|
|
264
|
+
).getAuthHeaders();
|
|
265
|
+
|
|
266
|
+
expect(fetchMock).toHaveBeenNthCalledWith(
|
|
267
|
+
1,
|
|
268
|
+
`${baseUrl}/v1/auth/tokens/refresh`,
|
|
269
|
+
{
|
|
270
|
+
method: "POST",
|
|
271
|
+
headers: { "Content-Type": "application/json" },
|
|
272
|
+
body: JSON.stringify({ refresh_token: scopedRefreshToken.token }),
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
expect(fetchMock).toHaveBeenNthCalledWith(
|
|
276
|
+
2,
|
|
277
|
+
`${secondBaseUrl}/v1/auth/tokens/anonymous`,
|
|
278
|
+
{ method: "POST" },
|
|
279
|
+
);
|
|
280
|
+
expect(values.has("aui:refresh_token")).toBe(false);
|
|
281
|
+
expect(values.get(`aui:refresh_token:${secondBaseUrl}`)).toBe(
|
|
282
|
+
JSON.stringify(secondRefreshToken),
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
|
|
110
286
|
it("returns an anonymous access token without localStorage", async () => {
|
|
111
287
|
delete (globalThis as { localStorage?: Storage }).localStorage;
|
|
112
288
|
mockAnonymousTokenFetch();
|
|
@@ -160,7 +336,7 @@ describe("AssistantCloudAnonymousAuthStrategy", () => {
|
|
|
160
336
|
).resolves.toEqual({ Authorization: `Bearer ${accessToken}` });
|
|
161
337
|
expect(getItem).toHaveBeenCalledTimes(2);
|
|
162
338
|
expect(setItem).toHaveBeenCalledTimes(2);
|
|
163
|
-
expect(removeItem).toHaveBeenCalledTimes(
|
|
339
|
+
expect(removeItem).toHaveBeenCalledTimes(2);
|
|
164
340
|
});
|
|
165
341
|
|
|
166
342
|
it("treats corrupted refresh token JSON as absent", async () => {
|
|
@@ -181,7 +357,8 @@ describe("AssistantCloudAnonymousAuthStrategy", () => {
|
|
|
181
357
|
await expect(strategy.getAuthHeaders()).resolves.toEqual({
|
|
182
358
|
Authorization: `Bearer ${accessToken}`,
|
|
183
359
|
});
|
|
184
|
-
expect(values.get(
|
|
360
|
+
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
|
|
361
|
+
expect(values.has("aui:refresh_token")).toBe(false);
|
|
185
362
|
});
|
|
186
363
|
|
|
187
364
|
it("rejects malformed anonymous token responses without persisting them", async () => {
|
|
@@ -298,6 +475,102 @@ describe("AssistantCloudAnonymousAuthStrategy", () => {
|
|
|
298
475
|
expect(setItem).not.toHaveBeenCalled();
|
|
299
476
|
});
|
|
300
477
|
|
|
478
|
+
it.each([429, 500, 503])(
|
|
479
|
+
"preserves the anonymous identity after transient status %i",
|
|
480
|
+
async (status) => {
|
|
481
|
+
const values = new Map([[refreshTokenKey, JSON.stringify(refreshToken)]]);
|
|
482
|
+
installLocalStorage({
|
|
483
|
+
getItem: (key) => values.get(key) ?? null,
|
|
484
|
+
setItem: (key, value) => {
|
|
485
|
+
values.set(key, value);
|
|
486
|
+
},
|
|
487
|
+
removeItem: (key) => {
|
|
488
|
+
values.delete(key);
|
|
489
|
+
},
|
|
490
|
+
} as Storage);
|
|
491
|
+
const fetchMock = vi.fn().mockResolvedValue({ ok: false, status });
|
|
492
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
493
|
+
|
|
494
|
+
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
|
|
495
|
+
|
|
496
|
+
await expect(strategy.getAuthHeaders()).rejects.toThrow(
|
|
497
|
+
`Assistant Cloud token refresh failed with status ${status}`,
|
|
498
|
+
);
|
|
499
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
500
|
+
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
|
|
501
|
+
},
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
it.each([401, 403])(
|
|
505
|
+
"replaces an anonymous identity after refresh is rejected with %i",
|
|
506
|
+
async (status) => {
|
|
507
|
+
const replacementRefreshToken = {
|
|
508
|
+
token: "r2",
|
|
509
|
+
expires_at: "2099-02-01",
|
|
510
|
+
};
|
|
511
|
+
const values = new Map([[refreshTokenKey, JSON.stringify(refreshToken)]]);
|
|
512
|
+
installLocalStorage({
|
|
513
|
+
getItem: (key) => values.get(key) ?? null,
|
|
514
|
+
setItem: (key, value) => {
|
|
515
|
+
values.set(key, value);
|
|
516
|
+
},
|
|
517
|
+
removeItem: (key) => {
|
|
518
|
+
values.delete(key);
|
|
519
|
+
},
|
|
520
|
+
} as Storage);
|
|
521
|
+
const fetchMock = vi
|
|
522
|
+
.fn()
|
|
523
|
+
.mockResolvedValueOnce({ ok: false, status })
|
|
524
|
+
.mockResolvedValueOnce({
|
|
525
|
+
ok: true,
|
|
526
|
+
json: vi.fn().mockResolvedValue({
|
|
527
|
+
access_token: accessToken,
|
|
528
|
+
refresh_token: replacementRefreshToken,
|
|
529
|
+
}),
|
|
530
|
+
});
|
|
531
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
532
|
+
|
|
533
|
+
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
|
|
534
|
+
|
|
535
|
+
await expect(strategy.getAuthHeaders()).resolves.toEqual({
|
|
536
|
+
Authorization: `Bearer ${accessToken}`,
|
|
537
|
+
});
|
|
538
|
+
expect(fetchMock).toHaveBeenNthCalledWith(
|
|
539
|
+
2,
|
|
540
|
+
`${baseUrl}/v1/auth/tokens/anonymous`,
|
|
541
|
+
{ method: "POST" },
|
|
542
|
+
);
|
|
543
|
+
expect(values.get(refreshTokenKey)).toBe(
|
|
544
|
+
JSON.stringify(replacementRefreshToken),
|
|
545
|
+
);
|
|
546
|
+
},
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
it("preserves a rejected refresh token until its replacement succeeds", async () => {
|
|
550
|
+
const values = new Map([[refreshTokenKey, JSON.stringify(refreshToken)]]);
|
|
551
|
+
installLocalStorage({
|
|
552
|
+
getItem: (key) => values.get(key) ?? null,
|
|
553
|
+
setItem: (key, value) => {
|
|
554
|
+
values.set(key, value);
|
|
555
|
+
},
|
|
556
|
+
removeItem: (key) => {
|
|
557
|
+
values.delete(key);
|
|
558
|
+
},
|
|
559
|
+
} as Storage);
|
|
560
|
+
vi.stubGlobal(
|
|
561
|
+
"fetch",
|
|
562
|
+
vi
|
|
563
|
+
.fn()
|
|
564
|
+
.mockResolvedValueOnce({ ok: false, status: 403 })
|
|
565
|
+
.mockResolvedValueOnce({ ok: false, status: 503 }),
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
|
|
569
|
+
|
|
570
|
+
await expect(strategy.getAuthHeaders()).resolves.toBe(false);
|
|
571
|
+
expect(values.get(refreshTokenKey)).toBe(JSON.stringify(refreshToken));
|
|
572
|
+
});
|
|
573
|
+
|
|
301
574
|
it("contextualizes invalid JSON token responses", async () => {
|
|
302
575
|
delete (globalThis as { localStorage?: Storage }).localStorage;
|
|
303
576
|
vi.stubGlobal(
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { AssistantCloudAPI } from "../AssistantCloudAPI";
|
|
3
|
+
import { AssistantCloud } from "../AssistantCloud";
|
|
4
|
+
import { AssistantCloudRuns } from "../AssistantCloudRuns";
|
|
5
|
+
import { CloudResponseError } from "../cloudResponse";
|
|
6
|
+
|
|
7
|
+
const createCloud = () =>
|
|
8
|
+
new AssistantCloud({
|
|
9
|
+
apiKey: "test-key",
|
|
10
|
+
userId: "user-id",
|
|
11
|
+
workspaceId: "workspace-id",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const streamBody = {
|
|
15
|
+
thread_id: "thread-id",
|
|
16
|
+
assistant_id: "system/thread_title" as const,
|
|
17
|
+
messages: [],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
describe("AssistantCloudRuns", () => {
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
vi.unstubAllGlobals();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("decodes text/plain run streams", async () => {
|
|
26
|
+
vi.stubGlobal(
|
|
27
|
+
"fetch",
|
|
28
|
+
vi.fn().mockResolvedValue(
|
|
29
|
+
new Response("Generated title", {
|
|
30
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
31
|
+
}),
|
|
32
|
+
),
|
|
33
|
+
);
|
|
34
|
+
const stream = await createCloud().runs.stream(streamBody);
|
|
35
|
+
let text = "";
|
|
36
|
+
|
|
37
|
+
await stream.pipeTo(
|
|
38
|
+
new WritableStream({
|
|
39
|
+
write(chunk) {
|
|
40
|
+
if (chunk.type === "text-delta") text += chunk.textDelta;
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(text).toBe("Generated title");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("rejects successful run responses without a body", async () => {
|
|
49
|
+
vi.stubGlobal(
|
|
50
|
+
"fetch",
|
|
51
|
+
vi.fn().mockResolvedValue(new Response(null, { status: 204 })),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
await expect(createCloud().runs.stream(streamBody)).rejects.toThrow(
|
|
55
|
+
new CloudResponseError(
|
|
56
|
+
'Invalid Assistant Cloud response for "run stream": expected a response body',
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("rejects and cancels run responses with the wrong content type", async () => {
|
|
62
|
+
const cancel = vi.fn();
|
|
63
|
+
const body = new ReadableStream({ cancel });
|
|
64
|
+
vi.stubGlobal(
|
|
65
|
+
"fetch",
|
|
66
|
+
vi.fn().mockResolvedValue(
|
|
67
|
+
new Response(body, {
|
|
68
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
69
|
+
}),
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
await expect(createCloud().runs.stream(streamBody)).rejects.toThrow(
|
|
74
|
+
new CloudResponseError(
|
|
75
|
+
'Invalid Assistant Cloud response for "run stream": expected a "text/plain" content type, received "text/html; charset=utf-8"',
|
|
76
|
+
),
|
|
77
|
+
);
|
|
78
|
+
expect(cancel).toHaveBeenCalledOnce();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("rejects and cancels run responses without a content type", async () => {
|
|
82
|
+
const cancel = vi.fn();
|
|
83
|
+
const body = new ReadableStream({ cancel });
|
|
84
|
+
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(body)));
|
|
85
|
+
|
|
86
|
+
await expect(createCloud().runs.stream(streamBody)).rejects.toThrow(
|
|
87
|
+
new CloudResponseError(
|
|
88
|
+
'Invalid Assistant Cloud response for "run stream": expected a "text/plain" content type, received no Content-Type header',
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
expect(cancel).toHaveBeenCalledOnce();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const createCloudRuns = () => {
|
|
96
|
+
const makeRequest = vi.fn();
|
|
97
|
+
const api = { makeRequest } as unknown as AssistantCloudAPI;
|
|
98
|
+
return { runs: new AssistantCloudRuns(api), makeRequest };
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
describe("AssistantCloudRuns responses", () => {
|
|
102
|
+
it("validates reported run IDs", async () => {
|
|
103
|
+
const { runs, makeRequest } = createCloudRuns();
|
|
104
|
+
const body = { thread_id: "thread-1", status: "completed" } as const;
|
|
105
|
+
makeRequest.mockResolvedValueOnce({ run_id: "run-1" });
|
|
106
|
+
|
|
107
|
+
await expect(runs.report(body)).resolves.toEqual({ run_id: "run-1" });
|
|
108
|
+
|
|
109
|
+
makeRequest.mockResolvedValueOnce({});
|
|
110
|
+
|
|
111
|
+
await expect(runs.report(body)).rejects.toThrow(
|
|
112
|
+
'Invalid Assistant Cloud response for "run_id": expected a string',
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
});
|