fix(beat-audio): harden voice-provider validation and resolveVoice fast path
Address PR-agent review findings: - resolveVoice fast path: replace ambiguous boolean comparison (voiceProvider === "stepfun") === serverStepfun with explicit per-provider equality checks. Prevents an undefined or unknown provider from matching the non-stepfun (xiaomi) branch by accident. - /api/beat-audio route: reject requests whose voice.provider is present but not in the VALID_TTS_PROVIDERS whitelist (e.g. "azure"). Previously such a request would pass validation when fallback fields were also present, and resolveVoice might use the invalid voice directly instead of falling back to reprovision — producing a silent beat instead of a voiced one.
This commit is contained in:
@@ -23,11 +23,18 @@ export async function POST(req: Request) {
|
|||||||
// engine's resolveVoice re-provisions on a provider mismatch. We only
|
// engine's resolveVoice re-provisions on a provider mismatch. We only
|
||||||
// require the beat text + SOMETHING to synthesize from.
|
// require the beat text + SOMETHING to synthesize from.
|
||||||
const VALID_TTS_PROVIDERS = ["xiaomi", "stepfun"];
|
const VALID_TTS_PROVIDERS = ["xiaomi", "stepfun"];
|
||||||
|
const hasInvalidVoiceProvider =
|
||||||
|
!!body.voice?.provider && !VALID_TTS_PROVIDERS.includes(body.voice.provider);
|
||||||
const hasVoice =
|
const hasVoice =
|
||||||
!!body.voice?.provider && VALID_TTS_PROVIDERS.includes(body.voice.provider);
|
!!body.voice?.provider && VALID_TTS_PROVIDERS.includes(body.voice.provider);
|
||||||
const hasFallback =
|
const hasFallback =
|
||||||
!!body.stepfunVoiceId || !!body.voiceDescription;
|
!!body.stepfunVoiceId || !!body.voiceDescription;
|
||||||
if (!body.beat?.id || !body.beat?.line || (!hasVoice && !hasFallback)) {
|
if (
|
||||||
|
!body.beat?.id ||
|
||||||
|
!body.beat?.line ||
|
||||||
|
hasInvalidVoiceProvider ||
|
||||||
|
(!hasVoice && !hasFallback)
|
||||||
|
) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: "beat.id and beat.line are required, plus either voice.provider (xiaomi|stepfun) or stepfunVoiceId/voiceDescription" },
|
{ error: "beat.id and beat.line are required, plus either voice.provider (xiaomi|stepfun) or stepfunVoiceId/voiceDescription" },
|
||||||
{ status: 400 },
|
{ status: 400 },
|
||||||
|
|||||||
@@ -258,10 +258,13 @@ async function resolveVoice(
|
|||||||
): Promise<CharacterVoice | undefined> {
|
): Promise<CharacterVoice | undefined> {
|
||||||
const serverStepfun = !!config.tts && isStepfun(config.tts);
|
const serverStepfun = !!config.tts && isStepfun(config.tts);
|
||||||
const voiceProvider = req.voice?.provider;
|
const voiceProvider = req.voice?.provider;
|
||||||
|
const voiceMatchesServer =
|
||||||
|
(voiceProvider === "stepfun" && serverStepfun) ||
|
||||||
|
(voiceProvider === "xiaomi" && !serverStepfun);
|
||||||
|
|
||||||
// Fast path: the client sent a matching voice. (Also covers the legacy
|
// Fast path: the client sent a matching voice. (Also covers the legacy
|
||||||
// xiaomi card + xiaomi server case where the 220KB was unavoidable anyway.)
|
// xiaomi card + xiaomi server case where the 220KB was unavoidable anyway.)
|
||||||
if (req.voice && (voiceProvider === "stepfun") === serverStepfun) {
|
if (req.voice && voiceMatchesServer) {
|
||||||
return req.voice;
|
return req.voice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user