Fix child startup readiness and ordered provider tools

Wait for shell bootstrap before dispatching child prompts, serialize provider preprocessing, and make permission callbacks idempotent. Restrict task lists to concrete multistep plans and stop inferring completion from tool activity.

Update regression fixtures and resolve existing lint and test-layout failures. Verified formatting, both presubmit Clippy commands, and 354 targeted nextest tests.
This commit is contained in:
2026-09-09 13:55:34 -05:00
parent 4b703c019f
commit 1845b9e20e
41 changed files with 1097 additions and 895 deletions
+2 -116
View File
@@ -519,122 +519,8 @@ fn text_indicates_authentication_failure(text: &str) -> bool {
}
#[cfg(test)]
mod tests {
use galaxy_agent_core::{AgentErrorKind, StopReason};
use rig_core::completion::CompletionError;
use super::{
completion_error_stop_reason, domain_tool_call, is_keepalive_event, map_completion_error,
};
#[test]
fn domain_tool_call_prefers_responses_call_id() {
let tool_call = rig_core::message::ToolCall::new(
"fc_item_123".to_string(),
rig_core::message::ToolFunction {
name: "read_files".to_string(),
arguments: serde_json::json!({"files": ["Cargo.toml"]}),
},
)
.with_call_id("call_123".to_string());
let call = domain_tool_call(tool_call);
assert_eq!(call.id, "call_123");
assert_eq!(call.name, "read_files");
}
#[test]
fn domain_tool_call_falls_back_to_wire_id_without_responses_call_id() {
let tool_call = rig_core::message::ToolCall::new(
"fc_item_123".to_string(),
rig_core::message::ToolFunction {
name: "read_files".to_string(),
arguments: serde_json::json!({"files": ["Cargo.toml"]}),
},
);
let call = domain_tool_call(tool_call);
assert_eq!(call.id, "fc_item_123");
assert_eq!(call.name, "read_files");
}
#[test]
fn provider_context_window_error_maps_to_semantic_stop_reason_and_kind() {
let status = rig_core::http_client::Response::builder()
.status(400)
.body(())
.unwrap()
.status();
let error = CompletionError::from_http_response(
status,
r#"{"error":{"message":"Your input exceeds the context window of this model. Please adjust your input and try again.","code":"400"}}"#,
);
assert_eq!(
completion_error_stop_reason(&error),
Some(StopReason::ContextWindowExceeded)
);
let mapped = map_completion_error(error);
assert_eq!(mapped.kind, AgentErrorKind::ContextWindowExceeded);
assert!(!mapped.recoverable);
}
#[test]
fn provider_context_length_string_maps_to_semantic_stop_reason() {
let error = CompletionError::ProviderError(
"context_length_exceeded: maximum context length is 128000 tokens".to_string(),
);
assert_eq!(
completion_error_stop_reason(&error),
Some(StopReason::ContextWindowExceeded)
);
}
#[test]
fn provider_keepalive_event_is_a_transport_heartbeat() {
assert!(is_keepalive_event(&serde_json::json!({
"type": "keepalive",
"sequence_number": 3,
})));
assert!(!is_keepalive_event(&serde_json::json!({
"type": "unsupported",
})));
}
#[test]
fn provider_server_error_is_recoverable() {
let status = rig_core::http_client::Response::builder()
.status(503)
.body(())
.unwrap()
.status();
let error = CompletionError::from_http_response(
status,
r#"{"error":{"message":"Service temporarily unavailable"}}"#,
);
let mapped = map_completion_error(error);
assert_eq!(mapped.kind, AgentErrorKind::Provider);
assert!(mapped.recoverable);
}
#[test]
fn flattened_bedrock_credential_errors_are_authentication_failures() {
for message in [
r#"{"__type":"ExpiredTokenException","message":"The security token included in the request is expired"}"#,
"UnrecognizedClientException: The security token included in the request is invalid",
"AccessDeniedException: not authorized to perform bedrock:ConverseStream",
] {
let mapped = map_completion_error(CompletionError::ProviderError(message.to_string()));
assert_eq!(mapped.kind, AgentErrorKind::Authentication);
assert!(!mapped.recoverable);
assert!(mapped.user_message.is_some());
}
}
}
#[path = "stream_unit_tests.rs"]
mod tests;
#[cfg(test)]
#[path = "stream_tests.rs"]