anthropic.rs

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