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