1use std::fmt::Write as _;
2use std::io::Write;
3use std::sync::Arc;
4
5use anyhow::{Context as _, Result};
6use assistant_settings::AssistantSettings;
7use assistant_tool::{ActionLog, Tool, ToolWorkingSet};
8use chrono::{DateTime, Utc};
9use collections::{BTreeMap, HashMap, HashSet};
10use fs::Fs;
11use futures::future::Shared;
12use futures::{FutureExt, StreamExt as _};
13use git;
14use gpui::{App, AppContext, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};
15use language_model::{
16 LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest,
17 LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,
18 LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError,
19 Role, StopReason, TokenUsage,
20};
21use project::git_store::{GitStore, GitStoreCheckpoint};
22use project::{Project, Worktree};
23use prompt_store::{
24 AssistantSystemPromptContext, PromptBuilder, RulesFile, WorktreeInfoForSystemPrompt,
25};
26use scripting_tool::{ScriptingSession, ScriptingTool};
27use serde::{Deserialize, Serialize};
28use settings::Settings;
29use util::{maybe, post_inc, ResultExt as _, TryFutureExt as _};
30use uuid::Uuid;
31
32use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};
33use crate::thread_store::{
34 SerializedMessage, SerializedMessageSegment, SerializedThread, SerializedToolResult,
35 SerializedToolUse,
36};
37use crate::tool_use::{PendingToolUse, PendingToolUseStatus, ToolType, ToolUse, ToolUseState};
38
39#[derive(Debug, Clone, Copy)]
40pub enum RequestKind {
41 Chat,
42 /// Used when summarizing a thread.
43 Summarize,
44}
45
46#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
47pub struct ThreadId(Arc<str>);
48
49impl ThreadId {
50 pub fn new() -> Self {
51 Self(Uuid::new_v4().to_string().into())
52 }
53}
54
55impl std::fmt::Display for ThreadId {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 write!(f, "{}", self.0)
58 }
59}
60
61#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
62pub struct MessageId(pub(crate) usize);
63
64impl MessageId {
65 fn post_inc(&mut self) -> Self {
66 Self(post_inc(&mut self.0))
67 }
68}
69
70/// A message in a [`Thread`].
71#[derive(Debug, Clone)]
72pub struct Message {
73 pub id: MessageId,
74 pub role: Role,
75 pub segments: Vec<MessageSegment>,
76}
77
78impl Message {
79 pub fn push_thinking(&mut self, text: &str) {
80 if let Some(MessageSegment::Thinking(segment)) = self.segments.last_mut() {
81 segment.push_str(text);
82 } else {
83 self.segments
84 .push(MessageSegment::Thinking(text.to_string()));
85 }
86 }
87
88 pub fn push_text(&mut self, text: &str) {
89 if let Some(MessageSegment::Text(segment)) = self.segments.last_mut() {
90 segment.push_str(text);
91 } else {
92 self.segments.push(MessageSegment::Text(text.to_string()));
93 }
94 }
95
96 pub fn to_string(&self) -> String {
97 let mut result = String::new();
98 for segment in &self.segments {
99 match segment {
100 MessageSegment::Text(text) => result.push_str(text),
101 MessageSegment::Thinking(text) => {
102 result.push_str("<think>");
103 result.push_str(text);
104 result.push_str("</think>");
105 }
106 }
107 }
108 result
109 }
110}
111
112#[derive(Debug, Clone)]
113pub enum MessageSegment {
114 Text(String),
115 Thinking(String),
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct ProjectSnapshot {
120 pub worktree_snapshots: Vec<WorktreeSnapshot>,
121 pub unsaved_buffer_paths: Vec<String>,
122 pub timestamp: DateTime<Utc>,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct WorktreeSnapshot {
127 pub worktree_path: String,
128 pub git_state: Option<GitState>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct GitState {
133 pub remote_url: Option<String>,
134 pub head_sha: Option<String>,
135 pub current_branch: Option<String>,
136 pub diff: Option<String>,
137}
138
139#[derive(Clone)]
140pub struct ThreadCheckpoint {
141 message_id: MessageId,
142 git_checkpoint: GitStoreCheckpoint,
143}
144
145#[derive(Copy, Clone, Debug)]
146pub enum ThreadFeedback {
147 Positive,
148 Negative,
149}
150
151pub enum LastRestoreCheckpoint {
152 Pending {
153 message_id: MessageId,
154 },
155 Error {
156 message_id: MessageId,
157 error: String,
158 },
159}
160
161impl LastRestoreCheckpoint {
162 pub fn message_id(&self) -> MessageId {
163 match self {
164 LastRestoreCheckpoint::Pending { message_id } => *message_id,
165 LastRestoreCheckpoint::Error { message_id, .. } => *message_id,
166 }
167 }
168}
169
170/// A thread of conversation with the LLM.
171pub struct Thread {
172 id: ThreadId,
173 updated_at: DateTime<Utc>,
174 summary: Option<SharedString>,
175 pending_summary: Task<Option<()>>,
176 messages: Vec<Message>,
177 next_message_id: MessageId,
178 context: BTreeMap<ContextId, ContextSnapshot>,
179 context_by_message: HashMap<MessageId, Vec<ContextId>>,
180 system_prompt_context: Option<AssistantSystemPromptContext>,
181 checkpoints_by_message: HashMap<MessageId, ThreadCheckpoint>,
182 completion_count: usize,
183 pending_completions: Vec<PendingCompletion>,
184 project: Entity<Project>,
185 prompt_builder: Arc<PromptBuilder>,
186 tools: Arc<ToolWorkingSet>,
187 tool_use: ToolUseState,
188 action_log: Entity<ActionLog>,
189 last_restore_checkpoint: Option<LastRestoreCheckpoint>,
190 pending_checkpoint: Option<ThreadCheckpoint>,
191 scripting_session: Entity<ScriptingSession>,
192 scripting_tool_use: ToolUseState,
193 initial_project_snapshot: Shared<Task<Option<Arc<ProjectSnapshot>>>>,
194 cumulative_token_usage: TokenUsage,
195 feedback: Option<ThreadFeedback>,
196}
197
198impl Thread {
199 pub fn new(
200 project: Entity<Project>,
201 tools: Arc<ToolWorkingSet>,
202 prompt_builder: Arc<PromptBuilder>,
203 cx: &mut Context<Self>,
204 ) -> Self {
205 Self {
206 id: ThreadId::new(),
207 updated_at: Utc::now(),
208 summary: None,
209 pending_summary: Task::ready(None),
210 messages: Vec::new(),
211 next_message_id: MessageId(0),
212 context: BTreeMap::default(),
213 context_by_message: HashMap::default(),
214 system_prompt_context: None,
215 checkpoints_by_message: HashMap::default(),
216 completion_count: 0,
217 pending_completions: Vec::new(),
218 project: project.clone(),
219 prompt_builder,
220 tools: tools.clone(),
221 last_restore_checkpoint: None,
222 pending_checkpoint: None,
223 tool_use: ToolUseState::new(tools.clone()),
224 scripting_session: cx.new(|cx| ScriptingSession::new(project.clone(), cx)),
225 scripting_tool_use: ToolUseState::new(tools),
226 action_log: cx.new(|_| ActionLog::new()),
227 initial_project_snapshot: {
228 let project_snapshot = Self::project_snapshot(project, cx);
229 cx.foreground_executor()
230 .spawn(async move { Some(project_snapshot.await) })
231 .shared()
232 },
233 cumulative_token_usage: TokenUsage::default(),
234 feedback: None,
235 }
236 }
237
238 pub fn deserialize(
239 id: ThreadId,
240 serialized: SerializedThread,
241 project: Entity<Project>,
242 tools: Arc<ToolWorkingSet>,
243 prompt_builder: Arc<PromptBuilder>,
244 cx: &mut Context<Self>,
245 ) -> Self {
246 let next_message_id = MessageId(
247 serialized
248 .messages
249 .last()
250 .map(|message| message.id.0 + 1)
251 .unwrap_or(0),
252 );
253 let tool_use =
254 ToolUseState::from_serialized_messages(tools.clone(), &serialized.messages, |name| {
255 name != ScriptingTool::NAME
256 });
257 let scripting_tool_use =
258 ToolUseState::from_serialized_messages(tools.clone(), &serialized.messages, |name| {
259 name == ScriptingTool::NAME
260 });
261 let scripting_session = cx.new(|cx| ScriptingSession::new(project.clone(), cx));
262
263 Self {
264 id,
265 updated_at: serialized.updated_at,
266 summary: Some(serialized.summary),
267 pending_summary: Task::ready(None),
268 messages: serialized
269 .messages
270 .into_iter()
271 .map(|message| Message {
272 id: message.id,
273 role: message.role,
274 segments: message
275 .segments
276 .into_iter()
277 .map(|segment| match segment {
278 SerializedMessageSegment::Text { text } => MessageSegment::Text(text),
279 SerializedMessageSegment::Thinking { text } => {
280 MessageSegment::Thinking(text)
281 }
282 })
283 .collect(),
284 })
285 .collect(),
286 next_message_id,
287 context: BTreeMap::default(),
288 context_by_message: HashMap::default(),
289 system_prompt_context: None,
290 checkpoints_by_message: HashMap::default(),
291 completion_count: 0,
292 pending_completions: Vec::new(),
293 last_restore_checkpoint: None,
294 pending_checkpoint: None,
295 project,
296 prompt_builder,
297 tools,
298 tool_use,
299 action_log: cx.new(|_| ActionLog::new()),
300 scripting_session,
301 scripting_tool_use,
302 initial_project_snapshot: Task::ready(serialized.initial_project_snapshot).shared(),
303 // TODO: persist token usage?
304 cumulative_token_usage: TokenUsage::default(),
305 feedback: None,
306 }
307 }
308
309 pub fn id(&self) -> &ThreadId {
310 &self.id
311 }
312
313 pub fn is_empty(&self) -> bool {
314 self.messages.is_empty()
315 }
316
317 pub fn updated_at(&self) -> DateTime<Utc> {
318 self.updated_at
319 }
320
321 pub fn touch_updated_at(&mut self) {
322 self.updated_at = Utc::now();
323 }
324
325 pub fn summary(&self) -> Option<SharedString> {
326 self.summary.clone()
327 }
328
329 pub fn summary_or_default(&self) -> SharedString {
330 const DEFAULT: SharedString = SharedString::new_static("New Thread");
331 self.summary.clone().unwrap_or(DEFAULT)
332 }
333
334 pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut Context<Self>) {
335 self.summary = Some(summary.into());
336 cx.emit(ThreadEvent::SummaryChanged);
337 }
338
339 pub fn message(&self, id: MessageId) -> Option<&Message> {
340 self.messages.iter().find(|message| message.id == id)
341 }
342
343 pub fn messages(&self) -> impl Iterator<Item = &Message> {
344 self.messages.iter()
345 }
346
347 pub fn is_generating(&self) -> bool {
348 !self.pending_completions.is_empty() || !self.all_tools_finished()
349 }
350
351 pub fn tools(&self) -> &Arc<ToolWorkingSet> {
352 &self.tools
353 }
354
355 pub fn pending_tool(&self, id: &LanguageModelToolUseId) -> Option<&PendingToolUse> {
356 self.tool_use
357 .pending_tool_uses()
358 .into_iter()
359 .find(|tool_use| &tool_use.id == id)
360 .or_else(|| {
361 self.scripting_tool_use
362 .pending_tool_uses()
363 .into_iter()
364 .find(|tool_use| &tool_use.id == id)
365 })
366 }
367
368 pub fn tools_needing_confirmation(&self) -> impl Iterator<Item = (ToolType, &PendingToolUse)> {
369 self.tool_use
370 .pending_tool_uses()
371 .into_iter()
372 .filter_map(|tool_use| {
373 if let PendingToolUseStatus::NeedsConfirmation(confirmation) = &tool_use.status {
374 Some((confirmation.tool_type.clone(), tool_use))
375 } else {
376 None
377 }
378 })
379 .chain(
380 self.scripting_tool_use
381 .pending_tool_uses()
382 .into_iter()
383 .filter_map(|tool_use| {
384 if tool_use.status.needs_confirmation() {
385 Some((ToolType::ScriptingTool, tool_use))
386 } else {
387 None
388 }
389 }),
390 )
391 }
392
393 pub fn checkpoint_for_message(&self, id: MessageId) -> Option<ThreadCheckpoint> {
394 self.checkpoints_by_message.get(&id).cloned()
395 }
396
397 pub fn restore_checkpoint(
398 &mut self,
399 checkpoint: ThreadCheckpoint,
400 cx: &mut Context<Self>,
401 ) -> Task<Result<()>> {
402 self.last_restore_checkpoint = Some(LastRestoreCheckpoint::Pending {
403 message_id: checkpoint.message_id,
404 });
405 cx.emit(ThreadEvent::CheckpointChanged);
406 cx.notify();
407
408 let project = self.project.read(cx);
409 let restore = project
410 .git_store()
411 .read(cx)
412 .restore_checkpoint(checkpoint.git_checkpoint.clone(), cx);
413 cx.spawn(async move |this, cx| {
414 let result = restore.await;
415 this.update(cx, |this, cx| {
416 if let Err(err) = result.as_ref() {
417 this.last_restore_checkpoint = Some(LastRestoreCheckpoint::Error {
418 message_id: checkpoint.message_id,
419 error: err.to_string(),
420 });
421 } else {
422 this.truncate(checkpoint.message_id, cx);
423 this.last_restore_checkpoint = None;
424 }
425 this.pending_checkpoint = None;
426 cx.emit(ThreadEvent::CheckpointChanged);
427 cx.notify();
428 })?;
429 result
430 })
431 }
432
433 fn finalize_pending_checkpoint(&mut self, cx: &mut Context<Self>) {
434 let pending_checkpoint = if self.is_generating() {
435 return;
436 } else if let Some(checkpoint) = self.pending_checkpoint.take() {
437 checkpoint
438 } else {
439 return;
440 };
441
442 let git_store = self.project.read(cx).git_store().clone();
443 let final_checkpoint = git_store.read(cx).checkpoint(cx);
444 cx.spawn(async move |this, cx| match final_checkpoint.await {
445 Ok(final_checkpoint) => {
446 let equal = git_store
447 .read_with(cx, |store, cx| {
448 store.compare_checkpoints(
449 pending_checkpoint.git_checkpoint.clone(),
450 final_checkpoint.clone(),
451 cx,
452 )
453 })?
454 .await
455 .unwrap_or(false);
456
457 if equal {
458 git_store
459 .read_with(cx, |store, cx| {
460 store.delete_checkpoint(pending_checkpoint.git_checkpoint, cx)
461 })?
462 .detach();
463 } else {
464 this.update(cx, |this, cx| {
465 this.insert_checkpoint(pending_checkpoint, cx)
466 })?;
467 }
468
469 git_store
470 .read_with(cx, |store, cx| {
471 store.delete_checkpoint(final_checkpoint, cx)
472 })?
473 .detach();
474
475 Ok(())
476 }
477 Err(_) => this.update(cx, |this, cx| {
478 this.insert_checkpoint(pending_checkpoint, cx)
479 }),
480 })
481 .detach();
482 }
483
484 fn insert_checkpoint(&mut self, checkpoint: ThreadCheckpoint, cx: &mut Context<Self>) {
485 self.checkpoints_by_message
486 .insert(checkpoint.message_id, checkpoint);
487 cx.emit(ThreadEvent::CheckpointChanged);
488 cx.notify();
489 }
490
491 pub fn last_restore_checkpoint(&self) -> Option<&LastRestoreCheckpoint> {
492 self.last_restore_checkpoint.as_ref()
493 }
494
495 pub fn truncate(&mut self, message_id: MessageId, cx: &mut Context<Self>) {
496 let Some(message_ix) = self
497 .messages
498 .iter()
499 .rposition(|message| message.id == message_id)
500 else {
501 return;
502 };
503 for deleted_message in self.messages.drain(message_ix..) {
504 self.context_by_message.remove(&deleted_message.id);
505 self.checkpoints_by_message.remove(&deleted_message.id);
506 }
507 cx.notify();
508 }
509
510 pub fn context_for_message(&self, id: MessageId) -> Option<Vec<ContextSnapshot>> {
511 let context = self.context_by_message.get(&id)?;
512 Some(
513 context
514 .into_iter()
515 .filter_map(|context_id| self.context.get(&context_id))
516 .cloned()
517 .collect::<Vec<_>>(),
518 )
519 }
520
521 /// Returns whether all of the tool uses have finished running.
522 pub fn all_tools_finished(&self) -> bool {
523 let mut all_pending_tool_uses = self
524 .tool_use
525 .pending_tool_uses()
526 .into_iter()
527 .chain(self.scripting_tool_use.pending_tool_uses());
528
529 // If the only pending tool uses left are the ones with errors, then
530 // that means that we've finished running all of the pending tools.
531 all_pending_tool_uses.all(|tool_use| tool_use.status.is_error())
532 }
533
534 pub fn tool_uses_for_message(&self, id: MessageId, cx: &App) -> Vec<ToolUse> {
535 self.tool_use.tool_uses_for_message(id, cx)
536 }
537
538 pub fn scripting_tool_uses_for_message(&self, id: MessageId, cx: &App) -> Vec<ToolUse> {
539 self.scripting_tool_use.tool_uses_for_message(id, cx)
540 }
541
542 pub fn tool_results_for_message(&self, id: MessageId) -> Vec<&LanguageModelToolResult> {
543 self.tool_use.tool_results_for_message(id)
544 }
545
546 pub fn tool_result(&self, id: &LanguageModelToolUseId) -> Option<&LanguageModelToolResult> {
547 self.tool_use.tool_result(id)
548 }
549
550 pub fn scripting_tool_results_for_message(
551 &self,
552 id: MessageId,
553 ) -> Vec<&LanguageModelToolResult> {
554 self.scripting_tool_use.tool_results_for_message(id)
555 }
556
557 pub fn message_has_tool_results(&self, message_id: MessageId) -> bool {
558 self.tool_use.message_has_tool_results(message_id)
559 }
560
561 pub fn message_has_scripting_tool_results(&self, message_id: MessageId) -> bool {
562 self.scripting_tool_use.message_has_tool_results(message_id)
563 }
564
565 pub fn insert_user_message(
566 &mut self,
567 text: impl Into<String>,
568 context: Vec<ContextSnapshot>,
569 git_checkpoint: Option<GitStoreCheckpoint>,
570 cx: &mut Context<Self>,
571 ) -> MessageId {
572 let message_id =
573 self.insert_message(Role::User, vec![MessageSegment::Text(text.into())], cx);
574 let context_ids = context.iter().map(|context| context.id).collect::<Vec<_>>();
575 self.context
576 .extend(context.into_iter().map(|context| (context.id, context)));
577 self.context_by_message.insert(message_id, context_ids);
578 if let Some(git_checkpoint) = git_checkpoint {
579 self.pending_checkpoint = Some(ThreadCheckpoint {
580 message_id,
581 git_checkpoint,
582 });
583 }
584 message_id
585 }
586
587 pub fn insert_message(
588 &mut self,
589 role: Role,
590 segments: Vec<MessageSegment>,
591 cx: &mut Context<Self>,
592 ) -> MessageId {
593 let id = self.next_message_id.post_inc();
594 self.messages.push(Message { id, role, segments });
595 self.touch_updated_at();
596 cx.emit(ThreadEvent::MessageAdded(id));
597 id
598 }
599
600 pub fn edit_message(
601 &mut self,
602 id: MessageId,
603 new_role: Role,
604 new_segments: Vec<MessageSegment>,
605 cx: &mut Context<Self>,
606 ) -> bool {
607 let Some(message) = self.messages.iter_mut().find(|message| message.id == id) else {
608 return false;
609 };
610 message.role = new_role;
611 message.segments = new_segments;
612 self.touch_updated_at();
613 cx.emit(ThreadEvent::MessageEdited(id));
614 true
615 }
616
617 pub fn delete_message(&mut self, id: MessageId, cx: &mut Context<Self>) -> bool {
618 let Some(index) = self.messages.iter().position(|message| message.id == id) else {
619 return false;
620 };
621 self.messages.remove(index);
622 self.context_by_message.remove(&id);
623 self.touch_updated_at();
624 cx.emit(ThreadEvent::MessageDeleted(id));
625 true
626 }
627
628 /// Returns the representation of this [`Thread`] in a textual form.
629 ///
630 /// This is the representation we use when attaching a thread as context to another thread.
631 pub fn text(&self) -> String {
632 let mut text = String::new();
633
634 for message in &self.messages {
635 text.push_str(match message.role {
636 language_model::Role::User => "User:",
637 language_model::Role::Assistant => "Assistant:",
638 language_model::Role::System => "System:",
639 });
640 text.push('\n');
641
642 for segment in &message.segments {
643 match segment {
644 MessageSegment::Text(content) => text.push_str(content),
645 MessageSegment::Thinking(content) => {
646 text.push_str(&format!("<think>{}</think>", content))
647 }
648 }
649 }
650 text.push('\n');
651 }
652
653 text
654 }
655
656 /// Serializes this thread into a format for storage or telemetry.
657 pub fn serialize(&self, cx: &mut Context<Self>) -> Task<Result<SerializedThread>> {
658 let initial_project_snapshot = self.initial_project_snapshot.clone();
659 cx.spawn(async move |this, cx| {
660 let initial_project_snapshot = initial_project_snapshot.await;
661 this.read_with(cx, |this, cx| SerializedThread {
662 version: SerializedThread::VERSION.to_string(),
663 summary: this.summary_or_default(),
664 updated_at: this.updated_at(),
665 messages: this
666 .messages()
667 .map(|message| SerializedMessage {
668 id: message.id,
669 role: message.role,
670 segments: message
671 .segments
672 .iter()
673 .map(|segment| match segment {
674 MessageSegment::Text(text) => {
675 SerializedMessageSegment::Text { text: text.clone() }
676 }
677 MessageSegment::Thinking(text) => {
678 SerializedMessageSegment::Thinking { text: text.clone() }
679 }
680 })
681 .collect(),
682 tool_uses: this
683 .tool_uses_for_message(message.id, cx)
684 .into_iter()
685 .chain(this.scripting_tool_uses_for_message(message.id, cx))
686 .map(|tool_use| SerializedToolUse {
687 id: tool_use.id,
688 name: tool_use.name,
689 input: tool_use.input,
690 })
691 .collect(),
692 tool_results: this
693 .tool_results_for_message(message.id)
694 .into_iter()
695 .chain(this.scripting_tool_results_for_message(message.id))
696 .map(|tool_result| SerializedToolResult {
697 tool_use_id: tool_result.tool_use_id.clone(),
698 is_error: tool_result.is_error,
699 content: tool_result.content.clone(),
700 })
701 .collect(),
702 })
703 .collect(),
704 initial_project_snapshot,
705 })
706 })
707 }
708
709 pub fn set_system_prompt_context(&mut self, context: AssistantSystemPromptContext) {
710 self.system_prompt_context = Some(context);
711 }
712
713 pub fn system_prompt_context(&self) -> &Option<AssistantSystemPromptContext> {
714 &self.system_prompt_context
715 }
716
717 pub fn load_system_prompt_context(
718 &self,
719 cx: &App,
720 ) -> Task<(AssistantSystemPromptContext, Option<ThreadError>)> {
721 let project = self.project.read(cx);
722 let tasks = project
723 .visible_worktrees(cx)
724 .map(|worktree| {
725 Self::load_worktree_info_for_system_prompt(
726 project.fs().clone(),
727 worktree.read(cx),
728 cx,
729 )
730 })
731 .collect::<Vec<_>>();
732
733 cx.spawn(async |_cx| {
734 let results = futures::future::join_all(tasks).await;
735 let mut first_err = None;
736 let worktrees = results
737 .into_iter()
738 .map(|(worktree, err)| {
739 if first_err.is_none() && err.is_some() {
740 first_err = err;
741 }
742 worktree
743 })
744 .collect::<Vec<_>>();
745 (AssistantSystemPromptContext::new(worktrees), first_err)
746 })
747 }
748
749 fn load_worktree_info_for_system_prompt(
750 fs: Arc<dyn Fs>,
751 worktree: &Worktree,
752 cx: &App,
753 ) -> Task<(WorktreeInfoForSystemPrompt, Option<ThreadError>)> {
754 let root_name = worktree.root_name().into();
755 let abs_path = worktree.abs_path();
756
757 // Note that Cline supports `.clinerules` being a directory, but that is not currently
758 // supported. This doesn't seem to occur often in GitHub repositories.
759 const RULES_FILE_NAMES: [&'static str; 5] = [
760 ".rules",
761 ".cursorrules",
762 ".windsurfrules",
763 ".clinerules",
764 "CLAUDE.md",
765 ];
766 let selected_rules_file = RULES_FILE_NAMES
767 .into_iter()
768 .filter_map(|name| {
769 worktree
770 .entry_for_path(name)
771 .filter(|entry| entry.is_file())
772 .map(|entry| (entry.path.clone(), worktree.absolutize(&entry.path)))
773 })
774 .next();
775
776 if let Some((rel_rules_path, abs_rules_path)) = selected_rules_file {
777 cx.spawn(async move |_| {
778 let rules_file_result = maybe!(async move {
779 let abs_rules_path = abs_rules_path?;
780 let text = fs.load(&abs_rules_path).await.with_context(|| {
781 format!("Failed to load assistant rules file {:?}", abs_rules_path)
782 })?;
783 anyhow::Ok(RulesFile {
784 rel_path: rel_rules_path,
785 abs_path: abs_rules_path.into(),
786 text: text.trim().to_string(),
787 })
788 })
789 .await;
790 let (rules_file, rules_file_error) = match rules_file_result {
791 Ok(rules_file) => (Some(rules_file), None),
792 Err(err) => (
793 None,
794 Some(ThreadError::Message {
795 header: "Error loading rules file".into(),
796 message: format!("{err}").into(),
797 }),
798 ),
799 };
800 let worktree_info = WorktreeInfoForSystemPrompt {
801 root_name,
802 abs_path,
803 rules_file,
804 };
805 (worktree_info, rules_file_error)
806 })
807 } else {
808 Task::ready((
809 WorktreeInfoForSystemPrompt {
810 root_name,
811 abs_path,
812 rules_file: None,
813 },
814 None,
815 ))
816 }
817 }
818
819 pub fn send_to_model(
820 &mut self,
821 model: Arc<dyn LanguageModel>,
822 request_kind: RequestKind,
823 cx: &mut Context<Self>,
824 ) {
825 let mut request = self.to_completion_request(request_kind, cx);
826 request.tools = {
827 let mut tools = Vec::new();
828
829 if self.tools.is_scripting_tool_enabled() {
830 tools.push(LanguageModelRequestTool {
831 name: ScriptingTool::NAME.into(),
832 description: ScriptingTool::DESCRIPTION.into(),
833 input_schema: ScriptingTool::input_schema(),
834 });
835 }
836
837 tools.extend(self.tools().enabled_tools(cx).into_iter().map(|tool| {
838 LanguageModelRequestTool {
839 name: tool.name(),
840 description: tool.description(),
841 input_schema: tool.input_schema(),
842 }
843 }));
844
845 tools
846 };
847
848 self.stream_completion(request, model, cx);
849 }
850
851 pub fn to_completion_request(
852 &self,
853 request_kind: RequestKind,
854 cx: &App,
855 ) -> LanguageModelRequest {
856 let mut request = LanguageModelRequest {
857 messages: vec![],
858 tools: Vec::new(),
859 stop: Vec::new(),
860 temperature: None,
861 };
862
863 if let Some(system_prompt_context) = self.system_prompt_context.as_ref() {
864 if let Some(system_prompt) = self
865 .prompt_builder
866 .generate_assistant_system_prompt(system_prompt_context)
867 .context("failed to generate assistant system prompt")
868 .log_err()
869 {
870 request.messages.push(LanguageModelRequestMessage {
871 role: Role::System,
872 content: vec![MessageContent::Text(system_prompt)],
873 cache: true,
874 });
875 }
876 } else {
877 log::error!("system_prompt_context not set.")
878 }
879
880 let mut referenced_context_ids = HashSet::default();
881
882 for message in &self.messages {
883 if let Some(context_ids) = self.context_by_message.get(&message.id) {
884 referenced_context_ids.extend(context_ids);
885 }
886
887 let mut request_message = LanguageModelRequestMessage {
888 role: message.role,
889 content: Vec::new(),
890 cache: false,
891 };
892
893 match request_kind {
894 RequestKind::Chat => {
895 self.tool_use
896 .attach_tool_results(message.id, &mut request_message);
897 self.scripting_tool_use
898 .attach_tool_results(message.id, &mut request_message);
899 }
900 RequestKind::Summarize => {
901 // We don't care about tool use during summarization.
902 }
903 }
904
905 if !message.segments.is_empty() {
906 request_message
907 .content
908 .push(MessageContent::Text(message.to_string()));
909 }
910
911 match request_kind {
912 RequestKind::Chat => {
913 self.tool_use
914 .attach_tool_uses(message.id, &mut request_message);
915 self.scripting_tool_use
916 .attach_tool_uses(message.id, &mut request_message);
917 }
918 RequestKind::Summarize => {
919 // We don't care about tool use during summarization.
920 }
921 };
922
923 request.messages.push(request_message);
924 }
925
926 if !referenced_context_ids.is_empty() {
927 let mut context_message = LanguageModelRequestMessage {
928 role: Role::User,
929 content: Vec::new(),
930 cache: false,
931 };
932
933 let referenced_context = referenced_context_ids
934 .into_iter()
935 .filter_map(|context_id| self.context.get(context_id))
936 .cloned();
937 attach_context_to_message(&mut context_message, referenced_context);
938
939 request.messages.push(context_message);
940 }
941
942 self.attach_stale_files(&mut request.messages, cx);
943
944 request
945 }
946
947 fn attach_stale_files(&self, messages: &mut Vec<LanguageModelRequestMessage>, cx: &App) {
948 const STALE_FILES_HEADER: &str = "These files changed since last read:";
949
950 let mut stale_message = String::new();
951
952 for stale_file in self.action_log.read(cx).stale_buffers(cx) {
953 let Some(file) = stale_file.read(cx).file() else {
954 continue;
955 };
956
957 if stale_message.is_empty() {
958 write!(&mut stale_message, "{}", STALE_FILES_HEADER).ok();
959 }
960
961 writeln!(&mut stale_message, "- {}", file.path().display()).ok();
962 }
963
964 if !stale_message.is_empty() {
965 let context_message = LanguageModelRequestMessage {
966 role: Role::User,
967 content: vec![stale_message.into()],
968 cache: false,
969 };
970
971 messages.push(context_message);
972 }
973 }
974
975 pub fn stream_completion(
976 &mut self,
977 request: LanguageModelRequest,
978 model: Arc<dyn LanguageModel>,
979 cx: &mut Context<Self>,
980 ) {
981 let pending_completion_id = post_inc(&mut self.completion_count);
982
983 let task = cx.spawn(async move |thread, cx| {
984 let stream = model.stream_completion(request, &cx);
985 let initial_token_usage =
986 thread.read_with(cx, |thread, _cx| thread.cumulative_token_usage.clone());
987 let stream_completion = async {
988 let mut events = stream.await?;
989 let mut stop_reason = StopReason::EndTurn;
990 let mut current_token_usage = TokenUsage::default();
991
992 while let Some(event) = events.next().await {
993 let event = event?;
994
995 thread.update(cx, |thread, cx| {
996 match event {
997 LanguageModelCompletionEvent::StartMessage { .. } => {
998 thread.insert_message(
999 Role::Assistant,
1000 vec![MessageSegment::Text(String::new())],
1001 cx,
1002 );
1003 }
1004 LanguageModelCompletionEvent::Stop(reason) => {
1005 stop_reason = reason;
1006 }
1007 LanguageModelCompletionEvent::UsageUpdate(token_usage) => {
1008 thread.cumulative_token_usage =
1009 thread.cumulative_token_usage.clone() + token_usage.clone()
1010 - current_token_usage.clone();
1011 current_token_usage = token_usage;
1012 }
1013 LanguageModelCompletionEvent::Text(chunk) => {
1014 if let Some(last_message) = thread.messages.last_mut() {
1015 if last_message.role == Role::Assistant {
1016 last_message.push_text(&chunk);
1017 cx.emit(ThreadEvent::StreamedAssistantText(
1018 last_message.id,
1019 chunk,
1020 ));
1021 } else {
1022 // If we won't have an Assistant message yet, assume this chunk marks the beginning
1023 // of a new Assistant response.
1024 //
1025 // Importantly: We do *not* want to emit a `StreamedAssistantText` event here, as it
1026 // will result in duplicating the text of the chunk in the rendered Markdown.
1027 thread.insert_message(
1028 Role::Assistant,
1029 vec![MessageSegment::Text(chunk.to_string())],
1030 cx,
1031 );
1032 };
1033 }
1034 }
1035 LanguageModelCompletionEvent::Thinking(chunk) => {
1036 if let Some(last_message) = thread.messages.last_mut() {
1037 if last_message.role == Role::Assistant {
1038 last_message.push_thinking(&chunk);
1039 cx.emit(ThreadEvent::StreamedAssistantThinking(
1040 last_message.id,
1041 chunk,
1042 ));
1043 } else {
1044 // If we won't have an Assistant message yet, assume this chunk marks the beginning
1045 // of a new Assistant response.
1046 //
1047 // Importantly: We do *not* want to emit a `StreamedAssistantText` event here, as it
1048 // will result in duplicating the text of the chunk in the rendered Markdown.
1049 thread.insert_message(
1050 Role::Assistant,
1051 vec![MessageSegment::Thinking(chunk.to_string())],
1052 cx,
1053 );
1054 };
1055 }
1056 }
1057 LanguageModelCompletionEvent::ToolUse(tool_use) => {
1058 if let Some(last_assistant_message) = thread
1059 .messages
1060 .iter()
1061 .rfind(|message| message.role == Role::Assistant)
1062 {
1063 if tool_use.name.as_ref() == ScriptingTool::NAME {
1064 thread.scripting_tool_use.request_tool_use(
1065 last_assistant_message.id,
1066 tool_use,
1067 cx,
1068 );
1069 } else {
1070 thread.tool_use.request_tool_use(
1071 last_assistant_message.id,
1072 tool_use,
1073 cx,
1074 );
1075 }
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 cx.emit(ThreadEvent::UsePendingTools);
1110 }
1111 StopReason::EndTurn => {}
1112 StopReason::MaxTokens => {}
1113 },
1114 Err(error) => {
1115 if error.is::<PaymentRequiredError>() {
1116 cx.emit(ThreadEvent::ShowError(ThreadError::PaymentRequired));
1117 } else if error.is::<MaxMonthlySpendReachedError>() {
1118 cx.emit(ThreadEvent::ShowError(
1119 ThreadError::MaxMonthlySpendReached,
1120 ));
1121 } else {
1122 let error_message = error
1123 .chain()
1124 .map(|err| err.to_string())
1125 .collect::<Vec<_>>()
1126 .join("\n");
1127 cx.emit(ThreadEvent::ShowError(ThreadError::Message {
1128 header: "Error interacting with language model".into(),
1129 message: SharedString::from(error_message.clone()),
1130 }));
1131 }
1132
1133 thread.cancel_last_completion(cx);
1134 }
1135 }
1136 cx.emit(ThreadEvent::DoneStreaming);
1137
1138 if let Ok(initial_usage) = initial_token_usage {
1139 let usage = thread.cumulative_token_usage.clone() - initial_usage;
1140
1141 telemetry::event!(
1142 "Assistant Thread Completion",
1143 thread_id = thread.id().to_string(),
1144 model = model.telemetry_id(),
1145 model_provider = model.provider_id().to_string(),
1146 input_tokens = usage.input_tokens,
1147 output_tokens = usage.output_tokens,
1148 cache_creation_input_tokens = usage.cache_creation_input_tokens,
1149 cache_read_input_tokens = usage.cache_read_input_tokens,
1150 );
1151 }
1152 })
1153 .ok();
1154 });
1155
1156 self.pending_completions.push(PendingCompletion {
1157 id: pending_completion_id,
1158 _task: task,
1159 });
1160 }
1161
1162 pub fn summarize(&mut self, cx: &mut Context<Self>) {
1163 let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
1164 return;
1165 };
1166 let Some(model) = LanguageModelRegistry::read_global(cx).active_model() else {
1167 return;
1168 };
1169
1170 if !provider.is_authenticated(cx) {
1171 return;
1172 }
1173
1174 let mut request = self.to_completion_request(RequestKind::Summarize, cx);
1175 request.messages.push(LanguageModelRequestMessage {
1176 role: Role::User,
1177 content: vec![
1178 "Generate a concise 3-7 word title for this conversation, omitting punctuation. Go straight to the title, without any preamble and prefix like `Here's a concise suggestion:...` or `Title:`"
1179 .into(),
1180 ],
1181 cache: false,
1182 });
1183
1184 self.pending_summary = cx.spawn(async move |this, cx| {
1185 async move {
1186 let stream = model.stream_completion_text(request, &cx);
1187 let mut messages = stream.await?;
1188
1189 let mut new_summary = String::new();
1190 while let Some(message) = messages.stream.next().await {
1191 let text = message?;
1192 let mut lines = text.lines();
1193 new_summary.extend(lines.next());
1194
1195 // Stop if the LLM generated multiple lines.
1196 if lines.next().is_some() {
1197 break;
1198 }
1199 }
1200
1201 this.update(cx, |this, cx| {
1202 if !new_summary.is_empty() {
1203 this.summary = Some(new_summary.into());
1204 }
1205
1206 cx.emit(ThreadEvent::SummaryChanged);
1207 })?;
1208
1209 anyhow::Ok(())
1210 }
1211 .log_err()
1212 .await
1213 });
1214 }
1215
1216 pub fn use_pending_tools(
1217 &mut self,
1218 cx: &mut Context<Self>,
1219 ) -> impl IntoIterator<Item = PendingToolUse> {
1220 let request = self.to_completion_request(RequestKind::Chat, cx);
1221 let messages = Arc::new(request.messages);
1222 let pending_tool_uses = self
1223 .tool_use
1224 .pending_tool_uses()
1225 .into_iter()
1226 .filter(|tool_use| tool_use.status.is_idle())
1227 .cloned()
1228 .collect::<Vec<_>>();
1229
1230 for tool_use in pending_tool_uses.iter() {
1231 if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
1232 if tool.needs_confirmation()
1233 && !AssistantSettings::get_global(cx).always_allow_tool_actions
1234 {
1235 self.tool_use.confirm_tool_use(
1236 tool_use.id.clone(),
1237 tool_use.ui_text.clone(),
1238 tool_use.input.clone(),
1239 messages.clone(),
1240 ToolType::NonScriptingTool(tool),
1241 );
1242 } else {
1243 self.run_tool(
1244 tool_use.id.clone(),
1245 tool_use.ui_text.clone(),
1246 tool_use.input.clone(),
1247 &messages,
1248 ToolType::NonScriptingTool(tool),
1249 cx,
1250 );
1251 }
1252 } else if let Some(tool) = self.tools.tool(&tool_use.name, cx) {
1253 self.run_tool(
1254 tool_use.id.clone(),
1255 tool_use.ui_text.clone(),
1256 tool_use.input.clone(),
1257 &messages,
1258 ToolType::NonScriptingTool(tool),
1259 cx,
1260 );
1261 }
1262 }
1263
1264 let pending_scripting_tool_uses = self
1265 .scripting_tool_use
1266 .pending_tool_uses()
1267 .into_iter()
1268 .filter(|tool_use| tool_use.status.is_idle())
1269 .cloned()
1270 .collect::<Vec<_>>();
1271
1272 for scripting_tool_use in pending_scripting_tool_uses.iter() {
1273 self.scripting_tool_use.confirm_tool_use(
1274 scripting_tool_use.id.clone(),
1275 scripting_tool_use.ui_text.clone(),
1276 scripting_tool_use.input.clone(),
1277 messages.clone(),
1278 ToolType::ScriptingTool,
1279 );
1280 }
1281
1282 pending_tool_uses
1283 .into_iter()
1284 .chain(pending_scripting_tool_uses)
1285 }
1286
1287 pub fn run_tool(
1288 &mut self,
1289 tool_use_id: LanguageModelToolUseId,
1290 ui_text: impl Into<SharedString>,
1291 input: serde_json::Value,
1292 messages: &[LanguageModelRequestMessage],
1293 tool_type: ToolType,
1294 cx: &mut Context<'_, Thread>,
1295 ) {
1296 match tool_type {
1297 ToolType::ScriptingTool => {
1298 let task = self.spawn_scripting_tool_use(tool_use_id.clone(), input, cx);
1299 self.scripting_tool_use
1300 .run_pending_tool(tool_use_id, ui_text.into(), task);
1301 }
1302 ToolType::NonScriptingTool(tool) => {
1303 let task = self.spawn_tool_use(tool_use_id.clone(), messages, input, tool, cx);
1304 self.tool_use
1305 .run_pending_tool(tool_use_id, ui_text.into(), task);
1306 }
1307 }
1308 }
1309
1310 fn spawn_tool_use(
1311 &mut self,
1312 tool_use_id: LanguageModelToolUseId,
1313 messages: &[LanguageModelRequestMessage],
1314 input: serde_json::Value,
1315 tool: Arc<dyn Tool>,
1316 cx: &mut Context<Thread>,
1317 ) -> Task<()> {
1318 let run_tool = tool.run(
1319 input,
1320 messages,
1321 self.project.clone(),
1322 self.action_log.clone(),
1323 cx,
1324 );
1325
1326 cx.spawn({
1327 async move |thread: WeakEntity<Thread>, cx| {
1328 let output = run_tool.await;
1329
1330 thread
1331 .update(cx, |thread, cx| {
1332 let pending_tool_use = thread
1333 .tool_use
1334 .insert_tool_output(tool_use_id.clone(), output);
1335
1336 cx.emit(ThreadEvent::ToolFinished {
1337 tool_use_id,
1338 pending_tool_use,
1339 canceled: false,
1340 });
1341 })
1342 .ok();
1343 }
1344 })
1345 }
1346
1347 fn spawn_scripting_tool_use(
1348 &mut self,
1349 tool_use_id: LanguageModelToolUseId,
1350 input: serde_json::Value,
1351 cx: &mut Context<Thread>,
1352 ) -> Task<()> {
1353 let task = match ScriptingTool::deserialize_input(input) {
1354 Err(err) => Task::ready(Err(err.into())),
1355 Ok(input) => {
1356 let (script_id, script_task) =
1357 self.scripting_session.update(cx, move |session, cx| {
1358 session.run_script(input.lua_script, cx)
1359 });
1360
1361 let session = self.scripting_session.clone();
1362 cx.spawn(async move |_, cx| {
1363 script_task.await;
1364
1365 let message = session.read_with(cx, |session, _cx| {
1366 // Using a id to get the script output seems impractical.
1367 // Why not just include it in the Task result?
1368 // This is because we'll later report the script state as it runs,
1369 session
1370 .get(script_id)
1371 .output_message_for_llm()
1372 .expect("Script shouldn't still be running")
1373 })?;
1374
1375 Ok(message)
1376 })
1377 }
1378 };
1379
1380 cx.spawn({
1381 let tool_use_id = tool_use_id.clone();
1382 async move |thread, cx| {
1383 let output = task.await;
1384 thread
1385 .update(cx, |thread, cx| {
1386 let pending_tool_use = thread
1387 .scripting_tool_use
1388 .insert_tool_output(tool_use_id.clone(), output);
1389
1390 cx.emit(ThreadEvent::ToolFinished {
1391 tool_use_id,
1392 pending_tool_use,
1393 canceled: false,
1394 });
1395 })
1396 .ok();
1397 }
1398 })
1399 }
1400
1401 pub fn attach_tool_results(
1402 &mut self,
1403 updated_context: Vec<ContextSnapshot>,
1404 cx: &mut Context<Self>,
1405 ) {
1406 self.context.extend(
1407 updated_context
1408 .into_iter()
1409 .map(|context| (context.id, context)),
1410 );
1411
1412 // Insert a user message to contain the tool results.
1413 self.insert_user_message(
1414 // TODO: Sending up a user message without any content results in the model sending back
1415 // responses that also don't have any content. We currently don't handle this case well,
1416 // so for now we provide some text to keep the model on track.
1417 "Here are the tool results.",
1418 Vec::new(),
1419 None,
1420 cx,
1421 );
1422 }
1423
1424 /// Cancels the last pending completion, if there are any pending.
1425 ///
1426 /// Returns whether a completion was canceled.
1427 pub fn cancel_last_completion(&mut self, cx: &mut Context<Self>) -> bool {
1428 let canceled = if self.pending_completions.pop().is_some() {
1429 true
1430 } else {
1431 let mut canceled = false;
1432 for pending_tool_use in self.tool_use.cancel_pending() {
1433 canceled = true;
1434 cx.emit(ThreadEvent::ToolFinished {
1435 tool_use_id: pending_tool_use.id.clone(),
1436 pending_tool_use: Some(pending_tool_use),
1437 canceled: true,
1438 });
1439 }
1440 canceled
1441 };
1442 self.finalize_pending_checkpoint(cx);
1443 canceled
1444 }
1445
1446 /// Returns the feedback given to the thread, if any.
1447 pub fn feedback(&self) -> Option<ThreadFeedback> {
1448 self.feedback
1449 }
1450
1451 /// Reports feedback about the thread and stores it in our telemetry backend.
1452 pub fn report_feedback(
1453 &mut self,
1454 feedback: ThreadFeedback,
1455 cx: &mut Context<Self>,
1456 ) -> Task<Result<()>> {
1457 let final_project_snapshot = Self::project_snapshot(self.project.clone(), cx);
1458 let serialized_thread = self.serialize(cx);
1459 let thread_id = self.id().clone();
1460 let client = self.project.read(cx).client();
1461 self.feedback = Some(feedback);
1462 cx.notify();
1463
1464 cx.background_spawn(async move {
1465 let final_project_snapshot = final_project_snapshot.await;
1466 let serialized_thread = serialized_thread.await?;
1467 let thread_data =
1468 serde_json::to_value(serialized_thread).unwrap_or_else(|_| serde_json::Value::Null);
1469
1470 let rating = match feedback {
1471 ThreadFeedback::Positive => "positive",
1472 ThreadFeedback::Negative => "negative",
1473 };
1474 telemetry::event!(
1475 "Assistant Thread Rated",
1476 rating,
1477 thread_id,
1478 thread_data,
1479 final_project_snapshot
1480 );
1481 client.telemetry().flush_events();
1482
1483 Ok(())
1484 })
1485 }
1486
1487 /// Create a snapshot of the current project state including git information and unsaved buffers.
1488 fn project_snapshot(
1489 project: Entity<Project>,
1490 cx: &mut Context<Self>,
1491 ) -> Task<Arc<ProjectSnapshot>> {
1492 let git_store = project.read(cx).git_store().clone();
1493 let worktree_snapshots: Vec<_> = project
1494 .read(cx)
1495 .visible_worktrees(cx)
1496 .map(|worktree| Self::worktree_snapshot(worktree, git_store.clone(), cx))
1497 .collect();
1498
1499 cx.spawn(async move |_, cx| {
1500 let worktree_snapshots = futures::future::join_all(worktree_snapshots).await;
1501
1502 let mut unsaved_buffers = Vec::new();
1503 cx.update(|app_cx| {
1504 let buffer_store = project.read(app_cx).buffer_store();
1505 for buffer_handle in buffer_store.read(app_cx).buffers() {
1506 let buffer = buffer_handle.read(app_cx);
1507 if buffer.is_dirty() {
1508 if let Some(file) = buffer.file() {
1509 let path = file.path().to_string_lossy().to_string();
1510 unsaved_buffers.push(path);
1511 }
1512 }
1513 }
1514 })
1515 .ok();
1516
1517 Arc::new(ProjectSnapshot {
1518 worktree_snapshots,
1519 unsaved_buffer_paths: unsaved_buffers,
1520 timestamp: Utc::now(),
1521 })
1522 })
1523 }
1524
1525 fn worktree_snapshot(
1526 worktree: Entity<project::Worktree>,
1527 git_store: Entity<GitStore>,
1528 cx: &App,
1529 ) -> Task<WorktreeSnapshot> {
1530 cx.spawn(async move |cx| {
1531 // Get worktree path and snapshot
1532 let worktree_info = cx.update(|app_cx| {
1533 let worktree = worktree.read(app_cx);
1534 let path = worktree.abs_path().to_string_lossy().to_string();
1535 let snapshot = worktree.snapshot();
1536 (path, snapshot)
1537 });
1538
1539 let Ok((worktree_path, snapshot)) = worktree_info else {
1540 return WorktreeSnapshot {
1541 worktree_path: String::new(),
1542 git_state: None,
1543 };
1544 };
1545
1546 let repo_info = git_store
1547 .update(cx, |git_store, cx| {
1548 git_store
1549 .repositories()
1550 .values()
1551 .find(|repo| repo.read(cx).worktree_id == snapshot.id())
1552 .and_then(|repo| {
1553 let repo = repo.read(cx);
1554 Some((repo.branch().cloned(), repo.local_repository()?))
1555 })
1556 })
1557 .ok()
1558 .flatten();
1559
1560 // Extract git information
1561 let git_state = match repo_info {
1562 None => None,
1563 Some((branch, repo)) => {
1564 let current_branch = branch.map(|branch| branch.name.to_string());
1565 let remote_url = repo.remote_url("origin");
1566 let head_sha = repo.head_sha();
1567
1568 // Get diff asynchronously
1569 let diff = repo
1570 .diff(git::repository::DiffType::HeadToWorktree, cx.clone())
1571 .await
1572 .ok();
1573
1574 Some(GitState {
1575 remote_url,
1576 head_sha,
1577 current_branch,
1578 diff,
1579 })
1580 }
1581 };
1582
1583 WorktreeSnapshot {
1584 worktree_path,
1585 git_state,
1586 }
1587 })
1588 }
1589
1590 pub fn to_markdown(&self, cx: &App) -> Result<String> {
1591 let mut markdown = Vec::new();
1592
1593 if let Some(summary) = self.summary() {
1594 writeln!(markdown, "# {summary}\n")?;
1595 };
1596
1597 for message in self.messages() {
1598 writeln!(
1599 markdown,
1600 "## {role}\n",
1601 role = match message.role {
1602 Role::User => "User",
1603 Role::Assistant => "Assistant",
1604 Role::System => "System",
1605 }
1606 )?;
1607 for segment in &message.segments {
1608 match segment {
1609 MessageSegment::Text(text) => writeln!(markdown, "{}\n", text)?,
1610 MessageSegment::Thinking(text) => {
1611 writeln!(markdown, "<think>{}</think>\n", text)?
1612 }
1613 }
1614 }
1615
1616 for tool_use in self.tool_uses_for_message(message.id, cx) {
1617 writeln!(
1618 markdown,
1619 "**Use Tool: {} ({})**",
1620 tool_use.name, tool_use.id
1621 )?;
1622 writeln!(markdown, "```json")?;
1623 writeln!(
1624 markdown,
1625 "{}",
1626 serde_json::to_string_pretty(&tool_use.input)?
1627 )?;
1628 writeln!(markdown, "```")?;
1629 }
1630
1631 for tool_result in self.tool_results_for_message(message.id) {
1632 write!(markdown, "**Tool Results: {}", tool_result.tool_use_id)?;
1633 if tool_result.is_error {
1634 write!(markdown, " (Error)")?;
1635 }
1636
1637 writeln!(markdown, "**\n")?;
1638 writeln!(markdown, "{}", tool_result.content)?;
1639 }
1640 }
1641
1642 Ok(String::from_utf8_lossy(&markdown).to_string())
1643 }
1644
1645 pub fn action_log(&self) -> &Entity<ActionLog> {
1646 &self.action_log
1647 }
1648
1649 pub fn project(&self) -> &Entity<Project> {
1650 &self.project
1651 }
1652
1653 pub fn cumulative_token_usage(&self) -> TokenUsage {
1654 self.cumulative_token_usage.clone()
1655 }
1656
1657 pub fn deny_tool_use(
1658 &mut self,
1659 tool_use_id: LanguageModelToolUseId,
1660 tool_type: ToolType,
1661 cx: &mut Context<Self>,
1662 ) {
1663 let err = Err(anyhow::anyhow!(
1664 "Permission to run tool action denied by user"
1665 ));
1666
1667 if let ToolType::ScriptingTool = tool_type {
1668 self.scripting_tool_use
1669 .insert_tool_output(tool_use_id.clone(), err);
1670 } else {
1671 self.tool_use.insert_tool_output(tool_use_id.clone(), err);
1672 }
1673
1674 cx.emit(ThreadEvent::ToolFinished {
1675 tool_use_id,
1676 pending_tool_use: None,
1677 canceled: true,
1678 });
1679 }
1680}
1681
1682#[derive(Debug, Clone)]
1683pub enum ThreadError {
1684 PaymentRequired,
1685 MaxMonthlySpendReached,
1686 Message {
1687 header: SharedString,
1688 message: SharedString,
1689 },
1690}
1691
1692#[derive(Debug, Clone)]
1693pub enum ThreadEvent {
1694 ShowError(ThreadError),
1695 StreamedCompletion,
1696 StreamedAssistantText(MessageId, String),
1697 StreamedAssistantThinking(MessageId, String),
1698 DoneStreaming,
1699 MessageAdded(MessageId),
1700 MessageEdited(MessageId),
1701 MessageDeleted(MessageId),
1702 SummaryChanged,
1703 UsePendingTools,
1704 ToolFinished {
1705 #[allow(unused)]
1706 tool_use_id: LanguageModelToolUseId,
1707 /// The pending tool use that corresponds to this tool.
1708 pending_tool_use: Option<PendingToolUse>,
1709 /// Whether the tool was canceled by the user.
1710 canceled: bool,
1711 },
1712 CheckpointChanged,
1713}
1714
1715impl EventEmitter<ThreadEvent> for Thread {}
1716
1717struct PendingCompletion {
1718 id: usize,
1719 _task: Task<()>,
1720}