Allow bool web_search in ToolsToml (#14352)

Summary
- add a custom deserializer so `[tools].web_search` can be a bool
(treated as disabled) or a config object
- extend core and app-server tests to cover bool handling in TOML config

Testing
- Not run (not requested)
This commit is contained in:
pakrym-oai
2026-03-11 09:24:10 -07:00
committed by Michael Bolin
Unverified
parent 7f22329389
commit 548583198a
4 changed files with 104 additions and 3 deletions
@@ -218,6 +218,38 @@ location = { country = "US", city = "New York", timezone = "America/New_York" }
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn config_read_ignores_bool_web_search_tool_config() -> Result<()> {
let codex_home = TempDir::new()?;
write_config(
&codex_home,
r#"
[tools]
web_search = true
"#,
)?;
let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
let request_id = mcp
.send_config_read_request(ConfigReadParams {
include_layers: false,
cwd: None,
})
.await?;
let resp: JSONRPCResponse = timeout(
DEFAULT_READ_TIMEOUT,
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
)
.await??;
let ConfigReadResponse { config, .. } = to_response(resp)?;
assert_eq!(config.tools.expect("tools present").web_search, None,);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn config_read_includes_apps() -> Result<()> {
let codex_home = TempDir::new()?;
+38
View File
@@ -175,6 +175,44 @@ enabled = false
);
}
#[test]
fn tools_web_search_true_deserializes_to_none() {
let cfg: ConfigToml = toml::from_str(
r#"
[tools]
web_search = true
"#,
)
.expect("TOML deserialization should succeed");
assert_eq!(
cfg.tools,
Some(ToolsToml {
web_search: None,
view_image: None,
})
);
}
#[test]
fn tools_web_search_false_deserializes_to_none() {
let cfg: ConfigToml = toml::from_str(
r#"
[tools]
web_search = false
"#,
)
.expect("TOML deserialization should succeed");
assert_eq!(
cfg.tools,
Some(ToolsToml {
web_search: None,
view_image: None,
})
);
}
#[test]
fn config_toml_deserializes_model_availability_nux() {
let toml = r#"
+30 -1
View File
@@ -82,6 +82,7 @@ use codex_utils_absolute_path::AbsolutePathBuf;
use codex_utils_absolute_path::AbsolutePathBufGuard;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use similar::DiffableStr;
use std::collections::BTreeMap;
@@ -1392,7 +1393,10 @@ pub struct RealtimeAudioToml {
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct ToolsToml {
#[serde(default)]
#[serde(
default,
deserialize_with = "deserialize_optional_web_search_tool_config"
)]
pub web_search: Option<WebSearchToolConfig>,
/// Enable the `view_image` tool that lets the agent attach local images.
@@ -1400,6 +1404,31 @@ pub struct ToolsToml {
pub view_image: Option<bool>,
}
#[derive(Deserialize)]
#[serde(untagged)]
enum WebSearchToolConfigInput {
Enabled(bool),
Config(WebSearchToolConfig),
}
fn deserialize_optional_web_search_tool_config<'de, D>(
deserializer: D,
) -> Result<Option<WebSearchToolConfig>, D::Error>
where
D: Deserializer<'de>,
{
let value = Option::<WebSearchToolConfigInput>::deserialize(deserializer)?;
Ok(match value {
None => None,
Some(WebSearchToolConfigInput::Enabled(enabled)) => {
let _ = enabled;
None
}
Some(WebSearchToolConfigInput::Config(config)) => Some(config),
})
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct AgentsToml {
+4 -2
View File
@@ -168,10 +168,12 @@ impl ConfigService {
};
let effective = layers.effective_config();
validate_config(&effective)
let effective_config_toml: ConfigToml = effective
.try_into()
.map_err(|err| ConfigServiceError::toml("invalid configuration", err))?;
let json_value = serde_json::to_value(&effective)
let json_value = serde_json::to_value(&effective_config_toml)
.map_err(|err| ConfigServiceError::json("failed to serialize configuration", err))?;
let config: ApiConfig = serde_json::from_value(json_value)
.map_err(|err| ConfigServiceError::json("failed to deserialize configuration", err))?;