Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ The API route publishes 1,050,000 context / 922,000 max input metadata. Its
`sol-pro`, `terra-pro`, and `luna-pro` virtual ids keep their selected public identity while the wire
uses the base model plus `reasoning.mode: "pro"`.

If the built-in `openai` provider is missing or disabled, the dashboard Accounts picker and Codex
Auth page can restore it: absent rows are created from the canonical preset, disabled canonical
rows are re-enabled without replacing saved mode or model settings, and noncanonical `openai`
rows are not offered that recovery path.

Shipped v1 configs migrate automatically to marker 2 and one option-aware row. The original config
is retained once at `~/.opencodex/config.json.pre-openai-tiers-v2.bak`; restore it with
`cp ~/.opencodex/config.json.pre-openai-tiers-v2.bak ~/.opencodex/config.json`.
Expand Down
2 changes: 2 additions & 0 deletions docs-site/src/content/docs/ja/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ bare `gpt-5.6-sol` は Providers ページの Pool/Direct オプションに従
max input 922,000 で `*-pro` virtual ID は公開状態を維持し、wire でベースモデルと
`reasoning.mode: "pro"` に切り替わります。

組み込み `openai` が欠落または無効な場合、ダッシュボードの Accounts ピッカーと Codex Auth から復元できます。欠落行は正規プリセットから作成され、正規の無効行は保存済みのモードやモデル設定を置き換えずに再有効化され、非正規の `openai` 行にはその復元経路は出ません。

出荷版 v1 config は marker 2 の単一オプション行に自動移行されます。オリジナルは
`~/.opencodex/config.json.pre-openai-tiers-v2.bak` に一度保存され、次のコマンドで復元します:
`cp ~/.opencodex/config.json.pre-openai-tiers-v2.bak ~/.opencodex/config.json`。
Expand Down
2 changes: 2 additions & 0 deletions docs-site/src/content/docs/ko/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ bare `gpt-5.6-sol`은 Providers 페이지의 Pool/Direct 옵션을 따르고,
max input 922,000이며 `*-pro` virtual id는 공개 상태에 유지되고 wire에서 base 모델과
`reasoning.mode: "pro"`로 바뀝니다.

내장 `openai` 제공자가 없거나 비활성화된 경우 대시보드 Accounts 선택기와 Codex Auth 페이지에서 복구할 수 있습니다. 없는 항목은 정규 프리셋으로 만들고, 비활성화된 정규 항목은 저장된 모드/모델 설정을 바꾸지 않고 다시 켜며, 비정규 `openai` 항목에는 그 복구 경로를 제공하지 않습니다.

shipped v1 config는 marker 2의 단일 옵션 행으로 자동 이관됩니다. 원본은
`~/.opencodex/config.json.pre-openai-tiers-v2.bak`에 한 번 보존되며 다음 명령으로 복원합니다:
`cp ~/.opencodex/config.json.pre-openai-tiers-v2.bak ~/.opencodex/config.json`.
Expand Down
5 changes: 5 additions & 0 deletions docs-site/src/content/docs/ru/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ description: Все способы, которыми opencodex аутентиф
виртуальные id `sol-pro`, `terra-pro` и `luna-pro` сохраняют выбранную публичную идентичность, тогда
как в фактическом запросе используется базовая модель плюс `reasoning.mode: "pro"`.

Если встроенный провайдер `openai` отсутствует или отключён, его можно восстановить из выбора Accounts
на панели и со страницы Codex Auth: отсутствующие записи создаются из канонического пресета, отключённые
канонические записи включаются без замены сохранённого режима и настроек моделей, а неканонические
записи `openai` этот путь восстановления не получают.

Поставляемые v1-конфигурации автоматически мигрируют на маркер 2 и одну строку с поддержкой опций.
Исходная конфигурация один раз сохраняется в `~/.opencodex/config.json.pre-openai-tiers-v2.bak`;
восстановить её можно командой
Expand Down
2 changes: 2 additions & 0 deletions docs-site/src/content/docs/zh-cn/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ bare `gpt-5.6-sol` 遵循 Providers 页面中的 Pool/Direct 选项,
922,000 max input;`*-pro` virtual id 保留在公开状态中,线上改写为 base 模型加
`reasoning.mode: "pro"`。

