feat: display subagent tool calls in CLI and UI (#6535)

Signed-off-by: rabi <ramishra@redhat.com>
This commit is contained in:
Rabi Mishra
2026-02-04 02:26:59 +05:30
committed by GitHub
parent 7ee634eaa7
commit 1e645c0212
5 changed files with 486 additions and 114 deletions
+44
View File
@@ -21,6 +21,7 @@ use tokio_util::task::AbortOnDropHandle;
pub use self::export::message_to_markdown;
pub use builder::{build_session, SessionBuilderConfig};
use console::Color;
use goose::agents::subagent_handler::SUBAGENT_TOOL_REQUEST_TYPE;
use goose::agents::AgentEvent;
use goose::permission::permission_confirmation::PrincipalType;
use goose::permission::Permission;
@@ -1537,6 +1538,49 @@ fn handle_mcp_notification(
) {
match notification {
ServerNotification::LoggingMessageNotification(log_notif) => {
if let Some(obj) = log_notif.params.data.as_object() {
if obj.get("type").and_then(|v| v.as_str()) == Some(SUBAGENT_TOOL_REQUEST_TYPE) {
if let (Some(subagent_id), Some(tool_call)) = (
obj.get("subagent_id").and_then(|v| v.as_str()),
obj.get("tool_call").and_then(|v| v.as_object()),
) {
let tool_name = tool_call
.get("name")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let arguments = tool_call
.get("arguments")
.and_then(|v| v.as_object())
.cloned();
if interactive {
let _ = progress_bars.hide();
}
if is_stream_json_mode {
emit_stream_event(&StreamEvent::Notification {
extension_id: extension_id.to_string(),
data: NotificationData::Log {
message: output::format_subagent_tool_call_message(
subagent_id,
tool_name,
),
},
});
return;
}
if !is_json_mode {
output::render_subagent_tool_call(
subagent_id,
tool_name,
arguments.as_ref(),
debug,
);
return;
}
}
}
}
let (formatted, subagent_id, notif_type) =
format_logging_notification(&log_notif.params.data, debug);
+97 -10
View File
@@ -613,21 +613,108 @@ fn render_default_request(call: &CallToolRequestParams, debug: bool) {
println!();
}
fn split_tool_name(tool_name: &str) -> (String, String) {
let parts: Vec<_> = tool_name.rsplit("__").collect();
let tool = parts.first().copied().unwrap_or("unknown");
let extension = parts
.split_first()
.map(|(_, s)| s.iter().rev().copied().collect::<Vec<_>>().join("__"))
.unwrap_or_default();
(tool.to_string(), extension)
}
pub fn format_subagent_tool_call_message(subagent_id: &str, tool_name: &str) -> String {
let short_id = subagent_id.rsplit('_').next().unwrap_or(subagent_id);
let (tool, extension) = split_tool_name(tool_name);
if extension.is_empty() {
format!("[subagent:{}] {}", short_id, tool)
} else {
format!("[subagent:{}] {} | {}", short_id, tool, extension)
}
}
pub fn render_subagent_tool_call(
subagent_id: &str,
tool_name: &str,
arguments: Option<&JsonObject>,
debug: bool,
) {
if tool_name == "code_execution__execute_code" {
let tool_graph = arguments
.and_then(|args| args.get("tool_graph"))
.and_then(Value::as_array)
.filter(|arr| !arr.is_empty());
if let Some(tool_graph) = tool_graph {
return render_subagent_tool_graph(subagent_id, tool_graph);
}
}
let tool_header = format!(
"─── {} ──────────────────────────",
style(format_subagent_tool_call_message(subagent_id, tool_name))
.magenta()
.dim()
);
println!();
println!("{}", tool_header);
print_params(&arguments.cloned(), 0, debug);
println!();
}
fn render_subagent_tool_graph(subagent_id: &str, tool_graph: &[Value]) {
let short_id = subagent_id.rsplit('_').next().unwrap_or(subagent_id);
let count = tool_graph.len();
let plural = if count == 1 { "" } else { "s" };
println!();
println!(
"─── {} {} tool call{} | {} ──────────────────────────",
style(format!("[subagent:{}]", short_id)).cyan(),
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!();
}
// Helper functions
fn print_tool_header(call: &CallToolRequestParams) {
let parts: Vec<_> = call.name.rsplit("__").collect();
let (tool, extension) = split_tool_name(&call.name);
let tool_header = format!(
"─── {} | {} ──────────────────────────",
style(parts.first().unwrap_or(&"unknown")),
style(
parts
.split_first()
.map(|(_, s)| s.iter().rev().copied().collect::<Vec<_>>().join("__"))
.unwrap_or_else(|| "unknown".to_string())
)
.magenta()
.dim(),
style(tool),
style(extension).magenta().dim(),
);
println!();
println!("{}", tool_header);