feat(settings): add ChatGPT OAuth device code login for Codex CLI

Add OAuth device code flow for Codex CLI official subscription auth,
allowing users to log in with their ChatGPT account directly from the
agent settings page without using the terminal.

- Backend: two new endpoints (codex_request_device_code, codex_poll_device_code)
  that handle the OpenAI OAuth device code flow and return tokens to frontend
- Frontend: login UI with verification URL, copyable user code, polling status,
  15-minute timeout, and auto-save via existing persistEnv/persistConfig path
- Auth.json written in Codex CLI compatible format (nested tokens, account_id,
  last_refresh) so codex-acp can use OAuth tokens directly
- Show logged-in status and re-login option when tokens are present
- Remove auth.json textarea from Codex settings UI
- i18n: all 10 languages updated with new login-related keys
This commit is contained in:
xintaofei
2026-04-16 00:29:01 +08:00
parent 7524613439
commit d163e42457
16 changed files with 689 additions and 61 deletions

View File

@@ -591,3 +591,30 @@ pub async fn opencode_uninstall_plugin(
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
Ok(Json(result))
}
pub async fn codex_request_device_code(
) -> Result<Json<acp_commands::CodexDeviceCodeResponse>, AppCommandError> {
let result = acp_commands::codex_request_device_code_core()
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
Ok(Json(result))
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CodexPollDeviceCodeParams {
pub device_auth_id: String,
pub user_code: String,
}
pub async fn codex_poll_device_code(
Json(params): Json<CodexPollDeviceCodeParams>,
) -> Result<Json<acp_commands::CodexDeviceCodePollResult>, AppCommandError> {
let result = acp_commands::codex_poll_device_code_core(
params.device_auth_id,
params.user_code,
)
.await
.map_err(|e| AppCommandError::task_execution_failed(e.to_string()))?;
Ok(Json(result))
}

View File

@@ -459,6 +459,14 @@ pub fn build_router(state: Arc<AppState>, token: String, static_dir: std::path::
"/opencode_uninstall_plugin",
post(handlers::acp::opencode_uninstall_plugin),
)
.route(
"/codex_request_device_code",
post(handlers::acp::codex_request_device_code),
)
.route(
"/codex_poll_device_code",
post(handlers::acp::codex_poll_device_code),
)
// ─── Experts ───
.route("/experts_list", post(handlers::experts::experts_list))
.route(