@amaster.ai/pi-telemetry 0.1.0-beta.0 → 0.1.1-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/dist/extension.d.ts +3 -0
- package/dist/extension.d.ts.map +1 -0
- package/dist/extension.js +125 -0
- package/dist/extension.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/langfuse.d.ts +3 -3
- package/dist/langfuse.d.ts.map +1 -1
- package/dist/langfuse.js +219 -194
- package/dist/langfuse.js.map +1 -1
- package/dist/otel.d.ts +3 -3
- package/dist/otel.d.ts.map +1 -1
- package/dist/otel.js +11 -9
- package/dist/otel.js.map +1 -1
- package/package.json +16 -3
package/dist/langfuse.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createHash, randomUUID } from
|
|
2
|
-
import { Langfuse } from
|
|
3
|
-
import { NoopRuntimeEventExporter, } from
|
|
4
|
-
const DEFAULT_LANGFUSE_BASE_URL =
|
|
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
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: 'copilot-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,37 @@ 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: 'copilot-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: 'copilot-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
99
|
this.spans.delete(rootKey);
|
|
100
100
|
break;
|
|
101
101
|
}
|
|
102
|
-
case
|
|
103
|
-
case
|
|
104
|
-
case
|
|
105
|
-
case
|
|
102
|
+
case 'chat_turn_steered':
|
|
103
|
+
case 'chat_turn_steer_delivered':
|
|
104
|
+
case 'chat_turn_followup_queued':
|
|
105
|
+
case 'chat_turn_followup_delivered': {
|
|
106
106
|
const output = event.details?.output ?? chatInputLifecycleOutput(event);
|
|
107
107
|
this.getSdkSpanParent(trace, rootKey).span({
|
|
108
108
|
id: langfuseSpanId(traceId, chatInputSpanKey(event)),
|
|
@@ -111,28 +111,28 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
111
111
|
endTime: event.createdAt,
|
|
112
112
|
input: event.details?.input,
|
|
113
113
|
output,
|
|
114
|
-
level:
|
|
114
|
+
level: 'DEFAULT',
|
|
115
115
|
metadata: lifecycleMetadata(event),
|
|
116
116
|
});
|
|
117
117
|
break;
|
|
118
118
|
}
|
|
119
|
-
case
|
|
120
|
-
case
|
|
119
|
+
case 'subagent_spawned':
|
|
120
|
+
case 'subagent_started': {
|
|
121
121
|
const key = subagentSpanKey(event);
|
|
122
122
|
const rootSpan = this.ensureSdkRootSpan(trace, rootKey, event);
|
|
123
123
|
const body = {
|
|
124
124
|
id: langfuseSpanId(traceId, key),
|
|
125
|
-
name:
|
|
125
|
+
name: 'subagent',
|
|
126
126
|
startTime: event.createdAt,
|
|
127
127
|
input: event.details?.input,
|
|
128
|
-
level:
|
|
128
|
+
level: 'DEFAULT',
|
|
129
129
|
metadata: lifecycleMetadata(event),
|
|
130
130
|
};
|
|
131
131
|
const span = this.spans.get(key);
|
|
132
132
|
if (span) {
|
|
133
133
|
span.update({
|
|
134
134
|
input: event.details?.input,
|
|
135
|
-
level:
|
|
135
|
+
level: 'DEFAULT',
|
|
136
136
|
metadata: lifecycleMetadata(event),
|
|
137
137
|
});
|
|
138
138
|
}
|
|
@@ -141,23 +141,23 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
141
141
|
}
|
|
142
142
|
break;
|
|
143
143
|
}
|
|
144
|
-
case
|
|
145
|
-
case
|
|
146
|
-
case
|
|
144
|
+
case 'subagent_completed':
|
|
145
|
+
case 'subagent_failed':
|
|
146
|
+
case 'subagent_cancelled': {
|
|
147
147
|
const key = subagentSpanKey(event);
|
|
148
148
|
const output = event.details?.output ?? (event.error ? { error: event.error } : undefined);
|
|
149
149
|
const rootSpan = this.ensureSdkRootSpan(trace, rootKey, event);
|
|
150
150
|
const span = this.spans.get(key) ??
|
|
151
151
|
this.getSdkSubagentParent(trace, rootKey, event).span({
|
|
152
152
|
id: langfuseSpanId(traceId, key),
|
|
153
|
-
name:
|
|
153
|
+
name: 'subagent',
|
|
154
154
|
startTime: event.createdAt,
|
|
155
155
|
metadata: lifecycleMetadata(event),
|
|
156
156
|
});
|
|
157
157
|
span.update({
|
|
158
158
|
output,
|
|
159
159
|
endTime: event.createdAt,
|
|
160
|
-
level: event.error ?
|
|
160
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
161
161
|
...(event.error ? { statusMessage: event.error } : {}),
|
|
162
162
|
metadata: lifecycleMetadata(event),
|
|
163
163
|
});
|
|
@@ -175,20 +175,20 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
175
175
|
const parent = this.getSdkEventParent(trace, rootKey, event);
|
|
176
176
|
const key = toolSpanKey(event);
|
|
177
177
|
const id = langfuseSpanId(traceId, key);
|
|
178
|
-
if (event.status ===
|
|
178
|
+
if (event.status === 'started') {
|
|
179
179
|
const body = {
|
|
180
180
|
id,
|
|
181
181
|
name: toolObservationName(event),
|
|
182
182
|
startTime: event.createdAt,
|
|
183
183
|
input: event.args ? { args: event.args } : undefined,
|
|
184
|
-
level:
|
|
184
|
+
level: 'DEFAULT',
|
|
185
185
|
metadata: toolMetadata(event),
|
|
186
186
|
};
|
|
187
187
|
const span = this.spans.get(key);
|
|
188
188
|
if (span) {
|
|
189
189
|
span.update({
|
|
190
190
|
input: event.args ? { args: event.args } : undefined,
|
|
191
|
-
level:
|
|
191
|
+
level: 'DEFAULT',
|
|
192
192
|
metadata: toolMetadata(event),
|
|
193
193
|
});
|
|
194
194
|
}
|
|
@@ -208,7 +208,7 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
208
208
|
span.update({
|
|
209
209
|
output,
|
|
210
210
|
endTime: event.createdAt,
|
|
211
|
-
level: event.error ?
|
|
211
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
212
212
|
...(event.error ? { statusMessage: event.error } : {}),
|
|
213
213
|
metadata: toolMetadata(event),
|
|
214
214
|
});
|
|
@@ -221,7 +221,7 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
221
221
|
const parent = this.getSdkEventParent(trace, rootKey, event);
|
|
222
222
|
const key = llmGenerationKey(event);
|
|
223
223
|
const id = langfuseSpanId(traceId, key);
|
|
224
|
-
if (event.status ===
|
|
224
|
+
if (event.status === 'started') {
|
|
225
225
|
const body = {
|
|
226
226
|
id,
|
|
227
227
|
name: llmGenerationObservationName(event),
|
|
@@ -257,9 +257,11 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
257
257
|
generation.update({
|
|
258
258
|
output: event.output ?? (event.error ? { error: event.error } : undefined),
|
|
259
259
|
endTime: event.createdAt,
|
|
260
|
-
level: event.error ?
|
|
260
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
261
261
|
...(event.error ? { statusMessage: event.error } : {}),
|
|
262
|
-
...(event.usage
|
|
262
|
+
...(event.usage
|
|
263
|
+
? { usage: toLangfuseUsage(event.usage), usageDetails: toLangfuseUsageDetails(event.usage) }
|
|
264
|
+
: {}),
|
|
263
265
|
model: event.model.model,
|
|
264
266
|
modelParameters: {
|
|
265
267
|
provider: event.model.provider,
|
|
@@ -278,7 +280,7 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
278
280
|
const trace = this.client.trace({
|
|
279
281
|
id: langfuseTraceId(traceId),
|
|
280
282
|
sessionId: event.sessionId,
|
|
281
|
-
name:
|
|
283
|
+
name: 'copilot-chat-turn',
|
|
282
284
|
timestamp: event.createdAt,
|
|
283
285
|
input: !isToolEvent(event) && !isLlmGenerationEvent(event) ? event.details?.input : undefined,
|
|
284
286
|
metadata: isToolEvent(event)
|
|
@@ -310,10 +312,10 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
310
312
|
}
|
|
311
313
|
const batchSpan = rootSpan.span({
|
|
312
314
|
id: langfuseSpanId(requireTraceId(event.traceId), batchKey),
|
|
313
|
-
name:
|
|
315
|
+
name: 'subagent fan-out',
|
|
314
316
|
startTime: event.createdAt,
|
|
315
317
|
input: event.details?.input,
|
|
316
|
-
level:
|
|
318
|
+
level: 'DEFAULT',
|
|
317
319
|
metadata: lifecycleMetadata(event),
|
|
318
320
|
});
|
|
319
321
|
this.spans.set(batchKey, batchSpan);
|
|
@@ -330,7 +332,7 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
330
332
|
}
|
|
331
333
|
span.update({
|
|
332
334
|
endTime: event.createdAt,
|
|
333
|
-
level: event.error ?
|
|
335
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
334
336
|
...(event.error ? { statusMessage: event.error } : {}),
|
|
335
337
|
metadata: lifecycleMetadata(event),
|
|
336
338
|
});
|
|
@@ -345,7 +347,7 @@ export class LangfuseSdkRuntimeEventExporter {
|
|
|
345
347
|
const traceId = requireTraceId(event.traceId);
|
|
346
348
|
const span = trace.span({
|
|
347
349
|
id: langfuseSpanId(traceId, rootKey),
|
|
348
|
-
name:
|
|
350
|
+
name: 'copilot-chat-turn',
|
|
349
351
|
startTime: event.createdAt,
|
|
350
352
|
metadata: isToolEvent(event)
|
|
351
353
|
? toolMetadata(event)
|
|
@@ -367,16 +369,18 @@ export class LangfuseHttpRuntimeEventExporter {
|
|
|
367
369
|
flushing;
|
|
368
370
|
constructor(config, fetchImpl) {
|
|
369
371
|
this.config = config;
|
|
370
|
-
this.endpoint = `${config.baseUrl.replace(/\/+$/,
|
|
371
|
-
this.authHeader = `Basic ${Buffer.from(`${config.publicKey}:${config.secretKey}`).toString(
|
|
372
|
-
this.fetchImpl =
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
372
|
+
this.endpoint = `${config.baseUrl.replace(/\/+$/, '')}/api/public/ingestion`;
|
|
373
|
+
this.authHeader = `Basic ${Buffer.from(`${config.publicKey}:${config.secretKey}`).toString('base64')}`;
|
|
374
|
+
this.fetchImpl =
|
|
375
|
+
fetchImpl ??
|
|
376
|
+
(async (input, init) => {
|
|
377
|
+
const response = await fetch(input, init);
|
|
378
|
+
return {
|
|
379
|
+
ok: response.ok,
|
|
380
|
+
status: response.status,
|
|
381
|
+
text: () => response.text(),
|
|
382
|
+
};
|
|
383
|
+
});
|
|
380
384
|
}
|
|
381
385
|
async publish(event) {
|
|
382
386
|
const redactedEvent = applyTelemetryRedaction(this.config, event);
|
|
@@ -432,17 +436,17 @@ export class LangfuseHttpRuntimeEventExporter {
|
|
|
432
436
|
}
|
|
433
437
|
async sendBatch(batch) {
|
|
434
438
|
const response = await this.fetchImpl(this.endpoint, {
|
|
435
|
-
method:
|
|
439
|
+
method: 'POST',
|
|
436
440
|
headers: {
|
|
437
441
|
authorization: this.authHeader,
|
|
438
|
-
|
|
439
|
-
|
|
442
|
+
'content-type': 'application/json',
|
|
443
|
+
'x-langfuse-ingestion-version': '4',
|
|
440
444
|
},
|
|
441
445
|
body: JSON.stringify({ batch }),
|
|
442
446
|
});
|
|
443
447
|
if (!response.ok) {
|
|
444
|
-
const text = await response.text().catch(() =>
|
|
445
|
-
throw new Error(`Langfuse ingestion failed with ${response.status}${text ? `: ${text}` :
|
|
448
|
+
const text = await response.text().catch(() => '');
|
|
449
|
+
throw new Error(`Langfuse ingestion failed with ${response.status}${text ? `: ${text}` : ''}`);
|
|
446
450
|
}
|
|
447
451
|
}
|
|
448
452
|
}
|
|
@@ -457,14 +461,16 @@ export class OtelRuntimeEventExporter {
|
|
|
457
461
|
constructor(config, fetchImpl) {
|
|
458
462
|
this.config = config;
|
|
459
463
|
this.endpoint = normalizeOtelTracesEndpoint(config.endpoint);
|
|
460
|
-
this.fetchImpl =
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
464
|
+
this.fetchImpl =
|
|
465
|
+
fetchImpl ??
|
|
466
|
+
(async (input, init) => {
|
|
467
|
+
const response = await fetch(input, init);
|
|
468
|
+
return {
|
|
469
|
+
ok: response.ok,
|
|
470
|
+
status: response.status,
|
|
471
|
+
text: () => response.text(),
|
|
472
|
+
};
|
|
473
|
+
});
|
|
468
474
|
}
|
|
469
475
|
async publish(event) {
|
|
470
476
|
const redactedEvent = applyTelemetryRedaction(this.config, event);
|
|
@@ -521,16 +527,16 @@ export class OtelRuntimeEventExporter {
|
|
|
521
527
|
}
|
|
522
528
|
async sendSpans(spans) {
|
|
523
529
|
const response = await this.fetchImpl(this.endpoint, {
|
|
524
|
-
method:
|
|
530
|
+
method: 'POST',
|
|
525
531
|
headers: {
|
|
526
|
-
|
|
532
|
+
'content-type': 'application/json',
|
|
527
533
|
...(this.config.headers ?? {}),
|
|
528
534
|
},
|
|
529
535
|
body: JSON.stringify(toOtelTracePayload(spans, this.config)),
|
|
530
536
|
});
|
|
531
537
|
if (!response.ok) {
|
|
532
|
-
const text = await response.text().catch(() =>
|
|
533
|
-
throw new Error(`${this.config.errorLabel ??
|
|
538
|
+
const text = await response.text().catch(() => '');
|
|
539
|
+
throw new Error(`${this.config.errorLabel ?? 'OTEL export'} failed with ${response.status}${text ? `: ${text}` : ''}`);
|
|
534
540
|
}
|
|
535
541
|
}
|
|
536
542
|
}
|
|
@@ -539,7 +545,7 @@ export function createRuntimeEventExporterFromEnv(env) {
|
|
|
539
545
|
if (!config.enabled) {
|
|
540
546
|
return new NoopRuntimeEventExporter();
|
|
541
547
|
}
|
|
542
|
-
if (config.transport ===
|
|
548
|
+
if (config.transport === 'sdk') {
|
|
543
549
|
return new LangfuseSdkRuntimeEventExporter(config);
|
|
544
550
|
}
|
|
545
551
|
return new LangfuseHttpRuntimeEventExporter(config);
|
|
@@ -550,18 +556,18 @@ export function resolveLangfuseConfig(env) {
|
|
|
550
556
|
const secretKey = trim(env.LANGFUSE_SECRET_KEY);
|
|
551
557
|
const baseUrl = trim(env.LANGFUSE_BASE_URL) ?? DEFAULT_LANGFUSE_BASE_URL;
|
|
552
558
|
const requestedTransport = parseLangfuseTransport(env.LANGFUSE_TRANSPORT);
|
|
553
|
-
const transport = requestedTransport ?? (publicKey && secretKey ?
|
|
559
|
+
const transport = requestedTransport ?? (publicKey && secretKey ? 'sdk' : 'ingestion');
|
|
554
560
|
const credentialsPresent = Boolean(publicKey && secretKey);
|
|
555
561
|
const serviceVersion = trim(env.TELEMETRY_SERVICE_VERSION);
|
|
556
562
|
return {
|
|
557
563
|
enabled: Boolean(enabled && credentialsPresent),
|
|
558
564
|
transport,
|
|
559
|
-
publicKey: publicKey ??
|
|
560
|
-
secretKey: secretKey ??
|
|
565
|
+
publicKey: publicKey ?? '',
|
|
566
|
+
secretKey: secretKey ?? '',
|
|
561
567
|
baseUrl,
|
|
562
568
|
flushAt: parsePositiveInteger(env.LANGFUSE_FLUSH_AT, DEFAULT_FLUSH_AT),
|
|
563
569
|
flushIntervalMs: parsePositiveInteger(env.LANGFUSE_FLUSH_INTERVAL_MS, DEFAULT_FLUSH_INTERVAL_MS),
|
|
564
|
-
serviceName: trim(env.TELEMETRY_SERVICE_NAME ?? env.OTEL_SERVICE_NAME) ??
|
|
570
|
+
serviceName: trim(env.TELEMETRY_SERVICE_NAME ?? env.OTEL_SERVICE_NAME) ?? 'pi-server',
|
|
565
571
|
...(serviceVersion ? { serviceVersion } : {}),
|
|
566
572
|
includePayloads: parseBooleanWithDefault(env.TELEMETRY_INCLUDE_PAYLOADS ?? env.LANGFUSE_INCLUDE_PAYLOADS, true),
|
|
567
573
|
};
|
|
@@ -581,31 +587,35 @@ export function mapRuntimeEventToLangfuse(event) {
|
|
|
581
587
|
function mapLifecycleEventToLangfuse(event) {
|
|
582
588
|
const traceId = requireTraceId(event.traceId);
|
|
583
589
|
switch (event.type) {
|
|
584
|
-
case
|
|
585
|
-
return [
|
|
590
|
+
case 'chat_turn_started':
|
|
591
|
+
return [
|
|
592
|
+
ingestionEvent('trace-create', event.createdAt, {
|
|
586
593
|
id: langfuseTraceId(traceId),
|
|
587
594
|
sessionId: event.sessionId,
|
|
588
|
-
name:
|
|
595
|
+
name: 'copilot-chat-turn',
|
|
589
596
|
timestamp: event.createdAt,
|
|
590
597
|
input: event.details?.input,
|
|
591
598
|
metadata: lifecycleMetadata(event),
|
|
592
|
-
})
|
|
593
|
-
|
|
594
|
-
case
|
|
599
|
+
}),
|
|
600
|
+
];
|
|
601
|
+
case 'chat_turn_completed':
|
|
602
|
+
case 'chat_turn_failed': {
|
|
595
603
|
const output = event.details?.output ?? (event.error ? { error: event.error } : undefined);
|
|
596
|
-
return [
|
|
604
|
+
return [
|
|
605
|
+
ingestionEvent('trace-update', event.createdAt, {
|
|
597
606
|
id: langfuseTraceId(traceId),
|
|
598
607
|
...(output !== undefined ? { output } : {}),
|
|
599
608
|
metadata: lifecycleMetadata(event),
|
|
600
|
-
})
|
|
609
|
+
}),
|
|
610
|
+
];
|
|
601
611
|
}
|
|
602
|
-
case
|
|
603
|
-
case
|
|
604
|
-
case
|
|
605
|
-
case
|
|
612
|
+
case 'chat_turn_steered':
|
|
613
|
+
case 'chat_turn_steer_delivered':
|
|
614
|
+
case 'chat_turn_followup_queued':
|
|
615
|
+
case 'chat_turn_followup_delivered': {
|
|
606
616
|
const spanId = langfuseSpanId(traceId, chatInputSpanKey(event));
|
|
607
617
|
return [
|
|
608
|
-
ingestionEvent(
|
|
618
|
+
ingestionEvent('span-create', event.createdAt, {
|
|
609
619
|
id: spanId,
|
|
610
620
|
traceId: langfuseTraceId(traceId),
|
|
611
621
|
parentObservationId: langfuseSpanId(traceId, chatSpanKey(event)),
|
|
@@ -614,7 +624,7 @@ function mapLifecycleEventToLangfuse(event) {
|
|
|
614
624
|
input: event.details?.input,
|
|
615
625
|
metadata: lifecycleMetadata(event),
|
|
616
626
|
}),
|
|
617
|
-
ingestionEvent(
|
|
627
|
+
ingestionEvent('span-update', event.createdAt, {
|
|
618
628
|
id: spanId,
|
|
619
629
|
traceId: langfuseTraceId(traceId),
|
|
620
630
|
endTime: event.createdAt,
|
|
@@ -623,43 +633,45 @@ function mapLifecycleEventToLangfuse(event) {
|
|
|
623
633
|
}),
|
|
624
634
|
];
|
|
625
635
|
}
|
|
626
|
-
case
|
|
627
|
-
case
|
|
636
|
+
case 'subagent_spawned':
|
|
637
|
+
case 'subagent_started': {
|
|
628
638
|
const batchKey = subagentBatchSpanKey(event);
|
|
629
639
|
return [
|
|
630
640
|
...(batchKey
|
|
631
641
|
? [
|
|
632
|
-
ingestionEvent(
|
|
642
|
+
ingestionEvent('span-create', event.createdAt, {
|
|
633
643
|
id: langfuseSpanId(traceId, batchKey),
|
|
634
644
|
traceId: langfuseTraceId(traceId),
|
|
635
645
|
parentObservationId: langfuseSpanId(traceId, chatSpanKey(event)),
|
|
636
|
-
name:
|
|
646
|
+
name: 'subagent fan-out',
|
|
637
647
|
startTime: event.createdAt,
|
|
638
648
|
input: event.details?.input,
|
|
639
649
|
metadata: lifecycleMetadata(event),
|
|
640
650
|
}),
|
|
641
651
|
]
|
|
642
652
|
: []),
|
|
643
|
-
ingestionEvent(
|
|
653
|
+
ingestionEvent('span-create', event.createdAt, {
|
|
644
654
|
id: langfuseSpanId(traceId, subagentSpanKey(event)),
|
|
645
655
|
traceId: langfuseTraceId(traceId),
|
|
646
656
|
parentObservationId: langfuseSpanId(traceId, batchKey ?? subagentSpawnToolSpanKey(event) ?? chatSpanKey(event)),
|
|
647
|
-
name:
|
|
657
|
+
name: 'subagent',
|
|
648
658
|
startTime: event.createdAt,
|
|
649
659
|
metadata: lifecycleMetadata(event),
|
|
650
660
|
}),
|
|
651
661
|
];
|
|
652
662
|
}
|
|
653
|
-
case
|
|
654
|
-
case
|
|
655
|
-
case
|
|
656
|
-
return [
|
|
663
|
+
case 'subagent_completed':
|
|
664
|
+
case 'subagent_failed':
|
|
665
|
+
case 'subagent_cancelled':
|
|
666
|
+
return [
|
|
667
|
+
ingestionEvent('span-update', event.createdAt, {
|
|
657
668
|
id: langfuseSpanId(traceId, subagentSpanKey(event)),
|
|
658
669
|
traceId: langfuseTraceId(traceId),
|
|
659
670
|
endTime: event.createdAt,
|
|
660
671
|
metadata: lifecycleMetadata(event),
|
|
661
672
|
...(event.error ? { output: { error: event.error } } : {}),
|
|
662
|
-
})
|
|
673
|
+
}),
|
|
674
|
+
];
|
|
663
675
|
default:
|
|
664
676
|
return assertNever(event.type);
|
|
665
677
|
}
|
|
@@ -668,8 +680,8 @@ function mapToolEventToLangfuse(event) {
|
|
|
668
680
|
const traceId = requireTraceId(event.traceId);
|
|
669
681
|
const spanId = langfuseSpanId(traceId, `tool:${event.sessionId}:${event.toolCallId}:${event.toolName}`);
|
|
670
682
|
const parentObservationId = langfuseParentObservationId(traceId, event);
|
|
671
|
-
if (event.status ===
|
|
672
|
-
return ingestionEvent(
|
|
683
|
+
if (event.status === 'started') {
|
|
684
|
+
return ingestionEvent('span-create', event.createdAt, {
|
|
673
685
|
id: spanId,
|
|
674
686
|
traceId: langfuseTraceId(traceId),
|
|
675
687
|
parentObservationId,
|
|
@@ -679,7 +691,7 @@ function mapToolEventToLangfuse(event) {
|
|
|
679
691
|
metadata: toolMetadata(event),
|
|
680
692
|
});
|
|
681
693
|
}
|
|
682
|
-
return ingestionEvent(
|
|
694
|
+
return ingestionEvent('span-update', event.createdAt, {
|
|
683
695
|
id: spanId,
|
|
684
696
|
traceId: langfuseTraceId(traceId),
|
|
685
697
|
endTime: event.createdAt,
|
|
@@ -691,8 +703,8 @@ function mapLlmGenerationEventToLangfuse(event) {
|
|
|
691
703
|
const traceId = requireTraceId(event.traceId);
|
|
692
704
|
const id = langfuseSpanId(traceId, llmGenerationKey(event));
|
|
693
705
|
const parentObservationId = langfuseParentObservationId(traceId, event);
|
|
694
|
-
if (event.status ===
|
|
695
|
-
return ingestionEvent(
|
|
706
|
+
if (event.status === 'started') {
|
|
707
|
+
return ingestionEvent('generation-create', event.createdAt, {
|
|
696
708
|
id,
|
|
697
709
|
traceId: langfuseTraceId(traceId),
|
|
698
710
|
parentObservationId,
|
|
@@ -707,14 +719,16 @@ function mapLlmGenerationEventToLangfuse(event) {
|
|
|
707
719
|
metadata: llmGenerationMetadata(event),
|
|
708
720
|
});
|
|
709
721
|
}
|
|
710
|
-
return ingestionEvent(
|
|
722
|
+
return ingestionEvent('generation-update', event.createdAt, {
|
|
711
723
|
id,
|
|
712
724
|
traceId: langfuseTraceId(traceId),
|
|
713
725
|
endTime: event.createdAt,
|
|
714
726
|
output: event.output ?? (event.error ? { error: event.error } : undefined),
|
|
715
|
-
level: event.error ?
|
|
727
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
716
728
|
...(event.error ? { statusMessage: event.error } : {}),
|
|
717
|
-
...(event.usage
|
|
729
|
+
...(event.usage
|
|
730
|
+
? { usage: toLangfuseUsage(event.usage), usageDetails: toLangfuseUsageDetails(event.usage) }
|
|
731
|
+
: {}),
|
|
718
732
|
model: event.model.model,
|
|
719
733
|
modelParameters: {
|
|
720
734
|
provider: event.model.provider,
|
|
@@ -739,32 +753,32 @@ function mapLifecycleEventToOtelSpans(event, pendingStarts) {
|
|
|
739
753
|
const rootKey = chatSpanKey(event);
|
|
740
754
|
const rootSpanId = langfuseSpanId(traceId, rootKey);
|
|
741
755
|
switch (event.type) {
|
|
742
|
-
case
|
|
756
|
+
case 'chat_turn_started':
|
|
743
757
|
pendingStarts.set(rootKey, {
|
|
744
758
|
traceId: langfuseTraceId(traceId),
|
|
745
759
|
spanId: rootSpanId,
|
|
746
|
-
name:
|
|
760
|
+
name: 'copilot-chat-turn',
|
|
747
761
|
startTime: event.createdAt,
|
|
748
762
|
attributes: {
|
|
749
763
|
...lifecycleMetadata(event),
|
|
750
|
-
...langfuseObservationAttributes({ input: event.details?.input, level:
|
|
764
|
+
...langfuseObservationAttributes({ input: event.details?.input, level: 'DEFAULT' }),
|
|
751
765
|
...langfuseTraceAttributes({ input: event.details?.input }),
|
|
752
766
|
},
|
|
753
767
|
});
|
|
754
768
|
return [];
|
|
755
|
-
case
|
|
756
|
-
case
|
|
769
|
+
case 'chat_turn_completed':
|
|
770
|
+
case 'chat_turn_failed':
|
|
757
771
|
return [
|
|
758
772
|
completeOtelSpan(pendingStarts, rootKey, {
|
|
759
773
|
traceId: langfuseTraceId(traceId),
|
|
760
774
|
spanId: rootSpanId,
|
|
761
|
-
name:
|
|
775
|
+
name: 'copilot-chat-turn',
|
|
762
776
|
startTime: event.createdAt,
|
|
763
777
|
attributes: {
|
|
764
778
|
...lifecycleMetadata(event),
|
|
765
779
|
...langfuseObservationAttributes({
|
|
766
780
|
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
767
|
-
level: event.error ?
|
|
781
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
768
782
|
}),
|
|
769
783
|
...langfuseTraceAttributes({
|
|
770
784
|
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
@@ -772,10 +786,10 @@ function mapLifecycleEventToOtelSpans(event, pendingStarts) {
|
|
|
772
786
|
},
|
|
773
787
|
}, event.createdAt, event.error),
|
|
774
788
|
];
|
|
775
|
-
case
|
|
776
|
-
case
|
|
777
|
-
case
|
|
778
|
-
case
|
|
789
|
+
case 'chat_turn_steered':
|
|
790
|
+
case 'chat_turn_steer_delivered':
|
|
791
|
+
case 'chat_turn_followup_queued':
|
|
792
|
+
case 'chat_turn_followup_delivered':
|
|
779
793
|
return [
|
|
780
794
|
{
|
|
781
795
|
traceId: langfuseTraceId(traceId),
|
|
@@ -789,43 +803,43 @@ function mapLifecycleEventToOtelSpans(event, pendingStarts) {
|
|
|
789
803
|
...langfuseObservationAttributes({
|
|
790
804
|
input: event.details?.input,
|
|
791
805
|
output: event.details?.output ?? chatInputLifecycleOutput(event),
|
|
792
|
-
level:
|
|
806
|
+
level: 'DEFAULT',
|
|
793
807
|
}),
|
|
794
808
|
},
|
|
795
809
|
},
|
|
796
810
|
];
|
|
797
|
-
case
|
|
798
|
-
case
|
|
811
|
+
case 'subagent_spawned':
|
|
812
|
+
case 'subagent_started': {
|
|
799
813
|
const key = subagentSpanKey(event);
|
|
800
814
|
pendingStarts.set(key, {
|
|
801
815
|
traceId: langfuseTraceId(traceId),
|
|
802
816
|
spanId: langfuseSpanId(traceId, key),
|
|
803
817
|
parentSpanId: langfuseSpanId(traceId, subagentSpawnToolSpanKey(event) ?? rootKey),
|
|
804
|
-
name:
|
|
818
|
+
name: 'subagent',
|
|
805
819
|
startTime: event.createdAt,
|
|
806
820
|
attributes: {
|
|
807
821
|
...lifecycleMetadata(event),
|
|
808
|
-
...langfuseObservationAttributes({ input: event.details?.input, level:
|
|
822
|
+
...langfuseObservationAttributes({ input: event.details?.input, level: 'DEFAULT' }),
|
|
809
823
|
},
|
|
810
824
|
});
|
|
811
825
|
return [];
|
|
812
826
|
}
|
|
813
|
-
case
|
|
814
|
-
case
|
|
815
|
-
case
|
|
827
|
+
case 'subagent_completed':
|
|
828
|
+
case 'subagent_failed':
|
|
829
|
+
case 'subagent_cancelled': {
|
|
816
830
|
const key = subagentSpanKey(event);
|
|
817
831
|
return [
|
|
818
832
|
completeOtelSpan(pendingStarts, key, {
|
|
819
833
|
traceId: langfuseTraceId(traceId),
|
|
820
834
|
spanId: langfuseSpanId(traceId, key),
|
|
821
835
|
parentSpanId: langfuseSpanId(traceId, subagentSpawnToolSpanKey(event) ?? rootKey),
|
|
822
|
-
name:
|
|
836
|
+
name: 'subagent',
|
|
823
837
|
startTime: event.createdAt,
|
|
824
838
|
attributes: {
|
|
825
839
|
...lifecycleMetadata(event),
|
|
826
840
|
...langfuseObservationAttributes({
|
|
827
841
|
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
828
|
-
level: event.error ?
|
|
842
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
829
843
|
}),
|
|
830
844
|
},
|
|
831
845
|
}, event.createdAt, event.error),
|
|
@@ -848,17 +862,17 @@ function mapToolEventToOtelSpans(event, pendingStarts) {
|
|
|
848
862
|
...toolMetadata(event),
|
|
849
863
|
...langfuseObservationAttributes({
|
|
850
864
|
output: event.error ? { error: event.error } : event.details,
|
|
851
|
-
level: event.error ?
|
|
865
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
852
866
|
}),
|
|
853
867
|
},
|
|
854
868
|
};
|
|
855
|
-
if (event.status ===
|
|
869
|
+
if (event.status === 'started') {
|
|
856
870
|
pendingStarts.set(key, {
|
|
857
871
|
...fallbackStart,
|
|
858
872
|
attributes: {
|
|
859
873
|
...fallbackStart.attributes,
|
|
860
874
|
...(event.args ? { args: event.args } : {}),
|
|
861
|
-
...langfuseObservationAttributes({ input: event.args, level:
|
|
875
|
+
...langfuseObservationAttributes({ input: event.args, level: 'DEFAULT' }),
|
|
862
876
|
},
|
|
863
877
|
});
|
|
864
878
|
return [];
|
|
@@ -876,23 +890,23 @@ function mapLlmGenerationEventToOtelSpans(event, pendingStarts) {
|
|
|
876
890
|
startTime: event.createdAt,
|
|
877
891
|
attributes: {
|
|
878
892
|
...llmGenerationMetadata(event),
|
|
879
|
-
|
|
893
|
+
'langfuse.observation.type': 'generation',
|
|
880
894
|
model: event.model.model,
|
|
881
895
|
provider: event.model.provider,
|
|
882
896
|
...(event.model.thinkingLevel ? { thinkingLevel: event.model.thinkingLevel } : {}),
|
|
883
897
|
...langfuseObservationAttributes({
|
|
884
898
|
output: event.output ?? (event.error ? { error: event.error } : undefined),
|
|
885
|
-
level: event.error ?
|
|
899
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
886
900
|
}),
|
|
887
901
|
...(event.usage ? flattenUsageAttributes(event.usage) : {}),
|
|
888
902
|
},
|
|
889
903
|
};
|
|
890
|
-
if (event.status ===
|
|
904
|
+
if (event.status === 'started') {
|
|
891
905
|
pendingStarts.set(key, {
|
|
892
906
|
...fallbackStart,
|
|
893
907
|
attributes: {
|
|
894
908
|
...fallbackStart.attributes,
|
|
895
|
-
...langfuseObservationAttributes({ input: event.input, level:
|
|
909
|
+
...langfuseObservationAttributes({ input: event.input, level: 'DEFAULT' }),
|
|
896
910
|
},
|
|
897
911
|
});
|
|
898
912
|
return [];
|
|
@@ -914,33 +928,37 @@ function completeOtelSpan(pendingStarts, key, fallbackStart, endTime, error) {
|
|
|
914
928
|
}
|
|
915
929
|
function langfuseObservationAttributes(input) {
|
|
916
930
|
return {
|
|
917
|
-
|
|
931
|
+
'langfuse.observation.type': 'span',
|
|
918
932
|
...(input.input !== undefined
|
|
919
933
|
? {
|
|
920
|
-
|
|
921
|
-
|
|
934
|
+
'langfuse.observation.input': toLangfuseObservationPayload(input.input),
|
|
935
|
+
'input.value': toLangfuseObservationPayload(input.input),
|
|
922
936
|
}
|
|
923
937
|
: {}),
|
|
924
938
|
...(input.output !== undefined
|
|
925
939
|
? {
|
|
926
|
-
|
|
927
|
-
|
|
940
|
+
'langfuse.observation.output': toLangfuseObservationPayload(input.output),
|
|
941
|
+
'output.value': toLangfuseObservationPayload(input.output),
|
|
928
942
|
}
|
|
929
943
|
: {}),
|
|
930
|
-
...(input.level ? {
|
|
944
|
+
...(input.level ? { 'langfuse.observation.level': input.level } : {}),
|
|
931
945
|
};
|
|
932
946
|
}
|
|
933
947
|
function langfuseTraceAttributes(input) {
|
|
934
948
|
return {
|
|
935
|
-
...(input.input !== undefined
|
|
936
|
-
|
|
949
|
+
...(input.input !== undefined
|
|
950
|
+
? { 'langfuse.trace.input': toLangfuseTracePayload(input.input) }
|
|
951
|
+
: {}),
|
|
952
|
+
...(input.output !== undefined
|
|
953
|
+
? { 'langfuse.trace.output': toLangfuseTracePayload(input.output) }
|
|
954
|
+
: {}),
|
|
937
955
|
};
|
|
938
956
|
}
|
|
939
957
|
function toLangfuseObservationPayload(value) {
|
|
940
958
|
return JSON.stringify(value);
|
|
941
959
|
}
|
|
942
960
|
function toLangfuseTracePayload(value) {
|
|
943
|
-
return typeof value ===
|
|
961
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
944
962
|
}
|
|
945
963
|
function chatSpanKey(event) {
|
|
946
964
|
const sessionId = event.parentSessionId ?? event.sessionId;
|
|
@@ -954,7 +972,7 @@ function subagentSpanKey(event) {
|
|
|
954
972
|
return `subagent:${event.runId ?? event.childSessionId ?? event.id}`;
|
|
955
973
|
}
|
|
956
974
|
function subagentBatchSpanKey(event) {
|
|
957
|
-
const batchId = event.spawnBatchId ?? stringFromJsonObject(event.details,
|
|
975
|
+
const batchId = event.spawnBatchId ?? stringFromJsonObject(event.details, 'spawnBatchId');
|
|
958
976
|
return event.traceId && batchId ? `subagent-batch:${event.traceId}:${batchId}` : undefined;
|
|
959
977
|
}
|
|
960
978
|
function telemetryEventSubagentSpanKey(event) {
|
|
@@ -983,23 +1001,25 @@ function toolObservationName(event) {
|
|
|
983
1001
|
}
|
|
984
1002
|
function chatInputObservationName(event) {
|
|
985
1003
|
const prefix = chatInputObservationPrefix(event.type);
|
|
986
|
-
const input = typeof event.details?.input ===
|
|
1004
|
+
const input = typeof event.details?.input === 'string'
|
|
1005
|
+
? truncateObservationSummary(event.details.input)
|
|
1006
|
+
: undefined;
|
|
987
1007
|
return input ? `${prefix} [${input}]` : prefix;
|
|
988
1008
|
}
|
|
989
1009
|
function chatInputObservationPrefix(type) {
|
|
990
|
-
if (type ===
|
|
991
|
-
return
|
|
1010
|
+
if (type === 'chat_turn_steered') {
|
|
1011
|
+
return 'chat-steer';
|
|
992
1012
|
}
|
|
993
|
-
if (type ===
|
|
994
|
-
return
|
|
1013
|
+
if (type === 'chat_turn_steer_delivered') {
|
|
1014
|
+
return 'chat-steer-delivered';
|
|
995
1015
|
}
|
|
996
|
-
if (type ===
|
|
997
|
-
return
|
|
1016
|
+
if (type === 'chat_turn_followup_delivered') {
|
|
1017
|
+
return 'chat-followup-delivered';
|
|
998
1018
|
}
|
|
999
|
-
return
|
|
1019
|
+
return 'chat-followup';
|
|
1000
1020
|
}
|
|
1001
1021
|
function chatInputLifecycleOutput(event) {
|
|
1002
|
-
return event.type ===
|
|
1022
|
+
return event.type === 'chat_turn_steer_delivered' || event.type === 'chat_turn_followup_delivered'
|
|
1003
1023
|
? { delivered: true, turnMode: event.details?.turnMode }
|
|
1004
1024
|
: { accepted: true, turnMode: event.details?.turnMode };
|
|
1005
1025
|
}
|
|
@@ -1007,59 +1027,59 @@ function summarizeToolArgsForName(toolName, args) {
|
|
|
1007
1027
|
if (!args) {
|
|
1008
1028
|
return undefined;
|
|
1009
1029
|
}
|
|
1010
|
-
const pathValue = stringArg(args,
|
|
1030
|
+
const pathValue = stringArg(args, 'path') ?? stringArg(args, 'filePath') ?? stringArg(args, 'absolutePath');
|
|
1011
1031
|
if (pathValue) {
|
|
1012
1032
|
return truncateObservationSummary(pathValue);
|
|
1013
1033
|
}
|
|
1014
|
-
const command = stringArg(args,
|
|
1034
|
+
const command = stringArg(args, 'command');
|
|
1015
1035
|
if (command) {
|
|
1016
1036
|
return truncateObservationSummary(command);
|
|
1017
1037
|
}
|
|
1018
|
-
const query = stringArg(args,
|
|
1038
|
+
const query = stringArg(args, 'query');
|
|
1019
1039
|
if (query) {
|
|
1020
1040
|
return truncateObservationSummary(query);
|
|
1021
1041
|
}
|
|
1022
|
-
const task = stringArg(args,
|
|
1023
|
-
if (task && toolName ===
|
|
1042
|
+
const task = stringArg(args, 'task');
|
|
1043
|
+
if (task && toolName === 'sessions_spawn') {
|
|
1024
1044
|
return truncateObservationSummary(task);
|
|
1025
1045
|
}
|
|
1026
|
-
const code = stringArg(args,
|
|
1046
|
+
const code = stringArg(args, 'code');
|
|
1027
1047
|
if (code) {
|
|
1028
1048
|
return truncateObservationSummary(code);
|
|
1029
1049
|
}
|
|
1030
|
-
const name = stringArg(args,
|
|
1031
|
-
if (name && toolName.startsWith(
|
|
1050
|
+
const name = stringArg(args, 'name');
|
|
1051
|
+
if (name && toolName.startsWith('mcp_')) {
|
|
1032
1052
|
return truncateObservationSummary(name);
|
|
1033
1053
|
}
|
|
1034
1054
|
return undefined;
|
|
1035
1055
|
}
|
|
1036
1056
|
function llmGenerationObservationName(event) {
|
|
1037
|
-
return `llm-generation [${event.runId || event.childSessionId ?
|
|
1057
|
+
return `llm-generation [${event.runId || event.childSessionId ? 'subagent' : 'main'}] [${summarizeLlmGenerationInputForName(event.input)}]`;
|
|
1038
1058
|
}
|
|
1039
1059
|
function summarizeLlmGenerationInputForName(input) {
|
|
1040
|
-
if (typeof input ===
|
|
1060
|
+
if (typeof input === 'string') {
|
|
1041
1061
|
return truncateObservationSummary(input);
|
|
1042
1062
|
}
|
|
1043
|
-
if (input && typeof input ===
|
|
1063
|
+
if (input && typeof input === 'object' && !Array.isArray(input)) {
|
|
1044
1064
|
const continuation = input.continuation === true;
|
|
1045
|
-
const index = typeof input.llmGenerationIndex ===
|
|
1046
|
-
const toolResults = typeof input.previousToolResultCount ===
|
|
1065
|
+
const index = typeof input.llmGenerationIndex === 'number' ? input.llmGenerationIndex : undefined;
|
|
1066
|
+
const toolResults = typeof input.previousToolResultCount === 'number' ? input.previousToolResultCount : undefined;
|
|
1047
1067
|
if (continuation) {
|
|
1048
|
-
return truncateObservationSummary(`continuation${index !== undefined ? ` #${index}` :
|
|
1068
|
+
return truncateObservationSummary(`continuation${index !== undefined ? ` #${index}` : ''}${toolResults !== undefined ? ` after ${toolResults} tool result(s)` : ''}`);
|
|
1049
1069
|
}
|
|
1050
1070
|
}
|
|
1051
|
-
return
|
|
1071
|
+
return 'request';
|
|
1052
1072
|
}
|
|
1053
1073
|
function stringArg(args, key) {
|
|
1054
1074
|
const value = args[key];
|
|
1055
|
-
return typeof value ===
|
|
1075
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
1056
1076
|
}
|
|
1057
1077
|
function stringFromJsonObject(value, key) {
|
|
1058
1078
|
const field = value?.[key];
|
|
1059
|
-
return typeof field ===
|
|
1079
|
+
return typeof field === 'string' && field.trim() ? field.trim() : undefined;
|
|
1060
1080
|
}
|
|
1061
1081
|
function truncateObservationSummary(value, maxLength = 90) {
|
|
1062
|
-
const normalized = value.replace(/\s+/g,
|
|
1082
|
+
const normalized = value.replace(/\s+/g, ' ').trim();
|
|
1063
1083
|
return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1)}…` : normalized;
|
|
1064
1084
|
}
|
|
1065
1085
|
function ensureEndAfterStart(startTime, endTime) {
|
|
@@ -1071,14 +1091,16 @@ function toOtelTracePayload(spans, config) {
|
|
|
1071
1091
|
{
|
|
1072
1092
|
resource: {
|
|
1073
1093
|
attributes: [
|
|
1074
|
-
otelAttribute(
|
|
1075
|
-
...(config.serviceVersion
|
|
1076
|
-
|
|
1094
|
+
otelAttribute('service.name', config.serviceName ?? 'pi-server'),
|
|
1095
|
+
...(config.serviceVersion
|
|
1096
|
+
? [otelAttribute('service.version', config.serviceVersion)]
|
|
1097
|
+
: []),
|
|
1098
|
+
otelAttribute('telemetry.sdk.name', '@amaster.ai/pi-telemetry'),
|
|
1077
1099
|
],
|
|
1078
1100
|
},
|
|
1079
1101
|
scopeSpans: [
|
|
1080
1102
|
{
|
|
1081
|
-
scope: { name:
|
|
1103
|
+
scope: { name: '@amaster.ai/pi-telemetry', version: '0.1.0' },
|
|
1082
1104
|
spans: spans.map(toOtelSpanPayload),
|
|
1083
1105
|
},
|
|
1084
1106
|
],
|
|
@@ -1105,13 +1127,13 @@ function otelAttribute(key, value) {
|
|
|
1105
1127
|
return { key, value: otelAttributeValue(value) };
|
|
1106
1128
|
}
|
|
1107
1129
|
function otelAttributeValue(value) {
|
|
1108
|
-
if (typeof value ===
|
|
1130
|
+
if (typeof value === 'boolean') {
|
|
1109
1131
|
return { boolValue: value };
|
|
1110
1132
|
}
|
|
1111
|
-
if (typeof value ===
|
|
1133
|
+
if (typeof value === 'number') {
|
|
1112
1134
|
return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
|
|
1113
1135
|
}
|
|
1114
|
-
if (typeof value ===
|
|
1136
|
+
if (typeof value === 'string') {
|
|
1115
1137
|
return { stringValue: value };
|
|
1116
1138
|
}
|
|
1117
1139
|
return { stringValue: JSON.stringify(value) };
|
|
@@ -1121,13 +1143,13 @@ function toUnixNano(iso) {
|
|
|
1121
1143
|
return String(BigInt(Number.isFinite(millis) ? millis : Date.now()) * 1000000n);
|
|
1122
1144
|
}
|
|
1123
1145
|
function normalizeOtelTracesEndpoint(endpoint) {
|
|
1124
|
-
return endpoint.endsWith(
|
|
1146
|
+
return endpoint.endsWith('/v1/traces') ? endpoint : `${endpoint.replace(/\/+$/, '')}/v1/traces`;
|
|
1125
1147
|
}
|
|
1126
1148
|
function shortCorrelationId(value) {
|
|
1127
1149
|
if (!value) {
|
|
1128
1150
|
return undefined;
|
|
1129
1151
|
}
|
|
1130
|
-
const normalized = value.startsWith(
|
|
1152
|
+
const normalized = value.startsWith('trace:') ? value.slice('trace:'.length) : value;
|
|
1131
1153
|
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
1154
|
if (uuid) {
|
|
1133
1155
|
return uuid.slice(0, 8);
|
|
@@ -1141,11 +1163,14 @@ function compactSessionId(value) {
|
|
|
1141
1163
|
if (!value) {
|
|
1142
1164
|
return undefined;
|
|
1143
1165
|
}
|
|
1144
|
-
const [root, ...subagents] = value.split(
|
|
1166
|
+
const [root, ...subagents] = value.split(':subagent:');
|
|
1145
1167
|
if (subagents.length === 0) {
|
|
1146
1168
|
return value;
|
|
1147
1169
|
}
|
|
1148
|
-
return [
|
|
1170
|
+
return [
|
|
1171
|
+
root,
|
|
1172
|
+
...subagents.map((sessionId) => `sub:${shortCorrelationId(sessionId) ?? sessionId}`),
|
|
1173
|
+
].join('/');
|
|
1149
1174
|
}
|
|
1150
1175
|
function lineageMetadata(event) {
|
|
1151
1176
|
const sessionId = compactSessionId(event.sessionId);
|
|
@@ -1202,7 +1227,7 @@ function toLangfuseUsage(usage) {
|
|
|
1202
1227
|
...(usage.input !== undefined ? { input: usage.input } : {}),
|
|
1203
1228
|
...(usage.output !== undefined ? { output: usage.output } : {}),
|
|
1204
1229
|
...(usage.totalTokens !== undefined ? { total: usage.totalTokens } : {}),
|
|
1205
|
-
unit:
|
|
1230
|
+
unit: 'TOKENS',
|
|
1206
1231
|
...(usage.cost?.input !== undefined ? { inputCost: usage.cost.input } : {}),
|
|
1207
1232
|
...(usage.cost?.output !== undefined ? { outputCost: usage.cost.output } : {}),
|
|
1208
1233
|
...(usage.cost?.total !== undefined ? { totalCost: usage.cost.total } : {}),
|
|
@@ -1219,12 +1244,12 @@ function toLangfuseUsageDetails(usage) {
|
|
|
1219
1244
|
}
|
|
1220
1245
|
function flattenUsageAttributes(usage) {
|
|
1221
1246
|
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 ? {
|
|
1247
|
+
...(usage.input !== undefined ? { 'usage.input': usage.input } : {}),
|
|
1248
|
+
...(usage.output !== undefined ? { 'usage.output': usage.output } : {}),
|
|
1249
|
+
...(usage.cacheRead !== undefined ? { 'usage.cache_read': usage.cacheRead } : {}),
|
|
1250
|
+
...(usage.cacheWrite !== undefined ? { 'usage.cache_write': usage.cacheWrite } : {}),
|
|
1251
|
+
...(usage.totalTokens !== undefined ? { 'usage.total_tokens': usage.totalTokens } : {}),
|
|
1252
|
+
...(usage.cost?.total !== undefined ? { 'usage.cost.total': usage.cost.total } : {}),
|
|
1228
1253
|
};
|
|
1229
1254
|
}
|
|
1230
1255
|
function ingestionEvent(type, timestamp, body) {
|
|
@@ -1242,7 +1267,7 @@ function langfuseSpanId(traceId, key) {
|
|
|
1242
1267
|
return stableHex(`span:${traceId}:${key}`, 16);
|
|
1243
1268
|
}
|
|
1244
1269
|
function stableHex(input, length) {
|
|
1245
|
-
return createHash(
|
|
1270
|
+
return createHash('sha256').update(input).digest('hex').slice(0, length);
|
|
1246
1271
|
}
|
|
1247
1272
|
function applyTelemetryRedaction(config, event) {
|
|
1248
1273
|
const redacted = config.redactEvent ? config.redactEvent(event) : event;
|
|
@@ -1274,13 +1299,13 @@ function redactJsonObjectPayload(input) {
|
|
|
1274
1299
|
return rest;
|
|
1275
1300
|
}
|
|
1276
1301
|
function isToolEvent(event) {
|
|
1277
|
-
return
|
|
1302
|
+
return 'toolCallId' in event;
|
|
1278
1303
|
}
|
|
1279
1304
|
function isLlmGenerationEvent(event) {
|
|
1280
|
-
return
|
|
1305
|
+
return 'llmGenerationId' in event;
|
|
1281
1306
|
}
|
|
1282
1307
|
function parseBoolean(value) {
|
|
1283
|
-
return value ===
|
|
1308
|
+
return value === '1' || value === 'true' || value === 'TRUE' || value === 'yes';
|
|
1284
1309
|
}
|
|
1285
1310
|
function parseBooleanWithDefault(value, fallback) {
|
|
1286
1311
|
if (value === undefined) {
|
|
@@ -1290,7 +1315,7 @@ function parseBooleanWithDefault(value, fallback) {
|
|
|
1290
1315
|
}
|
|
1291
1316
|
function parseLangfuseTransport(value) {
|
|
1292
1317
|
const normalized = value?.trim().toLowerCase();
|
|
1293
|
-
return normalized ===
|
|
1318
|
+
return normalized === 'sdk' || normalized === 'ingestion' ? normalized : undefined;
|
|
1294
1319
|
}
|
|
1295
1320
|
function parsePositiveInteger(value, fallback) {
|
|
1296
1321
|
const parsed = Number(value);
|
|
@@ -1305,7 +1330,7 @@ function assertNever(value) {
|
|
|
1305
1330
|
}
|
|
1306
1331
|
function requireTraceId(traceId) {
|
|
1307
1332
|
if (!traceId) {
|
|
1308
|
-
throw new Error(
|
|
1333
|
+
throw new Error('Telemetry event is missing traceId');
|
|
1309
1334
|
}
|
|
1310
1335
|
return traceId;
|
|
1311
1336
|
}
|