@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,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circuit breaker pattern implementation for handling repeated failures
|
|
3
|
+
*/
|
|
4
|
+
import { CIRCUIT_BREAKER_CONFIG } from '../constants.js';
|
|
5
|
+
/**
|
|
6
|
+
* Circuit breaker states
|
|
7
|
+
*/
|
|
8
|
+
export var CircuitState;
|
|
9
|
+
(function (CircuitState) {
|
|
10
|
+
CircuitState["CLOSED"] = "CLOSED";
|
|
11
|
+
CircuitState["OPEN"] = "OPEN";
|
|
12
|
+
CircuitState["HALF_OPEN"] = "HALF_OPEN"; // Testing if service recovered
|
|
13
|
+
})(CircuitState || (CircuitState = {}));
|
|
14
|
+
/**
|
|
15
|
+
* Default circuit breaker configuration
|
|
16
|
+
*/
|
|
17
|
+
export const DEFAULT_CIRCUIT_CONFIG = {
|
|
18
|
+
failureThreshold: CIRCUIT_BREAKER_CONFIG.FAILURE_THRESHOLD,
|
|
19
|
+
recoveryTimeout: CIRCUIT_BREAKER_CONFIG.RECOVERY_TIMEOUT,
|
|
20
|
+
successThreshold: CIRCUIT_BREAKER_CONFIG.SUCCESS_THRESHOLD,
|
|
21
|
+
timeWindow: CIRCUIT_BREAKER_CONFIG.TIME_WINDOW
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Circuit breaker implementation
|
|
25
|
+
*/
|
|
26
|
+
export class CircuitBreaker {
|
|
27
|
+
constructor(config = DEFAULT_CIRCUIT_CONFIG) {
|
|
28
|
+
this.config = config;
|
|
29
|
+
this.state = CircuitState.CLOSED;
|
|
30
|
+
this.failureCount = 0;
|
|
31
|
+
this.successCount = 0;
|
|
32
|
+
this.lastFailureTime = 0;
|
|
33
|
+
this.failures = []; // Timestamps of failures
|
|
34
|
+
this.lastCleanupTime = 0;
|
|
35
|
+
this.maxFailureHistorySize = CIRCUIT_BREAKER_CONFIG.MAX_FAILURE_HISTORY_SIZE;
|
|
36
|
+
this.lastCleanupTime = Date.now();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Execute a function with circuit breaker protection
|
|
40
|
+
*/
|
|
41
|
+
async execute(fn) {
|
|
42
|
+
if (this.state === CircuitState.OPEN) {
|
|
43
|
+
if (this.shouldAttemptRecovery()) {
|
|
44
|
+
this.state = CircuitState.HALF_OPEN;
|
|
45
|
+
this.successCount = 0;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
throw new Error('Circuit breaker is OPEN - failing fast');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const result = await fn();
|
|
53
|
+
this.onSuccess();
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
this.onFailure();
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if circuit breaker allows execution
|
|
63
|
+
*/
|
|
64
|
+
canExecute() {
|
|
65
|
+
if (this.state === CircuitState.CLOSED || this.state === CircuitState.HALF_OPEN)
|
|
66
|
+
return true;
|
|
67
|
+
if (this.state === CircuitState.OPEN && this.shouldAttemptRecovery())
|
|
68
|
+
return true;
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get current circuit breaker state
|
|
73
|
+
*/
|
|
74
|
+
getState() {
|
|
75
|
+
return this.state;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get circuit breaker statistics
|
|
79
|
+
*/
|
|
80
|
+
getStats() {
|
|
81
|
+
const now = Date.now();
|
|
82
|
+
const recentFailures = this.failures.filter(timestamp => now - timestamp < this.config.timeWindow).length;
|
|
83
|
+
let timeUntilRecovery;
|
|
84
|
+
if (this.state === CircuitState.OPEN) {
|
|
85
|
+
const timeSinceLastFailure = now - this.lastFailureTime;
|
|
86
|
+
timeUntilRecovery = Math.max(0, this.config.recoveryTimeout - timeSinceLastFailure);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
state: this.state,
|
|
90
|
+
failureCount: this.failureCount,
|
|
91
|
+
successCount: this.successCount,
|
|
92
|
+
recentFailures,
|
|
93
|
+
timeUntilRecovery
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Reset circuit breaker to closed state
|
|
98
|
+
*/
|
|
99
|
+
reset() {
|
|
100
|
+
this.state = CircuitState.CLOSED;
|
|
101
|
+
this.failureCount = 0;
|
|
102
|
+
this.successCount = 0;
|
|
103
|
+
this.lastFailureTime = 0;
|
|
104
|
+
this.lastCleanupTime = Date.now();
|
|
105
|
+
this.failures = [];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Handle successful execution
|
|
109
|
+
*/
|
|
110
|
+
onSuccess() {
|
|
111
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
112
|
+
this.successCount++;
|
|
113
|
+
if (this.successCount >= this.config.successThreshold) {
|
|
114
|
+
this.state = CircuitState.CLOSED;
|
|
115
|
+
this.failureCount = 0;
|
|
116
|
+
this.failures = [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (this.state === CircuitState.CLOSED) {
|
|
120
|
+
// Reset failure count on success in closed state
|
|
121
|
+
this.failureCount = 0;
|
|
122
|
+
this.performPeriodicCleanup();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Handle failed execution
|
|
127
|
+
*/
|
|
128
|
+
onFailure() {
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
this.failureCount++;
|
|
131
|
+
this.lastFailureTime = now;
|
|
132
|
+
// Prevent unbounded growth of failures array
|
|
133
|
+
if (this.failures.length >= this.maxFailureHistorySize) {
|
|
134
|
+
this.failures = this.failures.slice(-Math.floor(this.maxFailureHistorySize / 2));
|
|
135
|
+
}
|
|
136
|
+
this.failures.push(now);
|
|
137
|
+
this.cleanupOldFailures();
|
|
138
|
+
if (this.state === CircuitState.HALF_OPEN) {
|
|
139
|
+
// Go back to open state on any failure in half-open
|
|
140
|
+
this.state = CircuitState.OPEN;
|
|
141
|
+
this.successCount = 0;
|
|
142
|
+
}
|
|
143
|
+
else if (this.state === CircuitState.CLOSED) {
|
|
144
|
+
// Check if we should open the circuit
|
|
145
|
+
const recentFailures = this.failures.filter(timestamp => now - timestamp < this.config.timeWindow).length;
|
|
146
|
+
if (recentFailures >= this.config.failureThreshold) {
|
|
147
|
+
this.state = CircuitState.OPEN;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Check if we should attempt recovery from open state
|
|
153
|
+
*/
|
|
154
|
+
shouldAttemptRecovery() {
|
|
155
|
+
const now = Date.now();
|
|
156
|
+
return now - this.lastFailureTime >= this.config.recoveryTimeout;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Remove old failure timestamps outside the time window
|
|
160
|
+
*/
|
|
161
|
+
cleanupOldFailures() {
|
|
162
|
+
const now = Date.now();
|
|
163
|
+
this.failures = this.failures.filter(timestamp => now - timestamp < this.config.timeWindow);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Perform periodic cleanup to prevent memory leaks
|
|
167
|
+
* Only runs cleanup if enough time has passed since last cleanup
|
|
168
|
+
*/
|
|
169
|
+
performPeriodicCleanup() {
|
|
170
|
+
const now = Date.now();
|
|
171
|
+
const timeSinceLastCleanup = now - this.lastCleanupTime;
|
|
172
|
+
// Use constants for cleanup thresholds
|
|
173
|
+
if (timeSinceLastCleanup > CIRCUIT_BREAKER_CONFIG.CLEANUP_INTERVAL ||
|
|
174
|
+
this.failures.length > CIRCUIT_BREAKER_CONFIG.CLEANUP_SIZE_THRESHOLD) {
|
|
175
|
+
this.cleanupOldFailures();
|
|
176
|
+
this.lastCleanupTime = now;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Global circuit breaker instance for Revenium API calls
|
|
182
|
+
*/
|
|
183
|
+
let globalCircuitBreaker = null;
|
|
184
|
+
/**
|
|
185
|
+
* Get or create the global circuit breaker instance
|
|
186
|
+
*/
|
|
187
|
+
export function getCircuitBreaker(config) {
|
|
188
|
+
if (!globalCircuitBreaker) {
|
|
189
|
+
const finalConfig = config ? { ...DEFAULT_CIRCUIT_CONFIG, ...config } : DEFAULT_CIRCUIT_CONFIG;
|
|
190
|
+
globalCircuitBreaker = new CircuitBreaker(finalConfig);
|
|
191
|
+
}
|
|
192
|
+
return globalCircuitBreaker;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Reset the global circuit breaker
|
|
196
|
+
*/
|
|
197
|
+
export function resetCircuitBreaker() {
|
|
198
|
+
if (globalCircuitBreaker) {
|
|
199
|
+
globalCircuitBreaker.reset();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Check if the global circuit breaker allows execution
|
|
204
|
+
*/
|
|
205
|
+
export function canExecuteRequest() {
|
|
206
|
+
const circuitBreaker = getCircuitBreaker();
|
|
207
|
+
return circuitBreaker.canExecute();
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Execute a function with global circuit breaker protection
|
|
211
|
+
*/
|
|
212
|
+
export async function executeWithCircuitBreaker(fn) {
|
|
213
|
+
const circuitBreaker = getCircuitBreaker();
|
|
214
|
+
return circuitBreaker.execute(fn);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get global circuit breaker statistics
|
|
218
|
+
*/
|
|
219
|
+
export function getCircuitBreakerStats() {
|
|
220
|
+
const circuitBreaker = getCircuitBreaker();
|
|
221
|
+
return circuitBreaker.getStats();
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=circuit-breaker.js.map
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error handling utilities for Anthropic middleware with structured errors and context building
|
|
3
|
+
*/
|
|
4
|
+
import { RETRY_CONFIG } from '../constants.js';
|
|
5
|
+
/**
|
|
6
|
+
* Base error class for Revenium middleware errors
|
|
7
|
+
*/
|
|
8
|
+
export class ReveniumError extends Error {
|
|
9
|
+
constructor(message, code, context) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'ReveniumError';
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.context = context;
|
|
14
|
+
// Maintain proper stack trace for where our error was thrown
|
|
15
|
+
if (Error.captureStackTrace) {
|
|
16
|
+
Error.captureStackTrace(this, ReveniumError);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Configuration-related errors
|
|
22
|
+
*/
|
|
23
|
+
export class ConfigurationError extends ReveniumError {
|
|
24
|
+
constructor(message, context) {
|
|
25
|
+
super(message, 'CONFIGURATION_ERROR', context);
|
|
26
|
+
this.name = 'ConfigurationError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Anthropic SDK patching errors
|
|
31
|
+
*/
|
|
32
|
+
export class AnthropicPatchingError extends ReveniumError {
|
|
33
|
+
constructor(message, context) {
|
|
34
|
+
super(message, 'ANTHROPIC_PATCHING_ERROR', context);
|
|
35
|
+
this.name = 'AnthropicPatchingError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Request processing errors
|
|
40
|
+
*/
|
|
41
|
+
export class RequestProcessingError extends ReveniumError {
|
|
42
|
+
constructor(message, context) {
|
|
43
|
+
super(message, 'REQUEST_PROCESSING_ERROR', context);
|
|
44
|
+
this.name = 'RequestProcessingError';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Revenium API communication errors
|
|
49
|
+
*/
|
|
50
|
+
export class ReveniumApiError extends ReveniumError {
|
|
51
|
+
constructor(message, statusCode, responseBody, context) {
|
|
52
|
+
super(message, 'REVENIUM_API_ERROR', context);
|
|
53
|
+
this.name = 'ReveniumApiError';
|
|
54
|
+
this.statusCode = statusCode;
|
|
55
|
+
this.responseBody = responseBody;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Validation errors
|
|
60
|
+
*/
|
|
61
|
+
export class ValidationError extends ReveniumError {
|
|
62
|
+
constructor(message, validationErrors, context) {
|
|
63
|
+
super(message, 'VALIDATION_ERROR', context);
|
|
64
|
+
this.name = 'ValidationError';
|
|
65
|
+
this.validationErrors = validationErrors;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Stream processing errors
|
|
70
|
+
*/
|
|
71
|
+
export class StreamProcessingError extends ReveniumError {
|
|
72
|
+
constructor(message, context) {
|
|
73
|
+
super(message, 'STREAM_PROCESSING_ERROR', context);
|
|
74
|
+
this.name = 'StreamProcessingError';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Error context builder for consistent error reporting
|
|
79
|
+
*/
|
|
80
|
+
export class ErrorContext {
|
|
81
|
+
constructor() {
|
|
82
|
+
this.context = {};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Add request ID to error context
|
|
86
|
+
*/
|
|
87
|
+
withRequestId(requestId) {
|
|
88
|
+
this.context.requestId = requestId;
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Add model information to error context
|
|
93
|
+
*/
|
|
94
|
+
withModel(model) {
|
|
95
|
+
this.context.model = model;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Add duration to error context
|
|
100
|
+
*/
|
|
101
|
+
withDuration(duration) {
|
|
102
|
+
this.context.duration = duration;
|
|
103
|
+
return this;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Add HTTP status to error context
|
|
107
|
+
*/
|
|
108
|
+
withStatus(status) {
|
|
109
|
+
this.context.status = status;
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Add custom field to error context
|
|
114
|
+
*/
|
|
115
|
+
with(key, value) {
|
|
116
|
+
this.context[key] = value;
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build the context object
|
|
121
|
+
*/
|
|
122
|
+
build() {
|
|
123
|
+
return { ...this.context };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Safe error message extraction
|
|
128
|
+
*/
|
|
129
|
+
export function getErrorMessage(error) {
|
|
130
|
+
if (error instanceof Error)
|
|
131
|
+
return error.message;
|
|
132
|
+
if (typeof error === 'string')
|
|
133
|
+
return error;
|
|
134
|
+
if (error && typeof error === 'object' && 'message' in error) {
|
|
135
|
+
const message = error.message;
|
|
136
|
+
if (typeof message === 'string')
|
|
137
|
+
return message;
|
|
138
|
+
}
|
|
139
|
+
return 'Unknown error occurred';
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Safe error stack extraction
|
|
143
|
+
*/
|
|
144
|
+
export function getErrorStack(error) {
|
|
145
|
+
if (error instanceof Error && error.stack)
|
|
146
|
+
return error.stack;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Create error context builder
|
|
151
|
+
*/
|
|
152
|
+
export function createErrorContext() {
|
|
153
|
+
return new ErrorContext();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Handle and log errors consistently
|
|
157
|
+
*/
|
|
158
|
+
export function handleError(error, logger, context) {
|
|
159
|
+
const message = getErrorMessage(error);
|
|
160
|
+
const stack = getErrorStack(error);
|
|
161
|
+
const logContext = {
|
|
162
|
+
...context,
|
|
163
|
+
error: message,
|
|
164
|
+
stack: stack
|
|
165
|
+
};
|
|
166
|
+
if (error instanceof ReveniumError) {
|
|
167
|
+
logContext.errorCode = error.code;
|
|
168
|
+
logContext.errorContext = error.context;
|
|
169
|
+
if (error instanceof ReveniumApiError) {
|
|
170
|
+
logContext.statusCode = error.statusCode;
|
|
171
|
+
logContext.responseBody = error.responseBody;
|
|
172
|
+
}
|
|
173
|
+
if (error instanceof ValidationError) {
|
|
174
|
+
logContext.validationErrors = error.validationErrors;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
logger.error('Error occurred', logContext);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Wrap async function with error handling
|
|
181
|
+
*/
|
|
182
|
+
export function withErrorHandling(fn, errorHandler) {
|
|
183
|
+
return async (...args) => {
|
|
184
|
+
try {
|
|
185
|
+
return await fn(...args);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
errorHandler(error, ...args);
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Retry function with exponential backoff
|
|
195
|
+
*/
|
|
196
|
+
export async function withRetry(fn, maxRetries = RETRY_CONFIG.BASE_DELAY / 1000, // Convert to attempts for backward compatibility
|
|
197
|
+
baseDelay = RETRY_CONFIG.BASE_DELAY) {
|
|
198
|
+
let lastError;
|
|
199
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
200
|
+
try {
|
|
201
|
+
return await fn();
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
lastError = error;
|
|
205
|
+
if (attempt === maxRetries) {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
// Exponential backoff with jitter using constants
|
|
209
|
+
const delay = Math.min(baseDelay * Math.pow(2, attempt), RETRY_CONFIG.MAX_DELAY);
|
|
210
|
+
const jitter = Math.random() * RETRY_CONFIG.JITTER_FACTOR * delay;
|
|
211
|
+
await new Promise(resolve => setTimeout(resolve, delay + jitter));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
throw lastError;
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=error-handling.js.map
|