@sentrial/sdk 0.1.0 → 0.3.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.cjs CHANGED
@@ -22,17 +22,49 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ApiError: () => ApiError,
24
24
  EventType: () => EventType,
25
+ Experiment: () => Experiment,
26
+ ExperimentRunTracker: () => ExperimentRunTracker,
25
27
  Interaction: () => Interaction,
26
28
  NetworkError: () => NetworkError,
27
29
  SentrialClient: () => SentrialClient,
28
30
  SentrialError: () => SentrialError,
31
+ SessionContext: () => SessionContext,
32
+ Tool: () => Tool,
33
+ TrackSession: () => TrackSession,
29
34
  ValidationError: () => ValidationError,
30
35
  begin: () => begin,
31
36
  calculateAnthropicCost: () => calculateAnthropicCost,
32
37
  calculateGoogleCost: () => calculateGoogleCost,
33
38
  calculateOpenAICost: () => calculateOpenAICost,
39
+ clearExperimentContext: () => clearExperimentContext,
40
+ clearSessionContext: () => clearSessionContext,
34
41
  configure: () => configure,
35
- sentrial: () => sentrial
42
+ configureVercel: () => configureVercel,
43
+ getCurrentInteraction: () => getCurrentInteraction,
44
+ getCurrentSessionId: () => getCurrentSessionId,
45
+ getExperimentContext: () => getExperimentContext,
46
+ getExperimentId: () => getExperimentId,
47
+ getSessionContext: () => getSessionContext,
48
+ getSystemPrompt: () => getSystemPrompt,
49
+ getVariantName: () => getVariantName,
50
+ hashValue: () => hashValue,
51
+ isExperimentMode: () => isExperimentMode,
52
+ redactPayload: () => redactPayload,
53
+ redactString: () => redactString,
54
+ redactValue: () => redactValue,
55
+ replaceMatch: () => replaceMatch,
56
+ sentrial: () => sentrial,
57
+ setClient: () => setClient,
58
+ setDefaultClient: () => setDefaultClient,
59
+ setExperimentContext: () => setExperimentContext,
60
+ setSessionContext: () => setSessionContext,
61
+ withSession: () => withSession,
62
+ withTool: () => withTool,
63
+ wrapAISDK: () => wrapAISDK,
64
+ wrapAnthropic: () => wrapAnthropic,
65
+ wrapGoogle: () => wrapGoogle,
66
+ wrapLLM: () => wrapLLM,
67
+ wrapOpenAI: () => wrapOpenAI
36
68
  });
37
69
  module.exports = __toCommonJS(index_exports);
38
70
 
@@ -105,26 +137,126 @@ var ValidationError = class extends SentrialError {
105
137
  }
106
138
  };
107
139
 
