assistant-cloud 0.1.42 → 0.2.0

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 (81) hide show
  1. package/README.md +40 -0
  2. package/dist/AssistantCloud.d.ts +4 -0
  3. package/dist/AssistantCloud.d.ts.map +1 -1
  4. package/dist/AssistantCloud.js +11 -5
  5. package/dist/AssistantCloud.js.map +1 -1
  6. package/dist/AssistantCloudAPI.d.ts +17 -1
  7. package/dist/AssistantCloudAPI.d.ts.map +1 -1
  8. package/dist/AssistantCloudAPI.js +20 -9
  9. package/dist/AssistantCloudAPI.js.map +1 -1
  10. package/dist/AssistantCloudAuthStrategy.d.ts +4 -1
  11. package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
  12. package/dist/AssistantCloudAuthStrategy.js +79 -18
  13. package/dist/AssistantCloudAuthStrategy.js.map +1 -1
  14. package/dist/AssistantCloudEvents.d.ts +32 -0
  15. package/dist/AssistantCloudEvents.d.ts.map +1 -0
  16. package/dist/AssistantCloudEvents.js +115 -0
  17. package/dist/AssistantCloudEvents.js.map +1 -0
  18. package/dist/AssistantCloudFiles.d.ts +12 -1
  19. package/dist/AssistantCloudFiles.d.ts.map +1 -1
  20. package/dist/AssistantCloudFiles.js +13 -1
  21. package/dist/AssistantCloudFiles.js.map +1 -1
  22. package/dist/AssistantCloudRuns.d.ts +11 -0
  23. package/dist/AssistantCloudRuns.d.ts.map +1 -1
  24. package/dist/AssistantCloudRuns.js.map +1 -1
  25. package/dist/AssistantCloudScores.d.ts +27 -0
  26. package/dist/AssistantCloudScores.d.ts.map +1 -0
  27. package/dist/AssistantCloudScores.js +30 -0
  28. package/dist/AssistantCloudScores.js.map +1 -0
  29. package/dist/AssistantCloudThreadMessages.d.ts +11 -1
  30. package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
  31. package/dist/AssistantCloudThreadMessages.js +12 -1
  32. package/dist/AssistantCloudThreadMessages.js.map +1 -1
  33. package/dist/AssistantCloudThreads.d.ts +8 -0
  34. package/dist/AssistantCloudThreads.d.ts.map +1 -1
  35. package/dist/AssistantCloudThreads.js +9 -1
  36. package/dist/AssistantCloudThreads.js.map +1 -1
  37. package/dist/CloudMessagePersistence.d.ts +4 -0
  38. package/dist/CloudMessagePersistence.d.ts.map +1 -1
  39. package/dist/CloudMessagePersistence.js +53 -18
  40. package/dist/CloudMessagePersistence.js.map +1 -1
  41. package/dist/cloudResponse.d.ts +4 -1
  42. package/dist/cloudResponse.d.ts.map +1 -1
  43. package/dist/cloudResponse.js +14 -1
  44. package/dist/cloudResponse.js.map +1 -1
  45. package/dist/index.d.ts +7 -3
  46. package/dist/index.js +5 -2
  47. package/dist/runTelemetry.d.ts +79 -9
  48. package/dist/runTelemetry.d.ts.map +1 -1
  49. package/dist/runTelemetry.js +152 -6
  50. package/dist/runTelemetry.js.map +1 -1
  51. package/dist/telemetry/index.d.ts +32 -0
  52. package/dist/telemetry/index.d.ts.map +1 -0
  53. package/dist/telemetry/index.js +62 -0
  54. package/dist/telemetry/index.js.map +1 -0
  55. package/package.json +28 -4
  56. package/src/AssistantCloud.ts +20 -8
  57. package/src/AssistantCloudAPI.ts +42 -10
  58. package/src/AssistantCloudAuthStrategy.ts +153 -46
  59. package/src/AssistantCloudEvents.test.ts +124 -0
  60. package/src/AssistantCloudEvents.ts +185 -0
  61. package/src/AssistantCloudFiles.test.ts +59 -0
  62. package/src/AssistantCloudFiles.ts +39 -0
  63. package/src/AssistantCloudRuns.ts +12 -1
  64. package/src/AssistantCloudScores.test.ts +113 -0
  65. package/src/AssistantCloudScores.ts +61 -0
  66. package/src/AssistantCloudThreadMessages.test.ts +51 -0
  67. package/src/AssistantCloudThreadMessages.ts +33 -0
  68. package/src/AssistantCloudThreads.test.ts +21 -0
  69. package/src/AssistantCloudThreads.ts +21 -0
  70. package/src/CloudMessagePersistence.ts +65 -29
  71. package/src/cloudResponse.test.ts +34 -1
  72. package/src/cloudResponse.ts +30 -0
  73. package/src/index.ts +24 -1
  74. package/src/runTelemetry.test.ts +366 -0
  75. package/src/runTelemetry.ts +271 -16
  76. package/src/telemetry/index.test.ts +252 -0
  77. package/src/telemetry/index.ts +98 -0
  78. package/src/tests/AssistantCloud.test.ts +22 -0
  79. package/src/tests/AssistantCloudAPI.test.ts +44 -4
  80. package/src/tests/AssistantCloudAuthStrategy.test.ts +354 -11
  81. package/src/tests/CloudMessagePersistence.test.ts +208 -0
