@fallom/trace 0.1.1 → 0.1.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.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,11 +275,14 @@ 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,
39
- span: () => span
282
+ span: () => span,
283
+ wrapAnthropic: () => wrapAnthropic,
284
+ wrapGoogleAI: () => wrapGoogleAI,
285
+ wrapOpenAI: () => wrapOpenAI
40
286
  });
41
287
  var import_async_hooks = require("async_hooks");
42
288
  var import_sdk_node = require("@opentelemetry/sdk-node");
@@ -668,20 +914,29 @@ var Resource = (
668
914
  // src/trace.ts
669
915
  var sessionStorage = new import_async_hooks.AsyncLocalStorage();
670
916
  var fallbackSession = null;
671
- var apiKey = null;
672
- var baseUrl = "https://spans.fallom.com";
673
- var initialized = false;
917
+ var apiKey2 = null;
918
+ var baseUrl2 = "https://spans.fallom.com";
919
+ var initialized2 = false;
674
920
  var captureContent = true;
921
+ var debugMode2 = false;
675
922
  var sdk = null;
923
+ function log2(...args) {
924
+ if (debugMode2) console.log("[Fallom]", ...args);
925
+ }
676
926
  var fallomSpanProcessor = {
677
927
  onStart(span2, _parentContext) {
928
+ log2("\u{1F4CD} Span started:", span2.name || "unknown");
678
929
  const ctx = sessionStorage.getStore() || fallbackSession;
679
930
  if (ctx) {
680
931
  span2.setAttribute("fallom.config_key", ctx.configKey);
681
932
  span2.setAttribute("fallom.session_id", ctx.sessionId);
933
+ log2(" Added session context:", ctx.configKey, ctx.sessionId);
934
+ } else {
935
+ log2(" No session context available");
682
936
  }
683
937
  },
684
- onEnd(_span) {
938
+ onEnd(span2) {
939
+ log2("\u2705 Span ended:", span2.name, "duration:", span2.duration);
685
940
  },
686
941
  shutdown() {
687
942
  return Promise.resolve();
@@ -690,55 +945,103 @@ var fallomSpanProcessor = {
690
945
  return Promise.resolve();
691
946
  }
692
947
  };
693
- function init(options = {}) {
694
- if (initialized) return;
695
- apiKey = options.apiKey || process.env.FALLOM_API_KEY || null;
696
- baseUrl = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
948
+ async function init2(options = {}) {
949
+ if (initialized2) return;
950
+ debugMode2 = options.debug ?? false;
951
+ log2("\u{1F680} Initializing Fallom tracing...");
952
+ apiKey2 = options.apiKey || process.env.FALLOM_API_KEY || null;
953
+ baseUrl2 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
697
954
  const envCapture = process.env.FALLOM_CAPTURE_CONTENT?.toLowerCase();
698
955
  if (envCapture === "false" || envCapture === "0" || envCapture === "no") {
699
956
  captureContent = false;
700
957
  } else {
701
958
  captureContent = options.captureContent ?? true;
702
959
  }
703
- if (!apiKey) {
960
+ if (!apiKey2) {
704
961
  throw new Error(
705
962
  "No API key provided. Set FALLOM_API_KEY environment variable or pass apiKey parameter."
706
963
  );
707
964
  }
708
- initialized = true;
965
+ initialized2 = true;
966
+ log2("\u{1F4E1} Exporter URL:", `${baseUrl2}/v1/traces`);
709
967
  const exporter = new import_exporter_trace_otlp_http.OTLPTraceExporter({
710
- url: `${baseUrl}/v1/traces`,
968
+ url: `${baseUrl2}/v1/traces`,
711
969
  headers: {
712
- Authorization: `Bearer ${apiKey}`
970
+ Authorization: `Bearer ${apiKey2}`
713
971
  }
714
972
  });
973
+ const instrumentations = await getInstrumentations();
974
+ log2("\u{1F527} Loaded instrumentations:", instrumentations.length);
715
975
  sdk = new import_sdk_node.NodeSDK({
716
976
  resource: new Resource({
717
977
  "service.name": "fallom-traced-app"
718
978
  }),
719
979
  traceExporter: exporter,
720
- spanProcessor: fallomSpanProcessor
980
+ spanProcessor: fallomSpanProcessor,
981
+ instrumentations
721
982
  });
722
983
  sdk.start();
723
- autoInstrument();
984
+ log2("\u2705 SDK started");
724
985
  process.on("SIGTERM", () => {
725
986
  sdk?.shutdown().catch(console.error);
726
987
  });
727
988
  }
728
- function autoInstrument() {
989
+ async function getInstrumentations() {
990
+ const instrumentations = [];
991
+ await tryAddInstrumentation(
992
+ instrumentations,
993
+ "@traceloop/instrumentation-openai",
994
+ "OpenAIInstrumentation"
995
+ );
996
+ await tryAddInstrumentation(
997
+ instrumentations,
998
+ "@traceloop/instrumentation-anthropic",
999
+ "AnthropicInstrumentation"
1000
+ );
1001
+ await tryAddInstrumentation(
1002
+ instrumentations,
1003
+ "@traceloop/instrumentation-cohere",
1004
+ "CohereInstrumentation"
1005
+ );
1006
+ await tryAddInstrumentation(
1007
+ instrumentations,
1008
+ "@traceloop/instrumentation-bedrock",
1009
+ "BedrockInstrumentation"
1010
+ );
1011
+ await tryAddInstrumentation(
1012
+ instrumentations,
1013
+ "@traceloop/instrumentation-google-generativeai",
1014
+ "GoogleGenerativeAIInstrumentation"
1015
+ );
1016
+ await tryAddInstrumentation(
1017
+ instrumentations,
1018
+ "@traceloop/instrumentation-azure",
1019
+ "AzureOpenAIInstrumentation"
1020
+ );
1021
+ await tryAddInstrumentation(
1022
+ instrumentations,
1023
+ "@traceloop/instrumentation-vertexai",
1024
+ "VertexAIInstrumentation"
1025
+ );
1026
+ return instrumentations;
1027
+ }
1028
+ async function tryAddInstrumentation(instrumentations, pkg, className) {
729
1029
  try {
730
- const traceloopModule = require("@traceloop/node-server-sdk");
731
- const Traceloop = traceloopModule.Traceloop || traceloopModule.default?.Traceloop || traceloopModule;
732
- if (!Traceloop?.initialize) {
733
- return;
1030
+ const mod = await import(pkg);
1031
+ const InstrumentationClass = mod[className] || mod.default?.[className];
1032
+ if (InstrumentationClass) {
1033
+ instrumentations.push(
1034
+ new InstrumentationClass({ traceContent: captureContent })
1035
+ );
1036
+ log2(` \u2705 Loaded ${pkg}`);
1037
+ } else {
1038
+ log2(
1039
+ ` \u26A0\uFE0F ${pkg} loaded but ${className} not found. Available:`,
1040
+ Object.keys(mod)
1041
+ );
734
1042
  }
735
- Traceloop.initialize({
736
- baseUrl,
737
- apiKey,
738
- disableBatch: true,
739
- traceContent: captureContent
740
- });
741
- } catch {
1043
+ } catch (e) {
1044
+ log2(` \u274C ${pkg} not installed`);
742
1045
  }
743
1046
  }
744
1047
  function setSession(configKey, sessionId) {
@@ -759,7 +1062,7 @@ function clearSession() {
759
1062
  fallbackSession = null;
760
1063
  }
761
1064
  function span(data, options = {}) {
762
- if (!initialized) {
1065
+ if (!initialized2) {
763
1066
  throw new Error("Fallom not initialized. Call trace.init() first.");
764
1067
  }
765
1068
  const ctx = sessionStorage.getStore() || fallbackSession;
@@ -777,10 +1080,10 @@ async function sendSpan(configKey, sessionId, data) {
777
1080
  try {
778
1081
  const controller = new AbortController();
779
1082
  const timeoutId = setTimeout(() => controller.abort(), 5e3);
780
- await fetch(`${baseUrl}/spans`, {
1083
+ await fetch(`${baseUrl2}/spans`, {
781
1084
  method: "POST",
782
1085
  headers: {
783
- Authorization: `Bearer ${apiKey}`,
1086
+ Authorization: `Bearer ${apiKey2}`,
784
1087
  "Content-Type": "application/json"
785
1088
  },
786
1089
  body: JSON.stringify({
@@ -797,78 +1100,285 @@ async function sendSpan(configKey, sessionId, data) {
797
1100
  async function shutdown() {
798
1101
  if (sdk) {
799
1102
  await sdk.shutdown();
800
- initialized = false;
1103
+ initialized2 = false;
1104
+ }
1105
+ }
1106
+ async function sendTrace(trace) {
1107
+ try {
1108
+ const controller = new AbortController();
1109
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
1110
+ await fetch(`${baseUrl2}/v1/traces`, {
1111
+ method: "POST",
1112
+ headers: {
1113
+ Authorization: `Bearer ${apiKey2}`,
1114
+ "Content-Type": "application/json"
1115
+ },
1116
+ body: JSON.stringify(trace),
1117
+ signal: controller.signal
1118
+ });
1119
+ clearTimeout(timeoutId);
1120
+ log2("\u{1F4E4} Trace sent:", trace.name, trace.model);
1121
+ } catch {
801
1122
  }
802
1123
  }
1124
+ function wrapOpenAI(client) {
1125
+ const originalCreate = client.chat.completions.create.bind(
1126
+ client.chat.completions
1127
+ );
1128
+ client.chat.completions.create = async function(...args) {
1129
+ const ctx = sessionStorage.getStore() || fallbackSession;
1130
+ if (!ctx || !initialized2) {
1131
+ return originalCreate(...args);
1132
+ }
1133
+ let promptCtx = null;
1134
+ try {
1135
+ const { getPromptContext: getPromptContext2 } = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
1136
+ promptCtx = getPromptContext2();
1137
+ } catch {
1138
+ }
1139
+ const params = args[0] || {};
1140
+ const startTime = Date.now();
1141
+ try {
1142
+ const response = await originalCreate(...args);
1143
+ const endTime = Date.now();
1144
+ sendTrace({
1145
+ config_key: ctx.configKey,
1146
+ session_id: ctx.sessionId,
1147
+ name: "chat.completions.create",
1148
+ model: response?.model || params?.model,
1149
+ start_time: new Date(startTime).toISOString(),
1150
+ end_time: new Date(endTime).toISOString(),
1151
+ duration_ms: endTime - startTime,
1152
+ status: "OK",
1153
+ prompt_tokens: response?.usage?.prompt_tokens,
1154
+ completion_tokens: response?.usage?.completion_tokens,
1155
+ total_tokens: response?.usage?.total_tokens,
1156
+ input: captureContent ? JSON.stringify(params?.messages) : void 0,
1157
+ output: captureContent ? response?.choices?.[0]?.message?.content : void 0,
1158
+ prompt_key: promptCtx?.promptKey,
1159
+ prompt_version: promptCtx?.promptVersion,
1160
+ prompt_ab_test_key: promptCtx?.abTestKey,
1161
+ prompt_variant_index: promptCtx?.variantIndex
1162
+ }).catch(() => {
1163
+ });
1164
+ return response;
1165
+ } catch (error) {
1166
+ const endTime = Date.now();
1167
+ sendTrace({
1168
+ config_key: ctx.configKey,
1169
+ session_id: ctx.sessionId,
1170
+ name: "chat.completions.create",
1171
+ model: params?.model,
1172
+ start_time: new Date(startTime).toISOString(),
1173
+ end_time: new Date(endTime).toISOString(),
1174
+ duration_ms: endTime - startTime,
1175
+ status: "ERROR",
1176
+ error_message: error?.message,
1177
+ prompt_key: promptCtx?.promptKey,
1178
+ prompt_version: promptCtx?.promptVersion,
1179
+ prompt_ab_test_key: promptCtx?.abTestKey,
1180
+ prompt_variant_index: promptCtx?.variantIndex
1181
+ }).catch(() => {
1182
+ });
1183
+ throw error;
1184
+ }
1185
+ };
1186
+ return client;
1187
+ }
1188
+ function wrapAnthropic(client) {
1189
+ const originalCreate = client.messages.create.bind(client.messages);
1190
+ client.messages.create = async function(...args) {
1191
+ const ctx = sessionStorage.getStore() || fallbackSession;
1192
+ if (!ctx || !initialized2) {
1193
+ return originalCreate(...args);
1194
+ }
1195
+ let promptCtx = null;
1196
+ try {
1197
+ const { getPromptContext: getPromptContext2 } = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
1198
+ promptCtx = getPromptContext2();
1199
+ } catch {
1200
+ }
1201
+ const params = args[0] || {};
1202
+ const startTime = Date.now();
1203
+ try {
1204
+ const response = await originalCreate(...args);
1205
+ const endTime = Date.now();
1206
+ sendTrace({
1207
+ config_key: ctx.configKey,
1208
+ session_id: ctx.sessionId,
1209
+ name: "messages.create",
1210
+ model: response?.model || params?.model,
1211
+ start_time: new Date(startTime).toISOString(),
1212
+ end_time: new Date(endTime).toISOString(),
1213
+ duration_ms: endTime - startTime,
1214
+ status: "OK",
1215
+ prompt_tokens: response?.usage?.input_tokens,
1216
+ completion_tokens: response?.usage?.output_tokens,
1217
+ total_tokens: (response?.usage?.input_tokens || 0) + (response?.usage?.output_tokens || 0),
1218
+ input: captureContent ? JSON.stringify(params?.messages) : void 0,
1219
+ output: captureContent ? response?.content?.[0]?.text : void 0,
1220
+ prompt_key: promptCtx?.promptKey,
1221
+ prompt_version: promptCtx?.promptVersion,
1222
+ prompt_ab_test_key: promptCtx?.abTestKey,
1223
+ prompt_variant_index: promptCtx?.variantIndex
1224
+ }).catch(() => {
1225
+ });
1226
+ return response;
1227
+ } catch (error) {
1228
+ const endTime = Date.now();
1229
+ sendTrace({
1230
+ config_key: ctx.configKey,
1231
+ session_id: ctx.sessionId,
1232
+ name: "messages.create",
1233
+ model: params?.model,
1234
+ start_time: new Date(startTime).toISOString(),
1235
+ end_time: new Date(endTime).toISOString(),
1236
+ duration_ms: endTime - startTime,
1237
+ status: "ERROR",
1238
+ error_message: error?.message,
1239
+ prompt_key: promptCtx?.promptKey,
1240
+ prompt_version: promptCtx?.promptVersion,
1241
+ prompt_ab_test_key: promptCtx?.abTestKey,
1242
+ prompt_variant_index: promptCtx?.variantIndex
1243
+ }).catch(() => {
1244
+ });
1245
+ throw error;
1246
+ }
1247
+ };
1248
+ return client;
1249
+ }
1250
+ function wrapGoogleAI(model) {
1251
+ const originalGenerate = model.generateContent.bind(model);
1252
+ model.generateContent = async function(...args) {
1253
+ const ctx = sessionStorage.getStore() || fallbackSession;
1254
+ if (!ctx || !initialized2) {
1255
+ return originalGenerate(...args);
1256
+ }
1257
+ let promptCtx = null;
1258
+ try {
1259
+ const { getPromptContext: getPromptContext2 } = await Promise.resolve().then(() => (init_prompts(), prompts_exports));
1260
+ promptCtx = getPromptContext2();
1261
+ } catch {
1262
+ }
1263
+ const startTime = Date.now();
1264
+ try {
1265
+ const response = await originalGenerate(...args);
1266
+ const endTime = Date.now();
1267
+ const result = response?.response;
1268
+ const usage = result?.usageMetadata;
1269
+ sendTrace({
1270
+ config_key: ctx.configKey,
1271
+ session_id: ctx.sessionId,
1272
+ name: "generateContent",
1273
+ model: model?.model || "gemini",
1274
+ start_time: new Date(startTime).toISOString(),
1275
+ end_time: new Date(endTime).toISOString(),
1276
+ duration_ms: endTime - startTime,
1277
+ status: "OK",
1278
+ prompt_tokens: usage?.promptTokenCount,
1279
+ completion_tokens: usage?.candidatesTokenCount,
1280
+ total_tokens: usage?.totalTokenCount,
1281
+ input: captureContent ? JSON.stringify(args[0]) : void 0,
1282
+ output: captureContent ? result?.text?.() : void 0,
1283
+ prompt_key: promptCtx?.promptKey,
1284
+ prompt_version: promptCtx?.promptVersion,
1285
+ prompt_ab_test_key: promptCtx?.abTestKey,
1286
+ prompt_variant_index: promptCtx?.variantIndex
1287
+ }).catch(() => {
1288
+ });
1289
+ return response;
1290
+ } catch (error) {
1291
+ const endTime = Date.now();
1292
+ sendTrace({
1293
+ config_key: ctx.configKey,
1294
+ session_id: ctx.sessionId,
1295
+ name: "generateContent",
1296
+ model: model?.model || "gemini",
1297
+ start_time: new Date(startTime).toISOString(),
1298
+ end_time: new Date(endTime).toISOString(),
1299
+ duration_ms: endTime - startTime,
1300
+ status: "ERROR",
1301
+ error_message: error?.message,
1302
+ prompt_key: promptCtx?.promptKey,
1303
+ prompt_version: promptCtx?.promptVersion,
1304
+ prompt_ab_test_key: promptCtx?.abTestKey,
1305
+ prompt_variant_index: promptCtx?.variantIndex
1306
+ }).catch(() => {
1307
+ });
1308
+ throw error;
1309
+ }
1310
+ };
1311
+ return model;
1312
+ }
803
1313
 
804
1314
  // src/models.ts
805
1315
  var models_exports = {};
806
1316
  __export(models_exports, {
807
- get: () => get,
808
- init: () => init2
1317
+ get: () => get2,
1318
+ init: () => init3
809
1319
  });
810
- var import_crypto = require("crypto");
811
- var apiKey2 = null;
812
- var baseUrl2 = "https://spans.fallom.com";
813
- var initialized2 = false;
814
- var syncInterval = null;
815
- var debugMode = false;
1320
+ var import_crypto2 = require("crypto");
1321
+ var apiKey3 = null;
1322
+ var baseUrl3 = "https://spans.fallom.com";
1323
+ var initialized3 = false;
1324
+ var syncInterval2 = null;
1325
+ var debugMode3 = false;
816
1326
  var configCache = /* @__PURE__ */ new Map();
817
- var SYNC_TIMEOUT = 2e3;
1327
+ var SYNC_TIMEOUT2 = 2e3;
818
1328
  var RECORD_TIMEOUT = 1e3;
819
- function log(msg) {
820
- if (debugMode) {
1329
+ function log3(msg) {
1330
+ if (debugMode3) {
821
1331
  console.log(`[Fallom] ${msg}`);
822
1332
  }
823
1333
  }
824
- function init2(options = {}) {
825
- apiKey2 = options.apiKey || process.env.FALLOM_API_KEY || null;
826
- baseUrl2 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
827
- initialized2 = true;
828
- if (!apiKey2) {
1334
+ function init3(options = {}) {
1335
+ apiKey3 = options.apiKey || process.env.FALLOM_API_KEY || null;
1336
+ baseUrl3 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
1337
+ initialized3 = true;
1338
+ if (!apiKey3) {
829
1339
  return;
830
1340
  }
831
1341
  fetchConfigs().catch(() => {
832
1342
  });
833
- if (!syncInterval) {
834
- syncInterval = setInterval(() => {
1343
+ if (!syncInterval2) {
1344
+ syncInterval2 = setInterval(() => {
835
1345
  fetchConfigs().catch(() => {
836
1346
  });
837
1347
  }, 3e4);
838
- syncInterval.unref();
1348
+ syncInterval2.unref();
839
1349
  }
840
1350
  }
841
- function ensureInit() {
842
- if (!initialized2) {
1351
+ function ensureInit2() {
1352
+ if (!initialized3) {
843
1353
  try {
844
- init2();
1354
+ init3();
845
1355
  } catch {
846
1356
  }
847
1357
  }
848
1358
  }
849
- async function fetchConfigs(timeout = SYNC_TIMEOUT) {
850
- if (!apiKey2) {
851
- log("_fetchConfigs: No API key, skipping");
1359
+ async function fetchConfigs(timeout = SYNC_TIMEOUT2) {
1360
+ if (!apiKey3) {
1361
+ log3("_fetchConfigs: No API key, skipping");
852
1362
  return;
853
1363
  }
854
1364
  try {
855
- log(`Fetching configs from ${baseUrl2}/configs`);
1365
+ log3(`Fetching configs from ${baseUrl3}/configs`);
856
1366
  const controller = new AbortController();
857
1367
  const timeoutId = setTimeout(() => controller.abort(), timeout);
858
- const resp = await fetch(`${baseUrl2}/configs`, {
859
- headers: { Authorization: `Bearer ${apiKey2}` },
1368
+ const resp = await fetch(`${baseUrl3}/configs`, {
1369
+ headers: { Authorization: `Bearer ${apiKey3}` },
860
1370
  signal: controller.signal
861
1371
  });
862
1372
  clearTimeout(timeoutId);
863
- log(`Response status: ${resp.status}`);
1373
+ log3(`Response status: ${resp.status}`);
864
1374
  if (resp.ok) {
865
1375
  const data = await resp.json();
866
1376
  const configs = data.configs || [];
867
- log(`Got ${configs.length} configs: ${configs.map((c) => c.key)}`);
1377
+ log3(`Got ${configs.length} configs: ${configs.map((c) => c.key)}`);
868
1378
  for (const c of configs) {
869
1379
  const key = c.key;
870
1380
  const version = c.version || 1;
871
- log(`Config '${key}' v${version}: ${JSON.stringify(c.variants)}`);
1381
+ log3(`Config '${key}' v${version}: ${JSON.stringify(c.variants)}`);
872
1382
  if (!configCache.has(key)) {
873
1383
  configCache.set(key, { versions: /* @__PURE__ */ new Map(), latest: null });
874
1384
  }
@@ -877,21 +1387,21 @@ async function fetchConfigs(timeout = SYNC_TIMEOUT) {
877
1387
  cached.latest = version;
878
1388
  }
879
1389
  } else {
880
- log(`Fetch failed: ${resp.statusText}`);
1390
+ log3(`Fetch failed: ${resp.statusText}`);
881
1391
  }
882
1392
  } catch (e) {
883
- log(`Fetch exception: ${e}`);
1393
+ log3(`Fetch exception: ${e}`);
884
1394
  }
885
1395
  }
886
- async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT) {
887
- if (!apiKey2) return null;
1396
+ async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT2) {
1397
+ if (!apiKey3) return null;
888
1398
  try {
889
1399
  const controller = new AbortController();
890
1400
  const timeoutId = setTimeout(() => controller.abort(), timeout);
891
1401
  const resp = await fetch(
892
- `${baseUrl2}/configs/${configKey}/version/${version}`,
1402
+ `${baseUrl3}/configs/${configKey}/version/${version}`,
893
1403
  {
894
- headers: { Authorization: `Bearer ${apiKey2}` },
1404
+ headers: { Authorization: `Bearer ${apiKey3}` },
895
1405
  signal: controller.signal
896
1406
  }
897
1407
  );
@@ -908,22 +1418,22 @@ async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT)
908
1418
  }
909
1419
  return null;
910
1420
  }
911
- async function get(configKey, sessionId, options = {}) {
1421
+ async function get2(configKey, sessionId, options = {}) {
912
1422
  const { version, fallback, debug = false } = options;
913
- debugMode = debug;
914
- ensureInit();
915
- log(`get() called: configKey=${configKey}, sessionId=${sessionId}, fallback=${fallback}`);
1423
+ debugMode3 = debug;
1424
+ ensureInit2();
1425
+ log3(`get() called: configKey=${configKey}, sessionId=${sessionId}, fallback=${fallback}`);
916
1426
  try {
917
1427
  let configData = configCache.get(configKey);
918
- log(`Cache lookup for '${configKey}': ${configData ? "found" : "not found"}`);
1428
+ log3(`Cache lookup for '${configKey}': ${configData ? "found" : "not found"}`);
919
1429
  if (!configData) {
920
- log("Not in cache, fetching...");
921
- await fetchConfigs(SYNC_TIMEOUT);
1430
+ log3("Not in cache, fetching...");
1431
+ await fetchConfigs(SYNC_TIMEOUT2);
922
1432
  configData = configCache.get(configKey);
923
- log(`After fetch, cache lookup: ${configData ? "found" : "still not found"}`);
1433
+ log3(`After fetch, cache lookup: ${configData ? "found" : "still not found"}`);
924
1434
  }
925
1435
  if (!configData) {
926
- log(`Config not found, using fallback: ${fallback}`);
1436
+ log3(`Config not found, using fallback: ${fallback}`);
927
1437
  if (fallback) {
928
1438
  console.warn(`[Fallom WARNING] Config '${configKey}' not found, using fallback model: ${fallback}`);
929
1439
  return returnWithTrace(configKey, sessionId, fallback, 0);
@@ -937,7 +1447,7 @@ async function get(configKey, sessionId, options = {}) {
937
1447
  if (version !== void 0) {
938
1448
  config = configData.versions.get(version);
939
1449
  if (!config) {
940
- config = await fetchSpecificVersion(configKey, version, SYNC_TIMEOUT) || void 0;
1450
+ config = await fetchSpecificVersion(configKey, version, SYNC_TIMEOUT2) || void 0;
941
1451
  }
942
1452
  if (!config) {
943
1453
  if (fallback) {
@@ -961,22 +1471,22 @@ async function get(configKey, sessionId, options = {}) {
961
1471
  const variantsRaw = config.variants;
962
1472
  const configVersion = config.version || targetVersion;
963
1473
  const variants = Array.isArray(variantsRaw) ? variantsRaw : Object.values(variantsRaw);
964
- log(`Config found! Version: ${configVersion}, Variants: ${JSON.stringify(variants)}`);
965
- const hashBytes = (0, import_crypto.createHash)("md5").update(sessionId).digest();
1474
+ log3(`Config found! Version: ${configVersion}, Variants: ${JSON.stringify(variants)}`);
1475
+ const hashBytes = (0, import_crypto2.createHash)("md5").update(sessionId).digest();
966
1476
  const hashVal = hashBytes.readUInt32BE(0) % 1e6;
967
- log(`Session hash: ${hashVal} (out of 1,000,000)`);
1477
+ log3(`Session hash: ${hashVal} (out of 1,000,000)`);
968
1478
  let cumulative = 0;
969
1479
  let assignedModel = variants[variants.length - 1].model;
970
1480
  for (const v of variants) {
971
1481
  const oldCumulative = cumulative;
972
1482
  cumulative += v.weight * 1e4;
973
- log(`Variant ${v.model}: weight=${v.weight}%, range=${oldCumulative}-${cumulative}, hash=${hashVal}, match=${hashVal < cumulative}`);
1483
+ log3(`Variant ${v.model}: weight=${v.weight}%, range=${oldCumulative}-${cumulative}, hash=${hashVal}, match=${hashVal < cumulative}`);
974
1484
  if (hashVal < cumulative) {
975
1485
  assignedModel = v.model;
976
1486
  break;
977
1487
  }
978
1488
  }
979
- log(`\u2705 Assigned model: ${assignedModel}`);
1489
+ log3(`\u2705 Assigned model: ${assignedModel}`);
980
1490
  return returnWithTrace(configKey, sessionId, assignedModel, configVersion);
981
1491
  } catch (e) {
982
1492
  if (e instanceof Error && e.message.includes("not found")) {
@@ -1001,14 +1511,14 @@ function returnWithTrace(configKey, sessionId, model, version) {
1001
1511
  return model;
1002
1512
  }
1003
1513
  async function recordSession(configKey, version, sessionId, model) {
1004
- if (!apiKey2) return;
1514
+ if (!apiKey3) return;
1005
1515
  try {
1006
1516
  const controller = new AbortController();
1007
1517
  const timeoutId = setTimeout(() => controller.abort(), RECORD_TIMEOUT);
1008
- await fetch(`${baseUrl2}/sessions`, {
1518
+ await fetch(`${baseUrl3}/sessions`, {
1009
1519
  method: "POST",
1010
1520
  headers: {
1011
- Authorization: `Bearer ${apiKey2}`,
1521
+ Authorization: `Bearer ${apiKey3}`,
1012
1522
  "Content-Type": "application/json"
1013
1523
  },
1014
1524
  body: JSON.stringify({
@@ -1024,29 +1534,41 @@ async function recordSession(configKey, version, sessionId, model) {
1024
1534
  }
1025
1535
  }
1026
1536
 
1537
+ // src/index.ts
1538
+ init_prompts();
1539
+
1027
1540
  // src/init.ts
1028
- function init3(options = {}) {
1029
- const baseUrl3 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
1030
- init({
1541
+ init_prompts();
1542
+ async function init4(options = {}) {
1543
+ const baseUrl4 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
1544
+ await init2({
1031
1545
  apiKey: options.apiKey,
1032
- baseUrl: baseUrl3,
1033
- captureContent: options.captureContent
1546
+ baseUrl: baseUrl4,
1547
+ captureContent: options.captureContent,
1548
+ debug: options.debug
1034
1549
  });
1035
- init2({
1550
+ init3({
1551
+ apiKey: options.apiKey,
1552
+ baseUrl: baseUrl4
1553
+ });
1554
+ init({
1036
1555
  apiKey: options.apiKey,
1037
- baseUrl: baseUrl3
1556
+ baseUrl: baseUrl4
1038
1557
  });
1039
1558
  }
1040
1559
 
1041
1560
  // src/index.ts
1561
+ init_prompts();
1042
1562
  var index_default = {
1043
- init: init3,
1563
+ init: init4,
1044
1564
  trace: trace_exports,
1045
- models: models_exports
1565
+ models: models_exports,
1566
+ prompts: prompts_exports
1046
1567
  };
1047
1568
  // Annotate the CommonJS export names for ESM import in node:
1048
1569
  0 && (module.exports = {
1049
1570
  init,
1050
1571
  models,
1572
+ prompts,
1051
1573
  trace
1052
1574
  });