@revenium/anthropic 1.1.0 → 1.1.1
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/README.md +53 -53
- package/dist/cjs/tracking.js +21 -3
- package/dist/cjs/utils/trace-fields.js +36 -0
- package/dist/cjs/utils/validation.js +105 -69
- package/dist/cjs/wrapper.js +79 -0
- package/dist/esm/tracking.js +21 -3
- package/dist/esm/utils/trace-fields.js +35 -0
- package/dist/esm/utils/validation.js +106 -70
- package/dist/esm/wrapper.js +79 -0
- package/dist/types/types.d.ts +27 -8
- package/dist/types/utils/trace-fields.d.ts +1 -0
- package/dist/types/utils/validation.d.ts +1 -1
- package/examples/advanced.ts +4 -4
- package/examples/getting_started.ts +2 -2
- package/examples/metadata.ts +3 -3
- package/examples/prompt-capture-example.ts +105 -0
- package/package.json +1 -1
package/dist/esm/wrapper.js
CHANGED
|
@@ -6,6 +6,8 @@ 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";
|
|
9
|
+
import { shouldCapturePrompts } from "./utils/prompt-extraction.js";
|
|
10
|
+
import { detectVisionContent } from "./utils/trace-fields.js";
|
|
9
11
|
import { randomUUID } from "crypto";
|
|
10
12
|
// Global logger
|
|
11
13
|
const logger = getLogger();
|
|
@@ -128,6 +130,65 @@ export function unpatchAnthropic() {
|
|
|
128
130
|
export function isAnthropicPatched() {
|
|
129
131
|
return patchingContext.isPatched;
|
|
130
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Reconstruct a response object from streaming chunks for prompt capture
|
|
135
|
+
*/
|
|
136
|
+
function reconstructResponseFromChunks(chunks, model) {
|
|
137
|
+
const contentBlocks = [];
|
|
138
|
+
let stopReason;
|
|
139
|
+
let stopSequence;
|
|
140
|
+
const usage = {};
|
|
141
|
+
for (const chunk of chunks) {
|
|
142
|
+
if (chunk.type === "content_block_start" && chunk.content_block) {
|
|
143
|
+
contentBlocks.push({ ...chunk.content_block });
|
|
144
|
+
}
|
|
145
|
+
else if (chunk.type === "content_block_delta" && chunk.delta) {
|
|
146
|
+
const lastBlock = contentBlocks[contentBlocks.length - 1];
|
|
147
|
+
if (lastBlock && chunk.delta.type === "text_delta") {
|
|
148
|
+
if (lastBlock.type === "text") {
|
|
149
|
+
lastBlock.text = (lastBlock.text || "") + (chunk.delta.text || "");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else if (lastBlock && chunk.delta.type === "input_json_delta") {
|
|
153
|
+
if (lastBlock.type === "tool_use") {
|
|
154
|
+
lastBlock.input = lastBlock.input || "";
|
|
155
|
+
lastBlock.input +=
|
|
156
|
+
chunk.delta.partial_json || "";
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (chunk.type === "message_delta" && chunk.delta) {
|
|
161
|
+
const delta = chunk.delta;
|
|
162
|
+
if (delta.stop_reason) {
|
|
163
|
+
stopReason = delta.stop_reason;
|
|
164
|
+
}
|
|
165
|
+
if (delta.stop_sequence) {
|
|
166
|
+
stopSequence = delta.stop_sequence;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else if (chunk.type === "message_start" && chunk.message?.usage) {
|
|
170
|
+
Object.assign(usage, chunk.message.usage);
|
|
171
|
+
}
|
|
172
|
+
else if (chunk.usage) {
|
|
173
|
+
Object.assign(usage, chunk.usage);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
id: `reconstructed-${Date.now()}`,
|
|
178
|
+
type: "message",
|
|
179
|
+
role: "assistant",
|
|
180
|
+
content: contentBlocks,
|
|
181
|
+
model,
|
|
182
|
+
stop_reason: stopReason || "end_turn",
|
|
183
|
+
stop_sequence: stopSequence,
|
|
184
|
+
usage: {
|
|
185
|
+
input_tokens: usage.input_tokens || 0,
|
|
186
|
+
output_tokens: usage.output_tokens || 0,
|
|
187
|
+
cache_creation_input_tokens: usage.cache_creation_input_tokens,
|
|
188
|
+
cache_read_input_tokens: usage.cache_read_input_tokens,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
131
192
|
/**
|
|
132
193
|
* Handle streaming response by collecting chunks and extracting usage data
|
|
133
194
|
*/
|
|
@@ -160,6 +221,10 @@ async function handleStreamingResponse(stream, context) {
|
|
|
160
221
|
timeToFirstToken,
|
|
161
222
|
});
|
|
162
223
|
const usage = extractUsageFromStream(chunks);
|
|
224
|
+
let reconstructedResponse = undefined;
|
|
225
|
+
if (shouldCapturePrompts(metadata)) {
|
|
226
|
+
reconstructedResponse = reconstructResponseFromChunks(chunks, model);
|
|
227
|
+
}
|
|
163
228
|
// Create tracking data
|
|
164
229
|
const trackingData = {
|
|
165
230
|
requestId,
|
|
@@ -176,6 +241,8 @@ async function handleStreamingResponse(stream, context) {
|
|
|
176
241
|
responseTime,
|
|
177
242
|
timeToFirstToken,
|
|
178
243
|
requestBody: requestBody,
|
|
244
|
+
response: reconstructedResponse,
|
|
245
|
+
hasVisionContent: detectVisionContent(requestBody),
|
|
179
246
|
};
|
|
180
247
|
// Track usage asynchronously
|
|
181
248
|
trackUsageAsync(trackingData);
|
|
@@ -237,6 +304,8 @@ async function patchedCreateMethod(params, options) {
|
|
|
237
304
|
const responseTime = new Date();
|
|
238
305
|
// Extract usage information
|
|
239
306
|
const usage = extractUsageFromResponse(response);
|
|
307
|
+
// Detect vision content
|
|
308
|
+
const hasVisionContent = detectVisionContent(params);
|
|
240
309
|
// Create tracking data
|
|
241
310
|
const trackingData = {
|
|
242
311
|
requestId,
|
|
@@ -251,7 +320,9 @@ async function patchedCreateMethod(params, options) {
|
|
|
251
320
|
metadata,
|
|
252
321
|
requestTime,
|
|
253
322
|
responseTime,
|
|
323
|
+
hasVisionContent,
|
|
254
324
|
requestBody: params,
|
|
325
|
+
response,
|
|
255
326
|
};
|
|
256
327
|
// Track usage asynchronously
|
|
257
328
|
trackUsageAsync(trackingData);
|
|
@@ -336,6 +407,12 @@ async function* patchedStreamMethod(params, options) {
|
|
|
336
407
|
: undefined;
|
|
337
408
|
// Extract usage information from all chunks
|
|
338
409
|
const usage = extractUsageFromStream(chunks);
|
|
410
|
+
// Detect vision content
|
|
411
|
+
const hasVisionContent = detectVisionContent(params);
|
|
412
|
+
let reconstructedResponse = undefined;
|
|
413
|
+
if (shouldCapturePrompts(metadata)) {
|
|
414
|
+
reconstructedResponse = reconstructResponseFromChunks(chunks, params.model);
|
|
415
|
+
}
|
|
339
416
|
// Create tracking data
|
|
340
417
|
const trackingData = {
|
|
341
418
|
requestId,
|
|
@@ -351,7 +428,9 @@ async function* patchedStreamMethod(params, options) {
|
|
|
351
428
|
requestTime,
|
|
352
429
|
responseTime,
|
|
353
430
|
timeToFirstToken,
|
|
431
|
+
hasVisionContent,
|
|
354
432
|
requestBody: params,
|
|
433
|
+
response: reconstructedResponse,
|
|
355
434
|
};
|
|
356
435
|
// Track usage asynchronously
|
|
357
436
|
trackUsageAsync(trackingData);
|
package/dist/types/types.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface ReveniumConfig {
|
|
|
47
47
|
printSummary?: boolean | SummaryFormat;
|
|
48
48
|
/** Revenium team ID for fetching cost metrics from the API. If not provided, the summary will still be printed but without cost information. */
|
|
49
49
|
teamId?: string;
|
|
50
|
-
/** Whether to capture prompts
|
|
50
|
+
/** Whether to capture and send prompts to Revenium API (default: false) */
|
|
51
51
|
capturePrompts?: boolean;
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
@@ -60,17 +60,27 @@ export interface UsageMetadata {
|
|
|
60
60
|
traceId?: string;
|
|
61
61
|
/** Classification of AI operation (e.g., 'customer-support', 'content-generation', 'code-review') */
|
|
62
62
|
taskType?: string;
|
|
63
|
-
/** Customer organization
|
|
63
|
+
/** Customer organization name for multi-tenant applications (used for lookup/auto-creation) */
|
|
64
|
+
organizationName?: string;
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated Use organizationName instead. This field will be removed in a future version.
|
|
67
|
+
* Customer organization identifier for multi-tenant applications
|
|
68
|
+
*/
|
|
64
69
|
organizationId?: string;
|
|
65
70
|
/** Reference to billing plan or subscription tier */
|
|
66
71
|
subscriptionId?: string;
|
|
67
|
-
/** Product or feature
|
|
72
|
+
/** Product or feature name that is using AI services (used for lookup/auto-creation) */
|
|
73
|
+
productName?: string;
|
|
74
|
+
/**
|
|
75
|
+
* @deprecated Use productName instead. This field will be removed in a future version.
|
|
76
|
+
* Product or feature identifier that is using AI services
|
|
77
|
+
*/
|
|
68
78
|
productId?: string;
|
|
69
79
|
/** Agent or bot identifier for automated systems */
|
|
70
80
|
agent?: string;
|
|
71
81
|
/** Quality score of AI response (0.0-1.0) for performance tracking */
|
|
72
82
|
responseQualityScore?: number;
|
|
73
|
-
/**
|
|
83
|
+
/** Per-call override for prompt capture (overrides environment variable and config) */
|
|
74
84
|
capturePrompts?: boolean;
|
|
75
85
|
/** Allow additional custom fields for extensibility */
|
|
76
86
|
[key: string]: unknown;
|
|
@@ -133,8 +143,12 @@ export interface TrackingData {
|
|
|
133
143
|
responseTime: Date;
|
|
134
144
|
/** Time to first token in milliseconds (for streaming responses) */
|
|
135
145
|
timeToFirstToken?: number;
|
|
146
|
+
/** Whether the request contains vision/image content */
|
|
147
|
+
hasVisionContent?: boolean;
|
|
136
148
|
/** Request body containing Anthropic message parameters */
|
|
137
149
|
requestBody?: AnthropicMessageParams;
|
|
150
|
+
/** Response data from Anthropic API */
|
|
151
|
+
response?: AnthropicResponse;
|
|
138
152
|
}
|
|
139
153
|
/**
|
|
140
154
|
* Internal payload structure for Revenium API
|
|
@@ -165,10 +179,10 @@ export interface ReveniumPayload {
|
|
|
165
179
|
cacheReadTokenCount: number;
|
|
166
180
|
/** Total token count (sum of all token types) */
|
|
167
181
|
totalTokenCount: number;
|
|
168
|
-
/** Organization
|
|
169
|
-
|
|
170
|
-
/** Product
|
|
171
|
-
|
|
182
|
+
/** Organization name for multi-tenant tracking (used for lookup/auto-creation) */
|
|
183
|
+
organizationName?: string;
|
|
184
|
+
/** Product name (used for lookup/auto-creation) */
|
|
185
|
+
productName?: string;
|
|
172
186
|
/** Subscriber information */
|
|
173
187
|
subscriber?: Subscriber;
|
|
174
188
|
/** Subscription identifier */
|
|
@@ -201,9 +215,14 @@ export interface ReveniumPayload {
|
|
|
201
215
|
parentTransactionId?: string;
|
|
202
216
|
transactionName?: string;
|
|
203
217
|
region?: string;
|
|
218
|
+
hasVisionContent?: boolean;
|
|
204
219
|
credentialAlias?: string;
|
|
205
220
|
traceType?: string;
|
|
206
221
|
traceName?: string;
|
|
222
|
+
systemPrompt?: string;
|
|
223
|
+
inputMessages?: string;
|
|
224
|
+
outputResponse?: string;
|
|
225
|
+
promptsTruncated?: boolean;
|
|
207
226
|
}
|
|
208
227
|
/**
|
|
209
228
|
* Anthropic content block types for message validation
|
|
@@ -4,6 +4,7 @@ export declare function getCredentialAlias(): string | null;
|
|
|
4
4
|
export declare function getTraceType(): string | null;
|
|
5
5
|
export declare function getTraceName(): string | null;
|
|
6
6
|
export declare function detectOperationSubtype(requestBody?: any): string | null;
|
|
7
|
+
export declare function detectVisionContent(params?: any): boolean;
|
|
7
8
|
export declare function getParentTransactionId(): string | null;
|
|
8
9
|
export declare function getTransactionName(): string | null;
|
|
9
10
|
export declare function getRetryNumber(): number;
|
|
@@ -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 { ReveniumConfig, UsageMetadata } from
|
|
5
|
+
import { ReveniumConfig, UsageMetadata } from "../types";
|
|
6
6
|
/**
|
|
7
7
|
* Type guard for checking if a value is a non-null object
|
|
8
8
|
*/
|
package/examples/advanced.ts
CHANGED
|
@@ -17,8 +17,8 @@ const streamingWithMetadata = async () => {
|
|
|
17
17
|
try {
|
|
18
18
|
const metadata: UsageMetadata = {
|
|
19
19
|
subscriber: { id: "user-123", email: "user@example.com" },
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
organizationName: "my-org",
|
|
21
|
+
productName: "my-product",
|
|
22
22
|
taskType: "streaming-demo",
|
|
23
23
|
traceId: `session_${Date.now()}`,
|
|
24
24
|
};
|
|
@@ -101,7 +101,7 @@ const manualTracking = async () => {
|
|
|
101
101
|
responseTime: now,
|
|
102
102
|
metadata: {
|
|
103
103
|
subscriber: { id: "manual-user" },
|
|
104
|
-
|
|
104
|
+
organizationName: "manual-org",
|
|
105
105
|
taskType: "manual-tracking",
|
|
106
106
|
},
|
|
107
107
|
};
|
|
@@ -111,7 +111,7 @@ const manualTracking = async () => {
|
|
|
111
111
|
console.log(
|
|
112
112
|
`Tracked: ${
|
|
113
113
|
trackingData.inputTokens + trackingData.outputTokens
|
|
114
|
-
} tokens\n
|
|
114
|
+
} tokens\n`,
|
|
115
115
|
);
|
|
116
116
|
|
|
117
117
|
const status = getStatus();
|
|
@@ -27,11 +27,11 @@ async function main() {
|
|
|
27
27
|
},
|
|
28
28
|
|
|
29
29
|
// Organization & billing
|
|
30
|
-
|
|
30
|
+
organizationName: 'my-customers-name',
|
|
31
31
|
subscriptionId: 'plan-enterprise-2024',
|
|
32
32
|
|
|
33
33
|
// Product & task tracking
|
|
34
|
-
|
|
34
|
+
productName: 'my-product',
|
|
35
35
|
taskType: 'doc-summary',
|
|
36
36
|
agent: 'customer-support',
|
|
37
37
|
|
package/examples/metadata.ts
CHANGED
|
@@ -15,9 +15,9 @@ async function main() {
|
|
|
15
15
|
},
|
|
16
16
|
traceId: "trace-123",
|
|
17
17
|
taskType: "task-123",
|
|
18
|
-
|
|
18
|
+
organizationName: "AcmeCorp",
|
|
19
19
|
subscriptionId: "sub-123",
|
|
20
|
-
|
|
20
|
+
productName: "ai-assistant",
|
|
21
21
|
agent: "agent-123",
|
|
22
22
|
responseQualityScore: 0.95,
|
|
23
23
|
};
|
|
@@ -48,7 +48,7 @@ async function main() {
|
|
|
48
48
|
: "Non-text response";
|
|
49
49
|
console.log("RESPONSE: \n", textResponse);
|
|
50
50
|
console.log(
|
|
51
|
-
`Tokens: ${response.usage?.input_tokens} input + ${response.usage?.output_tokens} output\n
|
|
51
|
+
`Tokens: ${response.usage?.input_tokens} input + ${response.usage?.output_tokens} output\n`,
|
|
52
52
|
);
|
|
53
53
|
} catch (error) {
|
|
54
54
|
console.error("Error: ", error);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { configure } from "../src/index";
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
console.log("=== Anthropic Prompt Capture Example ===\n");
|
|
6
|
+
|
|
7
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
8
|
+
console.error("Error: ANTHROPIC_API_KEY environment variable is required");
|
|
9
|
+
console.error("Please set it before running this example:");
|
|
10
|
+
console.error(" export ANTHROPIC_API_KEY=your_api_key_here");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const config = {
|
|
15
|
+
reveniumApiKey: process.env.REVENIUM_METERING_API_KEY || "hak_test_key",
|
|
16
|
+
reveniumBaseUrl:
|
|
17
|
+
process.env.REVENIUM_METERING_BASE_URL || "https://api.revenium.ai",
|
|
18
|
+
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
|
19
|
+
capturePrompts: true,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
configure(config);
|
|
23
|
+
|
|
24
|
+
const anthropic = new Anthropic({
|
|
25
|
+
apiKey: config.anthropicApiKey,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log("Example 1: Prompt capture enabled via config");
|
|
29
|
+
console.log("Making request with prompt capture enabled...\n");
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const response = await anthropic.messages.create({
|
|
33
|
+
model: "claude-3-5-sonnet-20241022",
|
|
34
|
+
max_tokens: 100,
|
|
35
|
+
system: "You are a helpful assistant that provides concise answers.",
|
|
36
|
+
messages: [
|
|
37
|
+
{
|
|
38
|
+
role: "user",
|
|
39
|
+
content: "What is the capital of France?",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
usageMetadata: {
|
|
43
|
+
organizationName: "org-prompt-capture-demo",
|
|
44
|
+
productName: "prod-anthropic-prompt-capture",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
console.log(
|
|
49
|
+
"Response:",
|
|
50
|
+
response.content[0].type === "text" ? response.content[0].text : "",
|
|
51
|
+
);
|
|
52
|
+
console.log("\nPrompts captured and sent to Revenium API!");
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(
|
|
55
|
+
"Error:",
|
|
56
|
+
error instanceof Error ? error.message : String(error),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log("\n" + "=".repeat(50) + "\n");
|
|
61
|
+
|
|
62
|
+
console.log("Example 2: Per-call override to disable prompt capture");
|
|
63
|
+
console.log("Making request with prompt capture disabled via metadata...\n");
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const response2 = await anthropic.messages.create({
|
|
67
|
+
model: "claude-3-5-sonnet-20241022",
|
|
68
|
+
max_tokens: 100,
|
|
69
|
+
system: "You are a helpful assistant.",
|
|
70
|
+
messages: [
|
|
71
|
+
{
|
|
72
|
+
role: "user",
|
|
73
|
+
content: "What is 2+2?",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
usageMetadata: {
|
|
77
|
+
organizationName: "org-prompt-capture-demo",
|
|
78
|
+
productName: "prod-anthropic-prompt-capture",
|
|
79
|
+
capturePrompts: false,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
console.log(
|
|
84
|
+
"Response:",
|
|
85
|
+
response2.content[0].type === "text" ? response2.content[0].text : "",
|
|
86
|
+
);
|
|
87
|
+
console.log("\nPrompts NOT captured (overridden via metadata)!");
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(
|
|
90
|
+
"Error:",
|
|
91
|
+
error instanceof Error ? error.message : String(error),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log("\n" + "=".repeat(50) + "\n");
|
|
96
|
+
console.log("Examples completed!");
|
|
97
|
+
console.log(
|
|
98
|
+
"\nNote: Set REVENIUM_CAPTURE_PROMPTS=true in .env to enable globally",
|
|
99
|
+
);
|
|
100
|
+
console.log(
|
|
101
|
+
"Or use capturePrompts: true in config or metadata for per-call control",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
main().catch(console.error);
|
package/package.json
CHANGED