2639 lines · 92.4 KB · Rust
RawDownload
use std::fmt::Write as _;use std::io::Write;use std::ops::Range;use std::sync::Arc;use std::time::Instant; use anyhow::{Result, anyhow};use assistant_settings::AssistantSettings;use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolWorkingSet};use chrono::{DateTime, Utc};use collections::{BTreeMap, HashMap};use feature_flags::{self, FeatureFlagAppExt};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, LanguageModelId,    LanguageModelKnownError, LanguageModelRegistry, LanguageModelRequest,    LanguageModelRequestMessage, LanguageModelRequestTool, LanguageModelToolResult,    LanguageModelToolUseId, MaxMonthlySpendReachedError, MessageContent,    ModelRequestLimitReachedError, PaymentRequiredError, RequestUsage, Role, StopReason,    TokenUsage,};use project::Project;use project::git_store::{GitStore, GitStoreCheckpoint, RepositoryState};use prompt_store::PromptBuilder;use proto::Plan;use schemars::JsonSchema;use serde::{Deserialize, Serialize};use settings::Settings;use thiserror::Error;use util::{ResultExt as _, TryFutureExt as _, post_inc};use uuid::Uuid; use crate::context::{AssistantContext, ContextId, format_context_as_string};use crate::thread_store::{    SerializedMessage, SerializedMessageSegment, SerializedThread, SerializedToolResult,    SerializedToolUse, SharedProjectContext,};use crate::tool_use::{PendingToolUse, ToolUse, ToolUseMetadata, ToolUseState, USING_TOOL_MARKER}; #[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())    }} /// The ID of the user prompt that initiated a request.////// This equates to the user physically submitting a message to the model (e.g., by pressing the Enter key).#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]pub struct PromptId(Arc<str>); impl PromptId {    pub fn new() -> Self {        Self(Uuid::new_v4().to_string().into())    }} impl std::fmt::Display for PromptId {    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>,    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, signature: Option<String>) {        if let Some(MessageSegment::Thinking {            text: segment,            signature: current_signature,        }) = self.segments.last_mut()        {            if let Some(signature) = signature {                *current_signature = Some(signature);            }            segment.push_str(text);        } else {            self.segments.push(MessageSegment::Thinking {                text: text.to_string(),                signature,            });        }    }     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>\n");                    result.push_str(text);                    result.push_str("\n</think>");                }                MessageSegment::RedactedThinking(_) => {}            }        }         result    }} #[derive(Debug, Clone, PartialEq, Eq)]pub enum MessageSegment {    Text(String),    Thinking {        text: String,        signature: Option<String>,    },    RedactedThinking(Vec<u8>),} impl MessageSegment {    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,            Self::RedactedThinking(_) => false,        }    }} #[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, PartialEq, Eq)]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,} impl TotalTokenUsage {    pub fn ratio(&self) -> TokenUsageRatio {        #[cfg(debug_assertions)]        let warning_threshold: f32 = std::env::var("ZED_THREAD_WARNING_THRESHOLD")            .unwrap_or("0.8".to_string())            .parse()            .unwrap();        #[cfg(not(debug_assertions))]        let warning_threshold: f32 = 0.8;         if self.total >= self.max {            TokenUsageRatio::Exceeded        } else if self.total as f32 / self.max as f32 >= warning_threshold {            TokenUsageRatio::Warning        } else {            TokenUsageRatio::Normal        }    }     pub fn add(&self, tokens: usize) -> TotalTokenUsage {        TotalTokenUsage {            total: self.total + tokens,            max: self.max,        }    }} #[derive(Debug, 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,    last_prompt_id: PromptId,    context: BTreeMap<ContextId, AssistantContext>,    context_by_message: HashMap<MessageId, Vec<ContextId>>,    project_context: SharedProjectContext,    checkpoints_by_message: HashMap<MessageId, ThreadCheckpoint>,    completion_count: usize,    pending_completions: Vec<PendingCompletion>,    project: Entity<Project>,    prompt_builder: Arc<PromptBuilder>,    tools: Entity<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>>>>,    request_token_usage: Vec<TokenUsage>,    cumulative_token_usage: TokenUsage,    exceeded_window_error: Option<ExceededWindowError>,    feedback: Option<ThreadFeedback>,    message_feedback: HashMap<MessageId, ThreadFeedback>,    last_auto_capture_at: Option<Instant>,    request_callback: Option<        Box<dyn FnMut(&LanguageModelRequest, &[Result<LanguageModelCompletionEvent, String>])>,    >,} #[derive(Debug, Clone, Serialize, Deserialize)]pub struct ExceededWindowError {    /// Model used when last message exceeded context window    model_id: LanguageModelId,    /// Token count including last message    token_count: usize,} impl Thread {    pub fn new(        project: Entity<Project>,        tools: Entity<ToolWorkingSet>,        prompt_builder: Arc<PromptBuilder>,        system_prompt: SharedProjectContext,        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),            last_prompt_id: PromptId::new(),            context: BTreeMap::default(),            context_by_message: HashMap::default(),            project_context: system_prompt,            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()            },            request_token_usage: Vec::new(),            cumulative_token_usage: TokenUsage::default(),            exceeded_window_error: None,            feedback: None,            message_feedback: HashMap::default(),            last_auto_capture_at: None,            request_callback: None,        }    }     pub fn deserialize(        id: ThreadId,        serialized: SerializedThread,        project: Entity<Project>,        tools: Entity<ToolWorkingSet>,        prompt_builder: Arc<PromptBuilder>,        project_context: SharedProjectContext,        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, signature } => {                                MessageSegment::Thinking { text, signature }                            }                            SerializedMessageSegment::RedactedThinking { data } => {                                MessageSegment::RedactedThinking(data)                            }                        })                        .collect(),                    context: message.context,                })                .collect(),            next_message_id,            last_prompt_id: PromptId::new(),            context: BTreeMap::default(),            context_by_message: HashMap::default(),            project_context,            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(),            request_token_usage: serialized.request_token_usage,            cumulative_token_usage: serialized.cumulative_token_usage,            exceeded_window_error: None,            feedback: None,            message_feedback: HashMap::default(),            last_auto_capture_at: None,            request_callback: None,        }    }     pub fn set_request_callback(        &mut self,        callback: impl 'static        + FnMut(&LanguageModelRequest, &[Result<LanguageModelCompletionEvent, String>]),    ) {        self.request_callback = Some(Box::new(callback));    }     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 advance_prompt_id(&mut self) {        self.last_prompt_id = PromptId::new();    }     pub fn summary(&self) -> Option<SharedString> {        self.summary.clone()    }     pub fn project_context(&self) -> SharedProjectContext {        self.project_context.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;

Lines 1–500 of 2639.