@revenium/anthropic 1.0.9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
 
@@ -289,12 +291,62 @@ The middleware automatically sends the following fields to Revenium's `/meter/v2
289
291
 
290
292
  ### Environment Variables
291
293
 
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) |
294
+ | Variable | Required | Default | Description |
295
+ | ---------------------------- | -------- | ------------------------- | ------------------------------------------ |
296
+ | `REVENIUM_METERING_API_KEY` | Yes | - | Your Revenium API key |
297
+ | `ANTHROPIC_API_KEY` | Yes | - | Anthropic Claude API key |
298
+ | `REVENIUM_METERING_BASE_URL` | No | `https://api.revenium.ai` | Revenium metering API base URL |
299
+ | `REVENIUM_DEBUG` | No | `false` | Enable debug logging (true/false) |
300
+ | `REVENIUM_CAPTURE_PROMPTS` | No | `false` | Capture prompts and responses (true/false) |
301
+
302
+ ### Prompt Capture
303
+
304
+ The middleware can capture prompts and responses for analysis and debugging. This feature is **disabled by default** for privacy and performance.
305
+
306
+ #### Configuration
307
+
308
+ Enable via environment variable:
309
+
310
+ ```bash
311
+ REVENIUM_CAPTURE_PROMPTS=true
312
+ ```
313
+
314
+ Or configure programmatically:
315
+
316
+ ```typescript
317
+ import { configure } from "@revenium/anthropic";
318
+
319
+ configure({
320
+ reveniumApiKey: "hak_your_key",
321
+ capturePrompts: true,
322
+ });
323
+ ```
324
+
325
+ #### Per-Request Control
326
+
327
+ Override the global setting for individual requests:
328
+
329
+ ```typescript
330
+ const response = await anthropic.messages.create(
331
+ {
332
+ model: "claude-3-5-sonnet-20241022",
333
+ max_tokens: 1024,
334
+ messages: [{ role: "user", content: "Hello!" }],
335
+ },
336
+ {
337
+ capturePrompts: true, // Enable for this request only
338
+ },
339
+ );
340
+ ```
341
+
342
+ #### Security
343
+
344
+ All captured prompts are automatically sanitized to remove sensitive credentials including:
345
+
346
+ - API keys (sk-\*, sk-proj-\*, sk-ant-\*)
347
+ - Bearer tokens
348
+ - Passwords
349
+ - Generic tokens and api_key fields
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
@@ -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
@@ -1,31 +1,31 @@
1
- import { validateReveniumConfig } from './utils/validation.js';
2
- import { DEFAULT_CONFIG, ENV_VARS, LOGGING_CONFIG } from './constants.js';
1
+ import { validateReveniumConfig } from "./utils/validation.js";
2
+ import { DEFAULT_CONFIG, ENV_VARS, LOGGING_CONFIG } from "./constants.js";
3
3
  /**
4
4
  * Simple console logger implementation for Anthropic middleware
5
5
  */
6
6
  class ConsoleLogger {
7
7
  isDebugEnabled() {
8
- return process.env[ENV_VARS.DEBUG] === 'true';
8
+ return process.env[ENV_VARS.DEBUG] === "true";
9
9
  }
10
10
  formatMessage(level, message, context) {
11
11
  const timestamp = new Date().toISOString();
12
- const prefix = `[${LOGGING_CONFIG.MIDDLEWARE_NAME}${level === 'DEBUG' ? ' Debug' : ''}]`;
13
- const contextStr = context ? ` ${JSON.stringify(context)}` : '';
12
+ const prefix = `[${LOGGING_CONFIG.MIDDLEWARE_NAME}${level === "DEBUG" ? " Debug" : ""}]`;
13
+ const contextStr = context ? ` ${JSON.stringify(context)}` : "";
14
14
  return `${timestamp} ${prefix} ${message}${contextStr}`;
15
15
  }
16
16
  debug(message, context) {
17
17
  if (this.isDebugEnabled()) {
18
- console.debug(this.formatMessage('DEBUG', message, context));
18
+ console.debug(this.formatMessage("DEBUG", message, context));
19
19
  }
20
20
  }
21
21
  info(message, context) {
22
- console.info(this.formatMessage('INFO', message, context));
22
+ console.info(this.formatMessage("INFO", message, context));
23
23
  }
24
24
  warn(message, context) {
25
- console.warn(this.formatMessage('WARN', message, context));
25
+ console.warn(this.formatMessage("WARN", message, context));
26
26
  }
27
27
  error(message, context) {
28
- console.error(this.formatMessage('ERROR', message, context));
28
+ console.error(this.formatMessage("ERROR", message, context));
29
29
  }
30
30
  }
