@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.
@@ -6,8 +6,21 @@ import { getConfig, getLogger } from "./config.js";
6
6
  import { executeWithCircuitBreaker } from "./utils/circuit-breaker.js";
7
7
  import { withRetry, ReveniumApiError, createErrorContext, handleError, } from "./utils/error-handling.js";
8
8
  import { DEFAULT_CONFIG, API_ENDPOINTS, LOGGING_CONFIG, ANTHROPIC_PATTERNS, } from "./constants.js";
9
+ import { getEnvironment, getRegion, getCredentialAlias, getTraceType, getTraceName, detectOperationSubtype, getParentTransactionId, getTransactionName, getRetryNumber, } from "./utils/trace-fields.js";
10
+ import { printUsageSummary } from "./utils/summary-printer.js";
9
11
  // Global logger
10
12
  const logger = getLogger();
13
+ if (typeof process !== "undefined") {
14
+ process.on("uncaughtException", (error) => {
15
+ if (error.name === "AbortError") {
16
+ return;
17
+ }
18
+ if (error.stack && error.stack.includes("tracking.ts")) {
19
+ return;
20
+ }
21
+ throw error;
22
+ });
23
+ }
11
24
  /**
12
25
  * Send tracking data to Revenium API with resilience patterns
13
26
  */
@@ -25,7 +38,8 @@ export async function sendReveniumMetrics(data) {
25
38
  isStreamed: data.isStreamed,
26
39
  });
27
40
  // Build payload using exact structure from working implementations
28
- const payload = buildReveniumPayload(data);
41
+ // Built early so we can print summary even if Revenium tracking fails
42
+ const payload = await buildReveniumPayload(data);
29
43
  // Create request options
