Skip to content

Commit 040dd56

Browse files
committed
chore: Updates runtime models
1 parent f0df0e3 commit 040dd56

File tree

7 files changed

+271
-40
lines changed

7 files changed

+271
-40
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ impl ApiClient {
382382
conversation_id,
383383
user_input_message,
384384
history,
385+
agent_continuation_id,
385386
} = conversation;
386387

387388
let model_id_opt: Option<String> = user_input_message.model_id.clone();
@@ -400,6 +401,8 @@ impl ApiClient {
400401
.map(|v| v.into_iter().map(|i| i.try_into()).collect::<Result<Vec<_>, _>>())
401402
.transpose()?,
402403
)
404+
.set_agent_continuation_id(agent_continuation_id)
405+
.agent_task_type(amzn_codewhisperer_streaming_client::types::AgentTaskType::Vibe)
403406
.build()
404407
.expect("building conversation should not fail");
405408

@@ -744,6 +747,7 @@ mod tests {
744747
model_id: Some("model".to_owned()),
745748
},
746749
history: None,
750+
agent_continuation_id: None,
747751
})
748752
.await
749753
.unwrap();

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

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ pub struct ConversationState {
9797
pub conversation_id: Option<String>,
9898
pub user_input_message: UserInputMessage,
9999
pub history: Option<Vec<ChatMessage>>,
100+
pub agent_continuation_id: Option<String>,
100101
}
101102

102103
#[derive(Debug, Clone)]
@@ -563,6 +564,12 @@ pub enum ChatResponseStream {
563564
conversation_id: Option<String>,
564565
utterance_id: Option<String>,
565566
},
567+
MetadataEvent {
568+
total_tokens: Option<i32>,
569+
},
570+
MeteringEvent {
571+
usage: Option<f64>,
572+
},
566573
SupplementaryWebLinksEvent(()),
567574
ToolUseEvent {
568575
tool_use_id: String,
@@ -609,6 +616,23 @@ impl From<amzn_codewhisperer_streaming_client::types::ChatResponseStream> for Ch
609616
conversation_id,
610617
utterance_id,
611618
},
619+
amzn_codewhisperer_streaming_client::types::ChatResponseStream::MetadataEvent(
620+
amzn_codewhisperer_streaming_client::types::MetadataEvent { token_usage, .. },
621+
) => {
622+
println!(
623+
"DEBUG: Codewhisperer MetadataEvent matched! token_usage: {:?}",
624+
token_usage
625+
);
626+
ChatResponseStream::MetadataEvent {
627+
total_tokens: token_usage.map(|t| t.total_tokens),
628+
}
629+
},
630+
amzn_codewhisperer_streaming_client::types::ChatResponseStream::MeteringEvent(
631+
amzn_codewhisperer_streaming_client::types::MeteringEvent { usage, .. },
632+
) => {
633+
println!("DEBUG: Codewhisperer MeteringEvent matched! usage: {:?}", usage);
634+
ChatResponseStream::MeteringEvent { usage }
635+
},
612636
amzn_codewhisperer_streaming_client::types::ChatResponseStream::ToolUseEvent(
613637
amzn_codewhisperer_streaming_client::types::ToolUseEvent {
614638
tool_use_id,
@@ -626,7 +650,10 @@ impl From<amzn_codewhisperer_streaming_client::types::ChatResponseStream> for Ch
626650
amzn_codewhisperer_streaming_client::types::ChatResponseStream::SupplementaryWebLinksEvent(_) => {
627651
ChatResponseStream::SupplementaryWebLinksEvent(())
628652
},
629-
_ => ChatResponseStream::Unknown,
653+
other => {
654+
println!("DEBUG: Codewhisperer catch-all - Unknown event: {:?}", other);
655+
ChatResponseStream::Unknown
656+
},
630657
}
631658
}
632659
}
@@ -665,6 +692,23 @@ impl From<amzn_qdeveloper_streaming_client::types::ChatResponseStream> for ChatR
665692
conversation_id,
666693
utterance_id,
667694
},
695+
amzn_qdeveloper_streaming_client::types::ChatResponseStream::MetadataEvent(
696+
amzn_qdeveloper_streaming_client::types::MetadataEvent { token_usage, .. },
697+
) => {
698+
println!(
699+
"DEBUG: QDeveloper MetadataEvent matched! token_usage: {:?}",
700+
token_usage
701+
);
702+
ChatResponseStream::MetadataEvent {
703+
total_tokens: token_usage.map(|t| t.total_tokens),
704+
}
705+
},
706+
amzn_qdeveloper_streaming_client::types::ChatResponseStream::MeteringEvent(
707+
amzn_qdeveloper_streaming_client::types::MeteringEvent { usage, .. },
708+
) => {
709+
println!("DEBUG: QDeveloper MeteringEvent matched! usage: {:?}", usage);
710+
ChatResponseStream::MeteringEvent { usage }
711+
},
668712
amzn_qdeveloper_streaming_client::types::ChatResponseStream::ToolUseEvent(
669713
amzn_qdeveloper_streaming_client::types::ToolUseEvent {
670714
tool_use_id,
@@ -682,7 +726,10 @@ impl From<amzn_qdeveloper_streaming_client::types::ChatResponseStream> for ChatR
682726
amzn_qdeveloper_streaming_client::types::ChatResponseStream::SupplementaryWebLinksEvent(_) => {
683727
ChatResponseStream::SupplementaryWebLinksEvent(())
684728
},
685-
_ => ChatResponseStream::Unknown,
729+
other => {
730+
println!("DEBUG: Codewhisperer catch-all - Unknown event: {:?}", other);
731+
ChatResponseStream::Unknown
732+
},
686733
}
687734
}
688735
}
@@ -870,7 +917,7 @@ impl From<UserInputMessage> for amzn_codewhisperer_streaming_client::types::User
870917
.set_user_input_message_context(value.user_input_message_context.map(Into::into))
871918
.set_user_intent(value.user_intent.map(Into::into))
872919
.set_model_id(value.model_id)
873-
.origin(amzn_codewhisperer_streaming_client::types::Origin::Cli)
920+
.origin(amzn_codewhisperer_streaming_client::types::Origin::AiEditor)
874921
.build()
875922
.expect("Failed to build UserInputMessage")
876923
}
@@ -884,7 +931,7 @@ impl From<UserInputMessage> for amzn_qdeveloper_streaming_client::types::UserInp
884931
.set_user_input_message_context(value.user_input_message_context.map(Into::into))
885932
.set_user_intent(value.user_intent.map(Into::into))
886933
.set_model_id(value.model_id)
887-
.origin(amzn_qdeveloper_streaming_client::types::Origin::Cli)
934+
.origin(amzn_qdeveloper_streaming_client::types::Origin::AiEditor)
888935
.build()
889936
.expect("Failed to build UserInputMessage")
890937
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2490,23 +2490,23 @@ mod tests {
24902490
prompt_get: prompt1,
24912491
};
24922492