31
31
  /**
@@ -40,13 +40,14 @@ function loadConfigFromEnvironment() {
40
40
  reveniumApiKey: process.env[ENV_VARS.REVENIUM_API_KEY],
41
41
  reveniumBaseUrl: process.env[ENV_VARS.REVENIUM_BASE_URL],
42
42
  anthropicApiKey: process.env[ENV_VARS.ANTHROPIC_API_KEY],
43
- debug: process.env[ENV_VARS.DEBUG] === 'true',
43
+ debug: process.env[ENV_VARS.DEBUG] === "true",
44
44
  logLevel: process.env[ENV_VARS.LOG_LEVEL],
45
45
  apiTimeout: process.env[ENV_VARS.API_TIMEOUT],
46
46
  failSilent: process.env[ENV_VARS.FAIL_SILENT],
47
47
  maxRetries: process.env[ENV_VARS.MAX_RETRIES],
48
48
  printSummary: process.env[ENV_VARS.PRINT_SUMMARY],
49
- teamId: process.env[ENV_VARS.TEAM_ID]
49
+ teamId: process.env[ENV_VARS.TEAM_ID],
50
+ capturePrompts: process.env[ENV_VARS.CAPTURE_PROMPTS],
50
51
  };
51
52
  return env;
52
53
  }
@@ -57,11 +58,11 @@ function parsePrintSummary(value) {
57
58
  if (!value)
58
59
  return undefined;
59
60
  const lowerValue = value.toLowerCase();
60
- if (lowerValue === 'true' || lowerValue === 'human')
61
- return 'human';
62
- if (lowerValue === 'json')
63
- return 'json';
64
- if (lowerValue === 'false')
61
+ if (lowerValue === "true" || lowerValue === "human")
62
+ return "human";
63
+ if (lowerValue === "json")
64
+ return "json";
65
+ if (lowerValue === "false")
65
66
  return false;
66
67
  return undefined;
67
68
  }
@@ -73,9 +74,10 @@ function createConfigFromEnvironment(env) {
73
74
  return null;
74
75
  }
75
76
  const apiTimeout = env.apiTimeout ? parseInt(env.apiTimeout, 10) : undefined;
76
- const failSilent = env.failSilent !== 'false'; // Default to true
77
+ const failSilent = env.failSilent !== "false"; // Default to true
77
78
  const maxRetries = env.maxRetries ? parseInt(env.maxRetries, 10) : undefined;
78
79
  const printSummary = parsePrintSummary(env.printSummary);
80
+ const capturePrompts = env.capturePrompts === "true";
79
81
  return {
80
82
  reveniumApiKey: env.reveniumApiKey,
81
83
  reveniumBaseUrl: env.reveniumBaseUrl || DEFAULT_CONFIG.REVENIUM_BASE_URL,
@@ -84,7 +86,8 @@ function createConfigFromEnvironment(env) {
84
86
  failSilent,
85
87
  maxRetries,
86
88
  printSummary,
87
- teamId: env.teamId?.trim()
89
+ teamId: env.teamId?.trim(),
90
+ capturePrompts,
88
91
  };
89
92
  }
90
93
  /**
@@ -94,18 +97,18 @@ export function validateConfig(config) {
94
97
  const validation = validateReveniumConfig(config);
95
98
  if (!validation.isValid) {
96
99
  // Log detailed validation errors
97
- getLogger().error('Configuration validation failed', {
100
+ getLogger().error("Configuration validation failed", {
98
101
  errors: validation.errors,
99
102
  warnings: validation.warnings,
100
- suggestions: validation.suggestions
103
+ suggestions: validation.suggestions,
101
104
  });
102
105
  // Create detailed error message
103
- let errorMessage = 'Configuration validation failed:\n';
106
+ let errorMessage = "Configuration validation failed:\n";
104
107
  validation.errors.forEach((error, index) => {
105
108
  errorMessage += ` ${index + 1}. ${error}\n`;
106
109
  });
107
110
  if (validation.suggestions && validation.suggestions.length > 0) {
108
- errorMessage += '\nSuggestions:\n';
111
+ errorMessage += "\nSuggestions:\n";
109
112
  validation.suggestions.forEach((suggestion) => {
110
113
  errorMessage += ` • ${suggestion}\n`;
111
114
  });
@@ -114,8 +117,8 @@ export function validateConfig(config) {
114
117
  }
115
118
  // Log warnings if any
116
119
  if (validation.warnings && validation.warnings.length > 0) {
117
- getLogger().warn('Configuration warnings', {
118
- warnings: validation.warnings
120
+ getLogger().warn("Configuration warnings", {
121
+ warnings: validation.warnings,
119
122
  });
120
123
  }
121
124
  }
@@ -138,18 +141,18 @@ export function setConfig(config) {
138
141
  const validation = validateReveniumConfig(config);
139
142
  if (!validation.isValid) {
140
143
  // Log detailed validation errors
141
- getLogger().error('Configuration validation failed', {
144
+ getLogger().error("Configuration validation failed", {
142
145
  errors: validation.errors,
143
146
  warnings: validation.warnings,
144
- suggestions: validation.suggestions
147
+ suggestions: validation.suggestions,
145
148
  });
146
149
  // Create detailed error message
147
- let errorMessage = 'Configuration validation failed:\n';
150
+ let errorMessage = "Configuration validation failed:\n";
148
151
  validation.errors.forEach((error, index) => {
149
152
  errorMessage += ` ${index + 1}. ${error}\n`;
150
153
  });
151
154
  if (validation.suggestions && validation.suggestions.length > 0) {
152
- errorMessage += '\nSuggestions:\n';
155
+ errorMessage += "\nSuggestions:\n";
153
156
  validation.suggestions.forEach((suggestion) => {
154
157
  errorMessage += ` • ${suggestion}\n`;
155
158
  });
@@ -158,16 +161,16 @@ export function setConfig(config) {
158
161
  }
159
162
  // Log warnings if any
160
163
  if (validation.warnings && validation.warnings.length > 0) {
161
- getLogger().warn('Configuration warnings', {
162
- warnings: validation.warnings
164
+ getLogger().warn("Configuration warnings", {
165
+ warnings: validation.warnings,
163
166
  });
164
167
  }
165
168
  // Use the normalized config from validation (with defaults applied and fields trimmed)
166
169
  globalConfig = validation.config;
167
- globalLogger.debug('Revenium configuration updated', {
170
+ globalLogger.debug("Revenium configuration updated", {
168
171
  baseUrl: globalConfig.reveniumBaseUrl,
169
172
  hasApiKey: !!globalConfig.reveniumApiKey,
170
- hasAnthropicKey: !!globalConfig.anthropicApiKey
173
+ hasAnthropicKey: !!globalConfig.anthropicApiKey,
171
174
  });
172
175
  }
173
176
  /**
@@ -181,7 +184,7 @@ export function getLogger() {
181
184
  */
