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
+28 -23
View File
@@ -34,7 +34,7 @@ use crate::agents::types::SessionConfig;
use crate::agents::types::{FrontendTool, ToolResultReceiver};
use crate::config::{Config, ExtensionConfigManager, PermissionManager};
use crate::context_mgmt::auto_compact;
use crate::message::{push_message, Message, ToolRequest};
use crate::conversation::{debug_conversation_fix, fix_conversation, Conversation};
use crate::permission::permission_judge::{check_tool_permissions, PermissionCheckResult};
use crate::permission::PermissionConfirmation;
use crate::providers::base::Provider;
@@ -56,13 +56,13 @@ use super::final_output_tool::FinalOutputTool;
use super::platform_tools;
use super::tool_execution::{ToolCallResult, CHAT_MODE_TOOL_SKIPPED_RESPONSE, DECLINED_RESPONSE};
use crate::agents::subagent_task_config::TaskConfig;
use crate::conversation_fixer::{debug_conversation_fix, ConversationFixer};
use crate::conversation::message::{Message, ToolRequest};
const DEFAULT_MAX_TURNS: u32 = 1000;
/// Context needed for the reply function
pub struct ReplyContext {
pub messages: Vec<Message>,
pub messages: Conversation,
pub tools: Vec<Tool>,
pub toolshim_tools: Vec<Tool>,
pub system_prompt: String,
@@ -199,7 +199,7 @@ impl Agent {
/// Handle retry logic for the agent reply loop
async fn handle_retry_logic(
&self,
messages: &mut Vec<Message>,
messages: &mut Conversation,
session: &Option<SessionConfig>,
initial_messages: &[Message],
) -> Result<bool> {
@@ -218,24 +218,29 @@ impl Agent {
async fn prepare_reply_context(
&self,
unfixed_messages: &[Message],
unfixed_conversation: Conversation,
session: &Option<SessionConfig>,
) -> Result<ReplyContext> {
let (messages, issues) = ConversationFixer::fix_conversation(Vec::from(unfixed_messages));
let unfixed_messages = unfixed_conversation.messages().clone();
let (conversation, issues) = fix_conversation(unfixed_conversation.clone());
if !issues.is_empty() {
tracing::warn!(
"Conversation issue fixed: {}",
debug_conversation_fix(unfixed_messages, &messages, &issues)
debug_conversation_fix(
unfixed_messages.as_slice(),
conversation.messages(),
&issues
)
);
}
let initial_messages = messages.clone();
let initial_messages = conversation.messages().clone();
let config = Config::global();
let (tools, toolshim_tools, system_prompt) = self.prepare_tools_and_prompt().await?;
let goose_mode = Self::determine_goose_mode(session.as_ref(), config);
Ok(ReplyContext {
messages,
messages: conversation,
tools,
toolshim_tools,
system_prompt,
@@ -760,7 +765,7 @@ impl Agent {
&self,
messages: &[Message],
session: &Option<SessionConfig>,
) -> Result<Option<(Vec<Message>, String)>> {
) -> Result<Option<(Conversation, String)>> {
// Try to get session metadata for more accurate token counts
let session_metadata = if let Some(session_config) = session {
match session::storage::get_path(session_config.id.clone()) {
@@ -802,22 +807,22 @@ impl Agent {
Ok(None)
}
#[instrument(skip(self, unfixed_messages, session), fields(user_message))]
#[instrument(skip(self, unfixed_conversation, session), fields(user_message))]
pub async fn reply(
&self,
unfixed_messages: &[Message],
unfixed_conversation: Conversation,
session: Option<SessionConfig>,
cancel_token: Option<CancellationToken>,
) -> Result<BoxStream<'_, Result<AgentEvent>>> {
// Handle auto-compaction before processing
let (messages, compaction_msg) = match self
.handle_auto_compaction(unfixed_messages, &session)
.handle_auto_compaction(unfixed_conversation.messages(), &session)
.await?
{
Some((compacted_messages, msg)) => (compacted_messages, Some(msg)),
None => {
let context = self
.prepare_reply_context(unfixed_messages, &session)
.prepare_reply_context(unfixed_conversation, &session)
.await?;
(context.messages, None)
}
@@ -827,10 +832,10 @@ impl Agent {
if let Some(compaction_msg) = compaction_msg {
return Ok(Box::pin(async_stream::try_stream! {
yield AgentEvent::Message(Message::assistant().with_text(compaction_msg));
yield AgentEvent::HistoryReplaced(messages.clone());
yield AgentEvent::HistoryReplaced(messages.messages().clone());
// Continue with normal reply processing using compacted messages
let mut reply_stream = self.reply_internal(&messages, session, cancel_token).await?;
let mut reply_stream = self.reply_internal(messages, session, cancel_token).await?;
while let Some(event) = reply_stream.next().await {
yield event?;
}
@@ -838,13 +843,13 @@ impl Agent {
}
// No compaction needed, proceed with normal processing
self.reply_internal(&messages, session, cancel_token).await
self.reply_internal(messages, session, cancel_token).await
}
/// Main reply method that handles the actual agent processing
async fn reply_internal(
&self,
messages: &[Message],
messages: Conversation,
session: Option<SessionConfig>,
cancel_token: Option<CancellationToken>,
) -> Result<BoxStream<'_, Result<AgentEvent>>> {
@@ -905,7 +910,7 @@ impl Agent {
let mut stream = Self::stream_response_from_provider(
self.provider().await?,
&system_prompt,
&messages,
messages.messages(),
&tools,
&toolshim_tools,
).await?;
@@ -1074,8 +1079,8 @@ impl Agent {
yield AgentEvent::Message(final_message_tool_resp.clone());
added_message = true;
push_message(&mut messages_to_add, response);
push_message(&mut messages_to_add, final_message_tool_resp);
messages_to_add.push(response);
messages_to_add.push(final_message_tool_resp);
}
}
Err(ProviderError::ContextLengthExceeded(_)) => {
@@ -1244,7 +1249,7 @@ impl Agent {
}
}
pub async fn create_recipe(&self, mut messages: Vec<Message>) -> Result<Recipe> {
pub async fn create_recipe(&self, mut messages: Conversation) -> Result<Recipe> {
let extension_manager = self.extension_manager.read().await;
let extensions_info = extension_manager.get_extensions_info().await;
@@ -1273,7 +1278,7 @@ impl Agent {
.await
.as_ref()
.unwrap()
.complete(&system_prompt, &messages, &tools)
.complete(&system_prompt, messages.messages(), &tools)
.await?;
let content = result.as_concat_text();
+4 -3
View File
@@ -1,6 +1,7 @@
use anyhow::Ok;
use crate::message::Message;
use crate::conversation::message::Message;
use crate::conversation::Conversation;
use crate::token_counter::create_async_token_counter;
use crate::context_mgmt::summarize::summarize_messages_async;
@@ -14,7 +15,7 @@ impl Agent {
pub async fn truncate_context(
&self,
messages: &[Message], // last message is a user msg that led to assistant message with_context_length_exceeded
) -> Result<(Vec<Message>, Vec<usize>), anyhow::Error> {
) -> Result<(Conversation, Vec<usize>), anyhow::Error> {
let provider = self.provider().await?;
let token_counter = create_async_token_counter()
.await
@@ -51,7 +52,7 @@ impl Agent {
pub async fn summarize_context(
&self,
messages: &[Message], // last message is a user msg that led to assistant message with_context_length_exceeded
) -> Result<(Vec<Message>, Vec<usize>), anyhow::Error> {
) -> Result<(Conversation, Vec<usize>), anyhow::Error> {
let provider = self.provider().await?;
let token_counter = create_async_token_counter()
.await
+16 -8
View File
@@ -5,8 +5,10 @@ use std::sync::Arc;
use async_stream::try_stream;
use futures::stream::StreamExt;
use super::super::agents::Agent;
use crate::agents::router_tool_selector::RouterToolSelectionStrategy;
use crate::message::{Message, MessageContent, ToolRequest};
use crate::conversation::message::{Message, MessageContent, ToolRequest};
use crate::conversation::Conversation;
use crate::providers::base::{stream_from_single_message, MessageStream, Provider, ProviderUsage};
use crate::providers::errors::ProviderError;
use crate::providers::toolshim::{
@@ -16,8 +18,6 @@ use crate::providers::toolshim::{
use crate::session;
use rmcp::model::Tool;
use super::super::agents::Agent;
async fn toolshim_postprocess(
response: Message,
toolshim_tools: &[Tool],
@@ -127,12 +127,12 @@ impl Agent {
let messages_for_provider = if config.toolshim {
convert_tool_messages_to_text(messages)
} else {
messages.to_vec()
Conversation::new_unvalidated(messages.to_vec())
};
// Call the provider to get a response
let (mut response, usage) = provider
.complete(system_prompt, &messages_for_provider, tools)
.complete(system_prompt, messages_for_provider.messages(), tools)
.await?;
crate::providers::base::set_current_model(&usage.model);
@@ -159,7 +159,7 @@ impl Agent {
let messages_for_provider = if config.toolshim {
convert_tool_messages_to_text(messages)
} else {
messages.to_vec()
Conversation::new_unvalidated(messages.to_vec())
};
// Clone owned data to move into the async stream
@@ -170,11 +170,19 @@ impl Agent {
let mut stream = if provider.supports_streaming() {
provider
.stream(system_prompt.as_str(), &messages_for_provider, &tools)
.stream(
system_prompt.as_str(),
messages_for_provider.messages(),
&tools,
)
.await?
} else {
let (message, usage) = provider
.complete(system_prompt.as_str(), &messages_for_provider, &tools)
.complete(
system_prompt.as_str(),
messages_for_provider.messages(),
&tools,
)
.await?;
stream_from_single_message(message, usage)
};
+5 -5
View File
@@ -11,7 +11,8 @@ use crate::agents::types::{
RetryConfig, SuccessCheck, DEFAULT_ON_FAILURE_TIMEOUT_SECONDS, DEFAULT_RETRY_TIMEOUT_SECONDS,
};
use crate::config::Config;
use crate::message::Message;
use crate::conversation::message::Message;
use crate::conversation::Conversation;
use crate::tool_monitor::ToolMonitor;
/// Result of a retry logic evaluation
@@ -92,12 +93,11 @@ impl RetryManager {
/// Reset status for retry: clear message history and final output tool state
async fn reset_status_for_retry(
messages: &mut Vec<Message>,
messages: &mut Conversation,
initial_messages: &[Message],
final_output_tool: &Arc<Mutex<Option<crate::agents::final_output_tool::FinalOutputTool>>>,
) {
messages.clear();
messages.extend_from_slice(initial_messages);
*messages = Conversation::new_unvalidated(initial_messages.to_vec());
info!("Reset message history to initial state for retry");
if let Some(final_output_tool) = final_output_tool.lock().await.as_mut() {
@@ -109,7 +109,7 @@ impl RetryManager {
/// Handle retry logic for the agent reply loop
pub async fn handle_retry_logic(
&self,
messages: &mut Vec<Message>,
messages: &mut Conversation,
session: &Option<SessionConfig>,
initial_messages: &[Message],
final_output_tool: &Arc<Mutex<Option<crate::agents::final_output_tool::FinalOutputTool>>>,
@@ -12,7 +12,7 @@ use std::sync::Arc;
use tokio::sync::RwLock;
use crate::agents::tool_vectordb::ToolVectorDB;
use crate::message::Message;
use crate::conversation::message::Message;
use crate::model::ModelConfig;
use crate::providers::{self, base::Provider};
+10 -11
View File
@@ -3,7 +3,6 @@ use crate::{
agents::extension::ExtensionConfig,
agents::{extension_manager::ExtensionManager, Agent, TaskConfig},
config::ExtensionConfigManager,
message::{Message, MessageContent, ToolRequest},
prompt_template::render_global_file,
providers::errors::ProviderError,
};
@@ -13,6 +12,8 @@ use mcp_core::handler::ToolError;
use rmcp::model::Tool;
use serde::{Deserialize, Serialize};
// use serde_json::{self};
use crate::conversation::message::{Message, MessageContent, ToolRequest};
use crate::conversation::Conversation;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::{Mutex, RwLock};
use tokio_util::sync::CancellationToken;
@@ -41,7 +42,7 @@ pub struct SubAgentProgress {
/// A specialized agent that can handle specific tasks independently
pub struct SubAgent {
pub id: String,
pub conversation: Arc<Mutex<Vec<Message>>>,
pub conversation: Arc<Mutex<Conversation>>,
pub status: Arc<RwLock<SubAgentStatus>>,
pub config: TaskConfig,
pub turn_count: Arc<Mutex<usize>>,
@@ -80,7 +81,7 @@ impl SubAgent {
let subagent = Arc::new(SubAgent {
id: task_config.id.clone(),
conversation: Arc::new(Mutex::new(Vec::new())),
conversation: Arc::new(Mutex::new(Conversation::new_unvalidated(Vec::new()))),
status: Arc::new(RwLock::new(SubAgentStatus::Ready)),
config: task_config,
turn_count: Arc::new(Mutex::new(0)),
@@ -107,7 +108,7 @@ impl SubAgent {
&self,
message: String,
task_config: TaskConfig,
) -> Result<Vec<Message>, anyhow::Error> {
) -> Result<Conversation, anyhow::Error> {
debug!("Processing message for subagent {}", self.id);
// Get provider from task config
@@ -128,7 +129,10 @@ impl SubAgent {
}
// Get the current conversation for context
let mut messages = self.get_conversation().await;
let mut messages = {
let conversation = self.conversation.lock().await;
conversation.clone()
};
// Get tools from the subagent's own extension manager
let tools: Vec<Tool> = self
@@ -156,7 +160,7 @@ impl SubAgent {
match Agent::generate_response_from_provider(
Arc::clone(provider),
&system_prompt,
&messages,
messages.messages(),
&tools,
&toolshim_tools,
)
@@ -264,11 +268,6 @@ impl SubAgent {
conversation.push(message);
}
/// Get the full conversation history
async fn get_conversation(&self) -> Vec<Message> {
self.conversation.lock().await.clone()
}
/// Build the system prompt for the subagent using the template
async fn build_system_prompt(&self, available_tools: &[Tool]) -> Result<String, anyhow::Error> {
let mut context = HashMap::new();
+2 -2
View File
@@ -24,10 +24,10 @@ pub async fn run_complete_subagent_task(
.flat_map(|message| {
message.content.iter().filter_map(|content| {
match content {
crate::message::MessageContent::Text(text_content) => {
crate::conversation::message::MessageContent::Text(text_content) => {
Some(text_content.text.clone())
}
crate::message::MessageContent::ToolResponse(tool_response) => {
crate::conversation::message::MessageContent::ToolResponse(tool_response) => {
// Extract text from tool response
if let Ok(contents) = &tool_response.tool_result {
let texts: Vec<String> = contents
+1 -1
View File
@@ -9,7 +9,6 @@ use tokio_util::sync::CancellationToken;
use crate::config::permission::PermissionLevel;
use crate::config::PermissionManager;
use crate::message::{Message, ToolRequest};
use crate::permission::Permission;
use mcp_core::ToolResult;
use rmcp::model::{Content, ServerNotification};
@@ -32,6 +31,7 @@ impl From<ToolResult<Vec<Content>>> for ToolCallResult {
use super::agent::{tool_stream, ToolStream};
use crate::agents::Agent;
use crate::conversation::message::{Message, ToolRequest};
pub const DECLINED_RESPONSE: &str = "The user has declined to run this tool. \
DO NOT attempt to call this tool again. \
@@ -7,7 +7,7 @@ use crate::agents::tool_execution::ToolCallResult;
use crate::agents::tool_router_index_manager::ToolRouterIndexManager;
use crate::agents::tool_vectordb::generate_table_id;
use crate::config::Config;
use crate::message::ToolRequest;
use crate::conversation::message::ToolRequest;
use crate::providers::base::Provider;
use anyhow::{anyhow, Result};
use mcp_core::ToolError;