@@ -14,6 +14,18 @@ function createMockCloud() {
14
14
  } as unknown as AssistantCloud;
15
15
  }
16
16
 
17
+ function createCloudMessages(count: number) {
18
+ return Array.from({ length: count }, (_, index) => ({
19
+ id: `message-${index + 1}`,
20
+ parent_id: null,
21
+ height: index,
22
+ created_at: "2025-01-01T00:00:00Z" as unknown as Date,
23
+ updated_at: "2025-01-01T00:00:00Z" as unknown as Date,
24
+ format: "aui/v0",
25
+ content: {},
26
+ }));
27
+ }
28
+
17
29
  describe("CloudMessagePersistence", () => {
18
30
  let cloud: AssistantCloud;
19
31
  let persistence: CloudMessagePersistence;
@@ -35,8 +47,29 @@ describe("CloudMessagePersistence", () => {
35
47
 
36
48
  expect(persistence.isPersisted("local-1")).toBe(true);
37
49
  expect(await persistence.getRemoteId("local-1")).toBe("remote-1");
50
+ expect(persistence.getResolvedRemoteId("local-1")).toBe("remote-1");
38
51
  });
39
52
 
53
+ it.each(["__proto__", "constructor", "toString"])(
54
+ "supports prototype-named local ID %s",
55
+ async (messageId) => {
56
+ vi.mocked(cloud.threads.messages.create).mockResolvedValue({
57
+ message_id: `remote-${messageId}`,
58
+ });
59
+
60
+ expect(persistence.isPersisted(messageId)).toBe(false);
61
+
62
+ await persistence.append("thread-1", messageId, null, "aui/v0", {
63
+ text: "hello",
64
+ });
65
+
66
+ expect(persistence.isPersisted(messageId)).toBe(true);
67
+ expect(await persistence.getRemoteId(messageId)).toBe(
68
+ `remote-${messageId}`,
69
+ );
70
+ },
71
+ );
72
+
40
73
  it("uses the current client without losing ID mappings", async () => {
41
74
  const firstCloud = createMockCloud();
42
75
  const secondCloud = createMockCloud();
@@ -108,6 +141,99 @@ describe("CloudMessagePersistence", () => {
108
141
  });
109
142
  });
110
143
 
144
+ it("deduplicates concurrent child appends while the parent is pending", async () => {
145
+ let resolveParent!: (value: { message_id: string }) => void;
146
+ vi.mocked(cloud.threads.messages.create)
147
+ .mockImplementationOnce(
148
+ () =>
149
+ new Promise((resolve) => {
150
+ resolveParent = resolve;
151
+ }),
152
+ )
153
+ .mockResolvedValue({ message_id: "remote-child" });
154
+
155
+ const parent = persistence.append("thread-1", "parent", null, "aui/v0", {
156
+ text: "parent",
157
+ });
158
+ const firstChild = persistence.append(
159
+ "thread-1",
160
+ "child",
161
+ "parent",
162
+ "aui/v0",
163
+ { text: "child" },
164
+ );
165
+ const secondChild = persistence.append(
166
+ "thread-1",
167
+ "child",
168
+ "parent",
169
+ "aui/v0",
170
+ { text: "child" },
171
+ );
172
+
173
+ expect(persistence.isPersisted("child")).toBe(true);
174
+ resolveParent({ message_id: "remote-parent" });
175
+ await Promise.all([parent, firstChild, secondChild]);
176
+
177
+ expect(cloud.threads.messages.create).toHaveBeenCalledTimes(2);
178
+ });
179
+
180
+ it("resolves remote IDs throughout a concurrent message chain", async () => {
181
+ let resolveParent!: (value: { message_id: string }) => void;
182
+ vi.mocked(cloud.threads.messages.create)
183
+ .mockImplementationOnce(
184
+ () =>
185
+ new Promise((resolve) => {
186
+ resolveParent = resolve;
187
+ }),
188
+ )
189
+ .mockResolvedValueOnce({ message_id: "remote-child" })
190
+ .mockResolvedValueOnce({ message_id: "remote-grandchild" });
191
+
192
+ const parent = persistence.append("thread-1", "parent", null, "aui/v0", {
193
+ text: "parent",
194
+ });
195
+ const child = persistence.append("thread-1", "child", "parent", "aui/v0", {
196
+ text: "child",
197
+ });
198
+ const grandchild = persistence.append(
199
+ "thread-1",
200
+ "grandchild",
201
+ "child",
202
+ "aui/v0",
203
+ { text: "grandchild" },
204
+ );
205
+
206
+ resolveParent({ message_id: "remote-parent" });
207
+ await Promise.all([parent, child, grandchild]);
208
+
209
+ expect(cloud.threads.messages.create).toHaveBeenCalledWith("thread-1", {
210
+ parent_id: "remote-parent",
211
+ format: "aui/v0",
212
+ content: { text: "child" },
213
+ });
214
+ expect(cloud.threads.messages.create).toHaveBeenCalledWith("thread-1", {
215
+ parent_id: "remote-child",
216
+ format: "aui/v0",
217
+ content: { text: "grandchild" },
218
+ });
219
+ });
220
+
221
+ it("re-appends a message after its mapping has settled", async () => {
222
+ vi.mocked(cloud.threads.messages.create)
223
+ .mockResolvedValueOnce({ message_id: "remote-1" })
224
+ .mockResolvedValueOnce({ message_id: "remote-2" });
225
+
226
+ await persistence.append("thread-1", "local-1", null, "aui/v0", {
227
+ text: "first",
228
+ });
229
+ await persistence.append("thread-1", "local-1", null, "aui/v0", {
230
+ text: "second",
231
+ });
232
+
233
+ expect(cloud.threads.messages.create).toHaveBeenCalledTimes(2);
234
+ expect(await persistence.getRemoteId("local-1")).toBe("remote-2");
235
+ });
236
+
111
237
  it("loaded messages are marked as persisted and not re-created", async () => {
112
238
  vi.mocked(cloud.threads.messages.list).mockResolvedValue({
113
239
  messages: [
@@ -128,6 +254,88 @@ describe("CloudMessagePersistence", () => {
128
254
  expect(persistence.isPersisted("msg-1")).toBe(true);
129
255
  });
130
256
 
257
+ it("maps prototype-named loaded messages", async () => {
258
+ vi.mocked(cloud.threads.messages.list).mockResolvedValue({
259
+ messages: [
260
+ {
261
+ id: "__proto__",
262
+ parent_id: null,
263
+ height: 0,
264
+ created_at: "2025-01-01T00:00:00Z" as unknown as Date,
265
+ updated_at: "2025-01-01T00:00:00Z" as unknown as Date,
266
+ format: "aui/v0",
267
+ content: { text: "loaded" },
268
+ },
269
+ ],
270
+ });
271
+
272
+ await persistence.load("thread-1");
273
+
274
+ expect(persistence.isPersisted("__proto__")).toBe(true);
275
+ expect(await persistence.getRemoteId("__proto__")).toBe("__proto__");
276
+ });
277
+
278
+ it("follows the message cursor across pages in server order", async () => {
279
+ const rows = createCloudMessages(450);
280
+ vi.mocked(cloud.threads.messages.list).mockImplementation(
281
+ async (_threadId, query) => {
282
+ const start = query?.after
283
+ ? rows.findIndex((row) => row.id === query.after) + 1
284
+ : 0;
285
+ return { messages: rows.slice(start, start + 200) };
286
+ },
287
+ );
288
+
289
+ const messages = await persistence.load("thread-1", "aui/v0");
290
+
291
+ expect(messages.map((message) => message.id)).toEqual(
292
+ rows.map((row) => row.id),
293
+ );
294
+ expect(cloud.threads.messages.list).toHaveBeenCalledTimes(3);
295
+ expect(cloud.threads.messages.list).toHaveBeenNthCalledWith(1, "thread-1", {
296
+ format: "aui/v0",
297
+ limit: 200,
298
+ });
299
+ expect(cloud.threads.messages.list).toHaveBeenNthCalledWith(3, "thread-1", {
300
+ format: "aui/v0",
301
+ limit: 200,
302
+ after: "message-400",
303
+ });
304
+ });
305
+
306
+ it("stops without duplicates when a later cursor stops resolving", async () => {
307
+ const rows = createCloudMessages(450);
308
+ vi.mocked(cloud.threads.messages.list).mockImplementation(
309
+ async (_threadId, query) => {
310
+ // The third request replays page one, the way the endpoint answers a
311
+ // cursor whose message no longer resolves.
312
+ const start =
313
+ query?.after && query.after !== "message-400"
314
+ ? rows.findIndex((row) => row.id === query.after) + 1
315
+ : 0;
316
+ return { messages: rows.slice(start, start + 200) };
317
+ },
318
+ );
319
+
320
+ const messages = await persistence.load("thread-1", "aui/v0");
321
+
322
+ expect(messages.map((message) => message.id)).toEqual(
323
+ rows.slice(0, 400).map((row) => row.id),
324
+ );
325
+ expect(cloud.threads.messages.list).toHaveBeenCalledTimes(3);
326
+ });
327
+
328
+ it("stops when a page does not advance the cursor", async () => {
329
+ vi.mocked(cloud.threads.messages.list).mockResolvedValue({
330
+ messages: createCloudMessages(200),
331
+ });
332
+
333
+ const messages = await persistence.load("thread-1", "aui/v0");
334
+
335
+ expect(messages).toHaveLength(200);
336
+ expect(cloud.threads.messages.list).toHaveBeenCalledTimes(2);
337
+ });
338
+
131
339
  it("updates an already-persisted message", async () => {
132
340
  vi.mocked(cloud.threads.messages.create).mockResolvedValue({
133
341
  message_id: "remote-1",