mirror of
https://github.com/moeru-ai/airi.git
synced 2026-08-14 08:52:42 +00:00
feat(server): enforce official asr aliases
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user