@revenium/anthropic 1.0.8 → 1.0.9

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.
@@ -23,7 +23,7 @@ const logger = (0, config_1.getLogger)();
23
23
  const patchingContext = {
24
24
  originalMethods: {},
25
25
  isPatched: false,
26
- patchedInstances: new WeakSet()
26
+ patchedInstances: new WeakSet(),
27
27
  };
28
28
  /**
29
29
  * Get the Messages prototype using sophisticated prototype access
@@ -46,8 +46,8 @@ function getMessagesPrototype() {
46
46
  const config = (0, config_1.getConfig)();
47
47
  const apiKey = config?.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY;
48
48
  if (!apiKey) {
49
- throw new error_handling_1.AnthropicPatchingError('Unable to access Anthropic Messages prototype: No API key available and direct prototype access failed. ' +
50
- 'Provide ANTHROPIC_API_KEY environment variable or pass anthropicApiKey in config.');
49
+ throw new error_handling_1.AnthropicPatchingError("Unable to access Anthropic Messages prototype: No API key available and direct prototype access failed. " +
50
+ "Provide ANTHROPIC_API_KEY environment variable or pass anthropicApiKey in config.");
51
51
  }
52
52
  const minimalInstance = new sdk_1.default({ apiKey });
53
53
  const messagesPrototype = Object.getPrototypeOf(minimalInstance.messages);
@@ -65,19 +65,19 @@ function getMessagesPrototype() {
65
65
  */
