mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
standalone websearch extension (#23823)
## Summary Add the extension-backed standalone `web.run` tool so Codex can call the standalone search endpoint through the `codex-api` search client and return its encrypted output to Responses. - gate the new tool behind `standalone_web_search` - install the extension in the app-server thread registry and hide hosted `web_search` when standalone search is enabled for OpenAI providers so the two paths stay mutually exclusive - build search context from persisted history using a small tail heuristic: previous user message, assistant text between the last two user turns capped at about 1k tokens, and current user message ## Test Plan - `cargo test -p codex-web-search-extension` - `cargo test -p codex-api` - `cargo test -p codex-core hosted_tools_follow_provider_auth_model_and_config_gates`
This commit is contained in:
committed by
GitHub
Unverified
parent
aad59a0916
commit
a22706dfae
@@ -238,11 +238,12 @@ fn spec_for_model_request(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn hosted_model_tool_specs(turn_context: &TurnContext) -> Vec<ToolSpec> {
|
||||
fn hosted_model_tool_specs(context: &CoreToolPlanContext<'_>) -> Vec<ToolSpec> {
|
||||
let turn_context = context.turn_context;
|
||||
let mut specs = Vec::new();
|
||||
let provider_capabilities = turn_context.provider.capabilities();
|
||||
let web_search_mode = provider_capabilities
|
||||
.web_search
|
||||
let web_search_mode = (!standalone_web_run_available(context.extension_tool_executors)
|
||||
&& provider_capabilities.web_search)
|
||||
.then_some(turn_context.config.web_search_mode.value());
|
||||
let web_search_config = if provider_capabilities.web_search {
|
||||
turn_context.config.web_search_config.as_ref()
|
||||
@@ -504,11 +505,20 @@ fn add_tool_sources(context: &CoreToolPlanContext<'_>, planned_tools: &mut Plann
|
||||
add_mcp_runtime_tools(context, planned_tools);
|
||||
add_dynamic_tools(context, planned_tools);
|
||||
add_extension_tools(context, planned_tools);
|
||||
for spec in hosted_model_tool_specs(context.turn_context) {
|
||||
for spec in hosted_model_tool_specs(context) {
|
||||
planned_tools.add_hosted_spec(spec);
|
||||
}
|
||||
}
|
||||
|
||||
fn standalone_web_run_available(
|
||||
extension_tools: &[Arc<dyn ToolExecutor<ExtensionToolCall>>],
|
||||
) -> bool {
|
||||
let web_run = ToolName::namespaced("web", "run");
|
||||
extension_tools
|
||||
.iter()
|
||||
.any(|executor| executor.tool_name() == web_run)
|
||||
}
|
||||
|
||||
fn add_shell_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) {
|
||||
let turn_context = context.turn_context;
|
||||
let features = turn_context.features.get();
|
||||
|
||||
@@ -20,8 +20,11 @@ use codex_tools::DiscoverablePluginInfo;
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::ResponsesApiNamespaceTool;
|
||||
use codex_tools::ResponsesApiTool;
|
||||
use codex_tools::ToolCall as ExtensionToolCall;
|
||||
use codex_tools::ToolExecutor;
|
||||
use codex_tools::ToolExposure;
|
||||
use codex_tools::ToolName;
|
||||
use codex_tools::ToolOutput;
|
||||
use codex_tools::ToolSpec;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
@@ -37,6 +40,7 @@ struct ToolPlanInputs {
|
||||
mcp_tools: Option<Vec<ToolInfo>>,
|
||||
deferred_mcp_tools: Option<Vec<ToolInfo>>,
|
||||
discoverable_tools: Option<Vec<DiscoverableTool>>,
|
||||
extension_tool_executors: Vec<Arc<dyn ToolExecutor<ExtensionToolCall>>>,
|
||||
dynamic_tools: Vec<DynamicToolSpec>,
|
||||
}
|
||||
|
||||
@@ -176,7 +180,7 @@ async fn probe_with(
|
||||
mcp_tools: inputs.mcp_tools,
|
||||
deferred_mcp_tools: inputs.deferred_mcp_tools,
|
||||
discoverable_tools: inputs.discoverable_tools,
|
||||
extension_tool_executors: Vec::new(),
|
||||
extension_tool_executors: inputs.extension_tool_executors,
|
||||
dynamic_tools: inputs.dynamic_tools.as_slice(),
|
||||
},
|
||||
);
|
||||
@@ -253,6 +257,37 @@ fn use_bedrock_provider(turn: &mut TurnContext) {
|
||||
turn.provider = create_model_provider(provider_info, turn.auth_manager.clone());
|
||||
}
|
||||
|
||||
struct WebRunExtensionTool;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl ToolExecutor<ExtensionToolCall> for WebRunExtensionTool {
|
||||
fn tool_name(&self) -> ToolName {
|
||||
ToolName::namespaced("web", "run")
|
||||
}
|
||||
|
||||
fn spec(&self) -> ToolSpec {
|
||||
ToolSpec::Namespace(codex_tools::ResponsesApiNamespace {
|
||||
name: "web".to_string(),
|
||||
description: "Test web namespace.".to_string(),
|
||||
tools: vec![ResponsesApiNamespaceTool::Function(ResponsesApiTool {
|
||||
name: "run".to_string(),
|
||||
description: "Test standalone web search tool.".to_string(),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
parameters: codex_tools::JsonSchema::default(),
|
||||
output_schema: None,
|
||||
})],
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
_call: ExtensionToolCall,
|
||||
) -> Result<Box<dyn ToolOutput>, codex_tools::FunctionCallError> {
|
||||
Ok(Box::new(codex_tools::JsonToolOutput::new(json!({}))))
|
||||
}
|
||||
}
|
||||
|
||||
fn duplicate_primary_environment(turn: &mut TurnContext) {
|
||||
let mut second_environment = turn.environments.turn_environments[0].clone();
|
||||
second_environment.environment_id = "secondary".to_string();
|
||||
@@ -947,6 +982,26 @@ async fn hosted_tools_follow_provider_auth_model_and_config_gates() {
|
||||
}
|
||||
);
|
||||
|
||||
let standalone_web_search_without_web_run = probe(|turn| {
|
||||
set_feature(turn, Feature::StandaloneWebSearch, /*enabled*/ true);
|
||||
set_web_search_mode(turn, WebSearchMode::Live);
|
||||
})
|
||||
.await;
|
||||
standalone_web_search_without_web_run.assert_visible_contains(&["web_search"]);
|
||||
|
||||
let standalone_web_search = probe_with(
|
||||
|turn| {
|
||||
set_feature(turn, Feature::StandaloneWebSearch, /*enabled*/ true);
|
||||
set_web_search_mode(turn, WebSearchMode::Live);
|
||||
},
|
||||
ToolPlanInputs {
|
||||
extension_tool_executors: vec![Arc::new(WebRunExtensionTool)],
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await;
|
||||
standalone_web_search.assert_visible_lacks(&["web_search"]);
|
||||
|
||||
let unsupported_provider = probe(|turn| {
|
||||
set_web_search_mode(turn, WebSearchMode::Live);
|
||||
use_bedrock_provider(turn);
|
||||
|
||||
Reference in New Issue
Block a user