feat(server/tts): upgrade dashscope-cosyvoice adapter to v2 two-step REST

DashScope dropped cosyvoice-v1 from its REST-supported model list. v2
(and v3+) speak a different shape: voice / format / sample_rate live
under `input`, not `parameters`; non-streaming responses return
`output.audio.url` (signed OSS URL) instead of inline `output.audio.data`
base64. The previous adapter sent v1-shaped bodies to a bare
`https://dashscope-intl.aliyuncs.com/api/v1` baseURL and parsed
`audio.data`, which 404'd before the migration and would 200-with-no-
audio after — both invisible regressions for the gateway.

Adapter changes:
- Rewrite request body to v2 schema (voice/format under input).
- Add follow-up GET against `output.audio.url`; stream into ArrayBuffer
  with a 25 MB hard cap and explicit drain-tracking finally, so a
  misbehaving URL cannot exhaust memory and a half-read body cannot
  hang a connection.
- Re-document baseURL contract: adapters do NOT append path; ops must
  configure the FULL endpoint URL (root cause of the original 404
  storm). DEFAULT_COSYVOICE_MODEL bumped to `cosyvoice-v2`, default
  voice to `longxiaochun_v2`.

Voice catalog: regenerated with 19 representative cosyvoice-v2 voices
(assistant / customer-service / child / en-US / en-GB / ja-JP / ko-KR)
so the frontend voice picker is no longer a 2-entry stub. Full catalog
(100+) remains on the Alibaba docs page — we'll sync on demand rather
than scrape.

Seed script: `--dashscope-region intl|cn` (default `intl`),
`--dashscope-upstream-model cosyvoice-v2`, baseURL now resolves to
`https://<host>/api/v1/services/audio/tts/SpeechSynthesizer` so a
mis-typed region or path cannot reintroduce the 404.

Tests: new dashscope-cosyvoice.test.ts covers v2 body shape (asserts
`parameters` absent — regression), audio.url follow-up fetch, 401
propagation with `.status`, empty-envelope falling back into the
router's recoverable-error path, and catalog freshness (no leftover v1
ids). Verified locally against the staging DashScope key: 200 +
playable mp3 end to end.
This commit is contained in:
RainbowBird
2026-05-18 23:32:33 +08:00
parent 9aef35948c
commit 6ed0da86c3
4 changed files with 559 additions and 66 deletions
+16 -3
View File
@@ -49,6 +49,10 @@ interface Args {
azureRegion: string
azureTtsModel: string
dashscopeTtsModel: string
/** `intl` → dashscope-intl.aliyuncs.com (Singapore); `cn` → dashscope.aliyuncs.com (Beijing). */
dashscopeRegion: string
/** Concrete cosyvoice variant the adapter calls upstream. Independent from `dashscopeTtsModel` (the gateway-facing alias). */
dashscopeUpstreamModel: string
defaultTtsModel: string | undefined
}
@@ -76,7 +80,9 @@ function parseArgs(argv: string[]): Args {
defaultChatModel: values['default-chat-model'] ?? 'chat-default',
azureRegion: values['azure-region'] ?? 'eastasia',
azureTtsModel: values['azure-tts-model'] ?? 'microsoft/v1',
dashscopeTtsModel: values['dashscope-tts-model'] ?? 'alibaba/cosyvoice-v1',
dashscopeTtsModel: values['dashscope-tts-model'] ?? 'alibaba/cosyvoice-v2',
dashscopeRegion: values['dashscope-region'] ?? 'intl',
dashscopeUpstreamModel: values['dashscope-upstream-model'] ?? 'cosyvoice-v2',
defaultTtsModel: values['default-tts-model'],
}
}
@@ -132,14 +138,21 @@ function buildDashscope(args: Args, plaintext: string, envelope: EnvelopeCrypto)
modelName: args.dashscopeTtsModel,
keyEntryId,
})
// dashscope-cosyvoice adapter expects the FULL non-streaming endpoint path —
// it does not append `/services/audio/tts/SpeechSynthesizer` itself. A bare
// `/api/v1` baseURL was the root cause of the 404 storm during the v1→v2
// migration; do not regress here.
const host = args.dashscopeRegion === 'cn'
? 'dashscope.aliyuncs.com'
: 'dashscope-intl.aliyuncs.com'
return {
ttsModelName: args.dashscopeTtsModel,
ttsModel: {
provider: 'dashscope-cosyvoice',
upstreams: [{
baseURL: 'https://dashscope-intl.aliyuncs.com/api/v1',
baseURL: `https://${host}/api/v1/services/audio/tts/SpeechSynthesizer`,
keys: [{ id: keyEntryId, ciphertext }],
adapterParams: {},
adapterParams: { model: args.dashscopeUpstreamModel },
}],
},
}