assistant2.rs

  1mod assistant_settings;
  2mod attachments;
  3mod completion_provider;
  4mod tools;
  5pub mod ui;
  6
  7use ::ui::{div, prelude::*, Color, ViewContext};
  8use anyhow::{Context, Result};
  9use assistant_tooling::{ToolFunctionCall, ToolRegistry};
 10use attachments::{ActiveEditorAttachmentTool, UserAttachment, UserAttachmentStore};
 11use client::{proto, Client, UserStore};
 12use collections::HashMap;
 13use completion_provider::*;
 14use editor::Editor;
 15use feature_flags::FeatureFlagAppExt as _;
 16use futures::{future::join_all, StreamExt};
 17use gpui::{
 18    list, AnyElement, AppContext, AsyncWindowContext, ClickEvent, EventEmitter, FocusHandle,
 19    FocusableView, ListAlignment, ListState, Model, Render, Task, View, WeakView,
 20};
 21use language::{language_settings::SoftWrap, LanguageRegistry};
 22use open_ai::{FunctionContent, ToolCall, ToolCallContent};
 23use rich_text::RichText;
 24use semantic_index::{CloudEmbeddingProvider, ProjectIndex, SemanticIndex};
 25use serde::Deserialize;
 26use settings::Settings;
 27use std::sync::Arc;
 28use ui::{ActiveFileButton, Composer, ProjectIndexButton};
 29use util::{maybe, paths::EMBEDDINGS_DIR, ResultExt};
 30use workspace::{
 31    dock::{DockPosition, Panel, PanelEvent},
 32    Workspace,
 33};
 34
 35pub use assistant_settings::AssistantSettings;
 36
 37use crate::tools::{CreateBufferTool, ProjectIndexTool};
 38use crate::ui::UserOrAssistant;
 39
 40const MAX_COMPLETION_CALLS_PER_SUBMISSION: usize = 5;
 41
 42#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
 43pub struct Submit(SubmitMode);
 44
 45/// There are multiple different ways to submit a model request, represented by this enum.
 46#[derive(Eq, PartialEq, Copy, Clone, Deserialize)]
 47pub enum SubmitMode {
 48    /// Only include the conversation.
 49    Simple,
 50    /// Send the current file as context.
 51    CurrentFile,
 52    /// Search the codebase and send relevant excerpts.
 53    Codebase,
 54}
 55
 56gpui::actions!(assistant2, [Cancel, ToggleFocus, DebugProjectIndex]);
 57gpui::impl_actions!(assistant2, [Submit]);
 58
 59pub fn init(client: Arc<Client>, cx: &mut AppContext) {
 60    AssistantSettings::register(cx);
 61
 62    cx.spawn(|mut cx| {
 63        let client = client.clone();
 64        async move {
 65            let embedding_provider = CloudEmbeddingProvider::new(client.clone());
 66            let semantic_index = SemanticIndex::new(
 67                EMBEDDINGS_DIR.join("semantic-index-db.0.mdb"),
 68                Arc::new(embedding_provider),
 69                &mut cx,
 70            )
 71            .await?;
 72            cx.update(|cx| cx.set_global(semantic_index))
 73        }
 74    })
 75    .detach();
 76
 77    cx.set_global(CompletionProvider::new(CloudCompletionProvider::new(
 78        client,
 79    )));
 80
 81    cx.observe_new_views(
 82        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
 83            workspace.register_action(|workspace, _: &ToggleFocus, cx| {
 84                workspace.toggle_panel_focus::<AssistantPanel>(cx);
 85            });
 86        },
 87    )
 88    .detach();
 89}
 90
 91pub fn enabled(cx: &AppContext) -> bool {
 92    cx.is_staff()
 93}
 94
 95pub struct AssistantPanel {
 96    chat: View<AssistantChat>,
 97    width: Option<Pixels>,
 98}
 99
