diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 2028c4240e..9282c6079d 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -1054,6 +1054,7 @@ async fn submission_loop( model, effort, summary, + include_web_search_request, } => { // Recalculate the persistent turn context with provided overrides. let prev = Arc::clone(&turn_context); @@ -1097,13 +1098,16 @@ async fn submission_loop( .unwrap_or(prev.sandbox_policy.clone()); let new_cwd = cwd.clone().unwrap_or_else(|| prev.cwd.clone()); + let effective_web_search = + include_web_search_request.unwrap_or(prev.tools_config.web_search_request); + let tools_config = ToolsConfig::new(&ToolsConfigParams { model_family: &effective_family, approval_policy: new_approval_policy, sandbox_policy: new_sandbox_policy.clone(), include_plan_tool: config.include_plan_tool, include_apply_patch_tool: config.include_apply_patch_tool, - include_web_search_request: config.tools_web_search_request, + include_web_search_request: effective_web_search, use_streamable_shell_tool: config.use_experimental_streamable_shell_tool, include_view_image_tool: config.include_view_image_tool, }); diff --git a/codex-rs/core/tests/suite/prompt_caching.rs b/codex-rs/core/tests/suite/prompt_caching.rs index 11c6ea9acf..4ecb1a7111 100644 --- a/codex-rs/core/tests/suite/prompt_caching.rs +++ b/codex-rs/core/tests/suite/prompt_caching.rs @@ -393,6 +393,7 @@ async fn overrides_turn_context_but_keeps_cached_prefix_and_key_constant() { model: Some("o3".to_string()), effort: Some(ReasoningEffort::High), summary: Some(ReasoningSummary::Detailed), + include_web_search_request: None, }) .await .unwrap(); diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs index 2308b7d212..6058819a3e 100644 --- a/codex-rs/protocol/src/protocol.rs +++ b/codex-rs/protocol/src/protocol.rs @@ -116,6 +116,12 @@ pub enum Op { /// Updated reasoning summary preference (honored only for reasoning-capable models). #[serde(skip_serializing_if = "Option::is_none")] summary: Option, + + /// Toggle availability of the `web_search` tool in subsequent turns. + /// When `Some(true)`, include the web search tool; when `Some(false)`, + /// exclude it. When `None`, preserve the previous session state. + #[serde(skip_serializing_if = "Option::is_none")] + include_web_search_request: Option, }, /// Approve a command execution diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 40a7f20886..a140826954 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -308,6 +308,9 @@ impl App { AppEvent::UpdateSandboxPolicy(policy) => { self.chat_widget.set_sandbox_policy(policy); } + AppEvent::UpdateWebSearchRequest(enabled) => { + self.chat_widget.set_web_search_enabled(enabled); + } } Ok(true) } diff --git a/codex-rs/tui/src/app_event.rs b/codex-rs/tui/src/app_event.rs index 1896f0a46e..a680837031 100644 --- a/codex-rs/tui/src/app_event.rs +++ b/codex-rs/tui/src/app_event.rs @@ -57,6 +57,9 @@ pub(crate) enum AppEvent { /// Update the current sandbox policy in the running app and widget. UpdateSandboxPolicy(SandboxPolicy), + /// Update whether the web_search tool is available for this session. + UpdateWebSearchRequest(bool), + /// Forwarded conversation history snapshot from the current conversation. ConversationHistory(ConversationHistoryResponseEvent), } diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 7acb802c73..d6875507ac 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -832,6 +832,9 @@ impl ChatWidget { SlashCommand::Approvals => { self.open_approvals_popup(); } + SlashCommand::Search => { + self.open_search_popup(); + } SlashCommand::Quit => { self.app_event_tx.send(AppEvent::ExitRequest); } @@ -1174,6 +1177,7 @@ impl ChatWidget { model: Some(model_slug.clone()), effort: Some(effort), summary: None, + include_web_search_request: None, })); tx.send(AppEvent::UpdateModel(model_slug.clone())); tx.send(AppEvent::UpdateReasoningEffort(effort)); @@ -1222,6 +1226,7 @@ impl ChatWidget { model: None, effort: None, summary: None, + include_web_search_request: None, })); tx.send(AppEvent::UpdateAskForApprovalPolicy(approval)); tx.send(AppEvent::UpdateSandboxPolicy(sandbox.clone())); @@ -1242,6 +1247,47 @@ impl ChatWidget { ); } + pub(crate) fn open_search_popup(&mut self) { + let cur = self.config.tools_web_search_request; + let mut items: Vec = Vec::new(); + + for (name, enabled, desc) in [ + ("Search Disabled", false, None), + ( + "Search Enabled for Session", + true, + Some("Makes the web_search tool available to the model".to_string()), + ), + ] { + let is_current = cur == enabled; + let actions: Vec = vec![Box::new(move |tx| { + tx.send(AppEvent::CodexOp(Op::OverrideTurnContext { + cwd: None, + approval_policy: None, + sandbox_policy: None, + model: None, + effort: None, + summary: None, + include_web_search_request: Some(enabled), + })); + tx.send(AppEvent::UpdateWebSearchRequest(enabled)); + })]; + items.push(SelectionItem { + name: name.to_string(), + description: desc.clone(), + is_current, + actions, + }); + } + + self.bottom_pane.show_selection_view( + "Select Web Search".to_string(), + Some("Toggle web search tool availability for this session".to_string()), + Some("Press Enter to confirm or Esc to go back".to_string()), + items, + ); + } + /// Set the approval policy in the widget's config copy. pub(crate) fn set_approval_policy(&mut self, policy: AskForApproval) { self.config.approval_policy = policy; @@ -1262,6 +1308,11 @@ impl ChatWidget { self.config.model = model; } + /// Set web search tool availability in the widget's config copy. + pub(crate) fn set_web_search_enabled(&mut self, enabled: bool) { + self.config.tools_web_search_request = enabled; + } + pub(crate) fn add_mcp_output(&mut self) { if self.config.mcp_servers.is_empty() { self.add_to_history(history_cell::empty_mcp_output()); diff --git a/codex-rs/tui/src/slash_command.rs b/codex-rs/tui/src/slash_command.rs index 3268a92a2d..8f89f2fa52 100644 --- a/codex-rs/tui/src/slash_command.rs +++ b/codex-rs/tui/src/slash_command.rs @@ -14,6 +14,7 @@ pub enum SlashCommand { // more frequently used commands should be listed first. Model, Approvals, + Search, New, Init, Compact, @@ -40,6 +41,7 @@ impl SlashCommand { SlashCommand::Status => "show current session configuration and token usage", SlashCommand::Model => "choose what model and reasoning effort to use", SlashCommand::Approvals => "choose what Codex can do without approval", + SlashCommand::Search => "enable or disable web search for this session", SlashCommand::Mcp => "list configured MCP tools", SlashCommand::Logout => "log out of Codex", #[cfg(debug_assertions)] @@ -61,6 +63,7 @@ impl SlashCommand { | SlashCommand::Compact | SlashCommand::Model | SlashCommand::Approvals + | SlashCommand::Search | SlashCommand::Logout => false, SlashCommand::Diff | SlashCommand::Mention