anthropic.rs

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