@sentrial/sdk 0.1.0 → 0.2.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,44 @@ 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
+ isExperimentMode: () => isExperimentMode,
51
+ sentrial: () => sentrial,
52
+ setClient: () => setClient,
53
+ setDefaultClient: () => setDefaultClient,
54
+ setExperimentContext: () => setExperimentContext,
55
+ setSessionContext: () => setSessionContext,
56
+ withSession: () => withSession,
57
+ withTool: () => withTool,
58
+ wrapAISDK: () => wrapAISDK,
59
+ wrapAnthropic: () => wrapAnthropic,
60
+ wrapGoogle: () => wrapGoogle,
61
+ wrapLLM: () => wrapLLM,
62
+ wrapOpenAI: () => wrapOpenAI
36
63
  });
37
64
  module.exports = __toCommonJS(index_exports);
38
65
 
@@ -185,6 +212,12 @@ var EventType = /* @__PURE__ */ ((EventType2) => {
185
212
 
186
213
  // src/client.ts
187
214
  var DEFAULT_API_URL = "https://api.sentrial.com";
215
+ var MAX_RETRIES = 3;
216
+ var INITIAL_BACKOFF_MS = 500;
217
+ var MAX_BACKOFF_MS = 8e3;
218
+ var BACKOFF_MULTIPLIER = 2;
219
+ var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
220
+ var REQUEST_TIMEOUT_MS = 1e4;
188
221
  var SentrialClient = class {
189
222
  apiUrl;
190
223
  apiKey;
@@ -196,54 +229,83 @@ var SentrialClient = class {
196
229
  this.failSilently = config.failSilently ?? true;
197
230
  }
198
231
  /**
199
- * Make an HTTP request with graceful error handling
232
+ * Make an HTTP request with retry logic and exponential backoff.
233
+ *
234
+ * Retries on transient failures (network errors, timeouts, 429/5xx).
235
+ * Up to MAX_RETRIES attempts with exponential backoff.
200
236
  */
201
237
  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 = {};
238
+ let lastError;
239
+ let backoff = INITIAL_BACKOFF_MS;
240
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
241
+ try {
242
+ const headers = {
243
+ "Content-Type": "application/json"
244
+ };
245
+ if (this.apiKey) {
246
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
247
+ }
248
+ const controller = new AbortController();
249
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
250
+ let response;
217
251
  try {
218
- errorData = JSON.parse(errorBody);
219
- } catch {
252
+ response = await fetch(url, {
253
+ method,
254
+ headers,
255
+ body: body ? JSON.stringify(body) : void 0,
256
+ signal: controller.signal
257
+ });
258
+ } finally {
259
+ clearTimeout(timeoutId);
220
260
  }
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;
261
+ if (RETRYABLE_STATUS_CODES.has(response.status) && attempt < MAX_RETRIES) {
262
+ await this.sleep(backoff);
263
+ backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
264
+ continue;
265
+ }
266
+ if (!response.ok) {
267
+ const errorBody = await response.text();
268
+ let errorData = {};
269
+ try {
270
+ errorData = JSON.parse(errorBody);
271
+ } catch {
272
+ }
273
+ const error = new ApiError(
274
+ errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`,
275
+ response.status,
276
+ errorData.error?.code
277
+ );
278
+ if (this.failSilently) {
279
+ console.warn(`Sentrial: Request failed (${method} ${url}):`, error.message);
280
+ return null;
281
+ }
282
+ throw error;
283
+ }
284
+ return await response.json();
285
+ } catch (error) {
286
+ if (error instanceof ApiError) {
287
+ throw error;
288
+ }
289
+ lastError = error instanceof Error ? error : new Error(String(error));
290
+ if (attempt < MAX_RETRIES) {
291
+ await this.sleep(backoff);
292
+ backoff = Math.min(backoff * BACKOFF_MULTIPLIER, MAX_BACKOFF_MS);
293
+ continue;
229
294
  }
230
- throw error;
231
- }
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;
244
295
  }
245
- throw networkError;
246
296
  }
297
+ const networkError = new NetworkError(
298
+ lastError?.message ?? "Unknown network error",
299
+ lastError
300
+ );
301
+ if (this.failSilently) {
302
+ console.warn(`Sentrial: Request failed after ${MAX_RETRIES + 1} attempts (${method} ${url}):`, networkError.message);
303
+ return null;
304
+ }
305
+ throw networkError;
306
+ }
307
+ sleep(ms) {
308
+ return new Promise((resolve) => setTimeout(resolve, ms));
247
309
  }
248
310
  /**
249
311
  * Create a new session
@@ -259,6 +321,9 @@ var SentrialClient = class {
259
321
  userId: params.userId,
260
322
  metadata: params.metadata
261
323
  };
324
+ if (params.parentSessionId) {
325
+ payload.parentSessionId = params.parentSessionId;
326
+ }
262
327
  const response = await this.safeRequest(
263
328
  "POST",
264
329
  `${this.apiUrl}/api/sdk/sessions`,
@@ -352,6 +417,42 @@ var SentrialClient = class {
352
417
  updateState(key, value) {
353
418
  this.currentState[key] = value;
354
419
  }
420
+ /**
421
+ * Set the user input for a session
422
+ *
423
+ * @param sessionId - Session ID
424
+ * @param input - User input text
425
+ * @returns Updated session or null on error
426
+ */
427
+ async setInput(sessionId, input) {
428
+ return this.safeRequest(
429
+ "PATCH",
430
+ `${this.apiUrl}/api/sdk/sessions/${sessionId}`,
431
+ { userInput: input }
432
+ );
433
+ }
434
+ /**
435
+ * Track a generic event
436
+ *
437
+ * @param params - Event parameters
438
+ * @returns Event data or null on error
439
+ */
440
+ async trackEvent(params) {
441
+ const stateBefore = { ...this.currentState };
442
+ const payload = {
443
+ sessionId: params.sessionId,
444
+ eventType: params.eventType,
445
+ stateBefore,
446
+ stateAfter: { ...this.currentState }
447
+ };
448
+ if (params.eventData) {
449
+ Object.assign(payload, params.eventData);
450
+ }
451
+ if (params.metadata) {
452
+ payload.metadata = params.metadata;
453
+ }
454
+ return this.safeRequest("POST", `${this.apiUrl}/api/sdk/events`, payload);
455
+ }
355
456
  /**
356
457
  * Complete a session with performance metrics
357
458
  *
@@ -391,7 +492,8 @@ var SentrialClient = class {
391
492
  if (params.completionTokens !== void 0) payload.completionTokens = params.completionTokens;
392
493
  if (params.totalTokens !== void 0) payload.totalTokens = params.totalTokens;
393
494
  if (params.userInput !== void 0) payload.userInput = params.userInput;
394
- if (params.assistantOutput !== void 0) payload.assistantOutput = params.assistantOutput;
495
+ const output = params.assistantOutput ?? params.output;
496
+ if (output !== void 0) payload.assistantOutput = output;
395
497
  return this.safeRequest(
396
498
  "PATCH",
397
499
  `${this.apiUrl}/api/sdk/sessions/${params.sessionId}`,
@@ -438,7 +540,8 @@ var SentrialClient = class {
438
540
  sessionId,
439
541
  eventId,
440
542
  userId: params.userId,
441
- event: params.event
543
+ event: params.event,
544
+ userInput: params.input
442
545
  });
443
546
  }
444
547
  // Cost calculation static methods for convenience
@@ -459,6 +562,7 @@ var Interaction = class {
459
562
  success = true;
460
563
  failureReason;
461
564
  output;
565
+ userInput;
462
566
  degraded;
463
567
  constructor(config) {
464
568
  this.client = config.client;
@@ -466,6 +570,7 @@ var Interaction = class {
466
570
  this.eventId = config.eventId;
467
571
  this.userId = config.userId;
468
572
  this.event = config.event;
573
+ this.userInput = config.userInput;
469
574
  this.degraded = config.sessionId === null;
470
575
  }
471
576
  /**
@@ -511,6 +616,7 @@ var Interaction = class {
511
616
  promptTokens: params.promptTokens,
512
617
  completionTokens: params.completionTokens,
513
618
  totalTokens: params.totalTokens,
619
+ userInput: this.userInput,
514
620
  assistantOutput: finalOutput
515
621
  });
516
622
  }
@@ -576,20 +682,1474 @@ var sentrial = {
576
682
  configure,
577
683
  begin
578
684
  };
685
+
686
+ // src/vercel.ts
687
+ var _defaultClient = null;
688
+ var _globalConfig = {};
689
+ function configureVercel(config) {
690
+ _defaultClient = new SentrialClient({
691
+ apiKey: config.apiKey,
692
+ apiUrl: config.apiUrl,
693
+ failSilently: config.failSilently ?? true
694
+ });
695
+ _globalConfig = {
696
+ defaultAgent: config.defaultAgent,
697
+ userId: config.userId
698
+ };
699
+ }
700
+ function getClient2() {
701
+ if (!_defaultClient) {
702
+ _defaultClient = new SentrialClient();
703
+ }
704
+ return _defaultClient;
705
+ }
706
+ function extractModelInfo(model) {
707
+ const modelId = model.modelId || model.id || "unknown";
708
+ const provider = model.provider || guessProvider(modelId);
709
+ return { modelId, provider };
710
+ }
711
+ function guessProvider(modelId) {
712
+ if (modelId.includes("gpt") || modelId.includes("o1") || modelId.includes("o3")) return "openai";
713
+ if (modelId.includes("claude")) return "anthropic";
714
+ if (modelId.includes("gemini")) return "google";
715
+ if (modelId.includes("mistral")) return "mistral";
716
+ if (modelId.includes("llama")) return "meta";
717
+ return "unknown";
718
+ }
719
+ function calculateCostForCall(provider, modelId, promptTokens, completionTokens) {
720
+ const params = { model: modelId, inputTokens: promptTokens, outputTokens: completionTokens };
721
+ switch (provider.toLowerCase()) {
722
+ case "openai":
723
+ return calculateOpenAICost(params);
724
+ case "anthropic":
725
+ return calculateAnthropicCost(params);
726
+ case "google":
727
+ return calculateGoogleCost(params);
728
+ default:
729
+ return promptTokens * 3e-6 + completionTokens * 6e-6;
730
+ }
731
+ }
732
+ function extractInput(params) {
733
+ if (params.prompt) return params.prompt;
734
+ if (params.messages && params.messages.length > 0) {
735
+ const lastUserMessage = [...params.messages].reverse().find((m) => m.role === "user");
736
+ return lastUserMessage?.content || JSON.stringify(params.messages);
737
+ }
738
+ return "";
739
+ }
740
+ function wrapTools(tools, sessionId, client) {
741
+ if (!tools) return void 0;
742
+ const wrappedTools = {};
743
+ for (const [toolName, tool] of Object.entries(tools)) {
744
+ if (typeof tool.execute === "function") {
745
+ const originalExecute = tool.execute;
746
+ wrappedTools[toolName] = {
747
+ ...tool,
748
+ execute: async (...args) => {
749
+ const startTime = Date.now();
750
+ try {
751
+ const result = await originalExecute(...args);
752
+ const durationMs = Date.now() - startTime;
753
+ await client.trackToolCall({
754
+ sessionId,
755
+ toolName,
756
+ toolInput: args[0],
757
+ toolOutput: result,
758
+ reasoning: `Tool executed in ${durationMs}ms`
759
+ });
760
+ return result;
761
+ } catch (error) {
762
+ const durationMs = Date.now() - startTime;
763
+ await client.trackToolCall({
764
+ sessionId,
765
+ toolName,
766
+ toolInput: args[0],
767
+ toolOutput: {},
768
+ toolError: { message: error instanceof Error ? error.message : "Unknown error" },
769
+ reasoning: `Tool failed after ${durationMs}ms`
770
+ });
771
+ throw error;
772
+ }
773
+ }
774
+ };
775
+ } else {
776
+ wrappedTools[toolName] = tool;
777
+ }
778
+ }
779
+ return wrappedTools;
780
+ }
781
+ function wrapGenerateText(originalFn, client) {
782
+ return async (params) => {
783
+ const startTime = Date.now();
784
+ const { modelId, provider } = extractModelInfo(params.model);
785
+ const input = extractInput(params);
786
+ const sessionId = await client.createSession({
787
+ name: `generateText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
788
+ agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
789
+ userId: _globalConfig.userId ?? "anonymous",
790
+ metadata: {
791
+ model: modelId,
792
+ provider,
793
+ function: "generateText"
794
+ }
795
+ });
796
+ if (!sessionId) {
797
+ return originalFn(params);
798
+ }
799
+ await client.setInput(sessionId, input);
800
+ const wrappedParams = {
801
+ ...params,
802
+ tools: wrapTools(params.tools, sessionId, client)
803
+ };
804
+ try {
805
+ const result = await originalFn(wrappedParams);
806
+ const durationMs = Date.now() - startTime;
807
+ const promptTokens = result.usage?.promptTokens || 0;
808
+ const completionTokens = result.usage?.completionTokens || 0;
809
+ const totalTokens = result.usage?.totalTokens || promptTokens + completionTokens;
810
+ const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
811
+ await client.trackEvent({
812
+ sessionId,
813
+ eventType: "llm_call",
814
+ eventData: {
815
+ model: modelId,
816
+ provider,
817
+ prompt_tokens: promptTokens,
818
+ completion_tokens: completionTokens,
819
+ total_tokens: totalTokens,
820
+ finish_reason: result.finishReason,
821
+ tool_calls: result.toolCalls?.map((tc) => tc.toolName)
822
+ }
823
+ });
824
+ await client.completeSession({
825
+ sessionId,
826
+ success: true,
827
+ output: result.text,
828
+ durationMs,
829
+ estimatedCost: cost,
830
+ promptTokens,
831
+ completionTokens,
832
+ totalTokens
833
+ });
834
+ return result;
835
+ } catch (error) {
836
+ const durationMs = Date.now() - startTime;
837
+ await client.trackError({
838
+ sessionId,
839
+ errorType: error instanceof Error ? error.name : "Error",
840
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
841
+ });
842
+ await client.completeSession({
843
+ sessionId,
844
+ success: false,
845
+ failureReason: error instanceof Error ? error.message : "Unknown error",
846
+ durationMs
847
+ });
848
+ throw error;
849
+ }
850
+ };
851
+ }
852
+ function wrapStreamText(originalFn, client) {
853
+ return (params) => {
854
+ const startTime = Date.now();
855
+ const { modelId, provider } = extractModelInfo(params.model);
856
+ const input = extractInput(params);
857
+ let sessionId = null;
858
+ const sessionPromise = client.createSession({
859
+ name: `streamText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
860
+ agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
861
+ userId: _globalConfig.userId ?? "anonymous",
862
+ metadata: {
863
+ model: modelId,
864
+ provider,
865
+ function: "streamText"
866
+ }
867
+ }).then((id) => {
868
+ sessionId = id;
869
+ if (id) client.setInput(id, input);
870
+ return id;
871
+ });
872
+ const wrappedParams = {
873
+ ...params,
874
+ tools: params.tools ? wrapToolsAsync(params.tools, sessionPromise, client) : void 0
875
+ };
876
+ const result = originalFn(wrappedParams);
877
+ const originalTextStream = result.textStream;
878
+ let fullText = "";
879
+ result.textStream = (async function* () {
880
+ try {
881
+ for await (const chunk of originalTextStream) {
882
+ fullText += chunk;
883
+ yield chunk;
884
+ }
885
+ const durationMs = Date.now() - startTime;
886
+ const sid = sessionId || await sessionPromise;
887
+ if (sid) {
888
+ const usage = result.usage ? await result.usage : void 0;
889
+ const promptTokens = usage?.promptTokens || 0;
890
+ const completionTokens = usage?.completionTokens || 0;
891
+ const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
892
+ const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
893
+ await client.completeSession({
894
+ sessionId: sid,
895
+ success: true,
896
+ output: fullText,
897
+ durationMs,
898
+ estimatedCost: cost,
899
+ promptTokens,
900
+ completionTokens,
901
+ totalTokens
902
+ });
903
+ }
904
+ } catch (error) {
905
+ const durationMs = Date.now() - startTime;
906
+ const sid = sessionId || await sessionPromise;
907
+ if (sid) {
908
+ await client.trackError({
909
+ sessionId: sid,
910
+ errorType: error instanceof Error ? error.name : "Error",
911
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
912
+ });
913
+ await client.completeSession({
914
+ sessionId: sid,
915
+ success: false,
916
+ failureReason: error instanceof Error ? error.message : "Unknown error",
917
+ durationMs
918
+ });
919
+ }
920
+ throw error;
921
+ }
922
+ })();
923
+ return result;
924
+ };
925
+ }
926
+ function wrapToolsAsync(tools, sessionPromise, client) {
927
+ const wrappedTools = {};
928
+ for (const [toolName, tool] of Object.entries(tools)) {
929
+ if (typeof tool.execute === "function") {
930
+ const originalExecute = tool.execute;
931
+ wrappedTools[toolName] = {
932
+ ...tool,
933
+ execute: async (...args) => {
934
+ const startTime = Date.now();
935
+ const sessionId = await sessionPromise;
936
+ try {
937
+ const result = await originalExecute(...args);
938
+ const durationMs = Date.now() - startTime;
939
+ if (sessionId) {
940
+ await client.trackToolCall({
941
+ sessionId,
942
+ toolName,
943
+ toolInput: args[0],
944
+ toolOutput: result,
945
+ reasoning: `Tool executed in ${durationMs}ms`
946
+ });
947
+ }
948
+ return result;
949
+ } catch (error) {
950
+ const durationMs = Date.now() - startTime;
951
+ if (sessionId) {
952
+ await client.trackToolCall({
953
+ sessionId,
954
+ toolName,
955
+ toolInput: args[0],
956
+ toolOutput: {},
957
+ toolError: { message: error instanceof Error ? error.message : "Unknown error" },
958
+ reasoning: `Tool failed after ${durationMs}ms`
959
+ });
960
+ }
961
+ throw error;
962
+ }
963
+ }
964
+ };
965
+ } else {
966
+ wrappedTools[toolName] = tool;
967
+ }
968
+ }
969
+ return wrappedTools;
970
+ }
971
+ function wrapGenerateObject(originalFn, client) {
972
+ return async (params) => {
973
+ const startTime = Date.now();
974
+ const { modelId, provider } = extractModelInfo(params.model);
975
+ const input = extractInput(params);
976
+ const sessionId = await client.createSession({
977
+ name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
978
+ agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
979
+ userId: _globalConfig.userId ?? "anonymous",
980
+ metadata: {
981
+ model: modelId,
982
+ provider,
983
+ function: "generateObject"
984
+ }
985
+ });
986
+ if (!sessionId) {
987
+ return originalFn(params);
988
+ }
989
+ await client.setInput(sessionId, input);
990
+ try {
991
+ const result = await originalFn(params);
992
+ const durationMs = Date.now() - startTime;
993
+ const promptTokens = result.usage?.promptTokens || 0;
994
+ const completionTokens = result.usage?.completionTokens || 0;
995
+ const totalTokens = result.usage?.totalTokens || promptTokens + completionTokens;
996
+ const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
997
+ await client.completeSession({
998
+ sessionId,
999
+ success: true,
1000
+ output: JSON.stringify(result.object),
1001
+ durationMs,
1002
+ estimatedCost: cost,
1003
+ promptTokens,
1004
+ completionTokens,
1005
+ totalTokens
1006
+ });
1007
+ return result;
1008
+ } catch (error) {
1009
+ const durationMs = Date.now() - startTime;
1010
+ await client.trackError({
1011
+ sessionId,
1012
+ errorType: error instanceof Error ? error.name : "Error",
1013
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
1014
+ });
1015
+ await client.completeSession({
1016
+ sessionId,
1017
+ success: false,
1018
+ failureReason: error instanceof Error ? error.message : "Unknown error",
1019
+ durationMs
1020
+ });
1021
+ throw error;
1022
+ }
1023
+ };
1024
+ }
1025
+ function wrapStreamObject(originalFn, client) {
1026
+ return (params) => {
1027
+ const startTime = Date.now();
1028
+ const { modelId, provider } = extractModelInfo(params.model);
1029
+ const input = extractInput(params);
1030
+ const sessionPromise = client.createSession({
1031
+ name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1032
+ agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
1033
+ userId: _globalConfig.userId ?? "anonymous",
1034
+ metadata: {
1035
+ model: modelId,
1036
+ provider,
1037
+ function: "streamObject"
1038
+ }
1039
+ }).then((id) => {
1040
+ if (id) client.setInput(id, input);
1041
+ return id;
1042
+ });
1043
+ const result = originalFn(params);
1044
+ if (result.object) {
1045
+ const originalObjectPromise = result.object;
1046
+ result.object = originalObjectPromise.then(async (obj) => {
1047
+ const durationMs = Date.now() - startTime;
1048
+ const sessionId = await sessionPromise;
1049
+ if (sessionId) {
1050
+ const usage = result.usage ? await result.usage : void 0;
1051
+ const promptTokens = usage?.promptTokens || 0;
1052
+ const completionTokens = usage?.completionTokens || 0;
1053
+ const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
1054
+ const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
1055
+ await client.completeSession({
1056
+ sessionId,
1057
+ success: true,
1058
+ output: JSON.stringify(obj),
1059
+ durationMs,
1060
+ estimatedCost: cost,
1061
+ promptTokens,
1062
+ completionTokens,
1063
+ totalTokens
1064
+ });
1065
+ }
1066
+ return obj;
1067
+ }).catch(async (error) => {
1068
+ const durationMs = Date.now() - startTime;
1069
+ const sessionId = await sessionPromise;
1070
+ if (sessionId) {
1071
+ await client.trackError({
1072
+ sessionId,
1073
+ errorType: error instanceof Error ? error.name : "Error",
1074
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
1075
+ });
1076
+ await client.completeSession({
1077
+ sessionId,
1078
+ success: false,
1079
+ failureReason: error instanceof Error ? error.message : "Unknown error",
1080
+ durationMs
1081
+ });
1082
+ }
1083
+ throw error;
1084
+ });
1085
+ }
1086
+ return result;
1087
+ };
1088
+ }
1089
+ function wrapAISDK(ai) {
1090
+ const client = getClient2();
1091
+ return {
1092
+ generateText: ai.generateText ? wrapGenerateText(ai.generateText, client) : wrapGenerateText(() => Promise.reject(new Error("generateText not available")), client),
1093
+ streamText: ai.streamText ? wrapStreamText(ai.streamText, client) : wrapStreamText(() => ({ textStream: (async function* () {
1094
+ })() }), client),
1095
+ generateObject: ai.generateObject ? wrapGenerateObject(ai.generateObject, client) : wrapGenerateObject(() => Promise.reject(new Error("generateObject not available")), client),
1096
+ streamObject: ai.streamObject ? wrapStreamObject(ai.streamObject, client) : wrapStreamObject(() => ({}), client)
1097
+ };
1098
+ }
1099
+
1100
+ // src/wrappers.ts
1101
+ var _currentSessionId = null;
1102
+ var _currentClient = null;
1103
+ var _defaultClient2 = null;
1104
+ function setSessionContext(sessionId, client) {
1105
+ _currentSessionId = sessionId;
1106
+ if (client) {
1107
+ _currentClient = client;
1108
+ }
1109
+ }
1110
+ function clearSessionContext() {
1111
+ _currentSessionId = null;
1112
+ _currentClient = null;
1113
+ }
1114
+ function getSessionContext() {
1115
+ return _currentSessionId;
1116
+ }
1117
+ function setDefaultClient(client) {
1118
+ _defaultClient2 = client;
1119
+ }
1120
+ function getTrackingClient() {
1121
+ return _currentClient ?? _defaultClient2;
1122
+ }
1123
+ function wrapOpenAI(client, options = {}) {
1124
+ const { trackWithoutSession = false } = options;
1125
+ const chat = client.chat;
1126
+ if (!chat?.completions?.create) {
1127
+ console.warn("Sentrial: OpenAI client does not have chat.completions.create");
1128
+ return client;
1129
+ }
1130
+ const originalCreate = chat.completions.create.bind(chat.completions);
1131
+ chat.completions.create = async function(...args) {
1132
+ const startTime = Date.now();
1133
+ const params = args[0] ?? {};
1134
+ const messages = params.messages ?? [];
1135
+ const model = params.model ?? "unknown";
1136
+ try {
1137
+ const response = await originalCreate(...args);
1138
+ const durationMs = Date.now() - startTime;
1139
+ const promptTokens = response.usage?.prompt_tokens ?? 0;
1140
+ const completionTokens = response.usage?.completion_tokens ?? 0;
1141
+ const totalTokens = response.usage?.total_tokens ?? 0;
1142
+ let outputContent = "";
1143
+ if (response.choices?.[0]?.message?.content) {
1144
+ outputContent = response.choices[0].message.content;
1145
+ }
1146
+ const cost = calculateOpenAICost({ model, inputTokens: promptTokens, outputTokens: completionTokens });
1147
+ trackLLMCall({
1148
+ provider: "openai",
1149
+ model,
1150
+ messages,
1151
+ output: outputContent,
1152
+ promptTokens,
1153
+ completionTokens,
1154
+ totalTokens,
1155
+ cost,
1156
+ durationMs,
1157
+ trackWithoutSession
1158
+ });
1159
+ return response;
1160
+ } catch (error) {
1161
+ const durationMs = Date.now() - startTime;
1162
+ trackLLMError({
1163
+ provider: "openai",
1164
+ model,
1165
+ messages,
1166
+ error,
1167
+ durationMs,
1168
+ trackWithoutSession
1169
+ });
1170
+ throw error;
1171
+ }
1172
+ };
1173
+ return client;
1174
+ }
1175
+ function wrapAnthropic(client, options = {}) {
1176
+ const { trackWithoutSession = false } = options;
1177
+ const messages = client.messages;
1178
+ if (!messages?.create) {
1179
+ console.warn("Sentrial: Anthropic client does not have messages.create");
1180
+ return client;
1181
+ }
1182
+ const originalCreate = messages.create.bind(messages);
1183
+ messages.create = async function(...args) {
1184
+ const startTime = Date.now();
1185
+ const params = args[0] ?? {};
1186
+ const inputMessages = params.messages ?? [];
1187
+ const model = params.model ?? "unknown";
1188
+ const system = params.system ?? "";
1189
+ try {
1190
+ const response = await originalCreate(...args);
1191
+ const durationMs = Date.now() - startTime;
1192
+ const promptTokens = response.usage?.input_tokens ?? 0;
1193
+ const completionTokens = response.usage?.output_tokens ?? 0;
1194
+ const totalTokens = promptTokens + completionTokens;
1195
+ let outputContent = "";
1196
+ if (response.content) {
1197
+ for (const block of response.content) {
1198
+ if (block.type === "text") {
1199
+ outputContent += block.text;
1200
+ }
1201
+ }
1202
+ }
1203
+ const cost = calculateAnthropicCost({ model, inputTokens: promptTokens, outputTokens: completionTokens });
1204
+ const fullMessages = system ? [{ role: "system", content: system }, ...inputMessages] : inputMessages;
1205
+ trackLLMCall({
1206
+ provider: "anthropic",
1207
+ model,
1208
+ messages: fullMessages,
1209
+ output: outputContent,
1210
+ promptTokens,
1211
+ completionTokens,
1212
+ totalTokens,
1213
+ cost,
1214
+ durationMs,
1215
+ trackWithoutSession
1216
+ });
1217
+ return response;
1218
+ } catch (error) {
1219
+ const durationMs = Date.now() - startTime;
1220
+ trackLLMError({
1221
+ provider: "anthropic",
1222
+ model,
1223
+ messages: inputMessages,
1224
+ error,
1225
+ durationMs,
1226
+ trackWithoutSession
1227
+ });
1228
+ throw error;
1229
+ }
1230
+ };
1231
+ return client;
1232
+ }
1233
+ function wrapGoogle(model, options = {}) {
1234
+ const { trackWithoutSession = false } = options;
1235
+ const originalGenerate = model.generateContent;
1236
+ if (!originalGenerate) {
1237
+ console.warn("Sentrial: Google model does not have generateContent");
1238
+ return model;
1239
+ }
1240
+ model.generateContent = async function(...args) {
1241
+ const startTime = Date.now();
1242
+ const contents = args[0];
1243
+ const modelName = model.model ?? "gemini-unknown";
1244
+ const messages = googleContentsToMessages(contents);
1245
+ try {
1246
+ const response = await originalGenerate.apply(model, args);
1247
+ const durationMs = Date.now() - startTime;
1248
+ let promptTokens = 0;
1249
+ let completionTokens = 0;
1250
+ if (response.usageMetadata) {
1251
+ promptTokens = response.usageMetadata.promptTokenCount ?? 0;
1252
+ completionTokens = response.usageMetadata.candidatesTokenCount ?? 0;
1253
+ }
1254
+ const totalTokens = promptTokens + completionTokens;
1255
+ let outputContent = "";
1256
+ try {
1257
+ outputContent = response.response?.text() ?? "";
1258
+ } catch {
1259
+ }
1260
+ const cost = calculateGoogleCost({ model: modelName, inputTokens: promptTokens, outputTokens: completionTokens });
1261
+ trackLLMCall({
1262
+ provider: "google",
1263
+ model: modelName,
1264
+ messages,
1265
+ output: outputContent,
1266
+ promptTokens,
1267
+ completionTokens,
1268
+ totalTokens,
1269
+ cost,
1270
+ durationMs,
1271
+ trackWithoutSession
1272
+ });
1273
+ return response;
1274
+ } catch (error) {
1275
+ const durationMs = Date.now() - startTime;
1276
+ trackLLMError({
1277
+ provider: "google",
1278
+ model: modelName,
1279
+ messages,
1280
+ error,
1281
+ durationMs,
1282
+ trackWithoutSession
1283
+ });
1284
+ throw error;
1285
+ }
1286
+ };
1287
+ return model;
1288
+ }
1289
+ function googleContentsToMessages(contents) {
1290
+ if (typeof contents === "string") {
1291
+ return [{ role: "user", content: contents }];
1292
+ }
1293
+ if (Array.isArray(contents)) {
1294
+ return contents.map((item) => {
1295
+ if (typeof item === "string") {
1296
+ return { role: "user", content: item };
1297
+ }
1298
+ if (item && typeof item === "object") {
1299
+ return { role: item.role ?? "user", content: String(item.content ?? item) };
1300
+ }
1301
+ return { role: "user", content: String(item) };
1302
+ });
1303
+ }
1304
+ return [{ role: "user", content: String(contents) }];
1305
+ }
1306
+ function wrapLLM(client, provider) {
1307
+ if (provider === "openai" || client.chat?.completions?.create) {
1308
+ return wrapOpenAI(client);
1309
+ }
1310
+ if (provider === "anthropic" || client.messages?.create) {
1311
+ return wrapAnthropic(client);
1312
+ }
1313
+ if (provider === "google" || client.generateContent) {
1314
+ return wrapGoogle(client);
1315
+ }
1316
+ console.warn("Sentrial: Unknown LLM client type. No auto-tracking applied.");
1317
+ return client;
1318
+ }
1319
+ function trackLLMCall(params) {
1320
+ const client = getTrackingClient();
1321
+ if (!client) return;
1322
+ const sessionId = _currentSessionId;
1323
+ if (!sessionId && !params.trackWithoutSession) {
1324
+ return;
1325
+ }
1326
+ if (sessionId) {
1327
+ client.trackToolCall({
1328
+ sessionId,
1329
+ toolName: `llm:${params.provider}:${params.model}`,
1330
+ toolInput: {
1331
+ messages: params.messages,
1332
+ model: params.model,
1333
+ provider: params.provider
1334
+ },
1335
+ toolOutput: {
1336
+ content: params.output,
1337
+ tokens: {
1338
+ prompt: params.promptTokens,
1339
+ completion: params.completionTokens,
1340
+ total: params.totalTokens
1341
+ },
1342
+ cost_usd: params.cost
1343
+ },
1344
+ reasoning: `LLM call to ${params.provider} ${params.model}`,
1345
+ estimatedCost: params.cost,
1346
+ tokenCount: params.totalTokens,
1347
+ metadata: {
1348
+ provider: params.provider,
1349
+ model: params.model,
1350
+ duration_ms: params.durationMs,
1351
+ prompt_tokens: params.promptTokens,
1352
+ completion_tokens: params.completionTokens
1353
+ }
1354
+ }).catch((err) => {
1355
+ console.warn("Sentrial: Failed to track LLM call:", err.message);
1356
+ });
1357
+ }
1358
+ }
1359
+ function trackLLMError(params) {
1360
+ const client = getTrackingClient();
1361
+ if (!client) return;
1362
+ const sessionId = _currentSessionId;
1363
+ if (!sessionId && !params.trackWithoutSession) {
1364
+ return;
1365
+ }
1366
+ if (sessionId) {
1367
+ client.trackError({
1368
+ sessionId,
1369
+ errorMessage: params.error.message,
1370
+ errorType: params.error.name,
1371
+ toolName: `llm:${params.provider}:${params.model}`,
1372
+ metadata: {
1373
+ provider: params.provider,
1374
+ model: params.model,
1375
+ duration_ms: params.durationMs
1376
+ }
1377
+ }).catch((err) => {
1378
+ console.warn("Sentrial: Failed to track LLM error:", err.message);
1379
+ });
1380
+ }
1381
+ }
1382
+
1383
+ // src/decorators.ts
1384
+ var _defaultClient3 = null;
1385
+ var _currentInteraction = null;
1386
+ function getClient3() {
1387
+ if (!_defaultClient3) {
1388
+ try {
1389
+ _defaultClient3 = new SentrialClient();
1390
+ setDefaultClient(_defaultClient3);
1391
+ } catch {
1392
+ return null;
1393
+ }
1394
+ }
1395
+ return _defaultClient3;
1396
+ }
1397
+ function setClient(client) {
1398
+ _defaultClient3 = client;
1399
+ setDefaultClient(client);
1400
+ }
1401
+ function getCurrentSessionId() {
1402
+ return getSessionContext();
1403
+ }
1404
+ function getCurrentInteraction() {
1405
+ return _currentInteraction;
1406
+ }
1407
+ function withTool(name, fn) {
1408
+ const isAsync = fn.constructor.name === "AsyncFunction";
1409
+ if (isAsync) {
1410
+ return async function(...args) {
1411
+ return trackToolAsync(name, fn, args);
1412
+ };
1413
+ } else {
1414
+ return function(...args) {
1415
+ return trackToolSync(name, fn, args);
1416
+ };
1417
+ }
1418
+ }
1419
+ async function trackToolAsync(toolName, fn, args) {
1420
+ const startTime = Date.now();
1421
+ const toolInput = buildToolInput(args);
1422
+ const client = getClient3();
1423
+ const sessionId = getSessionContext();
1424
+ try {
1425
+ const result = await fn(...args);
1426
+ const durationMs = Date.now() - startTime;
1427
+ if (client && sessionId) {
1428
+ const toolOutput = serializeOutput(result);
1429
+ client.trackToolCall({
1430
+ sessionId,
1431
+ toolName,
1432
+ toolInput,
1433
+ toolOutput,
1434
+ metadata: { duration_ms: durationMs }
1435
+ }).catch((err) => {
1436
+ console.warn(`Sentrial: Failed to track tool ${toolName}:`, err.message);
1437
+ });
1438
+ }
1439
+ return result;
1440
+ } catch (error) {
1441
+ const durationMs = Date.now() - startTime;
1442
+ if (client && sessionId) {
1443
+ client.trackError({
1444
+ sessionId,
1445
+ errorMessage: error.message,
1446
+ errorType: error.name,
1447
+ toolName,
1448
+ stackTrace: error.stack,
1449
+ metadata: { duration_ms: durationMs }
1450
+ }).catch(() => {
1451
+ });
1452
+ }
1453
+ throw error;
1454
+ }
1455
+ }
1456
+ function trackToolSync(toolName, fn, args) {
1457
+ const startTime = Date.now();
1458
+ const toolInput = buildToolInput(args);
1459
+ const client = getClient3();
1460
+ const sessionId = getSessionContext();
1461
+ try {
1462
+ const result = fn(...args);
1463
+ const durationMs = Date.now() - startTime;
1464
+ if (client && sessionId) {
1465
+ const toolOutput = serializeOutput(result);
1466
+ client.trackToolCall({
1467
+ sessionId,
1468
+ toolName,
1469
+ toolInput,
1470
+ toolOutput,
1471
+ metadata: { duration_ms: durationMs }
1472
+ }).catch((err) => {
1473
+ console.warn(`Sentrial: Failed to track tool ${toolName}:`, err.message);
1474
+ });
1475
+ }
1476
+ return result;
1477
+ } catch (error) {
1478
+ const durationMs = Date.now() - startTime;
1479
+ if (client && sessionId) {
1480
+ client.trackError({
1481
+ sessionId,
1482
+ errorMessage: error.message,
1483
+ errorType: error.name,
1484
+ toolName,
1485
+ stackTrace: error.stack,
1486
+ metadata: { duration_ms: durationMs }
1487
+ }).catch(() => {
1488
+ });
1489
+ }
1490
+ throw error;
1491
+ }
1492
+ }
1493
+ function withSession(agentName, fn, options = {}) {
1494
+ return async function(...args) {
1495
+ const client = getClient3();
1496
+ if (!client) {
1497
+ return fn(...args);
1498
+ }
1499
+ const { userId, userInput } = extractParams(args, options);
1500
+ const interaction = await client.begin({
1501
+ userId,
1502
+ event: agentName,
1503
+ input: userInput
1504
+ });
1505
+ const sessionId = interaction.getSessionId();
1506
+ if (sessionId) {
1507
+ setSessionContext(sessionId, client);
1508
+ }
1509
+ _currentInteraction = interaction;
1510
+ try {
1511
+ const result = await fn(...args);
1512
+ let output;
1513
+ if (typeof result === "string") {
1514
+ output = result;
1515
+ } else if (result && typeof result === "object") {
1516
+ if ("response" in result) {
1517
+ output = String(result.response);
1518
+ } else if ("output" in result) {
1519
+ output = String(result.output);
1520
+ }
1521
+ }
1522
+ if (output === void 0 && result !== null && result !== void 0) {
1523
+ output = String(result).slice(0, 1e3);
1524
+ }
1525
+ await interaction.finish({ output, success: true });
1526
+ return result;
1527
+ } catch (error) {
1528
+ await interaction.finish({
1529
+ success: false,
1530
+ failureReason: `${error.name}: ${error.message}`
1531
+ });
1532
+ throw error;
1533
+ } finally {
1534
+ clearSessionContext();
1535
+ _currentInteraction = null;
1536
+ }
1537
+ };
1538
+ }
1539
+ function extractParams(args, options) {
1540
+ let userId = "anonymous";
1541
+ let userInput;
1542
+ if (typeof options.userIdParam === "number") {
1543
+ userId = String(args[options.userIdParam] ?? "anonymous");
1544
+ } else if (typeof options.userIdParam === "string") {
1545
+ userId = String(args[0] ?? "anonymous");
1546
+ } else {
1547
+ userId = String(args[0] ?? "anonymous");
1548
+ }
1549
+ if (typeof options.inputParam === "number") {
1550
+ userInput = String(args[options.inputParam] ?? "");
1551
+ } else {
1552
+ const input = args[1];
1553
+ if (typeof input === "string") {
1554
+ userInput = input;
1555
+ } else if (Array.isArray(input)) {
1556
+ userInput = JSON.stringify(input).slice(0, 500);
1557
+ }
1558
+ }
1559
+ return { userId, userInput };
1560
+ }
1561
+ function Tool(name) {
1562
+ return function(_target, propertyKey, descriptor) {
1563
+ const originalMethod = descriptor.value;
1564
+ const toolName = name ?? String(propertyKey);
1565
+ descriptor.value = async function(...args) {
1566
+ const startTime = Date.now();
1567
+ const toolInput = buildToolInput(args);
1568
+ const client = getClient3();
1569
+ const sessionId = getSessionContext();
1570
+ try {
1571
+ const result = await originalMethod.apply(this, args);
1572
+ const durationMs = Date.now() - startTime;
1573
+ if (client && sessionId) {
1574
+ const toolOutput = serializeOutput(result);
1575
+ client.trackToolCall({
1576
+ sessionId,
1577
+ toolName,
1578
+ toolInput,
1579
+ toolOutput,
1580
+ metadata: { duration_ms: durationMs }
1581
+ }).catch((err) => {
1582
+ console.warn(`Sentrial: Failed to track tool ${toolName}:`, err.message);
1583
+ });
1584
+ }
1585
+ return result;
1586
+ } catch (error) {
1587
+ const durationMs = Date.now() - startTime;
1588
+ if (client && sessionId) {
1589
+ client.trackError({
1590
+ sessionId,
1591
+ errorMessage: error.message,
1592
+ errorType: error.name,
1593
+ toolName,
1594
+ stackTrace: error.stack,
1595
+ metadata: { duration_ms: durationMs }
1596
+ }).catch(() => {
1597
+ });
1598
+ }
1599
+ throw error;
1600
+ }
1601
+ };
1602
+ return descriptor;
1603
+ };
1604
+ }
1605
+ function TrackSession(agentName, options) {
1606
+ return function(target, _propertyKey, descriptor) {
1607
+ const originalMethod = descriptor.value;
1608
+ const agent = agentName ?? target.constructor.name;
1609
+ descriptor.value = async function(...args) {
1610
+ const client = getClient3();
1611
+ if (!client) {
1612
+ return originalMethod.apply(this, args);
1613
+ }
1614
+ const { userId, userInput } = extractParams(args, options ?? {});
1615
+ const interaction = await client.begin({
1616
+ userId,
1617
+ event: agent,
1618
+ input: userInput
1619
+ });
1620
+ const sessionId = interaction.getSessionId();
1621
+ if (sessionId) {
1622
+ setSessionContext(sessionId, client);
1623
+ }
1624
+ _currentInteraction = interaction;
1625
+ try {
1626
+ const result = await originalMethod.apply(this, args);
1627
+ let output;
1628
+ if (typeof result === "string") {
1629
+ output = result;
1630
+ } else if (result && typeof result === "object") {
1631
+ if ("response" in result) {
1632
+ output = String(result.response);
1633
+ } else if ("output" in result) {
1634
+ output = String(result.output);
1635
+ }
1636
+ }
1637
+ if (output === void 0 && result !== null && result !== void 0) {
1638
+ output = String(result).slice(0, 1e3);
1639
+ }
1640
+ await interaction.finish({ output, success: true });
1641
+ return result;
1642
+ } catch (error) {
1643
+ await interaction.finish({
1644
+ success: false,
1645
+ failureReason: `${error.name}: ${error.message}`
1646
+ });
1647
+ throw error;
1648
+ } finally {
1649
+ clearSessionContext();
1650
+ _currentInteraction = null;
1651
+ }
1652
+ };
1653
+ return descriptor;
1654
+ };
1655
+ }
1656
+ var SessionContext = class {
1657
+ userId;
1658
+ agent;
1659
+ input;
1660
+ client;
1661
+ interaction = null;
1662
+ output;
1663
+ constructor(options) {
1664
+ this.userId = options.userId;
1665
+ this.agent = options.agent;
1666
+ this.input = options.input;
1667
+ this.client = options.client ?? getClient3();
1668
+ }
1669
+ /**
1670
+ * Start the session
1671
+ */
1672
+ async start() {
1673
+ if (!this.client) return this;
1674
+ this.interaction = await this.client.begin({
1675
+ userId: this.userId,
1676
+ event: this.agent,
1677
+ input: this.input
1678
+ });
1679
+ const sessionId = this.interaction.getSessionId();
1680
+ if (sessionId) {
1681
+ setSessionContext(sessionId, this.client);
1682
+ }
1683
+ _currentInteraction = this.interaction;
1684
+ return this;
1685
+ }
1686
+ /**
1687
+ * Set the output for this session
1688
+ */
1689
+ setOutput(output) {
1690
+ this.output = output;
1691
+ }
1692
+ /**
1693
+ * Finish the session
1694
+ */
1695
+ async finish(options) {
1696
+ if (this.interaction) {
1697
+ await this.interaction.finish({
1698
+ output: this.output,
1699
+ success: options?.success ?? true,
1700
+ failureReason: options?.error
1701
+ });
1702
+ }
1703
+ clearSessionContext();
1704
+ _currentInteraction = null;
1705
+ }
1706
+ /**
1707
+ * Get the session ID
1708
+ */
1709
+ get sessionId() {
1710
+ return this.interaction?.getSessionId() ?? null;
1711
+ }
1712
+ };
1713
+ function buildToolInput(args) {
1714
+ if (args.length === 0) {
1715
+ return {};
1716
+ }
1717
+ if (args.length === 1 && typeof args[0] === "object" && args[0] !== null) {
1718
+ return serializeValue(args[0]);
1719
+ }
1720
+ return {
1721
+ args: args.map(serializeValue)
1722
+ };
1723
+ }
1724
+ function serializeValue(value) {
1725
+ if (value === null || value === void 0) {
1726
+ return value;
1727
+ }
1728
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1729
+ return value;
1730
+ }
1731
+ if (Array.isArray(value)) {
1732
+ return value.map(serializeValue);
1733
+ }
1734
+ if (typeof value === "object") {
1735
+ const result = {};
1736
+ for (const [k, v] of Object.entries(value)) {
1737
+ result[k] = serializeValue(v);
1738
+ }
1739
+ return result;
1740
+ }
1741
+ try {
1742
+ return String(value).slice(0, 1e3);
1743
+ } catch {
1744
+ return `<${typeof value}>`;
1745
+ }
1746
+ }
1747
+ function serializeOutput(value) {
1748
+ if (value === null || value === void 0) {
1749
+ return { result: null };
1750
+ }
1751
+ if (typeof value === "object" && !Array.isArray(value)) {
1752
+ return serializeValue(value);
1753
+ }
1754
+ return { result: serializeValue(value) };
1755
+ }
1756
+
1757
+ // src/context.ts
1758
+ var _experimentContext = null;
1759
+ function getSystemPrompt(defaultPrompt) {
1760
+ if (_experimentContext?.systemPrompt) {
1761
+ return _experimentContext.systemPrompt;
1762
+ }
1763
+ return defaultPrompt ?? "";
1764
+ }
1765
+ function getExperimentContext() {
1766
+ return _experimentContext;
1767
+ }
1768
+ function isExperimentMode() {
1769
+ return _experimentContext !== null;
1770
+ }
1771
+ function getVariantName() {
1772
+ return _experimentContext?.variantName ?? null;
1773
+ }
1774
+ function getExperimentId() {
1775
+ return _experimentContext?.experimentId ?? null;
1776
+ }
1777
+ function setExperimentContext(context) {
1778
+ _experimentContext = context;
1779
+ }
1780
+ function clearExperimentContext() {
1781
+ _experimentContext = null;
1782
+ }
1783
+
1784
+ // src/experiment.ts
1785
+ var ExperimentRunTracker = class {
1786
+ experiment;
1787
+ variantName;
1788
+ baseSessionId;
1789
+ runId;
1790
+ resultSessionId;
1791
+ _success = true;
1792
+ _errorMessage;
1793
+ /** @internal */
1794
+ constructor(experiment, variantName, baseSessionId) {
1795
+ this.experiment = experiment;
1796
+ this.variantName = variantName;
1797
+ this.baseSessionId = baseSessionId;
1798
+ }
1799
+ /**
1800
+ * Start the run - creates a run record via API.
1801
+ */
1802
+ async start() {
1803
+ try {
1804
+ const response = await this.experiment.request(
1805
+ "POST",
1806
+ `/api/experiments/${this.experiment.experimentId}/runs`,
1807
+ {
1808
+ variantName: this.variantName,
1809
+ baseSessionId: this.baseSessionId
1810
+ }
1811
+ );
1812
+ this.runId = response?.run?.id;
1813
+ } catch {
1814
+ }
1815
+ return this;
1816
+ }
1817
+ /**
1818
+ * Set the session ID of the result session.
1819
+ */
1820
+ setResultSessionId(sessionId) {
1821
+ this.resultSessionId = sessionId;
1822
+ }
1823
+ /**
1824
+ * Mark the run as complete.
1825
+ */
1826
+ async complete() {
1827
+ if (!this.runId) return;
1828
+ try {
1829
+ await this.experiment.request(
1830
+ "PATCH",
1831
+ `/api/experiments/${this.experiment.experimentId}/runs/${this.runId}`,
1832
+ {
1833
+ status: "completed",
1834
+ resultSessionId: this.resultSessionId
1835
+ }
1836
+ );
1837
+ } catch {
1838
+ }
1839
+ }
1840
+ /**
1841
+ * Mark the run as failed.
1842
+ */
1843
+ async fail(errorMessage) {
1844
+ this._success = false;
1845
+ this._errorMessage = errorMessage;
1846
+ if (!this.runId) return;
1847
+ try {
1848
+ await this.experiment.request(
1849
+ "PATCH",
1850
+ `/api/experiments/${this.experiment.experimentId}/runs/${this.runId}`,
1851
+ {
1852
+ status: "failed",
1853
+ resultSessionId: this.resultSessionId,
1854
+ errorMessage
1855
+ }
1856
+ );
1857
+ } catch {
1858
+ }
1859
+ }
1860
+ /**
1861
+ * Get the result of this run.
1862
+ */
1863
+ getResult() {
1864
+ return {
1865
+ variantName: this.variantName,
1866
+ testCaseSessionId: this.baseSessionId,
1867
+ resultSessionId: this.resultSessionId,
1868
+ success: this._success,
1869
+ errorMessage: this._errorMessage
1870
+ };
1871
+ }
1872
+ };
1873
+ var Experiment = class {
1874
+ /** The experiment ID */
1875
+ experimentId;
1876
+ /** @internal */
1877
+ client;
1878
+ /** @internal */
1879
+ apiUrl;
1880
+ /** @internal */
1881
+ apiKey;
1882
+ config;
1883
+ _variants;
1884
+ _testCases;
1885
+ /**
1886
+ * Create an experiment instance.
1887
+ *
1888
+ * @param experimentId - The experiment ID from Sentrial dashboard
1889
+ * @param options - Configuration options
1890
+ */
1891
+ constructor(experimentId, options = {}) {
1892
+ this.experimentId = experimentId;
1893
+ this.apiUrl = (options.apiUrl ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_URL : void 0) ?? "https://api.sentrial.com").replace(/\/$/, "");
1894
+ this.apiKey = options.apiKey ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_KEY : void 0);
1895
+ this.client = new SentrialClient({
1896
+ apiKey: this.apiKey,
1897
+ apiUrl: this.apiUrl,
1898
+ failSilently: false
1899
+ // We want errors for experiments
1900
+ });
1901
+ }
1902
+ /**
1903
+ * Make an HTTP request to the API
1904
+ * @internal
1905
+ */
1906
+ async request(method, path, body) {
1907
+ const headers = {
1908
+ "Content-Type": "application/json"
1909
+ };
1910
+ if (this.apiKey) {
1911
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
1912
+ }
1913
+ const response = await fetch(`${this.apiUrl}${path}`, {
1914
+ method,
1915
+ headers,
1916
+ body: body ? JSON.stringify(body) : void 0
1917
+ });
1918
+ if (!response.ok) {
1919
+ return null;
1920
+ }
1921
+ return response.json();
1922
+ }
1923
+ /**
1924
+ * Load the experiment configuration from the API.
1925
+ */
1926
+ async load() {
1927
+ const response = await this.request(
1928
+ "GET",
1929
+ `/api/experiments/${this.experimentId}/runs`
1930
+ );
1931
+ if (!response?.experiment) {
1932
+ throw new Error(`Failed to load experiment config for ${this.experimentId}`);
1933
+ }
1934
+ this.config = response.experiment;
1935
+ const variants = this.config.variants;
1936
+ this._variants = variants?.map((v) => ({
1937
+ name: v.name ?? "",
1938
+ systemPrompt: v.systemPrompt ?? "",
1939
+ description: v.description
1940
+ })) ?? [];
1941
+ const testCases = this.config.testCases;
1942
+ this._testCases = testCases?.filter((tc) => tc.userInput)?.map((tc) => ({
1943
+ sessionId: tc.sessionId ?? "",
1944
+ userInput: tc.userInput ?? ""
1945
+ })) ?? [];
1946
+ return this;
1947
+ }
1948
+ /**
1949
+ * Get experiment name.
1950
+ */
1951
+ get name() {
1952
+ return this.config?.name ?? "";
1953
+ }
1954
+ /**
1955
+ * Get experiment status.
1956
+ */
1957
+ get status() {
1958
+ return this.config?.status ?? "";
1959
+ }
1960
+ /**
1961
+ * Get list of variants to test.
1962
+ */
1963
+ get variants() {
1964
+ if (!this._variants) {
1965
+ throw new Error("Experiment not loaded. Call load() first.");
1966
+ }
1967
+ return this._variants;
1968
+ }
1969
+ /**
1970
+ * Get list of test cases.
1971
+ */
1972
+ get testCases() {
1973
+ if (!this._testCases) {
1974
+ throw new Error("Experiment not loaded. Call load() first.");
1975
+ }
1976
+ return this._testCases;
1977
+ }
1978
+ /**
1979
+ * Get a specific variant by name.
1980
+ */
1981
+ getVariant(name) {
1982
+ return this.variants.find((v) => v.name === name);
1983
+ }
1984
+ /**
1985
+ * Create a run tracker for manual experiment runs.
1986
+ */
1987
+ async trackRun(variantName, baseSessionId) {
1988
+ const tracker = new ExperimentRunTracker(this, variantName, baseSessionId);
1989
+ await tracker.start();
1990
+ return tracker;
1991
+ }
1992
+ /**
1993
+ * Run the experiment with all variants and test cases.
1994
+ *
1995
+ * @param agentFn - Function that runs your agent with a test case and variant
1996
+ * @param options - Run options
1997
+ * @returns List of run results
1998
+ */
1999
+ async run(agentFn, options = {}) {
2000
+ const { parallel = false, maxWorkers = 4 } = options;
2001
+ if (!this._variants || !this._testCases) {
2002
+ await this.load();
2003
+ }
2004
+ const results = [];
2005
+ const totalRuns = this.variants.length * this.testCases.length;
2006
+ let completed = 0;
2007
+ console.log(`Running experiment: ${this.name || this.experimentId}`);
2008
+ console.log(` Variants: ${this.variants.length}`);
2009
+ console.log(` Test cases: ${this.testCases.length}`);
2010
+ console.log(` Total runs: ${totalRuns}`);
2011
+ console.log();
2012
+ const runSingle = async (variant, testCase) => {
2013
+ const tracker = await this.trackRun(variant.name, testCase.sessionId);
2014
+ setExperimentContext({
2015
+ systemPrompt: variant.systemPrompt,
2016
+ variantName: variant.name,
2017
+ experimentId: this.experimentId,
2018
+ testCaseId: testCase.sessionId
2019
+ });
2020
+ try {
2021
+ await agentFn(testCase, variant, tracker);
2022
+ await tracker.complete();
2023
+ return tracker.getResult();
2024
+ } catch (error) {
2025
+ const errorMessage = error instanceof Error ? error.message : String(error);
2026
+ await tracker.fail(errorMessage);
2027
+ return tracker.getResult();
2028
+ } finally {
2029
+ clearExperimentContext();
2030
+ }
2031
+ };
2032
+ if (parallel) {
2033
+ const queue = [];
2034
+ for (const variant of this.variants) {
2035
+ for (const testCase of this.testCases) {
2036
+ queue.push(async () => {
2037
+ const result = await runSingle(variant, testCase);
2038
+ results.push(result);
2039
+ completed++;
2040
+ const status = result.success ? "\u2713" : "\u2717";
2041
+ console.log(
2042
+ ` [${completed}/${totalRuns}] ${status} ${variant.name} x ${testCase.sessionId.slice(0, 8)}...`
2043
+ );
2044
+ });
2045
+ }
2046
+ }
2047
+ const executing = [];
2048
+ for (const task of queue) {
2049
+ const promise = task().then(() => {
2050
+ executing.splice(executing.indexOf(promise), 1);
2051
+ });
2052
+ executing.push(promise);
2053
+ if (executing.length >= maxWorkers) {
2054
+ await Promise.race(executing);
2055
+ }
2056
+ }
2057
+ await Promise.all(executing);
2058
+ } else {
2059
+ for (const variant of this.variants) {
2060
+ for (const testCase of this.testCases) {
2061
+ const result = await runSingle(variant, testCase);
2062
+ results.push(result);
2063
+ completed++;
2064
+ const status = result.success ? "\u2713" : "\u2717";
2065
+ console.log(
2066
+ ` [${completed}/${totalRuns}] ${status} ${variant.name} x ${testCase.sessionId.slice(0, 8)}...`
2067
+ );
2068
+ }
2069
+ }
2070
+ }
2071
+ console.log();
2072
+ console.log("Experiment complete!");
2073
+ console.log(` Successful: ${results.filter((r) => r.success).length}/${totalRuns}`);
2074
+ console.log(` Failed: ${results.filter((r) => !r.success).length}/${totalRuns}`);
2075
+ return results;
2076
+ }
2077
+ /**
2078
+ * Reset all runs for this experiment (for re-running).
2079
+ */
2080
+ async reset() {
2081
+ try {
2082
+ const response = await this.request(
2083
+ "DELETE",
2084
+ `/api/experiments/${this.experimentId}/runs`
2085
+ );
2086
+ if (response !== null) {
2087
+ this.config = void 0;
2088
+ this._variants = void 0;
2089
+ this._testCases = void 0;
2090
+ return true;
2091
+ }
2092
+ return false;
2093
+ } catch {
2094
+ return false;
2095
+ }
2096
+ }
2097
+ /**
2098
+ * Fetch aggregated results from the API.
2099
+ */
2100
+ async getResults() {
2101
+ try {
2102
+ const response = await this.request(
2103
+ "GET",
2104
+ `/api/experiments/${this.experimentId}/results`
2105
+ );
2106
+ return response;
2107
+ } catch {
2108
+ return null;
2109
+ }
2110
+ }
2111
+ };
579
2112
  // Annotate the CommonJS export names for ESM import in node:
580
2113
  0 && (module.exports = {
581
2114
  ApiError,
582
2115
  EventType,
2116
+ Experiment,
2117
+ ExperimentRunTracker,
583
2118
  Interaction,
584
2119
  NetworkError,
585
2120
  SentrialClient,
586
2121
  SentrialError,
2122
+ SessionContext,
2123
+ Tool,
2124
+ TrackSession,
587
2125
  ValidationError,
588
2126
  begin,
589
2127
  calculateAnthropicCost,
590
2128
  calculateGoogleCost,
591
2129
  calculateOpenAICost,
2130
+ clearExperimentContext,
2131
+ clearSessionContext,
592
2132
  configure,
593
- sentrial
2133
+ configureVercel,
2134
+ getCurrentInteraction,
2135
+ getCurrentSessionId,
2136
+ getExperimentContext,
2137
+ getExperimentId,
2138
+ getSessionContext,
2139
+ getSystemPrompt,
2140
+ getVariantName,
2141
+ isExperimentMode,
2142
+ sentrial,
2143
+ setClient,
2144
+ setDefaultClient,
2145
+ setExperimentContext,
2146
+ setSessionContext,
2147
+ withSession,
2148
+ withTool,
2149
+ wrapAISDK,
2150
+ wrapAnthropic,
2151
+ wrapGoogle,
2152
+ wrapLLM,
2153
+ wrapOpenAI
594
2154
  });
595
2155
  //# sourceMappingURL=index.cjs.map