Add assistant_context_editor crate (#23429)

This PR adds a new `assistant_context_editor` crate.

This will ultimately house the `ContextEditor` so that it can be
consumed by both `assistant` and `assistant2`.

For the purposes of this PR, we just introduce the crate and move some
supporting constructs to it, such as the `ContextStore`.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-01-21 16:22:59 -05:00 committed by GitHub
parent c450cd51ea
commit 9a7f1d1de4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 304 additions and 211 deletions

58
Cargo.lock generated
View file

@ -372,14 +372,13 @@ name = "assistant"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"assistant_context_editor",
"assistant_settings", "assistant_settings",
"assistant_slash_command", "assistant_slash_command",
"assistant_slash_commands", "assistant_slash_commands",
"assistant_tool", "assistant_tool",
"async-watch", "async-watch",
"chrono",
"client", "client",
"clock",
"collections", "collections",
"command_palette_hooks", "command_palette_hooks",
"context_server", "context_server",
@ -403,7 +402,6 @@ dependencies = [
"lsp", "lsp",
"menu", "menu",
"multi_buffer", "multi_buffer",
"open_ai",
"parking_lot", "parking_lot",
"paths", "paths",
"picker", "picker",
@ -412,21 +410,16 @@ dependencies = [
"prompt_library", "prompt_library",
"proto", "proto",
"rand 0.8.5", "rand 0.8.5",
"regex",
"rope", "rope",
"rpc",
"schemars", "schemars",
"search", "search",
"semantic_index", "semantic_index",
"serde", "serde",
"serde_json",
"serde_json_lenient", "serde_json_lenient",
"settings", "settings",
"similar", "similar",
"smallvec",
"smol", "smol",
"streaming_diff", "streaming_diff",
"strum",
"telemetry", "telemetry",
"telemetry_events", "telemetry_events",
"terminal", "terminal",
@ -437,7 +430,6 @@ dependencies = [
"ui", "ui",
"unindent", "unindent",
"util", "util",
"uuid",
"workspace", "workspace",
"zed_actions", "zed_actions",
] ]
@ -505,6 +497,53 @@ dependencies = [
"zed_actions", "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]] [[package]]
name = "assistant_settings" name = "assistant_settings"
version = "0.1.0" version = "0.1.0"
@ -2640,6 +2679,7 @@ dependencies = [
"anthropic", "anthropic",
"anyhow", "anyhow",
"assistant", "assistant",
"assistant_context_editor",
"assistant_slash_command", "assistant_slash_command",
"assistant_tool", "assistant_tool",
"async-stripe", "async-stripe",

View file

@ -7,6 +7,7 @@ members = [
"crates/assets", "crates/assets",
"crates/assistant", "crates/assistant",
"crates/assistant2", "crates/assistant2",
"crates/assistant_context_editor",
"crates/assistant_settings", "crates/assistant_settings",
"crates/assistant_slash_command", "crates/assistant_slash_command",
"crates/assistant_slash_commands", "crates/assistant_slash_commands",
@ -204,6 +205,7 @@ anthropic = { path = "crates/anthropic" }
assets = { path = "crates/assets" } assets = { path = "crates/assets" }
assistant = { path = "crates/assistant" } assistant = { path = "crates/assistant" }
assistant2 = { path = "crates/assistant2" } assistant2 = { path = "crates/assistant2" }
assistant_context_editor = { path = "crates/assistant_context_editor" }
assistant_settings = { path = "crates/assistant_settings" } assistant_settings = { path = "crates/assistant_settings" }
assistant_slash_command = { path = "crates/assistant_slash_command" } assistant_slash_command = { path = "crates/assistant_slash_command" }
assistant_slash_commands = { path = "crates/assistant_slash_commands" } assistant_slash_commands = { path = "crates/assistant_slash_commands" }

View file

@ -22,14 +22,13 @@ test-support = [
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
assistant_context_editor.workspace = true
assistant_settings.workspace = true assistant_settings.workspace = true
assistant_slash_command.workspace = true assistant_slash_command.workspace = true
assistant_slash_commands.workspace = true assistant_slash_commands.workspace = true
assistant_tool.workspace = true assistant_tool.workspace = true
async-watch.workspace = true async-watch.workspace = true
chrono.workspace = true
client.workspace = true client.workspace = true
clock.workspace = true
collections.workspace = true collections.workspace = true
command_palette_hooks.workspace = true command_palette_hooks.workspace = true
context_server.workspace = true context_server.workspace = true
@ -50,27 +49,21 @@ log.workspace = true
lsp.workspace = true lsp.workspace = true
menu.workspace = true menu.workspace = true
multi_buffer.workspace = true multi_buffer.workspace = true
open_ai = { workspace = true, features = ["schemars"] }
parking_lot.workspace = true parking_lot.workspace = true
paths.workspace = true paths.workspace = true
picker.workspace = true picker.workspace = true
project.workspace = true project.workspace = true
prompt_library.workspace = true prompt_library.workspace = true
proto.workspace = true proto.workspace = true
regex.workspace = true
rope.workspace = true rope.workspace = true
rpc.workspace = true
schemars.workspace = true schemars.workspace = true
search.workspace = true search.workspace = true
semantic_index.workspace = true semantic_index.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true
settings.workspace = true settings.workspace = true
similar.workspace = true similar.workspace = true
smallvec.workspace = true
smol.workspace = true smol.workspace = true
streaming_diff.workspace = true streaming_diff.workspace = true
strum.workspace = true
telemetry.workspace = true telemetry.workspace = true
telemetry_events.workspace = true telemetry_events.workspace = true
terminal.workspace = true terminal.workspace = true
@ -79,7 +72,6 @@ text.workspace = true
theme.workspace = true theme.workspace = true
ui.workspace = true ui.workspace = true
util.workspace = true util.workspace = true
uuid.workspace = true
workspace.workspace = true workspace.workspace = true
zed_actions.workspace = true zed_actions.workspace = true

View file

@ -1,12 +1,9 @@
#![cfg_attr(target_os = "windows", allow(unused, dead_code))] #![cfg_attr(target_os = "windows", allow(unused, dead_code))]
pub mod assistant_panel; pub mod assistant_panel;
mod context;
mod context_editor; mod context_editor;
mod context_history; mod context_history;
pub mod context_store;
mod inline_assistant; mod inline_assistant;
mod patch;
mod slash_command; mod slash_command;
pub(crate) mod slash_command_picker; pub(crate) mod slash_command_picker;
pub mod slash_command_settings; pub mod slash_command_settings;
@ -18,26 +15,23 @@ use std::sync::Arc;
use assistant_settings::AssistantSettings; use assistant_settings::AssistantSettings;
use assistant_slash_command::SlashCommandRegistry; use assistant_slash_command::SlashCommandRegistry;
use assistant_slash_commands::{ProjectSlashCommandFeatureFlag, SearchSlashCommandFeatureFlag}; use assistant_slash_commands::{ProjectSlashCommandFeatureFlag, SearchSlashCommandFeatureFlag};
use client::{proto, Client}; use client::Client;
use command_palette_hooks::CommandPaletteFilter; use command_palette_hooks::CommandPaletteFilter;
use feature_flags::FeatureFlagAppExt; use feature_flags::FeatureFlagAppExt;
use fs::Fs; use fs::Fs;
use gpui::impl_internal_actions; use gpui::impl_internal_actions;
use gpui::{actions, AppContext, Global, SharedString, UpdateGlobal}; use gpui::{actions, AppContext, Global, UpdateGlobal};
use language_model::{ use language_model::{
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
}; };
use prompt_library::{PromptBuilder, PromptLoadingParams}; use prompt_library::{PromptBuilder, PromptLoadingParams};
use semantic_index::{CloudEmbeddingProvider, SemanticDb}; use semantic_index::{CloudEmbeddingProvider, SemanticDb};
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore};
use util::ResultExt; use util::ResultExt;
pub use crate::assistant_panel::{AssistantPanel, AssistantPanelEvent}; pub use crate::assistant_panel::{AssistantPanel, AssistantPanelEvent};
pub use crate::context::*;
pub use crate::context_store::*;
pub(crate) use crate::inline_assistant::*; pub(crate) use crate::inline_assistant::*;
pub use crate::patch::*;
use crate::slash_command_settings::SlashCommandSettings; use crate::slash_command_settings::SlashCommandSettings;
actions!( actions!(
@ -72,15 +66,6 @@ impl_internal_actions!(assistant, [InsertDraggedFiles]);
const DEFAULT_CONTEXT_LINES: usize = 50; 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)] #[derive(Deserialize, Debug)]
pub struct LanguageModelUsage { pub struct LanguageModelUsage {
pub prompt_tokens: u32, pub prompt_tokens: u32,
@ -95,55 +80,6 @@ pub struct LanguageModelChoiceDelta {
pub finish_reason: Option<String>, pub finish_reason: Option<String>,
} }
#[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. /// The state pertaining to the Assistant.
#[derive(Default)] #[derive(Default)]
struct Assistant { struct Assistant {
@ -214,7 +150,7 @@ pub fn init(
}) })
.detach(); .detach();
context_store::init(&client.clone().into()); assistant_context_editor::init(client.clone(), cx);
prompt_library::init(cx); prompt_library::init(cx);
init_language_model_settings(cx); init_language_model_settings(cx);
assistant_slash_command::init(cx); assistant_slash_command::init(cx);

View file

@ -4,11 +4,11 @@ use crate::context_editor::{
use crate::context_history::ContextHistory; use crate::context_history::ContextHistory;
use crate::{ use crate::{
slash_command::SlashCommandCompletionProvider, slash_command::SlashCommandCompletionProvider,
terminal_inline_assistant::TerminalInlineAssistant, Context, ContextId, ContextStore, terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, DeployPromptLibrary,
ContextStoreEvent, DeployHistory, DeployPromptLibrary, InlineAssistant, InsertDraggedFiles, InlineAssistant, InsertDraggedFiles, NewContext, ToggleFocus, ToggleModelSelector,
NewContext, ToggleFocus, ToggleModelSelector,
}; };
use anyhow::Result; use anyhow::Result;
use assistant_context_editor::{Context, ContextId, ContextStore, ContextStoreEvent};
use assistant_settings::{AssistantDockPosition, AssistantSettings}; use assistant_settings::{AssistantDockPosition, AssistantSettings};
use assistant_slash_command::SlashCommandWorkingSet; use assistant_slash_command::SlashCommandWorkingSet;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;

View file

@ -1,4 +1,9 @@
use anyhow::Result; 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_settings::AssistantSettings;
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet}; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
use assistant_slash_commands::{ use assistant_slash_commands::{
@ -58,11 +63,8 @@ use workspace::{
use crate::{ use crate::{
humanize_token_count, slash_command::SlashCommandCompletionProvider, slash_command_picker, humanize_token_count, slash_command::SlashCommandCompletionProvider, slash_command_picker,
Assist, AssistantPanel, AssistantPatch, AssistantPatchStatus, CacheStatus, ConfirmCommand, Assist, AssistantPanel, ConfirmCommand, CopyCode, CycleMessageRole, Edit, InsertDraggedFiles,
Content, Context, ContextEvent, ContextId, CopyCode, CycleMessageRole, Edit, InsertIntoEditor, QuoteSelection, Split, ToggleModelSelector,
InsertDraggedFiles, InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus,
Message, MessageId, MessageMetadata, MessageStatus, ParsedSlashCommand,
PendingSlashCommandStatus, QuoteSelection, RequestType, Split, ToggleModelSelector,
}; };
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
@ -138,7 +140,7 @@ impl ContextEditor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
let completion_provider = SlashCommandCompletionProvider::new( let completion_provider = SlashCommandCompletionProvider::new(
context.read(cx).slash_commands.clone(), context.read(cx).slash_commands().clone(),
Some(cx.view().downgrade()), Some(cx.view().downgrade()),
Some(workspace.clone()), Some(workspace.clone()),
); );
@ -167,8 +169,8 @@ impl ContextEditor {
let sections = context.read(cx).slash_command_output_sections().to_vec(); let sections = context.read(cx).slash_command_output_sections().to_vec();
let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>(); let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
let slash_commands = context.read(cx).slash_commands.clone(); let slash_commands = context.read(cx).slash_commands().clone();
let tools = context.read(cx).tools.clone(); let tools = context.read(cx).tools().clone();
let mut this = Self { let mut this = Self {
context, context,
slash_commands, slash_commands,

View file

@ -1,5 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use assistant_context_editor::{ContextStore, RemoteContextMetadata, SavedContextMetadata};
use gpui::{ use gpui::{
AppContext, EventEmitter, FocusHandle, FocusableView, Model, Subscription, Task, View, WeakView, AppContext, EventEmitter, FocusHandle, FocusableView, Model, Subscription, Task, View, WeakView,
}; };
@ -10,7 +11,7 @@ use ui::{prelude::*, Avatar, ListItem, ListItemSpacing};
use workspace::Item; use workspace::Item;
use crate::context_editor::DEFAULT_TAB_TITLE; use crate::context_editor::DEFAULT_TAB_TITLE;
use crate::{AssistantPanel, ContextStore, RemoteContextMetadata, SavedContextMetadata}; use crate::AssistantPanel;
#[derive(Clone)] #[derive(Clone)]
pub enum ContextMetadata { pub enum ContextMetadata {

View file

@ -1,8 +1,9 @@
use crate::{ use crate::{
humanize_token_count, AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist, humanize_token_count, AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist,
CyclePreviousInlineAssist, RequestType, CyclePreviousInlineAssist,
}; };
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use assistant_context_editor::RequestType;
use assistant_settings::AssistantSettings; use assistant_settings::AssistantSettings;
use client::{telemetry::Telemetry, ErrorExt}; use client::{telemetry::Telemetry, ErrorExt};
use collections::{hash_map, HashMap, HashSet, VecDeque}; use collections::{hash_map, HashMap, HashSet, VecDeque};

View file

@ -1,8 +1,7 @@
use crate::context_editor::ContextEditor; use crate::context_editor::ContextEditor;
use anyhow::Result; use anyhow::Result;
use assistant_slash_command::AfterCompletion;
pub use assistant_slash_command::SlashCommand; pub use assistant_slash_command::SlashCommand;
use assistant_slash_command::SlashCommandWorkingSet; use assistant_slash_command::{AfterCompletion, SlashCommandLine, SlashCommandWorkingSet};
use editor::{CompletionProvider, Editor}; use editor::{CompletionProvider, Editor};
use fuzzy::{match_strings, StringMatchCandidate}; use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{Model, Task, ViewContext, WeakView, WindowContext}; use gpui::{Model, Task, ViewContext, WeakView, WindowContext};
@ -28,13 +27,6 @@ pub(crate) struct SlashCommandCompletionProvider {
workspace: Option<WeakView<Workspace>>, workspace: Option<WeakView<Workspace>>,
} }
pub(crate) struct SlashCommandLine {
/// The range within the line containing the command name.
pub name: Range<usize>,
/// Ranges within the line containing the command arguments.
pub arguments: Vec<Range<usize>>,
}
impl SlashCommandCompletionProvider { impl SlashCommandCompletionProvider {
pub fn new( pub fn new(
slash_commands: Arc<SlashCommandWorkingSet>, slash_commands: Arc<SlashCommandWorkingSet>,
@ -336,57 +328,3 @@ impl CompletionProvider for SlashCommandCompletionProvider {
false false
} }
} }
impl SlashCommandLine {
pub(crate) fn parse(line: &str) -> Option<Self> {
let mut call: Option<Self> = 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
}
}

View file

@ -1,7 +1,6 @@
use crate::{ use crate::{humanize_token_count, AssistantPanel, AssistantPanelEvent, DEFAULT_CONTEXT_LINES};
humanize_token_count, AssistantPanel, AssistantPanelEvent, RequestType, DEFAULT_CONTEXT_LINES,
};
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use assistant_context_editor::RequestType;
use assistant_settings::AssistantSettings; use assistant_settings::AssistantSettings;
use client::telemetry::Telemetry; use client::telemetry::Telemetry;
use collections::{HashMap, VecDeque}; use collections::{HashMap, VecDeque};

View file

@ -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"] }

View file

@ -0,0 +1 @@
../../LICENSE-GPL

View file

@ -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<Client>, _cx: &mut AppContext) {
context_store::init(&client.into());
}

View file

@ -1,14 +1,11 @@
#[cfg(test)] #[cfg(test)]
mod context_tests; mod context_tests;
use crate::{ use crate::patch::{AssistantEdit, AssistantPatch, AssistantPatchStatus};
slash_command::SlashCommandLine, AssistantEdit, AssistantPatch, AssistantPatchStatus,
MessageId, MessageStatus,
};
use anyhow::{anyhow, Context as _, Result}; use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{ use assistant_slash_command::{
SlashCommandContent, SlashCommandEvent, SlashCommandOutputSection, SlashCommandResult, SlashCommandContent, SlashCommandEvent, SlashCommandLine, SlashCommandOutputSection,
SlashCommandWorkingSet, SlashCommandResult, SlashCommandWorkingSet,
}; };
use assistant_slash_commands::FileCommandMetadata; use assistant_slash_commands::FileCommandMetadata;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;
@ -22,8 +19,6 @@ use gpui::{
AppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage, SharedString, AppContext, Context as _, EventEmitter, Model, ModelContext, RenderImage, SharedString,
Subscription, Task, Subscription, Task,
}; };
use prompt_library::PromptBuilder;
use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset}; use language::{AnchorRangeExt, Bias, Buffer, LanguageRegistry, OffsetRangeExt, Point, ToOffset};
use language_model::{ use language_model::{
LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent, LanguageModel, LanguageModelCacheConfiguration, LanguageModelCompletionEvent,
@ -38,6 +33,7 @@ use language_models::{
use open_ai::Model as OpenAiModel; use open_ai::Model as OpenAiModel;
use paths::contexts_dir; use paths::contexts_dir;
use project::Project; use project::Project;
use prompt_library::PromptBuilder;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
@ -52,9 +48,9 @@ use std::{
}; };
use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use text::{BufferSnapshot, ToPoint}; use text::{BufferSnapshot, ToPoint};
use ui::IconName;
use util::{post_inc, ResultExt, TryFutureExt}; use util::{post_inc, ResultExt, TryFutureExt};
use uuid::Uuid; use uuid::Uuid;
use workspace::ui::IconName;
#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)] #[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ContextId(String); 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)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RequestType { pub enum RequestType {
/// Request a normal chat response from the model. /// Request a normal chat response from the model.
@ -423,7 +477,7 @@ pub struct MessageCacheMetadata {
pub struct MessageMetadata { pub struct MessageMetadata {
pub role: Role, pub role: Role,
pub status: MessageStatus, pub status: MessageStatus,
pub(crate) timestamp: clock::Lamport, pub timestamp: clock::Lamport,
#[serde(skip)] #[serde(skip)]
pub cache: Option<MessageCacheMetadata>, pub cache: Option<MessageCacheMetadata>,
} }
@ -544,8 +598,8 @@ pub struct Context {
parsed_slash_commands: Vec<ParsedSlashCommand>, parsed_slash_commands: Vec<ParsedSlashCommand>,
invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>, invoked_slash_commands: HashMap<InvokedSlashCommandId, InvokedSlashCommand>,
edits_since_last_parse: language::Subscription, edits_since_last_parse: language::Subscription,
pub(crate) slash_commands: Arc<SlashCommandWorkingSet>, slash_commands: Arc<SlashCommandWorkingSet>,
pub(crate) tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>, slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>, pending_tool_uses_by_id: HashMap<LanguageModelToolUseId, PendingToolUse>,
message_anchors: Vec<MessageAnchor>, message_anchors: Vec<MessageAnchor>,
@ -790,6 +844,14 @@ impl Context {
} }
} }
pub fn slash_commands(&self) -> &Arc<SlashCommandWorkingSet> {
&self.slash_commands
}
pub fn tools(&self) -> &Arc<ToolWorkingSet> {
&self.tools
}
pub fn set_capability( pub fn set_capability(
&mut self, &mut self,
capability: language::Capability, capability: language::Capability,
@ -1048,11 +1110,7 @@ impl Context {
self.summary.as_ref() self.summary.as_ref()
} }
pub(crate) fn patch_containing( pub fn patch_containing(&self, position: Point, cx: &AppContext) -> Option<&AssistantPatch> {
&self,
position: Point,
cx: &AppContext,
) -> Option<&AssistantPatch> {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let index = self.patches.binary_search_by(|patch| { let index = self.patches.binary_search_by(|patch| {
let patch_range = patch.range.to_point(&buffer); let patch_range = patch.range.to_point(&buffer);
@ -1075,7 +1133,7 @@ impl Context {
self.patches.iter().map(|patch| patch.range.clone()) self.patches.iter().map(|patch| patch.range.clone())
} }
pub(crate) fn patch_for_range( pub fn patch_for_range(
&self, &self,
range: &Range<language::Anchor>, range: &Range<language::Anchor>,
cx: &AppContext, cx: &AppContext,
@ -1165,7 +1223,7 @@ impl Context {
} }
} }
pub(crate) fn token_count(&self) -> Option<usize> { pub fn token_count(&self) -> Option<usize> {
self.token_count self.token_count
} }
@ -2879,7 +2937,7 @@ impl Context {
self.message_anchors.insert(insertion_ix, new_anchor); self.message_anchors.insert(insertion_ix, new_anchor);
} }
pub(super) fn summarize(&mut self, replace_old: bool, cx: &mut ModelContext<Self>) { pub fn summarize(&mut self, replace_old: bool, cx: &mut ModelContext<Self>) {
let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else { let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
return; return;
}; };
@ -3118,7 +3176,7 @@ impl Context {
}); });
} }
pub(crate) fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext<Self>) { pub fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext<Self>) {
let timestamp = self.next_timestamp(); let timestamp = self.next_timestamp();
let summary = self.summary.get_or_insert(ContextSummary::default()); let summary = self.summary.get_or_insert(ContextSummary::default());
summary.timestamp = timestamp; summary.timestamp = timestamp;

View file

@ -1,7 +1,6 @@
use super::{AssistantEdit, MessageCacheMetadata};
use crate::{ use crate::{
assistant_panel, AssistantEditKind, CacheStatus, Context, ContextEvent, ContextId, AssistantEdit, AssistantEditKind, CacheStatus, Context, ContextEvent, ContextId,
ContextOperation, InvokedSlashCommandId, MessageId, MessageStatus, ContextOperation, InvokedSlashCommandId, MessageCacheMetadata, MessageId, MessageStatus,
}; };
use anyhow::Result; use anyhow::Result;
use assistant_slash_command::{ use assistant_slash_command::{
@ -48,7 +47,6 @@ fn test_inserting_and_removing_messages(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx); LanguageModelRegistry::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| { let context = cx.new_model(|cx| {
@ -189,7 +187,6 @@ fn test_message_splitting(cx: &mut AppContext) {
let settings_store = SettingsStore::test(cx); let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
LanguageModelRegistry::test(cx); LanguageModelRegistry::test(cx);
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); 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); let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx); LanguageModelRegistry::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| { let context = cx.new_model(|cx| {
@ -390,7 +386,6 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
cx.set_global(settings_store); cx.set_global(settings_store);
cx.update(LanguageModelRegistry::test); cx.update(LanguageModelRegistry::test);
cx.update(Project::init_settings); cx.update(Project::init_settings);
cx.update(assistant_panel::init);
let fs = FakeFs::new(cx.background_executor.clone()); let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree( 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; let project = Project::test(fs, [Path::new("/root")], cx).await;
cx.update(LanguageModelRegistry::test); cx.update(LanguageModelRegistry::test);
cx.update(assistant_panel::init);
let registry = Arc::new(LanguageRegistry::test(cx.executor())); let registry = Arc::new(LanguageRegistry::test(cx.executor()));
// Create a new context // Create a new context
@ -1081,7 +1075,6 @@ async fn test_serialization(cx: &mut TestAppContext) {
let settings_store = cx.update(SettingsStore::test); let settings_store = cx.update(SettingsStore::test);
cx.set_global(settings_store); cx.set_global(settings_store);
cx.update(LanguageModelRegistry::test); cx.update(LanguageModelRegistry::test);
cx.update(assistant_panel::init);
let registry = Arc::new(LanguageRegistry::test(cx.executor())); let registry = Arc::new(LanguageRegistry::test(cx.executor()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| { 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.set_global(settings_store);
cx.update(LanguageModelRegistry::test); cx.update(LanguageModelRegistry::test);
cx.update(assistant_panel::init);
let slash_commands = cx.update(SlashCommandRegistry::default_global); let slash_commands = cx.update(SlashCommandRegistry::default_global);
slash_commands.register_command(FakeSlashCommand("cmd-1".into()), false); slash_commands.register_command(FakeSlashCommand("cmd-1".into()), false);
slash_commands.register_command(FakeSlashCommand("cmd-2".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); let settings_store = SettingsStore::test(cx);
LanguageModelRegistry::test(cx); LanguageModelRegistry::test(cx);
cx.set_global(settings_store); cx.set_global(settings_store);
assistant_panel::init(cx);
let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone())); let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
let context = cx.new_model(|cx| { let context = cx.new_model(|cx| {

View file

@ -33,7 +33,7 @@ use std::{
}; };
use util::{ResultExt, TryFutureExt}; 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_message_handler(ContextStore::handle_advertise_contexts);
client.add_model_request_handler(ContextStore::handle_open_context); client.add_model_request_handler(ContextStore::handle_open_context);
client.add_model_request_handler(ContextStore::handle_create_context); client.add_model_request_handler(ContextStore::handle_create_context);
@ -497,11 +497,7 @@ impl ContextStore {
}) })
} }
pub(super) fn loaded_context_for_id( pub fn loaded_context_for_id(&self, id: &ContextId, cx: &AppContext) -> Option<Model<Context>> {
&self,
id: &ContextId,
cx: &AppContext,
) -> Option<Model<Context>> {
self.contexts.iter().find_map(|context| { self.contexts.iter().find_map(|context| {
let context = context.upgrade()?; let context = context.upgrade()?;
if context.read(cx).id() == id { if context.read(cx).id() == id {

View file

@ -9,7 +9,7 @@ use std::{cmp, ops::Range, path::Path, sync::Arc};
use text::{AnchorRangeExt as _, Bias, OffsetRangeExt as _, Point}; use text::{AnchorRangeExt as _, Bias, OffsetRangeExt as _, Point};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct AssistantPatch { pub struct AssistantPatch {
pub range: Range<language::Anchor>, pub range: Range<language::Anchor>,
pub title: SharedString, pub title: SharedString,
pub edits: Arc<[Result<AssistantEdit>]>, pub edits: Arc<[Result<AssistantEdit>]>,
@ -17,13 +17,13 @@ pub(crate) struct AssistantPatch {
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum AssistantPatchStatus { pub enum AssistantPatchStatus {
Pending, Pending,
Ready, Ready,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct AssistantEdit { pub struct AssistantEdit {
pub path: String, pub path: String,
pub kind: AssistantEditKind, pub kind: AssistantEditKind,
} }
@ -55,7 +55,7 @@ pub enum AssistantEditKind {
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct ResolvedPatch { pub struct ResolvedPatch {
pub edit_groups: HashMap<Model<Buffer>, Vec<ResolvedEditGroup>>, pub edit_groups: HashMap<Model<Buffer>, Vec<ResolvedEditGroup>>,
pub errors: Vec<AssistantPatchResolutionError>, pub errors: Vec<AssistantPatchResolutionError>,
} }
@ -74,7 +74,7 @@ pub struct ResolvedEdit {
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct AssistantPatchResolutionError { pub struct AssistantPatchResolutionError {
pub edit_ix: usize, pub edit_ix: usize,
pub message: String, pub message: String,
} }
@ -425,7 +425,7 @@ impl AssistantEditKind {
} }
impl AssistantPatch { impl AssistantPatch {
pub(crate) async fn resolve( pub async fn resolve(
&self, &self,
project: Model<Project>, project: Model<Project>,
cx: &mut AsyncAppContext, cx: &mut AsyncAppContext,

View file

@ -262,6 +262,67 @@ impl SlashCommandOutputSection<language::Anchor> {
} }
} }
pub struct SlashCommandLine {
/// The range within the line containing the command name.
pub name: Range<usize>,
/// Ranges within the line containing the command arguments.
pub arguments: Vec<Range<usize>>,
}
impl SlashCommandLine {
pub fn parse(line: &str) -> Option<Self> {
let mut call: Option<Self> = 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)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;

View file

@ -79,6 +79,7 @@ uuid.workspace = true
[dev-dependencies] [dev-dependencies]
assistant = { workspace = true, features = ["test-support"] } assistant = { workspace = true, features = ["test-support"] }
assistant_context_editor.workspace = true
assistant_slash_command.workspace = true assistant_slash_command.workspace = true
assistant_tool.workspace = true assistant_tool.workspace = true
async-trait.workspace = true async-trait.workspace = true

View file

@ -6,7 +6,7 @@ use crate::{
}, },
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use assistant::ContextStore; use assistant_context_editor::ContextStore;
use assistant_slash_command::SlashCommandWorkingSet; use assistant_slash_command::SlashCommandWorkingSet;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;
use call::{room, ActiveCall, ParticipantLocation, Room}; use call::{room, ActiveCall, ParticipantLocation, Room};

View file

@ -308,7 +308,7 @@ impl TestServer {
settings::KeymapFile::load_asset_allow_partial_failure(os_keymap, cx).unwrap(), settings::KeymapFile::load_asset_allow_partial_failure(os_keymap, cx).unwrap(),
); );
language_model::LanguageModelRegistry::test(cx); language_model::LanguageModelRegistry::test(cx);
assistant::context_store::init(&client.clone().into()); assistant_context_editor::init(client.clone(), cx);
}); });
client client