anthropic.rs

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