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:
@@ -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 },
|
||||
}],
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user