Fix cursor focus and selection in input box, add AWS env var warning box, and remove AWS Bedrock login banner
This commit is contained in:
@@ -2,17 +2,17 @@ use std::sync::{Arc, Mutex};
|
||||
|
||||
use anyhow::Result;
|
||||
use aws_config::BehaviorVersion;
|
||||
use aws_credential_types::provider::ProvideCredentials;
|
||||
use aws_sdk_bedrockruntime::config::Region;
|
||||
use aws_sdk_bedrockruntime::Client as BedrockRuntimeClient;
|
||||
|
||||
use crate::settings::ai::BedrockAuthMethod;
|
||||
|
||||
use super::convert::{build_converse_request, CachingConfig, ConversationMessage, ToolDefinition};
|
||||
use super::diagnostic::BedrockDiagnosticLogger;
|
||||
use super::external_config::ExternalBedrockConfig;
|
||||
use super::models::apply_cross_region_prefix;
|
||||
use super::response_translator::bedrock_stream_to_response_events;
|
||||
use crate::ai::agent::api::ResponseStream;
|
||||
use crate::settings::ai::BedrockAuthMethod;
|
||||
|
||||
fn strip_context_marker(model_id: &str) -> String {
|
||||
if let Some(base) = model_id.strip_suffix("[1m]") {
|
||||
@@ -36,6 +36,7 @@ pub struct BedrockClientConfig {
|
||||
pub region: String,
|
||||
pub access_key_id: String,
|
||||
pub secret_access_key: String,
|
||||
pub session_token: Option<String>,
|
||||
pub cross_region_inference: bool,
|
||||
}
|
||||
|
||||
@@ -86,10 +87,23 @@ pub enum BedrockError {
|
||||
|
||||
impl BedrockClient {
|
||||
pub async fn from_config(config: BedrockClientConfig) -> Result<Self, BedrockError> {
|
||||
log::info!(
|
||||
"[bedrock] from_config input: auth_method={:?}, profile={:?}, region={:?}, access_key_id_set={}, secret_access_key_set={}, session_token_set={}",
|
||||
config.auth_method,
|
||||
config.profile,
|
||||
config.region,
|
||||
!config.access_key_id.is_empty(),
|
||||
!config.secret_access_key.is_empty(),
|
||||
config.session_token.is_some(),
|
||||
);
|
||||
|
||||
let aws_config = match config.auth_method {
|
||||
BedrockAuthMethod::Profile | BedrockAuthMethod::Sso => {
|
||||
let mut loader =
|
||||
aws_config::defaults(BehaviorVersion::latest()).profile_name(&config.profile);
|
||||
let mut loader = aws_config::defaults(BehaviorVersion::latest());
|
||||
|
||||
if !config.profile.is_empty() && config.profile != "default" {
|
||||
loader = loader.profile_name(&config.profile);
|
||||
}
|
||||
|
||||
if !config.region.is_empty() {
|
||||
loader = loader.region(Region::new(config.region.clone()));
|
||||
@@ -105,7 +119,7 @@ impl BedrockClient {
|
||||
let creds = aws_credential_types::Credentials::new(
|
||||
&config.access_key_id,
|
||||
&config.secret_access_key,
|
||||
None,
|
||||
config.session_token,
|
||||
None,
|
||||
"warp-bedrock-static",
|
||||
);
|
||||
@@ -123,6 +137,24 @@ impl BedrockClient {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(provider) = aws_config.credentials_provider() {
|
||||
match provider.provide_credentials().await {
|
||||
Ok(creds) => {
|
||||
log::info!(
|
||||
"[bedrock] Resolved AWS credentials successfully: access_key_id={:?}, has_session_token={}, expiry={:?}",
|
||||
creds.access_key_id(),
|
||||
creds.session_token().is_some(),
|
||||
creds.expiry(),
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("[bedrock] Failed to resolve AWS credentials from provider: {e:?}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!("[bedrock] No credentials provider found in resolved AWS config");
|
||||
}
|
||||
|
||||
let region = aws_config
|
||||
.region()
|
||||
.map(|r| r.to_string())
|
||||
@@ -136,6 +168,7 @@ impl BedrockClient {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn converse_stream(
|
||||
&self,
|
||||
model_id: &str,
|
||||
@@ -174,6 +207,13 @@ impl BedrockClient {
|
||||
tools.len()
|
||||
);
|
||||
|
||||
log::info!(
|
||||
"[bedrock] Sending request payload to Bedrock:\nSystem Prompt: {:?}\nMessages: {:#?}\nTools: {:#?}",
|
||||
system_prompt,
|
||||
messages,
|
||||
tools
|
||||
);
|
||||
|
||||
let converted = build_converse_request(
|
||||
messages.clone(),
|
||||
system_prompt.clone(),
|
||||
@@ -302,12 +342,10 @@ impl BedrockClient {
|
||||
})?;
|
||||
|
||||
let mut response_text = String::new();
|
||||
if let Some(output_msg) = output.output() {
|
||||
if let aws_sdk_bedrockruntime::types::ConverseOutput::Message(msg) = output_msg {
|
||||
for block in msg.content() {
|
||||
if let aws_sdk_bedrockruntime::types::ContentBlock::Text(text) = block {
|
||||
response_text.push_str(text);
|
||||
}
|
||||
if let Some(aws_sdk_bedrockruntime::types::ConverseOutput::Message(msg)) = output.output() {
|
||||
for block in msg.content() {
|
||||
if let aws_sdk_bedrockruntime::types::ContentBlock::Text(text) = block {
|
||||
response_text.push_str(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ use aws_smithy_types::Document;
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
use super::external_config::ExternalBedrockConfig;
|
||||
|
||||
// Re-export shared provider types so existing imports from bedrock::convert continue to work.
|
||||
pub use crate::ai::provider::types::{
|
||||
ContentPart, ConversationMessage, MessageContent, MessageRole, ToolDefinition,
|
||||
@@ -47,6 +46,7 @@ pub struct ConvertedRequest {
|
||||
pub tool_config: Option<ToolConfiguration>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn build_converse_request(
|
||||
messages: Vec<ConversationMessage>,
|
||||
system_prompt: Option<String>,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use aws_sdk_bedrockruntime::types::{ContentBlock, ConversationRole};
|
||||
use aws_sdk_bedrockruntime::types::{ContentBlock, ConversationRole, SystemContentBlock, Tool};
|
||||
use serde_json::json;
|
||||
|
||||
use super::convert::*;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use chrono::{Local, Utc};
|
||||
use serde_json::Value as JsonValue;
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::fs::{self, File, OpenOptions};
|
||||
use std::io::{BufWriter, Read, Seek, SeekFrom, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
|
||||
use chrono::{Local, Utc};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
use super::convert::{ConversationMessage, MessageContent, MessageRole, ToolDefinition};
|
||||
|
||||
const ENV_VAR: &str = "GALAXY_BEDROCK_DIAGNOSTICS";
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use futures::StreamExt;
|
||||
use serde_json::json;
|
||||
use std::path::PathBuf;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use super::client::{BedrockClient, BedrockClientConfig};
|
||||
use super::convert::{ConversationMessage, MessageContent, MessageRole, ToolDefinition};
|
||||
use crate::settings::ai::BedrockAuthMethod;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
fn make_user_query_message(id: &str, task_id: &str, query: &str) -> api::Message {
|
||||
api::Message {
|
||||
@@ -24,6 +24,7 @@ fn make_user_query_message(id: &str, task_id: &str, query: &str) -> api::Message
|
||||
mode: None,
|
||||
intended_agent: 0,
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +55,7 @@ fn make_tool_call_run_shell(
|
||||
},
|
||||
)),
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +83,7 @@ fn make_tool_call_read_files(
|
||||
},
|
||||
)),
|
||||
})),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,12 +116,14 @@ fn make_tool_result_shell(
|
||||
output: output.into(),
|
||||
exit_code,
|
||||
command_id: String::new(),
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
},
|
||||
)),
|
||||
},
|
||||
)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +160,7 @@ fn make_tool_result_read_files(
|
||||
)),
|
||||
},
|
||||
)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +223,7 @@ fn make_settings(model: &str) -> api::request::Settings {
|
||||
supports_bundled_skills: false,
|
||||
supports_research_agent: false,
|
||||
supports_orchestration_v2: false,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,6 +267,7 @@ fn get_test_config() -> Option<BedrockClientConfig> {
|
||||
region,
|
||||
access_key_id: String::new(),
|
||||
secret_access_key: String::new(),
|
||||
session_token: None,
|
||||
cross_region_inference: false,
|
||||
})
|
||||
}
|
||||
@@ -691,7 +699,7 @@ impl AgentSimulation {
|
||||
match name {
|
||||
"run_shell_command" => {
|
||||
let command = input["command"].as_str().unwrap_or("echo 'no command'");
|
||||
let output = std::process::Command::new("sh")
|
||||
let output = command::blocking::Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(command)
|
||||
.current_dir(&self.project_path)
|
||||
@@ -759,7 +767,7 @@ impl AgentSimulation {
|
||||
.unwrap_or(self.project_path.to_str().unwrap_or("."));
|
||||
let mut result = String::new();
|
||||
for query in queries {
|
||||
let output = std::process::Command::new("grep")
|
||||
let output = command::blocking::Command::new("grep")
|
||||
.args(["-rn", query, path])
|
||||
.output();
|
||||
if let Ok(out) = output {
|
||||
@@ -999,7 +1007,7 @@ async fn test_agent_multi_turn_tool_use_produces_output() {
|
||||
let tool_use_id = format!("tool_{}", total_turns);
|
||||
let (result, is_error) = match tool.name.as_str() {
|
||||
"run_shell_command" => {
|
||||
let ls_output = std::process::Command::new("ls")
|
||||
let ls_output = command::blocking::Command::new("ls")
|
||||
.arg("-la")
|
||||
.current_dir(&project_path)
|
||||
.output()
|
||||
@@ -1075,7 +1083,7 @@ async fn test_agent_multi_turn_tool_use_produces_output() {
|
||||
// 2. No partial reasoning fragments leak to the UI
|
||||
// 3. The stream protocol is correct (Init, CreateTask, content, Finished)
|
||||
assert!(
|
||||
sim.all_text_output.len() > 0 || total_turns > 1,
|
||||
!sim.all_text_output.is_empty() || total_turns > 1,
|
||||
"Agent should either produce text or make multiple tool calls to explore"
|
||||
);
|
||||
|
||||
@@ -1160,39 +1168,34 @@ async fn test_reasoning_model_produces_substantial_output() {
|
||||
while let Some(event_result) = stream.next().await {
|
||||
event_count += 1;
|
||||
let event = event_result.expect("event should be Ok");
|
||||
if let Some(event_type) = &event.r#type {
|
||||
match event_type {
|
||||
api::response_event::Type::ClientActions(actions) => {
|
||||
for action in &actions.actions {
|
||||
if let Some(action_type) = &action.action {
|
||||
match action_type {
|
||||
api::client_action::Action::CreateTask(_) => {
|
||||
had_create_task = true;
|
||||
if let Some(api::response_event::Type::ClientActions(actions)) = &event.r#type {
|
||||
for action in &actions.actions {
|
||||
if let Some(action_type) = &action.action {
|
||||
match action_type {
|
||||
api::client_action::Action::CreateTask(_) => {
|
||||
had_create_task = true;
|
||||
}
|
||||
api::client_action::Action::AddMessagesToTask(add) => {
|
||||
for msg in &add.messages {
|
||||
if let Some(api::message::Message::AgentOutput(output)) =
|
||||
&msg.message
|
||||
{
|
||||
total_text.push_str(&output.text);
|
||||
}
|
||||
api::client_action::Action::AddMessagesToTask(add) => {
|
||||
for msg in &add.messages {
|
||||
if let Some(api::message::Message::AgentOutput(output)) =
|
||||
&msg.message
|
||||
{
|
||||
total_text.push_str(&output.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
api::client_action::Action::AppendToMessageContent(append) => {
|
||||
if let Some(msg) = &append.message {
|
||||
if let Some(api::message::Message::AgentOutput(output)) =
|
||||
&msg.message
|
||||
{
|
||||
total_text.push_str(&output.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
api::client_action::Action::AppendToMessageContent(append) => {
|
||||
if let Some(msg) = &append.message {
|
||||
if let Some(api::message::Message::AgentOutput(output)) =
|
||||
&msg.message
|
||||
{
|
||||
total_text.push_str(&output.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1610,7 +1613,7 @@ async fn test_slash_create_new_project() {
|
||||
|
||||
if !events.is_empty() {
|
||||
assert!(
|
||||
text.len() > 0
|
||||
!text.is_empty()
|
||||
|| events
|
||||
.iter()
|
||||
.any(|e| matches!(e.event_type, StreamEventType::ToolCallMessage { .. })),
|
||||
@@ -1645,7 +1648,7 @@ async fn test_slash_auto_code_diff() {
|
||||
assert_valid_stream(&events, "slash_auto_code_diff");
|
||||
|
||||
if !events.is_empty() {
|
||||
let has_action = text.len() > 0
|
||||
let has_action = !text.is_empty()
|
||||
|| events
|
||||
.iter()
|
||||
.any(|e| matches!(e.event_type, StreamEventType::ToolCallMessage { .. }));
|
||||
@@ -1775,7 +1778,7 @@ async fn test_slash_query_with_canned_response() {
|
||||
assert_valid_stream(&events, "slash_query_with_canned_response");
|
||||
|
||||
if !events.is_empty() {
|
||||
let has_output = text.len() > 0
|
||||
let has_output = !text.is_empty()
|
||||
|| events
|
||||
.iter()
|
||||
.any(|e| matches!(e.event_type, StreamEventType::ToolCallMessage { .. }));
|
||||
|
||||
@@ -144,7 +144,6 @@ fn parse_claude_code_model_map(
|
||||
model_id: arn,
|
||||
display_name,
|
||||
vision_supported: true,
|
||||
context_size: 200_000,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -22,6 +22,7 @@ fn get_test_config() -> Option<BedrockClientConfig> {
|
||||
region,
|
||||
access_key_id: String::new(),
|
||||
secret_access_key: String::new(),
|
||||
session_token: None,
|
||||
cross_region_inference: false,
|
||||
})
|
||||
}
|
||||
@@ -81,6 +82,7 @@ async fn collect_stream_output(
|
||||
while let Some(event) = stream.next().await {
|
||||
let event = event.expect("stream event should be Ok");
|
||||
if let Some(event_type) = event.r#type {
|
||||
use warp_multi_agent_api::message::Message;
|
||||
use warp_multi_agent_api::response_event::Type;
|
||||
match event_type {
|
||||
Type::ClientActions(actions) => {
|
||||
@@ -90,24 +92,16 @@ async fn collect_stream_output(
|
||||
match action_type {
|
||||
Action::AddMessagesToTask(add) => {
|
||||
for msg in add.messages {
|
||||
if let Some(msg_content) = msg.message {
|
||||
use warp_multi_agent_api::message::Message;
|
||||
match msg_content {
|
||||
Message::AgentOutput(output) => {
|
||||
text.push_str(&output.text);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if let Some(Message::AgentOutput(output)) = msg.message {
|
||||
text.push_str(&output.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::AppendToMessageContent(append) => {
|
||||
if let Some(msg) = append.message {
|
||||
if let Some(msg_content) = msg.message {
|
||||
if let Message::AgentOutput(output) = msg_content {
|
||||
text.push_str(&output.text);
|
||||
}
|
||||
}
|
||||
if let Some(Message::AgentOutput(output)) =
|
||||
append.message.and_then(|m| m.message)
|
||||
{
|
||||
text.push_str(&output.text);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -553,9 +547,10 @@ async fn test_arn_based_model() {
|
||||
return;
|
||||
};
|
||||
|
||||
let arn = std::env::var("BEDROCK_TEST_ARN").unwrap_or_else(|_| {
|
||||
"arn:aws:bedrock:us-east-1:156729649053:application-inference-profile/1tim45pgo320".into()
|
||||
});
|
||||
let Some(arn) = std::env::var("BEDROCK_TEST_ARN").ok() else {
|
||||
eprintln!("Skipping: BEDROCK_TEST_ARN not set");
|
||||
return;
|
||||
};
|
||||
|
||||
let client = BedrockClient::from_config(config)
|
||||
.await
|
||||
@@ -696,3 +691,100 @@ async fn test_all_tools_visible_to_model() {
|
||||
tools.len()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bedrock_integration_live_stream() {
|
||||
let Some(config) = get_test_config() else {
|
||||
eprintln!("Skipping: BEDROCK_INTEGRATION_TEST not set");
|
||||
return;
|
||||
};
|
||||
|
||||
println!("[live-test] Initializing Bedrock client...");
|
||||
let client = BedrockClient::from_config(config)
|
||||
.await
|
||||
.expect("client creation");
|
||||
let model = get_test_model();
|
||||
println!("[live-test] Using model: {model}");
|
||||
|
||||
let messages = vec![ConversationMessage {
|
||||
role: MessageRole::User,
|
||||
content: MessageContent::Text(
|
||||
"Write a 2-line poem about antigravity. Output only the poem.".into(),
|
||||
),
|
||||
}];
|
||||
|
||||
println!("[live-test] Requesting stream response from Bedrock...");
|
||||
let stream = client
|
||||
.converse_stream(
|
||||
&model,
|
||||
"live-test-task-id",
|
||||
true,
|
||||
messages,
|
||||
None,
|
||||
None,
|
||||
vec![],
|
||||
1024,
|
||||
None,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
Arc::new(Mutex::new(Vec::new())),
|
||||
Vec::new(),
|
||||
)
|
||||
.await
|
||||
.expect("converse_stream should succeed");
|
||||
|
||||
let mut full_text = String::new();
|
||||
let mut stream = stream;
|
||||
|
||||
while let Some(event) = stream.next().await {
|
||||
let event = event.expect("stream event should be Ok");
|
||||
if let Some(event_type) = event.r#type {
|
||||
use warp_multi_agent_api::message::Message;
|
||||
use warp_multi_agent_api::response_event::Type;
|
||||
match event_type {
|
||||
Type::ClientActions(actions) => {
|
||||
for action in actions.actions {
|
||||
if let Some(action_type) = action.action {
|
||||
use warp_multi_agent_api::client_action::Action;
|
||||
match action_type {
|
||||
Action::AddMessagesToTask(add) => {
|
||||
for msg in add.messages {
|
||||
if let Some(Message::AgentOutput(output)) = msg.message {
|
||||
print!("{}", output.text);
|
||||
full_text.push_str(&output.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::AppendToMessageContent(append) => {
|
||||
if let Some(Message::AgentOutput(output)) =
|
||||
append.message.and_then(|m| m.message)
|
||||
{
|
||||
print!("{}", output.text);
|
||||
full_text.push_str(&output.text);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Type::Finished(finished) => {
|
||||
println!(
|
||||
"\n[live-test] Stream finished. Reason: {:?}",
|
||||
finished.reason
|
||||
);
|
||||
if let Some(meta) = finished.conversation_usage_metadata {
|
||||
if let Some(usage) = meta.byok_token_usage.get("bedrock") {
|
||||
println!("[live-test] Token Usage - Total: {}", usage.total_tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("[live-test] Received full response:\n{}", full_text.trim());
|
||||
assert!(!full_text.is_empty(), "Model returned an empty response");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::settings::ai::BedrockModelConfig;
|
||||
#![allow(dead_code)]
|
||||
|
||||
use super::external_config::ExternalBedrockConfig;
|
||||
use crate::settings::ai::BedrockModelConfig;
|
||||
|
||||
pub struct DefaultModel {
|
||||
pub model_id: &'static str,
|
||||
@@ -128,7 +129,6 @@ pub fn get_effective_models(user_models: &[BedrockModelConfig]) -> Vec<BedrockMo
|
||||
model_id: m.model_id.to_string(),
|
||||
display_name: m.display_name.to_string(),
|
||||
vision_supported: m.vision_supported,
|
||||
context_size: m.context_size,
|
||||
})
|
||||
.collect();
|
||||
for default in defaults {
|
||||
@@ -145,7 +145,6 @@ pub fn get_effective_models(user_models: &[BedrockModelConfig]) -> Vec<BedrockMo
|
||||
model_id: m.model_id.to_string(),
|
||||
display_name: m.display_name.to_string(),
|
||||
vision_supported: m.vision_supported,
|
||||
context_size: m.context_size,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -92,7 +92,6 @@ fn test_get_effective_models_custom_overrides() {
|
||||
model_id: "custom.model-v1:0".to_string(),
|
||||
display_name: "Custom Model".to_string(),
|
||||
vision_supported: false,
|
||||
context_size: 200_000,
|
||||
}];
|
||||
let models = get_effective_models(&custom);
|
||||
assert_eq!(models.len(), 1);
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use super::convert::{
|
||||
@@ -357,6 +361,7 @@ fn extract_input_messages(request: &api::Request) -> Vec<api::Message> {
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
fetched_memories: vec![],
|
||||
message: Some(api::message::Message::ToolCallResult(
|
||||
api::message::ToolCallResult {
|
||||
tool_call_id: result.tool_call_id.clone(),
|
||||
@@ -382,6 +387,7 @@ fn extract_input_messages(request: &api::Request) -> Vec<api::Message> {
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
fetched_memories: vec![],
|
||||
message: Some(api::message::Message::UserQuery(
|
||||
api::message::UserQuery {
|
||||
query: query.query.clone(),
|
||||
@@ -422,6 +428,7 @@ fn extract_input_messages(request: &api::Request) -> Vec<api::Message> {
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
fetched_memories: vec![],
|
||||
message: Some(api::message::Message::UserQuery(
|
||||
api::message::UserQuery {
|
||||
query: query_text,
|
||||
@@ -445,6 +452,7 @@ fn extract_input_messages(request: &api::Request) -> Vec<api::Message> {
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
fetched_memories: vec![],
|
||||
message: Some(api::message::Message::UserQuery(api::message::UserQuery {
|
||||
query: query.query.clone(),
|
||||
..Default::default()
|
||||
@@ -462,6 +470,7 @@ fn extract_input_messages(request: &api::Request) -> Vec<api::Message> {
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
fetched_memories: vec![],
|
||||
message: Some(api::message::Message::ToolCallResult(
|
||||
api::message::ToolCallResult {
|
||||
tool_call_id: result.tool_call_id.clone(),
|
||||
@@ -492,6 +501,7 @@ fn extract_input_messages(request: &api::Request) -> Vec<api::Message> {
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
fetched_memories: vec![],
|
||||
message: Some(api::message::Message::UserQuery(api::message::UserQuery {
|
||||
query: prompt,
|
||||
..Default::default()
|
||||
@@ -728,7 +738,6 @@ fn ensure_starts_with_user_message(messages: &mut Vec<ConversationMessage>) {
|
||||
/// 4. Removes trailing assistant tool_use messages that have no following user message.
|
||||
/// 5. Ensures strict user/assistant role alternation.
|
||||
fn ensure_tool_results_paired(messages: &mut Vec<ConversationMessage>) {
|
||||
|
||||
// Collect all tool_result IDs that exist anywhere in the conversation.
|
||||
let mut all_result_ids = HashSet::new();
|
||||
for msg in messages.iter() {
|
||||
@@ -1035,7 +1044,7 @@ pub fn extract_tools(request: &api::Request) -> Vec<ToolDefinition> {
|
||||
let input_schema = tool
|
||||
.input_schema
|
||||
.as_ref()
|
||||
.map(|s| prost_struct_to_json(s))
|
||||
.map(prost_struct_to_json)
|
||||
.unwrap_or_else(|| serde_json::json!({"type": "object", "properties": {}}));
|
||||
tools.push(ToolDefinition {
|
||||
name,
|
||||
@@ -1057,7 +1066,7 @@ pub fn extract_tools(request: &api::Request) -> Vec<ToolDefinition> {
|
||||
let input_schema = tool
|
||||
.input_schema
|
||||
.as_ref()
|
||||
.map(|s| prost_struct_to_json(s))
|
||||
.map(prost_struct_to_json)
|
||||
.unwrap_or_else(|| serde_json::json!({"type": "object", "properties": {}}));
|
||||
tools.push(ToolDefinition {
|
||||
name,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use aws_sdk_bedrockruntime::operation::converse_stream::ConverseStreamOutput;
|
||||
@@ -10,11 +12,10 @@ use uuid::Uuid;
|
||||
use warp_multi_agent_api::response_event::stream_finished;
|
||||
use warp_multi_agent_api::{self as api, ClientAction, ResponseEvent};
|
||||
|
||||
use crate::ai::agent::api::Event;
|
||||
use crate::server::server_api::AIApiError;
|
||||
|
||||
use super::convert::{ContentPart, ConversationMessage, MessageContent, MessageRole};
|
||||
use super::diagnostic::BedrockDiagnosticLogger;
|
||||
use crate::ai::agent::api::Event;
|
||||
use crate::server::server_api::AIApiError;
|
||||
|
||||
fn json_to_prost_struct(value: &serde_json::Value) -> prost_types::Struct {
|
||||
let fields = match value.as_object() {
|
||||
@@ -57,6 +58,7 @@ pub fn context_window_for_model(model_id: &str) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn bedrock_stream_to_response_events(
|
||||
mut output: ConverseStreamOutput,
|
||||
task_id: String,
|
||||
@@ -129,41 +131,36 @@ pub fn bedrock_stream_to_response_events(
|
||||
}
|
||||
StreamEvent::ContentBlockStart(block_start) => {
|
||||
log::debug!("[bedrock] Event #{event_count}: ContentBlockStart");
|
||||
if let Some(start) = block_start.start() {
|
||||
match start {
|
||||
ContentBlockStart::ToolUse(tool_start) => {
|
||||
_has_tool_calls = true;
|
||||
if !buffered_text.is_empty() {
|
||||
let msg_id = current_text_message_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| Uuid::new_v4().to_string());
|
||||
if !text_flushed {
|
||||
current_text_message_id = Some(msg_id.clone());
|
||||
text_flushed = true;
|
||||
log::debug!("[bedrock] Flushing buffered text ({} chars) before tool call", buffered_text.len());
|
||||
let add_msg = build_add_agent_output_message(
|
||||
&task_id,
|
||||
&msg_id,
|
||||
&buffered_text,
|
||||
);
|
||||
yield Ok(add_msg);
|
||||
} else {
|
||||
log::debug!("[bedrock] Flushing remaining buffered text ({} chars) as append before tool call", buffered_text.len());
|
||||
let append = build_append_text(
|
||||
&task_id,
|
||||
&msg_id,
|
||||
&buffered_text,
|
||||
);
|
||||
yield Ok(append);
|
||||
}
|
||||
buffered_text.clear();
|
||||
}
|
||||
current_tool_use_id = tool_start.tool_use_id().to_string();
|
||||
current_tool_name = tool_start.name().to_string();
|
||||
current_tool_input_json.clear();
|
||||
if let Some(ContentBlockStart::ToolUse(tool_start)) = block_start.start() {
|
||||
_has_tool_calls = true;
|
||||
if !buffered_text.is_empty() {
|
||||
let msg_id = current_text_message_id
|
||||
.clone()
|
||||
.unwrap_or_else(|| Uuid::new_v4().to_string());
|
||||
if !text_flushed {
|
||||
current_text_message_id = Some(msg_id.clone());
|
||||
text_flushed = true;
|
||||
log::debug!("[bedrock] Flushing buffered text ({} chars) before tool call", buffered_text.len());
|
||||
let add_msg = build_add_agent_output_message(
|
||||
&task_id,
|
||||
&msg_id,
|
||||
&buffered_text,
|
||||
);
|
||||
yield Ok(add_msg);
|
||||
} else {
|
||||
log::debug!("[bedrock] Flushing remaining buffered text ({} chars) as append before tool call", buffered_text.len());
|
||||
let append = build_append_text(
|
||||
&task_id,
|
||||
&msg_id,
|
||||
&buffered_text,
|
||||
);
|
||||
yield Ok(append);
|
||||
}
|
||||
_ => {}
|
||||
buffered_text.clear();
|
||||
}
|
||||
current_tool_use_id = tool_start.tool_use_id().to_string();
|
||||
current_tool_name = tool_start.name().to_string();
|
||||
current_tool_input_json.clear();
|
||||
}
|
||||
}
|
||||
StreamEvent::ContentBlockDelta(delta) => {
|
||||
@@ -187,7 +184,7 @@ pub fn bedrock_stream_to_response_events(
|
||||
// AddMessagesToTask carries enough content for
|
||||
// the exchange to be fully registered before
|
||||
// subsequent AppendToMessageContent events arrive.
|
||||
if buffered_text.len() >= 1 {
|
||||
if !buffered_text.is_empty() {
|
||||
let msg_id = Uuid::new_v4().to_string();
|
||||
current_text_message_id = Some(msg_id.clone());
|
||||
text_flushed = true;
|
||||
@@ -201,10 +198,8 @@ pub fn bedrock_stream_to_response_events(
|
||||
}
|
||||
}
|
||||
}
|
||||
ContentBlockDelta::ReasoningContent(reasoning) => {
|
||||
if let ReasoningContentBlockDelta::Text(text) = reasoning {
|
||||
log::trace!("[bedrock] Reasoning delta ({} chars) - not displayed to user", text.len());
|
||||
}
|
||||
ContentBlockDelta::ReasoningContent(ReasoningContentBlockDelta::Text(text)) => {
|
||||
log::trace!("[bedrock] Reasoning delta ({} chars) - not displayed to user", text.len());
|
||||
}
|
||||
ContentBlockDelta::ToolUse(tool_delta) => {
|
||||
current_tool_input_json.push_str(tool_delta.input());
|
||||
@@ -593,6 +588,7 @@ fn build_user_query_message(task_id: &str, query_text: &str) -> ResponseEvent {
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
fetched_memories: vec![],
|
||||
message: Some(api::message::Message::UserQuery(api::message::UserQuery {
|
||||
query: query_text.to_string(),
|
||||
..Default::default()
|
||||
@@ -682,10 +678,14 @@ pub fn build_stream_finished(
|
||||
context_window_usage: context_usage,
|
||||
summarized: is_summarization,
|
||||
credits_spent: 0.0,
|
||||
platform_credits_spent: 0.0,
|
||||
total_input_tokens: input_tokens as u32,
|
||||
token_usage: vec![],
|
||||
tool_usage_metadata: None,
|
||||
warp_token_usage: std::collections::HashMap::new(),
|
||||
byok_token_usage,
|
||||
custom_endpoint_token_usage: std::collections::HashMap::new(),
|
||||
context_window_segments: vec![],
|
||||
});
|
||||
|
||||
ResponseEvent {
|
||||
@@ -757,6 +757,7 @@ fn build_add_agent_output_message(
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
fetched_memories: vec![],
|
||||
message: Some(api::message::Message::AgentOutput(
|
||||
api::message::AgentOutput {
|
||||
text: initial_text.to_string(),
|
||||
@@ -790,6 +791,7 @@ fn build_append_text(task_id: &str, message_id: &str, text_delta: &str) -> Respo
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
fetched_memories: vec![],
|
||||
message: Some(api::message::Message::AgentOutput(
|
||||
api::message::AgentOutput {
|
||||
text: text_delta.to_string(),
|
||||
@@ -1227,6 +1229,7 @@ pub fn build_tool_call_message(
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
fetched_memories: vec![],
|
||||
message: Some(api::message::Message::ToolCall(api::message::ToolCall {
|
||||
tool_call_id: effective_tool_call_id,
|
||||
tool: Some(tool_variant),
|
||||
@@ -1243,6 +1246,7 @@ pub fn build_tool_call_message(
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
fetched_memories: vec![],
|
||||
message: Some(api::message::Message::AgentOutput(
|
||||
api::message::AgentOutput {
|
||||
text: format!(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use warp_multi_agent_api::{self as api, response_event::stream_finished};
|
||||
use warp_multi_agent_api::response_event::stream_finished;
|
||||
use warp_multi_agent_api::{self as api};
|
||||
|
||||
use super::response_translator::*;
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use galaxyui::elements::{
|
||||
Container, CornerRadius, CrossAxisAlignment, Flex, ParentElement, Radius, Text,
|
||||
};
|
||||
use galaxyui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext};
|
||||
|
||||
use crate::ai::bedrock::convert::CachingConfig;
|
||||
use crate::ai::bedrock::external_config::ExternalBedrockConfig;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::ui_components::blended_colors;
|
||||
use galaxyui::{
|
||||
elements::{Container, CornerRadius, CrossAxisAlignment, Flex, ParentElement, Radius, Text},
|
||||
AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext,
|
||||
};
|
||||
|
||||
pub struct SettingsView {
|
||||
external_config: ExternalBedrockConfig,
|
||||
|
||||
@@ -9,16 +9,5 @@ pub fn multiply(a: i32, b: i32) -> i32 {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_add() {
|
||||
assert_eq!(add(2, 3), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiply() {
|
||||
assert_eq!(multiply(3, 4), 12);
|
||||
}
|
||||
}
|
||||
#[path = "lib_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_add() {
|
||||
assert_eq!(add(2, 3), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiply() {
|
||||
assert_eq!(multiply(3, 4), 12);
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
Reference in New Issue
Block a user