Make direct-provider agent runs durable

This commit is contained in:
2026-08-14 22:02:15 -05:00
parent f4a04d0240
commit b079f036fa
50 changed files with 9473 additions and 3189 deletions
+61 -3
View File
@@ -4,7 +4,8 @@ use std::sync::Arc;
use super::*;
use crate::ai::agent::task::TaskId;
use crate::ai::agent::{
AIAgentActionResultType, AnyFileContent, FileContext, GrepResult, ReadFilesResult,
AIAgentAction, AIAgentActionResultType, AIAgentActionType, AnyFileContent, FileContext,
GrepResult, ReadFilesResult,
};
fn make_action_result(id: &str) -> Arc<AIAgentActionResult> {
@@ -23,6 +24,36 @@ fn action_result(id: &str, result: AIAgentActionResultType) -> AIAgentActionResu
}
}
fn action(id: &str) -> AIAgentAction {
AIAgentAction {
id: AIAgentActionId::from(id.to_string()),
action: AIAgentActionType::InitProject,
task_id: TaskId::new("task".to_string()),
requires_result: true,
tool_name: Some("init_project".to_string()),
}
}
fn pending_tool_batch(call_ids: &[&str]) -> PendingToolBatch {
PendingToolBatch {
work_id: galaxy_agent_core::ExternalWorkId {
run_id: galaxy_agent_core::ProviderRunId::new("run"),
epoch: galaxy_agent_core::RunEpoch::new(7),
},
calls: call_ids
.iter()
.map(|call_id| galaxy_agent_core::PendingToolCall {
call: galaxy_agent_core::ToolCall {
id: (*call_id).to_string(),
name: "init_project".to_string(),
arguments: serde_json::json!({}),
},
state: galaxy_agent_core::PendingToolCallState::Proposed,
})
.collect(),
}
}
fn count_startable_actions_for_pass(phases: &[(RunningActionPhase, bool)]) -> usize {
let mut current_phase = None;
let mut count = 0;
@@ -45,6 +76,34 @@ fn count_startable_actions_for_pass(phases: &[(RunningActionPhase, bool)]) -> us
count
}
#[test]
fn provider_action_correlations_require_the_exact_unresolved_batch_order() {
let conversation_id = AIConversationId::new();
let batch = pending_tool_batch(&["first", "second"]);
let actions = vec![action("first"), action("second")];
let correlations = provider_action_correlations(&actions, conversation_id, &batch).unwrap();
assert_eq!(correlations.len(), 2);
assert_eq!(correlations[0].0, (conversation_id, actions[0].id.clone()));
assert_eq!(correlations[0].1.run_id, batch.work_id.run_id);
assert_eq!(correlations[0].1.epoch, batch.work_id.epoch);
assert_eq!(correlations[0].1.call_id, "first");
let error = provider_action_correlations(
&[action("second"), action("first")],
conversation_id,
&batch,
)
.unwrap_err();
assert_eq!(
error,
ProviderActionQueueError::ActionSetMismatch {
expected: vec!["first".to_string(), "second".to_string()],
received: vec!["second".to_string(), "first".to_string()],
}
);
}
#[test]
fn parallel_phase_only_admits_matching_autoexecutable_actions() {
let phase =
@@ -94,8 +153,7 @@ fn finished_results_stay_in_original_action_order() {
make_action_result("second"),
];
finished_results
.sort_by_key(|result| action_order.get(&result.id).copied().unwrap_or(usize::MAX));
sort_action_results_by_order(&mut finished_results, &action_order);
assert_eq!(
finished_results[0].id,