use ai_onboarding::YoungAccountBanner;use anthropic::AnthropicModelMode;use anyhow::{Context as _, Result, anyhow};use chrono::{DateTime, Utc};use client::{Client, ModelRequestUsage, UserStore, zed_urls};use cloud_llm_client::{ CLIENT_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, CURRENT_PLAN_HEADER_NAME, CompletionBody, CompletionEvent, CompletionRequestStatus, CountTokensBody, CountTokensResponse, EXPIRED_LLM_TOKEN_HEADER_NAME, ListModelsResponse, MODEL_REQUESTS_RESOURCE_HEADER_VALUE, Plan, SERVER_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME, TOOL_USE_LIMIT_REACHED_HEADER_NAME, ZED_VERSION_HEADER_NAME,};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::http::{HeaderMap, HeaderValue};use http_client::{AsyncBody, HttpClient, Method, Response, StatusCode};use language_model::{ AuthenticateError, LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelProviderTosView, LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolSchemaFormat, LlmApiToken, ModelRequestLimitReachedError, PaymentRequiredError, RateLimiter, RefreshLlmTokenListener,};use release_channel::AppVersion;use schemars::JsonSchema;use serde::{Deserialize, Serialize, de::DeserializeOwned};use settings::SettingsStore;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 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}; const PROVIDER_ID: LanguageModelProviderId = language_model::ZED_CLOUD_PROVIDER_ID;const PROVIDER_NAME: LanguageModelProviderName = language_model::ZED_CLOUD_PROVIDER_NAME; #[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<u64>, /// The maximum number of completion tokens allowed by the model (o1-* only) pub max_completion_tokens: Option<u64>, /// 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_of_service_task: Option<Task<Result<()>>>, models: Vec<Arc<cloud_llm_client::LanguageModel>>, default_model: Option<Arc<cloud_llm_client::LanguageModel>>, default_fast_model: Option<Arc<cloud_llm_client::LanguageModel>>, recommended_models: Vec<Arc<cloud_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: user_store.clone(), status, accept_terms_of_service_task: 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 is_authenticated = user_store .read_with(cx, |user_store, _cx| user_store.current_user().is_some())?; if is_authenticated { break; } cx.background_executor() .timer(Duration::from_millis(100)) .await; } let response = Self::fetch_models(client, llm_api_token).await?; this.update(cx, |this, cx| { this.update_models(response, cx); }) }) .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, move |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?; let response = Self::fetch_models(client, llm_api_token).await?; this.update(cx, |this, cx| { this.update_models(response, cx); }) }) .detach_and_log_err(cx); }, ), } } fn is_signed_out(&self, cx: &App) -> bool { self.user_store.read(cx).current_user().is_none() } fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> { let client = self.client.clone(); cx.spawn(async move |state, cx| { client.sign_in_with_optional_connect(true, &cx).await?; state.update(cx, |_, cx| cx.notify()) }) } fn has_accepted_terms_of_service(&self, cx: &App) -> bool { self.user_store.read(cx).has_accepted_terms_of_service() } fn accept_terms_of_service(&mut self, cx: &mut Context<Self>) { let user_store = self.user_store.clone(); self.accept_terms_of_service_task = 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_of_service_task = None; cx.notify() }) })); } fn update_models(&mut self, response: ListModelsResponse, cx: &mut Context<Self>) { 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(cloud_llm_client::LanguageModel { id: cloud_llm_client::LanguageModelId(format!("{}-thinking", model.id).into()), display_name: format!("{} Thinking", model.display_name), ..model })); } } self.default_model = models .iter() .find(|model| model.id == response.default_model) .cloned(); self.default_fast_model = models .iter() .find(|model| model.id == response.default_fast_model) .cloned(); self.recommended_models = response .recommended_models .iter() .filter_map(|id| models.iter().find(|model| &model.id == id)) .cloned() .collect(); self.models = models; 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<cloud_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 { PROVIDER_ID } fn name(&self) -> LanguageModelProviderName { PROVIDER_NAME } 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 { let state = self.state.read(cx); !state.is_signed_out(cx) && state.has_accepted_terms_of_service(cx) } 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::new(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> { let state = self.state.read(cx); if state.has_accepted_terms_of_service(cx) { return None; } Some( render_accept_terms(view, state.accept_terms_of_service_task.is_some(), { let state = self.state.clone(); move |_window, cx| { state.update(cx, |state, cx| state.accept_terms_of_service(cx)); } }) .into_any_element(), ) } fn reset_credentials(&self, _cx: &mut App) -> Task<Result<()>> { Task::ready(Ok(())) }} fn render_accept_terms( view_kind: LanguageModelProviderTosView, accept_terms_of_service_in_progress: bool, accept_terms_callback: impl Fn(&mut Window, &mut App) + 'static,) -> impl IntoElement { let thread_fresh_start = matches!(view_kind, LanguageModelProviderTosView::ThreadFreshStart); let thread_empty_state = matches!(view_kind, LanguageModelProviderTosView::ThreadEmptyState); 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_of_service_in_progress) .on_click(move |_, window, cx| (accept_terms_callback)(window, cx)), ); 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", )) .child(terms_button), ) .child({ match view_kind { LanguageModelProviderTosView::TextThreadPopup => { button_container.w_full().justify_end() } LanguageModelProviderTosView::Configuration => {Lines 1–500 of 1487. Load more
use ai_onboarding::YoungAccountBanner; use anthropic::AnthropicModelMode; use anyhow::{Context as _, Result, anyhow}; use chrono::{DateTime, Utc}; use client::{Client, ModelRequestUsage, UserStore, zed_urls}; use cloud_llm_client::{ CLIENT_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, CURRENT_PLAN_HEADER_NAME, CompletionBody, CompletionEvent, CompletionRequestStatus, CountTokensBody, CountTokensResponse, EXPIRED_LLM_TOKEN_HEADER_NAME, ListModelsResponse, MODEL_REQUESTS_RESOURCE_HEADER_VALUE, Plan, SERVER_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME, TOOL_USE_LIMIT_REACHED_HEADER_NAME, ZED_VERSION_HEADER_NAME, }; 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::http::{HeaderMap, HeaderValue}; use http_client::{AsyncBody, HttpClient, Method, Response, StatusCode}; use language_model::{ AuthenticateError, LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelProviderTosView, LanguageModelRequest, LanguageModelToolChoice, LanguageModelToolSchemaFormat, LlmApiToken, ModelRequestLimitReachedError, PaymentRequiredError, RateLimiter, RefreshLlmTokenListener, }; use release_channel::AppVersion; use schemars::JsonSchema; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use settings::SettingsStore; 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 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}; const PROVIDER_ID: LanguageModelProviderId = language_model::ZED_CLOUD_PROVIDER_ID; const PROVIDER_NAME: LanguageModelProviderName = language_model::ZED_CLOUD_PROVIDER_NAME; #[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<u64>, /// The maximum number of completion tokens allowed by the model (o1-* only) pub max_completion_tokens: Option<u64>, /// 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_of_service_task: Option<Task<Result<()>>>, models: Vec<Arc<cloud_llm_client::LanguageModel>>, default_model: Option<Arc<cloud_llm_client::LanguageModel>>, default_fast_model: Option<Arc<cloud_llm_client::LanguageModel>>, recommended_models: Vec<Arc<cloud_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: user_store.clone(), status, accept_terms_of_service_task: 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 is_authenticated = user_store .read_with(cx, |user_store, _cx| user_store.current_user().is_some())?; if is_authenticated { break; } cx.background_executor() .timer(Duration::from_millis(100)) .await; } let response = Self::fetch_models(client, llm_api_token).await?; this.update(cx, |this, cx| { this.update_models(response, cx); }) }) .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, move |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?; let response = Self::fetch_models(client, llm_api_token).await?; this.update(cx, |this, cx| { this.update_models(response, cx); }) }) .detach_and_log_err(cx); }, ), } } fn is_signed_out(&self, cx: &App) -> bool { self.user_store.read(cx).current_user().is_none() } fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> { let client = self.client.clone(); cx.spawn(async move |state, cx| { client.sign_in_with_optional_connect(true, &cx).await?; state.update(cx, |_, cx| cx.notify()) }) } fn has_accepted_terms_of_service(&self, cx: &App) -> bool { self.user_store.read(cx).has_accepted_terms_of_service() } fn accept_terms_of_service(&mut self, cx: &mut Context<Self>) { let user_store = self.user_store.clone(); self.accept_terms_of_service_task = 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_of_service_task = None; cx.notify() }) })); } fn update_models(&mut self, response: ListModelsResponse, cx: &mut Context<Self>) { 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(cloud_llm_client::LanguageModel { id: cloud_llm_client::LanguageModelId(format!("{}-thinking", model.id).into()), display_name: format!("{} Thinking", model.display_name), ..model })); } } self.default_model = models .iter() .find(|model| model.id == response.default_model) .cloned(); self.default_fast_model = models .iter() .find(|model| model.id == response.default_fast_model) .cloned(); self.recommended_models = response .recommended_models .iter() .filter_map(|id| models.iter().find(|model| &model.id == id)) .cloned() .collect(); self.models = models; 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<cloud_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 { PROVIDER_ID } fn name(&self) -> LanguageModelProviderName { PROVIDER_NAME } 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 { let state = self.state.read(cx); !state.is_signed_out(cx) && state.has_accepted_terms_of_service(cx) } 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::new(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> { let state = self.state.read(cx); if state.has_accepted_terms_of_service(cx) { return None; } Some( render_accept_terms(view, state.accept_terms_of_service_task.is_some(), { let state = self.state.clone(); move |_window, cx| { state.update(cx, |state, cx| state.accept_terms_of_service(cx)); } }) .into_any_element(), ) } fn reset_credentials(&self, _cx: &mut App) -> Task<Result<()>> { Task::ready(Ok(())) } } fn render_accept_terms( view_kind: LanguageModelProviderTosView, accept_terms_of_service_in_progress: bool, accept_terms_callback: impl Fn(&mut Window, &mut App) + 'static, ) -> impl IntoElement { let thread_fresh_start = matches!(view_kind, LanguageModelProviderTosView::ThreadFreshStart); let thread_empty_state = matches!(view_kind, LanguageModelProviderTosView::ThreadEmptyState); 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_of_service_in_progress) .on_click(move |_, window, cx| (accept_terms_callback)(window, cx)), ); 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", )) .child(terms_button), ) .child({ match view_kind { LanguageModelProviderTosView::TextThreadPopup => { button_container.w_full().justify_end() } LanguageModelProviderTosView::Configuration => {