支持Codex CLI的官网订阅设置
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -406,6 +406,10 @@
|
||||
},
|
||||
"codex": {
|
||||
"configDescription": "يدعم الإعداد السريع لعنوان API ومفتاح API واسم النموذج وجهد الاستدلال، مع المزامنة مع `auth.json` / `config.toml`.",
|
||||
"authMode": "طريقة المصادقة",
|
||||
"chatgptSubscription": "اشتراك رسمي",
|
||||
"chatgptSubscriptionHint": "تسجيل الدخول باشتراك ChatGPT الرسمي، لا حاجة لـ API Key",
|
||||
"apiKeyHint": "الاتصال باستخدام API Key بخدمات OpenAI أو الخدمات المتوافقة",
|
||||
"selectProvider": "اختر المزود",
|
||||
"modelName": "اسم النموذج",
|
||||
"selectReasoningEffort": "اختر Reasoning Effort",
|
||||
|
||||
@@ -406,6 +406,10 @@
|
||||
},
|
||||
"codex": {
|
||||
"configDescription": "Unterstützt schnelle Konfiguration von API-URL, API-Key, Modellname und reasoning effort und synchronisiert mit `auth.json` / `config.toml`.",
|
||||
"authMode": "Authentifizierungsmodus",
|
||||
"chatgptSubscription": "Offizielles Abonnement",
|
||||
"chatgptSubscriptionHint": "Mit offiziellem ChatGPT-Abonnement anmelden, kein API Key erforderlich",
|
||||
"apiKeyHint": "Mit API Key zu OpenAI oder kompatiblen API-Diensten verbinden",
|
||||
"selectProvider": "Provider auswählen",
|
||||
"modelName": "Modellname",
|
||||
"selectReasoningEffort": "Reasoning Effort auswählen",
|
||||
|
||||
@@ -406,6 +406,10 @@
|
||||
},
|
||||
"codex": {
|
||||
"configDescription": "Supports quick configuration for API URL, API Key, model name and reasoning effort, and syncs with `auth.json` / `config.toml`.",
|
||||
"authMode": "Auth Mode",
|
||||
"chatgptSubscription": "Official Subscription",
|
||||
"chatgptSubscriptionHint": "Log in with ChatGPT official subscription, no API Key required",
|
||||
"apiKeyHint": "Connect using API Key to OpenAI or compatible API services",
|
||||
"selectProvider": "Select Provider",
|
||||
"modelName": "Model Name",
|
||||
"selectReasoningEffort": "Select Reasoning Effort",
|
||||
|
||||
@@ -406,6 +406,10 @@
|
||||
},
|
||||
"codex": {
|
||||
"configDescription": "Admite configuración rápida de URL de API, API Key, nombre del modelo y reasoning effort, y sincroniza con `auth.json` / `config.toml`.",
|
||||
"authMode": "Modo de Autenticación",
|
||||
"chatgptSubscription": "Suscripción oficial",
|
||||
"chatgptSubscriptionHint": "Iniciar sesión con suscripción oficial de ChatGPT, sin necesidad de API Key",
|
||||
"apiKeyHint": "Conectar usando API Key a OpenAI o servicios API compatibles",
|
||||
"selectProvider": "Seleccionar proveedor",
|
||||
"modelName": "Nombre del modelo",
|
||||
"selectReasoningEffort": "Seleccionar Reasoning Effort",
|
||||
|
||||
@@ -406,6 +406,10 @@
|
||||
},
|
||||
"codex": {
|
||||
"configDescription": "Prend en charge la configuration rapide de l’URL API, de la clé API, du nom du modèle et du reasoning effort, avec synchronisation vers `auth.json` / `config.toml`.",
|
||||
"authMode": "Mode d’authentification",
|
||||
"chatgptSubscription": "Abonnement officiel",
|
||||
"chatgptSubscriptionHint": "Connectez-vous avec l’abonnement officiel ChatGPT, pas besoin d’API Key",
|
||||
"apiKeyHint": "Connectez-vous avec une API Key à OpenAI ou aux services API compatibles",
|
||||
"selectProvider": "Sélectionner un provider",
|
||||
"modelName": "Nom du modèle",
|
||||
"selectReasoningEffort": "Sélectionner Reasoning Effort",
|
||||
|
||||
@@ -406,6 +406,10 @@
|
||||
},
|
||||
"codex": {
|
||||
"configDescription": "API URL、API Key、モデル名、reasoning effort を素早く設定でき、`auth.json` / `config.toml` と同期します。",
|
||||
"authMode": "認証方式",
|
||||
"chatgptSubscription": "公式サブスクリプション",
|
||||
"chatgptSubscriptionHint": "ChatGPT 公式サブスクリプションでログイン、API Key 不要",
|
||||
"apiKeyHint": "API Key で OpenAI または互換 API サービスに接続",
|
||||
"selectProvider": "プロバイダーを選択",
|
||||
"modelName": "モデル名",
|
||||
"selectReasoningEffort": "Reasoning Effort を選択",
|
||||
|
||||
@@ -406,6 +406,10 @@
|
||||
},
|
||||
"codex": {
|
||||
"configDescription": "API URL, API Key, 모델 이름, reasoning effort를 빠르게 설정하고 `auth.json` / `config.toml`과 동기화합니다.",
|
||||
"authMode": "인증 방식",
|
||||
"chatgptSubscription": "공식 구독",
|
||||
"chatgptSubscriptionHint": "ChatGPT 공식 구독으로 로그인, API Key 불필요",
|
||||
"apiKeyHint": "API Key로 OpenAI 또는 호환 API 서비스에 연결",
|
||||
"selectProvider": "Provider 선택",
|
||||
"modelName": "모델 이름",
|
||||
"selectReasoningEffort": "Reasoning Effort 선택",
|
||||
|
||||
@@ -406,6 +406,10 @@
|
||||
},
|
||||
"codex": {
|
||||
"configDescription": "Suporta configuração rápida de URL da API, API Key, nome do modelo e reasoning effort, com sincronização para `auth.json` / `config.toml`.",
|
||||
"authMode": "Modo de Autenticação",
|
||||
"chatgptSubscription": "Assinatura oficial",
|
||||
"chatgptSubscriptionHint": "Faça login com assinatura oficial do ChatGPT, sem necessidade de API Key",
|
||||
"apiKeyHint": "Conecte-se usando API Key ao OpenAI ou serviços de API compatíveis",
|
||||
"selectProvider": "Selecionar provedor",
|
||||
"modelName": "Nome do modelo",
|
||||
"selectReasoningEffort": "Selecionar Reasoning Effort",
|
||||
|
||||
@@ -406,6 +406,10 @@
|
||||
},
|
||||
"codex": {
|
||||
"configDescription": "支持 API URL、API Key、模型名称、Reasoning Effort 快捷配置,并与 `auth.json` / `config.toml` 双向联动。",
|
||||
"authMode": "认证方式",
|
||||
"chatgptSubscription": "官网订阅",
|
||||
"chatgptSubscriptionHint": "使用 ChatGPT 官网订阅登录,无需配置 API Key",
|
||||
"apiKeyHint": "使用 API Key 连接 OpenAI 或兼容的 API 服务",
|
||||
"selectProvider": "选择 Provider",
|
||||
"modelName": "模型名称",
|
||||
"selectReasoningEffort": "选择 Reasoning Effort",
|
||||
|
||||
@@ -406,6 +406,10 @@
|
||||
},
|
||||
"codex": {
|
||||
"configDescription": "支援 API URL、API Key、模型名稱、Reasoning Effort 快捷配置,並與 `auth.json` / `config.toml` 雙向聯動。",
|
||||
"authMode": "認證方式",
|
||||
"chatgptSubscription": "官網訂閱",
|
||||
"chatgptSubscriptionHint": "使用 ChatGPT 官網訂閱登入,無需配置 API Key",
|
||||
"apiKeyHint": "使用 API Key 連接 OpenAI 或相容的 API 服務",
|
||||
"selectProvider": "選擇 Provider",
|
||||
"modelName": "模型名稱",
|
||||
"selectReasoningEffort": "選擇 Reasoning Effort",
|
||||
|
||||
Reference in New Issue
Block a user