use crate::FreeformTool;use crate::JsonSchema;use crate::LoadableToolSpec;use crate::ResponsesApiNamespace;use crate::ResponsesApiTool;use codex_protocol::config_types::WebSearchContextSize;use codex_protocol::config_types::WebSearchFilters as ConfigWebSearchFilters;use codex_protocol::config_types::WebSearchUserLocation as ConfigWebSearchUserLocation;use codex_protocol::config_types::WebSearchUserLocationType;use serde::Serialize;use serde_json::Value;/// When serialized as JSON, this produces a valid "Tool" in the OpenAI/// Responses API.#[derive(Debug, Clone, Serialize, PartialEq)]#[serde(tag = "type")]pub enum ToolSpec { #[serde(rename = "function")] Function(ResponsesApiTool), #[serde(rename = "namespace")] Namespace(ResponsesApiNamespace), #[serde(rename = "tool_search")] ToolSearch { execution: String, description: String, parameters: JsonSchema, }, #[serde(rename = "image_generation")] ImageGeneration { output_format: String }, // TODO: Understand why we get an error on web_search although the API docs // say it's supported. // https://platform.openai.com/docs/guides/tools-web-search?api-mode=responses#:~:text=%7B%20type%3A%20%22web_search%22%20%7D%2C // The `external_web_access` field determines whether the web search is over // cached or live content. // https://platform.openai.com/docs/guides/tools-web-search#live-internet-access #[serde(rename = "web_search")] WebSearch { #[serde(skip_serializing_if = "Option::is_none")] external_web_access: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] filters: Option<ResponsesApiWebSearchFilters>, #[serde(skip_serializing_if = "Option::is_none")] user_location: Option<ResponsesApiWebSearchUserLocation>, #[serde(skip_serializing_if = "Option::is_none")] search_context_size: Option<WebSearchContextSize>, #[serde(skip_serializing_if = "Option::is_none")] search_content_types: Option<Vec<String>>, }, #[serde(rename = "custom")] Freeform(FreeformTool),}impl ToolSpec { pub fn name(&self) -> &str { match self { ToolSpec::Function(tool) => tool.name.as_str(), ToolSpec::Namespace(namespace) => namespace.name.as_str(), ToolSpec::ToolSearch { .. } => "tool_search", ToolSpec::ImageGeneration { .. } => "image_generation", ToolSpec::WebSearch { .. } => "web_search", ToolSpec::Freeform(tool) => tool.name.as_str(), } }}impl From<LoadableToolSpec> for ToolSpec { fn from(value: LoadableToolSpec) -> Self { match value { LoadableToolSpec::Function(tool) => ToolSpec::Function(tool), LoadableToolSpec::Namespace(namespace) => ToolSpec::Namespace(namespace), } }}/// Returns JSON values that are compatible with Function Calling in the/// Responses API:/// https://platform.openai.com/docs/guides/function-calling?api-mode=responsespub fn create_tools_json_for_responses_api( tools: &[ToolSpec],) -> Result<Vec<Value>, serde_json::Error> { let mut tools_json = Vec::new(); for tool in tools { let json = serde_json::to_value(tool)?; tools_json.push(json); } Ok(tools_json)}#[derive(Debug, Clone, Serialize, PartialEq)]pub struct ResponsesApiWebSearchFilters { #[serde(skip_serializing_if = "Option::is_none")] pub allowed_domains: Option<Vec<String>>,}impl From<ConfigWebSearchFilters> for ResponsesApiWebSearchFilters { fn from(filters: ConfigWebSearchFilters) -> Self { Self { allowed_domains: filters.allowed_domains, } }}#[derive(Debug, Clone, Serialize, PartialEq)]pub struct ResponsesApiWebSearchUserLocation { #[serde(rename = "type")] pub r#type: WebSearchUserLocationType, #[serde(skip_serializing_if = "Option::is_none")] pub country: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub region: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub city: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] pub timezone: Option<String>,}impl From<ConfigWebSearchUserLocation> for ResponsesApiWebSearchUserLocation { fn from(user_location: ConfigWebSearchUserLocation) -> Self { Self { r#type: user_location.r#type, country: user_location.country, region: user_location.region, city: user_location.city, timezone: user_location.timezone, } }}#[cfg(test)]#[path = "tool_spec_tests.rs"]mod tests;