Skip to main content

lilo_rm_core/
version.rs

1use std::fmt::{Display, Formatter};
2use std::str::FromStr;
3
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6pub const RUNTIME_PROTOCOL_VERSION: &str = "0.6";
7
8pub const RUNTIME_PROTOCOL_CAPABILITIES: &[RuntimeCapability] = &[
9    RuntimeCapability::StructuredProtocolErrors,
10    RuntimeCapability::HeadlessStdioLogPaths,
11    RuntimeCapability::StatusSessionSetFilter,
12    RuntimeCapability::StatusUpdatedSinceFilter,
13    RuntimeCapability::TypedNudgeOutcomes,
14    RuntimeCapability::ValidateTargetPreflight,
15    RuntimeCapability::EventsCursor,
16    RuntimeCapability::EventsLongPoll,
17    RuntimeCapability::TmuxPaneSnapshot,
18    RuntimeCapability::KillOutcomes,
19    RuntimeCapability::SpawnConflicts,
20    RuntimeCapability::SpawnRequestMounts,
21];
22
23#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
24pub struct VersionInfo {
25    pub version: String,
26    pub git_sha: String,
27    pub protocol_version: String,
28    pub capabilities: Vec<RuntimeCapability>,
29}
30
31impl VersionInfo {
32    pub fn new(version: impl Into<String>, git_sha: impl Into<String>) -> Self {
33        Self {
34            version: version.into(),
35            git_sha: git_sha.into(),
36            protocol_version: RUNTIME_PROTOCOL_VERSION.to_owned(),
37            capabilities: RUNTIME_PROTOCOL_CAPABILITIES.to_vec(),
38        }
39    }
40}
41
42pub fn version_info() -> VersionInfo {
43    VersionInfo::new(env!("CARGO_PKG_VERSION"), env!("RTM_GIT_SHA"))
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47#[non_exhaustive]
48pub enum RuntimeCapability {
49    /// Error responses expose stable machine readable codes.
50    StructuredProtocolErrors,
51    /// Headless spawn responses include stdout and stderr log paths.
52    HeadlessStdioLogPaths,
53    /// Status requests accept a set of session ids.
54    StatusSessionSetFilter,
55    /// Status requests accept an updated time lower bound.
56    StatusUpdatedSinceFilter,
57    /// Nudge responses expose typed delivery outcomes.
58    TypedNudgeOutcomes,
59    /// ValidateTarget checks a target string without spawning.
60    ValidateTargetPreflight,
61    /// Events support durable cursor replay.
62    EventsCursor,
63    /// Events requests accept a bounded long poll wait window.
64    EventsLongPoll,
65    /// Tmux targets support on demand pane snapshot capture.
66    TmuxPaneSnapshot,
67    /// Kill responses include typed Signalled or AlreadyExited outcomes.
68    KillOutcomes,
69    /// Spawn rejects session reuse and occupied tmux panes with typed conflicts.
70    SpawnConflicts,
71    /// Spawn requests can carry declared host to container bind mounts.
72    SpawnRequestMounts,
73}
74
75impl RuntimeCapability {
76    pub const fn as_str(self) -> &'static str {
77        match self {
78            Self::StructuredProtocolErrors => "structured_protocol_errors",
79            Self::HeadlessStdioLogPaths => "headless_stdio_log_paths",
80            Self::StatusSessionSetFilter => "status_session_set_filter",
81            Self::StatusUpdatedSinceFilter => "status_updated_since_filter",
82            Self::TypedNudgeOutcomes => "typed_nudge_outcomes",
83            Self::ValidateTargetPreflight => "validate_target_preflight",
84            Self::EventsCursor => "events_cursor",
85            Self::EventsLongPoll => "events_long_poll",
86            Self::TmuxPaneSnapshot => "tmux_pane_snapshot",
87            Self::KillOutcomes => "kill_outcomes",
88            Self::SpawnConflicts => "spawn_conflicts",
89            Self::SpawnRequestMounts => "spawn_request_mounts",
90        }
91    }
92}
93
94impl Display for RuntimeCapability {
95    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
96        formatter.write_str(self.as_str())
97    }
98}
99
100impl FromStr for RuntimeCapability {
101    type Err = String;
102
103    fn from_str(value: &str) -> Result<Self, Self::Err> {
104        match value {
105            "structured_protocol_errors" => Ok(Self::StructuredProtocolErrors),
106            "headless_stdio_log_paths" => Ok(Self::HeadlessStdioLogPaths),
107            "status_session_set_filter" => Ok(Self::StatusSessionSetFilter),
108            "status_updated_since_filter" => Ok(Self::StatusUpdatedSinceFilter),
109            "typed_nudge_outcomes" => Ok(Self::TypedNudgeOutcomes),
110            "validate_target_preflight" => Ok(Self::ValidateTargetPreflight),
111            "events_cursor" => Ok(Self::EventsCursor),
112            "events_long_poll" => Ok(Self::EventsLongPoll),
113            "tmux_pane_snapshot" => Ok(Self::TmuxPaneSnapshot),
114            "kill_outcomes" => Ok(Self::KillOutcomes),
115            "spawn_conflicts" => Ok(Self::SpawnConflicts),
116            "spawn_request_mounts" => Ok(Self::SpawnRequestMounts),
117            other => Err(format!("unknown runtime capability {other}")),
118        }
119    }
120}
121
122impl Serialize for RuntimeCapability {
123    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
124    where
125        S: Serializer,
126    {
127        serializer.serialize_str(self.as_str())
128    }
129}
130
131impl<'de> Deserialize<'de> for RuntimeCapability {
132    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
133    where
134        D: Deserializer<'de>,
135    {
136        String::deserialize(deserializer)?
137            .parse()
138            .map_err(serde::de::Error::custom)
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::{
145        RUNTIME_PROTOCOL_CAPABILITIES, RUNTIME_PROTOCOL_VERSION, RuntimeCapability, VersionInfo,
146    };
147
148    #[test]
149    fn protocol_version_advertises_v06_spawn_conflict_contract() {
150        assert_eq!(RUNTIME_PROTOCOL_VERSION, "0.6");
151        assert_eq!(VersionInfo::new("rtm", "git").protocol_version, "0.6");
152    }
153
154    #[test]
155    fn protocol_capabilities_advertise_spawn_request_mounts() {
156        assert!(RUNTIME_PROTOCOL_CAPABILITIES.contains(&RuntimeCapability::SpawnRequestMounts));
157        assert_eq!(
158            RuntimeCapability::SpawnRequestMounts.as_str(),
159            "spawn_request_mounts"
160        );
161    }
162}