"frontend/src/vscode:/vscode.git/clone" did not exist on "d3062b2e46e59dcc0abc6a0757e82b731488a195"
Unverified Commit 7076717b authored by Wesley Liddick's avatar Wesley Liddick Committed by GitHub
Browse files

Merge pull request #772 from mt21625457/aicodex2api-main

feat(openai-ws): 合并 WS v2 透传模式与前端 ws mode
parents 33988637 c0a4fcea
...@@ -708,7 +708,7 @@ ...@@ -708,7 +708,7 @@
</div> </div>
</div> </div>
<!-- OpenAI WS Mode 三态off/shared/dedicated --> <!-- OpenAI WS Mode 三态off/ctx_pool/passthrough -->
<div <div
v-if="account?.platform === 'openai' && (account?.type === 'oauth' || account?.type === 'apikey')" v-if="account?.platform === 'openai' && (account?.type === 'oauth' || account?.type === 'apikey')"
class="border-t border-gray-200 pt-4 dark:border-dark-600" class="border-t border-gray-200 pt-4 dark:border-dark-600"
...@@ -720,7 +720,7 @@ ...@@ -720,7 +720,7 @@
{{ t('admin.accounts.openai.wsModeDesc') }} {{ t('admin.accounts.openai.wsModeDesc') }}
</p> </p>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400"> <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.accounts.openai.wsModeConcurrencyHint') }} {{ t(openAIWSModeConcurrencyHintKey) }}
</p> </p>
</div> </div>
<div class="w-52"> <div class="w-52">
...@@ -1273,10 +1273,11 @@ import { applyInterceptWarmup } from '@/components/account/credentialsBuilder' ...@@ -1273,10 +1273,11 @@ import { applyInterceptWarmup } from '@/components/account/credentialsBuilder'
import { formatDateTimeLocalInput, parseDateTimeLocalInput } from '@/utils/format' import { formatDateTimeLocalInput, parseDateTimeLocalInput } from '@/utils/format'
import { createStableObjectKeyResolver } from '@/utils/stableObjectKey' import { createStableObjectKeyResolver } from '@/utils/stableObjectKey'
import { import {
OPENAI_WS_MODE_DEDICATED, OPENAI_WS_MODE_CTX_POOL,
OPENAI_WS_MODE_OFF, OPENAI_WS_MODE_OFF,
OPENAI_WS_MODE_SHARED, OPENAI_WS_MODE_PASSTHROUGH,
isOpenAIWSModeEnabled, isOpenAIWSModeEnabled,
resolveOpenAIWSModeConcurrencyHintKey,
type OpenAIWSMode, type OpenAIWSMode,
resolveOpenAIWSModeFromExtra resolveOpenAIWSModeFromExtra
} from '@/utils/openaiWsMode' } from '@/utils/openaiWsMode'
...@@ -1387,8 +1388,8 @@ const codexCLIOnlyEnabled = ref(false) ...@@ -1387,8 +1388,8 @@ const codexCLIOnlyEnabled = ref(false)
const anthropicPassthroughEnabled = ref(false) const anthropicPassthroughEnabled = ref(false)
const openAIWSModeOptions = computed(() => [ const openAIWSModeOptions = computed(() => [
{ value: OPENAI_WS_MODE_OFF, label: t('admin.accounts.openai.wsModeOff') }, { value: OPENAI_WS_MODE_OFF, label: t('admin.accounts.openai.wsModeOff') },
{ value: OPENAI_WS_MODE_SHARED, label: t('admin.accounts.openai.wsModeShared') }, { value: OPENAI_WS_MODE_CTX_POOL, label: t('admin.accounts.openai.wsModeCtxPool') },
{ value: OPENAI_WS_MODE_DEDICATED, label: t('admin.accounts.openai.wsModeDedicated') } { value: OPENAI_WS_MODE_PASSTHROUGH, label: t('admin.accounts.openai.wsModePassthrough') }
]) ])
const openaiResponsesWebSocketV2Mode = computed({ const openaiResponsesWebSocketV2Mode = computed({
get: () => { get: () => {
...@@ -1405,6 +1406,9 @@ const openaiResponsesWebSocketV2Mode = computed({ ...@@ -1405,6 +1406,9 @@ const openaiResponsesWebSocketV2Mode = computed({
openaiOAuthResponsesWebSocketV2Mode.value = mode openaiOAuthResponsesWebSocketV2Mode.value = mode
} }
}) })
const openAIWSModeConcurrencyHintKey = computed(() =>
resolveOpenAIWSModeConcurrencyHintKey(openaiResponsesWebSocketV2Mode.value)
)
const isOpenAIModelRestrictionDisabled = computed(() => const isOpenAIModelRestrictionDisabled = computed(() =>
props.account?.platform === 'openai' && openaiPassthroughEnabled.value props.account?.platform === 'openai' && openaiPassthroughEnabled.value
) )
...@@ -2248,10 +2252,13 @@ const handleSubmit = async () => { ...@@ -2248,10 +2252,13 @@ const handleSubmit = async () => {
const currentExtra = (props.account.extra as Record<string, unknown>) || {} const currentExtra = (props.account.extra as Record<string, unknown>) || {}
const newExtra: Record<string, unknown> = { ...currentExtra } const newExtra: Record<string, unknown> = { ...currentExtra }
const hadCodexCLIOnlyEnabled = currentExtra.codex_cli_only === true const hadCodexCLIOnlyEnabled = currentExtra.codex_cli_only === true
newExtra.openai_oauth_responses_websockets_v2_mode = openaiOAuthResponsesWebSocketV2Mode.value if (props.account.type === 'oauth') {
newExtra.openai_apikey_responses_websockets_v2_mode = openaiAPIKeyResponsesWebSocketV2Mode.value newExtra.openai_oauth_responses_websockets_v2_mode = openaiOAuthResponsesWebSocketV2Mode.value
newExtra.openai_oauth_responses_websockets_v2_enabled = isOpenAIWSModeEnabled(openaiOAuthResponsesWebSocketV2Mode.value) newExtra.openai_oauth_responses_websockets_v2_enabled = isOpenAIWSModeEnabled(openaiOAuthResponsesWebSocketV2Mode.value)
newExtra.openai_apikey_responses_websockets_v2_enabled = isOpenAIWSModeEnabled(openaiAPIKeyResponsesWebSocketV2Mode.value) } else if (props.account.type === 'apikey') {
newExtra.openai_apikey_responses_websockets_v2_mode = openaiAPIKeyResponsesWebSocketV2Mode.value
newExtra.openai_apikey_responses_websockets_v2_enabled = isOpenAIWSModeEnabled(openaiAPIKeyResponsesWebSocketV2Mode.value)
}
delete newExtra.responses_websockets_v2_enabled delete newExtra.responses_websockets_v2_enabled
delete newExtra.openai_ws_enabled delete newExtra.openai_ws_enabled
if (openaiPassthroughEnabled.value) { if (openaiPassthroughEnabled.value) {
......
...@@ -1846,10 +1846,13 @@ export default { ...@@ -1846,10 +1846,13 @@ export default {
wsMode: 'WS mode', wsMode: 'WS mode',
wsModeDesc: 'Only applies to the current OpenAI account type.', wsModeDesc: 'Only applies to the current OpenAI account type.',
wsModeOff: 'Off (off)', wsModeOff: 'Off (off)',
wsModeCtxPool: 'Context Pool (ctx_pool)',
wsModePassthrough: 'Passthrough (passthrough)',
wsModeShared: 'Shared (shared)', wsModeShared: 'Shared (shared)',
wsModeDedicated: 'Dedicated (dedicated)', wsModeDedicated: 'Dedicated (dedicated)',
wsModeConcurrencyHint: wsModeConcurrencyHint:
'When WS mode is enabled, account concurrency becomes the WS connection pool limit for this account.', 'When WS mode is enabled, account concurrency becomes the WS connection pool limit for this account.',
wsModePassthroughHint: 'Passthrough mode does not use the WS connection pool.',
oauthResponsesWebsocketsV2: 'OAuth WebSocket Mode', oauthResponsesWebsocketsV2: 'OAuth WebSocket Mode',
oauthResponsesWebsocketsV2Desc: oauthResponsesWebsocketsV2Desc:
'Only applies to OpenAI OAuth. This account can use OpenAI WebSocket Mode only when enabled.', 'Only applies to OpenAI OAuth. This account can use OpenAI WebSocket Mode only when enabled.',
......
...@@ -1994,9 +1994,12 @@ export default { ...@@ -1994,9 +1994,12 @@ export default {
wsMode: 'WS mode', wsMode: 'WS mode',
wsModeDesc: '仅对当前 OpenAI 账号类型生效。', wsModeDesc: '仅对当前 OpenAI 账号类型生效。',
wsModeOff: '关闭(off)', wsModeOff: '关闭(off)',
wsModeCtxPool: '上下文池(ctx_pool)',
wsModePassthrough: '透传(passthrough)',
wsModeShared: '共享(shared)', wsModeShared: '共享(shared)',
wsModeDedicated: '独享(dedicated)', wsModeDedicated: '独享(dedicated)',
wsModeConcurrencyHint: '启用 WS mode 后,该账号并发数将作为该账号 WS 连接池上限。', wsModeConcurrencyHint: '启用 WS mode 后,该账号并发数将作为该账号 WS 连接池上限。',
wsModePassthroughHint: 'passthrough 模式不使用 WS 连接池。',
oauthResponsesWebsocketsV2: 'OAuth WebSocket Mode', oauthResponsesWebsocketsV2: 'OAuth WebSocket Mode',
oauthResponsesWebsocketsV2Desc: oauthResponsesWebsocketsV2Desc:
'仅对 OpenAI OAuth 生效。开启后该账号才允许使用 OpenAI WebSocket Mode 协议。', '仅对 OpenAI OAuth 生效。开启后该账号才允许使用 OpenAI WebSocket Mode 协议。',
......
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import { import {
OPENAI_WS_MODE_DEDICATED, OPENAI_WS_MODE_CTX_POOL,
OPENAI_WS_MODE_OFF, OPENAI_WS_MODE_OFF,
OPENAI_WS_MODE_SHARED, OPENAI_WS_MODE_PASSTHROUGH,
isOpenAIWSModeEnabled, isOpenAIWSModeEnabled,
normalizeOpenAIWSMode, normalizeOpenAIWSMode,
openAIWSModeFromEnabled, openAIWSModeFromEnabled,
resolveOpenAIWSModeConcurrencyHintKey,
resolveOpenAIWSModeFromExtra resolveOpenAIWSModeFromExtra
} from '@/utils/openaiWsMode' } from '@/utils/openaiWsMode'
describe('openaiWsMode utils', () => { describe('openaiWsMode utils', () => {
it('normalizes mode values', () => { it('normalizes mode values', () => {
expect(normalizeOpenAIWSMode('off')).toBe(OPENAI_WS_MODE_OFF) expect(normalizeOpenAIWSMode('off')).toBe(OPENAI_WS_MODE_OFF)
expect(normalizeOpenAIWSMode(' Shared ')).toBe(OPENAI_WS_MODE_SHARED) expect(normalizeOpenAIWSMode('ctx_pool')).toBe(OPENAI_WS_MODE_CTX_POOL)
expect(normalizeOpenAIWSMode('DEDICATED')).toBe(OPENAI_WS_MODE_DEDICATED) expect(normalizeOpenAIWSMode('passthrough')).toBe(OPENAI_WS_MODE_PASSTHROUGH)
expect(normalizeOpenAIWSMode(' Shared ')).toBe(OPENAI_WS_MODE_CTX_POOL)
expect(normalizeOpenAIWSMode('DEDICATED')).toBe(OPENAI_WS_MODE_CTX_POOL)
expect(normalizeOpenAIWSMode('invalid')).toBeNull() expect(normalizeOpenAIWSMode('invalid')).toBeNull()
}) })
it('maps legacy enabled flag to mode', () => { it('maps legacy enabled flag to mode', () => {
expect(openAIWSModeFromEnabled(true)).toBe(OPENAI_WS_MODE_SHARED) expect(openAIWSModeFromEnabled(true)).toBe(OPENAI_WS_MODE_CTX_POOL)
expect(openAIWSModeFromEnabled(false)).toBe(OPENAI_WS_MODE_OFF) expect(openAIWSModeFromEnabled(false)).toBe(OPENAI_WS_MODE_OFF)
expect(openAIWSModeFromEnabled('true')).toBeNull() expect(openAIWSModeFromEnabled('true')).toBeNull()
}) })
it('resolves by mode key first, then enabled, then fallback enabled keys', () => { it('resolves by mode key first, then enabled, then fallback enabled keys', () => {
const extra = { const extra = {
openai_oauth_responses_websockets_v2_mode: 'dedicated', openai_oauth_responses_websockets_v2_mode: 'passthrough',
openai_oauth_responses_websockets_v2_enabled: false, openai_oauth_responses_websockets_v2_enabled: false,
responses_websockets_v2_enabled: false responses_websockets_v2_enabled: false
} }
...@@ -34,7 +37,7 @@ describe('openaiWsMode utils', () => { ...@@ -34,7 +37,7 @@ describe('openaiWsMode utils', () => {
enabledKey: 'openai_oauth_responses_websockets_v2_enabled', enabledKey: 'openai_oauth_responses_websockets_v2_enabled',
fallbackEnabledKeys: ['responses_websockets_v2_enabled', 'openai_ws_enabled'] fallbackEnabledKeys: ['responses_websockets_v2_enabled', 'openai_ws_enabled']
}) })
expect(mode).toBe(OPENAI_WS_MODE_DEDICATED) expect(mode).toBe(OPENAI_WS_MODE_PASSTHROUGH)
}) })
it('falls back to default when nothing is present', () => { it('falls back to default when nothing is present', () => {
...@@ -47,9 +50,21 @@ describe('openaiWsMode utils', () => { ...@@ -47,9 +50,21 @@ describe('openaiWsMode utils', () => {
expect(mode).toBe(OPENAI_WS_MODE_OFF) expect(mode).toBe(OPENAI_WS_MODE_OFF)
}) })
it('treats off as disabled and shared/dedicated as enabled', () => { it('treats off as disabled and non-off modes as enabled', () => {
expect(isOpenAIWSModeEnabled(OPENAI_WS_MODE_OFF)).toBe(false) expect(isOpenAIWSModeEnabled(OPENAI_WS_MODE_OFF)).toBe(false)
expect(isOpenAIWSModeEnabled(OPENAI_WS_MODE_SHARED)).toBe(true) expect(isOpenAIWSModeEnabled(OPENAI_WS_MODE_CTX_POOL)).toBe(true)
expect(isOpenAIWSModeEnabled(OPENAI_WS_MODE_DEDICATED)).toBe(true) expect(isOpenAIWSModeEnabled(OPENAI_WS_MODE_PASSTHROUGH)).toBe(true)
})
it('resolves concurrency hint key by mode', () => {
expect(resolveOpenAIWSModeConcurrencyHintKey(OPENAI_WS_MODE_OFF)).toBe(
'admin.accounts.openai.wsModeConcurrencyHint'
)
expect(resolveOpenAIWSModeConcurrencyHintKey(OPENAI_WS_MODE_CTX_POOL)).toBe(
'admin.accounts.openai.wsModeConcurrencyHint'
)
expect(resolveOpenAIWSModeConcurrencyHintKey(OPENAI_WS_MODE_PASSTHROUGH)).toBe(
'admin.accounts.openai.wsModePassthroughHint'
)
}) })
}) })
export const OPENAI_WS_MODE_OFF = 'off' export const OPENAI_WS_MODE_OFF = 'off'
export const OPENAI_WS_MODE_SHARED = 'shared' export const OPENAI_WS_MODE_CTX_POOL = 'ctx_pool'
export const OPENAI_WS_MODE_DEDICATED = 'dedicated' export const OPENAI_WS_MODE_PASSTHROUGH = 'passthrough'
export type OpenAIWSMode = export type OpenAIWSMode =
| typeof OPENAI_WS_MODE_OFF | typeof OPENAI_WS_MODE_OFF
| typeof OPENAI_WS_MODE_SHARED | typeof OPENAI_WS_MODE_CTX_POOL
| typeof OPENAI_WS_MODE_DEDICATED | typeof OPENAI_WS_MODE_PASSTHROUGH
const OPENAI_WS_MODES = new Set<OpenAIWSMode>([ const OPENAI_WS_MODES = new Set<OpenAIWSMode>([
OPENAI_WS_MODE_OFF, OPENAI_WS_MODE_OFF,
OPENAI_WS_MODE_SHARED, OPENAI_WS_MODE_CTX_POOL,
OPENAI_WS_MODE_DEDICATED OPENAI_WS_MODE_PASSTHROUGH
]) ])
export interface ResolveOpenAIWSModeOptions { export interface ResolveOpenAIWSModeOptions {
...@@ -23,6 +23,9 @@ export interface ResolveOpenAIWSModeOptions { ...@@ -23,6 +23,9 @@ export interface ResolveOpenAIWSModeOptions {
export const normalizeOpenAIWSMode = (mode: unknown): OpenAIWSMode | null => { export const normalizeOpenAIWSMode = (mode: unknown): OpenAIWSMode | null => {
if (typeof mode !== 'string') return null if (typeof mode !== 'string') return null
const normalized = mode.trim().toLowerCase() const normalized = mode.trim().toLowerCase()
if (normalized === 'shared' || normalized === 'dedicated') {
return OPENAI_WS_MODE_CTX_POOL
}
if (OPENAI_WS_MODES.has(normalized as OpenAIWSMode)) { if (OPENAI_WS_MODES.has(normalized as OpenAIWSMode)) {
return normalized as OpenAIWSMode return normalized as OpenAIWSMode
} }
...@@ -31,13 +34,22 @@ export const normalizeOpenAIWSMode = (mode: unknown): OpenAIWSMode | null => { ...@@ -31,13 +34,22 @@ export const normalizeOpenAIWSMode = (mode: unknown): OpenAIWSMode | null => {
export const openAIWSModeFromEnabled = (enabled: unknown): OpenAIWSMode | null => { export const openAIWSModeFromEnabled = (enabled: unknown): OpenAIWSMode | null => {
if (typeof enabled !== 'boolean') return null if (typeof enabled !== 'boolean') return null
return enabled ? OPENAI_WS_MODE_SHARED : OPENAI_WS_MODE_OFF return enabled ? OPENAI_WS_MODE_CTX_POOL : OPENAI_WS_MODE_OFF
} }
export const isOpenAIWSModeEnabled = (mode: OpenAIWSMode): boolean => { export const isOpenAIWSModeEnabled = (mode: OpenAIWSMode): boolean => {
return mode !== OPENAI_WS_MODE_OFF return mode !== OPENAI_WS_MODE_OFF
} }
export const resolveOpenAIWSModeConcurrencyHintKey = (
mode: OpenAIWSMode
): 'admin.accounts.openai.wsModeConcurrencyHint' | 'admin.accounts.openai.wsModePassthroughHint' => {
if (mode === OPENAI_WS_MODE_PASSTHROUGH) {
return 'admin.accounts.openai.wsModePassthroughHint'
}
return 'admin.accounts.openai.wsModeConcurrencyHint'
}
export const resolveOpenAIWSModeFromExtra = ( export const resolveOpenAIWSModeFromExtra = (
extra: Record<string, unknown> | null | undefined, extra: Record<string, unknown> | null | undefined,
options: ResolveOpenAIWSModeOptions options: ResolveOpenAIWSModeOptions
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment