@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 +31 -0
- package/README.md +48 -0
- package/dist/cjs/index.js +9 -1
- package/dist/cjs/tool-context.js +23 -0
- package/dist/cjs/tool-tracker.js +173 -0
- package/dist/cjs/types/tool-metering.js +3 -0
- package/dist/cjs/types.js +15 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/tool-context.js +17 -0
- package/dist/esm/tool-tracker.js +169 -0
- package/dist/esm/types/tool-metering.js +2 -0
- package/dist/esm/types.js +1 -1
- package/dist/types/index.d.ts +11 -1
- package/dist/types/tool-context.d.ts +6 -0
- package/dist/types/tool-tracker.d.ts +4 -0
- package/dist/types/types/tool-metering.d.ts +47 -0
- package/dist/types/types.d.ts +1 -0
- package/package.json +13 -2
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
|
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
|
package/dist/esm/types.js
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -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,
|
|
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
|
package/dist/types/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenium/anthropic",
|
|
3
|
-
"version": "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
|
-
"
|
|
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"
|