mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
Preserve more tool metadata within outgoing message budgets (#47758)
## Why The 32 KiB per-result cap and 128 KiB aggregate budget discard tool metadata even when the outgoing request has room to retain it. ## What changed - Remove the per-result capture cap and raise the aggregate executed-tool metadata budget to 2 MiB. - Apply a 15 MiB soft budget to serialized HTTP requests and WebSocket messages, using the actual WebSocket delta. Trim optional result metadata on the wire copy while preserving ordinary content and logical history. - Prioritize `openai/resource_access` metadata during trimming, and include `overage_bytes` in omission markers. - Record bytes removed by metadata budgets in `codex.tool_metadata.shed_bytes`, tagged by stage. ## Testing Add coverage for HTTP and WebSocket message boundaries, resource preservation, residual marker removal, unchanged history, and shedding metrics. Update direct-tool and code-mode tests to verify metadata survives beyond the previous limits. GitOrigin-RevId: ed00996b77945300f60db1e52eff22b4de2707c1
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use schemars::JsonSchema;
|
||||
@@ -15,9 +14,7 @@ const MAX_TOOL_RESULT_SOURCES: usize = 32;
|
||||
/// Maximum UTF-8 bytes for each source's `type` and `id` separately, not the source list.
|
||||
pub const MAX_TOOL_RESULT_SOURCE_FIELD_BYTES: usize = 128;
|
||||
/// Maximum serialized warehouse-only attempted-tool metadata in one request.
|
||||
const MAX_EXECUTED_TOOL_CALL_METADATA_BYTES: usize = 128 * 1024;
|
||||
/// Maximum serialized raw metadata captured from one tool result.
|
||||
const MAX_TOOL_RESULT_METADATA_BYTES: usize = 32 * 1024;
|
||||
const MAX_EXECUTED_TOOL_CALL_METADATA_BYTES: usize = 2 * 1024 * 1024;
|
||||
const RESOURCE_ACCESS_METADATA_KEY: &str = "openai/resource_access";
|
||||
const EXECUTED_TOOL_CALL_METADATA_FIELD_BYTES: usize = b"\"executed_tool_calls\":".len();
|
||||
const INTERNAL_CHAT_MESSAGE_METADATA_PASSTHROUGH_FIELD_BYTES: usize =
|
||||
@@ -135,8 +132,6 @@ fn bound_executed_tool_calls_for_prompt_with_priority(
|
||||
}
|
||||
// Raw result metadata must not displace existing source evidence, calls or completion proof.
|
||||
// Shed the largest values first so one large result does not discard unrelated small results.
|
||||
let omitted = ToolResultMetadata::omitted_due_to_size_limit();
|
||||
let omitted_bytes = serde_json::to_vec(&omitted).map_or(usize::MAX, |value| value.len());
|
||||
let mut result_metadata = Vec::new();
|
||||
for (item_index, item) in items.iter_mut().enumerate() {
|
||||
if let Some(metadata) = item
|
||||
@@ -182,10 +177,13 @@ fn bound_executed_tool_calls_for_prompt_with_priority(
|
||||
total_metadata_bytes =
|
||||
total_metadata_bytes.saturating_sub(bytes.saturating_sub(retained_bytes));
|
||||
*bytes = retained_bytes;
|
||||
} else if *bytes > omitted_bytes {
|
||||
**metadata = omitted.clone();
|
||||
total_metadata_bytes = total_metadata_bytes.saturating_sub(*bytes - omitted_bytes);
|
||||
*bytes = omitted_bytes;
|
||||
} else {
|
||||
let retained_bytes = metadata.omit_if_smaller(
|
||||
*bytes,
|
||||
total_metadata_bytes - MAX_EXECUTED_TOOL_CALL_METADATA_BYTES,
|
||||
);
|
||||
total_metadata_bytes = total_metadata_bytes.saturating_sub(*bytes - retained_bytes);
|
||||
*bytes = retained_bytes;
|
||||
}
|
||||
}
|
||||
// Resource evidence is optional too; it must not displace the call inventory.
|
||||
@@ -197,17 +195,18 @@ fn bound_executed_tool_calls_for_prompt_with_priority(
|
||||
break;
|
||||
}
|
||||
// An omission marker can be larger than a small object, including an empty `_meta`.
|
||||
if *bytes > omitted_bytes {
|
||||
**metadata = omitted.clone();
|
||||
total_metadata_bytes = total_metadata_bytes.saturating_sub(*bytes - omitted_bytes);
|
||||
*bytes = omitted_bytes;
|
||||
}
|
||||
let retained_bytes = metadata.omit_if_smaller(
|
||||
*bytes,
|
||||
total_metadata_bytes - MAX_EXECUTED_TOOL_CALL_METADATA_BYTES,
|
||||
);
|
||||
total_metadata_bytes = total_metadata_bytes.saturating_sub(*bytes - retained_bytes);
|
||||
*bytes = retained_bytes;
|
||||
}
|
||||
// Use the updated sizes: markers should be removed before smaller provider metadata.
|
||||
result_metadata.sort_by_key(|(bytes, order, call_index, metadata)| {
|
||||
(
|
||||
std::cmp::Reverse(*bytes),
|
||||
metadata.0 != omitted.0,
|
||||
!metadata.is_omitted_due_to_size_limit(),
|
||||
*order,
|
||||
*call_index,
|
||||
)
|
||||
@@ -340,23 +339,9 @@ impl std::fmt::Debug for ToolResultMetadata {
|
||||
}
|
||||
|
||||
impl ToolResultMetadata {
|
||||
/// Bounds serialization before cloning, retaining resource evidence if the full result is too large.
|
||||
/// Captures the complete result; retention and outgoing-request budgets apply later.
|
||||
pub fn new(metadata: &serde_json::Value) -> Self {
|
||||
let mut limit = MetadataSizeLimit(MAX_TOOL_RESULT_METADATA_BYTES);
|
||||
if serde_json::to_writer(&mut limit, metadata).is_ok() {
|
||||
return Self(Some(metadata.clone()));
|
||||
}
|
||||
if let Some(resource_access) = metadata.get(RESOURCE_ACCESS_METADATA_KEY) {
|
||||
let resource_metadata =
|
||||
BTreeMap::from([(RESOURCE_ACCESS_METADATA_KEY, resource_access)]);
|
||||
let mut limit = MetadataSizeLimit(MAX_TOOL_RESULT_METADATA_BYTES);
|
||||
if serde_json::to_writer(&mut limit, &resource_metadata).is_ok() {
|
||||
return Self(Some(serde_json::json!({
|
||||
(RESOURCE_ACCESS_METADATA_KEY): resource_access,
|
||||
})));
|
||||
}
|
||||
}
|
||||
Self::omitted_due_to_size_limit()
|
||||
Self(Some(metadata.clone()))
|
||||
}
|
||||
|
||||
/// Leaves generic metadata unchanged when it has no resource-access field.
|
||||
@@ -371,39 +356,48 @@ impl ToolResultMetadata {
|
||||
true
|
||||
}
|
||||
|
||||
fn omitted_due_to_size_limit() -> Self {
|
||||
fn omitted_due_to_size_limit(overage_bytes: usize) -> Self {
|
||||
// MCP `_meta` is an object; this string is a harness omission status, not provider data.
|
||||
Self(Some(serde_json::Value::String(
|
||||
"omitted_due_to_size_limit".to_string(),
|
||||
)))
|
||||
Self(Some(serde_json::Value::String(format!(
|
||||
"omitted_due_to_size_limit (overage_bytes={overage_bytes})"
|
||||
))))
|
||||
}
|
||||
|
||||
fn is_omitted_due_to_size_limit(&self) -> bool {
|
||||
let Some(serde_json::Value::String(value)) = self.0.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
value == "omitted_due_to_size_limit"
|
||||
|| value
|
||||
.strip_prefix("omitted_due_to_size_limit (overage_bytes=")
|
||||
.and_then(|value| value.strip_suffix(')'))
|
||||
.is_some_and(|value| value.parse::<usize>().is_ok())
|
||||
}
|
||||
|
||||
fn omit_if_smaller(&mut self, original_bytes: usize, overage_bytes: usize) -> usize {
|
||||
if self.is_none() || self.is_omitted_due_to_size_limit() {
|
||||
return original_bytes;
|
||||
}
|
||||
let omitted = Self::omitted_due_to_size_limit(overage_bytes);
|
||||
let omitted_bytes = serde_json::to_vec(&omitted).map_or(usize::MAX, |value| value.len());
|
||||
if omitted_bytes < original_bytes {
|
||||
*self = omitted;
|
||||
omitted_bytes
|
||||
} else {
|
||||
original_bytes
|
||||
}
|
||||
}
|
||||
|
||||
fn is_none(&self) -> bool {
|
||||
self.0.is_none()
|
||||
}
|
||||
|
||||
/// Whether the bounded snapshot contains metadata or an omission marker.
|
||||
/// Whether the captured snapshot contains metadata or an omission marker.
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A bounded capture update. Omitted updates still clear any previously recorded evidence.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ToolResultSources(Option<Vec<ToolResultSource>>);
|
||||
@@ -494,7 +488,7 @@ impl ExecutedToolCall {
|
||||
self.tool_result_sources.is_some()
|
||||
}
|
||||
|
||||
/// Replaces the entire `_meta` snapshot, including with an omission marker if oversized.
|
||||
/// Replaces the entire captured `_meta` snapshot.
|
||||
pub fn set_tool_result_metadata(&mut self, metadata: ToolResultMetadata) {
|
||||
self.tool_result_metadata = metadata;
|
||||
}
|
||||
@@ -614,6 +608,17 @@ impl ResponseItem {
|
||||
result_metadata(self).eq(result_metadata(other))
|
||||
}
|
||||
|
||||
/// Whether an output carries raw result metadata or a harness omission marker.
|
||||
pub fn has_tool_result_metadata(&self) -> bool {
|
||||
self.executed_tool_call_metadata().is_some_and(|metadata| {
|
||||
metadata
|
||||
.executed_tool_calls
|
||||
.iter()
|
||||
.flatten()
|
||||
.any(|call| call.tool_result_metadata.is_some())
|
||||
})
|
||||
}
|
||||
|
||||
/// Omits raw tool results without changing existing call, source or completion metadata.
|
||||
pub fn clear_tool_result_metadata(&mut self) {
|
||||
if let Some(metadata) = self
|
||||
@@ -626,6 +631,36 @@ impl ResponseItem {
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks larger raw results with the full request's overage, preserving existing markers.
|
||||
pub fn omit_tool_result_metadata(&mut self, overage_bytes: usize) {
|
||||
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() {
|
||||
let result = &mut call.tool_result_metadata;
|
||||
let bytes = serde_json::to_vec(result).map_or(usize::MAX, |value| value.len());
|
||||
result.omit_if_smaller(bytes, overage_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Preserves complete resource evidence, marking other large results with request overage.
|
||||
pub fn retain_tool_resource_access_or_omit_metadata(&mut self, overage_bytes: usize) {
|
||||
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() {
|
||||
let result = &mut call.tool_result_metadata;
|
||||
if !result.retain_resource_access() {
|
||||
let bytes = serde_json::to_vec(result).map_or(usize::MAX, |value| value.len());
|
||||
result.omit_if_smaller(bytes, overage_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reduces optional results to resource evidence before considering call-inventory loss.
|
||||
pub fn retain_tool_resource_access(&mut self) {
|
||||
if let Some(metadata) = self
|
||||
|
||||
@@ -110,7 +110,7 @@ fn executed_tool_call_prompt_budget_includes_metadata_fields() -> Result<()> {
|
||||
String::new(),
|
||||
serde_json::Value::Null,
|
||||
)]);
|
||||
item.set_tool_call_cell_id("cell-\"\\");
|
||||
item.set_tool_call_cell_id(&format!("cell-\"\\{}", "x".repeat(512)));
|
||||
item.mark_tool_calls_complete();
|
||||
item
|
||||
})
|
||||
@@ -199,7 +199,8 @@ fn executed_tool_call_prompt_budget_includes_metadata_fields() -> Result<()> {
|
||||
let arguments = serde_json::json!({ "payload": "x".repeat(7 * 1024) });
|
||||
let argument_bytes = serde_json::to_vec(&arguments)?.len();
|
||||
assert!(argument_bytes < MAX_EXECUTED_TOOL_CALL_ARGUMENT_BYTES);
|
||||
assert!(24 * argument_bytes > MAX_EXECUTED_TOOL_CALL_METADATA_BYTES);
|
||||
let call_count = MAX_EXECUTED_TOOL_CALL_METADATA_BYTES / argument_bytes + 1;
|
||||
assert!(call_count * argument_bytes > MAX_EXECUTED_TOOL_CALL_METADATA_BYTES);
|
||||
let mut items = ["A", "wait-A", "B"].map(|call_id| {
|
||||
let mut item = output(call_id);
|
||||
item.mark_tool_calls_complete();
|
||||
@@ -208,7 +209,7 @@ fn executed_tool_call_prompt_budget_includes_metadata_fields() -> Result<()> {
|
||||
for item in &mut items[..2] {
|
||||
item.set_tool_call_cell_id("cell-A");
|
||||
}
|
||||
let calls = vec![ExecutedToolCall::new("test_tool".to_string(), arguments); 24];
|
||||
let calls = vec![ExecutedToolCall::new("test_tool".to_string(), arguments); call_count];
|
||||
items[0].append_executed_tool_calls(calls.clone());
|
||||
items[2].append_executed_tool_calls(vec![ExecutedToolCall::new(
|
||||
"test_tool".to_string(),
|
||||
@@ -230,7 +231,7 @@ fn executed_tool_call_prompt_budget_includes_metadata_fields() -> Result<()> {
|
||||
assert_eq!(recorded[0].name, "test_tool");
|
||||
let truncation = recorded[0].truncation().unwrap();
|
||||
assert_eq!(truncation.original_bytes, argument_bytes);
|
||||
assert_eq!(truncation.omitted_calls, Some(23));
|
||||
assert_eq!(truncation.omitted_calls, Some(call_count - 1));
|
||||
assert_eq!(
|
||||
bounded[1]
|
||||
.executed_tool_call_metadata()
|
||||
@@ -249,7 +250,7 @@ fn executed_tool_call_prompt_budget_includes_metadata_fields() -> Result<()> {
|
||||
.truncation()
|
||||
.unwrap()
|
||||
.omitted_calls,
|
||||
Some(47),
|
||||
Some(2 * call_count - 1),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -266,6 +267,22 @@ fn executed_tool_call_prompt_budget_includes_metadata_fields() -> Result<()> {
|
||||
16
|
||||
]);
|
||||
items[1].mark_tool_calls_complete();
|
||||
// Leave room for calls, but not their sources, without exceeding the argument limit.
|
||||
let padding_bytes = MAX_EXECUTED_TOOL_CALL_METADATA_BYTES - metadata_bytes(&items)? - 1024;
|
||||
let calls = items[0]
|
||||
.ensure_tool_call_metadata()
|
||||
.unwrap()
|
||||
.executed_tool_calls
|
||||
.as_mut()
|
||||
.unwrap();
|
||||
let call_count = calls.len();
|
||||
for (index, call) in calls.iter_mut().enumerate() {
|
||||
call.name.push_str(
|
||||
&"x".repeat(
|
||||
padding_bytes / call_count + usize::from(index < padding_bytes % call_count),
|
||||
),
|
||||
);
|
||||
}
|
||||
let expected = items.clone();
|
||||
assert!(
|
||||
first_executed_tool_call(&mut items[0])
|
||||
@@ -293,7 +310,7 @@ fn executed_tool_call_prompt_budget_includes_metadata_fields() -> Result<()> {
|
||||
.internal_chat_message_metadata_passthrough_mut()
|
||||
.unwrap() = metadata;
|
||||
let without_marker = item.clone();
|
||||
item.set_tool_call_cell_id("cell-\"\\");
|
||||
item.set_tool_call_cell_id(&format!("cell-\"\\{}", "x".repeat(256)));
|
||||
item.mark_tool_calls_complete();
|
||||
assert_eq!(
|
||||
metadata_bytes(std::slice::from_ref(&item))?,
|
||||
@@ -501,7 +518,7 @@ fn tool_result_source_snapshots_replace_atomically() -> Result<()> {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_result_metadata_is_host_only_bounded_and_redacted() -> Result<()> {
|
||||
fn tool_result_metadata_is_host_only_and_redacted_without_a_per_result_limit() -> Result<()> {
|
||||
let mut call = ExecutedToolCall::new("test_tool".to_string(), serde_json::json!({}));
|
||||
let without_metadata = call.clone();
|
||||
for metadata in [
|
||||
@@ -510,9 +527,14 @@ fn tool_result_metadata_is_host_only_bounded_and_redacted() -> Result<()> {
|
||||
"other": false,
|
||||
}),
|
||||
serde_json::json!({
|
||||
"openai/resource_access": { "resources": ["not-for-debug"] },
|
||||
"other": "preserved when the full metadata fits",
|
||||
"openai/resource_access": {
|
||||
"resource_coverage": "complete",
|
||||
"resources": ["not-for-debug", "x".repeat(40 * 1024)],
|
||||
},
|
||||
"other": "preserved along with resource_access",
|
||||
}),
|
||||
// Capture preserves the original value even when serialization escapes increase its size.
|
||||
serde_json::json!({ "value": "\0".repeat(32 * 1024) }),
|
||||
serde_json::json!({}),
|
||||
serde_json::json!(null),
|
||||
] {
|
||||
@@ -535,74 +557,80 @@ fn tool_result_metadata_is_host_only_bounded_and_redacted() -> Result<()> {
|
||||
without_metadata
|
||||
);
|
||||
}
|
||||
|
||||
let exact_limit = serde_json::json!({
|
||||
"value": "x".repeat(MAX_TOOL_RESULT_METADATA_BYTES - r#"{"value":""}"#.len()),
|
||||
});
|
||||
let capture = ToolResultMetadata::new(&exact_limit);
|
||||
assert!(capture.is_some());
|
||||
call.set_tool_result_metadata(capture);
|
||||
assert_eq!(
|
||||
serde_json::to_value(&call)?["tool_result_metadata"],
|
||||
exact_limit
|
||||
);
|
||||
// JSON escaping counts toward the bound, not just the in-memory string length.
|
||||
let oversized = serde_json::json!({ "value": "\0".repeat(MAX_TOOL_RESULT_METADATA_BYTES / 2) });
|
||||
let capture = ToolResultMetadata::new(&oversized);
|
||||
assert!(capture.is_some());
|
||||
call.set_tool_result_metadata(capture);
|
||||
let wire = serde_json::to_value(&call)?;
|
||||
assert_eq!(
|
||||
wire,
|
||||
serde_json::json!({
|
||||
"name": "test_tool",
|
||||
"arguments": {},
|
||||
"tool_result_metadata": "omitted_due_to_size_limit",
|
||||
})
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_value::<ExecutedToolCall>(wire)?,
|
||||
without_metadata
|
||||
);
|
||||
|
||||
let resource_access = serde_json::json!({
|
||||
"schema_version": 1,
|
||||
"resource_coverage": "incomplete",
|
||||
"resources": [{ "id": "not-for-debug" }],
|
||||
"reasons": ["tool_error"],
|
||||
});
|
||||
let oversized = serde_json::json!({
|
||||
"openai/resource_access": resource_access,
|
||||
"other": "x".repeat(MAX_TOOL_RESULT_METADATA_BYTES),
|
||||
});
|
||||
call.set_tool_result_metadata(ToolResultMetadata::new(&oversized));
|
||||
let wire = serde_json::to_value(&call)?;
|
||||
assert_eq!(
|
||||
wire["tool_result_metadata"],
|
||||
serde_json::json!({ "openai/resource_access": resource_access }),
|
||||
);
|
||||
assert!(!format!("{call:?}").contains("not-for-debug"));
|
||||
assert_eq!(
|
||||
serde_json::from_value::<ExecutedToolCall>(wire)?,
|
||||
without_metadata
|
||||
);
|
||||
// Do not trim resource lists while preserving a provider's coverage claim.
|
||||
let oversized_resources = serde_json::json!({
|
||||
"openai/resource_access": {
|
||||
"resource_coverage": "complete",
|
||||
"resources": ["x".repeat(MAX_TOOL_RESULT_METADATA_BYTES)],
|
||||
},
|
||||
});
|
||||
assert_eq!(
|
||||
ToolResultMetadata::new(&oversized_resources),
|
||||
ToolResultMetadata::omitted_due_to_size_limit(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wire_budget_helpers_preserve_resource_evidence_markers_and_small_values() {
|
||||
let resource_only = serde_json::json!({
|
||||
"openai/resource_access": { "resource_coverage": "complete", "resources": [] },
|
||||
});
|
||||
let mut item = output("exec");
|
||||
assert!(!item.has_tool_result_metadata());
|
||||
item.append_executed_tool_calls(
|
||||
[
|
||||
ToolResultMetadata::new(&serde_json::json!({
|
||||
"openai/resource_access": resource_only["openai/resource_access"],
|
||||
"payload": "x".repeat(512),
|
||||
})),
|
||||
ToolResultMetadata::new(&serde_json::json!({
|
||||
"omitted_due_to_size_limit": { "overage_bytes": 1 },
|
||||
"payload": "x".repeat(512),
|
||||
})),
|
||||
ToolResultMetadata::new(&serde_json::json!({})),
|
||||
ToolResultMetadata::new(&serde_json::json!("omitted_due_to_size_limit")),
|
||||
ToolResultMetadata::omitted_due_to_size_limit(/*overage_bytes*/ 123_456),
|
||||
ToolResultMetadata::default(),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|metadata| {
|
||||
let mut call = ExecutedToolCall::new("test_tool".to_string(), serde_json::json!({}));
|
||||
call.set_tool_result_metadata(metadata);
|
||||
call.set_tool_result_sources(ToolResultSources::parse_failed());
|
||||
call
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
item.set_tool_call_cell_id("exec");
|
||||
item.mark_tool_calls_complete();
|
||||
assert!(item.has_tool_result_metadata());
|
||||
let mut expected = item.clone();
|
||||
let calls = expected
|
||||
.ensure_tool_call_metadata()
|
||||
.unwrap()
|
||||
.executed_tool_calls
|
||||
.as_mut()
|
||||
.unwrap();
|
||||
calls[0].set_tool_result_metadata(ToolResultMetadata::new(&resource_only));
|
||||
calls[1].set_tool_result_metadata(ToolResultMetadata::omitted_due_to_size_limit(
|
||||
/*overage_bytes*/ 77,
|
||||
));
|
||||
item.retain_tool_resource_access_or_omit_metadata(/*overage_bytes*/ 77);
|
||||
assert_eq!(item, expected);
|
||||
item.retain_tool_resource_access_or_omit_metadata(/*overage_bytes*/ 1_234);
|
||||
assert_eq!(item, expected);
|
||||
|
||||
first_executed_tool_call(&mut expected)
|
||||
.unwrap()
|
||||
.set_tool_result_metadata(ToolResultMetadata::omitted_due_to_size_limit(
|
||||
/*overage_bytes*/ 1,
|
||||
));
|
||||
item.omit_tool_result_metadata(/*overage_bytes*/ 1);
|
||||
assert_eq!(item, expected);
|
||||
item.omit_tool_result_metadata(/*overage_bytes*/ 99_999);
|
||||
assert_eq!(item, expected);
|
||||
item.clear_tool_result_metadata();
|
||||
assert!(!item.has_tool_result_metadata());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_result_metadata_is_shed_before_sources_calls_or_completion() -> Result<()> {
|
||||
let mut call = ExecutedToolCall::new("test_tool".to_string(), serde_json::json!({}));
|
||||
// Keep each result below its capture limit while filling the larger request budget.
|
||||
let name = format!(
|
||||
"test_tool{}",
|
||||
"x".repeat(MAX_EXECUTED_TOOL_CALL_METADATA_BYTES / 8 - 16 * 1024)
|
||||
);
|
||||
let mut call = ExecutedToolCall::new(name, serde_json::json!({}));
|
||||
call.set_tool_result_sources(ToolResultSources::new(vec![ToolResultSource {
|
||||
r#type: "test_resource".to_string(),
|
||||
id: "R1".to_string(),
|
||||
@@ -633,17 +661,17 @@ fn raw_result_metadata_is_shed_before_sources_calls_or_completion() -> Result<()
|
||||
.unwrap()
|
||||
.set_tool_result_metadata(metadata.clone());
|
||||
}
|
||||
let original_bytes = items
|
||||
.iter()
|
||||
.map(executed_tool_call_metadata_bytes)
|
||||
.sum::<usize>();
|
||||
let mut expected = items.clone();
|
||||
first_executed_tool_call(&mut expected[0])
|
||||
.unwrap()
|
||||
.set_tool_result_metadata(ToolResultMetadata::omitted_due_to_size_limit());
|
||||
assert!(
|
||||
items
|
||||
.iter()
|
||||
.map(executed_tool_call_metadata_bytes)
|
||||
.sum::<usize>()
|
||||
> MAX_EXECUTED_TOOL_CALL_METADATA_BYTES
|
||||
);
|
||||
.set_tool_result_metadata(ToolResultMetadata::omitted_due_to_size_limit(
|
||||
original_bytes - MAX_EXECUTED_TOOL_CALL_METADATA_BYTES,
|
||||
));
|
||||
assert!(original_bytes > MAX_EXECUTED_TOOL_CALL_METADATA_BYTES);
|
||||
for bound in [
|
||||
bound_executed_tool_calls_for_prompt,
|
||||
bound_executed_tool_calls_for_prompt_prioritizing_recent,
|
||||
@@ -677,7 +705,7 @@ fn result_metadata_shedding_keeps_smaller_results_within_one_output() {
|
||||
serde_json::json!({ "value": "é".repeat(10_000) }),
|
||||
],
|
||||
1,
|
||||
ToolResultMetadata::omitted_due_to_size_limit(),
|
||||
None,
|
||||
),
|
||||
(
|
||||
[
|
||||
@@ -686,7 +714,7 @@ fn result_metadata_shedding_keeps_smaller_results_within_one_output() {
|
||||
serde_json::json!({ "value": "\0".repeat(4_500) }),
|
||||
],
|
||||
2,
|
||||
ToolResultMetadata::omitted_due_to_size_limit(),
|
||||
None,
|
||||
),
|
||||
(
|
||||
[
|
||||
@@ -698,7 +726,7 @@ fn result_metadata_shedding_keeps_smaller_results_within_one_output() {
|
||||
}),
|
||||
],
|
||||
2,
|
||||
ToolResultMetadata::new(&resource_only),
|
||||
Some(ToolResultMetadata::new(&resource_only)),
|
||||
),
|
||||
] {
|
||||
let mut metadata = metadata.to_vec();
|
||||
@@ -706,6 +734,7 @@ fn result_metadata_shedding_keeps_smaller_results_within_one_output() {
|
||||
serde_json::json!({ "padding": "x".repeat(17_000) });
|
||||
6
|
||||
]);
|
||||
let name_padding = (MAX_EXECUTED_TOOL_CALL_METADATA_BYTES - 128 * 1024) / metadata.len();
|
||||
let mut item = output("exec");
|
||||
item.append_executed_tool_calls(
|
||||
metadata
|
||||
@@ -713,7 +742,7 @@ fn result_metadata_shedding_keeps_smaller_results_within_one_output() {
|
||||
.enumerate()
|
||||
.map(|(index, metadata)| {
|
||||
let mut call = ExecutedToolCall::new(
|
||||
format!("tool_{index}"),
|
||||
format!("tool_{index}{}", "x".repeat(name_padding)),
|
||||
serde_json::json!({ "argument": index }),
|
||||
);
|
||||
call.set_tool_result_sources(ToolResultSources::new(vec![ToolResultSource {
|
||||
@@ -728,6 +757,11 @@ fn result_metadata_shedding_keeps_smaller_results_within_one_output() {
|
||||
item.set_tool_call_cell_id("exec");
|
||||
item.mark_tool_calls_complete();
|
||||
assert!(executed_tool_call_metadata_bytes(&item) > MAX_EXECUTED_TOOL_CALL_METADATA_BYTES);
|
||||
let replacement = replacement.unwrap_or_else(|| {
|
||||
ToolResultMetadata::omitted_due_to_size_limit(
|
||||
executed_tool_call_metadata_bytes(&item) - MAX_EXECUTED_TOOL_CALL_METADATA_BYTES,
|
||||
)
|
||||
});
|
||||
let mut expected = item.clone();
|
||||
expected
|
||||
.ensure_tool_call_metadata()
|
||||
@@ -762,7 +796,7 @@ fn result_metadata_shedding_removes_only_markers_needed_to_fit() {
|
||||
id: "R1".to_string(),
|
||||
}]));
|
||||
let mut calls = vec![call; 20];
|
||||
let omitted = ToolResultMetadata::omitted_due_to_size_limit();
|
||||
let omitted = ToolResultMetadata::omitted_due_to_size_limit(/*overage_bytes*/ 1);
|
||||
let marker_bytes = serde_json::to_vec(&omitted).unwrap().len();
|
||||
// A real object tied with a marker must survive even though it is older.
|
||||
let equal_sized = ToolResultMetadata::new(&serde_json::json!({
|
||||
@@ -790,9 +824,10 @@ fn result_metadata_shedding_removes_only_markers_needed_to_fit() {
|
||||
.unwrap();
|
||||
let call_count = calls.len();
|
||||
for (index, call) in calls.iter_mut().enumerate() {
|
||||
call.arguments = ExecutedToolCallArguments::Raw(serde_json::json!("x".repeat(
|
||||
remaining_bytes / call_count + usize::from(index < remaining_bytes % call_count)
|
||||
)));
|
||||
// Pad names so the per-argument limit cannot preempt result-metadata shedding.
|
||||
call.name.push_str(&"x".repeat(
|
||||
remaining_bytes / call_count + usize::from(index < remaining_bytes % call_count),
|
||||
));
|
||||
}
|
||||
assert_eq!(
|
||||
executed_tool_call_metadata_bytes(&item),
|
||||
@@ -806,8 +841,9 @@ fn result_metadata_shedding_removes_only_markers_needed_to_fit() {
|
||||
.as_mut()
|
||||
.unwrap()[1]
|
||||
.set_tool_result_metadata(ToolResultMetadata::default());
|
||||
// First downgrade the larger, newer result, then remove the older marker. The
|
||||
// removal order must use the updated sizes rather than the original raw size.
|
||||
let tied_markers = item.clone();
|
||||
// The new marker has a larger overage value and therefore more bytes. Remove
|
||||
// that marker first, while tied smaller markers still precede provider data.
|
||||
item.ensure_tool_call_metadata()
|
||||
.unwrap()
|
||||
.executed_tool_calls
|
||||
@@ -816,18 +852,29 @@ fn result_metadata_shedding_removes_only_markers_needed_to_fit() {
|
||||
.set_tool_result_metadata(ToolResultMetadata::new(&serde_json::json!({
|
||||
"large": "x".repeat(4 * 1024),
|
||||
})));
|
||||
for bound in [
|
||||
bound_executed_tool_calls_for_prompt,
|
||||
bound_executed_tool_calls_for_prompt_prioritizing_recent,
|
||||
] {
|
||||
let mut bounded = item.clone();
|
||||
bound(std::slice::from_mut(&mut bounded));
|
||||
assert_eq!(bounded, expected);
|
||||
assert!(
|
||||
executed_tool_call_metadata_bytes(&bounded) <= MAX_EXECUTED_TOOL_CALL_METADATA_BYTES
|
||||
);
|
||||
bound(std::slice::from_mut(&mut bounded));
|
||||
assert_eq!(bounded, expected);
|
||||
let mut larger_marker_expected = item.clone();
|
||||
larger_marker_expected
|
||||
.ensure_tool_call_metadata()
|
||||
.unwrap()
|
||||
.executed_tool_calls
|
||||
.as_mut()
|
||||
.unwrap()[3]
|
||||
.set_tool_result_metadata(ToolResultMetadata::default());
|
||||
for (item, expected) in [(tied_markers, expected), (item, larger_marker_expected)] {
|
||||
for bound in [
|
||||
bound_executed_tool_calls_for_prompt,
|
||||
bound_executed_tool_calls_for_prompt_prioritizing_recent,
|
||||
] {
|
||||
let mut bounded = item.clone();
|
||||
bound(std::slice::from_mut(&mut bounded));
|
||||
assert_eq!(bounded, expected);
|
||||
assert!(
|
||||
executed_tool_call_metadata_bytes(&bounded)
|
||||
<= MAX_EXECUTED_TOOL_CALL_METADATA_BYTES
|
||||
);
|
||||
bound(std::slice::from_mut(&mut bounded));
|
||||
assert_eq!(bounded, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -855,10 +902,10 @@ fn result_metadata_markers_do_not_displace_existing_evidence() {
|
||||
.unwrap();
|
||||
let call_count = calls.len();
|
||||
for (index, call) in calls.iter_mut().enumerate() {
|
||||
// Fill the budget exactly, with every argument still below its individual limit.
|
||||
call.arguments = ExecutedToolCallArguments::Raw(serde_json::json!("x".repeat(
|
||||
remaining_bytes / call_count + usize::from(index < remaining_bytes % call_count)
|
||||
)));
|
||||
// Fill the budget exactly without increasing arguments past their individual limit.
|
||||
call.name.push_str(&"x".repeat(
|
||||
remaining_bytes / call_count + usize::from(index < remaining_bytes % call_count),
|
||||
));
|
||||
}
|
||||
assert_eq!(
|
||||
executed_tool_call_metadata_bytes(&item),
|
||||
|
||||
Reference in New Issue
Block a user