feat: improved UX for tool calls via execute_code (#6205)
This commit is contained in:
@@ -271,6 +271,7 @@ fn render_tool_request(req: &ToolRequest, theme: Theme, debug: bool) {
|
||||
Ok(call) => match call.name.to_string().as_str() {
|
||||
"developer__text_editor" => render_text_editor_request(call, debug),
|
||||
"developer__shell" => render_shell_request(call, debug),
|
||||
"code_execution__execute_code" => render_execute_code_request(call, debug),
|
||||
"subagent" => render_subagent_request(call, debug),
|
||||
"todo__write" => render_todo_request(call, debug),
|
||||
_ => render_default_request(call, debug),
|
||||
@@ -445,6 +446,61 @@ fn render_shell_request(call: &CallToolRequestParam, debug: bool) {
|
||||
println!();
|
||||
}
|
||||
|
||||
fn render_execute_code_request(call: &CallToolRequestParam, debug: bool) {
|
||||
let tool_graph = call
|
||||
.arguments
|
||||
.as_ref()
|
||||
.and_then(|args| args.get("tool_graph"))
|
||||
.and_then(Value::as_array)
|
||||
.filter(|arr| !arr.is_empty());
|
||||
|
||||
let Some(tool_graph) = tool_graph else {
|
||||
return render_default_request(call, debug);
|
||||
};
|
||||
|
||||
let count = tool_graph.len();
|
||||
let plural = if count == 1 { "" } else { "s" };
|
||||
println!();
|
||||
println!(
|
||||
"─── {} tool call{} | {} ──────────────────────────",
|
||||
style(count).cyan(),
|
||||
plural,
|
||||
style("execute_code").magenta().dim()
|
||||
);
|
||||
|
||||
for (i, node) in tool_graph.iter().filter_map(Value::as_object).enumerate() {
|
||||
let tool = node
|
||||
.get("tool")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("unknown");
|
||||
let desc = node
|
||||
.get("description")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("");
|
||||
let deps: Vec<_> = node
|
||||
.get("depends_on")
|
||||
.and_then(Value::as_array)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(Value::as_u64)
|
||||
.map(|d| (d + 1).to_string())
|
||||
.collect();
|
||||
let deps_str = if deps.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" (uses {})", deps.join(", "))
|
||||
};
|
||||
println!(
|
||||
" {}. {}: {}{}",
|
||||
style(i + 1).dim(),
|
||||
style(tool).cyan(),
|
||||
style(desc).green(),
|
||||
style(deps_str).dim()
|
||||
);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
fn render_subagent_request(call: &CallToolRequestParam, debug: bool) {
|
||||
print_tool_header(call);
|
||||
|
||||
|
||||
@@ -31,10 +31,25 @@ type ToolCallRequest = (
|
||||
tokio::sync::oneshot::Sender<Result<String, String>>,
|
||||
);
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
struct ToolGraphNode {
|
||||
/// Tool name in format "server/tool" (e.g., "developer/shell")
|
||||
tool: String,
|
||||
/// Brief description of what this call does (e.g., "list files in /src")
|
||||
description: String,
|
||||
/// Indices of nodes this depends on (empty if no dependencies)
|
||||
#[serde(default)]
|
||||
depends_on: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
struct ExecuteCodeParams {
|
||||
/// JavaScript code with ES6 imports for MCP tools.
|
||||
code: String,
|
||||
/// DAG of tool calls showing execution flow. Each node represents a tool call.
|
||||
/// Use depends_on to show data flow (e.g., node 1 uses output from node 0).
|
||||
#[serde(default)]
|
||||
tool_graph: Vec<ToolGraphNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -701,6 +716,7 @@ impl McpClientTrait for CodeExecutionClient {
|
||||
Err(Error::TransportClosed)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
async fn list_tools(
|
||||
&self,
|
||||
_next_cursor: Option<String>,
|
||||
@@ -746,6 +762,15 @@ impl McpClientTrait for CodeExecutionClient {
|
||||
- Last expression is the result
|
||||
- No comments in code
|
||||
|
||||
TOOL_GRAPH: Always provide tool_graph to describe the execution flow for the UI.
|
||||
Each node has: tool (server/name), description (what it does), depends_on (indices of dependencies).
|
||||
Example for chained operations:
|
||||
[
|
||||
{"tool": "developer/shell", "description": "list files", "depends_on": []},
|
||||
{"tool": "developer/text_editor", "description": "read README.md", "depends_on": []},
|
||||
{"tool": "developer/text_editor", "description": "write output.txt", "depends_on": [0, 1]}
|
||||
]
|
||||
|
||||
BEFORE CALLING: Use the read_module tool to check required parameters.
|
||||
"#}
|
||||
.to_string(),
|
||||
|
||||
@@ -289,6 +289,7 @@ impl MappingReport {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
async fn build_canonical_models() -> Result<()> {
|
||||
println!("Fetching models from OpenRouter API...");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user