-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(cli): accept the qwen-audio Token Plan ASR family in voice transport resolution #10981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,15 @@ export type VoiceTransport = | |
| | 'dashscope-task-realtime' | ||
| | 'unsupported'; | ||
|
|
||
| /** Map a model id to the ASR transport it uses, or 'unsupported'. */ | ||
| /** | ||
| * Map a model id to the ASR transport it uses, or 'unsupported'. | ||
| * | ||
| * The `qwen-audio-<version>` family (Model Studio Token Plan, #10932) follows | ||
| * the same split as `qwen3-asr-flash`: `*-realtime` and `*-asr-flash-streaming` | ||
| * ids speak the OpenAI realtime WebSocket dialect, while bare and date-suffixed | ||
| * `*-asr-flash` ids use the batch chat/completions shape. Other ids in the | ||
| * family (filetrans, tts) are not dictation transports and stay unsupported. | ||
| */ | ||
| export function resolveVoiceTransport(model: string): VoiceTransport { | ||
| const id = model.toLowerCase(); | ||
| if (/^qwen3-asr-flash-realtime(?:-|$)/.test(id)) { | ||
|
|
@@ -22,6 +30,16 @@ export function resolveVoiceTransport(model: string): VoiceTransport { | |
| if (/^qwen3-asr-flash(?:-\d{4}-\d{2}-\d{2})?$/.test(id)) { | ||
| return 'qwen-asr-chat'; | ||
| } | ||
| if ( | ||
| /^qwen-audio-[\d.]+-(?:asr-flash-(?:realtime|streaming)|realtime)(?:-|$)/.test( | ||
| id, | ||
| ) | ||
| ) { | ||
| return 'qwen-asr-realtime'; | ||
| } | ||
| if (/^qwen-audio-[\d.]+-asr-flash(?:-\d{4}-\d{2}-\d{2})?$/.test(id)) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] R1-2: This diff makes the Witness: Extend both messages to also name the — qwen3.8-max via Qwen Code /review (v0.23.0) |
||
| return 'qwen-asr-chat'; | ||
| } | ||
| if (/^(fun-asr|paraformer).*realtime(?:-|$)/.test(id)) { | ||
| return 'dashscope-task-realtime'; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| /** | ||
| * @license | ||
| * Copyright 2025 Qwen | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| import { describe, expect, it } from 'vitest'; | ||
| import { extractVoiceModels, isVoiceModelId } from './voiceModels'; | ||
|
|
||
| describe('isVoiceModelId', () => { | ||
| it('accepts the qwen-audio Token Plan ASR family (#10932)', () => { | ||
| expect(isVoiceModelId('qwen-audio-3.0-asr-flash')).toBe(true); | ||
| expect(isVoiceModelId('qwen-audio-3.0-asr-flash-2026-09-01')).toBe(true); | ||
| expect(isVoiceModelId('qwen-audio-3.0-asr-flash-streaming')).toBe(true); | ||
| expect(isVoiceModelId('qwen-audio-3.0-asr-flash-realtime')).toBe(true); | ||
| expect(isVoiceModelId('qwen-audio-3.0-realtime-plus')).toBe(true); | ||
| expect(isVoiceModelId('Qwen-Audio-3.0-ASR-Flash')).toBe(true); | ||
| }); | ||
|
|
||
| it('keeps accepting the legacy ASR ids', () => { | ||
| expect(isVoiceModelId('qwen3-asr-flash')).toBe(true); | ||
| expect(isVoiceModelId('qwen3-asr-flash-2025-08-20')).toBe(true); | ||
| expect(isVoiceModelId('qwen3-asr-flash-realtime')).toBe(true); | ||
| expect(isVoiceModelId('fun-asr-realtime')).toBe(true); | ||
| expect(isVoiceModelId('paraformer-realtime-v2')).toBe(true); | ||
| }); | ||
|
|
||
| it('rejects non-ASR ids, including filetrans and tts in the family', () => { | ||
| expect(isVoiceModelId('gpt-4o')).toBe(false); | ||
| expect(isVoiceModelId('qwen3-asr-flash-filetrans')).toBe(false); | ||
| expect(isVoiceModelId('qwen-audio-3.0-asr-flash-filetrans')).toBe(false); | ||
| expect(isVoiceModelId('qwen-audio-3.0-tts-plus')).toBe(false); | ||
| }); | ||
| }); | ||
|
|
||
| describe('extractVoiceModels', () => { | ||
| it('lists qwen-audio ASR ids from a providers status and dedupes', () => { | ||
| const status = { | ||
| providers: [ | ||
| { | ||
| authType: 'USE_OPENAI', | ||
| models: [ | ||
| { | ||
| baseModelId: 'qwen-audio-3.0-asr-flash', | ||
| name: 'Qwen Audio ASR', | ||
| baseUrl: 'https://token-plan.example/compatible-mode/v1', | ||
| contextLimit: 8000, | ||
| }, | ||
| { | ||
| // Registered with an auth suffix; the base id is the voice id. | ||
| baseModelId: 'qwen-audio-3.0-asr-flash', | ||
| name: 'Qwen Audio ASR (duplicate provider)', | ||
| }, | ||
| { baseModelId: 'qwen-max', name: 'Qwen Max' }, | ||
| { baseModelId: 'qwen-audio-3.0-asr-flash-filetrans' }, | ||
| { isRuntime: true, baseModelId: 'qwen3-asr-flash-realtime' }, | ||
| ], | ||
| }, | ||
| ], | ||
| }; | ||
| const options = extractVoiceModels(status); | ||
| expect(options).toHaveLength(1); | ||
| expect(options[0]).toEqual({ | ||
| id: 'qwen-audio-3.0-asr-flash', | ||
| label: 'Qwen Audio ASR', | ||
| authType: 'USE_OPENAI', | ||
| baseUrl: 'https://token-plan.example/compatible-mode/v1', | ||
| contextWindow: 8000, | ||
| modalities: { audio: true }, | ||
| }); | ||
| }); | ||
|
|
||
| it('returns an empty list for an undefined status', () => { | ||
| expect(extractVoiceModels(undefined)).toEqual([]); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Critical] R1-1: [fails-closed] [regression] The
keytermsFiledescription was updated here, but the generatedpackages/vscode-ide-companion/schemas/settings.schema.jsonwas not regenerated — its line 105 still readsOnly applies to Qwen ASR models (qwen3-asr-*).. This PR touchespackages/cliandpackages/web-shell, so CI classifies it into thefullprofile; thelint_and_staticjob then runsnpm run generate:settings-schemafollowed 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:
Regenerate and commit the artifact — do not hand-edit the JSON:
Note that
scripts/generate-settings-schema.tscopiessetting.descriptionverbatim fromSETTINGS_SCHEMAand rewrites the whole file (andscripts/build.jsre-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 — afternpm run generate:settings-schema,git status --porcelain packages/vscode-ide-companion/schemas/settings.schema.jsonmust 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)