diff --git a/Cargo.lock b/Cargo.lock index 0278d21b43..263ec38cf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -372,14 +372,13 @@ name = "assistant" version = "0.1.0" dependencies = [ "anyhow", + "assistant_context_editor", "assistant_settings", "assistant_slash_command", "assistant_slash_commands", "assistant_tool", "async-watch", - "chrono", "client", - "clock", "collections", "command_palette_hooks", "context_server", @@ -403,7 +402,6 @@ dependencies = [ "lsp", "menu", "multi_buffer", - "open_ai", "parking_lot", "paths", "picker", @@ -412,21 +410,16 @@ dependencies = [ "prompt_library", "proto", "rand 0.8.5", - "regex", "rope", - "rpc", "schemars", "search", "semantic_index", "serde", - "serde_json", "serde_json_lenient", "settings", "similar", - "smallvec", "smol", "streaming_diff", - "strum", "telemetry", "telemetry_events", "terminal", @@ -437,7 +430,6 @@ dependencies = [ "ui", "unindent", "util", - "uuid", "workspace", "zed_actions", ] @@ -505,6 +497,53 @@ dependencies = [ "zed_actions", ] +[[package]] +name = "assistant_context_editor" +version = "0.1.0" +dependencies = [ + "anyhow", + "assistant_slash_command", + "assistant_slash_commands", + "assistant_tool", + "chrono", + "client", + "clock", + "collections", + "context_server", + "editor", + "feature_flags", + "fs", + "futures 0.3.31", + "fuzzy", + "gpui", + "language", + "language_model", + "language_models", + "log", + "open_ai", + "parking_lot", + "paths", + "pretty_assertions", + "project", + "prompt_library", + "rand 0.8.5", + "regex", + "rpc", + "serde", + "serde_json", + "settings", + "smallvec", + "smol", + "strum", + "telemetry_events", + "text", + "ui", + "unindent", + "util", + "uuid", + "workspace", +] + [[package]] name = "assistant_settings" version = "0.1.0" @@ -2640,6 +2679,7 @@ dependencies = [ "anthropic", "anyhow", "assistant", + "assistant_context_editor", "assistant_slash_command", "assistant_tool", "async-stripe", diff --git a/Cargo.toml b/Cargo.toml index 8ee5988c17..89e9c44574 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "crates/assets", "crates/assistant", "crates/assistant2", + "crates/assistant_context_editor", "crates/assistant_settings", "crates/assistant_slash_command", "crates/assistant_slash_commands", @@ -204,6 +205,7 @@ anthropic = { path = "crates/anthropic" } assets = { path = "crates/assets" } assistant = { path = "crates/assistant" } assistant2 = { path = "crates/assistant2" } +assistant_context_editor = { path = "crates/assistant_context_editor" } assistant_settings = { path = "crates/assistant_settings" } assistant_slash_command = { path = "crates/assistant_slash_command" } assistant_slash_commands = { path = "crates/assistant_slash_commands" } diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index f076135ef5..79ae691246 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -22,14 +22,13 @@ test-support = [ [dependencies] anyhow.workspace = true +assistant_context_editor.workspace = true assistant_settings.workspace = true assistant_slash_command.workspace = true assistant_slash_commands.workspace = true assistant_tool.workspace = true async-watch.workspace = true -chrono.workspace = true client.workspace = true -clock.workspace = true collections.workspace = true command_palette_hooks.workspace = true context_server.workspace = true @@ -50,27 +49,21 @@ log.workspace = true lsp.workspace = true menu.workspace = true multi_buffer.workspace = true -open_ai = { workspace = true, features = ["schemars"] } parking_lot.workspace = true paths.workspace = true picker.workspace = true project.workspace = true prompt_library.workspace = true proto.workspace = true -regex.workspace = true rope.workspace = true -rpc.workspace = true schemars.workspace = true search.workspace = true semantic_index.workspace = true serde.workspace = true -serde_json.workspace = true settings.workspace = true similar.workspace = true -smallvec.workspace = true smol.workspace = true streaming_diff.workspace = true -strum.workspace = true telemetry.workspace = true telemetry_events.workspace = true terminal.workspace = true @@ -79,7 +72,6 @@ text.workspace = true theme.workspace = true ui.workspace = true util.workspace = true -uuid.workspace = true workspace.workspace = true zed_actions.workspace = true diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index cacca012cb..dcfd15fda8 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -1,12 +1,9 @@ #![cfg_attr(target_os = "windows", allow(unused, dead_code))] pub mod assistant_panel; -mod context; mod context_editor; mod context_history; -pub mod context_store; mod inline_assistant; -mod patch; mod slash_command; pub(crate) mod slash_command_picker; pub mod slash_command_settings; @@ -18,26 +15,23 @@ use std::sync::Arc; use assistant_settings::AssistantSettings; use assistant_slash_command::SlashCommandRegistry; use assistant_slash_commands::{ProjectSlashCommandFeatureFlag, SearchSlashCommandFeatureFlag}; -use client::{proto, Client}; +use client::Client; use command_palette_hooks::CommandPaletteFilter; use feature_flags::FeatureFlagAppExt; use fs::Fs; use gpui::impl_internal_actions; -use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal}; +use gpui::{actions, AppContext, Global, UpdateGlobal}; use language_model::{ LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage, }; use prompt_library::{PromptBuilder, PromptLoadingParams}; use semantic_index::{CloudEmbeddingProvider, SemanticDb}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use settings::{Settings, SettingsStore}; use util::ResultExt; pub use crate::assistant_panel::{AssistantPanel, AssistantPanelEvent}; -pub use crate::context::*; -pub use crate::context_store::*; pub(crate) use crate::inline_assistant::*; -pub use crate::patch::*; use crate::slash_command_settings::SlashCommandSettings; actions!( @@ -72,15 +66,6 @@ impl_internal_actions!(assistant, [InsertDraggedFiles]); const DEFAULT_CONTEXT_LINES: usize = 50; -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct MessageId(clock::Lamport); - -impl MessageId { - pub fn as_u64(self) -> u64 { - self.0.as_u64() - } -} - #[derive(Deserialize, Debug)] pub struct LanguageModelUsage { pub prompt_tokens: u32, @@ -95,55 +80,6 @@ pub struct LanguageModelChoiceDelta { pub finish_reason: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub enum MessageStatus { - Pending, - Done, - Error(SharedString), - Canceled, -} - -impl MessageStatus { - pub fn from_proto(status: proto::ContextMessageStatus) -> MessageStatus { - match status.variant { - Some(proto::context_message_status::Variant::Pending(_)) => MessageStatus::Pending, - Some(proto::context_message_status::Variant::Done(_)) => MessageStatus::Done, - Some(proto::context_message_status::Variant::Error(error)) => { - MessageStatus::Error(error.message.into()) - } - Some(proto::context_message_status::Variant::Canceled(_)) => MessageStatus::Canceled, - None => MessageStatus::Pending, - } - } - - pub fn to_proto(&self) -> proto::ContextMessageStatus { - match self { - MessageStatus::Pending => proto::ContextMessageStatus { - variant: Some(proto::context_message_status::Variant::Pending( - proto::context_message_status::Pending {}, - )), - }, - MessageStatus::Done => proto::ContextMessageStatus { - variant: Some(proto::context_message_status::Variant::Done( - proto::context_message_status::Done {}, - )), - }, - MessageStatus::Error(message) => proto::ContextMessageStatus { - variant: Some(proto::context_message_status::Variant::Error( - proto::context_message_status::Error { - message: message.to_string(), - }, - )), - }, - MessageStatus::Canceled => proto::ContextMessageStatus { - variant: Some(proto::context_message_status::Variant::Canceled( - proto::context_message_status::Canceled {}, - )), - }, - } - } -} - /// The state pertaining to the Assistant. #[derive(Default)] struct Assistant { @@ -214,7 +150,7 @@ pub fn init( }) .detach(); - context_store::init(&client.clone().into()); + assistant_context_editor::init(client.clone(), cx); prompt_library::init(cx); init_language_model_settings(cx); assistant_slash_command::init(cx); diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 4937d97873..987a9c492c 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -4,11 +4,11 @@ use crate::context_editor::{ use crate::context_history::ContextHistory; use crate::{ slash_command::SlashCommandCompletionProvider, - terminal_inline_assistant::TerminalInlineAssistant, Context, ContextId, ContextStore, - ContextStoreEvent, DeployHistory, DeployPromptLibrary, InlineAssistant, InsertDraggedFiles, - NewContext, ToggleFocus, ToggleModelSelector, + terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, DeployPromptLibrary, + InlineAssistant, InsertDraggedFiles, NewContext, ToggleFocus, ToggleModelSelector, }; use anyhow::Result; +use assistant_context_editor::{Context, ContextId, ContextStore, ContextStoreEvent}; use assistant_settings::{AssistantDockPosition, AssistantSettings}; use assistant_slash_command::SlashCommandWorkingSet; use assistant_tool::ToolWorkingSet; diff --git a/crates/assistant/src/context_editor.rs b/crates/assistant/src/context_editor.rs index 33994ed1f9..c39b59aaf0 100644 --- a/crates/assistant/src/context_editor.rs +++ b/crates/assistant/src/context_editor.rs @@ -1,4 +1,9 @@ use anyhow::Result; +use assistant_context_editor::{ + AssistantPatch, AssistantPatchStatus, CacheStatus, Content, Context, ContextEvent, ContextId, + InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, + MessageStatus, ParsedSlashCommand, PendingSlashCommandStatus, RequestType, +}; use assistant_settings::AssistantSettings; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet}; use assistant_slash_commands::{ @@ -58,11 +63,8 @@ use workspace::{ use crate::{ humanize_token_count, slash_command::SlashCommandCompletionProvider, slash_command_picker, - Assist, AssistantPanel, AssistantPatch, AssistantPatchStatus, CacheStatus, ConfirmCommand, - Content, Context, ContextEvent, ContextId, CopyCode, CycleMessageRole, Edit, - InsertDraggedFiles, InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus, - Message, MessageId, MessageMetadata, MessageStatus, ParsedSlashCommand, - PendingSlashCommandStatus, QuoteSelection, RequestType, Split, ToggleModelSelector, + Assist, AssistantPanel, ConfirmCommand, CopyCode, CycleMessageRole, Edit, InsertDraggedFiles, + InsertIntoEditor, QuoteSelection, Split, ToggleModelSelector, }; #[derive(Copy, Clone, Debug, PartialEq)] @@ -138,7 +140,7 @@ impl ContextEditor { cx: &mut ViewContext, ) -> Self { let completion_provider = SlashCommandCompletionProvider::new( - context.read(cx).slash_commands.clone(), + context.read(cx).slash_commands().clone(), Some(cx.view().downgrade()), Some(workspace.clone()), ); @@ -167,8 +169,8 @@ impl ContextEditor { let sections = context.read(cx).slash_command_output_sections().to_vec(); let patch_ranges = context.read(cx).patch_ranges().collect::>(); - let slash_commands = context.read(cx).slash_commands.clone(); - let tools = context.read(cx).tools.clone(); + let slash_commands = context.read(cx).slash_commands().clone(); + let tools = context.read(cx).tools().clone(); let mut this = Self { context, slash_commands, diff --git a/crates/assistant/src/context_history.rs b/crates/assistant/src/context_history.rs index 9d2dc74e60..5003e638d1 100644 --- a/crates/assistant/src/context_history.rs +++ b/crates/assistant/src/context_history.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use assistant_context_editor::{ContextStore, RemoteContextMetadata, SavedContextMetadata}; use gpui::{ AppContext, EventEmitter, FocusHandle, FocusableView, Model, Subscription, Task, View, WeakView, }; @@ -10,7 +11,7 @@ use ui::{prelude::*, Avatar, ListItem, ListItemSpacing}; use workspace::Item; use crate::context_editor::DEFAULT_TAB_TITLE; -use crate::{AssistantPanel, ContextStore, RemoteContextMetadata, SavedContextMetadata}; +use crate::AssistantPanel; #[derive(Clone)] pub enum ContextMetadata { diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 74d20a5ed6..626ba37158 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -1,8 +1,9 @@ use crate::{ humanize_token_count, AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist, - CyclePreviousInlineAssist, RequestType, + CyclePreviousInlineAssist, }; use anyhow::{anyhow, Context as _, Result}; +use assistant_context_editor::RequestType; use assistant_settings::AssistantSettings; use client::{telemetry::Telemetry, ErrorExt}; use collections::{hash_map, HashMap, HashSet, VecDeque}; diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index 237448a41a..0963790c9d 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -1,8 +1,7 @@ use crate::context_editor::ContextEditor; use anyhow::Result; -use assistant_slash_command::AfterCompletion; pub use assistant_slash_command::SlashCommand; -use assistant_slash_command::SlashCommandWorkingSet; +use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet}; use editor::{CompletionProvider, Editor}; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{Model, Task, ViewContext, WeakView, WindowContext}; @@ -28,13 +27,6 @@ pub(crate) struct SlashCommandCompletionProvider { workspace: Option>, } -pub(crate) struct SlashCommandLine { - /// The range within the line containing the command name. - pub name: Range, - /// Ranges within the line containing the command arguments. - pub arguments: Vec>, -} - impl SlashCommandCompletionProvider { pub fn new( slash_commands: Arc, @@ -336,57 +328,3 @@ impl CompletionProvider for SlashCommandCompletionProvider { false } } - -impl SlashCommandLine { - pub(crate) fn parse(line: &str) -> Option { - let mut call: Option = None; - let mut ix = 0; - for c in line.chars() { - let next_ix = ix + c.len_utf8(); - if let Some(call) = &mut call { - // The command arguments start at the first non-whitespace character - // after the command name, and continue until the end of the line. - if let Some(argument) = call.arguments.last_mut() { - if c.is_whitespace() { - if (*argument).is_empty() { - argument.start = next_ix; - argument.end = next_ix; - } else { - argument.end = ix; - call.arguments.push(next_ix..next_ix); - } - } else { - argument.end = next_ix; - } - } - // The command name ends at the first whitespace character. - else if !call.name.is_empty() { - if c.is_whitespace() { - call.arguments = vec![next_ix..next_ix]; - } else { - call.name.end = next_ix; - } - } - // The command name must begin with a letter. - else if c.is_alphabetic() { - call.name.end = next_ix; - } else { - return None; - } - } - // Commands start with a slash. - else if c == '/' { - call = Some(SlashCommandLine { - name: next_ix..next_ix, - arguments: Vec::new(), - }); - } - // The line can't contain anything before the slash except for whitespace. - else if !c.is_whitespace() { - return None; - } - ix = next_ix; - } - call - } -} diff --git a/crates/assistant/src/terminal_inline_assistant.rs b/crates/assistant/src/terminal_inline_assistant.rs index b55a731c47..85db58aa6a 100644 --- a/crates/assistant/src/terminal_inline_assistant.rs +++ b/crates/assistant/src/terminal_inline_assistant.rs @@ -1,7 +1,6 @@ -use crate::{ - humanize_token_count, AssistantPanel, AssistantPanelEvent, RequestType, DEFAULT_CONTEXT_LINES, -}; +use crate::{humanize_token_count, AssistantPanel, AssistantPanelEvent, DEFAULT_CONTEXT_LINES}; use anyhow::{Context as _, Result}; +use assistant_context_editor::RequestType; use assistant_settings::AssistantSettings; use client::telemetry::Telemetry; use collections::{HashMap, VecDeque}; diff --git a/crates/assistant_context_editor/Cargo.toml b/crates/assistant_context_editor/Cargo.toml new file mode 100644 index 0000000000..1b7e4bfacb --- /dev/null +++ b/crates/assistant_context_editor/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "assistant_context_editor" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/assistant_context_editor.rs" + +[dependencies] +anyhow.workspace = true +assistant_slash_command.workspace = true +assistant_slash_commands.workspace = true +assistant_tool.workspace = true +chrono.workspace = true +client.workspace = true +clock.workspace = true +collections.workspace = true +context_server.workspace = true +editor.workspace = true +feature_flags.workspace = true +fs.workspace = true +futures.workspace = true +fuzzy.workspace = true +gpui.workspace = true +language.workspace = true +language_model.workspace = true +language_models.workspace = true +log.workspace = true +open_ai.workspace = true +paths.workspace = true +project.workspace = true +prompt_library.workspace = true +regex.workspace = true +rpc.workspace = true +serde.workspace = true +serde_json.workspace = true +smallvec.workspace = true +smol.workspace = true +strum.workspace = true +telemetry_events.workspace = true +text.workspace = true +ui.workspace = true +util.workspace = true +uuid.workspace = true + +[dev-dependencies] +language_model = { workspace = true, features = ["test-support"] } +parking_lot.workspace = true +pretty_assertions.workspace = true +rand.workspace = true +settings.workspace = true +unindent.workspace = true +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/assistant_context_editor/LICENSE-GPL b/crates/assistant_context_editor/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/assistant_context_editor/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/assistant_context_editor/src/assistant_context_editor.rs b/crates/assistant_context_editor/src/assistant_context_editor.rs new file mode 100644 index 0000000000..6f24de83f7 --- /dev/null +++ b/crates/assistant_context_editor/src/assistant_context_editor.rs @@ -0,0 +1,16 @@ +mod context; +mod context_store; +mod patch; + +use std::sync::Arc; + +use client::Client; +use gpui::AppContext; + +pub use crate::context::*; +pub use crate::context_store::*; +pub use crate::patch::*; + +pub fn init(client: Arc, _cx: &mut AppContext) { + context_store::init(&client.into()); +} diff --git a/crates/assistant/src/context.rs b/crates/assistant_context_editor/src/context.rs similarity index 97% rename from crates/assistant/src/context.rs rename to crates/assistant_context_editor/src/context.rs index 038bc0177d..f2709feb41 100644 --- a/crates/assistant/src/context.rs +++ b/crates/assistant_context_editor/src/context.rs @@ -1,14 +1,11 @@ #[cfg(test)] mod context_tests; -use crate::{ - slash_command::SlashCommandLine, AssistantEdit, AssistantPatch, AssistantPatchStatus, - MessageId, MessageStatus, -}; +use crate::patch::{AssistantEdit, AssistantPatch, AssistantPatchStatus}; use anyhow::{anyhow, Context as _, Result}; use assistant_slash_command::{ - SlashCommandContent, SlashCommandEvent, SlashCommandOutputSection, SlashCommandResult, - SlashCommandWorkingSet, + SlashCommandContent, SlashCommandEvent, SlashCommandLine, SlashCommandOutputSection, + SlashCommandResult, SlashCommandWorkingSet, }; use assistant_slash_commands::FileCommandMetadata; use assistant_tool::ToolWorkingSet; @@ -22,8 +19,6 @@ use gpui::{ AppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage, SharedString, Subscription, Task, }; -use prompt_library::PromptBuilder; - use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset}; use language_model::{ LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent, @@ -38,6 +33,7 @@ use language_models::{ use open_ai::Model as OpenAiModel; use paths::contexts_dir; use project::Project; +use prompt_library::PromptBuilder; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use std::{ @@ -52,9 +48,9 @@ use std::{ }; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use text::{BufferSnapshot, ToPoint}; +use ui::IconName; use util::{post_inc, ResultExt, TryFutureExt}; use uuid::Uuid; -use workspace::ui::IconName; #[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] pub struct ContextId(String); @@ -73,6 +69,64 @@ impl ContextId { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct MessageId(pub clock::Lamport); + +impl MessageId { + pub fn as_u64(self) -> u64 { + self.0.as_u64() + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub enum MessageStatus { + Pending, + Done, + Error(SharedString), + Canceled, +} + +impl MessageStatus { + pub fn from_proto(status: proto::ContextMessageStatus) -> MessageStatus { + match status.variant { + Some(proto::context_message_status::Variant::Pending(_)) => MessageStatus::Pending, + Some(proto::context_message_status::Variant::Done(_)) => MessageStatus::Done, + Some(proto::context_message_status::Variant::Error(error)) => { + MessageStatus::Error(error.message.into()) + } + Some(proto::context_message_status::Variant::Canceled(_)) => MessageStatus::Canceled, + None => MessageStatus::Pending, + } + } + + pub fn to_proto(&self) -> proto::ContextMessageStatus { + match self { + MessageStatus::Pending => proto::ContextMessageStatus { + variant: Some(proto::context_message_status::Variant::Pending( + proto::context_message_status::Pending {}, + )), + }, + MessageStatus::Done => proto::ContextMessageStatus { + variant: Some(proto::context_message_status::Variant::Done( + proto::context_message_status::Done {}, + )), + }, + MessageStatus::Error(message) => proto::ContextMessageStatus { + variant: Some(proto::context_message_status::Variant::Error( + proto::context_message_status::Error { + message: message.to_string(), + }, + )), + }, + MessageStatus::Canceled => proto::ContextMessageStatus { + variant: Some(proto::context_message_status::Variant::Canceled( + proto::context_message_status::Canceled {}, + )), + }, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum RequestType { /// Request a normal chat response from the model. @@ -423,7 +477,7 @@ pub struct MessageCacheMetadata { pub struct MessageMetadata { pub role: Role, pub status: MessageStatus, - pub(crate) timestamp: clock::Lamport, + pub timestamp: clock::Lamport, #[serde(skip)] pub cache: Option, } @@ -544,8 +598,8 @@ pub struct Context { parsed_slash_commands: Vec, invoked_slash_commands: HashMap, edits_since_last_parse: language::Subscription, - pub(crate) slash_commands: Arc, - pub(crate) tools: Arc, + slash_commands: Arc, + tools: Arc, slash_command_output_sections: Vec>, pending_tool_uses_by_id: HashMap, message_anchors: Vec, @@ -790,6 +844,14 @@ impl Context { } } + pub fn slash_commands(&self) -> &Arc { + &self.slash_commands + } + + pub fn tools(&self) -> &Arc { + &self.tools + } + pub fn set_capability( &mut self, capability: language::Capability, @@ -1048,11 +1110,7 @@ impl Context { self.summary.as_ref() } - pub(crate) fn patch_containing( - &self, - position: Point, - cx: &AppContext, - ) -> Option<&AssistantPatch> { + pub fn patch_containing(&self, position: Point, cx: &AppContext) -> Option<&AssistantPatch> { let buffer = self.buffer.read(cx); let index = self.patches.binary_search_by(|patch| { let patch_range = patch.range.to_point(&buffer); @@ -1075,7 +1133,7 @@ impl Context { self.patches.iter().map(|patch| patch.range.clone()) } - pub(crate) fn patch_for_range( + pub fn patch_for_range( &self, range: &Range, cx: &AppContext, @@ -1165,7 +1223,7 @@ impl Context { } } - pub(crate) fn token_count(&self) -> Option { + pub fn token_count(&self) -> Option { self.token_count } @@ -2879,7 +2937,7 @@ impl Context { self.message_anchors.insert(insertion_ix, new_anchor); } - pub(super) fn summarize(&mut self, replace_old: bool, cx: &mut ModelContext) { + pub fn summarize(&mut self, replace_old: bool, cx: &mut ModelContext) { let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else { return; }; @@ -3118,7 +3176,7 @@ impl Context { }); } - pub(crate) fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext) { + pub fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext) { let timestamp = self.next_timestamp(); let summary = self.summary.get_or_insert(ContextSummary::default()); summary.timestamp = timestamp; diff --git a/crates/assistant/src/context/context_tests.rs b/crates/assistant_context_editor/src/context/context_tests.rs similarity index 99% rename from crates/assistant/src/context/context_tests.rs rename to crates/assistant_context_editor/src/context/context_tests.rs index 6876b29913..dcb40f322d 100644 --- a/crates/assistant/src/context/context_tests.rs +++ b/crates/assistant_context_editor/src/context/context_tests.rs @@ -1,7 +1,6 @@ -use super::{AssistantEdit, MessageCacheMetadata}; use crate::{ - assistant_panel, AssistantEditKind, CacheStatus, Context, ContextEvent, ContextId, - ContextOperation, InvokedSlashCommandId, MessageId, MessageStatus, + AssistantEdit, AssistantEditKind, CacheStatus, Context, ContextEvent, ContextId, + ContextOperation, InvokedSlashCommandId, MessageCacheMetadata, MessageId, MessageStatus, }; use anyhow::Result; use assistant_slash_command::{ @@ -48,7 +47,6 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) { let settings_store = SettingsStore::test(cx); LanguageModelRegistry::test(cx); cx.set_global(settings_store); - assistant_panel::init(cx); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let context = cx.new_model(|cx| { @@ -189,7 +187,6 @@ fn test_message_splitting(cx: &mut AppContext) { let settings_store = SettingsStore::test(cx); cx.set_global(settings_store); LanguageModelRegistry::test(cx); - assistant_panel::init(cx); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); @@ -294,7 +291,6 @@ fn test_messages_for_offsets(cx: &mut AppContext) { let settings_store = SettingsStore::test(cx); LanguageModelRegistry::test(cx); cx.set_global(settings_store); - assistant_panel::init(cx); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let context = cx.new_model(|cx| { @@ -390,7 +386,6 @@ async fn test_slash_commands(cx: &mut TestAppContext) { cx.set_global(settings_store); cx.update(LanguageModelRegistry::test); cx.update(Project::init_settings); - cx.update(assistant_panel::init); let fs = FakeFs::new(cx.background_executor.clone()); fs.insert_tree( @@ -698,7 +693,6 @@ async fn test_workflow_step_parsing(cx: &mut TestAppContext) { let project = Project::test(fs, [Path::new("/root")], cx).await; cx.update(LanguageModelRegistry::test); - cx.update(assistant_panel::init); let registry = Arc::new(LanguageRegistry::test(cx.executor())); // Create a new context @@ -1081,7 +1075,6 @@ async fn test_serialization(cx: &mut TestAppContext) { let settings_store = cx.update(SettingsStore::test); cx.set_global(settings_store); cx.update(LanguageModelRegistry::test); - cx.update(assistant_panel::init); let registry = Arc::new(LanguageRegistry::test(cx.executor())); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let context = cx.new_model(|cx| { @@ -1173,7 +1166,6 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std cx.set_global(settings_store); cx.update(LanguageModelRegistry::test); - cx.update(assistant_panel::init); let slash_commands = cx.update(SlashCommandRegistry::default_global); slash_commands.register_command(FakeSlashCommand("cmd-1".into()), false); slash_commands.register_command(FakeSlashCommand("cmd-2".into()), false); @@ -1446,7 +1438,6 @@ fn test_mark_cache_anchors(cx: &mut AppContext) { let settings_store = SettingsStore::test(cx); LanguageModelRegistry::test(cx); cx.set_global(settings_store); - assistant_panel::init(cx); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let context = cx.new_model(|cx| { diff --git a/crates/assistant/src/context_store.rs b/crates/assistant_context_editor/src/context_store.rs similarity index 99% rename from crates/assistant/src/context_store.rs rename to crates/assistant_context_editor/src/context_store.rs index 92d9889f93..7b1b8c7d4c 100644 --- a/crates/assistant/src/context_store.rs +++ b/crates/assistant_context_editor/src/context_store.rs @@ -33,7 +33,7 @@ use std::{ }; use util::{ResultExt, TryFutureExt}; -pub fn init(client: &AnyProtoClient) { +pub(crate) fn init(client: &AnyProtoClient) { client.add_model_message_handler(ContextStore::handle_advertise_contexts); client.add_model_request_handler(ContextStore::handle_open_context); client.add_model_request_handler(ContextStore::handle_create_context); @@ -497,11 +497,7 @@ impl ContextStore { }) } - pub(super) fn loaded_context_for_id( - &self, - id: &ContextId, - cx: &AppContext, - ) -> Option> { + pub fn loaded_context_for_id(&self, id: &ContextId, cx: &AppContext) -> Option> { self.contexts.iter().find_map(|context| { let context = context.upgrade()?; if context.read(cx).id() == id { diff --git a/crates/assistant/src/patch.rs b/crates/assistant_context_editor/src/patch.rs similarity index 99% rename from crates/assistant/src/patch.rs rename to crates/assistant_context_editor/src/patch.rs index ca2df7a0e0..dbb9fb2b8f 100644 --- a/crates/assistant/src/patch.rs +++ b/crates/assistant_context_editor/src/patch.rs @@ -9,7 +9,7 @@ use std::{cmp, ops::Range, path::Path, sync::Arc}; use text::{AnchorRangeExt as _, Bias, OffsetRangeExt as _, Point}; #[derive(Clone, Debug)] -pub(crate) struct AssistantPatch { +pub struct AssistantPatch { pub range: Range, pub title: SharedString, pub edits: Arc<[Result]>, @@ -17,13 +17,13 @@ pub(crate) struct AssistantPatch { } #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub(crate) enum AssistantPatchStatus { +pub enum AssistantPatchStatus { Pending, Ready, } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct AssistantEdit { +pub struct AssistantEdit { pub path: String, pub kind: AssistantEditKind, } @@ -55,7 +55,7 @@ pub enum AssistantEditKind { } #[derive(Clone, Debug, Eq, PartialEq)] -pub(crate) struct ResolvedPatch { +pub struct ResolvedPatch { pub edit_groups: HashMap, Vec>, pub errors: Vec, } @@ -74,7 +74,7 @@ pub struct ResolvedEdit { } #[derive(Clone, Debug, Eq, PartialEq)] -pub(crate) struct AssistantPatchResolutionError { +pub struct AssistantPatchResolutionError { pub edit_ix: usize, pub message: String, } @@ -425,7 +425,7 @@ impl AssistantEditKind { } impl AssistantPatch { - pub(crate) async fn resolve( + pub async fn resolve( &self, project: Model, cx: &mut AsyncAppContext, diff --git a/crates/assistant_slash_command/src/assistant_slash_command.rs b/crates/assistant_slash_command/src/assistant_slash_command.rs index 8b3064bd30..d23647684f 100644 --- a/crates/assistant_slash_command/src/assistant_slash_command.rs +++ b/crates/assistant_slash_command/src/assistant_slash_command.rs @@ -262,6 +262,67 @@ impl SlashCommandOutputSection { } } +pub struct SlashCommandLine { + /// The range within the line containing the command name. + pub name: Range, + /// Ranges within the line containing the command arguments. + pub arguments: Vec>, +} + +impl SlashCommandLine { + pub fn parse(line: &str) -> Option { + let mut call: Option = None; + let mut ix = 0; + for c in line.chars() { + let next_ix = ix + c.len_utf8(); + if let Some(call) = &mut call { + // The command arguments start at the first non-whitespace character + // after the command name, and continue until the end of the line. + if let Some(argument) = call.arguments.last_mut() { + if c.is_whitespace() { + if (*argument).is_empty() { + argument.start = next_ix; + argument.end = next_ix; + } else { + argument.end = ix; + call.arguments.push(next_ix..next_ix); + } + } else { + argument.end = next_ix; + } + } + // The command name ends at the first whitespace character. + else if !call.name.is_empty() { + if c.is_whitespace() { + call.arguments = vec![next_ix..next_ix]; + } else { + call.name.end = next_ix; + } + } + // The command name must begin with a letter. + else if c.is_alphabetic() { + call.name.end = next_ix; + } else { + return None; + } + } + // Commands start with a slash. + else if c == '/' { + call = Some(SlashCommandLine { + name: next_ix..next_ix, + arguments: Vec::new(), + }); + } + // The line can't contain anything before the slash except for whitespace. + else if !c.is_whitespace() { + return None; + } + ix = next_ix; + } + call + } +} + #[cfg(test)] mod tests { use pretty_assertions::assert_eq; diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index 420b63ffcd..020a86c9f8 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -79,6 +79,7 @@ uuid.workspace = true [dev-dependencies] assistant = { workspace = true, features = ["test-support"] } +assistant_context_editor.workspace = true assistant_slash_command.workspace = true assistant_tool.workspace = true async-trait.workspace = true diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 37fff19a03..d3fa3dd269 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -6,7 +6,7 @@ use crate::{ }, }; use anyhow::{anyhow, Result}; -use assistant::ContextStore; +use assistant_context_editor::ContextStore; use assistant_slash_command::SlashCommandWorkingSet; use assistant_tool::ToolWorkingSet; use call::{room, ActiveCall, ParticipantLocation, Room}; diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index 5d0231153d..13905ead3c 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -308,7 +308,7 @@ impl TestServer { settings::KeymapFile::load_asset_allow_partial_failure(os_keymap, cx).unwrap(), ); language_model::LanguageModelRegistry::test(cx); - assistant::context_store::init(&client.clone().into()); + assistant_context_editor::init(client.clone(), cx); }); client