@promptmetrics/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1326 @@
1
+ // src/utils/http.ts
2
+ import axios from "axios";
3
+
4
+ // src/utils/errors.ts
5
+ var PromptMetricsError = class extends Error {
6
+ constructor(message, statusCode, code, details) {
7
+ super(message);
8
+ this.name = "PromptMetricsError";
9
+ this.statusCode = statusCode;
10
+ this.code = code;
11
+ this.details = details;
12
+ if (typeof Error.captureStackTrace === "function") {
13
+ Error.captureStackTrace(this, this.constructor);
14
+ }
15
+ }
16
+ /**
17
+ * Custom JSON serialization to include message field
18
+ */
19
+ toJSON() {
20
+ const result = {
21
+ name: this.name,
22
+ message: this.message,
23
+ statusCode: this.statusCode,
24
+ code: this.code
25
+ };
26
+ if (this.details !== void 0) {
27
+ result.details = this.details;
28
+ }
29
+ return result;
30
+ }
31
+ };
32
+ var AuthenticationError = class extends PromptMetricsError {
33
+ constructor(message = "Invalid API key or authentication failed") {
34
+ super(message, 401, "AUTHENTICATION_ERROR");
35
+ this.name = "AuthenticationError";
36
+ }
37
+ };
38
+ var AuthorizationError = class extends PromptMetricsError {
39
+ constructor(message = "You do not have permission to access this resource") {
40
+ super(message, 403, "AUTHORIZATION_ERROR");
41
+ this.name = "AuthorizationError";
42
+ }
43
+ };
44
+ var NotFoundError = class extends PromptMetricsError {
45
+ constructor(message = "Resource not found") {
46
+ super(message, 404, "NOT_FOUND_ERROR");
47
+ this.name = "NotFoundError";
48
+ }
49
+ };
50
+ var ValidationError = class extends PromptMetricsError {
51
+ constructor(message = "Invalid request parameters", details) {
52
+ super(message, 400, "VALIDATION_ERROR", details);
53
+ this.name = "ValidationError";
54
+ }
55
+ };
56
+ var RateLimitError = class extends PromptMetricsError {
57
+ constructor(message = "Rate limit exceeded") {
58
+ super(message, 429, "RATE_LIMIT_ERROR");
59
+ this.name = "RateLimitError";
60
+ }
61
+ };
62
+ var ServerError = class extends PromptMetricsError {
63
+ constructor(message = "Internal server error") {
64
+ super(message, 500, "SERVER_ERROR");
65
+ this.name = "ServerError";
66
+ }
67
+ };
68
+ var NetworkError = class extends PromptMetricsError {
69
+ constructor(message = "Network request failed") {
70
+ super(message, void 0, "NETWORK_ERROR");
71
+ this.name = "NetworkError";
72
+ }
73
+ };
74
+ var TimeoutError = class extends PromptMetricsError {
75
+ constructor(message = "Request timeout") {
76
+ super(message, 408, "TIMEOUT_ERROR");
77
+ this.name = "TimeoutError";
78
+ }
79
+ };
80
+
81
+ // src/utils/http.ts
82
+ var HttpClient = class {
83
+ constructor(config) {
84
+ this.maxRetries = config.maxRetries || 3;
85
+ this.debug = config.debug || false;
86
+ this.client = axios.create({
87
+ baseURL: config.baseURL,
88
+ timeout: config.timeout || 3e4,
89
+ headers: {
90
+ "Authorization": `Bearer ${config.apiKey}`,
91
+ "Content-Type": "application/json"
92
+ }
93
+ });
94
+ if (this.debug) {
95
+ this.client.interceptors.request.use((request) => {
96
+ console.log("[PromptMetrics SDK] Request:", {
97
+ method: request.method,
98
+ url: request.url,
99
+ params: request.params,
100
+ data: request.data
101
+ });
102
+ return request;
103
+ });
104
+ }
105
+ if (this.debug) {
106
+ this.client.interceptors.response.use(
107
+ (response) => {
108
+ console.log("[PromptMetrics SDK] Response:", {
109
+ status: response.status,
110
+ data: response.data
111
+ });
112
+ return response;
113
+ },
114
+ (error) => {
115
+ console.error("[PromptMetrics SDK] Error:", error);
116
+ return Promise.reject(error);
117
+ }
118
+ );
119
+ }
120
+ }
121
+ /**
122
+ * Make HTTP request with retry logic
123
+ */
124
+ async request(config) {
125
+ let lastError;
126
+ for (let attempt = 0; attempt < this.maxRetries; attempt++) {
127
+ try {
128
+ const response = await this.client.request(config);
129
+ return response.data;
130
+ } catch (error) {
131
+ lastError = this.handleError(error);
132
+ if (!this.shouldRetry(error) || attempt === this.maxRetries - 1) {
133
+ throw lastError;
134
+ }
135
+ const delay = Math.min(1e3 * Math.pow(2, attempt), 1e4);
136
+ await this.sleep(delay);
137
+ }
138
+ }
139
+ throw lastError;
140
+ }
141
+ /**
142
+ * GET request
143
+ */
144
+ async get(url, config) {
145
+ return this.request({ ...config, method: "GET", url });
146
+ }
147
+ /**
148
+ * POST request
149
+ */
150
+ async post(url, data, config) {
151
+ return this.request({ ...config, method: "POST", url, data });
152
+ }
153
+ /**
154
+ * PUT request
155
+ */
156
+ async put(url, data, config) {
157
+ return this.request({ ...config, method: "PUT", url, data });
158
+ }
159
+ /**
160
+ * PATCH request
161
+ */
162
+ async patch(url, data, config) {
163
+ return this.request({ ...config, method: "PATCH", url, data });
164
+ }
165
+ /**
166
+ * DELETE request
167
+ */
168
+ async delete(url, config) {
169
+ return this.request({ ...config, method: "DELETE", url });
170
+ }
171
+ /**
172
+ * Handle axios errors and convert to SDK errors
173
+ * Extracts details from backend HttpException format
174
+ */
175
+ handleError(error) {
176
+ if (!error.response) {
177
+ if (error.code === "ECONNABORTED") {
178
+ return new TimeoutError("Request timeout");
179
+ }
180
+ return new NetworkError(error.message || "Network request failed");
181
+ }
182
+ const { status, data } = error.response;
183
+ const errorData = data;
184
+ const message = errorData?.message || error.message || "An error occurred";
185
+ const details = errorData?.data;
186
+ switch (status) {
187
+ case 401:
188
+ return new AuthenticationError(message);
189
+ case 403:
190
+ return new AuthorizationError(message);
191
+ case 404:
192
+ return new NotFoundError(message);
193
+ case 400:
194
+ return new ValidationError(message, details);
195
+ case 429:
196
+ return new RateLimitError(message);
197
+ case 500:
198
+ case 502:
199
+ case 503:
200
+ case 504:
201
+ return new ServerError(message);
202
+ default:
203
+ return new PromptMetricsError(message, status, void 0, details);
204
+ }
205
+ }
206
+ /**
207
+ * Determine if request should be retried
208
+ */
209
+ shouldRetry(error) {
210
+ if (!error.response) {
211
+ return true;
212
+ }
213
+ const status = error.response.status;
214
+ return status >= 500 || status === 429;
215
+ }
216
+ /**
217
+ * Sleep utility for retry backoff
218
+ */
219
+ sleep(ms) {
220
+ return new Promise((resolve) => setTimeout(resolve, ms));
221
+ }
222
+ };
223
+
224
+ // src/resources/base.ts
225
+ var BaseResource = class {
226
+ constructor(http) {
227
+ this.http = http;
228
+ }
229
+ /**
230
+ * Transform backend response to SDK format
231
+ * Extracts data from the general response wrapper
232
+ *
233
+ * Backend format: { success: true, message: "...", responseData: { data: {...} } }
234
+ * SDK format: Just the data
235
+ */
236
+ transformResponse(backendResponse) {
237
+ if (typeof backendResponse === "object" && backendResponse !== null && "responseData" in backendResponse && backendResponse.responseData?.data !== void 0) {
238
+ return backendResponse.responseData.data;
239
+ }
240
+ return backendResponse;
241
+ }
242
+ };
243
+
244
+ // src/resources/templates.ts
245
+ var TemplateResource = class extends BaseResource {
246
+ /**
247
+ * Get a template by ID or name
248
+ * Workspace ID is automatically determined from API key
249
+ *
250
+ * @param identifier - Template ID or name
251
+ * @param options - Optional version and env_label filters
252
+ * @param options.version - Specific version number to fetch
253
+ * @param options.env_label - Environment label: 'development' | 'staging' | 'production'
254
+ * @returns Template with versions
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * // Get template by ID (returns all versions)
259
+ * const template = await client.templates.get('template_id_123');
260
+ *
261
+ * // Get template by name (returns all versions)
262
+ * const template = await client.templates.get('my-template-name');
263
+ *
264
+ * // Get specific version
265
+ * const template = await client.templates.get('my-template', {
266
+ * version: 2
267
+ * });
268
+ *
269
+ * // Get by environment label
270
+ * const template = await client.templates.get('my-template', {
271
+ * env_label: 'production'
272
+ * });
273
+ * ```
274
+ */
275
+ async get(identifier, options) {
276
+ const response = await this.http.get(
277
+ `/template/${identifier}`,
278
+ {
279
+ params: options || {}
280
+ }
281
+ );
282
+ const template = this.transformResponse(response);
283
+ if (!options?.version && !options?.env_label && template.versions) {
284
+ const productionVersion = template.versions.find(
285
+ (v) => v.env_label === "production"
286
+ );
287
+ if (productionVersion) {
288
+ template.versions = [productionVersion];
289
+ } else {
290
+ const latestVersion = template.versions.reduce(
291
+ (latest, current) => current.version > latest.version ? current : latest
292
+ );
293
+ template.versions = [latestVersion];
294
+ }
295
+ }
296
+ return template;
297
+ }
298
+ /**
299
+ * List prompts (templates) with advanced filtering
300
+ * Workspace ID is automatically determined from API key
301
+ *
302
+ * @param options - Filter and pagination options
303
+ * @param options.search - Search in prompt name/description
304
+ * @param options.parent_id - Filter by parent folder ID
305
+ * @param options.risk_level - Filter by risk level: 'LOW' | 'MEDIUM' | 'HIGH'
306
+ * @param options.created_by - Filter by creator user ID
307
+ * @param options.start_date - Filter by creation date (ISO string)
308
+ * @param options.end_date - Filter by creation date (ISO string)
309
+ * @param options.sort_by - Sort field: 'created_at' | 'updated_at' | 'name'
310
+ * @param options.sort_order - Sort order: 'asc' | 'desc'
311
+ * @param options.page - Page number (default: 1)
312
+ * @param options.per_page - Items per page (default: 50)
313
+ * @returns Prompts list with total count
314
+ *
315
+ * @example
316
+ * ```typescript
317
+ * // List all prompts
318
+ * const result = await client.templates.list();
319
+ * console.log(`Found ${result.total} prompts`);
320
+ *
321
+ * // Search and filter prompts
322
+ * const result = await client.templates.list({
323
+ * search: 'customer',
324
+ * risk_level: 'HIGH',
325
+ * sort_by: 'updated_at',
326
+ * sort_order: 'desc'
327
+ * });
328
+ *
329
+ * // Paginate results
330
+ * const result = await client.templates.list({
331
+ * page: 1,
332
+ * per_page: 20
333
+ * });
334
+ * ```
335
+ */
336
+ async list(options) {
337
+ const response = await this.http.get(
338
+ "/template/prompts",
339
+ {
340
+ params: options || {}
341
+ }
342
+ );
343
+ return this.transformResponse(response);
344
+ }
345
+ /**
346
+ * Run a template (canary-aware)
347
+ *
348
+ * This method supports canary deployments:
349
+ * - If no version/env_label specified: Uses production with canary support
350
+ * - If specific version/env_label specified: Uses that version directly (no canary)
351
+ *
352
+ * @param identifier - Template ID or name
353
+ * @param options - Run options including variables, model overrides, and version selection
354
+ * @returns PromptLog with execution results
355
+ *
356
+ * @example
357
+ * ```typescript
358
+ * // Run template with production version (canary-aware)
359
+ * const result = await client.templates.run('my-template', {
360
+ * variables: { name: 'John', topic: 'AI' }
361
+ * });
362
+ *
363
+ * // Run specific version (bypasses canary)
364
+ * const result = await client.templates.run('my-template', {
365
+ * version: 3,
366
+ * variables: { name: 'John' }
367
+ * });
368
+ *
369
+ * // Run with environment label
370
+ * const result = await client.templates.run('my-template', {
371
+ * env_label: 'staging',
372
+ * variables: { name: 'John' }
373
+ * });
374
+ *
375
+ * // Run with model override and tags
376
+ * const result = await client.templates.run('my-template', {
377
+ * variables: { name: 'John' },
378
+ * model: 'gpt-4',
379
+ * pm_tags: ['production', 'customer-support']
380
+ * });
381
+ * ```
382
+ */
383
+ async run(identifier, options) {
384
+ const response = await this.http.post(
385
+ `/template/${identifier}/run`,
386
+ options || {}
387
+ );
388
+ return this.transformResponse(response);
389
+ }
390
+ };
391
+
392
+ // src/resources/logs.ts
393
+ var LogResource = class extends BaseResource {
394
+ /**
395
+ * List prompt logs (SDK logs only by default)
396
+ *
397
+ * @param options - List options including filters, pagination
398
+ * @returns Array of prompt logs
399
+ *
400
+ * @example
401
+ * ```typescript
402
+ * // List all SDK logs (workspace_id from API key)
403
+ * const logs = await client.logs.list({});
404
+ *
405
+ * // Filter by template
406
+ * const logs = await client.logs.list({
407
+ * template_id: 'template_123'
408
+ * });
409
+ *
410
+ * // Filter by template version
411
+ * const logs = await client.logs.list({
412
+ * template_version_id: 'version_123'
413
+ * });
414
+ *
415
+ * // Filter by date range
416
+ * const logs = await client.logs.list({
417
+ * start_date: '2024-01-01',
418
+ * end_date: '2024-01-31'
419
+ * });
420
+ *
421
+ * // Filter by status
422
+ * const logs = await client.logs.list({
423
+ * status: 'SUCCESS'
424
+ * });
425
+ *
426
+ * // Paginate and sort results
427
+ * const logs = await client.logs.list({
428
+ * page: 1,
429
+ * limit: 50,
430
+ * sort_by: 'created_at',
431
+ * sort_order: 'desc'
432
+ * });
433
+ * ```
434
+ */
435
+ async list(options = {}) {
436
+ const params = {
437
+ source: "SDK",
438
+ ...options
439
+ };
440
+ const response = await this.http.get(
441
+ "/prompt-logs",
442
+ {
443
+ params
444
+ }
445
+ );
446
+ return this.transformResponse(response);
447
+ }
448
+ /**
449
+ * Get a single prompt log by ID
450
+ *
451
+ * @param logId - Log ID
452
+ * @returns Prompt log details
453
+ *
454
+ * @example
455
+ * ```typescript
456
+ * const log = await client.logs.get('log_123');
457
+ * ```
458
+ */
459
+ async get(logId) {
460
+ const response = await this.http.get(
461
+ `/prompt-logs/${logId}`
462
+ );
463
+ return this.transformResponse(response);
464
+ }
465
+ };
466
+
467
+ // src/utils/context.ts
468
+ import { AsyncLocalStorage } from "async_hooks";
469
+ var ContextManager = class {
470
+ constructor() {
471
+ this.storage = new AsyncLocalStorage();
472
+ }
473
+ /**
474
+ * Get current trace context
475
+ */
476
+ getContext() {
477
+ return this.storage.getStore();
478
+ }
479
+ /**
480
+ * Get current trace ID
481
+ */
482
+ getCurrentTraceId() {
483
+ return this.getContext()?.trace_id;
484
+ }
485
+ /**
486
+ * Get current span ID
487
+ */
488
+ getCurrentSpanId() {
489
+ return this.getContext()?.span_id;
490
+ }
491
+ /**
492
+ * Get current parent span ID
493
+ */
494
+ getParentSpanId() {
495
+ return this.getContext()?.parent_span_id;
496
+ }
497
+ /**
498
+ * Get current group ID
499
+ */
500
+ getGroupId() {
501
+ return this.getContext()?.group_id;
502
+ }
503
+ /**
504
+ * Get current group type
505
+ */
506
+ getGroupType() {
507
+ return this.getContext()?.group_type;
508
+ }
509
+ /**
510
+ * Get current metadata
511
+ */
512
+ getMetadata() {
513
+ return this.getContext()?.metadata;
514
+ }
515
+ /**
516
+ * Run function with new context
517
+ */
518
+ run(context, fn) {
519
+ return this.storage.run(context, fn);
520
+ }
521
+ /**
522
+ * Run async function with new context
523
+ */
524
+ async runAsync(context, fn) {
525
+ return this.storage.run(context, fn);
526
+ }
527
+ /**
528
+ * Update current context (merge with existing)
529
+ */
530
+ updateContext(updates) {
531
+ const current = this.getContext();
532
+ if (current) {
533
+ Object.assign(current, updates);
534
+ }
535
+ }
536
+ /**
537
+ * Set group for current context and all child spans
538
+ */
539
+ updateGroup(group_id, group_type) {
540
+ this.updateContext({ group_id, group_type });
541
+ }
542
+ /**
543
+ * Add metadata to current context
544
+ */
545
+ addMetadata(metadata) {
546
+ const current = this.getContext();
547
+ if (current) {
548
+ current.metadata = { ...current.metadata, ...metadata };
549
+ }
550
+ }
551
+ /**
552
+ * Add score to current context
553
+ */
554
+ addScore(score) {
555
+ const current = this.getContext();
556
+ if (current) {
557
+ if (!current.scores) {
558
+ current.scores = [];
559
+ }
560
+ const scoreWithTimestamp = {
561
+ ...score,
562
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
563
+ };
564
+ current.scores.push(scoreWithTimestamp);
565
+ }
566
+ }
567
+ };
568
+ var contextManager = new ContextManager();
569
+
570
+ // src/resources/versions.ts
571
+ var VersionResource = class extends BaseResource {
572
+ async get(identifier, versionNumber) {
573
+ if (versionNumber !== void 0) {
574
+ const response = await this.http.get(
575
+ `/template-version/${identifier}`,
576
+ { params: { version: versionNumber } }
577
+ );
578
+ return this.transformResponse(response);
579
+ } else {
580
+ const response = await this.http.get(
581
+ `/template-version/${identifier}`
582
+ );
583
+ return this.transformResponse(response);
584
+ }
585
+ }
586
+ /**
587
+ * Run a template version with variables
588
+ *
589
+ * Automatically correlates with active trace context if called within @traceable function.
590
+ * The prompt_log will be linked to the current trace for end-to-end observability.
591
+ *
592
+ * @param versionId - Version ID to run
593
+ * @param options - Run options including variables and parameters
594
+ * @returns Prompt execution log
595
+ *
596
+ * @example
597
+ * ```typescript
598
+ * // Run a version with variables
599
+ * const result = await client.versions.run('version_123', {
600
+ * variables: {
601
+ * user_name: 'John',
602
+ * topic: 'AI'
603
+ * }
604
+ * });
605
+ *
606
+ * // Run with custom parameters
607
+ * const result = await client.versions.run('version_123', {
608
+ * variables: { topic: 'ML' },
609
+ * parameters: {
610
+ * temperature: 0.7,
611
+ * max_tokens: 500
612
+ * }
613
+ * });
614
+ *
615
+ * // Run with custom tags (stored directly in prompt log)
616
+ * const result = await client.versions.run('version_123', {
617
+ * variables: { topic: 'AI' },
618
+ * pm_tags: ['customer-support', 'high-priority', 'eu-region']
619
+ * });
620
+ *
621
+ * // Run within traced function (auto-correlation)
622
+ * @pm.traceable({ name: 'generate_response' })
623
+ * async generateResponse(input: string) {
624
+ * // This run will be automatically linked to the trace
625
+ * const result = await pm.versions.run('version_123', {
626
+ * variables: { input }
627
+ * });
628
+ * return result;
629
+ * }
630
+ * ```
631
+ */
632
+ async run(versionId, options) {
633
+ const context = contextManager.getContext();
634
+ const requestPayload = {
635
+ ...options,
636
+ // Include trace correlation data if available
637
+ trace_id: context?.trace_id,
638
+ span_id: context?.span_id,
639
+ group_id: context?.group_id
640
+ };
641
+ const response = await this.http.post(
642
+ `/template-version/${versionId}/run`,
643
+ requestPayload
644
+ );
645
+ return this.transformResponse(response);
646
+ }
647
+ /**
648
+ * Update a template version
649
+ *
650
+ * @param versionId - Version ID to update
651
+ * @param options - Update options (env_label, metadata, etc.)
652
+ * @returns Updated template version
653
+ *
654
+ * @example
655
+ * ```typescript
656
+ * // Update environment label
657
+ * const version = await client.versions.update('version_123', {
658
+ * env_label: 'production'
659
+ * });
660
+ *
661
+ * // Update metadata
662
+ * const version = await client.versions.update('version_123', {
663
+ * metadata: {
664
+ * deployed_by: 'John Doe',
665
+ * deployment_date: '2024-01-15'
666
+ * }
667
+ * });
668
+ *
669
+ * // Update both env_label and metadata
670
+ * const version = await client.versions.update('version_123', {
671
+ * env_label: 'staging',
672
+ * metadata: { tested: true },
673
+ * });
674
+ *
675
+ * ```
676
+ */
677
+ async update(versionId, options) {
678
+ const response = await this.http.put(
679
+ `/template-version/${versionId}`,
680
+ options
681
+ );
682
+ return this.transformResponse(response);
683
+ }
684
+ };
685
+
686
+ // src/resources/providers.ts
687
+ var ProviderResource = class extends BaseResource {
688
+ /**
689
+ * List all LLM providers
690
+ *
691
+ * Returns providers with `has_key` flag indicating if user has configured API key
692
+ *
693
+ * @returns Array of LLM providers
694
+ *
695
+ * @example
696
+ * ```typescript
697
+ * // List all providers
698
+ * const providers = await client.providers.list();
699
+ *
700
+ * // Check which providers have keys configured
701
+ * const configuredProviders = providers.filter(p => p.has_key);
702
+ * ```
703
+ */
704
+ async list() {
705
+ const response = await this.http.get(
706
+ "/llm-provider"
707
+ );
708
+ return this.transformResponse(response);
709
+ }
710
+ /**
711
+ * Get models for a specific provider by slug
712
+ *
713
+ * Returns provider details with all available models and parameters.
714
+ * Only returns models if user has API key configured for the provider.
715
+ *
716
+ * @param slug - Provider slug (e.g., 'openai', 'anthropic', 'google')
717
+ * @returns Provider with models and parameters
718
+ *
719
+ * @example
720
+ * ```typescript
721
+ * // Get OpenAI models
722
+ * const openai = await client.providers.getModels('openai');
723
+ * console.log(openai.models); // Array of OpenAI models
724
+ *
725
+ * // Get Anthropic models
726
+ * const anthropic = await client.providers.getModels('anthropic');
727
+ * console.log(anthropic.models); // Array of Claude models
728
+ *
729
+ * // Get Google models
730
+ * const google = await client.providers.getModels('google');
731
+ * console.log(google.models); // Array of Gemini models
732
+ * ```
733
+ */
734
+ async getModels(slug) {
735
+ const response = await this.http.get(
736
+ `/llm-provider/${slug}/models`
737
+ );
738
+ return this.transformResponse(response);
739
+ }
740
+ };
741
+
742
+ // src/resources/traces.ts
743
+ var TraceResource = class extends BaseResource {
744
+ /**
745
+ * Create a new trace
746
+ *
747
+ * @param options - Trace creation options
748
+ * @returns Created trace
749
+ *
750
+ * @example
751
+ * ```typescript
752
+ * const trace = await client.traces.create({
753
+ * trace_id: 'trace_123',
754
+ * span_id: 'span_abc',
755
+ * function_name: 'process_customer_data',
756
+ * function_type: 'CUSTOM',
757
+ * start_time: new Date('2025-12-08T10:00:00Z'),
758
+ * end_time: new Date('2025-12-08T10:00:02Z'),
759
+ * duration_ms: 2000,
760
+ * status: 'SUCCESS',
761
+ * metadata: {
762
+ * customer_id: '12345',
763
+ * processing_stage: 'enrichment'
764
+ * }
765
+ * });
766
+ * ```
767
+ */
768
+ async create(options) {
769
+ const response = await this.http.post(
770
+ "/traces",
771
+ options
772
+ );
773
+ return this.transformResponse(response);
774
+ }
775
+ /**
776
+ * Create multiple traces in batch (optimized for high throughput)
777
+ *
778
+ * Reduces HTTP overhead by sending multiple traces in a single request.
779
+ * Supports partial success - some traces may succeed while others fail.
780
+ *
781
+ * @param traces - Array of trace creation options (max 100)
782
+ * @returns Batch result with created traces and errors
783
+ *
784
+ * @example
785
+ * ```typescript
786
+ * const result = await client.traces.createBatch([
787
+ * {
788
+ * trace_id: 'trace_123',
789
+ * span_id: 'span_abc',
790
+ * function_name: 'step_1',
791
+ * start_time: new Date(),
792
+ * end_time: new Date(),
793
+ * duration_ms: 100,
794
+ * status: 'SUCCESS'
795
+ * },
796
+ * {
797
+ * trace_id: 'trace_123',
798
+ * span_id: 'span_def',
799
+ * function_name: 'step_2',
800
+ * start_time: new Date(),
801
+ * end_time: new Date(),
802
+ * duration_ms: 200,
803
+ * status: 'SUCCESS'
804
+ * }
805
+ * ]);
806
+ *
807
+ * console.log(`Created: ${result.summary.successful}, Failed: ${result.summary.failed}`);
808
+ * ```
809
+ */
810
+ async createBatch(traces) {
811
+ if (!traces || traces.length === 0) {
812
+ throw new Error("Traces array cannot be empty");
813
+ }
814
+ if (traces.length > 100) {
815
+ throw new Error("Maximum 100 traces allowed per batch");
816
+ }
817
+ const response = await this.http.post("/traces/batch", { traces });
818
+ return this.transformResponse(response);
819
+ }
820
+ /**
821
+ * Add a score to a trace
822
+ *
823
+ * @param span_id - Span ID of the trace
824
+ * @param options - Score options
825
+ * @returns Updated trace with score
826
+ *
827
+ * @example
828
+ * ```typescript
829
+ * const trace = await client.traces.addScore('span_abc', {
830
+ * criteria: 'coherence',
831
+ * value: 0.92
832
+ * });
833
+ * ```
834
+ */
835
+ async addScore(span_id, options) {
836
+ const response = await this.http.post(
837
+ `/traces/${span_id}/score`,
838
+ options
839
+ );
840
+ return this.transformResponse(response);
841
+ }
842
+ /**
843
+ * Update trace metadata
844
+ *
845
+ * @param span_id - Span ID of the trace
846
+ * @param options - Metadata update options
847
+ * @returns Updated trace
848
+ *
849
+ * @example
850
+ * ```typescript
851
+ * const trace = await client.traces.updateMetadata('span_abc', {
852
+ * metadata: {
853
+ * processing_result: 'completed',
854
+ * items_processed: 150
855
+ * }
856
+ * });
857
+ * ```
858
+ */
859
+ async updateMetadata(span_id, options) {
860
+ const response = await this.http.patch(
861
+ `/traces/${span_id}/metadata`,
862
+ options
863
+ );
864
+ return this.transformResponse(response);
865
+ }
866
+ /**
867
+ * Get a single trace by span_id
868
+ *
869
+ * @param span_id - Span ID of the trace
870
+ * @returns Trace details
871
+ *
872
+ * @example
873
+ * ```typescript
874
+ * const trace = await client.traces.getBySpanId('span_abc');
875
+ * console.log(trace.function_name, trace.duration_ms);
876
+ * ```
877
+ */
878
+ async getBySpanId(span_id) {
879
+ const response = await this.http.get(
880
+ `/traces/span/${span_id}`
881
+ );
882
+ return this.transformResponse(response);
883
+ }
884
+ /**
885
+ * Get entire trace tree by trace_id
886
+ *
887
+ * Returns all spans in the trace organized as a tree structure
888
+ * with parent-child relationships
889
+ *
890
+ * @param trace_id - Trace ID
891
+ * @returns Array of trace tree nodes with nested children
892
+ *
893
+ * @example
894
+ * ```typescript
895
+ * const tree = await client.traces.getTrace('trace_123');
896
+ *
897
+ * // Tree structure with children
898
+ * tree.forEach(root => {
899
+ * console.log(`Root: ${root.function_name}`);
900
+ * root.children.forEach(child => {
901
+ * console.log(` Child: ${child.function_name}`);
902
+ * });
903
+ * });
904
+ * ```
905
+ */
906
+ async getTrace(trace_id) {
907
+ const response = await this.http.get(
908
+ `/traces/trace/${trace_id}`
909
+ );
910
+ return this.transformResponse(response);
911
+ }
912
+ /**
913
+ * Get all traces for a group_id
914
+ *
915
+ * @param group_id - Group ID (e.g., conversation_id, workflow_id)
916
+ * @returns Array of traces in the group
917
+ *
918
+ * @example
919
+ * ```typescript
920
+ * const traces = await client.traces.getGroup('conversation_abc');
921
+ * console.log(`Found ${traces.length} traces in conversation`);
922
+ * ```
923
+ */
924
+ async getGroup(group_id) {
925
+ const response = await this.http.get(
926
+ `/traces/group/${group_id}`
927
+ );
928
+ return this.transformResponse(response);
929
+ }
930
+ /**
931
+ * List traces with filters and pagination
932
+ *
933
+ * @param options - List options with filters
934
+ * @returns Paginated list of traces
935
+ *
936
+ * @example
937
+ * ```typescript
938
+ * // List all traces
939
+ * const result = await client.traces.list();
940
+ *
941
+ * // Filter by function name
942
+ * const filtered = await client.traces.list({
943
+ * function_name: 'process_customer_data',
944
+ * status: 'SUCCESS',
945
+ * page: 1,
946
+ * limit: 50
947
+ * });
948
+ *
949
+ * // Filter by date range
950
+ * const recent = await client.traces.list({
951
+ * start_date: '2025-12-01T00:00:00Z',
952
+ * end_date: '2025-12-08T23:59:59Z'
953
+ * });
954
+ * ```
955
+ */
956
+ async list(options) {
957
+ const response = await this.http.get(
958
+ "/traces",
959
+ { params: options }
960
+ );
961
+ return this.transformResponse(response);
962
+ }
963
+ /**
964
+ * Get trace analytics for workspace
965
+ *
966
+ * Returns aggregated statistics about trace execution including
967
+ * success rates, average duration, and function-level breakdown
968
+ *
969
+ * @param options - Optional date range filter
970
+ * @returns Analytics data
971
+ *
972
+ * @example
973
+ * ```typescript
974
+ * // Get all-time analytics
975
+ * const analytics = await client.traces.getAnalytics();
976
+ * console.log(`Success rate: ${analytics.success_rate}%`);
977
+ * console.log(`Avg duration: ${analytics.average_duration_ms}ms`);
978
+ *
979
+ * // Get analytics for date range
980
+ * const weeklyAnalytics = await client.traces.getAnalytics({
981
+ * start_date: '2025-12-01T00:00:00Z',
982
+ * end_date: '2025-12-08T23:59:59Z'
983
+ * });
984
+ *
985
+ * // Function breakdown
986
+ * analytics.function_breakdown.forEach(fn => {
987
+ * console.log(`${fn.function_name}: ${fn.count} calls, ${fn.avg_duration_ms}ms avg`);
988
+ * });
989
+ * ```
990
+ */
991
+ async getAnalytics(options) {
992
+ const response = await this.http.get(
993
+ "/traces/analytics",
994
+ { params: options }
995
+ );
996
+ return this.transformResponse(response);
997
+ }
998
+ /**
999
+ * Delete old traces (cleanup)
1000
+ *
1001
+ * Deletes traces older than the specified date
1002
+ *
1003
+ * @param before_date - Delete traces before this date
1004
+ * @returns Number of traces deleted
1005
+ *
1006
+ * @example
1007
+ * ```typescript
1008
+ * // Delete traces older than 90 days
1009
+ * const ninetyDaysAgo = new Date();
1010
+ * ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
1011
+ *
1012
+ * const result = await client.traces.cleanup(ninetyDaysAgo.toISOString());
1013
+ * console.log(`Deleted ${result.deleted_count} old traces`);
1014
+ * ```
1015
+ */
1016
+ async cleanup(before_date) {
1017
+ const response = await this.http.delete("/traces/cleanup", {
1018
+ data: { before_date }
1019
+ });
1020
+ return this.transformResponse(response);
1021
+ }
1022
+ };
1023
+
1024
+ // src/utils/track.ts
1025
+ var Track = class {
1026
+ /**
1027
+ * Add metadata to the current span
1028
+ * Metadata is collected in context and sent when the trace is created
1029
+ *
1030
+ * @example
1031
+ * ```typescript
1032
+ * pm.track.metadata({
1033
+ * customer_id: '12345',
1034
+ * processing_stage: 'enrichment'
1035
+ * });
1036
+ * ```
1037
+ */
1038
+ metadata(extraData) {
1039
+ const spanId = contextManager.getCurrentSpanId();
1040
+ if (!spanId) {
1041
+ throw new Error(
1042
+ "No active span context. metadata() must be called within a @traceable function."
1043
+ );
1044
+ }
1045
+ contextManager.addMetadata(extraData);
1046
+ }
1047
+ /**
1048
+ * Add a score to the current span
1049
+ * Score is collected in context and sent when the trace is created
1050
+ *
1051
+ * @example
1052
+ * ```typescript
1053
+ * pm.track.score({
1054
+ * criteria: 'coherence',
1055
+ * value: 0.92
1056
+ * });
1057
+ * ```
1058
+ */
1059
+ score(options) {
1060
+ const spanId = contextManager.getCurrentSpanId();
1061
+ if (!spanId) {
1062
+ throw new Error(
1063
+ "No active span context. score() must be called within a @traceable function."
1064
+ );
1065
+ }
1066
+ contextManager.addScore(options);
1067
+ }
1068
+ /**
1069
+ * Set group for current context and all child spans
1070
+ * Uses context to propagate group to nested functions
1071
+ *
1072
+ * @example
1073
+ * ```typescript
1074
+ * pm.track.group({
1075
+ * group_id: 'conversation_abc',
1076
+ * group_type: 'conversation' // Flexible - use any string
1077
+ * });
1078
+ * ```
1079
+ */
1080
+ group(options) {
1081
+ contextManager.updateGroup(options.group_id, options.group_type);
1082
+ }
1083
+ };
1084
+
1085
+ // src/utils/id-generator.ts
1086
+ import { randomBytes } from "crypto";
1087
+ var IdGenerator = class {
1088
+ /**
1089
+ * Generate a unique trace ID
1090
+ */
1091
+ static generateTraceId() {
1092
+ return `trace_${Date.now()}_${randomBytes(8).toString("hex")}`;
1093
+ }
1094
+ /**
1095
+ * Generate a unique span ID
1096
+ */
1097
+ static generateSpanId() {
1098
+ return `span_${Date.now()}_${randomBytes(8).toString("hex")}`;
1099
+ }
1100
+ /**
1101
+ * Generate a unique group ID
1102
+ */
1103
+ static generateGroupId(prefix = "group") {
1104
+ return `${prefix}_${Date.now()}_${randomBytes(8).toString("hex")}`;
1105
+ }
1106
+ };
1107
+
1108
+ // src/decorators/traceable.ts
1109
+ function traceable(traceResource, options = {}) {
1110
+ return function(_target, propertyKey, descriptor) {
1111
+ const originalMethod = descriptor.value;
1112
+ if (options.disabled) {
1113
+ return descriptor;
1114
+ }
1115
+ descriptor.value = async function(...args) {
1116
+ const functionName = options.name || propertyKey;
1117
+ const currentContext = contextManager.getContext();
1118
+ const trace_id = currentContext?.trace_id || IdGenerator.generateTraceId();
1119
+ const span_id = IdGenerator.generateSpanId();
1120
+ const parent_span_id = currentContext?.span_id;
1121
+ const group_id = currentContext?.group_id;
1122
+ const group_type = currentContext?.group_type;
1123
+ const tags = [...currentContext?.tags || [], ...options.tags || []];
1124
+ const metadata = {
1125
+ ...currentContext?.metadata,
1126
+ ...options.metadata
1127
+ };
1128
+ const start_time = /* @__PURE__ */ new Date();
1129
+ let status = "SUCCESS";
1130
+ let error;
1131
+ let result;
1132
+ let input;
1133
+ let output;
1134
+ let capturedContext;
1135
+ if (args.length > 0) {
1136
+ input = {};
1137
+ args.slice(0, 3).forEach((arg, index) => {
1138
+ try {
1139
+ input[`arg_${index}`] = JSON.parse(JSON.stringify(arg));
1140
+ } catch {
1141
+ input[`arg_${index}`] = String(arg);
1142
+ }
1143
+ });
1144
+ }
1145
+ try {
1146
+ result = await contextManager.runAsync(
1147
+ {
1148
+ trace_id,
1149
+ span_id,
1150
+ parent_span_id,
1151
+ group_id,
1152
+ group_type,
1153
+ tags,
1154
+ metadata,
1155
+ scores: []
1156
+ // Initialize empty scores array
1157
+ },
1158
+ async () => {
1159
+ const res = await originalMethod.apply(this, args);
1160
+ capturedContext = contextManager.getContext();
1161
+ return res;
1162
+ }
1163
+ );
1164
+ try {
1165
+ const serialized = JSON.parse(JSON.stringify(result));
1166
+ if (Array.isArray(serialized)) {
1167
+ output = { result: serialized };
1168
+ } else if (typeof serialized === "object" && serialized !== null) {
1169
+ output = serialized;
1170
+ } else {
1171
+ output = { result: serialized };
1172
+ }
1173
+ } catch {
1174
+ output = { result: String(result) };
1175
+ }
1176
+ } catch (err) {
1177
+ status = "ERROR";
1178
+ error = {
1179
+ message: err instanceof Error ? err.message : String(err),
1180
+ stack: err instanceof Error ? err.stack : void 0,
1181
+ code: err.code
1182
+ };
1183
+ throw err;
1184
+ } finally {
1185
+ const end_time = /* @__PURE__ */ new Date();
1186
+ const duration_ms = end_time.getTime() - start_time.getTime();
1187
+ try {
1188
+ const contextScores = capturedContext?.scores;
1189
+ const contextMetadata = capturedContext?.metadata || {};
1190
+ const finalMetadata = {
1191
+ ...metadata,
1192
+ ...contextMetadata
1193
+ };
1194
+ await traceResource.create({
1195
+ trace_id,
1196
+ span_id,
1197
+ parent_span_id,
1198
+ function_name: functionName,
1199
+ start_time,
1200
+ end_time,
1201
+ duration_ms,
1202
+ status,
1203
+ error,
1204
+ input,
1205
+ output,
1206
+ metadata: finalMetadata,
1207
+ scores: contextScores,
1208
+ tags,
1209
+ group_id,
1210
+ group_type
1211
+ });
1212
+ } catch (traceError) {
1213
+ console.error("[PromptMetrics] Failed to send trace:", traceError);
1214
+ }
1215
+ }
1216
+ return result;
1217
+ };
1218
+ return descriptor;
1219
+ };
1220
+ }
1221
+ function createTraceableDecorator(traceResource) {
1222
+ return (options = {}) => traceable(traceResource, options);
1223
+ }
1224
+
1225
+ // src/client.ts
1226
+ var PromptMetrics = class {
1227
+ /**
1228
+ * Create a new PromptMetrics client
1229
+ *
1230
+ * @param config - Configuration options
1231
+ * @throws {ValidationError} If API key is missing or invalid
1232
+ */
1233
+ constructor(config) {
1234
+ this.validateConfig(config);
1235
+ const baseURL = process.env.BASE_URL;
1236
+ if (!baseURL) {
1237
+ throw new ValidationError(
1238
+ "BASE_URL environment variable is required. Please set it in your .env file."
1239
+ );
1240
+ }
1241
+ this.httpClient = new HttpClient({
1242
+ baseURL,
1243
+ apiKey: config.apiKey,
1244
+ timeout: config.timeout,
1245
+ maxRetries: config.maxRetries,
1246
+ debug: config.debug
1247
+ });
1248
+ this.templates = new TemplateResource(this.httpClient);
1249
+ this.versions = new VersionResource(this.httpClient);
1250
+ this.logs = new LogResource(this.httpClient);
1251
+ this.providers = new ProviderResource(this.httpClient);
1252
+ this.traces = new TraceResource(this.httpClient);
1253
+ this.track = new Track();
1254
+ }
1255
+ /**
1256
+ * Create a @traceable decorator for automatic function tracking
1257
+ *
1258
+ * @param options - Decorator options
1259
+ * @returns Decorator function
1260
+ *
1261
+ * @example
1262
+ * ```typescript
1263
+ * class MyService {
1264
+ * @pm.traceable({ name: 'process_data' })
1265
+ * async processData(input: string) {
1266
+ * // Automatically tracked
1267
+ * return result;
1268
+ * }
1269
+ * }
1270
+ * ```
1271
+ */
1272
+ traceable(options = {}) {
1273
+ return createTraceableDecorator(this.traces)(options);
1274
+ }
1275
+ /**
1276
+ * Get current trace ID from context
1277
+ */
1278
+ getCurrentTraceId() {
1279
+ return contextManager.getCurrentTraceId();
1280
+ }
1281
+ /**
1282
+ * Get current span ID from context
1283
+ */
1284
+ getCurrentSpanId() {
1285
+ return contextManager.getCurrentSpanId();
1286
+ }
1287
+ /**
1288
+ * Validate configuration
1289
+ */
1290
+ validateConfig(config) {
1291
+ if (!config.apiKey) {
1292
+ throw new ValidationError(
1293
+ "API key is required. Please provide a valid workspace API key (starts with pm_)."
1294
+ );
1295
+ }
1296
+ if (typeof config.apiKey !== "string") {
1297
+ throw new ValidationError("API key must be a string.");
1298
+ }
1299
+ if (!config.apiKey.startsWith("pm_")) {
1300
+ throw new ValidationError(
1301
+ 'Invalid API key format. Workspace API keys should start with "pm_".'
1302
+ );
1303
+ }
1304
+ if (config.apiKey.length < 20) {
1305
+ throw new ValidationError(
1306
+ "API key appears to be too short. Please check your API key."
1307
+ );
1308
+ }
1309
+ }
1310
+ };
1311
+
1312
+ // src/index.ts
1313
+ var VERSION = "1.0.0";
1314
+ export {
1315
+ AuthenticationError,
1316
+ AuthorizationError,
1317
+ NetworkError,
1318
+ NotFoundError,
1319
+ PromptMetrics,
1320
+ PromptMetricsError,
1321
+ RateLimitError,
1322
+ ServerError,
1323
+ TimeoutError,
1324
+ VERSION,
1325
+ ValidationError
1326
+ };