2493-
let bundles = vec![&bundle1, &bundle2];
2493+
let bundles = [&bundle1, &bundle2];
24942494

24952495
// Test filtering by server
24962496
let filtered: Vec<&PromptBundle> = bundles.iter().filter(|b| b.server_name == "server1").copied().collect();
24972497
assert_eq!(filtered.len(), 1);
24982498
assert_eq!(filtered[0].server_name, "server1");
24992499

25002500
// Test no filtering (all bundles)
2501-
let all: Vec<&PromptBundle> = bundles.iter().copied().collect();
2501+
let all: Vec<&PromptBundle> = bundles.to_vec();
25022502
assert_eq!(all.len(), 2);
25032503
}
25042504

25052505
#[test]
25062506
fn test_ambiguous_prompt_message_generation() {
25072507
// Test generating disambiguation message
25082508
let prompt_name = "test_prompt";
2509-
let server_names = vec!["server1", "server2", "server3"];
2509+
let server_names = ["server1", "server2", "server3"];
25102510

25112511
let alt_names: Vec<String> = server_names
25122512
.iter()

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ use tracing::{
2727
debug,
2828
warn,
2929
};
30+
use uuid;
3031

3132
use super::cli::compact::CompactStrategy;
3233
use super::cli::hooks::HookOutput;
@@ -149,6 +150,9 @@ pub struct ConversationState {
149150
/// Tangent mode checkpoint - stores main conversation when in tangent mode
150151
#[serde(default, skip_serializing_if = "Option::is_none")]
151152
tangent_state: Option<ConversationCheckpoint>,
153+
/// Current continuation ID for billing tracking - generated per conversation turn
154+
#[serde(default, skip_serializing_if = "Option::is_none")]
155+
current_continuation_id: Option<String>,
152156
}
153157

154158
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -212,6 +216,7 @@ impl ConversationState {
212216
checkpoint_manager: None,
213217
mcp_enabled,
214218
tangent_state: None,
219+
current_continuation_id: None,
215220
}
216221
}
217222

