@mastra/observability 0.0.0-remove-unused-model-providers-api-20251030210744 → 0.0.0-vnext-20251104230439

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 (45) hide show
  1. package/CHANGELOG.md +9 -3
  2. package/README.md +101 -0
  3. package/dist/default-entrypoint.d.ts +16 -0
  4. package/dist/default-entrypoint.d.ts.map +1 -0
  5. package/dist/exporters/base.d.ts +111 -0
  6. package/dist/exporters/base.d.ts.map +1 -0
  7. package/dist/exporters/cloud.d.ts +30 -0
  8. package/dist/exporters/cloud.d.ts.map +1 -0
  9. package/dist/exporters/console.d.ts +10 -0
  10. package/dist/exporters/console.d.ts.map +1 -0
  11. package/dist/exporters/default.d.ts +93 -0
  12. package/dist/exporters/default.d.ts.map +1 -0
  13. package/dist/exporters/index.d.ts +9 -0
  14. package/dist/exporters/index.d.ts.map +1 -0
  15. package/dist/index.cjs +2052 -0
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.ts +7 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2029 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/init.d.ts +2 -0
  22. package/dist/init.d.ts.map +1 -0
  23. package/dist/model-tracing.d.ts +48 -0
  24. package/dist/model-tracing.d.ts.map +1 -0
  25. package/dist/registry.d.ts +51 -0
  26. package/dist/registry.d.ts.map +1 -0
  27. package/dist/span_processors/index.d.ts +5 -0
  28. package/dist/span_processors/index.d.ts.map +1 -0
  29. package/dist/span_processors/sensitive-data-filter.d.ts +85 -0
  30. package/dist/span_processors/sensitive-data-filter.d.ts.map +1 -0
  31. package/dist/spans/base.d.ts +71 -0
  32. package/dist/spans/base.d.ts.map +1 -0
  33. package/dist/spans/default.d.ts +13 -0
  34. package/dist/spans/default.d.ts.map +1 -0
  35. package/dist/spans/index.d.ts +7 -0
  36. package/dist/spans/index.d.ts.map +1 -0
  37. package/dist/spans/no-op.d.ts +15 -0
  38. package/dist/spans/no-op.d.ts.map +1 -0
  39. package/dist/tracers/base.d.ts +105 -0
  40. package/dist/tracers/base.d.ts.map +1 -0
  41. package/dist/tracers/default.d.ts +7 -0
  42. package/dist/tracers/default.d.ts.map +1 -0
  43. package/dist/tracers/index.d.ts +6 -0
  44. package/dist/tracers/index.d.ts.map +1 -0
  45. package/package.json +8 -6
package/dist/index.cjs CHANGED
@@ -1,4 +1,2056 @@
1
1
  'use strict';
2
2
 