140
+ // src/redact.ts
141
+ var import_crypto = require("crypto");
142
+ var DEFAULT_FIELDS = [
143
+ "userInput",
144
+ "assistantOutput",
145
+ "toolInput",
146
+ "toolOutput"
147
+ ];
148
+ var DEFAULT_BUILTIN = {
149
+ emails: true,
150
+ phones: true,
151
+ ssns: true,
152
+ creditCards: true,
153
+ ipAddresses: true
154
+ };
155
+ var EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
156
+ var PHONE_PATTERN = /(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g;
157
+ var SSN_PATTERN = /\b\d{3}-\d{2}-\d{4}\b/g;
158
+ var CREDIT_CARD_PATTERN = /\b(?:\d[-\s]?){13,19}\b/g;
159
+ var IP_ADDRESS_PATTERN = /\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b/g;
160
+ var BUILTIN_PATTERNS = {
161
+ emails: { pattern: EMAIL_PATTERN, label: "EMAIL" },
162
+ phones: { pattern: PHONE_PATTERN, label: "PHONE" },
163
+ ssns: { pattern: SSN_PATTERN, label: "SSN" },
164
+ creditCards: { pattern: CREDIT_CARD_PATTERN, label: "CREDIT_CARD" },
165
+ ipAddresses: { pattern: IP_ADDRESS_PATTERN, label: "IP_ADDRESS" }
166
+ };
167
+ function hashValue(value) {
168
+ return (0, import_crypto.createHash)("sha256").update(value).digest("hex").slice(0, 6);
169
+ }
170
+ function replaceMatch(match, label, mode) {
171
+ switch (mode) {
172
+ case "label":
173
+ return `[${label}]`;
174
+ case "hash":
175
+ return `[PII:${hashValue(match)}]`;
176
+ case "remove":
177
+ return "";
178
+ }
179
+ }
180
+ function redactString(value, mode, builtinPatterns, customPatterns) {
181
+ let result = value;
182
+ for (const [key, config] of Object.entries(BUILTIN_PATTERNS)) {
183
+ if (builtinPatterns[key]) {
184
+ const regex = new RegExp(config.pattern.source, config.pattern.flags);
185
+ result = result.replace(regex, (match) => replaceMatch(match, config.label, mode));
186
+ }
187
+ }
188
+ for (const custom of customPatterns) {
189
+ const regex = new RegExp(custom.pattern.source, custom.pattern.flags);
190
+ result = result.replace(regex, (match) => replaceMatch(match, custom.label, mode));
191
+ }
192
+ return result;
193
+ }
194
+ function redactValue(value, mode, builtinPatterns, customPatterns) {
195
+ if (typeof value === "string") {
196
+ return redactString(value, mode, builtinPatterns, customPatterns);
197
+ }
198
+ if (Array.isArray(value)) {
199
+ return value.map((item) => redactValue(item, mode, builtinPatterns, customPatterns));
200
+ }
201
+ if (value !== null && typeof value === "object") {
202
+ const result = {};
203
+ for (const [key, val] of Object.entries(value)) {
204
+ result[key] = redactValue(val, mode, builtinPatterns, customPatterns);
205
+ }
206
+ return result;
207
+ }
208
+ return value;
209
+ }
210
+ function redactPayload(payload, config) {
211
+ if (!config.enabled) {
212
+ return payload;
213
+ }
214
+ const mode = config.mode ?? "label";
215
+ const fields = config.fields ?? DEFAULT_FIELDS;
216
+ const builtinPatterns = {
217
+ ...DEFAULT_BUILTIN,
218
+ ...config.builtinPatterns
219
+ };
220
+ const customPatterns = config.customPatterns ?? [];
221
+ const result = { ...payload };
222
+ for (const field of fields) {
223
+ if (field in result && result[field] !== void 0 && result[field] !== null) {
224
+ result[field] = redactValue(result[field], mode, builtinPatterns, customPatterns);
225
+ }
226
+ }
227
+ return result;
228
+ }
229
+
108
230
  // src/cost.ts
109
231
  var OPENAI_PRICING = {
110
232
  "gpt-5.2": { input: 5, output: 15 },
111
233
  "gpt-5": { input: 4, output: 12 },
234
+ "gpt-4.1": { input: 2, output: 8 },
235
+ "gpt-4.1-mini": { input: 0.4, output: 1.6 },
236
+ "gpt-4.1-nano": { input: 0.1, output: 0.4 },
112
237
  "gpt-4o": { input: 2.5, output: 10 },
113
238
  "gpt-4o-mini": { input: 0.15, output: 0.6 },
114
239
  "gpt-4-turbo": { input: 10, output: 30 },
115
240
  "gpt-4": { input: 30, output: 60 },
116
241
  "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
242
+ "o4-mini": { input: 1.1, output: 4.4 },
117
243
  "o3": { input: 10, output: 40 },
118
- "o3-mini": { input: 3, output: 12 },
244
+ "o3-mini": { input: 1.1, output: 4.4 },
245
+ "o3-pro": { input: 20, output: 80 },
246
+ "o1": { input: 15, output: 60 },
119
247
  "o1-preview": { input: 15, output: 60 },
120
248
  "o1-mini": { input: 3, output: 12 }
121
249
  };
122
250
  var ANTHROPIC_PRICING = {
251
+ "claude-opus-4": { input: 15, output: 75 },
252
+ "claude-sonnet-4": { input: 3, output: 15 },
123
253
  "claude-4.5-opus": { input: 20, output: 100 },
124
254
  "claude-4.5-sonnet": { input: 4, output: 20 },
125
255
  "claude-4-opus": { input: 18, output: 90 },
126
256
  "claude-4-sonnet": { input: 3.5, output: 17.5 },
257
+ "claude-3-7-sonnet": { input: 3, output: 15 },
127
258
  "claude-3-5-sonnet": { input: 3, output: 15 },
259
+ "claude-3-5-haiku": { input: 0.8, output: 4 },
128
260
  "claude-3-opus": { input: 15, output: 75 },
129
261
  "claude-3-sonnet": { input: 3, output: 15 },
130
262
  "claude-3-haiku": { input: 0.25, output: 1.25 }
@@ -185,65 +317,168 @@ var EventType = /* @__PURE__ */ ((EventType2) => {
185
317
 
186
318
  // src/client.ts
187
319
  var DEFAULT_API_URL = "https://api.sentrial.com";
320
+ var MAX_RETRIES = 3;
321
+ var INITIAL_BACKOFF_MS = 500;
322
+ var MAX_BACKOFF_MS = 8e3;
323
+ var BACKOFF_MULTIPLIER = 2;
324
+ var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
325
+ var REQUEST_TIMEOUT_MS = 1e4;
188
326
  var SentrialClient = class {
189
327
  apiUrl;
190
328
  apiKey;
191
329
  failSilently;
330
+ piiConfig;
331
+ piiConfigNeedsHydration = false;
332
+ piiHydrationPromise;
192
333
  currentState = {};
193
334
  constructor(config = {}) {
194
335
  this.apiUrl = (config.apiUrl ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_URL : void 0) ?? DEFAULT_API_URL).replace(/\/$/, "");
195
336
  this.apiKey = config.apiKey ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_KEY : void 0);
196
337
  this.failSilently = config.failSilently ?? true;
338
+ if (config.pii === true) {
339
+ this.piiConfig = { enabled: true };
340
+ this.piiConfigNeedsHydration = true;
341
+ } else if (config.pii && typeof config.pii === "object") {
342
+ this.piiConfig = config.pii;
343
+ this.piiConfigNeedsHydration = false;
344
+ }
197
345
  }
198
346
  /**
199
- * Make an HTTP request with graceful error handling
347
+ * Fetch the organization's PII config from the server.
348
+ *
349
+ * Called lazily on the first request when `pii: true` was passed to the constructor.
350
+ * Uses a single shared promise so concurrent requests don't trigger duplicate fetches.
200
351
  */
201
- async safeRequest(method, url, body) {
202
- try {
203
- const headers = {
204
- "Content-Type": "application/json"
205
- };
206
- if (this.apiKey) {
207
- headers["Authorization"] = `Bearer ${this.apiKey}`;
208
- }
209
- const response = await fetch(url, {
210
- method,
211
- headers,
212
- body: body ? JSON.stringify(body) : void 0
213
- });
214
- if (!response.ok) {
215
- const errorBody = await response.text();
216
- let errorData = {};
352
+ async hydratePiiConfig() {
353
+ if (!this.piiConfigNeedsHydration) return;
354
+ if (this.piiHydrationPromise) {
355
+ await this.piiHydrationPromise;
356
+ return;
357
+ }
358
+ this.piiHydrationPromise = (async () => {
359
+ try {
360
+ const headers = {};
361
+ if (this.apiKey) {
362
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
363
+ }
364
+ const controller = new AbortController();
365
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
366
+ let response;
217
367
  try {
218
- errorData = JSON.parse(errorBody);
219
- } catch {
368
+ response = await fetch(`${this.apiUrl}/api/sdk/pii-config`, {
369
+ method: "GET",
370
+ headers,
371
+ signal: controller.signal
372
+ });
373
+ } finally {
374
+ clearTimeout(timeoutId);
220
375
  }
221
- const error = new ApiError(
222
- errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`,
223
- response.status,
224
- errorData.error?.code
225
- );
226
- if (this.failSilently) {
227
- console.warn(`Sentrial: Request failed (${method} ${url}):`, error.message);
228
- return null;
376
+ if (response.ok) {
377
+ const data = await response.json();
378
+ if (data.config) {
379
+ this.piiConfig = {
380
+ enabled: data.config.enabled,
381
+ mode: data.config.mode,
382
+ fields: data.config.fields,
383
+ builtinPatterns: data.config.builtinPatterns,
384
+ customPatterns: (data.config.customPatterns || []).map(
385
+ (cp) => ({
386
+ pattern: new RegExp(cp.pattern, "g"),
387
+ label: cp.label
388
+ })
389
+ ),
390
+ enhancedDetection: data.config.enhancedDetection
391
+ };
392
+ }
229
393
  }
230
- throw error;
394
+ } catch {
231
395
  }
232
- return await response.json();
233
- } catch (error) {
234
- if (error instanceof SentrialError) {
235
- throw error;
236
- }
237
- const networkError = new NetworkError(
238
- error instanceof Error ? error.message : "Unknown network error",
239
- error instanceof Error ? error : void 0
240
- );
241
- if (this.failSilently) {
242
- console.warn(`Sentrial: Request failed (${method} ${url}):`, networkError.message);
243
- return null;
396
+ this.piiConfigNeedsHydration = false;
397
+ })();
398
+ await this.piiHydrationPromise;
399
+ }
400
+ /**
401
+ * Make an HTTP request with retry logic and exponential backoff.
402
+ *
403
+ * Retries on transient failures (network errors, timeouts, 429/5xx).
404
+ * Up to MAX_RETRIES attempts with exponential backoff.
405
+ */
406
+ async safeRequest(method, url, body) {
407
+ if (this.piiConfigNeedsHydration) {
408
+ await this.hydratePiiConfig();
409
+ }
410
+ let lastError;
411
+ let backoff = INITIAL_BACKOFF_MS;
412
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
413
+ try {
414
+ const headers = {
415
+ "Content-Type": "application/json"
416
+ };
417
+ if (this.apiKey) {
418
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
419
+ }
420
+ const finalBody = this.piiConfig && body && typeof body === "object" ? redactPayload(body, this.piiConfig) : body;
421
+ const controller = new AbortController();
422
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
423
+ let response;
424
+ try {
425
+ response = await fetch(url, {
426
+ method,
427
+ headers,
428
+ body: finalBody ? JSON.stringify(finalBody) : void 0,
429
+ signal: controller.signal
430
+ });
431
+ } finally {
432
+ clearTimeout(timeoutId);
433
+ }
434
+ if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < MAX_RETRIES) {
435
+ await this.sleep(backoff);
436
+ backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
437
+ continue;
438
+ }
439
+ if (!response.ok) {
440
+ const errorBody = await response.text();
441
+ let errorData = {};
442
+ try {
443
+ errorData = JSON.parse(errorBody);
444
+ } catch {
445
+ }
446
+ const error = new ApiError(
447
+ errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`,
448
+ response.status,
449
+ errorData.error?.code
450
+ );
451
+ if (this.failSilently) {
452
+ console.warn(`Sentrial: Request failed (${method} ${url}):`, error.message);
453
+ return null;
454
+ }
455
+ throw error;
456
+ }
457
+ return await response.json();
458
+ } catch (error) {
459
+ if (error instanceof ApiError) {
460
+ throw error;
461
+ }
462
+ lastError = error instanceof Error ? error : new Error(String(error));
463
+ if (attempt < MAX_RETRIES) {
464
+ await this.sleep(backoff);
465
+ backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
466
+ continue;
467
+ }
244
468
  }
245
- throw networkError;
246
469
  }
470
+ const networkError = new NetworkError(
471
+ lastError?.message ?? "Unknown network error",
472
+ lastError
473
+ );
474
+ if (this.failSilently) {
475
+ console.warn(`Sentrial: Request failed after ${MAX_RETRIES + 1} attempts (${method} ${url}):`, networkError.message);
476
+ return null;
477
+ }
478
+ throw networkError;
479
+ }
480
+ sleep(ms) {
481
+ return new Promise((resolve) => setTimeout(resolve, ms));
247
482
  }
248
483
  /**
249
484
  * Create a new session
@@ -259,6 +494,12 @@ var SentrialClient = class {
259
494
  userId: params.userId,
260
495
  metadata: params.metadata
261
496
  };
497
+ if (params.parentSessionId) {
498
+ payload.parentSessionId = params.parentSessionId;
499
+ }
500
+ if (params.convoId) {
501
+ payload.convoId = params.convoId;
502
+ }
262
503
  const response = await this.safeRequest(
263
504
  "POST",
264
505
  `${this.apiUrl}/api/sdk/sessions`,
@@ -352,6 +593,42 @@ var SentrialClient = class {
352
593
  updateState(key, value) {
353
594
  this.currentState[key] = value;
354
595
  }
596
+ /**
597
+ * Set the user input for a session
598
+ *
599
+ * @param sessionId - Session ID
600
+ * @param input - User input text
601
+ * @returns Updated session or null on error
602
+ */
603
+ async setInput(sessionId, input) {
604
+ return this.safeRequest(
605
+ "PATCH",
606
+ `${this.apiUrl}/api/sdk/sessions/${sessionId}`,
607
+ { userInput: input }
608
+ );
609
+ }
610
+ /**
611
+ * Track a generic event
612
+ *
613
+ * @param params - Event parameters
614
+ * @returns Event data or null on error
615
+ */
616
+ async trackEvent(params) {
617
+ const stateBefore = { ...this.currentState };
618
+ const payload = {
619
+ sessionId: params.sessionId,
620
+ eventType: params.eventType,
621
+ stateBefore,
622
+ stateAfter: { ...this.currentState }
623
+ };
624
+ if (params.eventData) {
625
+ Object.assign(payload, params.eventData);
626
+ }
627
+ if (params.metadata) {
628
+ payload.metadata = params.metadata;
629
+ }
630
+ return this.safeRequest("POST", `${this.apiUrl}/api/sdk/events`, payload);
631
+ }
355
632
  /**
356
633
  * Complete a session with performance metrics
357
634
  *
@@ -391,7 +668,8 @@ var SentrialClient = class {
391
668
  if (params.completionTokens !== void 0) payload.completionTokens = params.completionTokens;
392
669
  if (params.totalTokens !== void 0) payload.totalTokens = params.totalTokens;
393
670
  if (params.userInput !== void 0) payload.userInput = params.userInput;
394
- if (params.assistantOutput !== void 0) payload.assistantOutput = params.assistantOutput;
671
+ const output = params.assistantOutput ?? params.output;
672
+ if (output !== void 0) payload.assistantOutput = output;
395
673
  return this.safeRequest(
396
674
  "PATCH",
397
675
  `${this.apiUrl}/api/sdk/sessions/${params.sessionId}`,
@@ -428,6 +706,7 @@ var SentrialClient = class {
428
706
  name: `${params.event}:${eventId.slice(0, 8)}`,
429
707
  agentName: params.event,
430
708
  userId: params.userId,
709
+ convoId: params.convoId,
431
710
  metadata: fullMetadata
432
711
  });
433
712
  if (params.input) {
@@ -438,7 +717,8 @@ var SentrialClient = class {
438
717
  sessionId,
439
718
  eventId,
440
719
  userId: params.userId,
441
- event: params.event
720
+ event: params.event,
721
+ userInput: params.input
442
722
  });
443
723
  }
444
724
  // Cost calculation static methods for convenience
@@ -459,6 +739,7 @@ var Interaction = class {
459
739
  success = true;
460
740
  failureReason;
461
741
  output;
742
+ userInput;
462
743
  degraded;
463
744
  constructor(config) {
464
745
  this.client = config.client;
@@ -466,6 +747,7 @@ var Interaction = class {
466
747
  this.eventId = config.eventId;
467
748
  this.userId = config.userId;
468
749
  this.event = config.event;
750
+ this.userInput = config.userInput;
469
751
  this.degraded = config.sessionId === null;
470
752
  }
471
753
  /**
@@ -511,6 +793,7 @@ var Interaction = class {
511
793
  promptTokens: params.promptTokens,
512
794
  completionTokens: params.completionTokens,
513
795
  totalTokens: params.totalTokens,
796
+ userInput: this.userInput,
514
797
  assistantOutput: finalOutput
515
798
  });
516
799
  }
@@ -576,20 +859,1599 @@ var sentrial = {
576
859
  configure,
577
860
  begin
578
861
  };
862
+
863
+ // src/vercel.ts
864
+ var _defaultClient = null;
865
+ var _globalConfig = {};
866
+ function configureVercel(config) {
867
+ _defaultClient = new SentrialClient({
868
+ apiKey: config.apiKey,
869
+ apiUrl: config.apiUrl,
870
+ failSilently: config.failSilently ?? true
871
+ });
872
+ _globalConfig = {
873
+ defaultAgent: config.defaultAgent,
874
+ userId: config.userId,
875
+ convoId: config.convoId
876
+ };
877
+ }
878
+ function getClient2() {
879
+ if (!_defaultClient) {
880
+ _defaultClient = new SentrialClient();
881
+ }
882
+ return _defaultClient;
883
+ }
884
+ function extractModelInfo(model) {
885
+ const modelId = model.modelId || model.id || "unknown";
886
+ const provider = model.provider || guessProvider(modelId);
887
+ return { modelId, provider };
888
+ }
889
+ function guessProvider(modelId) {
890
+ const id = modelId.toLowerCase();
891
+ if (id.includes("gpt") || id.includes("o1") || id.includes("o3") || id.includes("o4") || id.startsWith("chatgpt")) return "openai";
892
+ if (id.includes("claude")) return "anthropic";
893
+ if (id.includes("gemini")) return "google";
894
+ if (id.includes("mistral") || id.includes("mixtral") || id.includes("codestral") || id.includes("pixtral")) return "mistral";
895
+ if (id.includes("llama")) return "meta";
896
+ if (id.includes("deepseek")) return "deepseek";
897
+ if (id.includes("command")) return "cohere";
898
+ if (id.includes("qwen")) return "alibaba";
899
+ return "unknown";
900
+ }
901
+ function calculateCostForCall(provider, modelId, promptTokens, completionTokens) {
902
+ const params = { model: modelId, inputTokens: promptTokens, outputTokens: completionTokens };
903
+ switch (provider.toLowerCase()) {
904
+ case "openai":
905
+ return calculateOpenAICost(params);
906
+ case "anthropic":
907
+ return calculateAnthropicCost(params);
908
+ case "google":
909
+ return calculateGoogleCost(params);
910
+ case "deepseek":
911
+ return promptTokens / 1e6 * 0.27 + completionTokens / 1e6 * 1.1;
912
+ case "cohere":
913
+ return promptTokens / 1e6 * 0.5 + completionTokens / 1e6 * 1.5;
914
+ case "mistral":
915
+ return promptTokens / 1e6 * 2 + completionTokens / 1e6 * 6;
916
+ default:
917
+ return promptTokens * 3e-6 + completionTokens * 6e-6;
918
+ }
919
+ }
920
+ function extractInput(params) {
921
+ if (params.prompt) return params.prompt;
922
+ if (params.messages && params.messages.length > 0) {
923
+ const lastUserMessage = [...params.messages].reverse().find((m) => m.role === "user");
924
+ if (lastUserMessage) {
925
+ return typeof lastUserMessage.content === "string" ? lastUserMessage.content : JSON.stringify(lastUserMessage.content);
926
+ }
927
+ return JSON.stringify(params.messages);
928
+ }
929
+ return "";
930
+ }
931
+ function wrapTools(tools, sessionId, client) {
932
+ if (!tools) return void 0;
933
+ const wrappedTools = {};
934
+ for (const [toolName, tool] of Object.entries(tools)) {
935
+ if (typeof tool.execute === "function") {
936
+ const originalExecute = tool.execute;
937
+ wrappedTools[toolName] = {
938
+ ...tool,
939
+ execute: async (...args) => {
940
+ const startTime = Date.now();
941
+ try {
942
+ const result = await originalExecute(...args);
943
+ const durationMs = Date.now() - startTime;
944
+ client.trackToolCall({
945
+ sessionId,
946
+ toolName,
947
+ toolInput: args[0],
948
+ toolOutput: result,
949
+ reasoning: `Tool executed in ${durationMs}ms`
950
+ }).catch(() => {
951
+ });
952
+ return result;
953
+ } catch (error) {
954
+ const durationMs = Date.now() - startTime;
955
+ client.trackToolCall({
956
+ sessionId,
957
+ toolName,
958
+ toolInput: args[0],
959
+ toolOutput: {},
960
+ toolError: { message: error instanceof Error ? error.message : "Unknown error" },
961
+ reasoning: `Tool failed after ${durationMs}ms`
962
+ }).catch(() => {
963
+ });
964
+ throw error;
965
+ }
966
+ }
967
+ };
968
+ } else {
969
+ wrappedTools[toolName] = tool;
970
+ }
971
+ }
972
+ return wrappedTools;
973
+ }
974
+ function wrapToolsAsync(tools, sessionPromise, client) {
975
+ const wrappedTools = {};
976
+ for (const [toolName, tool] of Object.entries(tools)) {
977
+ if (typeof tool.execute === "function") {
978
+ const originalExecute = tool.execute;
979
+ wrappedTools[toolName] = {
980
+ ...tool,
981
+ execute: async (...args) => {
982
+ const startTime = Date.now();
983
+ const sid = await sessionPromise;
984
+ try {
985
+ const result = await originalExecute(...args);
986
+ const durationMs = Date.now() - startTime;
987
+ if (sid) {
988
+ client.trackToolCall({
989
+ sessionId: sid,
990
+ toolName,
991
+ toolInput: args[0],
992
+ toolOutput: result,
993
+ reasoning: `Tool executed in ${durationMs}ms`
994
+ }).catch(() => {
995
+ });
996
+ }
997
+ return result;
998
+ } catch (error) {
999
+ const durationMs = Date.now() - startTime;
1000
+ if (sid) {
1001
+ client.trackToolCall({
1002
+ sessionId: sid,
1003
+ toolName,
1004
+ toolInput: args[0],
1005
+ toolOutput: {},
1006
+ toolError: { message: error instanceof Error ? error.message : "Unknown error" },
1007
+ reasoning: `Tool failed after ${durationMs}ms`
1008
+ }).catch(() => {
1009
+ });
1010
+ }
1011
+ throw error;
1012
+ }
1013
+ }
1014
+ };
1015
+ } else {
1016
+ wrappedTools[toolName] = tool;
1017
+ }
1018
+ }
1019
+ return wrappedTools;
1020
+ }
1021
+ function wrapGenerateText(originalFn, client, config) {
1022
+ return async (params) => {
1023
+ const startTime = Date.now();
1024
+ const { modelId, provider } = extractModelInfo(params.model);
1025
+ const input = extractInput(params);
1026
+ const sessionId = await client.createSession({
1027
+ name: `generateText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1028
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1029
+ userId: config.userId ?? "anonymous",
1030
+ convoId: config.convoId,
1031
+ metadata: {
1032
+ model: modelId,
1033
+ provider,
1034
+ function: "generateText",
1035
+ ...params.maxSteps ? { maxSteps: params.maxSteps } : {}
1036
+ }
1037
+ });
1038
+ if (!sessionId) {
1039
+ return originalFn(params);
1040
+ }
1041
+ await client.setInput(sessionId, input);
1042
+ const wrappedParams = {
1043
+ ...params,
1044
+ tools: wrapTools(params.tools, sessionId, client)
1045
+ };
1046
+ try {
1047
+ const result = await originalFn(wrappedParams);
1048
+ const durationMs = Date.now() - startTime;
1049
+ const resolvedModelId = result.response?.modelId || modelId;
1050
+ const promptTokens = result.usage?.promptTokens || 0;
1051
+ const completionTokens = result.usage?.completionTokens || 0;
1052
+ const totalTokens = result.usage?.totalTokens || promptTokens + completionTokens;
1053
+ const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
1054
+ const steps = result.steps;
1055
+ if (steps && steps.length >= 1) {
1056
+ for (let i = 0; i < steps.length; i++) {
1057
+ const step = steps[i];
1058
+ await client.trackEvent({
1059
+ sessionId,
1060
+ eventType: "llm_call",
1061
+ eventData: {
1062
+ model: resolvedModelId,
1063
+ provider,
1064
+ step: i + 1,
1065
+ total_steps: steps.length,
1066
+ prompt_tokens: step.usage?.promptTokens || 0,
1067
+ completion_tokens: step.usage?.completionTokens || 0,
1068
+ total_tokens: step.usage?.totalTokens || 0,
1069
+ finish_reason: step.finishReason,
1070
+ tool_calls: step.toolCalls?.map((tc) => tc.toolName)
1071
+ }
1072
+ });
1073
+ }
1074
+ } else {
1075
+ await client.trackEvent({
1076
+ sessionId,
1077
+ eventType: "llm_call",
1078
+ eventData: {
1079
+ model: resolvedModelId,
1080
+ provider,
1081
+ prompt_tokens: promptTokens,
1082
+ completion_tokens: completionTokens,
1083
+ total_tokens: totalTokens,
1084
+ finish_reason: result.finishReason,
1085
+ tool_calls: result.toolCalls?.map((tc) => tc.toolName)
1086
+ }
1087
+ });
1088
+ }
1089
+ await client.completeSession({
1090
+ sessionId,
1091
+ success: true,
1092
+ output: result.text,
1093
+ durationMs,
1094
+ estimatedCost: cost,
1095
+ promptTokens,
1096
+ completionTokens,
1097
+ totalTokens
1098
+ });
1099
+ return result;
1100
+ } catch (error) {
1101
+ const durationMs = Date.now() - startTime;
1102
+ await client.trackError({
1103
+ sessionId,
1104
+ errorType: error instanceof Error ? error.name : "Error",
1105
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
1106
+ });
1107
+ await client.completeSession({
1108
+ sessionId,
1109
+ success: false,
1110
+ failureReason: error instanceof Error ? error.message : "Unknown error",
1111
+ durationMs
1112
+ });
1113
+ throw error;
1114
+ }
1115
+ };
1116
+ }
1117
+ function wrapStreamText(originalFn, client, config) {
1118
+ return (params) => {
1119
+ const startTime = Date.now();
1120
+ const { modelId, provider } = extractModelInfo(params.model);
1121
+ const input = extractInput(params);
1122
+ let sessionId = null;
1123
+ const sessionPromise = (async () => {
1124
+ try {
1125
+ const id = await client.createSession({
1126
+ name: `streamText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1127
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1128
+ userId: config.userId ?? "anonymous",
1129
+ convoId: config.convoId,
1130
+ metadata: {
1131
+ model: modelId,
1132
+ provider,
1133
+ function: "streamText"
1134
+ }
1135
+ });
1136
+ sessionId = id;
1137
+ if (id) {
1138
+ client.setInput(id, input).catch(() => {
1139
+ });
1140
+ }
1141
+ return id;
1142
+ } catch {
1143
+ return null;
1144
+ }
1145
+ })();
1146
+ const wrappedParams = {
1147
+ ...params,
1148
+ tools: params.tools ? wrapToolsAsync(params.tools, sessionPromise, client) : void 0
1149
+ };
1150
+ const result = originalFn(wrappedParams);
1151
+ let tracked = false;
1152
+ async function trackCompletion(fullText, error) {
1153
+ if (tracked) return;
1154
+ tracked = true;
1155
+ const durationMs = Date.now() - startTime;
1156
+ const sid = sessionId || await sessionPromise;
1157
+ if (!sid) return;
1158
+ if (error) {
1159
+ await client.trackError({
1160
+ sessionId: sid,
1161
+ errorType: error.name || "Error",
1162
+ errorMessage: error.message || "Unknown error"
1163
+ });
1164
+ await client.completeSession({
1165
+ sessionId: sid,
1166
+ success: false,
1167
+ failureReason: error.message || "Unknown error",
1168
+ durationMs
1169
+ });
1170
+ return;
1171
+ }
1172
+ let usage;
1173
+ try {
1174
+ usage = result.usage ? await result.usage : void 0;
1175
+ } catch {
1176
+ }
1177
+ const promptTokens = usage?.promptTokens || 0;
1178
+ const completionTokens = usage?.completionTokens || 0;
1179
+ const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
1180
+ const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
1181
+ await client.trackEvent({
1182
+ sessionId: sid,
1183
+ eventType: "llm_call",
1184
+ eventData: {
1185
+ model: modelId,
1186
+ provider,
1187
+ prompt_tokens: promptTokens,
1188
+ completion_tokens: completionTokens,
1189
+ total_tokens: totalTokens
1190
+ }
1191
+ });
1192
+ await client.completeSession({
1193
+ sessionId: sid,
1194
+ success: true,
1195
+ output: fullText,
1196
+ durationMs,
1197
+ estimatedCost: cost,
1198
+ promptTokens,
1199
+ completionTokens,
1200
+ totalTokens
1201
+ });
1202
+ }
1203
+ const textProp = result.text;
1204
+ if (typeof textProp === "string") {
1205
+ trackCompletion(textProp).catch(() => {
1206
+ });
1207
+ } else if (textProp != null && typeof textProp.then === "function") {
1208
+ const originalTextPromise = textProp;
1209
+ result.text = originalTextPromise.then((text) => {
1210
+ trackCompletion(text).catch(() => {
1211
+ });
1212
+ return text;
1213
+ }).catch((err) => {
1214
+ trackCompletion("", err instanceof Error ? err : new Error(String(err))).catch(() => {
1215
+ });
1216
+ throw err;
1217
+ });
1218
+ } else {
1219
+ const originalTextStream = result.textStream;
1220
+ let fullText = "";
1221
+ result.textStream = (async function* () {
1222
+ try {
1223
+ for await (const chunk of originalTextStream) {
1224
+ fullText += chunk;
1225
+ yield chunk;
1226
+ }
1227
+ await trackCompletion(fullText);
1228
+ } catch (error) {
1229
+ await trackCompletion(
1230
+ "",
1231
+ error instanceof Error ? error : new Error(String(error))
1232
+ );
1233
+ throw error;
1234
+ }
1235
+ })();
1236
+ }
1237
+ return result;
1238
+ };
1239
+ }
1240
+ function wrapGenerateObject(originalFn, client, config) {
1241
+ return async (params) => {
1242
+ const startTime = Date.now();
1243
+ const { modelId, provider } = extractModelInfo(params.model);
1244
+ const input = extractInput(params);
1245
+ const sessionId = await client.createSession({
1246
+ name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1247
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1248
+ userId: config.userId ?? "anonymous",
1249
+ convoId: config.convoId,
1250
+ metadata: {
1251
+ model: modelId,
1252
+ provider,
1253
+ function: "generateObject"
1254
+ }
1255
+ });
1256
+ if (!sessionId) {
1257
+ return originalFn(params);
1258
+ }
1259
+ await client.setInput(sessionId, input);
1260
+ try {
1261
+ const result = await originalFn(params);
1262
+ const durationMs = Date.now() - startTime;
1263
+ const resolvedModelId = result.response?.modelId || modelId;
1264
+ const promptTokens = result.usage?.promptTokens || 0;
1265
+ const completionTokens = result.usage?.completionTokens || 0;
1266
+ const totalTokens = result.usage?.totalTokens || promptTokens + completionTokens;
1267
+ const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
1268
+ await client.completeSession({
1269
+ sessionId,
1270
+ success: true,
1271
+ output: JSON.stringify(result.object),
1272
+ durationMs,
1273
+ estimatedCost: cost,
1274
+ promptTokens,
1275
+ completionTokens,
1276
+ totalTokens
1277
+ });
1278
+ return result;
1279
+ } catch (error) {
1280
+ const durationMs = Date.now() - startTime;
1281
+ await client.trackError({
1282
+ sessionId,
1283
+ errorType: error instanceof Error ? error.name : "Error",
1284
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
1285
+ });
1286
+ await client.completeSession({
1287
+ sessionId,
1288
+ success: false,
1289
+ failureReason: error instanceof Error ? error.message : "Unknown error",
1290
+ durationMs
1291
+ });
1292
+ throw error;
1293
+ }
1294
+ };
1295
+ }
1296
+ function wrapStreamObject(originalFn, client, config) {
1297
+ return (params) => {
1298
+ const startTime = Date.now();
1299
+ const { modelId, provider } = extractModelInfo(params.model);
1300
+ const input = extractInput(params);
1301
+ const sessionPromise = (async () => {
1302
+ try {
1303
+ const id = await client.createSession({
1304
+ name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1305
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1306
+ userId: config.userId ?? "anonymous",
1307
+ convoId: config.convoId,
1308
+ metadata: {
1309
+ model: modelId,
1310
+ provider,
1311
+ function: "streamObject"
1312
+ }
1313
+ });
1314
+ if (id) {
1315
+ client.setInput(id, input).catch(() => {
1316
+ });
1317
+ }
1318
+ return id;
1319
+ } catch {
1320
+ return null;
1321
+ }
1322
+ })();
1323
+ const result = originalFn(params);
1324
+ if (result.object) {
1325
+ const originalObjectPromise = result.object;
1326
+ result.object = originalObjectPromise.then(async (obj) => {
1327
+ const durationMs = Date.now() - startTime;
1328
+ const sid = await sessionPromise;
1329
+ if (sid) {
1330
+ let usage;
1331
+ try {
1332
+ usage = result.usage ? await result.usage : void 0;
1333
+ } catch {
1334
+ }
1335
+ const promptTokens = usage?.promptTokens || 0;
1336
+ const completionTokens = usage?.completionTokens || 0;
1337
+ const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
1338
+ const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
1339
+ await client.completeSession({
1340
+ sessionId: sid,
1341
+ success: true,
1342
+ output: JSON.stringify(obj),
1343
+ durationMs,
1344
+ estimatedCost: cost,
1345
+ promptTokens,
1346
+ completionTokens,
1347
+ totalTokens
1348
+ });
1349
+ }
1350
+ return obj;
1351
+ }).catch(async (error) => {
1352
+ const durationMs = Date.now() - startTime;
1353
+ const sid = await sessionPromise;
1354
+ if (sid) {
1355
+ await client.trackError({
1356
+ sessionId: sid,
1357
+ errorType: error instanceof Error ? error.name : "Error",
1358
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
1359
+ });
1360
+ await client.completeSession({
1361
+ sessionId: sid,
1362
+ success: false,
1363
+ failureReason: error instanceof Error ? error.message : "Unknown error",
1364
+ durationMs
1365
+ });
1366
+ }
1367
+ throw error;
1368
+ });
1369
+ }
1370
+ return result;
1371
+ };
1372
+ }
1373
+ function wrapAISDK(ai, options) {
1374
+ const client = options?.client ?? getClient2();
1375
+ const config = {
1376
+ defaultAgent: options?.defaultAgent ?? _globalConfig.defaultAgent,
1377
+ userId: options?.userId ?? _globalConfig.userId,
1378
+ convoId: options?.convoId ?? _globalConfig.convoId
1379
+ };
1380
+ return {
1381
+ generateText: ai.generateText ? wrapGenerateText(ai.generateText, client, config) : wrapGenerateText(
1382
+ () => Promise.reject(new Error("generateText not available")),
1383
+ client,
1384
+ config
1385
+ ),
1386
+ streamText: ai.streamText ? wrapStreamText(ai.streamText, client, config) : wrapStreamText(() => ({ textStream: (async function* () {
1387
+ })() }), client, config),
1388
+ generateObject: ai.generateObject ? wrapGenerateObject(ai.generateObject, client, config) : wrapGenerateObject(
1389
+ () => Promise.reject(new Error("generateObject not available")),
1390
+ client,
1391
+ config
1392
+ ),
1393
+ streamObject: ai.streamObject ? wrapStreamObject(ai.streamObject, client, config) : wrapStreamObject(() => ({}), client, config)
1394
+ };
1395
+ }
1396
+
1397
+ // src/wrappers.ts
1398
+ var _currentSessionId = null;
1399
+ var _currentClient = null;
1400
+ var _defaultClient2 = null;
1401
+ function setSessionContext(sessionId, client) {
1402
+ _currentSessionId = sessionId;
1403
+ if (client) {
1404
+ _currentClient = client;
1405
+ }
1406
+ }
1407
+ function clearSessionContext() {
1408
+ _currentSessionId = null;
1409
+ _currentClient = null;
1410
+ }
1411
+ function getSessionContext() {
1412
+ return _currentSessionId;
1413
+ }
1414
+ function setDefaultClient(client) {
1415
+ _defaultClient2 = client;
1416
+ }
1417
+ function getTrackingClient() {
1418
+ return _currentClient ?? _defaultClient2;
1419
+ }
1420
+ function wrapOpenAI(client, options = {}) {
1421
+ const { trackWithoutSession = false } = options;
1422
+ const chat = client.chat;
1423
+ if (!chat?.completions?.create) {
1424
+ console.warn("Sentrial: OpenAI client does not have chat.completions.create");
1425
+ return client;
1426
+ }
1427
+ const originalCreate = chat.completions.create.bind(chat.completions);
1428
+ chat.completions.create = async function(...args) {
1429
+ const startTime = Date.now();
1430
+ const params = args[0] ?? {};
1431
+ const messages = params.messages ?? [];
1432
+ const model = params.model ?? "unknown";
1433
+ try {
1434
+ const response = await originalCreate(...args);
1435
+ const durationMs = Date.now() - startTime;
1436
+ const promptTokens = response.usage?.prompt_tokens ?? 0;
1437
+ const completionTokens = response.usage?.completion_tokens ?? 0;
1438
+ const totalTokens = response.usage?.total_tokens ?? 0;
1439
+ let outputContent = "";
1440
+ if (response.choices?.[0]?.message?.content) {
1441
+ outputContent = response.choices[0].message.content;
1442
+ }
1443
+ const cost = calculateOpenAICost({ model, inputTokens: promptTokens, outputTokens: completionTokens });
1444
+ trackLLMCall({
1445
+ provider: "openai",
1446
+ model,
1447
+ messages,
1448
+ output: outputContent,
1449
+ promptTokens,
1450
+ completionTokens,
1451
+ totalTokens,
1452
+ cost,
1453
+ durationMs,
1454
+ trackWithoutSession
1455
+ });
1456
+ return response;
1457
+ } catch (error) {
1458
+ const durationMs = Date.now() - startTime;
1459
+ trackLLMError({
1460
+ provider: "openai",
1461
+ model,
1462
+ messages,
1463
+ error,
1464
+ durationMs,
1465
+ trackWithoutSession
1466
+ });
1467
+ throw error;
1468
+ }
1469
+ };
1470
+ return client;
1471
+ }
1472
+ function wrapAnthropic(client, options = {}) {
1473
+ const { trackWithoutSession = false } = options;
1474
+ const messages = client.messages;
1475
+ if (!messages?.create) {
1476
+ console.warn("Sentrial: Anthropic client does not have messages.create");
1477
+ return client;
1478
+ }
1479
+ const originalCreate = messages.create.bind(messages);
1480
+ messages.create = async function(...args) {
1481
+ const startTime = Date.now();
1482
+ const params = args[0] ?? {};
1483
+ const inputMessages = params.messages ?? [];
1484
+ const model = params.model ?? "unknown";
1485
+ const system = params.system ?? "";
1486
+ try {
1487
+ const response = await originalCreate(...args);
1488
+ const durationMs = Date.now() - startTime;
1489
+ const promptTokens = response.usage?.input_tokens ?? 0;
1490
+ const completionTokens = response.usage?.output_tokens ?? 0;
1491
+ const totalTokens = promptTokens + completionTokens;
1492
+ let outputContent = "";
1493
+ if (response.content) {
1494
+ for (const block of response.content) {
1495
+ if (block.type === "text") {
1496
+ outputContent += block.text;
1497
+ }
1498
+ }
1499
+ }
1500
+ const cost = calculateAnthropicCost({ model, inputTokens: promptTokens, outputTokens: completionTokens });
1501
+ const fullMessages = system ? [{ role: "system", content: system }, ...inputMessages] : inputMessages;
1502
+ trackLLMCall({
1503
+ provider: "anthropic",
1504
+ model,
1505
+ messages: fullMessages,
1506
+ output: outputContent,
1507
+ promptTokens,
1508
+ completionTokens,
1509
+ totalTokens,
1510
+ cost,
1511
+ durationMs,
1512
+ trackWithoutSession
1513
+ });
1514
+ return response;
1515
+ } catch (error) {
1516
+ const durationMs = Date.now() - startTime;
1517
+ trackLLMError({
1518
+ provider: "anthropic",
1519
+ model,
1520
+ messages: inputMessages,
1521
+ error,
1522
+ durationMs,
1523
+ trackWithoutSession
1524
+ });
1525
+ throw error;
1526
+ }
1527
+ };
1528
+ return client;
1529
+ }
1530
+ function wrapGoogle(model, options = {}) {
1531
+ const { trackWithoutSession = false } = options;
1532
+ const originalGenerate = model.generateContent;
1533
+ if (!originalGenerate) {
1534
+ console.warn("Sentrial: Google model does not have generateContent");
1535
+ return model;
1536
+ }
1537
+ model.generateContent = async function(...args) {
1538
+ const startTime = Date.now();
1539
+ const contents = args[0];
1540
+ const modelName = model.model ?? "gemini-unknown";
1541
+ const messages = googleContentsToMessages(contents);
1542
+ try {
1543
+ const response = await originalGenerate.apply(model, args);
1544
+ const durationMs = Date.now() - startTime;
1545
+ let promptTokens = 0;
1546
+ let completionTokens = 0;
1547
+ if (response.usageMetadata) {
1548
+ promptTokens = response.usageMetadata.promptTokenCount ?? 0;
1549
+ completionTokens = response.usageMetadata.candidatesTokenCount ?? 0;
1550
+ }
1551
+ const totalTokens = promptTokens + completionTokens;
1552
+ let outputContent = "";
1553
+ try {
1554
+ outputContent = response.response?.text() ?? "";
1555
+ } catch {
1556
+ }
1557
+ const cost = calculateGoogleCost({ model: modelName, inputTokens: promptTokens, outputTokens: completionTokens });
1558
+ trackLLMCall({
1559
+ provider: "google",
1560
+ model: modelName,
1561
+ messages,
1562
+ output: outputContent,
1563
+ promptTokens,
1564
+ completionTokens,
1565
+ totalTokens,
1566
+ cost,
1567
+ durationMs,
1568
+ trackWithoutSession
1569
+ });
1570
+ return response;
1571
+ } catch (error) {
1572
+ const durationMs = Date.now() - startTime;
1573
+ trackLLMError({
1574
+ provider: "google",
1575
+ model: modelName,
1576
+ messages,
1577
+ error,
1578
+ durationMs,
1579
+ trackWithoutSession
1580
+ });
1581
+ throw error;
1582
+ }
1583
+ };
1584
+ return model;
1585
+ }
1586
+ function googleContentsToMessages(contents) {
1587
+ if (typeof contents === "string") {
1588
+ return [{ role: "user", content: contents }];
1589
+ }
1590
+ if (Array.isArray(contents)) {
1591
+ return contents.map((item) => {
1592
+ if (typeof item === "string") {
1593
+ return { role: "user", content: item };
1594
+ }
1595
+ if (item && typeof item === "object") {
1596
+ return { role: item.role ?? "user", content: String(item.content ?? item) };
1597
+ }
1598
+ return { role: "user", content: String(item) };
1599
+ });
1600
+ }
1601
+ return [{ role: "user", content: String(contents) }];
1602
+ }
1603
+ function wrapLLM(client, provider) {
1604
+ if (provider === "openai" || client.chat?.completions?.create) {
1605
+ return wrapOpenAI(client);
1606
+ }
1607
+ if (provider === "anthropic" || client.messages?.create) {
1608
+ return wrapAnthropic(client);
1609
+ }
1610
+ if (provider === "google" || client.generateContent) {
1611
+ return wrapGoogle(client);
1612
+ }
1613
+ console.warn("Sentrial: Unknown LLM client type. No auto-tracking applied.");
1614
+ return client;
1615
+ }
1616
+ function trackLLMCall(params) {
1617
+ const client = getTrackingClient();
1618
+ if (!client) return;
1619
+ const sessionId = _currentSessionId;
1620
+ if (!sessionId && !params.trackWithoutSession) {
1621
+ return;
1622
+ }
1623
+ if (sessionId) {
1624
+ client.trackToolCall({
1625
+ sessionId,
1626
+ toolName: `llm:${params.provider}:${params.model}`,
1627
+ toolInput: {
1628
+ messages: params.messages,
1629
+ model: params.model,
1630
+ provider: params.provider
1631
+ },
1632
+ toolOutput: {
1633
+ content: params.output,
1634
+ tokens: {
1635
+ prompt: params.promptTokens,
1636
+ completion: params.completionTokens,
1637
+ total: params.totalTokens
1638
+ },
1639
+ cost_usd: params.cost
1640
+ },
1641
+ reasoning: `LLM call to ${params.provider} ${params.model}`,
1642
+ estimatedCost: params.cost,
1643
+ tokenCount: params.totalTokens,
1644
+ metadata: {
1645
+ provider: params.provider,
1646
+ model: params.model,
1647
+ duration_ms: params.durationMs,
1648
+ prompt_tokens: params.promptTokens,
1649
+ completion_tokens: params.completionTokens
1650
+ }
1651
+ }).catch((err) => {
1652
+ console.warn("Sentrial: Failed to track LLM call:", err.message);
1653
+ });
1654
+ }
1655
+ }
1656
+ function trackLLMError(params) {
1657
+ const client = getTrackingClient();
1658
+ if (!client) return;
1659
+ const sessionId = _currentSessionId;
1660
+ if (!sessionId && !params.trackWithoutSession) {
1661
+ return;
1662
+ }
1663
+ if (sessionId) {
1664
+ client.trackError({
1665
+ sessionId,
1666
+ errorMessage: params.error.message,
1667
+ errorType: params.error.name,
1668
+ toolName: `llm:${params.provider}:${params.model}`,
1669
+ metadata: {
1670
+ provider: params.provider,
1671
+ model: params.model,
1672
+ duration_ms: params.durationMs
1673
+ }
1674
+ }).catch((err) => {
1675
+ console.warn("Sentrial: Failed to track LLM error:", err.message);
1676
+ });
1677
+ }
1678
+ }
1679
+
1680
+ // src/decorators.ts
1681
+ var _defaultClient3 = null;
1682
+ var _currentInteraction = null;
1683
+ function getClient3() {
1684
+ if (!_defaultClient3) {
1685
+ try {
1686
+ _defaultClient3 = new SentrialClient();
1687
+ setDefaultClient(_defaultClient3);
1688
+ } catch {
1689
+ return null;
1690
+ }
1691
+ }
1692
+ return _defaultClient3;
1693
+ }
1694
+ function setClient(client) {
1695
+ _defaultClient3 = client;
1696
+ setDefaultClient(client);
1697
+ }
1698
+ function getCurrentSessionId() {
1699
+ return getSessionContext();
1700
+ }
1701
+ function getCurrentInteraction() {
1702
+ return _currentInteraction;
1703
+ }
1704
+ function withTool(name, fn) {
1705
+ const isAsync = fn.constructor.name === "AsyncFunction";
1706
+ if (isAsync) {
1707
+ return async function(...args) {
1708
+ return trackToolAsync(name, fn, args);
1709
+ };
1710
+ } else {
1711
+ return function(...args) {
1712
+ return trackToolSync(name, fn, args);
1713
+ };
1714
+ }
1715
+ }
1716
+ async function trackToolAsync(toolName, fn, args) {
1717
+ const startTime = Date.now();
1718
+ const toolInput = buildToolInput(args);
1719
+ const client = getClient3();
1720
+ const sessionId = getSessionContext();
1721
+ try {
1722
+ const result = await fn(...args);
1723
+ const durationMs = Date.now() - startTime;
1724
+ if (client && sessionId) {
1725
+ const toolOutput = serializeOutput(result);
1726
+ client.trackToolCall({
1727
+ sessionId,
1728
+ toolName,
1729
+ toolInput,
1730
+ toolOutput,
1731
+ metadata: { duration_ms: durationMs }
1732
+ }).catch((err) => {
1733
+ console.warn(`Sentrial: Failed to track tool ${toolName}:`, err.message);
1734
+ });
1735
+ }
1736
+ return result;
1737
+ } catch (error) {
1738
+ const durationMs = Date.now() - startTime;
1739
+ if (client && sessionId) {
1740
+ client.trackError({
1741
+ sessionId,
1742
+ errorMessage: error.message,
1743
+ errorType: error.name,
1744
+ toolName,
1745
+ stackTrace: error.stack,
1746
+ metadata: { duration_ms: durationMs }
1747
+ }).catch(() => {
1748
+ });
1749
+ }
1750
+ throw error;
1751
+ }
1752
+ }
1753
+ function trackToolSync(toolName, fn, args) {
1754
+ const startTime = Date.now();
1755
+ const toolInput = buildToolInput(args);
1756
+ const client = getClient3();
1757
+ const sessionId = getSessionContext();
1758
+ try {
1759
+ const result = fn(...args);
1760
+ const durationMs = Date.now() - startTime;
1761
+ if (client && sessionId) {
1762
+ const toolOutput = serializeOutput(result);
1763
+ client.trackToolCall({
1764
+ sessionId,
1765
+ toolName,
1766
+ toolInput,
1767
+ toolOutput,
1768
+ metadata: { duration_ms: durationMs }
1769
+ }).catch((err) => {
1770
+ console.warn(`Sentrial: Failed to track tool ${toolName}:`, err.message);
1771
+ });
1772
+ }
1773
+ return result;
1774
+ } catch (error) {
1775
+ const durationMs = Date.now() - startTime;
1776
+ if (client && sessionId) {
1777
+ client.trackError({
1778
+ sessionId,
1779
+ errorMessage: error.message,
1780
+ errorType: error.name,
1781
+ toolName,
1782
+ stackTrace: error.stack,
1783
+ metadata: { duration_ms: durationMs }
1784
+ }).catch(() => {
1785
+ });
1786
+ }
1787
+ throw error;
1788
+ }
1789
+ }
1790
+ function withSession(agentName, fn, options = {}) {
1791
+ return async function(...args) {
1792
+ const client = getClient3();
1793
+ if (!client) {
1794
+ return fn(...args);
1795
+ }
1796
+ const { userId, userInput } = extractParams(args, options);
1797
+ const interaction = await client.begin({
1798
+ userId,
1799
+ event: agentName,
1800
+ input: userInput
1801
+ });
1802
+ const sessionId = interaction.getSessionId();
1803
+ if (sessionId) {
1804
+ setSessionContext(sessionId, client);
1805
+ }
1806
+ _currentInteraction = interaction;
1807
+ try {
1808
+ const result = await fn(...args);
1809
+ let output;
1810
+ if (typeof result === "string") {
1811
+ output = result;
1812
+ } else if (result && typeof result === "object") {
1813
+ if ("response" in result) {
1814
+ output = String(result.response);
1815
+ } else if ("output" in result) {
1816
+ output = String(result.output);
1817
+ }
1818
+ }
1819
+ if (output === void 0 && result !== null && result !== void 0) {
1820
+ output = String(result).slice(0, 1e3);
1821
+ }
1822
+ await interaction.finish({ output, success: true });
1823
+ return result;
1824
+ } catch (error) {
1825
+ await interaction.finish({
1826
+ success: false,
1827
+ failureReason: `${error.name}: ${error.message}`
1828
+ });
1829
+ throw error;
1830
+ } finally {
1831
+ clearSessionContext();
1832
+ _currentInteraction = null;
1833
+ }
1834
+ };
1835
+ }
1836
+ function extractParams(args, options) {
1837
+ let userId = "anonymous";
1838
+ let userInput;
1839
+ if (typeof options.userIdParam === "number") {
1840
+ userId = String(args[options.userIdParam] ?? "anonymous");
1841
+ } else if (typeof options.userIdParam === "string") {
1842
+ userId = String(args[0] ?? "anonymous");
1843
+ } else {
1844
+ userId = String(args[0] ?? "anonymous");
1845
+ }
1846
+ if (typeof options.inputParam === "number") {
1847
+ userInput = String(args[options.inputParam] ?? "");
1848
+ } else {
1849
+ const input = args[1];
1850
+ if (typeof input === "string") {
1851
+ userInput = input;
1852
+ } else if (Array.isArray(input)) {
1853
+ userInput = JSON.stringify(input).slice(0, 500);
1854
+ }
1855
+ }
1856
+ return { userId, userInput };
1857
+ }
1858
+ function Tool(name) {
1859
+ return function(_target, propertyKey, descriptor) {
1860
+ const originalMethod = descriptor.value;
1861
+ const toolName = name ?? String(propertyKey);
1862
+ descriptor.value = async function(...args) {
1863
+ const startTime = Date.now();
1864
+ const toolInput = buildToolInput(args);
1865
+ const client = getClient3();
1866
+ const sessionId = getSessionContext();
1867
+ try {
1868
+ const result = await originalMethod.apply(this, args);
1869
+ const durationMs = Date.now() - startTime;
1870
+ if (client && sessionId) {
1871
+ const toolOutput = serializeOutput(result);
1872
+ client.trackToolCall({
1873
+ sessionId,
1874
+ toolName,
1875
+ toolInput,
1876
+ toolOutput,
1877
+ metadata: { duration_ms: durationMs }
1878
+ }).catch((err) => {
1879
+ console.warn(`Sentrial: Failed to track tool ${toolName}:`, err.message);
1880
+ });
1881
+ }
1882
+ return result;
1883
+ } catch (error) {
1884
+ const durationMs = Date.now() - startTime;
1885
+ if (client && sessionId) {
1886
+ client.trackError({
1887
+ sessionId,
1888
+ errorMessage: error.message,
1889
+ errorType: error.name,
1890
+ toolName,
1891
+ stackTrace: error.stack,
1892
+ metadata: { duration_ms: durationMs }
1893
+ }).catch(() => {
1894
+ });
1895
+ }
1896
+ throw error;
1897
+ }
1898
+ };
1899
+ return descriptor;
1900
+ };
1901
+ }
1902
+ function TrackSession(agentName, options) {
1903
+ return function(target, _propertyKey, descriptor) {
1904
+ const originalMethod = descriptor.value;
1905
+ const agent = agentName ?? target.constructor.name;
1906
+ descriptor.value = async function(...args) {
1907
+ const client = getClient3();
1908
+ if (!client) {
1909
+ return originalMethod.apply(this, args);
1910
+ }
1911
+ const { userId, userInput } = extractParams(args, options ?? {});
1912
+ const interaction = await client.begin({
1913
+ userId,
1914
+ event: agent,
1915
+ input: userInput
1916
+ });
1917
+ const sessionId = interaction.getSessionId();
1918
+ if (sessionId) {
1919
+ setSessionContext(sessionId, client);
1920
+ }
1921
+ _currentInteraction = interaction;
1922
+ try {
1923
+ const result = await originalMethod.apply(this, args);
1924
+ let output;
1925
+ if (typeof result === "string") {
1926
+ output = result;
1927
+ } else if (result && typeof result === "object") {
1928
+ if ("response" in result) {
1929
+ output = String(result.response);
1930
+ } else if ("output" in result) {
1931
+ output = String(result.output);
1932
+ }
1933
+ }
1934
+ if (output === void 0 && result !== null && result !== void 0) {
1935
+ output = String(result).slice(0, 1e3);
1936
+ }
1937
+ await interaction.finish({ output, success: true });
1938
+ return result;
1939
+ } catch (error) {
1940
+ await interaction.finish({
1941
+ success: false,
1942
+ failureReason: `${error.name}: ${error.message}`
1943
+ });
1944
+ throw error;
1945
+ } finally {
1946
+ clearSessionContext();
1947
+ _currentInteraction = null;
1948
+ }
1949
+ };
1950
+ return descriptor;
1951
+ };
1952
+ }
1953
+ var SessionContext = class {
1954
+ userId;
1955
+ agent;
1956
+ input;
1957
+ client;
1958
+ interaction = null;
1959
+ output;
1960
+ constructor(options) {
1961
+ this.userId = options.userId;
1962
+ this.agent = options.agent;
1963
+ this.input = options.input;
1964
+ this.client = options.client ?? getClient3();
1965
+ }
1966
+ /**
1967
+ * Start the session
1968
+ */
1969
+ async start() {
1970
+ if (!this.client) return this;
1971
+ this.interaction = await this.client.begin({
1972
+ userId: this.userId,
1973
+ event: this.agent,
1974
+ input: this.input
1975
+ });
1976
+ const sessionId = this.interaction.getSessionId();
1977
+ if (sessionId) {
1978
+ setSessionContext(sessionId, this.client);
1979
+ }
1980
+ _currentInteraction = this.interaction;
1981
+ return this;
1982
+ }
1983
+ /**
1984
+ * Set the output for this session
1985
+ */
1986
+ setOutput(output) {
1987
+ this.output = output;
1988
+ }
1989
+ /**
1990
+ * Finish the session
1991
+ */
1992
+ async finish(options) {
1993
+ if (this.interaction) {
1994
+ await this.interaction.finish({
1995
+ output: this.output,
1996
+ success: options?.success ?? true,
1997
+ failureReason: options?.error
1998
+ });
1999
+ }
2000
+ clearSessionContext();
2001
+ _currentInteraction = null;
2002
+ }
2003
+ /**
2004
+ * Get the session ID
2005
+ */
2006
+ get sessionId() {
2007
+ return this.interaction?.getSessionId() ?? null;
2008
+ }
2009
+ };
2010
+ function buildToolInput(args) {
2011
+ if (args.length === 0) {
2012
+ return {};
2013
+ }
2014
+ if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) {
2015
+ return serializeValue(args[0]);
2016
+ }
2017
+ return {
2018
+ args: args.map(serializeValue)
2019
+ };
2020
+ }
2021
+ function serializeValue(value) {
2022
+ if (value === null || value === void 0) {
2023
+ return value;
2024
+ }
2025
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
2026
+ return value;
2027
+ }
2028
+ if (Array.isArray(value)) {
2029
+ return value.map(serializeValue);
2030
+ }
2031
+ if (typeof value === "object") {
2032
+ const result = {};
2033
+ for (const [k, v] of Object.entries(value)) {
2034
+ result[k] = serializeValue(v);
2035
+ }
2036
+ return result;
2037
+ }
2038
+ try {
2039
+ return String(value).slice(0, 1e3);
2040
+ } catch {
2041
+ return `<${typeof value}>`;
2042
+ }
2043
+ }
2044
+ function serializeOutput(value) {
2045
+ if (value === null || value === void 0) {
2046
+ return { result: null };
2047
+ }
2048
+ if (typeof value === "object" && !Array.isArray(value)) {
2049
+ return serializeValue(value);
2050
+ }
2051
+ return { result: serializeValue(value) };
2052
+ }
2053
+
2054
+ // src/context.ts
2055
+ var _experimentContext = null;
2056
+ function getSystemPrompt(defaultPrompt) {
2057
+ if (_experimentContext?.systemPrompt) {
2058
+ return _experimentContext.systemPrompt;
2059
+ }
2060
+ return defaultPrompt ?? "";
2061
+ }
2062
+ function getExperimentContext() {
2063
+ return _experimentContext;
2064
+ }
2065
+ function isExperimentMode() {
2066
+ return _experimentContext !== null;
2067
+ }
2068
+ function getVariantName() {
2069
+ return _experimentContext?.variantName ?? null;
2070
+ }
2071
+ function getExperimentId() {
2072
+ return _experimentContext?.experimentId ?? null;
2073
+ }
2074
+ function setExperimentContext(context) {
2075
+ _experimentContext = context;
2076
+ }
2077
+ function clearExperimentContext() {
2078
+ _experimentContext = null;
2079
+ }
2080
+
2081
+ // src/experiment.ts
2082
+ var ExperimentRunTracker = class {
2083
+ experiment;
2084
+ variantName;
2085
+ baseSessionId;
2086
+ runId;
2087
+ resultSessionId;
2088
+ _success = true;
2089
+ _errorMessage;
2090
+ /** @internal */
2091
+ constructor(experiment, variantName, baseSessionId) {
2092
+ this.experiment = experiment;
2093
+ this.variantName = variantName;
2094
+ this.baseSessionId = baseSessionId;
2095
+ }
2096
+ /**
2097
+ * Start the run - creates a run record via API.
2098
+ */
2099
+ async start() {
2100
+ try {
2101
+ const response = await this.experiment.request(
2102
+ "POST",
2103
+ `/api/experiments/${this.experiment.experimentId}/runs`,
2104
+ {
2105
+ variantName: this.variantName,
2106
+ baseSessionId: this.baseSessionId
2107
+ }
2108
+ );
2109
+ this.runId = response?.run?.id;
2110
+ } catch {
2111
+ }
2112
+ return this;
2113
+ }
2114
+ /**
2115
+ * Set the session ID of the result session.
2116
+ */
2117
+ setResultSessionId(sessionId) {
2118
+ this.resultSessionId = sessionId;
2119
+ }
2120
+ /**
2121
+ * Mark the run as complete.
2122
+ */
2123
+ async complete() {
2124
+ if (!this.runId) return;
2125
+ try {
2126
+ await this.experiment.request(
2127
+ "PATCH",
2128
+ `/api/experiments/${this.experiment.experimentId}/runs/${this.runId}`,
2129
+ {
2130
+ status: "completed",
2131
+ resultSessionId: this.resultSessionId
2132
+ }
2133
+ );
2134
+ } catch {
2135
+ }
2136
+ }
2137
+ /**
2138
+ * Mark the run as failed.
2139
+ */
2140
+ async fail(errorMessage) {
2141
+ this._success = false;
2142
+ this._errorMessage = errorMessage;
2143
+ if (!this.runId) return;
2144
+ try {
2145
+ await this.experiment.request(
2146
+ "PATCH",
2147
+ `/api/experiments/${this.experiment.experimentId}/runs/${this.runId}`,
2148
+ {
2149
+ status: "failed",
2150
+ resultSessionId: this.resultSessionId,
2151
+ errorMessage
2152
+ }
2153
+ );
2154
+ } catch {
2155
+ }
2156
+ }
2157
+ /**
2158
+ * Get the result of this run.
2159
+ */
2160
+ getResult() {
2161
+ return {
2162
+ variantName: this.variantName,
2163
+ testCaseSessionId: this.baseSessionId,
2164
+ resultSessionId: this.resultSessionId,
2165
+ success: this._success,
2166
+ errorMessage: this._errorMessage
2167
+ };
2168
+ }
2169
+ };
2170
+ var Experiment = class {
2171
+ /** The experiment ID */
2172
+ experimentId;
2173
+ /** @internal */
2174
+ client;
2175
+ /** @internal */
2176
+ apiUrl;
2177
+ /** @internal */
2178
+ apiKey;
2179
+ config;
2180
+ _variants;
2181
+ _testCases;
2182
+ /**
2183
+ * Create an experiment instance.
2184
+ *
2185
+ * @param experimentId - The experiment ID from Sentrial dashboard
2186
+ * @param options - Configuration options
2187
+ */
2188
+ constructor(experimentId, options = {}) {
2189
+ this.experimentId = experimentId;
2190
+ this.apiUrl = (options.apiUrl ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_URL : void 0) ?? "https://api.sentrial.com").replace(/\/$/, "");
2191
+ this.apiKey = options.apiKey ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_KEY : void 0);
2192
+ this.client = new SentrialClient({
2193
+ apiKey: this.apiKey,
2194
+ apiUrl: this.apiUrl,
2195
+ failSilently: false
2196
+ // We want errors for experiments
2197
+ });
2198
+ }
2199
+ /**
2200
+ * Make an HTTP request to the API
2201
+ * @internal
2202
+ */
2203
+ async request(method, path, body) {
2204
+ const headers = {
2205
+ "Content-Type": "application/json"
2206
+ };
2207
+ if (this.apiKey) {
2208
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
2209
+ }
2210
+ const response = await fetch(`${this.apiUrl}${path}`, {
2211
+ method,
2212
+ headers,
2213
+ body: body ? JSON.stringify(body) : void 0
2214
+ });
2215
+ if (!response.ok) {
2216
+ return null;
2217
+ }
2218
+ return response.json();
2219
+ }
2220
+ /**
2221
+ * Load the experiment configuration from the API.
2222
+ */
2223
+ async load() {
2224
+ const response = await this.request(
2225
+ "GET",
2226
+ `/api/experiments/${this.experimentId}/runs`
2227
+ );
2228
+ if (!response?.experiment) {
2229
+ throw new Error(`Failed to load experiment config for ${this.experimentId}`);
2230
+ }
2231
+ this.config = response.experiment;
2232
+ const variants = this.config.variants;
2233
+ this._variants = variants?.map((v) => ({
2234
+ name: v.name ?? "",
2235
+ systemPrompt: v.systemPrompt ?? "",
2236
+ description: v.description
2237
+ })) ?? [];
2238
+ const testCases = this.config.testCases;
2239
+ this._testCases = testCases?.filter((tc) => tc.userInput)?.map((tc) => ({
2240
+ sessionId: tc.sessionId ?? "",
2241
+ userInput: tc.userInput ?? ""
2242
+ })) ?? [];
2243
+ return this;
2244
+ }
2245
+ /**
2246
+ * Get experiment name.
2247
+ */
2248
+ get name() {
2249
+ return this.config?.name ?? "";
2250
+ }
2251
+ /**
2252
+ * Get experiment status.
2253
+ */
2254
+ get status() {
2255
+ return this.config?.status ?? "";
2256
+ }
2257
+ /**
2258
+ * Get list of variants to test.
2259
+ */
2260
+ get variants() {
2261
+ if (!this._variants) {
2262
+ throw new Error("Experiment not loaded. Call load() first.");
2263
+ }
2264
+ return this._variants;
2265
+ }
2266
+ /**
2267
+ * Get list of test cases.
2268
+ */
2269
+ get testCases() {
2270
+ if (!this._testCases) {
2271
+ throw new Error("Experiment not loaded. Call load() first.");
2272
+ }
2273
+ return this._testCases;
2274
+ }
2275
+ /**
2276
+ * Get a specific variant by name.
2277
+ */
2278
+ getVariant(name) {
2279
+ return this.variants.find((v) => v.name === name);
2280
+ }
2281
+ /**
2282
+ * Create a run tracker for manual experiment runs.
2283
+ */
2284
+ async trackRun(variantName, baseSessionId) {
2285
+ const tracker = new ExperimentRunTracker(this, variantName, baseSessionId);
2286
+ await tracker.start();
2287
+ return tracker;
2288
+ }
2289
+ /**
2290
+ * Run the experiment with all variants and test cases.
2291
+ *
2292
+ * @param agentFn - Function that runs your agent with a test case and variant
2293
+ * @param options - Run options
2294
+ * @returns List of run results
2295
+ */
2296
+ async run(agentFn, options = {}) {
2297
+ const { parallel = false, maxWorkers = 4 } = options;
2298
+ if (!this._variants || !this._testCases) {
2299
+ await this.load();
2300
+ }
2301
+ const results = [];
2302
+ const totalRuns = this.variants.length * this.testCases.length;
2303
+ let completed = 0;
2304
+ console.log(`Running experiment: ${this.name || this.experimentId}`);
2305
+ console.log(` Variants: ${this.variants.length}`);
2306
+ console.log(` Test cases: ${this.testCases.length}`);
2307
+ console.log(` Total runs: ${totalRuns}`);
2308
+ console.log();
2309
+ const runSingle = async (variant, testCase) => {
2310
+ const tracker = await this.trackRun(variant.name, testCase.sessionId);
2311
+ setExperimentContext({
2312
+ systemPrompt: variant.systemPrompt,
2313
+ variantName: variant.name,
2314
+ experimentId: this.experimentId,
2315
+ testCaseId: testCase.sessionId
2316
+ });
2317
+ try {
2318
+ await agentFn(testCase, variant, tracker);
2319
+ await tracker.complete();
2320
+ return tracker.getResult();
2321
+ } catch (error) {
2322
+ const errorMessage = error instanceof Error ? error.message : String(error);
2323
+ await tracker.fail(errorMessage);
2324
+ return tracker.getResult();
2325
+ } finally {
2326
+ clearExperimentContext();
2327
+ }
2328
+ };
2329
+ if (parallel) {
2330
+ const queue = [];
2331
+ for (const variant of this.variants) {
2332
+ for (const testCase of this.testCases) {
2333
+ queue.push(async () => {
2334
+ const result = await runSingle(variant, testCase);
2335
+ results.push(result);
2336
+ completed++;
2337
+ const status = result.success ? "\u2713" : "\u2717";
2338
+ console.log(
2339
+ ` [${completed}/${totalRuns}] ${status} ${variant.name} x ${testCase.sessionId.slice(0, 8)}...`
2340
+ );
2341
+ });
2342
+ }
2343
+ }
2344
+ const executing = [];
2345
+ for (const task of queue) {
2346
+ const promise = task().then(() => {
2347
+ executing.splice(executing.indexOf(promise), 1);
2348
+ });
2349
+ executing.push(promise);
2350
+ if (executing.length >= maxWorkers) {
2351
+ await Promise.race(executing);
2352
+ }
2353
+ }
2354
+ await Promise.all(executing);
2355
+ } else {
2356
+ for (const variant of this.variants) {
2357
+ for (const testCase of this.testCases) {
2358
+ const result = await runSingle(variant, testCase);
2359
+ results.push(result);
2360
+ completed++;
2361
+ const status = result.success ? "\u2713" : "\u2717";
2362
+ console.log(
2363
+ ` [${completed}/${totalRuns}] ${status} ${variant.name} x ${testCase.sessionId.slice(0, 8)}...`
2364
+ );
2365
+ }
2366
+ }
2367
+ }
2368
+ console.log();
2369
+ console.log("Experiment complete!");
2370
+ console.log(` Successful: ${results.filter((r) => r.success).length}/${totalRuns}`);
2371
+ console.log(` Failed: ${results.filter((r) => !r.success).length}/${totalRuns}`);
2372
+ return results;
2373
+ }
2374
+ /**
2375
+ * Reset all runs for this experiment (for re-running).
2376
+ */
2377
+ async reset() {
2378
+ try {
2379
+ const response = await this.request(
2380
+ "DELETE",
2381
+ `/api/experiments/${this.experimentId}/runs`
2382
+ );
2383
+ if (response !== null) {
2384
+ this.config = void 0;
2385
+ this._variants = void 0;
2386
+ this._testCases = void 0;
2387
+ return true;
2388
+ }
2389
+ return false;
2390
+ } catch {
2391
+ return false;
2392
+ }
2393
+ }
2394
+ /**
2395
+ * Fetch aggregated results from the API.
2396
+ */
2397
+ async getResults() {
2398
+ try {
2399
+ const response = await this.request(
2400
+ "GET",
2401
+ `/api/experiments/${this.experimentId}/results`
2402
+ );
2403
+ return response;
2404
+ } catch {
2405
+ return null;
2406
+ }
2407
+ }
2408
+ };
579
2409
  // Annotate the CommonJS export names for ESM import in node:
580
2410
  0 && (module.exports = {
581
2411
  ApiError,
582
2412
  EventType,
2413
+ Experiment,
2414
+ ExperimentRunTracker,
583
2415
  Interaction,
584
2416
  NetworkError,
585
2417
  SentrialClient,
586
2418
  SentrialError,
2419
+ SessionContext,
2420
+ Tool,
2421
+ TrackSession,
587
2422
  ValidationError,
588
2423
  begin,
589
2424
  calculateAnthropicCost,
590
2425
  calculateGoogleCost,
591
2426
  calculateOpenAICost,
2427
+ clearExperimentContext,
2428
+ clearSessionContext,
592
2429
  configure,
593
- sentrial
2430
+ configureVercel,
2431
+ getCurrentInteraction,
2432
+ getCurrentSessionId,
2433
+ getExperimentContext,
2434
+ getExperimentId,
2435
+ getSessionContext,
2436
+ getSystemPrompt,
2437
+ getVariantName,
2438
+ hashValue,
2439
+ isExperimentMode,
2440
+ redactPayload,
2441
+ redactString,
2442
+ redactValue,
2443
+ replaceMatch,
2444
+ sentrial,
2445
+ setClient,
2446
+ setDefaultClient,
2447
+ setExperimentContext,
2448
+ setSessionContext,
2449
+ withSession,
2450
+ withTool,
2451
+ wrapAISDK,
2452
+ wrapAnthropic,
2453
+ wrapGoogle,
2454
+ wrapLLM,
2455
+ wrapOpenAI
594
2456
  });
595
2457
  //# sourceMappingURL=index.cjs.map