100impl AssistantPanel {
101    pub fn load(
102        workspace: WeakView<Workspace>,
103        cx: AsyncWindowContext,
104    ) -> Task<Result<View<Self>>> {
105        cx.spawn(|mut cx| async move {
106            let (app_state, project) = workspace.update(&mut cx, |workspace, _| {
107                (workspace.app_state().clone(), workspace.project().clone())
108            })?;
109
110            let user_store = app_state.user_store.clone();
111
112            cx.new_view(|cx| {
113                // todo!("this will panic if the semantic index failed to load or has not loaded yet")
114                let project_index = cx.update_global(|semantic_index: &mut SemanticIndex, cx| {
115                    semantic_index.project_index(project.clone(), cx)
116                });
117
118                let mut tool_registry = ToolRegistry::new();
119                tool_registry
120                    .register(
121                        ProjectIndexTool::new(project_index.clone(), app_state.fs.clone()),
122                        cx,
123                    )
124                    .context("failed to register ProjectIndexTool")
125                    .log_err();
126                tool_registry
127                    .register(
128                        CreateBufferTool::new(workspace.clone(), project.clone()),
129                        cx,
130                    )
131                    .context("failed to register CreateBufferTool")
132                    .log_err();
133
134                let mut attachment_store = UserAttachmentStore::new();
135                attachment_store.register(ActiveEditorAttachmentTool::new(workspace.clone(), cx));
136
137                Self::new(
138                    app_state.languages.clone(),
139                    Arc::new(attachment_store),
140                    Arc::new(tool_registry),
141                    user_store,
142                    Some(project_index),
143                    workspace,
144                    cx,
145                )
146            })
147        })
148    }
149
150    pub fn new(
151        language_registry: Arc<LanguageRegistry>,
152        attachment_store: Arc<UserAttachmentStore>,
153        tool_registry: Arc<ToolRegistry>,
154        user_store: Model<UserStore>,
155        project_index: Option<Model<ProjectIndex>>,
156        workspace: WeakView<Workspace>,
157        cx: &mut ViewContext<Self>,
158    ) -> Self {
159        let chat = cx.new_view(|cx| {
160            AssistantChat::new(
161                language_registry.clone(),
162                attachment_store.clone(),
163                tool_registry.clone(),
164                user_store,
165                project_index,
166                workspace,
167                cx,
168            )
169        });
170
171        Self { width: None, chat }
172    }
173}
174
175impl Render for AssistantPanel {
176    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
177        div()
178            .size_full()
179            .v_flex()
180            .p_2()
181            .bg(cx.theme().colors().background)
182            .child(self.chat.clone())
183    }
184}
185
186impl Panel for AssistantPanel {
187    fn persistent_name() -> &'static str {
188        "AssistantPanelv2"
189    }
190
191    fn position(&self, _cx: &WindowContext) -> workspace::dock::DockPosition {
192        // todo!("Add a setting / use assistant settings")
193        DockPosition::Right
194    }
195
196    fn position_is_valid(&self, position: workspace::dock::DockPosition) -> bool {
197        matches!(position, DockPosition::Right)
198    }
199
200    fn set_position(&mut self, _: workspace::dock::DockPosition, _: &mut ViewContext<Self>) {
201        // Do nothing until we have a setting for this
202    }
203
204    fn size(&self, _cx: &WindowContext) -> Pixels {
205        self.width.unwrap_or(px(400.))
206    }
207
208    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
209        self.width = size;
210        cx.notify();
211    }
212
213    fn icon(&self, _cx: &WindowContext) -> Option<::ui::IconName> {
214        Some(IconName::ZedAssistant)
215    }
216
217    fn icon_tooltip(&self, _: &WindowContext) -> Option<&'static str> {
218        Some("Assistant Panel ✨")
219    }
220
221    fn toggle_action(&self) -> Box<dyn gpui::Action> {
222        Box::new(ToggleFocus)
223    }
224}
225
226impl EventEmitter<PanelEvent> for AssistantPanel {}
227
228impl FocusableView for AssistantPanel {
229    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
230        self.chat.read(cx).composer_editor.read(cx).focus_handle(cx)
231    }
232}
233
234pub struct AssistantChat {
235    model: String,
236    messages: Vec<ChatMessage>,
237    list_state: ListState,
238    language_registry: Arc<LanguageRegistry>,
239    composer_editor: View<Editor>,
240    project_index_button: Option<View<ProjectIndexButton>>,
241    active_file_button: Option<View<ActiveFileButton>>,
242    user_store: Model<UserStore>,
243    next_message_id: MessageId,
244    collapsed_messages: HashMap<MessageId, bool>,
245    editing_message: Option<EditingMessage>,
246    pending_completion: Option<Task<()>>,
247    attachment_store: Arc<UserAttachmentStore>,
248    tool_registry: Arc<ToolRegistry>,
249    project_index: Option<Model<ProjectIndex>>,
250}
251
252struct EditingMessage {
253    id: MessageId,
254    old_body: Arc<str>,
255    body: View<Editor>,
256}
257
258impl AssistantChat {
259    fn new(
260        language_registry: Arc<LanguageRegistry>,
261        attachment_store: Arc<UserAttachmentStore>,
262        tool_registry: Arc<ToolRegistry>,
263        user_store: Model<UserStore>,
264        project_index: Option<Model<ProjectIndex>>,
265        workspace: WeakView<Workspace>,
266        cx: &mut ViewContext<Self>,
267    ) -> Self {
268        let model = CompletionProvider::get(cx).default_model();
269        let view = cx.view().downgrade();
270        let list_state = ListState::new(
271            0,
272            ListAlignment::Bottom,
273            px(1024.),
274            move |ix, cx: &mut WindowContext| {
275                view.update(cx, |this, cx| this.render_message(ix, cx))
276                    .unwrap()
277            },
278        );
279
280        let project_index_button = project_index.clone().map(|project_index| {
281            cx.new_view(|cx| ProjectIndexButton::new(project_index, tool_registry.clone(), cx))
282        });
283
284        let active_file_button = match workspace.upgrade() {
285            Some(workspace) => {
286                Some(cx.new_view(
287                    |cx| ActiveFileButton::new(attachment_store.clone(), workspace, cx), //
288                ))
289            }
290            _ => None,
291        };
292
293        Self {
294            model,
295            messages: Vec::new(),
296            composer_editor: cx.new_view(|cx| {
297                let mut editor = Editor::auto_height(80, cx);
298                editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
299                editor.set_placeholder_text("Send a message…", cx);
300                editor
301            }),
302            list_state,
303            user_store,
304            language_registry,
305            project_index_button,
306            active_file_button,
307            project_index,
308            next_message_id: MessageId(0),
309            editing_message: None,
310            collapsed_messages: HashMap::default(),
311            pending_completion: None,
312            attachment_store,
313            tool_registry,
314        }
315    }
316
317    fn editing_message_id(&self) -> Option<MessageId> {
318        self.editing_message.as_ref().map(|message| message.id)
319    }
320
321    fn focused_message_id(&self, cx: &WindowContext) -> Option<MessageId> {
322        self.messages.iter().find_map(|message| match message {
323            ChatMessage::User(message) => message
324                .body
325                .focus_handle(cx)
326                .contains_focused(cx)
327                .then_some(message.id),
328            ChatMessage::Assistant(_) => None,
329        })
330    }
331
332    fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
333        // If we're currently editing a message, cancel the edit.
334        if let Some(editing_message) = self.editing_message.take() {
335            editing_message
336                .body
337                .update(cx, |body, cx| body.set_text(editing_message.old_body, cx));
338            return;
339        }
340
341        if self.pending_completion.take().is_some() {
342            if let Some(ChatMessage::Assistant(message)) = self.messages.last() {
343                if message.body.text.is_empty() {
344                    self.pop_message(cx);
345                }
346            }
347            return;
348        }
349
350        cx.propagate();
351    }
352
353    fn submit(&mut self, Submit(mode): &Submit, cx: &mut ViewContext<Self>) {
354        if let Some(focused_message_id) = self.focused_message_id(cx) {
355            self.truncate_messages(focused_message_id, cx);
356            self.pending_completion.take();
357            self.composer_editor.focus_handle(cx).focus(cx);
358            if self.editing_message_id() == Some(focused_message_id) {
359                self.editing_message.take();
360            }
361        } else if self.composer_editor.focus_handle(cx).is_focused(cx) {
362            // Don't allow multiple concurrent completions.
363            if self.pending_completion.is_some() {
364                cx.propagate();
365                return;
366            }
367
368            let message = self.composer_editor.update(cx, |composer_editor, cx| {
369                let text = composer_editor.text(cx);
370                let id = self.next_message_id.post_inc();
371                let body = cx.new_view(|cx| {
372                    let mut editor = Editor::auto_height(80, cx);
373                    editor.set_text(text, cx);
374                    editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
375                    editor
376                });
377                composer_editor.clear(cx);
378
379                ChatMessage::User(UserMessage {
380                    id,
381                    body,
382                    attachments: Vec::new(),
383                })
384            });
385            self.push_message(message, cx);
386        } else {
387            log::error!("unexpected state: no user message editor is focused.");
388            return;
389        }
390
391        let mode = *mode;
392        self.pending_completion = Some(cx.spawn(move |this, mut cx| async move {
393            let attachments_task = this.update(&mut cx, |this, cx| {
394                let attachment_store = this.attachment_store.clone();
395                attachment_store.call_all_attachment_tools(cx)
396            });
397
398            let attachments = maybe!(async {
399                let attachments_task = attachments_task?;
400                let attachments = attachments_task.await?;
401
402                anyhow::Ok(attachments)
403            })
404            .await
405            .log_err()
406            .unwrap_or_default();
407
408            // Set the attachments to the _last_ user message
409            this.update(&mut cx, |this, _cx| {
410                if let Some(ChatMessage::User(message)) = this.messages.last_mut() {
411                    message.attachments = attachments;
412                }
413            })
414            .log_err();
415
416            Self::request_completion(
417                this.clone(),
418                mode,
419                MAX_COMPLETION_CALLS_PER_SUBMISSION,
420                &mut cx,
421            )
422            .await
423            .log_err();
424
425            this.update(&mut cx, |this, _cx| {
426                this.pending_completion = None;
427            })
428            .context("Failed to push new user message")
429            .log_err();
430        }));
431    }
432
433    fn debug_project_index(&mut self, _: &DebugProjectIndex, cx: &mut ViewContext<Self>) {
434        if let Some(index) = &self.project_index {
435            index.update(cx, |project_index, cx| {
436                project_index.debug(cx).detach_and_log_err(cx)
437            });
438        }
439    }
440
441    async fn request_completion(
442        this: WeakView<Self>,
443        mode: SubmitMode,
444        limit: usize,
445        cx: &mut AsyncWindowContext,
446    ) -> Result<()> {
447        let mut call_count = 0;
448        loop {
449            let complete = async {
450                let completion = this.update(cx, |this, cx| {
451                    this.push_new_assistant_message(cx);
452
453                    let definitions = if call_count < limit
454                        && matches!(mode, SubmitMode::Codebase | SubmitMode::Simple)
455                    {
456                        this.tool_registry.definitions()
457                    } else {
458                        Vec::new()
459                    };
460                    call_count += 1;
461
462                    let messages = this.completion_messages(cx);
463
464                    CompletionProvider::get(cx).complete(
465                        this.model.clone(),
466                        messages,
467                        Vec::new(),
468                        1.0,
469                        definitions,
470                    )
471                });
472
473                let mut stream = completion?.await?;
474                let mut body = String::new();
475                while let Some(delta) = stream.next().await {
476                    let delta = delta?;
477                    this.update(cx, |this, cx| {
478                        if let Some(ChatMessage::Assistant(AssistantMessage {
479                            body: message_body,
480                            tool_calls: message_tool_calls,
481                            ..
482                        })) = this.messages.last_mut()
483                        {
484                            if let Some(content) = &delta.content {
485                                body.push_str(content);
486                            }
487
488                            for tool_call in delta.tool_calls {
489                                let index = tool_call.index as usize;
490                                if index >= message_tool_calls.len() {
491                                    message_tool_calls.resize_with(index + 1, Default::default);
492                                }
493                                let call = &mut message_tool_calls[index];
494
495                                if let Some(id) = &tool_call.id {
496                                    call.id.push_str(id);
497                                }
498
499                                match tool_call.variant {
500                                    Some(proto::tool_call_delta::Variant::Function(tool_call)) => {
501                                        if let Some(name) = &tool_call.name {
502                                            call.name.push_str(name);
503                                        }
504                                        if let Some(arguments) = &tool_call.arguments {
505                                            call.arguments.push_str(arguments);
506                                        }
507                                    }
508                                    None => {}
509                                }
510                            }
511
512                            *message_body =
513                                RichText::new(body.clone(), &[], &this.language_registry);
514                            cx.notify();
515                        } else {
516                            unreachable!()
517                        }
518                    })?;
519                }
520
521                anyhow::Ok(())
522            }
523            .await;
524
525            let mut tool_tasks = Vec::new();
526            this.update(cx, |this, cx| {
527                if let Some(ChatMessage::Assistant(AssistantMessage {
528                    error: message_error,
529                    tool_calls,
530                    ..
531                })) = this.messages.last_mut()
532                {
533                    if let Err(error) = complete {
534                        message_error.replace(SharedString::from(error.to_string()));
535                        cx.notify();
536                    } else {
537                        for tool_call in tool_calls.iter() {
538                            tool_tasks.push(this.tool_registry.call(tool_call, cx));
539                        }
540                    }
541                }
542            })?;
543
544            if tool_tasks.is_empty() {
545                return Ok(());
546            }
547
548            let tools = join_all(tool_tasks.into_iter()).await;
549            // If the WindowContext went away for any tool's view we don't include it
550            // especially since the below call would fail for the same reason.
551            let tools = tools.into_iter().filter_map(|tool| tool.ok()).collect();
552
553            this.update(cx, |this, cx| {
554                if let Some(ChatMessage::Assistant(AssistantMessage { tool_calls, .. })) =
555                    this.messages.last_mut()
556                {
557                    *tool_calls = tools;
558                    cx.notify();
559                }
560            })?;
561        }
562    }
563
564    fn push_new_assistant_message(&mut self, cx: &mut ViewContext<Self>) {
565        let message = ChatMessage::Assistant(AssistantMessage {
566            id: self.next_message_id.post_inc(),
567            body: RichText::default(),
568            tool_calls: Vec::new(),
569            error: None,
570        });
571        self.push_message(message, cx);
572    }
573
574    fn push_message(&mut self, message: ChatMessage, cx: &mut ViewContext<Self>) {
575        let old_len = self.messages.len();
576        let focus_handle = Some(message.focus_handle(cx));
577        self.messages.push(message);
578        self.list_state
579            .splice_focusable(old_len..old_len, focus_handle);
580        cx.notify();
581    }
582
583    fn pop_message(&mut self, cx: &mut ViewContext<Self>) {
584        if self.messages.is_empty() {
585            return;
586        }
587
588        self.messages.pop();
589        self.list_state
590            .splice(self.messages.len()..self.messages.len() + 1, 0);
591        cx.notify();
592    }
593
594    fn truncate_messages(&mut self, last_message_id: MessageId, cx: &mut ViewContext<Self>) {
595        if let Some(index) = self.messages.iter().position(|message| match message {
596            ChatMessage::User(message) => message.id == last_message_id,
597            ChatMessage::Assistant(message) => message.id == last_message_id,
598        }) {
599            self.list_state.splice(index + 1..self.messages.len(), 0);
600            self.messages.truncate(index + 1);
601            cx.notify();
602        }
603    }
604
605    fn is_message_collapsed(&self, id: &MessageId) -> bool {
606        self.collapsed_messages.get(id).copied().unwrap_or_default()
607    }
608
609    fn toggle_message_collapsed(&mut self, id: MessageId) {
610        let entry = self.collapsed_messages.entry(id).or_insert(false);
611        *entry = !*entry;
612    }
613
614    fn render_error(
615        &self,
616        error: Option<SharedString>,
617        _ix: usize,
618        cx: &mut ViewContext<Self>,
619    ) -> AnyElement {
620        let theme = cx.theme();
621
622        if let Some(error) = error {
623            div()
624                .py_1()
625                .px_2()
626                .neg_mx_1()
627                .rounded_md()
628                .border()
629                .border_color(theme.status().error_border)
630                // .bg(theme.status().error_background)
631                .text_color(theme.status().error)
632                .child(error.clone())
633                .into_any_element()
634        } else {
635            div().into_any_element()
636        }
637    }
638
639    fn render_message(&self, ix: usize, cx: &mut ViewContext<Self>) -> AnyElement {
640        let is_last = ix == self.messages.len() - 1;
641
642        match &self.messages[ix] {
643            ChatMessage::User(UserMessage {
644                id,
645                body,
646                attachments,
647            }) => div()
648                .id(SharedString::from(format!("message-{}-container", id.0)))
649                .when(!is_last, |element| element.mb_2())
650                .map(|element| {
651                    if self.editing_message_id() == Some(*id) {
652                        element.child(Composer::new(
653                            body.clone(),
654                            self.project_index_button.clone(),
655                            self.active_file_button.clone(),
656                            crate::ui::ModelSelector::new(
657                                cx.view().downgrade(),
658                                self.model.clone(),
659                            )
660                            .into_any_element(),
661                        ))
662                    } else {
663                        element
664                            .on_click(cx.listener({
665                                let id = *id;
666                                let body = body.clone();
667                                move |assistant_chat, event: &ClickEvent, cx| {
668                                    if event.up.click_count == 2 {
669                                        assistant_chat.editing_message = Some(EditingMessage {
670                                            id,
671                                            body: body.clone(),
672                                            old_body: body.read(cx).text(cx).into(),
673                                        });
674                                        body.focus_handle(cx).focus(cx);
675                                    }
676                                }
677                            }))
678                            .child(crate::ui::ChatMessage::new(
679                                *id,
680                                UserOrAssistant::User(self.user_store.read(cx).current_user()),
681                                Some(
682                                    RichText::new(
683                                        body.read(cx).text(cx),
684                                        &[],
685                                        &self.language_registry,
686                                    )
687                                    .element(ElementId::from(id.0), cx),
688                                ),
689                                Some(
690                                    h_flex()
691                                        .gap_2()
692                                        .children(
693                                            attachments
694                                                .iter()
695                                                .map(|attachment| attachment.view.clone()),
696                                        )
697                                        .into_any_element(),
698                                ),
699                                self.is_message_collapsed(id),
700                                Box::new(cx.listener({
701                                    let id = *id;
702                                    move |assistant_chat, _event, _cx| {
703                                        assistant_chat.toggle_message_collapsed(id)
704                                    }
705                                })),
706                            ))
707                    }
708                })
709                .into_any(),
710            ChatMessage::Assistant(AssistantMessage {
711                id,
712                body,
713                error,
714                tool_calls,
715                ..
716            }) => {
717                let assistant_body = if body.text.is_empty() {
718                    None
719                } else {
720                    Some(
721                        div()
722                            .p_2()
723                            .child(body.element(ElementId::from(id.0), cx))
724                            .into_any_element(),
725                    )
726                };
727
728                let tools = tool_calls
729                    .iter()
730                    .map(|tool_call| {
731                        let result = &tool_call.result;
732                        let name = tool_call.name.clone();
733                        match result {
734                            Some(result) => div()
735                                .p_2()
736                                .child(result.into_any_element(&name))
737                                .into_any_element(),
738                            None => div()
739                                .p_2()
740                                .child(Label::new(name).color(Color::Modified))
741                                .child("Running...")
742                                .into_any_element(),
743                        }
744                    })
745                    .collect::<Vec<AnyElement>>();
746
747                let tools_body = if tools.is_empty() {
748                    None
749                } else {
750                    Some(div().children(tools).into_any_element())
751                };
752
753                div()
754                    .when(!is_last, |element| element.mb_2())
755                    .child(crate::ui::ChatMessage::new(
756                        *id,
757                        UserOrAssistant::Assistant,
758                        assistant_body,
759                        tools_body,
760                        self.is_message_collapsed(id),
761                        Box::new(cx.listener({
762                            let id = *id;
763                            move |assistant_chat, _event, _cx| {
764                                assistant_chat.toggle_message_collapsed(id)
765                            }
766                        })),
767                    ))
768                    .child(self.render_error(error.clone(), ix, cx))
769                    .into_any()
770            }
771        }
772    }
773
774    fn completion_messages(&self, cx: &mut WindowContext) -> Vec<CompletionMessage> {
775        let mut completion_messages = Vec::new();
776
777        for message in &self.messages {
778            match message {
779                ChatMessage::User(UserMessage {
780                    body, attachments, ..
781                }) => {
782                    completion_messages.extend(
783                        attachments
784                            .into_iter()
785                            .filter_map(|attachment| attachment.message.clone())
786                            .map(|content| CompletionMessage::System { content }),
787                    );
788
789                    // Show user's message last so that the assistant is grounded in the user's request
790                    completion_messages.push(CompletionMessage::User {
791                        content: body.read(cx).text(cx),
792                    });
793                }
794                ChatMessage::Assistant(AssistantMessage {
795                    body, tool_calls, ..
796                }) => {
797                    // In no case do we want to send an empty message. This shouldn't happen, but we might as well
798                    // not break the Chat API if it does.
799                    if body.text.is_empty() && tool_calls.is_empty() {
800                        continue;
801                    }
802
803                    let tool_calls_from_assistant = tool_calls
804                        .iter()
805                        .map(|tool_call| ToolCall {
806                            content: ToolCallContent::Function {
807                                function: FunctionContent {
808                                    name: tool_call.name.clone(),
809                                    arguments: tool_call.arguments.clone(),
810                                },
811                            },
812                            id: tool_call.id.clone(),
813                        })
814                        .collect();
815
816                    completion_messages.push(CompletionMessage::Assistant {
817                        content: Some(body.text.to_string()),
818                        tool_calls: tool_calls_from_assistant,
819                    });
820
821                    for tool_call in tool_calls {
822                        // todo!(): we should not be sending when the tool is still running / has no result
823                        // For now I'm going to have to assume we send an empty string because otherwise
824                        // the Chat API will break -- there is a required message for every tool call by ID
825                        let content = match &tool_call.result {
826                            Some(result) => result.format(&tool_call.name),
827                            None => "".to_string(),
828                        };
829
830                        completion_messages.push(CompletionMessage::Tool {
831                            content,
832                            tool_call_id: tool_call.id.clone(),
833                        });
834                    }
835                }
836            }
837        }
838
839        completion_messages
840    }
841}
842
843impl Render for AssistantChat {
844    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
845        div()
846            .relative()
847            .flex_1()
848            .v_flex()
849            .key_context("AssistantChat")
850            .on_action(cx.listener(Self::submit))
851            .on_action(cx.listener(Self::cancel))
852            .on_action(cx.listener(Self::debug_project_index))
853            .text_color(Color::Default.color(cx))
854            .child(list(self.list_state.clone()).flex_1())
855            .child(Composer::new(
856                self.composer_editor.clone(),
857                self.project_index_button.clone(),
858                self.active_file_button.clone(),
859                crate::ui::ModelSelector::new(cx.view().downgrade(), self.model.clone())
860                    .into_any_element(),
861            ))
862    }
863}
864
865#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
866pub struct MessageId(usize);
867
868impl MessageId {
869    fn post_inc(&mut self) -> Self {
870        let id = *self;
871        self.0 += 1;
872        id
873    }
874}
875
876enum ChatMessage {
877    User(UserMessage),
878    Assistant(AssistantMessage),
879}
880
881impl ChatMessage {
882    fn focus_handle(&self, cx: &AppContext) -> Option<FocusHandle> {
883        match self {
884            ChatMessage::User(UserMessage { body, .. }) => Some(body.focus_handle(cx)),
885            ChatMessage::Assistant(_) => None,
886        }
887    }
888}
889
890struct UserMessage {
891    id: MessageId,
892    body: View<Editor>,
893    attachments: Vec<UserAttachment>,
894}
895
896struct AssistantMessage {
897    id: MessageId,
898    body: RichText,
899    tool_calls: Vec<ToolFunctionCall>,
900    error: Option<SharedString>,
901}