From 9fbd6e619dec4a5f5635eb619a01d57c51827141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thi=C3=AAn=20To=C3=A1n?= Date: Fri, 20 Feb 2026 14:24:21 +0700 Subject: [PATCH] fix: correct token extraction for Claude non-streaming responses (#131) - Add response logging for non-streaming requests (5_res_provider.json, 7_res_client.json) - Fix extractUsageFromResponse() to check Claude format before OpenAI format - Prevents format misidentification that caused tokens to show as 0 - Claude uses input_tokens/output_tokens vs OpenAI's prompt_tokens/completion_tokens Fixes dashboard Details tab showing 0 tokens for Claude requests --- open-sse/handlers/chatCore.js | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/open-sse/handlers/chatCore.js b/open-sse/handlers/chatCore.js index cd774630..dee1c18b 100644 --- a/open-sse/handlers/chatCore.js +++ b/open-sse/handlers/chatCore.js @@ -194,18 +194,8 @@ function translateNonStreamingResponse(responseBody, targetFormat, sourceFormat) function extractUsageFromResponse(responseBody, provider) { if (!responseBody || typeof responseBody !== 'object') return null; - // OpenAI format - if (responseBody.usage && typeof responseBody.usage === 'object') { - return { - prompt_tokens: responseBody.usage.prompt_tokens || 0, - completion_tokens: responseBody.usage.completion_tokens || 0, - cached_tokens: responseBody.usage.prompt_tokens_details?.cached_tokens, - reasoning_tokens: responseBody.usage.completion_tokens_details?.reasoning_tokens - }; - } - - // Claude format - if (responseBody.usage && typeof responseBody.usage === 'object' && (responseBody.usage.input_tokens !== undefined || responseBody.usage.output_tokens !== undefined)) { + // Claude format - check first to avoid conflict with OpenAI check + if (responseBody.usage && typeof responseBody.usage === 'object' && responseBody.usage.input_tokens !== undefined) { return { prompt_tokens: responseBody.usage.input_tokens || 0, completion_tokens: responseBody.usage.output_tokens || 0, @@ -214,6 +204,16 @@ function extractUsageFromResponse(responseBody, provider) { }; } + // OpenAI format + if (responseBody.usage && typeof responseBody.usage === 'object' && responseBody.usage.prompt_tokens !== undefined) { + return { + prompt_tokens: responseBody.usage.prompt_tokens || 0, + completion_tokens: responseBody.usage.completion_tokens || 0, + cached_tokens: responseBody.usage.prompt_tokens_details?.cached_tokens, + reasoning_tokens: responseBody.usage.completion_tokens_details?.reasoning_tokens + }; + } + // Gemini format if (responseBody.usageMetadata && typeof responseBody.usageMetadata === 'object') { return { @@ -723,6 +723,14 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred } } + // Log provider response (raw response from provider) + reqLogger.logProviderResponse( + providerResponse.status, + providerResponse.statusText, + providerResponse.headers, + responseBody + ); + // Notify success - caller can clear error status if needed if (onRequestSuccess) { await onRequestSuccess(); @@ -758,6 +766,9 @@ export async function handleChatCore({ body, modelInfo, credentials, log, onCred translatedResponse.usage = filterUsageForFormat(buffered, sourceFormat); } + // Log converted response (final response to client) + reqLogger.logConvertedResponse(translatedResponse); + const totalLatency = Date.now() - requestStartTime; const requestDetail = { provider: provider || "unknown",