@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
|
@@ -2,35 +2,35 @@
|
|
|
2
2
|
* Validation utilities for Anthropic middleware
|
|
3
3
|
* Provides type-safe validation with detailed error reporting
|
|
4
4
|
*/
|
|
5
|
-
import { VALIDATION_CONFIG, ANTHROPIC_PATTERNS, DEFAULT_CONFIG } from
|
|
5
|
+
import { VALIDATION_CONFIG, ANTHROPIC_PATTERNS, DEFAULT_CONFIG, } from "../constants.js";
|
|
6
6
|
/**
|
|
7
7
|
* Type guard for checking if a value is a non-null object
|
|
8
8
|
*/
|
|
9
9
|
export function isObject(value) {
|
|
10
|
-
return typeof value ===
|
|
10
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
13
|
* Type guard for checking if a value is a string
|
|
14
14
|
*/
|
|
15
15
|
export function isString(value) {
|
|
16
|
-
return typeof value ===
|
|
16
|
+
return typeof value === "string";
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
19
|
* Type guard for checking if a value is a number
|
|
20
20
|
*/
|
|
21
21
|
export function isNumber(value) {
|
|
22
|
-
return typeof value ===
|
|
22
|
+
return typeof value === "number" && !isNaN(value);
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
25
|
* Type guard for checking if a value is a boolean
|
|
26
26
|
*/
|
|
27
27
|
export function isBoolean(value) {
|
|
28
|
-
return typeof value ===
|
|
28
|
+
return typeof value === "boolean";
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
31
|
* Validate and extract string from unknown value
|
|
32
32
|
*/
|
|
33
|
-
export function validateString(value, defaultValue =
|
|
33
|
+
export function validateString(value, defaultValue = "") {
|
|
34
34
|
return isString(value) ? value : defaultValue;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
@@ -49,8 +49,14 @@ export function validateUsageMetadata(metadata) {
|
|
|
49
49
|
const validated = {};
|
|
50
50
|
// Validate string fields
|
|
51
51
|
const stringFields = [
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
"traceId",
|
|
53
|
+
"taskType",
|
|
54
|
+
"organizationId",
|
|
55
|
+
"organizationName",
|
|
56
|
+
"subscriptionId",
|
|
57
|
+
"productId",
|
|
58
|
+
"productName",
|
|
59
|
+
"agent",
|
|
54
60
|
];
|
|
55
61
|
for (const field of stringFields) {
|
|
56
62
|
const value = metadata[field];
|
|
@@ -63,21 +69,25 @@ export function validateUsageMetadata(metadata) {
|
|
|
63
69
|
if (isObject(subscriberData)) {
|
|
64
70
|
const subscriber = {};
|
|
65
71
|
// Validate subscriber.id
|
|
66
|
-
if (isString(subscriberData?.id) &&
|
|
72
|
+
if (isString(subscriberData?.id) &&
|
|
73
|
+
subscriberData?.id?.trim()?.length > 0) {
|
|
67
74
|
subscriber.id = subscriberData?.id?.trim();
|
|
68
75
|
}
|
|
69
76
|
// Validate subscriber.email
|
|
70
|
-
if (isString(subscriberData?.email) &&
|
|
77
|
+
if (isString(subscriberData?.email) &&
|
|
78
|
+
subscriberData?.email?.trim()?.length > 0) {
|
|
71
79
|
subscriber.email = subscriberData?.email?.trim();
|
|
72
80
|
}
|
|
73
81
|
// Validate subscriber.credential object
|
|
74
82
|
const credentialData = subscriberData?.credential;
|
|
75
83
|
if (isObject(credentialData)) {
|
|
76
84
|
const credential = {};
|
|
77
|
-
if (isString(credentialData?.name) &&
|
|
85
|
+
if (isString(credentialData?.name) &&
|
|
86
|
+
credentialData?.name?.trim()?.length > 0) {
|
|
78
87
|
credential.name = credentialData?.name?.trim();
|
|
79
88
|
}
|
|
80
|
-
if (isString(credentialData?.value) &&
|
|
89
|
+
if (isString(credentialData?.value) &&
|
|
90
|
+
credentialData?.value?.trim()?.length > 0) {
|
|
81
91
|
credential.value = credentialData?.value?.trim();
|
|
82
92
|
}
|
|
83
93
|
// Only include credential if it has at least name or value
|
|
@@ -91,10 +101,18 @@ export function validateUsageMetadata(metadata) {
|
|
|
91
101
|
}
|
|
92
102
|
}
|
|
93
103
|
// Validate number fields
|
|
94
|
-
const responseQualityScore = metadata
|
|
95
|
-
|
|
104
|
+
const responseQualityScore = metadata
|
|
105
|
+
.responseQualityScore;
|
|
106
|
+
if (isNumber(responseQualityScore) &&
|
|
107
|
+
responseQualityScore >= 0 &&
|
|
108
|
+
responseQualityScore <= 1) {
|
|
96
109
|
validated.responseQualityScore = responseQualityScore;
|
|
97
110
|
}
|
|
111
|
+
// Validate boolean fields
|
|
112
|
+
const capturePrompts = metadata.capturePrompts;
|
|
113
|
+
if (typeof capturePrompts === "boolean") {
|
|
114
|
+
validated.capturePrompts = capturePrompts;
|
|
115
|
+
}
|
|
98
116
|
return validated;
|
|
99
117
|
}
|
|
100
118
|
/**
|
|
@@ -107,96 +125,104 @@ export function validateReveniumConfig(config) {
|
|
|
107
125
|
if (!isObject(config)) {
|
|
108
126
|
return {
|
|
109
127
|
isValid: false,
|
|
110
|
-
errors: [
|
|
128
|
+
errors: ["Configuration must be an object"],
|
|
111
129
|
warnings: [],
|
|
112
|
-
suggestions: [
|
|
130
|
+
suggestions: [
|
|
131
|
+
"Ensure you are passing a valid configuration object with required fields",
|
|
132
|
+
],
|
|
113
133
|
};
|
|
114
134
|
}
|
|
115
135
|
const cfg = config;
|
|
116
136
|
// Validate required Revenium API key
|
|
117
137
|
if (!isString(cfg?.reveniumApiKey)) {
|
|
118
|
-
errors.push(
|
|
119
|
-
suggestions.push(
|
|
138
|
+
errors.push("reveniumApiKey is required and must be a string");
|
|
139
|
+
suggestions.push("Set REVENIUM_METERING_API_KEY environment variable or provide reveniumApiKey in config");
|
|
120
140
|
}
|
|
121
141
|
else if (!cfg?.reveniumApiKey?.trim()) {
|
|
122
|
-
errors.push(
|
|
142
|
+
errors.push("reveniumApiKey cannot be empty");
|
|
123
143
|
}
|
|
124
144
|
else if (!cfg?.reveniumApiKey?.startsWith(VALIDATION_CONFIG.REVENIUM_API_KEY_PREFIX)) {
|
|
125
145
|
errors.push(`reveniumApiKey must start with "${VALIDATION_CONFIG.REVENIUM_API_KEY_PREFIX}"`);
|
|
126
|
-
suggestions.push(
|
|
146
|
+
suggestions.push("Obtain a valid Revenium API key from your Revenium dashboard");
|
|
127
147
|
}
|
|
128
148
|
else if (cfg?.reveniumApiKey?.length < VALIDATION_CONFIG.MIN_API_KEY_LENGTH) {
|
|
129
|
-
warnings.push(
|
|
149
|
+
warnings.push("reveniumApiKey appears to be too short - verify it is correct");
|
|
130
150
|
}
|
|
131
151
|
// Validate Revenium base URL (optional - defaults to https://api.revenium.ai)
|
|
132
152
|
if (cfg?.reveniumBaseUrl !== undefined) {
|
|
133
153
|
if (!isString(cfg?.reveniumBaseUrl)) {
|
|
134
|
-
errors.push(
|
|
154
|
+
errors.push("reveniumBaseUrl must be a string if provided");
|
|
135
155
|
}
|
|
136
156
|
else if (!cfg?.reveniumBaseUrl?.trim()) {
|
|
137
|
-
errors.push(
|
|
157
|
+
errors.push("reveniumBaseUrl cannot be empty if provided");
|
|
138
158
|
}
|
|
139
159
|
else {
|
|
140
160
|
try {
|
|
141
161
|
const url = new URL(cfg?.reveniumBaseUrl);
|
|
142
|
-
if (!url.protocol.startsWith(
|
|
143
|
-
errors.push(
|
|
162
|
+
if (!url.protocol.startsWith("http")) {
|
|
163
|
+
errors.push("reveniumBaseUrl must use HTTP or HTTPS protocol");
|
|
144
164
|
}
|
|
145
165
|
// Check for localhost/development hostnames (IPv4, IPv6, and named)
|
|
146
|
-
const localhostHostnames = [
|
|
166
|
+
const localhostHostnames = ["localhost", "127.0.0.1", "::1", "[::1]"];
|
|
147
167
|
if (localhostHostnames.includes(url.hostname)) {
|
|
148
|
-
warnings.push(
|
|
168
|
+
warnings.push("Using localhost for Revenium API - ensure this is intended for development");
|
|
149
169
|
}
|
|
150
170
|
}
|
|
151
171
|
catch {
|
|
152
|
-
errors.push(
|
|
153
|
-
suggestions.push(
|
|
172
|
+
errors.push("reveniumBaseUrl must be a valid URL");
|
|
173
|
+
suggestions.push("Use format: https://api.revenium.ai");
|
|
154
174
|
}
|
|
155
175
|
}
|
|
156
176
|
}
|
|
157
177
|
// Validate optional Anthropic API key
|
|
158
178
|
if (cfg?.anthropicApiKey !== undefined && !isString(cfg?.anthropicApiKey)) {
|
|
159
|
-
errors.push(
|
|
179
|
+
errors.push("anthropicApiKey must be a string if provided");
|
|
160
180
|
}
|
|
161
|
-
else if (cfg?.anthropicApiKey !== undefined &&
|
|
162
|
-
|
|
181
|
+
else if (cfg?.anthropicApiKey !== undefined &&
|
|
182
|
+
cfg?.anthropicApiKey?.trim()?.length === 0) {
|
|
183
|
+
warnings.push("anthropicApiKey is empty - API calls may fail");
|
|
163
184
|
}
|
|
164
|
-
else if (cfg?.anthropicApiKey &&
|
|
185
|
+
else if (cfg?.anthropicApiKey &&
|
|
186
|
+
!cfg?.anthropicApiKey?.startsWith(VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX)) {
|
|
165
187
|
warnings.push(`anthropicApiKey does not start with "${VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX}" - verify it is correct`);
|
|
166
188
|
}
|
|
167
189
|
// Validate optional timeout using constants
|
|
168
190
|
if (cfg?.apiTimeout !== undefined && !isNumber(cfg?.apiTimeout)) {
|
|
169
|
-
errors.push(
|
|
191
|
+
errors.push("apiTimeout must be a number if provided");
|
|
170
192
|
}
|
|
171
|
-
else if (cfg?.apiTimeout !== undefined &&
|
|
193
|
+
else if (cfg?.apiTimeout !== undefined &&
|
|
194
|
+
cfg?.apiTimeout < VALIDATION_CONFIG.MIN_API_TIMEOUT) {
|
|
172
195
|
errors.push(`apiTimeout must be at least ${VALIDATION_CONFIG.MIN_API_TIMEOUT}ms`);
|
|
173
196
|
}
|
|
174
|
-
else if (cfg?.apiTimeout !== undefined &&
|
|
197
|
+
else if (cfg?.apiTimeout !== undefined &&
|
|
198
|
+
cfg?.apiTimeout > VALIDATION_CONFIG.MAX_API_TIMEOUT) {
|
|
175
199
|
errors.push(`apiTimeout must not exceed ${VALIDATION_CONFIG.MAX_API_TIMEOUT}ms`);
|
|
176
200
|
}
|
|
177
|
-
else if (cfg?.apiTimeout !== undefined &&
|
|
178
|
-
|
|
201
|
+
else if (cfg?.apiTimeout !== undefined &&
|
|
202
|
+
cfg?.apiTimeout < VALIDATION_CONFIG.LOW_TIMEOUT_WARNING_THRESHOLD) {
|
|
203
|
+
warnings.push("apiTimeout is very low - may cause timeouts for slow networks");
|
|
179
204
|
}
|
|
180
205
|
// Validate optional failSilent
|
|
181
206
|
if (cfg?.failSilent && !isBoolean(cfg?.failSilent)) {
|
|
182
|
-
errors.push(
|
|
207
|
+
errors.push("failSilent must be a boolean if provided");
|
|
183
208
|
}
|
|
184
209
|
// Validate optional maxRetries using constants
|
|
185
210
|
if (cfg?.maxRetries !== undefined && !isNumber(cfg?.maxRetries)) {
|
|
186
|
-
errors.push(
|
|
211
|
+
errors.push("maxRetries must be a number if provided");
|
|
187
212
|
}
|
|
188
213
|
else if (cfg?.maxRetries !== undefined && cfg?.maxRetries < 0) {
|
|
189
|
-
errors.push(
|
|
214
|
+
errors.push("maxRetries cannot be negative");
|
|
190
215
|
}
|
|
191
|
-
else if (cfg?.maxRetries !== undefined &&
|
|
216
|
+
else if (cfg?.maxRetries !== undefined &&
|
|
217
|
+
cfg?.maxRetries > VALIDATION_CONFIG.MAX_RETRY_ATTEMPTS) {
|
|
192
218
|
errors.push(`maxRetries should not exceed ${VALIDATION_CONFIG.MAX_RETRY_ATTEMPTS}`);
|
|
193
219
|
}
|
|
194
220
|
else if (cfg?.maxRetries !== undefined && cfg?.maxRetries === 0) {
|
|
195
|
-
warnings.push(
|
|
221
|
+
warnings.push("maxRetries is 0 - no retry attempts will be made");
|
|
196
222
|
}
|
|
197
223
|
// Validate optional printSummary
|
|
198
224
|
if (cfg?.printSummary !== undefined) {
|
|
199
|
-
const validPrintSummaryValues = [true, false,
|
|
225
|
+
const validPrintSummaryValues = [true, false, "human", "json"];
|
|
200
226
|
if (!validPrintSummaryValues.includes(cfg?.printSummary)) {
|
|
201
227
|
errors.push("printSummary must be a boolean or one of: 'human', 'json'");
|
|
202
228
|
suggestions.push("Use printSummary: true, 'human', or 'json' to enable summary output");
|
|
@@ -205,10 +231,10 @@ export function validateReveniumConfig(config) {
|
|
|
205
231
|
// Validate optional teamId
|
|
206
232
|
if (cfg?.teamId !== undefined) {
|
|
207
233
|
if (!isString(cfg?.teamId)) {
|
|
208
|
-
errors.push(
|
|
234
|
+
errors.push("teamId must be a string if provided");
|
|
209
235
|
}
|
|
210
236
|
else if (cfg?.teamId?.trim()?.length === 0) {
|
|
211
|
-
errors.push(
|
|
237
|
+
errors.push("teamId cannot be empty if provided");
|
|
212
238
|
}
|
|
213
239
|
}
|
|
214
240
|
if (errors.length > 0) {
|
|
@@ -216,16 +242,16 @@ export function validateReveniumConfig(config) {
|
|
|
216
242
|
isValid: false,
|
|
217
243
|
errors,
|
|
218
244
|
warnings,
|
|
219
|
-
suggestions
|
|
245
|
+
suggestions,
|
|
220
246
|
};
|
|
221
247
|
}
|
|
222
248
|
// Determine validated printSummary value
|
|
223
249
|
let validatedPrintSummary;
|
|
224
|
-
if (cfg?.printSummary === true || cfg?.printSummary ===
|
|
225
|
-
validatedPrintSummary =
|
|
250
|
+
if (cfg?.printSummary === true || cfg?.printSummary === "human") {
|
|
251
|
+
validatedPrintSummary = "human";
|
|
226
252
|
}
|
|
227
|
-
else if (cfg?.printSummary ===
|
|
228
|
-
validatedPrintSummary =
|
|
253
|
+
else if (cfg?.printSummary === "json") {
|
|
254
|
+
validatedPrintSummary = "json";
|
|
229
255
|
}
|
|
230
256
|
else if (cfg?.printSummary === false) {
|
|
231
257
|
validatedPrintSummary = false;
|
|
@@ -233,20 +259,27 @@ export function validateReveniumConfig(config) {
|
|
|
233
259
|
// Build validated config (apply default for reveniumBaseUrl if not provided)
|
|
234
260
|
const validatedConfig = {
|
|
235
261
|
reveniumApiKey: cfg?.reveniumApiKey,
|
|
236
|
-
reveniumBaseUrl: isString(cfg?.reveniumBaseUrl)
|
|
237
|
-
|
|
262
|
+
reveniumBaseUrl: isString(cfg?.reveniumBaseUrl)
|
|
263
|
+
? cfg?.reveniumBaseUrl
|
|
264
|
+
: DEFAULT_CONFIG.REVENIUM_BASE_URL,
|
|
265
|
+
anthropicApiKey: isString(cfg?.anthropicApiKey)
|
|
266
|
+
? cfg?.anthropicApiKey
|
|
267
|
+
: undefined,
|
|
238
268
|
apiTimeout: isNumber(cfg?.apiTimeout) ? cfg?.apiTimeout : undefined,
|
|
239
269
|
failSilent: isBoolean(cfg?.failSilent) ? cfg?.failSilent : undefined,
|
|
240
270
|
maxRetries: isNumber(cfg?.maxRetries) ? cfg?.maxRetries : undefined,
|
|
241
271
|
printSummary: validatedPrintSummary,
|
|
242
|
-
teamId: isString(cfg?.teamId) ? cfg?.teamId?.trim() : undefined
|
|
272
|
+
teamId: isString(cfg?.teamId) ? cfg?.teamId?.trim() : undefined,
|
|
273
|
+
capturePrompts: isBoolean(cfg?.capturePrompts)
|
|
274
|
+
? cfg?.capturePrompts
|
|
275
|
+
: undefined,
|
|
243
276
|
};
|
|
244
277
|
return {
|
|
245
278
|
isValid: true,
|
|
246
279
|
errors: [],
|
|
247
280
|
warnings,
|
|
248
281
|
config: validatedConfig,
|
|
249
|
-
suggestions: suggestions.length > 0 ? suggestions : undefined
|
|
282
|
+
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
250
283
|
};
|
|
251
284
|
}
|
|
252
285
|
/**
|
|
@@ -258,27 +291,27 @@ export function validateAnthropicMessageParams(params) {
|
|
|
258
291
|
if (!isObject(params)) {
|
|
259
292
|
return {
|
|
260
293
|
isValid: false,
|
|
261
|
-
errors: [
|
|
262
|
-
warnings: []
|
|
294
|
+
errors: ["Message parameters must be an object"],
|
|
295
|
+
warnings: [],
|
|
263
296
|
};
|
|
264
297
|
}
|
|
265
298
|
const data = params;
|
|
266
299
|
// Validate required model field
|
|
267
300
|
if (!isString(data?.model)) {
|
|
268
|
-
errors.push(
|
|
301
|
+
errors.push("model field is required and must be a string");
|
|
269
302
|
}
|
|
270
303
|
else if (data?.model?.trim()?.length === 0) {
|
|
271
|
-
errors.push(
|
|
304
|
+
errors.push("model field cannot be empty");
|
|
272
305
|
}
|
|
273
306
|
else if (!ANTHROPIC_PATTERNS.CLAUDE_MODEL_PATTERN.test(data?.model)) {
|
|
274
307
|
warnings.push('Model name does not contain "claude" - verify it is a valid Anthropic model');
|
|
275
308
|
}
|
|
276
309
|
// Validate required messages array
|
|
277
310
|
if (!Array.isArray(data?.messages)) {
|
|
278
|
-
errors.push(
|
|
311
|
+
errors.push("messages field is required and must be an array");
|
|
279
312
|
}
|
|
280
313
|
else if (data?.messages?.length === 0) {
|
|
281
|
-
errors.push(
|
|
314
|
+
errors.push("messages array cannot be empty");
|
|
282
315
|
}
|
|
283
316
|
else {
|
|
284
317
|
// Validate message structure
|
|
@@ -291,37 +324,40 @@ export function validateAnthropicMessageParams(params) {
|
|
|
291
324
|
if (!isString(msg.role)) {
|
|
292
325
|
errors.push(`Message at index ${index} must have a role field`);
|
|
293
326
|
}
|
|
294
|
-
else if (![
|
|
327
|
+
else if (!["user", "assistant", "system"].includes(msg.role)) {
|
|
295
328
|
warnings.push(`Message at index ${index} has unusual role: ${msg.role}`);
|
|
296
329
|
}
|
|
297
|
-
if (msg?.content &&
|
|
330
|
+
if (msg?.content &&
|
|
331
|
+
!isString(msg?.content) &&
|
|
332
|
+
!Array.isArray(msg?.content)) {
|
|
298
333
|
warnings.push(`Message at index ${index} content should be a string or array`);
|
|
299
334
|
}
|
|
300
335
|
});
|
|
301
336
|
}
|
|
302
337
|
// Validate optional parameters
|
|
303
338
|
if (!isNumber(data?.max_tokens)) {
|
|
304
|
-
warnings.push(
|
|
339
|
+
warnings.push("max_tokens should be a number");
|
|
305
340
|
}
|
|
306
341
|
else if (data?.max_tokens <= 0) {
|
|
307
|
-
warnings.push(
|
|
342
|
+
warnings.push("max_tokens should be positive");
|
|
308
343
|
}
|
|
309
344
|
else if (data?.max_tokens > VALIDATION_CONFIG.HIGH_MAX_TOKENS_THRESHOLD) {
|
|
310
|
-
warnings.push(
|
|
345
|
+
warnings.push("max_tokens is very high - verify this is intended");
|
|
311
346
|
}
|
|
312
347
|
if (!isNumber(data?.temperature)) {
|
|
313
|
-
warnings.push(
|
|
348
|
+
warnings.push("temperature should be a number");
|
|
314
349
|
}
|
|
315
|
-
else if (data?.temperature < VALIDATION_CONFIG.MIN_TEMPERATURE ||
|
|
350
|
+
else if (data?.temperature < VALIDATION_CONFIG.MIN_TEMPERATURE ||
|
|
351
|
+
data?.temperature > VALIDATION_CONFIG.MAX_TEMPERATURE) {
|
|
316
352
|
warnings.push(`temperature should be between ${VALIDATION_CONFIG.MIN_TEMPERATURE} and ${VALIDATION_CONFIG.MAX_TEMPERATURE} for Anthropic models`);
|
|
317
353
|
}
|
|
318
354
|
if (data?.stream && !isBoolean(data?.stream)) {
|
|
319
|
-
warnings.push(
|
|
355
|
+
warnings.push("stream should be a boolean");
|
|
320
356
|
}
|
|
321
357
|
return {
|
|
322
358
|
isValid: errors.length === 0,
|
|
323
359
|
errors,
|
|
324
|
-
warnings
|
|
360
|
+
warnings,
|
|
325
361
|
};
|
|
326
362
|
}
|
|
327
363
|
//# sourceMappingURL=validation.js.map
|
package/dist/esm/wrapper.js
CHANGED
|
@@ -6,6 +6,8 @@ import { getLogger, getConfig } from "./config.js";
|
|
|
6
6
|
import { trackUsageAsync, extractUsageFromResponse, extractUsageFromStream, } from "./tracking.js";
|
|
7
7
|
import { validateAnthropicMessageParams, validateUsageMetadata, } from "./utils/validation.js";
|
|
8
8
|
import { AnthropicPatchingError, RequestProcessingError, StreamProcessingError, createErrorContext, handleError, } from "./utils/error-handling.js";
|
|
9
|
+
import { shouldCapturePrompts } from "./utils/prompt-extraction.js";
|
|
10
|
+
import { detectVisionContent } from "./utils/trace-fields.js";
|
|
9
11
|
import { randomUUID } from "crypto";
|
|
10
12
|
// Global logger
|
|
11
13
|
const logger = getLogger();
|
|
@@ -128,6 +130,65 @@ export function unpatchAnthropic() {
|
|
|
128
130
|
export function isAnthropicPatched() {
|
|
129
131
|
return patchingContext.isPatched;
|
|
130
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Reconstruct a response object from streaming chunks for prompt capture
|
|
135
|
+
*/
|
|
136
|
+
function reconstructResponseFromChunks(chunks, model) {
|
|
137
|
+
const contentBlocks = [];
|
|
138
|
+
let stopReason;
|
|
139
|
+
let stopSequence;
|
|
140
|
+
const usage = {};
|
|
141
|
+
for (const chunk of chunks) {
|
|
142
|
+
if (chunk.type === "content_block_start" && chunk.content_block) {
|
|
143
|
+
contentBlocks.push({ ...chunk.content_block });
|
|
144
|
+
}
|
|
145
|
+
else if (chunk.type === "content_block_delta" && chunk.delta) {
|
|
146
|
+
const lastBlock = contentBlocks[contentBlocks.length - 1];
|
|
147
|
+
if (lastBlock && chunk.delta.type === "text_delta") {
|
|
148
|
+
if (lastBlock.type === "text") {
|
|
149
|
+
lastBlock.text = (lastBlock.text || "") + (chunk.delta.text || "");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else if (lastBlock && chunk.delta.type === "input_json_delta") {
|
|
153
|
+
if (lastBlock.type === "tool_use") {
|
|
154
|
+
lastBlock.input = lastBlock.input || "";
|
|
155
|
+
lastBlock.input +=
|
|
156
|
+
chunk.delta.partial_json || "";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (chunk.type === "message_delta" && chunk.delta) {
|
|
161
|
+
const delta = chunk.delta;
|
|
162
|
+
if (delta.stop_reason) {
|
|
163
|
+
stopReason = delta.stop_reason;
|
|
164
|
+
}
|
|
165
|
+
if (delta.stop_sequence) {
|
|
166
|
+
stopSequence = delta.stop_sequence;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else if (chunk.type === "message_start" && chunk.message?.usage) {
|
|
170
|
+
Object.assign(usage, chunk.message.usage);
|
|
171
|
+
}
|
|
172
|
+
else if (chunk.usage) {
|
|
173
|
+
Object.assign(usage, chunk.usage);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
id: `reconstructed-${Date.now()}`,
|
|
178
|
+
type: "message",
|
|
179
|
+
role: "assistant",
|
|
180
|
+
content: contentBlocks,
|
|
181
|
+
model,
|
|
182
|
+
stop_reason: stopReason || "end_turn",
|
|
183
|
+
stop_sequence: stopSequence,
|
|
184
|
+
usage: {
|
|
185
|
+
input_tokens: usage.input_tokens || 0,
|
|
186
|
+
output_tokens: usage.output_tokens || 0,
|
|
187
|
+
cache_creation_input_tokens: usage.cache_creation_input_tokens,
|
|
188
|
+
cache_read_input_tokens: usage.cache_read_input_tokens,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
131
192
|
/**
|
|
132
193
|
* Handle streaming response by collecting chunks and extracting usage data
|
|
133
194
|
*/
|
|
@@ -160,6 +221,10 @@ async function handleStreamingResponse(stream, context) {
|
|
|
160
221
|
timeToFirstToken,
|
|
161
222
|
});
|
|
162
223
|
const usage = extractUsageFromStream(chunks);
|
|
224
|
+
let reconstructedResponse = undefined;
|
|
225
|
+
if (shouldCapturePrompts(metadata)) {
|
|
226
|
+
reconstructedResponse = reconstructResponseFromChunks(chunks, model);
|
|
227
|
+
}
|
|
163
228
|
// Create tracking data
|
|
164
229
|
const trackingData = {
|
|
165
230
|
requestId,
|
|
@@ -176,6 +241,8 @@ async function handleStreamingResponse(stream, context) {
|
|
|
176
241
|
responseTime,
|
|
177
242
|
timeToFirstToken,
|
|
178
243
|
requestBody: requestBody,
|
|
244
|
+
response: reconstructedResponse,
|
|
245
|
+
hasVisionContent: detectVisionContent(requestBody),
|
|
179
246
|
};
|
|
180
247
|
// Track usage asynchronously
|
|
181
248
|
trackUsageAsync(trackingData);
|
|
@@ -237,6 +304,8 @@ async function patchedCreateMethod(params, options) {
|
|
|
237
304
|
const responseTime = new Date();
|
|
238
305
|
// Extract usage information
|
|
239
306
|
const usage = extractUsageFromResponse(response);
|
|
307
|
+
// Detect vision content
|
|
308
|
+
const hasVisionContent = detectVisionContent(params);
|
|
240
309
|
// Create tracking data
|
|
241
310
|
const trackingData = {
|
|
242
311
|
requestId,
|
|
@@ -251,7 +320,9 @@ async function patchedCreateMethod(params, options) {
|
|
|
251
320
|
metadata,
|
|
252
321
|
requestTime,
|
|
253
322
|
responseTime,
|
|
323
|
+
hasVisionContent,
|
|
254
324
|
requestBody: params,
|
|
325
|
+
response,
|
|
255
326
|
};
|
|
256
327
|
// Track usage asynchronously
|
|
257
328
|
trackUsageAsync(trackingData);
|
|
@@ -336,6 +407,12 @@ async function* patchedStreamMethod(params, options) {
|
|
|
336
407
|
: undefined;
|
|
337
408
|
// Extract usage information from all chunks
|
|
338
409
|
const usage = extractUsageFromStream(chunks);
|
|
410
|
+
// Detect vision content
|
|
411
|
+
const hasVisionContent = detectVisionContent(params);
|
|
412
|
+
let reconstructedResponse = undefined;
|
|
413
|
+
if (shouldCapturePrompts(metadata)) {
|
|
414
|
+
reconstructedResponse = reconstructResponseFromChunks(chunks, params.model);
|
|
415
|
+
}
|
|
339
416
|
// Create tracking data
|
|
340
417
|
const trackingData = {
|
|
341
418
|
requestId,
|
|
@@ -351,7 +428,9 @@ async function* patchedStreamMethod(params, options) {
|
|
|
351
428
|
requestTime,
|
|
352
429
|
responseTime,
|
|
353
430
|
timeToFirstToken,
|
|
431
|
+
hasVisionContent,
|
|
354
432
|
requestBody: params,
|
|
433
|
+
response: reconstructedResponse,
|
|
355
434
|
};
|
|
356
435
|
// Track usage asynchronously
|
|
357
436
|
trackUsageAsync(trackingData);
|
package/dist/types/config.d.ts
CHANGED
|
@@ -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
|
package/dist/types/types.d.ts
CHANGED
|
@@ -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 and send prompts to Revenium API (default: false) */
|
|
51
|
+
capturePrompts?: boolean;
|
|
50
52
|
}
|
|
51
53
|
/**
|
|
52
54
|
* Usage metadata for enhanced tracking and analytics
|
|
@@ -58,16 +60,28 @@ export interface UsageMetadata {
|
|
|
58
60
|
traceId?: string;
|
|
59
61
|
/** Classification of AI operation (e.g., 'customer-support', 'content-generation', 'code-review') */
|
|
60
62
|
taskType?: string;
|
|
61
|
-
/** Customer organization
|
|
63
|
+
/** Customer organization name for multi-tenant applications (used for lookup/auto-creation) */
|
|
64
|
+
organizationName?: string;
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated Use organizationName instead. This field will be removed in a future version.
|
|
67
|
+
* Customer organization identifier for multi-tenant applications
|
|
68
|
+
*/
|
|
62
69
|
organizationId?: string;
|
|
63
70
|
/** Reference to billing plan or subscription tier */
|
|
64
71
|
subscriptionId?: string;
|
|
65
|
-
/** Product or feature
|
|
72
|
+
/** Product or feature name that is using AI services (used for lookup/auto-creation) */
|
|
73
|
+
productName?: string;
|
|
74
|
+
/**
|
|
75
|
+
* @deprecated Use productName instead. This field will be removed in a future version.
|
|
76
|
+
* Product or feature identifier that is using AI services
|
|
77
|
+
*/
|
|
66
78
|
productId?: string;
|
|
67
79
|
/** Agent or bot identifier for automated systems */
|
|
68
80
|
agent?: string;
|
|
69
81
|
/** Quality score of AI response (0.0-1.0) for performance tracking */
|
|
70
82
|
responseQualityScore?: number;
|
|
83
|
+
/** Per-call override for prompt capture (overrides environment variable and config) */
|
|
84
|
+
capturePrompts?: boolean;
|
|
71
85
|
/** Allow additional custom fields for extensibility */
|
|
72
86
|
[key: string]: unknown;
|
|
73
87
|
}
|
|
@@ -129,8 +143,12 @@ export interface TrackingData {
|
|
|
129
143
|
responseTime: Date;
|
|
130
144
|
/** Time to first token in milliseconds (for streaming responses) */
|
|
131
145
|
timeToFirstToken?: number;
|
|
146
|
+
/** Whether the request contains vision/image content */
|
|
147
|
+
hasVisionContent?: boolean;
|
|
132
148
|
/** Request body containing Anthropic message parameters */
|
|
133
149
|
requestBody?: AnthropicMessageParams;
|
|
150
|
+
/** Response data from Anthropic API */
|
|
151
|
+
response?: AnthropicResponse;
|
|
134
152
|
}
|
|
135
153
|
/**
|
|
136
154
|
* Internal payload structure for Revenium API
|
|
@@ -161,10 +179,10 @@ export interface ReveniumPayload {
|
|
|
161
179
|
cacheReadTokenCount: number;
|
|
162
180
|
/** Total token count (sum of all token types) */
|
|
163
181
|
totalTokenCount: number;
|
|
164
|
-
/** Organization
|
|
165
|
-
|
|
166
|
-
/** Product
|
|
167
|
-
|
|
182
|
+
/** Organization name for multi-tenant tracking (used for lookup/auto-creation) */
|
|
183
|
+
organizationName?: string;
|
|
184
|
+
/** Product name (used for lookup/auto-creation) */
|
|
185
|
+
productName?: string;
|
|
168
186
|
/** Subscriber information */
|
|
169
187
|
subscriber?: Subscriber;
|
|
170
188
|
/** Subscription identifier */
|
|
@@ -197,9 +215,14 @@ export interface ReveniumPayload {
|
|
|
197
215
|
parentTransactionId?: string;
|
|
198
216
|
transactionName?: string;
|
|
199
217
|
region?: string;
|
|
218
|
+
hasVisionContent?: boolean;
|
|
200
219
|
credentialAlias?: string;
|
|
201
220
|
traceType?: string;
|
|
202
221
|
traceName?: string;
|
|
222
|
+
systemPrompt?: string;
|
|
223
|
+
inputMessages?: string;
|
|
224
|
+
outputResponse?: string;
|
|
225
|
+
promptsTruncated?: boolean;
|
|
203
226
|
}
|
|
204
227
|
/**
|
|
205
228
|
* Anthropic content block types for message validation
|
|
@@ -328,8 +351,14 @@ export interface AnthropicMessageParams {
|
|
|
328
351
|
stream?: boolean;
|
|
329
352
|
/** Sequences that will stop generation */
|
|
330
353
|
stop_sequences?: string[];
|
|
331
|
-
/** System message to set context */
|
|
332
|
-
system?: string
|
|
354
|
+
/** System message to set context - can be string or array of content blocks */
|
|
355
|
+
system?: string | Array<{
|
|
356
|
+
type: "text";
|
|
357
|
+
text: string;
|
|
358
|
+
} | {
|
|
359
|
+
type: "image";
|
|
360
|
+
source: unknown;
|
|
361
|
+
}>;
|
|
333
362
|
/** Available tools for function calling */
|
|
334
363
|
tools?: AnthropicTool[];
|
|
335
364
|
/** Tool usage configuration */
|
|
@@ -469,6 +498,7 @@ export interface EnvironmentConfig {
|
|
|
469
498
|
maxRetries?: string;
|
|
470
499
|
printSummary?: string;
|
|
471
500
|
teamId?: string;
|
|
501
|
+
capturePrompts?: string;
|
|
472
502
|
}
|
|
473
503
|
/**
|
|
474
504
|
* Summary output format options
|