use crate::error::{AgentDbError, Result};
use crate::schema::now_ms;
use rusqlite::params;
use rusqlite::Connection;
use serde_json::Value;
use std::sync::{Arc, Mutex};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct Trace {
pub id: String,
pub session_id: Option<String>,
pub parent_id: Option<String>,
pub trace_type: String,
pub content: String,
pub metadata: Option<Value>,
pub created_at: i64,
}
pub struct TraceStore {
conn: Arc<Mutex<Connection>>,
}
impl TraceStore {
pub(crate) fn new(conn: Arc<Mutex<Connection>>) -> Self {
Self { conn }
}
pub fn add_trace(
&self,
session_id: Option<&str>,
parent_id: Option<&str>,
trace_type: &str,
content: &str,
metadata: Option<Value>,
) -> Result<String> {
let trace_id = Uuid::new_v4().to_string();
let meta_str = metadata.as_ref().map(|m| m.to_string());
let now = now_ms();
let conn = self.conn.lock().unwrap();
conn.execute(
"INSERT INTO _adb_traces
(id, session_id, parent_id, trace_type, content, metadata, created_at)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
params![trace_id, session_id, parent_id, trace_type, content, meta_str, now],
)?;
Ok(trace_id)
}
pub fn get_traces(&self, session_id: &str) -> Result<Vec<Trace>> {
let conn = self.conn.lock().unwrap();
let mut stmt = conn.prepare(
"SELECT id, session_id, parent_id, trace_type, content, metadata, created_at
FROM _adb_traces
WHERE session_id = ?1
ORDER BY created_at ASC",
)?;
let rows = stmt.query_map(params![session_id], parse_trace)?;
rows.map(|r| r.map_err(AgentDbError::Sqlite)).collect()
}
pub fn get_trace_tree(&self, root_id: &str) -> Result<Vec<Trace>> {
let conn = self.conn.lock().unwrap();
let mut stmt = conn.prepare(
"WITH RECURSIVE tree(id) AS (
SELECT id FROM _adb_traces WHERE id = ?1
UNION ALL
SELECT t.id
FROM _adb_traces t
JOIN tree ON t.parent_id = tree.id
)
SELECT t.id, t.session_id, t.parent_id, t.trace_type,
t.content, t.metadata, t.created_at
FROM _adb_traces t
JOIN tree ON t.id = tree.id
ORDER BY t.created_at ASC",
)?;
let rows = stmt.query_map(params![root_id], parse_trace)?;
rows.map(|r| r.map_err(AgentDbError::Sqlite)).collect()
}
}
fn parse_trace(row: &rusqlite::Row) -> rusqlite::Result<Trace> {
let meta_str: Option<String> = row.get(5)?;
Ok(Trace {
id: row.get(0)?,
session_id: row.get(1)?,
parent_id: row.get(2)?,
trace_type: row.get(3)?,
content: row.get(4)?,
metadata: meta_str
.as_deref()
.and_then(|s| serde_json::from_str(s).ok()),
created_at: row.get(6)?,
})
}