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 StructuredProtocolErrors,
51 HeadlessStdioLogPaths,
53 StatusSessionSetFilter,
55 StatusUpdatedSinceFilter,
57 TypedNudgeOutcomes,
59 ValidateTargetPreflight,
61 EventsCursor,
63 EventsLongPoll,
65 TmuxPaneSnapshot,
67 KillOutcomes,
69 SpawnConflicts,
71 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}