assistant-cloud 0.2.1 → 0.2.3

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 (91) hide show
  1. package/README.md +24 -8
  2. package/dist/AssistantCloud.d.ts +16 -19
  3. package/dist/AssistantCloud.d.ts.map +1 -1
  4. package/dist/AssistantCloud.js +7 -2
  5. package/dist/AssistantCloud.js.map +1 -1
  6. package/dist/AssistantCloudAPI.d.ts +74 -72
  7. package/dist/AssistantCloudAPI.d.ts.map +1 -1
  8. package/dist/AssistantCloudAPI.js.map +1 -1
  9. package/dist/AssistantCloudAuthStrategy.d.ts +29 -32
  10. package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
  11. package/dist/AssistantCloudAuthTokens.d.ts +7 -9
  12. package/dist/AssistantCloudAuthTokens.d.ts.map +1 -1
  13. package/dist/AssistantCloudEvents.d.ts +35 -29
  14. package/dist/AssistantCloudEvents.d.ts.map +1 -1
  15. package/dist/AssistantCloudEvents.js +66 -17
  16. package/dist/AssistantCloudEvents.js.map +1 -1
  17. package/dist/AssistantCloudFiles.d.ts +27 -29
  18. package/dist/AssistantCloudFiles.d.ts.map +1 -1
  19. package/dist/AssistantCloudProjectThreadMessages.d.ts +11 -13
  20. package/dist/AssistantCloudProjectThreadMessages.d.ts.map +1 -1
  21. package/dist/AssistantCloudProjectThreads.d.ts +12 -14
  22. package/dist/AssistantCloudProjectThreads.d.ts.map +1 -1
  23. package/dist/AssistantCloudProjects.d.ts +4 -7
  24. package/dist/AssistantCloudProjects.d.ts.map +1 -1
  25. package/dist/AssistantCloudRuns.d.ts +67 -56
  26. package/dist/AssistantCloudRuns.d.ts.map +1 -1
  27. package/dist/AssistantCloudRuns.js +9 -4
  28. package/dist/AssistantCloudRuns.js.map +1 -1
  29. package/dist/AssistantCloudScores.d.ts +20 -23
  30. package/dist/AssistantCloudScores.d.ts.map +1 -1
  31. package/dist/AssistantCloudThreadMessages.d.ts +41 -38
  32. package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
  33. package/dist/AssistantCloudThreadMessages.js +16 -4
  34. package/dist/AssistantCloudThreadMessages.js.map +1 -1
  35. package/dist/AssistantCloudThreads.d.ts +42 -44
  36. package/dist/AssistantCloudThreads.d.ts.map +1 -1
  37. package/dist/CloudEngagementReporter.d.ts +56 -58
  38. package/dist/CloudEngagementReporter.d.ts.map +1 -1
  39. package/dist/CloudEngagementReporter.js +4 -1
  40. package/dist/CloudEngagementReporter.js.map +1 -1
  41. package/dist/CloudMessagePersistence.d.ts +56 -58
  42. package/dist/CloudMessagePersistence.d.ts.map +1 -1
  43. package/dist/CloudMessagePersistence.js +3 -0
  44. package/dist/CloudMessagePersistence.js.map +1 -1
  45. package/dist/CloudRunReporter.d.ts +10 -13
  46. package/dist/CloudRunReporter.d.ts.map +1 -1
  47. package/dist/CloudRunReporter.js +13 -4
  48. package/dist/CloudRunReporter.js.map +1 -1
  49. package/dist/FormattedCloudPersistence.d.ts +36 -39
  50. package/dist/FormattedCloudPersistence.d.ts.map +1 -1
  51. package/dist/ai-sdk/index.d.ts +14 -17
  52. package/dist/ai-sdk/index.d.ts.map +1 -1
  53. package/dist/cloudResponse.d.ts +14 -17
  54. package/dist/cloudResponse.d.ts.map +1 -1
  55. package/dist/generateThreadTitle.d.ts +9 -12
  56. package/dist/generateThreadTitle.d.ts.map +1 -1
  57. package/dist/index.d.ts +18 -17
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/instrumentMcpSampling.d.ts +36 -39
  60. package/dist/instrumentMcpSampling.d.ts.map +1 -1
  61. package/dist/runTelemetry.d.ts +101 -94
  62. package/dist/runTelemetry.d.ts.map +1 -1
  63. package/dist/runTelemetry.js +57 -15
  64. package/dist/runTelemetry.js.map +1 -1
  65. package/dist/telemetry/index.d.ts +20 -23
  66. package/dist/telemetry/index.d.ts.map +1 -1
  67. package/dist/version.d.ts +1 -4
  68. package/dist/version.d.ts.map +1 -1
  69. package/dist/version.js +1 -1
  70. package/package.json +10 -9
  71. package/src/AssistantCloud.ts +17 -2
  72. package/src/AssistantCloudAPI.ts +4 -0
  73. package/src/AssistantCloudEvents.test.ts +157 -0
  74. package/src/AssistantCloudEvents.ts +82 -20
  75. package/src/AssistantCloudRuns.ts +35 -5
  76. package/src/AssistantCloudThreadMessages.test.ts +53 -2
  77. package/src/AssistantCloudThreadMessages.ts +34 -2
  78. package/src/BackendTranscriptCopy.test.ts +79 -0
  79. package/src/CloudEngagementReporter.ts +9 -3
  80. package/src/CloudMessagePersistence.ts +4 -0
  81. package/src/CloudRunReporter.ts +16 -4
  82. package/src/ai-sdk/index.test.ts +2 -2
  83. package/src/runTelemetry.test.ts +226 -2
  84. package/src/runTelemetry.ts +121 -17
  85. package/src/tests/AssistantCloud.test.ts +22 -0
  86. package/src/tests/AssistantCloudRuns.test.ts +28 -0
  87. package/src/tests/CloudEngagementReporter.test.ts +45 -0
  88. package/src/tests/CloudRunReporter.test.ts +49 -2
  89. package/dist/tests/setup.d.ts +0 -1
  90. package/dist/tests/setup.js +0 -7
  91. package/dist/tests/setup.js.map +0 -1
