@revenium/anthropic 1.0.7 → 1.0.9
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 +53 -1
- package/README.md +174 -21
- package/dist/cjs/config.js +55 -7
- package/dist/cjs/constants.js +39 -24
- package/dist/cjs/tracking.js +107 -20
- package/dist/cjs/types/anthropic-augmentation.js +0 -62
- package/dist/cjs/utils/summary-printer.js +189 -0
- package/dist/cjs/utils/trace-fields.js +117 -0
- package/dist/cjs/utils/validation.js +58 -26
- package/dist/cjs/wrapper.js +54 -40
- package/dist/esm/config.js +55 -7
- package/dist/esm/constants.js +38 -23
- package/dist/esm/tracking.js +107 -20
- package/dist/esm/types/anthropic-augmentation.js +0 -62
- package/dist/esm/utils/summary-printer.js +186 -0
- package/dist/esm/utils/trace-fields.js +106 -0
- package/dist/esm/utils/validation.js +59 -27
- package/dist/esm/wrapper.js +60 -46
- package/dist/types/config.d.ts +1 -0
- package/dist/types/constants.d.ts +16 -1
- package/dist/types/types/anthropic-augmentation.d.ts +0 -92
- package/dist/types/types.d.ts +28 -196
- package/dist/types/utils/summary-printer.d.ts +3 -0
- package/dist/types/utils/trace-fields.d.ts +10 -0
- package/examples/README.md +3 -3
- package/examples/advanced.ts +128 -0
- package/examples/basic.ts +132 -0
- package/examples/getting_started.ts +6 -6
- package/examples/metadata.ts +58 -0
- package/package.json +7 -6
- package/examples/.claude/settings.local.json +0 -24
- package/examples/advanced-features.ts +0 -469
- package/examples/basic-usage.ts +0 -314
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Validation utilities for Anthropic middleware
|
|
3
3
|
* Provides type-safe validation with detailed error reporting
|
|
4
4
|
*/
|
|
5
|
-
import { VALIDATION_CONFIG, ANTHROPIC_PATTERNS } from '../constants.js';
|
|
5
|
+
import { VALIDATION_CONFIG, ANTHROPIC_PATTERNS, DEFAULT_CONFIG } from '../constants.js';
|
|
6
6
|
/**
|
|
7
7
|
* Type guard for checking if a value is a non-null object
|
|
8
8
|
*/
|
|
@@ -128,38 +128,40 @@ export function validateReveniumConfig(config) {
|
|
|
128
128
|
else if (cfg?.reveniumApiKey?.length < VALIDATION_CONFIG.MIN_API_KEY_LENGTH) {
|
|
129
129
|
warnings.push('reveniumApiKey appears to be too short - verify it is correct');
|
|
130
130
|
}
|
|
131
|
-
// Validate Revenium base URL
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
131
|
+
// Validate Revenium base URL (optional - defaults to https://api.revenium.ai)
|
|
132
|
+
if (cfg?.reveniumBaseUrl !== undefined) {
|
|
133
|
+
if (!isString(cfg?.reveniumBaseUrl)) {
|
|
134
|
+
errors.push('reveniumBaseUrl must be a string if provided');
|
|
135
|
+
}
|
|
136
|
+
else if (!cfg?.reveniumBaseUrl?.trim()) {
|
|
137
|
+
errors.push('reveniumBaseUrl cannot be empty if provided');
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
try {
|
|
141
|
+
const url = new URL(cfg?.reveniumBaseUrl);
|
|
142
|
+
if (!url.protocol.startsWith('http')) {
|
|
143
|
+
errors.push('reveniumBaseUrl must use HTTP or HTTPS protocol');
|
|
144
|
+
}
|
|
145
|
+
// Check for localhost/development hostnames (IPv4, IPv6, and named)
|
|
146
|
+
const localhostHostnames = ['localhost', '127.0.0.1', '::1', '[::1]'];
|
|
147
|
+
if (localhostHostnames.includes(url.hostname)) {
|
|
148
|
+
warnings.push('Using localhost for Revenium API - ensure this is intended for development');
|
|
149
|
+
}
|
|
143
150
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
warnings.push('Using localhost for Revenium API - ensure this is intended for development');
|
|
151
|
+
catch {
|
|
152
|
+
errors.push('reveniumBaseUrl must be a valid URL');
|
|
153
|
+
suggestions.push('Use format: https://api.revenium.ai');
|
|
148
154
|
}
|
|
149
155
|
}
|
|
150
|
-
catch {
|
|
151
|
-
errors.push('reveniumBaseUrl must be a valid URL');
|
|
152
|
-
suggestions.push('Use format: https://api.revenium.io');
|
|
153
|
-
}
|
|
154
156
|
}
|
|
155
157
|
// Validate optional Anthropic API key
|
|
156
|
-
if (!isString(cfg?.anthropicApiKey)) {
|
|
158
|
+
if (cfg?.anthropicApiKey !== undefined && !isString(cfg?.anthropicApiKey)) {
|
|
157
159
|
errors.push('anthropicApiKey must be a string if provided');
|
|
158
160
|
}
|
|
159
|
-
else if (cfg?.anthropicApiKey?.trim()?.length === 0) {
|
|
161
|
+
else if (cfg?.anthropicApiKey !== undefined && cfg?.anthropicApiKey?.trim()?.length === 0) {
|
|
160
162
|
warnings.push('anthropicApiKey is empty - API calls may fail');
|
|
161
163
|
}
|
|
162
|
-
else if (!cfg?.anthropicApiKey?.startsWith(VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX)) {
|
|
164
|
+
else if (cfg?.anthropicApiKey && !cfg?.anthropicApiKey?.startsWith(VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX)) {
|
|
163
165
|
warnings.push(`anthropicApiKey does not start with "${VALIDATION_CONFIG.ANTHROPIC_API_KEY_PREFIX}" - verify it is correct`);
|
|
164
166
|
}
|
|
165
167
|
// Validate optional timeout using constants
|
|
@@ -192,6 +194,23 @@ export function validateReveniumConfig(config) {
|
|
|
192
194
|
else if (cfg?.maxRetries !== undefined && cfg?.maxRetries === 0) {
|
|
193
195
|
warnings.push('maxRetries is 0 - no retry attempts will be made');
|
|
194
196
|
}
|
|
197
|
+
// Validate optional printSummary
|
|
198
|
+
if (cfg?.printSummary !== undefined) {
|
|
199
|
+
const validPrintSummaryValues = [true, false, 'human', 'json'];
|
|
200
|
+
if (!validPrintSummaryValues.includes(cfg?.printSummary)) {
|
|
201
|
+
errors.push("printSummary must be a boolean or one of: 'human', 'json'");
|
|
202
|
+
suggestions.push("Use printSummary: true, 'human', or 'json' to enable summary output");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Validate optional teamId
|
|
206
|
+
if (cfg?.teamId !== undefined) {
|
|
207
|
+
if (!isString(cfg?.teamId)) {
|
|
208
|
+
errors.push('teamId must be a string if provided');
|
|
209
|
+
}
|
|
210
|
+
else if (cfg?.teamId?.trim()?.length === 0) {
|
|
211
|
+
errors.push('teamId cannot be empty if provided');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
195
214
|
if (errors.length > 0) {
|
|
196
215
|
return {
|
|
197
216
|
isValid: false,
|
|
@@ -200,14 +219,27 @@ export function validateReveniumConfig(config) {
|
|
|
200
219
|
suggestions
|
|
201
220
|
};
|
|
202
221
|
}
|
|
203
|
-
//
|
|
222
|
+
// Determine validated printSummary value
|
|
223
|
+
let validatedPrintSummary;
|
|
224
|
+
if (cfg?.printSummary === true || cfg?.printSummary === 'human') {
|
|
225
|
+
validatedPrintSummary = 'human';
|
|
226
|
+
}
|
|
227
|
+
else if (cfg?.printSummary === 'json') {
|
|
228
|
+
validatedPrintSummary = 'json';
|
|
229
|
+
}
|
|
230
|
+
else if (cfg?.printSummary === false) {
|
|
231
|
+
validatedPrintSummary = false;
|
|
232
|
+
}
|
|
233
|
+
// Build validated config (apply default for reveniumBaseUrl if not provided)
|
|
204
234
|
const validatedConfig = {
|
|
205
235
|
reveniumApiKey: cfg?.reveniumApiKey,
|
|
206
|
-
reveniumBaseUrl: cfg?.reveniumBaseUrl,
|
|
236
|
+
reveniumBaseUrl: isString(cfg?.reveniumBaseUrl) ? cfg?.reveniumBaseUrl : DEFAULT_CONFIG.REVENIUM_BASE_URL,
|
|
207
237
|
anthropicApiKey: isString(cfg?.anthropicApiKey) ? cfg?.anthropicApiKey : undefined,
|
|
208
238
|
apiTimeout: isNumber(cfg?.apiTimeout) ? cfg?.apiTimeout : undefined,
|
|
209
239
|
failSilent: isBoolean(cfg?.failSilent) ? cfg?.failSilent : undefined,
|
|
210
|
-
maxRetries: isNumber(cfg?.maxRetries) ? cfg?.maxRetries : undefined
|
|
240
|
+
maxRetries: isNumber(cfg?.maxRetries) ? cfg?.maxRetries : undefined,
|
|
241
|
+
printSummary: validatedPrintSummary,
|
|
242
|
+
teamId: isString(cfg?.teamId) ? cfg?.teamId?.trim() : undefined
|
|
211
243
|
};
|
|
212
244
|
return {
|
|
213
245
|
isValid: true,
|
package/dist/esm/wrapper.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Anthropic SDK wrapper with type safety and structured error handling
|
|
3
3
|
*/
|
|
4
|
-
import Anthropic from
|
|
5
|
-
import { getLogger } from
|
|
6
|
-
import { trackUsageAsync, extractUsageFromResponse, extractUsageFromStream } from
|
|
7
|
-
import { validateAnthropicMessageParams, validateUsageMetadata } from
|
|
8
|
-
import { AnthropicPatchingError, RequestProcessingError, StreamProcessingError, createErrorContext, handleError } from
|
|
9
|
-
import { randomUUID } from
|
|
4
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
5
|
+
import { getLogger, getConfig } from "./config.js";
|
|
6
|
+
import { trackUsageAsync, extractUsageFromResponse, extractUsageFromStream, } from "./tracking.js";
|
|
7
|
+
import { validateAnthropicMessageParams, validateUsageMetadata, } from "./utils/validation.js";
|
|
8
|
+
import { AnthropicPatchingError, RequestProcessingError, StreamProcessingError, createErrorContext, handleError, } from "./utils/error-handling.js";
|
|
9
|
+
import { randomUUID } from "crypto";
|
|
10
10
|
// Global logger
|
|
11
11
|
const logger = getLogger();
|
|
12
12
|
/**
|
|
@@ -15,7 +15,7 @@ const logger = getLogger();
|
|
|
15
15
|
const patchingContext = {
|
|
16
16
|
originalMethods: {},
|
|
17
17
|
isPatched: false,
|
|
18
|
-
patchedInstances: new WeakSet()
|
|
18
|
+
patchedInstances: new WeakSet(),
|
|
19
19
|
};
|
|
20
20
|
/**
|
|
21
21
|
* Get the Messages prototype using sophisticated prototype access
|
|
@@ -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(
|
|
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);
|
|
@@ -54,19 +57,19 @@ function getMessagesPrototype() {
|
|
|
54
57
|
*/
|
|
55
58
|
export function patchAnthropic() {
|
|
56
59
|
if (patchingContext.isPatched) {
|
|
57
|
-
logger.debug(
|
|
60
|
+
logger.debug("Anthropic SDK already patched, skipping duplicate initialization");
|
|
58
61
|
return;
|
|
59
62
|
}
|
|
60
63
|
try {
|
|
61
64
|
// Access the Messages class prototype using sophisticated prototype access
|
|
62
65
|
const messagesPrototype = getMessagesPrototype();
|
|
63
66
|
if (!messagesPrototype)
|
|
64
|
-
throw new AnthropicPatchingError(
|
|
67
|
+
throw new AnthropicPatchingError("Unable to access Anthropic Messages prototype");
|
|
65
68
|
// Store original methods
|
|
66
69
|
patchingContext.originalMethods.create = messagesPrototype?.create;
|
|
67
70
|
patchingContext.originalMethods.stream = messagesPrototype?.stream;
|
|
68
71
|
if (!patchingContext.originalMethods?.create) {
|
|
69
|
-
throw new AnthropicPatchingError(
|
|
72
|
+
throw new AnthropicPatchingError("Unable to find original create method");
|
|
70
73
|
}
|
|
71
74
|
// Patch the create method
|
|
72
75
|
const patchedCreateFunction = function (params, options) {
|
|
@@ -80,11 +83,11 @@ export function patchAnthropic() {
|
|
|
80
83
|
};
|
|
81
84
|
}
|
|
82
85
|
patchingContext.isPatched = true;
|
|
83
|
-
logger.info(
|
|
86
|
+
logger.info("Anthropic SDK patched successfully");
|
|
84
87
|
}
|
|
85
88
|
catch (error) {
|
|
86
89
|
const errorContext = createErrorContext()
|
|
87
|
-
.with(
|
|
90
|
+
.with("patchingAttempt", true)
|
|
88
91
|
.build();
|
|
89
92
|
handleError(error, logger, errorContext);
|
|
90
93
|
if (error instanceof AnthropicPatchingError)
|
|
@@ -109,11 +112,11 @@ export function unpatchAnthropic() {
|
|
|
109
112
|
}
|
|
110
113
|
patchingContext.isPatched = false;
|
|
111
114
|
patchingContext.originalMethods = {};
|
|
112
|
-
logger.info(
|
|
115
|
+
logger.info("Anthropic SDK unpatched successfully");
|
|
113
116
|
}
|
|
114
117
|
catch (error) {
|
|
115
118
|
const errorContext = createErrorContext()
|
|
116
|
-
.with(
|
|
119
|
+
.with("unpatchingAttempt", true)
|
|
117
120
|
.build();
|
|
118
121
|
handleError(error, logger, errorContext);
|
|
119
122
|
throw new AnthropicPatchingError(`Failed to unpatch Anthropic SDK: ${error instanceof Error ? error.message : String(error)}`, errorContext);
|
|
@@ -129,7 +132,7 @@ export function isAnthropicPatched() {
|
|
|
129
132
|
* Handle streaming response by collecting chunks and extracting usage data
|
|
130
133
|
*/
|
|
131
134
|
async function handleStreamingResponse(stream, context) {
|
|
132
|
-
const { requestId, model, metadata, requestTime, startTime } = context;
|
|
135
|
+
const { requestId, model, metadata, requestTime, startTime, requestBody } = context;
|
|
133
136
|
// Create a new async generator that collects chunks and tracks usage
|
|
134
137
|
async function* trackingStream() {
|
|
135
138
|
const chunks = [];
|
|
@@ -137,7 +140,7 @@ async function handleStreamingResponse(stream, context) {
|
|
|
137
140
|
try {
|
|
138
141
|
for await (const chunk of stream) {
|
|
139
142
|
// Track first token time
|
|
140
|
-
if (!firstTokenTime && chunk.type ===
|
|
143
|
+
if (!firstTokenTime && chunk.type === "content_block_delta") {
|
|
141
144
|
firstTokenTime = Date.now();
|
|
142
145
|
}
|
|
143
146
|
chunks.push(chunk);
|
|
@@ -147,10 +150,14 @@ async function handleStreamingResponse(stream, context) {
|
|
|
147
150
|
const endTime = Date.now();
|
|
148
151
|
const responseTime = new Date();
|
|
149
152
|
const duration = endTime - startTime;
|
|
150
|
-
|
|
153
|
+
const timeToFirstToken = firstTokenTime
|
|
154
|
+
? firstTokenTime - startTime
|
|
155
|
+
: undefined;
|
|
156
|
+
logger.debug("Stream completed, extracting usage", {
|
|
151
157
|
requestId,
|
|
152
158
|
chunkCount: chunks.length,
|
|
153
|
-
duration
|
|
159
|
+
duration,
|
|
160
|
+
timeToFirstToken,
|
|
154
161
|
});
|
|
155
162
|
const usage = extractUsageFromStream(chunks);
|
|
156
163
|
// Create tracking data
|
|
@@ -166,22 +173,24 @@ async function handleStreamingResponse(stream, context) {
|
|
|
166
173
|
stopReason: usage.stopReason,
|
|
167
174
|
metadata,
|
|
168
175
|
requestTime,
|
|
169
|
-
responseTime
|
|
176
|
+
responseTime,
|
|
177
|
+
timeToFirstToken,
|
|
178
|
+
requestBody: requestBody,
|
|
170
179
|
};
|
|
171
180
|
// Track usage asynchronously
|
|
172
181
|
trackUsageAsync(trackingData);
|
|
173
|
-
logger.debug(
|
|
182
|
+
logger.debug("Anthropic streaming request completed successfully", {
|
|
174
183
|
requestId,
|
|
175
184
|
model,
|
|
176
185
|
inputTokens: usage.inputTokens,
|
|
177
186
|
outputTokens: usage.outputTokens,
|
|
178
|
-
duration
|
|
187
|
+
duration,
|
|
179
188
|
});
|
|
180
189
|
}
|
|
181
190
|
catch (error) {
|
|
182
|
-
logger.error(
|
|
191
|
+
logger.error("Error processing streaming response", {
|
|
183
192
|
requestId,
|
|
184
|
-
error: error instanceof Error ? error.message : String(error)
|
|
193
|
+
error: error instanceof Error ? error.message : String(error),
|
|
185
194
|
});
|
|
186
195
|
throw error;
|
|
187
196
|
}
|
|
@@ -195,19 +204,19 @@ async function patchedCreateMethod(params, options) {
|
|
|
195
204
|
const requestId = randomUUID();
|
|
196
205
|
const startTime = Date.now();
|
|
197
206
|
const requestTime = new Date();
|
|
198
|
-
logger.debug(
|
|
207
|
+
logger.debug("Intercepted Anthropic messages.create call", {
|
|
199
208
|
requestId,
|
|
200
209
|
model: params.model,
|
|
201
210
|
hasMetadata: !!params.usageMetadata,
|
|
202
|
-
isStreaming: !!params.stream
|
|
211
|
+
isStreaming: !!params.stream,
|
|
203
212
|
});
|
|
204
213
|
// Validate parameters
|
|
205
214
|
const validation = validateAnthropicMessageParams(params);
|
|
206
215
|
if (!validation.isValid) {
|
|
207
|
-
logger.warn(
|
|
216
|
+
logger.warn("Invalid Anthropic parameters detected", {
|
|
208
217
|
requestId,
|
|
209
218
|
errors: validation.errors,
|
|
210
|
-
warnings: validation.warnings
|
|
219
|
+
warnings: validation.warnings,
|
|
211
220
|
});
|
|
212
221
|
}
|
|
213
222
|
// Extract and validate metadata
|
|
@@ -218,7 +227,7 @@ async function patchedCreateMethod(params, options) {
|
|
|
218
227
|
// Call original method
|
|
219
228
|
const originalCreate = patchingContext.originalMethods.create;
|
|
220
229
|
if (!originalCreate)
|
|
221
|
-
throw new RequestProcessingError(
|
|
230
|
+
throw new RequestProcessingError("Original create method not available");
|
|
222
231
|
const response = await originalCreate.call(this, cleanParams, options);
|
|
223
232
|
// Check if this is a streaming response
|
|
224
233
|
const isStreaming = !!params.stream;
|
|
@@ -241,16 +250,17 @@ async function patchedCreateMethod(params, options) {
|
|
|
241
250
|
stopReason: usage.stopReason,
|
|
242
251
|
metadata,
|
|
243
252
|
requestTime,
|
|
244
|
-
responseTime
|
|
253
|
+
responseTime,
|
|
254
|
+
requestBody: params,
|
|
245
255
|
};
|
|
246
256
|
// Track usage asynchronously
|
|
247
257
|
trackUsageAsync(trackingData);
|
|
248
|
-
logger.debug(
|
|
258
|
+
logger.debug("Anthropic request completed successfully", {
|
|
249
259
|
requestId,
|
|
250
260
|
model: params.model,
|
|
251
261
|
inputTokens: usage.inputTokens,
|
|
252
262
|
outputTokens: usage.outputTokens,
|
|
253
|
-
duration
|
|
263
|
+
duration,
|
|
254
264
|
});
|
|
255
265
|
return response;
|
|
256
266
|
}
|
|
@@ -260,7 +270,8 @@ async function patchedCreateMethod(params, options) {
|
|
|
260
270
|
model: params.model,
|
|
261
271
|
metadata,
|
|
262
272
|
requestTime,
|
|
263
|
-
startTime
|
|
273
|
+
startTime,
|
|
274
|
+
requestBody: params,
|
|
264
275
|
});
|
|
265
276
|
}
|
|
266
277
|
catch (error) {
|
|
@@ -285,18 +296,18 @@ async function* patchedStreamMethod(params, options) {
|
|
|
285
296
|
const responseTime = new Date();
|
|
286
297
|
const chunks = [];
|
|
287
298
|
let firstTokenTime;
|
|
288
|
-
logger.debug(
|
|
299
|
+
logger.debug("Intercepted Anthropic messages.stream call", {
|
|
289
300
|
requestId,
|
|
290
301
|
model: params.model,
|
|
291
|
-
hasMetadata: !!params.usageMetadata
|
|
302
|
+
hasMetadata: !!params.usageMetadata,
|
|
292
303
|
});
|
|
293
304
|
// Validate parameters
|
|
294
305
|
const validation = validateAnthropicMessageParams(params);
|
|
295
306
|
if (!validation.isValid) {
|
|
296
|
-
logger.warn(
|
|
307
|
+
logger.warn("Invalid Anthropic streaming parameters detected", {
|
|
297
308
|
requestId,
|
|
298
309
|
errors: validation.errors,
|
|
299
|
-
warnings: validation.warnings
|
|
310
|
+
warnings: validation.warnings,
|
|
300
311
|
});
|
|
301
312
|
}
|
|
302
313
|
// Extract and validate metadata
|
|
@@ -307,12 +318,12 @@ async function* patchedStreamMethod(params, options) {
|
|
|
307
318
|
// Call original stream method
|
|
308
319
|
const originalStream = patchingContext.originalMethods?.stream;
|
|
309
320
|
if (!originalStream) {
|
|
310
|
-
throw new StreamProcessingError(
|
|
321
|
+
throw new StreamProcessingError("Original stream method not available");
|
|
311
322
|
}
|
|
312
323
|
const stream = originalStream.call(this, cleanParams, options);
|
|
313
324
|
for await (const chunk of stream) {
|
|
314
325
|
// Track first token time
|
|
315
|
-
if (!firstTokenTime && chunk.type ===
|
|
326
|
+
if (!firstTokenTime && chunk.type === "content_block_delta") {
|
|
316
327
|
firstTokenTime = Date.now();
|
|
317
328
|
}
|
|
318
329
|
chunks.push(chunk);
|
|
@@ -320,7 +331,9 @@ async function* patchedStreamMethod(params, options) {
|
|
|
320
331
|
}
|
|
321
332
|
const endTime = Date.now();
|
|
322
333
|
const duration = endTime - startTime;
|
|
323
|
-
const timeToFirstToken = firstTokenTime
|
|
334
|
+
const timeToFirstToken = firstTokenTime
|
|
335
|
+
? firstTokenTime - startTime
|
|
336
|
+
: undefined;
|
|
324
337
|
// Extract usage information from all chunks
|
|
325
338
|
const usage = extractUsageFromStream(chunks);
|
|
326
339
|
// Create tracking data
|
|
@@ -337,18 +350,19 @@ async function* patchedStreamMethod(params, options) {
|
|
|
337
350
|
metadata,
|
|
338
351
|
requestTime,
|
|
339
352
|
responseTime,
|
|
340
|
-
timeToFirstToken
|
|
353
|
+
timeToFirstToken,
|
|
354
|
+
requestBody: params,
|
|
341
355
|
};
|
|
342
356
|
// Track usage asynchronously
|
|
343
357
|
trackUsageAsync(trackingData);
|
|
344
|
-
logger.debug(
|
|
358
|
+
logger.debug("Anthropic streaming request completed successfully", {
|
|
345
359
|
requestId,
|
|
346
360
|
model: params.model,
|
|
347
361
|
inputTokens: usage.inputTokens,
|
|
348
362
|
outputTokens: usage.outputTokens,
|
|
349
363
|
duration,
|
|
350
364
|
timeToFirstToken,
|
|
351
|
-
chunkCount: chunks.length
|
|
365
|
+
chunkCount: chunks.length,
|
|
352
366
|
});
|
|
353
367
|
}
|
|
354
368
|
catch (error) {
|
|
@@ -358,8 +372,8 @@ async function* patchedStreamMethod(params, options) {
|
|
|
358
372
|
.withRequestId(requestId)
|
|
359
373
|
.withModel(params.model)
|
|
360
374
|
.withDuration(duration)
|
|
361
|
-
.with(
|
|
362
|
-
.with(
|
|
375
|
+
.with("isStreaming", true)
|
|
376
|
+
.with("chunkCount", chunks.length)
|
|
363
377
|
.build();
|
|
364
378
|
handleError(error, logger, errorContext);
|
|
365
379
|
throw error;
|
package/dist/types/config.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare function validateConfig(config: ReveniumConfig): void;
|
|
|
13
13
|
export declare function getConfig(): ReveniumConfig | null;
|
|
14
14
|
/**
|
|
15
15
|
* Set the global configuration
|
|
16
|
+
* Uses the normalized config from validation (with defaults applied and fields trimmed)
|
|
16
17
|
*/
|
|
17
18
|
export declare function setConfig(config: ReveniumConfig): void;
|
|
18
19
|
/**
|
|
@@ -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 */
|
|
@@ -109,6 +109,21 @@ export declare const ENV_VARS: {
|
|
|
109
109
|
readonly FAIL_SILENT: "REVENIUM_FAIL_SILENT";
|
|
110
110
|
/** Maximum retries */
|
|
111
111
|
readonly MAX_RETRIES: "REVENIUM_MAX_RETRIES";
|
|
112
|
+
/** Print summary mode (true/false/human/json) */
|
|
113
|
+
readonly PRINT_SUMMARY: "REVENIUM_PRINT_SUMMARY";
|
|
114
|
+
/** Team ID for cost metrics retrieval */
|
|
115
|
+
readonly TEAM_ID: "REVENIUM_TEAM_ID";
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Summary printer configuration
|
|
119
|
+
*/
|
|
120
|
+
export declare const SUMMARY_PRINTER_CONFIG: {
|
|
121
|
+
/** Maximum number of retries when fetching cost metrics */
|
|
122
|
+
readonly MAX_RETRIES: 3;
|
|
123
|
+
/** Delay between retries in milliseconds */
|
|
124
|
+
readonly RETRY_DELAY: 2000;
|
|
125
|
+
/** Fetch timeout in milliseconds (prevents hung requests from keeping Node process alive) */
|
|
126
|
+
readonly FETCH_TIMEOUT: 10000;
|
|
112
127
|
};
|
|
113
128
|
/**
|
|
114
129
|
* API endpoints
|
|
@@ -18,68 +18,6 @@
|
|
|
18
18
|
* - **Automatic Validation**: TypeScript validates the structure at compile time
|
|
19
19
|
* - **Better Developer Experience**: Auto-completion and error detection
|
|
20
20
|
*
|
|
21
|
-
* ## Usage Examples:
|
|
22
|
-
*
|
|
23
|
-
* ### Basic Usage:
|
|
24
|
-
* ```typescript
|
|
25
|
-
* import '@revenium/anthropic';
|
|
26
|
-
* import Anthropic from '@anthropic-ai/sdk';
|
|
27
|
-
*
|
|
28
|
-
* const anthropic = new Anthropic();
|
|
29
|
-
*
|
|
30
|
-
* const response = await anthropic.messages.create({
|
|
31
|
-
* model: 'claude-3-5-sonnet-latest',
|
|
32
|
-
* max_tokens: 1024,
|
|
33
|
-
* messages: [{ role: 'user', content: 'Hello!' }],
|
|
34
|
-
* usageMetadata: { // TypeScript recognizes this natively
|
|
35
|
-
* subscriber: { id: 'user-123', email: 'user@example.com' },
|
|
36
|
-
* organizationId: 'my-company',
|
|
37
|
-
* taskType: 'customer-support',
|
|
38
|
-
* traceId: 'session-abc-123'
|
|
39
|
-
* }
|
|
40
|
-
* });
|
|
41
|
-
* ```
|
|
42
|
-
*
|
|
43
|
-
* ### Streaming Usage:
|
|
44
|
-
* ```typescript
|
|
45
|
-
* const stream = await anthropic.messages.stream({
|
|
46
|
-
* model: 'claude-3-5-sonnet-latest',
|
|
47
|
-
* max_tokens: 1024,
|
|
48
|
-
* messages: [{ role: 'user', content: 'Generate a report' }],
|
|
49
|
-
* usageMetadata: {
|
|
50
|
-
* taskType: 'content-generation',
|
|
51
|
-
* productId: 'report-generator',
|
|
52
|
-
* responseQualityScore: 0.95
|
|
53
|
-
* }
|
|
54
|
-
* });
|
|
55
|
-
* ```
|
|
56
|
-
*
|
|
57
|
-
* ### Advanced Usage with All Fields:
|
|
58
|
-
* ```typescript
|
|
59
|
-
* const response = await anthropic.messages.create({
|
|
60
|
-
* model: 'claude-3-5-sonnet-latest',
|
|
61
|
-
* max_tokens: 2048,
|
|
62
|
-
* messages: [{ role: 'user', content: 'Complex analysis task' }],
|
|
63
|
-
* usageMetadata: {
|
|
64
|
-
* subscriber: {
|
|
65
|
-
* id: 'user-456',
|
|
66
|
-
* email: 'analyst@company.com',
|
|
67
|
-
* credential: { name: 'api-key', value: 'sk-...' }
|
|
68
|
-
* },
|
|
69
|
-
* traceId: 'analysis-session-789',
|
|
70
|
-
* taskType: 'data-analysis',
|
|
71
|
-
* organizationId: 'enterprise-client',
|
|
72
|
-
* subscriptionId: 'premium-plan',
|
|
73
|
-
* productId: 'analytics-suite',
|
|
74
|
-
* agent: 'data-analyst-bot',
|
|
75
|
-
* responseQualityScore: 0.98,
|
|
76
|
-
* customField: 'custom-value' // Extensible with custom fields
|
|
77
|
-
* }
|
|
78
|
-
* });
|
|
79
|
-
* ```
|
|
80
|
-
*
|
|
81
|
-
* @public
|
|
82
|
-
* @since 1.1.0
|
|
83
21
|
*/
|
|
84
22
|
import { UsageMetadata } from "../types";
|
|
85
23
|
export {};
|
|
@@ -109,36 +47,6 @@ declare module "@anthropic-ai/sdk/resources/messages" {
|
|
|
109
47
|
* - **Multi-tenancy**: Organize usage by organizationId
|
|
110
48
|
* - **Quality Monitoring**: Track response quality scores
|
|
111
49
|
* - **Custom Analytics**: Add custom fields for specific business needs
|
|
112
|
-
*
|
|
113
|
-
* @example Basic user tracking
|
|
114
|
-
* ```typescript
|
|
115
|
-
* usageMetadata: {
|
|
116
|
-
* subscriber: { id: 'user-123', email: 'user@example.com' },
|
|
117
|
-
* organizationId: 'my-company'
|
|
118
|
-
* }
|
|
119
|
-
* ```
|
|
120
|
-
*
|
|
121
|
-
* @example Session and task tracking
|
|
122
|
-
* ```typescript
|
|
123
|
-
* usageMetadata: {
|
|
124
|
-
* traceId: 'session-abc-123',
|
|
125
|
-
* taskType: 'customer-support',
|
|
126
|
-
* productId: 'help-desk'
|
|
127
|
-
* }
|
|
128
|
-
* ```
|
|
129
|
-
*
|
|
130
|
-
* @example Advanced analytics
|
|
131
|
-
* ```typescript
|
|
132
|
-
* usageMetadata: {
|
|
133
|
-
* subscriber: { id: 'user-456' },
|
|
134
|
-
* taskType: 'content-generation',
|
|
135
|
-
* responseQualityScore: 0.95,
|
|
136
|
-
* customMetric: 'high-priority'
|
|
137
|
-
* }
|
|
138
|
-
* ```
|
|
139
|
-
*
|
|
140
|
-
* @public
|
|
141
|
-
* @since 1.1.0
|
|
142
50
|
*/
|
|
143
51
|
usageMetadata?: UsageMetadata;
|
|
144
52
|
}
|