Expose strongly-typed result for exec_command (#14183)

Summary
- document output types for the various tool handlers and registry so
the API exposes richer descriptions
- update unified execution helpers and client tests to align with the
new output metadata
- clean up unused helpers across tool dispatch paths

Testing
- Not run (not requested)
This commit is contained in:
pakrym-oai
2026-03-10 09:54:34 -07:00
committed by GitHub
parent e4edafe1a8
commit e52afd28b0
12 changed files with 278 additions and 154 deletions

View File

@@ -40,6 +40,40 @@ use std::collections::HashMap;
const SEARCH_TOOL_BM25_DESCRIPTION_TEMPLATE: &str =
include_str!("../../templates/search_tool/tool_description.md");
const WEB_SEARCH_CONTENT_TYPES: [&str; 2] = ["text", "image"];
fn unified_exec_output_schema() -> JsonValue {
json!({
"type": "object",
"properties": {
"chunk_id": {
"type": "string",
"description": "Chunk identifier included when the response reports one."
},
"wall_time_seconds": {
"type": "number",
"description": "Elapsed wall time spent waiting for output in seconds."
},
"exit_code": {
"type": "number",
"description": "Process exit code when the command finished during this call."
},
"session_id": {
"type": "string",
"description": "Session identifier to pass to write_stdin when the process is still running."
},
"original_token_count": {
"type": "number",
"description": "Approximate token count before output truncation."
},
"output": {
"type": "string",
"description": "Command output text, possibly truncated."
}
},
"required": ["wall_time_seconds", "output"],
"additionalProperties": false
})
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ShellCommandBackendConfig {
Classic,
@@ -479,6 +513,7 @@ fn create_exec_command_tool(allow_login_shell: bool, request_permission_enabled:
required: Some(vec!["cmd".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: Some(unified_exec_output_schema()),
})
}
@@ -526,6 +561,7 @@ fn create_write_stdin_tool() -> ToolSpec {
required: Some(vec!["session_id".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: Some(unified_exec_output_schema()),
})
}
@@ -579,6 +615,7 @@ Examples of valid command strings:
required: Some(vec!["command".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -646,6 +683,7 @@ Examples of valid command strings:
required: Some(vec!["command".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -668,6 +706,7 @@ fn create_view_image_tool() -> ToolSpec {
required: Some(vec!["path".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -793,6 +832,7 @@ fn create_spawn_agent_tool(config: &ToolsConfig) -> ToolSpec {
required: None,
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -869,6 +909,7 @@ fn create_spawn_agents_on_csv_tool() -> ToolSpec {
required: Some(vec!["csv_path".to_string(), "instruction".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -918,6 +959,7 @@ fn create_report_agent_job_result_tool() -> ToolSpec {
]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -960,6 +1002,7 @@ fn create_send_input_tool() -> ToolSpec {
required: Some(vec!["id".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -983,6 +1026,7 @@ fn create_resume_agent_tool() -> ToolSpec {
required: Some(vec!["id".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1017,6 +1061,7 @@ fn create_wait_tool() -> ToolSpec {
required: Some(vec!["ids".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1102,6 +1147,7 @@ fn create_request_user_input_tool(
required: Some(vec!["questions".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1126,6 +1172,7 @@ fn create_request_permissions_tool() -> ToolSpec {
required: Some(vec!["permissions".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1148,6 +1195,7 @@ fn create_close_agent_tool() -> ToolSpec {
required: Some(vec!["id".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1215,6 +1263,7 @@ fn create_test_sync_tool() -> ToolSpec {
required: None,
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1266,6 +1315,7 @@ fn create_grep_files_tool() -> ToolSpec {
required: Some(vec!["pattern".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1311,6 +1361,7 @@ fn create_search_tool_bm25_tool(app_tools: &HashMap<String, ToolInfo>) -> ToolSp
required: Some(vec!["query".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1414,6 +1465,7 @@ fn create_read_file_tool() -> ToolSpec {
required: Some(vec!["file_path".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1460,6 +1512,7 @@ fn create_list_dir_tool() -> ToolSpec {
required: Some(vec!["dir_path".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1534,6 +1587,7 @@ fn create_js_repl_reset_tool() -> ToolSpec {
required: None,
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1549,7 +1603,7 @@ source: /[\s\S]+/
enabled_tool_names.join(", ")
};
let description = format!(
"Runs JavaScript in a Node-backed `node:vm` context. This is a freeform tool: send raw JavaScript source text (no JSON/quotes/markdown fences). Direct tool calls remain available while `code_mode` is enabled. Inside JavaScript, import nested tools from `tools.js`, for example `import {{ exec_command }} from \"tools.js\"` or `import {{ tools }} from \"tools.js\"`. `tools[name]` and identifier wrappers like `await shell(args)` remain available for compatibility when the tool name is a valid JS identifier. Nested tool calls resolve to arrays of content items. Function tools require JSON object arguments. Freeform tools require raw strings. Use synchronous `add_content(value)` with a content item or content-item array, including `add_content(await exec_command(...))`, to return the same content items a direct tool call would expose to the model. Only content passed to `add_content(value)` is surfaced back to the model. Enabled nested tools: {enabled_list}."
"Runs JavaScript in a Node-backed `node:vm` context. This is a freeform tool: send raw JavaScript source text (no JSON/quotes/markdown fences). Direct tool calls remain available while `code_mode` is enabled. Inside JavaScript, import nested tools from `tools.js`, for example `import {{ exec_command }} from \"tools.js\"` or `import {{ tools }} from \"tools.js\"`. `tools[name]` and identifier wrappers like `await shell(args)` remain available for compatibility when the tool name is a valid JS identifier. Nested tool calls resolve to their code-mode result values. Function tools require JSON object arguments. Freeform tools require raw strings. Use synchronous `add_content(value)` with a content item, content-item array, or string. Structured nested-tool results should be converted to text first, for example with `JSON.stringify(...)`. Only content passed to `add_content(value)` is surfaced back to the model. Enabled nested tools: {enabled_list}."
);
ToolSpec::Freeform(FreeformTool {
@@ -1594,6 +1648,7 @@ fn create_list_mcp_resources_tool() -> ToolSpec {
required: None,
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1628,6 +1683,7 @@ fn create_list_mcp_resource_templates_tool() -> ToolSpec {
required: None,
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1664,6 +1720,7 @@ fn create_read_mcp_resource_tool() -> ToolSpec {
required: Some(vec!["server".to_string(), "uri".to_string()]),
additional_properties: Some(false.into()),
},
output_schema: None,
})
}
@@ -1726,6 +1783,7 @@ pub(crate) fn mcp_tool_to_openai_tool(
description: description.map(Into::into).unwrap_or_default(),
strict: false,
parameters: input_schema,
output_schema: None,
})
}
@@ -1739,6 +1797,7 @@ fn dynamic_tool_to_openai_tool(
description: tool.description.clone(),
strict: false,
parameters: input_schema,
output_schema: None,
})
}
@@ -3278,6 +3337,7 @@ mod tests {
},
description: "Do something cool".to_string(),
strict: false,
output_schema: None,
})
);
}
@@ -3516,6 +3576,7 @@ mod tests {
},
description: "Search docs".to_string(),
strict: false,
output_schema: None,
})
);
}
@@ -3567,6 +3628,7 @@ mod tests {
},
description: "Pagination".to_string(),
strict: false,
output_schema: None,
})
);
}
@@ -3622,6 +3684,7 @@ mod tests {
},
description: "Tags".to_string(),
strict: false,
output_schema: None,
})
);
}
@@ -3675,6 +3738,7 @@ mod tests {
},
description: "AnyOf Value".to_string(),
strict: false,
output_schema: None,
})
);
}
@@ -3933,6 +3997,7 @@ Examples of valid command strings:
},
description: "Do something cool".to_string(),
strict: false,
output_schema: None,
})
);
}
@@ -3950,6 +4015,7 @@ Examples of valid command strings:
required: None,
additional_properties: None,
},
output_schema: None,
})];
let responses_json = create_tools_json_for_responses_api(&tools).unwrap();