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';
/** 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 interface ProfileDetectionResult {
@@ -23,7 +23,7 @@ export interface ProfileDetectionResult {
settingsPath?: string;
profile?: Settings | ProfileMetadata;
message?: string;
/** For cliproxy variants: the underlying provider (gemini, codex, agy) */
/** For cliproxy variants: the underlying provider (gemini, codex, agy, qwen) */
provider?: CLIProxyProfileName;
}
@@ -89,7 +89,7 @@ class ProfileDetector {
* Detect profile type and return routing information
*
* 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)
* 2. Settings-based profiles (config.profiles section)
* 3. Account-based profiles (profiles.json)
@@ -100,7 +100,7 @@ class ProfileDetector {
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)) {
return {
type: 'cliproxy',
+1 -1
View File
@@ -295,7 +295,7 @@ async function main(): Promise<void> {
const profileInfo = detector.detectProfileType(profile);
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 customSettingsPath = profileInfo.settingsPath; // undefined for hardcoded profiles
await execClaudeWithCLIProxy(claudeCli, provider, remainingArgs, { customSettingsPath });
+10 -1
View File
@@ -100,6 +100,13 @@ const OAUTH_CONFIGS: Record<CLIProxyProvider, ProviderOAuthConfig> = {
scopes: ['api'],
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-'],
codex: ['codex-', 'openai-'],
agy: ['antigravity-', 'agy-'],
qwen: ['qwen-'],
};
/**
@@ -139,6 +147,7 @@ const PROVIDER_TYPE_VALUES: Record<CLIProxyProvider, string[]> = {
gemini: ['gemini'],
codex: ['codex'],
agy: ['antigravity'],
qwen: ['qwen'],
};
/**
@@ -256,7 +265,7 @@ export function getAuthStatus(provider: CLIProxyProvider): AuthStatus {
* Get auth status for all providers
*/
export function getAllAuthStatus(): AuthStatus[] {
const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy'];
const providers: CLIProxyProvider[] = ['gemini', 'codex', 'agy', 'qwen'];
return providers.map(getAuthStatus);
}
+1 -1
View File
@@ -80,7 +80,7 @@ async function waitForProxyReady(
* Execute Claude CLI with CLIProxy (main entry point)
*
* @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 config Optional executor configuration
*/
+2 -1
View File
@@ -30,6 +30,7 @@ const PROVIDER_DISPLAY_NAMES: Record<CLIProxyProvider, string> = {
gemini: 'Gemini',
codex: 'Codex',
agy: 'Antigravity',
qwen: 'Qwen Code',
};
/**
@@ -112,7 +113,7 @@ function generateUnifiedConfigContent(port: number = CLIPROXY_DEFAULT_PORT): str
// Unified config with all providers
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()}
port: ${port}
+2 -1
View File
@@ -110,8 +110,9 @@ export interface DownloadResult {
* - gemini: Google Gemini via OAuth
* - codex: OpenAI Codex via OAuth
* - 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)
+1
View File
@@ -54,6 +54,7 @@ export function handleHelpCommand(): void {
console.log(
` ${colored('ccs agy', 'yellow')} Antigravity (gemini-3-pro-preview)`
);
console.log(` ${colored('ccs qwen', 'yellow')} Qwen Code (qwen3-coder)`);
console.log('');
console.log(` ${colored('ccs <provider> --auth', 'yellow')} Authenticate only`);
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> {
// 1. Binary installed?
+2 -2
View File
@@ -17,8 +17,8 @@ export interface ProfilesConfig {
* Example: "flash" → gemini provider with gemini-2.5-flash model
*/
export interface CLIProxyVariantConfig {
/** CLIProxy provider to use (gemini, codex, agy) */
provider: 'gemini' | 'codex' | 'agy';
/** CLIProxy provider to use (gemini, codex, agy, qwen) */
provider: 'gemini' | 'codex' | 'agy' | 'qwen';
/** Path to settings.json with custom model configuration */
settings: string;
}