@@ -376,6 +381,12 @@ impl ConversationState {
376381

377382
pub fn reset_next_user_message(&mut self) {
378383
self.next_message = None;
384+
// Don't clear continuation_id here - it should persist for the entire turn
385+
}
386+
387+
/// Returns the current continuation ID for billing tracking
388+
pub fn continuation_id(&self) -> Option<&str> {
389+
self.current_continuation_id.as_deref()
379390
}
380391

381392
pub async fn set_next_user_message(&mut self, input: String) {
@@ -391,6 +402,10 @@ impl ConversationState {
391402
input
392403
};
393404

405+
// Generate new continuation ID for this turn
406+
let new_continuation_id = uuid::Uuid::new_v4().to_string();
407+
self.current_continuation_id = Some(new_continuation_id);
408+
394409
let msg = UserMessage::new_prompt(input, Some(Local::now().fixed_offset()));
395410
self.next_message = Some(msg);
396411
}
@@ -614,6 +629,7 @@ impl ConversationState {
614629
dropped_context_files,
615630
tools: &self.tools,
616631
model_id: self.model_info.as_ref().map(|m| m.model_id.as_str()),
632+
continuation_id: self.current_continuation_id.as_deref(),
617633
})
618634
}
619635

@@ -719,6 +735,7 @@ impl ConversationState {
719735
.unwrap_or(UserMessage::new_prompt(summary_content, None)) // should not happen
720736
.into_user_input_message(self.model_info.as_ref().map(|m| m.model_id.clone()), &tools),
721737
history: Some(flatten_history(history.iter())),
738+
agent_continuation_id: self.current_continuation_id.clone(),
722739
})
723740
}
724741

@@ -777,6 +794,7 @@ Return only the JSON configuration, no additional text.",
777794
conversation_id: Some(self.conversation_id.clone()),
778795
user_input_message: generation_message.into_user_input_message(self.model.clone(), &tools),
779796
history: Some(flatten_history(history.iter())),
797+
agent_continuation_id: self.current_continuation_id.clone(),
780798
})
781799
}
782800

@@ -957,6 +975,9 @@ Return only the JSON configuration, no additional text.",
957975
}
958976
}
959977

978+
#[cfg(test)]
979+
mod continuation_tests;
980+
960981
pub fn format_tool_spec(tool_spec: HashMap<String, ToolSpec>) -> HashMap<ToolOrigin, Vec<Tool>> {
961982
tool_spec
962983
.into_values()
@@ -992,6 +1013,7 @@ pub struct BackendConversationStateImpl<'a, T, U> {
9921013
pub dropped_context_files: Vec<(String, String)>,
9931014
pub tools: &'a HashMap<ToolOrigin, Vec<Tool>>,
9941015
pub model_id: Option<&'a str>,
1016+
pub continuation_id: Option<&'a str>,
9951017
}
9961018

