8284 lines · 310.9 KB · Rust
RawDownload
use crate::{    ApplyCodeActionTool, CodeActionStore, ContextServerRegistry, CopyPathTool, CreateDirectoryTool,    CreateThreadTool, DbLanguageModel, DbThread, DeletePathTool, DiagnosticsTool, EditFileTool,    FetchTool, FindPathTool, FindReferencesTool, GetCodeActionsTool, GoToDefinitionTool, GrepTool,    ListAgentsAndModelsTool, ListDirectoryTool, MovePathTool, ProjectSnapshot, ReadFileTool,    RenameTool, SandboxedTerminalTool, SpawnAgentTool, SystemPromptTemplate, Template, Templates,    TerminalTool, ToolPermissionDecision, WebSearchTool, WriteFileTool,    decide_permission_from_settings,};use acp_thread::{ClientUserMessageId, MentionUri};use action_log::ActionLog;use agent_settings::UserAgentsMd; use crate::sandboxing::{    SandboxRequest, ThreadSandbox, ThreadSandboxGrants, sandbox_git_dirs,    sandbox_worktree_writable_paths, sandboxing_available_for_project,    sandboxing_enabled_for_project,};use agent_client_protocol::schema::v1 as acp;use agent_settings::{    AgentProfileId, AgentProfileSettings, AgentSettings, AutoCompactThreshold, COMPACTION_PROMPT,    SUMMARIZE_THREAD_DETAILED_PROMPT, SUMMARIZE_THREAD_PROMPT, builtin_profiles,};use anyhow::{Context as _, Result, anyhow};use chrono::{DateTime, Local, Utc};use client::UserStore;use cloud_api_types::Plan;use collections::{HashMap, HashSet, IndexMap};use fs::Fs;use futures::{    FutureExt,    channel::{mpsc, oneshot},    future::Shared,    stream::FuturesUnordered,};use futures::{StreamExt, stream};use gpui::{    App, AppContext, AsyncApp, Context, Entity, EventEmitter, ReadGlobal as _, SharedString, Task,    WeakEntity,};use heck::ToSnakeCase as _;use language_model::{    CompletionIntent, LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent,    LanguageModelId, LanguageModelImage, LanguageModelProviderId, LanguageModelRegistry,    LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool,    LanguageModelToolResult, LanguageModelToolResultContent, LanguageModelToolSchemaFormat,    LanguageModelToolUse, LanguageModelToolUseId, MessageContent, Role, SelectedModel, Speed,    StopReason, TokenUsage, ZED_CLOUD_PROVIDER_ID,};use project::{Project, trusted_worktrees::TrustedWorktrees};use prompt_store::ProjectContext;use schemars::{JsonSchema, Schema};use serde::de::DeserializeOwned;use serde::{Deserialize, Serialize};use settings::{    LanguageModelSelection, Settings, SettingsStore, ToolPermissionMode, update_settings_file,};use std::fmt::Write;use std::{cell::RefCell, ops::ControlFlow};use std::{    collections::BTreeMap,    marker::PhantomData,    ops::RangeInclusive,    path::{Path, PathBuf},    rc::Rc,    sync::Arc,    time::{Duration, Instant},};use util::{ResultExt, debug_panic, markdown::MarkdownCodeBlock, paths::PathStyle};use uuid::Uuid; const TOOL_CANCELED_MESSAGE: &str = "Tool canceled by user";pub const MAX_TOOL_NAME_LENGTH: usize = 64;pub const MAX_SUBAGENT_DEPTH: u8 = 1; pub(crate) fn provider_compatible_tool_name(tool_name: &str) -> String {    let mut sanitized = String::new();    for character in tool_name.chars() {        if sanitized.len() >= MAX_TOOL_NAME_LENGTH {            break;        }         if character.is_ascii_alphanumeric() || character == '_' || character == '-' {            sanitized.push(character);        } else {            sanitized.push('_');        }    }     if sanitized.is_empty() {        sanitized.push_str("tool");    }     sanitized} #[derive(Clone, Debug, Eq, PartialEq)]pub struct SandboxStatusKey {    pub settings_sandbox: ThreadSandbox,    pub thread_sandbox: ThreadSandbox,    pub baseline_writable_paths: Vec<PathBuf>,    pub git_paths: Vec<PathBuf>,} #[derive(Clone, Debug, Eq, PartialEq)]pub struct VerifiedSandboxStatus {    pub settings_sandbox: ThreadSandbox,    pub thread_sandbox: ThreadSandbox,    pub baseline_writable_paths: Vec<PathBuf>,} pub enum SandboxStatusRefresh {    Ready(VerifiedSandboxStatus),    Pending(Task<VerifiedSandboxStatus>),} /// Auto-compaction is only available for models whose context window is at least/// this large. For smaller models there isn't enough headroom for a compaction/// pass to be worthwhile, so we leave the thread uncompacted and let the UI warn/// the user instead.pub const MIN_COMPACTION_CONTEXT_WINDOW: u64 = 80_000; // Using the heuristic that 1 token is about 4 bytes, keep the last 80K bytes of user-message content (~20k tokens).const COMPACTION_RETAINED_USER_MESSAGES_BYTE_BUDGET: usize = 80_000; /// Returned when a turn is attempted but no language model has been selected.#[derive(Debug)]pub struct NoModelConfiguredError; impl std::fmt::Display for NoModelConfiguredError {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        write!(f, "no language model configured")    }} impl std::error::Error for NoModelConfiguredError {} /// Context passed to a subagent thread for lifecycle management#[derive(Clone, Debug, Serialize, Deserialize)]pub struct SubagentContext {    /// ID of the parent thread    pub parent_thread_id: acp::SessionId,     /// Current depth level (0 = root agent, 1 = first-level subagent, etc.)    pub depth: u8,} /// 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)    }} pub(crate) const MAX_RETRY_ATTEMPTS: u8 = 4;pub(crate) const BASE_RETRY_DELAY: Duration = Duration::from_secs(5); #[derive(Debug, Clone)]enum RetryStrategy {    ExponentialBackoff {        initial_delay: Duration,        max_attempts: u8,    },    Fixed {        delay: Duration,        max_attempts: u8,    },} #[derive(Debug, PartialEq, Serialize, Deserialize)]pub enum Message {    User(UserMessage),    Agent(AgentMessage),    Resume,    Compaction(CompactionInfo),} #[derive(Debug, PartialEq, Serialize, Deserialize)]pub enum CompactionInfo {    Summary(SharedString),    ProviderNative {        provider: LanguageModelProviderId,        items: Vec<serde_json::Value>,    },} impl CompactionInfo {    fn to_request(&self) -> Vec<LanguageModelRequestMessage> {        match self {            Self::Summary(summary) => vec![LanguageModelRequestMessage {                role: Role::User,                content: vec![format!(                    "The previous conversation was compacted. Use this summary as context:\n\n{}",                    summary                )                .into()],                cache: false,                reasoning_details: None,            }],            Self::ProviderNative { .. } => Vec::new(),        }    }} impl Message {    pub fn as_agent_message(&self) -> Option<&AgentMessage> {        match self {            Message::Agent(agent_message) => Some(agent_message),            _ => None,        }    }     pub fn to_request(&self) -> Vec<LanguageModelRequestMessage> {        match self {            Message::User(message) => {                if message.content.is_empty() {                    vec![]                } else {                    vec![message.to_request()]                }            }            Message::Agent(message) => message.to_request(),            Message::Compaction(info) => info.to_request(),            Message::Resume => vec![LanguageModelRequestMessage {                role: Role::User,                content: vec!["Continue where you left off".into()],                cache: false,                reasoning_details: None,            }],        }    }     pub fn to_markdown(&self) -> String {        match self {            Message::User(message) => message.to_markdown(),            Message::Agent(message) => message.to_markdown(),            Message::Resume => "[resume]\n".into(),            Message::Compaction(_) => "--- Context Compacted ---\n".into(),        }    }     pub fn role(&self) -> Role {        match self {            Message::User(_) | Message::Resume | Message::Compaction(_) => Role::User,            Message::Agent(_) => Role::Assistant,        }    }} #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]pub struct UserMessage {    pub id: ClientUserMessageId,    pub content: Arc<[UserMessageContent]>,} #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]pub enum UserMessageContent {    Text(String),    Mention {        uri: MentionUri,        content: SharedString,    },    Image(LanguageModelImage),} impl UserMessage {    pub fn to_markdown(&self) -> String {        let mut markdown = String::new();         for content in &*self.content {            match content {                UserMessageContent::Text(text) => {                    markdown.push_str(text);                    markdown.push('\n');                }                UserMessageContent::Image(_) => {                    markdown.push_str("<image />\n");                }                UserMessageContent::Mention { uri, content } => {                    if !content.is_empty() {                        let _ = writeln!(&mut markdown, "{}\n\n{}", uri.as_link(), content);                    } else {                        let _ = writeln!(&mut markdown, "{}", uri.as_link());                    }                }            }        }         markdown    }     fn to_request(&self) -> LanguageModelRequestMessage {        let mut message = LanguageModelRequestMessage {            role: Role::User,            content: Vec::with_capacity(self.content.len()),            cache: false,            reasoning_details: None,        };         const OPEN_CONTEXT: &str = "<context>\n\            The following items were attached by the user. \            They are up-to-date and don't need to be re-read.\n\n";         const OPEN_FILES_TAG: &str = "<files>";        const OPEN_DIRECTORIES_TAG: &str = "<directories>";        const OPEN_SYMBOLS_TAG: &str = "<symbols>";        const OPEN_SELECTIONS_TAG: &str = "<selections>";        const OPEN_THREADS_TAG: &str = "<threads>";        const OPEN_FETCH_TAG: &str = "<fetched_urls>";        const OPEN_RULES_TAG: &str =            "<rules>\nThe user has specified the following rules that should be applied:\n";        const OPEN_DIAGNOSTICS_TAG: &str = "<diagnostics>";        const OPEN_DIFFS_TAG: &str = "<diffs>";        const MERGE_CONFLICT_TAG: &str = "<merge_conflicts>";        const OPEN_SKILLS_TAG: &str =            "<skills>\nThe user has attached the following agent skills:\n";         let mut file_context = OPEN_FILES_TAG.to_string();        let mut directory_context = OPEN_DIRECTORIES_TAG.to_string();        let mut symbol_context = OPEN_SYMBOLS_TAG.to_string();        let mut selection_context = OPEN_SELECTIONS_TAG.to_string();        let mut thread_context = OPEN_THREADS_TAG.to_string();        let mut fetch_context = OPEN_FETCH_TAG.to_string();        let mut rules_context = OPEN_RULES_TAG.to_string();        let mut diagnostics_context = OPEN_DIAGNOSTICS_TAG.to_string();        let mut diffs_context = OPEN_DIFFS_TAG.to_string();        let mut merge_conflict_context = MERGE_CONFLICT_TAG.to_string();        let mut skills_context = OPEN_SKILLS_TAG.to_string();         for chunk in &*self.content {            let chunk = match chunk {                UserMessageContent::Text(text) => {                    language_model::MessageContent::Text(text.clone())                }                UserMessageContent::Image(value) => {                    language_model::MessageContent::Image(value.clone())                }                UserMessageContent::Mention { uri, content } => {                    match uri {                        MentionUri::File { abs_path } => {                            write!(                                &mut file_context,                                "\n{}",                                MarkdownCodeBlock {                                    tag: &codeblock_tag(abs_path, None),                                    text: content,                                }                            )                            .ok();                        }                        MentionUri::PastedImage { .. } => {                            debug_panic!("pasted image URI should not be used in mention content")                        }                        MentionUri::Directory { .. } => {                            write!(&mut directory_context, "\n{}\n", content).ok();                        }                        MentionUri::Symbol {                            abs_path: path,                            line_range,                            ..                        } => {                            write!(                                &mut symbol_context,                                "\n{}",                                MarkdownCodeBlock {                                    tag: &codeblock_tag(path, Some(line_range)),                                    text: content                                }                            )                            .ok();                        }                        MentionUri::Selection {                            abs_path: path,                            line_range,                            ..                        } => {                            write!(                                &mut selection_context,                                "\n{}",                                MarkdownCodeBlock {                                    tag: &codeblock_tag(                                        path.as_deref().unwrap_or("Untitled".as_ref()),                                        Some(line_range)                                    ),                                    text: content                                }                            )                            .ok();                        }                        MentionUri::Thread { .. } => {                            write!(&mut thread_context, "\n{}\n", content).ok();                        }                        MentionUri::Rule { .. } => {                            // Deprecated: keeps legacy rule mentions as context.                            write!(                                &mut rules_context,                                "\n{}",                                MarkdownCodeBlock {                                    tag: "",                                    text: content                                }                            )                            .ok();                        }                        MentionUri::Fetch { url } => {                            write!(&mut fetch_context, "\nFetch: {}\n\n{}", url, content).ok();                        }                        MentionUri::Diagnostics { .. } => {                            write!(&mut diagnostics_context, "\n{}\n", content).ok();                        }                        MentionUri::TerminalSelection { .. } => {                            write!(                                &mut selection_context,                                "\n{}",                                MarkdownCodeBlock {                                    tag: "console",                                    text: content                                }                            )                            .ok();                        }                        MentionUri::GitDiff { base_ref } => {                            write!(                                &mut diffs_context,                                "\nBranch diff against {}:\n{}",                                base_ref,                                MarkdownCodeBlock {                                    tag: "diff",                                    text: content                                }                            )                            .ok();                        }                        MentionUri::MergeConflict { file_path } => {                            write!(                                &mut merge_conflict_context,                                "\nMerge conflict in {}:\n{}",                                file_path,                                MarkdownCodeBlock {                                    tag: "diff",                                    text: content                                }                            )                            .ok();                        }                        MentionUri::Skill { name, source, .. } => {                            let label = format!("{} ({})", name, source);                            write!(&mut skills_context, "\nSkill: {}\n{}\n", label, content).ok();                        }                    }                     language_model::MessageContent::Text(uri.as_link().to_string())                }            };             message.content.push(chunk);        }         let len_before_context = message.content.len();         if file_context.len() > OPEN_FILES_TAG.len() {            file_context.push_str("</files>\n");            message                .content                .push(language_model::MessageContent::Text(file_context));        }         if directory_context.len() > OPEN_DIRECTORIES_TAG.len() {            directory_context.push_str("</directories>\n");            message                .content                .push(language_model::MessageContent::Text(directory_context));        }         if symbol_context.len() > OPEN_SYMBOLS_TAG.len() {            symbol_context.push_str("</symbols>\n");            message                .content                .push(language_model::MessageContent::Text(symbol_context));        }         if selection_context.len() > OPEN_SELECTIONS_TAG.len() {            selection_context.push_str("</selections>\n");            message                .content                .push(language_model::MessageContent::Text(selection_context));        }         if diffs_context.len() > OPEN_DIFFS_TAG.len() {

Lines 1–500 of 8284.