mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
Render function attribute descriptions (#16880)
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -2242,7 +2242,7 @@ text(JSON.stringify(tool));
|
||||
parsed,
|
||||
serde_json::json!({
|
||||
"name": "view_image",
|
||||
"description": "View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags).\n\nexec tool declaration:\n```ts\ndeclare const tools: { view_image(args: { path: string; }): Promise<{ detail: string | null; image_url: string; }>; };\n```",
|
||||
"description": "View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags).\n\nexec tool declaration:\n```ts\ndeclare const tools: { view_image(args: {\n // Local filesystem path to an image file\n path: string;\n}): Promise<{\n // Image detail hint returned by view_image. Returns `original` when original resolution is preserved, otherwise `null`.\n detail: string | null;\n // Data URL for the loaded image.\n image_url: string;\n}>; };\n```",
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -1662,7 +1662,7 @@ fn code_mode_augments_builtin_tool_descriptions_with_typed_sample() {
|
||||
|
||||
assert_eq!(
|
||||
description,
|
||||
"View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags).\n\nexec tool declaration:\n```ts\ndeclare const tools: { view_image(args: { path: string; }): Promise<{ detail: string | null; image_url: string; }>; };\n```"
|
||||
"View a local image from the filesystem (only use if given a full filepath by the user, and the image isn't already attached to the thread context within <image ...> tags).\n\nexec tool declaration:\n```ts\ndeclare const tools: { view_image(args: {\n // Local filesystem path to an image file\n path: string;\n}): Promise<{\n // Image detail hint returned by view_image. Returns `original` when original resolution is preserved, otherwise `null`.\n detail: string | null;\n // Data URL for the loaded image.\n image_url: string;\n}>; };\n```"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user