30
44
  const requestOptions = {
31
45
  method: "POST",
@@ -41,10 +55,6 @@ export async function sendReveniumMetrics(data) {
41
55
  const controller = new AbortController();
42
56
  const timeoutId = setTimeout(() => controller.abort(), requestOptions.timeout);
43
57
  requestOptions.signal = controller.signal;
44
- // Handle abort signal errors to prevent unhandled error events
45
- controller.signal.addEventListener("abort", () => {
46
- // Silently handle abort - this is expected behavior for timeouts
47
- });
48
58
  try {
49
59
  // Execute with circuit breaker and retry logic
50
60
  await executeWithCircuitBreaker(async () => {
@@ -57,9 +67,12 @@ export async function sendReveniumMetrics(data) {
57
67
  let response;
58
68
  try {
59
69
  response = await fetch(`${config.reveniumBaseUrl}${API_ENDPOINTS.AI_COMPLETIONS}`, requestOptions);
70
+ if (response.body) {
71
+ response.body.once("error", () => { });
72
+ response.body.resume();
73
+ }
60
74
  }
61
75
  catch (fetchError) {
62
- // Handle AbortError and other fetch errors
63
76
  if (fetchError instanceof Error && fetchError.name === "AbortError") {
64
77
  throw new Error(`Request timeout after ${requestOptions.timeout}ms`);
65
78
  }
@@ -82,6 +95,17 @@ export async function sendReveniumMetrics(data) {
82
95
  status: response.status,
83
96
  duration: data.duration,
84
97
  });
98
+ try {
99
+ await response.text();
100
+ }
101
+ catch (bodyError) {
102
+ logger.debug("Error reading response body (non-critical)", {
103
+ requestId,
104
+ error: bodyError instanceof Error
105
+ ? bodyError.message
106
+ : String(bodyError),
107
+ });
108
+ }
85
109
  return response;
86
110
  }, config.maxRetries ?? DEFAULT_CONFIG.MAX_RETRIES);
87
111
  });
@@ -102,19 +126,28 @@ export async function sendReveniumMetrics(data) {
102
126
  }
103
127
  finally {
104
128
  clearTimeout(timeoutId);
129
+ // Print usage summary regardless of whether Revenium tracking succeeded or failed
130
+ // This ensures users see the summary "after each Anthropic API request" as documented
131
+ printUsageSummary(payload);
105
132
  }
106
133
  }
107
134
  /**
108
135
  * Build Revenium payload from tracking data
109
136
  */
110
- function buildReveniumPayload(data) {
137
+ async function buildReveniumPayload(data) {
111
138
  const now = new Date().toISOString();
112
139
  const requestTime = data.requestTime.toISOString();
113
140
  const completionStartTime = data.responseTime.toISOString();
114
141
  // Standard known fields
115
142
  const knownFields = new Set([
116
- 'taskType', 'agent', 'organizationId', 'productId', 'subscriber',
117
- 'subscriptionId', 'traceId', 'responseQualityScore'
143
+ "taskType",
144
+ "agent",
145
+ "organizationId",
146
+ "productId",
147
+ "subscriber",
148
+ "subscriptionId",
149
+ "traceId",
150
+ "responseQualityScore",
118
151
  ]);
119
152
  // Extract custom fields (anything NOT in the standard 8 fields)
120
153
  const customFields = {};
@@ -125,23 +158,26 @@ function buildReveniumPayload(data) {
125
158
  }
126
159
  }
127
160
  }
161
+ const environment = getEnvironment();
162
+ const region = await getRegion();
163
+ const credentialAlias = getCredentialAlias();
164
+ const traceType = getTraceType();
165
+ const traceName = getTraceName();
166
+ const parentTransactionId = getParentTransactionId();
167
+ const transactionName = getTransactionName();
168
+ const retryNumber = getRetryNumber();
169
+ const operationSubtype = detectOperationSubtype(data.requestBody);
128
170
  return {
129
171
  stopReason: getStopReason(data.stopReason),
130
172
  costType: "AI",
131
173
  isStreamed: data.isStreamed,
132
- taskType: data.metadata?.taskType,
133
- agent: data.metadata?.agent,
134
174
  operationType: "CHAT",
135
175
  inputTokenCount: data.inputTokens,
136
176
  outputTokenCount: data.outputTokens,
137
- reasoningTokenCount: 0, // Anthropic doesn't currently have reasoning tokens
177
+ reasoningTokenCount: 0,
138
178
  cacheCreationTokenCount: data.cacheCreationTokens || 0,
139
179
  cacheReadTokenCount: data.cacheReadTokens || 0,
140
180
  totalTokenCount: data.inputTokens + data.outputTokens,
141
- organizationId: data.metadata?.organizationId,
142
- productId: data.metadata?.productId,
143
- subscriber: data.metadata?.subscriber, // Pass through nested subscriber object directly
144
- subscriptionId: data.metadata?.subscriptionId,
145
181
  model: data.model,
146
182
  transactionId: data.requestId,
147
183
  responseTime: now,
@@ -150,10 +186,31 @@ function buildReveniumPayload(data) {
150
186
  requestTime: requestTime,
151
187
  completionStartTime: completionStartTime,
152
188
  timeToFirstToken: data.timeToFirstToken || 0,
153
- traceId: data.metadata?.traceId,
154
- responseQualityScore: data.metadata?.responseQualityScore, // Fixed: Now sending to Revenium
155
189
  middlewareSource: "nodejs",
156
- ...customFields, // Preserve any custom metadata fields
190
+ ...(data.metadata?.taskType && { taskType: data.metadata.taskType }),
191
+ ...(data.metadata?.agent && { agent: data.metadata.agent }),
192
+ ...(data.metadata?.organizationId && {
193
+ organizationId: data.metadata.organizationId,
194
+ }),
195
+ ...(data.metadata?.productId && { productId: data.metadata.productId }),
196
+ ...(data.metadata?.subscriber && { subscriber: data.metadata.subscriber }),
197
+ ...(data.metadata?.subscriptionId && {
198
+ subscriptionId: data.metadata.subscriptionId,
199
+ }),
200
+ ...(data.metadata?.traceId && { traceId: data.metadata.traceId }),
201
+ ...(data.metadata?.responseQualityScore !== undefined && {
202
+ responseQualityScore: data.metadata.responseQualityScore,
203
+ }),
204
+ ...(environment && { environment }),
205
+ ...(region && { region }),
206
+ ...(credentialAlias && { credentialAlias }),
207
+ ...(traceType && { traceType }),
208
+ ...(traceName && { traceName }),
209
+ ...(parentTransactionId && { parentTransactionId }),
210
+ ...(transactionName && { transactionName }),
211
+ ...(retryNumber !== undefined && { retryNumber }),
212
+ ...(operationSubtype && { operationSubtype }),
213
+ ...customFields,
157
214
  };
158
215
  }
159
216
  /**
@@ -175,7 +232,6 @@ export function trackUsageAsync(trackingData) {
175
232
  return logger.warn("Revenium configuration not available - skipping tracking", {
176
233
  requestId: trackingData.requestId,
177
234
  });
178
- // Run tracking in background without awaiting
179
235
  sendReveniumMetrics(trackingData)
180
236
  .then(() => {
181
237
  logger.debug("Revenium tracking completed successfully", {
@@ -185,6 +241,12 @@ export function trackUsageAsync(trackingData) {
185
241
  });
186
242
  })
187
243
  .catch((error) => {
244
+ if (error instanceof Error && error.name === "AbortError") {
245
+ logger.debug("Metrics request aborted (process exiting)", {
246
+ requestId: trackingData.requestId,
247
+ });
248
+ return;
249
+ }
188
250
  const errorContext = createErrorContext()
189
251
  .withRequestId(trackingData.requestId)
190
252
  .withModel(trackingData.model)
@@ -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
  export {};
85
23
  //# sourceMappingURL=anthropic-augmentation.js.map
@@ -0,0 +1,154 @@
1
+ import { DEFAULT_CONFIG } from "../constants.js";
2
+ import { getConfig } from "../config.js";
3
+ function sanitizeCredentials(text) {
4
+ const patterns = [
5
+ {
6
+ regex: /sk-proj-[a-zA-Z0-9_-]{48,}/g,
7
+ replacement: "sk-proj-***REDACTED***",
8
+ },
9
+ { regex: /sk-[a-zA-Z0-9_-]{20,}/g, replacement: "sk-***REDACTED***" },
10
+ {
11
+ regex: /Bearer\s+[a-zA-Z0-9_\-\.]+/gi,
12
+ replacement: "Bearer ***REDACTED***",
13
+ },
14
+ {
15
+ regex: /api[_-]?key["\s:=]+[a-zA-Z0-9_\-\.\+\/=]{20,}/gi,
16
+ replacement: "api_key: ***REDACTED***",
17
+ },
18
+ {
19
+ regex: /token["\s:=]+[a-zA-Z0-9_\-\.]{20,}/gi,
20
+ replacement: "token: ***REDACTED***",
21
+ },
22
+ {
23
+ regex: /password["\s:=]+[^\s"']{8,}/gi,
24
+ replacement: "password: ***REDACTED***",
25
+ },
26
+ ];
27
+ let sanitized = text;
28
+ for (const pattern of patterns) {
29
+ sanitized = sanitized.replace(pattern.regex, pattern.replacement);
30
+ }
31
+ return sanitized;
32
+ }
33
+ function truncateString(str, maxLength) {
34
+ const sanitized = sanitizeCredentials(str);
35
+ if (sanitized.length <= maxLength) {
36
+ return { value: sanitized, truncated: false };
37
+ }
38
+ return { value: sanitized.substring(0, maxLength), truncated: true };
39
+ }
40
+ function extractSystemPrompt(params) {
41
+ if (!params.system) {
42
+ return "";
43
+ }
44
+ if (typeof params.system === "string") {
45
+ return params.system;
46
+ }
47
+ if (Array.isArray(params.system)) {
48
+ return params.system
49
+ .map((block) => {
50
+ if (block.type === "text") {
51
+ return block.text;
52
+ }
53
+ if (block.type === "image") {
54
+ return "[IMAGE]";
55
+ }
56
+ return "";
57
+ })
58
+ .filter(Boolean)
59
+ .join("\n");
60
+ }
61
+ return "";
62
+ }
63
+ function extractInputMessages(params) {
64
+ if (!params.messages || params.messages.length === 0) {
65
+ return "";
66
+ }
67
+ return params.messages
68
+ .map((message) => {
69
+ const role = message.role;
70
+ let content = "";
71
+ if (typeof message.content === "string") {
72
+ content = message.content;
73
+ }
74
+ else if (Array.isArray(message.content)) {
75
+ content = message.content
76
+ .map((block) => {
77
+ if (block.type === "text") {
78
+ return block.text;
79
+ }
80
+ if (block.type === "image") {
81
+ return "[IMAGE]";
82
+ }
83
+ if (block.type === "tool_use") {
84
+ const toolName = block.name || "unknown";
85
+ return `[TOOL_USE: ${toolName}]`;
86
+ }
87
+ if (block.type === "tool_result") {
88
+ return "[TOOL_RESULT]";
89
+ }
90
+ return "";
91
+ })
92
+ .filter(Boolean)
93
+ .join("\n");
94
+ }
95
+ return `[${role}]\n${content}`;
96
+ })
97
+ .join("\n\n");
98
+ }
99
+ function extractOutputResponse(response) {
100
+ if (!response.content || response.content.length === 0) {
101
+ return "";
102
+ }
103
+ return response.content
104
+ .map((block) => {
105
+ if (block.type === "text") {
106
+ return block.text;
107
+ }
108
+ if (block.type === "tool_use") {
109
+ return `[TOOL_USE: ${block.name}]`;
110
+ }
111
+ return "";
112
+ })
113
+ .filter(Boolean)
114
+ .join("\n");
115
+ }
116
+ export function shouldCapturePrompts(metadata) {
117
+ if (metadata?.capturePrompts !== undefined) {
118
+ return metadata.capturePrompts;
119
+ }
120
+ const config = getConfig();
121
+ if (config?.capturePrompts !== undefined) {
122
+ return config.capturePrompts;
123
+ }
124
+ return DEFAULT_CONFIG.CAPTURE_PROMPTS;
125
+ }
126
+ export function extractPrompts(params, response, metadata) {
127
+ if (!shouldCapturePrompts(metadata)) {
128
+ return null;
129
+ }
130
+ const maxSize = DEFAULT_CONFIG.MAX_PROMPT_SIZE;
131
+ let anyTruncated = false;
132
+ const systemPromptRaw = extractSystemPrompt(params);
133
+ const systemPromptResult = truncateString(systemPromptRaw, maxSize);
134
+ anyTruncated = anyTruncated || systemPromptResult.truncated;
135
+ const inputMessagesRaw = extractInputMessages(params);
136
+ const inputMessagesResult = truncateString(inputMessagesRaw, maxSize);
137
+ anyTruncated = anyTruncated || inputMessagesResult.truncated;
138
+ const outputResponseRaw = extractOutputResponse(response);
139
+ const outputResponseResult = truncateString(outputResponseRaw, maxSize);
140
+ anyTruncated = anyTruncated || outputResponseResult.truncated;
141
+ const hasAnyContent = systemPromptResult.value ||
142
+ inputMessagesResult.value ||
143
+ outputResponseResult.value;
144
+ if (!hasAnyContent) {
145
+ return null;
146
+ }
147
+ return {
148
+ systemPrompt: systemPromptResult.value || undefined,
149
+ inputMessages: inputMessagesResult.value || undefined,
150
+ outputResponse: outputResponseResult.value || undefined,
151
+ promptsTruncated: anyTruncated,
152
+ };
153
+ }
154
+ //# sourceMappingURL=prompt-extraction.js.map
@@ -0,0 +1,186 @@
1
+ import { getConfig, getLogger } from "../config.js";
2
+ import { DEFAULT_CONFIG, SUMMARY_PRINTER_CONFIG } from "../constants.js";
3
+ const logger = getLogger();
4
+ function delayWithUnref(ms) {
5
+ return new Promise((resolve) => {
6
+ const timer = setTimeout(resolve, ms);
7
+ if (typeof timer.unref === "function") {
8
+ timer.unref();
9
+ }
10
+ });
11
+ }
12
+ async function fetchCompletionMetrics(transactionId, maxRetries = SUMMARY_PRINTER_CONFIG.MAX_RETRIES, retryDelay = SUMMARY_PRINTER_CONFIG.RETRY_DELAY) {
13
+ const config = getConfig();
14
+ if (!config) {
15
+ logger.debug("No config available for summary printing");
16
+ return null;
17
+ }
18
+ if (!config.teamId) {
19
+ logger.debug("Team ID not configured, skipping cost retrieval for summary");
20
+ return null;
21
+ }
22
+ const baseUrl = (config.reveniumBaseUrl || DEFAULT_CONFIG.REVENIUM_BASE_URL).replace(/\/+$/, "");
23
+ const url = `${baseUrl}/profitstream/v2/api/sources/metrics/ai/completions`;
24
+ const teamId = config.teamId.trim();
25
+ const urlWithParams = `${url}?teamId=${encodeURIComponent(teamId)}&transactionId=${encodeURIComponent(transactionId)}`;
26
+ logger.debug("Fetching completion metrics", { url: urlWithParams });
27
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
28
+ // Create an AbortController with timeout to prevent hung requests from keeping Node process alive
29
+ const controller = new AbortController();
30
+ const timeoutId = setTimeout(() => controller.abort(), SUMMARY_PRINTER_CONFIG.FETCH_TIMEOUT);
31
+ // Unref the timer so it doesn't keep the process alive
32
+ if (typeof timeoutId.unref === "function") {
33
+ timeoutId.unref();
34
+ }
35
+ try {
36
+ const response = await fetch(urlWithParams, {
37
+ method: "GET",
38
+ headers: {
39
+ Accept: "application/json",
40
+ "x-api-key": config.reveniumApiKey,
41
+ },
42
+ signal: controller.signal,
43
+ });
44
+ if (!response.ok) {
45
+ try {
46
+ await response.text();
47
+ }
48
+ catch { }
49
+ logger.debug(`Completions metrics API returned ${response.status}`, {
50
+ attempt: attempt + 1,
51
+ });
52
+ if (attempt < maxRetries - 1) {
53
+ await delayWithUnref(retryDelay);
54
+ continue;
55
+ }
56
+ return null;
57
+ }
58
+ const data = (await response.json());
59
+ const completions = data._embedded?.aICompletionMetricResourceList;
60
+ if (completions && completions.length > 0) {
61
+ return completions[0];
62
+ }
63
+ if (attempt < maxRetries - 1) {
64
+ logger.debug(`Waiting for metrics to aggregate (attempt ${attempt + 1}/${maxRetries})...`);
65
+ await delayWithUnref(retryDelay);
66
+ }
67
+ }
68
+ catch (error) {
69
+ logger.debug("Failed to fetch completion metrics", {
70
+ error: error instanceof Error ? error.message : String(error),
71
+ attempt: attempt + 1,
72
+ });
73
+ if (attempt < maxRetries - 1) {
74
+ await delayWithUnref(retryDelay);
75
+ }
76
+ }
77
+ finally {
78
+ clearTimeout(timeoutId);
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+ function isSummaryFormat(value) {
84
+ return value === "human" || value === "json";
85
+ }
86
+ function formatAndPrintJsonSummary(payload, metrics) {
87
+ const config = getConfig();
88
+ const summary = {
89
+ model: payload.model,
90
+ provider: payload.provider,
91
+ durationSeconds: payload.requestDuration / 1000,
92
+ inputTokenCount: payload.inputTokenCount,
93
+ outputTokenCount: payload.outputTokenCount,
94
+ totalTokenCount: payload.totalTokenCount,
95
+ cost: typeof metrics?.totalCost === "number" ? metrics.totalCost : null,
96
+ };
97
+ if (summary.cost === null) {
98
+ summary.costStatus = config?.teamId ? "pending" : "unavailable";
99
+ }
100
+ if (payload.traceId) {
101
+ summary.traceId = payload.traceId;
102
+ }
103
+ console.log(JSON.stringify(summary));
104
+ }
105
+ function formatAndPrintHumanSummary(payload, metrics) {
106
+ console.log("\n" + "=".repeat(60));
107
+ console.log("šŸ“Š REVENIUM USAGE SUMMARY");
108
+ console.log("=".repeat(60));
109
+ console.log(`šŸ¤– Model: ${payload.model}`);
110
+ console.log(`šŸ¢ Provider: ${payload.provider}`);
111
+ console.log(`ā±ļø Duration: ${(payload.requestDuration / 1000).toFixed(2)}s`);
112
+ console.log("\nšŸ’¬ Token Usage:");
113
+ console.log(` šŸ“„ Input Tokens: ${(payload.inputTokenCount ?? 0).toLocaleString()}`);
114
+ console.log(` šŸ“¤ Output Tokens: ${(payload.outputTokenCount ?? 0).toLocaleString()}`);
115
+ console.log(` šŸ“Š Total Tokens: ${(payload.totalTokenCount ?? 0).toLocaleString()}`);
116
+ if (typeof metrics?.totalCost === "number") {
117
+ console.log(`\nšŸ’° Cost: $${metrics.totalCost.toFixed(6)}`);
118
+ }
119
+ else {
120
+ const config = getConfig();
121
+ if (!config?.teamId) {
122
+ console.log(`\nšŸ’° Cost: Set REVENIUM_TEAM_ID environment variable to see pricing`);
123
+ }
124
+ else {
125
+ console.log(`\nšŸ’° Cost: (pending aggregation)`);
126
+ }
127
+ }
128
+ if (payload.traceId) {
129
+ console.log(`\nšŸ”– Trace ID: ${payload.traceId}`);
130
+ }
131
+ console.log("=".repeat(60) + "\n");
132
+ }
133
+ function formatAndPrintSummary(payload, metrics, format) {
134
+ if (format === "json") {
135
+ formatAndPrintJsonSummary(payload, metrics);
136
+ }
137
+ else {
138
+ formatAndPrintHumanSummary(payload, metrics);
139
+ }
140
+ }
141
+ function safeFormatAndPrintSummary(payload, metrics, format) {
142
+ try {
143
+ formatAndPrintSummary(payload, metrics, format);
144
+ }
145
+ catch (error) {
146
+ logger.debug("Failed to format and print summary", {
147
+ error: error instanceof Error ? error.message : String(error),
148
+ });
149
+ }
150
+ }
151
+ function getSummaryFormat(value) {
152
+ if (!value)
153
+ return null;
154
+ if (value === true)
155
+ return "human";
156
+ if (isSummaryFormat(value)) {
157
+ return value;
158
+ }
159
+ return null;
160
+ }
161
+ export function printUsageSummary(payload) {
162
+ const config = getConfig();
163
+ const format = getSummaryFormat(config?.printSummary);
164
+ if (!format) {
165
+ return;
166
+ }
167
+ if (config?.teamId && payload.transactionId) {
168
+ fetchCompletionMetrics(payload.transactionId)
169
+ .then((metrics) => {
170
+ safeFormatAndPrintSummary(payload, metrics, format);
171
+ })
172
+ .catch((error) => {
173
+ logger.debug("Failed to print usage summary with metrics", {
174
+ error: error instanceof Error ? error.message : String(error),
175
+ });
176
+ safeFormatAndPrintSummary(payload, null, format);
177
+ })
178
+ .catch(() => {
179
+ // Final safety catch to prevent unhandled rejections
180
+ });
181
+ }
182
+ else {
183
+ safeFormatAndPrintSummary(payload, null, format);
184
+ }
185
+ }
186
+ //# sourceMappingURL=summary-printer.js.map