mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-10-10 01:42:44 +08:00
140 lines
4.4 KiB
Rust
140 lines
4.4 KiB
Rust
use super::*;
|
|
use serde_json::json;
|
|
|
|
fn edit_call(index: usize, call_id: &str, path: &str, old: &str, new: &str) -> ToolCall {
|
|
ToolCall {
|
|
index,
|
|
call_id: call_id.into(),
|
|
model_call_id: "model:0".into(),
|
|
name: "StrReplace".into(),
|
|
arguments_text: String::new(),
|
|
arguments: json!({
|
|
"path": path,
|
|
"old_string": old,
|
|
"new_string": new,
|
|
}),
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn same_path_edits_start_one_at_a_time() {
|
|
let runtime = CursorToolRuntime::default();
|
|
let dispatcher = ToolDispatcher::new(runtime.clone());
|
|
let calls = [
|
|
edit_call(0, "first", "/tmp/a.txt", "left", "LEFT"),
|
|
edit_call(1, "second", "/tmp/a.txt", "right", "RIGHT"),
|
|
edit_call(2, "other", "/tmp/b.txt", "other", "OTHER"),
|
|
];
|
|
|
|
let dispatched = dispatcher
|
|
.start_batch(
|
|
&calls,
|
|
ToolBatchState {
|
|
completed: &HashSet::new(),
|
|
started: &HashSet::new(),
|
|
response_text: "",
|
|
response_thinking: "",
|
|
},
|
|
&[],
|
|
&BTreeMap::new(),
|
|
&ExecContext::default(),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(dispatched.len(), 2);
|
|
assert_eq!(exec(&dispatched[0]).exec_id, "first");
|
|
assert_eq!(exec(&dispatched[1]).exec_id, "other");
|
|
|
|
let mut file = "left right\n".to_string();
|
|
let first_write = advance_read(&runtime, exec(&dispatched[0]).id, &file).await;
|
|
file = write_text(&first_write);
|
|
assert_eq!(file, "LEFT right\n");
|
|
complete_write(&runtime, &first_write).await;
|
|
|
|
let second = dispatcher
|
|
.continue_after("first")
|
|
.await
|
|
.unwrap()
|
|
.expect("second same-path edit should start after the first completes");
|
|
assert_eq!(exec(&second).exec_id, "second");
|
|
let second_write = advance_read(&runtime, exec(&second).id, &file).await;
|
|
file = write_text(&second_write);
|
|
assert_eq!(file, "LEFT RIGHT\n");
|
|
complete_write(&runtime, &second_write).await;
|
|
assert!(dispatcher.continue_after("second").await.unwrap().is_none());
|
|
}
|
|
|
|
fn exec(dispatched: &DispatchedTool) -> &pb::ExecServerMessage {
|
|
dispatched
|
|
.messages
|
|
.iter()
|
|
.find_map(|message| match message.message.as_ref() {
|
|
Some(pb::agent_server_message::Message::ExecServerMessage(exec)) => Some(exec),
|
|
_ => None,
|
|
})
|
|
.expect("dispatched edit should contain an Exec request")
|
|
}
|
|
|
|
async fn advance_read(
|
|
runtime: &CursorToolRuntime,
|
|
id: u32,
|
|
content: &str,
|
|
) -> pb::ExecServerMessage {
|
|
let event = codec::client_event(
|
|
&pb::ExecClientMessage {
|
|
id,
|
|
message: Some(pb::exec_client_message::Message::ReadResult(
|
|
pb::ReadResult {
|
|
result: Some(pb::read_result::Result::Success(pb::ReadSuccess {
|
|
output: Some(pb::read_success::Output::Content(content.into())),
|
|
..Default::default()
|
|
})),
|
|
},
|
|
)),
|
|
..Default::default()
|
|
},
|
|
runtime,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
let codec::ClientExecEvent::Message(message) = event else {
|
|
panic!("edit read should advance to a write")
|
|
};
|
|
let Some(pb::agent_server_message::Message::ExecServerMessage(exec)) = message.message else {
|
|
panic!("edit read should emit an Exec write request")
|
|
};
|
|
exec
|
|
}
|
|
|
|
fn write_text(exec: &pb::ExecServerMessage) -> String {
|
|
let Some(pb::exec_server_message::Message::WriteArgs(args)) = exec.message.as_ref() else {
|
|
panic!("expected WriteArgs")
|
|
};
|
|
args.file_text.clone()
|
|
}
|
|
|
|
async fn complete_write(runtime: &CursorToolRuntime, exec: &pb::ExecServerMessage) {
|
|
let Some(pb::exec_server_message::Message::WriteArgs(args)) = exec.message.as_ref() else {
|
|
panic!("expected WriteArgs")
|
|
};
|
|
let event = codec::client_event(
|
|
&pb::ExecClientMessage {
|
|
id: exec.id,
|
|
message: Some(pb::exec_client_message::Message::WriteResult(
|
|
pb::WriteResult {
|
|
result: Some(pb::write_result::Result::Success(pb::WriteSuccess {
|
|
path: args.path.clone(),
|
|
..Default::default()
|
|
})),
|
|
},
|
|
)),
|
|
..Default::default()
|
|
},
|
|
runtime,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
assert!(matches!(event, codec::ClientExecEvent::Completed(_)));
|
|
}
|