From e13ed355f6537d9607a88524a0293047016ff6a8 Mon Sep 17 00:00:00 2001 From: Ryan Ward Date: Tue, 12 May 2026 10:10:00 -0500 Subject: [PATCH] Fix Bedrock not being used: enable by default, remove model_id gating, set fallback default model - bedrock_enabled default: false -> true (was never routing to Bedrock) - Remove is_bedrock_model check from bedrock_config_if_applicable (model_id could be 'auto' from server-populated prefs, causing Bedrock to be skipped) - Default to claude-sonnet-4 when model_id is empty or 'auto' - fallback_to_warp default: true -> false (no server exists) - Smoke test: exit immediately on no-text with diagnostic info instead of polling forever --- app/src/ai/agent/api/impl.rs | 6 +- app/src/ai/agent/task.rs | 12 +++- .../blocklist/controller/response_stream.rs | 8 +-- app/src/bedrock_smoke_test.rs | 67 ++++++++++++------- app/src/settings/ai.rs | 4 +- 5 files changed, 62 insertions(+), 35 deletions(-) diff --git a/app/src/ai/agent/api/impl.rs b/app/src/ai/agent/api/impl.rs index 6bc2b1a3..d1f45c53 100644 --- a/app/src/ai/agent/api/impl.rs +++ b/app/src/ai/agent/api/impl.rs @@ -167,13 +167,17 @@ pub async fn generate_multi_agent_output( log::info!("[bedrock] needs_create_task={needs_create_task}"); - let model_id = request + let mut model_id = request .settings .as_ref() .and_then(|s| s.model_config.as_ref()) .map(|mc| mc.base.clone()) .unwrap_or_default(); + if model_id.is_empty() || model_id == "auto" { + model_id = "us.anthropic.claude-sonnet-4-20250514-v1:0".to_string(); + } + log::info!("[bedrock] Model: {model_id}"); let diagnostic_logger = diff --git a/app/src/ai/agent/task.rs b/app/src/ai/agent/task.rs index 75787fbe..8762bb40 100644 --- a/app/src/ai/agent/task.rs +++ b/app/src/ai/agent/task.rs @@ -999,6 +999,13 @@ impl AIAgentExchange { .iter() .position(|m| m.id.0 == task_message.id); + let proto_text = task_message.message.as_ref().map(|m| match m { + api::message::Message::AgentOutput(o) => format!("AgentOutput(text_len={})", o.text.len()), + api::message::Message::ToolCall(t) => format!("ToolCall(id={})", t.tool_call_id), + other => format!("{:?}", std::mem::discriminant(other)), + }).unwrap_or_else(|| "None".to_string()); + log::info!("[bedrock-debug] upsert_output_for_message: id={}, proto_type={}", task_message.id, proto_text); + match task_message .clone() .to_client_output_message(ConversionParams { @@ -1007,9 +1014,8 @@ impl AIAgentExchange { task_id, })? { MaybeAIAgentOutputMessage::Message(m) => { - // Extract citations from the message and add to the output citations + log::info!("[bedrock-debug] upsert_output_for_message: client_message_type={:?}", std::mem::discriminant(&m.message)); output.extend_citations(m.citations.clone()); - // Upsert behavior: update the message if it exists, otherwise add it to the end of the list. if let Some(message_idx) = message_idx { output.messages[message_idx] = m; } else { @@ -1018,7 +1024,7 @@ impl AIAgentExchange { } MaybeAIAgentOutputMessage::NoClientRepresentation => { log::warn!( - "Tried to update output for message which no longer has a client representation" + "[bedrock-debug] upsert_output_for_message: NoClientRepresentation for msg_id={}", task_message.id ); } } diff --git a/app/src/ai/blocklist/controller/response_stream.rs b/app/src/ai/blocklist/controller/response_stream.rs index 0e3e94ed..31d098bd 100644 --- a/app/src/ai/blocklist/controller/response_stream.rs +++ b/app/src/ai/blocklist/controller/response_stream.rs @@ -14,7 +14,7 @@ use crate::{ conversation::AIConversationId, AIIdentifiers, CancellationReason, }, - bedrock::{client::BedrockClientConfig, models::is_bedrock_model}, + bedrock::client::BedrockClientConfig, }, network::NetworkStatus, report_error, send_telemetry_from_ctx, @@ -85,17 +85,13 @@ pub struct ResponseStream { impl ResponseStream { fn bedrock_config_if_applicable( - model_id: &str, + _model_id: &str, ctx: &ModelContext, ) -> Option { let settings = AISettings::as_ref(ctx); if !*settings.bedrock_enabled.value() { return None; } - let configured_models = settings.bedrock_models.value().clone(); - if !is_bedrock_model(model_id, &configured_models) { - return None; - } let auth_method = *settings.bedrock_auth_method.value(); Some(BedrockClientConfig { auth_method, diff --git a/app/src/bedrock_smoke_test.rs b/app/src/bedrock_smoke_test.rs index 04065687..90d06a86 100644 --- a/app/src/bedrock_smoke_test.rs +++ b/app/src/bedrock_smoke_test.rs @@ -12,17 +12,19 @@ use crate::BlocklistAIHistoryModel; const TARGET_DIR: &str = "~/GIT/stitcher/stitcher"; -const FILE_VISIBILITY_QUERY: &str = r#"/agent Okay, let's try again. Can you see files now? List the files in the current directory using your tools. Then respond with EXACTLY this structured format: +const FILE_VISIBILITY_QUERY: &str = r#"/agent Please review the hls stream server code and tell me how we inject ads? Use your tools to explore the codebase first. Then respond with EXACTLY this structured format at the end of your response: -results: yes -file_count: +Answered: YES +Results: +Files evaluated: -If you cannot see any files or your tools fail, respond with: +If you cannot find the answer or cannot see files, respond with: -results: no -file_count: 0 +Answered: NO +Results: +Files evaluated: 0 -You MUST use the file_glob or run_shell_command tool to check the directory contents first, then provide the structured response."#; +You MUST use tools (file_glob, read_files, grep, run_shell_command) to explore the codebase before answering."#; #[allow(dead_code)] const AGENT_QUERY: &str = r#"/agent Analyze this project and respond with EXACTLY this structured format at the end of your response: @@ -101,27 +103,28 @@ fn poll_file_visibility( if let Some(full_text) = get_finished_text(ctx, &terminal_view) { log::info!("[smoke-test] === FILE VISIBILITY RESULT ({:.1}s) ===", elapsed.as_secs_f64()); - let results = extract_field(&full_text, "results:"); - let file_count = extract_field(&full_text, "file_count:"); + let answered = extract_field(&full_text, "Answered:"); + let results = extract_field(&full_text, "Results:"); + let files_evaluated = extract_field(&full_text, "Files evaluated:"); - match (&results, &file_count) { - (Some(r), Some(c)) => { - log::info!("[smoke-test] results: {}", r); - log::info!("[smoke-test] file_count: {}", c); + match (&answered, &results, &files_evaluated) { + (Some(a), Some(r), Some(f)) => { + log::info!("[smoke-test] Answered: {}", a); + log::info!("[smoke-test] Results: {}", r); + log::info!("[smoke-test] Files evaluated: {}", f); - if *r == "yes" { - let count: u32 = c.parse().unwrap_or(0); + if a.to_uppercase().contains("YES") { + let count: u32 = f.trim().parse().unwrap_or(0); if count > 0 { - log::info!("[smoke-test] === FILE VISIBILITY TEST PASSED (found {} files) ===", count); - log::info!("[smoke-test] LLM can see files. Proceeding to main test..."); + log::info!("[smoke-test] === TEST PASSED (evaluated {} files) ===", count); std::process::exit(0); } else { - log::error!("[smoke-test] === FILE VISIBILITY TEST FAILED (results=yes but file_count=0) ==="); + log::error!("[smoke-test] === TEST FAILED (Answered=YES but Files evaluated=0) ==="); std::process::exit(1); } } else { - log::error!("[smoke-test] === FILE VISIBILITY TEST FAILED (results=no) ==="); - log::error!("[smoke-test] The LLM cannot see files in {}", TARGET_DIR); + log::error!("[smoke-test] === TEST FAILED (Answered=NO) ==="); + log::error!("[smoke-test] The LLM could not answer the question about {}", TARGET_DIR); log::error!("[smoke-test] Full response:"); for line in full_text.lines() { log::error!("[smoke-test] {}", line); @@ -130,7 +133,7 @@ fn poll_file_visibility( } } _ => { - log::error!("[smoke-test] === FILE VISIBILITY TEST FAILED (missing structured fields) ==="); + log::error!("[smoke-test] === TEST FAILED (missing structured fields) ==="); log::error!("[smoke-test] Full response:"); for line in full_text.lines() { log::error!("[smoke-test] {}", line); @@ -245,7 +248,6 @@ fn get_finished_text( let conv = history.active_conversation(view_id)?; if conv.status().is_in_progress() { - log::info!("[smoke-test] Still running..."); return None; } @@ -293,7 +295,26 @@ fn get_finished_text( if all_text.is_empty() { log::warn!("[smoke-test] Conversation finished but no text output found"); - return None; + log::warn!("[smoke-test] Conversation status: {:?}", conv.status()); + log::warn!("[smoke-test] Number of exchanges: {}", conv.exchanges_reversed().count()); + for (i, exchange) in conv.exchanges_reversed().enumerate() { + match &exchange.output_status { + AIAgentOutputStatus::Finished { + finished_output: FinishedAIAgentOutput::Success { output }, + } => { + let o = output.get(); + log::warn!("[smoke-test] Exchange {}: Finished/Success, messages={}", i, o.messages.len()); + for (j, msg) in o.messages.iter().enumerate() { + log::warn!("[smoke-test] msg[{}]: type={:?}", j, std::mem::discriminant(&msg.message)); + } + } + other => { + log::warn!("[smoke-test] Exchange {}: {:?}", i, std::mem::discriminant(other)); + } + } + } + log::error!("[smoke-test] === TEST FAILED (no text in finished conversation) ==="); + std::process::exit(1); } Some(all_text) diff --git a/app/src/settings/ai.rs b/app/src/settings/ai.rs index cd9f099c..6e739c2a 100644 --- a/app/src/settings/ai.rs +++ b/app/src/settings/ai.rs @@ -1071,7 +1071,7 @@ define_settings_group!(AISettings, settings: [ // Whether direct Bedrock integration is enabled (client calls Bedrock API directly). bedrock_enabled: BedrockEnabled { type: bool, - default: false, + default: true, supported_platforms: SupportedPlatforms::DESKTOP, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false, @@ -1113,7 +1113,7 @@ define_settings_group!(AISettings, settings: [ // Whether to fall back to routing through the Warp server when Bedrock credentials fail. bedrock_fallback_to_warp: BedrockFallbackToWarp { type: bool, - default: true, + default: false, supported_platforms: SupportedPlatforms::DESKTOP, sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes), private: false,