1#[cfg(feature = "json")]
5use std::collections::HashMap;
6use std::fmt;
7use std::str::FromStr;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "kebab-case")]
14pub enum SandboxMode {
15 ReadOnly,
17 #[default]
19 WorkspaceWrite,
20 DangerFullAccess,
22}
23
24impl SandboxMode {
25 pub(crate) fn as_arg(self) -> &'static str {
26 match self {
27 Self::ReadOnly => "read-only",
28 Self::WorkspaceWrite => "workspace-write",
29 Self::DangerFullAccess => "danger-full-access",
30 }
31 }
32}
33
34#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
36#[serde(rename_all = "kebab-case")]
37pub enum ApprovalPolicy {
38 Untrusted,
40 OnFailure,
42 #[default]
44 OnRequest,
45 Never,
47}
48
49impl ApprovalPolicy {
50 pub(crate) fn as_arg(self) -> &'static str {
51 match self {
52 Self::Untrusted => "untrusted",
53 Self::OnFailure => "on-failure",
54 Self::OnRequest => "on-request",
55 Self::Never => "never",
56 }
57 }
58}
59
60#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "lowercase")]
63pub enum Color {
64 Always,
66 Never,
68 #[default]
70 Auto,
71}
72
73impl Color {
74 pub(crate) fn as_arg(self) -> &'static str {
75 match self {
76 Self::Always => "always",
77 Self::Never => "never",
78 Self::Auto => "auto",
79 }
80 }
81}
82
83#[cfg(feature = "json")]
88#[derive(Debug, Clone, Deserialize, Serialize)]
89pub struct JsonLineEvent {
90 #[serde(rename = "type", default)]
91 pub event_type: String,
92 #[serde(flatten)]
93 pub extra: HashMap<String, serde_json::Value>,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub struct CliVersion {
101 pub major: u32,
102 pub minor: u32,
103 pub patch: u32,
104}
105
106impl CliVersion {
107 #[must_use]
108 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
109 Self {
110 major,
111 minor,
112 patch,
113 }
114 }
115
116 pub fn parse_version_output(output: &str) -> Result<Self, VersionParseError> {
117 output
118 .split_whitespace()
119 .find_map(|token| token.parse().ok())
120 .ok_or_else(|| VersionParseError(output.trim().to_string()))
121 }
122
123 #[must_use]
124 pub fn satisfies_minimum(&self, minimum: &CliVersion) -> bool {
125 self >= minimum
126 }
127}
128
129impl PartialOrd for CliVersion {
130 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
131 Some(self.cmp(other))
132 }
133}
134
135impl Ord for CliVersion {
136 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
137 self.major
138 .cmp(&other.major)
139 .then(self.minor.cmp(&other.minor))
140 .then(self.patch.cmp(&other.patch))
141 }
142}
143
144impl fmt::Display for CliVersion {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
147 }
148}
149
150impl FromStr for CliVersion {
151 type Err = VersionParseError;
152
153 fn from_str(s: &str) -> Result<Self, Self::Err> {
154 let parts: Vec<&str> = s.split('.').collect();
155 if parts.len() != 3 {
156 return Err(VersionParseError(s.to_string()));
157 }
158
159 Ok(Self {
160 major: parts[0]
161 .parse()
162 .map_err(|_| VersionParseError(s.to_string()))?,
163 minor: parts[1]
164 .parse()
165 .map_err(|_| VersionParseError(s.to_string()))?,
166 patch: parts[2]
167 .parse()
168 .map_err(|_| VersionParseError(s.to_string()))?,
169 })
170 }
171}
172
173#[derive(Debug, Clone, thiserror::Error)]
174#[error("invalid version string: {0:?}")]
175pub struct VersionParseError(pub String);
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn parses_codex_version_output() {
183 let version = CliVersion::parse_version_output("codex-cli 0.116.0").unwrap();
184 assert_eq!(version, CliVersion::new(0, 116, 0));
185 }
186
187 #[test]
188 fn parses_plain_version_output() {
189 let version = CliVersion::parse_version_output("0.116.0").unwrap();
190 assert_eq!(version, CliVersion::new(0, 116, 0));
191 }
192}