feat(gateway): add Codex Live and OpenAI Realtime

Implement preflighted Live/Realtime WebSocket transports, protocol-aware authentication, usage auditing, UI filtering, and legacy Codex permission migration.
This commit is contained in:
ZheFox
2026-08-21 04:27:34 +08:00
parent fe38dcd294
commit 2c89202001
105 changed files with 7553 additions and 947 deletions
@@ -121,6 +121,39 @@ describe('useUsageData', () => {
expect(totalRecords.value).toBe(1)
})
it('sends the WebSocket type filter to the admin records endpoint', async () => {
const isAdminPage = ref(true)
const { loadRecords } = useUsageData({ isAdminPage })
await loadRecords(
{ page: 1, pageSize: 20 },
{ status: 'websocket' },
{ preset: 'today', tz_offset_minutes: 0 },
)
expect(getAllUsageRecordsMock).toHaveBeenCalledWith(expect.objectContaining({
status: 'websocket',
include_total: false,
}))
})
it('sends the WebSocket type filter to the user records endpoint before pagination', async () => {
const isAdminPage = ref(false)
const { loadRecords } = useUsageData({ isAdminPage })
await loadRecords(
{ page: 2, pageSize: 20 },
{ status: 'websocket' },
{ preset: 'today', tz_offset_minutes: 0 },
)
expect(meGetUsageMock).toHaveBeenCalledWith(expect.objectContaining({
status: 'websocket',
limit: 20,
offset: 20,
}))
})
it('keeps locally resolved failure fields when a stale active record refreshes', async () => {
const isAdminPage = ref(true)
const { loadRecords, currentRecords } = useUsageData({ isAdminPage })
@@ -0,0 +1,59 @@
import { describe, expect, it } from 'vitest'
import { ref } from 'vue'
import type { UsageRecord } from '../../types'
import { useUsageFilters } from '../useUsageFilters'
function buildRecord(
id: string,
isWebSocket: boolean,
isStream = true,
): UsageRecord {
return {
id,
model: 'gpt-5',
input_tokens: 1,
output_tokens: 1,
total_tokens: 2,
cost: 0.01,
is_stream: isStream,
is_websocket: isWebSocket,
created_at: '2026-08-21T00:00:00Z',
}
}
describe('useUsageFilters', () => {
it('filters WebSocket records independently from streaming HTTP records', () => {
const records = ref([
buildRecord('responses-websocket', true),
buildRecord('streaming-http', false),
])
const filters = useUsageFilters({ allRecords: records })
filters.handleFilterStatusChange('websocket')
expect(filters.filteredRecords.value.map(record => record.id)).toEqual([
'responses-websocket',
])
})
it('keeps WebSocket records out of standard and streaming HTTP filters', () => {
const records = ref([
buildRecord('responses-websocket', true, true),
buildRecord('realtime-websocket', true, false),
buildRecord('streaming-http', false, true),
buildRecord('standard-http', false, false),
])
const filters = useUsageFilters({ allRecords: records })
filters.handleFilterStatusChange('stream')
expect(filters.filteredRecords.value.map(record => record.id)).toEqual([
'streaming-http',
])
filters.handleFilterStatusChange('standard')
expect(filters.filteredRecords.value.map(record => record.id)).toEqual([
'standard-http',
])
})
})
@@ -390,6 +390,9 @@ export function useUsageData(options: UseUsageDataOptions) {
if (filters?.search?.trim()) {
params.search = filters.search.trim()
}
if (filters?.status) {
params.status = filters.status
}
if (isAdminPage.value) {
// 管理员页面:使用管理员 API
@@ -405,9 +408,6 @@ export function useUsageData(options: UseUsageDataOptions) {
if (filters?.api_format) {
params.api_format = filters.api_format
}
if (filters?.status) {
params.status = filters.status
}
if (filters?.client_family) {
params.client_family = filters.client_family
}
@@ -648,6 +648,22 @@ export function useUsageData(options: UseUsageDataOptions) {
: existing.image_progress,
is_stream: upstreamIsStream,
is_websocket: mergeBooleanTrueWins(existing.is_websocket, record.is_websocket),
websocket_transport: record.websocket_transport || existing.websocket_transport,
usage_available: existing.usage_available === false || record.usage_available === false
? false
: (record.usage_available ?? existing.usage_available),
usage_pricing_available: existing.usage_pricing_available === false
|| record.usage_pricing_available === false
? false
: (record.usage_pricing_available ?? existing.usage_pricing_available),
input_audio_tokens: mergeSparseRecordMetric(
existing.input_audio_tokens,
record.input_audio_tokens,
) ?? record.input_audio_tokens,
output_audio_tokens: mergeSparseRecordMetric(
existing.output_audio_tokens,
record.output_audio_tokens,
) ?? record.output_audio_tokens,
upstream_is_stream: upstreamIsStream,
client_requested_stream: clientRequestedStream,
client_is_stream: clientIsStream,
@@ -5,6 +5,7 @@ import {
hasUsageRetry,
isUsageRecordFailed,
isUsageUpstreamStream,
isUsageWebSocket,
resolveDisplayRequestStatus,
} from '../utils/status'
@@ -69,13 +70,19 @@ export function useUsageFilters(options: UseUsageFiltersOptions) {
}
if (filterStatus.value !== '__all__') {
if (filterStatus.value === 'stream') {
if (filterStatus.value === 'websocket') {
records = records.filter(record => isUsageWebSocket(record))
} else if (filterStatus.value === 'stream') {
records = records.filter(record =>
isUsageUpstreamStream(record) && !isUsageRecordFailed(record)
isUsageUpstreamStream(record)
&& !isUsageWebSocket(record)
&& !isUsageRecordFailed(record)
)
} else if (filterStatus.value === 'standard') {
records = records.filter(record =>
!isUsageUpstreamStream(record) && !isUsageRecordFailed(record)
!isUsageUpstreamStream(record)
&& !isUsageWebSocket(record)
&& !isUsageRecordFailed(record)
)
} else if (filterStatus.value === 'active') {
records = records.filter(record =>