diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index 846025d58e2..f8a8804f0b8 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -132,70 +132,72 @@ impl From for AdditionalProperties { } fn create_exec_command_tool() -> ToolSpec { - let mut properties = BTreeMap::new(); - properties.insert( - "cmd".to_string(), - JsonSchema::String { - description: Some("Shell command to execute.".to_string()), - }, - ); - properties.insert( - "workdir".to_string(), - JsonSchema::String { - description: Some( - "Optional working directory to run the command in; defaults to the turn cwd." - .to_string(), - ), - }, - ); - properties.insert( - "shell".to_string(), - JsonSchema::String { - description: Some("Shell binary to launch. Defaults to /bin/bash.".to_string()), - }, - ); - properties.insert( - "login".to_string(), - JsonSchema::Boolean { - description: Some( - "Whether to run the shell with -l/-i semantics. Defaults to true.".to_string(), - ), - }, - ); - properties.insert( - "yield_time_ms".to_string(), - JsonSchema::Number { - description: Some( - "How long to wait (in milliseconds) for output before yielding.".to_string(), - ), - }, - ); - properties.insert( - "max_output_tokens".to_string(), - JsonSchema::Number { - description: Some( - "Maximum number of tokens to return. Excess output will be truncated.".to_string(), - ), - }, - ); - properties.insert( - "sandbox_permissions".to_string(), - JsonSchema::String { - description: Some( - "Sandbox permissions for the command. Set to \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\"." - .to_string(), - ), - }, - ); - properties.insert( - "justification".to_string(), - JsonSchema::String { - description: Some( - "Only set if sandbox_permissions is \"require_escalated\". 1-sentence explanation of why we want to run this command." - .to_string(), - ), - }, - ); + let properties = BTreeMap::from([ + ( + "cmd".to_string(), + JsonSchema::String { + description: Some("Shell command to execute.".to_string()), + }, + ), + ( + "workdir".to_string(), + JsonSchema::String { + description: Some( + "Optional working directory to run the command in; defaults to the turn cwd." + .to_string(), + ), + }, + ), + ( + "shell".to_string(), + JsonSchema::String { + description: Some("Shell binary to launch. Defaults to /bin/bash.".to_string()), + }, + ), + ( + "login".to_string(), + JsonSchema::Boolean { + description: Some( + "Whether to run the shell with -l/-i semantics. Defaults to true.".to_string(), + ), + }, + ), + ( + "yield_time_ms".to_string(), + JsonSchema::Number { + description: Some( + "How long to wait (in milliseconds) for output before yielding.".to_string(), + ), + }, + ), + ( + "max_output_tokens".to_string(), + JsonSchema::Number { + description: Some( + "Maximum number of tokens to return. Excess output will be truncated." + .to_string(), + ), + }, + ), + ( + "sandbox_permissions".to_string(), + JsonSchema::String { + description: Some( + "Sandbox permissions for the command. Set to \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\"." + .to_string(), + ), + }, + ), + ( + "justification".to_string(), + JsonSchema::String { + description: Some( + "Only set if sandbox_permissions is \"require_escalated\". 1-sentence explanation of why we want to run this command." + .to_string(), + ), + }, + ), + ]); ToolSpec::Function(ResponsesApiTool { name: "exec_command".to_string(), @@ -212,35 +214,37 @@ fn create_exec_command_tool() -> ToolSpec { } fn create_write_stdin_tool() -> ToolSpec { - let mut properties = BTreeMap::new(); - properties.insert( - "session_id".to_string(), - JsonSchema::Number { - description: Some("Identifier of the running unified exec session.".to_string()), - }, - ); - properties.insert( - "chars".to_string(), - JsonSchema::String { - description: Some("Bytes to write to stdin (may be empty to poll).".to_string()), - }, - ); - properties.insert( - "yield_time_ms".to_string(), - JsonSchema::Number { - description: Some( - "How long to wait (in milliseconds) for output before yielding.".to_string(), - ), - }, - ); - properties.insert( - "max_output_tokens".to_string(), - JsonSchema::Number { - description: Some( - "Maximum number of tokens to return. Excess output will be truncated.".to_string(), - ), - }, - ); + let properties = BTreeMap::from([ + ( + "session_id".to_string(), + JsonSchema::Number { + description: Some("Identifier of the running unified exec session.".to_string()), + }, + ), + ( + "chars".to_string(), + JsonSchema::String { + description: Some("Bytes to write to stdin (may be empty to poll).".to_string()), + }, + ), + ( + "yield_time_ms".to_string(), + JsonSchema::Number { + description: Some( + "How long to wait (in milliseconds) for output before yielding.".to_string(), + ), + }, + ), + ( + "max_output_tokens".to_string(), + JsonSchema::Number { + description: Some( + "Maximum number of tokens to return. Excess output will be truncated." + .to_string(), + ), + }, + ), + ]); ToolSpec::Function(ResponsesApiTool { name: "write_stdin".to_string(), @@ -257,39 +261,39 @@ fn create_write_stdin_tool() -> ToolSpec { } fn create_shell_tool() -> ToolSpec { - let mut properties = BTreeMap::new(); - properties.insert( - "command".to_string(), - JsonSchema::Array { - items: Box::new(JsonSchema::String { description: None }), - description: Some("The command to execute".to_string()), - }, - ); - properties.insert( - "workdir".to_string(), - JsonSchema::String { - description: Some("The working directory to execute the command in".to_string()), - }, - ); - properties.insert( - "timeout_ms".to_string(), - JsonSchema::Number { - description: Some("The timeout for the command in milliseconds".to_string()), - }, - ); - - properties.insert( - "sandbox_permissions".to_string(), - JsonSchema::String { - description: Some("Sandbox permissions for the command. Set to \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\".".to_string()), - }, - ); - properties.insert( - "justification".to_string(), - JsonSchema::String { - description: Some("Only set if sandbox_permissions is \"require_escalated\". 1-sentence explanation of why we want to run this command.".to_string()), - }, - ); + let properties = BTreeMap::from([ + ( + "command".to_string(), + JsonSchema::Array { + items: Box::new(JsonSchema::String { description: None }), + description: Some("The command to execute".to_string()), + }, + ), + ( + "workdir".to_string(), + JsonSchema::String { + description: Some("The working directory to execute the command in".to_string()), + }, + ), + ( + "timeout_ms".to_string(), + JsonSchema::Number { + description: Some("The timeout for the command in milliseconds".to_string()), + }, + ), + ( + "sandbox_permissions".to_string(), + JsonSchema::String { + description: Some("Sandbox permissions for the command. Set to \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\".".to_string()), + }, + ), + ( + "justification".to_string(), + JsonSchema::String { + description: Some("Only set if sandbox_permissions is \"require_escalated\". 1-sentence explanation of why we want to run this command.".to_string()), + }, + ), + ]); let description = if cfg!(windows) { r#"Runs a Powershell command (Windows) and returns its output. Arguments to `shell` will be passed to CreateProcessW(). Most commands should be prefixed with ["powershell.exe", "-Command"]. @@ -321,48 +325,49 @@ Examples of valid command strings: } fn create_shell_command_tool() -> ToolSpec { - let mut properties = BTreeMap::new(); - properties.insert( - "command".to_string(), - JsonSchema::String { - description: Some( - "The shell script to execute in the user's default shell".to_string(), - ), - }, - ); - properties.insert( - "workdir".to_string(), - JsonSchema::String { - description: Some("The working directory to execute the command in".to_string()), - }, - ); - properties.insert( - "login".to_string(), - JsonSchema::Boolean { - description: Some( - "Whether to run the shell with login shell semantics. Defaults to true." - .to_string(), - ), - }, - ); - properties.insert( - "timeout_ms".to_string(), - JsonSchema::Number { - description: Some("The timeout for the command in milliseconds".to_string()), - }, - ); - properties.insert( - "sandbox_permissions".to_string(), - JsonSchema::String { - description: Some("Sandbox permissions for the command. Set to \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\".".to_string()), - }, - ); - properties.insert( - "justification".to_string(), - JsonSchema::String { - description: Some("Only set if sandbox_permissions is \"require_escalated\". 1-sentence explanation of why we want to run this command.".to_string()), - }, - ); + let properties = BTreeMap::from([ + ( + "command".to_string(), + JsonSchema::String { + description: Some( + "The shell script to execute in the user's default shell".to_string(), + ), + }, + ), + ( + "workdir".to_string(), + JsonSchema::String { + description: Some("The working directory to execute the command in".to_string()), + }, + ), + ( + "login".to_string(), + JsonSchema::Boolean { + description: Some( + "Whether to run the shell with login shell semantics. Defaults to true." + .to_string(), + ), + }, + ), + ( + "timeout_ms".to_string(), + JsonSchema::Number { + description: Some("The timeout for the command in milliseconds".to_string()), + }, + ), + ( + "sandbox_permissions".to_string(), + JsonSchema::String { + description: Some("Sandbox permissions for the command. Set to \"require_escalated\" to request running without sandbox restrictions; defaults to \"use_default\".".to_string()), + }, + ), + ( + "justification".to_string(), + JsonSchema::String { + description: Some("Only set if sandbox_permissions is \"require_escalated\". 1-sentence explanation of why we want to run this command.".to_string()), + }, + ), + ]); let description = if cfg!(windows) { r#"Runs a Powershell command (Windows) and returns its output. @@ -394,13 +399,12 @@ Examples of valid command strings: fn create_view_image_tool() -> ToolSpec { // Support only local filesystem path. - let mut properties = BTreeMap::new(); - properties.insert( + let properties = BTreeMap::from([( "path".to_string(), JsonSchema::String { description: Some("Local filesystem path to an image file".to_string()), }, - ); + )]); ToolSpec::Function(ResponsesApiTool { name: "view_image".to_string(), @@ -417,54 +421,59 @@ fn create_view_image_tool() -> ToolSpec { } fn create_test_sync_tool() -> ToolSpec { - let mut properties = BTreeMap::new(); - properties.insert( - "sleep_before_ms".to_string(), - JsonSchema::Number { - description: Some("Optional delay in milliseconds before any other action".to_string()), - }, - ); - properties.insert( - "sleep_after_ms".to_string(), - JsonSchema::Number { - description: Some( - "Optional delay in milliseconds after completing the barrier".to_string(), - ), - }, - ); - - let mut barrier_properties = BTreeMap::new(); - barrier_properties.insert( - "id".to_string(), - JsonSchema::String { - description: Some( - "Identifier shared by concurrent calls that should rendezvous".to_string(), - ), - }, - ); - barrier_properties.insert( - "participants".to_string(), - JsonSchema::Number { - description: Some( - "Number of tool calls that must arrive before the barrier opens".to_string(), - ), - }, - ); - barrier_properties.insert( - "timeout_ms".to_string(), - JsonSchema::Number { - description: Some("Maximum time in milliseconds to wait at the barrier".to_string()), - }, - ); - - properties.insert( - "barrier".to_string(), - JsonSchema::Object { - properties: barrier_properties, - required: Some(vec!["id".to_string(), "participants".to_string()]), - additional_properties: Some(false.into()), - }, - ); + let barrier_properties = BTreeMap::from([ + ( + "id".to_string(), + JsonSchema::String { + description: Some( + "Identifier shared by concurrent calls that should rendezvous".to_string(), + ), + }, + ), + ( + "participants".to_string(), + JsonSchema::Number { + description: Some( + "Number of tool calls that must arrive before the barrier opens".to_string(), + ), + }, + ), + ( + "timeout_ms".to_string(), + JsonSchema::Number { + description: Some( + "Maximum time in milliseconds to wait at the barrier".to_string(), + ), + }, + ), + ]); + + let properties = BTreeMap::from([ + ( + "sleep_before_ms".to_string(), + JsonSchema::Number { + description: Some( + "Optional delay in milliseconds before any other action".to_string(), + ), + }, + ), + ( + "sleep_after_ms".to_string(), + JsonSchema::Number { + description: Some( + "Optional delay in milliseconds after completing the barrier".to_string(), + ), + }, + ), + ( + "barrier".to_string(), + JsonSchema::Object { + properties: barrier_properties, + required: Some(vec!["id".to_string(), "participants".to_string()]), + additional_properties: Some(false.into()), + }, + ), + ]); ToolSpec::Function(ResponsesApiTool { name: "test_sync_tool".to_string(), @@ -479,40 +488,41 @@ fn create_test_sync_tool() -> ToolSpec { } fn create_grep_files_tool() -> ToolSpec { - let mut properties = BTreeMap::new(); - properties.insert( - "pattern".to_string(), - JsonSchema::String { - description: Some("Regular expression pattern to search for.".to_string()), - }, - ); - properties.insert( - "include".to_string(), - JsonSchema::String { - description: Some( - "Optional glob that limits which files are searched (e.g. \"*.rs\" or \ - \"*.{ts,tsx}\")." - .to_string(), - ), - }, - ); - properties.insert( - "path".to_string(), - JsonSchema::String { - description: Some( - "Directory or file path to search. Defaults to the session's working directory." - .to_string(), - ), - }, - ); - properties.insert( - "limit".to_string(), - JsonSchema::Number { - description: Some( - "Maximum number of file paths to return (defaults to 100).".to_string(), - ), - }, - ); + let properties = BTreeMap::from([ + ( + "pattern".to_string(), + JsonSchema::String { + description: Some("Regular expression pattern to search for.".to_string()), + }, + ), + ( + "include".to_string(), + JsonSchema::String { + description: Some( + "Optional glob that limits which files are searched (e.g. \"*.rs\" or \ + \"*.{ts,tsx}\")." + .to_string(), + ), + }, + ), + ( + "path".to_string(), + JsonSchema::String { + description: Some( + "Directory or file path to search. Defaults to the session's working directory." + .to_string(), + ), + }, + ), + ( + "limit".to_string(), + JsonSchema::Number { + description: Some( + "Maximum number of file paths to return (defaults to 100).".to_string(), + ), + }, + ), + ]); ToolSpec::Function(ResponsesApiTool { name: "grep_files".to_string(), @@ -529,88 +539,93 @@ fn create_grep_files_tool() -> ToolSpec { } fn create_read_file_tool() -> ToolSpec { - let mut properties = BTreeMap::new(); - properties.insert( - "file_path".to_string(), - JsonSchema::String { - description: Some("Absolute path to the file".to_string()), - }, - ); - properties.insert( - "offset".to_string(), - JsonSchema::Number { - description: Some( - "The line number to start reading from. Must be 1 or greater.".to_string(), - ), - }, - ); - properties.insert( - "limit".to_string(), - JsonSchema::Number { - description: Some("The maximum number of lines to return.".to_string()), - }, - ); - properties.insert( - "mode".to_string(), - JsonSchema::String { - description: Some( - "Optional mode selector: \"slice\" for simple ranges (default) or \"indentation\" \ - to expand around an anchor line." - .to_string(), - ), - }, - ); - - let mut indentation_properties = BTreeMap::new(); - indentation_properties.insert( - "anchor_line".to_string(), - JsonSchema::Number { - description: Some( - "Anchor line to center the indentation lookup on (defaults to offset).".to_string(), - ), - }, - ); - indentation_properties.insert( - "max_levels".to_string(), - JsonSchema::Number { - description: Some( - "How many parent indentation levels (smaller indents) to include.".to_string(), - ), - }, - ); - indentation_properties.insert( - "include_siblings".to_string(), - JsonSchema::Boolean { - description: Some( - "When true, include additional blocks that share the anchor indentation." - .to_string(), - ), - }, - ); - indentation_properties.insert( - "include_header".to_string(), - JsonSchema::Boolean { - description: Some( - "Include doc comments or attributes directly above the selected block.".to_string(), - ), - }, - ); - indentation_properties.insert( - "max_lines".to_string(), - JsonSchema::Number { - description: Some( - "Hard cap on the number of lines returned when using indentation mode.".to_string(), - ), - }, - ); - properties.insert( - "indentation".to_string(), - JsonSchema::Object { - properties: indentation_properties, - required: None, - additional_properties: Some(false.into()), - }, - ); + let indentation_properties = BTreeMap::from([ + ( + "anchor_line".to_string(), + JsonSchema::Number { + description: Some( + "Anchor line to center the indentation lookup on (defaults to offset)." + .to_string(), + ), + }, + ), + ( + "max_levels".to_string(), + JsonSchema::Number { + description: Some( + "How many parent indentation levels (smaller indents) to include.".to_string(), + ), + }, + ), + ( + "include_siblings".to_string(), + JsonSchema::Boolean { + description: Some( + "When true, include additional blocks that share the anchor indentation." + .to_string(), + ), + }, + ), + ( + "include_header".to_string(), + JsonSchema::Boolean { + description: Some( + "Include doc comments or attributes directly above the selected block." + .to_string(), + ), + }, + ), + ( + "max_lines".to_string(), + JsonSchema::Number { + description: Some( + "Hard cap on the number of lines returned when using indentation mode." + .to_string(), + ), + }, + ), + ]); + + let properties = BTreeMap::from([ + ( + "file_path".to_string(), + JsonSchema::String { + description: Some("Absolute path to the file".to_string()), + }, + ), + ( + "offset".to_string(), + JsonSchema::Number { + description: Some( + "The line number to start reading from. Must be 1 or greater.".to_string(), + ), + }, + ), + ( + "limit".to_string(), + JsonSchema::Number { + description: Some("The maximum number of lines to return.".to_string()), + }, + ), + ( + "mode".to_string(), + JsonSchema::String { + description: Some( + "Optional mode selector: \"slice\" for simple ranges (default) or \"indentation\" \ + to expand around an anchor line." + .to_string(), + ), + }, + ), + ( + "indentation".to_string(), + JsonSchema::Object { + properties: indentation_properties, + required: None, + additional_properties: Some(false.into()), + }, + ), + ]); ToolSpec::Function(ResponsesApiTool { name: "read_file".to_string(), @@ -627,35 +642,36 @@ fn create_read_file_tool() -> ToolSpec { } fn create_list_dir_tool() -> ToolSpec { - let mut properties = BTreeMap::new(); - properties.insert( - "dir_path".to_string(), - JsonSchema::String { - description: Some("Absolute path to the directory to list.".to_string()), - }, - ); - properties.insert( - "offset".to_string(), - JsonSchema::Number { - description: Some( - "The entry number to start listing from. Must be 1 or greater.".to_string(), - ), - }, - ); - properties.insert( - "limit".to_string(), - JsonSchema::Number { - description: Some("The maximum number of entries to return.".to_string()), - }, - ); - properties.insert( - "depth".to_string(), - JsonSchema::Number { - description: Some( - "The maximum directory depth to traverse. Must be 1 or greater.".to_string(), - ), - }, - ); + let properties = BTreeMap::from([ + ( + "dir_path".to_string(), + JsonSchema::String { + description: Some("Absolute path to the directory to list.".to_string()), + }, + ), + ( + "offset".to_string(), + JsonSchema::Number { + description: Some( + "The entry number to start listing from. Must be 1 or greater.".to_string(), + ), + }, + ), + ( + "limit".to_string(), + JsonSchema::Number { + description: Some("The maximum number of entries to return.".to_string()), + }, + ), + ( + "depth".to_string(), + JsonSchema::Number { + description: Some( + "The maximum directory depth to traverse. Must be 1 or greater.".to_string(), + ), + }, + ), + ]); ToolSpec::Function(ResponsesApiTool { name: "list_dir".to_string(), @@ -672,25 +688,26 @@ fn create_list_dir_tool() -> ToolSpec { } fn create_list_mcp_resources_tool() -> ToolSpec { - let mut properties = BTreeMap::new(); - properties.insert( - "server".to_string(), - JsonSchema::String { - description: Some( - "Optional MCP server name. When omitted, lists resources from every configured server." - .to_string(), - ), - }, - ); - properties.insert( - "cursor".to_string(), - JsonSchema::String { - description: Some( - "Opaque cursor returned by a previous list_mcp_resources call for the same server." - .to_string(), - ), - }, - ); + let properties = BTreeMap::from([ + ( + "server".to_string(), + JsonSchema::String { + description: Some( + "Optional MCP server name. When omitted, lists resources from every configured server." + .to_string(), + ), + }, + ), + ( + "cursor".to_string(), + JsonSchema::String { + description: Some( + "Opaque cursor returned by a previous list_mcp_resources call for the same server." + .to_string(), + ), + }, + ), + ]); ToolSpec::Function(ResponsesApiTool { name: "list_mcp_resources".to_string(), @@ -705,25 +722,26 @@ fn create_list_mcp_resources_tool() -> ToolSpec { } fn create_list_mcp_resource_templates_tool() -> ToolSpec { - let mut properties = BTreeMap::new(); - properties.insert( - "server".to_string(), - JsonSchema::String { - description: Some( - "Optional MCP server name. When omitted, lists resource templates from all configured servers." - .to_string(), - ), - }, - ); - properties.insert( - "cursor".to_string(), - JsonSchema::String { - description: Some( - "Opaque cursor returned by a previous list_mcp_resource_templates call for the same server." - .to_string(), - ), - }, - ); + let properties = BTreeMap::from([ + ( + "server".to_string(), + JsonSchema::String { + description: Some( + "Optional MCP server name. When omitted, lists resource templates from all configured servers." + .to_string(), + ), + }, + ), + ( + "cursor".to_string(), + JsonSchema::String { + description: Some( + "Opaque cursor returned by a previous list_mcp_resource_templates call for the same server." + .to_string(), + ), + }, + ), + ]); ToolSpec::Function(ResponsesApiTool { name: "list_mcp_resource_templates".to_string(), @@ -738,25 +756,26 @@ fn create_list_mcp_resource_templates_tool() -> ToolSpec { } fn create_read_mcp_resource_tool() -> ToolSpec { - let mut properties = BTreeMap::new(); - properties.insert( - "server".to_string(), - JsonSchema::String { - description: Some( - "MCP server name exactly as configured. Must match the 'server' field returned by list_mcp_resources." - .to_string(), - ), - }, - ); - properties.insert( - "uri".to_string(), - JsonSchema::String { - description: Some( - "Resource URI to read. Must be one of the URIs returned by list_mcp_resources." - .to_string(), - ), - }, - ); + let properties = BTreeMap::from([ + ( + "server".to_string(), + JsonSchema::String { + description: Some( + "MCP server name exactly as configured. Must match the 'server' field returned by list_mcp_resources." + .to_string(), + ), + }, + ), + ( + "uri".to_string(), + JsonSchema::String { + description: Some( + "Resource URI to read. Must be one of the URIs returned by list_mcp_resources." + .to_string(), + ), + }, + ), + ]); ToolSpec::Function(ResponsesApiTool { name: "read_mcp_resource".to_string(), @@ -1240,7 +1259,7 @@ mod tests { // Build actual map name -> spec use std::collections::BTreeMap; use std::collections::HashSet; - let mut actual: BTreeMap = BTreeMap::new(); + let mut actual: BTreeMap = BTreeMap::from([]); let mut duplicate_names = Vec::new(); for t in &tools { let name = tool_name(&t.spec).to_string(); @@ -1254,7 +1273,7 @@ mod tests { ); // Build expected from the same helpers used by the builder. - let mut expected: BTreeMap = BTreeMap::new(); + let mut expected: BTreeMap = BTreeMap::from([]); for spec in [ create_exec_command_tool(), create_write_stdin_tool(), @@ -2159,8 +2178,8 @@ Examples of valid command strings: #[test] fn chat_tools_include_top_level_name() { - let mut properties = BTreeMap::new(); - properties.insert("foo".to_string(), JsonSchema::String { description: None }); + let properties = + BTreeMap::from([("foo".to_string(), JsonSchema::String { description: None })]); let tools = vec![ToolSpec::Function(ResponsesApiTool { name: "demo".to_string(), description: "A demo tool".to_string(),