mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-28 08:08:32 +00:00
Merge pull request #2834 from DaydreamCoding/pr/openai-codex-cli-allow-claude-code
feat(openai): codex_cli_only 新增放行 Claude Code Codex 插件的机制
This commit is contained in:
@@ -742,6 +742,50 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OpenAI OAuth: 额外放行 Claude Code 的 Codex 插件 -->
|
||||
<div v-if="allOpenAIOAuth" class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<label
|
||||
id="bulk-edit-openai-codex-allow-claude-code-label"
|
||||
class="input-label mb-0"
|
||||
for="bulk-edit-openai-codex-allow-claude-code-enabled"
|
||||
>
|
||||
{{ t('admin.accounts.openai.codexCLIOnlyAllowClaudeCode') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="enableCodexCLIOnlyAllowClaudeCode"
|
||||
id="bulk-edit-openai-codex-allow-claude-code-enabled"
|
||||
type="checkbox"
|
||||
aria-controls="bulk-edit-openai-codex-allow-claude-code"
|
||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
id="bulk-edit-openai-codex-allow-claude-code"
|
||||
:class="!enableCodexCLIOnlyAllowClaudeCode && 'pointer-events-none opacity-50'"
|
||||
>
|
||||
<p class="mb-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.openai.codexCLIOnlyAllowClaudeCodeDesc') }}
|
||||
</p>
|
||||
<button
|
||||
id="bulk-edit-openai-codex-allow-claude-code-toggle"
|
||||
type="button"
|
||||
:class="[
|
||||
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
|
||||
codexCLIOnlyAllowClaudeCodeEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
|
||||
]"
|
||||
@click="codexCLIOnlyAllowClaudeCodeEnabled = !codexCLIOnlyAllowClaudeCodeEnabled"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||
codexCLIOnlyAllowClaudeCodeEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OpenAI API Key WS mode -->
|
||||
<div v-if="allOpenAIAPIKey" class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
@@ -1219,6 +1263,7 @@ const enableOpenAIPassthrough = ref(false)
|
||||
const enableOpenAIWSMode = ref(false)
|
||||
const enableOpenAIAPIKeyWSMode = ref(false)
|
||||
const enableCodexCLIOnly = ref(false)
|
||||
const enableCodexCLIOnlyAllowClaudeCode = ref(false)
|
||||
const enableOpenAICompactMode = ref(false)
|
||||
const enableOpenAICompactModelMapping = ref(false)
|
||||
const enableRpmLimit = ref(false)
|
||||
@@ -1246,6 +1291,7 @@ const openaiPassthroughEnabled = ref(false)
|
||||
const openaiOAuthResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
|
||||
const openaiAPIKeyResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
|
||||
const codexCLIOnlyEnabled = ref(false)
|
||||
const codexCLIOnlyAllowClaudeCodeEnabled = ref(false)
|
||||
const openAICompactMode = ref<OpenAICompactMode>('auto')
|
||||
const openAICompactModelMappings = ref<ModelMapping[]>([])
|
||||
const rpmLimitEnabled = ref(false)
|
||||
@@ -1496,6 +1542,11 @@ const buildUpdatePayload = (): Record<string, unknown> | null => {
|
||||
extra.codex_cli_only = codexCLIOnlyEnabled.value
|
||||
}
|
||||
|
||||
if (enableCodexCLIOnlyAllowClaudeCode.value) {
|
||||
const extra = ensureExtra()
|
||||
extra.codex_cli_only_allowed_clients = codexCLIOnlyAllowClaudeCodeEnabled.value ? ['claude_code'] : []
|
||||
}
|
||||
|
||||
if (enableOpenAICompactMode.value) {
|
||||
const extra = ensureExtra()
|
||||
extra.openai_compact_mode = openAICompactMode.value
|
||||
@@ -1602,6 +1653,7 @@ const handleSubmit = async () => {
|
||||
enableOpenAIWSMode.value ||
|
||||
enableOpenAIAPIKeyWSMode.value ||
|
||||
enableCodexCLIOnly.value ||
|
||||
enableCodexCLIOnlyAllowClaudeCode.value ||
|
||||
enableOpenAICompactMode.value ||
|
||||
enableOpenAICompactModelMapping.value ||
|
||||
enableRpmLimit.value ||
|
||||
@@ -1704,6 +1756,7 @@ watch(
|
||||
enableOpenAIWSMode.value = false
|
||||
enableOpenAIAPIKeyWSMode.value = false
|
||||
enableCodexCLIOnly.value = false
|
||||
enableCodexCLIOnlyAllowClaudeCode.value = false
|
||||
enableOpenAICompactMode.value = false
|
||||
enableOpenAICompactModelMapping.value = false
|
||||
enableRpmLimit.value = false
|
||||
@@ -1727,6 +1780,7 @@ watch(
|
||||
openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||
openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||
codexCLIOnlyEnabled.value = false
|
||||
codexCLIOnlyAllowClaudeCodeEnabled.value = false
|
||||
openAICompactMode.value = 'auto'
|
||||
openAICompactModelMappings.value = []
|
||||
rpmLimitEnabled.value = false
|
||||
|
||||
@@ -2635,6 +2635,32 @@
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="codexCLIOnlyEnabled"
|
||||
class="mt-4 flex items-center justify-between border-l-2 border-gray-200 pl-4 dark:border-dark-600"
|
||||
>
|
||||
<div>
|
||||
<label class="input-label mb-0">{{ t('admin.accounts.openai.codexCLIOnlyAllowClaudeCode') }}</label>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.openai.codexCLIOnlyAllowClaudeCodeDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@click="codexCLIOnlyAllowClaudeCodeEnabled = !codexCLIOnlyAllowClaudeCodeEnabled"
|
||||
:class="[
|
||||
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
|
||||
codexCLIOnlyAllowClaudeCodeEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||
codexCLIOnlyAllowClaudeCodeEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OpenAI Compact 能力配置 -->
|
||||
@@ -3383,6 +3409,7 @@ const openAIEndpointCapabilities = ref<OpenAIEndpointCapability[]>(['chat_comple
|
||||
const openaiOAuthResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
|
||||
const openaiAPIKeyResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
|
||||
const codexCLIOnlyEnabled = ref(false)
|
||||
const codexCLIOnlyAllowClaudeCodeEnabled = ref(false)
|
||||
const anthropicPassthroughEnabled = ref(false)
|
||||
const webSearchEmulationMode = ref('default')
|
||||
const webSearchGlobalEnabled = ref(false)
|
||||
@@ -3807,6 +3834,7 @@ watch(
|
||||
openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||
openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||
codexCLIOnlyEnabled.value = false
|
||||
codexCLIOnlyAllowClaudeCodeEnabled.value = false
|
||||
}
|
||||
if (newPlatform !== 'anthropic') {
|
||||
anthropicPassthroughEnabled.value = false
|
||||
@@ -3827,6 +3855,7 @@ watch(
|
||||
([category, platform]) => {
|
||||
if (platform === 'openai' && category !== 'oauth-based') {
|
||||
codexCLIOnlyEnabled.value = false
|
||||
codexCLIOnlyAllowClaudeCodeEnabled.value = false
|
||||
}
|
||||
if (platform !== 'anthropic' || category !== 'apikey') {
|
||||
anthropicPassthroughEnabled.value = false
|
||||
@@ -4207,6 +4236,7 @@ const resetForm = () => {
|
||||
openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||
openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||
codexCLIOnlyEnabled.value = false
|
||||
codexCLIOnlyAllowClaudeCodeEnabled.value = false
|
||||
anthropicPassthroughEnabled.value = false
|
||||
webSearchEmulationMode.value = 'default'
|
||||
// Reset quota control state
|
||||
@@ -4285,6 +4315,15 @@ const buildOpenAIExtra = (base?: Record<string, unknown>): Record<string, unknow
|
||||
} else {
|
||||
delete extra.codex_cli_only
|
||||
}
|
||||
if (
|
||||
accountCategory.value === 'oauth-based' &&
|
||||
codexCLIOnlyEnabled.value &&
|
||||
codexCLIOnlyAllowClaudeCodeEnabled.value
|
||||
) {
|
||||
extra.codex_cli_only_allowed_clients = ['claude_code']
|
||||
} else {
|
||||
delete extra.codex_cli_only_allowed_clients
|
||||
}
|
||||
if (openAICompactMode.value !== 'auto') {
|
||||
extra.openai_compact_mode = openAICompactMode.value
|
||||
} else {
|
||||
|
||||
@@ -1673,6 +1673,32 @@
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="codexCLIOnlyEnabled"
|
||||
class="mt-4 flex items-center justify-between border-l-2 border-gray-200 pl-4 dark:border-dark-600"
|
||||
>
|
||||
<div>
|
||||
<label class="input-label mb-0">{{ t('admin.accounts.openai.codexCLIOnlyAllowClaudeCode') }}</label>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.openai.codexCLIOnlyAllowClaudeCodeDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@click="codexCLIOnlyAllowClaudeCodeEnabled = !codexCLIOnlyAllowClaudeCodeEnabled"
|
||||
:class="[
|
||||
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
|
||||
codexCLIOnlyAllowClaudeCodeEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||
codexCLIOnlyAllowClaudeCodeEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -2476,6 +2502,7 @@ const openAIEndpointCapabilities = ref<OpenAIEndpointCapability[]>(['chat_comple
|
||||
const openaiOAuthResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
|
||||
const openaiAPIKeyResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
|
||||
const codexCLIOnlyEnabled = ref(false)
|
||||
const codexCLIOnlyAllowClaudeCodeEnabled = ref(false)
|
||||
type CodexImageGenerationBridgeMode = 'inherit' | 'enabled' | 'disabled'
|
||||
const codexImageGenerationBridgeMode = ref<CodexImageGenerationBridgeMode>('inherit')
|
||||
const anthropicPassthroughEnabled = ref(false)
|
||||
@@ -2848,6 +2875,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||
openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||
codexCLIOnlyEnabled.value = false
|
||||
codexCLIOnlyAllowClaudeCodeEnabled.value = false
|
||||
codexImageGenerationBridgeMode.value = 'inherit'
|
||||
anthropicPassthroughEnabled.value = false
|
||||
webSearchEmulationMode.value = 'default'
|
||||
@@ -2885,6 +2913,9 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
})
|
||||
if (newAccount.type === 'oauth') {
|
||||
codexCLIOnlyEnabled.value = extra?.codex_cli_only === true
|
||||
codexCLIOnlyAllowClaudeCodeEnabled.value =
|
||||
Array.isArray(extra?.codex_cli_only_allowed_clients) &&
|
||||
(extra.codex_cli_only_allowed_clients as unknown[]).includes('claude_code')
|
||||
}
|
||||
const credentials = newAccount.credentials as Record<string, unknown> | undefined
|
||||
const compactMappings = credentials?.compact_model_mapping as Record<string, string> | undefined
|
||||
@@ -4004,6 +4035,12 @@ const handleSubmit = async () => {
|
||||
} else {
|
||||
delete newExtra.codex_cli_only
|
||||
}
|
||||
// 仅当 codex_cli_only 开启且子开关开启时写入 Claude Code 插件白名单,否则清除避免孤立字段
|
||||
if (codexCLIOnlyEnabled.value && codexCLIOnlyAllowClaudeCodeEnabled.value) {
|
||||
newExtra.codex_cli_only_allowed_clients = ['claude_code']
|
||||
} else {
|
||||
delete newExtra.codex_cli_only_allowed_clients
|
||||
}
|
||||
}
|
||||
|
||||
updatePayload.extra = newExtra
|
||||
|
||||
@@ -197,6 +197,25 @@ describe('BulkEditAccountModal', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('OpenAI OAuth 批量编辑应提交 codex_cli_only_allowed_clients 字段', async () => {
|
||||
const wrapper = mountModal({
|
||||
selectedPlatforms: ['openai'],
|
||||
selectedTypes: ['oauth']
|
||||
})
|
||||
|
||||
await wrapper.get('#bulk-edit-openai-codex-allow-claude-code-enabled').setValue(true)
|
||||
await wrapper.get('#bulk-edit-openai-codex-allow-claude-code-toggle').trigger('click')
|
||||
await wrapper.get('#bulk-edit-account-form').trigger('submit.prevent')
|
||||
await flushPromises()
|
||||
|
||||
expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledTimes(1)
|
||||
expect(adminAPI.accounts.bulkUpdate).toHaveBeenCalledWith([1, 2], {
|
||||
extra: {
|
||||
codex_cli_only_allowed_clients: ['claude_code']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('OpenAI API Key 批量编辑应提交 API Key 专属 WS mode 字段', async () => {
|
||||
const wrapper = mountModal({
|
||||
selectedPlatforms: ['openai'],
|
||||
|
||||
Reference in New Issue
Block a user