mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
fix(usage): 修复含失败信号的 pending 请求被误判为活跃的问题
- pending/streaming 状态含 status_code >= 400 或 error_message 时视为 failed - 候选状态派生改为 failed 优先于 stale pending/streaming - 活跃请求轮询时保留失败信号字段的更新 - 图片流支持 partial_image 事件转发及从 response.completed 读取最终图片
This commit is contained in:
@@ -79,6 +79,17 @@ describe('usage status helpers', () => {
|
||||
}))).toBe('streaming')
|
||||
})
|
||||
|
||||
it('treats active lifecycle records with failure signals as failed for display', () => {
|
||||
const record = buildUsageRecord({
|
||||
status: 'pending',
|
||||
status_code: 503,
|
||||
error_message: 'upstream failed',
|
||||
})
|
||||
|
||||
expect(resolveDisplayRequestStatus(record)).toBe('failed')
|
||||
expect(isUsageRecordFailed(record)).toBe(true)
|
||||
})
|
||||
|
||||
it('treats explicit success status code as authoritative for the timeline', () => {
|
||||
expect(resolveTimelineFinalStatus({
|
||||
traceFinalStatus: 'success',
|
||||
@@ -93,6 +104,13 @@ describe('usage status helpers', () => {
|
||||
})).toBe('failed')
|
||||
})
|
||||
|
||||
it('does not let stale pending candidates override terminal request status', () => {
|
||||
expect(resolveTimelineFinalStatus({
|
||||
hasPendingCandidates: true,
|
||||
requestStatus: 'failed',
|
||||
})).toBe('failed')
|
||||
})
|
||||
|
||||
it('uses explicit has_fallback flag for transfer filtering', () => {
|
||||
expect(hasUsageFallback(buildUsageRecord({ has_fallback: true }))).toBe(true)
|
||||
expect(hasUsageFallback(buildUsageRecord({ has_fallback: false }))).toBe(false)
|
||||
|
||||
@@ -4,8 +4,18 @@ export type TimelineFinalStatus = 'success' | 'failed' | 'streaming' | 'pending'
|
||||
|
||||
type RequestStatusLike = RequestStatus | string | null | undefined
|
||||
|
||||
type UsageFailureSignal = {
|
||||
status_code?: number | null
|
||||
error_message?: string | null
|
||||
}
|
||||
|
||||
type UsageDisplayStatusRecord = UsageFailureSignal & {
|
||||
status?: RequestStatusLike
|
||||
first_byte_time_ms?: number | null
|
||||
}
|
||||
|
||||
function hasLegacyFailureSignal(
|
||||
record: Pick<UsageRecord, 'status_code' | 'error_message'>
|
||||
record: UsageFailureSignal
|
||||
): boolean {
|
||||
return (typeof record.status_code === 'number' && record.status_code >= 400) ||
|
||||
(typeof record.error_message === 'string' && record.error_message.trim().length > 0)
|
||||
@@ -138,7 +148,7 @@ function usageApiFormatDefaultsToNonStream(apiFormat: string): boolean {
|
||||
}
|
||||
|
||||
function hasTerminalSuccessStatusCode(
|
||||
record: Pick<UsageRecord, 'status_code'>
|
||||
record: UsageFailureSignal
|
||||
): boolean {
|
||||
return typeof record.status_code === 'number' &&
|
||||
record.status_code >= 200 &&
|
||||
@@ -150,7 +160,10 @@ export function isUsageRecordFailed(
|
||||
): boolean {
|
||||
const status = typeof record.status === 'string' ? record.status.trim().toLowerCase() : ''
|
||||
if (status) {
|
||||
if (status === 'pending' || status === 'streaming' || status === 'cancelled') {
|
||||
if (status === 'pending' || status === 'streaming') {
|
||||
return !hasTerminalSuccessStatusCode(record) && hasLegacyFailureSignal(record)
|
||||
}
|
||||
if (status === 'cancelled') {
|
||||
return false
|
||||
}
|
||||
if (status === 'completed') {
|
||||
@@ -202,10 +215,13 @@ export function normalizeRequestStatus(status: RequestStatusLike): RequestStatus
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDisplayRequestStatus(
|
||||
record: Pick<UsageRecord, 'status' | 'first_byte_time_ms'>
|
||||
): RequestStatus | undefined {
|
||||
export function resolveDisplayRequestStatus(record: UsageDisplayStatusRecord): RequestStatus | undefined {
|
||||
const status = normalizeRequestStatus(record.status)
|
||||
if ((status === 'pending' || status === 'streaming') &&
|
||||
!hasTerminalSuccessStatusCode(record) &&
|
||||
hasLegacyFailureSignal(record)) {
|
||||
return 'failed'
|
||||
}
|
||||
if (status === 'streaming' && record.first_byte_time_ms == null) {
|
||||
return 'pending'
|
||||
}
|
||||
@@ -251,20 +267,28 @@ export function resolveTimelineFinalStatus(params: {
|
||||
requestStatus?: RequestStatusLike
|
||||
statusCode?: number
|
||||
}): TimelineFinalStatus {
|
||||
if (params.hasPendingCandidates) {
|
||||
return 'pending'
|
||||
}
|
||||
|
||||
if (typeof params.statusCode === 'number') {
|
||||
return params.statusCode >= 200 && params.statusCode < 400 ? 'success' : 'failed'
|
||||
}
|
||||
|
||||
const traceStatus = normalizeTimelineFinalStatus(params.traceFinalStatus)
|
||||
if (traceStatus) {
|
||||
if (traceStatus === 'success' || traceStatus === 'failed' || traceStatus === 'cancelled') {
|
||||
return traceStatus
|
||||
}
|
||||
|
||||
const requestStatus = mapRequestStatusToTimelineStatus(params.requestStatus)
|
||||
if (requestStatus === 'success' || requestStatus === 'failed' || requestStatus === 'cancelled') {
|
||||
return requestStatus
|
||||
}
|
||||
|
||||
if (params.hasPendingCandidates) {
|
||||
return 'pending'
|
||||
}
|
||||
|
||||
if (traceStatus) {
|
||||
return traceStatus
|
||||
}
|
||||
|
||||
if (requestStatus) {
|
||||
return requestStatus
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user