@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.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,140 @@ 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;
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
1120
+ }
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
+ textProp.then((text) => {
1139
+ trackCompletion(text).catch(() => {
1140
+ });
1141
+ }).catch((err) => {
1142
+ trackCompletion("", err instanceof Error ? err : new Error(String(err))).catch(() => {
1143
+ });
1144
+ });
1145
+ } else {
1146
+ const desc = Object.getOwnPropertyDescriptor(result, "textStream") ?? Object.getOwnPropertyDescriptor(Object.getPrototypeOf(result), "textStream");
1147
+ const isWritable = !desc || desc.writable || desc.set;
1148
+ if (isWritable) {
1149
+ const originalTextStream = result.textStream;
1150
+ let fullText = "";
1151
+ result.textStream = (async function* () {
871
1152
  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
- });
1153
+ for await (const chunk of originalTextStream) {
1154
+ fullText += chunk;
1155
+ yield chunk;
882
1156
  }
883
- return result;
1157
+ await trackCompletion(fullText);
884
1158
  } 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
- }
1159
+ await trackCompletion(
1160
+ "",
1161
+ error instanceof Error ? error : new Error(String(error))
1162
+ );
896
1163
  throw error;
897
1164
  }
898
- }
899
- };
900
- } else {
901
- wrappedTools[toolName] = tool;
1165
+ })();
1166
+ }
902
1167
  }
903
- }
904
- return wrappedTools;
1168
+ return result;
1169
+ };
905
1170
  }
