@revenium/anthropic 1.0.9 → 1.1.1

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.
@@ -18,30 +18,30 @@ const constants_1 = require("../constants");
18
18
  * Type guard for checking if a value is a non-null object
19
19
  */
20
20
  function isObject(value) {
21
- return typeof value === 'object' && value !== null && !Array.isArray(value);
21
+ return typeof value === "object" && value !== null && !Array.isArray(value);
22
22
  }
23
23
  /**
24
24
  * Type guard for checking if a value is a string
25
25
  */
26
26
  function isString(value) {
27
- return typeof value === 'string';
27
+ return typeof value === "string";
28
28
  }
29
29
  /**
30
30
  * Type guard for checking if a value is a number
31
31
  */
32
32
  function isNumber(value) {
33
- return typeof value === 'number' && !isNaN(value);
33
+ return typeof value === "number" && !isNaN(value);
34
34
  }
35
35
  /**
36
36
  * Type guard for checking if a value is a boolean
37
37
  */
38
38
  function isBoolean(value) {
39
- return typeof value === 'boolean';
39
+ return typeof value === "boolean";
40
40
  }
41
41
  /**
42
42
  * Validate and extract string from unknown value
43
43
  */
44
- function validateString(value, defaultValue = '') {
44
+ function validateString(value, defaultValue = "") {
45
45
  return isString(value) ? value : defaultValue;
46
46
  }
47
47
  /**
@@ -60,8 +60,14 @@ function validateUsageMetadata(metadata) {
60
60
  const validated = {};
61
61
  // Validate string fields
62
62
  const stringFields = [
63
- 'traceId', 'taskType', 'organizationId',
64
- 'subscriptionId', 'productId', 'agent'
63
+ "traceId",
64
+ "taskType",
65
+ "organizationId",
66
+ "organizationName",
67
+ "subscriptionId",
68
+ "productId",
69
+ "productName",
70
+ "agent",
65
71
  ];
66
72
  for (const field of stringFields) {
67
73
  const value = metadata[field];
@@ -74,21 +80,25 @@ function validateUsageMetadata(metadata) {
74
80
  if (isObject(subscriberData)) {
75
81
  const subscriber = {};
76
82
  // Validate subscriber.id
77
- if (isString(subscriberData?.id) && subscriberData?.id?.trim()?.length > 0) {
83
+ if (isString(subscriberData?.id) &&
84
+ subscriberData?.id?.trim()?.length > 0) {
78
85
  subscriber.id = subscriberData?.id?.trim();
79
86
  }
80
87
  // Validate subscriber.email
81
- if (isString(subscriberData?.email) && subscriberData?.email?.trim()?.length > 0) {
88
+ if (isString(subscriberData?.email) &&
89
+ subscriberData?.email?.trim()?.length > 0) {
82
90
  subscriber.email = subscriberData?.email?.trim();
83
91
  }
84
92
  // Validate subscriber.credential object
85
93
  const credentialData = subscriberData?.credential;
86
94
  if (isObject(credentialData)) {
87
95
  const credential = {};
88
- if (isString(credentialData?.name) && credentialData?.name?.trim()?.length > 0) {
96
+ if (isString(credentialData?.name) &&
97
+ credentialData?.name?.trim()?.length > 0) {
89
98
  credential.name = credentialData?.name?.trim();
90
99
  }
91
- if (isString(credentialData?.value) && credentialData?.value?.trim()?.length > 0) {
100
+ if (isString(credentialData?.value) &&
101
+ credentialData?.value?.trim()?.length > 0) {
92
102
  credential.value = credentialData?.value?.trim();
93
103
  }
94
104
  // Only include credential if it has at least name or value
@@ -102,10 +112,18 @@ function validateUsageMetadata(metadata) {
102
112
  }
103
113
  }
104
114
  // Validate number fields
105
- const responseQualityScore = metadata.responseQualityScore;
106
- if (isNumber(responseQualityScore) && responseQualityScore >= 0 && responseQualityScore <= 1) {
115
+ const responseQualityScore = metadata
116
+ .responseQualityScore;
117
+ if (isNumber(responseQualityScore) &&
118
+ responseQualityScore >= 0 &&
119
+ responseQualityScore <= 1) {
107
120
  validated.responseQualityScore = responseQualityScore;
108
121
  }
122
+ // Validate boolean fields
123
+ const capturePrompts = metadata.capturePrompts;
124
+ if (typeof capturePrompts === "boolean") {
125
+ validated.capturePrompts = capturePrompts;
126
+ }
109
127
  return validated;
110
128
  }
111
129
  /**
@@ -118,96 +136,104 @@ function validateReveniumConfig(config) {
118
136
  if (!isObject(config)) {
119
137
  return {
120
138
  isValid: false,
121
- errors: ['Configuration must be an object'],
139
+ errors: ["Configuration must be an object"],
122
140
  warnings: [],
123
- suggestions: ['Ensure you are passing a valid configuration object with required fields']
141
+ suggestions: [
142
+ "Ensure you are passing a valid configuration object with required fields",
143
+ ],
124
144
  };
125
145
  }
126
146
  const cfg = config;
127
147
  // Validate required Revenium API key
128
148
  if (!isString(cfg?.reveniumApiKey)) {
129
- errors.push('reveniumApiKey is required and must be a string');
130
- suggestions.push('Set REVENIUM_METERING_API_KEY environment variable or provide reveniumApiKey in config');
149
+ errors.push("reveniumApiKey is required and must be a string");
150
+ suggestions.push("Set REVENIUM_METERING_API_KEY environment variable or provide reveniumApiKey in config");
131
151
  }
132
152
  else if (!cfg?.reveniumApiKey?.trim()) {
133
- errors.push('reveniumApiKey cannot be empty');
153
+ errors.push("reveniumApiKey cannot be empty");
134
154
  }
135
155
  else if (!cfg?.reveniumApiKey?.startsWith(constants_1.VALIDATION_CONFIG.REVENIUM_API_KEY_PREFIX)) {
136
156
  errors.push(`reveniumApiKey must start with "${constants_1.VALIDATION_CONFIG.REVENIUM_API_KEY_PREFIX}"`);
137
- suggestions.push('Obtain a valid Revenium API key from your Revenium dashboard');
157
+ suggestions.push("Obtain a valid Revenium API key from your Revenium dashboard");
138
158
  }
139
159
  else if (cfg?.reveniumApiKey?.length < constants_1.VALIDATION_CONFIG.MIN_API_KEY_LENGTH) {
140
- warnings.push('reveniumApiKey appears to be too short - verify it is correct');
160
+ warnings.push("reveniumApiKey appears to be too short - verify it is correct");
141
161
  }
142
162
  // Validate Revenium base URL (optional - defaults to https://api.revenium.ai)
143
163
  if (cfg?.reveniumBaseUrl !== undefined) {
144
164
  if (!isString(cfg?.reveniumBaseUrl)) {
145
- errors.push('reveniumBaseUrl must be a string if provided');
165
+ errors.push("reveniumBaseUrl must be a string if provided");
146
166
  }
147
167
  else if (!cfg?.reveniumBaseUrl?.trim()) {
148
- errors.push('reveniumBaseUrl cannot be empty if provided');
168
+ errors.push("reveniumBaseUrl cannot be empty if provided");
149
169
  }
150
170
  else {
151
171
  try {
152
172
  const url = new URL(cfg?.reveniumBaseUrl);
153
- if (!url.protocol.startsWith('http')) {
154
- errors.push('reveniumBaseUrl must use HTTP or HTTPS protocol');
173
+ if (!url.protocol.startsWith("http")) {
174
+ errors.push("reveniumBaseUrl must use HTTP or HTTPS protocol");
155
175
  }
156
176
  // Check for localhost/development hostnames (IPv4, IPv6, and named)
157
- const localhostHostnames = ['localhost', '127.0.0.1', '::1', '[::1]'];
177
+ const localhostHostnames = ["localhost", "127.0.0.1", "::1", "[::1]"];
158
178
  if (localhostHostnames.includes(url.hostname)) {
159
- warnings.push('Using localhost for Revenium API - ensure this is intended for development');
179
+ warnings.push("Using localhost for Revenium API - ensure this is intended for development");
160
180
  }
161
181
  }
162
182
  catch {
163
- errors.push('reveniumBaseUrl must be a valid URL');
164
- suggestions.push('Use format: https://api.revenium.ai');
183
+ errors.push("reveniumBaseUrl must be a valid URL");
184
+ suggestions.push("Use format: https://api.revenium.ai");
165
185
  }
166
186
  }
167
187
  }
168
188
  // Validate optional Anthropic API key
169
189
  if (cfg?.anthropicApiKey !== undefined && !isString(cfg?.anthropicApiKey)) {
170
- errors.push('anthropicApiKey must be a string if provided');
190
+ errors.push("anthropicApiKey must be a string if provided");
171
191
  }
172
- else if (cfg?.anthropicApiKey !== undefined && cfg?.anthropicApiKey?.trim()?.length === 0) {
173
- warnings.push('anthropicApiKey is empty - API calls may fail');
192
+ else if (cfg?.anthropicApiKey !== undefined &&
193
+ cfg?.anthropicApiKey?.trim()?.length === 0) {
194
+ warnings.push("anthropicApiKey is empty - API calls may fail");
174
195
  }
175
- else if (cfg?.anthropicApiKey && !cfg?.anthropicApiKey?.startsWith(constants_1.VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX)) {
196
+ else if (cfg?.anthropicApiKey &&
197
+ !cfg?.anthropicApiKey?.startsWith(constants_1.VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX)) {
176
198
  warnings.push(`anthropicApiKey does not start with "${constants_1.VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX}" - verify it is correct`);
177
199
  }
178
200
  // Validate optional timeout using constants
179
201
  if (cfg?.apiTimeout !== undefined && !isNumber(cfg?.apiTimeout)) {
180
- errors.push('apiTimeout must be a number if provided');
202
+ errors.push("apiTimeout must be a number if provided");
181
203
  }
182
- else if (cfg?.apiTimeout !== undefined && cfg?.apiTimeout < constants_1.VALIDATION_CONFIG.MIN_API_TIMEOUT) {
204
+ else if (cfg?.apiTimeout !== undefined &&
205
+ cfg?.apiTimeout < constants_1.VALIDATION_CONFIG.MIN_API_TIMEOUT) {
183
206
  errors.push(`apiTimeout must be at least ${constants_1.VALIDATION_CONFIG.MIN_API_TIMEOUT}ms`);
184
207
  }
185
- else if (cfg?.apiTimeout !== undefined && cfg?.apiTimeout > constants_1.VALIDATION_CONFIG.MAX_API_TIMEOUT) {
208
+ else if (cfg?.apiTimeout !== undefined &&
209
+ cfg?.apiTimeout > constants_1.VALIDATION_CONFIG.MAX_API_TIMEOUT) {
186
210
  errors.push(`apiTimeout must not exceed ${constants_1.VALIDATION_CONFIG.MAX_API_TIMEOUT}ms`);
187
211
  }
188
- else if (cfg?.apiTimeout !== undefined && cfg?.apiTimeout < constants_1.VALIDATION_CONFIG.LOW_TIMEOUT_WARNING_THRESHOLD) {
189
- warnings.push('apiTimeout is very low - may cause timeouts for slow networks');
212
+ else if (cfg?.apiTimeout !== undefined &&
213
+ cfg?.apiTimeout < constants_1.VALIDATION_CONFIG.LOW_TIMEOUT_WARNING_THRESHOLD) {
214
+ warnings.push("apiTimeout is very low - may cause timeouts for slow networks");
190
215
  }
191
216
  // Validate optional failSilent
192
217
  if (cfg?.failSilent && !isBoolean(cfg?.failSilent)) {
193
- errors.push('failSilent must be a boolean if provided');
218
+ errors.push("failSilent must be a boolean if provided");
194
219
  }
195
220
  // Validate optional maxRetries using constants
196
221
  if (cfg?.maxRetries !== undefined && !isNumber(cfg?.maxRetries)) {
197
- errors.push('maxRetries must be a number if provided');
222
+ errors.push("maxRetries must be a number if provided");
198
223
  }
199
224
  else if (cfg?.maxRetries !== undefined && cfg?.maxRetries < 0) {
200
- errors.push('maxRetries cannot be negative');
225
+ errors.push("maxRetries cannot be negative");
201
226
  }
202
- else if (cfg?.maxRetries !== undefined && cfg?.maxRetries > constants_1.VALIDATION_CONFIG.MAX_RETRY_ATTEMPTS) {
227
+ else if (cfg?.maxRetries !== undefined &&
228
+ cfg?.maxRetries > constants_1.VALIDATION_CONFIG.MAX_RETRY_ATTEMPTS) {
203
229
  errors.push(`maxRetries should not exceed ${constants_1.VALIDATION_CONFIG.MAX_RETRY_ATTEMPTS}`);
204
230
  }
205
231
  else if (cfg?.maxRetries !== undefined && cfg?.maxRetries === 0) {
206
- warnings.push('maxRetries is 0 - no retry attempts will be made');
232
+ warnings.push("maxRetries is 0 - no retry attempts will be made");
207
233
  }
208
234
  // Validate optional printSummary
209
235
  if (cfg?.printSummary !== undefined) {
210
- const validPrintSummaryValues = [true, false, 'human', 'json'];
236
+ const validPrintSummaryValues = [true, false, "human", "json"];
211
237
  if (!validPrintSummaryValues.includes(cfg?.printSummary)) {
212
238
  errors.push("printSummary must be a boolean or one of: 'human', 'json'");
213
239
  suggestions.push("Use printSummary: true, 'human', or 'json' to enable summary output");
@@ -216,10 +242,10 @@ function validateReveniumConfig(config) {
216
242
  // Validate optional teamId
217
243
  if (cfg?.teamId !== undefined) {
218
244
  if (!isString(cfg?.teamId)) {
219
- errors.push('teamId must be a string if provided');
245
+ errors.push("teamId must be a string if provided");
220
246
  }
221
247
  else if (cfg?.teamId?.trim()?.length === 0) {
222
- errors.push('teamId cannot be empty if provided');
248
+ errors.push("teamId cannot be empty if provided");
223
249
  }
224
250
  }
225
251
  if (errors.length > 0) {
@@ -227,16 +253,16 @@ function validateReveniumConfig(config) {
227
253
  isValid: false,
228
254
  errors,
229
255
  warnings,
230
- suggestions
256
+ suggestions,
231
257
  };
232
258
  }
233
259
  // Determine validated printSummary value
234
260
  let validatedPrintSummary;
235
- if (cfg?.printSummary === true || cfg?.printSummary === 'human') {
236
- validatedPrintSummary = 'human';
261
+ if (cfg?.printSummary === true || cfg?.printSummary === "human") {
262
+ validatedPrintSummary = "human";
237
263
  }
238
- else if (cfg?.printSummary === 'json') {
239
- validatedPrintSummary = 'json';
264
+ else if (cfg?.printSummary === "json") {
265
+ validatedPrintSummary = "json";
240
266
  }
241
267
  else if (cfg?.printSummary === false) {
242
268
  validatedPrintSummary = false;
@@ -244,20 +270,27 @@ function validateReveniumConfig(config) {
244
270
  // Build validated config (apply default for reveniumBaseUrl if not provided)
245
271
  const validatedConfig = {
246
272
  reveniumApiKey: cfg?.reveniumApiKey,
247
- reveniumBaseUrl: isString(cfg?.reveniumBaseUrl) ? cfg?.reveniumBaseUrl : constants_1.DEFAULT_CONFIG.REVENIUM_BASE_URL,
248
- anthropicApiKey: isString(cfg?.anthropicApiKey) ? cfg?.anthropicApiKey : undefined,
273
+ reveniumBaseUrl: isString(cfg?.reveniumBaseUrl)
274
+ ? cfg?.reveniumBaseUrl
275
+ : constants_1.DEFAULT_CONFIG.REVENIUM_BASE_URL,
276
+ anthropicApiKey: isString(cfg?.anthropicApiKey)
277
+ ? cfg?.anthropicApiKey
278
+ : undefined,
249
279
  apiTimeout: isNumber(cfg?.apiTimeout) ? cfg?.apiTimeout : undefined,
250
280
  failSilent: isBoolean(cfg?.failSilent) ? cfg?.failSilent : undefined,
251
281
  maxRetries: isNumber(cfg?.maxRetries) ? cfg?.maxRetries : undefined,
252
282
  printSummary: validatedPrintSummary,
253
- teamId: isString(cfg?.teamId) ? cfg?.teamId?.trim() : undefined
283
+ teamId: isString(cfg?.teamId) ? cfg?.teamId?.trim() : undefined,
284
+ capturePrompts: isBoolean(cfg?.capturePrompts)
285
+ ? cfg?.capturePrompts
286
+ : undefined,
254
287
  };
255
288
  return {
256
289
  isValid: true,
257
290
  errors: [],
258
291
  warnings,
259
292
  config: validatedConfig,
260
- suggestions: suggestions.length > 0 ? suggestions : undefined
293
+ suggestions: suggestions.length > 0 ? suggestions : undefined,
261
294
  };
262
295
  }
263
296
  /**
@@ -269,27 +302,27 @@ function validateAnthropicMessageParams(params) {
269
302
  if (!isObject(params)) {
270
303
  return {
271
304
  isValid: false,
272
- errors: ['Message parameters must be an object'],
273
- warnings: []
305
+ errors: ["Message parameters must be an object"],
306
+ warnings: [],
274
307
  };
275
308
  }
276
309
  const data = params;
277
310
  // Validate required model field
278
311
  if (!isString(data?.model)) {
279
- errors.push('model field is required and must be a string');
312
+ errors.push("model field is required and must be a string");
280
313
  }
281
314
  else if (data?.model?.trim()?.length === 0) {
282
- errors.push('model field cannot be empty');
315
+ errors.push("model field cannot be empty");
283
316
  }
284
317
  else if (!constants_1.ANTHROPIC_PATTERNS.CLAUDE_MODEL_PATTERN.test(data?.model)) {
285
318
  warnings.push('Model name does not contain "claude" - verify it is a valid Anthropic model');
286
319
  }
287
320
  // Validate required messages array
288
321
  if (!Array.isArray(data?.messages)) {
289
- errors.push('messages field is required and must be an array');
322
+ errors.push("messages field is required and must be an array");
290
323
  }
291
324
  else if (data?.messages?.length === 0) {
292
- errors.push('messages array cannot be empty');
325
+ errors.push("messages array cannot be empty");
293
326
  }
294
327
  else {
295
328
  // Validate message structure
@@ -302,37 +335,40 @@ function validateAnthropicMessageParams(params) {
302
335
  if (!isString(msg.role)) {
303
336
  errors.push(`Message at index ${index} must have a role field`);
304
337
  }
305
- else if (!['user', 'assistant', 'system'].includes(msg.role)) {
338
+ else if (!["user", "assistant", "system"].includes(msg.role)) {
306
339
  warnings.push(`Message at index ${index} has unusual role: ${msg.role}`);
307
340
  }
308
- if (msg?.content && !isString(msg?.content) && !Array.isArray(msg?.content)) {
341
+ if (msg?.content &&
342
+ !isString(msg?.content) &&
343
+ !Array.isArray(msg?.content)) {
309
344
  warnings.push(`Message at index ${index} content should be a string or array`);
310
345
  }
311
346
  });
312
347
  }
313
348
  // Validate optional parameters
314
349
  if (!isNumber(data?.max_tokens)) {
315
- warnings.push('max_tokens should be a number');
350
+ warnings.push("max_tokens should be a number");
316
351
  }
317
352
  else if (data?.max_tokens <= 0) {
318
- warnings.push('max_tokens should be positive');
353
+ warnings.push("max_tokens should be positive");
319
354
  }
320
355
  else if (data?.max_tokens > constants_1.VALIDATION_CONFIG.HIGH_MAX_TOKENS_THRESHOLD) {
321
- warnings.push('max_tokens is very high - verify this is intended');
356
+ warnings.push("max_tokens is very high - verify this is intended");
322
357
  }
323
358
  if (!isNumber(data?.temperature)) {
324
- warnings.push('temperature should be a number');
359
+ warnings.push("temperature should be a number");
325
360
  }
326
- else if (data?.temperature < constants_1.VALIDATION_CONFIG.MIN_TEMPERATURE || data?.temperature > constants_1.VALIDATION_CONFIG.MAX_TEMPERATURE) {
361
+ else if (data?.temperature < constants_1.VALIDATION_CONFIG.MIN_TEMPERATURE ||
362
+ data?.temperature > constants_1.VALIDATION_CONFIG.MAX_TEMPERATURE) {
327
363
  warnings.push(`temperature should be between ${constants_1.VALIDATION_CONFIG.MIN_TEMPERATURE} and ${constants_1.VALIDATION_CONFIG.MAX_TEMPERATURE} for Anthropic models`);
328
364
  }
329
365
  if (data?.stream && !isBoolean(data?.stream)) {
330
- warnings.push('stream should be a boolean');
366
+ warnings.push("stream should be a boolean");
331
367
  }
332
368
  return {
333
369
  isValid: errors.length === 0,
334
370
  errors,
335
- warnings
371
+ warnings,
336
372
  };
337
373
  }
338
374
  //# sourceMappingURL=validation.js.map
@@ -14,6 +14,8 @@ const config_1 = require("./config");
14
14
  const tracking_1 = require("./tracking");
15
15
  const validation_1 = require("./utils/validation");
16
16
  const error_handling_1 = require("./utils/error-handling");
17
+ const prompt_extraction_1 = require("./utils/prompt-extraction");
18
+ const trace_fields_1 = require("./utils/trace-fields");
17
19
  const crypto_1 = require("crypto");
18
20
  // Global logger
19
21
  const logger = (0, config_1.getLogger)();
@@ -136,6 +138,65 @@ function unpatchAnthropic() {
136
138
  function isAnthropicPatched() {
137
139
  return patchingContext.isPatched;
138
140
  }
141
+ /**
142
+ * Reconstruct a response object from streaming chunks for prompt capture
143
+ */
144
+ function reconstructResponseFromChunks(chunks, model) {
145
+ const contentBlocks = [];
146
+ let stopReason;
147
+ let stopSequence;
148
+ const usage = {};
149
+ for (const chunk of chunks) {
150
+ if (chunk.type === "content_block_start" && chunk.content_block) {
151
+ contentBlocks.push({ ...chunk.content_block });
152
+ }
153
+ else if (chunk.type === "content_block_delta" && chunk.delta) {
154
+ const lastBlock = contentBlocks[contentBlocks.length - 1];
155
+ if (lastBlock && chunk.delta.type === "text_delta") {
156
+ if (lastBlock.type === "text") {
157
+ lastBlock.text = (lastBlock.text || "") + (chunk.delta.text || "");
158
+ }
159
+ }
160
+ else if (lastBlock && chunk.delta.type === "input_json_delta") {
161
+ if (lastBlock.type === "tool_use") {
162
+ lastBlock.input = lastBlock.input || "";
163
+ lastBlock.input +=
164
+ chunk.delta.partial_json || "";
165
+ }
166
+ }
167
+ }
168
+ else if (chunk.type === "message_delta" && chunk.delta) {
169
+ const delta = chunk.delta;
170
+ if (delta.stop_reason) {
171
+ stopReason = delta.stop_reason;
172
+ }
173
+ if (delta.stop_sequence) {
174
+ stopSequence = delta.stop_sequence;
175
+ }
176
+ }
177
+ else if (chunk.type === "message_start" && chunk.message?.usage) {
178
+ Object.assign(usage, chunk.message.usage);
179
+ }
180
+ else if (chunk.usage) {
181
+ Object.assign(usage, chunk.usage);
182
+ }
183
+ }
184
+ return {
185
+ id: `reconstructed-${Date.now()}`,
186
+ type: "message",
187
+ role: "assistant",
188
+ content: contentBlocks,
189
+ model,
190
+ stop_reason: stopReason || "end_turn",
191
+ stop_sequence: stopSequence,
192
+ usage: {
193
+ input_tokens: usage.input_tokens || 0,
194
+ output_tokens: usage.output_tokens || 0,
195
+ cache_creation_input_tokens: usage.cache_creation_input_tokens,
196
+ cache_read_input_tokens: usage.cache_read_input_tokens,
197
+ },
198
+ };
199
+ }
139
200
  /**
140
201
  * Handle streaming response by collecting chunks and extracting usage data
141
202
  */
@@ -168,6 +229,10 @@ async function handleStreamingResponse(stream, context) {
168
229
  timeToFirstToken,
169
230
  });
