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
@@ -8,6 +8,7 @@ import type { Provider } from "@/types";
const apiMocks = vi.hoisted(() => ({
add: vi.fn(),
ensureClaudeDesktopOfficialProvider: vi.fn(),
ensureCodexOfficialProvider: vi.fn(),
getAll: vi.fn(),
updateTrayMenu: vi.fn(),
}));
@@ -21,6 +22,8 @@ vi.mock("@/lib/api", () => ({
add: (...args: unknown[]) => apiMocks.add(...args),
ensureClaudeDesktopOfficialProvider: (...args: unknown[]) =>
apiMocks.ensureClaudeDesktopOfficialProvider(...args),
ensureCodexOfficialProvider: (...args: unknown[]) =>
apiMocks.ensureCodexOfficialProvider(...args),
getAll: (...args: unknown[]) => apiMocks.getAll(...args),
updateTrayMenu: (...args: unknown[]) => apiMocks.updateTrayMenu(...args),
},
@@ -59,6 +62,7 @@ beforeEach(() => {
apiMocks.ensureClaudeDesktopOfficialProvider
.mockReset()
.mockResolvedValue(true);
apiMocks.ensureCodexOfficialProvider.mockReset().mockResolvedValue(true);
apiMocks.getAll.mockReset().mockResolvedValue({});
apiMocks.updateTrayMenu.mockReset().mockResolvedValue(true);
uuidMocks.generateUUID.mockReset().mockReturnValue("generated-uuid");
@@ -133,4 +137,34 @@ describe("useAddProviderMutation", () => {
expect(apiMocks.add).not.toHaveBeenCalled();
expect(persistedProvider).toEqual(seedProvider);
});
it("recreates and returns the fixed Codex official seed", async () => {
const seedProvider: Provider = {
id: "codex-official",
name: "OpenAI Official",
settingsConfig: { auth: {}, config: "" },
category: "official",
};
apiMocks.getAll.mockResolvedValueOnce({
"codex-official": seedProvider,
});
const { wrapper } = createWrapper();
const { result } = renderHook(() => useAddProviderMutation("codex"), {
wrapper,
});
const persistedProvider = await act(async () =>
result.current.mutateAsync({
name: "OpenAI Official",
settingsConfig: { auth: {}, config: "" },
category: "official",
ensureCodexOfficialSeed: true,
}),
);
expect(apiMocks.ensureCodexOfficialProvider).toHaveBeenCalledTimes(1);
expect(apiMocks.getAll).toHaveBeenCalledWith("codex");
expect(apiMocks.add).not.toHaveBeenCalled();
expect(persistedProvider).toEqual(seedProvider);
});
});