@fallom/trace 0.1.3 → 0.1.5

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
@@ -3,6 +3,9 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __esm = (fn, res) => function __init() {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ };
6
9
  var __export = (target, all) => {
7
10
  for (var name in all)
8
11
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -17,12 +20,252 @@ var __copyProps = (to, from, except, desc) => {
17
20
  };
18
21
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
22
 
23
+ // src/prompts.ts
24
+ var prompts_exports = {};
25
+ __export(prompts_exports, {
26
+ clearPromptContext: () => clearPromptContext,
27
+ get: () => get,
28
+ getAB: () => getAB,
29
+ getPromptContext: () => getPromptContext,
30
+ init: () => init
31
+ });
32
+ function log(msg) {
33
+ if (debugMode) {
34
+ console.log(`[Fallom Prompts] ${msg}`);
35
+ }
36
+ }
37
+ function init(options = {}) {
38
+ apiKey = options.apiKey || process.env.FALLOM_API_KEY || null;
39
+ baseUrl = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
40
+ initialized = true;
41
+ if (!apiKey) {
42
+ return;
43
+ }
44
+ fetchAll().catch(() => {
45
+ });
46
+ if (!syncInterval) {
47
+ syncInterval = setInterval(() => {
48
+ fetchAll().catch(() => {
49
+ });
50
+ }, 3e4);
51
+ syncInterval.unref();
52
+ }
53
+ }
54
+ function ensureInit() {
55
+ if (!initialized) {
56
+ try {
57
+ init();
58
+ } catch {
59
+ }
60
+ }
61
+ }
62
+ async function fetchAll() {
63
+ await Promise.all([fetchPrompts(), fetchPromptABTests()]);
64
+ }
65
+ async function fetchPrompts(timeout = SYNC_TIMEOUT) {
66
+ if (!apiKey) return;
67
+ try {
68
+ const controller = new AbortController();
69
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
70
+ const resp = await fetch(`${baseUrl}/prompts`, {
71
+ headers: { Authorization: `Bearer ${apiKey}` },
72
+ signal: controller.signal
73
+ });
74
+ clearTimeout(timeoutId);
75
+ if (resp.ok) {
76
+ const data = await resp.json();
77
+ for (const p of data.prompts || []) {
78
+ if (!promptCache.has(p.key)) {
79
+ promptCache.set(p.key, { versions: /* @__PURE__ */ new Map(), current: null });
80
+ }
81
+ const cached = promptCache.get(p.key);
82
+ cached.versions.set(p.version, {
83
+ systemPrompt: p.system_prompt,
84
+ userTemplate: p.user_template
85
+ });
86
+ cached.current = p.version;
87
+ }
88
+ }
89
+ } catch {
90
+ }
91
+ }
92
+ async function fetchPromptABTests(timeout = SYNC_TIMEOUT) {
93
+ if (!apiKey) return;
94
+ try {
95
+ const controller = new AbortController();
96
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
97
+ const resp = await fetch(`${baseUrl}/prompt-ab-tests`, {
98
+ headers: { Authorization: `Bearer ${apiKey}` },
99
+ signal: controller.signal
100
+ });
101
+ clearTimeout(timeoutId);
102
+ if (resp.ok) {
103
+ const data = await resp.json();
104
+ for (const t of data.prompt_ab_tests || []) {
105
+ if (!promptABCache.has(t.key)) {
106
+ promptABCache.set(t.key, { versions: /* @__PURE__ */ new Map(), current: null });
107
+ }
108
+ const cached = promptABCache.get(t.key);
109
+ cached.versions.set(t.version, { variants: t.variants });
110
+ cached.current = t.version;
111
+ }
112
+ }
113
+ } catch {
114
+ }
115
+ }
116
+ function replaceVariables(template, variables) {
117
+ if (!variables) return template;
118
+ return template.replace(/\{\{(\s*\w+\s*)\}\}/g, (match, varName) => {
119
+ const key = varName.trim();
120
+ return key in variables ? String(variables[key]) : match;
121
+ });
122
+ }
123
+ function setPromptContext(ctx) {
124
+ promptContext = ctx;
125
+ }
126
+ function getPromptContext() {
127
+ const ctx = promptContext;
128
+ promptContext = null;
129
+ return ctx;
130
+ }
131
+ async function get(promptKey, options = {}) {
132
+ const { variables, version, debug = false } = options;
133
+ debugMode = debug;
134
+ ensureInit();
135
+ log(`get() called: promptKey=${promptKey}`);
136
+ let promptData = promptCache.get(promptKey);
137
+ if (!promptData) {
138
+ log("Not in cache, fetching...");
139
+ await fetchPrompts(SYNC_TIMEOUT);
140
+ promptData = promptCache.get(promptKey);
141
+ }
142
+ if (!promptData) {
143
+ throw new Error(
144
+ `Prompt '${promptKey}' not found. Check that it exists in your Fallom dashboard.`
145
+ );
146
+ }
147
+ const targetVersion = version ?? promptData.current;
148
+ const content = promptData.versions.get(targetVersion);
149
+ if (!content) {
150
+ throw new Error(
151
+ `Prompt '${promptKey}' version ${targetVersion} not found.`
152
+ );
153
+ }
154
+ const system = replaceVariables(content.systemPrompt, variables);
155
+ const user = replaceVariables(content.userTemplate, variables);
156
+ setPromptContext({
157
+ promptKey,
158
+ promptVersion: targetVersion
159
+ });
160
+ log(`\u2705 Got prompt: ${promptKey} v${targetVersion}`);
161
+ return {
162
+ key: promptKey,
163
+ version: targetVersion,
164
+ system,
165
+ user
166
+ };
167
+ }
168
+ async function getAB(abTestKey, sessionId, options = {}) {
169
+ const { variables, debug = false } = options;
170
+ debugMode = debug;
171
+ ensureInit();
172
+ log(`getAB() called: abTestKey=${abTestKey}, sessionId=${sessionId}`);
173
+ let abData = promptABCache.get(abTestKey);
174
+ if (!abData) {
175
+ log("Not in cache, fetching...");
176
+ await fetchPromptABTests(SYNC_TIMEOUT);
177
+ abData = promptABCache.get(abTestKey);
178
+ }
179
+ if (!abData) {
180
+ throw new Error(
181
+ `Prompt A/B test '${abTestKey}' not found. Check that it exists in your Fallom dashboard.`
182
+ );
183
+ }
184
+ const currentVersion = abData.current;
185
+ const versionData = abData.versions.get(currentVersion);
186
+ if (!versionData) {
187
+ throw new Error(`Prompt A/B test '${abTestKey}' has no current version.`);
188
+ }
189
+ const { variants } = versionData;
190
+ const hashBytes = (0, import_crypto.createHash)("md5").update(sessionId).digest();
191
+ const hashVal = hashBytes.readUInt32BE(0) % 1e6;
192
+ let cumulative = 0;
193
+ let selectedVariant = variants[variants.length - 1];
194
+ let selectedIndex = variants.length - 1;
195
+ for (let i = 0; i < variants.length; i++) {
196
+ cumulative += variants[i].weight * 1e4;
197
+ if (hashVal < cumulative) {
198
+ selectedVariant = variants[i];
199
+ selectedIndex = i;
200
+ break;
201
+ }
202
+ }
203
+ const promptKey = selectedVariant.prompt_key;
204
+ const promptVersion = selectedVariant.prompt_version;
205
+ let promptData = promptCache.get(promptKey);
206
+ if (!promptData) {
207
+ await fetchPrompts(SYNC_TIMEOUT);
208
+ promptData = promptCache.get(promptKey);
209
+ }
210
+ if (!promptData) {
211
+ throw new Error(
212
+ `Prompt '${promptKey}' (from A/B test '${abTestKey}') not found.`
213
+ );
214
+ }
215
+ const targetVersion = promptVersion ?? promptData.current;
216
+ const content = promptData.versions.get(targetVersion);
217
+ if (!content) {
218
+ throw new Error(
219
+ `Prompt '${promptKey}' version ${targetVersion} not found.`
220
+ );
221
+ }
222
+ const system = replaceVariables(content.systemPrompt, variables);
223
+ const user = replaceVariables(content.userTemplate, variables);
224
+ setPromptContext({
225
+ promptKey,
226
+ promptVersion: targetVersion,
227
+ abTestKey,
228
+ variantIndex: selectedIndex
229
+ });
230
+ log(
231
+ `\u2705 Got prompt from A/B: ${promptKey} v${targetVersion} (variant ${selectedIndex})`
232
+ );
233
+ return {
234
+ key: promptKey,
235
+ version: targetVersion,
236
+ system,
237
+ user,
238
+ abTestKey,
239
+ variantIndex: selectedIndex
240
+ };
241
+ }
242
+ function clearPromptContext() {
243
+ promptContext = null;
244
+ }
245
+ var import_crypto, apiKey, baseUrl, initialized, syncInterval, debugMode, promptCache, promptABCache, promptContext, SYNC_TIMEOUT;
246
+ var init_prompts = __esm({
247
+ "src/prompts.ts"() {
248
+ "use strict";
249
+ import_crypto = require("crypto");
250
+ apiKey = null;
251
+ baseUrl = "https://spans.fallom.com";
252
+ initialized = false;
253
+ syncInterval = null;
254
+ debugMode = false;
255
+ promptCache = /* @__PURE__ */ new Map();
256
+ promptABCache = /* @__PURE__ */ new Map();
257
+ promptContext = null;
258
+ SYNC_TIMEOUT = 2e3;
259
+ }
260
+ });
261
+
20
262
  // src/index.ts
21
263
  var index_exports = {};
22
264
  __export(index_exports, {
23
265
  default: () => index_default,
24
- init: () => init3,
266
+ init: () => init4,
25
267
  models: () => models_exports,
268
+ prompts: () => prompts_exports,
26
269
  trace: () => trace_exports
27
270
  });
28
271
  module.exports = __toCommonJS(index_exports);
@@ -32,7 +275,7 @@ var trace_exports = {};
32
275
  __export(trace_exports, {
33
276
  clearSession: () => clearSession,
34
277
  getSession: () => getSession,
35
- init: () => init,
278
+ init: () => init2,
36
279
  runWithSession: () => runWithSession,
37
280
  setSession: () => setSession,
38
281
  shutdown: () => shutdown,
@@ -671,29 +914,37 @@ var Resource = (
671
914
  // src/trace.ts
672
915
  var sessionStorage = new import_async_hooks.AsyncLocalStorage();
673
916
  var fallbackSession = null;
674
- var apiKey = null;
675
- var baseUrl = "https://spans.fallom.com";
676
- var initialized = false;
917
+ var apiKey2 = null;
918
+ var baseUrl2 = "https://spans.fallom.com";
919
+ var initialized2 = false;
677
920
  var captureContent = true;
678
- var debugMode = false;
921
+ var debugMode2 = false;
679
922
  var sdk = null;
680
- function log(...args) {
681
- if (debugMode) console.log("[Fallom]", ...args);
923
+ function log2(...args) {
924
+ if (debugMode2) console.log("[Fallom]", ...args);
682
925
  }
683
926
  var fallomSpanProcessor = {
684
927
  onStart(span2, _parentContext) {
685
- log("\u{1F4CD} Span started:", span2.name || "unknown");
928
+ log2("\u{1F4CD} Span started:", span2.name || "unknown");
686
929
  const ctx = sessionStorage.getStore() || fallbackSession;
687
930
  if (ctx) {
688
931
  span2.setAttribute("fallom.config_key", ctx.configKey);
689
932
  span2.setAttribute("fallom.session_id", ctx.sessionId);
690
- log(" Added session context:", ctx.configKey, ctx.sessionId);
933
+ if (ctx.customerId) {
934
+ span2.setAttribute("fallom.customer_id", ctx.customerId);
935
+ }
936
+ log2(
937
+ " Added session context:",
938
+ ctx.configKey,
939
+ ctx.sessionId,
940
+ ctx.customerId
941
+ );
691
942
  } else {
692
- log(" No session context available");
943
+ log2(" No session context available");
693
944
  }
694
945
  },
695
946
  onEnd(span2) {
696
- log("\u2705 Span ended:", span2.name, "duration:", span2.duration);
947
+ log2("\u2705 Span ended:", span2.name, "duration:", span2.duration);
697
948
  },
698
949
  shutdown() {
699
950
  return Promise.resolve();
@@ -702,33 +953,33 @@ var fallomSpanProcessor = {
702
953
  return Promise.resolve();
703
954
  }
704
955
  };
705
- async function init(options = {}) {
706
- if (initialized) return;
707
- debugMode = options.debug ?? false;
708
- log("\u{1F680} Initializing Fallom tracing...");
709
- apiKey = options.apiKey || process.env.FALLOM_API_KEY || null;
710
- baseUrl = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
956
+ async function init2(options = {}) {
957
+ if (initialized2) return;
958
+ debugMode2 = options.debug ?? false;
959
+ log2("\u{1F680} Initializing Fallom tracing...");
960
+ apiKey2 = options.apiKey || process.env.FALLOM_API_KEY || null;
961
+ baseUrl2 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
711
962
  const envCapture = process.env.FALLOM_CAPTURE_CONTENT?.toLowerCase();
712
963
  if (envCapture === "false" || envCapture === "0" || envCapture === "no") {
713
964
  captureContent = false;
714
965
  } else {
715
966
  captureContent = options.captureContent ?? true;
716
967
  }
717
- if (!apiKey) {
968
+ if (!apiKey2) {
718
969
  throw new Error(
719
970
  "No API key provided. Set FALLOM_API_KEY environment variable or pass apiKey parameter."
720
971
  );
721
972
  }
722
- initialized = true;
723
- log("\u{1F4E1} Exporter URL:", `${baseUrl}/v1/traces`);
973
+ initialized2 = true;
974
+ log2("\u{1F4E1} Exporter URL:", `${baseUrl2}/v1/traces`);
724
975
  const exporter = new import_exporter_trace_otlp_http.OTLPTraceExporter({
725
- url: `${baseUrl}/v1/traces`,
976
+ url: `${baseUrl2}/v1/traces`,
726
977
  headers: {
727
- Authorization: `Bearer ${apiKey}`
978
+ Authorization: `Bearer ${apiKey2}`
728
979
  }
729
980
  });
730
981
  const instrumentations = await getInstrumentations();
731
- log("\u{1F527} Loaded instrumentations:", instrumentations.length);
982
+ log2("\u{1F527} Loaded instrumentations:", instrumentations.length);
732
983
  sdk = new import_sdk_node.NodeSDK({
733
984
  resource: new Resource({
734
985
  "service.name": "fallom-traced-app"
@@ -738,20 +989,48 @@ async function init(options = {}) {
738
989
  instrumentations
739
990
  });
740
991
  sdk.start();
741
- log("\u2705 SDK started");
992
+ log2("\u2705 SDK started");
742
993
  process.on("SIGTERM", () => {
743
994
  sdk?.shutdown().catch(console.error);
744
995
  });
745
996
  }
746
997
  async function getInstrumentations() {
747
998
  const instrumentations = [];
748
- await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-openai", "OpenAIInstrumentation");
749
- await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-anthropic", "AnthropicInstrumentation");
750
- await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-cohere", "CohereInstrumentation");
751
- await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-bedrock", "BedrockInstrumentation");
752
- await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-google-generativeai", "GoogleGenerativeAIInstrumentation");
753
- await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-azure", "AzureOpenAIInstrumentation");
754
- await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-vertexai", "VertexAIInstrumentation");
999
+ await tryAddInstrumentation(
1000
+ instrumentations,
1001
+ "@traceloop/instrumentation-openai",
1002
+ "OpenAIInstrumentation"
1003
+ );
1004
+ await tryAddInstrumentation(
1005
+ instrumentations,
1006
+ "@traceloop/instrumentation-anthropic",
1007
+ "AnthropicInstrumentation"
1008
+ );
1009
+ await tryAddInstrumentation(
1010
+ instrumentations,
1011
+ "@traceloop/instrumentation-cohere",
1012
+ "CohereInstrumentation"
1013
+ );
1014
+ await tryAddInstrumentation(
1015
+ instrumentations,
1016
+ "@traceloop/instrumentation-bedrock",
1017
+ "BedrockInstrumentation"
1018
+ );
1019
+ await tryAddInstrumentation(
1020
+ instrumentations,
1021
+ "@traceloop/instrumentation-google-generativeai",
1022
+ "GoogleGenerativeAIInstrumentation"
1023
+ );
1024
+ await tryAddInstrumentation(
1025
+ instrumentations,
1026
+ "@traceloop/instrumentation-azure",
1027
+ "AzureOpenAIInstrumentation"
1028
+ );
1029
+ await tryAddInstrumentation(
1030
+ instrumentations,
1031
+ "@traceloop/instrumentation-vertexai",
1032
+ "VertexAIInstrumentation"
1033
+ );
755
1034
  return instrumentations;
756
1035
  }
757
1036
  async function tryAddInstrumentation(instrumentations, pkg, className) {
@@ -759,25 +1038,37 @@ async function tryAddInstrumentation(instrumentations, pkg, className) {
759
1038
  const mod = await import(pkg);
760
1039
  const InstrumentationClass = mod[className] || mod.default?.[className];
761
1040
  if (InstrumentationClass) {
762
- instrumentations.push(new InstrumentationClass({ traceContent: captureContent }));
763
- log(` \u2705 Loaded ${pkg}`);
1041
+ instrumentations.push(
1042
+ new InstrumentationClass({ traceContent: captureContent })
1043
+ );
1044
+ log2(` \u2705 Loaded ${pkg}`);
764
1045
  } else {
765
- log(` \u26A0\uFE0F ${pkg} loaded but ${className} not found. Available:`, Object.keys(mod));
1046
+ log2(
1047
+ ` \u26A0\uFE0F ${pkg} loaded but ${className} not found. Available:`,
1048
+ Object.keys(mod)
1049
+ );
766
1050
  }
767
1051
  } catch (e) {
768
- log(` \u274C ${pkg} not installed`);
1052
+ log2(` \u274C ${pkg} not installed`);
769
1053
  }
770
1054
  }
771
- function setSession(configKey, sessionId) {
1055
+ function setSession(configKey, sessionId, customerId) {
772
1056
  const store = sessionStorage.getStore();
773
1057
  if (store) {
774
1058
  store.configKey = configKey;
775
1059
  store.sessionId = sessionId;
1060
+ store.customerId = customerId;
776
1061
  }
777
- fallbackSession = { configKey, sessionId };
1062
+ fallbackSession = { configKey, sessionId, customerId };
778
1063
  }
779
- function runWithSession(configKey, sessionId, fn) {
780
- return sessionStorage.run({ configKey, sessionId }, fn);
1064
+ function runWithSession(configKey, sessionId, customerIdOrFn, fn) {
1065
+ if (typeof customerIdOrFn === "function") {
1066
+ return sessionStorage.run({ configKey, sessionId }, customerIdOrFn);
1067
+ }
1068
+ return sessionStorage.run(
1069
+ { configKey, sessionId, customerId: customerIdOrFn },
1070
+ fn
1071
+ );
781
1072
  }
782
1073
  function getSession() {
783
1074
  return sessionStorage.getStore() || fallbackSession || void 0;
@@ -786,7 +1077,7 @@ function clearSession() {
786
1077
  fallbackSession = null;
787
1078
  }
788
1079
  function span(data, options = {}) {
789
- if (!initialized) {
1080
+ if (!initialized2) {
790
1081
  throw new Error("Fallom not initialized. Call trace.init() first.");
791
1082
  }
792
1083
  const ctx = sessionStorage.getStore() || fallbackSession;
@@ -804,10 +1095,10 @@ async function sendSpan(configKey, sessionId, data) {
804
1095
  try {
805
1096
  const controller = new AbortController();
806
1097
  const timeoutId = setTimeout(() => controller.abort(), 5e3);
807
- await fetch(`${baseUrl}/spans`, {
1098
+ await fetch(`${baseUrl2}/spans`, {
808
1099
  method: "POST",
809
1100
  headers: {
810
- Authorization: `Bearer ${apiKey}`,
1101
+ Authorization: `Bearer ${apiKey2}`,
811
1102
  "Content-Type": "application/json"
812
1103
  },
813
1104
  body: JSON.stringify({
@@ -824,34 +1115,42 @@ async function sendSpan(configKey, sessionId, data) {
824
1115
  async function shutdown() {
825
1116
  if (sdk) {
826
1117
  await sdk.shutdown();
827
- initialized = false;
1118
+ initialized2 = false;
828
1119
  }
829
1120
  }
830
1121
  async function sendTrace(trace) {
831
1122
  try {
832
1123
  const controller = new AbortController();
833
1124
  const timeoutId = setTimeout(() => controller.abort(), 5e3);
834
- await fetch(`${baseUrl}/v1/traces`, {
1125
+ await fetch(`${baseUrl2}/v1/traces`, {
835
1126
  method: "POST",
836
1127
  headers: {
837
- Authorization: `Bearer ${apiKey}`,
1128
+ Authorization: `Bearer ${apiKey2}`,
838
1129
  "Content-Type": "application/json"
839
1130
  },
840
1131
  body: JSON.stringify(trace),
841
1132
  signal: controller.signal
842
1133
  });
843
1134
  clearTimeout(timeoutId);
844
- log("\u{1F4E4} Trace sent:", trace.name, trace.model);
1135
+ log2("\u{1F4E4} Trace sent:", trace.name, trace.model);
845
1136
  } catch {
846
1137
  }
847
1138
  }
848
1139
  function wrapOpenAI(client) {
849
- const originalCreate = client.chat.completions.create.bind(client.chat.completions);
1140
+ const originalCreate = client.chat.completions.create.bind(
1141
+ client.chat.completions
1142
+ );
850
1143
  client.chat.completions.create = async function(...args) {
851
1144
  const ctx = sessionStorage.getStore() || fallbackSession;
852
- if (!ctx || !initialized) {
1145
+ if (!ctx || !initialized2) {
853
1146
  return originalCreate(...args);
854
1147
  }
1148
+ let promptCtx = null;
1149
+ try {
1150
+ const { getPromptContext: getPromptContext2 } = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
1151
+ promptCtx = getPromptContext2();
1152
+ } catch {
1153
+ }
855
1154
  const params = args[0] || {};
856
1155
  const startTime = Date.now();
857
1156
  try {
@@ -860,6 +1159,7 @@ function wrapOpenAI(client) {
860
1159
  sendTrace({
861
1160
  config_key: ctx.configKey,
862
1161
  session_id: ctx.sessionId,
1162
+ customer_id: ctx.customerId,
863
1163
  name: "chat.completions.create",
864
1164
  model: response?.model || params?.model,
865
1165
  start_time: new Date(startTime).toISOString(),
@@ -870,7 +1170,11 @@ function wrapOpenAI(client) {
870
1170
  completion_tokens: response?.usage?.completion_tokens,
871
1171
  total_tokens: response?.usage?.total_tokens,
872
1172
  input: captureContent ? JSON.stringify(params?.messages) : void 0,
873
- output: captureContent ? response?.choices?.[0]?.message?.content : void 0
1173
+ output: captureContent ? response?.choices?.[0]?.message?.content : void 0,
1174
+ prompt_key: promptCtx?.promptKey,
1175
+ prompt_version: promptCtx?.promptVersion,
1176
+ prompt_ab_test_key: promptCtx?.abTestKey,
1177
+ prompt_variant_index: promptCtx?.variantIndex
874
1178
  }).catch(() => {
875
1179
  });
876
1180
  return response;
@@ -879,13 +1183,18 @@ function wrapOpenAI(client) {
879
1183
  sendTrace({
880
1184
  config_key: ctx.configKey,
881
1185
  session_id: ctx.sessionId,
1186
+ customer_id: ctx.customerId,
882
1187
  name: "chat.completions.create",
883
1188
  model: params?.model,
884
1189
  start_time: new Date(startTime).toISOString(),
885
1190
  end_time: new Date(endTime).toISOString(),
886
1191
  duration_ms: endTime - startTime,
887
1192
  status: "ERROR",
888
- error_message: error?.message
1193
+ error_message: error?.message,
1194
+ prompt_key: promptCtx?.promptKey,
1195
+ prompt_version: promptCtx?.promptVersion,
1196
+ prompt_ab_test_key: promptCtx?.abTestKey,
1197
+ prompt_variant_index: promptCtx?.variantIndex
889
1198
  }).catch(() => {
890
1199
  });
891
1200
  throw error;
@@ -897,9 +1206,15 @@ function wrapAnthropic(client) {
897
1206
  const originalCreate = client.messages.create.bind(client.messages);
898
1207
  client.messages.create = async function(...args) {
899
1208
  const ctx = sessionStorage.getStore() || fallbackSession;
900
- if (!ctx || !initialized) {
1209
+ if (!ctx || !initialized2) {
901
1210
  return originalCreate(...args);
902
1211
  }
1212
+ let promptCtx = null;
1213
+ try {
1214
+ const { getPromptContext: getPromptContext2 } = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
1215
+ promptCtx = getPromptContext2();
1216
+ } catch {
1217
+ }
903
1218
  const params = args[0] || {};
904
1219
  const startTime = Date.now();
905
1220
  try {
@@ -908,6 +1223,7 @@ function wrapAnthropic(client) {
908
1223
  sendTrace({
909
1224
  config_key: ctx.configKey,
910
1225
  session_id: ctx.sessionId,
1226
+ customer_id: ctx.customerId,
911
1227
  name: "messages.create",
912
1228
  model: response?.model || params?.model,
913
1229
  start_time: new Date(startTime).toISOString(),
@@ -918,7 +1234,11 @@ function wrapAnthropic(client) {
918
1234
  completion_tokens: response?.usage?.output_tokens,
919
1235
  total_tokens: (response?.usage?.input_tokens || 0) + (response?.usage?.output_tokens || 0),
920
1236
  input: captureContent ? JSON.stringify(params?.messages) : void 0,
921
- output: captureContent ? response?.content?.[0]?.text : void 0
1237
+ output: captureContent ? response?.content?.[0]?.text : void 0,
1238
+ prompt_key: promptCtx?.promptKey,
1239
+ prompt_version: promptCtx?.promptVersion,
1240
+ prompt_ab_test_key: promptCtx?.abTestKey,
1241
+ prompt_variant_index: promptCtx?.variantIndex
922
1242
  }).catch(() => {
923
1243
  });
924
1244
  return response;
@@ -927,13 +1247,18 @@ function wrapAnthropic(client) {
927
1247
  sendTrace({
928
1248
  config_key: ctx.configKey,
929
1249
  session_id: ctx.sessionId,
1250
+ customer_id: ctx.customerId,
930
1251
  name: "messages.create",
931
1252
  model: params?.model,
932
1253
  start_time: new Date(startTime).toISOString(),
933
1254
  end_time: new Date(endTime).toISOString(),
934
1255
  duration_ms: endTime - startTime,
935
1256
  status: "ERROR",
936
- error_message: error?.message
1257
+ error_message: error?.message,
1258
+ prompt_key: promptCtx?.promptKey,
1259
+ prompt_version: promptCtx?.promptVersion,
1260
+ prompt_ab_test_key: promptCtx?.abTestKey,
1261
+ prompt_variant_index: promptCtx?.variantIndex
937
1262
  }).catch(() => {
938
1263
  });
939
1264
  throw error;
@@ -945,9 +1270,15 @@ function wrapGoogleAI(model) {
945
1270
  const originalGenerate = model.generateContent.bind(model);
946
1271
  model.generateContent = async function(...args) {
947
1272
  const ctx = sessionStorage.getStore() || fallbackSession;
948
- if (!ctx || !initialized) {
1273
+ if (!ctx || !initialized2) {
949
1274
  return originalGenerate(...args);
950
1275
  }
1276
+ let promptCtx = null;
1277
+ try {
1278
+ const { getPromptContext: getPromptContext2 } = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
1279
+ promptCtx = getPromptContext2();
1280
+ } catch {
1281
+ }
951
1282
  const startTime = Date.now();
952
1283
  try {
953
1284
  const response = await originalGenerate(...args);
@@ -957,6 +1288,7 @@ function wrapGoogleAI(model) {
957
1288
  sendTrace({
958
1289
  config_key: ctx.configKey,
959
1290
  session_id: ctx.sessionId,
1291
+ customer_id: ctx.customerId,
960
1292
  name: "generateContent",
961
1293
  model: model?.model || "gemini",
962
1294
  start_time: new Date(startTime).toISOString(),
@@ -967,7 +1299,11 @@ function wrapGoogleAI(model) {
967
1299
  completion_tokens: usage?.candidatesTokenCount,
968
1300
  total_tokens: usage?.totalTokenCount,
969
1301
  input: captureContent ? JSON.stringify(args[0]) : void 0,
970
- output: captureContent ? result?.text?.() : void 0
1302
+ output: captureContent ? result?.text?.() : void 0,
1303
+ prompt_key: promptCtx?.promptKey,
1304
+ prompt_version: promptCtx?.promptVersion,
1305
+ prompt_ab_test_key: promptCtx?.abTestKey,
1306
+ prompt_variant_index: promptCtx?.variantIndex
971
1307
  }).catch(() => {
972
1308
  });
973
1309
  return response;
@@ -976,13 +1312,18 @@ function wrapGoogleAI(model) {
976
1312
  sendTrace({
977
1313
  config_key: ctx.configKey,
978
1314
  session_id: ctx.sessionId,
1315
+ customer_id: ctx.customerId,
979
1316
  name: "generateContent",
980
1317
  model: model?.model || "gemini",
981
1318
  start_time: new Date(startTime).toISOString(),
982
1319
  end_time: new Date(endTime).toISOString(),
983
1320
  duration_ms: endTime - startTime,
984
1321
  status: "ERROR",
985
- error_message: error?.message
1322
+ error_message: error?.message,
1323
+ prompt_key: promptCtx?.promptKey,
1324
+ prompt_version: promptCtx?.promptVersion,
1325
+ prompt_ab_test_key: promptCtx?.abTestKey,
1326
+ prompt_variant_index: promptCtx?.variantIndex
986
1327
  }).catch(() => {
987
1328
  });
988
1329
  throw error;
@@ -994,71 +1335,71 @@ function wrapGoogleAI(model) {
994
1335
  // src/models.ts
995
1336
  var models_exports = {};
996
1337
  __export(models_exports, {
997
- get: () => get,
998
- init: () => init2
1338
+ get: () => get2,
1339
+ init: () => init3
999
1340
  });
1000
- var import_crypto = require("crypto");
1001
- var apiKey2 = null;
1002
- var baseUrl2 = "https://spans.fallom.com";
1003
- var initialized2 = false;
1004
- var syncInterval = null;
1005
- var debugMode2 = false;
1341
+ var import_crypto2 = require("crypto");
1342
+ var apiKey3 = null;
1343
+ var baseUrl3 = "https://spans.fallom.com";
1344
+ var initialized3 = false;
1345
+ var syncInterval2 = null;
1346
+ var debugMode3 = false;
1006
1347
  var configCache = /* @__PURE__ */ new Map();
1007
- var SYNC_TIMEOUT = 2e3;
1348
+ var SYNC_TIMEOUT2 = 2e3;
1008
1349
  var RECORD_TIMEOUT = 1e3;
1009
- function log2(msg) {
1010
- if (debugMode2) {
1350
+ function log3(msg) {
1351
+ if (debugMode3) {
1011
1352
  console.log(`[Fallom] ${msg}`);
1012
1353
  }
1013
1354
  }
1014
- function init2(options = {}) {
1015
- apiKey2 = options.apiKey || process.env.FALLOM_API_KEY || null;
1016
- baseUrl2 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
1017
- initialized2 = true;
1018
- if (!apiKey2) {
1355
+ function init3(options = {}) {
1356
+ apiKey3 = options.apiKey || process.env.FALLOM_API_KEY || null;
1357
+ baseUrl3 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
1358
+ initialized3 = true;
1359
+ if (!apiKey3) {
1019
1360
  return;
1020
1361
  }
1021
1362
  fetchConfigs().catch(() => {
1022
1363
  });
1023
- if (!syncInterval) {
1024
- syncInterval = setInterval(() => {
1364
+ if (!syncInterval2) {
1365
+ syncInterval2 = setInterval(() => {
1025
1366
  fetchConfigs().catch(() => {
1026
1367
  });
1027
1368
  }, 3e4);
1028
- syncInterval.unref();
1369
+ syncInterval2.unref();
1029
1370
  }
1030
1371
  }
1031
- function ensureInit() {
1032
- if (!initialized2) {
1372
+ function ensureInit2() {
1373
+ if (!initialized3) {
1033
1374
  try {
1034
- init2();
1375
+ init3();
1035
1376
  } catch {
1036
1377
  }
1037
1378
  }
1038
1379
  }
1039
- async function fetchConfigs(timeout = SYNC_TIMEOUT) {
1040
- if (!apiKey2) {
1041
- log2("_fetchConfigs: No API key, skipping");
1380
+ async function fetchConfigs(timeout = SYNC_TIMEOUT2) {
1381
+ if (!apiKey3) {
1382
+ log3("_fetchConfigs: No API key, skipping");
1042
1383
  return;
1043
1384
  }
1044
1385
  try {
1045
- log2(`Fetching configs from ${baseUrl2}/configs`);
1386
+ log3(`Fetching configs from ${baseUrl3}/configs`);
1046
1387
  const controller = new AbortController();
1047
1388
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1048
- const resp = await fetch(`${baseUrl2}/configs`, {
1049
- headers: { Authorization: `Bearer ${apiKey2}` },
1389
+ const resp = await fetch(`${baseUrl3}/configs`, {
1390
+ headers: { Authorization: `Bearer ${apiKey3}` },
1050
1391
  signal: controller.signal
1051
1392
  });
1052
1393
  clearTimeout(timeoutId);
1053
- log2(`Response status: ${resp.status}`);
1394
+ log3(`Response status: ${resp.status}`);
1054
1395
  if (resp.ok) {
1055
1396
  const data = await resp.json();
1056
1397
  const configs = data.configs || [];
1057
- log2(`Got ${configs.length} configs: ${configs.map((c) => c.key)}`);
1398
+ log3(`Got ${configs.length} configs: ${configs.map((c) => c.key)}`);
1058
1399
  for (const c of configs) {
1059
1400
  const key = c.key;
1060
1401
  const version = c.version || 1;
1061
- log2(`Config '${key}' v${version}: ${JSON.stringify(c.variants)}`);
1402
+ log3(`Config '${key}' v${version}: ${JSON.stringify(c.variants)}`);
1062
1403
  if (!configCache.has(key)) {
1063
1404
  configCache.set(key, { versions: /* @__PURE__ */ new Map(), latest: null });
1064
1405
  }
@@ -1067,21 +1408,21 @@ async function fetchConfigs(timeout = SYNC_TIMEOUT) {
1067
1408
  cached.latest = version;
1068
1409
  }
1069
1410
  } else {
1070
- log2(`Fetch failed: ${resp.statusText}`);
1411
+ log3(`Fetch failed: ${resp.statusText}`);
1071
1412
  }
1072
1413
  } catch (e) {
1073
- log2(`Fetch exception: ${e}`);
1414
+ log3(`Fetch exception: ${e}`);
1074
1415
  }
1075
1416
  }
1076
- async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT) {
1077
- if (!apiKey2) return null;
1417
+ async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT2) {
1418
+ if (!apiKey3) return null;
1078
1419
  try {
1079
1420
  const controller = new AbortController();
1080
1421
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1081
1422
  const resp = await fetch(
1082
- `${baseUrl2}/configs/${configKey}/version/${version}`,
1423
+ `${baseUrl3}/configs/${configKey}/version/${version}`,
1083
1424
  {
1084
- headers: { Authorization: `Bearer ${apiKey2}` },
1425
+ headers: { Authorization: `Bearer ${apiKey3}` },
1085
1426
  signal: controller.signal
1086
1427
  }
1087
1428
  );
@@ -1098,22 +1439,22 @@ async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT)
1098
1439
  }
1099
1440
  return null;
1100
1441
  }
1101
- async function get(configKey, sessionId, options = {}) {
1442
+ async function get2(configKey, sessionId, options = {}) {
1102
1443
  const { version, fallback, debug = false } = options;
1103
- debugMode2 = debug;
1104
- ensureInit();
1105
- log2(`get() called: configKey=${configKey}, sessionId=${sessionId}, fallback=${fallback}`);
1444
+ debugMode3 = debug;
1445
+ ensureInit2();
1446
+ log3(`get() called: configKey=${configKey}, sessionId=${sessionId}, fallback=${fallback}`);
1106
1447
  try {
1107
1448
  let configData = configCache.get(configKey);
1108
- log2(`Cache lookup for '${configKey}': ${configData ? "found" : "not found"}`);
1449
+ log3(`Cache lookup for '${configKey}': ${configData ? "found" : "not found"}`);
1109
1450
  if (!configData) {
1110
- log2("Not in cache, fetching...");
1111
- await fetchConfigs(SYNC_TIMEOUT);
1451
+ log3("Not in cache, fetching...");
1452
+ await fetchConfigs(SYNC_TIMEOUT2);
1112
1453
  configData = configCache.get(configKey);
1113
- log2(`After fetch, cache lookup: ${configData ? "found" : "still not found"}`);
1454
+ log3(`After fetch, cache lookup: ${configData ? "found" : "still not found"}`);
1114
1455
  }
1115
1456
  if (!configData) {
1116
- log2(`Config not found, using fallback: ${fallback}`);
1457
+ log3(`Config not found, using fallback: ${fallback}`);
1117
1458
  if (fallback) {
1118
1459
  console.warn(`[Fallom WARNING] Config '${configKey}' not found, using fallback model: ${fallback}`);
1119
1460
  return returnWithTrace(configKey, sessionId, fallback, 0);
@@ -1127,7 +1468,7 @@ async function get(configKey, sessionId, options = {}) {
1127
1468
  if (version !== void 0) {
1128
1469
  config = configData.versions.get(version);
1129
1470
  if (!config) {
1130
- config = await fetchSpecificVersion(configKey, version, SYNC_TIMEOUT) || void 0;
1471
+ config = await fetchSpecificVersion(configKey, version, SYNC_TIMEOUT2) || void 0;
1131
1472
  }
1132
1473
  if (!config) {
1133
1474
  if (fallback) {
@@ -1151,22 +1492,22 @@ async function get(configKey, sessionId, options = {}) {
1151
1492
  const variantsRaw = config.variants;
1152
1493
  const configVersion = config.version || targetVersion;
1153
1494
  const variants = Array.isArray(variantsRaw) ? variantsRaw : Object.values(variantsRaw);
1154
- log2(`Config found! Version: ${configVersion}, Variants: ${JSON.stringify(variants)}`);
1155
- const hashBytes = (0, import_crypto.createHash)("md5").update(sessionId).digest();
1495
+ log3(`Config found! Version: ${configVersion}, Variants: ${JSON.stringify(variants)}`);
1496
+ const hashBytes = (0, import_crypto2.createHash)("md5").update(sessionId).digest();
1156
1497
  const hashVal = hashBytes.readUInt32BE(0) % 1e6;
1157
- log2(`Session hash: ${hashVal} (out of 1,000,000)`);
1498
+ log3(`Session hash: ${hashVal} (out of 1,000,000)`);
1158
1499
  let cumulative = 0;
1159
1500
  let assignedModel = variants[variants.length - 1].model;
1160
1501
  for (const v of variants) {
1161
1502
  const oldCumulative = cumulative;
1162
1503
  cumulative += v.weight * 1e4;
1163
- log2(`Variant ${v.model}: weight=${v.weight}%, range=${oldCumulative}-${cumulative}, hash=${hashVal}, match=${hashVal < cumulative}`);
1504
+ log3(`Variant ${v.model}: weight=${v.weight}%, range=${oldCumulative}-${cumulative}, hash=${hashVal}, match=${hashVal < cumulative}`);
1164
1505
  if (hashVal < cumulative) {
1165
1506
  assignedModel = v.model;
1166
1507
  break;
1167
1508
  }
1168
1509
  }
1169
- log2(`\u2705 Assigned model: ${assignedModel}`);
1510
+ log3(`\u2705 Assigned model: ${assignedModel}`);
1170
1511
  return returnWithTrace(configKey, sessionId, assignedModel, configVersion);
1171
1512
  } catch (e) {
1172
1513
  if (e instanceof Error && e.message.includes("not found")) {
@@ -1191,14 +1532,14 @@ function returnWithTrace(configKey, sessionId, model, version) {
1191
1532
  return model;
1192
1533
  }
1193
1534
  async function recordSession(configKey, version, sessionId, model) {
1194
- if (!apiKey2) return;
1535
+ if (!apiKey3) return;
1195
1536
  try {
1196
1537
  const controller = new AbortController();
1197
1538
  const timeoutId = setTimeout(() => controller.abort(), RECORD_TIMEOUT);
1198
- await fetch(`${baseUrl2}/sessions`, {
1539
+ await fetch(`${baseUrl3}/sessions`, {
1199
1540
  method: "POST",
1200
1541
  headers: {
1201
- Authorization: `Bearer ${apiKey2}`,
1542
+ Authorization: `Bearer ${apiKey3}`,
1202
1543
  "Content-Type": "application/json"
1203
1544
  },
1204
1545
  body: JSON.stringify({
@@ -1214,30 +1555,41 @@ async function recordSession(configKey, version, sessionId, model) {
1214
1555
  }
1215
1556
  }
1216
1557
 
1558
+ // src/index.ts
1559
+ init_prompts();
1560
+
1217
1561
  // src/init.ts
1218
- async function init3(options = {}) {
1219
- const baseUrl3 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
1220
- await init({
1562
+ init_prompts();
1563
+ async function init4(options = {}) {
1564
+ const baseUrl4 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
1565
+ await init2({
1221
1566
  apiKey: options.apiKey,
1222
- baseUrl: baseUrl3,
1567
+ baseUrl: baseUrl4,
1223
1568
  captureContent: options.captureContent,
1224
1569
  debug: options.debug
1225
1570
  });
1226
- init2({
1571
+ init3({
1572
+ apiKey: options.apiKey,
1573
+ baseUrl: baseUrl4
1574
+ });
1575
+ init({
1227
1576
  apiKey: options.apiKey,
1228
- baseUrl: baseUrl3
1577
+ baseUrl: baseUrl4
1229
1578
  });
1230
1579
  }
1231
1580
 
1232
1581
  // src/index.ts
1582
+ init_prompts();
1233
1583
  var index_default = {
1234
- init: init3,
1584
+ init: init4,
1235
1585
  trace: trace_exports,
1236
- models: models_exports
1586
+ models: models_exports,
1587
+ prompts: prompts_exports
1237
1588
  };
1238
1589
  // Annotate the CommonJS export names for ESM import in node:
1239
1590
  0 && (module.exports = {
1240
1591
  init,
1241
1592
  models,
1593
+ prompts,
1242
1594
  trace
1243
1595
  });