Skip to main content

xcodeai/session/
mod.rs

1pub mod store;
2pub use store::SessionStore;
3
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Session {
9    pub id: String,
10    pub title: Option<String>,
11    pub created_at: DateTime<Utc>,
12    pub updated_at: DateTime<Utc>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct StoredMessage {
17    pub id: String,
18    pub session_id: String,
19    pub role: String,
20    pub content: Option<String>,
21    pub tool_calls: Option<String>, // JSON-serialized Vec<ToolCall>
22    pub tool_call_id: Option<String>,
23    pub created_at: DateTime<Utc>,
24}
25
26/// Generate a short title from the first user message (≤50 chars, truncated at word boundary)
27pub fn auto_title(message: &str) -> String {
28    let trimmed = message.trim();
29    if trimmed.chars().count() <= 50 {
30        return trimmed.to_string();
31    }
32    // Find the byte index of the 50th character (safe for multi-byte CJK chars)
33    let byte_end = trimmed.char_indices().nth(50).map(|(i, _)| i).unwrap_or(trimmed.len());
34    let cut = &trimmed[..byte_end];
35    if let Some(pos) = cut.rfind(' ') {
36        trimmed[..pos].to_string()
37    } else {
38        cut.to_string()
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45
46    #[test]
47    fn test_auto_title_short() {
48        assert_eq!(auto_title("hello"), "hello");
49    }
50
51    #[test]
52    fn test_auto_title_truncates_at_word() {
53        let msg = "implement a function to read files from the filesystem efficiently";
54        let title = auto_title(msg);
55        assert!(title.len() <= 50);
56        assert!(!title.ends_with(' '));
57    }
58
59    #[test]
60    fn test_auto_title_no_spaces() {
61        let msg = "a".repeat(60);
62        let title = auto_title(&msg);
63        assert_eq!(title.len(), 50);
64    }
65
66    #[test]
67    fn test_auto_title_cjk() {
68        // This input caused a panic: byte index 50 is not a char boundary
69        let msg = "我要在老家建房,需要一个设计软件,最好是多agent 可以出设计效果的那种";
70        let title = auto_title(msg); // must not panic
71        assert!(title.chars().count() <= 50);
72        // Verify it doesn't slice mid-character
73        assert!(title.is_char_boundary(title.len()));
74    }
75
76    #[test]
77    fn test_auto_title_cjk_short() {
78        let msg = "你好世界";
79        assert_eq!(auto_title(msg), "你好世界");
80    }
81}