anthropic.rs

   1pub mod telemetry;
   2
   3use anthropic::{
   4    ANTHROPIC_API_URL, AnthropicError, AnthropicModelMode, ContentDelta, CountTokensRequest, Event,
   5    ResponseContent, ToolResultContent, ToolResultPart, Usage,
   6};
   7use anyhow::Result;
   8use collections::{BTreeMap, HashMap};
   9use futures::{FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream};
  10use gpui::{AnyView, App, AsyncApp, Context, Entity, Task};
  11use http_client::HttpClient;
  12use language_model::{
  13    ANTHROPIC_PROVIDER_ID, ANTHROPIC_PROVIDER_NAME, ApiKeyState, AuthenticateError,
  14    ConfigurationViewTargetAgent, EnvVar, IconOrSvg, LanguageModel,
  15    LanguageModelCacheConfiguration, LanguageModelCompletionError, LanguageModelCompletionEvent,
  16    LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId,
  17    LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest,
  18    LanguageModelToolChoice, LanguageModelToolResultContent, LanguageModelToolUse, MessageContent,
  19    RateLimiter, Role, StopReason, env_var,
  20};
  21use settings::{Settings, SettingsStore};
  22use std::pin::Pin;
  23use std::str::FromStr;
  24use std::sync::{Arc, LazyLock};
  25use strum::IntoEnumIterator;
  26use ui::{ButtonLink, ConfiguredApiCard, List, ListBulletItem, prelude::*};
  27use ui_input::InputField;
  28use util::ResultExt;
  29
  30use crate::provider::util::{fix_streamed_json, parse_tool_arguments};
  31
  32pub use settings::AnthropicAvailableModel as AvailableModel;
  33
  34const PROVIDER_ID: LanguageModelProviderId = ANTHROPIC_PROVIDER_ID;
  35const PROVIDER_NAME: LanguageModelProviderName = ANTHROPIC_PROVIDER_NAME;
  36
  37#[derive(Default, Clone, Debug, PartialEq)]
  38pub struct AnthropicSettings {
  39    pub api_url: String,
  40    /// Extend Zed's list of Anthropic models.
  41    pub available_models: Vec<AvailableModel>,
  42}
  43
  44pub struct AnthropicLanguageModelProvider {
  45    http_client: Arc<dyn HttpClient>,
  46    state: Entity<State>,
  47}
  48
  49const API_KEY_ENV_VAR_NAME: &str = "ANTHROPIC_API_KEY";
  50static API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);
  51
  52pub struct State {
  53    api_key_state: ApiKeyState,
  54}
  55
  56impl State {
  57    fn is_authenticated(&self) -> bool {
  58        self.api_key_state.has_key()
  59    }
  60
  61    fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {
  62        let api_url = AnthropicLanguageModelProvider::api_url(cx);
  63        self.api_key_state
  64            .store(api_url, api_key, |this| &mut this.api_key_state, cx)
  65    }
  66
  67    fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {
  68        let api_url = AnthropicLanguageModelProvider::api_url(cx);
  69        self.api_key_state
  70            .load_if_needed(api_url, |this| &mut this.api_key_state, cx)
  71    }
  72}
  73
  74impl AnthropicLanguageModelProvider {
  75    pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {
  76        let state = cx.new(|cx| {
  77            cx.observe_global::<SettingsStore>(|this: &mut State, cx| {
  78                let api_url = Self::api_url(cx);
  79                this.api_key_state
  80                    .handle_url_change(api_url, |this| &mut this.api_key_state, cx);
  81                cx.notify();
  82            })
  83            .detach();
  84            State {
  85                api_key_state: ApiKeyState::new(Self::api_url(cx), (*API_KEY_ENV_VAR).clone()),
  86            }
  87        });
  88
  89        Self { http_client, state }
  90    }
  91
  92    fn create_language_model(&self, model: anthropic::Model) -> Arc<dyn LanguageModel> {
  93        Arc::new(AnthropicModel {
  94            id: LanguageModelId::from(model.id().to_string()),
  95            model,
  96            state: self.state.clone(),
  97            http_client: self.http_client.clone(),
  98            request_limiter: RateLimiter::new(4),
  99        })
 100    }
 101
 102    fn settings(cx: &App) -> &AnthropicSettings {
 103        &crate::AllLanguageModelSettings::get_global(cx).anthropic
 104    }
 105
 106    fn api_url(cx: &App) -> SharedString {
 107        let api_url = &Self::settings(cx).api_url;
 108        if api_url.is_empty() {
 109            ANTHROPIC_API_URL.into()
 110        } else {
 111            SharedString::new(api_url.as_str())
 112        }
 113    }
 114}
 115
 116impl LanguageModelProviderState for AnthropicLanguageModelProvider {
 117    type ObservableEntity = State;
 118
 119    fn observable_entity(&self) -> Option<Entity<Self::ObservableEntity>> {
 120        Some(self.state.clone())
 121    }
 122}
 123
 124impl LanguageModelProvider for AnthropicLanguageModelProvider {
 125    fn id(&self) -> LanguageModelProviderId {
 126        PROVIDER_ID
 127    }
 128
 129    fn name(&self) -> LanguageModelProviderName {
 130        PROVIDER_NAME
 131    }
 132
 133    fn icon(&self) -> IconOrSvg {
 134        IconOrSvg::Icon(IconName::AiAnthropic)
 135    }
 136
 137    fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {
 138        Some(self.create_language_model(anthropic::Model::default()))
 139    }
 140
 141    fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {
 142        Some(self.create_language_model(anthropic::Model::default_fast()))
 143    }
 144
 145    fn recommended_models(&self, _cx: &App) -> Vec<Arc<dyn LanguageModel>> {
 146        [anthropic::Model::ClaudeSonnet4_6]
 147            .into_iter()
 148            .map(|model| self.create_language_model(model))
 149            .collect()
 150    }
 151
 152    fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
 153        let mut models = BTreeMap::default();
 154
 155        // Add base models from anthropic::Model::iter()
 156        for model in anthropic::Model::iter() {
 157            if !matches!(model, anthropic::Model::Custom { .. }) {
 158                models.insert(model.id().to_string(), model);
 159            }
 160        }
 161
 162        // Override with available models from settings
 163        for model in &AnthropicLanguageModelProvider::settings(cx).available_models {
 164            models.insert(
 165                model.name.clone(),
 166                anthropic::Model::Custom {
 167                    name: model.name.clone(),
 168                    display_name: model.display_name.clone(),
 169                    max_tokens: model.max_tokens,
 170                    tool_override: model.tool_override.clone(),
 171                    cache_configuration: model.cache_configuration.as_ref().map(|config| {
 172                        anthropic::AnthropicModelCacheConfiguration {
 173                            max_cache_anchors: config.max_cache_anchors,
 174                            should_speculate: config.should_speculate,
 175                            min_total_token: config.min_total_token,
 176                        }
 177                    }),
 178                    max_output_tokens: model.max_output_tokens,
 179                    default_temperature: model.default_temperature,
 180                    extra_beta_headers: model.extra_beta_headers.clone(),
 181                    mode: match model.mode.unwrap_or_default() {
 182                        settings::ModelMode::Default => AnthropicModelMode::Default,
 183                        settings::ModelMode::Thinking { budget_tokens } => {
 184                            AnthropicModelMode::Thinking { budget_tokens }
 185                        }
 186                    },
 187                },
 188            );
 189        }
 190
 191        models
 192            .into_values()
 193            .map(|model| self.create_language_model(model))
 194            .collect()
 195    }
 196
 197    fn is_authenticated(&self, cx: &App) -> bool {
 198        self.state.read(cx).is_authenticated()
 199    }
 200
 201    fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {
 202        self.state.update(cx, |state, cx| state.authenticate(cx))
 203    }
 204
 205    fn configuration_view(
 206        &self,
 207        target_agent: ConfigurationViewTargetAgent,
 208        window: &mut Window,
 209        cx: &mut App,
 210    ) -> AnyView {
 211        cx.new(|cx| ConfigurationView::new(self.state.clone(), target_agent, window, cx))
 212            .into()
 213    }
 214
 215    fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {
 216        self.state
 217            .update(cx, |state, cx| state.set_api_key(None, cx))
 218    }
 219}
 220
 221pub struct AnthropicModel {
 222    id: LanguageModelId,
 223    model: anthropic::Model,
 224    state: Entity<State>,
 225    http_client: Arc<dyn HttpClient>,
 226    request_limiter: RateLimiter,
 227}
 228
 229fn to_anthropic_content(content: MessageContent) -> Option<anthropic::RequestContent> {
 230    match content {
 231        MessageContent::Text(text) => {
 232            let text = if text.chars().last().is_some_and(|c| c.is_whitespace()) {
 233                text.trim_end().to_string()
 234            } else {
 235                text
 236            };
 237            if !text.is_empty() {
 238                Some(anthropic::RequestContent::Text {
 239                    text,
 240                    cache_control: None,
 241                })
 242            } else {
 243                None
 244            }
 245        }
 246        MessageContent::Thinking {
 247            text: thinking,
 248            signature,
 249        } => {
 250            if let Some(signature) = signature
 251                && !thinking.is_empty()
 252            {
 253                Some(anthropic::RequestContent::Thinking {
 254                    thinking,
 255                    signature,
 256                    cache_control: None,
 257                })
 258            } else {
 259                None
 260            }
 261        }
 262        MessageContent::RedactedThinking(data) => {
 263            if !data.is_empty() {
 264                Some(anthropic::RequestContent::RedactedThinking { data })
 265            } else {
 266                None
 267            }
 268        }
 269        MessageContent::Image(image) => Some(anthropic::RequestContent::Image {
 270            source: anthropic::ImageSource {
 271                source_type: "base64".to_string(),
 272                media_type: "image/png".to_string(),
 273                data: image.source.to_string(),
 274            },
 275            cache_control: None,
 276        }),
 277        MessageContent::ToolUse(tool_use) => Some(anthropic::RequestContent::ToolUse {
 278            id: tool_use.id.to_string(),
 279            name: tool_use.name.to_string(),
 280            input: tool_use.input,
 281            cache_control: None,
 282        }),
 283        MessageContent::ToolResult(tool_result) => Some(anthropic::RequestContent::ToolResult {
 284            tool_use_id: tool_result.tool_use_id.to_string(),
 285            is_error: tool_result.is_error,
 286            content: match tool_result.content {
 287                LanguageModelToolResultContent::Text(text) => {
 288                    ToolResultContent::Plain(text.to_string())
 289                }
 290                LanguageModelToolResultContent::Image(image) => {
 291                    ToolResultContent::Multipart(vec![ToolResultPart::Image {
 292                        source: anthropic::ImageSource {
 293                            source_type: "base64".to_string(),
 294                            media_type: "image/png".to_string(),
 295                            data: image.source.to_string(),
 296                        },
 297                    }])
 298                }
 299            },
 300            cache_control: None,
 301        }),
 302    }
 303}
 304
 305/// Convert a LanguageModelRequest to an Anthropic CountTokensRequest.
 306pub fn into_anthropic_count_tokens_request(
 307    request: LanguageModelRequest,
 308    model: String,
 309    mode: AnthropicModelMode,
 310) -> CountTokensRequest {
 311    let mut new_messages: Vec<anthropic::Message> = Vec::new();
 312    let mut system_message = String::new();
 313
 314    for message in request.messages {
 315        if message.contents_empty() {
 316            continue;
 317        }
 318
 319        match message.role {
 320            Role::User | Role::Assistant => {
 321                let anthropic_message_content: Vec<anthropic::RequestContent> = message
 322                    .content
 323                    .into_iter()
 324                    .filter_map(to_anthropic_content)
 325                    .collect();
 326                let anthropic_role = match message.role {
 327                    Role::User => anthropic::Role::User,
 328                    Role::Assistant => anthropic::Role::Assistant,
 329                    Role::System => unreachable!("System role should never occur here"),
 330                };
 331                if anthropic_message_content.is_empty() {
 332                    continue;
 333                }
 334
 335                if let Some(last_message) = new_messages.last_mut()
 336                    && last_message.role == anthropic_role
 337                {
 338                    last_message.content.extend(anthropic_message_content);
 339                    continue;
 340                }
 341
 342                new_messages.push(anthropic::Message {
 343                    role: anthropic_role,
 344                    content: anthropic_message_content,
 345                });
 346            }
 347            Role::System => {
 348                if !system_message.is_empty() {
 349                    system_message.push_str("\n\n");
 350                }
 351                system_message.push_str(&message.string_contents());
 352            }
 353        }
 354    }
 355
 356    CountTokensRequest {
 357        model,
 358        messages: new_messages,
 359        system: if system_message.is_empty() {
 360            None
 361        } else {
 362            Some(anthropic::StringOrContents::String(system_message))
 363        },
 364        thinking: if request.thinking_allowed {
 365            match mode {
 366                AnthropicModelMode::Thinking { budget_tokens } => {
 367                    Some(anthropic::Thinking::Enabled { budget_tokens })
 368                }
 369                AnthropicModelMode::AdaptiveThinking => Some(anthropic::Thinking::Adaptive),
 370                AnthropicModelMode::Default => None,
 371            }
 372        } else {
 373            None
 374        },
 375        tools: request
 376            .tools
 377            .into_iter()
 378            .map(|tool| anthropic::Tool {
 379                name: tool.name,
 380                description: tool.description,
 381                input_schema: tool.input_schema,
 382                eager_input_streaming: tool.use_input_streaming,
 383            })
 384            .collect(),
 385        tool_choice: request.tool_choice.map(|choice| match choice {
 386            LanguageModelToolChoice::Auto => anthropic::ToolChoice::Auto,
 387            LanguageModelToolChoice::Any => anthropic::ToolChoice::Any,
 388            LanguageModelToolChoice::None => anthropic::ToolChoice::None,
 389        }),
 390    }
 391}
 392
 393/// Estimate tokens using tiktoken. Used as a fallback when the API is unavailable,
 394/// or by providers (like Zed Cloud) that don't have direct Anthropic API access.
 395pub fn count_anthropic_tokens_with_tiktoken(request: LanguageModelRequest) -> Result<u64> {
 396    let messages = request.messages;
 397    let mut tokens_from_images = 0;
 398    let mut string_messages = Vec::with_capacity(messages.len());
 399
 400    for message in messages {
 401        let mut string_contents = String::new();
 402
 403        for content in message.content {
 404            match content {
 405                MessageContent::Text(text) => {
 406                    string_contents.push_str(&text);
 407                }
 408                MessageContent::Thinking { .. } => {
 409                    // Thinking blocks are not included in the input token count.
 410                }
 411                MessageContent::RedactedThinking(_) => {
 412                    // Thinking blocks are not included in the input token count.
 413                }
 414                MessageContent::Image(image) => {
 415                    tokens_from_images += image.estimate_tokens();
 416                }
 417                MessageContent::ToolUse(_tool_use) => {
 418                    // TODO: Estimate token usage from tool uses.
 419                }
 420                MessageContent::ToolResult(tool_result) => match &tool_result.content {
 421                    LanguageModelToolResultContent::Text(text) => {
 422                        string_contents.push_str(text);
 423                    }
 424                    LanguageModelToolResultContent::Image(image) => {
 425                        tokens_from_images += image.estimate_tokens();
 426                    }
 427                },
 428            }
 429        }
 430
 431        if !string_contents.is_empty() {
 432            string_messages.push(tiktoken_rs::ChatCompletionRequestMessage {
 433                role: match message.role {
 434                    Role::User => "user".into(),
 435                    Role::Assistant => "assistant".into(),
 436                    Role::System => "system".into(),
 437                },
 438                content: Some(string_contents),
 439                name: None,
 440                function_call: None,
 441            });
 442        }
 443    }
 444
 445    // Tiktoken doesn't yet support these models, so we manually use the
 446    // same tokenizer as GPT-4.
 447    tiktoken_rs::num_tokens_from_messages("gpt-4", &string_messages)
 448        .map(|tokens| (tokens + tokens_from_images) as u64)
 449}
 450
 451impl AnthropicModel {
 452    fn stream_completion(
 453        &self,
 454        request: anthropic::Request,
 455        cx: &AsyncApp,
 456    ) -> BoxFuture<
 457        'static,
 458        Result<
 459            BoxStream<'static, Result<anthropic::Event, AnthropicError>>,
 460            LanguageModelCompletionError,
 461        >,
 462    > {
 463        let http_client = self.http_client.clone();
 464
 465        let (api_key, api_url) = self.state.read_with(cx, |state, cx| {
 466            let api_url = AnthropicLanguageModelProvider::api_url(cx);
 467            (state.api_key_state.key(&api_url), api_url)
 468        });
 469
 470        let beta_headers = self.model.beta_headers();
 471
 472        async move {
 473            let Some(api_key) = api_key else {
 474                return Err(LanguageModelCompletionError::NoApiKey {
 475                    provider: PROVIDER_NAME,
 476                });
 477            };
 478            let request = anthropic::stream_completion(
 479                http_client.as_ref(),
 480                &api_url,
 481                &api_key,
 482                request,
 483                beta_headers,
 484            );
 485            request.await.map_err(Into::into)
 486        }
 487        .boxed()
 488    }
 489}
 490
 491impl LanguageModel for AnthropicModel {
 492    fn id(&self) -> LanguageModelId {
 493        self.id.clone()
 494    }
 495
 496    fn name(&self) -> LanguageModelName {
 497        LanguageModelName::from(self.model.display_name().to_string())
 498    }
 499
 500    fn provider_id(&self) -> LanguageModelProviderId {
 501        PROVIDER_ID
 502    }
 503
 504    fn provider_name(&self) -> LanguageModelProviderName {
 505        PROVIDER_NAME
 506    }
 507
 508    fn supports_tools(&self) -> bool {
 509        true
 510    }
 511
 512    fn supports_images(&self) -> bool {
 513        true
 514    }
 515
 516    fn supports_streaming_tools(&self) -> bool {
 517        true
 518    }
 519
 520    fn supports_tool_choice(&self, choice: LanguageModelToolChoice) -> bool {
 521        match choice {
 522            LanguageModelToolChoice::Auto
 523            | LanguageModelToolChoice::Any
 524            | LanguageModelToolChoice::None => true,
 525        }
 526    }
 527
 528    fn supports_thinking(&self) -> bool {
 529        self.model.supports_thinking()
 530    }
 531
 532    fn supported_effort_levels(&self) -> Vec<language_model::LanguageModelEffortLevel> {
 533        if self.model.supports_adaptive_thinking() {
 534            vec![
 535                language_model::LanguageModelEffortLevel {
 536                    name: "Low".into(),
 537                    value: "low".into(),
 538                    is_default: false,
 539                },
 540                language_model::LanguageModelEffortLevel {
 541                    name: "Medium".into(),
 542                    value: "medium".into(),
 543                    is_default: false,
 544                },
 545                language_model::LanguageModelEffortLevel {
 546                    name: "High".into(),
 547                    value: "high".into(),
 548                    is_default: true,
 549                },
 550                language_model::LanguageModelEffortLevel {
 551                    name: "Max".into(),
 552                    value: "max".into(),
 553                    is_default: false,
 554                },
 555            ]
 556        } else {
 557            Vec::new()
 558        }
 559    }
 560
 561    fn telemetry_id(&self) -> String {
 562        format!("anthropic/{}", self.model.id())
 563    }
 564
 565    fn api_key(&self, cx: &App) -> Option<String> {
 566        self.state.read_with(cx, |state, cx| {
 567            let api_url = AnthropicLanguageModelProvider::api_url(cx);
 568            state.api_key_state.key(&api_url).map(|key| key.to_string())
 569        })
 570    }
 571
 572    fn max_token_count(&self) -> u64 {
 573        self.model.max_token_count()
 574    }
 575
 576    fn max_output_tokens(&self) -> Option<u64> {
 577        Some(self.model.max_output_tokens())
 578    }
 579
 580    fn count_tokens(
 581        &self,
 582        request: LanguageModelRequest,
 583        cx: &App,
 584    ) -> BoxFuture<'static, Result<u64>> {
 585        let http_client = self.http_client.clone();
 586        let model_id = self.model.request_id().to_string();
 587        let mode = self.model.mode();
 588
 589        let (api_key, api_url) = self.state.read_with(cx, |state, cx| {
 590            let api_url = AnthropicLanguageModelProvider::api_url(cx);
 591            (
 592                state.api_key_state.key(&api_url).map(|k| k.to_string()),
 593                api_url.to_string(),
 594            )
 595        });
 596
 597        async move {
 598            // If no API key, fall back to tiktoken estimation
 599            let Some(api_key) = api_key else {
 600                return count_anthropic_tokens_with_tiktoken(request);
 601            };
 602
 603            let count_request =
 604                into_anthropic_count_tokens_request(request.clone(), model_id, mode);
 605
 606            match anthropic::count_tokens(http_client.as_ref(), &api_url, &api_key, count_request)
 607                .await
 608            {
 609                Ok(response) => Ok(response.input_tokens),
 610                Err(err) => {
 611                    log::error!(
 612                        "Anthropic count_tokens API failed, falling back to tiktoken: {err:?}"
 613                    );
 614                    count_anthropic_tokens_with_tiktoken(request)
 615                }
 616            }
 617        }
 618        .boxed()
 619    }
 620
 621    fn stream_completion(
 622        &self,
 623        request: LanguageModelRequest,
 624        cx: &AsyncApp,
 625    ) -> BoxFuture<
 626        'static,
 627        Result<
 628            BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
 629            LanguageModelCompletionError,
 630        >,
 631    > {
 632        let request = into_anthropic(
 633            request,
 634            self.model.request_id().into(),
 635            self.model.default_temperature(),
 636            self.model.max_output_tokens(),
 637            self.model.mode(),
 638        );
 639        let request = self.stream_completion(request, cx);
 640        let future = self.request_limiter.stream(async move {
 641            let response = request.await?;
 642            Ok(AnthropicEventMapper::new().map_stream(response))
 643        });
 644        async move { Ok(future.await?.boxed()) }.boxed()
 645    }
 646
 647    fn cache_configuration(&self) -> Option<LanguageModelCacheConfiguration> {
 648        self.model
 649            .cache_configuration()
 650            .map(|config| LanguageModelCacheConfiguration {
 651                max_cache_anchors: config.max_cache_anchors,
 652                should_speculate: config.should_speculate,
 653                min_total_token: config.min_total_token,
 654            })
 655    }
 656}
 657
 658pub fn into_anthropic(
 659    request: LanguageModelRequest,
 660    model: String,
 661    default_temperature: f32,
 662    max_output_tokens: u64,
 663    mode: AnthropicModelMode,
 664) -> anthropic::Request {
 665    let mut new_messages: Vec<anthropic::Message> = Vec::new();
 666    let mut system_message = String::new();
 667
 668    for message in request.messages {
 669        if message.contents_empty() {
 670            continue;
 671        }
 672
 673        match message.role {
 674            Role::User | Role::Assistant => {
 675                let mut anthropic_message_content: Vec<anthropic::RequestContent> = message
 676                    .content
 677                    .into_iter()
 678                    .filter_map(to_anthropic_content)
 679                    .collect();
 680                let anthropic_role = match message.role {
 681                    Role::User => anthropic::Role::User,
 682                    Role::Assistant => anthropic::Role::Assistant,
 683                    Role::System => unreachable!("System role should never occur here"),
 684                };
 685                if anthropic_message_content.is_empty() {
 686                    continue;
 687                }
 688
 689                if let Some(last_message) = new_messages.last_mut()
 690                    && last_message.role == anthropic_role
 691                {
 692                    last_message.content.extend(anthropic_message_content);
 693                    continue;
 694                }
 695
 696                // Mark the last segment of the message as cached
 697                if message.cache {
 698                    let cache_control_value = Some(anthropic::CacheControl {
 699                        cache_type: anthropic::CacheControlType::Ephemeral,
 700                    });
 701                    for message_content in anthropic_message_content.iter_mut().rev() {
 702                        match message_content {
 703                            anthropic::RequestContent::RedactedThinking { .. } => {
 704                                // Caching is not possible, fallback to next message
 705                            }
 706                            anthropic::RequestContent::Text { cache_control, .. }
 707                            | anthropic::RequestContent::Thinking { cache_control, .. }
 708                            | anthropic::RequestContent::Image { cache_control, .. }
 709                            | anthropic::RequestContent::ToolUse { cache_control, .. }
 710                            | anthropic::RequestContent::ToolResult { cache_control, .. } => {
 711                                *cache_control = cache_control_value;
 712                                break;
 713                            }
 714                        }
 715                    }
 716                }
 717
 718                new_messages.push(anthropic::Message {
 719                    role: anthropic_role,
 720                    content: anthropic_message_content,
 721                });
 722            }
 723            Role::System => {
 724                if !system_message.is_empty() {
 725                    system_message.push_str("\n\n");
 726                }
 727                system_message.push_str(&message.string_contents());
 728            }
 729        }
 730    }
 731
 732    anthropic::Request {
 733        model,
 734        messages: new_messages,
 735        max_tokens: max_output_tokens,
 736        system: if system_message.is_empty() {
 737            None
 738        } else {
 739            Some(anthropic::StringOrContents::String(system_message))
 740        },
 741        thinking: if request.thinking_allowed {
 742            match mode {
 743                AnthropicModelMode::Thinking { budget_tokens } => {
 744                    Some(anthropic::Thinking::Enabled { budget_tokens })
 745                }
 746                AnthropicModelMode::AdaptiveThinking => Some(anthropic::Thinking::Adaptive),
 747                AnthropicModelMode::Default => None,
 748            }
 749        } else {
 750            None
 751        },
 752        tools: request
 753            .tools
 754            .into_iter()
 755            .map(|tool| anthropic::Tool {
 756                name: tool.name,
 757                description: tool.description,
 758                input_schema: tool.input_schema,
 759                eager_input_streaming: tool.use_input_streaming,
 760            })
 761            .collect(),
 762        tool_choice: request.tool_choice.map(|choice| match choice {
 763            LanguageModelToolChoice::Auto => anthropic::ToolChoice::Auto,
 764            LanguageModelToolChoice::Any => anthropic::ToolChoice::Any,
 765            LanguageModelToolChoice::None => anthropic::ToolChoice::None,
 766        }),
 767        metadata: None,
 768        output_config: if request.thinking_allowed
 769            && matches!(mode, AnthropicModelMode::AdaptiveThinking)
 770        {
 771            request.thinking_effort.as_deref().and_then(|effort| {
 772                let effort = match effort {
 773                    "low" => Some(anthropic::Effort::Low),
 774                    "medium" => Some(anthropic::Effort::Medium),
 775                    "high" => Some(anthropic::Effort::High),
 776                    "max" => Some(anthropic::Effort::Max),
 777                    _ => None,
 778                };
 779                effort.map(|effort| anthropic::OutputConfig {
 780                    effort: Some(effort),
 781                })
 782            })
 783        } else {
 784            None
 785        },
 786        stop_sequences: Vec::new(),
 787        speed: request.speed.map(From::from),
 788        temperature: request.temperature.or(Some(default_temperature)),
 789        top_k: None,
 790        top_p: None,
 791    }
 792}
 793
 794pub struct AnthropicEventMapper {
 795    tool_uses_by_index: HashMap<usize, RawToolUse>,
 796    usage: Usage,
 797    stop_reason: StopReason,
 798}
 799
 800impl AnthropicEventMapper {
 801    pub fn new() -> Self {
 802        Self {
 803            tool_uses_by_index: HashMap::default(),
 804            usage: Usage::default(),
 805            stop_reason: StopReason::EndTurn,
 806        }
 807    }
 808
 809    pub fn map_stream(
 810        mut self,
 811        events: Pin<Box<dyn Send + Stream<Item = Result<Event, AnthropicError>>>>,
 812    ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>
 813    {
 814        events.flat_map(move |event| {
 815            futures::stream::iter(match event {
 816                Ok(event) => self.map_event(event),
 817                Err(error) => vec![Err(error.into())],
 818            })
 819        })
 820    }
 821
 822    pub fn map_event(
 823        &mut self,
 824        event: Event,
 825    ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {
 826        match event {
 827            Event::ContentBlockStart {
 828                index,
 829                content_block,
 830            } => match content_block {
 831                ResponseContent::Text { text } => {
 832                    vec![Ok(LanguageModelCompletionEvent::Text(text))]
 833                }
 834                ResponseContent::Thinking { thinking } => {
 835                    vec![Ok(LanguageModelCompletionEvent::Thinking {
 836                        text: thinking,
 837                        signature: None,
 838                    })]
 839                }
 840                ResponseContent::RedactedThinking { data } => {
 841                    vec![Ok(LanguageModelCompletionEvent::RedactedThinking { data })]
 842                }
 843                ResponseContent::ToolUse { id, name, .. } => {
 844                    self.tool_uses_by_index.insert(
 845                        index,
 846                        RawToolUse {
 847                            id,
 848                            name,
 849                            input_json: String::new(),
 850                        },
 851                    );
 852                    Vec::new()
 853                }
 854            },
 855            Event::ContentBlockDelta { index, delta } => match delta {
 856                ContentDelta::TextDelta { text } => {
 857                    vec![Ok(LanguageModelCompletionEvent::Text(text))]
 858                }
 859                ContentDelta::ThinkingDelta { thinking } => {
 860                    vec![Ok(LanguageModelCompletionEvent::Thinking {
 861                        text: thinking,
 862                        signature: None,
 863                    })]
 864                }
 865                ContentDelta::SignatureDelta { signature } => {
 866                    vec![Ok(LanguageModelCompletionEvent::Thinking {
 867                        text: "".to_string(),
 868                        signature: Some(signature),
 869                    })]
 870                }
 871                ContentDelta::InputJsonDelta { partial_json } => {
 872                    if let Some(tool_use) = self.tool_uses_by_index.get_mut(&index) {
 873                        tool_use.input_json.push_str(&partial_json);
 874
 875                        // Try to convert invalid (incomplete) JSON into
 876                        // valid JSON that serde can accept, e.g. by closing
 877                        // unclosed delimiters. This way, we can update the
 878                        // UI with whatever has been streamed back so far.
 879                        if let Ok(input) =
 880                            serde_json::Value::from_str(&fix_streamed_json(&tool_use.input_json))
 881                        {
 882                            return vec![Ok(LanguageModelCompletionEvent::ToolUse(
 883                                LanguageModelToolUse {
 884                                    id: tool_use.id.clone().into(),
 885                                    name: tool_use.name.clone().into(),
 886                                    is_input_complete: false,
 887                                    raw_input: tool_use.input_json.clone(),
 888                                    input,
 889                                    thought_signature: None,
 890                                },
 891                            ))];
 892                        }
 893                    }
 894                    vec![]
 895                }
 896            },
 897            Event::ContentBlockStop { index } => {
 898                if let Some(tool_use) = self.tool_uses_by_index.remove(&index) {
 899                    let input_json = tool_use.input_json.trim();
 900                    let event_result = match parse_tool_arguments(input_json) {
 901                        Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(
 902                            LanguageModelToolUse {
 903                                id: tool_use.id.into(),
 904                                name: tool_use.name.into(),
 905                                is_input_complete: true,
 906                                input,
 907                                raw_input: tool_use.input_json.clone(),
 908                                thought_signature: None,
 909                            },
 910                        )),
 911                        Err(json_parse_err) => {
 912                            Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {
 913                                id: tool_use.id.into(),
 914                                tool_name: tool_use.name.into(),
 915                                raw_input: input_json.into(),
 916                                json_parse_error: json_parse_err.to_string(),
 917                            })
 918                        }
 919                    };
 920
 921                    vec![event_result]
 922                } else {
 923                    Vec::new()
 924                }
 925            }
 926            Event::MessageStart { message } => {
 927                update_usage(&mut self.usage, &message.usage);
 928                vec![
 929                    Ok(LanguageModelCompletionEvent::UsageUpdate(convert_usage(
 930                        &self.usage,
 931                    ))),
 932                    Ok(LanguageModelCompletionEvent::StartMessage {
 933                        message_id: message.id,
 934                    }),
 935                ]
 936            }
 937            Event::MessageDelta { delta, usage } => {
 938                update_usage(&mut self.usage, &usage);
 939                if let Some(stop_reason) = delta.stop_reason.as_deref() {
 940                    self.stop_reason = match stop_reason {
 941                        "end_turn" => StopReason::EndTurn,
 942                        "max_tokens" => StopReason::MaxTokens,
 943                        "tool_use" => StopReason::ToolUse,
 944                        "refusal" => StopReason::Refusal,
 945                        _ => {
 946                            log::error!("Unexpected anthropic stop_reason: {stop_reason}");
 947                            StopReason::EndTurn
 948                        }
 949                    };
 950                }
 951                vec![Ok(LanguageModelCompletionEvent::UsageUpdate(
 952                    convert_usage(&self.usage),
 953                ))]
 954            }
 955            Event::MessageStop => {
 956                vec![Ok(LanguageModelCompletionEvent::Stop(self.stop_reason))]
 957            }
 958            Event::Error { error } => {
 959                vec![Err(error.into())]
 960            }
 961            _ => Vec::new(),
 962        }
 963    }
 964}
 965
 966struct RawToolUse {
 967    id: String,
 968    name: String,
 969    input_json: String,
 970}
 971
 972/// Updates usage data by preferring counts from `new`.
 973fn update_usage(usage: &mut Usage, new: &Usage) {
 974    if let Some(input_tokens) = new.input_tokens {
 975        usage.input_tokens = Some(input_tokens);
 976    }
 977    if let Some(output_tokens) = new.output_tokens {
 978        usage.output_tokens = Some(output_tokens);
 979    }
 980    if let Some(cache_creation_input_tokens) = new.cache_creation_input_tokens {
 981        usage.cache_creation_input_tokens = Some(cache_creation_input_tokens);
 982    }
 983    if let Some(cache_read_input_tokens) = new.cache_read_input_tokens {
 984        usage.cache_read_input_tokens = Some(cache_read_input_tokens);
 985    }
 986}
 987
 988fn convert_usage(usage: &Usage) -> language_model::TokenUsage {
 989    language_model::TokenUsage {
 990        input_tokens: usage.input_tokens.unwrap_or(0),
 991        output_tokens: usage.output_tokens.unwrap_or(0),
 992        cache_creation_input_tokens: usage.cache_creation_input_tokens.unwrap_or(0),
 993        cache_read_input_tokens: usage.cache_read_input_tokens.unwrap_or(0),
 994    }
 995}
 996
 997struct ConfigurationView {
 998    api_key_editor: Entity<InputField>,
 999    state: Entity<State>,
1000    load_credentials_task: Option<Task<()>>,
1001    target_agent: ConfigurationViewTargetAgent,
1002}
1003
1004impl ConfigurationView {
1005    const PLACEHOLDER_TEXT: &'static str = "sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
1006
1007    fn new(
1008        state: Entity<State>,
1009        target_agent: ConfigurationViewTargetAgent,
1010        window: &mut Window,
1011        cx: &mut Context<Self>,
1012    ) -> Self {
1013        cx.observe(&state, |_, _, cx| {
1014            cx.notify();
1015        })
1016        .detach();
1017
1018        let load_credentials_task = Some(cx.spawn({
1019            let state = state.clone();
1020            async move |this, cx| {
1021                let task = state.update(cx, |state, cx| state.authenticate(cx));
1022                // We don't log an error, because "not signed in" is also an error.
1023                let _ = task.await;
1024                this.update(cx, |this, cx| {
1025                    this.load_credentials_task = None;
1026                    cx.notify();
1027                })
1028                .log_err();
1029            }
1030        }));
1031
1032        Self {
1033            api_key_editor: cx.new(|cx| InputField::new(window, cx, Self::PLACEHOLDER_TEXT)),
1034            state,
1035            load_credentials_task,
1036            target_agent,
1037        }
1038    }
1039
1040    fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
1041        let api_key = self.api_key_editor.read(cx).text(cx);
1042        if api_key.is_empty() {
1043            return;
1044        }
1045
1046        // url changes can cause the editor to be displayed again
1047        self.api_key_editor
1048            .update(cx, |editor, cx| editor.set_text("", window, cx));
1049
1050        let state = self.state.clone();
1051        cx.spawn_in(window, async move |_, cx| {
1052            state
1053                .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))
1054                .await
1055        })
1056        .detach_and_log_err(cx);
1057    }
1058
1059    fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1060        self.api_key_editor
1061            .update(cx, |editor, cx| editor.set_text("", window, cx));
1062
1063        let state = self.state.clone();
1064        cx.spawn_in(window, async move |_, cx| {
1065            state
1066                .update(cx, |state, cx| state.set_api_key(None, cx))
1067                .await
1068        })
1069        .detach_and_log_err(cx);
1070    }
1071
1072    fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {
1073        !self.state.read(cx).is_authenticated()
1074    }
1075}
1076
1077impl Render for ConfigurationView {
1078    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1079        let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();
1080        let configured_card_label = if env_var_set {
1081            format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable")
1082        } else {
1083            let api_url = AnthropicLanguageModelProvider::api_url(cx);
1084            if api_url == ANTHROPIC_API_URL {
1085                "API key configured".to_string()
1086            } else {
1087                format!("API key configured for {}", api_url)
1088            }
1089        };
1090
1091        if self.load_credentials_task.is_some() {
1092            div()
1093                .child(Label::new("Loading credentials..."))
1094                .into_any_element()
1095        } else if self.should_render_editor(cx) {
1096            v_flex()
1097                .size_full()
1098                .on_action(cx.listener(Self::save_api_key))
1099                .child(Label::new(format!("To use {}, you need to add an API key. Follow these steps:", match &self.target_agent {
1100                    ConfigurationViewTargetAgent::ZedAgent => "Zed's agent with Anthropic".into(),
1101                    ConfigurationViewTargetAgent::Other(agent) => agent.clone(),
1102                })))
1103                .child(
1104                    List::new()
1105                        .child(
1106                            ListBulletItem::new("")
1107                                .child(Label::new("Create one by visiting"))
1108                                .child(ButtonLink::new("Anthropic's settings", "https://console.anthropic.com/settings/keys"))
1109                        )
1110                        .child(
1111                            ListBulletItem::new("Paste your API key below and hit enter to start using the agent")
1112                        )
1113                )
1114                .child(self.api_key_editor.clone())
1115                .child(
1116                    Label::new(
1117                        format!("You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed."),
1118                    )
1119                    .size(LabelSize::Small)
1120                    .color(Color::Muted)
1121                    .mt_0p5(),
1122                )
1123                .into_any_element()
1124        } else {
1125            ConfiguredApiCard::new(configured_card_label)
1126                .disabled(env_var_set)
1127                .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))
1128                .when(env_var_set, |this| {
1129                    this.tooltip_label(format!(
1130                    "To reset your API key, unset the {API_KEY_ENV_VAR_NAME} environment variable."
1131                ))
1132                })
1133                .into_any_element()
1134        }
1135    }
1136}
1137
1138#[cfg(test)]
1139mod tests {
1140    use super::*;
1141    use anthropic::AnthropicModelMode;
1142    use language_model::{LanguageModelRequestMessage, MessageContent};
1143
1144    #[test]
1145    fn test_cache_control_only_on_last_segment() {
1146        let request = LanguageModelRequest {
1147            messages: vec![LanguageModelRequestMessage {
1148                role: Role::User,
1149                content: vec![
1150                    MessageContent::Text("Some prompt".to_string()),
1151                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1152                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1153                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1154                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1155                ],
1156                cache: true,
1157                reasoning_details: None,
1158            }],
1159            thread_id: None,
1160            prompt_id: None,
1161            intent: None,
1162            stop: vec![],
1163            temperature: None,
1164            tools: vec![],
1165            tool_choice: None,
1166            thinking_allowed: true,
1167            thinking_effort: None,
1168            speed: None,
1169        };
1170
1171        let anthropic_request = into_anthropic(
1172            request,
1173            "claude-3-5-sonnet".to_string(),
1174            0.7,
1175            4096,
1176            AnthropicModelMode::Default,
1177        );
1178
1179        assert_eq!(anthropic_request.messages.len(), 1);
1180
1181        let message = &anthropic_request.messages[0];
1182        assert_eq!(message.content.len(), 5);
1183
1184        assert!(matches!(
1185            message.content[0],
1186            anthropic::RequestContent::Text {
1187                cache_control: None,
1188                ..
1189            }
1190        ));
1191        for i in 1..3 {
1192            assert!(matches!(
1193                message.content[i],
1194                anthropic::RequestContent::Image {
1195                    cache_control: None,
1196                    ..
1197                }
1198            ));
1199        }
1200
1201        assert!(matches!(
1202            message.content[4],
1203            anthropic::RequestContent::Image {
1204                cache_control: Some(anthropic::CacheControl {
1205                    cache_type: anthropic::CacheControlType::Ephemeral,
1206                }),
1207                ..
1208            }
1209        ));
1210    }
1211
1212    fn request_with_assistant_content(
1213        assistant_content: Vec<MessageContent>,
1214    ) -> anthropic::Request {
1215        let mut request = LanguageModelRequest {
1216            messages: vec![LanguageModelRequestMessage {
1217                role: Role::User,
1218                content: vec![MessageContent::Text("Hello".to_string())],
1219                cache: false,
1220                reasoning_details: None,
1221            }],
1222            thinking_effort: None,
1223            thread_id: None,
1224            prompt_id: None,
1225            intent: None,
1226            stop: vec![],
1227            temperature: None,
1228            tools: vec![],
1229            tool_choice: None,
1230            thinking_allowed: true,
1231            speed: None,
1232        };
1233        request.messages.push(LanguageModelRequestMessage {
1234            role: Role::Assistant,
1235            content: assistant_content,
1236            cache: false,
1237            reasoning_details: None,
1238        });
1239        into_anthropic(
1240            request,
1241            "claude-sonnet-4-5".to_string(),
1242            1.0,
1243            16000,
1244            AnthropicModelMode::Thinking {
1245                budget_tokens: Some(10000),
1246            },
1247        )
1248    }
1249
1250    #[test]
1251    fn test_unsigned_thinking_blocks_stripped() {
1252        let result = request_with_assistant_content(vec![
1253            MessageContent::Thinking {
1254                text: "Cancelled mid-think, no signature".to_string(),
1255                signature: None,
1256            },
1257            MessageContent::Text("Some response text".to_string()),
1258        ]);
1259
1260        let assistant_message = result
1261            .messages
1262            .iter()
1263            .find(|m| m.role == anthropic::Role::Assistant)
1264            .expect("assistant message should still exist");
1265
1266        assert_eq!(
1267            assistant_message.content.len(),
1268            1,
1269            "Only the text content should remain; unsigned thinking block should be stripped"
1270        );
1271        assert!(matches!(
1272            &assistant_message.content[0],
1273            anthropic::RequestContent::Text { text, .. } if text == "Some response text"
1274        ));
1275    }
1276
1277    #[test]
1278    fn test_signed_thinking_blocks_preserved() {
1279        let result = request_with_assistant_content(vec![
1280            MessageContent::Thinking {
1281                text: "Completed thinking".to_string(),
1282                signature: Some("valid-signature".to_string()),
1283            },
1284            MessageContent::Text("Response".to_string()),
1285        ]);
1286
1287        let assistant_message = result
1288            .messages
1289            .iter()
1290            .find(|m| m.role == anthropic::Role::Assistant)
1291            .expect("assistant message should exist");
1292
1293        assert_eq!(
1294            assistant_message.content.len(),
1295            2,
1296            "Both the signed thinking block and text should be preserved"
1297        );
1298        assert!(matches!(
1299            &assistant_message.content[0],
1300            anthropic::RequestContent::Thinking { thinking, signature, .. }
1301                if thinking == "Completed thinking" && signature == "valid-signature"
1302        ));
1303    }
1304
1305    #[test]
1306    fn test_only_unsigned_thinking_block_omits_entire_message() {
1307        let result = request_with_assistant_content(vec![MessageContent::Thinking {
1308            text: "Cancelled before any text or signature".to_string(),
1309            signature: None,
1310        }]);
1311
1312        let assistant_messages: Vec<_> = result
1313            .messages
1314            .iter()
1315            .filter(|m| m.role == anthropic::Role::Assistant)
1316            .collect();
1317
1318        assert_eq!(
1319            assistant_messages.len(),
1320            0,
1321            "An assistant message whose only content was an unsigned thinking block \
1322             should be omitted entirely"
1323        );
1324    }
1325}