Apply local updates and test/build fixes
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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