use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::{ProjectId, Session, SessionCallerReference, SessionKind, SupervisorReference};
pub const MAX_SUPERVISOR_DIRECTORY_PAGE: usize = 20;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SupervisorCurrentUserScope {
pub supervisor: SupervisorReference,
pub created_at: DateTime<Utc>,
pub orchestration_revision: u64,
pub legacy_state_revision: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SupervisorCurrentUserTarget {
pub session_id: String,
pub created_at: DateTime<Utc>,
pub kind: SessionKind,
pub root_session_id: String,
pub parent_session_id: Option<String>,
pub authority_sha256: String,
pub ancestry_sha256: String,
pub metadata_version: u64,
pub project_id: Option<ProjectId>,
}
impl SupervisorCurrentUserTarget {
pub fn capture(session: &Session) -> Result<Self, &'static str> {
Self::capture_with_ancestors(session, &[])
}
pub fn capture_with_ancestors(
session: &Session,
ancestors: &[crate::ActorAncestorObservation],
) -> Result<Self, &'static str> {
session
.validate_conversation_state()
.map_err(|_| "invalid target conversation authority")?;
crate::ActorSession::from_session(session)
.map_err(|_| "invalid canonical target identity")?;
if !session.authority_identity.is_ordinary() {
return Err("current-user target must have ordinary Session authority");
}
crate::HostScopeSessionObservation::capture(session, ancestors.to_vec())
.map_err(|_| "invalid canonical target ancestry")?;
Ok(Self {
session_id: session.id.clone(),
created_at: session.created_at,
kind: session.kind,
root_session_id: session.root_session_id.clone(),
parent_session_id: session.parent_session_id.clone(),
authority_sha256: hex::encode(Sha256::digest(
serde_json::to_vec(&SessionCallerReference::from(session))
.map_err(|_| "invalid target authority encoding")?,
)),
ancestry_sha256: hex::encode(Sha256::digest(
serde_json::to_vec(ancestors).map_err(|_| "invalid target ancestry encoding")?,
)),
metadata_version: session.metadata_version,
project_id: session
.project_id_meta()
.map(|id| id.parse().map_err(|_| "invalid target Project"))
.transpose()?,
})
}
pub fn matches(&self, session: &Session) -> bool {
Self::capture(session).is_ok_and(|current| current == *self)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SupervisorCurrentUserPage {
pub scope: SupervisorCurrentUserScope,
pub targets: Vec<SupervisorCurrentUserTarget>,
pub next_after: Option<String>,
}
pub fn supervisor_current_user_message_id(
supervisor: &SupervisorReference,
target: &SupervisorCurrentUserTarget,
operation_id: &str,
) -> crate::SessionMessageId {
crate::SessionMessageId::stable(
"supervisor_current_user_followup_v1",
&serde_json::json!({
"supervisor":supervisor,
"target_id":target.session_id,
"target_created_at":target.created_at,
"operation_id":operation_id,
}),
)
}