mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
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:
@@ -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 =>
|
||||
|
||||
Reference in New Issue
Block a user