@mastra/laminar 1.3.5 → 1.3.6

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
@@ -1,516 +1,449 @@
1
- import { TracingEventType, SpanType } from '@mastra/core/observability';
2
- import { BaseExporter } from '@mastra/observability';
3
- import { TraceFlags, SpanStatusCode, SpanKind } from '@opentelemetry/api';
4
- import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
5
- import { resourceFromAttributes } from '@opentelemetry/resources';
6
- import { SimpleSpanProcessor, BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
7
- import { ATTR_TELEMETRY_SDK_LANGUAGE, ATTR_TELEMETRY_SDK_VERSION, ATTR_TELEMETRY_SDK_NAME, ATTR_SERVICE_VERSION, ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
8
-
9
- // src/tracing.ts
10
- var LMNR_SPAN_INPUT = "lmnr.span.input";
11
- var LMNR_SPAN_OUTPUT = "lmnr.span.output";
12
- var LMNR_SPAN_TYPE = "lmnr.span.type";
13
- var LMNR_SPAN_PATH = "lmnr.span.path";
14
- var LMNR_SPAN_IDS_PATH = "lmnr.span.ids_path";
15
- var LMNR_SPAN_INSTRUMENTATION_SOURCE = "lmnr.span.instrumentation_source";
16
- var LMNR_SPAN_SDK_VERSION = "lmnr.span.sdk_version";
17
- var LMNR_SPAN_LANGUAGE_VERSION = "lmnr.span.language_version";
18
- var LMNR_ASSOCIATION_PREFIX = "lmnr.association.properties";
19
- var LMNR_SESSION_ID = `${LMNR_ASSOCIATION_PREFIX}.session_id`;
20
- var LMNR_USER_ID = `${LMNR_ASSOCIATION_PREFIX}.user_id`;
21
- var LMNR_TAGS = `${LMNR_ASSOCIATION_PREFIX}.tags`;
22
- var GEN_AI_SYSTEM = "gen_ai.system";
23
- var GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
24
- var GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
25
- var GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
26
- var GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
27
- var GEN_AI_CACHE_WRITE_INPUT_TOKENS = "gen_ai.usage.cache_creation_input_tokens";
28
- var GEN_AI_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read_input_tokens";
1
+ import { SpanType, TracingEventType } from "@mastra/core/observability";
2
+ import { BaseExporter } from "@mastra/observability";
3
+ import { SpanKind, SpanStatusCode, TraceFlags } from "@opentelemetry/api";
4
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
5
+ import { resourceFromAttributes } from "@opentelemetry/resources";
6
+ import { BatchSpanProcessor, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
7
+ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, ATTR_TELEMETRY_SDK_LANGUAGE, ATTR_TELEMETRY_SDK_NAME, ATTR_TELEMETRY_SDK_VERSION } from "@opentelemetry/semantic-conventions";
8
+ //#region src/tracing.ts
9
+ const LMNR_SPAN_INPUT = "lmnr.span.input";
10
+ const LMNR_SPAN_OUTPUT = "lmnr.span.output";
11
+ const LMNR_SPAN_TYPE = "lmnr.span.type";
12
+ const LMNR_SPAN_PATH = "lmnr.span.path";
13
+ const LMNR_SPAN_IDS_PATH = "lmnr.span.ids_path";
14
+ const LMNR_SPAN_INSTRUMENTATION_SOURCE = "lmnr.span.instrumentation_source";
15
+ const LMNR_SPAN_SDK_VERSION = "lmnr.span.sdk_version";
16
+ const LMNR_SPAN_LANGUAGE_VERSION = "lmnr.span.language_version";
17
+ const LMNR_ASSOCIATION_PREFIX = "lmnr.association.properties";
18
+ const LMNR_SESSION_ID = `${LMNR_ASSOCIATION_PREFIX}.session_id`;
19
+ const LMNR_USER_ID = `${LMNR_ASSOCIATION_PREFIX}.user_id`;
20
+ const LMNR_TAGS = `${LMNR_ASSOCIATION_PREFIX}.tags`;
21
+ const GEN_AI_SYSTEM = "gen_ai.system";
22
+ const GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
23
+ const GEN_AI_RESPONSE_MODEL = "gen_ai.response.model";
24
+ const GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens";
25
+ const GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens";
26
+ const GEN_AI_CACHE_WRITE_INPUT_TOKENS = "gen_ai.usage.cache_creation_input_tokens";
27
+ const GEN_AI_CACHE_READ_INPUT_TOKENS = "gen_ai.usage.cache_read_input_tokens";
29
28
  var LaminarExporter = class extends BaseExporter {
30
- name = "laminar";
31
- config;
32
- traceMap = /* @__PURE__ */ new Map();
33
- resource;
34
- scope;
35
- processor;
36
- exporter;
37
- isSetup = false;
38
- constructor(config = {}) {
39
- super(config);
40
- const apiKey = config.apiKey ?? process.env.LMNR_PROJECT_API_KEY;
41
- if (!apiKey) {
42
- this.setDisabled(
43
- "Missing required API key. Set LMNR_PROJECT_API_KEY environment variable or pass apiKey in config."
44
- );
45
- this.config = null;
46
- return;
47
- }
48
- const envEndpoint = process.env.LAMINAR_ENDPOINT;
49
- const baseUrl = stripTrailingSlash(config.baseUrl ?? process.env.LMNR_BASE_URL ?? "https://api.lmnr.ai");
50
- const endpoint = config.endpoint ?? envEndpoint ?? `${baseUrl}/v1/traces`;
51
- const headers = {
52
- ...config.headers,
53
- Authorization: `Bearer ${apiKey}`
54
- };
55
- this.config = {
56
- apiKey,
57
- baseUrl,
58
- endpoint,
59
- headers,
60
- realtime: config.realtime ?? false,
61
- disableBatch: config.disableBatch ?? false,
62
- batchSize: config.batchSize ?? 512,
63
- timeoutMillis: config.timeoutMillis ?? 3e4
64
- };
65
- }
66
- init(options) {
67
- const serviceName = options.config?.serviceName || "mastra-service";
68
- this.resource = resourceFromAttributes({
69
- [ATTR_SERVICE_NAME]: serviceName,
70
- [ATTR_SERVICE_VERSION]: "unknown",
71
- [ATTR_TELEMETRY_SDK_NAME]: "@mastra/laminar",
72
- [ATTR_TELEMETRY_SDK_VERSION]: "unknown",
73
- [ATTR_TELEMETRY_SDK_LANGUAGE]: "nodejs"
74
- });
75
- this.scope = {
76
- name: "@mastra/laminar",
77
- version: "unknown"
78
- };
79
- }
80
- async _exportTracingEvent(event) {
81
- if (event.type === TracingEventType.SPAN_STARTED && !event.exportedSpan.isEvent) {
82
- this.handleSpanStarted(event.exportedSpan);
83
- return;
84
- }
85
- if (event.type !== TracingEventType.SPAN_ENDED) {
86
- return;
87
- }
88
- await this.handleSpanEnded(event.exportedSpan);
89
- }
90
- handleSpanStarted(span) {
91
- const traceState = this.getOrCreateTraceState(span.traceId);
92
- const name = span.name;
93
- const parentId = span.parentSpanId;
94
- const parentPath = parentId ? traceState.spanPathById.get(parentId) : void 0;
95
- const parentIdsPath = parentId ? traceState.spanIdsPathById.get(parentId) : void 0;
96
- const spanPath = parentPath ? [...parentPath, name] : [name];
97
- const spanIdsPath = parentIdsPath ? [...parentIdsPath, otelSpanIdToUUID(span.id)] : [otelSpanIdToUUID(span.id)];
98
- traceState.spanPathById.set(span.id, spanPath);
99
- traceState.spanIdsPathById.set(span.id, spanIdsPath);
100
- traceState.activeSpanIds.add(span.id);
101
- }
102
- async handleSpanEnded(span) {
103
- if (!this.config) {
104
- return;
105
- }
106
- await this.setupIfNeeded();
107
- if (!this.processor || !this.exporter) {
108
- return;
109
- }
110
- const traceState = this.getOrCreateTraceState(span.traceId);
111
- if (!traceState.spanPathById.has(span.id) || !traceState.spanIdsPathById.has(span.id)) {
112
- const name = span.name;
113
- const parentId = span.parentSpanId;
114
- const parentPath = parentId ? traceState.spanPathById.get(parentId) : void 0;
115
- const parentIdsPath = parentId ? traceState.spanIdsPathById.get(parentId) : void 0;
116
- const spanPath = parentPath ? [...parentPath, name] : [name];
117
- const spanIdsPath = parentIdsPath ? [...parentIdsPath, otelSpanIdToUUID(span.id)] : [otelSpanIdToUUID(span.id)];
118
- traceState.spanPathById.set(span.id, spanPath);
119
- traceState.spanIdsPathById.set(span.id, spanIdsPath);
120
- }
121
- try {
122
- const otelSpan = this.convertSpanToOtel(span, traceState);
123
- this.processor.onEnd(otelSpan);
124
- if (this.config.realtime) {
125
- await this.processor.forceFlush();
126
- }
127
- } catch (error) {
128
- this.logger.error("[LaminarExporter] Failed to export span", { error, spanId: span.id, traceId: span.traceId });
129
- } finally {
130
- traceState.activeSpanIds.delete(span.id);
131
- if (traceState.activeSpanIds.size === 0) {
132
- this.traceMap.delete(span.traceId);
133
- }
134
- }
135
- }
136
- getOrCreateTraceState(traceId) {
137
- const existing = this.traceMap.get(traceId);
138
- if (existing) return existing;
139
- const created = {
140
- spanPathById: /* @__PURE__ */ new Map(),
141
- spanIdsPathById: /* @__PURE__ */ new Map(),
142
- activeSpanIds: /* @__PURE__ */ new Set()
143
- };
144
- this.traceMap.set(traceId, created);
145
- return created;
146
- }
147
- convertSpanToOtel(span, traceState) {
148
- if (!this.resource || !this.scope) {
149
- this.resource = resourceFromAttributes({
150
- [ATTR_SERVICE_NAME]: "mastra-service",
151
- [ATTR_SERVICE_VERSION]: "unknown",
152
- [ATTR_TELEMETRY_SDK_NAME]: "@mastra/laminar",
153
- [ATTR_TELEMETRY_SDK_VERSION]: "unknown",
154
- [ATTR_TELEMETRY_SDK_LANGUAGE]: "nodejs"
155
- });
156
- this.scope = { name: "@mastra/laminar", version: "unknown" };
157
- }
158
- const name = span.name;
159
- const kind = getSpanKind(span.type);
160
- const startTime = dateToHrTime(span.startTime);
161
- const endTime = span.endTime ? dateToHrTime(span.endTime) : startTime;
162
- const duration = computeDuration(span.startTime, span.endTime);
163
- const { status, events } = buildStatusAndEvents(span, startTime);
164
- const traceId = normalizeTraceId(span.traceId);
165
- const spanId = normalizeSpanId(span.id);
166
- const spanContext = {
167
- traceId,
168
- spanId,
169
- traceFlags: TraceFlags.SAMPLED,
170
- isRemote: false
171
- };
172
- const parentSpanContext = span.parentSpanId ? {
173
- traceId,
174
- spanId: normalizeSpanId(span.parentSpanId),
175
- traceFlags: TraceFlags.SAMPLED,
176
- isRemote: false
177
- } : void 0;
178
- const attributes = buildLaminarAttributes(span, traceState);
179
- const links = [];
180
- const readable = {
181
- name,
182
- kind,
183
- spanContext: () => spanContext,
184
- parentSpanContext,
185
- startTime,
186
- endTime,
187
- status,
188
- attributes,
189
- links,
190
- events,
191
- duration,
192
- ended: true,
193
- resource: this.resource,
194
- instrumentationScope: this.scope,
195
- droppedAttributesCount: 0,
196
- droppedEventsCount: 0,
197
- droppedLinksCount: 0
198
- };
199
- return readable;
200
- }
201
- async setupIfNeeded() {
202
- if (this.isSetup || !this.config) {
203
- return;
204
- }
205
- this.exporter = new OTLPTraceExporter({
206
- url: this.config.endpoint,
207
- headers: this.config.headers,
208
- timeoutMillis: this.config.timeoutMillis
209
- });
210
- this.processor = this.config.disableBatch ? new SimpleSpanProcessor(this.exporter) : new BatchSpanProcessor(this.exporter, {
211
- maxExportBatchSize: this.config.batchSize,
212
- exportTimeoutMillis: this.config.timeoutMillis
213
- });
214
- this.isSetup = true;
215
- }
216
- async submitScore(args) {
217
- if (!this.config) return;
218
- const { traceId, spanId, name, score, reason, metadata } = args;
219
- const payload = {
220
- name,
221
- score,
222
- source: "Code",
223
- metadata: { ...metadata ?? {}, ...reason ? { reason } : {} }
224
- };
225
- if (spanId) {
226
- payload.spanId = otelSpanIdToUUID(spanId);
227
- } else {
228
- payload.traceId = otelTraceIdToUUID(traceId);
229
- }
230
- const headers = {
231
- Authorization: `Bearer ${this.config.apiKey}`,
232
- "content-type": "application/json"
233
- };
234
- const controller = new AbortController();
235
- const timer = setTimeout(() => controller.abort(), this.config.timeoutMillis);
236
- try {
237
- const response = await fetch(`${stripTrailingSlash(this.config.baseUrl)}/v1/evaluators/score`, {
238
- method: "POST",
239
- headers,
240
- body: JSON.stringify(payload),
241
- signal: controller.signal
242
- });
243
- if (!response.ok) {
244
- this.logger.warn("[LaminarExporter] Failed to attach score to trace/span", {
245
- status: response.status,
246
- statusText: response.statusText,
247
- traceId,
248
- spanId,
249
- name
250
- });
251
- }
252
- } catch (error) {
253
- const isAbort = error instanceof Error && error.name === "AbortError";
254
- this.logger.error("[LaminarExporter] Error attaching score to trace/span", {
255
- error,
256
- timedOut: isAbort,
257
- timeoutMillis: this.config.timeoutMillis,
258
- traceId,
259
- spanId,
260
- name
261
- });
262
- } finally {
263
- clearTimeout(timer);
264
- }
265
- }
266
- async onScoreEvent(event) {
267
- const { score } = event;
268
- if (!score.traceId) return;
269
- await this.submitScore({
270
- traceId: score.traceId,
271
- spanId: score.spanId,
272
- name: score.scorerName ?? score.scorerId,
273
- score: score.score,
274
- reason: score.reason,
275
- metadata: score.metadata
276
- });
277
- }
278
- /**
279
- * @deprecated Use the observability score event pipeline (`mastra.observability.addScore`)
280
- * instead. Preserved for backwards compatibility; forwards to the same Laminar score endpoint
281
- * as `onScoreEvent`.
282
- */
283
- async _addScoreToTrace(args) {
284
- await this.submitScore({
285
- traceId: args.traceId,
286
- spanId: args.spanId,
287
- name: args.scorerName,
288
- score: args.score,
289
- reason: args.reason,
290
- metadata: args.metadata
291
- });
292
- }
293
- /**
294
- * Force flush any buffered spans without shutting down the exporter.
295
- * This is useful in serverless environments where you need to ensure spans
296
- * are exported before the runtime instance is terminated.
297
- */
298
- async flush() {
299
- if (this.isDisabled || !this.processor) return;
300
- try {
301
- await this.processor.forceFlush();
302
- this.logger.debug("[LaminarExporter] Flushed pending spans");
303
- } catch (error) {
304
- this.logger.error("[LaminarExporter] Error flushing spans", { error });
305
- }
306
- }
307
- async shutdown() {
308
- try {
309
- await this.processor?.shutdown();
310
- } finally {
311
- this.traceMap.clear();
312
- await super.shutdown();
313
- }
314
- }
29
+ name = "laminar";
30
+ config;
31
+ traceMap = /* @__PURE__ */ new Map();
32
+ resource;
33
+ scope;
34
+ processor;
35
+ exporter;
36
+ isSetup = false;
37
+ constructor(config = {}) {
38
+ super(config);
39
+ const apiKey = config.apiKey ?? process.env.LMNR_PROJECT_API_KEY;
40
+ if (!apiKey) {
41
+ this.setDisabled("Missing required API key. Set LMNR_PROJECT_API_KEY environment variable or pass apiKey in config.");
42
+ this.config = null;
43
+ return;
44
+ }
45
+ const envEndpoint = process.env.LAMINAR_ENDPOINT;
46
+ const baseUrl = stripTrailingSlash(config.baseUrl ?? process.env.LMNR_BASE_URL ?? "https://api.lmnr.ai");
47
+ const endpoint = config.endpoint ?? envEndpoint ?? `${baseUrl}/v1/traces`;
48
+ const headers = {
49
+ ...config.headers,
50
+ Authorization: `Bearer ${apiKey}`
51
+ };
52
+ this.config = {
53
+ apiKey,
54
+ baseUrl,
55
+ endpoint,
56
+ headers,
57
+ realtime: config.realtime ?? false,
58
+ disableBatch: config.disableBatch ?? false,
59
+ batchSize: config.batchSize ?? 512,
60
+ timeoutMillis: config.timeoutMillis ?? 3e4
61
+ };
62
+ }
63
+ init(options) {
64
+ const serviceName = options.config?.serviceName || "mastra-service";
65
+ this.resource = resourceFromAttributes({
66
+ [ATTR_SERVICE_NAME]: serviceName,
67
+ [ATTR_SERVICE_VERSION]: "unknown",
68
+ [ATTR_TELEMETRY_SDK_NAME]: "@mastra/laminar",
69
+ [ATTR_TELEMETRY_SDK_VERSION]: "unknown",
70
+ [ATTR_TELEMETRY_SDK_LANGUAGE]: "nodejs"
71
+ });
72
+ this.scope = {
73
+ name: "@mastra/laminar",
74
+ version: "unknown"
75
+ };
76
+ }
77
+ async _exportTracingEvent(event) {
78
+ if (event.type === TracingEventType.SPAN_STARTED && !event.exportedSpan.isEvent) {
79
+ this.handleSpanStarted(event.exportedSpan);
80
+ return;
81
+ }
82
+ if (event.type !== TracingEventType.SPAN_ENDED) return;
83
+ await this.handleSpanEnded(event.exportedSpan);
84
+ }
85
+ handleSpanStarted(span) {
86
+ const traceState = this.getOrCreateTraceState(span.traceId);
87
+ const name = span.name;
88
+ const parentId = span.parentSpanId;
89
+ const parentPath = parentId ? traceState.spanPathById.get(parentId) : void 0;
90
+ const parentIdsPath = parentId ? traceState.spanIdsPathById.get(parentId) : void 0;
91
+ const spanPath = parentPath ? [...parentPath, name] : [name];
92
+ const spanIdsPath = parentIdsPath ? [...parentIdsPath, otelSpanIdToUUID(span.id)] : [otelSpanIdToUUID(span.id)];
93
+ traceState.spanPathById.set(span.id, spanPath);
94
+ traceState.spanIdsPathById.set(span.id, spanIdsPath);
95
+ traceState.activeSpanIds.add(span.id);
96
+ }
97
+ async handleSpanEnded(span) {
98
+ if (!this.config) return;
99
+ await this.setupIfNeeded();
100
+ if (!this.processor || !this.exporter) return;
101
+ const traceState = this.getOrCreateTraceState(span.traceId);
102
+ if (!traceState.spanPathById.has(span.id) || !traceState.spanIdsPathById.has(span.id)) {
103
+ const name = span.name;
104
+ const parentId = span.parentSpanId;
105
+ const parentPath = parentId ? traceState.spanPathById.get(parentId) : void 0;
106
+ const parentIdsPath = parentId ? traceState.spanIdsPathById.get(parentId) : void 0;
107
+ const spanPath = parentPath ? [...parentPath, name] : [name];
108
+ const spanIdsPath = parentIdsPath ? [...parentIdsPath, otelSpanIdToUUID(span.id)] : [otelSpanIdToUUID(span.id)];
109
+ traceState.spanPathById.set(span.id, spanPath);
110
+ traceState.spanIdsPathById.set(span.id, spanIdsPath);
111
+ }
112
+ try {
113
+ const otelSpan = this.convertSpanToOtel(span, traceState);
114
+ this.processor.onEnd(otelSpan);
115
+ if (this.config.realtime) await this.processor.forceFlush();
116
+ } catch (error) {
117
+ this.logger.error("[LaminarExporter] Failed to export span", {
118
+ error,
119
+ spanId: span.id,
120
+ traceId: span.traceId
121
+ });
122
+ } finally {
123
+ traceState.activeSpanIds.delete(span.id);
124
+ if (traceState.activeSpanIds.size === 0) this.traceMap.delete(span.traceId);
125
+ }
126
+ }
127
+ getOrCreateTraceState(traceId) {
128
+ const existing = this.traceMap.get(traceId);
129
+ if (existing) return existing;
130
+ const created = {
131
+ spanPathById: /* @__PURE__ */ new Map(),
132
+ spanIdsPathById: /* @__PURE__ */ new Map(),
133
+ activeSpanIds: /* @__PURE__ */ new Set()
134
+ };
135
+ this.traceMap.set(traceId, created);
136
+ return created;
137
+ }
138
+ convertSpanToOtel(span, traceState) {
139
+ if (!this.resource || !this.scope) {
140
+ this.resource = resourceFromAttributes({
141
+ [ATTR_SERVICE_NAME]: "mastra-service",
142
+ [ATTR_SERVICE_VERSION]: "unknown",
143
+ [ATTR_TELEMETRY_SDK_NAME]: "@mastra/laminar",
144
+ [ATTR_TELEMETRY_SDK_VERSION]: "unknown",
145
+ [ATTR_TELEMETRY_SDK_LANGUAGE]: "nodejs"
146
+ });
147
+ this.scope = {
148
+ name: "@mastra/laminar",
149
+ version: "unknown"
150
+ };
151
+ }
152
+ const name = span.name;
153
+ const kind = getSpanKind(span.type);
154
+ const startTime = dateToHrTime(span.startTime);
155
+ const endTime = span.endTime ? dateToHrTime(span.endTime) : startTime;
156
+ const duration = computeDuration(span.startTime, span.endTime);
157
+ const { status, events } = buildStatusAndEvents(span, startTime);
158
+ const traceId = normalizeTraceId(span.traceId);
159
+ const spanContext = {
160
+ traceId,
161
+ spanId: normalizeSpanId(span.id),
162
+ traceFlags: TraceFlags.SAMPLED,
163
+ isRemote: false
164
+ };
165
+ return {
166
+ name,
167
+ kind,
168
+ spanContext: () => spanContext,
169
+ parentSpanContext: span.parentSpanId ? {
170
+ traceId,
171
+ spanId: normalizeSpanId(span.parentSpanId),
172
+ traceFlags: TraceFlags.SAMPLED,
173
+ isRemote: false
174
+ } : void 0,
175
+ startTime,
176
+ endTime,
177
+ status,
178
+ attributes: buildLaminarAttributes(span, traceState),
179
+ links: [],
180
+ events,
181
+ duration,
182
+ ended: true,
183
+ resource: this.resource,
184
+ instrumentationScope: this.scope,
185
+ droppedAttributesCount: 0,
186
+ droppedEventsCount: 0,
187
+ droppedLinksCount: 0
188
+ };
189
+ }
190
+ async setupIfNeeded() {
191
+ if (this.isSetup || !this.config) return;
192
+ this.exporter = new OTLPTraceExporter({
193
+ url: this.config.endpoint,
194
+ headers: this.config.headers,
195
+ timeoutMillis: this.config.timeoutMillis
196
+ });
197
+ this.processor = this.config.disableBatch ? new SimpleSpanProcessor(this.exporter) : new BatchSpanProcessor(this.exporter, {
198
+ maxExportBatchSize: this.config.batchSize,
199
+ exportTimeoutMillis: this.config.timeoutMillis
200
+ });
201
+ this.isSetup = true;
202
+ }
203
+ async submitScore(args) {
204
+ if (!this.config) return;
205
+ const { traceId, spanId, name, score, reason, metadata } = args;
206
+ const payload = {
207
+ name,
208
+ score,
209
+ source: "Code",
210
+ metadata: {
211
+ ...metadata ?? {},
212
+ ...reason ? { reason } : {}
213
+ }
214
+ };
215
+ if (spanId) payload.spanId = otelSpanIdToUUID(spanId);
216
+ else payload.traceId = otelTraceIdToUUID(traceId);
217
+ const headers = {
218
+ Authorization: `Bearer ${this.config.apiKey}`,
219
+ "content-type": "application/json"
220
+ };
221
+ const controller = new AbortController();
222
+ const timer = setTimeout(() => controller.abort(), this.config.timeoutMillis);
223
+ try {
224
+ const response = await fetch(`${stripTrailingSlash(this.config.baseUrl)}/v1/evaluators/score`, {
225
+ method: "POST",
226
+ headers,
227
+ body: JSON.stringify(payload),
228
+ signal: controller.signal
229
+ });
230
+ if (!response.ok) this.logger.warn("[LaminarExporter] Failed to attach score to trace/span", {
231
+ status: response.status,
232
+ statusText: response.statusText,
233
+ traceId,
234
+ spanId,
235
+ name
236
+ });
237
+ } catch (error) {
238
+ const isAbort = error instanceof Error && error.name === "AbortError";
239
+ this.logger.error("[LaminarExporter] Error attaching score to trace/span", {
240
+ error,
241
+ timedOut: isAbort,
242
+ timeoutMillis: this.config.timeoutMillis,
243
+ traceId,
244
+ spanId,
245
+ name
246
+ });
247
+ } finally {
248
+ clearTimeout(timer);
249
+ }
250
+ }
251
+ async onScoreEvent(event) {
252
+ const { score } = event;
253
+ if (!score.traceId) return;
254
+ await this.submitScore({
255
+ traceId: score.traceId,
256
+ spanId: score.spanId,
257
+ name: score.scorerName ?? score.scorerId,
258
+ score: score.score,
259
+ reason: score.reason,
260
+ metadata: score.metadata
261
+ });
262
+ }
263
+ /**
264
+ * @deprecated Use the observability score event pipeline (`mastra.observability.addScore`)
265
+ * instead. Preserved for backwards compatibility; forwards to the same Laminar score endpoint
266
+ * as `onScoreEvent`.
267
+ */
268
+ async _addScoreToTrace(args) {
269
+ await this.submitScore({
270
+ traceId: args.traceId,
271
+ spanId: args.spanId,
272
+ name: args.scorerName,
273
+ score: args.score,
274
+ reason: args.reason,
275
+ metadata: args.metadata
276
+ });
277
+ }
278
+ /**
279
+ * Force flush any buffered spans without shutting down the exporter.
280
+ * This is useful in serverless environments where you need to ensure spans
281
+ * are exported before the runtime instance is terminated.
282
+ */
283
+ async flush() {
284
+ if (this.isDisabled || !this.processor) return;
285
+ try {
286
+ await this.processor.forceFlush();
287
+ this.logger.debug("[LaminarExporter] Flushed pending spans");
288
+ } catch (error) {
289
+ this.logger.error("[LaminarExporter] Error flushing spans", { error });
290
+ }
291
+ }
292
+ async shutdown() {
293
+ try {
294
+ await this.processor?.shutdown();
295
+ } finally {
296
+ this.traceMap.clear();
297
+ await super.shutdown();
298
+ }
299
+ }
315
300
  };
316
301
  function buildLaminarAttributes(span, traceState) {
317
- const attributes = {};
318
- const spanPath = traceState.spanPathById.get(span.id);
319
- const spanIdsPath = traceState.spanIdsPathById.get(span.id);
320
- if (spanPath) {
321
- attributes[LMNR_SPAN_PATH] = spanPath;
322
- }
323
- if (spanIdsPath) {
324
- attributes[LMNR_SPAN_IDS_PATH] = spanIdsPath;
325
- }
326
- attributes[LMNR_SPAN_TYPE] = mapLaminarSpanType(span.type);
327
- attributes[LMNR_SPAN_INSTRUMENTATION_SOURCE] = "javascript";
328
- attributes[LMNR_SPAN_SDK_VERSION] = "unknown";
329
- attributes[LMNR_SPAN_LANGUAGE_VERSION] = process.version;
330
- const sessionId = span.metadata?.sessionId;
331
- if (typeof sessionId === "string" && sessionId.length > 0) {
332
- attributes[LMNR_SESSION_ID] = sessionId;
333
- }
334
- const userId = span.metadata?.userId;
335
- if (typeof userId === "string" && userId.length > 0) {
336
- attributes[LMNR_USER_ID] = userId;
337
- }
338
- if (span.metadata) {
339
- for (const [key, value] of Object.entries(span.metadata)) {
340
- if (key === "sessionId" || key === "userId" || value === void 0 || value === null) {
341
- continue;
342
- }
343
- const attributeValue = toLaminarAttributeValue(value);
344
- if (attributeValue === void 0) {
345
- continue;
346
- }
347
- attributes[`${LMNR_ASSOCIATION_PREFIX}.metadata.${key}`] = attributeValue;
348
- }
349
- }
350
- if (span.isRootSpan && span.tags?.length) {
351
- attributes[LMNR_TAGS] = span.tags;
352
- }
353
- if (span.input !== void 0) {
354
- attributes[LMNR_SPAN_INPUT] = serializeForLaminar(getLaminarSpanInput(span));
355
- }
356
- if (span.output !== void 0) {
357
- attributes[LMNR_SPAN_OUTPUT] = serializeForLaminar(span.output);
358
- }
359
- if (span.type === SpanType.MODEL_GENERATION) {
360
- const modelAttrs = span.attributes ?? {};
361
- if (modelAttrs.provider) {
362
- attributes[GEN_AI_SYSTEM] = normalizeProvider(modelAttrs.provider);
363
- }
364
- if (modelAttrs.model) {
365
- attributes[GEN_AI_REQUEST_MODEL] = modelAttrs.model;
366
- }
367
- if (modelAttrs.responseModel) {
368
- attributes[GEN_AI_RESPONSE_MODEL] = modelAttrs.responseModel;
369
- }
370
- Object.assign(attributes, formatLaminarUsage(modelAttrs.usage));
371
- }
372
- return attributes;
302
+ const attributes = {};
303
+ const spanPath = traceState.spanPathById.get(span.id);
304
+ const spanIdsPath = traceState.spanIdsPathById.get(span.id);
305
+ if (spanPath) attributes[LMNR_SPAN_PATH] = spanPath;
306
+ if (spanIdsPath) attributes[LMNR_SPAN_IDS_PATH] = spanIdsPath;
307
+ attributes[LMNR_SPAN_TYPE] = mapLaminarSpanType(span.type);
308
+ attributes[LMNR_SPAN_INSTRUMENTATION_SOURCE] = "javascript";
309
+ attributes[LMNR_SPAN_SDK_VERSION] = "unknown";
310
+ attributes[LMNR_SPAN_LANGUAGE_VERSION] = process.version;
311
+ const sessionId = span.metadata?.sessionId;
312
+ if (typeof sessionId === "string" && sessionId.length > 0) attributes[LMNR_SESSION_ID] = sessionId;
313
+ const userId = span.metadata?.userId;
314
+ if (typeof userId === "string" && userId.length > 0) attributes[LMNR_USER_ID] = userId;
315
+ if (span.metadata) for (const [key, value] of Object.entries(span.metadata)) {
316
+ if (key === "sessionId" || key === "userId" || value === void 0 || value === null) continue;
317
+ const attributeValue = toLaminarAttributeValue(value);
318
+ if (attributeValue === void 0) continue;
319
+ attributes[`${LMNR_ASSOCIATION_PREFIX}.metadata.${key}`] = attributeValue;
320
+ }
321
+ if (span.isRootSpan && span.tags?.length) attributes[LMNR_TAGS] = span.tags;
322
+ if (span.input !== void 0) attributes[LMNR_SPAN_INPUT] = serializeForLaminar(getLaminarSpanInput(span));
323
+ if (span.output !== void 0) attributes[LMNR_SPAN_OUTPUT] = serializeForLaminar(span.output);
324
+ if (span.type === SpanType.MODEL_GENERATION) {
325
+ const modelAttrs = span.attributes ?? {};
326
+ if (modelAttrs.provider) attributes[GEN_AI_SYSTEM] = normalizeProvider(modelAttrs.provider);
327
+ if (modelAttrs.model) attributes[GEN_AI_REQUEST_MODEL] = modelAttrs.model;
328
+ if (modelAttrs.responseModel) attributes[GEN_AI_RESPONSE_MODEL] = modelAttrs.responseModel;
329
+ Object.assign(attributes, formatLaminarUsage(modelAttrs.usage));
330
+ }
331
+ return attributes;
373
332
  }
374
333
  function mapLaminarSpanType(spanType) {
375
- switch (spanType) {
376
- case SpanType.MODEL_GENERATION:
377
- case SpanType.MODEL_STEP:
378
- case SpanType.MODEL_CHUNK:
379
- return "LLM";
380
- case SpanType.TOOL_CALL:
381
- case SpanType.MCP_TOOL_CALL:
382
- case SpanType.PROVIDER_TOOL_CALL:
383
- return "TOOL";
384
- default:
385
- return "DEFAULT";
386
- }
334
+ switch (spanType) {
335
+ case SpanType.MODEL_GENERATION:
336
+ case SpanType.MODEL_STEP:
337
+ case SpanType.MODEL_CHUNK: return "LLM";
338
+ case SpanType.TOOL_CALL:
339
+ case SpanType.MCP_TOOL_CALL:
340
+ case SpanType.PROVIDER_TOOL_CALL: return "TOOL";
341
+ default: return "DEFAULT";
342
+ }
387
343
  }
388
344
  function formatLaminarUsage(usage) {
389
- if (!usage) return {};
390
- const out = {};
391
- if (usage.inputTokens !== void 0) {
392
- out[GEN_AI_USAGE_INPUT_TOKENS] = usage.inputTokens;
393
- }
394
- if (usage.outputTokens !== void 0) {
395
- out[GEN_AI_USAGE_OUTPUT_TOKENS] = usage.outputTokens;
396
- }
397
- if (usage.inputDetails?.cacheWrite !== void 0) {
398
- out[GEN_AI_CACHE_WRITE_INPUT_TOKENS] = usage.inputDetails.cacheWrite;
399
- }
400
- if (usage.inputDetails?.cacheRead !== void 0) {
401
- out[GEN_AI_CACHE_READ_INPUT_TOKENS] = usage.inputDetails.cacheRead;
402
- }
403
- return out;
345
+ if (!usage) return {};
346
+ const out = {};
347
+ if (usage.inputTokens !== void 0) out[GEN_AI_USAGE_INPUT_TOKENS] = usage.inputTokens;
348
+ if (usage.outputTokens !== void 0) out[GEN_AI_USAGE_OUTPUT_TOKENS] = usage.outputTokens;
349
+ if (usage.inputDetails?.cacheWrite !== void 0) out[GEN_AI_CACHE_WRITE_INPUT_TOKENS] = usage.inputDetails.cacheWrite;
350
+ if (usage.inputDetails?.cacheRead !== void 0) out[GEN_AI_CACHE_READ_INPUT_TOKENS] = usage.inputDetails.cacheRead;
351
+ return out;
404
352
  }
405
353
  function serializeForLaminar(value) {
406
- if (typeof value === "string") {
407
- return value;
408
- }
409
- try {
410
- return JSON.stringify(value);
411
- } catch {
412
- return "[unserializable]";
413
- }
354
+ if (typeof value === "string") return value;
355
+ try {
356
+ return JSON.stringify(value);
357
+ } catch {
358
+ return "[unserializable]";
359
+ }
414
360
  }
415
361
  function getLaminarSpanInput(span) {
416
- if (span.type !== SpanType.MODEL_GENERATION) {
417
- return span.input;
418
- }
419
- const input = span.input;
420
- if (!input || typeof input !== "object" || Array.isArray(input)) {
421
- return input;
422
- }
423
- const maybeMessages = input.messages;
424
- return Array.isArray(maybeMessages) ? maybeMessages : input;
362
+ if (span.type !== SpanType.MODEL_GENERATION) return span.input;
363
+ const input = span.input;
364
+ if (!input || typeof input !== "object" || Array.isArray(input)) return input;
365
+ const maybeMessages = input.messages;
366
+ return Array.isArray(maybeMessages) ? maybeMessages : input;
425
367
  }
426
368
  function toLaminarAttributeValue(value) {
427
- if (value === void 0 || value === null) {
428
- return void 0;
429
- }
430
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
431
- return value;
432
- }
433
- if (Array.isArray(value)) {
434
- const isHomogeneous = value.every((v) => typeof v === "string") || value.every((v) => typeof v === "number") || value.every((v) => typeof v === "boolean");
435
- if (isHomogeneous) return value;
436
- }
437
- return serializeForLaminar(value);
369
+ if (value === void 0 || value === null) return;
370
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
371
+ if (Array.isArray(value)) {
372
+ if (value.every((v) => typeof v === "string") || value.every((v) => typeof v === "number") || value.every((v) => typeof v === "boolean")) return value;
373
+ }
374
+ return serializeForLaminar(value);
438
375
  }
376
+ /**
377
+ * Convert JavaScript Date to hrtime format
378
+ */
439
379
  function dateToHrTime(date) {
440
- const ms = date.getTime();
441
- const seconds = Math.floor(ms / 1e3);
442
- const nanoseconds = ms % 1e3 * 1e6;
443
- return [seconds, nanoseconds];
380
+ const ms = date.getTime();
381
+ return [Math.floor(ms / 1e3), ms % 1e3 * 1e6];
444
382
  }
445
383
  function computeDuration(start, end) {
446
- if (!end) return [0, 0];
447
- const diffMs = end.getTime() - start.getTime();
448
- return [Math.floor(diffMs / 1e3), diffMs % 1e3 * 1e6];
384
+ if (!end) return [0, 0];
385
+ const diffMs = end.getTime() - start.getTime();
386
+ return [Math.floor(diffMs / 1e3), diffMs % 1e3 * 1e6];
449
387
  }
450
388
  function buildStatusAndEvents(span, defaultTime) {
451
- const events = [];
452
- if (span.errorInfo) {
453
- const status = {
454
- code: SpanStatusCode.ERROR,
455
- message: span.errorInfo.message
456
- };
457
- events.push({
458
- name: "exception",
459
- attributes: {
460
- "exception.message": span.errorInfo.message,
461
- "exception.type": "Error",
462
- ...span.errorInfo.details?.stack && {
463
- "exception.stacktrace": span.errorInfo.details.stack
464
- }
465
- },
466
- time: defaultTime,
467
- droppedAttributesCount: 0
468
- });
469
- return { status, events };
470
- }
471
- return {
472
- status: { code: SpanStatusCode.OK },
473
- events
474
- };
389
+ const events = [];
390
+ if (span.errorInfo) {
391
+ const status = {
392
+ code: SpanStatusCode.ERROR,
393
+ message: span.errorInfo.message
394
+ };
395
+ events.push({
396
+ name: "exception",
397
+ attributes: {
398
+ "exception.message": span.errorInfo.message,
399
+ "exception.type": "Error",
400
+ ...span.errorInfo.details?.stack && { "exception.stacktrace": span.errorInfo.details.stack }
401
+ },
402
+ time: defaultTime,
403
+ droppedAttributesCount: 0
404
+ });
405
+ return {
406
+ status,
407
+ events
408
+ };
409
+ }
410
+ return {
411
+ status: { code: SpanStatusCode.OK },
412
+ events
413
+ };
475
414
  }
476
415
  function getSpanKind(type) {
477
- switch (type) {
478
- case SpanType.MODEL_GENERATION:
479
- case SpanType.MCP_TOOL_CALL:
480
- return SpanKind.CLIENT;
481
- default:
482
- return SpanKind.INTERNAL;
483
- }
416
+ switch (type) {
417
+ case SpanType.MODEL_GENERATION:
418
+ case SpanType.MCP_TOOL_CALL: return SpanKind.CLIENT;
419
+ default: return SpanKind.INTERNAL;
420
+ }
484
421
  }
485
422
  function stripTrailingSlash(url) {
486
- let end = url.length;
487
- while (end > 0 && url.charCodeAt(end - 1) === 47) {
488
- end--;
489
- }
490
- return end === url.length ? url : url.slice(0, end);
423
+ let end = url.length;
424
+ while (end > 0 && url.charCodeAt(end - 1) === 47) end--;
425
+ return end === url.length ? url : url.slice(0, end);
491
426
  }
492
427
  function normalizeTraceId(traceId) {
493
- let id = traceId.toLowerCase();
494
- if (id.startsWith("0x")) id = id.slice(2);
495
- return id.padStart(32, "0").slice(-32);
428
+ let id = traceId.toLowerCase();
429
+ if (id.startsWith("0x")) id = id.slice(2);
430
+ return id.padStart(32, "0").slice(-32);
496
431
  }
497
432
  function normalizeSpanId(spanId) {
498
- let id = spanId.toLowerCase();
499
- if (id.startsWith("0x")) id = id.slice(2);
500
- return id.padStart(16, "0").slice(-16);
433
+ let id = spanId.toLowerCase();
434
+ if (id.startsWith("0x")) id = id.slice(2);
435
+ return id.padStart(16, "0").slice(-16);
501
436
  }
502
437
  function otelSpanIdToUUID(spanId) {
503
- const normalized = normalizeSpanId(spanId);
504
- return normalized.padStart(32, "0").replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
438
+ return normalizeSpanId(spanId).padStart(32, "0").replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
505
439
  }
506
440
  function otelTraceIdToUUID(traceId) {
507
- const normalized = normalizeTraceId(traceId);
508
- return normalized.replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
441
+ return normalizeTraceId(traceId).replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
509
442
  }
510
443
  function normalizeProvider(provider) {
511
- return provider.split(".").shift()?.toLowerCase().trim() || provider.toLowerCase().trim();
444
+ return provider.split(".").shift()?.toLowerCase().trim() || provider.toLowerCase().trim();
512
445
  }
513
-
446
+ //#endregion
514
447
  export { LaminarExporter, otelSpanIdToUUID, otelTraceIdToUUID, stripTrailingSlash };
515
- //# sourceMappingURL=index.js.map
448
+
516
449
  //# sourceMappingURL=index.js.map