Merge pull request #1308 from simonsmh/feat/add-qoder-provider

feat(cliproxy): Qoder provider
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-05-30 15:35:55 -04:00
commit f815db391d
21 files changed
+236 -1

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
{
"env": {
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8317/api/provider/qoder",
"ANTHROPIC_AUTH_TOKEN": "ccs-internal-managed",
"ANTHROPIC_MODEL": "qoder/auto",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "qoder/ultimate",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "qoder/performance",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "qoder/efficient"
}
}
@@ -46,6 +46,7 @@ describe('provider-capabilities', () => {
'gitlab',
'codebuddy',
'kilo',
'qoder',
]);
});
@@ -66,6 +67,7 @@ describe('provider-capabilities', () => {
'cursor',
'codebuddy',
'kilo',
'qoder',
]);
expect(getProvidersByOAuthFlow('authorization_code')).toEqual([
'gemini',
@@ -85,6 +87,7 @@ describe('provider-capabilities', () => {
'kimi',
'codebuddy',
'kilo',
'qoder',
]);
});
+7
View File
@@ -306,6 +306,13 @@ export const OAUTH_CONFIGS: Record<CLIProxyProvider, ProviderOAuthConfig> = {
scopes: [],
authFlag: '--kilo-login',
},
qoder: {
provider: 'qoder',
displayName: 'Qoder',
authUrl: 'https://qoder.com/device/selectAccounts',
scopes: [],
authFlag: '--qoder-login',
},
};
/**
@@ -8,6 +8,7 @@ describe('base-config-loader new providers', () => {
['gitlab', '/api/provider/gitlab', 'gitlab-duo'],
['codebuddy', '/api/provider/codebuddy', 'auto'],
['kilo', '/api/provider/kilo', 'kilo/auto'],
['qoder', '/api/provider/qoder', 'qoder/auto'],
] as const)('loads base settings for %s', (provider, baseUrlPath, defaultModel) => {
const config = loadBaseConfig(provider);
+62
View File
@@ -265,6 +265,68 @@ export const MODEL_CATALOG: Partial<Record<CLIProxyProvider, ProviderCatalog>> =
},
],
},
qoder: {
provider: 'qoder',
displayName: 'Qoder',
defaultModel: 'qoder/auto',
models: [
{
id: 'qoder/auto',
name: 'Qoder Auto',
description: 'Auto selects the best Qoder model for your prompt',
},
{
id: 'qoder/ultimate',
name: 'Qoder Ultimate',
description: 'Highest quality Qoder tier',
},
{
id: 'qoder/performance',
name: 'Qoder Performance',
description: 'Balanced quality and speed',
},
{
id: 'qoder/efficient',
name: 'Qoder Efficient',
description: 'Cost-efficient Qoder tier',
},
{
id: 'qoder/lite',
name: 'Qoder Lite',
description: 'Fastest and most affordable Qoder tier',
},
{
id: 'qoder/qmodel',
name: 'Qwen 3.6 Plus (via Qoder)',
description: 'Qwen 3.6 Plus frontier model',
},
{
id: 'qoder/dmodel',
name: 'DeepSeek V4 Pro (via Qoder)',
description: 'DeepSeek V4 Pro frontier model',
},
{
id: 'qoder/dfmodel',
name: 'DeepSeek V4 Flash (via Qoder)',
description: 'DeepSeek V4 Flash frontier model',
},
{
id: 'qoder/gm51model',
name: 'GLM 5.1 (via Qoder)',
description: 'GLM 5.1 frontier model',
},
{
id: 'qoder/kmodel',
name: 'Kimi K2.6 (via Qoder)',
description: 'Kimi K2.6 frontier model',
},
{
id: 'qoder/mmodel',
name: 'MiniMax M2.7 (via Qoder)',
description: 'MiniMax M2.7 frontier model',
},
],
},
kimi: {
provider: 'kimi',
displayName: 'Kimi (Moonshot)',
+12
View File
@@ -182,6 +182,18 @@ export const PROVIDER_CAPABILITIES: Record<CLIProxyProvider, ProviderCapabilitie
tokenTypeValues: ['kilo'],
aliases: [],
},
qoder: {
displayName: 'Qoder',
description: 'Qoder AI coding assistant',
oauthFlow: 'device_code',
callbackPort: null,
callbackProviderName: 'qoder',
authUrlProviderName: 'qoder',
refreshOwnership: 'unsupported',
authFilePrefixes: ['qoder-'],
tokenTypeValues: ['qoder'],
aliases: [],
},
};
export const CLIPROXY_PROVIDER_IDS = Object.freeze(
+1
View File
@@ -38,6 +38,7 @@ const CHANNEL_TO_PROVIDER: Record<string, CLIProxyProvider> = {
kimi: 'kimi',
kiro: 'kiro',
'github-copilot': 'ghcp',
qoder: 'qoder',
};
/** CCS provider → channel name mapping (reverse) */
+3 -1
View File
@@ -16,7 +16,8 @@ export type CLIProxyProvider =
| 'cursor'
| 'gitlab'
| 'codebuddy'
| 'kilo';
| 'kilo'
| 'qoder';
/** CLIProxy backend selection */
export type CLIProxyBackend = 'original' | 'plus';
@@ -32,6 +33,7 @@ export const PLUS_ONLY_PROVIDERS: CLIProxyProvider[] = [
'gitlab',
'codebuddy',
'kilo',
'qoder',
];
/** Model mapping for each provider */
+1
View File
@@ -205,6 +205,7 @@ export const BUILTIN_PROVIDER_SHORTCUTS: readonly ShortcutEntry[] = CLIPROXY_PRO
gitlab: 'GitLab Duo via CLIProxy OAuth',
codebuddy: 'CodeBuddy via CLIProxy OAuth',
kilo: 'Kilo AI via CLIProxy OAuth',
qoder: 'Qoder AI via CLIProxy OAuth',
}[name] || 'CLIProxy OAuth provider',
})
);
+1
View File
@@ -16,6 +16,7 @@ export const RESERVED_PROFILE_NAMES = [
'gitlab',
'codebuddy',
'kilo',
'qoder',
// Copilot API (GitHub Copilot proxy)
'copilot',
// Cursor IDE (Cursor proxy daemon)
+1
View File
@@ -57,6 +57,7 @@ const PROVIDER_OWNER_HINTS: Record<string, string[]> = {
kimi: ['kimi', 'moonshot'],
kiro: ['kiro', 'aws'],
ghcp: ['github', 'copilot'],
qoder: ['qoder'],
};
function normalize(value: string | null | undefined): string {
+1
View File
@@ -97,6 +97,7 @@ describe('ProfileDetector', () => {
expect(detector.detectProfileType('gitlab').provider).toBe('gitlab');
expect(detector.detectProfileType('codebuddy').provider).toBe('codebuddy');
expect(detector.detectProfileType('kilo').provider).toBe('kilo');
expect(detector.detectProfileType('qoder').provider).toBe('qoder');
});
it('should detect settings-based profile from unified config', () => {
@@ -85,6 +85,7 @@ describe('completion backend', () => {
expect(values).toContain('gitlab');
expect(values).toContain('codebuddy');
expect(values).toContain('kilo');
expect(values).toContain('qoder');
expect(values).toContain('localglm');
expect(values).toContain('work');
expect(values).toContain('my-codex');
@@ -61,6 +61,7 @@ describe('help command parity', () => {
expect(rendered.includes('gitlab')).toBe(true);
expect(rendered.includes('codebuddy')).toBe(true);
expect(rendered.includes('kilo')).toBe(true);
expect(rendered.includes('qoder')).toBe(true);
expect(rendered.includes('--gitlab-token-login')).toBe(true);
expect(rendered.includes('--token-login')).toBe(true);
expect(rendered.includes('--gitlab-url <url>')).toBe(true);
@@ -18,6 +18,7 @@ describe('cliproxy-auth-routes start-url guard', () => {
"Provider 'codebuddy' uses Device Code flow"
);
expect(getStartUrlUnsupportedReason('kilo')).toContain("Provider 'kilo' uses Device Code flow");
expect(getStartUrlUnsupportedReason('qoder')).toContain("Provider 'qoder' uses Device Code flow");
});
it('allows Cursor browser URL auth on start-url', () => {
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="180" height="180" viewBox="0 0 180 180"><defs><clipPath id="master_svg0_381_5952"><rect x="0" y="0" width="180" height="180" rx="40"/></clipPath><clipPath id="master_svg1_381_5952/255_01913"><rect x="18" y="18" width="144" height="144" rx="0"/></clipPath></defs><g clip-path="url(#master_svg0_381_5952)"><rect x="0" y="0" width="180" height="180" rx="40" fill="#111113" fill-opacity="1"/><g clip-path="url(#master_svg1_381_5952/255_01913)"><g><path d="M147.59971253967285,101.41062772064208L147.59971253967285,81.42072772064209C147.59971253967285,70.06382772064208,142.65821253967283,60.949927720642094,134.05571253967287,56.41322772064209L89.62531253967285,32.98532772064209L89.40251253967284,33.41064472064209L89.17971253967285,33.83596072064209C97.45821253967284,38.20051772064209,102.20761253967285,47.00562772064209,102.20761253967285,57.992927720642086L102.20761253967285,77.98282772064209C102.20761253967285,78.58532772064208,102.19241253967286,79.21822772064209,102.16201253967284,79.8511277206421C102.15691253967285,79.9726277206421,102.14681253967285,80.0942277206421,102.13671253967286,80.22072772064209L102.11641253967285,80.51952772064209C102.09111253967285,80.9195277206421,102.06581253967286,81.3245277206421,102.02531253967285,81.72452772064209C102.01011253967286,81.88662772064208,101.98981253967285,82.05372772064209,101.96961253967285,82.21572772064209L101.92401253967284,82.59542772064209C101.88351253967285,82.92962772064209,101.84811253967285,83.2587277206421,101.79741253967285,83.59292772064208C101.77211253967286,83.78022772064209,101.74171253967285,83.9625277206421,101.71141253967285,84.1499277206421L101.66581253967286,84.4385277206421C101.61011253967285,84.77772772064209,101.55441253967285,85.11692772064208,101.48861253967286,85.45622772064209C101.39241253967285,85.9625277206421,101.29111253967285,86.46382772064209,101.17971253967285,86.9650277206421L101.14431253967285,87.11192772064209C101.03291253967285,87.59802772064208,100.91141253967285,88.07902772064209,100.77971253967286,88.5600277206421L100.66331253967286,88.98532772064209C100.52661253967285,89.47142772064208,100.38481253967285,89.95742772064209,100.23291253967285,90.4435277206421L100.09621253967285,90.86382772064209C99.92401253967284,91.39542772064209,99.74171253967285,91.92712772064209,99.54931253967285,92.45372772064209C99.49871253967285,92.59542772064209,99.44301253967285,92.7321277206421,99.38731253967285,92.87392772064209L99.30121253967286,93.10182772064209C99.14431253967285,93.5068277206421,98.98731253967284,93.9170277206421,98.82021253967285,94.3220277206421C98.72401253967286,94.5600277206421,98.61771253967285,94.7980277206421,98.51641253967284,95.03592772064209L98.48101253967286,95.12202772064208C98.34931253967285,95.43092772064209,98.21261253967285,95.7397277206421,98.07081253967286,96.04352772064209C97.94931253967286,96.31702772064209,97.81771253967285,96.58532772064208,97.69111253967284,96.8537277206421C97.54431253967286,97.1575277206421,97.40251253967284,97.45622772064209,97.25061253967286,97.75492772064209C97.10881253967285,98.0334277206421,96.96701253967285,98.30682772064209,96.82021253967285,98.58022772064209C96.67841253967285,98.84862772064209,96.53161253967285,99.1220277206421,96.38481253967285,99.3904277206421C96.23291253967285,99.66382772064209,96.07591253967286,99.93722772064208,95.91891253967285,100.2106277206421C95.76701253967286,100.4739277206421,95.61521253967285,100.7423277206421,95.45311253967284,101.00052772064208C95.29111253967285,101.27392772064209,95.12401253967286,101.54232772064209,94.95191253967286,101.81062772064209C94.78981253967285,102.0689277206421,94.63291253967284,102.32202772064208,94.46581253967285,102.57522772064209C94.29361253967285,102.84352772064209,94.11641253967285,103.10182772064209,93.91391253967285,103.40562772064209C93.75191253967284,103.64352772064208,93.58981253967285,103.88152772064208,93.42781253967286,104.11442772064208C93.25571253967286,104.3575277206421,93.08351253967285,104.59542772064209,92.88101253967285,104.87392772064209C92.69361253967284,105.13212772064209,92.50631253967285,105.3853277206421,92.31391253967286,105.63852772064209C92.12651253967286,105.8866277206421,91.93921253967285,106.1246277206421,91.74681253967285,106.36762772064209C91.54931253967285,106.61572772064208,91.35191253967285,106.86892772064209,91.15441253967285,107.11192772064209C90.98221253967284,107.32462772064208,90.80501253967284,107.53222772064208,90.62781253967285,107.7397277206421L90.59241253967285,107.78032772064209C90.35951253967285,108.0587277206421,90.12151253967285,108.33212772064209,89.87841253967285,108.60562772064209C89.68601253967284,108.82332772064208,89.49361253967285,109.0360277206421,89.30121253967286,109.24862772064209C89.05821253967285,109.51702772064209,88.81521253967284,109.77522772064209,88.56711253967285,110.0385277206421C88.37971253967285,110.2359277206421,88.18731253967286,110.4Line truncated

After

Width:  |  Height:  |  Size: 38 KiB

@@ -93,6 +93,7 @@ export function ProviderEditor({
qwen: ['alibaba', 'qwen'],
iflow: ['iflow'],
kilo: ['kilo'],
qoder: ['qoder'],
kiro: ['kiro', 'aws'],
ghcp: ['github', 'copilot'],
kimi: ['kimi', 'moonshot'],
@@ -25,6 +25,7 @@ const WIZARD_PROVIDER_ORDER: CLIProxyProvider[] = [
'iflow',
'kilo',
'kiro',
'qoder',
];
export const PROVIDERS: ProviderOption[] = WIZARD_PROVIDER_ORDER.map((id) => ({
+122
View File
@@ -454,6 +454,128 @@ export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
},
],
},
qoder: {
provider: 'qoder',
displayName: 'Qoder',
defaultModel: 'qoder/auto',
models: [
{
id: 'qoder/auto',
name: 'Qoder Auto',
description: 'Auto selects the best Qoder model for your prompt',
presetMapping: {
default: 'qoder/auto',
opus: 'qoder/auto',
sonnet: 'qoder/auto',
haiku: 'qoder/auto',
},
},
{
id: 'qoder/ultimate',
name: 'Qoder Ultimate',
description: 'Highest quality Qoder tier',
presetMapping: {
default: 'qoder/ultimate',
opus: 'qoder/ultimate',
sonnet: 'qoder/ultimate',
haiku: 'qoder/ultimate',
},
},
{
id: 'qoder/performance',
name: 'Qoder Performance',
description: 'Balanced quality and speed',
presetMapping: {
default: 'qoder/performance',
opus: 'qoder/performance',
sonnet: 'qoder/performance',
haiku: 'qoder/performance',
},
},
{
id: 'qoder/efficient',
name: 'Qoder Efficient',
description: 'Cost-efficient Qoder tier',
presetMapping: {
default: 'qoder/efficient',
opus: 'qoder/efficient',
sonnet: 'qoder/efficient',
haiku: 'qoder/efficient',
},
},
{
id: 'qoder/lite',
name: 'Qoder Lite',
description: 'Fastest and most affordable Qoder tier',
presetMapping: {
default: 'qoder/lite',
opus: 'qoder/lite',
sonnet: 'qoder/lite',
haiku: 'qoder/lite',
},
},
{
id: 'qoder/qmodel',
name: 'Qwen 3.6 Plus (via Qoder)',
description: 'Qwen 3.6 Plus frontier model',
presetMapping: {
default: 'qoder/qmodel',
opus: 'qoder/qmodel',
sonnet: 'qoder/qmodel',
haiku: 'qoder/qmodel',
},
},
{
id: 'qoder/dmodel',
name: 'DeepSeek V4 Pro (via Qoder)',
description: 'DeepSeek V4 Pro frontier model',
presetMapping: {
default: 'qoder/dmodel',
opus: 'qoder/dmodel',
sonnet: 'qoder/dmodel',
haiku: 'qoder/dfmodel',
},
},
{
id: 'qoder/dfmodel',
name: 'DeepSeek V4 Flash (via Qoder)',
description: 'DeepSeek V4 Flash frontier model',
},
{
id: 'qoder/gm51model',
name: 'GLM 5.1 (via Qoder)',
description: 'GLM 5.1 frontier model',
presetMapping: {
default: 'qoder/gm51model',
opus: 'qoder/gm51model',
sonnet: 'qoder/gm51model',
haiku: 'qoder/gm51model',
},
},
{
id: 'qoder/kmodel',
name: 'Kimi K2.6 (via Qoder)',
description: 'Kimi K2.6 frontier model',
presetMapping: {
default: 'qoder/kmodel',
opus: 'qoder/kmodel',
sonnet: 'qoder/kmodel',
haiku: 'qoder/kmodel',
},
},
{
id: 'qoder/mmodel',
name: 'MiniMax M2.7 (via Qoder)',
description: 'MiniMax M2.7 frontier model',
presetMapping: {
default: 'qoder/mmodel',
opus: 'qoder/mmodel',
sonnet: 'qoder/mmodel',
haiku: 'qoder/mmodel',
},
},
],
},
kimi: {
provider: 'kimi',
displayName: 'Kimi (Moonshot)',
+3
View File
@@ -105,6 +105,7 @@ export const PROVIDER_ASSETS: Partial<Record<ProviderVisualId, string>> = {
gitlab: '/assets/providers/gitlab.svg',
codebuddy: '/assets/providers/codebuddy.png',
kilo: '/assets/providers/kilo.png',
qoder: '/assets/providers/qoder.svg',
ghcp: '/assets/providers/copilot.svg',
claude: '/assets/providers/claude.svg',
kimi: '/assets/providers/kimi.svg',
@@ -135,6 +136,7 @@ export const PROVIDER_FALLBACK_VISUALS: Record<ProviderVisualId, ProviderFallbac
gitlab: { textClass: 'text-orange-600', letter: 'G' },
codebuddy: { textClass: 'text-blue-600', letter: 'B' },
kilo: { textClass: 'text-rose-600', letter: 'K' },
qoder: { textClass: 'text-amber-600', letter: 'Q' },
ghcp: { textClass: 'text-green-600', letter: 'C' },
kimi: { textClass: 'text-orange-500', letter: 'K' },
openai: { textClass: 'text-slate-900', letter: 'O' },
@@ -272,6 +274,7 @@ export const PROVIDER_COLORS: Record<string, string> = {
gitlab: '#FC6D26',
codebuddy: '#2563EB',
kilo: '#E11D48',
qoder: '#D97706',
ghcp: '#43aa8b',
claude: '#D97757',
kimi: '#FF6B35',
@@ -68,6 +68,7 @@ describe('provider presentation metadata', () => {
'gitlab',
'codebuddy',
'kilo',
'qoder',
]);
expect(getProviderSection('gitlab')?.id).toBe('plus-extra');
expect(getProviderSection('gemini')?.id).toBe('core');
@@ -128,6 +129,7 @@ describe('provider presentation metadata', () => {
'/assets/providers/codebuddy.png',
],
['kilo', 'Kilo AI', 'Kilo AI coding assistant', '/assets/providers/kilo.png'],
['qoder', 'Qoder', 'Qoder AI coding assistant', '/assets/providers/qoder.svg'],
])('recognizes %s across dashboard display helpers', (provider, name, description, asset) => {
expect(getProviderDisplayName(provider)).toBe(name);
expect(getProviderDescription(provider)).toBe(description);