Update Agent panel to work with CloudUserStore (#35436)

This PR updates the Agent panel to work with the `CloudUserStore`
instead of the `UserStore`, reducing its reliance on being connected to
Collab to function.

Release Notes:

- N/A

---------

Co-authored-by: Richard Feldman <oss@rtfeldman.com>
This commit is contained in:
Marshall Bowers 2025-07-31 21:44:43 -04:00 committed by GitHub
parent 09b93caa9b
commit 72d354de6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 212 additions and 108 deletions

View file

@ -12,7 +12,7 @@ use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet}; use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use client::{ModelRequestUsage, RequestUsage}; use client::{CloudUserStore, ModelRequestUsage, RequestUsage};
use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit}; use cloud_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
use collections::HashMap; use collections::HashMap;
use feature_flags::{self, FeatureFlagAppExt}; use feature_flags::{self, FeatureFlagAppExt};
@ -374,6 +374,7 @@ pub struct Thread {
completion_count: usize, completion_count: usize,
pending_completions: Vec<PendingCompletion>, pending_completions: Vec<PendingCompletion>,
project: Entity<Project>, project: Entity<Project>,
cloud_user_store: Entity<CloudUserStore>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
tools: Entity<ToolWorkingSet>, tools: Entity<ToolWorkingSet>,
tool_use: ToolUseState, tool_use: ToolUseState,
@ -444,6 +445,7 @@ pub struct ExceededWindowError {
impl Thread { impl Thread {
pub fn new( pub fn new(
project: Entity<Project>, project: Entity<Project>,
cloud_user_store: Entity<CloudUserStore>,
tools: Entity<ToolWorkingSet>, tools: Entity<ToolWorkingSet>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
system_prompt: SharedProjectContext, system_prompt: SharedProjectContext,
@ -470,6 +472,7 @@ impl Thread {
completion_count: 0, completion_count: 0,
pending_completions: Vec::new(), pending_completions: Vec::new(),
project: project.clone(), project: project.clone(),
cloud_user_store,
prompt_builder, prompt_builder,
tools: tools.clone(), tools: tools.clone(),
last_restore_checkpoint: None, last_restore_checkpoint: None,
@ -503,6 +506,7 @@ impl Thread {
id: ThreadId, id: ThreadId,
serialized: SerializedThread, serialized: SerializedThread,
project: Entity<Project>, project: Entity<Project>,
cloud_user_store: Entity<CloudUserStore>,
tools: Entity<ToolWorkingSet>, tools: Entity<ToolWorkingSet>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
project_context: SharedProjectContext, project_context: SharedProjectContext,
@ -603,6 +607,7 @@ impl Thread {
last_restore_checkpoint: None, last_restore_checkpoint: None,
pending_checkpoint: None, pending_checkpoint: None,
project: project.clone(), project: project.clone(),
cloud_user_store,
prompt_builder, prompt_builder,
tools: tools.clone(), tools: tools.clone(),
tool_use, tool_use,
@ -3255,16 +3260,14 @@ impl Thread {
} }
fn update_model_request_usage(&self, amount: u32, limit: UsageLimit, cx: &mut Context<Self>) { fn update_model_request_usage(&self, amount: u32, limit: UsageLimit, cx: &mut Context<Self>) {
self.project.update(cx, |project, cx| { self.cloud_user_store.update(cx, |cloud_user_store, cx| {
project.user_store().update(cx, |user_store, cx| { cloud_user_store.update_model_request_usage(
user_store.update_model_request_usage( ModelRequestUsage(RequestUsage {
ModelRequestUsage(RequestUsage { amount: amount as i32,
amount: amount as i32, limit,
limit, }),
}), cx,
cx, )
)
})
}); });
} }
@ -3883,6 +3886,7 @@ fn main() {{
thread.id.clone(), thread.id.clone(),
serialized, serialized,
thread.project.clone(), thread.project.clone(),
thread.cloud_user_store.clone(),
thread.tools.clone(), thread.tools.clone(),
thread.prompt_builder.clone(), thread.prompt_builder.clone(),
thread.project_context.clone(), thread.project_context.clone(),
@ -5479,10 +5483,16 @@ fn main() {{
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let (client, user_store) =
project.read_with(cx, |project, _cx| (project.client(), project.user_store()));
let cloud_user_store =
cx.new(|cx| CloudUserStore::new(client.cloud_client(), user_store, cx));
let thread_store = cx let thread_store = cx
.update(|_, cx| { .update(|_, cx| {
ThreadStore::load( ThreadStore::load(
project.clone(), project.clone(),
cloud_user_store,
cx.new(|_| ToolWorkingSet::default()), cx.new(|_| ToolWorkingSet::default()),
None, None,
Arc::new(PromptBuilder::new(None).unwrap()), Arc::new(PromptBuilder::new(None).unwrap()),

View file

@ -8,6 +8,7 @@ use agent_settings::{AgentProfileId, CompletionMode};
use anyhow::{Context as _, Result, anyhow}; use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{Tool, ToolId, ToolWorkingSet}; use assistant_tool::{Tool, ToolId, ToolWorkingSet};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use client::CloudUserStore;
use collections::HashMap; use collections::HashMap;
use context_server::ContextServerId; use context_server::ContextServerId;
use futures::{ use futures::{
@ -104,6 +105,7 @@ pub type TextThreadStore = assistant_context::ContextStore;
pub struct ThreadStore { pub struct ThreadStore {
project: Entity<Project>, project: Entity<Project>,
cloud_user_store: Entity<CloudUserStore>,
tools: Entity<ToolWorkingSet>, tools: Entity<ToolWorkingSet>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
prompt_store: Option<Entity<PromptStore>>, prompt_store: Option<Entity<PromptStore>>,
@ -124,6 +126,7 @@ impl EventEmitter<RulesLoadingError> for ThreadStore {}
impl ThreadStore { impl ThreadStore {
pub fn load( pub fn load(
project: Entity<Project>, project: Entity<Project>,
cloud_user_store: Entity<CloudUserStore>,
tools: Entity<ToolWorkingSet>, tools: Entity<ToolWorkingSet>,
prompt_store: Option<Entity<PromptStore>>, prompt_store: Option<Entity<PromptStore>>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
@ -133,8 +136,14 @@ impl ThreadStore {
let (thread_store, ready_rx) = cx.update(|cx| { let (thread_store, ready_rx) = cx.update(|cx| {
let mut option_ready_rx = None; let mut option_ready_rx = None;
let thread_store = cx.new(|cx| { let thread_store = cx.new(|cx| {
let (thread_store, ready_rx) = let (thread_store, ready_rx) = Self::new(
Self::new(project, tools, prompt_builder, prompt_store, cx); project,
cloud_user_store,
tools,
prompt_builder,
prompt_store,
cx,
);
option_ready_rx = Some(ready_rx); option_ready_rx = Some(ready_rx);
thread_store thread_store
}); });
@ -147,6 +156,7 @@ impl ThreadStore {
fn new( fn new(
project: Entity<Project>, project: Entity<Project>,
cloud_user_store: Entity<CloudUserStore>,
tools: Entity<ToolWorkingSet>, tools: Entity<ToolWorkingSet>,
prompt_builder: Arc<PromptBuilder>, prompt_builder: Arc<PromptBuilder>,
prompt_store: Option<Entity<PromptStore>>, prompt_store: Option<Entity<PromptStore>>,
@ -190,6 +200,7 @@ impl ThreadStore {
let this = Self { let this = Self {
project, project,
cloud_user_store,
tools, tools,
prompt_builder, prompt_builder,
prompt_store, prompt_store,
@ -407,6 +418,7 @@ impl ThreadStore {
cx.new(|cx| { cx.new(|cx| {
Thread::new( Thread::new(
self.project.clone(), self.project.clone(),
self.cloud_user_store.clone(),
self.tools.clone(), self.tools.clone(),
self.prompt_builder.clone(), self.prompt_builder.clone(),
self.project_context.clone(), self.project_context.clone(),
@ -425,6 +437,7 @@ impl ThreadStore {
ThreadId::new(), ThreadId::new(),
serialized, serialized,
self.project.clone(), self.project.clone(),
self.cloud_user_store.clone(),
self.tools.clone(), self.tools.clone(),
self.prompt_builder.clone(), self.prompt_builder.clone(),
self.project_context.clone(), self.project_context.clone(),
@ -456,6 +469,7 @@ impl ThreadStore {
id.clone(), id.clone(),
thread, thread,
this.project.clone(), this.project.clone(),
this.cloud_user_store.clone(),
this.tools.clone(), this.tools.clone(),
this.prompt_builder.clone(), this.prompt_builder.clone(),
this.project_context.clone(), this.project_context.clone(),

View file

@ -3820,6 +3820,7 @@ mod tests {
use super::*; use super::*;
use agent::{MessageSegment, context::ContextLoadResult, thread_store}; use agent::{MessageSegment, context::ContextLoadResult, thread_store};
use assistant_tool::{ToolRegistry, ToolWorkingSet}; use assistant_tool::{ToolRegistry, ToolWorkingSet};
use client::CloudUserStore;
use editor::EditorSettings; use editor::EditorSettings;
use fs::FakeFs; use fs::FakeFs;
use gpui::{AppContext, TestAppContext, VisualTestContext}; use gpui::{AppContext, TestAppContext, VisualTestContext};
@ -4116,10 +4117,16 @@ mod tests {
let (workspace, cx) = let (workspace, cx) =
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
let (client, user_store) =
project.read_with(cx, |project, _cx| (project.client(), project.user_store()));
let cloud_user_store =
cx.new(|cx| CloudUserStore::new(client.cloud_client(), user_store, cx));
let thread_store = cx let thread_store = cx
.update(|_, cx| { .update(|_, cx| {
ThreadStore::load( ThreadStore::load(
project.clone(), project.clone(),
cloud_user_store,
cx.new(|_| ToolWorkingSet::default()), cx.new(|_| ToolWorkingSet::default()),
None, None,
Arc::new(PromptBuilder::new(None).unwrap()), Arc::new(PromptBuilder::new(None).unwrap()),

View file

@ -1893,6 +1893,7 @@ mod tests {
use agent::thread_store::{self, ThreadStore}; use agent::thread_store::{self, ThreadStore};
use agent_settings::AgentSettings; use agent_settings::AgentSettings;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;
use client::CloudUserStore;
use editor::EditorSettings; use editor::EditorSettings;
use gpui::{TestAppContext, UpdateGlobal, VisualTestContext}; use gpui::{TestAppContext, UpdateGlobal, VisualTestContext};
use project::{FakeFs, Project}; use project::{FakeFs, Project};
@ -1932,11 +1933,17 @@ mod tests {
}) })
.unwrap(); .unwrap();
let (client, user_store) =
project.read_with(cx, |project, _cx| (project.client(), project.user_store()));
let cloud_user_store =
cx.new(|cx| CloudUserStore::new(client.cloud_client(), user_store, cx));
let prompt_store = None; let prompt_store = None;
let thread_store = cx let thread_store = cx
.update(|cx| { .update(|cx| {
ThreadStore::load( ThreadStore::load(
project.clone(), project.clone(),
cloud_user_store,
cx.new(|_| ToolWorkingSet::default()), cx.new(|_| ToolWorkingSet::default()),
prompt_store, prompt_store,
Arc::new(PromptBuilder::new(None).unwrap()), Arc::new(PromptBuilder::new(None).unwrap()),
@ -2098,11 +2105,17 @@ mod tests {
}) })
.unwrap(); .unwrap();
let (client, user_store) =
project.read_with(cx, |project, _cx| (project.client(), project.user_store()));
let cloud_user_store =
cx.new(|cx| CloudUserStore::new(client.cloud_client(), user_store, cx));
let prompt_store = None; let prompt_store = None;
let thread_store = cx let thread_store = cx
.update(|cx| { .update(|cx| {
ThreadStore::load( ThreadStore::load(
project.clone(), project.clone(),
cloud_user_store,
cx.new(|_| ToolWorkingSet::default()), cx.new(|_| ToolWorkingSet::default()),
prompt_store, prompt_store,
Arc::new(PromptBuilder::new(None).unwrap()), Arc::new(PromptBuilder::new(None).unwrap()),

View file

@ -43,7 +43,7 @@ use anyhow::{Result, anyhow};
use assistant_context::{AssistantContext, ContextEvent, ContextSummary}; use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
use assistant_slash_command::SlashCommandWorkingSet; use assistant_slash_command::SlashCommandWorkingSet;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;
use client::{DisableAiSettings, UserStore, zed_urls}; use client::{CloudUserStore, DisableAiSettings, UserStore, zed_urls};
use cloud_llm_client::{CompletionIntent, UsageLimit}; use cloud_llm_client::{CompletionIntent, UsageLimit};
use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer}; use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
use feature_flags::{self, FeatureFlagAppExt}; use feature_flags::{self, FeatureFlagAppExt};
@ -427,6 +427,7 @@ impl ActiveView {
pub struct AgentPanel { pub struct AgentPanel {
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
user_store: Entity<UserStore>, user_store: Entity<UserStore>,
cloud_user_store: Entity<CloudUserStore>,
project: Entity<Project>, project: Entity<Project>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
@ -486,6 +487,7 @@ impl AgentPanel {
let project = workspace.project().clone(); let project = workspace.project().clone();
ThreadStore::load( ThreadStore::load(
project, project,
workspace.app_state().cloud_user_store.clone(),
tools.clone(), tools.clone(),
prompt_store.clone(), prompt_store.clone(),
prompt_builder.clone(), prompt_builder.clone(),
@ -553,6 +555,7 @@ impl AgentPanel {
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx)); let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
let fs = workspace.app_state().fs.clone(); let fs = workspace.app_state().fs.clone();
let user_store = workspace.app_state().user_store.clone(); let user_store = workspace.app_state().user_store.clone();
let cloud_user_store = workspace.app_state().cloud_user_store.clone();
let project = workspace.project(); let project = workspace.project();
let language_registry = project.read(cx).languages().clone(); let language_registry = project.read(cx).languages().clone();
let client = workspace.client().clone(); let client = workspace.client().clone();
@ -579,7 +582,7 @@ impl AgentPanel {
MessageEditor::new( MessageEditor::new(
fs.clone(), fs.clone(),
workspace.clone(), workspace.clone(),
user_store.clone(), cloud_user_store.clone(),
message_editor_context_store.clone(), message_editor_context_store.clone(),
prompt_store.clone(), prompt_store.clone(),
thread_store.downgrade(), thread_store.downgrade(),
@ -706,6 +709,7 @@ impl AgentPanel {
active_view, active_view,
workspace, workspace,
user_store, user_store,
cloud_user_store,
project: project.clone(), project: project.clone(),
fs: fs.clone(), fs: fs.clone(),
language_registry, language_registry,
@ -848,7 +852,7 @@ impl AgentPanel {
MessageEditor::new( MessageEditor::new(
self.fs.clone(), self.fs.clone(),
self.workspace.clone(), self.workspace.clone(),
self.user_store.clone(), self.cloud_user_store.clone(),
context_store.clone(), context_store.clone(),
self.prompt_store.clone(), self.prompt_store.clone(),
self.thread_store.downgrade(), self.thread_store.downgrade(),
@ -1122,7 +1126,7 @@ impl AgentPanel {
MessageEditor::new( MessageEditor::new(
self.fs.clone(), self.fs.clone(),
self.workspace.clone(), self.workspace.clone(),
self.user_store.clone(), self.cloud_user_store.clone(),
context_store, context_store,
self.prompt_store.clone(), self.prompt_store.clone(),
self.thread_store.downgrade(), self.thread_store.downgrade(),
@ -1821,8 +1825,8 @@ impl AgentPanel {
} }
fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let user_store = self.user_store.read(cx); let cloud_user_store = self.cloud_user_store.read(cx);
let usage = user_store.model_request_usage(); let usage = cloud_user_store.model_request_usage();
let account_url = zed_urls::account_url(cx); let account_url = zed_urls::account_url(cx);

View file

@ -17,7 +17,7 @@ use agent::{
use agent_settings::{AgentSettings, CompletionMode}; use agent_settings::{AgentSettings, CompletionMode};
use ai_onboarding::ApiKeysWithProviders; use ai_onboarding::ApiKeysWithProviders;
use buffer_diff::BufferDiff; use buffer_diff::BufferDiff;
use client::UserStore; use client::CloudUserStore;
use cloud_llm_client::CompletionIntent; use cloud_llm_client::CompletionIntent;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use editor::actions::{MoveUp, Paste}; use editor::actions::{MoveUp, Paste};
@ -43,7 +43,6 @@ use language_model::{
use multi_buffer; use multi_buffer;
use project::Project; use project::Project;
use prompt_store::PromptStore; use prompt_store::PromptStore;
use proto::Plan;
use settings::Settings; use settings::Settings;
use std::time::Duration; use std::time::Duration;
use theme::ThemeSettings; use theme::ThemeSettings;
@ -79,7 +78,7 @@ pub struct MessageEditor {
editor: Entity<Editor>, editor: Entity<Editor>,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
project: Entity<Project>, project: Entity<Project>,
user_store: Entity<UserStore>, cloud_user_store: Entity<CloudUserStore>,
context_store: Entity<ContextStore>, context_store: Entity<ContextStore>,
prompt_store: Option<Entity<PromptStore>>, prompt_store: Option<Entity<PromptStore>>,
history_store: Option<WeakEntity<HistoryStore>>, history_store: Option<WeakEntity<HistoryStore>>,
@ -159,7 +158,7 @@ impl MessageEditor {
pub fn new( pub fn new(
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
workspace: WeakEntity<Workspace>, workspace: WeakEntity<Workspace>,
user_store: Entity<UserStore>, cloud_user_store: Entity<CloudUserStore>,
context_store: Entity<ContextStore>, context_store: Entity<ContextStore>,
prompt_store: Option<Entity<PromptStore>>, prompt_store: Option<Entity<PromptStore>>,
thread_store: WeakEntity<ThreadStore>, thread_store: WeakEntity<ThreadStore>,
@ -231,7 +230,7 @@ impl MessageEditor {
Self { Self {
editor: editor.clone(), editor: editor.clone(),
project: thread.read(cx).project().clone(), project: thread.read(cx).project().clone(),
user_store, cloud_user_store,
thread, thread,
incompatible_tools_state: incompatible_tools.clone(), incompatible_tools_state: incompatible_tools.clone(),
workspace, workspace,
@ -1287,26 +1286,16 @@ impl MessageEditor {
return None; return None;
} }
let user_store = self.user_store.read(cx); let cloud_user_store = self.cloud_user_store.read(cx);
if cloud_user_store.is_usage_based_billing_enabled() {
let ubb_enable = user_store
.usage_based_billing_enabled()
.map_or(false, |enabled| enabled);
if ubb_enable {
return None; return None;
} }
let plan = user_store let plan = cloud_user_store
.current_plan() .plan()
.map(|plan| match plan {
Plan::Free => cloud_llm_client::Plan::ZedFree,
Plan::ZedPro => cloud_llm_client::Plan::ZedPro,
Plan::ZedProTrial => cloud_llm_client::Plan::ZedProTrial,
})
.unwrap_or(cloud_llm_client::Plan::ZedFree); .unwrap_or(cloud_llm_client::Plan::ZedFree);
let usage = user_store.model_request_usage()?; let usage = cloud_user_store.model_request_usage()?;
Some( Some(
div() div()
@ -1769,7 +1758,7 @@ impl AgentPreview for MessageEditor {
) -> Option<AnyElement> { ) -> Option<AnyElement> {
if let Some(workspace) = workspace.upgrade() { if let Some(workspace) = workspace.upgrade() {
let fs = workspace.read(cx).app_state().fs.clone(); let fs = workspace.read(cx).app_state().fs.clone();
let user_store = workspace.read(cx).app_state().user_store.clone(); let cloud_user_store = workspace.read(cx).app_state().cloud_user_store.clone();
let project = workspace.read(cx).project().clone(); let project = workspace.read(cx).project().clone();
let weak_project = project.downgrade(); let weak_project = project.downgrade();
let context_store = cx.new(|_cx| ContextStore::new(weak_project, None)); let context_store = cx.new(|_cx| ContextStore::new(weak_project, None));
@ -1782,7 +1771,7 @@ impl AgentPreview for MessageEditor {
MessageEditor::new( MessageEditor::new(
fs, fs,
workspace.downgrade(), workspace.downgrade(),
user_store, cloud_user_store,
context_store, context_store,
None, None,
thread_store.downgrade(), thread_store.downgrade(),

View file

@ -7,7 +7,7 @@ use crate::{
}; };
use Role::*; use Role::*;
use assistant_tool::ToolRegistry; use assistant_tool::ToolRegistry;
use client::{Client, UserStore}; use client::{Client, CloudUserStore, UserStore};
use collections::HashMap; use collections::HashMap;
use fs::FakeFs; use fs::FakeFs;
use futures::{FutureExt, future::LocalBoxFuture}; use futures::{FutureExt, future::LocalBoxFuture};
@ -1470,12 +1470,14 @@ impl EditAgentTest {
client::init_settings(cx); client::init_settings(cx);
let client = Client::production(cx); let client = Client::production(cx);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx)); let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let cloud_user_store =
cx.new(|cx| CloudUserStore::new(client.cloud_client(), user_store.clone(), cx));
settings::init(cx); settings::init(cx);
Project::init_settings(cx); Project::init_settings(cx);
language::init(cx); language::init(cx);
language_model::init(client.clone(), cx); language_model::init(client.clone(), cx);
language_models::init(user_store.clone(), client.clone(), cx); language_models::init(user_store.clone(), cloud_user_store, client.clone(), cx);
crate::init(client.http_client(), cx); crate::init(client.http_client(), cx);
}); });

View file

@ -9,12 +9,13 @@ use gpui::{Context, Entity, Subscription, Task};
use util::{ResultExt as _, maybe}; use util::{ResultExt as _, maybe};
use crate::user::Event as RpcUserStoreEvent; use crate::user::Event as RpcUserStoreEvent;
use crate::{EditPredictionUsage, RequestUsage, UserStore}; use crate::{EditPredictionUsage, ModelRequestUsage, RequestUsage, UserStore};
pub struct CloudUserStore { pub struct CloudUserStore {
cloud_client: Arc<CloudApiClient>, cloud_client: Arc<CloudApiClient>,
authenticated_user: Option<Arc<AuthenticatedUser>>, authenticated_user: Option<Arc<AuthenticatedUser>>,
plan_info: Option<Arc<PlanInfo>>, plan_info: Option<Arc<PlanInfo>>,
model_request_usage: Option<ModelRequestUsage>,
edit_prediction_usage: Option<EditPredictionUsage>, edit_prediction_usage: Option<EditPredictionUsage>,
_maintain_authenticated_user_task: Task<()>, _maintain_authenticated_user_task: Task<()>,
_rpc_plan_updated_subscription: Subscription, _rpc_plan_updated_subscription: Subscription,
@ -33,6 +34,7 @@ impl CloudUserStore {
cloud_client: cloud_client.clone(), cloud_client: cloud_client.clone(),
authenticated_user: None, authenticated_user: None,
plan_info: None, plan_info: None,
model_request_usage: None,
edit_prediction_usage: None, edit_prediction_usage: None,
_maintain_authenticated_user_task: cx.spawn(async move |this, cx| { _maintain_authenticated_user_task: cx.spawn(async move |this, cx| {
maybe!(async move { maybe!(async move {
@ -104,6 +106,13 @@ impl CloudUserStore {
}) })
} }
pub fn trial_started_at(&self) -> Option<DateTime<Utc>> {
self.plan_info
.as_ref()
.and_then(|plan| plan.trial_started_at)
.map(|trial_started_at| trial_started_at.0)
}
pub fn has_accepted_tos(&self) -> bool { pub fn has_accepted_tos(&self) -> bool {
self.authenticated_user self.authenticated_user
.as_ref() .as_ref()
@ -127,6 +136,22 @@ impl CloudUserStore {
.unwrap_or_default() .unwrap_or_default()
} }
pub fn is_usage_based_billing_enabled(&self) -> bool {
self.plan_info
.as_ref()
.map(|plan| plan.is_usage_based_billing_enabled)
.unwrap_or_default()
}
pub fn model_request_usage(&self) -> Option<ModelRequestUsage> {
self.model_request_usage
}
pub fn update_model_request_usage(&mut self, usage: ModelRequestUsage, cx: &mut Context<Self>) {
self.model_request_usage = Some(usage);
cx.notify();
}
pub fn edit_prediction_usage(&self) -> Option<EditPredictionUsage> { pub fn edit_prediction_usage(&self) -> Option<EditPredictionUsage> {
self.edit_prediction_usage self.edit_prediction_usage
} }
@ -142,6 +167,10 @@ impl CloudUserStore {
fn update_authenticated_user(&mut self, response: GetAuthenticatedUserResponse) { fn update_authenticated_user(&mut self, response: GetAuthenticatedUserResponse) {
self.authenticated_user = Some(Arc::new(response.user)); self.authenticated_user = Some(Arc::new(response.user));
self.model_request_usage = Some(ModelRequestUsage(RequestUsage {
limit: response.plan.usage.model_requests.limit,
amount: response.plan.usage.model_requests.used as i32,
}));
self.edit_prediction_usage = Some(EditPredictionUsage(RequestUsage { self.edit_prediction_usage = Some(EditPredictionUsage(RequestUsage {
limit: response.plan.usage.edit_predictions.limit, limit: response.plan.usage.edit_predictions.limit,
amount: response.plan.usage.edit_predictions.used as i32, amount: response.plan.usage.edit_predictions.used as i32,

View file

@ -113,7 +113,6 @@ pub struct UserStore {
current_plan: Option<proto::Plan>, current_plan: Option<proto::Plan>,
subscription_period: Option<(DateTime<Utc>, DateTime<Utc>)>, subscription_period: Option<(DateTime<Utc>, DateTime<Utc>)>,
trial_started_at: Option<DateTime<Utc>>, trial_started_at: Option<DateTime<Utc>>,
model_request_usage: Option<ModelRequestUsage>,
is_usage_based_billing_enabled: Option<bool>, is_usage_based_billing_enabled: Option<bool>,
account_too_young: Option<bool>, account_too_young: Option<bool>,
has_overdue_invoices: Option<bool>, has_overdue_invoices: Option<bool>,
@ -191,7 +190,6 @@ impl UserStore {
current_plan: None, current_plan: None,
subscription_period: None, subscription_period: None,
trial_started_at: None, trial_started_at: None,
model_request_usage: None,
is_usage_based_billing_enabled: None, is_usage_based_billing_enabled: None,
account_too_young: None, account_too_young: None,
has_overdue_invoices: None, has_overdue_invoices: None,
@ -371,27 +369,12 @@ impl UserStore {
this.account_too_young = message.payload.account_too_young; this.account_too_young = message.payload.account_too_young;
this.has_overdue_invoices = message.payload.has_overdue_invoices; this.has_overdue_invoices = message.payload.has_overdue_invoices;
if let Some(usage) = message.payload.usage {
// limits are always present even though they are wrapped in Option
this.model_request_usage = usage
.model_requests_usage_limit
.and_then(|limit| {
RequestUsage::from_proto(usage.model_requests_usage_amount, limit)
})
.map(ModelRequestUsage);
}
cx.emit(Event::PlanUpdated); cx.emit(Event::PlanUpdated);
cx.notify(); cx.notify();
})?; })?;
Ok(()) Ok(())
} }
pub fn update_model_request_usage(&mut self, usage: ModelRequestUsage, cx: &mut Context<Self>) {
self.model_request_usage = Some(usage);
cx.notify();
}
fn update_contacts(&mut self, message: UpdateContacts, cx: &Context<Self>) -> Task<Result<()>> { fn update_contacts(&mut self, message: UpdateContacts, cx: &Context<Self>) -> Task<Result<()>> {
match message { match message {
UpdateContacts::Wait(barrier) => { UpdateContacts::Wait(barrier) => {
@ -776,10 +759,6 @@ impl UserStore {
self.is_usage_based_billing_enabled self.is_usage_based_billing_enabled
} }
pub fn model_request_usage(&self) -> Option<ModelRequestUsage> {
self.model_request_usage
}
pub fn watch_current_user(&self) -> watch::Receiver<Option<Arc<User>>> { pub fn watch_current_user(&self) -> watch::Receiver<Option<Arc<User>>> {
self.current_user.clone() self.current_user.clone()
} }

View file

@ -13,7 +13,7 @@ pub(crate) use tool_metrics::*;
use ::fs::RealFs; use ::fs::RealFs;
use clap::Parser; use clap::Parser;
use client::{Client, ProxySettings, UserStore}; use client::{Client, CloudUserStore, ProxySettings, UserStore};
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use extension::ExtensionHostProxy; use extension::ExtensionHostProxy;
use futures::future; use futures::future;
@ -329,6 +329,7 @@ pub struct AgentAppState {
pub languages: Arc<LanguageRegistry>, pub languages: Arc<LanguageRegistry>,
pub client: Arc<Client>, pub client: Arc<Client>,
pub user_store: Entity<UserStore>, pub user_store: Entity<UserStore>,
pub cloud_user_store: Entity<CloudUserStore>,
pub fs: Arc<dyn fs::Fs>, pub fs: Arc<dyn fs::Fs>,
pub node_runtime: NodeRuntime, pub node_runtime: NodeRuntime,
@ -383,6 +384,8 @@ pub fn init(cx: &mut App) -> Arc<AgentAppState> {
let languages = Arc::new(languages); let languages = Arc::new(languages);
let user_store = cx.new(|cx| UserStore::new(client.clone(), cx)); let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
let cloud_user_store =
cx.new(|cx| CloudUserStore::new(client.cloud_client(), user_store.clone(), cx));
extension::init(cx); extension::init(cx);
@ -422,7 +425,12 @@ pub fn init(cx: &mut App) -> Arc<AgentAppState> {
languages.clone(), languages.clone(),
); );
language_model::init(client.clone(), cx); language_model::init(client.clone(), cx);
language_models::init(user_store.clone(), client.clone(), cx); language_models::init(
user_store.clone(),
cloud_user_store.clone(),
client.clone(),
cx,
);
languages::init(languages.clone(), node_runtime.clone(), cx); languages::init(languages.clone(), node_runtime.clone(), cx);
prompt_store::init(cx); prompt_store::init(cx);
terminal_view::init(cx); terminal_view::init(cx);
@ -447,6 +455,7 @@ pub fn init(cx: &mut App) -> Arc<AgentAppState> {
languages, languages,
client, client,
user_store, user_store,
cloud_user_store,
fs, fs,
node_runtime, node_runtime,
prompt_builder, prompt_builder,

View file

@ -221,6 +221,7 @@ impl ExampleInstance {
let prompt_store = None; let prompt_store = None;
let thread_store = ThreadStore::load( let thread_store = ThreadStore::load(
project.clone(), project.clone(),
app_state.cloud_user_store.clone(),
tools, tools,
prompt_store, prompt_store,
app_state.prompt_builder.clone(), app_state.prompt_builder.clone(),

View file

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use ::settings::{Settings, SettingsStore}; use ::settings::{Settings, SettingsStore};
use client::{Client, UserStore}; use client::{Client, CloudUserStore, UserStore};
use collections::HashSet; use collections::HashSet;
use gpui::{App, Context, Entity}; use gpui::{App, Context, Entity};
use language_model::{LanguageModelProviderId, LanguageModelRegistry}; use language_model::{LanguageModelProviderId, LanguageModelRegistry};
@ -26,11 +26,22 @@ use crate::provider::vercel::VercelLanguageModelProvider;
use crate::provider::x_ai::XAiLanguageModelProvider; use crate::provider::x_ai::XAiLanguageModelProvider;
pub use crate::settings::*; pub use crate::settings::*;
pub fn init(user_store: Entity<UserStore>, client: Arc<Client>, cx: &mut App) { pub fn init(
user_store: Entity<UserStore>,
cloud_user_store: Entity<CloudUserStore>,
client: Arc<Client>,
cx: &mut App,
) {
crate::settings::init_settings(cx); crate::settings::init_settings(cx);
let registry = LanguageModelRegistry::global(cx); let registry = LanguageModelRegistry::global(cx);
registry.update(cx, |registry, cx| { registry.update(cx, |registry, cx| {
register_language_model_providers(registry, user_store, client.clone(), cx); register_language_model_providers(
registry,
user_store,
cloud_user_store,
client.clone(),
cx,
);
}); });
let mut openai_compatible_providers = AllLanguageModelSettings::get_global(cx) let mut openai_compatible_providers = AllLanguageModelSettings::get_global(cx)
@ -100,11 +111,17 @@ fn register_openai_compatible_providers(
fn register_language_model_providers( fn register_language_model_providers(
registry: &mut LanguageModelRegistry, registry: &mut LanguageModelRegistry,
user_store: Entity<UserStore>, user_store: Entity<UserStore>,
cloud_user_store: Entity<CloudUserStore>,
client: Arc<Client>, client: Arc<Client>,
cx: &mut Context<LanguageModelRegistry>, cx: &mut Context<LanguageModelRegistry>,
) { ) {
registry.register_provider( registry.register_provider(
CloudLanguageModelProvider::new(user_store.clone(), client.clone(), cx), CloudLanguageModelProvider::new(
user_store.clone(),
cloud_user_store.clone(),
client.clone(),
cx,
),
cx, cx,
); );

View file

@ -2,11 +2,11 @@ use ai_onboarding::YoungAccountBanner;
use anthropic::AnthropicModelMode; use anthropic::AnthropicModelMode;
use anyhow::{Context as _, Result, anyhow}; use anyhow::{Context as _, Result, anyhow};
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use client::{Client, ModelRequestUsage, UserStore, zed_urls}; use client::{Client, CloudUserStore, ModelRequestUsage, UserStore, zed_urls};
use cloud_llm_client::{ use cloud_llm_client::{
CLIENT_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, CURRENT_PLAN_HEADER_NAME, CompletionBody, CLIENT_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, CURRENT_PLAN_HEADER_NAME, CompletionBody,
CompletionEvent, CompletionRequestStatus, CountTokensBody, CountTokensResponse, CompletionEvent, CompletionRequestStatus, CountTokensBody, CountTokensResponse,
EXPIRED_LLM_TOKEN_HEADER_NAME, ListModelsResponse, MODEL_REQUESTS_RESOURCE_HEADER_VALUE, EXPIRED_LLM_TOKEN_HEADER_NAME, ListModelsResponse, MODEL_REQUESTS_RESOURCE_HEADER_VALUE, Plan,
SERVER_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME, SERVER_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME,
TOOL_USE_LIMIT_REACHED_HEADER_NAME, ZED_VERSION_HEADER_NAME, TOOL_USE_LIMIT_REACHED_HEADER_NAME, ZED_VERSION_HEADER_NAME,
}; };
@ -27,7 +27,6 @@ use language_model::{
LanguageModelToolChoice, LanguageModelToolSchemaFormat, LlmApiToken, LanguageModelToolChoice, LanguageModelToolSchemaFormat, LlmApiToken,
ModelRequestLimitReachedError, PaymentRequiredError, RateLimiter, RefreshLlmTokenListener, ModelRequestLimitReachedError, PaymentRequiredError, RateLimiter, RefreshLlmTokenListener,
}; };
use proto::Plan;
use release_channel::AppVersion; use release_channel::AppVersion;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde::{Deserialize, Serialize, de::DeserializeOwned};
@ -118,6 +117,7 @@ pub struct State {
client: Arc<Client>, client: Arc<Client>,
llm_api_token: LlmApiToken, llm_api_token: LlmApiToken,
user_store: Entity<UserStore>, user_store: Entity<UserStore>,
cloud_user_store: Entity<CloudUserStore>,
status: client::Status, status: client::Status,
accept_terms_of_service_task: Option<Task<Result<()>>>, accept_terms_of_service_task: Option<Task<Result<()>>>,
models: Vec<Arc<cloud_llm_client::LanguageModel>>, models: Vec<Arc<cloud_llm_client::LanguageModel>>,
@ -133,6 +133,7 @@ impl State {
fn new( fn new(
client: Arc<Client>, client: Arc<Client>,
user_store: Entity<UserStore>, user_store: Entity<UserStore>,
cloud_user_store: Entity<CloudUserStore>,
status: client::Status, status: client::Status,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Self { ) -> Self {
@ -142,6 +143,7 @@ impl State {
client: client.clone(), client: client.clone(),
llm_api_token: LlmApiToken::default(), llm_api_token: LlmApiToken::default(),
user_store, user_store,
cloud_user_store,
status, status,
accept_terms_of_service_task: None, accept_terms_of_service_task: None,
models: Vec::new(), models: Vec::new(),
@ -150,12 +152,19 @@ impl State {
recommended_models: Vec::new(), recommended_models: Vec::new(),
_fetch_models_task: cx.spawn(async move |this, cx| { _fetch_models_task: cx.spawn(async move |this, cx| {
maybe!(async move { maybe!(async move {
let (client, llm_api_token) = this let (client, cloud_user_store, llm_api_token) =
.read_with(cx, |this, _cx| (client.clone(), this.llm_api_token.clone()))?; this.read_with(cx, |this, _cx| {
(
client.clone(),
this.cloud_user_store.clone(),
this.llm_api_token.clone(),
)
})?;
loop { loop {
let status = this.read_with(cx, |this, _cx| this.status)?; let is_authenticated =
if matches!(status, client::Status::Connected { .. }) { cloud_user_store.read_with(cx, |this, _cx| this.is_authenticated())?;
if is_authenticated {
break; break;
} }
@ -194,8 +203,8 @@ impl State {
} }
} }
fn is_signed_out(&self) -> bool { fn is_signed_out(&self, cx: &App) -> bool {
self.status.is_signed_out() !self.cloud_user_store.read(cx).is_authenticated()
} }
fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> { fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<()>> {
@ -210,10 +219,7 @@ impl State {
} }
fn has_accepted_terms_of_service(&self, cx: &App) -> bool { fn has_accepted_terms_of_service(&self, cx: &App) -> bool {
self.user_store self.cloud_user_store.read(cx).has_accepted_tos()
.read(cx)
.current_user_has_accepted_terms()
.unwrap_or(false)
} }
fn accept_terms_of_service(&mut self, cx: &mut Context<Self>) { fn accept_terms_of_service(&mut self, cx: &mut Context<Self>) {
@ -297,11 +303,24 @@ impl State {
} }
impl CloudLanguageModelProvider { impl CloudLanguageModelProvider {
pub fn new(user_store: Entity<UserStore>, client: Arc<Client>, cx: &mut App) -> Self { pub fn new(
user_store: Entity<UserStore>,
cloud_user_store: Entity<CloudUserStore>,
client: Arc<Client>,
cx: &mut App,
) -> Self {
let mut status_rx = client.status(); let mut status_rx = client.status();
let status = *status_rx.borrow(); let status = *status_rx.borrow();
let state = cx.new(|cx| State::new(client.clone(), user_store.clone(), status, cx)); let state = cx.new(|cx| {
State::new(
client.clone(),
user_store.clone(),
cloud_user_store.clone(),
status,
cx,
)
});
let state_ref = state.downgrade(); let state_ref = state.downgrade();
let maintain_client_status = cx.spawn(async move |cx| { let maintain_client_status = cx.spawn(async move |cx| {
@ -398,7 +417,7 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
fn is_authenticated(&self, cx: &App) -> bool { fn is_authenticated(&self, cx: &App) -> bool {
let state = self.state.read(cx); let state = self.state.read(cx);
!state.is_signed_out() && state.has_accepted_terms_of_service(cx) !state.is_signed_out(cx) && state.has_accepted_terms_of_service(cx)
} }
fn authenticate(&self, _cx: &mut App) -> Task<Result<(), AuthenticateError>> { fn authenticate(&self, _cx: &mut App) -> Task<Result<(), AuthenticateError>> {
@ -614,9 +633,9 @@ impl CloudLanguageModel {
.and_then(|plan| cloud_llm_client::Plan::from_str(plan).ok()) .and_then(|plan| cloud_llm_client::Plan::from_str(plan).ok())
{ {
let plan = match plan { let plan = match plan {
cloud_llm_client::Plan::ZedFree => Plan::Free, cloud_llm_client::Plan::ZedFree => proto::Plan::Free,
cloud_llm_client::Plan::ZedPro => Plan::ZedPro, cloud_llm_client::Plan::ZedPro => proto::Plan::ZedPro,
cloud_llm_client::Plan::ZedProTrial => Plan::ZedProTrial, cloud_llm_client::Plan::ZedProTrial => proto::Plan::ZedProTrial,
}; };
return Err(anyhow!(ModelRequestLimitReachedError { plan })); return Err(anyhow!(ModelRequestLimitReachedError { plan }));
} }
@ -1118,7 +1137,7 @@ fn response_lines<T: DeserializeOwned>(
#[derive(IntoElement, RegisterComponent)] #[derive(IntoElement, RegisterComponent)]
struct ZedAiConfiguration { struct ZedAiConfiguration {
is_connected: bool, is_connected: bool,
plan: Option<proto::Plan>, plan: Option<Plan>,
subscription_period: Option<(DateTime<Utc>, DateTime<Utc>)>, subscription_period: Option<(DateTime<Utc>, DateTime<Utc>)>,
eligible_for_trial: bool, eligible_for_trial: bool,
has_accepted_terms_of_service: bool, has_accepted_terms_of_service: bool,
@ -1132,15 +1151,15 @@ impl RenderOnce for ZedAiConfiguration {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let young_account_banner = YoungAccountBanner; let young_account_banner = YoungAccountBanner;
let is_pro = self.plan == Some(proto::Plan::ZedPro); let is_pro = self.plan == Some(Plan::ZedPro);
let subscription_text = match (self.plan, self.subscription_period) { let subscription_text = match (self.plan, self.subscription_period) {
(Some(proto::Plan::ZedPro), Some(_)) => { (Some(Plan::ZedPro), Some(_)) => {
"You have access to Zed's hosted models through your Pro subscription." "You have access to Zed's hosted models through your Pro subscription."
} }
(Some(proto::Plan::ZedProTrial), Some(_)) => { (Some(Plan::ZedProTrial), Some(_)) => {
"You have access to Zed's hosted models through your Pro trial." "You have access to Zed's hosted models through your Pro trial."
} }
(Some(proto::Plan::Free), Some(_)) => { (Some(Plan::ZedFree), Some(_)) => {
"You have basic access to Zed's hosted models through the Free plan." "You have basic access to Zed's hosted models through the Free plan."
} }
_ => { _ => {
@ -1262,15 +1281,15 @@ impl ConfigurationView {
impl Render for ConfigurationView { impl Render for ConfigurationView {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let state = self.state.read(cx); let state = self.state.read(cx);
let user_store = state.user_store.read(cx); let cloud_user_store = state.cloud_user_store.read(cx);
ZedAiConfiguration { ZedAiConfiguration {
is_connected: !state.is_signed_out(), is_connected: !state.is_signed_out(cx),
plan: user_store.current_plan(), plan: cloud_user_store.plan(),
subscription_period: user_store.subscription_period(), subscription_period: cloud_user_store.subscription_period(),
eligible_for_trial: user_store.trial_started_at().is_none(), eligible_for_trial: cloud_user_store.trial_started_at().is_none(),
has_accepted_terms_of_service: state.has_accepted_terms_of_service(cx), has_accepted_terms_of_service: state.has_accepted_terms_of_service(cx),
account_too_young: user_store.account_too_young(), account_too_young: cloud_user_store.account_too_young(),
accept_terms_of_service_in_progress: state.accept_terms_of_service_task.is_some(), accept_terms_of_service_in_progress: state.accept_terms_of_service_task.is_some(),
accept_terms_of_service_callback: self.accept_terms_of_service_callback.clone(), accept_terms_of_service_callback: self.accept_terms_of_service_callback.clone(),
sign_in_callback: self.sign_in_callback.clone(), sign_in_callback: self.sign_in_callback.clone(),
@ -1286,7 +1305,7 @@ impl Component for ZedAiConfiguration {
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> { fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
fn configuration( fn configuration(
is_connected: bool, is_connected: bool,
plan: Option<proto::Plan>, plan: Option<Plan>,
eligible_for_trial: bool, eligible_for_trial: bool,
account_too_young: bool, account_too_young: bool,
has_accepted_terms_of_service: bool, has_accepted_terms_of_service: bool,
@ -1330,15 +1349,15 @@ impl Component for ZedAiConfiguration {
), ),
single_example( single_example(
"Free Plan", "Free Plan",
configuration(true, Some(proto::Plan::Free), true, false, true), configuration(true, Some(Plan::ZedFree), true, false, true),
), ),
single_example( single_example(
"Zed Pro Trial Plan", "Zed Pro Trial Plan",
configuration(true, Some(proto::Plan::ZedProTrial), true, false, true), configuration(true, Some(Plan::ZedProTrial), true, false, true),
), ),
single_example( single_example(
"Zed Pro Plan", "Zed Pro Plan",
configuration(true, Some(proto::Plan::ZedPro), true, false, true), configuration(true, Some(Plan::ZedPro), true, false, true),
), ),
]) ])
.into_any_element(), .into_any_element(),

View file

@ -556,7 +556,12 @@ pub fn main() {
); );
supermaven::init(app_state.client.clone(), cx); supermaven::init(app_state.client.clone(), cx);
language_model::init(app_state.client.clone(), cx); language_model::init(app_state.client.clone(), cx);
language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx); language_models::init(
app_state.user_store.clone(),
app_state.cloud_user_store.clone(),
app_state.client.clone(),
cx,
);
agent_settings::init(cx); agent_settings::init(cx);
agent_servers::init(cx); agent_servers::init(cx);
web_search::init(cx); web_search::init(cx);

View file

@ -4488,7 +4488,12 @@ mod tests {
); );
image_viewer::init(cx); image_viewer::init(cx);
language_model::init(app_state.client.clone(), cx); language_model::init(app_state.client.clone(), cx);
language_models::init(app_state.user_store.clone(), app_state.client.clone(), cx); language_models::init(
app_state.user_store.clone(),
app_state.cloud_user_store.clone(),
app_state.client.clone(),
cx,
);
web_search::init(cx); web_search::init(cx);
web_search_providers::init(app_state.client.clone(), cx); web_search_providers::init(app_state.client.clone(), cx);
let prompt_builder = PromptBuilder::load(app_state.fs.clone(), false, cx); let prompt_builder = PromptBuilder::load(app_state.fs.clone(), false, cx);

View file

@ -17,9 +17,10 @@ pub fn load_preview_thread_store(
cx: &mut AsyncApp, cx: &mut AsyncApp,
) -> Task<Result<Entity<ThreadStore>>> { ) -> Task<Result<Entity<ThreadStore>>> {
workspace workspace
.update(cx, |_, cx| { .update(cx, |workspace, cx| {
ThreadStore::load( ThreadStore::load(
project.clone(), project.clone(),
workspace.app_state().cloud_user_store.clone(),
cx.new(|_| ToolWorkingSet::default()), cx.new(|_| ToolWorkingSet::default()),
None, None,
Arc::new(PromptBuilder::new(None).unwrap()), Arc::new(PromptBuilder::new(None).unwrap()),