@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,374 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Anthropic SDK wrapper with type safety and structured error handling
|
|
4
|
+
*/
|
|
5
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.patchAnthropic = patchAnthropic;
|
|
10
|
+
exports.unpatchAnthropic = unpatchAnthropic;
|
|
11
|
+
exports.isAnthropicPatched = isAnthropicPatched;
|
|
12
|
+
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
13
|
+
const config_1 = require("./config");
|
|
14
|
+
const tracking_1 = require("./tracking");
|
|
15
|
+
const validation_1 = require("./utils/validation");
|
|
16
|
+
const error_handling_1 = require("./utils/error-handling");
|
|
17
|
+
const crypto_1 = require("crypto");
|
|
18
|
+
// Global logger
|
|
19
|
+
const logger = (0, config_1.getLogger)();
|
|
20
|
+
/**
|
|
21
|
+
* Global patching context
|
|
22
|
+
*/
|
|
23
|
+
const patchingContext = {
|
|
24
|
+
originalMethods: {},
|
|
25
|
+
isPatched: false,
|
|
26
|
+
patchedInstances: new WeakSet()
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Get the Messages prototype using sophisticated prototype access
|
|
30
|
+
* Uses multiple fallback strategies to access the Anthropic Messages class prototype
|
|
31
|
+
*/
|
|
32
|
+
function getMessagesPrototype() {
|
|
33
|
+
try {
|
|
34
|
+
// Method 1: Try to access through the constructor's prototype chain
|
|
35
|
+
// Look for the Messages constructor in the Anthropic module
|
|
36
|
+
// The Anthropic SDK typically exposes internal classes through the constructor
|
|
37
|
+
if (sdk_1.default?.Messages)
|
|
38
|
+
return sdk_1.default?.Messages?.prototype;
|
|
39
|
+
// Method 2: Try to access through the constructor's static properties
|
|
40
|
+
const anthropicConstructor = sdk_1.default;
|
|
41
|
+
if (anthropicConstructor?._Messages)
|
|
42
|
+
return anthropicConstructor?._Messages?.prototype;
|
|
43
|
+
// Method 3: Create a minimal instance with the real API key if available
|
|
44
|
+
// Fallback approach when direct prototype access methods fail
|
|
45
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
46
|
+
if (!apiKey) {
|
|
47
|
+
throw new error_handling_1.AnthropicPatchingError('Unable to access Anthropic Messages prototype: No API key available and direct prototype access failed');
|
|
48
|
+
}
|
|
49
|
+
const minimalInstance = new sdk_1.default({ apiKey });
|
|
50
|
+
const messagesPrototype = Object.getPrototypeOf(minimalInstance.messages);
|
|
51
|
+
// Clean up the minimal instance immediately
|
|
52
|
+
// Note: The instance will be garbage collected, but the prototype reference remains
|
|
53
|
+
return messagesPrototype;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// If all methods fail, throw a descriptive error
|
|
57
|
+
throw new error_handling_1.AnthropicPatchingError(`Unable to access Anthropic Messages prototype: ${error instanceof Error ? error.message : String(error)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Patch Anthropic SDK by modifying prototype methods
|
|
62
|
+
*/
|
|
63
|
+
function patchAnthropic() {
|
|
64
|
+
if (patchingContext.isPatched)
|
|
65
|
+
return;
|
|
66
|
+
try {
|
|
67
|
+
// Access the Messages class prototype using sophisticated prototype access
|
|
68
|
+
const messagesPrototype = getMessagesPrototype();
|
|
69
|
+
if (!messagesPrototype)
|
|
70
|
+
throw new error_handling_1.AnthropicPatchingError('Unable to access Anthropic Messages prototype');
|
|
71
|
+
// Store original methods
|
|
72
|
+
patchingContext.originalMethods.create = messagesPrototype?.create;
|
|
73
|
+
patchingContext.originalMethods.stream = messagesPrototype?.stream;
|
|
74
|
+
if (!patchingContext.originalMethods?.create) {
|
|
75
|
+
throw new error_handling_1.AnthropicPatchingError('Unable to find original create method');
|
|
76
|
+
}
|
|
77
|
+
// Patch the create method
|
|
78
|
+
const patchedCreateFunction = function (params, options) {
|
|
79
|
+
return patchedCreateMethod.call(this, params, options);
|
|
80
|
+
};
|
|
81
|
+
messagesPrototype.create = patchedCreateFunction;
|
|
82
|
+
// Patch the stream method if it exists
|
|
83
|
+
if (patchingContext.originalMethods?.stream) {
|
|
84
|
+
messagesPrototype.stream = function (params, options) {
|
|
85
|
+
return patchedStreamMethod.call(this, params, options);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
patchingContext.isPatched = true;
|
|
89
|
+
logger.info('Anthropic SDK patched successfully');
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const errorContext = (0, error_handling_1.createErrorContext)()
|
|
93
|
+
.with('patchingAttempt', true)
|
|
94
|
+
.build();
|
|
95
|
+
(0, error_handling_1.handleError)(error, logger, errorContext);
|
|
96
|
+
if (error instanceof error_handling_1.AnthropicPatchingError)
|
|
97
|
+
throw error;
|
|
98
|
+
throw new error_handling_1.AnthropicPatchingError(`Failed to patch Anthropic SDK: ${error instanceof Error ? error.message : String(error)}`, errorContext);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Unpatch Anthropic SDK (restore original methods)
|
|
103
|
+
*/
|
|
104
|
+
function unpatchAnthropic() {
|
|
105
|
+
if (!patchingContext.isPatched)
|
|
106
|
+
return;
|
|
107
|
+
try {
|
|
108
|
+
// Access the Messages class prototype using sophisticated prototype access
|
|
109
|
+
const messagesPrototype = getMessagesPrototype();
|
|
110
|
+
if (messagesPrototype && patchingContext.originalMethods.create) {
|
|
111
|
+
messagesPrototype.create = patchingContext.originalMethods.create;
|
|
112
|
+
}
|
|
113
|
+
if (messagesPrototype && patchingContext.originalMethods.stream) {
|
|
114
|
+
messagesPrototype.stream = patchingContext.originalMethods?.stream;
|
|
115
|
+
}
|
|
116
|
+
patchingContext.isPatched = false;
|
|
117
|
+
patchingContext.originalMethods = {};
|
|
118
|
+
logger.info('Anthropic SDK unpatched successfully');
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const errorContext = (0, error_handling_1.createErrorContext)()
|
|
122
|
+
.with('unpatchingAttempt', true)
|
|
123
|
+
.build();
|
|
124
|
+
(0, error_handling_1.handleError)(error, logger, errorContext);
|
|
125
|
+
throw new error_handling_1.AnthropicPatchingError(`Failed to unpatch Anthropic SDK: ${error instanceof Error ? error.message : String(error)}`, errorContext);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if Anthropic SDK is patched
|
|
130
|
+
*/
|
|
131
|
+
function isAnthropicPatched() {
|
|
132
|
+
return patchingContext.isPatched;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Handle streaming response by collecting chunks and extracting usage data
|
|
136
|
+
*/
|
|
137
|
+
async function handleStreamingResponse(stream, context) {
|
|
138
|
+
const { requestId, model, metadata, requestTime, startTime } = context;
|
|
139
|
+
// Create a new async generator that collects chunks and tracks usage
|
|
140
|
+
async function* trackingStream() {
|
|
141
|
+
const chunks = [];
|
|
142
|
+
let firstTokenTime;
|
|
143
|
+
try {
|
|
144
|
+
for await (const chunk of stream) {
|
|
145
|
+
// Track first token time
|
|
146
|
+
if (!firstTokenTime && chunk.type === 'content_block_delta') {
|
|
147
|
+
firstTokenTime = Date.now();
|
|
148
|
+
}
|
|
149
|
+
chunks.push(chunk);
|
|
150
|
+
yield chunk;
|
|
151
|
+
}
|
|
152
|
+
// After stream completes, extract usage and track
|
|
153
|
+
const endTime = Date.now();
|
|
154
|
+
const responseTime = new Date();
|
|
155
|
+
const duration = endTime - startTime;
|
|
156
|
+
logger.debug('Stream completed, extracting usage', {
|
|
157
|
+
requestId,
|
|
158
|
+
chunkCount: chunks.length,
|
|
159
|
+
duration
|
|
160
|
+
});
|
|
161
|
+
const usage = (0, tracking_1.extractUsageFromStream)(chunks);
|
|
162
|
+
// Create tracking data
|
|
163
|
+
const trackingData = {
|
|
164
|
+
requestId,
|
|
165
|
+
model,
|
|
166
|
+
inputTokens: usage.inputTokens,
|
|
167
|
+
outputTokens: usage.outputTokens,
|
|
168
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
169
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
170
|
+
duration,
|
|
171
|
+
isStreamed: true,
|
|
172
|
+
stopReason: usage.stopReason,
|
|
173
|
+
metadata,
|
|
174
|
+
requestTime,
|
|
175
|
+
responseTime
|
|
176
|
+
};
|
|
177
|
+
// Track usage asynchronously
|
|
178
|
+
(0, tracking_1.trackUsageAsync)(trackingData);
|
|
179
|
+
logger.debug('Anthropic streaming request completed successfully', {
|
|
180
|
+
requestId,
|
|
181
|
+
model,
|
|
182
|
+
inputTokens: usage.inputTokens,
|
|
183
|
+
outputTokens: usage.outputTokens,
|
|
184
|
+
duration
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
logger.error('Error processing streaming response', {
|
|
189
|
+
requestId,
|
|
190
|
+
error: error instanceof Error ? error.message : String(error)
|
|
191
|
+
});
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return trackingStream();
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Patched create method implementation
|
|
199
|
+
*/
|
|
200
|
+
async function patchedCreateMethod(params, options) {
|
|
201
|
+
const requestId = (0, crypto_1.randomUUID)();
|
|
202
|
+
const startTime = Date.now();
|
|
203
|
+
const requestTime = new Date();
|
|
204
|
+
logger.debug('Intercepted Anthropic messages.create call', {
|
|
205
|
+
requestId,
|
|
206
|
+
model: params.model,
|
|
207
|
+
hasMetadata: !!params.usageMetadata,
|
|
208
|
+
isStreaming: !!params.stream
|
|
209
|
+
});
|
|
210
|
+
// Validate parameters
|
|
211
|
+
const validation = (0, validation_1.validateAnthropicMessageParams)(params);
|
|
212
|
+
if (!validation.isValid) {
|
|
213
|
+
logger.warn('Invalid Anthropic parameters detected', {
|
|
214
|
+
requestId,
|
|
215
|
+
errors: validation.errors,
|
|
216
|
+
warnings: validation.warnings
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
// Extract and validate metadata
|
|
220
|
+
const metadata = (0, validation_1.validateUsageMetadata)(params.usageMetadata || {});
|
|
221
|
+
// Remove usageMetadata from params before calling original method
|
|
222
|
+
const { usageMetadata, ...cleanParams } = params;
|
|
223
|
+
try {
|
|
224
|
+
// Call original method
|
|
225
|
+
const originalCreate = patchingContext.originalMethods.create;
|
|
226
|
+
if (!originalCreate)
|
|
227
|
+
throw new error_handling_1.RequestProcessingError('Original create method not available');
|
|
228
|
+
const response = await originalCreate.call(this, cleanParams, options);
|
|
229
|
+
// Check if this is a streaming response
|
|
230
|
+
const isStreaming = !!params.stream;
|
|
231
|
+
if (!isStreaming) {
|
|
232
|
+
const endTime = Date.now();
|
|
233
|
+
const duration = endTime - startTime;
|
|
234
|
+
const responseTime = new Date();
|
|
235
|
+
// Extract usage information
|
|
236
|
+
const usage = (0, tracking_1.extractUsageFromResponse)(response);
|
|
237
|
+
// Create tracking data
|
|
238
|
+
const trackingData = {
|
|
239
|
+
requestId,
|
|
240
|
+
model: params.model,
|
|
241
|
+
inputTokens: usage.inputTokens,
|
|
242
|
+
outputTokens: usage.outputTokens,
|
|
243
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
244
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
245
|
+
duration,
|
|
246
|
+
isStreamed: false,
|
|
247
|
+
stopReason: usage.stopReason,
|
|
248
|
+
metadata,
|
|
249
|
+
requestTime,
|
|
250
|
+
responseTime
|
|
251
|
+
};
|
|
252
|
+
// Track usage asynchronously
|
|
253
|
+
(0, tracking_1.trackUsageAsync)(trackingData);
|
|
254
|
+
logger.debug('Anthropic request completed successfully', {
|
|
255
|
+
requestId,
|
|
256
|
+
model: params.model,
|
|
257
|
+
inputTokens: usage.inputTokens,
|
|
258
|
+
outputTokens: usage.outputTokens,
|
|
259
|
+
duration
|
|
260
|
+
});
|
|
261
|
+
return response;
|
|
262
|
+
}
|
|
263
|
+
// Handle streaming response - need to collect chunks and extract usage
|
|
264
|
+
return handleStreamingResponse(response, {
|
|
265
|
+
requestId,
|
|
266
|
+
model: params.model,
|
|
267
|
+
metadata,
|
|
268
|
+
requestTime,
|
|
269
|
+
startTime
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
const endTime = Date.now();
|
|
274
|
+
const duration = endTime - startTime;
|
|
275
|
+
const errorContext = (0, error_handling_1.createErrorContext)()
|
|
276
|
+
.withRequestId(requestId)
|
|
277
|
+
.withModel(params.model)
|
|
278
|
+
.withDuration(duration)
|
|
279
|
+
.build();
|
|
280
|
+
(0, error_handling_1.handleError)(error, logger, errorContext);
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Patched stream method implementation
|
|
286
|
+
*/
|
|
287
|
+
async function* patchedStreamMethod(params, options) {
|
|
288
|
+
const requestId = (0, crypto_1.randomUUID)();
|
|
289
|
+
const startTime = Date.now();
|
|
290
|
+
const requestTime = new Date();
|
|
291
|
+
const responseTime = new Date();
|
|
292
|
+
const chunks = [];
|
|
293
|
+
let firstTokenTime;
|
|
294
|
+
logger.debug('Intercepted Anthropic messages.stream call', {
|
|
295
|
+
requestId,
|
|
296
|
+
model: params.model,
|
|
297
|
+
hasMetadata: !!params.usageMetadata
|
|
298
|
+
});
|
|
299
|
+
// Validate parameters
|
|
300
|
+
const validation = (0, validation_1.validateAnthropicMessageParams)(params);
|
|
301
|
+
if (!validation.isValid) {
|
|
302
|
+
logger.warn('Invalid Anthropic streaming parameters detected', {
|
|
303
|
+
requestId,
|
|
304
|
+
errors: validation.errors,
|
|
305
|
+
warnings: validation.warnings
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
// Extract and validate metadata
|
|
309
|
+
const metadata = (0, validation_1.validateUsageMetadata)(params.usageMetadata || {});
|
|
310
|
+
// Remove usageMetadata from params before calling original method
|
|
311
|
+
const { usageMetadata, ...cleanParams } = params;
|
|
312
|
+
try {
|
|
313
|
+
// Call original stream method
|
|
314
|
+
const originalStream = patchingContext.originalMethods?.stream;
|
|
315
|
+
if (!originalStream) {
|
|
316
|
+
throw new error_handling_1.StreamProcessingError('Original stream method not available');
|
|
317
|
+
}
|
|
318
|
+
const stream = originalStream.call(this, cleanParams, options);
|
|
319
|
+
for await (const chunk of stream) {
|
|
320
|
+
// Track first token time
|
|
321
|
+
if (!firstTokenTime && chunk.type === 'content_block_delta') {
|
|
322
|
+
firstTokenTime = Date.now();
|
|
323
|
+
}
|
|
324
|
+
chunks.push(chunk);
|
|
325
|
+
yield chunk;
|
|
326
|
+
}
|
|
327
|
+
const endTime = Date.now();
|
|
328
|
+
const duration = endTime - startTime;
|
|
329
|
+
const timeToFirstToken = firstTokenTime ? firstTokenTime - startTime : undefined;
|
|
330
|
+
// Extract usage information from all chunks
|
|
331
|
+
const usage = (0, tracking_1.extractUsageFromStream)(chunks);
|
|
332
|
+
// Create tracking data
|
|
333
|
+
const trackingData = {
|
|
334
|
+
requestId,
|
|
335
|
+
model: params.model,
|
|
336
|
+
inputTokens: usage.inputTokens,
|
|
337
|
+
outputTokens: usage.outputTokens,
|
|
338
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
339
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
340
|
+
duration,
|
|
341
|
+
isStreamed: true,
|
|
342
|
+
stopReason: usage.stopReason,
|
|
343
|
+
metadata,
|
|
344
|
+
requestTime,
|
|
345
|
+
responseTime,
|
|
346
|
+
timeToFirstToken
|
|
347
|
+
};
|
|
348
|
+
// Track usage asynchronously
|
|
349
|
+
(0, tracking_1.trackUsageAsync)(trackingData);
|
|
350
|
+
logger.debug('Anthropic streaming request completed successfully', {
|
|
351
|
+
requestId,
|
|
352
|
+
model: params.model,
|
|
353
|
+
inputTokens: usage.inputTokens,
|
|
354
|
+
outputTokens: usage.outputTokens,
|
|
355
|
+
duration,
|
|
356
|
+
timeToFirstToken,
|
|
357
|
+
chunkCount: chunks.length
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
const endTime = Date.now();
|
|
362
|
+
const duration = endTime - startTime;
|
|
363
|
+
const errorContext = (0, error_handling_1.createErrorContext)()
|
|
364
|
+
.withRequestId(requestId)
|
|
365
|
+
.withModel(params.model)
|
|
366
|
+
.withDuration(duration)
|
|
367
|
+
.with('isStreaming', true)
|
|
368
|
+
.with('chunkCount', chunks.length)
|
|
369
|
+
.build();
|
|
370
|
+
(0, error_handling_1.handleError)(error, logger, errorContext);
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
//# sourceMappingURL=wrapper.js.map
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { validateReveniumConfig } from './utils/validation.js';
|
|
2
|
+
import { DEFAULT_CONFIG, ENV_VARS, LOGGING_CONFIG } from './constants.js';
|
|
3
|
+
/**
|
|
4
|
+
* Simple console logger implementation for Anthropic middleware
|
|
5
|
+
*/
|
|
6
|
+
class ConsoleLogger {
|
|
7
|
+
isDebugEnabled() {
|
|
8
|
+
return process.env[ENV_VARS.DEBUG] === 'true';
|
|
9
|
+
}
|
|
10
|
+
formatMessage(level, message, context) {
|
|
11
|
+
const timestamp = new Date().toISOString();
|
|
12
|
+
const prefix = `[${LOGGING_CONFIG.MIDDLEWARE_NAME}${level === 'DEBUG' ? ' Debug' : ''}]`;
|
|
13
|
+
const contextStr = context ? ` ${JSON.stringify(context)}` : '';
|
|
14
|
+
return `${timestamp} ${prefix} ${message}${contextStr}`;
|
|
15
|
+
}
|
|
16
|
+
debug(message, context) {
|
|
17
|
+
if (this.isDebugEnabled()) {
|
|
18
|
+
console.debug(this.formatMessage('DEBUG', message, context));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
info(message, context) {
|
|
22
|
+
console.info(this.formatMessage('INFO', message, context));
|
|
23
|
+
}
|
|
24
|
+
warn(message, context) {
|
|
25
|
+
console.warn(this.formatMessage('WARN', message, context));
|
|
26
|
+
}
|
|
27
|
+
error(message, context) {
|
|
28
|
+
console.error(this.formatMessage('ERROR', message, context));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Default console logger implementation
|
|
33
|
+
*/
|
|
34
|
+
export const defaultLogger = new ConsoleLogger();
|
|
35
|
+
/**
|
|
36
|
+
* Load configuration from environment variables
|
|
37
|
+
*/
|
|
38
|
+
function loadConfigFromEnvironment() {
|
|
39
|
+
const env = {
|
|
40
|
+
reveniumApiKey: process.env[ENV_VARS.REVENIUM_API_KEY],
|
|
41
|
+
reveniumBaseUrl: process.env[ENV_VARS.REVENIUM_BASE_URL],
|
|
42
|
+
anthropicApiKey: process.env[ENV_VARS.ANTHROPIC_API_KEY],
|
|
43
|
+
debug: process.env[ENV_VARS.DEBUG] === 'true',
|
|
44
|
+
logLevel: process.env[ENV_VARS.LOG_LEVEL],
|
|
45
|
+
apiTimeout: process.env[ENV_VARS.API_TIMEOUT],
|
|
46
|
+
failSilent: process.env[ENV_VARS.FAIL_SILENT],
|
|
47
|
+
maxRetries: process.env[ENV_VARS.MAX_RETRIES]
|
|
48
|
+
};
|
|
49
|
+
return env;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Convert environment config to Revenium config
|
|
53
|
+
*/
|
|
54
|
+
function createConfigFromEnvironment(env) {
|
|
55
|
+
if (!env.reveniumApiKey) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const apiTimeout = env.apiTimeout ? parseInt(env.apiTimeout, 10) : undefined;
|
|
59
|
+
const failSilent = env.failSilent !== 'false'; // Default to true
|
|
60
|
+
const maxRetries = env.maxRetries ? parseInt(env.maxRetries, 10) : undefined;
|
|
61
|
+
return {
|
|
62
|
+
reveniumApiKey: env.reveniumApiKey,
|
|
63
|
+
reveniumBaseUrl: env.reveniumBaseUrl || DEFAULT_CONFIG.REVENIUM_BASE_URL,
|
|
64
|
+
anthropicApiKey: env.anthropicApiKey,
|
|
65
|
+
apiTimeout,
|
|
66
|
+
failSilent,
|
|
67
|
+
maxRetries
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Validate Revenium configuration with enhanced error reporting
|
|
72
|
+
*/
|
|
73
|
+
export function validateConfig(config) {
|
|
74
|
+
const validation = validateReveniumConfig(config);
|
|
75
|
+
if (!validation.isValid) {
|
|
76
|
+
// Log detailed validation errors
|
|
77
|
+
getLogger().error('Configuration validation failed', {
|
|
78
|
+
errors: validation.errors,
|
|
79
|
+
warnings: validation.warnings,
|
|
80
|
+
suggestions: validation.suggestions
|
|
81
|
+
});
|
|
82
|
+
// Create detailed error message
|
|
83
|
+
let errorMessage = 'Configuration validation failed:\n';
|
|
84
|
+
validation.errors.forEach((error, index) => {
|
|
85
|
+
errorMessage += ` ${index + 1}. ${error}\n`;
|
|
86
|
+
});
|
|
87
|
+
if (validation.suggestions && validation.suggestions.length > 0) {
|
|
88
|
+
errorMessage += '\nSuggestions:\n';
|
|
89
|
+
validation.suggestions.forEach((suggestion) => {
|
|
90
|
+
errorMessage += ` • ${suggestion}\n`;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
throw new Error(errorMessage.trim());
|
|
94
|
+
}
|
|
95
|
+
// Log warnings if any
|
|
96
|
+
if (validation.warnings && validation.warnings.length > 0) {
|
|
97
|
+
getLogger().warn('Configuration warnings', {
|
|
98
|
+
warnings: validation.warnings
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Global configuration instance
|
|
104
|
+
*/
|
|
105
|
+
let globalConfig = null;
|
|
106
|
+
let globalLogger = defaultLogger;
|
|
107
|
+
/**
|
|
108
|
+
* Get the current global configuration
|
|
109
|
+
*/
|
|
110
|
+
export function getConfig() {
|
|
111
|
+
return globalConfig;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Set the global configuration
|
|
115
|
+
*/
|
|
116
|
+
export function setConfig(config) {
|
|
117
|
+
validateConfig(config);
|
|
118
|
+
globalConfig = config;
|
|
119
|
+
globalLogger.debug('Revenium configuration updated', {
|
|
120
|
+
baseUrl: config.reveniumBaseUrl,
|
|
121
|
+
hasApiKey: !!config.reveniumApiKey,
|
|
122
|
+
hasAnthropicKey: !!config.anthropicApiKey
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get the current logger
|
|
127
|
+
*/
|
|
128
|
+
export function getLogger() {
|
|
129
|
+
return globalLogger;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Set a custom logger
|
|
133
|
+
*/
|
|
134
|
+
export function setLogger(logger) {
|
|
135
|
+
globalLogger = logger;
|
|
136
|
+
globalLogger.debug('Custom logger set for Revenium middleware');
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Initialize configuration from environment variables
|
|
140
|
+
*/
|
|
141
|
+
export function initializeConfig() {
|
|
142
|
+
const env = loadConfigFromEnvironment();
|
|
143
|
+
const config = createConfigFromEnvironment(env);
|
|
144
|
+
if (config) {
|
|
145
|
+
try {
|
|
146
|
+
setConfig(config);
|
|
147
|
+
globalLogger.debug('Revenium middleware initialized from environment variables');
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
globalLogger.error('Failed to initialize Revenium configuration', {
|
|
152
|
+
error: error instanceof Error ? error.message : String(error)
|
|
153
|
+
});
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Log what's missing for easier debugging
|
|
158
|
+
if (!env.reveniumApiKey) {
|
|
159
|
+
globalLogger.warn(`Revenium middleware not initialized. Missing ${ENV_VARS.REVENIUM_API_KEY} environment variable`);
|
|
160
|
+
globalLogger.info(`Set ${ENV_VARS.REVENIUM_API_KEY} to enable automatic tracking`);
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get configuration status
|
|
166
|
+
*/
|
|
167
|
+
export function getConfigStatus() {
|
|
168
|
+
if (!globalConfig) {
|
|
169
|
+
return {
|
|
170
|
+
hasConfig: false,
|
|
171
|
+
hasApiKey: false,
|
|
172
|
+
hasAnthropicKey: false,
|
|
173
|
+
baseUrl: ''
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
hasConfig: true,
|
|
178
|
+
hasApiKey: !!globalConfig.reveniumApiKey,
|
|
179
|
+
hasAnthropicKey: !!globalConfig.anthropicApiKey,
|
|
180
|
+
baseUrl: globalConfig.reveniumBaseUrl
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Validate current configuration
|
|
185
|
+
*/
|
|
186
|
+
export function validateCurrentConfig() {
|
|
187
|
+
if (!globalConfig)
|
|
188
|
+
return false;
|
|
189
|
+
try {
|
|
190
|
+
const validation = validateReveniumConfig(globalConfig);
|
|
191
|
+
return validation.isValid;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=config.js.map
|