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
@@ -2,6 +2,54 @@
2
2
  const MAX_TELEMETRY_TEXT_LENGTH = 5e4;
3
3
  const BASE64_PATTERN = /^[A-Za-z0-9+/]{100,}={0,2}$/;
4
4
  /**
5
+ * Maps a finish event to the report status and outcome. `fallbackStatus`
6
+ * applies when the event carries neither a finish reason nor a failure flag.
7
+ */
8
+ function deriveRunOutcome(input, fallbackStatus = "completed") {
9
+ if (input.isError) return { status: "error" };
10
+ if (input.isAbort) return {
11
+ status: "incomplete",
12
+ outcome: "aborted"
13
+ };
14
+ if (input.isDisconnect) return {
15
+ status: "incomplete",
16
+ outcome: "disconnected"
17
+ };
18
+ switch (input.finishReason) {
19
+ case "length": return {
20
+ status: "incomplete",
21
+ outcome: "length"
22
+ };
23
+ case "content-filter":
24
+ case "content_filter": return {
25
+ status: "incomplete",
26
+ outcome: "content_filter"
27
+ };
28
+ case "cancelled": return {
29
+ status: "incomplete",
30
+ outcome: "aborted"
31
+ };
32
+ case "error": return { status: "error" };
33
+ default: return { status: input.finishReason === void 0 ? fallbackStatus : "completed" };
34
+ }
35
+ }
36
+ const MAX_RUN_ERROR_CODE_LENGTH = 64;
37
+ const MAX_RUN_ERROR_LENGTH = 2048;
38
+ /**
39
+ * Reads the message and code the runs endpoint stores for a failed run. The
40
+ * code is the error's `code` when it has one, else its class name.
41
+ */
42
+ function describeRunError(error) {
43
+ if (error == null) return {};
44
+ const record = typeof error === "object" ? error : void 0;
45
+ const message = typeof error === "string" ? error : typeof record?.message === "string" ? record.message : void 0;
46
+ const code = typeof record?.code === "string" ? record.code : typeof record?.name === "string" && record.name !== "Error" ? record.name : void 0;
47
+ return {
48
+ ...message ? { error: message.slice(0, MAX_RUN_ERROR_LENGTH) } : void 0,
49
+ ...code ? { errorCode: code.slice(0, MAX_RUN_ERROR_CODE_LENGTH) } : void 0
50
+ };
51
+ }
52
+ /**
5
53
  * Clamps a string to the size the runs endpoint accepts for a single span
6
54
  * field.
7
55
  */
@@ -58,6 +106,101 @@ function createRunTelemetryToolCall(init) {
58
106
  return call;
59
107
  }
