anthropic.rs

   1use crate::ui::InstructionListItem;
   2use crate::{AllLanguageModelSettings, api_key::ApiKeyState};
   3use anthropic::{
   4    AnthropicError, AnthropicModelMode, ContentDelta, Event, ResponseContent, ToolResultContent,
   5    ToolResultPart, Usage,
   6};
   7use anyhow::Result;
   8use collections::{BTreeMap, HashMap};
   9use editor::{Editor, EditorElement, EditorStyle};
  10use futures::Stream;
  11use futures::{FutureExt, StreamExt, future::BoxFuture, stream::BoxStream};
  12use gpui::{AnyView, App, AsyncApp, Context, Entity, FontStyle, Task, TextStyle, WhiteSpace};
  13use http_client::HttpClient;
  14use language_model::{
  15    AuthenticateError, ConfigurationViewTargetAgent, LanguageModel,
  16    LanguageModelCacheConfiguration, LanguageModelCompletionError, LanguageModelId,
  17    LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName,
  18    LanguageModelProviderState, LanguageModelRequest, LanguageModelToolChoice,
  19    LanguageModelToolResultContent, MessageContent, RateLimiter, Role,
  20};
  21use language_model::{LanguageModelCompletionEvent, LanguageModelToolUse, StopReason};
  22use schemars::JsonSchema;
  23use serde::{Deserialize, Serialize};
  24use settings::{Settings, SettingsStore};
  25use std::pin::Pin;
  26use std::str::FromStr;
  27use std::sync::{Arc, LazyLock};
  28use strum::IntoEnumIterator;
  29use theme::ThemeSettings;
  30use ui::{Icon, IconName, List, Tooltip, prelude::*};
  31use util::ResultExt;
  32use zed_env_vars::{EnvVar, env_var};
  33
  34const PROVIDER_ID: LanguageModelProviderId = language_model::ANTHROPIC_PROVIDER_ID;
  35const PROVIDER_NAME: LanguageModelProviderName = language_model::ANTHROPIC_PROVIDER_NAME;
  36
  37#[derive(Default, Clone, Debug, PartialEq)]
  38pub struct AnthropicSettings {
  39    pub api_url: String,
  40    /// Extend Zed's list of Anthropic models.
  41    pub available_models: Vec<AvailableModel>,
  42}
  43
  44#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
  45pub struct AvailableModel {
  46    /// The model's name in the Anthropic API. e.g. claude-3-5-sonnet-latest, claude-3-opus-20240229, etc
  47    pub name: String,
  48    /// The model's name in Zed's UI, such as in the model selector dropdown menu in the assistant panel.
  49    pub display_name: Option<String>,
  50    /// The model's context window size.
  51    pub max_tokens: u64,
  52    /// A model `name` to substitute when calling tools, in case the primary model doesn't support tool calling.
  53    pub tool_override: Option<String>,
  54    /// Configuration of Anthropic's caching API.
  55    pub cache_configuration: Option<LanguageModelCacheConfiguration>,
  56    pub max_output_tokens: Option<u64>,
  57    pub default_temperature: Option<f32>,
  58    #[serde(default)]
  59    pub extra_beta_headers: Vec<String>,
  60    /// The model's mode (e.g. thinking)
  61    pub mode: Option<ModelMode>,
  62}
  63
  64#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
  65#[serde(tag = "type", rename_all = "lowercase")]
  66pub enum ModelMode {
  67    #[default]
  68    Default,
  69    Thinking {
  70        /// The maximum number of tokens to use for reasoning. Must be lower than the model's `max_output_tokens`.
  71        budget_tokens: Option<u32>,
  72    },
  73}
  74
  75impl From<ModelMode> for AnthropicModelMode {
  76    fn from(value: ModelMode) -> Self {
  77        match value {
  78            ModelMode::Default => AnthropicModelMode::Default,
  79            ModelMode::Thinking { budget_tokens } => AnthropicModelMode::Thinking { budget_tokens },
  80        }
  81    }
  82}
  83
  84impl From<AnthropicModelMode> for ModelMode {
  85    fn from(value: AnthropicModelMode) -> Self {
  86        match value {
  87            AnthropicModelMode::Default => ModelMode::Default,
  88            AnthropicModelMode::Thinking { budget_tokens } => ModelMode::Thinking { budget_tokens },
  89        }
  90    }
  91}
  92
  93pub struct AnthropicLanguageModelProvider {
  94    http_client: Arc<dyn HttpClient>,
  95    state: gpui::Entity<State>,
  96}
  97
  98const API_KEY_ENV_VAR_NAME: &str = "ANTHROPIC_API_KEY";
  99static API_KEY_ENV_VAR: LazyLock<EnvVar> = env_var!(API_KEY_ENV_VAR_NAME);
 100
 101pub struct State {
 102    api_key_state: ApiKeyState,
 103}
 104
 105impl State {
 106    fn is_authenticated(&self) -> bool {
 107        self.api_key_state.has_key()
 108    }
 109
 110    fn set_api_key(&mut self, api_key: Option<String>, cx: &mut Context<Self>) -> Task<Result<()>> {
 111        let api_url = AnthropicLanguageModelProvider::api_url(cx);
 112        self.api_key_state
 113            .store(api_url, api_key, |this| &mut this.api_key_state, cx)
 114    }
 115
 116    fn authenticate(&mut self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> {
 117        let api_url = AnthropicLanguageModelProvider::api_url(cx);
 118        self.api_key_state.load_if_needed(
 119            api_url,
 120            &API_KEY_ENV_VAR,
 121            |this| &mut this.api_key_state,
 122            cx,
 123        )
 124    }
 125}
 126
 127impl AnthropicLanguageModelProvider {
 128    pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self {
 129        let state = cx.new(|cx| {
 130            cx.observe_global::<SettingsStore>(|this: &mut State, cx| {
 131                let api_url = Self::api_url(cx);
 132                this.api_key_state.handle_url_change(
 133                    api_url,
 134                    &API_KEY_ENV_VAR,
 135                    |this| &mut this.api_key_state,
 136                    cx,
 137                );
 138                cx.notify();
 139            })
 140            .detach();
 141            State {
 142                api_key_state: ApiKeyState::new(Self::api_url(cx)),
 143            }
 144        });
 145
 146        Self { http_client, state }
 147    }
 148
 149    fn create_language_model(&self, model: anthropic::Model) -> Arc<dyn LanguageModel> {
 150        Arc::new(AnthropicModel {
 151            id: LanguageModelId::from(model.id().to_string()),
 152            model,
 153            state: self.state.clone(),
 154            http_client: self.http_client.clone(),
 155            request_limiter: RateLimiter::new(4),
 156        })
 157    }
 158
 159    fn settings(cx: &App) -> &AnthropicSettings {
 160        &AllLanguageModelSettings::get_global(cx).anthropic
 161    }
 162
 163    fn api_url(cx: &App) -> SharedString {
 164        let api_url = &Self::settings(cx).api_url;
 165        if api_url.is_empty() {
 166            anthropic::ANTHROPIC_API_URL.into()
 167        } else {
 168            SharedString::new(api_url.as_str())
 169        }
 170    }
 171}
 172
 173impl LanguageModelProviderState for AnthropicLanguageModelProvider {
 174    type ObservableEntity = State;
 175
 176    fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> {
 177        Some(self.state.clone())
 178    }
 179}
 180
 181impl LanguageModelProvider for AnthropicLanguageModelProvider {
 182    fn id(&self) -> LanguageModelProviderId {
 183        PROVIDER_ID
 184    }
 185
 186    fn name(&self) -> LanguageModelProviderName {
 187        PROVIDER_NAME
 188    }
 189
 190    fn icon(&self) -> IconName {
 191        IconName::AiAnthropic
 192    }
 193
 194    fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {
 195        Some(self.create_language_model(anthropic::Model::default()))
 196    }
 197
 198    fn default_fast_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> {
 199        Some(self.create_language_model(anthropic::Model::default_fast()))
 200    }
 201
 202    fn recommended_models(&self, _cx: &App) -> Vec<Arc<dyn LanguageModel>> {
 203        [
 204            anthropic::Model::ClaudeSonnet4,
 205            anthropic::Model::ClaudeSonnet4Thinking,
 206        ]
 207        .into_iter()
 208        .map(|model| self.create_language_model(model))
 209        .collect()
 210    }
 211
 212    fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> {
 213        let mut models = BTreeMap::default();
 214
 215        // Add base models from anthropic::Model::iter()
 216        for model in anthropic::Model::iter() {
 217            if !matches!(model, anthropic::Model::Custom { .. }) {
 218                models.insert(model.id().to_string(), model);
 219            }
 220        }
 221
 222        // Override with available models from settings
 223        for model in AllLanguageModelSettings::get_global(cx)
 224            .anthropic
 225            .available_models
 226            .iter()
 227        {
 228            models.insert(
 229                model.name.clone(),
 230                anthropic::Model::Custom {
 231                    name: model.name.clone(),
 232                    display_name: model.display_name.clone(),
 233                    max_tokens: model.max_tokens,
 234                    tool_override: model.tool_override.clone(),
 235                    cache_configuration: model.cache_configuration.as_ref().map(|config| {
 236                        anthropic::AnthropicModelCacheConfiguration {
 237                            max_cache_anchors: config.max_cache_anchors,
 238                            should_speculate: config.should_speculate,
 239                            min_total_token: config.min_total_token,
 240                        }
 241                    }),
 242                    max_output_tokens: model.max_output_tokens,
 243                    default_temperature: model.default_temperature,
 244                    extra_beta_headers: model.extra_beta_headers.clone(),
 245                    mode: model.mode.clone().unwrap_or_default().into(),
 246                },
 247            );
 248        }
 249
 250        models
 251            .into_values()
 252            .map(|model| self.create_language_model(model))
 253            .collect()
 254    }
 255
 256    fn is_authenticated(&self, cx: &App) -> bool {
 257        self.state.read(cx).is_authenticated()
 258    }
 259
 260    fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> {
 261        self.state.update(cx, |state, cx| state.authenticate(cx))
 262    }
 263
 264    fn configuration_view(
 265        &self,
 266        target_agent: ConfigurationViewTargetAgent,
 267        window: &mut Window,
 268        cx: &mut App,
 269    ) -> AnyView {
 270        cx.new(|cx| ConfigurationView::new(self.state.clone(), target_agent, window, cx))
 271            .into()
 272    }
 273
 274    fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> {
 275        self.state
 276            .update(cx, |state, cx| state.set_api_key(None, cx))
 277    }
 278}
 279
 280pub struct AnthropicModel {
 281    id: LanguageModelId,
 282    model: anthropic::Model,
 283    state: gpui::Entity<State>,
 284    http_client: Arc<dyn HttpClient>,
 285    request_limiter: RateLimiter,
 286}
 287
 288pub fn count_anthropic_tokens(
 289    request: LanguageModelRequest,
 290    cx: &App,
 291) -> BoxFuture<'static, Result<u64>> {
 292    cx.background_spawn(async move {
 293        let messages = request.messages;
 294        let mut tokens_from_images = 0;
 295        let mut string_messages = Vec::with_capacity(messages.len());
 296
 297        for message in messages {
 298            use language_model::MessageContent;
 299
 300            let mut string_contents = String::new();
 301
 302            for content in message.content {
 303                match content {
 304                    MessageContent::Text(text) => {
 305                        string_contents.push_str(&text);
 306                    }
 307                    MessageContent::Thinking { .. } => {
 308                        // Thinking blocks are not included in the input token count.
 309                    }
 310                    MessageContent::RedactedThinking(_) => {
 311                        // Thinking blocks are not included in the input token count.
 312                    }
 313                    MessageContent::Image(image) => {
 314                        tokens_from_images += image.estimate_tokens();
 315                    }
 316                    MessageContent::ToolUse(_tool_use) => {
 317                        // TODO: Estimate token usage from tool uses.
 318                    }
 319                    MessageContent::ToolResult(tool_result) => match &tool_result.content {
 320                        LanguageModelToolResultContent::Text(text) => {
 321                            string_contents.push_str(text);
 322                        }
 323                        LanguageModelToolResultContent::Image(image) => {
 324                            tokens_from_images += image.estimate_tokens();
 325                        }
 326                    },
 327                }
 328            }
 329
 330            if !string_contents.is_empty() {
 331                string_messages.push(tiktoken_rs::ChatCompletionRequestMessage {
 332                    role: match message.role {
 333                        Role::User => "user".into(),
 334                        Role::Assistant => "assistant".into(),
 335                        Role::System => "system".into(),
 336                    },
 337                    content: Some(string_contents),
 338                    name: None,
 339                    function_call: None,
 340                });
 341            }
 342        }
 343
 344        // Tiktoken doesn't yet support these models, so we manually use the
 345        // same tokenizer as GPT-4.
 346        tiktoken_rs::num_tokens_from_messages("gpt-4", &string_messages)
 347            .map(|tokens| (tokens + tokens_from_images) as u64)
 348    })
 349    .boxed()
 350}
 351
 352impl AnthropicModel {
 353    fn stream_completion(
 354        &self,
 355        request: anthropic::Request,
 356        cx: &AsyncApp,
 357    ) -> BoxFuture<
 358        'static,
 359        Result<
 360            BoxStream<'static, Result<anthropic::Event, AnthropicError>>,
 361            LanguageModelCompletionError,
 362        >,
 363    > {
 364        let http_client = self.http_client.clone();
 365
 366        let api_key_and_url = self.state.read_with(cx, |state, cx| {
 367            let api_url = AnthropicLanguageModelProvider::api_url(cx);
 368            let api_key = state.api_key_state.key(&api_url);
 369            (api_key, api_url)
 370        });
 371        let (api_key, api_url) = match api_key_and_url {
 372            Ok(api_key_and_url) => api_key_and_url,
 373            Err(err) => {
 374                return futures::future::ready(Err(err.into())).boxed();
 375            }
 376        };
 377
 378        let beta_headers = self.model.beta_headers();
 379
 380        async move {
 381            let Some(api_key) = api_key else {
 382                return Err(LanguageModelCompletionError::NoApiKey {
 383                    provider: PROVIDER_NAME,
 384                });
 385            };
 386            let request = anthropic::stream_completion(
 387                http_client.as_ref(),
 388                &api_url,
 389                &api_key,
 390                request,
 391                beta_headers,
 392            );
 393            request.await.map_err(Into::into)
 394        }
 395        .boxed()
 396    }
 397}
 398
 399impl LanguageModel for AnthropicModel {
 400    fn id(&self) -> LanguageModelId {
 401        self.id.clone()
 402    }
 403
 404    fn name(&self) -> LanguageModelName {
 405        LanguageModelName::from(self.model.display_name().to_string())
 406    }
 407
 408    fn provider_id(&self) -> LanguageModelProviderId {
 409        PROVIDER_ID
 410    }
 411
 412    fn provider_name(&self) -> LanguageModelProviderName {
 413        PROVIDER_NAME
 414    }
 415
 416    fn supports_tools(&self) -> bool {
 417        true
 418    }
 419
 420    fn supports_images(&self) -> bool {
 421        true
 422    }
 423
 424    fn supports_tool_choice(&self, choice: LanguageModelToolChoice) -> bool {
 425        match choice {
 426            LanguageModelToolChoice::Auto
 427            | LanguageModelToolChoice::Any
 428            | LanguageModelToolChoice::None => true,
 429        }
 430    }
 431
 432    fn telemetry_id(&self) -> String {
 433        format!("anthropic/{}", self.model.id())
 434    }
 435
 436    fn api_key(&self, cx: &App) -> Option<String> {
 437        self.state.read_with(cx, |state, cx| {
 438            let api_url = AnthropicLanguageModelProvider::api_url(cx);
 439            state.api_key_state.key(&api_url).map(|key| key.to_string())
 440        })
 441    }
 442
 443    fn max_token_count(&self) -> u64 {
 444        self.model.max_token_count()
 445    }
 446
 447    fn max_output_tokens(&self) -> Option<u64> {
 448        Some(self.model.max_output_tokens())
 449    }
 450
 451    fn count_tokens(
 452        &self,
 453        request: LanguageModelRequest,
 454        cx: &App,
 455    ) -> BoxFuture<'static, Result<u64>> {
 456        count_anthropic_tokens(request, cx)
 457    }
 458
 459    fn stream_completion(
 460        &self,
 461        request: LanguageModelRequest,
 462        cx: &AsyncApp,
 463    ) -> BoxFuture<
 464        'static,
 465        Result<
 466            BoxStream<'static, Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>,
 467            LanguageModelCompletionError,
 468        >,
 469    > {
 470        let request = into_anthropic(
 471            request,
 472            self.model.request_id().into(),
 473            self.model.default_temperature(),
 474            self.model.max_output_tokens(),
 475            self.model.mode(),
 476        );
 477        let request = self.stream_completion(request, cx);
 478        let future = self.request_limiter.stream(async move {
 479            let response = request.await?;
 480            Ok(AnthropicEventMapper::new().map_stream(response))
 481        });
 482        async move { Ok(future.await?.boxed()) }.boxed()
 483    }
 484
 485    fn cache_configuration(&self) -> Option<LanguageModelCacheConfiguration> {
 486        self.model
 487            .cache_configuration()
 488            .map(|config| LanguageModelCacheConfiguration {
 489                max_cache_anchors: config.max_cache_anchors,
 490                should_speculate: config.should_speculate,
 491                min_total_token: config.min_total_token,
 492            })
 493    }
 494}
 495
 496pub fn into_anthropic(
 497    request: LanguageModelRequest,
 498    model: String,
 499    default_temperature: f32,
 500    max_output_tokens: u64,
 501    mode: AnthropicModelMode,
 502) -> anthropic::Request {
 503    let mut new_messages: Vec<anthropic::Message> = Vec::new();
 504    let mut system_message = String::new();
 505
 506    for message in request.messages {
 507        if message.contents_empty() {
 508            continue;
 509        }
 510
 511        match message.role {
 512            Role::User | Role::Assistant => {
 513                let mut anthropic_message_content: Vec<anthropic::RequestContent> = message
 514                    .content
 515                    .into_iter()
 516                    .filter_map(|content| match content {
 517                        MessageContent::Text(text) => {
 518                            let text = if text.chars().last().is_some_and(|c| c.is_whitespace()) {
 519                                text.trim_end().to_string()
 520                            } else {
 521                                text
 522                            };
 523                            if !text.is_empty() {
 524                                Some(anthropic::RequestContent::Text {
 525                                    text,
 526                                    cache_control: None,
 527                                })
 528                            } else {
 529                                None
 530                            }
 531                        }
 532                        MessageContent::Thinking {
 533                            text: thinking,
 534                            signature,
 535                        } => {
 536                            if !thinking.is_empty() {
 537                                Some(anthropic::RequestContent::Thinking {
 538                                    thinking,
 539                                    signature: signature.unwrap_or_default(),
 540                                    cache_control: None,
 541                                })
 542                            } else {
 543                                None
 544                            }
 545                        }
 546                        MessageContent::RedactedThinking(data) => {
 547                            if !data.is_empty() {
 548                                Some(anthropic::RequestContent::RedactedThinking { data })
 549                            } else {
 550                                None
 551                            }
 552                        }
 553                        MessageContent::Image(image) => Some(anthropic::RequestContent::Image {
 554                            source: anthropic::ImageSource {
 555                                source_type: "base64".to_string(),
 556                                media_type: "image/png".to_string(),
 557                                data: image.source.to_string(),
 558                            },
 559                            cache_control: None,
 560                        }),
 561                        MessageContent::ToolUse(tool_use) => {
 562                            Some(anthropic::RequestContent::ToolUse {
 563                                id: tool_use.id.to_string(),
 564                                name: tool_use.name.to_string(),
 565                                input: tool_use.input,
 566                                cache_control: None,
 567                            })
 568                        }
 569                        MessageContent::ToolResult(tool_result) => {
 570                            Some(anthropic::RequestContent::ToolResult {
 571                                tool_use_id: tool_result.tool_use_id.to_string(),
 572                                is_error: tool_result.is_error,
 573                                content: match tool_result.content {
 574                                    LanguageModelToolResultContent::Text(text) => {
 575                                        ToolResultContent::Plain(text.to_string())
 576                                    }
 577                                    LanguageModelToolResultContent::Image(image) => {
 578                                        ToolResultContent::Multipart(vec![ToolResultPart::Image {
 579                                            source: anthropic::ImageSource {
 580                                                source_type: "base64".to_string(),
 581                                                media_type: "image/png".to_string(),
 582                                                data: image.source.to_string(),
 583                                            },
 584                                        }])
 585                                    }
 586                                },
 587                                cache_control: None,
 588                            })
 589                        }
 590                    })
 591                    .collect();
 592                let anthropic_role = match message.role {
 593                    Role::User => anthropic::Role::User,
 594                    Role::Assistant => anthropic::Role::Assistant,
 595                    Role::System => unreachable!("System role should never occur here"),
 596                };
 597                if let Some(last_message) = new_messages.last_mut()
 598                    && last_message.role == anthropic_role
 599                {
 600                    last_message.content.extend(anthropic_message_content);
 601                    continue;
 602                }
 603
 604                // Mark the last segment of the message as cached
 605                if message.cache {
 606                    let cache_control_value = Some(anthropic::CacheControl {
 607                        cache_type: anthropic::CacheControlType::Ephemeral,
 608                    });
 609                    for message_content in anthropic_message_content.iter_mut().rev() {
 610                        match message_content {
 611                            anthropic::RequestContent::RedactedThinking { .. } => {
 612                                // Caching is not possible, fallback to next message
 613                            }
 614                            anthropic::RequestContent::Text { cache_control, .. }
 615                            | anthropic::RequestContent::Thinking { cache_control, .. }
 616                            | anthropic::RequestContent::Image { cache_control, .. }
 617                            | anthropic::RequestContent::ToolUse { cache_control, .. }
 618                            | anthropic::RequestContent::ToolResult { cache_control, .. } => {
 619                                *cache_control = cache_control_value;
 620                                break;
 621                            }
 622                        }
 623                    }
 624                }
 625
 626                new_messages.push(anthropic::Message {
 627                    role: anthropic_role,
 628                    content: anthropic_message_content,
 629                });
 630            }
 631            Role::System => {
 632                if !system_message.is_empty() {
 633                    system_message.push_str("\n\n");
 634                }
 635                system_message.push_str(&message.string_contents());
 636            }
 637        }
 638    }
 639
 640    anthropic::Request {
 641        model,
 642        messages: new_messages,
 643        max_tokens: max_output_tokens,
 644        system: if system_message.is_empty() {
 645            None
 646        } else {
 647            Some(anthropic::StringOrContents::String(system_message))
 648        },
 649        thinking: if request.thinking_allowed
 650            && let AnthropicModelMode::Thinking { budget_tokens } = mode
 651        {
 652            Some(anthropic::Thinking::Enabled { budget_tokens })
 653        } else {
 654            None
 655        },
 656        tools: request
 657            .tools
 658            .into_iter()
 659            .map(|tool| anthropic::Tool {
 660                name: tool.name,
 661                description: tool.description,
 662                input_schema: tool.input_schema,
 663            })
 664            .collect(),
 665        tool_choice: request.tool_choice.map(|choice| match choice {
 666            LanguageModelToolChoice::Auto => anthropic::ToolChoice::Auto,
 667            LanguageModelToolChoice::Any => anthropic::ToolChoice::Any,
 668            LanguageModelToolChoice::None => anthropic::ToolChoice::None,
 669        }),
 670        metadata: None,
 671        stop_sequences: Vec::new(),
 672        temperature: request.temperature.or(Some(default_temperature)),
 673        top_k: None,
 674        top_p: None,
 675    }
 676}
 677
 678pub struct AnthropicEventMapper {
 679    tool_uses_by_index: HashMap<usize, RawToolUse>,
 680    usage: Usage,
 681    stop_reason: StopReason,
 682}
 683
 684impl AnthropicEventMapper {
 685    pub fn new() -> Self {
 686        Self {
 687            tool_uses_by_index: HashMap::default(),
 688            usage: Usage::default(),
 689            stop_reason: StopReason::EndTurn,
 690        }
 691    }
 692
 693    pub fn map_stream(
 694        mut self,
 695        events: Pin<Box<dyn Send + Stream<Item = Result<Event, AnthropicError>>>>,
 696    ) -> impl Stream<Item = Result<LanguageModelCompletionEvent, LanguageModelCompletionError>>
 697    {
 698        events.flat_map(move |event| {
 699            futures::stream::iter(match event {
 700                Ok(event) => self.map_event(event),
 701                Err(error) => vec![Err(error.into())],
 702            })
 703        })
 704    }
 705
 706    pub fn map_event(
 707        &mut self,
 708        event: Event,
 709    ) -> Vec<Result<LanguageModelCompletionEvent, LanguageModelCompletionError>> {
 710        match event {
 711            Event::ContentBlockStart {
 712                index,
 713                content_block,
 714            } => match content_block {
 715                ResponseContent::Text { text } => {
 716                    vec![Ok(LanguageModelCompletionEvent::Text(text))]
 717                }
 718                ResponseContent::Thinking { thinking } => {
 719                    vec![Ok(LanguageModelCompletionEvent::Thinking {
 720                        text: thinking,
 721                        signature: None,
 722                    })]
 723                }
 724                ResponseContent::RedactedThinking { data } => {
 725                    vec![Ok(LanguageModelCompletionEvent::RedactedThinking { data })]
 726                }
 727                ResponseContent::ToolUse { id, name, .. } => {
 728                    self.tool_uses_by_index.insert(
 729                        index,
 730                        RawToolUse {
 731                            id,
 732                            name,
 733                            input_json: String::new(),
 734                        },
 735                    );
 736                    Vec::new()
 737                }
 738            },
 739            Event::ContentBlockDelta { index, delta } => match delta {
 740                ContentDelta::TextDelta { text } => {
 741                    vec![Ok(LanguageModelCompletionEvent::Text(text))]
 742                }
 743                ContentDelta::ThinkingDelta { thinking } => {
 744                    vec![Ok(LanguageModelCompletionEvent::Thinking {
 745                        text: thinking,
 746                        signature: None,
 747                    })]
 748                }
 749                ContentDelta::SignatureDelta { signature } => {
 750                    vec![Ok(LanguageModelCompletionEvent::Thinking {
 751                        text: "".to_string(),
 752                        signature: Some(signature),
 753                    })]
 754                }
 755                ContentDelta::InputJsonDelta { partial_json } => {
 756                    if let Some(tool_use) = self.tool_uses_by_index.get_mut(&index) {
 757                        tool_use.input_json.push_str(&partial_json);
 758
 759                        // Try to convert invalid (incomplete) JSON into
 760                        // valid JSON that serde can accept, e.g. by closing
 761                        // unclosed delimiters. This way, we can update the
 762                        // UI with whatever has been streamed back so far.
 763                        if let Ok(input) = serde_json::Value::from_str(
 764                            &partial_json_fixer::fix_json(&tool_use.input_json),
 765                        ) {
 766                            return vec![Ok(LanguageModelCompletionEvent::ToolUse(
 767                                LanguageModelToolUse {
 768                                    id: tool_use.id.clone().into(),
 769                                    name: tool_use.name.clone().into(),
 770                                    is_input_complete: false,
 771                                    raw_input: tool_use.input_json.clone(),
 772                                    input,
 773                                },
 774                            ))];
 775                        }
 776                    }
 777                    vec![]
 778                }
 779            },
 780            Event::ContentBlockStop { index } => {
 781                if let Some(tool_use) = self.tool_uses_by_index.remove(&index) {
 782                    let input_json = tool_use.input_json.trim();
 783                    let input_value = if input_json.is_empty() {
 784                        Ok(serde_json::Value::Object(serde_json::Map::default()))
 785                    } else {
 786                        serde_json::Value::from_str(input_json)
 787                    };
 788                    let event_result = match input_value {
 789                        Ok(input) => Ok(LanguageModelCompletionEvent::ToolUse(
 790                            LanguageModelToolUse {
 791                                id: tool_use.id.into(),
 792                                name: tool_use.name.into(),
 793                                is_input_complete: true,
 794                                input,
 795                                raw_input: tool_use.input_json.clone(),
 796                            },
 797                        )),
 798                        Err(json_parse_err) => {
 799                            Ok(LanguageModelCompletionEvent::ToolUseJsonParseError {
 800                                id: tool_use.id.into(),
 801                                tool_name: tool_use.name.into(),
 802                                raw_input: input_json.into(),
 803                                json_parse_error: json_parse_err.to_string(),
 804                            })
 805                        }
 806                    };
 807
 808                    vec![event_result]
 809                } else {
 810                    Vec::new()
 811                }
 812            }
 813            Event::MessageStart { message } => {
 814                update_usage(&mut self.usage, &message.usage);
 815                vec![
 816                    Ok(LanguageModelCompletionEvent::UsageUpdate(convert_usage(
 817                        &self.usage,
 818                    ))),
 819                    Ok(LanguageModelCompletionEvent::StartMessage {
 820                        message_id: message.id,
 821                    }),
 822                ]
 823            }
 824            Event::MessageDelta { delta, usage } => {
 825                update_usage(&mut self.usage, &usage);
 826                if let Some(stop_reason) = delta.stop_reason.as_deref() {
 827                    self.stop_reason = match stop_reason {
 828                        "end_turn" => StopReason::EndTurn,
 829                        "max_tokens" => StopReason::MaxTokens,
 830                        "tool_use" => StopReason::ToolUse,
 831                        "refusal" => StopReason::Refusal,
 832                        _ => {
 833                            log::error!("Unexpected anthropic stop_reason: {stop_reason}");
 834                            StopReason::EndTurn
 835                        }
 836                    };
 837                }
 838                vec![Ok(LanguageModelCompletionEvent::UsageUpdate(
 839                    convert_usage(&self.usage),
 840                ))]
 841            }
 842            Event::MessageStop => {
 843                vec![Ok(LanguageModelCompletionEvent::Stop(self.stop_reason))]
 844            }
 845            Event::Error { error } => {
 846                vec![Err(error.into())]
 847            }
 848            _ => Vec::new(),
 849        }
 850    }
 851}
 852
 853struct RawToolUse {
 854    id: String,
 855    name: String,
 856    input_json: String,
 857}
 858
 859/// Updates usage data by preferring counts from `new`.
 860fn update_usage(usage: &mut Usage, new: &Usage) {
 861    if let Some(input_tokens) = new.input_tokens {
 862        usage.input_tokens = Some(input_tokens);
 863    }
 864    if let Some(output_tokens) = new.output_tokens {
 865        usage.output_tokens = Some(output_tokens);
 866    }
 867    if let Some(cache_creation_input_tokens) = new.cache_creation_input_tokens {
 868        usage.cache_creation_input_tokens = Some(cache_creation_input_tokens);
 869    }
 870    if let Some(cache_read_input_tokens) = new.cache_read_input_tokens {
 871        usage.cache_read_input_tokens = Some(cache_read_input_tokens);
 872    }
 873}
 874
 875fn convert_usage(usage: &Usage) -> language_model::TokenUsage {
 876    language_model::TokenUsage {
 877        input_tokens: usage.input_tokens.unwrap_or(0),
 878        output_tokens: usage.output_tokens.unwrap_or(0),
 879        cache_creation_input_tokens: usage.cache_creation_input_tokens.unwrap_or(0),
 880        cache_read_input_tokens: usage.cache_read_input_tokens.unwrap_or(0),
 881    }
 882}
 883
 884struct ConfigurationView {
 885    api_key_editor: Entity<Editor>,
 886    state: gpui::Entity<State>,
 887    load_credentials_task: Option<Task<()>>,
 888    target_agent: ConfigurationViewTargetAgent,
 889}
 890
 891impl ConfigurationView {
 892    const PLACEHOLDER_TEXT: &'static str = "sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
 893
 894    fn new(
 895        state: gpui::Entity<State>,
 896        target_agent: ConfigurationViewTargetAgent,
 897        window: &mut Window,
 898        cx: &mut Context<Self>,
 899    ) -> Self {
 900        cx.observe(&state, |_, _, cx| {
 901            cx.notify();
 902        })
 903        .detach();
 904
 905        let load_credentials_task = Some(cx.spawn({
 906            let state = state.clone();
 907            async move |this, cx| {
 908                if let Some(task) = state
 909                    .update(cx, |state, cx| state.authenticate(cx))
 910                    .log_err()
 911                {
 912                    // We don't log an error, because "not signed in" is also an error.
 913                    let _ = task.await;
 914                }
 915                this.update(cx, |this, cx| {
 916                    this.load_credentials_task = None;
 917                    cx.notify();
 918                })
 919                .log_err();
 920            }
 921        }));
 922
 923        Self {
 924            api_key_editor: cx.new(|cx| {
 925                let mut editor = Editor::single_line(window, cx);
 926                editor.set_placeholder_text(Self::PLACEHOLDER_TEXT, window, cx);
 927                editor
 928            }),
 929            state,
 930            load_credentials_task,
 931            target_agent,
 932        }
 933    }
 934
 935    fn save_api_key(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
 936        let api_key = self.api_key_editor.read(cx).text(cx);
 937        if api_key.is_empty() {
 938            return;
 939        }
 940
 941        let state = self.state.clone();
 942        cx.spawn_in(window, async move |_, cx| {
 943            state
 944                .update(cx, |state, cx| state.set_api_key(Some(api_key), cx))?
 945                .await
 946        })
 947        .detach_and_log_err(cx);
 948    }
 949
 950    fn reset_api_key(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 951        self.api_key_editor
 952            .update(cx, |editor, cx| editor.set_text("", window, cx));
 953
 954        let state = self.state.clone();
 955        cx.spawn_in(window, async move |_, cx| {
 956            state
 957                .update(cx, |state, cx| state.set_api_key(None, cx))?
 958                .await
 959        })
 960        .detach_and_log_err(cx);
 961    }
 962
 963    fn render_api_key_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
 964        let settings = ThemeSettings::get_global(cx);
 965        let text_style = TextStyle {
 966            color: cx.theme().colors().text,
 967            font_family: settings.ui_font.family.clone(),
 968            font_features: settings.ui_font.features.clone(),
 969            font_fallbacks: settings.ui_font.fallbacks.clone(),
 970            font_size: rems(0.875).into(),
 971            font_weight: settings.ui_font.weight,
 972            font_style: FontStyle::Normal,
 973            line_height: relative(1.3),
 974            white_space: WhiteSpace::Normal,
 975            ..Default::default()
 976        };
 977        EditorElement::new(
 978            &self.api_key_editor,
 979            EditorStyle {
 980                background: cx.theme().colors().editor_background,
 981                local_player: cx.theme().players().local(),
 982                text: text_style,
 983                ..Default::default()
 984            },
 985        )
 986    }
 987
 988    fn should_render_editor(&self, cx: &mut Context<Self>) -> bool {
 989        !self.state.read(cx).is_authenticated()
 990    }
 991}
 992
 993impl Render for ConfigurationView {
 994    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 995        let env_var_set = self.state.read(cx).api_key_state.is_from_env_var();
 996
 997        if self.load_credentials_task.is_some() {
 998            div().child(Label::new("Loading credentials...")).into_any()
 999        } else if self.should_render_editor(cx) {
1000            v_flex()
1001                .size_full()
1002                .on_action(cx.listener(Self::save_api_key))
1003                .child(Label::new(format!("To use {}, you need to add an API key. Follow these steps:", match &self.target_agent {
1004                    ConfigurationViewTargetAgent::ZedAgent => "Zed's agent with Anthropic".into(),
1005                    ConfigurationViewTargetAgent::Other(agent) => agent.clone(),
1006                })))
1007                .child(
1008                    List::new()
1009                        .child(
1010                            InstructionListItem::new(
1011                                "Create one by visiting",
1012                                Some("Anthropic's settings"),
1013                                Some("https://console.anthropic.com/settings/keys")
1014                            )
1015                        )
1016                        .child(
1017                            InstructionListItem::text_only("Paste your API key below and hit enter to start using the agent")
1018                        )
1019                )
1020                .child(
1021                    h_flex()
1022                        .w_full()
1023                        .my_2()
1024                        .px_2()
1025                        .py_1()
1026                        .bg(cx.theme().colors().editor_background)
1027                        .border_1()
1028                        .border_color(cx.theme().colors().border)
1029                        .rounded_sm()
1030                        .child(self.render_api_key_editor(cx)),
1031                )
1032                .child(
1033                    Label::new(
1034                        format!("You can also assign the {API_KEY_ENV_VAR_NAME} environment variable and restart Zed."),
1035                    )
1036                    .size(LabelSize::Small)
1037                    .color(Color::Muted),
1038                )
1039                .into_any()
1040        } else {
1041            h_flex()
1042                .mt_1()
1043                .p_1()
1044                .justify_between()
1045                .rounded_md()
1046                .border_1()
1047                .border_color(cx.theme().colors().border)
1048                .bg(cx.theme().colors().background)
1049                .child(
1050                    h_flex()
1051                        .gap_1()
1052                        .child(Icon::new(IconName::Check).color(Color::Success))
1053                        .child(Label::new(if env_var_set {
1054                            format!("API key set in {API_KEY_ENV_VAR_NAME} environment variable.")
1055                        } else {
1056                            "API key configured.".to_string()
1057                        })),
1058                )
1059                .child(
1060                    Button::new("reset-key", "Reset Key")
1061                        .label_size(LabelSize::Small)
1062                        .icon(Some(IconName::Trash))
1063                        .icon_size(IconSize::Small)
1064                        .icon_position(IconPosition::Start)
1065                        .disabled(env_var_set)
1066                        .when(env_var_set, |this| {
1067                            this.tooltip(Tooltip::text(format!("To reset your API key, unset the {API_KEY_ENV_VAR_NAME} environment variable.")))
1068                        })
1069                        .on_click(cx.listener(|this, _, window, cx| this.reset_api_key(window, cx))),
1070                )
1071                .into_any()
1072        }
1073    }
1074}
1075
1076#[cfg(test)]
1077mod tests {
1078    use super::*;
1079    use anthropic::AnthropicModelMode;
1080    use language_model::{LanguageModelRequestMessage, MessageContent};
1081
1082    #[test]
1083    fn test_cache_control_only_on_last_segment() {
1084        let request = LanguageModelRequest {
1085            messages: vec![LanguageModelRequestMessage {
1086                role: Role::User,
1087                content: vec![
1088                    MessageContent::Text("Some prompt".to_string()),
1089                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1090                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1091                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1092                    MessageContent::Image(language_model::LanguageModelImage::empty()),
1093                ],
1094                cache: true,
1095            }],
1096            thread_id: None,
1097            prompt_id: None,
1098            intent: None,
1099            mode: None,
1100            stop: vec![],
1101            temperature: None,
1102            tools: vec![],
1103            tool_choice: None,
1104            thinking_allowed: true,
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}