@anvia/langfuse 1.0.0-rc.1 → 1.0.0-rc.3

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/index.js CHANGED
@@ -1,116 +1,155 @@
1
- // src/helpers.ts
2
- function modelInputMessage(message) {
3
- const { metadata: _metadata, ...result } = message;
4
- return result;
5
- }
6
- function modelInputMessages(messages) {
7
- return messages.map(modelInputMessage);
8
- }
9
- function modelParameters(request) {
10
- const params = {};
11
- if (request.temperature !== void 0) params.temperature = request.temperature;
12
- if (request.maxTokens !== void 0) params.maxTokens = request.maxTokens;
13
- if (request.toolChoice !== void 0) {
14
- params.toolChoice = typeof request.toolChoice === "string" ? request.toolChoice : request.toolChoice.name;
1
+ // src/redaction.ts
2
+ var DEFAULT_REPLACEMENT = "[REDACTED]";
3
+ var MAX_DEPTH = 16;
4
+ function createPiiRedactor(options = {}) {
5
+ const patterns = options.patterns ?? DEFAULT_PATTERNS;
6
+ const replacement = options.replacement ?? DEFAULT_REPLACEMENT;
7
+ const compiled = patterns.map((p) => ({
8
+ name: p.name,
9
+ regex: cloneRegex(p.regex, "g")
10
+ }));
11
+ const patternNamesList = patterns.map((p) => p.name);
12
+ function redactString(input) {
13
+ if (typeof input !== "string") return input;
14
+ let out = input;
15
+ for (const { name, regex } of compiled) {
16
+ out = applyPattern(out, name, regex, replacement);
17
+ }
18
+ return out;
15
19
  }
16
- return params;
20
+ function redactObject(input) {
21
+ return redactValue(input, 0, redactString);
22
+ }
23
+ function redactMessages(input) {
24
+ return input.map((message) => redactMessage(message, redactString));
25
+ }
26
+ function patternNames() {
27
+ return patternNamesList;
28
+ }
29
+ return { redactString, redactObject, redactMessages, patternNames };
17
30
  }
18
- function usageDetails(usage) {
19
- if (usage.details !== void 0 && Object.keys(usage.details).length > 0) {
20
- return { ...usage.details };
31
+ var DEFAULT_PATTERNS = [
32
+ { name: "email", regex: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g },
33
+ { name: "creditCard", regex: /\b(?:\d[ -]?){13,19}\b/g },
34
+ { name: "ipv4", regex: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g },
35
+ {
36
+ name: "phone",
37
+ regex: /(?<!\d)(?:\+\d{1,3}[\s.-]?)?(?:\(\d{2,4}\)[\s.-]?)?\d{3,4}[\s.-]?\d{3,4}(?:[\s.-]?\d{3,4})?(?!\d)/g
38
+ },
39
+ { name: "jwt", regex: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g },
40
+ {
41
+ name: "apiKey",
42
+ regex: /\b(?:sk|pk|api|key|token)[-_][A-Za-z0-9]{16,}\b/gi
21
43
  }
22
- return {
23
- input: usage.inputTokens,
24
- output: usage.outputTokens,
25
- total: usage.totalTokens
26
- };
44
+ ];
45
+ function applyPattern(input, name, regex, replacement) {
46
+ if (name === "creditCard") {
47
+ return redactCreditCards(input, replacement);
48
+ }
49
+ return input.replace(regex, replacement);
27
50
  }
28
- function usageDetailsFromRecord(usage) {
29
- if (isRecord(usage.details)) {
30
- const details = Object.fromEntries(
31
- Object.entries(usage.details).filter(
32
- (entry) => typeof entry[1] === "number" && Number.isFinite(entry[1]) && entry[1] >= 0
33
- )
34
- );
35
- if (Object.keys(details).length > 0) {
36
- return details;
51
+ function redactCreditCards(input, replacement) {
52
+ let out = "";
53
+ let i = 0;
54
+ while (i < input.length) {
55
+ const ch = input.charAt(i);
56
+ if (/\d/.test(ch)) {
57
+ const { length, valid } = longestLuhnChunk(input.slice(i));
58
+ if (valid) {
59
+ out += replacement;
60
+ i += length;
61
+ continue;
62
+ }
37
63
  }
64
+ out += ch;
65
+ i += 1;
38
66
  }
39
- return {
40
- input: numberValue(usage.inputTokens) ?? 0,
41
- output: numberValue(usage.outputTokens) ?? 0,
42
- total: numberValue(usage.totalTokens) ?? (numberValue(usage.inputTokens) ?? 0) + (numberValue(usage.outputTokens) ?? 0)
43
- };
44
- }
45
- function childMetadata(args, agentId, agentName, childTurn) {
46
- return {
47
- source: "agent_tool_event",
48
- childAgentId: agentId,
49
- childAgentName: agentName,
50
- childTurn,
51
- parentToolName: args.toolName,
52
- parentInternalCallId: args.internalCallId,
53
- parentToolCallId: args.toolCallId
54
- };
55
- }
56
- function generationKey(agentId, turn) {
57
- return `${agentId}:${turn}`;
67
+ return out;
58
68
  }
59
- function agentLabel(agentId, agentName) {
60
- return (agentName ?? agentId).replaceAll(/\s+/g, "_");
69
+ function longestLuhnChunk(s) {
70
+ let length = 0;
71
+ let bestValid = 0;
72
+ while (length < s.length && length < 40) {
73
+ const ch = s.charAt(length);
74
+ if (!/\d/.test(ch) && ch !== "-") break;
75
+ length += 1;
76
+ const candidate = s.slice(0, length).replace(/\D/g, "");
77
+ if (candidate.length >= 13 && candidate.length <= 19) {
78
+ if (startsWithKnownPrefix(candidate) && passesLuhn(candidate)) {
79
+ bestValid = length;
80
+ }
81
+ }
82
+ }
83
+ return { length: bestValid, valid: bestValid > 0 };
61
84
  }
62
- function isRecord(value) {
63
- return typeof value === "object" && value !== null && !Array.isArray(value);
85
+ function startsWithKnownPrefix(digits) {
86
+ if (digits.startsWith("4")) return true;
87
+ const two = digits.slice(0, 2);
88
+ if (two === "51" || two === "52" || two === "53" || two === "54" || two === "55") return true;
89
+ const four = digits.slice(0, 4);
90
+ if (four === "2221" || four === "2720") return true;
91
+ const twoAgain = digits.slice(0, 2);
92
+ if (twoAgain === "34" || twoAgain === "37") return true;
93
+ if (four === "6011" || twoAgain === "65") return true;
94
+ return digits.startsWith("35");
64
95
  }
65
- function numberValue(value) {
66
- return typeof value === "number" ? value : void 0;
96
+ function passesLuhn(digits) {
97
+ if (!/^\d+$/.test(digits)) return false;
98
+ let sum = 0;
99
+ let alt = false;
100
+ for (let i = digits.length - 1; i >= 0; i -= 1) {
101
+ const raw = digits.charCodeAt(i) - 48;
102
+ let value = raw;
103
+ if (alt) {
104
+ value *= 2;
105
+ if (value > 9) value -= 9;
106
+ }
107
+ sum += value;
108
+ alt = !alt;
109
+ }
110
+ return sum % 10 === 0;
67
111
  }
68
- function emptyToUndefined(value) {
69
- return value === void 0 || value.length === 0 ? void 0 : value;
112
+ function cloneRegex(source, flags) {
113
+ return new RegExp(source.source, flags + source.flags.replace(/g/g, ""));
70
114
  }
71
- function errorMessage(error) {
72
- return error instanceof Error ? error.message : String(error);
115
+ function redactValue(value, depth, redactStringFn) {
116
+ if (depth > MAX_DEPTH) return value;
117
+ if (typeof value === "string") {
118
+ return isBase64DataUrl(value) ? value : redactStringFn(value);
119
+ }
120
+ if (Array.isArray(value))
121
+ return value.map((entry) => redactValue(entry, depth + 1, redactStringFn));
122
+ if (value !== null && typeof value === "object") {
123
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
124
+ return value;
125
+ }
126
+ const record = value;
127
+ const out = {};
128
+ for (const [key, entry] of Object.entries(record)) {
129
+ if (key === "data" && typeof entry === "string" && (record.type === "base64" || record.type === "image" || record.type === "encrypted" || record.type === "redacted")) {
130
+ out[key] = entry;
131
+ } else {
132
+ out[key] = redactValue(entry, depth + 1, redactStringFn);
133
+ }
134
+ }
135
+ return out;
136
+ }
137
+ return value;
73
138
  }
74
-
75
- // src/config.ts
76
- var langfuseResolvedConfigSymbol = /* @__PURE__ */ Symbol.for(
77
- "@anvia/langfuse.resolvedConfig"
78
- );
79
- function resolveLangfuseConfig(options = {}, fallback) {
139
+ function redactMessage(message, redactStringFn) {
140
+ if (message.role === "system") {
141
+ return { ...message, content: redactStringFn(message.content) };
142
+ }
80
143
  return {
81
- publicKey: resolveStringOption(
82
- options.publicKey,
83
- fallback?.publicKey,
84
- process.env.LANGFUSE_PUBLIC_KEY
85
- ),
86
- secretKey: resolveStringOption(
87
- options.secretKey,
88
- fallback?.secretKey,
89
- process.env.LANGFUSE_SECRET_KEY
90
- ),
91
- baseUrl: resolveStringOption(options.baseUrl, fallback?.baseUrl, process.env.LANGFUSE_BASE_URL) ?? "https://cloud.langfuse.com",
92
- environment: resolveStringOption(
93
- options.environment,
94
- fallback?.environment,
95
- process.env.LANGFUSE_TRACING_ENVIRONMENT
96
- ),
97
- release: resolveStringOption(options.release, fallback?.release, process.env.LANGFUSE_RELEASE),
98
- serviceName: resolveStringOption(
99
- options.serviceName,
100
- fallback?.serviceName,
101
- process.env.LANGFUSE_SERVICE_NAME
102
- ),
103
- timeoutMs: options.timeoutMs ?? fallback?.timeoutMs ?? 3e4
144
+ ...message,
145
+ content: redactMessageContent(message.content, redactStringFn)
104
146
  };
105
147
  }
106
- function getResolvedLangfuseConfig(value) {
107
- if (typeof value !== "object" || value === null) {
108
- return void 0;
109
- }
110
- return value[langfuseResolvedConfigSymbol];
148
+ function redactMessageContent(value, redactStringFn) {
149
+ return redactValue(value, 0, redactStringFn);
111
150
  }
112
- function resolveStringOption(option, fallback, envVar) {
113
- return emptyToUndefined(option) ?? emptyToUndefined(fallback) ?? emptyToUndefined(envVar);
151
+ function isBase64DataUrl(value) {
152
+ return /^data:[^;,]+;base64,/i.test(value);
114
153
  }
115
154
 
116
155
  // src/scoring.ts
@@ -153,7 +192,7 @@ var ScoreQueue = class {
153
192
  timeoutMs;
154
193
  batchSize;
155
194
  flushIntervalMs;
156
- maxRetries;
195
+ maxAttempts;
157
196
  fetchImpl;
158
197
  sleep;
159
198
  setTimer;
@@ -165,7 +204,7 @@ var ScoreQueue = class {
165
204
  this.timeoutMs = options.timeoutMs;
166
205
  this.batchSize = options.batchSize;
167
206
  this.flushIntervalMs = options.flushIntervalMs;
168
- this.maxRetries = options.maxRetries;
207
+ this.maxAttempts = options.maxAttempts;
169
208
  this.fetchImpl = options.fetchImpl ?? fetch;
170
209
  this.sleep = options.sleep ?? defaultSleep;
171
210
  this.setTimer = options.setTimer ?? defaultSetTimer;
@@ -212,10 +251,7 @@ var ScoreQueue = class {
212
251
  async shutdown() {
213
252
  this.closed = true;
214
253
  this.clearScheduledTimer();
215
- try {
216
- await this.flush();
217
- } catch {
218
- }
254
+ await this.flush();
219
255
  }
220
256
  scheduleTimer() {
221
257
  if (this.timer !== null) {
@@ -239,7 +275,7 @@ var ScoreQueue = class {
239
275
  async sendBatch(scores) {
240
276
  const body = scores.map((score) => buildScoreBody(score));
241
277
  let lastError;
242
- for (let attempt = 0; attempt < this.maxRetries; attempt += 1) {
278
+ for (let attempt = 0; attempt < this.maxAttempts; attempt += 1) {
243
279
  try {
244
280
  const response = await this.fetchImpl(`${this.baseUrl}/api/public/scores`, {
245
281
  method: "POST",
@@ -268,12 +304,12 @@ var ScoreQueue = class {
268
304
  }
269
305
  lastError = error;
270
306
  }
271
- if (attempt < this.maxRetries - 1) {
307
+ if (attempt < this.maxAttempts - 1) {
272
308
  await this.sleep(computeBackoff(attempt));
273
309
  }
274
310
  }
275
311
  throw new RetryableLangfuseScoreError(
276
- `Langfuse score batch failed after ${this.maxRetries} attempts`,
312
+ `Langfuse score batch failed after ${this.maxAttempts} attempts`,
277
313
  scores,
278
314
  lastError
279
315
  );
@@ -301,24 +337,259 @@ var ScoreQueue = class {
301
337
  throw permanentError;
302
338
  }
303
339
  }
304
- };
305
- function buildScoreBody(score) {
306
- const body = {
307
- traceId: score.traceId,
308
- name: score.name,
309
- value: score.value
340
+ };
341
+ function buildScoreBody(score) {
342
+ const body = {
343
+ traceId: score.traceId,
344
+ name: score.name,
345
+ value: score.value
346
+ };
347
+ if (score.observationId !== void 0) body.observationId = score.observationId;
348
+ if (score.dataType !== void 0) body.dataType = score.dataType;
349
+ if (score.comment !== void 0) body.comment = score.comment;
350
+ if (score.metadata !== void 0) body.metadata = score.metadata;
351
+ const configId = score.configId ?? score.scoreConfigId;
352
+ if (configId !== void 0) body.configId = configId;
353
+ if (score.environment !== void 0) body.environment = score.environment;
354
+ if (score.timestamp !== void 0) {
355
+ body.timestamp = score.timestamp instanceof Date ? score.timestamp.toISOString() : score.timestamp;
356
+ }
357
+ return body;
358
+ }
359
+
360
+ // src/tracing.ts
361
+ import { LangfuseSpanProcessor } from "@langfuse/otel";
362
+ import {
363
+ LangfuseAgent,
364
+ LangfuseEvent,
365
+ LangfuseGeneration,
366
+ LangfuseGuardrail,
367
+ LangfuseOtelSpanAttributes,
368
+ LangfuseSpan,
369
+ LangfuseTool
370
+ } from "@langfuse/tracing";
371
+ import {
372
+ ROOT_CONTEXT,
373
+ TraceFlags,
374
+ trace
375
+ } from "@opentelemetry/api";
376
+ import { resourceFromAttributes } from "@opentelemetry/resources";
377
+ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
378
+ import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
379
+
380
+ // src/capture.ts
381
+ var DEFAULT_CAPTURE_MAX_BYTES = 262144;
382
+ var MIN_CAPTURE_MAX_BYTES = 96;
383
+ function validateCaptureMaxBytes(value) {
384
+ const resolved = value ?? DEFAULT_CAPTURE_MAX_BYTES;
385
+ if (!Number.isInteger(resolved) || resolved < MIN_CAPTURE_MAX_BYTES) {
386
+ throw new TypeError(
387
+ `Langfuse captureMaxBytes must be an integer of at least ${MIN_CAPTURE_MAX_BYTES}`
388
+ );
389
+ }
390
+ return resolved;
391
+ }
392
+ function sanitizeTraceValue(value, maxBytes) {
393
+ validateCaptureMaxBytes(maxBytes);
394
+ const sanitized = sanitizeValue(value, 0, /* @__PURE__ */ new WeakSet());
395
+ let serialized;
396
+ try {
397
+ serialized = JSON.stringify(sanitized) ?? String(sanitized);
398
+ } catch {
399
+ return omitted("unserializable");
400
+ }
401
+ const originalBytes = utf8Bytes(serialized);
402
+ if (originalBytes <= maxBytes) {
403
+ return sanitized;
404
+ }
405
+ const preview = boundedPreview(serialized, originalBytes, maxBytes);
406
+ return {
407
+ anviaTraceValue: "truncated",
408
+ originalBytes,
409
+ preview
410
+ };
411
+ }
412
+ function sanitizeValue(value, depth, seen) {
413
+ if (depth > 16) {
414
+ return omitted("depth");
415
+ }
416
+ if (typeof value === "string") {
417
+ if (/^data:[^;,]+;base64,/i.test(value)) {
418
+ return omitted("base64", utf8Bytes(value));
419
+ }
420
+ return value;
421
+ }
422
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
423
+ const byteLength = value.byteLength;
424
+ return omitted("binary", byteLength);
425
+ }
426
+ if (value === null || typeof value !== "object") {
427
+ return value;
428
+ }
429
+ if (seen.has(value)) {
430
+ return omitted("circular");
431
+ }
432
+ seen.add(value);
433
+ if (Array.isArray(value)) {
434
+ const result2 = value.map((entry) => sanitizeValue(entry, depth + 1, seen));
435
+ seen.delete(value);
436
+ return result2;
437
+ }
438
+ const record = value;
439
+ const result = {};
440
+ for (const [key, entry] of Object.entries(record)) {
441
+ if (key === "data" && typeof entry === "string" && (record.type === "base64" || record.type === "image" && typeof record.mediaType === "string")) {
442
+ result[key] = omitted("base64", utf8Bytes(entry));
443
+ continue;
444
+ }
445
+ result[key] = sanitizeValue(entry, depth + 1, seen);
446
+ }
447
+ seen.delete(value);
448
+ return result;
449
+ }
450
+ function omitted(reason, originalBytes) {
451
+ const value = {
452
+ anviaTraceValue: "omitted",
453
+ reason
454
+ };
455
+ if (originalBytes !== void 0) value.originalBytes = originalBytes;
456
+ return value;
457
+ }
458
+ function boundedPreview(value, originalBytes, maxBytes) {
459
+ let low = 0;
460
+ let high = value.length;
461
+ while (low < high) {
462
+ const middle = Math.ceil((low + high) / 2);
463
+ const candidate = {
464
+ anviaTraceValue: "truncated",
465
+ originalBytes,
466
+ preview: value.slice(0, middle)
467
+ };
468
+ if (utf8Bytes(JSON.stringify(candidate)) <= maxBytes) {
469
+ low = middle;
470
+ } else {
471
+ high = middle - 1;
472
+ }
473
+ }
474
+ return value.slice(0, low);
475
+ }
476
+ function utf8Bytes(value) {
477
+ return typeof Buffer === "undefined" ? new TextEncoder().encode(value).byteLength : Buffer.byteLength(value, "utf8");
478
+ }
479
+
480
+ // src/helpers.ts
481
+ function modelInputMessage(message) {
482
+ const { metadata: _metadata, ...result } = message;
483
+ return result;
484
+ }
485
+ function modelInputMessages(messages) {
486
+ return messages.map(modelInputMessage);
487
+ }
488
+ function modelParameters(request) {
489
+ const params = {};
490
+ if (request.temperature !== void 0) params.temperature = request.temperature;
491
+ if (request.maxTokens !== void 0) params.maxTokens = request.maxTokens;
492
+ if (request.toolChoice !== void 0) {
493
+ params.toolChoice = typeof request.toolChoice === "string" ? request.toolChoice : request.toolChoice.name;
494
+ }
495
+ return params;
496
+ }
497
+ function usageDetails(usage) {
498
+ if (usage.details !== void 0 && Object.keys(usage.details).length > 0) {
499
+ return { ...usage.details };
500
+ }
501
+ return {
502
+ input: usage.inputTokens,
503
+ output: usage.outputTokens,
504
+ total: usage.totalTokens
505
+ };
506
+ }
507
+ function usageDetailsFromRecord(usage) {
508
+ if (isRecord(usage.details)) {
509
+ const details = Object.fromEntries(
510
+ Object.entries(usage.details).filter(
511
+ (entry) => typeof entry[1] === "number" && Number.isFinite(entry[1]) && entry[1] >= 0
512
+ )
513
+ );
514
+ if (Object.keys(details).length > 0) {
515
+ return details;
516
+ }
517
+ }
518
+ return {
519
+ input: numberValue(usage.inputTokens) ?? 0,
520
+ output: numberValue(usage.outputTokens) ?? 0,
521
+ total: numberValue(usage.totalTokens) ?? (numberValue(usage.inputTokens) ?? 0) + (numberValue(usage.outputTokens) ?? 0)
310
522
  };
311
- if (score.observationId !== void 0) body.observationId = score.observationId;
312
- if (score.dataType !== void 0) body.dataType = score.dataType;
313
- if (score.comment !== void 0) body.comment = score.comment;
314
- if (score.metadata !== void 0) body.metadata = score.metadata;
315
- const configId = score.configId ?? score.scoreConfigId;
316
- if (configId !== void 0) body.configId = configId;
317
- if (score.environment !== void 0) body.environment = score.environment;
318
- if (score.timestamp !== void 0) {
319
- body.timestamp = score.timestamp instanceof Date ? score.timestamp.toISOString() : score.timestamp;
523
+ }
524
+ function childMetadata(args, agentId, agentName, childTurn) {
525
+ return {
526
+ source: "agent_tool_event",
527
+ childAgentId: agentId,
528
+ childAgentName: agentName,
529
+ childTurn,
530
+ parentToolName: args.toolName,
531
+ parentInternalCallId: args.internalCallId,
532
+ parentToolCallId: args.toolCallId
533
+ };
534
+ }
535
+ function generationKey(agentId, turn) {
536
+ return `${agentId}:${turn}`;
537
+ }
538
+ function agentLabel(agentId, agentName) {
539
+ return (agentName ?? agentId).replaceAll(/\s+/g, "_");
540
+ }
541
+ function isRecord(value) {
542
+ return typeof value === "object" && value !== null && !Array.isArray(value);
543
+ }
544
+ function numberValue(value) {
545
+ return typeof value === "number" ? value : void 0;
546
+ }
547
+ function emptyToUndefined(value) {
548
+ return value === void 0 || value.length === 0 ? void 0 : value;
549
+ }
550
+ function errorMessage(error) {
551
+ return error instanceof Error ? error.message : String(error);
552
+ }
553
+
554
+ // src/config.ts
555
+ var langfuseResolvedConfigSymbol = /* @__PURE__ */ Symbol.for(
556
+ "@anvia/langfuse.resolvedConfig"
557
+ );
558
+ function resolveLangfuseConfig(options = {}, fallback) {
559
+ return {
560
+ publicKey: resolveStringOption(
561
+ options.publicKey,
562
+ fallback?.publicKey,
563
+ process.env.LANGFUSE_PUBLIC_KEY
564
+ ),
565
+ secretKey: resolveStringOption(
566
+ options.secretKey,
567
+ fallback?.secretKey,
568
+ process.env.LANGFUSE_SECRET_KEY
569
+ ),
570
+ baseUrl: resolveStringOption(options.baseUrl, fallback?.baseUrl, process.env.LANGFUSE_BASE_URL) ?? "https://cloud.langfuse.com",
571
+ environment: resolveStringOption(
572
+ options.environment,
573
+ fallback?.environment,
574
+ process.env.LANGFUSE_TRACING_ENVIRONMENT
575
+ ),
576
+ release: resolveStringOption(options.release, fallback?.release, process.env.LANGFUSE_RELEASE),
577
+ serviceName: resolveStringOption(
578
+ options.serviceName,
579
+ fallback?.serviceName,
580
+ process.env.LANGFUSE_SERVICE_NAME
581
+ ),
582
+ timeoutMs: options.timeoutMs ?? fallback?.timeoutMs ?? 3e4
583
+ };
584
+ }
585
+ function getResolvedLangfuseConfig(value) {
586
+ if (typeof value !== "object" || value === null) {
587
+ return void 0;
320
588
  }
321
- return body;
589
+ return value[langfuseResolvedConfigSymbol];
590
+ }
591
+ function resolveStringOption(option, fallback, envVar) {
592
+ return emptyToUndefined(option) ?? emptyToUndefined(fallback) ?? emptyToUndefined(envVar);
322
593
  }
323
594
 
324
595
  // src/dataset-client.ts
@@ -377,7 +648,8 @@ function createLangfuseDatasetClient(tracing, options = {}) {
377
648
  if (dataset.metadata !== void 0) result.metadata = dataset.metadata;
378
649
  return result;
379
650
  },
380
- async getDataset(name) {
651
+ async getDataset(options2) {
652
+ const { name } = options2;
381
653
  const items = [];
382
654
  let description;
383
655
  let metadata;
@@ -417,7 +689,8 @@ function createLangfuseDatasetClient(tracing, options = {}) {
417
689
  if (metadata !== void 0) dataset.metadata = metadata;
418
690
  return dataset;
419
691
  },
420
- async upsertItems(name, items) {
692
+ async upsertItems(options2) {
693
+ const { name, items } = options2;
421
694
  const url = `${baseUrl}/api/public/datasets/${encodeURIComponent(name)}/items`;
422
695
  await request(url, {
423
696
  method: "POST",
@@ -427,7 +700,7 @@ function createLangfuseDatasetClient(tracing, options = {}) {
427
700
  async runExperiment(opts) {
428
701
  let items = opts.items;
429
702
  if (items === void 0) {
430
- const dataset = await this.getDataset(opts.datasetName);
703
+ const dataset = await this.getDataset({ name: opts.datasetName });
431
704
  items = dataset.items;
432
705
  }
433
706
  if (items === void 0 || items.length === 0) {
@@ -508,7 +781,8 @@ import {
508
781
  } from "@anvia/core/evals";
509
782
  var DEFAULT_TRUNCATE_BYTES = 2048;
510
783
  function createLangfuseEvalReporter(tracing, options = {}) {
511
- const onMissingTrace = options.onMissingTrace ?? (options.strict === true ? "throw" : "ignore");
784
+ const onMissingTrace = options.onMissingTrace ?? "ignore";
785
+ const traceObserver = options.traceObserver ?? "langfuse";
512
786
  const truncateAt = options.truncateInputAt ?? DEFAULT_TRUNCATE_BYTES;
513
787
  const includeMessages = options.includeMessages ?? true;
514
788
  const includeContext = options.includeContext ?? false;
@@ -517,18 +791,20 @@ function createLangfuseEvalReporter(tracing, options = {}) {
517
791
  if (args.outcome.outcome === "invalid" && options.publishInvalid !== true) {
518
792
  return;
519
793
  }
520
- const trace = args.trace ?? resolveEvalTraceRef({
794
+ const trace2 = args.trace ?? resolveEvalTraceRef({
521
795
  output: args.output,
522
796
  input: args.case.input,
523
797
  metadata: args.case.metadata
524
798
  });
525
- if (trace?.traceId === void 0 || trace.traceId.length === 0) {
799
+ if (trace2?.traceId === void 0 || trace2.traceId.length === 0 || trace2.observer !== void 0 && trace2.observer !== traceObserver) {
526
800
  if (onMissingTrace === "throw") {
527
- throw new Error("Langfuse eval reporter requires traceId");
801
+ throw new Error(
802
+ `Langfuse eval reporter requires traceId from observer ${JSON.stringify(traceObserver)}`
803
+ );
528
804
  }
529
805
  if (onMissingTrace === "warn") {
530
806
  console.warn(
531
- "[anvia/langfuse] eval reporter dropped score because no traceId was found",
807
+ "[anvia/langfuse] eval reporter dropped score because no matching trace was found",
532
808
  { caseId: args.case.id, metric: args.metric.name }
533
809
  );
534
810
  }
@@ -547,11 +823,11 @@ function createLangfuseEvalReporter(tracing, options = {}) {
547
823
  });
548
824
  const configId = resolveConfigId(args.metric);
549
825
  const score = {
550
- traceId: trace.traceId,
826
+ traceId: trace2.traceId,
551
827
  name: args.metric.name,
552
828
  value: projection.value
553
829
  };
554
- if (trace.observationId !== void 0) score.observationId = trace.observationId;
830
+ if (trace2.observationId !== void 0) score.observationId = trace2.observationId;
555
831
  if (args.metric.dataType !== void 0) score.dataType = args.metric.dataType;
556
832
  if (configId !== void 0) score.configId = configId;
557
833
  if (projection.explanation !== void 0) score.comment = projection.explanation;
@@ -649,18 +925,20 @@ function readMessages(output) {
649
925
 
650
926
  // src/experiment-runner.ts
651
927
  import { runEvalSuite } from "@anvia/core/evals";
652
- async function runEvalAsExperiment(evalOptions, experimentOptions) {
928
+ async function runLangfuseEvalExperiment(client, options) {
929
+ const evalOptions = options.suite;
930
+ const experimentOptions = options.experiment;
653
931
  const clientOptions = {};
654
932
  if (experimentOptions.pageSize !== void 0) clientOptions.pageSize = experimentOptions.pageSize;
655
933
  if (experimentOptions.timeoutMs !== void 0)
656
934
  clientOptions.timeoutMs = experimentOptions.timeoutMs;
657
- const client = experimentOptions.client ?? createLangfuseDatasetClient(experimentOptions.tracing, clientOptions);
935
+ const datasetClient = client.datasetClient(clientOptions);
658
936
  const suiteOptions = experimentOptions.publishScores === true ? {
659
937
  ...evalOptions,
660
938
  reporters: [
661
939
  ...evalOptions.reporters ?? [],
662
940
  createLangfuseEvalReporter(
663
- experimentOptions.tracing,
941
+ client,
664
942
  experimentOptions.reporterOptions
665
943
  )
666
944
  ]
@@ -705,30 +983,30 @@ async function runEvalAsExperiment(evalOptions, experimentOptions) {
705
983
  };
706
984
  }
707
985
  const output = result.output ?? void 0;
708
- const trace = readTraceFromOutput(result.output);
709
- return { output, trace };
986
+ const trace2 = readTraceFromOutput(result.output);
987
+ return { output, trace: trace2 };
710
988
  }
711
989
  };
712
990
  if (experimentOptions.description !== void 0) {
713
991
  runOptions.description = experimentOptions.description;
714
992
  }
715
993
  if (experimentOptions.metadata !== void 0) runOptions.metadata = experimentOptions.metadata;
716
- const datasetRun = await client.runExperiment(runOptions);
994
+ const datasetRun = await datasetClient.runExperiment(runOptions);
717
995
  return { suite, datasetRun };
718
996
  }
719
997
  function readTraceFromOutput(output) {
720
998
  if (typeof output !== "object" || output === null || !("trace" in output)) {
721
999
  return void 0;
722
1000
  }
723
- const trace = output.trace;
724
- if (typeof trace !== "object" || trace === null) {
1001
+ const trace2 = output.trace;
1002
+ if (typeof trace2 !== "object" || trace2 === null) {
725
1003
  return void 0;
726
1004
  }
727
- const traceId = trace.traceId;
1005
+ const traceId = trace2.traceId;
728
1006
  if (typeof traceId !== "string") {
729
1007
  return void 0;
730
1008
  }
731
- const observationId = trace.observationId;
1009
+ const observationId = trace2.observationId;
732
1010
  if (typeof observationId === "string") {
733
1011
  return { traceId, observationId };
734
1012
  }
@@ -761,7 +1039,8 @@ function createLangfusePromptClient(tracing, options = {}) {
761
1039
  }
762
1040
  return await response.json();
763
1041
  }
764
- async function getPrompt(name, opts = {}) {
1042
+ async function getPrompt(options2) {
1043
+ const { name, ...opts } = options2;
765
1044
  const key = `${name}::${opts.version ?? ""}::${opts.label ?? ""}`;
766
1045
  const ttl = opts.cacheTtlMs ?? defaultTtl;
767
1046
  if (opts.refresh !== true) {
@@ -790,18 +1069,18 @@ function createLangfusePromptClient(tracing, options = {}) {
790
1069
  cache.set(key, { prompt, expiresAt: Date.now() + ttl });
791
1070
  return prompt;
792
1071
  }
793
- function getPromptText(name, opts) {
794
- return getPrompt(name, opts).then((prompt) => {
1072
+ function getPromptText(options2) {
1073
+ return getPrompt(options2).then((prompt) => {
795
1074
  if (typeof prompt.prompt !== "string") {
796
- throw new Error(`Prompt ${name} is a chat prompt; expected text`);
1075
+ throw new Error(`Prompt ${options2.name} is a chat prompt; expected text`);
797
1076
  }
798
1077
  return prompt.prompt;
799
1078
  });
800
1079
  }
801
- function getPromptChat(name, opts) {
802
- return getPrompt(name, opts).then((prompt) => {
1080
+ function getPromptChat(options2) {
1081
+ return getPrompt(options2).then((prompt) => {
803
1082
  if (typeof prompt.prompt === "string") {
804
- throw new Error(`Prompt ${name} is a text prompt; expected chat`);
1083
+ throw new Error(`Prompt ${options2.name} is a text prompt; expected chat`);
805
1084
  }
806
1085
  return prompt.prompt;
807
1086
  });
@@ -828,313 +1107,75 @@ function normalizePrompt(raw, type) {
828
1107
  if (typeof content !== "string") {
829
1108
  throw new Error("Expected chat message content to be a string");
830
1109
  }
831
- if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") {
832
- throw new Error(`Unexpected chat message role: ${String(role)}`);
833
- }
834
- return { role, content };
835
- });
836
- }
837
- function buildAuthHeader2(publicKey, secretKey) {
838
- if (publicKey === void 0 || secretKey === void 0) {
839
- return {};
840
- }
841
- const encoded = Buffer.from(`${publicKey}:${secretKey}`).toString("base64");
842
- return { Authorization: `Basic ${encoded}` };
843
- }
844
- async function readErrorBody2(response) {
845
- try {
846
- return await response.text();
847
- } catch {
848
- return "<unreadable>";
849
- }
850
- }
851
-
852
- // src/redaction.ts
853
- var DEFAULT_REPLACEMENT = "[REDACTED]";
854
- var MAX_DEPTH = 16;
855
- function createPiiRedactor(options = {}) {
856
- const patterns = options.patterns ?? DEFAULT_PATTERNS;
857
- const replacement = options.replacement ?? DEFAULT_REPLACEMENT;
858
- const compiled = patterns.map((p) => ({
859
- name: p.name,
860
- regex: cloneRegex(p.regex, "g")
861
- }));
862
- const patternNamesList = patterns.map((p) => p.name);
863
- function redactString(input) {
864
- if (typeof input !== "string") return input;
865
- let out = input;
866
- for (const { name, regex } of compiled) {
867
- out = applyPattern(out, name, regex, replacement);
868
- }
869
- return out;
870
- }
871
- function redactObject(input) {
872
- return redactValue(input, 0, redactString);
873
- }
874
- function redactMessages(input) {
875
- return input.map((message) => redactMessage(message, redactString));
876
- }
877
- function patternNames() {
878
- return patternNamesList;
879
- }
880
- return { redactString, redactObject, redactMessages, patternNames };
881
- }
882
- var DEFAULT_PATTERNS = [
883
- { name: "email", regex: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g },
884
- { name: "creditCard", regex: /\b(?:\d[ -]?){13,19}\b/g },
885
- { name: "ipv4", regex: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g },
886
- {
887
- name: "phone",
888
- regex: /(?<!\d)(?:\+\d{1,3}[\s.-]?)?(?:\(\d{2,4}\)[\s.-]?)?\d{3,4}[\s.-]?\d{3,4}(?:[\s.-]?\d{3,4})?(?!\d)/g
889
- },
890
- { name: "jwt", regex: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g },
891
- {
892
- name: "apiKey",
893
- regex: /\b(?:sk|pk|api|key|token)[-_][A-Za-z0-9]{16,}\b/gi
894
- }
895
- ];
896
- function applyPattern(input, name, regex, replacement) {
897
- if (name === "creditCard") {
898
- return redactCreditCards(input, replacement);
899
- }
900
- return input.replace(regex, replacement);
901
- }
902
- function redactCreditCards(input, replacement) {
903
- let out = "";
904
- let i = 0;
905
- while (i < input.length) {
906
- const ch = input.charAt(i);
907
- if (/\d/.test(ch)) {
908
- const { length, valid } = longestLuhnChunk(input.slice(i));
909
- if (valid) {
910
- out += replacement;
911
- i += length;
912
- continue;
913
- }
914
- }
915
- out += ch;
916
- i += 1;
917
- }
918
- return out;
919
- }
920
- function longestLuhnChunk(s) {
921
- let length = 0;
922
- let bestValid = 0;
923
- while (length < s.length && length < 40) {
924
- const ch = s.charAt(length);
925
- if (!/\d/.test(ch) && ch !== "-") break;
926
- length += 1;
927
- const candidate = s.slice(0, length).replace(/\D/g, "");
928
- if (candidate.length >= 13 && candidate.length <= 19) {
929
- if (startsWithKnownPrefix(candidate) && passesLuhn(candidate)) {
930
- bestValid = length;
931
- }
932
- }
933
- }
934
- return { length: bestValid, valid: bestValid > 0 };
935
- }
936
- function startsWithKnownPrefix(digits) {
937
- if (digits.startsWith("4")) return true;
938
- const two = digits.slice(0, 2);
939
- if (two === "51" || two === "52" || two === "53" || two === "54" || two === "55") return true;
940
- const four = digits.slice(0, 4);
941
- if (four === "2221" || four === "2720") return true;
942
- const twoAgain = digits.slice(0, 2);
943
- if (twoAgain === "34" || twoAgain === "37") return true;
944
- if (four === "6011" || twoAgain === "65") return true;
945
- return digits.startsWith("35");
946
- }
947
- function passesLuhn(digits) {
948
- if (!/^\d+$/.test(digits)) return false;
949
- let sum = 0;
950
- let alt = false;
951
- for (let i = digits.length - 1; i >= 0; i -= 1) {
952
- const raw = digits.charCodeAt(i) - 48;
953
- let value = raw;
954
- if (alt) {
955
- value *= 2;
956
- if (value > 9) value -= 9;
957
- }
958
- sum += value;
959
- alt = !alt;
960
- }
961
- return sum % 10 === 0;
962
- }
963
- function cloneRegex(source, flags) {
964
- return new RegExp(source.source, flags + source.flags.replace(/g/g, ""));
965
- }
966
- function redactValue(value, depth, redactStringFn) {
967
- if (depth > MAX_DEPTH) return value;
968
- if (typeof value === "string") {
969
- return isBase64DataUrl(value) ? value : redactStringFn(value);
970
- }
971
- if (Array.isArray(value))
972
- return value.map((entry) => redactValue(entry, depth + 1, redactStringFn));
973
- if (value !== null && typeof value === "object") {
974
- if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
975
- return value;
976
- }
977
- const record = value;
978
- const out = {};
979
- for (const [key, entry] of Object.entries(record)) {
980
- if (key === "data" && typeof entry === "string" && (record.type === "base64" || record.type === "image" || record.type === "encrypted" || record.type === "redacted")) {
981
- out[key] = entry;
982
- } else {
983
- out[key] = redactValue(entry, depth + 1, redactStringFn);
984
- }
1110
+ if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") {
1111
+ throw new Error(`Unexpected chat message role: ${String(role)}`);
985
1112
  }
986
- return out;
987
- }
988
- return value;
989
- }
990
- function redactMessage(message, redactStringFn) {
991
- if (message.role === "system") {
992
- return { ...message, content: redactStringFn(message.content) };
993
- }
994
- return {
995
- ...message,
996
- content: redactMessageContent(message.content, redactStringFn)
997
- };
998
- }
999
- function redactMessageContent(value, redactStringFn) {
1000
- return redactValue(value, 0, redactStringFn);
1001
- }
1002
- function isBase64DataUrl(value) {
1003
- return /^data:[^;,]+;base64,/i.test(value);
1113
+ return { role, content };
1114
+ });
1004
1115
  }
1005
-
1006
- // src/tracing.ts
1007
- import { LangfuseSpanProcessor } from "@langfuse/otel";
1008
- import {
1009
- LangfuseOtelSpanAttributes,
1010
- startObservation
1011
- } from "@langfuse/tracing";
1012
- import { resourceFromAttributes } from "@opentelemetry/resources";
1013
- import { NodeSDK } from "@opentelemetry/sdk-node";
1014
- import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
1015
-
1016
- // src/capture.ts
1017
- var DEFAULT_CAPTURE_MAX_BYTES = 262144;
1018
- var MIN_CAPTURE_MAX_BYTES = 96;
1019
- function validateCaptureMaxBytes(value) {
1020
- const resolved = value ?? DEFAULT_CAPTURE_MAX_BYTES;
1021
- if (!Number.isInteger(resolved) || resolved < MIN_CAPTURE_MAX_BYTES) {
1022
- throw new TypeError(
1023
- `Langfuse captureMaxBytes must be an integer of at least ${MIN_CAPTURE_MAX_BYTES}`
1024
- );
1116
+ function buildAuthHeader2(publicKey, secretKey) {
1117
+ if (publicKey === void 0 || secretKey === void 0) {
1118
+ return {};
1025
1119
  }
1026
- return resolved;
1120
+ const encoded = Buffer.from(`${publicKey}:${secretKey}`).toString("base64");
1121
+ return { Authorization: `Basic ${encoded}` };
1027
1122
  }
1028
- function sanitizeTraceValue(value, maxBytes) {
1029
- validateCaptureMaxBytes(maxBytes);
1030
- const sanitized = sanitizeValue(value, 0, /* @__PURE__ */ new WeakSet());
1031
- let serialized;
1123
+ async function readErrorBody2(response) {
1032
1124
  try {
1033
- serialized = JSON.stringify(sanitized) ?? String(sanitized);
1125
+ return await response.text();
1034
1126
  } catch {
1035
- return omitted("unserializable");
1036
- }
1037
- const originalBytes = utf8Bytes(serialized);
1038
- if (originalBytes <= maxBytes) {
1039
- return sanitized;
1127
+ return "<unreadable>";
1040
1128
  }
1041
- const preview = boundedPreview(serialized, originalBytes, maxBytes);
1042
- return {
1043
- anviaTraceValue: "truncated",
1044
- originalBytes,
1045
- preview
1046
- };
1047
1129
  }
1048
- function sanitizeValue(value, depth, seen) {
1049
- if (depth > 16) {
1050
- return omitted("depth");
1051
- }
1052
- if (typeof value === "string") {
1053
- if (/^data:[^;,]+;base64,/i.test(value)) {
1054
- return omitted("base64", utf8Bytes(value));
1055
- }
1056
- return value;
1130
+
1131
+ // src/tracing.ts
1132
+ var LangfuseObservationFactory = class {
1133
+ constructor(tracer) {
1134
+ this.tracer = tracer;
1057
1135
  }
1058
- if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
1059
- const byteLength = value.byteLength;
1060
- return omitted("binary", byteLength);
1136
+ tracer;
1137
+ agent(name, attributes, parent) {
1138
+ return new LangfuseAgent({ otelSpan: this.startSpan(name, parent), attributes });
1061
1139
  }
1062
- if (value === null || typeof value !== "object") {
1063
- return value;
1140
+ span(name, attributes, parent) {
1141
+ return new LangfuseSpan({ otelSpan: this.startSpan(name, parent), attributes });
1064
1142
  }
1065
- if (seen.has(value)) {
1066
- return omitted("circular");
1143
+ generation(name, attributes, parent) {
1144
+ return new LangfuseGeneration({ otelSpan: this.startSpan(name, parent), attributes });
1067
1145
  }
1068
- seen.add(value);
1069
- if (Array.isArray(value)) {
1070
- const result2 = value.map((entry) => sanitizeValue(entry, depth + 1, seen));
1071
- seen.delete(value);
1072
- return result2;
1146
+ tool(name, attributes, parent) {
1147
+ return new LangfuseTool({ otelSpan: this.startSpan(name, parent), attributes });
1073
1148
  }
1074
- const record = value;
1075
- const result = {};
1076
- for (const [key, entry] of Object.entries(record)) {
1077
- if (key === "data" && typeof entry === "string" && (record.type === "base64" || record.type === "image" && typeof record.mediaType === "string")) {
1078
- result[key] = omitted("base64", utf8Bytes(entry));
1079
- continue;
1080
- }
1081
- result[key] = sanitizeValue(entry, depth + 1, seen);
1149
+ guardrail(name, attributes, parent) {
1150
+ return new LangfuseGuardrail({ otelSpan: this.startSpan(name, parent), attributes });
1082
1151
  }
1083
- seen.delete(value);
1084
- return result;
1085
- }
1086
- function omitted(reason, originalBytes) {
1087
- return {
1088
- anviaTraceValue: "omitted",
1089
- reason,
1090
- ...originalBytes === void 0 ? {} : { originalBytes }
1091
- };
1092
- }
1093
- function boundedPreview(value, originalBytes, maxBytes) {
1094
- let low = 0;
1095
- let high = value.length;
1096
- while (low < high) {
1097
- const middle = Math.ceil((low + high) / 2);
1098
- const candidate = {
1099
- anviaTraceValue: "truncated",
1100
- originalBytes,
1101
- preview: value.slice(0, middle)
1102
- };
1103
- if (utf8Bytes(JSON.stringify(candidate)) <= maxBytes) {
1104
- low = middle;
1105
- } else {
1106
- high = middle - 1;
1107
- }
1152
+ event(name, attributes, parent, timestamp) {
1153
+ const endTime = timestamp ?? /* @__PURE__ */ new Date();
1154
+ return new LangfuseEvent({
1155
+ otelSpan: this.startSpan(name, parent, timestamp),
1156
+ attributes,
1157
+ timestamp: endTime
1158
+ });
1108
1159
  }
1109
- return value.slice(0, low);
1110
- }
1111
- function utf8Bytes(value) {
1112
- return typeof Buffer === "undefined" ? new TextEncoder().encode(value).byteLength : Buffer.byteLength(value, "utf8");
1113
- }
1114
-
1115
- // src/tracing.ts
1116
- var langfuse = {
1117
- create(options = {}) {
1118
- return new LangfuseAgentObserver(options);
1160
+ startSpan(name, parent, startTime) {
1161
+ const parentContext = parent === void 0 ? ROOT_CONTEXT : "otelSpan" in parent ? trace.setSpan(ROOT_CONTEXT, parent.otelSpan) : trace.setSpanContext(ROOT_CONTEXT, parent);
1162
+ return this.tracer.startSpan(name, startTime === void 0 ? {} : { startTime }, parentContext);
1119
1163
  }
1120
1164
  };
1121
- var LangfuseAgentObserver = class {
1122
- processor;
1123
- sdk;
1165
+ var LangfuseClient = class {
1124
1166
  [langfuseResolvedConfigSymbol];
1125
1167
  publicKey;
1126
1168
  secretKey;
1127
1169
  baseUrl;
1128
1170
  serviceName;
1129
1171
  timeoutMs;
1130
- queue;
1131
- currentHandle;
1132
- redactor;
1133
- redactInputs;
1134
- redactOutputs;
1135
- captureMode;
1136
- captureMaxBytes;
1137
- constructor(options) {
1172
+ options;
1173
+ resource;
1174
+ initialization;
1175
+ closePromise;
1176
+ closed = false;
1177
+ constructor(options = {}) {
1178
+ this.options = options;
1138
1179
  const resolvedConfig = resolveLangfuseConfig(options);
1139
1180
  this[langfuseResolvedConfigSymbol] = resolvedConfig;
1140
1181
  this.publicKey = resolvedConfig.publicKey;
@@ -1142,50 +1183,82 @@ var LangfuseAgentObserver = class {
1142
1183
  this.baseUrl = resolvedConfig.baseUrl;
1143
1184
  this.serviceName = resolvedConfig.serviceName;
1144
1185
  this.timeoutMs = resolvedConfig.timeoutMs;
1145
- const processorOptions = {
1146
- baseUrl: this.baseUrl
1186
+ }
1187
+ observer(options = {}) {
1188
+ this.assertOpen();
1189
+ return new LangfuseAgentObserver(this, resolveLangfuseCapture(options));
1190
+ }
1191
+ evalReporter(options = {}) {
1192
+ this.assertOpen();
1193
+ const reporter = createLangfuseEvalReporter(this, options);
1194
+ return {
1195
+ report: (args) => {
1196
+ this.assertOpen();
1197
+ return reporter.report(args);
1198
+ }
1147
1199
  };
1148
- if (this.publicKey !== void 0) processorOptions.publicKey = this.publicKey;
1149
- if (this.secretKey !== void 0) processorOptions.secretKey = this.secretKey;
1150
- if (resolvedConfig.environment !== void 0) {
1151
- processorOptions.environment = resolvedConfig.environment;
1152
- }
1153
- if (resolvedConfig.release !== void 0) processorOptions.release = resolvedConfig.release;
1154
- this.processor = new LangfuseSpanProcessor(processorOptions);
1155
- const sdkOptions = {
1156
- spanProcessors: [this.processor]
1200
+ }
1201
+ promptClient(options = {}) {
1202
+ this.assertOpen();
1203
+ const prompts = createLangfusePromptClient(this, options);
1204
+ return {
1205
+ getPrompt: (getOptions) => {
1206
+ this.assertOpen();
1207
+ return prompts.getPrompt(getOptions);
1208
+ },
1209
+ getPromptText: (getOptions) => {
1210
+ this.assertOpen();
1211
+ return prompts.getPromptText(getOptions);
1212
+ },
1213
+ getPromptChat: (getOptions) => {
1214
+ this.assertOpen();
1215
+ return prompts.getPromptChat(getOptions);
1216
+ },
1217
+ refresh: () => {
1218
+ this.assertOpen();
1219
+ prompts.refresh();
1220
+ }
1157
1221
  };
1158
- if (this.serviceName !== void 0) {
1159
- sdkOptions.resource = resourceFromAttributes({
1160
- [SEMRESATTRS_SERVICE_NAME]: this.serviceName
1161
- });
1162
- }
1163
- this.sdk = new NodeSDK(sdkOptions);
1164
- this.sdk.start();
1165
- const batchSize = options.scoreBatchSize ?? 0;
1166
- this.queue = batchSize > 0 && this.publicKey !== void 0 && this.secretKey !== void 0 ? new ScoreQueue({
1167
- baseUrl: this.baseUrl,
1168
- publicKey: this.publicKey,
1169
- secretKey: this.secretKey,
1170
- timeoutMs: this.timeoutMs,
1171
- batchSize,
1172
- flushIntervalMs: options.scoreFlushIntervalMs ?? 250,
1173
- maxRetries: options.scoreMaxRetries ?? 3
1174
- }) : null;
1175
- this.redactInputs = options.redactInputs;
1176
- this.redactOutputs = options.redactOutputs;
1177
- this.captureMode = options.captureMode ?? "safe";
1178
- this.captureMaxBytes = validateCaptureMaxBytes(options.captureMaxBytes);
1179
- this.redactor = options.redactInputs !== void 0 || options.redactOutputs !== void 0 ? createPiiRedactor(options.redaction) : void 0;
1180
- }
1181
- async startRun(args) {
1222
+ }
1223
+ datasetClient(options = {}) {
1224
+ this.assertOpen();
1225
+ const datasets = createLangfuseDatasetClient(this, options);
1226
+ const client = this;
1227
+ return {
1228
+ createDataset(dataset) {
1229
+ client.assertOpen();
1230
+ return datasets.createDataset(dataset);
1231
+ },
1232
+ getDataset(getOptions) {
1233
+ client.assertOpen();
1234
+ return datasets.getDataset(getOptions);
1235
+ },
1236
+ upsertItems(upsertOptions) {
1237
+ client.assertOpen();
1238
+ return datasets.upsertItems(upsertOptions);
1239
+ },
1240
+ runExperiment(experimentOptions) {
1241
+ client.assertOpen();
1242
+ return datasets.runExperiment(experimentOptions);
1243
+ }
1244
+ };
1245
+ }
1246
+ runEvalExperiment(options) {
1247
+ this.assertOpen();
1248
+ return runLangfuseEvalExperiment(this, options);
1249
+ }
1250
+ async startObservedRun(args, capture) {
1251
+ const resource = await this.resources();
1182
1252
  const traceId = args.trace?.traceId;
1183
- const capturedInput = this.captureInput({
1184
- instructions: args.instructions,
1185
- prompt: args.prompt,
1186
- history: args.history
1187
- });
1188
- const capturedTraceMetadata = this.captureInput(args.trace?.metadata ?? {});
1253
+ const capturedInput = captureInput(
1254
+ {
1255
+ instructions: args.instructions,
1256
+ prompt: args.prompt,
1257
+ history: args.history
1258
+ },
1259
+ capture
1260
+ );
1261
+ const capturedTraceMetadata = captureInput(args.trace?.metadata ?? {}, capture);
1189
1262
  const metadata = {
1190
1263
  agentName: args.agentName,
1191
1264
  agentDescription: args.agentDescription,
@@ -1201,69 +1274,41 @@ var LangfuseAgentObserver = class {
1201
1274
  input: capturedInput,
1202
1275
  metadata
1203
1276
  };
1204
- if (args.trace?.version !== void 0) {
1205
- rootAttributes.version = args.trace.version;
1206
- }
1207
- const root = startObservation(
1277
+ if (args.trace?.version !== void 0) rootAttributes.version = args.trace.version;
1278
+ const root = resource.observations.agent(
1208
1279
  args.agentName ?? "agent.run",
1209
1280
  rootAttributes,
1210
- traceId === void 0 ? { asType: "agent" } : {
1211
- asType: "agent",
1212
- parentSpanContext: {
1213
- traceId,
1214
- spanId: "0000000000000001",
1215
- traceFlags: 1
1216
- }
1281
+ traceId === void 0 ? void 0 : {
1282
+ traceId,
1283
+ spanId: "0000000000000001",
1284
+ traceFlags: TraceFlags.SAMPLED
1217
1285
  }
1218
1286
  );
1219
1287
  applyTraceAttributes(root, args, capturedTraceMetadata);
1220
- const promptRef = resolvePromptRef(args);
1221
- const runObserver = new LangfuseRunObserver(
1288
+ return new LangfuseRunObserver(
1222
1289
  root,
1223
- {
1224
- traceId: root.traceId,
1225
- observationId: root.id
1226
- },
1227
- promptRef,
1228
- {
1229
- redactor: this.redactor,
1230
- redactInputs: this.redactInputs,
1231
- redactOutputs: this.redactOutputs,
1232
- captureMode: this.captureMode,
1233
- captureMaxBytes: this.captureMaxBytes
1234
- }
1290
+ resource.observations,
1291
+ { traceId: root.traceId, observationId: root.id },
1292
+ resolvePromptRef(args),
1293
+ capture
1235
1294
  );
1236
- this.currentHandle = runObserver.getHandle();
1237
- runObserver.setCurrentHandle = (handle) => {
1238
- this.currentHandle = handle;
1239
- };
1240
- runObserver.clearCurrentHandle = () => {
1241
- if (this.currentHandle === runObserver.getHandle()) {
1242
- this.currentHandle = void 0;
1243
- }
1244
- };
1245
- return runObserver;
1246
1295
  }
1247
1296
  async flush() {
1248
- await this.queue?.flush();
1249
- await this.processor.forceFlush();
1297
+ this.assertOpen();
1298
+ const resource = this.resource ?? (this.initialization === void 0 ? void 0 : await this.initialization);
1299
+ if (resource === void 0) return;
1300
+ await resource.queue?.flush();
1301
+ await resource.processor.forceFlush();
1250
1302
  }
1251
- async shutdown() {
1252
- await this.queue?.shutdown();
1253
- await this.sdk.shutdown();
1303
+ close() {
1304
+ this.closePromise ??= this.closeResources();
1305
+ return this.closePromise;
1254
1306
  }
1255
- async flushScores() {
1256
- await this.queue?.flush();
1307
+ async [Symbol.asyncDispose]() {
1308
+ await this.close();
1257
1309
  }
1258
1310
  scoreQueueDepth() {
1259
- return this.queue?.depth() ?? 0;
1260
- }
1261
- getCurrentTrace() {
1262
- return this.currentHandle;
1263
- }
1264
- captureInput(value) {
1265
- const redacted = this.redactor === void 0 || this.redactInputs === void 0 ? value : applyRedaction(this.redactor, value, this.redactInputs);
1266
- return sanitizeTraceValue(redacted, this.captureMaxBytes);
1311
+ return this.resource?.queue?.depth() ?? 0;
1267
1312
  }
1268
1313
  async score(args) {
1269
1314
  if (args.traceId === void 0 || args.traceId.length === 0) {
@@ -1273,12 +1318,100 @@ var LangfuseAgentObserver = class {
1273
1318
  throw new Error("Langfuse score requires publicKey and secretKey");
1274
1319
  }
1275
1320
  assertScoreValue(args.value, args.dataType);
1276
- if (this.queue !== null) {
1277
- this.queue.enqueue(args);
1321
+ const resource = await this.resources();
1322
+ if (resource.queue !== null) {
1323
+ resource.queue.enqueue(args);
1278
1324
  return;
1279
1325
  }
1280
1326
  await this.sendScore(args);
1281
1327
  }
1328
+ resources() {
1329
+ this.assertOpen();
1330
+ if (this.resource !== void 0) return Promise.resolve(this.resource);
1331
+ this.initialization ??= this.createResources().then((resource) => {
1332
+ this.resource = resource;
1333
+ return resource;
1334
+ }).catch((error) => {
1335
+ this.initialization = void 0;
1336
+ throw error;
1337
+ });
1338
+ return this.initialization;
1339
+ }
1340
+ async createResources() {
1341
+ const resolvedConfig = this[langfuseResolvedConfigSymbol];
1342
+ const processorOptions = {
1343
+ baseUrl: this.baseUrl
1344
+ };
1345
+ if (this.publicKey !== void 0) processorOptions.publicKey = this.publicKey;
1346
+ if (this.secretKey !== void 0) processorOptions.secretKey = this.secretKey;
1347
+ if (resolvedConfig.environment !== void 0) {
1348
+ processorOptions.environment = resolvedConfig.environment;
1349
+ }
1350
+ if (resolvedConfig.release !== void 0) processorOptions.release = resolvedConfig.release;
1351
+ const processor = new LangfuseSpanProcessor(processorOptions);
1352
+ const providerOptions = {
1353
+ spanProcessors: [processor]
1354
+ };
1355
+ if (this.serviceName !== void 0) {
1356
+ providerOptions.resource = resourceFromAttributes({
1357
+ [SEMRESATTRS_SERVICE_NAME]: this.serviceName
1358
+ });
1359
+ }
1360
+ let provider;
1361
+ try {
1362
+ provider = new NodeTracerProvider(providerOptions);
1363
+ } catch (error) {
1364
+ await processor.shutdown().catch(() => void 0);
1365
+ throw error;
1366
+ }
1367
+ try {
1368
+ const batchSize = this.options.scores?.batchSize ?? 0;
1369
+ const queue = batchSize > 0 && this.publicKey !== void 0 && this.secretKey !== void 0 ? new ScoreQueue({
1370
+ baseUrl: this.baseUrl,
1371
+ publicKey: this.publicKey,
1372
+ secretKey: this.secretKey,
1373
+ timeoutMs: this.timeoutMs,
1374
+ batchSize,
1375
+ flushIntervalMs: this.options.scores?.flushIntervalMs ?? 250,
1376
+ maxAttempts: scoreMaxAttempts(this.options.scores?.retries)
1377
+ }) : null;
1378
+ return {
1379
+ processor,
1380
+ provider,
1381
+ observations: new LangfuseObservationFactory(
1382
+ provider.getTracer("@anvia/langfuse", "1.0.0")
1383
+ ),
1384
+ queue
1385
+ };
1386
+ } catch (error) {
1387
+ await provider.shutdown().catch(() => void 0);
1388
+ throw error;
1389
+ }
1390
+ }
1391
+ async closeResources() {
1392
+ this.closed = true;
1393
+ const pending = this.resource === void 0 ? this.initialization : Promise.resolve(this.resource);
1394
+ if (pending === void 0) return;
1395
+ let resource;
1396
+ try {
1397
+ resource = await pending;
1398
+ } catch {
1399
+ return;
1400
+ }
1401
+ const settled = await Promise.allSettled([
1402
+ resource.queue?.shutdown() ?? Promise.resolve(),
1403
+ resource.provider.shutdown()
1404
+ ]);
1405
+ const failures = settled.flatMap(
1406
+ (result) => result.status === "rejected" ? [result.reason] : []
1407
+ );
1408
+ if (failures.length > 0) {
1409
+ throw new AggregateError(failures, "Failed to close LangfuseClient.");
1410
+ }
1411
+ }
1412
+ assertOpen() {
1413
+ if (this.closed) throw new Error("LangfuseClient is closed.");
1414
+ }
1282
1415
  async sendScore(args) {
1283
1416
  const body = buildScoreBody2(args);
1284
1417
  const response = await fetch(`${this.baseUrl}/api/public/scores`, {
@@ -1297,6 +1430,37 @@ var LangfuseAgentObserver = class {
1297
1430
  }
1298
1431
  }
1299
1432
  };
1433
+ var LangfuseAgentObserver = class {
1434
+ constructor(client, capture) {
1435
+ this.client = client;
1436
+ this.capture = capture;
1437
+ }
1438
+ client;
1439
+ capture;
1440
+ startRun(args) {
1441
+ return this.client.startObservedRun(args, this.capture);
1442
+ }
1443
+ };
1444
+ function resolveLangfuseCapture(options) {
1445
+ return {
1446
+ redactor: options.redactInputs !== void 0 || options.redactOutputs !== void 0 ? createPiiRedactor(options.redaction) : void 0,
1447
+ redactInputs: options.redactInputs,
1448
+ redactOutputs: options.redactOutputs,
1449
+ captureMode: options.captureMode ?? "safe",
1450
+ captureMaxBytes: validateCaptureMaxBytes(options.captureMaxBytes)
1451
+ };
1452
+ }
1453
+ function captureInput(value, capture) {
1454
+ const redacted = capture.redactor === void 0 || capture.redactInputs === void 0 ? value : applyRedaction(capture.redactor, value, capture.redactInputs);
1455
+ return sanitizeTraceValue(redacted, capture.captureMaxBytes);
1456
+ }
1457
+ function scoreMaxAttempts(retries) {
1458
+ if (retries === void 0) return 3;
1459
+ if (!Number.isSafeInteger(retries.maxAttempts) || retries.maxAttempts < 1) {
1460
+ throw new TypeError("Langfuse score retries.maxAttempts must be a positive integer.");
1461
+ }
1462
+ return retries.maxAttempts;
1463
+ }
1300
1464
  function assertScoreValue(value, dataType) {
1301
1465
  if (dataType === "NUMERIC") {
1302
1466
  if (typeof value !== "number") {
@@ -1436,20 +1600,15 @@ function asMetadata(value) {
1436
1600
  return isRecord(value) ? value : { value };
1437
1601
  }
1438
1602
  function eventStartTime(value) {
1439
- if (value instanceof Date) {
1440
- return Number.isNaN(value.getTime()) ? void 0 : value;
1441
- }
1442
- if (typeof value !== "string") {
1443
- return void 0;
1444
- }
1603
+ if (value === void 0) return void 0;
1445
1604
  const parsed = new Date(value);
1446
1605
  return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
1447
1606
  }
1448
1607
  var LangfuseRunObserver = class {
1449
- constructor(root, trace, promptRef, redaction) {
1608
+ constructor(root, observations, trace2, promptRef, redaction) {
1450
1609
  this.root = root;
1451
- this.trace = trace;
1452
- this.handle = this.buildHandle();
1610
+ this.observations = observations;
1611
+ this.trace = trace2;
1453
1612
  this.promptRef = promptRef;
1454
1613
  this.redactor = redaction.redactor;
1455
1614
  this.redactInputs = redaction.redactInputs;
@@ -1458,13 +1617,9 @@ var LangfuseRunObserver = class {
1458
1617
  this.captureMaxBytes = redaction.captureMaxBytes;
1459
1618
  }
1460
1619
  root;
1620
+ observations;
1461
1621
  trace;
1462
1622
  turnSpans = /* @__PURE__ */ new Map();
1463
- // Assigned by LangfuseAgentObserver.startRun so that the run can
1464
- // publish trace-handle updates back to the agent observer.
1465
- setCurrentHandle;
1466
- clearCurrentHandle;
1467
- handle;
1468
1623
  promptRef;
1469
1624
  redactor;
1470
1625
  redactInputs;
@@ -1491,7 +1646,7 @@ var LangfuseRunObserver = class {
1491
1646
  safeInput.tools = args.request.tools;
1492
1647
  safeInput.providerTools = args.request.providerTools;
1493
1648
  safeInput.outputSchema = args.request.outputSchema;
1494
- safeInput.additionalParams = args.request.additionalParams;
1649
+ safeInput.providerOptions = args.request.providerOptions;
1495
1650
  }
1496
1651
  const metadata = {
1497
1652
  turn: args.turn,
@@ -1499,7 +1654,7 @@ var LangfuseRunObserver = class {
1499
1654
  toolNames: args.request.tools.map((tool) => tool.name),
1500
1655
  providerToolNames: args.request.providerTools?.map((tool) => tool.name) ?? [],
1501
1656
  hasOutputSchema: args.request.outputSchema !== void 0,
1502
- additionalParamKeys: isRecord(args.request.additionalParams) ? Object.keys(args.request.additionalParams) : []
1657
+ providerOptionKeys: isRecord(args.request.providerOptions) ? Object.keys(args.request.providerOptions) : []
1503
1658
  };
1504
1659
  if (this.captureMode === "full" && args.providerRequest !== void 0) {
1505
1660
  metadata.providerRequest = args.providerRequest;
@@ -1507,7 +1662,7 @@ var LangfuseRunObserver = class {
1507
1662
  if (args.modelInfo !== void 0) {
1508
1663
  const modelInfo = {
1509
1664
  provider: args.modelInfo.provider,
1510
- defaultModel: args.modelInfo.defaultModel
1665
+ modelId: args.modelInfo.modelId
1511
1666
  };
1512
1667
  if (args.modelInfo.capabilities !== void 0) {
1513
1668
  modelInfo.capabilities = args.modelInfo.capabilities;
@@ -1517,7 +1672,7 @@ var LangfuseRunObserver = class {
1517
1672
  Object.assign(metadata, promptMetadata(this.promptRef));
1518
1673
  const generationAttributes = {
1519
1674
  input: this.redactInputValue(safeInput),
1520
- model: args.request.model ?? args.modelInfo?.defaultModel ?? "default",
1675
+ model: args.modelInfo?.modelId ?? "unknown",
1521
1676
  modelParameters: modelParameters(args.request),
1522
1677
  metadata: asMetadata(this.redactInputValue(metadata))
1523
1678
  };
@@ -1528,9 +1683,11 @@ var LangfuseRunObserver = class {
1528
1683
  isFallback: false
1529
1684
  };
1530
1685
  }
1531
- const generation = turn.startObservation(`model.turn.${args.turn}`, generationAttributes, {
1532
- asType: "generation"
1533
- });
1686
+ const generation = this.observations.generation(
1687
+ `model.turn.${args.turn}`,
1688
+ generationAttributes,
1689
+ turn
1690
+ );
1534
1691
  return new LangfuseGenerationObserver(generation, this, /* @__PURE__ */ new Date());
1535
1692
  }
1536
1693
  startTool(args) {
@@ -1544,7 +1701,7 @@ var LangfuseRunObserver = class {
1544
1701
  if (args.toolDefinition !== void 0) metadata.toolDefinition = args.toolDefinition;
1545
1702
  if (args.toolMetadata !== void 0) metadata.toolMetadata = args.toolMetadata;
1546
1703
  }
1547
- const tool = turn.startObservation(
1704
+ const tool = this.observations.tool(
1548
1705
  `tool.${args.toolName}`,
1549
1706
  {
1550
1707
  input: this.redactInputValue({
@@ -1553,18 +1710,22 @@ var LangfuseRunObserver = class {
1553
1710
  }),
1554
1711
  metadata: asMetadata(this.redactInputValue(metadata))
1555
1712
  },
1556
- { asType: "tool" }
1713
+ turn
1557
1714
  );
1558
- return new LangfuseToolObserver(tool, this);
1715
+ return new LangfuseToolObserver(tool, this, this.observations);
1559
1716
  }
1560
1717
  end(args) {
1561
1718
  this.closeAllTurns();
1562
- const redactedOutput = this.redactOutputValue(args.output);
1719
+ const observedOutput = args.status === "completed" ? { status: args.status, output: args.output, text: args.text } : args.status === "blocked" ? { status: args.status, stage: args.stage, text: args.text } : { status: args.status, interaction: args.interaction, text: args.text };
1720
+ const redactedOutput = this.redactOutputValue(observedOutput);
1563
1721
  const metadata = {
1722
+ runId: args.runId,
1723
+ status: args.status,
1564
1724
  usage: args.usage,
1565
1725
  messageCount: args.messages.length,
1566
1726
  sources: this.redactOutputValue(args.sources),
1567
- providerToolCalls: this.redactOutputValue(args.providerToolCalls)
1727
+ providerToolCalls: this.redactOutputValue(args.providerToolCalls),
1728
+ resumedFrom: this.redactOutputValue(args.resumedFrom)
1568
1729
  };
1569
1730
  if (this.captureMode === "full") {
1570
1731
  metadata.messages = this.redactTranscript(args.messages);
@@ -1573,7 +1734,6 @@ var LangfuseRunObserver = class {
1573
1734
  output: redactedOutput,
1574
1735
  metadata
1575
1736
  }).end();
1576
- this.clearCurrentHandle?.();
1577
1737
  }
1578
1738
  error(args) {
1579
1739
  this.closeAllTurns();
@@ -1593,7 +1753,6 @@ var LangfuseRunObserver = class {
1593
1753
  },
1594
1754
  metadata
1595
1755
  }).end();
1596
- this.clearCurrentHandle?.();
1597
1756
  }
1598
1757
  event(args) {
1599
1758
  const metadata = asMetadata(this.redactOutputValue(args.attributes ?? {}));
@@ -1602,43 +1761,19 @@ var LangfuseRunObserver = class {
1602
1761
  attributes.level = args.level;
1603
1762
  }
1604
1763
  const startTime = eventStartTime(args.timestamp);
1605
- this.root.startObservation(args.name, attributes, {
1606
- asType: "event",
1607
- ...startTime === void 0 ? {} : { startTime }
1608
- });
1609
- }
1610
- getHandle() {
1611
- return this.handle;
1612
- }
1613
- buildHandle() {
1614
- return {
1615
- traceId: this.trace.traceId ?? "",
1616
- observationId: this.trace.observationId ?? "",
1617
- addAttributes: (attributes) => {
1618
- this.root.update({ metadata: asMetadata(this.redactOutputValue(attributes)) });
1619
- this.setCurrentHandle?.(this.handle);
1620
- },
1621
- addEvent: (name, attributes) => {
1622
- this.root.startObservation(
1623
- name,
1624
- { metadata: asMetadata(this.redactOutputValue(attributes ?? {})) },
1625
- { asType: "event" }
1626
- );
1627
- this.setCurrentHandle?.(this.handle);
1628
- }
1629
- };
1764
+ this.observations.event(args.name, attributes, this.root, startTime);
1630
1765
  }
1631
1766
  turnSpan(turn) {
1632
1767
  const existing = this.turnSpans.get(turn);
1633
1768
  if (existing !== void 0) {
1634
1769
  return existing;
1635
1770
  }
1636
- const span = this.root.startObservation(
1771
+ const span = this.observations.span(
1637
1772
  `turn.${turn}`,
1638
1773
  {
1639
1774
  metadata: { turn }
1640
1775
  },
1641
- { asType: "span" }
1776
+ this.root
1642
1777
  );
1643
1778
  this.turnSpans.set(turn, span);
1644
1779
  return span;
@@ -1719,12 +1854,14 @@ function textFromObservedAssistantContent(content) {
1719
1854
  return content.flatMap((item) => item.type === "text" ? [item.text] : []).join("\n");
1720
1855
  }
1721
1856
  var LangfuseToolObserver = class {
1722
- constructor(tool, run) {
1857
+ constructor(tool, run, observations) {
1723
1858
  this.tool = tool;
1724
1859
  this.run = run;
1860
+ this.observations = observations;
1725
1861
  }
1726
1862
  tool;
1727
1863
  run;
1864
+ observations;
1728
1865
  childAgents = /* @__PURE__ */ new Map();
1729
1866
  childGenerations = /* @__PURE__ */ new Map();
1730
1867
  childTools = [];
@@ -1741,7 +1878,7 @@ var LangfuseToolObserver = class {
1741
1878
  if (child.type === "turn_start") {
1742
1879
  const promptMessage = isRecord(child.prompt) ? child.prompt : void 0;
1743
1880
  const historyMessages = Array.isArray(child.history) ? child.history.filter(isRecord) : [];
1744
- agent.startObservation(
1881
+ this.observations.event(
1745
1882
  `${agentLabel(agentId, agentName)}.turn.${childTurn}.start`,
1746
1883
  {
1747
1884
  input: this.run.redactInputValue({
@@ -1752,7 +1889,7 @@ var LangfuseToolObserver = class {
1752
1889
  this.run.redactInputValue(childMetadata(args, agentId, agentName, childTurn))
1753
1890
  )
1754
1891
  },
1755
- { asType: "event" }
1892
+ agent
1756
1893
  );
1757
1894
  return;
1758
1895
  }
@@ -1769,15 +1906,15 @@ var LangfuseToolObserver = class {
1769
1906
  input.tools = request.tools;
1770
1907
  input.providerTools = request.providerTools;
1771
1908
  input.outputSchema = request.outputSchema;
1772
- input.additionalParams = request.additionalParams;
1909
+ input.providerOptions = request.providerOptions;
1773
1910
  }
1774
1911
  const toolNames = Array.isArray(request.tools) ? request.tools.filter(isRecord).map((tool) => tool.name).filter((name) => typeof name === "string") : [];
1775
1912
  const providerToolNames = Array.isArray(request.providerTools) ? request.providerTools.filter(isRecord).map((tool) => tool.name).filter((name) => typeof name === "string") : [];
1776
- const generation = agent.startObservation(
1913
+ const generation = this.observations.generation(
1777
1914
  `${agentLabel(agentId, agentName)}.model.turn.${childTurn}`,
1778
1915
  {
1779
1916
  input: this.run.redactInputValue(input),
1780
- model: typeof request.model === "string" ? request.model : typeof modelInfo?.defaultModel === "string" ? modelInfo.defaultModel : "default",
1917
+ model: typeof modelInfo?.modelId === "string" ? modelInfo.modelId : "unknown",
1781
1918
  modelParameters: modelParameters(request),
1782
1919
  metadata: asMetadata(
1783
1920
  this.run.redactInputValue({
@@ -1790,7 +1927,7 @@ var LangfuseToolObserver = class {
1790
1927
  })
1791
1928
  )
1792
1929
  },
1793
- { asType: "generation" }
1930
+ agent
1794
1931
  );
1795
1932
  this.childGenerations.set(generationKey(agentId, childTurn), {
1796
1933
  generation,
@@ -1830,7 +1967,7 @@ var LangfuseToolObserver = class {
1830
1967
  if (child.type === "source" || child.type === "provider_tool_call") {
1831
1968
  const childGeneration = this.childGenerations.get(generationKey(agentId, childTurn));
1832
1969
  const parent = childGeneration?.generation ?? agent;
1833
- parent.startObservation(
1970
+ this.observations.event(
1834
1971
  `${agentLabel(agentId, agentName)}.${child.type}`,
1835
1972
  {
1836
1973
  output: this.run.redactOutputValue(
@@ -1840,12 +1977,12 @@ var LangfuseToolObserver = class {
1840
1977
  this.run.redactOutputValue(childMetadata(args, agentId, agentName, childTurn))
1841
1978
  )
1842
1979
  },
1843
- { asType: "event" }
1980
+ parent
1844
1981
  );
1845
1982
  return;
1846
1983
  }
1847
1984
  if (child.type === "guardrail_decision") {
1848
- agent.startObservation(
1985
+ this.observations.guardrail(
1849
1986
  `${agentLabel(agentId, agentName)}.guardrail`,
1850
1987
  {
1851
1988
  output: this.run.redactOutputValue(child.decision),
@@ -1853,7 +1990,7 @@ var LangfuseToolObserver = class {
1853
1990
  this.run.redactOutputValue(childMetadata(args, agentId, agentName, childTurn))
1854
1991
  )
1855
1992
  },
1856
- { asType: "guardrail" }
1993
+ agent
1857
1994
  ).end();
1858
1995
  return;
1859
1996
  }
@@ -1863,14 +2000,13 @@ var LangfuseToolObserver = class {
1863
2000
  output: this.run.redactOutputValue({ toolCall: child.toolCall })
1864
2001
  });
1865
2002
  const toolCall = child.toolCall;
1866
- const toolCallFunction = isRecord(toolCall.function) ? toolCall.function : void 0;
1867
- const toolName = typeof toolCallFunction?.name === "string" ? toolCallFunction.name : "tool";
1868
- const toolCallId = typeof toolCall.callId === "string" ? toolCall.callId : typeof toolCall.id === "string" ? toolCall.id : void 0;
1869
- const childTool = agent.startObservation(
2003
+ const toolName = typeof toolCall.toolName === "string" ? toolCall.toolName : "tool";
2004
+ const toolCallId = typeof toolCall.toolCallId === "string" ? toolCall.toolCallId : typeof toolCall.callId === "string" ? toolCall.callId : void 0;
2005
+ const childTool = this.observations.tool(
1870
2006
  `${agentLabel(agentId, agentName)}.${toolName}`,
1871
2007
  {
1872
2008
  input: this.run.redactInputValue({
1873
- args: toolCallFunction?.arguments ?? {},
2009
+ args: toolCall.input,
1874
2010
  toolCall
1875
2011
  }),
1876
2012
  metadata: asMetadata(
@@ -1881,7 +2017,7 @@ var LangfuseToolObserver = class {
1881
2017
  })
1882
2018
  )
1883
2019
  },
1884
- { asType: "tool" }
2020
+ agent
1885
2021
  );
1886
2022
  const childToolRecord = {
1887
2023
  agentId,
@@ -1917,13 +2053,18 @@ var LangfuseToolObserver = class {
1917
2053
  return;
1918
2054
  }
1919
2055
  if (child.type === "final") {
2056
+ const result = isRecord(child.result) ? child.result : {};
1920
2057
  const update = {
1921
- output: this.run.redactOutputValue(child.output)
2058
+ output: this.run.redactOutputValue({
2059
+ status: result.status,
2060
+ output: result.output,
2061
+ text: result.text
2062
+ })
1922
2063
  };
1923
2064
  const metadata = {};
1924
- if (isRecord(child.usage)) metadata.usage = child.usage;
1925
- if (this.run.isFullCapture() && Array.isArray(child.messages)) {
1926
- metadata.messages = this.run.redactTranscript(child.messages);
2065
+ if (isRecord(result.usage)) metadata.usage = result.usage;
2066
+ if (this.run.isFullCapture() && Array.isArray(result.messages)) {
2067
+ metadata.messages = this.run.redactTranscript(result.messages);
1927
2068
  }
1928
2069
  if (Object.keys(metadata).length > 0) update.metadata = metadata;
1929
2070
  agent.update(update).end();
@@ -1970,6 +2111,23 @@ var LangfuseToolObserver = class {
1970
2111
  }
1971
2112
  this.tool.update(attributes).end();
1972
2113
  }
2114
+ suspend(args) {
2115
+ this.endOpenChildren();
2116
+ this.tool.update({
2117
+ output: this.run.redactOutputValue({
2118
+ status: "suspended",
2119
+ interaction: args.interaction
2120
+ }),
2121
+ metadata: {
2122
+ turn: args.turn,
2123
+ internalCallId: args.internalCallId,
2124
+ interactionId: args.interaction.id,
2125
+ interactionType: args.interaction.type
2126
+ },
2127
+ level: "WARNING",
2128
+ statusMessage: "Tool call suspended for human interaction"
2129
+ }).end();
2130
+ }
1973
2131
  error(args) {
1974
2132
  this.endOpenChildren();
1975
2133
  const redactedError = this.run.redactOutputValue(errorMessage(args.error));
@@ -1989,14 +2147,14 @@ var LangfuseToolObserver = class {
1989
2147
  if (existing !== void 0) {
1990
2148
  return existing;
1991
2149
  }
1992
- const agent = this.tool.startObservation(
2150
+ const agent = this.observations.agent(
1993
2151
  `${agentLabel(agentId, agentName)}.run`,
1994
2152
  {
1995
2153
  metadata: asMetadata(
1996
2154
  this.run.redactInputValue(childMetadata(args, agentId, agentName, args.turn))
1997
2155
  )
1998
2156
  },
1999
- { asType: "agent" }
2157
+ this.tool
2000
2158
  );
2001
2159
  this.childAgents.set(agentId, agent);
2002
2160
  return agent;
@@ -2032,12 +2190,8 @@ var LangfuseToolObserver = class {
2032
2190
  };
2033
2191
  export {
2034
2192
  DEFAULT_PATTERNS,
2193
+ LangfuseClient,
2035
2194
  LangfuseScoreError,
2036
- createLangfuseDatasetClient,
2037
- createLangfuseEvalReporter,
2038
- createLangfusePromptClient,
2039
- createPiiRedactor,
2040
- langfuse,
2041
- runEvalAsExperiment
2195
+ createPiiRedactor
2042
2196
  };
2043
2197
  //# sourceMappingURL=index.js.map