feat: interactive after run (#1377)
This commit is contained in:
@@ -106,6 +106,14 @@ enum Command {
|
|||||||
)]
|
)]
|
||||||
input_text: Option<String>,
|
input_text: Option<String>,
|
||||||
|
|
||||||
|
/// Continue in interactive mode after processing input
|
||||||
|
#[arg(
|
||||||
|
short = 's',
|
||||||
|
long = "interactive",
|
||||||
|
help = "Continue in interactive mode after processing initial input"
|
||||||
|
)]
|
||||||
|
interactive: bool,
|
||||||
|
|
||||||
/// Name for this run session
|
/// Name for this run session
|
||||||
#[arg(
|
#[arg(
|
||||||
short,
|
short,
|
||||||
@@ -182,12 +190,13 @@ async fn main() -> Result<()> {
|
|||||||
}) => {
|
}) => {
|
||||||
let mut session = build_session(name, resume, extension, builtin).await;
|
let mut session = build_session(name, resume, extension, builtin).await;
|
||||||
setup_logging(session.session_file().file_stem().and_then(|s| s.to_str()))?;
|
setup_logging(session.session_file().file_stem().and_then(|s| s.to_str()))?;
|
||||||
let _ = session.start().await;
|
let _ = session.interactive(None).await;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Some(Command::Run {
|
Some(Command::Run {
|
||||||
instructions,
|
instructions,
|
||||||
input_text,
|
input_text,
|
||||||
|
interactive,
|
||||||
name,
|
name,
|
||||||
resume,
|
resume,
|
||||||
extension,
|
extension,
|
||||||
@@ -213,7 +222,12 @@ async fn main() -> Result<()> {
|
|||||||
};
|
};
|
||||||
let mut session = build_session(name, resume, extension, builtin).await;
|
let mut session = build_session(name, resume, extension, builtin).await;
|
||||||
setup_logging(session.session_file().file_stem().and_then(|s| s.to_str()))?;
|
setup_logging(session.session_file().file_stem().and_then(|s| s.to_str()))?;
|
||||||
let _ = session.headless_start(contents.clone()).await;
|
|
||||||
|
if interactive {
|
||||||
|
session.interactive(Some(contents)).await?;
|
||||||
|
} else {
|
||||||
|
session.headless(contents).await?;
|
||||||
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Some(Command::Agents(cmd)) => {
|
Some(Command::Agents(cmd)) => {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use goose::agents::Agent;
|
|||||||
use goose::message::{Message, MessageContent};
|
use goose::message::{Message, MessageContent};
|
||||||
use mcp_core::handler::ToolError;
|
use mcp_core::handler::ToolError;
|
||||||
use rand::{distributions::Alphanumeric, Rng};
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
use rustyline::Editor;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio;
|
use tokio;
|
||||||
|
|
||||||
@@ -104,8 +103,22 @@ impl Session {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(&mut self) -> Result<()> {
|
/// Process a single message and get the response
|
||||||
let mut editor = Editor::<(), rustyline::history::DefaultHistory>::new()?;
|
async fn process_message(&mut self, message: String) -> Result<()> {
|
||||||
|
self.messages.push(Message::user().with_text(&message));
|
||||||
|
storage::persist_messages(&self.session_file, &self.messages)?;
|
||||||
|
self.process_agent_response(false).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start an interactive session, optionally with an initial message
|
||||||
|
pub async fn interactive(&mut self, message: Option<String>) -> Result<()> {
|
||||||
|
// Process initial message if provided
|
||||||
|
if let Some(msg) = message {
|
||||||
|
self.process_message(msg).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut editor = rustyline::Editor::<(), rustyline::history::DefaultHistory>::new()?;
|
||||||
|
|
||||||
// Load history from messages
|
// Load history from messages
|
||||||
for msg in self
|
for msg in self
|
||||||
@@ -129,7 +142,7 @@ impl Session {
|
|||||||
storage::persist_messages(&self.session_file, &self.messages)?;
|
storage::persist_messages(&self.session_file, &self.messages)?;
|
||||||
|
|
||||||
output::show_thinking();
|
output::show_thinking();
|
||||||
self.process_agent_response(&mut editor).await?;
|
self.process_agent_response(true).await?;
|
||||||
output::hide_thinking();
|
output::hide_thinking();
|
||||||
}
|
}
|
||||||
input::InputResult::Exit => break,
|
input::InputResult::Exit => break,
|
||||||
@@ -184,19 +197,12 @@ impl Session {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn headless_start(&mut self, initial_message: String) -> Result<()> {
|
/// Process a single message and exit
|
||||||
self.messages
|
pub async fn headless(&mut self, message: String) -> Result<()> {
|
||||||
.push(Message::user().with_text(&initial_message));
|
self.process_message(message).await
|
||||||
storage::persist_messages(&self.session_file, &self.messages)?;
|
|
||||||
let mut editor = Editor::<(), rustyline::history::DefaultHistory>::new()?;
|
|
||||||
self.process_agent_response(&mut editor).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_agent_response(
|
async fn process_agent_response(&mut self, interactive: bool) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
editor: &mut Editor<(), rustyline::history::DefaultHistory>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut stream = self.agent.reply(&self.messages).await?;
|
let mut stream = self.agent.reply(&self.messages).await?;
|
||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
@@ -204,45 +210,26 @@ impl Session {
|
|||||||
tokio::select! {
|
tokio::select! {
|
||||||
result = stream.next() => {
|
result = stream.next() => {
|
||||||
match result {
|
match result {
|
||||||
Some(Ok(mut message)) => {
|
Some(Ok(message)) => {
|
||||||
|
// If it's a confirmation request, get approval but otherwise do not render/persist
|
||||||
// Handle tool confirmation requests before rendering
|
|
||||||
if let Some(MessageContent::ToolConfirmationRequest(confirmation)) = message.content.first() {
|
if let Some(MessageContent::ToolConfirmationRequest(confirmation)) = message.content.first() {
|
||||||
output::hide_thinking();
|
output::hide_thinking();
|
||||||
|
|
||||||
// Format the confirmation prompt
|
// Format the confirmation prompt
|
||||||
let prompt = "Goose would like to call the above tool. Allow? (y/n):".to_string();
|
let prompt = "Goose would like to call the above tool, do you approve?".to_string();
|
||||||
|
|
||||||
let confirmation_request = Message::user().with_tool_confirmation_request(
|
|
||||||
confirmation.id.clone(),
|
|
||||||
confirmation.tool_name.clone(),
|
|
||||||
confirmation.arguments.clone(),
|
|
||||||
Some(prompt)
|
|
||||||
);
|
|
||||||
output::render_message(&confirmation_request);
|
|
||||||
|
|
||||||
// Get confirmation from user
|
// Get confirmation from user
|
||||||
let confirmed = match input::get_input(editor)? {
|
let confirmed = cliclack::confirm(prompt).initial_value(true).interact()?;
|
||||||
input::InputResult::Message(content) => {
|
|
||||||
content.trim().to_lowercase().starts_with('y')
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.agent.handle_confirmation(confirmation.id.clone(), confirmed).await;
|
self.agent.handle_confirmation(confirmation.id.clone(), confirmed).await;
|
||||||
|
|
||||||
message = confirmation_request;
|
|
||||||
}
|
}
|
||||||
|
// otherwise we have a model/tool to render
|
||||||
// Only push the message if it's not a tool confirmation request
|
else {
|
||||||
if !message.content.iter().any(|content| matches!(content, MessageContent::ToolConfirmationRequest(_))) {
|
|
||||||
self.messages.push(message.clone());
|
self.messages.push(message.clone());
|
||||||
|
storage::persist_messages(&self.session_file, &self.messages)?;
|
||||||
|
if interactive {output::hide_thinking()};
|
||||||
|
output::render_message(&message);
|
||||||
|
if interactive {output::show_thinking()};
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::persist_messages(&self.session_file, &self.messages)?;
|
|
||||||
output::hide_thinking();
|
|
||||||
output::render_message(&message);
|
|
||||||
output::show_thinking();
|
|
||||||
}
|
}
|
||||||
Some(Err(e)) => {
|
Some(Err(e)) => {
|
||||||
eprintln!("Error: {}", e);
|
eprintln!("Error: {}", e);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use bat::WrappingMode;
|
use bat::WrappingMode;
|
||||||
use console::style;
|
use console::style;
|
||||||
use goose::config::Config;
|
use goose::config::Config;
|
||||||
use goose::message::{Message, MessageContent, ToolConfirmationRequest, ToolRequest, ToolResponse};
|
use goose::message::{Message, MessageContent, ToolRequest, ToolResponse};
|
||||||
use mcp_core::tool::ToolCall;
|
use mcp_core::tool::ToolCall;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
@@ -94,12 +94,12 @@ pub fn render_message(message: &Message) {
|
|||||||
MessageContent::Text(text) => print_markdown(&text.text, theme),
|
MessageContent::Text(text) => print_markdown(&text.text, theme),
|
||||||
MessageContent::ToolRequest(req) => render_tool_request(req, theme),
|
MessageContent::ToolRequest(req) => render_tool_request(req, theme),
|
||||||
MessageContent::ToolResponse(resp) => render_tool_response(resp, theme),
|
MessageContent::ToolResponse(resp) => render_tool_response(resp, theme),
|
||||||
MessageContent::ToolConfirmationRequest(req) => {
|
|
||||||
render_tool_confirmation_request(req, theme)
|
|
||||||
}
|
|
||||||
MessageContent::Image(image) => {
|
MessageContent::Image(image) => {
|
||||||
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
|
println!("Image: [data: {}, type: {}]", image.data, image.mime_type);
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
println!("Message type could not be rendered");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
@@ -150,17 +150,6 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tool_confirmation_request(req: &ToolConfirmationRequest, theme: Theme) {
|
|
||||||
match &req.prompt {
|
|
||||||
Some(prompt) => {
|
|
||||||
let colored_prompt =
|
|
||||||
prompt.replace("Allow? (y/n)", &format!("{}", style("Allow? (y/n)").cyan()));
|
|
||||||
println!("{}", colored_prompt);
|
|
||||||
}
|
|
||||||
None => print_markdown("No prompt provided", theme),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_error(message: &str) {
|
pub fn render_error(message: &str) {
|
||||||
println!("\n {} {}\n", style("error:").red().bold(), message);
|
println!("\n {} {}\n", style("error:").red().bold(), message);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user