Skip to content

Commit c3276a4

Browse files
authored
feat: add tangent mode duration tracking telemetry (#2710)
- Track time spent in tangent mode sessions - Send duration metric when exiting tangent mode via /tangent command - Use codewhispererterminal_chatSlashCommandExecuted metric with: - command: 'tangent' - subcommand: 'exit' - value: duration in seconds - Add comprehensive tests for duration calculation - Enables analytics on tangent mode usage patterns
1 parent 5bf5afa commit c3276a4

File tree

4 files changed

+154
-0
lines changed

4 files changed

+154
-0
lines changed

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,25 @@ pub struct TangentArgs;
1818
impl TangentArgs {
1919
pub async fn execute(self, os: &Os, session: &mut ChatSession) -> Result<ChatState, ChatError> {
2020
if session.conversation.is_in_tangent_mode() {
21+
// Get duration before exiting tangent mode
22+
let duration_seconds = session.conversation.get_tangent_duration_seconds().unwrap_or(0);
23+
2124
session.conversation.exit_tangent_mode();
25+
26+
// Send telemetry for tangent mode session
27+
if let Err(err) = os
28+
.telemetry
29+
.send_tangent_mode_session(
30+
&os.database,
31+
session.conversation.conversation_id().to_string(),
32+
crate::telemetry::TelemetryResult::Succeeded,
33+
crate::telemetry::core::TangentModeSessionArgs { duration_seconds },
34+
)
35+
.await
36+
{
37+
tracing::warn!(?err, "Failed to send tangent mode session telemetry");
38+
}
39+
2240
execute!(
2341
session.stderr,
2442
style::SetForegroundColor(Color::DarkGrey),
@@ -69,3 +87,43 @@ impl TangentArgs {
6987
})
7088
}
7189
}
90+
91+
#[cfg(test)]
92+
mod tests {
93+
use crate::cli::agent::Agents;
94+
use crate::cli::chat::conversation::ConversationState;
95+
use crate::cli::chat::tool_manager::ToolManager;
96+
use crate::os::Os;
97+
98+
#[tokio::test]
99+
async fn test_tangent_mode_duration_tracking() {
100+
let mut os = Os::new().await.unwrap();
101+
let agents = Agents::default();
102+
let mut tool_manager = ToolManager::default();
103+
let mut conversation = ConversationState::new(
104+
"test_conv_id",
105+
agents,
106+
tool_manager.load_tools(&mut os, &mut vec![]).await.unwrap(),
107+
tool_manager,
108+
None,
109+
&os,
110+
false, // mcp_enabled
111+
)
112+
.await;
113+
114+
// Test entering tangent mode
115+
assert!(!conversation.is_in_tangent_mode());
116+
conversation.enter_tangent_mode();
117+
assert!(conversation.is_in_tangent_mode());
118+
119+
// Should have a duration
120+
let duration = conversation.get_tangent_duration_seconds();
121+
assert!(duration.is_some());
122+
assert!(duration.unwrap() >= 0);
123+
124+
// Test exiting tangent mode
125+
conversation.exit_tangent_mode();
126+
assert!(!conversation.is_in_tangent_mode());
127+
assert!(conversation.get_tangent_duration_seconds().is_none());
128+
}
129+
}

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ struct ConversationCheckpoint {
139139
main_next_message: Option<UserMessage>,
140140
/// Main conversation transcript
141141
main_transcript: VecDeque<String>,
142+
/// Timestamp when tangent mode was entered (milliseconds since epoch)
143+
#[serde(default = "time::OffsetDateTime::now_utc")]
144+
tangent_start_time: time::OffsetDateTime,
142145
}
143146

144147
impl ConversationState {
@@ -229,6 +232,7 @@ impl ConversationState {
229232
main_history: self.history.clone(),
230233
main_next_message: self.next_message.clone(),
231234
main_transcript: self.transcript.clone(),
235+
tangent_start_time: time::OffsetDateTime::now_utc(),
232236
}
233237
}
234238

@@ -247,6 +251,14 @@ impl ConversationState {
247251
}
248252
}
249253

254+
/// Get tangent mode duration in seconds if currently in tangent mode
255+
pub fn get_tangent_duration_seconds(&self) -> Option<i64> {
256+
self.tangent_state.as_ref().map(|checkpoint| {
257+
let now = time::OffsetDateTime::now_utc();
258+
(now - checkpoint.tangent_start_time).whole_seconds()
259+
})
260+
}
261+
250262
/// Exit tangent mode - restore from checkpoint
251263
pub fn exit_tangent_mode(&mut self) {
252264
if let Some(checkpoint) = self.tangent_state.take() {
@@ -1412,4 +1424,40 @@ mod tests {
14121424
conversation.exit_tangent_mode();
14131425
assert!(!conversation.is_in_tangent_mode());
14141426
}
1427+
1428+
#[tokio::test]
1429+
async fn test_tangent_mode_duration() {
1430+
let mut os = Os::new().await.unwrap();
1431+
let agents = Agents::default();
1432+
let mut tool_manager = ToolManager::default();
1433+
let mut conversation = ConversationState::new(
1434+
"fake_conv_id",
1435+
agents,
1436+
tool_manager.load_tools(&mut os, &mut vec![]).await.unwrap(),
1437+
tool_manager,
1438+
None,
1439+
&os,
1440+
false, // mcp_enabled
1441+
)
1442+
.await;
1443+
1444+
// Initially not in tangent mode, no duration
1445+
assert!(conversation.get_tangent_duration_seconds().is_none());
1446+
1447+
// Enter tangent mode
1448+
conversation.enter_tangent_mode();
1449+
assert!(conversation.is_in_tangent_mode());
1450+
1451+
// Should have a duration (likely 0 seconds since it just started)
1452+
let duration = conversation.get_tangent_duration_seconds();
1453+
assert!(duration.is_some());
1454+
assert!(duration.unwrap() >= 0);
1455+
1456+
// Exit tangent mode
1457+
conversation.exit_tangent_mode();
1458+
assert!(!conversation.is_in_tangent_mode());
1459+
1460+
// No duration when not in tangent mode
1461+
assert!(conversation.get_tangent_duration_seconds().is_none());
1462+
}
14151463
}

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,25 @@ impl Event {
286286
}
287287
.into_metric_datum(),
288288
),
289+
EventType::TangentModeSession {
290+
conversation_id,
291+
result,
292+
args: TangentModeSessionArgs { duration_seconds },
293+
} => Some(
294+
CodewhispererterminalChatSlashCommandExecuted {
295+
create_time: self.created_time,
296+
value: Some(duration_seconds as f64),
297+
credential_start_url: self.credential_start_url.map(Into::into),
298+
sso_region: self.sso_region.map(Into::into),
299+
amazonq_conversation_id: Some(conversation_id.into()),
300+
codewhispererterminal_chat_slash_command: Some("tangent".to_string().into()),
301+
codewhispererterminal_chat_slash_subcommand: Some("exit".to_string().into()),
302+
result: Some(result.to_string().into()),
303+
reason: None,
304+
codewhispererterminal_in_cloudshell: None,
305+
}
306+
.into_metric_datum(),
307+
),
289308
EventType::ToolUseSuggested {
290309
conversation_id,
291310
utterance_id,
@@ -528,6 +547,13 @@ pub struct ChatAddedMessageParams {
528547
pub message_meta_tags: Vec<MessageMetaTag>,
529548
}
530549

550+
/// Optional fields for tangent mode session telemetry event.
551+
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
552+
pub struct TangentModeSessionArgs {
553+
/// Duration of tangent mode session in seconds
554+
pub duration_seconds: i64,
555+
}
556+
531557
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)]
532558
pub struct RecordUserTurnCompletionArgs {
533559
pub request_ids: Vec<Option<String>>,
@@ -592,6 +618,11 @@ pub enum EventType {
592618
result: TelemetryResult,
593619
args: RecordUserTurnCompletionArgs,
594620
},
621+
TangentModeSession {
622+
conversation_id: String,
623+
result: TelemetryResult,
624+
args: TangentModeSessionArgs,
625+
},
595626
ToolUseSuggested {
596627
conversation_id: String,
597628
utterance_id: Option<String>,

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use core::{
88
AgentConfigInitArgs,
99
ChatAddedMessageParams,
1010
RecordUserTurnCompletionArgs,
11+
TangentModeSessionArgs,
1112
ToolUseEventBuilder,
1213
};
1314
use std::str::FromStr;
@@ -322,6 +323,22 @@ impl TelemetryThread {
322323
Ok(self.tx.send(telemetry_event)?)
323324
}
324325

326+
pub async fn send_tangent_mode_session(
327+
&self,
328+
database: &Database,
329+
conversation_id: String,
330+
result: TelemetryResult,
331+
args: TangentModeSessionArgs,
332+
) -> Result<(), TelemetryError> {
333+
let mut telemetry_event = Event::new(EventType::TangentModeSession {
334+
conversation_id,
335+
result,
336+
args,
337+
});
338+
set_event_metadata(database, &mut telemetry_event).await;
339+
Ok(self.tx.send(telemetry_event)?)
340+
}
341+
325342
pub async fn send_tool_use_suggested(
326343
&self,
327344
database: &Database,

0 commit comments

Comments
 (0)