@ai-sdk/otel 1.0.0-canary.99 → 1.0.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.
package/dist/index.js CHANGED
@@ -467,6 +467,39 @@ function recordErrorOnSpan(span, error) {
467
467
  }
468
468
  }
469
469
 
470
+ // src/sanitize-attribute-value.ts
471
+ function isPrimitiveAttributeValue(value) {
472
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
473
+ }
474
+ function sanitizeAttributeValue(value) {
475
+ if (!Array.isArray(value)) {
476
+ return value;
477
+ }
478
+ const primitiveTypes = new Set(
479
+ value.filter(isPrimitiveAttributeValue).map((item) => typeof item)
480
+ );
481
+ if (primitiveTypes.size !== 1) {
482
+ return void 0;
483
+ }
484
+ const [primitiveType] = primitiveTypes;
485
+ if (primitiveType === "string") {
486
+ return value.filter((item) => typeof item === "string");
487
+ }
488
+ if (primitiveType === "number") {
489
+ return value.filter((item) => typeof item === "number");
490
+ }
491
+ return value.filter((item) => typeof item === "boolean");
492
+ }
493
+ function sanitizeAttributes(attributes) {
494
+ const result = {};
495
+ for (const [key, value] of Object.entries(attributes != null ? attributes : {})) {
496
+ if (value == null) continue;
497
+ const sanitized = sanitizeAttributeValue(value);
498
+ if (sanitized != null) result[key] = sanitized;
499
+ }
500
+ return result;
501
+ }
502
+
470
503
  // src/select-attributes.ts
471
504
  function shouldRecord(telemetry) {
472
505
  return (telemetry == null ? void 0 : telemetry.isEnabled) !== false;
@@ -481,16 +514,23 @@ function selectAttributes(telemetry, attributes) {
481
514
  if (typeof value === "object" && "input" in value && typeof value.input === "function") {
482
515
  if ((telemetry == null ? void 0 : telemetry.recordInputs) === false) continue;
483
516
  const resolved = value.input();
484
- if (resolved != null) result[key] = resolved;
517
+ if (resolved != null) {
518
+ const sanitized2 = sanitizeAttributeValue(resolved);
519
+ if (sanitized2 != null) result[key] = sanitized2;
520
+ }
485
521
  continue;
486
522
  }
487
523
  if (typeof value === "object" && "output" in value && typeof value.output === "function") {
488
524
  if ((telemetry == null ? void 0 : telemetry.recordOutputs) === false) continue;
489
525
  const resolved = value.output();
490
- if (resolved != null) result[key] = resolved;
526
+ if (resolved != null) {
527
+ const sanitized2 = sanitizeAttributeValue(resolved);
528
+ if (sanitized2 != null) result[key] = sanitized2;
529
+ }
491
530
  continue;
492
531
  }
493
- result[key] = value;
532
+ const sanitized = sanitizeAttributeValue(value);
533
+ if (sanitized != null) result[key] = sanitized;
494
534
  }
495
535
  return result;
496
536
  }
@@ -521,9 +561,23 @@ function normalizeSupplementalAttributes(options) {
521
561
  };
522
562
  }
523
563
  function getRuntimeContextAttributes(context4) {
524
- return Object.fromEntries(
525
- Object.entries(context4 != null ? context4 : {}).filter(([, value]) => value != null).map(([key, value]) => [`ai.settings.context.${key}`, value])
526
- );
564
+ const attributes = {};
565
+ for (const [key, value] of Object.entries(context4 != null ? context4 : {})) {
566
+ addRuntimeContextAttribute(attributes, `ai.settings.context.${key}`, value);
567
+ }
568
+ return attributes;
569
+ }
570
+ function addRuntimeContextAttribute(attributes, key, value) {
571
+ if (value == null) {
572
+ return;
573
+ }
574
+ if (Array.isArray(value) || typeof value !== "object") {
575
+ attributes[key] = value;
576
+ return;
577
+ }
578
+ for (const [nestedKey, nestedValue] of Object.entries(value)) {
579
+ addRuntimeContextAttribute(attributes, `${key}.${nestedKey}`, nestedValue);
580
+ }
527
581
  }
