mod connection;mod diff;mod mention;mod terminal; /// Key used in ACP ToolCall meta to store the tool's programmatic name./// This is a workaround since ACP's ToolCall doesn't have a dedicated name field.pub const TOOL_NAME_META_KEY: &str = "tool_name"; /// Key used in ACP ToolCall meta to store the session id when a subagent is spawned.pub const SUBAGENT_SESSION_ID_META_KEY: &str = "subagent_session_id"; /// Helper to extract tool name from ACP metapub fn tool_name_from_meta(meta: &Option<acp::Meta>) -> Option<SharedString> { meta.as_ref() .and_then(|m| m.get(TOOL_NAME_META_KEY)) .and_then(|v| v.as_str()) .map(|s| SharedString::from(s.to_owned()))} /// Helper to extract subagent session id from ACP metapub fn subagent_session_id_from_meta(meta: &Option<acp::Meta>) -> Option<acp::SessionId> { meta.as_ref() .and_then(|m| m.get(SUBAGENT_SESSION_ID_META_KEY)) .and_then(|v| v.as_str()) .map(|s| acp::SessionId::from(s.to_string()))} /// Helper to create meta with tool namepub fn meta_with_tool_name(tool_name: &str) -> acp::Meta { acp::Meta::from_iter([(TOOL_NAME_META_KEY.into(), tool_name.into())])}use collections::HashSet;pub use connection::*;pub use diff::*;use language::language_settings::FormatOnSave;pub use mention::*;use project::lsp_store::{FormatTrigger, LspFormatTarget};use serde::{Deserialize, Serialize};use serde_json::to_string_pretty; use task::{Shell, ShellBuilder};pub use terminal::*; use action_log::{ActionLog, ActionLogTelemetry};use agent_client_protocol::{self as acp};use anyhow::{Context as _, Result, anyhow};use futures::{FutureExt, channel::oneshot, future::BoxFuture};use gpui::{AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};use itertools::Itertools;use language::{Anchor, Buffer, BufferSnapshot, LanguageRegistry, Point, ToPoint, text_diff};use markdown::Markdown;use project::{AgentLocation, Project, git_store::GitStoreCheckpoint};use std::collections::HashMap;use std::error::Error;use std::fmt::{Formatter, Write};use std::ops::Range;use std::process::ExitStatus;use std::rc::Rc;use std::time::{Duration, Instant};use std::{fmt::Display, mem, path::PathBuf, sync::Arc};use text::Bias;use ui::App;use util::{ResultExt, get_default_system_shell_preferring_bash, paths::PathStyle};use uuid::Uuid; #[derive(Debug)]pub struct UserMessage { pub id: Option<UserMessageId>, pub content: ContentBlock, pub chunks: Vec<acp::ContentBlock>, pub checkpoint: Option<Checkpoint>, pub indented: bool,} #[derive(Debug)]pub struct Checkpoint { git_checkpoint: GitStoreCheckpoint, pub show: bool,} impl UserMessage { fn to_markdown(&self, cx: &App) -> String { let mut markdown = String::new(); if self .checkpoint .as_ref() .is_some_and(|checkpoint| checkpoint.show) { writeln!(markdown, "## User (checkpoint)").unwrap(); } else { writeln!(markdown, "## User").unwrap(); } writeln!(markdown).unwrap(); writeln!(markdown, "{}", self.content.to_markdown(cx)).unwrap(); writeln!(markdown).unwrap(); markdown }} #[derive(Debug, PartialEq)]pub struct AssistantMessage { pub chunks: Vec<AssistantMessageChunk>, pub indented: bool,} impl AssistantMessage { pub fn to_markdown(&self, cx: &App) -> String { format!( "## Assistant\n\n{}\n\n", self.chunks .iter() .map(|chunk| chunk.to_markdown(cx)) .join("\n\n") ) }} #[derive(Debug, PartialEq)]pub enum AssistantMessageChunk { Message { block: ContentBlock }, Thought { block: ContentBlock },} impl AssistantMessageChunk { pub fn from_str( chunk: &str, language_registry: &Arc<LanguageRegistry>, path_style: PathStyle, cx: &mut App, ) -> Self { Self::Message { block: ContentBlock::new(chunk.into(), language_registry, path_style, cx), } } fn to_markdown(&self, cx: &App) -> String { match self { Self::Message { block } => block.to_markdown(cx).to_string(), Self::Thought { block } => { format!("<thinking>\n{}\n</thinking>", block.to_markdown(cx)) } } }} #[derive(Debug)]pub enum AgentThreadEntry { UserMessage(UserMessage), AssistantMessage(AssistantMessage), ToolCall(ToolCall),} impl AgentThreadEntry { pub fn is_indented(&self) -> bool { match self { Self::UserMessage(message) => message.indented, Self::AssistantMessage(message) => message.indented, Self::ToolCall(_) => false, } } pub fn to_markdown(&self, cx: &App) -> String { match self { Self::UserMessage(message) => message.to_markdown(cx), Self::AssistantMessage(message) => message.to_markdown(cx), Self::ToolCall(tool_call) => tool_call.to_markdown(cx), } } pub fn user_message(&self) -> Option<&UserMessage> { if let AgentThreadEntry::UserMessage(message) = self { Some(message) } else { None } } pub fn diffs(&self) -> impl Iterator<Item = &Entity<Diff>> { if let AgentThreadEntry::ToolCall(call) = self { itertools::Either::Left(call.diffs()) } else { itertools::Either::Right(std::iter::empty()) } } pub fn terminals(&self) -> impl Iterator<Item = &Entity<Terminal>> { if let AgentThreadEntry::ToolCall(call) = self { itertools::Either::Left(call.terminals()) } else { itertools::Either::Right(std::iter::empty()) } } pub fn location(&self, ix: usize) -> Option<(acp::ToolCallLocation, AgentLocation)> { if let AgentThreadEntry::ToolCall(ToolCall { locations, resolved_locations, .. }) = self { Some(( locations.get(ix)?.clone(), resolved_locations.get(ix)?.clone()?, )) } else { None } }} #[derive(Debug)]pub struct ToolCall { pub id: acp::ToolCallId, pub label: Entity<Markdown>, pub kind: acp::ToolKind, pub content: Vec<ToolCallContent>, pub status: ToolCallStatus, pub locations: Vec<acp::ToolCallLocation>, pub resolved_locations: Vec<Option<AgentLocation>>, pub raw_input: Option<serde_json::Value>, pub raw_input_markdown: Option<Entity<Markdown>>, pub raw_output: Option<serde_json::Value>, pub tool_name: Option<SharedString>, pub subagent_session_id: Option<acp::SessionId>,} impl ToolCall { fn from_acp( tool_call: acp::ToolCall, status: ToolCallStatus, language_registry: Arc<LanguageRegistry>, path_style: PathStyle, terminals: &HashMap<acp::TerminalId, Entity<Terminal>>, cx: &mut App, ) -> Result<Self> { let title = if tool_call.kind == acp::ToolKind::Execute { tool_call.title } else if let Some((first_line, _)) = tool_call.title.split_once("\n") { first_line.to_owned() + "…" } else { tool_call.title }; let mut content = Vec::with_capacity(tool_call.content.len()); for item in tool_call.content { if let Some(item) = ToolCallContent::from_acp( item, language_registry.clone(), path_style, terminals, cx, )? { content.push(item); } } let raw_input_markdown = tool_call .raw_input .as_ref() .and_then(|input| markdown_for_raw_output(input, &language_registry, cx)); let tool_name = tool_name_from_meta(&tool_call.meta); let subagent_session = subagent_session_id_from_meta(&tool_call.meta); let result = Self { id: tool_call.tool_call_id, label: cx .new(|cx| Markdown::new(title.into(), Some(language_registry.clone()), None, cx)), kind: tool_call.kind, content, locations: tool_call.locations, resolved_locations: Vec::default(), status, raw_input: tool_call.raw_input, raw_input_markdown, raw_output: tool_call.raw_output, tool_name, subagent_session_id: subagent_session, }; Ok(result) } fn update_fields( &mut self, fields: acp::ToolCallUpdateFields, meta: Option<acp::Meta>, language_registry: Arc<LanguageRegistry>, path_style: PathStyle, terminals: &HashMap<acp::TerminalId, Entity<Terminal>>, cx: &mut App, ) -> Result<()> { let acp::ToolCallUpdateFields { kind, status, title, content, locations, raw_input, raw_output, .. } = fields; if let Some(kind) = kind { self.kind = kind; } if let Some(status) = status { self.status = status.into(); } if let Some(subagent_session_id) = subagent_session_id_from_meta(&meta) { self.subagent_session_id = Some(subagent_session_id); } if let Some(title) = title { if self.kind == acp::ToolKind::Execute { for terminal in self.terminals() { terminal.update(cx, |terminal, cx| { terminal.update_command_label(&title, cx); }); } } self.label.update(cx, |label, cx| { if self.kind == acp::ToolKind::Execute { label.replace(title, cx); } else if let Some((first_line, _)) = title.split_once("\n") { label.replace(first_line.to_owned() + "…", cx); } else { label.replace(title, cx); } }); } if let Some(content) = content { let mut new_content_len = content.len(); let mut content = content.into_iter(); // Reuse existing content if we can for (old, new) in self.content.iter_mut().zip(content.by_ref()) { let valid_content = old.update_from_acp(new, language_registry.clone(), path_style, terminals, cx)?; if !valid_content { new_content_len -= 1; } } for new in content { if let Some(new) = ToolCallContent::from_acp( new, language_registry.clone(), path_style, terminals, cx, )? { self.content.push(new); } else { new_content_len -= 1; } } self.content.truncate(new_content_len); } if let Some(locations) = locations { self.locations = locations; } if let Some(raw_input) = raw_input { self.raw_input_markdown = markdown_for_raw_output(&raw_input, &language_registry, cx); self.raw_input = Some(raw_input); } if let Some(raw_output) = raw_output { if self.content.is_empty() && let Some(markdown) = markdown_for_raw_output(&raw_output, &language_registry, cx) { self.content .push(ToolCallContent::ContentBlock(ContentBlock::Markdown { markdown, })); } self.raw_output = Some(raw_output); } Ok(()) } pub fn diffs(&self) -> impl Iterator<Item = &Entity<Diff>> { self.content.iter().filter_map(|content| match content { ToolCallContent::Diff(diff) => Some(diff), ToolCallContent::ContentBlock(_) => None, ToolCallContent::Terminal(_) => None, }) } pub fn terminals(&self) -> impl Iterator<Item = &Entity<Terminal>> { self.content.iter().filter_map(|content| match content { ToolCallContent::Terminal(terminal) => Some(terminal), ToolCallContent::ContentBlock(_) => None, ToolCallContent::Diff(_) => None, }) } pub fn is_subagent(&self) -> bool { self.tool_name.as_ref().is_some_and(|s| s == "subagent") || self.subagent_session_id.is_some() } pub fn to_markdown(&self, cx: &App) -> String { let mut markdown = format!( "**Tool Call: {}**\nStatus: {}\n\n", self.label.read(cx).source(), self.status ); for content in &self.content { markdown.push_str(content.to_markdown(cx).as_str()); markdown.push_str("\n\n"); } markdown } async fn resolve_location( location: acp::ToolCallLocation, project: WeakEntity<Project>, cx: &mut AsyncApp, ) -> Option<ResolvedLocation> { let buffer = project .update(cx, |project, cx| { project .project_path_for_absolute_path(&location.path, cx) .map(|path| project.open_buffer(path, cx)) }) .ok()??; let buffer = buffer.await.log_err()?; let position = buffer.update(cx, |buffer, _| { let snapshot = buffer.snapshot(); if let Some(row) = location.line { let column = snapshot.indent_size_for_line(row).len; let point = snapshot.clip_point(Point::new(row, column), Bias::Left); snapshot.anchor_before(point) } else { Anchor::min_for_buffer(snapshot.remote_id()) } }); Some(ResolvedLocation { buffer, position }) } fn resolve_locations( &self, project: Entity<Project>, cx: &mut App, ) -> Task<Vec<Option<ResolvedLocation>>> { let locations = self.locations.clone(); project.update(cx, |_, cx| { cx.spawn(async move |project, cx| { let mut new_locations = Vec::new(); for location in locations { new_locations.push(Self::resolve_location(location, project.clone(), cx).await); } new_locations }) }) }} // Separate so we can hold a strong reference to the buffer// for saving on the thread#[derive(Clone, Debug, PartialEq, Eq)]struct ResolvedLocation { buffer: Entity<Buffer>, position: Anchor,} impl From<&ResolvedLocation> for AgentLocation { fn from(value: &ResolvedLocation) -> Self { Self { buffer: value.buffer.downgrade(), position: value.position, } }} #[derive(Debug)]pub enum ToolCallStatus { /// The tool call hasn't started running yet, but we start showing it to /// the user. Pending, /// The tool call is waiting for confirmation from the user. WaitingForConfirmation { options: PermissionOptions, respond_tx: oneshot::Sender<acp::PermissionOptionId>, }, /// The tool call is currently running. InProgress, /// The tool call completed successfully. Completed, /// The tool call failed. Failed, /// The user rejected the tool call. Rejected, /// The user canceled generation so the tool call was canceled.Lines 1–500 of 4583. Load more
mod connection; mod diff; mod mention; mod terminal; /// Key used in ACP ToolCall meta to store the tool's programmatic name. /// This is a workaround since ACP's ToolCall doesn't have a dedicated name field. pub const TOOL_NAME_META_KEY: &str = "tool_name"; /// Key used in ACP ToolCall meta to store the session id when a subagent is spawned. pub const SUBAGENT_SESSION_ID_META_KEY: &str = "subagent_session_id"; /// Helper to extract tool name from ACP meta pub fn tool_name_from_meta(meta: &Option<acp::Meta>) -> Option<SharedString> { meta.as_ref() .and_then(|m| m.get(TOOL_NAME_META_KEY)) .and_then(|v| v.as_str()) .map(|s| SharedString::from(s.to_owned())) } /// Helper to extract subagent session id from ACP meta pub fn subagent_session_id_from_meta(meta: &Option<acp::Meta>) -> Option<acp::SessionId> { meta.as_ref() .and_then(|m| m.get(SUBAGENT_SESSION_ID_META_KEY)) .and_then(|v| v.as_str()) .map(|s| acp::SessionId::from(s.to_string())) } /// Helper to create meta with tool name pub fn meta_with_tool_name(tool_name: &str) -> acp::Meta { acp::Meta::from_iter([(TOOL_NAME_META_KEY.into(), tool_name.into())]) } use collections::HashSet; pub use connection::*; pub use diff::*; use language::language_settings::FormatOnSave; pub use mention::*; use project::lsp_store::{FormatTrigger, LspFormatTarget}; use serde::{Deserialize, Serialize}; use serde_json::to_string_pretty; use task::{Shell, ShellBuilder}; pub use terminal::*; use action_log::{ActionLog, ActionLogTelemetry}; use agent_client_protocol::{self as acp}; use anyhow::{Context as _, Result, anyhow}; use futures::{FutureExt, channel::oneshot, future::BoxFuture}; use gpui::{AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity}; use itertools::Itertools; use language::{Anchor, Buffer, BufferSnapshot, LanguageRegistry, Point, ToPoint, text_diff}; use markdown::Markdown; use project::{AgentLocation, Project, git_store::GitStoreCheckpoint}; use std::collections::HashMap; use std::error::Error; use std::fmt::{Formatter, Write}; use std::ops::Range; use std::process::ExitStatus; use std::rc::Rc; use std::time::{Duration, Instant}; use std::{fmt::Display, mem, path::PathBuf, sync::Arc}; use text::Bias; use ui::App; use util::{ResultExt, get_default_system_shell_preferring_bash, paths::PathStyle}; use uuid::Uuid; #[derive(Debug)] pub struct UserMessage { pub id: Option<UserMessageId>, pub content: ContentBlock, pub chunks: Vec<acp::ContentBlock>, pub checkpoint: Option<Checkpoint>, pub indented: bool, } #[derive(Debug)] pub struct Checkpoint { git_checkpoint: GitStoreCheckpoint, pub show: bool, } impl UserMessage { fn to_markdown(&self, cx: &App) -> String { let mut markdown = String::new(); if self .checkpoint .as_ref() .is_some_and(|checkpoint| checkpoint.show) { writeln!(markdown, "## User (checkpoint)").unwrap(); } else { writeln!(markdown, "## User").unwrap(); } writeln!(markdown).unwrap(); writeln!(markdown, "{}", self.content.to_markdown(cx)).unwrap(); writeln!(markdown).unwrap(); markdown } } #[derive(Debug, PartialEq)] pub struct AssistantMessage { pub chunks: Vec<AssistantMessageChunk>, pub indented: bool, } impl AssistantMessage { pub fn to_markdown(&self, cx: &App) -> String { format!( "## Assistant\n\n{}\n\n", self.chunks .iter() .map(|chunk| chunk.to_markdown(cx)) .join("\n\n") ) } } #[derive(Debug, PartialEq)] pub enum AssistantMessageChunk { Message { block: ContentBlock }, Thought { block: ContentBlock }, } impl AssistantMessageChunk { pub fn from_str( chunk: &str, language_registry: &Arc<LanguageRegistry>, path_style: PathStyle, cx: &mut App, ) -> Self { Self::Message { block: ContentBlock::new(chunk.into(), language_registry, path_style, cx), } } fn to_markdown(&self, cx: &App) -> String { match self { Self::Message { block } => block.to_markdown(cx).to_string(), Self::Thought { block } => { format!("<thinking>\n{}\n</thinking>", block.to_markdown(cx)) } } } } #[derive(Debug)] pub enum AgentThreadEntry { UserMessage(UserMessage), AssistantMessage(AssistantMessage), ToolCall(ToolCall), } impl AgentThreadEntry { pub fn is_indented(&self) -> bool { match self { Self::UserMessage(message) => message.indented, Self::AssistantMessage(message) => message.indented, Self::ToolCall(_) => false, } } pub fn to_markdown(&self, cx: &App) -> String { match self { Self::UserMessage(message) => message.to_markdown(cx), Self::AssistantMessage(message) => message.to_markdown(cx), Self::ToolCall(tool_call) => tool_call.to_markdown(cx), } } pub fn user_message(&self) -> Option<&UserMessage> { if let AgentThreadEntry::UserMessage(message) = self { Some(message) } else { None } } pub fn diffs(&self) -> impl Iterator<Item = &Entity<Diff>> { if let AgentThreadEntry::ToolCall(call) = self { itertools::Either::Left(call.diffs()) } else { itertools::Either::Right(std::iter::empty()) } } pub fn terminals(&self) -> impl Iterator<Item = &Entity<Terminal>> { if let AgentThreadEntry::ToolCall(call) = self { itertools::Either::Left(call.terminals()) } else { itertools::Either::Right(std::iter::empty()) } } pub fn location(&self, ix: usize) -> Option<(acp::ToolCallLocation, AgentLocation)> { if let AgentThreadEntry::ToolCall(ToolCall { locations, resolved_locations, .. }) = self { Some(( locations.get(ix)?.clone(), resolved_locations.get(ix)?.clone()?, )) } else { None } } } #[derive(Debug)] pub struct ToolCall { pub id: acp::ToolCallId, pub label: Entity<Markdown>, pub kind: acp::ToolKind, pub content: Vec<ToolCallContent>, pub status: ToolCallStatus, pub locations: Vec<acp::ToolCallLocation>, pub resolved_locations: Vec<Option<AgentLocation>>, pub raw_input: Option<serde_json::Value>, pub raw_input_markdown: Option<Entity<Markdown>>, pub raw_output: Option<serde_json::Value>, pub tool_name: Option<SharedString>, pub subagent_session_id: Option<acp::SessionId>, } impl ToolCall { fn from_acp( tool_call: acp::ToolCall, status: ToolCallStatus, language_registry: Arc<LanguageRegistry>, path_style: PathStyle, terminals: &HashMap<acp::TerminalId, Entity<Terminal>>, cx: &mut App, ) -> Result<Self> { let title = if tool_call.kind == acp::ToolKind::Execute { tool_call.title } else if let Some((first_line, _)) = tool_call.title.split_once("\n") { first_line.to_owned() + "…" } else { tool_call.title }; let mut content = Vec::with_capacity(tool_call.content.len()); for item in tool_call.content { if let Some(item) = ToolCallContent::from_acp( item, language_registry.clone(), path_style, terminals, cx, )? { content.push(item); } } let raw_input_markdown = tool_call .raw_input .as_ref() .and_then(|input| markdown_for_raw_output(input, &language_registry, cx)); let tool_name = tool_name_from_meta(&tool_call.meta); let subagent_session = subagent_session_id_from_meta(&tool_call.meta); let result = Self { id: tool_call.tool_call_id, label: cx .new(|cx| Markdown::new(title.into(), Some(language_registry.clone()), None, cx)), kind: tool_call.kind, content, locations: tool_call.locations, resolved_locations: Vec::default(), status, raw_input: tool_call.raw_input, raw_input_markdown, raw_output: tool_call.raw_output, tool_name, subagent_session_id: subagent_session, }; Ok(result) } fn update_fields( &mut self, fields: acp::ToolCallUpdateFields, meta: Option<acp::Meta>, language_registry: Arc<LanguageRegistry>, path_style: PathStyle, terminals: &HashMap<acp::TerminalId, Entity<Terminal>>, cx: &mut App, ) -> Result<()> { let acp::ToolCallUpdateFields { kind, status, title, content, locations, raw_input, raw_output, .. } = fields; if let Some(kind) = kind { self.kind = kind; } if let Some(status) = status { self.status = status.into(); } if let Some(subagent_session_id) = subagent_session_id_from_meta(&meta) { self.subagent_session_id = Some(subagent_session_id); } if let Some(title) = title { if self.kind == acp::ToolKind::Execute { for terminal in self.terminals() { terminal.update(cx, |terminal, cx| { terminal.update_command_label(&title, cx); }); } } self.label.update(cx, |label, cx| { if self.kind == acp::ToolKind::Execute { label.replace(title, cx); } else if let Some((first_line, _)) = title.split_once("\n") { label.replace(first_line.to_owned() + "…", cx); } else { label.replace(title, cx); } }); } if let Some(content) = content { let mut new_content_len = content.len(); let mut content = content.into_iter(); // Reuse existing content if we can for (old, new) in self.content.iter_mut().zip(content.by_ref()) { let valid_content = old.update_from_acp(new, language_registry.clone(), path_style, terminals, cx)?; if !valid_content { new_content_len -= 1; } } for new in content { if let Some(new) = ToolCallContent::from_acp( new, language_registry.clone(), path_style, terminals, cx, )? { self.content.push(new); } else { new_content_len -= 1; } } self.content.truncate(new_content_len); } if let Some(locations) = locations { self.locations = locations; } if let Some(raw_input) = raw_input { self.raw_input_markdown = markdown_for_raw_output(&raw_input, &language_registry, cx); self.raw_input = Some(raw_input); } if let Some(raw_output) = raw_output { if self.content.is_empty() && let Some(markdown) = markdown_for_raw_output(&raw_output, &language_registry, cx) { self.content .push(ToolCallContent::ContentBlock(ContentBlock::Markdown { markdown, })); } self.raw_output = Some(raw_output); } Ok(()) } pub fn diffs(&self) -> impl Iterator<Item = &Entity<Diff>> { self.content.iter().filter_map(|content| match content { ToolCallContent::Diff(diff) => Some(diff), ToolCallContent::ContentBlock(_) => None, ToolCallContent::Terminal(_) => None, }) } pub fn terminals(&self) -> impl Iterator<Item = &Entity<Terminal>> { self.content.iter().filter_map(|content| match content { ToolCallContent::Terminal(terminal) => Some(terminal), ToolCallContent::ContentBlock(_) => None, ToolCallContent::Diff(_) => None, }) } pub fn is_subagent(&self) -> bool { self.tool_name.as_ref().is_some_and(|s| s == "subagent") || self.subagent_session_id.is_some() } pub fn to_markdown(&self, cx: &App) -> String { let mut markdown = format!( "**Tool Call: {}**\nStatus: {}\n\n", self.label.read(cx).source(), self.status ); for content in &self.content { markdown.push_str(content.to_markdown(cx).as_str()); markdown.push_str("\n\n"); } markdown } async fn resolve_location( location: acp::ToolCallLocation, project: WeakEntity<Project>, cx: &mut AsyncApp, ) -> Option<ResolvedLocation> { let buffer = project .update(cx, |project, cx| { project .project_path_for_absolute_path(&location.path, cx) .map(|path| project.open_buffer(path, cx)) }) .ok()??; let buffer = buffer.await.log_err()?; let position = buffer.update(cx, |buffer, _| { let snapshot = buffer.snapshot(); if let Some(row) = location.line { let column = snapshot.indent_size_for_line(row).len; let point = snapshot.clip_point(Point::new(row, column), Bias::Left); snapshot.anchor_before(point) } else { Anchor::min_for_buffer(snapshot.remote_id()) } }); Some(ResolvedLocation { buffer, position }) } fn resolve_locations( &self, project: Entity<Project>, cx: &mut App, ) -> Task<Vec<Option<ResolvedLocation>>> { let locations = self.locations.clone(); project.update(cx, |_, cx| { cx.spawn(async move |project, cx| { let mut new_locations = Vec::new(); for location in locations { new_locations.push(Self::resolve_location(location, project.clone(), cx).await); } new_locations }) }) } } // Separate so we can hold a strong reference to the buffer // for saving on the thread #[derive(Clone, Debug, PartialEq, Eq)] struct ResolvedLocation { buffer: Entity<Buffer>, position: Anchor, } impl From<&ResolvedLocation> for AgentLocation { fn from(value: &ResolvedLocation) -> Self { Self { buffer: value.buffer.downgrade(), position: value.position, } } } #[derive(Debug)] pub enum ToolCallStatus { /// The tool call hasn't started running yet, but we start showing it to /// the user. Pending, /// The tool call is waiting for confirmation from the user. WaitingForConfirmation { options: PermissionOptions, respond_tx: oneshot::Sender<acp::PermissionOptionId>, }, /// The tool call is currently running. InProgress, /// The tool call completed successfully. Completed, /// The tool call failed. Failed, /// The user rejected the tool call. Rejected, /// The user canceled generation so the tool call was canceled.