use anthropic::{AnthropicModelMode, parse_prompt_too_long};use anyhow::{Context as _, Result, anyhow};use client::{Client, UserStore, zed_urls};use futures::{ AsyncBufReadExt, FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream,};use google_ai::GoogleModelMode;use gpui::{ AnyElement, AnyView, App, AsyncApp, Context, Entity, SemanticVersion, Subscription, Task,};use http_client::{AsyncBody, HttpClient, Method, Response, StatusCode};use language_model::{ AuthenticateError, LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionError, LanguageModelId, LanguageModelKnownError, LanguageModelName, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelProviderTosView, LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolSchemaFormat, ModelRequestLimitReachedError, RateLimiter, RequestUsage, ZED_CLOUD_PROVIDER_ID,};use language_model::{ LanguageModelCompletionEvent, LanguageModelProvider, LlmApiToken, PaymentRequiredError, RefreshLlmTokenListener,};use proto::Plan;use release_channel::AppVersion;use schemars::JsonSchema;use serde::{Deserialize, Serialize, de::DeserializeOwned};use settings::SettingsStore;use smol::Timer;use smol::io::{AsyncReadExt, BufReader};use std::pin::Pin;use std::str::FromStr as _;use std::sync::Arc;use std::time::Duration;use thiserror::Error;use ui::{TintColor, prelude::*};use util::{ResultExt as _, maybe};use zed_llm_client::{ CLIENT_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, CURRENT_PLAN_HEADER_NAME, CompletionBody, CompletionRequestStatus, CountTokensBody, CountTokensResponse, EXPIRED_LLM_TOKEN_HEADER_NAME, ListModelsResponse, MODEL_REQUESTS_RESOURCE_HEADER_VALUE, SERVER_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME, TOOL_USE_LIMIT_REACHED_HEADER_NAME, ZED_VERSION_HEADER_NAME,}; use crate::provider::anthropic::{AnthropicEventMapper, count_anthropic_tokens, into_anthropic};use crate::provider::google::{GoogleEventMapper, into_google};use crate::provider::open_ai::{OpenAiEventMapper, count_open_ai_tokens, into_open_ai}; pub const PROVIDER_NAME: &str = "Zed"; #[derive(Default, Clone, Debug, PartialEq)]pub struct ZedDotDevSettings { pub available_models: Vec<AvailableModel>,} #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]#[serde(rename_all = "lowercase")]pub enum AvailableProvider { Anthropic, OpenAi, Google,} #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]pub struct AvailableModel { /// The provider of the language model. pub provider: AvailableProvider, /// The model's name in the provider's API. e.g. claude-3-5-sonnet-20240620 pub name: String, /// The name displayed in the UI, such as in the assistant panel model dropdown menu. pub display_name: Option<String>, /// The size of the context window, indicating the maximum number of tokens the model can process. pub max_tokens: usize, /// The maximum number of output tokens allowed by the model. pub max_output_tokens: Option<u32>, /// The maximum number of completion tokens allowed by the model (o1-* only) pub max_completion_tokens: Option<u32>, /// Override this model with a different Anthropic model for tool calls. pub tool_override: Option<String>, /// Indicates whether this custom model supports caching. pub cache_configuration: Option<LanguageModelCacheConfiguration>, /// The default temperature to use for this model. pub default_temperature: Option<f32>, /// Any extra beta headers to provide when using the model. #[serde(default)] pub extra_beta_headers: Vec<String>, /// The model's mode (e.g. thinking) pub mode: Option<ModelMode>,} #[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]#[serde(tag = "type", rename_all = "lowercase")]pub enum ModelMode { #[default] Default, Thinking { /// The maximum number of tokens to use for reasoning. Must be lower than the model's `max_output_tokens`. budget_tokens: Option<u32>, },} impl From<ModelMode> for AnthropicModelMode { fn from(value: ModelMode) -> Self { match value { ModelMode::Default => AnthropicModelMode::Default, ModelMode::Thinking { budget_tokens } => AnthropicModelMode::Thinking { budget_tokens }, } }} pub struct CloudLanguageModelProvider { client: Arc<Client>, state: gpui::Entity<State>, _maintain_client_status: Task<()>,} pub struct State { client: Arc<Client>, llm_api_token: LlmApiToken, user_store: Entity<UserStore>, status: client::Status, accept_terms: Option<Task<Result<()>>>, models: Vec<Arc<zed_llm_client::LanguageModel>>, default_model: Option<Arc<zed_llm_client::LanguageModel>>, default_fast_model: Option<Arc<zed_llm_client::LanguageModel>>, recommended_models: Vec<Arc<zed_llm_client::LanguageModel>>, _fetch_models_task: Task<()>, _settings_subscription: Subscription, _llm_token_subscription: Subscription,} impl State { fn new( client: Arc<Client>, user_store: Entity<UserStore>, status: client::Status, cx: &mut Context<Self>, ) -> Self { let refresh_llm_token_listener = RefreshLlmTokenListener::global(cx); Self { client: client.clone(), llm_api_token: LlmApiToken::default(), user_store, status, accept_terms: None, models: Vec::new(), default_model: None, default_fast_model: None, recommended_models: Vec::new(), _fetch_models_task: cx.spawn(async move |this, cx| { maybe!(async move { let (client, llm_api_token) = this .read_with(cx, |this, _cx| (client.clone(), this.llm_api_token.clone()))?; loop { let status = this.read_with(cx, |this, _cx| this.status)?; if matches!(status, client::Status::Connected { .. }) { break; } cx.background_executor() .timer(Duration::from_millis(100)) .await; } let response = Self::fetch_models(client, llm_api_token).await?; cx.update(|cx| { this.update(cx, |this, cx| { let mut models = Vec::new(); for model in response.models { models.push(Arc::new(model.clone())); // Right now we represent thinking variants of models as separate models on the client, // so we need to insert variants for any model that supports thinking. if model.supports_thinking { models.push(Arc::new(zed_llm_client::LanguageModel { id: zed_llm_client::LanguageModelId( format!("{}-thinking", model.id).into(), ), display_name: format!("{} Thinking", model.display_name), ..model })); } } this.default_model = models .iter() .find(|model| model.id == response.default_model) .cloned(); this.default_fast_model = models .iter() .find(|model| model.id == response.default_fast_model) .cloned(); this.recommended_models = response .recommended_models .iter() .filter_map(|id| models.iter().find(|model| &model.id == id)) .cloned() .collect(); this.models = models; cx.notify(); }) })??; anyhow::Ok(()) }) .await .context("failed to fetch Zed models") .log_err(); }), _settings_subscription: cx.observe_global::<SettingsStore>(|_, cx| { cx.notify(); }), _llm_token_subscription: cx.subscribe( &refresh_llm_token_listener, |this, _listener, _event, cx| { let client = this.client.clone(); let llm_api_token = this.llm_api_token.clone(); cx.spawn(async move |_this, _cx| { llm_api_token.refresh(&client).await?; anyhow::Ok(()) }) .detach_and_log_err(cx); }, ), } } fn is_signed_out(&self) -> bool { self.status.is_signed_out() } fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> { let client = self.client.clone(); cx.spawn(async move |state, cx| { client .authenticate_and_connect(true, &cx) .await .into_response()?; state.update(cx, |_, cx| cx.notify()) }) } fn has_accepted_terms_of_service(&self, cx: &App) -> bool { self.user_store .read(cx) .current_user_has_accepted_terms() .unwrap_or(false) } fn accept_terms_of_service(&mut self, cx: &mut Context<Self>) { let user_store = self.user_store.clone(); self.accept_terms = Some(cx.spawn(async move |this, cx| { let _ = user_store .update(cx, |store, cx| store.accept_terms_of_service(cx))? .await; this.update(cx, |this, cx| { this.accept_terms = None; cx.notify() }) })); } async fn fetch_models( client: Arc<Client>, llm_api_token: LlmApiToken, ) -> Result<ListModelsResponse> { let http_client = &client.http_client(); let token = llm_api_token.acquire(&client).await?; let request = http_client::Request::builder() .method(Method::GET) .uri(http_client.build_zed_llm_url("/models", &[])?.as_ref()) .header("Authorization", format!("Bearer {token}")) .body(AsyncBody::empty())?; let mut response = http_client .send(request) .await .context("failed to send list models request")?; if response.status().is_success() { let mut body = String::new(); response.body_mut().read_to_string(&mut body).await?; return Ok(serde_json::from_str(&body)?); } else { let mut body = String::new(); response.body_mut().read_to_string(&mut body).await?; anyhow::bail!( "error listing models.\nStatus: {:?}\nBody: {body}", response.status(), ); } }} impl CloudLanguageModelProvider { pub fn new(user_store: Entity<UserStore>, client: Arc<Client>, cx: &mut App) -> Self { let mut status_rx = client.status(); let status = *status_rx.borrow(); let state = cx.new(|cx| State::new(client.clone(), user_store.clone(), status, cx)); let state_ref = state.downgrade(); let maintain_client_status = cx.spawn(async move |cx| { while let Some(status) = status_rx.next().await { if let Some(this) = state_ref.upgrade() { _ = this.update(cx, |this, cx| { if this.status != status { this.status = status; cx.notify(); } }); } else { break; } } }); Self { client, state: state.clone(), _maintain_client_status: maintain_client_status, } } fn create_language_model( &self, model: Arc<zed_llm_client::LanguageModel>, llm_api_token: LlmApiToken, ) -> Arc<dyn LanguageModel> { Arc::new(CloudLanguageModel { id: LanguageModelId(SharedString::from(model.id.0.clone())), model, llm_api_token: llm_api_token.clone(), client: self.client.clone(), request_limiter: RateLimiter::new(4), }) }} impl LanguageModelProviderState for CloudLanguageModelProvider { type ObservableEntity = State; fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> { Some(self.state.clone()) }} impl LanguageModelProvider for CloudLanguageModelProvider { fn id(&self) -> LanguageModelProviderId { LanguageModelProviderId(ZED_CLOUD_PROVIDER_ID.into()) } fn name(&self) -> LanguageModelProviderName { LanguageModelProviderName(PROVIDER_NAME.into()) } fn icon(&self) -> IconName { IconName::AiZed } fn default_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> { let default_model = self.state.read(cx).default_model.clone()?; let llm_api_token = self.state.read(cx).llm_api_token.clone(); Some(self.create_language_model(default_model, llm_api_token)) } fn default_fast_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> { let default_fast_model = self.state.read(cx).default_fast_model.clone()?; let llm_api_token = self.state.read(cx).llm_api_token.clone(); Some(self.create_language_model(default_fast_model, llm_api_token)) } fn recommended_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> { let llm_api_token = self.state.read(cx).llm_api_token.clone(); self.state .read(cx) .recommended_models .iter() .cloned() .map(|model| self.create_language_model(model, llm_api_token.clone())) .collect() } fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> { let llm_api_token = self.state.read(cx).llm_api_token.clone(); self.state .read(cx) .models .iter() .cloned() .map(|model| self.create_language_model(model, llm_api_token.clone())) .collect() } fn is_authenticated(&self, cx: &App) -> bool { !self.state.read(cx).is_signed_out() } fn authenticate(&self, _cx: &mut App) -> Task<Result<(), AuthenticateError>> { Task::ready(Ok(())) } fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView { cx.new(|_| ConfigurationView { state: self.state.clone(), }) .into() } fn must_accept_terms(&self, cx: &App) -> bool { !self.state.read(cx).has_accepted_terms_of_service(cx) } fn render_accept_terms( &self, view: LanguageModelProviderTosView, cx: &mut App, ) -> Option<AnyElement> { render_accept_terms(self.state.clone(), view, cx) } fn reset_credentials(&self, _cx: &mut App) -> Task<Result<()>> { Task::ready(Ok(())) }} fn render_accept_terms( state: Entity<State>, view_kind: LanguageModelProviderTosView, cx: &mut App,) -> Option<AnyElement> { if state.read(cx).has_accepted_terms_of_service(cx) { return None; } let accept_terms_disabled = state.read(cx).accept_terms.is_some(); let thread_fresh_start = matches!(view_kind, LanguageModelProviderTosView::ThreadFreshStart); let thread_empty_state = matches!(view_kind, LanguageModelProviderTosView::ThreadtEmptyState); let terms_button = Button::new("terms_of_service", "Terms of Service") .style(ButtonStyle::Subtle) .icon(IconName::ArrowUpRight) .icon_color(Color::Muted) .icon_size(IconSize::XSmall) .when(thread_empty_state, |this| this.label_size(LabelSize::Small)) .on_click(move |_, _window, cx| cx.open_url("https://zed.dev/terms-of-service")); let button_container = h_flex().child( Button::new("accept_terms", "I accept the Terms of Service") .when(!thread_empty_state, |this| { this.full_width() .style(ButtonStyle::Tinted(TintColor::Accent)) .icon(IconName::Check) .icon_position(IconPosition::Start) .icon_size(IconSize::Small) }) .when(thread_empty_state, |this| { this.style(ButtonStyle::Tinted(TintColor::Warning)) .label_size(LabelSize::Small) }) .disabled(accept_terms_disabled) .on_click({ let state = state.downgrade(); move |_, _window, cx| { state .update(cx, |state, cx| state.accept_terms_of_service(cx)) .ok(); } }), ); let form = if thread_empty_state { h_flex() .w_full() .flex_wrap() .justify_between() .child( h_flex() .child( Label::new("To start using Zed AI, please read and accept the") .size(LabelSize::Small), ) .child(terms_button), ) .child(button_container) } else { v_flex() .w_full() .gap_2() .child( h_flex() .flex_wrap() .when(thread_fresh_start, |this| this.justify_center()) .child(Label::new( "To start using Zed AI, please read and accept the",Lines 1–500 of 1141. Load more
use anthropic::{AnthropicModelMode, parse_prompt_too_long}; use anyhow::{Context as _, Result, anyhow}; use client::{Client, UserStore, zed_urls}; use futures::{ AsyncBufReadExt, FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream, }; use google_ai::GoogleModelMode; use gpui::{ AnyElement, AnyView, App, AsyncApp, Context, Entity, SemanticVersion, Subscription, Task, }; use http_client::{AsyncBody, HttpClient, Method, Response, StatusCode}; use language_model::{ AuthenticateError, LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionError, LanguageModelId, LanguageModelKnownError, LanguageModelName, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelProviderTosView, LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolSchemaFormat, ModelRequestLimitReachedError, RateLimiter, RequestUsage, ZED_CLOUD_PROVIDER_ID, }; use language_model::{ LanguageModelCompletionEvent, LanguageModelProvider, LlmApiToken, PaymentRequiredError, RefreshLlmTokenListener, }; use proto::Plan; use release_channel::AppVersion; use schemars::JsonSchema; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use settings::SettingsStore; use smol::Timer; use smol::io::{AsyncReadExt, BufReader}; use std::pin::Pin; use std::str::FromStr as _; use std::sync::Arc; use std::time::Duration; use thiserror::Error; use ui::{TintColor, prelude::*}; use util::{ResultExt as _, maybe}; use zed_llm_client::{ CLIENT_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, CURRENT_PLAN_HEADER_NAME, CompletionBody, CompletionRequestStatus, CountTokensBody, CountTokensResponse, EXPIRED_LLM_TOKEN_HEADER_NAME, ListModelsResponse, MODEL_REQUESTS_RESOURCE_HEADER_VALUE, SERVER_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME, TOOL_USE_LIMIT_REACHED_HEADER_NAME, ZED_VERSION_HEADER_NAME, }; use crate::provider::anthropic::{AnthropicEventMapper, count_anthropic_tokens, into_anthropic}; use crate::provider::google::{GoogleEventMapper, into_google}; use crate::provider::open_ai::{OpenAiEventMapper, count_open_ai_tokens, into_open_ai}; pub const PROVIDER_NAME: &str = "Zed"; #[derive(Default, Clone, Debug, PartialEq)] pub struct ZedDotDevSettings { pub available_models: Vec<AvailableModel>, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "lowercase")] pub enum AvailableProvider { Anthropic, OpenAi, Google, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct AvailableModel { /// The provider of the language model. pub provider: AvailableProvider, /// The model's name in the provider's API. e.g. claude-3-5-sonnet-20240620 pub name: String, /// The name displayed in the UI, such as in the assistant panel model dropdown menu. pub display_name: Option<String>, /// The size of the context window, indicating the maximum number of tokens the model can process. pub max_tokens: usize, /// The maximum number of output tokens allowed by the model. pub max_output_tokens: Option<u32>, /// The maximum number of completion tokens allowed by the model (o1-* only) pub max_completion_tokens: Option<u32>, /// Override this model with a different Anthropic model for tool calls. pub tool_override: Option<String>, /// Indicates whether this custom model supports caching. pub cache_configuration: Option<LanguageModelCacheConfiguration>, /// The default temperature to use for this model. pub default_temperature: Option<f32>, /// Any extra beta headers to provide when using the model. #[serde(default)] pub extra_beta_headers: Vec<String>, /// The model's mode (e.g. thinking) pub mode: Option<ModelMode>, } #[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] #[serde(tag = "type", rename_all = "lowercase")] pub enum ModelMode { #[default] Default, Thinking { /// The maximum number of tokens to use for reasoning. Must be lower than the model's `max_output_tokens`. budget_tokens: Option<u32>, }, } impl From<ModelMode> for AnthropicModelMode { fn from(value: ModelMode) -> Self { match value { ModelMode::Default => AnthropicModelMode::Default, ModelMode::Thinking { budget_tokens } => AnthropicModelMode::Thinking { budget_tokens }, } } } pub struct CloudLanguageModelProvider { client: Arc<Client>, state: gpui::Entity<State>, _maintain_client_status: Task<()>, } pub struct State { client: Arc<Client>, llm_api_token: LlmApiToken, user_store: Entity<UserStore>, status: client::Status, accept_terms: Option<Task<Result<()>>>, models: Vec<Arc<zed_llm_client::LanguageModel>>, default_model: Option<Arc<zed_llm_client::LanguageModel>>, default_fast_model: Option<Arc<zed_llm_client::LanguageModel>>, recommended_models: Vec<Arc<zed_llm_client::LanguageModel>>, _fetch_models_task: Task<()>, _settings_subscription: Subscription, _llm_token_subscription: Subscription, } impl State { fn new( client: Arc<Client>, user_store: Entity<UserStore>, status: client::Status, cx: &mut Context<Self>, ) -> Self { let refresh_llm_token_listener = RefreshLlmTokenListener::global(cx); Self { client: client.clone(), llm_api_token: LlmApiToken::default(), user_store, status, accept_terms: None, models: Vec::new(), default_model: None, default_fast_model: None, recommended_models: Vec::new(), _fetch_models_task: cx.spawn(async move |this, cx| { maybe!(async move { let (client, llm_api_token) = this .read_with(cx, |this, _cx| (client.clone(), this.llm_api_token.clone()))?; loop { let status = this.read_with(cx, |this, _cx| this.status)?; if matches!(status, client::Status::Connected { .. }) { break; } cx.background_executor() .timer(Duration::from_millis(100)) .await; } let response = Self::fetch_models(client, llm_api_token).await?; cx.update(|cx| { this.update(cx, |this, cx| { let mut models = Vec::new(); for model in response.models { models.push(Arc::new(model.clone())); // Right now we represent thinking variants of models as separate models on the client, // so we need to insert variants for any model that supports thinking. if model.supports_thinking { models.push(Arc::new(zed_llm_client::LanguageModel { id: zed_llm_client::LanguageModelId( format!("{}-thinking", model.id).into(), ), display_name: format!("{} Thinking", model.display_name), ..model })); } } this.default_model = models .iter() .find(|model| model.id == response.default_model) .cloned(); this.default_fast_model = models .iter() .find(|model| model.id == response.default_fast_model) .cloned(); this.recommended_models = response .recommended_models .iter() .filter_map(|id| models.iter().find(|model| &model.id == id)) .cloned() .collect(); this.models = models; cx.notify(); }) })??; anyhow::Ok(()) }) .await .context("failed to fetch Zed models") .log_err(); }), _settings_subscription: cx.observe_global::<SettingsStore>(|_, cx| { cx.notify(); }), _llm_token_subscription: cx.subscribe( &refresh_llm_token_listener, |this, _listener, _event, cx| { let client = this.client.clone(); let llm_api_token = this.llm_api_token.clone(); cx.spawn(async move |_this, _cx| { llm_api_token.refresh(&client).await?; anyhow::Ok(()) }) .detach_and_log_err(cx); }, ), } } fn is_signed_out(&self) -> bool { self.status.is_signed_out() } fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> { let client = self.client.clone(); cx.spawn(async move |state, cx| { client .authenticate_and_connect(true, &cx) .await .into_response()?; state.update(cx, |_, cx| cx.notify()) }) } fn has_accepted_terms_of_service(&self, cx: &App) -> bool { self.user_store .read(cx) .current_user_has_accepted_terms() .unwrap_or(false) } fn accept_terms_of_service(&mut self, cx: &mut Context<Self>) { let user_store = self.user_store.clone(); self.accept_terms = Some(cx.spawn(async move |this, cx| { let _ = user_store .update(cx, |store, cx| store.accept_terms_of_service(cx))? .await; this.update(cx, |this, cx| { this.accept_terms = None; cx.notify() }) })); } async fn fetch_models( client: Arc<Client>, llm_api_token: LlmApiToken, ) -> Result<ListModelsResponse> { let http_client = &client.http_client(); let token = llm_api_token.acquire(&client).await?; let request = http_client::Request::builder() .method(Method::GET) .uri(http_client.build_zed_llm_url("/models", &[])?.as_ref()) .header("Authorization", format!("Bearer {token}")) .body(AsyncBody::empty())?; let mut response = http_client .send(request) .await .context("failed to send list models request")?; if response.status().is_success() { let mut body = String::new(); response.body_mut().read_to_string(&mut body).await?; return Ok(serde_json::from_str(&body)?); } else { let mut body = String::new(); response.body_mut().read_to_string(&mut body).await?; anyhow::bail!( "error listing models.\nStatus: {:?}\nBody: {body}", response.status(), ); } } } impl CloudLanguageModelProvider { pub fn new(user_store: Entity<UserStore>, client: Arc<Client>, cx: &mut App) -> Self { let mut status_rx = client.status(); let status = *status_rx.borrow(); let state = cx.new(|cx| State::new(client.clone(), user_store.clone(), status, cx)); let state_ref = state.downgrade(); let maintain_client_status = cx.spawn(async move |cx| { while let Some(status) = status_rx.next().await { if let Some(this) = state_ref.upgrade() { _ = this.update(cx, |this, cx| { if this.status != status { this.status = status; cx.notify(); } }); } else { break; } } }); Self { client, state: state.clone(), _maintain_client_status: maintain_client_status, } } fn create_language_model( &self, model: Arc<zed_llm_client::LanguageModel>, llm_api_token: LlmApiToken, ) -> Arc<dyn LanguageModel> { Arc::new(CloudLanguageModel { id: LanguageModelId(SharedString::from(model.id.0.clone())), model, llm_api_token: llm_api_token.clone(), client: self.client.clone(), request_limiter: RateLimiter::new(4), }) } } impl LanguageModelProviderState for CloudLanguageModelProvider { type ObservableEntity = State; fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> { Some(self.state.clone()) } } impl LanguageModelProvider for CloudLanguageModelProvider { fn id(&self) -> LanguageModelProviderId { LanguageModelProviderId(ZED_CLOUD_PROVIDER_ID.into()) } fn name(&self) -> LanguageModelProviderName { LanguageModelProviderName(PROVIDER_NAME.into()) } fn icon(&self) -> IconName { IconName::AiZed } fn default_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> { let default_model = self.state.read(cx).default_model.clone()?; let llm_api_token = self.state.read(cx).llm_api_token.clone(); Some(self.create_language_model(default_model, llm_api_token)) } fn default_fast_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> { let default_fast_model = self.state.read(cx).default_fast_model.clone()?; let llm_api_token = self.state.read(cx).llm_api_token.clone(); Some(self.create_language_model(default_fast_model, llm_api_token)) } fn recommended_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> { let llm_api_token = self.state.read(cx).llm_api_token.clone(); self.state .read(cx) .recommended_models .iter() .cloned() .map(|model| self.create_language_model(model, llm_api_token.clone())) .collect() } fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> { let llm_api_token = self.state.read(cx).llm_api_token.clone(); self.state .read(cx) .models .iter() .cloned() .map(|model| self.create_language_model(model, llm_api_token.clone())) .collect() } fn is_authenticated(&self, cx: &App) -> bool { !self.state.read(cx).is_signed_out() } fn authenticate(&self, _cx: &mut App) -> Task<Result<(), AuthenticateError>> { Task::ready(Ok(())) } fn configuration_view(&self, _: &mut Window, cx: &mut App) -> AnyView { cx.new(|_| ConfigurationView { state: self.state.clone(), }) .into() } fn must_accept_terms(&self, cx: &App) -> bool { !self.state.read(cx).has_accepted_terms_of_service(cx) } fn render_accept_terms( &self, view: LanguageModelProviderTosView, cx: &mut App, ) -> Option<AnyElement> { render_accept_terms(self.state.clone(), view, cx) } fn reset_credentials(&self, _cx: &mut App) -> Task<Result<()>> { Task::ready(Ok(())) } } fn render_accept_terms( state: Entity<State>, view_kind: LanguageModelProviderTosView, cx: &mut App, ) -> Option<AnyElement> { if state.read(cx).has_accepted_terms_of_service(cx) { return None; } let accept_terms_disabled = state.read(cx).accept_terms.is_some(); let thread_fresh_start = matches!(view_kind, LanguageModelProviderTosView::ThreadFreshStart); let thread_empty_state = matches!(view_kind, LanguageModelProviderTosView::ThreadtEmptyState); let terms_button = Button::new("terms_of_service", "Terms of Service") .style(ButtonStyle::Subtle) .icon(IconName::ArrowUpRight) .icon_color(Color::Muted) .icon_size(IconSize::XSmall) .when(thread_empty_state, |this| this.label_size(LabelSize::Small)) .on_click(move |_, _window, cx| cx.open_url("https://zed.dev/terms-of-service")); let button_container = h_flex().child( Button::new("accept_terms", "I accept the Terms of Service") .when(!thread_empty_state, |this| { this.full_width() .style(ButtonStyle::Tinted(TintColor::Accent)) .icon(IconName::Check) .icon_position(IconPosition::Start) .icon_size(IconSize::Small) }) .when(thread_empty_state, |this| { this.style(ButtonStyle::Tinted(TintColor::Warning)) .label_size(LabelSize::Small) }) .disabled(accept_terms_disabled) .on_click({ let state = state.downgrade(); move |_, _window, cx| { state .update(cx, |state, cx| state.accept_terms_of_service(cx)) .ok(); } }), ); let form = if thread_empty_state { h_flex() .w_full() .flex_wrap() .justify_between() .child( h_flex() .child( Label::new("To start using Zed AI, please read and accept the") .size(LabelSize::Small), ) .child(terms_button), ) .child(button_container) } else { v_flex() .w_full() .gap_2() .child( h_flex() .flex_wrap() .when(thread_fresh_start, |this| this.justify_center()) .child(Label::new( "To start using Zed AI, please read and accept the",