@anyway-sh/node-server-sdk 0.22.8

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 (56) hide show
  1. package/README +35 -0
  2. package/dist/index.d.ts +1957 -0
  3. package/dist/index.js +4458 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/index.mjs +4382 -0
  6. package/dist/src/index.d.ts +10 -0
  7. package/dist/src/lib/associations/associations.d.ts +32 -0
  8. package/dist/src/lib/client/annotation/base-annotation.d.ts +20 -0
  9. package/dist/src/lib/client/annotation/user-feedback.d.ts +32 -0
  10. package/dist/src/lib/client/dataset/attachment-uploader.d.ts +50 -0
  11. package/dist/src/lib/client/dataset/attachment.d.ts +84 -0
  12. package/dist/src/lib/client/dataset/base-dataset.d.ts +10 -0
  13. package/dist/src/lib/client/dataset/column.d.ts +23 -0
  14. package/dist/src/lib/client/dataset/dataset.d.ts +43 -0
  15. package/dist/src/lib/client/dataset/datasets.d.ts +14 -0
  16. package/dist/src/lib/client/dataset/index.d.ts +8 -0
  17. package/dist/src/lib/client/dataset/row.d.ts +73 -0
  18. package/dist/src/lib/client/evaluator/evaluator.d.ts +28 -0
  19. package/dist/src/lib/client/evaluator/index.d.ts +2 -0
  20. package/dist/src/lib/client/experiment/experiment.d.ts +76 -0
  21. package/dist/src/lib/client/experiment/index.d.ts +2 -0
  22. package/dist/src/lib/client/traceloop-client.d.ts +40 -0
  23. package/dist/src/lib/configuration/index.d.ts +35 -0
  24. package/dist/src/lib/configuration/validation.d.ts +3 -0
  25. package/dist/src/lib/errors/index.d.ts +36 -0
  26. package/dist/src/lib/generated/evaluators/index.d.ts +5 -0
  27. package/dist/src/lib/generated/evaluators/mbt-evaluators.d.ts +386 -0
  28. package/dist/src/lib/generated/evaluators/registry.d.ts +12 -0
  29. package/dist/src/lib/generated/evaluators/types.d.ts +401 -0
  30. package/dist/src/lib/images/image-uploader.d.ts +15 -0
  31. package/dist/src/lib/images/index.d.ts +2 -0
  32. package/dist/src/lib/interfaces/annotations.interface.d.ts +35 -0
  33. package/dist/src/lib/interfaces/dataset.interface.d.ts +105 -0
  34. package/dist/src/lib/interfaces/evaluator.interface.d.ts +83 -0
  35. package/dist/src/lib/interfaces/experiment.interface.d.ts +117 -0
  36. package/dist/src/lib/interfaces/index.d.ts +8 -0
  37. package/dist/src/lib/interfaces/initialize-options.interface.d.ts +133 -0
  38. package/dist/src/lib/interfaces/prompts.interface.d.ts +53 -0
  39. package/dist/src/lib/interfaces/traceloop-client.interface.d.ts +7 -0
  40. package/dist/src/lib/node-server-sdk.d.ts +19 -0
  41. package/dist/src/lib/prompts/fetch.d.ts +3 -0
  42. package/dist/src/lib/prompts/index.d.ts +3 -0
  43. package/dist/src/lib/prompts/registry.d.ts +9 -0
  44. package/dist/src/lib/prompts/template.d.ts +3 -0
  45. package/dist/src/lib/tracing/ai-sdk-transformations.d.ts +5 -0
  46. package/dist/src/lib/tracing/association.d.ts +4 -0
  47. package/dist/src/lib/tracing/baggage-utils.d.ts +2 -0
  48. package/dist/src/lib/tracing/custom-metric.d.ts +14 -0
  49. package/dist/src/lib/tracing/decorators.d.ts +22 -0
  50. package/dist/src/lib/tracing/index.d.ts +14 -0
  51. package/dist/src/lib/tracing/manual.d.ts +60 -0
  52. package/dist/src/lib/tracing/sampler.d.ts +7 -0
  53. package/dist/src/lib/tracing/span-processor.d.ts +48 -0
  54. package/dist/src/lib/tracing/tracing.d.ts +10 -0
  55. package/dist/src/lib/utils/response-transformer.d.ts +19 -0
  56. package/package.json +127 -0
