fix(codex): restore cached tool call fields (#4160)

* fix(codex): restore cached tool call fields

* refactor(codex): merge duplicate enrich loops in chat history

enrich_call_item_from_cache copied the fill-if-empty loop for
reasoning_content/reasoning. The two loops are identical and key
order is irrelevant, so fold both key sets into a single loop.

Pure refactor, no behavior change; codex_chat_history tests pass.

---------

Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
chen-985211
2026-06-16 10:59:16 +08:00
committed by GitHub
Unverified
parent 3e38889ccc
commit 36b557b2e6
2 changed files with 57 additions and 6 deletions
+1 -1
View File
@@ -1350,7 +1350,7 @@ impl RequestForwarder {
.await;
if restored > 0 {
log::debug!(
"[Codex] Restored {restored} cached function call(s) for Chat upstream"
"[Codex] Restored or enriched {restored} cached function call item(s) for Chat upstream"
);
}
super::providers::apply_codex_chat_upstream_model(provider, &mut mapped_body);
@@ -150,7 +150,7 @@ impl CodexChatHistoryStore {
Some(item_type) if is_call_item_type(item_type) => {
if let Some(call_id) = response_item_call_id(&item) {
if let Some(cached) = lookup.call(&call_id) {
if enrich_call_item_reasoning(&mut item, cached) {
if enrich_call_item_from_cache(&mut item, cached) {
enriched += 1;
}
}
@@ -466,17 +466,26 @@ fn is_call_output_item_type(item_type: &str) -> bool {
)
}
fn enrich_call_item_reasoning(item: &mut Value, cached: &Value) -> bool {
fn enrich_call_item_from_cache(item: &mut Value, cached: &Value) -> bool {
let mut changed = false;
for key in ["reasoning_content", "reasoning"] {
for key in [
"name",
"namespace",
"arguments",
"input",
"status",
"execution",
"reasoning_content",
"reasoning",
] {
if item.get(key).is_some_and(|value| !is_empty_value(value)) {
continue;
}
let Some(reasoning) = cached.get(key).filter(|value| !is_empty_value(value)) else {
let Some(value) = cached.get(key).filter(|value| !is_empty_value(value)) else {
continue;
};
if let Some(object) = item.as_object_mut() {
object.insert(key.to_string(), reasoning.clone());
object.insert(key.to_string(), value.clone());
changed = true;
}
}
@@ -675,6 +684,48 @@ mod tests {
assert_eq!(input.len(), 2);
}
#[tokio::test]
async fn enriches_existing_function_call_missing_name_and_arguments() {
let history = CodexChatHistoryStore::default();
history
.record_response(&json!({
"id": "resp_1",
"output": [
{
"type": "function_call",
"call_id": "call_1",
"name": "read_file",
"arguments": "{\"path\":\"README.md\"}",
"reasoning_content": "Need to inspect the file."
}
]
}))
.await;
let mut request = json!({
"previous_response_id": "resp_1",
"input": [
{
"type": "function_call",
"call_id": "call_1"
},
{
"type": "function_call_output",
"call_id": "call_1",
"output": "ok"
}
]
});
assert_eq!(history.enrich_request(&mut request).await, 1);
let input = request["input"].as_array().unwrap();
assert_eq!(input[0]["type"], "function_call");
assert_eq!(input[0]["name"], "read_file");
assert_eq!(input[0]["arguments"], "{\"path\":\"README.md\"}");
assert_eq!(input[0]["reasoning_content"], "Need to inspect the file.");
assert_eq!(input[1]["type"], "function_call_output");
}
#[tokio::test]
async fn restores_parallel_tool_calls_as_one_assistant_group() {
let history = CodexChatHistoryStore::default();