Skip to content

Commit e1b92fc

Browse files
chaynaborsKonippicwoolum
authored
refactor: telemetry, state, and settings handling (#1757)
* Update login copy * chore: fix path to the dashboard README (#1696) * feat: add Windows support for os shim (#1714) Co-authored-by: Woolum <[email protected]> * start telemetry, state, and settings refactor * finish rewrite * finish rewrite * finish rewrite (actually?) * change profile set copy * fix spellcheck lint --------- Co-authored-by: Kyosuke Konishi <[email protected]> Co-authored-by: Chris Woolum <[email protected]> Co-authored-by: Woolum <[email protected]>
1 parent 1bf0f12 commit e1b92fc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1744
-3100
lines changed

crates/chat-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ homepage.workspace = true
66
publish.workspace = true
77
version.workspace = true
88
license.workspace = true
9+
default-run = "chat_cli"
910

1011
[lints]
1112
workspace = true

crates/chat-cli/src/api_client/clients/client.rs

Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,20 @@ use tracing::error;
88

99
use super::shared::bearer_sdk_config;
1010
use crate::api_client::interceptor::opt_out::OptOutInterceptor;
11-
use crate::api_client::profile::Profile;
1211
use crate::api_client::{
1312
ApiClientError,
1413
Endpoint,
1514
};
15+
use crate::auth::AuthError;
1616
use crate::auth::builder_id::BearerResolver;
1717
use crate::aws_common::{
1818
UserAgentOverrideInterceptor,
1919
app_name,
2020
};
21+
use crate::database::{
22+
AuthProfile,
23+
Database,
24+
};
2125

2226
mod inner {
2327
use amzn_codewhisperer_client::Client as CodewhispererClient;
@@ -32,76 +36,60 @@ mod inner {
3236
#[derive(Clone, Debug)]
3337
pub struct Client {
3438
inner: inner::Inner,
35-
profile_arn: Option<String>,
39+
profile: Option<AuthProfile>,
3640
}
3741

3842
impl Client {
39-
pub async fn new() -> Result<Client, ApiClientError> {
43+
pub async fn new(database: &mut Database, endpoint: Option<Endpoint>) -> Result<Client, AuthError> {
4044
if cfg!(test) {
4145
return Ok(Self {
4246
inner: inner::Inner::Mock,
43-
profile_arn: None,
47+
profile: None,
4448
});
4549
}
4650

47-
let endpoint = Endpoint::load_codewhisperer();
48-
Ok(Self::new_codewhisperer_client(&endpoint).await)
49-
}
50-
51-
pub async fn new_codewhisperer_client(endpoint: &Endpoint) -> Self {
52-
let conf_builder: amzn_codewhisperer_client::config::Builder = (&bearer_sdk_config(endpoint).await).into();
51+
let endpoint = endpoint.unwrap_or(Endpoint::load_codewhisperer(database));
52+
let conf_builder: amzn_codewhisperer_client::config::Builder =
53+
(&bearer_sdk_config(database, &endpoint).await).into();
5354
let conf = conf_builder
5455
.http_client(crate::aws_common::http_client::client())
55-
.interceptor(OptOutInterceptor::new())
56+
.interceptor(OptOutInterceptor::new(database))
5657
.interceptor(UserAgentOverrideInterceptor::new())
57-
.bearer_token_resolver(BearerResolver)
58+
.bearer_token_resolver(BearerResolver::new(database).await?)
5859
.app_name(app_name())
5960
.endpoint_url(endpoint.url())
6061
.build();
6162

6263
let inner = inner::Inner::Codewhisperer(CodewhispererClient::from_conf(conf));
6364

64-
let profile_arn = match crate::settings::state::get_value("api.codewhisperer.profile") {
65-
Ok(Some(profile)) => match profile.get("arn") {
66-
Some(arn) => match arn.as_str() {
67-
Some(arn) => Some(arn.to_string()),
68-
None => {
69-
error!("Stored arn is not a string. Instead it was: {arn}");
70-
None
71-
},
72-
},
73-
None => {
74-
error!("Stored profile does not contain an arn. Instead it was: {profile}");
75-
None
76-
},
77-
},
78-
Ok(None) => None,
65+
let profile = match database.get_auth_profile() {
66+
Ok(profile) => profile,
7967
Err(err) => {
80-
error!("Failed to retrieve profile: {}", err);
68+
error!("Failed to get auth profile: {err}");
8169
None
8270
},
8371
};
8472

85-
Self { inner, profile_arn }
73+
Ok(Self { inner, profile })
8674
}
8775

88-
// .telemetry_event(TelemetryEvent::UserTriggerDecisionEvent(user_trigger_decision_event))
89-
// .user_context(user_context)
90-
// .opt_out_preference(opt_out_preference)
9176
pub async fn send_telemetry_event(
9277
&self,
9378
telemetry_event: TelemetryEvent,
9479
user_context: UserContext,
95-
opt_out: OptOutPreference,
80+
telemetry_enabled: bool,
9681
) -> Result<(), ApiClientError> {
9782
match &self.inner {
9883
inner::Inner::Codewhisperer(client) => {
9984
let _ = client
10085
.send_telemetry_event()
10186
.telemetry_event(telemetry_event)
10287
.user_context(user_context)
103-
.opt_out_preference(opt_out)
104-
.set_profile_arn(self.profile_arn.clone())
88+
.opt_out_preference(match telemetry_enabled {
89+
true => OptOutPreference::OptIn,
90+
false => OptOutPreference::OptOut,
91+
})
92+
.set_profile_arn(self.profile.as_ref().map(|p| p.arn.clone()))
10593
.send()
10694
.await;
10795
Ok(())
@@ -110,23 +98,23 @@ impl Client {
11098
}
11199
}
112100

113-
pub async fn list_available_profiles(&self) -> Result<Vec<Profile>, ApiClientError> {
101+
pub async fn list_available_profiles(&self) -> Result<Vec<AuthProfile>, ApiClientError> {
114102
match &self.inner {
115103
inner::Inner::Codewhisperer(client) => {
116104
let mut profiles = vec![];
117105
let mut client = client.list_available_profiles().into_paginator().send();
118106
while let Some(profiles_output) = client.next().await {
119-
profiles.extend(profiles_output?.profiles().iter().cloned().map(Profile::from));
107+
profiles.extend(profiles_output?.profiles().iter().cloned().map(AuthProfile::from));
120108
}
121109

122110
Ok(profiles)
123111
},
124112
inner::Inner::Mock => Ok(vec![
125-
Profile {
113+
AuthProfile {
126114
arn: "my:arn:1".to_owned(),
127115
profile_name: "MyProfile".to_owned(),
128116
},
129-
Profile {
117+
AuthProfile {
130118
arn: "my:arn:2".to_owned(),
131119
profile_name: "MyOtherProfile".to_owned(),
132120
},
@@ -147,15 +135,14 @@ mod tests {
147135

148136
#[tokio::test]
149137
async fn create_clients() {
150-
let endpoint = Endpoint::load_codewhisperer();
151-
152-
let _ = Client::new().await;
153-
let _ = Client::new_codewhisperer_client(&endpoint).await;
138+
let mut database = crate::database::Database::new().await.unwrap();
139+
let _ = Client::new(&mut database, None).await;
154140
}
155141

156142
#[tokio::test]
157143
async fn test_mock() {
158-
let client = Client::new().await.unwrap();
144+
let mut database = crate::database::Database::new().await.unwrap();
145+
let client = Client::new(&mut database, None).await.unwrap();
159146
client
160147
.send_telemetry_event(
161148
TelemetryEvent::ChatAddMessageEvent(
@@ -171,7 +158,7 @@ mod tests {
171158
.product("<product>")
172159
.build()
173160
.unwrap(),
174-
OptOutPreference::OptIn,
161+
false,
175162
)
176163
.await
177164
.unwrap();

crates/chat-cli/src/api_client/clients/shared.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@ use crate::api_client::{
1414
Endpoint,
1515
};
1616
use crate::aws_common::behavior_version;
17+
use crate::database::Database;
18+
use crate::database::settings::Setting;
1719

1820
// TODO(bskiser): confirm timeout is updated to an appropriate value?
1921
const DEFAULT_TIMEOUT_DURATION: Duration = Duration::from_secs(60 * 5);
2022

21-
pub(crate) fn timeout_config() -> TimeoutConfig {
22-
let timeout = crate::settings::settings::get_int("api.timeout")
23-
.ok()
24-
.flatten()
23+
pub fn timeout_config(database: &Database) -> TimeoutConfig {
24+
let timeout = database
25+
.settings
26+
.get_int(Setting::ApiTimeout)
2527
.and_then(|i| i.try_into().ok())
2628
.map_or(DEFAULT_TIMEOUT_DURATION, Duration::from_millis);
2729

@@ -39,27 +41,31 @@ pub(crate) fn stalled_stream_protection_config() -> StalledStreamProtectionConfi
3941
.build()
4042
}
4143

42-
async fn base_sdk_config(region: Region, credentials_provider: impl ProvideCredentials + 'static) -> SdkConfig {
44+
async fn base_sdk_config(
45+
database: &Database,
46+
region: Region,
47+
credentials_provider: impl ProvideCredentials + 'static,
48+
) -> SdkConfig {
4349
aws_config::defaults(behavior_version())
4450
.region(region)
4551
.credentials_provider(credentials_provider)
46-
.timeout_config(timeout_config())
52+
.timeout_config(timeout_config(database))
4753
.retry_config(RetryConfig::adaptive())
4854
.load()
4955
.await
5056
}
5157

52-
pub(crate) async fn bearer_sdk_config(endpoint: &Endpoint) -> SdkConfig {
58+
pub async fn bearer_sdk_config(database: &Database, endpoint: &Endpoint) -> SdkConfig {
5359
let credentials = Credentials::new("xxx", "xxx", None, None, "xxx");
54-
base_sdk_config(endpoint.region().clone(), credentials).await
60+
base_sdk_config(database, endpoint.region().clone(), credentials).await
5561
}
5662

57-
pub(crate) async fn sigv4_sdk_config(endpoint: &Endpoint) -> Result<SdkConfig, ApiClientError> {
63+
pub async fn sigv4_sdk_config(database: &Database, endpoint: &Endpoint) -> Result<SdkConfig, ApiClientError> {
5864
let credentials_chain = CredentialsChain::new().await;
5965

6066
if let Err(err) = credentials_chain.provide_credentials().await {
6167
return Err(ApiClientError::Credentials(err));
6268
};
6369

64-
Ok(base_sdk_config(endpoint.region().clone(), credentials_chain).await)
70+
Ok(base_sdk_config(database, endpoint.region().clone(), credentials_chain).await)
6571
}

crates/chat-cli/src/api_client/clients/streaming_client.rs

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ use crate::aws_common::{
3030
UserAgentOverrideInterceptor,
3131
app_name,
3232
};
33+
use crate::database::{
34+
AuthProfile,
35+
Database,
36+
};
3337

3438
mod inner {
3539
use std::sync::{
@@ -53,72 +57,63 @@ mod inner {
5357
#[derive(Clone, Debug)]
5458
pub struct StreamingClient {
5559
inner: inner::Inner,
56-
profile_arn: Option<String>,
60+
profile: Option<AuthProfile>,
5761
}
5862

5963
impl StreamingClient {
60-
pub async fn new() -> Result<Self, ApiClientError> {
61-
let client = if crate::util::system_info::in_cloudshell()
62-
|| std::env::var("Q_USE_SENDMESSAGE").is_ok_and(|v| !v.is_empty())
63-
{
64-
Self::new_qdeveloper_client(&Endpoint::load_q()).await?
65-
} else {
66-
Self::new_codewhisperer_client(&Endpoint::load_codewhisperer()).await
67-
};
68-
Ok(client)
64+
pub async fn new(database: &mut Database) -> Result<Self, ApiClientError> {
65+
Ok(
66+
if crate::util::system_info::in_cloudshell()
67+
|| std::env::var("Q_USE_SENDMESSAGE").is_ok_and(|v| !v.is_empty())
68+
{
69+
Self::new_qdeveloper_client(database, &Endpoint::load_q(database)).await?
70+
} else {
71+
Self::new_codewhisperer_client(database, &Endpoint::load_codewhisperer(database)).await?
72+
},
73+
)
6974
}
7075

7176
pub fn mock(events: Vec<Vec<ChatResponseStream>>) -> Self {
7277
Self {
7378
inner: inner::Inner::Mock(Arc::new(Mutex::new(events.into_iter()))),
74-
profile_arn: None,
79+
profile: None,
7580
}
7681
}
7782

78-
pub async fn new_codewhisperer_client(endpoint: &Endpoint) -> Self {
83+
pub async fn new_codewhisperer_client(
84+
database: &mut Database,
85+
endpoint: &Endpoint,
86+
) -> Result<Self, ApiClientError> {
7987
let conf_builder: amzn_codewhisperer_streaming_client::config::Builder =
80-
(&bearer_sdk_config(endpoint).await).into();
88+
(&bearer_sdk_config(database, endpoint).await).into();
8189
let conf = conf_builder
8290
.http_client(crate::aws_common::http_client::client())
83-
.interceptor(OptOutInterceptor::new())
91+
.interceptor(OptOutInterceptor::new(database))
8492
.interceptor(UserAgentOverrideInterceptor::new())
85-
.bearer_token_resolver(BearerResolver)
93+
.bearer_token_resolver(BearerResolver::new(database).await?)
8694
.app_name(app_name())
8795
.endpoint_url(endpoint.url())
8896
.stalled_stream_protection(stalled_stream_protection_config())
8997
.build();
9098
let inner = inner::Inner::Codewhisperer(CodewhispererStreamingClient::from_conf(conf));
9199

92-
let profile_arn = match crate::settings::state::get_value("api.codewhisperer.profile") {
93-
Ok(Some(profile)) => match profile.get("arn") {
94-
Some(arn) => match arn.as_str() {
95-
Some(arn) => Some(arn.to_string()),
96-
None => {
97-
error!("Stored arn is not a string. Instead it was: {arn}");
98-
None
99-
},
100-
},
101-
None => {
102-
error!("Stored profile does not contain an arn. Instead it was: {profile}");
103-
None
104-
},
105-
},
106-
Ok(None) => None,
100+
let profile = match database.get_auth_profile() {
101+
Ok(profile) => profile,
107102
Err(err) => {
108-
error!("Failed to retrieve profile: {}", err);
103+
error!("Failed to get auth profile: {err}");
109104
None
110105
},
111106
};
112107

113-
Self { inner, profile_arn }
108+
Ok(Self { inner, profile })
114109
}
115110

116-
pub async fn new_qdeveloper_client(endpoint: &Endpoint) -> Result<Self, ApiClientError> {
111+
pub async fn new_qdeveloper_client(database: &Database, endpoint: &Endpoint) -> Result<Self, ApiClientError> {
117112
let conf_builder: amzn_qdeveloper_streaming_client::config::Builder =
118-
(&sigv4_sdk_config(endpoint).await?).into();
113+
(&sigv4_sdk_config(database, endpoint).await?).into();
119114
let conf = conf_builder
120115
.http_client(crate::aws_common::http_client::client())
121-
.interceptor(OptOutInterceptor::new())
116+
.interceptor(OptOutInterceptor::new(database))
122117
.interceptor(UserAgentOverrideInterceptor::new())
123118
.app_name(app_name())
124119
.endpoint_url(endpoint.url())
@@ -127,7 +122,7 @@ impl StreamingClient {
127122
let client = QDeveloperStreamingClient::from_conf(conf);
128123
Ok(Self {
129124
inner: inner::Inner::QDeveloper(client),
130-
profile_arn: None,
125+
profile: None,
131126
})
132127
}
133128

@@ -162,7 +157,7 @@ impl StreamingClient {
162157
let response = client
163158
.generate_assistant_response()
164159
.conversation_state(conversation_state)
165-
.set_profile_arn(self.profile_arn.clone())
160+
.set_profile_arn(self.profile.as_ref().map(|p| p.arn.clone()))
166161
.send()
167162
.await;
168163

@@ -267,11 +262,12 @@ mod tests {
267262

268263
#[tokio::test]
269264
async fn create_clients() {
270-
let endpoint = Endpoint::load_codewhisperer();
265+
let mut database = Database::new().await.unwrap();
266+
let endpoint = Endpoint::load_codewhisperer(&database);
271267

272-
let _ = StreamingClient::new().await;
273-
let _ = StreamingClient::new_codewhisperer_client(&endpoint).await;
274-
let _ = StreamingClient::new_qdeveloper_client(&endpoint).await;
268+
let _ = StreamingClient::new(&mut database).await;
269+
let _ = StreamingClient::new_codewhisperer_client(&mut database, &endpoint).await;
270+
let _ = StreamingClient::new_qdeveloper_client(&database, &endpoint).await;
275271
}
276272

277273
#[tokio::test]
@@ -311,7 +307,8 @@ mod tests {
311307
#[ignore]
312308
#[tokio::test]
313309
async fn assistant_response() {
314-
let client = StreamingClient::new().await.unwrap();
310+
let mut database = Database::new().await.unwrap();
311+
let client = StreamingClient::new(&mut database).await.unwrap();
315312
let mut response = client
316313
.send_message(ConversationState {
317314
conversation_id: None,

0 commit comments

Comments
 (0)