From 928700bbca4b659666ba67b2d2e95d1066c9e39c Mon Sep 17 00:00:00 2001 From: celia-oai Date: Tue, 12 May 2026 13:49:31 -0700 Subject: [PATCH] changes --- codex-rs/tools/src/dynamic_tool_tests.rs | 10 +++- codex-rs/tools/src/json_schema.rs | 5 +- codex-rs/tools/src/json_schema_tests.rs | 73 ++++++++++++++++++++++-- 3 files changed, 81 insertions(+), 7 deletions(-) diff --git a/codex-rs/tools/src/dynamic_tool_tests.rs b/codex-rs/tools/src/dynamic_tool_tests.rs index 077a0622ce..7d212342ab 100644 --- a/codex-rs/tools/src/dynamic_tool_tests.rs +++ b/codex-rs/tools/src/dynamic_tool_tests.rs @@ -29,7 +29,15 @@ fn parse_dynamic_tool_sanitizes_input_schema() { input_schema: JsonSchema::object( BTreeMap::from([( "id".to_string(), - JsonSchema::string(Some("Ticket identifier".to_string()),), + JsonSchema { + schema_type: Some(crate::JsonSchemaType::Single( + crate::JsonSchemaPrimitiveType::Object, + )), + description: Some("Ticket identifier".to_string()), + properties: Some(BTreeMap::new()), + additional_properties: Some(true.into()), + ..Default::default() + }, )]), /*required*/ None, /*additional_properties*/ None diff --git a/codex-rs/tools/src/json_schema.rs b/codex-rs/tools/src/json_schema.rs index 22a641491e..7d17c5335c 100644 --- a/codex-rs/tools/src/json_schema.rs +++ b/codex-rs/tools/src/json_schema.rs @@ -228,7 +228,10 @@ fn sanitize_json_schema(value: &mut JsonValue) { { schema_types.push(JsonSchemaPrimitiveType::Number); } else { - schema_types.push(JsonSchemaPrimitiveType::String); + // With no schema hints, fall back to an open object so unknown + // tool argument shapes can still carry arbitrary named fields. + schema_types.push(JsonSchemaPrimitiveType::Object); + map.insert("additionalProperties".to_string(), JsonValue::Bool(true)); } } diff --git a/codex-rs/tools/src/json_schema_tests.rs b/codex-rs/tools/src/json_schema_tests.rs index 3f13df7638..c28e0c19ed 100644 --- a/codex-rs/tools/src/json_schema_tests.rs +++ b/codex-rs/tools/src/json_schema_tests.rs @@ -34,7 +34,7 @@ fn parse_tool_input_schema_infers_object_shape_and_defaults_properties() { // // Expected normalization behavior: // - `properties` implies an object schema when `type` is omitted. - // - The child property keeps its description and defaults to a string type. + // - The child property keeps its description and defaults to an open object. let schema = parse_tool_input_schema(&serde_json::json!({ "properties": { "query": {"description": "search query"} @@ -47,7 +47,13 @@ fn parse_tool_input_schema_infers_object_shape_and_defaults_properties() { JsonSchema::object( BTreeMap::from([( "query".to_string(), - JsonSchema::string(Some("search query".to_string())), + JsonSchema { + schema_type: Some(JsonSchemaType::Single(JsonSchemaPrimitiveType::Object)), + description: Some("search query".to_string()), + properties: Some(BTreeMap::new()), + additional_properties: Some(true.into()), + ..Default::default() + }, )]), /*required*/ None, /*additional_properties*/ None @@ -250,16 +256,73 @@ fn parse_tool_input_schema_infers_string_from_enum_const_and_format_keywords() { } #[test] -fn parse_tool_input_schema_defaults_empty_schema_to_string() { +fn parse_tool_input_schema_defaults_empty_schema_to_open_object() { // Example schema shape: // {} // // Expected normalization behavior: // - With no structural hints at all, the normalizer falls back to a - // permissive string schema. + // permissive object schema. let schema = parse_tool_input_schema(&serde_json::json!({})).expect("parse schema"); - assert_eq!(schema, JsonSchema::string(/*description*/ None)); + assert_eq!( + schema, + JsonSchema::object(BTreeMap::new(), /*required*/ None, Some(true.into())) + ); +} + +#[test] +fn parse_tool_input_schema_defaults_nested_empty_schema_to_open_object() { + // Example schema shape: + // { + // "type": "object", + // "properties": { + // "metadata": { + // "properties": { + // "extra": {} + // } + // } + // } + // } + // + // Expected normalization behavior: + // - The sanitizer recurses through nested object properties. + // - The innermost `extra` field has no hints, so it falls back to an open + // object. + let schema = parse_tool_input_schema(&serde_json::json!({ + "type": "object", + "properties": { + "metadata": { + "properties": { + "extra": {} + } + } + } + })) + .expect("parse schema"); + + assert_eq!( + schema, + JsonSchema::object( + BTreeMap::from([( + "metadata".to_string(), + JsonSchema::object( + BTreeMap::from([( + "extra".to_string(), + JsonSchema::object( + BTreeMap::new(), + /*required*/ None, + Some(true.into()), + ), + )]), + /*required*/ None, + /*additional_properties*/ None, + ) + )]), + /*required*/ None, + /*additional_properties*/ None, + ) + ); } #[test]