@mastra/datadog 1.4.15 → 1.4.16-alpha.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/bridge.d.ts CHANGED
@@ -70,6 +70,15 @@ export interface DatadogBridgeConfig extends BaseExporterConfig {
70
70
  * LLM Observability tags instead of being nested in annotations.metadata.
71
71
  */
72
72
  requestContextKeys?: string[];
73
+ /**
74
+ * How long `flush()` waits, in milliseconds, after handing data to dd-trace.
75
+ * dd-trace sends LLM Observability spans and evaluations from a background
76
+ * writer and offers no way to await that request, so the wait keeps a process
77
+ * that exits right after `flush()` from cutting the send off. The wait only
78
+ * happens when something was sent since the last flush. Set to 0 to disable.
79
+ * Defaults to 2000.
80
+ */
81
+ flushGraceMs?: number;
73
82
  }
74
83
  /**
75
84
  * Datadog Bridge for Mastra Observability.
@@ -82,8 +91,15 @@ export declare class DatadogBridge extends BaseExporter implements Observability
82
91
  name: string;
83
92
  private config;
84
93
  private ddSpanMap;
94
+ /** Keys of the links each dd span was started with, so links set later are added once at finish. */
95
+ private startLinkKeys;
85
96
  private traceContext;
86
97
  private openSpanCounts;
98
+ private flushGraceMs;
99
+ /** True when spans or evaluations were handed to dd-trace since the last flush() */
100
+ private pendingSend;
101
+ /** Grace wait in progress, shared by flush() calls that overlap it */
102
+ private graceWait;
87
103
  constructor(config?: DatadogBridgeConfig);
88
104
  /**
89
105
  * Create a dd-trace span eagerly when a Mastra span is constructed.
@@ -124,6 +140,14 @@ export declare class DatadogBridge extends BaseExporter implements Observability
124
140
  private buildAnnotations;
125
141
  private setErrorTags;
126
142
  flush(): Promise<void>;
143
+ /**
144
+ * `llmobs.flush()` returns before the background writer's HTTP request
145
+ * completes and dd-trace gives nothing to await, so wait a bounded time
146
+ * instead. Starts one wait per batch of new data, so the repeated flushes
147
+ * during shutdown don't stack. A flush that overlaps a running wait joins
148
+ * it, so no caller resolves before the data it produced had its wait.
149
+ */
150
+ private waitForBackgroundSend;
127
151
  shutdown(): Promise<void>;
128
152
  }
