acp_thread.rs

   1mod connection;
   2pub use connection::*;
   3
   4use agent_client_protocol as acp;
   5use agentic_coding_protocol as acp_old;
   6use anyhow::{Context as _, Result};
   7use assistant_tool::ActionLog;
   8use buffer_diff::BufferDiff;
   9use editor::{Bias, MultiBuffer, PathKey};
  10use futures::{FutureExt, channel::oneshot, future::BoxFuture};
  11use gpui::{AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};
  12use itertools::Itertools;
  13use language::{
  14    Anchor, Buffer, BufferSnapshot, Capability, LanguageRegistry, OffsetRangeExt as _, Point,
  15    text_diff,
  16};
  17use markdown::Markdown;
  18use project::{AgentLocation, Project};
  19use std::cell::RefCell;
  20use std::collections::HashMap;
  21use std::error::Error;
  22use std::fmt::Formatter;
  23use std::rc::Rc;
  24use std::{
  25    fmt::Display,
  26    mem,
  27    path::{Path, PathBuf},
  28    sync::Arc,
  29};
  30use ui::App;
  31use util::ResultExt;
  32
  33#[derive(Debug)]
  34pub struct UserMessage {
  35    pub content: ContentBlock,
  36}
  37
  38impl UserMessage {
  39    pub fn from_acp(
  40        message: impl IntoIterator<Item = acp::ContentBlock>,
  41        language_registry: Arc<LanguageRegistry>,
  42        cx: &mut App,
  43    ) -> Self {
  44        let mut content = ContentBlock::Empty;
  45        for chunk in message {
  46            content.append(chunk, &language_registry, cx)
  47        }
  48        Self { content: content }
  49    }
  50
  51    fn to_markdown(&self, cx: &App) -> String {
  52        format!("## User\n{}\n", self.content.to_markdown(cx))
  53    }
  54}
  55
  56#[derive(Debug)]
  57pub struct MentionPath<'a>(&'a Path);
  58
  59impl<'a> MentionPath<'a> {
  60    const PREFIX: &'static str = "@file:";
  61
  62    pub fn new(path: &'a Path) -> Self {
  63        MentionPath(path)
  64    }
  65
  66    pub fn try_parse(url: &'a str) -> Option<Self> {
  67        let path = url.strip_prefix(Self::PREFIX)?;
  68        Some(MentionPath(Path::new(path)))
  69    }
  70
  71    pub fn path(&self) -> &Path {
  72        self.0
  73    }
  74}
  75
  76impl Display for MentionPath<'_> {
  77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  78        write!(
  79            f,
  80            "[@{}]({}{})",
  81            self.0.file_name().unwrap_or_default().display(),
  82            Self::PREFIX,
  83            self.0.display()
  84        )
  85    }
  86}
  87
  88#[derive(Debug, PartialEq)]
  89pub struct AssistantMessage {
  90    pub chunks: Vec<AssistantMessageChunk>,
  91}
  92
  93impl AssistantMessage {
  94    pub fn to_markdown(&self, cx: &App) -> String {
  95        format!(
  96            "## Assistant\n\n{}\n\n",
  97            self.chunks
  98                .iter()
  99                .map(|chunk| chunk.to_markdown(cx))
 100                .join("\n\n")
 101        )
 102    }
 103}
 104
 105#[derive(Debug, PartialEq)]
 106pub enum AssistantMessageChunk {
 107    Message { block: ContentBlock },
 108    Thought { block: ContentBlock },
 109}
 110
 111impl AssistantMessageChunk {
 112    pub fn from_str(chunk: &str, language_registry: &Arc<LanguageRegistry>, cx: &mut App) -> Self {
 113        Self::Message {
 114            block: ContentBlock::new(chunk.into(), language_registry, cx),
 115        }
 116    }
 117
 118    fn to_markdown(&self, cx: &App) -> String {
 119        match self {
 120            Self::Message { block } => block.to_markdown(cx).to_string(),
 121            Self::Thought { block } => {
 122                format!("<thinking>\n{}\n</thinking>", block.to_markdown(cx))
 123            }
 124        }
 125    }
 126}
 127
 128#[derive(Debug)]
 129pub enum AgentThreadEntry {
 130    UserMessage(UserMessage),
 131    AssistantMessage(AssistantMessage),
 132    ToolCall(ToolCall),
 133}
 134
 135impl AgentThreadEntry {
 136    fn to_markdown(&self, cx: &App) -> String {
 137        match self {
 138            Self::UserMessage(message) => message.to_markdown(cx),
 139            Self::AssistantMessage(message) => message.to_markdown(cx),
 140            Self::ToolCall(too_call) => too_call.to_markdown(cx),
 141        }
 142    }
 143
 144    // todo! return all diffs?
 145    pub fn first_diff(&self) -> Option<&Diff> {
 146        if let AgentThreadEntry::ToolCall(call) = self {
 147            call.first_diff()
 148        } else {
 149            None
 150        }
 151    }
 152
 153    pub fn locations(&self) -> Option<&[acp::ToolCallLocation]> {
 154        if let AgentThreadEntry::ToolCall(ToolCall { locations, .. }) = self {
 155            Some(locations)
 156        } else {
 157            None
 158        }
 159    }
 160}
 161
 162#[derive(Debug)]
 163pub struct ToolCall {
 164    pub id: acp::ToolCallId,
 165    pub label: Entity<Markdown>,
 166    pub kind: acp::ToolKind,
 167    pub content: Vec<ToolCallContent>,
 168    pub status: ToolCallStatus,
 169    pub locations: Vec<acp::ToolCallLocation>,
 170}
 171
 172impl ToolCall {
 173    fn from_acp(
 174        tool_call: acp::ToolCall,
 175        status: ToolCallStatus,
 176        language_registry: Arc<LanguageRegistry>,
 177        cx: &mut App,
 178    ) -> Self {
 179        Self {
 180            id: tool_call.id,
 181            label: cx.new(|cx| {
 182                Markdown::new(
 183                    tool_call.label.into(),
 184                    Some(language_registry.clone()),
 185                    None,
 186                    cx,
 187                )
 188            }),
 189            kind: tool_call.kind,
 190            content: tool_call
 191                .content
 192                .into_iter()
 193                .map(|content| ToolCallContent::from_acp(content, language_registry.clone(), cx))
 194                .collect(),
 195            locations: tool_call.locations,
 196            status,
 197        }
 198    }
 199
 200    pub fn first_diff(&self) -> Option<&Diff> {
 201        self.content.iter().find_map(|content| match content {
 202            ToolCallContent::ContentBlock { .. } => None,
 203            ToolCallContent::Diff { diff } => Some(diff),
 204        })
 205    }
 206
 207    fn to_markdown(&self, cx: &App) -> String {
 208        let mut markdown = format!(
 209            "**Tool Call: {}**\nStatus: {}\n\n",
 210            self.label.read(cx).source(),
 211            self.status
 212        );
 213        for content in &self.content {
 214            markdown.push_str(content.to_markdown(cx).as_str());
 215            markdown.push_str("\n\n");
 216        }
 217        markdown
 218    }
 219}
 220
 221#[derive(Debug)]
 222pub enum ToolCallStatus {
 223    WaitingForConfirmation {
 224        options: Vec<acp::PermissionOption>,
 225        respond_tx: oneshot::Sender<acp::PermissionOptionId>,
 226    },
 227    Allowed {
 228        status: acp::ToolCallStatus,
 229    },
 230    Rejected,
 231    Canceled,
 232}
 233
 234impl Display for ToolCallStatus {
 235    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
 236        write!(
 237            f,
 238            "{}",
 239            match self {
 240                ToolCallStatus::WaitingForConfirmation { .. } => "Waiting for confirmation",
 241                ToolCallStatus::Allowed { status } => match status {
 242                    acp::ToolCallStatus::InProgress => "In Progress",
 243                    acp::ToolCallStatus::Completed => "Completed",
 244                    acp::ToolCallStatus::Failed => "Failed",
 245                },
 246                ToolCallStatus::Rejected => "Rejected",
 247                ToolCallStatus::Canceled => "Canceled",
 248            }
 249        )
 250    }
 251}
 252
 253#[derive(Debug, PartialEq, Clone)]
 254pub enum ContentBlock {
 255    Empty,
 256    Markdown { markdown: Entity<Markdown> },
 257}
 258
 259impl ContentBlock {
 260    pub fn new(
 261        block: acp::ContentBlock,
 262        language_registry: &Arc<LanguageRegistry>,
 263        cx: &mut App,
 264    ) -> Self {
 265        let mut this = Self::Empty;
 266        this.append(block, language_registry, cx);
 267        this
 268    }
 269
 270    pub fn new_combined(
 271        blocks: impl IntoIterator<Item = acp::ContentBlock>,
 272        language_registry: Arc<LanguageRegistry>,
 273        cx: &mut App,
 274    ) -> Self {
 275        let mut this = Self::Empty;
 276        for block in blocks {
 277            this.append(block, &language_registry, cx);
 278        }
 279        this
 280    }
 281
 282    pub fn append(
 283        &mut self,
 284        block: acp::ContentBlock,
 285        language_registry: &Arc<LanguageRegistry>,
 286        cx: &mut App,
 287    ) {
 288        let new_content = match block {
 289            acp::ContentBlock::Text(text_content) => text_content.text.clone(),
 290            acp::ContentBlock::ResourceLink(resource_link) => {
 291                if let Some(path) = resource_link.uri.strip_prefix("file://") {
 292                    format!("{}", MentionPath(path.as_ref()))
 293                } else {
 294                    resource_link.uri.clone()
 295                }
 296            }
 297            acp::ContentBlock::Image(_)
 298            | acp::ContentBlock::Audio(_)
 299            | acp::ContentBlock::Resource(_) => String::new(),
 300        };
 301
 302        match self {
 303            ContentBlock::Empty => {
 304                *self = ContentBlock::Markdown {
 305                    markdown: cx.new(|cx| {
 306                        Markdown::new(
 307                            new_content.into(),
 308                            Some(language_registry.clone()),
 309                            None,
 310                            cx,
 311                        )
 312                    }),
 313                };
 314            }
 315            ContentBlock::Markdown { markdown } => {
 316                markdown.update(cx, |markdown, cx| markdown.append(&new_content, cx));
 317            }
 318        }
 319    }
 320
 321    fn to_markdown<'a>(&'a self, cx: &'a App) -> &'a str {
 322        match self {
 323            ContentBlock::Empty => "",
 324            ContentBlock::Markdown { markdown } => markdown.read(cx).source(),
 325        }
 326    }
 327
 328    pub fn markdown(&self) -> Option<&Entity<Markdown>> {
 329        match self {
 330            ContentBlock::Empty => None,
 331            ContentBlock::Markdown { markdown } => Some(markdown),
 332        }
 333    }
 334}
 335
 336#[derive(Debug)]
 337pub enum ToolCallContent {
 338    ContentBlock { content: ContentBlock },
 339    Diff { diff: Diff },
 340}
 341
 342impl ToolCallContent {
 343    pub fn from_acp(
 344        content: acp::ToolCallContent,
 345        language_registry: Arc<LanguageRegistry>,
 346        cx: &mut App,
 347    ) -> Self {
 348        match content {
 349            acp::ToolCallContent::ContentBlock { content } => Self::ContentBlock {
 350                content: ContentBlock::new(content, &language_registry, cx),
 351            },
 352            acp::ToolCallContent::Diff { diff } => Self::Diff {
 353                diff: Diff::from_acp(diff, language_registry, cx),
 354            },
 355        }
 356    }
 357
 358    pub fn to_markdown(&self, cx: &App) -> String {
 359        match self {
 360            Self::ContentBlock { content } => content.to_markdown(cx).to_string(),
 361            Self::Diff { diff } => diff.to_markdown(cx),
 362        }
 363    }
 364}
 365
 366#[derive(Debug)]
 367pub struct Diff {
 368    pub multibuffer: Entity<MultiBuffer>,
 369    pub path: PathBuf,
 370    pub new_buffer: Entity<Buffer>,
 371    pub old_buffer: Entity<Buffer>,
 372    _task: Task<Result<()>>,
 373}
 374
 375impl Diff {
 376    pub fn from_acp(
 377        diff: acp::Diff,
 378        language_registry: Arc<LanguageRegistry>,
 379        cx: &mut App,
 380    ) -> Self {
 381        let acp::Diff {
 382            path,
 383            old_text,
 384            new_text,
 385        } = diff;
 386
 387        let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly));
 388
 389        let new_buffer = cx.new(|cx| Buffer::local(new_text, cx));
 390        let old_buffer = cx.new(|cx| Buffer::local(old_text.unwrap_or("".into()), cx));
 391        let new_buffer_snapshot = new_buffer.read(cx).text_snapshot();
 392        let old_buffer_snapshot = old_buffer.read(cx).snapshot();
 393        let buffer_diff = cx.new(|cx| BufferDiff::new(&new_buffer_snapshot, cx));
 394        let diff_task = buffer_diff.update(cx, |diff, cx| {
 395            diff.set_base_text(
 396                old_buffer_snapshot,
 397                Some(language_registry.clone()),
 398                new_buffer_snapshot,
 399                cx,
 400            )
 401        });
 402
 403        let task = cx.spawn({
 404            let multibuffer = multibuffer.clone();
 405            let path = path.clone();
 406            let new_buffer = new_buffer.clone();
 407            async move |cx| {
 408                diff_task.await?;
 409
 410                multibuffer
 411                    .update(cx, |multibuffer, cx| {
 412                        let hunk_ranges = {
 413                            let buffer = new_buffer.read(cx);
 414                            let diff = buffer_diff.read(cx);
 415                            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, cx)
 416                                .map(|diff_hunk| diff_hunk.buffer_range.to_point(&buffer))
 417                                .collect::<Vec<_>>()
 418                        };
 419
 420                        multibuffer.set_excerpts_for_path(
 421                            PathKey::for_buffer(&new_buffer, cx),
 422                            new_buffer.clone(),
 423                            hunk_ranges,
 424                            editor::DEFAULT_MULTIBUFFER_CONTEXT,
 425                            cx,
 426                        );
 427                        multibuffer.add_diff(buffer_diff.clone(), cx);
 428                    })
 429                    .log_err();
 430
 431                if let Some(language) = language_registry
 432                    .language_for_file_path(&path)
 433                    .await
 434                    .log_err()
 435                {
 436                    new_buffer.update(cx, |buffer, cx| buffer.set_language(Some(language), cx))?;
 437                }
 438
 439                anyhow::Ok(())
 440            }
 441        });
 442
 443        Self {
 444            multibuffer,
 445            path,
 446            new_buffer,
 447            old_buffer,
 448            _task: task,
 449        }
 450    }
 451
 452    fn to_markdown(&self, cx: &App) -> String {
 453        let buffer_text = self
 454            .multibuffer
 455            .read(cx)
 456            .all_buffers()
 457            .iter()
 458            .map(|buffer| buffer.read(cx).text())
 459            .join("\n");
 460        format!("Diff: {}\n```\n{}\n```\n", self.path.display(), buffer_text)
 461    }
 462}
 463
 464#[derive(Debug, Default)]
 465pub struct Plan {
 466    pub entries: Vec<PlanEntry>,
 467}
 468
 469#[derive(Debug)]
 470pub struct PlanStats<'a> {
 471    pub in_progress_entry: Option<&'a PlanEntry>,
 472    pub pending: u32,
 473    pub completed: u32,
 474}
 475
 476impl Plan {
 477    pub fn is_empty(&self) -> bool {
 478        self.entries.is_empty()
 479    }
 480
 481    pub fn stats(&self) -> PlanStats<'_> {
 482        let mut stats = PlanStats {
 483            in_progress_entry: None,
 484            pending: 0,
 485            completed: 0,
 486        };
 487
 488        for entry in &self.entries {
 489            match &entry.status {
 490                acp::PlanEntryStatus::Pending => {
 491                    stats.pending += 1;
 492                }
 493                acp::PlanEntryStatus::InProgress => {
 494                    stats.in_progress_entry = stats.in_progress_entry.or(Some(entry));
 495                }
 496                acp::PlanEntryStatus::Completed => {
 497                    stats.completed += 1;
 498                }
 499            }
 500        }
 501
 502        stats
 503    }
 504}
 505
 506#[derive(Debug)]
 507pub struct PlanEntry {
 508    pub content: Entity<Markdown>,
 509    pub priority: acp::PlanEntryPriority,
 510    pub status: acp::PlanEntryStatus,
 511}
 512
 513impl PlanEntry {
 514    pub fn from_acp(entry: acp::PlanEntry, cx: &mut App) -> Self {
 515        Self {
 516            content: cx.new(|cx| Markdown::new_text(entry.content.into(), cx)),
 517            priority: entry.priority,
 518            status: entry.status,
 519        }
 520    }
 521}
 522
 523pub struct AcpThread {
 524    title: SharedString,
 525    entries: Vec<AgentThreadEntry>,
 526    plan: Plan,
 527    project: Entity<Project>,
 528    action_log: Entity<ActionLog>,
 529    shared_buffers: HashMap<Entity<Buffer>, BufferSnapshot>,
 530    send_task: Option<Task<()>>,
 531    connection: Rc<dyn AgentConnection>,
 532    session_id: acp::SessionId,
 533}
 534
 535pub enum AcpThreadEvent {
 536    NewEntry,
 537    EntryUpdated(usize),
 538}
 539
 540impl EventEmitter<AcpThreadEvent> for AcpThread {}
 541
 542#[derive(PartialEq, Eq)]
 543pub enum ThreadStatus {
 544    Idle,
 545    WaitingForToolConfirmation,
 546    Generating,
 547}
 548
 549#[derive(Debug, Clone)]
 550pub enum LoadError {
 551    Unsupported {
 552        error_message: SharedString,
 553        upgrade_message: SharedString,
 554        upgrade_command: String,
 555    },
 556    Exited(i32),
 557    Other(SharedString),
 558}
 559
 560impl Display for LoadError {
 561    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
 562        match self {
 563            LoadError::Unsupported { error_message, .. } => write!(f, "{}", error_message),
 564            LoadError::Exited(status) => write!(f, "Server exited with status {}", status),
 565            LoadError::Other(msg) => write!(f, "{}", msg),
 566        }
 567    }
 568}
 569
 570impl Error for LoadError {}
 571
 572impl AcpThread {
 573    pub fn new(
 574        connection: Rc<dyn AgentConnection>,
 575        // todo! remove me
 576        title: SharedString,
 577        project: Entity<Project>,
 578        session_id: acp::SessionId,
 579        cx: &mut Context<Self>,
 580    ) -> Self {
 581        let action_log = cx.new(|_| ActionLog::new(project.clone()));
 582
 583        Self {
 584            action_log,
 585            shared_buffers: Default::default(),
 586            entries: Default::default(),
 587            plan: Default::default(),
 588            title,
 589            project,
 590            send_task: None,
 591            connection,
 592            session_id,
 593        }
 594    }
 595
 596    pub fn action_log(&self) -> &Entity<ActionLog> {
 597        &self.action_log
 598    }
 599
 600    pub fn project(&self) -> &Entity<Project> {
 601        &self.project
 602    }
 603
 604    pub fn title(&self) -> SharedString {
 605        self.title.clone()
 606    }
 607
 608    pub fn entries(&self) -> &[AgentThreadEntry] {
 609        &self.entries
 610    }
 611
 612    pub fn status(&self) -> ThreadStatus {
 613        if self.send_task.is_some() {
 614            if self.waiting_for_tool_confirmation() {
 615                ThreadStatus::WaitingForToolConfirmation
 616            } else {
 617                ThreadStatus::Generating
 618            }
 619        } else {
 620            ThreadStatus::Idle
 621        }
 622    }
 623
 624    pub fn has_pending_edit_tool_calls(&self) -> bool {
 625        for entry in self.entries.iter().rev() {
 626            match entry {
 627                AgentThreadEntry::UserMessage(_) => return false,
 628                AgentThreadEntry::ToolCall(call) if call.first_diff().is_some() => return true,
 629                AgentThreadEntry::ToolCall(_) | AgentThreadEntry::AssistantMessage(_) => {}
 630            }
 631        }
 632
 633        false
 634    }
 635
 636    pub fn push_entry(&mut self, entry: AgentThreadEntry, cx: &mut Context<Self>) {
 637        self.entries.push(entry);
 638        cx.emit(AcpThreadEvent::NewEntry);
 639    }
 640
 641    pub fn push_assistant_chunk(
 642        &mut self,
 643        chunk: acp::ContentBlock,
 644        is_thought: bool,
 645        cx: &mut Context<Self>,
 646    ) {
 647        let language_registry = self.project.read(cx).languages().clone();
 648        let entries_len = self.entries.len();
 649        if let Some(last_entry) = self.entries.last_mut()
 650            && let AgentThreadEntry::AssistantMessage(AssistantMessage { chunks }) = last_entry
 651        {
 652            cx.emit(AcpThreadEvent::EntryUpdated(entries_len - 1));
 653            match (chunks.last_mut(), is_thought) {
 654                (Some(AssistantMessageChunk::Message { block }), false)
 655                | (Some(AssistantMessageChunk::Thought { block }), true) => {
 656                    block.append(chunk, &language_registry, cx)
 657                }
 658                _ => {
 659                    let block = ContentBlock::new(chunk, &language_registry, cx);
 660                    if is_thought {
 661                        chunks.push(AssistantMessageChunk::Thought { block })
 662                    } else {
 663                        chunks.push(AssistantMessageChunk::Message { block })
 664                    }
 665                }
 666            }
 667        } else {
 668            let block = ContentBlock::new(chunk, &language_registry, cx);
 669            let chunk = if is_thought {
 670                AssistantMessageChunk::Thought { block }
 671            } else {
 672                AssistantMessageChunk::Message { block }
 673            };
 674
 675            self.push_entry(
 676                AgentThreadEntry::AssistantMessage(AssistantMessage {
 677                    chunks: vec![chunk],
 678                }),
 679                cx,
 680            );
 681        }
 682    }
 683
 684    pub fn update_tool_call(
 685        &mut self,
 686        id: acp::ToolCallId,
 687        status: acp::ToolCallStatus,
 688        content: Option<Vec<acp::ToolCallContent>>,
 689        cx: &mut Context<Self>,
 690    ) -> Result<()> {
 691        let languages = self.project.read(cx).languages().clone();
 692        let (ix, current_call) = self.tool_call_mut(&id).context("Tool call not found")?;
 693
 694        if let Some(content) = content {
 695            current_call.content = content
 696                .into_iter()
 697                .map(|chunk| ToolCallContent::from_acp(chunk, languages.clone(), cx))
 698                .collect();
 699        }
 700        current_call.status = ToolCallStatus::Allowed { status };
 701
 702        cx.emit(AcpThreadEvent::EntryUpdated(ix));
 703
 704        Ok(())
 705    }
 706
 707    /// Updates a tool call if id matches an existing entry, otherwise inserts a new one.
 708    pub fn upsert_tool_call(&mut self, tool_call: acp::ToolCall, cx: &mut Context<Self>) {
 709        let status = ToolCallStatus::Allowed {
 710            status: tool_call.status,
 711        };
 712        self.upsert_tool_call_inner(tool_call, status, cx)
 713    }
 714
 715    pub fn upsert_tool_call_inner(
 716        &mut self,
 717        tool_call: acp::ToolCall,
 718        status: ToolCallStatus,
 719        cx: &mut Context<Self>,
 720    ) {
 721        let language_registry = self.project.read(cx).languages().clone();
 722        let call = ToolCall::from_acp(tool_call, status, language_registry, cx);
 723
 724        let location = call.locations.last().cloned();
 725
 726        if let Some((ix, current_call)) = self.tool_call_mut(&call.id) {
 727            *current_call = call;
 728
 729            cx.emit(AcpThreadEvent::EntryUpdated(ix));
 730        } else {
 731            self.push_entry(AgentThreadEntry::ToolCall(call), cx);
 732        }
 733
 734        if let Some(location) = location {
 735            self.set_project_location(location, cx)
 736        }
 737    }
 738
 739    fn tool_call_mut(&mut self, id: &acp::ToolCallId) -> Option<(usize, &mut ToolCall)> {
 740        // todo! use map
 741        self.entries
 742            .iter_mut()
 743            .enumerate()
 744            .rev()
 745            .find_map(|(index, tool_call)| {
 746                if let AgentThreadEntry::ToolCall(tool_call) = tool_call
 747                    && &tool_call.id == id
 748                {
 749                    Some((index, tool_call))
 750                } else {
 751                    None
 752                }
 753            })
 754    }
 755
 756    pub fn request_tool_call_permission(
 757        &mut self,
 758        tool_call: acp::ToolCall,
 759        options: Vec<acp::PermissionOption>,
 760        cx: &mut Context<Self>,
 761    ) -> oneshot::Receiver<acp::PermissionOptionId> {
 762        let (tx, rx) = oneshot::channel();
 763
 764        let status = ToolCallStatus::WaitingForConfirmation {
 765            options,
 766            respond_tx: tx,
 767        };
 768
 769        self.upsert_tool_call_inner(tool_call, status, cx);
 770        rx
 771    }
 772
 773    pub fn authorize_tool_call(
 774        &mut self,
 775        id: acp::ToolCallId,
 776        option_id: acp::PermissionOptionId,
 777        option_kind: acp::PermissionOptionKind,
 778        cx: &mut Context<Self>,
 779    ) {
 780        let Some((ix, call)) = self.tool_call_mut(&id) else {
 781            return;
 782        };
 783
 784        let new_status = match option_kind {
 785            acp::PermissionOptionKind::RejectOnce | acp::PermissionOptionKind::RejectAlways => {
 786                ToolCallStatus::Rejected
 787            }
 788            acp::PermissionOptionKind::AllowOnce | acp::PermissionOptionKind::AllowAlways => {
 789                ToolCallStatus::Allowed {
 790                    status: acp::ToolCallStatus::InProgress,
 791                }
 792            }
 793        };
 794
 795        let curr_status = mem::replace(&mut call.status, new_status);
 796
 797        if let ToolCallStatus::WaitingForConfirmation { respond_tx, .. } = curr_status {
 798            respond_tx.send(option_id).log_err();
 799        } else if cfg!(debug_assertions) {
 800            panic!("tried to authorize an already authorized tool call");
 801        }
 802
 803        cx.emit(AcpThreadEvent::EntryUpdated(ix));
 804    }
 805
 806    pub fn plan(&self) -> &Plan {
 807        &self.plan
 808    }
 809
 810    pub fn update_plan(&mut self, request: acp::Plan, cx: &mut Context<Self>) {
 811        self.plan = Plan {
 812            entries: request
 813                .entries
 814                .into_iter()
 815                .map(|entry| PlanEntry::from_acp(entry, cx))
 816                .collect(),
 817        };
 818
 819        cx.notify();
 820    }
 821
 822    fn clear_completed_plan_entries(&mut self, cx: &mut Context<Self>) {
 823        self.plan
 824            .entries
 825            .retain(|entry| !matches!(entry.status, acp::PlanEntryStatus::Completed));
 826        cx.notify();
 827    }
 828
 829    pub fn set_project_location(&self, location: acp::ToolCallLocation, cx: &mut Context<Self>) {
 830        self.project.update(cx, |project, cx| {
 831            let Some(path) = project.project_path_for_absolute_path(&location.path, cx) else {
 832                return;
 833            };
 834            let buffer = project.open_buffer(path, cx);
 835            cx.spawn(async move |project, cx| {
 836                let buffer = buffer.await?;
 837
 838                project.update(cx, |project, cx| {
 839                    let position = if let Some(line) = location.line {
 840                        let snapshot = buffer.read(cx).snapshot();
 841                        let point = snapshot.clip_point(Point::new(line, 0), Bias::Left);
 842                        snapshot.anchor_before(point)
 843                    } else {
 844                        Anchor::MIN
 845                    };
 846
 847                    project.set_agent_location(
 848                        Some(AgentLocation {
 849                            buffer: buffer.downgrade(),
 850                            position,
 851                        }),
 852                        cx,
 853                    );
 854                })
 855            })
 856            .detach_and_log_err(cx);
 857        });
 858    }
 859
 860    /// Returns true if the last turn is awaiting tool authorization
 861    pub fn waiting_for_tool_confirmation(&self) -> bool {
 862        for entry in self.entries.iter().rev() {
 863            match &entry {
 864                AgentThreadEntry::ToolCall(call) => match call.status {
 865                    ToolCallStatus::WaitingForConfirmation { .. } => return true,
 866                    ToolCallStatus::Allowed { .. }
 867                    | ToolCallStatus::Rejected
 868                    | ToolCallStatus::Canceled => continue,
 869                },
 870                AgentThreadEntry::UserMessage(_) | AgentThreadEntry::AssistantMessage(_) => {
 871                    // Reached the beginning of the turn
 872                    return false;
 873                }
 874            }
 875        }
 876        false
 877    }
 878
 879    pub fn authenticate(&self, cx: &mut App) -> impl use<> + Future<Output = Result<()>> {
 880        self.connection.authenticate(cx)
 881    }
 882
 883    #[cfg(any(test, feature = "test-support"))]
 884    pub fn send_raw(
 885        &mut self,
 886        message: &str,
 887        cx: &mut Context<Self>,
 888    ) -> BoxFuture<'static, Result<()>> {
 889        self.send(
 890            vec![acp::ContentBlock::Text(acp::TextContent {
 891                text: message.to_string(),
 892                annotations: None,
 893            })],
 894            cx,
 895        )
 896    }
 897
 898    pub fn send(
 899        &mut self,
 900        message: Vec<acp::ContentBlock>,
 901        cx: &mut Context<Self>,
 902    ) -> BoxFuture<'static, Result<()>> {
 903        let block = ContentBlock::new_combined(
 904            message.clone(),
 905            self.project.read(cx).languages().clone(),
 906            cx,
 907        );
 908        self.push_entry(
 909            AgentThreadEntry::UserMessage(UserMessage { content: block }),
 910            cx,
 911        );
 912        self.clear_completed_plan_entries(cx);
 913
 914        let (tx, rx) = oneshot::channel();
 915        let cancel_task = self.cancel(cx);
 916
 917        self.send_task = Some(cx.spawn(async move |this, cx| {
 918            async {
 919                cancel_task.await;
 920
 921                let result = this
 922                    .update(cx, |this, cx| {
 923                        this.connection.prompt(
 924                            acp::PromptToolArguments {
 925                                prompt: message,
 926                                session_id: this.session_id.clone(),
 927                            },
 928                            cx,
 929                        )
 930                    })?
 931                    .await;
 932                tx.send(result).log_err();
 933                this.update(cx, |this, _cx| this.send_task.take())?;
 934                anyhow::Ok(())
 935            }
 936            .await
 937            .log_err();
 938        }));
 939
 940        async move {
 941            match rx.await {
 942                Ok(Err(e)) => Err(e)?,
 943                _ => Ok(()),
 944            }
 945        }
 946        .boxed()
 947    }
 948
 949    pub fn cancel(&mut self, cx: &mut Context<Self>) -> Task<()> {
 950        let Some(send_task) = self.send_task.take() else {
 951            return Task::ready(());
 952        };
 953
 954        for entry in self.entries.iter_mut() {
 955            if let AgentThreadEntry::ToolCall(call) = entry {
 956                let cancel = matches!(
 957                    call.status,
 958                    ToolCallStatus::WaitingForConfirmation { .. }
 959                        | ToolCallStatus::Allowed {
 960                            status: acp::ToolCallStatus::InProgress
 961                        }
 962                );
 963
 964                if cancel {
 965                    call.status = ToolCallStatus::Canceled;
 966                }
 967            }
 968        }
 969
 970        self.connection.cancel(cx);
 971
 972        // Wait for the send task to complete
 973        cx.foreground_executor().spawn(send_task)
 974    }
 975
 976    pub fn read_text_file(
 977        &self,
 978        path: PathBuf,
 979        line: Option<u32>,
 980        limit: Option<u32>,
 981        reuse_shared_snapshot: bool,
 982        cx: &mut Context<Self>,
 983    ) -> Task<Result<String>> {
 984        let project = self.project.clone();
 985        let action_log = self.action_log.clone();
 986        cx.spawn(async move |this, cx| {
 987            let load = project.update(cx, |project, cx| {
 988                let path = project
 989                    .project_path_for_absolute_path(&path, cx)
 990                    .context("invalid path")?;
 991                anyhow::Ok(project.open_buffer(path, cx))
 992            });
 993            let buffer = load??.await?;
 994
 995            let snapshot = if reuse_shared_snapshot {
 996                this.read_with(cx, |this, _| {
 997                    this.shared_buffers.get(&buffer.clone()).cloned()
 998                })
 999                .log_err()
1000                .flatten()
1001            } else {
1002                None
1003            };
1004
1005            let snapshot = if let Some(snapshot) = snapshot {
1006                snapshot
1007            } else {
1008                action_log.update(cx, |action_log, cx| {
1009                    action_log.buffer_read(buffer.clone(), cx);
1010                })?;
1011                project.update(cx, |project, cx| {
1012                    let position = buffer
1013                        .read(cx)
1014                        .snapshot()
1015                        .anchor_before(Point::new(line.unwrap_or_default(), 0));
1016                    project.set_agent_location(
1017                        Some(AgentLocation {
1018                            buffer: buffer.downgrade(),
1019                            position,
1020                        }),
1021                        cx,
1022                    );
1023                })?;
1024
1025                buffer.update(cx, |buffer, _| buffer.snapshot())?
1026            };
1027
1028            this.update(cx, |this, _| {
1029                let text = snapshot.text();
1030                this.shared_buffers.insert(buffer.clone(), snapshot);
1031                if line.is_none() && limit.is_none() {
1032                    return Ok(text);
1033                }
1034                let limit = limit.unwrap_or(u32::MAX) as usize;
1035                let Some(line) = line else {
1036                    return Ok(text.lines().take(limit).collect::<String>());
1037                };
1038
1039                let count = text.lines().count();
1040                if count < line as usize {
1041                    anyhow::bail!("There are only {} lines", count);
1042                }
1043                Ok(text
1044                    .lines()
1045                    .skip(line as usize + 1)
1046                    .take(limit)
1047                    .collect::<String>())
1048            })?
1049        })
1050    }
1051
1052    pub fn write_text_file(
1053        &self,
1054        path: PathBuf,
1055        content: String,
1056        cx: &mut Context<Self>,
1057    ) -> Task<Result<()>> {
1058        let project = self.project.clone();
1059        let action_log = self.action_log.clone();
1060        cx.spawn(async move |this, cx| {
1061            let load = project.update(cx, |project, cx| {
1062                let path = project
1063                    .project_path_for_absolute_path(&path, cx)
1064                    .context("invalid path")?;
1065                anyhow::Ok(project.open_buffer(path, cx))
1066            });
1067            let buffer = load??.await?;
1068            let snapshot = this.update(cx, |this, cx| {
1069                this.shared_buffers
1070                    .get(&buffer)
1071                    .cloned()
1072                    .unwrap_or_else(|| buffer.read(cx).snapshot())
1073            })?;
1074            let edits = cx
1075                .background_executor()
1076                .spawn(async move {
1077                    let old_text = snapshot.text();
1078                    text_diff(old_text.as_str(), &content)
1079                        .into_iter()
1080                        .map(|(range, replacement)| {
1081                            (
1082                                snapshot.anchor_after(range.start)
1083                                    ..snapshot.anchor_before(range.end),
1084                                replacement,
1085                            )
1086                        })
1087                        .collect::<Vec<_>>()
1088                })
1089                .await;
1090            cx.update(|cx| {
1091                project.update(cx, |project, cx| {
1092                    project.set_agent_location(
1093                        Some(AgentLocation {
1094                            buffer: buffer.downgrade(),
1095                            position: edits
1096                                .last()
1097                                .map(|(range, _)| range.end)
1098                                .unwrap_or(Anchor::MIN),
1099                        }),
1100                        cx,
1101                    );
1102                });
1103
1104                action_log.update(cx, |action_log, cx| {
1105                    action_log.buffer_read(buffer.clone(), cx);
1106                });
1107                buffer.update(cx, |buffer, cx| {
1108                    buffer.edit(edits, None, cx);
1109                });
1110                action_log.update(cx, |action_log, cx| {
1111                    action_log.buffer_edited(buffer.clone(), cx);
1112                });
1113            })?;
1114            project
1115                .update(cx, |project, cx| project.save_buffer(buffer, cx))?
1116                .await
1117        })
1118    }
1119
1120    pub fn to_markdown(&self, cx: &App) -> String {
1121        self.entries.iter().map(|e| e.to_markdown(cx)).collect()
1122    }
1123}
1124
1125#[derive(Clone)]
1126pub struct OldAcpClientDelegate {
1127    thread: Rc<RefCell<WeakEntity<AcpThread>>>,
1128    cx: AsyncApp,
1129    next_tool_call_id: Rc<RefCell<u64>>,
1130    // sent_buffer_versions: HashMap<Entity<Buffer>, HashMap<u64, BufferSnapshot>>,
1131}
1132
1133impl OldAcpClientDelegate {
1134    pub fn new(thread: Rc<RefCell<WeakEntity<AcpThread>>>, cx: AsyncApp) -> Self {
1135        Self {
1136            thread,
1137            cx,
1138            next_tool_call_id: Rc::new(RefCell::new(0)),
1139        }
1140    }
1141}
1142
1143impl acp_old::Client for OldAcpClientDelegate {
1144    async fn stream_assistant_message_chunk(
1145        &self,
1146        params: acp_old::StreamAssistantMessageChunkParams,
1147    ) -> Result<(), acp_old::Error> {
1148        let cx = &mut self.cx.clone();
1149
1150        cx.update(|cx| {
1151            self.thread
1152                .borrow()
1153                .update(cx, |thread, cx| match params.chunk {
1154                    acp_old::AssistantMessageChunk::Text { text } => {
1155                        thread.push_assistant_chunk(text.into(), false, cx)
1156                    }
1157                    acp_old::AssistantMessageChunk::Thought { thought } => {
1158                        thread.push_assistant_chunk(thought.into(), true, cx)
1159                    }
1160                })
1161                .ok();
1162        })?;
1163
1164        Ok(())
1165    }
1166
1167    async fn request_tool_call_confirmation(
1168        &self,
1169        request: acp_old::RequestToolCallConfirmationParams,
1170    ) -> Result<acp_old::RequestToolCallConfirmationResponse, acp_old::Error> {
1171        let cx = &mut self.cx.clone();
1172
1173        let old_acp_id = *self.next_tool_call_id.borrow() + 1;
1174        self.next_tool_call_id.replace(old_acp_id);
1175
1176        let tool_call = into_new_tool_call(
1177            acp::ToolCallId(old_acp_id.to_string().into()),
1178            request.tool_call,
1179        );
1180
1181        let mut options = match request.confirmation {
1182            acp_old::ToolCallConfirmation::Edit { .. } => vec![(
1183                acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
1184                acp::PermissionOptionKind::AllowAlways,
1185                "Always Allow Edits".to_string(),
1186            )],
1187            acp_old::ToolCallConfirmation::Execute { root_command, .. } => vec![(
1188                acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
1189                acp::PermissionOptionKind::AllowAlways,
1190                format!("Always Allow {}", root_command),
1191            )],
1192            acp_old::ToolCallConfirmation::Mcp {
1193                server_name,
1194                tool_name,
1195                ..
1196            } => vec![
1197                (
1198                    acp_old::ToolCallConfirmationOutcome::AlwaysAllowMcpServer,
1199                    acp::PermissionOptionKind::AllowAlways,
1200                    format!("Always Allow {}", server_name),
1201                ),
1202                (
1203                    acp_old::ToolCallConfirmationOutcome::AlwaysAllowTool,
1204                    acp::PermissionOptionKind::AllowAlways,
1205                    format!("Always Allow {}", tool_name),
1206                ),
1207            ],
1208            acp_old::ToolCallConfirmation::Fetch { .. } => vec![(
1209                acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
1210                acp::PermissionOptionKind::AllowAlways,
1211                "Always Allow".to_string(),
1212            )],
1213            acp_old::ToolCallConfirmation::Other { .. } => vec![(
1214                acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
1215                acp::PermissionOptionKind::AllowAlways,
1216                "Always Allow".to_string(),
1217            )],
1218        };
1219
1220        options.extend([
1221            (
1222                acp_old::ToolCallConfirmationOutcome::Allow,
1223                acp::PermissionOptionKind::AllowOnce,
1224                "Allow".to_string(),
1225            ),
1226            (
1227                acp_old::ToolCallConfirmationOutcome::Reject,
1228                acp::PermissionOptionKind::RejectOnce,
1229                "Reject".to_string(),
1230            ),
1231        ]);
1232
1233        let mut outcomes = Vec::with_capacity(options.len());
1234        let mut acp_options = Vec::with_capacity(options.len());
1235
1236        for (index, (outcome, kind, label)) in options.into_iter().enumerate() {
1237            outcomes.push(outcome);
1238            acp_options.push(acp::PermissionOption {
1239                id: acp::PermissionOptionId(index.to_string().into()),
1240                label,
1241                kind,
1242            })
1243        }
1244
1245        let response = cx
1246            .update(|cx| {
1247                self.thread.borrow().update(cx, |thread, cx| {
1248                    thread.request_tool_call_permission(tool_call, acp_options, cx)
1249                })
1250            })?
1251            .context("Failed to update thread")?
1252            .await;
1253
1254        let outcome = match response {
1255            Ok(option_id) => outcomes[option_id.0.parse::<usize>().unwrap_or(0)],
1256            Err(oneshot::Canceled) => acp_old::ToolCallConfirmationOutcome::Cancel,
1257        };
1258
1259        Ok(acp_old::RequestToolCallConfirmationResponse {
1260            id: acp_old::ToolCallId(old_acp_id),
1261            outcome: outcome,
1262        })
1263    }
1264
1265    async fn push_tool_call(
1266        &self,
1267        request: acp_old::PushToolCallParams,
1268    ) -> Result<acp_old::PushToolCallResponse, acp_old::Error> {
1269        let cx = &mut self.cx.clone();
1270
1271        let old_acp_id = *self.next_tool_call_id.borrow() + 1;
1272        self.next_tool_call_id.replace(old_acp_id);
1273
1274        cx.update(|cx| {
1275            self.thread.borrow().update(cx, |thread, cx| {
1276                thread.upsert_tool_call(
1277                    into_new_tool_call(acp::ToolCallId(old_acp_id.to_string().into()), request),
1278                    cx,
1279                )
1280            })
1281        })?
1282        .context("Failed to update thread")?;
1283
1284        Ok(acp_old::PushToolCallResponse {
1285            id: acp_old::ToolCallId(old_acp_id),
1286        })
1287    }
1288
1289    async fn update_tool_call(
1290        &self,
1291        request: acp_old::UpdateToolCallParams,
1292    ) -> Result<(), acp_old::Error> {
1293        let cx = &mut self.cx.clone();
1294
1295        cx.update(|cx| {
1296            self.thread.borrow().update(cx, |thread, cx| {
1297                let languages = thread.project.read(cx).languages().clone();
1298
1299                if let Some((ix, tool_call)) = thread
1300                    .tool_call_mut(&acp::ToolCallId(request.tool_call_id.0.to_string().into()))
1301                {
1302                    tool_call.status = ToolCallStatus::Allowed {
1303                        status: into_new_tool_call_status(request.status),
1304                    };
1305                    tool_call.content = request
1306                        .content
1307                        .into_iter()
1308                        .map(|content| {
1309                            ToolCallContent::from_acp(
1310                                into_new_tool_call_content(content),
1311                                languages.clone(),
1312                                cx,
1313                            )
1314                        })
1315                        .collect();
1316
1317                    cx.emit(AcpThreadEvent::EntryUpdated(ix));
1318                    anyhow::Ok(())
1319                } else {
1320                    anyhow::bail!("Tool call not found")
1321                }
1322            })
1323        })?
1324        .context("Failed to update thread")??;
1325
1326        Ok(())
1327    }
1328
1329    async fn update_plan(&self, request: acp_old::UpdatePlanParams) -> Result<(), acp_old::Error> {
1330        let cx = &mut self.cx.clone();
1331
1332        cx.update(|cx| {
1333            self.thread.borrow().update(cx, |thread, cx| {
1334                thread.update_plan(
1335                    acp::Plan {
1336                        entries: request
1337                            .entries
1338                            .into_iter()
1339                            .map(into_new_plan_entry)
1340                            .collect(),
1341                    },
1342                    cx,
1343                )
1344            })
1345        })?
1346        .context("Failed to update thread")?;
1347
1348        Ok(())
1349    }
1350
1351    async fn read_text_file(
1352        &self,
1353        acp_old::ReadTextFileParams { path, line, limit }: acp_old::ReadTextFileParams,
1354    ) -> Result<acp_old::ReadTextFileResponse, acp_old::Error> {
1355        let content = self
1356            .cx
1357            .update(|cx| {
1358                self.thread.borrow().update(cx, |thread, cx| {
1359                    thread.read_text_file(path, line, limit, false, cx)
1360                })
1361            })?
1362            .context("Failed to update thread")?
1363            .await?;
1364        Ok(acp_old::ReadTextFileResponse { content })
1365    }
1366
1367    async fn write_text_file(
1368        &self,
1369        acp_old::WriteTextFileParams { path, content }: acp_old::WriteTextFileParams,
1370    ) -> Result<(), acp_old::Error> {
1371        self.cx
1372            .update(|cx| {
1373                self.thread
1374                    .borrow()
1375                    .update(cx, |thread, cx| thread.write_text_file(path, content, cx))
1376            })?
1377            .context("Failed to update thread")?
1378            .await?;
1379
1380        Ok(())
1381    }
1382}
1383
1384fn into_new_tool_call(id: acp::ToolCallId, request: acp_old::PushToolCallParams) -> acp::ToolCall {
1385    acp::ToolCall {
1386        id: id,
1387        label: request.label,
1388        kind: acp_kind_from_old_icon(request.icon),
1389        status: acp::ToolCallStatus::InProgress,
1390        content: request
1391            .content
1392            .into_iter()
1393            .map(into_new_tool_call_content)
1394            .collect(),
1395        locations: request
1396            .locations
1397            .into_iter()
1398            .map(into_new_tool_call_location)
1399            .collect(),
1400    }
1401}
1402
1403fn acp_kind_from_old_icon(icon: acp_old::Icon) -> acp::ToolKind {
1404    match icon {
1405        acp_old::Icon::FileSearch => acp::ToolKind::Search,
1406        acp_old::Icon::Folder => acp::ToolKind::Search,
1407        acp_old::Icon::Globe => acp::ToolKind::Search,
1408        acp_old::Icon::Hammer => acp::ToolKind::Other,
1409        acp_old::Icon::LightBulb => acp::ToolKind::Think,
1410        acp_old::Icon::Pencil => acp::ToolKind::Edit,
1411        acp_old::Icon::Regex => acp::ToolKind::Search,
1412        acp_old::Icon::Terminal => acp::ToolKind::Execute,
1413    }
1414}
1415
1416fn into_new_tool_call_status(status: acp_old::ToolCallStatus) -> acp::ToolCallStatus {
1417    match status {
1418        acp_old::ToolCallStatus::Running => acp::ToolCallStatus::InProgress,
1419        acp_old::ToolCallStatus::Finished => acp::ToolCallStatus::Completed,
1420        acp_old::ToolCallStatus::Error => acp::ToolCallStatus::Failed,
1421    }
1422}
1423
1424fn into_new_tool_call_content(content: acp_old::ToolCallContent) -> acp::ToolCallContent {
1425    match content {
1426        acp_old::ToolCallContent::Markdown { markdown } => acp::ToolCallContent::ContentBlock {
1427            content: acp::ContentBlock::Text(acp::TextContent {
1428                annotations: None,
1429                text: markdown,
1430            }),
1431        },
1432        acp_old::ToolCallContent::Diff { diff } => acp::ToolCallContent::Diff {
1433            diff: into_new_diff(diff),
1434        },
1435    }
1436}
1437
1438fn into_new_diff(diff: acp_old::Diff) -> acp::Diff {
1439    acp::Diff {
1440        path: diff.path,
1441        old_text: diff.old_text,
1442        new_text: diff.new_text,
1443    }
1444}
1445
1446fn into_new_tool_call_location(location: acp_old::ToolCallLocation) -> acp::ToolCallLocation {
1447    acp::ToolCallLocation {
1448        path: location.path,
1449        line: location.line,
1450    }
1451}
1452
1453fn into_new_plan_entry(entry: acp_old::PlanEntry) -> acp::PlanEntry {
1454    acp::PlanEntry {
1455        content: entry.content,
1456        priority: into_new_plan_priority(entry.priority),
1457        status: into_new_plan_status(entry.status),
1458    }
1459}
1460
1461fn into_new_plan_priority(priority: acp_old::PlanEntryPriority) -> acp::PlanEntryPriority {
1462    match priority {
1463        acp_old::PlanEntryPriority::Low => acp::PlanEntryPriority::Low,
1464        acp_old::PlanEntryPriority::Medium => acp::PlanEntryPriority::Medium,
1465        acp_old::PlanEntryPriority::High => acp::PlanEntryPriority::High,
1466    }
1467}
1468
1469fn into_new_plan_status(status: acp_old::PlanEntryStatus) -> acp::PlanEntryStatus {
1470    match status {
1471        acp_old::PlanEntryStatus::Pending => acp::PlanEntryStatus::Pending,
1472        acp_old::PlanEntryStatus::InProgress => acp::PlanEntryStatus::InProgress,
1473        acp_old::PlanEntryStatus::Completed => acp::PlanEntryStatus::Completed,
1474    }
1475}
1476
1477#[cfg(test)]
1478mod tests {
1479    use super::*;
1480    use anyhow::anyhow;
1481    use async_pipe::{PipeReader, PipeWriter};
1482    use futures::{channel::mpsc, future::LocalBoxFuture, select};
1483    use gpui::{AsyncApp, TestAppContext};
1484    use indoc::indoc;
1485    use project::FakeFs;
1486    use serde_json::json;
1487    use settings::SettingsStore;
1488    use smol::{future::BoxedLocal, stream::StreamExt as _};
1489    use std::{cell::RefCell, rc::Rc, time::Duration};
1490    use util::path;
1491
1492    fn init_test(cx: &mut TestAppContext) {
1493        env_logger::try_init().ok();
1494        cx.update(|cx| {
1495            let settings_store = SettingsStore::test(cx);
1496            cx.set_global(settings_store);
1497            Project::init_settings(cx);
1498            language::init(cx);
1499        });
1500    }
1501
1502    #[gpui::test]
1503    async fn test_thinking_concatenation(cx: &mut TestAppContext) {
1504        init_test(cx);
1505
1506        let fs = FakeFs::new(cx.executor());
1507        let project = Project::test(fs, [], cx).await;
1508        let (thread, fake_server) = fake_acp_thread(project, cx);
1509
1510        fake_server.update(cx, |fake_server, _| {
1511            fake_server.on_user_message(move |_, server, mut cx| async move {
1512                server
1513                    .update(&mut cx, |server, _| {
1514                        server.send_to_zed(acp_old::StreamAssistantMessageChunkParams {
1515                            chunk: acp_old::AssistantMessageChunk::Thought {
1516                                thought: "Thinking ".into(),
1517                            },
1518                        })
1519                    })?
1520                    .await
1521                    .unwrap();
1522                server
1523                    .update(&mut cx, |server, _| {
1524                        server.send_to_zed(acp_old::StreamAssistantMessageChunkParams {
1525                            chunk: acp_old::AssistantMessageChunk::Thought {
1526                                thought: "hard!".into(),
1527                            },
1528                        })
1529                    })?
1530                    .await
1531                    .unwrap();
1532
1533                Ok(())
1534            })
1535        });
1536
1537        thread
1538            .update(cx, |thread, cx| thread.send_raw("Hello from Zed!", cx))
1539            .await
1540            .unwrap();
1541
1542        let output = thread.read_with(cx, |thread, cx| thread.to_markdown(cx));
1543        assert_eq!(
1544            output,
1545            indoc! {r#"
1546            ## User
1547
1548            Hello from Zed!
1549
1550            ## Assistant
1551
1552            <thinking>
1553            Thinking hard!
1554            </thinking>
1555
1556            "#}
1557        );
1558    }
1559
1560    #[gpui::test]
1561    async fn test_edits_concurrently_to_user(cx: &mut TestAppContext) {
1562        init_test(cx);
1563
1564        let fs = FakeFs::new(cx.executor());
1565        fs.insert_tree(path!("/tmp"), json!({"foo": "one\ntwo\nthree\n"}))
1566            .await;
1567        let project = Project::test(fs.clone(), [], cx).await;
1568        let (thread, fake_server) = fake_acp_thread(project.clone(), cx);
1569        let (worktree, pathbuf) = project
1570            .update(cx, |project, cx| {
1571                project.find_or_create_worktree(path!("/tmp/foo"), true, cx)
1572            })
1573            .await
1574            .unwrap();
1575        let buffer = project
1576            .update(cx, |project, cx| {
1577                project.open_buffer((worktree.read(cx).id(), pathbuf), cx)
1578            })
1579            .await
1580            .unwrap();
1581
1582        let (read_file_tx, read_file_rx) = oneshot::channel::<()>();
1583        let read_file_tx = Rc::new(RefCell::new(Some(read_file_tx)));
1584
1585        fake_server.update(cx, |fake_server, _| {
1586            fake_server.on_user_message(move |_, server, mut cx| {
1587                let read_file_tx = read_file_tx.clone();
1588                async move {
1589                    let content = server
1590                        .update(&mut cx, |server, _| {
1591                            server.send_to_zed(acp_old::ReadTextFileParams {
1592                                path: path!("/tmp/foo").into(),
1593                                line: None,
1594                                limit: None,
1595                            })
1596                        })?
1597                        .await
1598                        .unwrap();
1599                    assert_eq!(content.content, "one\ntwo\nthree\n");
1600                    read_file_tx.take().unwrap().send(()).unwrap();
1601                    server
1602                        .update(&mut cx, |server, _| {
1603                            server.send_to_zed(acp_old::WriteTextFileParams {
1604                                path: path!("/tmp/foo").into(),
1605                                content: "one\ntwo\nthree\nfour\nfive\n".to_string(),
1606                            })
1607                        })?
1608                        .await
1609                        .unwrap();
1610                    Ok(())
1611                }
1612            })
1613        });
1614
1615        let request = thread.update(cx, |thread, cx| {
1616            thread.send_raw("Extend the count in /tmp/foo", cx)
1617        });
1618        read_file_rx.await.ok();
1619        buffer.update(cx, |buffer, cx| {
1620            buffer.edit([(0..0, "zero\n".to_string())], None, cx);
1621        });
1622        cx.run_until_parked();
1623        assert_eq!(
1624            buffer.read_with(cx, |buffer, _| buffer.text()),
1625            "zero\none\ntwo\nthree\nfour\nfive\n"
1626        );
1627        assert_eq!(
1628            String::from_utf8(fs.read_file_sync(path!("/tmp/foo")).unwrap()).unwrap(),
1629            "zero\none\ntwo\nthree\nfour\nfive\n"
1630        );
1631        request.await.unwrap();
1632    }
1633
1634    #[gpui::test]
1635    async fn test_succeeding_canceled_toolcall(cx: &mut TestAppContext) {
1636        init_test(cx);
1637
1638        let fs = FakeFs::new(cx.executor());
1639        let project = Project::test(fs, [], cx).await;
1640        let (thread, fake_server) = fake_acp_thread(project, cx);
1641
1642        let (end_turn_tx, end_turn_rx) = oneshot::channel::<()>();
1643
1644        let tool_call_id = Rc::new(RefCell::new(None));
1645        let end_turn_rx = Rc::new(RefCell::new(Some(end_turn_rx)));
1646        fake_server.update(cx, |fake_server, _| {
1647            let tool_call_id = tool_call_id.clone();
1648            fake_server.on_user_message(move |_, server, mut cx| {
1649                let end_turn_rx = end_turn_rx.clone();
1650                let tool_call_id = tool_call_id.clone();
1651                async move {
1652                    let tool_call_result = server
1653                        .update(&mut cx, |server, _| {
1654                            server.send_to_zed(acp_old::PushToolCallParams {
1655                                label: "Fetch".to_string(),
1656                                icon: acp_old::Icon::Globe,
1657                                content: None,
1658                                locations: vec![],
1659                            })
1660                        })?
1661                        .await
1662                        .unwrap();
1663                    *tool_call_id.clone().borrow_mut() = Some(tool_call_result.id);
1664                    end_turn_rx.take().unwrap().await.ok();
1665
1666                    Ok(())
1667                }
1668            })
1669        });
1670
1671        let request = thread.update(cx, |thread, cx| {
1672            thread.send_raw("Fetch https://example.com", cx)
1673        });
1674
1675        run_until_first_tool_call(&thread, cx).await;
1676
1677        thread.read_with(cx, |thread, _| {
1678            assert!(matches!(
1679                thread.entries[1],
1680                AgentThreadEntry::ToolCall(ToolCall {
1681                    status: ToolCallStatus::Allowed {
1682                        status: acp::ToolCallStatus::InProgress,
1683                        ..
1684                    },
1685                    ..
1686                })
1687            ));
1688        });
1689
1690        cx.run_until_parked();
1691
1692        thread.update(cx, |thread, cx| thread.cancel(cx)).await;
1693
1694        thread.read_with(cx, |thread, _| {
1695            assert!(matches!(
1696                &thread.entries[1],
1697                AgentThreadEntry::ToolCall(ToolCall {
1698                    status: ToolCallStatus::Canceled,
1699                    ..
1700                })
1701            ));
1702        });
1703
1704        fake_server
1705            .update(cx, |fake_server, _| {
1706                fake_server.send_to_zed(acp_old::UpdateToolCallParams {
1707                    tool_call_id: tool_call_id.borrow().unwrap(),
1708                    status: acp_old::ToolCallStatus::Finished,
1709                    content: None,
1710                })
1711            })
1712            .await
1713            .unwrap();
1714
1715        drop(end_turn_tx);
1716        request.await.unwrap();
1717
1718        thread.read_with(cx, |thread, _| {
1719            assert!(matches!(
1720                thread.entries[1],
1721                AgentThreadEntry::ToolCall(ToolCall {
1722                    status: ToolCallStatus::Allowed {
1723                        status: acp::ToolCallStatus::Completed,
1724                        ..
1725                    },
1726                    ..
1727                })
1728            ));
1729        });
1730    }
1731
1732    async fn run_until_first_tool_call(
1733        thread: &Entity<AcpThread>,
1734        cx: &mut TestAppContext,
1735    ) -> usize {
1736        let (mut tx, mut rx) = mpsc::channel::<usize>(1);
1737
1738        let subscription = cx.update(|cx| {
1739            cx.subscribe(thread, move |thread, _, cx| {
1740                for (ix, entry) in thread.read(cx).entries.iter().enumerate() {
1741                    if matches!(entry, AgentThreadEntry::ToolCall(_)) {
1742                        return tx.try_send(ix).unwrap();
1743                    }
1744                }
1745            })
1746        });
1747
1748        select! {
1749            _ = futures::FutureExt::fuse(smol::Timer::after(Duration::from_secs(10))) => {
1750                panic!("Timeout waiting for tool call")
1751            }
1752            ix = rx.next().fuse() => {
1753                drop(subscription);
1754                ix.unwrap()
1755            }
1756        }
1757    }
1758
1759    pub fn fake_acp_thread(
1760        project: Entity<Project>,
1761        cx: &mut TestAppContext,
1762    ) -> (Entity<AcpThread>, Entity<FakeAcpServer>) {
1763        let (stdin_tx, stdin_rx) = async_pipe::pipe();
1764        let (stdout_tx, stdout_rx) = async_pipe::pipe();
1765
1766        let thread = cx.new(|cx| {
1767            let foreground_executor = cx.foreground_executor().clone();
1768            let thread_rc = Rc::new(RefCell::new(cx.entity().downgrade()));
1769
1770            let (connection, io_fut) = acp_old::AgentConnection::connect_to_agent(
1771                OldAcpClientDelegate::new(thread_rc.clone(), cx.to_async()),
1772                stdin_tx,
1773                stdout_rx,
1774                move |fut| {
1775                    foreground_executor.spawn(fut).detach();
1776                },
1777            );
1778
1779            let io_task = cx.background_spawn({
1780                async move {
1781                    io_fut.await.log_err();
1782                    Ok(())
1783                }
1784            });
1785            let connection = OldAcpAgentConnection {
1786                connection,
1787                child_status: io_task,
1788            };
1789
1790            AcpThread::new(
1791                Rc::new(connection),
1792                "Test".into(),
1793                project,
1794                acp::SessionId("test".into()),
1795                cx,
1796            )
1797        });
1798        let agent = cx.update(|cx| cx.new(|cx| FakeAcpServer::new(stdin_rx, stdout_tx, cx)));
1799        (thread, agent)
1800    }
1801
1802    pub struct FakeAcpServer {
1803        connection: acp_old::ClientConnection,
1804
1805        _io_task: Task<()>,
1806        on_user_message: Option<
1807            Rc<
1808                dyn Fn(
1809                    acp_old::SendUserMessageParams,
1810                    Entity<FakeAcpServer>,
1811                    AsyncApp,
1812                ) -> LocalBoxFuture<'static, Result<(), acp_old::Error>>,
1813            >,
1814        >,
1815    }
1816
1817    #[derive(Clone)]
1818    struct FakeAgent {
1819        server: Entity<FakeAcpServer>,
1820        cx: AsyncApp,
1821    }
1822
1823    impl acp_old::Agent for FakeAgent {
1824        async fn initialize(
1825            &self,
1826            params: acp_old::InitializeParams,
1827        ) -> Result<acp_old::InitializeResponse, acp_old::Error> {
1828            Ok(acp_old::InitializeResponse {
1829                protocol_version: params.protocol_version,
1830                is_authenticated: true,
1831            })
1832        }
1833
1834        async fn authenticate(&self) -> Result<(), acp_old::Error> {
1835            Ok(())
1836        }
1837
1838        async fn cancel_send_message(&self) -> Result<(), acp_old::Error> {
1839            Ok(())
1840        }
1841
1842        async fn send_user_message(
1843            &self,
1844            request: acp_old::SendUserMessageParams,
1845        ) -> Result<(), acp_old::Error> {
1846            let mut cx = self.cx.clone();
1847            let handler = self
1848                .server
1849                .update(&mut cx, |server, _| server.on_user_message.clone())
1850                .ok()
1851                .flatten();
1852            if let Some(handler) = handler {
1853                handler(request, self.server.clone(), self.cx.clone()).await
1854            } else {
1855                Err(anyhow::anyhow!("No handler for on_user_message").into())
1856            }
1857        }
1858    }
1859
1860    impl FakeAcpServer {
1861        fn new(stdin: PipeReader, stdout: PipeWriter, cx: &Context<Self>) -> Self {
1862            let agent = FakeAgent {
1863                server: cx.entity(),
1864                cx: cx.to_async(),
1865            };
1866            let foreground_executor = cx.foreground_executor().clone();
1867
1868            let (connection, io_fut) = acp_old::ClientConnection::connect_to_client(
1869                agent.clone(),
1870                stdout,
1871                stdin,
1872                move |fut| {
1873                    foreground_executor.spawn(fut).detach();
1874                },
1875            );
1876            FakeAcpServer {
1877                connection: connection,
1878                on_user_message: None,
1879                _io_task: cx.background_spawn(async move {
1880                    io_fut.await.log_err();
1881                }),
1882            }
1883        }
1884
1885        fn on_user_message<F>(
1886            &mut self,
1887            handler: impl for<'a> Fn(
1888                acp_old::SendUserMessageParams,
1889                Entity<FakeAcpServer>,
1890                AsyncApp,
1891            ) -> F
1892            + 'static,
1893        ) where
1894            F: Future<Output = Result<(), acp_old::Error>> + 'static,
1895        {
1896            self.on_user_message
1897                .replace(Rc::new(move |request, server, cx| {
1898                    handler(request, server, cx).boxed_local()
1899                }));
1900        }
1901
1902        fn send_to_zed<T: acp_old::ClientRequest + 'static>(
1903            &self,
1904            message: T,
1905        ) -> BoxedLocal<Result<T::Response>> {
1906            self.connection
1907                .request(message)
1908                .map(|f| f.map_err(|err| anyhow!(err)))
1909                .boxed_local()
1910        }
1911    }
1912}