agent-context 0.1.3

Multi-backend agent context manager with three-zone memory model
Documentation
use serde::Serialize;
use serde::de::DeserializeOwned;

use crate::Role;

/// 后端消息类型必须实现此 trait。
///
/// `AgentContext` 不感知消息的具体内容结构,仅通过此 trait 获取两个关键信息:
///
/// - **角色**:用于按角色筛选和保留
/// - **推理保留策略**:用于格式转换时决定是否剥离 `reasoning_content`
///
/// ## 实现要求
///
/// 由于孤儿规则,你无法为外部类型直接实现此 trait。需要在你的 crate 中
/// 创建 newtype 包装器:
///
/// ```ignore
/// #[derive(Debug, Clone, Serialize, Deserialize)]
/// struct MyMessage(deepseek_sdk::Message);
///
/// impl ContextMessage for MyMessage {
///     fn role(&self) -> Role { /* 映射 */ }
///     fn preserve_reasoning(&self) -> bool { /* 按 provider 规则 */ }
/// }
/// ```
pub trait ContextMessage: Send + Sync + Clone + Serialize + DeserializeOwned {
    /// 返回此消息的角色。
    fn role(&self) -> Role;

    /// 此消息的 `reasoning_content` 是否需要在后续请求中保留。
    ///
    /// 各 LLM provider 有不同规则。例如 DeepSeek 要求:
    /// - 工具调用场景中 assistant 的 `reasoning_content` 必须完整回传
    /// - 非工具调用场景中可省略
    fn preserve_reasoning(&self) -> bool;

    /// 返回一个剥离 `reasoning_content` 的副本。
    ///
    /// 用于 [`ContextBackend::to_request_messages`](crate::ContextBackend::to_request_messages) 的默认实现:
    /// 对 `!preserve_reasoning()` 的消息剥离思维链,减少 token 消耗。
    fn without_reasoning(self) -> Self;

    /// 返回一个角色被替换为新角色的副本。
    ///
    /// 用于 [`ContextBackend::to_system_message`](crate::ContextBackend::to_system_message) 的默认实现:
    /// 将消息转换为 System 角色(压缩摘要等场景)。
    fn with_role(self, role: Role) -> Self;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
    struct MockMsg {
        role: Role,
        content: String,
    }

    impl ContextMessage for MockMsg {
        fn role(&self) -> Role {
            self.role
        }
        fn preserve_reasoning(&self) -> bool {
            false
        }
        fn without_reasoning(self) -> Self {
            self
        }
        fn with_role(mut self, role: Role) -> Self {
            self.role = role;
            self
        }
    }

    #[test]
    fn mock_msg_role() {
        let msg = MockMsg {
            role: Role::User,
            content: "hello".into(),
        };
        assert_eq!(msg.role(), Role::User);
    }

    #[test]
    fn mock_msg_preserve_reasoning() {
        let msg = MockMsg {
            role: Role::Assistant,
            content: "world".into(),
        };
        assert!(!msg.preserve_reasoning());
    }

    #[test]
    fn mock_msg_serde() {
        let msg = MockMsg {
            role: Role::Assistant,
            content: "world".into(),
        };
        let json = serde_json::to_string(&msg).unwrap();
        let back: MockMsg = serde_json::from_str(&json).unwrap();
        assert_eq!(back.content, "world");
    }
}