1use schemars::JsonSchema;
2use serde::Deserialize;
3use serde::Serialize;
4use strum_macros::Display;
5use strum_macros::EnumIter;
6use ts_rs::TS;
7
8pub use crate::openai_models::ReasoningEffort;
9
10#[derive(
14 Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS,
15)]
16#[serde(rename_all = "lowercase")]
17#[strum(serialize_all = "lowercase")]
18pub enum ReasoningSummary {
19 #[default]
20 Auto,
21 Concise,
22 Detailed,
23 None,
25}
26
27#[derive(
30 Hash,
31 Debug,
32 Serialize,
33 Deserialize,
34 Default,
35 Clone,
36 Copy,
37 PartialEq,
38 Eq,
39 Display,
40 JsonSchema,
41 TS,
42)]
43#[serde(rename_all = "lowercase")]
44#[strum(serialize_all = "lowercase")]
45pub enum Verbosity {
46 Low,
47 #[default]
48 Medium,
49 High,
50}
51
52#[derive(
53 Deserialize, Debug, Clone, Copy, PartialEq, Default, Serialize, Display, JsonSchema, TS,
54)]
55#[serde(rename_all = "kebab-case")]
56#[strum(serialize_all = "kebab-case")]
57pub enum SandboxMode {
58 #[serde(rename = "read-only")]
59 #[default]
60 ReadOnly,
61
62 #[serde(rename = "workspace-write")]
63 WorkspaceWrite,
64
65 #[serde(rename = "danger-full-access")]
66 DangerFullAccess,
67}
68
69#[derive(
70 Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Display, JsonSchema, TS,
71)]
72#[serde(rename_all = "kebab-case")]
73#[strum(serialize_all = "kebab-case")]
74pub enum WindowsSandboxLevel {
75 #[default]
76 Disabled,
77 RestrictedToken,
78 Elevated,
79}
80
81#[derive(
82 Debug,
83 Serialize,
84 Deserialize,
85 Clone,
86 Copy,
87 PartialEq,
88 Eq,
89 Display,
90 JsonSchema,
91 TS,
92 PartialOrd,
93 Ord,
94 EnumIter,
95)]
96#[serde(rename_all = "lowercase")]
97#[strum(serialize_all = "lowercase")]
98pub enum Personality {
99 None,
100 Friendly,
101 Pragmatic,
102}
103
104#[derive(
105 Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default,
106)]
107#[serde(rename_all = "lowercase")]
108#[strum(serialize_all = "lowercase")]
109pub enum WebSearchMode {
110 Disabled,
111 #[default]
112 Cached,
113 Live,
114}
115
116#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
117#[serde(rename_all = "lowercase")]
118#[strum(serialize_all = "lowercase")]
119pub enum ServiceTier {
120 Standard,
122 Fast,
123}
124
125#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
126#[serde(rename_all = "lowercase")]
127#[strum(serialize_all = "lowercase")]
128pub enum ForcedLoginMethod {
129 Chatgpt,
130 Api,
131}
132
133#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
136#[serde(rename_all = "lowercase")]
137#[strum(serialize_all = "lowercase")]
138pub enum TrustLevel {
139 Trusted,
140 Untrusted,
141}
142
143#[derive(
164 Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS,
165)]
166#[serde(rename_all = "lowercase")]
167#[strum(serialize_all = "lowercase")]
168pub enum AltScreenMode {
169 #[default]
171 Auto,
172 Always,
174 Never,
176}
177
178#[derive(
180 Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema, TS, Default,
181)]
182#[serde(rename_all = "snake_case")]
183pub enum ModeKind {
184 Plan,
185 #[default]
186 #[serde(
187 alias = "code",
188 alias = "pair_programming",
189 alias = "execute",
190 alias = "custom"
191 )]
192 Default,
193 #[doc(hidden)]
194 #[serde(skip_serializing, skip_deserializing)]
195 #[schemars(skip)]
196 #[ts(skip)]
197 PairProgramming,
198 #[doc(hidden)]
199 #[serde(skip_serializing, skip_deserializing)]
200 #[schemars(skip)]
201 #[ts(skip)]
202 Execute,
203}
204
205pub const TUI_VISIBLE_COLLABORATION_MODES: [ModeKind; 2] = [ModeKind::Default, ModeKind::Plan];
206
207impl ModeKind {
208 pub const fn display_name(self) -> &'static str {
209 match self {
210 Self::Plan => "Plan",
211 Self::Default => "Default",
212 Self::PairProgramming => "Pair Programming",
213 Self::Execute => "Execute",
214 }
215 }
216
217 pub const fn is_tui_visible(self) -> bool {
218 matches!(self, Self::Plan | Self::Default)
219 }
220
221 pub const fn allows_request_user_input(self) -> bool {
222 matches!(self, Self::Plan)
223 }
224}
225
226#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, JsonSchema, TS)]
228#[serde(rename_all = "lowercase")]
229pub struct CollaborationMode {
230 pub mode: ModeKind,
231 pub settings: Settings,
232}
233
234impl CollaborationMode {
235 fn settings_ref(&self) -> &Settings {
237 &self.settings
238 }
239
240 pub fn model(&self) -> &str {
241 self.settings_ref().model.as_str()
242 }
243
244 pub fn reasoning_effort(&self) -> Option<ReasoningEffort> {
245 self.settings_ref().reasoning_effort
246 }
247
248 pub fn with_updates(
256 &self,
257 model: Option<String>,
258 effort: Option<Option<ReasoningEffort>>,
259 developer_instructions: Option<Option<String>>,
260 ) -> Self {
261 let settings = self.settings_ref();
262 let updated_settings = Settings {
263 model: model.unwrap_or_else(|| settings.model.clone()),
264 reasoning_effort: effort.unwrap_or(settings.reasoning_effort),
265 developer_instructions: developer_instructions
266 .unwrap_or_else(|| settings.developer_instructions.clone()),
267 };
268
269 CollaborationMode {
270 mode: self.mode,
271 settings: updated_settings,
272 }
273 }
274
275 pub fn apply_mask(&self, mask: &CollaborationModeMask) -> Self {
281 let settings = self.settings_ref();
282 CollaborationMode {
283 mode: mask.mode.unwrap_or(self.mode),
284 settings: Settings {
285 model: mask.model.clone().unwrap_or_else(|| settings.model.clone()),
286 reasoning_effort: mask.reasoning_effort.unwrap_or(settings.reasoning_effort),
287 developer_instructions: mask
288 .developer_instructions
289 .clone()
290 .unwrap_or_else(|| settings.developer_instructions.clone()),
291 },
292 }
293 }
294}
295
296#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, JsonSchema, TS)]
298pub struct Settings {
299 pub model: String,
300 pub reasoning_effort: Option<ReasoningEffort>,
301 pub developer_instructions: Option<String>,
302}
303
304#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, JsonSchema, TS)]
307pub struct CollaborationModeMask {
308 pub name: String,
309 pub mode: Option<ModeKind>,
310 pub model: Option<String>,
311 pub reasoning_effort: Option<Option<ReasoningEffort>>,
312 pub developer_instructions: Option<Option<String>>,
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use pretty_assertions::assert_eq;
319
320 #[test]
321 fn apply_mask_can_clear_optional_fields() {
322 let mode = CollaborationMode {
323 mode: ModeKind::Default,
324 settings: Settings {
325 model: "gpt-5.2-codex".to_string(),
326 reasoning_effort: Some(ReasoningEffort::High),
327 developer_instructions: Some("stay focused".to_string()),
328 },
329 };
330 let mask = CollaborationModeMask {
331 name: "Clear".to_string(),
332 mode: None,
333 model: None,
334 reasoning_effort: Some(None),
335 developer_instructions: Some(None),
336 };
337
338 let expected = CollaborationMode {
339 mode: ModeKind::Default,
340 settings: Settings {
341 model: "gpt-5.2-codex".to_string(),
342 reasoning_effort: None,
343 developer_instructions: None,
344 },
345 };
346 assert_eq!(expected, mode.apply_mask(&mask));
347 }
348
349 #[test]
350 fn mode_kind_deserializes_alias_values_to_default() {
351 for alias in ["code", "pair_programming", "execute", "custom"] {
352 let json = format!("\"{alias}\"");
353 let mode: ModeKind = serde_json::from_str(&json).expect("deserialize mode");
354 assert_eq!(ModeKind::Default, mode);
355 }
356 }
357
358 #[test]
359 fn tui_visible_collaboration_modes_match_mode_kind_visibility() {
360 let expected = [ModeKind::Default, ModeKind::Plan];
361 assert_eq!(expected, TUI_VISIBLE_COLLABORATION_MODES);
362
363 for mode in TUI_VISIBLE_COLLABORATION_MODES {
364 assert!(mode.is_tui_visible());
365 }
366
367 assert!(!ModeKind::PairProgramming.is_tui_visible());
368 assert!(!ModeKind::Execute.is_tui_visible());
369 }
370}