182
185
  export function setLogger(logger) {
183
186
  globalLogger = logger;
184
- globalLogger.debug('Custom logger set for Revenium middleware');
187
+ globalLogger.debug("Custom logger set for Revenium middleware");
185
188
  }
186
189
  /**
187
190
  * Initialize configuration from environment variables
@@ -192,12 +195,12 @@ export function initializeConfig() {
192
195
  if (config) {
193
196
  try {
194
197
  setConfig(config);
195
- globalLogger.debug('Revenium middleware initialized from environment variables');
198
+ globalLogger.debug("Revenium middleware initialized from environment variables");
196
199
  return true;
197
200
  }
198
201
  catch (error) {
199
- globalLogger.error('Failed to initialize Revenium configuration', {
200
- error: error instanceof Error ? error.message : String(error)
202
+ globalLogger.error("Failed to initialize Revenium configuration", {
203
+ error: error instanceof Error ? error.message : String(error),
201
204
  });
202
205
  return false;
203
206
  }
@@ -218,14 +221,14 @@ export function getConfigStatus() {
218
221
  hasConfig: false,
219
222
  hasApiKey: false,
220
223
  hasAnthropicKey: false,
221
- baseUrl: ''
224
+ baseUrl: "",
222
225
  };
223
226
  }
224
227
  return {
225
228
  hasConfig: true,
226
229
  hasApiKey: !!globalConfig.reveniumApiKey,
227
230
  hasAnthropicKey: !!globalConfig.anthropicApiKey,
228
- baseUrl: globalConfig.reveniumBaseUrl
231
+ baseUrl: globalConfig.reveniumBaseUrl,
229
232
  };
230
233
  }
231
234
  /**
@@ -22,6 +22,10 @@ export const DEFAULT_CONFIG = {
22
22
  MAX_RETRY_ATTEMPTS: 10,
23
23
  /** Warning threshold for low API timeout */
