code mode: single line tool declarations (#14526)
## Summary - render code mode tool declarations as single-line TypeScript snippets - make the JSON schema renderer emit inline object shapes for these declarations - update code mode/spec expectations to match the new inline rendering ## Testing - `just fmt` - `cargo test -p codex-core render_json_schema_to_typescript` - `cargo test -p codex-core code_mode_augments_` - `cargo test -p codex-core --test all exports_all_tools_metadata -- --nocapture`
This commit is contained in:
parent
8e89e9eded
commit
9c9867c9fa
4 changed files with 31 additions and 59 deletions
|
|
@ -75,10 +75,10 @@ fn append_code_mode_sample(
|
|||
output_type: String,
|
||||
) -> String {
|
||||
let declaration = format!(
|
||||
"declare const tools: {{\n {}\n}};",
|
||||
"declare const tools: {{ {} }};",
|
||||
render_code_mode_tool_declaration(tool_name, input_name, input_type, output_type)
|
||||
);
|
||||
format!("{description}\n\nCode mode declaration:\n```ts\n{declaration}\n```")
|
||||
format!("{description}\n\nexec tool declaration:\n```ts\n{declaration}\n```")
|
||||
}
|
||||
|
||||
fn render_code_mode_tool_declaration(
|
||||
|
|
@ -87,28 +87,10 @@ fn render_code_mode_tool_declaration(
|
|||
input_type: String,
|
||||
output_type: String,
|
||||
) -> String {
|
||||
let input_type = indent_multiline_type(&input_type, 2);
|
||||
let output_type = indent_multiline_type(&output_type, 2);
|
||||
let tool_name = normalize_code_mode_identifier(tool_name);
|
||||
format!("{tool_name}({input_name}: {input_type}): Promise<{output_type}>;")
|
||||
}
|
||||
|
||||
fn indent_multiline_type(type_name: &str, spaces: usize) -> String {
|
||||
let indent = " ".repeat(spaces);
|
||||
type_name
|
||||
.lines()
|
||||
.enumerate()
|
||||
.map(|(index, line)| {
|
||||
if index == 0 {
|
||||
line.to_string()
|
||||
} else {
|
||||
format!("{indent}{line}")
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
pub(crate) fn normalize_code_mode_identifier(tool_key: &str) -> String {
|
||||
let mut identifier = String::new();
|
||||
|
||||
|
|
@ -134,10 +116,10 @@ pub(crate) fn normalize_code_mode_identifier(tool_key: &str) -> String {
|
|||
}
|
||||
|
||||
fn render_json_schema_to_typescript(schema: &JsonValue) -> String {
|
||||
render_json_schema_to_typescript_inner(schema, 0)
|
||||
render_json_schema_to_typescript_inner(schema)
|
||||
}
|
||||
|
||||
fn render_json_schema_to_typescript_inner(schema: &JsonValue, indent: usize) -> String {
|
||||
fn render_json_schema_to_typescript_inner(schema: &JsonValue) -> String {
|
||||
match schema {
|
||||
JsonValue::Bool(true) => "unknown".to_string(),
|
||||
JsonValue::Bool(false) => "never".to_string(),
|
||||
|
|
@ -160,7 +142,7 @@ fn render_json_schema_to_typescript_inner(schema: &JsonValue, indent: usize) ->
|
|||
if let Some(variants) = map.get(key).and_then(serde_json::Value::as_array) {
|
||||
let rendered = variants
|
||||
.iter()
|
||||
.map(|variant| render_json_schema_to_typescript_inner(variant, indent))
|
||||
.map(render_json_schema_to_typescript_inner)
|
||||
.collect::<Vec<_>>();
|
||||
if !rendered.is_empty() {
|
||||
return rendered.join(" | ");
|
||||
|
|
@ -171,7 +153,7 @@ fn render_json_schema_to_typescript_inner(schema: &JsonValue, indent: usize) ->
|
|||
if let Some(variants) = map.get("allOf").and_then(serde_json::Value::as_array) {
|
||||
let rendered = variants
|
||||
.iter()
|
||||
.map(|variant| render_json_schema_to_typescript_inner(variant, indent))
|
||||
.map(render_json_schema_to_typescript_inner)
|
||||
.collect::<Vec<_>>();
|
||||
if !rendered.is_empty() {
|
||||
return rendered.join(" & ");
|
||||
|
|
@ -183,9 +165,7 @@ fn render_json_schema_to_typescript_inner(schema: &JsonValue, indent: usize) ->
|
|||
let rendered = types
|
||||
.iter()
|
||||
.filter_map(serde_json::Value::as_str)
|
||||
.map(|schema_type| {
|
||||
render_json_schema_type_keyword(map, schema_type, indent)
|
||||
})
|
||||
.map(|schema_type| render_json_schema_type_keyword(map, schema_type))
|
||||
.collect::<Vec<_>>();
|
||||
if !rendered.is_empty() {
|
||||
return rendered.join(" | ");
|
||||
|
|
@ -193,7 +173,7 @@ fn render_json_schema_to_typescript_inner(schema: &JsonValue, indent: usize) ->
|
|||
}
|
||||
|
||||
if let Some(schema_type) = schema_type.as_str() {
|
||||
return render_json_schema_type_keyword(map, schema_type, indent);
|
||||
return render_json_schema_type_keyword(map, schema_type);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -201,11 +181,11 @@ fn render_json_schema_to_typescript_inner(schema: &JsonValue, indent: usize) ->
|
|||
|| map.contains_key("additionalProperties")
|
||||
|| map.contains_key("required")
|
||||
{
|
||||
return render_json_schema_object(map, indent);
|
||||
return render_json_schema_object(map);
|
||||
}
|
||||
|
||||
if map.contains_key("items") || map.contains_key("prefixItems") {
|
||||
return render_json_schema_array(map, indent);
|
||||
return render_json_schema_array(map);
|
||||
}
|
||||
|
||||
"unknown".to_string()
|
||||
|
|
@ -217,29 +197,28 @@ fn render_json_schema_to_typescript_inner(schema: &JsonValue, indent: usize) ->
|
|||
fn render_json_schema_type_keyword(
|
||||
map: &serde_json::Map<String, JsonValue>,
|
||||
schema_type: &str,
|
||||
indent: usize,
|
||||
) -> String {
|
||||
match schema_type {
|
||||
"string" => "string".to_string(),
|
||||
"number" | "integer" => "number".to_string(),
|
||||
"boolean" => "boolean".to_string(),
|
||||
"null" => "null".to_string(),
|
||||
"array" => render_json_schema_array(map, indent),
|
||||
"object" => render_json_schema_object(map, indent),
|
||||
"array" => render_json_schema_array(map),
|
||||
"object" => render_json_schema_object(map),
|
||||
_ => "unknown".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_json_schema_array(map: &serde_json::Map<String, JsonValue>, indent: usize) -> String {
|
||||
fn render_json_schema_array(map: &serde_json::Map<String, JsonValue>) -> String {
|
||||
if let Some(items) = map.get("items") {
|
||||
let item_type = render_json_schema_to_typescript_inner(items, indent + 2);
|
||||
let item_type = render_json_schema_to_typescript_inner(items);
|
||||
return format!("Array<{item_type}>");
|
||||
}
|
||||
|
||||
if let Some(items) = map.get("prefixItems").and_then(serde_json::Value::as_array) {
|
||||
let item_types = items
|
||||
.iter()
|
||||
.map(|item| render_json_schema_to_typescript_inner(item, indent + 2))
|
||||
.map(render_json_schema_to_typescript_inner)
|
||||
.collect::<Vec<_>>();
|
||||
if !item_types.is_empty() {
|
||||
return format!("[{}]", item_types.join(", "));
|
||||
|
|
@ -249,7 +228,7 @@ fn render_json_schema_array(map: &serde_json::Map<String, JsonValue>, indent: us
|
|||
"unknown[]".to_string()
|
||||
}
|
||||
|
||||
fn render_json_schema_object(map: &serde_json::Map<String, JsonValue>, indent: usize) -> String {
|
||||
fn render_json_schema_object(map: &serde_json::Map<String, JsonValue>) -> String {
|
||||
let required = map
|
||||
.get("required")
|
||||
.and_then(serde_json::Value::as_array)
|
||||
|
|
@ -268,7 +247,6 @@ fn render_json_schema_object(map: &serde_json::Map<String, JsonValue>, indent: u
|
|||
|
||||
let mut sorted_properties = properties.iter().collect::<Vec<_>>();
|
||||
sorted_properties.sort_unstable_by(|(name_a, _), (name_b, _)| name_a.cmp(name_b));
|
||||
|
||||
let mut lines = sorted_properties
|
||||
.into_iter()
|
||||
.map(|(name, value)| {
|
||||
|
|
@ -278,11 +256,8 @@ fn render_json_schema_object(map: &serde_json::Map<String, JsonValue>, indent: u
|
|||
"?"
|
||||
};
|
||||
let property_name = render_json_schema_property_name(name);
|
||||
let property_type = render_json_schema_to_typescript_inner(value, indent + 2);
|
||||
format!(
|
||||
"{}{property_name}{optional}: {property_type};",
|
||||
" ".repeat(indent + 2)
|
||||
)
|
||||
let property_type = render_json_schema_to_typescript_inner(value);
|
||||
format!("{property_name}{optional}: {property_type};")
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
|
@ -290,24 +265,21 @@ fn render_json_schema_object(map: &serde_json::Map<String, JsonValue>, indent: u
|
|||
let additional_type = match additional_properties {
|
||||
JsonValue::Bool(true) => Some("unknown".to_string()),
|
||||
JsonValue::Bool(false) => None,
|
||||
value => Some(render_json_schema_to_typescript_inner(value, indent + 2)),
|
||||
value => Some(render_json_schema_to_typescript_inner(value)),
|
||||
};
|
||||
|
||||
if let Some(additional_type) = additional_type {
|
||||
lines.push(format!(
|
||||
"{}[key: string]: {additional_type};",
|
||||
" ".repeat(indent + 2)
|
||||
));
|
||||
lines.push(format!("[key: string]: {additional_type};"));
|
||||
}
|
||||
} else if properties.is_empty() {
|
||||
lines.push(format!("{}[key: string]: unknown;", " ".repeat(indent + 2)));
|
||||
lines.push("[key: string]: unknown;".to_string());
|
||||
}
|
||||
|
||||
if lines.is_empty() {
|
||||
return "{}".to_string();
|
||||
}
|
||||
|
||||
format!("{{\n{}\n{}}}", lines.join("\n"), " ".repeat(indent))
|
||||
format!("{{ {} }}", lines.join(" "))
|
||||
}
|
||||
|
||||
fn render_json_schema_property_name(name: &str) -> String {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ fn render_json_schema_to_typescript_renders_object_properties() {
|
|||
|
||||
assert_eq!(
|
||||
render_json_schema_to_typescript(&schema),
|
||||
"{\n path: string;\n recursive?: boolean;\n}"
|
||||
"{ path: string; recursive?: boolean; }"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ fn render_json_schema_to_typescript_renders_additional_properties() {
|
|||
|
||||
assert_eq!(
|
||||
render_json_schema_to_typescript(&schema),
|
||||
"{\n tags?: Array<string>;\n [key: string]: number;\n}"
|
||||
"{ tags?: Array<string>; [key: string]: number; }"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ fn render_json_schema_to_typescript_sorts_object_properties() {
|
|||
|
||||
assert_eq!(
|
||||
render_json_schema_to_typescript(&schema),
|
||||
"{\n _meta?: string;\n content: Array<string>;\n isError?: boolean;\n structuredContent?: string;\n}"
|
||||
"{ _meta?: string; content: Array<string>; isError?: boolean; structuredContent?: string; }"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ fn append_code_mode_sample_uses_global_tools_for_valid_identifiers() {
|
|||
"{ foo: string }".to_string(),
|
||||
"unknown".to_string(),
|
||||
),
|
||||
"desc\n\nCode mode declaration:\n```ts\ndeclare const tools: {\n mcp__ologs__get_profile(args: { foo: string }): Promise<unknown>;\n};\n```"
|
||||
"desc\n\nexec tool declaration:\n```ts\ndeclare const tools: { mcp__ologs__get_profile(args: { foo: string }): Promise<unknown>; };\n```"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +99,6 @@ fn append_code_mode_sample_normalizes_invalid_identifiers() {
|
|||
"{ foo: string }".to_string(),
|
||||
"unknown".to_string(),
|
||||
),
|
||||
"desc\n\nCode mode declaration:\n```ts\ndeclare const tools: {\n mcp__rmcp__echo_tool(args: { foo: string }): Promise<unknown>;\n};\n```"
|
||||
"desc\n\nexec tool declaration:\n```ts\ndeclare const tools: { mcp__rmcp__echo_tool(args: { foo: string }): Promise<unknown>; };\n```"
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2432,7 +2432,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\nCode mode declaration:\n```ts\ndeclare const tools: {\n view_image(args: {\n path: string;\n }): Promise<unknown>;\n};\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: { path: string; }): Promise<unknown>; };\n```"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -2484,7 +2484,7 @@ fn code_mode_augments_mcp_tool_descriptions_with_namespaced_sample() {
|
|||
|
||||
assert_eq!(
|
||||
description,
|
||||
"Echo text\n\nCode mode declaration:\n```ts\ndeclare const tools: {\n mcp__sample__echo(args: {\n message: string;\n }): Promise<{\n _meta?: unknown;\n content: Array<unknown>;\n isError?: boolean;\n structuredContent?: unknown;\n }>;\n};\n```"
|
||||
"Echo text\n\nexec tool declaration:\n```ts\ndeclare const tools: { mcp__sample__echo(args: { message: string; }): Promise<{ _meta?: unknown; content: Array<unknown>; isError?: boolean; structuredContent?: unknown; }>; };\n```"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1828,7 +1828,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\nCode mode declaration:\n```ts\ndeclare const tools: {\n view_image(args: {\n path: string;\n }): Promise<unknown>;\n};\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: { path: string; }): Promise<unknown>; };\n```",
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -1863,7 +1863,7 @@ text(JSON.stringify(tool));
|
|||
parsed,
|
||||
serde_json::json!({
|
||||
"name": "mcp__rmcp__echo",
|
||||
"description": "Echo back the provided message and include environment data.\n\nCode mode declaration:\n```ts\ndeclare const tools: {\n mcp__rmcp__echo(args: {\n env_var?: string;\n message: string;\n }): Promise<{\n _meta?: unknown;\n content: Array<unknown>;\n isError?: boolean;\n structuredContent?: unknown;\n }>;\n};\n```",
|
||||
"description": "Echo back the provided message and include environment data.\n\nexec tool declaration:\n```ts\ndeclare const tools: { mcp__rmcp__echo(args: { env_var?: string; message: string; }): Promise<{ _meta?: unknown; content: Array<unknown>; isError?: boolean; structuredContent?: unknown; }>; };\n```",
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue