use crate::thread::Thread;
use crate::thread::Version;
use crate::Message;
use crate::Visibility;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SortOrder {
#[default]
Asc,
Desc,
}
#[derive(Debug, Clone)]
pub struct MessageQuery {
pub after: Option<i64>,
pub before: Option<i64>,
pub limit: usize,
pub order: SortOrder,
pub visibility: Option<Visibility>,
pub run_id: Option<String>,
}
impl Default for MessageQuery {
fn default() -> Self {
Self {
after: None,
before: None,
limit: 50,
order: SortOrder::Asc,
visibility: Some(Visibility::All),
run_id: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageWithCursor {
pub cursor: i64,
#[serde(flatten)]
pub message: Message,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessagePage {
pub messages: Vec<MessageWithCursor>,
pub has_more: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prev_cursor: Option<i64>,
}
#[derive(Debug, Clone)]
pub struct ThreadListQuery {
pub offset: usize,
pub limit: usize,
pub resource_id: Option<String>,
pub parent_thread_id: Option<String>,
}
impl Default for ThreadListQuery {
fn default() -> Self {
Self {
offset: 0,
limit: 50,
resource_id: None,
parent_thread_id: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThreadListPage {
pub items: Vec<String>,
pub total: usize,
pub has_more: bool,
}
pub fn paginate_in_memory(
messages: &[std::sync::Arc<Message>],
query: &MessageQuery,
) -> MessagePage {
let limit = query.limit.clamp(1, 200);
let total = messages.len();
if total == 0 {
return MessagePage {
messages: Vec::new(),
has_more: false,
next_cursor: None,
prev_cursor: None,
};
}
let start = query.after.map(|c| (c + 1).max(0) as usize).unwrap_or(0);
let end = query
.before
.map(|c| (c.max(0) as usize).min(total))
.unwrap_or(total);
if start >= total || start >= end {
return MessagePage {
messages: Vec::new(),
has_more: false,
next_cursor: None,
prev_cursor: None,
};
}
let mut items: Vec<(i64, &std::sync::Arc<Message>)> = messages[start..end]
.iter()
.enumerate()
.filter(|(_, m)| match query.visibility {
Some(vis) => m.visibility == vis,
None => true,
})
.filter(|(_, m)| match &query.run_id {
Some(rid) => {
m.metadata.as_ref().and_then(|meta| meta.run_id.as_deref()) == Some(rid.as_str())
}
None => true,
})
.map(|(i, m)| ((start + i) as i64, m))
.collect();
if query.order == SortOrder::Desc {
items.reverse();
}
let has_more = items.len() > limit;
let limited: Vec<_> = items.into_iter().take(limit).collect();
MessagePage {
next_cursor: limited.last().map(|(c, _)| *c),
prev_cursor: limited.first().map(|(c, _)| *c),
messages: limited
.into_iter()
.map(|(c, m)| MessageWithCursor {
cursor: c,
message: (**m).clone(),
})
.collect(),
has_more,
}
}
#[derive(Debug, Error)]
pub enum ThreadStoreError {
#[error("Thread not found: {0}")]
NotFound(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Invalid thread id: {0}")]
InvalidId(String),
#[error("Thread already exists")]
AlreadyExists,
#[error("Version conflict: expected {expected}, actual {actual}")]
VersionConflict { expected: Version, actual: Version },
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VersionPrecondition {
#[default]
Any,
Exact(Version),
}
#[derive(Debug, Clone, Copy)]
pub struct Committed {
pub version: Version,
}
#[derive(Debug, Clone)]
pub struct ThreadHead {
pub thread: Thread,
pub version: Version,
}