@revenium/anthropic 1.0.7 → 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 +19 -0
- package/README.md +49 -3
- 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 +3 -3
- package/examples/advanced-features.ts +1 -1
- package/examples/basic-usage.ts +2 -2
- package/package.json +5 -2
- package/examples/.claude/settings.local.json +0 -24
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
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
|
+
|
|
5
24
|
## [1.0.7] - 2025-10-25
|
|
6
25
|
|
|
7
26
|
### Changed
|
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
|
|
@@ -234,7 +277,9 @@ The middleware never blocks your application - if Revenium tracking fails, your
|
|
|
234
277
|
|
|
235
278
|
## Documentation
|
|
236
279
|
|
|
237
|
-
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).
|
|
238
283
|
|
|
239
284
|
## Contributing
|
|
240
285
|
|
|
@@ -256,6 +301,7 @@ This project is licensed under the MIT License - see the [LICENSE](https://githu
|
|
|
256
301
|
|
|
257
302
|
For issues, feature requests, or contributions:
|
|
258
303
|
|
|
304
|
+
- **Website**: [www.revenium.ai](https://www.revenium.ai)
|
|
259
305
|
- **GitHub Repository**: [revenium/revenium-middleware-anthropic-node](https://github.com/revenium/revenium-middleware-anthropic-node)
|
|
260
306
|
- **Issues**: [Report bugs or request features](https://github.com/revenium/revenium-middleware-anthropic-node/issues)
|
|
261
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
|
|
|
@@ -68,7 +68,7 @@ npx tsx node_modules/@revenium/anthropic/examples/advanced-features.ts
|
|
|
68
68
|
The simplest example to get you started with Revenium tracking:
|
|
69
69
|
|
|
70
70
|
- **Minimal setup** - Just import, configure, and start tracking
|
|
71
|
-
- **Complete metadata example** - Shows all
|
|
71
|
+
- **Complete metadata example** - Shows all 8 optional metadata fields
|
|
72
72
|
- **Ready to customize** - Uncomment the metadata section to add tracking context
|
|
73
73
|
|
|
74
74
|
**Key Features:**
|
|
@@ -137,7 +137,7 @@ Ensure your `tsconfig.json` includes:
|
|
|
137
137
|
|
|
138
138
|
## Requirements
|
|
139
139
|
|
|
140
|
-
- **Node.js
|
|
140
|
+
- **Node.js 18+** with TypeScript support
|
|
141
141
|
- **TypeScript 4.5+** for module augmentation features
|
|
142
142
|
- **Valid Revenium API key** (starts with `hak_`)
|
|
143
143
|
- **Valid Anthropic API key** (starts with `sk-ant-`)
|
|
@@ -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
|
@@ -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,
|
|
@@ -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
|
}
|
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,24 +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
|
-
"Bash(for section in \"Features\" \"Package Migration\" \"Getting Started\" \"Advanced Usage\" \"Configuration Options\" \"Troubleshooting\" \"Requirements\" \"Documentation\" \"Contributing\" \"Code of Conduct\" \"Security\" \"License\" \"Support\" \"Development\")",
|
|
15
|
-
"Bash(do)",
|
|
16
|
-
"Bash(echo:*)",
|
|
17
|
-
"Bash(done)",
|
|
18
|
-
"Bash(grep:*)",
|
|
19
|
-
"Bash(gitleaks detect:*)"
|
|
20
|
-
],
|
|
21
|
-
"deny": [],
|
|
22
|
-
"ask": []
|
|
23
|
-
}
|
|
24
|
-
}
|