Stop preserving bounds in tool input schemas (#40966)

## What changed

Remove `minimum`, `maximum`, and `maxLength` from the supported tool input
schema representation. Schema parsing now drops these bounds instead of
including them in generated tool declarations, including declarations for
reserved history and notes tools.

GitOrigin-RevId: b7c7733651e65d38cc3d0cef2efa127ab3f8a32f
This commit is contained in:
Channing Conger
2026-08-26 19:49:34 +00:00
committed by copyberry
parent f74bcd2811
commit ac644ed112
3 changed files with 3 additions and 78 deletions

View File

@@ -172,31 +172,6 @@ async fn app_server_uses_configured_notes_backend_for_context_window_hints(
"app-server should expose {namespace}.{tool_name} to the model"
);
}
// Reserved tools must retain Bridge-compatible constraints in the model request.
for (namespace, tool_name, property, expected) in [
(
"history",
"list_items",
"limit",
json!({"type": "integer", "minimum": 1}),
),
(
"notes",
"search_contents",
"query",
json!({"type": "string"}),
),
] {
let tool = request
.tool_by_name(namespace, tool_name)
.expect("history/notes tool should be exposed");
let mut schema = tool["parameters"]["properties"][property].clone();
schema
.as_object_mut()
.expect("parameter schema")
.remove("description");
assert_eq!(schema, expected, "{namespace}.{tool_name}.{property}");
}
}
assert!(request.tool_by_name("notes", "thread_hint").is_none());

View File

@@ -50,13 +50,6 @@ pub struct JsonSchema {
pub encrypted: Option<bool>,
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
pub enum_values: Option<Vec<JsonValue>>,
// Preserve bounds in reserved tool declarations, including exact integer values.
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum: Option<serde_json::Number>,
#[serde(skip_serializing_if = "Option::is_none")]
pub maximum: Option<serde_json::Number>,
#[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
pub max_length: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Box<JsonSchema>>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -528,10 +521,7 @@ fn sanitize_json_schema(value: &mut JsonValue) {
schema_types.push(JsonSchemaPrimitiveType::Object);
} else if map.contains_key("items") || map.contains_key("prefixItems") {
schema_types.push(JsonSchemaPrimitiveType::Array);
} else if map.contains_key("enum")
|| map.contains_key("format")
|| map.contains_key("maxLength")
{
} else if map.contains_key("enum") || map.contains_key("format") {
schema_types.push(JsonSchemaPrimitiveType::String);
} else if map.contains_key("minimum")
|| map.contains_key("maximum")

View File

@@ -38,34 +38,6 @@ fn json_schema_serializes_encrypted_marker() {
);
}
#[test]
fn parse_tool_input_schema_preserves_numeric_and_string_bounds() {
let input = serde_json::json!({
"type": "object",
"properties": {
"count": {
"type": "integer",
"minimum": 0,
"maximum": 9_007_199_254_740_993_u64
},
"score": {"type": "number", "minimum": -1.5, "maximum": 3.25},
"query": {"type": "string", "maxLength": 1000}
},
"additionalProperties": false
});
for parse in [
parse_tool_input_schema,
parse_tool_input_schema_without_compaction,
] {
let schema = parse(&input).expect("parse schema");
assert_eq!(
serde_json::to_value(schema).expect("serialize schema"),
input
);
}
}
#[test]
fn parse_tool_input_schema_infers_object_shape_and_defaults_properties() {
// Example schema shape:
@@ -252,10 +224,7 @@ fn parse_tool_input_schema_infers_number_from_numeric_keywords() {
}))
.expect("parse schema");
assert_eq!(
serde_json::to_value(schema).expect("serialize schema"),
serde_json::json!({"type": "number", "minimum": 1})
);
assert_eq!(schema, JsonSchema::number(/*description*/ None));
}
#[test]
@@ -277,12 +246,11 @@ fn parse_tool_input_schema_infers_number_from_multiple_of() {
}
#[test]
fn parse_tool_input_schema_infers_string_from_keywords() {
fn parse_tool_input_schema_infers_string_from_enum_const_and_format_keywords() {
// Example schema shapes:
// { "enum": ["fast", "safe"] }
// { "const": "file" }
// { "format": "date-time" }
// { "maxLength": 1000 }
//
// Expected normalization behavior:
// - `enum` and `const` normalize into explicit string-enum schemas.
@@ -299,10 +267,6 @@ fn parse_tool_input_schema_infers_string_from_keywords() {
"format": "date-time"
}))
.expect("parse format schema");
let max_length_schema = parse_tool_input_schema(&serde_json::json!({
"maxLength": 1000
}))
.expect("parse maxLength schema");
assert_eq!(
enum_schema,
@@ -316,10 +280,6 @@ fn parse_tool_input_schema_infers_string_from_keywords() {
JsonSchema::string_enum(vec![serde_json::json!("file")], /*description*/ None)
);
assert_eq!(format_schema, JsonSchema::string(/*description*/ None));
assert_eq!(
serde_json::to_value(max_length_schema).expect("serialize schema"),
serde_json::json!({"type": "string", "maxLength": 1000})
);
}
#[test]