Fix Warping indicator stuck after Bedrock LLM finishes responding

Bedrock was calling suggest_next_prompt tool which created a SuggestPrompt
action that waited forever on a oneshot channel for UI interaction that
never fires in the Bedrock path, keeping the conversation permanently
InProgress. Fixed by filtering the tool from the Bedrock tool list and
skipping it at the stream level when the LLM calls it from context history.

Also includes: Bedrock cache token tracking, cost estimation, LSP
improvements, conversation usage view updates, and external config support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ryan Ward
2026-05-18 12:13:05 -05:00
co-authored by Claude Opus 4.6
parent 95b4708e44
commit f37a744692
27 changed files with 2423 additions and 65 deletions
@@ -101,7 +101,7 @@ impl ResponseStream {
secret_access_key: settings.bedrock_secret_access_key.value().clone(),
cross_region_inference: *settings.bedrock_cross_region_inference.value(),
fallback_to_warp: *settings.bedrock_fallback_to_warp.value(),
})
}.with_external_fallbacks())
}
pub fn new(
+1 -1
View File
@@ -67,7 +67,7 @@ pub(crate) use view_util::{
NEW_AGENT_PANE_LABEL,
};
pub(crate) use view_util::format_credits;
pub(crate) use view_util::{format_credits, format_token_count};
pub use crate::ai::blocklist::block::{secret_redaction, AIBlockResponseRating, TextLocation};
pub use block::keyboard_navigable_buttons;
@@ -1,5 +1,5 @@
use crate::ai::blocklist::usage::render_context_window_usage_icon;
use crate::ai::blocklist::view_util::format_credits;
use crate::ai::blocklist::view_util::{format_cost_cents, format_credits, format_token_count};
use crate::appearance::Appearance;
use crate::persistence::model::{
token_usage_category_display_name, ModelTokenUsage, FULL_TERMINAL_USE_CATEGORY,
@@ -37,6 +37,11 @@ pub struct ConversationUsageInfo {
pub lines_added: i32,
pub lines_removed: i32,
pub commands_executed: i32,
pub total_input_tokens: u32,
pub total_output_tokens: u32,
pub total_cache_read_tokens: u32,
pub total_cache_write_tokens: u32,
pub estimated_cost_cents: f32,
}
/// Timing information for the last set of agent responses
@@ -263,6 +268,55 @@ impl ConversationUsageView {
);
}
// Token usage section
let total_tokens = self.usage_info.total_input_tokens
+ self.usage_info.total_output_tokens
+ self.usage_info.total_cache_read_tokens
+ self.usage_info.total_cache_write_tokens;
if total_tokens > 0 {
labels.push(render_label_text("Total tokens", appearance));
values.push(render_value_text(
format_token_count(total_tokens),
appearance,
));
labels.push(render_label_text(" Input", appearance));
values.push(render_value_text(
format_token_count(self.usage_info.total_input_tokens),
appearance,
));
labels.push(render_label_text(" Output", appearance));
values.push(render_value_text(
format_token_count(self.usage_info.total_output_tokens),
appearance,
));
if self.usage_info.total_cache_read_tokens > 0 {
labels.push(render_label_text(" Cache read", appearance));
values.push(render_value_text(
format_token_count(self.usage_info.total_cache_read_tokens),
appearance,
));
}
if self.usage_info.total_cache_write_tokens > 0 {
labels.push(render_label_text(" Cache write", appearance));
values.push(render_value_text(
format_token_count(self.usage_info.total_cache_write_tokens),
appearance,
));
}
if self.usage_info.estimated_cost_cents > 0.0 {
labels.push(render_label_text("Estimated cost", appearance));
values.push(render_value_text(
format_cost_cents(self.usage_info.estimated_cost_cents),
appearance,
));
}
}
labels.push(render_label_text("Context window used", appearance));
let context_usage_str =
format!("{}%", (self.usage_info.context_window_usage * 100.).round());
+22
View File
@@ -152,6 +152,28 @@ pub fn get_ai_block_overflow_menu_element_position_id(view_id: EntityId) -> Stri
}
/// Formats credit count to display as whole numbers when the value is effectively a whole number,
pub fn format_token_count(tokens: u32) -> String {
if tokens >= 1_000_000 {
format!("{:.1}M tokens", tokens as f64 / 1_000_000.0)
} else if tokens >= 1_000 {
format!("{:.1}k tokens", tokens as f64 / 1_000.0)
} else {
format!("{tokens} tokens")
}
}
pub fn format_cost_cents(cents: f32) -> String {
if cents >= 100.0 {
format!("${:.2}", cents / 100.0)
} else if cents >= 1.0 {
format!("{:.1}\u{00A2}", cents)
} else if cents > 0.0 {
format!("{:.2}\u{00A2}", cents)
} else {
"$0.00".to_string()
}
}
/// otherwise displays with one decimal place.
/// Returns a formatted string with proper pluralization ("credit" vs "credits").
pub fn format_credits(credits: f32) -> String {