60
108
  /**
109
+ * Resolves the model ID a run reports, in the order an app can supply it: an
110
+ * explicit `modelId`, the `custom` bag, then the per-step `response.modelId`
111
+ * that a `messageMetadata` callback copies off the AI SDK's finish-step part.
112
+ * The AI SDK puts no model ID on a UI message part, so message metadata is the
113
+ * only channel one arrives on.
114
+ */
115
+ function extractRunTelemetryModelId(metadata) {
116
+ if (!metadata) return void 0;
117
+ if (typeof metadata.modelId === "string") return metadata.modelId;
118
+ const custom = metadata.custom;
119
+ if (typeof custom?.modelId === "string") return custom.modelId;
120
+ const steps = metadata.steps;
121
+ if (!Array.isArray(steps)) return void 0;
122
+ for (const step of steps) {
123
+ if (!step || typeof step !== "object") continue;
124
+ const response = step.response;
125
+ if (!response || typeof response !== "object") continue;
126
+ const modelId = response.modelId;
127
+ if (typeof modelId === "string") return modelId;
128
+ }
129
+ }
130
+ function assignUsage(report, usage) {
131
+ if (!usage) return;
132
+ const normalized = normalizeRunTelemetryUsage(usage);
133
+ if (!normalized) return;
134
+ if (normalized.inputTokens !== void 0) report.input_tokens = normalized.inputTokens;
135
+ if (normalized.outputTokens !== void 0) report.output_tokens = normalized.outputTokens;
136
+ if (normalized.reasoningTokens !== void 0) report.reasoning_tokens = normalized.reasoningTokens;
137
+ if (normalized.cachedInputTokens !== void 0) report.cached_input_tokens = normalized.cachedInputTokens;
138
+ }
139
+ function createRunReportStep(init) {
140
+ const step = {};
141
+ const usage = init.usage ? normalizeRunTelemetryUsage(init.usage) : void 0;
142
+ if (usage?.inputTokens !== void 0) step.input_tokens = usage.inputTokens;
143
+ if (usage?.outputTokens !== void 0) step.output_tokens = usage.outputTokens;
144
+ if (usage?.reasoningTokens !== void 0) step.reasoning_tokens = usage.reasoningTokens;
145
+ if (usage?.cachedInputTokens !== void 0) step.cached_input_tokens = usage.cachedInputTokens;
146
+ if (init.toolCalls !== void 0) step.tool_calls = init.toolCalls;
147
+ if (init.startMs !== void 0) step.start_ms = init.startMs;
148
+ if (init.endMs !== void 0) step.end_ms = init.endMs;
149
+ if (init.finishReason !== void 0) step.finish_reason = init.finishReason.slice(0, 32);
150
+ return step;
151
+ }
152
+ function normalizeRunReportTags(tags) {
153
+ if (!tags) return void 0;
154
+ const result = [];
155
+ const seen = /* @__PURE__ */ new Set();
156
+ for (const tag of tags) {
157
+ const normalized = tag.trim().slice(0, 64).trim();
158
+ if (!normalized || seen.has(normalized)) continue;
159
+ seen.add(normalized);
160
+ result.push(normalized);
161
+ if (result.length === 20) break;
162
+ }
163
+ return result.length > 0 ? result : void 0;
164
+ }
165
+ function normalizeRunReportMilliseconds(value) {
166
+ if (value === void 0 || !Number.isFinite(value)) return void 0;
167
+ return Math.max(0, Math.round(value));
168
+ }
169
+ function createRunReport(init) {
170
+ const report = {
171
+ thread_id: init.threadId,
172
+ status: init.status
173
+ };
174
+ const traceId = init.traceId?.toLowerCase();
175
+ if (traceId && /^[0-9a-f]{32}$/.test(traceId)) report.trace_id = traceId;
176
+ if (init.outcome !== void 0) report.outcome_type = init.outcome;
177
+ if (init.errorCode !== void 0) report.error_code = init.errorCode;
178
+ if (init.error !== void 0) report.error = init.error;
179
+ if (init.messageId !== void 0) report.message_id = init.messageId;
180
+ if (init.modelId !== void 0) report.model_id = init.modelId;
181
+ if (init.provider !== void 0) {
182
+ report.provider = init.provider;
183
+ report.provider_type = init.provider;
184
+ }
185
+ assignUsage(report, init.usage);
186
+ if (init.steps !== void 0) {
187
+ report.steps = init.steps.map(createRunReportStep);
188
+ report.total_steps = init.steps.length;
189
+ } else if (init.totalSteps !== void 0) report.total_steps = init.totalSteps;
190
+ if (init.toolCalls !== void 0) report.tool_calls = init.toolCalls;
191
+ const durationMs = normalizeRunReportMilliseconds(init.durationMs);
192
+ if (durationMs !== void 0) report.duration_ms = durationMs;
193
+ const firstTokenMs = normalizeRunReportMilliseconds(init.firstTokenMs);
194
+ if (firstTokenMs !== void 0) report.first_token_ms = firstTokenMs;
195
+ if (init.outputText !== void 0) report.output_text = truncateRunTelemetryText(init.outputText);
196
+ if (init.metadata !== void 0) report.metadata = init.metadata;
197
+ if (init.telemetry?.environment !== void 0) report.environment = init.telemetry.environment;
198
+ if (init.telemetry?.release !== void 0) report.release = init.telemetry.release;
199
+ const tags = normalizeRunReportTags(init.telemetry?.tags);
200
+ if (tags !== void 0) report.tags = tags;
201
+ return report;
202
+ }
203
+ /**
61
204
  * Resolves the token counts a provider reports under any of the names the AI
62
205
  * SDK has used: the current top-level ones, the legacy prompt/completion pair,
63
206
  * and the v7 token detail objects. Returns undefined when no count is present,
@@ -77,6 +220,6 @@ function normalizeRunTelemetryUsage(usage) {
77
220
  };
78
221
  }
79
222
  //#endregion
80
- export { createRunTelemetryToolCall, normalizeRunTelemetryUsage, truncateRunTelemetryText };
223
+ export { createRunReport, createRunTelemetryToolCall, deriveRunOutcome, describeRunError, extractRunTelemetryModelId, normalizeRunTelemetryUsage, truncateRunTelemetryText };
81
224
 
82
225
  //# sourceMappingURL=runTelemetry.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runTelemetry.js","names":[],"sources":["../src/runTelemetry.ts"],"sourcesContent":["import type { SamplingCallData } from \"./instrumentMcpSampling\";\n\nconst MAX_TELEMETRY_TEXT_LENGTH = 50_000;\n\nconst BASE64_PATTERN = /^[A-Za-z0-9+/]{100,}={0,2}$/;\n\nexport type AssistantCloudRunReportToolCall = {\n tool_name: string;\n tool_call_id: string;\n tool_args?: string;\n tool_result?: string;\n tool_source?: \"mcp\" | \"frontend\" | \"backend\";\n start_ms?: number;\n end_ms?: number;\n sampling_calls?: SamplingCallData[];\n};\n\n/**\n * Clamps a string to the size the runs endpoint accepts for a single span\n * field.\n */\nexport function truncateRunTelemetryText(value: string): string {\n if (value.length <= MAX_TELEMETRY_TEXT_LENGTH) return value;\n return value.slice(0, MAX_TELEMETRY_TEXT_LENGTH);\n}\n\nfunction safeStringify(value: unknown): string | undefined {\n if (value == null) return undefined;\n try {\n return truncateRunTelemetryText(JSON.stringify(value));\n } catch {\n return undefined;\n }\n}\n\nfunction summarizeMcpResult(value: unknown): string | undefined {\n if (value == null) return undefined;\n try {\n const parsed = typeof value === \"string\" ? JSON.parse(value) : value;\n if (Array.isArray(parsed)) {\n const summarized = parsed.map((item) => {\n if (item && typeof item === \"object\" && item.type) {\n if (\n (item.type === \"image\" || item.type === \"audio\") &&\n typeof item.data === \"string\" &&\n BASE64_PATTERN.test(item.data.slice(0, 200))\n ) {\n const sizeKB = ((item.data.length * 3) / 4 / 1024).toFixed(1);\n return { ...item, data: `[${item.type}: ${sizeKB}KB]` };\n }\n }\n return item;\n });\n return truncateRunTelemetryText(JSON.stringify(summarized));\n }\n } catch {\n // not JSON array, fall through\n }\n return safeStringify(value);\n}\n\nexport type RunTelemetryToolCallInit = {\n toolName: string;\n toolCallId: string;\n args?: unknown;\n /**\n * Pre-serialized arguments, used in place of serializing `args`. Values over\n * the span size are clamped before they are included in the report.\n */\n argsText?: string | undefined;\n result?: unknown;\n toolSource?: \"mcp\" | \"frontend\" | \"backend\" | undefined;\n};\n\n/**\n * Serializes one tool call into the shape the runs endpoint accepts. An `mcp`\n * source has its result summarized, because MCP content blocks carry inline\n * base64 image and audio payloads that would otherwise dominate the report.\n */\nexport function createRunTelemetryToolCall(\n init: RunTelemetryToolCallInit,\n): AssistantCloudRunReportToolCall {\n const { toolName, toolCallId, args, argsText, result, toolSource } = init;\n const call: AssistantCloudRunReportToolCall = {\n tool_name: toolName,\n tool_call_id: toolCallId,\n };\n const toolArgs =\n argsText != null ? truncateRunTelemetryText(argsText) : safeStringify(args);\n if (toolArgs !== undefined) call.tool_args = toolArgs;\n const toolResult =\n toolSource === \"mcp\" ? summarizeMcpResult(result) : safeStringify(result);\n if (toolResult !== undefined) call.tool_result = toolResult;\n if (toolSource) call.tool_source = toolSource;\n return call;\n}\n\nexport type RunTelemetryUsage = {\n inputTokens?: number;\n outputTokens?: number;\n reasoningTokens?: number;\n cachedInputTokens?: number;\n};\n\nexport type RunTelemetryUsageInit = RunTelemetryUsage & {\n promptTokens?: number;\n completionTokens?: number;\n inputTokenDetails?: { cacheReadTokens?: number };\n outputTokenDetails?: { reasoningTokens?: number };\n};\n\n/**\n * Resolves the token counts a provider reports under any of the names the AI\n * SDK has used: the current top-level ones, the legacy prompt/completion pair,\n * and the v7 token detail objects. Returns undefined when no count is present,\n * so callers can tell an empty usage object from a zeroed one.\n */\nexport function normalizeRunTelemetryUsage(\n usage: RunTelemetryUsageInit,\n): RunTelemetryUsage | undefined {\n const inputTokens = usage.inputTokens ?? usage.promptTokens;\n const outputTokens = usage.outputTokens ?? usage.completionTokens;\n // AI SDK v7 moved these under token detail objects; v6 kept them top-level.\n const reasoningTokens =\n usage.reasoningTokens ?? usage.outputTokenDetails?.reasoningTokens;\n const cachedInputTokens =\n usage.cachedInputTokens ?? usage.inputTokenDetails?.cacheReadTokens;\n\n if (\n inputTokens == null &&\n outputTokens == null &&\n reasoningTokens == null &&\n cachedInputTokens == null\n ) {\n return undefined;\n }\n\n return {\n ...(inputTokens != null ? { inputTokens } : undefined),\n ...(outputTokens != null ? { outputTokens } : undefined),\n ...(reasoningTokens != null ? { reasoningTokens } : undefined),\n ...(cachedInputTokens != null ? { cachedInputTokens } : undefined),\n };\n}\n"],"mappings":";AAEA,MAAM,4BAA4B;AAElC,MAAM,iBAAiB;;;;;AAiBvB,SAAgB,yBAAyB,OAAuB;CAC9D,IAAI,MAAM,UAAU,2BAA2B,OAAO;CACtD,OAAO,MAAM,MAAM,GAAG,yBAAyB;AACjD;AAEA,SAAS,cAAc,OAAoC;CACzD,IAAI,SAAS,MAAM,OAAO,KAAA;CAC1B,IAAI;EACF,OAAO,yBAAyB,KAAK,UAAU,KAAK,CAAC;CACvD,QAAQ;EACN;CACF;AACF;AAEA,SAAS,mBAAmB,OAAoC;CAC9D,IAAI,SAAS,MAAM,OAAO,KAAA;CAC1B,IAAI;EACF,MAAM,SAAS,OAAO,UAAU,WAAW,KAAK,MAAM,KAAK,IAAI;EAC/D,IAAI,MAAM,QAAQ,MAAM,GAAG;GACzB,MAAM,aAAa,OAAO,KAAK,SAAS;IACtC,IAAI,QAAQ,OAAO,SAAS,YAAY,KAAK,MAExC;UAAA,KAAK,SAAS,WAAW,KAAK,SAAS,YACxC,OAAO,KAAK,SAAS,YACrB,eAAe,KAAK,KAAK,KAAK,MAAM,GAAG,GAAG,CAAC,GAC3C;MACA,MAAM,UAAW,KAAK,KAAK,SAAS,IAAK,IAAI,KAAA,CAAM,QAAQ,CAAC;MAC5D,OAAO;OAAE,GAAG;OAAM,MAAM,IAAI,KAAK,KAAK,IAAI,OAAO;MAAK;KACxD;;IAEF,OAAO;GACT,CAAC;GACD,OAAO,yBAAyB,KAAK,UAAU,UAAU,CAAC;EAC5D;CACF,QAAQ,CAER;CACA,OAAO,cAAc,KAAK;AAC5B;;;;;;AAoBA,SAAgB,2BACd,MACiC;CACjC,MAAM,EAAE,UAAU,YAAY,MAAM,UAAU,QAAQ,eAAe;CACrE,MAAM,OAAwC;EAC5C,WAAW;EACX,cAAc;CAChB;CACA,MAAM,WACJ,YAAY,OAAO,yBAAyB,QAAQ,IAAI,cAAc,IAAI;CAC5E,IAAI,aAAa,KAAA,GAAW,KAAK,YAAY;CAC7C,MAAM,aACJ,eAAe,QAAQ,mBAAmB,MAAM,IAAI,cAAc,MAAM;CAC1E,IAAI,eAAe,KAAA,GAAW,KAAK,cAAc;CACjD,IAAI,YAAY,KAAK,cAAc;CACnC,OAAO;AACT;;;;;;;AAsBA,SAAgB,2BACd,OAC+B;CAC/B,MAAM,cAAc,MAAM,eAAe,MAAM;CAC/C,MAAM,eAAe,MAAM,gBAAgB,MAAM;CAEjD,MAAM,kBACJ,MAAM,mBAAmB,MAAM,oBAAoB;CACrD,MAAM,oBACJ,MAAM,qBAAqB,MAAM,mBAAmB;CAEtD,IACE,eAAe,QACf,gBAAgB,QAChB,mBAAmB,QACnB,qBAAqB,MAErB;CAGF,OAAO;EACL,GAAI,eAAe,OAAO,EAAE,YAAY,IAAI,KAAA;EAC5C,GAAI,gBAAgB,OAAO,EAAE,aAAa,IAAI,KAAA;EAC9C,GAAI,mBAAmB,OAAO,EAAE,gBAAgB,IAAI,KAAA;EACpD,GAAI,qBAAqB,OAAO,EAAE,kBAAkB,IAAI,KAAA;CAC1D;AACF"}
