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        project: Entity<Project>,
 576        session_id: acp::SessionId,
 577        cx: &mut Context<Self>,
 578    ) -> Self {
 579        let action_log = cx.new(|_| ActionLog::new(project.clone()));
 580
 581        Self {
 582            action_log,
 583            shared_buffers: Default::default(),
 584            entries: Default::default(),
 585            plan: Default::default(),
 586            title: connection.name().into(),
 587            project,
 588            send_task: None,
 589            connection,
 590            session_id,
 591        }
 592    }
 593
 594    pub fn action_log(&self) -> &Entity<ActionLog> {
 595        &self.action_log
 596    }
 597
 598    pub fn project(&self) -> &Entity<Project> {
 599        &self.project
 600    }
 601
 602    pub fn title(&self) -> SharedString {
 603        self.title.clone()
 604    }
 605
 606    pub fn entries(&self) -> &[AgentThreadEntry] {
 607        &self.entries
 608    }
 609
 610    pub fn status(&self) -> ThreadStatus {
 611        if self.send_task.is_some() {
 612            if self.waiting_for_tool_confirmation() {
 613                ThreadStatus::WaitingForToolConfirmation
 614            } else {
 615                ThreadStatus::Generating
 616            }
 617        } else {
 618            ThreadStatus::Idle
 619        }
 620    }
 621
 622    pub fn has_pending_edit_tool_calls(&self) -> bool {
 623        for entry in self.entries.iter().rev() {
 624            match entry {
 625                AgentThreadEntry::UserMessage(_) => return false,
 626                AgentThreadEntry::ToolCall(call) if call.first_diff().is_some() => return true,
 627                AgentThreadEntry::ToolCall(_) | AgentThreadEntry::AssistantMessage(_) => {}
 628            }
 629        }
 630
 631        false
 632    }
 633
 634    pub fn push_entry(&mut self, entry: AgentThreadEntry, cx: &mut Context<Self>) {
 635        self.entries.push(entry);
 636        cx.emit(AcpThreadEvent::NewEntry);
 637    }
 638
 639    pub fn push_assistant_chunk(
 640        &mut self,
 641        chunk: acp::ContentBlock,
 642        is_thought: bool,
 643        cx: &mut Context<Self>,
 644    ) {
 645        let language_registry = self.project.read(cx).languages().clone();
 646        let entries_len = self.entries.len();
 647        if let Some(last_entry) = self.entries.last_mut()
 648            && let AgentThreadEntry::AssistantMessage(AssistantMessage { chunks }) = last_entry
 649        {
 650            cx.emit(AcpThreadEvent::EntryUpdated(entries_len - 1));
 651            match (chunks.last_mut(), is_thought) {
 652                (Some(AssistantMessageChunk::Message { block }), false)
 653                | (Some(AssistantMessageChunk::Thought { block }), true) => {
 654                    block.append(chunk, &language_registry, cx)
 655                }
 656                _ => {
 657                    let block = ContentBlock::new(chunk, &language_registry, cx);
 658                    if is_thought {
 659                        chunks.push(AssistantMessageChunk::Thought { block })
 660                    } else {
 661                        chunks.push(AssistantMessageChunk::Message { block })
 662                    }
 663                }
 664            }
 665        } else {
 666            let block = ContentBlock::new(chunk, &language_registry, cx);
 667            let chunk = if is_thought {
 668                AssistantMessageChunk::Thought { block }
 669            } else {
 670                AssistantMessageChunk::Message { block }
 671            };
 672
 673            self.push_entry(
 674                AgentThreadEntry::AssistantMessage(AssistantMessage {
 675                    chunks: vec![chunk],
 676                }),
 677                cx,
 678            );
 679        }
 680    }
 681
 682    pub fn update_tool_call(
 683        &mut self,
 684        id: acp::ToolCallId,
 685        status: acp::ToolCallStatus,
 686        content: Option<Vec<acp::ToolCallContent>>,
 687        cx: &mut Context<Self>,
 688    ) -> Result<()> {
 689        let languages = self.project.read(cx).languages().clone();
 690        let (ix, current_call) = self.tool_call_mut(&id).context("Tool call not found")?;
 691
 692        if let Some(content) = content {
 693            current_call.content = content
 694                .into_iter()
 695                .map(|chunk| ToolCallContent::from_acp(chunk, languages.clone(), cx))
 696                .collect();
 697        }
 698        current_call.status = ToolCallStatus::Allowed { status };
 699
 700        cx.emit(AcpThreadEvent::EntryUpdated(ix));
 701
 702        Ok(())
 703    }
 704
 705    /// Updates a tool call if id matches an existing entry, otherwise inserts a new one.
 706    pub fn upsert_tool_call(&mut self, tool_call: acp::ToolCall, cx: &mut Context<Self>) {
 707        let status = ToolCallStatus::Allowed {
 708            status: tool_call.status,
 709        };
 710        self.upsert_tool_call_inner(tool_call, status, cx)
 711    }
 712
 713    pub fn upsert_tool_call_inner(
 714        &mut self,
 715        tool_call: acp::ToolCall,
 716        status: ToolCallStatus,
 717        cx: &mut Context<Self>,
 718    ) {
 719        let language_registry = self.project.read(cx).languages().clone();
 720        let call = ToolCall::from_acp(tool_call, status, language_registry, cx);
 721
 722        let location = call.locations.last().cloned();
 723
 724        if let Some((ix, current_call)) = self.tool_call_mut(&call.id) {
 725            *current_call = call;
 726
 727            cx.emit(AcpThreadEvent::EntryUpdated(ix));
 728        } else {
 729            self.push_entry(AgentThreadEntry::ToolCall(call), cx);
 730        }
 731
 732        if let Some(location) = location {
 733            self.set_project_location(location, cx)
 734        }
 735    }
 736
 737    fn tool_call_mut(&mut self, id: &acp::ToolCallId) -> Option<(usize, &mut ToolCall)> {
 738        // todo! use map
 739        self.entries
 740            .iter_mut()
 741            .enumerate()
 742            .rev()
 743            .find_map(|(index, tool_call)| {
 744                if let AgentThreadEntry::ToolCall(tool_call) = tool_call
 745                    && &tool_call.id == id
 746                {
 747                    Some((index, tool_call))
 748                } else {
 749                    None
 750                }
 751            })
 752    }
 753
 754    pub fn request_tool_call_permission(
 755        &mut self,
 756        tool_call: acp::ToolCall,
 757        options: Vec<acp::PermissionOption>,
 758        cx: &mut Context<Self>,
 759    ) -> oneshot::Receiver<acp::PermissionOptionId> {
 760        let (tx, rx) = oneshot::channel();
 761
 762        let status = ToolCallStatus::WaitingForConfirmation {
 763            options,
 764            respond_tx: tx,
 765        };
 766
 767        self.upsert_tool_call_inner(tool_call, status, cx);
 768        rx
 769    }
 770
 771    pub fn authorize_tool_call(
 772        &mut self,
 773        id: acp::ToolCallId,
 774        option_id: acp::PermissionOptionId,
 775        option_kind: acp::PermissionOptionKind,
 776        cx: &mut Context<Self>,
 777    ) {
 778        let Some((ix, call)) = self.tool_call_mut(&id) else {
 779            return;
 780        };
 781
 782        let new_status = match option_kind {
 783            acp::PermissionOptionKind::RejectOnce | acp::PermissionOptionKind::RejectAlways => {
 784                ToolCallStatus::Rejected
 785            }
 786            acp::PermissionOptionKind::AllowOnce | acp::PermissionOptionKind::AllowAlways => {
 787                ToolCallStatus::Allowed {
 788                    status: acp::ToolCallStatus::InProgress,
 789                }
 790            }
 791        };
 792
 793        let curr_status = mem::replace(&mut call.status, new_status);
 794
 795        if let ToolCallStatus::WaitingForConfirmation { respond_tx, .. } = curr_status {
 796            respond_tx.send(option_id).log_err();
 797        } else if cfg!(debug_assertions) {
 798            panic!("tried to authorize an already authorized tool call");
 799        }
 800
 801        cx.emit(AcpThreadEvent::EntryUpdated(ix));
 802    }
 803
 804    pub fn plan(&self) -> &Plan {
 805        &self.plan
 806    }
 807
 808    pub fn update_plan(&mut self, request: acp::Plan, cx: &mut Context<Self>) {
 809        self.plan = Plan {
 810            entries: request
 811                .entries
 812                .into_iter()
 813                .map(|entry| PlanEntry::from_acp(entry, cx))
 814                .collect(),
 815        };
 816
 817        cx.notify();
 818    }
 819
 820    fn clear_completed_plan_entries(&mut self, cx: &mut Context<Self>) {
 821        self.plan
 822            .entries
 823            .retain(|entry| !matches!(entry.status, acp::PlanEntryStatus::Completed));
 824        cx.notify();
 825    }
 826
 827    pub fn set_project_location(&self, location: acp::ToolCallLocation, cx: &mut Context<Self>) {
 828        self.project.update(cx, |project, cx| {
 829            let Some(path) = project.project_path_for_absolute_path(&location.path, cx) else {
 830                return;
 831            };
 832            let buffer = project.open_buffer(path, cx);
 833            cx.spawn(async move |project, cx| {
 834                let buffer = buffer.await?;
 835
 836                project.update(cx, |project, cx| {
 837                    let position = if let Some(line) = location.line {
 838                        let snapshot = buffer.read(cx).snapshot();
 839                        let point = snapshot.clip_point(Point::new(line, 0), Bias::Left);
 840                        snapshot.anchor_before(point)
 841                    } else {
 842                        Anchor::MIN
 843                    };
 844
 845                    project.set_agent_location(
 846                        Some(AgentLocation {
 847                            buffer: buffer.downgrade(),
 848                            position,
 849                        }),
 850                        cx,
 851                    );
 852                })
 853            })
 854            .detach_and_log_err(cx);
 855        });
 856    }
 857
 858    /// Returns true if the last turn is awaiting tool authorization
 859    pub fn waiting_for_tool_confirmation(&self) -> bool {
 860        for entry in self.entries.iter().rev() {
 861            match &entry {
 862                AgentThreadEntry::ToolCall(call) => match call.status {
 863                    ToolCallStatus::WaitingForConfirmation { .. } => return true,
 864                    ToolCallStatus::Allowed { .. }
 865                    | ToolCallStatus::Rejected
 866                    | ToolCallStatus::Canceled => continue,
 867                },
 868                AgentThreadEntry::UserMessage(_) | AgentThreadEntry::AssistantMessage(_) => {
 869                    // Reached the beginning of the turn
 870                    return false;
 871                }
 872            }
 873        }
 874        false
 875    }
 876
 877    pub fn authenticate(&self, cx: &mut App) -> impl use<> + Future<Output = Result<()>> {
 878        self.connection.authenticate(cx)
 879    }
 880
 881    #[cfg(any(test, feature = "test-support"))]
 882    pub fn send_raw(
 883        &mut self,
 884        message: &str,
 885        cx: &mut Context<Self>,
 886    ) -> BoxFuture<'static, Result<()>> {
 887        self.send(
 888            vec![acp::ContentBlock::Text(acp::TextContent {
 889                text: message.to_string(),
 890                annotations: None,
 891            })],
 892            cx,
 893        )
 894    }
 895
 896    pub fn send(
 897        &mut self,
 898        message: Vec<acp::ContentBlock>,
 899        cx: &mut Context<Self>,
 900    ) -> BoxFuture<'static, Result<()>> {
 901        let block = ContentBlock::new_combined(
 902            message.clone(),
 903            self.project.read(cx).languages().clone(),
 904            cx,
 905        );
 906        self.push_entry(
 907            AgentThreadEntry::UserMessage(UserMessage { content: block }),
 908            cx,
 909        );
 910        self.clear_completed_plan_entries(cx);
 911
 912        let (tx, rx) = oneshot::channel();
 913        let cancel_task = self.cancel(cx);
 914
 915        self.send_task = Some(cx.spawn(async move |this, cx| {
 916            async {
 917                cancel_task.await;
 918
 919                let result = this
 920                    .update(cx, |this, cx| {
 921                        this.connection.prompt(
 922                            acp::PromptToolArguments {
 923                                prompt: message,
 924                                session_id: this.session_id.clone(),
 925                            },
 926                            cx,
 927                        )
 928                    })?
 929                    .await;
 930                tx.send(result).log_err();
 931                this.update(cx, |this, _cx| this.send_task.take())?;
 932                anyhow::Ok(())
 933            }
 934            .await
 935            .log_err();
 936        }));
 937
 938        async move {
 939            match rx.await {
 940                Ok(Err(e)) => Err(e)?,
 941                _ => Ok(()),
 942            }
 943        }
 944        .boxed()
 945    }
 946
 947    pub fn cancel(&mut self, cx: &mut Context<Self>) -> Task<()> {
 948        let Some(send_task) = self.send_task.take() else {
 949            return Task::ready(());
 950        };
 951
 952        for entry in self.entries.iter_mut() {
 953            if let AgentThreadEntry::ToolCall(call) = entry {
 954                let cancel = matches!(
 955                    call.status,
 956                    ToolCallStatus::WaitingForConfirmation { .. }
 957                        | ToolCallStatus::Allowed {
 958                            status: acp::ToolCallStatus::InProgress
 959                        }
 960                );
 961
 962                if cancel {
 963                    call.status = ToolCallStatus::Canceled;
 964                }
 965            }
 966        }
 967
 968        self.connection.cancel(&self.session_id, cx);
 969
 970        // Wait for the send task to complete
 971        cx.foreground_executor().spawn(send_task)
 972    }
 973
 974    pub fn read_text_file(
 975        &self,
 976        path: PathBuf,
 977        line: Option<u32>,
 978        limit: Option<u32>,
 979        reuse_shared_snapshot: bool,
 980        cx: &mut Context<Self>,
 981    ) -> Task<Result<String>> {
 982        let project = self.project.clone();
 983        let action_log = self.action_log.clone();
 984        cx.spawn(async move |this, cx| {
 985            let load = project.update(cx, |project, cx| {
 986                let path = project
 987                    .project_path_for_absolute_path(&path, cx)
 988                    .context("invalid path")?;
 989                anyhow::Ok(project.open_buffer(path, cx))
 990            });
 991            let buffer = load??.await?;
 992
 993            let snapshot = if reuse_shared_snapshot {
 994                this.read_with(cx, |this, _| {
 995                    this.shared_buffers.get(&buffer.clone()).cloned()
 996                })
 997                .log_err()
 998                .flatten()
 999            } else {
1000                None
1001            };
1002
1003            let snapshot = if let Some(snapshot) = snapshot {
1004                snapshot
1005            } else {
1006                action_log.update(cx, |action_log, cx| {
1007                    action_log.buffer_read(buffer.clone(), cx);
1008                })?;
1009                project.update(cx, |project, cx| {
1010                    let position = buffer
1011                        .read(cx)
1012                        .snapshot()
1013                        .anchor_before(Point::new(line.unwrap_or_default(), 0));
1014                    project.set_agent_location(
1015                        Some(AgentLocation {
1016                            buffer: buffer.downgrade(),
1017                            position,
1018                        }),
1019                        cx,
1020                    );
1021                })?;
1022
1023                buffer.update(cx, |buffer, _| buffer.snapshot())?
1024            };
1025
1026            this.update(cx, |this, _| {
1027                let text = snapshot.text();
1028                this.shared_buffers.insert(buffer.clone(), snapshot);
1029                if line.is_none() && limit.is_none() {
1030                    return Ok(text);
1031                }
1032                let limit = limit.unwrap_or(u32::MAX) as usize;
1033                let Some(line) = line else {
1034                    return Ok(text.lines().take(limit).collect::<String>());
1035                };
1036
1037                let count = text.lines().count();
1038                if count < line as usize {
1039                    anyhow::bail!("There are only {} lines", count);
1040                }
1041                Ok(text
1042                    .lines()
1043                    .skip(line as usize + 1)
1044                    .take(limit)
1045                    .collect::<String>())
1046            })?
1047        })
1048    }
1049
1050    pub fn write_text_file(
1051        &self,
1052        path: PathBuf,
1053        content: String,
1054        cx: &mut Context<Self>,
1055    ) -> Task<Result<()>> {
1056        let project = self.project.clone();
1057        let action_log = self.action_log.clone();
1058        cx.spawn(async move |this, cx| {
1059            let load = project.update(cx, |project, cx| {
1060                let path = project
1061                    .project_path_for_absolute_path(&path, cx)
1062                    .context("invalid path")?;
1063                anyhow::Ok(project.open_buffer(path, cx))
1064            });
1065            let buffer = load??.await?;
1066            let snapshot = this.update(cx, |this, cx| {
1067                this.shared_buffers
1068                    .get(&buffer)
1069                    .cloned()
1070                    .unwrap_or_else(|| buffer.read(cx).snapshot())
1071            })?;
1072            let edits = cx
1073                .background_executor()
1074                .spawn(async move {
1075                    let old_text = snapshot.text();
1076                    text_diff(old_text.as_str(), &content)
1077                        .into_iter()
1078                        .map(|(range, replacement)| {
1079                            (
1080                                snapshot.anchor_after(range.start)
1081                                    ..snapshot.anchor_before(range.end),
1082                                replacement,
1083                            )
1084                        })
1085                        .collect::<Vec<_>>()
1086                })
1087                .await;
1088            cx.update(|cx| {
1089                project.update(cx, |project, cx| {
1090                    project.set_agent_location(
1091                        Some(AgentLocation {
1092                            buffer: buffer.downgrade(),
1093                            position: edits
1094                                .last()
1095                                .map(|(range, _)| range.end)
1096                                .unwrap_or(Anchor::MIN),
1097                        }),
1098                        cx,
1099                    );
1100                });
1101
1102                action_log.update(cx, |action_log, cx| {
1103                    action_log.buffer_read(buffer.clone(), cx);
1104                });
1105                buffer.update(cx, |buffer, cx| {
1106                    buffer.edit(edits, None, cx);
1107                });
1108                action_log.update(cx, |action_log, cx| {
1109                    action_log.buffer_edited(buffer.clone(), cx);
1110                });
1111            })?;
1112            project
1113                .update(cx, |project, cx| project.save_buffer(buffer, cx))?
1114                .await
1115        })
1116    }
1117
1118    pub fn to_markdown(&self, cx: &App) -> String {
1119        self.entries.iter().map(|e| e.to_markdown(cx)).collect()
1120    }
1121}
1122
1123#[derive(Clone)]
1124pub struct OldAcpClientDelegate {
1125    thread: Rc<RefCell<WeakEntity<AcpThread>>>,
1126    cx: AsyncApp,
1127    next_tool_call_id: Rc<RefCell<u64>>,
1128    // sent_buffer_versions: HashMap<Entity<Buffer>, HashMap<u64, BufferSnapshot>>,
1129}
1130
1131impl OldAcpClientDelegate {
1132    pub fn new(thread: Rc<RefCell<WeakEntity<AcpThread>>>, cx: AsyncApp) -> Self {
1133        Self {
1134            thread,
1135            cx,
1136            next_tool_call_id: Rc::new(RefCell::new(0)),
1137        }
1138    }
1139}
1140
1141impl acp_old::Client for OldAcpClientDelegate {
1142    async fn stream_assistant_message_chunk(
1143        &self,
1144        params: acp_old::StreamAssistantMessageChunkParams,
1145    ) -> Result<(), acp_old::Error> {
1146        let cx = &mut self.cx.clone();
1147
1148        cx.update(|cx| {
1149            self.thread
1150                .borrow()
1151                .update(cx, |thread, cx| match params.chunk {
1152                    acp_old::AssistantMessageChunk::Text { text } => {
1153                        thread.push_assistant_chunk(text.into(), false, cx)
1154                    }
1155                    acp_old::AssistantMessageChunk::Thought { thought } => {
1156                        thread.push_assistant_chunk(thought.into(), true, cx)
1157                    }
1158                })
1159                .ok();
1160        })?;
1161
1162        Ok(())
1163    }
1164
1165    async fn request_tool_call_confirmation(
1166        &self,
1167        request: acp_old::RequestToolCallConfirmationParams,
1168    ) -> Result<acp_old::RequestToolCallConfirmationResponse, acp_old::Error> {
1169        let cx = &mut self.cx.clone();
1170
1171        let old_acp_id = *self.next_tool_call_id.borrow() + 1;
1172        self.next_tool_call_id.replace(old_acp_id);
1173
1174        let tool_call = into_new_tool_call(
1175            acp::ToolCallId(old_acp_id.to_string().into()),
1176            request.tool_call,
1177        );
1178
1179        let mut options = match request.confirmation {
1180            acp_old::ToolCallConfirmation::Edit { .. } => vec![(
1181                acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
1182                acp::PermissionOptionKind::AllowAlways,
1183                "Always Allow Edits".to_string(),
1184            )],
1185            acp_old::ToolCallConfirmation::Execute { root_command, .. } => vec![(
1186                acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
1187                acp::PermissionOptionKind::AllowAlways,
1188                format!("Always Allow {}", root_command),
1189            )],
1190            acp_old::ToolCallConfirmation::Mcp {
1191                server_name,
1192                tool_name,
1193                ..
1194            } => vec![
1195                (
1196                    acp_old::ToolCallConfirmationOutcome::AlwaysAllowMcpServer,
1197                    acp::PermissionOptionKind::AllowAlways,
1198                    format!("Always Allow {}", server_name),
1199                ),
1200                (
1201                    acp_old::ToolCallConfirmationOutcome::AlwaysAllowTool,
1202                    acp::PermissionOptionKind::AllowAlways,
1203                    format!("Always Allow {}", tool_name),
1204                ),
1205            ],
1206            acp_old::ToolCallConfirmation::Fetch { .. } => vec![(
1207                acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
1208                acp::PermissionOptionKind::AllowAlways,
1209                "Always Allow".to_string(),
1210            )],
1211            acp_old::ToolCallConfirmation::Other { .. } => vec![(
1212                acp_old::ToolCallConfirmationOutcome::AlwaysAllow,
1213                acp::PermissionOptionKind::AllowAlways,
1214                "Always Allow".to_string(),
1215            )],
1216        };
1217
1218        options.extend([
1219            (
1220                acp_old::ToolCallConfirmationOutcome::Allow,
1221                acp::PermissionOptionKind::AllowOnce,
1222                "Allow".to_string(),
1223            ),
1224            (
1225                acp_old::ToolCallConfirmationOutcome::Reject,
1226                acp::PermissionOptionKind::RejectOnce,
1227                "Reject".to_string(),
1228            ),
1229        ]);
1230
1231        let mut outcomes = Vec::with_capacity(options.len());
1232        let mut acp_options = Vec::with_capacity(options.len());
1233
1234        for (index, (outcome, kind, label)) in options.into_iter().enumerate() {
1235            outcomes.push(outcome);
1236            acp_options.push(acp::PermissionOption {
1237                id: acp::PermissionOptionId(index.to_string().into()),
1238                label,
1239                kind,
1240            })
1241        }
1242
1243        let response = cx
1244            .update(|cx| {
1245                self.thread.borrow().update(cx, |thread, cx| {
1246                    thread.request_tool_call_permission(tool_call, acp_options, cx)
1247                })
1248            })?
1249            .context("Failed to update thread")?
1250            .await;
1251
1252        let outcome = match response {
1253            Ok(option_id) => outcomes[option_id.0.parse::<usize>().unwrap_or(0)],
1254            Err(oneshot::Canceled) => acp_old::ToolCallConfirmationOutcome::Cancel,
1255        };
1256
1257        Ok(acp_old::RequestToolCallConfirmationResponse {
1258            id: acp_old::ToolCallId(old_acp_id),
1259            outcome: outcome,
1260        })
1261    }
1262
1263    async fn push_tool_call(
1264        &self,
1265        request: acp_old::PushToolCallParams,
1266    ) -> Result<acp_old::PushToolCallResponse, acp_old::Error> {
1267        let cx = &mut self.cx.clone();
1268
1269        let old_acp_id = *self.next_tool_call_id.borrow() + 1;
1270        self.next_tool_call_id.replace(old_acp_id);
1271
1272        cx.update(|cx| {
1273            self.thread.borrow().update(cx, |thread, cx| {
1274                thread.upsert_tool_call(
1275                    into_new_tool_call(acp::ToolCallId(old_acp_id.to_string().into()), request),
1276                    cx,
1277                )
1278            })
1279        })?
1280        .context("Failed to update thread")?;
1281
1282        Ok(acp_old::PushToolCallResponse {
1283            id: acp_old::ToolCallId(old_acp_id),
1284        })
1285    }
1286
1287    async fn update_tool_call(
1288        &self,
1289        request: acp_old::UpdateToolCallParams,
1290    ) -> Result<(), acp_old::Error> {
1291        let cx = &mut self.cx.clone();
1292
1293        cx.update(|cx| {
1294            self.thread.borrow().update(cx, |thread, cx| {
1295                let languages = thread.project.read(cx).languages().clone();
1296
1297                if let Some((ix, tool_call)) = thread
1298                    .tool_call_mut(&acp::ToolCallId(request.tool_call_id.0.to_string().into()))
1299                {
1300                    tool_call.status = ToolCallStatus::Allowed {
1301                        status: into_new_tool_call_status(request.status),
1302                    };
1303                    tool_call.content = request
1304                        .content
1305                        .into_iter()
1306                        .map(|content| {
1307                            ToolCallContent::from_acp(
1308                                into_new_tool_call_content(content),
1309                                languages.clone(),
1310                                cx,
1311                            )
1312                        })
1313                        .collect();
1314
1315                    cx.emit(AcpThreadEvent::EntryUpdated(ix));
1316                    anyhow::Ok(())
1317                } else {
1318                    anyhow::bail!("Tool call not found")
1319                }
1320            })
1321        })?
1322        .context("Failed to update thread")??;
1323
1324        Ok(())
1325    }
1326
1327    async fn update_plan(&self, request: acp_old::UpdatePlanParams) -> Result<(), acp_old::Error> {
1328        let cx = &mut self.cx.clone();
1329
1330        cx.update(|cx| {
1331            self.thread.borrow().update(cx, |thread, cx| {
1332                thread.update_plan(
1333                    acp::Plan {
1334                        entries: request
1335                            .entries
1336                            .into_iter()
1337                            .map(into_new_plan_entry)
1338                            .collect(),
1339                    },
1340                    cx,
1341                )
1342            })
1343        })?
1344        .context("Failed to update thread")?;
1345
1346        Ok(())
1347    }
1348
1349    async fn read_text_file(
1350        &self,
1351        acp_old::ReadTextFileParams { path, line, limit }: acp_old::ReadTextFileParams,
1352    ) -> Result<acp_old::ReadTextFileResponse, acp_old::Error> {
1353        let content = self
1354            .cx
1355            .update(|cx| {
1356                self.thread.borrow().update(cx, |thread, cx| {
1357                    thread.read_text_file(path, line, limit, false, cx)
1358                })
1359            })?
1360            .context("Failed to update thread")?
1361            .await?;
1362        Ok(acp_old::ReadTextFileResponse { content })
1363    }
1364
1365    async fn write_text_file(
1366        &self,
1367        acp_old::WriteTextFileParams { path, content }: acp_old::WriteTextFileParams,
1368    ) -> Result<(), acp_old::Error> {
1369        self.cx
1370            .update(|cx| {
1371                self.thread
1372                    .borrow()
1373                    .update(cx, |thread, cx| thread.write_text_file(path, content, cx))
1374            })?
1375            .context("Failed to update thread")?
1376            .await?;
1377
1378        Ok(())
1379    }
1380}
1381
1382fn into_new_tool_call(id: acp::ToolCallId, request: acp_old::PushToolCallParams) -> acp::ToolCall {
1383    acp::ToolCall {
1384        id: id,
1385        label: request.label,
1386        kind: acp_kind_from_old_icon(request.icon),
1387        status: acp::ToolCallStatus::InProgress,
1388        content: request
1389            .content
1390            .into_iter()
1391            .map(into_new_tool_call_content)
1392            .collect(),
1393        locations: request
1394            .locations
1395            .into_iter()
1396            .map(into_new_tool_call_location)
1397            .collect(),
1398    }
1399}
1400
1401fn acp_kind_from_old_icon(icon: acp_old::Icon) -> acp::ToolKind {
1402    match icon {
1403        acp_old::Icon::FileSearch => acp::ToolKind::Search,
1404        acp_old::Icon::Folder => acp::ToolKind::Search,
1405        acp_old::Icon::Globe => acp::ToolKind::Search,
1406        acp_old::Icon::Hammer => acp::ToolKind::Other,
1407        acp_old::Icon::LightBulb => acp::ToolKind::Think,
1408        acp_old::Icon::Pencil => acp::ToolKind::Edit,
1409        acp_old::Icon::Regex => acp::ToolKind::Search,
1410        acp_old::Icon::Terminal => acp::ToolKind::Execute,
1411    }
1412}
1413
1414fn into_new_tool_call_status(status: acp_old::ToolCallStatus) -> acp::ToolCallStatus {
1415    match status {
1416        acp_old::ToolCallStatus::Running => acp::ToolCallStatus::InProgress,
1417        acp_old::ToolCallStatus::Finished => acp::ToolCallStatus::Completed,
1418        acp_old::ToolCallStatus::Error => acp::ToolCallStatus::Failed,
1419    }
1420}
1421
1422fn into_new_tool_call_content(content: acp_old::ToolCallContent) -> acp::ToolCallContent {
1423    match content {
1424        acp_old::ToolCallContent::Markdown { markdown } => acp::ToolCallContent::ContentBlock {
1425            content: acp::ContentBlock::Text(acp::TextContent {
1426                annotations: None,
1427                text: markdown,
1428            }),
1429        },
1430        acp_old::ToolCallContent::Diff { diff } => acp::ToolCallContent::Diff {
1431            diff: into_new_diff(diff),
1432        },
1433    }
1434}
1435
1436fn into_new_diff(diff: acp_old::Diff) -> acp::Diff {
1437    acp::Diff {
1438        path: diff.path,
1439        old_text: diff.old_text,
1440        new_text: diff.new_text,
1441    }
1442}
1443
1444fn into_new_tool_call_location(location: acp_old::ToolCallLocation) -> acp::ToolCallLocation {
1445    acp::ToolCallLocation {
1446        path: location.path,
1447        line: location.line,
1448    }
1449}
1450
1451fn into_new_plan_entry(entry: acp_old::PlanEntry) -> acp::PlanEntry {
1452    acp::PlanEntry {
1453        content: entry.content,
1454        priority: into_new_plan_priority(entry.priority),
1455        status: into_new_plan_status(entry.status),
1456    }
1457}
1458
1459fn into_new_plan_priority(priority: acp_old::PlanEntryPriority) -> acp::PlanEntryPriority {
1460    match priority {
1461        acp_old::PlanEntryPriority::Low => acp::PlanEntryPriority::Low,
1462        acp_old::PlanEntryPriority::Medium => acp::PlanEntryPriority::Medium,
1463        acp_old::PlanEntryPriority::High => acp::PlanEntryPriority::High,
1464    }
1465}
1466
1467fn into_new_plan_status(status: acp_old::PlanEntryStatus) -> acp::PlanEntryStatus {
1468    match status {
1469        acp_old::PlanEntryStatus::Pending => acp::PlanEntryStatus::Pending,
1470        acp_old::PlanEntryStatus::InProgress => acp::PlanEntryStatus::InProgress,
1471        acp_old::PlanEntryStatus::Completed => acp::PlanEntryStatus::Completed,
1472    }
1473}
1474
1475#[cfg(test)]
1476mod tests {
1477    use super::*;
1478    use anyhow::anyhow;
1479    use async_pipe::{PipeReader, PipeWriter};
1480    use futures::{channel::mpsc, future::LocalBoxFuture, select};
1481    use gpui::{AsyncApp, TestAppContext};
1482    use indoc::indoc;
1483    use project::FakeFs;
1484    use serde_json::json;
1485    use settings::SettingsStore;
1486    use smol::{future::BoxedLocal, stream::StreamExt as _};
1487    use std::{cell::RefCell, rc::Rc, time::Duration};
1488    use util::path;
1489
1490    fn init_test(cx: &mut TestAppContext) {
1491        env_logger::try_init().ok();
1492        cx.update(|cx| {
1493            let settings_store = SettingsStore::test(cx);
1494            cx.set_global(settings_store);
1495            Project::init_settings(cx);
1496            language::init(cx);
1497        });
1498    }
1499
1500    #[gpui::test]
1501    async fn test_thinking_concatenation(cx: &mut TestAppContext) {
1502        init_test(cx);
1503
1504        let fs = FakeFs::new(cx.executor());
1505        let project = Project::test(fs, [], cx).await;
1506        let (thread, fake_server) = fake_acp_thread(project, cx);
1507
1508        fake_server.update(cx, |fake_server, _| {
1509            fake_server.on_user_message(move |_, server, mut cx| async move {
1510                server
1511                    .update(&mut cx, |server, _| {
1512                        server.send_to_zed(acp_old::StreamAssistantMessageChunkParams {
1513                            chunk: acp_old::AssistantMessageChunk::Thought {
1514                                thought: "Thinking ".into(),
1515                            },
1516                        })
1517                    })?
1518                    .await
1519                    .unwrap();
1520                server
1521                    .update(&mut cx, |server, _| {
1522                        server.send_to_zed(acp_old::StreamAssistantMessageChunkParams {
1523                            chunk: acp_old::AssistantMessageChunk::Thought {
1524                                thought: "hard!".into(),
1525                            },
1526                        })
1527                    })?
1528                    .await
1529                    .unwrap();
1530
1531                Ok(())
1532            })
1533        });
1534
1535        thread
1536            .update(cx, |thread, cx| thread.send_raw("Hello from Zed!", cx))
1537            .await
1538            .unwrap();
1539
1540        let output = thread.read_with(cx, |thread, cx| thread.to_markdown(cx));
1541        assert_eq!(
1542            output,
1543            indoc! {r#"
1544            ## User
1545
1546            Hello from Zed!
1547
1548            ## Assistant
1549
1550            <thinking>
1551            Thinking hard!
1552            </thinking>
1553
1554            "#}
1555        );
1556    }
1557
1558    #[gpui::test]
1559    async fn test_edits_concurrently_to_user(cx: &mut TestAppContext) {
1560        init_test(cx);
1561
1562        let fs = FakeFs::new(cx.executor());
1563        fs.insert_tree(path!("/tmp"), json!({"foo": "one\ntwo\nthree\n"}))
1564            .await;
1565        let project = Project::test(fs.clone(), [], cx).await;
1566        let (thread, fake_server) = fake_acp_thread(project.clone(), cx);
1567        let (worktree, pathbuf) = project
1568            .update(cx, |project, cx| {
1569                project.find_or_create_worktree(path!("/tmp/foo"), true, cx)
1570            })
1571            .await
1572            .unwrap();
1573        let buffer = project
1574            .update(cx, |project, cx| {
1575                project.open_buffer((worktree.read(cx).id(), pathbuf), cx)
1576            })
1577            .await
1578            .unwrap();
1579
1580        let (read_file_tx, read_file_rx) = oneshot::channel::<()>();
1581        let read_file_tx = Rc::new(RefCell::new(Some(read_file_tx)));
1582
1583        fake_server.update(cx, |fake_server, _| {
1584            fake_server.on_user_message(move |_, server, mut cx| {
1585                let read_file_tx = read_file_tx.clone();
1586                async move {
1587                    let content = server
1588                        .update(&mut cx, |server, _| {
1589                            server.send_to_zed(acp_old::ReadTextFileParams {
1590                                path: path!("/tmp/foo").into(),
1591                                line: None,
1592                                limit: None,
1593                            })
1594                        })?
1595                        .await
1596                        .unwrap();
1597                    assert_eq!(content.content, "one\ntwo\nthree\n");
1598                    read_file_tx.take().unwrap().send(()).unwrap();
1599                    server
1600                        .update(&mut cx, |server, _| {
1601                            server.send_to_zed(acp_old::WriteTextFileParams {
1602                                path: path!("/tmp/foo").into(),
1603                                content: "one\ntwo\nthree\nfour\nfive\n".to_string(),
1604                            })
1605                        })?
1606                        .await
1607                        .unwrap();
1608                    Ok(())
1609                }
1610            })
1611        });
1612
1613        let request = thread.update(cx, |thread, cx| {
1614            thread.send_raw("Extend the count in /tmp/foo", cx)
1615        });
1616        read_file_rx.await.ok();
1617        buffer.update(cx, |buffer, cx| {
1618            buffer.edit([(0..0, "zero\n".to_string())], None, cx);
1619        });
1620        cx.run_until_parked();
1621        assert_eq!(
1622            buffer.read_with(cx, |buffer, _| buffer.text()),
1623            "zero\none\ntwo\nthree\nfour\nfive\n"
1624        );
1625        assert_eq!(
1626            String::from_utf8(fs.read_file_sync(path!("/tmp/foo")).unwrap()).unwrap(),
1627            "zero\none\ntwo\nthree\nfour\nfive\n"
1628        );
1629        request.await.unwrap();
1630    }
1631
1632    #[gpui::test]
1633    async fn test_succeeding_canceled_toolcall(cx: &mut TestAppContext) {
1634        init_test(cx);
1635
1636        let fs = FakeFs::new(cx.executor());
1637        let project = Project::test(fs, [], cx).await;
1638        let (thread, fake_server) = fake_acp_thread(project, cx);
1639
1640        let (end_turn_tx, end_turn_rx) = oneshot::channel::<()>();
1641
1642        let tool_call_id = Rc::new(RefCell::new(None));
1643        let end_turn_rx = Rc::new(RefCell::new(Some(end_turn_rx)));
1644        fake_server.update(cx, |fake_server, _| {
1645            let tool_call_id = tool_call_id.clone();
1646            fake_server.on_user_message(move |_, server, mut cx| {
1647                let end_turn_rx = end_turn_rx.clone();
1648                let tool_call_id = tool_call_id.clone();
1649                async move {
1650                    let tool_call_result = server
1651                        .update(&mut cx, |server, _| {
1652                            server.send_to_zed(acp_old::PushToolCallParams {
1653                                label: "Fetch".to_string(),
1654                                icon: acp_old::Icon::Globe,
1655                                content: None,
1656                                locations: vec![],
1657                            })
1658                        })?
1659                        .await
1660                        .unwrap();
1661                    *tool_call_id.clone().borrow_mut() = Some(tool_call_result.id);
1662                    end_turn_rx.take().unwrap().await.ok();
1663
1664                    Ok(())
1665                }
1666            })
1667        });
1668
1669        let request = thread.update(cx, |thread, cx| {
1670            thread.send_raw("Fetch https://example.com", cx)
1671        });
1672
1673        run_until_first_tool_call(&thread, cx).await;
1674
1675        thread.read_with(cx, |thread, _| {
1676            assert!(matches!(
1677                thread.entries[1],
1678                AgentThreadEntry::ToolCall(ToolCall {
1679                    status: ToolCallStatus::Allowed {
1680                        status: acp::ToolCallStatus::InProgress,
1681                        ..
1682                    },
1683                    ..
1684                })
1685            ));
1686        });
1687
1688        cx.run_until_parked();
1689
1690        thread.update(cx, |thread, cx| thread.cancel(cx)).await;
1691
1692        thread.read_with(cx, |thread, _| {
1693            assert!(matches!(
1694                &thread.entries[1],
1695                AgentThreadEntry::ToolCall(ToolCall {
1696                    status: ToolCallStatus::Canceled,
1697                    ..
1698                })
1699            ));
1700        });
1701
1702        fake_server
1703            .update(cx, |fake_server, _| {
1704                fake_server.send_to_zed(acp_old::UpdateToolCallParams {
1705                    tool_call_id: tool_call_id.borrow().unwrap(),
1706                    status: acp_old::ToolCallStatus::Finished,
1707                    content: None,
1708                })
1709            })
1710            .await
1711            .unwrap();
1712
1713        drop(end_turn_tx);
1714        request.await.unwrap();
1715
1716        thread.read_with(cx, |thread, _| {
1717            assert!(matches!(
1718                thread.entries[1],
1719                AgentThreadEntry::ToolCall(ToolCall {
1720                    status: ToolCallStatus::Allowed {
1721                        status: acp::ToolCallStatus::Completed,
1722                        ..
1723                    },
1724                    ..
1725                })
1726            ));
1727        });
1728    }
1729
1730    async fn run_until_first_tool_call(
1731        thread: &Entity<AcpThread>,
1732        cx: &mut TestAppContext,
1733    ) -> usize {
1734        let (mut tx, mut rx) = mpsc::channel::<usize>(1);
1735
1736        let subscription = cx.update(|cx| {
1737            cx.subscribe(thread, move |thread, _, cx| {
1738                for (ix, entry) in thread.read(cx).entries.iter().enumerate() {
1739                    if matches!(entry, AgentThreadEntry::ToolCall(_)) {
1740                        return tx.try_send(ix).unwrap();
1741                    }
1742                }
1743            })
1744        });
1745
1746        select! {
1747            _ = futures::FutureExt::fuse(smol::Timer::after(Duration::from_secs(10))) => {
1748                panic!("Timeout waiting for tool call")
1749            }
1750            ix = rx.next().fuse() => {
1751                drop(subscription);
1752                ix.unwrap()
1753            }
1754        }
1755    }
1756
1757    pub fn fake_acp_thread(
1758        project: Entity<Project>,
1759        cx: &mut TestAppContext,
1760    ) -> (Entity<AcpThread>, Entity<FakeAcpServer>) {
1761        let (stdin_tx, stdin_rx) = async_pipe::pipe();
1762        let (stdout_tx, stdout_rx) = async_pipe::pipe();
1763
1764        let thread = cx.new(|cx| {
1765            let foreground_executor = cx.foreground_executor().clone();
1766            let thread_rc = Rc::new(RefCell::new(cx.entity().downgrade()));
1767
1768            let (connection, io_fut) = acp_old::AgentConnection::connect_to_agent(
1769                OldAcpClientDelegate::new(thread_rc.clone(), cx.to_async()),
1770                stdin_tx,
1771                stdout_rx,
1772                move |fut| {
1773                    foreground_executor.spawn(fut).detach();
1774                },
1775            );
1776
1777            let io_task = cx.background_spawn({
1778                async move {
1779                    io_fut.await.log_err();
1780                    Ok(())
1781                }
1782            });
1783            let connection = OldAcpAgentConnection {
1784                name: "test",
1785                connection,
1786                child_status: io_task,
1787            };
1788
1789            AcpThread::new(
1790                Rc::new(connection),
1791                project,
1792                acp::SessionId("test".into()),
1793                cx,
1794            )
1795        });
1796        let agent = cx.update(|cx| cx.new(|cx| FakeAcpServer::new(stdin_rx, stdout_tx, cx)));
1797        (thread, agent)
1798    }
1799
1800    pub struct FakeAcpServer {
1801        connection: acp_old::ClientConnection,
1802
1803        _io_task: Task<()>,
1804        on_user_message: Option<
1805            Rc<
1806                dyn Fn(
1807                    acp_old::SendUserMessageParams,
1808                    Entity<FakeAcpServer>,
1809                    AsyncApp,
1810                ) -> LocalBoxFuture<'static, Result<(), acp_old::Error>>,
1811            >,
1812        >,
1813    }
1814
1815    #[derive(Clone)]
1816    struct FakeAgent {
1817        server: Entity<FakeAcpServer>,
1818        cx: AsyncApp,
1819    }
1820
1821    impl acp_old::Agent for FakeAgent {
1822        async fn initialize(
1823            &self,
1824            params: acp_old::InitializeParams,
1825        ) -> Result<acp_old::InitializeResponse, acp_old::Error> {
1826            Ok(acp_old::InitializeResponse {
1827                protocol_version: params.protocol_version,
1828                is_authenticated: true,
1829            })
1830        }
1831
1832        async fn authenticate(&self) -> Result<(), acp_old::Error> {
1833            Ok(())
1834        }
1835
1836        async fn cancel_send_message(&self) -> Result<(), acp_old::Error> {
1837            Ok(())
1838        }
1839
1840        async fn send_user_message(
1841            &self,
1842            request: acp_old::SendUserMessageParams,
1843        ) -> Result<(), acp_old::Error> {
1844            let mut cx = self.cx.clone();
1845            let handler = self
1846                .server
1847                .update(&mut cx, |server, _| server.on_user_message.clone())
1848                .ok()
1849                .flatten();
1850            if let Some(handler) = handler {
1851                handler(request, self.server.clone(), self.cx.clone()).await
1852            } else {
1853                Err(anyhow::anyhow!("No handler for on_user_message").into())
1854            }
1855        }
1856    }
1857
1858    impl FakeAcpServer {
1859        fn new(stdin: PipeReader, stdout: PipeWriter, cx: &Context<Self>) -> Self {
1860            let agent = FakeAgent {
1861                server: cx.entity(),
1862                cx: cx.to_async(),
1863            };
1864            let foreground_executor = cx.foreground_executor().clone();
1865
1866            let (connection, io_fut) = acp_old::ClientConnection::connect_to_client(
1867                agent.clone(),
1868                stdout,
1869                stdin,
1870                move |fut| {
1871                    foreground_executor.spawn(fut).detach();
1872                },
1873            );
1874            FakeAcpServer {
1875                connection: connection,
1876                on_user_message: None,
1877                _io_task: cx.background_spawn(async move {
1878                    io_fut.await.log_err();
1879                }),
1880            }
1881        }
1882
1883        fn on_user_message<F>(
1884            &mut self,
1885            handler: impl for<'a> Fn(
1886                acp_old::SendUserMessageParams,
1887                Entity<FakeAcpServer>,
1888                AsyncApp,
1889            ) -> F
1890            + 'static,
1891        ) where
1892            F: Future<Output = Result<(), acp_old::Error>> + 'static,
1893        {
1894            self.on_user_message
1895                .replace(Rc::new(move |request, server, cx| {
1896                    handler(request, server, cx).boxed_local()
1897                }));
1898        }
1899
1900        fn send_to_zed<T: acp_old::ClientRequest + 'static>(
1901            &self,
1902            message: T,
1903        ) -> BoxedLocal<Result<T::Response>> {
1904            self.connection
1905                .request(message)
1906                .map(|f| f.map_err(|err| anyhow!(err)))
1907                .boxed_local()
1908        }
1909    }
1910}