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:
@@ -17,6 +17,7 @@ import {
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Copy,
|
||||
Download,
|
||||
Eye,
|
||||
EyeOff,
|
||||
@@ -75,6 +76,8 @@ import {
|
||||
acpUninstallAgent,
|
||||
acpUpdateAgentConfig,
|
||||
acpUpdateAgentEnv,
|
||||
codexPollDeviceCode,
|
||||
codexRequestDeviceCode,
|
||||
listModelProviders,
|
||||
} from "@/lib/api"
|
||||
import type {
|
||||
@@ -1550,6 +1553,18 @@ function inferCodexAuthMode(authJsonText: string): CodexAuthMode {
|
||||
return "api_key"
|
||||
}
|
||||
|
||||
function hasCodexChatgptTokens(authJsonText: string): boolean {
|
||||
const { authObject } = parseCodexAuthJsonObject(authJsonText)
|
||||
if (!authObject) return false
|
||||
const tokens = authObject.tokens as Record<string, unknown> | undefined
|
||||
if (tokens && typeof tokens === "object") {
|
||||
return (
|
||||
typeof tokens.access_token === "string" && tokens.access_token.length > 0
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function extractCodexImportantValues(
|
||||
authJsonText: string,
|
||||
configTomlText: string
|
||||
@@ -2686,6 +2701,17 @@ export function AcpAgentSettings() {
|
||||
const installStream = useAgentInstallStream()
|
||||
const [streamAgentType, setStreamAgentType] = useState<AgentType | null>(null)
|
||||
const installLogEndRef = useRef<HTMLDivElement | null>(null)
|
||||
const [codexDeviceCode, setCodexDeviceCode] = useState<{
|
||||
userCode: string
|
||||
verificationUrl: string
|
||||
deviceAuthId: string
|
||||
interval: number
|
||||
} | null>(null)
|
||||
const [codexLoginStatus, setCodexLoginStatus] = useState<
|
||||
"idle" | "requesting" | "polling" | "success" | "error"
|
||||
>("idle")
|
||||
const [codexLoginError, setCodexLoginError] = useState<string | null>(null)
|
||||
const codexPollCancelledRef = useRef(false)
|
||||
|
||||
const sortedAgents = useMemo(
|
||||
() =>
|
||||
@@ -3427,13 +3453,8 @@ export function AcpAgentSettings() {
|
||||
|
||||
const selectedMissingModelProvider =
|
||||
selectedNeedsModelProvider && selectedDraft?.modelProviderId == null
|
||||
const selectedCodexAuthJsonText = selectedDraft?.codexAuthJsonText ?? ""
|
||||
const selectedConfigText = selectedDraft?.configText ?? ""
|
||||
const selectedOpenCodeAuthJsonText = selectedDraft?.openCodeAuthJsonText ?? ""
|
||||
const selectedCodexAuthError = useMemo(() => {
|
||||
if (selectedAgentKind !== "codex" || !locale) return null
|
||||
return parseCodexAuthJsonText(selectedCodexAuthJsonText)
|
||||
}, [locale, selectedAgentKind, selectedCodexAuthJsonText])
|
||||
const selectedCodexReasoningEffortOption =
|
||||
selectedAgent?.agent_type === "codex" && selectedDraft
|
||||
? (CODEX_REASONING_EFFORT_OPTIONS.find(
|
||||
@@ -4594,31 +4615,6 @@ export function AcpAgentSettings() {
|
||||
[handleOpenCodeConfigPatch]
|
||||
)
|
||||
|
||||
const handleCodexAuthJsonTextChange = useCallback(
|
||||
(nextText: string) => {
|
||||
if (!selectedAgent || selectedAgent.agent_type !== "codex") return
|
||||
const important = extractCodexImportantValues(
|
||||
nextText,
|
||||
selectedDraft?.codexConfigTomlText ?? ""
|
||||
)
|
||||
updateSelectedDraft((current) => ({
|
||||
...current,
|
||||
codexAuthMode: inferCodexAuthMode(nextText),
|
||||
codexAuthJsonText: nextText,
|
||||
apiBaseUrl: important.apiBaseUrl,
|
||||
apiKey: important.apiKey ?? current.apiKey,
|
||||
model: important.model,
|
||||
codexModelProvider: important.modelProvider,
|
||||
codexProviderOptions: important.providerOptions,
|
||||
codexReasoningEffort: important.reasoningEffort,
|
||||
codexSupportsWebsockets: important.supportsWebsockets,
|
||||
codexSkills: important.skills,
|
||||
codexServiceTierFast: important.serviceTierFast,
|
||||
}))
|
||||
},
|
||||
[selectedAgent, selectedDraft, updateSelectedDraft]
|
||||
)
|
||||
|
||||
const handleCodexConfigTomlTextChange = useCallback(
|
||||
(nextText: string) => {
|
||||
if (!selectedAgent || selectedAgent.agent_type !== "codex") return
|
||||
@@ -4901,6 +4897,138 @@ export function AcpAgentSettings() {
|
||||
[selectedAgent, selectedDraft, updateSelectedDraft]
|
||||
)
|
||||
|
||||
const handleCodexDeviceLogin = useCallback(async () => {
|
||||
setCodexLoginStatus("requesting")
|
||||
setCodexLoginError(null)
|
||||
setCodexDeviceCode(null)
|
||||
codexPollCancelledRef.current = false
|
||||
try {
|
||||
const resp = await codexRequestDeviceCode()
|
||||
setCodexDeviceCode(resp)
|
||||
setCodexLoginStatus("polling")
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
setCodexLoginError(msg)
|
||||
setCodexLoginStatus("error")
|
||||
}
|
||||
}, [])
|
||||
|
||||
const cancelCodexDeviceLogin = useCallback(() => {
|
||||
codexPollCancelledRef.current = true
|
||||
setCodexLoginStatus("idle")
|
||||
setCodexDeviceCode(null)
|
||||
setCodexLoginError(null)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (codexLoginStatus !== "polling" || !codexDeviceCode) return
|
||||
codexPollCancelledRef.current = false
|
||||
const pollInterval = (codexDeviceCode.interval || 5) * 1000
|
||||
const deadline = Date.now() + 15 * 60 * 1000
|
||||
let timer: ReturnType<typeof setTimeout> | null = null
|
||||
let active = true
|
||||
|
||||
const poll = async () => {
|
||||
if (!active || codexPollCancelledRef.current) return
|
||||
if (Date.now() > deadline) {
|
||||
setCodexLoginError(t("codex.loginTimeout"))
|
||||
setCodexLoginStatus("error")
|
||||
setCodexDeviceCode(null)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const result = await codexPollDeviceCode({
|
||||
deviceAuthId: codexDeviceCode.deviceAuthId,
|
||||
userCode: codexDeviceCode.userCode,
|
||||
})
|
||||
if (!active || codexPollCancelledRef.current) return
|
||||
if (result.status === "success") {
|
||||
setCodexLoginStatus("success")
|
||||
setCodexDeviceCode(null)
|
||||
const authJson = JSON.stringify(
|
||||
{
|
||||
auth_mode: "chatgpt",
|
||||
OPENAI_API_KEY: null,
|
||||
tokens: {
|
||||
id_token: result.idToken,
|
||||
access_token: result.accessToken,
|
||||
refresh_token: result.refreshToken,
|
||||
account_id: result.accountId ?? "",
|
||||
},
|
||||
last_refresh: new Date().toISOString(),
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
updateSelectedDraft((current) => ({
|
||||
...current,
|
||||
codexAuthJsonText: authJson,
|
||||
}))
|
||||
const draft = drafts.codex
|
||||
if (draft) {
|
||||
const codexEnvText =
|
||||
draft.codexAuthMode === "chatgpt_subscription"
|
||||
? patchEnvText(draft.envText, {
|
||||
OPENAI_API_KEY: "",
|
||||
OPENAI_BASE_URL: "",
|
||||
})
|
||||
: draft.envText
|
||||
try {
|
||||
await Promise.all([
|
||||
persistEnv(
|
||||
"codex",
|
||||
draft.enabled,
|
||||
codexEnvText,
|
||||
draft.modelProviderId
|
||||
),
|
||||
persistConfig("codex", draft.configText, {
|
||||
codexAuthJsonText: authJson,
|
||||
codexConfigTomlText: draft.codexConfigTomlText,
|
||||
}),
|
||||
])
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
toast.error(t("codex.loginSaveFailed"), {
|
||||
description: msg,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if (result.status === "error") {
|
||||
setCodexLoginError(result.message ?? "Unknown error")
|
||||
setCodexLoginStatus("error")
|
||||
setCodexDeviceCode(null)
|
||||
return
|
||||
}
|
||||
timer = setTimeout(poll, pollInterval)
|
||||
} catch {
|
||||
if (!active || codexPollCancelledRef.current) return
|
||||
timer = setTimeout(poll, pollInterval)
|
||||
}
|
||||
}
|
||||
|
||||
timer = setTimeout(poll, pollInterval)
|
||||
return () => {
|
||||
active = false
|
||||
if (timer) clearTimeout(timer)
|
||||
}
|
||||
}, [
|
||||
codexLoginStatus,
|
||||
codexDeviceCode,
|
||||
drafts.codex,
|
||||
persistConfig,
|
||||
persistEnv,
|
||||
updateSelectedDraft,
|
||||
t,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedAgent?.agent_type !== "codex" && codexLoginStatus !== "idle") {
|
||||
cancelCodexDeviceLogin()
|
||||
}
|
||||
}, [selectedAgent, codexLoginStatus, cancelCodexDeviceLogin])
|
||||
|
||||
if (loadingAgents) {
|
||||
return (
|
||||
<div className="h-full flex items-center justify-center text-sm text-muted-foreground">
|
||||
@@ -5304,6 +5432,108 @@ export function AcpAgentSettings() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{selectedDraft.codexAuthMode === "chatgpt_subscription" && (
|
||||
<div className="space-y-2">
|
||||
{hasCodexChatgptTokens(
|
||||
selectedDraft.codexAuthJsonText
|
||||
) &&
|
||||
codexLoginStatus !== "polling" &&
|
||||
codexLoginStatus !== "requesting" && (
|
||||
<div className="flex items-center gap-1.5 text-xs text-green-600">
|
||||
<CheckCircle2 className="h-3 w-3" />
|
||||
{t("codex.loggedIn")}
|
||||
</div>
|
||||
)}
|
||||
{codexLoginStatus === "idle" && (
|
||||
<Button
|
||||
onClick={handleCodexDeviceLogin}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
{hasCodexChatgptTokens(
|
||||
selectedDraft.codexAuthJsonText
|
||||
)
|
||||
? t("codex.loginRelogin")
|
||||
: t("codex.loginButton")}
|
||||
</Button>
|
||||
)}
|
||||
{codexLoginStatus === "requesting" && (
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
{t("codex.loginRequesting")}
|
||||
</div>
|
||||
)}
|
||||
{codexLoginStatus === "polling" && codexDeviceCode && (
|
||||
<div className="space-y-2 rounded-md border p-3">
|
||||
<p className="text-xs">{t("codex.loginStep1")}</p>
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs text-primary underline cursor-pointer"
|
||||
onClick={() =>
|
||||
openUrl(codexDeviceCode.verificationUrl)
|
||||
}
|
||||
>
|
||||
{codexDeviceCode.verificationUrl}
|
||||
</button>
|
||||
<p className="text-xs mt-1">
|
||||
{t("codex.loginStep2")}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="rounded bg-muted px-2 py-1 text-sm font-mono font-bold tracking-widest">
|
||||
{codexDeviceCode.userCode}
|
||||
</code>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 p-0"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
codexDeviceCode.userCode
|
||||
)
|
||||
toast.success(t("codex.loginCodeCopied"))
|
||||
}}
|
||||
>
|
||||
<Copy className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground mt-1">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
{t("codex.loginPolling")}
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={cancelCodexDeviceLogin}
|
||||
>
|
||||
{t("codex.loginCancel")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{codexLoginStatus === "success" && (
|
||||
<div className="flex items-center gap-1.5 text-xs text-green-600">
|
||||
<CheckCircle2 className="h-3 w-3" />
|
||||
{t("codex.loginSuccess")}
|
||||
</div>
|
||||
)}
|
||||
{codexLoginStatus === "error" && (
|
||||
<div className="space-y-1.5">
|
||||
<p className="text-xs text-destructive">
|
||||
{t("codex.loginFailed", {
|
||||
message: codexLoginError ?? "Unknown error",
|
||||
})}
|
||||
</p>
|
||||
<Button
|
||||
onClick={handleCodexDeviceLogin}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
>
|
||||
{t("codex.loginRetry")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedDraft.codexAuthMode === "model_provider" && (
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[11px] text-muted-foreground">
|
||||
@@ -5498,27 +5728,6 @@ export function AcpAgentSettings() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[11px] text-muted-foreground">
|
||||
{t("codex.authJsonNative")}
|
||||
</label>
|
||||
<Textarea
|
||||
value={selectedDraft.codexAuthJsonText}
|
||||
onChange={(event) => {
|
||||
handleCodexAuthJsonTextChange(event.target.value)
|
||||
}}
|
||||
placeholder={`{
|
||||
"OPENAI_API_KEY": "sk-..."
|
||||
}`}
|
||||
className="min-h-28 max-h-60 font-mono text-xs"
|
||||
/>
|
||||
{selectedCodexAuthError && (
|
||||
<div className="rounded-md border border-red-500/30 bg-red-500/5 px-2.5 py-1.5 text-[11px] text-red-400">
|
||||
{selectedCodexAuthError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-1.5">
|
||||
<label className="text-[11px] text-muted-foreground">
|
||||
{t("codex.configTomlNative")}
|
||||
|
||||
@@ -557,7 +557,21 @@
|
||||
"enableFast": "تفعيل Fast",
|
||||
"enableFastAria": "تفعيل مستوى خدمة Fast لـ Codex",
|
||||
"authJsonNative": "auth.json (أصلي)",
|
||||
"configTomlNative": "config.toml (أصلي)"
|
||||
"configTomlNative": "config.toml (أصلي)",
|
||||
"loginButton": "تسجيل الدخول باستخدام ChatGPT",
|
||||
"loginRequesting": "جارٍ طلب رمز تسجيل الدخول...",
|
||||
"loginStep1": "افتح الرابط التالي في متصفحك:",
|
||||
"loginStep2": "أدخل الرمز أدناه:",
|
||||
"loginPolling": "في انتظار التفويض...",
|
||||
"loginCancel": "إلغاء",
|
||||
"loginSuccess": "تم تسجيل الدخول بنجاح، تم حفظ الإعدادات!",
|
||||
"loginFailed": "فشل تسجيل الدخول: {message}",
|
||||
"loginRetry": "إعادة المحاولة",
|
||||
"loginCodeCopied": "تم نسخ الرمز",
|
||||
"loggedIn": "تم تسجيل الدخول",
|
||||
"loginRelogin": "إعادة تسجيل الدخول / تبديل الحساب",
|
||||
"loginTimeout": "انتهت مهلة تسجيل الدخول، يرجى المحاولة مرة أخرى",
|
||||
"loginSaveFailed": "تم تسجيل الدخول بنجاح ولكن فشل حفظ الإعدادات"
|
||||
},
|
||||
"gemini": {
|
||||
"authConfig": "إعداد مصادقة Gemini",
|
||||
|
||||
@@ -557,7 +557,21 @@
|
||||
"enableFast": "Fast aktivieren",
|
||||
"enableFastAria": "Fast-Servicestufe für Codex aktivieren",
|
||||
"authJsonNative": "auth.json (nativ)",
|
||||
"configTomlNative": "config.toml (nativ)"
|
||||
"configTomlNative": "config.toml (nativ)",
|
||||
"loginButton": "Mit ChatGPT anmelden",
|
||||
"loginRequesting": "Login-Code wird angefordert...",
|
||||
"loginStep1": "Öffnen Sie die folgende URL in Ihrem Browser:",
|
||||
"loginStep2": "Geben Sie den folgenden Code ein:",
|
||||
"loginPolling": "Warte auf Autorisierung...",
|
||||
"loginCancel": "Abbrechen",
|
||||
"loginSuccess": "Erfolgreich angemeldet, Konfiguration gespeichert!",
|
||||
"loginFailed": "Anmeldung fehlgeschlagen: {message}",
|
||||
"loginRetry": "Erneut versuchen",
|
||||
"loginCodeCopied": "Code kopiert",
|
||||
"loggedIn": "Konto angemeldet",
|
||||
"loginRelogin": "Erneut anmelden / Konto wechseln",
|
||||
"loginTimeout": "Anmeldung abgelaufen, bitte erneut versuchen",
|
||||
"loginSaveFailed": "Anmeldung erfolgreich, aber Konfiguration konnte nicht gespeichert werden"
|
||||
},
|
||||
"gemini": {
|
||||
"authConfig": "Gemini-Auth-Konfiguration",
|
||||
|
||||
@@ -557,7 +557,21 @@
|
||||
"enableFast": "Enable Fast",
|
||||
"enableFastAria": "Enable Fast service tier for Codex",
|
||||
"authJsonNative": "auth.json (native)",
|
||||
"configTomlNative": "config.toml (native)"
|
||||
"configTomlNative": "config.toml (native)",
|
||||
"loginButton": "Log in with ChatGPT",
|
||||
"loginRequesting": "Requesting login code...",
|
||||
"loginStep1": "Open the following URL in your browser:",
|
||||
"loginStep2": "Enter the code below:",
|
||||
"loginPolling": "Waiting for authorization...",
|
||||
"loginCancel": "Cancel",
|
||||
"loginSuccess": "Logged in successfully, config saved!",
|
||||
"loginFailed": "Login failed: {message}",
|
||||
"loginRetry": "Retry",
|
||||
"loginCodeCopied": "Code copied",
|
||||
"loggedIn": "Account logged in",
|
||||
"loginRelogin": "Re-login / Switch account",
|
||||
"loginTimeout": "Login timed out, please try again",
|
||||
"loginSaveFailed": "Login succeeded but failed to save config"
|
||||
},
|
||||
"gemini": {
|
||||
"authConfig": "Gemini Auth Config",
|
||||
|
||||
@@ -557,7 +557,21 @@
|
||||
"enableFast": "Habilitar Fast",
|
||||
"enableFastAria": "Habilitar nivel de servicio Fast para Codex",
|
||||
"authJsonNative": "auth.json (nativo)",
|
||||
"configTomlNative": "config.toml (nativo)"
|
||||
"configTomlNative": "config.toml (nativo)",
|
||||
"loginButton": "Iniciar sesión con ChatGPT",
|
||||
"loginRequesting": "Solicitando código de inicio de sesión...",
|
||||
"loginStep1": "Abre la siguiente URL en tu navegador:",
|
||||
"loginStep2": "Introduce el siguiente código:",
|
||||
"loginPolling": "Esperando autorización...",
|
||||
"loginCancel": "Cancelar",
|
||||
"loginSuccess": "¡Inicio de sesión exitoso, configuración guardada!",
|
||||
"loginFailed": "Error de inicio de sesión: {message}",
|
||||
"loginRetry": "Reintentar",
|
||||
"loginCodeCopied": "Código copiado",
|
||||
"loggedIn": "Cuenta conectada",
|
||||
"loginRelogin": "Reconectar / Cambiar cuenta",
|
||||
"loginTimeout": "Tiempo de inicio de sesión agotado, inténtalo de nuevo",
|
||||
"loginSaveFailed": "Inicio de sesión exitoso pero falló al guardar la configuración"
|
||||
},
|
||||
"gemini": {
|
||||
"authConfig": "Configuración de autenticación de Gemini",
|
||||
|
||||
@@ -557,7 +557,21 @@
|
||||
"enableFast": "Activer Fast",
|
||||
"enableFastAria": "Activer le niveau de service Fast pour Codex",
|
||||
"authJsonNative": "auth.json (natif)",
|
||||
"configTomlNative": "config.toml (natif)"
|
||||
"configTomlNative": "config.toml (natif)",
|
||||
"loginButton": "Se connecter avec ChatGPT",
|
||||
"loginRequesting": "Demande du code de connexion...",
|
||||
"loginStep1": "Ouvrez l'URL suivante dans votre navigateur :",
|
||||
"loginStep2": "Entrez le code ci-dessous :",
|
||||
"loginPolling": "En attente d'autorisation...",
|
||||
"loginCancel": "Annuler",
|
||||
"loginSuccess": "Connexion réussie, configuration enregistrée !",
|
||||
"loginFailed": "Échec de la connexion : {message}",
|
||||
"loginRetry": "Réessayer",
|
||||
"loginCodeCopied": "Code copié",
|
||||
"loggedIn": "Compte connecté",
|
||||
"loginRelogin": "Reconnecter / Changer de compte",
|
||||
"loginTimeout": "Connexion expirée, veuillez réessayer",
|
||||
"loginSaveFailed": "Connexion réussie mais échec de la sauvegarde de la configuration"
|
||||
},
|
||||
"gemini": {
|
||||
"authConfig": "Configuration d’authentification Gemini",
|
||||
|
||||
@@ -557,7 +557,21 @@
|
||||
"enableFast": "Fast を有効化",
|
||||
"enableFastAria": "Codex の Fast サービスティアを有効化",
|
||||
"authJsonNative": "auth.json(ネイティブ)",
|
||||
"configTomlNative": "config.toml(ネイティブ)"
|
||||
"configTomlNative": "config.toml(ネイティブ)",
|
||||
"loginButton": "ChatGPT でログイン",
|
||||
"loginRequesting": "ログインコードを取得中...",
|
||||
"loginStep1": "ブラウザで以下の URL を開いてください:",
|
||||
"loginStep2": "以下のコードを入力してください:",
|
||||
"loginPolling": "認証を待っています...",
|
||||
"loginCancel": "キャンセル",
|
||||
"loginSuccess": "ログイン成功、設定を保存しました!",
|
||||
"loginFailed": "ログインに失敗しました:{message}",
|
||||
"loginRetry": "再試行",
|
||||
"loginCodeCopied": "コードをコピーしました",
|
||||
"loggedIn": "アカウントにログイン済み",
|
||||
"loginRelogin": "再ログイン / アカウント切替",
|
||||
"loginTimeout": "ログインがタイムアウトしました。再試行してください",
|
||||
"loginSaveFailed": "ログインは成功しましたが、設定の保存に失敗しました"
|
||||
},
|
||||
"gemini": {
|
||||
"authConfig": "Gemini 認証設定",
|
||||
|
||||
@@ -557,7 +557,21 @@
|
||||
"enableFast": "Fast 활성화",
|
||||
"enableFastAria": "Codex용 Fast 서비스 계층 활성화",
|
||||
"authJsonNative": "auth.json (네이티브)",
|
||||
"configTomlNative": "config.toml (네이티브)"
|
||||
"configTomlNative": "config.toml (네이티브)",
|
||||
"loginButton": "ChatGPT로 로그인",
|
||||
"loginRequesting": "로그인 코드 요청 중...",
|
||||
"loginStep1": "브라우저에서 다음 URL을 열어주세요:",
|
||||
"loginStep2": "아래 코드를 입력하세요:",
|
||||
"loginPolling": "인증 대기 중...",
|
||||
"loginCancel": "취소",
|
||||
"loginSuccess": "로그인 성공, 설정이 저장되었습니다!",
|
||||
"loginFailed": "로그인 실패: {message}",
|
||||
"loginRetry": "재시도",
|
||||
"loginCodeCopied": "코드가 복사되었습니다",
|
||||
"loggedIn": "계정 로그인됨",
|
||||
"loginRelogin": "재로그인 / 계정 전환",
|
||||
"loginTimeout": "로그인 시간 초과, 다시 시도해 주세요",
|
||||
"loginSaveFailed": "로그인 성공했지만 설정 저장 실패"
|
||||
},
|
||||
"gemini": {
|
||||
"authConfig": "Gemini 인증 설정",
|
||||
|
||||
@@ -557,7 +557,21 @@
|
||||
"enableFast": "Habilitar Fast",
|
||||
"enableFastAria": "Habilitar nível de serviço Fast para Codex",
|
||||
"authJsonNative": "auth.json (nativo)",
|
||||
"configTomlNative": "config.toml (nativo)"
|
||||
"configTomlNative": "config.toml (nativo)",
|
||||
"loginButton": "Entrar com ChatGPT",
|
||||
"loginRequesting": "Solicitando código de login...",
|
||||
"loginStep1": "Abra a seguinte URL no seu navegador:",
|
||||
"loginStep2": "Digite o código abaixo:",
|
||||
"loginPolling": "Aguardando autorização...",
|
||||
"loginCancel": "Cancelar",
|
||||
"loginSuccess": "Login realizado com sucesso, configuração salva!",
|
||||
"loginFailed": "Falha no login: {message}",
|
||||
"loginRetry": "Tentar novamente",
|
||||
"loginCodeCopied": "Código copiado",
|
||||
"loggedIn": "Conta conectada",
|
||||
"loginRelogin": "Reconectar / Trocar conta",
|
||||
"loginTimeout": "Login expirou, tente novamente",
|
||||
"loginSaveFailed": "Login realizado com sucesso, mas falha ao salvar configuração"
|
||||
},
|
||||
"gemini": {
|
||||
"authConfig": "Configuração de autenticação do Gemini",
|
||||
|
||||
@@ -557,7 +557,21 @@
|
||||
"enableFast": "启用 Fast",
|
||||
"enableFastAria": "Codex 启用 Fast 服务等级",
|
||||
"authJsonNative": "auth.json(原生)",
|
||||
"configTomlNative": "config.toml(原生)"
|
||||
"configTomlNative": "config.toml(原生)",
|
||||
"loginButton": "使用 ChatGPT 登录",
|
||||
"loginRequesting": "正在请求登录码...",
|
||||
"loginStep1": "在浏览器中打开以下链接:",
|
||||
"loginStep2": "输入以下代码:",
|
||||
"loginPolling": "等待授权中...",
|
||||
"loginCancel": "取消",
|
||||
"loginSuccess": "登录成功,配置已保存!",
|
||||
"loginFailed": "登录失败:{message}",
|
||||
"loginRetry": "重试",
|
||||
"loginCodeCopied": "已复制代码",
|
||||
"loggedIn": "账号已登录",
|
||||
"loginRelogin": "重新登录 / 切换账号",
|
||||
"loginTimeout": "登录超时,请重试",
|
||||
"loginSaveFailed": "登录成功但配置保存失败"
|
||||
},
|
||||
"gemini": {
|
||||
"authConfig": "Gemini 认证配置",
|
||||
|
||||
@@ -557,7 +557,21 @@
|
||||
"enableFast": "啟用 Fast",
|
||||
"enableFastAria": "Codex 啟用 Fast 服務等級",
|
||||
"authJsonNative": "auth.json(原生)",
|
||||
"configTomlNative": "config.toml(原生)"
|
||||
"configTomlNative": "config.toml(原生)",
|
||||
"loginButton": "使用 ChatGPT 登入",
|
||||
"loginRequesting": "正在請求登入碼...",
|
||||
"loginStep1": "在瀏覽器中打開以下連結:",
|
||||
"loginStep2": "輸入以下代碼:",
|
||||
"loginPolling": "等待授權中...",
|
||||
"loginCancel": "取消",
|
||||
"loginSuccess": "登入成功,配置已儲存!",
|
||||
"loginFailed": "登入失敗:{message}",
|
||||
"loginRetry": "重試",
|
||||
"loginCodeCopied": "已複製代碼",
|
||||
"loggedIn": "帳號已登入",
|
||||
"loginRelogin": "重新登入 / 切換帳號",
|
||||
"loginTimeout": "登入逾時,請重試",
|
||||
"loginSaveFailed": "登入成功但配置儲存失敗"
|
||||
},
|
||||
"gemini": {
|
||||
"authConfig": "Gemini 認證配置",
|
||||
|
||||
@@ -277,6 +277,32 @@ export async function acpReorderAgents(agentTypes: AgentType[]): Promise<void> {
|
||||
return getTransport().call("acp_reorder_agents", { agentTypes })
|
||||
}
|
||||
|
||||
export async function codexRequestDeviceCode(): Promise<{
|
||||
userCode: string
|
||||
verificationUrl: string
|
||||
deviceAuthId: string
|
||||
interval: number
|
||||
}> {
|
||||
return getTransport().call("codex_request_device_code", {})
|
||||
}
|
||||
|
||||
export async function codexPollDeviceCode(params: {
|
||||
deviceAuthId: string
|
||||
userCode: string
|
||||
}): Promise<{
|
||||
status: "pending" | "success" | "error"
|
||||
message?: string
|
||||
idToken?: string
|
||||
accessToken?: string
|
||||
refreshToken?: string
|
||||
accountId?: string
|
||||
}> {
|
||||
return getTransport().call("codex_poll_device_code", {
|
||||
deviceAuthId: params.deviceAuthId,
|
||||
userCode: params.userCode,
|
||||
})
|
||||
}
|
||||
|
||||
export async function acpPreflight(
|
||||
agentType: AgentType,
|
||||
forceRefresh?: boolean
|
||||
|
||||
Reference in New Issue
Block a user