mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 22:17:14 +00:00
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:
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -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 });
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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`);
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user