@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.
@@ -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
@@ -8,6 +8,7 @@ import { withRetry, ReveniumApiError, createErrorContext, handleError, } from ".
8
8
  import { DEFAULT_CONFIG, API_ENDPOINTS, LOGGING_CONFIG, ANTHROPIC_PATTERNS, } from "./constants.js";
9
9
  import { getEnvironment, getRegion, getCredentialAlias, getTraceType, getTraceName, detectOperationSubtype, getParentTransactionId, getTransactionName, getRetryNumber, } from "./utils/trace-fields.js";
10
10
  import { printUsageSummary } from "./utils/summary-printer.js";
11
+ import { extractPrompts } from "./utils/prompt-extraction.js";
11
12
  // Global logger
12
13
  const logger = getLogger();
13
14
  if (typeof process !== "undefined") {
@@ -143,7 +144,9 @@ async function buildReveniumPayload(data) {
143
144
  "taskType",
144
145
  "agent",
145
146
  "organizationId",
147
+ "organizationName",
146
148
  "productId",
149
+ "productName",
147
150
  "subscriber",
148
151
  "subscriptionId",
149
152
  "traceId",
@@ -167,6 +170,10 @@ async function buildReveniumPayload(data) {
167
170
  const transactionName = getTransactionName();
168
171
  const retryNumber = getRetryNumber();
169
172
  const operationSubtype = detectOperationSubtype(data.requestBody);
173
+ let promptData = null;
174
+ if (data.requestBody && data.response) {
175
+ promptData = extractPrompts(data.requestBody, data.response, data.metadata);
176
+ }
170
177
  return {
171
178
  stopReason: getStopReason(data.stopReason),
172
179
  costType: "AI",
@@ -189,10 +196,12 @@ async function buildReveniumPayload(data) {
189
196
  middlewareSource: "nodejs",
190
197
  ...(data.metadata?.taskType && { taskType: data.metadata.taskType }),
191
198
  ...(data.metadata?.agent && { agent: data.metadata.agent }),
192
- ...(data.metadata?.organizationId && {
193
- organizationId: data.metadata.organizationId,
199
+ ...((data.metadata?.organizationName || data.metadata?.organizationId) && {
200
+ organizationName: data.metadata.organizationName || data.metadata.organizationId,
201
+ }),
202
+ ...((data.metadata?.productName || data.metadata?.productId) && {
203
+ productName: data.metadata.productName || data.metadata.productId,
194
204
  }),
195
- ...(data.metadata?.productId && { productId: data.metadata.productId }),
196
205
  ...(data.metadata?.subscriber && { subscriber: data.metadata.subscriber }),
197
206
  ...(data.metadata?.subscriptionId && {
198
207
  subscriptionId: data.metadata.subscriptionId,
@@ -210,6 +219,15 @@ async function buildReveniumPayload(data) {
210
219
  ...(transactionName && { transactionName }),
211
220
  ...(retryNumber !== undefined && { retryNumber }),
212
221
  ...(operationSubtype && { operationSubtype }),
222
+ ...(data.hasVisionContent !== undefined && {
223
+ hasVisionContent: data.hasVisionContent,
224
+ }),
225
+ ...(promptData && {
226
+ systemPrompt: promptData.systemPrompt,
227
+ inputMessages: promptData.inputMessages,
228
+ outputResponse: promptData.outputResponse,
229
+ promptsTruncated: promptData.promptsTruncated,
230
+ }),
213
231
  ...customFields,
214
232
  };
215
233
  }
@@ -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
@@ -89,6 +89,41 @@ export function detectOperationSubtype(requestBody) {
89
89
  }
90
90
  return null;
91
91
  }
92
+ export function detectVisionContent(params) {
93
+ if (!params) {
94
+ return false;
95
+ }
96
+ try {
97
+ if (params.messages && Array.isArray(params.messages)) {
98
+ for (const message of params.messages) {
99
+ if (!message || typeof message !== "object" || !message.content) {
100
+ continue;
101
+ }
102
+ if (Array.isArray(message.content)) {
103
+ for (const block of message.content) {
104
+ if (block && typeof block === "object" && block.type === "image") {
105
+ return true;
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ if (params.system && Array.isArray(params.system)) {
112
+ for (const block of params.system) {
113
+ if (block && typeof block === "object" && block.type === "image") {
114
+ return true;
115
+ }
116
+ }
117
+ }
118
+ return false;
119
+ }
120
+ catch (error) {
121
+ logger.debug("Error detecting vision content, defaulting to false", {
122
+ error: error instanceof Error ? error.message : String(error),
123
+ });
124
+ return false;
125
+ }
126
+ }
92
127
  export function getParentTransactionId() {
93
128
  return process.env.REVENIUM_PARENT_TRANSACTION_ID || null;
94
129
  }