将压缩流程从物理删除原消息改为标记位 is_compacted 分离两个视图: - LLM 视角(load_messages_for_topic):只看 is_compacted=0 的压缩摘要+新消息 - UI 视角(load_messages_for_topic_full):看原消息(含 is_compacted=1)+新消息,排除摘要 新增 compact_topic_history:不删除原消息,仅打标记+插入摘要, 同事务内删除旧摘要避免累积。修复 token 统计因压缩丢失 usage 的问题。
464 lines
14 KiB
Rust
464 lines
14 KiB
Rust
use super::{
|
||
MemoryRecord, MemoryUpsert, SchedulerJobRecord, SchedulerJobState, SchedulerJobStatus,
|
||
SchedulerJobUpsert, SessionRecord, SkillEventRecord, StorageError, TodoRecord,
|
||
};
|
||
use crate::bus::ChatMessage;
|
||
|
||
pub trait ConversationRepository: Send + Sync + 'static {
|
||
fn ensure_channel_session(
|
||
&self,
|
||
channel_name: &str,
|
||
chat_id: &str,
|
||
) -> Result<SessionRecord, StorageError>;
|
||
|
||
/// 确保指定 session_id 的会话存在(如果不存在则创建)
|
||
fn ensure_session(
|
||
&self,
|
||
session_id: &str,
|
||
channel_name: &str,
|
||
chat_id: &str,
|
||
title: &str,
|
||
) -> Result<SessionRecord, StorageError>;
|
||
|
||
fn load_messages(&self, session_id: &str) -> Result<Vec<ChatMessage>, StorageError>;
|
||
|
||
fn load_messages_for_topic(
|
||
&self,
|
||
topic_id: &str,
|
||
session_id: Option<&str>,
|
||
) -> Result<Vec<ChatMessage>, StorageError>;
|
||
|
||
/// UI 视角:返回原始消息(含被压缩消费的 is_compacted=1 消息)+ 未压缩新消息,
|
||
/// 排除压缩摘要消息(system_context LIKE 'history_compaction%')。
|
||
fn load_messages_for_topic_full(
|
||
&self,
|
||
topic_id: &str,
|
||
session_id: Option<&str>,
|
||
) -> Result<Vec<ChatMessage>, StorageError>;
|
||
|
||
fn append_message(&self, session_id: &str, message: &ChatMessage) -> Result<(), StorageError>;
|
||
|
||
fn append_message_with_topic(
|
||
&self,
|
||
session_id: &str,
|
||
topic_id: Option<&str>,
|
||
message: &ChatMessage,
|
||
) -> Result<(), StorageError>;
|
||
|
||
fn append_messages_batch(
|
||
&self,
|
||
session_id: &str,
|
||
topic_id: Option<&str>,
|
||
messages: &[ChatMessage],
|
||
) -> Result<(), StorageError>;
|
||
|
||
fn clear_messages(&self, session_id: &str) -> Result<(), StorageError>;
|
||
|
||
fn compact_active_history(
|
||
&self,
|
||
session_id: &str,
|
||
snapshot_end_seq: i64,
|
||
preserved_system_messages: &[ChatMessage],
|
||
summary_message: &ChatMessage,
|
||
preserved_messages: &[ChatMessage],
|
||
) -> Result<bool, StorageError>;
|
||
|
||
/// Replace the entire active history for a session with new messages.
|
||
/// Used when the compressor has already produced a complete, validated
|
||
/// message list (e.g. two-segment compression).
|
||
fn replace_active_history(
|
||
&self,
|
||
session_id: &str,
|
||
messages: &[ChatMessage],
|
||
) -> Result<(), StorageError>;
|
||
|
||
/// Replace the entire history for a specific topic.
|
||
/// Deletes only messages belonging to the given topic_id, then inserts
|
||
/// the new messages with topic_id set correctly. Used by compressor when
|
||
/// it has produced a complete, validated message list for a single topic.
|
||
fn replace_topic_history(
|
||
&self,
|
||
session_id: &str,
|
||
topic_id: &str,
|
||
messages: &[ChatMessage],
|
||
) -> Result<(), StorageError>;
|
||
|
||
/// 压缩该 topic 的历史:保留原始消息(标记 is_compacted=1)+ 插入压缩摘要。
|
||
/// 不删除原消息,让前端仍能展示完整原始对话,LLM 只看压缩后的精简历史。
|
||
fn compact_topic_history(
|
||
&self,
|
||
session_id: &str,
|
||
topic_id: &str,
|
||
new_messages: &[ChatMessage],
|
||
) -> Result<(), StorageError>;
|
||
}
|
||
|
||
pub trait PromptInjectionRepository: Send + Sync + 'static {
|
||
fn get_session(&self, session_id: &str) -> Result<Option<SessionRecord>, StorageError>;
|
||
|
||
fn count_active_user_messages(&self, session_id: &str) -> Result<i64, StorageError>;
|
||
|
||
fn mark_agent_prompt_reinjected(&self, session_id: &str) -> Result<(), StorageError>;
|
||
}
|
||
|
||
pub trait MemoryRepository: Send + Sync + 'static {
|
||
fn put_memory(&self, input: &MemoryUpsert) -> Result<MemoryRecord, StorageError>;
|
||
|
||
fn update_memory(&self, input: &MemoryUpsert) -> Result<Option<MemoryRecord>, StorageError>;
|
||
|
||
fn delete_memory(
|
||
&self,
|
||
scope_kind: &str,
|
||
scope_key: &str,
|
||
namespace: &str,
|
||
memory_key: &str,
|
||
) -> Result<bool, StorageError>;
|
||
|
||
fn get_memory(
|
||
&self,
|
||
scope_kind: &str,
|
||
scope_key: &str,
|
||
namespace: &str,
|
||
memory_key: &str,
|
||
) -> Result<Option<MemoryRecord>, StorageError>;
|
||
|
||
fn list_memories(
|
||
&self,
|
||
scope_kind: &str,
|
||
scope_key: &str,
|
||
namespace: Option<&str>,
|
||
limit: usize,
|
||
) -> Result<Vec<MemoryRecord>, StorageError>;
|
||
|
||
fn search_memories_any(
|
||
&self,
|
||
scope_kind: &str,
|
||
scope_key: &str,
|
||
queries: &[String],
|
||
namespace: Option<&str>,
|
||
limit: usize,
|
||
) -> Result<Vec<MemoryRecord>, StorageError>;
|
||
}
|
||
|
||
pub trait SchedulerJobRepository: Send + Sync + 'static {
|
||
fn upsert_scheduler_job(
|
||
&self,
|
||
input: &SchedulerJobUpsert,
|
||
) -> Result<SchedulerJobRecord, StorageError>;
|
||
|
||
fn get_scheduler_job(&self, job_id: &str) -> Result<Option<SchedulerJobRecord>, StorageError>;
|
||
|
||
fn list_scheduler_jobs(
|
||
&self,
|
||
enabled_only: bool,
|
||
) -> Result<Vec<SchedulerJobRecord>, StorageError>;
|
||
|
||
fn list_running_scheduler_jobs(&self) -> Result<Vec<SchedulerJobRecord>, StorageError>;
|
||
|
||
fn delete_scheduler_job(&self, job_id: &str) -> Result<(), StorageError>;
|
||
|
||
fn update_scheduler_job_runtime(
|
||
&self,
|
||
job_id: &str,
|
||
state: SchedulerJobState,
|
||
last_status: Option<SchedulerJobStatus>,
|
||
last_error: Option<&str>,
|
||
run_count: i64,
|
||
last_fired_at: Option<i64>,
|
||
next_fire_at: Option<i64>,
|
||
paused_at: Option<i64>,
|
||
completed_at: Option<i64>,
|
||
) -> Result<(), StorageError>;
|
||
}
|
||
|
||
pub trait SkillEventRepository: Send + Sync + 'static {
|
||
fn append_skill_event(
|
||
&self,
|
||
session_id: Option<&str>,
|
||
event_type: &str,
|
||
skill_name: Option<&str>,
|
||
payload: &serde_json::Value,
|
||
) -> Result<(), StorageError>;
|
||
|
||
fn list_skill_events(
|
||
&self,
|
||
session_id: Option<&str>,
|
||
) -> Result<Vec<SkillEventRecord>, StorageError>;
|
||
}
|
||
|
||
pub trait TodoRepository: Send + Sync + 'static {
|
||
/// Replace all todos for a scope (full replacement pattern).
|
||
fn replace_todos(
|
||
&self,
|
||
scope_key: &str,
|
||
todo_records: &[TodoRecord],
|
||
) -> Result<Vec<TodoRecord>, StorageError>;
|
||
|
||
/// Load all todos for a scope, ordered by created_at.
|
||
fn list_todos(&self, scope_key: &str) -> Result<Vec<TodoRecord>, StorageError>;
|
||
}
|
||
|
||
impl ConversationRepository for super::SessionStore {
|
||
fn ensure_channel_session(
|
||
&self,
|
||
channel_name: &str,
|
||
chat_id: &str,
|
||
) -> Result<SessionRecord, StorageError> {
|
||
super::SessionStore::ensure_channel_session(self, channel_name, chat_id)
|
||
}
|
||
|
||
fn ensure_session(
|
||
&self,
|
||
session_id: &str,
|
||
channel_name: &str,
|
||
chat_id: &str,
|
||
title: &str,
|
||
) -> Result<SessionRecord, StorageError> {
|
||
super::SessionStore::ensure_session(self, session_id, channel_name, chat_id, title)
|
||
}
|
||
|
||
fn load_messages(&self, session_id: &str) -> Result<Vec<ChatMessage>, StorageError> {
|
||
super::SessionStore::load_messages(self, session_id)
|
||
}
|
||
|
||
fn load_messages_for_topic(
|
||
&self,
|
||
topic_id: &str,
|
||
session_id: Option<&str>,
|
||
) -> Result<Vec<ChatMessage>, StorageError> {
|
||
super::SessionStore::load_messages_for_topic(self, topic_id, session_id)
|
||
}
|
||
|
||
fn append_message(&self, session_id: &str, message: &ChatMessage) -> Result<(), StorageError> {
|
||
super::SessionStore::append_message(self, session_id, message)
|
||
}
|
||
|
||
fn append_message_with_topic(
|
||
&self,
|
||
session_id: &str,
|
||
topic_id: Option<&str>,
|
||
message: &ChatMessage,
|
||
) -> Result<(), StorageError> {
|
||
super::SessionStore::append_message_with_topic(self, session_id, topic_id, message)
|
||
}
|
||
|
||
fn append_messages_batch(
|
||
&self,
|
||
session_id: &str,
|
||
topic_id: Option<&str>,
|
||
messages: &[ChatMessage],
|
||
) -> Result<(), StorageError> {
|
||
super::SessionStore::append_messages_batch(self, session_id, topic_id, messages)
|
||
}
|
||
|
||
fn clear_messages(&self, session_id: &str) -> Result<(), StorageError> {
|
||
super::SessionStore::clear_messages(self, session_id)
|
||
}
|
||
|
||
fn compact_active_history(
|
||
&self,
|
||
session_id: &str,
|
||
snapshot_end_seq: i64,
|
||
preserved_system_messages: &[ChatMessage],
|
||
summary_message: &ChatMessage,
|
||
preserved_messages: &[ChatMessage],
|
||
) -> Result<bool, StorageError> {
|
||
super::SessionStore::compact_active_history(
|
||
self,
|
||
session_id,
|
||
snapshot_end_seq,
|
||
preserved_system_messages,
|
||
summary_message,
|
||
preserved_messages,
|
||
)
|
||
}
|
||
|
||
fn replace_active_history(
|
||
&self,
|
||
session_id: &str,
|
||
messages: &[ChatMessage],
|
||
) -> Result<(), StorageError> {
|
||
super::SessionStore::replace_active_history(self, session_id, messages)
|
||
}
|
||
|
||
fn replace_topic_history(
|
||
&self,
|
||
session_id: &str,
|
||
topic_id: &str,
|
||
messages: &[ChatMessage],
|
||
) -> Result<(), StorageError> {
|
||
super::SessionStore::replace_topic_history(self, session_id, topic_id, messages)
|
||
}
|
||
|
||
fn load_messages_for_topic_full(
|
||
&self,
|
||
topic_id: &str,
|
||
session_id: Option<&str>,
|
||
) -> Result<Vec<ChatMessage>, StorageError> {
|
||
super::SessionStore::load_messages_for_topic_full(self, topic_id, session_id)
|
||
}
|
||
|
||
fn compact_topic_history(
|
||
&self,
|
||
session_id: &str,
|
||
topic_id: &str,
|
||
new_messages: &[ChatMessage],
|
||
) -> Result<(), StorageError> {
|
||
super::SessionStore::compact_topic_history(self, session_id, topic_id, new_messages)
|
||
}
|
||
}
|
||
|
||
impl PromptInjectionRepository for super::SessionStore {
|
||
fn get_session(&self, session_id: &str) -> Result<Option<SessionRecord>, StorageError> {
|
||
super::SessionStore::get_session(self, session_id)
|
||
}
|
||
|
||
fn count_active_user_messages(&self, session_id: &str) -> Result<i64, StorageError> {
|
||
super::SessionStore::count_active_user_messages(self, session_id)
|
||
}
|
||
|
||
fn mark_agent_prompt_reinjected(&self, session_id: &str) -> Result<(), StorageError> {
|
||
super::SessionStore::mark_agent_prompt_reinjected(self, session_id)
|
||
}
|
||
}
|
||
|
||
impl MemoryRepository for super::SessionStore {
|
||
fn put_memory(&self, input: &MemoryUpsert) -> Result<MemoryRecord, StorageError> {
|
||
super::SessionStore::put_memory(self, input)
|
||
}
|
||
|
||
fn update_memory(&self, input: &MemoryUpsert) -> Result<Option<MemoryRecord>, StorageError> {
|
||
super::SessionStore::update_memory(self, input)
|
||
}
|
||
|
||
fn delete_memory(
|
||
&self,
|
||
scope_kind: &str,
|
||
scope_key: &str,
|
||
namespace: &str,
|
||
memory_key: &str,
|
||
) -> Result<bool, StorageError> {
|
||
super::SessionStore::delete_memory(self, scope_kind, scope_key, namespace, memory_key)
|
||
}
|
||
|
||
fn get_memory(
|
||
&self,
|
||
scope_kind: &str,
|
||
scope_key: &str,
|
||
namespace: &str,
|
||
memory_key: &str,
|
||
) -> Result<Option<MemoryRecord>, StorageError> {
|
||
super::SessionStore::get_memory(self, scope_kind, scope_key, namespace, memory_key)
|
||
}
|
||
|
||
fn list_memories(
|
||
&self,
|
||
scope_kind: &str,
|
||
scope_key: &str,
|
||
namespace: Option<&str>,
|
||
limit: usize,
|
||
) -> Result<Vec<MemoryRecord>, StorageError> {
|
||
super::SessionStore::list_memories(self, scope_kind, scope_key, namespace, limit)
|
||
}
|
||
|
||
fn search_memories_any(
|
||
&self,
|
||
scope_kind: &str,
|
||
scope_key: &str,
|
||
queries: &[String],
|
||
namespace: Option<&str>,
|
||
limit: usize,
|
||
) -> Result<Vec<MemoryRecord>, StorageError> {
|
||
super::SessionStore::search_memories_any(
|
||
self, scope_kind, scope_key, queries, namespace, limit,
|
||
)
|
||
}
|
||
}
|
||
|
||
impl SchedulerJobRepository for super::SessionStore {
|
||
fn upsert_scheduler_job(
|
||
&self,
|
||
input: &SchedulerJobUpsert,
|
||
) -> Result<SchedulerJobRecord, StorageError> {
|
||
super::SessionStore::upsert_scheduler_job(self, input)
|
||
}
|
||
|
||
fn get_scheduler_job(&self, job_id: &str) -> Result<Option<SchedulerJobRecord>, StorageError> {
|
||
super::SessionStore::get_scheduler_job(self, job_id)
|
||
}
|
||
|
||
fn list_scheduler_jobs(
|
||
&self,
|
||
enabled_only: bool,
|
||
) -> Result<Vec<SchedulerJobRecord>, StorageError> {
|
||
super::SessionStore::list_scheduler_jobs(self, enabled_only)
|
||
}
|
||
|
||
fn list_running_scheduler_jobs(&self) -> Result<Vec<SchedulerJobRecord>, StorageError> {
|
||
super::SessionStore::list_running_scheduler_jobs(self)
|
||
}
|
||
|
||
fn delete_scheduler_job(&self, job_id: &str) -> Result<(), StorageError> {
|
||
super::SessionStore::delete_scheduler_job(self, job_id)
|
||
}
|
||
|
||
fn update_scheduler_job_runtime(
|
||
&self,
|
||
job_id: &str,
|
||
state: SchedulerJobState,
|
||
last_status: Option<SchedulerJobStatus>,
|
||
last_error: Option<&str>,
|
||
run_count: i64,
|
||
last_fired_at: Option<i64>,
|
||
next_fire_at: Option<i64>,
|
||
paused_at: Option<i64>,
|
||
completed_at: Option<i64>,
|
||
) -> Result<(), StorageError> {
|
||
super::SessionStore::update_scheduler_job_runtime(
|
||
self,
|
||
job_id,
|
||
state,
|
||
last_status,
|
||
last_error,
|
||
run_count,
|
||
last_fired_at,
|
||
next_fire_at,
|
||
paused_at,
|
||
completed_at,
|
||
)
|
||
}
|
||
}
|
||
|
||
impl SkillEventRepository for super::SessionStore {
|
||
fn append_skill_event(
|
||
&self,
|
||
session_id: Option<&str>,
|
||
event_type: &str,
|
||
skill_name: Option<&str>,
|
||
payload: &serde_json::Value,
|
||
) -> Result<(), StorageError> {
|
||
super::SessionStore::append_skill_event(self, session_id, event_type, skill_name, payload)
|
||
}
|
||
|
||
fn list_skill_events(
|
||
&self,
|
||
session_id: Option<&str>,
|
||
) -> Result<Vec<SkillEventRecord>, StorageError> {
|
||
super::SessionStore::list_skill_events(self, session_id)
|
||
}
|
||
}
|
||
|
||
impl TodoRepository for super::SessionStore {
|
||
fn replace_todos(
|
||
&self,
|
||
scope_key: &str,
|
||
todo_records: &[TodoRecord],
|
||
) -> Result<Vec<TodoRecord>, StorageError> {
|
||
super::SessionStore::replace_todos(self, scope_key, todo_records)
|
||
}
|
||
|
||
fn list_todos(&self, scope_key: &str) -> Result<Vec<TodoRecord>, StorageError> {
|
||
super::SessionStore::list_todos(self, scope_key)
|
||
}
|
||
}
|