feat: display subagent tool calls in CLI and UI (#6535)
Signed-off-by: rabi <ramishra@redhat.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user