@amaster.ai/pi-telemetry 0.1.9 → 0.1.11

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