fix(cli): accept the qwen-audio Token Plan ASR family in voice transport resolution - #10981
fix(cli): accept the qwen-audio Token Plan ASR family in voice transport resolution#10981now-ing wants to merge 1 commit into
Conversation
…ort resolution Model Studio Token Plan serves speech recognition under qwen-audio-<version> ids (e.g. qwen-audio-3.0-asr-flash), but resolveVoiceTransport only matched the legacy qwen3-asr ids, so voice dictation rejected the family at the selection gate before any request was made. Route the family like its qwen3-asr counterpart: bare and date-suffixed asr-flash ids to the batch chat transport, *-realtime and *-asr-flash-streaming ids to the OpenAI realtime WebSocket transport (qwen-audio-3.0-realtime-plus is verified working there against the Token Plan endpoint). filetrans and tts ids stay unsupported. Mirror the patterns in the Web Shell's hand-maintained isVoiceModelId() picker gate and note the family in the keyterms docs, whose biasing rides the realtime transport. Fixes QwenLM#10932 Signed-off-by: mac <bishopapril850965@yahoo.com>
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
@now-ing thanks for picking up #10932 — before this can go to code review, the PR body needs the sections it's missing from the PR template:
## Risk & Scope— the three bullets (main risk or tradeoff / not validated or out of scope / breaking changes). This is the one that matters most here: the thread on #10932 established that the bareqwen-audio-3.0-asr-flashanswered400 {}on the batch chat/completions shape, and that-streamingisn't provisioned on Token Plan yet. So the transport mapping for those two ids is a forward-looking choice rather than a verified one, and "Not validated / out of scope" is exactly where that belongs.## Linked Issues—Fixes #10932currently sits inline under Why it's needed. GitHub does resolve it there, but the template asks for its own section.- The
<details><summary>中文说明</summary>block — a full Chinese translation of the body, paragraph by paragraph.
What this PR does, Why it's needed, How to verify, Evidence (Before & After) and Tested on are all filled in, so it's only the three above. Re-running triage after the edit picks the PR up from the top.
中文说明
@now-ing 感谢接手 #10932。在进入代码审查之前,PR 描述还需要补齐 PR 模板 中缺失的部分:
## Risk & Scope—— 三个条目(主要风险或取舍 / 未验证或超出范围 / 破坏性变更)。这一条在此处最重要:#10932 的讨论已经确认,裸 IDqwen-audio-3.0-asr-flash在批处理 chat/completions 形状下返回400 {},且-streaming变体在 Token Plan 上尚未开通。因此这两个 id 的 transport 映射属于前瞻性选择而非已验证结论,「未验证 / 超出范围」正是应该写清楚这一点的地方。## Linked Issues——Fixes #10932目前写在 Why it's needed 里。GitHub 确实能识别,但模板要求单独成节。<details><summary>中文说明</summary>区块 —— 需要逐段对应地完整翻译上面的英文正文。
What this PR does、Why it's needed、How to verify、Evidence (Before & After)、Tested on 都已填写,只缺上面三项。补齐后重新触发 triage,会从头开始审查这个 PR。
— Qwen Code · qwen3.8-max-2026-09-02
Maintainer verification — built a real runtime environment for this PRI stood up a local stand-in for the Model Studio Token Plan gateway and drove the real CLI, the real Verified at HarnessNo Token Plan credentials are needed. A local gateway speaks all three wire dialects the voice pipeline knows about, logging every request/frame as JSONL:
Microphone capture is supplied by a stand-in 1. It fixes the reported bug — real TUI, real dictation
main — main — hand-set PR — the model is selectable, and a full dictation round trip completes: The request that reached the gateway is the real {"kind":"http.asr","path":"/compatible-mode/v1/chat/completions","model":"qwen-audio-3.0-asr-flash",
"asr_options":{"enable_itn":true,"language":"en"},
"messages":[{"role":"system","content":[{"type":"text","text":"Qwen MCP grep regex localhost codebase …"}]},
{"role":"user","content":[{"type":"input_audio","format":"wav","dataBytes":51282}]}]}Note the leading system message: keyterms biasing does reach the new family on the batch path, and on the realtime path as 2. Real
|
| Probe | main | PR |
|---|---|---|
GET /workspace/voice → availableVoiceModels |
[qwen3-asr-flash] |
[qwen-audio-3.0-asr-flash (qwen-asr-chat), qwen-audio-3.0-realtime-plus (qwen-asr-realtime), qwen3-asr-flash] |
POST /workspace/voice/transcribe (real WAV body) |
400 unsupported_voice_model |
200 {"text":"…","model":"qwen-audio-3.0-asr-flash","transport":"qwen-asr-chat"} |
WS /voice/stream with qwen-audio-3.0-realtime-plus, real PCM |
ready{streaming:false} → error |
ready{streaming:true} → 2× interim → final |
The WS /voice/stream row is the Web Shell's own dictation path end to end, and it also shows the second-order effect of the fix: on main the realtime model silently degrades to streaming:false (isStreamingVoiceModel() is false for unsupported) before failing.
deriveWebSocketBase() needs no change, as the PR says — the socket landed on /api-ws/v1/realtime?model=qwen-audio-3.0-realtime-plus from a …/compatible-mode/v1 baseUrl with no URL plumbing.
3. Both allowlists agree — the "two gates" risk is closed
I ran 25 ids (real catalog ids, version-shape stress, suffix stress) through the CLI's resolveVoiceTransport() and the web-shell's isVoiceModelId() in the same process. The two hand-maintained gates agree on every one of the 25. A grep over the repo confirms there is no third allowlist — the other qwen3-asr-* hits are fixed default model ids in qwen-live/serve/live and error-message copy.
id cli transport web-shell allow
qwen-audio-3.0-asr-flash qwen-asr-chat true
qwen-audio-3.0-asr-flash-streaming qwen-asr-realtime true
qwen-audio-3.0-realtime-plus qwen-asr-realtime true
qwen-audio-4.1-asr-flash qwen-asr-chat true (forward-compatible ✓)
Qwen-Audio-3.0-ASR-Flash qwen-asr-chat true (case-insensitive ✓)
qwen-audio-3.0-asr-flash-filetrans unsupported false
qwen-audio-3.0-tts-plus unsupported false
qwen-audio-3.0-tts-realtime unsupported false
qwen-audio-3.0-omni-realtime unsupported false
qwen3-asr-flash / -realtime / paraformer… unchanged unchanged
Regex cost is linear — a 200 kB pathological id resolves in 0.85 ms, no backtracking blowup.
4. The PR's own tests are load-bearing
Reverting only the two production files and keeping the new tests fails them, so they would catch a regression:
packages/cli/src/ui/voice/voice-model.test.ts— 8 pass on PR, 2 fail on merge-base sourcepackages/web-shell/client/voice/voiceModels.test.ts— 5 pass on PR, 2 fail on merge-base source
Regression sweep: 344 tests pass across voice-service, voice-command, modelCommand, use-voice-input, ModelDialog, resolve-voice-config, voice-transcriber, qwen-asr-realtime-session, workspace-voice. eslint, prettier --check and npm run check-i18n are clean on the changed files. (src/ui/opentui/dialog-data.test.ts cannot load here for lack of @opentui/* — it fails identically on main, so it is my environment, not the PR.)
Findings
🔴 Blocking — the generated VS Code settings schema was not regenerated
packages/cli/src/config/settingsSchema.ts is the source for packages/vscode-ide-companion/schemas/settings.schema.json, and ci.yml regenerates it and fails if the tree becomes dirty. I ran the repo's own trusted classifier for this PR — classify-pr-profile.sh QwenLM/qwen-code 10981 → full — so that step will run:
The same command on main leaves the tree clean, so this is the PR's diff. Fix:
npm run generate:settings-schema && git add packages/vscode-ide-companion/schemas/settings.schema.json
🟡 The qwen-audio-3.0-realtime-plus claim is stronger than the evidence supports
The PR description says this id is "immediately usable for Token Plan dictation today (verified by the community probes in the issue)". What the probe in the issue actually established is that the id accepts the dialect (session.created/session.updated, commit accepted) — not that it emits conversation.item.input_audio_transcription.*. That distinction matters, because -realtime-plus reads like a conversational (speech-to-speech) realtime model, and those report the user's speech under response.audio_transcript.* instead.
I measured what happens in that case by pointing the real openQwenAsrRealtimeStream() at a gateway that accepts the dialect but answers a commit with response.* events only:
{"transcript": "", "thrown": null, "interims": [], "errors": [], "elapsedMs": 14}An empty transcript, no error, no interim — dictation looks like it worked and inserts nothing. This is pre-existing client behaviour, not something the PR introduces, and the transport mapping is still the best inference from the evidence. But I'd soften the claim to "resolves to the realtime transport; end-to-end transcript delivery on Token Plan still to be confirmed", so nobody treats it as validated.
🟢 Minor — degenerate version segments are accepted
[\d.]+ matches dot-only strings, so qwen-audio-..-asr-flash and qwen-audio-3...0-asr-flash resolve to qwen-asr-chat. Both gates agree, so there is no CLI/web-shell skew — the only consequence is that a nonexistent id reaches the server instead of being rejected locally. Cosmetic; [\d]+(?:\.[\d]+)* would tighten it if you care.
🟢 Minor — the new doc comment is slightly imprecise about filetrans
The comment says "Other ids in the family (filetrans, tts) are not dictation transports and stay unsupported". True for the bare -asr-flash-filetrans, but qwen-audio-3.0-asr-flash-realtime-filetrans and -streaming-filetrans resolve to qwen-asr-realtime, because the realtime rule matches at the (?:-|$) boundary before the suffix. No such ids exist in the catalog — only the comment overstates.
ℹ️ For the record
- Legacy DashScope ids without a version segment (
qwen-audio-asr,qwen-audio-asr-latest,qwen-audio-turbo) stayunsupported. Presumably deliberate, since those are not the ASR-flash family — just flagging it so it is a decision rather than an oversight. - On the live gateway today the bare id answers the batch shape with
400 {}(per the probes in Voice dictation cannot use Token Plan ASR: resolveVoiceTransport rejects qwen-audio-3.0-asr-flash as unsupported #10932). After this PR the user-visible message becomesVoice transcription request failed (400 Bad Request): {}— accurate, but opaque. Nothing to change here; worth a line in Voice dictation cannot use Token Plan ASR: resolveVoiceTransport rejects qwen-audio-3.0-asr-flash as unsupported #10932 so Token Plan users know what to expect until the server-side routing lands.
Verdict
Fix is correct, minimal, lands on both gates, and is proven end to end on the TUI, the daemon REST route and the daemon voice WebSocket. Regenerate settings.schema.json and this is good to merge; the -realtime-plus wording is worth softening in the same push.
中文版
维护者验证 —— 为本 PR 搭建了真实运行环境
我在本地搭了一个 Model Studio Token Plan 网关的替身,让真实的 CLI、真实的 qwen serve 守护进程和真实的 web-shell 语音模块跑在它上面,并与 merge-base(60161cb64a)做 A/B 对照。修复本身是正确的,在三个界面上都端到端跑通了。有一个阻塞项:生成物没有重新生成,Check settings schema is up-to-date 这一步 CI 会失败。
验证提交 ef99e5662,base 60161cb64a(PR 落后 main 两个提交,所有改动文件均无冲突改动)。
验证环境
不需要 Token Plan 凭据。本地网关同时讲语音链路认识的三种协议,并把每个请求/帧以 JSONL 记录下来:
| 端点 | 协议 | 对应 transport |
|---|---|---|
POST /compatible-mode/v1/chat/completions(带 input_audio) |
批处理 ASR | qwen-asr-chat |
WS /api-ws/v1/realtime?model=… |
OpenAI realtime | qwen-asr-realtime |
WS /api-ws/v1/inference |
DashScope run-task | dashscope-task-realtime |
麦克风采集由 PATH 上的替身 sox 提供,它输出真实的 16 kHz/单声道/s16le WAV,因此 TUI 走的是真正的录音器路径。两个对照臂跑同一份源码树,只切换 PR 的两个生产文件。
1. 确实修复了所报问题 —— 真实 TUI、真实语音输入
main 上按 triage 预测的两个界面都复现了 #10932:选择器用 formatUnsupportedVoiceModelMessage() 拒绝该模型;手动写入 voiceModel 后按空格,则在录音阶段抛出另一句报错。
main —— /model --voice 拒绝 qwen-audio-3.0-asr-flash:
main —— 手动设置 voiceModel,按空格听写:
PR —— 模型可选,并完成一次完整的语音输入闭环:
到达网关的请求正是真实的 transcribeViaQwenAsr 形状:
{"kind":"http.asr","path":"/compatible-mode/v1/chat/completions","model":"qwen-audio-3.0-asr-flash",
"asr_options":{"enable_itn":true,"language":"en"},
"messages":[{"role":"system","content":[{"type":"text","text":"Qwen MCP grep regex localhost codebase …"}]},
{"role":"user","content":[{"type":"input_audio","format":"wav","dataBytes":51282}]}]}注意开头那条 system 消息:keyterms 偏置确实作用到了新系列——批处理路径走 system 消息,realtime 路径走 session.update.input_audio_transcription.corpus_text。这说明本 PR 对 keytermsFile 文档/schema 措辞的修改在事实上是准确的,也就是 triage 中第 4 个待办项。
2. 真实 qwen serve 守护进程 —— 两条路由
| 探测 | main | PR |
|---|---|---|
GET /workspace/voice → availableVoiceModels |
[qwen3-asr-flash] |
[qwen-audio-3.0-asr-flash (qwen-asr-chat), qwen-audio-3.0-realtime-plus (qwen-asr-realtime), qwen3-asr-flash] |
POST /workspace/voice/transcribe(真实 WAV body) |
400 unsupported_voice_model |
200 {"text":"…","model":"qwen-audio-3.0-asr-flash","transport":"qwen-asr-chat"} |
WS /voice/stream 使用 qwen-audio-3.0-realtime-plus、真实 PCM |
ready{streaming:false} → error |
ready{streaming:true} → 2 次 interim → final |
WS /voice/stream 这一行就是 Web Shell 自己的语音输入链路的端到端验证,同时也展示了修复的二阶效果:在 main 上,realtime 模型会静默降级为 streaming:false(因为 unsupported 时 isStreamingVoiceModel() 返回 false),然后失败。
正如 PR 所说,deriveWebSocketBase() 无需改动——从 …/compatible-mode/v1 的 baseUrl 出发,socket 正确落在 /api-ws/v1/realtime?model=qwen-audio-3.0-realtime-plus,没有任何额外 URL 处理。
3. 两处白名单完全一致 —— triage 指出的"两道门"风险已闭合
我在同一个进程里,用 25 个 id(真实目录 id、版本形状压力、后缀压力)同时跑 CLI 的 resolveVoiceTransport() 与 web-shell 的 isVoiceModelId()。两处手工维护的门在 25 个 id 上判定完全一致。 全仓 grep 确认不存在第三处白名单——其余 qwen3-asr-* 命中都是 qwen-live/serve/live 里的固定默认模型 id 和报错文案。
id cli transport web-shell allow
qwen-audio-3.0-asr-flash qwen-asr-chat true
qwen-audio-3.0-asr-flash-streaming qwen-asr-realtime true
qwen-audio-3.0-realtime-plus qwen-asr-realtime true
qwen-audio-4.1-asr-flash qwen-asr-chat true (向前兼容 ✓)
Qwen-Audio-3.0-ASR-Flash qwen-asr-chat true (大小写不敏感 ✓)
qwen-audio-3.0-asr-flash-filetrans unsupported false
qwen-audio-3.0-tts-plus unsupported false
qwen-audio-3.0-tts-realtime unsupported false
qwen-audio-3.0-omni-realtime unsupported false
qwen3-asr-flash / -realtime / paraformer… 不变 不变
正则代价是线性的——200 kB 的病态 id 解析耗时 0.85 ms,没有回溯爆炸。
4. PR 自带的测试是有效的
只回滚两个生产文件、保留新增测试后,它们会失败,说明确实能拦住回归:
packages/cli/src/ui/voice/voice-model.test.ts—— PR 上 8 通过,merge-base 源码上 2 失败packages/web-shell/client/voice/voiceModels.test.ts—— PR 上 5 通过,merge-base 源码上 2 失败
回归扫描:voice-service、voice-command、modelCommand、use-voice-input、ModelDialog、resolve-voice-config、voice-transcriber、qwen-asr-realtime-session、workspace-voice 共 344 个测试通过。改动文件上 eslint、prettier --check、npm run check-i18n 均干净。(src/ui/opentui/dialog-data.test.ts 因本机缺少 @opentui/* 无法加载——它在 main 上同样失败,属于我的环境问题,与 PR 无关。)
结论清单
🔴 阻塞 —— 生成的 VS Code settings schema 未重新生成
packages/cli/src/config/settingsSchema.ts 是 packages/vscode-ide-companion/schemas/settings.schema.json 的来源,ci.yml 会重新生成它并在工作区变脏时失败。我用仓库自带的可信分类器跑了本 PR —— classify-pr-profile.sh QwenLM/qwen-code 10981 → full,因此这一步会执行:
同样的命令在 main 上不会产生任何改动,所以这确实是本 PR 的问题。修复:
npm run generate:settings-schema && git add packages/vscode-ide-companion/schemas/settings.schema.json
🟡 关于 qwen-audio-3.0-realtime-plus 的结论强于证据
PR 描述称该 id "immediately usable for Token Plan dictation today(已由 issue 中的社区探测验证)"。issue 里的探测实际确认的是该 id 接受这套协议(session.created/session.updated、commit 被接受),而没有确认它会发出 conversation.item.input_audio_transcription.* 事件。这个区别很关键:-realtime-plus 看起来像是对话式(语音到语音)realtime 模型,而这类模型会把用户语音的转写放在 response.audio_transcript.* 里。
我把真实的 openQwenAsrRealtimeStream() 指向一个"接受协议但 commit 后只回 response.*"的网关,量化了这种情况:
{"transcript": "", "thrown": null, "interims": [], "errors": [], "elapsedMs": 14}空转写、无报错、无中间结果——听写看起来成功了,却什么也没插入。这是客户端既有行为,并非本 PR 引入,而且从现有证据看这个 transport 映射仍是最合理的推断。但我建议把措辞放软为"解析到 realtime transport;Token Plan 上端到端的转写投递仍待确认",以免被当成已验证结论。
🟢 次要 —— 接受了退化的版本段
[\d.]+ 能匹配纯点字符串,因此 qwen-audio-..-asr-flash、qwen-audio-3...0-asr-flash 都会解析为 qwen-asr-chat。两处门判定一致,所以不存在 CLI/web-shell 偏差——唯一后果是一个不存在的 id 会被发到服务端,而不是在本地被拒。属于观感问题;若在意可改为 [\d]+(?:\.[\d]+)*。
🟢 次要 —— 新增注释对 filetrans 的描述略有出入
注释写道 "Other ids in the family (filetrans, tts) are not dictation transports and stay unsupported"。对裸的 -asr-flash-filetrans 成立,但 qwen-audio-3.0-asr-flash-realtime-filetrans 和 -streaming-filetrans 会解析为 qwen-asr-realtime,因为 realtime 规则在 (?:-|$) 边界处就匹配上了。目录中并不存在这类 id——只是注释说法过满。
ℹ️ 备录
- 不带版本段的旧 DashScope id(
qwen-audio-asr、qwen-audio-asr-latest、qwen-audio-turbo)仍为unsupported。推测是有意为之(它们不属于 ASR-flash 系列),此处标注一下,以便这是一个决定而非疏漏。 - 按 Voice dictation cannot use Token Plan ASR: resolveVoiceTransport rejects qwen-audio-3.0-asr-flash as unsupported #10932 中的探测,线上网关目前对裸 id 的批处理请求返回
400 {}。本 PR 之后用户看到的提示会变成Voice transcription request failed (400 Bad Request): {}—— 准确但不好理解。这里无需改动;建议在 Voice dictation cannot use Token Plan ASR: resolveVoiceTransport rejects qwen-audio-3.0-asr-flash as unsupported #10932 里补一句,让 Token Plan 用户知道在服务端路由补齐之前会看到什么。
结论
修复正确、改动最小、两处门都覆盖到了,并已在 TUI、守护进程 REST 路由和守护进程语音 WebSocket 上端到端验证。重新生成 settings.schema.json 后即可合并;建议在同一次推送里把 -realtime-plus 的措辞放软。
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Test Plan (not a blocker): 221 tests passing — this review observed 5832, 504 passed.
— qwen3.8-max via Qwen Code /review (v0.23.0)
| default: '', | ||
| description: | ||
| 'Path to a custom keyterms file (one term per line, "#" for comments) that biases voice transcription toward domain-specific terms. Relative paths resolve from the workspace root; defaults to ".qwen/voice-keyterms.txt" when present. The file contents are sent to the ASR provider and it is read only in trusted workspaces. Only applies to Qwen ASR models (qwen3-asr-*).', | ||
| 'Path to a custom keyterms file (one term per line, "#" for comments) that biases voice transcription toward domain-specific terms. Relative paths resolve from the workspace root; defaults to ".qwen/voice-keyterms.txt" when present. The file contents are sent to the ASR provider and it is read only in trusted workspaces. Only applies to Qwen ASR models (qwen3-asr-* and the qwen-audio-* ASR family).', |
There was a problem hiding this comment.
[Critical] R1-1: [fails-closed] [regression] The keytermsFile description was updated here, but the generated packages/vscode-ide-companion/schemas/settings.schema.json was not regenerated — its line 105 still reads Only applies to Qwen ASR models (qwen3-asr-*).. This PR touches packages/cli and packages/web-shell, so CI classifies it into the full profile; the lint_and_static job then runs npm run generate:settings-schema followed by the "Check settings schema is up-to-date" step (.github/workflows/ci.yml:1171-1185), which exits 1 when the schema file is dirty — and regenerating rewrites exactly this description. The PR therefore fails that CI gate as committed, and until the artifact is regenerated, VS Code settings IntelliSense keeps showing the stale family list, contradicting the docs this same PR updates.
Witness:
$ npm run generate:settings-schema (scratch tree at ef99e56625)
exit 0
$ git status --porcelain packages/vscode-ide-companion/schemas/settings.schema.json
M packages/vscode-ide-companion/schemas/settings.schema.json
- "description": "...Only applies to Qwen ASR models (qwen3-asr-*).",
+ "description": "...Only applies to Qwen ASR models (qwen3-asr-* and the qwen-audio-* ASR family).",
Regenerate and commit the artifact — do not hand-edit the JSON:
npm run generate:settings-schema
Note that scripts/generate-settings-schema.ts copies setting.description verbatim from SETTINGS_SCHEMA and rewrites the whole file (and scripts/build.js re-runs the generator after CLI builds), so the fix must be a regeneration, not a hand-edit of the description line. Acceptance criterion: the CI freshness gate itself — after npm run generate:settings-schema, git status --porcelain packages/vscode-ide-companion/schemas/settings.schema.json must be empty; without the regenerated artifact committed, the "Check settings schema is up-to-date" step exits 1.
— qwen3.8-max via Qwen Code /review (v0.23.0)
| ) { | ||
| return 'qwen-asr-realtime'; | ||
| } | ||
| if (/^qwen-audio-[\d.]+-asr-flash(?:-\d{4}-\d{2}-\d{2})?$/.test(id)) { |
There was a problem hiding this comment.
[Suggestion] R1-2: This diff makes the qwen-audio-*-asr-flash batch family selectable, but two recovery messages newly reachable for it still recommend only legacy ids: packages/cli/src/services/voice-transcriber.ts:843 ("Use qwen3-asr-flash for batch or choose a realtime voice model such as qwen3-asr-flash-realtime / fun-asr-realtime / paraformer-realtime-v2") and packages/cli/src/ui/hooks/use-voice-input.ts:335 ("switch voiceModel to qwen3-asr-flash for batch transcription"). A Token Plan user — the population issue 10932 enables — configures qwen-audio-3.0-asr-flash, which now passes the selection guard; if the endpoint then rejects it as model_not_supported, or a streaming qwen-audio model is selected without the native audio-capture module, the guidance sends them to qwen3-asr-flash — but the issue's own live probes show qwen3-asr-flash answering 404 model_not_found on the Token Plan gateway, so the user is pointed at a model their plan does not serve instead of their own family's batch variant, a second dead end.
Witness:
probe driving the real transcribeVoiceAudio() with a stubbed 400 model_not_supported response:
OBSERVED[qwen-audio-3.0-asr-flash]: This voice model cannot be used for batch
transcription. Use qwen3-asr-flash for batch or choose a realtime voice model such as
qwen3-asr-flash-realtime / fun-asr-realtime / paraformer-realtime-v2.
Extend both messages to also name the qwen-audio-*-asr-flash batch family — e.g. "Use qwen3-asr-flash or qwen-audio--asr-flash for batch ..." — or phrase the guidance generically ("a batch-capable ASR model").
— qwen3.8-max via Qwen Code /review (v0.23.0)
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterFull-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |










What this PR does
Teaches the voice pipeline about the new
qwen-audio-*Token Plan ASR model family, soresolveVoiceTransport()no longer rejects it asunsupported:packages/cli/src/services/voice-model.ts— adds two family rules inresolveVoiceTransport():qwen-audio-<version>-asr-flash(bare / dated snapshots) →qwen-asr-chat(same shape as the legacyqwen3-asr-flash)qwen-audio-<version>-asr-flash-streaming/-realtime/qwen-audio-<version>-realtime[-plus]→qwen-asr-realtime*-filetrans/*-tts-plus/ unrelated models still resolve tounsupported; the version segment is[\d.]+so future 4.x IDs keep workingpackages/web-shell/client/voice/voiceModels.ts— mirrors the same two patterns in the web-shell's hand-maintainedisVoiceModelId()allowlist (the second gate the triage pointed out)packages/cli/src/config/settingsSchema.ts+docs/users/configuration/settings.md— extends thekeytermsFiledescription to mention theqwen-audio-*ASR family (keyterms biasing hangs off theqwen-asr-realtimetransport, so new-family realtime variants inherit it)deriveWebSocketBase()already strips the/compatible-mode/v1suffix, so the Token Plan realtime endpoint works without URL plumbing —qwen-audio-3.0-realtime-plusis immediately usable for Token Plan dictation today (verified by the community probes in the issue).Why it's needed
Fixes #10932. Model Studio's Token Plan serves speech recognition under the new family ID
qwen-audio-3.0-asr-flash, but the client hardcodes only the legacy IDs, so voice transcription cannot select any Token Plan ASR model even where the server side accepts it. The gate is purely the client-side allowlist.Note: community probing in the issue shows the bare
qwen-audio-3.0-asr-flashis currently rejected by the Token Plan gateway on all three transports (server-side routing gap), and*-streamingvariants are not provisioned yet. This PR fixes the client side so those work the moment the server provisions them; it does not (and cannot) fix server-side routing.Reviewer Test Plan
How to verify
npx vitest run packages/cli/src/ui/voice/voice-model.test.ts packages/web-shell/client/voice/voiceModels.test.ts— newresolveVoiceTransportcases cover: bareqwen-audio-3.0-asr-flash→qwen-asr-chat;qwen-audio-3.0-asr-flash-streamingandqwen-audio-3.0-realtime-plus→qwen-asr-realtime; unrelated /-filetrans/-tts-plusIDs →unsupported.voice-service,voice-command,modelCommand,use-voice-input(24),ModelDialog(60),resolve-voice-config— 221 tests passing.tsc --noEmitforpackages/cliandpackages/web-shell, plus eslint and prettier, all exit 0.Evidence (Before & After)
Before: selecting a Token Plan ASR model rejects dictation with "unsupported model" from
resolveVoiceTransport().After: the model resolves to the proper chat/realtime transport (see tests above). N/A for screenshots — no visual surface changed.
Tested on