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