mirror of
https://github.com/openai/codex.git
synced 2026-10-31 19:28:49 +00:00
Group alternative sign-in methods in a submenu (#58427)
## What changed Move OpenAI API key, Amazon Bedrock, and Google Cloud Vertex AI options under “Sign in another way” during onboarding. Keep ChatGPT and device code sign-in on the main menu, and let `Esc` return from the alternative methods menu. Guard activation keys across menu transitions so holding `Enter` or a numeric shortcut cannot advance through multiple steps or save a prefilled API key without a fresh confirmation. ## Testing Add regression coverage for held activation keys and update onboarding tests and snapshots for submenu navigation, provider availability, and returning with `Esc`. GitOrigin-RevId: b5aa56e48f10b490cc8f04b5d4288d21b76d2c61
This commit is contained in:
@@ -182,6 +182,7 @@ async fn daemon_startup(command: &str) -> Result<()> {
|
||||
.success(),
|
||||
"failed to prepare CLI test executable"
|
||||
);
|
||||
let bedrock_onboarding = matches!(command, "bedrock" | "bedrock-running");
|
||||
fs::write(
|
||||
home.path().join("config.toml"),
|
||||
format!(
|
||||
@@ -195,6 +196,8 @@ async fn daemon_startup(command: &str) -> Result<()> {
|
||||
[projects.{}]\ntrust_level = \"{}\"\n",
|
||||
if command == "discovered" {
|
||||
"tui.status_line=[\"model\",\"thread-id\",\"thread-name\"]\nfeatures.daemon_auto_start=false"
|
||||
} else if bedrock_onboarding {
|
||||
"features.gcp_setup_wizard=false"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
@@ -210,7 +213,6 @@ async fn daemon_startup(command: &str) -> Result<()> {
|
||||
let persisted = command == "mismatch-disable-persisted";
|
||||
let disabling = matches!(command, "mismatch-disable" | "mismatch-disable-persisted");
|
||||
let restart = command == "mismatch-restart" || disabling;
|
||||
let bedrock_onboarding = matches!(command, "bedrock" | "bedrock-running");
|
||||
if !bedrock_onboarding {
|
||||
fs::write(
|
||||
home.path().join("auth.json"),
|
||||
@@ -361,7 +363,8 @@ async fn daemon_startup(command: &str) -> Result<()> {
|
||||
if restart { "Server:Localbackgroundserver" } else { "Model:" }
|
||||
}
|
||||
} else if bedrock_onboarding {
|
||||
"UseAmazonBedrock"
|
||||
// With GCP disabled, only Bedrock makes the sign-in menu advertise another provider.
|
||||
"UseanAPIkeyoranotherprovider"
|
||||
} else {
|
||||
args.extend([command.into(), "--strict-config".into()]);
|
||||
if command == "resume" {
|
||||
|
||||
@@ -43,9 +43,11 @@ use codex_protocol::config_types::ForcedLoginMethod;
|
||||
use std::cell::Cell;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::time::Instant;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::LoginStatus;
|
||||
use crate::activation_key_guard::ActivationKeyGuard;
|
||||
use crate::key_hint::KeyBinding;
|
||||
use crate::key_hint::KeyBindingListExt;
|
||||
use crate::motion::MotionMode;
|
||||
@@ -95,11 +97,18 @@ pub(crate) enum SignInState {
|
||||
pub(crate) enum SignInOption {
|
||||
ChatGpt,
|
||||
DeviceCode,
|
||||
OtherMethods,
|
||||
ApiKey,
|
||||
Bedrock,
|
||||
Gcp,
|
||||
}
|
||||
|
||||
impl SignInOption {
|
||||
fn is_alternative(self) -> bool {
|
||||
!matches!(self, Self::ChatGpt | Self::DeviceCode | Self::OtherMethods)
|
||||
}
|
||||
}
|
||||
|
||||
const API_KEY_DISABLED_MESSAGE: &str = "API key login is disabled.";
|
||||
pub(super) fn onboarding_request_id() -> codex_app_server_protocol::RequestId {
|
||||
codex_app_server_protocol::RequestId::String(Uuid::new_v4().to_string())
|
||||
@@ -181,6 +190,14 @@ impl ContinueWithDeviceCodeState {
|
||||
|
||||
impl KeyboardHandler for AuthModeWidget {
|
||||
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
if self
|
||||
.activation_key_guard
|
||||
.as_mut()
|
||||
.is_some_and(|guard| guard.consume(KeyBinding::from_event(key_event), key_event.kind))
|
||||
|| key_event.kind == KeyEventKind::Release
|
||||
{
|
||||
return;
|
||||
}
|
||||
if self.handle_gcp_key_event(&key_event) || self.handle_bedrock_key_event(&key_event) {
|
||||
return;
|
||||
}
|
||||
@@ -196,31 +213,26 @@ impl KeyboardHandler for AuthModeWidget {
|
||||
self.move_highlight(/*delta*/ 1);
|
||||
return;
|
||||
}
|
||||
if key_event.kind != KeyEventKind::Press {
|
||||
return;
|
||||
}
|
||||
if keys::SELECT_FIRST.is_pressed(key_event) {
|
||||
self.select_option_by_index(/*index*/ 0);
|
||||
self.select_option_by_index(/*index*/ 0, key_event);
|
||||
return;
|
||||
}
|
||||
if keys::SELECT_SECOND.is_pressed(key_event) {
|
||||
self.select_option_by_index(/*index*/ 1);
|
||||
self.select_option_by_index(/*index*/ 1, key_event);
|
||||
return;
|
||||
}
|
||||
if keys::SELECT_THIRD.is_pressed(key_event) {
|
||||
self.select_option_by_index(/*index*/ 2);
|
||||
return;
|
||||
}
|
||||
if keys::SELECT_FOURTH.is_pressed(key_event) {
|
||||
self.select_option_by_index(/*index*/ 3);
|
||||
return;
|
||||
}
|
||||
if keys::SELECT_FIFTH.is_pressed(key_event) {
|
||||
self.select_option_by_index(/*index*/ 4);
|
||||
self.select_option_by_index(/*index*/ 2, key_event);
|
||||
return;
|
||||
}
|
||||
if keys::CONFIRM.is_pressed(key_event) {
|
||||
let sign_in_state = { (*self.sign_in_state.read().unwrap()).clone() };
|
||||
match sign_in_state {
|
||||
SignInState::PickMode => {
|
||||
self.handle_sign_in_option(self.highlighted_mode);
|
||||
self.activate_highlighted_mode(key_event);
|
||||
}
|
||||
SignInState::ChatGptSuccessMessage => {
|
||||
*self.sign_in_state.write().unwrap() = SignInState::ChatGptSuccess;
|
||||
@@ -230,8 +242,14 @@ impl KeyboardHandler for AuthModeWidget {
|
||||
return;
|
||||
}
|
||||
if keys::CANCEL.is_pressed(key_event) {
|
||||
tracing::info!("Cancel onboarding auth step");
|
||||
self.cancel_active_attempt();
|
||||
if matches!(*self.sign_in_state.read().unwrap(), SignInState::PickMode)
|
||||
&& self.highlighted_mode.is_alternative()
|
||||
{
|
||||
self.highlighted_mode = SignInOption::OtherMethods;
|
||||
self.set_error(/*message*/ None);
|
||||
} else {
|
||||
self.cancel_active_attempt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +279,7 @@ impl KeyboardHandler for AuthModeWidget {
|
||||
pub(crate) struct AuthModeWidget {
|
||||
pub request_frame: FrameRequester,
|
||||
pub highlighted_mode: SignInOption,
|
||||
pub(super) activation_key_guard: Option<ActivationKeyGuard>,
|
||||
pub error: Arc<RwLock<Option<String>>>,
|
||||
pub sign_in_state: Arc<RwLock<SignInState>>,
|
||||
pub login_status: LoginStatus,
|
||||
@@ -361,42 +380,34 @@ impl AuthModeWidget {
|
||||
}
|
||||
|
||||
fn displayed_sign_in_options(&self) -> Vec<SignInOption> {
|
||||
if self.highlighted_mode.is_alternative() {
|
||||
let mut options = vec![SignInOption::ApiKey];
|
||||
if self.bedrock_setup_enabled {
|
||||
options.push(SignInOption::Bedrock);
|
||||
}
|
||||
if self.gcp_setup_enabled {
|
||||
options.push(SignInOption::Gcp);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
let mut options = vec![SignInOption::ChatGpt];
|
||||
if self.is_chatgpt_login_allowed() {
|
||||
options.push(SignInOption::DeviceCode);
|
||||
}
|
||||
if self.is_api_login_allowed() {
|
||||
options.push(SignInOption::ApiKey);
|
||||
if self.bedrock_setup_enabled {
|
||||
options.push(SignInOption::Bedrock);
|
||||
}
|
||||
if self.gcp_setup_enabled {
|
||||
options.push(SignInOption::Gcp);
|
||||
}
|
||||
}
|
||||
options
|
||||
}
|
||||
|
||||
fn selectable_sign_in_options(&self) -> Vec<SignInOption> {
|
||||
let mut options = Vec::new();
|
||||
if self.is_chatgpt_login_allowed() {
|
||||
options.push(SignInOption::ChatGpt);
|
||||
options.push(SignInOption::DeviceCode);
|
||||
}
|
||||
if self.is_api_login_allowed() {
|
||||
options.push(SignInOption::ApiKey);
|
||||
if self.bedrock_setup_enabled {
|
||||
options.push(SignInOption::Bedrock);
|
||||
}
|
||||
if self.gcp_setup_enabled {
|
||||
options.push(SignInOption::Gcp);
|
||||
}
|
||||
options.push(SignInOption::OtherMethods);
|
||||
}
|
||||
options
|
||||
}
|
||||
|
||||
fn move_highlight(&mut self, delta: isize) {
|
||||
let options = self.selectable_sign_in_options();
|
||||
if !matches!(*self.sign_in_state.read().unwrap(), SignInState::PickMode) {
|
||||
return;
|
||||
}
|
||||
let mut options = self.displayed_sign_in_options();
|
||||
if !self.is_chatgpt_login_allowed() {
|
||||
options.retain(|option| *option != SignInOption::ChatGpt);
|
||||
}
|
||||
if options.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -410,18 +421,38 @@ impl AuthModeWidget {
|
||||
self.highlighted_mode = options[next_index];
|
||||
}
|
||||
|
||||
fn select_option_by_index(&mut self, index: usize) {
|
||||
fn select_option_by_index(&mut self, index: usize, key_event: KeyEvent) {
|
||||
if !matches!(*self.sign_in_state.read().unwrap(), SignInState::PickMode) {
|
||||
return;
|
||||
}
|
||||
let options = self.displayed_sign_in_options();
|
||||
if let Some(option) = options.get(index).copied() {
|
||||
self.handle_sign_in_option(option);
|
||||
if let Some(option) = options.get(index).copied()
|
||||
&& (option != SignInOption::ChatGpt || self.is_chatgpt_login_allowed())
|
||||
{
|
||||
self.highlighted_mode = option;
|
||||
self.activate_highlighted_mode(key_event);
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_highlighted_mode(&mut self, key_event: KeyEvent) {
|
||||
let option = self.highlighted_mode;
|
||||
if option == SignInOption::OtherMethods || option.is_alternative() {
|
||||
// Enter and printable shortcuts can repeat as plain presses without a release.
|
||||
self.activation_key_guard = Some(ActivationKeyGuard::new_at(
|
||||
KeyBinding::from_event(key_event),
|
||||
Instant::now(),
|
||||
/*key_releases_reported*/ false,
|
||||
));
|
||||
}
|
||||
self.handle_sign_in_option(option);
|
||||
}
|
||||
|
||||
fn handle_sign_in_option(&mut self, option: SignInOption) {
|
||||
match option {
|
||||
SignInOption::OtherMethods => {
|
||||
self.highlighted_mode = SignInOption::ApiKey;
|
||||
self.set_error(/*message*/ None);
|
||||
}
|
||||
SignInOption::ChatGpt => {
|
||||
if self.is_chatgpt_login_allowed() {
|
||||
self.start_chatgpt_login();
|
||||
@@ -466,7 +497,9 @@ impl AuthModeWidget {
|
||||
}
|
||||
|
||||
fn render_pick_mode(&self, area: Rect, buf: &mut Buffer) {
|
||||
let mut lines: Vec<Line> = if self.bedrock_setup_enabled || self.gcp_setup_enabled {
|
||||
let mut lines: Vec<Line> = if self.highlighted_mode.is_alternative() {
|
||||
vec![" Sign in another way".into(), "".into()]
|
||||
} else if self.bedrock_setup_enabled || self.gcp_setup_enabled {
|
||||
vec![" Choose how you want to use Codex.".into(), "".into()]
|
||||
} else {
|
||||
vec![
|
||||
@@ -523,6 +556,18 @@ impl AuthModeWidget {
|
||||
|
||||
for (idx, option) in self.displayed_sign_in_options().into_iter().enumerate() {
|
||||
match option {
|
||||
SignInOption::OtherMethods => {
|
||||
lines.extend(create_mode_item(
|
||||
idx,
|
||||
option,
|
||||
"Sign in another way",
|
||||
if self.bedrock_setup_enabled || self.gcp_setup_enabled {
|
||||
"Use an API key or another provider"
|
||||
} else {
|
||||
"Use an OpenAI API key"
|
||||
},
|
||||
));
|
||||
}
|
||||
SignInOption::ChatGpt => {
|
||||
lines.extend(create_mode_item(
|
||||
idx,
|
||||
@@ -581,6 +626,13 @@ impl AuthModeWidget {
|
||||
self.confirm_binding().into(),
|
||||
" to continue".dim(),
|
||||
]));
|
||||
if self.highlighted_mode.is_alternative() {
|
||||
lines.push(Line::from(vec![
|
||||
" Press ".dim(),
|
||||
self.cancel_binding().into(),
|
||||
" to go back".dim(),
|
||||
]));
|
||||
}
|
||||
if let Some(err) = self.error_message() {
|
||||
lines.push("".into());
|
||||
lines.push(err.red().into());
|
||||
@@ -813,7 +865,9 @@ impl AuthModeWidget {
|
||||
*guard = SignInState::PickMode;
|
||||
self.set_error(/*message*/ None);
|
||||
should_request_frame = true;
|
||||
} else if keys::CONFIRM.is_pressed(*key_event) {
|
||||
} else if key_event.kind == KeyEventKind::Press
|
||||
&& keys::CONFIRM.is_pressed(*key_event)
|
||||
{
|
||||
let trimmed = state.value.trim().to_string();
|
||||
if trimmed.is_empty() {
|
||||
self.set_error(Some("API key cannot be empty".to_string()));
|
||||
@@ -1321,6 +1375,7 @@ mod tests {
|
||||
let widget = AuthModeWidget {
|
||||
request_frame: FrameRequester::test_dummy(),
|
||||
highlighted_mode: SignInOption::ChatGpt,
|
||||
activation_key_guard: None,
|
||||
error: Arc::new(RwLock::new(None)),
|
||||
sign_in_state: Arc::new(RwLock::new(SignInState::PickMode)),
|
||||
login_status: LoginStatus::NotAuthenticated,
|
||||
@@ -1383,7 +1438,7 @@ mod tests {
|
||||
vec![
|
||||
SignInOption::ChatGpt,
|
||||
SignInOption::DeviceCode,
|
||||
SignInOption::ApiKey,
|
||||
SignInOption::OtherMethods,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1404,15 +1459,13 @@ mod tests {
|
||||
&widget.app_server_target,
|
||||
Feature::BedrockSetupWizard,
|
||||
);
|
||||
widget.handle_key_event(KeyCode::Char('3').into());
|
||||
assert_eq!(
|
||||
widget.displayed_sign_in_options(),
|
||||
vec![
|
||||
SignInOption::ChatGpt,
|
||||
SignInOption::DeviceCode,
|
||||
SignInOption::ApiKey,
|
||||
SignInOption::Bedrock,
|
||||
]
|
||||
vec![SignInOption::ApiKey, SignInOption::Bedrock]
|
||||
);
|
||||
widget.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
|
||||
widget.move_highlight(/*delta*/ 1);
|
||||
|
||||
let area = Rect::new(0, 0, 76, 19);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
@@ -1438,11 +1491,8 @@ mod tests {
|
||||
2. Sign in with Device Code
|
||||
Sign in from another device with a one-time code
|
||||
|
||||
3. Use an OpenAI API key
|
||||
Pay for what you use
|
||||
|
||||
4. Use Amazon Bedrock
|
||||
Connect using your AWS credentials
|
||||
3. Sign in another way
|
||||
Use an API key or another provider
|
||||
|
||||
Press enter to continue
|
||||
"###);
|
||||
@@ -1753,6 +1803,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod activation_key_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "gcp_tests.rs"]
|
||||
mod gcp_tests;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#![cfg(test)]
|
||||
|
||||
//! Regression coverage for a key held across the sign-in menus.
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[tokio::test]
|
||||
async fn held_enter_requires_a_fresh_confirmation_to_save_a_prefilled_api_key() {
|
||||
let (mut widget, _home) = tests::widget_forced_chatgpt().await;
|
||||
widget.auth_config.forced_login_method = None;
|
||||
let enter = |kind| KeyEvent::new_with_kind(KeyCode::Enter, KeyModifiers::NONE, kind);
|
||||
|
||||
widget.handle_key_event(KeyCode::Up.into());
|
||||
widget.handle_key_event(enter(KeyEventKind::Press));
|
||||
widget.handle_key_event(enter(KeyEventKind::Repeat));
|
||||
widget.handle_key_event(enter(KeyEventKind::Press));
|
||||
widget.handle_key_event(KeyCode::Down.into());
|
||||
widget.handle_key_event(enter(KeyEventKind::Repeat));
|
||||
assert_eq!(widget.highlighted_mode, SignInOption::ApiKey);
|
||||
assert!(matches!(
|
||||
*widget.sign_in_state.read().unwrap(),
|
||||
SignInState::PickMode
|
||||
));
|
||||
|
||||
widget.handle_key_event(enter(KeyEventKind::Release));
|
||||
widget.handle_key_event(enter(KeyEventKind::Press));
|
||||
{
|
||||
let mut state = widget.sign_in_state.write().unwrap();
|
||||
let SignInState::ApiKeyEntry(input) = &mut *state else {
|
||||
panic!("expected API key entry");
|
||||
};
|
||||
input.value = "test-api-key".into();
|
||||
input.prepopulated_from_env = true;
|
||||
}
|
||||
let (request_frame, mut frames) = FrameRequester::test_channel();
|
||||
widget.request_frame = request_frame;
|
||||
widget.handle_key_event(enter(KeyEventKind::Press));
|
||||
widget.handle_key_event(enter(KeyEventKind::Repeat));
|
||||
widget.handle_key_event(KeyCode::Left.into());
|
||||
widget.handle_key_event(enter(KeyEventKind::Repeat));
|
||||
assert_eq!(
|
||||
frames.try_recv(),
|
||||
Err(tokio::sync::mpsc::error::TryRecvError::Empty)
|
||||
);
|
||||
|
||||
widget.handle_key_event(enter(KeyEventKind::Release));
|
||||
widget.handle_key_event(enter(KeyEventKind::Press));
|
||||
frames.try_recv().expect("fresh Enter should start saving");
|
||||
frames.recv().await.expect("API key save should finish");
|
||||
assert!(matches!(
|
||||
*widget.sign_in_state.read().unwrap(),
|
||||
SignInState::ApiKeyConfigured
|
||||
));
|
||||
}
|
||||
@@ -39,30 +39,29 @@ async fn gcp_menu_gating_and_setup_flow() {
|
||||
Rect::new(0, 0, 76, 8),
|
||||
);
|
||||
|
||||
let press = |key| KeyEvent::new(key, KeyModifiers::NONE);
|
||||
widget.gcp_setup_enabled = true;
|
||||
widget.handle_key_event(press(KeyCode::Char('3')));
|
||||
assert_eq!(
|
||||
widget.displayed_sign_in_options(),
|
||||
vec![
|
||||
SignInOption::ChatGpt,
|
||||
SignInOption::DeviceCode,
|
||||
SignInOption::ApiKey,
|
||||
SignInOption::Gcp
|
||||
]
|
||||
vec![SignInOption::ApiKey, SignInOption::Gcp]
|
||||
);
|
||||
widget.bedrock_setup_enabled = true;
|
||||
assert_eq!(
|
||||
widget.selectable_sign_in_options(),
|
||||
vec![
|
||||
SignInOption::ChatGpt,
|
||||
SignInOption::DeviceCode,
|
||||
SignInOption::ApiKey,
|
||||
SignInOption::Bedrock,
|
||||
SignInOption::Gcp
|
||||
]
|
||||
);
|
||||
widget.handle_key_event(KeyEvent::new_with_kind(
|
||||
KeyCode::Char('3'),
|
||||
KeyModifiers::NONE,
|
||||
KeyEventKind::Repeat,
|
||||
));
|
||||
assert_eq!(widget.highlighted_mode, SignInOption::ApiKey);
|
||||
assert!(matches!(
|
||||
*widget.sign_in_state.read().unwrap(),
|
||||
SignInState::PickMode
|
||||
));
|
||||
snapshot(&widget, "gcp_sign_in_menu", Rect::new(0, 0, 76, 21));
|
||||
widget.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
|
||||
snapshot(&widget, "gcp_sign_in_menu_short", Rect::new(0, 0, 76, 14));
|
||||
widget.handle_key_event(press(KeyCode::Up));
|
||||
snapshot(&widget, "gcp_sign_in_menu_short", Rect::new(0, 0, 76, 8));
|
||||
widget.handle_key_event(press(KeyCode::Esc));
|
||||
assert_eq!(widget.highlighted_mode, SignInOption::OtherMethods);
|
||||
let area = Rect::new(0, 0, 76, 12);
|
||||
|
||||
widget.auth_config.forced_login_method = Some(ForcedLoginMethod::Chatgpt);
|
||||
@@ -79,16 +78,28 @@ async fn gcp_menu_gating_and_setup_flow() {
|
||||
|
||||
*widget.sign_in_state.write().unwrap() =
|
||||
SignInState::ChatGptDeviceCode(ContinueWithDeviceCodeState::pending("login".into()));
|
||||
widget.handle_key_event(KeyEvent::new(KeyCode::Char('5'), KeyModifiers::NONE));
|
||||
widget.handle_key_event(press(KeyCode::Char('3')));
|
||||
assert!(matches!(
|
||||
&*widget.sign_in_state.read().unwrap(),
|
||||
SignInState::ChatGptDeviceCode(_)
|
||||
));
|
||||
widget.cancel_active_attempt();
|
||||
|
||||
widget.handle_key_event(KeyEvent::new(KeyCode::Char('5'), KeyModifiers::NONE));
|
||||
widget.handle_key_event(press(KeyCode::Char('3')));
|
||||
widget.handle_key_event(press(KeyCode::Char('1')));
|
||||
widget.handle_key_event(press(KeyCode::Esc));
|
||||
assert_eq!(widget.highlighted_mode, SignInOption::ApiKey);
|
||||
widget.handle_key_event(press(KeyCode::Char('3')));
|
||||
widget.handle_key_event(press(KeyCode::Esc));
|
||||
assert_eq!(widget.highlighted_mode, SignInOption::Gcp);
|
||||
widget.handle_key_event(press(KeyCode::Enter));
|
||||
assert!(widget.is_text_entry_active());
|
||||
assert!(widget.should_suppress_printable_quit());
|
||||
widget.handle_key_event(KeyEvent::new_with_kind(
|
||||
KeyCode::Enter,
|
||||
KeyModifiers::NONE,
|
||||
KeyEventKind::Release,
|
||||
));
|
||||
widget.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
assert_eq!(
|
||||
widget.error_message().as_deref(),
|
||||
|
||||
@@ -23,8 +23,6 @@ pub(crate) const SELECT_SECOND: [KeyBinding; 2] = [
|
||||
key_hint::plain(KeyCode::Char('n')),
|
||||
];
|
||||
pub(crate) const SELECT_THIRD: [KeyBinding; 1] = [key_hint::plain(KeyCode::Char('3'))];
|
||||
pub(crate) const SELECT_FOURTH: [KeyBinding; 1] = [key_hint::plain(KeyCode::Char('4'))];
|
||||
pub(crate) const SELECT_FIFTH: [KeyBinding; 1] = [key_hint::plain(KeyCode::Char('5'))];
|
||||
pub(crate) const CONFIRM: [KeyBinding; 1] = [key_hint::plain(KeyCode::Enter)];
|
||||
pub(crate) const CANCEL: [KeyBinding; 1] = [key_hint::plain(KeyCode::Esc)];
|
||||
pub(crate) const COPY_LINK: [KeyBinding; 1] = [key_hint::plain(KeyCode::Char('c'))];
|
||||
|
||||
@@ -173,12 +173,13 @@ impl OnboardingScreen {
|
||||
if auth_config.is_login_method_allowed(ForcedLoginMethod::Chatgpt) {
|
||||
SignInOption::ChatGpt
|
||||
} else {
|
||||
SignInOption::ApiKey
|
||||
SignInOption::OtherMethods
|
||||
};
|
||||
if let Some(app_server_request_handle) = app_server_request_handle {
|
||||
steps.push(Step::Auth(AuthModeWidget {
|
||||
request_frame: tui.frame_requester(),
|
||||
highlighted_mode,
|
||||
activation_key_guard: None,
|
||||
error: Arc::new(RwLock::new(None)),
|
||||
sign_in_state: Arc::new(RwLock::new(SignInState::PickMode)),
|
||||
login_status,
|
||||
@@ -352,7 +353,10 @@ impl KeyboardHandler for OnboardingScreen {
|
||||
/// starts, while cloud provider fields suppress them from the first character.
|
||||
/// Control/alt quit chords still work as emergency exits.
|
||||
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
if !matches!(key_event.kind, KeyEventKind::Press | KeyEventKind::Repeat) {
|
||||
if key_event.kind == KeyEventKind::Release {
|
||||
if let Some(widget) = self.auth_widget_mut() {
|
||||
widget.handle_key_event(key_event);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let text_entry_context = self.text_entry_context();
|
||||
|
||||
+5
-10
@@ -2,21 +2,16 @@
|
||||
source: tui/src/onboarding/gcp_tests.rs
|
||||
expression: terminal.backend().to_string()
|
||||
---
|
||||
Choose how you want to use Codex.
|
||||
Sign in another way
|
||||
|
||||
> 1. Sign in with ChatGPT
|
||||
Usage included with Plus, Pro, Business, and Enterprise plans
|
||||
|
||||
2. Sign in with Device Code
|
||||
Sign in from another device with a one-time code
|
||||
|
||||
3. Use an OpenAI API key
|
||||
> 1. Use an OpenAI API key
|
||||
Pay for what you use
|
||||
|
||||
4. Use Amazon Bedrock
|
||||
2. Use Amazon Bedrock
|
||||
Connect using your AWS credentials
|
||||
|
||||
5. Use Google Cloud Vertex AI
|
||||
3. Use Google Cloud Vertex AI
|
||||
Connect using your Google Cloud credentials
|
||||
|
||||
Press enter to continue
|
||||
Press esc to go back
|
||||
|
||||
+5
-6
@@ -2,11 +2,10 @@
|
||||
source: tui/src/onboarding/gcp_tests.rs
|
||||
expression: terminal.backend().to_string()
|
||||
---
|
||||
Choose how you want to use Codex.
|
||||
Sign in another way
|
||||
|
||||
1. Sign in with ChatGPT
|
||||
2. Sign in with Device Code
|
||||
3. Use an OpenAI API key
|
||||
4. Use Amazon Bedrock
|
||||
> 5. Use Google Cloud Vertex AI
|
||||
1. Use an OpenAI API key
|
||||
2. Use Amazon Bedrock
|
||||
> 3. Use Google Cloud Vertex AI
|
||||
Press enter to continue
|
||||
Press esc to go back
|
||||
|
||||
+1
-1
@@ -7,5 +7,5 @@ expression: terminal.backend().to_string()
|
||||
|
||||
> 1. Sign in with ChatGPT
|
||||
2. Sign in with Device Code
|
||||
3. Use an OpenAI API key
|
||||
3. Sign in another way
|
||||
Press enter to continue
|
||||
|
||||
Reference in New Issue
Block a user