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";const TOOL_CALL_INTERRUPTED_BY_FOLLOW_UP_MESSAGE: &str = "Permission denied: user sent a follow-up message instead of approving the tool call.";pub(crate) const FOLLOW_UP_PERMISSION_DENIED_OPTION_ID: &str = "follow_up_permission_denied";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));Lines 1–500 of 8683. Load more
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"; const TOOL_CALL_INTERRUPTED_BY_FOLLOW_UP_MESSAGE: &str = "Permission denied: user sent a follow-up message instead of approving the tool call."; pub(crate) const FOLLOW_UP_PERMISSION_DENIED_OPTION_ID: &str = "follow_up_permission_denied"; 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));