170
231
  const usage = (0, tracking_1.extractUsageFromStream)(chunks);
232
+ let reconstructedResponse = undefined;
233
+ if ((0, prompt_extraction_1.shouldCapturePrompts)(metadata)) {
234
+ reconstructedResponse = reconstructResponseFromChunks(chunks, model);
235
+ }
171
236
  // Create tracking data
172
237
  const trackingData = {
173
238
  requestId,
@@ -184,6 +249,8 @@ async function handleStreamingResponse(stream, context) {
184
249
  responseTime,
185
250
  timeToFirstToken,
186
251
  requestBody: requestBody,
252
+ response: reconstructedResponse,
253
+ hasVisionContent: (0, trace_fields_1.detectVisionContent)(requestBody),
187
254
  };
188
255
  // Track usage asynchronously
189
256
  (0, tracking_1.trackUsageAsync)(trackingData);
@@ -245,6 +312,8 @@ async function patchedCreateMethod(params, options) {
245
312
  const responseTime = new Date();
246
313
  // Extract usage information
247
314
  const usage = (0, tracking_1.extractUsageFromResponse)(response);
315
+ // Detect vision content
316
+ const hasVisionContent = (0, trace_fields_1.detectVisionContent)(params);
248
317
  // Create tracking data
249
318
  const trackingData = {
250
319
  requestId,
@@ -259,7 +328,9 @@ async function patchedCreateMethod(params, options) {
259
328
  metadata,
260
329
  requestTime,
261
330
  responseTime,
331
+ hasVisionContent,
262
332
  requestBody: params,
333
+ response,
263
334
  };
264
335
  // Track usage asynchronously
265
336
  (0, tracking_1.trackUsageAsync)(trackingData);
@@ -344,6 +415,12 @@ async function* patchedStreamMethod(params, options) {
344
415
  : undefined;
345
416
  // Extract usage information from all chunks
346
417
  const usage = (0, tracking_1.extractUsageFromStream)(chunks);
418
+ // Detect vision content
419
+ const hasVisionContent = (0, trace_fields_1.detectVisionContent)(params);
420
+ let reconstructedResponse = undefined;
421
+ if ((0, prompt_extraction_1.shouldCapturePrompts)(metadata)) {
422
+ reconstructedResponse = reconstructResponseFromChunks(chunks, params.model);
423
+ }
347
424
  // Create tracking data
348
425
  const trackingData = {
349
426
  requestId,
@@ -359,7 +436,9 @@ async function* patchedStreamMethod(params, options) {
359
436
  requestTime,
360
437
  responseTime,
361
438
  timeToFirstToken,
439
+ hasVisionContent,
362
440
  requestBody: params,
441
+ response: reconstructedResponse,
363
442
  };
364
443
  // Track usage asynchronously
365
444
  (0, tracking_1.trackUsageAsync)(trackingData);