Render function attribute descriptions (#16880)

This commit is contained in:
Vivian Fang
2026-04-08 02:10:45 -07:00
committed by GitHub
Unverified
parent ea516f9a40
commit 9091999c83
3 changed files with 117 additions and 25 deletions
+115 -23
View File
@@ -408,6 +408,45 @@ fn render_json_schema_array(map: &serde_json::Map<String, JsonValue>) -> String
"unknown[]".to_string()
}
fn append_additional_properties_line(
lines: &mut Vec<String>,
map: &serde_json::Map<String, JsonValue>,
properties: &serde_json::Map<String, JsonValue>,
line_prefix: &str,
) {
if let Some(additional_properties) = map.get("additionalProperties") {
let property_type = match additional_properties {
JsonValue::Bool(true) => Some("unknown".to_string()),
JsonValue::Bool(false) => None,
value => Some(render_json_schema_to_typescript_inner(value)),
};
if let Some(property_type) = property_type {
lines.push(format!("{line_prefix}[key: string]: {property_type};"));
}
} else if properties.is_empty() {
lines.push(format!("{line_prefix}[key: string]: unknown;"));
}
}
fn has_property_description(value: &JsonValue) -> bool {
value
.get("description")
.and_then(JsonValue::as_str)
.is_some_and(|description| !description.is_empty())
}
fn render_json_schema_object_property(name: &str, value: &JsonValue, required: &[&str]) -> String {
let optional = if required.iter().any(|required_name| required_name == &name) {
""
} else {
"?"
};
let property_name = render_json_schema_property_name(name);
let property_type = render_json_schema_to_typescript_inner(value);
format!("{property_name}{optional}: {property_type};")
}
fn render_json_schema_object(map: &serde_json::Map<String, JsonValue>) -> String {
let required = map
.get("required")
@@ -427,33 +466,39 @@ fn render_json_schema_object(map: &serde_json::Map<String, JsonValue>) -> String
let mut sorted_properties = properties.iter().collect::<Vec<_>>();
sorted_properties.sort_unstable_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b));
if sorted_properties
.iter()
.any(|(_, value)| has_property_description(value))
{
let mut lines = vec!["{".to_string()];
for (name, value) in sorted_properties {
if let Some(description) = value.get("description").and_then(JsonValue::as_str) {
for description_line in description
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
{
lines.push(format!(" // {description_line}"));
}
}
lines.push(format!(
" {}",
render_json_schema_object_property(name, value, &required)
));
}
append_additional_properties_line(&mut lines, map, &properties, " ");
lines.push("}".to_string());
return lines.join("\n");
}
let mut lines = sorted_properties
.into_iter()
.map(|(name, value)| {
let optional = if required.iter().any(|required_name| required_name == name) {
""
} else {
"?"
};
let property_name = render_json_schema_property_name(name);
let property_type = render_json_schema_to_typescript_inner(value);
format!("{property_name}{optional}: {property_type};")
})
.map(|(name, value)| render_json_schema_object_property(name, value, &required))
.collect::<Vec<_>>();
if let Some(additional_properties) = map.get("additionalProperties") {
let property_type = match additional_properties {
JsonValue::Bool(true) => Some("unknown".to_string()),
JsonValue::Bool(false) => None,
value => Some(render_json_schema_to_typescript_inner(value)),
};
if let Some(property_type) = property_type {
lines.push(format!("[key: string]: {property_type};"));
}
} else if properties.is_empty() {
lines.push("[key: string]: unknown;".to_string());
}
append_additional_properties_line(&mut lines, map, &properties, "");
if lines.is_empty() {
return "{}".to_string();
@@ -550,6 +595,53 @@ mod tests {
);
}
#[test]
fn augment_tool_definition_includes_property_descriptions_as_comments() {
let definition = ToolDefinition {
name: "weather_tool".to_string(),
description: "Weather tool".to_string(),
kind: CodeModeToolKind::Function,
input_schema: Some(json!({
"type": "object",
"properties": {
"weather": {
"type": "array",
"description": "look up weather for a given list of locations",
"items": {
"type": "object",
"properties": {
"location": { "type": "string" }
},
"required": ["location"]
}
}
},
"required": ["weather"]
})),
output_schema: Some(json!({
"type": "object",
"properties": {
"forecast": {
"type": "string",
"description": "human readable weather forecast"
}
},
"required": ["forecast"]
})),
};
let description = augment_tool_definition(definition).description;
assert!(description.contains(
r#"weather_tool(args: {
// look up weather for a given list of locations
weather: Array<{ location: string; }>;
}): Promise<{
// human readable weather forecast
forecast: string;
}>;"#
));
}
#[test]
fn code_mode_only_description_includes_nested_tools() {
let description = build_exec_tool_description(