@revenium/anthropic 1.0.8 → 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 +34 -1
- package/README.md +156 -49
- package/dist/cjs/config.js +55 -7
- package/dist/cjs/constants.js +39 -24
- package/dist/cjs/tracking.js +82 -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 +55 -23
- package/dist/cjs/wrapper.js +49 -41
- package/dist/esm/config.js +55 -7
- package/dist/esm/constants.js +38 -23
- package/dist/esm/tracking.js +82 -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 +56 -24
- package/dist/esm/wrapper.js +55 -47
- package/dist/types/config.d.ts +1 -0
- package/dist/types/constants.d.ts +15 -0
- 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/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
package/dist/cjs/tracking.js
CHANGED
|
@@ -15,8 +15,21 @@ const config_1 = require("./config");
|
|
|
15
15
|
const circuit_breaker_1 = require("./utils/circuit-breaker");
|
|
16
16
|
const error_handling_1 = require("./utils/error-handling");
|
|
17
17
|
const constants_1 = require("./constants");
|
|
18
|
+
const trace_fields_1 = require("./utils/trace-fields");
|
|
19
|
+
const summary_printer_1 = require("./utils/summary-printer");
|
|
18
20
|
// Global logger
|
|
19
21
|
const logger = (0, config_1.getLogger)();
|
|
22
|
+
if (typeof process !== "undefined") {
|
|
23
|
+
process.on("uncaughtException", (error) => {
|
|
24
|
+
if (error.name === "AbortError") {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (error.stack && error.stack.includes("tracking.ts")) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
20
33
|
/**
|
|
21
34
|
* Send tracking data to Revenium API with resilience patterns
|
|
22
35
|
*/
|
|
@@ -34,7 +47,8 @@ async function sendReveniumMetrics(data) {
|
|
|
34
47
|
isStreamed: data.isStreamed,
|
|
35
48
|
});
|
|
36
49
|
// Build payload using exact structure from working implementations
|
|
37
|
-
|
|
50
|
+
// Built early so we can print summary even if Revenium tracking fails
|
|
51
|
+
const payload = await buildReveniumPayload(data);
|
|
38
52
|
// Create request options
|
|
39
53
|
const requestOptions = {
|
|
40
54
|
method: "POST",
|
|
@@ -50,10 +64,6 @@ async function sendReveniumMetrics(data) {
|
|
|
50
64
|
const controller = new AbortController();
|
|
51
65
|
const timeoutId = setTimeout(() => controller.abort(), requestOptions.timeout);
|
|
52
66
|
requestOptions.signal = controller.signal;
|
|
53
|
-
// Handle abort signal errors to prevent unhandled error events
|
|
54
|
-
controller.signal.addEventListener("abort", () => {
|
|
55
|
-
// Silently handle abort - this is expected behavior for timeouts
|
|
56
|
-
});
|
|
57
67
|
try {
|
|
58
68
|
// Execute with circuit breaker and retry logic
|
|
59
69
|
await (0, circuit_breaker_1.executeWithCircuitBreaker)(async () => {
|
|
@@ -66,9 +76,12 @@ async function sendReveniumMetrics(data) {
|
|
|
66
76
|
let response;
|
|
67
77
|
try {
|
|
68
78
|
response = await (0, node_fetch_1.default)(`${config.reveniumBaseUrl}${constants_1.API_ENDPOINTS.AI_COMPLETIONS}`, requestOptions);
|
|
79
|
+
if (response.body) {
|
|
80
|
+
response.body.once("error", () => { });
|
|
81
|
+
response.body.resume();
|
|
82
|
+
}
|
|
69
83
|
}
|
|
70
84
|
catch (fetchError) {
|
|
71
|
-
// Handle AbortError and other fetch errors
|
|
72
85
|
if (fetchError instanceof Error && fetchError.name === "AbortError") {
|
|
73
86
|
throw new Error(`Request timeout after ${requestOptions.timeout}ms`);
|
|
74
87
|
}
|
|
@@ -91,6 +104,17 @@ async function sendReveniumMetrics(data) {
|
|
|
91
104
|
status: response.status,
|
|
92
105
|
duration: data.duration,
|
|
93
106
|
});
|
|
107
|
+
try {
|
|
108
|
+
await response.text();
|
|
109
|
+
}
|
|
110
|
+
catch (bodyError) {
|
|
111
|
+
logger.debug("Error reading response body (non-critical)", {
|
|
112
|
+
requestId,
|
|
113
|
+
error: bodyError instanceof Error
|
|
114
|
+
? bodyError.message
|
|
115
|
+
: String(bodyError),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
94
118
|
return response;
|
|
95
119
|
}, config.maxRetries ?? constants_1.DEFAULT_CONFIG.MAX_RETRIES);
|
|
96
120
|
});
|
|
@@ -111,19 +135,28 @@ async function sendReveniumMetrics(data) {
|
|
|
111
135
|
}
|
|
112
136
|
finally {
|
|
113
137
|
clearTimeout(timeoutId);
|
|
138
|
+
// Print usage summary regardless of whether Revenium tracking succeeded or failed
|
|
139
|
+
// This ensures users see the summary "after each Anthropic API request" as documented
|
|
140
|
+
(0, summary_printer_1.printUsageSummary)(payload);
|
|
114
141
|
}
|
|
115
142
|
}
|
|
116
143
|
/**
|
|
117
144
|
* Build Revenium payload from tracking data
|
|
118
145
|
*/
|
|
119
|
-
function buildReveniumPayload(data) {
|
|
146
|
+
async function buildReveniumPayload(data) {
|
|
120
147
|
const now = new Date().toISOString();
|
|
121
148
|
const requestTime = data.requestTime.toISOString();
|
|
122
149
|
const completionStartTime = data.responseTime.toISOString();
|
|
123
150
|
// Standard known fields
|
|
124
151
|
const knownFields = new Set([
|
|
125
|
-
|
|
126
|
-
|
|
152
|
+
"taskType",
|
|
153
|
+
"agent",
|
|
154
|
+
"organizationId",
|
|
155
|
+
"productId",
|
|
156
|
+
"subscriber",
|
|
157
|
+
"subscriptionId",
|
|
158
|
+
"traceId",
|
|
159
|
+
"responseQualityScore",
|
|
127
160
|
]);
|
|
128
161
|
// Extract custom fields (anything NOT in the standard 8 fields)
|
|
129
162
|
const customFields = {};
|
|
@@ -134,23 +167,26 @@ function buildReveniumPayload(data) {
|
|
|
134
167
|
}
|
|
135
168
|
}
|
|
136
169
|
}
|
|
170
|
+
const environment = (0, trace_fields_1.getEnvironment)();
|
|
171
|
+
const region = await (0, trace_fields_1.getRegion)();
|
|
172
|
+
const credentialAlias = (0, trace_fields_1.getCredentialAlias)();
|
|
173
|
+
const traceType = (0, trace_fields_1.getTraceType)();
|
|
174
|
+
const traceName = (0, trace_fields_1.getTraceName)();
|
|
175
|
+
const parentTransactionId = (0, trace_fields_1.getParentTransactionId)();
|
|
176
|
+
const transactionName = (0, trace_fields_1.getTransactionName)();
|
|
177
|
+
const retryNumber = (0, trace_fields_1.getRetryNumber)();
|
|
178
|
+
const operationSubtype = (0, trace_fields_1.detectOperationSubtype)(data.requestBody);
|
|
137
179
|
return {
|
|
138
180
|
stopReason: getStopReason(data.stopReason),
|
|
139
181
|
costType: "AI",
|
|
140
182
|
isStreamed: data.isStreamed,
|
|
141
|
-
taskType: data.metadata?.taskType,
|
|
142
|
-
agent: data.metadata?.agent,
|
|
143
183
|
operationType: "CHAT",
|
|
144
184
|
inputTokenCount: data.inputTokens,
|
|
145
185
|
outputTokenCount: data.outputTokens,
|
|
146
|
-
reasoningTokenCount: 0,
|
|
186
|
+
reasoningTokenCount: 0,
|
|
147
187
|
cacheCreationTokenCount: data.cacheCreationTokens || 0,
|
|
148
188
|
cacheReadTokenCount: data.cacheReadTokens || 0,
|
|
149
189
|
totalTokenCount: data.inputTokens + data.outputTokens,
|
|
150
|
-
organizationId: data.metadata?.organizationId,
|
|
151
|
-
productId: data.metadata?.productId,
|
|
152
|
-
subscriber: data.metadata?.subscriber, // Pass through nested subscriber object directly
|
|
153
|
-
subscriptionId: data.metadata?.subscriptionId,
|
|
154
190
|
model: data.model,
|
|
155
191
|
transactionId: data.requestId,
|
|
156
192
|
responseTime: now,
|
|
@@ -159,10 +195,31 @@ function buildReveniumPayload(data) {
|
|
|
159
195
|
requestTime: requestTime,
|
|
160
196
|
completionStartTime: completionStartTime,
|
|
161
197
|
timeToFirstToken: data.timeToFirstToken || 0,
|
|
162
|
-
traceId: data.metadata?.traceId,
|
|
163
|
-
responseQualityScore: data.metadata?.responseQualityScore, // Fixed: Now sending to Revenium
|
|
164
198
|
middlewareSource: "nodejs",
|
|
165
|
-
...
|
|
199
|
+
...(data.metadata?.taskType && { taskType: data.metadata.taskType }),
|
|
200
|
+
...(data.metadata?.agent && { agent: data.metadata.agent }),
|
|
201
|
+
...(data.metadata?.organizationId && {
|
|
202
|
+
organizationId: data.metadata.organizationId,
|
|
203
|
+
}),
|
|
204
|
+
...(data.metadata?.productId && { productId: data.metadata.productId }),
|
|
205
|
+
...(data.metadata?.subscriber && { subscriber: data.metadata.subscriber }),
|
|
206
|
+
...(data.metadata?.subscriptionId && {
|
|
207
|
+
subscriptionId: data.metadata.subscriptionId,
|
|
208
|
+
}),
|
|
209
|
+
...(data.metadata?.traceId && { traceId: data.metadata.traceId }),
|
|
210
|
+
...(data.metadata?.responseQualityScore !== undefined && {
|
|
211
|
+
responseQualityScore: data.metadata.responseQualityScore,
|
|
212
|
+
}),
|
|
213
|
+
...(environment && { environment }),
|
|
214
|
+
...(region && { region }),
|
|
215
|
+
...(credentialAlias && { credentialAlias }),
|
|
216
|
+
...(traceType && { traceType }),
|
|
217
|
+
...(traceName && { traceName }),
|
|
218
|
+
...(parentTransactionId && { parentTransactionId }),
|
|
219
|
+
...(transactionName && { transactionName }),
|
|
220
|
+
...(retryNumber !== undefined && { retryNumber }),
|
|
221
|
+
...(operationSubtype && { operationSubtype }),
|
|
222
|
+
...customFields,
|
|
166
223
|
};
|
|
167
224
|
}
|
|
168
225
|
/**
|
|
@@ -184,7 +241,6 @@ function trackUsageAsync(trackingData) {
|
|
|
184
241
|
return logger.warn("Revenium configuration not available - skipping tracking", {
|
|
185
242
|
requestId: trackingData.requestId,
|
|
186
243
|
});
|
|
187
|
-
// Run tracking in background without awaiting
|
|
188
244
|
sendReveniumMetrics(trackingData)
|
|
189
245
|
.then(() => {
|
|
190
246
|
logger.debug("Revenium tracking completed successfully", {
|
|
@@ -194,6 +250,12 @@ function trackUsageAsync(trackingData) {
|
|
|
194
250
|
});
|
|
195
251
|
})
|
|
196
252
|
.catch((error) => {
|
|
253
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
254
|
+
logger.debug("Metrics request aborted (process exiting)", {
|
|
255
|
+
requestId: trackingData.requestId,
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
197
259
|
const errorContext = (0, error_handling_1.createErrorContext)()
|
|
198
260
|
.withRequestId(trackingData.requestId)
|
|
199
261
|
.withModel(trackingData.model)
|
|
@@ -19,68 +19,6 @@
|
|
|
19
19
|
* - **Automatic Validation**: TypeScript validates the structure at compile time
|
|
20
20
|
* - **Better Developer Experience**: Auto-completion and error detection
|
|
21
21
|
*
|
|
22
|
-
* ## Usage Examples:
|
|
23
|
-
*
|
|
24
|
-
* ### Basic Usage:
|
|
25
|
-
* ```typescript
|
|
26
|
-
* import '@revenium/anthropic';
|
|
27
|
-
* import Anthropic from '@anthropic-ai/sdk';
|
|
28
|
-
*
|
|
29
|
-
* const anthropic = new Anthropic();
|
|
30
|
-
*
|
|
31
|
-
* const response = await anthropic.messages.create({
|
|
32
|
-
* model: 'claude-3-5-sonnet-latest',
|
|
33
|
-
* max_tokens: 1024,
|
|
34
|
-
* messages: [{ role: 'user', content: 'Hello!' }],
|
|
35
|
-
* usageMetadata: { // TypeScript recognizes this natively
|
|
36
|
-
* subscriber: { id: 'user-123', email: 'user@example.com' },
|
|
37
|
-
* organizationId: 'my-company',
|
|
38
|
-
* taskType: 'customer-support',
|
|
39
|
-
* traceId: 'session-abc-123'
|
|
40
|
-
* }
|
|
41
|
-
* });
|
|
42
|
-
* ```
|
|
43
|
-
*
|
|
44
|
-
* ### Streaming Usage:
|
|
45
|
-
* ```typescript
|
|
46
|
-
* const stream = await anthropic.messages.stream({
|
|
47
|
-
* model: 'claude-3-5-sonnet-latest',
|
|
48
|
-
* max_tokens: 1024,
|
|
49
|
-
* messages: [{ role: 'user', content: 'Generate a report' }],
|
|
50
|
-
* usageMetadata: {
|
|
51
|
-
* taskType: 'content-generation',
|
|
52
|
-
* productId: 'report-generator',
|
|
53
|
-
* responseQualityScore: 0.95
|
|
54
|
-
* }
|
|
55
|
-
* });
|
|
56
|
-
* ```
|
|
57
|
-
*
|
|
58
|
-
* ### Advanced Usage with All Fields:
|
|
59
|
-
* ```typescript
|
|
60
|
-
* const response = await anthropic.messages.create({
|
|
61
|
-
* model: 'claude-3-5-sonnet-latest',
|
|
62
|
-
* max_tokens: 2048,
|
|
63
|
-
* messages: [{ role: 'user', content: 'Complex analysis task' }],
|
|
64
|
-
* usageMetadata: {
|
|
65
|
-
* subscriber: {
|
|
66
|
-
* id: 'user-456',
|
|
67
|
-
* email: 'analyst@company.com',
|
|
68
|
-
* credential: { name: 'api-key', value: 'sk-...' }
|
|
69
|
-
* },
|
|
70
|
-
* traceId: 'analysis-session-789',
|
|
71
|
-
* taskType: 'data-analysis',
|
|
72
|
-
* organizationId: 'enterprise-client',
|
|
73
|
-
* subscriptionId: 'premium-plan',
|
|
74
|
-
* productId: 'analytics-suite',
|
|
75
|
-
* agent: 'data-analyst-bot',
|
|
76
|
-
* responseQualityScore: 0.98,
|
|
77
|
-
* customField: 'custom-value' // Extensible with custom fields
|
|
78
|
-
* }
|
|
79
|
-
* });
|
|
80
|
-
* ```
|
|
81
|
-
*
|
|
82
|
-
* @public
|
|
83
|
-
* @since 1.1.0
|
|
84
22
|
*/
|
|
85
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
86
24
|
//# sourceMappingURL=anthropic-augmentation.js.map
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.printUsageSummary = printUsageSummary;
|
|
4
|
+
const config_1 = require("../config");
|
|
5
|
+
const constants_1 = require("../constants");
|
|
6
|
+
const logger = (0, config_1.getLogger)();
|
|
7
|
+
function delayWithUnref(ms) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
const timer = setTimeout(resolve, ms);
|
|
10
|
+
if (typeof timer.unref === "function") {
|
|
11
|
+
timer.unref();
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async function fetchCompletionMetrics(transactionId, maxRetries = constants_1.SUMMARY_PRINTER_CONFIG.MAX_RETRIES, retryDelay = constants_1.SUMMARY_PRINTER_CONFIG.RETRY_DELAY) {
|
|
16
|
+
const config = (0, config_1.getConfig)();
|
|
17
|
+
if (!config) {
|
|
18
|
+
logger.debug("No config available for summary printing");
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
if (!config.teamId) {
|
|
22
|
+
logger.debug("Team ID not configured, skipping cost retrieval for summary");
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const baseUrl = (config.reveniumBaseUrl || constants_1.DEFAULT_CONFIG.REVENIUM_BASE_URL).replace(/\/+$/, "");
|
|
26
|
+
const url = `${baseUrl}/profitstream/v2/api/sources/metrics/ai/completions`;
|
|
27
|
+
const teamId = config.teamId.trim();
|
|
28
|
+
const urlWithParams = `${url}?teamId=${encodeURIComponent(teamId)}&transactionId=${encodeURIComponent(transactionId)}`;
|
|
29
|
+
logger.debug("Fetching completion metrics", { url: urlWithParams });
|
|
30
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
31
|
+
// Create an AbortController with timeout to prevent hung requests from keeping Node process alive
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timeoutId = setTimeout(() => controller.abort(), constants_1.SUMMARY_PRINTER_CONFIG.FETCH_TIMEOUT);
|
|
34
|
+
// Unref the timer so it doesn't keep the process alive
|
|
35
|
+
if (typeof timeoutId.unref === "function") {
|
|
36
|
+
timeoutId.unref();
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(urlWithParams, {
|
|
40
|
+
method: "GET",
|
|
41
|
+
headers: {
|
|
42
|
+
Accept: "application/json",
|
|
43
|
+
"x-api-key": config.reveniumApiKey,
|
|
44
|
+
},
|
|
45
|
+
signal: controller.signal,
|
|
46
|
+
});
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
try {
|
|
49
|
+
await response.text();
|
|
50
|
+
}
|
|
51
|
+
catch { }
|
|
52
|
+
logger.debug(`Completions metrics API returned ${response.status}`, {
|
|
53
|
+
attempt: attempt + 1,
|
|
54
|
+
});
|
|
55
|
+
if (attempt < maxRetries - 1) {
|
|
56
|
+
await delayWithUnref(retryDelay);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const data = (await response.json());
|
|
62
|
+
const completions = data._embedded?.aICompletionMetricResourceList;
|
|
63
|
+
if (completions && completions.length > 0) {
|
|
64
|
+
return completions[0];
|
|
65
|
+
}
|
|
66
|
+
if (attempt < maxRetries - 1) {
|
|
67
|
+
logger.debug(`Waiting for metrics to aggregate (attempt ${attempt + 1}/${maxRetries})...`);
|
|
68
|
+
await delayWithUnref(retryDelay);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
logger.debug("Failed to fetch completion metrics", {
|
|
73
|
+
error: error instanceof Error ? error.message : String(error),
|
|
74
|
+
attempt: attempt + 1,
|
|
75
|
+
});
|
|
76
|
+
if (attempt < maxRetries - 1) {
|
|
77
|
+
await delayWithUnref(retryDelay);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
clearTimeout(timeoutId);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function isSummaryFormat(value) {
|
|
87
|
+
return value === "human" || value === "json";
|
|
88
|
+
}
|
|
89
|
+
function formatAndPrintJsonSummary(payload, metrics) {
|
|
90
|
+
const config = (0, config_1.getConfig)();
|
|
91
|
+
const summary = {
|
|
92
|
+
model: payload.model,
|
|
93
|
+
provider: payload.provider,
|
|
94
|
+
durationSeconds: payload.requestDuration / 1000,
|
|
95
|
+
inputTokenCount: payload.inputTokenCount,
|
|
96
|
+
outputTokenCount: payload.outputTokenCount,
|
|
97
|
+
totalTokenCount: payload.totalTokenCount,
|
|
98
|
+
cost: typeof metrics?.totalCost === "number" ? metrics.totalCost : null,
|
|
99
|
+
};
|
|
100
|
+
if (summary.cost === null) {
|
|
101
|
+
summary.costStatus = config?.teamId ? "pending" : "unavailable";
|
|
102
|
+
}
|
|
103
|
+
if (payload.traceId) {
|
|
104
|
+
summary.traceId = payload.traceId;
|
|
105
|
+
}
|
|
106
|
+
console.log(JSON.stringify(summary));
|
|
107
|
+
}
|
|
108
|
+
function formatAndPrintHumanSummary(payload, metrics) {
|
|
109
|
+
console.log("\n" + "=".repeat(60));
|
|
110
|
+
console.log("📊 REVENIUM USAGE SUMMARY");
|
|
111
|
+
console.log("=".repeat(60));
|
|
112
|
+
console.log(`🤖 Model: ${payload.model}`);
|
|
113
|
+
console.log(`🏢 Provider: ${payload.provider}`);
|
|
114
|
+
console.log(`⏱️ Duration: ${(payload.requestDuration / 1000).toFixed(2)}s`);
|
|
115
|
+
console.log("\n💬 Token Usage:");
|
|
116
|
+
console.log(` 📥 Input Tokens: ${(payload.inputTokenCount ?? 0).toLocaleString()}`);
|
|
117
|
+
console.log(` 📤 Output Tokens: ${(payload.outputTokenCount ?? 0).toLocaleString()}`);
|
|
118
|
+
console.log(` 📊 Total Tokens: ${(payload.totalTokenCount ?? 0).toLocaleString()}`);
|
|
119
|
+
if (typeof metrics?.totalCost === "number") {
|
|
120
|
+
console.log(`\n💰 Cost: $${metrics.totalCost.toFixed(6)}`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const config = (0, config_1.getConfig)();
|
|
124
|
+
if (!config?.teamId) {
|
|
125
|
+
console.log(`\n💰 Cost: Set REVENIUM_TEAM_ID environment variable to see pricing`);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.log(`\n💰 Cost: (pending aggregation)`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (payload.traceId) {
|
|
132
|
+
console.log(`\n🔖 Trace ID: ${payload.traceId}`);
|
|
133
|
+
}
|
|
134
|
+
console.log("=".repeat(60) + "\n");
|
|
135
|
+
}
|
|
136
|
+
function formatAndPrintSummary(payload, metrics, format) {
|
|
137
|
+
if (format === "json") {
|
|
138
|
+
formatAndPrintJsonSummary(payload, metrics);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
formatAndPrintHumanSummary(payload, metrics);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function safeFormatAndPrintSummary(payload, metrics, format) {
|
|
145
|
+
try {
|
|
146
|
+
formatAndPrintSummary(payload, metrics, format);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
logger.debug("Failed to format and print summary", {
|
|
150
|
+
error: error instanceof Error ? error.message : String(error),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function getSummaryFormat(value) {
|
|
155
|
+
if (!value)
|
|
156
|
+
return null;
|
|
157
|
+
if (value === true)
|
|
158
|
+
return "human";
|
|
159
|
+
if (isSummaryFormat(value)) {
|
|
160
|
+
return value;
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
function printUsageSummary(payload) {
|
|
165
|
+
const config = (0, config_1.getConfig)();
|
|
166
|
+
const format = getSummaryFormat(config?.printSummary);
|
|
167
|
+
if (!format) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (config?.teamId && payload.transactionId) {
|
|
171
|
+
fetchCompletionMetrics(payload.transactionId)
|
|
172
|
+
.then((metrics) => {
|
|
173
|
+
safeFormatAndPrintSummary(payload, metrics, format);
|
|
174
|
+
})
|
|
175
|
+
.catch((error) => {
|
|
176
|
+
logger.debug("Failed to print usage summary with metrics", {
|
|
177
|
+
error: error instanceof Error ? error.message : String(error),
|
|
178
|
+
});
|
|
179
|
+
safeFormatAndPrintSummary(payload, null, format);
|
|
180
|
+
})
|
|
181
|
+
.catch(() => {
|
|
182
|
+
// Final safety catch to prevent unhandled rejections
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
safeFormatAndPrintSummary(payload, null, format);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=summary-printer.js.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getEnvironment = getEnvironment;
|
|
4
|
+
exports.getRegion = getRegion;
|
|
5
|
+
exports.getCredentialAlias = getCredentialAlias;
|
|
6
|
+
exports.getTraceType = getTraceType;
|
|
7
|
+
exports.getTraceName = getTraceName;
|
|
8
|
+
exports.detectOperationSubtype = detectOperationSubtype;
|
|
9
|
+
exports.getParentTransactionId = getParentTransactionId;
|
|
10
|
+
exports.getTransactionName = getTransactionName;
|
|
11
|
+
exports.getRetryNumber = getRetryNumber;
|
|
12
|
+
const config_1 = require("../config");
|
|
13
|
+
const logger = (0, config_1.getLogger)();
|
|
14
|
+
let cachedRegion = null;
|
|
15
|
+
let regionCached = false;
|
|
16
|
+
function getEnvironment() {
|
|
17
|
+
const env = process.env.REVENIUM_ENVIRONMENT ||
|
|
18
|
+
process.env.NODE_ENV ||
|
|
19
|
+
process.env.DEPLOYMENT_ENV ||
|
|
20
|
+
null;
|
|
21
|
+
if (env && env.length > 255) {
|
|
22
|
+
logger.warn(`environment exceeds max length of 255 characters. Truncating.`);
|
|
23
|
+
return env.substring(0, 255).trim();
|
|
24
|
+
}
|
|
25
|
+
return env ? env.trim() : null;
|
|
26
|
+
}
|
|
27
|
+
async function getRegion() {
|
|
28
|
+
if (regionCached) {
|
|
29
|
+
return cachedRegion;
|
|
30
|
+
}
|
|
31
|
+
const envRegion = process.env.AWS_REGION ||
|
|
32
|
+
process.env.AZURE_REGION ||
|
|
33
|
+
process.env.GCP_REGION ||
|
|
34
|
+
process.env.REVENIUM_REGION;
|
|
35
|
+
if (envRegion) {
|
|
36
|
+
cachedRegion = envRegion.trim();
|
|
37
|
+
regionCached = true;
|
|
38
|
+
return cachedRegion;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
const controller = new AbortController();
|
|
42
|
+
const timeoutId = setTimeout(() => controller.abort(), 1000);
|
|
43
|
+
const response = await fetch("http://169.254.169.254/latest/meta-data/placement/region", {
|
|
44
|
+
signal: controller.signal,
|
|
45
|
+
});
|
|
46
|
+
clearTimeout(timeoutId);
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
cachedRegion = null;
|
|
49
|
+
regionCached = true;
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const text = await response.text();
|
|
53
|
+
cachedRegion = text.trim();
|
|
54
|
+
regionCached = true;
|
|
55
|
+
return cachedRegion;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
cachedRegion = null;
|
|
59
|
+
regionCached = true;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function getCredentialAlias() {
|
|
64
|
+
const alias = process.env.REVENIUM_CREDENTIAL_ALIAS || null;
|
|
65
|
+
if (alias && alias.length > 255) {
|
|
66
|
+
logger.warn(`credentialAlias exceeds max length of 255 characters. Truncating.`);
|
|
67
|
+
return alias.substring(0, 255).trim();
|
|
68
|
+
}
|
|
69
|
+
return alias ? alias.trim() : null;
|
|
70
|
+
}
|
|
71
|
+
function getTraceType() {
|
|
72
|
+
const traceType = process.env.REVENIUM_TRACE_TYPE;
|
|
73
|
+
if (!traceType) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(traceType)) {
|
|
77
|
+
logger.warn(`Invalid trace_type format: ${traceType}. Must be alphanumeric with hyphens/underscores only.`);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (traceType.length > 128) {
|
|
81
|
+
logger.warn(`trace_type exceeds max length of 128 characters: ${traceType}. Truncating.`);
|
|
82
|
+
return traceType.substring(0, 128);
|
|
83
|
+
}
|
|
84
|
+
return traceType;
|
|
85
|
+
}
|
|
86
|
+
function getTraceName() {
|
|
87
|
+
const traceName = process.env.REVENIUM_TRACE_NAME;
|
|
88
|
+
if (!traceName) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
if (traceName.length > 256) {
|
|
92
|
+
logger.warn(`trace_name exceeds max length of 256 characters. Truncating.`);
|
|
93
|
+
return traceName.substring(0, 256);
|
|
94
|
+
}
|
|
95
|
+
return traceName;
|
|
96
|
+
}
|
|
97
|
+
function detectOperationSubtype(requestBody) {
|
|
98
|
+
if (requestBody && requestBody.tools && requestBody.tools.length > 0) {
|
|
99
|
+
return "function_call";
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
function getParentTransactionId() {
|
|
104
|
+
return process.env.REVENIUM_PARENT_TRANSACTION_ID || null;
|
|
105
|
+
}
|
|
106
|
+
function getTransactionName() {
|
|
107
|
+
return process.env.REVENIUM_TRANSACTION_NAME || null;
|
|
108
|
+
}
|
|
109
|
+
function getRetryNumber() {
|
|
110
|
+
const retryNum = process.env.REVENIUM_RETRY_NUMBER;
|
|
111
|
+
if (retryNum) {
|
|
112
|
+
const parsed = parseInt(retryNum, 10);
|
|
113
|
+
return isNaN(parsed) ? 0 : parsed;
|
|
114
|
+
}
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=trace-fields.js.map
|
|
@@ -139,29 +139,31 @@ function validateReveniumConfig(config) {
|
|
|
139
139
|
else if (cfg?.reveniumApiKey?.length < constants_1.VALIDATION_CONFIG.MIN_API_KEY_LENGTH) {
|
|
140
140
|
warnings.push('reveniumApiKey appears to be too short - verify it is correct');
|
|
141
141
|
}
|
|
142
|
-
// Validate Revenium base URL
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
142
|
+
// Validate Revenium base URL (optional - defaults to https://api.revenium.ai)
|
|
143
|
+
if (cfg?.reveniumBaseUrl !== undefined) {
|
|
144
|
+
if (!isString(cfg?.reveniumBaseUrl)) {
|
|
145
|
+
errors.push('reveniumBaseUrl must be a string if provided');
|
|
146
|
+
}
|
|
147
|
+
else if (!cfg?.reveniumBaseUrl?.trim()) {
|
|
148
|
+
errors.push('reveniumBaseUrl cannot be empty if provided');
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
try {
|
|
152
|
+
const url = new URL(cfg?.reveniumBaseUrl);
|
|
153
|
+
if (!url.protocol.startsWith('http')) {
|
|
154
|
+
errors.push('reveniumBaseUrl must use HTTP or HTTPS protocol');
|
|
155
|
+
}
|
|
156
|
+
// Check for localhost/development hostnames (IPv4, IPv6, and named)
|
|
157
|
+
const localhostHostnames = ['localhost', '127.0.0.1', '::1', '[::1]'];
|
|
158
|
+
if (localhostHostnames.includes(url.hostname)) {
|
|
159
|
+
warnings.push('Using localhost for Revenium API - ensure this is intended for development');
|
|
160
|
+
}
|
|
154
161
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
warnings.push('Using localhost for Revenium API - ensure this is intended for development');
|
|
162
|
+
catch {
|
|
163
|
+
errors.push('reveniumBaseUrl must be a valid URL');
|
|
164
|
+
suggestions.push('Use format: https://api.revenium.ai');
|
|
159
165
|
}
|
|
160
166
|
}
|
|
161
|
-
catch {
|
|
162
|
-
errors.push('reveniumBaseUrl must be a valid URL');
|
|
163
|
-
suggestions.push('Use format: https://api.revenium.ai');
|
|
164
|
-
}
|
|
165
167
|
}
|
|
166
168
|
// Validate optional Anthropic API key
|
|
167
169
|
if (cfg?.anthropicApiKey !== undefined && !isString(cfg?.anthropicApiKey)) {
|
|
@@ -203,6 +205,23 @@ function validateReveniumConfig(config) {
|
|
|
203
205
|
else if (cfg?.maxRetries !== undefined && cfg?.maxRetries === 0) {
|
|
204
206
|
warnings.push('maxRetries is 0 - no retry attempts will be made');
|
|
205
207
|
}
|
|
208
|
+
// Validate optional printSummary
|
|
209
|
+
if (cfg?.printSummary !== undefined) {
|
|
210
|
+
const validPrintSummaryValues = [true, false, 'human', 'json'];
|
|
211
|
+
if (!validPrintSummaryValues.includes(cfg?.printSummary)) {
|
|
212
|
+
errors.push("printSummary must be a boolean or one of: 'human', 'json'");
|
|
213
|
+
suggestions.push("Use printSummary: true, 'human', or 'json' to enable summary output");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// Validate optional teamId
|
|
217
|
+
if (cfg?.teamId !== undefined) {
|
|
218
|
+
if (!isString(cfg?.teamId)) {
|
|
219
|
+
errors.push('teamId must be a string if provided');
|
|
220
|
+
}
|
|
221
|
+
else if (cfg?.teamId?.trim()?.length === 0) {
|
|
222
|
+
errors.push('teamId cannot be empty if provided');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
206
225
|
if (errors.length > 0) {
|
|
207
226
|
return {
|
|
208
227
|
isValid: false,
|
|
@@ -211,14 +230,27 @@ function validateReveniumConfig(config) {
|
|
|
211
230
|
suggestions
|
|
212
231
|
};
|
|
213
232
|
}
|
|
214
|
-
//
|
|
233
|
+
// Determine validated printSummary value
|
|
234
|
+
let validatedPrintSummary;
|
|
235
|
+
if (cfg?.printSummary === true || cfg?.printSummary === 'human') {
|
|
236
|
+
validatedPrintSummary = 'human';
|
|
237
|
+
}
|
|
238
|
+
else if (cfg?.printSummary === 'json') {
|
|
239
|
+
validatedPrintSummary = 'json';
|
|
240
|
+
}
|
|
241
|
+
else if (cfg?.printSummary === false) {
|
|
242
|
+
validatedPrintSummary = false;
|
|
243
|
+
}
|
|
244
|
+
// Build validated config (apply default for reveniumBaseUrl if not provided)
|
|
215
245
|
const validatedConfig = {
|
|
216
246
|
reveniumApiKey: cfg?.reveniumApiKey,
|
|
217
|
-
reveniumBaseUrl: cfg?.reveniumBaseUrl,
|
|
247
|
+
reveniumBaseUrl: isString(cfg?.reveniumBaseUrl) ? cfg?.reveniumBaseUrl : constants_1.DEFAULT_CONFIG.REVENIUM_BASE_URL,
|
|
218
248
|
anthropicApiKey: isString(cfg?.anthropicApiKey) ? cfg?.anthropicApiKey : undefined,
|
|
219
249
|
apiTimeout: isNumber(cfg?.apiTimeout) ? cfg?.apiTimeout : undefined,
|
|
220
250
|
failSilent: isBoolean(cfg?.failSilent) ? cfg?.failSilent : undefined,
|
|
221
|
-
maxRetries: isNumber(cfg?.maxRetries) ? cfg?.maxRetries : undefined
|
|
251
|
+
maxRetries: isNumber(cfg?.maxRetries) ? cfg?.maxRetries : undefined,
|
|
252
|
+
printSummary: validatedPrintSummary,
|
|
253
|
+
teamId: isString(cfg?.teamId) ? cfg?.teamId?.trim() : undefined
|
|
222
254
|
};
|
|
223
255
|
return {
|
|
224
256
|
isValid: true,
|