@revenium/anthropic 1.1.0 → 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.
@@ -14,6 +14,8 @@ const config_1 = require("./config");
14
14
  const tracking_1 = require("./tracking");
15
15
  const validation_1 = require("./utils/validation");
16
16
  const error_handling_1 = require("./utils/error-handling");
17
+ const prompt_extraction_1 = require("./utils/prompt-extraction");
18
+ const trace_fields_1 = require("./utils/trace-fields");
17
19
  const crypto_1 = require("crypto");
18
20
  // Global logger
19
21
  const logger = (0, config_1.getLogger)();
@@ -136,6 +138,65 @@ function unpatchAnthropic() {
136
138
  function isAnthropicPatched() {
137
139
  return patchingContext.isPatched;
138
140
  }
141
+ /**
142
+ * Reconstruct a response object from streaming chunks for prompt capture
143
+ */
144
+ function reconstructResponseFromChunks(chunks, model) {
145
+ const contentBlocks = [];
146
+ let stopReason;
147
+ let stopSequence;
148
+ const usage = {};
149
+ for (const chunk of chunks) {
150
+ if (chunk.type === "content_block_start" && chunk.content_block) {
151
+ contentBlocks.push({ ...chunk.content_block });
152
+ }
153
+ else if (chunk.type === "content_block_delta" && chunk.delta) {
154
+ const lastBlock = contentBlocks[contentBlocks.length - 1];
155
+ if (lastBlock && chunk.delta.type === "text_delta") {
156
+ if (lastBlock.type === "text") {
157
+ lastBlock.text = (lastBlock.text || "") + (chunk.delta.text || "");
158
+ }
159
+ }
160
+ else if (lastBlock && chunk.delta.type === "input_json_delta") {
161
+ if (lastBlock.type === "tool_use") {
162
+ lastBlock.input = lastBlock.input || "";
163
+ lastBlock.input +=
164
+ chunk.delta.partial_json || "";
165
+ }
166
+ }
167
+ }
168
+ else if (chunk.type === "message_delta" && chunk.delta) {
169
+ const delta = chunk.delta;
170
+ if (delta.stop_reason) {
171
+ stopReason = delta.stop_reason;
172
+ }
173
+ if (delta.stop_sequence) {
174
+ stopSequence = delta.stop_sequence;
175
+ }
176
+ }
177
+ else if (chunk.type === "message_start" && chunk.message?.usage) {
178
+ Object.assign(usage, chunk.message.usage);
179
+ }
180
+ else if (chunk.usage) {
181
+ Object.assign(usage, chunk.usage);
182
+ }
183
+ }
184
+ return {
185
+ id: `reconstructed-${Date.now()}`,
186
+ type: "message",
187
+ role: "assistant",
188
+ content: contentBlocks,
189
+ model,
190
+ stop_reason: stopReason || "end_turn",
191
+ stop_sequence: stopSequence,
192
+ usage: {
193
+ input_tokens: usage.input_tokens || 0,
194
+ output_tokens: usage.output_tokens || 0,
195
+ cache_creation_input_tokens: usage.cache_creation_input_tokens,
196
+ cache_read_input_tokens: usage.cache_read_input_tokens,
197
+ },
198
+ };
199
+ }
139
200
  /**
140
201
  * Handle streaming response by collecting chunks and extracting usage data
141
202
  */
@@ -168,6 +229,10 @@ async function handleStreamingResponse(stream, context) {
168
229
  timeToFirstToken,
169
230
  });
170
231
  const usage = (0, tracking_1.extractUsageFromStream)(chunks);
232
+ let reconstructedResponse = undefined;
233
+ if ((0, prompt_extraction_1.shouldCapturePrompts)(metadata)) {
234
+ reconstructedResponse = reconstructResponseFromChunks(chunks, model);
235
+ }
171
236
  // Create tracking data
