@revenium/anthropic 1.1.1 → 1.1.3

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,32 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.1.3] - 2026-02-19
6
+
7
+ ### Changed
8
+
9
+ - Version bump (1.1.2 blocked by npm registry)
10
+
11
+ ## [1.1.2] - 2026-02-18
12
+
13
+ ### Changed
14
+
15
+ - Version bump for npm publish with updated changelog
16
+
17
+ ## [1.1.1] - 2026-02-18
18
+
19
+ ### Added
20
+
21
+ - Tool metering support with meter_tool event tracking
22
+ - Output fields feature for tool metering documentation
23
+ - Fetch timeout configuration for API calls
24
+
25
+ ### Fixed
26
+
27
+ - Removed dead response.text() call in sendToolEvent
28
+ - Tests updated to use global fetch mock
29
+ - Used native fetch in tool-tracker module
30
+
5
31
  ## [1.1.0] - 2026-01-20
6
32
 
7
33
  ### Added
@@ -20,6 +46,7 @@ All notable changes to this project will be documented in this file.
20
46
 
21
47
  - Added terminal cost/metrics summary output after each API call
22
48
  - Added distributed tracing support with 10 visualization fields
49
+ - Added CI/CD workflows for automated testing and integration
23
50
 
24
51
  ### Fixed
25
52
 
@@ -128,6 +155,10 @@ All notable changes to this project will be documented in this file.
128
155
  - Configurable retry logic
129
156
  - Debug logging support
130
157
 
158
+ [1.1.3]: https://github.com/revenium/revenium-middleware-anthropic-node/releases/tag/v1.1.3
159
+ [1.1.2]: https://github.com/revenium/revenium-middleware-anthropic-node/releases/tag/v1.1.2
160
+ [1.1.1]: https://github.com/revenium/revenium-middleware-anthropic-node/releases/tag/v1.1.1
161
+ [1.1.0]: https://github.com/revenium/revenium-middleware-anthropic-node/releases/tag/v1.1.0
131
162
  [1.0.9]: https://github.com/revenium/revenium-middleware-anthropic-node/releases/tag/v1.0.9
132
163
  [1.0.8]: https://github.com/revenium/revenium-middleware-anthropic-node/releases/tag/v1.0.8
133
164
  [1.0.7]: https://github.com/revenium/revenium-middleware-anthropic-node/releases/tag/v1.0.7
package/README.md CHANGED
@@ -295,6 +295,54 @@ Add business context to track usage by organization, user, task type, or custom
295
295
 
