@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.
@@ -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 (!isString(cfg?.reveniumBaseUrl)) {
144
- errors.push('reveniumBaseUrl is required and must be a string');
145
- }
146
- else if (!cfg?.reveniumBaseUrl?.trim()) {
147
- errors.push('reveniumBaseUrl cannot be empty');
148
- }
149
- else {
150
- try {
151
- const url = new URL(cfg?.reveniumBaseUrl);
152
- if (!url.protocol.startsWith('http')) {
153
- errors.push('reveniumBaseUrl must use HTTP or HTTPS protocol');
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
- // Check for localhost/development hostnames (IPv4, IPv6, and named)
156
- const localhostHostnames = ['localhost', '127.0.0.1', '::1', '[::1]'];
157
- if (localhostHostnames.includes(url.hostname)) {
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
- // Build validated config
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,