@@ -3,7 +3,7 @@ import type { AssistantCloudRunReport } from "./AssistantCloudRuns";
3
3
 
4
4
  const MAX_TELEMETRY_TEXT_LENGTH = 50_000;
5
5
 
6
- const BASE64_PATTERN = /^[A-Za-z0-9+/]{100,}={0,2}$/;
6
+ const BASE64_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/;
7
7
 
8
8
  export type AssistantCloudRunReportToolCall = {
9
9
  tool_name: string;
@@ -114,28 +114,81 @@ function safeStringify(value: unknown): string | undefined {
114
114
  }
115
115
  }
116
116
 
117
+ const base64SizeKB = (value: string) =>
118
+ ((value.length * 3) / 4 / 1024).toFixed(1);
119
+
120
+ // Both call sites read a field the MCP content grammar already defines as
121
+ // base64, so the test only has to recognise the encoding's own shape: its
122
+ // alphabet, and a length that is a multiple of four. A size floor would leave
123
+ // a short payload — `aGk=` is a whole image in this repo's own fixtures —
124
+ // serialized raw.
125
+ const isInlineBase64 = (value: unknown): value is string => {
126
+ if (typeof value !== "string") return false;
127
+ const head = value.slice(0, 200);
128
+ return head.length > 0 && head.length % 4 === 0 && BASE64_PATTERN.test(head);
129
+ };
130
+
131
+ function summarizeMcpContentBlock(item: unknown): unknown {
132
+ if (!item || typeof item !== "object") return item;
133
+ const block = item as {
134
+ type?: unknown;
135
+ data?: unknown;
136
+ resource?: unknown;
137
+ };
138
+ if (
139
+ (block.type === "image" || block.type === "audio") &&
140
+ isInlineBase64(block.data)
141
+ ) {
142
+ return {
143
+ ...block,
144
+ data: `[${String(block.type)}: ${base64SizeKB(block.data)}KB]`,
145
+ };
146
+ }
147
+ // An EmbeddedResource is the third inline carrier: a binary resource arrives
148
+ // as base64 under `resource.blob` rather than as a top-level `data` field.
149
+ if (
150
+ block.type === "resource" &&
151
+ block.resource &&
152
+ typeof block.resource === "object"
153
+ ) {
154
+ const resource = block.resource as { blob?: unknown };
155
+ if (isInlineBase64(resource.blob)) {
156
+ return {
157
+ ...block,
158
+ resource: {
159
+ ...resource,
160
+ blob: `[resource: ${base64SizeKB(resource.blob)}KB]`,
161
+ },
162
+ };
163
+ }
164
+ }
165
+ return item;
166
+ }
167
+
117
168
  function summarizeMcpResult(value: unknown): string | undefined {
118
169
  if (value == null) return undefined;
119
170
  try {
120
171
  const parsed = typeof value === "string" ? JSON.parse(value) : value;
121
172
  if (Array.isArray(parsed)) {
122
- const summarized = parsed.map((item) => {
123
- if (item && typeof item === "object" && item.type) {
124
- if (
125
- (item.type === "image" || item.type === "audio") &&
126
- typeof item.data === "string" &&
127
- BASE64_PATTERN.test(item.data.slice(0, 200))
128
- ) {
129
- const sizeKB = ((item.data.length * 3) / 4 / 1024).toFixed(1);
130
- return { ...item, data: `[${item.type}: ${sizeKB}KB]` };
131
- }
132
- }
133
- return item;
134
- });
135
- return truncateRunTelemetryText(JSON.stringify(summarized));
173
+ return truncateRunTelemetryText(
174
+ JSON.stringify(parsed.map(summarizeMcpContentBlock)),
175
+ );
176
+ }
177
+ // `callTool` resolves to a CallToolResult, so the blocks carrying base64
178
+ // arrive under `content` rather than as the result itself.
179
+ if (typeof parsed === "object") {
180
+ const content = (parsed as { content?: unknown }).content;
181
+ if (Array.isArray(content)) {
182
+ return truncateRunTelemetryText(
183
+ JSON.stringify({
184
+ ...(parsed as Record<string, unknown>),
185
+ content: content.map(summarizeMcpContentBlock),
186
+ }),
187
+ );
188
+ }
136
189
  }
137
190
  } catch {
138
- // not JSON array, fall through
191
+ // not a shape with summarizable content, fall through
139
192
  }
140
193
  return safeStringify(value);
141
194
  }
