mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-13 06:22:49 +00:00
fix(cliproxy): route plus backend to maintained fork
This commit is contained in:
@@ -118,7 +118,8 @@ config. Deep dive:
|
|||||||
|
|
||||||
Manage OAuth-backed providers, quota visibility, and proxy-wide routing from one place. CCS now
|
Manage OAuth-backed providers, quota visibility, and proxy-wide routing from one place. CCS now
|
||||||
surfaces round-robin vs fill-first natively in both CLI and dashboard flows instead of hiding that
|
surfaces round-robin vs fill-first natively in both CLI and dashboard flows instead of hiding that
|
||||||
choice inside raw upstream controls.
|
choice inside raw upstream controls. The original CLIProxyAPI backend remains the default; the
|
||||||
|
community-maintained CLIProxyAPIPlus fork is opt-in for plus-only providers.
|
||||||
Deep dive:
|
Deep dive:
|
||||||
[CLIProxy API](https://docs.ccs.kaitran.ca/features/proxy/cliproxy-api).
|
[CLIProxy API](https://docs.ccs.kaitran.ca/features/proxy/cliproxy-api).
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ All major modularization work is complete. The codebase evolved from monolithic
|
|||||||
|
|
||||||
### Recent Fixes
|
### Recent Fixes
|
||||||
|
|
||||||
|
- **2026-04-24**: **#1065** Local CLIProxy Plus is available again as an explicit opt-in backend through the community-maintained `kaitranntt/CLIProxyAPIPlus` fork. CCS keeps `original` as the default backend, no longer downgrades saved `backend: plus` configs to `original`, updates Plus release lookups to the maintained fork, and documents Plus as a targeted path for plus-only providers.
|
||||||
- **2026-04-21**: CLIProxy quota failover now quarantines exhausted Claude and Antigravity accounts out of live rotation when a healthy fallback exists. CCS persists those quota-triggered pauses across launches, automatically resumes them after the configured cooldown window, and deliberately avoids auto-pausing the last available account so single-account setups still degrade gracefully instead of hard-locking themselves.
|
- **2026-04-21**: CLIProxy quota failover now quarantines exhausted Claude and Antigravity accounts out of live rotation when a healthy fallback exists. CCS persists those quota-triggered pauses across launches, automatically resumes them after the configured cooldown window, and deliberately avoids auto-pausing the last available account so single-account setups still degrade gracefully instead of hard-locking themselves.
|
||||||
- **2026-04-20**: **#1051** Browser automation now defaults safe-off for new installs and upgrades that do not already carry explicit browser settings. CCS changes both Claude Browser Attach and Codex Browser Tools to start with `enabled: false` and `policy: manual`, normalizes missing browser policies on upgrade back to `manual`, preserves explicit existing enablement, and updates status/help/docs so browser tooling is never implied to auto-expose unless users opt in.
|
- **2026-04-20**: **#1051** Browser automation now defaults safe-off for new installs and upgrades that do not already carry explicit browser settings. CCS changes both Claude Browser Attach and Codex Browser Tools to start with `enabled: false` and `policy: manual`, normalizes missing browser policies on upgrade back to `manual`, preserves explicit existing enablement, and updates status/help/docs so browser tooling is never implied to auto-expose unless users opt in.
|
||||||
- **2026-04-19**: **#1051** Browser tooling now has an explicit exposure policy instead of only coarse enablement toggles. CCS adds `browser.<lane>.policy` (`auto` or `manual`) for both Claude Browser Attach and Codex Browser Tools, exposes CLI-first policy controls through `ccs browser policy`, `ccs browser enable`, and `ccs browser disable`, and adds one-run launch overrides `--browser` and `--no-browser` so users can force browser tooling on or off without editing saved config.
|
- **2026-04-19**: **#1051** Browser tooling now has an explicit exposure policy instead of only coarse enablement toggles. CCS adds `browser.<lane>.policy` (`auto` or `manual`) for both Claude Browser Attach and Codex Browser Tools, exposes CLI-first policy controls through `ccs browser policy`, `ccs browser enable`, and `ccs browser disable`, and adds one-run launch overrides `--browser` and `--no-browser` so users can force browser tooling on or off without editing saved config.
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ Detailed provider integration flows including CLIProxyAPI, legacy GLMT compatibi
|
|||||||
|
|
||||||
CLIProxyAPI is a local OAuth proxy binary that enables seamless integration with multiple AI providers. CCS manages the binary and configuration automatically.
|
CLIProxyAPI is a local OAuth proxy binary that enables seamless integration with multiple AI providers. CCS manages the binary and configuration automatically.
|
||||||
|
|
||||||
|
### Local Backend Choice
|
||||||
|
|
||||||
|
CCS defaults to the original `router-for-me/CLIProxyAPI` backend because it is the stable MIT upstream. The `plus` backend is an explicit opt-in path that downloads the community-maintained `kaitranntt/CLIProxyAPIPlus` fork for providers that still require Plus-only support, such as Kiro, GitHub Copilot, Cursor, GitLab, CodeBuddy, and Kilo. CCS does not silently downgrade `backend: plus` to `original`; users choose that backend deliberately when they need those providers.
|
||||||
|
|
||||||
```
|
```
|
||||||
+===========================================================================+
|
+===========================================================================+
|
||||||
| CLIProxyAPI Integration |
|
| CLIProxyAPI Integration |
|
||||||
@@ -22,7 +26,7 @@ CLIProxyAPI is a local OAuth proxy binary that enables seamless integration with
|
|||||||
| ANTHROPIC_BASE_URL = localhost:XXXX
|
| ANTHROPIC_BASE_URL = localhost:XXXX
|
||||||
v
|
v
|
||||||
+------------------+
|
+------------------+
|
||||||
| CLIProxyAPI | Local proxy binary (CLIProxyAPIPlus for kiro/ghcp)
|
| CLIProxyAPI | Local proxy binary (Plus fork opt-in for plus-only providers)
|
||||||
| (binary) |
|
| (binary) |
|
||||||
+------------------+
|
+------------------+
|
||||||
|
|
|
|
||||||
@@ -73,8 +77,8 @@ CLIProxyAPI is a local OAuth proxy binary that enables seamless integration with
|
|||||||
| Gemini | `gemini` | Authorization Code | 9876 | CLIProxyAPI |
|
| Gemini | `gemini` | Authorization Code | 9876 | CLIProxyAPI |
|
||||||
| Codex | `codex` | Authorization Code | 9876 | CLIProxyAPI |
|
| Codex | `codex` | Authorization Code | 9876 | CLIProxyAPI |
|
||||||
| Antigravity | `agy` | Authorization Code | 9876 | CLIProxyAPI |
|
| Antigravity | `agy` | Authorization Code | 9876 | CLIProxyAPI |
|
||||||
| Kiro (AWS) | `kiro` | Method-aware (default: Device Code) | 9876 | CLIProxyAPIPlus |
|
| Kiro (AWS) | `kiro` | Method-aware (default: Device Code) | 9876 | CLIProxyAPIPlus fork |
|
||||||
| GitHub Copilot | `ghcp` | Device Code | none | CLIProxyAPIPlus |
|
| GitHub Copilot | `ghcp` | Device Code | none | CLIProxyAPIPlus fork |
|
||||||
|
|
||||||
### Codex Duplicate-Email Account Identity
|
### Codex Duplicate-Email Account Identity
|
||||||
|
|
||||||
|
|||||||
@@ -5,17 +5,10 @@
|
|||||||
* Pattern: Mirrors npm install behavior (fast check, download only when needed)
|
* Pattern: Mirrors npm install behavior (fast check, download only when needed)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as path from 'path';
|
|
||||||
import { info, warn } from '../utils/ui';
|
import { info, warn } from '../utils/ui';
|
||||||
import { getBinDir, CLIPROXY_DEFAULT_PORT } from './config-generator';
|
import { getBinDir, CLIPROXY_DEFAULT_PORT } from './config-generator';
|
||||||
import { BinaryInfo, BinaryManagerConfig } from './types';
|
import { BinaryInfo, BinaryManagerConfig } from './types';
|
||||||
import {
|
import { BACKEND_CONFIG, DEFAULT_BACKEND, CLIPROXY_MAX_STABLE_VERSION } from './platform-detector';
|
||||||
BACKEND_CONFIG,
|
|
||||||
DEFAULT_BACKEND,
|
|
||||||
CLIPROXY_MAX_STABLE_VERSION,
|
|
||||||
getExecutableName,
|
|
||||||
} from './platform-detector';
|
|
||||||
import { stopProxy } from './services/proxy-lifecycle-service';
|
import { stopProxy } from './services/proxy-lifecycle-service';
|
||||||
import { waitForPortFree } from '../utils/port-utils';
|
import { waitForPortFree } from '../utils/port-utils';
|
||||||
import { loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
import { loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
||||||
@@ -23,7 +16,6 @@ import {
|
|||||||
UpdateCheckResult,
|
UpdateCheckResult,
|
||||||
checkForUpdates,
|
checkForUpdates,
|
||||||
deleteBinary,
|
deleteBinary,
|
||||||
getVersionCachePath,
|
|
||||||
getBinaryPath,
|
getBinaryPath,
|
||||||
isBinaryInstalled,
|
isBinaryInstalled,
|
||||||
getBinaryInfo,
|
getBinaryInfo,
|
||||||
@@ -38,23 +30,24 @@ import {
|
|||||||
} from './binary';
|
} from './binary';
|
||||||
|
|
||||||
import type { CLIProxyBackend } from './types';
|
import type { CLIProxyBackend } from './types';
|
||||||
import { getVersionListCachePath } from './binary/version-cache';
|
|
||||||
|
|
||||||
export const CLIPROXY_PLUS_TRACKING_URL = 'https://github.com/kaitranntt/ccs/issues/1062';
|
export const CLIPROXY_PLUS_FORK_URL = 'https://github.com/kaitranntt/CLIProxyAPIPlus';
|
||||||
|
export const CLIPROXY_PLUS_TRACKING_URL = 'https://github.com/kaitranntt/ccs/issues/1065';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track whether we've already warned the user about the Plus fallback this
|
* Track whether we've already shown the optional Plus backend notice this
|
||||||
* process lifetime. Prevents spamming the warning on every command.
|
* process lifetime. Prevents spamming the message on every command.
|
||||||
*/
|
*/
|
||||||
let plusFallbackWarned = false;
|
let plusBackendInfoShown = false;
|
||||||
|
|
||||||
function emitPlusFallbackWarning(): void {
|
function emitPlusBackendInfo(): void {
|
||||||
if (plusFallbackWarned) return;
|
if (plusBackendInfoShown) return;
|
||||||
plusFallbackWarned = true;
|
plusBackendInfoShown = true;
|
||||||
process.stderr.write(
|
process.stderr.write(
|
||||||
`${warn(
|
`${info(
|
||||||
'CLIProxyAPIPlus upstream repo is currently unavailable; local CLIProxy is falling back to ' +
|
'Using optional community-maintained CLIProxyAPIPlus backend. Stay on ' +
|
||||||
'`backend: original`. Run `ccs config` to update your saved config. ' +
|
'`backend: original` unless you need plus-only providers. ' +
|
||||||
|
`Fork: ${CLIPROXY_PLUS_FORK_URL} ` +
|
||||||
`Tracking: ${CLIPROXY_PLUS_TRACKING_URL}`
|
`Tracking: ${CLIPROXY_PLUS_TRACKING_URL}`
|
||||||
)}\n`
|
)}\n`
|
||||||
);
|
);
|
||||||
@@ -63,44 +56,26 @@ function emitPlusFallbackWarning(): void {
|
|||||||
export function getPlusBackendUnavailableMessage(provider?: string): string {
|
export function getPlusBackendUnavailableMessage(provider?: string): string {
|
||||||
const prefix = provider
|
const prefix = provider
|
||||||
? `${provider} requires CLIProxyAPIPlus,`
|
? `${provider} requires CLIProxyAPIPlus,`
|
||||||
: 'CLIProxyAPIPlus upstream repo is currently unavailable,';
|
: 'This provider requires CLIProxyAPIPlus,';
|
||||||
return (
|
return (
|
||||||
`${prefix} but local CLIProxy currently supports only \`backend: original\`. ` +
|
`${prefix} which is available through the optional community-maintained ` +
|
||||||
`Tracking: ${CLIPROXY_PLUS_TRACKING_URL}`
|
`\`backend: plus\` fork. Set \`cliproxy.backend: plus\` in config.yaml ` +
|
||||||
|
`only if you need plus-only providers. Fork: ${CLIPROXY_PLUS_FORK_URL}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ResolveLocalBackendOptions {
|
||||||
|
notifyOnPlus?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveLocalBackend(
|
export function resolveLocalBackend(
|
||||||
backend: CLIProxyBackend = DEFAULT_BACKEND,
|
backend: CLIProxyBackend = DEFAULT_BACKEND,
|
||||||
options: { warnOnFallback?: boolean } = {}
|
options: ResolveLocalBackendOptions = {}
|
||||||
): CLIProxyBackend {
|
): CLIProxyBackend {
|
||||||
if (backend !== 'plus') return backend;
|
if (backend === 'plus' && options.notifyOnPlus) {
|
||||||
if (options.warnOnFallback) {
|
emitPlusBackendInfo();
|
||||||
emitPlusFallbackWarning();
|
|
||||||
}
|
}
|
||||||
return 'original';
|
return backend;
|
||||||
}
|
|
||||||
|
|
||||||
function copyFallbackStateIfMissing(sourcePath: string, targetPath: string): void {
|
|
||||||
if (!fs.existsSync(sourcePath) || fs.existsSync(targetPath)) return;
|
|
||||||
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
||||||
fs.copyFileSync(sourcePath, targetPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function syncPlusFallbackStateIfNeeded(configuredBackend: CLIProxyBackend): void {
|
|
||||||
if (configuredBackend !== 'plus') return;
|
|
||||||
|
|
||||||
const plusDir = getBackendBinDir('plus');
|
|
||||||
const originalDir = getBackendBinDir('original');
|
|
||||||
|
|
||||||
copyFallbackStateIfMissing(
|
|
||||||
path.join(plusDir, getExecutableName('plus')),
|
|
||||||
path.join(originalDir, getExecutableName('original'))
|
|
||||||
);
|
|
||||||
copyFallbackStateIfMissing(path.join(plusDir, '.version'), path.join(originalDir, '.version'));
|
|
||||||
copyFallbackStateIfMissing(getVersionPinPath('plus'), getVersionPinPath('original'));
|
|
||||||
copyFallbackStateIfMissing(getVersionCachePath('plus'), getVersionCachePath('original'));
|
|
||||||
copyFallbackStateIfMissing(getVersionListCachePath('plus'), getVersionListCachePath('original'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConfiguredOrDefaultBackend(): CLIProxyBackend {
|
function getConfiguredOrDefaultBackend(): CLIProxyBackend {
|
||||||
@@ -117,16 +92,10 @@ export function getStoredConfiguredBackend(): CLIProxyBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get backend from config, with runtime fallback to 'original' when the user
|
* Get backend from config. Plus remains opt-in and resolves to the CCS-maintained
|
||||||
* still has `backend: plus` saved.
|
* community fork instead of degrading to original.
|
||||||
*
|
|
||||||
* Context (issue #1062): the upstream `router-for-me/CLIProxyAPIPlus` repo was
|
|
||||||
* deleted, so any `backend: plus` install/update path hits a 404. Rather than
|
|
||||||
* forcing users to manually edit config.yaml, we degrade to `original` at
|
|
||||||
* runtime and warn once. This keeps existing installations working without a
|
|
||||||
* reconfig step, while CCS self-maintains its own Plus fork (future work).
|
|
||||||
*/
|
*/
|
||||||
export function getConfiguredBackend(options: { warnOnFallback?: boolean } = {}): CLIProxyBackend {
|
export function getConfiguredBackend(options: ResolveLocalBackendOptions = {}): CLIProxyBackend {
|
||||||
return resolveLocalBackend(getConfiguredOrDefaultBackend(), options);
|
return resolveLocalBackend(getConfiguredOrDefaultBackend(), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,8 +133,7 @@ export class BinaryManager {
|
|||||||
|
|
||||||
constructor(config: Partial<BinaryManagerConfig> = {}, backend?: CLIProxyBackend) {
|
constructor(config: Partial<BinaryManagerConfig> = {}, backend?: CLIProxyBackend) {
|
||||||
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
this.backend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
this.backend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
const defaultConfig = createDefaultConfig(this.backend);
|
const defaultConfig = createDefaultConfig(this.backend);
|
||||||
this.config = { ...defaultConfig, ...config };
|
this.config = { ...defaultConfig, ...config };
|
||||||
}
|
}
|
||||||
@@ -217,8 +185,7 @@ export async function ensureCLIProxyBinary(
|
|||||||
options: EnsureCLIProxyBinaryOptions = {}
|
options: EnsureCLIProxyBinaryOptions = {}
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const configuredBackend = getConfiguredOrDefaultBackend();
|
const configuredBackend = getConfiguredOrDefaultBackend();
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const backend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const backend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
|
|
||||||
// Migrate old shared pin to backend-specific location (one-time migration)
|
// Migrate old shared pin to backend-specific location (one-time migration)
|
||||||
migrateVersionPin(backend);
|
migrateVersionPin(backend);
|
||||||
@@ -250,24 +217,21 @@ export async function ensureCLIProxyBinary(
|
|||||||
/** Check if CLIProxyAPI binary is installed */
|
/** Check if CLIProxyAPI binary is installed */
|
||||||
export function isCLIProxyInstalled(backend?: CLIProxyBackend): boolean {
|
export function isCLIProxyInstalled(backend?: CLIProxyBackend): boolean {
|
||||||
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
return new BinaryManager({}, effectiveBackend).isBinaryInstalled();
|
return new BinaryManager({}, effectiveBackend).isBinaryInstalled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get CLIProxyAPI binary path (may not exist) */
|
/** Get CLIProxyAPI binary path (may not exist) */
|
||||||
export function getCLIProxyPath(backend?: CLIProxyBackend): string {
|
export function getCLIProxyPath(backend?: CLIProxyBackend): string {
|
||||||
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
return new BinaryManager({}, effectiveBackend).getBinaryPath();
|
return new BinaryManager({}, effectiveBackend).getBinaryPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get installed CLIProxyAPI version from .version file */
|
/** Get installed CLIProxyAPI version from .version file */
|
||||||
export function getInstalledCliproxyVersion(backend?: CLIProxyBackend): string {
|
export function getInstalledCliproxyVersion(backend?: CLIProxyBackend): string {
|
||||||
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
return readInstalledVersion(
|
return readInstalledVersion(
|
||||||
getBackendBinDir(effectiveBackend),
|
getBackendBinDir(effectiveBackend),
|
||||||
BACKEND_CONFIG[effectiveBackend].fallbackVersion
|
BACKEND_CONFIG[effectiveBackend].fallbackVersion
|
||||||
@@ -294,8 +258,7 @@ export async function installCliproxyVersion(
|
|||||||
deps: InstallCliproxyVersionDeps = {}
|
deps: InstallCliproxyVersionDeps = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
const manager =
|
const manager =
|
||||||
deps.createManager?.({ version, verbose, forceVersion: true }, effectiveBackend) ??
|
deps.createManager?.({ version, verbose, forceVersion: true }, effectiveBackend) ??
|
||||||
new BinaryManager({ version, verbose, forceVersion: true }, effectiveBackend);
|
new BinaryManager({ version, verbose, forceVersion: true }, effectiveBackend);
|
||||||
@@ -337,8 +300,7 @@ export async function installCliproxyVersion(
|
|||||||
/** Fetch the latest CLIProxyAPI version from GitHub API */
|
/** Fetch the latest CLIProxyAPI version from GitHub API */
|
||||||
export async function fetchLatestCliproxyVersion(backend?: CLIProxyBackend): Promise<string> {
|
export async function fetchLatestCliproxyVersion(backend?: CLIProxyBackend): Promise<string> {
|
||||||
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
const result = await new BinaryManager({}, effectiveBackend).checkForUpdates();
|
const result = await new BinaryManager({}, effectiveBackend).checkForUpdates();
|
||||||
return result.latestVersion;
|
return result.latestVersion;
|
||||||
}
|
}
|
||||||
@@ -364,8 +326,7 @@ export async function checkCliproxyUpdate(
|
|||||||
backend?: CLIProxyBackend
|
backend?: CLIProxyBackend
|
||||||
): Promise<CliproxyUpdateCheckResult> {
|
): Promise<CliproxyUpdateCheckResult> {
|
||||||
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
const configuredBackend = backend ?? getConfiguredOrDefaultBackend();
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
const result = await new BinaryManager({}, effectiveBackend).checkForUpdates();
|
const result = await new BinaryManager({}, effectiveBackend).checkForUpdates();
|
||||||
|
|
||||||
// Import isNewerVersion for stability check
|
// Import isNewerVersion for stability check
|
||||||
|
|||||||
@@ -29,14 +29,14 @@ export const VERSION_PIN_FILE = '.version-pin';
|
|||||||
* @deprecated Use getGitHubApiUrls(backend) instead
|
* @deprecated Use getGitHubApiUrls(backend) instead
|
||||||
*/
|
*/
|
||||||
export const GITHUB_API_LATEST_RELEASE =
|
export const GITHUB_API_LATEST_RELEASE =
|
||||||
'https://api.github.com/repos/router-for-me/CLIProxyAPIPlus/releases/latest';
|
'https://api.github.com/repos/kaitranntt/CLIProxyAPIPlus/releases/latest';
|
||||||
export const GITHUB_API_ALL_RELEASES =
|
export const GITHUB_API_ALL_RELEASES =
|
||||||
'https://api.github.com/repos/router-for-me/CLIProxyAPIPlus/releases';
|
'https://api.github.com/repos/kaitranntt/CLIProxyAPIPlus/releases';
|
||||||
|
|
||||||
/** GitHub repos per backend */
|
/** GitHub repos per backend */
|
||||||
export const GITHUB_REPOS = {
|
export const GITHUB_REPOS = {
|
||||||
original: 'router-for-me/CLIProxyAPI',
|
original: 'router-for-me/CLIProxyAPI',
|
||||||
plus: 'router-for-me/CLIProxyAPIPlus',
|
plus: 'kaitranntt/CLIProxyAPIPlus',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/** Get GitHub API URLs for specific backend */
|
/** Get GitHub API URLs for specific backend */
|
||||||
|
|||||||
@@ -351,14 +351,14 @@ export async function execClaudeWithCLIProxy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!useRemoteProxy) {
|
if (!useRemoteProxy) {
|
||||||
localBackend = getConfiguredBackend({ warnOnFallback: true });
|
localBackend = getConfiguredBackend({ notifyOnPlus: true });
|
||||||
|
|
||||||
for (const p of allProviders) {
|
for (const p of allProviders) {
|
||||||
if (localBackend === 'original' && PLUS_ONLY_PROVIDERS.includes(p as CLIProxyProvider)) {
|
if (localBackend === 'original' && PLUS_ONLY_PROVIDERS.includes(p as CLIProxyProvider)) {
|
||||||
console.error('');
|
console.error('');
|
||||||
console.error(fail(getPlusBackendUnavailableMessage(p)));
|
console.error(fail(getPlusBackendUnavailableMessage(p)));
|
||||||
console.error('');
|
console.error('');
|
||||||
throw new Error(`Provider ${p} is temporarily unavailable on local CLIProxy`);
|
throw new Error(`Provider ${p} requires local CLIProxy Plus backend`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,20 +22,19 @@ export const BACKEND_CONFIG = {
|
|||||||
fallbackVersion: '6.8.2',
|
fallbackVersion: '6.8.2',
|
||||||
},
|
},
|
||||||
plus: {
|
plus: {
|
||||||
repo: 'router-for-me/CLIProxyAPIPlus',
|
repo: 'kaitranntt/CLIProxyAPIPlus',
|
||||||
binaryPrefix: 'CLIProxyAPIPlus',
|
binaryPrefix: 'CLIProxyAPIPlus',
|
||||||
executable: 'cli-proxy-api-plus',
|
executable: 'cli-proxy-api-plus',
|
||||||
fallbackVersion: '6.8.2-0',
|
fallbackVersion: '6.9.36-0',
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default backend
|
* Default backend
|
||||||
*
|
*
|
||||||
* Set to 'original' because upstream `router-for-me/CLIProxyAPIPlus` was
|
* Keep 'original' as the stable default. The Plus backend is available as an
|
||||||
* deleted (issue #1062). The original `router-for-me/CLIProxyAPI` repo is
|
* opt-in community-maintained fork for providers that are not available in the
|
||||||
* still maintained. Users with existing `backend: plus` configs are migrated
|
* original upstream binary.
|
||||||
* at runtime via a 404 fallback in the installer (see binary/installer.ts).
|
|
||||||
*/
|
*/
|
||||||
export const DEFAULT_BACKEND: CLIProxyBackend = 'original';
|
export const DEFAULT_BACKEND: CLIProxyBackend = 'original';
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
clearPinnedVersion,
|
clearPinnedVersion,
|
||||||
isVersionPinned,
|
isVersionPinned,
|
||||||
resolveLocalBackend,
|
resolveLocalBackend,
|
||||||
syncPlusFallbackStateIfNeeded,
|
|
||||||
} from '../binary-manager';
|
} from '../binary-manager';
|
||||||
import { BACKEND_CONFIG, DEFAULT_BACKEND } from '../platform-detector';
|
import { BACKEND_CONFIG, DEFAULT_BACKEND } from '../platform-detector';
|
||||||
import { CLIProxyBackend } from '../types';
|
import { CLIProxyBackend } from '../types';
|
||||||
@@ -58,8 +57,7 @@ export interface LatestVersionResult {
|
|||||||
export function getBinaryStatus(backend?: CLIProxyBackend): BinaryStatusResult {
|
export function getBinaryStatus(backend?: CLIProxyBackend): BinaryStatusResult {
|
||||||
const configuredBackend =
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
const backendConfig = BACKEND_CONFIG[effectiveBackend];
|
const backendConfig = BACKEND_CONFIG[effectiveBackend];
|
||||||
return {
|
return {
|
||||||
installed: isCLIProxyInstalled(effectiveBackend),
|
installed: isCLIProxyInstalled(effectiveBackend),
|
||||||
@@ -77,8 +75,7 @@ export function getBinaryStatus(backend?: CLIProxyBackend): BinaryStatusResult {
|
|||||||
export async function checkLatestVersion(backend?: CLIProxyBackend): Promise<LatestVersionResult> {
|
export async function checkLatestVersion(backend?: CLIProxyBackend): Promise<LatestVersionResult> {
|
||||||
const configuredBackend =
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use checkCliproxyUpdate which is backend-aware (uses correct GitHub repo)
|
// Use checkCliproxyUpdate which is backend-aware (uses correct GitHub repo)
|
||||||
@@ -127,8 +124,7 @@ export async function installVersion(
|
|||||||
|
|
||||||
const configuredBackend =
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await installCliproxyVersion(version, verbose, effectiveBackend);
|
await installCliproxyVersion(version, verbose, effectiveBackend);
|
||||||
@@ -157,8 +153,7 @@ export async function installLatest(
|
|||||||
): Promise<InstallResult> {
|
): Promise<InstallResult> {
|
||||||
const configuredBackend =
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const latestVersion = await fetchLatestCliproxyVersion(effectiveBackend);
|
const latestVersion = await fetchLatestCliproxyVersion(effectiveBackend);
|
||||||
@@ -196,8 +191,7 @@ export async function installLatest(
|
|||||||
export function isPinned(backend?: CLIProxyBackend): boolean {
|
export function isPinned(backend?: CLIProxyBackend): boolean {
|
||||||
const configuredBackend =
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
return isVersionPinned(effectiveBackend);
|
return isVersionPinned(effectiveBackend);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,8 +201,7 @@ export function isPinned(backend?: CLIProxyBackend): boolean {
|
|||||||
export function getPinned(backend?: CLIProxyBackend): string | null {
|
export function getPinned(backend?: CLIProxyBackend): string | null {
|
||||||
const configuredBackend =
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
return getPinnedVersion(effectiveBackend);
|
return getPinnedVersion(effectiveBackend);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +211,6 @@ export function getPinned(backend?: CLIProxyBackend): string | null {
|
|||||||
export function clearPin(backend?: CLIProxyBackend): void {
|
export function clearPin(backend?: CLIProxyBackend): void {
|
||||||
const configuredBackend =
|
const configuredBackend =
|
||||||
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
backend ?? loadOrCreateUnifiedConfig().cliproxy?.backend ?? DEFAULT_BACKEND;
|
||||||
syncPlusFallbackStateIfNeeded(configuredBackend);
|
const effectiveBackend = resolveLocalBackend(configuredBackend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(configuredBackend, { warnOnFallback: true });
|
|
||||||
clearPinnedVersion(effectiveBackend);
|
clearPinnedVersion(effectiveBackend);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ export function validateProfileName(name: string): string | null {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate provider/backend compatibility
|
* Validate provider/backend compatibility
|
||||||
* Returns error message if a provider requires Plus while local CLIProxy is
|
* Returns an error message if a provider requires Plus while local CLIProxy is
|
||||||
* running with the fallbacked original backend.
|
* configured for the default original backend.
|
||||||
*/
|
*/
|
||||||
export function validateProviderBackend(provider: CLIProxyProfileName): string | null {
|
export function validateProviderBackend(provider: CLIProxyProfileName): string | null {
|
||||||
// Normalize provider to lowercase for case-insensitive comparison
|
// Normalize provider to lowercase for case-insensitive comparison
|
||||||
|
|||||||
@@ -148,10 +148,9 @@ export type CLIProxyProvider =
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* CLIProxy backend selection
|
* CLIProxy backend selection
|
||||||
* - original: CLIProxyAPI (current default; upstream still maintained)
|
* - original: CLIProxyAPI (default; upstream maintained by router-for-me)
|
||||||
* - plus: CLIProxyAPIPlus (expanded provider support; upstream deleted as of
|
* - plus: CCS-maintained CLIProxyAPIPlus community fork for opt-in providers
|
||||||
* issue #1062 — runtime falls back to `original`. Retained for forward
|
* that are not available in the original backend
|
||||||
* compatibility once CCS self-maintains its own Plus build.)
|
|
||||||
*/
|
*/
|
||||||
export type CLIProxyBackend = 'original' | 'plus';
|
export type CLIProxyBackend = 'original' | 'plus';
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export async function showHelp(): Promise<void> {
|
|||||||
[
|
[
|
||||||
[
|
[
|
||||||
'--backend <type>',
|
'--backend <type>',
|
||||||
'Use specific backend: original | plus (local default: original; plus currently falls back locally)',
|
'Use specific backend: original | plus (default: original; plus uses community fork)',
|
||||||
],
|
],
|
||||||
['--target <cli>', 'Default target for created/edited variants: claude | droid'],
|
['--target <cli>', 'Default target for created/edited variants: claude | droid'],
|
||||||
['--verbose, -v', 'Show detailed diagnostics including routing hints and quota fetches'],
|
['--verbose, -v', 'Show detailed diagnostics including routing hints and quota fetches'],
|
||||||
@@ -105,11 +105,7 @@ export async function showHelp(): Promise<void> {
|
|||||||
|
|
||||||
console.log(dim(' Note: CLIProxy now persists by default. Use "stop" to terminate.'));
|
console.log(dim(' Note: CLIProxy now persists by default. Use "stop" to terminate.'));
|
||||||
console.log(dim(' Routing: use gcli/<model> or agy/<model> to keep overlapping models pinned.'));
|
console.log(dim(' Routing: use gcli/<model> or agy/<model> to keep overlapping models pinned.'));
|
||||||
console.log(
|
console.log(dim(' Backend: original is the default; plus is opt-in for plus-only providers.'));
|
||||||
dim(
|
|
||||||
' Backend: local CLIProxy currently uses original by default; saved plus configs fall back locally.'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(subheader('Notes:'));
|
console.log(subheader('Notes:'));
|
||||||
console.log(` Default fallback version: ${color(getFallbackVersion(), 'info')}`);
|
console.log(` Default fallback version: ${color(getFallbackVersion(), 'info')}`);
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { installCliproxyVersion, resolveLocalBackend } from '../../cliproxy/binary-manager';
|
||||||
installCliproxyVersion,
|
|
||||||
resolveLocalBackend,
|
|
||||||
syncPlusFallbackStateIfNeeded,
|
|
||||||
} from '../../cliproxy/binary-manager';
|
|
||||||
import { ensureCliproxyService, type ServiceStartResult } from '../../cliproxy/service-manager';
|
import { ensureCliproxyService, type ServiceStartResult } from '../../cliproxy/service-manager';
|
||||||
import { getProxyStatus as getProxyProcessStatus } from '../../cliproxy/session-tracker';
|
import { getProxyStatus as getProxyProcessStatus } from '../../cliproxy/session-tracker';
|
||||||
import { isCliproxyRunning } from '../../cliproxy/stats-fetcher';
|
import { isCliproxyRunning } from '../../cliproxy/stats-fetcher';
|
||||||
@@ -56,8 +52,7 @@ export async function installDashboardCliproxyVersion(
|
|||||||
backend: CLIProxyBackend,
|
backend: CLIProxyBackend,
|
||||||
deps: InstallDashboardCliproxyVersionDeps = defaultDeps
|
deps: InstallDashboardCliproxyVersionDeps = defaultDeps
|
||||||
): Promise<DashboardCliproxyInstallResult> {
|
): Promise<DashboardCliproxyInstallResult> {
|
||||||
syncPlusFallbackStateIfNeeded(backend);
|
const effectiveBackend = resolveLocalBackend(backend, { notifyOnPlus: true });
|
||||||
const effectiveBackend = resolveLocalBackend(backend, { warnOnFallback: true });
|
|
||||||
const backendLabel = effectiveBackend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy';
|
const backendLabel = effectiveBackend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy';
|
||||||
const shouldRestoreService = await wasProxyRunning(deps);
|
const shouldRestoreService = await wasProxyRunning(deps);
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ describe('Backend Selection', () => {
|
|||||||
|
|
||||||
it('has correct configuration for plus backend', () => {
|
it('has correct configuration for plus backend', () => {
|
||||||
const config = platformDetector.BACKEND_CONFIG.plus;
|
const config = platformDetector.BACKEND_CONFIG.plus;
|
||||||
assert.strictEqual(config.repo, 'router-for-me/CLIProxyAPIPlus');
|
assert.strictEqual(config.repo, 'kaitranntt/CLIProxyAPIPlus');
|
||||||
assert.strictEqual(config.binaryPrefix, 'CLIProxyAPIPlus');
|
assert.strictEqual(config.binaryPrefix, 'CLIProxyAPIPlus');
|
||||||
assert.strictEqual(config.executable, 'cli-proxy-api-plus');
|
assert.strictEqual(config.executable, 'cli-proxy-api-plus');
|
||||||
assert(config.fallbackVersion.match(/^\d+\.\d+\.\d+-\d+$/), 'plus version has -0 suffix');
|
assert(config.fallbackVersion.match(/^\d+\.\d+\.\d+-\d+$/), 'plus version has -0 suffix');
|
||||||
@@ -28,7 +28,7 @@ describe('Backend Selection', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('DEFAULT_BACKEND', () => {
|
describe('DEFAULT_BACKEND', () => {
|
||||||
it('defaults to original backend (Plus upstream deleted, issue #1062)', () => {
|
it('defaults to original backend', () => {
|
||||||
assert.strictEqual(platformDetector.DEFAULT_BACKEND, 'original');
|
assert.strictEqual(platformDetector.DEFAULT_BACKEND, 'original');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -45,7 +45,7 @@ describe('Backend Selection', () => {
|
|||||||
assert(info.binaryName.startsWith('CLIProxyAPIPlus_6.6.51-0_'));
|
assert(info.binaryName.startsWith('CLIProxyAPIPlus_6.6.51-0_'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses original backend by default (Plus upstream deleted, issue #1062)', () => {
|
it('uses original backend by default', () => {
|
||||||
const info = platformDetector.detectPlatform();
|
const info = platformDetector.detectPlatform();
|
||||||
assert(info.binaryName.startsWith('CLIProxyAPI_'));
|
assert(info.binaryName.startsWith('CLIProxyAPI_'));
|
||||||
assert(!info.binaryName.includes('CLIProxyAPIPlus'));
|
assert(!info.binaryName.includes('CLIProxyAPIPlus'));
|
||||||
@@ -77,7 +77,7 @@ describe('Backend Selection', () => {
|
|||||||
assert.strictEqual(name, expected);
|
assert.strictEqual(name, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('defaults to original backend (Plus upstream deleted, issue #1062)', () => {
|
it('defaults to original backend', () => {
|
||||||
const name = platformDetector.getExecutableName();
|
const name = platformDetector.getExecutableName();
|
||||||
const expected = isWindows ? 'cli-proxy-api.exe' : 'cli-proxy-api';
|
const expected = isWindows ? 'cli-proxy-api.exe' : 'cli-proxy-api';
|
||||||
assert.strictEqual(name, expected);
|
assert.strictEqual(name, expected);
|
||||||
@@ -93,10 +93,10 @@ describe('Backend Selection', () => {
|
|||||||
|
|
||||||
it('uses correct repo for plus backend', () => {
|
it('uses correct repo for plus backend', () => {
|
||||||
const url = platformDetector.getDownloadUrl('6.6.51-0', 'plus');
|
const url = platformDetector.getDownloadUrl('6.6.51-0', 'plus');
|
||||||
assert(url.includes('router-for-me/CLIProxyAPIPlus/releases'));
|
assert(url.includes('kaitranntt/CLIProxyAPIPlus/releases'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('defaults to original backend (Plus upstream deleted, issue #1062)', () => {
|
it('defaults to original backend', () => {
|
||||||
const url = platformDetector.getDownloadUrl();
|
const url = platformDetector.getDownloadUrl();
|
||||||
assert(url.includes('router-for-me/CLIProxyAPI/releases'));
|
assert(url.includes('router-for-me/CLIProxyAPI/releases'));
|
||||||
assert(!url.includes('CLIProxyAPIPlus'));
|
assert(!url.includes('CLIProxyAPIPlus'));
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('installCliproxyVersion', () => {
|
describe('installCliproxyVersion', () => {
|
||||||
it('degrades explicit plus backend requests to original before install flows run', async () => {
|
it('keeps explicit plus backend requests on plus before install flows run', async () => {
|
||||||
let seenBackend: string | undefined;
|
let seenBackend: string | undefined;
|
||||||
|
|
||||||
const binaryManager = await import(
|
const binaryManager = await import(
|
||||||
@@ -38,7 +38,7 @@ describe('installCliproxyVersion', () => {
|
|||||||
return {
|
return {
|
||||||
isBinaryInstalled: () => false,
|
isBinaryInstalled: () => false,
|
||||||
deleteBinary: () => undefined,
|
deleteBinary: () => undefined,
|
||||||
ensureBinary: async () => '/tmp/ccs-bin/original/cliproxy',
|
ensureBinary: async () => '/tmp/ccs-bin/plus/cliproxy',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
stopProxyFn: async () => ({ stopped: false, error: 'No active CLIProxy session found' }),
|
stopProxyFn: async () => ({ stopped: false, error: 'No active CLIProxy session found' }),
|
||||||
@@ -48,10 +48,10 @@ describe('installCliproxyVersion', () => {
|
|||||||
getInstalledVersion: () => '6.6.80',
|
getInstalledVersion: () => '6.6.80',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(seenBackend).toBe('original');
|
expect(seenBackend).toBe('plus');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns original and emits a real warning when plus backend is resolved locally', async () => {
|
it('returns plus and emits an informational notice when plus backend is resolved locally', async () => {
|
||||||
const binaryManager = await import(
|
const binaryManager = await import(
|
||||||
`../../../src/cliproxy/binary-manager?binary-manager-warning=${Date.now()}`
|
`../../../src/cliproxy/binary-manager?binary-manager-warning=${Date.now()}`
|
||||||
);
|
);
|
||||||
@@ -64,16 +64,17 @@ describe('installCliproxyVersion', () => {
|
|||||||
}) as typeof process.stderr.write;
|
}) as typeof process.stderr.write;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
expect(binaryManager.resolveLocalBackend('plus', { warnOnFallback: true })).toBe('original');
|
expect(binaryManager.resolveLocalBackend('plus', { notifyOnPlus: true })).toBe('plus');
|
||||||
} finally {
|
} finally {
|
||||||
process.stderr.write = originalWrite;
|
process.stderr.write = originalWrite;
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(writes.join('')).toContain('CLIProxyAPIPlus upstream repo is currently unavailable');
|
expect(writes.join('')).toContain('optional community-maintained CLIProxyAPIPlus backend');
|
||||||
expect(writes.join('')).toContain('backend: original');
|
expect(writes.join('')).toContain('backend: original');
|
||||||
|
expect(writes.join('')).toContain('kaitranntt/CLIProxyAPIPlus');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reuses plus binary and pin state when local runtime falls back to original', async () => {
|
it('uses plus binary and pin state when local runtime is configured for plus', async () => {
|
||||||
const { createEmptyUnifiedConfig } = await import('../../../src/config/unified-config-types');
|
const { createEmptyUnifiedConfig } = await import('../../../src/config/unified-config-types');
|
||||||
const { saveUnifiedConfig } = await import('../../../src/config/unified-config-loader');
|
const { saveUnifiedConfig } = await import('../../../src/config/unified-config-loader');
|
||||||
const { savePinnedVersion } = await import('../../../src/cliproxy/binary/version-cache');
|
const { savePinnedVersion } = await import('../../../src/cliproxy/binary/version-cache');
|
||||||
@@ -96,7 +97,7 @@ describe('installCliproxyVersion', () => {
|
|||||||
|
|
||||||
expect(status.installed).toBe(true);
|
expect(status.installed).toBe(true);
|
||||||
expect(status.pinnedVersion).toBe('6.6.80-0');
|
expect(status.pinnedVersion).toBe('6.6.80-0');
|
||||||
expect(status.binaryPath).toContain('/original/');
|
expect(status.binaryPath).toContain('/plus/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('attempts to stop the proxy even when there is no tracked running session', async () => {
|
it('attempts to stop the proxy even when there is no tracked running session', async () => {
|
||||||
|
|||||||
@@ -81,6 +81,12 @@ cliproxy:
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setConfiguredBackend(backend: 'original' | 'plus'): void {
|
||||||
|
const configPath = path.join(tmpDir, 'config.yaml');
|
||||||
|
const current = fs.readFileSync(configPath, 'utf-8');
|
||||||
|
fs.writeFileSync(configPath, current.replace(/backend: (original|plus)/, `backend: ${backend}`));
|
||||||
|
}
|
||||||
|
|
||||||
it('rejects provider change without model update', () => {
|
it('rejects provider change without model update', () => {
|
||||||
const result = updateVariant('demo', { provider: 'codex' });
|
const result = updateVariant('demo', { provider: 'codex' });
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(false);
|
||||||
@@ -96,13 +102,17 @@ cliproxy:
|
|||||||
expect(result.error).toContain('denylist');
|
expect(result.error).toContain('denylist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reports plus-only providers as temporarily unavailable on local CLIProxy', () => {
|
it('reports plus-only providers as requiring the optional plus backend', () => {
|
||||||
|
setConfiguredBackend('original');
|
||||||
|
|
||||||
const error = validateProviderBackend('ghcp');
|
const error = validateProviderBackend('ghcp');
|
||||||
expect(error).toContain('currently supports only `backend: original`');
|
expect(error).toContain('`backend: plus`');
|
||||||
expect(error).toContain('issues/1062');
|
expect(error).toContain('kaitranntt/CLIProxyAPIPlus');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('leaves the settings file unchanged when a plus-only provider update is rejected', () => {
|
it('leaves the settings file unchanged when a plus-only provider update is rejected', () => {
|
||||||
|
setConfiguredBackend('original');
|
||||||
|
|
||||||
const settingsPath = path.join(tmpDir, 'gemini-demo.settings.json');
|
const settingsPath = path.join(tmpDir, 'gemini-demo.settings.json');
|
||||||
const before = fs.readFileSync(settingsPath, 'utf-8');
|
const before = fs.readFileSync(settingsPath, 'utf-8');
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ function createDeps(
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('installDashboardCliproxyVersion', () => {
|
describe('installDashboardCliproxyVersion', () => {
|
||||||
it('restarts the proxy after install when it was already running', async () => {
|
it('restarts the plus proxy after install when it was already running', async () => {
|
||||||
const { deps, calls } = createDeps({ sessionRunning: true });
|
const { deps, calls } = createDeps({ sessionRunning: true });
|
||||||
|
|
||||||
const result = await installDashboardCliproxyVersion('6.7.1', 'plus', deps);
|
const result = await installDashboardCliproxyVersion('6.7.1', 'plus', deps);
|
||||||
@@ -56,14 +56,14 @@ describe('installDashboardCliproxyVersion', () => {
|
|||||||
success: true,
|
success: true,
|
||||||
restarted: true,
|
restarted: true,
|
||||||
port: 8317,
|
port: 8317,
|
||||||
message: 'Successfully installed CLIProxy v6.7.1 and restarted it on port 8317',
|
message: 'Successfully installed CLIProxy Plus v6.7.1 and restarted it on port 8317',
|
||||||
});
|
});
|
||||||
expect(calls.isCliproxyRunning).toBe(0);
|
expect(calls.isCliproxyRunning).toBe(0);
|
||||||
expect(calls.installCliproxyVersion).toBe(1);
|
expect(calls.installCliproxyVersion).toBe(1);
|
||||||
expect(calls.ensureCliproxyService).toBe(1);
|
expect(calls.ensureCliproxyService).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keeps the proxy stopped after install when it was not running beforehand', async () => {
|
it('keeps the plus proxy stopped after install when it was not running beforehand', async () => {
|
||||||
const { deps, calls } = createDeps({ sessionRunning: false, remoteRunning: false });
|
const { deps, calls } = createDeps({ sessionRunning: false, remoteRunning: false });
|
||||||
|
|
||||||
const result = await installDashboardCliproxyVersion('6.7.1', 'plus', deps);
|
const result = await installDashboardCliproxyVersion('6.7.1', 'plus', deps);
|
||||||
@@ -71,7 +71,7 @@ describe('installDashboardCliproxyVersion', () => {
|
|||||||
expect(result).toEqual<DashboardCliproxyInstallResult>({
|
expect(result).toEqual<DashboardCliproxyInstallResult>({
|
||||||
success: true,
|
success: true,
|
||||||
restarted: false,
|
restarted: false,
|
||||||
message: 'Successfully installed CLIProxy v6.7.1',
|
message: 'Successfully installed CLIProxy Plus v6.7.1',
|
||||||
});
|
});
|
||||||
expect(calls.isCliproxyRunning).toBe(1);
|
expect(calls.isCliproxyRunning).toBe(1);
|
||||||
expect(calls.installCliproxyVersion).toBe(1);
|
expect(calls.installCliproxyVersion).toBe(1);
|
||||||
@@ -118,8 +118,8 @@ describe('installDashboardCliproxyVersion', () => {
|
|||||||
expect(result).toEqual<DashboardCliproxyInstallResult>({
|
expect(result).toEqual<DashboardCliproxyInstallResult>({
|
||||||
success: false,
|
success: false,
|
||||||
restarted: false,
|
restarted: false,
|
||||||
error: 'Installed CLIProxy v6.7.1, but restart failed',
|
error: 'Installed CLIProxy Plus v6.7.1, but restart failed',
|
||||||
message: 'Installed CLIProxy v6.7.1, but failed to restart it',
|
message: 'Installed CLIProxy Plus v6.7.1, but failed to restart it',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ afterAll(async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('cliproxy-stats-routes install contract', () => {
|
describe('cliproxy-stats-routes install contract', () => {
|
||||||
it('routes saved plus configs through original backend for update checks', async () => {
|
it('keeps saved plus configs on the plus backend for update checks', async () => {
|
||||||
const response = await fetch(`${baseUrl}/api/cliproxy/update-check`);
|
const response = await fetch(`${baseUrl}/api/cliproxy/update-check`);
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
|
|
||||||
@@ -107,8 +107,8 @@ describe('cliproxy-stats-routes install contract', () => {
|
|||||||
latestVersion: string;
|
latestVersion: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(body.backend).toBe('original');
|
expect(body.backend).toBe('plus');
|
||||||
expect(body.backendLabel).toBe('CLIProxy');
|
expect(body.backendLabel).toBe('CLIProxy Plus');
|
||||||
expect(body.currentVersion).toBe('6.6.80');
|
expect(body.currentVersion).toBe('6.6.80');
|
||||||
expect(body.latestVersion).toBe('6.6.89');
|
expect(body.latestVersion).toBe('6.6.89');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user