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 pub person: Option<String>,
99}
100
101pub fn person_key(channel: &ChannelId, user: &UserId, person: Option<&str>) -> String {
105 match person {
106 Some(id) => format!("person:{id}"),
107 None => format!("{channel}/{user}"),
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
113pub struct Usage {
114 pub turns: u64,
116 pub tokens: u64,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122#[non_exhaustive]
123pub enum ToolDecision {
124 Allowed,
126 Denied,
128 Declined,
130}
131
132impl ToolDecision {
133 pub fn as_str(self) -> &'static str {
135 match self {
136 Self::Allowed => "allowed",
137 Self::Denied => "denied",
138 Self::Declined => "declined",
139 }
140 }
141
142 pub fn parse(value: &str) -> Option<Self> {
144 match value {
145 "allowed" => Some(Self::Allowed),
146 "denied" => Some(Self::Denied),
147 "declined" => Some(Self::Declined),
148 _ => None,
149 }
150 }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
156pub struct ToolCallRecord {
157 pub run_id: Option<RunId>,
159 pub command: Option<String>,
161 pub person: String,
163 pub conversation: ConversationKey,
165 pub server: String,
167 pub tool: String,
169 pub decision: ToolDecision,
171 pub error: Option<String>,
173 pub duration: Duration,
175 pub at: Timestamp,
177}
178
179#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct RoleGrant {
182 pub role: String,
184 pub account: String,
186 pub granted_by: String,
188 pub granted_at: Timestamp,
190 pub expires_at: Timestamp,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
196pub enum FireState {
197 Sending,
199 Sent,
201 Failed,
203 Missed,
205}
206
207impl FireState {
208 pub fn as_str(self) -> &'static str {
210 match self {
211 Self::Sending => "sending",
212 Self::Sent => "sent",
213 Self::Failed => "failed",
214 Self::Missed => "missed",
215 }
216 }
217
218 pub fn parse(value: &str) -> Option<Self> {
220 match value {
221 "sending" => Some(Self::Sending),
222 "sent" => Some(Self::Sent),
223 "failed" => Some(Self::Failed),
224 "missed" => Some(Self::Missed),
225 _ => None,
226 }
227 }
228
229 pub fn is_final(self) -> bool {
231 !matches!(self, Self::Sending)
232 }
233}
234
235#[derive(Debug, Clone, PartialEq, Eq)]
237pub struct Fire {
238 pub task_id: String,
240 pub fire_at: Timestamp,
242 pub state: FireState,
244 pub attempts: u32,
246 pub updated_at: Timestamp,
248}
249
250#[async_trait]
252pub trait ConversationStore: Send + Sync {
253 async fn mark_seen(
255 &self,
256 channel: &ChannelId,
257 event_id: &str,
258 ttl: Duration,
259 ) -> Result<bool, StoreError>;
260
261 async fn append(&self, message: NewMessage) -> Result<(), StoreError>;
263
264 async fn recent(
266 &self,
267 conversation: &ConversationKey,
268 limit: usize,
269 ) -> Result<Vec<StoredMessage>, StoreError>;
270
271 async fn reset(&self, conversation: &ConversationKey) -> Result<(), StoreError>;
274
275 async fn session(
277 &self,
278 conversation: &ConversationKey,
279 ) -> Result<Option<(String, String)>, StoreError>;
280
281 async fn set_session(
283 &self,
284 conversation: &ConversationKey,
285 backend: &str,
286 session: &str,
287 ) -> Result<(), StoreError>;
288
289 async fn record_run(&self, run: AgentRunRecord) -> Result<(), StoreError>;
291
292 async fn owners(&self) -> Result<Vec<(ChannelId, UserId)>, StoreError>;
294
295 async fn remove_owner(&self, channel: &ChannelId, user: &UserId) -> Result<bool, StoreError>;
297
298 async fn create_pairing_code(&self, code: &str, ttl: Duration) -> Result<(), StoreError>;
300
301 async fn redeem_pairing_code(
307 &self,
308 code: &str,
309 channel: &ChannelId,
310 user: &UserId,
311 ) -> Result<bool, StoreError>;
312
313 async fn claim_fire(&self, task_id: &str, fire_at: Timestamp) -> Result<Fire, StoreError>;
316
317 async fn set_fire(
319 &self,
320 task_id: &str,
321 fire_at: Timestamp,
322 state: FireState,
323 attempts: u32,
324 ) -> Result<(), StoreError>;
325
326 async fn recent_fires(&self, limit: usize) -> Result<Vec<Fire>, StoreError>;
328
329 async fn set_direct_chat(
331 &self,
332 channel: &ChannelId,
333 user: &UserId,
334 conversation: &ConversationKey,
335 ) -> Result<(), StoreError>;
336
337 async fn direct_chat(
339 &self,
340 channel: &ChannelId,
341 user: &UserId,
342 ) -> Result<Option<ConversationKey>, StoreError>;
343
344 async fn usage_since(&self, person: &str, since: Timestamp) -> Result<Usage, StoreError>;
346
347 async fn record_tool_call(&self, call: ToolCallRecord) -> Result<(), StoreError>;
349
350 async fn tool_calls(
352 &self,
353 since: Timestamp,
354 limit: usize,
355 ) -> Result<Vec<ToolCallRecord>, StoreError>;
356
357 async fn grant_role(&self, grant: RoleGrant) -> Result<(), StoreError>;
360
361 async fn revoke_role(&self, role: &str, account: &str) -> Result<bool, StoreError>;
363
364 async fn role_grants(&self, now: Timestamp) -> Result<Vec<RoleGrant>, StoreError>;
366
367 async fn purge_before(&self, before: Timestamp) -> Result<u64, StoreError>;
370}