chore: break up process agent response (#6348)
This commit is contained in:
+352
-306
@@ -864,13 +864,6 @@ impl CliSession {
|
||||
let is_json_mode = self.output_format == "json";
|
||||
let is_stream_json_mode = self.output_format == "stream-json";
|
||||
|
||||
// Helper to emit a streaming JSON event
|
||||
let emit_stream_event = |event: &StreamEvent| {
|
||||
if let Ok(json) = serde_json::to_string(event) {
|
||||
println!("{}", json);
|
||||
}
|
||||
};
|
||||
|
||||
let session_config = SessionConfig {
|
||||
id: self.session_id.clone(),
|
||||
schedule_id: self.scheduled_job_id.clone(),
|
||||
@@ -908,91 +901,30 @@ impl CliSession {
|
||||
result = stream.next() => {
|
||||
match result {
|
||||
Some(Ok(AgentEvent::Message(message))) => {
|
||||
let tool_call_confirmation = message.content.iter().find_map(|content| {
|
||||
if let MessageContent::ActionRequired(action) = content {
|
||||
#[allow(irrefutable_let_patterns)] // this is a one variant enum right now but it will have more
|
||||
if let ActionRequiredData::ToolConfirmation { id, tool_name, arguments, prompt } = &action.data {
|
||||
Some((id.clone(), tool_name.clone(), arguments.clone(), prompt.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let elicitation_request = message.content.iter().find_map(|content| {
|
||||
if let MessageContent::ActionRequired(action) = content {
|
||||
if let ActionRequiredData::Elicitation { id, message, requested_schema } = &action.data {
|
||||
Some((id.clone(), message.clone(), requested_schema.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if let Some((id, _tool_name, _arguments, security_prompt)) = tool_call_confirmation {
|
||||
output::hide_thinking();
|
||||
|
||||
// Format the confirmation prompt - use security message if present, otherwise use generic message
|
||||
let prompt = if let Some(security_message) = &security_prompt {
|
||||
println!("\n{}", security_message);
|
||||
"Do you allow this tool call?".to_string()
|
||||
} else {
|
||||
"Goose would like to call the above tool, do you allow?".to_string()
|
||||
};
|
||||
|
||||
// Get confirmation from user
|
||||
let permission_result = if security_prompt.is_none() {
|
||||
// No security message - show all options including "Always Allow"
|
||||
cliclack::select(prompt)
|
||||
.item(Permission::AllowOnce, "Allow", "Allow the tool call once")
|
||||
.item(Permission::AlwaysAllow, "Always Allow", "Always allow the tool call")
|
||||
.item(Permission::DenyOnce, "Deny", "Deny the tool call")
|
||||
.item(Permission::Cancel, "Cancel", "Cancel the AI response and tool call")
|
||||
.interact()
|
||||
} else {
|
||||
// Security message present - don't show "Always Allow"
|
||||
cliclack::select(prompt)
|
||||
.item(Permission::AllowOnce, "Allow", "Allow the tool call once")
|
||||
.item(Permission::DenyOnce, "Deny", "Deny the tool call")
|
||||
.item(Permission::Cancel, "Cancel", "Cancel the AI response and tool call")
|
||||
.interact()
|
||||
};
|
||||
|
||||
let permission = match permission_result {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
if e.kind() == std::io::ErrorKind::Interrupted {
|
||||
Permission::Cancel
|
||||
} else {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some((id, security_prompt)) = find_tool_confirmation(&message) {
|
||||
let permission = prompt_tool_confirmation(&security_prompt)?;
|
||||
|
||||
if permission == Permission::Cancel {
|
||||
output::render_text("Tool call cancelled. Returning to chat...", Some(Color::Yellow), true);
|
||||
|
||||
let mut response_message = Message::user();
|
||||
response_message.content.push(MessageContent::tool_response(
|
||||
id.clone(),
|
||||
Err(ErrorData { code: ErrorCode::INVALID_REQUEST, message: std::borrow::Cow::from("Tool call cancelled by user".to_string()), data: None })
|
||||
id,
|
||||
Err(ErrorData {
|
||||
code: ErrorCode::INVALID_REQUEST,
|
||||
message: std::borrow::Cow::from("Tool call cancelled by user"),
|
||||
data: None,
|
||||
}),
|
||||
));
|
||||
self.messages.push(response_message);
|
||||
cancel_token_clone.cancel();
|
||||
drop(stream);
|
||||
break;
|
||||
} else {
|
||||
self.agent.handle_confirmation(id.clone(), PermissionConfirmation {
|
||||
principal_type: PrincipalType::Tool,
|
||||
permission,
|
||||
}).await;
|
||||
}
|
||||
}
|
||||
else if let Some((elicitation_id, elicitation_message, schema)) = elicitation_request {
|
||||
self.agent.handle_confirmation(id, PermissionConfirmation {
|
||||
principal_type: PrincipalType::Tool,
|
||||
permission,
|
||||
}).await;
|
||||
} else if let Some((elicitation_id, elicitation_message, schema)) = find_elicitation_request(&message) {
|
||||
output::hide_thinking();
|
||||
let _ = progress_bars.hide();
|
||||
|
||||
@@ -1000,25 +932,16 @@ impl CliSession {
|
||||
Ok(Some(user_data)) => {
|
||||
let user_data_value = serde_json::to_value(user_data)
|
||||
.unwrap_or(serde_json::Value::Object(serde_json::Map::new()));
|
||||
|
||||
let response_message = Message::user()
|
||||
.with_content(MessageContent::action_required_elicitation_response(
|
||||
elicitation_id.clone(),
|
||||
elicitation_id,
|
||||
user_data_value,
|
||||
))
|
||||
.with_visibility(false, true);
|
||||
|
||||
self.messages.push(response_message.clone());
|
||||
// Elicitation responses return an empty stream - the response
|
||||
// unblocks the waiting tool call via ActionRequiredManager
|
||||
let _ = self
|
||||
.agent
|
||||
.reply(
|
||||
response_message,
|
||||
session_config.clone(),
|
||||
Some(cancel_token.clone()),
|
||||
)
|
||||
.await?;
|
||||
let _ = self.agent.reply(response_message, session_config.clone(), Some(cancel_token.clone())).await?;
|
||||
}
|
||||
Ok(None) => {
|
||||
output::render_text("Information request cancelled.", Some(Color::Yellow), true);
|
||||
@@ -1033,57 +956,13 @@ impl CliSession {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for content in &message.content {
|
||||
if let MessageContent::ToolRequest(tool_request) = content {
|
||||
if let Ok(tool_call) = &tool_request.tool_call {
|
||||
tracing::info!(counter.goose.tool_calls = 1,
|
||||
tool_name = %tool_call.name,
|
||||
"Tool call started"
|
||||
);
|
||||
}
|
||||
}
|
||||
if let MessageContent::ToolResponse(tool_response) = content {
|
||||
let tool_name = self.messages
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|msg| {
|
||||
msg.content.iter().find_map(|c| {
|
||||
if let MessageContent::ToolRequest(req) = c {
|
||||
if req.id == tool_response.id {
|
||||
if let Ok(tool_call) = &req.tool_call {
|
||||
Some(tool_call.name.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| "unknown".to_string().into());
|
||||
|
||||
let success = tool_response.tool_result.is_ok();
|
||||
let result_status = if success { "success" } else { "error" };
|
||||
tracing::info!(
|
||||
counter.goose.tool_completions = 1,
|
||||
tool_name = %tool_name,
|
||||
result = %result_status,
|
||||
"Tool call completed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
log_tool_metrics(&message, &self.messages);
|
||||
self.messages.push(message.clone());
|
||||
|
||||
if interactive {output::hide_thinking()};
|
||||
if interactive { output::hide_thinking() };
|
||||
let _ = progress_bars.hide();
|
||||
|
||||
// Handle different output formats
|
||||
if is_stream_json_mode {
|
||||
emit_stream_event(&StreamEvent::Message { message: message.clone() });
|
||||
} else if !is_json_mode {
|
||||
@@ -1091,181 +970,29 @@ impl CliSession {
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(Ok(AgentEvent::McpNotification((extension_id, message)))) => {
|
||||
match &message {
|
||||
ServerNotification::LoggingMessageNotification(notification) => {
|
||||
let data = ¬ification.params.data;
|
||||
let (formatted_message, subagent_id, message_notification_type) = match data {
|
||||
Value::String(s) => (s.clone(), None, None),
|
||||
Value::Object(o) => {
|
||||
// Check for subagent notification structure first
|
||||
if let Some(Value::String(msg)) = o.get("message") {
|
||||
// Extract subagent info for better display
|
||||
let subagent_id = o.get("subagent_id")
|
||||
.and_then(|v| v.as_str());
|
||||
let notification_type = o.get("type")
|
||||
.and_then(|v| v.as_str());
|
||||
|
||||
let formatted = match notification_type {
|
||||
Some("subagent_created") | Some("completed") | Some("terminated") => {
|
||||
format!("🤖 {}", msg)
|
||||
}
|
||||
Some("tool_usage") | Some("tool_completed") | Some("tool_error") => {
|
||||
format!("🔧 {}", msg)
|
||||
}
|
||||
Some("message_processing") | Some("turn_progress") => {
|
||||
format!("💭 {}", msg)
|
||||
}
|
||||
Some("response_generated") => {
|
||||
// Check verbosity setting for subagent response content
|
||||
let config = Config::global();
|
||||
let min_priority = config
|
||||
.get_param::<f32>("GOOSE_CLI_MIN_PRIORITY")
|
||||
.ok()
|
||||
.unwrap_or(0.5);
|
||||
|
||||
if min_priority > 0.1 && !self.debug {
|
||||
// High/Medium verbosity: show truncated response
|
||||
if let Some(response_content) = msg.strip_prefix("Responded: ") {
|
||||
format!("🤖 Responded: {}", safe_truncate(response_content, 100))
|
||||
} else {
|
||||
format!("🤖 {}", msg)
|
||||
}
|
||||
} else {
|
||||
// All verbosity or debug: show full response
|
||||
format!("🤖 {}", msg)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
msg.to_string()
|
||||
}
|
||||
};
|
||||
(formatted, subagent_id.map(str::to_string), notification_type.map(str::to_string))
|
||||
} else if let Some(Value::String(output)) = o.get("output") {
|
||||
// Extract type if present (e.g., "shell_output")
|
||||
let notification_type = o.get("type")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(str::to_string);
|
||||
|
||||
(output.to_owned(), None, notification_type)
|
||||
} else if let Some(result) = format_task_execution_notification(data) {
|
||||
result
|
||||
} else {
|
||||
(data.to_string(), None, None)
|
||||
}
|
||||
},
|
||||
v => {
|
||||
(v.to_string(), None, None)
|
||||
},
|
||||
};
|
||||
|
||||
if is_stream_json_mode {
|
||||
emit_stream_event(&StreamEvent::Notification {
|
||||
extension_id: extension_id.clone(),
|
||||
data: NotificationData::Log { message: formatted_message.clone() },
|
||||
});
|
||||
}
|
||||
// Handle subagent notifications - show immediately
|
||||
else if let Some(_id) = subagent_id {
|
||||
if interactive {
|
||||
let _ = progress_bars.hide();
|
||||
if !is_json_mode {
|
||||
println!("{}", console::style(&formatted_message).green().dim());
|
||||
}
|
||||
} else if !is_json_mode {
|
||||
progress_bars.log(&formatted_message);
|
||||
}
|
||||
} else if let Some(ref notification_type) = message_notification_type {
|
||||
if notification_type == TASK_EXECUTION_NOTIFICATION_TYPE {
|
||||
if interactive {
|
||||
let _ = progress_bars.hide();
|
||||
if !is_json_mode {
|
||||
print!("{}", formatted_message);
|
||||
std::io::stdout().flush().unwrap();
|
||||
}
|
||||
} else if !is_json_mode {
|
||||
print!("{}", formatted_message);
|
||||
std::io::stdout().flush().unwrap();
|
||||
}
|
||||
} else if notification_type == "shell_output" {
|
||||
if interactive {
|
||||
let _ = progress_bars.hide();
|
||||
}
|
||||
if !is_json_mode {
|
||||
println!("{}", formatted_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if output::is_showing_thinking() {
|
||||
output::set_thinking_message(&formatted_message);
|
||||
} else {
|
||||
progress_bars.log(&formatted_message);
|
||||
}
|
||||
},
|
||||
ServerNotification::ProgressNotification(notification) => {
|
||||
let progress = notification.params.progress;
|
||||
let text = notification.params.message.as_deref();
|
||||
let total = notification.params.total;
|
||||
let token = ¬ification.params.progress_token;
|
||||
|
||||
if is_stream_json_mode {
|
||||
emit_stream_event(&StreamEvent::Notification {
|
||||
extension_id: extension_id.clone(),
|
||||
data: NotificationData::Progress {
|
||||
progress,
|
||||
total,
|
||||
message: text.map(String::from),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
progress_bars.update(
|
||||
&token.0.to_string(),
|
||||
progress,
|
||||
total,
|
||||
text,
|
||||
);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
Some(Ok(AgentEvent::McpNotification((extension_id, notification)))) => {
|
||||
handle_mcp_notification(
|
||||
&extension_id,
|
||||
¬ification,
|
||||
&mut progress_bars,
|
||||
is_stream_json_mode,
|
||||
interactive,
|
||||
is_json_mode,
|
||||
self.debug,
|
||||
);
|
||||
}
|
||||
Some(Ok(AgentEvent::HistoryReplaced(updated_conversation))) => {
|
||||
self.messages = updated_conversation;
|
||||
}
|
||||
Some(Ok(AgentEvent::ModelChange { model, mode })) => {
|
||||
if is_stream_json_mode {
|
||||
emit_stream_event(&StreamEvent::ModelChange {
|
||||
model: model.clone(),
|
||||
mode: mode.clone(),
|
||||
});
|
||||
emit_stream_event(&StreamEvent::ModelChange { model: model.clone(), mode: mode.clone() });
|
||||
} else if self.debug {
|
||||
eprintln!("Model changed to {} in {} mode", model, mode);
|
||||
}
|
||||
}
|
||||
|
||||
Some(Err(e)) => {
|
||||
let error_msg = e.to_string();
|
||||
|
||||
if is_stream_json_mode {
|
||||
emit_stream_event(&StreamEvent::Error { error: error_msg.clone() });
|
||||
}
|
||||
|
||||
if e.downcast_ref::<goose::providers::errors::ProviderError>()
|
||||
.map(|provider_error| matches!(provider_error, goose::providers::errors::ProviderError::ContextLengthExceeded(_)))
|
||||
.unwrap_or(false) {
|
||||
|
||||
if !is_stream_json_mode {
|
||||
output::render_text(
|
||||
"Compaction requested. Should have happened in the agent!",
|
||||
Some(Color::Yellow),
|
||||
true
|
||||
);
|
||||
}
|
||||
warn!("Compaction requested. Should have happened in the agent!");
|
||||
}
|
||||
if !is_stream_json_mode {
|
||||
eprintln!("Error: {}", error_msg);
|
||||
}
|
||||
handle_agent_error(&e, is_stream_json_mode);
|
||||
cancel_token_clone.cancel();
|
||||
drop(stream);
|
||||
if let Err(e) = self.handle_interrupted_messages(false).await {
|
||||
@@ -1293,7 +1020,6 @@ impl CliSession {
|
||||
}
|
||||
}
|
||||
|
||||
// Output based on format
|
||||
if is_json_mode {
|
||||
let metadata = match SessionManager::get_session(&self.session_id, false).await {
|
||||
Ok(session) => JsonMetadata {
|
||||
@@ -1305,12 +1031,10 @@ impl CliSession {
|
||||
status: "completed".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let json_output = JsonOutput {
|
||||
messages: self.messages.messages().to_vec(),
|
||||
metadata,
|
||||
};
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&json_output)?);
|
||||
} else if is_stream_json_mode {
|
||||
let total_tokens = SessionManager::get_session(&self.session_id, false)
|
||||
@@ -1642,6 +1366,328 @@ impl CliSession {
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_stream_event(event: &StreamEvent) {
|
||||
if let Ok(json) = serde_json::to_string(event) {
|
||||
println!("{}", json);
|
||||
}
|
||||
}
|
||||
|
||||
/// Prompt user for tool call confirmation, returns the Permission selected
|
||||
fn prompt_tool_confirmation(security_prompt: &Option<String>) -> Result<Permission> {
|
||||
output::hide_thinking();
|
||||
|
||||
let prompt = if let Some(security_message) = security_prompt {
|
||||
println!("\n{}", security_message);
|
||||
"Do you allow this tool call?".to_string()
|
||||
} else {
|
||||
"Goose would like to call the above tool, do you allow?".to_string()
|
||||
};
|
||||
|
||||
let permission_result = if security_prompt.is_none() {
|
||||
cliclack::select(prompt)
|
||||
.item(Permission::AllowOnce, "Allow", "Allow the tool call once")
|
||||
.item(
|
||||
Permission::AlwaysAllow,
|
||||
"Always Allow",
|
||||
"Always allow the tool call",
|
||||
)
|
||||
.item(Permission::DenyOnce, "Deny", "Deny the tool call")
|
||||
.item(
|
||||
Permission::Cancel,
|
||||
"Cancel",
|
||||
"Cancel the AI response and tool call",
|
||||
)
|
||||
.interact()
|
||||
} else {
|
||||
cliclack::select(prompt)
|
||||
.item(Permission::AllowOnce, "Allow", "Allow the tool call once")
|
||||
.item(Permission::DenyOnce, "Deny", "Deny the tool call")
|
||||
.item(
|
||||
Permission::Cancel,
|
||||
"Cancel",
|
||||
"Cancel the AI response and tool call",
|
||||
)
|
||||
.interact()
|
||||
};
|
||||
|
||||
match permission_result {
|
||||
Ok(p) => Ok(p),
|
||||
Err(e) => {
|
||||
if e.kind() == std::io::ErrorKind::Interrupted {
|
||||
Ok(Permission::Cancel)
|
||||
} else {
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract tool confirmation request from a message
|
||||
fn find_tool_confirmation(message: &Message) -> Option<(String, Option<String>)> {
|
||||
message.content.iter().find_map(|content| {
|
||||
if let MessageContent::ActionRequired(action) = content {
|
||||
if let ActionRequiredData::ToolConfirmation { id, prompt, .. } = &action.data {
|
||||
return Some((id.clone(), prompt.clone()));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract elicitation request from a message
|
||||
fn find_elicitation_request(message: &Message) -> Option<(String, String, Value)> {
|
||||
message.content.iter().find_map(|content| {
|
||||
if let MessageContent::ActionRequired(action) = content {
|
||||
if let ActionRequiredData::Elicitation {
|
||||
id,
|
||||
message,
|
||||
requested_schema,
|
||||
} = &action.data
|
||||
{
|
||||
return Some((id.clone(), message.clone(), requested_schema.clone()));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Handle MCP notification event (logging or progress)
|
||||
fn handle_mcp_notification(
|
||||
extension_id: &str,
|
||||
notification: &ServerNotification,
|
||||
progress_bars: &mut output::McpSpinners,
|
||||
is_stream_json_mode: bool,
|
||||
interactive: bool,
|
||||
is_json_mode: bool,
|
||||
debug: bool,
|
||||
) {
|
||||
match notification {
|
||||
ServerNotification::LoggingMessageNotification(log_notif) => {
|
||||
let (formatted, subagent_id, notif_type) =
|
||||
format_logging_notification(&log_notif.params.data, debug);
|
||||
|
||||
if is_stream_json_mode {
|
||||
emit_stream_event(&StreamEvent::Notification {
|
||||
extension_id: extension_id.to_string(),
|
||||
data: NotificationData::Log {
|
||||
message: formatted.clone(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
display_log_notification(
|
||||
&formatted,
|
||||
subagent_id.as_deref(),
|
||||
notif_type.as_deref(),
|
||||
progress_bars,
|
||||
interactive,
|
||||
is_json_mode,
|
||||
);
|
||||
}
|
||||
}
|
||||
ServerNotification::ProgressNotification(prog_notif) => {
|
||||
if is_stream_json_mode {
|
||||
emit_stream_event(&StreamEvent::Notification {
|
||||
extension_id: extension_id.to_string(),
|
||||
data: NotificationData::Progress {
|
||||
progress: prog_notif.params.progress,
|
||||
total: prog_notif.params.total,
|
||||
message: prog_notif.params.message.clone(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
progress_bars.update(
|
||||
&prog_notif.params.progress_token.0.to_string(),
|
||||
prog_notif.params.progress,
|
||||
prog_notif.params.total,
|
||||
prog_notif.params.message.as_deref(),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a logging notification from MCP, returns (formatted_message, subagent_id, notification_type)
|
||||
fn format_logging_notification(
|
||||
data: &Value,
|
||||
debug: bool,
|
||||
) -> (String, Option<String>, Option<String>) {
|
||||
match data {
|
||||
Value::String(s) => (s.clone(), None, None),
|
||||
Value::Object(o) => {
|
||||
if let Some(Value::String(msg)) = o.get("message") {
|
||||
let subagent_id = o.get("subagent_id").and_then(|v| v.as_str());
|
||||
let notification_type = o.get("type").and_then(|v| v.as_str());
|
||||
|
||||
let formatted = match notification_type {
|
||||
Some("subagent_created") | Some("completed") | Some("terminated") => {
|
||||
format!("🤖 {}", msg)
|
||||
}
|
||||
Some("tool_usage") | Some("tool_completed") | Some("tool_error") => {
|
||||
format!("🔧 {}", msg)
|
||||
}
|
||||
Some("message_processing") | Some("turn_progress") => {
|
||||
format!("💭 {}", msg)
|
||||
}
|
||||
Some("response_generated") => {
|
||||
let config = Config::global();
|
||||
let min_priority = config
|
||||
.get_param::<f32>("GOOSE_CLI_MIN_PRIORITY")
|
||||
.ok()
|
||||
.unwrap_or(0.5);
|
||||
|
||||
if min_priority > 0.1 && !debug {
|
||||
if let Some(response_content) = msg.strip_prefix("Responded: ") {
|
||||
format!("🤖 Responded: {}", safe_truncate(response_content, 100))
|
||||
} else {
|
||||
format!("🤖 {}", msg)
|
||||
}
|
||||
} else {
|
||||
format!("🤖 {}", msg)
|
||||
}
|
||||
}
|
||||
_ => msg.to_string(),
|
||||
};
|
||||
(
|
||||
formatted,
|
||||
subagent_id.map(str::to_string),
|
||||
notification_type.map(str::to_string),
|
||||
)
|
||||
} else if let Some(Value::String(output)) = o.get("output") {
|
||||
let notification_type = o.get("type").and_then(|v| v.as_str()).map(str::to_string);
|
||||
(output.to_owned(), None, notification_type)
|
||||
} else if let Some(result) = format_task_execution_notification(data) {
|
||||
result
|
||||
} else {
|
||||
(data.to_string(), None, None)
|
||||
}
|
||||
}
|
||||
v => (v.to_string(), None, None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Display a logging notification based on its type and context
|
||||
fn display_log_notification(
|
||||
formatted_message: &str,
|
||||
subagent_id: Option<&str>,
|
||||
notification_type: Option<&str>,
|
||||
progress_bars: &mut output::McpSpinners,
|
||||
interactive: bool,
|
||||
is_json_mode: bool,
|
||||
) {
|
||||
if subagent_id.is_some() {
|
||||
if interactive {
|
||||
let _ = progress_bars.hide();
|
||||
if !is_json_mode {
|
||||
println!("{}", console::style(formatted_message).green().dim());
|
||||
}
|
||||
} else if !is_json_mode {
|
||||
progress_bars.log(formatted_message);
|
||||
}
|
||||
} else if let Some(ntype) = notification_type {
|
||||
if ntype == TASK_EXECUTION_NOTIFICATION_TYPE {
|
||||
if interactive {
|
||||
let _ = progress_bars.hide();
|
||||
}
|
||||
if !is_json_mode {
|
||||
print!("{}", formatted_message);
|
||||
std::io::stdout().flush().unwrap();
|
||||
}
|
||||
} else if ntype == "shell_output" {
|
||||
if interactive {
|
||||
let _ = progress_bars.hide();
|
||||
}
|
||||
if !is_json_mode {
|
||||
println!("{}", formatted_message);
|
||||
}
|
||||
}
|
||||
} else if output::is_showing_thinking() {
|
||||
output::set_thinking_message(&formatted_message.to_string());
|
||||
} else {
|
||||
progress_bars.log(formatted_message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Log tool request/response metrics
|
||||
fn log_tool_metrics(message: &Message, messages: &Conversation) {
|
||||
for content in &message.content {
|
||||
if let MessageContent::ToolRequest(tool_request) = content {
|
||||
if let Ok(tool_call) = &tool_request.tool_call {
|
||||
tracing::info!(
|
||||
counter.goose.tool_calls = 1,
|
||||
tool_name = %tool_call.name,
|
||||
"Tool call started"
|
||||
);
|
||||
}
|
||||
}
|
||||
if let MessageContent::ToolResponse(tool_response) = content {
|
||||
let tool_name = messages
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|msg| {
|
||||
msg.content.iter().find_map(|c| {
|
||||
if let MessageContent::ToolRequest(req) = c {
|
||||
if req.id == tool_response.id {
|
||||
req.tool_call.as_ref().ok().map(|tc| tc.name.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| "unknown".to_string().into());
|
||||
|
||||
let result_status = if tool_response.tool_result.is_ok() {
|
||||
"success"
|
||||
} else {
|
||||
"error"
|
||||
};
|
||||
tracing::info!(
|
||||
counter.goose.tool_completions = 1,
|
||||
tool_name = %tool_name,
|
||||
result = %result_status,
|
||||
"Tool call completed"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle and display an agent error
|
||||
fn handle_agent_error(e: &anyhow::Error, is_stream_json_mode: bool) {
|
||||
let error_msg = e.to_string();
|
||||
|
||||
if is_stream_json_mode {
|
||||
emit_stream_event(&StreamEvent::Error {
|
||||
error: error_msg.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if e.downcast_ref::<goose::providers::errors::ProviderError>()
|
||||
.map(|provider_error| {
|
||||
matches!(
|
||||
provider_error,
|
||||
goose::providers::errors::ProviderError::ContextLengthExceeded(_)
|
||||
)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if !is_stream_json_mode {
|
||||
output::render_text(
|
||||
"Compaction requested. Should have happened in the agent!",
|
||||
Some(Color::Yellow),
|
||||
true,
|
||||
);
|
||||
}
|
||||
warn!("Compaction requested. Should have happened in the agent!");
|
||||
}
|
||||
|
||||
if !is_stream_json_mode {
|
||||
eprintln!("Error: {}", error_msg);
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_reasoner() -> Result<Arc<dyn Provider>, anyhow::Error> {
|
||||
use goose::model::ModelConfig;
|
||||
use goose::providers::create;
|
||||
|
||||
Reference in New Issue
Block a user