Lots of changes... not done yet.

This commit is contained in:
Ryan Ward
2026-08-17 18:19:37 -05:00
parent b5f3290d1a
commit 56e3b51d48
55 changed files with 4494 additions and 1098 deletions
+5 -1
View File
@@ -820,7 +820,8 @@ impl ProviderRun {
call_id: call.call.id.clone(),
})
}
PendingToolCallState::PermissionPending { .. } | PendingToolCallState::Executing => {
PendingToolCallState::Executing => Ok(()),
PendingToolCallState::PermissionPending { .. } => {
Err(invalid_tool_transition(call, "tool start"))
}
}
@@ -840,6 +841,9 @@ impl ProviderRun {
call.state = PendingToolCallState::Resolved { result };
Ok(())
}
PendingToolCallState::Resolved {
result: completed_result,
} if completed_result == &result => Ok(()),
PendingToolCallState::Resolved { .. } => {
Err(ProviderRunProtocolError::DuplicateToolUpdate {
call_id: call.call.id.clone(),
@@ -358,6 +358,89 @@ fn parallel_tool_results_commit_atomically_in_original_call_order() {
assert_eq!(ids, vec!["first", "second"]);
}
#[test]
fn duplicate_tool_start_is_idempotent() {
let mut run = run();
let batch = accept_tool_turn(
&mut run,
tool_turn(vec![tool_call("read", "read_files")], &["read_files"]),
);
run.start_tool(&batch.work_id, "read").unwrap();
let started = run.clone();
run.start_tool(&batch.work_id, "read").unwrap();
assert_eq!(run, started);
}
#[test]
fn identical_tool_completion_is_idempotent() {
let mut run = run();
let batch = accept_tool_turn(
&mut run,
tool_turn(vec![tool_call("read", "read_files")], &["read_files"]),
);
let result = successful_result("read", "contents");
run.complete_tool(&batch.work_id, result.clone()).unwrap();
let completed = run.clone();
run.complete_tool(&batch.work_id, result).unwrap();
assert_eq!(run, completed);
}
#[test]
fn conflicting_tool_completion_is_rejected_without_mutation() {
let mut run = run();
let batch = accept_tool_turn(
&mut run,
tool_turn(vec![tool_call("read", "read_files")], &["read_files"]),
);
run.complete_tool(&batch.work_id, successful_result("read", "contents"))
.unwrap();
let completed = run.clone();
assert_eq!(
run.complete_tool(&batch.work_id, successful_result("read", "different"))
.unwrap_err(),
ProviderRunProtocolError::DuplicateToolUpdate {
call_id: "read".to_string(),
}
);
assert_eq!(run, completed);
}
#[test]
fn duplicate_tool_callbacks_do_not_prevent_eventual_batch_completion() {
let mut run = run();
let batch = accept_tool_turn(
&mut run,
tool_turn(
vec![
tool_call("first", "read_files"),
tool_call("second", "grep"),
],
&["read_files", "grep"],
),
);
let first_result = successful_result("first", "one");
run.start_tool(&batch.work_id, "first").unwrap();
run.start_tool(&batch.work_id, "first").unwrap();
run.complete_tool(&batch.work_id, first_result.clone())
.unwrap();
run.complete_tool(&batch.work_id, first_result).unwrap();
run.complete_tool(&batch.work_id, successful_result("second", "two"))
.unwrap();
let ProviderRunState::AwaitingTools { batch: completed } = run.state() else {
panic!("expected completed tool batch");
};
assert!(completed.is_complete());
run.commit_tool_batch(&batch.work_id).unwrap();
assert_eq!(run.state().phase(), ProviderRunPhase::ReadyToCallModel);
}
#[test]
fn exact_batch_submission_rejects_missing_duplicate_and_unknown_results_without_mutation() {
let mut run = run();