wip - disable AI

Ben Kunkle created

Change summary

crates/project/src/debugger/dap_store.rs |   3 
crates/project/src/project.rs            | 235 ++++++++-----------------
crates/settings/src/settings_content.rs  |   2 
3 files changed, 85 insertions(+), 155 deletions(-)

Detailed changes

crates/project/src/debugger/dap_store.rs 🔗

@@ -28,8 +28,9 @@ use futures::{
 };
 use gpui::{App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task};
 use http_client::HttpClient;
-use language::{Buffer, LanguageToolchainStore, language_settings::InlayHintKind};
+use language::{Buffer, LanguageToolchainStore};
 use node_runtime::NodeRuntime;
+use settings::InlayHintKind;
 
 use remote::RemoteClient;
 use rpc::{

crates/project/src/project.rs 🔗

@@ -980,46 +980,22 @@ pub struct DisableAiSettings {
     pub disable_ai: bool,
 }
 
-#[derive(
-    Copy,
-    Clone,
-    PartialEq,
-    Eq,
-    Debug,
-    Default,
-    serde::Serialize,
-    serde::Deserialize,
-    SettingsUi,
-    SettingsKey,
-    JsonSchema,
-)]
-#[settings_key(None)]
-pub struct DisableAiSettingContent {
-    pub disable_ai: Option<bool>,
-}
-
 impl settings::Settings for DisableAiSettings {
-    type FileContent = DisableAiSettingContent;
-
-    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
-        // For security reasons, settings can only make AI restrictions MORE strict, not less.
-        // (For example, if someone is working on a project that contractually
-        // requires no AI use, that should override the user's setting which
-        // permits AI use.)
-        // This also prevents an attacker from using project or server settings to enable AI when it should be disabled.
-        let disable_ai = sources
-            .project
-            .iter()
-            .chain(sources.user.iter())
-            .chain(sources.server.iter())
-            .chain(sources.release_channel.iter())
-            .chain(sources.profile.iter())
-            .any(|disabled| disabled.disable_ai == Some(true));
+    fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
+        Self {
+            disable_ai: content.project.disable_ai.unwrap(),
+        }
+    }
 
-        Ok(Self { disable_ai })
+    fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
+        self.disable_ai = self.disable_ai || content.project.disable_ai.unwrap_or(false);
     }
 