若内置 `openai` 提供商缺失或已禁用,可在仪表盘 Accounts 选择器或 Codex Auth 页面恢复:缺失行会从规范预设创建,已禁用的规范行会在不替换已保存模式/模型设置的情况下重新启用,非规范的 `openai` 行不会提供该恢复路径。

shipped v1 配置自动迁移到 marker 2 的单一选项行。原配置只保留一次到
`~/.opencodex/config.json.pre-openai-tiers-v2.bak`;恢复命令:
`cp ~/.opencodex/config.json.pre-openai-tiers-v2.bak ~/.opencodex/config.json`。
Expand Down
8 changes: 6 additions & 2 deletions gui/src/components/provider-catalog/ProviderCatalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,12 @@ export default function ProviderCatalog({
<a className="btn btn-ghost" href={row.href ?? "#codex-auth"}>{t("modal.accountManage")}</a>
)}
{onLogin && (
<button className={loggedIn ? "btn btn-ghost" : "btn btn-primary"} onClick={() => onLogin(row.id)}>
{loggedIn ? t("modal.accountAdd") : t("modal.accountLogin")}
<button
className={loggedIn ? "btn btn-ghost" : "btn btn-primary"}
disabled={busy}
onClick={() => { if (!busy) onLogin(row.id); }}
>
{busy ? t("codexAuth.enablingOpenai") : loggedIn ? t("modal.accountAdd") : t("modal.accountLogin")}
</button>
)}
</>
Expand Down
6 changes: 6 additions & 0 deletions gui/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,12 @@ export const de: Record<TKey, string> = {
"codexAuth.accountModeDirectDesc": "Anfragen verwenden nur die Hauptanmeldung; hinzugefügte Konten bleiben für den Pool-Modus gespeichert.",
"codexAuth.openaiMissing": "Der integrierte OpenAI-Anbieter ist nicht konfiguriert.",
"codexAuth.openaiDisabled": "Der integrierte OpenAI-Anbieter ist deaktiviert.",
"codexAuth.openaiUnavailableDesc": "Deine OpenAI-Konten sind weiterhin verfügbar. Aktiviere den Anbieter, um Codex-Anfragen weiterzuleiten.",
"codexAuth.enableOpenai": "OpenAI aktivieren",
"codexAuth.enablingOpenai": "Wird aktiviert...",
"codexAuth.enableOpenaiFailed": "OpenAI-Anbieter konnte nicht aktiviert werden.",
"codexAuth.openaiPresetLoadFailed": "OpenAI-Anbieter-Preset konnte nicht geladen werden.",
"codexAuth.openaiPresetUnavailable": "OpenAI-Anbieter-Preset ist nicht verfügbar.",
"codexAuth.openProviders": "Anbieter öffnen",
"codexAuth.add": "Hinzufügen",
"codexAuth.refreshQuota": "Kontingente aktualisieren",
Expand Down
6 changes: 6 additions & 0 deletions gui/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,12 @@ export const en = {
"codexAuth.accountModeDirectDesc": "Requests use only the main login; added accounts remain stored for Pool mode.",
"codexAuth.openaiMissing": "The built-in OpenAI provider is not configured.",
"codexAuth.openaiDisabled": "The built-in OpenAI provider is disabled.",
"codexAuth.openaiUnavailableDesc": "Your OpenAI accounts are still available. Enable the provider to route Codex requests.",
"codexAuth.enableOpenai": "Enable OpenAI",
"codexAuth.enablingOpenai": "Enabling...",
"codexAuth.enableOpenaiFailed": "Failed to enable the OpenAI provider.",
"codexAuth.openaiPresetLoadFailed": "Failed to load the OpenAI provider preset.",
"codexAuth.openaiPresetUnavailable": "OpenAI provider preset is unavailable.",
"codexAuth.openProviders": "Open Providers",
"codexAuth.add": "Add",
"codexAuth.refreshQuota": "Refresh quotas",
Expand Down
6 changes: 6 additions & 0 deletions gui/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,12 @@ export const ja: Record<TKey, string> = {
"codexAuth.accountModeDirectDesc": "リクエストはメインログインのみを使用します; 追加アカウントはプールモード用に保持されます。",
"codexAuth.openaiMissing": "組み込みの OpenAI プロバイダーが設定されていません。",
"codexAuth.openaiDisabled": "組み込みの OpenAI プロバイダーが無効です。",
"codexAuth.openaiUnavailableDesc": "OpenAI アカウントは引き続き利用できます。Codex リクエストをルーティングするにはプロバイダーを有効にしてください。",
"codexAuth.enableOpenai": "OpenAI を有効にする",
"codexAuth.enablingOpenai": "有効化中...",
"codexAuth.enableOpenaiFailed": "OpenAI プロバイダーを有効にできませんでした。",
"codexAuth.openaiPresetLoadFailed": "OpenAI プロバイダーのプリセットを読み込めませんでした。",
"codexAuth.openaiPresetUnavailable": "OpenAI プロバイダーのプリセットを利用できません。",
"codexAuth.openProviders": "プロバイダーを開く",
"codexAuth.add": "追加",
"codexAuth.refreshQuota": "クォータを更新",
Expand Down
6 changes: 6 additions & 0 deletions gui/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,12 @@ export const ko: Record<TKey, string> = {
"codexAuth.accountModeDirectDesc": "요청은 메인 로그인만 사용하며, 추가 계정은 풀 모드용으로 계속 저장됩니다.",
"codexAuth.openaiMissing": "내장 OpenAI 프로바이더가 설정되지 않았습니다.",
"codexAuth.openaiDisabled": "내장 OpenAI 프로바이더가 비활성화되어 있습니다.",
"codexAuth.openaiUnavailableDesc": "OpenAI 계정은 그대로 사용할 수 있습니다. Codex 요청을 라우팅하려면 프로바이더를 활성화하세요.",
"codexAuth.enableOpenai": "OpenAI 활성화",
"codexAuth.enablingOpenai": "활성화 중...",
"codexAuth.enableOpenaiFailed": "OpenAI 공급자를 활성화하지 못했습니다.",
"codexAuth.openaiPresetLoadFailed": "OpenAI 공급자 프리셋을 불러오지 못했습니다.",
"codexAuth.openaiPresetUnavailable": "OpenAI 공급자 프리셋을 사용할 수 없습니다.",
"codexAuth.openProviders": "프로바이더 열기",
"codexAuth.add": "추가",
"codexAuth.refreshQuota": "할당량 새로고침",
Expand Down
6 changes: 6 additions & 0 deletions gui/src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,12 @@ export const ru: Record<TKey, string> = {
"codexAuth.accountModeDirectDesc": "Запросы используют только основной вход; добавленные аккаунты сохраняются для режима пула.",
"codexAuth.openaiMissing": "Встроенный провайдер OpenAI не настроен.",
"codexAuth.openaiDisabled": "Встроенный провайдер OpenAI отключён.",
"codexAuth.openaiUnavailableDesc": "Ваши аккаунты OpenAI по-прежнему доступны. Включите провайдера для маршрутизации запросов Codex.",
"codexAuth.enableOpenai": "Включить OpenAI",
"codexAuth.enablingOpenai": "Включение...",
"codexAuth.enableOpenaiFailed": "Не удалось включить провайдер OpenAI.",
"codexAuth.openaiPresetLoadFailed": "Не удалось загрузить пресет провайдера OpenAI.",
"codexAuth.openaiPresetUnavailable": "Пресет провайдера OpenAI недоступен.",
"codexAuth.openProviders": "Открыть провайдеров",
"codexAuth.add": "Добавить",
"codexAuth.refreshQuota": "Обновить квоты",
Expand Down
6 changes: 6 additions & 0 deletions gui/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,12 @@ export const zh: Record<TKey, string> = {
"codexAuth.accountModeDirectDesc": "请求仅使用主登录;已添加账户会继续存储,供账户池模式使用。",
"codexAuth.openaiMissing": "未配置内置 OpenAI 提供方。",
"codexAuth.openaiDisabled": "内置 OpenAI 提供方已禁用。",
"codexAuth.openaiUnavailableDesc": "你的 OpenAI 账号仍然可用。启用提供方后即可路由 Codex 请求。",
"codexAuth.enableOpenai": "启用 OpenAI",
"codexAuth.enablingOpenai": "正在启用...",
"codexAuth.enableOpenaiFailed": "无法启用 OpenAI 提供方。",
"codexAuth.openaiPresetLoadFailed": "无法加载 OpenAI 提供方预设。",
"codexAuth.openaiPresetUnavailable": "OpenAI 提供方预设不可用。",
"codexAuth.openProviders": "打开提供商",
"codexAuth.add": "添加",
"codexAuth.refreshQuota": "刷新额度",
Expand Down
133 changes: 105 additions & 28 deletions gui/src/pages/CodexAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,71 @@ import { useCallback, useEffect, useState } from "react";
import { useT } from "../i18n";
import CodexAccountPool from "../components/CodexAccountPool";
import { codexAccountModeState, type CodexAccountModeState } from "../codex-multi-state";
import { ensureOpenAiProvider, openAiAccountProviderState, OpenAiEnableError } from "../provider-payload";

export type OpenAiAccountBannerState = CodexAccountModeState | "invalid" | null;

export function OpenAiAccountModeBanner({
state,
busy,
onEnable,
}: {
state: OpenAiAccountBannerState;
busy: boolean;
onEnable: () => void;
}) {
const t = useT();
return (
<div className="panel" style={{ marginBottom: 16 }}>
<div className="row">
<strong>{t("codexAuth.accountModeTitle")}</strong>
{state === "pool" && <span className="badge badge-accent">{t("codexAuth.accountModePool")}</span>}
{state === "direct" && <span className="badge badge-green">{t("codexAuth.accountModeDirect")}</span>}
</div>
{state === "pool" && (
<p className="card-sub" style={{ margin: "6px 0 0" }}>{t("codexAuth.accountModePoolDesc")}</p>
)}
{state === "direct" && (
<p className="card-sub" style={{ margin: "6px 0 0" }}>
{t("codexAuth.accountModeDirectDesc")} <a href="#providers">{t("codexAuth.openProviders")}</a>
</p>
)}
{(state === "absent" || state === "disabled") && (
<div className="row" style={{ alignItems: "center", marginTop: 8 }}>
<p className="card-sub" style={{ flex: 1, margin: 0 }}>{t("codexAuth.openaiUnavailableDesc")}</p>
<button className="btn btn-primary btn-sm" disabled={busy} onClick={onEnable}>
{busy ? t("codexAuth.enablingOpenai") : t("codexAuth.enableOpenai")}
</button>
</div>
)}
{state === "invalid" && (
<p className="card-sub" style={{ margin: "6px 0 0" }}>
{t("codexAuth.openaiMissing")} <a href="#providers">{t("codexAuth.openProviders")}</a>
</p>
)}
</div>
);
}

function openaiProviderFromConfig(config: unknown): {
adapter?: string;
authMode?: string;
baseUrl?: string;
disabled?: boolean;
} | undefined {
if (!config || typeof config !== "object") return undefined;
const providers = (config as { providers?: unknown }).providers;
if (!providers || typeof providers !== "object" || Array.isArray(providers)) return undefined;
if (!Object.hasOwn(providers, "openai")) return undefined;
const provider = (providers as Record<string, unknown>).openai;
if (!provider || typeof provider !== "object" || Array.isArray(provider)) return undefined;
return provider as {
adapter?: string;
authMode?: string;
baseUrl?: string;
disabled?: boolean;
};
}

/**
* Codex Auth page — a thin wrapper around CodexAccountPool (WP060 extraction).
Expand All @@ -10,12 +75,24 @@ import { codexAccountModeState, type CodexAccountModeState } from "../codex-mult
*/
export default function CodexAuth({ apiBase }: { apiBase: string }) {
const t = useT();
const [bannerState, setBannerState] = useState<OpenAiAccountBannerState>(null);
const [accountModeState, setAccountModeState] = useState<CodexAccountModeState | null>(null);
const [enableBusy, setEnableBusy] = useState(false);
const [enableError, setEnableError] = useState("");

const loadMode = useCallback(async () => {
try {
const config = await fetch(`${apiBase}/api/config`).then(r => r.json());
setAccountModeState(codexAccountModeState(config));
const providerState = openAiAccountProviderState(openaiProviderFromConfig(config));
if (providerState === "absent" || providerState === "disabled" || providerState === "invalid") {
setBannerState(providerState);
// Non-canonical / missing rows are not a live Codex account mode.
setAccountModeState(providerState === "disabled" ? "disabled" : "absent");
return;
}
const mode = codexAccountModeState(config);
setBannerState(mode);
setAccountModeState(mode);
} catch { /* banner degrades to no badge */ }
}, [apiBase]);

Expand All @@ -25,33 +102,33 @@ export default function CodexAuth({ apiBase }: { apiBase: string }) {
return () => { window.clearTimeout(timeout); window.clearInterval(iv); };
}, [loadMode]);

const banner = (
<div className="panel" style={{ marginBottom: 16 }}>
<div className="row">
<strong>{t("codexAuth.accountModeTitle")}</strong>
{accountModeState === "pool" && <span className="badge badge-accent">{t("codexAuth.accountModePool")}</span>}
{accountModeState === "direct" && <span className="badge badge-green">{t("codexAuth.accountModeDirect")}</span>}
</div>
{accountModeState === "pool" && (
<p className="card-sub" style={{ margin: "6px 0 0" }}>{t("codexAuth.accountModePoolDesc")}</p>
)}
{accountModeState === "direct" && (
<p className="card-sub" style={{ margin: "6px 0 0" }}>
{t("codexAuth.accountModeDirectDesc")} <a href="#providers">{t("codexAuth.openProviders")}</a>
</p>
)}
{accountModeState === "absent" && (
<p className="card-sub" style={{ margin: "8px 0 0" }}>
{t("codexAuth.openaiMissing")} <a href="#providers">{t("codexAuth.openProviders")}</a>
</p>
)}
{accountModeState === "disabled" && (
<p className="card-sub" style={{ margin: "8px 0 0" }}>
{t("codexAuth.openaiDisabled")} <a href="#providers">{t("codexAuth.openProviders")}</a>
</p>
)}
</div>
);
const enableOpenAi = async () => {
setEnableBusy(true);
setEnableError("");
try {
// Recovery is gated on the same canonical checks as Providers → Accounts.
if (bannerState !== "absent" && bannerState !== "disabled") return;
await ensureOpenAiProvider(apiBase, bannerState);
await loadMode();
} catch (error) {
if (error instanceof OpenAiEnableError) {
setEnableError(t(error.i18nKey));
} else {
setEnableError(error instanceof Error ? error.message : t("prov.saveFailed"));
}
} finally {
setEnableBusy(false);
}
};

const banner = <>
<OpenAiAccountModeBanner
state={bannerState}
busy={enableBusy}
onEnable={() => { void enableOpenAi(); }}
/>
{enableError && <div className="notice notice-err" role="alert">{enableError}</div>}
</>;

return <CodexAccountPool apiBase={apiBase} accountModeState={accountModeState} banner={banner} />;
}
Loading
Loading