24
24
  LOW_TIMEOUT_WARNING_THRESHOLD: 3000,
25
+ /** Default prompt capture behavior */
26
+ CAPTURE_PROMPTS: false,
27
+ /** Maximum size for each prompt field in characters (50KB) */
28
+ MAX_PROMPT_SIZE: 50000,
25
29
  };
26
30
  /**
27
31
  * Circuit breaker configuration constants
@@ -113,6 +117,8 @@ export const ENV_VARS = {
113
117
  PRINT_SUMMARY: "REVENIUM_PRINT_SUMMARY",
114
118
  /** Team ID for cost metrics retrieval */
115
119
  TEAM_ID: "REVENIUM_TEAM_ID",
120
+ /** Prompt capture mode */
121
+ CAPTURE_PROMPTS: "REVENIUM_CAPTURE_PROMPTS",
116
122
  };
117
123
  /**
118
124
  * Summary printer configuration
@@ -0,0 +1,154 @@
1
+ import { DEFAULT_CONFIG } from "../constants.js";
2
+ import { getConfig } from "../config.js";
3
+ function sanitizeCredentials(text) {
4
+ const patterns = [
5
+ {
6
+ regex: /sk-proj-[a-zA-Z0-9_-]{48,}/g,
7
+ replacement: "sk-proj-***REDACTED***",
8
+ },
9
+ { regex: /sk-[a-zA-Z0-9_-]{20,}/g, replacement: "sk-***REDACTED***" },
10
+ {
11
+ regex: /Bearer\s+[a-zA-Z0-9_\-\.]+/gi,
12
+ replacement: "Bearer ***REDACTED***",
13
+ },
14
+ {
15
+ regex: /api[_-]?key["\s:=]+[a-zA-Z0-9_\-\.\+\/=]{20,}/gi,
16
+ replacement: "api_key: ***REDACTED***",
17
+ },
18
+ {
19
+ regex: /token["\s:=]+[a-zA-Z0-9_\-\.]{20,}/gi,
20
+ replacement: "token: ***REDACTED***",
21
+ },
22
+ {
23
+ regex: /password["\s:=]+[^\s"']{8,}/gi,
24
+ replacement: "password: ***REDACTED***",
25
+ },
26
+ ];
27
+ let sanitized = text;
28
+ for (const pattern of patterns) {
29
+ sanitized = sanitized.replace(pattern.regex, pattern.replacement);
30
+ }
31
+ return sanitized;
32
+ }
33
+ function truncateString(str, maxLength) {
34
+ const sanitized = sanitizeCredentials(str);
35
+ if (sanitized.length <= maxLength) {
36
+ return { value: sanitized, truncated: false };
37
+ }
38
+ return { value: sanitized.substring(0, maxLength), truncated: true };
39
+ }
40
+ function extractSystemPrompt(params) {
41
+ if (!params.system) {
42
+ return "";
43
+ }
44
+ if (typeof params.system === "string") {
45
+ return params.system;
46
+ }
47
+ if (Array.isArray(params.system)) {
48
+ return params.system
49
+ .map((block) => {
50
+ if (block.type === "text") {
51
+ return block.text;
52
+ }
53
+ if (block.type === "image") {
54
+ return "[IMAGE]";
55
+ }
56
+ return "";
57
+ })
58
+ .filter(Boolean)
59
+ .join("\n");
60
+ }
61
+ return "";
62
+ }
63
+ function extractInputMessages(params) {
64
+ if (!params.messages || params.messages.length === 0) {
65
+ return "";
66
+ }
67
+ return params.messages
68
+ .map((message) => {
69
+ const role = message.role;
70
+ let content = "";
71
+ if (typeof message.content === "string") {
72
+ content = message.content;
73
+ }
74
+ else if (Array.isArray(message.content)) {
75
+ content = message.content
76
+ .map((block) => {
77
+ if (block.type === "text") {
78
+ return block.text;
79
+ }
80
+ if (block.type === "image") {
81
+ return "[IMAGE]";
82
+ }
83
+ if (block.type === "tool_use") {
84
+ const toolName = block.name || "unknown";
85
+ return `[TOOL_USE: ${toolName}]`;
86
+ }
87
+ if (block.type === "tool_result") {
88
+ return "[TOOL_RESULT]";
89
+ }
90
+ return "";
91
+ })
92
+ .filter(Boolean)
93
+ .join("\n");
94
+ }
95
+ return `[${role}]\n${content}`;
96
+ })
97
+ .join("\n\n");
98
+ }
99
+ function extractOutputResponse(response) {
100
+ if (!response.content || response.content.length === 0) {
101
+ return "";
102
+ }
103
+ return response.content
104
+ .map((block) => {
105
+ if (block.type === "text") {
106
+ return block.text;
107
+ }
108
+ if (block.type === "tool_use") {
109
+ return `[TOOL_USE: ${block.name}]`;
110
+ }
111
+ return "";
112
+ })
113
+ .filter(Boolean)
114
+ .join("\n");
115
+ }
116
+ export function shouldCapturePrompts(metadata) {
117
+ if (metadata?.capturePrompts !== undefined) {
118
+ return metadata.capturePrompts;
119
+ }
120
+ const config = getConfig();
121
+ if (config?.capturePrompts !== undefined) {
122
+ return config.capturePrompts;
123
+ }
124
+ return DEFAULT_CONFIG.CAPTURE_PROMPTS;
125
+ }
126
+ export function extractPrompts(params, response, metadata) {
127
+ if (!shouldCapturePrompts(metadata)) {
128
+ return null;
129
+ }
130
+ const maxSize = DEFAULT_CONFIG.MAX_PROMPT_SIZE;
131
+ let anyTruncated = false;
132
+ const systemPromptRaw = extractSystemPrompt(params);
133
+ const systemPromptResult = truncateString(systemPromptRaw, maxSize);
134
+ anyTruncated = anyTruncated || systemPromptResult.truncated;
135
+ const inputMessagesRaw = extractInputMessages(params);
136
+ const inputMessagesResult = truncateString(inputMessagesRaw, maxSize);
137
+ anyTruncated = anyTruncated || inputMessagesResult.truncated;
138
+ const outputResponseRaw = extractOutputResponse(response);
139
+ const outputResponseResult = truncateString(outputResponseRaw, maxSize);
140
+ anyTruncated = anyTruncated || outputResponseResult.truncated;
141
+ const hasAnyContent = systemPromptResult.value ||
142
+ inputMessagesResult.value ||
143
+ outputResponseResult.value;
144
+ if (!hasAnyContent) {
145
+ return null;
146
+ }
147
+ return {
148
+ systemPrompt: systemPromptResult.value || undefined,
149
+ inputMessages: inputMessagesResult.value || undefined,
150
+ outputResponse: outputResponseResult.value || undefined,
151
+ promptsTruncated: anyTruncated,
152
+ };
153
+ }
154
+ //# sourceMappingURL=prompt-extraction.js.map
@@ -1,4 +1,4 @@
1
- import { ReveniumConfig, Logger } from './types';
1
+ import { ReveniumConfig, Logger } from "./types";
2
2
  /**
3
3
  * Default console logger implementation
4
4
  */
