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