Merge pull request #1575 from walker1211/codex/fix-cliproxy-provider-routes

Fix CLIProxy provider routes for original backend
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-06-22 16:11:28 -04:00
commit 05c203aff2
14 files changed
+451 -107

No files matched your search

@@ -1,14 +1,13 @@
/**
* Regression tests for the `ccs api create --cliproxy-provider claude` bridge path.
* Regression tests for the `ccs api create --cliproxy-provider <provider>` bridge path.
*
* PR #1554 fixed the main CLIProxy env-builder to use the root URL for the built-in
* claude provider. This file locks the same rule on the parallel api-create bridge path
* The bridge path must use the same route rules as the main CLIProxy env-builder
* (resolveCliproxyBridgeProfile / listCliproxyBridgeProviders) so both paths stay
* consistent.
* consistent across backends.
*
* Background: CLIProxyAPI registers /v1/messages at the ROOT. The /api/provider/<x>
* prefix is a Plus-only route for non-Claude providers. Using /api/provider/claude
* returns 404 on the base CLIProxyAPI installation.
* Background: the original CLIProxyAPI backend registers Claude-compatible routes at
* the root. The /api/provider/<x> prefix is a Plus-only route. Using provider-scoped
* routes against the original backend returns 404.
*/
import * as fs from 'fs';
@@ -23,7 +22,7 @@ import { resolveCliproxyBridgeMetadata } from '../cliproxy-profile-bridge';
import { invalidateConfigCache } from '../../../config/config-loader-facade';
import { clearConfigCache } from '../../../cliproxy/config/base-config-loader';
describe('cliproxy-profile-bridge: claude provider uses root URL', () => {
describe('cliproxy-profile-bridge: backend-aware provider route paths', () => {
let tempHome: string;
let originalCcsHome: string | undefined;
@@ -37,12 +36,28 @@ describe('cliproxy-profile-bridge: claude provider uses root URL', () => {
});
afterEach(() => {
process.env.CCS_HOME = originalCcsHome;
if (originalCcsHome !== undefined) {
process.env.CCS_HOME = originalCcsHome;
} else {
delete process.env.CCS_HOME;
}
invalidateConfigCache();
clearConfigCache();
fs.rmSync(tempHome, { recursive: true, force: true });
});
function writeBackendConfig(backend: 'original' | 'plus'): void {
const ccsDir = path.join(tempHome, '.ccs');
fs.mkdirSync(ccsDir, { recursive: true });
fs.writeFileSync(
path.join(ccsDir, 'config.yaml'),
['version: 1', 'cliproxy:', ` backend: ${backend}`, ''].join('\n'),
'utf8'
);
invalidateConfigCache();
clearConfigCache();
}
// ── resolveCliproxyBridgeProfile ──────────────────────────────────────────
it('resolveCliproxyBridgeProfile(claude) produces root base URL', () => {
@@ -65,13 +80,20 @@ describe('cliproxy-profile-bridge: claude provider uses root URL', () => {
expect(profile.models.haiku).toBe('');
});
it('resolveCliproxyBridgeProfile(gemini) still uses scoped /api/provider path', () => {
it('resolveCliproxyBridgeProfile(gemini) uses root URL on the original backend', () => {
const profile = resolveCliproxyBridgeProfile('gemini');
expect(profile.baseUrl).toContain('/api/provider/gemini');
expect(profile.routePath).toBe('/api/provider/gemini');
expect(profile.baseUrl).toBe('http://127.0.0.1:8317/');
expect(profile.routePath).toBe('/');
});
it('resolveCliproxyBridgeProfile(codex) still uses scoped /api/provider path', () => {
it('resolveCliproxyBridgeProfile(codex) uses root URL on the original backend', () => {
const profile = resolveCliproxyBridgeProfile('codex');
expect(profile.baseUrl).toBe('http://127.0.0.1:8317/');
expect(profile.routePath).toBe('/');
});
it('resolveCliproxyBridgeProfile(codex) uses scoped path on the plus backend', () => {
writeBackendConfig('plus');
const profile = resolveCliproxyBridgeProfile('codex');
expect(profile.baseUrl).toContain('/api/provider/codex');
expect(profile.routePath).toBe('/api/provider/codex');
@@ -87,7 +109,15 @@ describe('cliproxy-profile-bridge: claude provider uses root URL', () => {
expect(claudeInfo?.routePath).not.toContain('/api/provider/claude');
});
it('listCliproxyBridgeProviders keeps scoped routePaths for non-claude providers', () => {
it('listCliproxyBridgeProviders shows root routePaths for original-backend providers', () => {
const providers = listCliproxyBridgeProviders();
for (const info of providers) {
expect(info.routePath).toBe('/');
}
});
it('listCliproxyBridgeProviders keeps scoped routePaths for plus-backend non-claude providers', () => {
writeBackendConfig('plus');
const providers = listCliproxyBridgeProviders();
for (const info of providers) {
if (info.provider === 'claude') continue;
+18 -9
View File
@@ -37,6 +37,7 @@ import {
loadConfigSafe,
mutateConfig,
} from '../../config/config-loader-facade';
import { ProfileError } from '../../errors/error-types';
/** Check if URL is an OpenRouter endpoint */
function isOpenRouterUrl(baseUrl: string): boolean {
@@ -333,19 +334,27 @@ export function createCliproxyBridgeProfile(
resolved.target,
provider
);
const detectedBridge = resolveCliproxyBridgeMetadata({
env: {
ANTHROPIC_BASE_URL: resolved.baseUrl,
ANTHROPIC_AUTH_TOKEN: resolved.apiKey,
},
});
return {
...result,
name: resolved.name,
provider,
target: resolved.target,
cliproxyBridge:
resolveCliproxyBridgeMetadata({
env: {
ANTHROPIC_BASE_URL: resolved.baseUrl,
ANTHROPIC_AUTH_TOKEN: resolved.apiKey,
},
}) ?? null,
cliproxyBridge: detectedBridge ?? {
provider,
providerDisplayName: resolved.providerDisplayName,
routePath: resolved.routePath,
currentBaseUrl: resolved.baseUrl,
source: resolved.source,
usesCurrentTarget: true,
usesCurrentAuthToken: true,
},
};
}
@@ -361,7 +370,7 @@ export function updateApiProfileTarget(
if (isUnifiedMode()) {
mutateConfig((config) => {
if (!config.profiles[name]) {
throw new Error(`API profile not found: ${name}`);
throw new ProfileError(`API profile not found: ${name}`, name);
}
if (target === 'claude') {
@@ -412,7 +421,7 @@ function removeApiProfileUnified(name: string): void {
const profile = config.profiles[name];
if (!profile) {
throw new Error(`API profile not found: ${name}`);
throw new ProfileError(`API profile not found: ${name}`, name);
}
if (profile.settings) {