package/dist/index.js ADDED
@@ -0,0 +1,4458 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+ var mime = require('mime-types');
6
+ var Papa = require('papaparse');
7
+ var sdkNode = require('@opentelemetry/sdk-node');
8
+ var api = require('@opentelemetry/api');
9
+ var exporterTraceOtlpProto = require('@opentelemetry/exporter-trace-otlp-proto');
10
+ var opentelemetryCloudTraceExporter = require('@google-cloud/opentelemetry-cloud-trace-exporter');
11
+ var semanticConventions = require('@opentelemetry/semantic-conventions');
12
+ var aiSemanticConventions = require('@traceloop/ai-semantic-conventions');
13
+ var instrumentationAnthropic = require('@traceloop/instrumentation-anthropic');
14
+ var instrumentationOpenai = require('@traceloop/instrumentation-openai');
15
+ var instrumentationLlamaindex = require('@traceloop/instrumentation-llamaindex');
16
+ var instrumentationVertexai = require('@traceloop/instrumentation-vertexai');
17
+ var instrumentationBedrock = require('@traceloop/instrumentation-bedrock');
18
+ var instrumentationCohere = require('@traceloop/instrumentation-cohere');
19
+ var instrumentationPinecone = require('@traceloop/instrumentation-pinecone');
20
+ var instrumentationLangchain = require('@traceloop/instrumentation-langchain');
21
+ var instrumentationChromadb = require('@traceloop/instrumentation-chromadb');
22
+ var instrumentationQdrant = require('@traceloop/instrumentation-qdrant');
23
+ var instrumentationTogether = require('@traceloop/instrumentation-together');
24
+ var instrumentationMcp = require('@traceloop/instrumentation-mcp');
25
+ var sdkTraceNode = require('@opentelemetry/sdk-trace-node');
26
+ var incubating = require('@opentelemetry/semantic-conventions/incubating');
27
+ var fetch$1 = require('cross-fetch');
28
+ var fetchBuilder = require('fetch-retry');
29
+ var core = require('@opentelemetry/core');
30
+ var nunjucks = require('nunjucks');
31
+
32
+ function _interopNamespaceDefault(e) {
33
+ var n = Object.create(null);
34
+ if (e) {
35
+ Object.keys(e).forEach(function (k) {
36
+ if (k !== 'default') {
37
+ var d = Object.getOwnPropertyDescriptor(e, k);
38
+ Object.defineProperty(n, k, d.get ? d : {
39
+ enumerable: true,
40
+ get: function () { return e[k]; }
41
+ });
42
+ }
43
+ });
44
+ }
45
+ n.default = e;
46
+ return Object.freeze(n);
47
+ }
48
+
49
+ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
50
+ var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
51
+ var mime__namespace = /*#__PURE__*/_interopNamespaceDefault(mime);
52
+ var Papa__namespace = /*#__PURE__*/_interopNamespaceDefault(Papa);
53
+
54
+ /**
55
+ * The severity of an error.
56
+ */
57
+ const SEVERITY = {
58
+ Warning: "Warning",
59
+ Error: "Error",
60
+ Critical: "Critical",
61
+ };
62
+ /**
63
+ * Base class for all Traceloop errors.
64
+ */
65
+ class TraceloopError extends Error {
66
+ constructor(message, severity = SEVERITY.Error) {
67
+ super(message);
68
+ this.severity = severity;
69
+ }
70
+ }
71
+ class NotInitializedError extends TraceloopError {
72
+ constructor() {
73
+ super(`The Traceloop SDK must be initialized by calling the "initialize" function prior to use.`, SEVERITY.Critical);
74
+ }
75
+ }
76
+ class InitializationError extends TraceloopError {
77
+ constructor(message, cause) {
78
+ super(message !== null && message !== void 0 ? message : "Failed to initialize Traceloop SDK", SEVERITY.Critical);
79
+ this.underlyingCause = cause;
80
+ }
81
+ }
82
+ class ArgumentNotProvidedError extends TraceloopError {
83
+ constructor(argumentName) {
84
+ super(`The "${argumentName}" argument is required and must be a string.`);
85
+ }
86
+ }
87
+ class PromptNotFoundError extends TraceloopError {
88
+ constructor(key) {
89
+ super(`The prompt "${key}" was not found in the registry.`);
90
+ }
91
+ }
92
+
93
+ var version = "0.22.8";
94
+
95
+ /**
96
+ * Base class for handling annotation operations with the Traceloop API.
97
+ * @internal
98
+ */
99
+ class BaseAnnotation {
100
+ constructor(client, flow) {
101
+ this.client = client;
102
+ this.flow = flow;
103
+ }
104
+ /**
105
+ * Creates a new annotation.
106
+ *
107
+ * @param options - The annotation creation options
108
+ * @returns Promise resolving to the fetch Response
109
+ */
110
+ async create(options) {
111
+ return await this.client.post(`/v2/annotation-tasks/${options.annotationTask}/annotations`, {
112
+ entity_instance_id: options.entity.id,
113
+ tags: options.tags,
114
+ source: "sdk",
115
+ flow: this.flow,
116
+ actor: {
117
+ type: "service",
118
+ id: this.client.appName,
119
+ },
120
+ });
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Handles user feedback annotations with the Traceloop API.
126
+ */
127
+ class UserFeedback extends BaseAnnotation {
128
+ constructor(client) {
129
+ super(client, "user_feedback");
130
+ }
131
+ /**
132
+ * Creates a new annotation for a specific task and entity.
133
+ *
134
+ * @param options - The options for creating an annotation
135
+ * @returns Promise resolving to the fetch Response
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * await client.annotation.create({
140
+ * annotationTask: 'sample-annotation-task',
141
+ * entity: {
142
+ * id: '123456',
143
+ * },
144
+ * tags: {
145
+ * sentiment: 'positive',
146
+ * score: 0.85,
147
+ * tones: ['happy', 'surprised']
148
+ * }
149
+ * });
150
+ * ```
151
+ */
152
+ async create(options) {
153
+ return await super.create(options);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Utility functions for transforming API responses from snake_case to camelCase
159
+ */
160
+ /**
161
+ * Converts a snake_case string to camelCase
162
+ * @param str The snake_case string to convert
163
+ * @returns The camelCase version of the string
164
+ */
165
+ function snakeToCamel(str) {
166
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
167
+ }
168
+ /**
169
+ * Recursively transforms all snake_case keys in an object to camelCase
170
+ * @param obj The object to transform
171
+ * @returns A new object with camelCase keys
172
+ */
173
+ function transformResponseKeys(obj) {
174
+ if (obj === null || obj === undefined) {
175
+ return obj;
176
+ }
177
+ if (Array.isArray(obj)) {
178
+ return obj.map(transformResponseKeys);
179
+ }
180
+ if (typeof obj === "object" && obj.constructor === Object) {
181
+ const transformed = {};
182
+ for (const [key, value] of Object.entries(obj)) {
183
+ const camelKey = snakeToCamel(key);
184
+ transformed[camelKey] = transformResponseKeys(value);
185
+ }
186
+ return transformed;
187
+ }
188
+ return obj;
189
+ }
190
+ /**
191
+ * Transforms API response data by converting snake_case keys to camelCase
192
+ * This function is designed to be used in the BaseDataset.handleResponse() method
193
+ * to ensure consistent camelCase format throughout the SDK
194
+ *
195
+ * @param data The raw API response data
196
+ * @returns The transformed data with camelCase keys
197
+ */
198
+ function transformApiResponse(data) {
199
+ return transformResponseKeys(data);
200
+ }
201
+
202
+ class BaseDatasetEntity {
203
+ constructor(client) {
204
+ this.client = client;
205
+ }
206
+ async handleResponse(response) {
207
+ if (!response.ok) {
208
+ let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
209
+ try {
210
+ const errorData = await response.json();
211
+ if (errorData.error) {
212
+ errorMessage = errorData.error;
213
+ }
214
+ }
215
+ catch (_a) {
216
+ // Use default HTTP error message if JSON parsing fails
217
+ }
218
+ throw new Error(errorMessage);
219
+ }
220
+ const contentType = response.headers.get("content-type");
221
+ if (contentType && contentType.includes("application/json")) {
222
+ const rawData = await response.json();
223
+ return transformApiResponse(rawData);
224
+ }
225
+ // Handle non-JSON responses (text/csv, etc.)
226
+ const textContent = await response.text();
227
+ return {
228
+ contentType: contentType || "text/plain",
229
+ body: textContent,
230
+ };
231
+ }
232
+ validateDatasetId(id) {
233
+ if (!id || typeof id !== "string" || id.trim().length === 0) {
234
+ throw new Error("Dataset ID is required and must be a non-empty string");
235
+ }
236
+ }
237
+ validateDatasetSlug(slug) {
238
+ if (!slug || typeof slug !== "string" || slug.trim().length === 0) {
239
+ throw new Error("Dataset slug is required and must be a non-empty string");
240
+ }
241
+ }
242
+ validateDatasetName(name) {
243
+ if (!name || typeof name !== "string" || name.trim().length === 0) {
244
+ throw new Error("Dataset name is required and must be a non-empty string");
245
+ }
246
+ }
247
+ }
248
+
249
+ function detectFileType(contentType) {
250
+ if (contentType.startsWith("image/"))
251
+ return "image";
252
+ if (contentType.startsWith("video/"))
253
+ return "video";
254
+ if (contentType.startsWith("audio/"))
255
+ return "audio";
256
+ return "file";
257
+ }
258
+ function getMimeType(filename) {
259
+ return mime__namespace.lookup(filename) || "application/octet-stream";
260
+ }
261
+ class Attachment {
262
+ constructor(options) {
263
+ this.type = "attachment";
264
+ const { filePath, data, filename, contentType, fileType, metadata } = options;
265
+ if (!filePath && !data) {
266
+ throw new Error("Either filePath or data must be provided");
267
+ }
268
+ if (filePath && data) {
269
+ throw new Error("Cannot provide both filePath and data");
270
+ }
271
+ if (data && !filename) {
272
+ throw new Error("filename is required when using data");
273
+ }
274
+ this._filePath = filePath;
275
+ this._data = data;
276
+ this._filename = filename;
277
+ this._contentType = contentType;
278
+ this._fileType = fileType;
279
+ this._metadata = metadata;
280
+ }
281
+ async getData() {
282
+ if (this._data) {
283
+ return Buffer.isBuffer(this._data) ? this._data : Buffer.from(this._data);
284
+ }
285
+ if (this._filePath) {
286
+ return fs__namespace.promises.readFile(this._filePath);
287
+ }
288
+ throw new Error("No data source available");
289
+ }
290
+ getFileName() {
291
+ if (this._filename) {
292
+ return this._filename;
293
+ }
294
+ if (this._filePath) {
295
+ return path__namespace.basename(this._filePath);
296
+ }
297
+ throw new Error("No filename available");
298
+ }
299
+ getContentType() {
300
+ if (this._contentType) {
301
+ return this._contentType;
302
+ }
303
+ return getMimeType(this.getFileName());
304
+ }
305
+ get fileType() {
306
+ if (this._fileType) {
307
+ return this._fileType;
308
+ }
309
+ return detectFileType(this.getContentType());
310
+ }
311
+ get metadata() {
312
+ return this._metadata;
313
+ }
314
+ }
315
+ class ExternalAttachment {
316
+ constructor(options) {
317
+ this.type = "external";
318
+ const { url, filename, contentType, fileType, metadata } = options;
319
+ if (!url || typeof url !== "string") {
320
+ throw new Error("URL is required and must be a string");
321
+ }
322
+ try {
323
+ new URL(url);
324
+ }
325
+ catch (_a) {
326
+ throw new Error("Invalid URL provided");
327
+ }
328
+ this._url = url;
329
+ this._filename = filename;
330
+ this._contentType = contentType;
331
+ this._fileType = fileType || "file";
332
+ this._metadata = metadata;
333
+ }
334
+ get url() {
335
+ return this._url;
336
+ }
337
+ get filename() {
338
+ return this._filename;
339
+ }
340
+ get contentType() {
341
+ return this._contentType;
342
+ }
343
+ get fileType() {
344
+ return this._fileType;
345
+ }
346
+ get metadata() {
347
+ return this._metadata;
348
+ }
349
+ }
350
+ class AttachmentReference {
351
+ constructor(storageType, storageKey, url, fileType, metadata) {
352
+ this.storageType = storageType;
353
+ this.storageKey = storageKey;
354
+ this.url = url;
355
+ this.fileType = fileType;
356
+ this.metadata = metadata;
357
+ }
358
+ async download(filePath) {
359
+ if (!this.url) {
360
+ throw new Error("Cannot download attachment: no URL available");
361
+ }
362
+ const response = await fetch(this.url);
363
+ if (!response.ok) {
364
+ throw new Error(`Failed to download attachment: ${response.status} ${response.statusText}`);
365
+ }
366
+ const arrayBuffer = await response.arrayBuffer();
367
+ const buffer = Buffer.from(arrayBuffer);
368
+ if (filePath) {
369
+ await fs__namespace.promises.writeFile(filePath, buffer);
370
+ return;
371
+ }
372
+ return buffer;
373
+ }
374
+ getUrl() {
375
+ return this.url;
376
+ }
377
+ toJSON() {
378
+ return {
379
+ storageType: this.storageType,
380
+ storageKey: this.storageKey,
381
+ url: this.url,
382
+ fileType: this.fileType,
383
+ metadata: this.metadata,
384
+ };
385
+ }
386
+ }
387
+ function isAttachment(value) {
388
+ return value instanceof Attachment || (value === null || value === void 0 ? void 0 : value.type) === "attachment";
389
+ }
390
+ function isExternalAttachment(value) {
391
+ return (value instanceof ExternalAttachment || (value === null || value === void 0 ? void 0 : value.type) === "external");
392
+ }
393
+ function isAttachmentReference(value) {
394
+ return (value instanceof AttachmentReference ||
395
+ ((value === null || value === void 0 ? void 0 : value.storageType) !== undefined &&
396
+ (value === null || value === void 0 ? void 0 : value.storageKey) !== undefined));
397
+ }
398
+ function isAnyAttachment(value) {
399
+ return isAttachment(value) || isExternalAttachment(value);
400
+ }
401
+ const attachment = {
402
+ file: (filePath, options) => {
403
+ return new Attachment(Object.assign({ filePath }, options));
404
+ },
405
+ buffer: (data, filename, options) => {
406
+ return new Attachment(Object.assign({ data,
407
+ filename }, options));
408
+ },
409
+ url: (url, options) => {
410
+ return new ExternalAttachment(Object.assign({ url }, options));
411
+ },
412
+ };
413
+
414
+ /**
415
+ * Handles attachment upload and registration operations
416
+ */
417
+ class AttachmentUploader {
418
+ constructor(client) {
419
+ this.client = client;
420
+ }
421
+ /**
422
+ * Requests a presigned upload URL from the API
423
+ */
424
+ async getUploadUrl(datasetSlug, rowId, columnSlug, fileName, contentType, fileType, metadata) {
425
+ const response = await this.client.post(`/v2/datasets/${datasetSlug}/rows/${rowId}/cells/${columnSlug}/upload-url`, {
426
+ type: fileType,
427
+ file_name: fileName,
428
+ content_type: contentType,
429
+ metadata: metadata,
430
+ });
431
+ if (!response.ok) {
432
+ let errorMessage = `Failed to get upload URL: ${response.status} ${response.statusText}`;
433
+ try {
434
+ const errorData = await response.json();
435
+ if (errorData.error) {
436
+ errorMessage = errorData.error;
437
+ }
438
+ }
439
+ catch (_a) {
440
+ // Use default error message
441
+ }
442
+ throw new Error(errorMessage);
443
+ }
444
+ const data = await response.json();
445
+ return {
446
+ uploadUrl: data.upload_url,
447
+ storageKey: data.storage_key,
448
+ expiresAt: data.expires_at,
449
+ method: data.method || "PUT",
450
+ };
451
+ }
452
+ /**
453
+ * Uploads file data directly to S3 using the presigned URL
454
+ */
455
+ async uploadToS3(uploadUrl, data, contentType) {
456
+ const response = await fetch(uploadUrl, {
457
+ method: "PUT",
458
+ headers: {
459
+ "Content-Type": contentType,
460
+ },
461
+ body: new Uint8Array(data),
462
+ });
463
+ if (!response.ok) {
464
+ throw new Error(`Failed to upload to S3: ${response.status} ${response.statusText}`);
465
+ }
466
+ }
467
+ /**
468
+ * Confirms the upload status with the API
469
+ */
470
+ async confirmUpload(datasetSlug, rowId, columnSlug, status, metadata) {
471
+ const response = await this.client.put(`/v2/datasets/${datasetSlug}/rows/${rowId}/cells/${columnSlug}/upload-status`, {
472
+ status,
473
+ metadata,
474
+ });
475
+ if (!response.ok) {
476
+ let errorMessage = `Failed to confirm upload: ${response.status} ${response.statusText}`;
477
+ try {
478
+ const errorData = await response.json();
479
+ if (errorData.error) {
480
+ errorMessage = errorData.error;
481
+ }
482
+ }
483
+ catch (_a) {
484
+ // Use default error message
485
+ }
486
+ throw new Error(errorMessage);
487
+ }
488
+ }
489
+ /**
490
+ * Registers an external URL as an attachment
491
+ */
492
+ async registerExternalUrl(datasetSlug, rowId, columnSlug, url, fileType, metadata) {
493
+ const response = await this.client.post(`/v2/datasets/${datasetSlug}/rows/${rowId}/cells/${columnSlug}/external-url`, {
494
+ type: fileType,
495
+ url,
496
+ metadata,
497
+ });
498
+ if (!response.ok) {
499
+ let errorMessage = `Failed to register external URL: ${response.status} ${response.statusText}`;
500
+ try {
501
+ const errorData = await response.json();
502
+ if (errorData.error) {
503
+ errorMessage = errorData.error;
504
+ }
505
+ }
506
+ catch (_a) {
507
+ // Use default error message
508
+ }
509
+ throw new Error(errorMessage);
510
+ }
511
+ const data = await response.json();
512
+ return new AttachmentReference("external", data.storage_key || "", url, fileType, metadata);
513
+ }
514
+ /**
515
+ * Uploads an Attachment through the full flow:
516
+ * 1. Get presigned URL
517
+ * 2. Upload to S3
518
+ * 3. Confirm upload
519
+ */
520
+ async uploadAttachment(datasetSlug, rowId, columnSlug, attachment) {
521
+ const fileName = attachment.getFileName();
522
+ const contentType = attachment.getContentType();
523
+ const fileType = attachment.fileType;
524
+ const data = await attachment.getData();
525
+ // Step 1: Get presigned upload URL
526
+ const uploadInfo = await this.getUploadUrl(datasetSlug, rowId, columnSlug, fileName, contentType, fileType, attachment.metadata);
527
+ // Step 2: Upload to S3
528
+ try {
529
+ await this.uploadToS3(uploadInfo.uploadUrl, data, contentType);
530
+ }
531
+ catch (error) {
532
+ // Confirm failure and re-throw
533
+ try {
534
+ await this.confirmUpload(datasetSlug, rowId, columnSlug, "failed", attachment.metadata);
535
+ }
536
+ catch (_a) {
537
+ // Ignore confirmation errors
538
+ }
539
+ throw error;
540
+ }
541
+ // Step 3: Confirm success
542
+ await this.confirmUpload(datasetSlug, rowId, columnSlug, "success", attachment.metadata);
543
+ return new AttachmentReference("internal", uploadInfo.storageKey, undefined, fileType, attachment.metadata);
544
+ }
545
+ /**
546
+ * Processes an ExternalAttachment by registering the URL
547
+ */
548
+ async processExternalAttachment(datasetSlug, rowId, columnSlug, attachment) {
549
+ return this.registerExternalUrl(datasetSlug, rowId, columnSlug, attachment.url, attachment.fileType, attachment.metadata);
550
+ }
551
+ /**
552
+ * Processes any attachment type (Attachment or ExternalAttachment)
553
+ */
554
+ async processAnyAttachment(datasetSlug, rowId, columnSlug, attachmentObj) {
555
+ if (attachmentObj instanceof Attachment) {
556
+ return this.uploadAttachment(datasetSlug, rowId, columnSlug, attachmentObj);
557
+ }
558
+ else {
559
+ return this.processExternalAttachment(datasetSlug, rowId, columnSlug, attachmentObj);
560
+ }
561
+ }
562
+ }
563
+
564
+ class Row extends BaseDatasetEntity {
565
+ constructor(client, data) {
566
+ super(client);
567
+ this._deleted = false;
568
+ this._data = data;
569
+ }
570
+ get id() {
571
+ return this._data.id;
572
+ }
573
+ get datasetId() {
574
+ return this._data.datasetId;
575
+ }
576
+ get datasetSlug() {
577
+ return this._data.datasetSlug;
578
+ }
579
+ get data() {
580
+ return Object.assign({}, this._data.data);
581
+ }
582
+ get createdAt() {
583
+ return this._data.createdAt;
584
+ }
585
+ get updatedAt() {
586
+ return this._data.updatedAt;
587
+ }
588
+ get deleted() {
589
+ return this._deleted;
590
+ }
591
+ getValue(columnName) {
592
+ const value = this._data.data[columnName];
593
+ return value !== undefined ? value : null;
594
+ }
595
+ hasColumn(columnName) {
596
+ return columnName in this._data.data;
597
+ }
598
+ getColumns() {
599
+ return Object.keys(this._data.data);
600
+ }
601
+ async update(options) {
602
+ if (this._deleted) {
603
+ throw new Error("Cannot update a deleted row");
604
+ }
605
+ if (!options.data || typeof options.data !== "object") {
606
+ throw new Error("Update data must be a valid object");
607
+ }
608
+ const updatedData = Object.assign(Object.assign({}, this._data.data), options.data);
609
+ const response = await this.client.put(`/v2/datasets/${this.datasetSlug}/rows/${this.id}`, {
610
+ Values: updatedData,
611
+ });
612
+ const result = await this.handleResponse(response);
613
+ if (result && result.id) {
614
+ this._data = result;
615
+ }
616
+ }
617
+ async partialUpdate(updates) {
618
+ if (this._deleted) {
619
+ throw new Error("Cannot update a deleted row");
620
+ }
621
+ if (!updates || typeof updates !== "object") {
622
+ throw new Error("Updates must be a valid object");
623
+ }
624
+ const response = await this.client.put(`/v2/datasets/${this.datasetSlug}/rows/${this.id}`, {
625
+ Values: updates,
626
+ });
627
+ const result = await this.handleResponse(response);
628
+ if (result && result.id) {
629
+ this._data = result;
630
+ }
631
+ }
632
+ async delete() {
633
+ if (this._deleted) {
634
+ throw new Error("Row is already deleted");
635
+ }
636
+ const response = await this.client.delete(`/v2/datasets/${this.datasetSlug}/rows/${this.id}`);
637
+ await this.handleResponse(response);
638
+ this._deleted = true;
639
+ }
640
+ toJSON() {
641
+ return Object.assign({}, this._data.data);
642
+ }
643
+ toCSVRow(columns, delimiter = ",") {
644
+ const columnsToUse = columns || this.getColumns();
645
+ const values = columnsToUse.map((column) => {
646
+ const value = this._data.data[column];
647
+ if (value === null || value === undefined) {
648
+ return "";
649
+ }
650
+ const stringValue = String(value);
651
+ if (stringValue.includes(delimiter) ||
652
+ stringValue.includes('"') ||
653
+ stringValue.includes("\n")) {
654
+ return `"${stringValue.replace(/"/g, '""')}"`;
655
+ }
656
+ return stringValue;
657
+ });
658
+ return values.join(delimiter);
659
+ }
660
+ validate(columnValidators) {
661
+ const errors = [];
662
+ if (columnValidators) {
663
+ Object.keys(columnValidators).forEach((columnName) => {
664
+ const validator = columnValidators[columnName];
665
+ const value = this._data.data[columnName];
666
+ if (!validator(value)) {
667
+ errors.push(`Invalid value for column '${columnName}': ${value}`);
668
+ }
669
+ });
670
+ }
671
+ return {
672
+ valid: errors.length === 0,
673
+ errors,
674
+ };
675
+ }
676
+ clone() {
677
+ const clonedData = Object.assign(Object.assign({}, this._data), { data: Object.assign({}, this._data.data) });
678
+ return new Row(this.client, clonedData);
679
+ }
680
+ /**
681
+ * Gets an attachment reference from a column
682
+ * @param columnName The name of the column containing the attachment
683
+ * @returns AttachmentReference if the column contains an attachment, null otherwise
684
+ */
685
+ getAttachment(columnName) {
686
+ const value = this._data.data[columnName];
687
+ if (!value || typeof value !== "object") {
688
+ return null;
689
+ }
690
+ // Value is now guaranteed to be an object
691
+ const objValue = value;
692
+ // Check if value is already an AttachmentReference
693
+ if (objValue instanceof AttachmentReference) {
694
+ return objValue;
695
+ }
696
+ // Check if value is a serialized attachment reference
697
+ if (isAttachmentReference(objValue)) {
698
+ const ref = objValue;
699
+ return new AttachmentReference(ref.storageType, ref.storageKey, ref.url, ref.fileType, ref.metadata);
700
+ }
701
+ return null;
702
+ }
703
+ /**
704
+ * Checks if a column contains an attachment
705
+ * @param columnName The name of the column to check
706
+ */
707
+ hasAttachment(columnName) {
708
+ return this.getAttachment(columnName) !== null;
709
+ }
710
+ /**
711
+ * Sets/uploads an attachment to a column
712
+ * @param columnSlug The slug of the column to set the attachment in
713
+ * @param attachment The attachment to upload (Attachment or ExternalAttachment)
714
+ * @returns The created AttachmentReference
715
+ *
716
+ * @example
717
+ * // Upload from file
718
+ * await row.setAttachment("image", new Attachment({ filePath: "./photo.jpg" }));
719
+ *
720
+ * @example
721
+ * // Set external URL
722
+ * await row.setAttachment("document", new ExternalAttachment({ url: "https://example.com/doc.pdf" }));
723
+ */
724
+ async setAttachment(columnSlug, attachment) {
725
+ if (this._deleted) {
726
+ throw new Error("Cannot set attachment on a deleted row");
727
+ }
728
+ const uploader = new AttachmentUploader(this.client);
729
+ const reference = await uploader.processAnyAttachment(this.datasetSlug, this.id, columnSlug, attachment);
730
+ // Update internal data
731
+ this._data.data[columnSlug] = reference.toJSON();
732
+ return reference;
733
+ }
734
+ /**
735
+ * Downloads an attachment from a column
736
+ * @param columnName The name of the column containing the attachment
737
+ * @param outputPath Optional file path to save the downloaded file
738
+ * @returns Buffer if no outputPath provided, void if saved to file
739
+ *
740
+ * @example
741
+ * // Get as buffer
742
+ * const data = await row.downloadAttachment("image");
743
+ *
744
+ * @example
745
+ * // Save to file
746
+ * await row.downloadAttachment("image", "./downloaded-image.png");
747
+ */
748
+ async downloadAttachment(columnName, outputPath) {
749
+ if (this._deleted) {
750
+ throw new Error("Cannot download attachment from a deleted row");
751
+ }
752
+ const attachment = this.getAttachment(columnName);
753
+ if (!attachment) {
754
+ throw new Error(`No attachment found in column '${columnName}'`);
755
+ }
756
+ return attachment.download(outputPath);
757
+ }
758
+ }
759
+
760
+ class Column extends BaseDatasetEntity {
761
+ constructor(client, data) {
762
+ super(client);
763
+ this._deleted = false;
764
+ this._data = data;
765
+ }
766
+ get slug() {
767
+ return this._data.slug;
768
+ }
769
+ get name() {
770
+ return this._data.name;
771
+ }
772
+ get type() {
773
+ return this._data.type;
774
+ }
775
+ get required() {
776
+ return this._data.required || false;
777
+ }
778
+ get description() {
779
+ return this._data.description;
780
+ }
781
+ get datasetId() {
782
+ return this._data.datasetId;
783
+ }
784
+ get datasetSlug() {
785
+ return this._data.datasetSlug;
786
+ }
787
+ get createdAt() {
788
+ return this._data.createdAt;
789
+ }
790
+ get updatedAt() {
791
+ return this._data.updatedAt;
792
+ }
793
+ get deleted() {
794
+ return this._deleted;
795
+ }
796
+ async update(options) {
797
+ if (this._deleted) {
798
+ throw new Error("Cannot update a deleted column");
799
+ }
800
+ if (options.name && typeof options.name !== "string") {
801
+ throw new Error("Column name must be a string");
802
+ }
803
+ if (options.type &&
804
+ !["string", "number", "boolean", "date", "file"].includes(options.type)) {
805
+ throw new Error("Column type must be one of: string, number, boolean, date, file");
806
+ }
807
+ const response = await this.client.put(`/v2/datasets/${this.datasetSlug}/columns/${this.slug}`, options);
808
+ const data = await this.handleResponse(response);
809
+ // API returns dataset data, extract column info if available
810
+ if (data.columns && data.columns[this.slug]) {
811
+ const columnData = data.columns[this.slug];
812
+ // Update only the fields that changed, preserve datasetSlug and other metadata
813
+ this._data = Object.assign(Object.assign({}, this._data), { name: columnData.name || this._data.name, type: columnData.type || this._data.type, description: columnData.description || this._data.description, updatedAt: data.updatedAt || this._data.updatedAt });
814
+ }
815
+ }
816
+ async delete() {
817
+ if (this._deleted) {
818
+ throw new Error("Column is already deleted");
819
+ }
820
+ const response = await this.client.delete(`/v2/datasets/${this.datasetSlug}/columns/${this.slug}`);
821
+ await this.handleResponse(response);
822
+ this._deleted = true;
823
+ }
824
+ validateValue(value) {
825
+ if (this.required && (value === null || value === undefined)) {
826
+ return false;
827
+ }
828
+ if (value === null || value === undefined) {
829
+ return true;
830
+ }
831
+ switch (this.type) {
832
+ case "string":
833
+ return typeof value === "string";
834
+ case "number":
835
+ return typeof value === "number" && !isNaN(value) && isFinite(value);
836
+ case "boolean":
837
+ return typeof value === "boolean";
838
+ default:
839
+ return false;
840
+ }
841
+ }
842
+ convertValue(value) {
843
+ if (value === null || value === undefined) {
844
+ return null;
845
+ }
846
+ switch (this.type) {
847
+ case "string":
848
+ return String(value);
849
+ case "number": {
850
+ const numValue = Number(value);
851
+ return isNaN(numValue) ? null : numValue;
852
+ }
853
+ case "boolean":
854
+ if (typeof value === "boolean")
855
+ return value;
856
+ if (typeof value === "string") {
857
+ const lower = value.toLowerCase();
858
+ if (lower === "true" || lower === "1")
859
+ return true;
860
+ if (lower === "false" || lower === "0")
861
+ return false;
862
+ }
863
+ return Boolean(value);
864
+ default:
865
+ return value;
866
+ }
867
+ }
868
+ }
869
+
870
+ class Dataset extends BaseDatasetEntity {
871
+ constructor(client, data) {
872
+ super(client);
873
+ this._deleted = false;
874
+ this._data = data;
875
+ this._attachmentUploader = new AttachmentUploader(client);
876
+ }
877
+ get id() {
878
+ return this._data.id;
879
+ }
880
+ get slug() {
881
+ return this._data.slug;
882
+ }
883
+ get name() {
884
+ return this._data.name;
885
+ }
886
+ get description() {
887
+ return this._data.description;
888
+ }
889
+ get version() {
890
+ return this._data.version;
891
+ }
892
+ get published() {
893
+ return this._data.published || false;
894
+ }
895
+ get createdAt() {
896
+ return this._data.createdAt || "";
897
+ }
898
+ get updatedAt() {
899
+ return this._data.updatedAt || "";
900
+ }
901
+ get deleted() {
902
+ return this._deleted;
903
+ }
904
+ async update(options) {
905
+ if (this._deleted) {
906
+ throw new Error("Cannot update a deleted dataset");
907
+ }
908
+ if (options.name) {
909
+ this.validateDatasetName(options.name);
910
+ }
911
+ const response = await this.client.put(`/v2/datasets/${this.slug}`, options);
912
+ await this.handleResponse(response);
913
+ }
914
+ async delete() {
915
+ if (this._deleted) {
916
+ throw new Error("Dataset is already deleted");
917
+ }
918
+ const response = await this.client.delete(`/v2/datasets/${this.slug}`);
919
+ await this.handleResponse(response);
920
+ this._deleted = true;
921
+ }
922
+ async publish(options = {}) {
923
+ if (this._deleted) {
924
+ throw new Error("Cannot publish a deleted dataset");
925
+ }
926
+ const response = await this.client.post(`/v2/datasets/${this.slug}/publish`, options);
927
+ const data = await this.handleResponse(response);
928
+ this._data = data;
929
+ }
930
+ async addColumn(columns) {
931
+ if (this._deleted) {
932
+ throw new Error("Cannot add columns to a deleted dataset");
933
+ }
934
+ if (!Array.isArray(columns) || columns.length === 0) {
935
+ throw new Error("Columns must be a non-empty array");
936
+ }
937
+ const results = [];
938
+ for (const column of columns) {
939
+ if (!column.name || typeof column.name !== "string") {
940
+ throw new Error("Column name is required and must be a string");
941
+ }
942
+ const response = await this.client.post(`/v2/datasets/${this.slug}/columns`, column);
943
+ const data = await this.handleResponse(response);
944
+ if (!data || !data.slug) {
945
+ throw new Error("Failed to create column: Invalid API response");
946
+ }
947
+ const columnResponse = {
948
+ slug: data.slug,
949
+ datasetId: this._data.id,
950
+ datasetSlug: this.slug,
951
+ name: data.name,
952
+ type: data.type,
953
+ required: data.required,
954
+ description: data.description,
955
+ createdAt: data.createdAt,
956
+ updatedAt: data.updatedAt,
957
+ };
958
+ results.push(new Column(this.client, columnResponse));
959
+ }
960
+ return results;
961
+ }
962
+ async getColumns() {
963
+ if (this._deleted) {
964
+ throw new Error("Cannot get columns from a deleted dataset");
965
+ }
966
+ if (!this._data.columns) {
967
+ return [];
968
+ }
969
+ const columns = [];
970
+ for (const [columnSlug, columnData] of Object.entries(this._data.columns)) {
971
+ const col = columnData;
972
+ const columnResponse = {
973
+ slug: columnSlug,
974
+ datasetId: this._data.id,
975
+ datasetSlug: this.slug,
976
+ name: col.name,
977
+ type: col.type,
978
+ required: col.required === true,
979
+ description: col.description,
980
+ createdAt: this._data.createdAt || this.createdAt,
981
+ updatedAt: this._data.updatedAt || this.updatedAt,
982
+ };
983
+ columns.push(new Column(this.client, columnResponse));
984
+ }
985
+ return columns;
986
+ }
987
+ async addRow(rowData) {
988
+ if (this._deleted) {
989
+ throw new Error("Cannot add row to a deleted dataset");
990
+ }
991
+ if (!rowData || typeof rowData !== "object") {
992
+ throw new Error("Row data must be a valid object");
993
+ }
994
+ const rows = await this.addRows([rowData]);
995
+ if (rows.length === 0) {
996
+ throw new Error("Failed to add row");
997
+ }
998
+ return rows[0];
999
+ }
1000
+ async addRows(rows) {
1001
+ if (this._deleted) {
1002
+ throw new Error("Cannot add rows to a deleted dataset");
1003
+ }
1004
+ if (!Array.isArray(rows)) {
1005
+ throw new Error("Rows must be an array");
1006
+ }
1007
+ const columns = await this.getColumns();
1008
+ const columnMap = new Map();
1009
+ columns.forEach((col) => {
1010
+ columnMap.set(col.name, col.slug);
1011
+ });
1012
+ // Phase 1: Extract attachments and prepare clean rows
1013
+ const { cleanRows, attachmentMap } = this.extractAttachments(rows, columnMap);
1014
+ // Phase 2: Create rows with regular data (attachments replaced with null)
1015
+ const transformedRows = cleanRows.map((row) => {
1016
+ const transformedRow = {};
1017
+ Object.keys(row).forEach((columnName) => {
1018
+ const columnSlug = columnMap.get(columnName);
1019
+ if (columnSlug) {
1020
+ transformedRow[columnSlug] = row[columnName];
1021
+ }
1022
+ });
1023
+ return transformedRow;
1024
+ });
1025
+ const payload = {
1026
+ Rows: transformedRows,
1027
+ };
1028
+ const response = await this.client.post(`/v2/datasets/${this.slug}/rows`, payload);
1029
+ const result = await this.handleResponse(response);
1030
+ const createdRows = [];
1031
+ if (result.rows) {
1032
+ for (const row of result.rows) {
1033
+ const rowResponse = {
1034
+ id: row.id,
1035
+ datasetId: this._data.id,
1036
+ datasetSlug: this.slug,
1037
+ data: this.transformValuesBackToNames(row.values, columnMap),
1038
+ createdAt: row.created_at,
1039
+ updatedAt: row.updated_at,
1040
+ };
1041
+ createdRows.push(new Row(this.client, rowResponse));
1042
+ }
1043
+ }
1044
+ // Phase 3: Process attachments for created rows
1045
+ if (attachmentMap.size > 0) {
1046
+ await this.processAttachments(createdRows, attachmentMap, columnMap);
1047
+ }
1048
+ return createdRows;
1049
+ }
1050
+ /**
1051
+ * Extracts attachments from rows and returns clean rows with null values
1052
+ */
1053
+ extractAttachments(rows, columnMap) {
1054
+ const attachmentMap = new Map();
1055
+ const cleanRows = [];
1056
+ rows.forEach((row, rowIndex) => {
1057
+ const cleanRow = {};
1058
+ const rowAttachments = new Map();
1059
+ Object.keys(row).forEach((columnName) => {
1060
+ const value = row[columnName];
1061
+ const columnSlug = columnMap.get(columnName);
1062
+ if (isAnyAttachment(value) && columnSlug) {
1063
+ // Store attachment for later processing
1064
+ rowAttachments.set(columnSlug, value);
1065
+ // Replace with null in the clean row
1066
+ cleanRow[columnName] = null;
1067
+ }
1068
+ else {
1069
+ cleanRow[columnName] = value;
1070
+ }
1071
+ });
1072
+ cleanRows.push(cleanRow);
1073
+ if (rowAttachments.size > 0) {
1074
+ attachmentMap.set(rowIndex, rowAttachments);
1075
+ }
1076
+ });
1077
+ return { cleanRows, attachmentMap };
1078
+ }
1079
+ /**
1080
+ * Processes attachments for created rows
1081
+ */
1082
+ async processAttachments(rows, attachmentMap, columnMap) {
1083
+ // Create reverse map for slug to name lookup
1084
+ const reverseColumnMap = new Map();
1085
+ columnMap.forEach((slug, name) => {
1086
+ reverseColumnMap.set(slug, name);
1087
+ });
1088
+ for (const [rowIndex, rowAttachments] of attachmentMap) {
1089
+ const row = rows[rowIndex];
1090
+ if (!row)
1091
+ continue;
1092
+ for (const [columnSlug, attachment] of rowAttachments) {
1093
+ try {
1094
+ const reference = await this._attachmentUploader.processAnyAttachment(this.slug, row.id, columnSlug, attachment);
1095
+ // Update the row's internal data with the attachment reference
1096
+ const columnName = reverseColumnMap.get(columnSlug);
1097
+ if (columnName) {
1098
+ row._data.data[columnName] = reference.toJSON();
1099
+ }
1100
+ }
1101
+ catch (error) {
1102
+ // Log warning but don't fail the entire operation
1103
+ console.warn(`Failed to process attachment for row ${row.id}, column ${columnSlug}:`, error);
1104
+ }
1105
+ }
1106
+ }
1107
+ }
1108
+ transformValuesBackToNames(values, columnMap) {
1109
+ const result = {};
1110
+ const reverseMap = new Map();
1111
+ columnMap.forEach((slug, name) => {
1112
+ reverseMap.set(slug, name);
1113
+ });
1114
+ Object.keys(values).forEach((columnSlug) => {
1115
+ const columnName = reverseMap.get(columnSlug);
1116
+ if (columnName) {
1117
+ result[columnName] = values[columnSlug];
1118
+ }
1119
+ });
1120
+ return result;
1121
+ }
1122
+ async getRows(limit = 100, offset = 0) {
1123
+ if (this._deleted) {
1124
+ throw new Error("Cannot get rows from a deleted dataset");
1125
+ }
1126
+ const response = await this.client.get(`/v2/datasets/${this.slug}/rows?limit=${limit}&offset=${offset}`);
1127
+ const data = await this.handleResponse(response);
1128
+ const rows = data.rows || [];
1129
+ return rows.map((row) => {
1130
+ const rowResponse = {
1131
+ id: row.id,
1132
+ datasetId: this._data.id,
1133
+ datasetSlug: this.slug,
1134
+ data: row.values || row.data || {},
1135
+ createdAt: row.created_at,
1136
+ updatedAt: row.updated_at,
1137
+ };
1138
+ return new Row(this.client, rowResponse);
1139
+ });
1140
+ }
1141
+ async fromCSV(csvContent, options = {}) {
1142
+ if (this._deleted) {
1143
+ throw new Error("Cannot import CSV to a deleted dataset");
1144
+ }
1145
+ const { hasHeader = true, delimiter = "," } = options;
1146
+ if (!csvContent || typeof csvContent !== "string") {
1147
+ throw new Error("CSV content must be a valid string");
1148
+ }
1149
+ const rows = this.parseCSV(csvContent, delimiter, hasHeader);
1150
+ if (rows.length === 0) {
1151
+ throw new Error("No data found in CSV");
1152
+ }
1153
+ const batchSize = 100;
1154
+ for (let i = 0; i < rows.length; i += batchSize) {
1155
+ const batch = rows.slice(i, i + batchSize);
1156
+ await this.addRows(batch);
1157
+ }
1158
+ }
1159
+ async getVersions() {
1160
+ if (this._deleted) {
1161
+ throw new Error("Cannot get versions of a deleted dataset");
1162
+ }
1163
+ const response = await this.client.get(`/v2/datasets/${this.slug}/versions`);
1164
+ return await this.handleResponse(response);
1165
+ }
1166
+ async getVersion(version) {
1167
+ if (this._deleted) {
1168
+ throw new Error("Cannot get version of a deleted dataset");
1169
+ }
1170
+ const versionsData = await this.getVersions();
1171
+ return versionsData.versions.find((v) => v.version === version) || null;
1172
+ }
1173
+ parseCSV(csvContent, delimiter, hasHeader) {
1174
+ const parseResult = Papa__namespace.parse(csvContent, {
1175
+ delimiter,
1176
+ header: hasHeader,
1177
+ skipEmptyLines: true,
1178
+ transformHeader: (header) => header.trim(),
1179
+ transform: (value) => this.parseValue(value.trim()),
1180
+ });
1181
+ if (parseResult.errors.length > 0) {
1182
+ throw new Error(`CSV parsing failed: ${parseResult.errors[0].message}`);
1183
+ }
1184
+ return parseResult.data;
1185
+ }
1186
+ parseValue(value) {
1187
+ if (value === "" || value.toLowerCase() === "null") {
1188
+ return null;
1189
+ }
1190
+ if (value.toLowerCase() === "true") {
1191
+ return true;
1192
+ }
1193
+ if (value.toLowerCase() === "false") {
1194
+ return false;
1195
+ }
1196
+ const numValue = Number(value);
1197
+ if (!isNaN(numValue) && isFinite(numValue)) {
1198
+ return numValue;
1199
+ }
1200
+ return value;
1201
+ }
1202
+ }
1203
+
1204
+ class Datasets extends BaseDatasetEntity {
1205
+ constructor(client) {
1206
+ super(client);
1207
+ }
1208
+ async create(options) {
1209
+ this.validateDatasetName(options.name);
1210
+ const response = await this.client.post("/v2/datasets", options);
1211
+ const data = await this.handleResponse(response);
1212
+ return new Dataset(this.client, data);
1213
+ }
1214
+ async get(slug) {
1215
+ this.validateDatasetSlug(slug);
1216
+ const response = await this.client.get(`/v2/datasets/${slug}`);
1217
+ const data = await this.handleResponse(response);
1218
+ return new Dataset(this.client, data);
1219
+ }
1220
+ async list() {
1221
+ const response = await this.client.get(`/v2/datasets`);
1222
+ const data = await this.handleResponse(response);
1223
+ if (!data || !data.datasets) {
1224
+ return {
1225
+ datasets: [],
1226
+ total: 0,
1227
+ };
1228
+ }
1229
+ const datasets = data.datasets.map((datasetData) => new Dataset(this.client, datasetData));
1230
+ return Object.assign(Object.assign({}, data), { datasets });
1231
+ }
1232
+ async delete(slug) {
1233
+ this.validateDatasetSlug(slug);
1234
+ const response = await this.client.delete(`/v2/datasets/${slug}`);
1235
+ await this.handleResponse(response);
1236
+ }
1237
+ async getVersionCSV(slug, version) {
1238
+ this.validateDatasetSlug(slug);
1239
+ if (!version || typeof version !== "string") {
1240
+ throw new Error("Version must be a non-empty string");
1241
+ }
1242
+ const response = await this.client.get(`/v2/datasets/${slug}/versions/${version}`);
1243
+ const csvData = await this.handleResponse(response);
1244
+ if (typeof csvData !== "string") {
1245
+ throw new Error("Expected CSV data as string from API");
1246
+ }
1247
+ return csvData;
1248
+ }
1249
+ async getVersionAsJsonl(slug, version) {
1250
+ if (!version || version === "") {
1251
+ throw new Error("Version is required");
1252
+ }
1253
+ const url = `/v2/datasets/${slug}/versions/${version}/jsonl`;
1254
+ const response = await this.client.get(url);
1255
+ if (!response.ok) {
1256
+ throw new Error(`Failed to fetch JSONL data: ${response.status} ${response.statusText}`);
1257
+ }
1258
+ const contentType = response.headers.get("content-type");
1259
+ if (contentType && contentType.includes("application/json")) {
1260
+ // If server returns JSON, handle it appropriately
1261
+ const jsonData = await response.json();
1262
+ if (jsonData.error) {
1263
+ throw new Error(jsonData.error);
1264
+ }
1265
+ // Convert JSON response to JSONL format if needed
1266
+ if (Array.isArray(jsonData)) {
1267
+ return jsonData.map((item) => JSON.stringify(item)).join("\n");
1268
+ }
1269
+ return JSON.stringify(jsonData);
1270
+ }
1271
+ // Expect JSONL format (text/plain or application/jsonl)
1272
+ return await response.text();
1273
+ }
1274
+ }
1275
+
1276
+ class Evaluator extends BaseDatasetEntity {
1277
+ constructor(client) {
1278
+ super(client);
1279
+ }
1280
+ /**
1281
+ * Run evaluators on experiment task results and wait for completion
1282
+ */
1283
+ async runExperimentEvaluator(options) {
1284
+ const { experimentId, experimentRunId, taskId, taskResult, evaluator, waitForResults = true, } = options;
1285
+ this.validateEvaluatorOptions(options);
1286
+ const triggerResponse = await this.triggerExperimentEvaluator({
1287
+ experimentId,
1288
+ experimentRunId,
1289
+ taskId,
1290
+ evaluator,
1291
+ taskResult,
1292
+ });
1293
+ if (!waitForResults) {
1294
+ return [
1295
+ {
1296
+ executionId: triggerResponse.executionId,
1297
+ result: { status: "running", startedAt: new Date().toISOString() },
1298
+ },
1299
+ ];
1300
+ }
1301
+ return this.waitForResult(triggerResponse.executionId, triggerResponse.streamUrl);
1302
+ }
1303
+ /**
1304
+ * Trigger evaluator execution without waiting for results
1305
+ */
1306
+ async triggerExperimentEvaluator(request) {
1307
+ const { experimentId, experimentRunId, taskId, evaluator, taskResult } = request;
1308
+ if (!experimentId || !taskResult) {
1309
+ throw new Error("experimentId, evaluator, and taskResult are required");
1310
+ }
1311
+ // Handle string, EvaluatorWithVersion, and EvaluatorWithConfig types
1312
+ const evaluatorName = typeof evaluator === "string" ? evaluator : evaluator.name;
1313
+ const evaluatorVersion = typeof evaluator === "string" ? undefined : evaluator.version;
1314
+ // Extract config if present (EvaluatorWithConfig type)
1315
+ const evaluatorConfig = typeof evaluator === "object" && "config" in evaluator
1316
+ ? evaluator.config
1317
+ : undefined;
1318
+ if (!evaluatorName) {
1319
+ throw new Error("evaluator name is required");
1320
+ }
1321
+ const inputSchemaMapping = this.createInputSchemaMapping(taskResult);
1322
+ const payload = {
1323
+ experiment_id: experimentId,
1324
+ experiment_run_id: experimentRunId,
1325
+ evaluator_version: evaluatorVersion,
1326
+ task_id: taskId,
1327
+ input_schema_mapping: inputSchemaMapping,
1328
+ };
1329
+ // Add evaluator config if present
1330
+ if (evaluatorConfig && Object.keys(evaluatorConfig).length > 0) {
1331
+ payload.evaluator_config = evaluatorConfig;
1332
+ }
1333
+ const response = await this.client.post(`/v2/evaluators/slug/${evaluatorName}/execute`, payload);
1334
+ const data = await this.handleResponse(response);
1335
+ return {
1336
+ executionId: data.executionId,
1337
+ streamUrl: data.streamUrl,
1338
+ };
1339
+ }
1340
+ /**
1341
+ * Wait for execution result via stream URL (actually JSON endpoint)
1342
+ */
1343
+ async waitForResult(executionId, streamUrl) {
1344
+ if (!executionId || !streamUrl) {
1345
+ throw new Error("Execution ID and stream URL are required");
1346
+ }
1347
+ const fullStreamUrl = `${this.client["baseUrl"]}/v2${streamUrl}`;
1348
+ try {
1349
+ const response = await fetch(fullStreamUrl, {
1350
+ headers: {
1351
+ Authorization: `Bearer ${this.client["apiKey"]}`,
1352
+ Accept: "application/json",
1353
+ "Cache-Control": "no-cache",
1354
+ },
1355
+ });
1356
+ if (!response.ok) {
1357
+ const errorText = await response.text();
1358
+ throw new Error(`Failed to get results: ${response.status}, body: ${errorText}`);
1359
+ }
1360
+ const responseText = await response.text();
1361
+ const responseData = JSON.parse(responseText);
1362
+ // Check execution ID match
1363
+ if (responseData.execution_id &&
1364
+ responseData.execution_id !== executionId) {
1365
+ throw new Error(`Execution ID mismatch: ${responseData.execution_id} !== ${executionId}`);
1366
+ }
1367
+ // Convert to ExecutionResponse format
1368
+ const executionResponse = {
1369
+ executionId: responseData.execution_id,
1370
+ result: responseData.result,
1371
+ };
1372
+ return [executionResponse];
1373
+ }
1374
+ catch (error) {
1375
+ throw new Error(`Failed to wait for result: ${error instanceof Error ? error.message : "Unknown error"}`);
1376
+ }
1377
+ }
1378
+ /**
1379
+ * Validate evaluator run options
1380
+ */
1381
+ validateEvaluatorOptions(options) {
1382
+ const { experimentId, evaluator, taskResult } = options;
1383
+ if (!experimentId ||
1384
+ typeof experimentId !== "string" ||
1385
+ experimentId.trim().length === 0) {
1386
+ throw new Error("Experiment ID is required and must be a non-empty string");
1387
+ }
1388
+ if (!evaluator) {
1389
+ throw new Error("At least one evaluator must be specified");
1390
+ }
1391
+ if (!taskResult) {
1392
+ throw new Error("At least one task result must be provided");
1393
+ }
1394
+ // Validate evaluator based on its type
1395
+ if (typeof evaluator === "string") {
1396
+ if (!evaluator.trim()) {
1397
+ throw new Error("Evaluator name cannot be empty");
1398
+ }
1399
+ }
1400
+ else {
1401
+ if (!evaluator.name ||
1402
+ typeof evaluator.name !== "string" ||
1403
+ !evaluator.name.trim()) {
1404
+ throw new Error("Evaluator must have a valid name");
1405
+ }
1406
+ }
1407
+ // Validate each task result
1408
+ if (!taskResult || typeof taskResult !== "object") {
1409
+ throw new Error(`Task result must be a valid object`);
1410
+ }
1411
+ }
1412
+ /**
1413
+ * Create InputSchemaMapping from input object
1414
+ */
1415
+ createInputSchemaMapping(input) {
1416
+ const mapping = {};
1417
+ for (const [key, value] of Object.entries(input)) {
1418
+ mapping[key] = { source: String(value) };
1419
+ }
1420
+ return mapping;
1421
+ }
1422
+ }
1423
+
1424
+ class Experiment {
1425
+ constructor(client) {
1426
+ this.client = client;
1427
+ this.evaluator = new Evaluator(client);
1428
+ this.datasets = new Datasets(client);
1429
+ }
1430
+ /**
1431
+ * Generate a unique experiment slug
1432
+ */
1433
+ generateExperimentSlug() {
1434
+ const timestamp = Date.now().toString(36);
1435
+ const random = Math.random().toString(36).substring(2, 7);
1436
+ return `exp-${timestamp}${random}`.substring(0, 15);
1437
+ }
1438
+ async handleResponse(response) {
1439
+ if (!response.ok) {
1440
+ let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
1441
+ try {
1442
+ const errorData = await response.json();
1443
+ if (errorData.error) {
1444
+ errorMessage = errorData.error;
1445
+ }
1446
+ }
1447
+ catch (_a) {
1448
+ // Use default HTTP error message if JSON parsing fails
1449
+ }
1450
+ throw new Error(errorMessage);
1451
+ }
1452
+ const contentType = (response.headers.get("content-type") || "").toLowerCase();
1453
+ if (contentType.includes("text/csv") ||
1454
+ contentType.includes("application/x-ndjson")) {
1455
+ return await response.text();
1456
+ }
1457
+ else {
1458
+ const rawData = await response.json();
1459
+ return transformApiResponse(rawData);
1460
+ }
1461
+ }
1462
+ /**
1463
+ * Run an experiment with the given task function and options
1464
+ */
1465
+ async run(task, options = {}) {
1466
+ // Check if running in GitHub Actions
1467
+ if (process.env.GITHUB_ACTIONS === "true") {
1468
+ return await this.runInGithub(task, options);
1469
+ }
1470
+ return await this.runLocally(task, options);
1471
+ }
1472
+ /**
1473
+ * Run an experiment locally (not in GitHub Actions)
1474
+ */
1475
+ async runLocally(task, options = {}) {
1476
+ const { datasetSlug, datasetVersion, evaluators = [], waitForResults = true, } = options;
1477
+ // When experimentSlug is not provided a random one is generated
1478
+ let { experimentSlug } = options;
1479
+ if (!experimentSlug) {
1480
+ experimentSlug =
1481
+ this.client.experimentSlug || this.generateExperimentSlug();
1482
+ }
1483
+ this.validateRunOptions(task, options);
1484
+ try {
1485
+ const evaluatorSlugs = evaluators.map((evaluator) => typeof evaluator === "string" ? evaluator : evaluator.name);
1486
+ const experimentResponse = await this.initializeExperiment({
1487
+ slug: experimentSlug,
1488
+ datasetSlug,
1489
+ datasetVersion,
1490
+ evaluatorSlugs,
1491
+ });
1492
+ const rows = await this.getDatasetRows(datasetSlug, datasetVersion);
1493
+ const taskResults = [];
1494
+ const taskErrors = [];
1495
+ const evaluationResults = [];
1496
+ for (const row of rows) {
1497
+ const taskOutput = await task(row);
1498
+ // Create TaskResponse object
1499
+ const taskResponse = {
1500
+ input: row,
1501
+ output: taskOutput,
1502
+ metadata: {
1503
+ rowId: row.id,
1504
+ timestamp: Date.now(),
1505
+ },
1506
+ timestamp: Date.now(),
1507
+ };
1508
+ taskResults.push(taskResponse);
1509
+ const response = await this.createTask(experimentSlug, experimentResponse.run.id, row, taskOutput);
1510
+ const taskId = response.id;
1511
+ if (evaluators.length > 0) {
1512
+ for (const evaluator of evaluators) {
1513
+ const singleEvaluationResult = await this.evaluator.runExperimentEvaluator({
1514
+ experimentId: experimentResponse.experiment.id,
1515
+ experimentRunId: experimentResponse.run.id,
1516
+ taskId,
1517
+ evaluator,
1518
+ taskResult: taskOutput,
1519
+ waitForResults,
1520
+ timeout: 120000, // 2 minutes default
1521
+ });
1522
+ evaluationResults.push(...singleEvaluationResult);
1523
+ }
1524
+ }
1525
+ }
1526
+ const evalResults = evaluationResults.map((evaluation) => evaluation.result);
1527
+ // Track last experiment slug and run ID for export methods
1528
+ this._lastExperimentSlug = experimentSlug;
1529
+ this._lastRunId = experimentResponse.run.id;
1530
+ return {
1531
+ taskResults: taskResults,
1532
+ errors: taskErrors,
1533
+ experimentId: experimentResponse.experiment.id,
1534
+ runId: experimentResponse.run.id,
1535
+ evaluations: evalResults,
1536
+ };
1537
+ }
1538
+ catch (error) {
1539
+ throw new Error(`Experiment execution failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1540
+ }
1541
+ }
1542
+ /**
1543
+ * Create a task for the experiment
1544
+ */
1545
+ async createTask(experimentSlug, experimentRunId, taskInput, taskOutput) {
1546
+ const body = {
1547
+ input: taskInput,
1548
+ output: taskOutput,
1549
+ };
1550
+ const response = await this.client.post(`/v2/experiments/${experimentSlug}/runs/${experimentRunId}/task`, body);
1551
+ if (!response.ok) {
1552
+ throw new Error(`Failed to create task for experiment '${experimentSlug}'`);
1553
+ }
1554
+ const data = await this.handleResponse(response);
1555
+ return {
1556
+ id: data.id,
1557
+ };
1558
+ }
1559
+ /**
1560
+ * Initialize a new experiment
1561
+ */
1562
+ async initializeExperiment(request) {
1563
+ if (request.aux) {
1564
+ request.experimentRunMetadata = Object.assign(Object.assign({}, request.experimentRunMetadata), { aux: request.aux });
1565
+ }
1566
+ if (request.relatedRef) {
1567
+ request.experimentRunMetadata = Object.assign(Object.assign({}, request.experimentRunMetadata), { related_ref: request.relatedRef });
1568
+ }
1569
+ const payload = {
1570
+ slug: request.slug,
1571
+ dataset_slug: request.datasetSlug,
1572
+ dataset_version: request.datasetVersion,
1573
+ evaluator_slugs: request.evaluatorSlugs,
1574
+ experiment_metadata: request.experimentMetadata,
1575
+ experiment_run_metadata: request.experimentRunMetadata,
1576
+ };
1577
+ const response = await this.client.put("/v2/experiments/initialize", payload);
1578
+ const data = await this.handleResponse(response);
1579
+ return data;
1580
+ }
1581
+ /**
1582
+ * Parse JSONL string into list of {col_name: col_value} dictionaries
1583
+ * Skips the first line (columns definition)
1584
+ */
1585
+ parseJsonlToRows(jsonlData) {
1586
+ const rows = [];
1587
+ const lines = jsonlData.trim().split("\n");
1588
+ // Skip the first line (columns definition)
1589
+ for (let i = 1; i < lines.length; i++) {
1590
+ const line = lines[i].trim();
1591
+ if (line) {
1592
+ try {
1593
+ const rowData = JSON.parse(line);
1594
+ rows.push(rowData);
1595
+ }
1596
+ catch (_a) {
1597
+ // Skip invalid JSON lines
1598
+ continue;
1599
+ }
1600
+ }
1601
+ }
1602
+ return rows;
1603
+ }
1604
+ /**
1605
+ * Get dataset rows for experiment execution
1606
+ */
1607
+ async getDatasetRows(datasetSlug, datasetVersion) {
1608
+ if (!datasetSlug) {
1609
+ throw new Error("Dataset slug is required for experiment execution");
1610
+ }
1611
+ const dataset = await this.datasets.getVersionAsJsonl(datasetSlug, datasetVersion || "");
1612
+ const rows = this.parseJsonlToRows(dataset);
1613
+ return rows;
1614
+ }
1615
+ /**
1616
+ * Validate experiment run options
1617
+ */
1618
+ validateRunOptions(task, options) {
1619
+ if (!task || typeof task !== "function") {
1620
+ throw new Error("Task function is required and must be a function");
1621
+ }
1622
+ if (options.evaluators) {
1623
+ options.evaluators.forEach((evaluator, index) => {
1624
+ if (typeof evaluator === "string") {
1625
+ if (!evaluator.trim()) {
1626
+ throw new Error(`Evaluator at index ${index} cannot be an empty string`);
1627
+ }
1628
+ }
1629
+ else {
1630
+ if (!evaluator || typeof evaluator !== "object") {
1631
+ throw new Error(`Evaluator at index ${index} must be a string or object with name and version`);
1632
+ }
1633
+ if (!evaluator.name ||
1634
+ typeof evaluator.name !== "string" ||
1635
+ !evaluator.name.trim()) {
1636
+ throw new Error(`Evaluator at index ${index} must have a valid non-empty name`);
1637
+ }
1638
+ }
1639
+ });
1640
+ }
1641
+ }
1642
+ /**
1643
+ * Extract GitHub Actions context from environment variables
1644
+ */
1645
+ getGithubContext() {
1646
+ const repository = process.env.GITHUB_REPOSITORY;
1647
+ const ref = process.env.GITHUB_REF;
1648
+ const sha = process.env.GITHUB_SHA;
1649
+ const actor = process.env.GITHUB_ACTOR;
1650
+ if (!repository || !ref || !sha || !actor) {
1651
+ throw new Error("Missing required GitHub environment variables: GITHUB_REPOSITORY, GITHUB_REF, GITHUB_SHA, or GITHUB_ACTOR");
1652
+ }
1653
+ // Extract PR number from ref (e.g., refs/pull/123/merge -> 123)
1654
+ const prMatch = ref.match(/refs\/pull\/(\d+)\//);
1655
+ const prNumber = prMatch ? prMatch[1] : null;
1656
+ if (!prNumber) {
1657
+ throw new Error(`This method can only be run on pull request events. Current ref: ${ref}`);
1658
+ }
1659
+ const prUrl = `https://github.com/${repository}/pull/${prNumber}`;
1660
+ return {
1661
+ repository,
1662
+ prUrl,
1663
+ commitHash: sha,
1664
+ actor,
1665
+ };
1666
+ }
1667
+ /**
1668
+ * Execute tasks locally and capture results
1669
+ */
1670
+ async executeTasksLocally(task, rows) {
1671
+ return await Promise.all(rows.map(async (row) => {
1672
+ try {
1673
+ const output = await task(row);
1674
+ return {
1675
+ input: row,
1676
+ output: output,
1677
+ metadata: {
1678
+ rowId: row.id,
1679
+ timestamp: Date.now(),
1680
+ },
1681
+ };
1682
+ }
1683
+ catch (error) {
1684
+ return {
1685
+ input: row,
1686
+ error: error instanceof Error ? error.message : String(error),
1687
+ metadata: {
1688
+ rowId: row.id,
1689
+ timestamp: Date.now(),
1690
+ },
1691
+ };
1692
+ }
1693
+ }));
1694
+ }
1695
+ /**
1696
+ * Run an experiment in GitHub Actions environment
1697
+ * This method executes tasks locally and submits results to the backend for evaluation
1698
+ */
1699
+ async runInGithub(task, options) {
1700
+ const { datasetSlug, datasetVersion, evaluators = [], experimentMetadata, experimentRunMetadata, relatedRef, aux, } = options;
1701
+ // Generate or use provided experiment slug
1702
+ let { experimentSlug } = options;
1703
+ if (!experimentSlug) {
1704
+ experimentSlug =
1705
+ this.client.experimentSlug || this.generateExperimentSlug();
1706
+ }
1707
+ if (!task || typeof task !== "function") {
1708
+ throw new Error("Task function is required and must be a function");
1709
+ }
1710
+ try {
1711
+ const githubContext = this.getGithubContext();
1712
+ const rows = await this.getDatasetRows(datasetSlug, datasetVersion);
1713
+ const taskResults = await this.executeTasksLocally(task, rows);
1714
+ // Prepare evaluator slugs
1715
+ const evaluatorSlugs = evaluators.map((evaluator) => typeof evaluator === "string" ? evaluator : evaluator.name);
1716
+ const mergedExperimentMetadata = Object.assign(Object.assign({}, (experimentMetadata || {})), { created_from: "github" });
1717
+ const mergedExperimentRunMetadata = Object.assign(Object.assign(Object.assign({}, (experimentRunMetadata || {})), (relatedRef && { related_ref: relatedRef })), (aux && { aux: aux }));
1718
+ // Submit to backend
1719
+ const payload = {
1720
+ experiment_slug: experimentSlug,
1721
+ dataset_slug: datasetSlug,
1722
+ dataset_version: datasetVersion,
1723
+ evaluator_slugs: evaluatorSlugs,
1724
+ task_results: taskResults,
1725
+ github_context: {
1726
+ repository: githubContext.repository,
1727
+ pr_url: githubContext.prUrl,
1728
+ commit_hash: githubContext.commitHash,
1729
+ actor: githubContext.actor,
1730
+ },
1731
+ experiment_metadata: mergedExperimentMetadata,
1732
+ experiment_run_metadata: mergedExperimentRunMetadata,
1733
+ };
1734
+ const response = await this.client.post("/v2/experiments/run-in-github", payload);
1735
+ const data = await this.handleResponse(response);
1736
+ // Track last experiment slug and run ID for export methods
1737
+ this._lastExperimentSlug = data.experimentSlug;
1738
+ this._lastRunId = data.runId;
1739
+ return data;
1740
+ }
1741
+ catch (error) {
1742
+ throw new Error(`GitHub experiment execution failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1743
+ }
1744
+ }
1745
+ /**
1746
+ * Resolve export parameters by falling back to last used values
1747
+ */
1748
+ resolveExportParams(experimentSlug, runId) {
1749
+ const slug = experimentSlug || this._lastExperimentSlug;
1750
+ const rid = runId || this._lastRunId;
1751
+ if (!slug) {
1752
+ throw new Error("experiment_slug is required");
1753
+ }
1754
+ if (!rid) {
1755
+ throw new Error("run_id is required");
1756
+ }
1757
+ return { slug, runId: rid };
1758
+ }
1759
+ /**
1760
+ * Export experiment results as CSV string
1761
+ * @param experimentSlug - Optional experiment slug (uses last run if not provided)
1762
+ * @param runId - Optional run ID (uses last run if not provided)
1763
+ * @returns CSV string of experiment results
1764
+ */
1765
+ async toCsvString(experimentSlug, runId) {
1766
+ const { slug, runId: rid } = this.resolveExportParams(experimentSlug, runId);
1767
+ const response = await this.client.get(`/v2/experiments/${slug}/runs/${rid}/export/csv`);
1768
+ if (!response.ok) {
1769
+ throw new Error(`Failed to export CSV for experiment '${slug}' run '${rid}'`);
1770
+ }
1771
+ const result = await this.handleResponse(response);
1772
+ if (result === null || result === undefined) {
1773
+ throw new Error(`Failed to export CSV for experiment '${slug}' run '${rid}'`);
1774
+ }
1775
+ return String(result);
1776
+ }
1777
+ /**
1778
+ * Export experiment results as JSON string
1779
+ * @param experimentSlug - Optional experiment slug (uses last run if not provided)
1780
+ * @param runId - Optional run ID (uses last run if not provided)
1781
+ * @returns JSON string of experiment results
1782
+ */
1783
+ async toJsonString(experimentSlug, runId) {
1784
+ const { slug, runId: rid } = this.resolveExportParams(experimentSlug, runId);
1785
+ const response = await this.client.get(`/v2/experiments/${slug}/runs/${rid}/export/json`);
1786
+ if (!response.ok) {
1787
+ throw new Error(`Failed to export JSON for experiment '${slug}' run '${rid}'`);
1788
+ }
1789
+ const result = await this.handleResponse(response);
1790
+ if (result === null || result === undefined) {
1791
+ throw new Error(`Failed to export JSON for experiment '${slug}' run '${rid}'`);
1792
+ }
1793
+ // If result is already a string, return it; otherwise stringify it
1794
+ return typeof result === "string" ? result : JSON.stringify(result);
1795
+ }
1796
+ }
1797
+
1798
+ /**
1799
+ * The main client for interacting with Traceloop's API.
1800
+ * This client can be used either directly or through the singleton pattern via configuration.
1801
+ *
1802
+ * @example
1803
+ * // Direct usage
1804
+ * const client = new TraceloopClient('your-api-key');
1805
+ *
1806
+ * @example
1807
+ * // Through configuration (recommended)
1808
+ * initialize({ apiKey: 'your-api-key', appName: 'your-app' });
1809
+ * const client = getClient();
1810
+ */
1811
+ class TraceloopClient {
1812
+ /**
1813
+ * Creates a new instance of the TraceloopClient.
1814
+ *
1815
+ * @param options - Configuration options for the client
1816
+ */
1817
+ constructor(options) {
1818
+ this.version = version;
1819
+ this.apiKey = options.apiKey;
1820
+ this.appName = options.appName;
1821
+ this.baseUrl =
1822
+ options.baseUrl ||
1823
+ process.env.ANYWAY_BASE_URL ||
1824
+ "https://api.traceloop.com";
1825
+ this.experimentSlug = options.experimentSlug;
1826
+ this.userFeedback = new UserFeedback(this);
1827
+ this.datasets = new Datasets(this);
1828
+ this.experiment = new Experiment(this);
1829
+ this.evaluator = new Evaluator(this);
1830
+ }
1831
+ async post(path, body) {
1832
+ return await fetch(`${this.baseUrl}${path}`, {
1833
+ method: "POST",
1834
+ headers: {
1835
+ "Content-Type": "application/json",
1836
+ Authorization: `Bearer ${this.apiKey}`,
1837
+ "X-Traceloop-SDK-Version": this.version,
1838
+ },
1839
+ body: JSON.stringify(body),
1840
+ });
1841
+ }
1842
+ async get(path) {
1843
+ return await fetch(`${this.baseUrl}${path}`, {
1844
+ method: "GET",
1845
+ headers: {
1846
+ Authorization: `Bearer ${this.apiKey}`,
1847
+ "X-Traceloop-SDK-Version": this.version,
1848
+ },
1849
+ });
1850
+ }
1851
+ async put(path, body) {
1852
+ return await fetch(`${this.baseUrl}${path}`, {
1853
+ method: "PUT",
1854
+ headers: {
1855
+ "Content-Type": "application/json",
1856
+ Authorization: `Bearer ${this.apiKey}`,
1857
+ "X-Traceloop-SDK-Version": this.version,
1858
+ },
1859
+ body: JSON.stringify(body),
1860
+ });
1861
+ }
1862
+ async delete(path) {
1863
+ return await fetch(`${this.baseUrl}${path}`, {
1864
+ method: "DELETE",
1865
+ headers: {
1866
+ Authorization: `Bearer ${this.apiKey}`,
1867
+ "X-Traceloop-SDK-Version": this.version,
1868
+ },
1869
+ });
1870
+ }
1871
+ }
1872
+
1873
+ // Auto-generated - DO NOT EDIT
1874
+ // Regenerate with: pnpm generate:evaluator-models
1875
+ const EVALUATOR_SLUGS = [
1876
+ 'agent-efficiency',
1877
+ 'agent-flow-quality',
1878
+ 'agent-goal-accuracy',
1879
+ 'agent-goal-completeness',
1880
+ 'agent-tool-error-detector',
1881
+ 'agent-tool-trajectory',
1882
+ 'answer-completeness',
1883
+ 'answer-correctness',
1884
+ 'answer-relevancy',
1885
+ 'char-count',
1886
+ 'char-count-ratio',
1887
+ 'context-relevance',
1888
+ 'conversation-quality',
1889
+ 'faithfulness',
1890
+ 'html-comparison',
1891
+ 'instruction-adherence',
1892
+ 'intent-change',
1893
+ 'json-validator',
1894
+ 'perplexity',
1895
+ 'pii-detector',
1896
+ 'placeholder-regex',
1897
+ 'profanity-detector',
1898
+ 'prompt-injection',
1899
+ 'prompt-perplexity',
1900
+ 'regex-validator',
1901
+ 'secrets-detector',
1902
+ 'semantic-similarity',
1903
+ 'sexism-detector',
1904
+ 'sql-validator',
1905
+ 'tone-detection',
1906
+ 'topic-adherence',
1907
+ 'toxicity-detector',
1908
+ 'uncertainty-detector',
1909
+ 'word-count',
1910
+ 'word-count-ratio',
1911
+ ];
1912
+ const EVALUATOR_SCHEMAS = {
1913
+ 'agent-efficiency': {
1914
+ slug: 'agent-efficiency',
1915
+ requiredInputFields: ['trajectory_completions', 'trajectory_prompts'],
1916
+ optionalConfigFields: [],
1917
+ description: "Evaluate agent efficiency - detect redundant calls, unnecessary follow-ups\n\n**Request Body:**\n- `input.trajectory_prompts` (string, required): JSON array of prompts in the agent trajectory\n- `input.trajectory_completions` (string, required): JSON array of completions in the agent trajectory",
1918
+ },
1919
+ 'agent-flow-quality': {
1920
+ slug: 'agent-flow-quality',
1921
+ requiredInputFields: ['trajectory_completions', 'trajectory_prompts'],
1922
+ optionalConfigFields: ['conditions', 'threshold'],
1923
+ description: "Validate agent trajectory against user-defined conditions\n\n**Request Body:**\n- `input.trajectory_prompts` (string, required): JSON array of prompts in the agent trajectory\n- `input.trajectory_completions` (string, required): JSON array of completions in the agent trajectory\n- `config.conditions` (array of strings, required): Array of evaluation conditions/rules to validate against\n- `config.threshold` (number, required): Score threshold for pass/fail determination (0.0-1.0)",
1924
+ },
1925
+ 'agent-goal-accuracy': {
1926
+ slug: 'agent-goal-accuracy',
1927
+ requiredInputFields: ['completion', 'question', 'reference'],
1928
+ optionalConfigFields: [],
1929
+ description: "Evaluate agent goal accuracy\n\n**Request Body:**\n- `input.question` (string, required): The original question or goal\n- `input.completion` (string, required): The agent's completion/response\n- `input.reference` (string, required): The expected reference answer",
1930
+ },
1931
+ 'agent-goal-completeness': {
1932
+ slug: 'agent-goal-completeness',
1933
+ requiredInputFields: ['trajectory_completions', 'trajectory_prompts'],
1934
+ optionalConfigFields: ['threshold'],
1935
+ description: "Measure if agent accomplished all user goals\n\n**Request Body:**\n- `input.trajectory_prompts` (string, required): JSON array of prompts in the agent trajectory\n- `input.trajectory_completions` (string, required): JSON array of completions in the agent trajectory\n- `config.threshold` (number, required): Score threshold for pass/fail determination (0.0-1.0)",
1936
+ },
1937
+ 'agent-tool-error-detector': {
1938
+ slug: 'agent-tool-error-detector',
1939
+ requiredInputFields: ['tool_input', 'tool_output'],
1940
+ optionalConfigFields: [],
1941
+ description: "Detect errors or failures during tool execution\n\n**Request Body:**\n- `input.tool_input` (string, required): JSON string of the tool input\n- `input.tool_output` (string, required): JSON string of the tool output",
1942
+ },
1943
+ 'agent-tool-trajectory': {
1944
+ slug: 'agent-tool-trajectory',
1945
+ requiredInputFields: ['executed_tool_calls', 'expected_tool_calls'],
1946
+ optionalConfigFields: ['input_params_sensitive', 'mismatch_sensitive', 'order_sensitive', 'threshold'],
1947
+ description: "Compare actual tool calls against expected reference tool calls\n\n**Request Body:**\n- `input.executed_tool_calls` (string, required): JSON array of actual tool calls made by the agent\n- `input.expected_tool_calls` (string, required): JSON array of expected/reference tool calls\n- `config.threshold` (float, optional): Score threshold for pass/fail determination (default: 0.5)\n- `config.mismatch_sensitive` (bool, optional): Whether tool calls must match exactly (default: false)\n- `config.order_sensitive` (bool, optional): Whether order of tool calls matters (default: false)\n- `config.input_params_sensitive` (bool, optional): Whether to compare input parameters (default: true)",
1948
+ },
1949
+ 'answer-completeness': {
1950
+ slug: 'answer-completeness',
1951
+ requiredInputFields: ['completion', 'context', 'question'],
1952
+ optionalConfigFields: [],
1953
+ description: "Evaluate whether the answer is complete and contains all the necessary information\n\n**Request Body:**\n- `input.question` (string, required): The original question\n- `input.completion` (string, required): The completion to evaluate for completeness\n- `input.context` (string, required): The context that provides the complete information",
1954
+ },
1955
+ 'answer-correctness': {
1956
+ slug: 'answer-correctness',
1957
+ requiredInputFields: ['completion', 'ground_truth', 'question'],
1958
+ optionalConfigFields: [],
1959
+ description: "Evaluate factual accuracy by comparing answers against ground truth\n\n**Request Body:**\n- `input.question` (string, required): The original question\n- `input.completion` (string, required): The completion to evaluate\n- `input.ground_truth` (string, required): The expected correct answer",
1960
+ },
1961
+ 'answer-relevancy': {
1962
+ slug: 'answer-relevancy',
1963
+ requiredInputFields: ['answer', 'question'],
1964
+ optionalConfigFields: [],
1965
+ description: "Check if an answer is relevant to a question\n\n**Request Body:**\n- `input.answer` (string, required): The answer to evaluate for relevancy\n- `input.question` (string, required): The question that the answer should be relevant to",
1966
+ },
1967
+ 'char-count': {
1968
+ slug: 'char-count',
1969
+ requiredInputFields: ['text'],
1970
+ optionalConfigFields: [],
1971
+ description: "Count the number of characters in text\n\n**Request Body:**\n- `input.text` (string, required): The text to count characters in",
1972
+ },
1973
+ 'char-count-ratio': {
1974
+ slug: 'char-count-ratio',
1975
+ requiredInputFields: ['denominator_text', 'numerator_text'],
1976
+ optionalConfigFields: [],
1977
+ description: "Calculate the ratio of characters between two texts\n\n**Request Body:**\n- `input.numerator_text` (string, required): The numerator text (will be divided by denominator)\n- `input.denominator_text` (string, required): The denominator text (divides the numerator)",
1978
+ },
1979
+ 'context-relevance': {
1980
+ slug: 'context-relevance',
1981
+ requiredInputFields: ['context', 'query'],
1982
+ optionalConfigFields: ['model'],
1983
+ description: "Evaluate whether retrieved context contains sufficient information to answer the query\n\n**Request Body:**\n- `input.query` (string, required): The query/question to evaluate context relevance for\n- `input.context` (string, required): The context to evaluate for relevance to the query\n- `config.model` (string, optional): Model to use for evaluation (default: gpt-4o)",
1984
+ },
1985
+ 'conversation-quality': {
1986
+ slug: 'conversation-quality',
1987
+ requiredInputFields: ['completions', 'prompts'],
1988
+ optionalConfigFields: [],
1989
+ description: "Evaluate conversation quality based on tone, clarity, flow, responsiveness, and transparency\n\n**Request Body:**\n- `input.prompts` (string, required): JSON array of prompts in the conversation\n- `input.completions` (string, required): JSON array of completions in the conversation",
1990
+ },
1991
+ 'faithfulness': {
1992
+ slug: 'faithfulness',
1993
+ requiredInputFields: ['completion', 'context', 'question'],
1994
+ optionalConfigFields: [],
1995
+ description: "Check if a completion is faithful to the provided context\n\n**Request Body:**\n- `input.completion` (string, required): The LLM completion to check for faithfulness\n- `input.context` (string, required): The context that the completion should be faithful to\n- `input.question` (string, required): The original question asked",
1996
+ },
1997
+ 'html-comparison': {
1998
+ slug: 'html-comparison',
1999
+ requiredInputFields: ['html1', 'html2'],
2000
+ optionalConfigFields: [],
2001
+ description: "Compare two HTML documents for structural and content similarity\n\n**Request Body:**\n- `input.html1` (string, required): The first HTML document to compare\n- `input.html2` (string, required): The second HTML document to compare",
2002
+ },
2003
+ 'instruction-adherence': {
2004
+ slug: 'instruction-adherence',
2005
+ requiredInputFields: ['instructions', 'response'],
2006
+ optionalConfigFields: [],
2007
+ description: "Evaluate how well responses follow given instructions\n\n**Request Body:**\n- `input.instructions` (string, required): The instructions that should be followed\n- `input.response` (string, required): The response to evaluate for instruction adherence",
2008
+ },
2009
+ 'intent-change': {
2010
+ slug: 'intent-change',
2011
+ requiredInputFields: ['completions', 'prompts'],
2012
+ optionalConfigFields: [],
2013
+ description: "Detect changes in user intent between prompts and completions\n\n**Request Body:**\n- `input.prompts` (string, required): JSON array of prompts in the conversation\n- `input.completions` (string, required): JSON array of completions in the conversation",
2014
+ },
2015
+ 'json-validator': {
2016
+ slug: 'json-validator',
2017
+ requiredInputFields: ['text'],
2018
+ optionalConfigFields: ['enable_schema_validation', 'schema_string'],
2019
+ description: "Validate JSON syntax\n\n**Request Body:**\n- `input.text` (string, required): The text to validate as JSON\n- `config.enable_schema_validation` (bool, optional): Enable JSON schema validation\n- `config.schema_string` (string, optional): JSON schema to validate against",
2020
+ },
2021
+ 'perplexity': {
2022
+ slug: 'perplexity',
2023
+ requiredInputFields: ['logprobs'],
2024
+ optionalConfigFields: [],
2025
+ description: "Measure text perplexity from logprobs\n\n**Request Body:**\n- `input.logprobs` (string, required): JSON array of log probabilities from the model",
2026
+ },
2027
+ 'pii-detector': {
2028
+ slug: 'pii-detector',
2029
+ requiredInputFields: ['text'],
2030
+ optionalConfigFields: ['probability_threshold'],
2031
+ description: "Detect personally identifiable information in text\n\n**Request Body:**\n- `input.text` (string, required): The text to scan for personally identifiable information\n- `config.probability_threshold` (float, optional): Detection threshold (default: 0.8)",
2032
+ },
2033
+ 'placeholder-regex': {
2034
+ slug: 'placeholder-regex',
2035
+ requiredInputFields: ['placeholder_value', 'text'],
2036
+ optionalConfigFields: ['case_sensitive', 'dot_include_nl', 'multi_line', 'should_match'],
2037
+ description: "Validate text against a placeholder regex pattern\n\n**Request Body:**\n- `input.placeholder_value` (string, required): The regex pattern to match against\n- `input.text` (string, required): The text to validate against the regex pattern\n- `config.should_match` (bool, optional): Whether the text should match the regex\n- `config.case_sensitive` (bool, optional): Case-sensitive matching\n- `config.dot_include_nl` (bool, optional): Dot matches newlines\n- `config.multi_line` (bool, optional): Multi-line mode",
2038
+ },
2039
+ 'profanity-detector': {
2040
+ slug: 'profanity-detector',
2041
+ requiredInputFields: ['text'],
2042
+ optionalConfigFields: [],
2043
+ description: "Detect profanity in text\n\n**Request Body:**\n- `input.text` (string, required): The text to scan for profanity",
2044
+ },
2045
+ 'prompt-injection': {
2046
+ slug: 'prompt-injection',
2047
+ requiredInputFields: ['prompt'],
2048
+ optionalConfigFields: ['threshold'],
2049
+ description: "Detect prompt injection attempts\n\n**Request Body:**\n- `input.prompt` (string, required): The prompt to check for injection attempts\n- `config.threshold` (float, optional): Detection threshold (default: 0.5)",
2050
+ },
2051
+ 'prompt-perplexity': {
2052
+ slug: 'prompt-perplexity',
2053
+ requiredInputFields: ['prompt'],
2054
+ optionalConfigFields: [],
2055
+ description: "Measure prompt perplexity to detect potential injection attempts\n\n**Request Body:**\n- `input.prompt` (string, required): The prompt to calculate perplexity for",
2056
+ },
2057
+ 'regex-validator': {
2058
+ slug: 'regex-validator',
2059
+ requiredInputFields: ['text'],
2060
+ optionalConfigFields: ['case_sensitive', 'dot_include_nl', 'multi_line', 'regex', 'should_match'],
2061
+ description: "Validate text against a regex pattern\n\n**Request Body:**\n- `input.text` (string, required): The text to validate against a regex pattern\n- `config.regex` (string, optional): The regex pattern to match against\n- `config.should_match` (bool, optional): Whether the text should match the regex\n- `config.case_sensitive` (bool, optional): Case-sensitive matching\n- `config.dot_include_nl` (bool, optional): Dot matches newlines\n- `config.multi_line` (bool, optional): Multi-line mode",
2062
+ },
2063
+ 'secrets-detector': {
2064
+ slug: 'secrets-detector',
2065
+ requiredInputFields: ['text'],
2066
+ optionalConfigFields: [],
2067
+ description: "Detect secrets and credentials in text\n\n**Request Body:**\n- `input.text` (string, required): The text to scan for secrets (API keys, passwords, etc.)",
2068
+ },
2069
+ 'semantic-similarity': {
2070
+ slug: 'semantic-similarity',
2071
+ requiredInputFields: ['completion', 'reference'],
2072
+ optionalConfigFields: [],
2073
+ description: "Calculate semantic similarity between completion and reference\n\n**Request Body:**\n- `input.completion` (string, required): The completion text to compare\n- `input.reference` (string, required): The reference text to compare against",
2074
+ },
2075
+ 'sexism-detector': {
2076
+ slug: 'sexism-detector',
2077
+ requiredInputFields: ['text'],
2078
+ optionalConfigFields: ['threshold'],
2079
+ description: "Detect sexist language and bias\n\n**Request Body:**\n- `input.text` (string, required): The text to scan for sexist content\n- `config.threshold` (float, optional): Detection threshold (default: 0.5)",
2080
+ },
2081
+ 'sql-validator': {
2082
+ slug: 'sql-validator',
2083
+ requiredInputFields: ['text'],
2084
+ optionalConfigFields: [],
2085
+ description: "Validate SQL query syntax\n\n**Request Body:**\n- `input.text` (string, required): The text to validate as SQL",
2086
+ },
2087
+ 'tone-detection': {
2088
+ slug: 'tone-detection',
2089
+ requiredInputFields: ['text'],
2090
+ optionalConfigFields: [],
2091
+ description: "Detect the tone of the text\n\n**Request Body:**\n- `input.text` (string, required): The text to detect the tone of",
2092
+ },
2093
+ 'topic-adherence': {
2094
+ slug: 'topic-adherence',
2095
+ requiredInputFields: ['completion', 'question', 'reference_topics'],
2096
+ optionalConfigFields: [],
2097
+ description: "Evaluate topic adherence\n\n**Request Body:**\n- `input.question` (string, required): The original question\n- `input.completion` (string, required): The completion to evaluate\n- `input.reference_topics` (string, required): Comma-separated list of expected topics",
2098
+ },
2099
+ 'toxicity-detector': {
2100
+ slug: 'toxicity-detector',
2101
+ requiredInputFields: ['text'],
2102
+ optionalConfigFields: ['threshold'],
2103
+ description: "Detect toxic or harmful language\n\n**Request Body:**\n- `input.text` (string, required): The text to scan for toxic content\n- `config.threshold` (float, optional): Detection threshold (default: 0.5)",
2104
+ },
2105
+ 'uncertainty-detector': {
2106
+ slug: 'uncertainty-detector',
2107
+ requiredInputFields: ['prompt'],
2108
+ optionalConfigFields: [],
2109
+ description: "Detect uncertainty in the text\n\n**Request Body:**\n- `input.prompt` (string, required): The text to detect uncertainty in",
2110
+ },
2111
+ 'word-count': {
2112
+ slug: 'word-count',
2113
+ requiredInputFields: ['text'],
2114
+ optionalConfigFields: [],
2115
+ description: "Count the number of words in text\n\n**Request Body:**\n- `input.text` (string, required): The text to count words in",
2116
+ },
2117
+ 'word-count-ratio': {
2118
+ slug: 'word-count-ratio',
2119
+ requiredInputFields: ['denominator_text', 'numerator_text'],
2120
+ optionalConfigFields: [],
2121
+ description: "Calculate the ratio of words between two texts\n\n**Request Body:**\n- `input.numerator_text` (string, required): The numerator text (will be divided by denominator)\n- `input.denominator_text` (string, required): The denominator text (divides the numerator)",
2122
+ },
2123
+ };
2124
+ function isValidEvaluatorSlug(slug) {
2125
+ return slug in EVALUATOR_SCHEMAS;
2126
+ }
2127
+
2128
+ // Auto-generated - DO NOT EDIT
2129
+ // Regenerate with: pnpm generate:evaluator-models
2130
+ // ─────────────────────────────────────────────────────────────────────────────
2131
+ // Utility functions
2132
+ // ─────────────────────────────────────────────────────────────────────────────
2133
+ /**
2134
+ * Create an evaluator configuration object.
2135
+ */
2136
+ function createEvaluator(slug, options) {
2137
+ return {
2138
+ name: slug,
2139
+ version: options === null || options === void 0 ? void 0 : options.version,
2140
+ config: options === null || options === void 0 ? void 0 : options.config,
2141
+ };
2142
+ }
2143
+ /**
2144
+ * Validate that required input fields are present in task output.
2145
+ */
2146
+ function validateEvaluatorInput(slug, taskOutput) {
2147
+ const schema = EVALUATOR_SCHEMAS[slug];
2148
+ if (!schema) {
2149
+ return { valid: false, missingFields: [] };
2150
+ }
2151
+ const missingFields = schema.requiredInputFields.filter((field) => !(field in taskOutput) || taskOutput[field] === undefined);
2152
+ return {
2153
+ valid: missingFields.length === 0,
2154
+ missingFields,
2155
+ };
2156
+ }
2157
+ /**
2158
+ * Get all available evaluator slugs.
2159
+ */
2160
+ function getAvailableEvaluatorSlugs() {
2161
+ return [...EVALUATOR_SLUGS];
2162
+ }
2163
+ /**
2164
+ * Get schema information for an evaluator.
2165
+ */
2166
+ function getEvaluatorSchemaInfo(slug) {
2167
+ return EVALUATOR_SCHEMAS[slug];
2168
+ }
2169
+ // ─────────────────────────────────────────────────────────────────────────────
2170
+ // Factory class
2171
+ // ─────────────────────────────────────────────────────────────────────────────
2172
+ /**
2173
+ * Factory class for creating type-safe MBT evaluator configurations.
2174
+ *
2175
+ * @example
2176
+ * ```typescript
2177
+ * import { EvaluatorMadeByTraceloop } from '@anyway-sh/node-server-sdk';
2178
+ *
2179
+ * const evaluators = [
2180
+ * EvaluatorMadeByTraceloop.piiDetector({ probability_threshold: 0.8 }),
2181
+ * EvaluatorMadeByTraceloop.faithfulness(),
2182
+ * ];
2183
+ * ```
2184
+ */
2185
+ class EvaluatorMadeByTraceloop {
2186
+ static create(slug, options) {
2187
+ return createEvaluator(slug, options);
2188
+ }
2189
+ static getAvailableSlugs() {
2190
+ return getAvailableEvaluatorSlugs();
2191
+ }
2192
+ static isValidSlug(slug) {
2193
+ return isValidEvaluatorSlug(slug);
2194
+ }
2195
+ /**
2196
+ * Evaluate agent efficiency - detect redundant calls, unnecessary follow-ups
2197
+
2198
+ **Request Body:**
2199
+ - `input.trajectory_prompts` (string, required): JSON array of prompts in the agent trajectory
2200
+ - `input.trajectory_completions` (string, required): JSON array of completions in the agent trajectory
2201
+ * Required task output fields: trajectory_completions, trajectory_prompts
2202
+ */
2203
+ static agentEfficiency() {
2204
+ return createEvaluator('agent-efficiency');
2205
+ }
2206
+ /**
2207
+ * Validate agent trajectory against user-defined conditions
2208
+
2209
+ **Request Body:**
2210
+ - `input.trajectory_prompts` (string, required): JSON array of prompts in the agent trajectory
2211
+ - `input.trajectory_completions` (string, required): JSON array of completions in the agent trajectory
2212
+ - `config.conditions` (array of strings, required): Array of evaluation conditions/rules to validate against
2213
+ - `config.threshold` (number, required): Score threshold for pass/fail determination (0.0-1.0)
2214
+ * Required task output fields: trajectory_completions, trajectory_prompts
2215
+ */
2216
+ static agentFlowQuality(config) {
2217
+ return createEvaluator('agent-flow-quality', { config: config });
2218
+ }
2219
+ /**
2220
+ * Evaluate agent goal accuracy
2221
+
2222
+ **Request Body:**
2223
+ - `input.question` (string, required): The original question or goal
2224
+ - `input.completion` (string, required): The agent's completion/response
2225
+ - `input.reference` (string, required): The expected reference answer
2226
+ * Required task output fields: completion, question, reference
2227
+ */
2228
+ static agentGoalAccuracy() {
2229
+ return createEvaluator('agent-goal-accuracy');
2230
+ }
2231
+ /**
2232
+ * Measure if agent accomplished all user goals
2233
+
2234
+ **Request Body:**
2235
+ - `input.trajectory_prompts` (string, required): JSON array of prompts in the agent trajectory
2236
+ - `input.trajectory_completions` (string, required): JSON array of completions in the agent trajectory
2237
+ - `config.threshold` (number, required): Score threshold for pass/fail determination (0.0-1.0)
2238
+ * Required task output fields: trajectory_completions, trajectory_prompts
2239
+ */
2240
+ static agentGoalCompleteness(config) {
2241
+ return createEvaluator('agent-goal-completeness', { config: config });
2242
+ }
2243
+ /**
2244
+ * Detect errors or failures during tool execution
2245
+
2246
+ **Request Body:**
2247
+ - `input.tool_input` (string, required): JSON string of the tool input
2248
+ - `input.tool_output` (string, required): JSON string of the tool output
2249
+ * Required task output fields: tool_input, tool_output
2250
+ */
2251
+ static agentToolErrorDetector() {
2252
+ return createEvaluator('agent-tool-error-detector');
2253
+ }
2254
+ /**
2255
+ * Compare actual tool calls against expected reference tool calls
2256
+
2257
+ **Request Body:**
2258
+ - `input.executed_tool_calls` (string, required): JSON array of actual tool calls made by the agent
2259
+ - `input.expected_tool_calls` (string, required): JSON array of expected/reference tool calls
2260
+ - `config.threshold` (float, optional): Score threshold for pass/fail determination (default: 0.5)
2261
+ - `config.mismatch_sensitive` (bool, optional): Whether tool calls must match exactly (default: false)
2262
+ - `config.order_sensitive` (bool, optional): Whether order of tool calls matters (default: false)
2263
+ - `config.input_params_sensitive` (bool, optional): Whether to compare input parameters (default: true)
2264
+ * Required task output fields: executed_tool_calls, expected_tool_calls
2265
+ */
2266
+ static agentToolTrajectory(config) {
2267
+ return createEvaluator('agent-tool-trajectory', { config: config });
2268
+ }
2269
+ /**
2270
+ * Evaluate whether the answer is complete and contains all the necessary information
2271
+
2272
+ **Request Body:**
2273
+ - `input.question` (string, required): The original question
2274
+ - `input.completion` (string, required): The completion to evaluate for completeness
2275
+ - `input.context` (string, required): The context that provides the complete information
2276
+ * Required task output fields: completion, context, question
2277
+ */
2278
+ static answerCompleteness() {
2279
+ return createEvaluator('answer-completeness');
2280
+ }
2281
+ /**
2282
+ * Evaluate factual accuracy by comparing answers against ground truth
2283
+
2284
+ **Request Body:**
2285
+ - `input.question` (string, required): The original question
2286
+ - `input.completion` (string, required): The completion to evaluate
2287
+ - `input.ground_truth` (string, required): The expected correct answer
2288
+ * Required task output fields: completion, ground_truth, question
2289
+ */
2290
+ static answerCorrectness() {
2291
+ return createEvaluator('answer-correctness');
2292
+ }
2293
+ /**
2294
+ * Check if an answer is relevant to a question
2295
+
2296
+ **Request Body:**
2297
+ - `input.answer` (string, required): The answer to evaluate for relevancy
2298
+ - `input.question` (string, required): The question that the answer should be relevant to
2299
+ * Required task output fields: answer, question
2300
+ */
2301
+ static answerRelevancy() {
2302
+ return createEvaluator('answer-relevancy');
2303
+ }
2304
+ /**
2305
+ * Count the number of characters in text
2306
+
2307
+ **Request Body:**
2308
+ - `input.text` (string, required): The text to count characters in
2309
+ * Required task output fields: text
2310
+ */
2311
+ static charCount() {
2312
+ return createEvaluator('char-count');
2313
+ }
2314
+ /**
2315
+ * Calculate the ratio of characters between two texts
2316
+
2317
+ **Request Body:**
2318
+ - `input.numerator_text` (string, required): The numerator text (will be divided by denominator)
2319
+ - `input.denominator_text` (string, required): The denominator text (divides the numerator)
2320
+ * Required task output fields: denominator_text, numerator_text
2321
+ */
2322
+ static charCountRatio() {
2323
+ return createEvaluator('char-count-ratio');
2324
+ }
2325
+ /**
2326
+ * Evaluate whether retrieved context contains sufficient information to answer the query
2327
+
2328
+ **Request Body:**
2329
+ - `input.query` (string, required): The query/question to evaluate context relevance for
2330
+ - `input.context` (string, required): The context to evaluate for relevance to the query
2331
+ - `config.model` (string, optional): Model to use for evaluation (default: gpt-4o)
2332
+ * Required task output fields: context, query
2333
+ */
2334
+ static contextRelevance(config) {
2335
+ return createEvaluator('context-relevance', { config: config });
2336
+ }
2337
+ /**
2338
+ * Evaluate conversation quality based on tone, clarity, flow, responsiveness, and transparency
2339
+
2340
+ **Request Body:**
2341
+ - `input.prompts` (string, required): JSON array of prompts in the conversation
2342
+ - `input.completions` (string, required): JSON array of completions in the conversation
2343
+ * Required task output fields: completions, prompts
2344
+ */
2345
+ static conversationQuality() {
2346
+ return createEvaluator('conversation-quality');
2347
+ }
2348
+ /**
2349
+ * Check if a completion is faithful to the provided context
2350
+
2351
+ **Request Body:**
2352
+ - `input.completion` (string, required): The LLM completion to check for faithfulness
2353
+ - `input.context` (string, required): The context that the completion should be faithful to
2354
+ - `input.question` (string, required): The original question asked
2355
+ * Required task output fields: completion, context, question
2356
+ */
2357
+ static faithfulness() {
2358
+ return createEvaluator('faithfulness');
2359
+ }
2360
+ /**
2361
+ * Compare two HTML documents for structural and content similarity
2362
+
2363
+ **Request Body:**
2364
+ - `input.html1` (string, required): The first HTML document to compare
2365
+ - `input.html2` (string, required): The second HTML document to compare
2366
+ * Required task output fields: html1, html2
2367
+ */
2368
+ static htmlComparison() {
2369
+ return createEvaluator('html-comparison');
2370
+ }
2371
+ /**
2372
+ * Evaluate how well responses follow given instructions
2373
+
2374
+ **Request Body:**
2375
+ - `input.instructions` (string, required): The instructions that should be followed
2376
+ - `input.response` (string, required): The response to evaluate for instruction adherence
2377
+ * Required task output fields: instructions, response
2378
+ */
2379
+ static instructionAdherence() {
2380
+ return createEvaluator('instruction-adherence');
2381
+ }
2382
+ /**
2383
+ * Detect changes in user intent between prompts and completions
2384
+
2385
+ **Request Body:**
2386
+ - `input.prompts` (string, required): JSON array of prompts in the conversation
2387
+ - `input.completions` (string, required): JSON array of completions in the conversation
2388
+ * Required task output fields: completions, prompts
2389
+ */
2390
+ static intentChange() {
2391
+ return createEvaluator('intent-change');
2392
+ }
2393
+ /**
2394
+ * Validate JSON syntax
2395
+
2396
+ **Request Body:**
2397
+ - `input.text` (string, required): The text to validate as JSON
2398
+ - `config.enable_schema_validation` (bool, optional): Enable JSON schema validation
2399
+ - `config.schema_string` (string, optional): JSON schema to validate against
2400
+ * Required task output fields: text
2401
+ */
2402
+ static jsonValidator(config) {
2403
+ return createEvaluator('json-validator', { config: config });
2404
+ }
2405
+ /**
2406
+ * Measure text perplexity from logprobs
2407
+
2408
+ **Request Body:**
2409
+ - `input.logprobs` (string, required): JSON array of log probabilities from the model
2410
+ * Required task output fields: logprobs
2411
+ */
2412
+ static perplexity() {
2413
+ return createEvaluator('perplexity');
2414
+ }
2415
+ /**
2416
+ * Detect personally identifiable information in text
2417
+
2418
+ **Request Body:**
2419
+ - `input.text` (string, required): The text to scan for personally identifiable information
2420
+ - `config.probability_threshold` (float, optional): Detection threshold (default: 0.8)
2421
+ * Required task output fields: text
2422
+ */
2423
+ static piiDetector(config) {
2424
+ return createEvaluator('pii-detector', { config: config });
2425
+ }
2426
+ /**
2427
+ * Validate text against a placeholder regex pattern
2428
+
2429
+ **Request Body:**
2430
+ - `input.placeholder_value` (string, required): The regex pattern to match against
2431
+ - `input.text` (string, required): The text to validate against the regex pattern
2432
+ - `config.should_match` (bool, optional): Whether the text should match the regex
2433
+ - `config.case_sensitive` (bool, optional): Case-sensitive matching
2434
+ - `config.dot_include_nl` (bool, optional): Dot matches newlines
2435
+ - `config.multi_line` (bool, optional): Multi-line mode
2436
+ * Required task output fields: placeholder_value, text
2437
+ */
2438
+ static placeholderRegex(config) {
2439
+ return createEvaluator('placeholder-regex', { config: config });
2440
+ }
2441
+ /**
2442
+ * Detect profanity in text
2443
+
2444
+ **Request Body:**
2445
+ - `input.text` (string, required): The text to scan for profanity
2446
+ * Required task output fields: text
2447
+ */
2448
+ static profanityDetector() {
2449
+ return createEvaluator('profanity-detector');
2450
+ }
2451
+ /**
2452
+ * Detect prompt injection attempts
2453
+
2454
+ **Request Body:**
2455
+ - `input.prompt` (string, required): The prompt to check for injection attempts
2456
+ - `config.threshold` (float, optional): Detection threshold (default: 0.5)
2457
+ * Required task output fields: prompt
2458
+ */
2459
+ static promptInjection(config) {
2460
+ return createEvaluator('prompt-injection', { config: config });
2461
+ }
2462
+ /**
2463
+ * Measure prompt perplexity to detect potential injection attempts
2464
+
2465
+ **Request Body:**
2466
+ - `input.prompt` (string, required): The prompt to calculate perplexity for
2467
+ * Required task output fields: prompt
2468
+ */
2469
+ static promptPerplexity() {
2470
+ return createEvaluator('prompt-perplexity');
2471
+ }
2472
+ /**
2473
+ * Validate text against a regex pattern
2474
+
2475
+ **Request Body:**
2476
+ - `input.text` (string, required): The text to validate against a regex pattern
2477
+ - `config.regex` (string, optional): The regex pattern to match against
2478
+ - `config.should_match` (bool, optional): Whether the text should match the regex
2479
+ - `config.case_sensitive` (bool, optional): Case-sensitive matching
2480
+ - `config.dot_include_nl` (bool, optional): Dot matches newlines
2481
+ - `config.multi_line` (bool, optional): Multi-line mode
2482
+ * Required task output fields: text
2483
+ */
2484
+ static regexValidator(config) {
2485
+ return createEvaluator('regex-validator', { config: config });
2486
+ }
2487
+ /**
2488
+ * Detect secrets and credentials in text
2489
+
2490
+ **Request Body:**
2491
+ - `input.text` (string, required): The text to scan for secrets (API keys, passwords, etc.)
2492
+ * Required task output fields: text
2493
+ */
2494
+ static secretsDetector() {
2495
+ return createEvaluator('secrets-detector');
2496
+ }
2497
+ /**
2498
+ * Calculate semantic similarity between completion and reference
2499
+
2500
+ **Request Body:**
2501
+ - `input.completion` (string, required): The completion text to compare
2502
+ - `input.reference` (string, required): The reference text to compare against
2503
+ * Required task output fields: completion, reference
2504
+ */
2505
+ static semanticSimilarity() {
2506
+ return createEvaluator('semantic-similarity');
2507
+ }
2508
+ /**
2509
+ * Detect sexist language and bias
2510
+
2511
+ **Request Body:**
2512
+ - `input.text` (string, required): The text to scan for sexist content
2513
+ - `config.threshold` (float, optional): Detection threshold (default: 0.5)
2514
+ * Required task output fields: text
2515
+ */
2516
+ static sexismDetector(config) {
2517
+ return createEvaluator('sexism-detector', { config: config });
2518
+ }
2519
+ /**
2520
+ * Validate SQL query syntax
2521
+
2522
+ **Request Body:**
2523
+ - `input.text` (string, required): The text to validate as SQL
2524
+ * Required task output fields: text
2525
+ */
2526
+ static sqlValidator() {
2527
+ return createEvaluator('sql-validator');
2528
+ }
2529
+ /**
2530
+ * Detect the tone of the text
2531
+
2532
+ **Request Body:**
2533
+ - `input.text` (string, required): The text to detect the tone of
2534
+ * Required task output fields: text
2535
+ */
2536
+ static toneDetection() {
2537
+ return createEvaluator('tone-detection');
2538
+ }
2539
+ /**
2540
+ * Evaluate topic adherence
2541
+
2542
+ **Request Body:**
2543
+ - `input.question` (string, required): The original question
2544
+ - `input.completion` (string, required): The completion to evaluate
2545
+ - `input.reference_topics` (string, required): Comma-separated list of expected topics
2546
+ * Required task output fields: completion, question, reference_topics
2547
+ */
2548
+ static topicAdherence() {
2549
+ return createEvaluator('topic-adherence');
2550
+ }
2551
+ /**
2552
+ * Detect toxic or harmful language
2553
+
2554
+ **Request Body:**
2555
+ - `input.text` (string, required): The text to scan for toxic content
2556
+ - `config.threshold` (float, optional): Detection threshold (default: 0.5)
2557
+ * Required task output fields: text
2558
+ */
2559
+ static toxicityDetector(config) {
2560
+ return createEvaluator('toxicity-detector', { config: config });
2561
+ }
2562
+ /**
2563
+ * Detect uncertainty in the text
2564
+
2565
+ **Request Body:**
2566
+ - `input.prompt` (string, required): The text to detect uncertainty in
2567
+ * Required task output fields: prompt
2568
+ */
2569
+ static uncertaintyDetector() {
2570
+ return createEvaluator('uncertainty-detector');
2571
+ }
2572
+ /**
2573
+ * Count the number of words in text
2574
+
2575
+ **Request Body:**
2576
+ - `input.text` (string, required): The text to count words in
2577
+ * Required task output fields: text
2578
+ */
2579
+ static wordCount() {
2580
+ return createEvaluator('word-count');
2581
+ }
2582
+ /**
2583
+ * Calculate the ratio of words between two texts
2584
+
2585
+ **Request Body:**
2586
+ - `input.numerator_text` (string, required): The numerator text (will be divided by denominator)
2587
+ - `input.denominator_text` (string, required): The denominator text (divides the numerator)
2588
+ * Required task output fields: denominator_text, numerator_text
2589
+ */
2590
+ static wordCountRatio() {
2591
+ return createEvaluator('word-count-ratio');
2592
+ }
2593
+ }
2594
+
2595
+ const validateConfiguration = (options) => {
2596
+ const { apiKey, traceloopSyncEnabled, traceloopSyncMaxRetries, traceloopSyncPollingInterval, traceloopSyncDevPollingInterval, } = options;
2597
+ if (apiKey && typeof apiKey !== "string") {
2598
+ throw new InitializationError('"apiKey" must be a string');
2599
+ }
2600
+ if (traceloopSyncEnabled) {
2601
+ if (typeof traceloopSyncMaxRetries !== "number" ||
2602
+ traceloopSyncMaxRetries <= 0) {
2603
+ throw new InitializationError('"traceloopSyncMaxRetries" must be an integer greater than 0.');
2604
+ }
2605
+ if (typeof traceloopSyncPollingInterval !== "number" ||
2606
+ traceloopSyncPollingInterval <= 0) {
2607
+ throw new InitializationError('"traceloopSyncPollingInterval" must be an integer greater than 0.');
2608
+ }
2609
+ if (typeof traceloopSyncDevPollingInterval !== "number" ||
2610
+ traceloopSyncDevPollingInterval <= 0) {
2611
+ throw new InitializationError('"traceloopSyncDevPollingInterval" must be an integer greater than 0.');
2612
+ }
2613
+ }
2614
+ };
2615
+
2616
+ const TRACER_NAME = "@anyway-sh/node-server-sdk";
2617
+ const TRACER_VERSION = version;
2618
+ const WORKFLOW_NAME_KEY = api.createContextKey("workflow_name");
2619
+ const ENTITY_NAME_KEY = api.createContextKey("entity_name");
2620
+ const AGENT_NAME_KEY = api.createContextKey("agent_name");
2621
+ const CONVERSATION_ID_KEY = api.createContextKey("conversation_id");
2622
+ const ASSOCATION_PROPERTIES_KEY = api.createContextKey("association_properties");
2623
+ const getTracer = () => {
2624
+ return api.trace.getTracer(TRACER_NAME, TRACER_VERSION);
2625
+ };
2626
+ const getTraceloopTracer = getTracer;
2627
+ const getEntityPath = (entityContext) => {
2628
+ const path = entityContext.getValue(ENTITY_NAME_KEY);
2629
+ return path ? `${path}` : undefined;
2630
+ };
2631
+
2632
+ const AI_GENERATE_TEXT = "ai.generateText";
2633
+ const AI_STREAM_TEXT = "ai.streamText";
2634
+ const AI_GENERATE_OBJECT = "ai.generateObject";
2635
+ const AI_STREAM_OBJECT = "ai.streamObject";
2636
+ const AI_GENERATE_TEXT_DO_GENERATE = "ai.generateText.doGenerate";
2637
+ const AI_GENERATE_OBJECT_DO_GENERATE = "ai.generateObject.doGenerate";
2638
+ const AI_STREAM_TEXT_DO_STREAM = "ai.streamText.doStream";
2639
+ const AI_STREAM_OBJECT_DO_STREAM = "ai.streamObject.doStream";
2640
+ const HANDLED_SPAN_NAMES = {
2641
+ [AI_GENERATE_TEXT]: "run.ai",
2642
+ [AI_STREAM_TEXT]: "stream.ai",
2643
+ [AI_GENERATE_OBJECT]: "object.ai",
2644
+ [AI_STREAM_OBJECT]: "stream-object.ai",
2645
+ [AI_GENERATE_TEXT_DO_GENERATE]: "text.generate",
2646
+ [AI_GENERATE_OBJECT_DO_GENERATE]: "object.generate",
2647
+ [AI_STREAM_TEXT_DO_STREAM]: "text.stream",
2648
+ [AI_STREAM_OBJECT_DO_STREAM]: "object.stream",
2649
+ };
2650
+ const TOOL_SPAN_NAME = "ai.toolCall";
2651
+ const AI_RESPONSE_TEXT = "ai.response.text";
2652
+ const AI_RESPONSE_OBJECT = "ai.response.object";
2653
+ const AI_RESPONSE_TOOL_CALLS = "ai.response.toolCalls";
2654
+ const AI_RESPONSE_PROVIDER_METADATA = "ai.response.providerMetadata";
2655
+ const AI_PROMPT_MESSAGES = "ai.prompt.messages";
2656
+ const AI_PROMPT = "ai.prompt";
2657
+ const AI_USAGE_PROMPT_TOKENS = "ai.usage.promptTokens";
2658
+ const AI_USAGE_COMPLETION_TOKENS = "ai.usage.completionTokens";
2659
+ const AI_MODEL_PROVIDER = "ai.model.provider";
2660
+ const AI_PROMPT_TOOLS = "ai.prompt.tools";
2661
+ const AI_TELEMETRY_METADATA_PREFIX = "ai.telemetry.metadata.";
2662
+ const TYPE_TEXT = "text";
2663
+ const TYPE_TOOL_CALL = "tool_call";
2664
+ const ROLE_ASSISTANT = "assistant";
2665
+ const ROLE_USER = "user";
2666
+ // Vendor mapping from AI SDK provider prefixes to standardized LLM_SYSTEM values
2667
+ // Uses prefixes to match AI SDK patterns like "openai.chat", "anthropic.messages", etc.
2668
+ const VENDOR_MAPPING = {
2669
+ openai: "OpenAI",
2670
+ azure: "Azure",
2671
+ "azure-openai": "Azure",
2672
+ anthropic: "Anthropic",
2673
+ cohere: "Cohere",
2674
+ mistral: "MistralAI",
2675
+ groq: "Groq",
2676
+ replicate: "Replicate",
2677
+ together: "TogetherAI",
2678
+ fireworks: "Fireworks",
2679
+ deepseek: "DeepSeek",
2680
+ perplexity: "Perplexity",
2681
+ "amazon-bedrock": "AWS",
2682
+ bedrock: "AWS",
2683
+ google: "Google",
2684
+ vertex: "Google",
2685
+ ollama: "Ollama",
2686
+ huggingface: "HuggingFace",
2687
+ openrouter: "OpenRouter",
2688
+ };
2689
+ const getAgentNameFromAttributes = (attributes) => {
2690
+ const agentAttr = attributes[`${AI_TELEMETRY_METADATA_PREFIX}agent`];
2691
+ return agentAttr && typeof agentAttr === "string" ? agentAttr : null;
2692
+ };
2693
+ const transformResponseText = (attributes) => {
2694
+ if (AI_RESPONSE_TEXT in attributes) {
2695
+ attributes[`${incubating.ATTR_GEN_AI_COMPLETION}.0.content`] =
2696
+ attributes[AI_RESPONSE_TEXT];
2697
+ attributes[`${incubating.ATTR_GEN_AI_COMPLETION}.0.role`] = ROLE_ASSISTANT;
2698
+ const outputMessage = {
2699
+ role: ROLE_ASSISTANT,
2700
+ parts: [
2701
+ {
2702
+ type: TYPE_TEXT,
2703
+ content: attributes[AI_RESPONSE_TEXT],
2704
+ },
2705
+ ],
2706
+ };
2707
+ attributes[incubating.ATTR_GEN_AI_OUTPUT_MESSAGES] = JSON.stringify([outputMessage]);
2708
+ delete attributes[AI_RESPONSE_TEXT];
2709
+ }
2710
+ };
2711
+ const transformResponseObject = (attributes) => {
2712
+ if (AI_RESPONSE_OBJECT in attributes) {
2713
+ attributes[`${incubating.ATTR_GEN_AI_COMPLETION}.0.content`] =
2714
+ attributes[AI_RESPONSE_OBJECT];
2715
+ attributes[`${incubating.ATTR_GEN_AI_COMPLETION}.0.role`] = ROLE_ASSISTANT;
2716
+ const outputMessage = {
2717
+ role: ROLE_ASSISTANT,
2718
+ parts: [
2719
+ {
2720
+ type: TYPE_TEXT,
2721
+ content: attributes[AI_RESPONSE_OBJECT],
2722
+ },
2723
+ ],
2724
+ };
2725
+ attributes[incubating.ATTR_GEN_AI_OUTPUT_MESSAGES] = JSON.stringify([outputMessage]);
2726
+ delete attributes[AI_RESPONSE_OBJECT];
2727
+ }
2728
+ };
2729
+ const transformResponseToolCalls = (attributes) => {
2730
+ if (AI_RESPONSE_TOOL_CALLS in attributes) {
2731
+ try {
2732
+ const toolCalls = JSON.parse(attributes[AI_RESPONSE_TOOL_CALLS]);
2733
+ attributes[`${incubating.ATTR_GEN_AI_COMPLETION}.0.role`] = ROLE_ASSISTANT;
2734
+ const toolCallParts = [];
2735
+ toolCalls.forEach((toolCall, index) => {
2736
+ var _a;
2737
+ // Support both v4 (args) and v5 (input) formats
2738
+ // Prefer v5 (input) if present
2739
+ const toolArgs = (_a = toolCall.input) !== null && _a !== void 0 ? _a : toolCall.args;
2740
+ attributes[`${incubating.ATTR_GEN_AI_COMPLETION}.0.tool_calls.${index}.name`] =
2741
+ toolCall.toolName;
2742
+ attributes[`${incubating.ATTR_GEN_AI_COMPLETION}.0.tool_calls.${index}.arguments`] = toolArgs;
2743
+ toolCallParts.push({
2744
+ type: TYPE_TOOL_CALL,
2745
+ tool_call: {
2746
+ name: toolCall.toolName,
2747
+ arguments: toolArgs,
2748
+ },
2749
+ });
2750
+ });
2751
+ if (toolCallParts.length > 0) {
2752
+ const outputMessage = {
2753
+ role: ROLE_ASSISTANT,
2754
+ parts: toolCallParts,
2755
+ };
2756
+ attributes[incubating.ATTR_GEN_AI_OUTPUT_MESSAGES] = JSON.stringify([
2757
+ outputMessage,
2758
+ ]);
2759
+ }
2760
+ delete attributes[AI_RESPONSE_TOOL_CALLS];
2761
+ }
2762
+ catch (_a) {
2763
+ // Ignore parsing errors
2764
+ }
2765
+ }
2766
+ };
2767
+ const processMessageContent = (content) => {
2768
+ if (Array.isArray(content)) {
2769
+ const textItems = content.filter((item) => item &&
2770
+ typeof item === "object" &&
2771
+ item.type === TYPE_TEXT &&
2772
+ item.text);
2773
+ if (textItems.length > 0) {
2774
+ const joinedText = textItems.map((item) => item.text).join(" ");
2775
+ return joinedText;
2776
+ }
2777
+ else {
2778
+ return JSON.stringify(content);
2779
+ }
2780
+ }
2781
+ if (content && typeof content === "object") {
2782
+ if (content.type === TYPE_TEXT && content.text) {
2783
+ return content.text;
2784
+ }
2785
+ return JSON.stringify(content);
2786
+ }
2787
+ if (typeof content === "string") {
2788
+ try {
2789
+ const parsed = JSON.parse(content);
2790
+ if (Array.isArray(parsed)) {
2791
+ const allTextItems = parsed.every((item) => item &&
2792
+ typeof item === "object" &&
2793
+ item.type === TYPE_TEXT &&
2794
+ item.text);
2795
+ if (allTextItems && parsed.length > 0) {
2796
+ return parsed.map((item) => item.text).join(" ");
2797
+ }
2798
+ else {
2799
+ return content;
2800
+ }
2801
+ }
2802
+ }
2803
+ catch (_a) {
2804
+ // Ignore parsing errors
2805
+ }
2806
+ return content;
2807
+ }
2808
+ return String(content);
2809
+ };
2810
+ const transformTools = (attributes) => {
2811
+ if (AI_PROMPT_TOOLS in attributes) {
2812
+ try {
2813
+ const tools = attributes[AI_PROMPT_TOOLS];
2814
+ if (Array.isArray(tools)) {
2815
+ tools.forEach((toolItem, index) => {
2816
+ var _a;
2817
+ let tool = toolItem;
2818
+ if (typeof toolItem === "string") {
2819
+ try {
2820
+ tool = JSON.parse(toolItem);
2821
+ }
2822
+ catch (_b) {
2823
+ return;
2824
+ }
2825
+ }
2826
+ if (tool && typeof tool === "object") {
2827
+ if (tool.name) {
2828
+ attributes[`${aiSemanticConventions.SpanAttributes.LLM_REQUEST_FUNCTIONS}.${index}.name`] = tool.name;
2829
+ }
2830
+ if (tool.description) {
2831
+ attributes[`${aiSemanticConventions.SpanAttributes.LLM_REQUEST_FUNCTIONS}.${index}.description`] = tool.description;
2832
+ }
2833
+ // Support both v4 (parameters) and v5 (inputSchema) formats
2834
+ // Prefer v5 (inputSchema) if present
2835
+ const schema = (_a = tool.inputSchema) !== null && _a !== void 0 ? _a : tool.parameters;
2836
+ if (schema) {
2837
+ attributes[`${aiSemanticConventions.SpanAttributes.LLM_REQUEST_FUNCTIONS}.${index}.parameters`] = typeof schema === "string" ? schema : JSON.stringify(schema);
2838
+ }
2839
+ }
2840
+ });
2841
+ }
2842
+ delete attributes[AI_PROMPT_TOOLS];
2843
+ }
2844
+ catch (_a) {
2845
+ // Ignore parsing errors
2846
+ }
2847
+ }
2848
+ };
2849
+ const transformPrompts = (attributes) => {
2850
+ if (AI_PROMPT_MESSAGES in attributes) {
2851
+ try {
2852
+ let jsonString = attributes[AI_PROMPT_MESSAGES];
2853
+ try {
2854
+ JSON.parse(jsonString);
2855
+ }
2856
+ catch (_a) {
2857
+ jsonString = jsonString.replace(/\\'/g, "'");
2858
+ jsonString = jsonString.replace(/\\\\\\\\/g, "\\\\");
2859
+ }
2860
+ const messages = JSON.parse(jsonString);
2861
+ const inputMessages = [];
2862
+ messages.forEach((msg, index) => {
2863
+ const processedContent = processMessageContent(msg.content);
2864
+ const contentKey = `${incubating.ATTR_GEN_AI_PROMPT}.${index}.content`;
2865
+ attributes[contentKey] = processedContent;
2866
+ attributes[`${incubating.ATTR_GEN_AI_PROMPT}.${index}.role`] = msg.role;
2867
+ // Add to OpenTelemetry standard gen_ai.input.messages format
2868
+ inputMessages.push({
2869
+ role: msg.role,
2870
+ parts: [
2871
+ {
2872
+ type: TYPE_TEXT,
2873
+ content: processedContent,
2874
+ },
2875
+ ],
2876
+ });
2877
+ });
2878
+ // Set the OpenTelemetry standard input messages attribute
2879
+ if (inputMessages.length > 0) {
2880
+ attributes[incubating.ATTR_GEN_AI_INPUT_MESSAGES] = JSON.stringify(inputMessages);
2881
+ }
2882
+ delete attributes[AI_PROMPT_MESSAGES];
2883
+ }
2884
+ catch (_b) {
2885
+ // Ignore parsing errors
2886
+ }
2887
+ }
2888
+ if (AI_PROMPT in attributes) {
2889
+ try {
2890
+ const promptData = JSON.parse(attributes[AI_PROMPT]);
2891
+ if (promptData.messages && Array.isArray(promptData.messages)) {
2892
+ const messages = promptData.messages;
2893
+ const inputMessages = [];
2894
+ messages.forEach((msg, index) => {
2895
+ const processedContent = processMessageContent(msg.content);
2896
+ const contentKey = `${incubating.ATTR_GEN_AI_PROMPT}.${index}.content`;
2897
+ attributes[contentKey] = processedContent;
2898
+ attributes[`${incubating.ATTR_GEN_AI_PROMPT}.${index}.role`] = msg.role;
2899
+ inputMessages.push({
2900
+ role: msg.role,
2901
+ parts: [
2902
+ {
2903
+ type: TYPE_TEXT,
2904
+ content: processedContent,
2905
+ },
2906
+ ],
2907
+ });
2908
+ });
2909
+ if (inputMessages.length > 0) {
2910
+ attributes[incubating.ATTR_GEN_AI_INPUT_MESSAGES] =
2911
+ JSON.stringify(inputMessages);
2912
+ }
2913
+ delete attributes[AI_PROMPT];
2914
+ }
2915
+ else if (promptData.prompt && typeof promptData.prompt === "string") {
2916
+ attributes[`${incubating.ATTR_GEN_AI_PROMPT}.0.content`] = promptData.prompt;
2917
+ attributes[`${incubating.ATTR_GEN_AI_PROMPT}.0.role`] = ROLE_USER;
2918
+ const inputMessage = {
2919
+ role: ROLE_USER,
2920
+ parts: [
2921
+ {
2922
+ type: TYPE_TEXT,
2923
+ content: promptData.prompt,
2924
+ },
2925
+ ],
2926
+ };
2927
+ attributes[incubating.ATTR_GEN_AI_INPUT_MESSAGES] = JSON.stringify([inputMessage]);
2928
+ delete attributes[AI_PROMPT];
2929
+ }
2930
+ }
2931
+ catch (_c) {
2932
+ // Ignore parsing errors
2933
+ }
2934
+ }
2935
+ };
2936
+ const transformPromptTokens = (attributes) => {
2937
+ if (!(incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS in attributes) &&
2938
+ AI_USAGE_PROMPT_TOKENS in attributes) {
2939
+ attributes[incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS] =
2940
+ attributes[AI_USAGE_PROMPT_TOKENS];
2941
+ }
2942
+ delete attributes[AI_USAGE_PROMPT_TOKENS];
2943
+ };
2944
+ const transformCompletionTokens = (attributes) => {
2945
+ if (!(incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS in attributes) &&
2946
+ AI_USAGE_COMPLETION_TOKENS in attributes) {
2947
+ attributes[incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS] =
2948
+ attributes[AI_USAGE_COMPLETION_TOKENS];
2949
+ }
2950
+ delete attributes[AI_USAGE_COMPLETION_TOKENS];
2951
+ };
2952
+ const transformProviderMetadata = (attributes) => {
2953
+ if (AI_RESPONSE_PROVIDER_METADATA in attributes) {
2954
+ try {
2955
+ const metadataStr = attributes[AI_RESPONSE_PROVIDER_METADATA];
2956
+ let metadata;
2957
+ if (typeof metadataStr === "string") {
2958
+ metadata = JSON.parse(metadataStr);
2959
+ }
2960
+ else if (typeof metadataStr === "object") {
2961
+ metadata = metadataStr;
2962
+ }
2963
+ else {
2964
+ return;
2965
+ }
2966
+ if (metadata.anthropic) {
2967
+ const anthropicMetadata = metadata.anthropic;
2968
+ if (anthropicMetadata.cacheCreationInputTokens !== undefined) {
2969
+ attributes[aiSemanticConventions.SpanAttributes.GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS] =
2970
+ anthropicMetadata.cacheCreationInputTokens;
2971
+ }
2972
+ if (anthropicMetadata.cacheReadInputTokens !== undefined) {
2973
+ attributes[aiSemanticConventions.SpanAttributes.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS] =
2974
+ anthropicMetadata.cacheReadInputTokens;
2975
+ }
2976
+ }
2977
+ if (metadata.openai) {
2978
+ const openaiMetadata = metadata.openai;
2979
+ if (openaiMetadata.cachedPromptTokens !== undefined) {
2980
+ attributes[aiSemanticConventions.SpanAttributes.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS] =
2981
+ openaiMetadata.cachedPromptTokens;
2982
+ }
2983
+ if (openaiMetadata.reasoningTokens !== undefined) {
2984
+ attributes[aiSemanticConventions.SpanAttributes.GEN_AI_USAGE_REASONING_TOKENS] =
2985
+ openaiMetadata.reasoningTokens;
2986
+ }
2987
+ }
2988
+ delete attributes[AI_RESPONSE_PROVIDER_METADATA];
2989
+ }
2990
+ catch (_a) {
2991
+ // Ignore JSON parsing errors
2992
+ }
2993
+ }
2994
+ };
2995
+ const calculateTotalTokens = (attributes) => {
2996
+ const inputTokens = attributes[incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS];
2997
+ const outputTokens = attributes[incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS];
2998
+ if (inputTokens && outputTokens) {
2999
+ attributes[`${aiSemanticConventions.SpanAttributes.LLM_USAGE_TOTAL_TOKENS}`] =
3000
+ Number(inputTokens) + Number(outputTokens);
3001
+ }
3002
+ };
3003
+ const transformVendor = (attributes) => {
3004
+ if (AI_MODEL_PROVIDER in attributes) {
3005
+ const vendor = attributes[AI_MODEL_PROVIDER];
3006
+ // Find matching vendor prefix in mapping
3007
+ let mappedVendor = null;
3008
+ let providerName = vendor;
3009
+ if (typeof vendor === "string" && vendor.length > 0) {
3010
+ // Extract provider name (part before first dot, or entire string if no dot)
3011
+ const dotIndex = vendor.indexOf(".");
3012
+ providerName = dotIndex > 0 ? vendor.substring(0, dotIndex) : vendor;
3013
+ for (const prefix of Object.keys(VENDOR_MAPPING)) {
3014
+ if (vendor.startsWith(prefix)) {
3015
+ mappedVendor = VENDOR_MAPPING[prefix];
3016
+ break;
3017
+ }
3018
+ }
3019
+ attributes[incubating.ATTR_GEN_AI_SYSTEM] = mappedVendor || vendor;
3020
+ attributes[incubating.ATTR_GEN_AI_PROVIDER_NAME] = providerName;
3021
+ }
3022
+ delete attributes[AI_MODEL_PROVIDER];
3023
+ }
3024
+ };
3025
+ const transformOperationName = (attributes, spanName) => {
3026
+ if (!spanName)
3027
+ return;
3028
+ let operationName;
3029
+ if (spanName.includes("generateText") ||
3030
+ spanName.includes("streamText") ||
3031
+ spanName.includes("generateObject") ||
3032
+ spanName.includes("streamObject")) {
3033
+ operationName = "chat";
3034
+ }
3035
+ else if (spanName === "ai.toolCall" || spanName.endsWith(".tool")) {
3036
+ operationName = "execute_tool";
3037
+ }
3038
+ if (operationName) {
3039
+ attributes[incubating.ATTR_GEN_AI_OPERATION_NAME] = operationName;
3040
+ }
3041
+ };
3042
+ const transformModelId = (attributes) => {
3043
+ const AI_MODEL_ID = "ai.model.id";
3044
+ if (AI_MODEL_ID in attributes) {
3045
+ attributes[incubating.ATTR_GEN_AI_REQUEST_MODEL] = attributes[AI_MODEL_ID];
3046
+ delete attributes[AI_MODEL_ID];
3047
+ }
3048
+ };
3049
+ const transformFinishReason = (attributes) => {
3050
+ const AI_RESPONSE_FINISH_REASON = "ai.response.finishReason";
3051
+ if (AI_RESPONSE_FINISH_REASON in attributes) {
3052
+ const finishReason = attributes[AI_RESPONSE_FINISH_REASON];
3053
+ attributes[incubating.ATTR_GEN_AI_RESPONSE_FINISH_REASONS] = Array.isArray(finishReason)
3054
+ ? finishReason
3055
+ : [finishReason];
3056
+ delete attributes[AI_RESPONSE_FINISH_REASON];
3057
+ }
3058
+ };
3059
+ const transformToolCallAttributes = (attributes) => {
3060
+ var _a, _b;
3061
+ if ("ai.toolCall.name" in attributes) {
3062
+ attributes[incubating.ATTR_GEN_AI_TOOL_NAME] = attributes["ai.toolCall.name"];
3063
+ // Keep ai.toolCall.name for now, will be deleted in transformToolCalls
3064
+ }
3065
+ if ("ai.toolCall.id" in attributes) {
3066
+ attributes[incubating.ATTR_GEN_AI_TOOL_CALL_ID] = attributes["ai.toolCall.id"];
3067
+ delete attributes["ai.toolCall.id"];
3068
+ }
3069
+ // Support both v4 (args) and v5 (input) formats
3070
+ // Prefer v5 (input) if present
3071
+ const toolArgs = (_a = attributes["ai.toolCall.input"]) !== null && _a !== void 0 ? _a : attributes["ai.toolCall.args"];
3072
+ if (toolArgs !== undefined) {
3073
+ attributes[incubating.ATTR_GEN_AI_TOOL_CALL_ARGUMENTS] = toolArgs;
3074
+ // Don't delete yet - transformToolCalls will handle entity input/output
3075
+ }
3076
+ // Support both v4 (result) and v5 (output) formats
3077
+ // Prefer v5 (output) if present
3078
+ const toolResult = (_b = attributes["ai.toolCall.output"]) !== null && _b !== void 0 ? _b : attributes["ai.toolCall.result"];
3079
+ if (toolResult !== undefined) {
3080
+ attributes[incubating.ATTR_GEN_AI_TOOL_CALL_RESULT] = toolResult;
3081
+ // Don't delete yet - transformToolCalls will handle entity input/output
3082
+ }
3083
+ };
3084
+ const transformConversationId = (attributes) => {
3085
+ const conversationId = attributes["ai.telemetry.metadata.conversationId"];
3086
+ const sessionId = attributes["ai.telemetry.metadata.sessionId"];
3087
+ if (conversationId) {
3088
+ attributes[incubating.ATTR_GEN_AI_CONVERSATION_ID] = conversationId;
3089
+ }
3090
+ else if (sessionId) {
3091
+ attributes[incubating.ATTR_GEN_AI_CONVERSATION_ID] = sessionId;
3092
+ }
3093
+ };
3094
+ const transformResponseMetadata = (attributes) => {
3095
+ const AI_RESPONSE_MODEL = "ai.response.model";
3096
+ const AI_RESPONSE_ID = "ai.response.id";
3097
+ if (AI_RESPONSE_MODEL in attributes) {
3098
+ attributes[incubating.ATTR_GEN_AI_RESPONSE_MODEL] = attributes[AI_RESPONSE_MODEL];
3099
+ delete attributes[AI_RESPONSE_MODEL];
3100
+ }
3101
+ if (AI_RESPONSE_ID in attributes) {
3102
+ attributes[incubating.ATTR_GEN_AI_RESPONSE_ID] = attributes[AI_RESPONSE_ID];
3103
+ delete attributes[AI_RESPONSE_ID];
3104
+ }
3105
+ };
3106
+ const transformTelemetryMetadata = (attributes, spanName) => {
3107
+ const keysToDelete = [];
3108
+ // Use the helper function to extract agent name
3109
+ const agentName = getAgentNameFromAttributes(attributes);
3110
+ // Find all ai.telemetry.metadata.* attributes
3111
+ for (const [key, value] of Object.entries(attributes)) {
3112
+ if (key.startsWith(AI_TELEMETRY_METADATA_PREFIX)) {
3113
+ const metadataKey = key.substring(AI_TELEMETRY_METADATA_PREFIX.length);
3114
+ // Always mark for deletion since it's a telemetry metadata attribute
3115
+ keysToDelete.push(key);
3116
+ if (metadataKey && value != null) {
3117
+ // Convert value to string for association properties
3118
+ const stringValue = typeof value === "string" ? value : String(value);
3119
+ // Also set as traceloop association property attribute
3120
+ attributes[`${aiSemanticConventions.SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES}.${metadataKey}`] = stringValue;
3121
+ }
3122
+ }
3123
+ }
3124
+ if (agentName) {
3125
+ attributes[incubating.ATTR_GEN_AI_AGENT_NAME] = agentName;
3126
+ const topLevelSpanNames = [
3127
+ AI_GENERATE_TEXT,
3128
+ AI_STREAM_TEXT,
3129
+ AI_GENERATE_OBJECT,
3130
+ AI_STREAM_OBJECT,
3131
+ ];
3132
+ if (spanName &&
3133
+ (spanName === `${agentName}.agent` ||
3134
+ topLevelSpanNames.includes(spanName))) {
3135
+ attributes[aiSemanticConventions.SpanAttributes.TRACELOOP_SPAN_KIND] =
3136
+ aiSemanticConventions.TraceloopSpanKindValues.AGENT;
3137
+ attributes[aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_NAME] = agentName;
3138
+ const inputMessages = attributes[incubating.ATTR_GEN_AI_INPUT_MESSAGES];
3139
+ const outputMessages = attributes[incubating.ATTR_GEN_AI_OUTPUT_MESSAGES];
3140
+ const toolArgs = attributes["ai.toolCall.args"];
3141
+ const toolResult = attributes["ai.toolCall.result"];
3142
+ if (inputMessages || outputMessages) {
3143
+ if (inputMessages) {
3144
+ attributes[aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_INPUT] = inputMessages;
3145
+ }
3146
+ if (outputMessages) {
3147
+ attributes[aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_OUTPUT] = outputMessages;
3148
+ }
3149
+ }
3150
+ else if (toolArgs || toolResult) {
3151
+ if (toolArgs) {
3152
+ attributes[aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_INPUT] = toolArgs;
3153
+ }
3154
+ if (toolResult) {
3155
+ attributes[aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_OUTPUT] = toolResult;
3156
+ }
3157
+ }
3158
+ }
3159
+ }
3160
+ keysToDelete.forEach((key) => {
3161
+ delete attributes[key];
3162
+ });
3163
+ };
3164
+ const transformLLMSpans = (attributes, spanName) => {
3165
+ transformOperationName(attributes, spanName);
3166
+ transformModelId(attributes);
3167
+ transformResponseText(attributes);
3168
+ transformResponseObject(attributes);
3169
+ transformResponseToolCalls(attributes);
3170
+ transformPrompts(attributes);
3171
+ transformTools(attributes);
3172
+ transformPromptTokens(attributes);
3173
+ transformCompletionTokens(attributes);
3174
+ transformProviderMetadata(attributes);
3175
+ transformFinishReason(attributes);
3176
+ transformResponseMetadata(attributes);
3177
+ calculateTotalTokens(attributes);
3178
+ transformVendor(attributes); // Also sets GEN_AI_PROVIDER_NAME
3179
+ transformConversationId(attributes);
3180
+ transformToolCallAttributes(attributes);
3181
+ transformTelemetryMetadata(attributes, spanName);
3182
+ };
3183
+ const transformToolCalls = (span) => {
3184
+ var _a, _b;
3185
+ // Support both v4 (args/result) and v5 (input/output) formats
3186
+ // Prefer v5 (input/output) if present
3187
+ const toolInput = (_a = span.attributes["ai.toolCall.input"]) !== null && _a !== void 0 ? _a : span.attributes["ai.toolCall.args"];
3188
+ const toolOutput = (_b = span.attributes["ai.toolCall.output"]) !== null && _b !== void 0 ? _b : span.attributes["ai.toolCall.result"];
3189
+ if (toolInput && toolOutput) {
3190
+ span.attributes[aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_INPUT] = toolInput;
3191
+ delete span.attributes["ai.toolCall.args"];
3192
+ delete span.attributes["ai.toolCall.input"];
3193
+ span.attributes[aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_OUTPUT] = toolOutput;
3194
+ delete span.attributes["ai.toolCall.result"];
3195
+ delete span.attributes["ai.toolCall.output"];
3196
+ span.attributes[aiSemanticConventions.SpanAttributes.TRACELOOP_SPAN_KIND] =
3197
+ aiSemanticConventions.TraceloopSpanKindValues.TOOL;
3198
+ // Set entity name from tool call name
3199
+ const toolName = span.attributes["ai.toolCall.name"];
3200
+ if (toolName) {
3201
+ span.attributes[aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_NAME] = toolName;
3202
+ delete span.attributes["ai.toolCall.name"];
3203
+ }
3204
+ }
3205
+ };
3206
+ const shouldHandleSpan = (span) => {
3207
+ var _a;
3208
+ return ((_a = span.instrumentationScope) === null || _a === void 0 ? void 0 : _a.name) === "ai";
3209
+ };
3210
+ const getAiSdkVersion = () => {
3211
+ try {
3212
+ return require("ai/package.json").version;
3213
+ }
3214
+ catch (_a) {
3215
+ return undefined;
3216
+ }
3217
+ };
3218
+ const TOP_LEVEL_AI_SPANS = [
3219
+ AI_GENERATE_TEXT,
3220
+ AI_STREAM_TEXT,
3221
+ AI_GENERATE_OBJECT,
3222
+ AI_STREAM_OBJECT,
3223
+ ];
3224
+ const transformAiSdkSpanNames = (span) => {
3225
+ if (span.name === TOOL_SPAN_NAME) {
3226
+ span.updateName(`${span.attributes["ai.toolCall.name"]}.tool`);
3227
+ }
3228
+ if (span.name in HANDLED_SPAN_NAMES) {
3229
+ const agentName = getAgentNameFromAttributes(span.attributes);
3230
+ const isTopLevelSpan = TOP_LEVEL_AI_SPANS.includes(span.name);
3231
+ if (agentName && isTopLevelSpan) {
3232
+ span.updateName(`${agentName}.agent`);
3233
+ }
3234
+ else if (!isTopLevelSpan) {
3235
+ span.updateName(HANDLED_SPAN_NAMES[span.name]);
3236
+ }
3237
+ }
3238
+ };
3239
+ const transformAiSdkSpanAttributes = (span) => {
3240
+ if (!shouldHandleSpan(span)) {
3241
+ return;
3242
+ }
3243
+ const aiSdkVersion = getAiSdkVersion();
3244
+ if (aiSdkVersion) {
3245
+ span.attributes["ai.sdk.version"] = aiSdkVersion;
3246
+ }
3247
+ transformLLMSpans(span.attributes, span.name);
3248
+ transformToolCalls(span);
3249
+ };
3250
+
3251
+ function parseKeyPairsIntoRecord(keyPairs) {
3252
+ const result = {};
3253
+ if (!keyPairs)
3254
+ return result;
3255
+ keyPairs.split(",").forEach((pair) => {
3256
+ const [key, value] = pair.split("=");
3257
+ if (key && value) {
3258
+ result[key.trim()] = value.trim();
3259
+ }
3260
+ });
3261
+ return result;
3262
+ }
3263
+
3264
+ const ALL_INSTRUMENTATION_LIBRARIES = "all";
3265
+ const spanAgentNames = new Map();
3266
+ const SPAN_AGENT_NAME_TTL = 5 * 60 * 1000;
3267
+ const AI_TELEMETRY_METADATA_AGENT = "ai.telemetry.metadata.agent";
3268
+ const cleanupExpiredSpanAgentNames = () => {
3269
+ const now = Date.now();
3270
+ for (const [spanId, entry] of spanAgentNames.entries()) {
3271
+ if (now - entry.timestamp > SPAN_AGENT_NAME_TTL) {
3272
+ spanAgentNames.delete(spanId);
3273
+ }
3274
+ }
3275
+ };
3276
+ /**
3277
+ * Creates a span processor with Traceloop's custom span handling logic.
3278
+ * This can be used independently of the full SDK initialization.
3279
+ *
3280
+ * @param options - Configuration options for the span processor
3281
+ * @returns A configured SpanProcessor instance
3282
+ */
3283
+ const createSpanProcessor = (options) => {
3284
+ var _a;
3285
+ const url = `${options.baseUrl || process.env.ANYWAY_BASE_URL || "https://api.traceloop.com"}/v1/traces`;
3286
+ const headers = options.headers ||
3287
+ (process.env.ANYWAY_HEADERS
3288
+ ? parseKeyPairsIntoRecord(process.env.ANYWAY_HEADERS)
3289
+ : { Authorization: `Bearer ${options.apiKey}` });
3290
+ const traceExporter = (_a = options.exporter) !== null && _a !== void 0 ? _a : new exporterTraceOtlpProto.OTLPTraceExporter({
3291
+ url,
3292
+ headers,
3293
+ });
3294
+ const spanProcessor = options.disableBatch
3295
+ ? new sdkTraceNode.SimpleSpanProcessor(traceExporter)
3296
+ : new sdkTraceNode.BatchSpanProcessor(traceExporter);
3297
+ // Store the original onEnd method
3298
+ const originalOnEnd = spanProcessor.onEnd.bind(spanProcessor);
3299
+ spanProcessor.onStart = onSpanStart;
3300
+ if (options.allowedInstrumentationLibraries === ALL_INSTRUMENTATION_LIBRARIES) {
3301
+ spanProcessor.onEnd = onSpanEnd(originalOnEnd);
3302
+ }
3303
+ else {
3304
+ const instrumentationLibraries = [...traceloopInstrumentationLibraries];
3305
+ if (options.allowedInstrumentationLibraries) {
3306
+ instrumentationLibraries.push(...options.allowedInstrumentationLibraries);
3307
+ }
3308
+ spanProcessor.onEnd = onSpanEnd(originalOnEnd, instrumentationLibraries);
3309
+ }
3310
+ return spanProcessor;
3311
+ };
3312
+ const traceloopInstrumentationLibraries = [
3313
+ "ai",
3314
+ "@anyway-sh/node-server-sdk",
3315
+ "@traceloop/instrumentation-openai",
3316
+ "@traceloop/instrumentation-langchain",
3317
+ "@traceloop/instrumentation-chroma",
3318
+ "@traceloop/instrumentation-anthropic",
3319
+ "@traceloop/instrumentation-llamaindex",
3320
+ "@traceloop/instrumentation-vertexai",
3321
+ "@traceloop/instrumentation-bedrock",
3322
+ "@traceloop/instrumentation-cohere",
3323
+ "@traceloop/instrumentation-pinecone",
3324
+ "@traceloop/instrumentation-qdrant",
3325
+ "@traceloop/instrumentation-together",
3326
+ "@traceloop/instrumentation-mcp",
3327
+ ];
3328
+ const onSpanStart = (span) => {
3329
+ const workflowName = api.context.active().getValue(WORKFLOW_NAME_KEY);
3330
+ if (workflowName) {
3331
+ span.setAttribute(aiSemanticConventions.SpanAttributes.TRACELOOP_WORKFLOW_NAME, workflowName);
3332
+ }
3333
+ const entityName = api.context.active().getValue(ENTITY_NAME_KEY);
3334
+ if (entityName) {
3335
+ span.setAttribute(aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_PATH, entityName);
3336
+ }
3337
+ let agentName = api.context.active().getValue(AGENT_NAME_KEY);
3338
+ if (!agentName) {
3339
+ const aiSdkAgent = span.attributes[AI_TELEMETRY_METADATA_AGENT];
3340
+ if (aiSdkAgent && typeof aiSdkAgent === "string") {
3341
+ agentName = aiSdkAgent;
3342
+ }
3343
+ }
3344
+ if (!agentName) {
3345
+ const parentSpanContext = span.parentSpanContext;
3346
+ const parentSpanId = parentSpanContext === null || parentSpanContext === void 0 ? void 0 : parentSpanContext.spanId;
3347
+ if (parentSpanId &&
3348
+ parentSpanId !== "0000000000000000" &&
3349
+ spanAgentNames.has(parentSpanId)) {
3350
+ agentName = spanAgentNames.get(parentSpanId).agentName;
3351
+ }
3352
+ }
3353
+ if (agentName) {
3354
+ span.setAttribute(incubating.ATTR_GEN_AI_AGENT_NAME, agentName);
3355
+ const spanId = span.spanContext().spanId;
3356
+ spanAgentNames.set(spanId, { agentName, timestamp: Date.now() });
3357
+ }
3358
+ // Check for conversation ID in context
3359
+ const conversationId = api.context.active().getValue(CONVERSATION_ID_KEY);
3360
+ if (conversationId) {
3361
+ span.setAttribute(incubating.ATTR_GEN_AI_CONVERSATION_ID, conversationId);
3362
+ }
3363
+ // Check for association properties in context (set by decorators or withAssociationProperties)
3364
+ const associationProperties = api.context
3365
+ .active()
3366
+ .getValue(ASSOCATION_PROPERTIES_KEY);
3367
+ if (associationProperties && Object.keys(associationProperties).length > 0) {
3368
+ for (const [key, value] of Object.entries(associationProperties)) {
3369
+ span.setAttribute(`${aiSemanticConventions.SpanAttributes.TRACELOOP_ASSOCIATION_PROPERTIES}.${key}`, value);
3370
+ }
3371
+ }
3372
+ transformAiSdkSpanNames(span);
3373
+ };
3374
+ /**
3375
+ * Ensures span compatibility between OTel v1.x and v2.x for OTLP transformer
3376
+ */
3377
+ const ensureSpanCompatibility = (span) => {
3378
+ const spanAny = span;
3379
+ // If the span already has instrumentationLibrary, it's compatible (OTel v2.x)
3380
+ if (spanAny.instrumentationLibrary) {
3381
+ return span;
3382
+ }
3383
+ // If it has instrumentationScope but no instrumentationLibrary (OTel v1.x),
3384
+ // add instrumentationLibrary as an alias to prevent OTLP transformer errors
3385
+ if (spanAny.instrumentationScope) {
3386
+ // Create a proxy that provides both properties
3387
+ return new Proxy(span, {
3388
+ get(target, prop) {
3389
+ if (prop === "instrumentationLibrary") {
3390
+ return target.instrumentationScope;
3391
+ }
3392
+ return target[prop];
3393
+ },
3394
+ });
3395
+ }
3396
+ // Fallback: add both properties with defaults
3397
+ return new Proxy(span, {
3398
+ get(target, prop) {
3399
+ if (prop === "instrumentationLibrary" ||
3400
+ prop === "instrumentationScope") {
3401
+ return {
3402
+ name: "unknown",
3403
+ version: undefined,
3404
+ schemaUrl: undefined,
3405
+ };
3406
+ }
3407
+ return target[prop];
3408
+ },
3409
+ });
3410
+ };
3411
+ const onSpanEnd = (originalOnEnd, instrumentationLibraries) => {
3412
+ return (span) => {
3413
+ var _a, _b, _c;
3414
+ if (instrumentationLibraries &&
3415
+ !instrumentationLibraries.includes(((_a = span.instrumentationScope) === null || _a === void 0 ? void 0 : _a.name) ||
3416
+ ((_b = span.instrumentationLibrary) === null || _b === void 0 ? void 0 : _b.name))) {
3417
+ return;
3418
+ }
3419
+ transformAiSdkSpanAttributes(span);
3420
+ const spanId = span.spanContext().spanId;
3421
+ const parentSpanId = (_c = span.parentSpanContext) === null || _c === void 0 ? void 0 : _c.spanId;
3422
+ let agentName = span.attributes[incubating.ATTR_GEN_AI_AGENT_NAME];
3423
+ if (agentName && typeof agentName === "string") {
3424
+ spanAgentNames.set(spanId, {
3425
+ agentName,
3426
+ timestamp: Date.now(),
3427
+ });
3428
+ }
3429
+ else if (parentSpanId &&
3430
+ parentSpanId !== "0000000000000000" &&
3431
+ spanAgentNames.has(parentSpanId)) {
3432
+ agentName = spanAgentNames.get(parentSpanId).agentName;
3433
+ span.attributes[incubating.ATTR_GEN_AI_AGENT_NAME] = agentName;
3434
+ spanAgentNames.set(spanId, {
3435
+ agentName,
3436
+ timestamp: Date.now(),
3437
+ });
3438
+ }
3439
+ if (Math.random() < 0.01) {
3440
+ cleanupExpiredSpanAgentNames();
3441
+ }
3442
+ const compatibleSpan = ensureSpanCompatibility(span);
3443
+ originalOnEnd(compatibleSpan);
3444
+ };
3445
+ };
3446
+
3447
+ class ImageUploader {
3448
+ constructor(baseUrl, apiKey) {
3449
+ this.baseUrl = baseUrl;
3450
+ this.apiKey = apiKey;
3451
+ }
3452
+ async uploadBase64Image(traceId, spanId, imageName, base64ImageData) {
3453
+ try {
3454
+ const imageUrl = await this.getImageUrl(traceId, spanId, imageName);
3455
+ await this.uploadImageData(imageUrl, base64ImageData);
3456
+ return imageUrl;
3457
+ }
3458
+ catch (error) {
3459
+ console.error("Failed to upload image:", error);
3460
+ throw error;
3461
+ }
3462
+ }
3463
+ async getImageUrl(traceId, spanId, imageName) {
3464
+ const response = await fetch(`${this.baseUrl}/v2/traces/${traceId}/spans/${spanId}/images`, {
3465
+ method: "POST",
3466
+ headers: {
3467
+ Authorization: `Bearer ${this.apiKey}`,
3468
+ "Content-Type": "application/json",
3469
+ },
3470
+ body: JSON.stringify({
3471
+ image_name: imageName,
3472
+ }),
3473
+ });
3474
+ if (!response.ok) {
3475
+ throw new Error(`Failed to get image URL: ${response.status} ${response.statusText}`);
3476
+ }
3477
+ const result = await response.json();
3478
+ return result.url;
3479
+ }
3480
+ async uploadImageData(url, base64ImageData) {
3481
+ const payload = {
3482
+ image_data: base64ImageData,
3483
+ };
3484
+ const response = await fetch(url, {
3485
+ method: "POST",
3486
+ headers: {
3487
+ Authorization: `Bearer ${this.apiKey}`,
3488
+ "Content-Type": "application/json",
3489
+ },
3490
+ body: JSON.stringify(payload),
3491
+ });
3492
+ if (!response.ok) {
3493
+ const errorText = await response.text();
3494
+ throw new Error(`Failed to upload image data: ${response.status} ${response.statusText}. ${errorText}`);
3495
+ }
3496
+ }
3497
+ }
3498
+
3499
+ let _sdk;
3500
+ let _spanProcessor;
3501
+ let openAIInstrumentation;
3502
+ let anthropicInstrumentation;
3503
+ let cohereInstrumentation;
3504
+ let vertexaiInstrumentation;
3505
+ let aiplatformInstrumentation;
3506
+ let bedrockInstrumentation;
3507
+ let langchainInstrumentation;
3508
+ let llamaIndexInstrumentation;
3509
+ let pineconeInstrumentation;
3510
+ let chromadbInstrumentation;
3511
+ let qdrantInstrumentation;
3512
+ let togetherInstrumentation;
3513
+ let mcpInstrumentation;
3514
+ const instrumentations = [];
3515
+ const initInstrumentations = (apiKey, baseUrl) => {
3516
+ const exceptionLogger = (e) => {
3517
+ console.debug("[Traceloop] Instrumentation exception:", e.message);
3518
+ };
3519
+ const enrichTokens = (process.env.ANYWAY_ENRICH_TOKENS || "true").toLowerCase() === "true";
3520
+ // Create image upload callback if we have credentials
3521
+ let uploadBase64ImageCallback;
3522
+ if (apiKey && baseUrl) {
3523
+ const imageUploader = new ImageUploader(baseUrl, apiKey);
3524
+ uploadBase64ImageCallback =
3525
+ imageUploader.uploadBase64Image.bind(imageUploader);
3526
+ }
3527
+ // Create or update OpenAI instrumentation
3528
+ if (openAIInstrumentation) {
3529
+ // Update existing instrumentation with new callback
3530
+ openAIInstrumentation.setConfig({
3531
+ enrichTokens,
3532
+ exceptionLogger,
3533
+ uploadBase64Image: uploadBase64ImageCallback,
3534
+ });
3535
+ }
3536
+ else {
3537
+ // Create new instrumentation
3538
+ openAIInstrumentation = new instrumentationOpenai.OpenAIInstrumentation({
3539
+ enrichTokens,
3540
+ exceptionLogger,
3541
+ uploadBase64Image: uploadBase64ImageCallback,
3542
+ });
3543
+ instrumentations.push(openAIInstrumentation);
3544
+ }
3545
+ if (!anthropicInstrumentation) {
3546
+ anthropicInstrumentation = new instrumentationAnthropic.AnthropicInstrumentation({
3547
+ exceptionLogger,
3548
+ });
3549
+ instrumentations.push(anthropicInstrumentation);
3550
+ }
3551
+ cohereInstrumentation = new instrumentationCohere.CohereInstrumentation({ exceptionLogger });
3552
+ instrumentations.push(cohereInstrumentation);
3553
+ vertexaiInstrumentation = new instrumentationVertexai.VertexAIInstrumentation({
3554
+ exceptionLogger,
3555
+ });
3556
+ instrumentations.push(vertexaiInstrumentation);
3557
+ aiplatformInstrumentation = new instrumentationVertexai.AIPlatformInstrumentation({
3558
+ exceptionLogger,
3559
+ });
3560
+ instrumentations.push(aiplatformInstrumentation);
3561
+ bedrockInstrumentation = new instrumentationBedrock.BedrockInstrumentation({ exceptionLogger });
3562
+ instrumentations.push(bedrockInstrumentation);
3563
+ pineconeInstrumentation = new instrumentationPinecone.PineconeInstrumentation({ exceptionLogger });
3564
+ instrumentations.push(pineconeInstrumentation);
3565
+ langchainInstrumentation = new instrumentationLangchain.LangChainInstrumentation({ exceptionLogger });
3566
+ instrumentations.push(langchainInstrumentation);
3567
+ llamaIndexInstrumentation = new instrumentationLlamaindex.LlamaIndexInstrumentation({
3568
+ exceptionLogger,
3569
+ });
3570
+ instrumentations.push(llamaIndexInstrumentation);
3571
+ chromadbInstrumentation = new instrumentationChromadb.ChromaDBInstrumentation({ exceptionLogger });
3572
+ instrumentations.push(chromadbInstrumentation);
3573
+ qdrantInstrumentation = new instrumentationQdrant.QdrantInstrumentation({ exceptionLogger });
3574
+ instrumentations.push(qdrantInstrumentation);
3575
+ togetherInstrumentation = new instrumentationTogether.TogetherInstrumentation({ exceptionLogger });
3576
+ instrumentations.push(togetherInstrumentation);
3577
+ mcpInstrumentation = new instrumentationMcp.McpInstrumentation({ exceptionLogger });
3578
+ instrumentations.push(mcpInstrumentation);
3579
+ };
3580
+ const manuallyInitInstrumentations = (instrumentModules, apiKey, baseUrl) => {
3581
+ const exceptionLogger = (e) => {
3582
+ console.debug("[Traceloop] Instrumentation exception:", e.message);
3583
+ };
3584
+ const enrichTokens = (process.env.ANYWAY_ENRICH_TOKENS || "true").toLowerCase() === "true";
3585
+ // Create image upload callback if we have credentials
3586
+ let uploadBase64ImageCallback;
3587
+ if (apiKey && baseUrl) {
3588
+ const imageUploader = new ImageUploader(baseUrl, apiKey);
3589
+ uploadBase64ImageCallback =
3590
+ imageUploader.uploadBase64Image.bind(imageUploader);
3591
+ }
3592
+ // Clear the instrumentations array that was initialized by default
3593
+ instrumentations.length = 0;
3594
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.openAI) {
3595
+ openAIInstrumentation = new instrumentationOpenai.OpenAIInstrumentation({
3596
+ enrichTokens,
3597
+ exceptionLogger,
3598
+ uploadBase64Image: uploadBase64ImageCallback,
3599
+ });
3600
+ instrumentations.push(openAIInstrumentation);
3601
+ openAIInstrumentation.manuallyInstrument(instrumentModules.openAI);
3602
+ }
3603
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.anthropic) {
3604
+ anthropicInstrumentation = new instrumentationAnthropic.AnthropicInstrumentation({
3605
+ exceptionLogger,
3606
+ });
3607
+ instrumentations.push(anthropicInstrumentation);
3608
+ anthropicInstrumentation.manuallyInstrument(instrumentModules.anthropic);
3609
+ }
3610
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.cohere) {
3611
+ cohereInstrumentation = new instrumentationCohere.CohereInstrumentation({ exceptionLogger });
3612
+ instrumentations.push(cohereInstrumentation);
3613
+ cohereInstrumentation.manuallyInstrument(instrumentModules.cohere);
3614
+ }
3615
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.google_vertexai) {
3616
+ vertexaiInstrumentation = new instrumentationVertexai.VertexAIInstrumentation({
3617
+ exceptionLogger,
3618
+ });
3619
+ instrumentations.push(vertexaiInstrumentation);
3620
+ vertexaiInstrumentation.manuallyInstrument(instrumentModules.google_vertexai);
3621
+ }
3622
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.google_aiplatform) {
3623
+ aiplatformInstrumentation = new instrumentationVertexai.AIPlatformInstrumentation({
3624
+ exceptionLogger,
3625
+ });
3626
+ instrumentations.push(aiplatformInstrumentation);
3627
+ aiplatformInstrumentation.manuallyInstrument(instrumentModules.google_aiplatform);
3628
+ }
3629
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.bedrock) {
3630
+ bedrockInstrumentation = new instrumentationBedrock.BedrockInstrumentation({ exceptionLogger });
3631
+ instrumentations.push(bedrockInstrumentation);
3632
+ bedrockInstrumentation.manuallyInstrument(instrumentModules.bedrock);
3633
+ }
3634
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.pinecone) {
3635
+ const instrumentation = new instrumentationPinecone.PineconeInstrumentation({ exceptionLogger });
3636
+ instrumentations.push(instrumentation);
3637
+ instrumentation.manuallyInstrument(instrumentModules.pinecone);
3638
+ }
3639
+ // Always enable LangChain instrumentation
3640
+ langchainInstrumentation = new instrumentationLangchain.LangChainInstrumentation({
3641
+ exceptionLogger,
3642
+ });
3643
+ instrumentations.push(langchainInstrumentation);
3644
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.llamaIndex) {
3645
+ llamaIndexInstrumentation = new instrumentationLlamaindex.LlamaIndexInstrumentation({
3646
+ exceptionLogger,
3647
+ });
3648
+ instrumentations.push(llamaIndexInstrumentation);
3649
+ llamaIndexInstrumentation.manuallyInstrument(instrumentModules.llamaIndex);
3650
+ }
3651
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.chromadb) {
3652
+ chromadbInstrumentation = new instrumentationChromadb.ChromaDBInstrumentation({ exceptionLogger });
3653
+ instrumentations.push(chromadbInstrumentation);
3654
+ chromadbInstrumentation.manuallyInstrument(instrumentModules.chromadb);
3655
+ }
3656
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.qdrant) {
3657
+ qdrantInstrumentation = new instrumentationQdrant.QdrantInstrumentation({ exceptionLogger });
3658
+ instrumentations.push(qdrantInstrumentation);
3659
+ qdrantInstrumentation.manuallyInstrument(instrumentModules.qdrant);
3660
+ }
3661
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.together) {
3662
+ togetherInstrumentation = new instrumentationTogether.TogetherInstrumentation({ exceptionLogger });
3663
+ instrumentations.push(togetherInstrumentation);
3664
+ togetherInstrumentation.manuallyInstrument(instrumentModules.together);
3665
+ }
3666
+ if (instrumentModules === null || instrumentModules === void 0 ? void 0 : instrumentModules.mcp) {
3667
+ mcpInstrumentation = new instrumentationMcp.McpInstrumentation({ exceptionLogger });
3668
+ instrumentations.push(mcpInstrumentation);
3669
+ mcpInstrumentation.manuallyInstrument(instrumentModules.mcp);
3670
+ }
3671
+ };
3672
+ /**
3673
+ * Initializes the Traceloop SDK.
3674
+ * Must be called once before any other SDK methods.
3675
+ *
3676
+ * @param options - The options to initialize the SDK. See the {@link InitializeOptions} for details.
3677
+ * @throws {InitializationError} if the configuration is invalid or if failed to fetch feature data.
3678
+ */
3679
+ const startTracing = (options) => {
3680
+ var _a;
3681
+ const apiKey = options.apiKey || process.env.ANYWAY_API_KEY;
3682
+ const baseUrl = options.baseUrl ||
3683
+ process.env.ANYWAY_BASE_URL ||
3684
+ "https://api.traceloop.com";
3685
+ if (Object.keys(options.instrumentModules || {}).length > 0) {
3686
+ manuallyInitInstrumentations(options.instrumentModules, apiKey, baseUrl);
3687
+ }
3688
+ else {
3689
+ // Initialize default instrumentations if no manual modules specified
3690
+ initInstrumentations(apiKey, baseUrl);
3691
+ }
3692
+ if (!shouldSendTraces()) {
3693
+ openAIInstrumentation === null || openAIInstrumentation === void 0 ? void 0 : openAIInstrumentation.setConfig({
3694
+ traceContent: false,
3695
+ });
3696
+ llamaIndexInstrumentation === null || llamaIndexInstrumentation === void 0 ? void 0 : llamaIndexInstrumentation.setConfig({
3697
+ traceContent: false,
3698
+ });
3699
+ vertexaiInstrumentation === null || vertexaiInstrumentation === void 0 ? void 0 : vertexaiInstrumentation.setConfig({
3700
+ traceContent: false,
3701
+ });
3702
+ aiplatformInstrumentation === null || aiplatformInstrumentation === void 0 ? void 0 : aiplatformInstrumentation.setConfig({
3703
+ traceContent: false,
3704
+ });
3705
+ bedrockInstrumentation === null || bedrockInstrumentation === void 0 ? void 0 : bedrockInstrumentation.setConfig({
3706
+ traceContent: false,
3707
+ });
3708
+ cohereInstrumentation === null || cohereInstrumentation === void 0 ? void 0 : cohereInstrumentation.setConfig({
3709
+ traceContent: false,
3710
+ });
3711
+ chromadbInstrumentation === null || chromadbInstrumentation === void 0 ? void 0 : chromadbInstrumentation.setConfig({
3712
+ traceContent: false,
3713
+ });
3714
+ togetherInstrumentation === null || togetherInstrumentation === void 0 ? void 0 : togetherInstrumentation.setConfig({
3715
+ traceContent: false,
3716
+ });
3717
+ }
3718
+ const headers = options.headers ||
3719
+ (process.env.ANYWAY_HEADERS
3720
+ ? parseKeyPairsIntoRecord(process.env.ANYWAY_HEADERS)
3721
+ : { Authorization: `Bearer ${options.apiKey}` });
3722
+ const traceExporter = (_a = options.exporter) !== null && _a !== void 0 ? _a : (options.gcpProjectId
3723
+ ? new opentelemetryCloudTraceExporter.TraceExporter({ projectId: options.gcpProjectId })
3724
+ : new exporterTraceOtlpProto.OTLPTraceExporter({
3725
+ url: `${baseUrl}/v1/traces`,
3726
+ headers,
3727
+ }));
3728
+ _spanProcessor = createSpanProcessor({
3729
+ apiKey: options.apiKey,
3730
+ baseUrl: options.baseUrl,
3731
+ disableBatch: options.disableBatch,
3732
+ exporter: traceExporter,
3733
+ headers,
3734
+ allowedInstrumentationLibraries: ALL_INSTRUMENTATION_LIBRARIES,
3735
+ });
3736
+ const spanProcessors = [_spanProcessor];
3737
+ if (options.processor) {
3738
+ spanProcessors.push(options.processor);
3739
+ }
3740
+ const resource = createResource({
3741
+ [semanticConventions.ATTR_SERVICE_NAME]: options.appName || process.env.npm_package_name || "unknown_service",
3742
+ });
3743
+ _sdk = new sdkNode.NodeSDK({
3744
+ resource,
3745
+ spanProcessors,
3746
+ contextManager: options.contextManager,
3747
+ textMapPropagator: options.propagator,
3748
+ traceExporter,
3749
+ instrumentations,
3750
+ // We should re-consider removing irrelevant spans here in the future
3751
+ // sampler: new TraceloopSampler(),
3752
+ });
3753
+ _sdk.start();
3754
+ };
3755
+ const shouldSendTraces = () => {
3756
+ if (!_configuration) {
3757
+ api.diag.warn("Traceloop not initialized");
3758
+ return false;
3759
+ }
3760
+ const contextShouldSendPrompts = api.context
3761
+ .active()
3762
+ .getValue(aiSemanticConventions.CONTEXT_KEY_ALLOW_TRACE_CONTENT);
3763
+ if (contextShouldSendPrompts !== undefined) {
3764
+ return contextShouldSendPrompts;
3765
+ }
3766
+ if (_configuration.traceContent === false ||
3767
+ (process.env.ANYWAY_TRACE_CONTENT || "true").toLowerCase() === "false") {
3768
+ return false;
3769
+ }
3770
+ return true;
3771
+ };
3772
+ const forceFlush = async () => {
3773
+ await _spanProcessor.forceFlush();
3774
+ };
3775
+ // Compatibility function for creating resources that works with both OTel v1.x and v2.x
3776
+ function createResource(attributes) {
3777
+ // Import the resource module at runtime to handle both v1.x and v2.x
3778
+ const resourcesModule = require("@opentelemetry/resources");
3779
+ // Try to use resourceFromAttributes if it exists (OTel v2.x)
3780
+ if (resourcesModule.resourceFromAttributes) {
3781
+ return resourcesModule.resourceFromAttributes(attributes);
3782
+ }
3783
+ // Fallback to constructor for OTel v1.x
3784
+ return new resourcesModule.Resource(attributes);
3785
+ }
3786
+
3787
+ const fetchRetry = fetchBuilder(fetch$1);
3788
+ const fetchPrompts = async (options) => {
3789
+ const { apiKey, baseUrl, traceloopSyncMaxRetries } = options;
3790
+ const response = await fetchRetry(`${baseUrl}/v1/traceloop/prompts`, {
3791
+ method: "GET",
3792
+ headers: {
3793
+ "Content-Type": "application/json",
3794
+ Authorization: `Bearer ${apiKey}`,
3795
+ "X-Traceloop-SDK-Version": "0.0.30",
3796
+ },
3797
+ retries: traceloopSyncMaxRetries,
3798
+ retryOn: function (attempt, error, response) {
3799
+ if (attempt >= traceloopSyncMaxRetries)
3800
+ return false;
3801
+ if ((response === null || response === void 0 ? void 0 : response.status) && response.status >= 500) {
3802
+ return true;
3803
+ }
3804
+ return false;
3805
+ },
3806
+ retryDelay: function (attempt) {
3807
+ return Math.pow(2, attempt) * 1000; // 1000, 2000, 4000
3808
+ },
3809
+ });
3810
+ return await response.json();
3811
+ };
3812
+
3813
+ const _prompts = {};
3814
+ let _initialized = false;
3815
+ let _initializedPromise;
3816
+ /**
3817
+ * Returns true once SDK prompt registry has been initialized, else rejects with an error.
3818
+ * @returns Promise<boolean>
3819
+ */
3820
+ const waitForInitialization = async () => {
3821
+ if (_initialized) {
3822
+ return true;
3823
+ }
3824
+ return await _initializedPromise;
3825
+ };
3826
+ const getPromptByKey = (key) => {
3827
+ if (!_prompts[key]) {
3828
+ throw new PromptNotFoundError(key);
3829
+ }
3830
+ return _prompts[key];
3831
+ };
3832
+ const populateRegistry = (prompts) => {
3833
+ prompts === null || prompts === void 0 ? void 0 : prompts.forEach((prompt) => {
3834
+ _prompts[prompt.key] = prompt;
3835
+ });
3836
+ };
3837
+ const initializeRegistry = (options) => {
3838
+ const { baseUrl, traceloopSyncEnabled, traceloopSyncPollingInterval, traceloopSyncDevPollingInterval, } = options;
3839
+ if (!traceloopSyncEnabled || !(baseUrl === null || baseUrl === void 0 ? void 0 : baseUrl.includes("traceloop")))
3840
+ return;
3841
+ let pollingInterval = traceloopSyncPollingInterval;
3842
+ _initializedPromise = fetchPrompts(options)
3843
+ .then(({ prompts, environment }) => {
3844
+ if (environment === "dev") {
3845
+ pollingInterval = traceloopSyncDevPollingInterval;
3846
+ }
3847
+ populateRegistry(prompts);
3848
+ _initialized = true;
3849
+ setInterval(async () => {
3850
+ try {
3851
+ const { prompts } = await fetchPrompts(options);
3852
+ populateRegistry(prompts);
3853
+ }
3854
+ catch (err) {
3855
+ api.diag.error("Failed to fetch prompt data", err);
3856
+ }
3857
+ }, pollingInterval * 1000).unref();
3858
+ return true;
3859
+ })
3860
+ .catch((e) => {
3861
+ throw new InitializationError("Failed to fetch prompt data to initialize Traceloop SDK", e);
3862
+ });
3863
+ };
3864
+
3865
+ let _configuration;
3866
+ let _client;
3867
+ /**
3868
+ * Initializes the Traceloop SDK and creates a singleton client instance if API key is provided.
3869
+ * Must be called once before any other SDK methods.
3870
+ *
3871
+ * @param options - The options to initialize the SDK. See the {@link InitializeOptions} for details.
3872
+ * @returns TraceloopClient - The singleton client instance if API key is provided, otherwise undefined.
3873
+ * @throws {InitializationError} if the configuration is invalid or if failed to fetch feature data.
3874
+ *
3875
+ * @example
3876
+ * ```typescript
3877
+ * initialize({
3878
+ * apiKey: 'your-api-key',
3879
+ * appName: 'your-app',
3880
+ * });
3881
+ * ```
3882
+ */
3883
+ const initialize = (options = {}) => {
3884
+ if (_configuration) {
3885
+ return;
3886
+ }
3887
+ if (!options.baseUrl) {
3888
+ options.baseUrl =
3889
+ process.env.ANYWAY_BASE_URL || "https://api.traceloop.com";
3890
+ }
3891
+ if (!options.apiKey) {
3892
+ options.apiKey = process.env.ANYWAY_API_KEY;
3893
+ }
3894
+ if (!options.appName) {
3895
+ options.appName = process.env.npm_package_name;
3896
+ }
3897
+ if (!options.experimentSlug) {
3898
+ options.experimentSlug = process.env.ANYWAY_EXP_SLUG;
3899
+ }
3900
+ if (options.traceloopSyncEnabled === undefined) {
3901
+ if (process.env.ANYWAY_SYNC_ENABLED !== undefined) {
3902
+ options.traceloopSyncEnabled = ["1", "true"].includes(process.env.ANYWAY_SYNC_ENABLED.toLowerCase());
3903
+ }
3904
+ else {
3905
+ options.traceloopSyncEnabled = true;
3906
+ }
3907
+ }
3908
+ if (options.traceloopSyncEnabled) {
3909
+ if (!options.traceloopSyncMaxRetries) {
3910
+ options.traceloopSyncMaxRetries =
3911
+ Number(process.env.ANYWAY_SYNC_MAX_RETRIES) || 3;
3912
+ }
3913
+ if (!options.traceloopSyncPollingInterval) {
3914
+ options.traceloopSyncPollingInterval =
3915
+ Number(process.env.ANYWAY_SYNC_POLLING_INTERVAL) || 60;
3916
+ }
3917
+ if (!options.traceloopSyncDevPollingInterval) {
3918
+ options.traceloopSyncDevPollingInterval =
3919
+ Number(process.env.ANYWAY_SYNC_DEV_POLLING_INTERVAL) || 5;
3920
+ }
3921
+ }
3922
+ validateConfiguration(options);
3923
+ _configuration = Object.freeze(options);
3924
+ if (!options.silenceInitializationMessage) {
3925
+ console.log(`Traceloop exporting traces to ${_configuration.exporter ? "a custom exporter" : _configuration.baseUrl}`);
3926
+ }
3927
+ if (options.tracingEnabled === undefined || options.tracingEnabled) {
3928
+ if (options.logLevel) {
3929
+ api.diag.setLogger(new api.DiagConsoleLogger(), logLevelToOtelLogLevel(options.logLevel));
3930
+ }
3931
+ startTracing(_configuration);
3932
+ }
3933
+ initializeRegistry(_configuration);
3934
+ if (options.apiKey) {
3935
+ _client = new TraceloopClient({
3936
+ apiKey: options.apiKey,
3937
+ baseUrl: options.baseUrl,
3938
+ appName: options.appName,
3939
+ experimentSlug: options.experimentSlug,
3940
+ });
3941
+ return _client;
3942
+ }
3943
+ return;
3944
+ };
3945
+ const logLevelToOtelLogLevel = (logLevel) => {
3946
+ switch (logLevel) {
3947
+ case "debug":
3948
+ return api.DiagLogLevel.DEBUG;
3949
+ case "info":
3950
+ return api.DiagLogLevel.INFO;
3951
+ case "warn":
3952
+ return api.DiagLogLevel.WARN;
3953
+ case "error":
3954
+ return api.DiagLogLevel.ERROR;
3955
+ }
3956
+ };
3957
+ /**
3958
+ * Gets the singleton instance of the TraceloopClient.
3959
+ * The SDK must be initialized with an API key before calling this function.
3960
+ *
3961
+ * @returns The TraceloopClient singleton instance
3962
+ * @throws {Error} if the SDK hasn't been initialized or was initialized without an API key
3963
+ *
3964
+ * @example
3965
+ * ```typescript
3966
+ * const client = getClient();
3967
+ * await client.annotation.create({ annotationTask: 'taskId', entityInstanceId: 'entityId', tags: { score: 0.9 } });
3968
+ * ```
3969
+ */
3970
+ const getClient = () => {
3971
+ if (!_client) {
3972
+ throw new Error("Traceloop must be initialized before getting client, Call initialize() first." +
3973
+ "If you already called initialize(), make sure you have an api key.");
3974
+ }
3975
+ return _client;
3976
+ };
3977
+
3978
+ function withEntity(type, { name, version, associationProperties, conversationId, traceContent: overrideTraceContent, inputParameters, suppressTracing: shouldSuppressTracing, }, fn, thisArg, ...args) {
3979
+ let entityContext = api.context.active();
3980
+ if (type === aiSemanticConventions.TraceloopSpanKindValues.WORKFLOW ||
3981
+ type === aiSemanticConventions.TraceloopSpanKindValues.AGENT) {
3982
+ entityContext = entityContext.setValue(WORKFLOW_NAME_KEY, name);
3983
+ }
3984
+ if (type === aiSemanticConventions.TraceloopSpanKindValues.AGENT) {
3985
+ entityContext = entityContext.setValue(AGENT_NAME_KEY, name);
3986
+ }
3987
+ const entityPath = getEntityPath(entityContext);
3988
+ if (type === aiSemanticConventions.TraceloopSpanKindValues.TOOL ||
3989
+ type === aiSemanticConventions.TraceloopSpanKindValues.TASK) {
3990
+ const fullEntityName = entityPath ? `${entityPath}.${name}` : name;
3991
+ entityContext = entityContext.setValue(ENTITY_NAME_KEY, fullEntityName);
3992
+ }
3993
+ if (overrideTraceContent != undefined) {
3994
+ entityContext = entityContext.setValue(aiSemanticConventions.CONTEXT_KEY_ALLOW_TRACE_CONTENT, overrideTraceContent);
3995
+ }
3996
+ if (associationProperties) {
3997
+ entityContext = entityContext.setValue(ASSOCATION_PROPERTIES_KEY, associationProperties);
3998
+ }
3999
+ if (conversationId) {
4000
+ entityContext = entityContext.setValue(CONVERSATION_ID_KEY, conversationId);
4001
+ }
4002
+ if (shouldSuppressTracing) {
4003
+ entityContext = core.suppressTracing(entityContext);
4004
+ }
4005
+ return api.context.with(entityContext, () => getTracer().startActiveSpan(`${name}.${type}`, {}, entityContext, async (span) => {
4006
+ if (type === aiSemanticConventions.TraceloopSpanKindValues.WORKFLOW ||
4007
+ type === aiSemanticConventions.TraceloopSpanKindValues.AGENT) {
4008
+ span.setAttribute(aiSemanticConventions.SpanAttributes.TRACELOOP_WORKFLOW_NAME, name);
4009
+ }
4010
+ span.setAttribute(aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_NAME, name);
4011
+ span.setAttribute(aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_PATH, entityPath || "");
4012
+ span.setAttribute(aiSemanticConventions.SpanAttributes.TRACELOOP_SPAN_KIND, type);
4013
+ // Set agent name on all spans when there's an active agent context
4014
+ const agentName = entityContext.getValue(AGENT_NAME_KEY);
4015
+ if (agentName) {
4016
+ span.setAttribute(incubating.ATTR_GEN_AI_AGENT_NAME, agentName);
4017
+ }
4018
+ // Set conversation ID on all spans when there's an active conversation context
4019
+ const conversationId = entityContext.getValue(CONVERSATION_ID_KEY);
4020
+ if (conversationId) {
4021
+ span.setAttribute(incubating.ATTR_GEN_AI_CONVERSATION_ID, conversationId);
4022
+ }
4023
+ if (version) {
4024
+ span.setAttribute(aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_VERSION, version);
4025
+ }
4026
+ if (shouldSendTraces()) {
4027
+ try {
4028
+ const input = inputParameters !== null && inputParameters !== void 0 ? inputParameters : args;
4029
+ if (input.length === 1 &&
4030
+ typeof input[0] === "object" &&
4031
+ !(input[0] instanceof Map)) {
4032
+ span.setAttribute(aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_INPUT, serialize({ args: [], kwargs: input[0] }));
4033
+ }
4034
+ else {
4035
+ span.setAttribute(aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_INPUT, serialize({
4036
+ args: input,
4037
+ kwargs: {},
4038
+ }));
4039
+ }
4040
+ }
4041
+ catch (error) {
4042
+ console.debug("Error setting input attributes", error);
4043
+ }
4044
+ }
4045
+ const res = fn.apply(thisArg, args);
4046
+ if (res instanceof Promise) {
4047
+ return res.then((resolvedRes) => {
4048
+ try {
4049
+ if (shouldSendTraces()) {
4050
+ span.setAttribute(aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_OUTPUT, serialize(resolvedRes));
4051
+ }
4052
+ }
4053
+ catch (error) {
4054
+ console.debug("Error setting output attributes", error);
4055
+ }
4056
+ finally {
4057
+ span.end();
4058
+ }
4059
+ return resolvedRes;
4060
+ });
4061
+ }
4062
+ try {
4063
+ if (shouldSendTraces()) {
4064
+ span.setAttribute(aiSemanticConventions.SpanAttributes.TRACELOOP_ENTITY_OUTPUT, serialize(res));
4065
+ }
4066
+ }
4067
+ catch (error) {
4068
+ console.debug("Error setting output attributes", error);
4069
+ }
4070
+ finally {
4071
+ span.end();
4072
+ }
4073
+ return res;
4074
+ }));
4075
+ }
4076
+ function withWorkflow(config, fn, ...args) {
4077
+ return withEntity(aiSemanticConventions.TraceloopSpanKindValues.WORKFLOW, config, fn, undefined, ...args);
4078
+ }
4079
+ function withTask(config, fn, ...args) {
4080
+ return withEntity(aiSemanticConventions.TraceloopSpanKindValues.TASK, config, fn, undefined, ...args);
4081
+ }
4082
+ function withAgent(config, fn, ...args) {
4083
+ return withEntity(aiSemanticConventions.TraceloopSpanKindValues.AGENT, config, fn, undefined, ...args);
4084
+ }
4085
+ function withTool(config, fn, ...args) {
4086
+ return withEntity(aiSemanticConventions.TraceloopSpanKindValues.TOOL, config, fn, undefined, ...args);
4087
+ }
4088
+ function entity(type, config) {
4089
+ return function (target, propertyKey, descriptor) {
4090
+ const originalMethod = descriptor.value;
4091
+ descriptor.value = function (...args) {
4092
+ var _a;
4093
+ let actualConfig;
4094
+ if (typeof config === "function") {
4095
+ actualConfig = config(this, ...args);
4096
+ }
4097
+ else {
4098
+ actualConfig = config;
4099
+ }
4100
+ const entityName = (_a = actualConfig.name) !== null && _a !== void 0 ? _a : originalMethod.name;
4101
+ return withEntity(type, Object.assign(Object.assign({}, actualConfig), { name: entityName }), originalMethod, this, ...args);
4102
+ };
4103
+ };
4104
+ }
4105
+ function workflow(config) {
4106
+ return entity(aiSemanticConventions.TraceloopSpanKindValues.WORKFLOW, config !== null && config !== void 0 ? config : {});
4107
+ }
4108
+ function task(config) {
4109
+ return entity(aiSemanticConventions.TraceloopSpanKindValues.TASK, config !== null && config !== void 0 ? config : {});
4110
+ }
4111
+ function agent(config) {
4112
+ return entity(aiSemanticConventions.TraceloopSpanKindValues.AGENT, config !== null && config !== void 0 ? config : {});
4113
+ }
4114
+ function tool(config) {
4115
+ return entity(aiSemanticConventions.TraceloopSpanKindValues.TOOL, config !== null && config !== void 0 ? config : {});
4116
+ }
4117
+ function withConversation(conversationId, fn, thisArg, ...args) {
4118
+ const conversationContext = api.context
4119
+ .active()
4120
+ .setValue(CONVERSATION_ID_KEY, conversationId);
4121
+ return api.context.with(conversationContext, () => withEntity(aiSemanticConventions.TraceloopSpanKindValues.WORKFLOW, { name: `conversation.${conversationId}` }, fn, thisArg, ...args));
4122
+ }
4123
+ function conversation(conversationId) {
4124
+ return function (target, propertyKey, descriptor) {
4125
+ const originalMethod = descriptor.value;
4126
+ descriptor.value = function (...args) {
4127
+ let actualConversationId;
4128
+ if (typeof conversationId === "function") {
4129
+ actualConversationId = conversationId(this, ...args);
4130
+ }
4131
+ else {
4132
+ actualConversationId = conversationId;
4133
+ }
4134
+ return withConversation(actualConversationId, originalMethod, this, ...args);
4135
+ };
4136
+ };
4137
+ }
4138
+ function cleanInput(input) {
4139
+ if (input instanceof Map) {
4140
+ return Array.from(input.entries());
4141
+ }
4142
+ else if (Array.isArray(input)) {
4143
+ return input.map((value) => cleanInput(value));
4144
+ }
4145
+ else if (!input) {
4146
+ return input;
4147
+ }
4148
+ else if (typeof input === "object") {
4149
+ // serialize object one by one
4150
+ const output = {};
4151
+ Object.entries(input).forEach(([key, value]) => {
4152
+ output[key] = cleanInput(value);
4153
+ });
4154
+ return output;
4155
+ }
4156
+ return input;
4157
+ }
4158
+ function serialize(input) {
4159
+ return JSON.stringify(cleanInput(input));
4160
+ }
4161
+
4162
+ class VectorSpan {
4163
+ constructor(span) {
4164
+ this.span = span;
4165
+ }
4166
+ reportQuery({ queryVector }) {
4167
+ if (!shouldSendTraces()) {
4168
+ this.span.addEvent(aiSemanticConventions.Events.DB_QUERY_EMBEDDINGS);
4169
+ }
4170
+ this.span.addEvent(aiSemanticConventions.Events.DB_QUERY_EMBEDDINGS, {
4171
+ [aiSemanticConventions.EventAttributes.DB_QUERY_EMBEDDINGS_VECTOR]: JSON.stringify(queryVector),
4172
+ });
4173
+ }
4174
+ reportResults({ results, }) {
4175
+ for (let i = 0; i < results.length; i++) {
4176
+ this.span.addEvent(aiSemanticConventions.Events.DB_QUERY_RESULT, {
4177
+ [aiSemanticConventions.EventAttributes.DB_QUERY_RESULT_ID]: results[i].ids,
4178
+ [aiSemanticConventions.EventAttributes.DB_QUERY_RESULT_SCORE]: results[i].scores,
4179
+ [aiSemanticConventions.EventAttributes.DB_QUERY_RESULT_DISTANCE]: results[i].distances,
4180
+ [aiSemanticConventions.EventAttributes.DB_QUERY_RESULT_METADATA]: JSON.stringify(results[i].metadata),
4181
+ [aiSemanticConventions.EventAttributes.DB_QUERY_RESULT_VECTOR]: results[i].vectors,
4182
+ [aiSemanticConventions.EventAttributes.DB_QUERY_RESULT_DOCUMENT]: results[i].documents,
4183
+ });
4184
+ }
4185
+ }
4186
+ }
4187
+ class LLMSpan {
4188
+ constructor(span) {
4189
+ this.span = span;
4190
+ }
4191
+ reportRequest({ model, messages, }) {
4192
+ this.span.setAttributes({
4193
+ [incubating.ATTR_GEN_AI_REQUEST_MODEL]: model,
4194
+ });
4195
+ messages.forEach((message, index) => {
4196
+ this.span.setAttributes({
4197
+ [`${incubating.ATTR_GEN_AI_PROMPT}.${index}.role`]: message.role,
4198
+ [`${incubating.ATTR_GEN_AI_PROMPT}.${index}.content`]: typeof message.content === "string"
4199
+ ? message.content
4200
+ : JSON.stringify(message.content),
4201
+ });
4202
+ });
4203
+ }
4204
+ reportResponse({ model, usage, completions, }) {
4205
+ this.span.setAttribute(incubating.ATTR_GEN_AI_RESPONSE_MODEL, model);
4206
+ if (usage) {
4207
+ this.span.setAttributes({
4208
+ [incubating.ATTR_GEN_AI_USAGE_INPUT_TOKENS]: usage.prompt_tokens,
4209
+ [incubating.ATTR_GEN_AI_USAGE_OUTPUT_TOKENS]: usage.completion_tokens,
4210
+ [aiSemanticConventions.SpanAttributes.LLM_USAGE_TOTAL_TOKENS]: usage.total_tokens,
4211
+ });
4212
+ }
4213
+ completions === null || completions === void 0 ? void 0 : completions.forEach((completion, index) => {
4214
+ this.span.setAttributes({
4215
+ [`${incubating.ATTR_GEN_AI_COMPLETION}.${index}.finish_reason`]: completion.finish_reason,
4216
+ [`${incubating.ATTR_GEN_AI_COMPLETION}.${index}.role`]: completion.message.role,
4217
+ [`${incubating.ATTR_GEN_AI_COMPLETION}.${index}.content`]: completion.message.content || "",
4218
+ });
4219
+ });
4220
+ }
4221
+ }
4222
+ function withVectorDBCall({ vendor, type }, fn, thisArg) {
4223
+ const entityContext = api.context.active();
4224
+ return getTracer().startActiveSpan(`${vendor}.${type}`, { [aiSemanticConventions.SpanAttributes.LLM_REQUEST_TYPE]: type }, entityContext, (span) => {
4225
+ // Set agent name if there's an active agent context
4226
+ const agentName = entityContext.getValue(AGENT_NAME_KEY);
4227
+ if (agentName) {
4228
+ span.setAttribute(incubating.ATTR_GEN_AI_AGENT_NAME, agentName);
4229
+ }
4230
+ const res = fn.apply(thisArg, [{ span: new VectorSpan(span) }]);
4231
+ if (res instanceof Promise) {
4232
+ return res.then((resolvedRes) => {
4233
+ span.end();
4234
+ return resolvedRes;
4235
+ });
4236
+ }
4237
+ span.end();
4238
+ return res;
4239
+ });
4240
+ }
4241
+ function withLLMCall({ vendor, type }, fn, thisArg) {
4242
+ const currentContext = api.context.active();
4243
+ const span = getTracer().startSpan(`${vendor}.${type}`, {}, currentContext);
4244
+ span.setAttribute(aiSemanticConventions.SpanAttributes.LLM_REQUEST_TYPE, type);
4245
+ // Set agent name if there's an active agent context
4246
+ const agentName = currentContext.getValue(AGENT_NAME_KEY);
4247
+ if (agentName) {
4248
+ span.setAttribute(incubating.ATTR_GEN_AI_AGENT_NAME, agentName);
4249
+ }
4250
+ api.trace.setSpan(currentContext, span);
4251
+ const res = fn.apply(thisArg, [{ span: new LLMSpan(span) }]);
4252
+ if (res instanceof Promise) {
4253
+ return res.then((resolvedRes) => {
4254
+ span.end();
4255
+ return resolvedRes;
4256
+ });
4257
+ }
4258
+ span.end();
4259
+ return res;
4260
+ }
4261
+
4262
+ function withAssociationProperties(properties, fn, thisArg, ...args) {
4263
+ if (Object.keys(properties).length === 0) {
4264
+ return fn.apply(thisArg, args);
4265
+ }
4266
+ const newContext = api.context
4267
+ .active()
4268
+ .setValue(ASSOCATION_PROPERTIES_KEY, properties);
4269
+ return api.context.with(newContext, fn, thisArg, ...args);
4270
+ }
4271
+
4272
+ /**
4273
+ * Reports a custom metric to the current active span.
4274
+ *
4275
+ * This function allows you to add a custom metric to the current span in the trace.
4276
+ * If there is no active span, a warning will be logged.
4277
+ *
4278
+ * @param {string} metricName - The name of the custom metric.
4279
+ * @param {number} metricValue - The numeric value of the custom metric.
4280
+ *
4281
+ * @example
4282
+ * reportCustomMetric('processing_time', 150);
4283
+ */
4284
+ const reportCustomMetric = (metricName, metricValue) => {
4285
+ const currentContext = api.context.active();
4286
+ const currentSpan = api.trace.getSpan(currentContext);
4287
+ if (currentSpan) {
4288
+ currentSpan.setAttribute(`traceloop.custom_metric.${metricName}`, metricValue);
4289
+ }
4290
+ else {
4291
+ api.diag.warn(`No active span found to report custom metric: ${metricName}`);
4292
+ }
4293
+ };
4294
+
4295
+ const TEMPLATING_ENGINE = {
4296
+ JINJA2: "jinja2",
4297
+ };
4298
+
4299
+ const env = new nunjucks.Environment(null, {
4300
+ throwOnUndefined: true, // throw error if param not found
4301
+ });
4302
+ const renderMessages = (promptVersion, variables) => {
4303
+ if (promptVersion.templating_engine === TEMPLATING_ENGINE.JINJA2) {
4304
+ return promptVersion.messages.map((message) => {
4305
+ try {
4306
+ if (typeof message.template === "string") {
4307
+ return {
4308
+ content: env.renderString(message.template, variables),
4309
+ role: message.role,
4310
+ };
4311
+ }
4312
+ else {
4313
+ return {
4314
+ content: message.template.map((content) => {
4315
+ if (content.type === "text") {
4316
+ return {
4317
+ type: "text",
4318
+ text: env.renderString(content.text, variables),
4319
+ };
4320
+ }
4321
+ else {
4322
+ return content;
4323
+ }
4324
+ }),
4325
+ role: message.role,
4326
+ };
4327
+ }
4328
+ }
4329
+ catch (err) {
4330
+ throw new TraceloopError(`Failed to render message template. Missing variables?`);
4331
+ }
4332
+ });
4333
+ }
4334
+ else {
4335
+ throw new TraceloopError(`Templating engine ${promptVersion.templating_engine} is not supported`);
4336
+ }
4337
+ };
4338
+
4339
+ const getEffectiveVersion = (prompt) => {
4340
+ const version = prompt.versions.find((v) => v.id === prompt.target.version);
4341
+ if (!version) {
4342
+ throw new TraceloopError(`Prompt version ${prompt.target.version} not found`);
4343
+ }
4344
+ return version;
4345
+ };
4346
+ const managedPromptTracingAttributes = (prompt, promptVersion, variables) => {
4347
+ const variableAttributes = Object.keys(variables).reduce((acc, key) => {
4348
+ acc[`traceloop.prompt.template_variables.${key}`] = variables[key];
4349
+ return acc;
4350
+ }, {});
4351
+ return Object.assign({ "traceloop.prompt.key": prompt.key, "traceloop.prompt.version": promptVersion.version, "traceloop.prompt.version_hash": promptVersion.hash, "traceloop.prompt.version_name": promptVersion.name }, variableAttributes);
4352
+ };
4353
+ const getPrompt = (key, variables) => {
4354
+ var _a;
4355
+ const prompt = getPromptByKey(key);
4356
+ const promptVersion = getEffectiveVersion(prompt);
4357
+ let result = {}; //TODO - SDK needs to do work specific to each vendor/model? maybe we do this in the backend?
4358
+ if (promptVersion.llm_config.mode === "completion") {
4359
+ const message = renderMessages(promptVersion, variables);
4360
+ result = Object.assign(Object.assign({}, promptVersion.llm_config), { prompt: (_a = message === null || message === void 0 ? void 0 : message[0]) === null || _a === void 0 ? void 0 : _a.content });
4361
+ }
4362
+ else {
4363
+ result = Object.assign({ messages: renderMessages(promptVersion, variables) }, promptVersion.llm_config);
4364
+ }
4365
+ if ((result === null || result === void 0 ? void 0 : result["stop"].length) === 0)
4366
+ delete result["stop"];
4367
+ delete result["mode"];
4368
+ result.extraAttributes = managedPromptTracingAttributes(prompt, promptVersion, variables);
4369
+ return result;
4370
+ };
4371
+
4372
+ /**
4373
+ * Standard association properties for tracing.
4374
+ * Use these with withAssociationProperties() or decorator associationProperties config.
4375
+ *
4376
+ * @example
4377
+ * ```typescript
4378
+ * // With withAssociationProperties
4379
+ * await traceloop.withAssociationProperties(
4380
+ * {
4381
+ * [traceloop.AssociationProperty.USER_ID]: "12345",
4382
+ * [traceloop.AssociationProperty.SESSION_ID]: "session-abc"
4383
+ * },
4384
+ * async () => {
4385
+ * await chat();
4386
+ * }
4387
+ * );
4388
+ *
4389
+ * // With decorator
4390
+ * @traceloop.workflow((thisArg) => ({
4391
+ * name: "my_workflow",
4392
+ * associationProperties: {
4393
+ * [traceloop.AssociationProperty.USER_ID]: (thisArg as MyClass).userId,
4394
+ * },
4395
+ * }))
4396
+ * ```
4397
+ */
4398
+ exports.AssociationProperty = void 0;
4399
+ (function (AssociationProperty) {
4400
+ AssociationProperty["CUSTOMER_ID"] = "customer_id";
4401
+ AssociationProperty["USER_ID"] = "user_id";
4402
+ AssociationProperty["SESSION_ID"] = "session_id";
4403
+ })(exports.AssociationProperty || (exports.AssociationProperty = {}));
4404
+
4405
+ exports.ALL_INSTRUMENTATION_LIBRARIES = ALL_INSTRUMENTATION_LIBRARIES;
4406
+ exports.ArgumentNotProvidedError = ArgumentNotProvidedError;
4407
+ exports.Attachment = Attachment;
4408
+ exports.AttachmentReference = AttachmentReference;
4409
+ exports.AttachmentUploader = AttachmentUploader;
4410
+ exports.Column = Column;
4411
+ exports.Dataset = Dataset;
4412
+ exports.Datasets = Datasets;
4413
+ exports.Evaluator = Evaluator;
4414
+ exports.EvaluatorMadeByTraceloop = EvaluatorMadeByTraceloop;
4415
+ exports.Experiment = Experiment;
4416
+ exports.ExternalAttachment = ExternalAttachment;
4417
+ exports.ImageUploader = ImageUploader;
4418
+ exports.InitializationError = InitializationError;
4419
+ exports.LLMSpan = LLMSpan;
4420
+ exports.NotInitializedError = NotInitializedError;
4421
+ exports.PromptNotFoundError = PromptNotFoundError;
4422
+ exports.Row = Row;
4423
+ exports.SEVERITY = SEVERITY;
4424
+ exports.TraceloopClient = TraceloopClient;
4425
+ exports.TraceloopError = TraceloopError;
4426
+ exports.VectorSpan = VectorSpan;
4427
+ exports.agent = agent;
4428
+ exports.attachment = attachment;
4429
+ exports.conversation = conversation;
4430
+ exports.createEvaluator = createEvaluator;
4431
+ exports.createSpanProcessor = createSpanProcessor;
4432
+ exports.forceFlush = forceFlush;
4433
+ exports.getAvailableEvaluatorSlugs = getAvailableEvaluatorSlugs;
4434
+ exports.getClient = getClient;
4435
+ exports.getEvaluatorSchemaInfo = getEvaluatorSchemaInfo;
4436
+ exports.getPrompt = getPrompt;
4437
+ exports.getTraceloopTracer = getTraceloopTracer;
4438
+ exports.initialize = initialize;
4439
+ exports.isAnyAttachment = isAnyAttachment;
4440
+ exports.isAttachment = isAttachment;
4441
+ exports.isAttachmentReference = isAttachmentReference;
4442
+ exports.isExternalAttachment = isExternalAttachment;
4443
+ exports.reportCustomMetric = reportCustomMetric;
4444
+ exports.task = task;
4445
+ exports.tool = tool;
4446
+ exports.traceloopInstrumentationLibraries = traceloopInstrumentationLibraries;
4447
+ exports.validateEvaluatorInput = validateEvaluatorInput;
4448
+ exports.waitForInitialization = waitForInitialization;
4449
+ exports.withAgent = withAgent;
4450
+ exports.withAssociationProperties = withAssociationProperties;
4451
+ exports.withConversation = withConversation;
4452
+ exports.withLLMCall = withLLMCall;
4453
+ exports.withTask = withTask;
4454
+ exports.withTool = withTool;
4455
+ exports.withVectorDBCall = withVectorDBCall;
4456
+ exports.withWorkflow = withWorkflow;
4457
+ exports.workflow = workflow;
4458
+ //# sourceMappingURL=index.js.map