Extract an agent_ui crate from agent (#33284)

Max Brunsfeld created

This PR moves the UI-dependent logic in the `agent` crate into its own
crate, `agent_ui`. The remaining `agent` crate no longer depends on
`editor`, `picker`, `ui`, `workspace`, etc.

This has compile time benefits, but the main motivation is to isolate
our core agentic logic, so that we can make agents more
pluggable/configurable.

Release Notes:

- N/A

Change summary

Cargo.lock                                                                            | 119 
Cargo.toml                                                                            |   2 
crates/agent/Cargo.toml                                                               |  40 
crates/agent/src/agent.rs                                                             | 309 
crates/agent/src/agent_profile.rs                                                     |   7 
crates/agent/src/context.rs                                                           |  84 
crates/agent/src/context_server_tool.rs                                               |   2 
crates/agent/src/context_store.rs                                                     |  73 
crates/agent/src/history_store.rs                                                     |  16 
crates/agent/src/thread.rs                                                            |  64 
crates/agent/src/thread_store.rs                                                      |  38 
crates/agent/src/tool_use.rs                                                          |  19 
crates/agent_ui/Cargo.toml                                                            | 107 
crates/agent_ui/LICENSE-GPL                                                           |   1 
crates/agent_ui/src/active_thread.rs                                                  |  34 
crates/agent_ui/src/agent_configuration.rs                                            |   0 
crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs             |   0 
crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs                      |   2 
crates/agent_ui/src/agent_configuration/manage_profiles_modal/profile_modal_header.rs |   0 
crates/agent_ui/src/agent_configuration/tool_picker.rs                                |   0 
crates/agent_ui/src/agent_diff.rs                                                     |   6 
crates/agent_ui/src/agent_model_selector.rs                                           |   0 
crates/agent_ui/src/agent_panel.rs                                                    |  62 
crates/agent_ui/src/agent_ui.rs                                                       | 285 
crates/agent_ui/src/buffer_codegen.rs                                                 |   9 
crates/agent_ui/src/context_picker.rs                                                 |  10 
crates/agent_ui/src/context_picker/completion_provider.rs                             |  11 
crates/agent_ui/src/context_picker/fetch_context_picker.rs                            |   2 
crates/agent_ui/src/context_picker/file_context_picker.rs                             |   2 
crates/agent_ui/src/context_picker/rules_context_picker.rs                            |   4 
crates/agent_ui/src/context_picker/symbol_context_picker.rs                           |   4 
crates/agent_ui/src/context_picker/thread_context_picker.rs                           |   8 
crates/agent_ui/src/context_server_configuration.rs                                   |   0 
crates/agent_ui/src/context_strip.rs                                                  |  72 
crates/agent_ui/src/debug.rs                                                          |   0 
crates/agent_ui/src/inline_assistant.rs                                               |  36 
crates/agent_ui/src/inline_prompt_editor.rs                                           |   9 
crates/agent_ui/src/message_editor.rs                                                 |  95 
crates/agent_ui/src/profile_selector.rs                                               |  13 
crates/agent_ui/src/slash_command_settings.rs                                         |   0 
crates/agent_ui/src/terminal_codegen.rs                                               |   0 
crates/agent_ui/src/terminal_inline_assistant.rs                                      |   8 
crates/agent_ui/src/thread_history.rs                                                 |  10 
crates/agent_ui/src/tool_compatibility.rs                                             |   6 
crates/agent_ui/src/ui.rs                                                             |   0 
crates/agent_ui/src/ui/agent_notification.rs                                          |   0 
crates/agent_ui/src/ui/animated_label.rs                                              |   0 
crates/agent_ui/src/ui/context_pill.rs                                                |   2 
crates/agent_ui/src/ui/max_mode_tooltip.rs                                            |   0 
crates/agent_ui/src/ui/onboarding_modal.rs                                            |   0 
crates/agent_ui/src/ui/preview.rs                                                     |   0 
crates/agent_ui/src/ui/preview/agent_preview.rs                                       |   0 
crates/agent_ui/src/ui/preview/usage_callouts.rs                                      |   0 
crates/agent_ui/src/ui/upsell.rs                                                      |   0 
crates/eval/Cargo.toml                                                                |   1 
crates/eval/src/eval.rs                                                               |   2 
crates/eval/src/example.rs                                                            |   2 
crates/zed/Cargo.toml                                                                 |   1 
crates/zed/src/main.rs                                                                |   2 
crates/zed/src/zed.rs                                                                 |  12 
crates/zed/src/zed/component_preview.rs                                               |  23 
crates/zed/src/zed/component_preview/preview_support/active_thread.rs                 |   3 
62 files changed, 865 insertions(+), 752 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -53,6 +53,89 @@ dependencies = [
 name = "agent"
 version = "0.1.0"
 dependencies = [
+ "agent_settings",
+ "anyhow",
+ "assistant_context_editor",
+ "assistant_tool",
+ "assistant_tools",
+ "chrono",
+ "client",
+ "collections",
+ "component",
+ "context_server",
+ "convert_case 0.8.0",
+ "feature_flags",
+ "fs",
+ "futures 0.3.31",
+ "git",
+ "gpui",
+ "heed",
+ "http_client",
+ "icons",
+ "indoc",
+ "itertools 0.14.0",
+ "language",
+ "language_model",
+ "log",
+ "paths",
+ "postage",
+ "pretty_assertions",
+ "project",
+ "prompt_store",
+ "proto",
+ "rand 0.8.5",
+ "ref-cast",
+ "rope",
+ "schemars",
+ "serde",
+ "serde_json",
+ "settings",
+ "smol",
+ "sqlez",
+ "telemetry",
+ "text",
+ "theme",
+ "thiserror 2.0.12",
+ "time",
+ "util",
+ "uuid",
+ "workspace",
+ "workspace-hack",
+ "zed_llm_client",
+ "zstd",
+]
+
+[[package]]
+name = "agent_settings"
+version = "0.1.0"
+dependencies = [
+ "anthropic",
+ "anyhow",
+ "collections",
+ "deepseek",
+ "fs",
+ "gpui",
+ "language_model",
+ "lmstudio",
+ "log",
+ "mistral",
+ "ollama",
+ "open_ai",
+ "paths",
+ "schemars",
+ "serde",
+ "serde_json",
+ "serde_json_lenient",
+ "settings",
+ "workspace-hack",
+ "zed_llm_client",
+]
+
+[[package]]
+name = "agent_ui"
+version = "0.1.0"
+dependencies = [
+ "agent",
  "agent_settings",
  "anyhow",
  "assistant_context_editor",
@@ -67,7 +150,6 @@ dependencies = [
  "collections",
  "component",
  "context_server",
- "convert_case 0.8.0",
  "db",
  "editor",
  "extension",
@@ -77,9 +159,7 @@ dependencies = [
  "fs",
  "futures 0.3.31",
  "fuzzy",
- "git",
  "gpui",
- "heed",
  "html_to_markdown",
  "http_client",
  "indexed_docs",
@@ -99,13 +179,11 @@ dependencies = [
  "parking_lot",
  "paths",
  "picker",
- "postage",
  "pretty_assertions",
  "project",
  "prompt_store",
  "proto",
  "rand 0.8.5",
- "ref-cast",
  "release_channel",
  "rope",
  "rules_library",
@@ -116,7 +194,6 @@ dependencies = [
  "serde_json_lenient",
  "settings",
  "smol",
- "sqlez",
  "streaming_diff",
  "telemetry",
  "telemetry_events",
@@ -124,7 +201,6 @@ dependencies = [
  "terminal_view",
  "text",
  "theme",
- "thiserror 2.0.12",
  "time",
  "time_format",
  "ui",
@@ -136,33 +212,6 @@ dependencies = [
  "workspace-hack",
  "zed_actions",
  "zed_llm_client",
- "zstd",
-]
-
-[[package]]
-name = "agent_settings"
-version = "0.1.0"
-dependencies = [
- "anthropic",
- "anyhow",
- "collections",
- "deepseek",
- "fs",
- "gpui",
- "language_model",
- "lmstudio",
- "log",
- "mistral",
- "ollama",
- "open_ai",
- "paths",
- "schemars",
- "serde",
- "serde_json",
- "serde_json_lenient",
- "settings",
- "workspace-hack",
- "zed_llm_client",
 ]
 
 [[package]]
@@ -5062,6 +5111,7 @@ version = "0.1.0"
 dependencies = [
  "agent",
  "agent_settings",
+ "agent_ui",
  "anyhow",
  "assistant_tool",
  "assistant_tools",
@@ -19868,6 +19918,7 @@ dependencies = [
  "activity_indicator",
  "agent",
  "agent_settings",
+ "agent_ui",
  "anyhow",
  "ashpd",
  "askpass",

Cargo.toml 🔗

@@ -2,6 +2,7 @@
 resolver = "2"
 members = [
     "crates/activity_indicator",
+    "crates/agent_ui",
     "crates/agent",
     "crates/agent_settings",
     "crates/anthropic",
@@ -214,6 +215,7 @@ edition = "2024"
 
 activity_indicator = { path = "crates/activity_indicator" }
 agent = { path = "crates/agent" }
+agent_ui = { path = "crates/agent_ui" }
 agent_settings = { path = "crates/agent_settings" }
 ai = { path = "crates/ai" }
 anthropic = { path = "crates/anthropic" }

crates/agent/Cargo.toml 🔗

@@ -22,93 +22,57 @@ test-support = [
 agent_settings.workspace = true
 anyhow.workspace = true
 assistant_context_editor.workspace = true
-assistant_slash_command.workspace = true
-assistant_slash_commands.workspace = true
 assistant_tool.workspace = true
-audio.workspace = true
-buffer_diff.workspace = true
 chrono.workspace = true
 client.workspace = true
 collections.workspace = true
 component.workspace = true
 context_server.workspace = true
 convert_case.workspace = true
-db.workspace = true
-editor.workspace = true
-extension.workspace = true
-extension_host.workspace = true
 feature_flags.workspace = true
-file_icons.workspace = true
 fs.workspace = true
 futures.workspace = true
-fuzzy.workspace = true
 git.workspace = true
 gpui.workspace = true
 heed.workspace = true
-html_to_markdown.workspace = true
+icons.workspace = true
 indoc.workspace = true
 http_client.workspace = true
-indexed_docs.workspace = true
-inventory.workspace = true
 itertools.workspace = true
-jsonschema.workspace = true
 language.workspace = true
 language_model.workspace = true
 log.workspace = true
-lsp.workspace = true
-markdown.workspace = true
-menu.workspace = true
-multi_buffer.workspace = true
-notifications.workspace = true
-ordered-float.workspace = true
-parking_lot.workspace = true
 paths.workspace = true
-picker.workspace = true
 postage.workspace = true
 project.workspace = true
 prompt_store.workspace = true
 proto.workspace = true
 ref-cast.workspace = true
-release_channel.workspace = true
 rope.workspace = true
-rules_library.workspace = true
 schemars.workspace = true
-search.workspace = true
 serde.workspace = true
 serde_json.workspace = true
-serde_json_lenient.workspace = true
 settings.workspace = true
 smol.workspace = true
 sqlez.workspace = true
-streaming_diff.workspace = true
 telemetry.workspace = true
-telemetry_events.workspace = true
-terminal.workspace = true
-terminal_view.workspace = true
 text.workspace = true
 theme.workspace = true
 thiserror.workspace = true
 time.workspace = true
-time_format.workspace = true
-ui.workspace = true
-urlencoding.workspace = true
 util.workspace = true
 uuid.workspace = true
-watch.workspace = true
 workspace-hack.workspace = true
-workspace.workspace = true
-zed_actions.workspace = true
 zed_llm_client.workspace = true
 zstd.workspace = true
 
 [dev-dependencies]
 assistant_tools.workspace = true
-buffer_diff = { workspace = true, features = ["test-support"] }
-editor = { workspace = true, features = ["test-support"] }
 gpui = { workspace = true, "features" = ["test-support"] }
 indoc.workspace = true
 language = { workspace = true, "features" = ["test-support"] }
 language_model = { workspace = true, "features" = ["test-support"] }
 pretty_assertions.workspace = true
 project = { workspace = true, features = ["test-support"] }
+workspace = { workspace = true, features = ["test-support"] }
 rand.workspace = true

crates/agent/src/agent.rs 🔗

@@ -1,297 +1,20 @@
-mod active_thread;
-mod agent_configuration;
-mod agent_diff;
-mod agent_model_selector;
-mod agent_panel;
-mod agent_profile;
-mod buffer_codegen;
-mod context;
-mod context_picker;
-mod context_server_configuration;
-mod context_server_tool;
-mod context_store;
-mod context_strip;
-mod debug;
-mod history_store;
-mod inline_assistant;
-mod inline_prompt_editor;
-mod message_editor;
-mod profile_selector;
-mod slash_command_settings;
-mod terminal_codegen;
-mod terminal_inline_assistant;
-mod thread;
-mod thread_history;
-mod thread_store;
-mod tool_compatibility;
-mod tool_use;
-mod ui;
-
-use std::sync::Arc;
-
-use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
-use assistant_slash_command::SlashCommandRegistry;
-use client::Client;
-use feature_flags::FeatureFlagAppExt as _;
-use fs::Fs;
-use gpui::{App, Entity, actions, impl_actions};
-use language::LanguageRegistry;
-use language_model::{
-    ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
-};
-use prompt_store::PromptBuilder;
-use schemars::JsonSchema;
-use serde::Deserialize;
-use settings::{Settings as _, SettingsStore};
-use thread::ThreadId;
-
-pub use crate::active_thread::ActiveThread;
-use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
-pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
-pub use crate::context::{ContextLoadResult, LoadedContext};
-pub use crate::inline_assistant::InlineAssistant;
-use crate::slash_command_settings::SlashCommandSettings;
-pub use crate::thread::{Message, MessageSegment, Thread, ThreadEvent};
-pub use crate::thread_store::{SerializedThread, TextThreadStore, ThreadStore};
-pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
+pub mod agent_profile;
+pub mod context;
+pub mod context_server_tool;
+pub mod context_store;
+pub mod history_store;
+pub mod thread;
+pub mod thread_store;
+pub mod tool_use;
+
+pub use context::{AgentContext, ContextId, ContextLoadResult};
 pub use context_store::ContextStore;
-pub use ui::preview::{all_agent_previews, get_agent_preview};
-
-actions!(
-    agent,
-    [
-        NewTextThread,
-        ToggleContextPicker,
-        ToggleNavigationMenu,
-        ToggleOptionsMenu,
-        DeleteRecentlyOpenThread,
-        ToggleProfileSelector,
-        RemoveAllContext,
-        ExpandMessageEditor,
-        OpenHistory,
-        AddContextServer,
-        RemoveSelectedThread,
-        Chat,
-        ChatWithFollow,
-        CycleNextInlineAssist,
-        CyclePreviousInlineAssist,
-        FocusUp,
-        FocusDown,
-        FocusLeft,
-        FocusRight,
-        RemoveFocusedContext,
-        AcceptSuggestedContext,
-        OpenActiveThreadAsMarkdown,
-        OpenAgentDiff,
-        Keep,
-        Reject,
-        RejectAll,
-        KeepAll,
-        Follow,
-        ResetTrialUpsell,
-        ResetTrialEndUpsell,
-        ContinueThread,
-        ContinueWithBurnMode,
-        ToggleBurnMode,
-    ]
-);
-
-#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema)]
-pub struct NewThread {
-    #[serde(default)]
-    from_thread_id: Option<ThreadId>,
-}
-
-#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
-pub struct ManageProfiles {
-    #[serde(default)]
-    pub customize_tools: Option<AgentProfileId>,
-}
-
-impl ManageProfiles {
-    pub fn customize_tools(profile_id: AgentProfileId) -> Self {
-        Self {
-            customize_tools: Some(profile_id),
-        }
-    }
-}
-
-impl_actions!(agent, [NewThread, ManageProfiles]);
-
-#[derive(Clone)]
-pub(crate) enum ModelUsageContext {
-    Thread(Entity<Thread>),
-    InlineAssistant,
-}
-
-impl ModelUsageContext {
-    pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
-        match self {
-            Self::Thread(thread) => thread.read(cx).configured_model(),
-            Self::InlineAssistant => {
-                LanguageModelRegistry::read_global(cx).inline_assistant_model()
-            }
-        }
-    }
-
-    pub fn language_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> {
-        self.configured_model(cx)
-            .map(|configured_model| configured_model.model)
-    }
-}
-
-/// Initializes the `agent` crate.
-pub fn init(
-    fs: Arc<dyn Fs>,
-    client: Arc<Client>,
-    prompt_builder: Arc<PromptBuilder>,
-    language_registry: Arc<LanguageRegistry>,
-    is_eval: bool,
-    cx: &mut App,
-) {
-    AgentSettings::register(cx);
-    SlashCommandSettings::register(cx);
+pub use thread::{
+    LastRestoreCheckpoint, Message, MessageCrease, MessageId, MessageSegment, Thread, ThreadError,
+    ThreadEvent, ThreadFeedback, ThreadId, ThreadSummary, TokenUsageRatio,
+};
+pub use thread_store::{SerializedThread, TextThreadStore, ThreadStore};
 
-    assistant_context_editor::init(client.clone(), cx);
-    rules_library::init(cx);
-    if !is_eval {
-        // Initializing the language model from the user settings messes with the eval, so we only initialize them when
-        // we're not running inside of the eval.
-        init_language_model_settings(cx);
-    }
-    assistant_slash_command::init(cx);
+pub fn init(cx: &mut gpui::App) {
     thread_store::init(cx);
-    agent_panel::init(cx);
-    context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
-
-    register_slash_commands(cx);
-    inline_assistant::init(
-        fs.clone(),
-        prompt_builder.clone(),
-        client.telemetry().clone(),
-        cx,
-    );
-    terminal_inline_assistant::init(
-        fs.clone(),
-        prompt_builder.clone(),
-        client.telemetry().clone(),
-        cx,
-    );
-    indexed_docs::init(cx);
-    cx.observe_new(move |workspace, window, cx| {
-        ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
-    })
-    .detach();
-    cx.observe_new(ManageProfilesModal::register).detach();
-}
-
-fn init_language_model_settings(cx: &mut App) {
-    update_active_language_model_from_settings(cx);
-
-    cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
-        .detach();
-    cx.subscribe(
-        &LanguageModelRegistry::global(cx),
-        |_, event: &language_model::Event, cx| match event {
-            language_model::Event::ProviderStateChanged
-            | language_model::Event::AddedProvider(_)
-            | language_model::Event::RemovedProvider(_) => {
-                update_active_language_model_from_settings(cx);
-            }
-            _ => {}
-        },
-    )
-    .detach();
-}
-
-fn update_active_language_model_from_settings(cx: &mut App) {
-    let settings = AgentSettings::get_global(cx);
-
-    fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
-        language_model::SelectedModel {
-            provider: LanguageModelProviderId::from(selection.provider.0.clone()),
-            model: LanguageModelId::from(selection.model.clone()),
-        }
-    }
-
-    let default = to_selected_model(&settings.default_model);
-    let inline_assistant = settings
-        .inline_assistant_model
-        .as_ref()
-        .map(to_selected_model);
-    let commit_message = settings
-        .commit_message_model
-        .as_ref()
-        .map(to_selected_model);
-    let thread_summary = settings
-        .thread_summary_model
-        .as_ref()
-        .map(to_selected_model);
-    let inline_alternatives = settings
-        .inline_alternatives
-        .iter()
-        .map(to_selected_model)
-        .collect::<Vec<_>>();
-
-    LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
-        registry.select_default_model(Some(&default), cx);
-        registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
-        registry.select_commit_message_model(commit_message.as_ref(), cx);
-        registry.select_thread_summary_model(thread_summary.as_ref(), cx);
-        registry.select_inline_alternative_models(inline_alternatives, cx);
-    });
-}
-
-fn register_slash_commands(cx: &mut App) {
-    let slash_command_registry = SlashCommandRegistry::global(cx);
-
-    slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
-    slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
-    slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
-    slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
-    slash_command_registry
-        .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
-    slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
-    slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
-    slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
-    slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
-    slash_command_registry
-        .register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
-    slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
-
-    cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
-        let slash_command_registry = slash_command_registry.clone();
-        move |is_enabled, _cx| {
-            if is_enabled {
-                slash_command_registry.register_command(
-                    assistant_slash_commands::StreamingExampleSlashCommand,
-                    false,
-                );
-            }
-        }
-    })
-    .detach();
-
-    update_slash_commands_from_settings(cx);
-    cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
-        .detach();
-}
-
-fn update_slash_commands_from_settings(cx: &mut App) {
-    let slash_command_registry = SlashCommandRegistry::global(cx);
-    let settings = SlashCommandSettings::get_global(cx);
-
-    if settings.docs.enabled {
-        slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
-    } else {
-        slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
-    }
-
-    if settings.cargo_workspace.enabled {
-        slash_command_registry
-            .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
-    } else {
-        slash_command_registry
-            .unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
-    }
 }

crates/agent/src/agent_profile.rs 🔗

@@ -5,9 +5,8 @@ use assistant_tool::{Tool, ToolSource, ToolWorkingSet};
 use collections::IndexMap;
 use convert_case::{Case, Casing};
 use fs::Fs;
-use gpui::{App, Entity};
+use gpui::{App, Entity, SharedString};
 use settings::{Settings, update_settings_file};
-use ui::SharedString;
 use util::ResultExt;
 
 #[derive(Clone, Debug, Eq, PartialEq)]
@@ -108,11 +107,11 @@ mod tests {
     use agent_settings::ContextServerPreset;
     use assistant_tool::ToolRegistry;
     use collections::IndexMap;
+    use gpui::SharedString;
     use gpui::{AppContext, TestAppContext};
     use http_client::FakeHttpClient;
     use project::Project;
     use settings::{Settings, SettingsStore};
-    use ui::SharedString;
 
     use super::*;
 
@@ -302,7 +301,7 @@ mod tests {
             unimplemented!()
         }
 
-        fn icon(&self) -> ui::IconName {
+        fn icon(&self) -> icons::IconName {
             unimplemented!()
         }
 

crates/agent/src/context.rs 🔗

@@ -1,30 +1,25 @@
-use std::fmt::{self, Display, Formatter, Write as _};
-use std::hash::{Hash, Hasher};
-use std::path::PathBuf;
-use std::{ops::Range, path::Path, sync::Arc};
-
+use crate::thread::Thread;
 use assistant_context_editor::AssistantContext;
 use assistant_tool::outline;
-use collections::{HashMap, HashSet};
-use editor::display_map::CreaseId;
-use editor::{Addon, Editor};
+use collections::HashSet;
 use futures::future;
 use futures::{FutureExt, future::Shared};
-use gpui::{App, AppContext as _, Entity, SharedString, Subscription, Task};
+use gpui::{App, AppContext as _, ElementId, Entity, SharedString, Task};
+use icons::IconName;
 use language::{Buffer, ParseStatus};
 use language_model::{LanguageModelImage, LanguageModelRequestMessage, MessageContent};
 use project::{Project, ProjectEntryId, ProjectPath, Worktree};
 use prompt_store::{PromptStore, UserPromptId};
 use ref_cast::RefCast;
 use rope::Point;
+use std::fmt::{self, Display, Formatter, Write as _};
+use std::hash::{Hash, Hasher};
+use std::path::PathBuf;
+use std::{ops::Range, path::Path, sync::Arc};
 use text::{Anchor, OffsetRangeExt as _};
-use ui::{Context, ElementId, IconName};
 use util::markdown::MarkdownCodeBlock;
 use util::{ResultExt as _, post_inc};
 
-use crate::context_store::{ContextStore, ContextStoreEvent};
-use crate::thread::Thread;
-
 pub const RULES_ICON: IconName = IconName::Context;
 
 pub enum ContextKind {
@@ -1117,69 +1112,6 @@ impl Hash for AgentContextKey {
     }
 }
 
-#[derive(Default)]
-pub struct ContextCreasesAddon {
-    creases: HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>>,
-    _subscription: Option<Subscription>,
-}
-
-impl Addon for ContextCreasesAddon {
-    fn to_any(&self) -> &dyn std::any::Any {
-        self
-    }
-
-    fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
-        Some(self)
-    }
-}
-
-impl ContextCreasesAddon {
-    pub fn new() -> Self {
-        Self {
-            creases: HashMap::default(),
-            _subscription: None,
-        }
-    }
-
-    pub fn add_creases(
-        &mut self,
-        context_store: &Entity<ContextStore>,
-        key: AgentContextKey,
-        creases: impl IntoIterator<Item = (CreaseId, SharedString)>,
-        cx: &mut Context<Editor>,
-    ) {
-        self.creases.entry(key).or_default().extend(creases);
-        self._subscription = Some(cx.subscribe(
-            &context_store,
-            |editor, _, event, cx| match event {
-                ContextStoreEvent::ContextRemoved(key) => {
-                    let Some(this) = editor.addon_mut::<Self>() else {
-                        return;
-                    };
-                    let (crease_ids, replacement_texts): (Vec<_>, Vec<_>) = this
-                        .creases
-                        .remove(key)
-                        .unwrap_or_default()
-                        .into_iter()
-                        .unzip();
-                    let ranges = editor
-                        .remove_creases(crease_ids, cx)
-                        .into_iter()
-                        .map(|(_, range)| range)
-                        .collect::<Vec<_>>();
-                    editor.unfold_ranges(&ranges, false, false, cx);
-                    editor.edit(ranges.into_iter().zip(replacement_texts), cx);
-                    cx.notify();
-                }
-            },
-        ))
-    }
-
-    pub fn into_inner(self) -> HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>> {
-        self.creases
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;

crates/agent/src/context_server_tool.rs 🔗

@@ -4,9 +4,9 @@ use anyhow::{Result, anyhow, bail};
 use assistant_tool::{ActionLog, Tool, ToolResult, ToolSource};
 use context_server::{ContextServerId, types};
 use gpui::{AnyWindowHandle, App, Entity, Task};
+use icons::IconName;
 use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchemaFormat};
 use project::{Project, context_server_store::ContextServerStore};
-use ui::IconName;
 
 pub struct ContextServerTool {
     store: Entity<ContextServerStore>,

crates/agent/src/context_store.rs 🔗

@@ -1,7 +1,12 @@
-use std::ops::Range;
-use std::path::{Path, PathBuf};
-use std::sync::Arc;
-
+use crate::{
+    context::{
+        AgentContextHandle, AgentContextKey, ContextId, ContextKind, DirectoryContextHandle,
+        FetchedUrlContext, FileContextHandle, ImageContext, RulesContextHandle,
+        SelectionContextHandle, SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle,
+    },
+    thread::{MessageId, Thread, ThreadId},
+    thread_store::ThreadStore,
+};
 use anyhow::{Context as _, Result, anyhow};
 use assistant_context_editor::AssistantContext;
 use collections::{HashSet, IndexSet};
@@ -9,20 +14,15 @@ use futures::{self, FutureExt};
 use gpui::{App, Context, Entity, EventEmitter, Image, SharedString, Task, WeakEntity};
 use language::{Buffer, File as _};
 use language_model::LanguageModelImage;
-use project::image_store::is_image_file;
-use project::{Project, ProjectItem, ProjectPath, Symbol};
+use project::{Project, ProjectItem, ProjectPath, Symbol, image_store::is_image_file};
 use prompt_store::UserPromptId;
 use ref_cast::RefCast as _;
-use text::{Anchor, OffsetRangeExt};
-
-use crate::ThreadStore;
-use crate::context::{
-    AgentContextHandle, AgentContextKey, ContextId, DirectoryContextHandle, FetchedUrlContext,
-    FileContextHandle, ImageContext, RulesContextHandle, SelectionContextHandle,
-    SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle,
+use std::{
+    ops::Range,
+    path::{Path, PathBuf},
+    sync::Arc,
 };
-use crate::context_strip::SuggestedContext;
-use crate::thread::{MessageId, Thread, ThreadId};
+use text::{Anchor, OffsetRangeExt};
 
 pub struct ContextStore {
     project: WeakEntity<Project>,
@@ -561,6 +561,49 @@ impl ContextStore {
     }
 }
 
+#[derive(Clone)]
+pub enum SuggestedContext {
+    File {
+        name: SharedString,
+        icon_path: Option<SharedString>,
+        buffer: WeakEntity<Buffer>,
+    },
+    Thread {
+        name: SharedString,
+        thread: WeakEntity<Thread>,
+    },
+    TextThread {
+        name: SharedString,
+        context: WeakEntity<AssistantContext>,
+    },
+}
+
+impl SuggestedContext {
+    pub fn name(&self) -> &SharedString {
+        match self {
+            Self::File { name, .. } => name,
+            Self::Thread { name, .. } => name,
+            Self::TextThread { name, .. } => name,
+        }
+    }
+
+    pub fn icon_path(&self) -> Option<SharedString> {
+        match self {
+            Self::File { icon_path, .. } => icon_path.clone(),
+            Self::Thread { .. } => None,
+            Self::TextThread { .. } => None,
+        }
+    }
+
+    pub fn kind(&self) -> ContextKind {
+        match self {
+            Self::File { .. } => ContextKind::File,
+            Self::Thread { .. } => ContextKind::Thread,
+            Self::TextThread { .. } => ContextKind::TextThread,
+        }
+    }
+}
+
 pub enum FileInclusion {
     Direct,
     InDirectory { full_path: PathBuf },

crates/agent/src/history_store.rs 🔗

@@ -1,21 +1,17 @@
-use std::{collections::VecDeque, path::Path, sync::Arc};
-
+use crate::{
+    ThreadId,
+    thread_store::{SerializedThreadMetadata, ThreadStore},
+};
 use anyhow::{Context as _, Result};
 use assistant_context_editor::SavedContextMetadata;
 use chrono::{DateTime, Utc};
-use gpui::{AsyncApp, Entity, SharedString, Task, prelude::*};
+use gpui::{App, AsyncApp, Entity, SharedString, Task, prelude::*};
 use itertools::Itertools;
 use paths::contexts_dir;
 use serde::{Deserialize, Serialize};
-use std::time::Duration;
-use ui::App;
+use std::{collections::VecDeque, path::Path, sync::Arc, time::Duration};
 use util::ResultExt as _;
 
-use crate::{
-    thread::ThreadId,
-    thread_store::{SerializedThreadMetadata, ThreadStore},
-};
-
 const MAX_RECENTLY_OPENED_ENTRIES: usize = 6;
 const NAVIGATION_HISTORY_PATH: &str = "agent-navigation-history.json";
 const SAVE_RECENTLY_OPENED_ENTRIES_DEBOUNCE: Duration = Duration::from_millis(50);

crates/agent/src/thread.rs 🔗

@@ -1,22 +1,25 @@
-use std::io::Write;
-use std::ops::Range;
-use std::sync::Arc;
-use std::time::Instant;
-
+use crate::{
+    agent_profile::AgentProfile,
+    context::{AgentContext, AgentContextHandle, ContextLoadResult, LoadedContext},
+    thread_store::{
+        SerializedCrease, SerializedLanguageModel, SerializedMessage, SerializedMessageSegment,
+        SerializedThread, SerializedToolResult, SerializedToolUse, SharedProjectContext,
+        ThreadStore,
+    },
+    tool_use::{PendingToolUse, ToolUse, ToolUseMetadata, ToolUseState},
+};
 use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
 use anyhow::{Result, anyhow};
 use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};
 use chrono::{DateTime, Utc};
 use client::{ModelRequestUsage, RequestUsage};
 use collections::{HashMap, HashSet};
-use editor::display_map::CreaseMetadata;
 use feature_flags::{self, FeatureFlagAppExt};
-use futures::future::Shared;
-use futures::{FutureExt, StreamExt as _};
+use futures::{FutureExt, StreamExt as _, future::Shared};
 use git::repository::DiffType;
 use gpui::{
     AnyWindowHandle, App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task,
-    WeakEntity,
+    WeakEntity, Window,
 };
 use language_model::{
     ConfiguredModel, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,
@@ -27,29 +30,21 @@ use language_model::{
     TokenUsage,
 };
 use postage::stream::Stream as _;
-use project::Project;
-use project::git_store::{GitStore, GitStoreCheckpoint, RepositoryState};
+use project::{
+    Project,
+    git_store::{GitStore, GitStoreCheckpoint, RepositoryState},
+};
 use prompt_store::{ModelContext, PromptBuilder};
 use proto::Plan;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::Settings;
+use std::{io::Write, ops::Range, sync::Arc, time::Instant};
 use thiserror::Error;
-use ui::Window;
 use util::{ResultExt as _, post_inc};
-
 use uuid::Uuid;
 use zed_llm_client::{CompletionIntent, CompletionRequestStatus, UsageLimit};
 
-use crate::ThreadStore;
-use crate::agent_profile::AgentProfile;
-use crate::context::{AgentContext, AgentContextHandle, ContextLoadResult, LoadedContext};
-use crate::thread_store::{
-    SerializedCrease, SerializedLanguageModel, SerializedMessage, SerializedMessageSegment,
-    SerializedThread, SerializedToolResult, SerializedToolUse, SharedProjectContext,
-};
-use crate::tool_use::{PendingToolUse, ToolUse, ToolUseMetadata, ToolUseState};
-
 #[derive(
     Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, JsonSchema,
 )]
@@ -98,13 +93,18 @@ impl MessageId {
     fn post_inc(&mut self) -> Self {
         Self(post_inc(&mut self.0))
     }
+
+    pub fn as_usize(&self) -> usize {
+        self.0
+    }
 }
 
 /// Stored information that can be used to resurrect a context crease when creating an editor for a past message.
 #[derive(Clone, Debug)]
 pub struct MessageCrease {
     pub range: Range<usize>,
-    pub metadata: CreaseMetadata,
+    pub icon_path: SharedString,
+    pub label: SharedString,
     /// None for a deserialized message, Some otherwise.
     pub context: Option<AgentContextHandle>,
 }
@@ -540,10 +540,8 @@ impl Thread {
                         .into_iter()
                         .map(|crease| MessageCrease {
                             range: crease.start..crease.end,
-                            metadata: CreaseMetadata {
-                                icon_path: crease.icon_path,
-                                label: crease.label,
-                            },
+                            icon_path: crease.icon_path,
+                            label: crease.label,
                             context: None,
                         })
                         .collect(),
@@ -1170,8 +1168,8 @@ impl Thread {
                             .map(|crease| SerializedCrease {
                                 start: crease.range.start,
                                 end: crease.range.end,
-                                icon_path: crease.metadata.icon_path.clone(),
-                                label: crease.metadata.label.clone(),
+                                icon_path: crease.icon_path.clone(),
+                                label: crease.label.clone(),
                             })
                             .collect(),
                         is_hidden: message.is_hidden,
@@ -2997,11 +2995,13 @@ fn resolve_tool_name_conflicts(tools: &[Arc<dyn Tool>]) -> Vec<(String, Arc<dyn
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{ThreadStore, context::load_context, context_store::ContextStore, thread_store};
+    use crate::{
+        context::load_context, context_store::ContextStore, thread_store, thread_store::ThreadStore,
+    };
     use agent_settings::{AgentProfileId, AgentSettings, LanguageModelParameters};
     use assistant_tool::ToolRegistry;
-    use editor::EditorSettings;
     use gpui::TestAppContext;
+    use icons::IconName;
     use language_model::fake_provider::{FakeLanguageModel, FakeLanguageModelProvider};
     use project::{FakeFs, Project};
     use prompt_store::PromptBuilder;
@@ -3009,7 +3009,6 @@ mod tests {
     use settings::{Settings, SettingsStore};
     use std::sync::Arc;
     use theme::ThemeSettings;
-    use ui::IconName;
     use util::path;
     use workspace::Workspace;
 
@@ -3837,7 +3836,6 @@ fn main() {{
             workspace::init_settings(cx);
             language_model::init_settings(cx);
             ThemeSettings::register(cx);
-            EditorSettings::register(cx);
             ToolRegistry::default_global(cx);
         });
     }

crates/agent/src/thread_store.rs 🔗

@@ -1,22 +1,25 @@
-use std::cell::{Ref, RefCell};
-use std::path::{Path, PathBuf};
-use std::rc::Rc;
-use std::sync::{Arc, Mutex};
-
+use crate::{
+    context_server_tool::ContextServerTool,
+    thread::{
+        DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,
+    },
+};
 use agent_settings::{AgentProfileId, CompletionMode};
 use anyhow::{Context as _, Result, anyhow};
 use assistant_tool::{ToolId, ToolWorkingSet};
 use chrono::{DateTime, Utc};
 use collections::HashMap;
 use context_server::ContextServerId;
-use futures::channel::{mpsc, oneshot};
-use futures::future::{self, BoxFuture, Shared};
-use futures::{FutureExt as _, StreamExt as _};
+use futures::{
+    FutureExt as _, StreamExt as _,
+    channel::{mpsc, oneshot},
+    future::{self, BoxFuture, Shared},
+};
 use gpui::{
     App, BackgroundExecutor, Context, Entity, EventEmitter, Global, ReadGlobal, SharedString,
-    Subscription, Task, prelude::*,
+    Subscription, Task, Window, prelude::*,
 };
-
+use indoc::indoc;
 use language_model::{LanguageModelToolResultContent, LanguageModelToolUseId, Role, TokenUsage};
 use project::context_server_store::{ContextServerStatus, ContextServerStore};
 use project::{Project, ProjectItem, ProjectPath, Worktree};
@@ -25,19 +28,18 @@ use prompt_store::{
     UserRulesContext, WorktreeContext,
 };
 use serde::{Deserialize, Serialize};
-use ui::Window;
-use util::ResultExt as _;
-
-use crate::context_server_tool::ContextServerTool;
-use crate::thread::{
-    DetailedSummaryState, ExceededWindowError, MessageId, ProjectSnapshot, Thread, ThreadId,
-};
-use indoc::indoc;
 use sqlez::{
     bindable::{Bind, Column},
     connection::Connection,
     statement::Statement,
 };
+use std::{
+    cell::{Ref, RefCell},
+    path::{Path, PathBuf},
+    rc::Rc,
+    sync::{Arc, Mutex},
+};
+use util::ResultExt as _;
 
 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
 pub enum DataType {

crates/agent/src/tool_use.rs 🔗

@@ -1,24 +1,23 @@
-use std::sync::Arc;
-
+use crate::{
+    thread::{MessageId, PromptId, ThreadId},
+    thread_store::SerializedMessage,
+};
 use anyhow::Result;
 use assistant_tool::{
     AnyToolCard, Tool, ToolResultContent, ToolResultOutput, ToolUseStatus, ToolWorkingSet,
 };
 use collections::HashMap;
-use futures::FutureExt as _;
-use futures::future::Shared;
-use gpui::{App, Entity, SharedString, Task};
+use futures::{FutureExt as _, future::Shared};
+use gpui::{App, Entity, SharedString, Task, Window};
+use icons::IconName;
 use language_model::{
     ConfiguredModel, LanguageModel, LanguageModelRequest, LanguageModelToolResult,
     LanguageModelToolResultContent, LanguageModelToolUse, LanguageModelToolUseId, Role,
 };
 use project::Project;
-use ui::{IconName, Window};
+use std::sync::Arc;
 use util::truncate_lines_to_byte_limit;
 
-use crate::thread::{MessageId, PromptId, ThreadId};
-use crate::thread_store::SerializedMessage;
-
 #[derive(Debug)]
 pub struct ToolUse {
     pub id: LanguageModelToolUseId,
@@ -26,7 +25,7 @@ pub struct ToolUse {
     pub ui_text: SharedString,
     pub status: ToolUseStatus,
     pub input: serde_json::Value,
-    pub icon: ui::IconName,
+    pub icon: icons::IconName,
     pub needs_confirmation: bool,
 }
 

crates/agent_ui/Cargo.toml 🔗

@@ -0,0 +1,107 @@
+[package]
+name = "agent_ui"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/agent_ui.rs"
+doctest = false
+
+[features]
+test-support = [
+    "gpui/test-support",
+    "language/test-support",
+]
+
+[dependencies]
+agent.workspace = true
+agent_settings.workspace = true
+anyhow.workspace = true
+assistant_context_editor.workspace = true
+assistant_slash_command.workspace = true
+assistant_slash_commands.workspace = true
+assistant_tool.workspace = true
+audio.workspace = true
+buffer_diff.workspace = true
+chrono.workspace = true
+client.workspace = true
+collections.workspace = true
+component.workspace = true
+context_server.workspace = true
+db.workspace = true
+editor.workspace = true
+extension.workspace = true
+extension_host.workspace = true
+feature_flags.workspace = true
+file_icons.workspace = true
+fs.workspace = true
+futures.workspace = true
+fuzzy.workspace = true
+gpui.workspace = true
+html_to_markdown.workspace = true
+indoc.workspace = true
+http_client.workspace = true
+indexed_docs.workspace = true
+inventory.workspace = true
+itertools.workspace = true
+jsonschema.workspace = true
+language.workspace = true
+language_model.workspace = true
+log.workspace = true
+lsp.workspace = true
+markdown.workspace = true
+menu.workspace = true
+multi_buffer.workspace = true
+notifications.workspace = true
+ordered-float.workspace = true
+parking_lot.workspace = true
+paths.workspace = true
+picker.workspace = true
+project.workspace = true
+prompt_store.workspace = true
+proto.workspace = true
+release_channel.workspace = true
+rope.workspace = true
+rules_library.workspace = true
+schemars.workspace = true
+search.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+serde_json_lenient.workspace = true
+settings.workspace = true
+smol.workspace = true
+streaming_diff.workspace = true
+telemetry.workspace = true
+telemetry_events.workspace = true
+terminal.workspace = true
+terminal_view.workspace = true
+text.workspace = true
+theme.workspace = true
+time.workspace = true
+time_format.workspace = true
+ui.workspace = true
+urlencoding.workspace = true
+util.workspace = true
+uuid.workspace = true
+watch.workspace = true
+workspace-hack.workspace = true
+workspace.workspace = true
+zed_actions.workspace = true
+zed_llm_client.workspace = true
+
+[dev-dependencies]
+assistant_tools.workspace = true
+buffer_diff = { workspace = true, features = ["test-support"] }
+editor = { workspace = true, features = ["test-support"] }
+gpui = { workspace = true, "features" = ["test-support"] }
+indoc.workspace = true
+language = { workspace = true, "features" = ["test-support"] }
+language_model = { workspace = true, "features" = ["test-support"] }
+pretty_assertions.workspace = true
+project = { workspace = true, features = ["test-support"] }
+rand.workspace = true

crates/agent/src/active_thread.rs → crates/agent_ui/src/active_thread.rs 🔗

@@ -1,18 +1,17 @@
-use crate::context::{AgentContextHandle, RULES_ICON};
 use crate::context_picker::{ContextPicker, MentionLink};
-use crate::context_store::ContextStore;
 use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
 use crate::message_editor::{extract_message_creases, insert_message_creases};
-use crate::thread::{
-    LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, Thread, ThreadError,
-    ThreadEvent, ThreadFeedback, ThreadSummary,
-};
-use crate::thread_store::{RulesLoadingError, TextThreadStore, ThreadStore};
-use crate::tool_use::{PendingToolUseStatus, ToolUse};
 use crate::ui::{
     AddedContext, AgentNotification, AgentNotificationEvent, AnimatedLabel, ContextPill,
 };
 use crate::{AgentPanel, ModelUsageContext};
+use agent::{
+    ContextStore, LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, TextThreadStore,
+    Thread, ThreadError, ThreadEvent, ThreadFeedback, ThreadStore, ThreadSummary,
+    context::{self, AgentContextHandle, RULES_ICON},
+    thread_store::RulesLoadingError,
+    tool_use::{PendingToolUseStatus, ToolUse},
+};
 use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
 use anyhow::Context as _;
 use assistant_tool::ToolUseStatus;
@@ -1583,8 +1582,7 @@ impl ActiveThread {
         let git_store = project.read(cx).git_store().clone();
         let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
 
-        let load_context_task =
-            crate::context::load_context(new_context, &project, &prompt_store, cx);
+        let load_context_task = context::load_context(new_context, &project, &prompt_store, cx);
         self._load_edited_message_context_task =
             Some(cx.spawn_in(window, async move |this, cx| {
                 let (context, checkpoint) =
@@ -1737,7 +1735,7 @@ impl ActiveThread {
             telemetry::event!(
                 "Assistant Thread Feedback Comments",
                 thread_id,
-                message_id = message_id.0,
+                message_id = message_id.as_usize(),
                 message_content,
                 comments = comments_value
             );
@@ -3723,8 +3721,10 @@ fn open_editor_at_position(
 
 #[cfg(test)]
 mod tests {
+    use super::*;
+    use agent::{MessageSegment, context::ContextLoadResult, thread_store};
     use assistant_tool::{ToolRegistry, ToolWorkingSet};
-    use editor::{EditorSettings, display_map::CreaseMetadata};
+    use editor::EditorSettings;
     use fs::FakeFs;
     use gpui::{AppContext, TestAppContext, VisualTestContext};
     use language_model::{
@@ -3738,10 +3738,6 @@ mod tests {
     use util::path;
     use workspace::CollaboratorId;
 
-    use crate::{ContextLoadResult, thread::MessageSegment, thread_store};
-
-    use super::*;
-
     #[gpui::test]
     async fn test_agent_is_unfollowed_after_cancelling_completion(cx: &mut TestAppContext) {
         init_test_settings(cx);
@@ -3813,10 +3809,8 @@ mod tests {
 
         let creases = vec![MessageCrease {
             range: 14..22,
-            metadata: CreaseMetadata {
-                icon_path: "icon".into(),
-                label: "foo.txt".into(),
-            },
+            icon_path: "icon".into(),
+            label: "foo.txt".into(),
             context: None,
         }];
 

crates/agent/src/agent_configuration/manage_profiles_modal.rs → crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs 🔗

@@ -15,8 +15,8 @@ use workspace::{ModalView, Workspace};
 
 use crate::agent_configuration::manage_profiles_modal::profile_modal_header::ProfileModalHeader;
 use crate::agent_configuration::tool_picker::{ToolPicker, ToolPickerDelegate};
-use crate::agent_profile::AgentProfile;
 use crate::{AgentPanel, ManageProfiles};
+use agent::agent_profile::AgentProfile;
 
 use super::tool_picker::ToolPickerMode;
 

crates/agent/src/agent_diff.rs → crates/agent_ui/src/agent_diff.rs 🔗

@@ -1,4 +1,5 @@
-use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll, Thread, ThreadEvent};
+use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll};
+use agent::{Thread, ThreadEvent};
 use agent_settings::AgentSettings;
 use anyhow::Result;
 use buffer_diff::DiffHunkStatus;
@@ -1748,7 +1749,8 @@ impl editor::Addon for EditorAgentDiffAddon {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{Keep, ThreadStore, thread_store};
+    use crate::Keep;
+    use agent::thread_store::{self, ThreadStore};
     use agent_settings::AgentSettings;
     use assistant_tool::ToolWorkingSet;
     use editor::EditorSettings;

crates/agent/src/agent_panel.rs → crates/agent_ui/src/agent_panel.rs 🔗

@@ -45,30 +45,34 @@ use ui::{
     PopoverMenuHandle, ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*,
 };
 use util::ResultExt as _;
-use workspace::dock::{DockPosition, Panel, PanelEvent};
 use workspace::{
     CollaboratorId, DraggedSelection, DraggedTab, ToggleZoom, ToolbarItemView, Workspace,
+    dock::{DockPosition, Panel, PanelEvent},
+};
+use zed_actions::{
+    DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
+    agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding},
+    assistant::{OpenRulesLibrary, ToggleFocus},
 };
-use zed_actions::agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding};
-use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};
-use zed_actions::{DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize};
 use zed_llm_client::{CompletionIntent, UsageLimit};
 
-use crate::active_thread::{self, ActiveThread, ActiveThreadEvent};
-use crate::agent_configuration::{AgentConfiguration, AssistantConfigurationEvent};
-use crate::agent_diff::AgentDiff;
-use crate::history_store::{HistoryEntryId, HistoryStore};
-use crate::message_editor::{MessageEditor, MessageEditorEvent};
-use crate::thread::{Thread, ThreadError, ThreadId, ThreadSummary, TokenUsageRatio};
-use crate::thread_history::{HistoryEntryElement, ThreadHistory};
-use crate::thread_store::ThreadStore;
-use crate::ui::AgentOnboardingModal;
 use crate::{
-    AddContextServer, AgentDiffPane, ContextStore, ContinueThread, ContinueWithBurnMode,
+    AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
     DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
     NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
-    ResetTrialUpsell, TextThreadStore, ThreadEvent, ToggleBurnMode, ToggleContextPicker,
-    ToggleNavigationMenu, ToggleOptionsMenu,
+    ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
+    active_thread::{self, ActiveThread, ActiveThreadEvent},
+    agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
+    agent_diff::AgentDiff,
+    message_editor::{MessageEditor, MessageEditorEvent},
+    thread_history::{HistoryEntryElement, ThreadHistory},
+    ui::AgentOnboardingModal,
+};
+use agent::{
+    Thread, ThreadError, ThreadEvent, ThreadId, ThreadSummary, TokenUsageRatio,
+    context_store::ContextStore,
+    history_store::{HistoryEntryId, HistoryStore},
+    thread_store::{TextThreadStore, ThreadStore},
 };
 
 const AGENT_PANEL_KEY: &str = "agent_panel";
@@ -369,7 +373,7 @@ pub struct AgentPanel {
     _default_model_subscription: Subscription,
     context_store: Entity<TextThreadStore>,
     prompt_store: Option<Entity<PromptStore>>,
-    inline_assist_context_store: Entity<crate::context_store::ContextStore>,
+    inline_assist_context_store: Entity<ContextStore>,
     configuration: Option<Entity<AgentConfiguration>>,
     configuration_subscription: Option<Subscription>,
     local_timezone: UtcOffset,
@@ -490,18 +494,10 @@ impl AgentPanel {
         let workspace = workspace.weak_handle();
         let weak_self = cx.entity().downgrade();
 
-        let message_editor_context_store = cx.new(|_cx| {
-            crate::context_store::ContextStore::new(
-                project.downgrade(),
-                Some(thread_store.downgrade()),
-            )
-        });
-        let inline_assist_context_store = cx.new(|_cx| {
-            crate::context_store::ContextStore::new(
-                project.downgrade(),
-                Some(thread_store.downgrade()),
-            )
-        });
+        let message_editor_context_store =
+            cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
+        let inline_assist_context_store =
+            cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
 
         let message_editor = cx.new(|cx| {
             MessageEditor::new(
@@ -708,9 +704,7 @@ impl AgentPanel {
         &self.prompt_store
     }
 
-    pub(crate) fn inline_assist_context_store(
-        &self,
-    ) -> &Entity<crate::context_store::ContextStore> {
+    pub(crate) fn inline_assist_context_store(&self) -> &Entity<ContextStore> {
         &self.inline_assist_context_store
     }
 
@@ -742,7 +736,7 @@ impl AgentPanel {
         self.set_active_view(thread_view, window, cx);
 
         let context_store = cx.new(|_cx| {
-            crate::context_store::ContextStore::new(
+            ContextStore::new(
                 self.project.downgrade(),
                 Some(self.thread_store.downgrade()),
             )
@@ -990,7 +984,7 @@ impl AgentPanel {
         let thread_view = ActiveView::thread(thread.clone(), window, cx);
         self.set_active_view(thread_view, window, cx);
         let context_store = cx.new(|_cx| {
-            crate::context_store::ContextStore::new(
+            ContextStore::new(
                 self.project.downgrade(),
                 Some(self.thread_store.downgrade()),
             )

crates/agent_ui/src/agent_ui.rs 🔗

@@ -0,0 +1,285 @@
+mod active_thread;
+mod agent_configuration;
+mod agent_diff;
+mod agent_model_selector;
+mod agent_panel;
+mod buffer_codegen;
+mod context_picker;
+mod context_server_configuration;
+mod context_strip;
+mod debug;
+mod inline_assistant;
+mod inline_prompt_editor;
+mod message_editor;
+mod profile_selector;
+mod slash_command_settings;
+mod terminal_codegen;
+mod terminal_inline_assistant;
+mod thread_history;
+mod tool_compatibility;
+mod ui;
+
+use std::sync::Arc;
+
+use agent::{Thread, ThreadId};
+use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
+use assistant_slash_command::SlashCommandRegistry;
+use client::Client;
+use feature_flags::FeatureFlagAppExt as _;
+use fs::Fs;
+use gpui::{App, Entity, actions, impl_actions};
+use language::LanguageRegistry;
+use language_model::{
+    ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
+};
+use prompt_store::PromptBuilder;
+use schemars::JsonSchema;
+use serde::Deserialize;
+use settings::{Settings as _, SettingsStore};
+
+pub use crate::active_thread::ActiveThread;
+use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
+pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
+pub use crate::inline_assistant::InlineAssistant;
+use crate::slash_command_settings::SlashCommandSettings;
+pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
+pub use ui::preview::{all_agent_previews, get_agent_preview};
+
+actions!(
+    agent,
+    [
+        NewTextThread,
+        ToggleContextPicker,
+        ToggleNavigationMenu,
+        ToggleOptionsMenu,
+        DeleteRecentlyOpenThread,
+        ToggleProfileSelector,
+        RemoveAllContext,
+        ExpandMessageEditor,
+        OpenHistory,
+        AddContextServer,
+        RemoveSelectedThread,
+        Chat,
+        ChatWithFollow,
+        CycleNextInlineAssist,
+        CyclePreviousInlineAssist,
+        FocusUp,
+        FocusDown,
+        FocusLeft,
+        FocusRight,
+        RemoveFocusedContext,
+        AcceptSuggestedContext,
+        OpenActiveThreadAsMarkdown,
+        OpenAgentDiff,
+        Keep,
+        Reject,
+        RejectAll,
+        KeepAll,
+        Follow,
+        ResetTrialUpsell,
+        ResetTrialEndUpsell,
+        ContinueThread,
+        ContinueWithBurnMode,
+        ToggleBurnMode,
+    ]
+);
+
+#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema)]
+pub struct NewThread {
+    #[serde(default)]
+    from_thread_id: Option<ThreadId>,
+}
+
+#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema)]
+pub struct ManageProfiles {
+    #[serde(default)]
+    pub customize_tools: Option<AgentProfileId>,
+}
+
+impl ManageProfiles {
+    pub fn customize_tools(profile_id: AgentProfileId) -> Self {
+        Self {
+            customize_tools: Some(profile_id),
+        }
+    }
+}
+
+impl_actions!(agent, [NewThread, ManageProfiles]);
+
+#[derive(Clone)]
+pub(crate) enum ModelUsageContext {
+    Thread(Entity<Thread>),
+    InlineAssistant,
+}
+
+impl ModelUsageContext {
+    pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
+        match self {
+            Self::Thread(thread) => thread.read(cx).configured_model(),
+            Self::InlineAssistant => {
+                LanguageModelRegistry::read_global(cx).inline_assistant_model()
+            }
+        }
+    }
+
+    pub fn language_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> {
+        self.configured_model(cx)
+            .map(|configured_model| configured_model.model)
+    }
+}
+
+/// Initializes the `agent` crate.
+pub fn init(
+    fs: Arc<dyn Fs>,
+    client: Arc<Client>,
+    prompt_builder: Arc<PromptBuilder>,
+    language_registry: Arc<LanguageRegistry>,
+    is_eval: bool,
+    cx: &mut App,
+) {
+    AgentSettings::register(cx);
+    SlashCommandSettings::register(cx);
+
+    assistant_context_editor::init(client.clone(), cx);
+    rules_library::init(cx);
+    if !is_eval {
+        // Initializing the language model from the user settings messes with the eval, so we only initialize them when
+        // we're not running inside of the eval.
+        init_language_model_settings(cx);
+    }
+    assistant_slash_command::init(cx);
+    agent::init(cx);
+    agent_panel::init(cx);
+    context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
+
+    register_slash_commands(cx);
+    inline_assistant::init(
+        fs.clone(),
+        prompt_builder.clone(),
+        client.telemetry().clone(),
+        cx,
+    );
+    terminal_inline_assistant::init(
+        fs.clone(),
+        prompt_builder.clone(),
+        client.telemetry().clone(),
+        cx,
+    );
+    indexed_docs::init(cx);
+    cx.observe_new(move |workspace, window, cx| {
+        ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
+    })
+    .detach();
+    cx.observe_new(ManageProfilesModal::register).detach();
+}
+
+fn init_language_model_settings(cx: &mut App) {
+    update_active_language_model_from_settings(cx);
+
+    cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
+        .detach();
+    cx.subscribe(
+        &LanguageModelRegistry::global(cx),
+        |_, event: &language_model::Event, cx| match event {
+            language_model::Event::ProviderStateChanged
+            | language_model::Event::AddedProvider(_)
+            | language_model::Event::RemovedProvider(_) => {
+                update_active_language_model_from_settings(cx);
+            }
+            _ => {}
+        },
+    )
+    .detach();
+}
+
+fn update_active_language_model_from_settings(cx: &mut App) {
+    let settings = AgentSettings::get_global(cx);
+
+    fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
+        language_model::SelectedModel {
+            provider: LanguageModelProviderId::from(selection.provider.0.clone()),
+            model: LanguageModelId::from(selection.model.clone()),
+        }
+    }
+
+    let default = to_selected_model(&settings.default_model);
+    let inline_assistant = settings
+        .inline_assistant_model
+        .as_ref()
+        .map(to_selected_model);
+    let commit_message = settings
+        .commit_message_model
+        .as_ref()
+        .map(to_selected_model);
+    let thread_summary = settings
+        .thread_summary_model
+        .as_ref()
+        .map(to_selected_model);
+    let inline_alternatives = settings
+        .inline_alternatives
+        .iter()
+        .map(to_selected_model)
+        .collect::<Vec<_>>();
+
+    LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
+        registry.select_default_model(Some(&default), cx);
+        registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
+        registry.select_commit_message_model(commit_message.as_ref(), cx);
+        registry.select_thread_summary_model(thread_summary.as_ref(), cx);
+        registry.select_inline_alternative_models(inline_alternatives, cx);
+    });
+}
+
+fn register_slash_commands(cx: &mut App) {
+    let slash_command_registry = SlashCommandRegistry::global(cx);
+
+    slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
+    slash_command_registry
+        .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
+    slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
+    slash_command_registry
+        .register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
+
+    cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
+        let slash_command_registry = slash_command_registry.clone();
+        move |is_enabled, _cx| {
+            if is_enabled {
+                slash_command_registry.register_command(
+                    assistant_slash_commands::StreamingExampleSlashCommand,
+                    false,
+                );
+            }
+        }
+    })
+    .detach();
+
+    update_slash_commands_from_settings(cx);
+    cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
+        .detach();
+}
+
+fn update_slash_commands_from_settings(cx: &mut App) {
+    let slash_command_registry = SlashCommandRegistry::global(cx);
+    let settings = SlashCommandSettings::get_global(cx);
+
+    if settings.docs.enabled {
+        slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
+    } else {
+        slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
+    }
+
+    if settings.cargo_workspace.enabled {
+        slash_command_registry
+            .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
+    } else {
+        slash_command_registry
+            .unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
+    }
+}

crates/agent/src/buffer_codegen.rs → crates/agent_ui/src/buffer_codegen.rs 🔗

@@ -1,6 +1,8 @@
-use crate::context::ContextLoadResult;
 use crate::inline_prompt_editor::CodegenStatus;
-use crate::{context::load_context, context_store::ContextStore};
+use agent::{
+    ContextStore,
+    context::{ContextLoadResult, load_context},
+};
 use agent_settings::AgentSettings;
 use anyhow::{Context as _, Result};
 use client::telemetry::Telemetry;
@@ -18,8 +20,7 @@ use language_model::{
 use multi_buffer::MultiBufferRow;
 use parking_lot::Mutex;
 use project::Project;
-use prompt_store::PromptBuilder;
-use prompt_store::PromptStore;
+use prompt_store::{PromptBuilder, PromptStore};
 use rope::Rope;
 use smol::future::FutureExt;
 use std::{

crates/agent/src/context_picker.rs → crates/agent_ui/src/context_picker.rs 🔗

@@ -37,10 +37,12 @@ use uuid::Uuid;
 use workspace::{Workspace, notifications::NotifyResultExt};
 
 use crate::AgentPanel;
-use crate::context::RULES_ICON;
-use crate::context_store::ContextStore;
-use crate::thread::ThreadId;
-use crate::thread_store::{TextThreadStore, ThreadStore};
+use agent::{
+    ThreadId,
+    context::RULES_ICON,
+    context_store::ContextStore,
+    thread_store::{TextThreadStore, ThreadStore},
+};
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 enum ContextPickerEntry {

crates/agent/src/context_picker/completion_provider.rs → crates/agent_ui/src/context_picker/completion_provider.rs 🔗

@@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
 use std::sync::Arc;
 use std::sync::atomic::AtomicBool;
 
+use agent::context_store::ContextStore;
 use anyhow::Result;
 use editor::{CompletionProvider, Editor, ExcerptId, ToOffset as _};
 use file_icons::FileIcons;
@@ -20,10 +21,11 @@ use ui::prelude::*;
 use util::ResultExt as _;
 use workspace::Workspace;
 
-use crate::Thread;
-use crate::context::{AgentContextHandle, AgentContextKey, ContextCreasesAddon, RULES_ICON};
-use crate::context_store::ContextStore;
-use crate::thread_store::{TextThreadStore, ThreadStore};
+use agent::{
+    Thread,
+    context::{AgentContextHandle, AgentContextKey, RULES_ICON},
+    thread_store::{TextThreadStore, ThreadStore},
+};
 
 use super::fetch_context_picker::fetch_url_content;
 use super::file_context_picker::{FileMatch, search_files};
@@ -35,6 +37,7 @@ use super::{
     ContextPickerAction, ContextPickerEntry, ContextPickerMode, MentionLink, RecentEntry,
     available_context_picker_entries, recent_context_picker_entries, selection_ranges,
 };
+use crate::message_editor::ContextCreasesAddon;
 
 pub(crate) enum Match {
     File(FileMatch),

crates/agent/src/context_picker/fetch_context_picker.rs → crates/agent_ui/src/context_picker/fetch_context_picker.rs 🔗

@@ -2,6 +2,7 @@ use std::cell::RefCell;
 use std::rc::Rc;
 use std::sync::Arc;
 
+use agent::context_store::ContextStore;
 use anyhow::{Context as _, Result, bail};
 use futures::AsyncReadExt as _;
 use gpui::{App, DismissEvent, Entity, FocusHandle, Focusable, Task, WeakEntity};
@@ -12,7 +13,6 @@ use ui::{Context, ListItem, Window, prelude::*};
 use workspace::Workspace;
 
 use crate::context_picker::ContextPicker;
-use crate::context_store::ContextStore;
 
 pub struct FetchContextPicker {
     picker: Entity<Picker<FetchContextPickerDelegate>>,

crates/agent/src/context_picker/file_context_picker.rs → crates/agent_ui/src/context_picker/file_context_picker.rs 🔗

@@ -14,7 +14,7 @@ use util::ResultExt as _;
 use workspace::Workspace;
 
 use crate::context_picker::ContextPicker;
-use crate::context_store::{ContextStore, FileInclusion};
+use agent::context_store::{ContextStore, FileInclusion};
 
 pub struct FileContextPicker {
     picker: Entity<Picker<FileContextPickerDelegate>>,

crates/agent/src/context_picker/rules_context_picker.rs → crates/agent_ui/src/context_picker/rules_context_picker.rs 🔗

@@ -7,9 +7,9 @@ use prompt_store::{PromptId, PromptStore, UserPromptId};
 use ui::{ListItem, prelude::*};
 use util::ResultExt as _;
 
-use crate::context::RULES_ICON;
 use crate::context_picker::ContextPicker;
-use crate::context_store::{self, ContextStore};
+use agent::context::RULES_ICON;
+use agent::context_store::{self, ContextStore};
 
 pub struct RulesContextPicker {
     picker: Entity<Picker<RulesContextPickerDelegate>>,

crates/agent/src/context_picker/symbol_context_picker.rs → crates/agent_ui/src/context_picker/symbol_context_picker.rs 🔗

@@ -14,9 +14,9 @@ use ui::{ListItem, prelude::*};
 use util::ResultExt as _;
 use workspace::Workspace;
 
-use crate::context::AgentContextHandle;
 use crate::context_picker::ContextPicker;
-use crate::context_store::ContextStore;
+use agent::context::AgentContextHandle;
+use agent::context_store::ContextStore;
 
 pub struct SymbolContextPicker {
     picker: Entity<Picker<SymbolContextPickerDelegate>>,

crates/agent/src/context_picker/thread_context_picker.rs → crates/agent_ui/src/context_picker/thread_context_picker.rs 🔗

@@ -9,9 +9,11 @@ use picker::{Picker, PickerDelegate};
 use ui::{ListItem, prelude::*};
 
 use crate::context_picker::ContextPicker;
-use crate::context_store::{self, ContextStore};
-use crate::thread::ThreadId;
-use crate::thread_store::{TextThreadStore, ThreadStore};
+use agent::{
+    ThreadId,
+    context_store::{self, ContextStore},
+    thread_store::{TextThreadStore, ThreadStore},
+};
 
 pub struct ThreadContextPicker {
     picker: Entity<Picker<ThreadContextPickerDelegate>>,

crates/agent/src/context_strip.rs → crates/agent_ui/src/context_strip.rs 🔗

@@ -1,7 +1,15 @@
-use std::path::Path;
-use std::rc::Rc;
-
-use assistant_context_editor::AssistantContext;
+use crate::{
+    AcceptSuggestedContext, AgentPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
+    ModelUsageContext, RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
+    context_picker::ContextPicker,
+    ui::{AddedContext, ContextPill},
+};
+use agent::context_store::SuggestedContext;
+use agent::{
+    context::AgentContextHandle,
+    context_store::ContextStore,
+    thread_store::{TextThreadStore, ThreadStore},
+};
 use collections::HashSet;
 use editor::Editor;
 use file_icons::FileIcons;
@@ -10,22 +18,11 @@ use gpui::{
     Subscription, WeakEntity,
 };
 use itertools::Itertools;
-use language::Buffer;
 use project::ProjectItem;
+use std::{path::Path, rc::Rc};
 use ui::{PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
 use workspace::Workspace;
 
-use crate::context::{AgentContextHandle, ContextKind};
-use crate::context_picker::ContextPicker;
-use crate::context_store::ContextStore;
-use crate::thread::Thread;
-use crate::thread_store::{TextThreadStore, ThreadStore};
-use crate::ui::{AddedContext, ContextPill};
-use crate::{
-    AcceptSuggestedContext, AgentPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
-    ModelUsageContext, RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
-};
-
 pub struct ContextStrip {
     context_store: Entity<ContextStore>,
     context_picker: Entity<ContextPicker>,
@@ -575,46 +572,3 @@ pub enum SuggestContextKind {
     File,
     Thread,
 }
-
-#[derive(Clone)]
-pub enum SuggestedContext {
-    File {
-        name: SharedString,
-        icon_path: Option<SharedString>,
-        buffer: WeakEntity<Buffer>,
-    },
-    Thread {
-        name: SharedString,
-        thread: WeakEntity<Thread>,
-    },
-    TextThread {
-        name: SharedString,
-        context: WeakEntity<AssistantContext>,
-    },
-}
-
-impl SuggestedContext {
-    pub fn name(&self) -> &SharedString {
-        match self {
-            Self::File { name, .. } => name,
-            Self::Thread { name, .. } => name,
-            Self::TextThread { name, .. } => name,
-        }
-    }
-
-    pub fn icon_path(&self) -> Option<SharedString> {
-        match self {
-            Self::File { icon_path, .. } => icon_path.clone(),
-            Self::Thread { .. } => None,
-            Self::TextThread { .. } => None,
-        }
-    }
-
-    pub fn kind(&self) -> ContextKind {
-        match self {
-            Self::File { .. } => ContextKind::File,
-            Self::Thread { .. } => ContextKind::Thread,
-            Self::TextThread { .. } => ContextKind::TextThread,
-        }
-    }
-}

crates/agent/src/inline_assistant.rs → crates/agent_ui/src/inline_assistant.rs 🔗

@@ -4,18 +4,27 @@ use std::ops::Range;
 use std::rc::Rc;
 use std::sync::Arc;
 
+use crate::{
+    AgentPanel,
+    buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent},
+    inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent},
+    terminal_inline_assistant::TerminalInlineAssistant,
+};
+use agent::{
+    context_store::ContextStore,
+    thread_store::{TextThreadStore, ThreadStore},
+};
 use agent_settings::AgentSettings;
 use anyhow::{Context as _, Result};
 use client::telemetry::Telemetry;
 use collections::{HashMap, HashSet, VecDeque, hash_map};
-use editor::display_map::EditorMargins;
 use editor::{
     Anchor, AnchorRangeExt, CodeActionProvider, Editor, EditorEvent, ExcerptId, ExcerptRange,
     MultiBuffer, MultiBufferSnapshot, ToOffset as _, ToPoint,
     actions::SelectAll,
     display_map::{
-        BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock,
-        ToDisplayPoint,
+        BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, EditorMargins,
+        RenderBlock, ToDisplayPoint,
     },
 };
 use fs::Fs;
@@ -24,16 +33,13 @@ use gpui::{
     WeakEntity, Window, point,
 };
 use language::{Buffer, Point, Selection, TransactionId};
-use language_model::ConfigurationError;
-use language_model::ConfiguredModel;
-use language_model::{LanguageModelRegistry, report_assistant_event};
+use language_model::{
+    ConfigurationError, ConfiguredModel, LanguageModelRegistry, report_assistant_event,
+};
 use multi_buffer::MultiBufferRow;
 use parking_lot::Mutex;
-use project::LspAction;
-use project::Project;
-use project::{CodeAction, ProjectTransaction};
-use prompt_store::PromptBuilder;
-use prompt_store::PromptStore;
+use project::{CodeAction, LspAction, Project, ProjectTransaction};
+use prompt_store::{PromptBuilder, PromptStore};
 use settings::{Settings, SettingsStore};
 use telemetry_events::{AssistantEventData, AssistantKind, AssistantPhase};
 use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
@@ -43,14 +49,6 @@ use util::{RangeExt, ResultExt, maybe};
 use workspace::{ItemHandle, Toast, Workspace, dock::Panel, notifications::NotificationId};
 use zed_actions::agent::OpenConfiguration;
 
-use crate::AgentPanel;
-use crate::buffer_codegen::{BufferCodegen, CodegenAlternative, CodegenEvent};
-use crate::context_store::ContextStore;
-use crate::inline_prompt_editor::{CodegenStatus, InlineAssistId, PromptEditor, PromptEditorEvent};
-use crate::terminal_inline_assistant::TerminalInlineAssistant;
-use crate::thread_store::TextThreadStore;
-use crate::thread_store::ThreadStore;
-
 pub fn init(
     fs: Arc<dyn Fs>,
     prompt_builder: Arc<PromptBuilder>,

crates/agent/src/inline_prompt_editor.rs → crates/agent_ui/src/inline_prompt_editor.rs 🔗

@@ -1,14 +1,15 @@
 use crate::agent_model_selector::AgentModelSelector;
 use crate::buffer_codegen::BufferCodegen;
-use crate::context::ContextCreasesAddon;
 use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider};
-use crate::context_store::ContextStore;
 use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
-use crate::message_editor::{extract_message_creases, insert_message_creases};
+use crate::message_editor::{ContextCreasesAddon, extract_message_creases, insert_message_creases};
 use crate::terminal_codegen::TerminalCodegen;
-use crate::thread_store::{TextThreadStore, ThreadStore};
 use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist, ModelUsageContext};
 use crate::{RemoveAllContext, ToggleContextPicker};
+use agent::{
+    context_store::ContextStore,
+    thread_store::{TextThreadStore, ThreadStore},
+};
 use assistant_context_editor::language_model_selector::ToggleModelSelector;
 use client::ErrorExt;
 use collections::VecDeque;

crates/agent/src/message_editor.rs → crates/agent_ui/src/message_editor.rs 🔗

@@ -3,21 +3,25 @@ use std::rc::Rc;
 use std::sync::Arc;
 
 use crate::agent_model_selector::AgentModelSelector;
-use crate::context::{AgentContextKey, ContextCreasesAddon, ContextLoadResult, load_context};
 use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
 use crate::ui::{
     MaxModeTooltip,
     preview::{AgentPreview, UsageCallout},
 };
+use agent::{
+    context::{AgentContextKey, ContextLoadResult, load_context},
+    context_store::ContextStoreEvent,
+};
 use agent_settings::{AgentSettings, CompletionMode};
 use assistant_context_editor::language_model_selector::ToggleModelSelector;
 use buffer_diff::BufferDiff;
 use client::UserStore;
 use collections::{HashMap, HashSet};
 use editor::actions::{MoveUp, Paste};
+use editor::display_map::CreaseId;
 use editor::{
-    AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement, EditorEvent,
-    EditorMode, EditorStyle, MultiBuffer,
+    Addon, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
+    EditorEvent, EditorMode, EditorStyle, MultiBuffer,
 };
 use file_icons::FileIcons;
 use fs::Fs;
@@ -46,16 +50,18 @@ use workspace::{CollaboratorId, Workspace};
 use zed_llm_client::CompletionIntent;
 
 use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
-use crate::context_store::ContextStore;
 use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
 use crate::profile_selector::ProfileSelector;
-use crate::thread::{MessageCrease, Thread, TokenUsageRatio};
-use crate::thread_store::{TextThreadStore, ThreadStore};
 use crate::{
     ActiveThread, AgentDiffPane, Chat, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll,
     ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode,
     ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
 };
+use agent::{
+    MessageCrease, Thread, TokenUsageRatio,
+    context_store::ContextStore,
+    thread_store::{TextThreadStore, ThreadStore},
+};
 
 #[derive(RegisterComponent)]
 pub struct MessageEditor {
@@ -1466,6 +1472,69 @@ impl MessageEditor {
     }
 }
 
+#[derive(Default)]
+pub struct ContextCreasesAddon {
+    creases: HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>>,
+    _subscription: Option<Subscription>,
+}
+
+impl Addon for ContextCreasesAddon {
+    fn to_any(&self) -> &dyn std::any::Any {
+        self
+    }
+
+    fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
+        Some(self)
+    }
+}
+
+impl ContextCreasesAddon {
+    pub fn new() -> Self {
+        Self {
+            creases: HashMap::default(),
+            _subscription: None,
+        }
+    }
+
+    pub fn add_creases(
+        &mut self,
+        context_store: &Entity<ContextStore>,
+        key: AgentContextKey,
+        creases: impl IntoIterator<Item = (CreaseId, SharedString)>,
+        cx: &mut Context<Editor>,
+    ) {
+        self.creases.entry(key).or_default().extend(creases);
+        self._subscription = Some(cx.subscribe(
+            &context_store,
+            |editor, _, event, cx| match event {
+                ContextStoreEvent::ContextRemoved(key) => {
+                    let Some(this) = editor.addon_mut::<Self>() else {
+                        return;
+                    };
+                    let (crease_ids, replacement_texts): (Vec<_>, Vec<_>) = this
+                        .creases
+                        .remove(key)
+                        .unwrap_or_default()
+                        .into_iter()
+                        .unzip();
+                    let ranges = editor
+                        .remove_creases(crease_ids, cx)
+                        .into_iter()
+                        .map(|(_, range)| range)
+                        .collect::<Vec<_>>();
+                    editor.unfold_ranges(&ranges, false, false, cx);
+                    editor.edit(ranges.into_iter().zip(replacement_texts), cx);
+                    cx.notify();
+                }
+            },
+        ))
+    }
+
+    pub fn into_inner(self) -> HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>> {
+        self.creases
+    }
+}
+
 pub fn extract_message_creases(
     editor: &mut Editor,
     cx: &mut Context<'_, Editor>,
@@ -1504,8 +1573,9 @@ pub fn extract_message_creases(
                 let context = contexts_by_crease_id.remove(&id);
                 MessageCrease {
                     range,
-                    metadata,
                     context,
+                    label: metadata.label,
+                    icon_path: metadata.icon_path,
                 }
             })
             .collect()
@@ -1577,8 +1647,8 @@ pub fn insert_message_creases(
             let start = buffer_snapshot.anchor_after(crease.range.start);
             let end = buffer_snapshot.anchor_before(crease.range.end);
             crease_for_mention(
-                crease.metadata.label.clone(),
-                crease.metadata.icon_path.clone(),
+                crease.label.clone(),
+                crease.icon_path.clone(),
                 start..end,
                 cx.weak_entity(),
             )
@@ -1590,12 +1660,7 @@ pub fn insert_message_creases(
         for (crease, id) in message_creases.iter().zip(ids) {
             if let Some(context) = crease.context.as_ref() {
                 let key = AgentContextKey(context.clone());
-                addon.add_creases(
-                    context_store,
-                    key,
-                    vec![(id, crease.metadata.label.clone())],
-                    cx,
-                );
+                addon.add_creases(context_store, key, vec![(id, crease.label.clone())], cx);
             }
         }
     }

crates/agent/src/profile_selector.rs → crates/agent_ui/src/profile_selector.rs 🔗

@@ -1,20 +1,19 @@
-use std::sync::Arc;
-
+use crate::{ManageProfiles, ToggleProfileSelector};
+use agent::{
+    Thread,
+    agent_profile::{AgentProfile, AvailableProfiles},
+};
 use agent_settings::{AgentDockPosition, AgentProfileId, AgentSettings, builtin_profiles};
 use fs::Fs;
 use gpui::{Action, Empty, Entity, FocusHandle, Subscription, prelude::*};
 use language_model::LanguageModelRegistry;
 use settings::{Settings as _, SettingsStore, update_settings_file};
+use std::sync::Arc;
 use ui::{
     ContextMenu, ContextMenuEntry, DocumentationSide, PopoverMenu, PopoverMenuHandle, Tooltip,
     prelude::*,
 };
 
-use crate::{
-    ManageProfiles, Thread, ToggleProfileSelector,
-    agent_profile::{AgentProfile, AvailableProfiles},
-};
-
 pub struct ProfileSelector {
     profiles: AvailableProfiles,
     fs: Arc<dyn Fs>,

crates/agent/src/terminal_inline_assistant.rs → crates/agent_ui/src/terminal_inline_assistant.rs 🔗

@@ -1,10 +1,12 @@
-use crate::context::load_context;
-use crate::context_store::ContextStore;
 use crate::inline_prompt_editor::{
     CodegenStatus, PromptEditor, PromptEditorEvent, TerminalInlineAssistId,
 };
 use crate::terminal_codegen::{CLEAR_INPUT, CodegenEvent, TerminalCodegen};
-use crate::thread_store::{TextThreadStore, ThreadStore};
+use agent::{
+    context::load_context,
+    context_store::ContextStore,
+    thread_store::{TextThreadStore, ThreadStore},
+};
 use agent_settings::AgentSettings;
 use anyhow::{Context as _, Result};
 use client::telemetry::Telemetry;

crates/agent/src/thread_history.rs → crates/agent_ui/src/thread_history.rs 🔗

@@ -1,7 +1,5 @@
-use std::fmt::Display;
-use std::ops::Range;
-use std::sync::Arc;
-
+use crate::{AgentPanel, RemoveSelectedThread};
+use agent::history_store::{HistoryEntry, HistoryStore};
 use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
 use editor::{Editor, EditorEvent};
 use fuzzy::{StringMatch, StringMatchCandidate};
@@ -9,6 +7,7 @@ use gpui::{
     App, ClickEvent, Empty, Entity, FocusHandle, Focusable, ScrollStrategy, Stateful, Task,
     UniformListScrollHandle, WeakEntity, Window, uniform_list,
 };
+use std::{fmt::Display, ops::Range, sync::Arc};
 use time::{OffsetDateTime, UtcOffset};
 use ui::{
     HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Scrollbar, ScrollbarState,
@@ -16,9 +15,6 @@ use ui::{
 };
 use util::ResultExt;
 
-use crate::history_store::{HistoryEntry, HistoryStore};
-use crate::{AgentPanel, RemoveSelectedThread};
-
 pub struct ThreadHistory {
     agent_panel: WeakEntity<AgentPanel>,
     history_store: Entity<HistoryStore>,

crates/agent/src/tool_compatibility.rs → crates/agent_ui/src/tool_compatibility.rs 🔗

@@ -1,13 +1,11 @@
-use std::sync::Arc;
-
+use agent::{Thread, ThreadEvent};
 use assistant_tool::{Tool, ToolSource};
 use collections::HashMap;
 use gpui::{App, Context, Entity, IntoElement, Render, Subscription, Window};
 use language_model::{LanguageModel, LanguageModelToolSchemaFormat};
+use std::sync::Arc;
 use ui::prelude::*;
 
-use crate::{Thread, ThreadEvent};
-
 pub struct IncompatibleToolsState {
     cache: HashMap<LanguageModelToolSchemaFormat, Vec<Arc<dyn Tool>>>,
     thread: Entity<Thread>,

crates/agent/src/ui/context_pill.rs → crates/agent_ui/src/ui/context_pill.rs 🔗

@@ -12,7 +12,7 @@ use prompt_store::PromptStore;
 use rope::Point;
 use ui::{IconButtonShape, Tooltip, prelude::*, tooltip_container};
 
-use crate::context::{
+use agent::context::{
     AgentContext, AgentContextHandle, ContextId, ContextKind, DirectoryContext,
     DirectoryContextHandle, FetchedUrlContext, FileContext, FileContextHandle, ImageContext,
     ImageStatus, RulesContext, RulesContextHandle, SelectionContext, SelectionContextHandle,

crates/eval/Cargo.toml 🔗

@@ -19,6 +19,7 @@ path = "src/explorer.rs"
 
 [dependencies]
 agent.workspace = true
+agent_ui.workspace = true
 agent_settings.workspace = true
 anyhow.workspace = true
 assistant_tool.workspace = true

crates/eval/src/eval.rs 🔗

@@ -423,7 +423,7 @@ pub fn init(cx: &mut App) -> Arc<AgentAppState> {
     terminal_view::init(cx);
     let stdout_is_a_pty = false;
     let prompt_builder = PromptBuilder::load(fs.clone(), stdout_is_a_pty, cx);
-    agent::init(
+    agent_ui::init(
         fs.clone(),
         client.clone(),
         prompt_builder.clone(),

crates/eval/src/example.rs 🔗

@@ -100,7 +100,7 @@ impl ExampleContext {
     pub fn new(
         meta: ExampleMetadata,
         log_prefix: String,
-        agent_thread: Entity<agent::Thread>,
+        agent_thread: Entity<Thread>,
         model: Arc<dyn LanguageModel>,
         app: AsyncApp,
     ) -> Self {

crates/zed/Cargo.toml 🔗

@@ -21,6 +21,7 @@ path = "src/main.rs"
 [dependencies]
 activity_indicator.workspace = true
 agent.workspace = true
+agent_ui.workspace = true
 agent_settings.workspace = true
 anyhow.workspace = true
 askpass.workspace = true

crates/zed/src/main.rs 🔗

@@ -531,7 +531,7 @@ pub fn main() {
             cx,
         );
         let prompt_builder = PromptBuilder::load(app_state.fs.clone(), stdout_is_a_pty(), cx);
-        agent::init(
+        agent_ui::init(
             app_state.fs.clone(),
             app_state.client.clone(),
             prompt_builder.clone(),

crates/zed/src/zed.rs 🔗

@@ -9,7 +9,7 @@ mod quick_action_bar;
 #[cfg(target_os = "windows")]
 pub(crate) mod windows_only_instance;
 
-use agent::AgentDiffToolbar;
+use agent_ui::AgentDiffToolbar;
 use anyhow::Context as _;
 pub use app_menus::*;
 use assets::Assets;
@@ -515,7 +515,7 @@ fn initialize_panels(
         let is_assistant2_enabled = !cfg!(test);
         let agent_panel = if is_assistant2_enabled {
             let agent_panel =
-                agent::AgentPanel::load(workspace_handle.clone(), prompt_builder, cx.clone())
+                agent_ui::AgentPanel::load(workspace_handle.clone(), prompt_builder, cx.clone())
                     .await?;
 
             Some(agent_panel)
@@ -536,13 +536,13 @@ fn initialize_panels(
             // Once we ship `assistant2` we can push this back down into `agent::agent_panel::init`.
             if is_assistant2_enabled {
                 <dyn AgentPanelDelegate>::set_global(
-                    Arc::new(agent::ConcreteAssistantPanelDelegate),
+                    Arc::new(agent_ui::ConcreteAssistantPanelDelegate),
                     cx,
                 );
 
                 workspace
-                    .register_action(agent::AgentPanel::toggle_focus)
-                    .register_action(agent::InlineAssistant::inline_assist);
+                    .register_action(agent_ui::AgentPanel::toggle_focus)
+                    .register_action(agent_ui::InlineAssistant::inline_assist);
             }
         })?;
 
@@ -4320,7 +4320,7 @@ mod tests {
             web_search::init(cx);
             web_search_providers::init(app_state.client.clone(), cx);
             let prompt_builder = PromptBuilder::load(app_state.fs.clone(), false, cx);
-            agent::init(
+            agent_ui::init(
                 app_state.fs.clone(),
                 app_state.client.clone(),
                 prompt_builder.clone(),

crates/zed/src/zed/component_preview.rs 🔗

@@ -5,20 +5,14 @@
 mod persistence;
 mod preview_support;
 
-use std::ops::Range;
-use std::sync::Arc;
-
-use std::iter::Iterator;
-
-use agent::{ActiveThread, TextThreadStore, ThreadStore};
+use agent::{TextThreadStore, ThreadStore};
+use agent_ui::ActiveThread;
 use client::UserStore;
+use collections::HashMap;
 use component::{ComponentId, ComponentMetadata, ComponentStatus, components};
 use gpui::{
     App, Entity, EventEmitter, FocusHandle, Focusable, Task, WeakEntity, Window, list, prelude::*,
 };
-
-use collections::HashMap;
-
 use gpui::{ListState, ScrollHandle, ScrollStrategy, UniformListScrollHandle};
 use languages::LanguageRegistry;
 use notifications::status_toast::{StatusToast, ToastIcon};
@@ -27,11 +21,14 @@ use preview_support::active_thread::{
     load_preview_text_thread_store, load_preview_thread_store, static_active_thread,
 };
 use project::Project;
+use std::{iter::Iterator, ops::Range, sync::Arc};
 use ui::{ButtonLike, Divider, HighlightedLabel, ListItem, ListSubHeader, Tooltip, prelude::*};
 use ui_input::SingleLineInput;
 use util::ResultExt as _;
-use workspace::{AppState, ItemId, SerializableItem, delete_unloaded_items};
-use workspace::{Item, Workspace, WorkspaceId, item::ItemEvent};
+use workspace::{
+    AppState, Item, ItemId, SerializableItem, Workspace, WorkspaceId, delete_unloaded_items,
+    item::ItemEvent,
+};
 
 pub fn init(app_state: Arc<AppState>, cx: &mut App) {
     workspace::register_serializable_item::<ComponentPreview>(cx);
@@ -642,7 +639,7 @@ impl ComponentPreview {
         // Check if the component's scope is Agent
         if scope == ComponentScope::Agent {
             if let Some(active_thread) = self.active_thread.clone() {
-                if let Some(element) = agent::get_agent_preview(
+                if let Some(element) = agent_ui::get_agent_preview(
                     &component.id(),
                     self.workspace.clone(),
                     active_thread,
@@ -1140,7 +1137,7 @@ impl ComponentPreviewPage {
     fn render_preview(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {
         // Try to get agent preview first if we have an active thread
         let maybe_agent_preview = if let Some(active_thread) = self.active_thread.as_ref() {
-            agent::get_agent_preview(
+            agent_ui::get_agent_preview(
                 &self.component.id(),
                 self.workspace.clone(),
                 active_thread.clone(),

crates/zed/src/zed/component_preview/preview_support/active_thread.rs 🔗

@@ -1,4 +1,5 @@
-use agent::{ActiveThread, ContextStore, MessageSegment, TextThreadStore, ThreadStore};
+use agent::{ContextStore, MessageSegment, TextThreadStore, ThreadStore};
+use agent_ui::ActiveThread;
 use anyhow::{Result, anyhow};
 use assistant_tool::ToolWorkingSet;
 use gpui::{AppContext, AsyncApp, Entity, Task, WeakEntity};