129
153
  //# sourceMappingURL=bridge.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EACV,YAAY,EAKZ,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,QAAQ,EACR,OAAO,EACR,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAAE,YAAY,EAAuB,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AA+DhE;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC7D;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,qBAAa,aAAc,SAAQ,YAAa,YAAW,mBAAmB;IAC5E,IAAI,SAAoB;IAExB,OAAO,CAAC,MAAM,CAA8E;IAC5F,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,YAAY,CAAmC;IACvD,OAAO,CAAC,cAAc,CAA6B;IAEnD,YAAY,MAAM,GAAE,mBAAwB,EA8C3C;IAED;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAAG,OAAO,GAAG,SAAS,CAyEpE;IAED;;OAEG;IACH,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEpE;IAED;;OAEG;IACH,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEtD;IAEK,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCnD;IAED,OAAO,CAAC,sBAAsB;IAmB9B;;OAEG;IACH,UAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BtE;IAED,OAAO,CAAC,kBAAkB;IA0C1B,OAAO,CAAC,eAAe;IASvB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAwD7B;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAIjD;IAED,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,gBAAgB;IAiGxB,OAAO,CAAC,YAAY;IASd,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAkB3B;IAEK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CA0B9B;CACF"}
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EACV,YAAY,EAKZ,mBAAmB,EACnB,iBAAiB,EACjB,UAAU,EACV,QAAQ,EACR,OAAO,EACR,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAAE,YAAY,EAAuB,MAAM,uBAAuB,CAAC;AAC1E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AA+DhE;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC7D;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAE9B;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,qBAAa,aAAc,SAAQ,YAAa,YAAW,mBAAmB;IAC5E,IAAI,SAAoB;IAExB,OAAO,CAAC,MAAM,CAA8E;IAC5F,OAAO,CAAC,SAAS,CAA0B;IAC3C,oGAAoG;IACpG,OAAO,CAAC,aAAa,CAAsC;IAC3D,OAAO,CAAC,YAAY,CAAmC;IACvD,OAAO,CAAC,cAAc,CAA6B;IACnD,OAAO,CAAC,YAAY,CAAS;IAC7B,oFAAoF;IACpF,OAAO,CAAC,WAAW,CAAS;IAC5B,sEAAsE;IACtE,OAAO,CAAC,SAAS,CAA8B;IAE/C,YAAY,MAAM,GAAE,mBAAwB,EA+C3C;IAED;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAAG,OAAO,GAAG,SAAS,CA8EpE;IAED;;OAEG;IACH,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAEpE;IAED;;OAEG;IACH,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAEtD;IAEK,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCnD;IAED,OAAO,CAAC,sBAAsB;IAmB9B;;OAEG;IACH,UAAgB,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BtE;IAED,OAAO,CAAC,kBAAkB;IA0C1B,OAAO,CAAC,eAAe;IASvB;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAqE7B;;;;;;OAMG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAIjD;IAED,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,mBAAmB;IAW3B,OAAO,CAAC,gBAAgB;IAiGxB,OAAO,CAAC,YAAY;IASd,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAoB3B;IAED;;;;;;OAMG;YACW,qBAAqB;IAY7B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CA2B9B;CACF"}
package/dist/index.cjs CHANGED
@@ -83,6 +83,20 @@ function formatUsageMetrics(usage) {
83
83
  * Utility functions for Datadog LLM Observability Exporter
84
84
  */
85
85
  /**
86
+ * Datadog span links for Mastra span links, such as the caller of a served MCP
87
+ * request. Each link is resolved from a W3C `traceparent`, so only full-length
88
+ * W3C IDs are kept, and links dd-trace can't resolve are skipped.
89
+ */
90
+ function toDatadogLinks(links) {
91
+ const ddLinks = [];
92
+ for (const link of links ?? []) {
93
+ if (!/^[0-9a-f]{32}$/.test(link.traceId) || !/^[0-9a-f]{16}$/.test(link.spanId)) continue;
94
+ const context = dd_trace.default.extract("text_map", { traceparent: `00-${link.traceId}-${link.spanId}-01` });
95
+ if (context) ddLinks.push({ context });
96
+ }
97
+ return ddLinks;
98
+ }
99
+ /**
86
100
  * Maps Mastra SpanTypes to Datadog LLMObs span kinds for the legacy hierarchy
87
101
  * (no `model-inference-span` feature). MODEL_STEP is the actual API call here
88
102
  * because MODEL_INFERENCE doesn't exist.
@@ -333,10 +347,18 @@ var DatadogBridge = class extends _mastra_observability.BaseExporter {
333
347
  name = "datadog-bridge";
334
348
  config;
335
349
  ddSpanMap = /* @__PURE__ */ new Map();
350
+ /** Keys of the links each dd span was started with, so links set later are added once at finish. */
351
+ startLinkKeys = /* @__PURE__ */ new WeakMap();
336
352
  traceContext = /* @__PURE__ */ new Map();
337
353
  openSpanCounts = /* @__PURE__ */ new Map();
354
+ flushGraceMs;
355
+ /** True when spans or evaluations were handed to dd-trace since the last flush() */
356
+ pendingSend = false;
357
+ /** Grace wait in progress, shared by flush() calls that overlap it */
358
+ graceWait = null;
338
359
  constructor(config = {}) {
339
360
  super(config);
361
+ this.flushGraceMs = config.flushGraceMs ?? 2e3;
340
362
  const mlApp = config.mlApp ?? process.env.DD_LLMOBS_ML_APP;
341
363
  const apiKey = config.apiKey ?? process.env.DD_API_KEY;
342
364
  const site = config.site ?? process.env.DD_SITE ?? "datadoghq.com";
@@ -396,10 +418,13 @@ var DatadogBridge = class extends _mastra_observability.BaseExporter {
396
418
  if (apmParentDdSpan) parentSource = "active-scope";
397
419
  }
398
420
  if (!llmobsParentDdSpan && externalParentId) llmobsParentDdSpan = apmParentDdSpan;
421
+ const links = toDatadogLinks(options.links);
399
422
  const ddSpan = dd_trace.default.startSpan(options.name, {
400
423
  ...apmParentDdSpan ? { childOf: apmParentDdSpan } : {},
401
- ...options.startTime ? { startTime: toDate(options.startTime).getTime() } : {}
424
+ ...options.startTime ? { startTime: toDate(options.startTime).getTime() } : {},
425
+ ...links.length > 0 ? { links } : {}
402
426
  });
427
+ if (options.links?.length) this.startLinkKeys.set(ddSpan, new Set(options.links.map((link) => `${link.traceId}:${link.spanId}`)));
403
428
  const ddContext = ddSpan.context?.();
404
429
  const spanId = ddContext?.toSpanId?.(true) ?? generateSpanId();
405
430
  const traceId = ddContext?.toTraceId?.(true) ?? (externalParentId ? options.parent?.traceId ?? generateTraceId() : generateTraceId());
@@ -440,6 +465,7 @@ var DatadogBridge = class extends _mastra_observability.BaseExporter {
440
465
  return;
441
466
  }
442
467
  try {
468
+ this.pendingSend = true;
443
469
  dd_trace.default.llmobs.submitEvaluation({
444
470
  traceId: score.traceId,
445
471
  spanId: toDatadogSpanId(score.spanId)
@@ -562,7 +588,20 @@ var DatadogBridge = class extends _mastra_observability.BaseExporter {
562
588
  });
563
589
  }
564
590
  try {
565
- if (typeof ddSpan.finish === "function") ddSpan.finish(endTime.getTime());
591
+ const startKeys = this.startLinkKeys.get(ddSpan);
592
+ const lateLinks = toDatadogLinks(span.links?.filter((link) => !startKeys?.has(`${link.traceId}:${link.spanId}`)));
593
+ if (lateLinks.length > 0 && typeof ddSpan.addLinks === "function") ddSpan.addLinks(lateLinks);
594
+ } catch (error) {
595
+ this.logger.error("[DatadogBridge] Failed to add span links", {
596
+ error,
597
+ spanId: span.id
598
+ });
599
+ }
600
+ try {
601
+ if (typeof ddSpan.finish === "function") {
602
+ this.pendingSend = true;
603
+ ddSpan.finish(endTime.getTime());
604
+ }
566
605
  } catch (error) {
567
606
  this.logger.error("[DatadogBridge] Failed to finish dd span", {
568
607
  error,
@@ -667,6 +706,7 @@ var DatadogBridge = class extends _mastra_observability.BaseExporter {
667
706
  } catch (e) {
668
707
  this.logger.error("Error flushing llmobs", { error: e });
669
708
  }
709
+ await this.waitForBackgroundSend();
670
710
  try {
671
711
  await flushApmExporter();
672
712
  this.logger.debug("Datadog APM exporter flushed");
@@ -674,11 +714,32 @@ var DatadogBridge = class extends _mastra_observability.BaseExporter {
674
714
  this.logger.error("Error flushing Datadog APM exporter", { error: e });
675
715
  }
676
716
  }
717
+ /**
718
+ * `llmobs.flush()` returns before the background writer's HTTP request
719
+ * completes and dd-trace gives nothing to await, so wait a bounded time
720
+ * instead. Starts one wait per batch of new data, so the repeated flushes
721
+ * during shutdown don't stack. A flush that overlaps a running wait joins
722
+ * it, so no caller resolves before the data it produced had its wait.
723
+ */
724
+ async waitForBackgroundSend() {
725
+ if (!(this.flushGraceMs > 0) || !Number.isFinite(this.flushGraceMs)) return;
726
+ if (this.pendingSend) {
727
+ this.pendingSend = false;
728
+ const wait = new Promise((resolve) => setTimeout(resolve, this.flushGraceMs)).finally(() => {
729
+ if (this.graceWait === wait) this.graceWait = null;
730
+ });
731
+ this.graceWait = wait;
732
+ }
733
+ await this.graceWait;
734
+ }
677
735
  async shutdown() {
678
736
  for (const [spanId, ddSpan] of this.ddSpanMap) {
679
737
  this.logger.warn(`[DatadogBridge] Force-finishing APM span that was not properly closed [id=${spanId}]`);
680
738
  try {
681
- if (typeof ddSpan.finish === "function") ddSpan.finish();
739
+ if (typeof ddSpan.finish === "function") {
740
+ this.pendingSend = true;
741
+ ddSpan.finish();
742
+ }
682
743
  } catch {}
683
744
  }
684
745
  this.ddSpanMap.clear();
@@ -743,8 +804,14 @@ var DatadogExporter = class extends _mastra_observability.BaseExporter {
743
804
  config;
744
805
  traceContext = /* @__PURE__ */ new Map();
745
806
  traceState = /* @__PURE__ */ new Map();
807
+ flushGraceMs;
808
+ /** True when spans or evaluations were handed to dd-trace since the last flush() */
809
+ pendingSend = false;
810
+ /** Grace wait in progress, shared by flush() calls that overlap it */
811
+ graceWait = null;
746
812
  constructor(config = {}) {
747
813
  super(config);
814
+ this.flushGraceMs = config.flushGraceMs ?? 2e3;
748
815
  const mlApp = config.mlApp ?? process.env.DD_LLMOBS_ML_APP;
749
816
  const apiKey = config.apiKey ?? process.env.DD_API_KEY;
750
817
  const site = config.site ?? process.env.DD_SITE ?? "datadoghq.com";
@@ -922,6 +989,7 @@ var DatadogExporter = class extends _mastra_observability.BaseExporter {
922
989
  return;
923
990
  }
924
991
  try {
992
+ this.pendingSend = true;
925
993
  dd_trace.default.llmobs.submitEvaluation({
926
994
  traceId: exported.traceId,
927
995
  spanId: exported.spanId
@@ -956,6 +1024,25 @@ var DatadogExporter = class extends _mastra_observability.BaseExporter {
956
1024
  } catch (e) {
957
1025
  this.logger.error("Error flushing llmobs", { error: e });
958
1026
  }
1027
+ await this.waitForBackgroundSend();
1028
+ }
1029
+ /**
1030
+ * `llmobs.flush()` returns before the background writer's HTTP request
1031
+ * completes and dd-trace gives nothing to await, so wait a bounded time
1032
+ * instead. Starts one wait per batch of new data, so the repeated flushes
1033
+ * during shutdown don't stack. A flush that overlaps a running wait joins
1034
+ * it, so no caller resolves before the data it produced had its wait.
1035
+ */
1036
+ async waitForBackgroundSend() {
1037
+ if (!(this.flushGraceMs > 0) || !Number.isFinite(this.flushGraceMs)) return;
1038
+ if (this.pendingSend) {
1039
+ this.pendingSend = false;
1040
+ const wait = new Promise((resolve) => setTimeout(resolve, this.flushGraceMs)).finally(() => {
1041
+ if (this.graceWait === wait) this.graceWait = null;
1042
+ });
1043
+ this.graceWait = wait;
1044
+ }
1045
+ await this.graceWait;
959
1046
  }
960
1047
  /**
961
1048
  * Gracefully shuts down the exporter.
@@ -1132,6 +1219,7 @@ var DatadogExporter = class extends _mastra_observability.BaseExporter {
1132
1219
  provider: ownAttrs?.provider ?? inheritedModelAttrs?.provider
1133
1220
  };
1134
1221
  const startTime = toDate(span.startTime);
1222
+ const links = toDatadogLinks(span.links);
1135
1223
  const endTime = span.endTime ? toDate(span.endTime) : span.isEvent ? startTime : /* @__PURE__ */ new Date();
1136
1224
  return {
1137
1225
  traceOptions: {
@@ -1141,7 +1229,8 @@ var DatadogExporter = class extends _mastra_observability.BaseExporter {
1141
1229
  userId: traceCtx.userId,
1142
1230
  startTime,
1143
1231
  ...kind === "llm" && attrs?.model ? { modelName: attrs.model } : {},
1144
- ...kind === "llm" && attrs?.provider ? { modelProvider: attrs.provider } : {}
1232
+ ...kind === "llm" && attrs?.provider ? { modelProvider: attrs.provider } : {},
1233
+ ...links.length > 0 ? { links } : {}
1145
1234
  },
1146
1235
  endTimeMs: endTime.getTime()
1147
1236
  };
@@ -1158,6 +1247,7 @@ var DatadogExporter = class extends _mastra_observability.BaseExporter {
1158
1247
  model: span.attributes?.model,
1159
1248
  provider: span.attributes?.provider
1160
1249
  } : inheritedModelAttrs;
1250
+ this.pendingSend = true;
1161
1251
  dd_trace.default.llmobs.trace(traceOptions, (ddSpan) => {
1162
1252
  const annotations = this.buildAnnotations(span);
1163
1253
  if (Object.keys(annotations).length > 0) dd_trace.default.llmobs.annotate(ddSpan, annotations);