feat(codex): expose official routing and restore the built-in provider

Let users switch between the built-in OpenAI provider and third-party Codex providers directly from the provider panel while takeover mode remains active.

Centralize the frontend capability predicate so only the fixed codex-official seed receives native-login routing support, and keep copied UUID-based official entries clearly marked as unsupported.

Add an idempotent backend command that recreates the deleted official seed, wire it into the add-provider flow, refresh localized guidance, and add mutation and provider-action regression coverage.
This commit is contained in:
Jason
2026-07-12 17:52:12 +08:00
parent f2c6d48e19
commit f15184edb0
15 changed files with 228 additions and 21 deletions
@@ -31,6 +31,7 @@ interface AddProviderDialogProps {
providerKey?: string;
suggestedDefaults?: OpenClawSuggestedDefaults;
ensureClaudeDesktopOfficialSeed?: boolean;
ensureCodexOfficialSeed?: boolean;
},
) => Promise<void> | void;
}
@@ -116,6 +117,7 @@ export function AddProviderDialog({
providerKey?: string;
suggestedDefaults?: OpenClawSuggestedDefaults;
ensureClaudeDesktopOfficialSeed?: boolean;
ensureCodexOfficialSeed?: boolean;
} = {
name: values.name.trim(),
notes: values.notes?.trim() || undefined,
@@ -137,6 +139,14 @@ export function AddProviderDialog({
preset?.category === "official";
}
if (appId === "codex" && values.presetId) {
const presetIndex = parseInt(values.presetId.replace("codex-", ""));
const preset = codexProviderPresets[presetIndex];
providerData.ensureCodexOfficialSeed =
values.presetCategory === "official" &&
preset?.category === "official";
}
// OpenCode/OpenClaw: pass providerKey for ID generation
if (
(appId === "opencode" || appId === "openclaw" || appId === "hermes") &&
+27 -6
View File
@@ -25,6 +25,7 @@ import {
isCodexAnthropicWireApi,
isCodexChatWireApi,
} from "@/utils/providerConfigUtils";
import { supportsOfficialProxyTakeover } from "@/utils/providerCapabilities";
import { useProviderHealth } from "@/lib/query/failover";
import { useUsageQuery } from "@/lib/query/queries";
@@ -208,8 +209,14 @@ export function ProviderCard({
// 并不兑现(绕过 UI 即可切换)→ 属虚保护,却以误伤 category 缺失的自定义供应商为代价。
// 3) 预设导入的官方一定带 category="official"category 缺失的「真官方」现实中≈不存在。
// 真官方就该有显式 category;手动新建官方应引导标注,而不是靠空字段猜。
const supportsOfficialRouting = supportsOfficialProxyTakeover(
appId,
provider,
);
const isOfficialBlockedByProxy =
isProxyTakeover && provider.category === "official";
isProxyTakeover &&
provider.category === "official" &&
!supportsOfficialRouting;
const isCopilot =
provider.meta?.providerType === PROVIDER_TYPES.GITHUB_COPILOT ||
provider.meta?.usage_script?.templateType === "github_copilot";
@@ -405,14 +412,28 @@ export function ProviderCard({
</span>
)}
{appId === "codex" && provider.category === "official" && (
<span className="inline-flex items-center rounded-md bg-slate-200 px-1.5 py-0.5 text-[10px] font-semibold text-slate-700 dark:bg-slate-700/60 dark:text-slate-200">
{t("codex.noRoutingSupport", {
defaultValue: "不支持路由",
})}
{appId === "codex" && supportsOfficialRouting && (
<span className="inline-flex items-center rounded-md bg-sky-100 px-1.5 py-0.5 text-[10px] font-semibold text-sky-700 dark:bg-sky-900/40 dark:text-sky-300">
{isProxyTakeover
? t("codex.officialRouting", {
defaultValue: "官方账号路由",
})
: t("codex.nativeLogin", {
defaultValue: "Codex 登录",
})}
</span>
)}
{appId === "codex" &&
provider.category === "official" &&
!supportsOfficialRouting && (
<span className="inline-flex items-center rounded-md bg-slate-200 px-1.5 py-0.5 text-[10px] font-semibold text-slate-700 dark:bg-slate-700/60 dark:text-slate-200">
{t("codex.noRoutingSupport", {
defaultValue: "不支持路由",
})}
</span>
)}
{isProxyRunning && isInFailoverQueue && health && (
<ProviderHealthBadge
consecutiveFailures={health.consecutive_failures}