diff --git a/app/src/ai/generate_code_review_content/mod.rs b/app/src/ai/generate_code_review_content/mod.rs index e625a5c62..65635341c 100644 --- a/app/src/ai/generate_code_review_content/mod.rs +++ b/app/src/ai/generate_code_review_content/mod.rs @@ -1,8 +1 @@ -// TODO(edward): follow-up — gate callers of this module on both -// 1. an `AISettings` opt-out (mirror `is_shared_block_title_generation_enabled`), and -// 2. a customer-type guard (exclude Enterprise unless Warp plan / dogfood), -// matching the pattern in `terminal/share_block_modal.rs::should_send_title_gen_request`. -// `FeatureFlag::GitOperationsInCodeReview` already gates the surrounding UI, -// but does not address AI-specific privacy / opt-out concerns for sending -// diffs to an LLM. pub(crate) mod api; diff --git a/app/src/code_review/git_dialog/mod.rs b/app/src/code_review/git_dialog/mod.rs index 8f3ef3063..4937cb282 100644 --- a/app/src/code_review/git_dialog/mod.rs +++ b/app/src/code_review/git_dialog/mod.rs @@ -134,8 +134,7 @@ fn show_toast(msg: impl Into, ctx: &mut ViewContext) { /// /// Folds the parent feature flag, the user's dedicated per-feature AI toggle /// (which itself requires active AI / auth / remote-session org policy to -/// allow AI), and an enterprise check with the same Warp-plan exception and -/// dogfood override as `share_block_modal.rs::should_send_title_gen_request`. +/// allow AI), and the current team's Git Operations AI tier policy. /// /// When this returns `false`, call sites skip AI entirely: commit.rs opens /// with the manual-type placeholder and pr.rs goes straight to @@ -143,7 +142,7 @@ fn show_toast(msg: impl Into, ctx: &mut ViewContext) { fn should_send_git_ops_ai_request(app: &AppContext) -> bool { FeatureFlag::GitOperationsInCodeReview.is_enabled() && AISettings::as_ref(app).is_git_operations_autogen_enabled(app) - && UserWorkspaces::as_ref(app).ai_allowed_for_current_team() + && UserWorkspaces::as_ref(app).is_git_operations_ai_enabled() } /// Maps a raw git error string to a user-friendly toast message. Known diff --git a/app/src/settings_view/ai_page.rs b/app/src/settings_view/ai_page.rs index 1c9103d87..59e3b4478 100644 --- a/app/src/settings_view/ai_page.rs +++ b/app/src/settings_view/ai_page.rs @@ -2454,6 +2454,9 @@ impl TypedActionView for AISettingsPageView { ctx.notify(); } AISettingsPageAction::ToggleGitOperationsAutogen => { + if !UserWorkspaces::as_ref(ctx).is_git_operations_ai_enabled() { + return; + } match AISettings::handle(ctx).update(ctx, |settings, ctx| { settings .git_operations_autogen_enabled_internal @@ -3726,7 +3729,7 @@ impl ActiveAIWidget { && AISettings::as_ref(app) .git_operations_autogen_enabled_internal .is_supported_on_current_platform() - && UserWorkspaces::as_ref(app).ai_allowed_for_current_team() + && UserWorkspaces::as_ref(app).is_git_operations_ai_enabled() } fn render_next_command_section( diff --git a/app/src/workspaces/gql_convert.rs b/app/src/workspaces/gql_convert.rs index d960b8c59..b2044f555 100644 --- a/app/src/workspaces/gql_convert.rs +++ b/app/src/workspaces/gql_convert.rs @@ -174,6 +174,7 @@ impl From for WarpAiPolicy { is_code_suggestions_toggleable: gql_warp_ai_policy.is_code_suggestions_toggleable, is_prompt_suggestions_toggleable: gql_warp_ai_policy.is_prompt_suggestions_toggleable, is_next_command_enabled: gql_warp_ai_policy.is_next_command_enabled, + is_git_operations_ai_enabled: gql_warp_ai_policy.is_git_operations_ai_enabled, is_voice_enabled: gql_warp_ai_policy.is_voice_enabled, } } diff --git a/app/src/workspaces/user_workspaces.rs b/app/src/workspaces/user_workspaces.rs index 1cc36566f..8f40c69c3 100644 --- a/app/src/workspaces/user_workspaces.rs +++ b/app/src/workspaces/user_workspaces.rs @@ -387,7 +387,7 @@ impl UserWorkspaces { /// Returns `true` if active AI is allowed for the current workspace, based on billing config. /// /// In the future, we should store active AI enablement on the policy directly. For now, we - /// proxy whether active AI by checking if prompt suggestions, next command, or code suggestions are enabled. + /// proxy whether active AI by checking whether any active AI feature is enabled. pub fn is_active_ai_allowed(&self) -> bool { self.current_team().is_none_or(|team| { team.billing_metadata @@ -397,6 +397,7 @@ impl UserWorkspaces { policy.is_prompt_suggestions_toggleable || policy.is_next_command_enabled || policy.is_code_suggestions_toggleable + || policy.is_git_operations_ai_enabled }) }) } @@ -454,6 +455,19 @@ impl UserWorkspaces { }) } + /// Whether Git Operations AI is enabled for the current user, based on the active policies. + /// Note that the value may be incorrect if called before the team's billing metadata has been fetched. + pub fn is_git_operations_ai_enabled(&self) -> bool { + self.current_team() + // If the user has no team, they can toggle Git Operations AI (no restrictions). + .is_none_or(|team| { + team.billing_metadata + .tier + .warp_ai_policy + .is_some_and(|policy| policy.is_git_operations_ai_enabled) + }) + } + /// Whether voice input should be toggleable for the current user, based on the active policies. /// Note that the value may be incorrect if called before the team's billing metadata has been fetched. /// If voice input support is not compiled into this build, always returns `false`. diff --git a/app/src/workspaces/workspace.rs b/app/src/workspaces/workspace.rs index cab4f7600..94084f3e3 100644 --- a/app/src/workspaces/workspace.rs +++ b/app/src/workspaces/workspace.rs @@ -292,6 +292,8 @@ pub struct WarpAiPolicy { pub is_code_suggestions_toggleable: bool, pub is_prompt_suggestions_toggleable: bool, pub is_next_command_enabled: bool, + #[serde(default)] + pub is_git_operations_ai_enabled: bool, pub is_voice_enabled: bool, } #[derive(Clone, Debug, Copy, Serialize, Deserialize)] diff --git a/crates/graphql/src/api/billing.rs b/crates/graphql/src/api/billing.rs index 2e562603a..b0be284a4 100644 --- a/crates/graphql/src/api/billing.rs +++ b/crates/graphql/src/api/billing.rs @@ -147,6 +147,7 @@ pub struct WarpAiPolicy { pub is_code_suggestions_toggleable: bool, pub is_prompt_suggestions_toggleable: bool, pub is_next_command_enabled: bool, + pub is_git_operations_ai_enabled: bool, pub is_voice_enabled: bool, } diff --git a/crates/graphql/src/api/mutations/create_team.rs b/crates/graphql/src/api/mutations/create_team.rs index 18852bbfd..a284c368d 100644 --- a/crates/graphql/src/api/mutations/create_team.rs +++ b/crates/graphql/src/api/mutations/create_team.rs @@ -35,6 +35,7 @@ mutation CreateTeam($input: CreateTeamInput!, $request_context: RequestContext!) isCodeSuggestionsToggleable isPromptSuggestionsToggleable isNextCommandEnabled + isGitOperationsAiEnabled isVoiceEnabled } teamSizePolicy { diff --git a/crates/graphql/src/api/queries/get_workspaces_metadata_for_user.rs b/crates/graphql/src/api/queries/get_workspaces_metadata_for_user.rs index 6cabf54db..973855b31 100644 --- a/crates/graphql/src/api/queries/get_workspaces_metadata_for_user.rs +++ b/crates/graphql/src/api/queries/get_workspaces_metadata_for_user.rs @@ -36,6 +36,7 @@ query GetWorkspacesMetadataForUser($requestContext: RequestContext!) { isCodeSuggestionsToggleable isPromptSuggestionsToggleable isNextCommandEnabled + isGitOperationsAiEnabled isVoiceEnabled } teamSizePolicy { diff --git a/crates/warp_graphql_schema/api/schema.graphql b/crates/warp_graphql_schema/api/schema.graphql index 2d371a657..529769469 100644 --- a/crates/warp_graphql_schema/api/schema.graphql +++ b/crates/warp_graphql_schema/api/schema.graphql @@ -3917,6 +3917,7 @@ type WarpAiPolicy { acceptedAutosuggestionsLimit: Int! disablePremiumModels: Boolean! isCodeSuggestionsToggleable: Boolean! + isGitOperationsAiEnabled: Boolean! isNextCommandEnabled: Boolean! isPromptSuggestionsToggleable: Boolean! isUnlimited: Boolean!