172
237
  const trackingData = {
173
238
  requestId,
@@ -184,6 +249,8 @@ async function handleStreamingResponse(stream, context) {
184
249
  responseTime,
185
250
  timeToFirstToken,
186
251
  requestBody: requestBody,
252
+ response: reconstructedResponse,
253
+ hasVisionContent: (0, trace_fields_1.detectVisionContent)(requestBody),
187
254
  };
188
255
  // Track usage asynchronously
189
256
  (0, tracking_1.trackUsageAsync)(trackingData);
@@ -245,6 +312,8 @@ async function patchedCreateMethod(params, options) {
245
312
  const responseTime = new Date();
246
313
  // Extract usage information
247
314
  const usage = (0, tracking_1.extractUsageFromResponse)(response);
315
+ // Detect vision content
316
+ const hasVisionContent = (0, trace_fields_1.detectVisionContent)(params);
248
317
  // Create tracking data
249
318
  const trackingData = {
250
319
  requestId,
@@ -259,7 +328,9 @@ async function patchedCreateMethod(params, options) {
259
328
  metadata,
260
329
  requestTime,
261
330
  responseTime,
331
+ hasVisionContent,
262
332
  requestBody: params,
333
+ response,
263
334
  };
264
335
  // Track usage asynchronously
265
336
  (0, tracking_1.trackUsageAsync)(trackingData);
@@ -344,6 +415,12 @@ async function* patchedStreamMethod(params, options) {
344
415
  : undefined;
345
416
  // Extract usage information from all chunks
346
417
  const usage = (0, tracking_1.extractUsageFromStream)(chunks);
418
+ // Detect vision content
419
+ const hasVisionContent = (0, trace_fields_1.detectVisionContent)(params);
420
+ let reconstructedResponse = undefined;
421
+ if ((0, prompt_extraction_1.shouldCapturePrompts)(metadata)) {
422
+ reconstructedResponse = reconstructResponseFromChunks(chunks, params.model);
423
+ }
347
424
  // Create tracking data
348
425
  const trackingData = {
349
426
  requestId,
@@ -359,7 +436,9 @@ async function* patchedStreamMethod(params, options) {
359
436
  requestTime,
360
437
  responseTime,
361
438
  timeToFirstToken,
439
+ hasVisionContent,
362
440
  requestBody: params,
441
+ response: reconstructedResponse,
363
442
  };
364
443
  // Track usage asynchronously
365
444
  (0, tracking_1.trackUsageAsync)(trackingData);
@@ -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
  }
@@ -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
  }
@@ -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 '../constants.js';
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 === 'object' && value !== null && !Array.isArray(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 === 'string';
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 === 'number' && !isNaN(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 === 'boolean';
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
- 'traceId', 'taskType', 'organizationId',
53
- 'subscriptionId', 'productId', 'agent'
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) && subscriberData?.id?.trim()?.length > 0) {
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) && subscriberData?.email?.trim()?.length > 0) {
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) && credentialData?.name?.trim()?.length > 0) {
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) && credentialData?.value?.trim()?.length > 0) {
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.responseQualityScore;
95
- if (isNumber(responseQualityScore) && responseQualityScore >= 0 && responseQualityScore <= 1) {
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: ['Configuration must be an object'],
128
+ errors: ["Configuration must be an object"],
111
129
  warnings: [],
112
- suggestions: ['Ensure you are passing a valid configuration object with required fields']
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('reveniumApiKey is required and must be a string');
119
- suggestions.push('Set REVENIUM_METERING_API_KEY environment variable or provide reveniumApiKey in config');
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('reveniumApiKey cannot be empty');
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('Obtain a valid Revenium API key from your Revenium dashboard');
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('reveniumApiKey appears to be too short - verify it is correct');
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('reveniumBaseUrl must be a string if provided');
154
+ errors.push("reveniumBaseUrl must be a string if provided");
135
155
  }
136
156
  else if (!cfg?.reveniumBaseUrl?.trim()) {
137
- errors.push('reveniumBaseUrl cannot be empty if provided');
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('http')) {
143
- errors.push('reveniumBaseUrl must use HTTP or HTTPS protocol');
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 = ['localhost', '127.0.0.1', '::1', '[::1]'];
166
+ const localhostHostnames = ["localhost", "127.0.0.1", "::1", "[::1]"];
147
167
  if (localhostHostnames.includes(url.hostname)) {
148
- warnings.push('Using localhost for Revenium API - ensure this is intended for development');
168
+ warnings.push("Using localhost for Revenium API - ensure this is intended for development");
149
169
  }
150
170
  }
151
171
  catch {
152
- errors.push('reveniumBaseUrl must be a valid URL');
153
- suggestions.push('Use format: https://api.revenium.ai');
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('anthropicApiKey must be a string if provided');
179
+ errors.push("anthropicApiKey must be a string if provided");
160
180
  }
161
- else if (cfg?.anthropicApiKey !== undefined && cfg?.anthropicApiKey?.trim()?.length === 0) {
162
- warnings.push('anthropicApiKey is empty - API calls may fail');
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 && !cfg?.anthropicApiKey?.startsWith(VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX)) {
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('apiTimeout must be a number if provided');
191
+ errors.push("apiTimeout must be a number if provided");
170
192
  }
171
- else if (cfg?.apiTimeout !== undefined && cfg?.apiTimeout < VALIDATION_CONFIG.MIN_API_TIMEOUT) {
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 && cfg?.apiTimeout > VALIDATION_CONFIG.MAX_API_TIMEOUT) {
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 && cfg?.apiTimeout < VALIDATION_CONFIG.LOW_TIMEOUT_WARNING_THRESHOLD) {
178
- warnings.push('apiTimeout is very low - may cause timeouts for slow networks');
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('failSilent must be a boolean if provided');
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('maxRetries must be a number if provided');
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('maxRetries cannot be negative');
214
+ errors.push("maxRetries cannot be negative");
190
215
  }
191
- else if (cfg?.maxRetries !== undefined && cfg?.maxRetries > VALIDATION_CONFIG.MAX_RETRY_ATTEMPTS) {
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('maxRetries is 0 - no retry attempts will be made');
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, 'human', 'json'];
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('teamId must be a string if provided');
234
+ errors.push("teamId must be a string if provided");
209
235
  }
