@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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.1.0] - 2026-01-20
6
+
7
+ ### Added
8
+
9
+ - Prompt capture functionality with credential sanitization
10
+ - Environment variable: REVENIUM_CAPTURE_PROMPTS (default: false)
11
+ - Automatic redaction of sensitive credentials in captured prompts
12
+
13
+ ### Security
14
+
15
+ - Added sanitization for API keys, tokens, passwords, and Bearer tokens in prompt data
16
+
5
17
  ## [1.0.9] - 2026-01-06
6
18
 
7
19
  ### Added
package/README.md CHANGED
@@ -12,6 +12,7 @@ Automatically track and meter your Anthropic Claude API usage with Revenium. Thi
12
12
 
13
13
  - **Seamless Integration**: Drop-in replacement with zero code changes required
14
14
  - **Complete Metering**: Track tokens, costs, and performance metrics automatically
15
+ - **Prompt Capture**: Optional capture of prompts and responses with automatic credential sanitization
15
16
  - **Custom Metadata**: Add business context with native TypeScript support
16
17
  - **Streaming Support**: Real-time streaming with comprehensive analytics
17
18
  - **Tool Use Support**: Full support for Anthropic's function calling and tools
@@ -157,6 +158,7 @@ configure({
157
158
  > **Note**: Cost data requires `teamId` to be configured. Without it, the summary will show token usage but not cost.
158
159
  >
159
160
  > **Optional fields**: The JSON output may include additional fields depending on the context:
161
+ >
160
162
  > - `costStatus`: `"pending"` (when `teamId` is set but cost is not yet available) or `"unavailable"` (when `teamId` is not configured). Only present when `cost` is `null`.
161
163
  > - `traceId`: The trace ID for request correlation (only present if `traceId` was provided in `usageMetadata`).
162
164
 
@@ -192,6 +194,55 @@ REVENIUM_TRANSACTION_NAME=Answer Customer Question
192
194
  REVENIUM_RETRY_NUMBER=0
193
195
  ```
194
196
 
197
+ ## Prompt Capture
198
+
199
+ The middleware can capture prompts and responses for analysis and debugging. This feature is **disabled by default** for privacy and performance.
200
+
201
+ ### Configuration
202
+
203
+ Enable via environment variable:
204
+
205
+ ```bash
206
+ REVENIUM_CAPTURE_PROMPTS=true
207
+ ```
208
+
209
+ Or configure programmatically:
210
+
211
+ ```typescript
212
+ import { configure } from "@revenium/anthropic";
213
+
214
+ configure({
215
+ reveniumApiKey: "hak_your_key",
216
+ capturePrompts: true,
217
+ });
218
+ ```
219
+
220
+ ### Per-Request Control
221
+
222
+ Override the global setting for individual requests:
223
+
224
+ ```typescript
225
+ const response = await anthropic.messages.create(
226
+ {
227
+ model: "claude-3-5-sonnet-20241022",
228
+ max_tokens: 1024,
229
+ messages: [{ role: "user", content: "Hello!" }],
230
+ },
231
+ {
232
+ capturePrompts: true, // Enable for this request only
233
+ },
234
+ );
235
+ ```
236
+
237
+ ### Security
238
+
239
+ All captured prompts are automatically sanitized to remove sensitive credentials including:
240
+
241
+ - API keys (sk-\*, sk-proj-\*, sk-ant-\*)
242
+ - Bearer tokens
243
+ - Passwords
244
+ - Generic tokens and api_key fields
245
+
195
246
  ## Advanced Usage
196
247
 
197
248
  ### Initialization Options
@@ -232,9 +283,9 @@ Add business context to track usage by organization, user, task type, or custom
232
283
  | `subscriber.email` | User email address | Identify users for support, compliance, or usage reports |
233
284
  | `subscriber.credential.name` | Authentication credential name | Track which API key or service account made the request |
234
285
  | `subscriber.credential.value` | Authentication credential value | Associate usage with specific credentials for security auditing |
235
- | `organizationId` | Organization or company identifier | Multi-tenant cost allocation, usage quotas per organization |
286
+ | `organizationName` | Organization or company name | Multi-tenant cost allocation, usage quotas per organization (used for lookup/auto-creation) |
236
287
  | `subscriptionId` | Subscription plan identifier | Track usage against subscription limits, identify plan upgrade opportunities |
237
- | `productId` | Your product or feature identifier | Attribute AI costs to specific features in your application (e.g., "chatbot", "email-assistant") |
288
+ | `productName` | Your product or feature name | Attribute AI costs to specific features in your application (e.g., "chatbot", "email-assistant") |
238
289
  | `agent` | AI agent or bot identifier | Distinguish between multiple AI agents or automation workflows in your system |
239
290
  | `responseQualityScore` | Custom quality rating (0.0-1.0) | Track user satisfaction or automated quality metrics for model performance analysis |
240
291
 
@@ -262,8 +313,8 @@ The middleware automatically sends the following fields to Revenium's `/meter/v2
262
313
  | `cacheCreationTokenCount` | number | Anthropic Usage | Tokens used for prompt caching creation |
263
314
  | `cacheReadTokenCount` | number | Anthropic Usage | Tokens read from prompt cache |
264
315
  | `totalTokenCount` | number | Calculated | Sum of input + output tokens |
265
- | `organizationId` | string | usageMetadata | Optional organization identifier |
266
- | `productId` | string | usageMetadata | Optional product identifier |
316
+ | `organizationName` | string | usageMetadata | Optional organization name (used for lookup/auto-creation) |
317
+ | `productName` | string | usageMetadata | Optional product name (used for lookup/auto-creation) |
267
318
  | `subscriber` | object | usageMetadata | Optional subscriber info (id, email, credential) |
268
319
  | `subscriptionId` | string | usageMetadata | Optional subscription plan identifier |
269
320
  | `model` | string | Request | Anthropic model used (e.g., "claude-3-5-haiku-20241022") |
@@ -289,12 +340,13 @@ The middleware automatically sends the following fields to Revenium's `/meter/v2
289
340
 
290
341
  ### Environment Variables
291
342
 
292
- | Variable | Required | Default | Description |
293
- | ---------------------------- | -------- | ------------------------- | --------------------------------- |
294
- | `REVENIUM_METERING_API_KEY` | Yes | - | Your Revenium API key |
295
- | `ANTHROPIC_API_KEY` | Yes | - | Anthropic Claude API key |
296
- | `REVENIUM_METERING_BASE_URL` | No | `https://api.revenium.ai` | Revenium metering API base URL |
297
- | `REVENIUM_DEBUG` | No | `false` | Enable debug logging (true/false) |
343
+ | Variable | Required | Default | Description |
344
+ | ---------------------------- | -------- | ------------------------- | ------------------------------------------ |
345
+ | `REVENIUM_METERING_API_KEY` | Yes | - | Your Revenium API key |
346
+ | `ANTHROPIC_API_KEY` | Yes | - | Anthropic Claude API key |
347
+ | `REVENIUM_METERING_BASE_URL` | No | `https://api.revenium.ai` | Revenium metering API base URL |
348
+ | `REVENIUM_DEBUG` | No | `false` | Enable debug logging (true/false) |
349
+ | `REVENIUM_CAPTURE_PROMPTS` | No | `false` | Capture prompts and responses (true/false) |
298
350
 
299
351
  ### Manual Configuration
300
352
 
@@ -16,27 +16,27 @@ const constants_1 = require("./constants");
16
16
  */
17
17
  class ConsoleLogger {
18
18
  isDebugEnabled() {
19
- return process.env[constants_1.ENV_VARS.DEBUG] === 'true';
19
+ return process.env[constants_1.ENV_VARS.DEBUG] === "true";
20
20
  }
21
21
  formatMessage(level, message, context) {
22
22
  const timestamp = new Date().toISOString();
23
- const prefix = `[${constants_1.LOGGING_CONFIG.MIDDLEWARE_NAME}${level === 'DEBUG' ? ' Debug' : ''}]`;
24
- const contextStr = context ? ` ${JSON.stringify(context)}` : '';
23
+ const prefix = `[${constants_1.LOGGING_CONFIG.MIDDLEWARE_NAME}${level === "DEBUG" ? " Debug" : ""}]`;
24
+ const contextStr = context ? ` ${JSON.stringify(context)}` : "";
25
25
  return `${timestamp} ${prefix} ${message}${contextStr}`;
26
26
  }
27
27
  debug(message, context) {
28
28
  if (this.isDebugEnabled()) {
29
- console.debug(this.formatMessage('DEBUG', message, context));
29
+ console.debug(this.formatMessage("DEBUG", message, context));
30
30
  }
31
31
  }
32
32
  info(message, context) {
33
- console.info(this.formatMessage('INFO', message, context));
33
+ console.info(this.formatMessage("INFO", message, context));
34
34
  }
35
35
  warn(message, context) {
36
- console.warn(this.formatMessage('WARN', message, context));
36
+ console.warn(this.formatMessage("WARN", message, context));
37
37
  }
38
38
  error(message, context) {
39
- console.error(this.formatMessage('ERROR', message, context));
39
+ console.error(this.formatMessage("ERROR", message, context));
40
40
  }
41
41
  }
42
42
  /**
@@ -51,13 +51,14 @@ function loadConfigFromEnvironment() {
51
51
  reveniumApiKey: process.env[constants_1.ENV_VARS.REVENIUM_API_KEY],
52
52
  reveniumBaseUrl: process.env[constants_1.ENV_VARS.REVENIUM_BASE_URL],
53
53
  anthropicApiKey: process.env[constants_1.ENV_VARS.ANTHROPIC_API_KEY],
54
- debug: process.env[constants_1.ENV_VARS.DEBUG] === 'true',
54
+ debug: process.env[constants_1.ENV_VARS.DEBUG] === "true",
55
55
  logLevel: process.env[constants_1.ENV_VARS.LOG_LEVEL],
56
56
  apiTimeout: process.env[constants_1.ENV_VARS.API_TIMEOUT],
57
57
  failSilent: process.env[constants_1.ENV_VARS.FAIL_SILENT],
58
58
  maxRetries: process.env[constants_1.ENV_VARS.MAX_RETRIES],
59
59
  printSummary: process.env[constants_1.ENV_VARS.PRINT_SUMMARY],
60
- teamId: process.env[constants_1.ENV_VARS.TEAM_ID]
60
+ teamId: process.env[constants_1.ENV_VARS.TEAM_ID],
61
+ capturePrompts: process.env[constants_1.ENV_VARS.CAPTURE_PROMPTS],
61
62
  };
62
63
  return env;
63
64
  }
@@ -68,11 +69,11 @@ function parsePrintSummary(value) {
68
69
  if (!value)
69
70
  return undefined;
70
71
  const lowerValue = value.toLowerCase();
71
- if (lowerValue === 'true' || lowerValue === 'human')
72
- return 'human';
73
- if (lowerValue === 'json')
74
- return 'json';
75
- if (lowerValue === 'false')
72
+ if (lowerValue === "true" || lowerValue === "human")
73
+ return "human";
74
+ if (lowerValue === "json")
75
+ return "json";
76
+ if (lowerValue === "false")
76
77
  return false;
77
78
  return undefined;
78
79
  }
@@ -84,9 +85,10 @@ function createConfigFromEnvironment(env) {
84
85
  return null;
85
86
  }
86
87
  const apiTimeout = env.apiTimeout ? parseInt(env.apiTimeout, 10) : undefined;
87
- const failSilent = env.failSilent !== 'false'; // Default to true
88
+ const failSilent = env.failSilent !== "false"; // Default to true
88
89
  const maxRetries = env.maxRetries ? parseInt(env.maxRetries, 10) : undefined;
89
90
  const printSummary = parsePrintSummary(env.printSummary);
91
+ const capturePrompts = env.capturePrompts === "true";
90
92
  return {
91
93
  reveniumApiKey: env.reveniumApiKey,
92
94
  reveniumBaseUrl: env.reveniumBaseUrl || constants_1.DEFAULT_CONFIG.REVENIUM_BASE_URL,
@@ -95,7 +97,8 @@ function createConfigFromEnvironment(env) {
95
97
  failSilent,
96
98
  maxRetries,
97
99
  printSummary,
98
- teamId: env.teamId?.trim()
100
+ teamId: env.teamId?.trim(),
101
+ capturePrompts,
99
102
  };
100
103
  }
101
104
  /**
@@ -105,18 +108,18 @@ function validateConfig(config) {
105
108
  const validation = (0, validation_1.validateReveniumConfig)(config);
106
109
  if (!validation.isValid) {
107
110
  // Log detailed validation errors
108
- getLogger().error('Configuration validation failed', {
111
+ getLogger().error("Configuration validation failed", {
109
112
  errors: validation.errors,
110
113
  warnings: validation.warnings,
111
- suggestions: validation.suggestions
114
+ suggestions: validation.suggestions,
112
115
  });
113
116
  // Create detailed error message
114
- let errorMessage = 'Configuration validation failed:\n';
117
+ let errorMessage = "Configuration validation failed:\n";
115
118
  validation.errors.forEach((error, index) => {
116
119
  errorMessage += ` ${index + 1}. ${error}\n`;
117
120
  });
118
121
  if (validation.suggestions && validation.suggestions.length > 0) {
119
- errorMessage += '\nSuggestions:\n';
122
+ errorMessage += "\nSuggestions:\n";
120
123
  validation.suggestions.forEach((suggestion) => {
121
124
  errorMessage += ` • ${suggestion}\n`;
122
125
  });
@@ -125,8 +128,8 @@ function validateConfig(config) {
125
128
  }
126
129
  // Log warnings if any
127
130
  if (validation.warnings && validation.warnings.length > 0) {
128
- getLogger().warn('Configuration warnings', {
129
- warnings: validation.warnings
131
+ getLogger().warn("Configuration warnings", {
132
+ warnings: validation.warnings,
130
133
  });
131
134
  }
132
135
  }
@@ -149,18 +152,18 @@ function setConfig(config) {
149
152
  const validation = (0, validation_1.validateReveniumConfig)(config);
150
153
  if (!validation.isValid) {
151
154
  // Log detailed validation errors
152
- getLogger().error('Configuration validation failed', {
155
+ getLogger().error("Configuration validation failed", {
153
156
  errors: validation.errors,
154
157
  warnings: validation.warnings,
155
- suggestions: validation.suggestions
158
+ suggestions: validation.suggestions,
156
159
  });
157
160
  // Create detailed error message
158
- let errorMessage = 'Configuration validation failed:\n';
161
+ let errorMessage = "Configuration validation failed:\n";
159
162
  validation.errors.forEach((error, index) => {
160
163
  errorMessage += ` ${index + 1}. ${error}\n`;
161
164
  });
162
165
  if (validation.suggestions && validation.suggestions.length > 0) {
163
- errorMessage += '\nSuggestions:\n';
166
+ errorMessage += "\nSuggestions:\n";
164
167
  validation.suggestions.forEach((suggestion) => {
165
168
  errorMessage += ` • ${suggestion}\n`;
166
169
  });
@@ -169,16 +172,16 @@ function setConfig(config) {
169
172
  }
170
173
  // Log warnings if any
171
174
  if (validation.warnings && validation.warnings.length > 0) {
172
- getLogger().warn('Configuration warnings', {
173
- warnings: validation.warnings
175
+ getLogger().warn("Configuration warnings", {
176
+ warnings: validation.warnings,
174
177
  });
175
178
  }
176
179
  // Use the normalized config from validation (with defaults applied and fields trimmed)
177
180
  globalConfig = validation.config;
178
- globalLogger.debug('Revenium configuration updated', {
181
+ globalLogger.debug("Revenium configuration updated", {
179
182
  baseUrl: globalConfig.reveniumBaseUrl,
180
183
  hasApiKey: !!globalConfig.reveniumApiKey,
181
- hasAnthropicKey: !!globalConfig.anthropicApiKey
184
+ hasAnthropicKey: !!globalConfig.anthropicApiKey,
182
185
  });
183
186
  }
184
187
  /**
@@ -192,7 +195,7 @@ function getLogger() {
192
195
  */
193
196
  function setLogger(logger) {
194
197
  globalLogger = logger;
195
- globalLogger.debug('Custom logger set for Revenium middleware');
198
+ globalLogger.debug("Custom logger set for Revenium middleware");
196
199
  }
197
200
  /**
198
201
  * Initialize configuration from environment variables
@@ -203,12 +206,12 @@ function initializeConfig() {
203
206
  if (config) {
204
207
  try {
205
208
  setConfig(config);
206
- globalLogger.debug('Revenium middleware initialized from environment variables');
209
+ globalLogger.debug("Revenium middleware initialized from environment variables");
207
210
  return true;
208
211
  }
209
212
  catch (error) {
210
- globalLogger.error('Failed to initialize Revenium configuration', {
211
- error: error instanceof Error ? error.message : String(error)
213
+ globalLogger.error("Failed to initialize Revenium configuration", {
214
+ error: error instanceof Error ? error.message : String(error),
212
215
  });
213
216
  return false;
214
217
  }
@@ -229,14 +232,14 @@ function getConfigStatus() {
229
232
  hasConfig: false,
230
233
  hasApiKey: false,
231
234
  hasAnthropicKey: false,
232
- baseUrl: ''
235
+ baseUrl: "",
233
236
  };
234
237
  }
235
238
  return {
236
239
  hasConfig: true,
237
240
  hasApiKey: !!globalConfig.reveniumApiKey,
238
241
  hasAnthropicKey: !!globalConfig.anthropicApiKey,
239
- baseUrl: globalConfig.reveniumBaseUrl
242
+ baseUrl: globalConfig.reveniumBaseUrl,
240
243
  };
241
244
  }
242
245
  /**
@@ -25,6 +25,10 @@ exports.DEFAULT_CONFIG = {
25
25
  MAX_RETRY_ATTEMPTS: 10,
26
26
  /** Warning threshold for low API timeout */
27
27
  LOW_TIMEOUT_WARNING_THRESHOLD: 3000,
28
+ /** Default prompt capture behavior */
29
+ CAPTURE_PROMPTS: false,
30
+ /** Maximum size for each prompt field in characters (50KB) */
31
+ MAX_PROMPT_SIZE: 50000,
28
32
  };
29
33
  /**
30
34
  * Circuit breaker configuration constants
@@ -116,6 +120,8 @@ exports.ENV_VARS = {
116
120
  PRINT_SUMMARY: "REVENIUM_PRINT_SUMMARY",
117
121
  /** Team ID for cost metrics retrieval */
118
122
  TEAM_ID: "REVENIUM_TEAM_ID",
123
+ /** Prompt capture mode */
124
+ CAPTURE_PROMPTS: "REVENIUM_CAPTURE_PROMPTS",
119
125
  };
