@revenium/anthropic 1.0.8 → 1.1.0
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 +46 -1
- package/README.md +208 -49
- package/dist/cjs/config.js +80 -29
- package/dist/cjs/constants.js +45 -24
- package/dist/cjs/tracking.js +82 -20
- package/dist/cjs/types/anthropic-augmentation.js +0 -62
- package/dist/cjs/utils/prompt-extraction.js +158 -0
- package/dist/cjs/utils/summary-printer.js +189 -0
- package/dist/cjs/utils/trace-fields.js +117 -0
- package/dist/cjs/utils/validation.js +55 -23
- package/dist/cjs/wrapper.js +49 -41
- package/dist/esm/config.js +82 -31
- package/dist/esm/constants.js +44 -23
- package/dist/esm/tracking.js +82 -20
- package/dist/esm/types/anthropic-augmentation.js +0 -62
- package/dist/esm/utils/prompt-extraction.js +154 -0
- package/dist/esm/utils/summary-printer.js +186 -0
- package/dist/esm/utils/trace-fields.js +106 -0
- package/dist/esm/utils/validation.js +56 -24
- package/dist/esm/wrapper.js +55 -47
- package/dist/types/config.d.ts +2 -1
- package/dist/types/constants.d.ts +21 -0
- package/dist/types/types/anthropic-augmentation.d.ts +0 -92
- package/dist/types/types.d.ts +41 -198
- package/dist/types/utils/prompt-extraction.d.ts +10 -0
- package/dist/types/utils/summary-printer.d.ts +3 -0
- package/dist/types/utils/trace-fields.d.ts +10 -0
- 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 +4 -6
- package/examples/advanced-features.ts +0 -469
- package/examples/basic-usage.ts +0 -314
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { getLogger } from "../config.js";
|
|
2
|
+
const logger = getLogger();
|
|
3
|
+
let cachedRegion = null;
|
|
4
|
+
let regionCached = false;
|
|
5
|
+
export function getEnvironment() {
|
|
6
|
+
const env = process.env.REVENIUM_ENVIRONMENT ||
|
|
7
|
+
process.env.NODE_ENV ||
|
|
8
|
+
process.env.DEPLOYMENT_ENV ||
|
|
9
|
+
null;
|
|
10
|
+
if (env && env.length > 255) {
|
|
11
|
+
logger.warn(`environment exceeds max length of 255 characters. Truncating.`);
|
|
12
|
+
return env.substring(0, 255).trim();
|
|
13
|
+
}
|
|
14
|
+
return env ? env.trim() : null;
|
|
15
|
+
}
|
|
16
|
+
export async function getRegion() {
|
|
17
|
+
if (regionCached) {
|
|
18
|
+
return cachedRegion;
|
|
19
|
+
}
|
|
20
|
+
const envRegion = process.env.AWS_REGION ||
|
|
21
|
+
process.env.AZURE_REGION ||
|
|
22
|
+
process.env.GCP_REGION ||
|
|
23
|
+
process.env.REVENIUM_REGION;
|
|
24
|
+
if (envRegion) {
|
|
25
|
+
cachedRegion = envRegion.trim();
|
|
26
|
+
regionCached = true;
|
|
27
|
+
return cachedRegion;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
const timeoutId = setTimeout(() => controller.abort(), 1000);
|
|
32
|
+
const response = await fetch("http://169.254.169.254/latest/meta-data/placement/region", {
|
|
33
|
+
signal: controller.signal,
|
|
34
|
+
});
|
|
35
|
+
clearTimeout(timeoutId);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
cachedRegion = null;
|
|
38
|
+
regionCached = true;
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
cachedRegion = text.trim();
|
|
43
|
+
regionCached = true;
|
|
44
|
+
return cachedRegion;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
cachedRegion = null;
|
|
48
|
+
regionCached = true;
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function getCredentialAlias() {
|
|
53
|
+
const alias = process.env.REVENIUM_CREDENTIAL_ALIAS || null;
|
|
54
|
+
if (alias && alias.length > 255) {
|
|
55
|
+
logger.warn(`credentialAlias exceeds max length of 255 characters. Truncating.`);
|
|
56
|
+
return alias.substring(0, 255).trim();
|
|
57
|
+
}
|
|
58
|
+
return alias ? alias.trim() : null;
|
|
59
|
+
}
|
|
60
|
+
export function getTraceType() {
|
|
61
|
+
const traceType = process.env.REVENIUM_TRACE_TYPE;
|
|
62
|
+
if (!traceType) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(traceType)) {
|
|
66
|
+
logger.warn(`Invalid trace_type format: ${traceType}. Must be alphanumeric with hyphens/underscores only.`);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
if (traceType.length > 128) {
|
|
70
|
+
logger.warn(`trace_type exceeds max length of 128 characters: ${traceType}. Truncating.`);
|
|
71
|
+
return traceType.substring(0, 128);
|
|
72
|
+
}
|
|
73
|
+
return traceType;
|
|
74
|
+
}
|
|
75
|
+
export function getTraceName() {
|
|
76
|
+
const traceName = process.env.REVENIUM_TRACE_NAME;
|
|
77
|
+
if (!traceName) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (traceName.length > 256) {
|
|
81
|
+
logger.warn(`trace_name exceeds max length of 256 characters. Truncating.`);
|
|
82
|
+
return traceName.substring(0, 256);
|
|
83
|
+
}
|
|
84
|
+
return traceName;
|
|
85
|
+
}
|
|
86
|
+
export function detectOperationSubtype(requestBody) {
|
|
87
|
+
if (requestBody && requestBody.tools && requestBody.tools.length > 0) {
|
|
88
|
+
return "function_call";
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
export function getParentTransactionId() {
|
|
93
|
+
return process.env.REVENIUM_PARENT_TRANSACTION_ID || null;
|
|
94
|
+
}
|
|
95
|
+
export function getTransactionName() {
|
|
96
|
+
return process.env.REVENIUM_TRANSACTION_NAME || null;
|
|
97
|
+
}
|
|
98
|
+
export function getRetryNumber() {
|
|
99
|
+
const retryNum = process.env.REVENIUM_RETRY_NUMBER;
|
|
100
|
+
if (retryNum) {
|
|
101
|
+
const parsed = parseInt(retryNum, 10);
|
|
102
|
+
return isNaN(parsed) ? 0 : parsed;
|
|
103
|
+
}
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=trace-fields.js.map
|
|
@@ -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,29 +128,31 @@ 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.ai');
|
|
153
|
-
}
|
|
154
156
|
}
|
|
155
157
|
// Validate optional Anthropic API key
|
|
156
158
|
if (cfg?.anthropicApiKey !== undefined && !isString(cfg?.anthropicApiKey)) {
|
|
@@ -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, getConfig } 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
|
|
@@ -38,8 +38,8 @@ function getMessagesPrototype() {
|
|
|
38
38
|
const config = getConfig();
|
|
39
39
|
const apiKey = config?.anthropicApiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
40
40
|
if (!apiKey) {
|
|
41
|
-
throw new AnthropicPatchingError(
|
|
42
|
-
|
|
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.");
|
|
43
43
|
}
|
|
44
44
|
const minimalInstance = new Anthropic({ apiKey });
|
|
45
45
|
const messagesPrototype = Object.getPrototypeOf(minimalInstance.messages);
|
|
@@ -57,19 +57,19 @@ function getMessagesPrototype() {
|
|
|
57
57
|
*/
|
|
58
58
|
export function patchAnthropic() {
|
|
59
59
|
if (patchingContext.isPatched) {
|
|
60
|
-
logger.debug(
|
|
60
|
+
logger.debug("Anthropic SDK already patched, skipping duplicate initialization");
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
try {
|
|
64
64
|
// Access the Messages class prototype using sophisticated prototype access
|
|
65
65
|
const messagesPrototype = getMessagesPrototype();
|
|
66
66
|
if (!messagesPrototype)
|
|
67
|
-
throw new AnthropicPatchingError(
|
|
67
|
+
throw new AnthropicPatchingError("Unable to access Anthropic Messages prototype");
|
|
68
68
|
// Store original methods
|
|
69
69
|
patchingContext.originalMethods.create = messagesPrototype?.create;
|
|
70
70
|
patchingContext.originalMethods.stream = messagesPrototype?.stream;
|
|
71
71
|
if (!patchingContext.originalMethods?.create) {
|
|
72
|
-
throw new AnthropicPatchingError(
|
|
72
|
+
throw new AnthropicPatchingError("Unable to find original create method");
|
|
73
73
|
}
|
|
74
74
|
// Patch the create method
|
|
75
75
|
const patchedCreateFunction = function (params, options) {
|
|
@@ -83,11 +83,11 @@ export function patchAnthropic() {
|
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
patchingContext.isPatched = true;
|
|
86
|
-
logger.info(
|
|
86
|
+
logger.info("Anthropic SDK patched successfully");
|
|
87
87
|
}
|
|
88
88
|
catch (error) {
|
|
89
89
|
const errorContext = createErrorContext()
|
|
90
|
-
.with(
|
|
90
|
+
.with("patchingAttempt", true)
|
|
91
91
|
.build();
|
|
92
92
|
handleError(error, logger, errorContext);
|
|
93
93
|
if (error instanceof AnthropicPatchingError)
|
|
@@ -112,11 +112,11 @@ export function unpatchAnthropic() {
|
|
|
112
112
|
}
|
|
113
113
|
patchingContext.isPatched = false;
|
|
114
114
|
patchingContext.originalMethods = {};
|
|
115
|
-
logger.info(
|
|
115
|
+
logger.info("Anthropic SDK unpatched successfully");
|
|
116
116
|
}
|
|
117
117
|
catch (error) {
|
|
118
118
|
const errorContext = createErrorContext()
|
|
119
|
-
.with(
|
|
119
|
+
.with("unpatchingAttempt", true)
|
|
120
120
|
.build();
|
|
121
121
|
handleError(error, logger, errorContext);
|
|
122
122
|
throw new AnthropicPatchingError(`Failed to unpatch Anthropic SDK: ${error instanceof Error ? error.message : String(error)}`, errorContext);
|
|
@@ -132,7 +132,7 @@ export function isAnthropicPatched() {
|
|
|
132
132
|
* Handle streaming response by collecting chunks and extracting usage data
|
|
133
133
|
*/
|
|
134
134
|
async function handleStreamingResponse(stream, context) {
|
|
135
|
-
const { requestId, model, metadata, requestTime, startTime } = context;
|
|
135
|
+
const { requestId, model, metadata, requestTime, startTime, requestBody } = context;
|
|
136
136
|
// Create a new async generator that collects chunks and tracks usage
|
|
137
137
|
async function* trackingStream() {
|
|
138
138
|
const chunks = [];
|
|
@@ -140,7 +140,7 @@ async function handleStreamingResponse(stream, context) {
|
|
|
140
140
|
try {
|
|
141
141
|
for await (const chunk of stream) {
|
|
142
142
|
// Track first token time
|
|
143
|
-
if (!firstTokenTime && chunk.type ===
|
|
143
|
+
if (!firstTokenTime && chunk.type === "content_block_delta") {
|
|
144
144
|
firstTokenTime = Date.now();
|
|
145
145
|
}
|
|
146
146
|
chunks.push(chunk);
|
|
@@ -150,12 +150,14 @@ async function handleStreamingResponse(stream, context) {
|
|
|
150
150
|
const endTime = Date.now();
|
|
151
151
|
const responseTime = new Date();
|
|
152
152
|
const duration = endTime - startTime;
|
|
153
|
-
const timeToFirstToken = firstTokenTime
|
|
154
|
-
|
|
153
|
+
const timeToFirstToken = firstTokenTime
|
|
154
|
+
? firstTokenTime - startTime
|
|
155
|
+
: undefined;
|
|
156
|
+
logger.debug("Stream completed, extracting usage", {
|
|
155
157
|
requestId,
|
|
156
158
|
chunkCount: chunks.length,
|
|
157
159
|
duration,
|
|
158
|
-
timeToFirstToken
|
|
160
|
+
timeToFirstToken,
|
|
159
161
|
});
|
|
160
162
|
const usage = extractUsageFromStream(chunks);
|
|
161
163
|
// Create tracking data
|
|
@@ -172,22 +174,23 @@ async function handleStreamingResponse(stream, context) {
|
|
|
172
174
|
metadata,
|
|
173
175
|
requestTime,
|
|
174
176
|
responseTime,
|
|
175
|
-
timeToFirstToken
|
|
177
|
+
timeToFirstToken,
|
|
178
|
+
requestBody: requestBody,
|
|
176
179
|
};
|
|
177
180
|
// Track usage asynchronously
|
|
178
181
|
trackUsageAsync(trackingData);
|
|
179
|
-
logger.debug(
|
|
182
|
+
logger.debug("Anthropic streaming request completed successfully", {
|
|
180
183
|
requestId,
|
|
181
184
|
model,
|
|
182
185
|
inputTokens: usage.inputTokens,
|
|
183
186
|
outputTokens: usage.outputTokens,
|
|
184
|
-
duration
|
|
187
|
+
duration,
|
|
185
188
|
});
|
|
186
189
|
}
|
|
187
190
|
catch (error) {
|
|
188
|
-
logger.error(
|
|
191
|
+
logger.error("Error processing streaming response", {
|
|
189
192
|
requestId,
|
|
190
|
-
error: error instanceof Error ? error.message : String(error)
|
|
193
|
+
error: error instanceof Error ? error.message : String(error),
|
|
191
194
|
});
|
|
192
195
|
throw error;
|
|
193
196
|
}
|
|
@@ -201,19 +204,19 @@ async function patchedCreateMethod(params, options) {
|
|
|
201
204
|
const requestId = randomUUID();
|
|
202
205
|
const startTime = Date.now();
|
|
203
206
|
const requestTime = new Date();
|
|
204
|
-
logger.debug(
|
|
207
|
+
logger.debug("Intercepted Anthropic messages.create call", {
|
|
205
208
|
requestId,
|
|
206
209
|
model: params.model,
|
|
207
210
|
hasMetadata: !!params.usageMetadata,
|
|
208
|
-
isStreaming: !!params.stream
|
|
211
|
+
isStreaming: !!params.stream,
|
|
209
212
|
});
|
|
210
213
|
// Validate parameters
|
|
211
214
|
const validation = validateAnthropicMessageParams(params);
|
|
212
215
|
if (!validation.isValid) {
|
|
213
|
-
logger.warn(
|
|
216
|
+
logger.warn("Invalid Anthropic parameters detected", {
|
|
214
217
|
requestId,
|
|
215
218
|
errors: validation.errors,
|
|
216
|
-
warnings: validation.warnings
|
|
219
|
+
warnings: validation.warnings,
|
|
217
220
|
});
|
|
218
221
|
}
|
|
219
222
|
// Extract and validate metadata
|
|
@@ -224,7 +227,7 @@ async function patchedCreateMethod(params, options) {
|
|
|
224
227
|
// Call original method
|
|
225
228
|
const originalCreate = patchingContext.originalMethods.create;
|
|
226
229
|
if (!originalCreate)
|
|
227
|
-
throw new RequestProcessingError(
|
|
230
|
+
throw new RequestProcessingError("Original create method not available");
|
|
228
231
|
const response = await originalCreate.call(this, cleanParams, options);
|
|
229
232
|
// Check if this is a streaming response
|
|
230
233
|
const isStreaming = !!params.stream;
|
|
@@ -247,16 +250,17 @@ async function patchedCreateMethod(params, options) {
|
|
|
247
250
|
stopReason: usage.stopReason,
|
|
248
251
|
metadata,
|
|
249
252
|
requestTime,
|
|
250
|
-
responseTime
|
|
253
|
+
responseTime,
|
|
254
|
+
requestBody: params,
|
|
251
255
|
};
|
|
252
256
|
// Track usage asynchronously
|
|
253
257
|
trackUsageAsync(trackingData);
|
|
254
|
-
logger.debug(
|
|
258
|
+
logger.debug("Anthropic request completed successfully", {
|
|
255
259
|
requestId,
|
|
256
260
|
model: params.model,
|
|
257
261
|
inputTokens: usage.inputTokens,
|
|
258
262
|
outputTokens: usage.outputTokens,
|
|
259
|
-
duration
|
|
263
|
+
duration,
|
|
260
264
|
});
|
|
261
265
|
return response;
|
|
262
266
|
}
|
|
@@ -266,7 +270,8 @@ async function patchedCreateMethod(params, options) {
|
|
|
266
270
|
model: params.model,
|
|
267
271
|
metadata,
|
|
268
272
|
requestTime,
|
|
269
|
-
startTime
|
|
273
|
+
startTime,
|
|
274
|
+
requestBody: params,
|
|
270
275
|
});
|
|
271
276
|
}
|
|
272
277
|
catch (error) {
|
|
@@ -291,18 +296,18 @@ async function* patchedStreamMethod(params, options) {
|
|
|
291
296
|
const responseTime = new Date();
|
|
292
297
|
const chunks = [];
|
|
293
298
|
let firstTokenTime;
|
|
294
|
-
logger.debug(
|
|
299
|
+
logger.debug("Intercepted Anthropic messages.stream call", {
|
|
295
300
|
requestId,
|
|
296
301
|
model: params.model,
|
|
297
|
-
hasMetadata: !!params.usageMetadata
|
|
302
|
+
hasMetadata: !!params.usageMetadata,
|
|
298
303
|
});
|
|
299
304
|
// Validate parameters
|
|
300
305
|
const validation = validateAnthropicMessageParams(params);
|
|
301
306
|
if (!validation.isValid) {
|
|
302
|
-
logger.warn(
|
|
307
|
+
logger.warn("Invalid Anthropic streaming parameters detected", {
|
|
303
308
|
requestId,
|
|
304
309
|
errors: validation.errors,
|
|
305
|
-
warnings: validation.warnings
|
|
310
|
+
warnings: validation.warnings,
|
|
306
311
|
});
|
|
307
312
|
}
|
|
308
313
|
// Extract and validate metadata
|
|
@@ -313,12 +318,12 @@ async function* patchedStreamMethod(params, options) {
|
|
|
313
318
|
// Call original stream method
|
|
314
319
|
const originalStream = patchingContext.originalMethods?.stream;
|
|
315
320
|
if (!originalStream) {
|
|
316
|
-
throw new StreamProcessingError(
|
|
321
|
+
throw new StreamProcessingError("Original stream method not available");
|
|
317
322
|
}
|
|
318
323
|
const stream = originalStream.call(this, cleanParams, options);
|
|
319
324
|
for await (const chunk of stream) {
|
|
320
325
|
// Track first token time
|
|
321
|
-
if (!firstTokenTime && chunk.type ===
|
|
326
|
+
if (!firstTokenTime && chunk.type === "content_block_delta") {
|
|
322
327
|
firstTokenTime = Date.now();
|
|
323
328
|
}
|
|
324
329
|
chunks.push(chunk);
|
|
@@ -326,7 +331,9 @@ async function* patchedStreamMethod(params, options) {
|
|
|
326
331
|
}
|
|
327
332
|
const endTime = Date.now();
|
|
328
333
|
const duration = endTime - startTime;
|
|
329
|
-
const timeToFirstToken = firstTokenTime
|
|
334
|
+
const timeToFirstToken = firstTokenTime
|
|
335
|
+
? firstTokenTime - startTime
|
|
336
|
+
: undefined;
|
|
330
337
|
// Extract usage information from all chunks
|
|
331
338
|
const usage = extractUsageFromStream(chunks);
|
|
332
339
|
// Create tracking data
|
|
@@ -343,18 +350,19 @@ async function* patchedStreamMethod(params, options) {
|
|
|
343
350
|
metadata,
|
|
344
351
|
requestTime,
|
|
345
352
|
responseTime,
|
|
346
|
-
timeToFirstToken
|
|
353
|
+
timeToFirstToken,
|
|
354
|
+
requestBody: params,
|
|
347
355
|
};
|
|
348
356
|
// Track usage asynchronously
|
|
349
357
|
trackUsageAsync(trackingData);
|
|
350
|
-
logger.debug(
|
|
358
|
+
logger.debug("Anthropic streaming request completed successfully", {
|
|
351
359
|
requestId,
|
|
352
360
|
model: params.model,
|
|
353
361
|
inputTokens: usage.inputTokens,
|
|
354
362
|
outputTokens: usage.outputTokens,
|
|
355
363
|
duration,
|
|
356
364
|
timeToFirstToken,
|
|
357
|
-
chunkCount: chunks.length
|
|
365
|
+
chunkCount: chunks.length,
|
|
358
366
|
});
|
|
359
367
|
}
|
|
360
368
|
catch (error) {
|
|
@@ -364,8 +372,8 @@ async function* patchedStreamMethod(params, options) {
|
|
|
364
372
|
.withRequestId(requestId)
|
|
365
373
|
.withModel(params.model)
|
|
366
374
|
.withDuration(duration)
|
|
367
|
-
.with(
|
|
368
|
-
.with(
|
|
375
|
+
.with("isStreaming", true)
|
|
376
|
+
.with("chunkCount", chunks.length)
|
|
369
377
|
.build();
|
|
370
378
|
handleError(error, logger, errorContext);
|
|
371
379
|
throw error;
|
package/dist/types/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReveniumConfig, Logger } from
|
|
1
|
+
import { ReveniumConfig, Logger } from "./types";
|
|
2
2
|
/**
|
|
3
3
|
* Default console logger implementation
|
|
4
4
|
*/
|
|
@@ -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
|
/**
|
|
@@ -22,6 +22,10 @@ export declare const DEFAULT_CONFIG: {
|
|
|
22
22
|
readonly MAX_RETRY_ATTEMPTS: 10;
|
|
23
23
|
/** Warning threshold for low API timeout */
|
|
24
24
|
readonly LOW_TIMEOUT_WARNING_THRESHOLD: 3000;
|
|
25
|
+
/** Default prompt capture behavior */
|
|
26
|
+
readonly CAPTURE_PROMPTS: false;
|
|
27
|
+
/** Maximum size for each prompt field in characters (50KB) */
|
|
28
|
+
readonly MAX_PROMPT_SIZE: 50000;
|
|
25
29
|
};
|
|
26
30
|
/**
|
|
27
31
|
* Circuit breaker configuration constants
|
|
@@ -109,6 +113,23 @@ export declare const ENV_VARS: {
|
|
|
109
113
|
readonly FAIL_SILENT: "REVENIUM_FAIL_SILENT";
|
|
110
114
|
/** Maximum retries */
|
|
111
115
|
readonly MAX_RETRIES: "REVENIUM_MAX_RETRIES";
|
|
116
|
+
/** Print summary mode (true/false/human/json) */
|
|
117
|
+
readonly PRINT_SUMMARY: "REVENIUM_PRINT_SUMMARY";
|
|
118
|
+
/** Team ID for cost metrics retrieval */
|
|
119
|
+
readonly TEAM_ID: "REVENIUM_TEAM_ID";
|
|
120
|
+
/** Prompt capture mode */
|
|
121
|
+
readonly CAPTURE_PROMPTS: "REVENIUM_CAPTURE_PROMPTS";
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Summary printer configuration
|
|
125
|
+
*/
|
|
126
|
+
export declare const SUMMARY_PRINTER_CONFIG: {
|
|
127
|
+
/** Maximum number of retries when fetching cost metrics */
|
|
128
|
+
readonly MAX_RETRIES: 3;
|
|
129
|
+
/** Delay between retries in milliseconds */
|
|
130
|
+
readonly RETRY_DELAY: 2000;
|
|
131
|
+
/** Fetch timeout in milliseconds (prevents hung requests from keeping Node process alive) */
|
|
132
|
+
readonly FETCH_TIMEOUT: 10000;
|
|
112
133
|
};
|
|
113
134
|
/**
|
|
114
135
|
* 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
|
}
|