Skip to content

feat: region expansion #1333

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1b9897d
add user profile sub command with mock profiles
evanliu048 Apr 23, 2025
d5b0511
checkpoint
chaynabors Apr 23, 2025
3914f68
modify protos and add handlers
chaynabors Apr 23, 2025
2a7f4a8
Merge remote-tracking branch 'origin/main' into feature/region-expansion
chaynabors Apr 23, 2025
0244372
Merge remote-tracking branch 'origin/feature/region-expansion' into r…
evanliu048 Apr 24, 2025
3ba8797
coorprate with real response
evanliu048 Apr 24, 2025
a5b958f
remove unused code
chaynabors Apr 24, 2025
a809843
add profile in whoami
evanliu048 Apr 24, 2025
0c5f454
Merge branch 'feature/region-expansion' of github.com:aws/amazon-q-de…
evanliu048 Apr 24, 2025
1df40e0
hi yifan
chaynabors Apr 24, 2025
1a60fe6
byebye ctrlc handler
chaynabors Apr 25, 2025
4f3593c
hi chay (add profile_arn)
evanliu048 Apr 25, 2025
d0144a3
remove nonstreaming q client
chaynabors Apr 25, 2025
64f2e76
add profile telemetry
evanliu048 Apr 25, 2025
20213e4
Merge branch 'feature/region-expansion' of github.com:aws/amazon-q-de…
evanliu048 Apr 25, 2025
5333084
add profile selector in dashboard
chaynabors Apr 28, 2025
37275c5
cleanup
chaynabors Apr 28, 2025
0d430c4
Merge remote-tracking branch 'origin/main' into feature/region-expansion
chaynabors Apr 28, 2025
53713bf
revise whoami flag in login&profile command flow
evanliu048 Apr 28, 2025
b845ad2
add amazonq_profileState
evanliu048 Apr 28, 2025
9f063cc
finish up major tasks
chaynabors Apr 29, 2025
6300b5c
finish up final tasks
chaynabors Apr 29, 2025
c042381
fix lints
chaynabors Apr 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion crates/amzn-toolkit-telemetry/src/config/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ pub use ::aws_smithy_runtime_api::client::endpoint::{
};
pub use ::aws_smithy_types::endpoint::Endpoint;

///
#[cfg(test)]
mod test {}

Expand Down
26 changes: 26 additions & 0 deletions crates/fig_api_client/src/clients/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use crate::model::{
RecommendationsInput,
RecommendationsOutput,
};
use crate::profile::Profile;
use crate::{
Customization,
Endpoint,
Expand Down Expand Up @@ -207,6 +208,31 @@ impl Client {
inner::Inner::Mock => Ok(()),
}
}

pub async fn list_available_profiles(&self) -> Result<Vec<Profile>, Error> {
match &self.0 {
inner::Inner::Codewhisperer(client) => {
let mut profiles = vec![];
while let Some(profiles_output) = client.list_available_profiles().into_paginator().send().next().await
{
profiles.extend(profiles_output?.profiles().iter().cloned().map(Profile::from));
}

Ok(profiles)
},
inner::Inner::Consolas(_) => Err(Error::UnsupportedConsolas("list_available_profiles")),
inner::Inner::Mock => Ok(vec![
Profile {
arn: "my:arn:1".to_owned(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - use a valid arn format for these

profile_name: "MyProfile".to_owned(),
},
Profile {
arn: "my:arn:2".to_owned(),
profile_name: "MyOtherProfile".to_owned(),
},
]),
}
}
}

