2707 lines · 95.0 KB · Rust
RawDownload
use crate::{    ContextServerRegistry, CopyPathTool, CreateDirectoryTool, DbLanguageModel, DbThread,    DeletePathTool, DiagnosticsTool, EditFileTool, FetchTool, FindPathTool, GrepTool,    ListDirectoryTool, MovePathTool, NowTool, OpenTool, ProjectSnapshot, ReadFileTool,    SystemPromptTemplate, Template, Templates, TerminalTool, ThinkingTool, WebSearchTool,};use acp_thread::{MentionUri, UserMessageId};use action_log::ActionLog; use agent_client_protocol as acp;use agent_settings::{    AgentProfileId, AgentProfileSettings, AgentSettings, CompletionMode,    SUMMARIZE_THREAD_DETAILED_PROMPT, SUMMARIZE_THREAD_PROMPT,};use anyhow::{Context as _, Result, anyhow};use chrono::{DateTime, Utc};use client::{ModelRequestUsage, RequestUsage, UserStore};use cloud_llm_client::{CompletionIntent, Plan, UsageLimit};use collections::{HashMap, HashSet, IndexMap};use fs::Fs;use futures::stream;use futures::{    FutureExt,    channel::{mpsc, oneshot},    future::Shared,    stream::FuturesUnordered,};use gpui::{    App, AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,};use language_model::{    LanguageModel, LanguageModelCompletionError, LanguageModelCompletionEvent, LanguageModelExt,    LanguageModelId, LanguageModelImage, LanguageModelProviderId, LanguageModelRegistry,    LanguageModelRequest, LanguageModelRequestMessage, LanguageModelRequestTool,    LanguageModelToolResult, LanguageModelToolResultContent, LanguageModelToolSchemaFormat,    LanguageModelToolUse, LanguageModelToolUseId, Role, SelectedModel, StopReason, TokenUsage,    ZED_CLOUD_PROVIDER_ID,};use project::Project;use prompt_store::ProjectContext;use schemars::{JsonSchema, Schema};use serde::{Deserialize, Serialize};use settings::{LanguageModelSelection, Settings, update_settings_file};use smol::stream::StreamExt;use std::{    collections::BTreeMap,    ops::RangeInclusive,    path::Path,    rc::Rc,    sync::Arc,    time::{Duration, Instant},};use std::{fmt::Write, path::PathBuf};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; /// 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, Clone, PartialEq, Eq, Serialize, Deserialize)]pub enum Message {    User(UserMessage),    Agent(AgentMessage),    Resume,} 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) => vec![message.to_request()],            Message::Agent(message) => message.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(),        }    }     pub fn role(&self) -> Role {        match self {            Message::User(_) | Message::Resume => Role::User,            Message::Agent(_) => Role::Assistant,        }    }} #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]pub struct UserMessage {    pub id: UserMessageId,    pub content: Vec<UserMessageContent>,} #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]pub enum UserMessageContent {    Text(String),    Mention { uri: MentionUri, content: String },    Image(LanguageModelImage),} impl UserMessage {    pub fn to_markdown(&self) -> String {        let mut markdown = String::from("## User\n\n");         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";         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();         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.to_string(),                                }                            )                            .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::TextThread { .. } => {                            write!(&mut thread_context, "\n{}\n", content).ok();                        }                        MentionUri::Rule { .. } => {                            write!(                                &mut rules_context,                                "\n{}",                                MarkdownCodeBlock {                                    tag: "",                                    text: content                                }                            )                            .ok();                        }                        MentionUri::Fetch { url } => {                            write!(&mut fetch_context, "\nFetch: {}\n\n{}", url, 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 thread_context.len() > OPEN_THREADS_TAG.len() {            thread_context.push_str("</threads>\n");            message                .content                .push(language_model::MessageContent::Text(thread_context));        }         if fetch_context.len() > OPEN_FETCH_TAG.len() {            fetch_context.push_str("</fetched_urls>\n");            message                .content                .push(language_model::MessageContent::Text(fetch_context));        }         if rules_context.len() > OPEN_RULES_TAG.len() {            rules_context.push_str("</user_rules>\n");            message                .content                .push(language_model::MessageContent::Text(rules_context));        }         if message.content.len() > len_before_context {            message.content.insert(                len_before_context,                language_model::MessageContent::Text(OPEN_CONTEXT.into()),            );            message                .content                .push(language_model::MessageContent::Text("</context>".into()));        }         message    }} fn codeblock_tag(full_path: &Path, line_range: Option<&RangeInclusive<u32>>) -> String {    let mut result = String::new();     if let Some(extension) = full_path.extension().and_then(|ext| ext.to_str()) {        let _ = write!(result, "{} ", extension);    }     let _ = write!(result, "{}", full_path.display());     if let Some(range) = line_range {        if range.start() == range.end() {            let _ = write!(result, ":{}", range.start() + 1);        } else {            let _ = write!(result, ":{}-{}", range.start() + 1, range.end() + 1);        }    }     result} impl AgentMessage {    pub fn to_markdown(&self) -> String {        let mut markdown = String::from("## Assistant\n\n");         for content in &self.content {            match content {                AgentMessageContent::Text(text) => {                    markdown.push_str(text);                    markdown.push('\n');                }                AgentMessageContent::Thinking { text, .. } => {                    markdown.push_str("<think>");                    markdown.push_str(text);                    markdown.push_str("</think>\n");                }                AgentMessageContent::RedactedThinking(_) => {                    markdown.push_str("<redacted_thinking />\n")                }                AgentMessageContent::ToolUse(tool_use) => {                    markdown.push_str(&format!(                        "**Tool Use**: {} (ID: {})\n",                        tool_use.name, tool_use.id                    ));                    markdown.push_str(&format!(                        "{}\n",                        MarkdownCodeBlock {                            tag: "json",                            text: &format!("{:#}", tool_use.input)                        }                    ));                }            }        }         for tool_result in self.tool_results.values() {            markdown.push_str(&format!(                "**Tool Result**: {} (ID: {})\n\n",                tool_result.tool_name, tool_result.tool_use_id            ));            if tool_result.is_error {                markdown.push_str("**ERROR:**\n");            }             match &tool_result.content {                LanguageModelToolResultContent::Text(text) => {                    writeln!(markdown, "{text}\n").ok();                }                LanguageModelToolResultContent::Image(_) => {                    writeln!(markdown, "<image />\n").ok();                }            }             if let Some(output) = tool_result.output.as_ref() {                writeln!(                    markdown,                    "**Debug Output**:\n\n```json\n{}\n```\n",                    serde_json::to_string_pretty(output).unwrap()                )                .unwrap();            }        }         markdown    }     pub fn to_request(&self) -> Vec<LanguageModelRequestMessage> {        let mut assistant_message = LanguageModelRequestMessage {            role: Role::Assistant,            content: Vec::with_capacity(self.content.len()),            cache: false,            reasoning_details: self.reasoning_details.clone(),        };        for chunk in &self.content {            match chunk {                AgentMessageContent::Text(text) => {                    assistant_message                        .content                        .push(language_model::MessageContent::Text(text.clone()));                }                AgentMessageContent::Thinking { text, signature } => {                    assistant_message                        .content                        .push(language_model::MessageContent::Thinking {                            text: text.clone(),                            signature: signature.clone(),                        });                }                AgentMessageContent::RedactedThinking(value) => {                    assistant_message.content.push(                        language_model::MessageContent::RedactedThinking(value.clone()),                    );                }                AgentMessageContent::ToolUse(tool_use) => {                    if self.tool_results.contains_key(&tool_use.id) {                        assistant_message                            .content                            .push(language_model::MessageContent::ToolUse(tool_use.clone()));                    }                }            };        }         let mut user_message = LanguageModelRequestMessage {            role: Role::User,            content: Vec::new(),            cache: false,            reasoning_details: None,        };         for tool_result in self.tool_results.values() {            let mut tool_result = tool_result.clone();            // Surprisingly, the API fails if we return an empty string here.            // It thinks we are sending a tool use without a tool result.            if tool_result.content.is_empty() {                tool_result.content = "<Tool returned an empty string>".into();            }            user_message                .content                .push(language_model::MessageContent::ToolResult(tool_result));        }         let mut messages = Vec::new();

Lines 1–500 of 2707.