Problem
Cora fails to parse LLM responses from GPT-5.4 (and potentially other newer models) with:
failed to parse LLM JSON response: duplicate field `prompt_tokens` at line 1 column 2993
Root Cause
GPT-5.4 returns both legacy and new field names in the usage object simultaneously:
"usage": {
"prompt_tokens": 2615,
"completion_tokens": 581,
"total_tokens": 3196,
"input_tokens": 2615,
"output_tokens": 581
}
Cora's Usage struct (src/engine/llm.rs:92-99) uses serde aliases to accept both naming conventions:
#[serde(default, alias = "promptTokens", alias = "input_tokens")]
prompt_tokens: u32,
serde_json >= 1.0.120 tracks visited fields during struct deserialization. When input_tokens (alias) maps to prompt_tokens (already set from primary key), it triggers duplicate field error.
Previous models (GPT-4, Claude, etc.) only send one format — GPT-5.4 sends both, triggering the guard.
Fix
Option B: Custom deserialize via serde_json::Value intermediate
Parse the usage field as serde_json::Value first on the ChatResponse struct, then deduplicate field names and convert to the typed Usage struct in post-processing. This handles all three cases:
- Legacy only (
prompt_tokens) — works as before
- New only (
input_tokens) — alias resolves correctly
- Both (GPT-5.4) — deduplicate before typed conversion
Affected code
src/engine/llm.rs — Usage struct, ChatResponse struct, chat_completion() function, stream usage extraction
Severity
Medium — blocks all reviews using GPT-5.4 and potentially other providers that send dual usage fields. Workaround: pin to models that only send one format.
Problem
Cora fails to parse LLM responses from GPT-5.4 (and potentially other newer models) with:
Root Cause
GPT-5.4 returns both legacy and new field names in the
usageobject simultaneously:Cora's
Usagestruct (src/engine/llm.rs:92-99) uses serde aliases to accept both naming conventions:serde_json>= 1.0.120 tracks visited fields during struct deserialization. Wheninput_tokens(alias) maps toprompt_tokens(already set from primary key), it triggersduplicate fielderror.Previous models (GPT-4, Claude, etc.) only send one format — GPT-5.4 sends both, triggering the guard.
Fix
Option B: Custom deserialize via serde_json::Value intermediate
Parse the
usagefield asserde_json::Valuefirst on theChatResponsestruct, then deduplicate field names and convert to the typedUsagestruct in post-processing. This handles all three cases:prompt_tokens) — works as beforeinput_tokens) — alias resolves correctlyAffected code
src/engine/llm.rs—Usagestruct,ChatResponsestruct,chat_completion()function, stream usage extractionSeverity
Medium — blocks all reviews using GPT-5.4 and potentially other providers that send dual usage fields. Workaround: pin to models that only send one format.