anthropic.rs

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