@@ -223,6 +276,7 @@ export type RunReportStepInit = {
223
276
  startMs?: number | undefined;
224
277
  endMs?: number | undefined;
225
278
  finishReason?: string | undefined;
279
+ input?: string | undefined;
226
280
  };
227
281
 
228
282
  /**
@@ -245,11 +299,12 @@ export type RunMessageTelemetry = {
245
299
  export type RunReportInit = {
246
300
  threadId: string;
247
301
  status: AssistantCloudRunReport["status"];
248
- outcome?: RunReportOutcome | undefined;
302
+ outcome?: AssistantCloudRunReport["outcome_type"] | undefined;
249
303
  errorCode?: string | undefined;
250
304
  error?: string | undefined;
251
305
  messageId?: string | undefined;
252
306
  traceId?: string | undefined;
307
+ rootSpanId?: string | undefined;
253
308
  modelId?: string | undefined;
254
309
  provider?: string | undefined;
255
310
  usage?: RunTelemetryUsageInit | undefined;
@@ -258,7 +313,17 @@ export type RunReportInit = {
258
313
  toolCalls?: AssistantCloudRunReportToolCall[] | undefined;
259
314
  durationMs?: number | undefined;
260
315
  firstTokenMs?: number | undefined;
316
+ costUsd?: number | undefined;
317
+ costDetails?:
318
+ | {
319
+ input?: number | undefined;
320
+ inputCachedTokens?: number | undefined;
321
+ output?: number | undefined;
322
+ total?: number | undefined;
323
+ }
324
+ | undefined;
261
325
  outputText?: string | undefined;
326
+ attributes?: Record<string, unknown> | undefined;
262
327
  metadata?: Record<string, unknown> | undefined;
263
328
  telemetry?: {
264
329
  environment?: string | undefined;
@@ -308,6 +373,9 @@ function createRunReportStep(
308
373
  if (init.finishReason !== undefined) {
309
374
  step.finish_reason = init.finishReason.slice(0, 32);
310
375
  }
376
+ if (init.input !== undefined) {
377
+ step.input = truncateRunTelemetryText(init.input);
378
+ }
311
379
  return step;
312
380
  }
313
381
 
@@ -334,6 +402,34 @@ function normalizeRunReportMilliseconds(
334
402
  return Math.max(0, Math.round(value));
335
403
  }
336
404
 
405
+ function normalizeRunReportCost(value: number | undefined): number | undefined {
406
+ if (value === undefined || !Number.isFinite(value) || value < 0) {
407
+ return undefined;
408
+ }
409
+ return value;
410
+ }
411
+
412
+ function assignCostDetails(
413
+ report: AssistantCloudRunReport,
414
+ costDetails: RunReportInit["costDetails"],
415
+ ): void {
416
+ if (!costDetails) return;
417
+ const normalized: NonNullable<AssistantCloudRunReport["cost_details"]> = {};
418
+ const input = normalizeRunReportCost(costDetails.input);
419
+ if (input !== undefined) normalized.input = input;
420
+ const inputCachedTokens = normalizeRunReportCost(
421
+ costDetails.inputCachedTokens,
422
+ );
423
+ if (inputCachedTokens !== undefined) {
424
+ normalized.input_cached_tokens = inputCachedTokens;
425
+ }
426
+ const output = normalizeRunReportCost(costDetails.output);
427
+ if (output !== undefined) normalized.output = output;
428
+ const total = normalizeRunReportCost(costDetails.total);
429
+ if (total !== undefined) normalized.total = total;
430
+ if (Object.keys(normalized).length > 0) report.cost_details = normalized;
431
+ }
432
+
337
433
  export function createRunReport(init: RunReportInit): AssistantCloudRunReport {
338
434
  const report: AssistantCloudRunReport = {
339
435
  thread_id: init.threadId,
@@ -341,6 +437,10 @@ export function createRunReport(init: RunReportInit): AssistantCloudRunReport {
341
437
  };
342
438
  const traceId = init.traceId?.toLowerCase();
343
439
  if (traceId && /^[0-9a-f]{32}$/.test(traceId)) report.trace_id = traceId;
440
+ const rootSpanId = init.rootSpanId?.toLowerCase();
441
+ if (rootSpanId && /^[0-9a-f]{16}$/.test(rootSpanId)) {
442
+ report.root_span_id = rootSpanId;
443
+ }
344
444
  if (init.outcome !== undefined) report.outcome_type = init.outcome;
345
445
  if (init.errorCode !== undefined) report.error_code = init.errorCode;
346
446
  if (init.error !== undefined) report.error = init.error;
@@ -362,9 +462,13 @@ export function createRunReport(init: RunReportInit): AssistantCloudRunReport {
362
462
  if (durationMs !== undefined) report.duration_ms = durationMs;
363
463
  const firstTokenMs = normalizeRunReportMilliseconds(init.firstTokenMs);
364
464
  if (firstTokenMs !== undefined) report.first_token_ms = firstTokenMs;
465
+ const costUsd = normalizeRunReportCost(init.costUsd);
466
+ if (costUsd !== undefined) report.cost_usd = costUsd;
467
+ assignCostDetails(report, init.costDetails);
365
468
  if (init.outputText !== undefined) {
366
469
  report.output_text = truncateRunTelemetryText(init.outputText);
367
470
  }
471
+ if (init.attributes !== undefined) report.attributes = init.attributes;
368
472
  if (init.metadata !== undefined) report.metadata = init.metadata;
369
473
  if (init.telemetry?.environment !== undefined) {
370
474
  report.environment = init.telemetry.environment;
@@ -14,6 +14,7 @@ const createCloud = (
14
14
 
15
15
  describe("AssistantCloud telemetry config", () => {
16
16
  afterEach(() => {
17
+ vi.useRealTimers();
17
18
  vi.unstubAllGlobals();
18
19
  });
19
20
 
@@ -34,6 +35,27 @@ describe("AssistantCloud telemetry config", () => {
34
35
  });
35
36
  });
36
37
 
38
+ it.each(["enabled", "events"] as const)(
39
+ "does not resume pending events after telemetry %s is disabled",
40
+ async (property) => {
41
+ vi.useFakeTimers();
42
+ const fetchMock = vi.fn().mockRejectedValueOnce(new Error("offline"));
43
+ vi.stubGlobal("fetch", fetchMock);
44
+ const cloud = createCloud();
45
+
46
+ for (let index = 0; index < 20; index++) {
47
+ cloud.events.track({ kind: "message_sent" });
48
+ }
49
+ await vi.waitFor(() => expect(fetchMock).toHaveBeenCalledOnce());
50
+
51
+ cloud.telemetry[property] = false;
52
+ cloud.telemetry[property] = true;
53
+ await vi.runAllTimersAsync();
54
+
55
+ expect(fetchMock).toHaveBeenCalledOnce();
56
+ },
57
+ );
58
+
37
59
  it("stays enabled when the config object carries an undefined enabled", () => {
38
60
  const beforeReport: NonNullable<
39
61
  AssistantCloudTelemetryConfig["beforeReport"]
@@ -90,6 +90,34 @@ describe("AssistantCloudRuns", () => {
90
90
  );
91
91
  expect(cancel).toHaveBeenCalledOnce();
92
92
  });
93
+
94
+ it("pins the UI message stream protocol in assistant options", () => {
95
+ const { protocol } =
96
+ createCloud().runs.__internal_getAssistantOptions("assistant-id");
97
+
98
+ expect(protocol).toBe("ui-message-stream");
99
+ });
100
+
101
+ it("uses the requested thread ID in assistant options", async () => {
102
+ const { body } =
103
+ createCloud().runs.__internal_getAssistantOptions("assistant-id");
104
+
105
+ await expect(body({ threadId: "remote-thread" })).resolves.toEqual({
106
+ assistant_id: "assistant-id",
107
+ response_format: "vercel-ai-data-stream/v1",
108
+ thread_id: "remote-thread",
109
+ });
110
+ });
111
+
112
+ it("rejects assistant options without a thread ID", async () => {
113
+ const { body } =
114
+ createCloud().runs.__internal_getAssistantOptions("assistant-id");
115
+
116
+ await expect(body({})).rejects.toThrow(
117
+ "Assistant Cloud runs need a thread",
118
+ );
119
+ await expect(body()).rejects.toThrow("Assistant Cloud runs need a thread");
120
+ });
93
121
  });
94
122
 
95
123
  const createCloudRuns = () => {
@@ -47,6 +47,24 @@ describe("CloudEngagementReporter", () => {
47
47
  });
48
48
  });
49
49
 
50
+ it("drops an event its resolver declines", async () => {
51
+ const { cloud, track } = createCloud();
52
+ const reporter = new CloudEngagementReporter(cloud, (threadId) =>
53
+ threadId === "known" ? { thread_id: "remote-known" } : undefined,
54
+ );
55
+
56
+ reporter.messageSent("unknown", { chars: 3, attachments: 0 });
57
+ reporter.messageSent("known", { chars: 5, attachments: 0 });
58
+ await flush();
59
+
60
+ expect(track).toHaveBeenCalledOnce();
61
+ expect(track).toHaveBeenCalledWith({
62
+ kind: "message_sent",
63
+ thread_id: "remote-known",
64
+ props: { chars: 5, attachments: 0 },
65
+ });
66
+ });
67
+
50
68
  it("reports tool approval decisions with their resolved message IDs", async () => {
51
69
  const { cloud, track } = createCloud();
52
70
  const reporter = new CloudEngagementReporter(
@@ -113,6 +131,33 @@ describe("CloudEngagementReporter", () => {
113
131
  );
114
132
  });
115
133
 
134
+ it("measures the next send from a stopped run, not the last completed one", async () => {
135
+ const { cloud, track } = createCloud();
136
+ const reporter = new CloudEngagementReporter(cloud);
137
+
138
+ vi.setSystemTime(new Date("2023-01-01T00:00:00.000Z"));
139
+ reporter.runStarted("t1");
140
+ vi.setSystemTime(new Date("2023-01-01T00:01:40.000Z"));
141
+ reporter.runEnded("t1");
142
+
143
+ // a second run the user stops rather than lets finish
144
+ vi.setSystemTime(new Date("2023-01-01T00:03:20.000Z"));
145
+ reporter.runStarted("t1");
146
+ vi.setSystemTime(new Date("2023-01-01T00:05:00.000Z"));
147
+ reporter.runStopped("t1");
148
+
149
+ vi.setSystemTime(new Date("2023-01-01T00:05:10.000Z"));
150
+ reporter.messageSent("t1", { chars: 3, attachments: 0 });
151
+ await flush();
152
+
153
+ // a stopped run is still a run that ended, so the idle gap is the 10s
154
+ // since the stop, not the 210s since the last completed run
155
+ const sent = track.mock.calls
156
+ .map(([event]) => event as { kind: string; value?: number })
157
+ .find((event) => event.kind === "message_sent");
158
+ expect(sent?.value).toBe(10_000);
159
+ });
160
+
116
161
  it("shows one error per run and one suggestion list per thread", async () => {
117
162
  const { cloud, track } = createCloud();
118
163
  const reporter = new CloudEngagementReporter(cloud);
@@ -1,6 +1,8 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import type { AssistantCloud } from "../AssistantCloud";
3
+ import { CloudAPIError } from "../AssistantCloudAPI";
3
4
  import { CloudRunReporter } from "../CloudRunReporter";
5
+ import { CloudResponseError } from "../cloudResponse";
4
6
 
5
7
  const createCloud = (telemetry: AssistantCloud["telemetry"]) => {
6
8
  const report = vi.fn().mockResolvedValue({ run_id: "run_1" });
@@ -19,7 +21,13 @@ describe("CloudRunReporter", () => {
19
21
  });
20
22
 
21
23
  it("stamps the cloud's environment, release and tags and applies beforeReport last", async () => {
22
- const beforeReport = vi.fn((report) => ({ ...report, model_id: "gpt" }));
24
+ const beforeReport = vi.fn((report) => ({
25
+ ...report,
26
+ model_id: "gpt",
27
+ cost_usd: 0.012,
28
+ attributes: { tenant: "acme" },
29
+ root_span_id: "0011223344556677",
30
+ }));
23
31
  const { cloud, report } = createCloud({
24
32
  enabled: true,
25
33
  environment: "production",
@@ -41,7 +49,13 @@ describe("CloudRunReporter", () => {
41
49
  }),
42
50
  );
43
51
  expect(report).toHaveBeenCalledWith(
44
- expect.objectContaining({ thread_id: "thread_1", model_id: "gpt" }),
52
+ expect.objectContaining({
53
+ thread_id: "thread_1",
54
+ model_id: "gpt",
55
+ cost_usd: 0.012,
56
+ attributes: { tenant: "acme" },
57
+ root_span_id: "0011223344556677",
58
+ }),
45
59
  );
46
60
  });
47
61
 
@@ -69,6 +83,39 @@ describe("CloudRunReporter", () => {
69
83
  expect(report).toHaveBeenCalledTimes(3);
70
84
  });
71
85
 
86
+ it("allows a keyed run to retry after rate limiting", async () => {
87
+ const { cloud, report } = createCloud({ enabled: true });
88
+ report.mockRejectedValueOnce(new CloudAPIError("rate limited", 429));
89
+ const reporter = new CloudRunReporter(cloud);
90
+
91
+ await reporter.report({ threadId: "t", status: "completed" }, "t:m");
92
+ await reporter.report({ threadId: "t", status: "completed" }, "t:m");
93
+ await reporter.report({ threadId: "t", status: "completed" }, "t:m");
94
+
95
+ expect(report).toHaveBeenCalledTimes(2);
96
+ });
97
+
98
+ it.each([
99
+ ["a transport failure", new Error("offline")],
100
+ ["a request timeout", new CloudAPIError("timeout", 408)],
101
+ ["a server error", new CloudAPIError("unavailable", 503)],
102
+ [
103
+ "a successful response with an invalid body",
104
+ new CloudResponseError("invalid response"),
105
+ ],
106
+ ["a successful response with invalid JSON", new SyntaxError("invalid")],
107
+ ["a permanent client error", new CloudAPIError("invalid report", 400)],
108
+ ])("does not retry after %s", async (_name, error) => {
109
+ const { cloud, report } = createCloud({ enabled: true });
110
+ report.mockRejectedValueOnce(error);
111
+ const reporter = new CloudRunReporter(cloud);
112
+
113
+ await reporter.report({ threadId: "t", status: "completed" }, "t:m");
114
+ await reporter.report({ threadId: "t", status: "completed" }, "t:m");
115
+
116
+ expect(report).toHaveBeenCalledOnce();
117
+ });
118
+
72
119
  it("swallows a failed send and reads the cloud through a getter", async () => {
73
120
  const { cloud } = createCloud({ enabled: true });
74
121
  (cloud.runs.report as ReturnType<typeof vi.fn>).mockRejectedValue(
@@ -1 +0,0 @@
1
- export {}
@@ -1,7 +0,0 @@
1
- import { vi } from "vitest";
2
- //#region src/tests/setup.ts
3
- vi.useFakeTimers({ toFake: ["Date"] });
4
- vi.setSystemTime(/* @__PURE__ */ new Date("2023-01-01T00:00:00.000Z"));
5
- //#endregion
6
-
7
- //# sourceMappingURL=setup.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"setup.js","names":[],"sources":["../../src/tests/setup.ts"],"sourcesContent":["import { vi } from \"vitest\";\n\nvi.useFakeTimers({ toFake: [\"Date\"] });\nvi.setSystemTime(new Date(\"2023-01-01T00:00:00.000Z\"));\n"],"mappings":";;AAEA,GAAG,cAAc,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;AACrC,GAAG,8BAAc,IAAI,KAAK,0BAA0B,CAAC"}