use async_trait::async_trait;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::entities::session_record;
use crate::types::DatabaseError;
#[derive(Debug, Clone)]
pub struct SessionListFilters {
pub user_id: Uuid,
pub permitted_dataset_ids: Vec<Uuid>,
pub since: Option<DateTime<Utc>>,
pub status_filter: Option<String>,
pub limit: u32,
pub offset: u32,
pub order_by: String,
pub descending: bool,
}
#[derive(Debug, Clone)]
pub struct SessionRowWithStatus {
pub record: session_record::Model,
pub effective_status: String,
}
impl SessionRowWithStatus {
pub fn to_dict(&self) -> serde_json::Value {
let mut value = self.record.to_dict();
if let Some(map) = value.as_object_mut() {
map.insert(
"effective_status".to_string(),
serde_json::Value::String(self.effective_status.clone()),
);
}
value
}
}
#[derive(Debug, Clone)]
pub struct SessionListPage {
pub sessions: Vec<SessionRowWithStatus>,
pub total: i64,
pub limit: u32,
pub offset: u32,
}
impl SessionListPage {
pub fn has_more(&self) -> bool {
let returned = i64::try_from(self.sessions.len()).unwrap_or(i64::MAX);
let offset = i64::from(self.offset);
offset.saturating_add(returned) < self.total
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionStats {
pub sessions: i64,
pub total_spend_usd: f64,
pub avg_spend_per_session_usd: f64,
pub tokens_in: i64,
pub tokens_out: i64,
pub tokens_total: i64,
pub agent_time_s: f64,
pub avg_session_s: f64,
pub success_rate: f64,
pub completed: i64,
pub failed: i64,
pub abandoned: i64,
pub running: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostByModelRow {
pub model: String,
pub session_count: i64,
pub cost_usd: f64,
pub tokens_in: i64,
pub tokens_out: i64,
}
#[async_trait]
#[allow(clippy::too_many_arguments)] pub trait SessionLifecycleDb: Send + Sync {
async fn ensure_and_touch_session(
&self,
session_id: &str,
user_id: Uuid,
dataset_id: Option<Uuid>,
) -> Result<(), DatabaseError>;
async fn accumulate_usage(
&self,
session_id: &str,
user_id: Uuid,
model: Option<&str>,
tokens_in: i64,
tokens_out: i64,
cost_usd: f64,
errored: bool,
) -> Result<(), DatabaseError>;
async fn get_session_row(
&self,
session_id: &str,
user_id: Uuid,
permitted_dataset_ids: &[Uuid],
prefer_other_owner: bool,
) -> Result<Option<SessionRowWithStatus>, DatabaseError>;
async fn list_session_rows(
&self,
filters: SessionListFilters,
) -> Result<SessionListPage, DatabaseError>;
async fn aggregate_stats(
&self,
user_id: Uuid,
permitted_dataset_ids: &[Uuid],
since: Option<DateTime<Utc>>,
) -> Result<SessionStats, DatabaseError>;
async fn cost_by_model(
&self,
user_id: Uuid,
permitted_dataset_ids: &[Uuid],
since: Option<DateTime<Utc>>,
) -> Result<Vec<CostByModelRow>, DatabaseError>;
}