Apply local updates and test/build fixes
This commit is contained in:
@@ -577,10 +577,17 @@ fn ensure_tool_results_paired(messages: &mut Vec<ConversationMessage>) {
|
||||
let insert_idx = i + 1;
|
||||
|
||||
// If the next message is already a user message, merge synthetic results into it.
|
||||
// IMPORTANT: ToolResult blocks must come BEFORE text content in a user message
|
||||
// that follows an assistant tool_use. The Bedrock/Anthropic API validates this
|
||||
// ordering and rejects requests where text precedes tool_result.
|
||||
if insert_idx < messages.len() && messages[insert_idx].role == MessageRole::User {
|
||||
match &mut messages[insert_idx].content {
|
||||
MessageContent::MultiPart(parts) => {
|
||||
// Prepend synthetic results before existing parts so
|
||||
// tool_result blocks appear first in the content.
|
||||
let existing = std::mem::take(parts);
|
||||
parts.extend(synthetic_results);
|
||||
parts.extend(existing);
|
||||
}
|
||||
existing => {
|
||||
// Convert existing single content + synthetic results into MultiPart.
|
||||
@@ -607,11 +614,18 @@ fn ensure_tool_results_paired(messages: &mut Vec<ConversationMessage>) {
|
||||
},
|
||||
MessageContent::MultiPart(_) => unreachable!(),
|
||||
};
|
||||
let mut parts = vec![existing_part];
|
||||
parts.extend(synthetic_results);
|
||||
// Synthetic tool_result blocks come first, then the
|
||||
// original content (text), matching the Bedrock API
|
||||
// requirement that tool_result precedes other content.
|
||||
let mut parts = synthetic_results;
|
||||
parts.push(existing_part);
|
||||
*existing = MessageContent::MultiPart(parts);
|
||||
}
|
||||
}
|
||||
log::info!(
|
||||
"[bedrock] Merged synthetic tool_result(s) into existing user message at index {}",
|
||||
insert_idx
|
||||
);
|
||||
} else {
|
||||
// No user message follows — insert a new one.
|
||||
let content = if synthetic_results.len() == 1 {
|
||||
@@ -1272,3 +1286,7 @@ fn format_tool_call_result(result: &api::message::ToolCallResult) -> String {
|
||||
"Tool completed.".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "convert_request_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
use serde_json::json;
|
||||
|
||||
use super::super::convert::{ContentPart, ConversationMessage, MessageContent, MessageRole};
|
||||
use super::sanitize_messages_for_bedrock;
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_messages_prepends_synthetic_tool_result_before_existing_user_text() {
|
||||
let tool_use_id = "tooluse_Pzmn1QfoWgJsA8sb4RHTM3".to_string();
|
||||
let existing_user_text = "What happened?".to_string();
|
||||
|
||||
let mut messages = vec![
|
||||
ConversationMessage {
|
||||
role: MessageRole::User,
|
||||
content: MessageContent::Text("Run a command.".to_string()),
|
||||
},
|
||||
ConversationMessage {
|
||||
role: MessageRole::Assistant,
|
||||
content: MessageContent::ToolUse {
|
||||
tool_use_id: tool_use_id.clone(),
|
||||
name: "run_shell_command".to_string(),
|
||||
input: json!({ "command": "ls" }),
|
||||
},
|
||||
},
|
||||
ConversationMessage {
|
||||
role: MessageRole::User,
|
||||
content: MessageContent::Text(existing_user_text.clone()),
|
||||
},
|
||||
];
|
||||
|
||||
sanitize_messages_for_bedrock(&mut messages);
|
||||
|
||||
assert_eq!(messages.len(), 3);
|
||||
|
||||
let parts = match &messages[2].content {
|
||||
MessageContent::MultiPart(parts) => parts,
|
||||
other => panic!("Expected MultiPart content, got: {:?}", other),
|
||||
};
|
||||
|
||||
assert_eq!(parts.len(), 2);
|
||||
assert!(
|
||||
matches!(&parts[0], ContentPart::ToolResult { tool_use_id: id, .. } if id == &tool_use_id)
|
||||
);
|
||||
assert!(matches!(
|
||||
&parts[1],
|
||||
ContentPart::Text(text) if text == &existing_user_text
|
||||
));
|
||||
}
|
||||
@@ -555,6 +555,7 @@ impl AgentSimulation {
|
||||
8192,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
Arc::new(Mutex::new(Vec::new())),
|
||||
)
|
||||
.await
|
||||
@@ -1140,6 +1141,7 @@ async fn test_reasoning_model_produces_substantial_output() {
|
||||
8192,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
Arc::new(Mutex::new(Vec::new())),
|
||||
)
|
||||
.await
|
||||
@@ -1261,6 +1263,7 @@ async fn test_event_sequence_matches_controller_expectations() {
|
||||
100,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
Arc::new(Mutex::new(Vec::new())),
|
||||
)
|
||||
.await
|
||||
@@ -1374,6 +1377,7 @@ async fn test_followup_turn_does_not_send_create_task() {
|
||||
100,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
Arc::new(Mutex::new(Vec::new())),
|
||||
)
|
||||
.await
|
||||
@@ -1438,7 +1442,11 @@ async fn run_slash_command_test(
|
||||
let task_id = uuid::Uuid::new_v4().to_string();
|
||||
println!(
|
||||
"\n{}\n[SLASH CMD TEST] {}\n[SLASH CMD TEST] Model: {}\n[SLASH CMD TEST] Message: {:.100}\n{}",
|
||||
"=".repeat(80), test_name, model, user_message, "=".repeat(80)
|
||||
"=".repeat(80),
|
||||
test_name,
|
||||
model,
|
||||
user_message,
|
||||
"=".repeat(80)
|
||||
);
|
||||
|
||||
let stream = client
|
||||
@@ -1452,6 +1460,7 @@ async fn run_slash_command_test(
|
||||
4096,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
Arc::new(Mutex::new(Vec::new())),
|
||||
)
|
||||
.await
|
||||
@@ -1678,6 +1687,7 @@ async fn test_slash_resume_conversation() {
|
||||
256,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
Arc::new(Mutex::new(Vec::new())),
|
||||
)
|
||||
.await
|
||||
@@ -1788,6 +1798,7 @@ async fn test_empty_messages_safety_check() {
|
||||
100,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
Arc::new(Mutex::new(Vec::new())),
|
||||
)
|
||||
.await
|
||||
@@ -1829,17 +1840,26 @@ async fn test_full_proto_round_trip_with_tool_history() {
|
||||
let request = make_request(
|
||||
&task_id,
|
||||
vec![
|
||||
make_user_query_message("msg-1", &task_id, "List all files in the current directory."),
|
||||
make_user_query_message(
|
||||
"msg-1",
|
||||
&task_id,
|
||||
"List all files in the current directory.",
|
||||
),
|
||||
make_tool_call_run_shell(tool_call_id_1, &task_id, tool_call_id_1, "ls -la"),
|
||||
make_tool_call_read_files(tool_call_id_2, &task_id, tool_call_id_2, "README.md"),
|
||||
make_tool_result_shell(
|
||||
"msg-result-1", &task_id, tool_call_id_1,
|
||||
"msg-result-1",
|
||||
&task_id,
|
||||
tool_call_id_1,
|
||||
"total 16\ndrwxr-xr-x 5 user staff 160 May 6 10:00 .\n-rw-r--r-- 1 user staff 100 May 6 10:00 README.md\n-rw-r--r-- 1 user staff 200 May 6 10:00 Cargo.toml",
|
||||
0,
|
||||
),
|
||||
make_tool_result_read_files(
|
||||
"msg-result-2", &task_id, tool_call_id_2,
|
||||
"README.md", "# Sample Project\nA test project.",
|
||||
"msg-result-2",
|
||||
&task_id,
|
||||
tool_call_id_2,
|
||||
"README.md",
|
||||
"# Sample Project\nA test project.",
|
||||
),
|
||||
],
|
||||
make_user_inputs_input("Now summarize what you found in one sentence."),
|
||||
@@ -1938,6 +1958,7 @@ async fn test_full_proto_round_trip_with_tool_history() {
|
||||
1024,
|
||||
None,
|
||||
true,
|
||||
None,
|
||||
Arc::new(Mutex::new(Vec::new())),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -64,6 +64,7 @@ async fn collect_stream_output(
|
||||
8192,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
Arc::new(Mutex::new(Vec::new())),
|
||||
)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user