diff --git a/open-sse/handlers/chatCore/requestDetail.js b/open-sse/handlers/chatCore/requestDetail.js index d9dde1a3..0c767c75 100644 --- a/open-sse/handlers/chatCore/requestDetail.js +++ b/open-sse/handlers/chatCore/requestDetail.js @@ -80,14 +80,29 @@ export function saveUsageStats({ provider, model, tokens, connectionId, apiKey, if (inTokens === 0 && outTokens === 0) return; + // Extract cache/reasoning tokens (unified from different formats) + const cacheRead = tokens.cache_read_input_tokens || tokens.cached_tokens || tokens.prompt_tokens_details?.cached_tokens || 0; + const cacheCreation = tokens.cache_creation_input_tokens || 0; + const reasoning = tokens.reasoning_tokens || 0; + const time = new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" }); const accountSuffix = connectionId ? ` | account=${connectionId.slice(0, 8)}...` : ""; - console.log(`${COLORS.green}[${time}] 📊 [${label}] ${provider.toUpperCase()} | in=${inTokens} | out=${outTokens}${accountSuffix}${COLORS.reset}`); + + let msg = `${COLORS.green}[${time}] 📊 [${label}] ${provider?.toUpperCase() || "UNKNOWN"} | in=${inTokens} | out=${outTokens}${accountSuffix}`; + if (tokens.estimated) msg += ` ${COLORS.yellow}(estimated)${COLORS.reset}`; + if (cacheRead) msg += ` | cache_read=${cacheRead}`; + if (cacheCreation) msg += ` | cache_create=${cacheCreation}`; + if (reasoning) msg += ` | reasoning=${reasoning}`; + msg += `${COLORS.reset}`; + console.log(msg); - // Normalize to OpenAI token shape for storage + // Normalize to OpenAI token shape for storage (include all token types) const normalized = { prompt_tokens: tokens.prompt_tokens ?? tokens.input_tokens ?? 0, - completion_tokens: tokens.completion_tokens ?? tokens.output_tokens ?? 0 + completion_tokens: tokens.completion_tokens ?? tokens.output_tokens ?? 0, + cache_read_input_tokens: cacheRead, + cache_creation_input_tokens: cacheCreation, + reasoning_tokens: reasoning, }; saveRequestUsage({ diff --git a/open-sse/handlers/chatCore/streamingHandler.js b/open-sse/handlers/chatCore/streamingHandler.js index f96bb9bd..757bb5b4 100644 --- a/open-sse/handlers/chatCore/streamingHandler.js +++ b/open-sse/handlers/chatCore/streamingHandler.js @@ -5,7 +5,7 @@ import { pipeWithDisconnect } from "../../utils/streamHandler.js"; import { PROVIDERS } from "../../config/providers.js"; import { STREAM_STALL_TIMEOUT_MS } from "../../config/runtimeConfig.js"; import { buildAbortedResponsesTerminalBytes } from "../../utils/responsesStreamHelpers.js"; -import { buildRequestDetail, extractRequestConfig, saveUsageStats } from "./requestDetail.js"; +import { buildRequestDetail, extractRequestConfig } from "./requestDetail.js"; import { saveRequestDetail } from "@/lib/usageDb.js"; const SSE_HEADERS = { @@ -99,8 +99,6 @@ export function buildOnStreamComplete({ provider, model, connectionId, apiKey, r }, { id: streamDetailId })).catch(err => { console.error("[RequestDetail] Failed to update streaming content:", err.message); }); - - saveUsageStats({ provider, model, tokens: usage, connectionId, apiKey, endpoint: clientRawRequest?.endpoint, label: "STREAM USAGE" }); }; return { onStreamComplete, streamDetailId }; diff --git a/open-sse/utils/stream.js b/open-sse/utils/stream.js index 4886efe0..b8d5d819 100644 --- a/open-sse/utils/stream.js +++ b/open-sse/utils/stream.js @@ -1,7 +1,8 @@ import { translateResponse, initState } from "../translator/index.js"; import { FORMATS } from "../translator/formats.js"; import { trackPendingRequest, appendRequestLog } from "@/lib/usageDb.js"; -import { extractUsage, hasValidUsage, estimateUsage, logUsage, addBufferToUsage, filterUsageForFormat, COLORS } from "./usageTracking.js"; +import { extractUsage, hasValidUsage, estimateUsage, addBufferToUsage, filterUsageForFormat, COLORS } from "./usageTracking.js"; +import { saveUsageStats } from "../handlers/chatCore/requestDetail.js"; import { parseSSELine, hasValuableContent, fixInvalidId, formatSSE } from "./streamHelpers.js"; import { getOpenAIResponsesEventName, isOpenAIResponsesTerminalEvent, formatIncompleteOpenAIResponsesStreamFailure } from "./responsesStreamHelpers.js"; import { dbg, isDebugEnabled } from "./debugLog.js"; @@ -331,7 +332,7 @@ export function createSSEStream(options = {}) { } if (hasValidUsage(usage)) { - logUsage(provider, usage, model, connectionId, apiKey); + saveUsageStats({ provider, model, tokens: usage, connectionId, apiKey }); } else { appendRequestLog({ model, provider, connectionId, tokens: null, status: "200 OK" }).catch(() => { }); } @@ -414,7 +415,7 @@ export function createSSEStream(options = {}) { } if (hasValidUsage(state?.usage)) { - logUsage(state.provider || targetFormat, state.usage, model, connectionId, apiKey); + saveUsageStats({ provider: state.provider || targetFormat, model, tokens: state.usage, connectionId, apiKey }); } else { appendRequestLog({ model, provider, connectionId, tokens: null, status: "200 OK" }).catch(() => { }); } diff --git a/open-sse/utils/usageTracking.js b/open-sse/utils/usageTracking.js index aed41118..8055bc14 100644 --- a/open-sse/utils/usageTracking.js +++ b/open-sse/utils/usageTracking.js @@ -2,7 +2,6 @@ * Token Usage Tracking - Extract, normalize, estimate and log token usage */ -import { saveRequestUsage, appendRequestLog } from "@/lib/usageDb.js"; import { FORMATS } from "../translator/formats.js"; // ANSI color codes @@ -299,49 +298,3 @@ export function estimateUsage(body, contentLength, targetFormat = FORMATS.OPENAI targetFormat ); } - -/** - * Log usage with cache info (green color) - */ -export function logUsage(provider, usage, model = null, connectionId = null, apiKey = null) { - if (!usage || typeof usage !== "object") return; - - const p = provider?.toUpperCase() || "UNKNOWN"; - - // Support both formats: - // - OpenAI: prompt_tokens, completion_tokens - // - Claude: input_tokens, output_tokens - const inTokens = usage?.prompt_tokens || usage?.input_tokens || 0; - const outTokens = usage?.completion_tokens || usage?.output_tokens || 0; - const accountPrefix = connectionId ? connectionId.slice(0, 8) + "..." : "unknown"; - - let msg = `[${getTimeString()}] 📊 ${COLORS.green}[USAGE] ${p} | in=${inTokens} | out=${outTokens} | account=${accountPrefix}${COLORS.reset}`; - - // Add estimated flag if present - if (usage.estimated) { - msg += ` ${COLORS.yellow}(estimated)${COLORS.reset}`; - } - - // Add cache info if present (unified from different formats) - const cacheRead = usage.cache_read_input_tokens || usage.cached_tokens || usage.prompt_tokens_details?.cached_tokens; - if (cacheRead) msg += ` | cache_read=${cacheRead}`; - - const cacheCreation = usage.cache_creation_input_tokens; - if (cacheCreation) msg += ` | cache_create=${cacheCreation}`; - - const reasoning = usage.reasoning_tokens; - if (reasoning) msg += ` | reasoning=${reasoning}`; - - console.log(msg); - - // Save to usage DB - const tokens = { - prompt_tokens: inTokens, - completion_tokens: outTokens, - cache_read_input_tokens: cacheRead || 0, - cache_creation_input_tokens: cacheCreation || 0, - reasoning_tokens: reasoning || 0 - }; - saveRequestUsage({ model, provider, connectionId, tokens, apiKey: apiKey || undefined }).catch(() => { }); - appendRequestLog({ model, provider, connectionId, tokens, status: "200 OK" }).catch(() => { }); -}