assistant-cloud 0.1.43 → 0.2.1

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 (120) hide show
  1. package/README.md +66 -0
  2. package/dist/AssistantCloud.d.ts +6 -1
  3. package/dist/AssistantCloud.d.ts.map +1 -1
  4. package/dist/AssistantCloud.js +13 -5
  5. package/dist/AssistantCloud.js.map +1 -1
  6. package/dist/AssistantCloudAPI.d.ts +28 -3
  7. package/dist/AssistantCloudAPI.d.ts.map +1 -1
  8. package/dist/AssistantCloudAPI.js +37 -10
  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 +15 -3
  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 +119 -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 +12 -0
  23. package/dist/AssistantCloudRuns.d.ts.map +1 -1
  24. package/dist/AssistantCloudRuns.js +2 -1
  25. package/dist/AssistantCloudRuns.js.map +1 -1
  26. package/dist/AssistantCloudScores.d.ts +27 -0
  27. package/dist/AssistantCloudScores.d.ts.map +1 -0
  28. package/dist/AssistantCloudScores.js +30 -0
  29. package/dist/AssistantCloudScores.js.map +1 -0
  30. package/dist/AssistantCloudThreadMessages.d.ts +11 -1
  31. package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
  32. package/dist/AssistantCloudThreadMessages.js +12 -1
  33. package/dist/AssistantCloudThreadMessages.js.map +1 -1
  34. package/dist/AssistantCloudThreads.d.ts +8 -0
  35. package/dist/AssistantCloudThreads.d.ts.map +1 -1
  36. package/dist/AssistantCloudThreads.js +9 -1
  37. package/dist/AssistantCloudThreads.js.map +1 -1
  38. package/dist/CloudEngagementReporter.d.ts +74 -0
  39. package/dist/CloudEngagementReporter.d.ts.map +1 -0
  40. package/dist/CloudEngagementReporter.js +132 -0
  41. package/dist/CloudEngagementReporter.js.map +1 -0
  42. package/dist/CloudMessagePersistence.d.ts +12 -9
  43. package/dist/CloudMessagePersistence.d.ts.map +1 -1
  44. package/dist/CloudMessagePersistence.js +54 -20
  45. package/dist/CloudMessagePersistence.js.map +1 -1
  46. package/dist/CloudRunReporter.d.ts +20 -0
  47. package/dist/CloudRunReporter.d.ts.map +1 -0
  48. package/dist/CloudRunReporter.js +38 -0
  49. package/dist/CloudRunReporter.js.map +1 -0
  50. package/dist/FormattedCloudPersistence.d.ts +3 -8
  51. package/dist/FormattedCloudPersistence.d.ts.map +1 -1
  52. package/dist/FormattedCloudPersistence.js +3 -8
  53. package/dist/FormattedCloudPersistence.js.map +1 -1
  54. package/dist/ai-sdk/index.d.ts +33 -0
  55. package/dist/ai-sdk/index.d.ts.map +1 -0
  56. package/dist/ai-sdk/index.js +147 -0
  57. package/dist/ai-sdk/index.js.map +1 -0
  58. package/dist/cloudResponse.d.ts +4 -1
  59. package/dist/cloudResponse.d.ts.map +1 -1
  60. package/dist/cloudResponse.js +14 -1
  61. package/dist/cloudResponse.js.map +1 -1
  62. package/dist/generateThreadTitle.js +1 -0
  63. package/dist/generateThreadTitle.js.map +1 -1
  64. package/dist/index.d.ts +10 -4
  65. package/dist/index.js +7 -2
  66. package/dist/instrumentMcpSampling.d.ts.map +1 -1
  67. package/dist/instrumentMcpSampling.js +12 -2
  68. package/dist/instrumentMcpSampling.js.map +1 -1
  69. package/dist/runTelemetry.d.ts +86 -7
  70. package/dist/runTelemetry.d.ts.map +1 -1
  71. package/dist/runTelemetry.js +144 -1
  72. package/dist/runTelemetry.js.map +1 -1
  73. package/dist/telemetry/index.d.ts +32 -0
  74. package/dist/telemetry/index.d.ts.map +1 -0
  75. package/dist/telemetry/index.js +62 -0
  76. package/dist/telemetry/index.js.map +1 -0
  77. package/dist/version.d.ts +5 -0
  78. package/dist/version.d.ts.map +1 -0
  79. package/dist/version.js +6 -0
  80. package/dist/version.js.map +1 -0
  81. package/package.json +43 -6
  82. package/src/AssistantCloud.ts +23 -8
  83. package/src/AssistantCloudAPI.ts +72 -10
  84. package/src/AssistantCloudAuthStrategy.ts +17 -2
  85. package/src/AssistantCloudEvents.test.ts +149 -0
  86. package/src/AssistantCloudEvents.ts +190 -0
  87. package/src/AssistantCloudFiles.test.ts +59 -0
  88. package/src/AssistantCloudFiles.ts +39 -0
  89. package/src/AssistantCloudRuns.ts +13 -1
  90. package/src/AssistantCloudScores.test.ts +113 -0
  91. package/src/AssistantCloudScores.ts +61 -0
  92. package/src/AssistantCloudThreadMessages.test.ts +51 -0
  93. package/src/AssistantCloudThreadMessages.ts +33 -0
  94. package/src/AssistantCloudThreads.test.ts +21 -0
  95. package/src/AssistantCloudThreads.ts +21 -0
  96. package/src/CloudEngagementReporter.ts +231 -0
  97. package/src/CloudMessagePersistence.ts +70 -27
  98. package/src/CloudRunReporter.ts +38 -0
  99. package/src/FormattedCloudPersistence.ts +3 -8
  100. package/src/ai-sdk/index.test.ts +258 -0
  101. package/src/ai-sdk/index.ts +222 -0
  102. package/src/cloudResponse.test.ts +34 -1
  103. package/src/cloudResponse.ts +30 -0
  104. package/src/generateThreadTitle.test.ts +32 -0
  105. package/src/generateThreadTitle.ts +1 -0
  106. package/src/index.ts +35 -2
  107. package/src/instrumentMcpSampling.test.ts +102 -0
  108. package/src/instrumentMcpSampling.ts +16 -2
  109. package/src/runTelemetry.test.ts +325 -0
  110. package/src/runTelemetry.ts +274 -6
  111. package/src/telemetry/index.test.ts +252 -0
  112. package/src/telemetry/index.ts +98 -0
  113. package/src/tests/AssistantCloud.test.ts +55 -1
  114. package/src/tests/AssistantCloudAPI.test.ts +95 -4
  115. package/src/tests/AssistantCloudAuthStrategy.test.ts +93 -2
  116. package/src/tests/AssistantCloudProjects.test.ts +1 -5
  117. package/src/tests/CloudEngagementReporter.test.ts +184 -0
  118. package/src/tests/CloudMessagePersistence.test.ts +350 -0
  119. package/src/tests/CloudRunReporter.test.ts +100 -0
  120. package/src/version.ts +4 -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();
