mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-30 08:22:34 +00:00
fix(openai): preserve Codex namespace tools on OAuth Responses forwarding
Flattening Codex `type:"namespace"` tool declarations into `namespace__name` renames the tool but cannot rewrite the contract the client already handed the model: Codex tells it to call `to=functions.collaboration.spawn_agent`. Under `tool_mode: code_mode_only` (gpt-5.6-*) collaboration tools are the model's only direct channel, so the rename left it unable to address them — it fell back to `functions.exec` and looped on meaningless shell commands until the session collapsed, with no upstream error anywhere. Fixes #4978. Preserving is what the upstream actually wants. OAuth egress is unconditionally chatgpt.com/backend-api/codex/responses (base_url is read only for API-key accounts), i.e. the party that defines the extension; codex-rs builds one request for both WS and HTTP and falls back WS->HTTP mid-session, so these declarations already reach that host over HTTP — as they do today via the responses-lite carrier and the WS/HTTP bridge, neither of which ever flattened. Namespace names cannot be allowlisted: `features.multi_agent_v2.tool_namespace` is user-configurable (openai/codex#31864 recommends renaming it to `agents`) and MCP/connector namespaces are generated at runtime (mcp__codex_apps__gmail). So the default is inverted rather than extended with more names. - Preserve namespace declarations by default for OpenAI OAuth; the new per-account `extra.openai_responses_flatten_namespaces` restores the previous behavior for deployments routing OAuth traffic to a relay that rejects them. - Keep `input[].namespace` on tool-call items for OAuth non-compact requests. The upstream requires the round-trip ("Missing namespace for function_call '...'. Round-trip the model's function_call item with its namespace field included."), so preserving declarations while stripping the calls would 400 on the second turn. The item-type allowlist mirrors the existing reactive strip. - Keep compact on its current behavior: that endpoint rejects the field outright ("Unknown parameter: 'input[894].namespace'", #4761) and there is no evidence either way about namespace declarations there, so this change does not widen its surface. API-key accounts are untouched — their upstream is a standard Responses API and the reactive retry only clears one index per round trip. - Clear the flatten mapping at the start of each Forward attempt so an account reached through failover cannot restore responses with the previous account's mapping. Admin UI gets an OAuth-only toggle (create/edit/bulk) plus zh/en copy noting the compact exception.
This commit is contained in:
@@ -82,6 +82,57 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OpenAI Codex namespace 工具摊平(兼容开关,仅 OAuth) -->
|
||||
<div
|
||||
v-if="allOpenAIOAuthOnly"
|
||||
class="border-t border-gray-200 pt-4 dark:border-dark-600"
|
||||
>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<div class="flex-1 pr-4">
|
||||
<label
|
||||
id="bulk-edit-openai-flatten-namespaces-label"
|
||||
class="input-label mb-0"
|
||||
for="bulk-edit-openai-flatten-namespaces-enabled"
|
||||
>
|
||||
{{ t('admin.accounts.openai.flattenNamespaces') }}
|
||||
</label>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.openai.flattenNamespacesDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
<input
|
||||
v-model="enableOpenAIFlattenNamespaces"
|
||||
id="bulk-edit-openai-flatten-namespaces-enabled"
|
||||
type="checkbox"
|
||||
aria-controls="bulk-edit-openai-flatten-namespaces-body"
|
||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
id="bulk-edit-openai-flatten-namespaces-body"
|
||||
:class="!enableOpenAIFlattenNamespaces && 'pointer-events-none opacity-50'"
|
||||
role="group"
|
||||
aria-labelledby="bulk-edit-openai-flatten-namespaces-label"
|
||||
>
|
||||
<button
|
||||
id="bulk-edit-openai-flatten-namespaces-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',
|
||||
openaiFlattenNamespacesEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
|
||||
]"
|
||||
@click="openaiFlattenNamespacesEnabled = !openaiFlattenNamespacesEnabled"
|
||||
>
|
||||
<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',
|
||||
openaiFlattenNamespacesEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Base URL (API Key only) -->
|
||||
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
@@ -1331,6 +1382,16 @@ const allOpenAIOAuth = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
// 严格 OAuth(不含 setup-token):namespace 摊平兼容开关只对 OAuth 账号生效
|
||||
const allOpenAIOAuthOnly = computed(() => {
|
||||
return (
|
||||
targetSelectedPlatforms.value.length === 1 &&
|
||||
targetSelectedPlatforms.value[0] === 'openai' &&
|
||||
targetSelectedTypes.value.length > 0 &&
|
||||
targetSelectedTypes.value.every(t => t === 'oauth')
|
||||
)
|
||||
})
|
||||
|
||||
const allOpenAIAPIKey = computed(() => {
|
||||
return (
|
||||
targetSelectedPlatforms.value.length === 1 &&
|
||||
@@ -1398,6 +1459,7 @@ const enableRateMultiplier = ref(false)
|
||||
const enableStatus = ref(false)
|
||||
const enableGroups = ref(false)
|
||||
const enableOpenAIPassthrough = ref(false)
|
||||
const enableOpenAIFlattenNamespaces = ref(false)
|
||||
const enableOpenAIWSMode = ref(false)
|
||||
const enableOpenAIAPIKeyWSMode = ref(false)
|
||||
const enableUpstreamBillingAutoProbe = ref(false)
|
||||
@@ -1429,6 +1491,8 @@ const rateMultiplier = ref(1)
|
||||
const status = ref<'active' | 'inactive'>('active')
|
||||
const groupIds = ref<number[]>([])
|
||||
const openaiPassthroughEnabled = ref(false)
|
||||
// Codex namespace 工具摊平兼容开关(仅 OAuth),缺省关闭即原样保留
|
||||
const openaiFlattenNamespacesEnabled = ref(false)
|
||||
const openaiOAuthResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
|
||||
const openaiAPIKeyResponsesWebSocketV2Mode = ref<OpenAIWSMode>(OPENAI_WS_MODE_OFF)
|
||||
const upstreamBillingAutoProbeMode = ref<'enabled' | 'disabled'>('enabled')
|
||||
@@ -1638,6 +1702,12 @@ const buildUpdatePayload = (): Record<string, unknown> | null => {
|
||||
}
|
||||
}
|
||||
|
||||
// 同时校验可见性:勾选后又改了目标筛选条件时,不应把该键写到非 OAuth 账号上
|
||||
if (enableOpenAIFlattenNamespaces.value && allOpenAIOAuthOnly.value) {
|
||||
const extra = ensureExtra()
|
||||
extra.openai_responses_flatten_namespaces = openaiFlattenNamespacesEnabled.value
|
||||
}
|
||||
|
||||
if (enableModelRestriction.value && !isOpenAIModelRestrictionDisabled.value) {
|
||||
// 统一使用 model_mapping 字段
|
||||
if (modelRestrictionMode.value === 'whitelist') {
|
||||
@@ -1806,6 +1876,7 @@ const handleSubmit = async () => {
|
||||
const hasAnyFieldEnabled =
|
||||
enableBaseUrl.value ||
|
||||
enableOpenAIPassthrough.value ||
|
||||
enableOpenAIFlattenNamespaces.value ||
|
||||
enableModelRestriction.value ||
|
||||
enableCustomErrorCodes.value ||
|
||||
enableInterceptWarmup.value ||
|
||||
@@ -1946,6 +2017,7 @@ watch(
|
||||
enableStatus.value = false
|
||||
enableGroups.value = false
|
||||
enableOpenAIPassthrough.value = false
|
||||
enableOpenAIFlattenNamespaces.value = false
|
||||
enableOpenAIWSMode.value = false
|
||||
enableOpenAIAPIKeyWSMode.value = false
|
||||
enableUpstreamBillingAutoProbe.value = false
|
||||
@@ -1958,6 +2030,7 @@ watch(
|
||||
// Reset all values
|
||||
baseUrl.value = ''
|
||||
openaiPassthroughEnabled.value = false
|
||||
openaiFlattenNamespacesEnabled.value = false
|
||||
modelRestrictionMode.value = 'whitelist'
|
||||
allowedModels.value = []
|
||||
modelMappings.value = []
|
||||
|
||||
@@ -2781,6 +2781,37 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OpenAI Codex namespace 工具摊平(兼容开关,仅 OAuth) -->
|
||||
<div
|
||||
v-if="form.platform === 'openai' && form.type === 'oauth'"
|
||||
class="border-t border-gray-200 pt-4 dark:border-dark-600"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="input-label mb-0">{{ t('admin.accounts.openai.flattenNamespaces') }}</label>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.openai.flattenNamespacesDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="create-openai-flatten-namespaces-toggle"
|
||||
@click="openaiFlattenNamespacesEnabled = !openaiFlattenNamespacesEnabled"
|
||||
: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',
|
||||
openaiFlattenNamespacesEnabled ? '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',
|
||||
openaiFlattenNamespacesEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OpenAI WS Mode 三态(off/ctx_pool/passthrough) -->
|
||||
<div
|
||||
v-if="form.platform === 'openai' && (accountCategory === 'oauth-based' || accountCategory === 'apikey')"
|
||||
@@ -3778,6 +3809,8 @@ const applyGrokOAuthUpstreamConfig = (credentials: Record<string, unknown>) => {
|
||||
const interceptWarmupRequests = ref(false)
|
||||
const autoPauseOnExpired = ref(true)
|
||||
const openaiPassthroughEnabled = ref(false)
|
||||
// OpenAI Codex namespace 工具摊平兼容开关(仅 OAuth),缺省关闭即原样保留
|
||||
const openaiFlattenNamespacesEnabled = ref(false)
|
||||
const openAILongContextBillingEnabled = ref(false)
|
||||
const openAILongContextBillingTouched = ref(false)
|
||||
const openAICompactMode = ref<OpenAICompactMode>('auto')
|
||||
@@ -4231,6 +4264,7 @@ watch(
|
||||
}
|
||||
if (newPlatform !== 'openai') {
|
||||
openaiPassthroughEnabled.value = false
|
||||
openaiFlattenNamespacesEnabled.value = false
|
||||
openAIEndpointCapabilities.value = ['chat_completions', 'embeddings']
|
||||
openaiOAuthResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||
openaiAPIKeyResponsesWebSocketV2Mode.value = OPENAI_WS_MODE_OFF
|
||||
@@ -4656,6 +4690,7 @@ const resetForm = () => {
|
||||
interceptWarmupRequests.value = false
|
||||
autoPauseOnExpired.value = true
|
||||
openaiPassthroughEnabled.value = false
|
||||
openaiFlattenNamespacesEnabled.value = false
|
||||
openAILongContextBillingEnabled.value = false
|
||||
openAILongContextBillingTouched.value = false
|
||||
openAICompactMode.value = 'auto'
|
||||
@@ -4740,6 +4775,12 @@ const buildOpenAIExtra = (base?: Record<string, unknown>): Record<string, unknow
|
||||
delete extra.openai_passthrough
|
||||
delete extra.openai_oauth_passthrough
|
||||
}
|
||||
// 缺省即保留 namespace,不写空值,避免 extra 里堆积默认项
|
||||
if (form.type === 'oauth' && openaiFlattenNamespacesEnabled.value) {
|
||||
extra.openai_responses_flatten_namespaces = true
|
||||
} else {
|
||||
delete extra.openai_responses_flatten_namespaces
|
||||
}
|
||||
extra.openai_long_context_billing_enabled = openAILongContextBillingEnabled.value
|
||||
|
||||
if (accountCategory.value === 'oauth-based' && codexCLIOnlyEnabled.value) {
|
||||
|
||||
@@ -1481,6 +1481,37 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OpenAI Codex namespace 工具摊平(兼容开关,仅 OAuth) -->
|
||||
<div
|
||||
v-if="account?.platform === 'openai' && account?.type === 'oauth'"
|
||||
class="border-t border-gray-200 pt-4 dark:border-dark-600"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="input-label mb-0">{{ t('admin.accounts.openai.flattenNamespaces') }}</label>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.openai.flattenNamespacesDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="edit-openai-flatten-namespaces-toggle"
|
||||
@click="openaiFlattenNamespacesEnabled = !openaiFlattenNamespacesEnabled"
|
||||
: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',
|
||||
openaiFlattenNamespacesEnabled ? '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',
|
||||
openaiFlattenNamespacesEnabled ? 'translate-x-5' : 'translate-x-0'
|
||||
]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OpenAI Codex hosted image_generation bridge policy -->
|
||||
<div
|
||||
v-if="account?.platform === 'openai' && (account?.type === 'oauth' || account?.type === 'setup-token' || account?.type === 'apikey')"
|
||||
@@ -2841,6 +2872,8 @@ const customBaseUrl = ref('')
|
||||
|
||||
// OpenAI 自动透传开关(OAuth/API Key)
|
||||
const openaiPassthroughEnabled = ref(false)
|
||||
// OpenAI Codex namespace 工具摊平兼容开关(仅 OAuth),缺省关闭即原样保留
|
||||
const openaiFlattenNamespacesEnabled = ref(false)
|
||||
const openAILongContextBillingEnabled = ref(false)
|
||||
// OpenAI 订阅档位(Plus/Pro/Free)手动覆盖值,存于 credentials.plan_type;'' 表示清空/自动识别
|
||||
const editPlanType = ref<string>('')
|
||||
@@ -3276,6 +3309,7 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
|
||||
// Load OpenAI passthrough toggle (OpenAI OAuth/SetupToken/API Key)
|
||||
openaiPassthroughEnabled.value = false
|
||||
openaiFlattenNamespacesEnabled.value = false
|
||||
openAILongContextBillingEnabled.value = false
|
||||
editPlanType.value = ''
|
||||
openAICompactMode.value = 'auto'
|
||||
@@ -3292,6 +3326,8 @@ const syncFormFromAccount = (newAccount: Account | null) => {
|
||||
webSearchEmulationMode.value = 'default'
|
||||
if (newAccount.platform === 'openai' && (newAccount.type === 'oauth' || newAccount.type === 'setup-token' || newAccount.type === 'apikey')) {
|
||||
openaiPassthroughEnabled.value = extra?.openai_passthrough === true || extra?.openai_oauth_passthrough === true
|
||||
openaiFlattenNamespacesEnabled.value =
|
||||
newAccount.type === 'oauth' && extra?.openai_responses_flatten_namespaces === true
|
||||
const longContextBillingValue = extra?.openai_long_context_billing_enabled
|
||||
openAILongContextBillingEnabled.value = longContextBillingValue === true
|
||||
// plan_type 手动覆盖仅 OAuth 有实际调度语义(IsOpenAIChatGPTSubscription 要求 oauth),故只对 oauth 回填
|
||||
@@ -4526,6 +4562,12 @@ const handleSubmit = async () => {
|
||||
delete newExtra.openai_passthrough
|
||||
delete newExtra.openai_oauth_passthrough
|
||||
}
|
||||
// 缺省即保留 namespace,不写空值,避免 extra 里堆积默认项
|
||||
if (props.account.type === 'oauth' && openaiFlattenNamespacesEnabled.value) {
|
||||
newExtra.openai_responses_flatten_namespaces = true
|
||||
} else {
|
||||
delete newExtra.openai_responses_flatten_namespaces
|
||||
}
|
||||
if (isSparkShadow.value) {
|
||||
delete newExtra.openai_long_context_billing_enabled
|
||||
} else {
|
||||
|
||||
@@ -237,6 +237,34 @@ describe('BulkEditAccountModal', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('OpenAI OAuth 批量编辑可开启 namespace 摊平兼容开关', async () => {
|
||||
const wrapper = mountModal({
|
||||
selectedPlatforms: ['openai'],
|
||||
selectedTypes: ['oauth']
|
||||
})
|
||||
|
||||
await wrapper.get('#bulk-edit-openai-flatten-namespaces-enabled').setValue(true)
|
||||
await wrapper.get('#bulk-edit-openai-flatten-namespaces-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: {
|
||||
openai_responses_flatten_namespaces: true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('namespace 摊平开关不对 setup-token 等非 OAuth 选择展示', async () => {
|
||||
const wrapper = mountModal({
|
||||
selectedPlatforms: ['openai'],
|
||||
selectedTypes: ['oauth', 'setup-token']
|
||||
})
|
||||
|
||||
expect(wrapper.find('#bulk-edit-openai-flatten-namespaces-enabled').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('OpenAI OAuth 批量编辑应提交 OAuth 专属 WS mode 字段(含 http_bridge)', async () => {
|
||||
const wrapper = mountModal({
|
||||
selectedPlatforms: ['openai'],
|
||||
|
||||
@@ -167,6 +167,21 @@ describe('CreateAccountModal OpenAI long-context billing', () => {
|
||||
expect(createAccountMock.mock.calls[0]?.[0]?.extra?.openai_long_context_billing_enabled).toBe(false)
|
||||
})
|
||||
|
||||
// namespace 摊平是仅 OAuth 的兼容开关:API Key 走 chat completions 回退桥时由桥自行摊平
|
||||
it('shows the Codex namespace flatten toggle only for OpenAI OAuth accounts', async () => {
|
||||
const wrapper = mountModal()
|
||||
await selectButtonByText(wrapper, 'OpenAI')
|
||||
|
||||
expect(wrapper.find('[data-testid="create-openai-flatten-namespaces-toggle"]').exists()).toBe(
|
||||
true
|
||||
)
|
||||
|
||||
await selectButtonByText(wrapper, 'API Key')
|
||||
expect(wrapper.find('[data-testid="create-openai-flatten-namespaces-toggle"]').exists()).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('enables upstream billing probes by default for new OpenAI API key accounts', async () => {
|
||||
await submitApiKeyAccount('openai')
|
||||
|
||||
|
||||
@@ -416,6 +416,57 @@ describe('EditAccountModal', () => {
|
||||
expect(updateAccountMock.mock.calls[0]?.[1]?.extra?.openai_long_context_billing_enabled).toBe(false)
|
||||
})
|
||||
|
||||
it('loads and clears the OAuth-only Codex namespace flatten toggle', async () => {
|
||||
const account = buildAccount()
|
||||
account.type = 'oauth'
|
||||
account.extra = {
|
||||
openai_responses_flatten_namespaces: true
|
||||
}
|
||||
updateAccountMock.mockReset()
|
||||
checkMixedChannelRiskMock.mockReset()
|
||||
checkMixedChannelRiskMock.mockResolvedValue({ has_risk: false })
|
||||
updateAccountMock.mockResolvedValue(account)
|
||||
|
||||
const wrapper = mountModal(account)
|
||||
const toggle = wrapper.get('[data-testid="edit-openai-flatten-namespaces-toggle"]')
|
||||
|
||||
// 关闭后应从 extra 中删除该键,而不是写入 false
|
||||
await toggle.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(
|
||||
'openai_responses_flatten_namespaces'
|
||||
)
|
||||
})
|
||||
|
||||
it('submits the Codex namespace flatten toggle when switched on', async () => {
|
||||
const account = buildAccount()
|
||||
account.type = 'oauth'
|
||||
updateAccountMock.mockReset()
|
||||
checkMixedChannelRiskMock.mockReset()
|
||||
checkMixedChannelRiskMock.mockResolvedValue({ has_risk: false })
|
||||
updateAccountMock.mockResolvedValue(account)
|
||||
|
||||
const wrapper = mountModal(account)
|
||||
await wrapper.get('[data-testid="edit-openai-flatten-namespaces-toggle"]').trigger('click')
|
||||
await wrapper.get('form#edit-account-form').trigger('submit.prevent')
|
||||
|
||||
expect(updateAccountMock).toHaveBeenCalledTimes(1)
|
||||
expect(updateAccountMock.mock.calls[0]?.[1]?.extra?.openai_responses_flatten_namespaces).toBe(
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
it('hides the Codex namespace flatten toggle for non-OAuth OpenAI accounts', async () => {
|
||||
const account = buildAccount()
|
||||
const wrapper = mountModal(account)
|
||||
|
||||
expect(wrapper.find('[data-testid="edit-openai-flatten-namespaces-toggle"]').exists()).toBe(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
it('defaults legacy OpenAI accounts to long-context billing disabled', async () => {
|
||||
const account = buildAccount()
|
||||
updateAccountMock.mockReset()
|
||||
|
||||
@@ -489,6 +489,9 @@ export default {
|
||||
oauthPassthrough: 'Auto passthrough (auth only)',
|
||||
oauthPassthroughDesc:
|
||||
'When enabled, this OpenAI account uses automatic passthrough: the gateway forwards request/response as-is and only swaps auth, while keeping billing/concurrency/audit and necessary safety filtering.',
|
||||
flattenNamespaces: 'Flatten Codex namespace tools (compatibility)',
|
||||
flattenNamespacesDesc:
|
||||
'Disabled by default: Codex namespace tool declarations are forwarded as-is on /responses, which is what the ChatGPT Codex backend expects. Enable only when this OAuth account is routed to a relay that rejects namespace tools — flattening renames them to namespace__tool, which breaks models that address collaboration tools as functions.<namespace>.<tool>. Compaction requests always flatten regardless of this switch.',
|
||||
longContextBilling: 'API long-context pricing',
|
||||
longContextBillingDesc:
|
||||
'Disabled by default. Enable only when this account\'s upstream charges OpenAI API long-context rates above the model threshold.',
|
||||
|
||||
@@ -556,6 +556,9 @@ export default {
|
||||
oauthPassthrough: '自动透传(仅替换认证)',
|
||||
oauthPassthroughDesc:
|
||||
'开启后,该 OpenAI 账号将自动透传请求与响应,仅替换认证并保留计费/并发/审计及必要安全过滤;如遇兼容性问题可随时关闭回滚。',
|
||||
flattenNamespaces: '摊平 Codex namespace 工具(兼容)',
|
||||
flattenNamespacesDesc:
|
||||
'默认关闭:/responses 上的 namespace 工具声明原样转发,这正是 ChatGPT Codex 后端期望的形态。仅当该 OAuth 账号指向不认识 namespace 的兼容上游时才开启——摊平会把工具改名为 namespace__tool,使按 functions.<命名空间>.<工具> 寻址的模型(如 gpt-5.6 多智能体)无法调用。压缩(compact)请求不受该开关影响,始终摊平。',
|
||||
longContextBilling: 'API 长上下文计费',
|
||||
longContextBillingDesc: '默认关闭。仅当该账号的上游会按模型阈值收取 OpenAI API 长上下文费率时开启。',
|
||||
responsesWebsocketsV2: 'Responses WebSocket v2',
|
||||
|
||||
Reference in New Issue
Block a user