use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{configuration::SessionType, transports::StructuredEntry};
pub use crate::transports::vocabulary::{
DeliveryPayloadMode, LookFreshness, LookSnapshotSource, SendOutcome,
};
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ListedSessionTransport {
Tmux,
Acp,
Ui,
Pubsub,
}
impl From<SessionType> for ListedSessionTransport {
fn from(value: SessionType) -> Self {
match value {
SessionType::Tmux => Self::Tmux,
SessionType::Acp => Self::Acp,
SessionType::Ui => Self::Ui,
SessionType::Pubsub => Self::Pubsub,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct ListedSession {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
pub transport: ListedSessionTransport,
pub ready: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ListedBundleState {
Up,
Down,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ListedBundleStartupHealth {
Healthy,
Degraded,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "snapshot_format", rename_all = "snake_case")]
pub enum LookSnapshotPayload {
Lines {
snapshot_lines: Vec<String>,
},
StructuredEntriesV1 {
snapshot_entries: Vec<StructuredEntry>,
entries_total: usize,
returned_entries_count: usize,
freshness: LookFreshness,
snapshot_source: LookSnapshotSource,
#[serde(skip_serializing_if = "Option::is_none")]
stale_reason_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
snapshot_age_ms: Option<u64>,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct StartupFailureRecord {
pub session_id: String,
pub transport: ListedSessionTransport,
pub code: String,
pub reason: String,
pub timestamp: String,
pub sequence: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<Value>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct ListedBundle {
pub id: String,
pub hosted: bool,
pub state: ListedBundleState,
#[serde(skip_serializing_if = "Option::is_none")]
pub startup_health: Option<ListedBundleStartupHealth>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state_reason_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state_reason: Option<String>,
pub startup_failure_count: usize,
pub recent_startup_failures: Vec<StartupFailureRecord>,
pub principals: Vec<ListedSession>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct SendResult {
pub target_session: String,
pub message_id: String,
pub outcome: SendOutcome,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<Value>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct ReconciliationReport {
pub bootstrap_session: Option<String>,
pub created_sessions: Vec<String>,
pub pruned_sessions: Vec<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct ShutdownReport {
pub pruned_sessions: Vec<String>,
pub killed_tmux_server: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BundleStartupReport {
pub ready_session_count: usize,
pub failed_startups: Vec<StartupFailureRecord>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct RelayError {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<Value>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct RelayStreamEvent {
pub event_type: String,
pub target_session: String,
pub created_at: String,
pub payload: Value,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct BundleTransitionEntry {
pub bundle_name: String,
pub outcome: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(tag = "operation", rename_all = "snake_case")]
pub enum RelayRequest {
Up,
Down,
List {
requester_session: Option<String>,
},
Send {
request_id: Option<String>,
requester_session: String,
message: String,
targets: Vec<String>,
broadcast: bool,
#[serde(default)]
quiet_window_ms: Option<u64>,
},
Look {
requester_session: String,
target_session: String,
#[serde(default)]
lines: Option<usize>,
#[serde(default)]
offset: Option<usize>,
},
Raww {
request_id: Option<String>,
requester_session: String,
target_session: String,
text: String,
#[serde(default)]
no_enter: bool,
},
ChoicesPick {
choice_request_id: String,
outcome: String,
#[serde(default)]
option_id: Option<String>,
},
ChoicesList,
NewPeer {
principal_id: String,
#[serde(default)]
scope: Option<String>,
#[serde(default)]
output_path: Option<String>,
},
ChangePsk {
principal_id: String,
},
IdentityIntrospect {
target_session: String,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum RelayResponse {
BundleTransition {
schema_version: String,
action: String,
bundles: Vec<BundleTransitionEntry>,
changed_bundle_count: usize,
skipped_bundle_count: usize,
failed_bundle_count: usize,
changed_any: bool,
},
List {
schema_version: String,
bundle: ListedBundle,
},
Send {
schema_version: String,
request_id: Option<String>,
requester_session: String,
#[serde(skip_serializing_if = "Option::is_none")]
sender_display_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
authenticated_identity: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
on_behalf_of: Option<String>,
results: Vec<SendResult>,
},
Look {
schema_version: String,
requester_session: String,
target_session: String,
captured_at: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
authenticated_identity: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
on_behalf_of: Option<String>,
#[serde(flatten)]
snapshot: LookSnapshotPayload,
},
Raww {
schema_version: String,
status: String,
target_session: String,
transport: ListedSessionTransport,
#[serde(skip_serializing_if = "Option::is_none")]
request_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
message_id: Option<String>,
},
ChoicesPick {
schema_version: String,
status: String,
choice_request_id: String,
outcome: String,
#[serde(skip_serializing_if = "Option::is_none")]
decided_by: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
reason_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
reason: Option<String>,
},
ChoicesList {
schema_version: String,
pending_requests: Vec<PendingChoiceEntry>,
},
NewPeer {
schema_version: String,
principal_id: String,
principal_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
psk: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
output_path: Option<String>,
config_snippet: String,
},
ChangePsk {
schema_version: String,
principal_id: String,
psk: String,
},
IdentityIntrospect {
schema_version: String,
principal_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
expires_at: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
on_behalf_of: Option<String>,
verified: bool,
},
Error {
error: RelayError,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct PendingChoiceEntry {
pub message_id: String,
pub choice_request_id: String,
pub target_session: String,
pub requested_kind: String,
pub requested_details: Value,
pub enqueued_at: String,
}