assistant-cloud 0.1.43 → 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.
- package/README.md +40 -0
- package/dist/AssistantCloud.d.ts +4 -0
- package/dist/AssistantCloud.d.ts.map +1 -1
- package/dist/AssistantCloud.js +11 -5
- package/dist/AssistantCloud.js.map +1 -1
- package/dist/AssistantCloudAPI.d.ts +17 -1
- package/dist/AssistantCloudAPI.d.ts.map +1 -1
- package/dist/AssistantCloudAPI.js +20 -9
- package/dist/AssistantCloudAPI.js.map +1 -1
- package/dist/AssistantCloudAuthStrategy.d.ts +4 -1
- package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
- package/dist/AssistantCloudAuthStrategy.js +11 -1
- package/dist/AssistantCloudAuthStrategy.js.map +1 -1
- package/dist/AssistantCloudEvents.d.ts +32 -0
- package/dist/AssistantCloudEvents.d.ts.map +1 -0
- package/dist/AssistantCloudEvents.js +115 -0
- package/dist/AssistantCloudEvents.js.map +1 -0
- package/dist/AssistantCloudFiles.d.ts +12 -1
- package/dist/AssistantCloudFiles.d.ts.map +1 -1
- package/dist/AssistantCloudFiles.js +13 -1
- package/dist/AssistantCloudFiles.js.map +1 -1
- package/dist/AssistantCloudRuns.d.ts +11 -0
- package/dist/AssistantCloudRuns.d.ts.map +1 -1
- package/dist/AssistantCloudRuns.js.map +1 -1
- package/dist/AssistantCloudScores.d.ts +27 -0
- package/dist/AssistantCloudScores.d.ts.map +1 -0
- package/dist/AssistantCloudScores.js +30 -0
- package/dist/AssistantCloudScores.js.map +1 -0
- package/dist/AssistantCloudThreadMessages.d.ts +11 -1
- package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
- package/dist/AssistantCloudThreadMessages.js +12 -1
- package/dist/AssistantCloudThreadMessages.js.map +1 -1
- package/dist/AssistantCloudThreads.d.ts +8 -0
- package/dist/AssistantCloudThreads.d.ts.map +1 -1
- package/dist/AssistantCloudThreads.js +9 -1
- package/dist/AssistantCloudThreads.js.map +1 -1
- package/dist/CloudMessagePersistence.d.ts +4 -0
- package/dist/CloudMessagePersistence.d.ts.map +1 -1
- package/dist/CloudMessagePersistence.js +38 -11
- package/dist/CloudMessagePersistence.js.map +1 -1
- package/dist/cloudResponse.d.ts +4 -1
- package/dist/cloudResponse.d.ts.map +1 -1
- package/dist/cloudResponse.js +14 -1
- package/dist/cloudResponse.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -2
- package/dist/runTelemetry.d.ts +70 -7
- package/dist/runTelemetry.d.ts.map +1 -1
- package/dist/runTelemetry.js +144 -1
- package/dist/runTelemetry.js.map +1 -1
- package/dist/telemetry/index.d.ts +32 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +62 -0
- package/dist/telemetry/index.js.map +1 -0
- package/package.json +28 -4
- package/src/AssistantCloud.ts +20 -8
- package/src/AssistantCloudAPI.ts +42 -10
- package/src/AssistantCloudAuthStrategy.ts +13 -0
- package/src/AssistantCloudEvents.test.ts +124 -0
- package/src/AssistantCloudEvents.ts +185 -0
- package/src/AssistantCloudFiles.test.ts +59 -0
- package/src/AssistantCloudFiles.ts +39 -0
- package/src/AssistantCloudRuns.ts +12 -1
- package/src/AssistantCloudScores.test.ts +113 -0
- package/src/AssistantCloudScores.ts +61 -0
- package/src/AssistantCloudThreadMessages.test.ts +51 -0
- package/src/AssistantCloudThreadMessages.ts +33 -0
- package/src/AssistantCloudThreads.test.ts +21 -0
- package/src/AssistantCloudThreads.ts +21 -0
- package/src/CloudMessagePersistence.ts +48 -16
- package/src/cloudResponse.test.ts +34 -1
- package/src/cloudResponse.ts +30 -0
- package/src/index.ts +24 -1
- package/src/runTelemetry.test.ts +325 -0
- package/src/runTelemetry.ts +257 -6
- package/src/telemetry/index.test.ts +252 -0
- package/src/telemetry/index.ts +98 -0
- package/src/tests/AssistantCloud.test.ts +22 -0
- package/src/tests/AssistantCloudAPI.test.ts +44 -4
- package/src/tests/AssistantCloudAuthStrategy.test.ts +71 -2
- package/src/tests/CloudMessagePersistence.test.ts +115 -0
package/src/runTelemetry.test.ts
CHANGED
|
@@ -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");
|
|
@@ -169,3 +453,44 @@ describe("normalizeRunTelemetryUsage", () => {
|
|
|
169
453
|
).toBeUndefined();
|
|
170
454
|
});
|
|
171
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();
|
|
495
|
+
});
|
|
496
|
+
});
|
package/src/runTelemetry.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SamplingCallData } from "./instrumentMcpSampling";
|
|
2
|
+
import type { AssistantCloudRunReport } from "./AssistantCloudRuns";
|
|
2
3
|
|
|
3
4
|
const MAX_TELEMETRY_TEXT_LENGTH = 50_000;
|
|
4
5
|
|
|
@@ -15,6 +16,86 @@ export type AssistantCloudRunReportToolCall = {
|
|
|
15
16
|
sampling_calls?: SamplingCallData[];
|
|
16
17
|
};
|
|
17
18
|
|
|
19
|
+
export type RunReportOutcome =
|
|
20
|
+
| "aborted"
|
|
21
|
+
| "disconnected"
|
|
22
|
+
| "length"
|
|
23
|
+
| "content_filter";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Maps a finish event to the report status and outcome. `fallbackStatus`
|
|
27
|
+
* applies when the event carries neither a finish reason nor a failure flag.
|
|
28
|
+
*/
|
|
29
|
+
export function deriveRunOutcome(
|
|
30
|
+
input: {
|
|
31
|
+
finishReason?: string | undefined;
|
|
32
|
+
isAbort?: boolean | undefined;
|
|
33
|
+
isDisconnect?: boolean | undefined;
|
|
34
|
+
isError?: boolean | undefined;
|
|
35
|
+
},
|
|
36
|
+
fallbackStatus: "completed" | "incomplete" = "completed",
|
|
37
|
+
): {
|
|
38
|
+
status: "completed" | "incomplete" | "error";
|
|
39
|
+
outcome?: RunReportOutcome;
|
|
40
|
+
} {
|
|
41
|
+
if (input.isError) return { status: "error" };
|
|
42
|
+
if (input.isAbort) return { status: "incomplete", outcome: "aborted" };
|
|
43
|
+
if (input.isDisconnect) {
|
|
44
|
+
return { status: "incomplete", outcome: "disconnected" };
|
|
45
|
+
}
|
|
46
|
+
switch (input.finishReason) {
|
|
47
|
+
case "length":
|
|
48
|
+
return { status: "incomplete", outcome: "length" };
|
|
49
|
+
case "content-filter":
|
|
50
|
+
case "content_filter":
|
|
51
|
+
return { status: "incomplete", outcome: "content_filter" };
|
|
52
|
+
case "cancelled":
|
|
53
|
+
return { status: "incomplete", outcome: "aborted" };
|
|
54
|
+
case "error":
|
|
55
|
+
return { status: "error" };
|
|
56
|
+
default:
|
|
57
|
+
return {
|
|
58
|
+
status: input.finishReason === undefined ? fallbackStatus : "completed",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const MAX_RUN_ERROR_CODE_LENGTH = 64;
|
|
64
|
+
const MAX_RUN_ERROR_LENGTH = 2048;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reads the message and code the runs endpoint stores for a failed run. The
|
|
68
|
+
* code is the error's `code` when it has one, else its class name.
|
|
69
|
+
*/
|
|
70
|
+
export function describeRunError(error: unknown): {
|
|
71
|
+
error?: string;
|
|
72
|
+
errorCode?: string;
|
|
73
|
+
} {
|
|
74
|
+
if (error == null) return {};
|
|
75
|
+
const record =
|
|
76
|
+
typeof error === "object" ? (error as Record<string, unknown>) : undefined;
|
|
77
|
+
const message =
|
|
78
|
+
typeof error === "string"
|
|
79
|
+
? error
|
|
80
|
+
: typeof record?.message === "string"
|
|
81
|
+
? record.message
|
|
82
|
+
: undefined;
|
|
83
|
+
const code =
|
|
84
|
+
typeof record?.code === "string"
|
|
85
|
+
? record.code
|
|
86
|
+
: typeof record?.name === "string" && record.name !== "Error"
|
|
87
|
+
? record.name
|
|
88
|
+
: undefined;
|
|
89
|
+
return {
|
|
90
|
+
...(message
|
|
91
|
+
? { error: message.slice(0, MAX_RUN_ERROR_LENGTH) }
|
|
92
|
+
: undefined),
|
|
93
|
+
...(code
|
|
94
|
+
? { errorCode: code.slice(0, MAX_RUN_ERROR_CODE_LENGTH) }
|
|
95
|
+
: undefined),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
18
99
|
/**
|
|
19
100
|
* Clamps a string to the size the runs endpoint accepts for a single span
|
|
20
101
|
* field.
|
|
@@ -95,20 +176,190 @@ export function createRunTelemetryToolCall(
|
|
|
95
176
|
return call;
|
|
96
177
|
}
|
|
97
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Resolves the model ID a run reports, in the order an app can supply it: an
|
|
181
|
+
* explicit `modelId`, the `custom` bag, then the per-step `response.modelId`
|
|
182
|
+
* that a `messageMetadata` callback copies off the AI SDK's finish-step part.
|
|
183
|
+
* The AI SDK puts no model ID on a UI message part, so message metadata is the
|
|
184
|
+
* only channel one arrives on.
|
|
185
|
+
*/
|
|
186
|
+
export function extractRunTelemetryModelId(
|
|
187
|
+
metadata: Record<string, unknown> | undefined,
|
|
188
|
+
): string | undefined {
|
|
189
|
+
if (!metadata) return undefined;
|
|
190
|
+
if (typeof metadata.modelId === "string") return metadata.modelId;
|
|
191
|
+
const custom = metadata.custom as Record<string, unknown> | undefined;
|
|
192
|
+
if (typeof custom?.modelId === "string") return custom.modelId;
|
|
193
|
+
|
|
194
|
+
const steps: unknown = metadata.steps;
|
|
195
|
+
if (!Array.isArray(steps)) return undefined;
|
|
196
|
+
for (const step of steps as unknown[]) {
|
|
197
|
+
if (!step || typeof step !== "object") continue;
|
|
198
|
+
const response = (step as Record<string, unknown>).response;
|
|
199
|
+
if (!response || typeof response !== "object") continue;
|
|
200
|
+
const modelId = (response as Record<string, unknown>).modelId;
|
|
201
|
+
if (typeof modelId === "string") return modelId;
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
98
206
|
export type RunTelemetryUsage = {
|
|
99
|
-
inputTokens?: number;
|
|
100
|
-
outputTokens?: number;
|
|
101
|
-
reasoningTokens?: number;
|
|
102
|
-
cachedInputTokens?: number;
|
|
207
|
+
inputTokens?: number | undefined;
|
|
208
|
+
outputTokens?: number | undefined;
|
|
209
|
+
reasoningTokens?: number | undefined;
|
|
210
|
+
cachedInputTokens?: number | undefined;
|
|
103
211
|
};
|
|
104
212
|
|
|
105
213
|
export type RunTelemetryUsageInit = RunTelemetryUsage & {
|
|
106
|
-
promptTokens?: number;
|
|
107
|
-
completionTokens?: number;
|
|
214
|
+
promptTokens?: number | undefined;
|
|
215
|
+
completionTokens?: number | undefined;
|
|
108
216
|
inputTokenDetails?: { cacheReadTokens?: number };
|
|
109
217
|
outputTokenDetails?: { reasoningTokens?: number };
|
|
110
218
|
};
|
|
111
219
|
|
|
220
|
+
export type RunReportStepInit = {
|
|
221
|
+
usage?: RunTelemetryUsageInit | undefined;
|
|
222
|
+
toolCalls?: AssistantCloudRunReportToolCall[] | undefined;
|
|
223
|
+
startMs?: number | undefined;
|
|
224
|
+
endMs?: number | undefined;
|
|
225
|
+
finishReason?: string | undefined;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export type RunReportInit = {
|
|
229
|
+
threadId: string;
|
|
230
|
+
status: AssistantCloudRunReport["status"];
|
|
231
|
+
outcome?: RunReportOutcome | undefined;
|
|
232
|
+
errorCode?: string | undefined;
|
|
233
|
+
error?: string | undefined;
|
|
234
|
+
messageId?: string | undefined;
|
|
235
|
+
traceId?: string | undefined;
|
|
236
|
+
modelId?: string | undefined;
|
|
237
|
+
provider?: string | undefined;
|
|
238
|
+
usage?: RunTelemetryUsageInit | undefined;
|
|
239
|
+
steps?: RunReportStepInit[] | undefined;
|
|
240
|
+
totalSteps?: number | undefined;
|
|
241
|
+
toolCalls?: AssistantCloudRunReportToolCall[] | undefined;
|
|
242
|
+
durationMs?: number | undefined;
|
|
243
|
+
firstTokenMs?: number | undefined;
|
|
244
|
+
outputText?: string | undefined;
|
|
245
|
+
metadata?: Record<string, unknown> | undefined;
|
|
246
|
+
telemetry?: {
|
|
247
|
+
environment?: string | undefined;
|
|
248
|
+
release?: string | undefined;
|
|
249
|
+
tags?: readonly string[] | undefined;
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
function assignUsage(
|
|
254
|
+
report: AssistantCloudRunReport,
|
|
255
|
+
usage: RunTelemetryUsageInit | undefined,
|
|
256
|
+
): void {
|
|
257
|
+
if (!usage) return;
|
|
258
|
+
const normalized = normalizeRunTelemetryUsage(usage);
|
|
259
|
+
if (!normalized) return;
|
|
260
|
+
if (normalized.inputTokens !== undefined) {
|
|
261
|
+
report.input_tokens = normalized.inputTokens;
|
|
262
|
+
}
|
|
263
|
+
if (normalized.outputTokens !== undefined) {
|
|
264
|
+
report.output_tokens = normalized.outputTokens;
|
|
265
|
+
}
|
|
266
|
+
if (normalized.reasoningTokens !== undefined) {
|
|
267
|
+
report.reasoning_tokens = normalized.reasoningTokens;
|
|
268
|
+
}
|
|
269
|
+
if (normalized.cachedInputTokens !== undefined) {
|
|
270
|
+
report.cached_input_tokens = normalized.cachedInputTokens;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function createRunReportStep(
|
|
275
|
+
init: RunReportStepInit,
|
|
276
|
+
): NonNullable<AssistantCloudRunReport["steps"]>[number] {
|
|
277
|
+
const step: NonNullable<AssistantCloudRunReport["steps"]>[number] = {};
|
|
278
|
+
const usage = init.usage ? normalizeRunTelemetryUsage(init.usage) : undefined;
|
|
279
|
+
if (usage?.inputTokens !== undefined) step.input_tokens = usage.inputTokens;
|
|
280
|
+
if (usage?.outputTokens !== undefined)
|
|
281
|
+
step.output_tokens = usage.outputTokens;
|
|
282
|
+
if (usage?.reasoningTokens !== undefined) {
|
|
283
|
+
step.reasoning_tokens = usage.reasoningTokens;
|
|
284
|
+
}
|
|
285
|
+
if (usage?.cachedInputTokens !== undefined) {
|
|
286
|
+
step.cached_input_tokens = usage.cachedInputTokens;
|
|
287
|
+
}
|
|
288
|
+
if (init.toolCalls !== undefined) step.tool_calls = init.toolCalls;
|
|
289
|
+
if (init.startMs !== undefined) step.start_ms = init.startMs;
|
|
290
|
+
if (init.endMs !== undefined) step.end_ms = init.endMs;
|
|
291
|
+
if (init.finishReason !== undefined) {
|
|
292
|
+
step.finish_reason = init.finishReason.slice(0, 32);
|
|
293
|
+
}
|
|
294
|
+
return step;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function normalizeRunReportTags(
|
|
298
|
+
tags: readonly string[] | undefined,
|
|
299
|
+
): string[] | undefined {
|
|
300
|
+
if (!tags) return undefined;
|
|
301
|
+
const result: string[] = [];
|
|
302
|
+
const seen = new Set<string>();
|
|
303
|
+
for (const tag of tags) {
|
|
304
|
+
const normalized = tag.trim().slice(0, 64).trim();
|
|
305
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
306
|
+
seen.add(normalized);
|
|
307
|
+
result.push(normalized);
|
|
308
|
+
if (result.length === 20) break;
|
|
309
|
+
}
|
|
310
|
+
return result.length > 0 ? result : undefined;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function normalizeRunReportMilliseconds(
|
|
314
|
+
value: number | undefined,
|
|
315
|
+
): number | undefined {
|
|
316
|
+
if (value === undefined || !Number.isFinite(value)) return undefined;
|
|
317
|
+
return Math.max(0, Math.round(value));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function createRunReport(init: RunReportInit): AssistantCloudRunReport {
|
|
321
|
+
const report: AssistantCloudRunReport = {
|
|
322
|
+
thread_id: init.threadId,
|
|
323
|
+
status: init.status,
|
|
324
|
+
};
|
|
325
|
+
const traceId = init.traceId?.toLowerCase();
|
|
326
|
+
if (traceId && /^[0-9a-f]{32}$/.test(traceId)) report.trace_id = traceId;
|
|
327
|
+
if (init.outcome !== undefined) report.outcome_type = init.outcome;
|
|
328
|
+
if (init.errorCode !== undefined) report.error_code = init.errorCode;
|
|
329
|
+
if (init.error !== undefined) report.error = init.error;
|
|
330
|
+
if (init.messageId !== undefined) report.message_id = init.messageId;
|
|
331
|
+
if (init.modelId !== undefined) report.model_id = init.modelId;
|
|
332
|
+
if (init.provider !== undefined) {
|
|
333
|
+
report.provider = init.provider;
|
|
334
|
+
report.provider_type = init.provider;
|
|
335
|
+
}
|
|
336
|
+
assignUsage(report, init.usage);
|
|
337
|
+
if (init.steps !== undefined) {
|
|
338
|
+
report.steps = init.steps.map(createRunReportStep);
|
|
339
|
+
report.total_steps = init.steps.length;
|
|
340
|
+
} else if (init.totalSteps !== undefined) {
|
|
341
|
+
report.total_steps = init.totalSteps;
|
|
342
|
+
}
|
|
343
|
+
if (init.toolCalls !== undefined) report.tool_calls = init.toolCalls;
|
|
344
|
+
const durationMs = normalizeRunReportMilliseconds(init.durationMs);
|
|
345
|
+
if (durationMs !== undefined) report.duration_ms = durationMs;
|
|
346
|
+
const firstTokenMs = normalizeRunReportMilliseconds(init.firstTokenMs);
|
|
347
|
+
if (firstTokenMs !== undefined) report.first_token_ms = firstTokenMs;
|
|
348
|
+
if (init.outputText !== undefined) {
|
|
349
|
+
report.output_text = truncateRunTelemetryText(init.outputText);
|
|
350
|
+
}
|
|
351
|
+
if (init.metadata !== undefined) report.metadata = init.metadata;
|
|
352
|
+
if (init.telemetry?.environment !== undefined) {
|
|
353
|
+
report.environment = init.telemetry.environment;
|
|
354
|
+
}
|
|
355
|
+
if (init.telemetry?.release !== undefined) {
|
|
356
|
+
report.release = init.telemetry.release;
|
|
357
|
+
}
|
|
358
|
+
const tags = normalizeRunReportTags(init.telemetry?.tags);
|
|
359
|
+
if (tags !== undefined) report.tags = tags;
|
|
360
|
+
return report;
|
|
361
|
+
}
|
|
362
|
+
|
|
112
363
|
/**
|
|
113
364
|
* Resolves the token counts a provider reports under any of the names the AI
|
|
114
365
|
* SDK has used: the current top-level ones, the legacy prompt/completion pair,
|