1
+ {"version":3,"file":"runTelemetry.js","names":[],"sources":["../src/runTelemetry.ts"],"sourcesContent":["import type { SamplingCallData } from \"./instrumentMcpSampling\";\nimport type { AssistantCloudRunReport } from \"./AssistantCloudRuns\";\n\nconst MAX_TELEMETRY_TEXT_LENGTH = 50_000;\n\nconst BASE64_PATTERN = /^[A-Za-z0-9+/]{100,}={0,2}$/;\n\nexport type AssistantCloudRunReportToolCall = {\n tool_name: string;\n tool_call_id: string;\n tool_args?: string;\n tool_result?: string;\n tool_source?: \"mcp\" | \"frontend\" | \"backend\";\n start_ms?: number;\n end_ms?: number;\n sampling_calls?: SamplingCallData[];\n};\n\nexport type RunReportOutcome =\n | \"aborted\"\n | \"disconnected\"\n | \"length\"\n | \"content_filter\";\n\n/**\n * Maps a finish event to the report status and outcome. `fallbackStatus`\n * applies when the event carries neither a finish reason nor a failure flag.\n */\nexport function deriveRunOutcome(\n input: {\n finishReason?: string | undefined;\n isAbort?: boolean | undefined;\n isDisconnect?: boolean | undefined;\n isError?: boolean | undefined;\n },\n fallbackStatus: \"completed\" | \"incomplete\" = \"completed\",\n): {\n status: \"completed\" | \"incomplete\" | \"error\";\n outcome?: RunReportOutcome;\n} {\n if (input.isError) return { status: \"error\" };\n if (input.isAbort) return { status: \"incomplete\", outcome: \"aborted\" };\n if (input.isDisconnect) {\n return { status: \"incomplete\", outcome: \"disconnected\" };\n }\n switch (input.finishReason) {\n case \"length\":\n return { status: \"incomplete\", outcome: \"length\" };\n case \"content-filter\":\n case \"content_filter\":\n return { status: \"incomplete\", outcome: \"content_filter\" };\n case \"cancelled\":\n return { status: \"incomplete\", outcome: \"aborted\" };\n case \"error\":\n return { status: \"error\" };\n default:\n return {\n status: input.finishReason === undefined ? fallbackStatus : \"completed\",\n };\n }\n}\n\nconst MAX_RUN_ERROR_CODE_LENGTH = 64;\nconst MAX_RUN_ERROR_LENGTH = 2048;\n\n/**\n * Reads the message and code the runs endpoint stores for a failed run. The\n * code is the error's `code` when it has one, else its class name.\n */\nexport function describeRunError(error: unknown): {\n error?: string;\n errorCode?: string;\n} {\n if (error == null) return {};\n const record =\n typeof error === \"object\" ? (error as Record<string, unknown>) : undefined;\n const message =\n typeof error === \"string\"\n ? error\n : typeof record?.message === \"string\"\n ? record.message\n : undefined;\n const code =\n typeof record?.code === \"string\"\n ? record.code\n : typeof record?.name === \"string\" && record.name !== \"Error\"\n ? record.name\n : undefined;\n return {\n ...(message\n ? { error: message.slice(0, MAX_RUN_ERROR_LENGTH) }\n : undefined),\n ...(code\n ? { errorCode: code.slice(0, MAX_RUN_ERROR_CODE_LENGTH) }\n : undefined),\n };\n}\n\n/**\n * Clamps a string to the size the runs endpoint accepts for a single span\n * field.\n */\nexport function truncateRunTelemetryText(value: string): string {\n if (value.length <= MAX_TELEMETRY_TEXT_LENGTH) return value;\n return value.slice(0, MAX_TELEMETRY_TEXT_LENGTH);\n}\n\nfunction safeStringify(value: unknown): string | undefined {\n if (value == null) return undefined;\n try {\n return truncateRunTelemetryText(JSON.stringify(value));\n } catch {\n return undefined;\n }\n}\n\nfunction summarizeMcpResult(value: unknown): string | undefined {\n if (value == null) return undefined;\n try {\n const parsed = typeof value === \"string\" ? JSON.parse(value) : value;\n if (Array.isArray(parsed)) {\n const summarized = parsed.map((item) => {\n if (item && typeof item === \"object\" && item.type) {\n if (\n (item.type === \"image\" || item.type === \"audio\") &&\n typeof item.data === \"string\" &&\n BASE64_PATTERN.test(item.data.slice(0, 200))\n ) {\n const sizeKB = ((item.data.length * 3) / 4 / 1024).toFixed(1);\n return { ...item, data: `[${item.type}: ${sizeKB}KB]` };\n }\n }\n return item;\n });\n return truncateRunTelemetryText(JSON.stringify(summarized));\n }\n } catch {\n // not JSON array, fall through\n }\n return safeStringify(value);\n}\n\nexport type RunTelemetryToolCallInit = {\n toolName: string;\n toolCallId: string;\n args?: unknown;\n /**\n * Pre-serialized arguments, used in place of serializing `args`. Values over\n * the span size are clamped before they are included in the report.\n */\n argsText?: string | undefined;\n result?: unknown;\n toolSource?: \"mcp\" | \"frontend\" | \"backend\" | undefined;\n};\n\n/**\n * Serializes one tool call into the shape the runs endpoint accepts. An `mcp`\n * source has its result summarized, because MCP content blocks carry inline\n * base64 image and audio payloads that would otherwise dominate the report.\n */\nexport function createRunTelemetryToolCall(\n init: RunTelemetryToolCallInit,\n): AssistantCloudRunReportToolCall {\n const { toolName, toolCallId, args, argsText, result, toolSource } = init;\n const call: AssistantCloudRunReportToolCall = {\n tool_name: toolName,\n tool_call_id: toolCallId,\n };\n const toolArgs =\n argsText != null ? truncateRunTelemetryText(argsText) : safeStringify(args);\n if (toolArgs !== undefined) call.tool_args = toolArgs;\n const toolResult =\n toolSource === \"mcp\" ? summarizeMcpResult(result) : safeStringify(result);\n if (toolResult !== undefined) call.tool_result = toolResult;\n if (toolSource) call.tool_source = toolSource;\n return call;\n}\n\n/**\n * Resolves the model ID a run reports, in the order an app can supply it: an\n * explicit `modelId`, the `custom` bag, then the per-step `response.modelId`\n * that a `messageMetadata` callback copies off the AI SDK's finish-step part.\n * The AI SDK puts no model ID on a UI message part, so message metadata is the\n * only channel one arrives on.\n */\nexport function extractRunTelemetryModelId(\n metadata: Record<string, unknown> | undefined,\n): string | undefined {\n if (!metadata) return undefined;\n if (typeof metadata.modelId === \"string\") return metadata.modelId;\n const custom = metadata.custom as Record<string, unknown> | undefined;\n if (typeof custom?.modelId === \"string\") return custom.modelId;\n\n const steps: unknown = metadata.steps;\n if (!Array.isArray(steps)) return undefined;\n for (const step of steps as unknown[]) {\n if (!step || typeof step !== \"object\") continue;\n const response = (step as Record<string, unknown>).response;\n if (!response || typeof response !== \"object\") continue;\n const modelId = (response as Record<string, unknown>).modelId;\n if (typeof modelId === \"string\") return modelId;\n }\n return undefined;\n}\n\nexport type RunTelemetryUsage = {\n inputTokens?: number | undefined;\n outputTokens?: number | undefined;\n reasoningTokens?: number | undefined;\n cachedInputTokens?: number | undefined;\n};\n\nexport type RunTelemetryUsageInit = RunTelemetryUsage & {\n promptTokens?: number | undefined;\n completionTokens?: number | undefined;\n inputTokenDetails?: { cacheReadTokens?: number };\n outputTokenDetails?: { reasoningTokens?: number };\n};\n\nexport type RunReportStepInit = {\n usage?: RunTelemetryUsageInit | undefined;\n toolCalls?: AssistantCloudRunReportToolCall[] | undefined;\n startMs?: number | undefined;\n endMs?: number | undefined;\n finishReason?: string | undefined;\n};\n\n/**\n * The run report fields read from the messages of one run, in whichever\n * format they were stored: the status the messages imply, the tool calls, the\n * steps, the text, the usage and the model.\n */\nexport type RunMessageTelemetry = {\n assistantMessageId?: string;\n status: \"completed\" | \"incomplete\";\n toolCalls?: AssistantCloudRunReportToolCall[];\n steps?: RunReportStepInit[];\n totalSteps?: number;\n outputText?: string;\n usage?: RunTelemetryUsage;\n modelId?: string;\n metadata?: Record<string, unknown>;\n};\n\nexport type RunReportInit = {\n threadId: string;\n status: AssistantCloudRunReport[\"status\"];\n outcome?: RunReportOutcome | undefined;\n errorCode?: string | undefined;\n error?: string | undefined;\n messageId?: string | undefined;\n traceId?: string | undefined;\n modelId?: string | undefined;\n provider?: string | undefined;\n usage?: RunTelemetryUsageInit | undefined;\n steps?: RunReportStepInit[] | undefined;\n totalSteps?: number | undefined;\n toolCalls?: AssistantCloudRunReportToolCall[] | undefined;\n durationMs?: number | undefined;\n firstTokenMs?: number | undefined;\n outputText?: string | undefined;\n metadata?: Record<string, unknown> | undefined;\n telemetry?: {\n environment?: string | undefined;\n release?: string | undefined;\n tags?: readonly string[] | undefined;\n };\n};\n\nfunction assignUsage(\n report: AssistantCloudRunReport,\n usage: RunTelemetryUsageInit | undefined,\n): void {\n if (!usage) return;\n const normalized = normalizeRunTelemetryUsage(usage);\n if (!normalized) return;\n if (normalized.inputTokens !== undefined) {\n report.input_tokens = normalized.inputTokens;\n }\n if (normalized.outputTokens !== undefined) {\n report.output_tokens = normalized.outputTokens;\n }\n if (normalized.reasoningTokens !== undefined) {\n report.reasoning_tokens = normalized.reasoningTokens;\n }\n if (normalized.cachedInputTokens !== undefined) {\n report.cached_input_tokens = normalized.cachedInputTokens;\n }\n}\n\nfunction createRunReportStep(\n init: RunReportStepInit,\n): NonNullable<AssistantCloudRunReport[\"steps\"]>[number] {\n const step: NonNullable<AssistantCloudRunReport[\"steps\"]>[number] = {};\n const usage = init.usage ? normalizeRunTelemetryUsage(init.usage) : undefined;\n if (usage?.inputTokens !== undefined) step.input_tokens = usage.inputTokens;\n if (usage?.outputTokens !== undefined)\n step.output_tokens = usage.outputTokens;\n if (usage?.reasoningTokens !== undefined) {\n step.reasoning_tokens = usage.reasoningTokens;\n }\n if (usage?.cachedInputTokens !== undefined) {\n step.cached_input_tokens = usage.cachedInputTokens;\n }\n if (init.toolCalls !== undefined) step.tool_calls = init.toolCalls;\n if (init.startMs !== undefined) step.start_ms = init.startMs;\n if (init.endMs !== undefined) step.end_ms = init.endMs;\n if (init.finishReason !== undefined) {\n step.finish_reason = init.finishReason.slice(0, 32);\n }\n return step;\n}\n\nfunction normalizeRunReportTags(\n tags: readonly string[] | undefined,\n): string[] | undefined {\n if (!tags) return undefined;\n const result: string[] = [];\n const seen = new Set<string>();\n for (const tag of tags) {\n const normalized = tag.trim().slice(0, 64).trim();\n if (!normalized || seen.has(normalized)) continue;\n seen.add(normalized);\n result.push(normalized);\n if (result.length === 20) break;\n }\n return result.length > 0 ? result : undefined;\n}\n\nfunction normalizeRunReportMilliseconds(\n value: number | undefined,\n): number | undefined {\n if (value === undefined || !Number.isFinite(value)) return undefined;\n return Math.max(0, Math.round(value));\n}\n\nexport function createRunReport(init: RunReportInit): AssistantCloudRunReport {\n const report: AssistantCloudRunReport = {\n thread_id: init.threadId,\n status: init.status,\n };\n const traceId = init.traceId?.toLowerCase();\n if (traceId && /^[0-9a-f]{32}$/.test(traceId)) report.trace_id = traceId;\n if (init.outcome !== undefined) report.outcome_type = init.outcome;\n if (init.errorCode !== undefined) report.error_code = init.errorCode;\n if (init.error !== undefined) report.error = init.error;\n if (init.messageId !== undefined) report.message_id = init.messageId;\n if (init.modelId !== undefined) report.model_id = init.modelId;\n if (init.provider !== undefined) {\n report.provider = init.provider;\n report.provider_type = init.provider;\n }\n assignUsage(report, init.usage);\n if (init.steps !== undefined) {\n report.steps = init.steps.map(createRunReportStep);\n report.total_steps = init.steps.length;\n } else if (init.totalSteps !== undefined) {\n report.total_steps = init.totalSteps;\n }\n if (init.toolCalls !== undefined) report.tool_calls = init.toolCalls;\n const durationMs = normalizeRunReportMilliseconds(init.durationMs);\n if (durationMs !== undefined) report.duration_ms = durationMs;\n const firstTokenMs = normalizeRunReportMilliseconds(init.firstTokenMs);\n if (firstTokenMs !== undefined) report.first_token_ms = firstTokenMs;\n if (init.outputText !== undefined) {\n report.output_text = truncateRunTelemetryText(init.outputText);\n }\n if (init.metadata !== undefined) report.metadata = init.metadata;\n if (init.telemetry?.environment !== undefined) {\n report.environment = init.telemetry.environment;\n }\n if (init.telemetry?.release !== undefined) {\n report.release = init.telemetry.release;\n }\n const tags = normalizeRunReportTags(init.telemetry?.tags);\n if (tags !== undefined) report.tags = tags;\n return report;\n}\n\n/**\n * Resolves the token counts a provider reports under any of the names the AI\n * SDK has used: the current top-level ones, the legacy prompt/completion pair,\n * and the v7 token detail objects. Returns undefined when no count is present,\n * so callers can tell an empty usage object from a zeroed one.\n */\nexport function normalizeRunTelemetryUsage(\n usage: RunTelemetryUsageInit,\n): RunTelemetryUsage | undefined {\n const inputTokens = usage.inputTokens ?? usage.promptTokens;\n const outputTokens = usage.outputTokens ?? usage.completionTokens;\n // AI SDK v7 moved these under token detail objects; v6 kept them top-level.\n const reasoningTokens =\n usage.reasoningTokens ?? usage.outputTokenDetails?.reasoningTokens;\n const cachedInputTokens =\n usage.cachedInputTokens ?? usage.inputTokenDetails?.cacheReadTokens;\n\n if (\n inputTokens == null &&\n outputTokens == null &&\n reasoningTokens == null &&\n cachedInputTokens == null\n ) {\n return undefined;\n }\n\n return {\n ...(inputTokens != null ? { inputTokens } : undefined),\n ...(outputTokens != null ? { outputTokens } : undefined),\n ...(reasoningTokens != null ? { reasoningTokens } : undefined),\n ...(cachedInputTokens != null ? { cachedInputTokens } : undefined),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { ReadableSpan, SpanExporter, SpanProcessor } from "@opentelemetry/sdk-trace-base";
2
+ //#region src/telemetry/index.d.ts
3
+ type AssistantCloudTraceExportOptions = {
4
+ apiKey: string;
5
+ baseUrl?: string;
6
+ headers?: Record<string, string>;
7
+ };
8
+ type AssistantCloudSpanProcessorOptions = {
9
+ filter?: (span: ReadableSpan) => boolean;
10
+ };
11
+ declare function assistantCloudTraceExportOptions({ apiKey, baseUrl, headers }: AssistantCloudTraceExportOptions): {
12
+ url: string;
13
+ headers: Record<string, string>;
14
+ };
15
+ declare function createAssistantCloudTraceExporter(options: AssistantCloudTraceExportOptions): SpanExporter;
16
+ declare function isAssistantCloudSpan(span: ReadableSpan): boolean;
17
+ declare function createAssistantCloudSpanProcessor(exporter: SpanExporter, options?: AssistantCloudSpanProcessorOptions): SpanProcessor;
18
+ declare function assistantCloudTraceMetadata(): {
19
+ traceId?: string;
20
+ };
21
+ declare function withAssistantCloudTraceMetadata<Part extends {
22
+ type: string;
23
+ } = {
24
+ type: string;
25
+ }>(messageMetadata?: (options: {
26
+ part: Part;
27
+ }) => Record<string, unknown> | undefined): (options: {
28
+ part: Part;
29
+ }) => Record<string, unknown> | undefined;
30
+ //#endregion
31
+ export { AssistantCloudSpanProcessorOptions, AssistantCloudTraceExportOptions, assistantCloudTraceExportOptions, assistantCloudTraceMetadata, createAssistantCloudSpanProcessor, createAssistantCloudTraceExporter, isAssistantCloudSpan, withAssistantCloudTraceMetadata };
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/telemetry/index.ts"],"mappings":";;KAYY;EACV;EACA;EACA,UAAU;;KAGA;EACV,UAAU,MAAM;;iBAGF,mCACd,QACA,SACA,WACC;EACD;EACA,SAAS;;iBAmBK,kCACd,SAAS,mCACR;iBAIa,qBAAqB,MAAM;iBAO3B,kCACd,UAAU,cACV,UAAS,qCACR;iBAca;EAAiC;;iBAOjC,gCACd;EAAe;;EAAmB;GAElC,mBAAmB;EACjB,MAAM;MACF,uCACJ;EAAW,MAAM;MAAW"}
@@ -0,0 +1,62 @@
1
+ import { trace } from "@opentelemetry/api";
2
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
3
+ import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
4
+ //#region src/telemetry/index.ts
5
+ const DEFAULT_BACKEND_BASE_URL = "https://backend.assistant-api.com";
6
+ const AI_SPAN_PREFIXES = [
7
+ "gen_ai.",
8
+ "ai.",
9
+ "llm."
10
+ ];
11
+ function assistantCloudTraceExportOptions({ apiKey, baseUrl = DEFAULT_BACKEND_BASE_URL, headers }) {
12
+ if (!apiKey) throw new Error("An Assistant Cloud API key is required");
13
+ return {
14
+ url: `${trimTrailingSlashes(baseUrl)}/v1/traces`,
15
+ headers: {
16
+ Authorization: `Bearer ${apiKey}`,
17
+ ...headers
18
+ }
19
+ };
20
+ }
21
+ function trimTrailingSlashes(url) {
22
+ let end = url.length;
23
+ while (end > 0 && url[end - 1] === "/") end--;
24
+ return url.slice(0, end);
25
+ }
26
+ function createAssistantCloudTraceExporter(options) {
27
+ return new OTLPTraceExporter(assistantCloudTraceExportOptions(options));
28
+ }
29
+ function isAssistantCloudSpan(span) {
30
+ return typeof span.attributes["gen_ai.operation.name"] === "string" || AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix));
31
+ }
32
+ function createAssistantCloudSpanProcessor(exporter, options = {}) {
33
+ const processor = new BatchSpanProcessor(exporter);
34
+ const filter = options.filter ?? isAssistantCloudSpan;
35
+ return {
36
+ onStart: (span, parentContext) => processor.onStart(span, parentContext),
37
+ onEnd: (span) => {
38
+ if (filter(span)) processor.onEnd(span);
39
+ },
40
+ forceFlush: () => processor.forceFlush(),
41
+ shutdown: () => processor.shutdown()
42
+ };
43
+ }
44
+ function assistantCloudTraceMetadata() {
45
+ const spanContext = trace.getActiveSpan()?.spanContext();
46
+ return spanContext && trace.isSpanContextValid(spanContext) ? { traceId: spanContext.traceId } : {};
47
+ }
48
+ function withAssistantCloudTraceMetadata(messageMetadata) {
49
+ return (options) => {
50
+ const metadata = messageMetadata?.(options);
51
+ if (options.part.type !== "start") return metadata;
52
+ const trace = assistantCloudTraceMetadata();
53
+ return trace.traceId === void 0 ? metadata : {
54
+ ...metadata,
55
+ ...trace
56
+ };
57
+ };
58
+ }
59
+ //#endregion
60
+ export { assistantCloudTraceExportOptions, assistantCloudTraceMetadata, createAssistantCloudSpanProcessor, createAssistantCloudTraceExporter, isAssistantCloudSpan, withAssistantCloudTraceMetadata };
61
+
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/telemetry/index.ts"],"sourcesContent":["import { trace } from \"@opentelemetry/api\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n BatchSpanProcessor,\n type ReadableSpan,\n type SpanExporter,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\n\nconst DEFAULT_BACKEND_BASE_URL = \"https://backend.assistant-api.com\";\nconst AI_SPAN_PREFIXES = [\"gen_ai.\", \"ai.\", \"llm.\"];\n\nexport type AssistantCloudTraceExportOptions = {\n apiKey: string;\n baseUrl?: string;\n headers?: Record<string, string>;\n};\n\nexport type AssistantCloudSpanProcessorOptions = {\n filter?: (span: ReadableSpan) => boolean;\n};\n\nexport function assistantCloudTraceExportOptions({\n apiKey,\n baseUrl = DEFAULT_BACKEND_BASE_URL,\n headers,\n}: AssistantCloudTraceExportOptions): {\n url: string;\n headers: Record<string, string>;\n} {\n if (!apiKey) throw new Error(\"An Assistant Cloud API key is required\");\n\n return {\n url: `${trimTrailingSlashes(baseUrl)}/v1/traces`,\n headers: {\n Authorization: `Bearer ${apiKey}`,\n ...headers,\n },\n };\n}\n\nfunction trimTrailingSlashes(url: string): string {\n let end = url.length;\n while (end > 0 && url[end - 1] === \"/\") end--;\n return url.slice(0, end);\n}\n\nexport function createAssistantCloudTraceExporter(\n options: AssistantCloudTraceExportOptions,\n): SpanExporter {\n return new OTLPTraceExporter(assistantCloudTraceExportOptions(options));\n}\n\nexport function isAssistantCloudSpan(span: ReadableSpan): boolean {\n return (\n typeof span.attributes[\"gen_ai.operation.name\"] === \"string\" ||\n AI_SPAN_PREFIXES.some((prefix) => span.name.startsWith(prefix))\n );\n}\n\nexport function createAssistantCloudSpanProcessor(\n exporter: SpanExporter,\n options: AssistantCloudSpanProcessorOptions = {},\n): SpanProcessor {\n const processor = new BatchSpanProcessor(exporter);\n const filter = options.filter ?? isAssistantCloudSpan;\n\n return {\n onStart: (span, parentContext) => processor.onStart(span, parentContext),\n onEnd: (span) => {\n if (filter(span)) processor.onEnd(span);\n },\n forceFlush: () => processor.forceFlush(),\n shutdown: () => processor.shutdown(),\n };\n}\n\nexport function assistantCloudTraceMetadata(): { traceId?: string } {\n const spanContext = trace.getActiveSpan()?.spanContext();\n return spanContext && trace.isSpanContextValid(spanContext)\n ? { traceId: spanContext.traceId }\n : {};\n}\n\nexport function withAssistantCloudTraceMetadata<\n Part extends { type: string } = { type: string },\n>(\n messageMetadata?: (options: {\n part: Part;\n }) => Record<string, unknown> | undefined,\n): (options: { part: Part }) => Record<string, unknown> | undefined {\n return (options) => {\n const metadata = messageMetadata?.(options);\n if (options.part.type !== \"start\") return metadata;\n const trace = assistantCloudTraceMetadata();\n return trace.traceId === undefined ? metadata : { ...metadata, ...trace };\n };\n}\n"],"mappings":";;;;AASA,MAAM,2BAA2B;AACjC,MAAM,mBAAmB;CAAC;CAAW;CAAO;AAAM;AAYlD,SAAgB,iCAAiC,EAC/C,QACA,UAAU,0BACV,WAIA;CACA,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,wCAAwC;CAErE,OAAO;EACL,KAAK,GAAG,oBAAoB,OAAO,EAAE;EACrC,SAAS;GACP,eAAe,UAAU;GACzB,GAAG;EACL;CACF;AACF;AAEA,SAAS,oBAAoB,KAAqB;CAChD,IAAI,MAAM,IAAI;CACd,OAAO,MAAM,KAAK,IAAI,MAAM,OAAO,KAAK;CACxC,OAAO,IAAI,MAAM,GAAG,GAAG;AACzB;AAEA,SAAgB,kCACd,SACc;CACd,OAAO,IAAI,kBAAkB,iCAAiC,OAAO,CAAC;AACxE;AAEA,SAAgB,qBAAqB,MAA6B;CAChE,OACE,OAAO,KAAK,WAAW,6BAA6B,YACpD,iBAAiB,MAAM,WAAW,KAAK,KAAK,WAAW,MAAM,CAAC;AAElE;AAEA,SAAgB,kCACd,UACA,UAA8C,CAAC,GAChC;CACf,MAAM,YAAY,IAAI,mBAAmB,QAAQ;CACjD,MAAM,SAAS,QAAQ,UAAU;CAEjC,OAAO;EACL,UAAU,MAAM,kBAAkB,UAAU,QAAQ,MAAM,aAAa;EACvE,QAAQ,SAAS;GACf,IAAI,OAAO,IAAI,GAAG,UAAU,MAAM,IAAI;EACxC;EACA,kBAAkB,UAAU,WAAW;EACvC,gBAAgB,UAAU,SAAS;CACrC;AACF;AAEA,SAAgB,8BAAoD;CAClE,MAAM,cAAc,MAAM,cAAc,CAAC,EAAE,YAAY;CACvD,OAAO,eAAe,MAAM,mBAAmB,WAAW,IACtD,EAAE,SAAS,YAAY,QAAQ,IAC/B,CAAC;AACP;AAEA,SAAgB,gCAGd,iBAGkE;CAClE,QAAQ,YAAY;EAClB,MAAM,WAAW,kBAAkB,OAAO;EAC1C,IAAI,QAAQ,KAAK,SAAS,SAAS,OAAO;EAC1C,MAAM,QAAQ,4BAA4B;EAC1C,OAAO,MAAM,YAAY,KAAA,IAAY,WAAW;GAAE,GAAG;GAAU,GAAG;EAAM;CAC1E;AACF"}
@@ -0,0 +1,5 @@
1
+ //#region src/version.d.ts
2
+ declare const ASSISTANT_CLOUD_VERSION: string;
3
+ //#endregion
4
+ export { ASSISTANT_CLOUD_VERSION };
5
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","names":[],"sources":["../src/version.ts"],"mappings":";cAAa"}
@@ -0,0 +1,6 @@
1
+ //#region src/version.ts
2
+ const ASSISTANT_CLOUD_VERSION = "0.2.1";
3
+ //#endregion
4
+ export { ASSISTANT_CLOUD_VERSION };
5
+
6
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","names":[],"sources":["../src/version.ts"],"sourcesContent":["export const ASSISTANT_CLOUD_VERSION: string =\n typeof __AUI_PACKAGE_VERSION__ === \"string\"\n ? __AUI_PACKAGE_VERSION__\n : \"0.0.0\";\n"],"mappings":";AAAA,MAAa,0BAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistant-cloud",
3
- "version": "0.1.43",
3
+ "version": "0.2.1",
4
4
  "description": "Cloud integration for assistant-ui",
