Edit tool debugging (#26637)

Adds an `debug: edit tool` action that opens a new view which will help
us debug the edit tool internals. As the edit tool runs, the log
displays:

- Instructions provided by the main model
- Response stream from the editor model
- Parsed edit blocks
- Tool output provided back to main model

The log automatically records all edit tool interactions for staff, so
if you notice something weird, you can debug it retroactively without
having to open the debug tool first. We may want to limit the number of
recorded requests later.

I have a few more ideas for it, but this seems like a good starting
point.


https://github.com/user-attachments/assets/c61f5ce8-08b1-4500-accb-db2a480eb3ab


Release Notes:

- N/A
This commit is contained in:
Agus Zubiaga 2025-03-13 01:03:01 -03:00 committed by GitHub
parent 0081b816fe
commit 606aa7a78c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 486 additions and 2 deletions

View file

@ -1,4 +1,5 @@
mod edit_action;
pub mod log;
use anyhow::{anyhow, Context, Result};
use assistant_tool::Tool;
@ -9,11 +10,13 @@ use gpui::{App, Entity, Task};
use language_model::{
LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,
};
use log::{EditToolLog, EditToolRequestId};
use project::{Project, ProjectPath};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::Write;
use std::sync::Arc;
use util::ResultExt;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct EditFilesToolInput {
@ -60,6 +63,45 @@ impl Tool for EditFilesTool {
Err(err) => return Task::ready(Err(anyhow!(err))),
};
match EditToolLog::try_global(cx) {
Some(log) => {
let req_id = log.update(cx, |log, cx| {
log.new_request(input.edit_instructions.clone(), cx)
});
let task =
EditFilesTool::run(input, messages, project, Some((log.clone(), req_id)), cx);
cx.spawn(|mut cx| async move {
let result = task.await;
let str_result = match &result {
Ok(out) => Ok(out.clone()),
Err(err) => Err(err.to_string()),
};
log.update(&mut cx, |log, cx| {
log.set_tool_output(req_id, str_result, cx)
})
.log_err();
result
})
}
None => EditFilesTool::run(input, messages, project, None, cx),
}
}
}
impl EditFilesTool {
fn run(
input: EditFilesToolInput,
messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
log: Option<(Entity<EditToolLog>, EditToolRequestId)>,
cx: &mut App,
) -> Task<Result<String>> {
let model_registry = LanguageModelRegistry::read_global(cx);
let Some(model) = model_registry.editor_model() else {
return Task::ready(Err(anyhow!("No editor model configured")));
@ -97,8 +139,21 @@ impl Tool for EditFilesTool {
let mut changed_buffers = HashSet::default();
let mut applied_edits = 0;
let log = log.clone();
while let Some(chunk) = chunks.stream.next().await {
for action in parser.parse_chunk(&chunk?) {
let chunk = chunk?;
let new_actions = parser.parse_chunk(&chunk);
if let Some((ref log, req_id)) = log {
log.update(&mut cx, |log, cx| {
log.push_editor_response_chunk(req_id, &chunk, &new_actions, cx)
})
.log_err();
}
for action in new_actions {
let project_path = project.read_with(&cx, |project, cx| {
let worktree_root_name = action
.file_path()
@ -157,7 +212,7 @@ impl Tool for EditFilesTool {
project
.update(&mut cx, |project, cx| {
if let Some(file) = buffer.read(&cx).file() {
let _ = write!(&mut answer, "{}\n\n", &file.path().display());
let _ = writeln!(&mut answer, "{}", &file.path().display());
}
project.save_buffer(buffer, cx)