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
  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) -> IconOrSvg {
 129        IconOrSvg::Icon(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 (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        });
 451
 452        let beta_headers = self.model.beta_headers();
 453
 454        async move {
 455            let Some(api_key) = api_key else {
 456                return Err(LanguageModelCompletionError::NoApiKey {
 457                    provider: PROVIDER_NAME,
 458                });
 459            };
 460            let request = anthropic::stream_completion(
 461                http_client.as_ref(),
 462                &api_url,
 463                &api_key,
 464                request,
 465                beta_headers,
 466            );
 467            request.await.map_err(Into::into)
 468        }
 469        .boxed()
 470    }
 471}
 472
 473impl LanguageModel for AnthropicModel {
 474    fn id(&self) -> LanguageModelId {
 475        self.id.clone()
 476    }
 477
 478    fn name(&self) -> LanguageModelName {
 479        LanguageModelName::from(self.model.display_name().to_string())
 480    }
 481
 482    fn provider_id(&self) -> LanguageModelProviderId {
 483        PROVIDER_ID
 484    }
 485
 486    fn provider_name(&self) -> LanguageModelProviderName {
 487        PROVIDER_NAME
 488    }
 489
 490    fn supports_tools(&self) -> bool {
 491        true
 492    }
 493
 494    fn supports_images(&self) -> bool {
 495        true
 496    }
 497
 498    fn supports_streaming_tools(&self) -> bool {
 499        true
 500    }
 501
 502    fn supports_tool_choice(&self, choice: LanguageModelToolChoice) -> bool {
 503        match choice {
 504            LanguageModelToolChoice::Auto
 505            | LanguageModelToolChoice::Any
 506            | LanguageModelToolChoice::None => true,
 507        }
 508    }
 509
 510    fn telemetry_id(&self) -> String {
 511        format!("anthropic/{}", self.model.id())
 512    }
 513
 514    fn api_key(&self, cx: &App) -> Option<String> {
 515        self.state.read_with(cx, |state, cx| {
 516            let api_url = AnthropicLanguageModelProvider::api_url(cx);
 517            state.api_key_state.key(&api_url).map(|key| key.to_string())
 518        })
 519    }
 520
 521    fn max_token_count(&self) -> u64 {
 522        self.model.max_token_count()
 523    }
 524
 525    fn max_output_tokens(&self) -> Option<u64> {
 526        Some(self.model.max_output_tokens())
 527    }
 528
 529    fn count_tokens(
 530        &self,
 531        request: LanguageModelRequest,
 532        cx: &App,
 533    ) -> BoxFuture<'static, Result<u64>> {
 534        let http_client = self.http_client.clone();
 535        let model_id = self.model.request_id().to_string();
 536        let mode = self.model.mode();
 537
 538        let (api_key, api_url) = self.state.read_with(cx, |state, cx| {
 539            let api_url = AnthropicLanguageModelProvider::api_url(cx);
 540            (
 541                state.api_key_state.key(&api_url).map(|k| k.to_string()),
 542                api_url.to_string(),
 543            )
 544        });
 545
 546        async move {
 547            // If no API key, fall back to tiktoken estimation
 548            let Some(api_key) = api_key else {
 549                return count_anthropic_tokens_with_tiktoken(request);
 550            };
 551
 552            let count_request =
 553                into_anthropic_count_tokens_request(request.clone(), model_id, mode);
 554
 555            match anthropic::count_tokens(http_client.as_ref(), &api_url, &api_key, count_request)
 556                .await
 557            {
 558                Ok(response) => Ok(response.input_tokens),
 559                Err(err) => {
 560                    log::error!(
 561                        "Anthropic count_tokens API failed, falling back to tiktoken: {err:?}"
 562                    );
 563                    count_anthropic_tokens_with_tiktoken(request)
 564                }
 565            }
 566        }
 567        .boxed()
 568    }
 569
 570    fn stream_completion(
 571        &self,
 572        request: LanguageModelRequest,
 573        cx: &AsyncApp,
 574    ) -> BoxFuture<
 575        'static,
 576        Result<
 577            BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
 578            LanguageModelCompletionError,
 579        >,
 580    > {
 581        let request = into_anthropic(
 582            request,
 583            self.model.request_id().into(),
 584            self.model.default_temperature(),
 585            self.model.max_output_tokens(),
 586            self.model.mode(),
 587        );
 588        let request = self.stream_completion(request, cx);
 589        let future = self.request_limiter.stream(async move {
 590            let response = request.await?;
 591            Ok(AnthropicEventMapper::new().map_stream(response))
 592        });
 593        async move { Ok(future.await?.boxed()) }.boxed()
 594    }
 595
 596    fn cache_configuration(&self) -> Option<LanguageModelCacheConfiguration> {
 597        self.model
 598            .cache_configuration()
 599            .map(|config| LanguageModelCacheConfiguration {
 600                max_cache_anchors: config.max_cache_anchors,
 601                should_speculate: config.should_speculate,
 602                min_total_token: config.min_total_token,
 603            })
 604    }
 605}
 606
 607pub fn into_anthropic(
 608    request: LanguageModelRequest,
 609    model: String,
 610    default_temperature: f32,
 611    max_output_tokens: u64,
 612    mode: AnthropicModelMode,
 613) -> anthropic::Request {
 614    let mut new_messages: Vec<anthropic::Message> = Vec::new();
 615    let mut system_message = String::new();
 616
 617    for message in request.messages {
 618        if message.contents_empty() {
 619            continue;
 620        }
 621
 622        match message.role {
 623            Role::User | Role::Assistant => {
 624                let mut anthropic_message_content: Vec<anthropic::RequestContent> = message
 625                    .content
 626                    .into_iter()
 627                    .filter_map(|content| match content {
 628                        MessageContent::Text(text) => {
 629                            let text = if text.chars().last().is_some_and(|c| c.is_whitespace()) {
 630                                text.trim_end().to_string()
 631                            } else {
 632                                text
 633                            };
 634                            if !text.is_empty() {
 635                                Some(anthropic::RequestContent::Text {
 636                                    text,
 637                                    cache_control: None,
 638                                })
 639                            } else {
 640                                None
 641                            }
 642                        }
 643                        MessageContent::Thinking {
 644                            text: thinking,
 645                            signature,
 646                        } => {
 647                            if !thinking.is_empty() {
 648                                Some(anthropic::RequestContent::Thinking {
 649                                    thinking,
 650                                    signature: signature.unwrap_or_default(),
 651                                    cache_control: None,
 652                                })
 653                            } else {
 654                                None
 655                            }
 656                        }
 657                        MessageContent::RedactedThinking(data) => {
 658                            if !data.is_empty() {
 659                                Some(anthropic::RequestContent::RedactedThinking { data })
 660                            } else {
 661                                None
 662                            }
 663                        }
 664                        MessageContent::Image(image) => Some(anthropic::RequestContent::Image {
 665                            source: anthropic::ImageSource {
 666                                source_type: "base64".to_string(),
 667                                media_type: "image/png".to_string(),
 668                                data: image.source.to_string(),
 669                            },
 670                            cache_control: None,
 671                        }),
 672                        MessageContent::ToolUse(tool_use) => {
 673                            Some(anthropic::RequestContent::ToolUse {
 674                                id: tool_use.id.to_string(),
 675                                name: tool_use.name.to_string(),
 676                                input: tool_use.input,
 677                                cache_control: None,
 678                            })
 679                        }
 680                        MessageContent::ToolResult(tool_result) => {
 681                            Some(anthropic::RequestContent::ToolResult {
 682                                tool_use_id: tool_result.tool_use_id.to_string(),
 683                                is_error: tool_result.is_error,
 684                                content: match tool_result.content {
 685                                    LanguageModelToolResultContent::Text(text) => {
 686                                        ToolResultContent::Plain(text.to_string())
 687                                    }
 688                                    LanguageModelToolResultContent::Image(image) => {
 689                                        ToolResultContent::Multipart(vec![ToolResultPart::Image {
 690                                            source: anthropic::ImageSource {
 691                                                source_type: "base64".to_string(),
 692                                                media_type: "image/png".to_string(),
 693                                                data: image.source.to_string(),
 694                                            },
 695                                        }])
 696                                    }
 697                                },
 698                                cache_control: None,
 699                            })
 700                        }
 701                    })
 702                    .collect();
 703                let anthropic_role = match message.role {
 704                    Role::User => anthropic::Role::User,
 705                    Role::Assistant => anthropic::Role::Assistant,
 706                    Role::System => unreachable!("System role should never occur here"),
 707                };
 708                if let Some(last_message) = new_messages.last_mut()
 709                    && last_message.role == anthropic_role
 710                {
 711                    last_message.content.extend(anthropic_message_content);
 712                    continue;
 713                }
 714
 715                // Mark the last segment of the message as cached
 716                if message.cache {
 717                    let cache_control_value = Some(anthropic::CacheControl {
 718                        cache_type: anthropic::CacheControlType::Ephemeral,
 719                    });
 720                    for message_content in anthropic_message_content.iter_mut().rev() {
 721                        match message_content {
 722                            anthropic::RequestContent::RedactedThinking { .. } => {
 723                                // Caching is not possible, fallback to next message
 724                            }
 725                            anthropic::RequestContent::Text { cache_control, .. }
 726                            | anthropic::RequestContent::Thinking { cache_control, .. }
 727                            | anthropic::RequestContent::Image { cache_control, .. }
 728                            | anthropic::RequestContent::ToolUse { cache_control, .. }
 729                            | anthropic::RequestContent::ToolResult { cache_control, .. } => {
 730                                *cache_control = cache_control_value;
 731                                break;
 732                            }
 733                        }
 734                    }
 735                }
 736
 737                new_messages.push(anthropic::Message {
 738                    role: anthropic_role,
 739                    content: anthropic_message_content,
 740                });
 741            }
 742            Role::System => {
 743                if !system_message.is_empty() {
 744                    system_message.push_str("\n\n");
 745                }
 746                system_message.push_str(&message.string_contents());
 747            }
 748        }
 749    }
 750
 751    anthropic::Request {
 752        model,
 753        messages: new_messages,
 754        max_tokens: max_output_tokens,
 755        system: if system_message.is_empty() {
 756            None
 757        } else {
 758            Some(anthropic::StringOrContents::String(system_message))
 759        },
 760        thinking: if request.thinking_allowed
 761            && let AnthropicModelMode::Thinking { budget_tokens } = mode
 762        {
 763            Some(anthropic::Thinking::Enabled { budget_tokens })
 764        } else {
 765            None
 766        },
 767        tools: request
 768            .tools
 769            .into_iter()
 770            .map(|tool| anthropic::Tool {
 771                name: tool.name,
 772                description: tool.description,
 773                input_schema: tool.input_schema,
 774            })
 775            .collect(),
 776        tool_choice: request.tool_choice.map(|choice| match choice {
 777            LanguageModelToolChoice::Auto => anthropic::ToolChoice::Auto,
 778            LanguageModelToolChoice::Any => anthropic::ToolChoice::Any,
 779            LanguageModelToolChoice::None => anthropic::ToolChoice::None,
 780        }),
 781        metadata: None,
 782        stop_sequences: Vec::new(),
 783        temperature: request.temperature.or(Some(default_temperature)),
 784        top_k: None,
 785        top_p: None,
 786    }
 787}
 788
 789pub struct AnthropicEventMapper {
 790    tool_uses_by_index: HashMap<usize, RawToolUse>,
 791    usage: Usage,
 792    stop_reason: StopReason,
 793}
 794
 795impl AnthropicEventMapper {
 796    pub fn new() -> Self {
 797        Self {
 798            tool_uses_by_index: HashMap::default(),
 799            usage: Usage::default(),
 800            stop_reason: StopReason::EndTurn,
 801        }
 802    }
 803
 804    pub fn map_stream(
 805        mut self,
 806        events: Pin<Box<dyn Send + Stream<Item = Result<Event, AnthropicError>>>>,
 807    ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>
 808    {
 809        events.flat_map(move |event| {
 810            futures::stream::iter(match event {
 811                Ok(event) => self.map_event(event),
 812                Err(error) => vec![Err(error.into())],
 813            })
 814        })
 815    }
 816
 817    pub fn map_event(
 818        &mut self,
 819        event: Event,
 820    ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {
 821        match event {
 822            Event::ContentBlockStart {
 823                index,
 824                content_block,
 825            } => match content_block {
 826                ResponseContent::Text { text } => {
 827                    vec![Ok(LanguageModelCompletionEvent::Text(text))]
 828                }
 829                ResponseContent::Thinking { thinking } => {
 830                    vec![Ok(LanguageModelCompletionEvent::Thinking {
 831                        text: thinking,
 832                        signature: None,
 833                    })]
 834                }
 835                ResponseContent::RedactedThinking { data } => {
 836                    vec![Ok(LanguageModelCompletionEvent::RedactedThinking { data })]
 837                }
 838                ResponseContent::ToolUse { id, name, .. } => {
 839                    self.tool_uses_by_index.insert(
 840                        index,
 841                        RawToolUse {
 842                            id,
 843                            name,
 844                            input_json: String::new(),
 845                        },
 846                    );
 847                    Vec::new()
 848                }
 849            },
 850            Event::ContentBlockDelta { index, delta } => match delta {
 851                ContentDelta::TextDelta { text } => {
 852                    vec![Ok(LanguageModelCompletionEvent::Text(text))]
 853                }
 854                ContentDelta::ThinkingDelta { thinking } => {
 855                    vec![Ok(LanguageModelCompletionEvent::Thinking {
 856                        text: thinking,
 857                        signature: None,
 858                    })]
 859                }
 860                ContentDelta::SignatureDelta { signature } => {
 861                    vec![Ok(LanguageModelCompletionEvent::Thinking {
 862                        text: "".to_string(),
 863                        signature: Some(signature),
 864                    })]
 865                }
 866                ContentDelta::InputJsonDelta { partial_json } => {
 867                    if let Some(tool_use) = self.tool_uses_by_index.get_mut(&index) {
 868                        tool_use.input_json.push_str(&partial_json);
 869
 870                        // Try to convert invalid (incomplete) JSON into
 871                        // valid JSON that serde can accept, e.g. by closing
 872                        // unclosed delimiters. This way, we can update the
 873                        // UI with whatever has been streamed back so far.
 874                        if let Ok(input) = serde_json::Value::from_str(
 875                            &partial_json_fixer::fix_json(&tool_use.input_json),
 876                        ) {
 877                            return vec![Ok(LanguageModelCompletionEvent::ToolUse(
 878                                LanguageModelToolUse {
 879                                    id: tool_use.id.clone().into(),
 880                                    name: tool_use.name.clone().into(),
 881                                    is_input_complete: false,
 882                                    raw_input: tool_use.input_json.clone(),
 883                                    input,
 884                                    thought_signature: None,
 885                                },
 886                            ))];
 887                        }
 888                    }
 889                    vec![]
 890                }
 891            },
 892            Event::ContentBlockStop { index } => {
 893                if let Some(tool_use) = self.tool_uses_by_index.remove(&index) {
 894                    let input_json = tool_use.input_json.trim();
 895                    let input_value = if input_json.is_empty() {
 896                        Ok(serde_json::Value::Object(serde_json::Map::default()))
 897                    } else {
 898                        serde_json::Value::from_str(input_json)
 899                    };
 900                    let event_result = match input_value {
 901                        Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(
 902                            LanguageModelToolUse {
 903                                id: tool_use.id.into(),
 904                                name: tool_use.name.into(),
 905                                is_input_complete: true,
 906                                input,
 907                                raw_input: tool_use.input_json.clone(),
 908                                thought_signature: None,
 909                            },
 910                        )),
 911                        Err(json_parse_err) => {
 912                            Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {
 913                                id: tool_use.id.into(),
 914                                tool_name: tool_use.name.into(),
 915                                raw_input: input_json.into(),
 916                                json_parse_error: json_parse_err.to_string(),
 917                            })
 918                        }
 919                    };
 920
 921                    vec![event_result]
 922                } else {
 923                    Vec::new()
 924                }
 925            }
 926            Event::MessageStart { message } => {
 927                update_usage(&mut self.usage, &message.usage);
 928                vec![
 929                    Ok(LanguageModelCompletionEvent::UsageUpdate(convert_usage(
 930                        &self.usage,
 931                    ))),
 932                    Ok(LanguageModelCompletionEvent::StartMessage {
 933                        message_id: message.id,
 934                    }),
 935                ]
 936            }
 937            Event::MessageDelta { delta, usage } => {
 938                update_usage(&mut self.usage, &usage);
 939                if let Some(stop_reason) = delta.stop_reason.as_deref() {
 940                    self.stop_reason = match stop_reason {
 941                        "end_turn" => StopReason::EndTurn,
 942                        "max_tokens" => StopReason::MaxTokens,
 943                        "tool_use" => StopReason::ToolUse,
 944                        "refusal" => StopReason::Refusal,
 945                        _ => {
 946                            log::error!("Unexpected anthropic stop_reason: {stop_reason}");
 947                            StopReason::EndTurn
 948                        }
 949                    };
 950                }
 951                vec![Ok(LanguageModelCompletionEvent::UsageUpdate(
 952                    convert_usage(&self.usage),
 953                ))]
 954            }
 955            Event::MessageStop => {
 956                vec![Ok(LanguageModelCompletionEvent::Stop(self.stop_reason))]
 957            }
 958            Event::Error { error } => {
 959                vec![Err(error.into())]
 960            }
 961            _ => Vec::new(),
 962        }
 963    }
 964}
 965
 966struct RawToolUse {
 967    id: String,
 968    name: String,
 969    input_json: String,
 970}
 971
 972/// Updates usage data by preferring counts from `new`.
 973fn update_usage(usage: &mut Usage, new: &Usage) {
 974    if let Some(input_tokens) = new.input_tokens {
 975        usage.input_tokens = Some(input_tokens);
 976    }
 977    if let Some(output_tokens) = new.output_tokens {
 978        usage.output_tokens = Some(output_tokens);
 979    }
 980    if let Some(cache_creation_input_tokens) = new.cache_creation_input_tokens {
 981        usage.cache_creation_input_tokens = Some(cache_creation_input_tokens);
 982    }
 983    if let Some(cache_read_input_tokens) = new.cache_read_input_tokens {
 984        usage.cache_read_input_tokens = Some(cache_read_input_tokens);
 985    }
 986}
 987
 988fn convert_usage(usage: &Usage) -> language_model::TokenUsage {
 989    language_model::TokenUsage {
 990        input_tokens: usage.input_tokens.unwrap_or(0),
 991        output_tokens: usage.output_tokens.unwrap_or(0),
 992        cache_creation_input_tokens: usage.cache_creation_input_tokens.unwrap_or(0),
 993        cache_read_input_tokens: usage.cache_read_input_tokens.unwrap_or(0),
 994    }
 995}
 996
 997struct ConfigurationView {
 998    api_key_editor: Entity<InputField>,
 999    state: Entity<State>,
1000    load_credentials_task: Option<Task<()>>,
1001    target_agent: ConfigurationViewTargetAgent,
1002}
1003
1004impl ConfigurationView {
1005    const PLACEHOLDER_TEXT: &'static str = "sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
1006
1007    fn new(
1008        state: Entity<State>,
1009        target_agent: ConfigurationViewTargetAgent,
1010        window: &mut Window,
1011        cx: &mut Context<Self>,
1012    ) -> Self {
1013        cx.observe(&state, |_, _, cx| {
1014            cx.notify();
1015        })
1016        .detach();
1017
1018        let load_credentials_task = Some(cx.spawn({
1019            let state = state.clone();
1020            async move |this, cx| {
1021                let task = state.update(cx, |state, cx| state.authenticate(cx));
1022                // We don't log an error, because "not signed in" is also an error.
1023                let _ = task.await;
1024                this.update(cx, |this, cx| {
1025                    this.load_credentials_task = None;
1026                    cx.notify();
1027                })
1028                .log_err();
1029            }
1030        }));
1031
1032        Self {
1033            api_key_editor: cx.new(|cx| InputField::new(window, cx, Self::PLACEHOLDER_TEXT)),
1034            state,
1035            load_credentials_task,
1036            target_agent,
1037        }
1038    }
1039
1040    fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
1041        let api_key = self.api_key_editor.read(cx).text(cx);
1042        if api_key.is_empty() {
1043            return;
1044        }
1045
1046        // url changes can cause the editor to be displayed again
1047        self.api_key_editor
1048            .update(cx, |editor, cx| editor.set_text("", window, cx));
1049
1050        let state = self.state.clone();
1051        cx.spawn_in(window, async move |_, cx| {
1052            state
1053                .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))
1054                .await
1055        })
1056        .detach_and_log_err(cx);
1057    }
1058
1059    fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1060        self.api_key_editor
1061            .update(cx, |editor, cx| editor.set_text("", window, cx));
1062
1063        let state = self.state.clone();
1064        cx.spawn_in(window, async move |_, cx| {
1065            state
1066                .update(cx, |state, cx| state.set_api_key(None, cx))
1067                .await
1068        })
1069        .detach_and_log_err(cx);
1070    }
1071
1072    fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {
1073        !self.state.read(cx).is_authenticated()
1074    }
1075}
1076
1077impl Render for ConfigurationView {
1078    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1079        let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();
1080        let configured_card_label = if env_var_set {
1081            format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable")
1082        } else {
1083            let api_url = AnthropicLanguageModelProvider::api_url(cx);
1084            if api_url == ANTHROPIC_API_URL {
1085                "API key configured".to_string()
1086            } else {
1087                format!("API key configured for {}", api_url)
1088            }
1089        };
1090
1091        if self.load_credentials_task.is_some() {
1092            div()
1093                .child(Label::new("Loading credentials..."))
1094                .into_any_element()
1095        } else if self.should_render_editor(cx) {
1096            v_flex()
1097                .size_full()
1098                .on_action(cx.listener(Self::save_api_key))
1099                .child(Label::new(format!("To use {}, you need to add an API key. Follow these steps:", match &self.target_agent {
1100                    ConfigurationViewTargetAgent::ZedAgent => "Zed's agent with Anthropic".into(),
1101                    ConfigurationViewTargetAgent::Other(agent) => agent.clone(),
1102                })))
1103                .child(
1104                    List::new()
1105                        .child(
1106                            ListBulletItem::new("")
1107                                .child(Label::new("Create one by visiting"))
1108                                .child(ButtonLink::new("Anthropic's settings", "https://console.anthropic.com/settings/keys"))
1109                        )
1110                        .child(
1111                            ListBulletItem::new("Paste your API key below and hit enter to start using the agent")
1112                        )
1113                )
1114                .child(self.api_key_editor.clone())
1115                .child(
1116                    Label::new(
1117                        format!("You can also set the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed."),
1118                    )
1119                    .size(LabelSize::Small)
1120                    .color(Color::Muted)
1121                    .mt_0p5(),
1122                )
1123                .into_any_element()
1124        } else {
1125            ConfiguredApiCard::new(configured_card_label)
1126                .disabled(env_var_set)
1127                .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx)))
1128                .when(env_var_set, |this| {
1129                    this.tooltip_label(format!(
1130                    "To reset your API key, unset the {API_KEY_ENV_VAR_NAME} environment variable."
1131                ))
1132                })
1133                .into_any_element()
1134        }
1135    }
1136}
1137
1138#[cfg(test)]
1139mod tests {
1140    use super::*;
1141    use anthropic::AnthropicModelMode;
1142    use language_model::{LanguageModelRequestMessage, MessageContent};
1143
1144    #[test]
1145    fn test_cache_control_only_on_last_segment() {
1146        let request = LanguageModelRequest {
1147            messages: vec![LanguageModelRequestMessage {
1148                role: Role::User,
1149                content: vec![
1150                    MessageContent::Text("Some prompt".to_string()),
1151                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1152                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1153                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1154                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1155                ],
1156                cache: true,
1157                reasoning_details: None,
1158            }],
1159            thread_id: None,
1160            prompt_id: None,
1161            intent: None,
1162            mode: None,
1163            stop: vec![],
1164            temperature: None,
1165            tools: vec![],
1166            tool_choice: None,
1167            thinking_allowed: true,
1168        };
1169
1170        let anthropic_request = into_anthropic(
1171            request,
1172            "claude-3-5-sonnet".to_string(),
1173            0.7,
1174            4096,
1175            AnthropicModelMode::Default,
1176        );
1177
1178        assert_eq!(anthropic_request.messages.len(), 1);
1179
1180        let message = &anthropic_request.messages[0];
1181        assert_eq!(message.content.len(), 5);
1182
1183        assert!(matches!(
1184            message.content[0],
1185            anthropic::RequestContent::Text {
1186                cache_control: None,
1187                ..
1188            }
1189        ));
1190        for i in 1..3 {
1191            assert!(matches!(
1192                message.content[i],
1193                anthropic::RequestContent::Image {
1194                    cache_control: None,
1195                    ..
1196                }
1197            ));
1198        }
1199
1200        assert!(matches!(
1201            message.content[4],
1202            anthropic::RequestContent::Image {
1203                cache_control: Some(anthropic::CacheControl {
1204                    cache_type: anthropic::CacheControlType::Ephemeral,
1205                }),
1206                ..
1207            }
1208        ));
1209    }
1210}