feat(cliproxy): add qwen code oauth provider support (#31)

Add 'qwen' as new CLIProxy OAuth provider with --qwen-login flag.
Support qwen3-coder model with base config at config/base-qwen.settings.json.
Register qwen OAuth endpoints and token prefix handling.
Update help documentation across all CLI entry points.

Implements issue #29
This commit is contained in:
Kai (Tam Nhu) Tran
2025-12-01 03:51:47 -05:00
committed by GitHub
parent ca8a95cdaf
commit a3f1e52ac6
10 changed files with 34 additions and 12 deletions
+10
View File
@@ -0,0 +1,10 @@
{
"env": {
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8317/api/provider/qwen",
"ANTHROPIC_AUTH_TOKEN": "ccs-internal-managed",
"ANTHROPIC_MODEL": "qwen3-coder",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "qwen3-coder",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "qwen3-coder",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "qwen3-coder"
}
}
+4 -4
View File
@@ -14,7 +14,7 @@ import { Config, Settings, ProfileMetadata } from '../types';
export type ProfileType = 'settings' | 'account' | 'cliproxy' | 'default'; export type ProfileType = 'settings' | 'account' | 'cliproxy' | 'default';
/** CLIProxy profile names (OAuth-based, zero config) */ /** CLIProxy profile names (OAuth-based, zero config) */
export const CLIPROXY_PROFILES = ['gemini', 'codex', 'agy'] as const; export const CLIPROXY_PROFILES = ['gemini', 'codex', 'agy', 'qwen'] as const;
export type CLIProxyProfileName = (typeof CLIPROXY_PROFILES)[number]; export type CLIProxyProfileName = (typeof CLIPROXY_PROFILES)[number];
export interface ProfileDetectionResult { export interface ProfileDetectionResult {
@@ -23,7 +23,7 @@ export interface ProfileDetectionResult {
settingsPath?: string; settingsPath?: string;
profile?: Settings | ProfileMetadata; profile?: Settings | ProfileMetadata;
message?: string; message?: string;
/** For cliproxy variants: the underlying provider (gemini, codex, agy) */ /** For cliproxy variants: the underlying provider (gemini, codex, agy, qwen) */
provider?: CLIProxyProfileName; provider?: CLIProxyProfileName;
} }
@@ -89,7 +89,7 @@ class ProfileDetector {
* Detect profile type and return routing information * Detect profile type and return routing information
* *
* Priority order: * Priority order:
* 0. Hardcoded CLIProxy profiles (gemini, codex, agy) * 0. Hardcoded CLIProxy profiles (gemini, codex, agy, qwen)
* 1. User-defined CLIProxy variants (config.cliproxy section) * 1. User-defined CLIProxy variants (config.cliproxy section)
* 2. Settings-based profiles (config.profiles section) * 2. Settings-based profiles (config.profiles section)
* 3. Account-based profiles (profiles.json) * 3. Account-based profiles (profiles.json)
@@ -100,7 +100,7 @@ class ProfileDetector {
return this.resolveDefaultProfile(); return this.resolveDefaultProfile();
} }
// Priority 0: Check CLIProxy profiles (gemini, codex, agy) - OAuth-based, zero config // Priority 0: Check CLIProxy profiles (gemini, codex, agy, qwen) - OAuth-based, zero config
if (CLIPROXY_PROFILES.includes(profileName as CLIProxyProfileName)) { if (CLIPROXY_PROFILES.includes(profileName as CLIProxyProfileName)) {
return { return {
type: 'cliproxy', type: 'cliproxy',
+1 -1
View File
@@ -295,7 +295,7 @@ async function main(): Promise<void> {
const profileInfo = detector.detectProfileType(profile); const profileInfo = detector.detectProfileType(profile);
if (profileInfo.type === 'cliproxy') { if (profileInfo.type === 'cliproxy') {
// CLIPROXY FLOW: OAuth-based profiles (gemini, codex, agy) or user-defined variants // CLIPROXY FLOW: OAuth-based profiles (gemini, codex, agy, qwen) or user-defined variants
const provider = profileInfo.provider || (profileInfo.name as CLIProxyProvider); const provider = profileInfo.provider || (profileInfo.name as CLIProxyProvider);
const customSettingsPath = profileInfo.settingsPath; // undefined for hardcoded profiles const customSettingsPath = profileInfo.settingsPath; // undefined for hardcoded profiles
await execClaudeWithCLIProxy(claudeCli, provider, remainingArgs, { customSettingsPath }); await execClaudeWithCLIProxy(claudeCli, provider, remainingArgs, { customSettingsPath });
+10 -1
View File
@@ -100,6 +100,13 @@ const OAUTH_CONFIGS: Record<CLIProxyProvider, ProviderOAuthConfig> = {
scopes: ['api'], scopes: ['api'],
authFlag: '--antigravity-login', authFlag: '--antigravity-login',
}, },
qwen: {
provider: 'qwen',
displayName: 'Qwen Code',
authUrl: 'https://chat.qwen.ai/api/v1/oauth2/device/code',
scopes: ['openid', 'profile', 'email', 'model.completion'],
authFlag: '--qwen-login',
},
}; };
/** /**
@@ -129,6 +136,7 @@ const PROVIDER_AUTH_PREFIXES: Record<CLIProxyProvider, string[]> = {
gemini: ['gemini-', 'google-'], gemini: ['gemini-', 'google-'],
codex: ['codex-', 'openai-'], codex: ['codex-', 'openai-'],
agy: ['antigravity-', 'agy-'], agy: ['antigravity-', 'agy-'],
qwen: ['qwen-'],
}; };
/** /**
@@ -139,6 +147,7 @@ const PROVIDER_TYPE_VALUES: Record<CLIProxyProvider, string[]> = {
gemini: ['gemini'], gemini: ['gemini'],
codex: ['codex'], codex: ['codex'],
agy: ['antigravity'], agy: ['antigravity'],
qwen: ['qwen'],
}; };
/** /**
@@ -256,7 +265,7 @@ export function getAuthStatus(provider: CLIProxyProvider): AuthStatus {
* Get auth status for all providers * Get auth status for all providers
*/ */
export function getAllAuthStatus(): AuthStatus[] { export function getAllAuthStatus(): AuthStatus[] {
const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy']; const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen'];
return providers.map(getAuthStatus); return providers.map(getAuthStatus);
} }
+1 -1
View File
@@ -80,7 +80,7 @@ async function waitForProxyReady(
* Execute Claude CLI with CLIProxy (main entry point) * Execute Claude CLI with CLIProxy (main entry point)
* *
* @param claudeCli Path to Claude CLI executable * @param claudeCli Path to Claude CLI executable
* @param provider CLIProxy provider (gemini, codex, agy) * @param provider CLIProxy provider (gemini, codex, agy, qwen)
* @param args Arguments to pass to Claude CLI * @param args Arguments to pass to Claude CLI
* @param config Optional executor configuration * @param config Optional executor configuration
*/ */
+2 -1
View File
@@ -30,6 +30,7 @@ const PROVIDER_DISPLAY_NAMES: Record<CLIProxyProvider, string> = {
gemini: 'Gemini', gemini: 'Gemini',
codex: 'Codex', codex: 'Codex',
agy: 'Antigravity', agy: 'Antigravity',
qwen: 'Qwen Code',
}; };
/** /**
@@ -112,7 +113,7 @@ function generateUnifiedConfigContent(port: number = CLIPROXY_DEFAULT_PORT): str
// Unified config with all providers // Unified config with all providers
const config = `# CLIProxyAPI unified config generated by CCS const config = `# CLIProxyAPI unified config generated by CCS
# Supports: gemini, codex, agy (concurrent usage) # Supports: gemini, codex, agy, qwen (concurrent usage)
# Generated: ${new Date().toISOString()} # Generated: ${new Date().toISOString()}
port: ${port} port: ${port}
+2 -1
View File
@@ -110,8 +110,9 @@ export interface DownloadResult {
* - gemini: Google Gemini via OAuth * - gemini: Google Gemini via OAuth
* - codex: OpenAI Codex via OAuth * - codex: OpenAI Codex via OAuth
* - agy: Antigravity via OAuth (short name for easy usage) * - agy: Antigravity via OAuth (short name for easy usage)
* - qwen: Qwen Code via OAuth (qwen3-coder)
*/ */
export type CLIProxyProvider = 'gemini' | 'codex' | 'agy'; export type CLIProxyProvider = 'gemini' | 'codex' | 'agy' | 'qwen';
/** /**
* CLIProxy config.yaml structure (minimal) * CLIProxy config.yaml structure (minimal)
+1
View File
@@ -54,6 +54,7 @@ export function handleHelpCommand(): void {
console.log( console.log(
` ${colored('ccs agy', 'yellow')} Antigravity (gemini-3-pro-preview)` ` ${colored('ccs agy', 'yellow')} Antigravity (gemini-3-pro-preview)`
); );
console.log(` ${colored('ccs qwen', 'yellow')} Qwen Code (qwen3-coder)`);
console.log(''); console.log('');
console.log(` ${colored('ccs <provider> --auth', 'yellow')} Authenticate only`); console.log(` ${colored('ccs <provider> --auth', 'yellow')} Authenticate only`);
console.log(` ${colored('ccs <provider> --logout', 'yellow')} Clear authentication`); console.log(` ${colored('ccs <provider> --logout', 'yellow')} Clear authentication`);
+1 -1
View File
@@ -754,7 +754,7 @@ class Doctor {
} }
/** /**
* Check 11: CLIProxy health (OAuth profiles: gemini, codex, agy) * Check 11: CLIProxy health (OAuth profiles: gemini, codex, agy, qwen)
*/ */
private async checkCLIProxy(): Promise<void> { private async checkCLIProxy(): Promise<void> {
// 1. Binary installed? // 1. Binary installed?
+2 -2
View File
@@ -17,8 +17,8 @@ export interface ProfilesConfig {
* Example: "flash" → gemini provider with gemini-2.5-flash model * Example: "flash" → gemini provider with gemini-2.5-flash model
*/ */
export interface CLIProxyVariantConfig { export interface CLIProxyVariantConfig {
/** CLIProxy provider to use (gemini, codex, agy) */ /** CLIProxy provider to use (gemini, codex, agy, qwen) */
provider: 'gemini' | 'codex' | 'agy'; provider: 'gemini' | 'codex' | 'agy' | 'qwen';
/** Path to settings.json with custom model configuration */ /** Path to settings.json with custom model configuration */
settings: string; settings: string;
} }