@fallom/trace 0.2.2 → 0.2.4

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.mjs CHANGED
@@ -795,32 +795,6 @@ function generateHexId(length) {
795
795
  crypto.getRandomValues(bytes);
796
796
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
797
797
  }
798
- function messagesToOtelAttributes(messages, completion, model, responseId) {
799
- const attrs = {};
800
- if (model) {
801
- attrs["gen_ai.request.model"] = model;
802
- attrs["gen_ai.response.model"] = model;
803
- }
804
- if (responseId) {
805
- attrs["gen_ai.response.id"] = responseId;
806
- }
807
- if (messages) {
808
- messages.forEach((msg, i) => {
809
- attrs[`gen_ai.prompt.${i}.role`] = msg.role;
810
- attrs[`gen_ai.prompt.${i}.content`] = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
811
- });
812
- }
813
- if (completion) {
814
- attrs["gen_ai.completion.0.role"] = completion.role;
815
- attrs["gen_ai.completion.0.content"] = typeof completion.content === "string" ? completion.content : JSON.stringify(completion.content);
816
- if (completion.tool_calls) {
817
- attrs["gen_ai.completion.0.tool_calls"] = JSON.stringify(
818
- completion.tool_calls
819
- );
820
- }
821
- }
822
- return attrs;
823
- }
824
798
 
825
799
  // src/trace/wrappers/openai.ts
