2297 lines · 79.2 KB · Rust
RawDownload
use std::fmt::Write as _;use std::io::Write;use std::ops::Range;use std::sync::Arc; use anyhow::{Context as _, Result, anyhow};use assistant_settings::AssistantSettings;use assistant_tool::{ActionLog, Tool, ToolWorkingSet};use chrono::{DateTime, Utc};use collections::{BTreeMap, HashMap};use fs::Fs;use futures::future::Shared;use futures::{FutureExt, StreamExt as _};use git::repository::DiffType;use gpui::{App, AppContext, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};use language_model::{    ConfiguredModel, LanguageModel, LanguageModelCompletionEvent, LanguageModelRegistry,    LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool,    LanguageModelToolResult, LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent,    PaymentRequiredError, Role, StopReason, TokenUsage,};use project::git_store::{GitStore, GitStoreCheckpoint, RepositoryState};use project::{Project, Worktree};use prompt_store::{    AssistantSystemPromptContext, PromptBuilder, RulesFile, WorktreeInfoForSystemPrompt,};use schemars::JsonSchema;use serde::{Deserialize, Serialize};use settings::Settings;use util::{ResultExt as _, TryFutureExt as _, maybe, post_inc};use uuid::Uuid; use crate::context::{AssistantContext, ContextId, format_context_as_string};use crate::thread_store::{    SerializedMessage, SerializedMessageSegment, SerializedThread, SerializedToolResult,    SerializedToolUse,};use crate::tool_use::{PendingToolUse, ToolUse, ToolUseState, USING_TOOL_MARKER}; #[derive(Debug, Clone, Copy)]pub enum RequestKind {    Chat,    /// Used when summarizing a thread.    Summarize,} #[derive(    Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize, JsonSchema,)]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)    }} impl From<&str> for ThreadId {    fn from(value: &str) -> Self {        Self(value.into())    }} #[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>,    pub context: String,} impl Message {    /// Returns whether the message contains any meaningful text that should be displayed    /// The model sometimes runs tool without producing any text or just a marker ([`USING_TOOL_MARKER`])    pub fn should_display_content(&self) -> bool {        self.segments.iter().all(|segment| segment.should_display())    }     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();         if !self.context.is_empty() {            result.push_str(&self.context);        }         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, PartialEq, Eq)]pub enum MessageSegment {    Text(String),    Thinking(String),} impl MessageSegment {    pub fn text_mut(&mut self) -> &mut String {        match self {            Self::Text(text) => text,            Self::Thinking(text) => text,        }    }     pub fn should_display(&self) -> bool {        // We add USING_TOOL_MARKER when making a request that includes tool uses        // without non-whitespace text around them, and this can cause the model        // to mimic the pattern, so we consider those segments not displayable.        match self {            Self::Text(text) => text.is_empty() || text.trim() == USING_TOOL_MARKER,            Self::Thinking(text) => text.is_empty() || text.trim() == USING_TOOL_MARKER,        }    }} #[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,        }    }} #[derive(Clone, Debug, Default, Serialize, Deserialize)]pub enum DetailedSummaryState {    #[default]    NotGenerated,    Generating {        message_id: MessageId,    },    Generated {        text: SharedString,        message_id: MessageId,    },} #[derive(Default)]pub struct TotalTokenUsage {    pub total: usize,    pub max: usize,    pub ratio: TokenUsageRatio,} #[derive(Default, PartialEq, Eq)]pub enum TokenUsageRatio {    #[default]    Normal,    Warning,    Exceeded,} /// A thread of conversation with the LLM.pub struct Thread {    id: ThreadId,    updated_at: DateTime<Utc>,    summary: Option<SharedString>,    pending_summary: Task<Option<()>>,    detailed_summary_state: DetailedSummaryState,    messages: Vec<Message>,    next_message_id: MessageId,    context: BTreeMap<ContextId, AssistantContext>,    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),            detailed_summary_state: DetailedSummaryState::NotGenerated,            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(project.clone())),            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),            detailed_summary_state: serialized.detailed_summary_state,            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(),                    context: message.context,                })                .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: project.clone(),            prompt_builder,            tools,            tool_use,            action_log: cx.new(|_| ActionLog::new(project)),            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 const DEFAULT_SUMMARY: SharedString = SharedString::new_static("New Thread");     pub fn summary_or_default(&self) -> SharedString {        self.summary.clone().unwrap_or(Self::DEFAULT_SUMMARY)    }     pub fn set_summary(&mut self, new_summary: impl Into<SharedString>, cx: &mut Context<Self>) {        let Some(current_summary) = &self.summary else {            // Don't allow setting summary until generated            return;        };         let mut new_summary = new_summary.into();         if new_summary.is_empty() {            new_summary = Self::DEFAULT_SUMMARY;        }         if current_summary != &new_summary {            self.summary = Some(new_summary);            cx.emit(ThreadEvent::SummaryChanged);        }    }     pub fn latest_detailed_summary_or_text(&self) -> SharedString {        self.latest_detailed_summary()            .unwrap_or_else(|| self.text().into())    }     fn latest_detailed_summary(&self) -> Option<SharedString> {        if let DetailedSummaryState::Generated { text, .. } = &self.detailed_summary_state {            Some(text.clone())        } else {            None        }    }     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 git_store = self.project().read(cx).git_store().clone();        let restore = git_store.update(cx, |git_store, cx| {            git_store.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() {

Lines 1–500 of 2297.