Claude Fable cannot be offered with Zero Data Retention; Anthropic retains inference logs for 30 days. This adds: - A telemetry.anthropic_retention setting (default false), surfaced in the settings UI Privacy section. - A requires_data_retention() query on the LanguageModel trait, true for cloud models with the claude-fable-5 id prefix. - A hard gate in CloudLanguageModel::stream_completion that raises a typed DataRetentionConsentRequiredError when consent is missing, which is non-retryable. - An agent panel callout for that error with Switch to Opus 4.8 / Accept actions; both resume the failed turn after acting, so the user's message continues without retyping.
Unified · Split
| @@ -1471,6 +1471,9 @@ · 1470 unmodified lines | ||
| 1471 | 1471 | "diagnostics": true, |
| 1472 | 1472 | // Send anonymized usage data like what languages you're using Zed with. |
| 1473 | 1473 | "metrics": true, |
| 1474 | + // Allow sending requests to Anthropic models that cannot be offered with | |
| 1475 | + // Zero Data Retention | |
| 1476 | + "anthropic_retention": false, | |
| 1474 | 1477 | }, |
| 1475 | 1478 | // Whether to disable all AI features in Zed. |
| 1476 | 1479 | // |
| @@ -3976,6 +3976,9 @@ impl Thread { · 3975 unmodified lines | ||
| 3976 | 3976 | // Retrying won't help for Payment Required errors. |
| 3977 | 3977 | None |
| 3978 | 3978 | } |
| 3979 | + // Retrying won't help until the user consents to data retention | |
| 3980 | + // or switches models. | |
| 3981 | + DataRetentionConsentRequired { .. } => None, | |
| 3979 | 3982 | // Conservatively assume that any other errors are non-retryable |
| 3980 | 3983 | HttpResponseError { .. } | Other(..) => Some(RetryStrategy::Fixed { |
| 3981 | 3984 | delay: BASE_RETRY_DELAY, |
| @@ -123,6 +123,7 @@ enum ThreadFeedback { · 122 unmodified lines | ||
| 123 | 123 | #[derive(Debug)] |
| 124 | 124 | pub(crate) enum ThreadError { |
| 125 | 125 | PaymentRequired, |
| 126 | + DataRetentionConsentRequired, | |
| 126 | 127 | Refusal, |
| 127 | 128 | AuthenticationRequired(SharedString), |
| 128 | 129 | RateLimitExceeded { |
| @@ -196,6 +197,7 @@ impl From<anyhow::Error> for ThreadError { · 67 unmodified lines | ||
| 196 | 197 | provider: provider.to_string().into(), |
| 197 | 198 | }, |
| 198 | 199 | UpstreamProviderError { .. } => Self::RequestFailed, |
| 200 | + DataRetentionConsentRequired { .. } => Self::DataRetentionConsentRequired, | |
| 199 | 201 | BadRequestFormat { provider, .. } |
| 200 | 202 | | HttpResponseError { provider, .. } |
| 201 | 203 | | ApiEndpointNotFound { provider } => Self::ApiError { |
| @@ -3443,6 +3445,21 @@ pub(crate) mod tests { · 3241 unmodified lines | ||
| 3443 | 3445 | |
| 3444 | 3446 | use super::*; |
| 3445 | 3447 | |
| 3448 | + #[test] | |
| 3449 | + fn test_data_retention_error_maps_from_provider_error() { | |
| 3450 | + // The agent wraps the provider error in a fresh `anyhow::Error`, so | |
| 3451 | + // the mapping must downcast to `LanguageModelCompletionError` rather | |
| 3452 | + // than matching on the anyhow error directly. | |
| 3453 | + let provider_error = LanguageModelCompletionError::DataRetentionConsentRequired { | |
| 3454 | + model_name: "Claude Fable 5".to_string(), | |
| 3455 | + }; | |
| 3456 | + let error = ThreadError::from(anyhow!(provider_error)); | |
| 3457 | + assert!( | |
| 3458 | + matches!(error, ThreadError::DataRetentionConsentRequired), | |
| 3459 | + "expected ThreadError::DataRetentionConsentRequired, got: {error:?}" | |
| 3460 | + ); | |
| 3461 | + } | |
| 3462 | + | |
| 3446 | 3463 | #[gpui::test] |
| 3447 | 3464 | async fn test_drop(cx: &mut TestAppContext) { |
| 3448 | 3465 | init_test(cx); |
| @@ -23,10 +23,10 @@ use gpui::Stateful; · 22 unmodified lines | ||
| 23 | 23 | use gpui::TaskExt; |
| 24 | 24 | use heapless::Vec as ArrayVec; |
| 25 | 25 | use language_model::{ |
| 26 | − FastModeConfirmation, LanguageModelEffortLevel, LanguageModelId, LanguageModelProviderId, | |
| 27 | − LanguageModelRegistry, Speed, | |
| 26 | + FastModeConfirmation, LanguageModel, LanguageModelEffortLevel, LanguageModelId, | |
| 27 | + LanguageModelProviderId, LanguageModelRegistry, Speed, | |
| 28 | 28 | }; |
| 29 | −use settings::update_settings_file; | |
| 29 | +use settings::{update_settings_file, update_settings_file_with_completion}; | |
| 30 | 30 | use ui::{ |
| 31 | 31 | ButtonLike, CalloutBorderPosition, SpinnerLabel, SpinnerVariant, SplitButton, SplitButtonStyle, |
| 32 | 32 | Tab, |
| @@ -36,6 +36,9 @@ use workspace::{OpenOptions, SERIALIZATION_THROTTLE_TIME}; · 3 unmodified lines | ||
| 36 | 36 | |
| 37 | 37 | use super::*; |
| 38 | 38 | |
| 39 | +// TODO: Replace with the final link explaining Anthropic's data retention policy. | |
| 40 | +const DATA_RETENTION_LEARN_MORE_URL: &str = "#link-tbd"; | |
| 41 | + | |
| 39 | 42 | #[derive(Default)] |
| 40 | 43 | struct ThreadFeedbackState { |
| 41 | 44 | feedback: Option<ThreadFeedback>, |
| @@ -1714,6 +1717,13 @@ impl ThreadView { · 1672 unmodified lines | ||
| 1714 | 1717 | ); |
| 1715 | 1718 | ("refusal", None, message.into()) |
| 1716 | 1719 | } |
| 1720 | + ThreadError::DataRetentionConsentRequired => { | |
| 1721 | + let message = format!( | |
| 1722 | + "{} is not available with Zero Data Retention.", | |
| 1723 | + self.current_model_name(cx) | |
| 1724 | + ); | |
| 1725 | + ("data_retention_consent_required", None, message.into()) | |
| 1726 | + } | |
| 1717 | 1727 | ThreadError::AuthenticationRequired(message) => { |
| 1718 | 1728 | ("authentication_required", None, message.clone()) |
| 1719 | 1729 | } |
| @@ -9348,6 +9358,9 @@ impl ThreadView { · 7628 unmodified lines | ||
| 9348 | 9358 | self.render_any_thread_error(message.clone(), window, cx) |
| 9349 | 9359 | } |
| 9350 | 9360 | ThreadError::Refusal => self.render_refusal_error(cx), |
| 9361 | + ThreadError::DataRetentionConsentRequired => { | |
| 9362 | + self.render_data_retention_consent_error(cx) | |
| 9363 | + } | |
| 9351 | 9364 | ThreadError::AuthenticationRequired(error) => { |
| 9352 | 9365 | self.render_authentication_required_error(error.clone(), cx) |
| 9353 | 9366 | } |
| @@ -10119,6 +10132,118 @@ impl ThreadView { · 765 unmodified lines | ||
| 10119 | 10132 | ) |
| 10120 | 10133 | } |
| 10121 | 10134 | |
| 10135 | + /// Returns the model to offer as a downgrade target when the current model | |
| 10136 | + /// requires data retention consent (e.g. Opus 4.8 for Fable). | |
| 10137 | + fn data_retention_fallback_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> { | |
| 10138 | + let thread = self.as_native_thread(cx)?; | |
| 10139 | + let model = thread.read(cx).model()?.clone(); | |
| 10140 | + let fallback_id = model.refusal_fallback_model_id()?; | |
| 10141 | + LanguageModelRegistry::read_global(cx) | |
| 10142 | + .available_models(cx) | |
| 10143 | + .find(|fallback| { | |
| 10144 | + fallback.provider_id() == model.provider_id() | |
| 10145 | + && fallback.id().0.as_ref() == fallback_id | |
| 10146 | + }) | |
| 10147 | + } | |
| 10148 | + | |
| 10149 | + fn render_data_retention_consent_error(&self, cx: &mut Context<Self>) -> Callout { | |
| 10150 | + let fallback_model = self.data_retention_fallback_model(cx); | |
| 10151 | + | |
| 10152 | + Callout::new() | |
| 10153 | + .severity(Severity::Warning) | |
| 10154 | + .icon(IconName::Warning) | |
| 10155 | + .title(format!( | |
| 10156 | + "Note: {} cannot be offered with Zero Data Retention.", | |
| 10157 | + self.current_model_name(cx) | |
| 10158 | + )) | |
| 10159 | + .description_slot( | |
| 10160 | + h_flex() | |
| 10161 | + .gap_1() | |
| 10162 | + .child( | |
| 10163 | + Label::new("Anthropic will retain inference logs.") | |
| 10164 | + .size(LabelSize::Small) | |
| 10165 | + .color(Color::Muted), | |
| 10166 | + ) | |
| 10167 | + .child( | |
| 10168 | + Button::new("data-retention-learn-more", "Learn More") | |
| 10169 | + .label_size(LabelSize::Small) | |
| 10170 | + .on_click(|_, _, cx| { | |
| 10171 | + cx.open_url(DATA_RETENTION_LEARN_MORE_URL); | |
| 10172 | + }), | |
| 10173 | + ), | |
| 10174 | + ) | |
| 10175 | + .actions_slot( | |
| 10176 | + h_flex() | |
| 10177 | + .gap_0p5() | |
| 10178 | + .when_some(fallback_model, |this, fallback| { | |
| 10179 | + this.child( | |
| 10180 | + Button::new( | |
| 10181 | + "switch-data-retention-fallback", | |
| 10182 | + format!("Switch to {}", fallback.name().0), | |
| 10183 | + ) | |
| 10184 | + .label_size(LabelSize::Small) | |
| 10185 | + .on_click(cx.listener(|this, _, _, cx| { | |
| 10186 | + this.switch_to_data_retention_fallback_and_resend(cx); | |
| 10187 | + })), | |
| 10188 | + ) | |
| 10189 | + }) | |
| 10190 | + .child( | |
| 10191 | + Button::new("accept-data-retention", "Accept") | |
| 10192 | + .label_size(LabelSize::Small) | |
| 10193 | + .style(ButtonStyle::Tinted(TintColor::Warning)) | |
| 10194 | + .on_click(cx.listener(|this, _, _, cx| { | |
| 10195 | + this.accept_data_retention_and_resend(cx); | |
| 10196 | + })), | |
| 10197 | + ), | |
| 10198 | + ) | |
| 10199 | + .dismiss_action(self.dismiss_error_button(cx)) | |
| 10200 | + } | |
| 10201 | + | |
| 10202 | + fn accept_data_retention_and_resend(&mut self, cx: &mut Context<Self>) { | |
| 10203 | + let fs = self.thread.read(cx).project().read(cx).fs().clone(); | |
| 10204 | + // Resume the failed turn only once the in-memory settings reflect | |
| 10205 | + // consent, otherwise the resent request would be rejected again. | |
| 10206 | + let completion = update_settings_file_with_completion(fs, cx, |settings, _| { | |
| 10207 | + settings | |
| 10208 | + .telemetry | |
| 10209 | + .get_or_insert_default() | |
| 10210 | + .anthropic_retention = Some(true); | |
| 10211 | + }); | |
| 10212 | + cx.spawn(async move |this, cx| { | |
| 10213 | + completion.await??; | |
| 10214 | + this.update(cx, |this, cx| this.retry_generation(cx))?; | |
| 10215 | + anyhow::Ok(()) | |
| 10216 | + }) | |
| 10217 | + .detach_and_log_err(cx); | |
| 10218 | + } | |
| 10219 | + | |
| 10220 | + fn switch_to_data_retention_fallback_and_resend(&mut self, cx: &mut Context<Self>) { | |
| 10221 | + let Some(fallback) = self.data_retention_fallback_model(cx) else { | |
| 10222 | + return; | |
| 10223 | + }; | |
| 10224 | + let model_id = acp_thread::AgentModelId::new(format!( | |
| 10225 | + "{}/{}", | |
| 10226 | + fallback.provider_id().0, | |
| 10227 | + fallback.id().0 | |
| 10228 | + )); | |
| 10229 | + let session_id = self.thread.read(cx).session_id().clone(); | |
| 10230 | + let Some(selector) = self | |
| 10231 | + .thread | |
| 10232 | + .read(cx) | |
| 10233 | + .connection() | |
| 10234 | + .model_selector(&session_id) | |
| 10235 | + else { | |
| 10236 | + return; | |
| 10237 | + }; | |
| 10238 | + let select = selector.select_model(model_id, cx); | |
| 10239 | + cx.spawn(async move |this, cx| { | |
| 10240 | + select.await?; | |
| 10241 | + this.update(cx, |this, cx| this.retry_generation(cx))?; | |
| 10242 | + anyhow::Ok(()) | |
| 10243 | + }) | |
| 10244 | + .detach_and_log_err(cx); | |
| 10245 | + } | |
| 10246 | + | |
| 10122 | 10247 | fn open_permission_dropdown( |
| 10123 | 10248 | &mut self, |
| 10124 | 10249 | _: &crate::OpenPermissionDropdown, |
| @@ -20,7 +20,7 @@ pub mod completion; · 19 unmodified lines | ||
| 20 | 20 | pub const ANTHROPIC_API_URL: &str = "https://api.anthropic.com"; |
| 21 | 21 | const FAST_MODE_BETA_HEADER: &str = "fast-mode-2026-02-01"; |
| 22 | 22 | |
| 23 | −pub const FABLE_MODEL_ID_PREFIX: &str = "claude-fable"; | |
| 23 | +pub const FABLE_MODEL_ID_PREFIX: &str = "claude-fable-5"; | |
| 24 | 24 | pub const FABLE_FALLBACK_MODEL_ID: &str = "claude-opus-4-8"; |
| 25 | 25 | |
| 26 | 26 | #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] |
| @@ -540,13 +540,16 @@ impl<T: 'static> Drop for PendingEntitySubscription<T> { · 539 unmodified lines | ||
| 540 | 540 | pub struct TelemetrySettings { |
| 541 | 541 | pub diagnostics: bool, |
| 542 | 542 | pub metrics: bool, |
| 543 | + pub anthropic_retention: bool, | |
| 543 | 544 | } |
| 544 | 545 | |
| 545 | 546 | impl settings::Settings for TelemetrySettings { |
| 546 | 547 | fn from_settings(content: &SettingsContent) -> Self { |
| 548 | + let telemetry = content.telemetry.as_ref().unwrap(); | |
| 547 | 549 | Self { |
| 548 | − diagnostics: content.telemetry.as_ref().unwrap().diagnostics.unwrap(), | |
| 549 | − metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(), | |
| 550 | + diagnostics: telemetry.diagnostics.unwrap(), | |
| 551 | + metrics: telemetry.metrics.unwrap(), | |
| 552 | + anthropic_retention: telemetry.anthropic_retention.unwrap(), | |
| 550 | 553 | } |
| 551 | 554 | } |
| 552 | 555 | } |
| @@ -60,6 +60,18 @@ pub trait LanguageModel: Send + Sync { · 59 unmodified lines | ||
| 60 | 60 | false |
| 61 | 61 | } |
| 62 | 62 | |
| 63 | + /// Whether requests to this model require the user to consent to the | |
| 64 | + /// upstream provider retaining inference logs (i.e. the model cannot be | |
| 65 | + /// offered with Zero Data Retention). | |
| 66 | + fn requires_data_retention(&self) -> bool { | |
| 67 | + false | |
| 68 | + } | |
| 69 | + | |
| 70 | + /// When this model refuses a request, the model ID to fall back to (same provider). | |
| 71 | + fn refusal_fallback_model_id(&self) -> Option<&'static str> { | |
| 72 | + None | |
| 73 | + } | |
| 74 | + | |
| 63 | 75 | fn telemetry_id(&self) -> String; |
| 64 | 76 | |
| 65 | 77 | fn api_key(&self, _cx: &App) -> Option<String> { |
| @@ -112,11 +124,6 @@ pub trait LanguageModel: Send + Sync { · 46 unmodified lines | ||
| 112 | 124 | false |
| 113 | 125 | } |
| 114 | 126 | |
| 115 | − /// When this model refuses a request, the model ID to fall back to (same provider). | |
| 116 | − fn refusal_fallback_model_id(&self) -> Option<&'static str> { | |
| 117 | − None | |
| 118 | − } | |
| 119 | − | |
| 120 | 127 | fn tool_input_format(&self) -> LanguageModelToolSchemaFormat { |
| 121 | 128 | LanguageModelToolSchemaFormat::JsonSchema |
| 122 | 129 | } |
| @@ -88,6 +88,14 @@ impl LanguageModelCompletionEvent { · 87 unmodified lines | ||
| 88 | 88 | pub enum LanguageModelCompletionError { |
| 89 | 89 | #[error("prompt too large for context window")] |
| 90 | 90 | PromptTooLarge { tokens: Option<u64> }, |
| 91 | + /// The model requires the user to consent to the upstream provider | |
| 92 | + /// retaining inference logs (see `LanguageModel::requires_data_retention`) | |
| 93 | + /// and that consent has not been given. | |
| 94 | + #[error( | |
| 95 | + "{model_name} cannot be offered with Zero Data Retention. \ | |
| 96 | + Anthropic will retain inference logs." | |
| 97 | + )] | |
| 98 | + DataRetentionConsentRequired { model_name: String }, | |
| 91 | 99 | #[error("missing {provider} API key")] |
| 92 | 100 | NoApiKey { provider: LanguageModelProviderName }, |
| 93 | 101 | #[error("{provider}'s API rate limit exceeded")] |
| @@ -1,6 +1,8 @@ | ||
| 1 | 1 | use ai_onboarding::YoungAccountBanner; |
| 2 | 2 | use anyhow::Result; |
| 3 | −use client::{Client, RefreshLlmTokenListener, UserStore, global_llm_token, zed_urls}; | |
| 3 | +use client::{ | |
| 4 | + Client, RefreshLlmTokenListener, TelemetrySettings, UserStore, global_llm_token, zed_urls, | |
| 5 | +}; | |
| 4 | 6 | use cloud_api_client::LlmApiToken; |
| 5 | 7 | use cloud_api_types::OrganizationId; |
| 6 | 8 | use cloud_api_types::Plan; |
| @@ -70,6 +72,14 @@ impl CloudLlmTokenProvider for ClientTokenProvider { · 63 unmodified lines | ||
| 70 | 72 | .await |
| 71 | 73 | }) |
| 72 | 74 | } |
| 75 | + | |
| 76 | + fn has_data_retention_consent(&self, cx: &impl AppContext) -> bool { | |
| 77 | + cx.read_global(|settings_store: &SettingsStore, _| { | |
| 78 | + settings_store | |
| 79 | + .get::<TelemetrySettings>(None) | |
| 80 | + .anthropic_retention | |
| 81 | + }) | |
| 82 | + } | |
| 73 | 83 | } |
| 74 | 84 | |
| 75 | 85 | #[derive(Default, Clone, Debug, PartialEq)] |
| @@ -55,6 +55,11 @@ pub trait CloudLlmTokenProvider: Send + Sync { · 54 unmodified lines | ||
| 55 | 55 | fn auth_context(&self, cx: &impl AppContext) -> Self::AuthContext; |
| 56 | 56 | fn cached_token(&self, auth_context: Self::AuthContext) -> BoxFuture<'static, Result<String>>; |
| 57 | 57 | fn refresh_token(&self, auth_context: Self::AuthContext) -> BoxFuture<'static, Result<String>>; |
| 58 | + | |
| 59 | + /// Whether the user has consented to upstream providers retaining | |
| 60 | + /// inference logs for models that require it (see | |
| 61 | + /// [`LanguageModel::requires_data_retention`]). | |
| 62 | + fn has_data_retention_consent(&self, cx: &impl AppContext) -> bool; | |
| 58 | 63 | } |
| 59 | 64 | |
| 60 | 65 | /// Sends an authenticated request to the Zed LLM service, retrying once with |
| @@ -298,6 +303,27 @@ impl<TP: CloudLlmTokenProvider + 'static> LanguageModel for CloudLanguageModel<T · 237 unmodified lines | ||
| 298 | 303 | self.model.is_latest |
| 299 | 304 | } |
| 300 | 305 | |
| 306 | + fn requires_data_retention(&self) -> bool { | |
| 307 | + // Anthropic cannot offer Fable models with Zero Data Retention | |
| 308 | + self.id | |
| 309 | + .0 | |
| 310 | + .as_ref() | |
| 311 | + .starts_with(anthropic::FABLE_MODEL_ID_PREFIX) | |
| 312 | + } | |
| 313 | + | |
| 314 | + fn refusal_fallback_model_id(&self) -> Option<&'static str> { | |
| 315 | + if self | |
| 316 | + .id | |
| 317 | + .0 | |
| 318 | + .as_ref() | |
| 319 | + .starts_with(anthropic::FABLE_MODEL_ID_PREFIX) | |
| 320 | + { | |
| 321 | + Some(anthropic::FABLE_FALLBACK_MODEL_ID) | |
| 322 | + } else { | |
| 323 | + None | |
| 324 | + } | |
| 325 | + } | |
| 326 | + | |
| 301 | 327 | fn supports_tools(&self) -> bool { |
| 302 | 328 | self.model.supports_tools |
| 303 | 329 | } |
| @@ -343,19 +369,6 @@ impl<TP: CloudLlmTokenProvider + 'static> LanguageModel for CloudLanguageModel<T · 39 unmodified lines | ||
| 343 | 369 | matches!(self.model.provider, OpenAi | XAi) |
| 344 | 370 | } |
| 345 | 371 | |
| 346 | − fn refusal_fallback_model_id(&self) -> Option<&'static str> { | |
| 347 | − if self | |
| 348 | − .id | |
| 349 | − .0 | |
| 350 | − .as_ref() | |
| 351 | − .starts_with(anthropic::FABLE_MODEL_ID_PREFIX) | |
| 352 | − { | |
| 353 | − Some(anthropic::FABLE_FALLBACK_MODEL_ID) | |
| 354 | − } else { | |
| 355 | − None | |
| 356 | − } | |
| 357 | − } | |
| 358 | − | |
| 359 | 372 | fn telemetry_id(&self) -> String { |
| 360 | 373 | format!("zed.dev/{}", self.model.id) |
| 361 | 374 | } |
| @@ -392,6 +405,14 @@ impl<TP: CloudLlmTokenProvider + 'static> LanguageModel for CloudLanguageModel<T · 30 unmodified lines | ||
| 392 | 405 | LanguageModelCompletionError, |
| 393 | 406 | >, |
| 394 | 407 | > { |
| 408 | + if self.requires_data_retention() && !self.token_provider.has_data_retention_consent(cx) { | |
| 409 | + let model_name = self.model.display_name.clone(); | |
| 410 | + return async move { | |
| 411 | + Err(LanguageModelCompletionError::DataRetentionConsentRequired { model_name }) | |
| 412 | + } | |
| 413 | + .boxed(); | |
| 414 | + } | |
| 415 | + | |
| 395 | 416 | let thread_id = request.thread_id.clone(); |
| 396 | 417 | let prompt_id = request.prompt_id.clone(); |
| 397 | 418 | let app_version = self.app_version.clone(); |
| @@ -862,6 +862,7 @@ impl VsCodeSettings { · 861 unmodified lines | ||
| 862 | 862 | Some(TelemetrySettingsContent { |
| 863 | 863 | metrics: Some(metrics), |
| 864 | 864 | diagnostics: Some(diagnostics), |
| 865 | + anthropic_retention: None, | |
| 865 | 866 | }) |
| 866 | 867 | }) |
| 867 | 868 | } |
| @@ -521,6 +521,11 @@ pub struct TelemetrySettingsContent { · 520 unmodified lines | ||
| 521 | 521 | /// |
| 522 | 522 | /// Default: true |
| 523 | 523 | pub metrics: Option<bool>, |
| 524 | + /// Allow sending requests to Anthropic models that cannot be offered with | |
| 525 | + /// Zero Data Retention. | |
| 526 | + /// | |
| 527 | + /// Default: false | |
| 528 | + pub anthropic_retention: Option<bool>, | |
| 524 | 529 | } |
| 525 | 530 | |
| 526 | 531 | impl Default for TelemetrySettingsContent { |
| @@ -528,6 +533,7 @@ impl Default for TelemetrySettingsContent { · 1 unmodified line | ||
| 528 | 533 | Self { |
| 529 | 534 | diagnostics: Some(true), |
| 530 | 535 | metrics: Some(true), |
| 536 | + anthropic_retention: Some(false), | |
| 531 | 537 | } |
| 532 | 538 | } |
| 533 | 539 | } |
| @@ -365,7 +365,7 @@ fn general_page(cx: &App) -> SettingsPage { · 364 unmodified lines | ||
| 365 | 365 | ] |
| 366 | 366 | } |
| 367 | 367 | |
| 368 | − fn privacy_section() -> [SettingsPageItem; 3] { | |
| 368 | + fn privacy_section() -> [SettingsPageItem; 4] { | |
| 369 | 369 | [ |
| 370 | 370 | SettingsPageItem::SectionHeader("Privacy"), |
| 371 | 371 | SettingsPageItem::SettingItem(SettingItem { |
| @@ -409,6 +409,28 @@ fn general_page(cx: &App) -> SettingsPage { · 37 unmodified lines | ||
| 409 | 409 | metadata: None, |
| 410 | 410 | files: USER, |
| 411 | 411 | }), |
| 412 | + SettingsPageItem::SettingItem(SettingItem { | |
| 413 | + title: "Anthropic Data Retention", | |
| 414 | + description: "Allow sending requests to Anthropic models that cannot be offered with Zero Data Retention.", | |
| 415 | + field: Box::new(SettingField { | |
| 416 | + organization_override: None, | |
| 417 | + json_path: Some("telemetry.anthropic_retention"), | |
| 418 | + pick: |settings_content| { | |
| 419 | + settings_content | |
| 420 | + .telemetry | |
| 421 | + .as_ref() | |
| 422 | + .and_then(|telemetry| telemetry.anthropic_retention.as_ref()) | |
| 423 | + }, | |
| 424 | + write: |settings_content, value, _| { | |
| 425 | + settings_content | |
| 426 | + .telemetry | |
| 427 | + .get_or_insert_default() | |
| 428 | + .anthropic_retention = value; | |
| 429 | + }, | |
| 430 | + }), | |
| 431 | + metadata: None, | |
| 432 | + files: USER, | |
| 433 | + }), | |
| 412 | 434 | ] |
| 413 | 435 | } |
| 414 | 436 | |