@fallom/trace 0.2.16 → 0.2.18

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,
@@ -2658,20 +2727,36 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
2658
2727
  tools: params?.tools ? Object.keys(params.tools) : void 0,
2659
2728
  maxSteps: params?.maxSteps
2660
2729
  });
2661
- const mapToolCall = (tc) => ({
2662
- toolCallId: tc?.toolCallId,
2663
- toolName: tc?.toolName,
2664
- args: tc?.args ?? tc?.input,
2665
- // v4: args, v5: input
2666
- type: tc?.type
2667
- });
2668
- const mapToolResult = (tr) => ({
2669
- toolCallId: tr?.toolCallId,
2670
- toolName: tr?.toolName,
2671
- result: tr?.result ?? tr?.output,
2672
- // v4: result, v5: output
2673
- type: tr?.type
2674
- });
2730
+ const mapToolCall = (tc) => {
2731
+ let args2 = tc?.args ?? tc?.input;
2732
+ if (args2 === void 0 && tc) {
2733
+ const { type, toolCallId, toolName, providerExecuted, dynamic, invalid, error, providerMetadata, ...rest } = tc;
2734
+ if (Object.keys(rest).length > 0) {
2735
+ args2 = rest;
2736
+ }
2737
+ }
2738
+ return {
2739
+ toolCallId: tc?.toolCallId,
2740
+ toolName: tc?.toolName,
2741
+ args: args2,
2742
+ type: tc?.type
2743
+ };
2744
+ };
2745
+ const mapToolResult = (tr) => {
2746
+ let result2 = tr?.result ?? tr?.output;
2747
+ if (result2 === void 0 && tr) {
2748
+ const { type, toolCallId, toolName, ...rest } = tr;
2749
+ if (Object.keys(rest).length > 0) {
2750
+ result2 = rest;
2751
+ }
2752
+ }
2753
+ return {
2754
+ toolCallId: tr?.toolCallId,
2755
+ toolName: tr?.toolName,
2756
+ result: result2,
2757
+ type: tr?.type
2758
+ };
2759
+ };
2675
2760
  attributes["fallom.raw.response"] = JSON.stringify({
2676
2761
  text: result?.text,
2677
2762
  finishReason: result?.finishReason,
@@ -2705,6 +2790,13 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
2705
2790
  result.experimental_providerMetadata
2706
2791
  );
2707
2792
  }
2793
+ try {
2794
+ attributes["fallom.raw.metadata"] = JSON.stringify(
2795
+ result,
2796
+ sanitizeMetadataOnly
2797
+ );
2798
+ } catch {
2799
+ }
2708
2800
  const totalDurationMs = endTime - startTime;
