use std::collections::{BTreeMap, BTreeSet};
use serde::{Deserialize, Serialize};
use serde_json::json;
use crate::discourse::{AgentStatus, ArchiveRecord, RoomJoinRequest, RoomResponse};
use crate::error::Result;
use crate::identity::AgentId;
use crate::mail::{MailCardCache, MailboxCard, MessagePayload, Packet};
use crate::profile::AgentProfile;
use super::inputs::{RoomSendMessageInput, RoomSubmitEventInput};
use super::views::{
ActiveTurn, AgentProtocolsHost, HeldDraft, InboxItem, MailDelivery, MailDirection,
RoomMemberView, TimelineItem,
};
pub type RoomKey = (String, String);
#[derive(Clone, Debug, Default)]
pub struct LocalConnectorState {
pub hosts: BTreeMap<String, AgentProtocolsHost>,
pub(crate) rooms: BTreeMap<RoomKey, LocalRoomState>,
pub(crate) profiles: BTreeMap<AgentId, AgentProfile>,
pub(crate) join_requests: BTreeMap<RoomKey, Vec<RoomJoinRequest>>,
pub(crate) own_join_requests: BTreeMap<RoomKey, RoomJoinRequest>,
pub(crate) agent_statuses: BTreeMap<RoomKey, BTreeMap<AgentId, AgentStatus>>,
pub(crate) inbox: BTreeMap<String, InboxEntry>,
pub(crate) drafts: BTreeMap<String, HeldDraftEntry>,
pub(crate) mail: MailState,
}
impl LocalConnectorState {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct LocalRoomState {
pub(crate) host: String,
pub(crate) room: RoomResponse,
pub(crate) head_seq: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) head_hash: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) presented_seq: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) presented_hash: Option<String>,
pub(crate) synced_seq: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) synced_hash: Option<String>,
pub(crate) subscribed: bool,
pub(crate) members: BTreeMap<AgentId, RoomMemberView>,
pub(crate) timeline: Vec<TimelineItem>,
pub(crate) records: Vec<ArchiveRecord>,
pub(crate) read_seq: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) active_turn: Option<ActiveTurn>,
}
impl LocalRoomState {
pub(crate) fn new(host: String, room: RoomResponse) -> Self {
Self {
host,
room,
head_seq: 0,
head_hash: None,
presented_seq: None,
presented_hash: None,
synced_seq: 0,
synced_hash: None,
subscribed: false,
members: BTreeMap::new(),
timeline: Vec::new(),
records: Vec::new(),
read_seq: 0,
active_turn: None,
}
}
pub(crate) fn unread_count(&self) -> usize {
self.timeline
.iter()
.filter(|item| item.seq > self.read_seq)
.count()
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct MailState {
pub(crate) mailboxes: BTreeMap<String, Vec<MailboxCard>>,
pub(crate) pins: MailCardCache,
pub(crate) blocked_senders: BTreeSet<AgentId>,
pub(crate) messages: Vec<StoredMailMessage>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct StoredMailMessage {
pub(crate) direction: MailDirection,
pub(crate) message: MessagePayload,
pub(crate) mailbox_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) received_at: Option<i64>,
#[serde(default)]
pub(crate) read: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) packet: Option<Packet>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub(crate) delivery: Option<MailDelivery>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub(crate) enum InboxEntryState {
Pending,
Claimed {
until: i64,
},
Deferred {
until: i64,
},
Acknowledged,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct InboxEntry {
pub(crate) item: InboxItem,
pub(crate) state: InboxEntryState,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct HeldDraftEntry {
pub(crate) draft: HeldDraft,
pub(crate) request: HeldDraftRequest,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "kind", content = "input", rename_all = "snake_case")]
pub(crate) enum HeldDraftRequest {
Message(RoomSendMessageInput),
Event(RoomSubmitEventInput),
}
pub const LOCAL_CONNECTOR_SNAPSHOT_VERSION: u64 = 2;
#[derive(Serialize, Deserialize)]
pub(crate) struct ConnectorSnapshot {
pub(crate) version: u64,
pub(crate) agent_id: AgentId,
pub(crate) next_nonce: u64,
hosts: BTreeMap<String, AgentProtocolsHost>,
rooms: Vec<LocalRoomState>,
profiles: BTreeMap<AgentId, AgentProfile>,
join_requests: Vec<RoomJoinRequests>,
own_join_requests: Vec<OwnJoinRequest>,
agent_statuses: Vec<RoomAgentStatuses>,
inbox: BTreeMap<String, InboxEntry>,
drafts: BTreeMap<String, HeldDraftEntry>,
mail: MailSnapshot,
}
#[derive(Serialize, Deserialize)]
struct MailSnapshot {
mailboxes: Vec<OwnMailbox>,
pins: Vec<MailboxCard>,
blocked_senders: BTreeSet<AgentId>,
messages: Vec<StoredMailMessage>,
}
#[derive(Serialize, Deserialize)]
struct OwnMailbox {
mailbox_id: String,
cards: Vec<MailboxCard>,
}
#[derive(Serialize, Deserialize)]
struct RoomJoinRequests {
host: String,
room_id: String,
requests: Vec<RoomJoinRequest>,
}
#[derive(Serialize, Deserialize)]
struct OwnJoinRequest {
host: String,
room_id: String,
request: RoomJoinRequest,
}
#[derive(Serialize, Deserialize)]
struct RoomAgentStatuses {
host: String,
room_id: String,
statuses: BTreeMap<AgentId, AgentStatus>,
}
impl ConnectorSnapshot {
pub(crate) fn new(
agent_id: AgentId,
next_nonce: u64,
state: &LocalConnectorState,
) -> Result<Self> {
let pins = serde_json::from_value(state.mail.pins.snapshot()?["pins"].take())?;
Ok(Self {
version: LOCAL_CONNECTOR_SNAPSHOT_VERSION,
agent_id,
next_nonce,
hosts: state.hosts.clone(),
rooms: state.rooms.values().cloned().collect(),
profiles: state.profiles.clone(),
join_requests: state
.join_requests
.iter()
.map(|((host, room_id), requests)| RoomJoinRequests {
host: host.clone(),
room_id: room_id.clone(),
requests: requests.clone(),
})
.collect(),
own_join_requests: state
.own_join_requests
.iter()
.map(|((host, room_id), request)| OwnJoinRequest {
host: host.clone(),
room_id: room_id.clone(),
request: request.clone(),
})
.collect(),
agent_statuses: state
.agent_statuses
.iter()
.map(|((host, room_id), statuses)| RoomAgentStatuses {
host: host.clone(),
room_id: room_id.clone(),
statuses: statuses.clone(),
})
.collect(),
inbox: state.inbox.clone(),
drafts: state.drafts.clone(),
mail: MailSnapshot {
mailboxes: state
.mail
.mailboxes
.iter()
.map(|(mailbox_id, cards)| OwnMailbox {
mailbox_id: mailbox_id.clone(),
cards: cards.clone(),
})
.collect(),
pins,
blocked_senders: state.mail.blocked_senders.clone(),
messages: state.mail.messages.clone(),
},
})
}
pub(crate) fn into_state(self) -> Result<LocalConnectorState> {
let pins = MailCardCache::from_snapshot(&json!({"version": 1, "pins": self.mail.pins}))?;
Ok(LocalConnectorState {
hosts: self.hosts,
rooms: self
.rooms
.into_iter()
.map(|room| ((room.host.clone(), room.room.id.clone()), room))
.collect(),
profiles: self.profiles,
join_requests: self
.join_requests
.into_iter()
.map(|entry| ((entry.host, entry.room_id), entry.requests))
.collect(),
own_join_requests: self
.own_join_requests
.into_iter()
.map(|entry| ((entry.host, entry.room_id), entry.request))
.collect(),
agent_statuses: self
.agent_statuses
.into_iter()
.map(|entry| ((entry.host, entry.room_id), entry.statuses))
.collect(),
inbox: self.inbox,
drafts: self.drafts,
mail: MailState {
mailboxes: self
.mail
.mailboxes
.into_iter()
.map(|mailbox| (mailbox.mailbox_id, mailbox.cards))
.collect(),
pins,
blocked_senders: self.mail.blocked_senders,
messages: self.mail.messages,
},
})
}
}