History

Gate Claude Fable behind Anthropic data retention consent

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.

Mikayla Maki committed 5 months ago

Commit 8b7c1931 parent db3a44c13 files · +252 −26Browse files

Unified · Split

13 files
  1. assets/settings/default.json +3 −0
  2. crates/agent/src/thread.rs +3 −0
  3. crates/agent_ui/src/conversation_view.rs +17 −0
  4. crates/agent_ui/src/conversation_view/thread_view.rs +128 −3
  5. crates/anthropic/src/anthropic.rs +1 −1
  6. crates/client/src/client.rs +5 −2
  7. crates/language_model/src/language_model.rs +12 −5
  8. crates/language_model_core/src/language_model_core.rs +8 −0
  9. crates/language_models/src/provider/cloud.rs +11 −1
  10. crates/language_models_cloud/src/language_models_cloud.rs +34 −13
  11. crates/settings/src/vscode_import.rs +1 −0
  12. crates/settings_content/src/settings_content.rs +6 −0
  13. crates/settings_ui/src/page_data.rs +23 −1
assets/settings/default.json+3−0
@@ -1471,6 +1471,9 @@ · 1470 unmodified lines
1471 "diagnostics": true,1471 "diagnostics": true,
1472 // Send anonymized usage data like what languages you're using Zed with.1472 // Send anonymized usage data like what languages you're using Zed with.
1473 "metrics": true,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 // Whether to disable all AI features in Zed.1478 // Whether to disable all AI features in Zed.
1476 //1479 //
crates/agent/src/thread.rs+3−0
@@ -3976,6 +3976,9 @@ impl Thread { · 3975 unmodified lines
3976 // Retrying won't help for Payment Required errors.3976 // Retrying won't help for Payment Required errors.
3977 None3977 None
3978 }3978 }
3979 // Retrying won't help until the user consents to data retention
3980 // or switches models.
3981 DataRetentionConsentRequired { .. } => None,
3979 // Conservatively assume that any other errors are non-retryable3982 // Conservatively assume that any other errors are non-retryable
3980 HttpResponseError { .. } | Other(..) => Some(RetryStrategy::Fixed {3983 HttpResponseError { .. } | Other(..) => Some(RetryStrategy::Fixed {
3981 delay: BASE_RETRY_DELAY,3984 delay: BASE_RETRY_DELAY,
crates/agent_ui/src/conversation_view.rs+17−0
@@ -123,6 +123,7 @@ enum ThreadFeedback { · 122 unmodified lines
123#[derive(Debug)]123#[derive(Debug)]
124pub(crate) enum ThreadError {124pub(crate) enum ThreadError {
125 PaymentRequired,125 PaymentRequired,
126 DataRetentionConsentRequired,
126 Refusal,127 Refusal,
127 AuthenticationRequired(SharedString),128 AuthenticationRequired(SharedString),
128 RateLimitExceeded {129 RateLimitExceeded {
@@ -196,6 +197,7 @@ impl From<anyhow::Error> for ThreadError { · 67 unmodified lines
196 provider: provider.to_string().into(),197 provider: provider.to_string().into(),
197 },198 },
198 UpstreamProviderError { .. } => Self::RequestFailed,199 UpstreamProviderError { .. } => Self::RequestFailed,
200 DataRetentionConsentRequired { .. } => Self::DataRetentionConsentRequired,
199 BadRequestFormat { provider, .. }201 BadRequestFormat { provider, .. }
200 | HttpResponseError { provider, .. }202 | HttpResponseError { provider, .. }
201 | ApiEndpointNotFound { provider } => Self::ApiError {203 | ApiEndpointNotFound { provider } => Self::ApiError {
@@ -3443,6 +3445,21 @@ pub(crate) mod tests { · 3241 unmodified lines
3443 3445
3444 use super::*;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 #[gpui::test]3463 #[gpui::test]
3447 async fn test_drop(cx: &mut TestAppContext) {3464 async fn test_drop(cx: &mut TestAppContext) {
3448 init_test(cx);3465 init_test(cx);
crates/agent_ui/src/conversation_view/thread_view.rs+128−3
@@ -23,10 +23,10 @@ use gpui::Stateful; · 22 unmodified lines
23use gpui::TaskExt;23use gpui::TaskExt;
24use heapless::Vec as ArrayVec;24use heapless::Vec as ArrayVec;
25use language_model::{25use language_model::{
26 FastModeConfirmation, LanguageModelEffortLevel, LanguageModelId, LanguageModelProviderId,26 FastModeConfirmation, LanguageModel, LanguageModelEffortLevel, LanguageModelId,
27 LanguageModelRegistry, Speed,27 LanguageModelProviderId, LanguageModelRegistry, Speed,
28};28};
29use settings::update_settings_file;29use settings::{update_settings_file, update_settings_file_with_completion};
30use ui::{30use ui::{
31 ButtonLike, CalloutBorderPosition, SpinnerLabel, SpinnerVariant, SplitButton, SplitButtonStyle,31 ButtonLike, CalloutBorderPosition, SpinnerLabel, SpinnerVariant, SplitButton, SplitButtonStyle,
32 Tab,32 Tab,
@@ -36,6 +36,9 @@ use workspace::{OpenOptions, SERIALIZATION_THROTTLE_TIME}; · 3 unmodified lines
36 36
37use super::*;37use super::*;
38 38
39// TODO: Replace with the final link explaining Anthropic's data retention policy.
40const DATA_RETENTION_LEARN_MORE_URL: &str = "#link-tbd";
41
39#[derive(Default)]42#[derive(Default)]
40struct ThreadFeedbackState {43struct ThreadFeedbackState {
41 feedback: Option<ThreadFeedback>,44 feedback: Option<ThreadFeedback>,
@@ -1714,6 +1717,13 @@ impl ThreadView { · 1672 unmodified lines
1714 );1717 );
1715 ("refusal", None, message.into())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 ThreadError::AuthenticationRequired(message) => {1727 ThreadError::AuthenticationRequired(message) => {
1718 ("authentication_required", None, message.clone())1728 ("authentication_required", None, message.clone())
1719 }1729 }
@@ -9348,6 +9358,9 @@ impl ThreadView { · 7628 unmodified lines
9348 self.render_any_thread_error(message.clone(), window, cx)9358 self.render_any_thread_error(message.clone(), window, cx)
9349 }9359 }
9350 ThreadError::Refusal => self.render_refusal_error(cx),9360 ThreadError::Refusal => self.render_refusal_error(cx),
9361 ThreadError::DataRetentionConsentRequired => {
9362 self.render_data_retention_consent_error(cx)
9363 }
9351 ThreadError::AuthenticationRequired(error) => {9364 ThreadError::AuthenticationRequired(error) => {
9352 self.render_authentication_required_error(error.clone(), cx)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 fn open_permission_dropdown(10247 fn open_permission_dropdown(
10123 &mut self,10248 &mut self,
10124 _: &crate::OpenPermissionDropdown,10249 _: &crate::OpenPermissionDropdown,
crates/anthropic/src/anthropic.rs+1−1
@@ -20,7 +20,7 @@ pub mod completion; · 19 unmodified lines
20pub const ANTHROPIC_API_URL: &str = "https://api.anthropic.com";20pub const ANTHROPIC_API_URL: &str = "https://api.anthropic.com";
21const FAST_MODE_BETA_HEADER: &str = "fast-mode-2026-02-01";21const FAST_MODE_BETA_HEADER: &str = "fast-mode-2026-02-01";
22 22
23pub const FABLE_MODEL_ID_PREFIX: &str = "claude-fable";23pub const FABLE_MODEL_ID_PREFIX: &str = "claude-fable-5";
24pub const FABLE_FALLBACK_MODEL_ID: &str = "claude-opus-4-8";24pub const FABLE_FALLBACK_MODEL_ID: &str = "claude-opus-4-8";
25 25
26#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]26#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
crates/client/src/client.rs+5−2
@@ -540,13 +540,16 @@ impl<T: 'static> Drop for PendingEntitySubscription<T> { · 539 unmodified lines
540pub struct TelemetrySettings {540pub struct TelemetrySettings {
541 pub diagnostics: bool,541 pub diagnostics: bool,
542 pub metrics: bool,542 pub metrics: bool,
543 pub anthropic_retention: bool,
543}544}
544 545
545impl settings::Settings for TelemetrySettings {546impl settings::Settings for TelemetrySettings {
546 fn from_settings(content: &SettingsContent) -> Self {547 fn from_settings(content: &SettingsContent) -> Self {
548 let telemetry = content.telemetry.as_ref().unwrap();
547 Self {549 Self {
548 diagnostics: content.telemetry.as_ref().unwrap().diagnostics.unwrap(),550 diagnostics: telemetry.diagnostics.unwrap(),
549 metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(),551 metrics: telemetry.metrics.unwrap(),
552 anthropic_retention: telemetry.anthropic_retention.unwrap(),
550 }553 }
551 }554 }
552}555}
crates/language_model/src/language_model.rs+12−5
@@ -60,6 +60,18 @@ pub trait LanguageModel: Send + Sync { · 59 unmodified lines
60 false60 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 fn telemetry_id(&self) -> String;75 fn telemetry_id(&self) -> String;
64 76
65 fn api_key(&self, _cx: &App) -> Option<String> {77 fn api_key(&self, _cx: &App) -> Option<String> {
@@ -112,11 +124,6 @@ pub trait LanguageModel: Send + Sync { · 46 unmodified lines
112 false124 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 fn tool_input_format(&self) -> LanguageModelToolSchemaFormat {127 fn tool_input_format(&self) -> LanguageModelToolSchemaFormat {
121 LanguageModelToolSchemaFormat::JsonSchema128 LanguageModelToolSchemaFormat::JsonSchema
122 }129 }
crates/language_model_core/src/language_model_core.rs+8−0
@@ -88,6 +88,14 @@ impl LanguageModelCompletionEvent { · 87 unmodified lines
88pub enum LanguageModelCompletionError {88pub enum LanguageModelCompletionError {
89 #[error("prompt too large for context window")]89 #[error("prompt too large for context window")]
90 PromptTooLarge { tokens: Option<u64> },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 #[error("missing {provider} API key")]99 #[error("missing {provider} API key")]
92 NoApiKey { provider: LanguageModelProviderName },100 NoApiKey { provider: LanguageModelProviderName },
93 #[error("{provider}'s API rate limit exceeded")]101 #[error("{provider}'s API rate limit exceeded")]
crates/language_models/src/provider/cloud.rs+11−1
@@ -1,6 +1,8 @@
1use ai_onboarding::YoungAccountBanner;1use ai_onboarding::YoungAccountBanner;
2use anyhow::Result;2use anyhow::Result;
3use client::{Client, RefreshLlmTokenListener, UserStore, global_llm_token, zed_urls};3use client::{
4 Client, RefreshLlmTokenListener, TelemetrySettings, UserStore, global_llm_token, zed_urls,
5};
4use cloud_api_client::LlmApiToken;6use cloud_api_client::LlmApiToken;
5use cloud_api_types::OrganizationId;7use cloud_api_types::OrganizationId;
6use cloud_api_types::Plan;8use cloud_api_types::Plan;
@@ -70,6 +72,14 @@ impl CloudLlmTokenProvider for ClientTokenProvider { · 63 unmodified lines
70 .await72 .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#[derive(Default, Clone, Debug, PartialEq)]85#[derive(Default, Clone, Debug, PartialEq)]
crates/language_models_cloud/src/language_models_cloud.rs+34−13
@@ -55,6 +55,11 @@ pub trait CloudLlmTokenProvider: Send + Sync { · 54 unmodified lines
55 fn auth_context(&self, cx: &impl AppContext) -> Self::AuthContext;55 fn auth_context(&self, cx: &impl AppContext) -> Self::AuthContext;
56 fn cached_token(&self, auth_context: Self::AuthContext) -> BoxFuture<'static, Result<String>>;56 fn cached_token(&self, auth_context: Self::AuthContext) -> BoxFuture<'static, Result<String>>;
57 fn refresh_token(&self, auth_context: Self::AuthContext) -> BoxFuture<'static, Result<String>>;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/// Sends an authenticated request to the Zed LLM service, retrying once with65/// 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 self.model.is_latest303 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 fn supports_tools(&self) -> bool {327 fn supports_tools(&self) -> bool {
302 self.model.supports_tools328 self.model.supports_tools
303 }329 }
@@ -343,19 +369,6 @@ impl<TP: CloudLlmTokenProvider + 'static> LanguageModel for CloudLanguageModel<T · 39 unmodified lines
343 matches!(self.model.provider, OpenAi | XAi)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 fn telemetry_id(&self) -> String {372 fn telemetry_id(&self) -> String {
360 format!("zed.dev/{}", self.model.id)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 LanguageModelCompletionError,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 let thread_id = request.thread_id.clone();416 let thread_id = request.thread_id.clone();
396 let prompt_id = request.prompt_id.clone();417 let prompt_id = request.prompt_id.clone();
397 let app_version = self.app_version.clone();418 let app_version = self.app_version.clone();
crates/settings/src/vscode_import.rs+1−0
@@ -862,6 +862,7 @@ impl VsCodeSettings { · 861 unmodified lines
862 Some(TelemetrySettingsContent {862 Some(TelemetrySettingsContent {
863 metrics: Some(metrics),863 metrics: Some(metrics),
864 diagnostics: Some(diagnostics),864 diagnostics: Some(diagnostics),
865 anthropic_retention: None,
865 })866 })
866 })867 })
867 }868 }
crates/settings_content/src/settings_content.rs+6−0
@@ -521,6 +521,11 @@ pub struct TelemetrySettingsContent { · 520 unmodified lines
521 ///521 ///
522 /// Default: true522 /// Default: true
523 pub metrics: Option<bool>,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
526impl Default for TelemetrySettingsContent {531impl Default for TelemetrySettingsContent {
@@ -528,6 +533,7 @@ impl Default for TelemetrySettingsContent { · 1 unmodified line
528 Self {533 Self {
529 diagnostics: Some(true),534 diagnostics: Some(true),
530 metrics: Some(true),535 metrics: Some(true),
536 anthropic_retention: Some(false),
531 }537 }
532 }538 }
533}539}
crates/settings_ui/src/page_data.rs+23−1
@@ -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 SettingsPageItem::SectionHeader("Privacy"),370 SettingsPageItem::SectionHeader("Privacy"),
371 SettingsPageItem::SettingItem(SettingItem {371 SettingsPageItem::SettingItem(SettingItem {
@@ -409,6 +409,28 @@ fn general_page(cx: &App) -> SettingsPage { · 37 unmodified lines
409 metadata: None,409 metadata: None,
410 files: USER,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