use std::collections::{HashMap, HashSet}; use std::sync::Arc; use crate::agent::AgentError; use crate::bus::ChatMessage; use crate::storage::{ ConversationRepository, SessionRecord, SkillEventRepository, persistent_session_id, }; fn preview_text(content: &str, max_chars: usize) -> String { let mut preview = content.chars().take(max_chars).collect::(); if content.chars().count() > max_chars { preview.push_str("..."); } preview.replace('\n', "\\n") } pub(crate) struct SessionHistory { channel_name: String, chat_histories: HashMap>, chat_topic_ids: HashMap, // 每个 chat 的当前 topic history_topic_ids: HashMap, // 每个 chat 的历史所对应的话题 compression_in_flight: HashSet, conversations: Arc, skill_events: Arc, } impl SessionHistory { pub(crate) fn new( channel_name: impl Into, conversations: Arc, skill_events: Arc, ) -> Self { Self { channel_name: channel_name.into(), chat_histories: HashMap::new(), chat_topic_ids: HashMap::new(), history_topic_ids: HashMap::new(), compression_in_flight: HashSet::new(), conversations, skill_events, } } pub(crate) fn persistent_session_id(&self, chat_id: &str) -> String { persistent_session_id(&self.channel_name, chat_id) } pub(crate) fn ensure_persistent_session( &self, chat_id: &str, ) -> Result { self.conversations .ensure_channel_session(&self.channel_name, chat_id) .map_err(|err| AgentError::Other(format!("session persistence error: {}", err))) } pub(crate) fn ensure_chat_loaded( &mut self, chat_id: &str, topic_id: Option<&str>, ) -> Result<(), AgentError> { if self.chat_histories.contains_key(chat_id) { return Ok(()); } // 如果提供了 topic_id,按 topic 加载;否则按 session 加载 let history = if let Some(tid) = topic_id { self.conversations .load_messages_for_topic(tid) .map_err(|err| AgentError::Other(format!("session history load error: {}", err)))? } else { self.conversations .load_messages(&self.persistent_session_id(chat_id)) .map_err(|err| AgentError::Other(format!("session history load error: {}", err)))? }; self.chat_histories.insert(chat_id.to_string(), history); Ok(()) } pub(crate) fn ensure_agent_prompt_before_user_message( &mut self, _chat_id: &str, ) -> Result<(), AgentError> { // 提示词现在由 AgentPromptProvider 统一处理,不需要在此处注入 Ok(()) } pub(crate) fn get_or_create_history(&mut self, chat_id: &str) -> &mut Vec { self.chat_histories.entry(chat_id.to_string()).or_default() } pub(crate) fn get_history(&self, chat_id: &str) -> Option<&Vec> { self.chat_histories.get(chat_id) } pub(crate) fn set_history(&mut self, chat_id: &str, history: Vec) { self.chat_histories.insert(chat_id.to_string(), history); // 记录历史对应的话题(当前设置的话题) if let Some(topic_id) = self.chat_topic_ids.get(chat_id) { self.history_topic_ids.insert(chat_id.to_string(), topic_id.clone()); } } /// 获取指定 chat 的历史所对应的话题 pub(crate) fn history_topic(&self, chat_id: &str) -> Option<&str> { self.history_topic_ids.get(chat_id).map(|s| s.as_str()) } /// 设置指定 chat 的当前 topic pub(crate) fn set_chat_topic(&mut self, chat_id: &str, topic_id: String) { self.chat_topic_ids.insert(chat_id.to_string(), topic_id); } /// 获取指定 chat 的当前 topic pub(crate) fn chat_topic(&self, chat_id: &str) -> Option<&str> { self.chat_topic_ids.get(chat_id).map(|s| s.as_str()) } /// 清除指定 chat 的 topic pub(crate) fn clear_chat_topic(&mut self, chat_id: &str) { self.chat_topic_ids.remove(chat_id); } pub(crate) fn add_message(&mut self, chat_id: &str, message: ChatMessage) { self.get_or_create_history(chat_id).push(message); } pub(crate) fn remove_history(&mut self, chat_id: &str) { self.chat_histories.remove(chat_id); self.compression_in_flight.remove(chat_id); self.history_topic_ids.remove(chat_id); } pub(crate) fn clear_chat_history(&mut self, chat_id: &str) -> Result<(), AgentError> { if let Some(history) = self.chat_histories.get_mut(chat_id) { let len = history.len(); history.clear(); #[cfg(debug_assertions)] tracing::debug!(chat_id = %chat_id, previous_len = len, "Chat history cleared"); } self.conversations .clear_messages(&self.persistent_session_id(chat_id)) .map_err(|err| AgentError::Other(format!("clear history persistence error: {}", err))) } pub(crate) fn append_persisted_messages( &mut self, chat_id: &str, messages: I, ) -> Result<(), AgentError> where I: IntoIterator, { let messages: Vec = messages.into_iter().collect(); if messages.is_empty() { return Ok(()); } for message in messages { self.add_message(chat_id, message); } Ok(()) } /// 将消息保存到指定话题 /// 每条消息已通过 PersistingEmittedMessageHandler 逐条持久化,此处仅保留接口兼容 pub(crate) fn append_to_topic( &self, _chat_id: &str, _topic_id: &str, messages: &[ChatMessage], ) -> Result<(), AgentError> { if messages.is_empty() { return Ok(()); } Ok(()) } pub(crate) fn latest_user_message(&self, chat_id: &str) -> Option<&ChatMessage> { self.get_history(chat_id) .and_then(|history| history.iter().rev().find(|message| message.role == "user")) } pub(crate) fn matches_current_user_turn(&self, chat_id: &str, message: &ChatMessage) -> bool { self.latest_user_message(chat_id) .map(|current| { current.id == message.id || (current.content == message.content && current.timestamp == message.timestamp && current.media_refs == message.media_refs) }) .unwrap_or(false) } pub(crate) fn stale_result_diagnostics( &self, chat_id: &str, ) -> (Option<&str>, Option, bool, usize) { let latest_user = self.latest_user_message(chat_id); let latest_user_id = latest_user.map(|message| message.id.as_str()); let latest_user_preview = latest_user.map(|message| preview_text(&message.content, 80)); let compression_in_flight = self.compression_in_flight.contains(chat_id); let history_len = self .get_history(chat_id) .map(|history| history.len()) .unwrap_or(0); ( latest_user_id, latest_user_preview, compression_in_flight, history_len, ) } pub(crate) fn clear_all_history(&mut self) -> Result<(), AgentError> { let chat_ids: Vec = self.chat_histories.keys().cloned().collect(); let total: usize = self.chat_histories.values().map(|h| h.len()).sum(); self.chat_histories.clear(); self.compression_in_flight.clear(); #[cfg(debug_assertions)] tracing::debug!(previous_total = total, "All chat histories cleared"); for chat_id in chat_ids { self.conversations .clear_messages(&self.persistent_session_id(&chat_id)) .map_err(|err| { AgentError::Other(format!("clear history persistence error: {}", err)) })?; } Ok(()) } pub(crate) fn try_start_background_compaction(&mut self, chat_id: &str) -> bool { self.compression_in_flight.insert(chat_id.to_string()) } pub(crate) fn finish_background_compaction(&mut self, chat_id: &str) { self.compression_in_flight.remove(chat_id); } pub(crate) fn reload_chat_history(&mut self, chat_id: &str) -> Result<(), AgentError> { let history = self .conversations .load_messages(&self.persistent_session_id(chat_id)) .map_err(|err| AgentError::Other(format!("session history reload error: {}", err)))?; self.chat_histories.insert(chat_id.to_string(), history); Ok(()) } pub(crate) fn conversations(&self) -> Arc { self.conversations.clone() } pub(crate) fn append_skill_event( &self, chat_id: &str, event_type: &str, skill_name: Option<&str>, payload: &serde_json::Value, ) -> Result<(), AgentError> { self.skill_events .append_skill_event( Some(&self.persistent_session_id(chat_id)), event_type, skill_name, payload, ) .map_err(|err| AgentError::Other(format!("append skill event error: {}", err))) } }