-    fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
+    fn import_from_vscode(
+        _vscode: &settings::VsCodeSettings,
+        _current: &mut settings::SettingsContent,
+    ) {
+    }
 }
 
 impl Project {
@@ -5668,145 +5644,96 @@ fn provide_inline_values(
 mod disable_ai_settings_tests {
     use super::*;
     use gpui::TestAppContext;
-    use settings::{Settings, SettingsSources};
+    use rpc::proto::cancel_language_server_work::Work;
+    use settings::Settings;
 
     #[gpui::test]
     async fn test_disable_ai_settings_security(cx: &mut TestAppContext) {
-        fn disable_setting(value: Option<bool>) -> DisableAiSettingContent {
-            DisableAiSettingContent { disable_ai: value }
-        }
         cx.update(|cx| {
+            let mut store = SettingsStore::new(cx, &settings::test_settings());
+            store.register_setting::<DisableAiSettings>(cx);
+            let ai_disabled = |cx| DisableAiSettings::get_global(cx).disable_ai;
             // Test 1: Default is false (AI enabled)
-            let sources = SettingsSources {
-                default: &DisableAiSettingContent {
-                    disable_ai: Some(false),
-                },
-                global: None,
-                extensions: None,
-                user: None,
-                release_channel: None,
-                operating_system: None,
-                profile: None,
-                server: None,
-                project: &[],
-            };
-            let settings = DisableAiSettings::load(sources, cx).unwrap();
-            assert!(!settings.disable_ai, "Default should allow AI");
+            assert!(!ai_disabled(cx), "Default should allow AI");
+
+            let disable_true = serde_json::json!({
+                "disable_ai": true
+            })
+            .to_string();
+            let disable_false = serde_json::json!({
+                "disable_ai": false
+            })
+            .to_string();
 
             // Test 2: Global true, local false -> still disabled (local cannot re-enable)
-            let global_true = disable_setting(Some(true));
-            let local_false = disable_setting(Some(false));
-            let sources = SettingsSources {
-                default: &disable_setting(Some(false)),
-                global: None,
-                extensions: None,
-                user: Some(&global_true),
-                release_channel: None,
-                operating_system: None,
-                profile: None,
-                server: None,
-                project: &[&local_false],
-            };
-            let settings = DisableAiSettings::load(sources, cx).unwrap();
-            assert!(
-                settings.disable_ai,
-                "Local false cannot override global true"
-            );
+            store.set_user_settings(&disable_false, cx);
+            assert!(ai_disabled(cx), "Local false cannot override global true");
 
             // Test 3: Global false, local true -> disabled (local can make more restrictive)
-            let global_false = disable_setting(Some(false));
-            let local_true = disable_setting(Some(true));
-            let sources = SettingsSources {
-                default: &disable_setting(Some(false)),
-                global: None,
-                extensions: None,
-                user: Some(&global_false),
-                release_channel: None,
-                operating_system: None,
-                profile: None,
-                server: None,
-                project: &[&local_true],
-            };
-            let settings = DisableAiSettings::load(sources, cx).unwrap();
-            assert!(settings.disable_ai, "Local true can override global false");
+            store.set_user_settings(&disable_false, cx);
+            store.set_local_settings(
+                WorktreeId::from_usize(0),
+                Path::new("project.json").into(),
+                settings::LocalSettingsKind::Settings,
+                Some(&disable_true),
+                cx,
+            );
+            assert!(ai_disabled(cx), "Local true can override global false");
+            store.clear_local_settings(WorktreeId::from_usize(0), cx);
 
             // Test 4: Server can only make more restrictive (set to true)
-            let user_false = disable_setting(Some(false));
-            let server_true = disable_setting(Some(true));
-            let sources = SettingsSources {
-                default: &disable_setting(Some(false)),
-                global: None,
-                extensions: None,
-                user: Some(&user_false),
-                release_channel: None,
-                operating_system: None,
-                profile: None,
-                server: Some(&server_true),
-                project: &[],
-            };
-            let settings = DisableAiSettings::load(sources, cx).unwrap();
+            store.set_user_settings(&disable_false, cx);
+            store.set_server_settings(&disable_true, cx);
             assert!(
-                settings.disable_ai,
+                ai_disabled(cx),
                 "Server can set to true even if user is false"
             );
 
             // Test 5: Server false cannot override user true
-            let user_true = disable_setting(Some(true));
-            let server_false = disable_setting(Some(false));
-            let sources = SettingsSources {
-                default: &disable_setting(Some(false)),
-                global: None,
-                extensions: None,
-                user: Some(&user_true),
-                release_channel: None,
-                operating_system: None,
-                profile: None,
-                server: Some(&server_false),
-                project: &[],
-            };
-            let settings = DisableAiSettings::load(sources, cx).unwrap();
-            assert!(
-                settings.disable_ai,
-                "Server false cannot override user true"
-            );
+            store.set_server_settings(&disable_false, cx);
+            store.set_user_settings(&disable_true, cx);
+            assert!(ai_disabled(cx), "Server false cannot override user true");
 
             // Test 6: Multiple local settings, any true disables AI
-            let global_false = disable_setting(Some(false));
-            let local_false3 = disable_setting(Some(false));
-            let local_true2 = disable_setting(Some(true));
-            let local_false4 = disable_setting(Some(false));
-            let sources = SettingsSources {
-                default: &disable_setting(Some(false)),
-                global: None,
-                extensions: None,
-                user: Some(&global_false),
-                release_channel: None,
-                operating_system: None,
-                profile: None,
-                server: None,
-                project: &[&local_false3, &local_true2, &local_false4],
-            };
-            let settings = DisableAiSettings::load(sources, cx).unwrap();
-            assert!(settings.disable_ai, "Any local true should disable AI");
+            store.set_local_settings(
+                WorktreeId::from_usize(0),
+                Path::new("a").into(),
+                settings::LocalSettingsKind::Settings,
+                Some(&disable_false),
+                cx,
+            );
+            store.set_local_settings(
+                WorktreeId::from_usize(1),
+                Path::new("b").into(),
+                settings::LocalSettingsKind::Settings,
+                Some(&disable_true),
+                cx,
+            );
+            store.set_local_settings(
+                WorktreeId::from_usize(2),
+                Path::new("c").into(),
+                settings::LocalSettingsKind::Settings,
+                Some(&disable_false),
+                cx,
+            );
+            store.set_user_settings(&disable_false, cx);
+            assert!(ai_disabled(cx), "Any local true should disable AI");
+            store.clear_local_settings(WorktreeId::from_usize(0), cx);
+            store.clear_local_settings(WorktreeId::from_usize(1), cx);
+            store.clear_local_settings(WorktreeId::from_usize(2), cx);
 
             // Test 7: All three sources can independently disable AI
-            let user_false2 = disable_setting(Some(false));
-            let server_false2 = disable_setting(Some(false));
-            let local_true3 = disable_setting(Some(true));
-            let sources = SettingsSources {
-                default: &disable_setting(Some(false)),
-                global: None,
-                extensions: None,
-                user: Some(&user_false2),
-                release_channel: None,
-                operating_system: None,
-                profile: None,
-                server: Some(&server_false2),
-                project: &[&local_true3],
-            };
-            let settings = DisableAiSettings::load(sources, cx).unwrap();
+            store.set_server_settings(&disable_false, cx);
+            store.set_user_settings(&disable_false, cx);
+            store.set_local_settings(
+                WorktreeId::from_usize(0),
+                Path::new("a").into(),
+                settings::LocalSettingsKind::Settings,
+                Some(&disable_true),
+                cx,
+            );
             assert!(
-                settings.disable_ai,
+                ai_disabled(cx),
                 "Local can disable even if user and server are false"
             );
         });

crates/settings/src/settings_content.rs 🔗

@@ -154,6 +154,8 @@ pub struct ProjectSettingsContent {
 
     #[serde(flatten)]
     pub worktree: WorktreeSettingsContent,
+
+    pub disable_ai: Option<bool>,
 }
 
 #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)]