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