codex-tools: extract code mode tool spec adapters (#16132)

## Why

The longer-term `codex-tools` migration is to move pure tool-definition
and tool-spec plumbing out of `codex-core` while leaving session- and
runtime-coupled orchestration behind.

The remaining code-mode adapter layer in
`core/src/tools/code_mode_description.rs` was a good next extraction
seam because it only transformed `ToolSpec` values for code mode and
already delegated the low-level description rendering to
`codex-code-mode`.

## What Changed

- added `codex-rs/tools/src/code_mode.rs` with
`augment_tool_spec_for_code_mode()` and
`tool_spec_to_code_mode_tool_definition()`
- added focused unit coverage in `codex-rs/tools/src/code_mode_tests.rs`
- rewired `core/src/tools/spec.rs` and `core/src/tools/code_mode/mod.rs`
to use the extracted adapters from `codex-tools`
- removed the old `core/src/tools/code_mode_description.rs` shim and its
test file from `codex-core`
- added the `codex-code-mode` dependency to `codex-tools`, updated
`Cargo.lock`, and refreshed the `codex-tools` README to reflect the
expanded boundary

## Test Plan

- `cargo test -p codex-tools`
- `CARGO_TARGET_DIR=/tmp/codex-core-code-mode-adapters cargo test -p
codex-core --lib tools::spec::`
- `CARGO_TARGET_DIR=/tmp/codex-core-code-mode-adapters cargo test -p
codex-core --lib tools::code_mode::`
- `just bazel-lock-update`
- `just bazel-lock-check`
- `just argument-comment-lint`

## References

- #15923
- #15928
- #15944
- #15953
- #16031
- #16047
- #16129
This commit is contained in:
Michael Bolin
2026-03-28 15:32:35 -07:00
committed by GitHub
parent c25c0d6e9e
commit 2238c16a91
11 changed files with 202 additions and 197 deletions

View File

@@ -0,0 +1,123 @@
use super::augment_tool_spec_for_code_mode;
use super::tool_spec_to_code_mode_tool_definition;
use crate::AdditionalProperties;
use crate::FreeformTool;
use crate::FreeformToolFormat;
use crate::JsonSchema;
use crate::ResponsesApiTool;
use crate::ToolSpec;
use pretty_assertions::assert_eq;
use serde_json::json;
use std::collections::BTreeMap;
#[test]
fn augment_tool_spec_for_code_mode_augments_function_tools() {
assert_eq!(
augment_tool_spec_for_code_mode(ToolSpec::Function(ResponsesApiTool {
name: "lookup_order".to_string(),
description: "Look up an order".to_string(),
strict: false,
defer_loading: Some(true),
parameters: JsonSchema::Object {
properties: BTreeMap::from([(
"order_id".to_string(),
JsonSchema::String { description: None },
)]),
required: Some(vec!["order_id".to_string()]),
additional_properties: Some(AdditionalProperties::Boolean(false)),
},
output_schema: Some(json!({
"type": "object",
"properties": {
"ok": {"type": "boolean"}
},
"required": ["ok"],
})),
})),
ToolSpec::Function(ResponsesApiTool {
name: "lookup_order".to_string(),
description: "Look up an order\n\nexec tool declaration:\n```ts\ndeclare const tools: { lookup_order(args: { order_id: string; }): Promise<{ ok: boolean; }>; };\n```".to_string(),
strict: false,
defer_loading: Some(true),
parameters: JsonSchema::Object {
properties: BTreeMap::from([(
"order_id".to_string(),
JsonSchema::String { description: None },
)]),
required: Some(vec!["order_id".to_string()]),
additional_properties: Some(AdditionalProperties::Boolean(false)),
},
output_schema: Some(json!({
"type": "object",
"properties": {
"ok": {"type": "boolean"}
},
"required": ["ok"],
})),
})
);
}
#[test]
fn augment_tool_spec_for_code_mode_preserves_exec_tool_description() {
assert_eq!(
augment_tool_spec_for_code_mode(ToolSpec::Freeform(FreeformTool {
name: codex_code_mode::PUBLIC_TOOL_NAME.to_string(),
description: "Run code".to_string(),
format: FreeformToolFormat {
r#type: "grammar".to_string(),
syntax: "lark".to_string(),
definition: "start: \"exec\"".to_string(),
},
})),
ToolSpec::Freeform(FreeformTool {
name: codex_code_mode::PUBLIC_TOOL_NAME.to_string(),
description: "Run code".to_string(),
format: FreeformToolFormat {
r#type: "grammar".to_string(),
syntax: "lark".to_string(),
definition: "start: \"exec\"".to_string(),
},
})
);
}
#[test]
fn tool_spec_to_code_mode_tool_definition_returns_augmented_nested_tools() {
let spec = ToolSpec::Freeform(FreeformTool {
name: "apply_patch".to_string(),
description: "Apply a patch".to_string(),
format: FreeformToolFormat {
r#type: "grammar".to_string(),
syntax: "lark".to_string(),
definition: "start: \"patch\"".to_string(),
},
});
assert_eq!(
tool_spec_to_code_mode_tool_definition(&spec),
Some(codex_code_mode::ToolDefinition {
name: "apply_patch".to_string(),
description: "Apply a patch\n\nexec tool declaration:\n```ts\ndeclare const tools: { apply_patch(input: string): Promise<unknown>; };\n```".to_string(),
kind: codex_code_mode::CodeModeToolKind::Freeform,
input_schema: None,
output_schema: None,
})
);
}
#[test]
fn tool_spec_to_code_mode_tool_definition_skips_unsupported_variants() {
assert_eq!(
tool_spec_to_code_mode_tool_definition(&ToolSpec::ToolSearch {
execution: "sync".to_string(),
description: "Search".to_string(),
parameters: JsonSchema::Object {
properties: BTreeMap::new(),
required: None,
additional_properties: None,
},
}),
None
);
}