@@ -219,6 +252,299 @@ describe("CloudMessagePersistence", () => {
219
252
  await persistence.load("thread-1");
220
253
 
221
254
  expect(persistence.isPersisted("msg-1")).toBe(true);
255
+ expect(await persistence.getRemoteId("msg-1")).toBe("msg-1");
256
+ });
257
+
258
+ it("preserves a pending append mapping when load returns the same ID", async () => {
259
+ const messages = createCloudMessages(1);
260
+ let resolveLoad!: (value: { messages: typeof messages }) => void;
261
+ let resolveAppend!: (value: { message_id: string }) => void;
262
+ vi.mocked(cloud.threads.messages.list).mockImplementationOnce(
263
+ () =>
264
+ new Promise((resolve) => {
265
+ resolveLoad = resolve;
266
+ }),
267
+ );
268
+ vi.mocked(cloud.threads.messages.create).mockImplementationOnce(
269
+ () =>
270
+ new Promise((resolve) => {
271
+ resolveAppend = resolve;
272
+ }),
273
+ );
274
+
275
+ const load = persistence.load("thread-1");
276
+ const append = persistence.append(
277
+ "thread-1",
278
+ "message-1",
279
+ null,
280
+ "aui/v0",
281
+ {},
282
+ );
283
+ resolveLoad({ messages });
284
+ await load;
285
+ resolveAppend({ message_id: "remote-2" });
286
+ await append;
287
+
288
+ expect(await persistence.getRemoteId("message-1")).toBe("remote-2");
289
+ await persistence.update("thread-1", "message-1", "aui/v0", {
290
+ text: "updated",
291
+ });
292
+ expect(cloud.threads.messages.update).toHaveBeenCalledWith(
293
+ "thread-1",
294
+ "remote-2",
295
+ { content: { text: "updated" } },
296
+ );
297
+ });
298
+
299
+ it("preserves a resolved append mapping when load returns the same ID", async () => {
300
+ const messages = createCloudMessages(1);
301
+ vi.mocked(cloud.threads.messages.create).mockResolvedValue({
302
+ message_id: "remote-1",
303
+ });
304
+ vi.mocked(cloud.threads.messages.list).mockResolvedValue({
305
+ messages,
306
+ });
307
+
308
+ await persistence.append("thread-1", "message-1", null, "aui/v0", {});
309
+ await persistence.load("thread-1");
310
+
311
+ expect(await persistence.getRemoteId("message-1")).toBe("remote-1");
312
+ });
313
+
314
+ it("keeps the loaded ID when a pending append for it fails", async () => {
315
+ const messages = createCloudMessages(1);
316
+ const failure = new Error("create failed");
317
+ let resolveLoad!: (value: { messages: typeof messages }) => void;
318
+ let rejectAppend!: (error: Error) => void;
319
+ vi.mocked(cloud.threads.messages.list).mockImplementationOnce(
320
+ () =>
321
+ new Promise((resolve) => {
322
+ resolveLoad = resolve;
323
+ }),
324
+ );
325
+ vi.mocked(cloud.threads.messages.create).mockImplementationOnce(
326
+ () =>
327
+ new Promise((_resolve, reject) => {
328
+ rejectAppend = reject;
329
+ }),
330
+ );
331
+
332
+ const load = persistence.load("thread-1");
333
+ const append = persistence.append(
334
+ "thread-1",
335
+ "message-1",
336
+ null,
337
+ "aui/v0",
338
+ {},
339
+ );
340
+ resolveLoad({ messages });
341
+ await load;
342
+ rejectAppend(failure);
343
+
344
+ await expect(append).rejects.toBe(failure);
345
+ expect(persistence.isPersisted("message-1")).toBe(true);
346
+ expect(await persistence.getRemoteId("message-1")).toBe("message-1");
347
+ });
348
+
349
+ it("does not restore a loaded ID after reset when its pending append fails", async () => {
350
+ const messages = createCloudMessages(1);
351
+ const failure = new Error("create failed");
352
+ let rejectAppend!: (error: Error) => void;
353
+ vi.mocked(cloud.threads.messages.list).mockResolvedValue({ messages });
354
+ vi.mocked(cloud.threads.messages.create).mockImplementationOnce(
355
+ () =>
356
+ new Promise((_resolve, reject) => {
357
+ rejectAppend = reject;
358
+ }),
359
+ );
360
+
361
+ const append = persistence.append(
362
+ "thread-1",
363
+ "message-1",
364
+ null,
365
+ "aui/v0",
366
+ {},
367
+ );
368
+ await persistence.load("thread-1");
369
+ persistence.reset();
370
+ rejectAppend(failure);
371
+
372
+ await expect(append).rejects.toBe(failure);
373
+ expect(persistence.isPersisted("message-1")).toBe(false);
374
+ });
375
+
376
+ it("does not restore IDs from a load that finishes after reset", async () => {
377
+ const oldMessages = createCloudMessages(1);
378
+ const newMessages = [{ ...oldMessages[0]!, id: "new-message" }];
379
+ let resolveOld!: (value: { messages: typeof oldMessages }) => void;
380
+ vi.mocked(cloud.threads.messages.list)
381
+ .mockImplementationOnce(
382
+ () =>
383
+ new Promise((resolve) => {
384
+ resolveOld = resolve;
385
+ }),
386
+ )
387
+ .mockResolvedValueOnce({ messages: newMessages });
388
+
389
+ const oldLoad = persistence.load("old-thread");
390
+ persistence.reset();
391
+ await persistence.load("new-thread");
392
+ resolveOld({ messages: oldMessages });
393
+
394
+ expect(await oldLoad).toEqual(oldMessages);
395
+ expect(persistence.isPersisted("message-1")).toBe(false);
396
+ expect(persistence.isPersisted("new-message")).toBe(true);
397
+ });
398
+
399
+ it("keeps a new append mapping when an old load returns the same ID", async () => {
400
+ const messages = createCloudMessages(1);
401
+ let resolveOld!: (value: { messages: typeof messages }) => void;
402
+ vi.mocked(cloud.threads.messages.list).mockImplementationOnce(
403
+ () =>
404
+ new Promise((resolve) => {
405
+ resolveOld = resolve;
406
+ }),
407
+ );
408
+ vi.mocked(cloud.threads.messages.create).mockResolvedValue({
409
+ message_id: "new-remote-id",
410
+ });
411
+
412
+ const oldLoad = persistence.load("old-thread");
413
+ persistence.reset();
414
+ await persistence.append("new-thread", "message-1", null, "aui/v0", {});
415
+ resolveOld({ messages });
416
+ await oldLoad;
417
+
418
+ expect(await persistence.getRemoteId("message-1")).toBe("new-remote-id");
419
+ });
420
+
421
+ it("does not populate any old IDs when reset happens between history pages", async () => {
422
+ const messages = createCloudMessages(201);
423
+ let resolveLast!: (value: { messages: typeof messages }) => void;
424
+ vi.mocked(cloud.threads.messages.list)
425
+ .mockResolvedValueOnce({ messages: messages.slice(0, 200) })
426
+ .mockImplementationOnce(
427
+ () =>
428
+ new Promise((resolve) => {
429
+ resolveLast = resolve;
430
+ }),
431
+ );
432
+
433
+ const load = persistence.load("old-thread");
434
+ await vi.waitFor(() =>
435
+ expect(cloud.threads.messages.list).toHaveBeenCalledTimes(2),
436
+ );
437
+ persistence.reset();
438
+ resolveLast({ messages: messages.slice(200) });
439
+
440
+ expect(await load).toEqual(messages);
441
+ expect(
442
+ messages.some((message) => persistence.isPersisted(message.id)),
443
+ ).toBe(false);
444
+ });
445
+
446
+ it("propagates an old load failure without disturbing post-reset state", async () => {
447
+ let rejectOld!: (error: Error) => void;
448
+ vi.mocked(cloud.threads.messages.list).mockImplementationOnce(
449
+ () =>
450
+ new Promise((_resolve, reject) => {
451
+ rejectOld = reject;
452
+ }),
453
+ );
454
+ vi.mocked(cloud.threads.messages.create).mockResolvedValue({
455
+ message_id: "new-remote-id",
456
+ });
457
+ const failure = new Error("history failed");
458
+ const oldLoad = persistence.load("old-thread");
459
+ const rejected = expect(oldLoad).rejects.toBe(failure);
460
+ persistence.reset();
461
+ await persistence.append("new-thread", "new-message", null, "aui/v0", {});
462
+ rejectOld(failure);
463
+
464
+ await rejected;
465
+ expect(await persistence.getRemoteId("new-message")).toBe("new-remote-id");
466
+ });
467
+
468
+ it("maps prototype-named loaded messages", async () => {
469
+ vi.mocked(cloud.threads.messages.list).mockResolvedValue({
470
+ messages: [
471
+ {
472
+ id: "__proto__",
473
+ parent_id: null,
474
+ height: 0,
475
+ created_at: "2025-01-01T00:00:00Z" as unknown as Date,
476
+ updated_at: "2025-01-01T00:00:00Z" as unknown as Date,
477
+ format: "aui/v0",
478
+ content: { text: "loaded" },
479
+ },
480
+ ],
481
+ });
482
+
483
+ await persistence.load("thread-1");
484
+
485
+ expect(persistence.isPersisted("__proto__")).toBe(true);
486
+ expect(await persistence.getRemoteId("__proto__")).toBe("__proto__");
487
+ });
488
+
489
+ it("follows the message cursor across pages in server order", async () => {
490
+ const rows = createCloudMessages(450);
491
+ vi.mocked(cloud.threads.messages.list).mockImplementation(
492
+ async (_threadId, query) => {
493
+ const start = query?.after
494
+ ? rows.findIndex((row) => row.id === query.after) + 1
495
+ : 0;
496
+ return { messages: rows.slice(start, start + 200) };
497
+ },
498
+ );
499
+
500
+ const messages = await persistence.load("thread-1", "aui/v0");
501
+
502
+ expect(messages.map((message) => message.id)).toEqual(
503
+ rows.map((row) => row.id),
504
+ );
505
+ expect(cloud.threads.messages.list).toHaveBeenCalledTimes(3);
506
+ expect(cloud.threads.messages.list).toHaveBeenNthCalledWith(1, "thread-1", {
507
+ format: "aui/v0",
508
+ limit: 200,
509
+ });
510
+ expect(cloud.threads.messages.list).toHaveBeenNthCalledWith(3, "thread-1", {
511
+ format: "aui/v0",
512
+ limit: 200,
513
+ after: "message-400",
514
+ });
515
+ });
516
+
517
+ it("stops without duplicates when a later cursor stops resolving", async () => {
518
+ const rows = createCloudMessages(450);
519
+ vi.mocked(cloud.threads.messages.list).mockImplementation(
520
+ async (_threadId, query) => {
521
+ // The third request replays page one, the way the endpoint answers a
522
+ // cursor whose message no longer resolves.
523
+ const start =
524
+ query?.after && query.after !== "message-400"
525
+ ? rows.findIndex((row) => row.id === query.after) + 1
526
+ : 0;
527
+ return { messages: rows.slice(start, start + 200) };
528
+ },
529
+ );
530
+
531
+ const messages = await persistence.load("thread-1", "aui/v0");
532
+
533
+ expect(messages.map((message) => message.id)).toEqual(
534
+ rows.slice(0, 400).map((row) => row.id),
535
+ );
536
+ expect(cloud.threads.messages.list).toHaveBeenCalledTimes(3);
537
+ });
538
+
539
+ it("stops when a page does not advance the cursor", async () => {
540
+ vi.mocked(cloud.threads.messages.list).mockResolvedValue({
541
+ messages: createCloudMessages(200),
542
+ });
543
+
544
+ const messages = await persistence.load("thread-1", "aui/v0");
545
+
546
+ expect(messages).toHaveLength(200);
547
+ expect(cloud.threads.messages.list).toHaveBeenCalledTimes(2);
222
548
  });
