@fallom/trace 0.1.12 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,252 @@
1
+ import {
2
+ __export
3
+ } from "./chunk-7P6ASYW6.mjs";
4
+
5
+ // src/prompts.ts
6
+ var prompts_exports = {};
7
+ __export(prompts_exports, {
8
+ clearPromptContext: () => clearPromptContext,
9
+ get: () => get,
10
+ getAB: () => getAB,
11
+ getPromptContext: () => getPromptContext,
12
+ init: () => init
13
+ });
14
+ import { createHash } from "crypto";
15
+ var apiKey = null;
16
+ var baseUrl = "https://prompts.fallom.com";
17
+ var initialized = false;
18
+ var syncInterval = null;
19
+ var debugMode = false;
20
+ var promptCache = /* @__PURE__ */ new Map();
21
+ var promptABCache = /* @__PURE__ */ new Map();
22
+ var promptContext = null;
23
+ var SYNC_TIMEOUT = 2e3;
24
+ function log(msg) {
25
+ if (debugMode) {
26
+ console.log(`[Fallom Prompts] ${msg}`);
27
+ }
28
+ }
29
+ function init(options = {}) {
30
+ apiKey = options.apiKey || process.env.FALLOM_API_KEY || null;
31
+ baseUrl = options.baseUrl || process.env.FALLOM_PROMPTS_URL || process.env.FALLOM_BASE_URL || "https://prompts.fallom.com";
32
+ initialized = true;
33
+ if (!apiKey) {
34
+ return;
35
+ }
36
+ fetchAll().catch(() => {
37
+ });
38
+ if (!syncInterval) {
39
+ syncInterval = setInterval(() => {
40
+ fetchAll().catch(() => {
41
+ });
42
+ }, 3e4);
43
+ syncInterval.unref();
44
+ }
45
+ }
46
+ function ensureInit() {
47
+ if (!initialized) {
48
+ try {
49
+ init();
50
+ } catch {
51
+ }
52
+ }
53
+ }
54
+ async function fetchAll() {
55
+ await Promise.all([fetchPrompts(), fetchPromptABTests()]);
56
+ }
57
+ async function fetchPrompts(timeout = SYNC_TIMEOUT) {
58
+ if (!apiKey) return;
59
+ try {
60
+ const controller = new AbortController();
61
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
62
+ const resp = await fetch(`${baseUrl}/prompts`, {
63
+ headers: { Authorization: `Bearer ${apiKey}` },
64
+ signal: controller.signal
65
+ });
66
+ clearTimeout(timeoutId);
67
+ if (resp.ok) {
68
+ const data = await resp.json();
69
+ for (const p of data.prompts || []) {
70
+ if (!promptCache.has(p.key)) {
71
+ promptCache.set(p.key, { versions: /* @__PURE__ */ new Map(), current: null });
72
+ }
73
+ const cached = promptCache.get(p.key);
74
+ cached.versions.set(p.version, {
75
+ systemPrompt: p.system_prompt,
76
+ userTemplate: p.user_template
77
+ });
78
+ cached.current = p.version;
79
+ }
80
+ }
81
+ } catch {
82
+ }
83
+ }
84
+ async function fetchPromptABTests(timeout = SYNC_TIMEOUT) {
85
+ if (!apiKey) return;
86
+ try {
87
+ const controller = new AbortController();
88
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
89
+ const resp = await fetch(`${baseUrl}/prompt-ab-tests`, {
90
+ headers: { Authorization: `Bearer ${apiKey}` },
91
+ signal: controller.signal
92
+ });
93
+ clearTimeout(timeoutId);
94
+ if (resp.ok) {
95
+ const data = await resp.json();
96
+ for (const t of data.prompt_ab_tests || []) {
97
+ if (!promptABCache.has(t.key)) {
98
+ promptABCache.set(t.key, { versions: /* @__PURE__ */ new Map(), current: null });
99
+ }
100
+ const cached = promptABCache.get(t.key);
101
+ cached.versions.set(t.version, { variants: t.variants });
102
+ cached.current = t.version;
103
+ }
104
+ }
105
+ } catch {
106
+ }
107
+ }
108
+ function replaceVariables(template, variables) {
109
+ if (!variables) return template;
110
+ return template.replace(/\{\{(\s*\w+\s*)\}\}/g, (match, varName) => {
111
+ const key = varName.trim();
112
+ return key in variables ? String(variables[key]) : match;
113
+ });
114
+ }
115
+ function setPromptContext(ctx) {
116
+ promptContext = ctx;
117
+ }
118
+ function getPromptContext() {
119
+ const ctx = promptContext;
120
+ promptContext = null;
121
+ return ctx;
122
+ }
123
+ async function get(promptKey, options = {}) {
124
+ const { variables, version, debug = false } = options;
125
+ debugMode = debug;
126
+ ensureInit();
127
+ log(`get() called: promptKey=${promptKey}`);
128
+ let promptData = promptCache.get(promptKey);
129
+ if (!promptData) {
130
+ log("Not in cache, fetching...");
131
+ await fetchPrompts(SYNC_TIMEOUT);
132
+ promptData = promptCache.get(promptKey);
133
+ }
134
+ if (!promptData) {
135
+ throw new Error(
136
+ `Prompt '${promptKey}' not found. Check that it exists in your Fallom dashboard.`
137
+ );
138
+ }
139
+ const targetVersion = version ?? promptData.current;
140
+ const content = promptData.versions.get(targetVersion);
141
+ if (!content) {
142
+ throw new Error(
143
+ `Prompt '${promptKey}' version ${targetVersion} not found.`
144
+ );
145
+ }
146
+ const system = replaceVariables(content.systemPrompt, variables);
147
+ const user = replaceVariables(content.userTemplate, variables);
148
+ setPromptContext({
149
+ promptKey,
150
+ promptVersion: targetVersion
151
+ });
152
+ log(`\u2705 Got prompt: ${promptKey} v${targetVersion}`);
153
+ return {
154
+ key: promptKey,
155
+ version: targetVersion,
156
+ system,
157
+ user
158
+ };
159
+ }
160
+ async function getAB(abTestKey, sessionId, options = {}) {
161
+ const { variables, debug = false } = options;
162
+ debugMode = debug;
163
+ ensureInit();
164
+ log(`getAB() called: abTestKey=${abTestKey}, sessionId=${sessionId}`);
165
+ let abData = promptABCache.get(abTestKey);
166
+ if (!abData) {
167
+ log("Not in cache, fetching...");
168
+ await fetchPromptABTests(SYNC_TIMEOUT);
169
+ abData = promptABCache.get(abTestKey);
170
+ }
171
+ if (!abData) {
172
+ throw new Error(
173
+ `Prompt A/B test '${abTestKey}' not found. Check that it exists in your Fallom dashboard.`
174
+ );
175
+ }
176
+ const currentVersion = abData.current;
177
+ const versionData = abData.versions.get(currentVersion);
178
+ if (!versionData) {
179
+ throw new Error(`Prompt A/B test '${abTestKey}' has no current version.`);
180
+ }
181
+ const { variants } = versionData;
182
+ log(`A/B test '${abTestKey}' has ${variants?.length ?? 0} variants`);
183
+ log(`Version data: ${JSON.stringify(versionData, null, 2)}`);
184
+ if (!variants || variants.length === 0) {
185
+ throw new Error(
186
+ `Prompt A/B test '${abTestKey}' has no variants configured.`
187
+ );
188
+ }
189
+ const hashBytes = createHash("md5").update(sessionId).digest();
190
+ const hashVal = hashBytes.readUInt32BE(0) % 1e6;
191
+ let cumulative = 0;
192
+ let selectedVariant = variants[variants.length - 1];
193
+ let selectedIndex = variants.length - 1;
194
+ for (let i = 0; i < variants.length; i++) {
195
+ cumulative += variants[i].weight * 1e4;
196
+ if (hashVal < cumulative) {
197
+ selectedVariant = variants[i];
198
+ selectedIndex = i;
199
+ break;
200
+ }
201
+ }
202
+ const promptKey = selectedVariant.prompt_key;
203
+ const promptVersion = selectedVariant.prompt_version;
204
+ let promptData = promptCache.get(promptKey);
205
+ if (!promptData) {
206
+ await fetchPrompts(SYNC_TIMEOUT);
207
+ promptData = promptCache.get(promptKey);
208
+ }
209
+ if (!promptData) {
210
+ throw new Error(
211
+ `Prompt '${promptKey}' (from A/B test '${abTestKey}') not found.`
212
+ );
213
+ }
214
+ const targetVersion = promptVersion ?? promptData.current;
215
+ const content = promptData.versions.get(targetVersion);
216
+ if (!content) {
217
+ throw new Error(
218
+ `Prompt '${promptKey}' version ${targetVersion} not found.`
219
+ );
220
+ }
221
+ const system = replaceVariables(content.systemPrompt, variables);
222
+ const user = replaceVariables(content.userTemplate, variables);
223
+ setPromptContext({
224
+ promptKey,
225
+ promptVersion: targetVersion,
226
+ abTestKey,
227
+ variantIndex: selectedIndex
228
+ });
229
+ log(
230
+ `\u2705 Got prompt from A/B: ${promptKey} v${targetVersion} (variant ${selectedIndex})`
231
+ );
232
+ return {
233
+ key: promptKey,
234
+ version: targetVersion,
235
+ system,
236
+ user,
237
+ abTestKey,
238
+ variantIndex: selectedIndex
239
+ };
240
+ }
241
+ function clearPromptContext() {
242
+ promptContext = null;
243
+ }
244
+
245
+ export {
246
+ init,
247
+ getPromptContext,
248
+ get,
249
+ getAB,
250
+ clearPromptContext,
251
+ prompts_exports
252
+ };
@@ -0,0 +1,251 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/models.ts
8
+ var models_exports = {};
9
+ __export(models_exports, {
10
+ get: () => get,
11
+ init: () => init
12
+ });
13
+ import { createHash } from "crypto";
14
+ var apiKey = null;
15
+ var baseUrl = "https://configs.fallom.com";
16
+ var initialized = false;
17
+ var syncInterval = null;
18
+ var debugMode = false;
19
+ var configCache = /* @__PURE__ */ new Map();
20
+ var SYNC_TIMEOUT = 2e3;
21
+ var RECORD_TIMEOUT = 1e3;
22
+ function log(msg) {
23
+ if (debugMode) {
24
+ console.log(`[Fallom] ${msg}`);
25
+ }
26
+ }
27
+ function init(options = {}) {
28
+ apiKey = options.apiKey || process.env.FALLOM_API_KEY || null;
29
+ baseUrl = options.baseUrl || process.env.FALLOM_CONFIGS_URL || process.env.FALLOM_BASE_URL || "https://configs.fallom.com";
30
+ initialized = true;
31
+ if (!apiKey) {
32
+ return;
33
+ }
34
+ fetchConfigs().catch(() => {
35
+ });
36
+ if (!syncInterval) {
37
+ syncInterval = setInterval(() => {
38
+ fetchConfigs().catch(() => {
39
+ });
40
+ }, 3e4);
41
+ syncInterval.unref();
42
+ }
43
+ }
44
+ function ensureInit() {
45
+ if (!initialized) {
46
+ try {
47
+ init();
48
+ } catch {
49
+ }
50
+ }
51
+ }
52
+ async function fetchConfigs(timeout = SYNC_TIMEOUT) {
53
+ if (!apiKey) {
54
+ log("_fetchConfigs: No API key, skipping");
55
+ return;
56
+ }
57
+ try {
58
+ log(`Fetching configs from ${baseUrl}/configs`);
59
+ const controller = new AbortController();
60
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
61
+ const resp = await fetch(`${baseUrl}/configs`, {
62
+ headers: { Authorization: `Bearer ${apiKey}` },
63
+ signal: controller.signal
64
+ });
65
+ clearTimeout(timeoutId);
66
+ log(`Response status: ${resp.status}`);
67
+ if (resp.ok) {
68
+ const data = await resp.json();
69
+ const configs = data.configs || [];
70
+ log(`Got ${configs.length} configs: ${configs.map((c) => c.key)}`);
71
+ for (const c of configs) {
72
+ const key = c.key;
73
+ const version = c.version || 1;
74
+ log(`Config '${key}' v${version}: ${JSON.stringify(c.variants)}`);
75
+ if (!configCache.has(key)) {
76
+ configCache.set(key, { versions: /* @__PURE__ */ new Map(), latest: null });
77
+ }
78
+ const cached = configCache.get(key);
79
+ cached.versions.set(version, c);
80
+ cached.latest = version;
81
+ }
82
+ } else {
83
+ log(`Fetch failed: ${resp.statusText}`);
84
+ }
85
+ } catch (e) {
86
+ log(`Fetch exception: ${e}`);
87
+ }
88
+ }
89
+ async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT) {
90
+ if (!apiKey) return null;
91
+ try {
92
+ const controller = new AbortController();
93
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
94
+ const resp = await fetch(
95
+ `${baseUrl}/configs/${configKey}/version/${version}`,
96
+ {
97
+ headers: { Authorization: `Bearer ${apiKey}` },
98
+ signal: controller.signal
99
+ }
100
+ );
101
+ clearTimeout(timeoutId);
102
+ if (resp.ok) {
103
+ const config = await resp.json();
104
+ if (!configCache.has(configKey)) {
105
+ configCache.set(configKey, { versions: /* @__PURE__ */ new Map(), latest: null });
106
+ }
107
+ configCache.get(configKey).versions.set(version, config);
108
+ return config;
109
+ }
110
+ } catch {
111
+ }
112
+ return null;
113
+ }
114
+ async function get(configKey, sessionId, options = {}) {
115
+ const { version, fallback, debug = false } = options;
116
+ debugMode = debug;
117
+ ensureInit();
118
+ log(
119
+ `get() called: configKey=${configKey}, sessionId=${sessionId}, fallback=${fallback}`
120
+ );
121
+ try {
122
+ let configData = configCache.get(configKey);
123
+ log(
124
+ `Cache lookup for '${configKey}': ${configData ? "found" : "not found"}`
125
+ );
126
+ if (!configData) {
127
+ log("Not in cache, fetching...");
128
+ await fetchConfigs(SYNC_TIMEOUT);
129
+ configData = configCache.get(configKey);
130
+ log(
131
+ `After fetch, cache lookup: ${configData ? "found" : "still not found"}`
132
+ );
133
+ }
134
+ if (!configData) {
135
+ log(`Config not found, using fallback: ${fallback}`);
136
+ if (fallback) {
137
+ console.warn(
138
+ `[Fallom WARNING] Config '${configKey}' not found, using fallback model: ${fallback}`
139
+ );
140
+ return returnModel(configKey, sessionId, fallback, 0);
141
+ }
142
+ throw new Error(
143
+ `Config '${configKey}' not found. Check that it exists in your Fallom dashboard.`
144
+ );
145
+ }
146
+ let config;
147
+ let targetVersion;
148
+ if (version !== void 0) {
149
+ config = configData.versions.get(version);
150
+ if (!config) {
151
+ config = await fetchSpecificVersion(configKey, version, SYNC_TIMEOUT) || void 0;
152
+ }
153
+ if (!config) {
154
+ if (fallback) {
155
+ console.warn(
156
+ `[Fallom WARNING] Config '${configKey}' version ${version} not found, using fallback: ${fallback}`
157
+ );
158
+ return returnModel(configKey, sessionId, fallback, 0);
159
+ }
160
+ throw new Error(`Config '${configKey}' version ${version} not found.`);
161
+ }
162
+ targetVersion = version;
163
+ } else {
164
+ targetVersion = configData.latest;
165
+ config = configData.versions.get(targetVersion);
166
+ if (!config) {
167
+ if (fallback) {
168
+ console.warn(
169
+ `[Fallom WARNING] Config '${configKey}' has no cached version, using fallback: ${fallback}`
170
+ );
171
+ return returnModel(configKey, sessionId, fallback, 0);
172
+ }
173
+ throw new Error(`Config '${configKey}' has no cached version.`);
174
+ }
175
+ }
176
+ const variantsRaw = config.variants;
177
+ const configVersion = config.version || targetVersion;
178
+ const variants = Array.isArray(variantsRaw) ? variantsRaw : Object.values(variantsRaw);
179
+ log(
180
+ `Config found! Version: ${configVersion}, Variants: ${JSON.stringify(
181
+ variants
182
+ )}`
183
+ );
184
+ const hashBytes = createHash("md5").update(sessionId).digest();
185
+ const hashVal = hashBytes.readUInt32BE(0) % 1e6;
186
+ log(`Session hash: ${hashVal} (out of 1,000,000)`);
187
+ let cumulative = 0;
188
+ let assignedModel = variants[variants.length - 1].model;
189
+ for (const v of variants) {
190
+ const oldCumulative = cumulative;
191
+ cumulative += v.weight * 1e4;
192
+ log(
193
+ `Variant ${v.model}: weight=${v.weight}%, range=${oldCumulative}-${cumulative}, hash=${hashVal}, match=${hashVal < cumulative}`
194
+ );
195
+ if (hashVal < cumulative) {
196
+ assignedModel = v.model;
197
+ break;
198
+ }
199
+ }
200
+ log(`\u2705 Assigned model: ${assignedModel}`);
201
+ return returnModel(configKey, sessionId, assignedModel, configVersion);
202
+ } catch (e) {
203
+ if (e instanceof Error && e.message.includes("not found")) {
204
+ throw e;
205
+ }
206
+ if (fallback) {
207
+ console.warn(
208
+ `[Fallom WARNING] Error getting model for '${configKey}': ${e}. Using fallback: ${fallback}`
209
+ );
210
+ return returnModel(configKey, sessionId, fallback, 0);
211
+ }
212
+ throw e;
213
+ }
214
+ }
215
+ function returnModel(configKey, sessionId, model, version) {
216
+ if (version > 0) {
217
+ recordSession(configKey, version, sessionId, model).catch(() => {
218
+ });
219
+ }
220
+ return model;
221
+ }
222
+ async function recordSession(configKey, version, sessionId, model) {
223
+ if (!apiKey) return;
224
+ try {
225
+ const controller = new AbortController();
226
+ const timeoutId = setTimeout(() => controller.abort(), RECORD_TIMEOUT);
227
+ await fetch(`${baseUrl}/sessions`, {
228
+ method: "POST",
229
+ headers: {
230
+ Authorization: `Bearer ${apiKey}`,
231
+ "Content-Type": "application/json"
232
+ },
233
+ body: JSON.stringify({
234
+ config_key: configKey,
235
+ config_version: version,
236
+ session_id: sessionId,
237
+ assigned_model: model
238
+ }),
239
+ signal: controller.signal
240
+ });
241
+ clearTimeout(timeoutId);
242
+ } catch {
243
+ }
244
+ }
245
+
246
+ export {
247
+ __export,
248
+ init,
249
+ get,
250
+ models_exports
251
+ };