use std::fmt::Write as _;use std::io::Write;use std::sync::Arc; use anyhow::{Context as _, Result};use assistant_settings::AssistantSettings;use assistant_tool::{ActionLog, Tool, ToolWorkingSet};use chrono::{DateTime, Utc};use collections::{BTreeMap, HashMap, HashSet};use fs::Fs;use futures::future::Shared;use futures::{FutureExt, StreamExt as _};use git;use gpui::{App, AppContext, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};use language_model::{ LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, Role, StopReason, TokenUsage,};use project::git_store::{GitStore, GitStoreCheckpoint};use project::{Project, Worktree};use prompt_store::{ AssistantSystemPromptContext, PromptBuilder, RulesFile, WorktreeInfoForSystemPrompt,};use serde::{Deserialize, Serialize};use settings::Settings;use util::{maybe, post_inc, ResultExt as _, TryFutureExt as _};use uuid::Uuid; use crate::context::{attach_context_to_message, ContextId, ContextSnapshot};use crate::thread_store::{ SerializedMessage, SerializedMessageSegment, SerializedThread, SerializedToolResult, SerializedToolUse,};use crate::tool_use::{PendingToolUse, ToolUse, ToolUseState}; #[derive(Debug, Clone, Copy)]pub enum RequestKind { Chat, /// Used when summarizing a thread. Summarize,} #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]pub struct ThreadId(Arc<str>); impl ThreadId { pub fn new() -> Self { Self(Uuid::new_v4().to_string().into()) }} impl std::fmt::Display for ThreadId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) }} #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]pub struct MessageId(pub(crate) usize); impl MessageId { fn post_inc(&mut self) -> Self { Self(post_inc(&mut self.0)) }} /// A message in a [`Thread`].#[derive(Debug, Clone)]pub struct Message { pub id: MessageId, pub role: Role, pub segments: Vec<MessageSegment>,} impl Message { pub fn push_thinking(&mut self, text: &str) { if let Some(MessageSegment::Thinking(segment)) = self.segments.last_mut() { segment.push_str(text); } else { self.segments .push(MessageSegment::Thinking(text.to_string())); } } pub fn push_text(&mut self, text: &str) { if let Some(MessageSegment::Text(segment)) = self.segments.last_mut() { segment.push_str(text); } else { self.segments.push(MessageSegment::Text(text.to_string())); } } pub fn to_string(&self) -> String { let mut result = String::new(); for segment in &self.segments { match segment { MessageSegment::Text(text) => result.push_str(text), MessageSegment::Thinking(text) => { result.push_str("<think>"); result.push_str(text); result.push_str("</think>"); } } } result }} #[derive(Debug, Clone)]pub enum MessageSegment { Text(String), Thinking(String),} #[derive(Debug, Clone, Serialize, Deserialize)]pub struct ProjectSnapshot { pub worktree_snapshots: Vec<WorktreeSnapshot>, pub unsaved_buffer_paths: Vec<String>, pub timestamp: DateTime<Utc>,} #[derive(Debug, Clone, Serialize, Deserialize)]pub struct WorktreeSnapshot { pub worktree_path: String, pub git_state: Option<GitState>,} #[derive(Debug, Clone, Serialize, Deserialize)]pub struct GitState { pub remote_url: Option<String>, pub head_sha: Option<String>, pub current_branch: Option<String>, pub diff: Option<String>,} #[derive(Clone)]pub struct ThreadCheckpoint { message_id: MessageId, git_checkpoint: GitStoreCheckpoint,} #[derive(Copy, Clone, Debug)]pub enum ThreadFeedback { Positive, Negative,} pub enum LastRestoreCheckpoint { Pending { message_id: MessageId, }, Error { message_id: MessageId, error: String, },} impl LastRestoreCheckpoint { pub fn message_id(&self) -> MessageId { match self { LastRestoreCheckpoint::Pending { message_id } => *message_id, LastRestoreCheckpoint::Error { message_id, .. } => *message_id, } }} /// A thread of conversation with the LLM.pub struct Thread { id: ThreadId, updated_at: DateTime<Utc>, summary: Option<SharedString>, pending_summary: Task<Option<()>>, messages: Vec<Message>, next_message_id: MessageId, context: BTreeMap<ContextId, ContextSnapshot>, context_by_message: HashMap<MessageId, Vec<ContextId>>, system_prompt_context: Option<AssistantSystemPromptContext>, checkpoints_by_message: HashMap<MessageId, ThreadCheckpoint>, completion_count: usize, pending_completions: Vec<PendingCompletion>, project: Entity<Project>, prompt_builder: Arc<PromptBuilder>, tools: Arc<ToolWorkingSet>, tool_use: ToolUseState, action_log: Entity<ActionLog>, last_restore_checkpoint: Option<LastRestoreCheckpoint>, pending_checkpoint: Option<ThreadCheckpoint>, initial_project_snapshot: Shared<Task<Option<Arc<ProjectSnapshot>>>>, cumulative_token_usage: TokenUsage, feedback: Option<ThreadFeedback>,} impl Thread { pub fn new( project: Entity<Project>, tools: Arc<ToolWorkingSet>, prompt_builder: Arc<PromptBuilder>, cx: &mut Context<Self>, ) -> Self { Self { id: ThreadId::new(), updated_at: Utc::now(), summary: None, pending_summary: Task::ready(None), messages: Vec::new(), next_message_id: MessageId(0), context: BTreeMap::default(), context_by_message: HashMap::default(), system_prompt_context: None, checkpoints_by_message: HashMap::default(), completion_count: 0, pending_completions: Vec::new(), project: project.clone(), prompt_builder, tools: tools.clone(), last_restore_checkpoint: None, pending_checkpoint: None, tool_use: ToolUseState::new(tools.clone()), action_log: cx.new(|_| ActionLog::new()), initial_project_snapshot: { let project_snapshot = Self::project_snapshot(project, cx); cx.foreground_executor() .spawn(async move { Some(project_snapshot.await) }) .shared() }, cumulative_token_usage: TokenUsage::default(), feedback: None, } } pub fn deserialize( id: ThreadId, serialized: SerializedThread, project: Entity<Project>, tools: Arc<ToolWorkingSet>, prompt_builder: Arc<PromptBuilder>, cx: &mut Context<Self>, ) -> Self { let next_message_id = MessageId( serialized .messages .last() .map(|message| message.id.0 + 1) .unwrap_or(0), ); let tool_use = ToolUseState::from_serialized_messages(tools.clone(), &serialized.messages, |_| true); Self { id, updated_at: serialized.updated_at, summary: Some(serialized.summary), pending_summary: Task::ready(None), messages: serialized .messages .into_iter() .map(|message| Message { id: message.id, role: message.role, segments: message .segments .into_iter() .map(|segment| match segment { SerializedMessageSegment::Text { text } => MessageSegment::Text(text), SerializedMessageSegment::Thinking { text } => { MessageSegment::Thinking(text) } }) .collect(), }) .collect(), next_message_id, context: BTreeMap::default(), context_by_message: HashMap::default(), system_prompt_context: None, checkpoints_by_message: HashMap::default(), completion_count: 0, pending_completions: Vec::new(), last_restore_checkpoint: None, pending_checkpoint: None, project, prompt_builder, tools, tool_use, action_log: cx.new(|_| ActionLog::new()), initial_project_snapshot: Task::ready(serialized.initial_project_snapshot).shared(), cumulative_token_usage: serialized.cumulative_token_usage, feedback: None, } } pub fn id(&self) -> &ThreadId { &self.id } pub fn is_empty(&self) -> bool { self.messages.is_empty() } pub fn updated_at(&self) -> DateTime<Utc> { self.updated_at } pub fn touch_updated_at(&mut self) { self.updated_at = Utc::now(); } pub fn summary(&self) -> Option<SharedString> { self.summary.clone() } pub fn summary_or_default(&self) -> SharedString { const DEFAULT: SharedString = SharedString::new_static("New Thread"); self.summary.clone().unwrap_or(DEFAULT) } pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut Context<Self>) { self.summary = Some(summary.into()); cx.emit(ThreadEvent::SummaryChanged); } pub fn message(&self, id: MessageId) -> Option<&Message> { self.messages.iter().find(|message| message.id == id) } pub fn messages(&self) -> impl Iterator<Item = &Message> { self.messages.iter() } pub fn is_generating(&self) -> bool { !self.pending_completions.is_empty() || !self.all_tools_finished() } pub fn tools(&self) -> &Arc<ToolWorkingSet> { &self.tools } pub fn pending_tool(&self, id: &LanguageModelToolUseId) -> Option<&PendingToolUse> { self.tool_use .pending_tool_uses() .into_iter() .find(|tool_use| &tool_use.id == id) } pub fn tools_needing_confirmation(&self) -> impl Iterator<Item = &PendingToolUse> { self.tool_use .pending_tool_uses() .into_iter() .filter(|tool_use| tool_use.status.needs_confirmation()) } pub fn has_pending_tool_uses(&self) -> bool { !self.tool_use.pending_tool_uses().is_empty() } pub fn checkpoint_for_message(&self, id: MessageId) -> Option<ThreadCheckpoint> { self.checkpoints_by_message.get(&id).cloned() } pub fn restore_checkpoint( &mut self, checkpoint: ThreadCheckpoint, cx: &mut Context<Self>, ) -> Task<Result<()>> { self.last_restore_checkpoint = Some(LastRestoreCheckpoint::Pending { message_id: checkpoint.message_id, }); cx.emit(ThreadEvent::CheckpointChanged); cx.notify(); let project = self.project.read(cx); let restore = project .git_store() .read(cx) .restore_checkpoint(checkpoint.git_checkpoint.clone(), cx); cx.spawn(async move |this, cx| { let result = restore.await; this.update(cx, |this, cx| { if let Err(err) = result.as_ref() { this.last_restore_checkpoint = Some(LastRestoreCheckpoint::Error { message_id: checkpoint.message_id, error: err.to_string(), }); } else { this.truncate(checkpoint.message_id, cx); this.last_restore_checkpoint = None; } this.pending_checkpoint = None; cx.emit(ThreadEvent::CheckpointChanged); cx.notify(); })?; result }) } fn finalize_pending_checkpoint(&mut self, cx: &mut Context<Self>) { let pending_checkpoint = if self.is_generating() { return; } else if let Some(checkpoint) = self.pending_checkpoint.take() { checkpoint } else { return; }; let git_store = self.project.read(cx).git_store().clone(); let final_checkpoint = git_store.read(cx).checkpoint(cx); cx.spawn(async move |this, cx| match final_checkpoint.await { Ok(final_checkpoint) => { let equal = git_store .read_with(cx, |store, cx| { store.compare_checkpoints( pending_checkpoint.git_checkpoint.clone(), final_checkpoint.clone(), cx, ) })? .await .unwrap_or(false); if equal { git_store .read_with(cx, |store, cx| { store.delete_checkpoint(pending_checkpoint.git_checkpoint, cx) })? .detach(); } else { this.update(cx, |this, cx| { this.insert_checkpoint(pending_checkpoint, cx) })?; } git_store .read_with(cx, |store, cx| { store.delete_checkpoint(final_checkpoint, cx) })? .detach(); Ok(()) } Err(_) => this.update(cx, |this, cx| { this.insert_checkpoint(pending_checkpoint, cx) }), }) .detach(); } fn insert_checkpoint(&mut self, checkpoint: ThreadCheckpoint, cx: &mut Context<Self>) { self.checkpoints_by_message .insert(checkpoint.message_id, checkpoint); cx.emit(ThreadEvent::CheckpointChanged); cx.notify(); } pub fn last_restore_checkpoint(&self) -> Option<&LastRestoreCheckpoint> { self.last_restore_checkpoint.as_ref() } pub fn truncate(&mut self, message_id: MessageId, cx: &mut Context<Self>) { let Some(message_ix) = self .messages .iter() .rposition(|message| message.id == message_id) else { return; }; for deleted_message in self.messages.drain(message_ix..) { self.context_by_message.remove(&deleted_message.id); self.checkpoints_by_message.remove(&deleted_message.id); } cx.notify(); } pub fn context_for_message(&self, id: MessageId) -> Option<Vec<ContextSnapshot>> { let context = self.context_by_message.get(&id)?; Some( context .into_iter() .filter_map(|context_id| self.context.get(&context_id)) .cloned() .collect::<Vec<_>>(), ) } /// Returns whether all of the tool uses have finished running. pub fn all_tools_finished(&self) -> bool { // If the only pending tool uses left are the ones with errors, then // that means that we've finished running all of the pending tools. self.tool_use .pending_tool_uses() .iter() .all(|tool_use| tool_use.status.is_error()) } pub fn tool_uses_for_message(&self, id: MessageId, cx: &App) -> Vec<ToolUse> { self.tool_use.tool_uses_for_message(id, cx) } pub fn tool_results_for_message(&self, id: MessageId) -> Vec<&LanguageModelToolResult> {Lines 1–500 of 1580. Load more
use std::fmt::Write as _; use std::io::Write; use std::sync::Arc; use anyhow::{Context as _, Result}; use assistant_settings::AssistantSettings; use assistant_tool::{ActionLog, Tool, ToolWorkingSet}; use chrono::{DateTime, Utc}; use collections::{BTreeMap, HashMap, HashSet}; use fs::Fs; use futures::future::Shared; use futures::{FutureExt, StreamExt as _}; use git; use gpui::{App, AppContext, Context, Entity, EventEmitter, SharedString, Task, WeakEntity}; use language_model::{ LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult, LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent, PaymentRequiredError, Role, StopReason, TokenUsage, }; use project::git_store::{GitStore, GitStoreCheckpoint}; use project::{Project, Worktree}; use prompt_store::{ AssistantSystemPromptContext, PromptBuilder, RulesFile, WorktreeInfoForSystemPrompt, }; use serde::{Deserialize, Serialize}; use settings::Settings; use util::{maybe, post_inc, ResultExt as _, TryFutureExt as _}; use uuid::Uuid; use crate::context::{attach_context_to_message, ContextId, ContextSnapshot}; use crate::thread_store::{ SerializedMessage, SerializedMessageSegment, SerializedThread, SerializedToolResult, SerializedToolUse, }; use crate::tool_use::{PendingToolUse, ToolUse, ToolUseState}; #[derive(Debug, Clone, Copy)] pub enum RequestKind { Chat, /// Used when summarizing a thread. Summarize, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)] pub struct ThreadId(Arc<str>); impl ThreadId { pub fn new() -> Self { Self(Uuid::new_v4().to_string().into()) } } impl std::fmt::Display for ThreadId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)] pub struct MessageId(pub(crate) usize); impl MessageId { fn post_inc(&mut self) -> Self { Self(post_inc(&mut self.0)) } } /// A message in a [`Thread`]. #[derive(Debug, Clone)] pub struct Message { pub id: MessageId, pub role: Role, pub segments: Vec<MessageSegment>, } impl Message { pub fn push_thinking(&mut self, text: &str) { if let Some(MessageSegment::Thinking(segment)) = self.segments.last_mut() { segment.push_str(text); } else { self.segments .push(MessageSegment::Thinking(text.to_string())); } } pub fn push_text(&mut self, text: &str) { if let Some(MessageSegment::Text(segment)) = self.segments.last_mut() { segment.push_str(text); } else { self.segments.push(MessageSegment::Text(text.to_string())); } } pub fn to_string(&self) -> String { let mut result = String::new(); for segment in &self.segments { match segment { MessageSegment::Text(text) => result.push_str(text), MessageSegment::Thinking(text) => { result.push_str("<think>"); result.push_str(text); result.push_str("</think>"); } } } result } } #[derive(Debug, Clone)] pub enum MessageSegment { Text(String), Thinking(String), } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProjectSnapshot { pub worktree_snapshots: Vec<WorktreeSnapshot>, pub unsaved_buffer_paths: Vec<String>, pub timestamp: DateTime<Utc>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WorktreeSnapshot { pub worktree_path: String, pub git_state: Option<GitState>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GitState { pub remote_url: Option<String>, pub head_sha: Option<String>, pub current_branch: Option<String>, pub diff: Option<String>, } #[derive(Clone)] pub struct ThreadCheckpoint { message_id: MessageId, git_checkpoint: GitStoreCheckpoint, } #[derive(Copy, Clone, Debug)] pub enum ThreadFeedback { Positive, Negative, } pub enum LastRestoreCheckpoint { Pending { message_id: MessageId, }, Error { message_id: MessageId, error: String, }, } impl LastRestoreCheckpoint { pub fn message_id(&self) -> MessageId { match self { LastRestoreCheckpoint::Pending { message_id } => *message_id, LastRestoreCheckpoint::Error { message_id, .. } => *message_id, } } } /// A thread of conversation with the LLM. pub struct Thread { id: ThreadId, updated_at: DateTime<Utc>, summary: Option<SharedString>, pending_summary: Task<Option<()>>, messages: Vec<Message>, next_message_id: MessageId, context: BTreeMap<ContextId, ContextSnapshot>, context_by_message: HashMap<MessageId, Vec<ContextId>>, system_prompt_context: Option<AssistantSystemPromptContext>, checkpoints_by_message: HashMap<MessageId, ThreadCheckpoint>, completion_count: usize, pending_completions: Vec<PendingCompletion>, project: Entity<Project>, prompt_builder: Arc<PromptBuilder>, tools: Arc<ToolWorkingSet>, tool_use: ToolUseState, action_log: Entity<ActionLog>, last_restore_checkpoint: Option<LastRestoreCheckpoint>, pending_checkpoint: Option<ThreadCheckpoint>, initial_project_snapshot: Shared<Task<Option<Arc<ProjectSnapshot>>>>, cumulative_token_usage: TokenUsage, feedback: Option<ThreadFeedback>, } impl Thread { pub fn new( project: Entity<Project>, tools: Arc<ToolWorkingSet>, prompt_builder: Arc<PromptBuilder>, cx: &mut Context<Self>, ) -> Self { Self { id: ThreadId::new(), updated_at: Utc::now(), summary: None, pending_summary: Task::ready(None), messages: Vec::new(), next_message_id: MessageId(0), context: BTreeMap::default(), context_by_message: HashMap::default(), system_prompt_context: None, checkpoints_by_message: HashMap::default(), completion_count: 0, pending_completions: Vec::new(), project: project.clone(), prompt_builder, tools: tools.clone(), last_restore_checkpoint: None, pending_checkpoint: None, tool_use: ToolUseState::new(tools.clone()), action_log: cx.new(|_| ActionLog::new()), initial_project_snapshot: { let project_snapshot = Self::project_snapshot(project, cx); cx.foreground_executor() .spawn(async move { Some(project_snapshot.await) }) .shared() }, cumulative_token_usage: TokenUsage::default(), feedback: None, } } pub fn deserialize( id: ThreadId, serialized: SerializedThread, project: Entity<Project>, tools: Arc<ToolWorkingSet>, prompt_builder: Arc<PromptBuilder>, cx: &mut Context<Self>, ) -> Self { let next_message_id = MessageId( serialized .messages .last() .map(|message| message.id.0 + 1) .unwrap_or(0), ); let tool_use = ToolUseState::from_serialized_messages(tools.clone(), &serialized.messages, |_| true); Self { id, updated_at: serialized.updated_at, summary: Some(serialized.summary), pending_summary: Task::ready(None), messages: serialized .messages .into_iter() .map(|message| Message { id: message.id, role: message.role, segments: message .segments .into_iter() .map(|segment| match segment { SerializedMessageSegment::Text { text } => MessageSegment::Text(text), SerializedMessageSegment::Thinking { text } => { MessageSegment::Thinking(text) } }) .collect(), }) .collect(), next_message_id, context: BTreeMap::default(), context_by_message: HashMap::default(), system_prompt_context: None, checkpoints_by_message: HashMap::default(), completion_count: 0, pending_completions: Vec::new(), last_restore_checkpoint: None, pending_checkpoint: None, project, prompt_builder, tools, tool_use, action_log: cx.new(|_| ActionLog::new()), initial_project_snapshot: Task::ready(serialized.initial_project_snapshot).shared(), cumulative_token_usage: serialized.cumulative_token_usage, feedback: None, } } pub fn id(&self) -> &ThreadId { &self.id } pub fn is_empty(&self) -> bool { self.messages.is_empty() } pub fn updated_at(&self) -> DateTime<Utc> { self.updated_at } pub fn touch_updated_at(&mut self) { self.updated_at = Utc::now(); } pub fn summary(&self) -> Option<SharedString> { self.summary.clone() } pub fn summary_or_default(&self) -> SharedString { const DEFAULT: SharedString = SharedString::new_static("New Thread"); self.summary.clone().unwrap_or(DEFAULT) } pub fn set_summary(&mut self, summary: impl Into<SharedString>, cx: &mut Context<Self>) { self.summary = Some(summary.into()); cx.emit(ThreadEvent::SummaryChanged); } pub fn message(&self, id: MessageId) -> Option<&Message> { self.messages.iter().find(|message| message.id == id) } pub fn messages(&self) -> impl Iterator<Item = &Message> { self.messages.iter() } pub fn is_generating(&self) -> bool { !self.pending_completions.is_empty() || !self.all_tools_finished() } pub fn tools(&self) -> &Arc<ToolWorkingSet> { &self.tools } pub fn pending_tool(&self, id: &LanguageModelToolUseId) -> Option<&PendingToolUse> { self.tool_use .pending_tool_uses() .into_iter() .find(|tool_use| &tool_use.id == id) } pub fn tools_needing_confirmation(&self) -> impl Iterator<Item = &PendingToolUse> { self.tool_use .pending_tool_uses() .into_iter() .filter(|tool_use| tool_use.status.needs_confirmation()) } pub fn has_pending_tool_uses(&self) -> bool { !self.tool_use.pending_tool_uses().is_empty() } pub fn checkpoint_for_message(&self, id: MessageId) -> Option<ThreadCheckpoint> { self.checkpoints_by_message.get(&id).cloned() } pub fn restore_checkpoint( &mut self, checkpoint: ThreadCheckpoint, cx: &mut Context<Self>, ) -> Task<Result<()>> { self.last_restore_checkpoint = Some(LastRestoreCheckpoint::Pending { message_id: checkpoint.message_id, }); cx.emit(ThreadEvent::CheckpointChanged); cx.notify(); let project = self.project.read(cx); let restore = project .git_store() .read(cx) .restore_checkpoint(checkpoint.git_checkpoint.clone(), cx); cx.spawn(async move |this, cx| { let result = restore.await; this.update(cx, |this, cx| { if let Err(err) = result.as_ref() { this.last_restore_checkpoint = Some(LastRestoreCheckpoint::Error { message_id: checkpoint.message_id, error: err.to_string(), }); } else { this.truncate(checkpoint.message_id, cx); this.last_restore_checkpoint = None; } this.pending_checkpoint = None; cx.emit(ThreadEvent::CheckpointChanged); cx.notify(); })?; result }) } fn finalize_pending_checkpoint(&mut self, cx: &mut Context<Self>) { let pending_checkpoint = if self.is_generating() { return; } else if let Some(checkpoint) = self.pending_checkpoint.take() { checkpoint } else { return; }; let git_store = self.project.read(cx).git_store().clone(); let final_checkpoint = git_store.read(cx).checkpoint(cx); cx.spawn(async move |this, cx| match final_checkpoint.await { Ok(final_checkpoint) => { let equal = git_store .read_with(cx, |store, cx| { store.compare_checkpoints( pending_checkpoint.git_checkpoint.clone(), final_checkpoint.clone(), cx, ) })? .await .unwrap_or(false); if equal { git_store .read_with(cx, |store, cx| { store.delete_checkpoint(pending_checkpoint.git_checkpoint, cx) })? .detach(); } else { this.update(cx, |this, cx| { this.insert_checkpoint(pending_checkpoint, cx) })?; } git_store .read_with(cx, |store, cx| { store.delete_checkpoint(final_checkpoint, cx) })? .detach(); Ok(()) } Err(_) => this.update(cx, |this, cx| { this.insert_checkpoint(pending_checkpoint, cx) }), }) .detach(); } fn insert_checkpoint(&mut self, checkpoint: ThreadCheckpoint, cx: &mut Context<Self>) { self.checkpoints_by_message .insert(checkpoint.message_id, checkpoint); cx.emit(ThreadEvent::CheckpointChanged); cx.notify(); } pub fn last_restore_checkpoint(&self) -> Option<&LastRestoreCheckpoint> { self.last_restore_checkpoint.as_ref() } pub fn truncate(&mut self, message_id: MessageId, cx: &mut Context<Self>) { let Some(message_ix) = self .messages .iter() .rposition(|message| message.id == message_id) else { return; }; for deleted_message in self.messages.drain(message_ix..) { self.context_by_message.remove(&deleted_message.id); self.checkpoints_by_message.remove(&deleted_message.id); } cx.notify(); } pub fn context_for_message(&self, id: MessageId) -> Option<Vec<ContextSnapshot>> { let context = self.context_by_message.get(&id)?; Some( context .into_iter() .filter_map(|context_id| self.context.get(&context_id)) .cloned() .collect::<Vec<_>>(), ) } /// Returns whether all of the tool uses have finished running. pub fn all_tools_finished(&self) -> bool { // If the only pending tool uses left are the ones with errors, then // that means that we've finished running all of the pending tools. self.tool_use .pending_tool_uses() .iter() .all(|tool_use| tool_use.status.is_error()) } pub fn tool_uses_for_message(&self, id: MessageId, cx: &App) -> Vec<ToolUse> { self.tool_use.tool_uses_for_message(id, cx) } pub fn tool_results_for_message(&self, id: MessageId) -> Vec<&LanguageModelToolResult> {