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