From 48664bf514b977631f861811326d162860763224 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Tue, 16 Sep 2025 11:42:57 -0500 Subject: [PATCH] wip - disable AI --- 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(-) diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 6c1449b728d3ee5b8c8b019d5e527e9adfb3bf25..ab0d69b6d9f7da254e659a0d5e5d0cfee0322711 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/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::{ diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index db19aed6135f9de13e83fe07cbbf3c7186c0c603..fde9087e3480256fd0afe7f698fa2317e086b920 100644 --- a/crates/project/src/project.rs +++ b/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, -} - impl settings::Settings for DisableAiSettings { - type FileContent = DisableAiSettingContent; - - fn load(sources: SettingsSources, _: &mut App) -> Result { - // 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) -> DisableAiSettingContent { - DisableAiSettingContent { disable_ai: value } - } cx.update(|cx| { + let mut store = SettingsStore::new(cx, &settings::test_settings()); + store.register_setting::(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" ); }); diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 8acbf49361bd7669732e35faad84f16ec99a7431..ca1e20a30aa8373e84cf618ad03d1966e8a7865c 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -154,6 +154,8 @@ pub struct ProjectSettingsContent { #[serde(flatten)] pub worktree: WorktreeSettingsContent, + + pub disable_ai: Option, } #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)]