Add locations to native agent tool calls, and wire them up to UI (#36058)

Release Notes:

- N/A

---------

Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Cole Miller 2025-08-12 21:48:28 -04:00 committed by GitHub
parent d78bd8f1d7
commit 1957e1f642
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 257 additions and 114 deletions

View file

@ -65,7 +65,7 @@ pub enum EditAgentOutputEvent {
ResolvingEditRange(Range<Anchor>),
UnresolvedEditRange,
AmbiguousEditRange(Vec<Range<usize>>),
Edited,
Edited(Range<Anchor>),
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
@ -178,7 +178,9 @@ impl EditAgent {
)
});
output_events_tx
.unbounded_send(EditAgentOutputEvent::Edited)
.unbounded_send(EditAgentOutputEvent::Edited(
language::Anchor::MIN..language::Anchor::MAX,
))
.ok();
})?;
@ -200,7 +202,9 @@ impl EditAgent {
});
})?;
output_events_tx
.unbounded_send(EditAgentOutputEvent::Edited)
.unbounded_send(EditAgentOutputEvent::Edited(
language::Anchor::MIN..language::Anchor::MAX,
))
.ok();
}
}
@ -336,8 +340,8 @@ impl EditAgent {
// Edit the buffer and report edits to the action log as part of the
// same effect cycle, otherwise the edit will be reported as if the
// user made it.
cx.update(|cx| {
let max_edit_end = buffer.update(cx, |buffer, cx| {
let (min_edit_start, max_edit_end) = cx.update(|cx| {
let (min_edit_start, max_edit_end) = buffer.update(cx, |buffer, cx| {
buffer.edit(edits.iter().cloned(), None, cx);
let max_edit_end = buffer
.summaries_for_anchors::<Point, _>(
@ -345,7 +349,16 @@ impl EditAgent {
)
.max()
.unwrap();
buffer.anchor_before(max_edit_end)
let min_edit_start = buffer
.summaries_for_anchors::<Point, _>(
edits.iter().map(|(range, _)| &range.start),
)
.min()
.unwrap();
(
buffer.anchor_after(min_edit_start),
buffer.anchor_before(max_edit_end),
)
});
self.action_log
.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
@ -358,9 +371,10 @@ impl EditAgent {
cx,
);
});
(min_edit_start, max_edit_end)
})?;
output_events
.unbounded_send(EditAgentOutputEvent::Edited)
.unbounded_send(EditAgentOutputEvent::Edited(min_edit_start..max_edit_end))
.ok();
}
@ -755,6 +769,7 @@ mod tests {
use gpui::{AppContext, TestAppContext};
use indoc::indoc;
use language_model::fake_provider::FakeLanguageModel;
use pretty_assertions::assert_matches;
use project::{AgentLocation, Project};
use rand::prelude::*;
use rand::rngs::StdRng;
@ -992,7 +1007,10 @@ mod tests {
model.send_last_completion_stream_text_chunk("<new_text>abX");
cx.run_until_parked();
assert_eq!(drain_events(&mut events), [EditAgentOutputEvent::Edited]);
assert_matches!(
drain_events(&mut events).as_slice(),
[EditAgentOutputEvent::Edited(_)]
);
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),
"abXc\ndef\nghi\njkl"
@ -1007,7 +1025,10 @@ mod tests {
model.send_last_completion_stream_text_chunk("cY");
cx.run_until_parked();
assert_eq!(drain_events(&mut events), [EditAgentOutputEvent::Edited]);
assert_matches!(
drain_events(&mut events).as_slice(),
[EditAgentOutputEvent::Edited { .. }]
);
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),
"abXcY\ndef\nghi\njkl"
@ -1118,9 +1139,9 @@ mod tests {
model.send_last_completion_stream_text_chunk("GHI</new_text>");
cx.run_until_parked();
assert_eq!(
drain_events(&mut events),
vec![EditAgentOutputEvent::Edited]
assert_matches!(
drain_events(&mut events).as_slice(),
[EditAgentOutputEvent::Edited { .. }]
);
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),
@ -1165,9 +1186,9 @@ mod tests {
);
cx.run_until_parked();
assert_eq!(
drain_events(&mut events),
vec![EditAgentOutputEvent::Edited]
assert_matches!(
drain_events(&mut events).as_slice(),
[EditAgentOutputEvent::Edited(_)]
);
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),
@ -1183,9 +1204,9 @@ mod tests {
chunks_tx.unbounded_send("```\njkl\n").unwrap();
cx.run_until_parked();
assert_eq!(
drain_events(&mut events),
vec![EditAgentOutputEvent::Edited]
assert_matches!(
drain_events(&mut events).as_slice(),
[EditAgentOutputEvent::Edited { .. }]
);
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),
@ -1201,9 +1222,9 @@ mod tests {
chunks_tx.unbounded_send("mno\n").unwrap();
cx.run_until_parked();
assert_eq!(
drain_events(&mut events),
vec![EditAgentOutputEvent::Edited]
assert_matches!(
drain_events(&mut events).as_slice(),
[EditAgentOutputEvent::Edited { .. }]
);
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),
@ -1219,9 +1240,9 @@ mod tests {
chunks_tx.unbounded_send("pqr\n```").unwrap();
cx.run_until_parked();
assert_eq!(
drain_events(&mut events),
vec![EditAgentOutputEvent::Edited]
assert_matches!(
drain_events(&mut events).as_slice(),
[EditAgentOutputEvent::Edited(_)],
);
assert_eq!(
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),

View file

@ -307,7 +307,7 @@ impl Tool for EditFileTool {
let mut ambiguous_ranges = Vec::new();
while let Some(event) = events.next().await {
match event {
EditAgentOutputEvent::Edited => {
EditAgentOutputEvent::Edited { .. } => {
if let Some(card) = card_clone.as_ref() {
card.update(cx, |card, cx| card.update_diff(cx))?;
}