diff --git a/rustcode/engine/src/network.rs b/rustcode/engine/src/network.rs index bcf6f22f5..5a4043a52 100644 --- a/rustcode/engine/src/network.rs +++ b/rustcode/engine/src/network.rs @@ -198,6 +198,25 @@ pub(crate) const CHATGPT_TURN_STATE_HEADER: &str = "x-codex-turn-state"; /// synchronous MCP/filesystem work. Cache metadata is snapshotted before the /// computation and sticky names are committed only if that snapshot is still /// current when the computation finishes. +/// The root agent's tool policy for a request. The system prompt and the +/// provider request both build it here, so a tool the prompt describes +/// (`localhost_preview`) is always in the request's `tools` too. +pub(crate) fn root_schema_policy( + s: &AppState, + compact_tool_prompt: bool, +) -> crate::tools::ToolSchemaPolicy { + let policy = crate::tools::ToolSchemaPolicy::root_for_mode_with_compact_prompt( + s.delegation_active, + s.agent_mode, + compact_tool_prompt, + ); + if s.preview_client.is_some() && !compact_tool_prompt { + policy.with_preview_tool() + } else { + policy + } +} + pub(crate) async fn prepare_native_tool_schemas( state: &Arc>, policy: crate::tools::ToolSchemaPolicy, @@ -1558,17 +1577,10 @@ pub(crate) async fn prepare_turn_request_with_checkpoint_and_prefix_cache( && !matches!(protocol, crate::config::ToolProtocol::ApiNative); let session_title_tool_available = s.session_title_tool_available && agent_mode != crate::config::AgentMode::Plan; - let mut schema_policy = crate::tools::ToolSchemaPolicy::root_for_mode_with_compact_prompt( - delegation_active, - agent_mode, - compact_tool_prompt, - ); + let mut schema_policy = root_schema_policy(&s, compact_tool_prompt); if session_title_tool_available { schema_policy = schema_policy.with_session_title_tool(); } - if s.preview_client.is_some() && !compact_tool_prompt { - schema_policy = schema_policy.with_preview_tool(); - } // The session-title tool only changes the native `tools` field; its // guidance goes in the context tail so the system prompt (and the // cached prefix) stays identical between the first and later turns. diff --git a/rustcode/engine/src/network/turn/request.rs b/rustcode/engine/src/network/turn/request.rs index ebd7e8846..11d490b9a 100644 --- a/rustcode/engine/src/network/turn/request.rs +++ b/rustcode/engine/src/network/turn/request.rs @@ -586,11 +586,7 @@ pub(super) async fn collect_round( ( s.api_base_url.clone(), s.model_name.clone(), - crate::tools::ToolSchemaPolicy::root_for_mode_with_compact_prompt( - s.delegation_active, - s.agent_mode, - compact_tool_prompt, - ), + crate::network::root_schema_policy(&s, compact_tool_prompt), s.active_session_id.clone(), ) }; diff --git a/rustcode/engine/src/preview.rs b/rustcode/engine/src/preview.rs index d2ccbfc5c..61e5f8657 100644 --- a/rustcode/engine/src/preview.rs +++ b/rustcode/engine/src/preview.rs @@ -754,6 +754,28 @@ mod tests { }) } + /// The prompt's hint and the provider request's `tools` come from one + /// policy: a session with a preview offers the tool it is told about. + #[test] + fn request_policy_offers_preview_with_a_client() { + let mut state = crate::app::AppState::new(); + assert!(!crate::network::root_schema_policy(&state, false).include_preview_tool); + state.preview_client = Some(answering(Value::Null) as Arc); + let policy = crate::network::root_schema_policy(&state, false); + assert!(policy.include_preview_tool); + let mut prompt = String::new(); + crate::tools::schema::append_preview_hint(&mut prompt, policy); + assert!(prompt.contains(PREVIEW_TOOL_NAME)); + let tools = crate::tools::native_tools_schema_for_context(policy, &[]).0; + assert!( + tools + .iter() + .any(|tool| tool.to_string().contains(PREVIEW_TOOL_NAME)) + ); + // A compact prompt describes no preview, so offers none. + assert!(!crate::network::root_schema_policy(&state, true).include_preview_tool); + } + fn no_save(_: &[u8], _: &str) -> std::io::Result { panic!("nothing should be saved") }