chore: use a Conversation type (#3735)

This commit is contained in:
Jack Amadeo
2025-08-06 22:06:13 -04:00
committed by GitHub
parent 17ea675def
commit 5eb01004a4
90 changed files with 994 additions and 790 deletions
+2 -2
View File
@@ -2,7 +2,7 @@ use crate::session::build_session;
use crate::session::SessionBuilderConfig;
use crate::{logging, session, Session};
use async_trait::async_trait;
use goose::message::Message;
use goose::conversation::Conversation;
use goose_bench::bench_session::{BenchAgent, BenchBaseSession};
use goose_bench::eval_suites::ExtensionRequirements;
use std::path::PathBuf;
@@ -18,7 +18,7 @@ impl BenchBaseSession for Session {
fn session_file(&self) -> Option<PathBuf> {
self.session_file()
}
fn message_history(&self) -> Vec<Message> {
fn message_history(&self) -> Conversation {
self.message_history()
}
fn get_total_token_usage(&self) -> anyhow::Result<Option<i32>> {
+2 -2
View File
@@ -13,7 +13,7 @@ use goose::config::{
Config, ConfigError, ExperimentManager, ExtensionConfigManager, ExtensionEntry,
PermissionManager,
};
use goose::message::Message;
use goose::conversation::message::Message;
use goose::providers::{create, providers};
use rmcp::model::{Tool, ToolAnnotations};
use rmcp::object;
@@ -1551,7 +1551,7 @@ pub fn configure_max_turns_dialog() -> Result<(), Box<dyn Error>> {
/// Handle OpenRouter authentication
pub async fn handle_openrouter_auth() -> Result<(), Box<dyn Error>> {
use goose::config::{configure_openrouter, signup_openrouter::OpenRouterAuth};
use goose::message::Message;
use goose::conversation::message::Message;
use goose::providers::create;
// Use the OpenRouter authentication flow
+15 -11
View File
@@ -195,7 +195,8 @@ pub fn handle_session_export(identifier: Identifier, output_path: Option<PathBuf
};
// Generate the markdown content using the export functionality
let markdown = export_session_to_markdown(messages, &session_file_path, None);
let markdown =
export_session_to_markdown(messages.messages().clone(), &session_file_path, None);
// Output the markdown
if let Some(output) = output_path {
@@ -214,7 +215,7 @@ pub fn handle_session_export(identifier: Identifier, output_path: Option<PathBuf
/// This function handles the formatting of a complete session including headers,
/// message organization, and proper tool request/response pairing.
fn export_session_to_markdown(
messages: Vec<goose::message::Message>,
messages: Vec<goose::conversation::message::Message>,
session_file: &Path,
session_name_override: Option<&str>,
) -> String {
@@ -242,10 +243,12 @@ fn export_session_to_markdown(
for message in &messages {
// Check if this is a User message containing only ToolResponses
let is_only_tool_response = message.role == rmcp::model::Role::User
&& message
.content
.iter()
.all(|content| matches!(content, goose::message::MessageContent::ToolResponse(_)));
&& message.content.iter().all(|content| {
matches!(
content,
goose::conversation::message::MessageContent::ToolResponse(_)
)
});
// If the previous message had tool requests and this one is just tool responses,
// don't create a new User section - we'll attach the responses to the tool calls
@@ -274,11 +277,12 @@ fn export_session_to_markdown(
markdown_output.push_str("\n\n---\n\n");
// Check if this message has any tool requests, to handle the next message differently
if message
.content
.iter()
.any(|content| matches!(content, goose::message::MessageContent::ToolRequest(_)))
{
if message.content.iter().any(|content| {
matches!(
content,
goose::conversation::message::MessageContent::ToolRequest(_)
)
}) {
skip_next_if_tool_response = true;
}
}
+14 -10
View File
@@ -10,7 +10,8 @@ use axum::{
};
use futures::{sink::SinkExt, stream::StreamExt};
use goose::agents::{Agent, AgentEvent};
use goose::message::Message as GooseMessage;
use goose::conversation::message::Message as GooseMessage;
use goose::conversation::Conversation;
use goose::session;
use serde::{Deserialize, Serialize};
use std::{net::SocketAddr, sync::Arc};
@@ -18,7 +19,7 @@ use tokio::sync::{Mutex, RwLock};
use tower_http::cors::{Any, CorsLayer};
use tracing::error;
type SessionStore = Arc<RwLock<std::collections::HashMap<String, Arc<Mutex<Vec<GooseMessage>>>>>>;
type SessionStore = Arc<RwLock<std::collections::HashMap<String, Arc<Mutex<Conversation>>>>>;
type CancellationStore = Arc<RwLock<std::collections::HashMap<String, tokio::task::AbortHandle>>>;
#[derive(Clone)]
@@ -319,8 +320,8 @@ async fn handle_socket(socket: WebSocket, state: AppState) {
let mut sessions = state.sessions.write().await;
// Load existing messages from JSONL file if it exists
let existing_messages = session::read_messages(&session_file)
.unwrap_or_else(|_| Vec::new());
let existing_messages =
session::read_messages(&session_file).unwrap_or_default();
let new_session = Arc::new(Mutex::new(existing_messages));
sessions.insert(session_id.clone(), new_session.clone());
@@ -435,21 +436,21 @@ async fn handle_socket(socket: WebSocket, state: AppState) {
async fn process_message_streaming(
agent: &Agent,
session_messages: Arc<Mutex<Vec<GooseMessage>>>,
session_messages: Arc<Mutex<Conversation>>,
session_file: std::path::PathBuf,
content: String,
sender: Arc<Mutex<futures::stream::SplitSink<WebSocket, Message>>>,
) -> Result<()> {
use futures::StreamExt;
use goose::agents::SessionConfig;
use goose::message::MessageContent;
use goose::conversation::message::MessageContent;
use goose::session;
// Create a user message
let user_message = GooseMessage::user().with_text(content.clone());
// Messages will be auto-compacted in agent.reply() if needed
let messages = {
let messages: Conversation = {
let mut session_msgs = session_messages.lock().await;
session_msgs.push(user_message.clone());
session_msgs.clone()
@@ -493,7 +494,10 @@ async fn process_message_streaming(
retry_config: None,
};
match agent.reply(&messages, Some(session_config), None).await {
match agent
.reply(messages.clone(), Some(session_config), None)
.await
{
Ok(mut stream) => {
while let Some(result) = stream.next().await {
match result {
@@ -617,7 +621,7 @@ async fn process_message_streaming(
// For now, auto-summarize in web mode
// TODO: Implement proper UI for context handling
let (summarized_messages, _) =
agent.summarize_context(&messages).await?;
agent.summarize_context(messages.messages()).await?;
{
let mut session_msgs = session_messages.lock().await;
*session_msgs = summarized_messages;
@@ -633,7 +637,7 @@ async fn process_message_streaming(
// Replace the session's message history with the compacted messages
{
let mut session_msgs = session_messages.lock().await;
*session_msgs = new_messages;
*session_msgs = Conversation::new_unvalidated(new_messages);
}
// Persist the updated messages to the JSONL file
@@ -4,7 +4,7 @@
use crate::scenario_tests::scenario_runner::SCENARIO_TESTS_DIR;
use base64::engine::general_purpose;
use base64::Engine;
use goose::message::Message;
use goose::conversation::message::Message;
use goose::providers::base::Provider;
pub type MessageGenerator<'a> = Box<dyn Fn(&dyn Provider) -> Message + 'a>;
@@ -100,7 +100,7 @@ impl McpClientTrait for MockClient {
is_error: None,
structured_content: None,
}),
Err(e) => Err(Error::UnexpectedResponse),
Err(_e) => Err(Error::UnexpectedResponse),
}
} else {
Err(Error::UnexpectedResponse)
@@ -1,4 +1,5 @@
use dotenvy::dotenv;
use goose::conversation::Conversation;
use crate::scenario_tests::message_generator::MessageGenerator;
use crate::scenario_tests::mock_client::weather_client;
@@ -6,7 +7,6 @@ use crate::scenario_tests::provider_configs::{get_provider_configs, ProviderConf
use crate::session::Session;
use anyhow::Result;
use goose::agents::Agent;
use goose::message::Message;
use goose::model::ModelConfig;
use goose::providers::{create, testprovider::TestProvider};
use std::collections::{HashMap, HashSet};
@@ -18,7 +18,7 @@ pub const SCENARIO_TESTS_DIR: &str = "src/scenario_tests";
#[derive(Debug, Clone)]
pub struct ScenarioResult {
pub messages: Vec<Message>,
pub messages: Conversation,
pub error: Option<String>,
}
@@ -214,7 +214,7 @@ where
break;
}
}
let updated_messages = session.message_history().to_vec();
let updated_messages = session.message_history();
if let Some(ref err_msg) = error {
if err_msg.contains("No recorded response found") {
@@ -7,7 +7,7 @@ mod tests {
use crate::scenario_tests::mock_client::WEATHER_TYPE;
use crate::scenario_tests::scenario_runner::run_scenario;
use anyhow::Result;
use goose::message::Message;
use goose::conversation::message::Message;
#[tokio::test]
async fn test_what_is_your_name() -> Result<()> {
+2 -2
View File
@@ -1,4 +1,4 @@
use goose::message::{Message, MessageContent, ToolRequest, ToolResponse};
use goose::conversation::message::{Message, MessageContent, ToolRequest, ToolResponse};
use goose::utils::safe_truncate;
use rmcp::model::{RawContent, ResourceContents, Role};
use serde_json::Value;
@@ -360,7 +360,7 @@ pub fn message_to_markdown(message: &Message, export_all_content: bool) -> Strin
#[cfg(test)]
mod tests {
use super::*;
use goose::message::{Message, ToolRequest, ToolResponse};
use goose::conversation::message::{Message, ToolRequest, ToolResponse};
use mcp_core::tool::ToolCall;
use rmcp::model::{Content, RawTextContent, TextContent};
use serde_json::json;
+28 -20
View File
@@ -10,13 +10,13 @@ mod thinking;
use crate::session::task_execution_display::{
format_task_execution_notification, TASK_EXECUTION_NOTIFICATION_TYPE,
};
use goose::conversation::Conversation;
use std::io::Write;
pub use self::export::message_to_markdown;
pub use builder::{build_session, SessionBuilderConfig, SessionSettings};
use console::Color;
use goose::agents::AgentEvent;
use goose::message::push_message;
use goose::permission::permission_confirmation::PrincipalType;
use goose::permission::Permission;
use goose::permission::PermissionConfirmation;
@@ -31,7 +31,6 @@ use goose::agents::extension::{Envs, ExtensionConfig};
use goose::agents::types::RetryConfig;
use goose::agents::{Agent, SessionConfig};
use goose::config::Config;
use goose::message::{Message, MessageContent};
use goose::providers::pricing::initialize_pricing_cache;
use goose::session;
use input::InputResult;
@@ -39,6 +38,7 @@ use mcp_core::handler::ToolError;
use rmcp::model::PromptMessage;
use rmcp::model::ServerNotification;
use goose::conversation::message::{Message, MessageContent};
use rand::{distributions::Alphanumeric, Rng};
use rustyline::EditMode;
use serde_json::Value;
@@ -56,7 +56,7 @@ pub enum RunMode {
pub struct Session {
agent: Agent,
messages: Vec<Message>,
messages: Conversation,
session_file: Option<PathBuf>,
// Cache for completion data - using std::sync for thread safety without async
completion_cache: Arc<std::sync::RwLock<CompletionCache>>,
@@ -134,11 +134,11 @@ impl Session {
let messages = if let Some(session_file) = &session_file {
session::read_messages(session_file).unwrap_or_else(|e| {
eprintln!("Warning: Failed to load message history: {}", e);
Vec::new()
Conversation::new_unvalidated(Vec::new())
})
} else {
// Don't try to read messages if we're not saving sessions
Vec::new()
Conversation::new_unvalidated(Vec::new())
};
Session {
@@ -157,12 +157,12 @@ impl Session {
/// Helper function to summarize context messages
async fn summarize_context_messages(
messages: &mut Vec<Message>,
messages: &mut Conversation,
agent: &Agent,
message_suffix: &str,
) -> Result<()> {
// Summarize messages to fit within context length
let (summarized_messages, _) = agent.summarize_context(messages).await?;
let (summarized_messages, _) = agent.summarize_context(messages.messages()).await?;
let msg = format!("Context maxed out\n{}\n{}", "-".repeat(50), message_suffix);
output::render_text(&msg, Some(Color::Yellow), true);
*messages = summarized_messages;
@@ -719,8 +719,10 @@ impl Session {
let provider = self.agent.provider().await?;
// Call the summarize_context method which uses the summarize_messages function
let (summarized_messages, _) =
self.agent.summarize_context(&self.messages).await?;
let (summarized_messages, _) = self
.agent
.summarize_context(self.messages.messages())
.await?;
// Update the session messages with the summarized ones
self.messages = summarized_messages;
@@ -771,12 +773,14 @@ impl Session {
async fn plan_with_reasoner_model(
&mut self,
plan_messages: Vec<Message>,
plan_messages: Conversation,
reasoner: Arc<dyn Provider>,
) -> Result<(), anyhow::Error> {
let plan_prompt = self.agent.get_plan_prompt().await?;
output::show_thinking();
let (plan_response, _usage) = reasoner.complete(&plan_prompt, &plan_messages, &[]).await?;
let (plan_response, _usage) = reasoner
.complete(&plan_prompt, plan_messages.messages(), &[])
.await?;
output::render_message(&plan_response, self.debug);
output::hide_thinking();
let planner_response_type =
@@ -875,7 +879,11 @@ impl Session {
});
let mut stream = self
.agent
.reply(&self.messages, session_config.clone(), Some(cancel_token))
.reply(
self.messages.clone(),
session_config.clone(),
Some(cancel_token),
)
.await?;
let mut progress_bars = output::McpSpinners::new();
@@ -921,7 +929,7 @@ impl Session {
confirmation.id.clone(),
Err(ToolError::ExecutionError("Tool call cancelled by user".to_string()))
));
push_message(&mut self.messages, response_message);
self.messages.push(response_message);
if let Some(session_file) = &self.session_file {
let working_dir = std::env::current_dir().ok();
session::persist_messages_with_schedule_id(
@@ -983,7 +991,7 @@ impl Session {
}
"truncate" => {
// Truncate messages to fit within context length
let (truncated_messages, _) = self.agent.truncate_context(&self.messages).await?;
let (truncated_messages, _) = self.agent.truncate_context(self.messages.messages()).await?;
let msg = if context_strategy == "truncate" {
format!("Context maxed out - automatically truncated messages.\n{}\nGoose tried its best to truncate messages for you.", "-".repeat(50))
} else {
@@ -1013,7 +1021,7 @@ impl Session {
stream = self
.agent
.reply(
&self.messages,
self.messages.clone(),
session_config.clone(),
None
)
@@ -1065,7 +1073,7 @@ impl Session {
}
}
push_message(&mut self.messages, message.clone());
self.messages.push(message.clone());
// No need to update description on assistant messages
if let Some(session_file) = &self.session_file {
@@ -1193,7 +1201,7 @@ impl Session {
}
Some(Ok(AgentEvent::HistoryReplaced(new_messages))) => {
// Replace the session's message history with the compacted messages
self.messages = new_messages;
self.messages = Conversation::new_unvalidated(new_messages);
// Persist the updated messages to the session file
if let Some(session_file) = &self.session_file {
@@ -1414,7 +1422,7 @@ impl Session {
cache.last_updated = Instant::now();
}
pub fn message_history(&self) -> Vec<Message> {
pub fn message_history(&self) -> Conversation {
self.messages.clone()
}
@@ -1432,7 +1440,7 @@ impl Session {
);
// Render each message
for message in &self.messages {
for message in self.messages.iter() {
output::render_message(message, self.debug);
}
@@ -1612,7 +1620,7 @@ impl Session {
}
fn push_message(&mut self, message: Message) {
push_message(&mut self.messages, message);
self.messages.push(message);
}
}
+2 -1
View File
@@ -2,7 +2,7 @@ use anstream::println;
use bat::WrappingMode;
use console::{style, Color};
use goose::config::Config;
use goose::message::{Message, MessageContent, ToolRequest, ToolResponse};
use goose::conversation::message::{Message, MessageContent, ToolRequest, ToolResponse};
use goose::providers::pricing::get_model_pricing;
use goose::providers::pricing::parse_model_id;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
@@ -16,6 +16,7 @@ use std::io::{Error, IsTerminal, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
// Re-export theme for use in main
#[derive(Clone, Copy)]
pub enum Theme {