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