chore: use a Conversation type (#3735)
This commit is contained in:
@@ -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>> {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<()> {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
Reference in New Issue
Block a user