@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.js CHANGED
@@ -67,26 +67,126 @@ var ValidationError = class extends SentrialError {
67
67
  }
68
68
  };
69
69
 
70
+ // src/redact.ts
71
+ import { createHash } from "crypto";
72
+ var DEFAULT_FIELDS = [
73
+ "userInput",
74
+ "assistantOutput",
75
+ "toolInput",
76
+ "toolOutput"
77
+ ];
78
+ var DEFAULT_BUILTIN = {
79
+ emails: true,
80
+ phones: true,
81
+ ssns: true,
82
+ creditCards: true,
83
+ ipAddresses: true
84
+ };
85
+ var EMAIL_PATTERN = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
86
+ var PHONE_PATTERN = /(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g;
87
+ var SSN_PATTERN = /\b\d{3}-\d{2}-\d{4}\b/g;
88
+ var CREDIT_CARD_PATTERN = /\b(?:\d[-\s]?){13,19}\b/g;
89
+ 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;
90
+ var BUILTIN_PATTERNS = {
91
+ emails: { pattern: EMAIL_PATTERN, label: "EMAIL" },
92
+ phones: { pattern: PHONE_PATTERN, label: "PHONE" },
93
+ ssns: { pattern: SSN_PATTERN, label: "SSN" },
94
+ creditCards: { pattern: CREDIT_CARD_PATTERN, label: "CREDIT_CARD" },
95
+ ipAddresses: { pattern: IP_ADDRESS_PATTERN, label: "IP_ADDRESS" }
96
+ };
97
+ function hashValue(value) {
98
+ return createHash("sha256").update(value).digest("hex").slice(0, 6);
99
+ }
100
+ function replaceMatch(match, label, mode) {
101
+ switch (mode) {
102
+ case "label":
103
+ return `[${label}]`;
104
+ case "hash":
105
+ return `[PII:${hashValue(match)}]`;
106
+ case "remove":
107
+ return "";
108
+ }
109
+ }
110
+ function redactString(value, mode, builtinPatterns, customPatterns) {
111
+ let result = value;
112
+ for (const [key, config] of Object.entries(BUILTIN_PATTERNS)) {
113
+ if (builtinPatterns[key]) {
114
+ const regex = new RegExp(config.pattern.source, config.pattern.flags);
115
+ result = result.replace(regex, (match) => replaceMatch(match, config.label, mode));
116
+ }
117
+ }
118
+ for (const custom of customPatterns) {
119
+ const regex = new RegExp(custom.pattern.source, custom.pattern.flags);
120
+ result = result.replace(regex, (match) => replaceMatch(match, custom.label, mode));
121
+ }
122
+ return result;
123
+ }
124
+ function redactValue(value, mode, builtinPatterns, customPatterns) {
125
+ if (typeof value === "string") {
126
+ return redactString(value, mode, builtinPatterns, customPatterns);
127
+ }
128
+ if (Array.isArray(value)) {
129
+ return value.map((item) => redactValue(item, mode, builtinPatterns, customPatterns));
130
+ }
131
+ if (value !== null && typeof value === "object") {
132
+ const result = {};
133
+ for (const [key, val] of Object.entries(value)) {
134
+ result[key] = redactValue(val, mode, builtinPatterns, customPatterns);
135
+ }
136
+ return result;
137
+ }
138
+ return value;
139
+ }
140
+ function redactPayload(payload, config) {
141
+ if (!config.enabled) {
142
+ return payload;
143
+ }
144
+ const mode = config.mode ?? "label";
145
+ const fields = config.fields ?? DEFAULT_FIELDS;
146
+ const builtinPatterns = {
147
+ ...DEFAULT_BUILTIN,
148
+ ...config.builtinPatterns
149
+ };
150
+ const customPatterns = config.customPatterns ?? [];
151
+ const result = { ...payload };
152
+ for (const field of fields) {
153
+ if (field in result && result[field] !== void 0 && result[field] !== null) {
154
+ result[field] = redactValue(result[field], mode, builtinPatterns, customPatterns);
155
+ }
156
+ }
157
+ return result;
158
+ }
159
+
70
160
  // src/cost.ts
71
161
  var OPENAI_PRICING = {
72
162
  "gpt-5.2": { input: 5, output: 15 },
73
163
  "gpt-5": { input: 4, output: 12 },
164
+ "gpt-4.1": { input: 2, output: 8 },
165
+ "gpt-4.1-mini": { input: 0.4, output: 1.6 },
166
+ "gpt-4.1-nano": { input: 0.1, output: 0.4 },
74
167
  "gpt-4o": { input: 2.5, output: 10 },
75
168
  "gpt-4o-mini": { input: 0.15, output: 0.6 },
76
169
  "gpt-4-turbo": { input: 10, output: 30 },
77
170
  "gpt-4": { input: 30, output: 60 },
78
171
  "gpt-3.5-turbo": { input: 0.5, output: 1.5 },
172
+ "o4-mini": { input: 1.1, output: 4.4 },
79
173
  "o3": { input: 10, output: 40 },
80
- "o3-mini": { input: 3, output: 12 },
174
+ "o3-mini": { input: 1.1, output: 4.4 },
175
+ "o3-pro": { input: 20, output: 80 },
176
+ "o1": { input: 15, output: 60 },
81
177
  "o1-preview": { input: 15, output: 60 },
82
178
  "o1-mini": { input: 3, output: 12 }
83
179
  };
84
180
  var ANTHROPIC_PRICING = {
181
+ "claude-opus-4": { input: 15, output: 75 },
182
+ "claude-sonnet-4": { input: 3, output: 15 },
85
183
  "claude-4.5-opus": { input: 20, output: 100 },
86
184
  "claude-4.5-sonnet": { input: 4, output: 20 },
87
185
  "claude-4-opus": { input: 18, output: 90 },
88
186
  "claude-4-sonnet": { input: 3.5, output: 17.5 },
187
+ "claude-3-7-sonnet": { input: 3, output: 15 },
89
188
  "claude-3-5-sonnet": { input: 3, output: 15 },
189
+ "claude-3-5-haiku": { input: 0.8, output: 4 },
90
190
  "claude-3-opus": { input: 15, output: 75 },
91
191
  "claude-3-sonnet": { input: 3, output: 15 },
92
192
  "claude-3-haiku": { input: 0.25, output: 1.25 }
@@ -157,11 +257,75 @@ var SentrialClient = class {
157
257
  apiUrl;
158
258
  apiKey;
159
259
  failSilently;
260
+ piiConfig;
261
+ piiConfigNeedsHydration = false;
262
+ piiHydrationPromise;
160
263
  currentState = {};
161
264
  constructor(config = {}) {
162
265
  this.apiUrl = (config.apiUrl ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_URL : void 0) ?? DEFAULT_API_URL).replace(/\/$/, "");
163
266
  this.apiKey = config.apiKey ?? (typeof process !== "undefined" ? process.env?.SENTRIAL_API_KEY : void 0);
164
267
  this.failSilently = config.failSilently ?? true;
268
+ if (config.pii === true) {
269
+ this.piiConfig = { enabled: true };
270
+ this.piiConfigNeedsHydration = true;
271
+ } else if (config.pii && typeof config.pii === "object") {
272
+ this.piiConfig = config.pii;
273
+ this.piiConfigNeedsHydration = false;
274
+ }
275
+ }
276
+ /**
277
+ * Fetch the organization's PII config from the server.
278
+ *
279
+ * Called lazily on the first request when `pii: true` was passed to the constructor.
280
+ * Uses a single shared promise so concurrent requests don't trigger duplicate fetches.
281
+ */
282
+ async hydratePiiConfig() {
283
+ if (!this.piiConfigNeedsHydration) return;
284
+ if (this.piiHydrationPromise) {
285
+ await this.piiHydrationPromise;
286
+ return;
287
+ }
288
+ this.piiHydrationPromise = (async () => {
289
+ try {
290
+ const headers = {};
291
+ if (this.apiKey) {
292
+ headers["Authorization"] = `Bearer ${this.apiKey}`;
293
+ }
294
+ const controller = new AbortController();
295
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
296
+ let response;
297
+ try {
298
+ response = await fetch(`${this.apiUrl}/api/sdk/pii-config`, {
299
+ method: "GET",
300
+ headers,
301
+ signal: controller.signal
302
+ });
303
+ } finally {
304
+ clearTimeout(timeoutId);
305
+ }
306
+ if (response.ok) {
307
+ const data = await response.json();
308
+ if (data.config) {
309
+ this.piiConfig = {
310
+ enabled: data.config.enabled,
311
+ mode: data.config.mode,
312
+ fields: data.config.fields,
313
+ builtinPatterns: data.config.builtinPatterns,
314
+ customPatterns: (data.config.customPatterns || []).map(
315
+ (cp) => ({
316
+ pattern: new RegExp(cp.pattern, "g"),
317
+ label: cp.label
318
+ })
319
+ ),
320
+ enhancedDetection: data.config.enhancedDetection
321
+ };
322
+ }
323
+ }
324
+ } catch {
325
+ }
326
+ this.piiConfigNeedsHydration = false;
327
+ })();
328
+ await this.piiHydrationPromise;
165
329
  }
166
330
  /**
167
331
  * Make an HTTP request with retry logic and exponential backoff.
@@ -170,6 +334,9 @@ var SentrialClient = class {
170
334
  * Up to MAX_RETRIES attempts with exponential backoff.
171
335
  */
172
336
  async safeRequest(method, url, body) {
337
+ if (this.piiConfigNeedsHydration) {
338
+ await this.hydratePiiConfig();
339
+ }
173
340
  let lastError;
174
341
  let backoff = INITIAL_BACKOFF_MS;
175
342
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
@@ -180,6 +347,7 @@ var SentrialClient = class {
180
347
  if (this.apiKey) {
181
348
  headers["Authorization"] = `Bearer ${this.apiKey}`;
182
349
  }
350
+ const finalBody = this.piiConfig && body && typeof body === "object" ? redactPayload(body, this.piiConfig) : body;
183
351
  const controller = new AbortController();
184
352
  const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
185
353
  let response;
@@ -187,7 +355,7 @@ var SentrialClient = class {
187
355
  response = await fetch(url, {
188
356
  method,
189
357
  headers,
190
- body: body ? JSON.stringify(body) : void 0,
358
+ body: finalBody ? JSON.stringify(finalBody) : void 0,
191
359
  signal: controller.signal
192
360
  });
193
361
  } finally {
@@ -259,6 +427,9 @@ var SentrialClient = class {
259
427
  if (params.parentSessionId) {
260
428
  payload.parentSessionId = params.parentSessionId;
261
429
  }
430
+ if (params.convoId) {
431
+ payload.convoId = params.convoId;
432
+ }
262
433
  const response = await this.safeRequest(
263
434
  "POST",
264
435
  `${this.apiUrl}/api/sdk/sessions`,
@@ -465,6 +636,7 @@ var SentrialClient = class {
465
636
  name: `${params.event}:${eventId.slice(0, 8)}`,
466
637
  agentName: params.event,
467
638
  userId: params.userId,
639
+ convoId: params.convoId,
468
640
  metadata: fullMetadata
469
641
  });
470
642
  if (params.input) {
@@ -629,7 +801,8 @@ function configureVercel(config) {
629
801
  });
630
802
  _globalConfig = {
631
803
  defaultAgent: config.defaultAgent,
632
- userId: config.userId
804
+ userId: config.userId,
805
+ convoId: config.convoId
633
806
  };
634
807
  }
635
808
  function getClient2() {
@@ -644,11 +817,15 @@ function extractModelInfo(model) {
644
817
  return { modelId, provider };
645
818
  }
646
819
  function guessProvider(modelId) {
647
- if (modelId.includes("gpt") || modelId.includes("o1") || modelId.includes("o3")) return "openai";
648
- if (modelId.includes("claude")) return "anthropic";
649
- if (modelId.includes("gemini")) return "google";
650
- if (modelId.includes("mistral")) return "mistral";
651
- if (modelId.includes("llama")) return "meta";
820
+ const id = modelId.toLowerCase();
821
+ if (id.includes("gpt") || id.includes("o1") || id.includes("o3") || id.includes("o4") || id.startsWith("chatgpt")) return "openai";
822
+ if (id.includes("claude")) return "anthropic";
823
+ if (id.includes("gemini")) return "google";
824
+ if (id.includes("mistral") || id.includes("mixtral") || id.includes("codestral") || id.includes("pixtral")) return "mistral";
825
+ if (id.includes("llama")) return "meta";
826
+ if (id.includes("deepseek")) return "deepseek";
827
+ if (id.includes("command")) return "cohere";
828
+ if (id.includes("qwen")) return "alibaba";
652
829
  return "unknown";
653
830
  }
654
831
  function calculateCostForCall(provider, modelId, promptTokens, completionTokens) {
@@ -660,6 +837,12 @@ function calculateCostForCall(provider, modelId, promptTokens, completionTokens)
660
837
  return calculateAnthropicCost(params);
661
838
  case "google":
662
839
  return calculateGoogleCost(params);
840
+ case "deepseek":
841
+ return promptTokens / 1e6 * 0.27 + completionTokens / 1e6 * 1.1;
842
+ case "cohere":
843
+ return promptTokens / 1e6 * 0.5 + completionTokens / 1e6 * 1.5;
844
+ case "mistral":
845
+ return promptTokens / 1e6 * 2 + completionTokens / 1e6 * 6;
663
846
  default:
664
847
  return promptTokens * 3e-6 + completionTokens * 6e-6;
665
848
  }
@@ -668,7 +851,10 @@ function extractInput(params) {
668
851
  if (params.prompt) return params.prompt;
669
852
  if (params.messages && params.messages.length > 0) {
670
853
  const lastUserMessage = [...params.messages].reverse().find((m) => m.role === "user");
671
- return lastUserMessage?.content || JSON.stringify(params.messages);
854
+ if (lastUserMessage) {
855
+ return typeof lastUserMessage.content === "string" ? lastUserMessage.content : JSON.stringify(lastUserMessage.content);
856
+ }
857
+ return JSON.stringify(params.messages);
672
858
  }
673
859
  return "";
674
860
  }
@@ -685,23 +871,25 @@ function wrapTools(tools, sessionId, client) {
685
871
  try {
686
872
  const result = await originalExecute(...args);
687
873
  const durationMs = Date.now() - startTime;
688
- await client.trackToolCall({
874
+ client.trackToolCall({
689
875
  sessionId,
690
876
  toolName,
691
877
  toolInput: args[0],
692
878
  toolOutput: result,
693
879
  reasoning: `Tool executed in ${durationMs}ms`
880
+ }).catch(() => {
694
881
  });
695
882
  return result;
696
883
  } catch (error) {
697
884
  const durationMs = Date.now() - startTime;
698
- await client.trackToolCall({
885
+ client.trackToolCall({
699
886
  sessionId,
700
887
  toolName,
701
888
  toolInput: args[0],
702
889
  toolOutput: {},
703
890
  toolError: { message: error instanceof Error ? error.message : "Unknown error" },
704
891
  reasoning: `Tool failed after ${durationMs}ms`
892
+ }).catch(() => {
705
893
  });
706
894
  throw error;
707
895
  }
@@ -713,19 +901,68 @@ function wrapTools(tools, sessionId, client) {
713
901
  }
714
902
  return wrappedTools;
715
903
  }
716
- function wrapGenerateText(originalFn, client) {
904
+ function wrapToolsAsync(tools, sessionPromise, client) {
905
+ const wrappedTools = {};
906
+ for (const [toolName, tool] of Object.entries(tools)) {
907
+ if (typeof tool.execute === "function") {
908
+ const originalExecute = tool.execute;
909
+ wrappedTools[toolName] = {
910
+ ...tool,
911
+ execute: async (...args) => {
912
+ const startTime = Date.now();
913
+ const sid = await sessionPromise;
914
+ try {
915
+ const result = await originalExecute(...args);
916
+ const durationMs = Date.now() - startTime;
917
+ if (sid) {
918
+ client.trackToolCall({
919
+ sessionId: sid,
920
+ toolName,
921
+ toolInput: args[0],
922
+ toolOutput: result,
923
+ reasoning: `Tool executed in ${durationMs}ms`
924
+ }).catch(() => {
925
+ });
926
+ }
927
+ return result;
928
+ } catch (error) {
929
+ const durationMs = Date.now() - startTime;
930
+ if (sid) {
931
+ client.trackToolCall({
932
+ sessionId: sid,
933
+ toolName,
934
+ toolInput: args[0],
935
+ toolOutput: {},
936
+ toolError: { message: error instanceof Error ? error.message : "Unknown error" },
937
+ reasoning: `Tool failed after ${durationMs}ms`
938
+ }).catch(() => {
939
+ });
940
+ }
941
+ throw error;
942
+ }
943
+ }
944
+ };
945
+ } else {
946
+ wrappedTools[toolName] = tool;
947
+ }
948
+ }
949
+ return wrappedTools;
950
+ }
951
+ function wrapGenerateText(originalFn, client, config) {
717
952
  return async (params) => {
718
953
  const startTime = Date.now();
719
954
  const { modelId, provider } = extractModelInfo(params.model);
720
955
  const input = extractInput(params);
721
956
  const sessionId = await client.createSession({
722
957
  name: `generateText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
723
- agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
724
- userId: _globalConfig.userId ?? "anonymous",
958
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
959
+ userId: config.userId ?? "anonymous",
960
+ convoId: config.convoId,
725
961
  metadata: {
726
962
  model: modelId,
727
963
  provider,
728
- function: "generateText"
964
+ function: "generateText",
965
+ ...params.maxSteps ? { maxSteps: params.maxSteps } : {}
729
966
  }
730
967
  });
731
968
  if (!sessionId) {
@@ -739,23 +976,46 @@ function wrapGenerateText(originalFn, client) {
739
976
  try {
740
977
  const result = await originalFn(wrappedParams);
741
978
  const durationMs = Date.now() - startTime;
979
+ const resolvedModelId = result.response?.modelId || modelId;
742
980
  const promptTokens = result.usage?.promptTokens || 0;
743
981
  const completionTokens = result.usage?.completionTokens || 0;
744
982
  const totalTokens = result.usage?.totalTokens || promptTokens + completionTokens;
745
- const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
746
- await client.trackEvent({
747
- sessionId,
748
- eventType: "llm_call",
749
- eventData: {
750
- model: modelId,
751
- provider,
752
- prompt_tokens: promptTokens,
753
- completion_tokens: completionTokens,
754
- total_tokens: totalTokens,
755
- finish_reason: result.finishReason,
756
- tool_calls: result.toolCalls?.map((tc) => tc.toolName)
983
+ const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
984
+ const steps = result.steps;
985
+ if (steps && steps.length >= 1) {
986
+ for (let i = 0; i < steps.length; i++) {
987
+ const step = steps[i];
988
+ await client.trackEvent({
989
+ sessionId,
990
+ eventType: "llm_call",
991
+ eventData: {
992
+ model: resolvedModelId,
993
+ provider,
994
+ step: i + 1,
995
+ total_steps: steps.length,
996
+ prompt_tokens: step.usage?.promptTokens || 0,
997
+ completion_tokens: step.usage?.completionTokens || 0,
998
+ total_tokens: step.usage?.totalTokens || 0,
999
+ finish_reason: step.finishReason,
1000
+ tool_calls: step.toolCalls?.map((tc) => tc.toolName)
1001
+ }
1002
+ });
757
1003
  }
758
- });
1004
+ } else {
1005
+ await client.trackEvent({
1006
+ sessionId,
1007
+ eventType: "llm_call",
1008
+ eventData: {
1009
+ model: resolvedModelId,
1010
+ provider,
1011
+ prompt_tokens: promptTokens,
1012
+ completion_tokens: completionTokens,
1013
+ total_tokens: totalTokens,
1014
+ finish_reason: result.finishReason,
1015
+ tool_calls: result.toolCalls?.map((tc) => tc.toolName)
1016
+ }
1017
+ });
1018
+ }
759
1019
  await client.completeSession({
760
1020
  sessionId,
761
1021
  success: true,
@@ -784,134 +1044,139 @@ function wrapGenerateText(originalFn, client) {
784
1044
  }
785
1045
  };
786
1046
  }
787
- function wrapStreamText(originalFn, client) {
1047
+ function wrapStreamText(originalFn, client, config) {
788
1048
  return (params) => {
789
1049
  const startTime = Date.now();
790
1050
  const { modelId, provider } = extractModelInfo(params.model);
791
1051
  const input = extractInput(params);
792
1052
  let sessionId = null;
793
- const sessionPromise = client.createSession({
794
- name: `streamText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
795
- agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
796
- userId: _globalConfig.userId ?? "anonymous",
797
- metadata: {
798
- model: modelId,
799
- provider,
800
- function: "streamText"
1053
+ const sessionPromise = (async () => {
1054
+ try {
1055
+ const id = await client.createSession({
1056
+ name: `streamText: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1057
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1058
+ userId: config.userId ?? "anonymous",
1059
+ convoId: config.convoId,
1060
+ metadata: {
1061
+ model: modelId,
1062
+ provider,
1063
+ function: "streamText"
1064
+ }
1065
+ });
1066
+ sessionId = id;
1067
+ if (id) {
1068
+ client.setInput(id, input).catch(() => {
1069
+ });
1070
+ }
1071
+ return id;
1072
+ } catch {
1073
+ return null;
801
1074
  }
802
- }).then((id) => {
803
- sessionId = id;
804
- if (id) client.setInput(id, input);
805
- return id;
806
- });
1075
+ })();
807
1076
  const wrappedParams = {
808
1077
  ...params,
809
1078
  tools: params.tools ? wrapToolsAsync(params.tools, sessionPromise, client) : void 0
810
1079
  };
811
1080
  const result = originalFn(wrappedParams);
812
- const originalTextStream = result.textStream;
813
- let fullText = "";
814
- result.textStream = (async function* () {
1081
+ let tracked = false;
1082
+ async function trackCompletion(fullText, error) {
1083
+ if (tracked) return;
1084
+ tracked = true;
1085
+ const durationMs = Date.now() - startTime;
1086
+ const sid = sessionId || await sessionPromise;
1087
+ if (!sid) return;
1088
+ if (error) {
1089
+ await client.trackError({
1090
+ sessionId: sid,
1091
+ errorType: error.name || "Error",
1092
+ errorMessage: error.message || "Unknown error"
1093
+ });
1094
+ await client.completeSession({
1095
+ sessionId: sid,
1096
+ success: false,
1097
+ failureReason: error.message || "Unknown error",
1098
+ durationMs
1099
+ });
1100
+ return;
1101
+ }
1102
+ let usage;
815
1103
  try {
816
- for await (const chunk of originalTextStream) {
817
- fullText += chunk;
818
- yield chunk;
819
- }
820
- const durationMs = Date.now() - startTime;
821
- const sid = sessionId || await sessionPromise;
822
- if (sid) {
823
- const usage = result.usage ? await result.usage : void 0;
824
- const promptTokens = usage?.promptTokens || 0;
825
- const completionTokens = usage?.completionTokens || 0;
826
- const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
827
- const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
828
- await client.completeSession({
829
- sessionId: sid,
830
- success: true,
831
- output: fullText,
832
- durationMs,
833
- estimatedCost: cost,
834
- promptTokens,
835
- completionTokens,
836
- totalTokens
837
- });
838
- }
839
- } catch (error) {
840
- const durationMs = Date.now() - startTime;
841
- const sid = sessionId || await sessionPromise;
842
- if (sid) {
843
- await client.trackError({
844
- sessionId: sid,
845
- errorType: error instanceof Error ? error.name : "Error",
846
- errorMessage: error instanceof Error ? error.message : "Unknown error"
847
- });
848
- await client.completeSession({
849
- sessionId: sid,
850
- success: false,
851
- failureReason: error instanceof Error ? error.message : "Unknown error",
852
- durationMs
853
- });
854
- }
855
- throw error;
1104
+ usage = result.usage ? await result.usage : void 0;
1105
+ } catch {
856
1106
  }
857
- })();
858
- return result;
859
- };
860
- }
861
- function wrapToolsAsync(tools, sessionPromise, client) {
862
- const wrappedTools = {};
863
- for (const [toolName, tool] of Object.entries(tools)) {
864
- if (typeof tool.execute === "function") {
865
- const originalExecute = tool.execute;
866
- wrappedTools[toolName] = {
867
- ...tool,
868
- execute: async (...args) => {
869
- const startTime = Date.now();
870
- const sessionId = await sessionPromise;
871
- try {
872
- const result = await originalExecute(...args);
873
- const durationMs = Date.now() - startTime;
874
- if (sessionId) {
875
- await client.trackToolCall({
876
- sessionId,
877
- toolName,
878
- toolInput: args[0],
879
- toolOutput: result,
880
- reasoning: `Tool executed in ${durationMs}ms`
881
- });
882
- }
883
- return result;
884
- } catch (error) {
885
- const durationMs = Date.now() - startTime;
886
- if (sessionId) {
887
- await client.trackToolCall({
888
- sessionId,
889
- toolName,
890
- toolInput: args[0],
891
- toolOutput: {},
892
- toolError: { message: error instanceof Error ? error.message : "Unknown error" },
893
- reasoning: `Tool failed after ${durationMs}ms`
894
- });
895
- }
896
- throw error;
897
- }
1107
+ const promptTokens = usage?.promptTokens || 0;
1108
+ const completionTokens = usage?.completionTokens || 0;
1109
+ const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
1110
+ const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
1111
+ await client.trackEvent({
1112
+ sessionId: sid,
1113
+ eventType: "llm_call",
1114
+ eventData: {
1115
+ model: modelId,
1116
+ provider,
1117
+ prompt_tokens: promptTokens,
1118
+ completion_tokens: completionTokens,
1119
+ total_tokens: totalTokens
898
1120
  }
899
- };
1121
+ });
1122
+ await client.completeSession({
1123
+ sessionId: sid,
1124
+ success: true,
1125
+ output: fullText,
1126
+ durationMs,
1127
+ estimatedCost: cost,
1128
+ promptTokens,
1129
+ completionTokens,
1130
+ totalTokens
1131
+ });
1132
+ }
1133
+ const textProp = result.text;
1134
+ if (typeof textProp === "string") {
1135
+ trackCompletion(textProp).catch(() => {
1136
+ });
1137
+ } else if (textProp != null && typeof textProp.then === "function") {
1138
+ const originalTextPromise = textProp;
1139
+ result.text = originalTextPromise.then((text) => {
1140
+ trackCompletion(text).catch(() => {
1141
+ });
1142
+ return text;
1143
+ }).catch((err) => {
1144
+ trackCompletion("", err instanceof Error ? err : new Error(String(err))).catch(() => {
1145
+ });
1146
+ throw err;
1147
+ });
900
1148
  } else {
901
- wrappedTools[toolName] = tool;
1149
+ const originalTextStream = result.textStream;
1150
+ let fullText = "";
1151
+ result.textStream = (async function* () {
1152
+ try {
1153
+ for await (const chunk of originalTextStream) {
1154
+ fullText += chunk;
1155
+ yield chunk;
1156
+ }
1157
+ await trackCompletion(fullText);
1158
+ } catch (error) {
1159
+ await trackCompletion(
1160
+ "",
1161
+ error instanceof Error ? error : new Error(String(error))
1162
+ );
1163
+ throw error;
1164
+ }
1165
+ })();
902
1166
  }
903
- }
904
- return wrappedTools;
1167
+ return result;
1168
+ };
905
1169
  }
906
- function wrapGenerateObject(originalFn, client) {
1170
+ function wrapGenerateObject(originalFn, client, config) {
907
1171
  return async (params) => {
908
1172
  const startTime = Date.now();
909
1173
  const { modelId, provider } = extractModelInfo(params.model);
910
1174
  const input = extractInput(params);
911
1175
  const sessionId = await client.createSession({
912
1176
  name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
913
- agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
914
- userId: _globalConfig.userId ?? "anonymous",
1177
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1178
+ userId: config.userId ?? "anonymous",
1179
+ convoId: config.convoId,
915
1180
  metadata: {
916
1181
  model: modelId,
917
1182
  provider,
@@ -925,10 +1190,11 @@ function wrapGenerateObject(originalFn, client) {
925
1190
  try {
926
1191
  const result = await originalFn(params);
927
1192
  const durationMs = Date.now() - startTime;
1193
+ const resolvedModelId = result.response?.modelId || modelId;
928
1194
  const promptTokens = result.usage?.promptTokens || 0;
929
1195
  const completionTokens = result.usage?.completionTokens || 0;
930
1196
  const totalTokens = result.usage?.totalTokens || promptTokens + completionTokens;
931
- const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
1197
+ const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
932
1198
  await client.completeSession({
933
1199
  sessionId,
934
1200
  success: true,
@@ -957,38 +1223,51 @@ function wrapGenerateObject(originalFn, client) {
957
1223
  }
958
1224
  };
959
1225
  }
960
- function wrapStreamObject(originalFn, client) {
1226
+ function wrapStreamObject(originalFn, client, config) {
961
1227
  return (params) => {
962
1228
  const startTime = Date.now();
963
1229
  const { modelId, provider } = extractModelInfo(params.model);
964
1230
  const input = extractInput(params);
965
- const sessionPromise = client.createSession({
966
- name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
967
- agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
968
- userId: _globalConfig.userId ?? "anonymous",
969
- metadata: {
970
- model: modelId,
971
- provider,
972
- function: "streamObject"
1231
+ const sessionPromise = (async () => {
1232
+ try {
1233
+ const id = await client.createSession({
1234
+ name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1235
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1236
+ userId: config.userId ?? "anonymous",
1237
+ convoId: config.convoId,
1238
+ metadata: {
1239
+ model: modelId,
1240
+ provider,
1241
+ function: "streamObject"
1242
+ }
1243
+ });
1244
+ if (id) {
1245
+ client.setInput(id, input).catch(() => {
1246
+ });
1247
+ }
1248
+ return id;
1249
+ } catch {
1250
+ return null;
973
1251
  }
974
- }).then((id) => {
975
- if (id) client.setInput(id, input);
976
- return id;
977
- });
1252
+ })();
978
1253
  const result = originalFn(params);
979
1254
  if (result.object) {
980
1255
  const originalObjectPromise = result.object;
981
1256
  result.object = originalObjectPromise.then(async (obj) => {
982
1257
  const durationMs = Date.now() - startTime;
983
- const sessionId = await sessionPromise;
984
- if (sessionId) {
985
- const usage = result.usage ? await result.usage : void 0;
1258
+ const sid = await sessionPromise;
1259
+ if (sid) {
1260
+ let usage;
1261
+ try {
1262
+ usage = result.usage ? await result.usage : void 0;
1263
+ } catch {
1264
+ }
986
1265
  const promptTokens = usage?.promptTokens || 0;
987
1266
  const completionTokens = usage?.completionTokens || 0;
988
1267
  const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
989
1268
  const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
990
1269
  await client.completeSession({
991
- sessionId,
1270
+ sessionId: sid,
992
1271
  success: true,
993
1272
  output: JSON.stringify(obj),
994
1273
  durationMs,
@@ -1001,15 +1280,15 @@ function wrapStreamObject(originalFn, client) {
1001
1280
  return obj;
1002
1281
  }).catch(async (error) => {
1003
1282
  const durationMs = Date.now() - startTime;
1004
- const sessionId = await sessionPromise;
1005
- if (sessionId) {
1283
+ const sid = await sessionPromise;
1284
+ if (sid) {
1006
1285
  await client.trackError({
1007
- sessionId,
1286
+ sessionId: sid,
1008
1287
  errorType: error instanceof Error ? error.name : "Error",
1009
1288
  errorMessage: error instanceof Error ? error.message : "Unknown error"
1010
1289
  });
1011
1290
  await client.completeSession({
1012
- sessionId,
1291
+ sessionId: sid,
1013
1292
  success: false,
1014
1293
  failureReason: error instanceof Error ? error.message : "Unknown error",
1015
1294
  durationMs
@@ -1021,14 +1300,27 @@ function wrapStreamObject(originalFn, client) {
1021
1300
  return result;
1022
1301
  };
1023
1302
  }
1024
- function wrapAISDK(ai) {
1025
- const client = getClient2();
1303
+ function wrapAISDK(ai, options) {
1304
+ const client = options?.client ?? getClient2();
1305
+ const config = {
1306
+ defaultAgent: options?.defaultAgent ?? _globalConfig.defaultAgent,
1307
+ userId: options?.userId ?? _globalConfig.userId,
1308
+ convoId: options?.convoId ?? _globalConfig.convoId
1309
+ };
1026
1310
  return {
1027
- generateText: ai.generateText ? wrapGenerateText(ai.generateText, client) : wrapGenerateText(() => Promise.reject(new Error("generateText not available")), client),
1028
- streamText: ai.streamText ? wrapStreamText(ai.streamText, client) : wrapStreamText(() => ({ textStream: (async function* () {
1029
- })() }), client),
1030
- generateObject: ai.generateObject ? wrapGenerateObject(ai.generateObject, client) : wrapGenerateObject(() => Promise.reject(new Error("generateObject not available")), client),
1031
- streamObject: ai.streamObject ? wrapStreamObject(ai.streamObject, client) : wrapStreamObject(() => ({}), client)
1311
+ generateText: ai.generateText ? wrapGenerateText(ai.generateText, client, config) : wrapGenerateText(
1312
+ () => Promise.reject(new Error("generateText not available")),
1313
+ client,
1314
+ config
1315
+ ),
1316
+ streamText: ai.streamText ? wrapStreamText(ai.streamText, client, config) : wrapStreamText(() => ({ textStream: (async function* () {
1317
+ })() }), client, config),
1318
+ generateObject: ai.generateObject ? wrapGenerateObject(ai.generateObject, client, config) : wrapGenerateObject(
1319
+ () => Promise.reject(new Error("generateObject not available")),
1320
+ client,
1321
+ config
1322
+ ),
1323
+ streamObject: ai.streamObject ? wrapStreamObject(ai.streamObject, client, config) : wrapStreamObject(() => ({}), client, config)
1032
1324
  };
1033
1325
  }
1034
1326
 
@@ -2072,7 +2364,12 @@ export {
2072
2364
  getSessionContext,
2073
2365
  getSystemPrompt,
2074
2366
  getVariantName,
2367
+ hashValue,
2075
2368
  isExperimentMode,
2369
+ redactPayload,
2370
+ redactString,
2371
+ redactValue,
2372
+ replaceMatch,
2076
2373
  sentrial,
2077
2374
  setClient,
2078
2375
  setDefaultClient,