5
5
  "keywords": [
6
6
  "assistant",
@@ -16,6 +16,14 @@
16
16
  ".": {
17
17
  "types": "./dist/index.d.ts",
18
18
  "default": "./dist/index.js"
19
+ },
20
+ "./telemetry": {
21
+ "types": "./dist/telemetry/index.d.ts",
22
+ "default": "./dist/telemetry/index.js"
23
+ },
24
+ "./ai-sdk": {
25
+ "types": "./dist/ai-sdk/index.d.ts",
26
+ "default": "./dist/ai-sdk/index.js"
19
27
  }
20
28
  },
21
29
  "main": "./dist/index.js",
@@ -27,12 +35,38 @@
27
35
  ],
28
36
  "sideEffects": false,
29
37
  "dependencies": {
30
- "assistant-stream": "^0.3.41"
38
+ "assistant-stream": "^0.3.43"
39
+ },
40
+ "peerDependencies": {
41
+ "@opentelemetry/api": "^1.9.0",
42
+ "@opentelemetry/exporter-trace-otlp-http": ">=0.200.0",
43
+ "@opentelemetry/sdk-trace-base": "^2.1.0",
44
+ "ai": "^6.0.0 || ^7.0.0"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "@opentelemetry/api": {
48
+ "optional": true
49
+ },
50
+ "@opentelemetry/exporter-trace-otlp-http": {
51
+ "optional": true
52
+ },
53
+ "@opentelemetry/sdk-trace-base": {
54
+ "optional": true
55
+ },
56
+ "ai": {
57
+ "optional": true
58
+ }
31
59
  },