906
- function wrapGenerateObject(originalFn, client) {
1171
+ function wrapGenerateObject(originalFn, client, config) {
907
1172
  return async (params) => {
908
1173
  const startTime = Date.now();
909
1174
  const { modelId, provider } = extractModelInfo(params.model);
910
1175
  const input = extractInput(params);
911
1176
  const sessionId = await client.createSession({
912
1177
  name: `generateObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
913
- agentName: _globalConfig.defaultAgent ?? "vercel-ai-sdk",
914
- userId: _globalConfig.userId ?? "anonymous",
1178
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1179
+ userId: config.userId ?? "anonymous",
1180
+ convoId: config.convoId,
915
1181
  metadata: {
916
1182
  model: modelId,
917
1183
  provider,
@@ -925,10 +1191,11 @@ function wrapGenerateObject(originalFn, client) {
925
1191
  try {
926
1192
  const result = await originalFn(params);
927
1193
  const durationMs = Date.now() - startTime;
1194
+ const resolvedModelId = result.response?.modelId || modelId;
928
1195
  const promptTokens = result.usage?.promptTokens || 0;
929
1196
  const completionTokens = result.usage?.completionTokens || 0;
930
1197
  const totalTokens = result.usage?.totalTokens || promptTokens + completionTokens;
931
- const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
1198
+ const cost = calculateCostForCall(provider, resolvedModelId, promptTokens, completionTokens);
932
1199
  await client.completeSession({
933
1200
  sessionId,
934
1201
  success: true,
@@ -957,78 +1224,100 @@ function wrapGenerateObject(originalFn, client) {
957
1224
  }
958
1225
  };
959
1226
  }
960
- function wrapStreamObject(originalFn, client) {
1227
+ function wrapStreamObject(originalFn, client, config) {
961
1228
  return (params) => {
962
1229
  const startTime = Date.now();
963
1230
  const { modelId, provider } = extractModelInfo(params.model);
964
1231
  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"
1232
+ const sessionPromise = (async () => {
1233
+ try {
1234
+ const id = await client.createSession({
1235
+ name: `streamObject: ${input.slice(0, 50)}${input.length > 50 ? "..." : ""}`,
1236
+ agentName: config.defaultAgent ?? "vercel-ai-sdk",
1237
+ userId: config.userId ?? "anonymous",
1238
+ convoId: config.convoId,
1239
+ metadata: {
1240
+ model: modelId,
1241
+ provider,
1242
+ function: "streamObject"
1243
+ }
1244
+ });
1245
+ if (id) {
1246
+ client.setInput(id, input).catch(() => {
1247
+ });
1248
+ }
1249
+ return id;
1250
+ } catch {
1251
+ return null;
973
1252
  }
974
- }).then((id) => {
975
- if (id) client.setInput(id, input);
976
- return id;
977
- });
1253
+ })();
978
1254
  const result = originalFn(params);
979
- if (result.object) {
980
- const originalObjectPromise = result.object;
981
- result.object = originalObjectPromise.then(async (obj) => {
1255
+ const objectProp = result.object;
1256
+ if (objectProp && typeof objectProp.then === "function") {
1257
+ objectProp.then(async (obj) => {
982
1258
  const durationMs = Date.now() - startTime;
983
- const sessionId = await sessionPromise;
984
- if (sessionId) {
985
- const usage = result.usage ? await result.usage : void 0;
986
- const promptTokens = usage?.promptTokens || 0;
987
- const completionTokens = usage?.completionTokens || 0;
988
- const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
989
- const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
990
- await client.completeSession({
991
- sessionId,
992
- success: true,
993
- output: JSON.stringify(obj),
994
- durationMs,
995
- estimatedCost: cost,
996
- promptTokens,
997
- completionTokens,
998
- totalTokens
999
- });
1259
+ const sid = await sessionPromise;
1260
+ if (!sid) return;
1261
+ let usage;
1262
+ try {
1263
+ usage = result.usage ? await result.usage : void 0;
1264
+ } catch {
1000
1265
  }
1001
- return obj;
1266
+ const promptTokens = usage?.promptTokens || 0;
1267
+ const completionTokens = usage?.completionTokens || 0;
1268
+ const totalTokens = usage?.totalTokens || promptTokens + completionTokens;
1269
+ const cost = calculateCostForCall(provider, modelId, promptTokens, completionTokens);
1270
+ await client.completeSession({
1271
+ sessionId: sid,
1272
+ success: true,
1273
+ output: JSON.stringify(obj),
1274
+ durationMs,
1275
+ estimatedCost: cost,
1276
+ promptTokens,
1277
+ completionTokens,
1278
+ totalTokens
1279
+ });
1002
1280
  }).catch(async (error) => {
1003
1281
  const durationMs = Date.now() - startTime;
1004
- const sessionId = await sessionPromise;
1005
- if (sessionId) {
1006
- await client.trackError({
1007
- sessionId,
1008
- errorType: error instanceof Error ? error.name : "Error",
1009
- errorMessage: error instanceof Error ? error.message : "Unknown error"
1010
- });
1011
- await client.completeSession({
1012
- sessionId,
1013
- success: false,
1014
- failureReason: error instanceof Error ? error.message : "Unknown error",
1015
- durationMs
1016
- });
1017
- }
1018
- throw error;
1282
+ const sid = await sessionPromise;
1283
+ if (!sid) return;
1284
+ await client.trackError({
1285
+ sessionId: sid,
1286
+ errorType: error instanceof Error ? error.name : "Error",
1287
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
1288
+ });
1289
+ await client.completeSession({
1290
+ sessionId: sid,
1291
+ success: false,
1292
+ failureReason: error instanceof Error ? error.message : "Unknown error",
1293
+ durationMs
1294
+ });
1019
1295
  });
1020
1296
  }
1021
1297
  return result;
1022
1298
  };
1023
1299
  }
1024
- function wrapAISDK(ai) {
1025
- const client = getClient2();
1300
+ function wrapAISDK(ai, options) {
1301
+ const client = options?.client ?? getClient2();
1302
+ const config = {
1303
+ defaultAgent: options?.defaultAgent ?? _globalConfig.defaultAgent,
1304
+ userId: options?.userId ?? _globalConfig.userId,
1305
+ convoId: options?.convoId ?? _globalConfig.convoId
1306
+ };
1026
1307
  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)
1308
+ generateText: ai.generateText ? wrapGenerateText(ai.generateText, client, config) : wrapGenerateText(
1309
+ () => Promise.reject(new Error("generateText not available")),
1310
+ client,
1311
+ config
1312
+ ),
1313
+ streamText: ai.streamText ? wrapStreamText(ai.streamText, client, config) : wrapStreamText(() => ({ textStream: (async function* () {
1314
+ })() }), client, config),
1315
+ generateObject: ai.generateObject ? wrapGenerateObject(ai.generateObject, client, config) : wrapGenerateObject(
1316
+ () => Promise.reject(new Error("generateObject not available")),
1317
+ client,
1318
+ config
1319
+ ),
1320
+ streamObject: ai.streamObject ? wrapStreamObject(ai.streamObject, client, config) : wrapStreamObject(() => ({}), client, config)
1032
1321
  };
1033
1322
  }
1034
1323
 
@@ -2072,7 +2361,12 @@ export {
2072
2361
  getSessionContext,
2073
2362
  getSystemPrompt,
2074
2363
  getVariantName,
2364
+ hashValue,
2075
2365
  isExperimentMode,
2366
+ redactPayload,
2367
+ redactString,
2368
+ redactValue,
2369
+ replaceMatch,
2076
2370
  sentrial,
2077
2371
  setClient,
2078
2372
  setDefaultClient,