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
@@ -1,12 +1,296 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
+ createRunReport,
3
4
  createRunTelemetryToolCall,
5
+ deriveRunOutcome,
6
+ describeRunError,
7
+ extractRunTelemetryModelId,
4
8
  normalizeRunTelemetryUsage,
5
9
  truncateRunTelemetryText,
6
10
  } from "./runTelemetry";
7
11
 
8
12
  const MAX = 50_000;
9
13
 
14
+ describe("deriveRunOutcome", () => {
15
+ it.each([
16
+ [{ isError: true }, { status: "error" }],
17
+ [{ isAbort: true }, { status: "incomplete", outcome: "aborted" }],
18
+ [{ isDisconnect: true }, { status: "incomplete", outcome: "disconnected" }],
19
+ [{ finishReason: "length" }, { status: "incomplete", outcome: "length" }],
20
+ [
21
+ { finishReason: "content-filter" },
22
+ { status: "incomplete", outcome: "content_filter" },
23
+ ],
24
+ [
25
+ { finishReason: "content_filter" },
26
+ { status: "incomplete", outcome: "content_filter" },
27
+ ],
28
+ [
29
+ { finishReason: "cancelled" },
30
+ { status: "incomplete", outcome: "aborted" },
31
+ ],
32
+ [{ finishReason: "stop" }, { status: "completed" }],
33
+ [{ finishReason: "tool-calls" }, { status: "completed" }],
34
+ [{}, { status: "completed" }],
35
+ [{ finishReason: "other" }, { status: "completed" }],
36
+ [{ finishReason: "error" }, { status: "error" }],
37
+ ])("maps %o", (input, expected) => {
38
+ expect(deriveRunOutcome(input)).toEqual(expected);
39
+ });
40
+
41
+ it("uses the fallback status only when the event carries no finish reason", () => {
42
+ expect(deriveRunOutcome({}, "incomplete")).toEqual({
43
+ status: "incomplete",
44
+ });
45
+ expect(deriveRunOutcome({ finishReason: "stop" }, "incomplete")).toEqual({
46
+ status: "completed",
47
+ });
48
+ expect(deriveRunOutcome({ finishReason: "length" }, "incomplete")).toEqual({
49
+ status: "incomplete",
50
+ outcome: "length",
51
+ });
52
+ expect(deriveRunOutcome({ isError: true }, "incomplete")).toEqual({
53
+ status: "error",
54
+ });
55
+ });
56
+ });
57
+
58
+ describe("describeRunError", () => {
59
+ it("reads the message and the class name of an AI SDK error", () => {
60
+ const error = new Error("Rate limited");
61
+ error.name = "AI_APICallError";
62
+ expect(describeRunError(error)).toEqual({
63
+ error: "Rate limited",
64
+ errorCode: "AI_APICallError",
65
+ });
66
+ });
67
+
68
+ it("prefers an explicit code and skips the plain Error name", () => {
69
+ expect(
70
+ describeRunError(Object.assign(new Error("boom"), { code: "ETIMEDOUT" })),
71
+ ).toEqual({ error: "boom", errorCode: "ETIMEDOUT" });
72
+ expect(describeRunError(new Error("boom"))).toEqual({ error: "boom" });
73
+ expect(describeRunError("boom")).toEqual({ error: "boom" });
74
+ expect(describeRunError(undefined)).toEqual({});
75
+ expect(describeRunError(42)).toEqual({});
76
+ });
77
+
78
+ it("clamps the message and code to what the runs endpoint accepts", () => {
79
+ const error = new Error("m".repeat(3000));
80
+ error.name = "c".repeat(100);
81
+ expect(describeRunError(error)).toEqual({
82
+ error: "m".repeat(2048),
83
+ errorCode: "c".repeat(64),
84
+ });
85
+ });
86
+ });
87
+
88
+ describe("createRunReport", () => {
89
+ const toolCall = {
90
+ tool_name: "weather",
91
+ tool_call_id: "call-1",
92
+ tool_args: '{"city":"Singapore"}',
93
+ };
94
+
95
+ it("creates the full aui/v0 report", () => {
96
+ expect(
97
+ createRunReport({
98
+ threadId: "aui-thread",
99
+ status: "incomplete",
100
+ outcome: "length",
101
+ errorCode: "model_limit",
102
+ error: "response cut off",
103
+ messageId: "cloud-message",
104
+ traceId: "AABBCCDDEEFF00112233445566778899",
105
+ modelId: "provider/model",
106
+ provider: "openai",
107
+ usage: {
108
+ promptTokens: 11,
109
+ completionTokens: 7,
110
+ reasoningTokens: 3,
111
+ cachedInputTokens: 2,
112
+ },
113
+ steps: [
114
+ {
115
+ usage: { inputTokens: 5, outputTokens: 4 },
116
+ toolCalls: [toolCall],
117
+ startMs: 10,
118
+ endMs: 20,
119
+ finishReason: "tool-calls",
120
+ },
121
+ {
122
+ usage: { inputTokens: 6, outputTokens: 3, reasoningTokens: 3 },
123
+ startMs: 21,
124
+ endMs: 42,
125
+ finishReason: "length".repeat(8),
126
+ },
127
+ ],
128
+ toolCalls: [toolCall],
129
+ durationMs: 42.5,
130
+ firstTokenMs: -4,
131
+ outputText: "hello from aui/v0",
132
+ metadata: { tenant: "acme" },
133
+ telemetry: {
134
+ environment: "production",
135
+ release: "web-2026.09.09",
136
+ tags: [" region:sg ", "region:sg", "tier:paid"],
137
+ },
138
+ }),
139
+ ).toEqual({
140
+ thread_id: "aui-thread",
141
+ status: "incomplete",
142
+ outcome_type: "length",
143
+ error_code: "model_limit",
144
+ error: "response cut off",
145
+ message_id: "cloud-message",
146
+ trace_id: "aabbccddeeff00112233445566778899",
147
+ model_id: "provider/model",
148
+ provider: "openai",
149
+ provider_type: "openai",
150
+ input_tokens: 11,
151
+ output_tokens: 7,
152
+ reasoning_tokens: 3,
153
+ cached_input_tokens: 2,
154
+ steps: [
155
+ {
156
+ input_tokens: 5,
157
+ output_tokens: 4,
158
+ tool_calls: [toolCall],
159
+ start_ms: 10,
160
+ end_ms: 20,
161
+ finish_reason: "tool-calls",
162
+ },
163
+ {
164
+ input_tokens: 6,
165
+ output_tokens: 3,
166
+ reasoning_tokens: 3,
167
+ start_ms: 21,
168
+ end_ms: 42,
169
+ finish_reason: "lengthlengthlengthlengthlengthle",
170
+ },
171
+ ],
172
+ total_steps: 2,
173
+ tool_calls: [toolCall],
174
+ duration_ms: 43,
175
+ first_token_ms: 0,
176
+ output_text: "hello from aui/v0",
177
+ metadata: { tenant: "acme" },
178
+ environment: "production",
179
+ release: "web-2026.09.09",
180
+ tags: ["region:sg", "tier:paid"],
181
+ });
182
+ });
183
+
184
+ it("creates the full ai-sdk/v6 report", () => {
185
+ expect(
186
+ createRunReport({
187
+ threadId: "ai-sdk-thread",
188
+ status: "completed",
189
+ traceId: "00112233445566778899aabbccddeeff",
190
+ modelId: "gpt-5.6-terra",
191
+ provider: "gateway",
192
+ usage: {
193
+ inputTokens: 100,
194
+ outputTokens: 50,
195
+ inputTokenDetails: { cacheReadTokens: 10 },
196
+ outputTokenDetails: { reasoningTokens: 20 },
197
+ },
198
+ steps: [
199
+ {
200
+ usage: {
201
+ inputTokens: 100,
202
+ outputTokens: 50,
203
+ inputTokenDetails: { cacheReadTokens: 10 },
204
+ outputTokenDetails: { reasoningTokens: 20 },
205
+ },
206
+ finishReason: "stop",
207
+ },
208
+ ],
209
+ durationMs: 125.2,
210
+ firstTokenMs: 12.6,
211
+ outputText: "hello from ai-sdk/v6",
212
+ telemetry: { tags: ["sdk:v6"] },
213
+ }),
214
+ ).toEqual({
215
+ thread_id: "ai-sdk-thread",
216
+ status: "completed",
217
+ trace_id: "00112233445566778899aabbccddeeff",
218
+ model_id: "gpt-5.6-terra",
219
+ provider: "gateway",
220
+ provider_type: "gateway",
221
+ input_tokens: 100,
222
+ output_tokens: 50,
223
+ reasoning_tokens: 20,
224
+ cached_input_tokens: 10,
225
+ steps: [
226
+ {
227
+ input_tokens: 100,
228
+ output_tokens: 50,
229
+ reasoning_tokens: 20,
230
+ cached_input_tokens: 10,
231
+ finish_reason: "stop",
232
+ },
233
+ ],
234
+ total_steps: 1,
235
+ duration_ms: 125,
236
+ first_token_ms: 13,
237
+ output_text: "hello from ai-sdk/v6",
238
+ tags: ["sdk:v6"],
239
+ });
240
+ });
241
+
242
+ it("normalizes tags and limits them to twenty 64-character values", () => {
243
+ const longTag = "a".repeat(70);
244
+ const tags = [" one ", "", "one", longTag];
245
+ for (let index = 0; index < 24; index++) tags.push(`tag:${index}`);
246
+
247
+ const report = createRunReport({
248
+ threadId: "thread",
249
+ status: "completed",
250
+ telemetry: { tags },
251
+ });
252
+
253
+ expect(report.tags).toEqual([
254
+ "one",
255
+ "a".repeat(64),
256
+ ...Array.from({ length: 18 }, (_, index) => `tag:${index}`),
257
+ ]);
258
+ });
259
+
260
+ it("trims the whitespace a 64-character cut leaves behind", () => {
261
+ const report = createRunReport({
262
+ threadId: "thread",
263
+ status: "completed",
264
+ telemetry: { tags: [`${"b".repeat(63)} tail`] },
265
+ });
266
+
267
+ expect(report.tags).toEqual(["b".repeat(63)]);
268
+ });
269
+
270
+ it("lowercases valid trace IDs, omits invalid IDs, and omits undefined keys", () => {
271
+ expect(
272
+ createRunReport({
273
+ threadId: "thread",
274
+ status: "completed",
275
+ traceId: "AABBCCDDEEFF00112233445566778899",
276
+ }).trace_id,
277
+ ).toBe("aabbccddeeff00112233445566778899");
278
+ expect(
279
+ createRunReport({
280
+ threadId: "thread",
281
+ status: "completed",
282
+ traceId: "aabbccdd",
283
+ }),
284
+ ).toEqual({ thread_id: "thread", status: "completed" });
285
+ expect(
286
+ createRunReport({
287
+ threadId: "thread",
288
+ status: "completed",
289
+ }),
290
+ ).toEqual({ thread_id: "thread", status: "completed" });
291
+ });
292
+ });
293
+
10
294
  describe("truncateRunTelemetryText", () => {
11
295
  it("passes text at or under the cap through unchanged", () => {
12
296
  expect(truncateRunTelemetryText("hello")).toBe("hello");
@@ -124,7 +408,89 @@ describe("normalizeRunTelemetryUsage", () => {
124
408
  ).toEqual({ inputTokens: 0, cachedInputTokens: 5 });
125
409
  });
126
410
 
411
+ it("reads the AI SDK v7 token detail objects", () => {
412
+ expect(
413
+ normalizeRunTelemetryUsage({
414
+ inputTokens: 12,
415
+ outputTokens: 7,
416
+ inputTokenDetails: { cacheReadTokens: 5 },
417
+ outputTokenDetails: { reasoningTokens: 3 },
418
+ }),
419
+ ).toEqual({
420
+ inputTokens: 12,
421
+ outputTokens: 7,
422
+ reasoningTokens: 3,
423
+ cachedInputTokens: 5,
424
+ });
425
+ });
426
+
427
+ it("prefers the top-level detail counts over the nested ones", () => {
428
+ expect(
429
+ normalizeRunTelemetryUsage({
430
+ reasoningTokens: 3,
431
+ cachedInputTokens: 5,
432
+ inputTokenDetails: { cacheReadTokens: 90 },
433
+ outputTokenDetails: { reasoningTokens: 90 },
434
+ }),
435
+ ).toEqual({ reasoningTokens: 3, cachedInputTokens: 5 });
436
+ });
437
+
438
+ it("returns a usage object when only the nested counts are present", () => {
439
+ expect(
440
+ normalizeRunTelemetryUsage({
441
+ inputTokenDetails: { cacheReadTokens: 5 },
442
+ }),
443
+ ).toEqual({ cachedInputTokens: 5 });
444
+ });
445
+
127
446
  it("returns undefined when no count is present", () => {
128
447
  expect(normalizeRunTelemetryUsage({})).toBeUndefined();
448
+ expect(
449
+ normalizeRunTelemetryUsage({
450
+ inputTokenDetails: {},
451
+ outputTokenDetails: {},
452
+ }),
453
+ ).toBeUndefined();
454
+ });
455
+ });
456
+
457
+ describe("extractRunTelemetryModelId", () => {
458
+ it("prefers an explicit model ID over every fallback", () => {
459
+ expect(
460
+ extractRunTelemetryModelId({
461
+ modelId: "explicit",
462
+ custom: { modelId: "custom" },
463
+ steps: [{ response: { modelId: "step" } }],
464
+ }),
465
+ ).toBe("explicit");
466
+ });
467
+
468
+ it("falls back to the custom bag before the steps", () => {
469
+ expect(
470
+ extractRunTelemetryModelId({
471
+ custom: { modelId: "custom" },
472
+ steps: [{ response: { modelId: "step" } }],
473
+ }),
474
+ ).toBe("custom");
475
+ });
476
+
477
+ it("reads the first step that carries a response model ID", () => {
478
+ expect(
479
+ extractRunTelemetryModelId({
480
+ steps: [{ usage: { inputTokens: 1 } }, { response: { modelId: "b" } }],
481
+ }),
482
+ ).toBe("b");
483
+ });
484
+
485
+ it("returns undefined when no channel carries one", () => {
486
+ expect(extractRunTelemetryModelId(undefined)).toBeUndefined();
487
+ expect(extractRunTelemetryModelId({})).toBeUndefined();
488
+ expect(
489
+ extractRunTelemetryModelId({
490
+ modelId: 7,
491
+ custom: { modelId: null },
492
+ steps: [null, "step", { response: null }, { response: { modelId: 7 } }],
493
+ }),
494
+ ).toBeUndefined();
129
495
  });
130
496
  });