@amaster.ai/pi-telemetry 0.1.0-beta.0 → 0.1.2-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/README.md +68 -38
- package/dist/config.d.ts +27 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +12 -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 +126 -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 +6 -3
- package/dist/langfuse.d.ts.map +1 -1
- package/dist/langfuse.js +249 -194
- 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 +20 -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,29 +545,59 @@ 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);
|
|
546
552
|
}
|
|
553
|
+
export function createLangfuseExporter(telemetryConfig) {
|
|
554
|
+
const config = resolveLangfuseExporterConfig(telemetryConfig);
|
|
555
|
+
if (!config.enabled) {
|
|
556
|
+
return new NoopRuntimeEventExporter();
|
|
557
|
+
}
|
|
558
|
+
if (config.transport === 'sdk') {
|
|
559
|
+
return new LangfuseSdkRuntimeEventExporter(config);
|
|
560
|
+
}
|
|
561
|
+
return new LangfuseHttpRuntimeEventExporter(config);
|
|
562
|
+
}
|
|
563
|
+
export function resolveLangfuseExporterConfig(telemetryConfig) {
|
|
564
|
+
const lf = telemetryConfig.langfuse;
|
|
565
|
+
const publicKey = lf?.publicKey ?? '';
|
|
566
|
+
const secretKey = lf?.secretKey ?? '';
|
|
567
|
+
const credentialsPresent = Boolean(publicKey && secretKey);
|
|
568
|
+
const requestedTransport = lf?.transport;
|
|
569
|
+
const transport = requestedTransport ?? (credentialsPresent ? 'sdk' : 'ingestion');
|
|
570
|
+
return {
|
|
571
|
+
enabled: Boolean(lf?.enabled && credentialsPresent),
|
|
572
|
+
transport,
|
|
573
|
+
publicKey,
|
|
574
|
+
secretKey,
|
|
575
|
+
baseUrl: lf?.baseUrl ?? DEFAULT_LANGFUSE_BASE_URL,
|
|
576
|
+
flushAt: lf?.flushAt ?? DEFAULT_FLUSH_AT,
|
|
577
|
+
flushIntervalMs: lf?.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
|
|
578
|
+
serviceName: telemetryConfig.serviceName ?? 'pi-server',
|
|
579
|
+
...(telemetryConfig.serviceVersion ? { serviceVersion: telemetryConfig.serviceVersion } : {}),
|
|
580
|
+
includePayloads: telemetryConfig.includePayloads ?? true,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
547
583
|
export function resolveLangfuseConfig(env) {
|
|
548
584
|
const enabled = parseBoolean(env.LANGFUSE_ENABLED);
|
|
549
585
|
const publicKey = trim(env.LANGFUSE_PUBLIC_KEY);
|
|
550
586
|
const secretKey = trim(env.LANGFUSE_SECRET_KEY);
|
|
551
587
|
const baseUrl = trim(env.LANGFUSE_BASE_URL) ?? DEFAULT_LANGFUSE_BASE_URL;
|
|
552
588
|
const requestedTransport = parseLangfuseTransport(env.LANGFUSE_TRANSPORT);
|
|
553
|
-
const transport = requestedTransport ?? (publicKey && secretKey ?
|
|
589
|
+
const transport = requestedTransport ?? (publicKey && secretKey ? 'sdk' : 'ingestion');
|
|
554
590
|
const credentialsPresent = Boolean(publicKey && secretKey);
|
|
555
591
|
const serviceVersion = trim(env.TELEMETRY_SERVICE_VERSION);
|
|
556
592
|
return {
|
|
557
593
|
enabled: Boolean(enabled && credentialsPresent),
|
|
558
594
|
transport,
|
|
559
|
-
publicKey: publicKey ??
|
|
560
|
-
secretKey: secretKey ??
|
|
595
|
+
publicKey: publicKey ?? '',
|
|
596
|
+
secretKey: secretKey ?? '',
|
|
561
597
|
baseUrl,
|
|
562
598
|
flushAt: parsePositiveInteger(env.LANGFUSE_FLUSH_AT, DEFAULT_FLUSH_AT),
|
|
563
599
|
flushIntervalMs: parsePositiveInteger(env.LANGFUSE_FLUSH_INTERVAL_MS, DEFAULT_FLUSH_INTERVAL_MS),
|
|
564
|
-
serviceName: trim(env.TELEMETRY_SERVICE_NAME ?? env.OTEL_SERVICE_NAME) ??
|
|
600
|
+
serviceName: trim(env.TELEMETRY_SERVICE_NAME ?? env.OTEL_SERVICE_NAME) ?? 'pi-server',
|
|
565
601
|
...(serviceVersion ? { serviceVersion } : {}),
|
|
566
602
|
includePayloads: parseBooleanWithDefault(env.TELEMETRY_INCLUDE_PAYLOADS ?? env.LANGFUSE_INCLUDE_PAYLOADS, true),
|
|
567
603
|
};
|
|
@@ -581,31 +617,35 @@ export function mapRuntimeEventToLangfuse(event) {
|
|
|
581
617
|
function mapLifecycleEventToLangfuse(event) {
|
|
582
618
|
const traceId = requireTraceId(event.traceId);
|
|
583
619
|
switch (event.type) {
|
|
584
|
-
case
|
|
585
|
-
return [
|
|
620
|
+
case 'chat_turn_started':
|
|
621
|
+
return [
|
|
622
|
+
ingestionEvent('trace-create', event.createdAt, {
|
|
586
623
|
id: langfuseTraceId(traceId),
|
|
587
624
|
sessionId: event.sessionId,
|
|
588
|
-
name:
|
|
625
|
+
name: 'copilot-chat-turn',
|
|
589
626
|
timestamp: event.createdAt,
|
|
590
627
|
input: event.details?.input,
|
|
591
628
|
metadata: lifecycleMetadata(event),
|
|
592
|
-
})
|
|
593
|
-
|
|
594
|
-
case
|
|
629
|
+
}),
|
|
630
|
+
];
|
|
631
|
+
case 'chat_turn_completed':
|
|
632
|
+
case 'chat_turn_failed': {
|
|
595
633
|
const output = event.details?.output ?? (event.error ? { error: event.error } : undefined);
|
|
596
|
-
return [
|
|
634
|
+
return [
|
|
635
|
+
ingestionEvent('trace-update', event.createdAt, {
|
|
597
636
|
id: langfuseTraceId(traceId),
|
|
598
637
|
...(output !== undefined ? { output } : {}),
|
|
599
638
|
metadata: lifecycleMetadata(event),
|
|
600
|
-
})
|
|
639
|
+
}),
|
|
640
|
+
];
|
|
601
641
|
}
|
|
602
|
-
case
|
|
603
|
-
case
|
|
604
|
-
case
|
|
605
|
-
case
|
|
642
|
+
case 'chat_turn_steered':
|
|
643
|
+
case 'chat_turn_steer_delivered':
|
|
644
|
+
case 'chat_turn_followup_queued':
|
|
645
|
+
case 'chat_turn_followup_delivered': {
|
|
606
646
|
const spanId = langfuseSpanId(traceId, chatInputSpanKey(event));
|
|
607
647
|
return [
|
|
608
|
-
ingestionEvent(
|
|
648
|
+
ingestionEvent('span-create', event.createdAt, {
|
|
609
649
|
id: spanId,
|
|
610
650
|
traceId: langfuseTraceId(traceId),
|
|
611
651
|
parentObservationId: langfuseSpanId(traceId, chatSpanKey(event)),
|
|
@@ -614,7 +654,7 @@ function mapLifecycleEventToLangfuse(event) {
|
|
|
614
654
|
input: event.details?.input,
|
|
615
655
|
metadata: lifecycleMetadata(event),
|
|
616
656
|
}),
|
|
617
|
-
ingestionEvent(
|
|
657
|
+
ingestionEvent('span-update', event.createdAt, {
|
|
618
658
|
id: spanId,
|
|
619
659
|
traceId: langfuseTraceId(traceId),
|
|
620
660
|
endTime: event.createdAt,
|
|
@@ -623,43 +663,45 @@ function mapLifecycleEventToLangfuse(event) {
|
|
|
623
663
|
}),
|
|
624
664
|
];
|
|
625
665
|
}
|
|
626
|
-
case
|
|
627
|
-
case
|
|
666
|
+
case 'subagent_spawned':
|
|
667
|
+
case 'subagent_started': {
|
|
628
668
|
const batchKey = subagentBatchSpanKey(event);
|
|
629
669
|
return [
|
|
630
670
|
...(batchKey
|
|
631
671
|
? [
|
|
632
|
-
ingestionEvent(
|
|
672
|
+
ingestionEvent('span-create', event.createdAt, {
|
|
633
673
|
id: langfuseSpanId(traceId, batchKey),
|
|
634
674
|
traceId: langfuseTraceId(traceId),
|
|
635
675
|
parentObservationId: langfuseSpanId(traceId, chatSpanKey(event)),
|
|
636
|
-
name:
|
|
676
|
+
name: 'subagent fan-out',
|
|
637
677
|
startTime: event.createdAt,
|
|
638
678
|
input: event.details?.input,
|
|
639
679
|
metadata: lifecycleMetadata(event),
|
|
640
680
|
}),
|
|
641
681
|
]
|
|
642
682
|
: []),
|
|
643
|
-
ingestionEvent(
|
|
683
|
+
ingestionEvent('span-create', event.createdAt, {
|
|
644
684
|
id: langfuseSpanId(traceId, subagentSpanKey(event)),
|
|
645
685
|
traceId: langfuseTraceId(traceId),
|
|
646
686
|
parentObservationId: langfuseSpanId(traceId, batchKey ?? subagentSpawnToolSpanKey(event) ?? chatSpanKey(event)),
|
|
647
|
-
name:
|
|
687
|
+
name: 'subagent',
|
|
648
688
|
startTime: event.createdAt,
|
|
649
689
|
metadata: lifecycleMetadata(event),
|
|
650
690
|
}),
|
|
651
691
|
];
|
|
652
692
|
}
|
|
653
|
-
case
|
|
654
|
-
case
|
|
655
|
-
case
|
|
656
|
-
return [
|
|
693
|
+
case 'subagent_completed':
|
|
694
|
+
case 'subagent_failed':
|
|
695
|
+
case 'subagent_cancelled':
|
|
696
|
+
return [
|
|
697
|
+
ingestionEvent('span-update', event.createdAt, {
|
|
657
698
|
id: langfuseSpanId(traceId, subagentSpanKey(event)),
|
|
658
699
|
traceId: langfuseTraceId(traceId),
|
|
659
700
|
endTime: event.createdAt,
|
|
660
701
|
metadata: lifecycleMetadata(event),
|
|
661
702
|
...(event.error ? { output: { error: event.error } } : {}),
|
|
662
|
-
})
|
|
703
|
+
}),
|
|
704
|
+
];
|
|
663
705
|
default:
|
|
664
706
|
return assertNever(event.type);
|
|
665
707
|
}
|
|
@@ -668,8 +710,8 @@ function mapToolEventToLangfuse(event) {
|
|
|
668
710
|
const traceId = requireTraceId(event.traceId);
|
|
669
711
|
const spanId = langfuseSpanId(traceId, `tool:${event.sessionId}:${event.toolCallId}:${event.toolName}`);
|
|
670
712
|
const parentObservationId = langfuseParentObservationId(traceId, event);
|
|
671
|
-
if (event.status ===
|
|
672
|
-
return ingestionEvent(
|
|
713
|
+
if (event.status === 'started') {
|
|
714
|
+
return ingestionEvent('span-create', event.createdAt, {
|
|
673
715
|
id: spanId,
|
|
674
716
|
traceId: langfuseTraceId(traceId),
|
|
675
717
|
parentObservationId,
|
|
@@ -679,7 +721,7 @@ function mapToolEventToLangfuse(event) {
|
|
|
679
721
|
metadata: toolMetadata(event),
|
|
680
722
|
});
|
|
681
723
|
}
|
|
682
|
-
return ingestionEvent(
|
|
724
|
+
return ingestionEvent('span-update', event.createdAt, {
|
|
683
725
|
id: spanId,
|
|
684
726
|
traceId: langfuseTraceId(traceId),
|
|
685
727
|
endTime: event.createdAt,
|
|
@@ -691,8 +733,8 @@ function mapLlmGenerationEventToLangfuse(event) {
|
|
|
691
733
|
const traceId = requireTraceId(event.traceId);
|
|
692
734
|
const id = langfuseSpanId(traceId, llmGenerationKey(event));
|
|
693
735
|
const parentObservationId = langfuseParentObservationId(traceId, event);
|
|
694
|
-
if (event.status ===
|
|
695
|
-
return ingestionEvent(
|
|
736
|
+
if (event.status === 'started') {
|
|
737
|
+
return ingestionEvent('generation-create', event.createdAt, {
|
|
696
738
|
id,
|
|
697
739
|
traceId: langfuseTraceId(traceId),
|
|
698
740
|
parentObservationId,
|
|
@@ -707,14 +749,16 @@ function mapLlmGenerationEventToLangfuse(event) {
|
|
|
707
749
|
metadata: llmGenerationMetadata(event),
|
|
708
750
|
});
|
|
709
751
|
}
|
|
710
|
-
return ingestionEvent(
|
|
752
|
+
return ingestionEvent('generation-update', event.createdAt, {
|
|
711
753
|
id,
|
|
712
754
|
traceId: langfuseTraceId(traceId),
|
|
713
755
|
endTime: event.createdAt,
|
|
714
756
|
output: event.output ?? (event.error ? { error: event.error } : undefined),
|
|
715
|
-
level: event.error ?
|
|
757
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
716
758
|
...(event.error ? { statusMessage: event.error } : {}),
|
|
717
|
-
...(event.usage
|
|
759
|
+
...(event.usage
|
|
760
|
+
? { usage: toLangfuseUsage(event.usage), usageDetails: toLangfuseUsageDetails(event.usage) }
|
|
761
|
+
: {}),
|
|
718
762
|
model: event.model.model,
|
|
719
763
|
modelParameters: {
|
|
720
764
|
provider: event.model.provider,
|
|
@@ -739,32 +783,32 @@ function mapLifecycleEventToOtelSpans(event, pendingStarts) {
|
|
|
739
783
|
const rootKey = chatSpanKey(event);
|
|
740
784
|
const rootSpanId = langfuseSpanId(traceId, rootKey);
|
|
741
785
|
switch (event.type) {
|
|
742
|
-
case
|
|
786
|
+
case 'chat_turn_started':
|
|
743
787
|
pendingStarts.set(rootKey, {
|
|
744
788
|
traceId: langfuseTraceId(traceId),
|
|
745
789
|
spanId: rootSpanId,
|
|
746
|
-
name:
|
|
790
|
+
name: 'copilot-chat-turn',
|
|
747
791
|
startTime: event.createdAt,
|
|
748
792
|
attributes: {
|
|
749
793
|
...lifecycleMetadata(event),
|
|
750
|
-
...langfuseObservationAttributes({ input: event.details?.input, level:
|
|
794
|
+
...langfuseObservationAttributes({ input: event.details?.input, level: 'DEFAULT' }),
|
|
751
795
|
...langfuseTraceAttributes({ input: event.details?.input }),
|
|
752
796
|
},
|
|
753
797
|
});
|
|
754
798
|
return [];
|
|
755
|
-
case
|
|
756
|
-
case
|
|
799
|
+
case 'chat_turn_completed':
|
|
800
|
+
case 'chat_turn_failed':
|
|
757
801
|
return [
|
|
758
802
|
completeOtelSpan(pendingStarts, rootKey, {
|
|
759
803
|
traceId: langfuseTraceId(traceId),
|
|
760
804
|
spanId: rootSpanId,
|
|
761
|
-
name:
|
|
805
|
+
name: 'copilot-chat-turn',
|
|
762
806
|
startTime: event.createdAt,
|
|
763
807
|
attributes: {
|
|
764
808
|
...lifecycleMetadata(event),
|
|
765
809
|
...langfuseObservationAttributes({
|
|
766
810
|
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
767
|
-
level: event.error ?
|
|
811
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
768
812
|
}),
|
|
769
813
|
...langfuseTraceAttributes({
|
|
770
814
|
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
@@ -772,10 +816,10 @@ function mapLifecycleEventToOtelSpans(event, pendingStarts) {
|
|
|
772
816
|
},
|
|
773
817
|
}, event.createdAt, event.error),
|
|
774
818
|
];
|
|
775
|
-
case
|
|
776
|
-
case
|
|
777
|
-
case
|
|
778
|
-
case
|
|
819
|
+
case 'chat_turn_steered':
|
|
820
|
+
case 'chat_turn_steer_delivered':
|
|
821
|
+
case 'chat_turn_followup_queued':
|
|
822
|
+
case 'chat_turn_followup_delivered':
|
|
779
823
|
return [
|
|
780
824
|
{
|
|
781
825
|
traceId: langfuseTraceId(traceId),
|
|
@@ -789,43 +833,43 @@ function mapLifecycleEventToOtelSpans(event, pendingStarts) {
|
|
|
789
833
|
...langfuseObservationAttributes({
|
|
790
834
|
input: event.details?.input,
|
|
791
835
|
output: event.details?.output ?? chatInputLifecycleOutput(event),
|
|
792
|
-
level:
|
|
836
|
+
level: 'DEFAULT',
|
|
793
837
|
}),
|
|
794
838
|
},
|
|
795
839
|
},
|
|
796
840
|
];
|
|
797
|
-
case
|
|
798
|
-
case
|
|
841
|
+
case 'subagent_spawned':
|
|
842
|
+
case 'subagent_started': {
|
|
799
843
|
const key = subagentSpanKey(event);
|
|
800
844
|
pendingStarts.set(key, {
|
|
801
845
|
traceId: langfuseTraceId(traceId),
|
|
802
846
|
spanId: langfuseSpanId(traceId, key),
|
|
803
847
|
parentSpanId: langfuseSpanId(traceId, subagentSpawnToolSpanKey(event) ?? rootKey),
|
|
804
|
-
name:
|
|
848
|
+
name: 'subagent',
|
|
805
849
|
startTime: event.createdAt,
|
|
806
850
|
attributes: {
|
|
807
851
|
...lifecycleMetadata(event),
|
|
808
|
-
...langfuseObservationAttributes({ input: event.details?.input, level:
|
|
852
|
+
...langfuseObservationAttributes({ input: event.details?.input, level: 'DEFAULT' }),
|
|
809
853
|
},
|
|
810
854
|
});
|
|
811
855
|
return [];
|
|
812
856
|
}
|
|
813
|
-
case
|
|
814
|
-
case
|
|
815
|
-
case
|
|
857
|
+
case 'subagent_completed':
|
|
858
|
+
case 'subagent_failed':
|
|
859
|
+
case 'subagent_cancelled': {
|
|
816
860
|
const key = subagentSpanKey(event);
|
|
817
861
|
return [
|
|
818
862
|
completeOtelSpan(pendingStarts, key, {
|
|
819
863
|
traceId: langfuseTraceId(traceId),
|
|
820
864
|
spanId: langfuseSpanId(traceId, key),
|
|
821
865
|
parentSpanId: langfuseSpanId(traceId, subagentSpawnToolSpanKey(event) ?? rootKey),
|
|
822
|
-
name:
|
|
866
|
+
name: 'subagent',
|
|
823
867
|
startTime: event.createdAt,
|
|
824
868
|
attributes: {
|
|
825
869
|
...lifecycleMetadata(event),
|
|
826
870
|
...langfuseObservationAttributes({
|
|
827
871
|
output: event.details?.output ?? (event.error ? { error: event.error } : undefined),
|
|
828
|
-
level: event.error ?
|
|
872
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
829
873
|
}),
|
|
830
874
|
},
|
|
831
875
|
}, event.createdAt, event.error),
|
|
@@ -848,17 +892,17 @@ function mapToolEventToOtelSpans(event, pendingStarts) {
|
|
|
848
892
|
...toolMetadata(event),
|
|
849
893
|
...langfuseObservationAttributes({
|
|
850
894
|
output: event.error ? { error: event.error } : event.details,
|
|
851
|
-
level: event.error ?
|
|
895
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
852
896
|
}),
|
|
853
897
|
},
|
|
854
898
|
};
|
|
855
|
-
if (event.status ===
|
|
899
|
+
if (event.status === 'started') {
|
|
856
900
|
pendingStarts.set(key, {
|
|
857
901
|
...fallbackStart,
|
|
858
902
|
attributes: {
|
|
859
903
|
...fallbackStart.attributes,
|
|
860
904
|
...(event.args ? { args: event.args } : {}),
|
|
861
|
-
...langfuseObservationAttributes({ input: event.args, level:
|
|
905
|
+
...langfuseObservationAttributes({ input: event.args, level: 'DEFAULT' }),
|
|
862
906
|
},
|
|
863
907
|
});
|
|
864
908
|
return [];
|
|
@@ -876,23 +920,23 @@ function mapLlmGenerationEventToOtelSpans(event, pendingStarts) {
|
|
|
876
920
|
startTime: event.createdAt,
|
|
877
921
|
attributes: {
|
|
878
922
|
...llmGenerationMetadata(event),
|
|
879
|
-
|
|
923
|
+
'langfuse.observation.type': 'generation',
|
|
880
924
|
model: event.model.model,
|
|
881
925
|
provider: event.model.provider,
|
|
882
926
|
...(event.model.thinkingLevel ? { thinkingLevel: event.model.thinkingLevel } : {}),
|
|
883
927
|
...langfuseObservationAttributes({
|
|
884
928
|
output: event.output ?? (event.error ? { error: event.error } : undefined),
|
|
885
|
-
level: event.error ?
|
|
929
|
+
level: event.error ? 'ERROR' : 'DEFAULT',
|
|
886
930
|
}),
|
|
887
931
|
...(event.usage ? flattenUsageAttributes(event.usage) : {}),
|
|
888
932
|
},
|
|
889
933
|
};
|
|
890
|
-
if (event.status ===
|
|
934
|
+
if (event.status === 'started') {
|
|
891
935
|
pendingStarts.set(key, {
|
|
892
936
|
...fallbackStart,
|
|
893
937
|
attributes: {
|
|
894
938
|
...fallbackStart.attributes,
|
|
895
|
-
...langfuseObservationAttributes({ input: event.input, level:
|
|
939
|
+
...langfuseObservationAttributes({ input: event.input, level: 'DEFAULT' }),
|
|
896
940
|
},
|
|
897
941
|
});
|
|
898
942
|
return [];
|
|
@@ -914,33 +958,37 @@ function completeOtelSpan(pendingStarts, key, fallbackStart, endTime, error) {
|
|
|
914
958
|
}
|
|
915
959
|
function langfuseObservationAttributes(input) {
|
|
916
960
|
return {
|
|
917
|
-
|
|
961
|
+
'langfuse.observation.type': 'span',
|
|
918
962
|
...(input.input !== undefined
|
|
919
963
|
? {
|
|
920
|
-
|
|
921
|
-
|
|
964
|
+
'langfuse.observation.input': toLangfuseObservationPayload(input.input),
|
|
965
|
+
'input.value': toLangfuseObservationPayload(input.input),
|
|
922
966
|
}
|
|
923
967
|
: {}),
|
|
924
968
|
...(input.output !== undefined
|
|
925
969
|
? {
|
|
926
|
-
|
|
927
|
-
|
|
970
|
+
'langfuse.observation.output': toLangfuseObservationPayload(input.output),
|
|
971
|
+
'output.value': toLangfuseObservationPayload(input.output),
|
|
928
972
|
}
|
|
929
973
|
: {}),
|
|
930
|
-
...(input.level ? {
|
|
974
|
+
...(input.level ? { 'langfuse.observation.level': input.level } : {}),
|
|
931
975
|
};
|
|
932
976
|
}
|
|
933
977
|
function langfuseTraceAttributes(input) {
|
|
934
978
|
return {
|
|
935
|
-
...(input.input !== undefined
|
|
936
|
-
|
|
979
|
+
...(input.input !== undefined
|
|
980
|
+
? { 'langfuse.trace.input': toLangfuseTracePayload(input.input) }
|
|
981
|
+
: {}),
|
|
982
|
+
...(input.output !== undefined
|
|
983
|
+
? { 'langfuse.trace.output': toLangfuseTracePayload(input.output) }
|
|
984
|
+
: {}),
|
|
937
985
|
};
|
|
938
986
|
}
|
|
939
987
|
function toLangfuseObservationPayload(value) {
|
|
940
988
|
return JSON.stringify(value);
|
|
941
989
|
}
|
|
942
990
|
function toLangfuseTracePayload(value) {
|
|
943
|
-
return typeof value ===
|
|
991
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
944
992
|
}
|
|
945
993
|
function chatSpanKey(event) {
|
|
946
994
|
const sessionId = event.parentSessionId ?? event.sessionId;
|
|
@@ -954,7 +1002,7 @@ function subagentSpanKey(event) {
|
|
|
954
1002
|
return `subagent:${event.runId ?? event.childSessionId ?? event.id}`;
|
|
955
1003
|
}
|
|
956
1004
|
function subagentBatchSpanKey(event) {
|
|
957
|
-
const batchId = event.spawnBatchId ?? stringFromJsonObject(event.details,
|
|
1005
|
+
const batchId = event.spawnBatchId ?? stringFromJsonObject(event.details, 'spawnBatchId');
|
|
958
1006
|
return event.traceId && batchId ? `subagent-batch:${event.traceId}:${batchId}` : undefined;
|
|
959
1007
|
}
|
|
960
1008
|
function telemetryEventSubagentSpanKey(event) {
|
|
@@ -983,23 +1031,25 @@ function toolObservationName(event) {
|
|
|
983
1031
|
}
|
|
984
1032
|
function chatInputObservationName(event) {
|
|
985
1033
|
const prefix = chatInputObservationPrefix(event.type);
|
|
986
|
-
const input = typeof event.details?.input ===
|
|
1034
|
+
const input = typeof event.details?.input === 'string'
|
|
1035
|
+
? truncateObservationSummary(event.details.input)
|
|
1036
|
+
: undefined;
|
|
987
1037
|
return input ? `${prefix} [${input}]` : prefix;
|
|
988
1038
|
}
|
|
989
1039
|
function chatInputObservationPrefix(type) {
|
|
990
|
-
if (type ===
|
|
991
|
-
return
|
|
1040
|
+
if (type === 'chat_turn_steered') {
|
|
1041
|
+
return 'chat-steer';
|
|
992
1042
|
}
|
|
993
|
-
if (type ===
|
|
994
|
-
return
|
|
1043
|
+
if (type === 'chat_turn_steer_delivered') {
|
|
1044
|
+
return 'chat-steer-delivered';
|
|
995
1045
|
}
|
|
996
|
-
if (type ===
|
|
997
|
-
return
|
|
1046
|
+
if (type === 'chat_turn_followup_delivered') {
|
|
1047
|
+
return 'chat-followup-delivered';
|
|
998
1048
|
}
|
|
999
|
-
return
|
|
1049
|
+
return 'chat-followup';
|
|
1000
1050
|
}
|
|
1001
1051
|
function chatInputLifecycleOutput(event) {
|
|
1002
|
-
return event.type ===
|
|
1052
|
+
return event.type === 'chat_turn_steer_delivered' || event.type === 'chat_turn_followup_delivered'
|
|
1003
1053
|
? { delivered: true, turnMode: event.details?.turnMode }
|
|
1004
1054
|
: { accepted: true, turnMode: event.details?.turnMode };
|
|
1005
1055
|
}
|
|
@@ -1007,59 +1057,59 @@ function summarizeToolArgsForName(toolName, args) {
|
|
|
1007
1057
|
if (!args) {
|
|
1008
1058
|
return undefined;
|
|
1009
1059
|
}
|
|
1010
|
-
const pathValue = stringArg(args,
|
|
1060
|
+
const pathValue = stringArg(args, 'path') ?? stringArg(args, 'filePath') ?? stringArg(args, 'absolutePath');
|
|
1011
1061
|
if (pathValue) {
|
|
1012
1062
|
return truncateObservationSummary(pathValue);
|
|
1013
1063
|
}
|
|
1014
|
-
const command = stringArg(args,
|
|
1064
|
+
const command = stringArg(args, 'command');
|
|
1015
1065
|
if (command) {
|
|
1016
1066
|
return truncateObservationSummary(command);
|
|
1017
1067
|
}
|
|
1018
|
-
const query = stringArg(args,
|
|
1068
|
+
const query = stringArg(args, 'query');
|
|
1019
1069
|
if (query) {
|
|
1020
1070
|
return truncateObservationSummary(query);
|
|
1021
1071
|
}
|
|
1022
|
-
const task = stringArg(args,
|
|
1023
|
-
if (task && toolName ===
|
|
1072
|
+
const task = stringArg(args, 'task');
|
|
1073
|
+
if (task && toolName === 'sessions_spawn') {
|
|
1024
1074
|
return truncateObservationSummary(task);
|
|
1025
1075
|
}
|
|
1026
|
-
const code = stringArg(args,
|
|
1076
|
+
const code = stringArg(args, 'code');
|
|
1027
1077
|
if (code) {
|
|
1028
1078
|
return truncateObservationSummary(code);
|
|
1029
1079
|
}
|
|
1030
|
-
const name = stringArg(args,
|
|
1031
|
-
if (name && toolName.startsWith(
|
|
1080
|
+
const name = stringArg(args, 'name');
|
|
1081
|
+
if (name && toolName.startsWith('mcp_')) {
|
|
1032
1082
|
return truncateObservationSummary(name);
|
|
1033
1083
|
}
|
|
1034
1084
|
return undefined;
|
|
1035
1085
|
}
|
|
1036
1086
|
function llmGenerationObservationName(event) {
|
|
1037
|
-
return `llm-generation [${event.runId || event.childSessionId ?
|
|
1087
|
+
return `llm-generation [${event.runId || event.childSessionId ? 'subagent' : 'main'}] [${summarizeLlmGenerationInputForName(event.input)}]`;
|
|
1038
1088
|
}
|
|
1039
1089
|
function summarizeLlmGenerationInputForName(input) {
|
|
1040
|
-
if (typeof input ===
|
|
1090
|
+
if (typeof input === 'string') {
|
|
1041
1091
|
return truncateObservationSummary(input);
|
|
1042
1092
|
}
|
|
1043
|
-
if (input && typeof input ===
|
|
1093
|
+
if (input && typeof input === 'object' && !Array.isArray(input)) {
|
|
1044
1094
|
const continuation = input.continuation === true;
|
|
1045
|
-
const index = typeof input.llmGenerationIndex ===
|
|
1046
|
-
const toolResults = typeof input.previousToolResultCount ===
|
|
1095
|
+
const index = typeof input.llmGenerationIndex === 'number' ? input.llmGenerationIndex : undefined;
|
|
1096
|
+
const toolResults = typeof input.previousToolResultCount === 'number' ? input.previousToolResultCount : undefined;
|
|
1047
1097
|
if (continuation) {
|
|
1048
|
-
return truncateObservationSummary(`continuation${index !== undefined ? ` #${index}` :
|
|
1098
|
+
return truncateObservationSummary(`continuation${index !== undefined ? ` #${index}` : ''}${toolResults !== undefined ? ` after ${toolResults} tool result(s)` : ''}`);
|
|
1049
1099
|
}
|
|
1050
1100
|
}
|
|
1051
|
-
return
|
|
1101
|
+
return 'request';
|
|
1052
1102
|
}
|
|
1053
1103
|
function stringArg(args, key) {
|
|
1054
1104
|
const value = args[key];
|
|
1055
|
-
return typeof value ===
|
|
1105
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
1056
1106
|
}
|
|
1057
1107
|
function stringFromJsonObject(value, key) {
|
|
1058
1108
|
const field = value?.[key];
|
|
1059
|
-
return typeof field ===
|
|
1109
|
+
return typeof field === 'string' && field.trim() ? field.trim() : undefined;
|
|
1060
1110
|
}
|
|
1061
1111
|
function truncateObservationSummary(value, maxLength = 90) {
|
|
1062
|
-
const normalized = value.replace(/\s+/g,
|
|
1112
|
+
const normalized = value.replace(/\s+/g, ' ').trim();
|
|
1063
1113
|
return normalized.length > maxLength ? `${normalized.slice(0, maxLength - 1)}…` : normalized;
|
|
1064
1114
|
}
|
|
1065
1115
|
function ensureEndAfterStart(startTime, endTime) {
|
|
@@ -1071,14 +1121,16 @@ function toOtelTracePayload(spans, config) {
|
|
|
1071
1121
|
{
|
|
1072
1122
|
resource: {
|
|
1073
1123
|
attributes: [
|
|
1074
|
-
otelAttribute(
|
|
1075
|
-
...(config.serviceVersion
|
|
1076
|
-
|
|
1124
|
+
otelAttribute('service.name', config.serviceName ?? 'pi-server'),
|
|
1125
|
+
...(config.serviceVersion
|
|
1126
|
+
? [otelAttribute('service.version', config.serviceVersion)]
|
|
1127
|
+
: []),
|
|
1128
|
+
otelAttribute('telemetry.sdk.name', '@amaster.ai/pi-telemetry'),
|
|
1077
1129
|
],
|
|
1078
1130
|
},
|
|
1079
1131
|
scopeSpans: [
|
|
1080
1132
|
{
|
|
1081
|
-
scope: { name:
|
|
1133
|
+
scope: { name: '@amaster.ai/pi-telemetry', version: '0.1.0' },
|
|
1082
1134
|
spans: spans.map(toOtelSpanPayload),
|
|
1083
1135
|
},
|
|
1084
1136
|
],
|
|
@@ -1105,13 +1157,13 @@ function otelAttribute(key, value) {
|
|
|
1105
1157
|
return { key, value: otelAttributeValue(value) };
|
|
1106
1158
|
}
|
|
1107
1159
|
function otelAttributeValue(value) {
|
|
1108
|
-
if (typeof value ===
|
|
1160
|
+
if (typeof value === 'boolean') {
|
|
1109
1161
|
return { boolValue: value };
|
|
1110
1162
|
}
|
|
1111
|
-
if (typeof value ===
|
|
1163
|
+
if (typeof value === 'number') {
|
|
1112
1164
|
return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
|
|
1113
1165
|
}
|
|
1114
|
-
if (typeof value ===
|
|
1166
|
+
if (typeof value === 'string') {
|
|
1115
1167
|
return { stringValue: value };
|
|
1116
1168
|
}
|
|
1117
1169
|
return { stringValue: JSON.stringify(value) };
|
|
@@ -1121,13 +1173,13 @@ function toUnixNano(iso) {
|
|
|
1121
1173
|
return String(BigInt(Number.isFinite(millis) ? millis : Date.now()) * 1000000n);
|
|
1122
1174
|
}
|
|
1123
1175
|
function normalizeOtelTracesEndpoint(endpoint) {
|
|
1124
|
-
return endpoint.endsWith(
|
|
1176
|
+
return endpoint.endsWith('/v1/traces') ? endpoint : `${endpoint.replace(/\/+$/, '')}/v1/traces`;
|
|
1125
1177
|
}
|
|
1126
1178
|
function shortCorrelationId(value) {
|
|
1127
1179
|
if (!value) {
|
|
1128
1180
|
return undefined;
|
|
1129
1181
|
}
|
|
1130
|
-
const normalized = value.startsWith(
|
|
1182
|
+
const normalized = value.startsWith('trace:') ? value.slice('trace:'.length) : value;
|
|
1131
1183
|
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
1184
|
if (uuid) {
|
|
1133
1185
|
return uuid.slice(0, 8);
|
|
@@ -1141,11 +1193,14 @@ function compactSessionId(value) {
|
|
|
1141
1193
|
if (!value) {
|
|
1142
1194
|
return undefined;
|
|
1143
1195
|
}
|
|
1144
|
-
const [root, ...subagents] = value.split(
|
|
1196
|
+
const [root, ...subagents] = value.split(':subagent:');
|
|
1145
1197
|
if (subagents.length === 0) {
|
|
1146
1198
|
return value;
|
|
1147
1199
|
}
|
|
1148
|
-
return [
|
|
1200
|
+
return [
|
|
1201
|
+
root,
|
|
1202
|
+
...subagents.map((sessionId) => `sub:${shortCorrelationId(sessionId) ?? sessionId}`),
|
|
1203
|
+
].join('/');
|
|
1149
1204
|
}
|
|
1150
1205
|
function lineageMetadata(event) {
|
|
1151
1206
|
const sessionId = compactSessionId(event.sessionId);
|
|
@@ -1202,7 +1257,7 @@ function toLangfuseUsage(usage) {
|
|
|
1202
1257
|
...(usage.input !== undefined ? { input: usage.input } : {}),
|
|
1203
1258
|
...(usage.output !== undefined ? { output: usage.output } : {}),
|
|
1204
1259
|
...(usage.totalTokens !== undefined ? { total: usage.totalTokens } : {}),
|
|
1205
|
-
unit:
|
|
1260
|
+
unit: 'TOKENS',
|
|
1206
1261
|
...(usage.cost?.input !== undefined ? { inputCost: usage.cost.input } : {}),
|
|
1207
1262
|
...(usage.cost?.output !== undefined ? { outputCost: usage.cost.output } : {}),
|
|
1208
1263
|
...(usage.cost?.total !== undefined ? { totalCost: usage.cost.total } : {}),
|
|
@@ -1219,12 +1274,12 @@ function toLangfuseUsageDetails(usage) {
|
|
|
1219
1274
|
}
|
|
1220
1275
|
function flattenUsageAttributes(usage) {
|
|
1221
1276
|
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 ? {
|
|
1277
|
+
...(usage.input !== undefined ? { 'usage.input': usage.input } : {}),
|
|
1278
|
+
...(usage.output !== undefined ? { 'usage.output': usage.output } : {}),
|
|
1279
|
+
...(usage.cacheRead !== undefined ? { 'usage.cache_read': usage.cacheRead } : {}),
|
|
1280
|
+
...(usage.cacheWrite !== undefined ? { 'usage.cache_write': usage.cacheWrite } : {}),
|
|
1281
|
+
...(usage.totalTokens !== undefined ? { 'usage.total_tokens': usage.totalTokens } : {}),
|
|
1282
|
+
...(usage.cost?.total !== undefined ? { 'usage.cost.total': usage.cost.total } : {}),
|
|
1228
1283
|
};
|
|
1229
1284
|
}
|
|
1230
1285
|
function ingestionEvent(type, timestamp, body) {
|
|
@@ -1242,7 +1297,7 @@ function langfuseSpanId(traceId, key) {
|
|
|
1242
1297
|
return stableHex(`span:${traceId}:${key}`, 16);
|
|
1243
1298
|
}
|
|
1244
1299
|
function stableHex(input, length) {
|
|
1245
|
-
return createHash(
|
|
1300
|
+
return createHash('sha256').update(input).digest('hex').slice(0, length);
|
|
1246
1301
|
}
|
|
1247
1302
|
function applyTelemetryRedaction(config, event) {
|
|
1248
1303
|
const redacted = config.redactEvent ? config.redactEvent(event) : event;
|
|
@@ -1274,13 +1329,13 @@ function redactJsonObjectPayload(input) {
|
|
|
1274
1329
|
return rest;
|
|
1275
1330
|
}
|
|
1276
1331
|
function isToolEvent(event) {
|
|
1277
|
-
return
|
|
1332
|
+
return 'toolCallId' in event;
|
|
1278
1333
|
}
|
|
1279
1334
|
function isLlmGenerationEvent(event) {
|
|
1280
|
-
return
|
|
1335
|
+
return 'llmGenerationId' in event;
|
|
1281
1336
|
}
|
|
1282
1337
|
function parseBoolean(value) {
|
|
1283
|
-
return value ===
|
|
1338
|
+
return value === '1' || value === 'true' || value === 'TRUE' || value === 'yes';
|
|
1284
1339
|
}
|
|
1285
1340
|
function parseBooleanWithDefault(value, fallback) {
|
|
1286
1341
|
if (value === undefined) {
|
|
@@ -1290,7 +1345,7 @@ function parseBooleanWithDefault(value, fallback) {
|
|
|
1290
1345
|
}
|
|
1291
1346
|
function parseLangfuseTransport(value) {
|
|
1292
1347
|
const normalized = value?.trim().toLowerCase();
|
|
1293
|
-
return normalized ===
|
|
1348
|
+
return normalized === 'sdk' || normalized === 'ingestion' ? normalized : undefined;
|
|
1294
1349
|
}
|
|
1295
1350
|
function parsePositiveInteger(value, fallback) {
|
|
1296
1351
|
const parsed = Number(value);
|
|
@@ -1305,7 +1360,7 @@ function assertNever(value) {
|
|
|
1305
1360
|
}
|
|
1306
1361
|
function requireTraceId(traceId) {
|
|
1307
1362
|
if (!traceId) {
|
|
1308
|
-
throw new Error(
|
|
1363
|
+
throw new Error('Telemetry event is missing traceId');
|
|
1309
1364
|
}
|
|
1310
1365
|
return traceId;
|
|
1311
1366
|
}
|