支持Codex CLI的官网订阅设置

This commit is contained in:
xintaofei
2026-03-15 18:17:46 +08:00
parent 9688248200
commit ca7f6ecd25
11 changed files with 248 additions and 81 deletions

View File

@@ -92,6 +92,7 @@ interface AgentDraft {
googleCloudProject: string
googleCloudLocation: string
googleApplicationCredentials: string
codexAuthMode: CodexAuthMode
codexModelProvider: string
codexProviderOptions: string[]
codexReasoningEffort: CodexReasoningEffort
@@ -970,6 +971,10 @@ interface CodexImportantValues {
}
const CODEX_DEFAULT_MODEL_PROVIDER = "codeg"
const CODEX_AUTH_MODES = ["api_key", "chatgpt_subscription"] as const
type CodexAuthMode = (typeof CODEX_AUTH_MODES)[number]
type CodexReasoningEffort = "low" | "medium" | "high" | "xhigh"
const CODEX_REASONING_EFFORT_OPTIONS: ReadonlyArray<{
@@ -1283,6 +1288,21 @@ function parseCodexAuthJsonText(authJsonText: string): string | null {
return parseCodexAuthJsonObject(authJsonText).error
}
function inferCodexAuthMode(authJsonText: string): CodexAuthMode {
const { authObject } = parseCodexAuthJsonObject(authJsonText)
if (authObject) {
// 官网订阅auth_mode 为 chatgpt或没有 OPENAI_API_KEY或值为 null
if (
authObject.auth_mode === "chatgpt" ||
!("OPENAI_API_KEY" in authObject) ||
authObject.OPENAI_API_KEY === null
) {
return "chatgpt_subscription"
}
}
return "api_key"
}
function extractCodexImportantValues(
authJsonText: string,
configTomlText: string
@@ -1982,6 +2002,10 @@ function buildAgentDraft(agent: AcpAgentInfo): AgentDraft {
googleCloudProject: geminiImportant.googleCloudProject,
googleCloudLocation: geminiImportant.googleCloudLocation,
googleApplicationCredentials: geminiImportant.googleApplicationCredentials,
codexAuthMode:
agent.agent_type === "codex"
? inferCodexAuthMode(codexAuthJsonText)
: "api_key",
codexModelProvider: codexImportant.modelProvider,
codexProviderOptions: codexImportant.providerOptions,
codexReasoningEffort: codexImportant.reasoningEffort,
@@ -3956,6 +3980,7 @@ export function AcpAgentSettings() {
)
updateSelectedDraft((current) => ({
...current,
codexAuthMode: inferCodexAuthMode(nextText),
codexAuthJsonText: nextText,
apiBaseUrl: important.apiBaseUrl,
apiKey: important.apiKey ?? current.apiKey,
@@ -4027,6 +4052,58 @@ export function AcpAgentSettings() {
[selectedAgent, selectedDraft, updateSelectedDraft]
)
const handleCodexAuthModeChange = useCallback(
(nextMode: CodexAuthMode) => {
if (
!selectedAgent ||
!selectedDraft ||
selectedAgent.agent_type !== "codex"
)
return
const nextAuthJsonText =
nextMode === "chatgpt_subscription"
? "{}"
: JSON.stringify({ OPENAI_API_KEY: "" }, null, 2)
const nextConfigTomlText =
nextMode === "chatgpt_subscription"
? ""
: selectedDraft.codexConfigTomlText
const nextEnvText =
nextMode === "chatgpt_subscription"
? patchEnvText(selectedDraft.envText, {
OPENAI_API_KEY: "",
OPENAI_BASE_URL: "",
})
: selectedDraft.envText
const synced = extractCodexImportantValues(
nextAuthJsonText,
nextConfigTomlText
)
updateSelectedDraft((current) => ({
...current,
codexAuthMode: nextMode,
codexAuthJsonText: nextAuthJsonText,
codexConfigTomlText: nextConfigTomlText,
envText: nextEnvText,
apiBaseUrl:
nextMode === "chatgpt_subscription" ? "" : synced.apiBaseUrl,
apiKey:
nextMode === "chatgpt_subscription" ? "" : (synced.apiKey ?? ""),
model: synced.model,
codexModelProvider: synced.modelProvider,
codexProviderOptions: synced.providerOptions,
codexReasoningEffort: synced.reasoningEffort,
codexSupportsWebsockets: synced.supportsWebsockets,
}))
},
[selectedAgent, selectedDraft, updateSelectedDraft]
)
const handleCodexImportantConfigChange = useCallback(
(
key: "apiBaseUrl" | "apiKey" | "model" | "reasoningEffort",
@@ -4484,106 +4561,148 @@ export function AcpAgentSettings() {
<div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground">
Provider
{t("codex.authMode")}
</label>
<Select
value={selectedDraft.codexModelProvider}
onValueChange={handleCodexModelProviderChange}
value={selectedDraft.codexAuthMode}
onValueChange={(value) => {
if (
CODEX_AUTH_MODES.includes(value as CodexAuthMode)
) {
handleCodexAuthModeChange(value as CodexAuthMode)
}
}}
>
<SelectTrigger className="w-full">
<SelectValue
placeholder={t("codex.selectProvider")}
/>
<SelectValue />
</SelectTrigger>
<SelectContent align="start">
{selectedDraft.codexProviderOptions.map(
(provider) => (
<SelectItem key={provider} value={provider}>
{provider}
</SelectItem>
)
)}
{CODEX_AUTH_MODES.map((mode) => (
<SelectItem key={mode} value={mode}>
{mode === "chatgpt_subscription"
? t("codex.chatgptSubscription")
: "API Key"}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-[11px] text-muted-foreground">
{selectedDraft.codexAuthMode === "chatgpt_subscription"
? t("codex.chatgptSubscriptionHint")
: t("codex.apiKeyHint")}
</p>
</div>
<div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground">
API URL
</label>
<Input
value={selectedDraft.apiBaseUrl}
onChange={(event) => {
handleCodexImportantConfigChange(
"apiBaseUrl",
event.target.value
)
}}
placeholder="https://api.openai.com/v1"
/>
</div>
{selectedDraft.codexAuthMode !== "chatgpt_subscription" && (
<div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground">
Provider
</label>
<Select
value={selectedDraft.codexModelProvider}
onValueChange={handleCodexModelProviderChange}
>
<SelectTrigger className="w-full">
<SelectValue
placeholder={t("codex.selectProvider")}
/>
</SelectTrigger>
<SelectContent align="start">
{selectedDraft.codexProviderOptions.map(
(provider) => (
<SelectItem key={provider} value={provider}>
{provider}
</SelectItem>
)
)}
</SelectContent>
</Select>
</div>
)}
<div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground">
API Key
</label>
<div className="flex items-center gap-2">
{selectedDraft.codexAuthMode !== "chatgpt_subscription" && (
<div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground">
API URL
</label>
<Input
type={
showApiKeys[selectedAgent.agent_type]
? "text"
: "password"
}
value={selectedDraft.apiKey}
value={selectedDraft.apiBaseUrl}
onChange={(event) => {
handleCodexImportantConfigChange(
"apiKey",
"apiBaseUrl",
event.target.value
)
}}
placeholder="sk-..."
placeholder="https://api.openai.com/v1"
/>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
setShowApiKeys((prev) => ({
...prev,
[selectedAgent.agent_type]:
!prev[selectedAgent.agent_type],
}))
}}
title={
showApiKeys[selectedAgent.agent_type]
? t("actions.hideApiKey")
: t("actions.showApiKey")
}
>
{showApiKeys[selectedAgent.agent_type] ? (
<EyeOff className="h-3.5 w-3.5" />
) : (
<Eye className="h-3.5 w-3.5" />
)}
</Button>
</div>
</div>
)}
<div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground">
{t("codex.modelName")}
</label>
<Input
value={selectedDraft.model}
onChange={(event) => {
handleCodexImportantConfigChange(
"model",
event.target.value
)
}}
placeholder="gpt-5 / gpt-5-mini"
/>
</div>
{selectedDraft.codexAuthMode !== "chatgpt_subscription" && (
<div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground">
API Key
</label>
<div className="flex items-center gap-2">
<Input
type={
showApiKeys[selectedAgent.agent_type]
? "text"
: "password"
}
value={selectedDraft.apiKey}
onChange={(event) => {
handleCodexImportantConfigChange(
"apiKey",
event.target.value
)
}}
placeholder="sk-..."
/>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
setShowApiKeys((prev) => ({
...prev,
[selectedAgent.agent_type]:
!prev[selectedAgent.agent_type],
}))
}}
title={
showApiKeys[selectedAgent.agent_type]
? t("actions.hideApiKey")
: t("actions.showApiKey")
}
>
{showApiKeys[selectedAgent.agent_type] ? (
<EyeOff className="h-3.5 w-3.5" />
) : (
<Eye className="h-3.5 w-3.5" />
)}
</Button>
</div>
</div>
)}
{selectedDraft.codexAuthMode !== "chatgpt_subscription" && (
<div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground">
{t("codex.modelName")}
</label>
<Input
value={selectedDraft.model}
onChange={(event) => {
handleCodexImportantConfigChange(
"model",
event.target.value
)
}}
placeholder="gpt-5 / gpt-5-mini"
/>
</div>
)}
<div className="space-y-1.5">
<label className="text-[11px] text-muted-foreground">
@@ -4679,10 +4798,18 @@ supports_websockets = true`}
<Button
size="sm"
onClick={() => {
const codexEnvText =
selectedDraft.codexAuthMode ===
"chatgpt_subscription"
? patchEnvText(selectedDraft.envText, {
OPENAI_API_KEY: "",
OPENAI_BASE_URL: "",
})
: selectedDraft.envText
persistPreferences(
selectedAgent.agent_type,
selectedDraft.enabled,
selectedDraft.envText,
codexEnvText,
selectedDraft.configText,
{
codexAuthJsonText: