mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
codex: reuse history normalization for tool search (#30618)
This commit is contained in:
@@ -5,3 +5,4 @@ pub(crate) mod updates;
|
||||
pub(crate) use history::ContextManager;
|
||||
pub(crate) use history::is_user_turn_boundary;
|
||||
pub(crate) use history::truncate_function_output_payload;
|
||||
pub(crate) use normalize::canonicalize_for_durable_history;
|
||||
|
||||
@@ -1,19 +1,113 @@
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::models::SearchToolCallParams;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashSet;
|
||||
use tracing::info;
|
||||
use tracing::warn;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::util::error_or_panic;
|
||||
use tracing::info;
|
||||
|
||||
const IMAGE_CONTENT_OMITTED_PLACEHOLDER: &str =
|
||||
"image content omitted because you do not support image input";
|
||||
const INVALID_TOOL_SEARCH_QUERY: &str = "[invalid tool_search arguments omitted]";
|
||||
// Changing this value would change model-visible IDs and invalidate prompt caches.
|
||||
const SYNTHETIC_OUTPUT_ID_NAMESPACE: Uuid = Uuid::from_u128(0x90d38d3e_6a5b_4d52_bfe2_2f1e634bfac4);
|
||||
|
||||
/// Returns a canonical durable-history view when an item must differ from its raw event.
|
||||
///
|
||||
/// Client-executed tool search is the only case handled here because its arguments have a fixed
|
||||
/// schema that can be safely deserialized and reserialized. Function-call arguments and custom
|
||||
/// tool input are intentionally preserved as schema-specific or freeform strings.
|
||||
pub(crate) fn canonicalize_for_durable_history(
|
||||
thread_id: &ThreadId,
|
||||
items: &[ResponseItem],
|
||||
) -> Option<Vec<ResponseItem>> {
|
||||
if !items.iter().any(|item| {
|
||||
matches!(
|
||||
item,
|
||||
ResponseItem::ToolSearchCall { execution, .. } if execution == "client"
|
||||
)
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
let ResponseItem::ToolSearchCall {
|
||||
id,
|
||||
call_id,
|
||||
status,
|
||||
execution,
|
||||
arguments,
|
||||
internal_chat_message_metadata_passthrough,
|
||||
} = item
|
||||
else {
|
||||
return Some(item.clone());
|
||||
};
|
||||
if execution != "client" {
|
||||
return Some(item.clone());
|
||||
}
|
||||
|
||||
let Some(call_id) = call_id.as_deref().filter(|call_id| !call_id.is_empty()) else {
|
||||
warn!(
|
||||
%thread_id,
|
||||
"dropping client tool_search call with missing call_id from durable history"
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
let canonical_arguments = match SearchToolCallParams::deserialize(arguments) {
|
||||
Ok(params) => match serde_json::to_value(params) {
|
||||
Ok(canonical_arguments) => canonical_arguments,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
%thread_id,
|
||||
call_id,
|
||||
%err,
|
||||
"failed to serialize canonical client tool_search arguments"
|
||||
);
|
||||
invalid_tool_search_arguments()
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
%thread_id,
|
||||
call_id,
|
||||
error_category = ?err.classify(),
|
||||
error_line = err.line(),
|
||||
error_column = err.column(),
|
||||
"replacing malformed client tool_search arguments in durable history"
|
||||
);
|
||||
invalid_tool_search_arguments()
|
||||
}
|
||||
};
|
||||
|
||||
Some(ResponseItem::ToolSearchCall {
|
||||
id: id.clone(),
|
||||
call_id: Some(call_id.to_string()),
|
||||
status: status.clone(),
|
||||
execution: execution.clone(),
|
||||
arguments: canonical_arguments,
|
||||
internal_chat_message_metadata_passthrough:
|
||||
internal_chat_message_metadata_passthrough.clone(),
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn invalid_tool_search_arguments() -> serde_json::Value {
|
||||
serde_json::json!({ "query": INVALID_TOOL_SEARCH_QUERY })
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_call_outputs_present(items: &mut Vec<ResponseItem>) {
|
||||
let mut function_output_ids = HashSet::new();
|
||||
let mut tool_search_output_ids = HashSet::new();
|
||||
|
||||
@@ -208,7 +208,6 @@ mod input_queue;
|
||||
mod mcp;
|
||||
mod mcp_runtime;
|
||||
pub(crate) mod multi_agents;
|
||||
mod response_item_history;
|
||||
mod review;
|
||||
mod rollout_budget;
|
||||
mod rollout_reconstruction;
|
||||
@@ -2727,7 +2726,7 @@ impl Session {
|
||||
} else {
|
||||
items
|
||||
};
|
||||
let history_items = response_item_history::canonicalize_for_durable_history(
|
||||
let history_items = crate::context_manager::canonicalize_for_durable_history(
|
||||
&self.thread_id,
|
||||
raw_response_items.as_ref(),
|
||||
);
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
use codex_protocol::ThreadId;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::models::SearchToolCallParams;
|
||||
use serde::Deserialize;
|
||||
use tracing::warn;
|
||||
|
||||
const INVALID_TOOL_SEARCH_QUERY: &str = "[invalid tool_search arguments omitted]";
|
||||
|
||||
/// Returns a canonical durable-history view when an item must differ from its raw event.
|
||||
///
|
||||
/// Client-executed tool search is the only case handled here because its arguments have a fixed
|
||||
/// schema that can be safely deserialized and reserialized. Function-call arguments and custom
|
||||
/// tool input are intentionally preserved as schema-specific or freeform strings.
|
||||
pub(super) fn canonicalize_for_durable_history(
|
||||
thread_id: &ThreadId,
|
||||
items: &[ResponseItem],
|
||||
) -> Option<Vec<ResponseItem>> {
|
||||
if !items.iter().any(|item| {
|
||||
matches!(
|
||||
item,
|
||||
ResponseItem::ToolSearchCall { execution, .. } if execution == "client"
|
||||
)
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(
|
||||
items
|
||||
.iter()
|
||||
.filter_map(|item| {
|
||||
let ResponseItem::ToolSearchCall {
|
||||
id,
|
||||
call_id,
|
||||
status,
|
||||
execution,
|
||||
arguments,
|
||||
internal_chat_message_metadata_passthrough,
|
||||
} = item
|
||||
else {
|
||||
return Some(item.clone());
|
||||
};
|
||||
if execution != "client" {
|
||||
return Some(item.clone());
|
||||
}
|
||||
|
||||
let Some(call_id) = call_id.as_deref().filter(|call_id| !call_id.is_empty()) else {
|
||||
warn!(
|
||||
%thread_id,
|
||||
"dropping client tool_search call with missing call_id from durable history"
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
||||
let canonical_arguments = match SearchToolCallParams::deserialize(arguments) {
|
||||
Ok(params) => match serde_json::to_value(params) {
|
||||
Ok(canonical_arguments) => canonical_arguments,
|
||||
Err(err) => {
|
||||
warn!(
|
||||
%thread_id,
|
||||
call_id,
|
||||
%err,
|
||||
"failed to serialize canonical client tool_search arguments"
|
||||
);
|
||||
invalid_tool_search_arguments()
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
%thread_id,
|
||||
call_id,
|
||||
error_category = ?err.classify(),
|
||||
error_line = err.line(),
|
||||
error_column = err.column(),
|
||||
"replacing malformed client tool_search arguments in durable history"
|
||||
);
|
||||
invalid_tool_search_arguments()
|
||||
}
|
||||
};
|
||||
|
||||
Some(ResponseItem::ToolSearchCall {
|
||||
id: id.clone(),
|
||||
call_id: Some(call_id.to_string()),
|
||||
status: status.clone(),
|
||||
execution: execution.clone(),
|
||||
arguments: canonical_arguments,
|
||||
internal_chat_message_metadata_passthrough:
|
||||
internal_chat_message_metadata_passthrough.clone(),
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn invalid_tool_search_arguments() -> serde_json::Value {
|
||||
serde_json::json!({ "query": INVALID_TOOL_SEARCH_QUERY })
|
||||
}
|
||||
Reference in New Issue
Block a user