refactor(frontend): merge Codex image bridge and tool policy into one four-state control

Replace the two adjacent account-level controls (3-state injection bridge
+ 2-state explicit tool policy) in EditAccountModal with a single
four-state selector: follow channel / force inject / no injection /
block all. The four states map onto the existing extra keys
(codex_image_generation_bridge, codex_image_generation_explicit_tool_policy)
with no backend change; strip already disables the bridge upstream, so
the effective decision space is exactly these four states.

- load: strip takes precedence and reads as "block all"; legacy
  bridge_enabled key still recognized and cleaned up on save
- save: block writes policy=strip and drops the bridge override;
  inherit clears both keys
- semantic accent colors per state (sky/emerald/amber/rose) with dark
  mode support; state badge follows the same palette
- replace account-scope i18n keys in zh/en (channel-scope keys untouched)
- rewrite specs to cover all four states (25/25 passing)
This commit is contained in:
shaw
2026-07-04 09:49:37 +08:00
parent dfdb838ce4
commit be297b90ce
4 changed files with 126 additions and 159 deletions
@@ -1370,7 +1370,7 @@
</div>
</div>
<!-- OpenAI Codex 图片生成桥接账号级覆盖 -->
<!-- OpenAI Codex 图片工具统一策略(自动注入 + 客户端显式携带) -->
<div
v-if="account?.platform === 'openai' && (account?.type === 'oauth' || account?.type === 'setup-token' || account?.type === 'apikey')"
class="border-t border-gray-200 pt-4 dark:border-dark-600"
@@ -1382,91 +1382,39 @@
</div>
<div class="min-w-0 flex-1">
<div class="flex flex-wrap items-center gap-2">
<label class="input-label mb-0">{{ t('admin.accounts.openai.codexImageGenerationBridge') }}</label>
<label class="input-label mb-0">{{ t('admin.accounts.openai.codexImageTool') }}</label>
<span
class="rounded-full px-2 py-0.5 text-[11px] font-medium"
:class="codexImageGenerationBridgeBadgeClass"
:class="codexImageToolBadgeClass"
>
{{ codexImageGenerationBridgeBadgeLabel }}
{{ codexImageToolBadgeLabel }}
</span>
</div>
<p class="mt-1 text-xs leading-5 text-slate-600 dark:text-slate-300">
{{ t('admin.accounts.openai.codexImageGenerationBridgeDesc') }}
{{ t('admin.accounts.openai.codexImageToolDesc') }}
</p>
</div>
</div>
<div class="border-t border-sky-100 bg-white/70 p-2 dark:border-sky-900/50 dark:bg-dark-800/70">
<div class="grid grid-cols-1 gap-2 sm:grid-cols-3">
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<button
v-for="option in codexImageGenerationBridgeOptions"
v-for="option in codexImageToolOptions"
:key="option.value"
type="button"
:data-testid="`codex-image-bridge-${option.value}`"
@click="codexImageGenerationBridgeMode = option.value"
:data-testid="`codex-image-tool-${option.value}`"
@click="codexImageToolMode = option.value"
:class="[
'group flex min-h-[68px] items-start gap-2 rounded-md border px-3 py-2 text-left transition-all',
codexImageGenerationBridgeMode === option.value
? 'border-sky-300 bg-sky-50 text-sky-900 shadow-sm ring-1 ring-sky-200 dark:border-sky-700 dark:bg-sky-900/25 dark:text-sky-100 dark:ring-sky-800'
'group flex min-h-[62px] items-start gap-2 rounded-md border px-3 py-2 text-left transition-all',
codexImageToolMode === option.value
? option.selectedCardClass
: 'border-transparent bg-transparent text-slate-600 hover:border-gray-200 hover:bg-gray-50 dark:text-slate-300 dark:hover:border-dark-500 dark:hover:bg-dark-700'
]"
>
<span
:class="[
'mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border transition-colors',
codexImageGenerationBridgeMode === option.value
? 'border-sky-500 bg-sky-500 text-white'
: 'border-gray-300 text-transparent group-hover:border-gray-400 dark:border-dark-500'
]"
>
<Icon name="check" size="xs" :stroke-width="2" />
</span>
<span class="min-w-0">
<span class="block text-sm font-medium">{{ option.label }}</span>
<span class="mt-0.5 block text-xs leading-4 text-slate-500 dark:text-slate-400">{{ option.description }}</span>
</span>
</button>
</div>
</div>
</div>
</div>
<!-- OpenAI Codex 显式 image_generation tool 策略 -->
<div
v-if="account?.platform === 'openai' && (account?.type === 'oauth' || account?.type === 'setup-token' || account?.type === 'apikey')"
class="border-t border-gray-200 pt-4 dark:border-dark-600"
>
<div class="overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm dark:border-dark-600 dark:bg-dark-800">
<div class="flex items-start gap-3 px-4 py-3">
<div class="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-gray-50 text-slate-600 ring-1 ring-gray-200 dark:bg-dark-700 dark:text-slate-300 dark:ring-dark-600">
<Icon name="filter" size="sm" />
</div>
<div class="min-w-0 flex-1">
<label class="input-label mb-0">{{ t('admin.accounts.openai.codexImageToolPolicy') }}</label>
<p class="mt-1 text-xs leading-5 text-slate-600 dark:text-slate-300">
{{ t('admin.accounts.openai.codexImageToolPolicyDesc') }}
</p>
</div>
</div>
<div class="border-t border-gray-200 bg-gray-50/70 p-2 dark:border-dark-600 dark:bg-dark-700/50">
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
<button
v-for="option in codexImageToolPolicyOptions"
:key="option.value"
type="button"
:data-testid="`codex-image-tool-policy-${option.value}`"
@click="codexImageToolPolicyMode = option.value"
:class="[
'group flex min-h-[62px] items-start gap-2 rounded-md border px-3 py-2 text-left transition-all',
codexImageToolPolicyMode === option.value
? 'border-slate-300 bg-white text-slate-900 shadow-sm ring-1 ring-slate-200 dark:border-slate-600 dark:bg-dark-800 dark:text-slate-100 dark:ring-dark-500'
: 'border-transparent bg-transparent text-slate-600 hover:border-gray-200 hover:bg-white dark:text-slate-300 dark:hover:border-dark-500 dark:hover:bg-dark-800'
]"
>
<span
:class="[
'mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full border transition-colors',
codexImageToolPolicyMode === option.value
? 'border-slate-600 bg-slate-700 text-white dark:border-slate-400 dark:bg-slate-500'
codexImageToolMode === option.value
? option.selectedDotClass
: 'border-gray-300 text-transparent group-hover:border-gray-400 dark:border-dark-500'
]"
>
@@ -2679,10 +2627,8 @@ const openaiOAuthResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF
const openaiAPIKeyResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
const codexCLIOnlyEnabled = ref(false)
const codexCLIOnlyAppServerEnabled = ref(false)
type CodexImageGenerationBridgeMode = 'inherit' | 'enabled' | 'disabled'
const codexImageGenerationBridgeMode = ref<CodexImageGenerationBridgeMode>('inherit')
type CodexImageToolPolicyMode = 'allow' | 'strip'
const codexImageToolPolicyMode = ref<CodexImageToolPolicyMode>('allow')
type CodexImageToolMode = 'inherit' | 'enabled' | 'disabled' | 'block'
const codexImageToolMode = ref<CodexImageToolMode>('inherit')
type AnthropicAPIKeyAuthScheme = 'x_api_key' | 'authorization_bearer'
const anthropicPassthroughEnabled = ref(false)
const anthropicAPIKeyAuthScheme = ref<AnthropicAPIKeyAuthScheme>('x_api_key')
@@ -2736,58 +2682,61 @@ const openaiResponsesWebSocketV2Mode = computed({
const openAIWSModeConcurrencyHintKey = computed(() =>
resolveOpenAIWSModeConcurrencyHintKey(openaiResponsesWebSocketV2Mode.value)
)
const codexImageGenerationBridgeOptions = computed<Array<{
value: CodexImageGenerationBridgeMode
const codexImageToolOptions = computed<Array<{
value: CodexImageToolMode
label: string
description: string
selectedCardClass: string
selectedDotClass: string
}>>(() => [
{
value: 'inherit',
label: t('admin.accounts.openai.codexImageGenerationBridgeInherit'),
description: t('admin.accounts.openai.codexImageGenerationBridgeInheritDesc')
label: t('admin.accounts.openai.codexImageToolInherit'),
description: t('admin.accounts.openai.codexImageToolInheritDesc'),
selectedCardClass: 'border-sky-300 bg-sky-50 text-sky-900 shadow-sm ring-1 ring-sky-200 dark:border-sky-700 dark:bg-sky-900/25 dark:text-sky-100 dark:ring-sky-800',
selectedDotClass: 'border-sky-500 bg-sky-500 text-white'
},
{
value: 'enabled',
label: t('admin.accounts.openai.codexImageGenerationBridgeEnabled'),
description: t('admin.accounts.openai.codexImageGenerationBridgeEnabledDesc')
label: t('admin.accounts.openai.codexImageToolEnabled'),
description: t('admin.accounts.openai.codexImageToolEnabledDesc'),
selectedCardClass: 'border-emerald-300 bg-emerald-50 text-emerald-900 shadow-sm ring-1 ring-emerald-200 dark:border-emerald-700 dark:bg-emerald-900/25 dark:text-emerald-100 dark:ring-emerald-800',
selectedDotClass: 'border-emerald-500 bg-emerald-500 text-white'
},
{
value: 'disabled',
label: t('admin.accounts.openai.codexImageGenerationBridgeDisabled'),
description: t('admin.accounts.openai.codexImageGenerationBridgeDisabledDesc')
}
])
const codexImageToolPolicyOptions = computed<Array<{
value: CodexImageToolPolicyMode
label: string
description: string
}>>(() => [
{
value: 'allow',
label: t('admin.accounts.openai.codexImageToolPolicyAllow'),
description: t('admin.accounts.openai.codexImageToolPolicyAllowDesc')
label: t('admin.accounts.openai.codexImageToolDisabled'),
description: t('admin.accounts.openai.codexImageToolDisabledDesc'),
selectedCardClass: 'border-amber-300 bg-amber-50 text-amber-900 shadow-sm ring-1 ring-amber-200 dark:border-amber-700 dark:bg-amber-900/25 dark:text-amber-100 dark:ring-amber-800',
selectedDotClass: 'border-amber-500 bg-amber-500 text-white'
},
{
value: 'strip',
label: t('admin.accounts.openai.codexImageToolPolicyStrip'),
description: t('admin.accounts.openai.codexImageToolPolicyStripDesc')
value: 'block',
label: t('admin.accounts.openai.codexImageToolBlock'),
description: t('admin.accounts.openai.codexImageToolBlockDesc'),
selectedCardClass: 'border-rose-300 bg-rose-50 text-rose-900 shadow-sm ring-1 ring-rose-200 dark:border-rose-700 dark:bg-rose-900/25 dark:text-rose-100 dark:ring-rose-800',
selectedDotClass: 'border-rose-500 bg-rose-500 text-white'
}
])
const codexImageGenerationBridgeBadgeLabel = computed(() => {
switch (codexImageGenerationBridgeMode.value) {
const codexImageToolBadgeLabel = computed(() => {
switch (codexImageToolMode.value) {
case 'enabled':
return t('admin.accounts.openai.codexImageGenerationBridgeBadgeEnabled')
return t('admin.accounts.openai.codexImageToolBadgeEnabled')
case 'disabled':
return t('admin.accounts.openai.codexImageGenerationBridgeBadgeDisabled')
return t('admin.accounts.openai.codexImageToolBadgeDisabled')
case 'block':
return t('admin.accounts.openai.codexImageToolBadgeBlock')
default:
return t('admin.accounts.openai.codexImageGenerationBridgeBadgeInherit')
return t('admin.accounts.openai.codexImageToolBadgeInherit')
}
})
const codexImageGenerationBridgeBadgeClass = computed(() => {
switch (codexImageGenerationBridgeMode.value) {
const codexImageToolBadgeClass = computed(() => {
switch (codexImageToolMode.value) {
case 'enabled':
return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300'
case 'disabled':
return 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300'
case 'block':
return 'bg-rose-100 text-rose-700 dark:bg-rose-900/40 dark:text-rose-300'
default:
return 'bg-slate-100 text-slate-600 dark:bg-dark-600 dark:text-slate-300'
@@ -3105,8 +3054,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
codexCLIOnlyEnabled.value = false
codexCLIOnlyAppServerEnabled.value = false
codexImageGenerationBridgeMode.value = 'inherit'
codexImageToolPolicyMode.value = 'allow'
codexImageToolMode.value = 'inherit'
anthropicPassthroughEnabled.value = false
anthropicAPIKeyAuthScheme.value = 'x_api_key'
webSearchEmulationMode.value = 'default'
@@ -3125,14 +3073,13 @@ const syncFormFromAccount = (newAccount: Account | null) => {
const codexImageGenerationBridgeValue = typeof extra?.codex_image_generation_bridge === 'boolean'
? extra.codex_image_generation_bridge
: extra?.codex_image_generation_bridge_enabled
if (codexImageGenerationBridgeValue === true) {
codexImageGenerationBridgeMode.value = 'enabled'
if (extra?.codex_image_generation_explicit_tool_policy === 'strip') {
codexImageToolMode.value = 'block'
} else if (codexImageGenerationBridgeValue === true) {
codexImageToolMode.value = 'enabled'
} else if (codexImageGenerationBridgeValue === false) {
codexImageGenerationBridgeMode.value = 'disabled'
codexImageToolMode.value = 'disabled'
}
codexImageToolPolicyMode.value = extra?.codex_image_generation_explicit_tool_policy === 'strip'
? 'strip'
: 'allow'
openaiOAuthResponsesWebSocketV2Mode.value = resolveOpenAIWSModeFromExtra(extra, {
modeKey: 'openai_oauth_responses_websockets_v2_mode',
enabledKey: 'openai_oauth_responses_websockets_v2_enabled',
@@ -4277,15 +4224,19 @@ const handleSubmit = async () => {
}
delete newExtra.codex_image_generation_bridge_enabled
if (codexImageGenerationBridgeMode.value === 'inherit') {
delete newExtra.codex_image_generation_bridge
} else {
newExtra.codex_image_generation_bridge = codexImageGenerationBridgeMode.value === 'enabled'
}
if (codexImageToolPolicyMode.value === 'strip') {
newExtra.codex_image_generation_explicit_tool_policy = 'strip'
} else {
delete newExtra.codex_image_generation_explicit_tool_policy
switch (codexImageToolMode.value) {
case 'enabled':
case 'disabled':
newExtra.codex_image_generation_bridge = codexImageToolMode.value === 'enabled'
delete newExtra.codex_image_generation_explicit_tool_policy
break
case 'block':
newExtra.codex_image_generation_explicit_tool_policy = 'strip'
delete newExtra.codex_image_generation_bridge
break
default:
delete newExtra.codex_image_generation_bridge
delete newExtra.codex_image_generation_explicit_tool_policy
}
if (props.account.type === 'oauth' || props.account.type === 'setup-token') {
@@ -611,7 +611,7 @@ describe('EditAccountModal', () => {
expect(updateAccountMock.mock.calls[0]?.[1]?.extra?.openai_responses_supported).toBe(true)
})
it('submits account-level Codex image generation bridge override', async () => {
it('submits Codex image tool force-inject mode as bridge override', async () => {
const account = buildAccount()
account.extra = {
codex_image_generation_bridge: false,
@@ -624,15 +624,16 @@ describe('EditAccountModal', () => {
const wrapper = mountModal(account)
await wrapper.get('button[data-testid="codex-image-bridge-enabled"]').trigger('click')
await wrapper.get('button[data-testid="codex-image-tool-enabled"]').trigger('click')
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
expect(updateAccountMock).toHaveBeenCalledTimes(1)
expect(updateAccountMock.mock.calls[0]?.[1]?.extra?.codex_image_generation_bridge).toBe(true)
expect(updateAccountMock.mock.calls[0]?.[1]?.extra).not.toHaveProperty('codex_image_generation_bridge_enabled')
expect(updateAccountMock.mock.calls[0]?.[1]?.extra).not.toHaveProperty('codex_image_generation_explicit_tool_policy')
})
it('submits account-level Codex image_generation tool strip policy', async () => {
it('submits Codex image tool no-injection mode without strip policy', async () => {
const account = buildAccount()
updateAccountMock.mockReset()
checkMixedChannelRiskMock.mockReset()
@@ -641,14 +642,35 @@ describe('EditAccountModal', () => {
const wrapper = mountModal(account)
await wrapper.get('button[data-testid="codex-image-tool-policy-strip"]').trigger('click')
await wrapper.get('button[data-testid="codex-image-tool-disabled"]').trigger('click')
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
expect(updateAccountMock).toHaveBeenCalledTimes(1)
expect(updateAccountMock.mock.calls[0]?.[1]?.extra?.codex_image_generation_bridge).toBe(false)
expect(updateAccountMock.mock.calls[0]?.[1]?.extra).not.toHaveProperty('codex_image_generation_explicit_tool_policy')
})
it('submits Codex image tool block mode as strip policy and clears bridge override', async () => {
const account = buildAccount()
account.extra = {
codex_image_generation_bridge: true
}
updateAccountMock.mockReset()
checkMixedChannelRiskMock.mockReset()
checkMixedChannelRiskMock.mockResolvedValue({ has_risk: false })
updateAccountMock.mockResolvedValue(account)
const wrapper = mountModal(account)
await wrapper.get('button[data-testid="codex-image-tool-block"]').trigger('click')
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
expect(updateAccountMock).toHaveBeenCalledTimes(1)
expect(updateAccountMock.mock.calls[0]?.[1]?.extra?.codex_image_generation_explicit_tool_policy).toBe('strip')
expect(updateAccountMock.mock.calls[0]?.[1]?.extra).not.toHaveProperty('codex_image_generation_bridge')
})
it('keeps account-level Codex image_generation tool policy unset when allowing', async () => {
it('loads strip policy as block mode and clears both keys when reset to inherit', async () => {
const account = buildAccount()
account.extra = {
codex_image_generation_explicit_tool_policy: 'strip'
@@ -660,11 +682,12 @@ describe('EditAccountModal', () => {
const wrapper = mountModal(account)
await wrapper.get('button[data-testid="codex-image-tool-policy-allow"]').trigger('click')
await wrapper.get('button[data-testid="codex-image-tool-inherit"]').trigger('click')
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
expect(updateAccountMock).toHaveBeenCalledTimes(1)
expect(updateAccountMock.mock.calls[0]?.[1]?.extra).not.toHaveProperty('codex_image_generation_explicit_tool_policy')
expect(updateAccountMock.mock.calls[0]?.[1]?.extra).not.toHaveProperty('codex_image_generation_bridge')
})
it('setup-token account can select and submit OAuth WS mode', async () => {
+15 -19
View File
@@ -3557,25 +3557,21 @@ export default {
codexCLIOnlyAppServer: 'Allow Codex app-server clients',
codexCLIOnlyAppServerDesc:
"Effective only when the switch above is on. When enabled, this account also allows third-party clients that embed the Codex engine over the app-server protocol (e.g. Claude Code's codex plugin); they still pass the global engine-fingerprint gate. OR-combined with the global app-server toggle.",
codexImageGenerationBridge: 'Codex image-generation bridge',
codexImageGenerationBridgeDesc:
'Account policy takes precedence over channel and global settings. Only controls whether Codex requests through the /responses text endpoint receive the image_generation tool; standalone image-generation endpoints are unaffected.',
codexImageGenerationBridgeInherit: 'Follow channel',
codexImageGenerationBridgeInheritDesc: 'Do not write an account override; use the channel or global policy.',
codexImageGenerationBridgeEnabled: 'Force on',
codexImageGenerationBridgeEnabledDesc: 'Allow image tool injection for Codex /responses requests.',
codexImageGenerationBridgeDisabled: 'Force off',
codexImageGenerationBridgeDisabledDesc: 'Block image tool injection for Codex /responses requests.',
codexImageGenerationBridgeBadgeInherit: 'Channel policy',
codexImageGenerationBridgeBadgeEnabled: 'Account on',
codexImageGenerationBridgeBadgeDisabled: 'Account off',
codexImageToolPolicy: 'Codex image tool forwarding',
codexImageToolPolicyDesc:
'Only applies to client-provided image_generation tools in Codex /responses text requests; standalone image-generation endpoints are unaffected.',
codexImageToolPolicyAllow: 'Allow',
codexImageToolPolicyAllowDesc: 'Default behavior. Keep client-provided image_generation tools.',
codexImageToolPolicyStrip: 'Remove',
codexImageToolPolicyStripDesc: 'Delete image_generation tools and matching tool_choice before forwarding.',
codexImageTool: 'Codex image tool',
codexImageToolDesc:
'One policy for the image_generation tool on Codex /responses text requests: whether it is auto-injected, and whether client-provided tools pass through. Account policy takes precedence over channel and global settings; standalone image-generation endpoints are unaffected.',
codexImageToolInherit: 'Follow channel',
codexImageToolInheritDesc: 'No account override; injection follows the channel or global policy, and client-provided image tools pass through.',
codexImageToolEnabled: 'Force inject',
codexImageToolEnabledDesc: 'Always inject the image tool for Codex /responses requests.',
codexImageToolDisabled: 'No injection',
codexImageToolDisabledDesc: 'Never auto-inject; client-provided image tools still pass through.',
codexImageToolBlock: 'Block all',
codexImageToolBlockDesc: 'No injection, and client-provided image tools plus matching tool_choice are removed.',
codexImageToolBadgeInherit: 'Channel policy',
codexImageToolBadgeEnabled: 'Force inject',
codexImageToolBadgeDisabled: 'No injection',
codexImageToolBadgeBlock: 'Blocked',
compactMode: 'Compact mode',
compactModeDesc:
'Controls how this account participates in /responses/compact routing. Auto follows probe results, Force On always allows, Force Off always excludes.',
+15 -18
View File
@@ -3730,24 +3730,21 @@ export default {
codexCLIOnlyDesc: '仅对 OpenAI OAuth 生效。开启后仅允许 Codex 官方客户端家族访问;关闭后完全绕过并保持原逻辑。',
codexCLIOnlyAppServer: '允许 Codex app-server 客户端',
codexCLIOnlyAppServerDesc: '仅在上方开关开启时生效。开启后本账号额外放行内嵌 Codex 引擎、经 app-server 协议接入的第三方客户端(如 Claude Code 的 codex 插件),仍需通过全局引擎指纹门;与全局 app-server 开关取 OR(任一开即放行)。',
codexImageGenerationBridge: 'Codex 图片生成桥接',
codexImageGenerationBridgeDesc:
'账号级策略优先于渠道和全局配置。仅控制 Codex 走 /responses 文本端点时是否注入 image_generation 工具;不影响独立图片生成接口。',
codexImageGenerationBridgeInherit: '跟随渠道',
codexImageGenerationBridgeInheritDesc: '不写入账号覆盖,继续使用渠道或全局策略。',
codexImageGenerationBridgeEnabled: '强制开启',
codexImageGenerationBridgeEnabledDesc: '允许 Codex /responses 请求获得图片工具注入。',
codexImageGenerationBridgeDisabled: '强制关闭',
codexImageGenerationBridgeDisabledDesc: '阻断 Codex /responses 的图片工具注入。',
codexImageGenerationBridgeBadgeInherit: '渠道策略',
codexImageGenerationBridgeBadgeEnabled: '账号开启',
codexImageGenerationBridgeBadgeDisabled: '账号关闭',
codexImageToolPolicy: 'Codex 图片工具传递策略',
codexImageToolPolicyDesc: '仅作用于 Codex /responses 文本端点中客户端显式携带的 image_generation tool;不影响独立图片生成接口。',
codexImageToolPolicyAllow: '放行',
codexImageToolPolicyAllowDesc: '默认行为,保留客户端显式发送的 image_generation tool。',
codexImageToolPolicyStrip: '移除',
codexImageToolPolicyStripDesc: '转发前删除 image_generation tool 和指向它的 tool_choice。',
codexImageTool: 'Codex 图片工具',
codexImageToolDesc:
'统一控制 Codex /responses 文本请求的 image_generation 图片工具:是否自动注入,以及客户端自带该工具时是否放行。账号级策略优先于渠道和全局配置,不影响独立图片生成接口。',
codexImageToolInherit: '跟随渠道',
codexImageToolInheritDesc: '不写入账号覆盖,是否注入由渠道或全局策略决定;客户端自带的图片工具照常放行。',
codexImageToolEnabled: '强制注入',
codexImageToolEnabledDesc: '始终为 Codex /responses 请求注入图片工具。',
codexImageToolDisabled: '关闭注入',
codexImageToolDisabledDesc: '不自动注入;客户端自带的图片工具仍会放行。',
codexImageToolBlock: '完全阻断',
codexImageToolBlockDesc: '不注入,并移除客户端自带的图片工具及指向它的 tool_choice。',
codexImageToolBadgeInherit: '渠道策略',
codexImageToolBadgeEnabled: '强制注入',
codexImageToolBadgeDisabled: '关闭注入',
codexImageToolBadgeBlock: '完全阻断',
compactMode: 'Compact 模式',
compactModeDesc:
'控制本账号在 /responses/compact 调度中的参与方式。Auto 跟随探测结果,Force On 强制允许,Force Off 强制排除。',