PicoBot/src/session/session_id.rs
xiaoxixi 86dea0f874 Refactor session management to support dialog-based architecture
- Removed InputHandler and related input event handling code.
- Updated GatewayState to handle new session commands for dialogs.
- Introduced UnifiedSessionId for managing session identifiers across channels and chats.
- Refactored Session and SessionManager to manage dialogs instead of sessions.
- Added methods for creating, listing, switching, renaming, archiving, and deleting dialogs.
- Updated storage functions to accommodate dialog IDs in persistent session management.
- Enhanced tests to cover new dialog functionalities and ensure stability.
2026-04-26 20:59:54 +08:00

121 lines
3.5 KiB
Rust

/// Unified session identifier composed of channel, chat_id, and dialog_id
///
/// Format: `channel:chat_id:dialog_id`
///
/// Examples:
/// - CLI: `"cli_chat:sid_abc123:dialog_xyz"`
/// - Feishu: `"feishu:oc_123456:dialog_xyz"`
///
/// For simple cases where only one dialog exists per chat:
/// - `dialog_id` defaults to `"default"`
use serde::{Deserialize, Serialize};
pub const DEFAULT_DIALOG_ID: &str = "default";
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct UnifiedSessionId {
pub channel: String,
pub chat_id: String,
pub dialog_id: String,
}
impl UnifiedSessionId {
/// Create a new UnifiedSessionId
pub fn new(channel: impl Into<String>, chat_id: impl Into<String>, dialog_id: impl Into<String>) -> Self {
Self {
channel: channel.into(),
chat_id: chat_id.into(),
dialog_id: dialog_id.into(),
}
}
/// Create with default dialog_id ("default")
pub fn with_default_dialog(channel: impl Into<String>, chat_id: impl Into<String>) -> Self {
Self {
channel: channel.into(),
chat_id: chat_id.into(),
dialog_id: DEFAULT_DIALOG_ID.to_string(),
}
}
/// Parse from string format "channel:chat_id:dialog_id"
pub fn parse(s: &str) -> Option<Self> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() != 3 {
return None;
}
Some(Self {
channel: parts[0].to_string(),
chat_id: parts[1].to_string(),
dialog_id: parts[2].to_string(),
})
}
/// Convert to string format "channel:chat_id:dialog_id"
pub fn to_string(&self) -> String {
format!("{}:{}:{}", self.channel, self.chat_id, self.dialog_id)
}
/// Get the session key without dialog_id (channel:chat_id)
/// This is used to group all dialogs within a chat
pub fn chat_scope(&self) -> String {
format!("{}:{}", self.channel, self.chat_id)
}
}
impl std::fmt::Display for UnifiedSessionId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_string())
}
}
// Note: No Deref implementation to avoid confusion between String and UnifiedSessionId
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let id = UnifiedSessionId::new("cli_chat", "sid123", "dialog456");
assert_eq!(id.channel, "cli_chat");
assert_eq!(id.chat_id, "sid123");
assert_eq!(id.dialog_id, "dialog456");
}
#[test]
fn test_with_default_dialog() {
let id = UnifiedSessionId::with_default_dialog("feishu", "oc123");
assert_eq!(id.channel, "feishu");
assert_eq!(id.chat_id, "oc123");
assert_eq!(id.dialog_id, "default");
}
#[test]
fn test_parse() {
let id = UnifiedSessionId::parse("cli_chat:sid123:dialog456").unwrap();
assert_eq!(id.channel, "cli_chat");
assert_eq!(id.chat_id, "sid123");
assert_eq!(id.dialog_id, "dialog456");
}
#[test]
fn test_parse_invalid() {
assert!(UnifiedSessionId::parse("invalid").is_none());
assert!(UnifiedSessionId::parse("only:two").is_none());
}
#[test]
fn test_to_string() {
let id = UnifiedSessionId::new("feishu", "oc123", "dialog789");
assert_eq!(id.to_string(), "feishu:oc123:dialog789");
}
#[test]
fn test_chat_scope() {
let id = UnifiedSessionId::new("feishu", "oc123", "dialog789");
assert_eq!(id.chat_scope(), "feishu:oc123");
}
}