210
236
  else if (cfg?.teamId?.trim()?.length === 0) {
211
- errors.push('teamId cannot be empty if provided');
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 === 'human') {
225
- validatedPrintSummary = 'human';
250
+ if (cfg?.printSummary === true || cfg?.printSummary === "human") {
251
+ validatedPrintSummary = "human";
226
252
  }
227
- else if (cfg?.printSummary === 'json') {
228
- validatedPrintSummary = 'json';
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) ? cfg?.reveniumBaseUrl : DEFAULT_CONFIG.REVENIUM_BASE_URL,
237
- anthropicApiKey: isString(cfg?.anthropicApiKey) ? cfg?.anthropicApiKey : undefined,
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: ['Message parameters must be an object'],
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('model field is required and must be a string');
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('model field cannot be empty');
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('messages field is required and must be an array');
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('messages array cannot be empty');
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 (!['user', 'assistant', 'system'].includes(msg.role)) {
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 && !isString(msg?.content) && !Array.isArray(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('max_tokens should be a number');
339
+ warnings.push("max_tokens should be a number");
305
340
  }
306
341
  else if (data?.max_tokens <= 0) {
307
- warnings.push('max_tokens should be positive');
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('max_tokens is very high - verify this is intended');
345
+ warnings.push("max_tokens is very high - verify this is intended");
311
346
  }
312
347
  if (!isNumber(data?.temperature)) {
313
- warnings.push('temperature should be a number');
348
+ warnings.push("temperature should be a number");
314
349
  }
315
- else if (data?.temperature < VALIDATION_CONFIG.MIN_TEMPERATURE || data?.temperature > VALIDATION_CONFIG.MAX_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('stream should be a boolean');
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