codeswarm_adapters/
policy.rs1use crate::Mode;
4
5pub const DEFAULT_POLICY_ID: &str = "codeswarm:mode:full-access";
6
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct ModePolicy {
9 pub id: &'static str,
10 pub name: &'static str,
11 pub description: &'static str,
12 aliases: &'static [&'static str],
13}
14
15impl ModePolicy {
16 pub fn resolve(&self, modes: &[Mode]) -> Option<Mode> {
17 modes.iter().find_map(|mode| {
18 let id = normalize(&mode.id);
19 let label = normalize(&mode.label);
20 self.aliases
21 .iter()
22 .any(|alias| *alias == id || *alias == label)
23 .then(|| mode.clone())
24 })
25 }
26
27 pub fn display_mode(&self) -> Mode {
28 Mode {
29 id: self.id.into(),
30 label: self.name.into(),
31 }
32 }
33}
34
35pub const POLICIES: &[ModePolicy] = &[
36 ModePolicy {
37 id: "codeswarm:mode:plan",
38 name: "Plan",
39 description: "Read-only planning with no tool execution",
40 aliases: &["plan", "planmode", "readonly"],
41 },
42 ModePolicy {
43 id: "codeswarm:mode:manual",
44 name: "Manual",
45 description: "Ask before operations that require permission",
46 aliases: &["default", "manual", "ask", "agymanual"],
47 },
48 ModePolicy {
49 id: "codeswarm:mode:accept-edits",
50 name: "Accept Edits",
51 description: "Automatically approve file edits, but keep safeguards",
52 aliases: &["acceptedits", "autoedit", "autoapproveedits"],
53 },
54 ModePolicy {
55 id: DEFAULT_POLICY_ID,
56 name: "Auto pilot",
57 description: "Automatically approve tools and bypass prompts",
58 aliases: &[
59 "fullaccess",
60 "yolo",
61 "bypasspermissions",
62 "skippermissions",
63 "codeswarmstartupfullaccess",
64 "agyfullaccess",
65 ],
66 },
67];
68
69pub fn resolve(policy_id: &str, modes: &[Mode]) -> Option<Mode> {
70 POLICIES
71 .iter()
72 .find(|policy| policy.id == policy_id)
73 .and_then(|policy| policy.resolve(modes))
74}
75
76pub fn shared_modes(mode_sets: &[Vec<Mode>]) -> Vec<Mode> {
79 if mode_sets.is_empty() {
80 return Vec::new();
81 }
82 POLICIES
83 .iter()
84 .filter(|policy| {
85 mode_sets
86 .iter()
87 .all(|modes| policy.resolve(modes).is_some())
88 })
89 .map(ModePolicy::display_mode)
90 .collect()
91}
92
93pub fn shared_current_mode(states: &[(Vec<Mode>, Option<String>)]) -> Option<Mode> {
94 POLICIES.iter().find_map(|policy| {
95 states
96 .iter()
97 .all(|(modes, current)| {
98 current.as_ref().is_some_and(|current| {
99 policy
100 .resolve(modes)
101 .is_some_and(|native| native.id == *current)
102 })
103 })
104 .then(|| policy.display_mode())
105 })
106}
107
108fn normalize(value: &str) -> String {
109 value
110 .chars()
111 .filter(char::is_ascii_alphanumeric)
112 .flat_map(char::to_lowercase)
113 .collect()
114}
115
116#[cfg(test)]
117mod tests {
118 use crate::Mode;
119
120 use super::{DEFAULT_POLICY_ID, shared_current_mode, shared_modes};
121
122 #[test]
123 fn maps_different_native_names_to_one_policy() {
124 let sets = vec![
125 vec![Mode {
126 id: "yolo".into(),
127 label: "YOLO".into(),
128 }],
129 vec![Mode {
130 id: "full-access".into(),
131 label: "Auto pilot".into(),
132 }],
133 ];
134 let modes = shared_modes(&sets);
135 assert!(modes.iter().any(|mode| mode.id == DEFAULT_POLICY_ID));
136 }
137
138 #[test]
139 fn maps_codex_full_access_catalog_to_auto_pilot() {
140 let modes = vec![
141 Mode {
142 id: "read-only".into(),
143 label: "Ask for approval".into(),
144 },
145 Mode {
146 id: "agent".into(),
147 label: "Approve for me".into(),
148 },
149 Mode {
150 id: "agent-full-access".into(),
151 label: "Full access".into(),
152 },
153 ];
154 assert_eq!(
155 super::resolve(DEFAULT_POLICY_ID, &modes).map(|mode| mode.id),
156 Some("agent-full-access".into())
157 );
158 }
159
160 #[test]
161 fn mixed_native_state_is_not_a_user_visible_mode() {
162 let states = vec![
163 (
164 vec![Mode {
165 id: "plan".into(),
166 label: "Plan".into(),
167 }],
168 Some("plan".into()),
169 ),
170 (
171 vec![Mode {
172 id: "default".into(),
173 label: "Manual".into(),
174 }],
175 Some("default".into()),
176 ),
177 ];
178 assert_eq!(shared_current_mode(&states), None);
179 }
180}