@fallom/trace 0.2.16 → 0.2.17

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,308 @@
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 evaluateTargeting(targeting, customerId, context) {
28
+ if (!targeting || targeting.enabled === false) {
29
+ return null;
30
+ }
31
+ const evalContext = {
32
+ ...context || {},
33
+ ...customerId ? { customerId } : {}
34
+ };
35
+ log(`Evaluating targeting with context: ${JSON.stringify(evalContext)}`);
36
+ if (targeting.individualTargets) {
37
+ for (const target of targeting.individualTargets) {
38
+ const fieldValue = evalContext[target.field];
39
+ if (fieldValue === target.value) {
40
+ log(`Individual target matched: ${target.field}=${target.value} -> variant ${target.variantIndex}`);
41
+ return target.variantIndex;
42
+ }
43
+ }
44
+ }
45
+ if (targeting.rules) {
46
+ for (const rule of targeting.rules) {
47
+ const allConditionsMatch = rule.conditions.every((condition) => {
48
+ const fieldValue = evalContext[condition.field];
49
+ if (fieldValue === void 0) return false;
50
+ switch (condition.operator) {
51
+ case "eq":
52
+ return fieldValue === condition.value;
53
+ case "neq":
54
+ return fieldValue !== condition.value;
55
+ case "in":
56
+ return Array.isArray(condition.value) && condition.value.includes(fieldValue);
57
+ case "nin":
58
+ return Array.isArray(condition.value) && !condition.value.includes(fieldValue);
59
+ case "contains":
60
+ return typeof condition.value === "string" && fieldValue.includes(condition.value);
61
+ case "startsWith":
62
+ return typeof condition.value === "string" && fieldValue.startsWith(condition.value);
63
+ case "endsWith":
64
+ return typeof condition.value === "string" && fieldValue.endsWith(condition.value);
65
+ default:
66
+ return false;
67
+ }
68
+ });
69
+ if (allConditionsMatch) {
70
+ log(`Rule matched: ${JSON.stringify(rule.conditions)} -> variant ${rule.variantIndex}`);
71
+ return rule.variantIndex;
72
+ }
73
+ }
74
+ }
75
+ log("No targeting rules matched, falling back to weighted random");
76
+ return null;
77
+ }
78
+ function init(options = {}) {
79
+ apiKey = options.apiKey || process.env.FALLOM_API_KEY || null;
80
+ baseUrl = options.baseUrl || process.env.FALLOM_CONFIGS_URL || process.env.FALLOM_BASE_URL || "https://configs.fallom.com";
81
+ initialized = true;
82
+ if (!apiKey) {
83
+ return;
84
+ }
85
+ fetchConfigs().catch(() => {
86
+ });
87
+ if (!syncInterval) {
88
+ syncInterval = setInterval(() => {
89
+ fetchConfigs().catch(() => {
90
+ });
91
+ }, 3e4);
92
+ syncInterval.unref();
93
+ }
94
+ }
95
+ function ensureInit() {
96
+ if (!initialized) {
97
+ try {
98
+ init();
99
+ } catch {
100
+ }
101
+ }
102
+ }
103
+ async function fetchConfigs(timeout = SYNC_TIMEOUT) {
104
+ if (!apiKey) {
105
+ log("_fetchConfigs: No API key, skipping");
106
+ return;
107
+ }
108
+ try {
109
+ log(`Fetching configs from ${baseUrl}/configs`);
110
+ const controller = new AbortController();
111
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
112
+ const resp = await fetch(`${baseUrl}/configs`, {
113
+ headers: { Authorization: `Bearer ${apiKey}` },
114
+ signal: controller.signal
115
+ });
116
+ clearTimeout(timeoutId);
117
+ log(`Response status: ${resp.status}`);
118
+ if (resp.ok) {
119
+ const data = await resp.json();
120
+ const configs = data.configs || [];
121
+ log(`Got ${configs.length} configs: ${configs.map((c) => c.key)}`);
122
+ for (const c of configs) {
123
+ const key = c.key;
124
+ const version = c.version || 1;
125
+ log(`Config '${key}' v${version}: ${JSON.stringify(c.variants)}`);
126
+ if (!configCache.has(key)) {
127
+ configCache.set(key, { versions: /* @__PURE__ */ new Map(), latest: null });
128
+ }
129
+ const cached = configCache.get(key);
130
+ cached.versions.set(version, c);
131
+ cached.latest = version;
132
+ }
133
+ } else {
134
+ log(`Fetch failed: ${resp.statusText}`);
135
+ }
136
+ } catch (e) {
137
+ log(`Fetch exception: ${e}`);
138
+ }
139
+ }
140
+ async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT) {
141
+ if (!apiKey) return null;
142
+ try {
143
+ const controller = new AbortController();
144
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
145
+ const resp = await fetch(
146
+ `${baseUrl}/configs/${configKey}/version/${version}`,
147
+ {
148
+ headers: { Authorization: `Bearer ${apiKey}` },
149
+ signal: controller.signal
150
+ }
151
+ );
152
+ clearTimeout(timeoutId);
153
+ if (resp.ok) {
154
+ const config = await resp.json();
155
+ if (!configCache.has(configKey)) {
156
+ configCache.set(configKey, { versions: /* @__PURE__ */ new Map(), latest: null });
157
+ }
158
+ configCache.get(configKey).versions.set(version, config);
159
+ return config;
160
+ }
161
+ } catch {
162
+ }
163
+ return null;
164
+ }
165
+ async function get(configKey, sessionId, options = {}) {
166
+ const { version, fallback, customerId, context, debug = false } = options;
167
+ debugMode = debug;
168
+ ensureInit();
169
+ log(
170
+ `get() called: configKey=${configKey}, sessionId=${sessionId}, fallback=${fallback}`
171
+ );
172
+ try {
173
+ let configData = configCache.get(configKey);
174
+ log(
175
+ `Cache lookup for '${configKey}': ${configData ? "found" : "not found"}`
176
+ );
177
+ if (!configData) {
178
+ log("Not in cache, fetching...");
179
+ await fetchConfigs(SYNC_TIMEOUT);
180
+ configData = configCache.get(configKey);
181
+ log(
182
+ `After fetch, cache lookup: ${configData ? "found" : "still not found"}`
183
+ );
184
+ }
185
+ if (!configData) {
186
+ log(`Config not found, using fallback: ${fallback}`);
187
+ if (fallback) {
188
+ console.warn(
189
+ `[Fallom WARNING] Config '${configKey}' not found, using fallback model: ${fallback}`
190
+ );
191
+ return returnModel(configKey, sessionId, fallback, 0);
192
+ }
193
+ throw new Error(
194
+ `Config '${configKey}' not found. Check that it exists in your Fallom dashboard.`
195
+ );
196
+ }
197
+ let config;
198
+ let targetVersion;
199
+ if (version !== void 0) {
200
+ config = configData.versions.get(version);
201
+ if (!config) {
202
+ config = await fetchSpecificVersion(configKey, version, SYNC_TIMEOUT) || void 0;
203
+ }
204
+ if (!config) {
205
+ if (fallback) {
206
+ console.warn(
207
+ `[Fallom WARNING] Config '${configKey}' version ${version} not found, using fallback: ${fallback}`
208
+ );
209
+ return returnModel(configKey, sessionId, fallback, 0);
210
+ }
211
+ throw new Error(`Config '${configKey}' version ${version} not found.`);
212
+ }
213
+ targetVersion = version;
214
+ } else {
215
+ targetVersion = configData.latest;
216
+ config = configData.versions.get(targetVersion);
217
+ if (!config) {
218
+ if (fallback) {
219
+ console.warn(
220
+ `[Fallom WARNING] Config '${configKey}' has no cached version, using fallback: ${fallback}`
221
+ );
222
+ return returnModel(configKey, sessionId, fallback, 0);
223
+ }
224
+ throw new Error(`Config '${configKey}' has no cached version.`);
225
+ }
226
+ }
227
+ const variantsRaw = config.variants;
228
+ const configVersion = config.version || targetVersion;
229
+ const variants = Array.isArray(variantsRaw) ? variantsRaw : Object.values(variantsRaw);
230
+ log(
231
+ `Config found! Version: ${configVersion}, Variants: ${JSON.stringify(
232
+ variants
233
+ )}`
234
+ );
235
+ const targetedVariantIndex = evaluateTargeting(config.targeting, customerId, context);
236
+ if (targetedVariantIndex !== null && variants[targetedVariantIndex]) {
237
+ const assignedModel2 = variants[targetedVariantIndex].model;
238
+ log(`\u2705 Assigned model via targeting: ${assignedModel2}`);
239
+ return returnModel(configKey, sessionId, assignedModel2, configVersion);
240
+ }
241
+ const hashBytes = createHash("md5").update(sessionId).digest();
242
+ const hashVal = hashBytes.readUInt32BE(0) % 1e6;
243
+ log(`Session hash: ${hashVal} (out of 1,000,000)`);
244
+ let cumulative = 0;
245
+ let assignedModel = variants[variants.length - 1].model;
246
+ for (const v of variants) {
247
+ const oldCumulative = cumulative;
248
+ cumulative += v.weight * 1e4;
249
+ log(
250
+ `Variant ${v.model}: weight=${v.weight}%, range=${oldCumulative}-${cumulative}, hash=${hashVal}, match=${hashVal < cumulative}`
251
+ );
252
+ if (hashVal < cumulative) {
253
+ assignedModel = v.model;
254
+ break;
255
+ }
256
+ }
257
+ log(`\u2705 Assigned model via weighted random: ${assignedModel}`);
258
+ return returnModel(configKey, sessionId, assignedModel, configVersion);
259
+ } catch (e) {
260
+ if (e instanceof Error && e.message.includes("not found")) {
261
+ throw e;
262
+ }
263
+ if (fallback) {
264
+ console.warn(
265
+ `[Fallom WARNING] Error getting model for '${configKey}': ${e}. Using fallback: ${fallback}`
266
+ );
267
+ return returnModel(configKey, sessionId, fallback, 0);
268
+ }
269
+ throw e;
270
+ }
271
+ }
272
+ function returnModel(configKey, sessionId, model, version) {
273
+ if (version > 0) {
274
+ recordSession(configKey, version, sessionId, model).catch(() => {
275
+ });
276
+ }
277
+ return model;
278
+ }
279
+ async function recordSession(configKey, version, sessionId, model) {
280
+ if (!apiKey) return;
281
+ try {
282
+ const controller = new AbortController();
283
+ const timeoutId = setTimeout(() => controller.abort(), RECORD_TIMEOUT);
284
+ await fetch(`${baseUrl}/sessions`, {
285
+ method: "POST",
286
+ headers: {
287
+ Authorization: `Bearer ${apiKey}`,
288
+ "Content-Type": "application/json"
289
+ },
290
+ body: JSON.stringify({
291
+ config_key: configKey,
292
+ config_version: version,
293
+ session_id: sessionId,
294
+ assigned_model: model
295
+ }),
296
+ signal: controller.signal
297
+ });
298
+ clearTimeout(timeoutId);
299
+ } catch {
300
+ }
301
+ }
302
+
303
+ export {
304
+ __export,
305
+ init,
306
+ get,
307
+ models_exports
308
+ };
package/dist/index.js CHANGED
@@ -1201,7 +1201,7 @@ var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otl
1201
1201
  // node_modules/@opentelemetry/resources/build/esm/Resource.js
