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:
2026-07-02 14:54:15 -05:00
parent 4770ac06b5
commit 3769646ca6
1194 changed files with 5312 additions and 8032 deletions
+9 -4
View File
@@ -14,14 +14,14 @@ pub use convert_from::{
MaybeAIAgentOutputMessage, MessageToAIAgentOutputMessageError,
};
use futures_lite::Stream;
use mcp::TemplatableMCPServerInfo;
pub use r#impl::generate_multi_agent_output;
use serde::Serialize;
use galaxy_core::channel::ChannelState;
use galaxy_core::execution_mode::AppExecutionMode;
use galaxy_core::features::FeatureFlag;
use galaxy_core::user_preferences::GetUserPreferences;
use galaxyui::{AppContext, EntityId, SingletonEntity as _};
use mcp::TemplatableMCPServerInfo;
pub use r#impl::generate_multi_agent_output;
use serde::Serialize;
use super::{AIAgentInput, MCPContext, MCPServer, RequestMetadata, Suggestions};
use crate::ai::agent::conversation::AIConversationId;
@@ -211,6 +211,11 @@ impl RequestParams {
supported_tools_override: None,
parent_agent_id: None,
agent_name: None,
root_task_id: None,
bedrock_message_history: vec![],
bedrock_progressive_summary: None,
bedrock_tool_result_archive: vec![],
bedrock_messages_sent: std::sync::Arc::new(std::sync::Mutex::new(vec![])),
}
}
@@ -303,7 +308,7 @@ impl RequestParams {
let geap_binding: Option<::ai::api_keys::GeapMintBinding> = None;
let api_keys = api_key_manager.api_keys_for_request(
is_byo_enabled,
user_workspaces.is_aws_bedrock_credentials_enabled(app),
user_workspaces.is_bedrock_enabled(app),
geap_binding,
);
let is_custom_inference_enabled = user_workspaces.is_custom_inference_enabled(app);
+5 -1
View File
@@ -14,8 +14,8 @@ use ai::agent::action_result::{
};
use ai::skills::{ParsedSkill, SkillPathOrigin};
use chrono::{DateTime, Local, TimeZone};
use persistence::model::AgentConversationData;
use galaxy_core::command::ExitCode;
use persistence::model::AgentConversationData;
use warp_multi_agent_api as api;
use warp_multi_agent_api::ask_user_question_result::answer_item::Answer as AskUserQuestionAnswer;
@@ -89,6 +89,8 @@ pub fn convert_conversation_data_to_ai_conversation(
autoexecute_override: None,
last_event_sequence: None,
pinned: false,
progressive_summary: None,
messages_summarized_up_to: 0,
},
RestorationMode::Continue => AgentConversationData {
server_conversation_token: Some(
@@ -110,6 +112,8 @@ pub fn convert_conversation_data_to_ai_conversation(
autoexecute_override: None,
last_event_sequence: None,
pinned: false,
progressive_summary: None,
messages_summarized_up_to: 0,
},
};
@@ -31,6 +31,7 @@ fn test_server_metadata(
token_usage: vec![],
tool_usage_metadata: Default::default(),
context_window_segments: Vec::new(),
..Default::default()
},
metadata: ServerMetadata {
uid: ServerId::default(),
+63 -31
View File
@@ -8,11 +8,15 @@ use warp_multi_agent_api as api;
use super::convert_to::convert_input;
use super::{ConvertToAPITypeError, RequestParams, ResponseStream};
use crate::ai::agent::redaction;
use crate::server::server_api::{AIApiError, ServerApi};
use crate::ai::openai::translator as openai_translator;
use crate::ai::provider::ProviderConfig;
use crate::server::server_api::ai::AIClient;
use crate::server::server_api::AIApiError;
use crate::terminal::model::session::SessionType;
pub async fn generate_multi_agent_output(
provider_config: ProviderConfig,
server_api: Arc<dyn AIClient>,
mut params: RequestParams,
cancellation_rx: futures::channel::oneshot::Receiver<()>,
) -> Result<ResponseStream, ConvertToAPITypeError> {
@@ -65,9 +69,9 @@ pub async fn generate_multi_agent_output(
input: Some(convert_input(params.input)?),
settings: Some(api::request::Settings {
model_config: Some(api::request::settings::ModelConfig {
base: params.model.into(),
cli_agent: params.cli_agent_model.into(),
computer_use_agent: params.computer_use_model.into(),
base: params.model.clone().into(),
cli_agent: params.cli_agent_model.clone().into(),
computer_use_agent: params.computer_use_model.clone().into(),
base_model_context_window_limit: params.context_window_limit.unwrap_or(0),
..Default::default()
}),
@@ -136,24 +140,11 @@ pub async fn generate_multi_agent_output(
mcp_context: params.mcp_context.map(Into::into),
};
let response_stream =
warp_multi_agent_client::generate_multi_agent_output(server_api.as_ref(), &request).await;
match response_stream {
Ok(stream) => {
let output_stream = stream
.then(|result| async {
match result {
Ok(event) => Ok(event),
Err(error) => Err(convert_multi_agent_client_error(error).await),
}
})
.take_until(cancellation_rx);
Ok(Box::pin(output_stream))
}
match provider_config {
ProviderConfig::OpenAI(config) => {
let translator_request = openai_translator::TranslatorRequest {
config,
model_id,
model_id: params.model.as_str().to_string(),
root_task_id: params.root_task_id.clone(),
message_history: params.bedrock_message_history.clone(),
tool_result_archive: params.bedrock_tool_result_archive.clone(),
@@ -178,19 +169,60 @@ pub async fn generate_multi_agent_output(
}
}
}
ProviderConfig::Bedrock(config) => {
let translator_request = crate::ai::bedrock::translator::TranslatorRequest {
config,
model_id: params.model.as_str().to_string(),
root_task_id: params.root_task_id.clone(),
bedrock_message_history: params.bedrock_message_history.clone(),
bedrock_tool_result_archive: params.bedrock_tool_result_archive.clone(),
bedrock_progressive_summary: params.bedrock_progressive_summary.clone(),
bedrock_messages_sent: params.bedrock_messages_sent.clone(),
};
match crate::ai::bedrock::translator::execute(translator_request, &mut request).await {
Ok(stream) => {
let output_stream = stream.take_until(cancellation_rx);
Ok(Box::pin(output_stream))
}
Err(e) => {
log::error!("[bedrock] Translator error: {e}");
let err = Arc::new(crate::server::server_api::AIApiError::Stream {
stream_type: "bedrock",
source: anyhow::anyhow!("{e}"),
});
let (tx, rx) = async_channel::unbounded();
let _ = tx.send(Err(err)).await;
Ok(Box::pin(rx))
}
}
}
ProviderConfig::None => {
log::error!("No AI provider configured. Cannot process request.");
let err = Arc::new(crate::server::server_api::AIApiError::Stream {
stream_type: "provider_dispatch",
source: anyhow::anyhow!(
"No AI backend available. Please configure a provider in Settings > AI."
),
});
let (tx, rx) = async_channel::unbounded();
let _ = tx
.send(Err(convert_multi_agent_client_error(e).await))
.await;
Ok(Box::pin(rx))
let response_stream = warp_multi_agent_client::generate_multi_agent_output(
server_api.base_client().as_ref(),
&request,
)
.await;
match response_stream {
Ok(stream) => {
let output_stream = stream
.then(|result| async {
match result {
Ok(event) => Ok(event),
Err(error) => Err(convert_multi_agent_client_error(error).await),
}
})
.take_until(cancellation_rx);
Ok(Box::pin(output_stream))
}
Err(e) => {
let (tx, rx) = async_channel::unbounded();
let _ = tx
.send(Err(convert_multi_agent_client_error(e).await))
.await;
Ok(Box::pin(rx))
}
}
}
}
}
+86 -12
View File
@@ -5,10 +5,6 @@ use ai::agent::orchestration_config::{OrchestrationConfig, OrchestrationConfigSt
use ai::document::AIDocumentId;
use ai::skills::SkillPathOrigin;
use chrono::{DateTime, Local, TimeZone};
use itertools::Itertools as _;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use vec1::{Size0Error, Vec1};
use galaxy_cli::agent::Harness;
use galaxy_core::command::ExitCode;
use galaxy_core::execution_mode::AppExecutionMode;
@@ -16,12 +12,16 @@ use galaxy_core::features::FeatureFlag;
use galaxy_core::send_telemetry_from_ctx;
use galaxy_core::ui::appearance::Appearance;
use galaxy_core::ui::theme::color::internal_colors;
use galaxy_core::ui::theme::WarpTheme;
use galaxy_core::ui::theme::GalaxyTheme;
use galaxyui::color::ColorU;
use galaxyui::{AppContext, EntityId, ModelContext, SingletonEntity};
use itertools::Itertools as _;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use vec1::{Size0Error, Vec1};
use warp_multi_agent_api::response_event::stream_finished;
use warp_multi_agent_api::response_event::stream_finished::TokenUsage;
use warp_multi_agent_api::{self as api};
use galaxyui::color::ColorU;
use galaxyui::{AppContext, EntityId, ModelContext, SingletonEntity};
use super::api::ServerConversationToken;
use super::task::helper::*;
@@ -327,6 +327,12 @@ pub struct AIConversation {
/// Whether the user has pinned this child agent in the orchestration
/// pill bar. Persisted via `AgentConversationData.pinned`.
pinned: bool,
bedrock_message_history: Vec<crate::ai::bedrock::convert::ConversationMessage>,
tool_result_archive: Vec<crate::ai::bedrock::convert::ConversationMessage>,
progressive_summary: Option<String>,
messages_summarized_up_to: usize,
current_context_tokens: u32,
has_pending_progressive_summary: bool,
}
pub(crate) fn artifact_from_fork_proto(
@@ -382,6 +388,12 @@ impl AIConversation {
last_event_sequence: None,
orchestration_configs: HashMap::new(),
pinned: false,
bedrock_message_history: Vec::new(),
tool_result_archive: Vec::new(),
progressive_summary: None,
messages_summarized_up_to: 0,
current_context_tokens: 0,
has_pending_progressive_summary: false,
}
}
@@ -413,6 +425,12 @@ impl AIConversation {
tasks: Vec<api::Task>,
conversation_data: Option<AgentConversationData>,
) -> Result<Self, RestoreConversationError> {
let bedrock_message_history: Vec<crate::ai::bedrock::convert::ConversationMessage> = tasks
.iter()
.flat_map(|task| task.messages.iter())
.filter_map(crate::ai::bedrock::request_translator::convert_proto_message)
.collect();
let (task_store, todo_lists, status) = if tasks.is_empty() {
// Bypass `derive_status_from_root_task`: it would return `Success`
// for a root with no exchanges, silently misclassifying a restored
@@ -518,6 +536,8 @@ impl AIConversation {
autoexecute_override,
last_event_sequence,
pinned,
progressive_summary,
messages_summarized_up_to,
) = if let Some(data) = conversation_data {
let server_conversation_token = data
.server_conversation_token
@@ -565,6 +585,8 @@ impl AIConversation {
autoexecute_override,
data.last_event_sequence,
data.pinned,
data.progressive_summary.clone(),
data.messages_summarized_up_to,
)
} else {
(
@@ -582,9 +604,33 @@ impl AIConversation {
AIConversationAutoexecuteMode::default(),
None,
false,
None,
0,
)
};
let restored_token_usage = {
let mut map = HashMap::new();
let cache_read = conversation_usage_metadata.total_cache_read_tokens;
let cache_write = conversation_usage_metadata.total_cache_write_tokens;
let cache_miss = conversation_usage_metadata.total_cache_miss_tokens;
let cost = conversation_usage_metadata.total_cost_cents;
if cache_read > 0 || cache_write > 0 || cache_miss > 0 || cost > 0.0 {
map.insert(
"restored".to_string(),
TokenUsage {
model_id: "restored".to_string(),
total_input: cache_miss,
output: 0,
input_cache_read: cache_read,
input_cache_write: cache_write,
cost_in_cents: cost,
},
);
}
map
};
Ok(Self {
id,
is_viewing_shared_session: false,
@@ -622,6 +668,12 @@ impl AIConversation {
last_event_sequence,
orchestration_configs: HashMap::new(),
pinned,
bedrock_message_history,
tool_result_archive: Vec::new(),
progressive_summary,
messages_summarized_up_to,
current_context_tokens: 0,
has_pending_progressive_summary: false,
})
}
@@ -629,6 +681,22 @@ impl AIConversation {
self.id
}
pub fn current_context_tokens(&self) -> u32 {
self.current_context_tokens
}
pub fn set_current_context_tokens(&mut self, val: u32) {
self.current_context_tokens = val;
}
pub fn has_pending_progressive_summary(&self) -> bool {
self.has_pending_progressive_summary
}
pub fn set_has_pending_progressive_summary(&mut self, val: bool) {
self.has_pending_progressive_summary = val;
}
pub fn bedrock_message_history(&self) -> &[crate::ai::bedrock::convert::ConversationMessage] {
&self.bedrock_message_history
}
@@ -765,6 +833,10 @@ impl AIConversation {
self.conversation_usage_metadata.context_window_usage
}
pub fn set_context_window_usage(&mut self, val: f32) {
self.conversation_usage_metadata.context_window_usage = val;
}
/// The per-segment breakdown of the context window (e.g. system prompt,
/// tool definitions, conversation history). Scaled so the segments sum to
/// `context_window_usage`. Empty when the server did not emit segments.
@@ -963,7 +1035,7 @@ impl AIConversation {
// Sanity check: reject durations that are clearly wrong (> 24 hours
// likely means start_time defaulted to epoch during session restore).
if ms < 0 || ms > 86_400_000 {
if !(0..=86_400_000).contains(&ms) {
return None;
}
Some(ms)
@@ -2447,7 +2519,7 @@ impl AIConversation {
let is_hidden = self.is_exchange_hidden(exchange_id);
ctx.emit(BlocklistAIHistoryEvent::UpdatedStreamingExchange {
exchange_id,
terminal_view_id,
terminal_surface_id: terminal_view_id,
conversation_id: self.id,
is_hidden,
});
@@ -3278,7 +3350,7 @@ impl AIConversation {
response_stream_id: Some(response_stream_id.clone()),
exchange_id,
task_id: task_id.clone(),
terminal_view_id,
terminal_surface_id,
conversation_id: self.id,
is_hidden,
});
@@ -3465,7 +3537,7 @@ impl AIConversation {
&mut self,
block_id: &BlockId,
) -> TaskId {
let new_task = Task::new_optimistic_cli_agent_subtask(block_id.clone());
let new_task = Task::new_optimistic_cli_agent_subtask(block_id.clone(), None);
let new_task_id = new_task.id().clone();
self.optimistic_cli_subagent_subtask_id = Some(new_task_id.clone());
self.task_store.insert(new_task);
@@ -3670,6 +3742,8 @@ impl AIConversation {
autoexecute_override: Some(self.autoexecute_override.into()),
last_event_sequence: self.last_event_sequence,
pinned: self.pinned,
progressive_summary: self.progressive_summary.clone(),
messages_summarized_up_to: self.messages_summarized_up_to,
},
};
ctx.spawn(
@@ -4784,7 +4858,7 @@ impl ConversationStatus {
pub fn status_icon_and_color(
&self,
theme: &WarpTheme,
theme: &GalaxyTheme,
color_style: StatusColorStyle,
) -> (Icon, ColorU) {
match self {
+2 -2
View File
@@ -27,6 +27,8 @@ use ai::skills::ParsedSkill;
use chrono::{DateTime, Local, TimeDelta};
use comment::ReviewComment;
use derivative::Derivative;
use galaxy_core::channel::ChannelState;
use galaxy_core::features::FeatureFlag;
use markdown_parser::{parse_markdown, FormattedTable, FormattedText, FormattedTextInline};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
@@ -34,8 +36,6 @@ use session_sharing_protocol::common::ParticipantId;
use task::TaskId;
pub use telemetry::AIIdentifiers;
use uuid::Uuid;
use galaxy_core::channel::ChannelState;
use galaxy_core::features::FeatureFlag;
use warp_editor::render::model::LineCount;
use warp_multi_agent_api::{diff_hunk as diff_hunk_api, AgentEvent, AgentType};
+3 -8
View File
@@ -119,6 +119,7 @@ struct ServerTask {
}
mod optimistic {
use crate::terminal::model::block::BlockId;
#[derive(Debug, Clone)]
pub(super) struct CLIAgentSubtask {
@@ -211,7 +212,7 @@ impl Task {
subagent_params: Some(SubagentParams {
tool_call_id: String::new(),
call: api::message::tool_call::Subagent {
task_id: task_id,
task_id,
payload: String::new(),
metadata: Some(Metadata::Cli(
api::message::tool_call::subagent::CliSubagent {
@@ -815,13 +816,7 @@ impl Task {
"[bedrock] append_to_message_content: message_id={} not found, treating as implicit add",
message.id
);
self.add_messages(
vec![message],
exchange_id,
current_todo_list,
current_comments,
false,
)?;
self.add_messages(vec![message], exchange_id, message_context, false)?;
return self
.try_get_source()?
.messages
+1 -1
View File
@@ -11,6 +11,7 @@ use crate::ai::agent::{
SubagentCall,
};
use crate::ai::llms::LLMId;
use crate::terminal::model::block::BlockId;
fn create_test_exchange() -> AIAgentExchange {
AIAgentExchange {
@@ -40,7 +41,6 @@ fn create_test_task_with_exchanges(exchange_count: usize) -> Task {
}
fn create_test_subtask_with_exchanges(exchange_count: usize) -> Task {
use crate::terminal::model::block::BlockId;
let mut task = Task::new_optimistic_cli_agent_subtask(BlockId::new(), None);
for _ in 0..exchange_count {
task.append_exchange(create_test_exchange());
+1 -1
View File
@@ -1,4 +1,3 @@
use pathfinder_color::ColorU;
use galaxy_core::ui::appearance::Appearance;
use galaxy_core::ui::theme::Fill;
use galaxyui::elements::{
@@ -12,6 +11,7 @@ use galaxyui::{
AppContext, Element, Entity, EntityId, ModelHandle, SingletonEntity, TypedActionView, View,
ViewContext,
};
use pathfinder_color::ColorU;
use crate::ai::agent::icons::{in_progress_icon, pending_icon, succeeded_icon};
use crate::ai::agent::todos::AIAgentTodoList;