feat(server): enforce official asr aliases

This commit is contained in:
RainbowBird
2026-07-01 22:34:56 +08:00
parent feec4da1fa
commit b2d7442949
3 changed files with 123 additions and 4 deletions
+1
View File
@@ -230,6 +230,7 @@ export async function buildApp(deps: AppDeps) {
env: deps.env,
configKV: deps.configKV,
envelopeCrypto: deps.envelopeCrypto,
officialCatalogService: deps.officialCatalogService,
}))
// Cross-instance config invalidation. The subscriber owns its own
@@ -1,11 +1,13 @@
import type { RouterConfig } from '../../services/domain/llm-router/types'
import type { OfficialCatalogService } from '../../services/domain/official-catalog'
import { Buffer } from 'node:buffer'
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import { createEnvelopeCrypto } from '../../utils/envelope-crypto'
import { resolveOfficialAliyunNlsCredentials } from './route'
import { ApiError } from '../../utils/error'
import { resolveOfficialAliyunNlsCredentials, resolveOfficialAliyunNlsCredentialsFromConfig } from './route'
function createRouterConfig(overrides?: Partial<RouterConfig>): RouterConfig {
return {
@@ -20,6 +22,35 @@ function createRouterConfig(overrides?: Partial<RouterConfig>): RouterConfig {
}
}
function createOfficialCatalogService(routeModelId = 'auto'): OfficialCatalogService {
return {
syncAliasesFromRouterConfig: vi.fn(async () => []),
resolveEnabledAlias: vi.fn(async () => ({
id: 'alias-auto',
surface: 'asr',
aliasId: 'auto',
displayName: 'Auto',
enabled: true,
displayOrder: 0,
fallbackEnabled: true,
loadBalancingEnabled: false,
createdAt: new Date(),
updatedAt: new Date(),
routes: [{
id: 'route-1',
aliasId: 'alias-auto',
routerModelId: routeModelId,
pool: 'primary',
enabled: true,
weight: 1,
displayOrder: 0,
createdAt: new Date(),
updatedAt: new Date(),
}],
})),
} as unknown as OfficialCatalogService
}
describe('resolveOfficialAliyunNlsCredentials', () => {
/**
* @example
@@ -69,4 +100,76 @@ describe('resolveOfficialAliyunNlsCredentials', () => {
region: 'cn-shanghai',
})
})
it('resolves official ASR alias through the catalog before decrypting credentials', async () => {
const envelope = createEnvelopeCrypto({ masterKey: Buffer.alloc(32, 7) })
const ciphertext = envelope.encryptKey(' secret ', {
modelName: 'aliyun/asr-primary',
keyEntryId: 'aliyun-nls-asr-prod-1',
})
const routerConfig = createRouterConfig({
asr: {
models: {
'aliyun/asr-primary': {
provider: 'aliyun-nls',
upstreams: [{
keys: [{ id: 'aliyun-nls-asr-prod-1', ciphertext }],
adapterParams: {
accessKeyId: 'ak',
appKey: 'app',
},
}],
},
},
},
})
const officialCatalogService = createOfficialCatalogService('aliyun/asr-primary')
const credentials = await resolveOfficialAliyunNlsCredentialsFromConfig({
configKV: { getOptional: vi.fn(async () => routerConfig) } as never,
envelopeCrypto: envelope,
officialCatalogService,
})
expect(credentials).toMatchObject({
accessKeyId: 'ak',
accessKeySecret: 'secret',
appKey: 'app',
})
expect(officialCatalogService.syncAliasesFromRouterConfig).toHaveBeenCalledWith({
surface: 'asr',
modelIds: ['aliyun/asr-primary'],
})
expect(officialCatalogService.resolveEnabledAlias).toHaveBeenCalledWith('asr', 'auto')
})
it('rejects disabled official ASR aliases before credentials are used', async () => {
const envelope = createEnvelopeCrypto({ masterKey: Buffer.alloc(32, 7) })
const officialCatalogService = createOfficialCatalogService()
vi.mocked(officialCatalogService.resolveEnabledAlias).mockRejectedValueOnce(
new ApiError(400, 'OFFICIAL_ALIAS_DISABLED', 'Official provider alias is disabled'),
)
const routerConfig = createRouterConfig({
asr: {
models: {
auto: {
provider: 'aliyun-nls',
upstreams: [{
keys: [{ id: 'aliyun-nls-asr-prod-1', ciphertext: 'unused' }],
adapterParams: {},
}],
},
},
},
})
await expect(resolveOfficialAliyunNlsCredentialsFromConfig({
configKV: { getOptional: vi.fn(async () => routerConfig) } as never,
envelopeCrypto: envelope,
officialCatalogService,
})).rejects.toMatchObject({
statusCode: 400,
errorCode: 'OFFICIAL_ALIAS_DISABLED',
})
})
})
@@ -4,6 +4,7 @@ import type { AuthInstance } from '../../libs/auth'
import type { Env } from '../../libs/env'
import type { ConfigKVService } from '../../services/adapters/config-kv'
import type { RouterConfig } from '../../services/domain/llm-router/types'
import type { OfficialCatalogService } from '../../services/domain/official-catalog'
import type { EnvelopeCrypto } from '../../utils/envelope-crypto'
import { resolveRequestAuth } from '../../libs/request-auth'
@@ -84,12 +85,25 @@ export function resolveOfficialAliyunNlsCredentials(
}
}
async function resolveOfficialAliyunNlsCredentialsFromConfig(input: {
export async function resolveOfficialAliyunNlsCredentialsFromConfig(input: {
configKV: ConfigKVService
envelopeCrypto: EnvelopeCrypto
officialCatalogService: OfficialCatalogService
}) {
const routerConfig = await input.configKV.getOptional('LLM_ROUTER_CONFIG')
const credentials = resolveOfficialAliyunNlsCredentials(routerConfig, input.envelopeCrypto)
const modelIds = Object.keys(routerConfig?.asr?.models ?? {}).sort()
if (modelIds.length === 0)
return null
await input.officialCatalogService.syncAliasesFromRouterConfig({
surface: 'asr',
modelIds,
})
const alias = await input.officialCatalogService.resolveEnabledAlias('asr', OFFICIAL_ASR_MODEL_NAME)
const primary = alias.routes.find(route => route.pool === 'primary')
const modelName = (primary ?? alias.routes[0]).routerModelId
const credentials = resolveOfficialAliyunNlsCredentials(routerConfig, input.envelopeCrypto, modelName)
if (!credentials)
return null
@@ -113,6 +127,7 @@ export function createAudioTranscriptionStreamHandler(input: {
env: Env
configKV: ConfigKVService
envelopeCrypto: EnvelopeCrypto
officialCatalogService: OfficialCatalogService
}) {
return async function handleAudioTranscriptionStream(c: Context) {
const session = await resolveRequestAuth(