826
800
  function wrapOpenAI(client, sessionCtx) {
@@ -842,18 +816,25 @@ function wrapOpenAI(client, sessionCtx) {
842
816
  try {
843
817
  const response = await originalCreate(...args);
844
818
  const endTime = Date.now();
845
- const attributes = captureContent2 ? messagesToOtelAttributes(
846
- params?.messages,
847
- response?.choices?.[0]?.message,
848
- response?.model || params?.model,
849
- response?.id
850
- ) : {};
819
+ const attributes = {
820
+ "fallom.sdk_version": "2",
821
+ "fallom.method": "chat.completions.create"
822
+ };
823
+ if (captureContent2) {
824
+ attributes["fallom.raw.request"] = JSON.stringify({
825
+ messages: params?.messages,
826
+ model: params?.model
827
+ });
828
+ attributes["fallom.raw.response"] = JSON.stringify({
829
+ text: response?.choices?.[0]?.message?.content,
830
+ finishReason: response?.choices?.[0]?.finish_reason,
831
+ responseId: response?.id,
832
+ model: response?.model
833
+ });
834
+ }
851
835
  if (response?.usage) {
852
836
  attributes["fallom.raw.usage"] = JSON.stringify(response.usage);
853
837
  }
854
- if (response?.choices?.[0]?.finish_reason) {
855
- attributes["gen_ai.response.finish_reason"] = response.choices[0].finish_reason;
856
- }
857
838
  sendTrace({
858
839
  config_key: ctx.configKey,
859
840
  session_id: ctx.sessionId,
@@ -868,24 +849,12 @@ function wrapOpenAI(client, sessionCtx) {
868
849
  end_time: new Date(endTime).toISOString(),
869
850
  duration_ms: endTime - startTime,
870
851
  status: "OK",
871
- prompt_tokens: response?.usage?.prompt_tokens,
872
- completion_tokens: response?.usage?.completion_tokens,
873
- total_tokens: response?.usage?.total_tokens,
874
- attributes: Object.keys(attributes).length > 0 ? attributes : void 0
852
+ attributes
875
853
  }).catch(() => {
876
854
  });
877
855
  return response;
878
856
  } catch (error) {
879
857
  const endTime = Date.now();
880
- const attributes = captureContent2 ? messagesToOtelAttributes(
881
- params?.messages,
882
- void 0,
883
- params?.model,
884
- void 0
885
- ) : void 0;
886
- if (attributes) {
887
- attributes["error.message"] = error?.message;
888
- }
889
858
  sendTrace({
890
859
  config_key: ctx.configKey,
891
860
  session_id: ctx.sessionId,
@@ -901,7 +870,10 @@ function wrapOpenAI(client, sessionCtx) {
901
870
  duration_ms: endTime - startTime,
902
871
  status: "ERROR",
903
872
  error_message: error?.message,
904
- attributes
873
+ attributes: {
874
+ "fallom.sdk_version": "2",
875
+ "fallom.method": "chat.completions.create"
876
+ }
905
877
  }).catch(() => {
906
878
  });
907
879
  throw error;
@@ -928,21 +900,26 @@ function wrapAnthropic(client, sessionCtx) {
928
900
  try {
929
901
  const response = await originalCreate(...args);
930
902
  const endTime = Date.now();
931
- const attributes = captureContent2 ? messagesToOtelAttributes(
932
- params?.messages,
933
- { role: "assistant", content: response?.content?.[0]?.text || "" },
934
- response?.model || params?.model,
935
- response?.id
936
- ) : {};
937
- if (params?.system) {
938
- attributes["gen_ai.system_prompt"] = params.system;
903
+ const attributes = {
904
+ "fallom.sdk_version": "2",
905
+ "fallom.method": "messages.create"
906
+ };
907
+ if (captureContent2) {
908
+ attributes["fallom.raw.request"] = JSON.stringify({
909
+ messages: params?.messages,
910
+ system: params?.system,
911
+ model: params?.model
912
+ });
913
+ attributes["fallom.raw.response"] = JSON.stringify({
914
+ text: response?.content?.[0]?.text,
915
+ finishReason: response?.stop_reason,
916
+ responseId: response?.id,
917
+ model: response?.model
918
+ });
939
919
  }
940
920
  if (response?.usage) {
941
921
  attributes["fallom.raw.usage"] = JSON.stringify(response.usage);
942
922
  }
943
- if (response?.stop_reason) {
944
- attributes["gen_ai.response.finish_reason"] = response.stop_reason;
945
- }
946
923
  sendTrace({
947
924
  config_key: ctx.configKey,
948
925
  session_id: ctx.sessionId,
@@ -957,27 +934,12 @@ function wrapAnthropic(client, sessionCtx) {
957
934
  end_time: new Date(endTime).toISOString(),
958
935
  duration_ms: endTime - startTime,
959
936
  status: "OK",
960
- prompt_tokens: response?.usage?.input_tokens,
961
- completion_tokens: response?.usage?.output_tokens,
962
- total_tokens: (response?.usage?.input_tokens || 0) + (response?.usage?.output_tokens || 0),
963
- attributes: Object.keys(attributes).length > 0 ? attributes : void 0
937
+ attributes
964
938
  }).catch(() => {
965
939
  });
966
940
  return response;
967
941
  } catch (error) {
968
942
  const endTime = Date.now();
969
- const attributes = captureContent2 ? messagesToOtelAttributes(
970
- params?.messages,
971
- void 0,
972
- params?.model,
973
- void 0
974
- ) : void 0;
975
- if (attributes) {
976
- attributes["error.message"] = error?.message;
977
- if (params?.system) {
978
- attributes["gen_ai.system_prompt"] = params.system;
979
- }
980
- }
981
943
  sendTrace({
982
944
  config_key: ctx.configKey,
983
945
  session_id: ctx.sessionId,
@@ -993,7 +955,10 @@ function wrapAnthropic(client, sessionCtx) {
993
955
  duration_ms: endTime - startTime,
994
956
  status: "ERROR",
995
957
  error_message: error?.message,
996
- attributes
958
+ attributes: {
959
+ "fallom.sdk_version": "2",
960
+ "fallom.method": "messages.create"
961
+ }
997
962
  }).catch(() => {
998
963
  });
999
964
  throw error;
@@ -1004,50 +969,36 @@ function wrapAnthropic(client, sessionCtx) {
1004
969
 
1005
970
  // src/trace/wrappers/google-ai.ts
1006
971
  function wrapGoogleAI(model, sessionCtx) {
1007
- const originalGenerate = model.generateContent.bind(model);
972
+ const originalGenerateContent = model.generateContent.bind(model);
1008
973
  const ctx = sessionCtx;
1009
974
  model.generateContent = async function(...args) {
1010
975
  if (!isInitialized()) {
1011
- return originalGenerate(...args);
976
+ return originalGenerateContent(...args);
1012
977
  }
1013
978
  const traceCtx = getTraceContextStorage().getStore() || getFallbackTraceContext();
1014
979
  const traceId = traceCtx?.traceId || generateHexId(32);
1015
980
  const spanId = generateHexId(16);
1016
981
  const parentSpanId = traceCtx?.parentSpanId;
982
+ const request = args[0];
1017
983
  const startTime = Date.now();
1018
984
  const captureContent2 = shouldCaptureContent();
1019
985
  try {
1020
- const response = await originalGenerate(...args);
986
+ const response = await originalGenerateContent(...args);
1021
987
  const endTime = Date.now();
1022
- const result = response?.response;
1023
- const usage = result?.usageMetadata;
1024
- const modelName = model?.model || "gemini";
1025
- const attributes = {};
988
+ const result = response?.response || response;
989
+ const attributes = {
990
+ "fallom.sdk_version": "2",
991
+ "fallom.method": "generateContent"
992
+ };
1026
993
  if (captureContent2) {
1027
- attributes["gen_ai.request.model"] = modelName;
1028
- attributes["gen_ai.response.model"] = modelName;
1029
- const input = args[0];
1030
- if (typeof input === "string") {
1031
- attributes["gen_ai.prompt.0.role"] = "user";
1032
- attributes["gen_ai.prompt.0.content"] = input;
1033
- } else if (input?.contents) {
1034
- input.contents.forEach((content, i) => {
1035
- attributes[`gen_ai.prompt.${i}.role`] = content.role || "user";
1036
- attributes[`gen_ai.prompt.${i}.content`] = content.parts?.[0]?.text || JSON.stringify(content.parts);
1037
- });
1038
- }
1039
- const outputText = result?.text?.();
1040
- if (outputText) {
1041
- attributes["gen_ai.completion.0.role"] = "assistant";
1042
- attributes["gen_ai.completion.0.content"] = outputText;
1043
- }
1044
- }
1045
- if (usage) {
1046
- attributes["fallom.raw.usage"] = JSON.stringify(usage);
994
+ attributes["fallom.raw.request"] = JSON.stringify(request);
995
+ attributes["fallom.raw.response"] = JSON.stringify({
996
+ text: result?.text?.(),
997
+ candidates: result?.candidates
998
+ });
1047
999
  }
1048
- const candidate = result?.candidates?.[0];
1049
- if (candidate?.finishReason) {
1050
- attributes["gen_ai.response.finish_reason"] = candidate.finishReason;
1000
+ if (result?.usageMetadata) {
1001
+ attributes["fallom.raw.usage"] = JSON.stringify(result.usageMetadata);
1051
1002
  }
1052
1003
  sendTrace({
1053
1004
  config_key: ctx.configKey,
@@ -1058,31 +1009,17 @@ function wrapGoogleAI(model, sessionCtx) {
1058
1009
  parent_span_id: parentSpanId,
1059
1010
  name: "generateContent",
1060
1011
  kind: "llm",
1061
- model: modelName,
1012
+ model: model.model || "gemini",
1062
1013
  start_time: new Date(startTime).toISOString(),
1063
1014
  end_time: new Date(endTime).toISOString(),
1064
1015
  duration_ms: endTime - startTime,
1065
1016
  status: "OK",
1066
- prompt_tokens: usage?.promptTokenCount,
1067
- completion_tokens: usage?.candidatesTokenCount,
1068
- total_tokens: usage?.totalTokenCount,
1069
- attributes: Object.keys(attributes).length > 0 ? attributes : void 0
1017
+ attributes
1070
1018
  }).catch(() => {
1071
1019
  });
1072
1020
  return response;
1073
1021
  } catch (error) {
1074
1022
  const endTime = Date.now();
1075
- const modelName = model?.model || "gemini";
1076
- const attributes = {};
1077
- if (captureContent2) {
1078
- attributes["gen_ai.request.model"] = modelName;
1079
- attributes["error.message"] = error?.message;
1080
- const input = args[0];
1081
- if (typeof input === "string") {
1082
- attributes["gen_ai.prompt.0.role"] = "user";
1083
- attributes["gen_ai.prompt.0.content"] = input;
1084
- }
1085
- }
1086
1023
  sendTrace({
1087
1024
  config_key: ctx.configKey,
1088
1025
  session_id: ctx.sessionId,
@@ -1092,13 +1029,16 @@ function wrapGoogleAI(model, sessionCtx) {
1092
1029
  parent_span_id: parentSpanId,
1093
1030
  name: "generateContent",
1094
1031
  kind: "llm",
1095
- model: modelName,
1032
+ model: model.model || "gemini",
1096
1033
  start_time: new Date(startTime).toISOString(),
1097
1034
  end_time: new Date(endTime).toISOString(),
1098
1035
  duration_ms: endTime - startTime,
1099
1036
  status: "ERROR",
1100
1037
  error_message: error?.message,
1101
- attributes: captureContent2 ? attributes : void 0
1038
+ attributes: {
1039
+ "fallom.sdk_version": "2",
1040
+ "fallom.method": "generateContent"
1041
+ }
1102
1042
  }).catch(() => {
1103
1043
  });
1104
1044
  throw error;
@@ -1107,35 +1047,6 @@ function wrapGoogleAI(model, sessionCtx) {
1107
1047
  return model;
1108
1048
  }
1109
1049
 
1110
- // src/trace/wrappers/vercel-ai/utils.ts
1111
- function extractUsageFromResult(result, directUsage) {
1112
- let usage = directUsage ?? result?.usage;
1113
- const isValidNumber = (v) => v !== null && v !== void 0 && !Number.isNaN(v);
1114
- let promptTokens = isValidNumber(usage?.promptTokens) ? usage.promptTokens : isValidNumber(usage?.inputTokens) ? usage.inputTokens : isValidNumber(usage?.prompt_tokens) ? usage.prompt_tokens : void 0;
1115
- let completionTokens = isValidNumber(usage?.completionTokens) ? usage.completionTokens : isValidNumber(usage?.outputTokens) ? usage.outputTokens : isValidNumber(usage?.completion_tokens) ? usage.completion_tokens : void 0;
1116
- let totalTokens = isValidNumber(usage?.totalTokens) ? usage.totalTokens : isValidNumber(usage?.total_tokens) ? usage.total_tokens : void 0;
1117
- let cost;
1118
- const orUsage = result?.experimental_providerMetadata?.openrouter?.usage;
1119
- if (orUsage) {
1120
- if (promptTokens === void 0 && isValidNumber(orUsage.promptTokens)) {
1121
- promptTokens = orUsage.promptTokens;
1122
- }
1123
- if (completionTokens === void 0 && isValidNumber(orUsage.completionTokens)) {
1124
- completionTokens = orUsage.completionTokens;
1125
- }
1126
- if (totalTokens === void 0 && isValidNumber(orUsage.totalTokens)) {
1127
- totalTokens = orUsage.totalTokens;
1128
- }
1129
- if (isValidNumber(orUsage.cost)) {
1130
- cost = orUsage.cost;
1131
- }
1132
- }
1133
- if (totalTokens === void 0 && (promptTokens !== void 0 || completionTokens !== void 0)) {
1134
- totalTokens = (promptTokens ?? 0) + (completionTokens ?? 0);
1135
- }
1136
- return { promptTokens, completionTokens, totalTokens, cost };
1137
- }
1138
-
1139
1050
  // src/trace/wrappers/vercel-ai/generate-text.ts
1140
1051
  function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
1141
1052
  const ctx = sessionCtx;
@@ -1154,54 +1065,33 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
1154
1065
  const result = await aiModule.generateText(...args);
1155
1066
  const endTime = Date.now();
1156
1067
  if (debug || isDebugMode()) {
1157
- console.log(
1158
- "\n\u{1F50D} [Fallom Debug] generateText result keys:",
1159
- Object.keys(result || {})
1160
- );
1161
- console.log(
1162
- "\u{1F50D} [Fallom Debug] result.usage:",
1163
- JSON.stringify(result?.usage, null, 2)
1164
- );
1165
- console.log(
1166
- "\u{1F50D} [Fallom Debug] result.experimental_providerMetadata:",
1167
- JSON.stringify(result?.experimental_providerMetadata, null, 2)
1168
- );
1068
+ console.log("\n\u{1F50D} [Fallom Debug] generateText raw result:", JSON.stringify(result, null, 2));
1169
1069
  }
1170
1070
  const modelId = result?.response?.modelId || params?.model?.modelId || String(params?.model || "unknown");
1171
- const attributes = {};
1071
+ const attributes = {
1072
+ "fallom.sdk_version": "2",
1073
+ "fallom.method": "generateText"
1074
+ };
1172
1075
  if (captureContent2) {
1173
- attributes["gen_ai.request.model"] = modelId;
1174
- attributes["gen_ai.response.model"] = modelId;
1175
- if (params?.prompt) {
1176
- attributes["gen_ai.prompt.0.role"] = "user";
1177
- attributes["gen_ai.prompt.0.content"] = params.prompt;
1178
- }
1179
- if (params?.messages) {
1180
- params.messages.forEach((msg, i) => {
1181
- attributes[`gen_ai.prompt.${i}.role`] = msg.role;
1182
- attributes[`gen_ai.prompt.${i}.content`] = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
1183
- });
1184
- }
1185
- if (result?.text) {
1186
- attributes["gen_ai.completion.0.role"] = "assistant";
1187
- attributes["gen_ai.completion.0.content"] = result.text;
1188
- }
1189
- if (result?.response?.id) {
1190
- attributes["gen_ai.response.id"] = result.response.id;
1191
- }
1076
+ attributes["fallom.raw.request"] = JSON.stringify({
1077
+ prompt: params?.prompt,
1078
+ messages: params?.messages,
1079
+ system: params?.system,
1080
+ model: modelId
1081
+ });
1082
+ attributes["fallom.raw.response"] = JSON.stringify({
1083
+ text: result?.text,
1084
+ finishReason: result?.finishReason,
1085
+ responseId: result?.response?.id,
1086
+ modelId: result?.response?.modelId
1087
+ });
1192
1088
  }
1193
1089
  if (result?.usage) {
1194
1090
  attributes["fallom.raw.usage"] = JSON.stringify(result.usage);
1195
1091
  }
1196
1092
  if (result?.experimental_providerMetadata) {
1197
- attributes["fallom.raw.providerMetadata"] = JSON.stringify(
1198
- result.experimental_providerMetadata
1199
- );
1093
+ attributes["fallom.raw.providerMetadata"] = JSON.stringify(result.experimental_providerMetadata);
1200
1094
  }
1201
- if (result?.finishReason) {
1202
- attributes["gen_ai.response.finish_reason"] = result.finishReason;
1203
- }
1204
- const usage = extractUsageFromResult(result);
1205
1095
  sendTrace({
1206
1096
  config_key: ctx.configKey,
1207
1097
  session_id: ctx.sessionId,
@@ -1216,10 +1106,7 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
1216
1106
  end_time: new Date(endTime).toISOString(),
1217
1107
  duration_ms: endTime - startTime,
1218
1108
  status: "OK",
1219
- prompt_tokens: usage.promptTokens,
1220
- completion_tokens: usage.completionTokens,
1221
- total_tokens: usage.totalTokens,
1222
- attributes: captureContent2 ? attributes : void 0
1109
+ attributes
1223
1110
  }).catch(() => {
1224
1111
  });
1225
1112
  return result;
@@ -1240,7 +1127,17 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
1240
1127
  end_time: new Date(endTime).toISOString(),
1241
1128
  duration_ms: endTime - startTime,
1242
1129
  status: "ERROR",
1243
- error_message: error?.message
1130
+ error_message: error?.message,
1131
+ attributes: {
1132
+ "fallom.sdk_version": "2",
1133
+ "fallom.method": "generateText",
1134
+ "fallom.raw.request": JSON.stringify({
1135
+ prompt: params?.prompt,
1136
+ messages: params?.messages,
1137
+ system: params?.system,
1138
+ model: modelId
1139
+ })
1140
+ }
1244
1141
  }).catch(() => {
1245
1142
  });
1246
1143
  throw error;
@@ -1269,15 +1166,17 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
1269
1166
  let firstTokenTime = null;
1270
1167
  const modelId = params?.model?.modelId || String(params?.model || "unknown");
1271
1168
  if (result?.usage) {
1272
- result.usage.then(async (rawUsage) => {
1169
+ Promise.all([
1170
+ result.usage.catch(() => null),
1171
+ result.text?.catch(() => null),
1172
+ result.finishReason?.catch(() => null)
1173
+ ]).then(async ([rawUsage, responseText, finishReason]) => {
1273
1174
  const endTime = Date.now();
1274
1175
  if (debug || isDebugMode()) {
1275
- console.log(
1276
- "\n\u{1F50D} [Fallom Debug] streamText usage:",
1277
- JSON.stringify(rawUsage, null, 2)
1278
- );
1176
+ console.log("\n\u{1F50D} [Fallom Debug] streamText raw usage:", JSON.stringify(rawUsage, null, 2));
1177
+ console.log("\u{1F50D} [Fallom Debug] streamText response text:", responseText?.slice(0, 100));
1178
+ console.log("\u{1F50D} [Fallom Debug] streamText finish reason:", finishReason);
1279
1179
  }
1280
- log2("\u{1F4CA} streamText usage:", JSON.stringify(rawUsage, null, 2));
1281
1180
  let providerMetadata = result?.experimental_providerMetadata;
1282
1181
  if (providerMetadata && typeof providerMetadata.then === "function") {
1283
1182
  try {
@@ -1286,28 +1185,35 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
1286
1185
  providerMetadata = void 0;
1287
1186
  }
1288
1187
  }
1289
- const usage = extractUsageFromResult(
1290
- { experimental_providerMetadata: providerMetadata },
1291
- rawUsage
1292
- );
1293
- const attributes = {};
1188
+ const attributes = {
1189
+ "fallom.sdk_version": "2",
1190
+ "fallom.method": "streamText",
1191
+ "fallom.is_streaming": true
1192
+ };
1294
1193
  if (captureContent2) {
1295
- attributes["gen_ai.request.model"] = modelId;
1296
- if (params?.prompt) {
1297
- attributes["gen_ai.prompt.0.role"] = "user";
1298
- attributes["gen_ai.prompt.0.content"] = params.prompt;
1194
+ attributes["fallom.raw.request"] = JSON.stringify({
1195
+ prompt: params?.prompt,
1196
+ messages: params?.messages,
1197
+ system: params?.system,
1198
+ model: modelId
1199
+ });
1200
+ if (responseText || finishReason) {
1201
+ attributes["fallom.raw.response"] = JSON.stringify({
1202
+ text: responseText,
1203
+ finishReason
1204
+ });
1299
1205
  }
1300
1206
  }
1301
- if (firstTokenTime) {
1302
- attributes["gen_ai.time_to_first_token_ms"] = firstTokenTime - startTime;
1303
- }
1304
1207
  if (rawUsage) {
1305
1208
  attributes["fallom.raw.usage"] = JSON.stringify(rawUsage);
1306
1209
  }
1307
1210
  if (providerMetadata) {
1308
1211
  attributes["fallom.raw.providerMetadata"] = JSON.stringify(providerMetadata);
1309
1212
  }
1310
- const tracePayload = {
1213
+ if (firstTokenTime) {
1214
+ attributes["fallom.time_to_first_token_ms"] = firstTokenTime - startTime;
1215
+ }
1216
+ sendTrace({
1311
1217
  config_key: ctx.configKey,
1312
1218
  session_id: ctx.sessionId,
1313
1219
  customer_id: ctx.customerId,
@@ -1321,13 +1227,10 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
1321
1227
  end_time: new Date(endTime).toISOString(),
1322
1228
  duration_ms: endTime - startTime,
1323
1229
  status: "OK",
1324
- prompt_tokens: usage.promptTokens,
1325
- completion_tokens: usage.completionTokens,
1326
- total_tokens: usage.totalTokens,
1327
1230
  time_to_first_token_ms: firstTokenTime ? firstTokenTime - startTime : void 0,
1328
- attributes: captureContent2 ? attributes : void 0
1329
- };
1330
- sendTrace(tracePayload).catch(() => {
1231
+ is_streaming: true,
1232
+ attributes
1233
+ }).catch(() => {
1331
1234
  });
1332
1235
  }).catch((error) => {
1333
1236
  const endTime = Date.now();
@@ -1346,7 +1249,12 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
1346
1249
  end_time: new Date(endTime).toISOString(),
1347
1250
  duration_ms: endTime - startTime,
1348
1251
  status: "ERROR",
1349
- error_message: error?.message
1252
+ error_message: error?.message,
1253
+ attributes: {
1254
+ "fallom.sdk_version": "2",
1255
+ "fallom.method": "streamText",
1256
+ "fallom.is_streaming": true
1257
+ }
1350
1258
  }).catch(() => {
1351
1259
  });
1352
1260
  });
@@ -1394,25 +1302,30 @@ function createGenerateObjectWrapper(aiModule, sessionCtx, debug = false) {
1394
1302
  const endTime = Date.now();
1395
1303
  if (debug || isDebugMode()) {
1396
1304
  console.log(
1397
- "\n\u{1F50D} [Fallom Debug] generateObject result keys:",
1398
- Object.keys(result || {})
1399
- );
1400
- console.log(
1401
- "\u{1F50D} [Fallom Debug] result.usage:",
1402
- JSON.stringify(result?.usage, null, 2)
1305
+ "\n\u{1F50D} [Fallom Debug] generateObject raw result:",
1306
+ JSON.stringify(result, null, 2)
1403
1307
  );
1404
1308
  }
1405
1309
  const modelId = result?.response?.modelId || params?.model?.modelId || String(params?.model || "unknown");
1406
- const attributes = {};
1310
+ const attributes = {
1311
+ "fallom.sdk_version": "2",
1312
+ "fallom.method": "generateObject"
1313
+ };
1407
1314
  if (captureContent2) {
1408
- attributes["gen_ai.request.model"] = modelId;
1409
- attributes["gen_ai.response.model"] = modelId;
1410
- if (result?.object) {
1411
- attributes["gen_ai.completion.0.role"] = "assistant";
1412
- attributes["gen_ai.completion.0.content"] = JSON.stringify(
1413
- result.object
1414
- );
1415
- }
1315
+ attributes["fallom.raw.request"] = JSON.stringify({
1316
+ prompt: params?.prompt,
1317
+ messages: params?.messages,
1318
+ system: params?.system,
1319
+ model: modelId,
1320
+ schema: params?.schema ? "provided" : void 0
1321
+ // Don't send full schema, just note if present
1322
+ });
1323
+ attributes["fallom.raw.response"] = JSON.stringify({
1324
+ object: result?.object,
1325
+ finishReason: result?.finishReason,
1326
+ responseId: result?.response?.id,
1327
+ modelId: result?.response?.modelId
1328
+ });
1416
1329
  }
1417
1330
  if (result?.usage) {
1418
1331
  attributes["fallom.raw.usage"] = JSON.stringify(result.usage);
@@ -1422,10 +1335,6 @@ function createGenerateObjectWrapper(aiModule, sessionCtx, debug = false) {
1422
1335
  result.experimental_providerMetadata
1423
1336
  );
1424
1337
  }
1425
- if (result?.finishReason) {
1426
- attributes["gen_ai.response.finish_reason"] = result.finishReason;
1427
- }
1428
- const usage = extractUsageFromResult(result);
1429
1338
  sendTrace({
1430
1339
  config_key: ctx.configKey,
1431
1340
  session_id: ctx.sessionId,
@@ -1440,10 +1349,7 @@ function createGenerateObjectWrapper(aiModule, sessionCtx, debug = false) {
1440
1349
  end_time: new Date(endTime).toISOString(),
1441
1350
  duration_ms: endTime - startTime,
1442
1351
  status: "OK",
1443
- prompt_tokens: usage.promptTokens,
1444
- completion_tokens: usage.completionTokens,
1445
- total_tokens: usage.totalTokens,
1446
- attributes: captureContent2 ? attributes : void 0
1352
+ attributes
1447
1353
  }).catch(() => {
1448
1354
  });
1449
1355
  return result;
@@ -1464,7 +1370,11 @@ function createGenerateObjectWrapper(aiModule, sessionCtx, debug = false) {
1464
1370
  end_time: new Date(endTime).toISOString(),
1465
1371
  duration_ms: endTime - startTime,
1466
1372
  status: "ERROR",
1467
- error_message: error?.message
1373
+ error_message: error?.message,
1374
+ attributes: {
1375
+ "fallom.sdk_version": "2",
1376
+ "fallom.method": "generateObject"
1377
+ }
1468
1378
  }).catch(() => {
1469
1379
  });
1470
1380
  throw error;
@@ -1473,9 +1383,6 @@ function createGenerateObjectWrapper(aiModule, sessionCtx, debug = false) {
1473
1383
  }
1474
1384
 
1475
1385
  // src/trace/wrappers/vercel-ai/stream-object.ts
1476
- function log3(...args) {
1477
- if (isDebugMode()) console.log("[Fallom]", ...args);
1478
- }
1479
1386
  function createStreamObjectWrapper(aiModule, sessionCtx, debug = false) {
1480
1387
  const ctx = sessionCtx;
1481
1388
  return async (...args) => {
@@ -1483,7 +1390,6 @@ function createStreamObjectWrapper(aiModule, sessionCtx, debug = false) {
1483
1390
  const startTime = Date.now();
1484
1391
  const captureContent2 = shouldCaptureContent();
1485
1392
  const result = await aiModule.streamObject(...args);
1486
- log3("\u{1F50D} streamObject result keys:", Object.keys(result || {}));
1487
1393
  if (!isInitialized()) {
1488
1394
  return result;
1489
1395
  }
@@ -1491,18 +1397,19 @@ function createStreamObjectWrapper(aiModule, sessionCtx, debug = false) {
1491
1397
  const traceId = traceCtx?.traceId || generateHexId(32);
1492
1398
  const spanId = generateHexId(16);
1493
1399
  const parentSpanId = traceCtx?.parentSpanId;
1494
- let firstTokenTime = null;
1495
1400
  const modelId = params?.model?.modelId || String(params?.model || "unknown");
1496
1401
  if (result?.usage) {
1497
- result.usage.then(async (rawUsage) => {
1402
+ Promise.all([
1403
+ result.usage.catch(() => null),
1404
+ result.object?.catch(() => null),
1405
+ result.finishReason?.catch(() => null)
1406
+ ]).then(async ([rawUsage, responseObject, finishReason]) => {
1498
1407
  const endTime = Date.now();
1499
1408
  if (debug || isDebugMode()) {
1500
- console.log(
1501
- "\n\u{1F50D} [Fallom Debug] streamObject usage:",
1502
- JSON.stringify(rawUsage, null, 2)
1503
- );
1409
+ console.log("\n\u{1F50D} [Fallom Debug] streamObject raw usage:", JSON.stringify(rawUsage, null, 2));
1410
+ console.log("\u{1F50D} [Fallom Debug] streamObject response object:", JSON.stringify(responseObject)?.slice(0, 100));
1411
+ console.log("\u{1F50D} [Fallom Debug] streamObject finish reason:", finishReason);
1504
1412
  }
1505
- log3("\u{1F4CA} streamObject usage:", JSON.stringify(rawUsage, null, 2));
1506
1413
  let providerMetadata = result?.experimental_providerMetadata;
1507
1414
  if (providerMetadata && typeof providerMetadata.then === "function") {
1508
1415
  try {
@@ -1511,16 +1418,25 @@ function createStreamObjectWrapper(aiModule, sessionCtx, debug = false) {
1511
1418
  providerMetadata = void 0;
1512
1419
  }
1513
1420
  }
1514
- const usage = extractUsageFromResult(
1515
- { experimental_providerMetadata: providerMetadata },
1516
- rawUsage
1517
- );
1518
- const attributes = {};
1421
+ const attributes = {
1422
+ "fallom.sdk_version": "2",
1423
+ "fallom.method": "streamObject",
1424
+ "fallom.is_streaming": true
1425
+ };
1519
1426
  if (captureContent2) {
1520
- attributes["gen_ai.request.model"] = modelId;
1521
- }
1522
- if (firstTokenTime) {
1523
- attributes["gen_ai.time_to_first_token_ms"] = firstTokenTime - startTime;
1427
+ attributes["fallom.raw.request"] = JSON.stringify({
1428
+ prompt: params?.prompt,
1429
+ messages: params?.messages,
1430
+ system: params?.system,
1431
+ model: modelId,
1432
+ schema: params?.schema ? "provided" : void 0
1433
+ });
1434
+ if (responseObject || finishReason) {
1435
+ attributes["fallom.raw.response"] = JSON.stringify({
1436
+ object: responseObject,
1437
+ finishReason
1438
+ });
1439
+ }
1524
1440
  }
1525
1441
  if (rawUsage) {
1526
1442
  attributes["fallom.raw.usage"] = JSON.stringify(rawUsage);
@@ -1542,10 +1458,8 @@ function createStreamObjectWrapper(aiModule, sessionCtx, debug = false) {
1542
1458
  end_time: new Date(endTime).toISOString(),
1543
1459
  duration_ms: endTime - startTime,
1544
1460
  status: "OK",
1545
- prompt_tokens: usage.promptTokens,
1546
- completion_tokens: usage.completionTokens,
1547
- total_tokens: usage.totalTokens,
1548
- attributes: captureContent2 ? attributes : void 0
1461
+ is_streaming: true,
1462
+ attributes
1549
1463
  }).catch(() => {
1550
1464
  });
1551
1465
  }).catch((error) => {
@@ -1564,31 +1478,16 @@ function createStreamObjectWrapper(aiModule, sessionCtx, debug = false) {
1564
1478
  end_time: new Date(endTime).toISOString(),
1565
1479
  duration_ms: endTime - startTime,
1566
1480
  status: "ERROR",
1567
- error_message: error?.message
1481
+ error_message: error?.message,
1482
+ attributes: {
1483
+ "fallom.sdk_version": "2",
1484
+ "fallom.method": "streamObject",
1485
+ "fallom.is_streaming": true
1486
+ }
1568
1487
  }).catch(() => {
1569
1488
  });
1570
1489
  });
1571
1490
  }
1572
- if (result?.partialObjectStream) {
1573
- const originalStream = result.partialObjectStream;
1574
- const wrappedStream = (async function* () {
1575
- for await (const chunk of originalStream) {
1576
- if (!firstTokenTime) {
1577
- firstTokenTime = Date.now();
1578
- log3("\u23F1\uFE0F Time to first token:", firstTokenTime - startTime, "ms");
1579
- }
1580
- yield chunk;
1581
- }
1582
- })();
1583
- return new Proxy(result, {
1584
- get(target, prop) {
1585
- if (prop === "partialObjectStream") {
1586
- return wrappedStream;
1587
- }
1588
- return target[prop];
1589
- }
1590
- });
1591
- }
1592
1491
  return result;
1593
1492
  };
1594
1493
  }
@@ -1607,105 +1506,69 @@ function wrapAISDK(ai, sessionCtx, options) {
1607
1506
  // src/trace/wrappers/mastra.ts
1608
1507
  function wrapMastraAgent(agent, sessionCtx) {
1609
1508
  const originalGenerate = agent.generate.bind(agent);
1610
- const agentName = agent.name || "MastraAgent";
1611
1509
  const ctx = sessionCtx;
1612
1510
  agent.generate = async function(...args) {
1613
1511
  if (!isInitialized()) {
1614
1512
  return originalGenerate(...args);
1615
1513
  }
1616
- const traceId = generateHexId(32);
1514
+ const traceCtx = getTraceContextStorage().getStore() || getFallbackTraceContext();
1515
+ const traceId = traceCtx?.traceId || generateHexId(32);
1617
1516
  const spanId = generateHexId(16);
1517
+ const parentSpanId = traceCtx?.parentSpanId;
1518
+ const input = args[0];
1618
1519
  const startTime = Date.now();
1619
- const messages = args[0] || [];
1520
+ const captureContent2 = shouldCaptureContent();
1620
1521
  try {
1621
1522
  const result = await originalGenerate(...args);
1622
1523
  const endTime = Date.now();
1623
- const model = result?.model?.modelId || "unknown";
1624
- const toolCalls = [];
1625
- if (result?.steps?.length) {
1626
- for (const step of result.steps) {
1627
- if (step.toolCalls?.length) {
1628
- for (let i = 0; i < step.toolCalls.length; i++) {
1629
- const tc = step.toolCalls[i];
1630
- const tr = step.toolResults?.[i];
1631
- toolCalls.push({
1632
- name: tc.toolName,
1633
- arguments: tc.args,
1634
- result: tr?.result
1635
- });
1636
- }
1637
- }
1638
- }
1639
- }
1640
1524
  const attributes = {
1641
- "gen_ai.system": "Mastra",
1642
- "gen_ai.request.model": model,
1643
- "gen_ai.response.model": model,
1644
- "fallom.source": "mastra-agent",
1645
- "llm.request.type": "chat"
1525
+ "fallom.sdk_version": "2",
1526
+ "fallom.method": "agent.generate",
1527
+ "fallom.agent_name": agent.name || "unknown"
1646
1528
  };
1647
- if (Array.isArray(messages)) {
1648
- messages.forEach((msg, i) => {
1649
- attributes[`gen_ai.prompt.${i}.role`] = msg.role || "user";
1650
- attributes[`gen_ai.prompt.${i}.content`] = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
1651
- });
1652
- }
1653
- if (result?.text) {
1654
- attributes["gen_ai.completion.0.role"] = "assistant";
1655
- attributes["gen_ai.completion.0.content"] = result.text;
1656
- attributes["gen_ai.completion.0.finish_reason"] = "stop";
1657
- }
1658
- if (toolCalls.length > 0) {
1659
- attributes["fallom.tool_calls"] = JSON.stringify(toolCalls);
1660
- toolCalls.forEach((tc, i) => {
1661
- attributes[`gen_ai.completion.0.tool_calls.${i}.name`] = tc.name;
1662
- attributes[`gen_ai.completion.0.tool_calls.${i}.type`] = "function";
1663
- attributes[`gen_ai.completion.0.tool_calls.${i}.arguments`] = JSON.stringify(tc.arguments);
1664
- });
1665
- }
1666
- if (result?.usage) {
1667
- attributes["gen_ai.usage.prompt_tokens"] = result.usage.promptTokens;
1668
- attributes["gen_ai.usage.completion_tokens"] = result.usage.completionTokens;
1669
- attributes["llm.usage.total_tokens"] = result.usage.totalTokens;
1529
+ if (captureContent2) {
1530
+ attributes["fallom.raw.request"] = JSON.stringify(input);
1531
+ attributes["fallom.raw.response"] = JSON.stringify(result);
1670
1532
  }
1671
- const traceData = {
1533
+ sendTrace({
1672
1534
  config_key: ctx.configKey,
1673
1535
  session_id: ctx.sessionId,
1674
1536
  customer_id: ctx.customerId,
1675
1537
  trace_id: traceId,
1676
1538
  span_id: spanId,
1677
- name: `mastra.${agentName}.generate`,
1678
- kind: "client",
1679
- model,
1539
+ parent_span_id: parentSpanId,
1540
+ name: `agent.${agent.name || "unknown"}.generate`,
1541
+ kind: "agent",
1680
1542
  start_time: new Date(startTime).toISOString(),
1681
1543
  end_time: new Date(endTime).toISOString(),
1682
1544
  duration_ms: endTime - startTime,
1683
1545
  status: "OK",
1684
- prompt_tokens: result?.usage?.promptTokens,
1685
- completion_tokens: result?.usage?.completionTokens,
1686
- total_tokens: result?.usage?.totalTokens,
1687
1546
  attributes
1688
- };
1689
- sendTrace(traceData).catch(() => {
1547
+ }).catch(() => {
1690
1548
  });
1691
1549
  return result;
1692
1550
  } catch (error) {
1693
1551
  const endTime = Date.now();
1694
- const traceData = {
1552
+ sendTrace({
1695
1553
  config_key: ctx.configKey,
1696
1554
  session_id: ctx.sessionId,
1697
1555
  customer_id: ctx.customerId,
1698
1556
  trace_id: traceId,
1699
1557
  span_id: spanId,
1700
- name: `mastra.${agentName}.generate`,
1701
- kind: "client",
1558
+ parent_span_id: parentSpanId,
1559
+ name: `agent.${agent.name || "unknown"}.generate`,
1560
+ kind: "agent",
1702
1561
  start_time: new Date(startTime).toISOString(),
1703
1562
  end_time: new Date(endTime).toISOString(),
1704
1563
  duration_ms: endTime - startTime,
1705
1564
  status: "ERROR",
1706
- error_message: error instanceof Error ? error.message : String(error)
1707
- };
1708
- sendTrace(traceData).catch(() => {
1565
+ error_message: error?.message,
1566
+ attributes: {
1567
+ "fallom.sdk_version": "2",
1568
+ "fallom.method": "agent.generate",
1569
+ "fallom.agent_name": agent.name || "unknown"
1570
+ }
1571
+ }).catch(() => {
1709
1572
  });
1710
1573
  throw error;
1711
1574
  }
@@ -1745,6 +1608,9 @@ var FallomSession = class {
1745
1608
  /**
1746
1609
  * Wrap a Vercel AI SDK model to trace all calls (PostHog style).
1747
1610
  * Returns the same model type with tracing injected.
1611
+ *
1612
+ * Note: This only captures tokens/timing, not prompt/completion content.
1613
+ * Use wrapAISDK for full content tracing.
1748
1614
  */
1749
1615
  traceModel(model) {
1750
1616
  const ctx = this.ctx;
@@ -1770,17 +1636,18 @@ var FallomSession = class {
1770
1636
  trace_id: traceId,
1771
1637
  span_id: spanId,
1772
1638
  parent_span_id: traceCtx?.parentSpanId,
1773
- name: "generateText",
1639
+ name: "doGenerate",
1774
1640
  kind: "llm",
1775
1641
  model: modelId,
1776
1642
  start_time: new Date(startTime).toISOString(),
1777
1643
  end_time: new Date(endTime).toISOString(),
1778
1644
  duration_ms: endTime - startTime,
1779
1645
  status: "OK",
1780
- prompt_tokens: usage?.promptTokens,
1781
- completion_tokens: usage?.completionTokens,
1782
- total_tokens: usage?.totalTokens,
1783
- attributes: shouldCaptureContent() && usage ? { "fallom.raw.usage": JSON.stringify(usage) } : void 0
1646
+ attributes: {
1647
+ "fallom.sdk_version": "2",
1648
+ "fallom.method": "traceModel.doGenerate",
1649
+ ...usage ? { "fallom.raw.usage": JSON.stringify(usage) } : {}
1650
+ }
1784
1651
  }).catch(() => {
1785
1652
  });
1786
1653
  return result;
@@ -1793,14 +1660,15 @@ var FallomSession = class {
1793
1660
  trace_id: traceId,
1794
1661
  span_id: spanId,
1795
1662
  parent_span_id: traceCtx?.parentSpanId,
1796
- name: "generateText",
1663
+ name: "doGenerate",
1797
1664
  kind: "llm",
1798
1665
  model: model.modelId || "unknown",
1799
1666
  start_time: new Date(startTime).toISOString(),
1800
1667
  end_time: new Date(endTime).toISOString(),
1801
1668
  duration_ms: endTime - startTime,
1802
1669
  status: "ERROR",
1803
- error_message: error instanceof Error ? error.message : String(error)
1670
+ error_message: error instanceof Error ? error.message : String(error),
1671
+ attributes: { "fallom.sdk_version": "2", "fallom.method": "traceModel.doGenerate" }
1804
1672
  }).catch(() => {
1805
1673
  });
1806
1674
  throw error;
@@ -1825,14 +1693,19 @@ var FallomSession = class {
1825
1693
  trace_id: traceId,
1826
1694
  span_id: spanId,
1827
1695
  parent_span_id: traceCtx?.parentSpanId,
1828
- name: "streamText",
1696
+ name: "doStream",
1829
1697
  kind: "llm",
1830
1698
  model: modelId,
1831
1699
  start_time: new Date(startTime).toISOString(),
1832
1700
  end_time: new Date(Date.now()).toISOString(),
1833
1701
  duration_ms: Date.now() - startTime,
1834
1702
  status: "OK",
1835
- is_streaming: true
1703
+ is_streaming: true,
1704
+ attributes: {
1705
+ "fallom.sdk_version": "2",
1706
+ "fallom.method": "traceModel.doStream",
1707
+ "fallom.is_streaming": true
1708
+ }
1836
1709
  }).catch(() => {
1837
1710
  });
1838
1711
  return result;
@@ -1844,7 +1717,7 @@ var FallomSession = class {
1844
1717
  trace_id: traceId,
1845
1718
  span_id: spanId,
1846
1719
  parent_span_id: traceCtx?.parentSpanId,
1847
- name: "streamText",
1720
+ name: "doStream",
1848
1721
  kind: "llm",
1849
1722
  model: modelId,
1850
1723
  start_time: new Date(startTime).toISOString(),
@@ -1852,7 +1725,12 @@ var FallomSession = class {
1852
1725
  duration_ms: Date.now() - startTime,
1853
1726
  status: "ERROR",
1854
1727
  error_message: error instanceof Error ? error.message : String(error),
1855
- is_streaming: true
1728
+ is_streaming: true,
1729
+ attributes: {
1730
+ "fallom.sdk_version": "2",
1731
+ "fallom.method": "traceModel.doStream",
1732
+ "fallom.is_streaming": true
1733
+ }
1856
1734
  }).catch(() => {
1857
1735
  });
1858
1736
  throw error;
@@ -1905,7 +1783,7 @@ var promptCache = /* @__PURE__ */ new Map();
1905
1783
  var promptABCache = /* @__PURE__ */ new Map();
1906
1784
  var promptContext = null;
1907
1785
  var SYNC_TIMEOUT = 2e3;
1908
- function log4(msg) {
1786
+ function log3(msg) {
1909
1787
  if (debugMode2) {
1910
1788
  console.log(`[Fallom Prompts] ${msg}`);
1911
1789
  }
@@ -2008,10 +1886,10 @@ async function get(promptKey, options = {}) {
2008
1886
  const { variables, version, debug = false } = options;
2009
1887
  debugMode2 = debug;
2010
1888
  ensureInit();
2011
- log4(`get() called: promptKey=${promptKey}`);
1889
+ log3(`get() called: promptKey=${promptKey}`);
2012
1890
  let promptData = promptCache.get(promptKey);
2013
1891
  if (!promptData) {
2014
- log4("Not in cache, fetching...");
1892
+ log3("Not in cache, fetching...");
2015
1893
  await fetchPrompts(SYNC_TIMEOUT);
2016
1894
  promptData = promptCache.get(promptKey);
2017
1895
  }
@@ -2033,7 +1911,7 @@ async function get(promptKey, options = {}) {
2033
1911
  promptKey,
2034
1912
  promptVersion: targetVersion
2035
1913
  });
2036
- log4(`\u2705 Got prompt: ${promptKey} v${targetVersion}`);
1914
+ log3(`\u2705 Got prompt: ${promptKey} v${targetVersion}`);
2037
1915
  return {
2038
1916
  key: promptKey,
2039
1917
  version: targetVersion,
@@ -2045,10 +1923,10 @@ async function getAB(abTestKey, sessionId, options = {}) {
2045
1923
  const { variables, debug = false } = options;
2046
1924
  debugMode2 = debug;
2047
1925
  ensureInit();
2048
- log4(`getAB() called: abTestKey=${abTestKey}, sessionId=${sessionId}`);
1926
+ log3(`getAB() called: abTestKey=${abTestKey}, sessionId=${sessionId}`);
2049
1927
  let abData = promptABCache.get(abTestKey);
2050
1928
  if (!abData) {
2051
- log4("Not in cache, fetching...");
1929
+ log3("Not in cache, fetching...");
2052
1930
  await fetchPromptABTests(SYNC_TIMEOUT);
2053
1931
  abData = promptABCache.get(abTestKey);
2054
1932
  }
@@ -2063,8 +1941,8 @@ async function getAB(abTestKey, sessionId, options = {}) {
2063
1941
  throw new Error(`Prompt A/B test '${abTestKey}' has no current version.`);
2064
1942
  }
2065
1943
  const { variants } = versionData;
2066
- log4(`A/B test '${abTestKey}' has ${variants?.length ?? 0} variants`);
2067
- log4(`Version data: ${JSON.stringify(versionData, null, 2)}`);
1944
+ log3(`A/B test '${abTestKey}' has ${variants?.length ?? 0} variants`);
1945
+ log3(`Version data: ${JSON.stringify(versionData, null, 2)}`);
2068
1946
  if (!variants || variants.length === 0) {
2069
1947
  throw new Error(
2070
1948
  `Prompt A/B test '${abTestKey}' has no variants configured.`
@@ -2110,7 +1988,7 @@ async function getAB(abTestKey, sessionId, options = {}) {
2110
1988
  abTestKey,
2111
1989
  variantIndex: selectedIndex
2112
1990
  });
2113
- log4(
1991
+ log3(
2114
1992
  `\u2705 Got prompt from A/B: ${promptKey} v${targetVersion} (variant ${selectedIndex})`
2115
1993
  );
2116
1994
  return {