mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-28 08:44:41 +08:00
Add managed ChatGPT account binding for Codex
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
import EndpointSpeedTest from "./EndpointSpeedTest";
|
||||
import { CodexOAuthSection } from "./CodexOAuthSection";
|
||||
import { ApiKeySection, EndpointField, ModelDropdown } from "./shared";
|
||||
import {
|
||||
fetchModelsForConfig,
|
||||
@@ -46,6 +47,10 @@ interface CodexFormFieldsProps {
|
||||
websiteUrl: string;
|
||||
isPartner?: boolean;
|
||||
partnerPromotionKey?: string;
|
||||
isCodexOauthPreset?: boolean;
|
||||
selectedCodexAccountId?: string | null;
|
||||
onCodexAccountSelect?: (accountId: string | null) => void;
|
||||
codexOauthNoneOptionLabel?: string;
|
||||
|
||||
// Base URL
|
||||
shouldShowSpeedTest: boolean;
|
||||
@@ -111,6 +116,10 @@ export function CodexFormFields({
|
||||
websiteUrl,
|
||||
isPartner,
|
||||
partnerPromotionKey,
|
||||
isCodexOauthPreset = false,
|
||||
selectedCodexAccountId,
|
||||
onCodexAccountSelect,
|
||||
codexOauthNoneOptionLabel,
|
||||
shouldShowSpeedTest,
|
||||
codexBaseUrl,
|
||||
onBaseUrlChange,
|
||||
@@ -284,26 +293,37 @@ export function CodexFormFields({
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Codex OAuth 账号选择 */}
|
||||
{isCodexOauthPreset && (
|
||||
<CodexOAuthSection
|
||||
selectedAccountId={selectedCodexAccountId}
|
||||
onAccountSelect={onCodexAccountSelect}
|
||||
noneOptionLabel={codexOauthNoneOptionLabel}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Codex API Key 输入框 */}
|
||||
<ApiKeySection
|
||||
id="codexApiKey"
|
||||
label="API Key"
|
||||
value={codexApiKey}
|
||||
onChange={onApiKeyChange}
|
||||
category={category}
|
||||
shouldShowLink={shouldShowApiKeyLink}
|
||||
websiteUrl={websiteUrl}
|
||||
isPartner={isPartner}
|
||||
partnerPromotionKey={partnerPromotionKey}
|
||||
placeholder={{
|
||||
official: t("providerForm.codexOfficialNoApiKey", {
|
||||
defaultValue: "官方供应商无需 API Key",
|
||||
}),
|
||||
thirdParty: t("providerForm.codexApiKeyAutoFill", {
|
||||
defaultValue: "输入 API Key,将自动填充到配置",
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
{!isCodexOauthPreset && (
|
||||
<ApiKeySection
|
||||
id="codexApiKey"
|
||||
label="API Key"
|
||||
value={codexApiKey}
|
||||
onChange={onApiKeyChange}
|
||||
category={category}
|
||||
shouldShowLink={shouldShowApiKeyLink}
|
||||
websiteUrl={websiteUrl}
|
||||
isPartner={isPartner}
|
||||
partnerPromotionKey={partnerPromotionKey}
|
||||
placeholder={{
|
||||
official: t("providerForm.codexOfficialNoApiKey", {
|
||||
defaultValue: "官方供应商无需 API Key",
|
||||
}),
|
||||
thirdParty: t("providerForm.codexApiKeyAutoFill", {
|
||||
defaultValue: "输入 API Key,将自动填充到配置",
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Codex Base URL 输入框 */}
|
||||
{shouldShowSpeedTest && (
|
||||
|
||||
@@ -31,6 +31,8 @@ interface CodexOAuthSectionProps {
|
||||
selectedAccountId?: string | null;
|
||||
/** 账号选择回调 */
|
||||
onAccountSelect?: (accountId: string | null) => void;
|
||||
/** 空选择项文案;默认表示使用托管认证的默认账号 */
|
||||
noneOptionLabel?: string;
|
||||
/** 是否开启 Codex FAST mode */
|
||||
fastModeEnabled?: boolean;
|
||||
/** FAST mode 切换回调 */
|
||||
@@ -47,6 +49,7 @@ export const CodexOAuthSection: React.FC<CodexOAuthSectionProps> = ({
|
||||
className,
|
||||
selectedAccountId,
|
||||
onAccountSelect,
|
||||
noneOptionLabel,
|
||||
fastModeEnabled = false,
|
||||
onFastModeChange,
|
||||
}) => {
|
||||
@@ -111,7 +114,7 @@ export const CodexOAuthSection: React.FC<CodexOAuthSectionProps> = ({
|
||||
</div>
|
||||
|
||||
{/* 账号选择器 */}
|
||||
{hasAnyAccount && onAccountSelect && (
|
||||
{onAccountSelect && (hasAnyAccount || noneOptionLabel) && (
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm text-muted-foreground">
|
||||
{t("codexOauth.selectAccount", "选择账号")}
|
||||
@@ -131,7 +134,8 @@ export const CodexOAuthSection: React.FC<CodexOAuthSectionProps> = ({
|
||||
<SelectContent>
|
||||
<SelectItem value="none">
|
||||
<span className="text-muted-foreground">
|
||||
{t("codexOauth.useDefaultAccount", "使用默认账号")}
|
||||
{noneOptionLabel ??
|
||||
t("codexOauth.useDefaultAccount", "使用默认账号")}
|
||||
</span>
|
||||
</SelectItem>
|
||||
{accounts.map((account) => (
|
||||
|
||||
@@ -126,6 +126,16 @@ type PresetEntry = {
|
||||
| HermesProviderPreset;
|
||||
};
|
||||
|
||||
function getPresetProviderType(
|
||||
preset: PresetEntry["preset"] | null | undefined,
|
||||
): "github_copilot" | "codex_oauth" | undefined {
|
||||
if (!preset || !("providerType" in preset)) return undefined;
|
||||
return preset.providerType === "github_copilot" ||
|
||||
preset.providerType === "codex_oauth"
|
||||
? preset.providerType
|
||||
: undefined;
|
||||
}
|
||||
|
||||
const codexApiFormatFromWireApi = (
|
||||
wireApi: string | undefined,
|
||||
): CodexApiFormat | undefined => {
|
||||
@@ -354,6 +364,13 @@ function ProviderFormFull({
|
||||
initialData?.meta?.pricingModelSource,
|
||||
),
|
||||
});
|
||||
setSelectedGitHubAccountId(
|
||||
resolveManagedAccountId(initialData?.meta, "github_copilot"),
|
||||
);
|
||||
setSelectedCodexAccountId(
|
||||
resolveManagedAccountId(initialData?.meta, "codex_oauth"),
|
||||
);
|
||||
setCodexFastMode(initialData?.meta?.codexFastMode ?? false);
|
||||
setCodexChatReasoning(initialData?.meta?.codexChatReasoning ?? {});
|
||||
}, [appId, initialData, supportsFullUrl]);
|
||||
|
||||
@@ -635,6 +652,41 @@ function ProviderFormFull({
|
||||
}));
|
||||
}, [appId]);
|
||||
|
||||
const selectedPresetEntry = useMemo(
|
||||
() =>
|
||||
selectedPresetId && selectedPresetId !== "custom"
|
||||
? (presetEntries.find((entry) => entry.id === selectedPresetId) ??
|
||||
null)
|
||||
: null,
|
||||
[presetEntries, selectedPresetId],
|
||||
);
|
||||
const selectedPresetProviderType = getPresetProviderType(
|
||||
selectedPresetEntry?.preset,
|
||||
);
|
||||
const initialProviderType = initialData?.meta?.providerType;
|
||||
const isCopilotProvider =
|
||||
appId === "claude" &&
|
||||
(selectedPresetProviderType === "github_copilot" ||
|
||||
initialProviderType === "github_copilot" ||
|
||||
baseUrl.includes("githubcopilot.com"));
|
||||
const isClaudeCodexOauthProvider =
|
||||
appId === "claude" &&
|
||||
(selectedPresetProviderType === "codex_oauth" ||
|
||||
initialProviderType === "codex_oauth");
|
||||
const isCodexOfficialProvider =
|
||||
appId === "codex" &&
|
||||
(category === "official" ||
|
||||
(selectedPresetProviderType === "codex_oauth" &&
|
||||
selectedPresetEntry?.preset.category === "official"));
|
||||
const isCodexOfficialManagedOauthBound =
|
||||
isCodexOfficialProvider && Boolean(selectedCodexAccountId);
|
||||
const wasCodexOfficialManagedOauthBound =
|
||||
appId === "codex" &&
|
||||
initialData?.category === "official" &&
|
||||
Boolean(resolveManagedAccountId(initialData?.meta, "codex_oauth"));
|
||||
const requiresCodexOauthLogin =
|
||||
isClaudeCodexOauthProvider || isCodexOfficialManagedOauthBound;
|
||||
|
||||
const {
|
||||
templateValues,
|
||||
templateValueEntries,
|
||||
@@ -1051,13 +1103,6 @@ function ProviderFormFull({
|
||||
}
|
||||
|
||||
// OAuth 未登录:B 类(token 根本不存在,保存了也没法建立)
|
||||
const isCopilotProvider =
|
||||
templatePreset?.providerType === "github_copilot" ||
|
||||
initialData?.meta?.providerType === "github_copilot" ||
|
||||
baseUrl.includes("githubcopilot.com");
|
||||
const isCodexOauthProvider =
|
||||
templatePreset?.providerType === "codex_oauth" ||
|
||||
initialData?.meta?.providerType === "codex_oauth";
|
||||
if (isCopilotProvider && !isCopilotAuthenticated) {
|
||||
toast.error(
|
||||
t("copilot.loginRequired", {
|
||||
@@ -1066,7 +1111,7 @@ function ProviderFormFull({
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (isCodexOauthProvider && !isCodexOauthAuthenticated) {
|
||||
if (requiresCodexOauthLogin && !isCodexOauthAuthenticated) {
|
||||
toast.error(
|
||||
t("codexOauth.loginRequired", {
|
||||
defaultValue: "请先登录 ChatGPT 账号",
|
||||
@@ -1110,14 +1155,14 @@ function ProviderFormFull({
|
||||
// cloud_provider(如 Bedrock)通过模板变量处理认证,跳过通用校验
|
||||
if (category !== "official" && category !== "cloud_provider") {
|
||||
if (appId === "claude") {
|
||||
if (!isCodexOauthProvider && !baseUrl.trim()) {
|
||||
if (!isClaudeCodexOauthProvider && !baseUrl.trim()) {
|
||||
issues.push(
|
||||
t("providerForm.endpointRequired", {
|
||||
defaultValue: "非官方供应商请填写 API 端点",
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (!isCopilotProvider && !isCodexOauthProvider && !apiKey.trim()) {
|
||||
if (!isCopilotProvider && !isClaudeCodexOauthProvider && !apiKey.trim()) {
|
||||
issues.push(
|
||||
t("providerForm.apiKeyRequired", {
|
||||
defaultValue: "非官方供应商请填写 API Key",
|
||||
@@ -1168,20 +1213,17 @@ function ProviderFormFull({
|
||||
};
|
||||
|
||||
const performSubmit = async (values: ProviderFormData) => {
|
||||
// OAuth / 其它身份识别(与 handleSubmit 保持一致)
|
||||
const isCopilotProvider =
|
||||
templatePreset?.providerType === "github_copilot" ||
|
||||
initialData?.meta?.providerType === "github_copilot" ||
|
||||
baseUrl.includes("githubcopilot.com");
|
||||
const isCodexOauthProvider =
|
||||
templatePreset?.providerType === "codex_oauth" ||
|
||||
initialData?.meta?.providerType === "codex_oauth";
|
||||
|
||||
let settingsConfig: string;
|
||||
|
||||
if (appId === "codex") {
|
||||
try {
|
||||
const authJson = JSON.parse(codexAuth);
|
||||
const shouldStripManagedCodexAuth =
|
||||
category === "official" &&
|
||||
(isCodexOfficialManagedOauthBound ||
|
||||
wasCodexOfficialManagedOauthBound);
|
||||
const authJson = shouldStripManagedCodexAuth
|
||||
? {}
|
||||
: JSON.parse(codexAuth);
|
||||
let normalizedCodexConfig =
|
||||
category !== "official" && (codexConfig ?? "").trim()
|
||||
? setCodexWireApi(codexConfig ?? "", "responses")
|
||||
@@ -1343,9 +1385,11 @@ function ProviderFormFull({
|
||||
const baseMeta: ProviderMeta | undefined =
|
||||
payload.meta ?? (initialData?.meta ? { ...initialData.meta } : undefined);
|
||||
|
||||
// 确定 providerType(新建时从预设获取,编辑时从现有数据获取)
|
||||
const providerType =
|
||||
templatePreset?.providerType || initialData?.meta?.providerType;
|
||||
const providerType = isCopilotProvider
|
||||
? "github_copilot"
|
||||
: isClaudeCodexOauthProvider || isCodexOfficialManagedOauthBound
|
||||
? "codex_oauth"
|
||||
: undefined;
|
||||
|
||||
const nextMeta: ProviderMeta = {
|
||||
...(baseMeta ?? {}),
|
||||
@@ -1367,19 +1411,25 @@ function ProviderFormFull({
|
||||
authProvider: "github_copilot",
|
||||
accountId: selectedGitHubAccountId ?? undefined,
|
||||
}
|
||||
: isCodexOauthProvider
|
||||
: isClaudeCodexOauthProvider
|
||||
? {
|
||||
source: "managed_account",
|
||||
authProvider: "codex_oauth",
|
||||
accountId: selectedCodexAccountId ?? undefined,
|
||||
}
|
||||
: isCodexOfficialManagedOauthBound
|
||||
? {
|
||||
source: "managed_account",
|
||||
authProvider: "codex_oauth",
|
||||
accountId: selectedCodexAccountId ?? undefined,
|
||||
}
|
||||
: undefined,
|
||||
// GitHub Copilot 多账号:保存关联的账号 ID
|
||||
githubAccountId:
|
||||
isCopilotProvider && selectedGitHubAccountId
|
||||
? selectedGitHubAccountId
|
||||
: undefined,
|
||||
codexFastMode: isCodexOauthProvider ? codexFastMode : undefined,
|
||||
codexFastMode: isClaudeCodexOauthProvider ? codexFastMode : undefined,
|
||||
codexChatReasoning:
|
||||
appId === "codex" &&
|
||||
category !== "official" &&
|
||||
@@ -1412,9 +1462,18 @@ function ProviderFormFull({
|
||||
: undefined,
|
||||
};
|
||||
|
||||
if (!isCodexOauthProvider && "codexFastMode" in nextMeta) {
|
||||
if (!isClaudeCodexOauthProvider && "codexFastMode" in nextMeta) {
|
||||
delete nextMeta.codexFastMode;
|
||||
}
|
||||
if (!providerType && "providerType" in nextMeta) {
|
||||
delete nextMeta.providerType;
|
||||
}
|
||||
if (!nextMeta.authBinding && "authBinding" in nextMeta) {
|
||||
delete nextMeta.authBinding;
|
||||
}
|
||||
if (!nextMeta.githubAccountId && "githubAccountId" in nextMeta) {
|
||||
delete nextMeta.githubAccountId;
|
||||
}
|
||||
|
||||
payload.meta = nextMeta;
|
||||
|
||||
@@ -1953,22 +2012,12 @@ function ProviderFormFull({
|
||||
websiteUrl={claudeWebsiteUrl}
|
||||
isPartner={isClaudePartner}
|
||||
partnerPromotionKey={claudePartnerPromotionKey}
|
||||
isCopilotPreset={
|
||||
templatePreset?.providerType === "github_copilot" ||
|
||||
initialData?.meta?.providerType === "github_copilot" ||
|
||||
baseUrl.includes("githubcopilot.com")
|
||||
}
|
||||
isCodexOauthPreset={
|
||||
templatePreset?.providerType === "codex_oauth" ||
|
||||
initialData?.meta?.providerType === "codex_oauth"
|
||||
}
|
||||
isCopilotPreset={isCopilotProvider}
|
||||
isCodexOauthPreset={isClaudeCodexOauthProvider}
|
||||
usesOAuth={
|
||||
templatePreset?.requiresOAuth === true ||
|
||||
templatePreset?.providerType === "github_copilot" ||
|
||||
initialData?.meta?.providerType === "github_copilot" ||
|
||||
baseUrl.includes("githubcopilot.com") ||
|
||||
templatePreset?.providerType === "codex_oauth" ||
|
||||
initialData?.meta?.providerType === "codex_oauth"
|
||||
isCopilotProvider ||
|
||||
isClaudeCodexOauthProvider
|
||||
}
|
||||
isCopilotAuthenticated={isCopilotAuthenticated}
|
||||
selectedGitHubAccountId={selectedGitHubAccountId}
|
||||
@@ -2022,6 +2071,10 @@ function ProviderFormFull({
|
||||
websiteUrl={codexWebsiteUrl}
|
||||
isPartner={isCodexPartner}
|
||||
partnerPromotionKey={codexPartnerPromotionKey}
|
||||
isCodexOauthPreset={isCodexOfficialProvider}
|
||||
selectedCodexAccountId={selectedCodexAccountId}
|
||||
onCodexAccountSelect={setSelectedCodexAccountId}
|
||||
codexOauthNoneOptionLabel="暂不绑定托管账号"
|
||||
shouldShowSpeedTest={shouldShowSpeedTest}
|
||||
codexBaseUrl={codexBaseUrl}
|
||||
onBaseUrlChange={handleCodexBaseUrlChange}
|
||||
|
||||
Reference in New Issue
Block a user