9971019
impl BackendConversationStateImpl<'_, std::collections::vec_deque::Iter<'_, HistoryEntry>, Option<Vec<HistoryEntry>>> {
@@ -1007,6 +1029,7 @@ impl BackendConversationStateImpl<'_, std::collections::vec_deque::Iter<'_, Hist
10071029
conversation_id: Some(self.conversation_id.to_string()),
10081030
user_input_message,
10091031
history: Some(history),
1032+
agent_continuation_id: self.continuation_id.map(str::to_string),
10101033
})
10111034
}
10121035

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// ABOUTME: Tests for continuation ID billing functionality
2+
// ABOUTME: Ensures continuation IDs are generated and propagated correctly for billing consistency
3+
4+
#[cfg(test)]
5+
mod tests {
6+
use std::collections::HashMap;
7+
8+
use uuid;
9+
10+
use super::super::ConversationState;
11+
use crate::cli::agent::Agents;
12+
use crate::cli::chat::tool_manager::ToolManager;
13+
use crate::os::Os;
14+
15+
#[tokio::test]
16+
async fn test_continuation_id_generation() {
17+
// Setup
18+
let os = Os::new().await.unwrap();
19+
let agents = Agents::default();
20+
let tool_manager = ToolManager::default();
21+
let mut conversation = ConversationState::new(
22+
"test-conversation",
23+
agents,
24+
HashMap::new(),
25+
tool_manager,
26+
None,
27+
&os,
28+
true,
29+
)
30+
.await;
31+
32+
// Initially no continuation ID
33+
assert!(conversation.continuation_id().is_none());
34+
35+
// Set user message should generate continuation ID
36+
conversation.set_next_user_message("test message".to_string()).await;
37+
38+
let continuation_id = conversation.continuation_id();
39+
assert!(continuation_id.is_some());
40+
assert!(!continuation_id.unwrap().is_empty());
41+
}
42+
43+
#[tokio::test]
44+
async fn test_continuation_id_uniqueness() {
45+
// Setup
46+
let os = Os::new().await.unwrap();
47+
let agents = Agents::default();
48+
let tool_manager = ToolManager::default();
49+
let mut conversation = ConversationState::new(
50+
"test-conversation",
51+
agents,
52+
HashMap::new(),
53+
tool_manager,
54+
None,
55+
&os,
56+
true,
57+
)
58+
.await;
59+
60+
// Generate first continuation ID
61+
conversation.set_next_user_message("first message".to_string()).await;
62+
let first_id = conversation.continuation_id().unwrap().to_string();
63+
64+
// Reset and generate second continuation ID
65+
conversation.reset_next_user_message();
66+
conversation.set_next_user_message("second message".to_string()).await;
67+
let second_id = conversation.continuation_id().unwrap().to_string();
68+
69+
// Should be different
70+
assert_ne!(first_id, second_id);
71+
}
72+
73+
#[tokio::test]
74+
async fn test_continuation_id_format() {
75+
// Setup
76+
let os = Os::new().await.unwrap();
77+
let agents = Agents::default();
78+
let tool_manager = ToolManager::default();
79+
let mut conversation = ConversationState::new(
80+
"test-conversation",
81+
agents,
82+
HashMap::new(),
83+
tool_manager,
84+
None,
85+
&os,
86+
true,
87+
)
88+
.await;
89+
90+
// Generate continuation ID
91+
conversation.set_next_user_message("test message".to_string()).await;
92+
let continuation_id = conversation.continuation_id().unwrap();
93+
94+
// Should be a valid UUID format (36 characters with hyphens)
95+
assert_eq!(continuation_id.len(), 36);
96+
assert!(continuation_id.chars().filter(|&c| c == '-').count() == 4);
97+
98+
// Should be parseable as UUID
99+
assert!(uuid::Uuid::parse_str(continuation_id).is_ok());
100+
}
101+
102+
#[tokio::test]
103+
async fn test_continuation_id_in_api_request() {
104+
// Setup
105+
let os = Os::new().await.unwrap();
106+
let agents = Agents::default();
107+
let tool_manager = ToolManager::default();
108+
let mut conversation = ConversationState::new(
109+
"test-conversation",
110+
agents,
111+
HashMap::new(),
112+
tool_manager,
113+
None,
114+
&os,
115+
true,
116+
)
117+
.await;
118+
119+
// Set user message to generate continuation ID
120+
conversation.set_next_user_message("test message".to_string()).await;
121+
let expected_continuation_id = conversation.continuation_id().unwrap().to_string();
122+
123+
// Create sendable conversation state (this is what gets sent to API)
124+
let mut stderr = std::io::stderr();
125+
let sendable_state = conversation
126+
.as_sendable_conversation_state(&os, &mut stderr, false)
127+
.await
128+
.unwrap();
129+
130+
// Verify continuation ID is included in the API request
131+
assert_eq!(
132+
sendable_state.agent_continuation_id.as_deref(),
133+
Some(expected_continuation_id.as_str())
134+
);
135+
}
136+
}

0 commit comments

Comments
 (0)