@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.js
CHANGED
|
@@ -1,516 +1,449 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { BaseExporter } from
|
|
3
|
-
import {
|
|
4
|
-
import { OTLPTraceExporter } from
|
|
5
|
-
import { resourceFromAttributes } from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
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
|
-
|
|
318
|
-
|
|
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
|
-
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
441
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
444
|
+
return provider.split(".").shift()?.toLowerCase().trim() || provider.toLowerCase().trim();
|
|
512
445
|
}
|
|
513
|
-
|
|
446
|
+
//#endregion
|
|
514
447
|
export { LaminarExporter, otelSpanIdToUUID, otelTraceIdToUUID, stripTrailingSlash };
|
|
515
|
-
|
|
448
|
+
|
|
516
449
|
//# sourceMappingURL=index.js.map
|