296
296
  - [examples/advanced-features.ts](https://github.com/revenium/revenium-middleware-anthropic-node/blob/HEAD/examples/advanced-features.ts) - Working examples
297
297
 
298
+ ## Tool Metering
299
+
300
+ Track execution of custom tools and external API calls with automatic timing, error handling, and metadata collection.
301
+
302
+ ### Quick Example
303
+
304
+ ```typescript
305
+ import { meterTool, setToolContext } from '@revenium/anthropic';
306
+
307
+ setToolContext({
308
+ agent: 'my-agent',
309
+ traceId: 'session-123'
310
+ });
311
+
312
+ const result = await meterTool('weather-api', async () => {
313
+ return await fetch('https://api.example.com/weather');
314
+ }, {
315
+ operation: 'get_forecast',
316
+ outputFields: ['temperature', 'humidity']
317
+ });
318
+ ```
319
+
320
+ ### Functions
321
+
322
+ **meterTool(toolId, fn, metadata?)**
323
+
324
+ Wraps a function with automatic metering. Captures duration, success/failure, and errors. Returns function result unchanged.
325
+
326
+ **reportToolCall(toolId, report)**
327
+
328
+ Manually report a tool call that was already executed. Useful when wrapping is not possible.
329
+
330
+ **Context Management**
331
+
332
+ - `setToolContext(ctx)` - Set context for all subsequent tool calls
333
+ - `getToolContext()` - Get current context
334
+ - `clearToolContext()` - Clear context
335
+ - `runWithToolContext(ctx, fn)` - Run function with scoped context
336
+
337
+ ### Metadata Options
338
+
339
+ | Field | Description |
340
+ |-------|-------------|
341
+ | `operation` | Tool operation name (e.g., "search", "scrape") |
342
+ | `outputFields` | Array of field names to auto-extract from result |
343
+ | `usageMetadata` | Custom metrics (e.g., tokens, results count) |
344
+ | `agent`, `traceId`, etc. | Context fields (inherited from setToolContext) |
345
+
298
346
  ## Metadata Fields
299
347
 
300
348
  The middleware automatically sends the following fields to Revenium's `/meter/v2/ai/completions` endpoint:
package/dist/cjs/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Clean architecture without backward compatibility constraints
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.validateUsageMetadata = exports.validateAnthropicMessageParams = exports.validateReveniumConfig = exports.canExecuteRequest = exports.resetCircuitBreaker = exports.getCircuitBreakerStats = exports.extractUsageFromStream = exports.trackUsageAsync = exports.sendReveniumMetrics = exports.isAnthropicPatched = exports.unpatchAnthropic = exports.patchAnthropic = exports.validateCurrentConfig = exports.getConfigStatus = exports.getConfig = exports.getLogger = exports.setLogger = exports.setConfig = void 0;
7
+ exports.runWithToolContext = exports.clearToolContext = exports.getToolContext = exports.setToolContext = exports.reportToolCall = exports.meterTool = exports.validateUsageMetadata = exports.validateAnthropicMessageParams = exports.validateReveniumConfig = exports.canExecuteRequest = exports.resetCircuitBreaker = exports.getCircuitBreakerStats = exports.extractUsageFromStream = exports.trackUsageAsync = exports.sendReveniumMetrics = exports.isAnthropicPatched = exports.unpatchAnthropic = exports.patchAnthropic = exports.validateCurrentConfig = exports.getConfigStatus = exports.getConfig = exports.getLogger = exports.setLogger = exports.setConfig = void 0;
8
8
  exports.initialize = initialize;
9
9
  exports.configure = configure;
10
10
  exports.isInitialized = isInitialized;
@@ -44,6 +44,14 @@ var validation_1 = require("./utils/validation");
44
44
  Object.defineProperty(exports, "validateReveniumConfig", { enumerable: true, get: function () { return validation_1.validateReveniumConfig; } });
45
45
  Object.defineProperty(exports, "validateAnthropicMessageParams", { enumerable: true, get: function () { return validation_1.validateAnthropicMessageParams; } });
46
46
  Object.defineProperty(exports, "validateUsageMetadata", { enumerable: true, get: function () { return validation_1.validateUsageMetadata; } });
47
+ var tool_tracker_1 = require("./tool-tracker");
48
+ Object.defineProperty(exports, "meterTool", { enumerable: true, get: function () { return tool_tracker_1.meterTool; } });
49
+ Object.defineProperty(exports, "reportToolCall", { enumerable: true, get: function () { return tool_tracker_1.reportToolCall; } });
50
+ var tool_context_1 = require("./tool-context");
51
+ Object.defineProperty(exports, "setToolContext", { enumerable: true, get: function () { return tool_context_1.setToolContext; } });
52
+ Object.defineProperty(exports, "getToolContext", { enumerable: true, get: function () { return tool_context_1.getToolContext; } });
53
+ Object.defineProperty(exports, "clearToolContext", { enumerable: true, get: function () { return tool_context_1.clearToolContext; } });
54
+ Object.defineProperty(exports, "runWithToolContext", { enumerable: true, get: function () { return tool_context_1.runWithToolContext; } });
47
55
  /**
48
56
  * Initialize the Revenium middleware with configuration from environment variables
49
57
  * This function can be called explicitly for better error handling and control
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setToolContext = setToolContext;
4
+ exports.getToolContext = getToolContext;
5
+ exports.clearToolContext = clearToolContext;
6
+ exports.runWithToolContext = runWithToolContext;
7
+ const async_hooks_1 = require("async_hooks");
8
+ const contextStorage = new async_hooks_1.AsyncLocalStorage();
9
+ function setToolContext(ctx) {
10
+ const current = contextStorage.getStore() ?? {};
11
+ contextStorage.enterWith({ ...current, ...ctx });
12
+ }
13
+ function getToolContext() {
14
+ return contextStorage.getStore() ?? {};
15
+ }
16
+ function clearToolContext() {
17
+ contextStorage.enterWith({});
18
+ }
19
+ function runWithToolContext(ctx, fn) {
20
+ const merged = { ...getToolContext(), ...ctx };
21
+ return contextStorage.run(merged, fn);
22
+ }
23
+ //# sourceMappingURL=tool-context.js.map
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.meterTool = meterTool;
4
+ exports.reportToolCall = reportToolCall;
5
+ const crypto_1 = require("crypto");
6
+ const tool_context_1 = require("./tool-context");
7
+ const config_1 = require("./config");
8
+ const constants_1 = require("./constants");
9
+ const MIDDLEWARE_SOURCE = "revenium-anthropic-node";
10
+ const TOOL_EVENTS_ENDPOINT = "/tool/events";
11
+ function isPromise(value) {
12
+ return value !== null && typeof value === "object" && typeof value.then === "function";
13
+ }
14
+ function extractOutputFields(result, fields) {
15
+ if (typeof result !== "object" || result === null) {
16
+ return {};
17
+ }
18
+ const extracted = {};
19
+ for (const field of fields) {
20
+ if (field in result) {
21
+ extracted[field] = result[field];
22
+ }
23
+ }
24
+ return extracted;
25
+ }
26
+ function buildToolEventPayload(toolId, durationMs, success, metadata, errorMessage) {
27
+ const context = (0, tool_context_1.getToolContext)();
28
+ const transactionId = metadata?.transactionId ?? context.transactionId ?? (0, crypto_1.randomUUID)();
29
+ return {
30
+ transactionId,
31
+ toolId,
32
+ operation: metadata?.operation,
33
+ durationMs,
34
+ success,
35
+ timestamp: new Date().toISOString(),
36
+ errorMessage,
37
+ usageMetadata: metadata?.usageMetadata,
38
+ agent: metadata?.agent ?? context.agent,
39
+ organizationName: metadata?.organizationName ?? context.organizationName,
40
+ productName: metadata?.productName ?? context.productName,
41
+ subscriberCredential: metadata?.subscriberCredential ?? context.subscriberCredential,
42
+ workflowId: metadata?.workflowId ?? context.workflowId,
43
+ traceId: metadata?.traceId ?? context.traceId,
44
+ middlewareSource: MIDDLEWARE_SOURCE,
45
+ };
46
+ }
47
+ async function sendToolEvent(payload) {
48
+ const config = (0, config_1.getConfig)();
49
+ const logger = (0, config_1.getLogger)();
50
+ if (!config) {
51
+ logger.warn("Revenium configuration not found, skipping tool event tracking");
52
+ return;
53
+ }
54
+ const url = `${config.reveniumBaseUrl || constants_1.DEFAULT_CONFIG.REVENIUM_BASE_URL}${TOOL_EVENTS_ENDPOINT}`;
55
+ logger.debug("Sending tool event to Revenium", {
56
+ url,
57
+ toolId: payload.toolId,
58
+ transactionId: payload.transactionId,
59
+ operation: payload.operation,
60
+ durationMs: payload.durationMs,
61
+ success: payload.success,
62
+ });
63
+ const controller = new AbortController();
64
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
65
+ const response = await fetch(url, {
66
+ method: "POST",
67
+ headers: {
68
+ "Content-Type": "application/json",
69
+ "Accept": "application/json",
70
+ "x-api-key": config.reveniumApiKey,
71
+ },
72
+ body: JSON.stringify(payload),
73
+ signal: controller.signal,
74
+ }).finally(() => clearTimeout(timeoutId));
75
+ logger.debug("Tool event response", {
76
+ status: response.status,
77
+ statusText: response.statusText,
78
+ transactionId: payload.transactionId,
79
+ toolId: payload.toolId,
80
+ });
81
+ if (!response.ok) {
82
+ const responseText = await response.text();
83
+ logger.error("Tool event API error", {
84
+ status: response.status,
85
+ statusText: response.statusText,
86
+ body: responseText,
87
+ transactionId: payload.transactionId,
88
+ toolId: payload.toolId,
89
+ });
90
+ throw new Error(`Revenium tool event API error: ${response.status} ${response.statusText}`);
91
+ }
92
+ logger.debug("Tool event sent successfully", {
93
+ transactionId: payload.transactionId,
94
+ toolId: payload.toolId,
95
+ });
96
+ }
97
+ function dispatchToolEvent(payload) {
98
+ const logger = (0, config_1.getLogger)();
99
+ sendToolEvent(payload)
100
+ .then(() => {
101
+ logger.debug("Tool event sent successfully", {
102
+ transactionId: payload.transactionId,
103
+ toolId: payload.toolId,
104
+ });
105
+ })
106
+ .catch((error) => {
107
+ logger.warn("Failed to send tool event", {
108
+ transactionId: payload.transactionId,
109
+ toolId: payload.toolId,
110
+ error: error instanceof Error ? error.message : String(error),
111
+ });
112
+ });
113
+ }
114
+ function meterTool(toolId, fn, metadata) {
115
+ const startTime = performance.now();
116
+ const handleSuccess = (result) => {
117
+ const durationMs = Math.round(performance.now() - startTime);
118
+ let finalMetadata = metadata;
119
+ if (metadata?.outputFields && metadata.outputFields.length > 0) {
120
+ const extracted = extractOutputFields(result, metadata.outputFields);
121
+ finalMetadata = {
122
+ ...metadata,
123
+ usageMetadata: {
124
+ ...metadata.usageMetadata,
125
+ ...extracted,
126
+ },
127
+ };
128
+ }
129
+ const payload = buildToolEventPayload(toolId, durationMs, true, finalMetadata);
130
+ dispatchToolEvent(payload);
131
+ return result;
132
+ };
133
+ const handleError = (error) => {
134
+ const durationMs = Math.round(performance.now() - startTime);
135
+ const errorMessage = error instanceof Error ? error.message : String(error);
136
+ const payload = buildToolEventPayload(toolId, durationMs, false, metadata, errorMessage);
137
+ dispatchToolEvent(payload);
138
+ throw error;
139
+ };
140
+ try {
141
+ const result = fn();
142
+ if (isPromise(result)) {
143
+ return result.then(handleSuccess, handleError);
144
+ }
145
+ return Promise.resolve(handleSuccess(result));
146
+ }
147
+ catch (error) {
148
+ return Promise.reject(handleError(error));
149
+ }
150
+ }
151
+ function reportToolCall(toolId, report) {
152
+ const context = (0, tool_context_1.getToolContext)();
153
+ const transactionId = report.transactionId ?? context.transactionId ?? (0, crypto_1.randomUUID)();
154
+ const payload = {
155
+ transactionId,
156
+ toolId,
157
+ operation: report.operation,
158
+ durationMs: report.durationMs,
159
+ success: report.success,
160
+ timestamp: report.timestamp ?? new Date().toISOString(),
161
+ errorMessage: report.errorMessage,
162
+ usageMetadata: report.usageMetadata,
163
+ agent: report.agent ?? context.agent,
164
+ organizationName: report.organizationName ?? context.organizationName,
165
+ productName: report.productName ?? context.productName,
166
+ subscriberCredential: report.subscriberCredential ?? context.subscriberCredential,
167
+ workflowId: report.workflowId ?? context.workflowId,
168
+ traceId: report.traceId ?? context.traceId,
169
+ middlewareSource: MIDDLEWARE_SOURCE,
170
+ };
171
+ dispatchToolEvent(payload);
172
+ }
173
+ //# sourceMappingURL=tool-tracker.js.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=tool-metering.js.map
package/dist/cjs/types.js CHANGED
@@ -2,5 +2,20 @@
2
2
  /**
3
3
  * Type definitions for Anthropic middleware
4
4
  */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
17
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
18
+ };
5
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
+ __exportStar(require("./types/tool-metering"), exports);
6
21
  //# sourceMappingURL=types.js.map
