Files
ccs/src/cliproxy/binary/platform-detector.ts
T
Kai (Tam Nhu) TranandGitHub 50b0ffba75 fix(cliproxy): refresh upstream model and quota surfaces (#1158)
* fix(cliproxy): refresh upstream model and quota surfaces

* test(cliproxy): cover codex legacy model fallback
2026-05-01 02:19:31 -04:00

210 lines
6.1 KiB
TypeScript

/**
* Platform Detector for CLIProxyAPI Binary Downloads
*
* Detects OS and architecture to determine correct binary asset.
* Supports 6 platforms: darwin/linux/windows x amd64/arm64
*/
import {
PlatformInfo,
SupportedOS,
SupportedArch,
ArchiveExtension,
CLIProxyBackend,
} from '../types';
/** Backend configuration */
export const BACKEND_CONFIG = {
original: {
repo: 'router-for-me/CLIProxyAPI',
binaryPrefix: 'CLIProxyAPI',
executable: 'cli-proxy-api',
fallbackVersion: '6.9.45',
},
plus: {
repo: 'kaitranntt/CLIProxyAPIPlus',
binaryPrefix: 'CLIProxyAPIPlus',
executable: 'cli-proxy-api-plus',
fallbackVersion: '6.9.45-0',
},
} as const;
/**
* Default backend
*
* Keep 'original' as the stable default. The Plus backend is available as an
* opt-in community-maintained fork for providers that are not available in the
* original upstream binary.
*/
export const DEFAULT_BACKEND: CLIProxyBackend = 'original';
/**
* CLIProxyAPIPlus fallback version (used when GitHub API unavailable)
* Auto-update fetches latest from GitHub; this is only a safety net
* Note: CLIProxyAPIPlus uses v6.6.X-0 suffix pattern
* @deprecated Use getFallbackVersion() or BACKEND_CONFIG instead
*/
export const CLIPROXY_FALLBACK_VERSION = BACKEND_CONFIG[DEFAULT_BACKEND].fallbackVersion;
/**
* Maximum stable version cap - prevents auto-update to known unstable releases
* Currently set high since v89+ are all stable.
* Only v81-88 have known bugs (see CLIPROXY_FAULTY_RANGE).
* See: https://github.com/kaitranntt/ccs/issues/269
*/
export const CLIPROXY_MAX_STABLE_VERSION = '9.9.999-0';
/**
* Faulty version range - versions with known critical bugs
* v81-88 have context cancellation bugs causing intermittent 500 errors
* v89 confirmed stable
*/
export const CLIPROXY_FAULTY_RANGE = { min: '6.6.81-0', max: '6.6.88-0' };
/** @deprecated Use CLIPROXY_FALLBACK_VERSION instead */
export const CLIPROXY_VERSION = CLIPROXY_FALLBACK_VERSION;
/**
* Platform mapping from Node.js values to CLIProxyAPI naming
*/
const OS_MAP: Record<string, SupportedOS | undefined> = {
darwin: 'darwin',
linux: 'linux',
win32: 'windows',
};
const ARCH_MAP: Record<string, SupportedArch | undefined> = {
x64: 'amd64',
arm64: 'arm64',
};
/**
* Detect current platform and return binary info
* @param version Optional version for binaryName (defaults to backend fallback)
* @param backend Backend variant to use (defaults to DEFAULT_BACKEND)
* @throws Error if platform is unsupported
*/
export function detectPlatform(
version?: string,
backend: CLIProxyBackend = DEFAULT_BACKEND
): PlatformInfo {
const nodePlatform = process.platform;
const nodeArch = process.arch;
const os = OS_MAP[nodePlatform];
const arch = ARCH_MAP[nodeArch];
if (!os) {
throw new Error(
`Unsupported operating system: ${nodePlatform}\n` +
`Supported: macOS (darwin), Linux, Windows`
);
}
if (!arch) {
throw new Error(
`Unsupported CPU architecture: ${nodeArch}\n` + `Supported: x64 (amd64), arm64`
);
}
const config = BACKEND_CONFIG[backend];
const ver = version || config.fallbackVersion;
const extension: ArchiveExtension = os === 'windows' ? 'zip' : 'tar.gz';
const binaryName = `${config.binaryPrefix}_${ver}_${os}_${arch}.${extension}`;
return {
os,
arch,
binaryName,
extension,
};
}
/**
* Get executable name based on platform
* @param backend Backend variant to use (defaults to DEFAULT_BACKEND)
* @returns Binary executable name (with .exe on Windows)
*/
export function getExecutableName(backend: CLIProxyBackend = DEFAULT_BACKEND): string {
const config = BACKEND_CONFIG[backend];
const platform = detectPlatform(undefined, backend);
return platform.os === 'windows' ? `${config.executable}.exe` : config.executable;
}
/**
* Get the name of the binary inside the archive
* @param backend Backend variant to use (defaults to DEFAULT_BACKEND)
* @returns Binary name as it appears in the tar.gz/zip
*/
export function getArchiveBinaryName(backend: CLIProxyBackend = DEFAULT_BACKEND): string {
return getExecutableName(backend);
}
/**
* Get download URL for current platform
* @param version Version to download (defaults to backend fallback version)
* @param backend Backend variant to use (defaults to DEFAULT_BACKEND)
* @returns Full GitHub release download URL
*/
export function getDownloadUrl(
version?: string,
backend: CLIProxyBackend = DEFAULT_BACKEND
): string {
const config = BACKEND_CONFIG[backend];
const ver = version || config.fallbackVersion;
const platform = detectPlatform(ver, backend);
return `https://github.com/${config.repo}/releases/download/v${ver}/${platform.binaryName}`;
}
/**
* Get checksums.txt URL for version
* @param version Version to get checksums for (defaults to backend fallback version)
* @param backend Backend variant to use (defaults to DEFAULT_BACKEND)
* @returns Full URL to checksums.txt
*/
export function getChecksumsUrl(
version?: string,
backend: CLIProxyBackend = DEFAULT_BACKEND
): string {
const config = BACKEND_CONFIG[backend];
const ver = version || config.fallbackVersion;
return `https://github.com/${config.repo}/releases/download/v${ver}/checksums.txt`;
}
/**
* Get fallback version for backend
* @param backend Backend variant to use (defaults to DEFAULT_BACKEND)
* @returns Fallback version string
*/
export function getFallbackVersion(backend: CLIProxyBackend = DEFAULT_BACKEND): string {
return BACKEND_CONFIG[backend].fallbackVersion;
}
/**
* Check if platform is supported
* @returns true if current platform is supported
*/
export function isPlatformSupported(): boolean {
try {
detectPlatform();
return true;
} catch {
return false;
}
}
/**
* Get human-readable platform description
* @returns Description string (e.g., "macOS arm64")
*/
export function getPlatformDescription(): string {
try {
const platform = detectPlatform();
const osName =
platform.os === 'darwin' ? 'macOS' : platform.os === 'linux' ? 'Linux' : 'Windows';
return `${osName} ${platform.arch}`;
} catch {
return `${process.platform} ${process.arch} (unsupported)`;
}
}