1use std::time::Duration;
4
5use jiff::Timestamp;
6use serde::{Deserialize, Serialize};
7
8use crate::agent::TokenUsage;
9use crate::caps::ToolPolicy;
10use crate::id::{ChannelId, ConversationKey, MessageId, RunId, UserId};
11use crate::{BoxError, async_trait};
12
13#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub enum StoreError {
17 #[error("the database schema version {found} is newer than this okbot supports ({supported})")]
19 TooNew {
20 found: u32,
22 supported: u32,
24 },
25 #[error("storage error: {0}")]
27 Backend(#[source] BoxError),
28}
29
30pub const PAIRING_MAX_FAILURES: u32 = 5;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(rename_all = "kebab-case")]
36pub enum MessageRole {
37 User,
39 Bot,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct NewMessage {
46 pub conversation: ConversationKey,
48 pub role: MessageRole,
50 pub author_id: Option<UserId>,
52 pub author_name: String,
54 pub text: String,
56 pub message_id: Option<MessageId>,
58 pub at: Timestamp,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct StoredMessage {
65 pub role: MessageRole,
67 pub author_name: String,
69 pub text: String,
71 pub at: Timestamp,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
77pub struct AgentRunRecord {
78 pub run_id: RunId,
80 pub conversation: ConversationKey,
82 pub backend: String,
84 pub model: Option<String>,
86 pub tools: ToolPolicy,
88 pub usage: Option<TokenUsage>,
90 pub duration: Duration,
92 pub error: Option<String>,
94 pub started_at: Timestamp,
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub enum FireState {
101 Sending,
103 Sent,
105 Failed,
107 Missed,
109}
110
111impl FireState {
112 pub fn as_str(self) -> &'static str {
114 match self {
115 Self::Sending => "sending",
116 Self::Sent => "sent",
117 Self::Failed => "failed",
118 Self::Missed => "missed",
119 }
120 }
121
122 pub fn parse(value: &str) -> Option<Self> {
124 match value {
125 "sending" => Some(Self::Sending),
126 "sent" => Some(Self::Sent),
127 "failed" => Some(Self::Failed),
128 "missed" => Some(Self::Missed),
129 _ => None,
130 }
131 }
132
133 pub fn is_final(self) -> bool {
135 !matches!(self, Self::Sending)
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct Fire {
142 pub task_id: String,
144 pub fire_at: Timestamp,
146 pub state: FireState,
148 pub attempts: u32,
150 pub updated_at: Timestamp,
152}
153
154#[async_trait]
156pub trait ConversationStore: Send + Sync {
157 async fn mark_seen(
159 &self,
160 channel: &ChannelId,
161 event_id: &str,
162 ttl: Duration,
163 ) -> Result<bool, StoreError>;
164
165 async fn append(&self, message: NewMessage) -> Result<(), StoreError>;
167
168 async fn recent(
170 &self,
171 conversation: &ConversationKey,
172 limit: usize,
173 ) -> Result<Vec<StoredMessage>, StoreError>;
174
175 async fn reset(&self, conversation: &ConversationKey) -> Result<(), StoreError>;
178
179 async fn session(
181 &self,
182 conversation: &ConversationKey,
183 ) -> Result<Option<(String, String)>, StoreError>;
184
185 async fn set_session(
187 &self,
188 conversation: &ConversationKey,
189 backend: &str,
190 session: &str,
191 ) -> Result<(), StoreError>;
192
193 async fn record_run(&self, run: AgentRunRecord) -> Result<(), StoreError>;
195
196 async fn owners(&self) -> Result<Vec<(ChannelId, UserId)>, StoreError>;
198
199 async fn remove_owner(&self, channel: &ChannelId, user: &UserId) -> Result<bool, StoreError>;
201
202 async fn create_pairing_code(&self, code: &str, ttl: Duration) -> Result<(), StoreError>;
204
205 async fn redeem_pairing_code(
211 &self,
212 code: &str,
213 channel: &ChannelId,
214 user: &UserId,
215 ) -> Result<bool, StoreError>;
216
217 async fn claim_fire(&self, task_id: &str, fire_at: Timestamp) -> Result<Fire, StoreError>;
220
221 async fn set_fire(
223 &self,
224 task_id: &str,
225 fire_at: Timestamp,
226 state: FireState,
227 attempts: u32,
228 ) -> Result<(), StoreError>;
229
230 async fn recent_fires(&self, limit: usize) -> Result<Vec<Fire>, StoreError>;
232
233 async fn set_direct_chat(
235 &self,
236 channel: &ChannelId,
237 user: &UserId,
238 conversation: &ConversationKey,
239 ) -> Result<(), StoreError>;
240
241 async fn direct_chat(
243 &self,
244 channel: &ChannelId,
245 user: &UserId,
246 ) -> Result<Option<ConversationKey>, StoreError>;
247
248 async fn purge_before(&self, before: Timestamp) -> Result<u64, StoreError>;
251}