mirror of
https://github.com/openai/codex.git
synced 2026-11-03 19:53:25 +00:00
Count serialized JSON bytes without allocating strings (#40604)
## Why History estimation and executed-tool-call limits only need the size of serialized JSON, not the serialized output itself. ## What changed - Add a shared `serialized_json_bytes` helper that streams JSON into a byte counter. - Use it for response-item size estimates and executed-tool-call argument limits. - Preserve serialization errors so callers can apply their existing fallback behavior. ## Testing - Cover response-item sizing with escaped characters, newlines, and multibyte text. GitOrigin-RevId: a4417996df96df5c51cae62830adc496912f69c7
This commit is contained in:
committed by
copyberry
parent
70b5cfc73b
commit
46aa019e80
@@ -7,6 +7,7 @@ use crate::event_mapping::has_non_contextual_dev_message_content;
|
||||
use crate::event_mapping::is_contextual_dev_message_content;
|
||||
use crate::event_mapping::is_contextual_user_message_content;
|
||||
use crate::session::turn_context::TurnContext;
|
||||
use crate::utils::json::serialized_json_bytes;
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
||||
use codex_context_fragments::set_annotated_content;
|
||||
@@ -666,8 +667,8 @@ fn estimate_response_item_model_visible_bytes(item: &ResponseItem) -> i64 {
|
||||
..
|
||||
} => i64::try_from(estimate_reasoning_length(content.len())).unwrap_or(i64::MAX),
|
||||
item => {
|
||||
let raw = serde_json::to_string(item)
|
||||
.map(|serialized| i64::try_from(serialized.len()).unwrap_or(i64::MAX))
|
||||
let raw = serialized_json_bytes(item)
|
||||
.map(|len| i64::try_from(len).unwrap_or(i64::MAX))
|
||||
.unwrap_or_default();
|
||||
let (image_payload_bytes, image_replacement_bytes) =
|
||||
image_data_url_estimate_adjustment(item);
|
||||
|
||||
@@ -2724,7 +2724,7 @@ fn text_only_items_unchanged() {
|
||||
id: None,
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: "Hello world, this is a response.".to_string(),
|
||||
text: "Hello, \"world\"!\nこんにちは".to_string(),
|
||||
}],
|
||||
phase: None,
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
|
||||
@@ -12,6 +12,7 @@ use serde_json::Value as JsonValue;
|
||||
use crate::tools::context::ToolCallSource;
|
||||
use crate::tools::context::ToolPayload;
|
||||
use crate::tools::router::ToolCall;
|
||||
use crate::utils::json::serialized_json_bytes;
|
||||
|
||||
const MAX_EXECUTED_TOOL_CALL_ARGUMENT_BYTES: usize = 8 * 1024;
|
||||
const MAX_EXECUTED_TOOL_CALL_FULL_ARGUMENT_BYTES_PER_OUTPUT: usize = 32 * 1024;
|
||||
@@ -39,28 +40,6 @@ struct RecordedCell {
|
||||
pending_full_argument_bytes: usize,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct JsonByteCounter(usize);
|
||||
|
||||
impl std::io::Write for JsonByteCounter {
|
||||
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
|
||||
self.0 = self.0.saturating_add(bytes.len());
|
||||
Ok(bytes.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn serialized_json_bytes<T: serde::Serialize + ?Sized>(value: &T) -> usize {
|
||||
let mut counter = JsonByteCounter::default();
|
||||
if serde_json::to_writer(&mut counter, value).is_err() {
|
||||
return usize::MAX;
|
||||
}
|
||||
counter.0
|
||||
}
|
||||
|
||||
impl ExecutedToolCallRecorder {
|
||||
pub(crate) fn record_tool_call(
|
||||
&self,
|
||||
@@ -87,8 +66,10 @@ impl ExecutedToolCallRecorder {
|
||||
|
||||
let original_bytes = match &call.payload {
|
||||
ToolPayload::Function { arguments } => arguments.len(),
|
||||
ToolPayload::Custom { input } => serialized_json_bytes(input),
|
||||
ToolPayload::ToolSearch { arguments } => serialized_json_bytes(arguments),
|
||||
ToolPayload::Custom { input } => serialized_json_bytes(input).unwrap_or(usize::MAX),
|
||||
ToolPayload::ToolSearch { arguments } => {
|
||||
serialized_json_bytes(arguments).unwrap_or(usize::MAX)
|
||||
}
|
||||
};
|
||||
let name = codex_tools::code_mode_name_for_tool_name(&call.tool_name);
|
||||
let recorded_call = if original_bytes > MAX_EXECUTED_TOOL_CALL_ARGUMENT_BYTES {
|
||||
|
||||
@@ -199,7 +199,8 @@ fn executed_tool_call_recorder_bounds_retained_history_and_reports_omissions() {
|
||||
.retained_calls
|
||||
.values()
|
||||
.map(serialized_json_bytes)
|
||||
.sum::<usize>();
|
||||
.sum::<serde_json::Result<usize>>()
|
||||
.expect("retained calls must serialize");
|
||||
assert!(retained_bytes <= MAX_EXECUTED_TOOL_CALL_FULL_ARGUMENT_BYTES_PER_OUTPUT);
|
||||
|
||||
let metadata = prompt
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#[derive(Default)]
|
||||
struct JsonByteCounter(usize);
|
||||
|
||||
impl std::io::Write for JsonByteCounter {
|
||||
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
|
||||
self.0 = self.0.saturating_add(bytes.len());
|
||||
Ok(bytes.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Counts serialized JSON bytes without retaining the serialized output.
|
||||
pub(crate) fn serialized_json_bytes<T: serde::Serialize + ?Sized>(
|
||||
value: &T,
|
||||
) -> serde_json::Result<usize> {
|
||||
let mut counter = JsonByteCounter::default();
|
||||
serde_json::to_writer(&mut counter, value)?;
|
||||
Ok(counter.0)
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub(crate) mod json;
|
||||
pub mod path_utils;
|
||||
|
||||
Reference in New Issue
Block a user