@revenium/anthropic 1.1.3 → 1.1.4

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.1.4] - 2026-03-03
6
+
7
+ ### Fixed
8
+
9
+ - Use canonical model name from API response for pricing resolution
10
+ - Use resolved model in streaming error context
11
+
5
12
  ## [1.1.3] - 2026-02-19
6
13
 
7
14
  ### Changed
@@ -201,27 +201,29 @@ function reconstructResponseFromChunks(chunks, model) {
201
201
  * Handle streaming response by collecting chunks and extracting usage data
202
202
  */
203
203
  async function handleStreamingResponse(stream, context) {
204
- const { requestId, model, metadata, requestTime, startTime, requestBody } = context;
205
- // Create a new async generator that collects chunks and tracks usage
204
+ const { requestId, requestModel, metadata, requestTime, startTime, requestBody } = context;
206
205
  async function* trackingStream() {
207
206
  const chunks = [];
208
207
  let firstTokenTime;
208
+ let resolvedModel;
209
209
  try {
210
210
  for await (const chunk of stream) {
211
- // Track first token time
212
211
  if (!firstTokenTime && chunk.type === "content_block_delta") {
213
212
  firstTokenTime = Date.now();
214
213
  }
214
+ if (!resolvedModel && chunk.type === "message_start" && chunk.message?.model) {
215
+ resolvedModel = chunk.message.model;
216
+ }
215
217
  chunks.push(chunk);
216
218
  yield chunk;
217
219
  }
218
- // After stream completes, extract usage and track
219
220
  const endTime = Date.now();
220
221
  const responseTime = new Date();
221
222
  const duration = endTime - startTime;
222
223
  const timeToFirstToken = firstTokenTime
223
224
  ? firstTokenTime - startTime
224
225
  : undefined;
226
+ const model = resolvedModel ?? requestModel;
225
227
  logger.debug("Stream completed, extracting usage", {
226
228
  requestId,
227
229
  chunkCount: chunks.length,
@@ -233,7 +235,6 @@ async function handleStreamingResponse(stream, context) {
233
235
  if ((0, prompt_extraction_1.shouldCapturePrompts)(metadata)) {
234
236
  reconstructedResponse = reconstructResponseFromChunks(chunks, model);
235
237
  }
236
- // Create tracking data
237
238
  const trackingData = {
238
239
  requestId,
239
240
  model,
@@ -317,7 +318,7 @@ async function patchedCreateMethod(params, options) {
317
318
  // Create tracking data
318
319
  const trackingData = {
319
320
  requestId,
320
- model: params.model,
321
+ model: response.model ?? params.model,
321
322
  inputTokens: usage.inputTokens,
322
323
  outputTokens: usage.outputTokens,
323
324
  cacheCreationTokens: usage.cacheCreationTokens,
@@ -336,7 +337,7 @@ async function patchedCreateMethod(params, options) {
336
337
  (0, tracking_1.trackUsageAsync)(trackingData);
337
338
  logger.debug("Anthropic request completed successfully", {
338
339
  requestId,
339
- model: params.model,
340
+ model: response.model ?? params.model,
340
341
  inputTokens: usage.inputTokens,
341
342
  outputTokens: usage.outputTokens,
342
343
  duration,
@@ -346,7 +347,7 @@ async function patchedCreateMethod(params, options) {
346
347
  // Handle streaming response - need to collect chunks and extract usage
347
348
  return handleStreamingResponse(response, {
348
349
  requestId,
349
- model: params.model,
350
+ requestModel: params.model,
350
351
  metadata,
351
352
  requestTime,
352
353
  startTime,
@@ -375,12 +376,12 @@ async function* patchedStreamMethod(params, options) {
375
376
  const responseTime = new Date();
376
377
  const chunks = [];
377
378
  let firstTokenTime;
379
+ let resolvedModel;
378
380
  logger.debug("Intercepted Anthropic messages.stream call", {
379
381
  requestId,
380
382
  model: params.model,
381
383
  hasMetadata: !!params.usageMetadata,
382
384
  });
383
- // Validate parameters
384
385
  const validation = (0, validation_1.validateAnthropicMessageParams)(params);
385
386
  if (!validation.isValid) {
386
387
  logger.warn("Invalid Anthropic streaming parameters detected", {
@@ -389,22 +390,21 @@ async function* patchedStreamMethod(params, options) {
389
390
  warnings: validation.warnings,
390
391
  });
391
392
  }
392
- // Extract and validate metadata
393
393
  const metadata = (0, validation_1.validateUsageMetadata)(params.usageMetadata || {});
394
- // Remove usageMetadata from params before calling original method
395
394
  const { usageMetadata, ...cleanParams } = params;
396
395
  try {
397
- // Call original stream method
398
396
  const originalStream = patchingContext.originalMethods?.stream;
399
397
  if (!originalStream) {
400
398
  throw new error_handling_1.StreamProcessingError("Original stream method not available");
401
399
  }
402
400
  const stream = originalStream.call(this, cleanParams, options);
403
401
  for await (const chunk of stream) {
404
- // Track first token time
405
402
  if (!firstTokenTime && chunk.type === "content_block_delta") {
406
403
  firstTokenTime = Date.now();
407
404
  }
405
+ if (!resolvedModel && chunk.type === "message_start" && chunk.message?.model) {
406
+ resolvedModel = chunk.message.model;
407
+ }
408
408
  chunks.push(chunk);
409
409
  yield chunk;
410
410
  }
@@ -413,18 +413,16 @@ async function* patchedStreamMethod(params, options) {
413
413
  const timeToFirstToken = firstTokenTime
414
414
  ? firstTokenTime - startTime
415
415
  : undefined;
416
- // Extract usage information from all chunks
416
+ const model = resolvedModel ?? params.model;
417
417
  const usage = (0, tracking_1.extractUsageFromStream)(chunks);
418
- // Detect vision content
419
418
  const hasVisionContent = (0, trace_fields_1.detectVisionContent)(params);
420
419
  let reconstructedResponse = undefined;
421
420
  if ((0, prompt_extraction_1.shouldCapturePrompts)(metadata)) {
422
- reconstructedResponse = reconstructResponseFromChunks(chunks, params.model);
421
+ reconstructedResponse = reconstructResponseFromChunks(chunks, model);
423
422
  }
424
- // Create tracking data
425
423
  const trackingData = {
426
424
  requestId,
427
- model: params.model,
425
+ model,
428
426
  inputTokens: usage.inputTokens,
429
427
  outputTokens: usage.outputTokens,
430
428
  cacheCreationTokens: usage.cacheCreationTokens,
@@ -440,11 +438,10 @@ async function* patchedStreamMethod(params, options) {
440
438
  requestBody: params,
441
439
  response: reconstructedResponse,
442
440
  };
443
- // Track usage asynchronously
444
441
  (0, tracking_1.trackUsageAsync)(trackingData);
445
442
  logger.debug("Anthropic streaming request completed successfully", {
446
443
  requestId,
447
- model: params.model,
444
+ model,
448
445
  inputTokens: usage.inputTokens,
449
446
  outputTokens: usage.outputTokens,
450
447
  duration,
@@ -457,7 +454,7 @@ async function* patchedStreamMethod(params, options) {
457
454
  const duration = endTime - startTime;
458
455
  const errorContext = (0, error_handling_1.createErrorContext)()
459
456
  .withRequestId(requestId)
460
- .withModel(params.model)
457
+ .withModel(resolvedModel ?? params.model)
461
458
  .withDuration(duration)
462
459
  .with("isStreaming", true)
463
460
  .with("chunkCount", chunks.length)
@@ -193,27 +193,29 @@ function reconstructResponseFromChunks(chunks, model) {
193
193
  * Handle streaming response by collecting chunks and extracting usage data
194
194
  */
195
195
  async function handleStreamingResponse(stream, context) {
196
- const { requestId, model, metadata, requestTime, startTime, requestBody } = context;
197
- // Create a new async generator that collects chunks and tracks usage
196
+ const { requestId, requestModel, metadata, requestTime, startTime, requestBody } = context;
198
197
  async function* trackingStream() {
199
198
  const chunks = [];
200
199
  let firstTokenTime;
200
+ let resolvedModel;
201
201
  try {
202
202
  for await (const chunk of stream) {
203
- // Track first token time
204
203
  if (!firstTokenTime && chunk.type === "content_block_delta") {
205
204
  firstTokenTime = Date.now();
206
205
  }
206
+ if (!resolvedModel && chunk.type === "message_start" && chunk.message?.model) {
207
+ resolvedModel = chunk.message.model;
208
+ }
207
209
  chunks.push(chunk);
208
210
  yield chunk;
209
211
  }
210
- // After stream completes, extract usage and track
211
212
  const endTime = Date.now();
212
213
  const responseTime = new Date();
213
214
  const duration = endTime - startTime;
214
215
  const timeToFirstToken = firstTokenTime
215
216
  ? firstTokenTime - startTime
216
217
  : undefined;
218
+ const model = resolvedModel ?? requestModel;
217
219
  logger.debug("Stream completed, extracting usage", {
218
220
  requestId,
219
221
  chunkCount: chunks.length,
@@ -225,7 +227,6 @@ async function handleStreamingResponse(stream, context) {
225
227
  if (shouldCapturePrompts(metadata)) {
226
228
  reconstructedResponse = reconstructResponseFromChunks(chunks, model);
227
229
  }
228
- // Create tracking data
229
230
  const trackingData = {
230
231
  requestId,
231
232
  model,
@@ -309,7 +310,7 @@ async function patchedCreateMethod(params, options) {
309
310
  // Create tracking data
310
311
  const trackingData = {
311
312
  requestId,
312
- model: params.model,
313
+ model: response.model ?? params.model,
313
314
  inputTokens: usage.inputTokens,
314
315
  outputTokens: usage.outputTokens,
315
316
  cacheCreationTokens: usage.cacheCreationTokens,
@@ -328,7 +329,7 @@ async function patchedCreateMethod(params, options) {
328
329
  trackUsageAsync(trackingData);
329
330
  logger.debug("Anthropic request completed successfully", {
330
331
  requestId,
331
- model: params.model,
332
+ model: response.model ?? params.model,
332
333
  inputTokens: usage.inputTokens,
333
334
  outputTokens: usage.outputTokens,
334
335
  duration,
@@ -338,7 +339,7 @@ async function patchedCreateMethod(params, options) {
338
339
  // Handle streaming response - need to collect chunks and extract usage
339
340
  return handleStreamingResponse(response, {
340
341
  requestId,
341
- model: params.model,
342
+ requestModel: params.model,
342
343
  metadata,
343
344
  requestTime,
344
345
  startTime,
@@ -367,12 +368,12 @@ async function* patchedStreamMethod(params, options) {
367
368
  const responseTime = new Date();
368
369
  const chunks = [];
369
370
  let firstTokenTime;
371
+ let resolvedModel;
370
372
  logger.debug("Intercepted Anthropic messages.stream call", {
371
373
  requestId,
372
374
  model: params.model,
373
375
  hasMetadata: !!params.usageMetadata,
374
376
  });
375
- // Validate parameters
376
377
  const validation = validateAnthropicMessageParams(params);
377
378
  if (!validation.isValid) {
378
379
  logger.warn("Invalid Anthropic streaming parameters detected", {
@@ -381,22 +382,21 @@ async function* patchedStreamMethod(params, options) {
381
382
  warnings: validation.warnings,
382
383
  });
383
384
  }
384
- // Extract and validate metadata
385
385
  const metadata = validateUsageMetadata(params.usageMetadata || {});
386
- // Remove usageMetadata from params before calling original method
387
386
  const { usageMetadata, ...cleanParams } = params;
388
387
  try {
389
- // Call original stream method
390
388
  const originalStream = patchingContext.originalMethods?.stream;
391
389
  if (!originalStream) {
392
390
  throw new StreamProcessingError("Original stream method not available");
393
391
  }
394
392
  const stream = originalStream.call(this, cleanParams, options);
395
393
  for await (const chunk of stream) {
396
- // Track first token time
397
394
  if (!firstTokenTime && chunk.type === "content_block_delta") {
398
395
  firstTokenTime = Date.now();
399
396
  }
397
+ if (!resolvedModel && chunk.type === "message_start" && chunk.message?.model) {
398
+ resolvedModel = chunk.message.model;
399
+ }
400
400
  chunks.push(chunk);
401
401
  yield chunk;
402
402
  }
@@ -405,18 +405,16 @@ async function* patchedStreamMethod(params, options) {
405
405
  const timeToFirstToken = firstTokenTime
406
406
  ? firstTokenTime - startTime
407
407
  : undefined;
408
- // Extract usage information from all chunks
408
+ const model = resolvedModel ?? params.model;
409
409
  const usage = extractUsageFromStream(chunks);
410
- // Detect vision content
411
410
  const hasVisionContent = detectVisionContent(params);
412
411
  let reconstructedResponse = undefined;
413
412
  if (shouldCapturePrompts(metadata)) {
414
- reconstructedResponse = reconstructResponseFromChunks(chunks, params.model);
413
+ reconstructedResponse = reconstructResponseFromChunks(chunks, model);
415
414
  }
416
- // Create tracking data
417
415
  const trackingData = {
418
416
  requestId,
419
- model: params.model,
417
+ model,
420
418
  inputTokens: usage.inputTokens,
421
419
  outputTokens: usage.outputTokens,
422
420
  cacheCreationTokens: usage.cacheCreationTokens,
@@ -432,11 +430,10 @@ async function* patchedStreamMethod(params, options) {
432
430
  requestBody: params,
433
431
  response: reconstructedResponse,
434
432
  };
435
- // Track usage asynchronously
436
433
  trackUsageAsync(trackingData);
437
434
  logger.debug("Anthropic streaming request completed successfully", {
438
435
  requestId,
439
- model: params.model,
436
+ model,
440
437
  inputTokens: usage.inputTokens,
441
438
  outputTokens: usage.outputTokens,
442
439
  duration,
@@ -449,7 +446,7 @@ async function* patchedStreamMethod(params, options) {
449
446
  const duration = endTime - startTime;
450
447
  const errorContext = createErrorContext()
451
448
  .withRequestId(requestId)
452
- .withModel(params.model)
449
+ .withModel(resolvedModel ?? params.model)
453
450
  .withDuration(duration)
454
451
  .with("isStreaming", true)
455
452
  .with("chunkCount", chunks.length)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenium/anthropic",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Transparent TypeScript middleware for automatic Revenium usage tracking with Anthropic Claude AI",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",