| use std::collections::HashSet; |
|
|
| use schemars::JsonSchema; |
| use serde::Deserialize; |
| use serde::Serialize; |
| use ts_rs::TS; |
|
|
| use super::InternalChatMessageMetadataPassthrough; |
| use super::ResponseItem; |
|
|
| const MAX_EXECUTED_TOOL_CALL_ARGUMENT_BYTES: usize = 8 * 1024; |
| |
| const MAX_TOOL_RESULT_SOURCES: usize = 32; |
| |
| pub const MAX_TOOL_RESULT_SOURCE_FIELD_BYTES: usize = 128; |
| |
| const MAX_EXECUTED_TOOL_CALL_METADATA_BYTES: usize = 32 * 1024; |
| const EXECUTED_TOOL_CALL_METADATA_FIELD_BYTES: usize = b"\"executed_tool_calls\":".len(); |
| const INTERNAL_CHAT_MESSAGE_METADATA_PASSTHROUGH_FIELD_BYTES: usize = |
| b"\"internal_chat_message_metadata_passthrough\":".len(); |
|
|
| fn executed_tool_call_metadata_field_bytes( |
| metadata: &InternalChatMessageMetadataPassthrough, |
| ) -> usize { |
| let fields = InternalChatMessageMetadataPassthrough { |
| cell_id: metadata.cell_id.clone(), |
| tool_calls_complete: metadata.tool_calls_complete, |
| ..Default::default() |
| }; |
| let mut bytes = |
| serde_json::to_vec(&fields).map_or(usize::MAX, |fields| fields.len().saturating_sub(2)); |
| if metadata.executed_tool_calls.is_some() { |
| bytes = bytes |
| .saturating_add(usize::from(bytes > 0)) |
| .saturating_add(EXECUTED_TOOL_CALL_METADATA_FIELD_BYTES); |
| } |
| if bytes == 0 { |
| 0 |
| } else if metadata.turn_id.is_some() |
| || metadata.create_time.is_some() |
| || metadata.content_item_kinds.is_some() |
| { |
| bytes + 1 |
| } else { |
| bytes + INTERNAL_CHAT_MESSAGE_METADATA_PASSTHROUGH_FIELD_BYTES + 3 |
| } |
| } |
|
|
| |
| pub fn executed_tool_call_metadata_bytes(item: &ResponseItem) -> usize { |
| let Some(metadata) = item.executed_tool_call_metadata() else { |
| return 0; |
| }; |
| metadata |
| .executed_tool_calls |
| .as_ref() |
| .map_or(0, |calls| { |
| serde_json::to_vec(calls) |
| .map(|calls| calls.len()) |
| .unwrap_or(usize::MAX) |
| }) |
| .saturating_add(executed_tool_call_metadata_field_bytes(metadata)) |
| } |
|
|
| impl InternalChatMessageMetadataPassthrough { |
| |
| pub fn has_same_tool_calls(&self, calls: &[ExecutedToolCall]) -> bool { |
| self.executed_tool_calls.as_ref().is_some_and(|recorded| { |
| recorded.len() == calls.len() |
| && recorded.iter().zip(calls).all(|(recorded, call)| { |
| recorded.name == call.name && recorded.arguments() == call.arguments() |
| }) |
| }) |
| } |
| } |
|
|
| |
| pub fn bound_executed_tool_calls_for_prompt(items: &mut [ResponseItem]) { |
| bound_executed_tool_calls_for_prompt_with_priority(items, false); |
| } |
|
|
| |
| pub fn bound_executed_tool_calls_for_prompt_prioritizing_recent(items: &mut [ResponseItem]) { |
| items.reverse(); |
| bound_executed_tool_calls_for_prompt_with_priority(items, true); |
| items.reverse(); |
| } |
|
|
| fn bound_executed_tool_calls_for_prompt_with_priority( |
| items: &mut [ResponseItem], |
| prioritize_recent: bool, |
| ) { |
| let mut damaged_cells = HashSet::new(); |
| for item in items.iter_mut() { |
| let Some(metadata) = item |
| .internal_chat_message_metadata_passthrough_mut() |
| .and_then(Option::as_mut) |
| else { |
| continue; |
| }; |
| let mut truncated = false; |
| for call in metadata.executed_tool_calls.iter_mut().flatten() { |
| let argument_bytes = serde_json::to_vec(&call.arguments) |
| .map(|bytes| bytes.len()) |
| .unwrap_or(usize::MAX); |
| if call.truncation().is_none() && argument_bytes > MAX_EXECUTED_TOOL_CALL_ARGUMENT_BYTES |
| { |
| call.set_truncation( |
| argument_bytes, |
| MAX_EXECUTED_TOOL_CALL_ARGUMENT_BYTES, |
| None, |
| ); |
| } |
| truncated |= call.truncation().is_some(); |
| } |
| if truncated { |
| metadata.tool_calls_complete = None; |
| damaged_cells.extend(metadata.cell_id.clone()); |
| } |
| } |
| clear_damaged_cell_completeness(items, &damaged_cells); |
|
|
| let metadata_bytes = |items: &[ResponseItem]| { |
| items.iter().fold(0_usize, |bytes, item| { |
| bytes.saturating_add(executed_tool_call_metadata_bytes(item)) |
| }) |
| }; |
| if metadata_bytes(items) <= MAX_EXECUTED_TOOL_CALL_METADATA_BYTES { |
| return; |
| } |
| |
| for item in items.iter_mut() { |
| if let Some(metadata) = item |
| .internal_chat_message_metadata_passthrough_mut() |
| .and_then(Option::as_mut) |
| { |
| for call in metadata.executed_tool_calls.iter_mut().flatten() { |
| if call.tool_result_metadata.is_some() { |
| call.tool_result_metadata = ToolResultMetadata::omitted_due_to_size_limit(); |
| } |
| } |
| } |
| } |
|
|
| if metadata_bytes(items) <= MAX_EXECUTED_TOOL_CALL_METADATA_BYTES { |
| return; |
| } |
| |
| for item in items.iter_mut() { |
| item.clear_tool_result_metadata(); |
| } |
|
|
| if metadata_bytes(items) <= MAX_EXECUTED_TOOL_CALL_METADATA_BYTES { |
| return; |
| } |
| |
| for item in items.iter_mut() { |
| if let Some(metadata) = item |
| .internal_chat_message_metadata_passthrough_mut() |
| .and_then(Option::as_mut) |
| { |
| for call in metadata.executed_tool_calls.iter_mut().flatten() { |
| call.tool_result_sources = None; |
| } |
| } |
| } |
| if metadata_bytes(items) <= MAX_EXECUTED_TOOL_CALL_METADATA_BYTES { |
| return; |
| } |
|
|
| let mut remaining_items = items |
| .iter() |
| .filter(|item| executed_tool_call_metadata_bytes(item) > 0) |
| .count(); |
| let mut remaining_bytes = MAX_EXECUTED_TOOL_CALL_METADATA_BYTES; |
| for item in items.iter_mut() { |
| let item_bytes = executed_tool_call_metadata_bytes(item); |
| if item_bytes == 0 { |
| continue; |
| } |
| let item_budget = if prioritize_recent { |
| remaining_bytes |
| } else { |
| remaining_bytes / remaining_items |
| }; |
| if item_bytes > item_budget { |
| |
| damaged_cells.extend( |
| item.executed_tool_call_metadata() |
| .and_then(|metadata| metadata.cell_id.clone()), |
| ); |
| item.clear_tool_calls_complete(); |
| item.bound_executed_tool_calls_with_budget(item_budget); |
| } |
| remaining_bytes = remaining_bytes.saturating_sub(executed_tool_call_metadata_bytes(item)); |
| remaining_items -= 1; |
| } |
| clear_damaged_cell_completeness(items, &damaged_cells); |
| } |
|
|
| fn clear_damaged_cell_completeness(items: &mut [ResponseItem], damaged_cells: &HashSet<String>) { |
| for item in items { |
| if item.executed_tool_call_metadata().is_some_and(|metadata| { |
| metadata |
| .cell_id |
| .as_ref() |
| .is_some_and(|cell_id| damaged_cells.contains(cell_id)) |
| }) { |
| item.clear_tool_calls_complete(); |
| } |
| } |
| } |
|
|
| |
| #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, TS)] |
| #[serde(untagged)] |
| pub enum ExecutedToolCallArguments { |
| Raw(serde_json::Value), |
| #[serde(skip_deserializing)] |
| Truncated { |
| #[serde(rename = "_codex_executed_tool_call_truncated")] |
| truncation: ExecutedToolCallTruncation, |
| }, |
| } |
|
|
| |
| #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, TS)] |
| pub struct ExecutedToolCall { |
| pub name: String, |
| #[ts(type = "unknown")] |
| arguments: ExecutedToolCallArguments, |
| |
| |
| #[serde(default, skip_deserializing, skip_serializing_if = "Option::is_none")] |
| #[schemars(skip)] |
| #[ts(skip)] |
| tool_result_sources: Option<Vec<ToolResultSource>>, |
| |
| |
| #[serde( |
| default, |
| skip_deserializing, |
| skip_serializing_if = "ToolResultMetadata::is_none" |
| )] |
| #[schemars(skip)] |
| #[ts(skip)] |
| tool_result_metadata: ToolResultMetadata, |
| } |
|
|
| |
| #[derive(Clone, Default, Serialize, PartialEq, Eq)] |
| #[serde(transparent)] |
| pub struct ToolResultMetadata(Option<serde_json::Value>); |
|
|
| impl std::fmt::Debug for ToolResultMetadata { |
| fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| formatter.write_str("ToolResultMetadata([redacted])") |
| } |
| } |
|
|
| impl ToolResultMetadata { |
| |
| pub fn new(metadata: &serde_json::Value) -> Self { |
| let mut limit = MetadataSizeLimit(MAX_EXECUTED_TOOL_CALL_METADATA_BYTES); |
| if serde_json::to_writer(&mut limit, metadata).is_ok() { |
| Self(Some(metadata.clone())) |
| } else { |
| Self::omitted_due_to_size_limit() |
| } |
| } |
|
|
| fn omitted_due_to_size_limit() -> Self { |
| |
| Self(Some(serde_json::Value::String( |
| "omitted_due_to_size_limit".to_string(), |
| ))) |
| } |
|
|
| fn is_none(&self) -> bool { |
| self.0.is_none() |
| } |
|
|
| |
| pub fn is_some(&self) -> bool { |
| self.0.is_some() |
| } |
| } |
|
|
| struct MetadataSizeLimit(usize); |
|
|
| impl std::io::Write for MetadataSizeLimit { |
| fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> { |
| self.0 = self |
| .0 |
| .checked_sub(bytes.len()) |
| .ok_or_else(|| std::io::Error::other("tool result metadata exceeds the byte limit"))?; |
| Ok(bytes.len()) |
| } |
|
|
| fn flush(&mut self) -> std::io::Result<()> { |
| Ok(()) |
| } |
| } |
|
|
| |
| #[derive(Debug, Clone, PartialEq, Eq)] |
| pub struct ToolResultSources(Option<Vec<ToolResultSource>>); |
|
|
| impl ToolResultSources { |
| |
| |
| pub fn new(sources: Vec<ToolResultSource>) -> Self { |
| let mut unique_sources = Vec::new(); |
| for source in sources { |
| if unique_sources.contains(&source) { |
| continue; |
| } |
| if unique_sources.len() == MAX_TOOL_RESULT_SOURCES |
| || source.r#type.len() > MAX_TOOL_RESULT_SOURCE_FIELD_BYTES |
| || source.id.len() > MAX_TOOL_RESULT_SOURCE_FIELD_BYTES |
| { |
| return Self(None); |
| } |
| unique_sources.push(source); |
| } |
| Self(Some(unique_sources)) |
| } |
|
|
| |
| |
| pub fn parse_failed() -> Self { |
| Self(Some(vec![ToolResultSource { |
| r#type: "parse_failed".to_string(), |
| id: String::new(), |
| }])) |
| } |
| } |
|
|
| |
| #[derive(Debug, Clone, Serialize, PartialEq, Eq)] |
| pub struct ToolResultSource { |
| #[serde(rename = "type")] |
| pub r#type: String, |
| pub id: String, |
| } |
|
|
| |
| #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, TS)] |
| pub struct ExecutedToolCallTruncation { |
| original_bytes: usize, |
| max_bytes: usize, |
| #[serde(default, skip_serializing_if = "Option::is_none")] |
| omitted_calls: Option<usize>, |
| #[serde(default, skip_serializing_if = "Option::is_none")] |
| original_name_bytes: Option<usize>, |
| } |
|
|
| impl ExecutedToolCall { |
| |
| pub fn new(name: String, arguments: serde_json::Value) -> Self { |
| let arguments = if arguments |
| .as_object() |
| .is_some_and(|object| object.contains_key("_codex_executed_tool_call_truncated")) |
| { |
| serde_json::json!({ "_codex_executed_tool_call_raw": arguments }) |
| } else { |
| arguments |
| }; |
| Self { |
| name, |
| arguments: ExecutedToolCallArguments::Raw(arguments), |
| tool_result_sources: None, |
| tool_result_metadata: ToolResultMetadata::default(), |
| } |
| } |
|
|
| |
| pub fn truncated(name: String, original_bytes: usize, max_bytes: usize) -> Self { |
| let mut call = Self::new(name, serde_json::Value::Null); |
| call.set_truncation(original_bytes, max_bytes, None); |
| call |
| } |
|
|
| |
| pub fn arguments(&self) -> &ExecutedToolCallArguments { |
| &self.arguments |
| } |
|
|
| |
| pub fn set_tool_result_sources(&mut self, sources: ToolResultSources) -> bool { |
| self.tool_result_sources = sources.0; |
| self.tool_result_sources.is_some() |
| } |
|
|
| |
| pub fn set_tool_result_metadata(&mut self, metadata: ToolResultMetadata) { |
| self.tool_result_metadata = metadata; |
| } |
|
|
| fn truncation(&self) -> Option<&ExecutedToolCallTruncation> { |
| match &self.arguments { |
| ExecutedToolCallArguments::Raw(_) => None, |
| ExecutedToolCallArguments::Truncated { truncation } => Some(truncation), |
| } |
| } |
|
|
| fn set_truncation( |
| &mut self, |
| original_bytes: usize, |
| max_bytes: usize, |
| omitted_calls: Option<usize>, |
| ) { |
| self.set_truncation_with_name( |
| original_bytes, |
| max_bytes, |
| omitted_calls, |
| None, |
| ); |
| } |
|
|
| fn set_truncation_with_name( |
| &mut self, |
| original_bytes: usize, |
| max_bytes: usize, |
| omitted_calls: Option<usize>, |
| original_name_bytes: Option<usize>, |
| ) { |
| self.arguments = ExecutedToolCallArguments::Truncated { |
| truncation: ExecutedToolCallTruncation { |
| original_bytes, |
| max_bytes, |
| omitted_calls, |
| original_name_bytes, |
| }, |
| }; |
| } |
| } |
|
|
| impl ResponseItem { |
| fn ensure_tool_call_metadata(&mut self) -> Option<&mut InternalChatMessageMetadataPassthrough> { |
| self.internal_chat_message_metadata_passthrough_mut() |
| .map(Option::get_or_insert_default) |
| } |
|
|
| |
| pub fn set_tool_call_cell_id(&mut self, cell_id: &str) { |
| if let Some(metadata) = self.ensure_tool_call_metadata() { |
| metadata.cell_id = Some(cell_id.to_string()); |
| } |
| } |
|
|
| |
| pub fn append_executed_tool_calls(&mut self, calls: Vec<ExecutedToolCall>) { |
| if calls.is_empty() { |
| return; |
| } |
| let Some(metadata) = self.ensure_tool_call_metadata() else { |
| return; |
| }; |
| metadata |
| .executed_tool_calls |
| .get_or_insert_with(Vec::new) |
| .extend(calls); |
| } |
|
|
| |
| pub fn mark_tool_calls_complete(&mut self) { |
| if let Some(metadata) = self.ensure_tool_call_metadata() { |
| metadata.tool_calls_complete = Some(true); |
| } |
| } |
|
|
| |
| pub fn clear_tool_calls_complete(&mut self) { |
| if let Some(metadata) = self |
| .internal_chat_message_metadata_passthrough_mut() |
| .and_then(Option::as_mut) |
| { |
| metadata.tool_calls_complete = None; |
| } |
| } |
|
|
| |
| pub fn executed_tool_call_metadata(&self) -> Option<&InternalChatMessageMetadataPassthrough> { |
| self.internal_chat_message_metadata_passthrough() |
| } |
|
|
| |
| pub fn clear_tool_result_metadata(&mut self) { |
| if let Some(metadata) = self |
| .internal_chat_message_metadata_passthrough_mut() |
| .and_then(Option::as_mut) |
| { |
| for call in metadata.executed_tool_calls.iter_mut().flatten() { |
| call.tool_result_metadata = ToolResultMetadata::default(); |
| } |
| } |
| } |
|
|
| |
| fn bound_executed_tool_calls_with_budget(&mut self, max_metadata_bytes: usize) { |
| let Some(metadata) = self.executed_tool_call_metadata() else { |
| return; |
| }; |
| let max_call_bytes = |
| max_metadata_bytes.saturating_sub(executed_tool_call_metadata_field_bytes(metadata)); |
| let Some(calls) = self |
| .internal_chat_message_metadata_passthrough_mut() |
| .and_then(Option::as_mut) |
| .and_then(|metadata| metadata.executed_tool_calls.as_mut()) |
| .filter(|calls| !calls.is_empty()) |
| else { |
| self.clear_executed_tool_calls(); |
| return; |
| }; |
| let represented_calls = calls.iter().fold(0_usize, |count, call| { |
| count.saturating_add(1).saturating_add( |
| call.truncation() |
| .and_then(|truncation| truncation.omitted_calls) |
| .unwrap_or_default(), |
| ) |
| }); |
| calls.truncate(1); |
| let call = &mut calls[0]; |
| let original_bytes = call |
| .truncation() |
| .map(|truncation| truncation.original_bytes) |
| .unwrap_or_else(|| { |
| serde_json::to_vec(&call.arguments) |
| .map(|bytes| bytes.len()) |
| .unwrap_or(usize::MAX) |
| }); |
| let original_name_bytes = call |
| .truncation() |
| .and_then(|truncation| truncation.original_name_bytes); |
| let omitted_calls = (represented_calls > 1).then_some(represented_calls - 1); |
| call.set_truncation_with_name( |
| original_bytes, |
| max_call_bytes.min(MAX_EXECUTED_TOOL_CALL_ARGUMENT_BYTES), |
| omitted_calls, |
| original_name_bytes, |
| ); |
| let serialized_bytes = |calls: &[ExecutedToolCall]| { |
| serde_json::to_vec(calls) |
| .map(|bytes| bytes.len()) |
| .unwrap_or(usize::MAX) |
| }; |
| if serialized_bytes(calls) > max_call_bytes { |
| let call = &mut calls[0]; |
| call.set_truncation_with_name( |
| original_bytes, |
| max_call_bytes.min(MAX_EXECUTED_TOOL_CALL_ARGUMENT_BYTES), |
| omitted_calls, |
| Some(original_name_bytes.unwrap_or(call.name.len())), |
| ); |
| |
| let excess_bytes = serialized_bytes(calls).saturating_sub(max_call_bytes); |
| let name = &mut calls[0].name; |
| name.truncate(name.floor_char_boundary(name.len().saturating_sub(excess_bytes))); |
| } |
| if serialized_bytes(calls) > max_call_bytes { |
| self.clear_executed_tool_calls(); |
| } |
| } |
|
|
| |
| pub fn clear_executed_tool_calls(&mut self) { |
| let Some(metadata) = self.internal_chat_message_metadata_passthrough_mut() else { |
| return; |
| }; |
| let Some(passthrough) = metadata.as_mut() else { |
| return; |
| }; |
| passthrough.cell_id = None; |
| passthrough.executed_tool_calls = None; |
| passthrough.tool_calls_complete = None; |
| if *passthrough == InternalChatMessageMetadataPassthrough::default() { |
| *metadata = None; |
| } |
| } |
| } |
|
|
| #[cfg(test)] |
| #[path = "executed_tool_calls_tests.rs"] |
| mod tests; |
|
|