1use std::fmt::Write as _;
2use std::io::Write;
3use std::ops::Range;
4use std::sync::Arc;
5
6use anyhow::{Context as _, Result, anyhow};
7use assistant_settings::AssistantSettings;
8use assistant_tool::{ActionLog, Tool, ToolWorkingSet};
9use chrono::{DateTime, Utc};
10use collections::{BTreeMap, HashMap};
11use fs::Fs;
12use futures::future::Shared;
13use futures::{FutureExt, StreamExt as _};
14use git::repository::DiffType;
15use gpui::{App, AppContext, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};
16use language_model::{
17 ConfiguredModel, LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry,
18 LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool,
19 LanguageModelToolResult, LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent,
20 PaymentRequiredError, Role, StopReason, TokenUsage,
21};
22use project::git_store::{GitStore, GitStoreCheckpoint, RepositoryState};
23use project::{Project, Worktree};
24use prompt_store::{
25 AssistantSystemPromptContext, PromptBuilder, RulesFile, WorktreeInfoForSystemPrompt,
26};
27use schemars::JsonSchema;
28use serde::{Deserialize, Serialize};
29use settings::Settings;
30use util::{ResultExt as _, TryFutureExt as _, maybe, post_inc};
31use uuid::Uuid;
32
33use crate::context::{AssistantContext, ContextId, format_context_as_string};
34use crate::thread_store::{
35 SerializedMessage, SerializedMessageSegment, SerializedThread, SerializedToolResult,
36 SerializedToolUse,
37};
38use crate::tool_use::{PendingToolUse, ToolUse, ToolUseState, USING_TOOL_MARKER};
39
40#[derive(Debug, Clone, Copy)]
41pub enum RequestKind {
42 Chat,
43 /// Used when summarizing a thread.
44 Summarize,
45}
46
47#[derive(
48 Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, JsonSchema,
49)]
50pub struct ThreadId(Arc<str>);
51
52impl ThreadId {
53 pub fn new() -> Self {
54 Self(Uuid::new_v4().to_string().into())
55 }
56}
57
58impl std::fmt::Display for ThreadId {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 write!(f, "{}", self.0)
61 }
62}
63
64impl From<&str> for ThreadId {
65 fn from(value: &str) -> Self {
66 Self(value.into())
67 }
68}
69
70#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
71pub struct MessageId(pub(crate) usize);
72
73impl MessageId {
74 fn post_inc(&mut self) -> Self {
75 Self(post_inc(&mut self.0))
76 }
77}
78
79/// A message in a [`Thread`].
80#[derive(Debug, Clone)]
81pub struct Message {
82 pub id: MessageId,
83 pub role: Role,
84 pub segments: Vec<MessageSegment>,
85 pub context: String,
86}
87
88impl Message {
89 /// Returns whether the message contains any meaningful text that should be displayed
90 /// The model sometimes runs tool without producing any text or just a marker ([`USING_TOOL_MARKER`])
91 pub fn should_display_content(&self) -> bool {
92 self.segments.iter().all(|segment| segment.should_display())
93 }
94
95 pub fn push_thinking(&mut self, text: &str) {
96 if let Some(MessageSegment::Thinking(segment)) = self.segments.last_mut() {
97 segment.push_str(text);
98 } else {
99 self.segments
100 .push(MessageSegment::Thinking(text.to_string()));
101 }
102 }
103
104 pub fn push_text(&mut self, text: &str) {
105 if let Some(MessageSegment::Text(segment)) = self.segments.last_mut() {
106 segment.push_str(text);
107 } else {
108 self.segments.push(MessageSegment::Text(text.to_string()));
109 }
110 }
111
112 pub fn to_string(&self) -> String {
113 let mut result = String::new();
114
115 if !self.context.is_empty() {
116 result.push_str(&self.context);
117 }
118
119 for segment in &self.segments {
120 match segment {
121 MessageSegment::Text(text) => result.push_str(text),
122 MessageSegment::Thinking(text) => {
123 result.push_str("<think>");
124 result.push_str(text);
125 result.push_str("</think>");
126 }
127 }
128 }
129
130 result
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub enum MessageSegment {
136 Text(String),
137 Thinking(String),
138}
139
140impl MessageSegment {
141 pub fn text_mut(&mut self) -> &mut String {
142 match self {
143 Self::Text(text) => text,
144 Self::Thinking(text) => text,
145 }
146 }
147
148 pub fn should_display(&self) -> bool {
149 // We add USING_TOOL_MARKER when making a request that includes tool uses
150 // without non-whitespace text around them, and this can cause the model
151 // to mimic the pattern, so we consider those segments not displayable.
152 match self {
153 Self::Text(text) => text.is_empty() || text.trim() == USING_TOOL_MARKER,
154 Self::Thinking(text) => text.is_empty() || text.trim() == USING_TOOL_MARKER,
155 }
156 }
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ProjectSnapshot {
161 pub worktree_snapshots: Vec<WorktreeSnapshot>,
162 pub unsaved_buffer_paths: Vec<String>,
163 pub timestamp: DateTime<Utc>,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct WorktreeSnapshot {
168 pub worktree_path: String,
169 pub git_state: Option<GitState>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct GitState {
174 pub remote_url: Option<String>,
175 pub head_sha: Option<String>,
176 pub current_branch: Option<String>,
177 pub diff: Option<String>,
178}
179
180#[derive(Clone)]
181pub struct ThreadCheckpoint {
182 message_id: MessageId,
183 git_checkpoint: GitStoreCheckpoint,
184}
185
186#[derive(Copy, Clone, Debug)]
187pub enum ThreadFeedback {
188 Positive,
189 Negative,
190}
191
192pub enum LastRestoreCheckpoint {
193 Pending {
194 message_id: MessageId,
195 },
196 Error {
197 message_id: MessageId,
198 error: String,
199 },
200}
201
202impl LastRestoreCheckpoint {
203 pub fn message_id(&self) -> MessageId {
204 match self {
205 LastRestoreCheckpoint::Pending { message_id } => *message_id,
206 LastRestoreCheckpoint::Error { message_id, .. } => *message_id,
207 }
208 }
209}
210
211#[derive(Clone, Debug, Default, Serialize, Deserialize)]
212pub enum DetailedSummaryState {
213 #[default]
214 NotGenerated,
215 Generating {
216 message_id: MessageId,
217 },
218 Generated {
219 text: SharedString,
220 message_id: MessageId,
221 },
222}
223
224#[derive(Default)]
225pub struct TotalTokenUsage {
226 pub total: usize,
227 pub max: usize,
228 pub ratio: TokenUsageRatio,
229}
230
231#[derive(Default, PartialEq, Eq)]
232pub enum TokenUsageRatio {
233 #[default]
234 Normal,
235 Warning,
236 Exceeded,
237}
238
239/// A thread of conversation with the LLM.
240pub struct Thread {
241 id: ThreadId,
242 updated_at: DateTime<Utc>,
243 summary: Option<SharedString>,
244 pending_summary: Task<Option<()>>,
245 detailed_summary_state: DetailedSummaryState,
246 messages: Vec<Message>,
247 next_message_id: MessageId,
248 context: BTreeMap<ContextId, AssistantContext>,
249 context_by_message: HashMap<MessageId, Vec<ContextId>>,
250 system_prompt_context: Option<AssistantSystemPromptContext>,
251 checkpoints_by_message: HashMap<MessageId, ThreadCheckpoint>,
252 completion_count: usize,
253 pending_completions: Vec<PendingCompletion>,
254 project: Entity<Project>,
255 prompt_builder: Arc<PromptBuilder>,
256 tools: Arc<ToolWorkingSet>,
257 tool_use: ToolUseState,
258 action_log: Entity<ActionLog>,
259 last_restore_checkpoint: Option<LastRestoreCheckpoint>,
260 pending_checkpoint: Option<ThreadCheckpoint>,
261 initial_project_snapshot: Shared<Task<Option<Arc<ProjectSnapshot>>>>,
262 cumulative_token_usage: TokenUsage,
263 feedback: Option<ThreadFeedback>,
264}
265
266impl Thread {
267 pub fn new(
268 project: Entity<Project>,
269 tools: Arc<ToolWorkingSet>,
270 prompt_builder: Arc<PromptBuilder>,
271 cx: &mut Context<Self>,
272 ) -> Self {
273 Self {
274 id: ThreadId::new(),
275 updated_at: Utc::now(),
276 summary: None,
277 pending_summary: Task::ready(None),
278 detailed_summary_state: DetailedSummaryState::NotGenerated,
279 messages: Vec::new(),
280 next_message_id: MessageId(0),
281 context: BTreeMap::default(),
282 context_by_message: HashMap::default(),
283 system_prompt_context: None,
284 checkpoints_by_message: HashMap::default(),
285 completion_count: 0,
286 pending_completions: Vec::new(),
287 project: project.clone(),
288 prompt_builder,
289 tools: tools.clone(),
290 last_restore_checkpoint: None,
291 pending_checkpoint: None,
292 tool_use: ToolUseState::new(tools.clone()),
293 action_log: cx.new(|_| ActionLog::new(project.clone())),
294 initial_project_snapshot: {
295 let project_snapshot = Self::project_snapshot(project, cx);
296 cx.foreground_executor()
297 .spawn(async move { Some(project_snapshot.await) })
298 .shared()
299 },
300 cumulative_token_usage: TokenUsage::default(),
301 feedback: None,
302 }
303 }
304
305 pub fn deserialize(
306 id: ThreadId,
307 serialized: SerializedThread,
308 project: Entity<Project>,
309 tools: Arc<ToolWorkingSet>,
310 prompt_builder: Arc<PromptBuilder>,
311 cx: &mut Context<Self>,
312 ) -> Self {
313 let next_message_id = MessageId(
314 serialized
315 .messages
316 .last()
317 .map(|message| message.id.0 + 1)
318 .unwrap_or(0),
319 );
320 let tool_use =
321 ToolUseState::from_serialized_messages(tools.clone(), &serialized.messages, |_| true);
322
323 Self {
324 id,
325 updated_at: serialized.updated_at,
326 summary: Some(serialized.summary),
327 pending_summary: Task::ready(None),
328 detailed_summary_state: serialized.detailed_summary_state,
329 messages: serialized
330 .messages
331 .into_iter()
332 .map(|message| Message {
333 id: message.id,
334 role: message.role,
335 segments: message
336 .segments
337 .into_iter()
338 .map(|segment| match segment {
339 SerializedMessageSegment::Text { text } => MessageSegment::Text(text),
340 SerializedMessageSegment::Thinking { text } => {
341 MessageSegment::Thinking(text)
342 }
343 })
344 .collect(),
345 context: message.context,
346 })
347 .collect(),
348 next_message_id,
349 context: BTreeMap::default(),
350 context_by_message: HashMap::default(),
351 system_prompt_context: None,
352 checkpoints_by_message: HashMap::default(),
353 completion_count: 0,
354 pending_completions: Vec::new(),
355 last_restore_checkpoint: None,
356 pending_checkpoint: None,
357 project: project.clone(),
358 prompt_builder,
359 tools,
360 tool_use,
361 action_log: cx.new(|_| ActionLog::new(project)),
362 initial_project_snapshot: Task::ready(serialized.initial_project_snapshot).shared(),
363 cumulative_token_usage: serialized.cumulative_token_usage,
364 feedback: None,
365 }
366 }
367
368 pub fn id(&self) -> &ThreadId {
369 &self.id
370 }
371
372 pub fn is_empty(&self) -> bool {
373 self.messages.is_empty()
374 }
375
376 pub fn updated_at(&self) -> DateTime<Utc> {
377 self.updated_at
378 }
379
380 pub fn touch_updated_at(&mut self) {
381 self.updated_at = Utc::now();
382 }
383
384 pub fn summary(&self) -> Option<SharedString> {
385 self.summary.clone()
386 }
387
388 pub fn summary_or_default(&self) -> SharedString {
389 const DEFAULT: SharedString = SharedString::new_static("New Thread");
390 self.summary.clone().unwrap_or(DEFAULT)
391 }
392
393 pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut Context<Self>) {
394 self.summary = Some(summary.into());
395 cx.emit(ThreadEvent::SummaryChanged);
396 }
397
398 pub fn latest_detailed_summary_or_text(&self) -> SharedString {
399 self.latest_detailed_summary()
400 .unwrap_or_else(|| self.text().into())
401 }
402
403 fn latest_detailed_summary(&self) -> Option<SharedString> {
404 if let DetailedSummaryState::Generated { text, .. } = &self.detailed_summary_state {
405 Some(text.clone())
406 } else {
407 None
408 }
409 }
410
411 pub fn message(&self, id: MessageId) -> Option<&Message> {
412 self.messages.iter().find(|message| message.id == id)
413 }
414
415 pub fn messages(&self) -> impl Iterator<Item = &Message> {
416 self.messages.iter()
417 }
418
419 pub fn is_generating(&self) -> bool {
420 !self.pending_completions.is_empty() || !self.all_tools_finished()
421 }
422
423 pub fn tools(&self) -> &Arc<ToolWorkingSet> {
424 &self.tools
425 }
426
427 pub fn pending_tool(&self, id: &LanguageModelToolUseId) -> Option<&PendingToolUse> {
428 self.tool_use
429 .pending_tool_uses()
430 .into_iter()
431 .find(|tool_use| &tool_use.id == id)
432 }
433
434 pub fn tools_needing_confirmation(&self) -> impl Iterator<Item = &PendingToolUse> {
435 self.tool_use
436 .pending_tool_uses()
437 .into_iter()
438 .filter(|tool_use| tool_use.status.needs_confirmation())
439 }
440
441 pub fn has_pending_tool_uses(&self) -> bool {
442 !self.tool_use.pending_tool_uses().is_empty()
443 }
444
445 pub fn checkpoint_for_message(&self, id: MessageId) -> Option<ThreadCheckpoint> {
446 self.checkpoints_by_message.get(&id).cloned()
447 }
448
449 pub fn restore_checkpoint(
450 &mut self,
451 checkpoint: ThreadCheckpoint,
452 cx: &mut Context<Self>,
453 ) -> Task<Result<()>> {
454 self.last_restore_checkpoint = Some(LastRestoreCheckpoint::Pending {
455 message_id: checkpoint.message_id,
456 });
457 cx.emit(ThreadEvent::CheckpointChanged);
458 cx.notify();
459
460 let project = self.project.read(cx);
461 let restore = project
462 .git_store()
463 .read(cx)
464 .restore_checkpoint(checkpoint.git_checkpoint.clone(), cx);
465 cx.spawn(async move |this, cx| {
466 let result = restore.await;
467 this.update(cx, |this, cx| {
468 if let Err(err) = result.as_ref() {
469 this.last_restore_checkpoint = Some(LastRestoreCheckpoint::Error {
470 message_id: checkpoint.message_id,
471 error: err.to_string(),
472 });
473 } else {
474 this.truncate(checkpoint.message_id, cx);
475 this.last_restore_checkpoint = None;
476 }
477 this.pending_checkpoint = None;
478 cx.emit(ThreadEvent::CheckpointChanged);
479 cx.notify();
480 })?;
481 result
482 })
483 }
484
485 fn finalize_pending_checkpoint(&mut self, cx: &mut Context<Self>) {
486 let pending_checkpoint = if self.is_generating() {
487 return;
488 } else if let Some(checkpoint) = self.pending_checkpoint.take() {
489 checkpoint
490 } else {
491 return;
492 };
493
494 let git_store = self.project.read(cx).git_store().clone();
495 let final_checkpoint = git_store.read(cx).checkpoint(cx);
496 cx.spawn(async move |this, cx| match final_checkpoint.await {
497 Ok(final_checkpoint) => {
498 let equal = git_store
499 .read_with(cx, |store, cx| {
500 store.compare_checkpoints(
501 pending_checkpoint.git_checkpoint.clone(),
502 final_checkpoint.clone(),
503 cx,
504 )
505 })?
506 .await
507 .unwrap_or(false);
508
509 if equal {
510 git_store
511 .read_with(cx, |store, cx| {
512 store.delete_checkpoint(pending_checkpoint.git_checkpoint, cx)
513 })?
514 .detach();
515 } else {
516 this.update(cx, |this, cx| {
517 this.insert_checkpoint(pending_checkpoint, cx)
518 })?;
519 }
520
521 git_store
522 .read_with(cx, |store, cx| {
523 store.delete_checkpoint(final_checkpoint, cx)
524 })?
525 .detach();
526
527 Ok(())
528 }
529 Err(_) => this.update(cx, |this, cx| {
530 this.insert_checkpoint(pending_checkpoint, cx)
531 }),
532 })
533 .detach();
534 }
535
536 fn insert_checkpoint(&mut self, checkpoint: ThreadCheckpoint, cx: &mut Context<Self>) {
537 self.checkpoints_by_message
538 .insert(checkpoint.message_id, checkpoint);
539 cx.emit(ThreadEvent::CheckpointChanged);
540 cx.notify();
541 }
542
543 pub fn last_restore_checkpoint(&self) -> Option<&LastRestoreCheckpoint> {
544 self.last_restore_checkpoint.as_ref()
545 }
546
547 pub fn truncate(&mut self, message_id: MessageId, cx: &mut Context<Self>) {
548 let Some(message_ix) = self
549 .messages
550 .iter()
551 .rposition(|message| message.id == message_id)
552 else {
553 return;
554 };
555 for deleted_message in self.messages.drain(message_ix..) {
556 self.context_by_message.remove(&deleted_message.id);
557 self.checkpoints_by_message.remove(&deleted_message.id);
558 }
559 cx.notify();
560 }
561
562 pub fn context_for_message(&self, id: MessageId) -> impl Iterator<Item = &AssistantContext> {
563 self.context_by_message
564 .get(&id)
565 .into_iter()
566 .flat_map(|context| {
567 context
568 .iter()
569 .filter_map(|context_id| self.context.get(&context_id))
570 })
571 }
572
573 /// Returns whether all of the tool uses have finished running.
574 pub fn all_tools_finished(&self) -> bool {
575 // If the only pending tool uses left are the ones with errors, then
576 // that means that we've finished running all of the pending tools.
577 self.tool_use
578 .pending_tool_uses()
579 .iter()
580 .all(|tool_use| tool_use.status.is_error())
581 }
582
583 pub fn tool_uses_for_message(&self, id: MessageId, cx: &App) -> Vec<ToolUse> {
584 self.tool_use.tool_uses_for_message(id, cx)
585 }
586
587 pub fn tool_results_for_message(&self, id: MessageId) -> Vec<&LanguageModelToolResult> {
588 self.tool_use.tool_results_for_message(id)
589 }
590
591 pub fn tool_result(&self, id: &LanguageModelToolUseId) -> Option<&LanguageModelToolResult> {
592 self.tool_use.tool_result(id)
593 }
594
595 pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
596 self.tool_use.message_has_tool_results(message_id)
597 }
598
599 pub fn insert_user_message(
600 &mut self,
601 text: impl Into<String>,
602 context: Vec<AssistantContext>,
603 git_checkpoint: Option<GitStoreCheckpoint>,
604 cx: &mut Context<Self>,
605 ) -> MessageId {
606 let text = text.into();
607
608 let message_id = self.insert_message(Role::User, vec![MessageSegment::Text(text)], cx);
609
610 // Filter out contexts that have already been included in previous messages
611 let new_context: Vec<_> = context
612 .into_iter()
613 .filter(|ctx| !self.context.contains_key(&ctx.id()))
614 .collect();
615
616 if !new_context.is_empty() {
617 if let Some(context_string) = format_context_as_string(new_context.iter(), cx) {
618 if let Some(message) = self.messages.iter_mut().find(|m| m.id == message_id) {
619 message.context = context_string;
620 }
621 }
622
623 self.action_log.update(cx, |log, cx| {
624 // Track all buffers added as context
625 for ctx in &new_context {
626 match ctx {
627 AssistantContext::File(file_ctx) => {
628 log.buffer_added_as_context(file_ctx.context_buffer.buffer.clone(), cx);
629 }
630 AssistantContext::Directory(dir_ctx) => {
631 for context_buffer in &dir_ctx.context_buffers {
632 log.buffer_added_as_context(context_buffer.buffer.clone(), cx);
633 }
634 }
635 AssistantContext::Symbol(symbol_ctx) => {
636 log.buffer_added_as_context(
637 symbol_ctx.context_symbol.buffer.clone(),
638 cx,
639 );
640 }
641 AssistantContext::FetchedUrl(_) | AssistantContext::Thread(_) => {}
642 }
643 }
644 });
645 }
646
647 let context_ids = new_context
648 .iter()
649 .map(|context| context.id())
650 .collect::<Vec<_>>();
651 self.context.extend(
652 new_context
653 .into_iter()
654 .map(|context| (context.id(), context)),
655 );
656 self.context_by_message.insert(message_id, context_ids);
657
658 if let Some(git_checkpoint) = git_checkpoint {
659 self.pending_checkpoint = Some(ThreadCheckpoint {
660 message_id,
661 git_checkpoint,
662 });
663 }
664 message_id
665 }
666
667 pub fn insert_message(
668 &mut self,
669 role: Role,
670 segments: Vec<MessageSegment>,
671 cx: &mut Context<Self>,
672 ) -> MessageId {
673 let id = self.next_message_id.post_inc();
674 self.messages.push(Message {
675 id,
676 role,
677 segments,
678 context: String::new(),
679 });
680 self.touch_updated_at();
681 cx.emit(ThreadEvent::MessageAdded(id));
682 id
683 }
684
685 pub fn edit_message(
686 &mut self,
687 id: MessageId,
688 new_role: Role,
689 new_segments: Vec<MessageSegment>,
690 cx: &mut Context<Self>,
691 ) -> bool {
692 let Some(message) = self.messages.iter_mut().find(|message| message.id == id) else {
693 return false;
694 };
695 message.role = new_role;
696 message.segments = new_segments;
697 self.touch_updated_at();
698 cx.emit(ThreadEvent::MessageEdited(id));
699 true
700 }
701
702 pub fn delete_message(&mut self, id: MessageId, cx: &mut Context<Self>) -> bool {
703 let Some(index) = self.messages.iter().position(|message| message.id == id) else {
704 return false;
705 };
706 self.messages.remove(index);
707 self.context_by_message.remove(&id);
708 self.touch_updated_at();
709 cx.emit(ThreadEvent::MessageDeleted(id));
710 true
711 }
712
713 /// Returns the representation of this [`Thread`] in a textual form.
714 ///
715 /// This is the representation we use when attaching a thread as context to another thread.
716 pub fn text(&self) -> String {
717 let mut text = String::new();
718
719 for message in &self.messages {
720 text.push_str(match message.role {
721 language_model::Role::User => "User:",
722 language_model::Role::Assistant => "Assistant:",
723 language_model::Role::System => "System:",
724 });
725 text.push('\n');
726
727 for segment in &message.segments {
728 match segment {
729 MessageSegment::Text(content) => text.push_str(content),
730 MessageSegment::Thinking(content) => {
731 text.push_str(&format!("<think>{}</think>", content))
732 }
733 }
734 }
735 text.push('\n');
736 }
737
738 text
739 }
740
741 /// Serializes this thread into a format for storage or telemetry.
742 pub fn serialize(&self, cx: &mut Context<Self>) -> Task<Result<SerializedThread>> {
743 let initial_project_snapshot = self.initial_project_snapshot.clone();
744 cx.spawn(async move |this, cx| {
745 let initial_project_snapshot = initial_project_snapshot.await;
746 this.read_with(cx, |this, cx| SerializedThread {
747 version: SerializedThread::VERSION.to_string(),
748 summary: this.summary_or_default(),
749 updated_at: this.updated_at(),
750 messages: this
751 .messages()
752 .map(|message| SerializedMessage {
753 id: message.id,
754 role: message.role,
755 segments: message
756 .segments
757 .iter()
758 .map(|segment| match segment {
759 MessageSegment::Text(text) => {
760 SerializedMessageSegment::Text { text: text.clone() }
761 }
762 MessageSegment::Thinking(text) => {
763 SerializedMessageSegment::Thinking { text: text.clone() }
764 }
765 })
766 .collect(),
767 tool_uses: this
768 .tool_uses_for_message(message.id, cx)
769 .into_iter()
770 .map(|tool_use| SerializedToolUse {
771 id: tool_use.id,
772 name: tool_use.name,
773 input: tool_use.input,
774 })
775 .collect(),
776 tool_results: this
777 .tool_results_for_message(message.id)
778 .into_iter()
779 .map(|tool_result| SerializedToolResult {
780 tool_use_id: tool_result.tool_use_id.clone(),
781 is_error: tool_result.is_error,
782 content: tool_result.content.clone(),
783 })
784 .collect(),
785 context: message.context.clone(),
786 })
787 .collect(),
788 initial_project_snapshot,
789 cumulative_token_usage: this.cumulative_token_usage.clone(),
790 detailed_summary_state: this.detailed_summary_state.clone(),
791 })
792 })
793 }
794
795 pub fn set_system_prompt_context(&mut self, context: AssistantSystemPromptContext) {
796 self.system_prompt_context = Some(context);
797 }
798
799 pub fn system_prompt_context(&self) -> &Option<AssistantSystemPromptContext> {
800 &self.system_prompt_context
801 }
802
803 pub fn load_system_prompt_context(
804 &self,
805 cx: &App,
806 ) -> Task<(AssistantSystemPromptContext, Option<ThreadError>)> {
807 let project = self.project.read(cx);
808 let tasks = project
809 .visible_worktrees(cx)
810 .map(|worktree| {
811 Self::load_worktree_info_for_system_prompt(
812 project.fs().clone(),
813 worktree.read(cx),
814 cx,
815 )
816 })
817 .collect::<Vec<_>>();
818
819 cx.spawn(async |_cx| {
820 let results = futures::future::join_all(tasks).await;
821 let mut first_err = None;
822 let worktrees = results
823 .into_iter()
824 .map(|(worktree, err)| {
825 if first_err.is_none() && err.is_some() {
826 first_err = err;
827 }
828 worktree
829 })
830 .collect::<Vec<_>>();
831 (AssistantSystemPromptContext::new(worktrees), first_err)
832 })
833 }
834
835 fn load_worktree_info_for_system_prompt(
836 fs: Arc<dyn Fs>,
837 worktree: &Worktree,
838 cx: &App,
839 ) -> Task<(WorktreeInfoForSystemPrompt, Option<ThreadError>)> {
840 let root_name = worktree.root_name().into();
841 let abs_path = worktree.abs_path();
842
843 // Note that Cline supports `.clinerules` being a directory, but that is not currently
844 // supported. This doesn't seem to occur often in GitHub repositories.
845 const RULES_FILE_NAMES: [&'static str; 6] = [
846 ".rules",
847 ".cursorrules",
848 ".windsurfrules",
849 ".clinerules",
850 ".github/copilot-instructions.md",
851 "CLAUDE.md",
852 ];
853 let selected_rules_file = RULES_FILE_NAMES
854 .into_iter()
855 .filter_map(|name| {
856 worktree
857 .entry_for_path(name)
858 .filter(|entry| entry.is_file())
859 .map(|entry| (entry.path.clone(), worktree.absolutize(&entry.path)))
860 })
861 .next();
862
863 if let Some((rel_rules_path, abs_rules_path)) = selected_rules_file {
864 cx.spawn(async move |_| {
865 let rules_file_result = maybe!(async move {
866 let abs_rules_path = abs_rules_path?;
867 let text = fs.load(&abs_rules_path).await.with_context(|| {
868 format!("Failed to load assistant rules file {:?}", abs_rules_path)
869 })?;
870 anyhow::Ok(RulesFile {
871 rel_path: rel_rules_path,
872 abs_path: abs_rules_path.into(),
873 text: text.trim().to_string(),
874 })
875 })
876 .await;
877 let (rules_file, rules_file_error) = match rules_file_result {
878 Ok(rules_file) => (Some(rules_file), None),
879 Err(err) => (
880 None,
881 Some(ThreadError::Message {
882 header: "Error loading rules file".into(),
883 message: format!("{err}").into(),
884 }),
885 ),
886 };
887 let worktree_info = WorktreeInfoForSystemPrompt {
888 root_name,
889 abs_path,
890 rules_file,
891 };
892 (worktree_info, rules_file_error)
893 })
894 } else {
895 Task::ready((
896 WorktreeInfoForSystemPrompt {
897 root_name,
898 abs_path,
899 rules_file: None,
900 },
901 None,
902 ))
903 }
904 }
905
906 pub fn send_to_model(
907 &mut self,
908 model: Arc<dyn LanguageModel>,
909 request_kind: RequestKind,
910 cx: &mut Context<Self>,
911 ) {
912 let mut request = self.to_completion_request(request_kind, cx);
913 if model.supports_tools() {
914 request.tools = {
915 let mut tools = Vec::new();
916 tools.extend(self.tools().enabled_tools(cx).into_iter().map(|tool| {
917 LanguageModelRequestTool {
918 name: tool.name(),
919 description: tool.description(),
920 input_schema: tool.input_schema(model.tool_input_format()),
921 }
922 }));
923
924 tools
925 };
926 }
927
928 self.stream_completion(request, model, cx);
929 }
930
931 pub fn used_tools_since_last_user_message(&self) -> bool {
932 for message in self.messages.iter().rev() {
933 if self.tool_use.message_has_tool_results(message.id) {
934 return true;
935 } else if message.role == Role::User {
936 return false;
937 }
938 }
939
940 false
941 }
942
943 pub fn to_completion_request(
944 &self,
945 request_kind: RequestKind,
946 cx: &App,
947 ) -> LanguageModelRequest {
948 let mut request = LanguageModelRequest {
949 messages: vec![],
950 tools: Vec::new(),
951 stop: Vec::new(),
952 temperature: None,
953 };
954
955 if let Some(system_prompt_context) = self.system_prompt_context.as_ref() {
956 if let Some(system_prompt) = self
957 .prompt_builder
958 .generate_assistant_system_prompt(system_prompt_context)
959 .context("failed to generate assistant system prompt")
960 .log_err()
961 {
962 request.messages.push(LanguageModelRequestMessage {
963 role: Role::System,
964 content: vec![MessageContent::Text(system_prompt)],
965 cache: true,
966 });
967 }
968 } else {
969 log::error!("system_prompt_context not set.")
970 }
971
972 for message in &self.messages {
973 let mut request_message = LanguageModelRequestMessage {
974 role: message.role,
975 content: Vec::new(),
976 cache: false,
977 };
978
979 match request_kind {
980 RequestKind::Chat => {
981 self.tool_use
982 .attach_tool_results(message.id, &mut request_message);
983 }
984 RequestKind::Summarize => {
985 // We don't care about tool use during summarization.
986 if self.tool_use.message_has_tool_results(message.id) {
987 continue;
988 }
989 }
990 }
991
992 if !message.segments.is_empty() {
993 request_message
994 .content
995 .push(MessageContent::Text(message.to_string()));
996 }
997
998 match request_kind {
999 RequestKind::Chat => {
1000 self.tool_use
1001 .attach_tool_uses(message.id, &mut request_message);
1002 }
1003 RequestKind::Summarize => {
1004 // We don't care about tool use during summarization.
1005 }
1006 };
1007
1008 request.messages.push(request_message);
1009 }
1010
1011 // https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
1012 if let Some(last) = request.messages.last_mut() {
1013 last.cache = true;
1014 }
1015
1016 self.attached_tracked_files_state(&mut request.messages, cx);
1017
1018 request
1019 }
1020
1021 fn attached_tracked_files_state(
1022 &self,
1023 messages: &mut Vec<LanguageModelRequestMessage>,
1024 cx: &App,
1025 ) {
1026 const STALE_FILES_HEADER: &str = "These files changed since last read:";
1027
1028 let mut stale_message = String::new();
1029
1030 let action_log = self.action_log.read(cx);
1031
1032 for stale_file in action_log.stale_buffers(cx) {
1033 let Some(file) = stale_file.read(cx).file() else {
1034 continue;
1035 };
1036
1037 if stale_message.is_empty() {
1038 write!(&mut stale_message, "{}\n", STALE_FILES_HEADER).ok();
1039 }
1040
1041 writeln!(&mut stale_message, "- {}", file.path().display()).ok();
1042 }
1043
1044 let mut content = Vec::with_capacity(2);
1045
1046 if !stale_message.is_empty() {
1047 content.push(stale_message.into());
1048 }
1049
1050 if action_log.has_edited_files_since_project_diagnostics_check() {
1051 content.push(
1052 "\n\nWhen you're done making changes, make sure to check project diagnostics \
1053 and fix all errors AND warnings you introduced! \
1054 DO NOT mention you're going to do this until you're done."
1055 .into(),
1056 );
1057 }
1058
1059 if !content.is_empty() {
1060 let context_message = LanguageModelRequestMessage {
1061 role: Role::User,
1062 content,
1063 cache: false,
1064 };
1065
1066 messages.push(context_message);
1067 }
1068 }
1069
1070 pub fn stream_completion(
1071 &mut self,
1072 request: LanguageModelRequest,
1073 model: Arc<dyn LanguageModel>,
1074 cx: &mut Context<Self>,
1075 ) {
1076 let pending_completion_id = post_inc(&mut self.completion_count);
1077
1078 let task = cx.spawn(async move |thread, cx| {
1079 let stream = model.stream_completion(request, &cx);
1080 let initial_token_usage =
1081 thread.read_with(cx, |thread, _cx| thread.cumulative_token_usage.clone());
1082 let stream_completion = async {
1083 let mut events = stream.await?;
1084 let mut stop_reason = StopReason::EndTurn;
1085 let mut current_token_usage = TokenUsage::default();
1086
1087 while let Some(event) = events.next().await {
1088 let event = event?;
1089
1090 thread.update(cx, |thread, cx| {
1091 match event {
1092 LanguageModelCompletionEvent::StartMessage { .. } => {
1093 thread.insert_message(
1094 Role::Assistant,
1095 vec![MessageSegment::Text(String::new())],
1096 cx,
1097 );
1098 }
1099 LanguageModelCompletionEvent::Stop(reason) => {
1100 stop_reason = reason;
1101 }
1102 LanguageModelCompletionEvent::UsageUpdate(token_usage) => {
1103 thread.cumulative_token_usage =
1104 thread.cumulative_token_usage.clone() + token_usage.clone()
1105 - current_token_usage.clone();
1106 current_token_usage = token_usage;
1107 }
1108 LanguageModelCompletionEvent::Text(chunk) => {
1109 if let Some(last_message) = thread.messages.last_mut() {
1110 if last_message.role == Role::Assistant {
1111 last_message.push_text(&chunk);
1112 cx.emit(ThreadEvent::StreamedAssistantText(
1113 last_message.id,
1114 chunk,
1115 ));
1116 } else {
1117 // If we won't have an Assistant message yet, assume this chunk marks the beginning
1118 // of a new Assistant response.
1119 //
1120 // Importantly: We do *not* want to emit a `StreamedAssistantText` event here, as it
1121 // will result in duplicating the text of the chunk in the rendered Markdown.
1122 thread.insert_message(
1123 Role::Assistant,
1124 vec![MessageSegment::Text(chunk.to_string())],
1125 cx,
1126 );
1127 };
1128 }
1129 }
1130 LanguageModelCompletionEvent::Thinking(chunk) => {
1131 if let Some(last_message) = thread.messages.last_mut() {
1132 if last_message.role == Role::Assistant {
1133 last_message.push_thinking(&chunk);
1134 cx.emit(ThreadEvent::StreamedAssistantThinking(
1135 last_message.id,
1136 chunk,
1137 ));
1138 } else {
1139 // If we won't have an Assistant message yet, assume this chunk marks the beginning
1140 // of a new Assistant response.
1141 //
1142 // Importantly: We do *not* want to emit a `StreamedAssistantText` event here, as it
1143 // will result in duplicating the text of the chunk in the rendered Markdown.
1144 thread.insert_message(
1145 Role::Assistant,
1146 vec![MessageSegment::Thinking(chunk.to_string())],
1147 cx,
1148 );
1149 };
1150 }
1151 }
1152 LanguageModelCompletionEvent::ToolUse(tool_use) => {
1153 let last_assistant_message_id = thread
1154 .messages
1155 .iter_mut()
1156 .rfind(|message| message.role == Role::Assistant)
1157 .map(|message| message.id)
1158 .unwrap_or_else(|| {
1159 thread.insert_message(Role::Assistant, vec![], cx)
1160 });
1161
1162 thread.tool_use.request_tool_use(
1163 last_assistant_message_id,
1164 tool_use,
1165 cx,
1166 );
1167 }
1168 }
1169
1170 thread.touch_updated_at();
1171 cx.emit(ThreadEvent::StreamedCompletion);
1172 cx.notify();
1173 })?;
1174
1175 smol::future::yield_now().await;
1176 }
1177
1178 thread.update(cx, |thread, cx| {
1179 thread
1180 .pending_completions
1181 .retain(|completion| completion.id != pending_completion_id);
1182
1183 if thread.summary.is_none() && thread.messages.len() >= 2 {
1184 thread.summarize(cx);
1185 }
1186 })?;
1187
1188 anyhow::Ok(stop_reason)
1189 };
1190
1191 let result = stream_completion.await;
1192
1193 thread
1194 .update(cx, |thread, cx| {
1195 thread.finalize_pending_checkpoint(cx);
1196 match result.as_ref() {
1197 Ok(stop_reason) => match stop_reason {
1198 StopReason::ToolUse => {
1199 cx.emit(ThreadEvent::UsePendingTools);
1200 }
1201 StopReason::EndTurn => {}
1202 StopReason::MaxTokens => {}
1203 },
1204 Err(error) => {
1205 if error.is::<PaymentRequiredError>() {
1206 cx.emit(ThreadEvent::ShowError(ThreadError::PaymentRequired));
1207 } else if error.is::<MaxMonthlySpendReachedError>() {
1208 cx.emit(ThreadEvent::ShowError(
1209 ThreadError::MaxMonthlySpendReached,
1210 ));
1211 } else {
1212 let error_message = error
1213 .chain()
1214 .map(|err| err.to_string())
1215 .collect::<Vec<_>>()
1216 .join("\n");
1217 cx.emit(ThreadEvent::ShowError(ThreadError::Message {
1218 header: "Error interacting with language model".into(),
1219 message: SharedString::from(error_message.clone()),
1220 }));
1221 }
1222
1223 thread.cancel_last_completion(cx);
1224 }
1225 }
1226 cx.emit(ThreadEvent::DoneStreaming);
1227
1228 if let Ok(initial_usage) = initial_token_usage {
1229 let usage = thread.cumulative_token_usage.clone() - initial_usage;
1230
1231 telemetry::event!(
1232 "Assistant Thread Completion",
1233 thread_id = thread.id().to_string(),
1234 model = model.telemetry_id(),
1235 model_provider = model.provider_id().to_string(),
1236 input_tokens = usage.input_tokens,
1237 output_tokens = usage.output_tokens,
1238 cache_creation_input_tokens = usage.cache_creation_input_tokens,
1239 cache_read_input_tokens = usage.cache_read_input_tokens,
1240 );
1241 }
1242 })
1243 .ok();
1244 });
1245
1246 self.pending_completions.push(PendingCompletion {
1247 id: pending_completion_id,
1248 _task: task,
1249 });
1250 }
1251
1252 pub fn summarize(&mut self, cx: &mut Context<Self>) {
1253 let Some(model) = LanguageModelRegistry::read_global(cx).thread_summary_model() else {
1254 return;
1255 };
1256
1257 if !model.provider.is_authenticated(cx) {
1258 return;
1259 }
1260
1261 let mut request = self.to_completion_request(RequestKind::Summarize, cx);
1262 request.messages.push(LanguageModelRequestMessage {
1263 role: Role::User,
1264 content: vec![
1265 "Generate a concise 3-7 word title for this conversation, omitting punctuation. \
1266 Go straight to the title, without any preamble and prefix like `Here's a concise suggestion:...` or `Title:`. \
1267 If the conversation is about a specific subject, include it in the title. \
1268 Be descriptive. DO NOT speak in the first person."
1269 .into(),
1270 ],
1271 cache: false,
1272 });
1273
1274 self.pending_summary = cx.spawn(async move |this, cx| {
1275 async move {
1276 let stream = model.model.stream_completion_text(request, &cx);
1277 let mut messages = stream.await?;
1278
1279 let mut new_summary = String::new();
1280 while let Some(message) = messages.stream.next().await {
1281 let text = message?;
1282 let mut lines = text.lines();
1283 new_summary.extend(lines.next());
1284
1285 // Stop if the LLM generated multiple lines.
1286 if lines.next().is_some() {
1287 break;
1288 }
1289 }
1290
1291 this.update(cx, |this, cx| {
1292 if !new_summary.is_empty() {
1293 this.summary = Some(new_summary.into());
1294 }
1295
1296 cx.emit(ThreadEvent::SummaryChanged);
1297 })?;
1298
1299 anyhow::Ok(())
1300 }
1301 .log_err()
1302 .await
1303 });
1304 }
1305
1306 pub fn generate_detailed_summary(&mut self, cx: &mut Context<Self>) -> Option<Task<()>> {
1307 let last_message_id = self.messages.last().map(|message| message.id)?;
1308
1309 match &self.detailed_summary_state {
1310 DetailedSummaryState::Generating { message_id, .. }
1311 | DetailedSummaryState::Generated { message_id, .. }
1312 if *message_id == last_message_id =>
1313 {
1314 // Already up-to-date
1315 return None;
1316 }
1317 _ => {}
1318 }
1319
1320 let ConfiguredModel { model, provider } =
1321 LanguageModelRegistry::read_global(cx).thread_summary_model()?;
1322
1323 if !provider.is_authenticated(cx) {
1324 return None;
1325 }
1326
1327 let mut request = self.to_completion_request(RequestKind::Summarize, cx);
1328
1329 request.messages.push(LanguageModelRequestMessage {
1330 role: Role::User,
1331 content: vec![
1332 "Generate a detailed summary of this conversation. Include:\n\
1333 1. A brief overview of what was discussed\n\
1334 2. Key facts or information discovered\n\
1335 3. Outcomes or conclusions reached\n\
1336 4. Any action items or next steps if any\n\
1337 Format it in Markdown with headings and bullet points."
1338 .into(),
1339 ],
1340 cache: false,
1341 });
1342
1343 let task = cx.spawn(async move |thread, cx| {
1344 let stream = model.stream_completion_text(request, &cx);
1345 let Some(mut messages) = stream.await.log_err() else {
1346 thread
1347 .update(cx, |this, _cx| {
1348 this.detailed_summary_state = DetailedSummaryState::NotGenerated;
1349 })
1350 .log_err();
1351
1352 return;
1353 };
1354
1355 let mut new_detailed_summary = String::new();
1356
1357 while let Some(chunk) = messages.stream.next().await {
1358 if let Some(chunk) = chunk.log_err() {
1359 new_detailed_summary.push_str(&chunk);
1360 }
1361 }
1362
1363 thread
1364 .update(cx, |this, _cx| {
1365 this.detailed_summary_state = DetailedSummaryState::Generated {
1366 text: new_detailed_summary.into(),
1367 message_id: last_message_id,
1368 };
1369 })
1370 .log_err();
1371 });
1372
1373 self.detailed_summary_state = DetailedSummaryState::Generating {
1374 message_id: last_message_id,
1375 };
1376
1377 Some(task)
1378 }
1379
1380 pub fn is_generating_detailed_summary(&self) -> bool {
1381 matches!(
1382 self.detailed_summary_state,
1383 DetailedSummaryState::Generating { .. }
1384 )
1385 }
1386
1387 pub fn use_pending_tools(
1388 &mut self,
1389 cx: &mut Context<Self>,
1390 ) -> impl IntoIterator<Item = PendingToolUse> + use<> {
1391 let request = self.to_completion_request(RequestKind::Chat, cx);
1392 let messages = Arc::new(request.messages);
1393 let pending_tool_uses = self
1394 .tool_use
1395 .pending_tool_uses()
1396 .into_iter()
1397 .filter(|tool_use| tool_use.status.is_idle())
1398 .cloned()
1399 .collect::<Vec<_>>();
1400
1401 for tool_use in pending_tool_uses.iter() {
1402 if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
1403 if tool.needs_confirmation()
1404 && !AssistantSettings::get_global(cx).always_allow_tool_actions
1405 {
1406 self.tool_use.confirm_tool_use(
1407 tool_use.id.clone(),
1408 tool_use.ui_text.clone(),
1409 tool_use.input.clone(),
1410 messages.clone(),
1411 tool,
1412 );
1413 cx.emit(ThreadEvent::ToolConfirmationNeeded);
1414 } else {
1415 self.run_tool(
1416 tool_use.id.clone(),
1417 tool_use.ui_text.clone(),
1418 tool_use.input.clone(),
1419 &messages,
1420 tool,
1421 cx,
1422 );
1423 }
1424 }
1425 }
1426
1427 pending_tool_uses
1428 }
1429
1430 pub fn run_tool(
1431 &mut self,
1432 tool_use_id: LanguageModelToolUseId,
1433 ui_text: impl Into<SharedString>,
1434 input: serde_json::Value,
1435 messages: &[LanguageModelRequestMessage],
1436 tool: Arc<dyn Tool>,
1437 cx: &mut Context<Thread>,
1438 ) {
1439 let task = self.spawn_tool_use(tool_use_id.clone(), messages, input, tool, cx);
1440 self.tool_use
1441 .run_pending_tool(tool_use_id, ui_text.into(), task);
1442 }
1443
1444 fn spawn_tool_use(
1445 &mut self,
1446 tool_use_id: LanguageModelToolUseId,
1447 messages: &[LanguageModelRequestMessage],
1448 input: serde_json::Value,
1449 tool: Arc<dyn Tool>,
1450 cx: &mut Context<Thread>,
1451 ) -> Task<()> {
1452 let tool_name: Arc<str> = tool.name().into();
1453
1454 let run_tool = if self.tools.is_disabled(&tool.source(), &tool_name) {
1455 Task::ready(Err(anyhow!("tool is disabled: {tool_name}")))
1456 } else {
1457 tool.run(
1458 input,
1459 messages,
1460 self.project.clone(),
1461 self.action_log.clone(),
1462 cx,
1463 )
1464 };
1465
1466 cx.spawn({
1467 async move |thread: WeakEntity<Thread>, cx| {
1468 let output = run_tool.await;
1469
1470 thread
1471 .update(cx, |thread, cx| {
1472 let pending_tool_use = thread.tool_use.insert_tool_output(
1473 tool_use_id.clone(),
1474 tool_name,
1475 output,
1476 );
1477
1478 cx.emit(ThreadEvent::ToolFinished {
1479 tool_use_id,
1480 pending_tool_use,
1481 canceled: false,
1482 });
1483 })
1484 .ok();
1485 }
1486 })
1487 }
1488
1489 pub fn attach_tool_results(&mut self, cx: &mut Context<Self>) {
1490 // Insert a user message to contain the tool results.
1491 self.insert_user_message(
1492 // TODO: Sending up a user message without any content results in the model sending back
1493 // responses that also don't have any content. We currently don't handle this case well,
1494 // so for now we provide some text to keep the model on track.
1495 "Here are the tool results.",
1496 Vec::new(),
1497 None,
1498 cx,
1499 );
1500 }
1501
1502 /// Cancels the last pending completion, if there are any pending.
1503 ///
1504 /// Returns whether a completion was canceled.
1505 pub fn cancel_last_completion(&mut self, cx: &mut Context<Self>) -> bool {
1506 let canceled = if self.pending_completions.pop().is_some() {
1507 true
1508 } else {
1509 let mut canceled = false;
1510 for pending_tool_use in self.tool_use.cancel_pending() {
1511 canceled = true;
1512 cx.emit(ThreadEvent::ToolFinished {
1513 tool_use_id: pending_tool_use.id.clone(),
1514 pending_tool_use: Some(pending_tool_use),
1515 canceled: true,
1516 });
1517 }
1518 canceled
1519 };
1520 self.finalize_pending_checkpoint(cx);
1521 canceled
1522 }
1523
1524 /// Returns the feedback given to the thread, if any.
1525 pub fn feedback(&self) -> Option<ThreadFeedback> {
1526 self.feedback
1527 }
1528
1529 /// Reports feedback about the thread and stores it in our telemetry backend.
1530 pub fn report_feedback(
1531 &mut self,
1532 feedback: ThreadFeedback,
1533 cx: &mut Context<Self>,
1534 ) -> Task<Result<()>> {
1535 let final_project_snapshot = Self::project_snapshot(self.project.clone(), cx);
1536 let serialized_thread = self.serialize(cx);
1537 let thread_id = self.id().clone();
1538 let client = self.project.read(cx).client();
1539 self.feedback = Some(feedback);
1540 cx.notify();
1541
1542 cx.background_spawn(async move {
1543 let final_project_snapshot = final_project_snapshot.await;
1544 let serialized_thread = serialized_thread.await?;
1545 let thread_data =
1546 serde_json::to_value(serialized_thread).unwrap_or_else(|_| serde_json::Value::Null);
1547
1548 let rating = match feedback {
1549 ThreadFeedback::Positive => "positive",
1550 ThreadFeedback::Negative => "negative",
1551 };
1552 telemetry::event!(
1553 "Assistant Thread Rated",
1554 rating,
1555 thread_id,
1556 thread_data,
1557 final_project_snapshot
1558 );
1559 client.telemetry().flush_events();
1560
1561 Ok(())
1562 })
1563 }
1564
1565 /// Create a snapshot of the current project state including git information and unsaved buffers.
1566 fn project_snapshot(
1567 project: Entity<Project>,
1568 cx: &mut Context<Self>,
1569 ) -> Task<Arc<ProjectSnapshot>> {
1570 let git_store = project.read(cx).git_store().clone();
1571 let worktree_snapshots: Vec<_> = project
1572 .read(cx)
1573 .visible_worktrees(cx)
1574 .map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
1575 .collect();
1576
1577 cx.spawn(async move |_, cx| {
1578 let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
1579
1580 let mut unsaved_buffers = Vec::new();
1581 cx.update(|app_cx| {
1582 let buffer_store = project.read(app_cx).buffer_store();
1583 for buffer_handle in buffer_store.read(app_cx).buffers() {
1584 let buffer = buffer_handle.read(app_cx);
1585 if buffer.is_dirty() {
1586 if let Some(file) = buffer.file() {
1587 let path = file.path().to_string_lossy().to_string();
1588 unsaved_buffers.push(path);
1589 }
1590 }
1591 }
1592 })
1593 .ok();
1594
1595 Arc::new(ProjectSnapshot {
1596 worktree_snapshots,
1597 unsaved_buffer_paths: unsaved_buffers,
1598 timestamp: Utc::now(),
1599 })
1600 })
1601 }
1602
1603 fn worktree_snapshot(
1604 worktree: Entity<project::Worktree>,
1605 git_store: Entity<GitStore>,
1606 cx: &App,
1607 ) -> Task<WorktreeSnapshot> {
1608 cx.spawn(async move |cx| {
1609 // Get worktree path and snapshot
1610 let worktree_info = cx.update(|app_cx| {
1611 let worktree = worktree.read(app_cx);
1612 let path = worktree.abs_path().to_string_lossy().to_string();
1613 let snapshot = worktree.snapshot();
1614 (path, snapshot)
1615 });
1616
1617 let Ok((worktree_path, _snapshot)) = worktree_info else {
1618 return WorktreeSnapshot {
1619 worktree_path: String::new(),
1620 git_state: None,
1621 };
1622 };
1623
1624 let git_state = git_store
1625 .update(cx, |git_store, cx| {
1626 git_store
1627 .repositories()
1628 .values()
1629 .find(|repo| {
1630 repo.read(cx)
1631 .abs_path_to_repo_path(&worktree.read(cx).abs_path())
1632 .is_some()
1633 })
1634 .cloned()
1635 })
1636 .ok()
1637 .flatten()
1638 .map(|repo| {
1639 repo.read_with(cx, |repo, _| {
1640 let current_branch =
1641 repo.branch.as_ref().map(|branch| branch.name.to_string());
1642 repo.send_job(|state, _| async move {
1643 let RepositoryState::Local { backend, .. } = state else {
1644 return GitState {
1645 remote_url: None,
1646 head_sha: None,
1647 current_branch,
1648 diff: None,
1649 };
1650 };
1651
1652 let remote_url = backend.remote_url("origin");
1653 let head_sha = backend.head_sha();
1654 let diff = backend.diff(DiffType::HeadToWorktree).await.ok();
1655
1656 GitState {
1657 remote_url,
1658 head_sha,
1659 current_branch,
1660 diff,
1661 }
1662 })
1663 })
1664 });
1665
1666 let git_state = match git_state {
1667 Some(git_state) => match git_state.ok() {
1668 Some(git_state) => git_state.await.ok(),
1669 None => None,
1670 },
1671 None => None,
1672 };
1673
1674 WorktreeSnapshot {
1675 worktree_path,
1676 git_state,
1677 }
1678 })
1679 }
1680
1681 pub fn to_markdown(&self, cx: &App) -> Result<String> {
1682 let mut markdown = Vec::new();
1683
1684 if let Some(summary) = self.summary() {
1685 writeln!(markdown, "# {summary}\n")?;
1686 };
1687
1688 for message in self.messages() {
1689 writeln!(
1690 markdown,
1691 "## {role}\n",
1692 role = match message.role {
1693 Role::User => "User",
1694 Role::Assistant => "Assistant",
1695 Role::System => "System",
1696 }
1697 )?;
1698
1699 if !message.context.is_empty() {
1700 writeln!(markdown, "{}", message.context)?;
1701 }
1702
1703 for segment in &message.segments {
1704 match segment {
1705 MessageSegment::Text(text) => writeln!(markdown, "{}\n", text)?,
1706 MessageSegment::Thinking(text) => {
1707 writeln!(markdown, "<think>{}</think>\n", text)?
1708 }
1709 }
1710 }
1711
1712 for tool_use in self.tool_uses_for_message(message.id, cx) {
1713 writeln!(
1714 markdown,
1715 "**Use Tool: {} ({})**",
1716 tool_use.name, tool_use.id
1717 )?;
1718 writeln!(markdown, "```json")?;
1719 writeln!(
1720 markdown,
1721 "{}",
1722 serde_json::to_string_pretty(&tool_use.input)?
1723 )?;
1724 writeln!(markdown, "```")?;
1725 }
1726
1727 for tool_result in self.tool_results_for_message(message.id) {
1728 write!(markdown, "**Tool Results: {}", tool_result.tool_use_id)?;
1729 if tool_result.is_error {
1730 write!(markdown, " (Error)")?;
1731 }
1732
1733 writeln!(markdown, "**\n")?;
1734 writeln!(markdown, "{}", tool_result.content)?;
1735 }
1736 }
1737
1738 Ok(String::from_utf8_lossy(&markdown).to_string())
1739 }
1740
1741 pub fn keep_edits_in_range(
1742 &mut self,
1743 buffer: Entity<language::Buffer>,
1744 buffer_range: Range<language::Anchor>,
1745 cx: &mut Context<Self>,
1746 ) {
1747 self.action_log.update(cx, |action_log, cx| {
1748 action_log.keep_edits_in_range(buffer, buffer_range, cx)
1749 });
1750 }
1751
1752 pub fn keep_all_edits(&mut self, cx: &mut Context<Self>) {
1753 self.action_log
1754 .update(cx, |action_log, cx| action_log.keep_all_edits(cx));
1755 }
1756
1757 pub fn reject_edits_in_range(
1758 &mut self,
1759 buffer: Entity<language::Buffer>,
1760 buffer_range: Range<language::Anchor>,
1761 cx: &mut Context<Self>,
1762 ) -> Task<Result<()>> {
1763 self.action_log.update(cx, |action_log, cx| {
1764 action_log.reject_edits_in_range(buffer, buffer_range, cx)
1765 })
1766 }
1767
1768 pub fn action_log(&self) -> &Entity<ActionLog> {
1769 &self.action_log
1770 }
1771
1772 pub fn project(&self) -> &Entity<Project> {
1773 &self.project
1774 }
1775
1776 pub fn cumulative_token_usage(&self) -> TokenUsage {
1777 self.cumulative_token_usage.clone()
1778 }
1779
1780 pub fn total_token_usage(&self, cx: &App) -> TotalTokenUsage {
1781 let model_registry = LanguageModelRegistry::read_global(cx);
1782 let Some(model) = model_registry.default_model() else {
1783 return TotalTokenUsage::default();
1784 };
1785
1786 let max = model.model.max_token_count();
1787
1788 #[cfg(debug_assertions)]
1789 let warning_threshold: f32 = std::env::var("ZED_THREAD_WARNING_THRESHOLD")
1790 .unwrap_or("0.8".to_string())
1791 .parse()
1792 .unwrap();
1793 #[cfg(not(debug_assertions))]
1794 let warning_threshold: f32 = 0.8;
1795
1796 let total = self.cumulative_token_usage.total_tokens() as usize;
1797
1798 let ratio = if total >= max {
1799 TokenUsageRatio::Exceeded
1800 } else if total as f32 / max as f32 >= warning_threshold {
1801 TokenUsageRatio::Warning
1802 } else {
1803 TokenUsageRatio::Normal
1804 };
1805
1806 TotalTokenUsage { total, max, ratio }
1807 }
1808
1809 pub fn deny_tool_use(
1810 &mut self,
1811 tool_use_id: LanguageModelToolUseId,
1812 tool_name: Arc<str>,
1813 cx: &mut Context<Self>,
1814 ) {
1815 let err = Err(anyhow::anyhow!(
1816 "Permission to run tool action denied by user"
1817 ));
1818
1819 self.tool_use
1820 .insert_tool_output(tool_use_id.clone(), tool_name, err);
1821
1822 cx.emit(ThreadEvent::ToolFinished {
1823 tool_use_id,
1824 pending_tool_use: None,
1825 canceled: true,
1826 });
1827 }
1828}
1829
1830#[derive(Debug, Clone)]
1831pub enum ThreadError {
1832 PaymentRequired,
1833 MaxMonthlySpendReached,
1834 Message {
1835 header: SharedString,
1836 message: SharedString,
1837 },
1838}
1839
1840#[derive(Debug, Clone)]
1841pub enum ThreadEvent {
1842 ShowError(ThreadError),
1843 StreamedCompletion,
1844 StreamedAssistantText(MessageId, String),
1845 StreamedAssistantThinking(MessageId, String),
1846 DoneStreaming,
1847 MessageAdded(MessageId),
1848 MessageEdited(MessageId),
1849 MessageDeleted(MessageId),
1850 SummaryChanged,
1851 UsePendingTools,
1852 ToolFinished {
1853 #[allow(unused)]
1854 tool_use_id: LanguageModelToolUseId,
1855 /// The pending tool use that corresponds to this tool.
1856 pending_tool_use: Option<PendingToolUse>,
1857 /// Whether the tool was canceled by the user.
1858 canceled: bool,
1859 },
1860 CheckpointChanged,
1861 ToolConfirmationNeeded,
1862}
1863
1864impl EventEmitter<ThreadEvent> for Thread {}
1865
1866struct PendingCompletion {
1867 id: usize,
1868 _task: Task<()>,
1869}
1870
1871#[cfg(test)]
1872mod tests {
1873 use super::*;
1874 use crate::{ThreadStore, context_store::ContextStore, thread_store};
1875 use assistant_settings::AssistantSettings;
1876 use context_server::ContextServerSettings;
1877 use editor::EditorSettings;
1878 use gpui::TestAppContext;
1879 use project::{FakeFs, Project};
1880 use prompt_store::PromptBuilder;
1881 use serde_json::json;
1882 use settings::{Settings, SettingsStore};
1883 use std::sync::Arc;
1884 use theme::ThemeSettings;
1885 use util::path;
1886 use workspace::Workspace;
1887
1888 #[gpui::test]
1889 async fn test_message_with_context(cx: &mut TestAppContext) {
1890 init_test_settings(cx);
1891
1892 let project = create_test_project(
1893 cx,
1894 json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
1895 )
1896 .await;
1897
1898 let (_workspace, _thread_store, thread, context_store) =
1899 setup_test_environment(cx, project.clone()).await;
1900
1901 add_file_to_context(&project, &context_store, "test/code.rs", cx)
1902 .await
1903 .unwrap();
1904
1905 let context =
1906 context_store.update(cx, |store, _| store.context().first().cloned().unwrap());
1907
1908 // Insert user message with context
1909 let message_id = thread.update(cx, |thread, cx| {
1910 thread.insert_user_message("Please explain this code", vec![context], None, cx)
1911 });
1912
1913 // Check content and context in message object
1914 let message = thread.read_with(cx, |thread, _| thread.message(message_id).unwrap().clone());
1915
1916 // Use different path format strings based on platform for the test
1917 #[cfg(windows)]
1918 let path_part = r"test\code.rs";
1919 #[cfg(not(windows))]
1920 let path_part = "test/code.rs";
1921
1922 let expected_context = format!(
1923 r#"
1924<context>
1925The following items were attached by the user. You don't need to use other tools to read them.
1926
1927<files>
1928```rs {path_part}
1929fn main() {{
1930 println!("Hello, world!");
1931}}
1932```
1933</files>
1934</context>
1935"#
1936 );
1937
1938 assert_eq!(message.role, Role::User);
1939 assert_eq!(message.segments.len(), 1);
1940 assert_eq!(
1941 message.segments[0],
1942 MessageSegment::Text("Please explain this code".to_string())
1943 );
1944 assert_eq!(message.context, expected_context);
1945
1946 // Check message in request
1947 let request = thread.read_with(cx, |thread, cx| {
1948 thread.to_completion_request(RequestKind::Chat, cx)
1949 });
1950
1951 assert_eq!(request.messages.len(), 1);
1952 let expected_full_message = format!("{}Please explain this code", expected_context);
1953 assert_eq!(request.messages[0].string_contents(), expected_full_message);
1954 }
1955
1956 #[gpui::test]
1957 async fn test_only_include_new_contexts(cx: &mut TestAppContext) {
1958 init_test_settings(cx);
1959
1960 let project = create_test_project(
1961 cx,
1962 json!({
1963 "file1.rs": "fn function1() {}\n",
1964 "file2.rs": "fn function2() {}\n",
1965 "file3.rs": "fn function3() {}\n",
1966 }),
1967 )
1968 .await;
1969
1970 let (_, _thread_store, thread, context_store) =
1971 setup_test_environment(cx, project.clone()).await;
1972
1973 // Open files individually
1974 add_file_to_context(&project, &context_store, "test/file1.rs", cx)
1975 .await
1976 .unwrap();
1977 add_file_to_context(&project, &context_store, "test/file2.rs", cx)
1978 .await
1979 .unwrap();
1980 add_file_to_context(&project, &context_store, "test/file3.rs", cx)
1981 .await
1982 .unwrap();
1983
1984 // Get the context objects
1985 let contexts = context_store.update(cx, |store, _| store.context().clone());
1986 assert_eq!(contexts.len(), 3);
1987
1988 // First message with context 1
1989 let message1_id = thread.update(cx, |thread, cx| {
1990 thread.insert_user_message("Message 1", vec![contexts[0].clone()], None, cx)
1991 });
1992
1993 // Second message with contexts 1 and 2 (context 1 should be skipped as it's already included)
1994 let message2_id = thread.update(cx, |thread, cx| {
1995 thread.insert_user_message(
1996 "Message 2",
1997 vec![contexts[0].clone(), contexts[1].clone()],
1998 None,
1999 cx,
2000 )
2001 });
2002
2003 // Third message with all three contexts (contexts 1 and 2 should be skipped)
2004 let message3_id = thread.update(cx, |thread, cx| {
2005 thread.insert_user_message(
2006 "Message 3",
2007 vec![
2008 contexts[0].clone(),
2009 contexts[1].clone(),
2010 contexts[2].clone(),
2011 ],
2012 None,
2013 cx,
2014 )
2015 });
2016
2017 // Check what contexts are included in each message
2018 let (message1, message2, message3) = thread.read_with(cx, |thread, _| {
2019 (
2020 thread.message(message1_id).unwrap().clone(),
2021 thread.message(message2_id).unwrap().clone(),
2022 thread.message(message3_id).unwrap().clone(),
2023 )
2024 });
2025
2026 // First message should include context 1
2027 assert!(message1.context.contains("file1.rs"));
2028
2029 // Second message should include only context 2 (not 1)
2030 assert!(!message2.context.contains("file1.rs"));
2031 assert!(message2.context.contains("file2.rs"));
2032
2033 // Third message should include only context 3 (not 1 or 2)
2034 assert!(!message3.context.contains("file1.rs"));
2035 assert!(!message3.context.contains("file2.rs"));
2036 assert!(message3.context.contains("file3.rs"));
2037
2038 // Check entire request to make sure all contexts are properly included
2039 let request = thread.read_with(cx, |thread, cx| {
2040 thread.to_completion_request(RequestKind::Chat, cx)
2041 });
2042
2043 // The request should contain all 3 messages
2044 assert_eq!(request.messages.len(), 3);
2045
2046 // Check that the contexts are properly formatted in each message
2047 assert!(request.messages[0].string_contents().contains("file1.rs"));
2048 assert!(!request.messages[0].string_contents().contains("file2.rs"));
2049 assert!(!request.messages[0].string_contents().contains("file3.rs"));
2050
2051 assert!(!request.messages[1].string_contents().contains("file1.rs"));
2052 assert!(request.messages[1].string_contents().contains("file2.rs"));
2053 assert!(!request.messages[1].string_contents().contains("file3.rs"));
2054
2055 assert!(!request.messages[2].string_contents().contains("file1.rs"));
2056 assert!(!request.messages[2].string_contents().contains("file2.rs"));
2057 assert!(request.messages[2].string_contents().contains("file3.rs"));
2058 }
2059
2060 #[gpui::test]
2061 async fn test_message_without_files(cx: &mut TestAppContext) {
2062 init_test_settings(cx);
2063
2064 let project = create_test_project(
2065 cx,
2066 json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
2067 )
2068 .await;
2069
2070 let (_, _thread_store, thread, _context_store) =
2071 setup_test_environment(cx, project.clone()).await;
2072
2073 // Insert user message without any context (empty context vector)
2074 let message_id = thread.update(cx, |thread, cx| {
2075 thread.insert_user_message("What is the best way to learn Rust?", vec![], None, cx)
2076 });
2077
2078 // Check content and context in message object
2079 let message = thread.read_with(cx, |thread, _| thread.message(message_id).unwrap().clone());
2080
2081 // Context should be empty when no files are included
2082 assert_eq!(message.role, Role::User);
2083 assert_eq!(message.segments.len(), 1);
2084 assert_eq!(
2085 message.segments[0],
2086 MessageSegment::Text("What is the best way to learn Rust?".to_string())
2087 );
2088 assert_eq!(message.context, "");
2089
2090 // Check message in request
2091 let request = thread.read_with(cx, |thread, cx| {
2092 thread.to_completion_request(RequestKind::Chat, cx)
2093 });
2094
2095 assert_eq!(request.messages.len(), 1);
2096 assert_eq!(
2097 request.messages[0].string_contents(),
2098 "What is the best way to learn Rust?"
2099 );
2100
2101 // Add second message, also without context
2102 let message2_id = thread.update(cx, |thread, cx| {
2103 thread.insert_user_message("Are there any good books?", vec![], None, cx)
2104 });
2105
2106 let message2 =
2107 thread.read_with(cx, |thread, _| thread.message(message2_id).unwrap().clone());
2108 assert_eq!(message2.context, "");
2109
2110 // Check that both messages appear in the request
2111 let request = thread.read_with(cx, |thread, cx| {
2112 thread.to_completion_request(RequestKind::Chat, cx)
2113 });
2114
2115 assert_eq!(request.messages.len(), 2);
2116 assert_eq!(
2117 request.messages[0].string_contents(),
2118 "What is the best way to learn Rust?"
2119 );
2120 assert_eq!(
2121 request.messages[1].string_contents(),
2122 "Are there any good books?"
2123 );
2124 }
2125
2126 #[gpui::test]
2127 async fn test_stale_buffer_notification(cx: &mut TestAppContext) {
2128 init_test_settings(cx);
2129
2130 let project = create_test_project(
2131 cx,
2132 json!({"code.rs": "fn main() {\n println!(\"Hello, world!\");\n}"}),
2133 )
2134 .await;
2135
2136 let (_workspace, _thread_store, thread, context_store) =
2137 setup_test_environment(cx, project.clone()).await;
2138
2139 // Open buffer and add it to context
2140 let buffer = add_file_to_context(&project, &context_store, "test/code.rs", cx)
2141 .await
2142 .unwrap();
2143
2144 let context =
2145 context_store.update(cx, |store, _| store.context().first().cloned().unwrap());
2146
2147 // Insert user message with the buffer as context
2148 thread.update(cx, |thread, cx| {
2149 thread.insert_user_message("Explain this code", vec![context], None, cx)
2150 });
2151
2152 // Create a request and check that it doesn't have a stale buffer warning yet
2153 let initial_request = thread.read_with(cx, |thread, cx| {
2154 thread.to_completion_request(RequestKind::Chat, cx)
2155 });
2156
2157 // Make sure we don't have a stale file warning yet
2158 let has_stale_warning = initial_request.messages.iter().any(|msg| {
2159 msg.string_contents()
2160 .contains("These files changed since last read:")
2161 });
2162 assert!(
2163 !has_stale_warning,
2164 "Should not have stale buffer warning before buffer is modified"
2165 );
2166
2167 // Modify the buffer
2168 buffer.update(cx, |buffer, cx| {
2169 // Find a position at the end of line 1
2170 buffer.edit(
2171 [(1..1, "\n println!(\"Added a new line\");\n")],
2172 None,
2173 cx,
2174 );
2175 });
2176
2177 // Insert another user message without context
2178 thread.update(cx, |thread, cx| {
2179 thread.insert_user_message("What does the code do now?", vec![], None, cx)
2180 });
2181
2182 // Create a new request and check for the stale buffer warning
2183 let new_request = thread.read_with(cx, |thread, cx| {
2184 thread.to_completion_request(RequestKind::Chat, cx)
2185 });
2186
2187 // We should have a stale file warning as the last message
2188 let last_message = new_request
2189 .messages
2190 .last()
2191 .expect("Request should have messages");
2192
2193 // The last message should be the stale buffer notification
2194 assert_eq!(last_message.role, Role::User);
2195
2196 // Check the exact content of the message
2197 let expected_content = "These files changed since last read:\n- code.rs\n";
2198 assert_eq!(
2199 last_message.string_contents(),
2200 expected_content,
2201 "Last message should be exactly the stale buffer notification"
2202 );
2203 }
2204
2205 fn init_test_settings(cx: &mut TestAppContext) {
2206 cx.update(|cx| {
2207 let settings_store = SettingsStore::test(cx);
2208 cx.set_global(settings_store);
2209 language::init(cx);
2210 Project::init_settings(cx);
2211 AssistantSettings::register(cx);
2212 thread_store::init(cx);
2213 workspace::init_settings(cx);
2214 ThemeSettings::register(cx);
2215 ContextServerSettings::register(cx);
2216 EditorSettings::register(cx);
2217 });
2218 }
2219
2220 // Helper to create a test project with test files
2221 async fn create_test_project(
2222 cx: &mut TestAppContext,
2223 files: serde_json::Value,
2224 ) -> Entity<Project> {
2225 let fs = FakeFs::new(cx.executor());
2226 fs.insert_tree(path!("/test"), files).await;
2227 Project::test(fs, [path!("/test").as_ref()], cx).await
2228 }
2229
2230 async fn setup_test_environment(
2231 cx: &mut TestAppContext,
2232 project: Entity<Project>,
2233 ) -> (
2234 Entity<Workspace>,
2235 Entity<ThreadStore>,
2236 Entity<Thread>,
2237 Entity<ContextStore>,
2238 ) {
2239 let (workspace, cx) =
2240 cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
2241
2242 let thread_store = cx.update(|_, cx| {
2243 ThreadStore::new(
2244 project.clone(),
2245 Arc::default(),
2246 Arc::new(PromptBuilder::new(None).unwrap()),
2247 cx,
2248 )
2249 .unwrap()
2250 });
2251
2252 let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
2253 let context_store = cx.new(|_cx| ContextStore::new(workspace.downgrade(), None));
2254
2255 (workspace, thread_store, thread, context_store)
2256 }
2257
2258 async fn add_file_to_context(
2259 project: &Entity<Project>,
2260 context_store: &Entity<ContextStore>,
2261 path: &str,
2262 cx: &mut TestAppContext,
2263 ) -> Result<Entity<language::Buffer>> {
2264 let buffer_path = project
2265 .read_with(cx, |project, cx| project.find_project_path(path, cx))
2266 .unwrap();
2267
2268 let buffer = project
2269 .update(cx, |project, cx| project.open_buffer(buffer_path, cx))
2270 .await
2271 .unwrap();
2272
2273 context_store
2274 .update(cx, |store, cx| {
2275 store.add_file_from_buffer(buffer.clone(), cx)
2276 })
2277 .await?;
2278
2279 Ok(buffer)
2280 }
2281}