From b4f262c7bdaaffc04cddb25a374f4067b02612ae Mon Sep 17 00:00:00 2001 From: Yeeyzy Date: Wed, 3 Jun 2026 14:10:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(codex):=20always=20include=20output=5Ftoken?= =?UTF-8?q?s=5Fdetails.reasoning=5Ftokens=20in=20=E2=80=A6=20(#3514)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(codex): always include output_tokens_details.reasoning_tokens in chat→responses transform Codex CLI strictly requires reasoning_tokens in the response.completed usage object. Custom providers using /chat/completions often omit completion_tokens_details, causing repeated parse failures and retries. * fix(codex): guard non-object completion_tokens_details before insertion --------- Co-authored-by: yeeyzy --- .../src/proxy/providers/streaming_codex_chat.rs | 3 ++- .../src/proxy/providers/transform_codex_chat.rs | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src-tauri/src/proxy/providers/streaming_codex_chat.rs b/src-tauri/src/proxy/providers/streaming_codex_chat.rs index 1146d095d..318fe36a9 100644 --- a/src-tauri/src/proxy/providers/streaming_codex_chat.rs +++ b/src-tauri/src/proxy/providers/streaming_codex_chat.rs @@ -801,7 +801,8 @@ impl ChatToResponsesState { json!({ "input_tokens": 0, "output_tokens": 0, - "total_tokens": 0 + "total_tokens": 0, + "output_tokens_details": { "reasoning_tokens": 0 } }) }) }) diff --git a/src-tauri/src/proxy/providers/transform_codex_chat.rs b/src-tauri/src/proxy/providers/transform_codex_chat.rs index a3568dce5..8720753c6 100644 --- a/src-tauri/src/proxy/providers/transform_codex_chat.rs +++ b/src-tauri/src/proxy/providers/transform_codex_chat.rs @@ -1507,7 +1507,8 @@ pub(crate) fn chat_usage_to_responses_usage(usage: Option<&Value>) -> Value { return json!({ "input_tokens": 0, "output_tokens": 0, - "total_tokens": 0 + "total_tokens": 0, + "output_tokens_details": { "reasoning_tokens": 0 } }); }; @@ -1540,8 +1541,17 @@ pub(crate) fn chat_usage_to_responses_usage(usage: Option<&Value>) -> Value { result["input_tokens_details"] = json!({ "cached_tokens": cached }); } - if let Some(details) = usage.get("completion_tokens_details") { - result["output_tokens_details"] = details.clone(); + if let Some(details) = usage + .get("completion_tokens_details") + .filter(|v| v.is_object()) + { + let mut details = details.clone(); + if details.get("reasoning_tokens").is_none() { + details["reasoning_tokens"] = json!(0); + } + result["output_tokens_details"] = details; + } else { + result["output_tokens_details"] = json!({ "reasoning_tokens": 0 }); } if let Some(cache_read) = usage.get("cache_read_input_tokens") {