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.
Files changed (37) hide show
  1. package/dist/AssistantCloudAPI.d.ts.map +1 -1
  2. package/dist/AssistantCloudAPI.js.map +1 -1
  3. package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
  4. package/dist/AssistantCloudAuthStrategy.js +32 -7
  5. package/dist/AssistantCloudAuthStrategy.js.map +1 -1
  6. package/dist/AssistantCloudAuthTokens.d.ts.map +1 -1
  7. package/dist/AssistantCloudAuthTokens.js.map +1 -1
  8. package/dist/AssistantCloudFiles.d.ts.map +1 -1
  9. package/dist/AssistantCloudFiles.js.map +1 -1
  10. package/dist/AssistantCloudProjectThreadMessages.d.ts.map +1 -1
  11. package/dist/AssistantCloudProjectThreadMessages.js.map +1 -1
  12. package/dist/AssistantCloudProjectThreads.d.ts +1 -1
  13. package/dist/AssistantCloudProjectThreads.d.ts.map +1 -1
  14. package/dist/AssistantCloudProjectThreads.js +1 -1
  15. package/dist/AssistantCloudProjectThreads.js.map +1 -1
  16. package/dist/AssistantCloudRuns.d.ts.map +1 -1
  17. package/dist/AssistantCloudRuns.js.map +1 -1
  18. package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
  19. package/dist/AssistantCloudThreadMessages.js.map +1 -1
  20. package/dist/AssistantCloudThreads.d.ts +1 -1
  21. package/dist/AssistantCloudThreads.d.ts.map +1 -1
  22. package/dist/AssistantCloudThreads.js +1 -1
  23. package/dist/AssistantCloudThreads.js.map +1 -1
  24. package/dist/index.js +1 -1
  25. package/package.json +3 -3
  26. package/src/AssistantCloudAPI.ts +4 -4
  27. package/src/AssistantCloudAuthStrategy.ts +82 -19
  28. package/src/AssistantCloudAuthTokens.ts +5 -1
  29. package/src/AssistantCloudFiles.ts +5 -1
  30. package/src/AssistantCloudProjectThreadMessages.ts +5 -1
  31. package/src/AssistantCloudProjectThreads.ts +4 -1
  32. package/src/AssistantCloudRuns.ts +5 -1
  33. package/src/AssistantCloudThreadMessages.ts +5 -1
  34. package/src/AssistantCloudThreads.ts +4 -1
  35. package/src/tests/AssistantCloudAPI.test.ts +25 -84
  36. package/src/tests/AssistantCloudAuthStrategy.test.ts +152 -6
  37. 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
- constructor(private cloud: AssistantCloudAPI) {}
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
- constructor(private cloud: AssistantCloudAPI) {}
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
- constructor(private cloud: AssistantCloudAPI) {
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 = { token: "r1", expires_at: "2099-01-01" };
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: refreshToken,
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("persists the refresh token and returns the anonymous access token", async () => {
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(JSON.stringify(refreshToken));
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", () => {