diff --git a/codex-rs/core/src/agent/control_tests.rs b/codex-rs/core/src/agent/control_tests.rs index ffc7e11f7a..4e2f8034fe 100644 --- a/codex-rs/core/src/agent/control_tests.rs +++ b/codex-rs/core/src/agent/control_tests.rs @@ -2880,7 +2880,7 @@ async fn spawn_agent_can_fork_parent_thread_history_with_sanitized_items() { let tool_declarations = ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: vec![serde_json::json!({"type": "function", "name": "parent_tool"})], + tools: Arc::from([serde_json::json!({"type": "function", "name": "parent_tool"})]), }; let tool_state = serde_json::json!({"top_level_tools": {"function:parent_tool": "hash"}}) .as_object() @@ -3286,7 +3286,7 @@ async fn spawn_agent_fork_strips_parent_usage_hints_from_compacted_history(marke let tool_declarations = ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: vec![serde_json::json!({"type": "function", "name": "compacted_tool"})], + tools: Arc::from([serde_json::json!({"type": "function", "name": "compacted_tool"})]), }; let tools_update = ContextualUserFragment::into(IncrementalToolsHint( codex_prompts::ResolvedIncrementalToolMessages::default().tool_update_hint, @@ -3865,7 +3865,7 @@ async fn spawn_agent_full_fork_legacy_compaction_rebuilds_child_instructions_onc rollout_items.push(rollout_response_item(ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: vec![serde_json::json!({"type": "function", "name": "parent_tool"})], + tools: Arc::from([serde_json::json!({"type": "function", "name": "parent_tool"})]), })); rollout_items.push(RolloutItem::TurnContext( turn_context.to_turn_context_item(), diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 0bab5f257a..981e015434 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -986,7 +986,7 @@ impl ModelClient { Uuid::new_v5(&prefix_namespace, &serde_json::to_vec(&tools)?), )), role: "developer".to_string(), - tools, + tools: Arc::from(tools), }); } None diff --git a/codex-rs/core/src/context/world_state/top_level_tools.rs b/codex-rs/core/src/context/world_state/top_level_tools.rs index 7fa9275535..4dbab8b181 100644 --- a/codex-rs/core/src/context/world_state/top_level_tools.rs +++ b/codex-rs/core/src/context/world_state/top_level_tools.rs @@ -213,7 +213,7 @@ impl WorldStateSection for TopLevelToolsState { ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools, + tools: Arc::from(tools), }, )); } diff --git a/codex-rs/core/src/context/world_state/top_level_tools_tests.rs b/codex-rs/core/src/context/world_state/top_level_tools_tests.rs index e3e7fb8c4f..4ac92c2603 100644 --- a/codex-rs/core/src/context/world_state/top_level_tools_tests.rs +++ b/codex-rs/core/src/context/world_state/top_level_tools_tests.rs @@ -25,7 +25,7 @@ fn item(tools: Vec) -> ResponseItem { ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools, + tools: Arc::from(tools), } } diff --git a/codex-rs/core/src/context/world_state/world_state_tests.rs b/codex-rs/core/src/context/world_state/world_state_tests.rs index 61739840a3..86a1b00b22 100644 --- a/codex-rs/core/src/context/world_state/world_state_tests.rs +++ b/codex-rs/core/src/context/world_state/world_state_tests.rs @@ -9,6 +9,7 @@ use pretty_assertions::assert_ne; use serde::Deserialize; use serde::Serialize; use serde_json::json; +use std::sync::Arc; #[derive(Clone, Deserialize, Serialize)] struct TestSection { @@ -443,9 +444,9 @@ impl WorldStateSection for MixedOutputSection { content: WorldStateUpdateContent::Item(Box::new(ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: vec![ + tools: Arc::from([ json!({"type": "function", "name": "example", "description": self.0.to_string()}), - ], + ]), })), }, WorldStateUpdate { @@ -457,7 +458,7 @@ impl WorldStateSection for MixedOutputSection { content: WorldStateUpdateContent::Item(Box::new(ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: vec![json!({"type": "function", "name": "context_tool"})], + tools: Arc::from([json!({"type": "function", "name": "context_tool"})]), })), }, WorldStateUpdate::fragment(TestFragment("after tools".to_string())), @@ -481,13 +482,13 @@ fn mixed_output_preserves_order_and_restores_its_snapshot() { ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: vec![json!({"type": "function", "name": "example", "description": "2"})], + tools: Arc::from([json!({"type": "function", "name": "example", "description": "2"})]), }, ContextualUserFragment::into(TestFragment("prefix instructions".to_string())), ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: vec![json!({"type": "function", "name": "context_tool"})], + tools: Arc::from([json!({"type": "function", "name": "context_tool"})]), }, ContextualUserFragment::into(TestFragment("after tools".to_string())), ]; diff --git a/codex-rs/core/src/context_manager/updates_tests.rs b/codex-rs/core/src/context_manager/updates_tests.rs index 0f25f43b49..7f937f7c1d 100644 --- a/codex-rs/core/src/context_manager/updates_tests.rs +++ b/codex-rs/core/src/context_manager/updates_tests.rs @@ -8,6 +8,7 @@ use codex_protocol::models::InternalChatMessageMetadataPassthrough; use codex_protocol::models::input_text; use pretty_assertions::assert_eq; use serde_json::json; +use std::sync::Arc; struct TestFragment { role: &'static str, @@ -76,7 +77,7 @@ fn additional_tools() -> ResponseItem { ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: vec![json!({"type": "function", "name": "lookup", "parameters": {}})], + tools: Arc::from([json!({"type": "function", "name": "lookup", "parameters": {}})]), } } diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 1713ec1a5b..a60ce15c26 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -365,7 +365,7 @@ fn assign_missing_response_item_ids_assigns_additional_tools_ids() { let items = Cow::Owned(vec![ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: Vec::new(), + tools: Arc::from([]), }]); let items = Session::assign_missing_response_item_ids(items); diff --git a/codex-rs/core/src/session/world_state_item_tests.rs b/codex-rs/core/src/session/world_state_item_tests.rs index d253e64543..e2af2169c3 100644 --- a/codex-rs/core/src/session/world_state_item_tests.rs +++ b/codex-rs/core/src/session/world_state_item_tests.rs @@ -77,7 +77,7 @@ fn additional_tools(name: &str) -> ResponseItem { ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: vec![json!({"type": "function", "name": name})], + tools: Arc::from([json!({"type": "function", "name": name})]), } } diff --git a/codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs b/codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs index 1f66eff9cb..da8fc86f94 100644 --- a/codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs +++ b/codex-rs/ext/guardian-v2/src/async_scorer/sampler.rs @@ -189,7 +189,7 @@ impl LunaSampler { ResponseItemEnvelope::new(ResponseItem::AdditionalTools { id: None, role: "developer".to_owned(), - tools: Vec::new(), + tools: Arc::from([]), }), ResponseItemEnvelope::new(ResponseItem::from(request.instructions.clone())), ]; diff --git a/codex-rs/protocol/src/models.rs b/codex-rs/protocol/src/models.rs index a5fa8f4f19..351f45c03c 100644 --- a/codex-rs/protocol/src/models.rs +++ b/codex-rs/protocol/src/models.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::io; use std::num::NonZeroUsize; use std::path::Path; +use std::sync::Arc; use codex_utils_image::PromptImageMode; use codex_utils_image::data_url_from_bytes; @@ -1055,7 +1056,8 @@ pub enum ResponseItem { id: Option, role: String, #[ts(type = "unknown[]")] - tools: Vec, + // Share immutable declarations across history and request copies. + tools: Arc<[serde_json::Value]>, }, Message { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -2807,7 +2809,7 @@ mod tests { let mut additional_tools = ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: Vec::new(), + tools: Arc::from([]), }; additional_tools.set_id(Some(ResponseItemId::with_suffix("at", "test"))); assert_eq!( diff --git a/codex-rs/rollout/src/recorder_tests.rs b/codex-rs/rollout/src/recorder_tests.rs index cb80a56bee..d8916fea98 100644 --- a/codex-rs/rollout/src/recorder_tests.rs +++ b/codex-rs/rollout/src/recorder_tests.rs @@ -638,14 +638,14 @@ async fn recorder_preserves_additional_tools_in_both_history_modes() -> std::io: ResponseItem::AdditionalTools { id: None, role: "developer".to_string(), - tools: vec![serde_json::json!({ + tools: Arc::from([serde_json::json!({ "type": "namespace", "name": "functions", "description": "Available tools.", "tools": [{"type": "function", "name": "lookup", "description": "Look up a value.", "parameters": {"type": "object", "properties": {}}}] - })], + })]), } .into(), );