2709
2801
  const sortedToolTimings = Array.from(toolTimings.values()).sort(
2710
2802
  (a, b) => a.startTime - b.startTime
@@ -2877,7 +2969,9 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
2877
2969
  let wrappedParams = params;
2878
2970
  if (params.tools && typeof params.tools === "object") {
2879
2971
  const wrappedTools = {};
2880
- for (const [toolName, tool] of Object.entries(params.tools)) {
2972
+ for (const [toolName, tool] of Object.entries(
2973
+ params.tools
2974
+ )) {
2881
2975
  if (tool && typeof tool.execute === "function") {
2882
2976
  const originalExecute = tool.execute;
2883
2977
  wrappedTools[toolName] = {
@@ -2960,10 +3054,54 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
2960
3054
  "\u{1F50D} [Fallom Debug] streamText toolCalls:",
2961
3055
  JSON.stringify(toolCalls, null, 2)
2962
3056
  );
3057
+ if (toolCalls?.[0]) {
3058
+ console.log(
3059
+ "\u{1F50D} [Fallom Debug] streamText toolCalls[0] keys:",
3060
+ Object.keys(toolCalls[0])
3061
+ );
3062
+ console.log(
3063
+ "\u{1F50D} [Fallom Debug] streamText toolCalls[0] full:",
3064
+ JSON.stringify(
3065
+ toolCalls[0],
3066
+ Object.getOwnPropertyNames(toolCalls[0]),
3067
+ 2
3068
+ )
3069
+ );
3070
+ }
2963
3071
  console.log(
2964
3072
  "\u{1F50D} [Fallom Debug] streamText steps count:",
2965
3073
  steps?.length
2966
3074
  );
3075
+ if (steps?.[0]?.toolCalls?.[0]) {
3076
+ const tc = steps[0].toolCalls[0];
3077
+ console.log(
3078
+ "\u{1F50D} [Fallom Debug] steps[0].toolCalls[0] keys:",
3079
+ Object.keys(tc)
3080
+ );
3081
+ console.log(
3082
+ "\u{1F50D} [Fallom Debug] steps[0].toolCalls[0].args (v4):",
3083
+ tc.args
3084
+ );
3085
+ console.log(
3086
+ "\u{1F50D} [Fallom Debug] steps[0].toolCalls[0].input (v5):",
3087
+ tc.input
3088
+ );
3089
+ }
3090
+ if (steps?.[0]?.toolResults?.[0]) {
3091
+ const tr = steps[0].toolResults[0];
3092
+ console.log(
3093
+ "\u{1F50D} [Fallom Debug] steps[0].toolResults[0] keys:",
3094
+ Object.keys(tr)
3095
+ );
3096
+ console.log(
3097
+ "\u{1F50D} [Fallom Debug] steps[0].toolResults[0].result (v4):",
3098
+ typeof tr.result === "string" ? tr.result.slice(0, 200) : tr.result
3099
+ );
3100
+ console.log(
3101
+ "\u{1F50D} [Fallom Debug] steps[0].toolResults[0].output (v5):",
3102
+ typeof tr.output === "string" ? tr.output.slice(0, 200) : tr.output
3103
+ );
3104
+ }
2967
3105
  }
2968
3106
  let providerMetadata = result?.experimental_providerMetadata;
2969
3107
  if (providerMetadata && typeof providerMetadata.then === "function") {
@@ -2979,20 +3117,46 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
2979
3117
  "fallom.is_streaming": true
2980
3118
  };
2981
3119
  if (captureContent2) {
2982
- const mapToolCall = (tc) => ({
2983
- toolCallId: tc?.toolCallId,
2984
- toolName: tc?.toolName,
2985
- args: tc?.args ?? tc?.input,
2986
- // v4: args, v5: input
2987
- type: tc?.type
2988
- });
2989
- const mapToolResult = (tr) => ({
2990
- toolCallId: tr?.toolCallId,
2991
- toolName: tr?.toolName,
2992
- result: tr?.result ?? tr?.output,
2993
- // v4: result, v5: output
2994
- type: tr?.type
2995
- });
3120
+ const mapToolCall = (tc) => {
3121
+ let args2 = tc?.args ?? tc?.input;
3122
+ if (args2 === void 0 && tc) {
3123
+ const {
3124
+ type,
3125
+ toolCallId,
3126
+ toolName,
3127
+ providerExecuted,
3128
+ dynamic,
3129
+ invalid,
3130
+ error,
3131
+ providerMetadata: providerMetadata2,
3132
+ ...rest
3133
+ } = tc;
3134
+ if (Object.keys(rest).length > 0) {
3135
+ args2 = rest;
3136
+ }
3137
+ }
3138
+ return {
3139
+ toolCallId: tc?.toolCallId,
3140
+ toolName: tc?.toolName,
3141
+ args: args2,
3142
+ type: tc?.type
3143
+ };
3144
+ };
3145
+ const mapToolResult = (tr) => {
3146
+ let result2 = tr?.result ?? tr?.output;
3147
+ if (result2 === void 0 && tr) {
3148
+ const { type, toolCallId, toolName, ...rest } = tr;
3149
+ if (Object.keys(rest).length > 0) {
3150
+ result2 = rest;
3151
+ }
3152
+ }
3153
+ return {
3154
+ toolCallId: tr?.toolCallId,
3155
+ toolName: tr?.toolName,
3156
+ result: result2,
3157
+ type: tr?.type
3158
+ };
3159
+ };
2996
3160
  attributes["fallom.raw.request"] = JSON.stringify({
2997
3161
  prompt: params?.prompt,
2998
3162
  messages: params?.messages,
@@ -3033,6 +3197,13 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
3033
3197
  if (firstTokenTime) {
3034
3198
  attributes["fallom.time_to_first_token_ms"] = firstTokenTime - startTime;
3035
3199
  }
3200
+ try {
3201
+ attributes["fallom.raw.metadata"] = JSON.stringify(
3202
+ result,
3203
+ sanitizeMetadataOnly
3204
+ );
3205
+ } catch {
3206
+ }
3036
3207
  const totalDurationMs = endTime - startTime;
3037
3208
  const sortedToolTimings = Array.from(toolTimings.values()).sort(
3038
3209
  (a, b) => a.startTime - b.startTime
@@ -3057,8 +3228,12 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
3057
3228
  });
3058
3229
  }
3059
3230
  if (sortedToolTimings.length > 0) {
3060
- const firstToolStart = Math.min(...sortedToolTimings.map((t) => t.startTime));
3061
- const lastToolEnd = Math.max(...sortedToolTimings.map((t) => t.endTime));
3231
+ const firstToolStart = Math.min(
3232
+ ...sortedToolTimings.map((t) => t.startTime)
3233
+ );
3234
+ const lastToolEnd = Math.max(
3235
+ ...sortedToolTimings.map((t) => t.endTime)
3236
+ );
3062
3237
  if (firstToolStart > 10) {
3063
3238
  waterfallTimings.phases.push({
3064
3239
  type: "llm",
@@ -3238,6 +3413,10 @@ function createGenerateObjectWrapper(aiModule, sessionCtx, debug = false) {
3238
3413
  result.experimental_providerMetadata
3239
3414
  );
3240
3415
  }
3416
+ try {
3417
+ attributes["fallom.raw.metadata"] = JSON.stringify(result, sanitizeMetadataOnly);
3418
+ } catch {
3419
+ }
3241
3420
  const promptCtx = getPromptContext();
3242
3421
  sendTrace({
3243
3422
  config_key: ctx.configKey,
@@ -3357,6 +3536,10 @@ function createStreamObjectWrapper(aiModule, sessionCtx, debug = false) {
3357
3536
  if (providerMetadata) {
3358
3537
  attributes["fallom.raw.providerMetadata"] = JSON.stringify(providerMetadata);
3359
3538
  }
3539
+ try {
3540
+ attributes["fallom.raw.metadata"] = JSON.stringify(result, sanitizeMetadataOnly);
3541
+ } catch {
3542
+ }
3360
3543
  const promptCtx = getPromptContext();
3361
3544
  sendTrace({
3362
3545
  config_key: ctx.configKey,
@@ -3453,6 +3636,13 @@ function wrapMastraAgent(agent, sessionCtx) {
3453
3636
  attributes["fallom.raw.request"] = JSON.stringify(input);
3454
3637
  attributes["fallom.raw.response"] = JSON.stringify(result);
3455
3638
  }
3639
+ try {
3640
+ attributes["fallom.raw.metadata"] = JSON.stringify(
3641
+ result,
3642
+ sanitizeMetadataOnly
3643
+ );
3644
+ } catch {
3645
+ }
3456
3646
  sendTrace({
3457
3647
  config_key: ctx.configKey,
3458
3648
  session_id: ctx.sessionId,