synth_ai_core/tracing/
storage.rs1use async_trait::async_trait;
7use chrono::{DateTime, Utc};
8use serde_json::Value;
9
10use super::error::TracingError;
11use super::models::{
12 EventReward, MarkovBlanketMessage, OutcomeReward, SessionTimeStep, SessionTrace, TracingEvent,
13};
14
15#[async_trait]
19pub trait TraceStorage: Send + Sync {
20 async fn initialize(&self) -> Result<(), TracingError>;
26
27 async fn close(&self) -> Result<(), TracingError>;
29
30 async fn ensure_session(
39 &self,
40 session_id: &str,
41 created_at: DateTime<Utc>,
42 metadata: &Value,
43 ) -> Result<(), TracingError>;
44
45 async fn get_session(&self, session_id: &str) -> Result<Option<SessionTrace>, TracingError>;
47
48 async fn delete_session(&self, session_id: &str) -> Result<bool, TracingError>;
50
51 async fn ensure_timestep(
60 &self,
61 session_id: &str,
62 step: &SessionTimeStep,
63 ) -> Result<i64, TracingError>;
64
65 async fn update_timestep(
67 &self,
68 session_id: &str,
69 step_id: &str,
70 completed_at: Option<DateTime<Utc>>,
71 ) -> Result<(), TracingError>;
72
73 async fn insert_event(
79 &self,
80 session_id: &str,
81 timestep_db_id: Option<i64>,
82 event: &TracingEvent,
83 ) -> Result<i64, TracingError>;
84
85 async fn insert_message(
91 &self,
92 session_id: &str,
93 timestep_db_id: Option<i64>,
94 msg: &MarkovBlanketMessage,
95 ) -> Result<i64, TracingError>;
96
97 async fn insert_outcome_reward(
103 &self,
104 session_id: &str,
105 reward: &OutcomeReward,
106 ) -> Result<i64, TracingError>;
107
108 async fn insert_event_reward(
110 &self,
111 session_id: &str,
112 event_id: i64,
113 message_id: Option<i64>,
114 turn_number: Option<i32>,
115 reward: &EventReward,
116 ) -> Result<i64, TracingError>;
117
118 async fn query(&self, sql: &str, params: QueryParams) -> Result<Vec<Value>, TracingError>;
124
125 async fn update_session_counts(&self, session_id: &str) -> Result<(), TracingError>;
127}
128
129#[derive(Debug, Clone, Default)]
131pub enum QueryParams {
132 #[default]
134 None,
135 Positional(Vec<Value>),
137 Named(Vec<(String, Value)>),
139}
140
141impl QueryParams {
142 pub fn is_empty(&self) -> bool {
144 matches!(self, QueryParams::None)
145 }
146}
147
148impl From<Vec<Value>> for QueryParams {
149 fn from(values: Vec<Value>) -> Self {
150 if values.is_empty() {
151 QueryParams::None
152 } else {
153 QueryParams::Positional(values)
154 }
155 }
156}
157
158impl From<Vec<(String, Value)>> for QueryParams {
159 fn from(values: Vec<(String, Value)>) -> Self {
160 if values.is_empty() {
161 QueryParams::None
162 } else {
163 QueryParams::Named(values)
164 }
165 }
166}
167
168#[derive(Debug, Clone)]
170pub struct StorageConfig {
171 pub db_url: String,
173 pub auth_token: Option<String>,
175 pub auto_init: bool,
177}
178
179impl Default for StorageConfig {
180 fn default() -> Self {
181 Self {
182 db_url: ":memory:".to_string(),
183 auth_token: None,
184 auto_init: true,
185 }
186 }
187}
188
189impl StorageConfig {
190 pub fn memory() -> Self {
192 Self::default()
193 }
194
195 pub fn file(path: impl Into<String>) -> Self {
197 Self {
198 db_url: path.into(),
199 auth_token: None,
200 auto_init: true,
201 }
202 }
203
204 pub fn turso(url: impl Into<String>, token: impl Into<String>) -> Self {
206 Self {
207 db_url: url.into(),
208 auth_token: Some(token.into()),
209 auto_init: true,
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_storage_config_default() {
220 let config = StorageConfig::default();
221 assert_eq!(config.db_url, ":memory:");
222 assert!(config.auth_token.is_none());
223 assert!(config.auto_init);
224 }
225
226 #[test]
227 fn test_storage_config_file() {
228 let config = StorageConfig::file("/tmp/test.db");
229 assert_eq!(config.db_url, "/tmp/test.db");
230 }
231
232 #[test]
233 fn test_storage_config_turso() {
234 let config = StorageConfig::turso("libsql://test.turso.io", "token123");
235 assert_eq!(config.db_url, "libsql://test.turso.io");
236 assert_eq!(config.auth_token, Some("token123".to_string()));
237 }
238}