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:
pakrym-oai 2026-03-13 10:08:34 -07:00 committed by GitHub
parent 8e89e9eded
commit 9c9867c9fa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 31 additions and 59 deletions

View file

@ -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 {

View file

@ -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```"
);
}

View file

@ -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```"
);
}

View file

@ -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```",
})
);