package/dist/esm/index.js CHANGED
@@ -17,6 +17,8 @@ export { sendReveniumMetrics, trackUsageAsync, extractUsageFromStream, } from ".
17
17
  // Export utility functions
18
18
  export { getCircuitBreakerStats, resetCircuitBreaker, canExecuteRequest, } from "./utils/circuit-breaker.js";
19
19
  export { validateReveniumConfig, validateAnthropicMessageParams, validateUsageMetadata, } from "./utils/validation.js";
20
+ export { meterTool, reportToolCall, } from "./tool-tracker.js";
21
+ export { setToolContext, getToolContext, clearToolContext, runWithToolContext, } from "./tool-context.js";
20
22
  /**
21
23
  * Initialize the Revenium middleware with configuration from environment variables
22
24
  * This function can be called explicitly for better error handling and control
@@ -0,0 +1,17 @@
1
+ import { AsyncLocalStorage } from "async_hooks";
2
+ const contextStorage = new AsyncLocalStorage();
3
+ export function setToolContext(ctx) {
4
+ const current = contextStorage.getStore() ?? {};
5
+ contextStorage.enterWith({ ...current, ...ctx });
6
+ }
7
+ export function getToolContext() {
8
+ return contextStorage.getStore() ?? {};
9
+ }
10
+ export function clearToolContext() {
11
+ contextStorage.enterWith({});
12
+ }
13
+ export function runWithToolContext(ctx, fn) {
14
+ const merged = { ...getToolContext(), ...ctx };
15
+ return contextStorage.run(merged, fn);
16
+ }
17
+ //# sourceMappingURL=tool-context.js.map
@@ -0,0 +1,169 @@
1
+ import { randomUUID } from "crypto";
2
+ import { getToolContext } from "./tool-context.js";
3
+ import { getConfig, getLogger } from "./config.js";
4
+ import { DEFAULT_CONFIG } from "./constants.js";
5
+ const MIDDLEWARE_SOURCE = "revenium-anthropic-node";
6
+ const TOOL_EVENTS_ENDPOINT = "/tool/events";
7
+ function isPromise(value) {
8
+ return value !== null && typeof value === "object" && typeof value.then === "function";
9
+ }
10
+ function extractOutputFields(result, fields) {
11
+ if (typeof result !== "object" || result === null) {
12
+ return {};
13
+ }
14
+ const extracted = {};
15
+ for (const field of fields) {
16
+ if (field in result) {
17
+ extracted[field] = result[field];
18
+ }
19
+ }
20
+ return extracted;
21
+ }
22
+ function buildToolEventPayload(toolId, durationMs, success, metadata, errorMessage) {
23
+ const context = getToolContext();
24
+ const transactionId = metadata?.transactionId ?? context.transactionId ?? randomUUID();
25
+ return {
26
+ transactionId,
27
+ toolId,
28
+ operation: metadata?.operation,
29
+ durationMs,
30
+ success,
31
+ timestamp: new Date().toISOString(),
32
+ errorMessage,
33
+ usageMetadata: metadata?.usageMetadata,
34
+ agent: metadata?.agent ?? context.agent,
35
+ organizationName: metadata?.organizationName ?? context.organizationName,
36
+ productName: metadata?.productName ?? context.productName,
37
+ subscriberCredential: metadata?.subscriberCredential ?? context.subscriberCredential,
38
+ workflowId: metadata?.workflowId ?? context.workflowId,
39
+ traceId: metadata?.traceId ?? context.traceId,
40
+ middlewareSource: MIDDLEWARE_SOURCE,
41
+ };
42
+ }
43
+ async function sendToolEvent(payload) {
44
+ const config = getConfig();
45
+ const logger = getLogger();
46
+ if (!config) {
47
+ logger.warn("Revenium configuration not found, skipping tool event tracking");
48
+ return;
49
+ }
50
+ const url = `${config.reveniumBaseUrl || DEFAULT_CONFIG.REVENIUM_BASE_URL}${TOOL_EVENTS_ENDPOINT}`;
51
+ logger.debug("Sending tool event to Revenium", {
52
+ url,
53
+ toolId: payload.toolId,
54
+ transactionId: payload.transactionId,
55
+ operation: payload.operation,
56
+ durationMs: payload.durationMs,
57
+ success: payload.success,
58
+ });
59
+ const controller = new AbortController();
60
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
61
+ const response = await fetch(url, {
62
+ method: "POST",
63
+ headers: {
64
+ "Content-Type": "application/json",
65
+ "Accept": "application/json",
66
+ "x-api-key": config.reveniumApiKey,
67
+ },
68
+ body: JSON.stringify(payload),
69
+ signal: controller.signal,
70
+ }).finally(() => clearTimeout(timeoutId));
71
+ logger.debug("Tool event response", {
72
+ status: response.status,
73
+ statusText: response.statusText,
74
+ transactionId: payload.transactionId,
75
+ toolId: payload.toolId,
76
+ });
77
+ if (!response.ok) {
78
+ const responseText = await response.text();
79
+ logger.error("Tool event API error", {
80
+ status: response.status,
81
+ statusText: response.statusText,
82
+ body: responseText,
83
+ transactionId: payload.transactionId,
84
+ toolId: payload.toolId,
85
+ });
86
+ throw new Error(`Revenium tool event API error: ${response.status} ${response.statusText}`);
87
+ }
88
+ logger.debug("Tool event sent successfully", {
89
+ transactionId: payload.transactionId,
90
+ toolId: payload.toolId,
91
+ });
92
+ }
93
+ function dispatchToolEvent(payload) {
94
+ const logger = getLogger();
95
+ sendToolEvent(payload)
96
+ .then(() => {
97
+ logger.debug("Tool event sent successfully", {
98
+ transactionId: payload.transactionId,
99
+ toolId: payload.toolId,
100
+ });
101
+ })
102
+ .catch((error) => {
103
+ logger.warn("Failed to send tool event", {
104
+ transactionId: payload.transactionId,
105
+ toolId: payload.toolId,
106
+ error: error instanceof Error ? error.message : String(error),
107
+ });
108
+ });
109
+ }
110
+ export function meterTool(toolId, fn, metadata) {
111
+ const startTime = performance.now();
112
+ const handleSuccess = (result) => {
113
+ const durationMs = Math.round(performance.now() - startTime);
114
+ let finalMetadata = metadata;
115
+ if (metadata?.outputFields && metadata.outputFields.length > 0) {
116
+ const extracted = extractOutputFields(result, metadata.outputFields);
117
+ finalMetadata = {
118
+ ...metadata,
119
+ usageMetadata: {
120
+ ...metadata.usageMetadata,
121
+ ...extracted,
122
+ },
123
+ };
124
+ }
125
+ const payload = buildToolEventPayload(toolId, durationMs, true, finalMetadata);
126
+ dispatchToolEvent(payload);
127
+ return result;
128
+ };
129
+ const handleError = (error) => {
130
+ const durationMs = Math.round(performance.now() - startTime);
131
+ const errorMessage = error instanceof Error ? error.message : String(error);
132
+ const payload = buildToolEventPayload(toolId, durationMs, false, metadata, errorMessage);
133
+ dispatchToolEvent(payload);
134
+ throw error;
135
+ };
136
+ try {
137
+ const result = fn();
138
+ if (isPromise(result)) {
139
+ return result.then(handleSuccess, handleError);
140
+ }
141
+ return Promise.resolve(handleSuccess(result));
142
+ }
143
+ catch (error) {
144
+ return Promise.reject(handleError(error));
145
+ }
146
+ }
147
+ export function reportToolCall(toolId, report) {
148
+ const context = getToolContext();
149
+ const transactionId = report.transactionId ?? context.transactionId ?? randomUUID();
150
+ const payload = {
151
+ transactionId,
152
+ toolId,
153
+ operation: report.operation,
154
+ durationMs: report.durationMs,
155
+ success: report.success,
156
+ timestamp: report.timestamp ?? new Date().toISOString(),
157
+ errorMessage: report.errorMessage,
158
+ usageMetadata: report.usageMetadata,
159
+ agent: report.agent ?? context.agent,
160
+ organizationName: report.organizationName ?? context.organizationName,
161
+ productName: report.productName ?? context.productName,
162
+ subscriberCredential: report.subscriberCredential ?? context.subscriberCredential,
163
+ workflowId: report.workflowId ?? context.workflowId,
164
+ traceId: report.traceId ?? context.traceId,
165
+ middlewareSource: MIDDLEWARE_SOURCE,
166
+ };
167
+ dispatchToolEvent(payload);
168
+ }
169
+ //# sourceMappingURL=tool-tracker.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tool-metering.js.map
package/dist/esm/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * Type definitions for Anthropic middleware
3
3
  */
4
- export {};
4
+ export * from "./types/tool-metering.js";
5
5
  //# sourceMappingURL=types.js.map
@@ -23,13 +23,23 @@ TrackingData,
23
23
  /** Configuration validation result with detailed feedback */
24
24
  ConfigValidationResult,
25
25
  /** Request validation result for API call validation */
26
- RequestValidationResult, } from "./types";
26
+ RequestValidationResult,
27
+ /** Tool context for tool metering */
28
+ ToolContext,
29
+ /** Tool metadata for tool metering */
30
+ ToolMetadata,
31
+ /** Tool event payload structure */
32
+ ToolEventPayload,
33
+ /** Tool call report structure */
34
+ ToolCallReport, } from "./types";
27
35
  import type { MiddlewareStatus } from "./types";
28
36
  export { setConfig, setLogger, getLogger, getConfig, getConfigStatus, validateCurrentConfig, } from "./config";
29
37
  export { patchAnthropic, unpatchAnthropic, isAnthropicPatched, } from "./wrapper";
30
38
  export { sendReveniumMetrics, trackUsageAsync, extractUsageFromStream, } from "./tracking";
31
39
  export { getCircuitBreakerStats, resetCircuitBreaker, canExecuteRequest, } from "./utils/circuit-breaker";
32
40
  export { validateReveniumConfig, validateAnthropicMessageParams, validateUsageMetadata, } from "./utils/validation";