async fn codewhisperer_generate_recommendation_inner(
Expand Down
2 changes: 1 addition & 1 deletion crates/fig_api_client/src/clients/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod client;
mod shared;
pub(crate) mod shared;
mod streaming_client;

pub use client::{
Expand Down
4 changes: 4 additions & 0 deletions crates/fig_api_client/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ pub const PROD_CODEWHISPERER_ENDPOINT_REGION: Region = Region::from_static("us-e
pub const PROD_Q_ENDPOINT_URL: &str = "https://q.us-east-1.amazonaws.com";
pub const PROD_Q_ENDPOINT_REGION: Region = Region::from_static("us-east-1");

// FRA endpoint constants
pub const PROD_CODEWHISPERER_FRA_ENDPOINT_URL: &str = "https://codewhisperer.eu-central-1.amazonaws.com";
pub const PROD_CODEWHISPERER_FRA_ENDPOINT_REGION: Region = Region::from_static("eu-central-1");

// Opt out constants
pub const SHARE_CODEWHISPERER_CONTENT_SETTINGS_KEY: &str = "codeWhisperer.shareCodeWhispererContentWithAWS";
pub const X_AMZN_CODEWHISPERER_OPT_OUT_HEADER: &str = "x-amzn-codewhisperer-optout";
Expand Down
49 changes: 32 additions & 17 deletions crates/fig_api_client/src/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,56 @@ use serde_json::Value;
use crate::consts::{
PROD_CODEWHISPERER_ENDPOINT_REGION,
PROD_CODEWHISPERER_ENDPOINT_URL,
PROD_CODEWHISPERER_FRA_ENDPOINT_REGION,
PROD_CODEWHISPERER_FRA_ENDPOINT_URL,
PROD_Q_ENDPOINT_REGION,
PROD_Q_ENDPOINT_URL,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Endpoint {
url: Cow<'static, str>,
region: Region,
pub url: Cow<'static, str>,
pub region: Region,
}

impl Endpoint {
const PROD_CODEWHISPERER: Self = Self {
pub const CODEWHISPERER_ENDPOINTS: [Self; 2] = [Self::DEFAULT_ENDPOINT, Self {
url: Cow::Borrowed(PROD_CODEWHISPERER_FRA_ENDPOINT_URL),
region: PROD_CODEWHISPERER_FRA_ENDPOINT_REGION,
}];
pub const DEFAULT_ENDPOINT: Self = Self {
url: Cow::Borrowed(PROD_CODEWHISPERER_ENDPOINT_URL),
region: PROD_CODEWHISPERER_ENDPOINT_REGION,
};
const PROD_Q: Self = Self {
pub const PROD_Q: Self = Self {
url: Cow::Borrowed(PROD_Q_ENDPOINT_URL),
region: PROD_Q_ENDPOINT_REGION,
};

pub fn load_codewhisperer() -> Self {
match fig_settings::settings::get_value("api.codewhisperer.service") {
Ok(Some(Value::Object(o))) => {
let endpoint = o.get("endpoint").and_then(|v| v.as_str());
let region = o.get("region").and_then(|v| v.as_str());
let (endpoint, region) =
if let Ok(Some(Value::Object(o))) = fig_settings::settings::get_value("api.codewhisperer.service") {
// The following branch is evaluated in case the user has set their own endpoint.
(
o.get("endpoint").and_then(|v| v.as_str()).map(|v| v.to_owned()),
o.get("region").and_then(|v| v.as_str()).map(|v| v.to_owned()),
)
} else if let Ok(Some(Value::Object(o))) = fig_settings::state::get_value("api.codewhisperer.service") {
// The following branch is evaluated in the case of user profile being set.
(
o.get("endpoint").and_then(|v| v.as_str()).map(|v| v.to_owned()),
o.get("region").and_then(|v| v.as_str()).map(|v| v.to_owned()),
)
} else {
(None, None)
};

match (endpoint, region) {
(Some(endpoint), Some(region)) => Self {
url: endpoint.to_owned().into(),
region: Region::new(region.to_owned()),
},
_ => Endpoint::PROD_CODEWHISPERER,
}
match (endpoint, region) {
(Some(endpoint), Some(region)) => Self {
url: endpoint.clone().into(),
region: Region::new(region.clone()),
},
_ => Endpoint::PROD_CODEWHISPERER,
_ => Endpoint::DEFAULT_ENDPOINT,
}
}

Expand Down Expand Up @@ -82,7 +97,7 @@ mod tests {
let _ = Endpoint::load_codewhisperer();
let _ = Endpoint::load_q();

let prod = Endpoint::PROD_CODEWHISPERER;
let prod = &Endpoint::CODEWHISPERER_ENDPOINTS[0];
Url::parse(prod.url()).unwrap();
assert_eq!(prod.region(), &PROD_CODEWHISPERER_ENDPOINT_REGION);

Expand Down
8 changes: 6 additions & 2 deletions crates/fig_api_client/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use amzn_codewhisperer_client::operation::generate_completions::GenerateCompletionsError;
use amzn_codewhisperer_client::operation::list_available_customizations::ListAvailableCustomizationsError;
use amzn_codewhisperer_client::operation::list_available_profiles::ListAvailableProfilesError;
pub use amzn_codewhisperer_streaming_client::operation::generate_assistant_response::GenerateAssistantResponseError;
// use amzn_codewhisperer_streaming_client::operation::send_message::SendMessageError as
// CodewhispererSendMessageError;
use amzn_codewhisperer_streaming_client::types::error::ChatResponseStreamError as CodewhispererChatResponseStreamError;
use amzn_consolas_client::operation::generate_recommendations::GenerateRecommendationsError;
use amzn_consolas_client::operation::list_customizations::ListCustomizationsError;
Expand Down Expand Up @@ -61,6 +60,9 @@ pub enum Error {

#[error("unsupported action by consolas: {0}")]
UnsupportedConsolas(&'static str),

#[error(transparent)]
ListAvailableProfilesError(#[from] SdkError<ListAvailableProfilesError, HttpResponse>),
}

impl Error {
Expand All @@ -75,6 +77,7 @@ impl Error {
e.as_service_error().is_some_and(|e| e.is_throttling_error())
},
Error::QDeveloperSendMessage(e) => e.as_service_error().is_some_and(|e| e.is_throttling_error()),
Error::ListAvailableProfilesError(e) => e.as_service_error().is_some_and(|e| e.is_throttling_error()),
Error::CodewhispererChatResponseStream(_)
| Error::QDeveloperChatResponseStream(_)
| Error::SmithyBuild(_)
Expand All @@ -94,6 +97,7 @@ impl Error {
Error::CodewhispererGenerateAssistantResponse(e) => e.as_service_error().is_some(),
Error::QDeveloperSendMessage(e) => e.as_service_error().is_some(),
Error::ContextWindowOverflow => true,
Error::ListAvailableProfilesError(e) => e.as_service_error().is_some(),
Error::CodewhispererChatResponseStream(_)
| Error::QDeveloperChatResponseStream(_)
| Error::SmithyBuild(_)
Expand Down
2 changes: 2 additions & 0 deletions crates/fig_api_client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod endpoints;
pub mod error;
pub(crate) mod interceptor;
pub mod model;
pub mod profile;

pub use clients::{
Client,
Expand All @@ -14,3 +15,4 @@ pub use clients::{
pub use customization::Customization;
pub use endpoints::Endpoint;
pub use error::Error;
pub use profile::list_available_profiles;
35 changes: 35 additions & 0 deletions crates/fig_api_client/src/profile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use serde::{
Deserialize,
Serialize,
};

use crate::Client;
use crate::endpoints::Endpoint;

#[derive(Debug, Deserialize, Serialize)]
pub struct Profile {
pub arn: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating an Arn type which has a structure region already parsed out would be neat, instead of silently failing if arn is somehow ever invalid

pub profile_name: String,
}

impl From<amzn_codewhisperer_client::types::Profile> for Profile {
fn from(profile: amzn_codewhisperer_client::types::Profile) -> Self {
Self {
arn: profile.arn,
profile_name: profile.profile_name,
}
}
}

pub async fn list_available_profiles() -> Vec<Profile> {
let mut profiles = vec![];
for endpoint in Endpoint::CODEWHISPERER_ENDPOINTS {
let client = Client::new_codewhisperer_client(&endpoint).await;
match client.list_available_profiles().await {
Ok(mut p) => profiles.append(&mut p),
Err(e) => tracing::warn!("Failed to list profiles from endpoint {:?}: {:?}", endpoint, e),
}
}

profiles
}
Empty file added crates/fig_auth/src/pr
Empty file.
26 changes: 26 additions & 0 deletions crates/fig_auth/src/profile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use serde::{
Deserialize,
Serialize,
};

#[derive(Debug, Deserialize, Serialize)]
pub struct Profile {
pub arn: String,
pub profile_name: String,
}

impl From<amzn_codewhisperer_client::types::Profile> for Profile {
fn from(profile: amzn_codewhisperer_client::types::Profile) -> Self {
Self {
arn: profile.arn,
profile_name: profile.profile_name,
}
}
}

pub fn set_profile(profile: Profile) -> Result<(), Error> {
Ok(fig_settings::state::set_value(
"api.codewhisperer.service",
serde_json::to_string(&profile)?,
)?)
}
4 changes: 4 additions & 0 deletions crates/fig_desktop_api/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,15 @@ where
HistoryQueryRequest,
InsertTextRequest,
InstallRequest,
ListAvailableProfilesRequest,
NotificationRequest,
OnboardingRequest,
OpenInExternalApplicationRequest,
PingRequest,
PositionWindowRequest,
ReadFileRequest,
RunProcessRequest,
SetProfileRequest,
TelemetryPageRequest,
TelemetryTrackRequest,
UpdateApplicationPropertiesRequest,
Expand Down Expand Up @@ -265,6 +267,8 @@ where
CheckForUpdatesRequest(request) => update::check_for_updates(request).await,
GetPlatformInfoRequest(request) => platform::get_platform_info(request, &ctx).await,
UserLogoutRequest(request) => event_handler.user_logout(request!(request)).await,
ListAvailableProfilesRequest(request) => profile::list_available_profiles(request).await,
SetProfileRequest(request) => profile::set_profile(request),
}
},
None => {
Expand Down
1 change: 1 addition & 0 deletions crates/fig_desktop_api/src/requests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod history;
pub mod install;
pub mod other;
pub mod platform;
pub mod profile;
pub mod settings;
pub mod state;
pub mod telemetry;
Expand Down
50 changes: 50 additions & 0 deletions crates/fig_desktop_api/src/requests/profile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use fig_api_client::profile::Profile;
use fig_proto::fig::{
ListAvailableProfilesRequest,
ListAvailableProfilesResponse,
SetProfileRequest,
};

use super::{
RequestResult,
RequestResultImpl,
ServerOriginatedSubMessage,
};

pub fn set_profile(request: SetProfileRequest) -> RequestResult {
let Some(profile) = request.profile else {
return RequestResult::error("Profile was not provided.");
};

let profile = Profile {
arn: profile.arn,
profile_name: profile.profile_name,
};

let profile = match serde_json::to_string(&profile) {
Ok(profile) => profile,
Err(err) => return RequestResult::error(err.to_string()),
};

if let Err(err) = fig_settings::state::set_value("api.codewhisperer.profile", profile) {
return RequestResult::error(err.to_string());
}

RequestResult::success()
}

pub async fn list_available_profiles(_request: ListAvailableProfilesRequest) -> RequestResult {
Ok(
ServerOriginatedSubMessage::ListAvailableProfilesResponse(ListAvailableProfilesResponse {
profiles: fig_api_client::profile::list_available_profiles()
.await
.iter()
.map(|profile| fig_proto::fig::Profile {
arn: profile.arn.clone(),
profile_name: profile.profile_name.clone(),
})
.collect(),
})
.into(),
)
}
2 changes: 1 addition & 1 deletion crates/q_cli/src/cli/chat/skim_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ mod tests {
#[test]
fn test_hardcoded_commands_in_commands_array() {
// Get the set of available commands from prompt.rs
let available_commands: HashSet<String> = get_available_commands().iter().map(|cmd| cmd.clone()).collect();
let available_commands: HashSet<String> = get_available_commands().iter().cloned().collect();

// List of hardcoded commands used in select_command
let hardcoded_commands = vec![
Expand Down
18 changes: 18 additions & 0 deletions proto/fig.proto
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ message ClientOriginatedMessage {
AuthFinishPkceAuthorizationRequest auth_finish_pkce_authorization_request = 150;
AuthCancelPkceAuthorizationRequest auth_cancel_pkce_authorization_request = 151;
GetPlatformInfoRequest get_platform_info_request = 152;
ListAvailableProfilesRequest list_available_profiles_request = 155;
SetProfileRequest set_profile_request = 156;
}

reserved 115;
Expand Down Expand Up @@ -75,6 +77,7 @@ message ServerOriginatedMessage {
AuthFinishPkceAuthorizationResponse auth_finish_pkce_authorization_response = 123;
AuthCancelPkceAuthorizationResponse auth_cancel_pkce_authorization_response = 124;
GetPlatformInfoResponse get_platform_info_response = 125;
ListAvailableProfilesResponse list_available_profiles_response = 155;

Notification notification = 1000;
}
Expand Down Expand Up @@ -378,6 +381,21 @@ message GetPlatformInfoResponse {
optional AppBundleType app_bundle_type = 4;
}

message Profile {
string arn = 1;
string profileName = 2;
}

message ListAvailableProfilesRequest {}

message ListAvailableProfilesResponse {
repeated Profile profiles = 1;
}

message SetProfileRequest {
Profile profile = 1;
}

/// User

// todo
Expand Down
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[toolchain]
channel = "1.84.0"
channel = "1.85.0"
profile = "minimal"
components = ["rustfmt", "clippy"]
targets = [
Expand Down
Loading