anthropic.rs

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