528
582
  function getHeaderAttributes(headers) {
529
583
  return Object.fromEntries(
@@ -550,6 +604,21 @@ function selectSupplementalAttributes(telemetry, enabledAttributes, attributes)
550
604
  }
551
605
 
552
606
  // src/open-telemetry.ts
607
+ function msToSeconds(durationMs) {
608
+ return durationMs == null ? void 0 : durationMs / 1e3;
609
+ }
610
+ function getGenAIClientPerformanceAttributes(performance) {
611
+ var _a;
612
+ return {
613
+ "gen_ai.client.operation.duration": msToSeconds(performance.responseTimeMs),
614
+ "gen_ai.client.operation.time_to_first_chunk": msToSeconds(
615
+ performance.timeToFirstOutputMs
616
+ ),
617
+ "gen_ai.client.operation.time_per_output_chunk": msToSeconds(
618
+ (_a = performance.timeBetweenOutputChunksMs) == null ? void 0 : _a.avg
619
+ )
620
+ };
621
+ }
553
622
  var OpenTelemetry = class {
554
623
  constructor(options = {}) {
555
624
  this.callStates = /* @__PURE__ */ new Map();
@@ -584,7 +653,7 @@ var OpenTelemetry = class {
584
653
  customAttributes = void 0;
585
654
  }
586
655
  return {
587
- ...customAttributes,
656
+ ...sanitizeAttributes(customAttributes),
588
657
  ...attributes
589
658
  };
590
659
  }
@@ -600,6 +669,22 @@ var OpenTelemetry = class {
600
669
  }
601
670
  return context2.with(toolSpanEntry.context, execute);
602
671
  }
672
+ /**
673
+ * Runs the provider `doGenerate`/`doStream` call with the active model-call
674
+ * context.
675
+ */
676
+ executeLanguageModelCall({
677
+ callId,
678
+ execute
679
+ }) {
680
+ var _a;
681
+ const state = this.getCallState(callId);
682
+ const modelCallContext = (_a = state == null ? void 0 : state.inferenceContext) != null ? _a : state == null ? void 0 : state.stepContext;
683
+ if (modelCallContext == null) {
684
+ return execute();
685
+ }
686
+ return context2.with(modelCallContext, execute);
687
+ }
603
688
  onStart(event) {
604
689
  if (event.operationId === "ai.embed" || event.operationId === "ai.embedMany") {
605
690
  this.onEmbedOperationStart(event);
@@ -840,7 +925,7 @@ var OpenTelemetry = class {
840
925
  );
841
926
  }
842
927
  /** @deprecated */
843
- onObjectStepFinish(event) {
928
+ onObjectStepEnd(event) {
844
929
  var _a;
845
930
  const state = this.getCallState(event.callId);
846
931
  if (!(state == null ? void 0 : state.inferenceSpan)) return;
@@ -1035,6 +1120,7 @@ var OpenTelemetry = class {
1035
1120
  const { telemetry } = state;
1036
1121
  state.inferenceSpan.setAttributes(
1037
1122
  selectAttributes(telemetry, {
1123
+ ...getGenAIClientPerformanceAttributes(event.performance),
1038
1124
  "gen_ai.response.finish_reasons": [event.finishReason],
1039
1125
  "gen_ai.response.id": event.responseId,
1040
1126
  "gen_ai.usage.input_tokens": event.usage.inputTokens,
@@ -1108,6 +1194,11 @@ var OpenTelemetry = class {
1108
1194
  const { span } = toolSpanEntry;
1109
1195
  const { telemetry } = state;
1110
1196
  const { toolOutput } = event;
1197
+ span.setAttributes(
1198
+ selectAttributes(telemetry, {
1199
+ "gen_ai.execute_tool.duration": msToSeconds(event.toolExecutionMs)
1200
+ })
1201
+ );
1111
1202
  if (toolOutput.type === "tool-result") {
1112
1203
  try {
1113
1204
  span.setAttributes(
@@ -1125,7 +1216,7 @@ var OpenTelemetry = class {
1125
1216
  span.end();
1126
1217
  state.toolSpans.delete(event.toolCall.toolCallId);
1127
1218
  }
1128
- onStepFinish(event) {
1219
+ onStepEnd(event) {
1129
1220
  const state = this.getCallState(event.callId);
1130
1221
  if (!(state == null ? void 0 : state.stepSpan)) return;
1131
1222
  const { telemetry } = state;
@@ -1141,6 +1232,10 @@ var OpenTelemetry = class {
1141
1232
  state.stepSpan = void 0;
1142
1233
  state.stepContext = void 0;
1143
1234
  }
1235
+ /** @deprecated Use `onStepEnd` instead. */
1236
+ onStepFinish(event) {
1237
+ this.onStepEnd(event);
1238
+ }
1144
1239
  onEnd(event) {
1145
1240
  const state = this.getCallState(event.callId);
1146
1241
  if (!(state == null ? void 0 : state.rootSpan)) return;
@@ -1166,17 +1261,17 @@ var OpenTelemetry = class {
1166
1261
  state.rootSpan.setAttributes(
1167
1262
  selectAttributes(telemetry, {
1168
1263
  "gen_ai.response.finish_reasons": [event.finishReason],
1169
- "gen_ai.usage.input_tokens": event.totalUsage.inputTokens,
1170
- "gen_ai.usage.output_tokens": event.totalUsage.outputTokens,
1171
- "gen_ai.usage.cache_read.input_tokens": (_a = event.totalUsage.inputTokenDetails) == null ? void 0 : _a.cacheReadTokens,
1172
- "gen_ai.usage.cache_creation.input_tokens": (_b = event.totalUsage.inputTokenDetails) == null ? void 0 : _b.cacheWriteTokens,
1264
+ "gen_ai.usage.input_tokens": event.usage.inputTokens,
1265
+ "gen_ai.usage.output_tokens": event.usage.outputTokens,
1266
+ "gen_ai.usage.cache_read.input_tokens": (_a = event.usage.inputTokenDetails) == null ? void 0 : _a.cacheReadTokens,
1267
+ "gen_ai.usage.cache_creation.input_tokens": (_b = event.usage.inputTokenDetails) == null ? void 0 : _b.cacheWriteTokens,
1173
1268
  "gen_ai.output.messages": {
1174
1269
  output: () => {
1175
1270
  var _a2;
1176
1271
  return JSON.stringify(
1177
1272
  formatOutputMessages({
1178
1273
  text: (_a2 = event.text) != null ? _a2 : void 0,
1179
- reasoning: event.reasoning,
1274
+ reasoning: event.finalStep.reasoning,
1180
1275
  toolCalls: event.toolCalls,
1181
1276
  files: event.files,
1182
1277
  finishReason: event.finishReason
@@ -1189,9 +1284,9 @@ var OpenTelemetry = class {
1189
1284
  this.supplementalAttributes,
1190
1285
  {
1191
1286
  providerMetadata: {
1192
- "ai.response.providerMetadata": event.providerMetadata ? JSON.stringify(event.providerMetadata) : void 0
1287
+ "ai.response.providerMetadata": event.finalStep.providerMetadata ? JSON.stringify(event.finalStep.providerMetadata) : void 0
1193
1288
  },
1194
- usage: getDetailedUsageAttributes(event.totalUsage)
1289
+ usage: getDetailedUsageAttributes(event.usage)
1195
1290
  }
1196
1291
  )
1197
1292
  })
@@ -1446,6 +1541,34 @@ var OpenTelemetry = class {
1446
1541
  span.end();
1447
1542
  state.rerankSpan = void 0;
1448
1543
  }
1544
+ onAbort(event) {
1545
+ const state = this.getCallState(event.callId);
1546
+ if (!(state == null ? void 0 : state.rootSpan)) return;
1547
+ for (const { span: toolSpan } of state.toolSpans.values()) {
1548
+ toolSpan.end();
1549
+ }
1550
+ state.toolSpans.clear();
1551
+ if (state.inferenceSpan) {
1552
+ state.inferenceSpan.end();
1553
+ state.inferenceSpan = void 0;
1554
+ state.inferenceContext = void 0;
1555
+ }
1556
+ if (state.stepSpan) {
1557
+ state.stepSpan.end();
1558
+ state.stepSpan = void 0;
1559
+ state.stepContext = void 0;
1560
+ }
1561
+ for (const { span: embedSpan } of state.embedSpans.values()) {
1562
+ embedSpan.end();
1563
+ }
1564
+ state.embedSpans.clear();
1565
+ if (state.rerankSpan) {
1566
+ state.rerankSpan.span.end();
1567
+ state.rerankSpan = void 0;
1568
+ }
1569
+ state.rootSpan.end();
1570
+ this.cleanupCallState(event.callId);
1571
+ }
1449
1572
  onError(error) {
1450
1573
  var _a;
1451
1574
  const event = error;
@@ -1524,12 +1647,7 @@ function getBaseTelemetryAttributes({
1524
1647
  return attributes;
1525
1648
  }, {}),
1526
1649
  // add context as attributes:
1527
- ...Object.entries(context4 != null ? context4 : {}).reduce((attributes, [key, value]) => {
1528
- if (value != void 0) {
1529
- attributes[`ai.settings.context.${key}`] = value;
1530
- }
1531
- return attributes;
1532
- }, {}),
1650
+ ...getRuntimeContextAttributes(context4),
1533
1651
  // request headers
1534
1652
  ...Object.entries(headers != null ? headers : {}).reduce((attributes, [key, value]) => {
1535
1653
  if (value !== void 0) {
@@ -1597,16 +1715,23 @@ function selectAttributes2(telemetry, attributes) {
1597
1715
  if (typeof value === "object" && "input" in value && typeof value.input === "function") {
1598
1716
  if ((telemetry == null ? void 0 : telemetry.recordInputs) === false) continue;
1599
1717
  const resolved = value.input();
1600
- if (resolved != null) result[key] = resolved;
1718
+ if (resolved != null) {
1719
+ const sanitized2 = sanitizeAttributeValue(resolved);
1720
+ if (sanitized2 != null) result[key] = sanitized2;
1721
+ }
1601
1722
  continue;
1602
1723
  }
1603
1724
  if (typeof value === "object" && "output" in value && typeof value.output === "function") {
1604
1725
  if ((telemetry == null ? void 0 : telemetry.recordOutputs) === false) continue;
1605
1726
  const resolved = value.output();
1606
- if (resolved != null) result[key] = resolved;
1727
+ if (resolved != null) {
1728
+ const sanitized2 = sanitizeAttributeValue(resolved);
1729
+ if (sanitized2 != null) result[key] = sanitized2;
1730
+ }
1607
1731
  continue;
1608
1732
  }
1609
- result[key] = value;
1733
+ const sanitized = sanitizeAttributeValue(value);
1734
+ if (sanitized != null) result[key] = sanitized;
1610
1735
  }
1611
1736
  return result;
1612
1737
  }
@@ -1634,6 +1759,21 @@ var LegacyOpenTelemetry = class {
1634
1759
  }
1635
1760
  return context3.with(toolSpanEntry.context, execute);
1636
1761
  }
1762
+ /**
1763
+ * Runs the provider `doGenerate`/`doStream` call with the active legacy
1764
+ * model-call context.
1765
+ */
1766
+ executeLanguageModelCall({
1767
+ callId,
1768
+ execute
1769
+ }) {
1770
+ var _a;
1771
+ const stepContext = (_a = this.getCallState(callId)) == null ? void 0 : _a.stepContext;
1772
+ if (stepContext == null) {
1773
+ return execute();
1774
+ }
1775
+ return context3.with(stepContext, execute);
1776
+ }
1637
1777
  onStart(event) {
1638
1778
  if (event.operationId === "ai.embed" || event.operationId === "ai.embedMany") {
1639
1779
  this.onEmbedOperationStart(event);
@@ -1796,7 +1936,7 @@ var LegacyOpenTelemetry = class {
1796
1936
  state.stepContext = trace2.setSpan(state.rootContext, state.stepSpan);
1797
1937
  }
1798
1938
  /** @deprecated */
1799
- onObjectStepFinish(event) {
1939
+ onObjectStepEnd(event) {
1800
1940
  var _a, _b;
1801
1941
  const state = this.getCallState(event.callId);
1802
1942
  if (!(state == null ? void 0 : state.stepSpan)) return;
@@ -1985,7 +2125,7 @@ var LegacyOpenTelemetry = class {
1985
2125
  span.end();
1986
2126
  state.toolSpans.delete(event.toolCall.toolCallId);
1987
2127
  }
1988
- onStepFinish(event) {
2128
+ onStepEnd(event) {
1989
2129
  var _a, _b, _c, _d, _e, _f, _g;
1990
2130
  const state = this.getCallState(event.callId);
1991
2131
  if (!(state == null ? void 0 : state.stepSpan)) return;
@@ -2025,7 +2165,7 @@ var LegacyOpenTelemetry = class {
2025
2165
  "ai.response.model": event.response.modelId,
2026
2166
  "ai.response.timestamp": event.response.timestamp.toISOString(),
2027
2167
  "ai.response.providerMetadata": event.providerMetadata ? JSON.stringify(event.providerMetadata) : void 0,
2028
- "ai.response.msToFirstChunk": isStreamText ? event.performance.timeToFirstOutputTokenMs : void 0,
2168
+ "ai.response.msToFirstChunk": isStreamText ? event.performance.timeToFirstOutputMs : void 0,
2029
2169
  "ai.response.msToFinish": isStreamText ? event.performance.responseTimeMs : void 0,
2030
2170
  "ai.response.avgOutputTokensPerSecond": isStreamText ? event.performance.effectiveOutputTokensPerSecond : void 0,
2031
2171
  "ai.usage.inputTokens": event.usage.inputTokens,
@@ -2045,9 +2185,9 @@ var LegacyOpenTelemetry = class {
2045
2185
  "gen_ai.usage.output_tokens": event.usage.outputTokens
2046
2186
  })
2047
2187
  );
2048
- if (isStreamText && event.performance.timeToFirstOutputTokenMs != null) {
2188
+ if (isStreamText && event.performance.timeToFirstOutputMs != null) {
2049
2189
  state.stepSpan.addEvent("ai.stream.firstChunk", {
2050
- "ai.response.msToFirstChunk": event.performance.timeToFirstOutputTokenMs
2190
+ "ai.response.msToFirstChunk": event.performance.timeToFirstOutputMs
2051
2191
  });
2052
2192
  }
2053
2193
  if (isStreamText) {
@@ -2060,6 +2200,10 @@ var LegacyOpenTelemetry = class {
2060
2200
  state.stepSpan = void 0;
2061
2201
  state.stepContext = void 0;
2062
2202
  }
2203
+ /** @deprecated Use `onStepEnd` instead. */
2204
+ onStepFinish(event) {
2205
+ this.onStepEnd(event);
2206
+ }
2063
2207
  onEnd(event) {
2064
2208
  const state = this.getCallState(event.callId);
2065
2209
  if (!(state == null ? void 0 : state.rootSpan)) return;
@@ -2092,7 +2236,7 @@ var LegacyOpenTelemetry = class {
2092
2236
  }
2093
2237
  },
2094
2238
  "ai.response.reasoning": {
2095
- output: () => event.reasoning.length > 0 ? event.reasoning.filter((part) => "text" in part).map((part) => part.text).join("\n") : void 0
2239
+ output: () => event.finalStep.reasoning.length > 0 ? event.finalStep.reasoning.filter((part) => "text" in part).map((part) => part.text).join("\n") : void 0
2096
2240
  },
2097
2241
  "ai.response.toolCalls": {
2098
2242
  output: () => event.toolCalls.length > 0 ? JSON.stringify(
@@ -2112,17 +2256,17 @@ var LegacyOpenTelemetry = class {
2112
2256
  }))
2113
2257
  ) : void 0
2114
2258
  },
2115
- "ai.response.providerMetadata": event.providerMetadata ? JSON.stringify(event.providerMetadata) : void 0,
2116
- "ai.usage.inputTokens": event.totalUsage.inputTokens,
2117
- "ai.usage.outputTokens": event.totalUsage.outputTokens,
2118
- "ai.usage.totalTokens": event.totalUsage.totalTokens,
2119
- "ai.usage.reasoningTokens": (_a = event.totalUsage.outputTokenDetails) == null ? void 0 : _a.reasoningTokens,
2120
- "ai.usage.cachedInputTokens": (_b = event.totalUsage.inputTokenDetails) == null ? void 0 : _b.cacheReadTokens,
2121
- "ai.usage.inputTokenDetails.noCacheTokens": (_c = event.totalUsage.inputTokenDetails) == null ? void 0 : _c.noCacheTokens,
2122
- "ai.usage.inputTokenDetails.cacheReadTokens": (_d = event.totalUsage.inputTokenDetails) == null ? void 0 : _d.cacheReadTokens,
2123
- "ai.usage.inputTokenDetails.cacheWriteTokens": (_e = event.totalUsage.inputTokenDetails) == null ? void 0 : _e.cacheWriteTokens,
2124
- "ai.usage.outputTokenDetails.textTokens": (_f = event.totalUsage.outputTokenDetails) == null ? void 0 : _f.textTokens,
2125
- "ai.usage.outputTokenDetails.reasoningTokens": (_g = event.totalUsage.outputTokenDetails) == null ? void 0 : _g.reasoningTokens
2259
+ "ai.response.providerMetadata": event.finalStep.providerMetadata ? JSON.stringify(event.finalStep.providerMetadata) : void 0,
2260
+ "ai.usage.inputTokens": event.usage.inputTokens,
2261
+ "ai.usage.outputTokens": event.usage.outputTokens,
2262
+ "ai.usage.totalTokens": event.usage.totalTokens,
2263
+ "ai.usage.reasoningTokens": (_a = event.usage.outputTokenDetails) == null ? void 0 : _a.reasoningTokens,
2264
+ "ai.usage.cachedInputTokens": (_b = event.usage.inputTokenDetails) == null ? void 0 : _b.cacheReadTokens,
2265
+ "ai.usage.inputTokenDetails.noCacheTokens": (_c = event.usage.inputTokenDetails) == null ? void 0 : _c.noCacheTokens,
2266
+ "ai.usage.inputTokenDetails.cacheReadTokens": (_d = event.usage.inputTokenDetails) == null ? void 0 : _d.cacheReadTokens,
2267
+ "ai.usage.inputTokenDetails.cacheWriteTokens": (_e = event.usage.inputTokenDetails) == null ? void 0 : _e.cacheWriteTokens,
2268
+ "ai.usage.outputTokenDetails.textTokens": (_f = event.usage.outputTokenDetails) == null ? void 0 : _f.textTokens,
2269
+ "ai.usage.outputTokenDetails.reasoningTokens": (_g = event.usage.outputTokenDetails) == null ? void 0 : _g.reasoningTokens
2126
2270
  })
2127
2271
  );
2128
2272
  state.rootSpan.end();
@@ -2300,6 +2444,29 @@ var LegacyOpenTelemetry = class {
2300
2444
  span.end();
2301
2445
  state.rerankSpan = void 0;
2302
2446
  }
2447
+ onAbort(event) {
2448
+ const state = this.getCallState(event.callId);
2449
+ if (!(state == null ? void 0 : state.rootSpan)) return;
2450
+ for (const { span: toolSpan } of state.toolSpans.values()) {
2451
+ toolSpan.end();
2452
+ }
2453
+ state.toolSpans.clear();
2454
+ if (state.stepSpan) {
2455
+ state.stepSpan.end();
2456
+ state.stepSpan = void 0;
2457
+ state.stepContext = void 0;
2458
+ }
2459
+ for (const { span: embedSpan } of state.embedSpans.values()) {
2460
+ embedSpan.end();
2461
+ }
2462
+ state.embedSpans.clear();
2463
+ if (state.rerankSpan) {
2464
+ state.rerankSpan.span.end();
2465
+ state.rerankSpan = void 0;
2466
+ }
2467
+ state.rootSpan.end();
2468
+ this.cleanupCallState(event.callId);
2469
+ }
2303
2470
  onError(error) {
2304
2471
  var _a;
2305
2472
  const event = error;