cognee_database/entities/session_record.rs
1//! SeaORM entity for the `session_records` table (LIB-03).
2//!
3//! Mirrors Python's `SessionRecord` SQLAlchemy model at
4//! `cognee/modules/session_lifecycle/models.py:10-86` byte-for-byte:
5//!
6//! - Composite primary key on `(session_id, user_id)`.
7//! - `status` defaults to `"running"`. The `"abandoned"` value is **never**
8//! written to the row — it is inferred at read time by LIB-05's
9//! `effective_status` SQL expression based on `last_activity_at`.
10//! - Aggregate counters (`tokens_in`, `tokens_out`, `cost_usd`,
11//! `error_count`) accumulate via `LLMGateway` / `SessionManager` hooks.
12//!
13//! UUIDs (`user_id`, `dataset_id`) are persisted as 32-char hex strings to
14//! match the rest of the schema (see `uuid_hex.rs`); LIB-05's trait
15//! converts `uuid::Uuid` ↔ `String` at the repository boundary.
16
17use sea_orm::entity::prelude::*;
18use serde::{Deserialize, Serialize};
19
20#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
21#[sea_orm(table_name = "session_records")]
22pub struct Model {
23 /// Caller-provided session id (e.g. `"cc_myproj_ab12cd34ef56"`).
24 /// Scoped per user — same string from two different users is two
25 /// different sessions.
26 #[sea_orm(primary_key, auto_increment = false)]
27 pub session_id: String,
28 /// Owning user id, hex-encoded UUID.
29 #[sea_orm(primary_key, auto_increment = false)]
30 pub user_id: String,
31
32 /// Optional dataset scope (hex UUID); used by ACL filtering when
33 /// listing/aggregating.
34 pub dataset_id: Option<String>,
35
36 /// Stored status. `"abandoned"` is inferred at read time, never
37 /// written (see LIB-05).
38 pub status: String,
39
40 pub started_at: DateTimeUtc,
41 pub last_activity_at: DateTimeUtc,
42 pub ended_at: Option<DateTimeUtc>,
43
44 pub tokens_in: i32,
45 pub tokens_out: i32,
46 pub cost_usd: f64,
47
48 pub error_count: i32,
49
50 pub last_model: Option<String>,
51}
52
53#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
54pub enum Relation {}
55
56impl ActiveModelBehavior for ActiveModel {}
57
58impl Model {
59 /// Serialize to a JSON object whose key ordering matches Python's
60 /// [`SessionRecord.to_dict()`](https://github.com/topoteretes/cognee/blob/main/cognee/modules/session_lifecycle/models.py#L68-L86).
61 ///
62 /// Used by LIB-05's repository / E-09's HTTP DTO. Field order is
63 /// load-bearing: clients (e.g. the dashboard) snapshot the response
64 /// shape in test fixtures.
65 pub fn to_dict(&self) -> serde_json::Value {
66 // Insertion order matters for Python parity: callers (E-09's HTTP
67 // DTO + dashboard test snapshots) compare the rendered JSON
68 // shape literally. The `cognee-database` crate enables
69 // `serde_json/preserve_order` so `json!` emits keys in the
70 // order they appear here.
71 serde_json::json!({
72 "session_id": self.session_id,
73 "user_id": self.user_id,
74 "dataset_id": self.dataset_id,
75 "status": self.status,
76 "started_at": self.started_at.to_rfc3339(),
77 "last_activity_at": self.last_activity_at.to_rfc3339(),
78 "ended_at": self.ended_at.map(|t| t.to_rfc3339()),
79 "tokens_in": self.tokens_in,
80 "tokens_out": self.tokens_out,
81 "cost_usd": self.cost_usd,
82 "error_count": self.error_count,
83 "last_model": self.last_model,
84 })
85 }
86}