Codex Handbook
tools/src/responses_api.rs 140 lines
use crate::JsonSchema;use crate::ToolDefinition;use crate::ToolName;use crate::parse_dynamic_tool;use crate::parse_mcp_tool;use codex_protocol::dynamic_tools::DynamicToolFunctionSpec;use serde::Deserialize;use serde::Serialize;use serde_json::Value;#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]pub struct FreeformTool {    pub name: String,    pub description: String,    pub format: FreeformToolFormat,}#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]pub struct FreeformToolFormat {    pub r#type: String,    pub syntax: String,    pub definition: String,}#[derive(Debug, Clone, Serialize, PartialEq)]pub struct ResponsesApiTool {    pub name: String,    pub description: String,    /// TODO: Validation. When strict is set to true, the JSON schema,    /// `required` and `additional_properties` must be present. All fields in    /// `properties` must be present in `required`.    pub strict: bool,    #[serde(skip_serializing_if = "Option::is_none")]    pub defer_loading: Option<bool>,    pub parameters: JsonSchema,    #[serde(skip)]    pub output_schema: Option<Value>,}#[derive(Debug, Clone, Serialize, PartialEq)]#[serde(tag = "type")]#[allow(clippy::large_enum_variant)]pub enum LoadableToolSpec {    #[allow(dead_code)]    #[serde(rename = "function")]    Function(ResponsesApiTool),    #[serde(rename = "namespace")]    Namespace(ResponsesApiNamespace),}#[derive(Debug, Clone, Serialize, PartialEq)]pub struct ResponsesApiNamespace {    pub name: String,    pub description: String,    pub tools: Vec<ResponsesApiNamespaceTool>,}pub fn default_namespace_description(namespace_name: &str) -> String {    format!("Tools in the {namespace_name} namespace.")}#[derive(Debug, Clone, Serialize, PartialEq)]#[serde(tag = "type")]pub enum ResponsesApiNamespaceTool {    #[serde(rename = "function")]    Function(ResponsesApiTool),}pub fn dynamic_tool_to_responses_api_tool(    tool: &DynamicToolFunctionSpec,) -> Result<ResponsesApiTool, serde_json::Error> {    Ok(tool_definition_to_responses_api_tool(parse_dynamic_tool(        tool,    )?))}pub fn coalesce_loadable_tool_specs(    specs: impl IntoIterator<Item = LoadableToolSpec>,) -> Vec<LoadableToolSpec> {    let mut coalesced_specs = Vec::new();    for spec in specs {        match spec {            LoadableToolSpec::Function(tool) => {                coalesced_specs.push(LoadableToolSpec::Function(tool));            }            LoadableToolSpec::Namespace(mut namespace) => {                if let Some(existing_namespace) =                    coalesced_specs.iter_mut().find_map(|spec| match spec {                        LoadableToolSpec::Namespace(existing_namespace)                            if existing_namespace.name == namespace.name =>                        {                            Some(existing_namespace)                        }                        LoadableToolSpec::Function(_) | LoadableToolSpec::Namespace(_) => None,                    })                {                    existing_namespace.tools.append(&mut namespace.tools);                } else {                    coalesced_specs.push(LoadableToolSpec::Namespace(namespace));                }            }        }    }    coalesced_specs}pub fn mcp_tool_to_responses_api_tool(    tool_name: &ToolName,    tool: &rmcp::model::Tool,) -> Result<ResponsesApiTool, serde_json::Error> {    Ok(tool_definition_to_responses_api_tool(        parse_mcp_tool(tool)?.renamed(tool_name.name.clone()),    ))}pub fn mcp_tool_to_deferred_responses_api_tool(    tool_name: &ToolName,    tool: &rmcp::model::Tool,) -> Result<ResponsesApiTool, serde_json::Error> {    Ok(tool_definition_to_responses_api_tool(        parse_mcp_tool(tool)?            .renamed(tool_name.name.clone())            .into_deferred(),    ))}pub fn tool_definition_to_responses_api_tool(tool_definition: ToolDefinition) -> ResponsesApiTool {    ResponsesApiTool {        name: tool_definition.name,        description: tool_definition.description,        strict: false,        defer_loading: tool_definition.defer_loading.then_some(true),        parameters: tool_definition.input_schema,        output_schema: tool_definition.output_schema,    }}#[cfg(test)]#[path = "responses_api_tests.rs"]mod tests;