1pub mod assistant_panel;
2pub mod assistant_settings;
3mod context;
4pub mod context_store;
5mod inline_assistant;
6mod model_selector;
7mod prompt_library;
8mod prompts;
9mod slash_command;
10mod streaming_diff;
11mod terminal_inline_assistant;
12
13pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
14use assistant_settings::AssistantSettings;
15use assistant_slash_command::SlashCommandRegistry;
16use client::{proto, Client};
17use command_palette_hooks::CommandPaletteFilter;
18pub use context::*;
19pub use context_store::*;
20use fs::Fs;
21use gpui::{actions, impl_actions, AppContext, Global, SharedString, UpdateGlobal};
22use indexed_docs::IndexedDocsRegistry;
23pub(crate) use inline_assistant::*;
24use language_model::{
25 LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
26};
27pub(crate) use model_selector::*;
28use semantic_index::{CloudEmbeddingProvider, SemanticIndex};
29use serde::{Deserialize, Serialize};
30use settings::{update_settings_file, Settings, SettingsStore};
31use slash_command::{
32 active_command, default_command, diagnostics_command, docs_command, fetch_command,
33 file_command, now_command, project_command, prompt_command, search_command, symbols_command,
34 tabs_command, term_command,
35};
36use std::sync::Arc;
37pub(crate) use streaming_diff::*;
38
39actions!(
40 assistant,
41 [
42 Assist,
43 Split,
44 CycleMessageRole,
45 QuoteSelection,
46 InsertIntoEditor,
47 ToggleFocus,
48 ResetKey,
49 InsertActivePrompt,
50 DeployHistory,
51 DeployPromptLibrary,
52 ConfirmCommand,
53 ToggleModelSelector,
54 DebugEditSteps
55 ]
56);
57
58const DEFAULT_CONTEXT_LINES: usize = 20;
59
60#[derive(Clone, Default, Deserialize, PartialEq)]
61pub struct InlineAssist {
62 prompt: Option<String>,
63}
64
65impl_actions!(assistant, [InlineAssist]);
66
67#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
68pub struct MessageId(clock::Lamport);
69
70impl MessageId {
71 pub fn as_u64(self) -> u64 {
72 self.0.as_u64()
73 }
74}
75
76#[derive(Deserialize, Debug)]
77pub struct LanguageModelUsage {
78 pub prompt_tokens: u32,
79 pub completion_tokens: u32,
80 pub total_tokens: u32,
81}
82
83#[derive(Deserialize, Debug)]
84pub struct LanguageModelChoiceDelta {
85 pub index: u32,
86 pub delta: LanguageModelResponseMessage,
87 pub finish_reason: Option<String>,
88}
89
90#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
91pub enum MessageStatus {
92 Pending,
93 Done,
94 Error(SharedString),
95}
96
97impl MessageStatus {
98 pub fn from_proto(status: proto::ContextMessageStatus) -> MessageStatus {
99 match status.variant {
100 Some(proto::context_message_status::Variant::Pending(_)) => MessageStatus::Pending,
101 Some(proto::context_message_status::Variant::Done(_)) => MessageStatus::Done,
102 Some(proto::context_message_status::Variant::Error(error)) => {
103 MessageStatus::Error(error.message.into())
104 }
105 None => MessageStatus::Pending,
106 }
107 }
108
109 pub fn to_proto(&self) -> proto::ContextMessageStatus {
110 match self {
111 MessageStatus::Pending => proto::ContextMessageStatus {
112 variant: Some(proto::context_message_status::Variant::Pending(
113 proto::context_message_status::Pending {},
114 )),
115 },
116 MessageStatus::Done => proto::ContextMessageStatus {
117 variant: Some(proto::context_message_status::Variant::Done(
118 proto::context_message_status::Done {},
119 )),
120 },
121 MessageStatus::Error(message) => proto::ContextMessageStatus {
122 variant: Some(proto::context_message_status::Variant::Error(
123 proto::context_message_status::Error {
124 message: message.to_string(),
125 },
126 )),
127 },
128 }
129 }
130}
131
132/// The state pertaining to the Assistant.
133#[derive(Default)]
134struct Assistant {
135 /// Whether the Assistant is enabled.
136 enabled: bool,
137}
138
139impl Global for Assistant {}
140
141impl Assistant {
142 const NAMESPACE: &'static str = "assistant";
143
144 fn set_enabled(&mut self, enabled: bool, cx: &mut AppContext) {
145 if self.enabled == enabled {
146 return;
147 }
148
149 self.enabled = enabled;
150
151 if !enabled {
152 CommandPaletteFilter::update_global(cx, |filter, _cx| {
153 filter.hide_namespace(Self::NAMESPACE);
154 });
155
156 return;
157 }
158
159 CommandPaletteFilter::update_global(cx, |filter, _cx| {
160 filter.show_namespace(Self::NAMESPACE);
161 });
162 }
163}
164
165pub fn init(fs: Arc<dyn Fs>, client: Arc<Client>, cx: &mut AppContext) {
166 cx.set_global(Assistant::default());
167 AssistantSettings::register(cx);
168
169 // TODO: remove this when 0.148.0 is released.
170 if AssistantSettings::get_global(cx).using_outdated_settings_version {
171 update_settings_file::<AssistantSettings>(fs.clone(), cx, {
172 let fs = fs.clone();
173 |content, cx| {
174 content.update_file(fs, cx);
175 }
176 });
177 }
178
179 cx.spawn(|mut cx| {
180 let client = client.clone();
181 async move {
182 let embedding_provider = CloudEmbeddingProvider::new(client.clone());
183 let semantic_index = SemanticIndex::new(
184 paths::embeddings_dir().join("semantic-index-db.0.mdb"),
185 Arc::new(embedding_provider),
186 &mut cx,
187 )
188 .await?;
189 cx.update(|cx| cx.set_global(semantic_index))
190 }
191 })
192 .detach();
193
194 context_store::init(&client);
195 prompt_library::init(cx);
196 init_language_model_settings(cx);
197 assistant_slash_command::init(cx);
198 register_slash_commands(cx);
199 assistant_panel::init(cx);
200 inline_assistant::init(fs.clone(), client.telemetry().clone(), cx);
201 terminal_inline_assistant::init(fs.clone(), client.telemetry().clone(), cx);
202 IndexedDocsRegistry::init_global(cx);
203
204 CommandPaletteFilter::update_global(cx, |filter, _cx| {
205 filter.hide_namespace(Assistant::NAMESPACE);
206 });
207 Assistant::update_global(cx, |assistant, cx| {
208 let settings = AssistantSettings::get_global(cx);
209
210 assistant.set_enabled(settings.enabled, cx);
211 });
212 cx.observe_global::<SettingsStore>(|cx| {
213 Assistant::update_global(cx, |assistant, cx| {
214 let settings = AssistantSettings::get_global(cx);
215 assistant.set_enabled(settings.enabled, cx);
216 });
217 })
218 .detach();
219}
220
221fn init_language_model_settings(cx: &mut AppContext) {
222 update_active_language_model_from_settings(cx);
223
224 cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
225 .detach();
226 cx.subscribe(
227 &LanguageModelRegistry::global(cx),
228 |_, event: &language_model::Event, cx| match event {
229 language_model::Event::ProviderStateChanged
230 | language_model::Event::AddedProvider(_)
231 | language_model::Event::RemovedProvider(_) => {
232 update_active_language_model_from_settings(cx);
233 }
234 _ => {}
235 },
236 )
237 .detach();
238}
239
240fn update_active_language_model_from_settings(cx: &mut AppContext) {
241 let settings = AssistantSettings::get_global(cx);
242 let provider_name = LanguageModelProviderId::from(settings.default_model.provider.clone());
243 let model_id = LanguageModelId::from(settings.default_model.model.clone());
244 LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
245 registry.select_active_model(&provider_name, &model_id, cx);
246 });
247}
248
249fn register_slash_commands(cx: &mut AppContext) {
250 let slash_command_registry = SlashCommandRegistry::global(cx);
251 slash_command_registry.register_command(file_command::FileSlashCommand, true);
252 slash_command_registry.register_command(active_command::ActiveSlashCommand, true);
253 slash_command_registry.register_command(symbols_command::OutlineSlashCommand, true);
254 slash_command_registry.register_command(tabs_command::TabsSlashCommand, true);
255 slash_command_registry.register_command(project_command::ProjectSlashCommand, true);
256 slash_command_registry.register_command(search_command::SearchSlashCommand, true);
257 slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
258 slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
259 slash_command_registry.register_command(term_command::TermSlashCommand, true);
260 slash_command_registry.register_command(now_command::NowSlashCommand, true);
261 slash_command_registry.register_command(diagnostics_command::DiagnosticsSlashCommand, true);
262 slash_command_registry.register_command(docs_command::DocsSlashCommand, true);
263 slash_command_registry.register_command(fetch_command::FetchSlashCommand, false);
264}
265
266pub fn humanize_token_count(count: usize) -> String {
267 match count {
268 0..=999 => count.to_string(),
269 1000..=9999 => {
270 let thousands = count / 1000;
271 let hundreds = (count % 1000 + 50) / 100;
272 if hundreds == 0 {
273 format!("{}k", thousands)
274 } else if hundreds == 10 {
275 format!("{}k", thousands + 1)
276 } else {
277 format!("{}.{}k", thousands, hundreds)
278 }
279 }
280 _ => format!("{}k", (count + 500) / 1000),
281 }
282}
283
284#[cfg(test)]
285#[ctor::ctor]
286fn init_logger() {
287 if std::env::var("RUST_LOG").is_ok() {
288 env_logger::init();
289 }
290}