@@ -22,6 +22,10 @@ export declare const DEFAULT_CONFIG: {
22
22
  readonly MAX_RETRY_ATTEMPTS: 10;
23
23
  /** Warning threshold for low API timeout */
24
24
  readonly LOW_TIMEOUT_WARNING_THRESHOLD: 3000;
25
+ /** Default prompt capture behavior */
26
+ readonly CAPTURE_PROMPTS: false;
27
+ /** Maximum size for each prompt field in characters (50KB) */
28
+ readonly MAX_PROMPT_SIZE: 50000;
25
29
  };
26
30
  /**
27
31
  * Circuit breaker configuration constants
@@ -113,6 +117,8 @@ export declare const ENV_VARS: {
113
117
  readonly PRINT_SUMMARY: "REVENIUM_PRINT_SUMMARY";
114
118
  /** Team ID for cost metrics retrieval */
115
119
  readonly TEAM_ID: "REVENIUM_TEAM_ID";
120
+ /** Prompt capture mode */
121
+ readonly CAPTURE_PROMPTS: "REVENIUM_CAPTURE_PROMPTS";
116
122
  };
117
123
  /**
118
124
  * Summary printer configuration
@@ -47,6 +47,8 @@ export interface ReveniumConfig {
47
47
  printSummary?: boolean | SummaryFormat;
48
48
  /** Revenium team ID for fetching cost metrics from the API. If not provided, the summary will still be printed but without cost information. */
