mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-11 17:43:43 -10:00
189 lines
6.1 KiB
TypeScript
189 lines
6.1 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import {
|
|
encodeVisionModelForSetting,
|
|
extractBareModelId,
|
|
decodeVisionModelForPicker,
|
|
resolveFastModelForPicker,
|
|
} from './modelEncoding';
|
|
|
|
describe('encodeVisionModelForSetting', () => {
|
|
it('encodes standard ACP format', () => {
|
|
expect(encodeVisionModelForSetting('qwen-max(qwen-oauth)')).toBe(
|
|
'qwen-oauth:qwen-max',
|
|
);
|
|
});
|
|
|
|
it('passes through non-ACP format unchanged', () => {
|
|
expect(encodeVisionModelForSetting('plain-model')).toBe('plain-model');
|
|
});
|
|
|
|
it('passes through empty parens unchanged', () => {
|
|
expect(encodeVisionModelForSetting('model()')).toBe('model()');
|
|
});
|
|
|
|
it('passes through colon-bearing IDs unchanged', () => {
|
|
expect(encodeVisionModelForSetting('openai:gpt-4o')).toBe('openai:gpt-4o');
|
|
});
|
|
|
|
it('handles nested parentheses by matching outermost ACP pattern', () => {
|
|
// The regex matches the outermost group: modelId(authType)
|
|
expect(encodeVisionModelForSetting('model(name)(extra)')).toBe(
|
|
'extra:model(name)',
|
|
);
|
|
});
|
|
|
|
it('passes through already-encoded colon format unchanged', () => {
|
|
// If someone somehow passes an already-encoded authType:modelId,
|
|
// it should be left alone — not double-encoded.
|
|
expect(encodeVisionModelForSetting('qwen-oauth:qwen-vl-max')).toBe(
|
|
'qwen-oauth:qwen-vl-max',
|
|
);
|
|
});
|
|
|
|
it('passes through malformed — bare authType only', () => {
|
|
// '(authType)' has no modelId before the parens and no text
|
|
// before the opening paren that can serve as group 1, so the
|
|
// regex won't match and we get the input back unchanged.
|
|
expect(encodeVisionModelForSetting('(authType)')).toBe('(authType)');
|
|
});
|
|
|
|
it('passes through malformed — unclosed paren', () => {
|
|
expect(encodeVisionModelForSetting('modelId(')).toBe('modelId(');
|
|
});
|
|
|
|
it('passes through malformed — double-parens inside', () => {
|
|
expect(encodeVisionModelForSetting('a((b))')).toBe('a((b))');
|
|
});
|
|
|
|
it('passes through empty string unchanged', () => {
|
|
expect(encodeVisionModelForSetting('')).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('resolveFastModelForPicker', () => {
|
|
it('matches an endpoint pin uniquely and leaves missing or ambiguous pins unresolved', () => {
|
|
const models = [
|
|
{
|
|
id: 'qwen-route:v1:a',
|
|
baseModelId: 'shared',
|
|
authType: 'openai',
|
|
baseUrl: 'https://first.example/v1',
|
|
},
|
|
{
|
|
id: 'qwen-route:v1:b',
|
|
baseModelId: 'shared',
|
|
authType: 'openai',
|
|
baseUrl: 'https://second.example/v1?token=value#fragment',
|
|
},
|
|
{ id: 'unique(openai)', baseModelId: 'unique', authType: 'openai' },
|
|
];
|
|
expect(
|
|
resolveFastModelForPicker(
|
|
'openai:shared\0https://SECOND.example/v1',
|
|
models,
|
|
),
|
|
).toBe('qwen-route:v1:b');
|
|
const missing = 'openai:shared\0https://removed.example/v1';
|
|
expect(resolveFastModelForPicker(missing, models)).toBe(missing);
|
|
expect(resolveFastModelForPicker('openai:shared', models)).toBe(
|
|
'openai:shared',
|
|
);
|
|
expect(resolveFastModelForPicker('unique', models)).toBe('unique(openai)');
|
|
const samePublicEndpoint = models.map((model) => ({
|
|
...model,
|
|
baseUrl: 'https://second.example/v1',
|
|
}));
|
|
const ambiguous = 'openai:shared\0https://second.example/v1';
|
|
expect(resolveFastModelForPicker(ambiguous, samePublicEndpoint)).toBe(
|
|
ambiguous,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('extractBareModelId', () => {
|
|
it('extracts bare model ID from ACP format', () => {
|
|
expect(extractBareModelId('qwen-max(qwen-oauth)')).toBe('qwen-max');
|
|
});
|
|
|
|
it('passes through non-ACP format unchanged', () => {
|
|
expect(extractBareModelId('plain-model')).toBe('plain-model');
|
|
});
|
|
|
|
it('passes through empty parens unchanged', () => {
|
|
// The regex requires at least one char in each capture group,
|
|
// so '()' does not match and passes through.
|
|
expect(extractBareModelId('()')).toBe('()');
|
|
});
|
|
});
|
|
|
|
describe('decodeVisionModelForPicker', () => {
|
|
it('decodes authType:modelId back to ACP format', () => {
|
|
expect(decodeVisionModelForPicker('qwen-oauth:qwen-max')).toBe(
|
|
'qwen-max(qwen-oauth)',
|
|
);
|
|
});
|
|
|
|
it('handles colon-bearing model IDs — splits on first colon only', () => {
|
|
expect(decodeVisionModelForPicker('openai:gpt-4o:online')).toBe(
|
|
'gpt-4o:online(openai)',
|
|
);
|
|
});
|
|
|
|
it('passes through values without a colon', () => {
|
|
expect(decodeVisionModelForPicker('plain-model')).toBe('plain-model');
|
|
});
|
|
|
|
it('passes through ACP-formatted values unchanged', () => {
|
|
expect(decodeVisionModelForPicker('qwen-max(qwen-oauth)')).toBe(
|
|
'qwen-max(qwen-oauth)',
|
|
);
|
|
});
|
|
|
|
it('passes through empty string unchanged', () => {
|
|
expect(decodeVisionModelForPicker('')).toBe('');
|
|
});
|
|
|
|
it('passes through leading-colon malformed input', () => {
|
|
// colonIdx === 0, which is not > 0, so passthrough
|
|
expect(decodeVisionModelForPicker(':modelId')).toBe(':modelId');
|
|
});
|
|
|
|
it('strips \\0baseUrl suffix before decoding', () => {
|
|
expect(
|
|
decodeVisionModelForPicker(
|
|
'qwen-oauth:qwen-vl-max\0https://api.example.com',
|
|
),
|
|
).toBe('qwen-vl-max(qwen-oauth)');
|
|
});
|
|
|
|
it('handles colon-bearing ID with \\0baseUrl suffix', () => {
|
|
expect(
|
|
decodeVisionModelForPicker(
|
|
'openai:gpt-4o:online\0https://api.openai.com',
|
|
),
|
|
).toBe('gpt-4o:online(openai)');
|
|
});
|
|
|
|
it('passes through bare ID with \\0baseUrl suffix but no colon', () => {
|
|
expect(
|
|
decodeVisionModelForPicker('plain-model\0https://api.example.com'),
|
|
).toBe('plain-model');
|
|
});
|
|
});
|
|
|
|
describe('round-trip: encode then decode', () => {
|
|
it('preserves the original ACP format', () => {
|
|
const original = 'qwen-vl-max(qwen-oauth)';
|
|
const encoded = encodeVisionModelForSetting(original);
|
|
const decoded = decodeVisionModelForPicker(encoded);
|
|
expect(decoded).toBe(original);
|
|
});
|
|
|
|
it('preserves colon-bearing IDs after round-trip', () => {
|
|
const original = 'gpt-4o:new-model(openai)';
|
|
const encoded = encodeVisionModelForSetting(original);
|
|
const decoded = decodeVisionModelForPicker(encoded);
|
|
expect(decoded).toBe(original);
|
|
});
|
|
});
|