Skip to content

Commit fc7e8fd

Browse files
author
kiran-garre
committed
Merge branch 'main' of github.com:aws/amazon-q-developer-cli into fix-todo-bugs
2 parents 296a46b + e97878f commit fc7e8fd

File tree

7 files changed

+153
-90
lines changed

7 files changed

+153
-90
lines changed

crates/chat-cli/src/api_client/delay_interceptor.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use crossterm::{
1919
style,
2020
};
2121

22+
use crate::api_client::MAX_RETRY_DELAY_DURATION;
23+
2224
#[derive(Debug, Clone)]
2325
pub struct DelayTrackingInterceptor {
2426
minor_delay_threshold: Duration,
@@ -62,20 +64,16 @@ impl Intercept for DelayTrackingInterceptor {
6264
let now = Instant::now();
6365

6466
if let Some(last_attempt_time) = cfg.load::<LastAttemptTime>() {
65-
let delay = now.duration_since(last_attempt_time.0);
67+
let delay = now.duration_since(last_attempt_time.0).min(MAX_RETRY_DELAY_DURATION);
6668

6769
if delay >= self.major_delay_threshold {
6870
Self::print_warning(format!(
69-
"Auto Retry #{} delayed by {:.1}s. Service is under heavy load - consider switching models.",
71+
"Retry #{}, retrying within {:.1}s..",
7072
attempt_number,
71-
delay.as_secs_f64()
73+
MAX_RETRY_DELAY_DURATION.as_secs_f64()
7274
));
7375
} else if delay >= self.minor_delay_threshold {
74-
Self::print_warning(format!(
75-
"Auto Retry #{} delayed by {:.1}s due to transient issues.",
76-
attempt_number,
77-
delay.as_secs_f64()
78-
));
76+
Self::print_warning(format!("Retry #{}, retrying within 5s..", attempt_number,));
7977
}
8078
}
8179

crates/chat-cli/src/api_client/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ pub const X_AMZN_CODEWHISPERER_OPT_OUT_HEADER: &str = "x-amzn-codewhisperer-opto
7272
// TODO(bskiser): confirm timeout is updated to an appropriate value?
7373
const DEFAULT_TIMEOUT_DURATION: Duration = Duration::from_secs(60 * 5);
7474

75+
pub const MAX_RETRY_DELAY_DURATION: Duration = Duration::from_secs(10);
76+
7577
#[derive(Clone, Debug)]
7678
pub struct ModelListResult {
7779
pub models: Vec<Model>,
@@ -616,7 +618,7 @@ fn timeout_config(database: &Database) -> TimeoutConfig {
616618
fn retry_config() -> RetryConfig {
617619
RetryConfig::adaptive()
618620
.with_max_attempts(3)
619-
.with_max_backoff(Duration::from_secs(10))
621+
.with_max_backoff(MAX_RETRY_DELAY_DURATION)
620622
}
621623

622624
pub fn stalled_stream_protection_config() -> StalledStreamProtectionConfig {

crates/chat-cli/src/cli/chat/cli/experiment.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,13 @@ async fn select_experiment(os: &mut Os, session: &mut ChatSession) -> Result<Opt
8080
experiment_labels.push(label);
8181
}
8282

83-
experiment_labels.push(String::new());
84-
experiment_labels.push(format!(
85-
"{}",
86-
style::Stylize::white("⚠ Experimental features may be changed or removed at any time")
87-
));
83+
// Show disclaimer before selection
84+
queue!(
85+
session.stderr,
86+
style::SetForegroundColor(Color::Yellow),
87+
style::Print("⚠ Experimental features may be changed or removed at any time\n\n"),
88+
style::ResetColor,
89+
)?;
8890

8991
let selection: Option<_> = match Select::with_theme(&crate::util::dialoguer_theme())
9092
.with_prompt("Select an experiment to toggle")
@@ -107,14 +109,14 @@ async fn select_experiment(os: &mut Os, session: &mut ChatSession) -> Result<Opt
107109
queue!(session.stderr, style::ResetColor)?;
108110

109111
if let Some(index) = selection {
110-
// Clear the dialoguer selection line to avoid showing old status
112+
// Clear the dialoguer selection line and disclaimer
111113
queue!(
112114
session.stderr,
113-
crossterm::cursor::MoveUp(1),
114-
crossterm::terminal::Clear(crossterm::terminal::ClearType::CurrentLine),
115+
crossterm::cursor::MoveUp(3), // Move up past selection + 2 disclaimer lines
116+
crossterm::terminal::Clear(crossterm::terminal::ClearType::FromCursorDown),
115117
)?;
116118

117-
// Skip if user selected disclaimer or empty line
119+
// Skip if user selected disclaimer or empty line (last 2 items)
118120
if index >= AVAILABLE_EXPERIMENTS.len() {
119121
return Ok(Some(ChatState::PromptUser {
120122
skip_printing_tools: false,

crates/chat-cli/src/cli/chat/cli/profile.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -221,31 +221,55 @@ impl AgentSubcommand {
221221
},
222222

223223
Self::Generate {} => {
224-
let agent_name = match session.read_user_input("Enter agent name: ", false) {
225-
Some(input) => input.trim().to_string(),
226-
None => {
224+
let agent_name = match crate::util::input("Enter agent name: ", None) {
225+
Ok(input) => input.trim().to_string(),
226+
Err(_) => {
227227
return Ok(ChatState::PromptUser {
228228
skip_printing_tools: true,
229229
});
230230
},
231231
};
232232

233-
let agent_description = match session.read_user_input("Enter agent description: ", false) {
234-
Some(input) => input.trim().to_string(),
235-
None => {
233+
let agent_description = match crate::util::input("Enter agent description: ", None) {
234+
Ok(input) => input.trim().to_string(),
235+
Err(_) => {
236236
return Ok(ChatState::PromptUser {
237237
skip_printing_tools: true,
238238
});
239239
},
240240
};
241241

242242
let scope_options = vec!["Local (current workspace)", "Global (all workspaces)"];
243-
let scope_selection = Select::new()
243+
let scope_selection = match Select::with_theme(&crate::util::dialoguer_theme())
244244
.with_prompt("Agent scope")
245245
.items(&scope_options)
246246
.default(0)
247-
.interact()
248-
.map_err(|e| ChatError::Custom(format!("Failed to get scope selection: {}", e).into()))?;
247+
.interact_on_opt(&dialoguer::console::Term::stdout())
248+
{
249+
Ok(sel) => {
250+
let _ = crossterm::execute!(
251+
std::io::stdout(),
252+
crossterm::style::SetForegroundColor(crossterm::style::Color::Magenta)
253+
);
254+
sel
255+
},
256+
// Ctrl‑C -> Err(Interrupted)
257+
Err(dialoguer::Error::IO(ref e)) if e.kind() == std::io::ErrorKind::Interrupted => {
258+
return Ok(ChatState::PromptUser {
259+
skip_printing_tools: true,
260+
});
261+
},
262+
Err(e) => return Err(ChatError::Custom(format!("Failed to get scope selection: {e}").into())),
263+
};
264+
265+
let scope_selection = match scope_selection {
266+
Some(selection) => selection,
267+
None => {
268+
return Ok(ChatState::PromptUser {
269+
skip_printing_tools: true,
270+
});
271+
},
272+
};
249273

250274
let is_global = scope_selection == 1;
251275

crates/chat-cli/src/cli/chat/line_tracker.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ impl FileLineTracker {
3434
}
3535

3636
pub fn lines_by_agent(&self) -> isize {
37-
(self.after_fswrite_lines as isize) - (self.before_fswrite_lines as isize)
37+
let lines = (self.after_fswrite_lines as isize) - (self.before_fswrite_lines as isize);
38+
lines.abs()
3839
}
3940
}

crates/chat-cli/src/cli/chat/mod.rs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,25 +1716,19 @@ impl ChatSession {
17161716
// Parse and validate the initial generated config
17171717
let initial_agent_config = match serde_json::from_str::<Agent>(&agent_config_json) {
17181718
Ok(config) => config,
1719-
Err(err) => {
1719+
Err(_) => {
17201720
execute!(
17211721
self.stderr,
17221722
style::SetForegroundColor(Color::Red),
1723-
style::Print(format!("✗ Failed to parse generated agent config: {}\n\n", err)),
1723+
style::Print("✗ The LLM did not generate a valid agent configuration. Please try again.\n\n"),
17241724
style::SetForegroundColor(Color::Reset)
17251725
)?;
1726-
return Err(ChatError::Custom(format!("Invalid agent config: {}", err).into()));
1726+
return Ok(ChatState::PromptUser {
1727+
skip_printing_tools: true,
1728+
});
17271729
},
17281730
};
17291731

1730-
// Display the generated agent config with syntax highlighting
1731-
execute!(
1732-
self.stderr,
1733-
style::SetForegroundColor(Color::Green),
1734-
style::Print(format!("✓ Generated agent config for '{}':\n\n", agent_name)),
1735-
style::SetForegroundColor(Color::Reset)
1736-
)?;
1737-
17381732
let formatted_json = serde_json::to_string_pretty(&initial_agent_config)
17391733
.map_err(|e| ChatError::Custom(format!("Failed to format JSON: {}", e).into()))?;
17401734

@@ -1750,9 +1744,9 @@ impl ChatSession {
17501744
style::Print(format!("✗ Invalid edited configuration: {}\n\n", err)),
17511745
style::SetForegroundColor(Color::Reset)
17521746
)?;
1753-
return Err(ChatError::Custom(
1754-
format!("Invalid agent config after editing: {}", err).into(),
1755-
));
1747+
return Ok(ChatState::PromptUser {
1748+
skip_printing_tools: true,
1749+
});
17561750
},
17571751
};
17581752

crates/chat-cli/src/telemetry/mod.rs

Lines changed: 90 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use std::str::FromStr;
1515

1616
use amzn_codewhisperer_client::types::{
1717
ChatAddMessageEvent,
18+
ChatInteractWithMessageEvent,
19+
ChatMessageInteractionType,
1820
IdeCategory,
1921
OperatingSystem,
2022
TelemetryEvent,
@@ -574,55 +576,95 @@ impl TelemetryClient {
574576
return;
575577
};
576578

577-
if let EventType::ChatAddedMessage {
578-
conversation_id,
579-
data:
580-
ChatAddedMessageParams {
581-
message_id,
582-
model,
583-
time_to_first_chunk_ms,
584-
time_between_chunks_ms,
585-
assistant_response_length,
586-
..
587-
},
588-
..
589-
} = &event.ty
590-
{
591-
let user_context = self.user_context().unwrap();
592-
// Short-Term fix for Validation errors -
593-
// chatAddMessageEvent.timeBetweenChunks' : Member must have length less than or equal to 100
594-
let time_between_chunks_truncated = time_between_chunks_ms
595-
.as_ref()
596-
.map(|chunks| chunks.iter().take(100).cloned().collect());
597-
598-
let chat_add_message_event = match ChatAddMessageEvent::builder()
599-
.conversation_id(conversation_id)
600-
.message_id(message_id.clone().unwrap_or("not_set".to_string()))
601-
.set_time_to_first_chunk_milliseconds(*time_to_first_chunk_ms)
602-
.set_time_between_chunks(time_between_chunks_truncated)
603-
.set_response_length(*assistant_response_length)
604-
.build()
605-
{
606-
Ok(event) => event,
607-
Err(err) => {
579+
match &event.ty {
580+
EventType::ChatAddedMessage {
581+
conversation_id,
582+
data:
583+
ChatAddedMessageParams {
584+
message_id,
585+
model,
586+
time_to_first_chunk_ms,
587+
time_between_chunks_ms,
588+
assistant_response_length,
589+
..
590+
},
591+
..
592+
} => {
593+
let user_context = self.user_context().unwrap();
594+
// Short-Term fix for Validation errors -
595+
// chatAddMessageEvent.timeBetweenChunks' : Member must have length less than or equal to 100
596+
let time_between_chunks_truncated = time_between_chunks_ms
597+
.as_ref()
598+
.map(|chunks| chunks.iter().take(100).cloned().collect());
599+
600+
let chat_add_message_event = match ChatAddMessageEvent::builder()
601+
.conversation_id(conversation_id)
602+
.message_id(message_id.clone().unwrap_or("not_set".to_string()))
603+
.set_time_to_first_chunk_milliseconds(*time_to_first_chunk_ms)
604+
.set_time_between_chunks(time_between_chunks_truncated)
605+
.set_response_length(*assistant_response_length)
606+
.build()
607+
{
608+
Ok(event) => event,
609+
Err(err) => {
610+
error!(err =% DisplayErrorContext(err), "Failed to send cw telemetry event");
611+
return;
612+
},
613+
};
614+
615+
let event = TelemetryEvent::ChatAddMessageEvent(chat_add_message_event);
616+
debug!(
617+
?event,
618+
?user_context,
619+
telemetry_enabled = self.telemetry_enabled,
620+
"Sending cw telemetry event"
621+
);
622+
if let Err(err) = codewhisperer_client
623+
.send_telemetry_event(event, user_context, self.telemetry_enabled, model.to_owned())
624+
.await
625+
{
608626
error!(err =% DisplayErrorContext(err), "Failed to send cw telemetry event");
609-
return;
610-
},
611-
};
612-
613-
let event = TelemetryEvent::ChatAddMessageEvent(chat_add_message_event);
614-
debug!(
615-
?event,
616-
?user_context,
617-
telemetry_enabled = self.telemetry_enabled,
618-
"Sending cw telemetry event"
619-
);
620-
if let Err(err) = codewhisperer_client
621-
.send_telemetry_event(event, user_context, self.telemetry_enabled, model.to_owned())
622-
.await
623-
{
624-
error!(err =% DisplayErrorContext(err), "Failed to send cw telemetry event");
625-
}
627+
}
628+
},
629+
EventType::AgentContribution {
630+
conversation_id,
631+
utterance_id,
632+
lines_by_agent,
633+
..
634+
} => {
635+
let user_context = self.user_context().unwrap();
636+
637+
let builder = ChatInteractWithMessageEvent::builder()
638+
.conversation_id(conversation_id)
639+
.message_id(utterance_id.clone().unwrap_or("not_set".to_string()))
640+
.accepted_line_count(lines_by_agent.map_or(0, |lines| lines as i32))
641+
.interaction_type(ChatMessageInteractionType::AgenticCodeAccepted);
642+
643+
let chat_interact_event = match builder.build() {
644+
Ok(event) => event,
645+
Err(err) => {
646+
error!(err =% DisplayErrorContext(err), "Failed to build ChatInteractWithMessageEvent");
647+
return;
648+
},
649+
};
650+
651+
let event = TelemetryEvent::ChatInteractWithMessageEvent(chat_interact_event);
652+
debug!(
653+
?event,
654+
?user_context,
655+
telemetry_enabled = self.telemetry_enabled,
656+
"Sending cw telemetry event"
657+
);
658+
if let Err(err) = codewhisperer_client
659+
.send_telemetry_event(event, user_context, self.telemetry_enabled, None)
660+
.await
661+
{
662+
error!(err =% DisplayErrorContext(err), "Failed to send cw telemetry event");
663+
}
664+
},
665+
_ => {
666+
// No CW telemetry event for other event types
667+
},
626668
}
627669
}
628670

0 commit comments

Comments
 (0)