41
+ export { meterTool, reportToolCall, } from "./tool-tracker";
42
+ export { setToolContext, getToolContext, clearToolContext, runWithToolContext, } from "./tool-context";
33
43
  export declare function initialize(): void;
34
44
  /**
35
45
  * Manual initialization with custom configuration
@@ -0,0 +1,6 @@
1
+ import { ToolContext } from "./types/tool-metering";
2
+ export declare function setToolContext(ctx: ToolContext): void;
3
+ export declare function getToolContext(): ToolContext;
4
+ export declare function clearToolContext(): void;
5
+ export declare function runWithToolContext<T>(ctx: ToolContext, fn: () => T | Promise<T>): T | Promise<T>;
6
+ //# sourceMappingURL=tool-context.d.ts.map
@@ -0,0 +1,4 @@
1
+ import { ToolMetadata, ToolCallReport } from "./types/tool-metering";
2
+ export declare function meterTool<T>(toolId: string, fn: () => T | Promise<T>, metadata?: ToolMetadata): Promise<T>;
3
+ export declare function reportToolCall(toolId: string, report: ToolCallReport): void;
4
+ //# sourceMappingURL=tool-tracker.d.ts.map
@@ -0,0 +1,47 @@
1
+ export interface ToolContext {
2
+ agent?: string;
3
+ organizationName?: string;
4
+ productName?: string;
5
+ subscriberCredential?: string;
6
+ workflowId?: string;
7
+ traceId?: string;
8
+ transactionId?: string;
9
+ }
10
+ export interface ToolMetadata extends ToolContext {
11
+ operation?: string;
12
+ outputFields?: string[];
13
+ usageMetadata?: Record<string, unknown>;
14
+ }
15
+ export interface ToolEventPayload {
16
+ transactionId: string;
17
+ toolId: string;
18
+ operation?: string;
19
+ durationMs: number;
20
+ success: boolean;
21
+ timestamp: string;
22
+ errorMessage?: string;
23
+ usageMetadata?: Record<string, unknown>;
24
+ agent?: string;
25
+ organizationName?: string;
26
+ productName?: string;
27
+ subscriberCredential?: string;
28
+ workflowId?: string;
29
+ traceId?: string;
30
+ middlewareSource: string;
31
+ }
32
+ export interface ToolCallReport {
33
+ operation?: string;
34
+ durationMs: number;
35
+ success: boolean;
36
+ errorMessage?: string;
37
+ usageMetadata?: Record<string, unknown>;
38
+ agent?: string;
39
+ organizationName?: string;
40
+ productName?: string;
41
+ subscriberCredential?: string;
42
+ workflowId?: string;
43
+ traceId?: string;
44
+ transactionId?: string;
45
+ timestamp?: string;
46
+ }
47
+ //# sourceMappingURL=tool-metering.d.ts.map
@@ -506,4 +506,5 @@ export interface EnvironmentConfig {
506
506
  * - 'json': JSON formatted output for automation/parsing
507
507
  */
