assistant-cloud 0.1.36 → 0.1.38
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/dist/AssistantCloudAPI.d.ts.map +1 -1
- package/dist/AssistantCloudAPI.js.map +1 -1
- package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
- package/dist/AssistantCloudAuthStrategy.js +32 -7
- package/dist/AssistantCloudAuthStrategy.js.map +1 -1
- package/dist/AssistantCloudAuthTokens.d.ts.map +1 -1
- package/dist/AssistantCloudAuthTokens.js.map +1 -1
- package/dist/AssistantCloudFiles.d.ts.map +1 -1
- package/dist/AssistantCloudFiles.js.map +1 -1
- package/dist/AssistantCloudProjectThreadMessages.d.ts.map +1 -1
- package/dist/AssistantCloudProjectThreadMessages.js.map +1 -1
- package/dist/AssistantCloudProjectThreads.d.ts +1 -1
- package/dist/AssistantCloudProjectThreads.d.ts.map +1 -1
- package/dist/AssistantCloudProjectThreads.js +1 -1
- package/dist/AssistantCloudProjectThreads.js.map +1 -1
- package/dist/AssistantCloudRuns.d.ts.map +1 -1
- package/dist/AssistantCloudRuns.js.map +1 -1
- package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
- package/dist/AssistantCloudThreadMessages.js.map +1 -1
- package/dist/AssistantCloudThreads.d.ts +1 -1
- package/dist/AssistantCloudThreads.d.ts.map +1 -1
- package/dist/AssistantCloudThreads.js +1 -1
- package/dist/AssistantCloudThreads.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/AssistantCloudAPI.ts +4 -4
- package/src/AssistantCloudAuthStrategy.ts +82 -19
- package/src/AssistantCloudAuthTokens.ts +5 -1
- package/src/AssistantCloudFiles.ts +5 -1
- package/src/AssistantCloudProjectThreadMessages.ts +5 -1
- package/src/AssistantCloudProjectThreads.ts +4 -1
- package/src/AssistantCloudRuns.ts +5 -1
- package/src/AssistantCloudThreadMessages.ts +5 -1
- package/src/AssistantCloudThreads.ts +4 -1
- package/src/tests/AssistantCloudAPI.test.ts +25 -84
- package/src/tests/AssistantCloudAuthStrategy.test.ts +152 -6
- package/src/tests/AssistantCloudFiles.test.ts +0 -562
|
@@ -48,7 +48,11 @@ export type AssistantCloudRunReport = {
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
export class AssistantCloudRuns {
|
|
51
|
-
|
|
51
|
+
private cloud: AssistantCloudAPI;
|
|
52
|
+
|
|
53
|
+
constructor(cloud: AssistantCloudAPI) {
|
|
54
|
+
this.cloud = cloud;
|
|
55
|
+
}
|
|
52
56
|
|
|
53
57
|
public __internal_getAssistantOptions(assistantId: string) {
|
|
54
58
|
return {
|
|
@@ -59,7 +59,11 @@ export const decodeCloudMessage = (
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
export class AssistantCloudThreadMessages {
|
|
62
|
-
|
|
62
|
+
private cloud: AssistantCloudAPI;
|
|
63
|
+
|
|
64
|
+
constructor(cloud: AssistantCloudAPI) {
|
|
65
|
+
this.cloud = cloud;
|
|
66
|
+
}
|
|
63
67
|
|
|
64
68
|
public async list(
|
|
65
69
|
threadId: string,
|
|
@@ -78,7 +78,10 @@ export const decodeCloudThread = (
|
|
|
78
78
|
export class AssistantCloudThreads {
|
|
79
79
|
public readonly messages: AssistantCloudThreadMessages;
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
private cloud: AssistantCloudAPI;
|
|
82
|
+
|
|
83
|
+
constructor(cloud: AssistantCloudAPI) {
|
|
84
|
+
this.cloud = cloud;
|
|
82
85
|
this.messages = new AssistantCloudThreadMessages(cloud);
|
|
83
86
|
}
|
|
84
87
|
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
2
|
import { AssistantCloudAPI, CloudAPIError } from "../AssistantCloudAPI";
|
|
3
|
-
|
|
4
|
-
const createJwt = () => {
|
|
5
|
-
const payload = Buffer.from(JSON.stringify({ exp: 4_102_444_800 })).toString(
|
|
6
|
-
"base64url",
|
|
7
|
-
);
|
|
8
|
-
return `header.${payload}.signature`;
|
|
9
|
-
};
|
|
3
|
+
import { CloudResponseError } from "../cloudResponse";
|
|
10
4
|
|
|
11
5
|
describe("AssistantCloudAPI", () => {
|
|
12
6
|
beforeEach(() => {
|
|
@@ -101,65 +95,6 @@ describe("AssistantCloudAPI", () => {
|
|
|
101
95
|
expect(url.toString()).toBe("https://custom.example.com/v1/threads");
|
|
102
96
|
});
|
|
103
97
|
|
|
104
|
-
it("strips a trailing slash from a JWT baseUrl", async () => {
|
|
105
|
-
const fetchMock = vi.fn().mockResolvedValue({
|
|
106
|
-
ok: true,
|
|
107
|
-
headers: new Headers(),
|
|
108
|
-
});
|
|
109
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
110
|
-
|
|
111
|
-
const api = new AssistantCloudAPI({
|
|
112
|
-
baseUrl: "https://custom.example.com/",
|
|
113
|
-
authToken: async () => createJwt(),
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
await api.makeRawRequest("/threads");
|
|
117
|
-
|
|
118
|
-
const [url] = fetchMock.mock.calls[0]!;
|
|
119
|
-
expect(url.toString()).toBe("https://custom.example.com/v1/threads");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("strips a trailing slash from anonymous auth URLs", async () => {
|
|
123
|
-
const storage = {
|
|
124
|
-
getItem: vi.fn().mockReturnValue(null),
|
|
125
|
-
setItem: vi.fn(),
|
|
126
|
-
removeItem: vi.fn(),
|
|
127
|
-
};
|
|
128
|
-
vi.stubGlobal("localStorage", storage);
|
|
129
|
-
|
|
130
|
-
const fetchMock = vi
|
|
131
|
-
.fn()
|
|
132
|
-
.mockResolvedValueOnce({
|
|
133
|
-
ok: true,
|
|
134
|
-
json: vi.fn().mockResolvedValue({
|
|
135
|
-
access_token: createJwt(),
|
|
136
|
-
refresh_token: {
|
|
137
|
-
token: "refresh-token",
|
|
138
|
-
expires_at: "2100-01-01T00:00:00.000Z",
|
|
139
|
-
},
|
|
140
|
-
}),
|
|
141
|
-
})
|
|
142
|
-
.mockResolvedValueOnce({
|
|
143
|
-
ok: true,
|
|
144
|
-
headers: new Headers(),
|
|
145
|
-
});
|
|
146
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
147
|
-
|
|
148
|
-
const api = new AssistantCloudAPI({
|
|
149
|
-
baseUrl: "https://custom.example.com/",
|
|
150
|
-
anonymous: true,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
await api.makeRawRequest("/threads");
|
|
154
|
-
|
|
155
|
-
expect(fetchMock.mock.calls[0]![0]).toBe(
|
|
156
|
-
"https://custom.example.com/v1/auth/tokens/anonymous",
|
|
157
|
-
);
|
|
158
|
-
expect(fetchMock.mock.calls[1]![0].toString()).toBe(
|
|
159
|
-
"https://custom.example.com/v1/threads",
|
|
160
|
-
);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
98
|
it("rejects before fetch when auth token callback returns null", async () => {
|
|
164
99
|
const fetchMock = vi.fn();
|
|
165
100
|
vi.stubGlobal("fetch", fetchMock);
|
|
@@ -184,6 +119,30 @@ describe("AssistantCloudAPI", () => {
|
|
|
184
119
|
await expect(api.initializeAuth()).resolves.toBe(false);
|
|
185
120
|
});
|
|
186
121
|
|
|
122
|
+
it("rejects initializeAuth with context for malformed anonymous responses", async () => {
|
|
123
|
+
vi.stubGlobal(
|
|
124
|
+
"fetch",
|
|
125
|
+
vi.fn().mockResolvedValue({
|
|
126
|
+
ok: true,
|
|
127
|
+
json: vi.fn().mockResolvedValue({
|
|
128
|
+
access_token: 123,
|
|
129
|
+
refresh_token: null,
|
|
130
|
+
}),
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const api = new AssistantCloudAPI({
|
|
135
|
+
baseUrl: "https://test.example.com",
|
|
136
|
+
anonymous: true,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await expect(api.initializeAuth()).rejects.toThrow(
|
|
140
|
+
new CloudResponseError(
|
|
141
|
+
'Invalid Assistant Cloud response for "anonymous auth token response.access_token": expected a string',
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
187
146
|
it("throws APIError with parsed message for JSON error responses", async () => {
|
|
188
147
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
189
148
|
ok: false,
|
|
@@ -289,22 +248,4 @@ describe("AssistantCloudAPI", () => {
|
|
|
289
248
|
await expect(api.makeRequest("/threads/t-1")).resolves.toBeUndefined();
|
|
290
249
|
expect(text).not.toHaveBeenCalled();
|
|
291
250
|
});
|
|
292
|
-
|
|
293
|
-
it("makeRequest returns undefined from a whitespace-only success body", async () => {
|
|
294
|
-
const fetchMock = vi.fn().mockResolvedValue({
|
|
295
|
-
ok: true,
|
|
296
|
-
status: 200,
|
|
297
|
-
headers: new Headers(),
|
|
298
|
-
text: vi.fn().mockResolvedValue(" "),
|
|
299
|
-
});
|
|
300
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
301
|
-
|
|
302
|
-
const api = new AssistantCloudAPI({
|
|
303
|
-
apiKey: "test-key",
|
|
304
|
-
userId: "u-1",
|
|
305
|
-
workspaceId: "w-1",
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
await expect(api.makeRequest("/threads/t-1")).resolves.toBeUndefined();
|
|
309
|
-
});
|
|
310
251
|
});
|
|
@@ -3,10 +3,14 @@ import {
|
|
|
3
3
|
AssistantCloudAnonymousAuthStrategy,
|
|
4
4
|
AssistantCloudJWTAuthStrategy,
|
|
5
5
|
} from "../AssistantCloudAuthStrategy";
|
|
6
|
+
import { CloudResponseError } from "../cloudResponse";
|
|
6
7
|
|
|
7
8
|
const baseUrl = "https://test.example.com";
|
|
8
9
|
const accessToken = `${Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url")}.${Buffer.from(JSON.stringify({ exp: 4102444800 })).toString("base64url")}.sig`;
|
|
9
|
-
const refreshToken = {
|
|
10
|
+
const refreshToken = {
|
|
11
|
+
token: "r1",
|
|
12
|
+
expires_at: "2099-01-01",
|
|
13
|
+
};
|
|
10
14
|
|
|
11
15
|
let originalLocalStorageDescriptor: PropertyDescriptor | undefined;
|
|
12
16
|
|
|
@@ -17,12 +21,12 @@ const installLocalStorage = (storage: Storage): void => {
|
|
|
17
21
|
});
|
|
18
22
|
};
|
|
19
23
|
|
|
20
|
-
const mockAnonymousTokenFetch = () => {
|
|
24
|
+
const mockAnonymousTokenFetch = (nextRefreshToken = refreshToken) => {
|
|
21
25
|
const fetchMock = vi.fn().mockResolvedValue({
|
|
22
26
|
ok: true,
|
|
23
27
|
json: vi.fn().mockResolvedValue({
|
|
24
28
|
access_token: accessToken,
|
|
25
|
-
refresh_token:
|
|
29
|
+
refresh_token: nextRefreshToken,
|
|
26
30
|
}),
|
|
27
31
|
});
|
|
28
32
|
vi.stubGlobal("fetch", fetchMock);
|
|
@@ -50,7 +54,14 @@ describe("AssistantCloudAnonymousAuthStrategy", () => {
|
|
|
50
54
|
}
|
|
51
55
|
});
|
|
52
56
|
|
|
53
|
-
it(
|
|
57
|
+
it.each([
|
|
58
|
+
"2099-01-01",
|
|
59
|
+
"2099-01-01T00:00:00Z",
|
|
60
|
+
"2099-01-01T00:00:00+0000",
|
|
61
|
+
"2099-01-01T00:00:00",
|
|
62
|
+
"2099-01-01 00:00:00+00",
|
|
63
|
+
])("persists refresh tokens with expiry %s", async (expiresAt) => {
|
|
64
|
+
const nextRefreshToken = { ...refreshToken, expires_at: expiresAt };
|
|
54
65
|
const values = new Map<string, string>();
|
|
55
66
|
installLocalStorage({
|
|
56
67
|
getItem: (key) => values.get(key) ?? null,
|
|
@@ -61,14 +72,16 @@ describe("AssistantCloudAnonymousAuthStrategy", () => {
|
|
|
61
72
|
values.delete(key);
|
|
62
73
|
},
|
|
63
74
|
} as Storage);
|
|
64
|
-
const fetchMock = mockAnonymousTokenFetch();
|
|
75
|
+
const fetchMock = mockAnonymousTokenFetch(nextRefreshToken);
|
|
65
76
|
|
|
66
77
|
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
|
|
67
78
|
|
|
68
79
|
await expect(strategy.getAuthHeaders()).resolves.toEqual({
|
|
69
80
|
Authorization: `Bearer ${accessToken}`,
|
|
70
81
|
});
|
|
71
|
-
expect(values.get("aui:refresh_token")).toBe(
|
|
82
|
+
expect(values.get("aui:refresh_token")).toBe(
|
|
83
|
+
JSON.stringify(nextRefreshToken),
|
|
84
|
+
);
|
|
72
85
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
73
86
|
`${baseUrl}/v1/auth/tokens/anonymous`,
|
|
74
87
|
{ method: "POST" },
|
|
@@ -170,6 +183,139 @@ describe("AssistantCloudAnonymousAuthStrategy", () => {
|
|
|
170
183
|
});
|
|
171
184
|
expect(values.get("aui:refresh_token")).toBe(JSON.stringify(refreshToken));
|
|
172
185
|
});
|
|
186
|
+
|
|
187
|
+
it("rejects malformed anonymous token responses without persisting them", async () => {
|
|
188
|
+
const setItem = vi.fn();
|
|
189
|
+
installLocalStorage({
|
|
190
|
+
getItem: () => null,
|
|
191
|
+
setItem,
|
|
192
|
+
removeItem: vi.fn(),
|
|
193
|
+
} as unknown as Storage);
|
|
194
|
+
vi.stubGlobal(
|
|
195
|
+
"fetch",
|
|
196
|
+
vi.fn().mockResolvedValue({
|
|
197
|
+
ok: true,
|
|
198
|
+
json: vi.fn().mockResolvedValue({
|
|
199
|
+
access_token: accessToken,
|
|
200
|
+
refresh_token: "not-a-refresh-token",
|
|
201
|
+
}),
|
|
202
|
+
}),
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
|
|
206
|
+
|
|
207
|
+
await expect(strategy.getAuthHeaders()).rejects.toThrow(
|
|
208
|
+
new CloudResponseError(
|
|
209
|
+
'Invalid Assistant Cloud response for "anonymous auth token response.refresh_token": expected an object',
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
expect(setItem).not.toHaveBeenCalled();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("rejects empty refresh token expiry without persisting it", async () => {
|
|
216
|
+
const setItem = vi.fn();
|
|
217
|
+
installLocalStorage({
|
|
218
|
+
getItem: () => null,
|
|
219
|
+
setItem,
|
|
220
|
+
removeItem: vi.fn(),
|
|
221
|
+
} as unknown as Storage);
|
|
222
|
+
vi.stubGlobal(
|
|
223
|
+
"fetch",
|
|
224
|
+
vi.fn().mockResolvedValue({
|
|
225
|
+
ok: true,
|
|
226
|
+
json: vi.fn().mockResolvedValue({
|
|
227
|
+
access_token: accessToken,
|
|
228
|
+
refresh_token: {
|
|
229
|
+
token: "r2",
|
|
230
|
+
expires_at: "",
|
|
231
|
+
},
|
|
232
|
+
}),
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
|
|
237
|
+
|
|
238
|
+
await expect(strategy.getAuthHeaders()).rejects.toThrow(
|
|
239
|
+
new CloudResponseError(
|
|
240
|
+
'Invalid Assistant Cloud response for "anonymous auth token response.refresh_token.expires_at": expected a non-empty string',
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
expect(setItem).not.toHaveBeenCalled();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("contextualizes malformed refresh token responses", async () => {
|
|
247
|
+
installLocalStorage({
|
|
248
|
+
getItem: () => JSON.stringify(refreshToken),
|
|
249
|
+
setItem: vi.fn(),
|
|
250
|
+
removeItem: vi.fn(),
|
|
251
|
+
} as unknown as Storage);
|
|
252
|
+
vi.stubGlobal(
|
|
253
|
+
"fetch",
|
|
254
|
+
vi.fn().mockResolvedValue({
|
|
255
|
+
ok: true,
|
|
256
|
+
json: vi.fn().mockResolvedValue({ access_token: 123 }),
|
|
257
|
+
}),
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
|
|
261
|
+
|
|
262
|
+
await expect(strategy.getAuthHeaders()).rejects.toThrow(
|
|
263
|
+
new CloudResponseError(
|
|
264
|
+
'Invalid Assistant Cloud response for "refresh auth token response.access_token": expected a string',
|
|
265
|
+
),
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("accepts valid refresh responses without a rotated refresh token", async () => {
|
|
270
|
+
const setItem = vi.fn();
|
|
271
|
+
installLocalStorage({
|
|
272
|
+
getItem: () => JSON.stringify(refreshToken),
|
|
273
|
+
setItem,
|
|
274
|
+
removeItem: vi.fn(),
|
|
275
|
+
} as unknown as Storage);
|
|
276
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
277
|
+
ok: true,
|
|
278
|
+
json: vi.fn().mockResolvedValue({
|
|
279
|
+
access_token: accessToken,
|
|
280
|
+
refresh_token: null,
|
|
281
|
+
}),
|
|
282
|
+
});
|
|
283
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
284
|
+
|
|
285
|
+
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
|
|
286
|
+
|
|
287
|
+
await expect(strategy.getAuthHeaders()).resolves.toEqual({
|
|
288
|
+
Authorization: `Bearer ${accessToken}`,
|
|
289
|
+
});
|
|
290
|
+
expect(fetchMock).toHaveBeenCalledWith(
|
|
291
|
+
`${baseUrl}/v1/auth/tokens/refresh`,
|
|
292
|
+
{
|
|
293
|
+
method: "POST",
|
|
294
|
+
headers: { "Content-Type": "application/json" },
|
|
295
|
+
body: JSON.stringify({ refresh_token: refreshToken.token }),
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
expect(setItem).not.toHaveBeenCalled();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("contextualizes invalid JSON token responses", async () => {
|
|
302
|
+
delete (globalThis as { localStorage?: Storage }).localStorage;
|
|
303
|
+
vi.stubGlobal(
|
|
304
|
+
"fetch",
|
|
305
|
+
vi.fn().mockResolvedValue({
|
|
306
|
+
ok: true,
|
|
307
|
+
json: vi.fn().mockRejectedValue(new SyntaxError("Unexpected token")),
|
|
308
|
+
}),
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const strategy = new AssistantCloudAnonymousAuthStrategy(baseUrl);
|
|
312
|
+
|
|
313
|
+
await expect(strategy.getAuthHeaders()).rejects.toThrow(
|
|
314
|
+
new CloudResponseError(
|
|
315
|
+
'Invalid Assistant Cloud response for "anonymous auth token response": expected valid JSON',
|
|
316
|
+
),
|
|
317
|
+
);
|
|
318
|
+
});
|
|
173
319
|
});
|
|
174
320
|
|
|
175
321
|
describe("AssistantCloudJWTAuthStrategy", () => {
|