mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] Use standalone tools for Responses Lite (#26490)
## Summary Responses Lite does not execute hosted Responses tools, so models using it must route web search and image generation through Codex-owned executors & standalone Response's API endpoints. This PR is stacked on #26487. ## Validation - `cargo test -p codex-core responses_lite_ --lib` - `cargo test -p codex-core standalone_executors_remain_hidden_without_flags_or_responses_lite --lib` - `cargo test -p codex-core hosted_tools_follow_provider_auth_model_and_config_gates --lib` - `cargo test -p codex-web-search-extension -p codex-image-generation-extension` - `cargo test -p codex-app-server --test all standalone_` - `cargo fmt --all -- --check`
This commit is contained in:
committed by
GitHub
Unverified
parent
4f655bc3b7
commit
ffe90cb5c3
Generated
+2
-2
@@ -2549,6 +2549,7 @@ dependencies = [
|
||||
"codex-feedback",
|
||||
"codex-git-utils",
|
||||
"codex-hooks",
|
||||
"codex-image-generation-extension",
|
||||
"codex-install-context",
|
||||
"codex-login",
|
||||
"codex-mcp",
|
||||
@@ -2584,6 +2585,7 @@ dependencies = [
|
||||
"codex-utils-pty",
|
||||
"codex-utils-stream-parser",
|
||||
"codex-utils-string",
|
||||
"codex-web-search-extension",
|
||||
"codex-windows-sandbox",
|
||||
"core_test_support",
|
||||
"csv",
|
||||
@@ -3048,7 +3050,6 @@ dependencies = [
|
||||
"codex-api",
|
||||
"codex-core",
|
||||
"codex-extension-api",
|
||||
"codex-features",
|
||||
"codex-login",
|
||||
"codex-model-provider",
|
||||
"codex-model-provider-info",
|
||||
@@ -4208,7 +4209,6 @@ dependencies = [
|
||||
"codex-api",
|
||||
"codex-core",
|
||||
"codex-extension-api",
|
||||
"codex-features",
|
||||
"codex-login",
|
||||
"codex-model-provider",
|
||||
"codex-model-provider-info",
|
||||
|
||||
@@ -133,9 +133,11 @@ codex-shell-escalation = { workspace = true }
|
||||
[dev-dependencies]
|
||||
assert_cmd = { workspace = true }
|
||||
assert_matches = { workspace = true }
|
||||
codex-image-generation-extension = { workspace = true }
|
||||
codex-otel = { workspace = true }
|
||||
codex-test-binary-support = { workspace = true }
|
||||
codex-utils-cargo-bin = { workspace = true }
|
||||
codex-web-search-extension = { workspace = true }
|
||||
core_test_support = { workspace = true }
|
||||
ctor = { workspace = true }
|
||||
insta = { workspace = true }
|
||||
|
||||
@@ -56,6 +56,7 @@ use crate::tools::router::ToolRouterParams;
|
||||
use codex_features::Feature;
|
||||
use codex_login::AuthManager;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::dynamic_tools::DynamicToolSpec;
|
||||
use codex_protocol::openai_models::ConfigShellToolType;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
@@ -246,22 +247,31 @@ fn spec_for_model_request(
|
||||
|
||||
fn hosted_model_tool_specs(context: &CoreToolPlanContext<'_>) -> Vec<ToolSpec> {
|
||||
let turn_context = context.turn_context;
|
||||
// Responses Lite accepts schemas for client-executed tools, not hosted Responses tools.
|
||||
if turn_context.model_info.use_responses_lite {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut specs = Vec::new();
|
||||
let provider_capabilities = turn_context.provider.capabilities();
|
||||
let web_search_mode = (!standalone_web_run_available(context.extension_tool_executors)
|
||||
&& provider_capabilities.web_search)
|
||||
let standalone_web_search_available = standalone_web_search_enabled(turn_context)
|
||||
&& context
|
||||
.extension_tool_executors
|
||||
.iter()
|
||||
.any(|executor| executor.tool_name() == ToolName::namespaced("web", "run"));
|
||||
// `Some(Cached/Live/Disabled)` are the options for mode when standalone search is unavailable
|
||||
// and the provider supports hosted search. `None` prevents emitting a hosted search tool.
|
||||
let web_search_mode = (!standalone_web_search_available
|
||||
&& turn_context.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()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(web_search_tool) = create_web_search_tool(WebSearchToolOptions {
|
||||
let web_search_config = web_search_mode
|
||||
.as_ref()
|
||||
.and(turn_context.config.web_search_config.as_ref());
|
||||
if let Some(hosted_web_search_tool) = create_web_search_tool(WebSearchToolOptions {
|
||||
web_search_mode,
|
||||
web_search_config,
|
||||
web_search_tool_type: turn_context.model_info.web_search_tool_type,
|
||||
}) {
|
||||
specs.push(web_search_tool);
|
||||
specs.push(hosted_web_search_tool);
|
||||
}
|
||||
// TODO: Remove hosted image generation once the standalone extension is ready.
|
||||
if image_generation_tool_enabled(turn_context)
|
||||
@@ -336,9 +346,15 @@ fn image_generation_runtime_enabled(turn_context: &TurnContext) -> bool {
|
||||
}
|
||||
|
||||
fn standalone_image_generation_model_visible(turn_context: &TurnContext) -> bool {
|
||||
image_generation_runtime_enabled(turn_context)
|
||||
&& turn_context.features.get().enabled(Feature::ImageGenExt)
|
||||
&& namespace_tools_enabled(turn_context)
|
||||
if !image_generation_runtime_enabled(turn_context) || !namespace_tools_enabled(turn_context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if turn_context.model_info.use_responses_lite {
|
||||
return true;
|
||||
}
|
||||
|
||||
turn_context.features.get().enabled(Feature::ImageGenExt)
|
||||
}
|
||||
|
||||
fn standalone_image_generation_available(
|
||||
@@ -554,13 +570,13 @@ fn add_tool_sources(context: &CoreToolPlanContext<'_>, planned_tools: &mut Plann
|
||||
}
|
||||
}
|
||||
|
||||
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 standalone_web_search_enabled(turn_context: &TurnContext) -> bool {
|
||||
namespace_tools_enabled(turn_context)
|
||||
&& (turn_context.model_info.use_responses_lite
|
||||
|| turn_context
|
||||
.features
|
||||
.get()
|
||||
.enabled(Feature::StandaloneWebSearch))
|
||||
}
|
||||
|
||||
fn add_shell_tools(context: &CoreToolPlanContext<'_>, planned_tools: &mut PlannedTools) {
|
||||
@@ -883,8 +899,16 @@ fn append_extension_tool_executors(
|
||||
reserved_tool_names.insert(ToolName::plain(TOOL_SEARCH_TOOL_NAME));
|
||||
}
|
||||
|
||||
let standalone_web_search_enabled = standalone_web_search_enabled(turn_context);
|
||||
let web_search_mode_on = turn_context.config.web_search_mode.value() != WebSearchMode::Disabled;
|
||||
|
||||
for executor in executors.iter().cloned() {
|
||||
let tool_name = executor.tool_name();
|
||||
if tool_name == ToolName::namespaced("web", "run")
|
||||
&& (!standalone_web_search_enabled || !web_search_mode_on)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if tool_name == ToolName::namespaced(IMAGE_GEN_NAMESPACE, IMAGEGEN_TOOL_NAME)
|
||||
&& !standalone_image_generation_model_visible(turn_context)
|
||||
{
|
||||
|
||||
@@ -23,12 +23,14 @@ use codex_core::thread_store_from_config;
|
||||
use codex_exec_server::CreateDirectoryOptions;
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_exec_server::RemoveOptions;
|
||||
use codex_extension_api::ExtensionRegistry;
|
||||
use codex_extension_api::empty_extension_registry;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_model_provider_info::built_in_model_providers;
|
||||
use codex_models_manager::bundled_models_response;
|
||||
use codex_protocol::models::PermissionProfile;
|
||||
use codex_protocol::openai_models::ModelInfo;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
use codex_protocol::protocol::AskForApproval;
|
||||
use codex_protocol::protocol::EventMsg;
|
||||
@@ -216,6 +218,7 @@ pub struct TestCodexBuilder {
|
||||
cloud_config_bundle: Option<CloudConfigBundleLoader>,
|
||||
user_shell_override: Option<Shell>,
|
||||
exec_server_url: Option<String>,
|
||||
extensions: Arc<ExtensionRegistry<Config>>,
|
||||
}
|
||||
|
||||
impl TestCodexBuilder {
|
||||
@@ -239,6 +242,26 @@ impl TestCodexBuilder {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_model_info_override<T>(self, model: &str, override_model_info: T) -> Self
|
||||
where
|
||||
T: FnOnce(&mut ModelInfo) + Send + 'static,
|
||||
{
|
||||
let model = model.to_string();
|
||||
self.with_config(move |config| {
|
||||
let model_catalog = config.model_catalog.get_or_insert_with(|| {
|
||||
bundled_models_response()
|
||||
.unwrap_or_else(|err| panic!("bundled models.json should parse: {err}"))
|
||||
});
|
||||
let model_info = model_catalog
|
||||
.models
|
||||
.iter_mut()
|
||||
.find(|model_info| model_info.slug == model)
|
||||
.unwrap_or_else(|| panic!("{model} should exist in the configured model catalog"));
|
||||
override_model_info(model_info);
|
||||
config.model = Some(model);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_pre_build_hook<F>(mut self, hook: F) -> Self
|
||||
where
|
||||
F: FnOnce(&Path) + Send + 'static,
|
||||
@@ -280,6 +303,11 @@ impl TestCodexBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_extensions(mut self, extensions: Arc<ExtensionRegistry<Config>>) -> Self {
|
||||
self.extensions = extensions;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_windows_cmd_shell(self) -> Self {
|
||||
if cfg!(windows) {
|
||||
self.with_user_shell(get_shell_by_model_provided_path(&PathBuf::from("cmd.exe")))
|
||||
@@ -473,7 +501,7 @@ impl TestCodexBuilder {
|
||||
codex_core::test_support::auth_manager_from_auth(auth.clone()),
|
||||
SessionSource::Exec,
|
||||
Arc::clone(&environment_manager),
|
||||
empty_extension_registry(),
|
||||
Arc::clone(&self.extensions),
|
||||
/*analytics_events_client*/ None,
|
||||
thread_store,
|
||||
state_db.clone(),
|
||||
@@ -1041,6 +1069,7 @@ pub fn test_codex() -> TestCodexBuilder {
|
||||
cloud_config_bundle: None,
|
||||
user_shell_override: None,
|
||||
exec_server_url: None,
|
||||
extensions: empty_extension_registry(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1894,20 +1894,10 @@ async fn responses_lite_sets_all_turns_context_and_disables_parallel_tool_calls(
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut model_catalog = bundled_models_response()
|
||||
.unwrap_or_else(|err| panic!("bundled models.json should parse: {err}"));
|
||||
let model = model_catalog
|
||||
.models
|
||||
.iter_mut()
|
||||
.find(|model| model.slug == "gpt-5.4")
|
||||
.expect("gpt-5.4 exists in bundled models.json");
|
||||
model.use_responses_lite = true;
|
||||
model.supports_parallel_tool_calls = true;
|
||||
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_model("gpt-5.4")
|
||||
.with_config(move |config| {
|
||||
config.model_catalog = Some(model_catalog);
|
||||
.with_model_info_override("gpt-5.4", |model_info| {
|
||||
model_info.use_responses_lite = true;
|
||||
model_info.supports_parallel_tool_calls = true;
|
||||
})
|
||||
.build(&server)
|
||||
.await?;
|
||||
|
||||
@@ -91,6 +91,7 @@ mod request_permissions_tool;
|
||||
mod request_plugin_install;
|
||||
mod request_user_input;
|
||||
mod responses_api_proxy_headers;
|
||||
mod responses_lite;
|
||||
mod resume;
|
||||
mod resume_warning;
|
||||
mod review;
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use codex_core::config::Config;
|
||||
use codex_extension_api::ExtensionRegistry;
|
||||
use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_features::Feature;
|
||||
use codex_image_generation_extension::install as install_image_generation_extension;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_protocol::config_types::WebSearchMode;
|
||||
use codex_protocol::openai_models::InputModality;
|
||||
use codex_web_search_extension::install as install_web_search_extension;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::skip_if_no_network;
|
||||
use core_test_support::test_codex::test_codex;
|
||||
use serde_json::Value;
|
||||
|
||||
fn responses_extensions(auth: &CodexAuth) -> Arc<ExtensionRegistry<Config>> {
|
||||
let auth_manager = codex_core::test_support::auth_manager_from_auth(auth.clone());
|
||||
let mut extension_builder = ExtensionRegistryBuilder::<Config>::new();
|
||||
install_web_search_extension(&mut extension_builder, Arc::clone(&auth_manager));
|
||||
install_image_generation_extension(&mut extension_builder, auth_manager);
|
||||
Arc::new(extension_builder.build())
|
||||
}
|
||||
|
||||
fn configure_responses_tools(config: &mut Config) {
|
||||
assert!(config.web_search_mode.set(WebSearchMode::Live).is_ok());
|
||||
assert!(
|
||||
config
|
||||
.features
|
||||
.disable(Feature::StandaloneWebSearch)
|
||||
.is_ok()
|
||||
);
|
||||
assert!(config.features.enable(Feature::ImageGeneration).is_ok());
|
||||
assert!(config.features.disable(Feature::ImageGenExt).is_ok());
|
||||
}
|
||||
|
||||
fn configure_image_capable_model(model_info: &mut codex_protocol::openai_models::ModelInfo) {
|
||||
model_info.input_modalities = vec![InputModality::Text, InputModality::Image];
|
||||
}
|
||||
|
||||
fn has_hosted_tool(tools: &[Value], tool_type: &str) -> bool {
|
||||
tools
|
||||
.iter()
|
||||
.any(|tool| tool.get("type").and_then(Value::as_str) == Some(tool_type))
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn responses_lite_uses_standalone_web_search_and_image_generation() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let response_mock = responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-1"),
|
||||
responses::ev_completed("resp-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
let extensions = responses_extensions(&auth);
|
||||
|
||||
let mut builder = test_codex()
|
||||
.with_auth(auth)
|
||||
.with_extensions(extensions)
|
||||
.with_model_info_override("gpt-5.4", |model_info| {
|
||||
model_info.use_responses_lite = true;
|
||||
configure_image_capable_model(model_info);
|
||||
})
|
||||
.with_config(configure_responses_tools);
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
test.submit_turn("Use standalone tools").await?;
|
||||
|
||||
let request = response_mock.single_request();
|
||||
request
|
||||
.tool_by_name("web", "run")
|
||||
.context("Responses Lite should expose standalone web search")?;
|
||||
request
|
||||
.tool_by_name("image_gen", "imagegen")
|
||||
.context("Responses Lite should expose standalone image generation")?;
|
||||
|
||||
let body = request.body_json();
|
||||
let tools = body["tools"]
|
||||
.as_array()
|
||||
.context("Responses request tools should be an array")?;
|
||||
assert!(!has_hosted_tool(tools, "web_search"));
|
||||
assert!(!has_hosted_tool(tools, "image_generation"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn responses_lite_omits_hosted_tools_without_standalone_extensions() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let response_mock = responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-1"),
|
||||
responses::ev_completed("resp-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut builder = test_codex()
|
||||
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
.with_model_info_override("gpt-5.4", |model_info| {
|
||||
model_info.use_responses_lite = true;
|
||||
configure_image_capable_model(model_info);
|
||||
})
|
||||
.with_config(configure_responses_tools);
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
test.submit_turn("Do not use hosted tools").await?;
|
||||
|
||||
let body = response_mock.single_request().body_json();
|
||||
let tools = body["tools"]
|
||||
.as_array()
|
||||
.context("Responses request tools should be an array")?;
|
||||
assert!(!has_hosted_tool(tools, "web_search"));
|
||||
assert!(!has_hosted_tool(tools, "image_generation"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn non_lite_uses_hosted_tools_when_standalone_features_are_disabled() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let response_mock = responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![
|
||||
responses::ev_response_created("resp-1"),
|
||||
responses::ev_completed("resp-1"),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
let extensions = responses_extensions(&auth);
|
||||
let mut builder = test_codex()
|
||||
.with_auth(auth)
|
||||
.with_extensions(extensions)
|
||||
.with_model_info_override("gpt-5.4", configure_image_capable_model)
|
||||
.with_config(configure_responses_tools);
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
test.submit_turn("Use hosted tools").await?;
|
||||
|
||||
let request = response_mock.single_request();
|
||||
assert!(request.tool_by_name("web", "run").is_none());
|
||||
assert!(request.tool_by_name("image_gen", "imagegen").is_none());
|
||||
let body = request.body_json();
|
||||
let tools = body["tools"]
|
||||
.as_array()
|
||||
.context("Responses request tools should be an array")?;
|
||||
assert!(has_hosted_tool(tools, "web_search"));
|
||||
assert!(has_hosted_tool(tools, "image_generation"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -17,7 +17,6 @@ async-trait = { workspace = true }
|
||||
codex-api = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-extension-api = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-model-provider = { workspace = true }
|
||||
codex-model-provider-info = { workspace = true }
|
||||
|
||||
@@ -9,7 +9,6 @@ use codex_extension_api::ThreadStartInput;
|
||||
use codex_extension_api::ToolCall;
|
||||
use codex_extension_api::ToolContributor;
|
||||
use codex_extension_api::ToolExecutor;
|
||||
use codex_features::Feature;
|
||||
use codex_login::AuthManager;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
@@ -25,7 +24,7 @@ struct ImageGenerationExtension {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ImageGenerationExtensionConfig {
|
||||
enabled: bool,
|
||||
available: bool,
|
||||
provider: ModelProviderInfo,
|
||||
codex_home: AbsolutePathBuf,
|
||||
}
|
||||
@@ -34,8 +33,8 @@ impl From<&Config> for ImageGenerationExtensionConfig {
|
||||
/// Resolves whether standalone image generation should be available for a thread.
|
||||
fn from(config: &Config) -> Self {
|
||||
Self {
|
||||
enabled: config.features.enabled(Feature::ImageGenExt)
|
||||
&& config.model_provider.is_openai(),
|
||||
// Core selects this executor per turn using the feature flag or model metadata.
|
||||
available: config.model_provider.is_openai(),
|
||||
provider: config.model_provider.clone(),
|
||||
codex_home: config.codex_home.clone(),
|
||||
}
|
||||
@@ -75,7 +74,7 @@ impl ToolContributor for ImageGenerationExtension {
|
||||
let Some(config) = thread_store.get::<ImageGenerationExtensionConfig>() else {
|
||||
return Vec::new();
|
||||
};
|
||||
if !config.enabled || !self.auth_manager.current_auth_uses_codex_backend() {
|
||||
if !config.available || !self.auth_manager.current_auth_uses_codex_backend() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
@@ -90,7 +89,7 @@ impl ToolContributor for ImageGenerationExtension {
|
||||
}
|
||||
}
|
||||
|
||||
/// Installs the feature-gated standalone image-generation extension contributors.
|
||||
/// Installs the standalone image-generation extension contributors.
|
||||
pub fn install(registry: &mut ExtensionRegistryBuilder<Config>, auth_manager: Arc<AuthManager>) {
|
||||
let extension = Arc::new(ImageGenerationExtension { auth_manager });
|
||||
registry.thread_lifecycle_contributor(extension.clone());
|
||||
|
||||
@@ -17,7 +17,6 @@ async-trait = { workspace = true }
|
||||
codex-api = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-extension-api = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-model-provider = { workspace = true }
|
||||
codex-model-provider-info = { workspace = true }
|
||||
|
||||
@@ -13,7 +13,6 @@ use codex_extension_api::ExtensionRegistryBuilder;
|
||||
use codex_extension_api::ThreadLifecycleContributor;
|
||||
use codex_extension_api::ThreadStartInput;
|
||||
use codex_extension_api::ToolContributor;
|
||||
use codex_features::Feature;
|
||||
use codex_login::AuthManager;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
@@ -29,7 +28,7 @@ struct WebSearchExtension {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct WebSearchExtensionConfig {
|
||||
enabled: bool,
|
||||
available: bool,
|
||||
provider: ModelProviderInfo,
|
||||
settings: SearchSettings,
|
||||
}
|
||||
@@ -38,8 +37,8 @@ impl From<&Config> for WebSearchExtensionConfig {
|
||||
fn from(config: &Config) -> Self {
|
||||
let web_search_mode = config.web_search_mode.value();
|
||||
Self {
|
||||
enabled: config.features.enabled(Feature::StandaloneWebSearch)
|
||||
&& config.model_provider.is_openai()
|
||||
// Core selects this executor per turn using the feature flag or model metadata.
|
||||
available: config.model_provider.is_openai()
|
||||
&& web_search_mode != WebSearchMode::Disabled,
|
||||
provider: config.model_provider.clone(),
|
||||
settings: search_settings(config, web_search_mode),
|
||||
@@ -111,7 +110,7 @@ impl ToolContributor for WebSearchExtension {
|
||||
let Some(config) = thread_store.get::<WebSearchExtensionConfig>() else {
|
||||
return Vec::new();
|
||||
};
|
||||
if !config.enabled {
|
||||
if !config.available {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
@@ -160,7 +159,7 @@ mod tests {
|
||||
let session_store = ExtensionData::new("session");
|
||||
let thread_store = ExtensionData::new("11111111-1111-4111-8111-111111111111");
|
||||
thread_store.insert(WebSearchExtensionConfig {
|
||||
enabled: true,
|
||||
available: true,
|
||||
provider: ModelProviderInfo::create_openai_provider(/*base_url*/ None),
|
||||
settings: Default::default(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user