@amaster.ai/pi-telemetry 0.1.0-beta.0
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/LICENSE +201 -0
- package/README.md +61 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/langfuse.d.ts +98 -0
- package/dist/langfuse.d.ts.map +1 -0
- package/dist/langfuse.js +1312 -0
- package/dist/langfuse.js.map +1 -0
- package/dist/otel.d.ts +6 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +64 -0
- package/dist/otel.js.map +1 -0
- package/package.json +50 -0
package/dist/langfuse.js
ADDED
|
@@ -0,0 +1,1312 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { Langfuse } from "langfuse";
|
|
3
|
+
import { NoopRuntimeEventExporter, } from "./index.js";
|
|
4
|
+
const DEFAULT_LANGFUSE_BASE_URL = "https://cloud.langfuse.com";
|
|
5
|
+
const DEFAULT_FLUSH_AT = 20;
|
|
6
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 5000;
|
|
7
|
+
export class LangfuseSdkRuntimeEventExporter {
|
|
8
|
+
config;
|
|
9
|
+
client;
|
|
10
|
+
traces = new Map();
|
|
11
|
+
spans = new Map();
|
|
12
|
+
generations = new Map();
|
|
13
|
+
constructor(config, client) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.client =
|
|
16
|
+
client ??
|
|
17
|
+
new Langfuse({
|
|
18
|
+
publicKey: config.publicKey,
|
|
19
|
+
secretKey: config.secretKey,
|
|
20
|
+
baseUrl: config.baseUrl,
|
|
21
|
+
flushAt: config.flushAt,
|
|
22
|
+
flushInterval: config.flushIntervalMs,
|
|
23
|
+
enabled: true,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async publish(event) {
|
|
27
|
+
const redactedEvent = applyTelemetryRedaction(this.config, event);
|
|
28
|
+
if (!redactedEvent?.traceId) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (isLlmGenerationEvent(redactedEvent)) {
|
|
32
|
+
this.publishLlmGenerationEvent(redactedEvent);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (isToolEvent(redactedEvent)) {
|
|
36
|
+
this.publishToolEvent(redactedEvent);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.publishLifecycleEvent(redactedEvent);
|
|
40
|
+
}
|
|
41
|
+
async flush() {
|
|
42
|
+
await this.client.flushAsync();
|
|
43
|
+
}
|
|
44
|
+
async close() {
|
|
45
|
+
await this.client.shutdownAsync();
|
|
46
|
+
}
|
|
47
|
+
publishLifecycleEvent(event) {
|
|
48
|
+
const traceId = requireTraceId(event.traceId);
|
|
49
|
+
const trace = this.getOrCreateSdkTrace(event);
|
|
50
|
+
const rootKey = chatSpanKey(event);
|
|
51
|
+
const rootSpanId = langfuseSpanId(traceId, rootKey);
|
|
52
|
+
switch (event.type) {
|
|
53
|
+
case "chat_turn_started": {
|
|
54
|
+
const body = {
|
|
55
|
+
id: rootSpanId,
|
|
56
|
+
name: "copilot-chat-turn",
|
|
57
|
+
startTime: event.createdAt,
|
|
58
|
+
input: event.details?.input,
|
|
59
|
+
level: "DEFAULT",
|
|
60
|
+
metadata: lifecycleMetadata(event),
|
|
61
|
+
};
|
|
62
|
+
const span = this.spans.get(rootKey);
|
|
63
|
+
if (span) {
|
|
64
|
+
span.update({
|
|
65
|
+
input: event.details?.input,
|
|
66
|
+
level: "DEFAULT",
|
|
67
|
+
metadata: lifecycleMetadata(event),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.spans.set(rootKey, trace.span(body));
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case "chat_turn_completed":
|
|
76
|
+
case "chat_turn_failed": {
|
|
77
|
+
const output = event.details?.output ?? (event.error ? { error: event.error } : undefined);
|
|
78
|
+
this.closeSdkSubagentBatchSpans(traceId, event);
|
|
79
|
+
trace.update({
|
|
80
|
+
sessionId: event.sessionId,
|
|
81
|
+
name: "copilot-chat-turn",
|
|
82
|
+
output,
|
|
83
|
+
metadata: lifecycleMetadata(event),
|
|
84
|
+
});
|
|
85
|
+
const span = this.spans.get(rootKey) ??
|
|
86
|
+
trace.span({
|
|
87
|
+
id: rootSpanId,
|
|
88
|
+
name: "copilot-chat-turn",
|
|
89
|
+
startTime: event.createdAt,
|
|
90
|
+
metadata: lifecycleMetadata(event),
|
|
91
|
+
});
|
|
92
|
+
span.update({
|
|
93
|
+
output,
|
|
94
|
+
endTime: event.createdAt,
|
|
95
|
+
level: event.error ? "ERROR" : "DEFAULT",
|
|
96
|
+
...(event.error ? { statusMessage: event.error } : {}),
|
|
97
|
+
metadata: lifecycleMetadata(event),
|
|
98
|
+
});
|
|
99
|
+
this.spans.delete(rootKey);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case "chat_turn_steered":
|
|
103
|
+
case "chat_turn_steer_delivered":
|
|
104
|
+
case "chat_turn_followup_queued":
|
|
105
|
+
case "chat_turn_followup_delivered": {
|
|
106
|
+
const output = event.details?.output ?? chatInputLifecycleOutput(event);
|
|
107
|
+
this.getSdkSpanParent(trace, rootKey).span({
|
|
108
|
+
id: langfuseSpanId(traceId, chatInputSpanKey(event)),
|
|
109
|
+
name: chatInputObservationName(event),
|
|
110
|
+
startTime: event.createdAt,
|
|
111
|
+
endTime: event.createdAt,
|
|
112
|
+
input: event.details?.input,
|
|
113
|
+
output,
|
|
114
|
+
level: "DEFAULT",
|
|
115
|
+
metadata: lifecycleMetadata(event),
|
|
116
|
+
});
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "subagent_spawned":
|
|
120
|
+
case "subagent_started": {
|
|
121
|
+
const key = subagentSpanKey(event);
|
|
122
|
+
const rootSpan = this.ensureSdkRootSpan(trace, rootKey, event);
|
|
123
|
+
const body = {
|
|
124
|
+
id: langfuseSpanId(traceId, key),
|
|
125
|
+
name: "subagent",
|
|
126
|
+
startTime: event.createdAt,
|
|
127
|
+
input: event.details?.input,
|
|
128
|
+
level: "DEFAULT",
|
|
129
|
+
metadata: lifecycleMetadata(event),
|
|
130
|
+
};
|
|
131
|
+
const span = this.spans.get(key);
|
|
132
|
+
if (span) {
|
|
133
|
+
span.update({
|
|
134
|
+
input: event.details?.input,
|
|
135
|
+
level: "DEFAULT",
|
|
136
|
+
metadata: lifecycleMetadata(event),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.spans.set(key, this.getSdkSubagentParent(trace, rootKey, event).span(body));
|
|
141
|
+
}
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case "subagent_completed":
|
|
145
|
+
case "subagent_failed":
|
|
146
|
+
case "subagent_cancelled": {
|
|
147
|
+
const key = subagentSpanKey(event);
|
|
148
|
+
const output = event.details?.output ?? (event.error ? { error: event.error } : undefined);
|
|
149
|
+
const rootSpan = this.ensureSdkRootSpan(trace, rootKey, event);
|
|
150
|
+
const span = this.spans.get(key) ??
|
|
151
|
+
this.getSdkSubagentParent(trace, rootKey, event).span({
|
|
152
|
+
id: langfuseSpanId(traceId, key),
|
|
153
|
+
name: "subagent",
|
|
154
|
+
startTime: event.createdAt,
|
|
155
|
+
metadata: lifecycleMetadata(event),
|
|
156
|
+
});
|
|
157
|
+
span.update({
|
|
158
|
+
output,
|
|
159
|
+
endTime: event.createdAt,
|
|
160
|
+
level: event.error ? "ERROR" : "DEFAULT",
|
|
161
|
+
...(event.error ? { statusMessage: event.error } : {}),
|
|
162
|
+
metadata: lifecycleMetadata(event),
|
|
163
|
+
});
|
|
164
|
+
this.spans.delete(key);
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
default:
|
|
168
|
+
assertNever(event.type);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
publishToolEvent(event) {
|
|
172
|
+
const traceId = requireTraceId(event.traceId);
|
|
173
|
+
const trace = this.getOrCreateSdkTrace(event);
|
|
174
|
+
const rootKey = chatSpanKey(event);
|
|
175
|
+
const parent = this.getSdkEventParent(trace, rootKey, event);
|
|
176
|
+
const key = toolSpanKey(event);
|
|
177
|
+
const id = langfuseSpanId(traceId, key);
|
|
178
|
+
if (event.status === "started") {
|
|
179
|
+
const body = {
|
|
180
|
+
id,
|
|
181
|
+
name: toolObservationName(event),
|
|
182
|
+
startTime: event.createdAt,
|
|
183
|
+
input: event.args ? { args: event.args } : undefined,
|
|
184
|
+
level: "DEFAULT",
|
|
185
|
+
metadata: toolMetadata(event),
|
|
186
|
+
};
|
|
187
|
+
const span = this.spans.get(key);
|
|
188
|
+
if (span) {
|
|
189
|
+
span.update({
|
|
190
|
+
input: event.args ? { args: event.args } : undefined,
|
|
191
|
+
level: "DEFAULT",
|
|
192
|
+
metadata: toolMetadata(event),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
this.spans.set(key, parent.span(body));
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const output = event.error ? { error: event.error } : (event.details ?? {});
|
|
201
|
+
const span = this.spans.get(key) ??
|
|
202
|
+
parent.span({
|
|
203
|
+
id,
|
|
204
|
+
name: toolObservationName(event),
|
|
205
|
+
startTime: event.createdAt,
|
|
206
|
+
metadata: toolMetadata(event),
|
|
207
|
+
});
|
|
208
|
+
span.update({
|
|
209
|
+
output,
|
|
210
|
+
endTime: event.createdAt,
|
|
211
|
+
level: event.error ? "ERROR" : "DEFAULT",
|
|
212
|
+
...(event.error ? { statusMessage: event.error } : {}),
|
|
213
|
+
metadata: toolMetadata(event),
|
|
214
|
+
});
|
|
215
|
+
this.spans.delete(key);
|
|
216
|
+
}
|
|
217
|
+
publishLlmGenerationEvent(event) {
|
|
218
|
+
const traceId = requireTraceId(event.traceId);
|
|
219
|
+
const trace = this.getOrCreateSdkTrace(event);
|
|
220
|
+
const rootKey = chatSpanKey(event);
|
|
221
|
+
const parent = this.getSdkEventParent(trace, rootKey, event);
|
|
222
|
+
const key = llmGenerationKey(event);
|
|
223
|
+
const id = langfuseSpanId(traceId, key);
|
|
224
|
+
if (event.status === "started") {
|
|
225
|
+
const body = {
|
|
226
|
+
id,
|
|
227
|
+
name: llmGenerationObservationName(event),
|
|
228
|
+
startTime: event.createdAt,
|
|
229
|
+
model: event.model.model,
|
|
230
|
+
modelParameters: {
|
|
231
|
+
provider: event.model.provider,
|
|
232
|
+
...(event.model.thinkingLevel ? { thinkingLevel: event.model.thinkingLevel } : {}),
|
|
233
|
+
},
|
|
234
|
+
input: event.input,
|
|
235
|
+
metadata: llmGenerationMetadata(event),
|
|
236
|
+
};
|
|
237
|
+
const generation = this.generations.get(key);
|
|
238
|
+
if (generation) {
|
|
239
|
+
generation.update({
|
|
240
|
+
input: event.input,
|
|
241
|
+
metadata: llmGenerationMetadata(event),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
this.generations.set(key, parent.generation(body));
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const generation = this.generations.get(key) ??
|
|
250
|
+
parent.generation({
|
|
251
|
+
id,
|
|
252
|
+
name: llmGenerationObservationName(event),
|
|
253
|
+
startTime: event.createdAt,
|
|
254
|
+
model: event.model.model,
|
|
255
|
+
metadata: llmGenerationMetadata(event),
|
|
256
|
+
});
|
|
257
|
+
generation.update({
|
|
258
|
+
output: event.output ?? (event.error ? { error: event.error } : undefined),
|
|
259
|
+
endTime: event.createdAt,
|
|
260
|
+
level: event.error ? "ERROR" : "DEFAULT",
|
|
261
|
+
...(event.error ? { statusMessage: event.error } : {}),
|
|
262
|
+
...(event.usage ? { usage: toLangfuseUsage(event.usage), usageDetails: toLangfuseUsageDetails(event.usage) } : {}),
|
|
263
|
+
model: event.model.model,
|
|
264
|
+
modelParameters: {
|
|
265
|
+
provider: event.model.provider,
|
|
266
|
+
...(event.model.thinkingLevel ? { thinkingLevel: event.model.thinkingLevel } : {}),
|
|
267
|
+
},
|
|
268
|
+
metadata: llmGenerationMetadata(event),
|
|
269
|
+
});
|
|
270
|
+
this.generations.delete(key);
|
|
271
|
+
}
|
|
272
|
+
getOrCreateSdkTrace(event) {
|
|
273
|
+
const traceId = requireTraceId(event.traceId);
|
|
274
|
+
const existing = this.traces.get(traceId);
|
|
275
|
+
if (existing) {
|
|
276
|
+
return existing;
|
|
277
|
+
}
|
|
278
|
+
const trace = this.client.trace({
|
|
279
|
+
id: langfuseTraceId(traceId),
|
|
280
|
+
sessionId: event.sessionId,
|
|
281
|
+
name: "copilot-chat-turn",
|
|
282
|
+
timestamp: event.createdAt,
|
|
283
|
+
input: !isToolEvent(event) && !isLlmGenerationEvent(event) ? event.details?.input : undefined,
|
|
284
|
+
metadata: isToolEvent(event)
|
|
285
|
+
? toolMetadata(event)
|
|
286
|
+
: isLlmGenerationEvent(event)
|
|
287
|
+
? llmGenerationMetadata(event)
|
|
288
|
+
: lifecycleMetadata(event),
|
|
289
|
+
});
|
|
290
|
+
this.traces.set(traceId, trace);
|
|
291
|
+
return trace;
|
|
292
|
+
}
|
|
293
|
+
getSdkSpanParent(trace, rootKey) {
|
|
294
|
+
return this.spans.get(rootKey) ?? trace;
|
|
295
|
+
}
|
|
296
|
+
getSdkEventParent(trace, rootKey, event) {
|
|
297
|
+
const subagentKey = telemetryEventSubagentSpanKey(event);
|
|
298
|
+
if (subagentKey) {
|
|
299
|
+
return this.spans.get(subagentKey) ?? this.getSdkSpanParent(trace, rootKey);
|
|
300
|
+
}
|
|
301
|
+
return this.getSdkSpanParent(trace, rootKey);
|
|
302
|
+
}
|
|
303
|
+
getSdkSubagentParent(trace, rootKey, event) {
|
|
304
|
+
const rootSpan = this.ensureSdkRootSpan(trace, rootKey, event);
|
|
305
|
+
const batchKey = subagentBatchSpanKey(event);
|
|
306
|
+
if (batchKey) {
|
|
307
|
+
const existing = this.spans.get(batchKey);
|
|
308
|
+
if (existing) {
|
|
309
|
+
return existing;
|
|
310
|
+
}
|
|
311
|
+
const batchSpan = rootSpan.span({
|
|
312
|
+
id: langfuseSpanId(requireTraceId(event.traceId), batchKey),
|
|
313
|
+
name: "subagent fan-out",
|
|
314
|
+
startTime: event.createdAt,
|
|
315
|
+
input: event.details?.input,
|
|
316
|
+
level: "DEFAULT",
|
|
317
|
+
metadata: lifecycleMetadata(event),
|
|
318
|
+
});
|
|
319
|
+
this.spans.set(batchKey, batchSpan);
|
|
320
|
+
return batchSpan;
|
|
321
|
+
}
|
|
322
|
+
const spawnToolKey = subagentSpawnToolSpanKey(event);
|
|
323
|
+
return (spawnToolKey ? this.spans.get(spawnToolKey) : undefined) ?? rootSpan;
|
|
324
|
+
}
|
|
325
|
+
closeSdkSubagentBatchSpans(traceId, event) {
|
|
326
|
+
const prefix = `subagent-batch:${traceId}:`;
|
|
327
|
+
for (const [key, span] of [...this.spans.entries()]) {
|
|
328
|
+
if (!key.startsWith(prefix)) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
span.update({
|
|
332
|
+
endTime: event.createdAt,
|
|
333
|
+
level: event.error ? "ERROR" : "DEFAULT",
|
|
334
|
+
...(event.error ? { statusMessage: event.error } : {}),
|
|
335
|
+
metadata: lifecycleMetadata(event),
|
|
336
|
+
});
|
|
337
|
+
this.spans.delete(key);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
ensureSdkRootSpan(trace, rootKey, event) {
|
|
341
|
+
const existing = this.spans.get(rootKey);
|
|
342
|
+
if (existing) {
|
|
343
|
+
return existing;
|
|
344
|
+
}
|
|
345
|
+
const traceId = requireTraceId(event.traceId);
|
|
346
|
+
const span = trace.span({
|
|
347
|
+
id: langfuseSpanId(traceId, rootKey),
|
|
348
|
+
name: "copilot-chat-turn",
|
|
349
|
+
startTime: event.createdAt,
|
|
350
|
+
metadata: isToolEvent(event)
|
|
351
|
+
? toolMetadata(event)
|
|
352
|
+
: isLlmGenerationEvent(event)
|
|
353
|
+
? llmGenerationMetadata(event)
|
|
354
|
+
: lifecycleMetadata(event),
|
|
355
|
+
});
|
|
356
|
+
this.spans.set(rootKey, span);
|
|
357
|
+
return span;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
export class LangfuseHttpRuntimeEventExporter {
|
|
361
|
+
config;
|
|
362
|
+
endpoint;
|
|
363
|
+
authHeader;
|
|
364
|
+
fetchImpl;
|
|
365
|
+
queue = [];
|
|
366
|
+
flushTimer;
|
|
367
|
+
flushing;
|
|
368
|
+
constructor(config, fetchImpl) {
|
|
369
|
+
this.config = config;
|
|
370
|
+
this.endpoint = `${config.baseUrl.replace(/\/+$/, "")}/api/public/ingestion`;
|
|
371
|
+
this.authHeader = `Basic ${Buffer.from(`${config.publicKey}:${config.secretKey}`).toString("base64")}`;
|
|
372
|
+
this.fetchImpl = fetchImpl ?? (async (input, init) => {
|
|
373
|
+
const response = await fetch(input, init);
|
|
374
|
+
return {
|
|
375
|
+
ok: response.ok,
|
|
376
|
+
status: response.status,
|
|
377
|
+
text: () => response.text(),
|
|
378
|
+
};
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
async publish(event) {
|
|
382
|
+
const redactedEvent = applyTelemetryRedaction(this.config, event);
|
|
383
|
+
if (!redactedEvent) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const mapped = mapRuntimeEventToLangfuse(redactedEvent);
|
|
387
|
+
if (mapped.length === 0) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
this.queue.push(...mapped);
|
|
391
|
+
if (this.queue.length >= this.config.flushAt) {
|
|
392
|
+
await this.flush();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
this.scheduleFlush();
|
|
396
|
+
}
|
|
397
|
+
async flush() {
|
|
398
|
+
if (this.flushing) {
|
|
399
|
+
await this.flushing;
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (this.flushTimer) {
|
|
403
|
+
clearTimeout(this.flushTimer);
|
|
404
|
+
this.flushTimer = undefined;
|
|
405
|
+
}
|
|
406
|
+
const batch = this.queue.splice(0, this.queue.length);
|
|
407
|
+
if (batch.length === 0) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
this.flushing = this.sendBatch(batch)
|
|
411
|
+
.catch((error) => {
|
|
412
|
+
this.queue.unshift(...batch);
|
|
413
|
+
throw error;
|
|
414
|
+
})
|
|
415
|
+
.finally(() => {
|
|
416
|
+
this.flushing = undefined;
|
|
417
|
+
});
|
|
418
|
+
await this.flushing;
|
|
419
|
+
}
|
|
420
|
+
async close() {
|
|
421
|
+
await this.flush();
|
|
422
|
+
}
|
|
423
|
+
scheduleFlush() {
|
|
424
|
+
if (this.flushTimer) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
this.flushTimer = setTimeout(() => {
|
|
428
|
+
this.flushTimer = undefined;
|
|
429
|
+
void this.flush().catch(() => undefined);
|
|
430
|
+
}, this.config.flushIntervalMs);
|
|
431
|
+
this.flushTimer.unref();
|
|
432
|
+
}
|
|
433
|
+
async sendBatch(batch) {
|
|
434
|
+
const response = await this.fetchImpl(this.endpoint, {
|
|
435
|
+
method: "POST",
|
|
436
|
+
headers: {
|
|
437
|
+
authorization: this.authHeader,
|
|
438
|
+
"content-type": "application/json",
|
|
439
|
+
"x-langfuse-ingestion-version": "4",
|
|
440
|
+
},
|
|
441
|
+
body: JSON.stringify({ batch }),
|
|
442
|
+
});
|
|
443
|
+
if (!response.ok) {
|
|
444
|
+
const text = await response.text().catch(() => "");
|
|
445
|
+
throw new Error(`Langfuse ingestion failed with ${response.status}${text ? `: ${text}` : ""}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
export class OtelRuntimeEventExporter {
|
|
450
|
+
config;
|
|
451
|
+
endpoint;
|
|
452
|
+
fetchImpl;
|
|
453
|
+
pendingStarts = new Map();
|
|
454
|
+
queue = [];
|
|
455
|
+
flushTimer;
|
|
456
|
+
flushing;
|
|
457
|
+
constructor(config, fetchImpl) {
|
|
458
|
+
this.config = config;
|
|
459
|
+
this.endpoint = normalizeOtelTracesEndpoint(config.endpoint);
|
|
460
|
+
this.fetchImpl = fetchImpl ?? (async (input, init) => {
|
|
461
|
+
const response = await fetch(input, init);
|
|
462
|
+
return {
|
|
463
|
+
ok: response.ok,
|
|
464
|
+
status: response.status,
|
|
465
|
+
text: () => response.text(),
|
|
466
|
+
};
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
async publish(event) {
|
|
470
|
+
const redactedEvent = applyTelemetryRedaction(this.config, event);
|
|
471
|
+
if (!redactedEvent) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
for (const span of mapRuntimeEventToOtelSpans(redactedEvent, this.pendingStarts)) {
|
|
475
|
+
this.queue.push(span);
|
|
476
|
+
}
|
|
477
|
+
if (this.queue.length === 0) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (this.queue.length >= this.config.flushAt) {
|
|
481
|
+
await this.flush();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
this.scheduleFlush();
|
|
485
|
+
}
|
|
486
|
+
async flush() {
|
|
487
|
+
if (this.flushing) {
|
|
488
|
+
await this.flushing;
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (this.flushTimer) {
|
|
492
|
+
clearTimeout(this.flushTimer);
|
|
493
|
+
this.flushTimer = undefined;
|
|
494
|
+
}
|
|
495
|
+
const spans = this.queue.splice(0, this.queue.length);
|
|
496
|
+
if (spans.length === 0) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
this.flushing = this.sendSpans(spans)
|
|
500
|
+
.catch((error) => {
|
|
501
|
+
this.queue.unshift(...spans);
|
|
502
|
+
throw error;
|
|
503
|
+
})
|
|
504
|
+
.finally(() => {
|
|
505
|
+
this.flushing = undefined;
|
|
506
|
+
});
|
|
507
|
+
await this.flushing;
|
|
508
|
+
}
|
|
509
|
+
async close() {
|
|
510
|
+
await this.flush();
|
|
511
|
+
}
|
|
512
|
+
scheduleFlush() {
|
|
513
|
+
if (this.flushTimer) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
this.flushTimer = setTimeout(() => {
|
|
517
|
+
this.flushTimer = undefined;
|
|
518
|
+
void this.flush().catch(() => undefined);
|
|
519
|
+
}, this.config.flushIntervalMs);
|
|
520
|
+
this.flushTimer.unref();
|
|
521
|
+
}
|
|
522
|
+
async sendSpans(spans) {
|
|
523
|
+
const response = await this.fetchImpl(this.endpoint, {
|
|
524
|
+
method: "POST",
|
|
525
|
+
headers: {
|
|
526
|
+
"content-type": "application/json",
|
|
527
|
+
...(this.config.headers ?? {}),
|
|
528
|
+
},
|
|
529
|
+
body: JSON.stringify(toOtelTracePayload(spans, this.config)),
|
|
530
|
+
});
|
|
531
|
+
if (!response.ok) {
|
|
532
|
+
const text = await response.text().catch(() => "");
|
|
533
|
+
throw new Error(`${this.config.errorLabel ?? "OTEL export"} failed with ${response.status}${text ? `: ${text}` : ""}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
export function createRuntimeEventExporterFromEnv(env) {
|
|
538
|
+
const config = resolveLangfuseConfig(env);
|
|
539
|
+
if (!config.enabled) {
|
|
540
|
+
return new NoopRuntimeEventExporter();
|
|
541
|
+
}
|
|
542
|
+
if (config.transport === "sdk") {
|
|
543
|
+
return new LangfuseSdkRuntimeEventExporter(config);
|
|
544
|
+
}
|
|
545
|
+
return new LangfuseHttpRuntimeEventExporter(config);
|
|
546
|
+
}
|
|
547
|
+
export function resolveLangfuseConfig(env) {
|
|
548
|
+
const enabled = parseBoolean(env.LANGFUSE_ENABLED);
|
|
549
|
+
const publicKey = trim(env.LANGFUSE_PUBLIC_KEY);
|
|
550
|
+
const secretKey = trim(env.LANGFUSE_SECRET_KEY);
|
|
551
|
+
const baseUrl = trim(env.LANGFUSE_BASE_URL) ?? DEFAULT_LANGFUSE_BASE_URL;
|
|
552
|
+
const requestedTransport = parseLangfuseTransport(env.LANGFUSE_TRANSPORT);
|
|
553
|
+
const transport = requestedTransport ?? (publicKey && secretKey ? "sdk" : "ingestion");
|
|
554
|
+
const credentialsPresent = Boolean(publicKey && secretKey);
|
|
555
|
+
const serviceVersion = trim(env.TELEMETRY_SERVICE_VERSION);
|
|
556
|
+
return {
|
|
557
|
+
enabled: Boolean(enabled && credentialsPresent),
|
|
558
|
+
transport,
|
|
559
|
+
publicKey: publicKey ?? "",
|
|
560
|
+
secretKey: secretKey ?? "",
|
|
561
|
+
baseUrl,
|
|
562
|
+
flushAt: parsePositiveInteger(env.LANGFUSE_FLUSH_AT, DEFAULT_FLUSH_AT),
|
|
563
|
+
flushIntervalMs: parsePositiveInteger(env.LANGFUSE_FLUSH_INTERVAL_MS, DEFAULT_FLUSH_INTERVAL_MS),
|
|
564
|
+
serviceName: trim(env.TELEMETRY_SERVICE_NAME ?? env.OTEL_SERVICE_NAME) ?? "pi-server",
|
|
565
|
+
...(serviceVersion ? { serviceVersion } : {}),
|
|
566
|
+
includePayloads: parseBooleanWithDefault(env.TELEMETRY_INCLUDE_PAYLOADS ?? env.LANGFUSE_INCLUDE_PAYLOADS, true),
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
export function mapRuntimeEventToLangfuse(event) {
|
|
570
|
+
if (!event.traceId) {
|
|
571
|
+
return [];
|
|
572
|
+
}
|
|
573
|
+
if (isLlmGenerationEvent(event)) {
|
|
574
|
+
return [mapLlmGenerationEventToLangfuse(event)];
|
|
575
|
+
}
|
|
576
|
+
if (isToolEvent(event)) {
|
|
577
|
+
return [mapToolEventToLangfuse(event)];
|
|
578
|
+
}
|
|
579
|
+
return mapLifecycleEventToLangfuse(event);
|
|
580
|
+
}
|
|
581
|
+
function mapLifecycleEventToLangfuse(event) {
|
|
582
|
+
const traceId = requireTraceId(event.traceId);
|
|
583
|
+
switch (event.type) {
|
|
584
|
+
case "chat_turn_started":
|
|
585
|
+
return [ingestionEvent("trace-create", event.createdAt, {
|
|
586
|
+
id: langfuseTraceId(traceId),
|
|
587
|
+
sessionId: event.sessionId,
|
|
588
|
+
name: "copilot-chat-turn",
|
|
589
|
+
timestamp: event.createdAt,
|
|
590
|
+
input: event.details?.input,
|
|
591
|
+
metadata: lifecycleMetadata(event),
|
|
592
|
+
})];
|
|
593
|
+
case "chat_turn_completed":
|
|
594
|
+
case "chat_turn_failed": {
|
|
595
|
+
const output = event.details?.output ?? (event.error ? { error: event.error } : undefined);
|
|
596
|
+
return [ingestionEvent("trace-update", event.createdAt, {
|
|
597
|
+
id: langfuseTraceId(traceId),
|
|
598
|
+
...(output !== undefined ? { output } : {}),
|
|
599
|
+
metadata: lifecycleMetadata(event),
|
|
600
|
+
})];
|
|
601
|
+
}
|
|
602
|
+
case "chat_turn_steered":
|
|
603
|
+
case "chat_turn_steer_delivered":
|
|
604
|
+
case "chat_turn_followup_queued":
|
|
605
|
+
case "chat_turn_followup_delivered": {
|
|
606
|
+
const spanId = langfuseSpanId(traceId, chatInputSpanKey(event));
|
|
607
|
+
return [
|
|
608
|
+
ingestionEvent("span-create", event.createdAt, {
|
|
609
|
+
id: spanId,
|
|
610
|
+
traceId: langfuseTraceId(traceId),
|
|
611
|
+
parentObservationId: langfuseSpanId(traceId, chatSpanKey(event)),
|
|
612
|
+
name: chatInputObservationName(event),
|
|
613
|
+
startTime: event.createdAt,
|
|
614
|
+
input: event.details?.input,
|
|
615
|
+
metadata: lifecycleMetadata(event),
|
|
616
|
+
}),
|
|
617
|
+
ingestionEvent("span-update", event.createdAt, {
|
|
618
|
+
id: spanId,
|
|
619
|
+
traceId: langfuseTraceId(traceId),
|
|
620
|
+
endTime: event.createdAt,
|
|
621
|
+
output: event.details?.output ?? chatInputLifecycleOutput(event),
|
|
622
|
+
metadata: lifecycleMetadata(event),
|
|
623
|
+
}),
|
|
624
|
+
];
|
|
625
|
+
}
|
|
626
|
+
case "subagent_spawned":
|
|
627
|
+
case "subagent_started": {
|
|
628
|
+
const batchKey = subagentBatchSpanKey(event);
|
|
629
|
+
return [
|
|
630
|
+
...(batchKey
|
|
631
|
+
? [
|
|
632
|
+
ingestionEvent("span-create", event.createdAt, {
|
|
633
|
+
id: langfuseSpanId(traceId, batchKey),
|
|
634
|
+
traceId: langfuseTraceId(traceId),
|
|
635
|
+
parentObservationId: langfuseSpanId(traceId, chatSpanKey(event)),
|
|
636
|
+
name: "subagent fan-out",
|
|
637
|
+
startTime: event.createdAt,
|
|
638
|
+
input: event.details?.input,
|
|
639
|
+
metadata: lifecycleMetadata(event),
|
|
640
|
+
}),
|
|
641
|
+
]
|
|
642
|
+
: []),
|
|
643
|
+
ingestionEvent("span-create", event.createdAt, {
|
|
644
|
+
id: langfuseSpanId(traceId, subagentSpanKey(event)),
|
|
645
|
+
traceId: langfuseTraceId(traceId),
|
|
646
|
+
parentObservationId: langfuseSpanId(traceId, batchKey ?? subagentSpawnToolSpanKey(event) ?? chatSpanKey(event)),
|
|
647
|
+
name: "subagent",
|
|
648
|
+
startTime: event.createdAt,
|
|
649
|
+
metadata: lifecycleMetadata(event),
|
|
650
|
+
}),
|
|
651
|
+
];
|
|
652
|
+
}
|
|
653
|
+
case "subagent_completed":
|
|
654
|
+
case "subagent_failed":
|
|
655
|
+
case "subagent_cancelled":
|
|
656
|
+
return [ingestionEvent("span-update", event.createdAt, {
|
|
657
|
+
id: langfuseSpanId(traceId, subagentSpanKey(event)),
|
|
658
|
+
traceId: langfuseTraceId(traceId),
|
|
659
|
+
endTime: event.createdAt,
|
|
660
|
+
metadata: lifecycleMetadata(event),
|
|
661
|
+
...(event.error ? { output: { error: event.error } } : {}),
|
|
662
|
+
})];
|
|
663
|
+
default:
|
|
664
|
+
return assertNever(event.type);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function mapToolEventToLangfuse(event) {
|
|
668
|
+
const traceId = requireTraceId(event.traceId);
|
|
669
|
+
const spanId = langfuseSpanId(traceId, `tool:${event.sessionId}:${event.toolCallId}:${event.toolName}`);
|
|
670
|
+
const parentObservationId = langfuseParentObservationId(traceId, event);
|
|
671
|
+
if (event.status === "started") {
|
|
672
|
+
return ingestionEvent("span-create", event.createdAt, {
|
|
673
|
+
id: spanId,
|
|
674
|
+
traceId: langfuseTraceId(traceId),
|
|
675
|
+
parentObservationId,
|
|
676
|
+
name: toolObservationName(event),
|
|
677
|
+
startTime: event.createdAt,
|
|
678
|
+
input: event.args ?? {},
|
|
679
|
+
metadata: toolMetadata(event),
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
return ingestionEvent("span-update", event.createdAt, {
|
|
683
|
+
id: spanId,
|
|
684
|
+
traceId: langfuseTraceId(traceId),
|
|
685
|
+
endTime: event.createdAt,
|
|
686
|
+
metadata: toolMetadata(event),
|
|
687
|
+
...(event.error ? { output: { error: event.error } } : { output: event.details ?? {} }),
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
function mapLlmGenerationEventToLangfuse(event) {
|
|
691
|
+
const traceId = requireTraceId(event.traceId);
|
|
692
|
+
const id = langfuseSpanId(traceId, llmGenerationKey(event));
|
|
693
|
+
const parentObservationId = langfuseParentObservationId(traceId, event);
|
|
694
|
+
if (event.status === "started") {
|
|
695
|
+
return ingestionEvent("generation-create", event.createdAt, {
|
|
696
|
+
id,
|
|
697
|
+
traceId: langfuseTraceId(traceId),
|
|
698
|
+
parentObservationId,
|
|
699
|
+
name: llmGenerationObservationName(event),
|
|
700
|
+
startTime: event.createdAt,
|
|
701
|
+
model: event.model.model,
|
|
702
|
+
modelParameters: {
|
|
703
|
+
provider: event.model.provider,
|
|
704
|
+
...(event.model.thinkingLevel ? { thinkingLevel: event.model.thinkingLevel } : {}),
|
|
705
|
+
},
|
|
706
|
+
input: event.input,
|
|
707
|
+
metadata: llmGenerationMetadata(event),
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
return ingestionEvent("generation-update", event.createdAt, {
|
|
711
|
+
id,
|
|
712
|
+
traceId: langfuseTraceId(traceId),
|
|
713
|
+
endTime: event.createdAt,
|
|
714
|
+
output: event.output ?? (event.error ? { error: event.error } : undefined),
|
|
715
|
+
level: event.error ? "ERROR" : "DEFAULT",
|
|
716
|
+
...(event.error ? { statusMessage: event.error } : {}),
|
|
717
|
+
...(event.usage ? { usage: toLangfuseUsage(event.usage), usageDetails: toLangfuseUsageDetails(event.usage) } : {}),
|
|
718
|
+
model: event.model.model,
|
|
719
|
+
modelParameters: {
|
|
720
|
+
provider: event.model.provider,
|
|
721
|
+
...(event.model.thinkingLevel ? { thinkingLevel: event.model.thinkingLevel } : {}),
|
|
722
|
+
},
|
|
723
|
+
metadata: llmGenerationMetadata(event),
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
function mapRuntimeEventToOtelSpans(event, pendingStarts) {
|
|
727
|
+
if (!event.traceId) {
|
|
728
|
+
return [];
|
|
729
|
+
}
|
|
730
|
+
if (isLlmGenerationEvent(event)) {
|
|
731
|
+
return mapLlmGenerationEventToOtelSpans(event, pendingStarts);
|
|
732
|
+
}
|
|
733
|
+
return isToolEvent(event)
|
|
734
|
+
? mapToolEventToOtelSpans(event, pendingStarts)
|
|
735
|
+
: mapLifecycleEventToOtelSpans(event, pendingStarts);
|
|
736
|
+
}
|
|
737
|
+
function mapLifecycleEventToOtelSpans(event, pendingStarts) {
|
|
738
|
+
const traceId = requireTraceId(event.traceId);
|
|
739
|
+
const rootKey = chatSpanKey(event);
|
|
740
|
+
const rootSpanId = langfuseSpanId(traceId, rootKey);
|
|
741
|
+
switch (event.type) {
|
|
742
|
+
case "chat_turn_started":
|
|
743
|
+
pendingStarts.set(rootKey, {
|
|
744
|
+
traceId: langfuseTraceId(traceId),
|
|
745
|
+
spanId: rootSpanId,
|
|
746
|
+
name: "copilot-chat-turn",
|
|
747
|
+
startTime: event.createdAt,
|
|
748
|
+
attributes: {
|
|
749
|
+
...lifecycleMetadata(event),
|
|
750
|
+
...langfuseObservationAttributes({ input: event.details?.input, level: "DEFAULT" }),
|
|
751
|
+
...langfuseTraceAttributes({ input: event.details?.input }),
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
return [];
|
|
755
|
+
case "chat_turn_completed":
|
|
756
|
+
case "chat_turn_failed":
|
|
757
|
+
return [
|
|
758
|
+
completeOtelSpan(pendingStarts, rootKey, {
|
|
759
|
+
traceId: langfuseTraceId(traceId),
|
|
760
|
+
spanId: rootSpanId,
|
|
761
|
+
name: "copilot-chat-turn",
|
|
762
|
+
startTime: event.createdAt,
|
|
763
|
+
attributes: {
|
|
764
|
+
...lifecycleMetadata(event),
|
|
765
|
+
...langfuseObservationAttributes({
|
|
766
|
+
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
767
|
+
level: event.error ? "ERROR" : "DEFAULT",
|
|
768
|
+
}),
|
|
769
|
+
...langfuseTraceAttributes({
|
|
770
|
+
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
771
|
+
}),
|
|
772
|
+
},
|
|
773
|
+
}, event.createdAt, event.error),
|
|
774
|
+
];
|
|
775
|
+
case "chat_turn_steered":
|
|
776
|
+
case "chat_turn_steer_delivered":
|
|
777
|
+
case "chat_turn_followup_queued":
|
|
778
|
+
case "chat_turn_followup_delivered":
|
|
779
|
+
return [
|
|
780
|
+
{
|
|
781
|
+
traceId: langfuseTraceId(traceId),
|
|
782
|
+
spanId: langfuseSpanId(traceId, chatInputSpanKey(event)),
|
|
783
|
+
parentSpanId: rootSpanId,
|
|
784
|
+
name: chatInputObservationName(event),
|
|
785
|
+
startTime: event.createdAt,
|
|
786
|
+
endTime: event.createdAt,
|
|
787
|
+
attributes: {
|
|
788
|
+
...lifecycleMetadata(event),
|
|
789
|
+
...langfuseObservationAttributes({
|
|
790
|
+
input: event.details?.input,
|
|
791
|
+
output: event.details?.output ?? chatInputLifecycleOutput(event),
|
|
792
|
+
level: "DEFAULT",
|
|
793
|
+
}),
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
];
|
|
797
|
+
case "subagent_spawned":
|
|
798
|
+
case "subagent_started": {
|
|
799
|
+
const key = subagentSpanKey(event);
|
|
800
|
+
pendingStarts.set(key, {
|
|
801
|
+
traceId: langfuseTraceId(traceId),
|
|
802
|
+
spanId: langfuseSpanId(traceId, key),
|
|
803
|
+
parentSpanId: langfuseSpanId(traceId, subagentSpawnToolSpanKey(event) ?? rootKey),
|
|
804
|
+
name: "subagent",
|
|
805
|
+
startTime: event.createdAt,
|
|
806
|
+
attributes: {
|
|
807
|
+
...lifecycleMetadata(event),
|
|
808
|
+
...langfuseObservationAttributes({ input: event.details?.input, level: "DEFAULT" }),
|
|
809
|
+
},
|
|
810
|
+
});
|
|
811
|
+
return [];
|
|
812
|
+
}
|
|
813
|
+
case "subagent_completed":
|
|
814
|
+
case "subagent_failed":
|
|
815
|
+
case "subagent_cancelled": {
|
|
816
|
+
const key = subagentSpanKey(event);
|
|
817
|
+
return [
|
|
818
|
+
completeOtelSpan(pendingStarts, key, {
|
|
819
|
+
traceId: langfuseTraceId(traceId),
|
|
820
|
+
spanId: langfuseSpanId(traceId, key),
|
|
821
|
+
parentSpanId: langfuseSpanId(traceId, subagentSpawnToolSpanKey(event) ?? rootKey),
|
|
822
|
+
name: "subagent",
|
|
823
|
+
startTime: event.createdAt,
|
|
824
|
+
attributes: {
|
|
825
|
+
...lifecycleMetadata(event),
|
|
826
|
+
...langfuseObservationAttributes({
|
|
827
|
+
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
828
|
+
level: event.error ? "ERROR" : "DEFAULT",
|
|
829
|
+
}),
|
|
830
|
+
},
|
|
831
|
+
}, event.createdAt, event.error),
|
|
832
|
+
];
|
|
833
|
+
}
|
|
834
|
+
default:
|
|
835
|
+
return assertNever(event.type);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
function mapToolEventToOtelSpans(event, pendingStarts) {
|
|
839
|
+
const traceId = requireTraceId(event.traceId);
|
|
840
|
+
const key = toolSpanKey(event);
|
|
841
|
+
const fallbackStart = {
|
|
842
|
+
traceId: langfuseTraceId(traceId),
|
|
843
|
+
spanId: langfuseSpanId(traceId, key),
|
|
844
|
+
parentSpanId: langfuseParentObservationId(traceId, event),
|
|
845
|
+
name: toolObservationName(event),
|
|
846
|
+
startTime: event.createdAt,
|
|
847
|
+
attributes: {
|
|
848
|
+
...toolMetadata(event),
|
|
849
|
+
...langfuseObservationAttributes({
|
|
850
|
+
output: event.error ? { error: event.error } : event.details,
|
|
851
|
+
level: event.error ? "ERROR" : "DEFAULT",
|
|
852
|
+
}),
|
|
853
|
+
},
|
|
854
|
+
};
|
|
855
|
+
if (event.status === "started") {
|
|
856
|
+
pendingStarts.set(key, {
|
|
857
|
+
...fallbackStart,
|
|
858
|
+
attributes: {
|
|
859
|
+
...fallbackStart.attributes,
|
|
860
|
+
...(event.args ? { args: event.args } : {}),
|
|
861
|
+
...langfuseObservationAttributes({ input: event.args, level: "DEFAULT" }),
|
|
862
|
+
},
|
|
863
|
+
});
|
|
864
|
+
return [];
|
|
865
|
+
}
|
|
866
|
+
return [completeOtelSpan(pendingStarts, key, fallbackStart, event.createdAt, event.error)];
|
|
867
|
+
}
|
|
868
|
+
function mapLlmGenerationEventToOtelSpans(event, pendingStarts) {
|
|
869
|
+
const traceId = requireTraceId(event.traceId);
|
|
870
|
+
const key = llmGenerationKey(event);
|
|
871
|
+
const fallbackStart = {
|
|
872
|
+
traceId: langfuseTraceId(traceId),
|
|
873
|
+
spanId: langfuseSpanId(traceId, key),
|
|
874
|
+
parentSpanId: langfuseParentObservationId(traceId, event),
|
|
875
|
+
name: llmGenerationObservationName(event),
|
|
876
|
+
startTime: event.createdAt,
|
|
877
|
+
attributes: {
|
|
878
|
+
...llmGenerationMetadata(event),
|
|
879
|
+
"langfuse.observation.type": "generation",
|
|
880
|
+
model: event.model.model,
|
|
881
|
+
provider: event.model.provider,
|
|
882
|
+
...(event.model.thinkingLevel ? { thinkingLevel: event.model.thinkingLevel } : {}),
|
|
883
|
+
...langfuseObservationAttributes({
|
|
884
|
+
output: event.output ?? (event.error ? { error: event.error } : undefined),
|
|
885
|
+
level: event.error ? "ERROR" : "DEFAULT",
|
|
886
|
+
}),
|
|
887
|
+
...(event.usage ? flattenUsageAttributes(event.usage) : {}),
|
|
888
|
+
},
|
|
889
|
+
};
|
|
890
|
+
if (event.status === "started") {
|
|
891
|
+
pendingStarts.set(key, {
|
|
892
|
+
...fallbackStart,
|
|
893
|
+
attributes: {
|
|
894
|
+
...fallbackStart.attributes,
|
|
895
|
+
...langfuseObservationAttributes({ input: event.input, level: "DEFAULT" }),
|
|
896
|
+
},
|
|
897
|
+
});
|
|
898
|
+
return [];
|
|
899
|
+
}
|
|
900
|
+
return [completeOtelSpan(pendingStarts, key, fallbackStart, event.createdAt, event.error)];
|
|
901
|
+
}
|
|
902
|
+
function completeOtelSpan(pendingStarts, key, fallbackStart, endTime, error) {
|
|
903
|
+
const started = pendingStarts.get(key) ?? fallbackStart;
|
|
904
|
+
pendingStarts.delete(key);
|
|
905
|
+
return {
|
|
906
|
+
...started,
|
|
907
|
+
endTime: ensureEndAfterStart(started.startTime, endTime),
|
|
908
|
+
attributes: {
|
|
909
|
+
...started.attributes,
|
|
910
|
+
...fallbackStart.attributes,
|
|
911
|
+
},
|
|
912
|
+
...(error ? { status: { code: 2, message: error } } : { status: { code: 1 } }),
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
function langfuseObservationAttributes(input) {
|
|
916
|
+
return {
|
|
917
|
+
"langfuse.observation.type": "span",
|
|
918
|
+
...(input.input !== undefined
|
|
919
|
+
? {
|
|
920
|
+
"langfuse.observation.input": toLangfuseObservationPayload(input.input),
|
|
921
|
+
"input.value": toLangfuseObservationPayload(input.input),
|
|
922
|
+
}
|
|
923
|
+
: {}),
|
|
924
|
+
...(input.output !== undefined
|
|
925
|
+
? {
|
|
926
|
+
"langfuse.observation.output": toLangfuseObservationPayload(input.output),
|
|
927
|
+
"output.value": toLangfuseObservationPayload(input.output),
|
|
928
|
+
}
|
|
929
|
+
: {}),
|
|
930
|
+
...(input.level ? { "langfuse.observation.level": input.level } : {}),
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
function langfuseTraceAttributes(input) {
|
|
934
|
+
return {
|
|
935
|
+
...(input.input !== undefined ? { "langfuse.trace.input": toLangfuseTracePayload(input.input) } : {}),
|
|
936
|
+
...(input.output !== undefined ? { "langfuse.trace.output": toLangfuseTracePayload(input.output) } : {}),
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
function toLangfuseObservationPayload(value) {
|
|
940
|
+
return JSON.stringify(value);
|
|
941
|
+
}
|
|
942
|
+
function toLangfuseTracePayload(value) {
|
|
943
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
944
|
+
}
|
|
945
|
+
function chatSpanKey(event) {
|
|
946
|
+
const sessionId = event.parentSessionId ?? event.sessionId;
|
|
947
|
+
const conversationId = event.parentSessionId ?? event.conversationId ?? sessionId;
|
|
948
|
+
return `chat:${sessionId}:${conversationId}`;
|
|
949
|
+
}
|
|
950
|
+
function chatInputSpanKey(event) {
|
|
951
|
+
return `chat-input:${event.sessionId}:${event.id}`;
|
|
952
|
+
}
|
|
953
|
+
function subagentSpanKey(event) {
|
|
954
|
+
return `subagent:${event.runId ?? event.childSessionId ?? event.id}`;
|
|
955
|
+
}
|
|
956
|
+
function subagentBatchSpanKey(event) {
|
|
957
|
+
const batchId = event.spawnBatchId ?? stringFromJsonObject(event.details, "spawnBatchId");
|
|
958
|
+
return event.traceId && batchId ? `subagent-batch:${event.traceId}:${batchId}` : undefined;
|
|
959
|
+
}
|
|
960
|
+
function telemetryEventSubagentSpanKey(event) {
|
|
961
|
+
return event.runId || event.childSessionId
|
|
962
|
+
? `subagent:${event.runId ?? event.childSessionId}`
|
|
963
|
+
: undefined;
|
|
964
|
+
}
|
|
965
|
+
function langfuseParentObservationId(traceId, event) {
|
|
966
|
+
const subagentKey = telemetryEventSubagentSpanKey(event);
|
|
967
|
+
return langfuseSpanId(traceId, subagentKey ?? chatSpanKey(event));
|
|
968
|
+
}
|
|
969
|
+
function toolSpanKey(event) {
|
|
970
|
+
return `tool:${event.sessionId}:${event.toolCallId}:${event.toolName}`;
|
|
971
|
+
}
|
|
972
|
+
function subagentSpawnToolSpanKey(event) {
|
|
973
|
+
return event.parentSessionId && event.parentToolCallId
|
|
974
|
+
? `tool:${event.parentSessionId}:${event.parentToolCallId}:sessions_spawn`
|
|
975
|
+
: undefined;
|
|
976
|
+
}
|
|
977
|
+
function llmGenerationKey(event) {
|
|
978
|
+
return `llm-generation:${event.sessionId}:${event.llmGenerationId}`;
|
|
979
|
+
}
|
|
980
|
+
function toolObservationName(event) {
|
|
981
|
+
const summary = summarizeToolArgsForName(event.toolName, event.args);
|
|
982
|
+
return summary ? `${event.toolName} [${summary}]` : event.toolName;
|
|
983
|
+
}
|
|
984
|
+
function chatInputObservationName(event) {
|
|
985
|
+
const prefix = chatInputObservationPrefix(event.type);
|
|
986
|
+
const input = typeof event.details?.input === "string" ? truncateObservationSummary(event.details.input) : undefined;
|
|
987
|
+
return input ? `${prefix} [${input}]` : prefix;
|
|
988
|
+
}
|
|
989
|
+
function chatInputObservationPrefix(type) {
|
|
990
|
+
if (type === "chat_turn_steered") {
|
|
991
|
+
return "chat-steer";
|
|
992
|
+
}
|
|
993
|
+
if (type === "chat_turn_steer_delivered") {
|
|
994
|
+
return "chat-steer-delivered";
|
|
995
|
+
}
|
|
996
|
+
if (type === "chat_turn_followup_delivered") {
|
|
997
|
+
return "chat-followup-delivered";
|
|
998
|
+
}
|
|
999
|
+
return "chat-followup";
|
|
1000
|
+
}
|
|
1001
|
+
function chatInputLifecycleOutput(event) {
|
|
1002
|
+
return event.type === "chat_turn_steer_delivered" || event.type === "chat_turn_followup_delivered"
|
|
1003
|
+
? { delivered: true, turnMode: event.details?.turnMode }
|
|
1004
|
+
: { accepted: true, turnMode: event.details?.turnMode };
|
|
1005
|
+
}
|
|
1006
|
+
function summarizeToolArgsForName(toolName, args) {
|
|
1007
|
+
if (!args) {
|
|
1008
|
+
return undefined;
|
|
1009
|
+
}
|
|
1010
|
+
const pathValue = stringArg(args, "path") ?? stringArg(args, "filePath") ?? stringArg(args, "absolutePath");
|
|
1011
|
+
if (pathValue) {
|
|
1012
|
+
return truncateObservationSummary(pathValue);
|
|
1013
|
+
}
|
|
1014
|
+
const command = stringArg(args, "command");
|
|
1015
|
+
if (command) {
|
|
1016
|
+
return truncateObservationSummary(command);
|
|
1017
|
+
}
|
|
1018
|
+
const query = stringArg(args, "query");
|
|
1019
|
+
if (query) {
|
|
1020
|
+
return truncateObservationSummary(query);
|
|
1021
|
+
}
|
|
1022
|
+
const task = stringArg(args, "task");
|
|
1023
|
+
if (task && toolName === "sessions_spawn") {
|
|
1024
|
+
return truncateObservationSummary(task);
|
|
1025
|
+
}
|
|
1026
|
+
const code = stringArg(args, "code");
|
|
1027
|
+
if (code) {
|
|
1028
|
+
return truncateObservationSummary(code);
|
|
1029
|
+
}
|
|
1030
|
+
const name = stringArg(args, "name");
|
|
1031
|
+
if (name && toolName.startsWith("mcp_")) {
|
|
1032
|
+
return truncateObservationSummary(name);
|
|
1033
|
+
}
|
|
1034
|
+
return undefined;
|
|
1035
|
+
}
|
|
1036
|
+
function llmGenerationObservationName(event) {
|
|
1037
|
+
return `llm-generation [${event.runId || event.childSessionId ? "subagent" : "main"}] [${summarizeLlmGenerationInputForName(event.input)}]`;
|
|
1038
|
+
}
|
|
1039
|
+
function summarizeLlmGenerationInputForName(input) {
|
|
1040
|
+
if (typeof input === "string") {
|
|
1041
|
+
return truncateObservationSummary(input);
|
|
1042
|
+
}
|
|
1043
|
+
if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
1044
|
+
const continuation = input.continuation === true;
|
|
1045
|
+
const index = typeof input.llmGenerationIndex === "number" ? input.llmGenerationIndex : undefined;
|
|
1046
|
+
const toolResults = typeof input.previousToolResultCount === "number" ? input.previousToolResultCount : undefined;
|
|
1047
|
+
if (continuation) {
|
|
1048
|
+
return truncateObservationSummary(`continuation${index !== undefined ? ` #${index}` : ""}${toolResults !== undefined ? ` after ${toolResults} tool result(s)` : ""}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return "request";
|
|
1052
|
+
}
|
|
1053
|
+
function stringArg(args, key) {
|
|
1054
|
+
const value = args[key];
|
|
1055
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
1056
|
+
}
|
|
1057
|
+
function stringFromJsonObject(value, key) {
|
|
1058
|
+
const field = value?.[key];
|
|
1059
|
+
return typeof field === "string" && field.trim() ? field.trim() : undefined;
|
|
1060
|
+
}
|
|
1061
|
+
function truncateObservationSummary(value, maxLength = 90) {
|
|
1062
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
1063
|
+
return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1)}…` : normalized;
|
|
1064
|
+
}
|
|
1065
|
+
function ensureEndAfterStart(startTime, endTime) {
|
|
1066
|
+
return Date.parse(endTime) >= Date.parse(startTime) ? endTime : startTime;
|
|
1067
|
+
}
|
|
1068
|
+
function toOtelTracePayload(spans, config) {
|
|
1069
|
+
return {
|
|
1070
|
+
resourceSpans: [
|
|
1071
|
+
{
|
|
1072
|
+
resource: {
|
|
1073
|
+
attributes: [
|
|
1074
|
+
otelAttribute("service.name", config.serviceName ?? "pi-server"),
|
|
1075
|
+
...(config.serviceVersion ? [otelAttribute("service.version", config.serviceVersion)] : []),
|
|
1076
|
+
otelAttribute("telemetry.sdk.name", "@amaster.ai/pi-telemetry"),
|
|
1077
|
+
],
|
|
1078
|
+
},
|
|
1079
|
+
scopeSpans: [
|
|
1080
|
+
{
|
|
1081
|
+
scope: { name: "@amaster.ai/pi-telemetry", version: "0.1.0" },
|
|
1082
|
+
spans: spans.map(toOtelSpanPayload),
|
|
1083
|
+
},
|
|
1084
|
+
],
|
|
1085
|
+
},
|
|
1086
|
+
],
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
function toOtelSpanPayload(span) {
|
|
1090
|
+
return {
|
|
1091
|
+
traceId: span.traceId,
|
|
1092
|
+
spanId: span.spanId,
|
|
1093
|
+
...(span.parentSpanId ? { parentSpanId: span.parentSpanId } : {}),
|
|
1094
|
+
name: span.name,
|
|
1095
|
+
kind: 1,
|
|
1096
|
+
startTimeUnixNano: toUnixNano(span.startTime),
|
|
1097
|
+
endTimeUnixNano: toUnixNano(span.endTime),
|
|
1098
|
+
attributes: Object.entries(span.attributes)
|
|
1099
|
+
.filter((entry) => entry[1] !== undefined)
|
|
1100
|
+
.map(([key, value]) => otelAttribute(key, value)),
|
|
1101
|
+
...(span.status ? { status: span.status } : {}),
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
function otelAttribute(key, value) {
|
|
1105
|
+
return { key, value: otelAttributeValue(value) };
|
|
1106
|
+
}
|
|
1107
|
+
function otelAttributeValue(value) {
|
|
1108
|
+
if (typeof value === "boolean") {
|
|
1109
|
+
return { boolValue: value };
|
|
1110
|
+
}
|
|
1111
|
+
if (typeof value === "number") {
|
|
1112
|
+
return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
|
|
1113
|
+
}
|
|
1114
|
+
if (typeof value === "string") {
|
|
1115
|
+
return { stringValue: value };
|
|
1116
|
+
}
|
|
1117
|
+
return { stringValue: JSON.stringify(value) };
|
|
1118
|
+
}
|
|
1119
|
+
function toUnixNano(iso) {
|
|
1120
|
+
const millis = Date.parse(iso);
|
|
1121
|
+
return String(BigInt(Number.isFinite(millis) ? millis : Date.now()) * 1000000n);
|
|
1122
|
+
}
|
|
1123
|
+
function normalizeOtelTracesEndpoint(endpoint) {
|
|
1124
|
+
return endpoint.endsWith("/v1/traces") ? endpoint : `${endpoint.replace(/\/+$/, "")}/v1/traces`;
|
|
1125
|
+
}
|
|
1126
|
+
function shortCorrelationId(value) {
|
|
1127
|
+
if (!value) {
|
|
1128
|
+
return undefined;
|
|
1129
|
+
}
|
|
1130
|
+
const normalized = value.startsWith("trace:") ? value.slice("trace:".length) : value;
|
|
1131
|
+
const uuid = normalized.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i)?.[0];
|
|
1132
|
+
if (uuid) {
|
|
1133
|
+
return uuid.slice(0, 8);
|
|
1134
|
+
}
|
|
1135
|
+
if (/^[0-9a-f]{32}$/i.test(normalized)) {
|
|
1136
|
+
return normalized.slice(0, 8);
|
|
1137
|
+
}
|
|
1138
|
+
return normalized.length > 24 ? normalized.slice(0, 12) : normalized;
|
|
1139
|
+
}
|
|
1140
|
+
function compactSessionId(value) {
|
|
1141
|
+
if (!value) {
|
|
1142
|
+
return undefined;
|
|
1143
|
+
}
|
|
1144
|
+
const [root, ...subagents] = value.split(":subagent:");
|
|
1145
|
+
if (subagents.length === 0) {
|
|
1146
|
+
return value;
|
|
1147
|
+
}
|
|
1148
|
+
return [root, ...subagents.map((sessionId) => `sub:${shortCorrelationId(sessionId) ?? sessionId}`)].join("/");
|
|
1149
|
+
}
|
|
1150
|
+
function lineageMetadata(event) {
|
|
1151
|
+
const sessionId = compactSessionId(event.sessionId);
|
|
1152
|
+
const conversationId = compactSessionId(event.conversationId);
|
|
1153
|
+
const taskRunId = shortCorrelationId(event.taskRunId ?? event.runId);
|
|
1154
|
+
return {
|
|
1155
|
+
...(sessionId ? { sessionId } : {}),
|
|
1156
|
+
...(conversationId && conversationId !== sessionId ? { conversationId } : {}),
|
|
1157
|
+
...(event.parentSessionId ? { parentSessionId: compactSessionId(event.parentSessionId) } : {}),
|
|
1158
|
+
...(event.childSessionId ? { childSessionId: compactSessionId(event.childSessionId) } : {}),
|
|
1159
|
+
...(taskRunId ? { taskRunId } : {}),
|
|
1160
|
+
...(event.spawnBatchId ? { spawnBatchId: shortCorrelationId(event.spawnBatchId) } : {}),
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
function lifecycleMetadata(event) {
|
|
1164
|
+
return {
|
|
1165
|
+
eventType: event.type,
|
|
1166
|
+
...lineageMetadata(event),
|
|
1167
|
+
...(event.durationMs !== undefined ? { durationMs: event.durationMs } : {}),
|
|
1168
|
+
...(event.model ? { model: `${event.model.provider}/${event.model.model}` } : {}),
|
|
1169
|
+
...(event.model?.thinkingLevel ? { thinkingLevel: event.model.thinkingLevel } : {}),
|
|
1170
|
+
...(event.toolPolicyProfile ? { toolPolicyProfile: event.toolPolicyProfile } : {}),
|
|
1171
|
+
...(event.details ? { details: event.details } : {}),
|
|
1172
|
+
...(event.error ? { error: event.error } : {}),
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
function toolMetadata(event) {
|
|
1176
|
+
return {
|
|
1177
|
+
...lineageMetadata(event),
|
|
1178
|
+
toolCallId: event.toolCallId,
|
|
1179
|
+
toolName: event.toolName,
|
|
1180
|
+
status: event.status,
|
|
1181
|
+
...(event.durationMs !== undefined ? { durationMs: event.durationMs } : {}),
|
|
1182
|
+
...(event.details ? { details: event.details } : {}),
|
|
1183
|
+
...(event.error ? { error: event.error } : {}),
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
function llmGenerationMetadata(event) {
|
|
1187
|
+
return {
|
|
1188
|
+
...lineageMetadata(event),
|
|
1189
|
+
llmGenerationId: event.llmGenerationId,
|
|
1190
|
+
status: event.status,
|
|
1191
|
+
model: `${event.model.provider}/${event.model.model}`,
|
|
1192
|
+
...(event.model.thinkingLevel ? { thinkingLevel: event.model.thinkingLevel } : {}),
|
|
1193
|
+
...(event.durationMs !== undefined ? { durationMs: event.durationMs } : {}),
|
|
1194
|
+
...(event.responseId ? { responseId: event.responseId } : {}),
|
|
1195
|
+
...(event.stopReason ? { stopReason: event.stopReason } : {}),
|
|
1196
|
+
...(event.usage ? { usage: event.usage } : {}),
|
|
1197
|
+
...(event.error ? { error: event.error } : {}),
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
function toLangfuseUsage(usage) {
|
|
1201
|
+
return {
|
|
1202
|
+
...(usage.input !== undefined ? { input: usage.input } : {}),
|
|
1203
|
+
...(usage.output !== undefined ? { output: usage.output } : {}),
|
|
1204
|
+
...(usage.totalTokens !== undefined ? { total: usage.totalTokens } : {}),
|
|
1205
|
+
unit: "TOKENS",
|
|
1206
|
+
...(usage.cost?.input !== undefined ? { inputCost: usage.cost.input } : {}),
|
|
1207
|
+
...(usage.cost?.output !== undefined ? { outputCost: usage.cost.output } : {}),
|
|
1208
|
+
...(usage.cost?.total !== undefined ? { totalCost: usage.cost.total } : {}),
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
function toLangfuseUsageDetails(usage) {
|
|
1212
|
+
return {
|
|
1213
|
+
...(usage.input !== undefined ? { input: usage.input } : {}),
|
|
1214
|
+
...(usage.output !== undefined ? { output: usage.output } : {}),
|
|
1215
|
+
...(usage.cacheRead !== undefined ? { cache_read: usage.cacheRead } : {}),
|
|
1216
|
+
...(usage.cacheWrite !== undefined ? { cache_write: usage.cacheWrite } : {}),
|
|
1217
|
+
...(usage.totalTokens !== undefined ? { total: usage.totalTokens } : {}),
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
function flattenUsageAttributes(usage) {
|
|
1221
|
+
return {
|
|
1222
|
+
...(usage.input !== undefined ? { "usage.input": usage.input } : {}),
|
|
1223
|
+
...(usage.output !== undefined ? { "usage.output": usage.output } : {}),
|
|
1224
|
+
...(usage.cacheRead !== undefined ? { "usage.cache_read": usage.cacheRead } : {}),
|
|
1225
|
+
...(usage.cacheWrite !== undefined ? { "usage.cache_write": usage.cacheWrite } : {}),
|
|
1226
|
+
...(usage.totalTokens !== undefined ? { "usage.total_tokens": usage.totalTokens } : {}),
|
|
1227
|
+
...(usage.cost?.total !== undefined ? { "usage.cost.total": usage.cost.total } : {}),
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
function ingestionEvent(type, timestamp, body) {
|
|
1231
|
+
return {
|
|
1232
|
+
id: randomUUID(),
|
|
1233
|
+
timestamp,
|
|
1234
|
+
type,
|
|
1235
|
+
body,
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
function langfuseTraceId(traceId) {
|
|
1239
|
+
return stableHex(`trace:${traceId}`, 32);
|
|
1240
|
+
}
|
|
1241
|
+
function langfuseSpanId(traceId, key) {
|
|
1242
|
+
return stableHex(`span:${traceId}:${key}`, 16);
|
|
1243
|
+
}
|
|
1244
|
+
function stableHex(input, length) {
|
|
1245
|
+
return createHash("sha256").update(input).digest("hex").slice(0, length);
|
|
1246
|
+
}
|
|
1247
|
+
function applyTelemetryRedaction(config, event) {
|
|
1248
|
+
const redacted = config.redactEvent ? config.redactEvent(event) : event;
|
|
1249
|
+
if (!redacted) {
|
|
1250
|
+
return undefined;
|
|
1251
|
+
}
|
|
1252
|
+
return config.includePayloads === false ? stripTelemetryPayloads(redacted) : redacted;
|
|
1253
|
+
}
|
|
1254
|
+
function stripTelemetryPayloads(event) {
|
|
1255
|
+
if (isLlmGenerationEvent(event)) {
|
|
1256
|
+
const { input: _input, output: _output, ...rest } = event;
|
|
1257
|
+
return rest;
|
|
1258
|
+
}
|
|
1259
|
+
if (isToolEvent(event)) {
|
|
1260
|
+
const { args: _args, details, ...rest } = event;
|
|
1261
|
+
return {
|
|
1262
|
+
...rest,
|
|
1263
|
+
...(details ? { details: redactJsonObjectPayload(details) } : {}),
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
const { details, ...rest } = event;
|
|
1267
|
+
return {
|
|
1268
|
+
...rest,
|
|
1269
|
+
...(details ? { details: redactJsonObjectPayload(details) } : {}),
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
function redactJsonObjectPayload(input) {
|
|
1273
|
+
const { input: _input, output: _output, args: _args, content: _content, code: _code, ...rest } = input;
|
|
1274
|
+
return rest;
|
|
1275
|
+
}
|
|
1276
|
+
function isToolEvent(event) {
|
|
1277
|
+
return "toolCallId" in event;
|
|
1278
|
+
}
|
|
1279
|
+
function isLlmGenerationEvent(event) {
|
|
1280
|
+
return "llmGenerationId" in event;
|
|
1281
|
+
}
|
|
1282
|
+
function parseBoolean(value) {
|
|
1283
|
+
return value === "1" || value === "true" || value === "TRUE" || value === "yes";
|
|
1284
|
+
}
|
|
1285
|
+
function parseBooleanWithDefault(value, fallback) {
|
|
1286
|
+
if (value === undefined) {
|
|
1287
|
+
return fallback;
|
|
1288
|
+
}
|
|
1289
|
+
return parseBoolean(value);
|
|
1290
|
+
}
|
|
1291
|
+
function parseLangfuseTransport(value) {
|
|
1292
|
+
const normalized = value?.trim().toLowerCase();
|
|
1293
|
+
return normalized === "sdk" || normalized === "ingestion" ? normalized : undefined;
|
|
1294
|
+
}
|
|
1295
|
+
function parsePositiveInteger(value, fallback) {
|
|
1296
|
+
const parsed = Number(value);
|
|
1297
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
1298
|
+
}
|
|
1299
|
+
function trim(value) {
|
|
1300
|
+
const trimmed = value?.trim();
|
|
1301
|
+
return trimmed ? trimmed : undefined;
|
|
1302
|
+
}
|
|
1303
|
+
function assertNever(value) {
|
|
1304
|
+
throw new Error(`Unexpected telemetry event type: ${String(value)}`);
|
|
1305
|
+
}
|
|
1306
|
+
function requireTraceId(traceId) {
|
|
1307
|
+
if (!traceId) {
|
|
1308
|
+
throw new Error("Telemetry event is missing traceId");
|
|
1309
|
+
}
|
|
1310
|
+
return traceId;
|
|
1311
|
+
}
|
|
1312
|
+
//# sourceMappingURL=langfuse.js.map
|