@amaster.ai/pi-telemetry 0.1.0-beta.0 → 0.1.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/README.md +90 -37
- package/dist/config.d.ts +27 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -0
- package/dist/extension.d.ts +3 -0
- package/dist/extension.d.ts.map +1 -0
- package/dist/extension.js +313 -0
- package/dist/extension.js.map +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/langfuse.d.ts +4 -25
- package/dist/langfuse.d.ts.map +1 -1
- package/dist/langfuse.js +184 -412
- package/dist/langfuse.js.map +1 -1
- package/dist/otel.d.ts +6 -3
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +32 -9
- package/dist/otel.js.map +1 -1
- package/package.json +38 -3
- package/preview.png +0 -0
package/dist/langfuse.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createHash
|
|
2
|
-
import { Langfuse } from
|
|
3
|
-
import { NoopRuntimeEventExporter, } from
|
|
4
|
-
const DEFAULT_LANGFUSE_BASE_URL =
|
|
1
|
+
import { createHash } 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
5
|
const DEFAULT_FLUSH_AT = 20;
|
|
6
6
|
const DEFAULT_FLUSH_INTERVAL_MS = 5000;
|
|
7
7
|
export class LangfuseSdkRuntimeEventExporter {
|
|
@@ -50,20 +50,20 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
50
50
|
const rootKey = chatSpanKey(event);
|
|
51
51
|
const rootSpanId = langfuseSpanId(traceId, rootKey);
|
|
52
52
|
switch (event.type) {
|
|
53
|
-
case
|
|
53
|
+
case 'chat_turn_started': {
|
|
54
54
|
const body = {
|
|
55
55
|
id: rootSpanId,
|
|
56
|
-
name:
|
|
56
|
+
name: 'chat-turn',
|
|
57
57
|
startTime: event.createdAt,
|
|
58
58
|
input: event.details?.input,
|
|
59
|
-
level:
|
|
59
|
+
level: 'DEFAULT',
|
|
60
60
|
metadata: lifecycleMetadata(event),
|
|
61
61
|
};
|
|
62
62
|
const span = this.spans.get(rootKey);
|
|
63
63
|
if (span) {
|
|
64
64
|
span.update({
|
|
65
65
|
input: event.details?.input,
|
|
66
|
-
level:
|
|
66
|
+
level: 'DEFAULT',
|
|
67
67
|
metadata: lifecycleMetadata(event),
|
|
68
68
|
});
|
|
69
69
|
}
|
|
@@ -72,37 +72,36 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
72
72
|
}
|
|
73
73
|
break;
|
|
74
74
|
}
|
|
75
|
-
case
|
|
76
|
-
case
|
|
75
|
+
case 'chat_turn_completed':
|
|
76
|
+
case 'chat_turn_failed': {
|
|
77
77
|
const output = event.details?.output ?? (event.error ? { error: event.error } : undefined);
|
|
78
78
|
this.closeSdkSubagentBatchSpans(traceId, event);
|
|
79
79
|
trace.update({
|
|
80
80
|
sessionId: event.sessionId,
|
|
81
|
-
name:
|
|
81
|
+
name: 'chat-turn',
|
|
82
82
|
output,
|
|
83
83
|
metadata: lifecycleMetadata(event),
|
|
84
84
|
});
|
|
85
85
|
const span = this.spans.get(rootKey) ??
|
|
86
86
|
trace.span({
|
|
87
87
|
id: rootSpanId,
|
|
88
|
-
name:
|
|
88
|
+
name: 'chat-turn',
|
|
89
89
|
startTime: event.createdAt,
|
|
90
90
|
metadata: lifecycleMetadata(event),
|
|
91
91
|
});
|
|
92
92
|
span.update({
|
|
93
93
|
output,
|
|
94
94
|
endTime: event.createdAt,
|
|
95
|
-
level: event.error ?
|
|
95
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
96
96
|
...(event.error ? { statusMessage: event.error } : {}),
|
|
97
97
|
metadata: lifecycleMetadata(event),
|
|
98
98
|
});
|
|
99
|
-
this.spans.delete(rootKey);
|
|
100
99
|
break;
|
|
101
100
|
}
|
|
102
|
-
case
|
|
103
|
-
case
|
|
104
|
-
case
|
|
105
|
-
case
|
|
101
|
+
case 'chat_turn_steered':
|
|
102
|
+
case 'chat_turn_steer_delivered':
|
|
103
|
+
case 'chat_turn_followup_queued':
|
|
104
|
+
case 'chat_turn_followup_delivered': {
|
|
106
105
|
const output = event.details?.output ?? chatInputLifecycleOutput(event);
|
|
107
106
|
this.getSdkSpanParent(trace, rootKey).span({
|
|
108
107
|
id: langfuseSpanId(traceId, chatInputSpanKey(event)),
|
|
@@ -111,28 +110,28 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
111
110
|
endTime: event.createdAt,
|
|
112
111
|
input: event.details?.input,
|
|
113
112
|
output,
|
|
114
|
-
level:
|
|
113
|
+
level: 'DEFAULT',
|
|
115
114
|
metadata: lifecycleMetadata(event),
|
|
116
115
|
});
|
|
117
116
|
break;
|
|
118
117
|
}
|
|
119
|
-
case
|
|
120
|
-
case
|
|
118
|
+
case 'subagent_spawned':
|
|
119
|
+
case 'subagent_started': {
|
|
121
120
|
const key = subagentSpanKey(event);
|
|
122
|
-
|
|
121
|
+
this.ensureSdkRootSpan(trace, rootKey, event);
|
|
123
122
|
const body = {
|
|
124
123
|
id: langfuseSpanId(traceId, key),
|
|
125
|
-
name:
|
|
124
|
+
name: 'subagent',
|
|
126
125
|
startTime: event.createdAt,
|
|
127
126
|
input: event.details?.input,
|
|
128
|
-
level:
|
|
127
|
+
level: 'DEFAULT',
|
|
129
128
|
metadata: lifecycleMetadata(event),
|
|
130
129
|
};
|
|
131
130
|
const span = this.spans.get(key);
|
|
132
131
|
if (span) {
|
|
133
132
|
span.update({
|
|
134
133
|
input: event.details?.input,
|
|
135
|
-
level:
|
|
134
|
+
level: 'DEFAULT',
|
|
136
135
|
metadata: lifecycleMetadata(event),
|
|
137
136
|
});
|
|
138
137
|
}
|
|
@@ -141,27 +140,26 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
141
140
|
}
|
|
142
141
|
break;
|
|
143
142
|
}
|
|
144
|
-
case
|
|
145
|
-
case
|
|
146
|
-
case
|
|
143
|
+
case 'subagent_completed':
|
|
144
|
+
case 'subagent_failed':
|
|
145
|
+
case 'subagent_cancelled': {
|
|
147
146
|
const key = subagentSpanKey(event);
|
|
148
147
|
const output = event.details?.output ?? (event.error ? { error: event.error } : undefined);
|
|
149
|
-
|
|
148
|
+
this.ensureSdkRootSpan(trace, rootKey, event);
|
|
150
149
|
const span = this.spans.get(key) ??
|
|
151
150
|
this.getSdkSubagentParent(trace, rootKey, event).span({
|
|
152
151
|
id: langfuseSpanId(traceId, key),
|
|
153
|
-
name:
|
|
152
|
+
name: 'subagent',
|
|
154
153
|
startTime: event.createdAt,
|
|
155
154
|
metadata: lifecycleMetadata(event),
|
|
156
155
|
});
|
|
157
156
|
span.update({
|
|
158
157
|
output,
|
|
159
158
|
endTime: event.createdAt,
|
|
160
|
-
level: event.error ?
|
|
159
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
161
160
|
...(event.error ? { statusMessage: event.error } : {}),
|
|
162
161
|
metadata: lifecycleMetadata(event),
|
|
163
162
|
});
|
|
164
|
-
this.spans.delete(key);
|
|
165
163
|
break;
|
|
166
164
|
}
|
|
167
165
|
default:
|
|
@@ -175,20 +173,20 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
175
173
|
const parent = this.getSdkEventParent(trace, rootKey, event);
|
|
176
174
|
const key = toolSpanKey(event);
|
|
177
175
|
const id = langfuseSpanId(traceId, key);
|
|
178
|
-
if (event.status ===
|
|
176
|
+
if (event.status === 'started') {
|
|
179
177
|
const body = {
|
|
180
178
|
id,
|
|
181
179
|
name: toolObservationName(event),
|
|
182
180
|
startTime: event.createdAt,
|
|
183
181
|
input: event.args ? { args: event.args } : undefined,
|
|
184
|
-
level:
|
|
182
|
+
level: 'DEFAULT',
|
|
185
183
|
metadata: toolMetadata(event),
|
|
186
184
|
};
|
|
187
185
|
const span = this.spans.get(key);
|
|
188
186
|
if (span) {
|
|
189
187
|
span.update({
|
|
190
188
|
input: event.args ? { args: event.args } : undefined,
|
|
191
|
-
level:
|
|
189
|
+
level: 'DEFAULT',
|
|
192
190
|
metadata: toolMetadata(event),
|
|
193
191
|
});
|
|
194
192
|
}
|
|
@@ -208,7 +206,7 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
208
206
|
span.update({
|
|
209
207
|
output,
|
|
210
208
|
endTime: event.createdAt,
|
|
211
|
-
level: event.error ?
|
|
209
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
212
210
|
...(event.error ? { statusMessage: event.error } : {}),
|
|
213
211
|
metadata: toolMetadata(event),
|
|
214
212
|
});
|
|
@@ -221,7 +219,7 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
221
219
|
const parent = this.getSdkEventParent(trace, rootKey, event);
|
|
222
220
|
const key = llmGenerationKey(event);
|
|
223
221
|
const id = langfuseSpanId(traceId, key);
|
|
224
|
-
if (event.status ===
|
|
222
|
+
if (event.status === 'started') {
|
|
225
223
|
const body = {
|
|
226
224
|
id,
|
|
227
225
|
name: llmGenerationObservationName(event),
|
|
@@ -257,9 +255,11 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
257
255
|
generation.update({
|
|
258
256
|
output: event.output ?? (event.error ? { error: event.error } : undefined),
|
|
259
257
|
endTime: event.createdAt,
|
|
260
|
-
level: event.error ?
|
|
258
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
261
259
|
...(event.error ? { statusMessage: event.error } : {}),
|
|
262
|
-
...(event.usage
|
|
260
|
+
...(event.usage
|
|
261
|
+
? { usage: toLangfuseUsage(event.usage), usageDetails: toLangfuseUsageDetails(event.usage) }
|
|
262
|
+
: {}),
|
|
263
263
|
model: event.model.model,
|
|
264
264
|
modelParameters: {
|
|
265
265
|
provider: event.model.provider,
|
|
@@ -278,7 +278,7 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
278
278
|
const trace = this.client.trace({
|
|
279
279
|
id: langfuseTraceId(traceId),
|
|
280
280
|
sessionId: event.sessionId,
|
|
281
|
-
name:
|
|
281
|
+
name: 'chat-turn',
|
|
282
282
|
timestamp: event.createdAt,
|
|
283
283
|
input: !isToolEvent(event) && !isLlmGenerationEvent(event) ? event.details?.input : undefined,
|
|
284
284
|
metadata: isToolEvent(event)
|
|
@@ -310,10 +310,10 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
310
310
|
}
|
|
311
311
|
const batchSpan = rootSpan.span({
|
|
312
312
|
id: langfuseSpanId(requireTraceId(event.traceId), batchKey),
|
|
313
|
-
name:
|
|
313
|
+
name: 'subagent fan-out',
|
|
314
314
|
startTime: event.createdAt,
|
|
315
315
|
input: event.details?.input,
|
|
316
|
-
level:
|
|
316
|
+
level: 'DEFAULT',
|
|
317
317
|
metadata: lifecycleMetadata(event),
|
|
318
318
|
});
|
|
319
319
|
this.spans.set(batchKey, batchSpan);
|
|
@@ -330,11 +330,10 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
330
330
|
}
|
|
331
331
|
span.update({
|
|
332
332
|
endTime: event.createdAt,
|
|
333
|
-
level: event.error ?
|
|
333
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
334
334
|
...(event.error ? { statusMessage: event.error } : {}),
|
|
335
335
|
metadata: lifecycleMetadata(event),
|
|
336
336
|
});
|
|
337
|
-
this.spans.delete(key);
|
|
338
337
|
}
|
|
339
338
|
}
|
|
340
339
|
ensureSdkRootSpan(trace, rootKey, event) {
|
|
@@ -345,7 +344,7 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
345
344
|
const traceId = requireTraceId(event.traceId);
|
|
346
345
|
const span = trace.span({
|
|
347
346
|
id: langfuseSpanId(traceId, rootKey),
|
|
348
|
-
name:
|
|
347
|
+
name: 'chat-turn',
|
|
349
348
|
startTime: event.createdAt,
|
|
350
349
|
metadata: isToolEvent(event)
|
|
351
350
|
? toolMetadata(event)
|
|
@@ -357,95 +356,6 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
357
356
|
return span;
|
|
358
357
|
}
|
|
359
358
|
}
|
|
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
359
|
export class OtelRuntimeEventExporter {
|
|
450
360
|
config;
|
|
451
361
|
endpoint;
|
|
@@ -457,14 +367,16 @@ export class OtelRuntimeEventExporter {
|
|
|
457
367
|
constructor(config, fetchImpl) {
|
|
458
368
|
this.config = config;
|
|
459
369
|
this.endpoint = normalizeOtelTracesEndpoint(config.endpoint);
|
|
460
|
-
this.fetchImpl =
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
370
|
+
this.fetchImpl =
|
|
371
|
+
fetchImpl ??
|
|
372
|
+
(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
|
+
});
|
|
468
380
|
}
|
|
469
381
|
async publish(event) {
|
|
470
382
|
const redactedEvent = applyTelemetryRedaction(this.config, event);
|
|
@@ -521,16 +433,16 @@ export class OtelRuntimeEventExporter {
|
|
|
521
433
|
}
|
|
522
434
|
async sendSpans(spans) {
|
|
523
435
|
const response = await this.fetchImpl(this.endpoint, {
|
|
524
|
-
method:
|
|
436
|
+
method: 'POST',
|
|
525
437
|
headers: {
|
|
526
|
-
|
|
438
|
+
'content-type': 'application/json',
|
|
527
439
|
...(this.config.headers ?? {}),
|
|
528
440
|
},
|
|
529
441
|
body: JSON.stringify(toOtelTracePayload(spans, this.config)),
|
|
530
442
|
});
|
|
531
443
|
if (!response.ok) {
|
|
532
|
-
const text = await response.text().catch(() =>
|
|
533
|
-
throw new Error(`${this.config.errorLabel ??
|
|
444
|
+
const text = await response.text().catch(() => '');
|
|
445
|
+
throw new Error(`${this.config.errorLabel ?? 'OTEL export'} failed with ${response.status}${text ? `: ${text}` : ''}`);
|
|
534
446
|
}
|
|
535
447
|
}
|
|
536
448
|
}
|
|
@@ -539,190 +451,51 @@ export function createRuntimeEventExporterFromEnv(env) {
|
|
|
539
451
|
if (!config.enabled) {
|
|
540
452
|
return new NoopRuntimeEventExporter();
|
|
541
453
|
}
|
|
542
|
-
|
|
543
|
-
|
|
454
|
+
return new LangfuseSdkRuntimeEventExporter(config);
|
|
455
|
+
}
|
|
456
|
+
export function createLangfuseExporter(telemetryConfig) {
|
|
457
|
+
const config = resolveLangfuseExporterConfig(telemetryConfig);
|
|
458
|
+
if (!config.enabled) {
|
|
459
|
+
return new NoopRuntimeEventExporter();
|
|
544
460
|
}
|
|
545
|
-
return new
|
|
461
|
+
return new LangfuseSdkRuntimeEventExporter(config);
|
|
462
|
+
}
|
|
463
|
+
export function resolveLangfuseExporterConfig(telemetryConfig) {
|
|
464
|
+
const lf = telemetryConfig.langfuse;
|
|
465
|
+
const publicKey = lf?.publicKey ?? '';
|
|
466
|
+
const secretKey = lf?.secretKey ?? '';
|
|
467
|
+
const credentialsPresent = Boolean(publicKey && secretKey);
|
|
468
|
+
return {
|
|
469
|
+
enabled: Boolean(lf?.enabled && credentialsPresent),
|
|
470
|
+
publicKey,
|
|
471
|
+
secretKey,
|
|
472
|
+
baseUrl: lf?.baseUrl ?? DEFAULT_LANGFUSE_BASE_URL,
|
|
473
|
+
flushAt: lf?.flushAt ?? DEFAULT_FLUSH_AT,
|
|
474
|
+
flushIntervalMs: lf?.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
|
|
475
|
+
serviceName: telemetryConfig.serviceName ?? 'pi-server',
|
|
476
|
+
...(telemetryConfig.serviceVersion ? { serviceVersion: telemetryConfig.serviceVersion } : {}),
|
|
477
|
+
includePayloads: telemetryConfig.includePayloads ?? true,
|
|
478
|
+
};
|
|
546
479
|
}
|
|
547
480
|
export function resolveLangfuseConfig(env) {
|
|
548
481
|
const enabled = parseBoolean(env.LANGFUSE_ENABLED);
|
|
549
482
|
const publicKey = trim(env.LANGFUSE_PUBLIC_KEY);
|
|
550
483
|
const secretKey = trim(env.LANGFUSE_SECRET_KEY);
|
|
551
484
|
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
485
|
const credentialsPresent = Boolean(publicKey && secretKey);
|
|
555
486
|
const serviceVersion = trim(env.TELEMETRY_SERVICE_VERSION);
|
|
556
487
|
return {
|
|
557
488
|
enabled: Boolean(enabled && credentialsPresent),
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
secretKey: secretKey ?? "",
|
|
489
|
+
publicKey: publicKey ?? '',
|
|
490
|
+
secretKey: secretKey ?? '',
|
|
561
491
|
baseUrl,
|
|
562
492
|
flushAt: parsePositiveInteger(env.LANGFUSE_FLUSH_AT, DEFAULT_FLUSH_AT),
|
|
563
493
|
flushIntervalMs: parsePositiveInteger(env.LANGFUSE_FLUSH_INTERVAL_MS, DEFAULT_FLUSH_INTERVAL_MS),
|
|
564
|
-
serviceName: trim(env.TELEMETRY_SERVICE_NAME ?? env.OTEL_SERVICE_NAME) ??
|
|
494
|
+
serviceName: trim(env.TELEMETRY_SERVICE_NAME ?? env.OTEL_SERVICE_NAME) ?? 'pi-server',
|
|
565
495
|
...(serviceVersion ? { serviceVersion } : {}),
|
|
566
496
|
includePayloads: parseBooleanWithDefault(env.TELEMETRY_INCLUDE_PAYLOADS ?? env.LANGFUSE_INCLUDE_PAYLOADS, true),
|
|
567
497
|
};
|
|
568
498
|
}
|
|
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
499
|
function mapRuntimeEventToOtelSpans(event, pendingStarts) {
|
|
727
500
|
if (!event.traceId) {
|
|
728
501
|
return [];
|
|
@@ -739,32 +512,32 @@ function mapLifecycleEventToOtelSpans(event, pendingStarts) {
|
|
|
739
512
|
const rootKey = chatSpanKey(event);
|
|
740
513
|
const rootSpanId = langfuseSpanId(traceId, rootKey);
|
|
741
514
|
switch (event.type) {
|
|
742
|
-
case
|
|
515
|
+
case 'chat_turn_started':
|
|
743
516
|
pendingStarts.set(rootKey, {
|
|
744
517
|
traceId: langfuseTraceId(traceId),
|
|
745
518
|
spanId: rootSpanId,
|
|
746
|
-
name:
|
|
519
|
+
name: 'chat-turn',
|
|
747
520
|
startTime: event.createdAt,
|
|
748
521
|
attributes: {
|
|
749
522
|
...lifecycleMetadata(event),
|
|
750
|
-
...langfuseObservationAttributes({ input: event.details?.input, level:
|
|
523
|
+
...langfuseObservationAttributes({ input: event.details?.input, level: 'DEFAULT' }),
|
|
751
524
|
...langfuseTraceAttributes({ input: event.details?.input }),
|
|
752
525
|
},
|
|
753
526
|
});
|
|
754
527
|
return [];
|
|
755
|
-
case
|
|
756
|
-
case
|
|
528
|
+
case 'chat_turn_completed':
|
|
529
|
+
case 'chat_turn_failed':
|
|
757
530
|
return [
|
|
758
531
|
completeOtelSpan(pendingStarts, rootKey, {
|
|
759
532
|
traceId: langfuseTraceId(traceId),
|
|
760
533
|
spanId: rootSpanId,
|
|
761
|
-
name:
|
|
534
|
+
name: 'chat-turn',
|
|
762
535
|
startTime: event.createdAt,
|
|
763
536
|
attributes: {
|
|
764
537
|
...lifecycleMetadata(event),
|
|
765
538
|
...langfuseObservationAttributes({
|
|
766
539
|
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
767
|
-
level: event.error ?
|
|
540
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
768
541
|
}),
|
|
769
542
|
...langfuseTraceAttributes({
|
|
770
543
|
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
@@ -772,10 +545,10 @@ function mapLifecycleEventToOtelSpans(event, pendingStarts) {
|
|
|
772
545
|
},
|
|
773
546
|
}, event.createdAt, event.error),
|
|
774
547
|
];
|
|
775
|
-
case
|
|
776
|
-
case
|
|
777
|
-
case
|
|
778
|
-
case
|
|
548
|
+
case 'chat_turn_steered':
|
|
549
|
+
case 'chat_turn_steer_delivered':
|
|
550
|
+
case 'chat_turn_followup_queued':
|
|
551
|
+
case 'chat_turn_followup_delivered':
|
|
779
552
|
return [
|
|
780
553
|
{
|
|
781
554
|
traceId: langfuseTraceId(traceId),
|
|
@@ -789,43 +562,43 @@ function mapLifecycleEventToOtelSpans(event, pendingStarts) {
|
|
|
789
562
|
...langfuseObservationAttributes({
|
|
790
563
|
input: event.details?.input,
|
|
791
564
|
output: event.details?.output ?? chatInputLifecycleOutput(event),
|
|
792
|
-
level:
|
|
565
|
+
level: 'DEFAULT',
|
|
793
566
|
}),
|
|
794
567
|
},
|
|
795
568
|
},
|
|
796
569
|
];
|
|
797
|
-
case
|
|
798
|
-
case
|
|
570
|
+
case 'subagent_spawned':
|
|
571
|
+
case 'subagent_started': {
|
|
799
572
|
const key = subagentSpanKey(event);
|
|
800
573
|
pendingStarts.set(key, {
|
|
801
574
|
traceId: langfuseTraceId(traceId),
|
|
802
575
|
spanId: langfuseSpanId(traceId, key),
|
|
803
576
|
parentSpanId: langfuseSpanId(traceId, subagentSpawnToolSpanKey(event) ?? rootKey),
|
|
804
|
-
name:
|
|
577
|
+
name: 'subagent',
|
|
805
578
|
startTime: event.createdAt,
|
|
806
579
|
attributes: {
|
|
807
580
|
...lifecycleMetadata(event),
|
|
808
|
-
...langfuseObservationAttributes({ input: event.details?.input, level:
|
|
581
|
+
...langfuseObservationAttributes({ input: event.details?.input, level: 'DEFAULT' }),
|
|
809
582
|
},
|
|
810
583
|
});
|
|
811
584
|
return [];
|
|
812
585
|
}
|
|
813
|
-
case
|
|
814
|
-
case
|
|
815
|
-
case
|
|
586
|
+
case 'subagent_completed':
|
|
587
|
+
case 'subagent_failed':
|
|
588
|
+
case 'subagent_cancelled': {
|
|
816
589
|
const key = subagentSpanKey(event);
|
|
817
590
|
return [
|
|
818
591
|
completeOtelSpan(pendingStarts, key, {
|
|
819
592
|
traceId: langfuseTraceId(traceId),
|
|
820
593
|
spanId: langfuseSpanId(traceId, key),
|
|
821
594
|
parentSpanId: langfuseSpanId(traceId, subagentSpawnToolSpanKey(event) ?? rootKey),
|
|
822
|
-
name:
|
|
595
|
+
name: 'subagent',
|
|
823
596
|
startTime: event.createdAt,
|
|
824
597
|
attributes: {
|
|
825
598
|
...lifecycleMetadata(event),
|
|
826
599
|
...langfuseObservationAttributes({
|
|
827
600
|
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
828
|
-
level: event.error ?
|
|
601
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
829
602
|
}),
|
|
830
603
|
},
|
|
831
604
|
}, event.createdAt, event.error),
|
|
@@ -848,17 +621,17 @@ function mapToolEventToOtelSpans(event, pendingStarts) {
|
|
|
848
621
|
...toolMetadata(event),
|
|
849
622
|
...langfuseObservationAttributes({
|
|
850
623
|
output: event.error ? { error: event.error } : event.details,
|
|
851
|
-
level: event.error ?
|
|
624
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
852
625
|
}),
|
|
853
626
|
},
|
|
854
627
|
};
|
|
855
|
-
if (event.status ===
|
|
628
|
+
if (event.status === 'started') {
|
|
856
629
|
pendingStarts.set(key, {
|
|
857
630
|
...fallbackStart,
|
|
858
631
|
attributes: {
|
|
859
632
|
...fallbackStart.attributes,
|
|
860
633
|
...(event.args ? { args: event.args } : {}),
|
|
861
|
-
...langfuseObservationAttributes({ input: event.args, level:
|
|
634
|
+
...langfuseObservationAttributes({ input: event.args, level: 'DEFAULT' }),
|
|
862
635
|
},
|
|
863
636
|
});
|
|
864
637
|
return [];
|
|
@@ -876,23 +649,23 @@ function mapLlmGenerationEventToOtelSpans(event, pendingStarts) {
|
|
|
876
649
|
startTime: event.createdAt,
|
|
877
650
|
attributes: {
|
|
878
651
|
...llmGenerationMetadata(event),
|
|
879
|
-
|
|
652
|
+
'langfuse.observation.type': 'generation',
|
|
880
653
|
model: event.model.model,
|
|
881
654
|
provider: event.model.provider,
|
|
882
655
|
...(event.model.thinkingLevel ? { thinkingLevel: event.model.thinkingLevel } : {}),
|
|
883
656
|
...langfuseObservationAttributes({
|
|
884
657
|
output: event.output ?? (event.error ? { error: event.error } : undefined),
|
|
885
|
-
level: event.error ?
|
|
658
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
886
659
|
}),
|
|
887
660
|
...(event.usage ? flattenUsageAttributes(event.usage) : {}),
|
|
888
661
|
},
|
|
889
662
|
};
|
|
890
|
-
if (event.status ===
|
|
663
|
+
if (event.status === 'started') {
|
|
891
664
|
pendingStarts.set(key, {
|
|
892
665
|
...fallbackStart,
|
|
893
666
|
attributes: {
|
|
894
667
|
...fallbackStart.attributes,
|
|
895
|
-
...langfuseObservationAttributes({ input: event.input, level:
|
|
668
|
+
...langfuseObservationAttributes({ input: event.input, level: 'DEFAULT' }),
|
|
896
669
|
},
|
|
897
670
|
});
|
|
898
671
|
return [];
|
|
@@ -914,33 +687,37 @@ function completeOtelSpan(pendingStarts, key, fallbackStart, endTime, error) {
|
|
|
914
687
|
}
|
|
915
688
|
function langfuseObservationAttributes(input) {
|
|
916
689
|
return {
|
|
917
|
-
|
|
690
|
+
'langfuse.observation.type': 'span',
|
|
918
691
|
...(input.input !== undefined
|
|
919
692
|
? {
|
|
920
|
-
|
|
921
|
-
|
|
693
|
+
'langfuse.observation.input': toLangfuseObservationPayload(input.input),
|
|
694
|
+
'input.value': toLangfuseObservationPayload(input.input),
|
|
922
695
|
}
|
|
923
696
|
: {}),
|
|
924
697
|
...(input.output !== undefined
|
|
925
698
|
? {
|
|
926
|
-
|
|
927
|
-
|
|
699
|
+
'langfuse.observation.output': toLangfuseObservationPayload(input.output),
|
|
700
|
+
'output.value': toLangfuseObservationPayload(input.output),
|
|
928
701
|
}
|
|
929
702
|
: {}),
|
|
930
|
-
...(input.level ? {
|
|
703
|
+
...(input.level ? { 'langfuse.observation.level': input.level } : {}),
|
|
931
704
|
};
|
|
932
705
|
}
|
|
933
706
|
function langfuseTraceAttributes(input) {
|
|
934
707
|
return {
|
|
935
|
-
...(input.input !== undefined
|
|
936
|
-
|
|
708
|
+
...(input.input !== undefined
|
|
709
|
+
? { 'langfuse.trace.input': toLangfuseTracePayload(input.input) }
|
|
710
|
+
: {}),
|
|
711
|
+
...(input.output !== undefined
|
|
712
|
+
? { 'langfuse.trace.output': toLangfuseTracePayload(input.output) }
|
|
713
|
+
: {}),
|
|
937
714
|
};
|
|
938
715
|
}
|
|
939
716
|
function toLangfuseObservationPayload(value) {
|
|
940
717
|
return JSON.stringify(value);
|
|
941
718
|
}
|
|
942
719
|
function toLangfuseTracePayload(value) {
|
|
943
|
-
return typeof value ===
|
|
720
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
944
721
|
}
|
|
945
722
|
function chatSpanKey(event) {
|
|
946
723
|
const sessionId = event.parentSessionId ?? event.sessionId;
|
|
@@ -954,7 +731,7 @@ function subagentSpanKey(event) {
|
|
|
954
731
|
return `subagent:${event.runId ?? event.childSessionId ?? event.id}`;
|
|
955
732
|
}
|
|
956
733
|
function subagentBatchSpanKey(event) {
|
|
957
|
-
const batchId = event.spawnBatchId ?? stringFromJsonObject(event.details,
|
|
734
|
+
const batchId = event.spawnBatchId ?? stringFromJsonObject(event.details, 'spawnBatchId');
|
|
958
735
|
return event.traceId && batchId ? `subagent-batch:${event.traceId}:${batchId}` : undefined;
|
|
959
736
|
}
|
|
960
737
|
function telemetryEventSubagentSpanKey(event) {
|
|
@@ -983,23 +760,25 @@ function toolObservationName(event) {
|
|
|
983
760
|
}
|
|
984
761
|
function chatInputObservationName(event) {
|
|
985
762
|
const prefix = chatInputObservationPrefix(event.type);
|
|
986
|
-
const input = typeof event.details?.input ===
|
|
763
|
+
const input = typeof event.details?.input === 'string'
|
|
764
|
+
? truncateObservationSummary(event.details.input)
|
|
765
|
+
: undefined;
|
|
987
766
|
return input ? `${prefix} [${input}]` : prefix;
|
|
988
767
|
}
|
|
989
768
|
function chatInputObservationPrefix(type) {
|
|
990
|
-
if (type ===
|
|
991
|
-
return
|
|
769
|
+
if (type === 'chat_turn_steered') {
|
|
770
|
+
return 'chat-steer';
|
|
992
771
|
}
|
|
993
|
-
if (type ===
|
|
994
|
-
return
|
|
772
|
+
if (type === 'chat_turn_steer_delivered') {
|
|
773
|
+
return 'chat-steer-delivered';
|
|
995
774
|
}
|
|
996
|
-
if (type ===
|
|
997
|
-
return
|
|
775
|
+
if (type === 'chat_turn_followup_delivered') {
|
|
776
|
+
return 'chat-followup-delivered';
|
|
998
777
|
}
|
|
999
|
-
return
|
|
778
|
+
return 'chat-followup';
|
|
1000
779
|
}
|
|
1001
780
|
function chatInputLifecycleOutput(event) {
|
|
1002
|
-
return event.type ===
|
|
781
|
+
return event.type === 'chat_turn_steer_delivered' || event.type === 'chat_turn_followup_delivered'
|
|
1003
782
|
? { delivered: true, turnMode: event.details?.turnMode }
|
|
1004
783
|
: { accepted: true, turnMode: event.details?.turnMode };
|
|
1005
784
|
}
|
|
@@ -1007,59 +786,59 @@ function summarizeToolArgsForName(toolName, args) {
|
|
|
1007
786
|
if (!args) {
|
|
1008
787
|
return undefined;
|
|
1009
788
|
}
|
|
1010
|
-
const pathValue = stringArg(args,
|
|
789
|
+
const pathValue = stringArg(args, 'path') ?? stringArg(args, 'filePath') ?? stringArg(args, 'absolutePath');
|
|
1011
790
|
if (pathValue) {
|
|
1012
791
|
return truncateObservationSummary(pathValue);
|
|
1013
792
|
}
|
|
1014
|
-
const command = stringArg(args,
|
|
793
|
+
const command = stringArg(args, 'command');
|
|
1015
794
|
if (command) {
|
|
1016
795
|
return truncateObservationSummary(command);
|
|
1017
796
|
}
|
|
1018
|
-
const query = stringArg(args,
|
|
797
|
+
const query = stringArg(args, 'query');
|
|
1019
798
|
if (query) {
|
|
1020
799
|
return truncateObservationSummary(query);
|
|
1021
800
|
}
|
|
1022
|
-
const task = stringArg(args,
|
|
1023
|
-
if (task && toolName ===
|
|
801
|
+
const task = stringArg(args, 'task');
|
|
802
|
+
if (task && toolName === 'sessions_spawn') {
|
|
1024
803
|
return truncateObservationSummary(task);
|
|
1025
804
|
}
|
|
1026
|
-
const code = stringArg(args,
|
|
805
|
+
const code = stringArg(args, 'code');
|
|
1027
806
|
if (code) {
|
|
1028
807
|
return truncateObservationSummary(code);
|
|
1029
808
|
}
|
|
1030
|
-
const name = stringArg(args,
|
|
1031
|
-
if (name && toolName.startsWith(
|
|
809
|
+
const name = stringArg(args, 'name');
|
|
810
|
+
if (name && toolName.startsWith('mcp_')) {
|
|
1032
811
|
return truncateObservationSummary(name);
|
|
1033
812
|
}
|
|
1034
813
|
return undefined;
|
|
1035
814
|
}
|
|
1036
815
|
function llmGenerationObservationName(event) {
|
|
1037
|
-
return `llm-generation [${event.runId || event.childSessionId ?
|
|
816
|
+
return `llm-generation [${event.runId || event.childSessionId ? 'subagent' : 'main'}] [${summarizeLlmGenerationInputForName(event.input)}]`;
|
|
1038
817
|
}
|
|
1039
818
|
function summarizeLlmGenerationInputForName(input) {
|
|
1040
|
-
if (typeof input ===
|
|
819
|
+
if (typeof input === 'string') {
|
|
1041
820
|
return truncateObservationSummary(input);
|
|
1042
821
|
}
|
|
1043
|
-
if (input && typeof input ===
|
|
822
|
+
if (input && typeof input === 'object' && !Array.isArray(input)) {
|
|
1044
823
|
const continuation = input.continuation === true;
|
|
1045
|
-
const index = typeof input.llmGenerationIndex ===
|
|
1046
|
-
const toolResults = typeof input.previousToolResultCount ===
|
|
824
|
+
const index = typeof input.llmGenerationIndex === 'number' ? input.llmGenerationIndex : undefined;
|
|
825
|
+
const toolResults = typeof input.previousToolResultCount === 'number' ? input.previousToolResultCount : undefined;
|
|
1047
826
|
if (continuation) {
|
|
1048
|
-
return truncateObservationSummary(`continuation${index !== undefined ? ` #${index}` :
|
|
827
|
+
return truncateObservationSummary(`continuation${index !== undefined ? ` #${index}` : ''}${toolResults !== undefined ? ` after ${toolResults} tool result(s)` : ''}`);
|
|
1049
828
|
}
|
|
1050
829
|
}
|
|
1051
|
-
return
|
|
830
|
+
return 'request';
|
|
1052
831
|
}
|
|
1053
832
|
function stringArg(args, key) {
|
|
1054
833
|
const value = args[key];
|
|
1055
|
-
return typeof value ===
|
|
834
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
1056
835
|
}
|
|
1057
836
|
function stringFromJsonObject(value, key) {
|
|
1058
837
|
const field = value?.[key];
|
|
1059
|
-
return typeof field ===
|
|
838
|
+
return typeof field === 'string' && field.trim() ? field.trim() : undefined;
|
|
1060
839
|
}
|
|
1061
840
|
function truncateObservationSummary(value, maxLength = 90) {
|
|
1062
|
-
const normalized = value.replace(/\s+/g,
|
|
841
|
+
const normalized = value.replace(/\s+/g, ' ').trim();
|
|
1063
842
|
return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1)}…` : normalized;
|
|
1064
843
|
}
|
|
1065
844
|
function ensureEndAfterStart(startTime, endTime) {
|
|
@@ -1071,14 +850,16 @@ function toOtelTracePayload(spans, config) {
|
|
|
1071
850
|
{
|
|
1072
851
|
resource: {
|
|
1073
852
|
attributes: [
|
|
1074
|
-
otelAttribute(
|
|
1075
|
-
...(config.serviceVersion
|
|
1076
|
-
|
|
853
|
+
otelAttribute('service.name', config.serviceName ?? 'pi-server'),
|
|
854
|
+
...(config.serviceVersion
|
|
855
|
+
? [otelAttribute('service.version', config.serviceVersion)]
|
|
856
|
+
: []),
|
|
857
|
+
otelAttribute('telemetry.sdk.name', '@amaster.ai/pi-telemetry'),
|
|
1077
858
|
],
|
|
1078
859
|
},
|
|
1079
860
|
scopeSpans: [
|
|
1080
861
|
{
|
|
1081
|
-
scope: { name:
|
|
862
|
+
scope: { name: '@amaster.ai/pi-telemetry', version: '0.1.0' },
|
|
1082
863
|
spans: spans.map(toOtelSpanPayload),
|
|
1083
864
|
},
|
|
1084
865
|
],
|
|
@@ -1105,13 +886,13 @@ function otelAttribute(key, value) {
|
|
|
1105
886
|
return { key, value: otelAttributeValue(value) };
|
|
1106
887
|
}
|
|
1107
888
|
function otelAttributeValue(value) {
|
|
1108
|
-
if (typeof value ===
|
|
889
|
+
if (typeof value === 'boolean') {
|
|
1109
890
|
return { boolValue: value };
|
|
1110
891
|
}
|
|
1111
|
-
if (typeof value ===
|
|
892
|
+
if (typeof value === 'number') {
|
|
1112
893
|
return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
|
|
1113
894
|
}
|
|
1114
|
-
if (typeof value ===
|
|
895
|
+
if (typeof value === 'string') {
|
|
1115
896
|
return { stringValue: value };
|
|
1116
897
|
}
|
|
1117
898
|
return { stringValue: JSON.stringify(value) };
|
|
@@ -1121,13 +902,13 @@ function toUnixNano(iso) {
|
|
|
1121
902
|
return String(BigInt(Number.isFinite(millis) ? millis : Date.now()) * 1000000n);
|
|
1122
903
|
}
|
|
1123
904
|
function normalizeOtelTracesEndpoint(endpoint) {
|
|
1124
|
-
return endpoint.endsWith(
|
|
905
|
+
return endpoint.endsWith('/v1/traces') ? endpoint : `${endpoint.replace(/\/+$/, '')}/v1/traces`;
|
|
1125
906
|
}
|
|
1126
907
|
function shortCorrelationId(value) {
|
|
1127
908
|
if (!value) {
|
|
1128
909
|
return undefined;
|
|
1129
910
|
}
|
|
1130
|
-
const normalized = value.startsWith(
|
|
911
|
+
const normalized = value.startsWith('trace:') ? value.slice('trace:'.length) : value;
|
|
1131
912
|
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
913
|
if (uuid) {
|
|
1133
914
|
return uuid.slice(0, 8);
|
|
@@ -1141,11 +922,14 @@ function compactSessionId(value) {
|
|
|
1141
922
|
if (!value) {
|
|
1142
923
|
return undefined;
|
|
1143
924
|
}
|
|
1144
|
-
const [root, ...subagents] = value.split(
|
|
925
|
+
const [root, ...subagents] = value.split(':subagent:');
|
|
1145
926
|
if (subagents.length === 0) {
|
|
1146
927
|
return value;
|
|
1147
928
|
}
|
|
1148
|
-
return [
|
|
929
|
+
return [
|
|
930
|
+
root,
|
|
931
|
+
...subagents.map((sessionId) => `sub:${shortCorrelationId(sessionId) ?? sessionId}`),
|
|
932
|
+
].join('/');
|
|
1149
933
|
}
|
|
1150
934
|
function lineageMetadata(event) {
|
|
1151
935
|
const sessionId = compactSessionId(event.sessionId);
|
|
@@ -1202,7 +986,7 @@ function toLangfuseUsage(usage) {
|
|
|
1202
986
|
...(usage.input !== undefined ? { input: usage.input } : {}),
|
|
1203
987
|
...(usage.output !== undefined ? { output: usage.output } : {}),
|
|
1204
988
|
...(usage.totalTokens !== undefined ? { total: usage.totalTokens } : {}),
|
|
1205
|
-
unit:
|
|
989
|
+
unit: 'TOKENS',
|
|
1206
990
|
...(usage.cost?.input !== undefined ? { inputCost: usage.cost.input } : {}),
|
|
1207
991
|
...(usage.cost?.output !== undefined ? { outputCost: usage.cost.output } : {}),
|
|
1208
992
|
...(usage.cost?.total !== undefined ? { totalCost: usage.cost.total } : {}),
|
|
@@ -1219,20 +1003,12 @@ function toLangfuseUsageDetails(usage) {
|
|
|
1219
1003
|
}
|
|
1220
1004
|
function flattenUsageAttributes(usage) {
|
|
1221
1005
|
return {
|
|
1222
|
-
...(usage.input !== undefined ? {
|
|
1223
|
-
...(usage.output !== undefined ? {
|
|
1224
|
-
...(usage.cacheRead !== undefined ? {
|
|
1225
|
-
...(usage.cacheWrite !== undefined ? {
|
|
1226
|
-
...(usage.totalTokens !== undefined ? {
|
|
1227
|
-
...(usage.cost?.total !== undefined ? {
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
function ingestionEvent(type, timestamp, body) {
|
|
1231
|
-
return {
|
|
1232
|
-
id: randomUUID(),
|
|
1233
|
-
timestamp,
|
|
1234
|
-
type,
|
|
1235
|
-
body,
|
|
1006
|
+
...(usage.input !== undefined ? { 'usage.input': usage.input } : {}),
|
|
1007
|
+
...(usage.output !== undefined ? { 'usage.output': usage.output } : {}),
|
|
1008
|
+
...(usage.cacheRead !== undefined ? { 'usage.cache_read': usage.cacheRead } : {}),
|
|
1009
|
+
...(usage.cacheWrite !== undefined ? { 'usage.cache_write': usage.cacheWrite } : {}),
|
|
1010
|
+
...(usage.totalTokens !== undefined ? { 'usage.total_tokens': usage.totalTokens } : {}),
|
|
1011
|
+
...(usage.cost?.total !== undefined ? { 'usage.cost.total': usage.cost.total } : {}),
|
|
1236
1012
|
};
|
|
1237
1013
|
}
|
|
1238
1014
|
function langfuseTraceId(traceId) {
|
|
@@ -1242,7 +1018,7 @@ function langfuseSpanId(traceId, key) {
|
|
|
1242
1018
|
return stableHex(`span:${traceId}:${key}`, 16);
|
|
1243
1019
|
}
|
|
1244
1020
|
function stableHex(input, length) {
|
|
1245
|
-
return createHash(
|
|
1021
|
+
return createHash('sha256').update(input).digest('hex').slice(0, length);
|
|
1246
1022
|
}
|
|
1247
1023
|
function applyTelemetryRedaction(config, event) {
|
|
1248
1024
|
const redacted = config.redactEvent ? config.redactEvent(event) : event;
|
|
@@ -1274,13 +1050,13 @@ function redactJsonObjectPayload(input) {
|
|
|
1274
1050
|
return rest;
|
|
1275
1051
|
}
|
|
1276
1052
|
function isToolEvent(event) {
|
|
1277
|
-
return
|
|
1053
|
+
return 'toolCallId' in event;
|
|
1278
1054
|
}
|
|
1279
1055
|
function isLlmGenerationEvent(event) {
|
|
1280
|
-
return
|
|
1056
|
+
return 'llmGenerationId' in event;
|
|
1281
1057
|
}
|
|
1282
1058
|
function parseBoolean(value) {
|
|
1283
|
-
return value ===
|
|
1059
|
+
return value === '1' || value === 'true' || value === 'TRUE' || value === 'yes';
|
|
1284
1060
|
}
|
|
1285
1061
|
function parseBooleanWithDefault(value, fallback) {
|
|
1286
1062
|
if (value === undefined) {
|
|
@@ -1288,10 +1064,6 @@ function parseBooleanWithDefault(value, fallback) {
|
|
|
1288
1064
|
}
|
|
1289
1065
|
return parseBoolean(value);
|
|
1290
1066
|
}
|
|
1291
|
-
function parseLangfuseTransport(value) {
|
|
1292
|
-
const normalized = value?.trim().toLowerCase();
|
|
1293
|
-
return normalized === "sdk" || normalized === "ingestion" ? normalized : undefined;
|
|
1294
|
-
}
|
|
1295
1067
|
function parsePositiveInteger(value, fallback) {
|
|
1296
1068
|
const parsed = Number(value);
|
|
1297
1069
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
@@ -1305,7 +1077,7 @@ function assertNever(value) {
|
|
|
1305
1077
|
}
|
|
1306
1078
|
function requireTraceId(traceId) {
|
|
1307
1079
|
if (!traceId) {
|
|
1308
|
-
throw new Error(
|
|
1080
|
+
throw new Error('Telemetry event is missing traceId');
|
|
1309
1081
|
}
|
|
1310
1082
|
return traceId;
|
|
1311
1083
|
}
|