use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use starweaver_core::{RunId, SessionId};
use starweaver_stream::{AgentStreamRecord, DisplayMessage, ReplayEvent, ReplaySnapshot};
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct StreamPublicationTargets {
pub archive: bool,
pub replay: bool,
}
impl StreamPublicationTargets {
#[must_use]
pub const fn new(archive: bool, replay: bool) -> Self {
Self { archive, replay }
}
#[must_use]
pub const fn is_empty(self) -> bool {
!self.archive && !self.replay
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PendingStreamPublication {
pub publication_id: String,
pub session_id: SessionId,
pub run_id: RunId,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub stream_records: Vec<AgentStreamRecord>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub display_messages: Vec<DisplayMessage>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub replay_events: Vec<ReplayEvent>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub display_snapshot: Option<ReplaySnapshot>,
pub archive_pending: bool,
pub replay_pending: bool,
pub created_at: DateTime<Utc>,
}
impl starweaver_core::VersionedRecord for PendingStreamPublication {
const SCHEMA: &'static str = "starweaver.session.stream_publication";
}
fn publication_id(session_id: &SessionId, run_id: &RunId) -> String {
let mut digest = Sha256::new();
for component in [session_id.as_str(), run_id.as_str()] {
digest.update(component.len().to_string().as_bytes());
digest.update(b":");
digest.update(component.as_bytes());
digest.update(b";");
}
format!("publication-sha256:{:x}", digest.finalize())
}
impl PendingStreamPublication {
#[must_use]
pub fn new(
session_id: SessionId,
run_id: RunId,
targets: StreamPublicationTargets,
created_at: DateTime<Utc>,
) -> Self {
let publication_id = publication_id(&session_id, &run_id);
Self {
publication_id,
session_id,
run_id,
stream_records: Vec::new(),
display_messages: Vec::new(),
replay_events: Vec::new(),
display_snapshot: None,
archive_pending: targets.archive,
replay_pending: targets.replay,
created_at,
}
}
#[must_use]
pub const fn is_complete(&self) -> bool {
!self.archive_pending && !self.replay_pending
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum StreamPublicationTarget {
Archive,
Replay,
}
#[cfg(test)]
mod tests {
use chrono::Utc;
use starweaver_core::{RunId, SessionId};
use super::{PendingStreamPublication, StreamPublicationTargets};
#[test]
fn publication_identity_is_unambiguous_for_delimiter_bearing_ids() {
let first = PendingStreamPublication::new(
SessionId::from_string("a:b"),
RunId::from_string("c"),
StreamPublicationTargets::new(true, true),
Utc::now(),
);
let second = PendingStreamPublication::new(
SessionId::from_string("a"),
RunId::from_string("b:c"),
StreamPublicationTargets::new(true, true),
Utc::now(),
);
assert_ne!(first.publication_id, second.publication_id);
}
}