3
+ var base = require('@mastra/core/base');
4
+ var logger = require('@mastra/core/logger');
5
+ var observability = require('@mastra/core/observability');
6
+ var utils = require('@mastra/core/utils');
7
+ var web = require('stream/web');
8
+ var error = require('@mastra/core/error');
9
+
10
+ // src/tracers/base.ts
11
+ var ModelSpanTracker = class {
12
+ #modelSpan;
13
+ #currentStepSpan;
14
+ #currentChunkSpan;
15
+ #accumulator = {};
16
+ #stepIndex = 0;
17
+ #chunkSequence = 0;
18
+ constructor(modelSpan) {
19
+ this.#modelSpan = modelSpan;
20
+ }
21
+ /**
22
+ * Get the tracing context for creating child spans.
23
+ * Returns the current step span if active, otherwise the model span.
24
+ */
25
+ getTracingContext() {
26
+ return {
27
+ currentSpan: this.#currentStepSpan ?? this.#modelSpan
28
+ };
29
+ }
30
+ /**
31
+ * Report an error on the generation span
32
+ */
33
+ reportGenerationError(options) {
34
+ this.#modelSpan?.error(options);
35
+ }
36
+ /**
37
+ * End the generation span
38
+ */
39
+ endGeneration(options) {
40
+ this.#modelSpan?.end(options);
41
+ }
42
+ /**
43
+ * Update the generation span
44
+ */
45
+ updateGeneration(options) {
46
+ this.#modelSpan?.update(options);
47
+ }
48
+ /**
49
+ * Start a new Model execution step
50
+ */
51
+ #startStepSpan(payload) {
52
+ this.#currentStepSpan = this.#modelSpan?.createChildSpan({
53
+ name: `step: ${this.#stepIndex}`,
54
+ type: observability.AISpanType.MODEL_STEP,
55
+ attributes: {
56
+ stepIndex: this.#stepIndex,
57
+ ...payload?.messageId ? { messageId: payload.messageId } : {},
58
+ ...payload?.warnings?.length ? { warnings: payload.warnings } : {}
59
+ },
60
+ input: payload?.request
61
+ });
62
+ this.#chunkSequence = 0;
63
+ }
64
+ /**
65
+ * End the current Model execution step with token usage, finish reason, output, and metadata
66
+ */
67
+ #endStepSpan(payload) {
68
+ if (!this.#currentStepSpan) return;
69
+ const output = payload.output;
70
+ const { usage, ...otherOutput } = output;
71
+ const stepResult = payload.stepResult;
72
+ const metadata = payload.metadata;
73
+ const cleanMetadata = metadata ? { ...metadata } : void 0;
74
+ if (cleanMetadata?.request) {
75
+ delete cleanMetadata.request;
76
+ }
77
+ this.#currentStepSpan.end({
78
+ output: otherOutput,
79
+ attributes: {
80
+ usage,
81
+ isContinued: stepResult.isContinued,
82
+ finishReason: stepResult.reason,
83
+ warnings: stepResult.warnings
84
+ },
85
+ metadata: {
86
+ ...cleanMetadata
87
+ }
88
+ });
89
+ this.#currentStepSpan = void 0;
90
+ this.#stepIndex++;
91
+ }
92
+ /**
93
+ * Create a new chunk span (for multi-part chunks like text-start/delta/end)
94
+ */
95
+ #startChunkSpan(chunkType, initialData) {
96
+ if (!this.#currentStepSpan) {
97
+ this.#startStepSpan();
98
+ }
99
+ this.#currentChunkSpan = this.#currentStepSpan?.createChildSpan({
100
+ name: `chunk: '${chunkType}'`,
101
+ type: observability.AISpanType.MODEL_CHUNK,
102
+ attributes: {
103
+ chunkType,
104
+ sequenceNumber: this.#chunkSequence
105
+ }
106
+ });
107
+ this.#accumulator = initialData || {};
108
+ }
109
+ /**
110
+ * Append string content to a specific field in the accumulator
111
+ */
112
+ #appendToAccumulator(field, text) {
113
+ if (this.#accumulator[field] === void 0) {
114
+ this.#accumulator[field] = text;
115
+ } else {
116
+ this.#accumulator[field] += text;
117
+ }
118
+ }
119
+ /**
120
+ * End the current chunk span.
121
+ * Safe to call multiple times - will no-op if span already ended.
122
+ */
123
+ #endChunkSpan(output) {
124
+ if (!this.#currentChunkSpan) return;
125
+ this.#currentChunkSpan.end({
126
+ output: output !== void 0 ? output : this.#accumulator
127
+ });
128
+ this.#currentChunkSpan = void 0;
129
+ this.#accumulator = {};
130
+ this.#chunkSequence++;
131
+ }
132
+ /**
133
+ * Create an event span (for single chunks like tool-call)
134
+ */
135
+ #createEventSpan(chunkType, output) {
136
+ if (!this.#currentStepSpan) {
137
+ this.#startStepSpan();
138
+ }
139
+ const span = this.#currentStepSpan?.createEventSpan({
140
+ name: `chunk: '${chunkType}'`,
141
+ type: observability.AISpanType.MODEL_CHUNK,
142
+ attributes: {
143
+ chunkType,
144
+ sequenceNumber: this.#chunkSequence
145
+ },
146
+ output
147
+ });
148
+ if (span) {
149
+ this.#chunkSequence++;
150
+ }
151
+ }
152
+ /**
153
+ * Check if there is currently an active chunk span
154
+ */
155
+ #hasActiveChunkSpan() {
156
+ return !!this.#currentChunkSpan;
157
+ }
158
+ /**
159
+ * Get the current accumulator value
160
+ */
161
+ #getAccumulator() {
162
+ return this.#accumulator;
163
+ }
164
+ /**
165
+ * Handle text chunk spans (text-start/delta/end)
166
+ */
167
+ #handleTextChunk(chunk) {
168
+ switch (chunk.type) {
169
+ case "text-start":
170
+ this.#startChunkSpan("text");
171
+ break;
172
+ case "text-delta":
173
+ this.#appendToAccumulator("text", chunk.payload.text);
174
+ break;
175
+ case "text-end": {
176
+ this.#endChunkSpan();
177
+ break;
178
+ }
179
+ }
180
+ }
181
+ /**
182
+ * Handle reasoning chunk spans (reasoning-start/delta/end)
183
+ */
184
+ #handleReasoningChunk(chunk) {
185
+ switch (chunk.type) {
186
+ case "reasoning-start":
187
+ this.#startChunkSpan("reasoning");
188
+ break;
189
+ case "reasoning-delta":
190
+ this.#appendToAccumulator("text", chunk.payload.text);
191
+ break;
192
+ case "reasoning-end": {
193
+ this.#endChunkSpan();
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ /**
199
+ * Handle tool call chunk spans (tool-call-input-streaming-start/delta/end, tool-call)
200
+ */
201
+ #handleToolCallChunk(chunk) {
202
+ switch (chunk.type) {
203
+ case "tool-call-input-streaming-start":
204
+ this.#startChunkSpan("tool-call", {
205
+ toolName: chunk.payload.toolName,
206
+ toolCallId: chunk.payload.toolCallId
207
+ });
208
+ break;
209
+ case "tool-call-delta":
210
+ this.#appendToAccumulator("toolInput", chunk.payload.argsTextDelta);
211
+ break;
212
+ case "tool-call-input-streaming-end":
213
+ case "tool-call": {
214
+ const acc = this.#getAccumulator();
215
+ let toolInput;
216
+ try {
217
+ toolInput = acc.toolInput ? JSON.parse(acc.toolInput) : {};
218
+ } catch {
219
+ toolInput = acc.toolInput;
220
+ }
221
+ this.#endChunkSpan({
222
+ toolName: acc.toolName,
223
+ toolCallId: acc.toolCallId,
224
+ toolInput
225
+ });
226
+ break;
227
+ }
228
+ }
229
+ }
230
+ /**
231
+ * Handle object chunk spans (object, object-result)
232
+ */
233
+ #handleObjectChunk(chunk) {
234
+ switch (chunk.type) {
235
+ case "object":
236
+ if (!this.#hasActiveChunkSpan()) {
237
+ this.#startChunkSpan("object");
238
+ }
239
+ break;
240
+ case "object-result":
241
+ this.#endChunkSpan(chunk.object);
242
+ break;
243
+ }
244
+ }
245
+ /**
246
+ * Wraps a stream with model tracing transform to track MODEL_STEP and MODEL_CHUNK spans.
247
+ *
248
+ * This should be added to the stream pipeline to automatically
249
+ * create MODEL_STEP and MODEL_CHUNK spans for each semantic unit in the stream.
250
+ */
251
+ wrapStream(stream) {
252
+ return stream.pipeThrough(
253
+ new web.TransformStream({
254
+ transform: (chunk, controller) => {
255
+ controller.enqueue(chunk);
256
+ switch (chunk.type) {
257
+ case "text-start":
258
+ case "text-delta":
259
+ case "text-end":
260
+ this.#handleTextChunk(chunk);
261
+ break;
262
+ case "tool-call-input-streaming-start":
263
+ case "tool-call-delta":
264
+ case "tool-call-input-streaming-end":
265
+ case "tool-call":
266
+ this.#handleToolCallChunk(chunk);
267
+ break;
268
+ case "reasoning-start":
269
+ case "reasoning-delta":
270
+ case "reasoning-end":
271
+ this.#handleReasoningChunk(chunk);
272
+ break;
273
+ case "object":
274
+ case "object-result":
275
+ this.#handleObjectChunk(chunk);
276
+ break;
277
+ case "step-start":
278
+ this.#startStepSpan(chunk.payload);
279
+ break;
280
+ case "step-finish":
281
+ this.#endStepSpan(chunk.payload);
282
+ break;
283
+ case "raw":
284
+ // Skip raw chunks as they're redundant
285
+ case "start":
286
+ case "finish":
287
+ break;
288
+ // Default: auto-create event span for all other chunk types
289
+ default: {
290
+ let outputPayload = chunk.payload;
291
+ if (outputPayload && typeof outputPayload === "object" && "data" in outputPayload) {
292
+ const typedPayload = outputPayload;
293
+ outputPayload = { ...typedPayload };
294
+ if (typedPayload.data) {
295
+ outputPayload.size = typeof typedPayload.data === "string" ? typedPayload.data.length : typedPayload.data instanceof Uint8Array ? typedPayload.data.length : void 0;
296
+ delete outputPayload.data;
297
+ }
298
+ }
299
+ this.#createEventSpan(chunk.type, outputPayload);
300
+ break;
301
+ }
302
+ }
303
+ }
304
+ })
305
+ );
306
+ }
307
+ };
308
+
309
+ // src/spans/base.ts
310
+ function isSpanInternal(spanType, flags) {
311
+ if (flags === void 0 || flags === observability.InternalSpans.NONE) {
312
+ return false;
313
+ }
314
+ switch (spanType) {
315
+ // Workflow-related spans
316
+ case observability.AISpanType.WORKFLOW_RUN:
317
+ case observability.AISpanType.WORKFLOW_STEP:
318
+ case observability.AISpanType.WORKFLOW_CONDITIONAL:
319
+ case observability.AISpanType.WORKFLOW_CONDITIONAL_EVAL:
320
+ case observability.AISpanType.WORKFLOW_PARALLEL:
321
+ case observability.AISpanType.WORKFLOW_LOOP:
322
+ case observability.AISpanType.WORKFLOW_SLEEP:
323
+ case observability.AISpanType.WORKFLOW_WAIT_EVENT:
324
+ return (flags & observability.InternalSpans.WORKFLOW) !== 0;
325
+ // Agent-related spans
326
+ case observability.AISpanType.AGENT_RUN:
327
+ return (flags & observability.InternalSpans.AGENT) !== 0;
328
+ // Tool-related spans
329
+ case observability.AISpanType.TOOL_CALL:
330
+ case observability.AISpanType.MCP_TOOL_CALL:
331
+ return (flags & observability.InternalSpans.TOOL) !== 0;
332
+ // Model-related spans
333
+ case observability.AISpanType.MODEL_GENERATION:
334
+ case observability.AISpanType.MODEL_STEP:
335
+ case observability.AISpanType.MODEL_CHUNK:
336
+ return (flags & observability.InternalSpans.MODEL) !== 0;
337
+ // Default: never internal
338
+ default:
339
+ return false;
340
+ }
341
+ }
342
+ var BaseAISpan = class {
343
+ name;
344
+ type;
345
+ attributes;
346
+ parent;
347
+ startTime;
348
+ endTime;
349
+ isEvent;
350
+ isInternal;
351
+ aiTracing;
352
+ input;
353
+ output;
354
+ errorInfo;
355
+ metadata;
356
+ traceState;
357
+ /** Parent span ID (for root spans that are children of external spans) */
358
+ parentSpanId;
359
+ constructor(options, aiTracing) {
360
+ this.name = options.name;
361
+ this.type = options.type;
362
+ this.attributes = deepClean(options.attributes) || {};
363
+ this.metadata = deepClean(options.metadata);
364
+ this.parent = options.parent;
365
+ this.startTime = /* @__PURE__ */ new Date();
366
+ this.aiTracing = aiTracing;
367
+ this.isEvent = options.isEvent ?? false;
368
+ this.isInternal = isSpanInternal(this.type, options.tracingPolicy?.internal);
369
+ this.traceState = options.traceState;
370
+ if (this.isEvent) {
371
+ this.output = deepClean(options.output);
372
+ } else {
373
+ this.input = deepClean(options.input);
374
+ }
375
+ }
376
+ createChildSpan(options) {
377
+ return this.aiTracing.startSpan({ ...options, parent: this, isEvent: false });
378
+ }
379
+ createEventSpan(options) {
380
+ return this.aiTracing.startSpan({ ...options, parent: this, isEvent: true });
381
+ }
382
+ /**
383
+ * Create a ModelSpanTracker for this span (only works if this is a MODEL_GENERATION span)
384
+ * Returns undefined for non-MODEL_GENERATION spans
385
+ */
386
+ createTracker() {
387
+ if (this.type !== observability.AISpanType.MODEL_GENERATION) {
388
+ return void 0;
389
+ }
390
+ return new ModelSpanTracker(this);
391
+ }
392
+ /** Returns `TRUE` if the span is the root span of a trace */
393
+ get isRootSpan() {
394
+ return !this.parent;
395
+ }
396
+ /** Get the closest parent spanId that isn't an internal span */
397
+ getParentSpanId(includeInternalSpans) {
398
+ if (!this.parent) {
399
+ return this.parentSpanId;
400
+ }
401
+ if (includeInternalSpans) return this.parent.id;
402
+ if (this.parent.isInternal) return this.parent.getParentSpanId(includeInternalSpans);
403
+ return this.parent.id;
404
+ }
405
+ /** Find the closest parent span of a specific type by walking up the parent chain */
406
+ findParent(spanType) {
407
+ let current = this.parent;
408
+ while (current) {
409
+ if (current.type === spanType) {
410
+ return current;
411
+ }
412
+ current = current.parent;
413
+ }
414
+ return void 0;
415
+ }
416
+ /** Returns a lightweight span ready for export */
417
+ exportSpan(includeInternalSpans) {
418
+ return {
419
+ id: this.id,
420
+ traceId: this.traceId,
421
+ name: this.name,
422
+ type: this.type,
423
+ attributes: this.attributes,
424
+ metadata: this.metadata,
425
+ startTime: this.startTime,
426
+ endTime: this.endTime,
427
+ input: this.input,
428
+ output: this.output,
429
+ errorInfo: this.errorInfo,
430
+ isEvent: this.isEvent,
431
+ isRootSpan: this.isRootSpan,
432
+ parentSpanId: this.getParentSpanId(includeInternalSpans)
433
+ };
434
+ }
435
+ get externalTraceId() {
436
+ return this.isValid ? this.traceId : void 0;
437
+ }
438
+ };
439
+ var DEFAULT_KEYS_TO_STRIP = /* @__PURE__ */ new Set([
440
+ "logger",
441
+ "experimental_providerMetadata",
442
+ "providerMetadata",
443
+ "steps",
444
+ "tracingContext"
445
+ ]);
446
+ function deepClean(value, options = {}, _seen = /* @__PURE__ */ new WeakSet(), _depth = 0) {
447
+ const { keysToStrip = DEFAULT_KEYS_TO_STRIP, maxDepth = 10 } = options;
448
+ if (_depth > maxDepth) {
449
+ return "[MaxDepth]";
450
+ }
451
+ if (value === null || typeof value !== "object") {
452
+ try {
453
+ JSON.stringify(value);
454
+ return value;
455
+ } catch (error) {
456
+ return `[${error instanceof Error ? error.message : String(error)}]`;
457
+ }
458
+ }
459
+ if (_seen.has(value)) {
460
+ return "[Circular]";
461
+ }
462
+ _seen.add(value);
463
+ if (Array.isArray(value)) {
464
+ return value.map((item) => deepClean(item, options, _seen, _depth + 1));
465
+ }
466
+ const cleaned = {};
467
+ for (const [key, val] of Object.entries(value)) {
468
+ if (keysToStrip.has(key)) {
469
+ continue;
470
+ }
471
+ try {
472
+ cleaned[key] = deepClean(val, options, _seen, _depth + 1);
473
+ } catch (error) {
474
+ cleaned[key] = `[${error instanceof Error ? error.message : String(error)}]`;
475
+ }
476
+ }
477
+ return cleaned;
478
+ }
479
+ var DefaultAISpan = class extends BaseAISpan {
480
+ id;
481
+ traceId;
482
+ constructor(options, aiTracing) {
483
+ super(options, aiTracing);
484
+ this.id = generateSpanId();
485
+ if (options.parent) {
486
+ this.traceId = options.parent.traceId;
487
+ } else if (options.traceId) {
488
+ if (isValidTraceId(options.traceId)) {
489
+ this.traceId = options.traceId;
490
+ } else {
491
+ console.error(
492
+ `[Mastra Tracing] Invalid traceId: must be 1-32 hexadecimal characters, got "${options.traceId}". Generating new trace ID.`
493
+ );
494
+ this.traceId = generateTraceId();
495
+ }
496
+ } else {
497
+ this.traceId = generateTraceId();
498
+ }
499
+ if (!options.parent && options.parentSpanId) {
500
+ if (isValidSpanId(options.parentSpanId)) {
501
+ this.parentSpanId = options.parentSpanId;
502
+ } else {
503
+ console.error(
504
+ `[Mastra Tracing] Invalid parentSpanId: must be 1-16 hexadecimal characters, got "${options.parentSpanId}". Ignoring parent span ID.`
505
+ );
506
+ }
507
+ }
508
+ }
509
+ end(options) {
510
+ if (this.isEvent) {
511
+ return;
512
+ }
513
+ this.endTime = /* @__PURE__ */ new Date();
514
+ if (options?.output !== void 0) {
515
+ this.output = deepClean(options.output);
516
+ }
517
+ if (options?.attributes) {
518
+ this.attributes = { ...this.attributes, ...deepClean(options.attributes) };
519
+ }
520
+ if (options?.metadata) {
521
+ this.metadata = { ...this.metadata, ...deepClean(options.metadata) };
522
+ }
523
+ }
524
+ error(options) {
525
+ if (this.isEvent) {
526
+ return;
527
+ }
528
+ const { error: error$1, endSpan = true, attributes, metadata } = options;
529
+ this.errorInfo = error$1 instanceof error.MastraError ? {
530
+ id: error$1.id,
531
+ details: error$1.details,
532
+ category: error$1.category,
533
+ domain: error$1.domain,
534
+ message: error$1.message
535
+ } : {
536
+ message: error$1.message
537
+ };
538
+ if (attributes) {
539
+ this.attributes = { ...this.attributes, ...deepClean(attributes) };
540
+ }
541
+ if (metadata) {
542
+ this.metadata = { ...this.metadata, ...deepClean(metadata) };
543
+ }
544
+ if (endSpan) {
545
+ this.end();
546
+ } else {
547
+ this.update({});
548
+ }
549
+ }
550
+ update(options) {
551
+ if (this.isEvent) {
552
+ return;
553
+ }
554
+ if (options.input !== void 0) {
555
+ this.input = deepClean(options.input);
556
+ }
557
+ if (options.output !== void 0) {
558
+ this.output = deepClean(options.output);
559
+ }
560
+ if (options.attributes) {
561
+ this.attributes = { ...this.attributes, ...deepClean(options.attributes) };
562
+ }
563
+ if (options.metadata) {
564
+ this.metadata = { ...this.metadata, ...deepClean(options.metadata) };
565
+ }
566
+ }
567
+ get isValid() {
568
+ return true;
569
+ }
570
+ async export() {
571
+ return JSON.stringify({
572
+ spanId: this.id,
573
+ traceId: this.traceId,
574
+ startTime: this.startTime,
575
+ endTime: this.endTime,
576
+ attributes: this.attributes,
577
+ metadata: this.metadata
578
+ });
579
+ }
580
+ };
581
+ function generateSpanId() {
582
+ const bytes = new Uint8Array(8);
583
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
584
+ crypto.getRandomValues(bytes);
585
+ } else {
586
+ for (let i = 0; i < 8; i++) {
587
+ bytes[i] = Math.floor(Math.random() * 256);
588
+ }
589
+ }
590
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
591
+ }
592
+ function generateTraceId() {
593
+ const bytes = new Uint8Array(16);
594
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
595
+ crypto.getRandomValues(bytes);
596
+ } else {
597
+ for (let i = 0; i < 16; i++) {
598
+ bytes[i] = Math.floor(Math.random() * 256);
599
+ }
600
+ }
601
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
602
+ }
603
+ function isValidTraceId(traceId) {
604
+ return /^[0-9a-f]{1,32}$/i.test(traceId);
605
+ }
606
+ function isValidSpanId(spanId) {
607
+ return /^[0-9a-f]{1,16}$/i.test(spanId);
608
+ }
609
+
610
+ // src/spans/no-op.ts
611
+ var NoOpAISpan = class extends BaseAISpan {
612
+ id;
613
+ traceId;
614
+ constructor(options, aiTracing) {
615
+ super(options, aiTracing);
616
+ this.id = "no-op";
617
+ this.traceId = "no-op-trace";
618
+ }
619
+ end(_options) {
620
+ }
621
+ error(_options) {
622
+ }
623
+ update(_options) {
624
+ }
625
+ get isValid() {
626
+ return false;
627
+ }
628
+ };
629
+
630
+ // src/tracers/base.ts
631
+ var BaseAITracing = class extends base.MastraBase {
632
+ config;
633
+ constructor(config) {
634
+ super({ component: logger.RegisteredLogger.AI_TRACING, name: config.serviceName });
635
+ this.config = {
636
+ serviceName: config.serviceName,
637
+ name: config.name,
638
+ sampling: config.sampling ?? { type: observability.SamplingStrategyType.ALWAYS },
639
+ exporters: config.exporters ?? [],
640
+ processors: config.processors ?? [],
641
+ includeInternalSpans: config.includeInternalSpans ?? false,
642
+ requestContextKeys: config.requestContextKeys ?? []
643
+ };
644
+ }
645
+ /**
646
+ * Override setLogger to add AI tracing specific initialization log
647
+ * and propagate logger to exporters
648
+ */
649
+ __setLogger(logger) {
650
+ super.__setLogger(logger);
651
+ this.exporters.forEach((exporter) => {
652
+ if (typeof exporter.__setLogger === "function") {
653
+ exporter.__setLogger(logger);
654
+ }
655
+ });
656
+ this.logger.debug(
657
+ `[AI Tracing] Initialized [service=${this.config.serviceName}] [instance=${this.config.name}] [sampling=${this.config.sampling.type}]`
658
+ );
659
+ }
660
+ // ============================================================================
661
+ // Protected getters for clean config access
662
+ // ============================================================================
663
+ get exporters() {
664
+ return this.config.exporters || [];
665
+ }
666
+ get processors() {
667
+ return this.config.processors || [];
668
+ }
669
+ // ============================================================================
670
+ // Public API - Single type-safe span creation method
671
+ // ============================================================================
672
+ /**
673
+ * Start a new span of a specific AISpanType
674
+ */
675
+ startSpan(options) {
676
+ const { customSamplerOptions, requestContext, metadata, tracingOptions, ...rest } = options;
677
+ if (!this.shouldSample(customSamplerOptions)) {
678
+ return new NoOpAISpan({ ...rest, metadata }, this);
679
+ }
680
+ let traceState;
681
+ if (options.parent) {
682
+ traceState = options.parent.traceState;
683
+ } else {
684
+ traceState = this.computeTraceState(tracingOptions);
685
+ }
686
+ const enrichedMetadata = this.extractMetadataFromRequestContext(requestContext, metadata, traceState);
687
+ const span = this.createSpan({
688
+ ...rest,
689
+ metadata: enrichedMetadata,
690
+ traceState
691
+ });
692
+ if (span.isEvent) {
693
+ this.emitSpanEnded(span);
694
+ } else {
695
+ this.wireSpanLifecycle(span);
696
+ this.emitSpanStarted(span);
697
+ }
698
+ return span;
699
+ }
700
+ // ============================================================================
701
+ // Configuration Management
702
+ // ============================================================================
703
+ /**
704
+ * Get current configuration
705
+ */
706
+ getConfig() {
707
+ return { ...this.config };
708
+ }
709
+ // ============================================================================
710
+ // Plugin Access
711
+ // ============================================================================
712
+ /**
713
+ * Get all exporters
714
+ */
715
+ getExporters() {
716
+ return [...this.exporters];
717
+ }
718
+ /**
719
+ * Get all processors
720
+ */
721
+ getProcessors() {
722
+ return [...this.processors];
723
+ }
724
+ /**
725
+ * Get the logger instance (for exporters and other components)
726
+ */
727
+ getLogger() {
728
+ return this.logger;
729
+ }
730
+ // ============================================================================
731
+ // Span Lifecycle Management
732
+ // ============================================================================
733
+ /**
734
+ * Automatically wires up AI tracing lifecycle events for any span
735
+ * This ensures all spans emit events regardless of implementation
736
+ */
737
+ wireSpanLifecycle(span) {
738
+ if (!this.config.includeInternalSpans && span.isInternal) {
739
+ return;
740
+ }
741
+ const originalEnd = span.end.bind(span);
742
+ const originalUpdate = span.update.bind(span);
743
+ span.end = (options) => {
744
+ if (span.isEvent) {
745
+ this.logger.warn(`End event is not available on event spans`);
746
+ return;
747
+ }
748
+ originalEnd(options);
749
+ this.emitSpanEnded(span);
750
+ };
751
+ span.update = (options) => {
752
+ if (span.isEvent) {
753
+ this.logger.warn(`Update() is not available on event spans`);
754
+ return;
755
+ }
756
+ originalUpdate(options);
757
+ this.emitSpanUpdated(span);
758
+ };
759
+ }
760
+ // ============================================================================
761
+ // Utility Methods
762
+ // ============================================================================
763
+ /**
764
+ * Check if an AI trace should be sampled
765
+ */
766
+ shouldSample(options) {
767
+ const { sampling } = this.config;
768
+ switch (sampling.type) {
769
+ case observability.SamplingStrategyType.ALWAYS:
770
+ return true;
771
+ case observability.SamplingStrategyType.NEVER:
772
+ return false;
773
+ case observability.SamplingStrategyType.RATIO:
774
+ if (sampling.probability === void 0 || sampling.probability < 0 || sampling.probability > 1) {
775
+ this.logger.warn(
776
+ `Invalid sampling probability: ${sampling.probability}. Expected value between 0 and 1. Defaulting to no sampling.`
777
+ );
778
+ return false;
779
+ }
780
+ return Math.random() < sampling.probability;
781
+ case observability.SamplingStrategyType.CUSTOM:
782
+ return sampling.sampler(options);
783
+ default:
784
+ throw new Error(`Sampling strategy type not implemented: ${sampling.type}`);
785
+ }
786
+ }
787
+ /**
788
+ * Compute TraceState for a new trace based on configured and per-request keys
789
+ */
790
+ computeTraceState(tracingOptions) {
791
+ const configuredKeys = this.config.requestContextKeys ?? [];
792
+ const additionalKeys = tracingOptions?.requestContextKeys ?? [];
793
+ const allKeys = [...configuredKeys, ...additionalKeys];
794
+ if (allKeys.length === 0) {
795
+ return void 0;
796
+ }
797
+ return {
798
+ requestContextKeys: allKeys
799
+ };
800
+ }
801
+ /**
802
+ * Extract metadata from RequestContext using TraceState
803
+ */
804
+ extractMetadataFromRequestContext(requestContext, explicitMetadata, traceState) {
805
+ if (!requestContext || !traceState || traceState.requestContextKeys.length === 0) {
806
+ return explicitMetadata;
807
+ }
808
+ const extracted = this.extractKeys(requestContext, traceState.requestContextKeys);
809
+ if (Object.keys(extracted).length === 0 && !explicitMetadata) {
810
+ return void 0;
811
+ }
812
+ return {
813
+ ...extracted,
814
+ ...explicitMetadata
815
+ // Explicit metadata always wins
816
+ };
817
+ }
818
+ /**
819
+ * Extract specific keys from RequestContext
820
+ */
821
+ extractKeys(requestContext, keys) {
822
+ const result = {};
823
+ for (const key of keys) {
824
+ const parts = key.split(".");
825
+ const rootKey = parts[0];
826
+ const value = requestContext.get(rootKey);
827
+ if (value !== void 0) {
828
+ if (parts.length > 1) {
829
+ const nestedPath = parts.slice(1).join(".");
830
+ const nestedValue = utils.getNestedValue(value, nestedPath);
831
+ if (nestedValue !== void 0) {
832
+ utils.setNestedValue(result, key, nestedValue);
833
+ }
834
+ } else {
835
+ utils.setNestedValue(result, key, value);
836
+ }
837
+ }
838
+ }
839
+ return result;
840
+ }
841
+ /**
842
+ * Process a span through all processors
843
+ */
844
+ processSpan(span) {
845
+ for (const processor of this.processors) {
846
+ if (!span) {
847
+ break;
848
+ }
849
+ try {
850
+ span = processor.process(span);
851
+ } catch (error) {
852
+ this.logger.error(`[AI Tracing] Processor error [name=${processor.name}]`, error);
853
+ }
854
+ }
855
+ return span;
856
+ }
857
+ // ============================================================================
858
+ // Event-driven Export Methods
859
+ // ============================================================================
860
+ getSpanForExport(span) {
861
+ if (!span.isValid) return void 0;
862
+ if (span.isInternal && !this.config.includeInternalSpans) return void 0;
863
+ const processedSpan = this.processSpan(span);
864
+ return processedSpan?.exportSpan(this.config.includeInternalSpans);
865
+ }
866
+ /**
867
+ * Emit a span started event
868
+ */
869
+ emitSpanStarted(span) {
870
+ const exportedSpan = this.getSpanForExport(span);
871
+ if (exportedSpan) {
872
+ this.exportEvent({ type: observability.AITracingEventType.SPAN_STARTED, exportedSpan }).catch((error) => {
873
+ this.logger.error("[AI Tracing] Failed to export span_started event", error);
874
+ });
875
+ }
876
+ }
877
+ /**
878
+ * Emit a span ended event (called automatically when spans end)
879
+ */
880
+ emitSpanEnded(span) {
881
+ const exportedSpan = this.getSpanForExport(span);
882
+ if (exportedSpan) {
883
+ this.exportEvent({ type: observability.AITracingEventType.SPAN_ENDED, exportedSpan }).catch((error) => {
884
+ this.logger.error("[AI Tracing] Failed to export span_ended event", error);
885
+ });
886
+ }
887
+ }
888
+ /**
889
+ * Emit a span updated event
890
+ */
891
+ emitSpanUpdated(span) {
892
+ const exportedSpan = this.getSpanForExport(span);
893
+ if (exportedSpan) {
894
+ this.exportEvent({ type: observability.AITracingEventType.SPAN_UPDATED, exportedSpan }).catch((error) => {
895
+ this.logger.error("[AI Tracing] Failed to export span_updated event", error);
896
+ });
897
+ }
898
+ }
899
+ /**
900
+ * Export tracing event through all exporters (realtime mode)
901
+ */
902
+ async exportEvent(event) {
903
+ const exportPromises = this.exporters.map(async (exporter) => {
904
+ try {
905
+ if (exporter.exportEvent) {
906
+ await exporter.exportEvent(event);
907
+ this.logger.debug(`[AI Tracing] Event exported [exporter=${exporter.name}] [type=${event.type}]`);
908
+ }
909
+ } catch (error) {
910
+ this.logger.error(`[AI Tracing] Export error [exporter=${exporter.name}]`, error);
911
+ }
912
+ });
913
+ await Promise.allSettled(exportPromises);
914
+ }
915
+ // ============================================================================
916
+ // Lifecycle Management
917
+ // ============================================================================
918
+ /**
919
+ * Initialize AI tracing (called by Mastra during component registration)
920
+ */
921
+ init() {
922
+ this.logger.debug(`[AI Tracing] Initialization started [name=${this.name}]`);
923
+ this.logger.info(`[AI Tracing] Initialized successfully [name=${this.name}]`);
924
+ }
925
+ /**
926
+ * Shutdown AI tracing and clean up resources
927
+ */
928
+ async shutdown() {
929
+ this.logger.debug(`[AI Tracing] Shutdown started [name=${this.name}]`);
930
+ const shutdownPromises = [...this.exporters.map((e) => e.shutdown()), ...this.processors.map((p) => p.shutdown())];
931
+ await Promise.allSettled(shutdownPromises);
932
+ this.logger.info(`[AI Tracing] Shutdown completed [name=${this.name}]`);
933
+ }
934
+ };
935
+
936
+ // src/tracers/default.ts
937
+ var DefaultAITracing = class extends BaseAITracing {
938
+ constructor(config) {
939
+ super(config);
940
+ }
941
+ createSpan(options) {
942
+ return new DefaultAISpan(options, this);
943
+ }
944
+ };
945
+ var BaseExporter = class {
946
+ /** Mastra logger instance */
947
+ logger;
948
+ /** Whether this exporter is disabled */
949
+ isDisabled = false;
950
+ /**
951
+ * Initialize the base exporter with logger
952
+ */
953
+ constructor(config = {}) {
954
+ const logLevel = this.resolveLogLevel(config.logLevel);
955
+ this.logger = config.logger ?? new logger.ConsoleLogger({ level: logLevel, name: this.constructor.name });
956
+ }
957
+ /**
958
+ * Set the logger for the exporter (called by Mastra/AITracing during initialization)
959
+ */
960
+ __setLogger(logger) {
961
+ this.logger = logger;
962
+ this.logger.debug(`Logger updated for exporter [name=${this.name}]`);
963
+ }
964
+ /**
965
+ * Convert string log level to LogLevel enum
966
+ */
967
+ resolveLogLevel(logLevel) {
968
+ if (!logLevel) {
969
+ return logger.LogLevel.INFO;
970
+ }
971
+ if (typeof logLevel === "number") {
972
+ return logLevel;
973
+ }
974
+ const logLevelMap = {
975
+ debug: logger.LogLevel.DEBUG,
976
+ info: logger.LogLevel.INFO,
977
+ warn: logger.LogLevel.WARN,
978
+ error: logger.LogLevel.ERROR
979
+ };
980
+ return logLevelMap[logLevel] ?? logger.LogLevel.INFO;
981
+ }
982
+ /**
983
+ * Mark the exporter as disabled and log a message
984
+ *
985
+ * @param reason - Reason why the exporter is disabled
986
+ */
987
+ setDisabled(reason) {
988
+ this.isDisabled = true;
989
+ this.logger.warn(`${this.name} disabled: ${reason}`);
990
+ }
991
+ /**
992
+ * Export a tracing event
993
+ *
994
+ * This method checks if the exporter is disabled before calling _exportEvent.
995
+ * Subclasses should implement _exportEvent instead of overriding this method.
996
+ */
997
+ async exportEvent(event) {
998
+ if (this.isDisabled) {
999
+ return;
1000
+ }
1001
+ await this._exportEvent(event);
1002
+ }
1003
+ /**
1004
+ * Shutdown the exporter and clean up resources
1005
+ *
1006
+ * Default implementation just logs. Override to add custom cleanup.
1007
+ */
1008
+ async shutdown() {
1009
+ this.logger.info(`${this.name} shutdown complete`);
1010
+ }
1011
+ };
1012
+ var CloudExporter = class extends BaseExporter {
1013
+ name = "mastra-cloud-ai-tracing-exporter";
1014
+ config;
1015
+ buffer;
1016
+ flushTimer = null;
1017
+ constructor(config = {}) {
1018
+ super(config);
1019
+ const accessToken = config.accessToken ?? process.env.MASTRA_CLOUD_ACCESS_TOKEN;
1020
+ if (!accessToken) {
1021
+ this.setDisabled(
1022
+ "MASTRA_CLOUD_ACCESS_TOKEN environment variable not set. \u{1F680} Sign up for Mastra Cloud at https://cloud.mastra.ai to see your AI traces online and obtain your access token."
1023
+ );
1024
+ }
1025
+ const endpoint = config.endpoint ?? process.env.MASTRA_CLOUD_AI_TRACES_ENDPOINT ?? "https://api.mastra.ai/ai/spans/publish";
1026
+ this.config = {
1027
+ logger: this.logger,
1028
+ logLevel: config.logLevel ?? logger.LogLevel.INFO,
1029
+ maxBatchSize: config.maxBatchSize ?? 1e3,
1030
+ maxBatchWaitMs: config.maxBatchWaitMs ?? 5e3,
1031
+ maxRetries: config.maxRetries ?? 3,
1032
+ accessToken: accessToken || "",
1033
+ endpoint
1034
+ };
1035
+ this.buffer = {
1036
+ spans: [],
1037
+ totalSize: 0
1038
+ };
1039
+ }
1040
+ async _exportEvent(event) {
1041
+ if (event.type !== observability.AITracingEventType.SPAN_ENDED) {
1042
+ return;
1043
+ }
1044
+ this.addToBuffer(event);
1045
+ if (this.shouldFlush()) {
1046
+ this.flush().catch((error) => {
1047
+ this.logger.error("Batch flush failed", {
1048
+ error: error instanceof Error ? error.message : String(error)
1049
+ });
1050
+ });
1051
+ } else if (this.buffer.totalSize === 1) {
1052
+ this.scheduleFlush();
1053
+ }
1054
+ }
1055
+ addToBuffer(event) {
1056
+ if (this.buffer.totalSize === 0) {
1057
+ this.buffer.firstEventTime = /* @__PURE__ */ new Date();
1058
+ }
1059
+ const spanRecord = this.formatSpan(event.exportedSpan);
1060
+ this.buffer.spans.push(spanRecord);
1061
+ this.buffer.totalSize++;
1062
+ }
1063
+ formatSpan(span) {
1064
+ const spanRecord = {
1065
+ traceId: span.traceId,
1066
+ spanId: span.id,
1067
+ parentSpanId: span.parentSpanId ?? null,
1068
+ name: span.name,
1069
+ spanType: span.type,
1070
+ attributes: span.attributes ?? null,
1071
+ metadata: span.metadata ?? null,
1072
+ startedAt: span.startTime,
1073
+ endedAt: span.endTime ?? null,
1074
+ input: span.input ?? null,
1075
+ output: span.output ?? null,
1076
+ error: span.errorInfo,
1077
+ isEvent: span.isEvent,
1078
+ createdAt: /* @__PURE__ */ new Date(),
1079
+ updatedAt: null
1080
+ };
1081
+ return spanRecord;
1082
+ }
1083
+ shouldFlush() {
1084
+ if (this.buffer.totalSize >= this.config.maxBatchSize) {
1085
+ return true;
1086
+ }
1087
+ if (this.buffer.firstEventTime && this.buffer.totalSize > 0) {
1088
+ const elapsed = Date.now() - this.buffer.firstEventTime.getTime();
1089
+ if (elapsed >= this.config.maxBatchWaitMs) {
1090
+ return true;
1091
+ }
1092
+ }
1093
+ return false;
1094
+ }
1095
+ scheduleFlush() {
1096
+ if (this.flushTimer) {
1097
+ clearTimeout(this.flushTimer);
1098
+ }
1099
+ this.flushTimer = setTimeout(() => {
1100
+ this.flush().catch((error$1) => {
1101
+ const mastraError = new error.MastraError(
1102
+ {
1103
+ id: `CLOUD_AI_TRACING_FAILED_TO_SCHEDULE_FLUSH`,
1104
+ domain: error.ErrorDomain.MASTRA_OBSERVABILITY,
1105
+ category: error.ErrorCategory.USER
1106
+ },
1107
+ error$1
1108
+ );
1109
+ this.logger.trackException(mastraError);
1110
+ this.logger.error("Scheduled flush failed", mastraError);
1111
+ });
1112
+ }, this.config.maxBatchWaitMs);
1113
+ }
1114
+ async flush() {
1115
+ if (this.flushTimer) {
1116
+ clearTimeout(this.flushTimer);
1117
+ this.flushTimer = null;
1118
+ }
1119
+ if (this.buffer.totalSize === 0) {
1120
+ return;
1121
+ }
1122
+ const startTime = Date.now();
1123
+ const spansCopy = [...this.buffer.spans];
1124
+ const flushReason = this.buffer.totalSize >= this.config.maxBatchSize ? "size" : "time";
1125
+ this.resetBuffer();
1126
+ try {
1127
+ await this.batchUpload(spansCopy);
1128
+ const elapsed = Date.now() - startTime;
1129
+ this.logger.debug("Batch flushed successfully", {
1130
+ batchSize: spansCopy.length,
1131
+ flushReason,
1132
+ durationMs: elapsed
1133
+ });
1134
+ } catch (error$1) {
1135
+ const mastraError = new error.MastraError(
1136
+ {
1137
+ id: `CLOUD_AI_TRACING_FAILED_TO_BATCH_UPLOAD`,
1138
+ domain: error.ErrorDomain.MASTRA_OBSERVABILITY,
1139
+ category: error.ErrorCategory.USER,
1140
+ details: {
1141
+ droppedBatchSize: spansCopy.length
1142
+ }
1143
+ },
1144
+ error$1
1145
+ );
1146
+ this.logger.trackException(mastraError);
1147
+ this.logger.error("Batch upload failed after all retries, dropping batch", mastraError);
1148
+ }
1149
+ }
1150
+ /**
1151
+ * Uploads spans to cloud API using fetchWithRetry for all retry logic
1152
+ */
1153
+ async batchUpload(spans) {
1154
+ const headers = {
1155
+ Authorization: `Bearer ${this.config.accessToken}`,
1156
+ "Content-Type": "application/json"
1157
+ };
1158
+ const options = {
1159
+ method: "POST",
1160
+ headers,
1161
+ body: JSON.stringify({ spans })
1162
+ };
1163
+ await utils.fetchWithRetry(this.config.endpoint, options, this.config.maxRetries);
1164
+ }
1165
+ resetBuffer() {
1166
+ this.buffer.spans = [];
1167
+ this.buffer.firstEventTime = void 0;
1168
+ this.buffer.totalSize = 0;
1169
+ }
1170
+ async shutdown() {
1171
+ if (this.isDisabled) {
1172
+ return;
1173
+ }
1174
+ if (this.flushTimer) {
1175
+ clearTimeout(this.flushTimer);
1176
+ this.flushTimer = null;
1177
+ }
1178
+ if (this.buffer.totalSize > 0) {
1179
+ this.logger.info("Flushing remaining events on shutdown", {
1180
+ remainingEvents: this.buffer.totalSize
1181
+ });
1182
+ try {
1183
+ await this.flush();
1184
+ } catch (error$1) {
1185
+ const mastraError = new error.MastraError(
1186
+ {
1187
+ id: `CLOUD_AI_TRACING_FAILED_TO_FLUSH_REMAINING_EVENTS_DURING_SHUTDOWN`,
1188
+ domain: error.ErrorDomain.MASTRA_OBSERVABILITY,
1189
+ category: error.ErrorCategory.USER,
1190
+ details: {
1191
+ remainingEvents: this.buffer.totalSize
1192
+ }
1193
+ },
1194
+ error$1
1195
+ );
1196
+ this.logger.trackException(mastraError);
1197
+ this.logger.error("Failed to flush remaining events during shutdown", mastraError);
1198
+ }
1199
+ }
1200
+ this.logger.info("CloudExporter shutdown complete");
1201
+ }
1202
+ };
1203
+ var ConsoleExporter = class extends BaseExporter {
1204
+ name = "tracing-console-exporter";
1205
+ constructor(config = {}) {
1206
+ super(config);
1207
+ }
1208
+ async _exportEvent(event) {
1209
+ const span = event.exportedSpan;
1210
+ const formatAttributes = (attributes) => {
1211
+ try {
1212
+ return JSON.stringify(attributes, null, 2);
1213
+ } catch (error) {
1214
+ const errMsg = error instanceof Error ? error.message : "Unknown formatting error";
1215
+ return `[Unable to serialize attributes: ${errMsg}]`;
1216
+ }
1217
+ };
1218
+ const formatDuration = (startTime, endTime) => {
1219
+ if (!endTime) return "N/A";
1220
+ const duration = endTime.getTime() - startTime.getTime();
1221
+ return `${duration}ms`;
1222
+ };
1223
+ switch (event.type) {
1224
+ case observability.AITracingEventType.SPAN_STARTED:
1225
+ this.logger.info(`\u{1F680} SPAN_STARTED`);
1226
+ this.logger.info(` Type: ${span.type}`);
1227
+ this.logger.info(` Name: ${span.name}`);
1228
+ this.logger.info(` ID: ${span.id}`);
1229
+ this.logger.info(` Trace ID: ${span.traceId}`);
1230
+ if (span.input !== void 0) {
1231
+ this.logger.info(` Input: ${formatAttributes(span.input)}`);
1232
+ }
1233
+ this.logger.info(` Attributes: ${formatAttributes(span.attributes)}`);
1234
+ this.logger.info("\u2500".repeat(80));
1235
+ break;
1236
+ case observability.AITracingEventType.SPAN_ENDED:
1237
+ const duration = formatDuration(span.startTime, span.endTime);
1238
+ this.logger.info(`\u2705 SPAN_ENDED`);
1239
+ this.logger.info(` Type: ${span.type}`);
1240
+ this.logger.info(` Name: ${span.name}`);
1241
+ this.logger.info(` ID: ${span.id}`);
1242
+ this.logger.info(` Duration: ${duration}`);
1243
+ this.logger.info(` Trace ID: ${span.traceId}`);
1244
+ if (span.input !== void 0) {
1245
+ this.logger.info(` Input: ${formatAttributes(span.input)}`);
1246
+ }
1247
+ if (span.output !== void 0) {
1248
+ this.logger.info(` Output: ${formatAttributes(span.output)}`);
1249
+ }
1250
+ if (span.errorInfo) {
1251
+ this.logger.info(` Error: ${formatAttributes(span.errorInfo)}`);
1252
+ }
1253
+ this.logger.info(` Attributes: ${formatAttributes(span.attributes)}`);
1254
+ this.logger.info("\u2500".repeat(80));
1255
+ break;
1256
+ case observability.AITracingEventType.SPAN_UPDATED:
1257
+ this.logger.info(`\u{1F4DD} SPAN_UPDATED`);
1258
+ this.logger.info(` Type: ${span.type}`);
1259
+ this.logger.info(` Name: ${span.name}`);
1260
+ this.logger.info(` ID: ${span.id}`);
1261
+ this.logger.info(` Trace ID: ${span.traceId}`);
1262
+ if (span.input !== void 0) {
1263
+ this.logger.info(` Input: ${formatAttributes(span.input)}`);
1264
+ }
1265
+ if (span.output !== void 0) {
1266
+ this.logger.info(` Output: ${formatAttributes(span.output)}`);
1267
+ }
1268
+ if (span.errorInfo) {
1269
+ this.logger.info(` Error: ${formatAttributes(span.errorInfo)}`);
1270
+ }
1271
+ this.logger.info(` Updated Attributes: ${formatAttributes(span.attributes)}`);
1272
+ this.logger.info("\u2500".repeat(80));
1273
+ break;
1274
+ default:
1275
+ this.logger.warn(`Tracing event type not implemented: ${event.type}`);
1276
+ }
1277
+ }
1278
+ async shutdown() {
1279
+ this.logger.info("ConsoleExporter shutdown");
1280
+ }
1281
+ };
1282
+ function resolveStrategy(userConfig, storage, logger) {
1283
+ if (userConfig.strategy && userConfig.strategy !== "auto") {
1284
+ const hints = storage.aiTracingStrategy;
1285
+ if (hints.supported.includes(userConfig.strategy)) {
1286
+ return userConfig.strategy;
1287
+ }
1288
+ logger.warn("User-specified AI tracing strategy not supported by storage adapter, falling back to auto-selection", {
1289
+ userStrategy: userConfig.strategy,
1290
+ storageAdapter: storage.constructor.name,
1291
+ supportedStrategies: hints.supported,
1292
+ fallbackStrategy: hints.preferred
1293
+ });
1294
+ }
1295
+ return storage.aiTracingStrategy.preferred;
1296
+ }
1297
+ var DefaultExporter = class {
1298
+ name = "tracing-default-exporter";
1299
+ logger;
1300
+ storage;
1301
+ config;
1302
+ resolvedStrategy;
1303
+ buffer;
1304
+ flushTimer = null;
1305
+ // Track all spans that have been created, persists across flushes
1306
+ allCreatedSpans = /* @__PURE__ */ new Set();
1307
+ constructor(config = {}, logger$1) {
1308
+ if (logger$1) {
1309
+ this.logger = logger$1;
1310
+ } else {
1311
+ this.logger = new logger.ConsoleLogger({ level: logger.LogLevel.INFO });
1312
+ }
1313
+ this.config = {
1314
+ maxBatchSize: config.maxBatchSize ?? 1e3,
1315
+ maxBufferSize: config.maxBufferSize ?? 1e4,
1316
+ maxBatchWaitMs: config.maxBatchWaitMs ?? 5e3,
1317
+ maxRetries: config.maxRetries ?? 4,
1318
+ retryDelayMs: config.retryDelayMs ?? 500,
1319
+ strategy: config.strategy ?? "auto"
1320
+ };
1321
+ this.buffer = {
1322
+ creates: [],
1323
+ updates: [],
1324
+ insertOnly: [],
1325
+ seenSpans: /* @__PURE__ */ new Set(),
1326
+ spanSequences: /* @__PURE__ */ new Map(),
1327
+ completedSpans: /* @__PURE__ */ new Set(),
1328
+ outOfOrderCount: 0,
1329
+ totalSize: 0
1330
+ };
1331
+ this.resolvedStrategy = "batch-with-updates";
1332
+ }
1333
+ strategyInitialized = false;
1334
+ /**
1335
+ * Initialize the exporter (called after all dependencies are ready)
1336
+ */
1337
+ init(options) {
1338
+ this.storage = options.mastra?.getStorage();
1339
+ if (!this.storage) {
1340
+ this.logger.warn("DefaultExporter disabled: Storage not available. Traces will not be persisted.");
1341
+ return;
1342
+ }
1343
+ this.initializeStrategy(this.storage);
1344
+ }
1345
+ /**
1346
+ * Initialize the resolved strategy once storage is available
1347
+ */
1348
+ initializeStrategy(storage) {
1349
+ if (this.strategyInitialized) return;
1350
+ this.resolvedStrategy = resolveStrategy(this.config, storage, this.logger);
1351
+ this.strategyInitialized = true;
1352
+ this.logger.debug("AI tracing exporter initialized", {
1353
+ strategy: this.resolvedStrategy,
1354
+ source: this.config.strategy !== "auto" ? "user" : "auto",
1355
+ storageAdapter: storage.constructor.name,
1356
+ maxBatchSize: this.config.maxBatchSize,
1357
+ maxBatchWaitMs: this.config.maxBatchWaitMs
1358
+ });
1359
+ }
1360
+ /**
1361
+ * Builds a unique span key for tracking
1362
+ */
1363
+ buildSpanKey(traceId, spanId) {
1364
+ return `${traceId}:${spanId}`;
1365
+ }
1366
+ /**
1367
+ * Gets the next sequence number for a span
1368
+ */
1369
+ getNextSequence(spanKey) {
1370
+ const current = this.buffer.spanSequences.get(spanKey) || 0;
1371
+ const next = current + 1;
1372
+ this.buffer.spanSequences.set(spanKey, next);
1373
+ return next;
1374
+ }
1375
+ /**
1376
+ * Handles out-of-order span updates by logging and skipping
1377
+ */
1378
+ handleOutOfOrderUpdate(event) {
1379
+ this.logger.warn("Out-of-order span update detected - skipping event", {
1380
+ spanId: event.exportedSpan.id,
1381
+ traceId: event.exportedSpan.traceId,
1382
+ spanName: event.exportedSpan.name,
1383
+ eventType: event.type
1384
+ });
1385
+ }
1386
+ /**
1387
+ * Adds an event to the appropriate buffer based on strategy
1388
+ */
1389
+ addToBuffer(event) {
1390
+ const spanKey = this.buildSpanKey(event.exportedSpan.traceId, event.exportedSpan.id);
1391
+ if (this.buffer.totalSize === 0) {
1392
+ this.buffer.firstEventTime = /* @__PURE__ */ new Date();
1393
+ }
1394
+ switch (event.type) {
1395
+ case observability.AITracingEventType.SPAN_STARTED:
1396
+ if (this.resolvedStrategy === "batch-with-updates") {
1397
+ const createRecord = this.buildCreateRecord(event.exportedSpan);
1398
+ this.buffer.creates.push(createRecord);
1399
+ this.buffer.seenSpans.add(spanKey);
1400
+ this.allCreatedSpans.add(spanKey);
1401
+ }
1402
+ break;
1403
+ case observability.AITracingEventType.SPAN_UPDATED:
1404
+ if (this.resolvedStrategy === "batch-with-updates") {
1405
+ if (this.allCreatedSpans.has(spanKey)) {
1406
+ this.buffer.updates.push({
1407
+ traceId: event.exportedSpan.traceId,
1408
+ spanId: event.exportedSpan.id,
1409
+ updates: this.buildUpdateRecord(event.exportedSpan),
1410
+ sequenceNumber: this.getNextSequence(spanKey)
1411
+ });
1412
+ } else {
1413
+ this.handleOutOfOrderUpdate(event);
1414
+ this.buffer.outOfOrderCount++;
1415
+ }
1416
+ }
1417
+ break;
1418
+ case observability.AITracingEventType.SPAN_ENDED:
1419
+ if (this.resolvedStrategy === "batch-with-updates") {
1420
+ if (this.allCreatedSpans.has(spanKey)) {
1421
+ this.buffer.updates.push({
1422
+ traceId: event.exportedSpan.traceId,
1423
+ spanId: event.exportedSpan.id,
1424
+ updates: this.buildUpdateRecord(event.exportedSpan),
1425
+ sequenceNumber: this.getNextSequence(spanKey)
1426
+ });
1427
+ this.buffer.completedSpans.add(spanKey);
1428
+ } else if (event.exportedSpan.isEvent) {
1429
+ const createRecord = this.buildCreateRecord(event.exportedSpan);
1430
+ this.buffer.creates.push(createRecord);
1431
+ this.buffer.seenSpans.add(spanKey);
1432
+ this.allCreatedSpans.add(spanKey);
1433
+ this.buffer.completedSpans.add(spanKey);
1434
+ } else {
1435
+ this.handleOutOfOrderUpdate(event);
1436
+ this.buffer.outOfOrderCount++;
1437
+ }
1438
+ } else if (this.resolvedStrategy === "insert-only") {
1439
+ const createRecord = this.buildCreateRecord(event.exportedSpan);
1440
+ this.buffer.insertOnly.push(createRecord);
1441
+ this.buffer.completedSpans.add(spanKey);
1442
+ this.allCreatedSpans.add(spanKey);
1443
+ }
1444
+ break;
1445
+ }
1446
+ this.buffer.totalSize = this.buffer.creates.length + this.buffer.updates.length + this.buffer.insertOnly.length;
1447
+ }
1448
+ /**
1449
+ * Checks if buffer should be flushed based on size or time triggers
1450
+ */
1451
+ shouldFlush() {
1452
+ if (this.buffer.totalSize >= this.config.maxBufferSize) {
1453
+ return true;
1454
+ }
1455
+ if (this.buffer.totalSize >= this.config.maxBatchSize) {
1456
+ return true;
1457
+ }
1458
+ if (this.buffer.firstEventTime && this.buffer.totalSize > 0) {
1459
+ const elapsed = Date.now() - this.buffer.firstEventTime.getTime();
1460
+ if (elapsed >= this.config.maxBatchWaitMs) {
1461
+ return true;
1462
+ }
1463
+ }
1464
+ return false;
1465
+ }
1466
+ /**
1467
+ * Resets the buffer after successful flush
1468
+ */
1469
+ resetBuffer(completedSpansToCleanup = /* @__PURE__ */ new Set()) {
1470
+ this.buffer.creates = [];
1471
+ this.buffer.updates = [];
1472
+ this.buffer.insertOnly = [];
1473
+ this.buffer.seenSpans.clear();
1474
+ this.buffer.spanSequences.clear();
1475
+ this.buffer.completedSpans.clear();
1476
+ this.buffer.outOfOrderCount = 0;
1477
+ this.buffer.firstEventTime = void 0;
1478
+ this.buffer.totalSize = 0;
1479
+ for (const spanKey of completedSpansToCleanup) {
1480
+ this.allCreatedSpans.delete(spanKey);
1481
+ }
1482
+ }
1483
+ /**
1484
+ * Schedules a flush using setTimeout
1485
+ */
1486
+ scheduleFlush() {
1487
+ if (this.flushTimer) {
1488
+ clearTimeout(this.flushTimer);
1489
+ }
1490
+ this.flushTimer = setTimeout(() => {
1491
+ this.flush().catch((error) => {
1492
+ this.logger.error("Scheduled flush failed", {
1493
+ error: error instanceof Error ? error.message : String(error)
1494
+ });
1495
+ });
1496
+ }, this.config.maxBatchWaitMs);
1497
+ }
1498
+ /**
1499
+ * Serializes span attributes to storage record format
1500
+ * Handles all AI span types and their specific attributes
1501
+ */
1502
+ serializeAttributes(span) {
1503
+ if (!span.attributes) {
1504
+ return null;
1505
+ }
1506
+ try {
1507
+ return JSON.parse(
1508
+ JSON.stringify(span.attributes, (_key, value) => {
1509
+ if (value instanceof Date) {
1510
+ return value.toISOString();
1511
+ }
1512
+ if (typeof value === "object" && value !== null) {
1513
+ return value;
1514
+ }
1515
+ return value;
1516
+ })
1517
+ );
1518
+ } catch (error) {
1519
+ this.logger.warn("Failed to serialize span attributes, storing as null", {
1520
+ spanId: span.id,
1521
+ spanType: span.type,
1522
+ error: error instanceof Error ? error.message : String(error)
1523
+ });
1524
+ return null;
1525
+ }
1526
+ }
1527
+ buildCreateRecord(span) {
1528
+ return {
1529
+ traceId: span.traceId,
1530
+ spanId: span.id,
1531
+ parentSpanId: span.parentSpanId ?? null,
1532
+ name: span.name,
1533
+ scope: null,
1534
+ spanType: span.type,
1535
+ attributes: this.serializeAttributes(span),
1536
+ metadata: span.metadata ?? null,
1537
+ links: null,
1538
+ startedAt: span.startTime,
1539
+ endedAt: span.endTime ?? null,
1540
+ input: span.input,
1541
+ output: span.output,
1542
+ error: span.errorInfo,
1543
+ isEvent: span.isEvent
1544
+ };
1545
+ }
1546
+ buildUpdateRecord(span) {
1547
+ return {
1548
+ name: span.name,
1549
+ scope: null,
1550
+ attributes: this.serializeAttributes(span),
1551
+ metadata: span.metadata ?? null,
1552
+ links: null,
1553
+ endedAt: span.endTime ?? null,
1554
+ input: span.input,
1555
+ output: span.output,
1556
+ error: span.errorInfo
1557
+ };
1558
+ }
1559
+ /**
1560
+ * Handles realtime strategy - processes each event immediately
1561
+ */
1562
+ async handleRealtimeEvent(event, storage) {
1563
+ const span = event.exportedSpan;
1564
+ const spanKey = this.buildSpanKey(span.traceId, span.id);
1565
+ if (span.isEvent) {
1566
+ if (event.type === observability.AITracingEventType.SPAN_ENDED) {
1567
+ await storage.createAISpan(this.buildCreateRecord(event.exportedSpan));
1568
+ } else {
1569
+ this.logger.warn(`Tracing event type not implemented for event spans: ${event.type}`);
1570
+ }
1571
+ } else {
1572
+ switch (event.type) {
1573
+ case observability.AITracingEventType.SPAN_STARTED:
1574
+ await storage.createAISpan(this.buildCreateRecord(event.exportedSpan));
1575
+ this.allCreatedSpans.add(spanKey);
1576
+ break;
1577
+ case observability.AITracingEventType.SPAN_UPDATED:
1578
+ await storage.updateAISpan({
1579
+ traceId: span.traceId,
1580
+ spanId: span.id,
1581
+ updates: this.buildUpdateRecord(span)
1582
+ });
1583
+ break;
1584
+ case observability.AITracingEventType.SPAN_ENDED:
1585
+ await storage.updateAISpan({
1586
+ traceId: span.traceId,
1587
+ spanId: span.id,
1588
+ updates: this.buildUpdateRecord(span)
1589
+ });
1590
+ this.allCreatedSpans.delete(spanKey);
1591
+ break;
1592
+ default:
1593
+ this.logger.warn(`Tracing event type not implemented for span spans: ${event.type}`);
1594
+ }
1595
+ }
1596
+ }
1597
+ /**
1598
+ * Handles batch-with-updates strategy - buffers events and processes in batches
1599
+ */
1600
+ handleBatchWithUpdatesEvent(event) {
1601
+ this.addToBuffer(event);
1602
+ if (this.shouldFlush()) {
1603
+ this.flush().catch((error) => {
1604
+ this.logger.error("Batch flush failed", {
1605
+ error: error instanceof Error ? error.message : String(error)
1606
+ });
1607
+ });
1608
+ } else if (this.buffer.totalSize === 1) {
1609
+ this.scheduleFlush();
1610
+ }
1611
+ }
1612
+ /**
1613
+ * Handles insert-only strategy - only processes SPAN_ENDED events in batches
1614
+ */
1615
+ handleInsertOnlyEvent(event) {
1616
+ if (event.type === observability.AITracingEventType.SPAN_ENDED) {
1617
+ this.addToBuffer(event);
1618
+ if (this.shouldFlush()) {
1619
+ this.flush().catch((error) => {
1620
+ this.logger.error("Batch flush failed", {
1621
+ error: error instanceof Error ? error.message : String(error)
1622
+ });
1623
+ });
1624
+ } else if (this.buffer.totalSize === 1) {
1625
+ this.scheduleFlush();
1626
+ }
1627
+ }
1628
+ }
1629
+ /**
1630
+ * Calculates retry delay using exponential backoff
1631
+ */
1632
+ calculateRetryDelay(attempt) {
1633
+ return this.config.retryDelayMs * Math.pow(2, attempt);
1634
+ }
1635
+ /**
1636
+ * Flushes the current buffer to storage with retry logic
1637
+ */
1638
+ async flush() {
1639
+ if (!this.storage) {
1640
+ this.logger.debug("Cannot flush traces. Mastra storage is not initialized");
1641
+ return;
1642
+ }
1643
+ if (this.flushTimer) {
1644
+ clearTimeout(this.flushTimer);
1645
+ this.flushTimer = null;
1646
+ }
1647
+ if (this.buffer.totalSize === 0) {
1648
+ return;
1649
+ }
1650
+ const startTime = Date.now();
1651
+ const flushReason = this.buffer.totalSize >= this.config.maxBufferSize ? "overflow" : this.buffer.totalSize >= this.config.maxBatchSize ? "size" : "time";
1652
+ const bufferCopy = {
1653
+ creates: [...this.buffer.creates],
1654
+ updates: [...this.buffer.updates],
1655
+ insertOnly: [...this.buffer.insertOnly],
1656
+ seenSpans: new Set(this.buffer.seenSpans),
1657
+ spanSequences: new Map(this.buffer.spanSequences),
1658
+ completedSpans: new Set(this.buffer.completedSpans),
1659
+ outOfOrderCount: this.buffer.outOfOrderCount,
1660
+ firstEventTime: this.buffer.firstEventTime,
1661
+ totalSize: this.buffer.totalSize
1662
+ };
1663
+ this.resetBuffer();
1664
+ await this.flushWithRetries(this.storage, bufferCopy, 0);
1665
+ const elapsed = Date.now() - startTime;
1666
+ this.logger.debug("Batch flushed", {
1667
+ strategy: this.resolvedStrategy,
1668
+ batchSize: bufferCopy.totalSize,
1669
+ flushReason,
1670
+ durationMs: elapsed,
1671
+ outOfOrderCount: bufferCopy.outOfOrderCount > 0 ? bufferCopy.outOfOrderCount : void 0
1672
+ });
1673
+ }
1674
+ /**
1675
+ * Attempts to flush with exponential backoff retry logic
1676
+ */
1677
+ async flushWithRetries(storage, buffer, attempt) {
1678
+ try {
1679
+ if (this.resolvedStrategy === "batch-with-updates") {
1680
+ if (buffer.creates.length > 0) {
1681
+ await storage.batchCreateAISpans({ records: buffer.creates });
1682
+ }
1683
+ if (buffer.updates.length > 0) {
1684
+ const sortedUpdates = buffer.updates.sort((a, b) => {
1685
+ const spanCompare = this.buildSpanKey(a.traceId, a.spanId).localeCompare(
1686
+ this.buildSpanKey(b.traceId, b.spanId)
1687
+ );
1688
+ if (spanCompare !== 0) return spanCompare;
1689
+ return a.sequenceNumber - b.sequenceNumber;
1690
+ });
1691
+ await storage.batchUpdateAISpans({ records: sortedUpdates });
1692
+ }
1693
+ } else if (this.resolvedStrategy === "insert-only") {
1694
+ if (buffer.insertOnly.length > 0) {
1695
+ await storage.batchCreateAISpans({ records: buffer.insertOnly });
1696
+ }
1697
+ }
1698
+ for (const spanKey of buffer.completedSpans) {
1699
+ this.allCreatedSpans.delete(spanKey);
1700
+ }
1701
+ } catch (error) {
1702
+ if (attempt < this.config.maxRetries) {
1703
+ const retryDelay = this.calculateRetryDelay(attempt);
1704
+ this.logger.warn("Batch flush failed, retrying", {
1705
+ attempt: attempt + 1,
1706
+ maxRetries: this.config.maxRetries,
1707
+ nextRetryInMs: retryDelay,
1708
+ error: error instanceof Error ? error.message : String(error)
1709
+ });
1710
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
1711
+ return this.flushWithRetries(storage, buffer, attempt + 1);
1712
+ } else {
1713
+ this.logger.error("Batch flush failed after all retries, dropping batch", {
1714
+ finalAttempt: attempt + 1,
1715
+ maxRetries: this.config.maxRetries,
1716
+ droppedBatchSize: buffer.totalSize,
1717
+ error: error instanceof Error ? error.message : String(error)
1718
+ });
1719
+ for (const spanKey of buffer.completedSpans) {
1720
+ this.allCreatedSpans.delete(spanKey);
1721
+ }
1722
+ }
1723
+ }
1724
+ }
1725
+ async exportEvent(event) {
1726
+ if (!this.storage) {
1727
+ this.logger.debug("Cannot store traces. Mastra storage is not initialized");
1728
+ return;
1729
+ }
1730
+ if (!this.strategyInitialized) {
1731
+ this.initializeStrategy(this.storage);
1732
+ }
1733
+ switch (this.resolvedStrategy) {
1734
+ case "realtime":
1735
+ await this.handleRealtimeEvent(event, this.storage);
1736
+ break;
1737
+ case "batch-with-updates":
1738
+ this.handleBatchWithUpdatesEvent(event);
1739
+ break;
1740
+ case "insert-only":
1741
+ this.handleInsertOnlyEvent(event);
1742
+ break;
1743
+ }
1744
+ }
1745
+ async shutdown() {
1746
+ if (this.flushTimer) {
1747
+ clearTimeout(this.flushTimer);
1748
+ this.flushTimer = null;
1749
+ }
1750
+ if (this.buffer.totalSize > 0) {
1751
+ this.logger.info("Flushing remaining events on shutdown", {
1752
+ remainingEvents: this.buffer.totalSize
1753
+ });
1754
+ try {
1755
+ await this.flush();
1756
+ } catch (error) {
1757
+ this.logger.error("Failed to flush remaining events during shutdown", {
1758
+ error: error instanceof Error ? error.message : String(error)
1759
+ });
1760
+ }
1761
+ }
1762
+ this.logger.info("DefaultExporter shutdown complete");
1763
+ }
1764
+ };
1765
+
1766
+ // src/span_processors/sensitive-data-filter.ts
1767
+ var SensitiveDataFilter = class {
1768
+ name = "sensitive-data-filter";
1769
+ sensitiveFields;
1770
+ redactionToken;
1771
+ redactionStyle;
1772
+ constructor(options = {}) {
1773
+ this.sensitiveFields = (options.sensitiveFields || [
1774
+ "password",
1775
+ "token",
1776
+ "secret",
1777
+ "key",
1778
+ "apikey",
1779
+ "auth",
1780
+ "authorization",
1781
+ "bearer",
1782
+ "bearertoken",
1783
+ "jwt",
1784
+ "credential",
1785
+ "clientsecret",
1786
+ "privatekey",
1787
+ "refresh",
1788
+ "ssn"
1789
+ ]).map((f) => this.normalizeKey(f));
1790
+ this.redactionToken = options.redactionToken ?? "[REDACTED]";
1791
+ this.redactionStyle = options.redactionStyle ?? "full";
1792
+ }
1793
+ /**
1794
+ * Process a span by filtering sensitive data across its key fields.
1795
+ * Fields processed: attributes, metadata, input, output, errorInfo.
1796
+ *
1797
+ * @param span - The input span to filter
1798
+ * @returns A new span with sensitive values redacted
1799
+ */
1800
+ process(span) {
1801
+ span.attributes = this.tryFilter(span.attributes);
1802
+ span.metadata = this.tryFilter(span.metadata);
1803
+ span.input = this.tryFilter(span.input);
1804
+ span.output = this.tryFilter(span.output);
1805
+ span.errorInfo = this.tryFilter(span.errorInfo);
1806
+ return span;
1807
+ }
1808
+ /**
1809
+ * Recursively filter objects/arrays for sensitive keys.
1810
+ * Handles circular references by replacing with a marker.
1811
+ */
1812
+ deepFilter(obj, seen = /* @__PURE__ */ new WeakSet()) {
1813
+ if (obj === null || typeof obj !== "object") {
1814
+ return obj;
1815
+ }
1816
+ if (seen.has(obj)) {
1817
+ return "[Circular Reference]";
1818
+ }
1819
+ seen.add(obj);
1820
+ if (Array.isArray(obj)) {
1821
+ return obj.map((item) => this.deepFilter(item, seen));
1822
+ }
1823
+ const filtered = {};
1824
+ for (const key of Object.keys(obj)) {
1825
+ const normKey = this.normalizeKey(key);
1826
+ if (this.isSensitive(normKey)) {
1827
+ if (obj[key] && typeof obj[key] === "object") {
1828
+ filtered[key] = this.deepFilter(obj[key], seen);
1829
+ } else {
1830
+ filtered[key] = this.redactValue(obj[key]);
1831
+ }
1832
+ } else {
1833
+ filtered[key] = this.deepFilter(obj[key], seen);
1834
+ }
1835
+ }
1836
+ return filtered;
1837
+ }
1838
+ tryFilter(value) {
1839
+ try {
1840
+ return this.deepFilter(value);
1841
+ } catch {
1842
+ return { error: { processor: this.name } };
1843
+ }
1844
+ }
1845
+ /**
1846
+ * Normalize keys by lowercasing and stripping non-alphanumeric characters.
1847
+ * Ensures consistent matching for variants like "api-key", "api_key", "Api Key".
1848
+ */
1849
+ normalizeKey(key) {
1850
+ return key.toLowerCase().replace(/[^a-z0-9]/g, "");
1851
+ }
1852
+ /**
1853
+ * Check whether a normalized key exactly matches any sensitive field.
1854
+ * Both key and sensitive fields are normalized by removing all non-alphanumeric
1855
+ * characters and converting to lowercase before comparison.
1856
+ *
1857
+ * Examples:
1858
+ * - "api_key", "api-key", "ApiKey" all normalize to "apikey" → MATCHES "apikey"
1859
+ * - "promptTokens", "prompt_tokens" normalize to "prompttokens" → DOES NOT MATCH "token"
1860
+ */
1861
+ isSensitive(normalizedKey) {
1862
+ return this.sensitiveFields.some((sensitiveField) => {
1863
+ return normalizedKey === sensitiveField;
1864
+ });
1865
+ }
1866
+ /**
1867
+ * Redact a sensitive value.
1868
+ * - Full style: replaces with a fixed token.
1869
+ * - Partial style: shows 3 chars at start and end, hides the middle.
1870
+ *
1871
+ * Non-string values are converted to strings before partial redaction.
1872
+ */
1873
+ redactValue(value) {
1874
+ if (this.redactionStyle === "full") {
1875
+ return this.redactionToken;
1876
+ }
1877
+ const str = String(value);
1878
+ const len = str.length;
1879
+ if (len <= 6) {
1880
+ return this.redactionToken;
1881
+ }
1882
+ return str.slice(0, 3) + "\u2026" + str.slice(len - 3);
1883
+ }
1884
+ async shutdown() {
1885
+ }
1886
+ };
1887
+ var AITracingRegistry = class {
1888
+ instances = /* @__PURE__ */ new Map();
1889
+ defaultInstance;
1890
+ configSelector;
1891
+ /**
1892
+ * Register a tracing instance
1893
+ */
1894
+ register(name, instance, isDefault = false) {
1895
+ if (this.instances.has(name)) {
1896
+ throw new Error(`AI Tracing instance '${name}' already registered`);
1897
+ }
1898
+ this.instances.set(name, instance);
1899
+ if (isDefault || !this.defaultInstance) {
1900
+ this.defaultInstance = instance;
1901
+ }
1902
+ }
1903
+ /**
1904
+ * Get a tracing instance by name
1905
+ */
1906
+ get(name) {
1907
+ return this.instances.get(name);
1908
+ }
1909
+ /**
1910
+ * Get the default tracing instance
1911
+ */
1912
+ getDefault() {
1913
+ return this.defaultInstance;
1914
+ }
1915
+ /**
1916
+ * Set the tracing selector function
1917
+ */
1918
+ setSelector(selector) {
1919
+ this.configSelector = selector;
1920
+ }
1921
+ /**
1922
+ * Get the selected tracing instance based on context
1923
+ */
1924
+ getSelected(options) {
1925
+ if (this.configSelector) {
1926
+ const selected = this.configSelector(options, this.instances);
1927
+ if (selected && this.instances.has(selected)) {
1928
+ return this.instances.get(selected);
1929
+ }
1930
+ }
1931
+ return this.defaultInstance;
1932
+ }
1933
+ /**
1934
+ * Unregister a tracing instance
1935
+ */
1936
+ unregister(name) {
1937
+ return this.instances.delete(name);
1938
+ }
1939
+ /**
1940
+ * Shutdown all instances and clear the registry
1941
+ */
1942
+ async shutdown() {
1943
+ const shutdownPromises = Array.from(this.instances.values()).map((instance) => instance.shutdown());
1944
+ await Promise.allSettled(shutdownPromises);
1945
+ this.instances.clear();
1946
+ }
1947
+ /**
1948
+ * Clear all instances without shutdown
1949
+ */
1950
+ clear() {
1951
+ this.instances.clear();
1952
+ this.defaultInstance = void 0;
1953
+ this.configSelector = void 0;
1954
+ }
1955
+ /**
1956
+ * Get all registered instances
1957
+ */
1958
+ getAll() {
1959
+ return new Map(this.instances);
1960
+ }
1961
+ };
1962
+ var aiTracingRegistry = new AITracingRegistry();
1963
+ function registerAITracing(name, instance, isDefault = false) {
1964
+ aiTracingRegistry.register(name, instance, isDefault);
1965
+ }
1966
+ function getAITracing(name) {
1967
+ return aiTracingRegistry.get(name);
1968
+ }
1969
+ function getDefaultAITracing() {
1970
+ return aiTracingRegistry.getDefault();
1971
+ }
1972
+ function setSelector(selector) {
1973
+ aiTracingRegistry.setSelector(selector);
1974
+ }
1975
+ function getSelectedAITracing(options) {
1976
+ return aiTracingRegistry.getSelected(options);
1977
+ }
1978
+ function unregisterAITracing(name) {
1979
+ return aiTracingRegistry.unregister(name);
1980
+ }
1981
+ async function shutdownAITracingRegistry() {
1982
+ await aiTracingRegistry.shutdown();
1983
+ }
1984
+ function clearAITracingRegistry() {
1985
+ aiTracingRegistry.clear();
1986
+ }
1987
+ function getAllAITracing() {
1988
+ return aiTracingRegistry.getAll();
1989
+ }
1990
+ function hasAITracing(name) {
1991
+ const tracing = getAITracing(name);
1992
+ if (!tracing) return false;
1993
+ const config = tracing.getConfig();
1994
+ const sampling = config.sampling;
1995
+ return sampling.type !== observability.SamplingStrategyType.NEVER;
1996
+ }
1997
+ function isAITracingInstance(obj) {
1998
+ return obj instanceof BaseAITracing;
1999
+ }
2000
+ function setupAITracingRegistry(config) {
2001
+ if (!config) {
2002
+ return;
2003
+ }
2004
+ if (config.default?.enabled && config.configs?.["default"]) {
2005
+ throw new Error(
2006
+ "Cannot use 'default' as a custom config name when default tracing is enabled. Please rename your custom config to avoid conflicts."
2007
+ );
2008
+ }
2009
+ if (config.default?.enabled) {
2010
+ const defaultInstance = new DefaultAITracing({
2011
+ serviceName: "mastra",
2012
+ name: "default",
2013
+ sampling: { type: observability.SamplingStrategyType.ALWAYS },
2014
+ exporters: [new DefaultExporter(), new CloudExporter()],
2015
+ processors: [new SensitiveDataFilter()]
2016
+ });
2017
+ registerAITracing("default", defaultInstance, true);
2018
+ }
2019
+ if (config.configs) {
2020
+ const instances = Object.entries(config.configs);
2021
+ instances.forEach(([name, tracingDef], index) => {
2022
+ const instance = isAITracingInstance(tracingDef) ? tracingDef : new DefaultAITracing({ ...tracingDef, name });
2023
+ const isDefault = !config.default?.enabled && index === 0;
2024
+ registerAITracing(name, instance, isDefault);
2025
+ });
2026
+ }
2027
+ if (config.configSelector) {
2028
+ setSelector(config.configSelector);
2029
+ }
2030
+ }
2031
+
2032
+ exports.BaseAISpan = BaseAISpan;
2033
+ exports.BaseAITracing = BaseAITracing;
2034
+ exports.BaseExporter = BaseExporter;
2035
+ exports.CloudExporter = CloudExporter;
2036
+ exports.ConsoleExporter = ConsoleExporter;
2037
+ exports.DefaultAISpan = DefaultAISpan;
2038
+ exports.DefaultAITracing = DefaultAITracing;
2039
+ exports.DefaultExporter = DefaultExporter;
2040
+ exports.ModelSpanTracker = ModelSpanTracker;
2041
+ exports.NoOpAISpan = NoOpAISpan;
2042
+ exports.SensitiveDataFilter = SensitiveDataFilter;
2043
+ exports.clearAITracingRegistry = clearAITracingRegistry;
2044
+ exports.deepClean = deepClean;
2045
+ exports.getAITracing = getAITracing;
2046
+ exports.getAllAITracing = getAllAITracing;
2047
+ exports.getDefaultAITracing = getDefaultAITracing;
2048
+ exports.getSelectedAITracing = getSelectedAITracing;
2049
+ exports.hasAITracing = hasAITracing;
2050
+ exports.registerAITracing = registerAITracing;
2051
+ exports.setSelector = setSelector;
2052
+ exports.setupAITracingRegistry = setupAITracingRegistry;
2053
+ exports.shutdownAITracingRegistry = shutdownAITracingRegistry;
2054
+ exports.unregisterAITracing = unregisterAITracing;
3
2055
  //# sourceMappingURL=index.cjs.map
4
2056
  //# sourceMappingURL=index.cjs.map