@sentrial/sdk 0.2.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
@@ -47,7 +47,12 @@ __export(index_exports, {
47
47
  getSessionContext: () => getSessionContext,
48
48
  getSystemPrompt: () => getSystemPrompt,
49
49
  getVariantName: () => getVariantName,
50
+ hashValue: () => hashValue,
50
51
  isExperimentMode: () => isExperimentMode,
52
+ redactPayload: () => redactPayload,
53
+ redactString: () => redactString,
54
+ redactValue: () => redactValue,
55
+ replaceMatch: () => replaceMatch,
51
56
  sentrial: () => sentrial,
52
57
  setClient: () => setClient,
53
58
  setDefaultClient: () => setDefaultClient,
@@ -132,26 +137,126 @@ var ValidationError = class extends SentrialError {
132
137
  }
133
138
  };
134
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
+
135
230
  // src/cost.ts
136
231
  var OPENAI_PRICING = {
137
232
  "gpt-5.2": { input: 5, output: 15 },
138
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 },
139
237
  "gpt-4o": { input: 2.5, output: 10 },
140
238
  "gpt-4o-mini": { input: 0.15, output: 0.6 },
141
239
  "gpt-4-turbo": { input: 10, output: 30 },
142
240
  "gpt-4": { input: 30, output: 60 },
143
241
  "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
242
+ "o4-mini": { input: 1.1, output: 4.4 },
144
243
  "o3": { input: 10, output: 40 },
145
- "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 },
146
247
  "o1-preview": { input: 15, output: 60 },
147
248
  "o1-mini": { input: 3, output: 12 }
148
249
  };
149
250
  var ANTHROPIC_PRICING = {
251
+ "claude-opus-4": { input: 15, output: 75 },
252
+ "claude-sonnet-4": { input: 3, output: 15 },
150
253
  "claude-4.5-opus": { input: 20, output: 100 },
151
254
  "claude-4.5-sonnet": { input: 4, output: 20 },
152
255
  "claude-4-opus": { input: 18, output: 90 },
153
256
  "claude-4-sonnet": { input: 3.5, output: 17.5 },
257
+ "claude-3-7-sonnet": { input: 3, output: 15 },
154
258
  "claude-3-5-sonnet": { input: 3, output: 15 },
259
+ "claude-3-5-haiku": { input: 0.8, output: 4 },
155
260
  "claude-3-opus": { input: 15, output: 75 },
156
261
  "claude-3-sonnet": { input: 3, output: 15 },
157
262
  "claude-3-haiku": { input: 0.25, output: 1.25 }
@@ -222,11 +327,75 @@ var SentrialClient = class {
222
327
  apiUrl;
223
328
  apiKey;
224
329
  failSilently;
330
+ piiConfig;
331
+ piiConfigNeedsHydration = false;
332
+ piiHydrationPromise;
225
333
  currentState = {};
226
334
  constructor(config = {}) {
227
335
  this.apiUrl = (config.apiUrl ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_URL : void 0) ?? DEFAULT_API_URL).replace(/\/$/, "");
228
336
  this.apiKey = config.apiKey ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_KEY : void 0);
229
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
+ }
345
+ }
346
+ /**
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.
351
+ */
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;
367
+ try {
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);
375
+ }
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
+ }
393
+ }
394
+ } catch {
395
+ }
396
+ this.piiConfigNeedsHydration = false;
397
+ })();
398
+ await this.piiHydrationPromise;
230
399
  }
231
400
  /**
232
401
  * Make an HTTP request with retry logic and exponential backoff.
@@ -235,6 +404,9 @@ var SentrialClient = class {
235
404
  * Up to MAX_RETRIES attempts with exponential backoff.
236
405
  */