66
66
  function patchAnthropic() {
67
67
  if (patchingContext.isPatched) {
68
- logger.debug('Anthropic SDK already patched, skipping duplicate initialization');
68
+ logger.debug("Anthropic SDK already patched, skipping duplicate initialization");
69
69
  return;
70
70
  }
71
71
  try {
72
72
  // Access the Messages class prototype using sophisticated prototype access
73
73
  const messagesPrototype = getMessagesPrototype();
74
74
  if (!messagesPrototype)
75
- throw new error_handling_1.AnthropicPatchingError('Unable to access Anthropic Messages prototype');
75
+ throw new error_handling_1.AnthropicPatchingError("Unable to access Anthropic Messages prototype");
76
76
  // Store original methods
77
77
  patchingContext.originalMethods.create = messagesPrototype?.create;
78
78
  patchingContext.originalMethods.stream = messagesPrototype?.stream;
79
79
  if (!patchingContext.originalMethods?.create) {
80
- throw new error_handling_1.AnthropicPatchingError('Unable to find original create method');
80
+ throw new error_handling_1.AnthropicPatchingError("Unable to find original create method");
81
81
  }
82
82
  // Patch the create method
83
83
  const patchedCreateFunction = function (params, options) {
@@ -91,11 +91,11 @@ function patchAnthropic() {
91
91
  };
92
92
  }
93
93
  patchingContext.isPatched = true;
94
- logger.info('Anthropic SDK patched successfully');
94
+ logger.info("Anthropic SDK patched successfully");
95
95
  }
96
96
  catch (error) {
97
97
  const errorContext = (0, error_handling_1.createErrorContext)()
98
- .with('patchingAttempt', true)
98
+ .with("patchingAttempt", true)
99
99
  .build();
100
100
  (0, error_handling_1.handleError)(error, logger, errorContext);
101
101
  if (error instanceof error_handling_1.AnthropicPatchingError)
@@ -120,11 +120,11 @@ function unpatchAnthropic() {
120
120
  }
121
121
  patchingContext.isPatched = false;
122
122
  patchingContext.originalMethods = {};
123
- logger.info('Anthropic SDK unpatched successfully');
123
+ logger.info("Anthropic SDK unpatched successfully");
124
124
  }
125
125
  catch (error) {
126
126
  const errorContext = (0, error_handling_1.createErrorContext)()
127
- .with('unpatchingAttempt', true)
127
+ .with("unpatchingAttempt", true)
128
128
  .build();
129
129
  (0, error_handling_1.handleError)(error, logger, errorContext);
130
130
  throw new error_handling_1.AnthropicPatchingError(`Failed to unpatch Anthropic SDK: ${error instanceof Error ? error.message : String(error)}`, errorContext);
@@ -140,7 +140,7 @@ function isAnthropicPatched() {
140
140
  * Handle streaming response by collecting chunks and extracting usage data
141
141
  */
142
142
  async function handleStreamingResponse(stream, context) {
143
- const { requestId, model, metadata, requestTime, startTime } = context;
143
+ const { requestId, model, metadata, requestTime, startTime, requestBody } = context;
144
144
  // Create a new async generator that collects chunks and tracks usage
145
145
  async function* trackingStream() {
146
146
  const chunks = [];
@@ -148,7 +148,7 @@ async function handleStreamingResponse(stream, context) {
148
148
  try {
149
149
  for await (const chunk of stream) {
150
150
  // Track first token time
151
- if (!firstTokenTime && chunk.type === 'content_block_delta') {
151
+ if (!firstTokenTime && chunk.type === "content_block_delta") {
152
152
  firstTokenTime = Date.now();
153
153
  }
154
154
  chunks.push(chunk);
@@ -158,12 +158,14 @@ async function handleStreamingResponse(stream, context) {
158
158
  const endTime = Date.now();
159
159
  const responseTime = new Date();
160
160
  const duration = endTime - startTime;
161
- const timeToFirstToken = firstTokenTime ? firstTokenTime - startTime : undefined;
162
- logger.debug('Stream completed, extracting usage', {
161
+ const timeToFirstToken = firstTokenTime
162
+ ? firstTokenTime - startTime
163
+ : undefined;
164
+ logger.debug("Stream completed, extracting usage", {
163
165
  requestId,
164
166
  chunkCount: chunks.length,
165
167
  duration,
166
- timeToFirstToken
168
+ timeToFirstToken,
167
169
  });
168
170
  const usage = (0, tracking_1.extractUsageFromStream)(chunks);
169
171
  // Create tracking data
@@ -180,22 +182,23 @@ async function handleStreamingResponse(stream, context) {
180
182
  metadata,
181
183
  requestTime,
182
184
  responseTime,
183
- timeToFirstToken
185
+ timeToFirstToken,
186
+ requestBody: requestBody,
184
187
  };
185
188
  // Track usage asynchronously
186
189
  (0, tracking_1.trackUsageAsync)(trackingData);
187
- logger.debug('Anthropic streaming request completed successfully', {
190
+ logger.debug("Anthropic streaming request completed successfully", {
188
191
  requestId,
189
192
  model,
190
193
  inputTokens: usage.inputTokens,
191
194
  outputTokens: usage.outputTokens,
192
- duration
195
+ duration,
193
196
  });
194
197
  }
195
198
  catch (error) {
196
- logger.error('Error processing streaming response', {
199
+ logger.error("Error processing streaming response", {
197
200
  requestId,
198
- error: error instanceof Error ? error.message : String(error)
201
+ error: error instanceof Error ? error.message : String(error),
199
202
  });
200
203
  throw error;
201
204
  }
@@ -209,19 +212,19 @@ async function patchedCreateMethod(params, options) {
209
212
  const requestId = (0, crypto_1.randomUUID)();
210
213
  const startTime = Date.now();
211
214
  const requestTime = new Date();
212
- logger.debug('Intercepted Anthropic messages.create call', {
215
+ logger.debug("Intercepted Anthropic messages.create call", {
213
216
  requestId,
214
217
  model: params.model,
215
218
  hasMetadata: !!params.usageMetadata,
216
- isStreaming: !!params.stream
219
+ isStreaming: !!params.stream,
217
220
  });
218
221
  // Validate parameters
219
222
  const validation = (0, validation_1.validateAnthropicMessageParams)(params);
220
223
  if (!validation.isValid) {
221
- logger.warn('Invalid Anthropic parameters detected', {
224
+ logger.warn("Invalid Anthropic parameters detected", {
222
225
  requestId,
223
226
  errors: validation.errors,
224
- warnings: validation.warnings
227
+ warnings: validation.warnings,
225
228
  });
226
229
  }
227
230
  // Extract and validate metadata
@@ -232,7 +235,7 @@ async function patchedCreateMethod(params, options) {
232
235
  // Call original method
233
236
  const originalCreate = patchingContext.originalMethods.create;
234
237
  if (!originalCreate)
235
- throw new error_handling_1.RequestProcessingError('Original create method not available');
238
+ throw new error_handling_1.RequestProcessingError("Original create method not available");
236
239
  const response = await originalCreate.call(this, cleanParams, options);
237
240
  // Check if this is a streaming response
238
241
  const isStreaming = !!params.stream;
@@ -255,16 +258,17 @@ async function patchedCreateMethod(params, options) {
255
258
  stopReason: usage.stopReason,
256
259
  metadata,
257
260
  requestTime,
258
- responseTime
261
+ responseTime,
262
+ requestBody: params,
259
263
  };
260
264
  // Track usage asynchronously
261
265
  (0, tracking_1.trackUsageAsync)(trackingData);
262
- logger.debug('Anthropic request completed successfully', {
266
+ logger.debug("Anthropic request completed successfully", {
263
267
  requestId,
264
268
  model: params.model,
265
269
  inputTokens: usage.inputTokens,
266
270
  outputTokens: usage.outputTokens,
267
- duration
271
+ duration,
268
272
  });
269
273
  return response;
270
274
  }
@@ -274,7 +278,8 @@ async function patchedCreateMethod(params, options) {
274
278
  model: params.model,
275
279
  metadata,
276
280
  requestTime,
277
- startTime
281
+ startTime,
282
+ requestBody: params,
278
283
  });
279
284
  }
280
285
  catch (error) {
@@ -299,18 +304,18 @@ async function* patchedStreamMethod(params, options) {
299
304
  const responseTime = new Date();
300
305
  const chunks = [];
301
306
  let firstTokenTime;
302
- logger.debug('Intercepted Anthropic messages.stream call', {
307
+ logger.debug("Intercepted Anthropic messages.stream call", {
303
308
  requestId,
304
309
  model: params.model,
305
- hasMetadata: !!params.usageMetadata
310
+ hasMetadata: !!params.usageMetadata,
306
311
  });
307
312
  // Validate parameters
308
313
  const validation = (0, validation_1.validateAnthropicMessageParams)(params);
309
314
  if (!validation.isValid) {
310
- logger.warn('Invalid Anthropic streaming parameters detected', {
315
+ logger.warn("Invalid Anthropic streaming parameters detected", {
311
316
  requestId,
312
317
  errors: validation.errors,
313
- warnings: validation.warnings
318
+ warnings: validation.warnings,
314
319
  });
315
320
  }
316
321
  // Extract and validate metadata
@@ -321,12 +326,12 @@ async function* patchedStreamMethod(params, options) {
321
326
  // Call original stream method
322
327
  const originalStream = patchingContext.originalMethods?.stream;
323
328
  if (!originalStream) {
324
- throw new error_handling_1.StreamProcessingError('Original stream method not available');
329
+ throw new error_handling_1.StreamProcessingError("Original stream method not available");
325
330
  }
326
331
  const stream = originalStream.call(this, cleanParams, options);
327
332
  for await (const chunk of stream) {
328
333
  // Track first token time
329
- if (!firstTokenTime && chunk.type === 'content_block_delta') {
334
+ if (!firstTokenTime && chunk.type === "content_block_delta") {
330
335
  firstTokenTime = Date.now();
331
336
  }
332
337
  chunks.push(chunk);
@@ -334,7 +339,9 @@ async function* patchedStreamMethod(params, options) {
334
339
  }
335
340
  const endTime = Date.now();
336
341
  const duration = endTime - startTime;
337
- const timeToFirstToken = firstTokenTime ? firstTokenTime - startTime : undefined;
342
+ const timeToFirstToken = firstTokenTime
343
+ ? firstTokenTime - startTime
344
+ : undefined;
338
345
  // Extract usage information from all chunks
339
346
  const usage = (0, tracking_1.extractUsageFromStream)(chunks);
340
347
  // Create tracking data
@@ -351,18 +358,19 @@ async function* patchedStreamMethod(params, options) {
351
358
  metadata,
352
359
  requestTime,
353
360
  responseTime,
354
- timeToFirstToken
361
+ timeToFirstToken,
362
+ requestBody: params,
355
363
  };
356
364
  // Track usage asynchronously
357
365
  (0, tracking_1.trackUsageAsync)(trackingData);
358
- logger.debug('Anthropic streaming request completed successfully', {
366
+ logger.debug("Anthropic streaming request completed successfully", {
359
367
  requestId,
360
368
  model: params.model,
361
369
  inputTokens: usage.inputTokens,
362
370
  outputTokens: usage.outputTokens,
363
371
  duration,
364
372
  timeToFirstToken,
365
- chunkCount: chunks.length
373
+ chunkCount: chunks.length,
366
374
  });
367
375
  }
368
376
  catch (error) {
@@ -372,8 +380,8 @@ async function* patchedStreamMethod(params, options) {
372
380
  .withRequestId(requestId)
373
381
  .withModel(params.model)
374
382
  .withDuration(duration)
375
- .with('isStreaming', true)
376
- .with('chunkCount', chunks.length)
383
+ .with("isStreaming", true)
384
+ .with("chunkCount", chunks.length)
377
385
  .build();
378
386
  (0, error_handling_1.handleError)(error, logger, errorContext);
379
387
  throw error;
@@ -44,10 +44,27 @@ function loadConfigFromEnvironment() {
44
44
  logLevel: process.env[ENV_VARS.LOG_LEVEL],
45
45
  apiTimeout: process.env[ENV_VARS.API_TIMEOUT],
46
46
  failSilent: process.env[ENV_VARS.FAIL_SILENT],
47
- maxRetries: process.env[ENV_VARS.MAX_RETRIES]
47
+ maxRetries: process.env[ENV_VARS.MAX_RETRIES],
48
+ printSummary: process.env[ENV_VARS.PRINT_SUMMARY],
49
+ teamId: process.env[ENV_VARS.TEAM_ID]
48
50
  };
49
51
  return env;
50
52
  }
53
+ /**
54
+ * Parse printSummary environment variable value
55
+ */
56
+ function parsePrintSummary(value) {
57
+ if (!value)
58
+ return undefined;
59
+ const lowerValue = value.toLowerCase();
60
+ if (lowerValue === 'true' || lowerValue === 'human')
61
+ return 'human';
62
+ if (lowerValue === 'json')
63
+ return 'json';
64
+ if (lowerValue === 'false')
65
+ return false;
66
+ return undefined;
67
+ }
51
68
  /**
52
69
  * Convert environment config to Revenium config
53
70
  */
@@ -58,13 +75,16 @@ function createConfigFromEnvironment(env) {
58
75
  const apiTimeout = env.apiTimeout ? parseInt(env.apiTimeout, 10) : undefined;
59
76
  const failSilent = env.failSilent !== 'false'; // Default to true
60
77
  const maxRetries = env.maxRetries ? parseInt(env.maxRetries, 10) : undefined;
78
+ const printSummary = parsePrintSummary(env.printSummary);
61
79
  return {
62
80
  reveniumApiKey: env.reveniumApiKey,
63
81
  reveniumBaseUrl: env.reveniumBaseUrl || DEFAULT_CONFIG.REVENIUM_BASE_URL,
64
82
  anthropicApiKey: env.anthropicApiKey,
65
83
  apiTimeout,
66
84
  failSilent,
67
- maxRetries
85
+ maxRetries,
86
+ printSummary,
87
+ teamId: env.teamId?.trim()
68
88
  };
69
89
  }
70
90
  /**
@@ -112,14 +132,42 @@ export function getConfig() {
112
132
  }
113
133
  /**
114
134
  * Set the global configuration
135
+ * Uses the normalized config from validation (with defaults applied and fields trimmed)
115
136
  */
116
137
  export function setConfig(config) {
117
- validateConfig(config);
118
- globalConfig = config;
138
+ const validation = validateReveniumConfig(config);
139
+ if (!validation.isValid) {
140
+ // Log detailed validation errors
141
+ getLogger().error('Configuration validation failed', {
142
+ errors: validation.errors,
143
+ warnings: validation.warnings,
144
+ suggestions: validation.suggestions
145
+ });
146
+ // Create detailed error message
147
+ let errorMessage = 'Configuration validation failed:\n';
148
+ validation.errors.forEach((error, index) => {
149
+ errorMessage += ` ${index + 1}. ${error}\n`;
150
+ });
151
+ if (validation.suggestions && validation.suggestions.length > 0) {
152
+ errorMessage += '\nSuggestions:\n';
153
+ validation.suggestions.forEach((suggestion) => {
154
+ errorMessage += ` • ${suggestion}\n`;
155
+ });
156
+ }
157
+ throw new Error(errorMessage.trim());
158
+ }
159
+ // Log warnings if any
160
+ if (validation.warnings && validation.warnings.length > 0) {
161
+ getLogger().warn('Configuration warnings', {
162
+ warnings: validation.warnings
163
+ });
164
+ }
165
+ // Use the normalized config from validation (with defaults applied and fields trimmed)
166
+ globalConfig = validation.config;
119
167
  globalLogger.debug('Revenium configuration updated', {
120
- baseUrl: config.reveniumBaseUrl,
121
- hasApiKey: !!config.reveniumApiKey,
122
- hasAnthropicKey: !!config.anthropicApiKey
168
+ baseUrl: globalConfig.reveniumBaseUrl,
169
+ hasApiKey: !!globalConfig.reveniumApiKey,
170
+ hasAnthropicKey: !!globalConfig.anthropicApiKey
123
171
  });
124
172
  }
125
173
  /**
@@ -7,7 +7,7 @@
7
7
  */
8
8
  export const DEFAULT_CONFIG = {
9
9
  /** Default Revenium API base URL */
10
- REVENIUM_BASE_URL: 'https://api.revenium.ai',
10
+ REVENIUM_BASE_URL: "https://api.revenium.ai",
11
11
  /** Default API timeout in milliseconds */
12
12
  API_TIMEOUT: 5000,
13
13
  /** Default maximum retries for failed API calls */
@@ -60,9 +60,9 @@ export const VALIDATION_CONFIG = {
60
60
  /** Minimum API key length */
61
61
  MIN_API_KEY_LENGTH: 20,
62
62
  /** Required API key prefix for Revenium */
63
- REVENIUM_API_KEY_PREFIX: 'hak_',
63
+ REVENIUM_API_KEY_PREFIX: "hak_",
64
64
  /** Required API key prefix for Anthropic */
65
- ANTHROPIC_API_KEY_PREFIX: 'sk-ant-',
65
+ ANTHROPIC_API_KEY_PREFIX: "sk-ant-",
66
66
  /** Maximum tokens warning threshold */
67
67
  HIGH_MAX_TOKENS_THRESHOLD: 4096,
68
68
  /** Temperature range */
@@ -83,39 +83,54 @@ export const VALIDATION_CONFIG = {
83
83
  */
84
84
  export const LOGGING_CONFIG = {
85
85
  /** Middleware name for log prefixes */
86
- MIDDLEWARE_NAME: 'Revenium',
86
+ MIDDLEWARE_NAME: "Revenium",
87
87
  /** User agent string for API requests */
88
- USER_AGENT: 'revenium-middleware-anthropic-node/1.0.0',
88
+ USER_AGENT: "revenium-middleware-anthropic-node/1.0.0",
89
89
  /** Debug environment variable name */
90
- DEBUG_ENV_VAR: 'REVENIUM_DEBUG',
90
+ DEBUG_ENV_VAR: "REVENIUM_DEBUG",
91
91
  };
92
92
  /**
93
93
  * Environment variable names
94
94
  */
95
95
  export const ENV_VARS = {
96
96
  /** Revenium API key */
97
- REVENIUM_API_KEY: 'REVENIUM_METERING_API_KEY',
97
+ REVENIUM_API_KEY: "REVENIUM_METERING_API_KEY",
98
98
  /** Revenium base URL */
99
- REVENIUM_BASE_URL: 'REVENIUM_METERING_BASE_URL',
99
+ REVENIUM_BASE_URL: "REVENIUM_METERING_BASE_URL",
100
100
  /** Anthropic API key */
101
- ANTHROPIC_API_KEY: 'ANTHROPIC_API_KEY',
101
+ ANTHROPIC_API_KEY: "ANTHROPIC_API_KEY",
102
102
  /** Debug mode */
103
- DEBUG: 'REVENIUM_DEBUG',
103
+ DEBUG: "REVENIUM_DEBUG",
104
104
  /** Log level */
105
- LOG_LEVEL: 'REVENIUM_LOG_LEVEL',
105
+ LOG_LEVEL: "REVENIUM_LOG_LEVEL",
106
106
  /** API timeout */
107
- API_TIMEOUT: 'REVENIUM_API_TIMEOUT',
107
+ API_TIMEOUT: "REVENIUM_API_TIMEOUT",
108
108
  /** Fail silent mode */
109
- FAIL_SILENT: 'REVENIUM_FAIL_SILENT',
109
+ FAIL_SILENT: "REVENIUM_FAIL_SILENT",
110
110
  /** Maximum retries */
111
- MAX_RETRIES: 'REVENIUM_MAX_RETRIES',
111
+ MAX_RETRIES: "REVENIUM_MAX_RETRIES",
112
+ /** Print summary mode (true/false/human/json) */
113
+ PRINT_SUMMARY: "REVENIUM_PRINT_SUMMARY",
114
+ /** Team ID for cost metrics retrieval */
115
+ TEAM_ID: "REVENIUM_TEAM_ID",
116
+ };
117
+ /**
118
+ * Summary printer configuration
119
+ */
120
+ export const SUMMARY_PRINTER_CONFIG = {
121
+ /** Maximum number of retries when fetching cost metrics */
122
+ MAX_RETRIES: 3,
123
+ /** Delay between retries in milliseconds */
124
+ RETRY_DELAY: 2000,
125
+ /** Fetch timeout in milliseconds (prevents hung requests from keeping Node process alive) */
126
+ FETCH_TIMEOUT: 10000,
112
127
  };
113
128
  /**
114
129
  * API endpoints
115
130
  */
116
131
  export const API_ENDPOINTS = {
117
132
  /** Revenium AI completions endpoint */
118
- AI_COMPLETIONS: '/meter/v2/ai/completions',
133
+ AI_COMPLETIONS: "/meter/v2/ai/completions",
119
134
  };
120
135
  /**
121
136
  * Anthropic model patterns
@@ -125,17 +140,17 @@ export const ANTHROPIC_PATTERNS = {
125
140
  CLAUDE_MODEL_PATTERN: /claude/i,
126
141
  /** Known Anthropic stop reasons */
127
142
  STOP_REASONS: {
128
- END_TURN: 'end_turn',
129
- MAX_TOKENS: 'max_tokens',
130
- STOP_SEQUENCE: 'stop_sequence',
131
- TOOL_USE: 'tool_use',
143
+ END_TURN: "end_turn",
144
+ MAX_TOKENS: "max_tokens",
145
+ STOP_SEQUENCE: "stop_sequence",
146
+ TOOL_USE: "tool_use",
132
147
  },
133
148
  /** Revenium stop reason mappings */
134
149
  REVENIUM_STOP_REASON_MAP: {
135
- 'end_turn': 'END',
136
- 'max_tokens': 'TOKEN_LIMIT',
137
- 'stop_sequence': 'END_SEQUENCE',
138
- 'tool_use': 'END',
150
+ end_turn: "END",
151
+ max_tokens: "TOKEN_LIMIT",
152
+ stop_sequence: "END_SEQUENCE",
153
+ tool_use: "END",
139
154
  },
140
155
  };
141
156
  //# sourceMappingURL=constants.js.map