fix(frontend): 按端点能力约束会话压缩映射

This commit is contained in:
MMEXA
2026-07-14 02:05:10 +08:00
parent f10d631a9c
commit 93e2f95c47
5 changed files with 262 additions and 10 deletions
@@ -5,6 +5,7 @@ import {
COMPACT_REQUEST_SCOPE_VALUE,
formatModelMappingEndpointLabel,
formatModelMappingRequestScope,
modelMappingEndpointScopeSupportsSessionCompaction,
modelMappingOperationsKey,
modelMappingOperationsFromScopeValue,
modelMappingRequestScopeOptions,
@@ -36,12 +37,50 @@ describe('model mapping request scope', () => {
it('preserves an unknown operation scope while editing', () => {
const operations = ['future_operation', 'compact']
const value = modelMappingRequestScopeValue(operations)
const options = modelMappingRequestScopeOptions(operations)
const options = modelMappingRequestScopeOptions(
operations,
{ sessionCompaction: true },
)
expect(modelMappingOperationsFromScopeValue(value)).toEqual(operations)
expect(options).toContainEqual({ value, label: '仅匹配:future_operation, compact' })
})
it('offers compact scope only when the selected endpoint scope supports it', () => {
expect(modelMappingRequestScopeOptions(undefined, { sessionCompaction: false }))
.toEqual([{ value: ALL_REQUESTS_SCOPE_VALUE, label: '所有请求' }])
expect(modelMappingRequestScopeOptions(undefined, { sessionCompaction: true }))
.toContainEqual({ value: COMPACT_REQUEST_SCOPE_VALUE, label: '仅会话压缩' })
})
it('requires every explicitly selected endpoint to use OpenAI Responses', () => {
const responsesEndpoint = {
id: 'responses',
api_format: 'OPENAI_RESPONSES',
base_url: 'https://api.example.com/v1',
is_active: true,
}
const chatEndpoint = {
id: 'chat',
api_format: 'openai:chat',
base_url: 'https://api.example.com/v1',
is_active: true,
}
expect(modelMappingEndpointScopeSupportsSessionCompaction(
undefined,
[responsesEndpoint, chatEndpoint],
)).toBe(false)
expect(modelMappingEndpointScopeSupportsSessionCompaction(
[responsesEndpoint.id],
[responsesEndpoint, chatEndpoint],
)).toBe(true)
expect(modelMappingEndpointScopeSupportsSessionCompaction(
[responsesEndpoint.id, chatEndpoint.id],
[responsesEndpoint, chatEndpoint],
)).toBe(false)
})
it('rejects malformed scope values without constructing operations', () => {
expect(modelMappingOperationsFromScopeValue('compact')).toBeUndefined()
expect(modelMappingOperationsFromScopeValue('{"compact":true}')).toBeUndefined()
@@ -1,4 +1,8 @@
import { formatApiFormat } from '@/api/endpoints/types/api-format'
import {
API_FORMATS,
formatApiFormat,
normalizeApiFormatAlias,
} from '@/api/endpoints/types/api-format'
export const MODEL_MAPPING_OPERATION_COMPACT = 'compact'
@@ -12,6 +16,10 @@ export interface ModelMappingRequestScopeOption {
label: string
}
export interface ModelMappingRequestScopeCapabilities {
sessionCompaction: boolean
}
export interface ModelMappingRequestScopeLabels {
allRequests: string
sessionCompactionOnly: string
@@ -90,14 +98,22 @@ export function formatModelMappingRequestScope(
export function modelMappingRequestScopeOptions(
operations: string[] | undefined,
capabilities: ModelMappingRequestScopeCapabilities,
labels: ModelMappingRequestScopeLabels = DEFAULT_REQUEST_SCOPE_LABELS,
): ModelMappingRequestScopeOption[] {
const options: ModelMappingRequestScopeOption[] = [
{ value: ALL_REQUESTS_SCOPE_VALUE, label: labels.allRequests },
{ value: COMPACT_REQUEST_SCOPE_VALUE, label: labels.sessionCompactionOnly },
]
if (capabilities.sessionCompaction) {
options.push({
value: COMPACT_REQUEST_SCOPE_VALUE,
label: labels.sessionCompactionOnly,
})
}
const currentValue = modelMappingRequestScopeValue(operations)
if (!options.some(option => option.value === currentValue)) {
const compactScopeUnavailable = currentValue === COMPACT_REQUEST_SCOPE_VALUE
&& !capabilities.sessionCompaction
if (!compactScopeUnavailable && !options.some(option => option.value === currentValue)) {
options.push({
value: currentValue,
label: formatModelMappingRequestScope(operations, labels),
@@ -106,6 +122,23 @@ export function modelMappingRequestScopeOptions(
return options
}
export function modelMappingEndpointScopeSupportsSessionCompaction(
endpointIds: string[] | undefined,
endpoints: ModelMappingEndpoint[],
): boolean {
const selectedIds = [...new Set(
(endpointIds ?? []).map(id => id.trim()).filter(Boolean),
)]
if (selectedIds.length === 0) return false
const endpointsById = new Map(endpoints.map(endpoint => [endpoint.id, endpoint]))
return selectedIds.every((endpointId) => {
const endpoint = endpointsById.get(endpointId)
return endpoint !== undefined
&& normalizeApiFormatAlias(endpoint.api_format) === API_FORMATS.OPENAI_RESPONSES
})
}
export function formatModelMappingEndpointLabel(
endpoint: ModelMappingEndpoint,
endpoints: ModelMappingEndpoint[],