237
406
  async safeRequest(method, url, body) {
407
+ if (this.piiConfigNeedsHydration) {
408
+ await this.hydratePiiConfig();
409
+ }
238
410
  let lastError;
239
411
  let backoff = INITIAL_BACKOFF_MS;
240
412
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
@@ -245,6 +417,7 @@ var SentrialClient = class {
245
417
  if (this.apiKey) {
246
418
  headers["Authorization"] = `Bearer ${this.apiKey}`;
247
419
  }
420
+ const finalBody = this.piiConfig && body && typeof body === "object" ? redactPayload(body, this.piiConfig) : body;
248
421
  const controller = new AbortController();
249
422
  const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
250
423
  let response;
@@ -252,7 +425,7 @@ var SentrialClient = class {
252
425
  response = await fetch(url, {
253
426
  method,
254
427
  headers,
255
- body: body ? JSON.stringify(body) : void 0,
428
+ body: finalBody ? JSON.stringify(finalBody) : void 0,
256
429
  signal: controller.signal
257
430
  });
258
431
  } finally {
@@ -324,6 +497,9 @@ var SentrialClient = class {
324
497
  if (params.parentSessionId) {
325
498
  payload.parentSessionId = params.parentSessionId;
326
499
  }
500
+ if (params.convoId) {
501
+ payload.convoId = params.convoId;
502
+ }
327
503
  const response = await this.safeRequest(
328
504
  "POST",
329
505
  `${this.apiUrl}/api/sdk/sessions`,
@@ -530,6 +706,7 @@ var SentrialClient = class {
530
706
  name: `${params.event}:${eventId.slice(0, 8)}`,
531
707
  agentName: params.event,
532
708
  userId: params.userId,
709
+ convoId: params.convoId,
533
710
  metadata: fullMetadata
534
711
  });
535
712
  if (params.input) {
@@ -694,7 +871,8 @@ function configureVercel(config) {
694
871
  });
695
872
  _globalConfig = {
696
873
  defaultAgent: config.defaultAgent,
697
- userId: config.userId
874
+ userId: config.userId,
875
+ convoId: config.convoId
698
876
  };
699
877
  }
700
878
  function getClient2() {
@@ -709,11 +887,15 @@ function extractModelInfo(model) {
709
887
  return { modelId, provider };
710
888
  }
711
889
  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";
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";
717
899
  return "unknown";
718
900
  }
719
901
  function calculateCostForCall(provider, modelId, promptTokens, completionTokens) {
@@ -725,6 +907,12 @@ function calculateCostForCall(provider, modelId, promptTokens, completionTokens)
725
907
  return calculateAnthropicCost(params);
726
908
  case "google":
727
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;
728
916
  default:
729
917
  return promptTokens * 3e-6 + completionTokens * 6e-6;
730
918
  }
@@ -733,7 +921,10 @@ function extractInput(params) {
733
921
  if (params.prompt) return params.prompt;
734
922
  if (params.messages && params.messages.length > 0) {
735
923
  const lastUserMessage = [...params.messages].reverse().find((m) => m.role === "user");
736
- return lastUserMessage?.content || JSON.stringify(params.messages);
924
+ if (lastUserMessage) {
925
+ return typeof lastUserMessage.content === "string" ? lastUserMessage.content : JSON.stringify(lastUserMessage.content);
926
+ }
927
+ return JSON.stringify(params.messages);
737
928
  }
738
929
  return "";
739
930
  }
@@ -750,23 +941,25 @@ function wrapTools(tools, sessionId, client) {
750
941
  try {
751
942
  const result = await originalExecute(...args);
752
943
  const durationMs = Date.now() - startTime;
753
- await client.trackToolCall({
944
+ client.trackToolCall({
754
945
  sessionId,
755
946
  toolName,
756
947
  toolInput: args[0],
757
948
  toolOutput: result,
758
949
  reasoning: `Tool executed in ${durationMs}ms`
950
+ }).catch(() => {
759
951
  });
760
952
  return result;
761
953
  } catch (error) {
762
954
  const durationMs = Date.now() - startTime;
763
- await client.trackToolCall({
955
+ client.trackToolCall({
764
956
  sessionId,
765
957
  toolName,
766
958
  toolInput: args[0],
767
959
  toolOutput: {},
768
960
  toolError: { message: error instanceof Error ? error.message : "Unknown error" },
769
961
  reasoning: `Tool failed after ${durationMs}ms`
962
+ }).catch(() => {
770
963
  });
771
964
  throw error;
772
965
  }
@@ -778,19 +971,68 @@ function wrapTools(tools, sessionId, client) {
778
971
  }
779
972
  return wrappedTools;
780
973
  }
781
- function wrapGenerateText(originalFn, client) {
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) {
782
1022
  return async (params) => {
783
1023
  const startTime = Date.now();
784
1024
  const { modelId, provider } = extractModelInfo(params.model);
785
1025
  const input = extractInput(params);
786
1026
  const sessionId = await client.createSession({
787
1027
  name: `generateText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
788
- agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
789
- userId: _globalConfig.userId ?? "anonymous",
1028
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1029
+ userId: config.userId ?? "anonymous",
1030
+ convoId: config.convoId,
790
1031
  metadata: {
791
1032
  model: modelId,
792
1033
  provider,
793
- function: "generateText"
1034
+ function: "generateText",
1035
+ ...params.maxSteps ? { maxSteps: params.maxSteps } : {}
794
1036
  }
795
1037
  });
796
1038
  if (!sessionId) {
@@ -804,23 +1046,46 @@ function wrapGenerateText(originalFn, client) {
804
1046
  try {
805
1047
  const result = await originalFn(wrappedParams);
806
1048
  const durationMs = Date.now() - startTime;
1049
+ const resolvedModelId = result.response?.modelId || modelId;
807
1050
  const promptTokens = result.usage?.promptTokens || 0;
808
1051
  const completionTokens = result.usage?.completionTokens || 0;
809
1052
  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)
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
+ });
822
1073
  }
823
- });
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
+ }
824
1089
  await client.completeSession({
825
1090
  sessionId,
826
1091
  success: true,
@@ -849,134 +1114,139 @@ function wrapGenerateText(originalFn, client) {
849
1114
  }
850
1115
  };
851
1116
  }
852
- function wrapStreamText(originalFn, client) {
1117
+ function wrapStreamText(originalFn, client, config) {
853
1118
  return (params) => {
854
1119
  const startTime = Date.now();
855
1120
  const { modelId, provider } = extractModelInfo(params.model);
856
1121
  const input = extractInput(params);
857
1122
  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"
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;
866
1144
  }
867
- }).then((id) => {
868
- sessionId = id;
869
- if (id) client.setInput(id, input);
870
- return id;
871
- });
1145
+ })();
872
1146
  const wrappedParams = {
873
1147
  ...params,
874
1148
  tools: params.tools ? wrapToolsAsync(params.tools, sessionPromise, client) : void 0
875
1149
  };
876
1150
  const result = originalFn(wrappedParams);
877
- const originalTextStream = result.textStream;
878
- let fullText = "";
879
- result.textStream = (async function* () {
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;
880
1173
  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;
1174
+ usage = result.usage ? await result.usage : void 0;
1175
+ } catch {
921
1176
  }
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
- }
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
963
1190
  }
964
- };
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
+ });
965
1218
  } else {
966
- wrappedTools[toolName] = tool;
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
+ })();
967
1236
  }
968
- }
969
- return wrappedTools;
1237
+ return result;
1238
+ };
970
1239
  }
971
- function wrapGenerateObject(originalFn, client) {
1240
+ function wrapGenerateObject(originalFn, client, config) {
972
1241
  return async (params) => {
973
1242
  const startTime = Date.now();
974
1243
  const { modelId, provider } = extractModelInfo(params.model);
975
1244
  const input = extractInput(params);
976
1245
  const sessionId = await client.createSession({
977
1246
  name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
978
- agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
979
- userId: _globalConfig.userId ?? "anonymous",
1247
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1248
+ userId: config.userId ?? "anonymous",
1249
+ convoId: config.convoId,
980
1250
  metadata: {
981
1251
  model: modelId,
982
1252
  provider,
@@ -990,10 +1260,11 @@ function wrapGenerateObject(originalFn, client) {
990
1260
  try {
991
1261
  const result = await originalFn(params);
992
1262
  const durationMs = Date.now() - startTime;
1263
+ const resolvedModelId = result.response?.modelId || modelId;
993
1264
  const promptTokens = result.usage?.promptTokens || 0;
994
1265
  const completionTokens = result.usage?.completionTokens || 0;
995
1266
  const totalTokens = result.usage?.totalTokens || promptTokens + completionTokens;
996
- const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
1267
+ const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
997
1268
  await client.completeSession({
998
1269
  sessionId,
999
1270
  success: true,
@@ -1022,38 +1293,51 @@ function wrapGenerateObject(originalFn, client) {
1022
1293
  }
1023
1294
  };
1024
1295
  }
1025
- function wrapStreamObject(originalFn, client) {
1296
+ function wrapStreamObject(originalFn, client, config) {
1026
1297
  return (params) => {
1027
1298
  const startTime = Date.now();
1028
1299
  const { modelId, provider } = extractModelInfo(params.model);
1029
1300
  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"
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;
1038
1321
  }
1039
- }).then((id) => {
1040
- if (id) client.setInput(id, input);
1041
- return id;
1042
- });
1322
+ })();
1043
1323
  const result = originalFn(params);
1044
1324
  if (result.object) {
1045
1325
  const originalObjectPromise = result.object;
1046
1326
  result.object = originalObjectPromise.then(async (obj) => {
1047
1327
  const durationMs = Date.now() - startTime;
1048
- const sessionId = await sessionPromise;
1049
- if (sessionId) {
1050
- const usage = result.usage ? await result.usage : void 0;
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
+ }
1051
1335
  const promptTokens = usage?.promptTokens || 0;
1052
1336
  const completionTokens = usage?.completionTokens || 0;
1053
1337
  const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
1054
1338
  const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
1055
1339
  await client.completeSession({
1056
- sessionId,
1340
+ sessionId: sid,
1057
1341
  success: true,
1058
1342
  output: JSON.stringify(obj),
1059
1343
  durationMs,
@@ -1066,15 +1350,15 @@ function wrapStreamObject(originalFn, client) {
1066
1350
  return obj;
1067
1351
  }).catch(async (error) => {
1068
1352
  const durationMs = Date.now() - startTime;
1069
- const sessionId = await sessionPromise;
1070
- if (sessionId) {
1353
+ const sid = await sessionPromise;
1354
+ if (sid) {
1071
1355
  await client.trackError({
1072
- sessionId,
1356
+ sessionId: sid,
1073
1357
  errorType: error instanceof Error ? error.name : "Error",
1074
1358
  errorMessage: error instanceof Error ? error.message : "Unknown error"
1075
1359
  });
1076
1360
  await client.completeSession({
1077
- sessionId,
1361
+ sessionId: sid,
1078
1362
  success: false,
1079
1363
  failureReason: error instanceof Error ? error.message : "Unknown error",
1080
1364
  durationMs
@@ -1086,14 +1370,27 @@ function wrapStreamObject(originalFn, client) {
1086
1370
  return result;
1087
1371
  };
1088
1372
  }
1089
- function wrapAISDK(ai) {
1090
- const client = getClient2();
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
+ };
1091
1380
  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)
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)
1097
1394
  };
1098
1395
  }
1099
1396
 
@@ -2138,7 +2435,12 @@ var Experiment = class {
2138
2435
  getSessionContext,
2139
2436
  getSystemPrompt,
2140
2437
  getVariantName,
2438
+ hashValue,
2141
2439
  isExperimentMode,
2440
+ redactPayload,
2441
+ redactString,
2442
+ redactValue,
2443
+ replaceMatch,
2142
2444
  sentrial,
2143
2445
  setClient,
2144
2446
  setDefaultClient,