mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-22 15:15:18 +00:00
* feat(voice): support trusted private ASR base URLs * fix(voice): address private endpoint review findings * test(voice): cover private endpoint edge cases * test(voice): pin remaining endpoint edge cases * fix(voice): address private endpoint review feedback * fix(voice): clarify allowlist URL and normalize IPv6 * fix(voice): harden NAT64 address validation * fix(voice): address managed endpoint review findings * refactor(voice): extract shared IPv6 transition unwrap ladder (#8350) Deduplicate the IPv6-transition unwrapping sequence (mapped, compatible, NAT64, dotted-quad) that was repeated verbatim between isPrivateNetworkIp and isAlwaysBlockedVoiceAddress on both CLI and Desktop surfaces. A single unwrapIpv6TransitionStep helper now yields the next canonical address (or 'blocked' for unrecognized ::ffff: forms), and each predicate recurses through it, preserving the exact re-check semantics at every unwrap level. * test(voice): cover allowInsecureBaseUrl wiring through desktop default transports (#8350) * fix(voice): add allowlist hint to private-network rejection error (#8350) * fix(voice): reject always-blocked base URLs before offering the allowlist hint (#8350) * fix(voice): resolve exact desktop voice provider before OAuth (#8350) * fix(voice): address review feedback for trusted private base URLs (#8350) * fix(voice): align desktop voice resolution with CLI semantics (#8350) * fix(voice): scope desktop fail-closed resolution to policy-bearing entries (#8350) * fix(voice): address round-8 review findings for trusted private base URLs (#8350) Run the invasive process-global `mock.module('ws')` suite as voice-ws-handler.isolated.ts so the desktop package's single-process `bun test` run no longer leaks the fake socket into unrelated ws consumers; the existing isolated loop runs it in its own process. Shape-guard the desktop provider scan: non-object modelProviders elements are skipped (falling through to OAuth instead of throwing a raw TypeError), and non-string baseUrl/envKey/settings.env values on a voice-model entry now surface the PROVIDER_ENTRY_REMEDY remediation error instead of crashing. Compute the DashScope-compatible /v1 rewrite before any allowlist match in fromExactModelProvider so the stage-1 check, the remediation messages, and the top-level recheck all compare the same final URL and a single allowlist entry converges for split-horizon deployments. Extend the CLI allowlist remediation messages to state which settings scopes honor the entry, since serve mode never shows the interactive workspace-strip warning. Thread providerProtocol through the CLI voice model seams (createVoiceModelSource and the daemon buildModelsConfig) so protocol-mapped custom provider groups resolve like the rest of the CLI model surface, and document the remaining protocol-agnostic desktop scan in the design doc. Correct the getHomeEnvFallback comment: it adopts the narrower getHomeEnvFallbackVars candidate set on purpose. Add multi-record DNS answer tests on both CLI and desktop net guards so the records.some classification is pinned against the array shape defaultLookupHost always produces in production. * fix(voice): address round-9 review findings for trusted private base URLs (#8350) * fix(voice): address round-10 review findings for trusted private base URLs (#8350) * fix(voice): classify desktop voice duplicates before ambiguity check (#8350) * fix(scripts): compare voice guard mirrors as parse trees (#8350) --------- Co-authored-by: rockybot2026 <265985139+rockybot2026@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
164 lines
5 KiB
JavaScript
164 lines
5 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2026 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
import {
|
|
checkMirrorSet,
|
|
extractTopLevelUnit,
|
|
normalizeMirroredCode,
|
|
} from '../check-voice-guard-sync.js';
|
|
|
|
const checkScriptPath = fileURLToPath(
|
|
new URL('../check-voice-guard-sync.js', import.meta.url),
|
|
);
|
|
|
|
const CLI_STYLE = [
|
|
'function isPrivate(host: string): boolean {',
|
|
' // Blocks IP-literal private networks only.',
|
|
' if (isLoopbackHost(host)) {',
|
|
' return false;',
|
|
' }',
|
|
' return host.startsWith("10.");',
|
|
'}',
|
|
].join('\n');
|
|
|
|
const DESKTOP_STYLE = [
|
|
'export function isPrivate(host: string): boolean {',
|
|
' /** Different comment. */',
|
|
' if (isLoopbackHost(host)) return false;',
|
|
' return host.startsWith("10.");',
|
|
'}',
|
|
].join('\n');
|
|
|
|
describe('check-voice-guard-sync', () => {
|
|
it('ignores comments, whitespace, brace style, and the export modifier', () => {
|
|
expect(
|
|
checkMirrorSet(CLI_STYLE, DESKTOP_STYLE, [
|
|
{ kind: 'function', name: 'isPrivate' },
|
|
]),
|
|
).toEqual([]);
|
|
});
|
|
|
|
it('reports a changed body as drift', () => {
|
|
const drifted = DESKTOP_STYLE.replace('10.', '172.');
|
|
expect(
|
|
checkMirrorSet(CLI_STYLE, drifted, [
|
|
{ kind: 'function', name: 'isPrivate' },
|
|
]),
|
|
).toEqual([{ name: 'isPrivate', reason: 'bodies differ' }]);
|
|
});
|
|
|
|
it('reports a unit missing from one side', () => {
|
|
expect(
|
|
checkMirrorSet(CLI_STYLE, '', [{ kind: 'function', name: 'isPrivate' }]),
|
|
).toEqual([{ name: 'isPrivate', reason: 'missing in the desktop file' }]);
|
|
expect(
|
|
checkMirrorSet('', DESKTOP_STYLE, [
|
|
{ kind: 'function', name: 'isPrivate' },
|
|
]),
|
|
).toEqual([{ name: 'isPrivate', reason: 'missing in the CLI file' }]);
|
|
});
|
|
|
|
it('extracts a function up to its column-0 closing brace', () => {
|
|
const source = `${DESKTOP_STYLE}\n\nfunction next(): void {}\n`;
|
|
expect(
|
|
extractTopLevelUnit(source, { kind: 'function', name: 'isPrivate' }),
|
|
).toBe(DESKTOP_STYLE);
|
|
});
|
|
|
|
it('extracts a const+for block unit', () => {
|
|
const block = [
|
|
'const BLOCKS = new BlockList();',
|
|
"for (const [address, prefix] of [['2001::', 23]] as const) {",
|
|
" BLOCKS.addSubnet(address, prefix, 'ipv6');",
|
|
'}',
|
|
].join('\n');
|
|
const source = `${block}\n\nfunction next(): void {}\n`;
|
|
expect(extractTopLevelUnit(source, { kind: 'block', name: 'BLOCKS' })).toBe(
|
|
block,
|
|
);
|
|
});
|
|
|
|
it('compares literal contents exactly and drops comments', () => {
|
|
const source = [
|
|
'function url(host: string): string {',
|
|
' // Prefix with the ASR scheme.',
|
|
' return `http://[${host}]/`;',
|
|
'}',
|
|
].join('\n');
|
|
const normalized = normalizeMirroredCode(source);
|
|
expect(normalized).toContain('`http://[${host}]/`');
|
|
expect(normalized).not.toContain('Prefix with the ASR scheme');
|
|
});
|
|
|
|
it('reports drift when braces are removed inside a template literal', () => {
|
|
const cli = [
|
|
'function f(a: string, prefix: string): string {',
|
|
' return `${a}${prefix}`;',
|
|
'}',
|
|
].join('\n');
|
|
const desktop = cli.replace('${prefix}', '$prefix');
|
|
expect(
|
|
checkMirrorSet(cli, desktop, [{ kind: 'function', name: 'f' }]),
|
|
).toEqual([{ name: 'f', reason: 'bodies differ' }]);
|
|
});
|
|
|
|
it('reports drift when whitespace changes inside a string literal', () => {
|
|
const cli = [
|
|
'function f(prefix: string): string {',
|
|
" return prefix.replace('/compatible-mode/v1', '');",
|
|
'}',
|
|
].join('\n');
|
|
const desktop = cli.replace(
|
|
"'/compatible-mode/v1'",
|
|
"'/compatible-mode/ v1'",
|
|
);
|
|
expect(
|
|
checkMirrorSet(cli, desktop, [{ kind: 'function', name: 'f' }]),
|
|
).toEqual([{ name: 'f', reason: 'bodies differ' }]);
|
|
});
|
|
|
|
it('reports drift when a newline changes return semantics', () => {
|
|
const cli = ['function f(): number {', ' return 1;', '}'].join('\n');
|
|
const desktop = ['function f(): number {', ' return', ' 1;', '}'].join(
|
|
'\n',
|
|
);
|
|
expect(
|
|
checkMirrorSet(cli, desktop, [{ kind: 'function', name: 'f' }]),
|
|
).toEqual([{ name: 'f', reason: 'bodies differ' }]);
|
|
});
|
|
|
|
it('reports drift when a statement moves into or out of a block', () => {
|
|
const cli = [
|
|
'function f(x: boolean): void {',
|
|
' if (x) {',
|
|
' a();',
|
|
' }',
|
|
' b();',
|
|
'}',
|
|
].join('\n');
|
|
const desktop = [
|
|
'function f(x: boolean): void {',
|
|
' if (x) {',
|
|
' a();',
|
|
' b();',
|
|
' }',
|
|
'}',
|
|
].join('\n');
|
|
expect(
|
|
checkMirrorSet(cli, desktop, [{ kind: 'function', name: 'f' }]),
|
|
).toEqual([{ name: 'f', reason: 'bodies differ' }]);
|
|
});
|
|
|
|
it('passes on the real mirrored sources', () => {
|
|
const output = execFileSync(process.execPath, [checkScriptPath], {
|
|
encoding: 'utf8',
|
|
});
|
|
expect(output).toContain('Voice guard mirror check passed.');
|
|
});
|
|
});
|