mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 14:16:43 +00:00
fix(cliproxy): improve qwen oauth error handling
- Updated OAuth port documentation with correct flow types per provider - Removed Qwen from port cleanup logic (Device Code Flow has no callback) - Fixed Qwen error messages to show Device Code Flow troubleshooting tips Fixes #29
This commit is contained in:
@@ -14,12 +14,72 @@
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
import { spawn, execSync } from 'child_process';
|
||||
import { ProgressIndicator } from '../utils/progress-indicator';
|
||||
import { getProviderAuthDir, generateConfig } from './config-generator';
|
||||
import { ensureCLIProxyBinary } from './binary-manager';
|
||||
import { CLIProxyProvider } from './types';
|
||||
|
||||
/**
|
||||
* OAuth callback ports used by CLIProxyAPI (hardcoded in binary)
|
||||
* See: https://github.com/router-for-me/CLIProxyAPI/tree/main/internal/auth
|
||||
*
|
||||
* OAuth flow types per provider:
|
||||
* - Gemini: Authorization Code Flow with local callback server on port 8085
|
||||
* - Codex: Authorization Code Flow with local callback server on port 1455
|
||||
* - Agy: Authorization Code Flow with local callback server on port 51121
|
||||
* - Qwen: Device Code Flow (polling-based, NO callback port needed)
|
||||
*
|
||||
* We auto-kill processes on callback ports before OAuth to avoid conflicts.
|
||||
*/
|
||||
const OAUTH_CALLBACK_PORTS: Partial<Record<CLIProxyProvider, number>> = {
|
||||
gemini: 8085,
|
||||
// codex uses 1455
|
||||
// agy uses 51121
|
||||
// qwen uses Device Code Flow - no callback port needed
|
||||
};
|
||||
|
||||
/**
|
||||
* Kill any process using a specific port
|
||||
* Used to free OAuth callback port before authentication
|
||||
*/
|
||||
function killProcessOnPort(port: number, verbose: boolean): boolean {
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
// Windows: use netstat + taskkill
|
||||
const result = execSync(`netstat -ano | findstr :${port}`, {
|
||||
encoding: 'utf-8',
|
||||
stdio: 'pipe',
|
||||
});
|
||||
const lines = result.trim().split('\n');
|
||||
for (const line of lines) {
|
||||
const parts = line.trim().split(/\s+/);
|
||||
const pid = parts[parts.length - 1];
|
||||
if (pid && /^\d+$/.test(pid)) {
|
||||
execSync(`taskkill /F /PID ${pid}`, { stdio: 'pipe' });
|
||||
if (verbose) console.error(`[auth] Killed process ${pid} on port ${port}`);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// Unix: use lsof + kill
|
||||
const result = execSync(`lsof -ti:${port}`, { encoding: 'utf-8', stdio: 'pipe' });
|
||||
const pids = result
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((p) => p);
|
||||
for (const pid of pids) {
|
||||
execSync(`kill -9 ${pid}`, { stdio: 'pipe' });
|
||||
if (verbose) console.error(`[auth] Killed process ${pid} on port ${port}`);
|
||||
}
|
||||
return pids.length > 0;
|
||||
}
|
||||
} catch {
|
||||
// No process on port or command failed - that's fine
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if running in a headless environment (no browser available)
|
||||
*/
|
||||
@@ -328,6 +388,16 @@ export async function triggerOAuth(
|
||||
const configPath = generateConfig(provider);
|
||||
log(`Config generated: ${configPath}`);
|
||||
|
||||
// Free OAuth callback port if this provider shares it with another
|
||||
// Qwen and Gemini both use port 8085 - kill any existing process to avoid conflict
|
||||
const callbackPort = OAUTH_CALLBACK_PORTS[provider];
|
||||
if (callbackPort) {
|
||||
const killed = killProcessOnPort(callbackPort, verbose);
|
||||
if (killed) {
|
||||
log(`Freed port ${callbackPort} for OAuth callback`);
|
||||
}
|
||||
}
|
||||
|
||||
// Build args: config + auth flag + optional --no-browser for headless
|
||||
const args = ['--config', configPath, oauthConfig.authFlag];
|
||||
if (headless) {
|
||||
@@ -428,8 +498,15 @@ export async function triggerOAuth(
|
||||
} else {
|
||||
if (!headless) spinner.fail('Authentication incomplete');
|
||||
console.error('[X] Token not found after authentication');
|
||||
console.error(' The OAuth flow may have been cancelled or port 8085 was in use');
|
||||
console.error(' Try: pkill -f cli-proxy-api && ccs gemini --auth');
|
||||
// Qwen uses Device Code Flow (polling), others use Authorization Code Flow (callback)
|
||||
if (provider === 'qwen') {
|
||||
console.error(' Qwen uses Device Code Flow - ensure you completed auth in browser');
|
||||
console.error(' The polling may have timed out or been cancelled');
|
||||
console.error(' Try: ccs qwen --auth --verbose');
|
||||
} else {
|
||||
console.error(' The OAuth flow may have been cancelled or callback port was in use');
|
||||
console.error(` Try: pkill -f cli-proxy-api && ccs ${provider} --auth`);
|
||||
}
|
||||
resolve(false);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { PlatformInfo, SupportedOS, SupportedArch, ArchiveExtension } from './ty
|
||||
* CLIProxyAPI fallback version (used when GitHub API unavailable)
|
||||
* Auto-update fetches latest from GitHub; this is only a safety net
|
||||
*/
|
||||
export const CLIPROXY_FALLBACK_VERSION = '6.5.27';
|
||||
export const CLIPROXY_FALLBACK_VERSION = '6.5.31';
|
||||
|
||||
/** @deprecated Use CLIPROXY_FALLBACK_VERSION instead */
|
||||
export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION;
|
||||
|
||||
Reference in New Issue
Block a user