@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/dist/langfuse.js CHANGED
@@ -1,7 +1,7 @@
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";
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 "chat_turn_started": {
53
+ case 'chat_turn_started': {
54
54
  const body = {
55
55
  id: rootSpanId,
56
- name: "copilot-chat-turn",
56
+ name: 'chat-turn',
57
57
  startTime: event.createdAt,
58
58
  input: event.details?.input,
59
- level: "DEFAULT",
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: "DEFAULT",
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 "chat_turn_completed":
76
- case "chat_turn_failed": {
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: "copilot-chat-turn",
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: "copilot-chat-turn",
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 ? "ERROR" : "DEFAULT",
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 "chat_turn_steered":
103
- case "chat_turn_steer_delivered":
104
- case "chat_turn_followup_queued":
105
- case "chat_turn_followup_delivered": {
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: "DEFAULT",
113
+ level: 'DEFAULT',
115
114
  metadata: lifecycleMetadata(event),
116
115
  });
117
116
  break;
118
117
  }
119
- case "subagent_spawned":
120
- case "subagent_started": {
118
+ case 'subagent_spawned':
119
+ case 'subagent_started': {
121
120
  const key = subagentSpanKey(event);
122
- const rootSpan = this.ensureSdkRootSpan(trace, rootKey, event);
121
+ this.ensureSdkRootSpan(trace, rootKey, event);
123
122
  const body = {
124
123
  id: langfuseSpanId(traceId, key),
125
- name: "subagent",
124
+ name: 'subagent',
126
125
  startTime: event.createdAt,
127
126
  input: event.details?.input,
128
- level: "DEFAULT",
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: "DEFAULT",
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 "subagent_completed":
145
- case "subagent_failed":
146
- case "subagent_cancelled": {
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
- const rootSpan = this.ensureSdkRootSpan(trace, rootKey, event);
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: "subagent",
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 ? "ERROR" : "DEFAULT",
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 === "started") {
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: "DEFAULT",
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: "DEFAULT",
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 ? "ERROR" : "DEFAULT",
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 === "started") {
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 ? "ERROR" : "DEFAULT",
258
+ level: event.error ? 'ERROR' : 'DEFAULT',
261
259
  ...(event.error ? { statusMessage: event.error } : {}),
262
- ...(event.usage ? { usage: toLangfuseUsage(event.usage), usageDetails: toLangfuseUsageDetails(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: "copilot-chat-turn",
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: "subagent fan-out",
313
+ name: 'subagent fan-out',
314
314
  startTime: event.createdAt,
315
315
  input: event.details?.input,
316
- level: "DEFAULT",
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 ? "ERROR" : "DEFAULT",
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: "copilot-chat-turn",
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 = fetchImpl ?? (async (input, init) => {
461
- const response = await fetch(input, init);
462
- return {
463
- ok: response.ok,
464
- status: response.status,
465
- text: () => response.text(),
466
- };
467
- });
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: "POST",
436
+ method: 'POST',
525
437
  headers: {
526
- "content-type": "application/json",
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 ?? "OTEL export"} failed with ${response.status}${text ? `: ${text}` : ""}`);
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
- if (config.transport === "sdk") {
543
- return new LangfuseSdkRuntimeEventExporter(config);
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 LangfuseHttpRuntimeEventExporter(config);
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
- transport,
559
- publicKey: publicKey ?? "",
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) ?? "pi-server",
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 "chat_turn_started":
515
+ case 'chat_turn_started':
743
516
  pendingStarts.set(rootKey, {
744
517
  traceId: langfuseTraceId(traceId),
745
518
  spanId: rootSpanId,
746
- name: "copilot-chat-turn",
519
+ name: 'chat-turn',
747
520
  startTime: event.createdAt,
748
521
  attributes: {
749
522
  ...lifecycleMetadata(event),
750
- ...langfuseObservationAttributes({ input: event.details?.input, level: "DEFAULT" }),
523
+ ...langfuseObservationAttributes({ input: event.details?.input, level: 'DEFAULT' }),
751
524
  ...langfuseTraceAttributes({ input: event.details?.input }),
752
525
  },
753
526
  });
754
527
  return [];
755
- case "chat_turn_completed":
756
- case "chat_turn_failed":
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: "copilot-chat-turn",
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 ? "ERROR" : "DEFAULT",
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 "chat_turn_steered":
776
- case "chat_turn_steer_delivered":
777
- case "chat_turn_followup_queued":
778
- case "chat_turn_followup_delivered":
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: "DEFAULT",
565
+ level: 'DEFAULT',
793
566
  }),
794
567
  },
795
568
  },
796
569
  ];
797
- case "subagent_spawned":
798
- case "subagent_started": {
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: "subagent",
577
+ name: 'subagent',
805
578
  startTime: event.createdAt,
806
579
  attributes: {
807
580
  ...lifecycleMetadata(event),
808
- ...langfuseObservationAttributes({ input: event.details?.input, level: "DEFAULT" }),
581
+ ...langfuseObservationAttributes({ input: event.details?.input, level: 'DEFAULT' }),
809
582
  },
810
583
  });
811
584
  return [];
812
585
  }
813
- case "subagent_completed":
814
- case "subagent_failed":
815
- case "subagent_cancelled": {
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: "subagent",
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 ? "ERROR" : "DEFAULT",
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 ? "ERROR" : "DEFAULT",
624
+ level: event.error ? 'ERROR' : 'DEFAULT',
852
625
  }),
853
626
  },
854
627
  };
855
- if (event.status === "started") {
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: "DEFAULT" }),
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
- "langfuse.observation.type": "generation",
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 ? "ERROR" : "DEFAULT",
658
+ level: event.error ? 'ERROR' : 'DEFAULT',
886
659
  }),
887
660
  ...(event.usage ? flattenUsageAttributes(event.usage) : {}),
888
661
  },
889
662
  };
890
- if (event.status === "started") {
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: "DEFAULT" }),
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
- "langfuse.observation.type": "span",
690
+ 'langfuse.observation.type': 'span',
918
691
  ...(input.input !== undefined
919
692
  ? {
920
- "langfuse.observation.input": toLangfuseObservationPayload(input.input),
921
- "input.value": toLangfuseObservationPayload(input.input),
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
- "langfuse.observation.output": toLangfuseObservationPayload(input.output),
927
- "output.value": toLangfuseObservationPayload(input.output),
699
+ 'langfuse.observation.output': toLangfuseObservationPayload(input.output),
700
+ 'output.value': toLangfuseObservationPayload(input.output),
928
701
  }
929
702
  : {}),
930
- ...(input.level ? { "langfuse.observation.level": 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 ? { "langfuse.trace.input": toLangfuseTracePayload(input.input) } : {}),
936
- ...(input.output !== undefined ? { "langfuse.trace.output": toLangfuseTracePayload(input.output) } : {}),
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 === "string" ? value : JSON.stringify(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, "spawnBatchId");
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 === "string" ? truncateObservationSummary(event.details.input) : undefined;
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 === "chat_turn_steered") {
991
- return "chat-steer";
769
+ if (type === 'chat_turn_steered') {
770
+ return 'chat-steer';
992
771
  }
993
- if (type === "chat_turn_steer_delivered") {
994
- return "chat-steer-delivered";
772
+ if (type === 'chat_turn_steer_delivered') {
773
+ return 'chat-steer-delivered';
995
774
  }
996
- if (type === "chat_turn_followup_delivered") {
997
- return "chat-followup-delivered";
775
+ if (type === 'chat_turn_followup_delivered') {
776
+ return 'chat-followup-delivered';
998
777
  }
999
- return "chat-followup";
778
+ return 'chat-followup';
1000
779
  }
1001
780
  function chatInputLifecycleOutput(event) {
1002
- return event.type === "chat_turn_steer_delivered" || event.type === "chat_turn_followup_delivered"
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, "path") ?? stringArg(args, "filePath") ?? stringArg(args, "absolutePath");
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, "command");
793
+ const command = stringArg(args, 'command');
1015
794
  if (command) {
1016
795
  return truncateObservationSummary(command);
1017
796
  }
1018
- const query = stringArg(args, "query");
797
+ const query = stringArg(args, 'query');
1019
798
  if (query) {
1020
799
  return truncateObservationSummary(query);
1021
800
  }
1022
- const task = stringArg(args, "task");
1023
- if (task && toolName === "sessions_spawn") {
801
+ const task = stringArg(args, 'task');
802
+ if (task && toolName === 'sessions_spawn') {
1024
803
  return truncateObservationSummary(task);
1025
804
  }
1026
- const code = stringArg(args, "code");
805
+ const code = stringArg(args, 'code');
1027
806
  if (code) {
1028
807
  return truncateObservationSummary(code);
1029
808
  }
1030
- const name = stringArg(args, "name");
1031
- if (name && toolName.startsWith("mcp_")) {
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 ? "subagent" : "main"}] [${summarizeLlmGenerationInputForName(event.input)}]`;
816
+ return `llm-generation [${event.runId || event.childSessionId ? 'subagent' : 'main'}] [${summarizeLlmGenerationInputForName(event.input)}]`;
1038
817
  }
1039
818
  function summarizeLlmGenerationInputForName(input) {
1040
- if (typeof input === "string") {
819
+ if (typeof input === 'string') {
1041
820
  return truncateObservationSummary(input);
1042
821
  }
1043
- if (input && typeof input === "object" && !Array.isArray(input)) {
822
+ if (input && typeof input === 'object' && !Array.isArray(input)) {
1044
823
  const continuation = input.continuation === true;
1045
- const index = typeof input.llmGenerationIndex === "number" ? input.llmGenerationIndex : undefined;
1046
- const toolResults = typeof input.previousToolResultCount === "number" ? input.previousToolResultCount : undefined;
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}` : ""}${toolResults !== undefined ? ` after ${toolResults} tool result(s)` : ""}`);
827
+ return truncateObservationSummary(`continuation${index !== undefined ? ` #${index}` : ''}${toolResults !== undefined ? ` after ${toolResults} tool result(s)` : ''}`);
1049
828
  }
1050
829
  }
1051
- return "request";
830
+ return 'request';
1052
831
  }
1053
832
  function stringArg(args, key) {
1054
833
  const value = args[key];
1055
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
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 === "string" && field.trim() ? field.trim() : undefined;
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, " ").trim();
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("service.name", config.serviceName ?? "pi-server"),
1075
- ...(config.serviceVersion ? [otelAttribute("service.version", config.serviceVersion)] : []),
1076
- otelAttribute("telemetry.sdk.name", "@amaster.ai/pi-telemetry"),
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: "@amaster.ai/pi-telemetry", version: "0.1.0" },
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 === "boolean") {
889
+ if (typeof value === 'boolean') {
1109
890
  return { boolValue: value };
1110
891
  }
1111
- if (typeof value === "number") {
892
+ if (typeof value === 'number') {
1112
893
  return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
1113
894
  }
1114
- if (typeof value === "string") {
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("/v1/traces") ? endpoint : `${endpoint.replace(/\/+$/, "")}/v1/traces`;
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("trace:") ? value.slice("trace:".length) : value;
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(":subagent:");
925
+ const [root, ...subagents] = value.split(':subagent:');
1145
926
  if (subagents.length === 0) {
1146
927
  return value;
1147
928
  }
1148
- return [root, ...subagents.map((sessionId) => `sub:${shortCorrelationId(sessionId) ?? sessionId}`)].join("/");
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: "TOKENS",
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 ? { "usage.input": usage.input } : {}),
1223
- ...(usage.output !== undefined ? { "usage.output": usage.output } : {}),
1224
- ...(usage.cacheRead !== undefined ? { "usage.cache_read": usage.cacheRead } : {}),
1225
- ...(usage.cacheWrite !== undefined ? { "usage.cache_write": usage.cacheWrite } : {}),
1226
- ...(usage.totalTokens !== undefined ? { "usage.total_tokens": usage.totalTokens } : {}),
1227
- ...(usage.cost?.total !== undefined ? { "usage.cost.total": usage.cost.total } : {}),
1228
- };
1229
- }
1230
- function ingestionEvent(type, timestamp, body) {
1231
- return {
1232
- id: randomUUID(),
1233
- timestamp,
1234
- type,
1235
- body,
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("sha256").update(input).digest("hex").slice(0, length);
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 "toolCallId" in event;
1053
+ return 'toolCallId' in event;
1278
1054
  }
1279
1055
  function isLlmGenerationEvent(event) {
1280
- return "llmGenerationId" in event;
1056
+ return 'llmGenerationId' in event;
1281
1057
  }
1282
1058
  function parseBoolean(value) {
1283
- return value === "1" || value === "true" || value === "TRUE" || value === "yes";
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("Telemetry event is missing traceId");
1080
+ throw new Error('Telemetry event is missing traceId');
1309
1081
  }
1310
1082
  return traceId;
1311
1083
  }