32
60
  "devDependencies": {
33
- "@assistant-ui/x-buildutils": "0.0.25",
34
- "@types/node": "^26.4.0",
35
- "vitest": "^4.1.11"
61
+ "@assistant-ui/x-buildutils": "0.0.27",
62
+ "@opentelemetry/api": "^1.9.0",
63
+ "@opentelemetry/exporter-trace-otlp-http": "^0.205.0",
64
+ "@opentelemetry/sdk-trace-base": "^2.1.0",
65
+ "@types/node": "^26.4.1",
66
+ "ai": "^7.0.93",
67
+ "ai-v6": "npm:ai@^6.0.277",
68
+ "jsdom": "^30.0.1",
69
+ "vitest": "^5.0.0"
36
70
  },
37
71
  "publishConfig": {
38
72
  "access": "public",
@@ -49,7 +83,10 @@
49
83
  },
50
84
  "scripts": {
51
85
  "build": "aui-build",
52
- "test": "vitest run",
86
+ "test": "pnpm run test:v7 && pnpm run test:peer-v6 && pnpm run test:types:peer-v6",
87
+ "test:v7": "vitest run",
88
+ "test:peer-v6": "vitest run --config vitest.peer-v6.config.ts",
89
+ "test:types:peer-v6": "tsc --noEmit --project tsconfig.peer-v6.json",
53
90
  "test:watch": "vitest"
54
91
  }
