mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
codex: rename api-provision slash command
Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
//! Browser-based OAuth flow for provisioning OpenAI project API keys.
|
||||
//! Browser-based OAuth flow for creating OpenAI project API keys.
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -20,7 +20,7 @@ const CALLBACK_PORT: u16 = 0;
|
||||
const CALLBACK_PATH: &str = "/auth/callback";
|
||||
const SCOPE: &str = "openid email profile offline_access";
|
||||
const APP: &str = "api";
|
||||
const USER_AGENT: &str = "Codex-API-Provision/1.0";
|
||||
const USER_AGENT: &str = "Codex-Create-API-Key/1.0";
|
||||
const PROJECT_API_KEY_NAME: &str = "Codex CLI";
|
||||
const PROJECT_POLL_INTERVAL_SECONDS: u64 = 10;
|
||||
const PROJECT_POLL_TIMEOUT_SECONDS: u64 = 60;
|
||||
@@ -28,7 +28,7 @@ const OAUTH_TIMEOUT_SECONDS: u64 = 15 * 60;
|
||||
const HTTP_TIMEOUT_SECONDS: u64 = 30;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct ApiProvisionOptions {
|
||||
struct CreateApiKeyOptions {
|
||||
issuer: String,
|
||||
client_id: String,
|
||||
audience: String,
|
||||
@@ -41,15 +41,15 @@ struct ApiProvisionOptions {
|
||||
project_poll_timeout_seconds: u64,
|
||||
}
|
||||
|
||||
pub struct PendingApiProvisioning {
|
||||
pub struct PendingCreateApiKey {
|
||||
client: Client,
|
||||
options: ApiProvisionOptions,
|
||||
options: CreateApiKeyOptions,
|
||||
redirect_uri: String,
|
||||
code_verifier: String,
|
||||
callback_server: AuthorizationCodeServer,
|
||||
}
|
||||
|
||||
impl PendingApiProvisioning {
|
||||
impl PendingCreateApiKey {
|
||||
pub fn auth_url(&self) -> &str {
|
||||
&self.callback_server.auth_url
|
||||
}
|
||||
@@ -62,13 +62,13 @@ impl PendingApiProvisioning {
|
||||
self.callback_server.open_browser()
|
||||
}
|
||||
|
||||
pub async fn finish(self) -> Result<ProvisionedApiKey, ApiProvisionError> {
|
||||
pub async fn finish(self) -> Result<CreatedApiKey, CreateApiKeyError> {
|
||||
let code = self
|
||||
.callback_server
|
||||
.wait_for_code(Duration::from_secs(OAUTH_TIMEOUT_SECONDS))
|
||||
.await
|
||||
.map_err(|err| ApiProvisionError::message(err.to_string()))?;
|
||||
provision_from_authorization_code(
|
||||
.map_err(|err| CreateApiKeyError::message(err.to_string()))?;
|
||||
create_api_key_from_authorization_code(
|
||||
&self.client,
|
||||
&self.options,
|
||||
&self.redirect_uri,
|
||||
@@ -80,7 +80,7 @@ impl PendingApiProvisioning {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProvisionedApiKey {
|
||||
pub struct CreatedApiKey {
|
||||
pub organization_id: String,
|
||||
pub organization_title: Option<String>,
|
||||
pub default_project_id: String,
|
||||
@@ -88,8 +88,8 @@ pub struct ProvisionedApiKey {
|
||||
pub project_api_key: String,
|
||||
}
|
||||
|
||||
pub fn start_api_provisioning() -> Result<PendingApiProvisioning, ApiProvisionError> {
|
||||
let options = ApiProvisionOptions {
|
||||
pub fn start_create_api_key() -> Result<PendingCreateApiKey, CreateApiKeyError> {
|
||||
let options = CreateApiKeyOptions {
|
||||
issuer: AUTH_ISSUER.to_string(),
|
||||
client_id: PLATFORM_HYDRA_CLIENT_ID.to_string(),
|
||||
audience: PLATFORM_AUDIENCE.to_string(),
|
||||
@@ -111,10 +111,10 @@ pub fn start_api_provisioning() -> Result<PendingApiProvisioning, ApiProvisionEr
|
||||
.map_err(|err| std::io::Error::other(err.to_string()))
|
||||
},
|
||||
)
|
||||
.map_err(|err| ApiProvisionError::message(err.to_string()))?;
|
||||
.map_err(|err| CreateApiKeyError::message(err.to_string()))?;
|
||||
let redirect_uri = callback_server.redirect_uri.clone();
|
||||
|
||||
Ok(PendingApiProvisioning {
|
||||
Ok(PendingCreateApiKey {
|
||||
client,
|
||||
options,
|
||||
redirect_uri,
|
||||
@@ -124,16 +124,16 @@ pub fn start_api_provisioning() -> Result<PendingApiProvisioning, ApiProvisionEr
|
||||
}
|
||||
|
||||
fn build_authorize_url(
|
||||
options: &ApiProvisionOptions,
|
||||
options: &CreateApiKeyOptions,
|
||||
redirect_uri: &str,
|
||||
pkce: &PkceCodes,
|
||||
state: &str,
|
||||
) -> Result<String, ApiProvisionError> {
|
||||
) -> Result<String, CreateApiKeyError> {
|
||||
let mut url = Url::parse(&format!(
|
||||
"{}/oauth/authorize",
|
||||
options.issuer.trim_end_matches('/')
|
||||
))
|
||||
.map_err(|err| ApiProvisionError::message(format!("invalid issuer URL: {err}")))?;
|
||||
.map_err(|err| CreateApiKeyError::message(format!("invalid issuer URL: {err}")))?;
|
||||
url.query_pairs_mut()
|
||||
.append_pair("audience", &options.audience)
|
||||
.append_pair("client_id", &options.client_id)
|
||||
@@ -146,20 +146,20 @@ fn build_authorize_url(
|
||||
Ok(url.to_string())
|
||||
}
|
||||
|
||||
fn build_http_client() -> Result<Client, ApiProvisionError> {
|
||||
fn build_http_client() -> Result<Client, CreateApiKeyError> {
|
||||
build_reqwest_client_with_custom_ca(
|
||||
reqwest::Client::builder().timeout(Duration::from_secs(HTTP_TIMEOUT_SECONDS)),
|
||||
)
|
||||
.map_err(|err| ApiProvisionError::message(format!("failed to build HTTP client: {err}")))
|
||||
.map_err(|err| CreateApiKeyError::message(format!("failed to build HTTP client: {err}")))
|
||||
}
|
||||
|
||||
async fn provision_from_authorization_code(
|
||||
async fn create_api_key_from_authorization_code(
|
||||
client: &Client,
|
||||
options: &ApiProvisionOptions,
|
||||
options: &CreateApiKeyOptions,
|
||||
redirect_uri: &str,
|
||||
code_verifier: &str,
|
||||
code: &str,
|
||||
) -> Result<ProvisionedApiKey, ApiProvisionError> {
|
||||
) -> Result<CreatedApiKey, CreateApiKeyError> {
|
||||
let tokens = exchange_authorization_code_for_tokens(
|
||||
client,
|
||||
&options.issuer,
|
||||
@@ -195,7 +195,7 @@ async fn provision_from_authorization_code(
|
||||
.key
|
||||
.sensitive_id;
|
||||
|
||||
Ok(ProvisionedApiKey {
|
||||
Ok(CreatedApiKey {
|
||||
organization_id: target.organization_id,
|
||||
organization_title: target.organization_title,
|
||||
default_project_id: target.project_id,
|
||||
@@ -211,7 +211,7 @@ async fn exchange_authorization_code_for_tokens(
|
||||
redirect_uri: &str,
|
||||
code_verifier: &str,
|
||||
code: &str,
|
||||
) -> Result<OAuthTokens, ApiProvisionError> {
|
||||
) -> Result<OAuthTokens, CreateApiKeyError> {
|
||||
let url = format!("{}/oauth/token", issuer.trim_end_matches('/'));
|
||||
execute_json(
|
||||
client
|
||||
@@ -241,7 +241,7 @@ async fn onboarding_login(
|
||||
api_base: &str,
|
||||
app: &str,
|
||||
access_token: &str,
|
||||
) -> Result<OnboardingLoginResponse, ApiProvisionError> {
|
||||
) -> Result<OnboardingLoginResponse, CreateApiKeyError> {
|
||||
let url = format!(
|
||||
"{}/dashboard/onboarding/login",
|
||||
api_base.trim_end_matches('/')
|
||||
@@ -263,7 +263,7 @@ async fn list_organizations(
|
||||
client: &Client,
|
||||
api_base: &str,
|
||||
session_key: &str,
|
||||
) -> Result<Vec<Organization>, ApiProvisionError> {
|
||||
) -> Result<Vec<Organization>, CreateApiKeyError> {
|
||||
let url = format!("{}/v1/organizations", api_base.trim_end_matches('/'));
|
||||
let response: DataList<Organization> = execute_json(
|
||||
client
|
||||
@@ -283,7 +283,7 @@ async fn list_projects(
|
||||
api_base: &str,
|
||||
session_key: &str,
|
||||
organization_id: &str,
|
||||
) -> Result<Vec<Project>, ApiProvisionError> {
|
||||
) -> Result<Vec<Project>, CreateApiKeyError> {
|
||||
let url = format!(
|
||||
"{}/dashboard/organizations/{}/projects?detail=basic&limit=100",
|
||||
api_base.trim_end_matches('/'),
|
||||
@@ -309,7 +309,7 @@ async fn wait_for_default_project(
|
||||
session_key: &str,
|
||||
poll_interval_seconds: u64,
|
||||
timeout_seconds: u64,
|
||||
) -> Result<ProvisioningTarget, ApiProvisionError> {
|
||||
) -> Result<ProvisioningTarget, CreateApiKeyError> {
|
||||
let deadline = std::time::Instant::now() + Duration::from_secs(timeout_seconds);
|
||||
loop {
|
||||
let organizations = list_organizations(client, api_base, session_key).await?;
|
||||
@@ -333,7 +333,7 @@ async fn wait_for_default_project(
|
||||
};
|
||||
|
||||
if std::time::Instant::now() >= deadline {
|
||||
return Err(ApiProvisionError::message(format!(
|
||||
return Err(CreateApiKeyError::message(format!(
|
||||
"Timed out waiting for an organization and default project. Last observed state: {last_state}"
|
||||
)));
|
||||
}
|
||||
@@ -367,7 +367,7 @@ async fn create_project_api_key(
|
||||
session_key: &str,
|
||||
target: &ProvisioningTarget,
|
||||
key_name: &str,
|
||||
) -> Result<CreateApiKeyResponse, ApiProvisionError> {
|
||||
) -> Result<CreateProjectApiKeyResponse, CreateApiKeyError> {
|
||||
let url = format!(
|
||||
"{}/dashboard/organizations/{}/projects/{}/api_keys",
|
||||
api_base.trim_end_matches('/'),
|
||||
@@ -394,26 +394,26 @@ async fn execute_json<T>(
|
||||
request: reqwest::RequestBuilder,
|
||||
method: &str,
|
||||
url: &str,
|
||||
) -> Result<T, ApiProvisionError>
|
||||
) -> Result<T, CreateApiKeyError>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
{
|
||||
let response = request
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| ApiProvisionError::message(format!("Network error calling {url}: {err}")))?;
|
||||
.map_err(|err| CreateApiKeyError::message(format!("Network error calling {url}: {err}")))?;
|
||||
let status = response.status();
|
||||
let body = response.bytes().await.map_err(|err| {
|
||||
ApiProvisionError::message(format!("Failed reading response from {url}: {err}"))
|
||||
CreateApiKeyError::message(format!("Failed reading response from {url}: {err}"))
|
||||
})?;
|
||||
if !status.is_success() {
|
||||
return Err(ApiProvisionError::api(
|
||||
return Err(CreateApiKeyError::api(
|
||||
format!("{method} {url} failed with HTTP {status}"),
|
||||
String::from_utf8_lossy(&body).into_owned(),
|
||||
));
|
||||
}
|
||||
serde_json::from_slice(&body)
|
||||
.map_err(|err| ApiProvisionError::message(format!("{url} returned invalid JSON: {err}")))
|
||||
.map_err(|err| CreateApiKeyError::message(format!("{url} returned invalid JSON: {err}")))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -472,21 +472,21 @@ struct ProvisioningTarget {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreateApiKeyResponse {
|
||||
key: CreatedApiKey,
|
||||
struct CreateProjectApiKeyResponse {
|
||||
key: CreatedProjectApiKey,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CreatedApiKey {
|
||||
struct CreatedProjectApiKey {
|
||||
sensitive_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ApiProvisionError {
|
||||
pub struct CreateApiKeyError {
|
||||
message: String,
|
||||
}
|
||||
|
||||
impl ApiProvisionError {
|
||||
impl CreateApiKeyError {
|
||||
fn message(message: String) -> Self {
|
||||
Self { message }
|
||||
}
|
||||
@@ -498,14 +498,14 @@ impl ApiProvisionError {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ApiProvisionError {
|
||||
impl std::fmt::Display for CreateApiKeyError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ApiProvisionError {}
|
||||
impl std::error::Error for CreateApiKeyError {}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "api_provision_tests.rs"]
|
||||
#[path = "create_api_key_tests.rs"]
|
||||
mod tests;
|
||||
+4
-4
@@ -59,7 +59,7 @@ fn find_default_project_returns_initial_project() {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn provision_from_authorization_code_provisions_api_key() {
|
||||
async fn create_api_key_from_authorization_code_creates_api_key() {
|
||||
let server = MockServer::start().await;
|
||||
Mock::given(method("POST"))
|
||||
.and(path("/oauth/token"))
|
||||
@@ -129,7 +129,7 @@ async fn provision_from_authorization_code_provisions_api_key() {
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let options = ApiProvisionOptions {
|
||||
let options = CreateApiKeyOptions {
|
||||
issuer: server.uri(),
|
||||
client_id: "client-123".to_string(),
|
||||
audience: PLATFORM_AUDIENCE.to_string(),
|
||||
@@ -143,7 +143,7 @@ async fn provision_from_authorization_code_provisions_api_key() {
|
||||
};
|
||||
let client = build_http_client().expect("client");
|
||||
|
||||
let output = provision_from_authorization_code(
|
||||
let output = create_api_key_from_authorization_code(
|
||||
&client,
|
||||
&options,
|
||||
"http://localhost:5000/auth/callback",
|
||||
@@ -155,7 +155,7 @@ async fn provision_from_authorization_code_provisions_api_key() {
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
ProvisionedApiKey {
|
||||
CreatedApiKey {
|
||||
organization_id: "org-default".to_string(),
|
||||
organization_title: Some("Default Org".to_string()),
|
||||
default_project_id: "proj-default".to_string(),
|
||||
@@ -1,7 +1,7 @@
|
||||
pub mod auth;
|
||||
pub mod token_data;
|
||||
|
||||
mod api_provision;
|
||||
mod create_api_key;
|
||||
mod device_code_auth;
|
||||
mod oauth_callback_server;
|
||||
mod pkce;
|
||||
@@ -17,10 +17,10 @@ pub use server::LoginServer;
|
||||
pub use server::ServerOptions;
|
||||
pub use server::run_login_server;
|
||||
|
||||
pub use api_provision::ApiProvisionError;
|
||||
pub use api_provision::PendingApiProvisioning;
|
||||
pub use api_provision::ProvisionedApiKey;
|
||||
pub use api_provision::start_api_provisioning;
|
||||
pub use create_api_key::CreateApiKeyError;
|
||||
pub use create_api_key::CreatedApiKey;
|
||||
pub use create_api_key::PendingCreateApiKey;
|
||||
pub use create_api_key::start_create_api_key;
|
||||
pub use auth::AuthConfig;
|
||||
pub use auth::AuthCredentialsStoreMode;
|
||||
pub use auth::AuthDotJson;
|
||||
|
||||
@@ -291,7 +291,7 @@ use crate::status_indicator_widget::STATUS_DETAILS_DEFAULT_MAX_LINES;
|
||||
use crate::status_indicator_widget::StatusDetailsCapitalization;
|
||||
use crate::text_formatting::truncate_text;
|
||||
use crate::tui::FrameRequester;
|
||||
mod api_provision;
|
||||
mod create_api_key;
|
||||
mod dotenv_api_key;
|
||||
mod interrupts;
|
||||
use self::interrupts::InterruptManager;
|
||||
@@ -4735,8 +4735,8 @@ impl ChatWidget {
|
||||
tx.send(AppEvent::DiffResult(text));
|
||||
});
|
||||
}
|
||||
SlashCommand::ApiProvision => {
|
||||
self.start_api_provision();
|
||||
SlashCommand::CreateApiKey => {
|
||||
self.start_create_api_key();
|
||||
}
|
||||
SlashCommand::Copy => {
|
||||
let Some(text) = self.last_copyable_output.as_deref() else {
|
||||
|
||||
+21
-21
@@ -6,10 +6,10 @@ use codex_core::AuthManager;
|
||||
use codex_core::auth::AuthCredentialsStoreMode;
|
||||
use codex_core::auth::login_with_api_key;
|
||||
use codex_core::auth::read_openai_api_key_from_env;
|
||||
use codex_login::CreatedApiKey;
|
||||
use codex_login::OPENAI_API_KEY_ENV_VAR;
|
||||
use codex_login::PendingApiProvisioning;
|
||||
use codex_login::ProvisionedApiKey;
|
||||
use codex_login::start_api_provisioning;
|
||||
use codex_login::PendingCreateApiKey;
|
||||
use codex_login::start_create_api_key as start_create_api_key_flow;
|
||||
use codex_protocol::config_types::ForcedLoginMethod;
|
||||
use ratatui::style::Stylize;
|
||||
use ratatui::text::Line;
|
||||
@@ -23,8 +23,8 @@ use crate::history_cell;
|
||||
use crate::history_cell::PlainHistoryCell;
|
||||
|
||||
impl ChatWidget {
|
||||
pub(crate) fn start_api_provision(&mut self) {
|
||||
match start_api_provision(
|
||||
pub(crate) fn start_create_api_key(&mut self) {
|
||||
match start_create_api_key_command(
|
||||
self.app_event_tx.clone(),
|
||||
self.auth_manager.clone(),
|
||||
self.config.codex_home.clone(),
|
||||
@@ -42,7 +42,7 @@ impl ChatWidget {
|
||||
}
|
||||
}
|
||||
|
||||
fn start_api_provision(
|
||||
fn start_create_api_key_command(
|
||||
app_event_tx: AppEventSender,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
codex_home: PathBuf,
|
||||
@@ -61,8 +61,8 @@ fn start_api_provision(
|
||||
)
|
||||
})?;
|
||||
|
||||
let session = start_api_provisioning()
|
||||
.map_err(|err| format!("Failed to start API provisioning: {err}"))?;
|
||||
let session = start_create_api_key_flow()
|
||||
.map_err(|err| format!("Failed to start API key creation: {err}"))?;
|
||||
let browser_opened = session.open_browser();
|
||||
let start_message = continue_in_browser_message(
|
||||
session.auth_url(),
|
||||
@@ -91,10 +91,10 @@ fn start_api_provision(
|
||||
fn existing_shell_api_key_message() -> PlainHistoryCell {
|
||||
history_cell::new_info_event(
|
||||
format!(
|
||||
"{OPENAI_API_KEY_ENV_VAR} is already set in this Codex session; skipping API provisioning."
|
||||
"{OPENAI_API_KEY_ENV_VAR} is already set in this Codex session; skipping API key creation."
|
||||
),
|
||||
Some(format!(
|
||||
"This Codex session already inherited {OPENAI_API_KEY_ENV_VAR} from its shell environment. Unset it and run /api-provision again if you want Codex to provision and save a different key."
|
||||
"This Codex session already inherited {OPENAI_API_KEY_ENV_VAR} from its shell environment. Unset it and run /create-api-key again if you want Codex to create and save a different key."
|
||||
)),
|
||||
)
|
||||
}
|
||||
@@ -108,7 +108,7 @@ fn continue_in_browser_message(
|
||||
let mut lines = vec![
|
||||
vec![
|
||||
"• ".dim(),
|
||||
"Finish API provisioning via your browser.".into(),
|
||||
"Finish API key creation via your browser.".into(),
|
||||
]
|
||||
.into(),
|
||||
"".into(),
|
||||
@@ -122,7 +122,7 @@ fn continue_in_browser_message(
|
||||
);
|
||||
} else {
|
||||
lines.push(
|
||||
" Codex couldn't auto-open your browser, but the provisioning flow is still waiting."
|
||||
" Codex couldn't auto-open your browser, but the API key creation flow is still waiting."
|
||||
.dark_gray()
|
||||
.into(),
|
||||
);
|
||||
@@ -156,7 +156,7 @@ fn continue_in_browser_message(
|
||||
}
|
||||
|
||||
async fn complete_command(
|
||||
session: PendingApiProvisioning,
|
||||
session: PendingCreateApiKey,
|
||||
dotenv_path: PathBuf,
|
||||
codex_home: PathBuf,
|
||||
forced_login_method: Option<ForcedLoginMethod>,
|
||||
@@ -165,13 +165,13 @@ async fn complete_command(
|
||||
let provisioned = match session.finish().await {
|
||||
Ok(provisioned) => provisioned,
|
||||
Err(err) => {
|
||||
return history_cell::new_error_event(format!("API provisioning failed: {err}"));
|
||||
return history_cell::new_error_event(format!("API key creation failed: {err}"));
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = upsert_dotenv_api_key(&dotenv_path, &provisioned.project_api_key) {
|
||||
return history_cell::new_error_event(format!(
|
||||
"Provisioning completed, but Codex could not update {}: {err}",
|
||||
"API key creation completed, but Codex could not update {}: {err}",
|
||||
dotenv_path.display()
|
||||
));
|
||||
}
|
||||
@@ -210,7 +210,7 @@ fn live_apply_api_key(
|
||||
}
|
||||
|
||||
fn success_cell(
|
||||
provisioned: &ProvisionedApiKey,
|
||||
provisioned: &CreatedApiKey,
|
||||
dotenv_path: &Path,
|
||||
live_apply_outcome: LiveApplyOutcome,
|
||||
) -> PlainHistoryCell {
|
||||
@@ -224,7 +224,7 @@ fn success_cell(
|
||||
.unwrap_or_else(|| provisioned.default_project_id.clone());
|
||||
let hint = match live_apply_outcome {
|
||||
LiveApplyOutcome::Applied => Some(
|
||||
"Updated this session to use the newly provisioned API key without touching auth.json."
|
||||
"Updated this session to use the newly created API key without touching auth.json."
|
||||
.to_string(),
|
||||
),
|
||||
LiveApplyOutcome::Skipped(reason) => Some(reason),
|
||||
@@ -236,7 +236,7 @@ fn success_cell(
|
||||
|
||||
history_cell::new_info_event(
|
||||
format!(
|
||||
"Provisioned an API key for {organization} / {project} and saved {OPENAI_API_KEY_ENV_VAR} to {}.",
|
||||
"Created an API key for {organization} / {project} and saved {OPENAI_API_KEY_ENV_VAR} to {}.",
|
||||
dotenv_path.display()
|
||||
),
|
||||
hint,
|
||||
@@ -258,7 +258,7 @@ mod tests {
|
||||
#[test]
|
||||
fn success_cell_snapshot() {
|
||||
let cell = success_cell(
|
||||
&ProvisionedApiKey {
|
||||
&CreatedApiKey {
|
||||
organization_id: "org-default".to_string(),
|
||||
organization_title: Some("Default Org".to_string()),
|
||||
default_project_id: "proj-default".to_string(),
|
||||
@@ -275,7 +275,7 @@ mod tests {
|
||||
#[test]
|
||||
fn success_cell_snapshot_when_live_apply_is_skipped() {
|
||||
let cell = success_cell(
|
||||
&ProvisionedApiKey {
|
||||
&CreatedApiKey {
|
||||
organization_id: "org-default".to_string(),
|
||||
organization_title: None,
|
||||
default_project_id: "proj-default".to_string(),
|
||||
@@ -310,7 +310,7 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
render_cell(&cell),
|
||||
"• OPENAI_API_KEY is already set in this Codex session; skipping API provisioning. This Codex session already inherited OPENAI_API_KEY from its shell environment. Unset it and run /api-provision again if you want Codex to provision and save a different key."
|
||||
"• OPENAI_API_KEY is already set in this Codex session; skipping API key creation. This Codex session already inherited OPENAI_API_KEY from its shell environment. Unset it and run /create-api-key again if you want Codex to create and save a different key."
|
||||
);
|
||||
}
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
---
|
||||
source: tui/src/chatwidget/api_provision.rs
|
||||
expression: render_cell(&cell)
|
||||
---
|
||||
• Provisioned an API key for Default Org / Default Project and saved OPENAI_API_KEY to /tmp/workspace/.env.local. Updated this session to use the newly provisioned API key without touching auth.json.
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
---
|
||||
source: tui/src/chatwidget/api_provision.rs
|
||||
expression: render_cell(&cell)
|
||||
---
|
||||
• Provisioned an API key for org-default / proj-default and saved OPENAI_API_KEY to /tmp/workspace/.env.local. Saved OPENAI_API_KEY to .env.local, but left this session unchanged because ChatGPT login is required here.
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
---
|
||||
source: tui/src/chatwidget/api_provision.rs
|
||||
source: tui/src/chatwidget/create_api_key.rs
|
||||
expression: render_cell(&cell)
|
||||
---
|
||||
• Finish API provisioning via your browser.
|
||||
• Finish API key creation via your browser.
|
||||
|
||||
Codex couldn't auto-open your browser, but the provisioning flow is still waiting.
|
||||
Codex couldn't auto-open your browser, but the API key creation flow is still waiting.
|
||||
|
||||
Open the following link to authenticate:
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tui/src/chatwidget/create_api_key.rs
|
||||
expression: render_cell(&cell)
|
||||
---
|
||||
• Created an API key for Default Org / Default Project and saved OPENAI_API_KEY to /tmp/workspace/.env.local. Updated this session to use the newly created API key without touching auth.json.
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
---
|
||||
source: tui/src/chatwidget/create_api_key.rs
|
||||
expression: render_cell(&cell)
|
||||
---
|
||||
• Created an API key for org-default / proj-default and saved OPENAI_API_KEY to /tmp/workspace/.env.local. Saved OPENAI_API_KEY to .env.local, but left this session unchanged because ChatGPT login is required here.
|
||||
@@ -34,7 +34,7 @@ pub enum SlashCommand {
|
||||
Agent,
|
||||
// Undo,
|
||||
Diff,
|
||||
ApiProvision,
|
||||
CreateApiKey,
|
||||
Copy,
|
||||
Mention,
|
||||
Status,
|
||||
@@ -83,7 +83,7 @@ impl SlashCommand {
|
||||
// SlashCommand::Undo => "ask Codex to undo a turn",
|
||||
SlashCommand::Quit | SlashCommand::Exit => "exit Codex",
|
||||
SlashCommand::Diff => "show git diff (including untracked files)",
|
||||
SlashCommand::ApiProvision => "provision an API key and save it to .env.local",
|
||||
SlashCommand::CreateApiKey => "create an API key and save it to .env.local",
|
||||
SlashCommand::Copy => "copy the latest Codex output to your clipboard",
|
||||
SlashCommand::Mention => "mention a file",
|
||||
SlashCommand::Skills => "use skills to improve how Codex performs specific tasks",
|
||||
@@ -157,7 +157,7 @@ impl SlashCommand {
|
||||
| SlashCommand::Experimental
|
||||
| SlashCommand::Review
|
||||
| SlashCommand::Plan
|
||||
| SlashCommand::ApiProvision
|
||||
| SlashCommand::CreateApiKey
|
||||
| SlashCommand::Clear
|
||||
| SlashCommand::Logout
|
||||
| SlashCommand::MemoryDrop
|
||||
@@ -225,13 +225,13 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_provision_command_metadata() {
|
||||
fn create_api_key_command_metadata() {
|
||||
assert_eq!(
|
||||
SlashCommand::from_str("api-provision"),
|
||||
Ok(SlashCommand::ApiProvision)
|
||||
SlashCommand::from_str("create-api-key"),
|
||||
Ok(SlashCommand::CreateApiKey)
|
||||
);
|
||||
assert_eq!(SlashCommand::ApiProvision.command(), "api-provision");
|
||||
assert!(!SlashCommand::ApiProvision.supports_inline_args());
|
||||
assert!(!SlashCommand::ApiProvision.available_during_task());
|
||||
assert_eq!(SlashCommand::CreateApiKey.command(), "create-api-key");
|
||||
assert!(!SlashCommand::CreateApiKey.supports_inline_args());
|
||||
assert!(!SlashCommand::CreateApiKey.available_during_task());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user