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