mirror of
https://github.com/openai/codex.git
synced 2026-05-01 11:52:10 +03:00
codex-tools: extract tool spec models (#16047)
## Why This continues the `codex-tools` migration by moving another passive tool-definition layer out of `codex-core`. After `ResponsesApiTool` and the lower-level schema adapters moved into `codex-tools`, `core/src/client_common.rs` was still owning `ToolSpec` and the web-search request wire types even though they are serialized data models rather than runtime orchestration. Keeping those types in `codex-core` makes the crate boundary look smaller than it really is and leaves non-runtime tool-shape code coupled to core. ## What changed - moved `ToolSpec`, `ResponsesApiWebSearchFilters`, and `ResponsesApiWebSearchUserLocation` into `codex-rs/tools/src/tool_spec.rs` - added focused unit tests in `codex-rs/tools/src/tool_spec_tests.rs` for: - `ToolSpec::name()` - web-search config conversions - `ToolSpec` serialization for `web_search` and `tool_search` - kept `codex-rs/tools/src/lib.rs` exports-only by re-exporting the new module from `lib.rs` - reduced `core/src/client_common.rs` to a compatibility shim that re-exports the extracted tool-spec types for current core call sites - updated `core/src/tools/spec_tests.rs` to consume the extracted web-search types directly from `codex-tools` - updated `codex-rs/tools/README.md` so the crate contract reflects that `codex-tools` now owns the passive tool-spec request models in addition to the lower-level Responses API structs ## Test plan - `cargo test -p codex-tools` - `cargo test -p codex-core --lib tools::spec::` - `cargo test -p codex-core --lib client_common::` - `just fix -p codex-tools -p codex-core` - `just argument-comment-lint` ## References - #15923 - #15928 - #15944 - #15953 - #16031
This commit is contained in:
183
codex-rs/tools/src/tool_spec_tests.rs
Normal file
183
codex-rs/tools/src/tool_spec_tests.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
use super::ResponsesApiWebSearchFilters;
|
||||
use super::ResponsesApiWebSearchUserLocation;
|
||||
use super::ToolSpec;
|
||||
use crate::AdditionalProperties;
|
||||
use crate::FreeformTool;
|
||||
use crate::FreeformToolFormat;
|
||||
use crate::JsonSchema;
|
||||
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 pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[test]
|
||||
fn tool_spec_name_covers_all_variants() {
|
||||
assert_eq!(
|
||||
ToolSpec::Function(ResponsesApiTool {
|
||||
name: "lookup_order".to_string(),
|
||||
description: "Look up an order".to_string(),
|
||||
strict: false,
|
||||
defer_loading: None,
|
||||
parameters: JsonSchema::Object {
|
||||
properties: BTreeMap::new(),
|
||||
required: None,
|
||||
additional_properties: None,
|
||||
},
|
||||
output_schema: None,
|
||||
})
|
||||
.name(),
|
||||
"lookup_order"
|
||||
);
|
||||
assert_eq!(
|
||||
ToolSpec::ToolSearch {
|
||||
execution: "sync".to_string(),
|
||||
description: "Search for tools".to_string(),
|
||||
parameters: JsonSchema::Object {
|
||||
properties: BTreeMap::new(),
|
||||
required: None,
|
||||
additional_properties: None,
|
||||
},
|
||||
}
|
||||
.name(),
|
||||
"tool_search"
|
||||
);
|
||||
assert_eq!(ToolSpec::LocalShell {}.name(), "local_shell");
|
||||
assert_eq!(
|
||||
ToolSpec::ImageGeneration {
|
||||
output_format: "png".to_string(),
|
||||
}
|
||||
.name(),
|
||||
"image_generation"
|
||||
);
|
||||
assert_eq!(
|
||||
ToolSpec::WebSearch {
|
||||
external_web_access: Some(true),
|
||||
filters: None,
|
||||
user_location: None,
|
||||
search_context_size: None,
|
||||
search_content_types: None,
|
||||
}
|
||||
.name(),
|
||||
"web_search"
|
||||
);
|
||||
assert_eq!(
|
||||
ToolSpec::Freeform(FreeformTool {
|
||||
name: "exec".to_string(),
|
||||
description: "Run a command".to_string(),
|
||||
format: FreeformToolFormat {
|
||||
r#type: "grammar".to_string(),
|
||||
syntax: "lark".to_string(),
|
||||
definition: "start: \"exec\"".to_string(),
|
||||
},
|
||||
})
|
||||
.name(),
|
||||
"exec"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_config_converts_to_responses_api_types() {
|
||||
assert_eq!(
|
||||
ResponsesApiWebSearchFilters::from(ConfigWebSearchFilters {
|
||||
allowed_domains: Some(vec!["example.com".to_string()]),
|
||||
}),
|
||||
ResponsesApiWebSearchFilters {
|
||||
allowed_domains: Some(vec!["example.com".to_string()]),
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
ResponsesApiWebSearchUserLocation::from(ConfigWebSearchUserLocation {
|
||||
r#type: WebSearchUserLocationType::Approximate,
|
||||
country: Some("US".to_string()),
|
||||
region: Some("California".to_string()),
|
||||
city: Some("San Francisco".to_string()),
|
||||
timezone: Some("America/Los_Angeles".to_string()),
|
||||
}),
|
||||
ResponsesApiWebSearchUserLocation {
|
||||
r#type: WebSearchUserLocationType::Approximate,
|
||||
country: Some("US".to_string()),
|
||||
region: Some("California".to_string()),
|
||||
city: Some("San Francisco".to_string()),
|
||||
timezone: Some("America/Los_Angeles".to_string()),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_search_tool_spec_serializes_expected_wire_shape() {
|
||||
assert_eq!(
|
||||
serde_json::to_value(ToolSpec::WebSearch {
|
||||
external_web_access: Some(true),
|
||||
filters: Some(ResponsesApiWebSearchFilters {
|
||||
allowed_domains: Some(vec!["example.com".to_string()]),
|
||||
}),
|
||||
user_location: Some(ResponsesApiWebSearchUserLocation {
|
||||
r#type: WebSearchUserLocationType::Approximate,
|
||||
country: Some("US".to_string()),
|
||||
region: Some("California".to_string()),
|
||||
city: Some("San Francisco".to_string()),
|
||||
timezone: Some("America/Los_Angeles".to_string()),
|
||||
}),
|
||||
search_context_size: Some(WebSearchContextSize::High),
|
||||
search_content_types: Some(vec!["text".to_string(), "image".to_string()]),
|
||||
})
|
||||
.expect("serialize web_search"),
|
||||
json!({
|
||||
"type": "web_search",
|
||||
"external_web_access": true,
|
||||
"filters": {
|
||||
"allowed_domains": ["example.com"],
|
||||
},
|
||||
"user_location": {
|
||||
"type": "approximate",
|
||||
"country": "US",
|
||||
"region": "California",
|
||||
"city": "San Francisco",
|
||||
"timezone": "America/Los_Angeles",
|
||||
},
|
||||
"search_context_size": "high",
|
||||
"search_content_types": ["text", "image"],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_search_tool_spec_serializes_expected_wire_shape() {
|
||||
assert_eq!(
|
||||
serde_json::to_value(ToolSpec::ToolSearch {
|
||||
execution: "sync".to_string(),
|
||||
description: "Search app tools".to_string(),
|
||||
parameters: JsonSchema::Object {
|
||||
properties: BTreeMap::from([(
|
||||
"query".to_string(),
|
||||
JsonSchema::String {
|
||||
description: Some("Tool search query".to_string()),
|
||||
},
|
||||
)]),
|
||||
required: Some(vec!["query".to_string()]),
|
||||
additional_properties: Some(AdditionalProperties::Boolean(false)),
|
||||
},
|
||||
})
|
||||
.expect("serialize tool_search"),
|
||||
json!({
|
||||
"type": "tool_search",
|
||||
"execution": "sync",
|
||||
"description": "Search app tools",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Tool search query",
|
||||
}
|
||||
},
|
||||
"required": ["query"],
|
||||
"additionalProperties": false,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user