120
126
  /**
121
127
  * Summary printer configuration
@@ -17,6 +17,7 @@ const error_handling_1 = require("./utils/error-handling");
17
17
  const constants_1 = require("./constants");
18
18
  const trace_fields_1 = require("./utils/trace-fields");
19
19
  const summary_printer_1 = require("./utils/summary-printer");
20
+ const prompt_extraction_1 = require("./utils/prompt-extraction");
20
21
  // Global logger
21
22
  const logger = (0, config_1.getLogger)();
22
23
  if (typeof process !== "undefined") {
@@ -152,7 +153,9 @@ async function buildReveniumPayload(data) {
152
153
  "taskType",
153
154
  "agent",
154
155
  "organizationId",
156
+ "organizationName",
155
157
  "productId",
158
+ "productName",
156
159
  "subscriber",
157
160
  "subscriptionId",
158
161
  "traceId",
@@ -176,6 +179,10 @@ async function buildReveniumPayload(data) {
176
179
  const transactionName = (0, trace_fields_1.getTransactionName)();
177
180
  const retryNumber = (0, trace_fields_1.getRetryNumber)();
178
181
  const operationSubtype = (0, trace_fields_1.detectOperationSubtype)(data.requestBody);
182
+ let promptData = null;
183
+ if (data.requestBody && data.response) {
184
+ promptData = (0, prompt_extraction_1.extractPrompts)(data.requestBody, data.response, data.metadata);
185
+ }
179
186
  return {
180
187
  stopReason: getStopReason(data.stopReason),
181
188
  costType: "AI",
@@ -198,10 +205,12 @@ async function buildReveniumPayload(data) {
198
205
  middlewareSource: "nodejs",
199
206
  ...(data.metadata?.taskType && { taskType: data.metadata.taskType }),
200
207
  ...(data.metadata?.agent && { agent: data.metadata.agent }),
201
- ...(data.metadata?.organizationId && {
202
- organizationId: data.metadata.organizationId,
208
+ ...((data.metadata?.organizationName || data.metadata?.organizationId) && {
209
+ organizationName: data.metadata.organizationName || data.metadata.organizationId,
210
+ }),
211
+ ...((data.metadata?.productName || data.metadata?.productId) && {
212
+ productName: data.metadata.productName || data.metadata.productId,
203
213
  }),
204
- ...(data.metadata?.productId && { productId: data.metadata.productId }),
205
214
  ...(data.metadata?.subscriber && { subscriber: data.metadata.subscriber }),
206
215
  ...(data.metadata?.subscriptionId && {
207
216
  subscriptionId: data.metadata.subscriptionId,
@@ -219,6 +228,15 @@ async function buildReveniumPayload(data) {
219
228
  ...(transactionName && { transactionName }),
220
229
  ...(retryNumber !== undefined && { retryNumber }),
221
230
  ...(operationSubtype && { operationSubtype }),
231
+ ...(data.hasVisionContent !== undefined && {
232
+ hasVisionContent: data.hasVisionContent,
233
+ }),
234
+ ...(promptData && {
235
+ systemPrompt: promptData.systemPrompt,
236
+ inputMessages: promptData.inputMessages,
237
+ outputResponse: promptData.outputResponse,
238
+ promptsTruncated: promptData.promptsTruncated,
239
+ }),
222
240
  ...customFields,
223
241
  };
224
242
  }
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.shouldCapturePrompts = shouldCapturePrompts;
4
+ exports.extractPrompts = extractPrompts;
5
+ const constants_1 = require("../constants");
6
+ const config_1 = require("../config");
7
+ function sanitizeCredentials(text) {
8
+ const patterns = [
9
+ {
10
+ regex: /sk-proj-[a-zA-Z0-9_-]{48,}/g,
11
+ replacement: "sk-proj-***REDACTED***",
12
+ },
13
+ { regex: /sk-[a-zA-Z0-9_-]{20,}/g, replacement: "sk-***REDACTED***" },
14
+ {
15
+ regex: /Bearer\s+[a-zA-Z0-9_\-\.]+/gi,
16
+ replacement: "Bearer ***REDACTED***",
17
+ },
18
+ {
19
+ regex: /api[_-]?key["\s:=]+[a-zA-Z0-9_\-\.\+\/=]{20,}/gi,
20
+ replacement: "api_key: ***REDACTED***",
21
+ },
22
+ {
23
+ regex: /token["\s:=]+[a-zA-Z0-9_\-\.]{20,}/gi,
24
+ replacement: "token: ***REDACTED***",
25
+ },
26
+ {
27
+ regex: /password["\s:=]+[^\s"']{8,}/gi,
28
+ replacement: "password: ***REDACTED***",
29
+ },
30
+ ];
31
+ let sanitized = text;
32
+ for (const pattern of patterns) {
33
+ sanitized = sanitized.replace(pattern.regex, pattern.replacement);
34
+ }
35
+ return sanitized;
36
+ }
37
+ function truncateString(str, maxLength) {
38
+ const sanitized = sanitizeCredentials(str);
39
+ if (sanitized.length <= maxLength) {
40
+ return { value: sanitized, truncated: false };
41
+ }
42
+ return { value: sanitized.substring(0, maxLength), truncated: true };
43
+ }
44
+ function extractSystemPrompt(params) {
45
+ if (!params.system) {
46
+ return "";
47
+ }
48
+ if (typeof params.system === "string") {
49
+ return params.system;
50
+ }
51
+ if (Array.isArray(params.system)) {
52
+ return params.system
53
+ .map((block) => {
54
+ if (block.type === "text") {
55
+ return block.text;
56
+ }
57
+ if (block.type === "image") {
58
+ return "[IMAGE]";
59
+ }
60
+ return "";
61
+ })
62
+ .filter(Boolean)
63
+ .join("\n");
64
+ }
65
+ return "";
66
+ }
67
+ function extractInputMessages(params) {
68
+ if (!params.messages || params.messages.length === 0) {
69
+ return "";
70
+ }
71
+ return params.messages
72
+ .map((message) => {
73
+ const role = message.role;
74
+ let content = "";
75
+ if (typeof message.content === "string") {
76
+ content = message.content;
77
+ }
78
+ else if (Array.isArray(message.content)) {
79
+ content = message.content
80
+ .map((block) => {
81
+ if (block.type === "text") {
82
+ return block.text;
83
+ }
84
+ if (block.type === "image") {
85
+ return "[IMAGE]";
86
+ }
87
+ if (block.type === "tool_use") {
88
+ const toolName = block.name || "unknown";
89
+ return `[TOOL_USE: ${toolName}]`;
90
+ }
91
+ if (block.type === "tool_result") {
92
+ return "[TOOL_RESULT]";
93
+ }
94
+ return "";
95
+ })
96
+ .filter(Boolean)
97
+ .join("\n");
98
+ }
99
+ return `[${role}]\n${content}`;
100
+ })
101
+ .join("\n\n");
102
+ }
103
+ function extractOutputResponse(response) {
104
+ if (!response.content || response.content.length === 0) {
105
+ return "";
106
+ }
107
+ return response.content
108
+ .map((block) => {
109
+ if (block.type === "text") {
110
+ return block.text;
111
+ }
112
+ if (block.type === "tool_use") {
113
+ return `[TOOL_USE: ${block.name}]`;
114
+ }
115
+ return "";
116
+ })
117
+ .filter(Boolean)
118
+ .join("\n");
119
+ }
120
+ function shouldCapturePrompts(metadata) {
121
+ if (metadata?.capturePrompts !== undefined) {
122
+ return metadata.capturePrompts;
123
+ }
124
+ const config = (0, config_1.getConfig)();
125
+ if (config?.capturePrompts !== undefined) {
126
+ return config.capturePrompts;
127
+ }
128
+ return constants_1.DEFAULT_CONFIG.CAPTURE_PROMPTS;
129
+ }
130
+ function extractPrompts(params, response, metadata) {
131
+ if (!shouldCapturePrompts(metadata)) {
132
+ return null;
133
+ }
134
+ const maxSize = constants_1.DEFAULT_CONFIG.MAX_PROMPT_SIZE;
135
+ let anyTruncated = false;
136
+ const systemPromptRaw = extractSystemPrompt(params);
137
+ const systemPromptResult = truncateString(systemPromptRaw, maxSize);
138
+ anyTruncated = anyTruncated || systemPromptResult.truncated;
139
+ const inputMessagesRaw = extractInputMessages(params);
140
+ const inputMessagesResult = truncateString(inputMessagesRaw, maxSize);
141
+ anyTruncated = anyTruncated || inputMessagesResult.truncated;
142
+ const outputResponseRaw = extractOutputResponse(response);
143
+ const outputResponseResult = truncateString(outputResponseRaw, maxSize);
144
+ anyTruncated = anyTruncated || outputResponseResult.truncated;
145
+ const hasAnyContent = systemPromptResult.value ||
146
+ inputMessagesResult.value ||
147
+ outputResponseResult.value;
148
+ if (!hasAnyContent) {
149
+ return null;
150
+ }
151
+ return {
152
+ systemPrompt: systemPromptResult.value || undefined,
153
+ inputMessages: inputMessagesResult.value || undefined,
154
+ outputResponse: outputResponseResult.value || undefined,
155
+ promptsTruncated: anyTruncated,
156
+ };
157
+ }
158
+ //# sourceMappingURL=prompt-extraction.js.map
@@ -6,6 +6,7 @@ exports.getCredentialAlias = getCredentialAlias;
6
6
  exports.getTraceType = getTraceType;
7
7
  exports.getTraceName = getTraceName;
8
8
  exports.detectOperationSubtype = detectOperationSubtype;
9
+ exports.detectVisionContent = detectVisionContent;
9
10
  exports.getParentTransactionId = getParentTransactionId;
10
11
  exports.getTransactionName = getTransactionName;
11
12
  exports.getRetryNumber = getRetryNumber;
@@ -100,6 +101,41 @@ function detectOperationSubtype(requestBody) {
100
101
  }
101
102
  return null;
102
103
  }
104
+ function detectVisionContent(params) {
105
+ if (!params) {
106
+ return false;
107
+ }
108
+ try {
109
+ if (params.messages && Array.isArray(params.messages)) {
110
+ for (const message of params.messages) {
111
+ if (!message || typeof message !== "object" || !message.content) {
112
+ continue;
113
+ }
114
+ if (Array.isArray(message.content)) {
115
+ for (const block of message.content) {
116
+ if (block && typeof block === "object" && block.type === "image") {
117
+ return true;
118
+ }
119
+ }
120
+ }
121
+ }
122
+ }
123
+ if (params.system && Array.isArray(params.system)) {
124
+ for (const block of params.system) {
125
+ if (block && typeof block === "object" && block.type === "image") {
126
+ return true;
127
+ }
128
+ }
129
+ }
130
+ return false;
131
+ }
132
+ catch (error) {
133
+ logger.debug("Error detecting vision content, defaulting to false", {
134
+ error: error instanceof Error ? error.message : String(error),
135
+ });
136
+ return false;
137
+ }
138
+ }
103
139
  function getParentTransactionId() {
104
140
  return process.env.REVENIUM_PARENT_TRANSACTION_ID || null;
105
141
  }