Files
codex/codex-rs/core/src/client_tool_metadata.rs
T
ningyi-oai dbb875d23c Bound tool-call observations to the outgoing Responses message budget (#47957)
## Why

Removing tool-result metadata alone can leave requests above the 15 MiB message budget when recorded tool calls and arguments occupy the remaining space. If trimming loses calls or arguments from a Code Mode cell, later waits must not report its call inventory as complete.

## What changed

- Bound all optional tool-call observations against the serialized HTTP or WebSocket message size, preserving ordinary tool outputs and continuation history.
- Shed generic result metadata, sources, and recorded arguments before resource-access evidence, retaining tool names and smaller metadata where possible.
- Invalidate completeness across affected cell outputs and later waits when the outgoing copy loses calls or arguments.
- Preserve size-limit omission markers for direct tool-result metadata when they fit, while continuing to exclude that metadata from custom providers.

## Testing

Add regression coverage for HTTP and WebSocket message budgets, unchanged tool results and history, resource-evidence retention, direct-call omission markers, and Code Mode waits after argument truncation.

GitOrigin-RevId: 538dc9d8c11b0830ec70327422f98a1803122e85
2026-09-24 22:54:29 +00:00

46 lines
1.7 KiB
Rust

//! Bounds optional tool observations using the actual outgoing Responses message.
//! Only the wire copy changes; model-visible input and WebSocket continuation history stay intact.
use crate::tools::metadata_metrics;
use crate::utils::json::serialized_json_bytes;
use codex_protocol::models::ResponseItem;
use codex_protocol::models::bound_executed_tool_calls_for_message;
use serde::Serialize;
const MAX_RESPONSE_MESSAGE_BYTES: usize = 15 * 1024 * 1024;
pub(super) fn bounded_input<T: Serialize>(
message: &T,
input: &[ResponseItem],
) -> Option<Vec<ResponseItem>> {
if !input.iter().any(|item| {
item.executed_tool_call_metadata().is_some_and(|metadata| {
metadata.executed_tool_calls.is_some()
|| metadata.cell_id.is_some()
|| metadata.tool_calls_complete.is_some()
})
}) {
return None;
}
// Serialization errors belong to the transport's existing error path, not this soft budget.
let message_bytes = serialized_json_bytes(message).ok()?;
if message_bytes <= MAX_RESPONSE_MESSAGE_BYTES {
return None;
}
let before = metadata_metrics::metadata_bytes(input);
let mut bounded = input.to_vec();
let overage = message_bytes - MAX_RESPONSE_MESSAGE_BYTES;
bound_executed_tool_calls_for_message(&mut bounded, before.saturating_sub(overage));
let after = metadata_metrics::metadata_bytes(&bounded);
if before == after {
return None;
}
metadata_metrics::record_shedding("message", before, after, codex_otel::global().as_ref());
// Ordinary content alone can exceed the soft limit. Preserve it and existing error handling.
Some(bounded)
}
#[cfg(test)]
#[path = "client_tool_metadata_tests.rs"]
mod tests;