1202
1202
  var import_api = require("@opentelemetry/api");
1203
1203
 
1204
- // node_modules/@opentelemetry/semantic-conventions/build/esm/resource/SemanticResourceAttributes.js
1204
+ // node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions/build/esm/resource/SemanticResourceAttributes.js
1205
1205
  var SemanticResourceAttributes = {
1206
1206
  /**
1207
1207
  * Name of the cloud provider.
@@ -2216,6 +2216,54 @@ function clearPromptContext() {
2216
2216
  promptContext = null;
2217
2217
  }
2218
2218
 
2219
+ // src/trace/wrappers/shared-utils.ts
2220
+ function sanitizeMetadataOnly(key, value) {
2221
+ const contentKeys = [
2222
+ "text",
2223
+ "content",
2224
+ "message",
2225
+ "messages",
2226
+ "object",
2227
+ "prompt",
2228
+ "system",
2229
+ "input",
2230
+ "output",
2231
+ "response",
2232
+ "toolCalls",
2233
+ "toolResults",
2234
+ "steps",
2235
+ "reasoning",
2236
+ "rawResponse",
2237
+ "rawCall",
2238
+ "body",
2239
+ "candidates",
2240
+ "parts"
2241
+ ];
2242
+ if (contentKeys.includes(key)) {
2243
+ if (typeof value === "string") {
2244
+ return `[content omitted: ${value.length} chars]`;
2245
+ }
2246
+ if (Array.isArray(value)) {
2247
+ return `[content omitted: ${value.length} items]`;
2248
+ }
2249
+ if (typeof value === "object" && value !== null) {
2250
+ return "[content omitted]";
2251
+ }
2252
+ }
2253
+ if (typeof value === "string") {
2254
+ if (value.startsWith("data:image/")) {
2255
+ return "[base64 image omitted]";
2256
+ }
2257
+ if (value.length > 1e3) {
2258
+ return `[large string omitted: ${value.length} chars]`;
2259
+ }
2260
+ }
2261
+ if (value instanceof Uint8Array || value && value.type === "Buffer") {
2262
+ return "[binary data omitted]";
2263
+ }
2264
+ return value;
2265
+ }
2266
+
2219
2267
  // src/trace/wrappers/openai.ts
2220
2268
  function wrapOpenAI(client, sessionCtx) {
2221
2269
  const originalCreate = client.chat.completions.create.bind(
@@ -2263,6 +2311,13 @@ function wrapOpenAI(client, sessionCtx) {
2263
2311
  if (response?.usage) {
2264
2312
  attributes["fallom.raw.usage"] = JSON.stringify(response.usage);
2265
2313
  }
2314
+ try {
2315
+ attributes["fallom.raw.metadata"] = JSON.stringify(
2316
+ response,
2317
+ sanitizeMetadataOnly
2318
+ );
2319
+ } catch {
2320
+ }
2266
2321
  const waterfallTimings = {
2267
2322
  requestStart: 0,
2268
2323
  requestEnd: endTime - startTime,
@@ -2389,6 +2444,13 @@ function wrapAnthropic(client, sessionCtx) {
2389
2444
  if (response?.usage) {
2390
2445
  attributes["fallom.raw.usage"] = JSON.stringify(response.usage);
2391
2446
  }
2447
+ try {
2448
+ attributes["fallom.raw.metadata"] = JSON.stringify(
2449
+ response,
2450
+ sanitizeMetadataOnly
2451
+ );
2452
+ } catch {
2453
+ }
2392
2454
  const waterfallTimings = {
2393
2455
  requestStart: 0,
2394
2456
  requestEnd: endTime - startTime,
@@ -2509,6 +2571,13 @@ function wrapGoogleAI(model, sessionCtx) {
2509
2571
  if (result?.usageMetadata) {
2510
2572
  attributes["fallom.raw.usage"] = JSON.stringify(result.usageMetadata);
2511
2573
  }
2574
+ try {
2575
+ attributes["fallom.raw.metadata"] = JSON.stringify(
2576
+ result,
2577
+ sanitizeMetadataOnly
2578
+ );
2579
+ } catch {
2580
+ }
2512
2581
  const waterfallTimings = {
2513
2582
  requestStart: 0,
2514
2583
  requestEnd: endTime - startTime,
@@ -2705,6 +2774,13 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
2705
2774
  result.experimental_providerMetadata
2706
2775
  );
2707
2776
  }
2777
+ try {
2778
+ attributes["fallom.raw.metadata"] = JSON.stringify(
2779
+ result,
2780
+ sanitizeMetadataOnly
2781
+ );
2782
+ } catch {
2783
+ }
2708
2784
  const totalDurationMs = endTime - startTime;
2709
2785
  const sortedToolTimings = Array.from(toolTimings.values()).sort(
2710
2786
  (a, b) => a.startTime - b.startTime
@@ -3033,6 +3109,10 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
3033
3109
  if (firstTokenTime) {
3034
3110
  attributes["fallom.time_to_first_token_ms"] = firstTokenTime - startTime;
3035
3111
  }
3112
+ try {
3113
+ attributes["fallom.raw.metadata"] = JSON.stringify(result, sanitizeMetadataOnly);
3114
+ } catch {
3115
+ }
3036
3116
  const totalDurationMs = endTime - startTime;
3037
3117
  const sortedToolTimings = Array.from(toolTimings.values()).sort(
3038
3118
  (a, b) => a.startTime - b.startTime
@@ -3238,6 +3318,10 @@ function createGenerateObjectWrapper(aiModule, sessionCtx, debug = false) {
3238
3318
  result.experimental_providerMetadata
3239
3319
  );
3240
3320
  }
3321
+ try {
3322
+ attributes["fallom.raw.metadata"] = JSON.stringify(result, sanitizeMetadataOnly);
3323
+ } catch {
3324
+ }
3241
3325
  const promptCtx = getPromptContext();
3242
3326
  sendTrace({
3243
3327
  config_key: ctx.configKey,
@@ -3357,6 +3441,10 @@ function createStreamObjectWrapper(aiModule, sessionCtx, debug = false) {
3357
3441
  if (providerMetadata) {
3358
3442
  attributes["fallom.raw.providerMetadata"] = JSON.stringify(providerMetadata);
3359
3443
  }
3444
+ try {
3445
+ attributes["fallom.raw.metadata"] = JSON.stringify(result, sanitizeMetadataOnly);
3446
+ } catch {
3447
+ }
3360
3448
  const promptCtx = getPromptContext();
3361
3449
  sendTrace({
3362
3450
  config_key: ctx.configKey,
@@ -3453,6 +3541,13 @@ function wrapMastraAgent(agent, sessionCtx) {
3453
3541
  attributes["fallom.raw.request"] = JSON.stringify(input);
3454
3542
  attributes["fallom.raw.response"] = JSON.stringify(result);
3455
3543
  }
3544
+ try {
3545
+ attributes["fallom.raw.metadata"] = JSON.stringify(
3546
+ result,
3547
+ sanitizeMetadataOnly
3548
+ );
3549
+ } catch {
3550
+ }
3456
3551
  sendTrace({
3457
3552
  config_key: ctx.configKey,
3458
3553
  session_id: ctx.sessionId,
package/dist/index.mjs CHANGED
@@ -41,7 +41,7 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
41
41
  // node_modules/@opentelemetry/resources/build/esm/Resource.js
42
42
  import { diag } from "@opentelemetry/api";
43
43
 
44
- // node_modules/@opentelemetry/semantic-conventions/build/esm/resource/SemanticResourceAttributes.js
44
+ // node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions/build/esm/resource/SemanticResourceAttributes.js
45
45
  var SemanticResourceAttributes = {
46
46
  /**
47
47
  * Name of the cloud provider.
@@ -1056,6 +1056,54 @@ function clearPromptContext() {
1056
1056
  promptContext = null;
1057
1057
  }
1058
1058
 
1059
+ // src/trace/wrappers/shared-utils.ts
1060
+ function sanitizeMetadataOnly(key, value) {
1061
+ const contentKeys = [
1062
+ "text",
1063
+ "content",
1064
+ "message",
1065
+ "messages",
1066
+ "object",
1067
+ "prompt",
1068
+ "system",
1069
+ "input",
1070
+ "output",
1071
+ "response",
1072
+ "toolCalls",
1073
+ "toolResults",
1074
+ "steps",
1075
+ "reasoning",
1076
+ "rawResponse",
1077
+ "rawCall",
1078
+ "body",
1079
+ "candidates",
1080
+ "parts"
1081
+ ];
1082
+ if (contentKeys.includes(key)) {
1083
+ if (typeof value === "string") {
1084
+ return `[content omitted: ${value.length} chars]`;
1085
+ }
1086
+ if (Array.isArray(value)) {
1087
+ return `[content omitted: ${value.length} items]`;
1088
+ }
1089
+ if (typeof value === "object" && value !== null) {
1090
+ return "[content omitted]";
1091
+ }
1092
+ }
1093
+ if (typeof value === "string") {
1094
+ if (value.startsWith("data:image/")) {
1095
+ return "[base64 image omitted]";
1096
+ }
1097
+ if (value.length > 1e3) {
1098
+ return `[large string omitted: ${value.length} chars]`;
1099
+ }
1100
+ }
1101
+ if (value instanceof Uint8Array || value && value.type === "Buffer") {
1102
+ return "[binary data omitted]";
1103
+ }
1104
+ return value;
1105
+ }
1106
+
1059
1107
  // src/trace/wrappers/openai.ts
1060
1108
  function wrapOpenAI(client, sessionCtx) {
1061
1109
  const originalCreate = client.chat.completions.create.bind(
@@ -1103,6 +1151,13 @@ function wrapOpenAI(client, sessionCtx) {
1103
1151
  if (response?.usage) {
1104
1152
  attributes["fallom.raw.usage"] = JSON.stringify(response.usage);
1105
1153
  }
1154
+ try {
1155
+ attributes["fallom.raw.metadata"] = JSON.stringify(
1156
+ response,
1157
+ sanitizeMetadataOnly
1158
+ );
1159
+ } catch {
1160
+ }
1106
1161
  const waterfallTimings = {
1107
1162
  requestStart: 0,
1108
1163
  requestEnd: endTime - startTime,
@@ -1229,6 +1284,13 @@ function wrapAnthropic(client, sessionCtx) {
1229
1284
  if (response?.usage) {
1230
1285
  attributes["fallom.raw.usage"] = JSON.stringify(response.usage);
1231
1286
  }
1287
+ try {
1288
+ attributes["fallom.raw.metadata"] = JSON.stringify(
1289
+ response,
1290
+ sanitizeMetadataOnly
1291
+ );
1292
+ } catch {
1293
+ }
1232
1294
  const waterfallTimings = {
1233
1295
  requestStart: 0,
1234
1296
  requestEnd: endTime - startTime,
@@ -1349,6 +1411,13 @@ function wrapGoogleAI(model, sessionCtx) {
1349
1411
  if (result?.usageMetadata) {
1350
1412
  attributes["fallom.raw.usage"] = JSON.stringify(result.usageMetadata);
1351
1413
  }
1414
+ try {
1415
+ attributes["fallom.raw.metadata"] = JSON.stringify(
1416
+ result,
1417
+ sanitizeMetadataOnly
1418
+ );
1419
+ } catch {
1420
+ }
1352
1421
  const waterfallTimings = {
1353
1422
  requestStart: 0,
1354
1423
  requestEnd: endTime - startTime,
@@ -1545,6 +1614,13 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
1545
1614
  result.experimental_providerMetadata
1546
1615
  );
1547
1616
  }
1617
+ try {
1618
+ attributes["fallom.raw.metadata"] = JSON.stringify(
1619
+ result,
1620
+ sanitizeMetadataOnly
1621
+ );
1622
+ } catch {
1623
+ }
1548
1624
  const totalDurationMs = endTime - startTime;
1549
1625
  const sortedToolTimings = Array.from(toolTimings.values()).sort(
1550
1626
  (a, b) => a.startTime - b.startTime
@@ -1873,6 +1949,10 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
1873
1949
  if (firstTokenTime) {
1874
1950
  attributes["fallom.time_to_first_token_ms"] = firstTokenTime - startTime;
1875
1951
  }
1952
+ try {
1953
+ attributes["fallom.raw.metadata"] = JSON.stringify(result, sanitizeMetadataOnly);
1954
+ } catch {
1955
+ }
1876
1956
  const totalDurationMs = endTime - startTime;
1877
1957
  const sortedToolTimings = Array.from(toolTimings.values()).sort(
1878
1958
  (a, b) => a.startTime - b.startTime
@@ -2078,6 +2158,10 @@ function createGenerateObjectWrapper(aiModule, sessionCtx, debug = false) {
2078
2158
  result.experimental_providerMetadata
2079
2159
  );
2080
2160
  }
2161
+ try {
2162
+ attributes["fallom.raw.metadata"] = JSON.stringify(result, sanitizeMetadataOnly);
2163
+ } catch {
2164
+ }
2081
2165
  const promptCtx = getPromptContext();
2082
2166
  sendTrace({
2083
2167
  config_key: ctx.configKey,
@@ -2197,6 +2281,10 @@ function createStreamObjectWrapper(aiModule, sessionCtx, debug = false) {
2197
2281
  if (providerMetadata) {
2198
2282
  attributes["fallom.raw.providerMetadata"] = JSON.stringify(providerMetadata);
2199
2283
  }
2284
+ try {
2285
+ attributes["fallom.raw.metadata"] = JSON.stringify(result, sanitizeMetadataOnly);
2286
+ } catch {
2287
+ }
2200
2288
  const promptCtx = getPromptContext();
2201
2289
  sendTrace({
2202
2290
  config_key: ctx.configKey,
@@ -2293,6 +2381,13 @@ function wrapMastraAgent(agent, sessionCtx) {
2293
2381
  attributes["fallom.raw.request"] = JSON.stringify(input);
2294
2382
  attributes["fallom.raw.response"] = JSON.stringify(result);
2295
2383
  }
2384
+ try {
2385
+ attributes["fallom.raw.metadata"] = JSON.stringify(
2386
+ result,
2387
+ sanitizeMetadataOnly
2388
+ );
2389
+ } catch {
2390
+ }
2296
2391
  sendTrace({
2297
2392
  config_key: ctx.configKey,
2298
2393
  session_id: ctx.sessionId,
@@ -0,0 +1,8 @@
1
+ import {
2
+ get,
3
+ init
4
+ } from "./chunk-KFD5AQ7V.mjs";
5
+ export {
6
+ get,
7
+ init
8
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fallom/trace",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
4
4
  "description": "Model A/B testing and tracing for LLM applications. Zero latency, production-ready.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",