@revenium/anthropic 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +807 -0
- package/dist/cjs/config.js +208 -0
- package/dist/cjs/constants.js +144 -0
- package/dist/cjs/index.js +148 -0
- package/dist/cjs/tracking.js +252 -0
- package/dist/cjs/types/anthropic-augmentation.js +87 -0
- package/dist/cjs/types.js +6 -0
- package/dist/cjs/utils/circuit-breaker.js +232 -0
- package/dist/cjs/utils/error-handling.js +233 -0
- package/dist/cjs/utils/validation.js +307 -0
- package/dist/cjs/wrapper.js +374 -0
- package/dist/esm/config.js +197 -0
- package/dist/esm/constants.js +141 -0
- package/dist/esm/index.js +121 -0
- package/dist/esm/tracking.js +243 -0
- package/dist/esm/types/anthropic-augmentation.js +86 -0
- package/dist/esm/types.js +5 -0
- package/dist/esm/utils/circuit-breaker.js +223 -0
- package/dist/esm/utils/error-handling.js +216 -0
- package/dist/esm/utils/validation.js +296 -0
- package/dist/esm/wrapper.js +366 -0
- package/dist/types/config.d.ts +43 -0
- package/dist/types/constants.d.ts +141 -0
- package/dist/types/index.d.ts +54 -0
- package/dist/types/tracking.d.ts +42 -0
- package/dist/types/types/anthropic-augmentation.d.ts +182 -0
- package/dist/types/types.d.ts +647 -0
- package/dist/types/utils/circuit-breaker.d.ts +110 -0
- package/dist/types/utils/error-handling.d.ts +108 -0
- package/dist/types/utils/validation.d.ts +57 -0
- package/dist/types/wrapper.d.ts +16 -0
- package/package.json +74 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Error handling utilities for Anthropic middleware with structured errors and context building
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ErrorContext = exports.StreamProcessingError = exports.ValidationError = exports.ReveniumApiError = exports.RequestProcessingError = exports.AnthropicPatchingError = exports.ConfigurationError = exports.ReveniumError = void 0;
|
|
7
|
+
exports.getErrorMessage = getErrorMessage;
|
|
8
|
+
exports.getErrorStack = getErrorStack;
|
|
9
|
+
exports.createErrorContext = createErrorContext;
|
|
10
|
+
exports.handleError = handleError;
|
|
11
|
+
exports.withErrorHandling = withErrorHandling;
|
|
12
|
+
exports.withRetry = withRetry;
|
|
13
|
+
const constants_1 = require("../constants");
|
|
14
|
+
/**
|
|
15
|
+
* Base error class for Revenium middleware errors
|
|
16
|
+
*/
|
|
17
|
+
class ReveniumError extends Error {
|
|
18
|
+
constructor(message, code, context) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'ReveniumError';
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.context = context;
|
|
23
|
+
// Maintain proper stack trace for where our error was thrown
|
|
24
|
+
if (Error.captureStackTrace) {
|
|
25
|
+
Error.captureStackTrace(this, ReveniumError);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.ReveniumError = ReveniumError;
|
|
30
|
+
/**
|
|
31
|
+
* Configuration-related errors
|
|
32
|
+
*/
|
|
33
|
+
class ConfigurationError extends ReveniumError {
|
|
34
|
+
constructor(message, context) {
|
|
35
|
+
super(message, 'CONFIGURATION_ERROR', context);
|
|
36
|
+
this.name = 'ConfigurationError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.ConfigurationError = ConfigurationError;
|
|
40
|
+
/**
|
|
41
|
+
* Anthropic SDK patching errors
|
|
42
|
+
*/
|
|
43
|
+
class AnthropicPatchingError extends ReveniumError {
|
|
44
|
+
constructor(message, context) {
|
|
45
|
+
super(message, 'ANTHROPIC_PATCHING_ERROR', context);
|
|
46
|
+
this.name = 'AnthropicPatchingError';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.AnthropicPatchingError = AnthropicPatchingError;
|
|
50
|
+
/**
|
|
51
|
+
* Request processing errors
|
|
52
|
+
*/
|
|
53
|
+
class RequestProcessingError extends ReveniumError {
|
|
54
|
+
constructor(message, context) {
|
|
55
|
+
super(message, 'REQUEST_PROCESSING_ERROR', context);
|
|
56
|
+
this.name = 'RequestProcessingError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.RequestProcessingError = RequestProcessingError;
|
|
60
|
+
/**
|
|
61
|
+
* Revenium API communication errors
|
|
62
|
+
*/
|
|
63
|
+
class ReveniumApiError extends ReveniumError {
|
|
64
|
+
constructor(message, statusCode, responseBody, context) {
|
|
65
|
+
super(message, 'REVENIUM_API_ERROR', context);
|
|
66
|
+
this.name = 'ReveniumApiError';
|
|
67
|
+
this.statusCode = statusCode;
|
|
68
|
+
this.responseBody = responseBody;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.ReveniumApiError = ReveniumApiError;
|
|
72
|
+
/**
|
|
73
|
+
* Validation errors
|
|
74
|
+
*/
|
|
75
|
+
class ValidationError extends ReveniumError {
|
|
76
|
+
constructor(message, validationErrors, context) {
|
|
77
|
+
super(message, 'VALIDATION_ERROR', context);
|
|
78
|
+
this.name = 'ValidationError';
|
|
79
|
+
this.validationErrors = validationErrors;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.ValidationError = ValidationError;
|
|
83
|
+
/**
|
|
84
|
+
* Stream processing errors
|
|
85
|
+
*/
|
|
86
|
+
class StreamProcessingError extends ReveniumError {
|
|
87
|
+
constructor(message, context) {
|
|
88
|
+
super(message, 'STREAM_PROCESSING_ERROR', context);
|
|
89
|
+
this.name = 'StreamProcessingError';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
exports.StreamProcessingError = StreamProcessingError;
|
|
93
|
+
/**
|
|
94
|
+
* Error context builder for consistent error reporting
|
|
95
|
+
*/
|
|
96
|
+
class ErrorContext {
|
|
97
|
+
constructor() {
|
|
98
|
+
this.context = {};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Add request ID to error context
|
|
102
|
+
*/
|
|
103
|
+
withRequestId(requestId) {
|
|
104
|
+
this.context.requestId = requestId;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Add model information to error context
|
|
109
|
+
*/
|
|
110
|
+
withModel(model) {
|
|
111
|
+
this.context.model = model;
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Add duration to error context
|
|
116
|
+
*/
|
|
117
|
+
withDuration(duration) {
|
|
118
|
+
this.context.duration = duration;
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Add HTTP status to error context
|
|
123
|
+
*/
|
|
124
|
+
withStatus(status) {
|
|
125
|
+
this.context.status = status;
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Add custom field to error context
|
|
130
|
+
*/
|
|
131
|
+
with(key, value) {
|
|
132
|
+
this.context[key] = value;
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Build the context object
|
|
137
|
+
*/
|
|
138
|
+
build() {
|
|
139
|
+
return { ...this.context };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
exports.ErrorContext = ErrorContext;
|
|
143
|
+
/**
|
|
144
|
+
* Safe error message extraction
|
|
145
|
+
*/
|
|
146
|
+
function getErrorMessage(error) {
|
|
147
|
+
if (error instanceof Error)
|
|
148
|
+
return error.message;
|
|
149
|
+
if (typeof error === 'string')
|
|
150
|
+
return error;
|
|
151
|
+
if (error && typeof error === 'object' && 'message' in error) {
|
|
152
|
+
const message = error.message;
|
|
153
|
+
if (typeof message === 'string')
|
|
154
|
+
return message;
|
|
155
|
+
}
|
|
156
|
+
return 'Unknown error occurred';
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Safe error stack extraction
|
|
160
|
+
*/
|
|
161
|
+
function getErrorStack(error) {
|
|
162
|
+
if (error instanceof Error && error.stack)
|
|
163
|
+
return error.stack;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Create error context builder
|
|
168
|
+
*/
|
|
169
|
+
function createErrorContext() {
|
|
170
|
+
return new ErrorContext();
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Handle and log errors consistently
|
|
174
|
+
*/
|
|
175
|
+
function handleError(error, logger, context) {
|
|
176
|
+
const message = getErrorMessage(error);
|
|
177
|
+
const stack = getErrorStack(error);
|
|
178
|
+
const logContext = {
|
|
179
|
+
...context,
|
|
180
|
+
error: message,
|
|
181
|
+
stack: stack
|
|
182
|
+
};
|
|
183
|
+
if (error instanceof ReveniumError) {
|
|
184
|
+
logContext.errorCode = error.code;
|
|
185
|
+
logContext.errorContext = error.context;
|
|
186
|
+
if (error instanceof ReveniumApiError) {
|
|
187
|
+
logContext.statusCode = error.statusCode;
|
|
188
|
+
logContext.responseBody = error.responseBody;
|
|
189
|
+
}
|
|
190
|
+
if (error instanceof ValidationError) {
|
|
191
|
+
logContext.validationErrors = error.validationErrors;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
logger.error('Error occurred', logContext);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Wrap async function with error handling
|
|
198
|
+
*/
|
|
199
|
+
function withErrorHandling(fn, errorHandler) {
|
|
200
|
+
return async (...args) => {
|
|
201
|
+
try {
|
|
202
|
+
return await fn(...args);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
errorHandler(error, ...args);
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Retry function with exponential backoff
|
|
212
|
+
*/
|
|
213
|
+
async function withRetry(fn, maxRetries = constants_1.RETRY_CONFIG.BASE_DELAY / 1000, // Convert to attempts for backward compatibility
|
|
214
|
+
baseDelay = constants_1.RETRY_CONFIG.BASE_DELAY) {
|
|
215
|
+
let lastError;
|
|
216
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
217
|
+
try {
|
|
218
|
+
return await fn();
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
lastError = error;
|
|
222
|
+
if (attempt === maxRetries) {
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
// Exponential backoff with jitter using constants
|
|
226
|
+
const delay = Math.min(baseDelay * Math.pow(2, attempt), constants_1.RETRY_CONFIG.MAX_DELAY);
|
|
227
|
+
const jitter = Math.random() * constants_1.RETRY_CONFIG.JITTER_FACTOR * delay;
|
|
228
|
+
await new Promise(resolve => setTimeout(resolve, delay + jitter));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
throw lastError;
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=error-handling.js.map
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Validation utilities for Anthropic middleware
|
|
4
|
+
* Provides type-safe validation with detailed error reporting
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.isObject = isObject;
|
|
8
|
+
exports.isString = isString;
|
|
9
|
+
exports.isNumber = isNumber;
|
|
10
|
+
exports.isBoolean = isBoolean;
|
|
11
|
+
exports.validateString = validateString;
|
|
12
|
+
exports.validateNumber = validateNumber;
|
|
13
|
+
exports.validateUsageMetadata = validateUsageMetadata;
|
|
14
|
+
exports.validateReveniumConfig = validateReveniumConfig;
|
|
15
|
+
exports.validateAnthropicMessageParams = validateAnthropicMessageParams;
|
|
16
|
+
const constants_1 = require("../constants");
|
|
17
|
+
/**
|
|
18
|
+
* Type guard for checking if a value is a non-null object
|
|
19
|
+
*/
|
|
20
|
+
function isObject(value) {
|
|
21
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Type guard for checking if a value is a string
|
|
25
|
+
*/
|
|
26
|
+
function isString(value) {
|
|
27
|
+
return typeof value === 'string';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Type guard for checking if a value is a number
|
|
31
|
+
*/
|
|
32
|
+
function isNumber(value) {
|
|
33
|
+
return typeof value === 'number' && !isNaN(value);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Type guard for checking if a value is a boolean
|
|
37
|
+
*/
|
|
38
|
+
function isBoolean(value) {
|
|
39
|
+
return typeof value === 'boolean';
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Validate and extract string from unknown value
|
|
43
|
+
*/
|
|
44
|
+
function validateString(value, defaultValue = '') {
|
|
45
|
+
return isString(value) ? value : defaultValue;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Validate and extract number from unknown value
|
|
49
|
+
*/
|
|
50
|
+
function validateNumber(value, defaultValue = 0) {
|
|
51
|
+
return isNumber(value) ? value : defaultValue;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validate usage metadata object with Anthropic-specific considerations
|
|
55
|
+
*/
|
|
56
|
+
function validateUsageMetadata(metadata) {
|
|
57
|
+
if (!isObject(metadata)) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
const validated = {};
|
|
61
|
+
// Validate string fields
|
|
62
|
+
const stringFields = [
|
|
63
|
+
'traceId', 'taskId', 'taskType', 'subscriberEmail', 'subscriberId',
|
|
64
|
+
'subscriberCredentialName', 'subscriberCredential', 'organizationId',
|
|
65
|
+
'subscriptionId', 'productId', 'agent'
|
|
66
|
+
];
|
|
67
|
+
for (const field of stringFields) {
|
|
68
|
+
const value = metadata[field];
|
|
69
|
+
if (isString(value) && value.trim().length > 0) {
|
|
70
|
+
validated[field] = value.trim();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Validate nested subscriber object
|
|
74
|
+
const subscriberData = metadata.subscriber;
|
|
75
|
+
if (isObject(subscriberData)) {
|
|
76
|
+
const subscriber = {};
|
|
77
|
+
// Validate subscriber.id
|
|
78
|
+
if (isString(subscriberData?.id) && subscriberData?.id?.trim()?.length > 0) {
|
|
79
|
+
subscriber.id = subscriberData?.id?.trim();
|
|
80
|
+
}
|
|
81
|
+
// Validate subscriber.email
|
|
82
|
+
if (isString(subscriberData?.email) && subscriberData?.email?.trim()?.length > 0) {
|
|
83
|
+
subscriber.email = subscriberData?.email?.trim();
|
|
84
|
+
}
|
|
85
|
+
// Validate subscriber.credential object
|
|
86
|
+
const credentialData = subscriberData?.credential;
|
|
87
|
+
if (isObject(credentialData)) {
|
|
88
|
+
const credential = {};
|
|
89
|
+
if (isString(credentialData?.name) && credentialData?.name?.trim()?.length > 0) {
|
|
90
|
+
credential.name = credentialData?.name?.trim();
|
|
91
|
+
}
|
|
92
|
+
if (isString(credentialData?.value) && credentialData?.value?.trim()?.length > 0) {
|
|
93
|
+
credential.value = credentialData?.value?.trim();
|
|
94
|
+
}
|
|
95
|
+
// Only include credential if it has at least name or value
|
|
96
|
+
if (credential?.name || credential?.value) {
|
|
97
|
+
subscriber.credential = credential;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Only include subscriber if it has at least one field
|
|
101
|
+
if (subscriber?.id || subscriber?.email || subscriber?.credential) {
|
|
102
|
+
validated.subscriber = subscriber;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Validate number fields
|
|
106
|
+
const responseQualityScore = metadata.responseQualityScore;
|
|
107
|
+
if (isNumber(responseQualityScore) && responseQualityScore >= 0 && responseQualityScore <= 1) {
|
|
108
|
+
validated.responseQualityScore = responseQualityScore;
|
|
109
|
+
}
|
|
110
|
+
return validated;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Comprehensive Revenium configuration validation for Anthropic
|
|
114
|
+
*/
|
|
115
|
+
function validateReveniumConfig(config) {
|
|
116
|
+
const errors = [];
|
|
117
|
+
const warnings = [];
|
|
118
|
+
const suggestions = [];
|
|
119
|
+
if (!isObject(config)) {
|
|
120
|
+
return {
|
|
121
|
+
isValid: false,
|
|
122
|
+
errors: ['Configuration must be an object'],
|
|
123
|
+
warnings: [],
|
|
124
|
+
suggestions: ['Ensure you are passing a valid configuration object with required fields']
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const cfg = config;
|
|
128
|
+
// Validate required Revenium API key
|
|
129
|
+
if (!isString(cfg?.reveniumApiKey)) {
|
|
130
|
+
errors.push('reveniumApiKey is required and must be a string');
|
|
131
|
+
suggestions.push('Set REVENIUM_METERING_API_KEY environment variable or provide reveniumApiKey in config');
|
|
132
|
+
}
|
|
133
|
+
else if (!cfg?.reveniumApiKey?.trim()) {
|
|
134
|
+
errors.push('reveniumApiKey cannot be empty');
|
|
135
|
+
}
|
|
136
|
+
else if (!cfg?.reveniumApiKey?.startsWith(constants_1.VALIDATION_CONFIG.REVENIUM_API_KEY_PREFIX)) {
|
|
137
|
+
errors.push(`reveniumApiKey must start with "${constants_1.VALIDATION_CONFIG.REVENIUM_API_KEY_PREFIX}"`);
|
|
138
|
+
suggestions.push('Obtain a valid Revenium API key from your Revenium dashboard');
|
|
139
|
+
}
|
|
140
|
+
else if (cfg?.reveniumApiKey?.length < constants_1.VALIDATION_CONFIG.MIN_API_KEY_LENGTH) {
|
|
141
|
+
warnings.push('reveniumApiKey appears to be too short - verify it is correct');
|
|
142
|
+
}
|
|
143
|
+
// Validate Revenium base URL
|
|
144
|
+
if (!isString(cfg?.reveniumBaseUrl)) {
|
|
145
|
+
errors.push('reveniumBaseUrl is required and must be a string');
|
|
146
|
+
}
|
|
147
|
+
else if (!cfg?.reveniumBaseUrl?.trim()) {
|
|
148
|
+
errors.push('reveniumBaseUrl cannot be empty');
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
try {
|
|
152
|
+
const url = new URL(cfg?.reveniumBaseUrl);
|
|
153
|
+
if (!url.protocol.startsWith('http')) {
|
|
154
|
+
errors.push('reveniumBaseUrl must use HTTP or HTTPS protocol');
|
|
155
|
+
}
|
|
156
|
+
// Check for localhost/development hostnames (IPv4, IPv6, and named)
|
|
157
|
+
const localhostHostnames = ['localhost', '127.0.0.1', '::1', '[::1]'];
|
|
158
|
+
if (localhostHostnames.includes(url.hostname)) {
|
|
159
|
+
warnings.push('Using localhost for Revenium API - ensure this is intended for development');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
errors.push('reveniumBaseUrl must be a valid URL');
|
|
164
|
+
suggestions.push('Use format: https://api.revenium.io/meter');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Validate optional Anthropic API key
|
|
168
|
+
if (!isString(cfg?.anthropicApiKey)) {
|
|
169
|
+
errors.push('anthropicApiKey must be a string if provided');
|
|
170
|
+
}
|
|
171
|
+
else if (cfg?.anthropicApiKey?.trim()?.length === 0) {
|
|
172
|
+
warnings.push('anthropicApiKey is empty - API calls may fail');
|
|
173
|
+
}
|
|
174
|
+
else if (!cfg?.anthropicApiKey?.startsWith(constants_1.VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX)) {
|
|
175
|
+
warnings.push(`anthropicApiKey does not start with "${constants_1.VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX}" - verify it is correct`);
|
|
176
|
+
}
|
|
177
|
+
// Validate optional timeout using constants
|
|
178
|
+
if (cfg?.apiTimeout !== undefined && !isNumber(cfg?.apiTimeout)) {
|
|
179
|
+
errors.push('apiTimeout must be a number if provided');
|
|
180
|
+
}
|
|
181
|
+
else if (cfg?.apiTimeout !== undefined && cfg?.apiTimeout < constants_1.VALIDATION_CONFIG.MIN_API_TIMEOUT) {
|
|
182
|
+
errors.push(`apiTimeout must be at least ${constants_1.VALIDATION_CONFIG.MIN_API_TIMEOUT}ms`);
|
|
183
|
+
}
|
|
184
|
+
else if (cfg?.apiTimeout !== undefined && cfg?.apiTimeout > constants_1.VALIDATION_CONFIG.MAX_API_TIMEOUT) {
|
|
185
|
+
errors.push(`apiTimeout must not exceed ${constants_1.VALIDATION_CONFIG.MAX_API_TIMEOUT}ms`);
|
|
186
|
+
}
|
|
187
|
+
else if (cfg?.apiTimeout !== undefined && cfg?.apiTimeout < constants_1.VALIDATION_CONFIG.LOW_TIMEOUT_WARNING_THRESHOLD) {
|
|
188
|
+
warnings.push('apiTimeout is very low - may cause timeouts for slow networks');
|
|
189
|
+
}
|
|
190
|
+
// Validate optional failSilent
|
|
191
|
+
if (cfg?.failSilent && !isBoolean(cfg?.failSilent)) {
|
|
192
|
+
errors.push('failSilent must be a boolean if provided');
|
|
193
|
+
}
|
|
194
|
+
// Validate optional maxRetries using constants
|
|
195
|
+
if (cfg?.maxRetries !== undefined && !isNumber(cfg?.maxRetries)) {
|
|
196
|
+
errors.push('maxRetries must be a number if provided');
|
|
197
|
+
}
|
|
198
|
+
else if (cfg?.maxRetries !== undefined && cfg?.maxRetries < 0) {
|
|
199
|
+
errors.push('maxRetries cannot be negative');
|
|
200
|
+
}
|
|
201
|
+
else if (cfg?.maxRetries !== undefined && cfg?.maxRetries > constants_1.VALIDATION_CONFIG.MAX_RETRY_ATTEMPTS) {
|
|
202
|
+
errors.push(`maxRetries should not exceed ${constants_1.VALIDATION_CONFIG.MAX_RETRY_ATTEMPTS}`);
|
|
203
|
+
}
|
|
204
|
+
else if (cfg?.maxRetries !== undefined && cfg?.maxRetries === 0) {
|
|
205
|
+
warnings.push('maxRetries is 0 - no retry attempts will be made');
|
|
206
|
+
}
|
|
207
|
+
if (errors.length > 0) {
|
|
208
|
+
return {
|
|
209
|
+
isValid: false,
|
|
210
|
+
errors,
|
|
211
|
+
warnings,
|
|
212
|
+
suggestions
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// Build validated config
|
|
216
|
+
const validatedConfig = {
|
|
217
|
+
reveniumApiKey: cfg?.reveniumApiKey,
|
|
218
|
+
reveniumBaseUrl: cfg?.reveniumBaseUrl,
|
|
219
|
+
anthropicApiKey: isString(cfg?.anthropicApiKey) ? cfg?.anthropicApiKey : undefined,
|
|
220
|
+
apiTimeout: isNumber(cfg?.apiTimeout) ? cfg?.apiTimeout : undefined,
|
|
221
|
+
failSilent: isBoolean(cfg?.failSilent) ? cfg?.failSilent : undefined,
|
|
222
|
+
maxRetries: isNumber(cfg?.maxRetries) ? cfg?.maxRetries : undefined
|
|
223
|
+
};
|
|
224
|
+
return {
|
|
225
|
+
isValid: true,
|
|
226
|
+
errors: [],
|
|
227
|
+
warnings,
|
|
228
|
+
config: validatedConfig,
|
|
229
|
+
suggestions: suggestions.length > 0 ? suggestions : undefined
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Validate Anthropic message creation parameters
|
|
234
|
+
*/
|
|
235
|
+
function validateAnthropicMessageParams(params) {
|
|
236
|
+
const errors = [];
|
|
237
|
+
const warnings = [];
|
|
238
|
+
if (!isObject(params)) {
|
|
239
|
+
return {
|
|
240
|
+
isValid: false,
|
|
241
|
+
errors: ['Message parameters must be an object'],
|
|
242
|
+
warnings: []
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const data = params;
|
|
246
|
+
// Validate required model field
|
|
247
|
+
if (!isString(data?.model)) {
|
|
248
|
+
errors.push('model field is required and must be a string');
|
|
249
|
+
}
|
|
250
|
+
else if (data?.model?.trim()?.length === 0) {
|
|
251
|
+
errors.push('model field cannot be empty');
|
|
252
|
+
}
|
|
253
|
+
else if (!constants_1.ANTHROPIC_PATTERNS.CLAUDE_MODEL_PATTERN.test(data?.model)) {
|
|
254
|
+
warnings.push('Model name does not contain "claude" - verify it is a valid Anthropic model');
|
|
255
|
+
}
|
|
256
|
+
// Validate required messages array
|
|
257
|
+
if (!Array.isArray(data?.messages)) {
|
|
258
|
+
errors.push('messages field is required and must be an array');
|
|
259
|
+
}
|
|
260
|
+
else if (data?.messages?.length === 0) {
|
|
261
|
+
errors.push('messages array cannot be empty');
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
// Validate message structure
|
|
265
|
+
data?.messages?.forEach((message, index) => {
|
|
266
|
+
if (!isObject(message)) {
|
|
267
|
+
errors.push(`Message at index ${index} must be an object`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const msg = message;
|
|
271
|
+
if (!isString(msg.role)) {
|
|
272
|
+
errors.push(`Message at index ${index} must have a role field`);
|
|
273
|
+
}
|
|
274
|
+
else if (!['user', 'assistant', 'system'].includes(msg.role)) {
|
|
275
|
+
warnings.push(`Message at index ${index} has unusual role: ${msg.role}`);
|
|
276
|
+
}
|
|
277
|
+
if (msg?.content && !isString(msg?.content) && !Array.isArray(msg?.content)) {
|
|
278
|
+
warnings.push(`Message at index ${index} content should be a string or array`);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// Validate optional parameters
|
|
283
|
+
if (!isNumber(data?.max_tokens)) {
|
|
284
|
+
warnings.push('max_tokens should be a number');
|
|
285
|
+
}
|
|
286
|
+
else if (data?.max_tokens <= 0) {
|
|
287
|
+
warnings.push('max_tokens should be positive');
|
|
288
|
+
}
|
|
289
|
+
else if (data?.max_tokens > constants_1.VALIDATION_CONFIG.HIGH_MAX_TOKENS_THRESHOLD) {
|
|
290
|
+
warnings.push('max_tokens is very high - verify this is intended');
|
|
291
|
+
}
|
|
292
|
+
if (!isNumber(data?.temperature)) {
|
|
293
|
+
warnings.push('temperature should be a number');
|
|
294
|
+
}
|
|
295
|
+
else if (data?.temperature < constants_1.VALIDATION_CONFIG.MIN_TEMPERATURE || data?.temperature > constants_1.VALIDATION_CONFIG.MAX_TEMPERATURE) {
|
|
296
|
+
warnings.push(`temperature should be between ${constants_1.VALIDATION_CONFIG.MIN_TEMPERATURE} and ${constants_1.VALIDATION_CONFIG.MAX_TEMPERATURE} for Anthropic models`);
|
|
297
|
+
}
|
|
298
|
+
if (data?.stream && !isBoolean(data?.stream)) {
|
|
299
|
+
warnings.push('stream should be a boolean');
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
isValid: errors.length === 0,
|
|
303
|
+
errors,
|
|
304
|
+
warnings
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=validation.js.map
|