1mod acp;
2mod agent_configuration;
3mod agent_diff;
4mod agent_model_selector;
5mod agent_panel;
6mod buffer_codegen;
7mod context;
8mod context_picker;
9mod context_server_configuration;
10mod context_store;
11mod context_strip;
12mod inline_assistant;
13mod inline_prompt_editor;
14mod language_model_selector;
15mod profile_selector;
16mod slash_command;
17mod slash_command_picker;
18mod terminal_codegen;
19mod terminal_inline_assistant;
20mod text_thread_editor;
21mod ui;
22
23use std::rc::Rc;
24use std::sync::Arc;
25
26use agent_settings::{AgentProfileId, AgentSettings};
27use assistant_slash_command::SlashCommandRegistry;
28use client::Client;
29use command_palette_hooks::CommandPaletteFilter;
30use feature_flags::FeatureFlagAppExt as _;
31use fs::Fs;
32use gpui::{Action, App, Entity, SharedString, actions};
33use language::LanguageRegistry;
34use language_model::{
35 ConfiguredModel, LanguageModel, LanguageModelId, LanguageModelProviderId, LanguageModelRegistry,
36};
37use project::DisableAiSettings;
38use project::agent_server_store::AgentServerCommand;
39use prompt_store::PromptBuilder;
40use schemars::JsonSchema;
41use serde::{Deserialize, Serialize};
42use settings::{LanguageModelSelection, Settings as _, SettingsStore};
43use std::any::TypeId;
44
45use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
46pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
47pub use crate::inline_assistant::InlineAssistant;
48pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
49pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
50use zed_actions;
51
52actions!(
53 agent,
54 [
55 /// Creates a new text-based conversation thread.
56 NewTextThread,
57 /// Toggles the context picker interface for adding files, symbols, or other context.
58 ToggleContextPicker,
59 /// Toggles the menu to create new agent threads.
60 ToggleNewThreadMenu,
61 /// Toggles the navigation menu for switching between threads and views.
62 ToggleNavigationMenu,
63 /// Toggles the options menu for agent settings and preferences.
64 ToggleOptionsMenu,
65 /// Deletes the recently opened thread from history.
66 DeleteRecentlyOpenThread,
67 /// Toggles the profile or mode selector for switching between agent profiles.
68 ToggleProfileSelector,
69 /// Cycles through available session modes.
70 CycleModeSelector,
71 /// Removes all added context from the current conversation.
72 RemoveAllContext,
73 /// Expands the message editor to full size.
74 ExpandMessageEditor,
75 /// Opens the conversation history view.
76 OpenHistory,
77 /// Adds a context server to the configuration.
78 AddContextServer,
79 /// Removes the currently selected thread.
80 RemoveSelectedThread,
81 /// Starts a chat conversation with follow-up enabled.
82 ChatWithFollow,
83 /// Cycles to the next inline assist suggestion.
84 CycleNextInlineAssist,
85 /// Cycles to the previous inline assist suggestion.
86 CyclePreviousInlineAssist,
87 /// Moves focus up in the interface.
88 FocusUp,
89 /// Moves focus down in the interface.
90 FocusDown,
91 /// Moves focus left in the interface.
92 FocusLeft,
93 /// Moves focus right in the interface.
94 FocusRight,
95 /// Removes the currently focused context item.
96 RemoveFocusedContext,
97 /// Accepts the suggested context item.
98 AcceptSuggestedContext,
99 /// Opens the active thread as a markdown file.
100 OpenActiveThreadAsMarkdown,
101 /// Opens the agent diff view to review changes.
102 OpenAgentDiff,
103 /// Keeps the current suggestion or change.
104 Keep,
105 /// Rejects the current suggestion or change.
106 Reject,
107 /// Rejects all suggestions or changes.
108 RejectAll,
109 /// Keeps all suggestions or changes.
110 KeepAll,
111 /// Allow this operation only this time.
112 AllowOnce,
113 /// Allow this operation and remember the choice.
114 AllowAlways,
115 /// Reject this operation only this time.
116 RejectOnce,
117 /// Follows the agent's suggestions.
118 Follow,
119 /// Resets the trial upsell notification.
120 ResetTrialUpsell,
121 /// Resets the trial end upsell notification.
122 ResetTrialEndUpsell,
123 /// Continues the current thread.
124 ContinueThread,
125 /// Continues the thread with burn mode enabled.
126 ContinueWithBurnMode,
127 /// Toggles burn mode for faster responses.
128 ToggleBurnMode,
129 ]
130);
131
132/// Creates a new conversation thread, optionally based on an existing thread.
133#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
134#[action(namespace = agent)]
135#[serde(deny_unknown_fields)]
136pub struct NewThread;
137
138/// Creates a new external agent conversation thread.
139#[derive(Default, Clone, PartialEq, Deserialize, JsonSchema, Action)]
140#[action(namespace = agent)]
141#[serde(deny_unknown_fields)]
142pub struct NewExternalAgentThread {
143 /// Which agent to use for the conversation.
144 agent: Option<ExternalAgent>,
145}
146
147#[derive(Clone, PartialEq, Deserialize, JsonSchema, Action)]
148#[action(namespace = agent)]
149#[serde(deny_unknown_fields)]
150pub struct NewNativeAgentThreadFromSummary {
151 from_session_id: agent_client_protocol::SessionId,
152}
153
154// TODO unify this with AgentType
155#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
156#[serde(rename_all = "snake_case")]
157pub enum ExternalAgent {
158 Gemini,
159 ClaudeCode,
160 Codex,
161 NativeAgent,
162 Custom {
163 name: SharedString,
164 command: AgentServerCommand,
165 },
166}
167
168fn placeholder_command() -> AgentServerCommand {
169 AgentServerCommand {
170 path: "/placeholder".into(),
171 args: vec![],
172 env: None,
173 }
174}
175
176impl ExternalAgent {
177 pub fn parse_built_in(server: &dyn agent_servers::AgentServer) -> Option<Self> {
178 match server.telemetry_id() {
179 "gemini-cli" => Some(Self::Gemini),
180 "claude-code" => Some(Self::ClaudeCode),
181 "codex" => Some(Self::Codex),
182 "zed" => Some(Self::NativeAgent),
183 _ => None,
184 }
185 }
186
187 pub fn server(
188 &self,
189 fs: Arc<dyn fs::Fs>,
190 history: Entity<agent::HistoryStore>,
191 ) -> Rc<dyn agent_servers::AgentServer> {
192 match self {
193 Self::Gemini => Rc::new(agent_servers::Gemini),
194 Self::ClaudeCode => Rc::new(agent_servers::ClaudeCode),
195 Self::Codex => Rc::new(agent_servers::Codex),
196 Self::NativeAgent => Rc::new(agent::NativeAgentServer::new(fs, history)),
197 Self::Custom { name, command: _ } => {
198 Rc::new(agent_servers::CustomAgentServer::new(name.clone()))
199 }
200 }
201 }
202}
203
204/// Opens the profile management interface for configuring agent tools and settings.
205#[derive(PartialEq, Clone, Default, Debug, Deserialize, JsonSchema, Action)]
206#[action(namespace = agent)]
207#[serde(deny_unknown_fields)]
208pub struct ManageProfiles {
209 #[serde(default)]
210 pub customize_tools: Option<AgentProfileId>,
211}
212
213impl ManageProfiles {
214 pub fn customize_tools(profile_id: AgentProfileId) -> Self {
215 Self {
216 customize_tools: Some(profile_id),
217 }
218 }
219}
220
221#[derive(Clone)]
222pub(crate) enum ModelUsageContext {
223 InlineAssistant,
224}
225
226impl ModelUsageContext {
227 pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
228 match self {
229 Self::InlineAssistant => {
230 LanguageModelRegistry::read_global(cx).inline_assistant_model()
231 }
232 }
233 }
234
235 pub fn language_model(&self, cx: &App) -> Option<Arc<dyn LanguageModel>> {
236 self.configured_model(cx)
237 .map(|configured_model| configured_model.model)
238 }
239}
240
241/// Initializes the `agent` crate.
242pub fn init(
243 fs: Arc<dyn Fs>,
244 client: Arc<Client>,
245 prompt_builder: Arc<PromptBuilder>,
246 language_registry: Arc<LanguageRegistry>,
247 is_eval: bool,
248 cx: &mut App,
249) {
250 assistant_text_thread::init(client.clone(), cx);
251 rules_library::init(cx);
252 if !is_eval {
253 // Initializing the language model from the user settings messes with the eval, so we only initialize them when
254 // we're not running inside of the eval.
255 init_language_model_settings(cx);
256 }
257 assistant_slash_command::init(cx);
258 agent_panel::init(cx);
259 context_server_configuration::init(language_registry.clone(), fs.clone(), cx);
260 TextThreadEditor::init(cx);
261
262 register_slash_commands(cx);
263 inline_assistant::init(
264 fs.clone(),
265 prompt_builder.clone(),
266 client.telemetry().clone(),
267 cx,
268 );
269 terminal_inline_assistant::init(fs.clone(), prompt_builder, client.telemetry().clone(), cx);
270 cx.observe_new(move |workspace, window, cx| {
271 ConfigureContextServerModal::register(workspace, language_registry.clone(), window, cx)
272 })
273 .detach();
274 cx.observe_new(ManageProfilesModal::register).detach();
275
276 // Update command palette filter based on AI settings
277 update_command_palette_filter(cx);
278
279 // Watch for settings changes
280 cx.observe_global::<SettingsStore>(|app_cx| {
281 // When settings change, update the command palette filter
282 update_command_palette_filter(app_cx);
283 })
284 .detach();
285}
286
287fn update_command_palette_filter(cx: &mut App) {
288 let disable_ai = DisableAiSettings::get_global(cx).disable_ai;
289 CommandPaletteFilter::update_global(cx, |filter, _| {
290 if disable_ai {
291 filter.hide_namespace("agent");
292 filter.hide_namespace("assistant");
293 filter.hide_namespace("copilot");
294 filter.hide_namespace("supermaven");
295 filter.hide_namespace("zed_predict_onboarding");
296 filter.hide_namespace("edit_prediction");
297
298 use editor::actions::{
299 AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
300 PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
301 };
302 let edit_prediction_actions = [
303 TypeId::of::<AcceptEditPrediction>(),
304 TypeId::of::<AcceptPartialEditPrediction>(),
305 TypeId::of::<ShowEditPrediction>(),
306 TypeId::of::<NextEditPrediction>(),
307 TypeId::of::<PreviousEditPrediction>(),
308 TypeId::of::<ToggleEditPrediction>(),
309 ];
310 filter.hide_action_types(&edit_prediction_actions);
311 filter.hide_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
312 } else {
313 filter.show_namespace("agent");
314 filter.show_namespace("assistant");
315 filter.show_namespace("copilot");
316 filter.show_namespace("zed_predict_onboarding");
317
318 filter.show_namespace("edit_prediction");
319
320 use editor::actions::{
321 AcceptEditPrediction, AcceptPartialEditPrediction, NextEditPrediction,
322 PreviousEditPrediction, ShowEditPrediction, ToggleEditPrediction,
323 };
324 let edit_prediction_actions = [
325 TypeId::of::<AcceptEditPrediction>(),
326 TypeId::of::<AcceptPartialEditPrediction>(),
327 TypeId::of::<ShowEditPrediction>(),
328 TypeId::of::<NextEditPrediction>(),
329 TypeId::of::<PreviousEditPrediction>(),
330 TypeId::of::<ToggleEditPrediction>(),
331 ];
332 filter.show_action_types(edit_prediction_actions.iter());
333
334 filter.show_action_types(&[TypeId::of::<zed_actions::OpenZedPredictOnboarding>()]);
335 }
336 });
337}
338
339fn init_language_model_settings(cx: &mut App) {
340 update_active_language_model_from_settings(cx);
341
342 cx.observe_global::<SettingsStore>(update_active_language_model_from_settings)
343 .detach();
344 cx.subscribe(
345 &LanguageModelRegistry::global(cx),
346 |_, event: &language_model::Event, cx| match event {
347 language_model::Event::ProviderStateChanged(_)
348 | language_model::Event::AddedProvider(_)
349 | language_model::Event::RemovedProvider(_) => {
350 update_active_language_model_from_settings(cx);
351 }
352 _ => {}
353 },
354 )
355 .detach();
356}
357
358fn update_active_language_model_from_settings(cx: &mut App) {
359 let settings = AgentSettings::get_global(cx);
360
361 fn to_selected_model(selection: &LanguageModelSelection) -> language_model::SelectedModel {
362 language_model::SelectedModel {
363 provider: LanguageModelProviderId::from(selection.provider.0.clone()),
364 model: LanguageModelId::from(selection.model.clone()),
365 }
366 }
367
368 let default = settings.default_model.as_ref().map(to_selected_model);
369 let inline_assistant = settings
370 .inline_assistant_model
371 .as_ref()
372 .map(to_selected_model);
373 let commit_message = settings
374 .commit_message_model
375 .as_ref()
376 .map(to_selected_model);
377 let thread_summary = settings
378 .thread_summary_model
379 .as_ref()
380 .map(to_selected_model);
381 let inline_alternatives = settings
382 .inline_alternatives
383 .iter()
384 .map(to_selected_model)
385 .collect::<Vec<_>>();
386
387 LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
388 registry.select_default_model(default.as_ref(), cx);
389 registry.select_inline_assistant_model(inline_assistant.as_ref(), cx);
390 registry.select_commit_message_model(commit_message.as_ref(), cx);
391 registry.select_thread_summary_model(thread_summary.as_ref(), cx);
392 registry.select_inline_alternative_models(inline_alternatives, cx);
393 });
394}
395
396fn register_slash_commands(cx: &mut App) {
397 let slash_command_registry = SlashCommandRegistry::global(cx);
398
399 slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
400 slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
401 slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
402 slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
403 slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
404 slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
405 slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
406 slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
407 slash_command_registry
408 .register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
409 slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
410
411 cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
412 move |is_enabled, _cx| {
413 if is_enabled {
414 slash_command_registry.register_command(
415 assistant_slash_commands::StreamingExampleSlashCommand,
416 false,
417 );
418 }
419 }
420 })
421 .detach();
422}