508
508
  export type SummaryFormat = "human" | "json";
509
+ export * from "./types/tool-metering";
509
510
  //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revenium/anthropic",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
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",
@@ -24,9 +24,17 @@
24
24
  "build:types": "tsc -p tsconfig.types.json",
25
25
  "fix-esm": "node scripts/fix-esm-imports.js",
26
26
  "dev": "tsc --watch",
27
- "example": "npm run build && tsx examples/basic.ts",
27
+ "test": "jest",
28
+ "test:unit": "jest --testPathPatterns=tests/unit",
29
+ "test:integration": "jest --testPathPatterns=tests/integration",
30
+ "test:performance": "jest --testPathPatterns=tests/performance",
31
+ "test:watch": "jest --watch",
32
+ "test:coverage": "jest --coverage",
33
+ "test:e2e": "npm run build && tsx tests/test-trace-fields.ts",
34
+ "example": "npm run build && tsx examples/getting_started.ts",
28
35
  "example:basic": "npm run build && tsx examples/basic.ts",
29
36
  "example:advanced": "npm run build && tsx examples/advanced.ts",
37
+ "example:metadata": "npm run build && tsx examples/metadata.ts",
30
38
  "clean": "rimraf dist",
31
39
  "prepublishOnly": "npm run clean && npm run build"
32
40
  },
@@ -59,10 +67,13 @@
59
67
  },
60
68
  "devDependencies": {
61
69
  "@anthropic-ai/sdk": "^0.55.1",
70
+ "@types/jest": "^30.0.0",
62
71
  "@types/node": "^20.0.0",
63
72
  "@types/node-fetch": "^2.6.12",
64
73
  "dotenv": "^16.5.0",
74
+ "jest": "^30.2.0",
65
75
  "rimraf": "^6.0.1",
76
+ "ts-jest": "^29.4.6",
66
77
  "ts-node": "^10.9.2",
67
78
  "tsx": "^4.20.3",
68
79
  "typescript": "^5.8.3"