use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use strum_macros::Display;
use strum_macros::EnumIter;
use ts_rs::TS;
pub use crate::openai_models::ReasoningEffort;
#[derive(
Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ReasoningSummary {
#[default]
Auto,
Concise,
Detailed,
None,
}
#[derive(
Hash,
Debug,
Serialize,
Deserialize,
Default,
Clone,
Copy,
PartialEq,
Eq,
Display,
JsonSchema,
TS,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum Verbosity {
Low,
#[default]
Medium,
High,
}
#[derive(
Deserialize, Debug, Clone, Copy, PartialEq, Default, Serialize, Display, JsonSchema, TS,
)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum SandboxMode {
#[serde(rename = "read-only")]
#[default]
ReadOnly,
#[serde(rename = "workspace-write")]
WorkspaceWrite,
#[serde(rename = "danger-full-access")]
DangerFullAccess,
}
#[derive(
Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Display, JsonSchema, TS,
)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub enum WindowsSandboxLevel {
#[default]
Disabled,
RestrictedToken,
Elevated,
}
#[derive(
Debug,
Serialize,
Deserialize,
Clone,
Copy,
PartialEq,
Eq,
Display,
JsonSchema,
TS,
PartialOrd,
Ord,
EnumIter,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum Personality {
None,
Friendly,
Pragmatic,
}
#[derive(
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS, Default,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum WebSearchMode {
Disabled,
#[default]
Cached,
Live,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ServiceTier {
Standard,
Fast,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum ForcedLoginMethod {
Chatgpt,
Api,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum TrustLevel {
Trusted,
Untrusted,
}
#[derive(
Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, JsonSchema, TS,
)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum AltScreenMode {
#[default]
Auto,
Always,
Never,
}
#[derive(
Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema, TS, Default,
)]
#[serde(rename_all = "snake_case")]
pub enum ModeKind {
Plan,
#[default]
#[serde(
alias = "code",
alias = "pair_programming",
alias = "execute",
alias = "custom"
)]
Default,
#[doc(hidden)]
#[serde(skip_serializing, skip_deserializing)]
#[schemars(skip)]
#[ts(skip)]
PairProgramming,
#[doc(hidden)]
#[serde(skip_serializing, skip_deserializing)]
#[schemars(skip)]
#[ts(skip)]
Execute,
}
pub const TUI_VISIBLE_COLLABORATION_MODES: [ModeKind; 2] = [ModeKind::Default, ModeKind::Plan];
impl ModeKind {
pub const fn display_name(self) -> &'static str {
match self {
Self::Plan => "Plan",
Self::Default => "Default",
Self::PairProgramming => "Pair Programming",
Self::Execute => "Execute",
}
}
pub const fn is_tui_visible(self) -> bool {
matches!(self, Self::Plan | Self::Default)
}
pub const fn allows_request_user_input(self) -> bool {
matches!(self, Self::Plan)
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, JsonSchema, TS)]
#[serde(rename_all = "lowercase")]
pub struct CollaborationMode {
pub mode: ModeKind,
pub settings: Settings,
}
impl CollaborationMode {
fn settings_ref(&self) -> &Settings {
&self.settings
}
pub fn model(&self) -> &str {
self.settings_ref().model.as_str()
}
pub fn reasoning_effort(&self) -> Option<ReasoningEffort> {
self.settings_ref().reasoning_effort
}
pub fn with_updates(
&self,
model: Option<String>,
effort: Option<Option<ReasoningEffort>>,
developer_instructions: Option<Option<String>>,
) -> Self {
let settings = self.settings_ref();
let updated_settings = Settings {
model: model.unwrap_or_else(|| settings.model.clone()),
reasoning_effort: effort.unwrap_or(settings.reasoning_effort),
developer_instructions: developer_instructions
.unwrap_or_else(|| settings.developer_instructions.clone()),
};
CollaborationMode {
mode: self.mode,
settings: updated_settings,
}
}
pub fn apply_mask(&self, mask: &CollaborationModeMask) -> Self {
let settings = self.settings_ref();
CollaborationMode {
mode: mask.mode.unwrap_or(self.mode),
settings: Settings {
model: mask.model.clone().unwrap_or_else(|| settings.model.clone()),
reasoning_effort: mask.reasoning_effort.unwrap_or(settings.reasoning_effort),
developer_instructions: mask
.developer_instructions
.clone()
.unwrap_or_else(|| settings.developer_instructions.clone()),
},
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, JsonSchema, TS)]
pub struct Settings {
pub model: String,
pub reasoning_effort: Option<ReasoningEffort>,
pub developer_instructions: Option<String>,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, JsonSchema, TS)]
pub struct CollaborationModeMask {
pub name: String,
pub mode: Option<ModeKind>,
pub model: Option<String>,
pub reasoning_effort: Option<Option<ReasoningEffort>>,
pub developer_instructions: Option<Option<String>>,
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn apply_mask_can_clear_optional_fields() {
let mode = CollaborationMode {
mode: ModeKind::Default,
settings: Settings {
model: "gpt-5.2-codex".to_string(),
reasoning_effort: Some(ReasoningEffort::High),
developer_instructions: Some("stay focused".to_string()),
},
};
let mask = CollaborationModeMask {
name: "Clear".to_string(),
mode: None,
model: None,
reasoning_effort: Some(None),
developer_instructions: Some(None),
};
let expected = CollaborationMode {
mode: ModeKind::Default,
settings: Settings {
model: "gpt-5.2-codex".to_string(),
reasoning_effort: None,
developer_instructions: None,
},
};
assert_eq!(expected, mode.apply_mask(&mask));
}
#[test]
fn mode_kind_deserializes_alias_values_to_default() {
for alias in ["code", "pair_programming", "execute", "custom"] {
let json = format!("\"{alias}\"");
let mode: ModeKind = serde_json::from_str(&json).expect("deserialize mode");
assert_eq!(ModeKind::Default, mode);
}
}
#[test]
fn tui_visible_collaboration_modes_match_mode_kind_visibility() {
let expected = [ModeKind::Default, ModeKind::Plan];
assert_eq!(expected, TUI_VISIBLE_COLLABORATION_MODES);
for mode in TUI_VISIBLE_COLLABORATION_MODES {
assert!(mode.is_tui_visible());
}
assert!(!ModeKind::PairProgramming.is_tui_visible());
assert!(!ModeKind::Execute.is_tui_visible());
}
}