@revenium/anthropic 1.0.6 → 1.0.8
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 +26 -0
- package/README.md +49 -11
- package/dist/cjs/constants.js +1 -1
- package/dist/cjs/tracking.js +28 -3
- package/dist/cjs/utils/validation.js +4 -4
- package/dist/cjs/wrapper.js +10 -4
- package/dist/esm/constants.js +1 -1
- package/dist/esm/tracking.js +28 -3
- package/dist/esm/utils/validation.js +4 -4
- package/dist/esm/wrapper.js +11 -5
- package/dist/types/constants.d.ts +1 -1
- package/dist/types/types.d.ts +3 -3
- package/examples/README.md +23 -2
- package/examples/advanced-features.ts +5 -5
- package/examples/basic-usage.ts +6 -6
- package/examples/getting_started.ts +1 -1
- package/package.json +5 -2
- package/examples/.claude/settings.local.json +0 -18
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.0.8] - 2025-11-07
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Fixed `anthropicApiKey` configuration parameter to honor config value before environment variable
|
|
9
|
+
- Fixed `failSilent=false` configuration to properly propagate tracking errors
|
|
10
|
+
- Fixed `maxRetries=0` handling to allow zero retries when explicitly configured
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added `timeToFirstToken` metric tracking for streaming requests
|
|
14
|
+
- Added custom metadata field preservation beyond standard fields
|
|
15
|
+
- Added website link (www.revenium.ai) to README Support section
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Updated Revenium API base URL from `api.revenium.io` to `api.revenium.ai`
|
|
19
|
+
- Updated Node.js requirement from 16+ to 18+
|
|
20
|
+
- Updated `.gitignore` to exclude `dist/` directory and `.env.test`
|
|
21
|
+
- Excluded `.claude/` directory and `*.log` files from npm package
|
|
22
|
+
- Removed `package-lock.json` from public repository allowlist
|
|
23
|
+
|
|
24
|
+
## [1.0.7] - 2025-10-25
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Updated all examples to use `claude-haiku-4-5` (validated Anthropic model identifier)
|
|
28
|
+
- Improved examples documentation with getting_started.ts reference
|
|
29
|
+
- Streamlined README by removing OpenRouter section for clarity
|
|
30
|
+
|
|
5
31
|
## [1.0.6] - 2025-10-24
|
|
6
32
|
|
|
7
33
|
### Added
|
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@revenium/anthropic)
|
|
4
4
|
[](https://www.npmjs.com/package/@revenium/anthropic)
|
|
5
5
|
[](https://docs.revenium.io)
|
|
6
|
+
[](https://www.revenium.ai)
|
|
6
7
|
[](https://opensource.org/licenses/MIT)
|
|
7
8
|
|
|
8
9
|
Automatically track and meter your Anthropic Claude API usage with Revenium. This middleware provides seamless integration with **Anthropic Claude SDK**, requiring minimal code changes and featuring native TypeScript support.
|
|
@@ -45,7 +46,7 @@ REVENIUM_METERING_API_KEY="hak_your_revenium_key"
|
|
|
45
46
|
ANTHROPIC_API_KEY="sk-ant-your_anthropic_key"
|
|
46
47
|
```
|
|
47
48
|
|
|
48
|
-
> **Note**: `REVENIUM_METERING_BASE_URL` defaults to `https://api.revenium.
|
|
49
|
+
> **Note**: `REVENIUM_METERING_BASE_URL` defaults to `https://api.revenium.ai` and doesn't need to be set unless using a different environment.
|
|
49
50
|
|
|
50
51
|
### 3. Run Your First Example
|
|
51
52
|
|
|
@@ -132,6 +133,48 @@ Add business context to track usage by organization, user, task type, or custom
|
|
|
132
133
|
| `agent` | AI agent or bot identifier | Distinguish between multiple AI agents or automation workflows in your system |
|
|
133
134
|
| `responseQualityScore` | Custom quality rating (0.0-1.0) | Track user satisfaction or automated quality metrics for model performance analysis |
|
|
134
135
|
|
|
136
|
+
**All metadata fields are optional.** Custom fields beyond the standard ones are automatically preserved and sent to Revenium.
|
|
137
|
+
|
|
138
|
+
**Resources:**
|
|
139
|
+
- [examples/advanced-features.ts](https://github.com/revenium/revenium-middleware-anthropic-node/blob/HEAD/examples/advanced-features.ts) - Working examples
|
|
140
|
+
|
|
141
|
+
## Metadata Fields
|
|
142
|
+
|
|
143
|
+
The middleware automatically sends the following fields to Revenium's `/meter/v2/ai/completions` endpoint:
|
|
144
|
+
|
|
145
|
+
| Field | Type | Source | Description |
|
|
146
|
+
|-------|------|--------|-------------|
|
|
147
|
+
| `stopReason` | string | Anthropic Response | Why completion stopped: "END", "MAX_TOKENS", "STOP_SEQUENCE", "TOOL_USE" |
|
|
148
|
+
| `costType` | string | Fixed | Always "AI" for AI completions |
|
|
149
|
+
| `isStreamed` | boolean | Request Type | Whether response was streamed |
|
|
150
|
+
| `taskType` | string | usageMetadata | Optional task categorization |
|
|
151
|
+
| `agent` | string | usageMetadata | Optional agent identifier |
|
|
152
|
+
| `operationType` | string | Fixed | Always "CHAT" for message completions |
|
|
153
|
+
| `inputTokenCount` | number | Anthropic Usage | Input tokens consumed |
|
|
154
|
+
| `outputTokenCount` | number | Anthropic Usage | Output tokens generated |
|
|
155
|
+
| `reasoningTokenCount` | number | Fixed | Always 0 (Anthropic doesn't report reasoning tokens) |
|
|
156
|
+
| `cacheCreationTokenCount` | number | Anthropic Usage | Tokens used for prompt caching creation |
|
|
157
|
+
| `cacheReadTokenCount` | number | Anthropic Usage | Tokens read from prompt cache |
|
|
158
|
+
| `totalTokenCount` | number | Calculated | Sum of input + output tokens |
|
|
159
|
+
| `organizationId` | string | usageMetadata | Optional organization identifier |
|
|
160
|
+
| `productId` | string | usageMetadata | Optional product identifier |
|
|
161
|
+
| `subscriber` | object | usageMetadata | Optional subscriber info (id, email, credential) |
|
|
162
|
+
| `subscriptionId` | string | usageMetadata | Optional subscription plan identifier |
|
|
163
|
+
| `model` | string | Request | Anthropic model used (e.g., "claude-3-5-haiku-20241022") |
|
|
164
|
+
| `transactionId` | string | Generated | Unique request identifier (UUID) |
|
|
165
|
+
| `responseTime` | string | Timestamp | ISO 8601 timestamp of response completion |
|
|
166
|
+
| `requestDuration` | number | Measured | Total request duration in milliseconds |
|
|
167
|
+
| `provider` | string | Fixed | Always "Anthropic" |
|
|
168
|
+
| `requestTime` | string | Timestamp | ISO 8601 timestamp when request started |
|
|
169
|
+
| `completionStartTime` | string | Timestamp | ISO 8601 timestamp when completion generation started |
|
|
170
|
+
| `timeToFirstToken` | number | Measured | Milliseconds from request start to first token (streaming only) |
|
|
171
|
+
| `traceId` | string | usageMetadata | Optional trace identifier for request correlation |
|
|
172
|
+
| `responseQualityScore` | number | usageMetadata | Optional quality rating (0.0-1.0) |
|
|
173
|
+
| `middlewareSource` | string | Fixed | Always "nodejs" to identify this middleware |
|
|
174
|
+
| Custom Fields | any | usageMetadata | Any additional fields in usageMetadata are preserved |
|
|
175
|
+
|
|
176
|
+
**Note:** Fields marked as "usageMetadata" come from the optional `usageMetadata` object you pass to `messages.create()`.
|
|
177
|
+
|
|
135
178
|
**Resources:**
|
|
136
179
|
- [API Reference](https://revenium.readme.io/reference/meter_ai_completion) - Complete metadata field documentation
|
|
137
180
|
|
|
@@ -143,7 +186,7 @@ Add business context to track usage by organization, user, task type, or custom
|
|
|
143
186
|
| ---------------------------- | -------- | -------------------------- | --------------------------------- |
|
|
144
187
|
| `REVENIUM_METERING_API_KEY` | Yes | - | Your Revenium API key |
|
|
145
188
|
| `ANTHROPIC_API_KEY` | Yes | - | Anthropic Claude API key |
|
|
146
|
-
| `REVENIUM_METERING_BASE_URL` | No | `https://api.revenium.
|
|
189
|
+
| `REVENIUM_METERING_BASE_URL` | No | `https://api.revenium.ai` | Revenium metering API base URL |
|
|
147
190
|
| `REVENIUM_DEBUG` | No | `false` | Enable debug logging (true/false) |
|
|
148
191
|
|
|
149
192
|
### Manual Configuration
|
|
@@ -191,14 +234,6 @@ npm install
|
|
|
191
234
|
npm run build
|
|
192
235
|
```
|
|
193
236
|
|
|
194
|
-
### OpenRouter Users
|
|
195
|
-
|
|
196
|
-
**Note:** Revenium's automatic token cost calculation uses AI model names from the providers themselves (e.g., `claude-sonnet-4-20250514`).
|
|
197
|
-
|
|
198
|
-
If you are using a service like OpenRouter that changes model names (from `claude-sonnet-4-20250514` to `anthropic/claude-sonnet-4`), automatic cost calculations may not work properly in Revenium (though all other processing will work normally).
|
|
199
|
-
|
|
200
|
-
Supporting OpenRouter AI model names is something we're working on for the future. If this is critical for your use case, please use the 'provide feedback' button in the Help Center to let us know.
|
|
201
|
-
|
|
202
237
|
### Debug Mode
|
|
203
238
|
|
|
204
239
|
Enable detailed logging to troubleshoot issues:
|
|
@@ -242,7 +277,9 @@ The middleware never blocks your application - if Revenium tracking fails, your
|
|
|
242
277
|
|
|
243
278
|
## Documentation
|
|
244
279
|
|
|
245
|
-
For detailed documentation, visit [docs.revenium.io](https://docs.revenium.io)
|
|
280
|
+
For detailed documentation, visit [docs.revenium.io](https://docs.revenium.io).
|
|
281
|
+
For contributor lifecycle, validation, and provider metadata, see
|
|
282
|
+
[docs/standardization/README.md](https://github.com/revenium/revenium-middleware-anthropic-node/blob/HEAD/docs/standardization/README.md).
|
|
246
283
|
|
|
247
284
|
## Contributing
|
|
248
285
|
|
|
@@ -264,6 +301,7 @@ This project is licensed under the MIT License - see the [LICENSE](https://githu
|
|
|
264
301
|
|
|
265
302
|
For issues, feature requests, or contributions:
|
|
266
303
|
|
|
304
|
+
- **Website**: [www.revenium.ai](https://www.revenium.ai)
|
|
267
305
|
- **GitHub Repository**: [revenium/revenium-middleware-anthropic-node](https://github.com/revenium/revenium-middleware-anthropic-node)
|
|
268
306
|
- **Issues**: [Report bugs or request features](https://github.com/revenium/revenium-middleware-anthropic-node/issues)
|
|
269
307
|
- **Documentation**: [docs.revenium.io](https://docs.revenium.io)
|
package/dist/cjs/constants.js
CHANGED
|
@@ -10,7 +10,7 @@ exports.ANTHROPIC_PATTERNS = exports.API_ENDPOINTS = exports.ENV_VARS = exports.
|
|
|
10
10
|
*/
|
|
11
11
|
exports.DEFAULT_CONFIG = {
|
|
12
12
|
/** Default Revenium API base URL */
|
|
13
|
-
REVENIUM_BASE_URL: 'https://api.revenium.
|
|
13
|
+
REVENIUM_BASE_URL: 'https://api.revenium.ai',
|
|
14
14
|
/** Default API timeout in milliseconds */
|
|
15
15
|
API_TIMEOUT: 5000,
|
|
16
16
|
/** Default maximum retries for failed API calls */
|
package/dist/cjs/tracking.js
CHANGED
|
@@ -92,7 +92,7 @@ async function sendReveniumMetrics(data) {
|
|
|
92
92
|
duration: data.duration,
|
|
93
93
|
});
|
|
94
94
|
return response;
|
|
95
|
-
}, config.maxRetries
|
|
95
|
+
}, config.maxRetries ?? constants_1.DEFAULT_CONFIG.MAX_RETRIES);
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
catch (error) {
|
|
@@ -102,8 +102,12 @@ async function sendReveniumMetrics(data) {
|
|
|
102
102
|
.withDuration(data.duration)
|
|
103
103
|
.build();
|
|
104
104
|
(0, error_handling_1.handleError)(error, logger, errorContext);
|
|
105
|
-
//
|
|
106
|
-
//
|
|
105
|
+
// Respect failSilent configuration
|
|
106
|
+
// By default (failSilent=true), tracking errors don't break the user's application
|
|
107
|
+
// If failSilent=false, re-throw the error for user handling
|
|
108
|
+
if (config.failSilent === false) {
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
107
111
|
}
|
|
108
112
|
finally {
|
|
109
113
|
clearTimeout(timeoutId);
|
|
@@ -116,6 +120,20 @@ function buildReveniumPayload(data) {
|
|
|
116
120
|
const now = new Date().toISOString();
|
|
117
121
|
const requestTime = data.requestTime.toISOString();
|
|
118
122
|
const completionStartTime = data.responseTime.toISOString();
|
|
123
|
+
// Standard known fields
|
|
124
|
+
const knownFields = new Set([
|
|
125
|
+
'taskType', 'agent', 'organizationId', 'productId', 'subscriber',
|
|
126
|
+
'subscriptionId', 'traceId', 'responseQualityScore'
|
|
127
|
+
]);
|
|
128
|
+
// Extract custom fields (anything NOT in the standard 8 fields)
|
|
129
|
+
const customFields = {};
|
|
130
|
+
if (data.metadata) {
|
|
131
|
+
for (const [key, value] of Object.entries(data.metadata)) {
|
|
132
|
+
if (!knownFields.has(key)) {
|
|
133
|
+
customFields[key] = value;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
119
137
|
return {
|
|
120
138
|
stopReason: getStopReason(data.stopReason),
|
|
121
139
|
costType: "AI",
|
|
@@ -144,6 +162,7 @@ function buildReveniumPayload(data) {
|
|
|
144
162
|
traceId: data.metadata?.traceId,
|
|
145
163
|
responseQualityScore: data.metadata?.responseQualityScore, // Fixed: Now sending to Revenium
|
|
146
164
|
middlewareSource: "nodejs",
|
|
165
|
+
...customFields, // Preserve any custom metadata fields
|
|
147
166
|
};
|
|
148
167
|
}
|
|
149
168
|
/**
|
|
@@ -184,6 +203,12 @@ function trackUsageAsync(trackingData) {
|
|
|
184
203
|
requestId: trackingData.requestId,
|
|
185
204
|
context: errorContext,
|
|
186
205
|
});
|
|
206
|
+
// If failSilent=false, propagate the error
|
|
207
|
+
// Note: Since this is fire-and-forget, the error won't be caught by caller
|
|
208
|
+
// but it will appear as an unhandled promise rejection
|
|
209
|
+
if (config.failSilent === false) {
|
|
210
|
+
throw error;
|
|
211
|
+
}
|
|
187
212
|
});
|
|
188
213
|
}
|
|
189
214
|
/**
|
|
@@ -160,17 +160,17 @@ function validateReveniumConfig(config) {
|
|
|
160
160
|
}
|
|
161
161
|
catch {
|
|
162
162
|
errors.push('reveniumBaseUrl must be a valid URL');
|
|
163
|
-
suggestions.push('Use format: https://api.revenium.
|
|
163
|
+
suggestions.push('Use format: https://api.revenium.ai');
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
// Validate optional Anthropic API key
|
|
167
|
-
if (!isString(cfg?.anthropicApiKey)) {
|
|
167
|
+
if (cfg?.anthropicApiKey !== undefined && !isString(cfg?.anthropicApiKey)) {
|
|
168
168
|
errors.push('anthropicApiKey must be a string if provided');
|
|
169
169
|
}
|
|
170
|
-
else if (cfg?.anthropicApiKey?.trim()?.length === 0) {
|
|
170
|
+
else if (cfg?.anthropicApiKey !== undefined && cfg?.anthropicApiKey?.trim()?.length === 0) {
|
|
171
171
|
warnings.push('anthropicApiKey is empty - API calls may fail');
|
|
172
172
|
}
|
|
173
|
-
else if (!cfg?.anthropicApiKey?.startsWith(constants_1.VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX)) {
|
|
173
|
+
else if (cfg?.anthropicApiKey && !cfg?.anthropicApiKey?.startsWith(constants_1.VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX)) {
|
|
174
174
|
warnings.push(`anthropicApiKey does not start with "${constants_1.VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX}" - verify it is correct`);
|
|
175
175
|
}
|
|
176
176
|
// Validate optional timeout using constants
|
package/dist/cjs/wrapper.js
CHANGED
|
@@ -42,9 +42,12 @@ function getMessagesPrototype() {
|
|
|
42
42
|
return anthropicConstructor?._Messages?.prototype;
|
|
43
43
|
// Method 3: Create a minimal instance with the real API key if available
|
|
44
44
|
// Fallback approach when direct prototype access methods fail
|
|
45
|
-
|
|
45
|
+
// Check config first, then environment variable
|
|
46
|
+
const config = (0, config_1.getConfig)();
|
|
47
|
+
const apiKey = config?.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
46
48
|
if (!apiKey) {
|
|
47
|
-
throw new error_handling_1.AnthropicPatchingError('Unable to access Anthropic Messages prototype: No API key available and direct prototype access failed'
|
|
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.');
|
|
48
51
|
}
|
|
49
52
|
const minimalInstance = new sdk_1.default({ apiKey });
|
|
50
53
|
const messagesPrototype = Object.getPrototypeOf(minimalInstance.messages);
|
|
@@ -155,10 +158,12 @@ async function handleStreamingResponse(stream, context) {
|
|
|
155
158
|
const endTime = Date.now();
|
|
156
159
|
const responseTime = new Date();
|
|
157
160
|
const duration = endTime - startTime;
|
|
161
|
+
const timeToFirstToken = firstTokenTime ? firstTokenTime - startTime : undefined;
|
|
158
162
|
logger.debug('Stream completed, extracting usage', {
|
|
159
163
|
requestId,
|
|
160
164
|
chunkCount: chunks.length,
|
|
161
|
-
duration
|
|
165
|
+
duration,
|
|
166
|
+
timeToFirstToken
|
|
162
167
|
});
|
|
163
168
|
const usage = (0, tracking_1.extractUsageFromStream)(chunks);
|
|
164
169
|
// Create tracking data
|
|
@@ -174,7 +179,8 @@ async function handleStreamingResponse(stream, context) {
|
|
|
174
179
|
stopReason: usage.stopReason,
|
|
175
180
|
metadata,
|
|
176
181
|
requestTime,
|
|
177
|
-
responseTime
|
|
182
|
+
responseTime,
|
|
183
|
+
timeToFirstToken
|
|
178
184
|
};
|
|
179
185
|
// Track usage asynchronously
|
|
180
186
|
(0, tracking_1.trackUsageAsync)(trackingData);
|
package/dist/esm/constants.js
CHANGED
|
@@ -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.
|
|
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 */
|
package/dist/esm/tracking.js
CHANGED
|
@@ -83,7 +83,7 @@ export async function sendReveniumMetrics(data) {
|
|
|
83
83
|
duration: data.duration,
|
|
84
84
|
});
|
|
85
85
|
return response;
|
|
86
|
-
}, config.maxRetries
|
|
86
|
+
}, config.maxRetries ?? DEFAULT_CONFIG.MAX_RETRIES);
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
89
|
catch (error) {
|
|
@@ -93,8 +93,12 @@ export async function sendReveniumMetrics(data) {
|
|
|
93
93
|
.withDuration(data.duration)
|
|
94
94
|
.build();
|
|
95
95
|
handleError(error, logger, errorContext);
|
|
96
|
-
//
|
|
97
|
-
//
|
|
96
|
+
// Respect failSilent configuration
|
|
97
|
+
// By default (failSilent=true), tracking errors don't break the user's application
|
|
98
|
+
// If failSilent=false, re-throw the error for user handling
|
|
99
|
+
if (config.failSilent === false) {
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
98
102
|
}
|
|
99
103
|
finally {
|
|
100
104
|
clearTimeout(timeoutId);
|
|
@@ -107,6 +111,20 @@ function buildReveniumPayload(data) {
|
|
|
107
111
|
const now = new Date().toISOString();
|
|
108
112
|
const requestTime = data.requestTime.toISOString();
|
|
109
113
|
const completionStartTime = data.responseTime.toISOString();
|
|
114
|
+
// Standard known fields
|
|
115
|
+
const knownFields = new Set([
|
|
116
|
+
'taskType', 'agent', 'organizationId', 'productId', 'subscriber',
|
|
117
|
+
'subscriptionId', 'traceId', 'responseQualityScore'
|
|
118
|
+
]);
|
|
119
|
+
// Extract custom fields (anything NOT in the standard 8 fields)
|
|
120
|
+
const customFields = {};
|
|
121
|
+
if (data.metadata) {
|
|
122
|
+
for (const [key, value] of Object.entries(data.metadata)) {
|
|
123
|
+
if (!knownFields.has(key)) {
|
|
124
|
+
customFields[key] = value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
110
128
|
return {
|
|
111
129
|
stopReason: getStopReason(data.stopReason),
|
|
112
130
|
costType: "AI",
|
|
@@ -135,6 +153,7 @@ function buildReveniumPayload(data) {
|
|
|
135
153
|
traceId: data.metadata?.traceId,
|
|
136
154
|
responseQualityScore: data.metadata?.responseQualityScore, // Fixed: Now sending to Revenium
|
|
137
155
|
middlewareSource: "nodejs",
|
|
156
|
+
...customFields, // Preserve any custom metadata fields
|
|
138
157
|
};
|
|
139
158
|
}
|
|
140
159
|
/**
|
|
@@ -175,6 +194,12 @@ export function trackUsageAsync(trackingData) {
|
|
|
175
194
|
requestId: trackingData.requestId,
|
|
176
195
|
context: errorContext,
|
|
177
196
|
});
|
|
197
|
+
// If failSilent=false, propagate the error
|
|
198
|
+
// Note: Since this is fire-and-forget, the error won't be caught by caller
|
|
199
|
+
// but it will appear as an unhandled promise rejection
|
|
200
|
+
if (config.failSilent === false) {
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
178
203
|
});
|
|
179
204
|
}
|
|
180
205
|
/**
|
|
@@ -149,17 +149,17 @@ export function validateReveniumConfig(config) {
|
|
|
149
149
|
}
|
|
150
150
|
catch {
|
|
151
151
|
errors.push('reveniumBaseUrl must be a valid URL');
|
|
152
|
-
suggestions.push('Use format: https://api.revenium.
|
|
152
|
+
suggestions.push('Use format: https://api.revenium.ai');
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
// Validate optional Anthropic API key
|
|
156
|
-
if (!isString(cfg?.anthropicApiKey)) {
|
|
156
|
+
if (cfg?.anthropicApiKey !== undefined && !isString(cfg?.anthropicApiKey)) {
|
|
157
157
|
errors.push('anthropicApiKey must be a string if provided');
|
|
158
158
|
}
|
|
159
|
-
else if (cfg?.anthropicApiKey?.trim()?.length === 0) {
|
|
159
|
+
else if (cfg?.anthropicApiKey !== undefined && cfg?.anthropicApiKey?.trim()?.length === 0) {
|
|
160
160
|
warnings.push('anthropicApiKey is empty - API calls may fail');
|
|
161
161
|
}
|
|
162
|
-
else if (!cfg?.anthropicApiKey?.startsWith(VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX)) {
|
|
162
|
+
else if (cfg?.anthropicApiKey && !cfg?.anthropicApiKey?.startsWith(VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX)) {
|
|
163
163
|
warnings.push(`anthropicApiKey does not start with "${VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX}" - verify it is correct`);
|
|
164
164
|
}
|
|
165
165
|
// Validate optional timeout using constants
|
package/dist/esm/wrapper.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Anthropic SDK wrapper with type safety and structured error handling
|
|
3
3
|
*/
|
|
4
4
|
import Anthropic from '@anthropic-ai/sdk';
|
|
5
|
-
import { getLogger } from './config.js';
|
|
5
|
+
import { getLogger, getConfig } from './config.js';
|
|
6
6
|
import { trackUsageAsync, extractUsageFromResponse, extractUsageFromStream } from './tracking.js';
|
|
7
7
|
import { validateAnthropicMessageParams, validateUsageMetadata } from './utils/validation.js';
|
|
8
8
|
import { AnthropicPatchingError, RequestProcessingError, StreamProcessingError, createErrorContext, handleError } from './utils/error-handling.js';
|
|
@@ -34,9 +34,12 @@ function getMessagesPrototype() {
|
|
|
34
34
|
return anthropicConstructor?._Messages?.prototype;
|
|
35
35
|
// Method 3: Create a minimal instance with the real API key if available
|
|
36
36
|
// Fallback approach when direct prototype access methods fail
|
|
37
|
-
|
|
37
|
+
// Check config first, then environment variable
|
|
38
|
+
const config = getConfig();
|
|
39
|
+
const apiKey = config?.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
38
40
|
if (!apiKey) {
|
|
39
|
-
throw new AnthropicPatchingError('Unable to access Anthropic Messages prototype: No API key available and direct prototype access failed'
|
|
41
|
+
throw new AnthropicPatchingError('Unable to access Anthropic Messages prototype: No API key available and direct prototype access failed. ' +
|
|
42
|
+
'Provide ANTHROPIC_API_KEY environment variable or pass anthropicApiKey in config.');
|
|
40
43
|
}
|
|
41
44
|
const minimalInstance = new Anthropic({ apiKey });
|
|
42
45
|
const messagesPrototype = Object.getPrototypeOf(minimalInstance.messages);
|
|
@@ -147,10 +150,12 @@ async function handleStreamingResponse(stream, context) {
|
|
|
147
150
|
const endTime = Date.now();
|
|
148
151
|
const responseTime = new Date();
|
|
149
152
|
const duration = endTime - startTime;
|
|
153
|
+
const timeToFirstToken = firstTokenTime ? firstTokenTime - startTime : undefined;
|
|
150
154
|
logger.debug('Stream completed, extracting usage', {
|
|
151
155
|
requestId,
|
|
152
156
|
chunkCount: chunks.length,
|
|
153
|
-
duration
|
|
157
|
+
duration,
|
|
158
|
+
timeToFirstToken
|
|
154
159
|
});
|
|
155
160
|
const usage = extractUsageFromStream(chunks);
|
|
156
161
|
// Create tracking data
|
|
@@ -166,7 +171,8 @@ async function handleStreamingResponse(stream, context) {
|
|
|
166
171
|
stopReason: usage.stopReason,
|
|
167
172
|
metadata,
|
|
168
173
|
requestTime,
|
|
169
|
-
responseTime
|
|
174
|
+
responseTime,
|
|
175
|
+
timeToFirstToken
|
|
170
176
|
};
|
|
171
177
|
// Track usage asynchronously
|
|
172
178
|
trackUsageAsync(trackingData);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export declare const DEFAULT_CONFIG: {
|
|
9
9
|
/** Default Revenium API base URL */
|
|
10
|
-
readonly REVENIUM_BASE_URL: "https://api.revenium.
|
|
10
|
+
readonly REVENIUM_BASE_URL: "https://api.revenium.ai";
|
|
11
11
|
/** Default API timeout in milliseconds */
|
|
12
12
|
readonly API_TIMEOUT: 5000;
|
|
13
13
|
/** Default maximum retries for failed API calls */
|
package/dist/types/types.d.ts
CHANGED
|
@@ -51,9 +51,9 @@ export interface Subscriber {
|
|
|
51
51
|
* @example
|
|
52
52
|
* ```typescript
|
|
53
53
|
* const config: ReveniumConfig = {
|
|
54
|
-
* reveniumApiKey: '
|
|
55
|
-
* reveniumBaseUrl: 'https://api.revenium.
|
|
56
|
-
* anthropicApiKey: 'sk-ant-
|
|
54
|
+
* reveniumApiKey: 'hak_your_key_value_here',
|
|
55
|
+
* reveniumBaseUrl: 'https://api.revenium.ai',
|
|
56
|
+
* anthropicApiKey: 'sk-ant-your_api_key_here',
|
|
57
57
|
* apiTimeout: 8000,
|
|
58
58
|
* failSilent: true,
|
|
59
59
|
* maxRetries: 3
|
package/examples/README.md
CHANGED
|
@@ -32,7 +32,7 @@ REVENIUM_METERING_API_KEY=hak_your_revenium_api_key
|
|
|
32
32
|
ANTHROPIC_API_KEY=sk-ant-your_anthropic_api_key
|
|
33
33
|
|
|
34
34
|
# Optional
|
|
35
|
-
REVENIUM_METERING_BASE_URL=https://api.revenium.
|
|
35
|
+
REVENIUM_METERING_BASE_URL=https://api.revenium.ai
|
|
36
36
|
REVENIUM_DEBUG=false
|
|
37
37
|
```
|
|
38
38
|
|
|
@@ -46,6 +46,7 @@ npm run example:basic
|
|
|
46
46
|
npm run example:advanced
|
|
47
47
|
|
|
48
48
|
# Or use tsx directly
|
|
49
|
+
npx tsx examples/getting_started.ts
|
|
49
50
|
npx tsx examples/basic-usage.ts
|
|
50
51
|
npx tsx examples/advanced-features.ts
|
|
51
52
|
```
|
|
@@ -55,12 +56,32 @@ npx tsx examples/advanced-features.ts
|
|
|
55
56
|
Examples are included in your `node_modules/@revenium/anthropic/examples/` directory:
|
|
56
57
|
|
|
57
58
|
```bash
|
|
59
|
+
npx tsx node_modules/@revenium/anthropic/examples/getting_started.ts
|
|
58
60
|
npx tsx node_modules/@revenium/anthropic/examples/basic-usage.ts
|
|
59
61
|
npx tsx node_modules/@revenium/anthropic/examples/advanced-features.ts
|
|
60
62
|
```
|
|
61
63
|
|
|
62
64
|
## Available Examples
|
|
63
65
|
|
|
66
|
+
### `getting_started.ts` - Simple Entry Point
|
|
67
|
+
|
|
68
|
+
The simplest example to get you started with Revenium tracking:
|
|
69
|
+
|
|
70
|
+
- **Minimal setup** - Just import, configure, and start tracking
|
|
71
|
+
- **Complete metadata example** - Shows all 8 optional metadata fields
|
|
72
|
+
- **Ready to customize** - Uncomment the metadata section to add tracking context
|
|
73
|
+
|
|
74
|
+
**Key Features:**
|
|
75
|
+
|
|
76
|
+
- Auto-initialization from environment variables
|
|
77
|
+
- Native `usageMetadata` support via module augmentation
|
|
78
|
+
- All metadata fields documented with examples
|
|
79
|
+
- Single API call demonstration
|
|
80
|
+
|
|
81
|
+
**Perfect for:** First-time users, quick validation, understanding metadata structure
|
|
82
|
+
|
|
83
|
+
**See the file for complete code examples.**
|
|
84
|
+
|
|
64
85
|
### `basic-usage.ts` - Initialization Methods
|
|
65
86
|
|
|
66
87
|
Demonstrates the three ways to initialize the Revenium middleware:
|
|
@@ -116,7 +137,7 @@ Ensure your `tsconfig.json` includes:
|
|
|
116
137
|
|
|
117
138
|
## Requirements
|
|
118
139
|
|
|
119
|
-
- **Node.js
|
|
140
|
+
- **Node.js 18+** with TypeScript support
|
|
120
141
|
- **TypeScript 4.5+** for module augmentation features
|
|
121
142
|
- **Valid Revenium API key** (starts with `hak_`)
|
|
122
143
|
- **Valid Anthropic API key** (starts with `sk-ant-`)
|
|
@@ -122,7 +122,7 @@ async function demonstrateAdvancedFeatures(): Promise<void> {
|
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
const stream1 = await anthropic.messages.create({
|
|
125
|
-
model: "claude-
|
|
125
|
+
model: "claude-haiku-4-5",
|
|
126
126
|
max_tokens: 150,
|
|
127
127
|
messages: [
|
|
128
128
|
{
|
|
@@ -187,7 +187,7 @@ async function demonstrateAdvancedFeatures(): Promise<void> {
|
|
|
187
187
|
const now = new Date();
|
|
188
188
|
const trackingData: TrackingData = {
|
|
189
189
|
requestId: `manual_${Date.now()}`,
|
|
190
|
-
model: "claude-
|
|
190
|
+
model: "claude-haiku-4-5",
|
|
191
191
|
inputTokens: 25,
|
|
192
192
|
outputTokens: 150,
|
|
193
193
|
duration: 2500,
|
|
@@ -245,7 +245,7 @@ async function demonstrateAdvancedFeatures(): Promise<void> {
|
|
|
245
245
|
} satisfies UsageMetadata;
|
|
246
246
|
|
|
247
247
|
const stream2 = await anthropic.messages.create({
|
|
248
|
-
model: "claude-
|
|
248
|
+
model: "claude-haiku-4-5",
|
|
249
249
|
max_tokens: 100,
|
|
250
250
|
messages: [
|
|
251
251
|
{
|
|
@@ -296,7 +296,7 @@ async function demonstrateAdvancedFeatures(): Promise<void> {
|
|
|
296
296
|
} satisfies UsageMetadata;
|
|
297
297
|
|
|
298
298
|
const toolStream = await anthropic.messages.create({
|
|
299
|
-
model: "claude-
|
|
299
|
+
model: "claude-haiku-4-5",
|
|
300
300
|
max_tokens: 200,
|
|
301
301
|
tools: [
|
|
302
302
|
{
|
|
@@ -432,7 +432,7 @@ function checkEnvironment(): void {
|
|
|
432
432
|
console.error(" REVENIUM_METERING_API_KEY=hak_your_api_key");
|
|
433
433
|
console.error(" ANTHROPIC_API_KEY=sk-ant-your_anthropic_key");
|
|
434
434
|
console.error("\nOptional (uses defaults if not set):");
|
|
435
|
-
console.error(" REVENIUM_METERING_BASE_URL=https://api.revenium.
|
|
435
|
+
console.error(" REVENIUM_METERING_BASE_URL=https://api.revenium.ai");
|
|
436
436
|
console.error(" REVENIUM_DEBUG=true # For detailed logging");
|
|
437
437
|
process.exit(1);
|
|
438
438
|
}
|
package/examples/basic-usage.ts
CHANGED
|
@@ -52,7 +52,7 @@ async function demonstrateBasicUsage() {
|
|
|
52
52
|
// Example 1: Basic request without metadata (still tracked automatically)
|
|
53
53
|
console.log("Example 1: Basic request without metadata...");
|
|
54
54
|
const basicResponse = await anthropic.messages.create({
|
|
55
|
-
model: "claude-
|
|
55
|
+
model: "claude-haiku-4-5",
|
|
56
56
|
max_tokens: 50,
|
|
57
57
|
messages: [
|
|
58
58
|
{ role: "user", content: "What is the capital of France? Be concise." },
|
|
@@ -73,7 +73,7 @@ async function demonstrateBasicUsage() {
|
|
|
73
73
|
// Example 2: Request with rich metadata for enhanced tracking
|
|
74
74
|
console.log("Example 2: Request with rich metadata...");
|
|
75
75
|
const metadataResponse = await anthropic.messages.create({
|
|
76
|
-
model: "claude-
|
|
76
|
+
model: "claude-haiku-4-5",
|
|
77
77
|
max_tokens: 80,
|
|
78
78
|
messages: [
|
|
79
79
|
{ role: "user", content: "Explain quantum computing in one sentence." },
|
|
@@ -146,7 +146,7 @@ async function demonstrateExplicitInitialization() {
|
|
|
146
146
|
// Now use Anthropic with guaranteed tracking
|
|
147
147
|
const anthropic = new Anthropic();
|
|
148
148
|
const response = await anthropic.messages.create({
|
|
149
|
-
model: "claude-
|
|
149
|
+
model: "claude-haiku-4-5",
|
|
150
150
|
max_tokens: 60,
|
|
151
151
|
messages: [
|
|
152
152
|
{
|
|
@@ -194,7 +194,7 @@ async function demonstrateManualConfiguration() {
|
|
|
194
194
|
reveniumApiKey:
|
|
195
195
|
process.env.REVENIUM_METERING_API_KEY || "hak_your_api_key_here",
|
|
196
196
|
reveniumBaseUrl:
|
|
197
|
-
process.env.REVENIUM_METERING_BASE_URL || "https://api.revenium.
|
|
197
|
+
process.env.REVENIUM_METERING_BASE_URL || "https://api.revenium.ai",
|
|
198
198
|
|
|
199
199
|
// Optional: Anthropic API key (can also be set in Anthropic client)
|
|
200
200
|
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
|
@@ -222,7 +222,7 @@ async function demonstrateManualConfiguration() {
|
|
|
222
222
|
});
|
|
223
223
|
|
|
224
224
|
const response = await anthropic.messages.create({
|
|
225
|
-
model: "claude-
|
|
225
|
+
model: "claude-haiku-4-5",
|
|
226
226
|
max_tokens: 80,
|
|
227
227
|
messages: [
|
|
228
228
|
{
|
|
@@ -279,7 +279,7 @@ function checkEnvironment() {
|
|
|
279
279
|
console.error(" REVENIUM_METERING_API_KEY=hak_your_api_key");
|
|
280
280
|
console.error(" ANTHROPIC_API_KEY=sk-ant-your_anthropic_key");
|
|
281
281
|
console.error("\nOptional (uses defaults if not set):");
|
|
282
|
-
console.error(" REVENIUM_METERING_BASE_URL=https://api.revenium.
|
|
282
|
+
console.error(" REVENIUM_METERING_BASE_URL=https://api.revenium.ai");
|
|
283
283
|
console.error(" REVENIUM_DEBUG=true # For detailed logging");
|
|
284
284
|
process.exit(1);
|
|
285
285
|
}
|
|
@@ -8,7 +8,7 @@ async function main() {
|
|
|
8
8
|
|
|
9
9
|
// Chat completion with metadata
|
|
10
10
|
const response = await anthropic.messages.create({
|
|
11
|
-
model: 'claude-
|
|
11
|
+
model: 'claude-haiku-4-5',
|
|
12
12
|
max_tokens: 2000,
|
|
13
13
|
messages: [
|
|
14
14
|
{ role: 'user', content: 'Please verify you are ready to assist me.' }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenium/anthropic",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
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",
|
|
@@ -73,12 +73,15 @@
|
|
|
73
73
|
"dist/esm/**/*.js",
|
|
74
74
|
"dist/types/**/*.d.ts",
|
|
75
75
|
"examples/**/*",
|
|
76
|
+
"!examples/.claude/**",
|
|
77
|
+
"!examples/**/*.log",
|
|
78
|
+
"!examples/**/*.sh",
|
|
76
79
|
"README.md",
|
|
77
80
|
"CHANGELOG.md",
|
|
78
81
|
"LICENSE"
|
|
79
82
|
],
|
|
80
83
|
"sideEffects": false,
|
|
81
84
|
"engines": {
|
|
82
|
-
"node": ">=
|
|
85
|
+
"node": ">=18.0.0"
|
|
83
86
|
}
|
|
84
87
|
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Read(//Users/daithi/git/rev/git/revenium-middleware-anthropic-node/**)",
|
|
5
|
-
"Bash(npm init:*)",
|
|
6
|
-
"Bash(npm install:*)",
|
|
7
|
-
"Bash(cp:*)",
|
|
8
|
-
"Bash(npx tsx:*)",
|
|
9
|
-
"WebFetch(domain:revenium.readme.io)",
|
|
10
|
-
"Bash(tee:*)",
|
|
11
|
-
"Bash(npm run clean:*)",
|
|
12
|
-
"Bash(npm run build:*)",
|
|
13
|
-
"Bash(node -e \"require(''@revenium/anthropic''); console.log(''✅ Option A (Auto-init) - Import works'')\")"
|
|
14
|
-
],
|
|
15
|
-
"deny": [],
|
|
16
|
-
"ask": []
|
|
17
|
-
}
|
|
18
|
-
}
|