@sentrial/sdk 0.2.0 → 0.3.1

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,140 @@ 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;
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
+ textProp.then((text) => {
1209
+ trackCompletion(text).catch(() => {
1210
+ });
1211
+ }).catch((err) => {
1212
+ trackCompletion("", err instanceof Error ? err : new Error(String(err))).catch(() => {
1213
+ });
1214
+ });
1215
+ } else {
1216
+ const desc = Object.getOwnPropertyDescriptor(result, "textStream") ?? Object.getOwnPropertyDescriptor(Object.getPrototypeOf(result), "textStream");
1217
+ const isWritable = !desc || desc.writable || desc.set;
1218
+ if (isWritable) {
1219
+ const originalTextStream = result.textStream;
1220
+ let fullText = "";
1221
+ result.textStream = (async function* () {
936
1222
  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
- });
1223
+ for await (const chunk of originalTextStream) {
1224
+ fullText += chunk;
1225
+ yield chunk;
947
1226
  }
948
- return result;
1227
+ await trackCompletion(fullText);
949
1228
  } 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
- }
1229
+ await trackCompletion(
1230
+ "",
1231
+ error instanceof Error ? error : new Error(String(error))
1232
+ );
961
1233
  throw error;
962
1234
  }
963
- }
964
- };
965
- } else {
966
- wrappedTools[toolName] = tool;
1235
+ })();
1236
+ }
967
1237
  }
968
- }
969
- return wrappedTools;
1238
+ return result;
1239
+ };
970
1240
  }
971
- function wrapGenerateObject(originalFn, client) {
1241
+ function wrapGenerateObject(originalFn, client, config) {
972
1242
  return async (params) => {
973
1243
  const startTime = Date.now();
974
1244
  const { modelId, provider } = extractModelInfo(params.model);
975
1245
  const input = extractInput(params);
976
1246
  const sessionId = await client.createSession({
977
1247
  name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
978
- agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
979
- userId: _globalConfig.userId ?? "anonymous",
1248
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1249
+ userId: config.userId ?? "anonymous",
1250
+ convoId: config.convoId,
980
1251
  metadata: {
981
1252
  model: modelId,
982
1253
  provider,
@@ -990,10 +1261,11 @@ function wrapGenerateObject(originalFn, client) {
990
1261
  try {
991
1262
  const result = await originalFn(params);
992
1263
  const durationMs = Date.now() - startTime;
1264
+ const resolvedModelId = result.response?.modelId || modelId;
993
1265
  const promptTokens = result.usage?.promptTokens || 0;
994
1266
  const completionTokens = result.usage?.completionTokens || 0;
995
1267
  const totalTokens = result.usage?.totalTokens || promptTokens + completionTokens;
996
- const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
1268
+ const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
997
1269
  await client.completeSession({
998
1270
  sessionId,
999
1271
  success: true,
@@ -1022,78 +1294,100 @@ function wrapGenerateObject(originalFn, client) {
1022
1294
  }
1023
1295
  };
1024
1296
  }
1025
- function wrapStreamObject(originalFn, client) {
1297
+ function wrapStreamObject(originalFn, client, config) {
1026
1298
  return (params) => {
1027
1299
  const startTime = Date.now();
1028
1300
  const { modelId, provider } = extractModelInfo(params.model);
1029
1301
  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"
1302
+ const sessionPromise = (async () => {
1303
+ try {
1304
+ const id = await client.createSession({
1305
+ name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1306
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1307
+ userId: config.userId ?? "anonymous",
1308
+ convoId: config.convoId,
1309
+ metadata: {
1310
+ model: modelId,
1311
+ provider,
1312
+ function: "streamObject"
1313
+ }
1314
+ });
1315
+ if (id) {
1316
+ client.setInput(id, input).catch(() => {
1317
+ });
1318
+ }
1319
+ return id;
1320
+ } catch {
1321
+ return null;
1038
1322
  }
1039
- }).then((id) => {
1040
- if (id) client.setInput(id, input);
1041
- return id;
1042
- });
1323
+ })();
1043
1324
  const result = originalFn(params);
1044
- if (result.object) {
1045
- const originalObjectPromise = result.object;
1046
- result.object = originalObjectPromise.then(async (obj) => {
1325
+ const objectProp = result.object;
1326
+ if (objectProp && typeof objectProp.then === "function") {
1327
+ objectProp.then(async (obj) => {
1047
1328
  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
- });
1329
+ const sid = await sessionPromise;
1330
+ if (!sid) return;
1331
+ let usage;
1332
+ try {
1333
+ usage = result.usage ? await result.usage : void 0;
1334
+ } catch {
1065
1335
  }
1066
- return obj;
1336
+ const promptTokens = usage?.promptTokens || 0;
1337
+ const completionTokens = usage?.completionTokens || 0;
1338
+ const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
1339
+ const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
1340
+ await client.completeSession({
1341
+ sessionId: sid,
1342
+ success: true,
1343
+ output: JSON.stringify(obj),
1344
+ durationMs,
1345
+ estimatedCost: cost,
1346
+ promptTokens,
1347
+ completionTokens,
1348
+ totalTokens
1349
+ });
1067
1350
  }).catch(async (error) => {
1068
1351
  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;
1352
+ const sid = await sessionPromise;
1353
+ if (!sid) return;
1354
+ await client.trackError({
1355
+ sessionId: sid,
1356
+ errorType: error instanceof Error ? error.name : "Error",
1357
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
1358
+ });
1359
+ await client.completeSession({
1360
+ sessionId: sid,
1361
+ success: false,
1362
+ failureReason: error instanceof Error ? error.message : "Unknown error",
1363
+ durationMs
1364
+ });
1084
1365
  });
1085
1366
  }
1086
1367
  return result;
1087
1368
  };
1088
1369
  }
1089
- function wrapAISDK(ai) {
1090
- const client = getClient2();
1370
+ function wrapAISDK(ai, options) {
1371
+ const client = options?.client ?? getClient2();
1372
+ const config = {
1373
+ defaultAgent: options?.defaultAgent ?? _globalConfig.defaultAgent,
1374
+ userId: options?.userId ?? _globalConfig.userId,
1375
+ convoId: options?.convoId ?? _globalConfig.convoId
1376
+ };
1091
1377
  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)
1378
+ generateText: ai.generateText ? wrapGenerateText(ai.generateText, client, config) : wrapGenerateText(
1379
+ () => Promise.reject(new Error("generateText not available")),
1380
+ client,
1381
+ config
1382
+ ),
1383
+ streamText: ai.streamText ? wrapStreamText(ai.streamText, client, config) : wrapStreamText(() => ({ textStream: (async function* () {
1384
+ })() }), client, config),
1385
+ generateObject: ai.generateObject ? wrapGenerateObject(ai.generateObject, client, config) : wrapGenerateObject(
1386
+ () => Promise.reject(new Error("generateObject not available")),
1387
+ client,
1388
+ config
1389
+ ),
1390
+ streamObject: ai.streamObject ? wrapStreamObject(ai.streamObject, client, config) : wrapStreamObject(() => ({}), client, config)
1097
1391
  };
1098
1392
  }
1099
1393
 
@@ -2138,7 +2432,12 @@ var Experiment = class {
2138
2432
  getSessionContext,
2139
2433
  getSystemPrompt,
2140
2434
  getVariantName,
2435
+ hashValue,
2141
2436
  isExperimentMode,
2437
+ redactPayload,
2438
+ redactString,
2439
+ redactValue,
2440
+ replaceMatch,
2142
2441
  sentrial,
2143
2442
  setClient,
2144
2443
  setDefaultClient,