@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 +12 -0
- package/README.md +62 -10
- package/dist/cjs/config.js +39 -36
- package/dist/cjs/constants.js +6 -0
- package/dist/cjs/tracking.js +21 -3
- package/dist/cjs/utils/prompt-extraction.js +158 -0
- package/dist/cjs/utils/trace-fields.js +36 -0
- package/dist/cjs/utils/validation.js +105 -69
- package/dist/cjs/wrapper.js +79 -0
- package/dist/esm/config.js +41 -38
- package/dist/esm/constants.js +6 -0
- package/dist/esm/tracking.js +21 -3
- package/dist/esm/utils/prompt-extraction.js +154 -0
- package/dist/esm/utils/trace-fields.js +35 -0
- package/dist/esm/utils/validation.js +106 -70
- package/dist/esm/wrapper.js +79 -0
- package/dist/types/config.d.ts +1 -1
- package/dist/types/constants.d.ts +6 -0
- package/dist/types/types.d.ts +38 -8
- package/dist/types/utils/prompt-extraction.d.ts +10 -0
- package/dist/types/utils/trace-fields.d.ts +1 -0
- package/dist/types/utils/validation.d.ts +1 -1
- package/examples/advanced.ts +4 -4
- package/examples/getting_started.ts +2 -2
- package/examples/metadata.ts +3 -3
- package/examples/prompt-capture-example.ts +105 -0
- package/package.json +1 -1
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
|
-
| `
|
|
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
|
-
| `
|
|
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
|
-
| `
|
|
266
|
-
| `
|
|
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
|
|
package/dist/cjs/config.js
CHANGED
|
@@ -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] ===
|
|
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 ===
|
|
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(
|
|
29
|
+
console.debug(this.formatMessage("DEBUG", message, context));
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
info(message, context) {
|
|
33
|
-
console.info(this.formatMessage(
|
|
33
|
+
console.info(this.formatMessage("INFO", message, context));
|
|
34
34
|
}
|
|
35
35
|
warn(message, context) {
|
|
36
|
-
console.warn(this.formatMessage(
|
|
36
|
+
console.warn(this.formatMessage("WARN", message, context));
|
|
37
37
|
}
|
|
38
38
|
error(message, context) {
|
|
39
|
-
console.error(this.formatMessage(
|
|
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] ===
|
|
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 ===
|
|
72
|
-
return
|
|
73
|
-
if (lowerValue ===
|
|
74
|
-
return
|
|
75
|
-
if (lowerValue ===
|
|
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 !==
|
|
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(
|
|
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 =
|
|
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 +=
|
|
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(
|
|
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(
|
|
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 =
|
|
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 +=
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
209
|
+
globalLogger.debug("Revenium middleware initialized from environment variables");
|
|
207
210
|
return true;
|
|
208
211
|
}
|
|
209
212
|
catch (error) {
|
|
210
|
-
globalLogger.error(
|
|
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
|
/**
|
package/dist/cjs/constants.js
CHANGED
|
@@ -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
|
package/dist/cjs/tracking.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|