thread.rs

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