[codex] Move tool specs into core handlers (#21416)

## Why

This is the first mechanical slice of moving tool spec ownership toward
the handlers. `codex-tools` should keep shared primitives and conversion
helpers, while builtin tool specs and registration planning live in
`codex-core` with the handlers that own those tools.

Keeping this PR to relocation and import updates isolates the copy/move
review from the later logic change that wires specs through registered
handlers.

## What changed

- Moved builtin tool spec constructors from `codex-rs/tools/src` into
`codex-rs/core/src/tools/handlers/*_spec.rs` or nearby core tool
modules.
- Moved the registry planning code into
`codex-rs/core/src/tools/spec_plan.rs` and its associated types/tests
into core.
- Kept shared primitives in `codex-tools`, including `ToolSpec`,
schema/types, discovery/config primitives, dynamic/MCP conversion
helpers, and code-mode collection helpers.
- Updated handlers that referenced moved argument types or tool-name
constants to use the core spec modules.
- Moved spec tests next to the moved spec modules.

## Verification

- `cargo check -p codex-tools`
- `cargo check -p codex-core`
- `cargo test -p codex-tools`
- `cargo test -p codex-core _spec::tests`
- `cargo test -p codex-core tools::spec_plan::tests`
- `just fix -p codex-tools`
- `just fix -p codex-core`

Note: I also tried the broader `cargo test -p codex-core tools::`; it
reached the moved spec-plan/spec tests successfully, then aborted with a
stack overflow in
`tools::handlers::multi_agents::tests::tool_handlers_cascade_close_and_resume_and_keep_explicitly_closed_subtrees_closed`,
which is outside this spec relocation.
This commit is contained in:
pakrym-oai
2026-05-06 15:40:50 -07:00
committed by GitHub
parent d5eea229cc
commit 9417cf9696
46 changed files with 858 additions and 801 deletions

View File

@@ -0,0 +1,88 @@
use codex_code_mode::ToolDefinition as CodeModeToolDefinition;
use codex_tools::FreeformTool;
use codex_tools::FreeformToolFormat;
use codex_tools::ToolSpec;
use std::collections::BTreeMap;
pub(crate) fn create_code_mode_tool(
enabled_tools: &[CodeModeToolDefinition],
namespace_descriptions: &BTreeMap<String, codex_code_mode::ToolNamespaceDescription>,
code_mode_only: bool,
deferred_tools_available: bool,
) -> ToolSpec {
const CODE_MODE_FREEFORM_GRAMMAR: &str = r#"
start: pragma_source | plain_source
pragma_source: PRAGMA_LINE NEWLINE SOURCE
plain_source: SOURCE
PRAGMA_LINE: /[ \t]*\/\/ @exec:[^\r\n]*/
NEWLINE: /\r?\n/
SOURCE: /[\s\S]+/
"#;
ToolSpec::Freeform(FreeformTool {
name: codex_code_mode::PUBLIC_TOOL_NAME.to_string(),
description: codex_code_mode::build_exec_tool_description(
enabled_tools,
namespace_descriptions,
code_mode_only,
deferred_tools_available,
),
format: FreeformToolFormat {
r#type: "grammar".to_string(),
syntax: "lark".to_string(),
definition: CODE_MODE_FREEFORM_GRAMMAR.to_string(),
},
})
}
#[cfg(test)]
mod tests {
use super::*;
use codex_tools::ToolName;
use pretty_assertions::assert_eq;
#[test]
fn create_code_mode_tool_matches_expected_spec() {
let enabled_tools = vec![codex_code_mode::ToolDefinition {
name: "update_plan".to_string(),
tool_name: ToolName::plain("update_plan"),
description: "Update the plan".to_string(),
kind: codex_code_mode::CodeModeToolKind::Function,
input_schema: None,
output_schema: None,
}];
assert_eq!(
create_code_mode_tool(
&enabled_tools,
&BTreeMap::new(),
/*code_mode_only*/ true,
/*deferred_tools_available*/ false,
),
ToolSpec::Freeform(FreeformTool {
name: codex_code_mode::PUBLIC_TOOL_NAME.to_string(),
description: codex_code_mode::build_exec_tool_description(
&enabled_tools,
&BTreeMap::new(),
/*code_mode_only*/ true,
/*deferred_tools_available*/ false
),
format: FreeformToolFormat {
r#type: "grammar".to_string(),
syntax: "lark".to_string(),
definition: r#"
start: pragma_source | plain_source
pragma_source: PRAGMA_LINE NEWLINE SOURCE
plain_source: SOURCE
PRAGMA_LINE: /[ \t]*\/\/ @exec:[^\r\n]*/
NEWLINE: /\r?\n/
SOURCE: /[\s\S]+/
"#
.to_string(),
},
})
);
}
}

View File

@@ -1,6 +1,8 @@
mod execute_handler;
pub(crate) mod execute_spec;
mod response_adapter;
mod wait_handler;
pub(crate) mod wait_spec;
use std::collections::HashSet;
use std::sync::Arc;

View File

@@ -0,0 +1,105 @@
use codex_tools::JsonSchema;
use codex_tools::ResponsesApiTool;
use codex_tools::ToolSpec;
use std::collections::BTreeMap;
pub(crate) fn create_wait_tool() -> ToolSpec {
let properties = BTreeMap::from([
(
"cell_id".to_string(),
JsonSchema::string(Some("Identifier of the running exec cell.".to_string())),
),
(
"yield_time_ms".to_string(),
JsonSchema::number(Some(
"How long to wait (in milliseconds) for more output before yielding again."
.to_string(),
)),
),
(
"max_tokens".to_string(),
JsonSchema::number(Some(
"Maximum number of output tokens to return for this wait call.".to_string(),
)),
),
(
"terminate".to_string(),
JsonSchema::boolean(Some(
"Whether to terminate the running exec cell.".to_string(),
)),
),
]);
ToolSpec::Function(ResponsesApiTool {
name: codex_code_mode::WAIT_TOOL_NAME.to_string(),
description: format!(
"Waits on a yielded `{}` cell and returns new output or completion.\n{}",
codex_code_mode::PUBLIC_TOOL_NAME,
codex_code_mode::build_wait_tool_description().trim()
),
strict: false,
parameters: JsonSchema::object(
properties,
Some(vec!["cell_id".to_string()]),
Some(false.into()),
),
output_schema: None,
defer_loading: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn create_wait_tool_matches_expected_spec() {
assert_eq!(
create_wait_tool(),
ToolSpec::Function(ResponsesApiTool {
name: codex_code_mode::WAIT_TOOL_NAME.to_string(),
description: format!(
"Waits on a yielded `{}` cell and returns new output or completion.\n{}",
codex_code_mode::PUBLIC_TOOL_NAME,
codex_code_mode::build_wait_tool_description().trim()
),
strict: false,
defer_loading: None,
parameters: JsonSchema::object(
BTreeMap::from([
(
"cell_id".to_string(),
JsonSchema::string(Some(
"Identifier of the running exec cell.".to_string()
)),
),
(
"max_tokens".to_string(),
JsonSchema::number(Some(
"Maximum number of output tokens to return for this wait call."
.to_string(),
)),
),
(
"terminate".to_string(),
JsonSchema::boolean(Some(
"Whether to terminate the running exec cell.".to_string(),
)),
),
(
"yield_time_ms".to_string(),
JsonSchema::number(Some(
"How long to wait (in milliseconds) for more output before yielding again."
.to_string(),
)),
),
]),
Some(vec!["cell_id".to_string()]),
Some(false.into()),
),
output_schema: None,
})
);
}
}