49
49
  teamId?: string;
50
+ /** Whether to capture prompts and responses for analysis (default: false) */
51
+ capturePrompts?: boolean;
50
52
  }
51
53
  /**
52
54
  * Usage metadata for enhanced tracking and analytics
@@ -68,6 +70,8 @@ export interface UsageMetadata {
68
70
  agent?: string;
69
71
  /** Quality score of AI response (0.0-1.0) for performance tracking */
70
72
  responseQualityScore?: number;
73
+ /** Whether to capture prompts and responses for this request (overrides global config) */
74
+ capturePrompts?: boolean;
71
75
  /** Allow additional custom fields for extensibility */
72
76
  [key: string]: unknown;
73
77
  }
@@ -328,8 +332,14 @@ export interface AnthropicMessageParams {
328
332
  stream?: boolean;
329
333
  /** Sequences that will stop generation */
330
334
  stop_sequences?: string[];
331
- /** System message to set context */
332
- system?: string;
335
+ /** System message to set context - can be string or array of content blocks */
336
+ system?: string | Array<{
337
+ type: "text";
338
+ text: string;
339
+ } | {
340
+ type: "image";
341
+ source: unknown;
342
+ }>;
333
343
  /** Available tools for function calling */
334
344
  tools?: AnthropicTool[];
335
345
  /** Tool usage configuration */
@@ -469,6 +479,7 @@ export interface EnvironmentConfig {
469
479
  maxRetries?: string;
470
480
  printSummary?: string;
471
481
  teamId?: string;
482
+ capturePrompts?: string;
472
483
  }
473
484
  /**
474
485
  * Summary output format options
@@ -0,0 +1,10 @@
1
+ import { AnthropicMessageParams, AnthropicResponse, UsageMetadata } from "../types";
2
+ export interface PromptData {
3
+ systemPrompt?: string;
4
+ inputMessages?: string;
5
+ outputResponse?: string;
6
+ promptsTruncated: boolean;
7
+ }
8
+ export declare function shouldCapturePrompts(metadata?: UsageMetadata): boolean;
9
+ export declare function extractPrompts(params: AnthropicMessageParams, response: AnthropicResponse, metadata?: UsageMetadata): PromptData | null;
10
+ //# sourceMappingURL=prompt-extraction.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenium/anthropic",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "description": "Transparent TypeScript middleware for automatic Revenium usage tracking with Anthropic Claude AI",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",