@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.
@@ -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