Apply local updates and test/build fixes

This commit is contained in:
Ryan Ward
2026-05-13 15:56:40 -05:00
parent cee61e2af0
commit b61d6bcbce
36 changed files with 567 additions and 327 deletions
+1 -1
View File
@@ -260,7 +260,7 @@ pub async fn generate_multi_agent_output(
format!("MultiPart[{}]", part_descs.join(", "))
}
};
log::debug!(
log::info!(
"[bedrock] msg[{}]: role={:?}, content={}",
i,
msg.role,
+12 -11
View File
@@ -14,7 +14,7 @@ use crate::terminal::model::block::{
};
use crate::ai::agent::api::convert_conversation::{
ConvertToExchanges, compute_time_to_first_token_ms_from_messages,
compute_time_to_first_token_ms_from_messages, ConvertToExchanges,
};
use ai::document::AIDocumentId;
use chrono::{DateTime, Local, TimeZone};
@@ -29,8 +29,8 @@ use galaxy_core::execution_mode::AppExecutionMode;
use galaxy_core::features::FeatureFlag;
use galaxy_core::send_telemetry_from_ctx;
use galaxy_core::ui::appearance::Appearance;
use galaxy_core::ui::theme::WarpTheme;
use galaxy_core::ui::theme::color::internal_colors;
use galaxy_core::ui::theme::WarpTheme;
use galaxyui::color::ColorU;
use galaxyui::{EntityId, ModelContext, SingletonEntity};
use uuid::Uuid;
@@ -40,35 +40,36 @@ use warp_multi_agent_api::{self as api, response_event::stream_finished::TokenUs
use crate::ai::agent::{AIIdentifiers, CancellationReason};
use crate::{
BlocklistAIHistoryModel, GlobalResourceHandlesProvider,
ai::{
agent::{
AIAgentOutputMessage, AIAgentOutputMessageType, MessageToAIAgentOutputMessageError,
icons::{
failed_icon, gray_stop_icon, in_progress_icon, succeeded_icon, yellow_stop_icon,
},
todos::AIAgentTodoList,
AIAgentOutputMessage, AIAgentOutputMessageType, MessageToAIAgentOutputMessageError,
},
blocklist::BlocklistAIHistoryEvent,
},
persistence::{
ModelEvent,
model::{AgentConversationData, PersistedAutoexecuteMode},
ModelEvent,
},
ui_components::icons::Icon,
BlocklistAIHistoryModel, GlobalResourceHandlesProvider,
};
use super::task::{ExtractMessagesError, UpdateTaskError, UpgradeOptimisticTaskError};
use super::{
api::ServerConversationToken,
task::{
derive_todo_lists_from_root_task,
helper::*,
transaction::{SavedTask, Transaction},
Task, TaskId,
},
AIAgentAction, AIAgentActionId, AIAgentContext, AIAgentExchange, AIAgentExchangeId,
AIAgentInput, AIAgentOutputStatus, AIAgentTodo, AIAgentTodoId, FinishedAIAgentOutput,
MessageId, RenderableAIError, RequestCost,
api::ServerConversationToken,
task::{
Task, TaskId, derive_todo_lists_from_root_task,
helper::*,
transaction::{SavedTask, Transaction},
},
};
use super::{
AIAgentOutput, OutputModelInfo, ServerOutputId, Shared, SuggestedLoggingId, Suggestions,
+1 -1
View File
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use super::{
AIConversation, AIConversationAutoexecuteMode, AIConversationId, artifact_from_fork_proto,
artifact_from_fork_proto, AIConversation, AIConversationAutoexecuteMode, AIConversationId,
};
use crate::ai::artifacts::Artifact;
use crate::persistence::model::AgentConversationData;
+20 -2
View File
@@ -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
));
}
+26 -5
View File
@@ -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;
+1
View File
@@ -64,6 +64,7 @@ async fn collect_stream_output(
8192,
None,
false,
None,
Arc::new(Mutex::new(Vec::new())),
)
.await