mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-04 08:27:46 +08:00
fix: preserve provider auth metadata
This commit is contained in:
@@ -931,7 +931,28 @@ const emit = defineEmits<{
|
||||
requestId?: string | null
|
||||
status?: RequestStateStatus
|
||||
statusCode?: number | null
|
||||
inputTokens?: number | null
|
||||
effectiveInputTokens?: number | null
|
||||
outputTokens?: number | null
|
||||
totalTokens?: number | null
|
||||
cacheCreationInputTokens?: number | null
|
||||
cacheCreationEphemeral5mInputTokens?: number | null
|
||||
cacheCreationEphemeral1hInputTokens?: number | null
|
||||
cacheReadInputTokens?: number | null
|
||||
cost?: number | null
|
||||
actualCost?: number | null
|
||||
responseTimeMs?: number | null
|
||||
firstByteTimeMs?: number | null
|
||||
isStream?: boolean | null
|
||||
upstreamIsStream?: boolean | null
|
||||
clientRequestedStream?: boolean | null
|
||||
clientIsStream?: boolean | null
|
||||
apiFormat?: string | null
|
||||
endpointApiFormat?: string | null
|
||||
hasFormatConversion?: boolean | null
|
||||
targetModel?: string | null
|
||||
reasoningEffort?: string | null
|
||||
serviceTier?: string | null
|
||||
imageProgress?: ImageProgress | null
|
||||
errorMessage?: string | null
|
||||
}]
|
||||
@@ -1092,6 +1113,28 @@ function resolveRequestStateStatusFromDetail(nextDetail: Pick<RequestDetail, 'st
|
||||
return resolveRequestStateStatus(nextDetail.status, nextDetail.status_code, nextDetail.error_message)
|
||||
}
|
||||
|
||||
function detailTotalCost(nextDetail: RequestDetail): number | null {
|
||||
const structuredCost = typeof nextDetail.cost === 'object' ? nextDetail.cost?.total : null
|
||||
const totalCost = toNumber(nextDetail.total_cost)
|
||||
const legacyCost = toNumber(nextDetail.cost)
|
||||
return [structuredCost, totalCost, legacyCost]
|
||||
.map(value => toNumber(value))
|
||||
.find((value): value is number => value != null && value > 0)
|
||||
?? toNumber(structuredCost)
|
||||
?? totalCost
|
||||
?? legacyCost
|
||||
}
|
||||
|
||||
function detailCacheCreation5mTokens(nextDetail: RequestDetail): number | null {
|
||||
return toNumber(nextDetail.cache_creation_input_tokens_5m)
|
||||
?? toNumber(nextDetail.cache_creation_ephemeral_5m_input_tokens)
|
||||
}
|
||||
|
||||
function detailCacheCreation1hTokens(nextDetail: RequestDetail): number | null {
|
||||
return toNumber(nextDetail.cache_creation_input_tokens_1h)
|
||||
?? toNumber(nextDetail.cache_creation_ephemeral_1h_input_tokens)
|
||||
}
|
||||
|
||||
function emitDetailRequestState(nextDetail: RequestDetail) {
|
||||
const id = props.requestId
|
||||
if (!id) return
|
||||
@@ -1101,7 +1144,28 @@ function emitDetailRequestState(nextDetail: RequestDetail) {
|
||||
requestId: nextDetail.request_id || nextDetail.id || null,
|
||||
status: resolveRequestStateStatusFromDetail(nextDetail),
|
||||
statusCode: nextDetail.status_code ?? undefined,
|
||||
inputTokens: nextDetail.input_tokens ?? nextDetail.tokens?.input ?? null,
|
||||
effectiveInputTokens: displayInputTokens.value,
|
||||
outputTokens: nextDetail.output_tokens ?? nextDetail.tokens?.output ?? null,
|
||||
totalTokens: nextDetail.total_tokens ?? nextDetail.tokens?.total ?? null,
|
||||
cacheCreationInputTokens: nextDetail.cache_creation_input_tokens ?? null,
|
||||
cacheCreationEphemeral5mInputTokens: detailCacheCreation5mTokens(nextDetail),
|
||||
cacheCreationEphemeral1hInputTokens: detailCacheCreation1hTokens(nextDetail),
|
||||
cacheReadInputTokens: nextDetail.cache_read_input_tokens ?? null,
|
||||
cost: detailTotalCost(nextDetail),
|
||||
actualCost: nextDetail.actual_cost ?? null,
|
||||
responseTimeMs: nextDetail.response_time_ms ?? undefined,
|
||||
firstByteTimeMs: nextDetail.first_byte_time_ms ?? null,
|
||||
isStream: nextDetail.is_stream ?? null,
|
||||
upstreamIsStream: nextDetail.upstream_is_stream ?? null,
|
||||
clientRequestedStream: nextDetail.client_requested_stream ?? null,
|
||||
clientIsStream: nextDetail.client_is_stream ?? null,
|
||||
apiFormat: nextDetail.api_format ?? null,
|
||||
endpointApiFormat: nextDetail.endpoint_api_format ?? null,
|
||||
hasFormatConversion: nextDetail.has_format_conversion ?? null,
|
||||
targetModel: nextDetail.target_model ?? null,
|
||||
reasoningEffort: nextDetail.reasoning_effort ?? null,
|
||||
serviceTier: nextDetail.service_tier ?? null,
|
||||
errorMessage: nextDetail.error_message ?? undefined,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -147,6 +147,172 @@ describe('useUsageData', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves detail-filled usage metrics when a later list refresh is still empty', async () => {
|
||||
const isAdminPage = ref(true)
|
||||
const { loadRecords, currentRecords } = useUsageData({ isAdminPage })
|
||||
const dateRange = { preset: 'today', tz_offset_minutes: 0 }
|
||||
|
||||
getAllUsageRecordsMock.mockResolvedValueOnce({
|
||||
records: [buildUsageRecord({
|
||||
status: 'completed',
|
||||
input_tokens: 0,
|
||||
effective_input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
total_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
cache_creation_ephemeral_5m_input_tokens: 0,
|
||||
cache_creation_ephemeral_1h_input_tokens: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
cost: 0,
|
||||
actual_cost: 0,
|
||||
response_time_ms: null,
|
||||
first_byte_time_ms: null,
|
||||
is_stream: false,
|
||||
upstream_is_stream: false,
|
||||
client_requested_stream: false,
|
||||
client_is_stream: false,
|
||||
})],
|
||||
total: 1,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
})
|
||||
|
||||
await loadRecords({ page: 1, pageSize: 20 }, undefined, dateRange)
|
||||
|
||||
Object.assign(currentRecords.value[0], {
|
||||
input_tokens: 1138,
|
||||
effective_input_tokens: 1138,
|
||||
output_tokens: 244,
|
||||
total_tokens: 81126,
|
||||
cache_creation_input_tokens: 17,
|
||||
cache_creation_ephemeral_5m_input_tokens: 5,
|
||||
cache_creation_ephemeral_1h_input_tokens: 12,
|
||||
cache_read_input_tokens: 79744,
|
||||
cost: 0.052882,
|
||||
actual_cost: 0.052882,
|
||||
response_time_ms: 5570,
|
||||
first_byte_time_ms: 1600,
|
||||
is_stream: true,
|
||||
upstream_is_stream: true,
|
||||
client_requested_stream: true,
|
||||
client_is_stream: true,
|
||||
api_format: 'openai:responses',
|
||||
endpoint_api_format: 'openai:responses',
|
||||
has_format_conversion: false,
|
||||
has_retry: true,
|
||||
target_model: 'gpt-5.5',
|
||||
reasoning_effort: 'xhigh',
|
||||
service_tier: 'auto',
|
||||
})
|
||||
|
||||
getAllUsageRecordsMock.mockResolvedValueOnce({
|
||||
records: [buildUsageRecord({
|
||||
status: 'completed',
|
||||
input_tokens: 0,
|
||||
effective_input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
total_tokens: 0,
|
||||
cache_creation_input_tokens: 0,
|
||||
cache_creation_ephemeral_5m_input_tokens: 0,
|
||||
cache_creation_ephemeral_1h_input_tokens: 0,
|
||||
cache_read_input_tokens: 0,
|
||||
cost: 0,
|
||||
actual_cost: 0,
|
||||
response_time_ms: null,
|
||||
first_byte_time_ms: null,
|
||||
is_stream: false,
|
||||
upstream_is_stream: false,
|
||||
client_requested_stream: false,
|
||||
client_is_stream: false,
|
||||
api_format: undefined,
|
||||
endpoint_api_format: undefined,
|
||||
has_format_conversion: undefined,
|
||||
has_retry: false,
|
||||
target_model: null,
|
||||
reasoning_effort: null,
|
||||
service_tier: null,
|
||||
})],
|
||||
total: 1,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
})
|
||||
|
||||
await loadRecords({ page: 1, pageSize: 20 }, undefined, dateRange)
|
||||
|
||||
expect(currentRecords.value[0]).toMatchObject({
|
||||
status: 'completed',
|
||||
input_tokens: 1138,
|
||||
effective_input_tokens: 1138,
|
||||
output_tokens: 244,
|
||||
total_tokens: 81126,
|
||||
cache_creation_input_tokens: 17,
|
||||
cache_creation_ephemeral_5m_input_tokens: 5,
|
||||
cache_creation_ephemeral_1h_input_tokens: 12,
|
||||
cache_read_input_tokens: 79744,
|
||||
cost: 0.052882,
|
||||
actual_cost: 0.052882,
|
||||
response_time_ms: 5570,
|
||||
first_byte_time_ms: 1600,
|
||||
is_stream: true,
|
||||
upstream_is_stream: true,
|
||||
client_requested_stream: true,
|
||||
client_is_stream: true,
|
||||
api_format: 'openai:responses',
|
||||
endpoint_api_format: 'openai:responses',
|
||||
has_format_conversion: false,
|
||||
has_retry: true,
|
||||
target_model: 'gpt-5.5',
|
||||
reasoning_effort: 'xhigh',
|
||||
service_tier: 'auto',
|
||||
})
|
||||
})
|
||||
|
||||
it('allows finalized list metrics to replace larger detail estimates', async () => {
|
||||
const isAdminPage = ref(true)
|
||||
const { loadRecords, currentRecords } = useUsageData({ isAdminPage })
|
||||
const dateRange = { preset: 'today', tz_offset_minutes: 0 }
|
||||
|
||||
getAllUsageRecordsMock.mockResolvedValueOnce({
|
||||
records: [buildUsageRecord({
|
||||
status: 'completed',
|
||||
input_tokens: 1200,
|
||||
output_tokens: 300,
|
||||
total_tokens: 1500,
|
||||
cost: 0.09,
|
||||
actual_cost: 0.09,
|
||||
})],
|
||||
total: 1,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
})
|
||||
|
||||
await loadRecords({ page: 1, pageSize: 20 }, undefined, dateRange)
|
||||
|
||||
getAllUsageRecordsMock.mockResolvedValueOnce({
|
||||
records: [buildUsageRecord({
|
||||
status: 'completed',
|
||||
input_tokens: 1100,
|
||||
output_tokens: 250,
|
||||
total_tokens: 1350,
|
||||
cost: 0.07,
|
||||
actual_cost: 0.07,
|
||||
})],
|
||||
total: 1,
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
})
|
||||
|
||||
await loadRecords({ page: 1, pageSize: 20 }, undefined, dateRange)
|
||||
|
||||
expect(currentRecords.value[0]).toMatchObject({
|
||||
input_tokens: 1100,
|
||||
output_tokens: 250,
|
||||
total_tokens: 1350,
|
||||
cost: 0.07,
|
||||
actual_cost: 0.07,
|
||||
})
|
||||
})
|
||||
|
||||
it('refreshes exact admin record totals after an estimated first page', async () => {
|
||||
const isAdminPage = ref(true)
|
||||
const { loadRecords, totalRecords } = useUsageData({ isAdminPage })
|
||||
|
||||
@@ -436,6 +436,38 @@ export function useUsageData(options: UseUsageDataOptions) {
|
||||
return existingValue ?? nextValue
|
||||
}
|
||||
|
||||
function mergeSparseRecordMetric(
|
||||
existingValue: number | null | undefined,
|
||||
nextValue: number | null | undefined
|
||||
): number | null | undefined {
|
||||
const existingIsPositive = typeof existingValue === 'number' && Number.isFinite(existingValue) && existingValue > 0
|
||||
const nextIsPositive = typeof nextValue === 'number' && Number.isFinite(nextValue) && nextValue > 0
|
||||
|
||||
if (nextIsPositive) {
|
||||
return nextValue
|
||||
}
|
||||
if (existingIsPositive) {
|
||||
return existingValue
|
||||
}
|
||||
return existingValue ?? nextValue
|
||||
}
|
||||
|
||||
function mergeBooleanTrueWins(
|
||||
existingValue: boolean | null | undefined,
|
||||
nextValue: boolean | null | undefined
|
||||
): boolean | undefined {
|
||||
if (existingValue === true || nextValue === true) {
|
||||
return true
|
||||
}
|
||||
if (typeof nextValue === 'boolean') {
|
||||
return nextValue
|
||||
}
|
||||
if (typeof existingValue === 'boolean') {
|
||||
return existingValue
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function mergeRecordStatus(
|
||||
current: UsageRecord[],
|
||||
next: UsageRecord[]
|
||||
@@ -466,104 +498,94 @@ export function useUsageData(options: UseUsageDataOptions) {
|
||||
(nextRank === currentRank && existing.status === record.status)
|
||||
)
|
||||
const mergedStatus = statusProgressed ? record.status : existing.status
|
||||
const protectStatus = mergedStatus !== record.status
|
||||
|
||||
// 确定是否需要保护 provider(避免 pending/unknown/unknow 覆盖已有的正确值)
|
||||
const isPendingProvider = !isUsageProviderVisible(record.provider)
|
||||
const hasValidExistingProvider = isUsageProviderVisible(existing.provider)
|
||||
const protectProvider = isPendingProvider && hasValidExistingProvider
|
||||
|
||||
// 如果需要保护状态,说明本地数据比后端更新,应该保留本地的所有实时更新字段
|
||||
if (protectStatus) {
|
||||
const recordUpstreamIsStream = typeof record.upstream_is_stream === 'boolean'
|
||||
? record.upstream_is_stream
|
||||
: typeof record.is_stream === 'boolean'
|
||||
? record.is_stream
|
||||
: undefined
|
||||
const existingUpstreamIsStream = typeof existing.upstream_is_stream === 'boolean'
|
||||
? existing.upstream_is_stream
|
||||
: typeof existing.is_stream === 'boolean'
|
||||
? existing.is_stream
|
||||
: undefined
|
||||
const upstreamIsStream = recordUpstreamIsStream ?? existingUpstreamIsStream ?? false
|
||||
const recordUpstreamIsStream = typeof record.upstream_is_stream === 'boolean'
|
||||
? record.upstream_is_stream
|
||||
: typeof record.is_stream === 'boolean'
|
||||
? record.is_stream
|
||||
: undefined
|
||||
const existingUpstreamIsStream = typeof existing.upstream_is_stream === 'boolean'
|
||||
? existing.upstream_is_stream
|
||||
: typeof existing.is_stream === 'boolean'
|
||||
? existing.is_stream
|
||||
: undefined
|
||||
const upstreamIsStream = mergeBooleanTrueWins(existingUpstreamIsStream, recordUpstreamIsStream) ?? false
|
||||
|
||||
const recordClientRequestedStream = typeof record.client_requested_stream === 'boolean'
|
||||
? record.client_requested_stream
|
||||
: typeof record.client_is_stream === 'boolean'
|
||||
? record.client_is_stream
|
||||
: undefined
|
||||
const existingClientRequestedStream = typeof existing.client_requested_stream === 'boolean'
|
||||
? existing.client_requested_stream
|
||||
: typeof existing.client_is_stream === 'boolean'
|
||||
? existing.client_is_stream
|
||||
: undefined
|
||||
const clientRequestedStream = recordClientRequestedStream ?? existingClientRequestedStream
|
||||
|
||||
const recordClientIsStream = typeof record.client_is_stream === 'boolean'
|
||||
const recordClientRequestedStream = typeof record.client_requested_stream === 'boolean'
|
||||
? record.client_requested_stream
|
||||
: typeof record.client_is_stream === 'boolean'
|
||||
? record.client_is_stream
|
||||
: typeof record.client_requested_stream === 'boolean'
|
||||
? record.client_requested_stream
|
||||
: undefined
|
||||
const existingClientIsStream = typeof existing.client_is_stream === 'boolean'
|
||||
: undefined
|
||||
const existingClientRequestedStream = typeof existing.client_requested_stream === 'boolean'
|
||||
? existing.client_requested_stream
|
||||
: typeof existing.client_is_stream === 'boolean'
|
||||
? existing.client_is_stream
|
||||
: typeof existing.client_requested_stream === 'boolean'
|
||||
? existing.client_requested_stream
|
||||
: undefined
|
||||
const clientIsStream = recordClientIsStream ?? existingClientIsStream ?? clientRequestedStream
|
||||
: undefined
|
||||
const clientRequestedStream = mergeBooleanTrueWins(existingClientRequestedStream, recordClientRequestedStream)
|
||||
|
||||
return {
|
||||
...record,
|
||||
// 保留本地的状态和所有通过轮询更新的字段
|
||||
status: mergedStatus,
|
||||
provider: protectProvider ? existing.provider : (record.provider || existing.provider),
|
||||
input_tokens: Number.isFinite(record.input_tokens)
|
||||
? record.input_tokens
|
||||
: existing.input_tokens,
|
||||
effective_input_tokens: record.effective_input_tokens ?? existing.effective_input_tokens,
|
||||
output_tokens: existing.output_tokens || record.output_tokens,
|
||||
cache_creation_input_tokens: existing.cache_creation_input_tokens ?? record.cache_creation_input_tokens,
|
||||
cache_creation_ephemeral_5m_input_tokens:
|
||||
existing.cache_creation_ephemeral_5m_input_tokens
|
||||
?? record.cache_creation_ephemeral_5m_input_tokens,
|
||||
cache_creation_ephemeral_1h_input_tokens:
|
||||
existing.cache_creation_ephemeral_1h_input_tokens
|
||||
?? record.cache_creation_ephemeral_1h_input_tokens,
|
||||
cache_read_input_tokens: existing.cache_read_input_tokens ?? record.cache_read_input_tokens,
|
||||
cost: existing.cost || record.cost,
|
||||
actual_cost: existing.actual_cost ?? record.actual_cost,
|
||||
response_time_ms: mergePositiveDurationMs(existing.response_time_ms, record.response_time_ms),
|
||||
first_byte_time_ms: mergePositiveDurationMs(existing.first_byte_time_ms, record.first_byte_time_ms),
|
||||
updated_at: existing.updated_at ?? record.updated_at,
|
||||
response_time_updated_at: existing.response_time_updated_at ?? record.response_time_updated_at,
|
||||
status_code: existing.status_code ?? record.status_code,
|
||||
error_message: existing.error_message ?? record.error_message,
|
||||
image_progress: existing.image_progress ?? record.image_progress,
|
||||
is_stream: upstreamIsStream,
|
||||
upstream_is_stream: upstreamIsStream,
|
||||
client_requested_stream: clientRequestedStream,
|
||||
client_is_stream: clientIsStream,
|
||||
api_format: existing.api_format || record.api_format,
|
||||
endpoint_api_format: existing.endpoint_api_format || record.endpoint_api_format,
|
||||
has_format_conversion: existing.has_format_conversion ?? record.has_format_conversion,
|
||||
has_fallback: existing.has_fallback === true || record.has_fallback === true,
|
||||
api_key_name: existing.api_key_name || record.api_key_name,
|
||||
provider_key_name: existing.provider_key_name || record.provider_key_name,
|
||||
rate_multiplier: existing.rate_multiplier ?? record.rate_multiplier,
|
||||
target_model: existing.target_model || record.target_model,
|
||||
reasoning_effort: existing.reasoning_effort || record.reasoning_effort,
|
||||
service_tier: existing.service_tier || record.service_tier
|
||||
}
|
||||
const recordClientIsStream = typeof record.client_is_stream === 'boolean'
|
||||
? record.client_is_stream
|
||||
: typeof record.client_requested_stream === 'boolean'
|
||||
? record.client_requested_stream
|
||||
: undefined
|
||||
const existingClientIsStream = typeof existing.client_is_stream === 'boolean'
|
||||
? existing.client_is_stream
|
||||
: typeof existing.client_requested_stream === 'boolean'
|
||||
? existing.client_requested_stream
|
||||
: undefined
|
||||
const clientIsStream = mergeBooleanTrueWins(existingClientIsStream, recordClientIsStream) ?? clientRequestedStream
|
||||
|
||||
return {
|
||||
...record,
|
||||
// 保留详情抽屉/活跃轮询已经拿到的完整指标,避免列表刷新用 0 或空值回退。
|
||||
status: mergedStatus,
|
||||
provider: protectProvider ? existing.provider : (record.provider || existing.provider),
|
||||
input_tokens: mergeSparseRecordMetric(existing.input_tokens, record.input_tokens) ?? record.input_tokens,
|
||||
effective_input_tokens: mergeSparseRecordMetric(existing.effective_input_tokens, record.effective_input_tokens) ?? record.effective_input_tokens,
|
||||
output_tokens: mergeSparseRecordMetric(existing.output_tokens, record.output_tokens) ?? record.output_tokens,
|
||||
total_tokens: mergeSparseRecordMetric(existing.total_tokens, record.total_tokens) ?? record.total_tokens,
|
||||
cache_creation_input_tokens: mergeSparseRecordMetric(existing.cache_creation_input_tokens, record.cache_creation_input_tokens) ?? record.cache_creation_input_tokens,
|
||||
cache_creation_ephemeral_5m_input_tokens:
|
||||
mergeSparseRecordMetric(
|
||||
existing.cache_creation_ephemeral_5m_input_tokens,
|
||||
record.cache_creation_ephemeral_5m_input_tokens
|
||||
) ?? record.cache_creation_ephemeral_5m_input_tokens,
|
||||
cache_creation_ephemeral_1h_input_tokens:
|
||||
mergeSparseRecordMetric(
|
||||
existing.cache_creation_ephemeral_1h_input_tokens,
|
||||
record.cache_creation_ephemeral_1h_input_tokens
|
||||
) ?? record.cache_creation_ephemeral_1h_input_tokens,
|
||||
cache_read_input_tokens: mergeSparseRecordMetric(existing.cache_read_input_tokens, record.cache_read_input_tokens) ?? record.cache_read_input_tokens,
|
||||
cost: mergeSparseRecordMetric(existing.cost, record.cost) ?? record.cost,
|
||||
actual_cost: mergeSparseRecordMetric(existing.actual_cost, record.actual_cost) ?? record.actual_cost,
|
||||
response_time_ms: mergePositiveDurationMs(existing.response_time_ms, record.response_time_ms),
|
||||
first_byte_time_ms: mergePositiveDurationMs(existing.first_byte_time_ms, record.first_byte_time_ms),
|
||||
updated_at: record.updated_at ?? existing.updated_at,
|
||||
response_time_updated_at: record.response_time_updated_at ?? existing.response_time_updated_at,
|
||||
status_code: record.status_code ?? existing.status_code,
|
||||
error_message: record.error_message ?? existing.error_message,
|
||||
image_progress: record.image_progress ?? existing.image_progress,
|
||||
is_stream: upstreamIsStream,
|
||||
upstream_is_stream: upstreamIsStream,
|
||||
client_requested_stream: clientRequestedStream,
|
||||
client_is_stream: clientIsStream,
|
||||
api_format: record.api_format || existing.api_format,
|
||||
endpoint_api_format: record.endpoint_api_format || existing.endpoint_api_format,
|
||||
has_format_conversion: record.has_format_conversion ?? existing.has_format_conversion,
|
||||
has_fallback: existing.has_fallback === true || record.has_fallback === true,
|
||||
has_retry: existing.has_retry === true || record.has_retry === true,
|
||||
api_key_name: record.api_key_name || existing.api_key_name,
|
||||
provider_key_name: record.provider_key_name || existing.provider_key_name,
|
||||
rate_multiplier: record.rate_multiplier ?? existing.rate_multiplier,
|
||||
target_model: record.target_model ?? existing.target_model,
|
||||
reasoning_effort: record.reasoning_effort ?? existing.reasoning_effort,
|
||||
service_tier: record.service_tier ?? existing.service_tier
|
||||
}
|
||||
|
||||
// 只需要保护 provider
|
||||
if (protectProvider) {
|
||||
return {
|
||||
...record,
|
||||
provider: existing.provider
|
||||
}
|
||||
}
|
||||
|
||||
return record
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user