223
549
 
224
550
  it("updates an already-persisted message", async () => {
@@ -268,6 +594,30 @@ describe("CloudMessagePersistence", () => {
268
594
  expect(persistence.isPersisted("local-1")).toBe(false);
269
595
  });
270
596
 
597
+ it("does not restore an append mapping after reset", async () => {
598
+ let resolveAppend!: (value: { message_id: string }) => void;
599
+ vi.mocked(cloud.threads.messages.create).mockImplementationOnce(
600
+ () =>
601
+ new Promise((resolve) => {
602
+ resolveAppend = resolve;
603
+ }),
604
+ );
605
+
606
+ const append = persistence.append(
607
+ "thread-1",
608
+ "local-1",
609
+ null,
610
+ "aui/v0",
611
+ {},
612
+ );
613
+ persistence.reset();
614
+ resolveAppend({ message_id: "remote-1" });
615
+
616
+ await expect(append).resolves.toBeUndefined();
617
+ expect(persistence.isPersisted("local-1")).toBe(false);
618
+ expect(await persistence.getRemoteId("local-1")).toBeUndefined();
619
+ });
620
+
271
621
  it("reset clears all ID mappings", async () => {
272
622
  vi.mocked(cloud.threads.messages.create).mockResolvedValue({
273
623
  message_id: "remote-1",
@@ -0,0 +1,100 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import type { AssistantCloud } from "../AssistantCloud";
3
+ import { CloudRunReporter } from "../CloudRunReporter";
4
+
5
+ const createCloud = (telemetry: AssistantCloud["telemetry"]) => {
6
+ const report = vi.fn().mockResolvedValue({ run_id: "run_1" });
7
+ const cloud = { telemetry, runs: { report } } as unknown as AssistantCloud;
8
+ return { cloud, report };
9
+ };
10
+
11
+ describe("CloudRunReporter", () => {
12
+ it("sends nothing while telemetry is disabled", async () => {
13
+ const { cloud, report } = createCloud({ enabled: false });
14
+ await new CloudRunReporter(cloud).report({
15
+ threadId: "thread_1",
16
+ status: "completed",
17
+ });
18
+ expect(report).not.toHaveBeenCalled();
19
+ });
20
+
21
+ it("stamps the cloud's environment, release and tags and applies beforeReport last", async () => {
22
+ const beforeReport = vi.fn((report) => ({ ...report, model_id: "gpt" }));
23
+ const { cloud, report } = createCloud({
24
+ enabled: true,
25
+ environment: "production",
26
+ release: "1.2.3",
27
+ tags: ["web"],
28
+ beforeReport,
29
+ });
30
+ await new CloudRunReporter(cloud).report({
31
+ threadId: "thread_1",
32
+ status: "completed",
33
+ outputText: "hi",
34
+ });
35
+ expect(beforeReport).toHaveBeenCalledWith(
36
+ expect.objectContaining({
37
+ thread_id: "thread_1",
38
+ environment: "production",
39
+ release: "1.2.3",
40
+ tags: ["web"],
41
+ }),
42
+ );
43
+ expect(report).toHaveBeenCalledWith(
44
+ expect.objectContaining({ thread_id: "thread_1", model_id: "gpt" }),
45
+ );
46
+ });
47
+
48
+ it("skips a report the hook vetoes without consuming its key", async () => {
49
+ const { cloud, report } = createCloud({
50
+ enabled: true,
51
+ beforeReport: vi
52
+ .fn()
53
+ .mockReturnValueOnce(null)
54
+ .mockImplementation((r) => r),
55
+ });
56
+ const reporter = new CloudRunReporter(cloud);
57
+ await reporter.report({ threadId: "t", status: "completed" }, "t:m");
58
+ await reporter.report({ threadId: "t", status: "completed" }, "t:m");
59
+ expect(report).toHaveBeenCalledOnce();
60
+ });
61
+
62
+ it("reports a keyed run once and an unkeyed run every time", async () => {
63
+ const { cloud, report } = createCloud({ enabled: true });
64
+ const reporter = new CloudRunReporter(cloud);
65
+ await reporter.report({ threadId: "t", status: "completed" }, "t:m");
66
+ await reporter.report({ threadId: "t", status: "completed" }, "t:m");
67
+ await reporter.report({ threadId: "t", status: "completed" });
68
+ await reporter.report({ threadId: "t", status: "completed" });
69
+ expect(report).toHaveBeenCalledTimes(3);
70
+ });
71
+
72
+ it("swallows a failed send and reads the cloud through a getter", async () => {
73
+ const { cloud } = createCloud({ enabled: true });
74
+ (cloud.runs.report as ReturnType<typeof vi.fn>).mockRejectedValue(
75
+ new Error("offline"),
76
+ );
77
+ await expect(
78
+ new CloudRunReporter(() => cloud).report({
79
+ threadId: "t",
80
+ status: "error",
81
+ }),
82
+ ).resolves.toBeUndefined();
83
+ });
84
+
85
+ it("resolves when the hook throws and reports nothing", async () => {
86
+ const { cloud, report } = createCloud({
87
+ enabled: true,
88
+ beforeReport: () => {
89
+ throw new Error("hook");
90
+ },
91
+ });
92
+ await expect(
93
+ new CloudRunReporter(cloud).report({
94
+ threadId: "t",
95
+ status: "completed",
96
+ }),
97
+ ).resolves.toBeUndefined();
98
+ expect(report).not.toHaveBeenCalled();
99
+ });
100
+ });
package/src/version.ts ADDED
@@ -0,0 +1,4 @@
1
+ export const ASSISTANT_CLOUD_VERSION: string =
2
+ typeof __AUI_PACKAGE_VERSION__ === "string"
3
+ ? __AUI_PACKAGE_VERSION__
4
+ : "0.0.0";