55
92
  }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  AssistantCloudAPI,
3
3
  type AssistantCloudConfig,
4
+ type SdkIdentity,
4
5
  type AssistantCloudTelemetryConfig,
5
6
  } from "./AssistantCloudAPI";
6
7
  import { AssistantCloudAuthTokens } from "./AssistantCloudAuthTokens";
@@ -8,6 +9,8 @@ import { AssistantCloudProjects } from "./AssistantCloudProjects";
8
9
  import { AssistantCloudRuns } from "./AssistantCloudRuns";
9
10
  import { AssistantCloudThreads } from "./AssistantCloudThreads";
10
11
  import { AssistantCloudFiles } from "./AssistantCloudFiles";
12
+ import { AssistantCloudEvents } from "./AssistantCloudEvents";
13
+ import { AssistantCloudScores } from "./AssistantCloudScores";
11
14
 
12
15
  export class AssistantCloud {
13
16
  public readonly threads;
@@ -15,10 +18,25 @@ export class AssistantCloud {
15
18
  public readonly auth;
16
19
  public readonly runs;
17
20
  public readonly files;
21
+ public readonly events;
22
+ public readonly scores;
18
23
  public readonly telemetry: AssistantCloudTelemetryConfig;
24
+ public readonly registerSdk: (sdk: SdkIdentity) => void;
19
25
 
20
26
  constructor(config: AssistantCloudConfig) {
21
27
  const api = new AssistantCloudAPI(config);
28
+ this.registerSdk = api.registerSdk;
29
+ const t = config.telemetry;
30
+ this.telemetry =
31
+ t === false
32
+ ? { enabled: false }
33
+ : t === true || t === undefined
34
+ ? { enabled: true }
35
+ : {
36
+ ...t,
37
+ enabled: t.enabled !== false,
38
+ };
39
+
22
40
  this.threads = new AssistantCloudThreads(api);
23
41
  this.projects = new AssistantCloudProjects(api);
24
42
  this.auth = {
@@ -26,13 +44,10 @@ export class AssistantCloud {
26
44
  };
27
45
  this.runs = new AssistantCloudRuns(api);
28
46
  this.files = new AssistantCloudFiles(api);
29
-
30
- const t = config.telemetry;
31
- this.telemetry =
32
- t === false
33
- ? { enabled: false }
34
- : t === true || t === undefined
35
- ? { enabled: true }
36
- : { ...t, enabled: t.enabled !== false };
47
+ this.events = new AssistantCloudEvents(
48
+ api,
49
+ () => this.telemetry.enabled !== false && this.telemetry.events !== false,
50
+ );
51
+ this.scores = new AssistantCloudScores(api);
37
52
  }
38
53
  }
@@ -3,11 +3,31 @@ import {
3
3
  AssistantCloudJWTAuthStrategy,
4
4
  AssistantCloudAPIKeyAuthStrategy,
5
5
  AssistantCloudAnonymousAuthStrategy,
6
+ normalizeBaseUrl,
6
7
  } from "./AssistantCloudAuthStrategy";
7
8
  import type { AssistantCloudRunReport } from "./AssistantCloudRuns";
9
+ import { ASSISTANT_CLOUD_VERSION } from "./version";
10
+
11
+ export type SdkIdentity = {
12
+ name: string;
13
+ version: string;
14
+ };
8
15
 
9
16
  export type AssistantCloudTelemetryConfig = {
17
+ /**
18
+ * Enables Assistant Cloud telemetry. Defaults to `true`. Set to `false` to
19
+ * disable both run reports and engagement events.
20
+ */
10
21
  enabled?: boolean;
22
+ /**
23
+ * Enables Assistant Cloud engagement events. Defaults to `true` when
24
+ * telemetry is enabled. Set to `false` to keep run reports while disabling
25
+ * engagement events.
26
+ */
27
+ events?: boolean;
28
+ release?: string;
29
+ environment?: string;
30
+ tags?: string[];
11
31
  /**
12
32
  * Called before each telemetry report is sent.
13
33
  * Return a modified report to enrich it (e.g. add `model_id`),
@@ -39,7 +59,10 @@ export type AssistantCloudConfig = (
39
59
  *
40
60
  * When enabled, the SDK automatically reports run metadata (status, step
41
61
  * count, tool calls, and token usage) to Assistant Cloud after each
42
- * assistant message is saved. No message content is sent.
62
+ * assistant message is saved. Reports can also include assistant output,
63
+ * tool arguments and results, errors, and metadata, which may contain
64
+ * sensitive content. Use `beforeReport` to redact or drop reports, or
65
+ * `telemetry: false` to disable reporting.
43
66
  *
44
67
  * - `true` / `undefined` — enabled with defaults
45
68
  * - `false` — disabled
@@ -50,10 +73,19 @@ export type AssistantCloudConfig = (
50
73
 
51
74
  export class CloudAPIError extends Error {
52
75
  public readonly status: number;
53
-
54
- constructor(message: string, status: number) {
76
+ public readonly code?: string;
77
+ public readonly details?: Record<string, unknown>;
78
+
79
+ constructor(
80
+ message: string,
81
+ status: number,
82
+ code?: string,
83
+ details?: Record<string, unknown>,
84
+ ) {
55
85
  super(message);
56
86
  this.status = status;
87
+ if (code !== undefined) this.code = code;
88
+ if (details !== undefined) this.details = details;
57
89
  this.name = "CloudAPIError";
58
90
  }
59
91
  }
@@ -63,18 +95,34 @@ type MakeRequestOptions = {
63
95
  headers?: Record<string, string> | undefined;
64
96
  query?: Record<string, string | number | boolean> | undefined;
65
97
  body?: object | undefined;
98
+ keepalive?: boolean | undefined;
66
99
  };
67
100
 
68
- const normalizeBaseUrl = (baseUrl: string) => {
69
- if (!baseUrl || !baseUrl.endsWith("/")) return baseUrl;
70
- return baseUrl.slice(0, -1);
71
- };
101
+ const HEADER_TOKEN = /^[\x21-\x7e]+$/;
72
102
 
73
103
  export class AssistantCloudAPI {
74
104
  public _auth: AssistantCloudAuthStrategy;
75
105
  public _baseUrl;
106
+ public readonly registerSdk: (sdk: SdkIdentity) => void;
107
+ public readonly sdkHeader: () => string;
76
108
 
77
109
  constructor(config: AssistantCloudConfig) {
110
+ const sdks = new Map<string, SdkIdentity>();
111
+ this.registerSdk = (sdk) => {
112
+ const name = sdk.name.trim();
113
+ const version = sdk.version.trim();
114
+ if (!HEADER_TOKEN.test(name) || !HEADER_TOKEN.test(version)) return;
115
+ sdks.set(`${name}/${version}`, { name, version });
116
+ };
117
+ this.sdkHeader = () =>
118
+ [
119
+ `assistant-cloud/${ASSISTANT_CLOUD_VERSION}`,
120
+ ...Array.from(
121
+ sdks.values(),
122
+ ({ name, version }) => `${name}/${version}`,
123
+ ),
124
+ ].join(" ");
125
+
78
126
  if ("authToken" in config) {
79
127
  this._baseUrl = normalizeBaseUrl(config.baseUrl);
80
128
  this._auth = new AssistantCloudJWTAuthStrategy(config.authToken);
@@ -112,6 +160,7 @@ export class AssistantCloudAPI {
112
160
  ...authHeaders,
113
161
  ...options.headers,
114
162
  "Content-Type": "application/json",
163
+ "Aui-Sdk": this.sdkHeader(),
115
164
  };
116
165
 
117
166
  const queryParams = new URLSearchParams();
@@ -133,6 +182,7 @@ export class AssistantCloudAPI {
133
182
  method: options.method ?? "GET",
134
183
  headers,
135
184
  body: options.body ? JSON.stringify(options.body) : null,
185
+ ...(options.keepalive ? { keepalive: true } : {}),
136
186
  });
137
187
 
138
188
  this._auth.readAuthHeaders(response.headers);
@@ -140,15 +190,27 @@ export class AssistantCloudAPI {
140
190
  if (!response.ok) {
141
191
  const text = await response.text();
142
192
  let message: string | undefined;
193
+ let code: string | undefined;
194
+ let details: Record<string, unknown> | undefined;
143
195
  try {
144
- const body = JSON.parse(text);
145
- if (typeof body?.message === "string" && body.message.length > 0) {
146
- message = body.message;
196
+ const body = JSON.parse(text) as unknown;
197
+ if (typeof body === "object" && body !== null && !Array.isArray(body)) {
198
+ const record = body as Record<string, unknown>;
199
+ if (typeof record.message === "string" && record.message.length > 0) {
200
+ message = record.message;
201
+ }
202
+ if (typeof record.error === "string") {
203
+ code = record.error;
204
+ details = { ...record };
205
+ delete details.error;
206
+ }
147
207
  }
148
208
  } catch {}
149
209
  throw new CloudAPIError(
150
210
  message ?? `Request failed with status ${response.status}, ${text}`,
151
211
  response.status,
212
+ code,
213
+ details,
152
214
  );
153
215
  }
154
216