refactor: rename proxy to cliproxy_server and update API routes

This commit is contained in:
kaitranntt
2025-12-19 02:14:49 -05:00
parent eeb6913d96
commit 8d8d4c248a
7 changed files with 104 additions and 161 deletions
+1 -78
View File
@@ -195,84 +195,6 @@ Without Developer Mode, CCS falls back to copying directories.
<br>
## Remote Proxy
Connect to a remote CLIProxyAPI server (Docker, Kubernetes, or another machine) instead of using the local binary.
### Configuration
Configure via dashboard (**Settings → Proxy** tab) or `~/.ccs/config.yaml`:
```yaml
proxy:
remote:
enabled: true
host: "192.168.1.100" # Remote server hostname/IP
port: 8317 # Default CLIProxy port
protocol: http # http or https
auth-token: "" # Optional auth token
fallback:
enabled: true # Fallback to local if remote unreachable
auto-start: true # Auto-start local proxy on fallback
local:
port: 8317
auto-start: true
```
### CLI Flags
Override config for one-time use:
| Flag | Description |
|------|-------------|
| `--proxy-host <host>` | Remote proxy hostname/IP |
| `--proxy-port <port>` | Proxy port (default: 8317) |
| `--proxy-protocol <proto>` | Protocol: `http` or `https` |
| `--proxy-auth-token <token>` | Auth token for remote proxy |
| `--local-proxy` | Force local mode, ignore remote config |
| `--remote-only` | Fail if remote unreachable (no fallback) |
```bash
# One-time remote connection
ccs gemini --proxy-host 192.168.1.100 --proxy-port 8317
# Force local mode
ccs gemini --local-proxy
# Strict remote mode (no fallback)
ccs gemini --proxy-host remote.example.com --remote-only
```
### Environment Variables
For CI/CD and automation:
| Variable | Description |
|----------|-------------|
| `CCS_PROXY_HOST` | Remote proxy hostname |
| `CCS_PROXY_PORT` | Proxy port |
| `CCS_PROXY_PROTOCOL` | Protocol (`http`/`https`) |
| `CCS_PROXY_AUTH_TOKEN` | Auth token |
| `CCS_PROXY_FALLBACK_ENABLED` | Enable local fallback (`1`/`0`) |
```bash
# Docker example
export CCS_PROXY_HOST="cliproxy-container"
export CCS_PROXY_PORT="8317"
ccs gemini "implement feature"
```
### Priority Resolution
Configuration sources are merged with this priority (highest first):
1. **CLI flags** — One-time overrides
2. **Environment variables** — CI/CD automation
3. **config.yaml** — Persistent settings
4. **Defaults** — Local mode, port 8317
<br>
## WebSearch
Third-party profiles (Gemini, Codex, GLM, etc.) cannot use Anthropic's native WebSearch. CCS automatically configures MCP-based web search as a fallback.
@@ -321,6 +243,7 @@ See [docs/websearch.md](./docs/websearch.md) for detailed configuration and trou
| OAuth Providers | [docs.ccs.kaitran.ca/providers/oauth-providers](https://docs.ccs.kaitran.ca/providers/oauth-providers) |
| Multi-Account Claude | [docs.ccs.kaitran.ca/providers/claude-accounts](https://docs.ccs.kaitran.ca/providers/claude-accounts) |
| API Profiles | [docs.ccs.kaitran.ca/providers/api-profiles](https://docs.ccs.kaitran.ca/providers/api-profiles) |
| Remote Proxy | [docs.ccs.kaitran.ca/features/remote-proxy](https://docs.ccs.kaitran.ca/features/remote-proxy) |
| CLI Reference | [docs.ccs.kaitran.ca/reference/cli-commands](https://docs.ccs.kaitran.ca/reference/cli-commands) |
| Architecture | [docs.ccs.kaitran.ca/reference/architecture](https://docs.ccs.kaitran.ca/reference/architecture) |
| Troubleshooting | [docs.ccs.kaitran.ca/reference/troubleshooting](https://docs.ccs.kaitran.ca/reference/troubleshooting) |
+23 -12
View File
@@ -16,7 +16,7 @@ import {
UNIFIED_CONFIG_VERSION,
DEFAULT_COPILOT_CONFIG,
DEFAULT_GLOBAL_ENV,
DEFAULT_PROXY_CONFIG,
DEFAULT_CLIPROXY_SERVER_CONFIG,
GlobalEnvConfig,
} from './unified-config-types';
import { isUnifiedConfigEnabled } from './feature-flags';
@@ -178,22 +178,33 @@ function mergeWithDefaults(partial: Partial<UnifiedConfig>): UnifiedConfig {
enabled: partial.global_env?.enabled ?? true,
env: partial.global_env?.env ?? { ...DEFAULT_GLOBAL_ENV },
},
// Proxy config - remote/local CLIProxyAPI settings
proxy: {
// CLIProxy server config - remote/local CLIProxyAPI settings
cliproxy_server: {
remote: {
enabled: partial.proxy?.remote?.enabled ?? DEFAULT_PROXY_CONFIG.remote.enabled,
host: partial.proxy?.remote?.host ?? DEFAULT_PROXY_CONFIG.remote.host,
port: partial.proxy?.remote?.port ?? DEFAULT_PROXY_CONFIG.remote.port,
protocol: partial.proxy?.remote?.protocol ?? DEFAULT_PROXY_CONFIG.remote.protocol,
auth_token: partial.proxy?.remote?.auth_token ?? DEFAULT_PROXY_CONFIG.remote.auth_token,
enabled:
partial.cliproxy_server?.remote?.enabled ?? DEFAULT_CLIPROXY_SERVER_CONFIG.remote.enabled,
host: partial.cliproxy_server?.remote?.host ?? DEFAULT_CLIPROXY_SERVER_CONFIG.remote.host,
port: partial.cliproxy_server?.remote?.port ?? DEFAULT_CLIPROXY_SERVER_CONFIG.remote.port,
protocol:
partial.cliproxy_server?.remote?.protocol ??
DEFAULT_CLIPROXY_SERVER_CONFIG.remote.protocol,
auth_token:
partial.cliproxy_server?.remote?.auth_token ??
DEFAULT_CLIPROXY_SERVER_CONFIG.remote.auth_token,
},
fallback: {
enabled: partial.proxy?.fallback?.enabled ?? DEFAULT_PROXY_CONFIG.fallback.enabled,
auto_start: partial.proxy?.fallback?.auto_start ?? DEFAULT_PROXY_CONFIG.fallback.auto_start,
enabled:
partial.cliproxy_server?.fallback?.enabled ??
DEFAULT_CLIPROXY_SERVER_CONFIG.fallback.enabled,
auto_start:
partial.cliproxy_server?.fallback?.auto_start ??
DEFAULT_CLIPROXY_SERVER_CONFIG.fallback.auto_start,
},
local: {
port: partial.proxy?.local?.port ?? DEFAULT_PROXY_CONFIG.local.port,
auto_start: partial.proxy?.local?.auto_start ?? DEFAULT_PROXY_CONFIG.local.auto_start,
port: partial.cliproxy_server?.local?.port ?? DEFAULT_CLIPROXY_SERVER_CONFIG.local.port,
auto_start:
partial.cliproxy_server?.local?.auto_start ??
DEFAULT_CLIPROXY_SERVER_CONFIG.local.auto_start,
},
},
};
+7 -7
View File
@@ -224,10 +224,10 @@ export interface ProxyLocalConfig {
}
/**
* Proxy configuration section.
* CLIProxy server configuration section.
* Controls whether CCS uses local or remote CLIProxyAPI instance.
*/
export interface ProxyConfig {
export interface CliproxyServerConfig {
/** Remote proxy settings */
remote: ProxyRemoteConfig;
/** Fallback behavior when remote is unreachable */
@@ -311,8 +311,8 @@ export interface UnifiedConfig {
global_env?: GlobalEnvConfig;
/** Copilot API configuration (GitHub Copilot proxy) */
copilot?: CopilotConfig;
/** Proxy configuration for remote/local CLIProxyAPI */
proxy?: ProxyConfig;
/** CLIProxy server configuration for remote/local mode */
cliproxy_server?: CliproxyServerConfig;
}
/**
@@ -343,10 +343,10 @@ export const DEFAULT_COPILOT_CONFIG: CopilotConfig = {
};
/**
* Default proxy configuration.
* Default CLIProxy server configuration.
* Local mode by default - remote must be explicitly enabled.
*/
export const DEFAULT_PROXY_CONFIG: ProxyConfig = {
export const DEFAULT_CLIPROXY_SERVER_CONFIG: CliproxyServerConfig = {
remote: {
enabled: false,
host: '',
@@ -411,7 +411,7 @@ export function createEmptyUnifiedConfig(): UnifiedConfig {
env: { ...DEFAULT_GLOBAL_ENV },
},
copilot: { ...DEFAULT_COPILOT_CONFIG },
proxy: { ...DEFAULT_PROXY_CONFIG },
cliproxy_server: { ...DEFAULT_CLIPROXY_SERVER_CONFIG },
};
}
+3 -3
View File
@@ -51,9 +51,9 @@ export async function startServer(options: ServerOptions): Promise<ServerInstanc
const { usageRoutes } = await import('./usage-routes');
app.use('/api/usage', usageRoutes);
// Proxy settings routes (Phase 5)
const proxyRoutes = (await import('./routes/proxy-routes')).default;
app.use('/api/proxy', proxyRoutes);
// CLIProxy server settings routes (Phase 5)
const cliproxyServerRoutes = (await import('./routes/proxy-routes')).default;
app.use('/api/cliproxy-server', cliproxyServerRoutes);
// Dev mode: use Vite middleware for HMR
if (options.dev) {
+24 -21
View File
@@ -1,69 +1,72 @@
/**
* Proxy Routes - API endpoints for proxy configuration
* CLIProxy Server Routes - API endpoints for proxy configuration
*
* Provides REST endpoints for managing CLIProxyAPI connection settings:
* - GET /api/proxy - Get proxy configuration
* - PUT /api/proxy - Update proxy configuration
* - POST /api/proxy/test - Test remote connection
* - GET /api/cliproxy-server - Get proxy configuration
* - PUT /api/cliproxy-server - Update proxy configuration
* - POST /api/cliproxy-server/test - Test remote connection
*/
import { Router, Request, Response } from 'express';
import { loadOrCreateUnifiedConfig, saveUnifiedConfig } from '../../config/unified-config-loader';
import { testConnection } from '../../cliproxy/remote-proxy-client';
import { DEFAULT_PROXY_CONFIG, ProxyConfig } from '../../config/unified-config-types';
import {
DEFAULT_CLIPROXY_SERVER_CONFIG,
CliproxyServerConfig,
} from '../../config/unified-config-types';
const router = Router();
/**
* GET /api/proxy - Get proxy configuration
* GET /api/cliproxy-server - Get proxy configuration
*/
router.get('/', async (_req: Request, res: Response) => {
try {
const config = await loadOrCreateUnifiedConfig();
res.json(config.proxy || DEFAULT_PROXY_CONFIG);
res.json(config.cliproxy_server || DEFAULT_CLIPROXY_SERVER_CONFIG);
} catch (error) {
console.error('[proxy-routes] Failed to load proxy config:', error);
console.error('[cliproxy-server-routes] Failed to load proxy config:', error);
res.status(500).json({ error: 'Failed to load proxy config' });
}
});
/**
* PUT /api/proxy - Update proxy configuration
* PUT /api/cliproxy-server - Update proxy configuration
*/
router.put('/', async (req: Request, res: Response) => {
try {
const config = await loadOrCreateUnifiedConfig();
const updates = req.body as Partial<ProxyConfig>;
const updates = req.body as Partial<CliproxyServerConfig>;
// Deep merge with defaults and current config
config.proxy = {
config.cliproxy_server = {
remote: {
...DEFAULT_PROXY_CONFIG.remote,
...config.proxy?.remote,
...DEFAULT_CLIPROXY_SERVER_CONFIG.remote,
...config.cliproxy_server?.remote,
...updates.remote,
},
fallback: {
...DEFAULT_PROXY_CONFIG.fallback,
...config.proxy?.fallback,
...DEFAULT_CLIPROXY_SERVER_CONFIG.fallback,
...config.cliproxy_server?.fallback,
...updates.fallback,
},
local: {
...DEFAULT_PROXY_CONFIG.local,
...config.proxy?.local,
...DEFAULT_CLIPROXY_SERVER_CONFIG.local,
...config.cliproxy_server?.local,
...updates.local,
},
};
await saveUnifiedConfig(config);
res.json(config.proxy);
res.json(config.cliproxy_server);
} catch (error) {
console.error('[proxy-routes] Failed to save proxy config:', error);
console.error('[cliproxy-server-routes] Failed to save proxy config:', error);
res.status(500).json({ error: 'Failed to save proxy config' });
}
});
/**
* POST /api/proxy/test - Test remote proxy connection
* POST /api/cliproxy-server/test - Test remote proxy connection
*/
router.post('/test', async (req: Request, res: Response) => {
try {
@@ -85,7 +88,7 @@ router.post('/test', async (req: Request, res: Response) => {
res.json(status);
} catch (error) {
console.error('[proxy-routes] Failed to test connection:', error);
console.error('[cliproxy-server-routes] Failed to test connection:', error);
res.status(500).json({ error: 'Failed to test connection' });
}
});
+10 -10
View File
@@ -183,8 +183,8 @@ export interface ProxyLocalConfig {
auto_start: boolean;
}
/** Proxy configuration */
export interface ProxyConfig {
/** CLIProxy server configuration */
export interface CliproxyServerConfig {
remote: ProxyRemoteConfig;
fallback: ProxyFallbackConfig;
local: ProxyLocalConfig;
@@ -389,13 +389,13 @@ export const api = {
method: 'DELETE',
}),
},
/** Proxy configuration API */
proxy: {
/** Get proxy configuration */
get: () => request<ProxyConfig>('/proxy'),
/** Update proxy configuration */
update: (config: Partial<ProxyConfig>) =>
request<ProxyConfig>('/proxy', {
/** CLIProxy server configuration API */
cliproxyServer: {
/** Get cliproxy server configuration */
get: () => request<CliproxyServerConfig>('/cliproxy-server'),
/** Update cliproxy server configuration */
update: (config: Partial<CliproxyServerConfig>) =>
request<CliproxyServerConfig>('/cliproxy-server', {
method: 'PUT',
body: JSON.stringify(config),
}),
@@ -407,7 +407,7 @@ export const api = {
authToken?: string;
allowSelfSigned?: boolean;
}) =>
request<RemoteProxyStatus>('/proxy/test', {
request<RemoteProxyStatus>('/cliproxy-server/test', {
method: 'POST',
body: JSON.stringify(params),
}),
+36 -30
View File
@@ -43,7 +43,7 @@ import {
} from 'lucide-react';
import { CodeEditor } from '@/components/code-editor';
import { api } from '@/lib/api-client';
import type { ProxyConfig, RemoteProxyStatus } from '@/lib/api-client';
import type { CliproxyServerConfig, RemoteProxyStatus } from '@/lib/api-client';
interface ProviderConfig {
enabled?: boolean;
@@ -117,7 +117,7 @@ export function SettingsPage() {
const [newEnvKey, setNewEnvKey] = useState('');
const [newEnvValue, setNewEnvValue] = useState('');
// Proxy state
const [proxyConfig, setProxyConfig] = useState<ProxyConfig | null>(null);
const [proxyConfig, setCliproxyServerConfig] = useState<CliproxyServerConfig | null>(null);
const [proxyLoading, setProxyLoading] = useState(true);
const [proxySaving, setProxySaving] = useState(false);
const [proxyError, setProxyError] = useState<string | null>(null);
@@ -131,7 +131,7 @@ export function SettingsPage() {
fetchStatus();
fetchRawConfig();
fetchGlobalEnvConfig();
fetchProxyConfig();
fetchCliproxyServerConfig();
}, []);
// Sync local model inputs when config changes
@@ -204,12 +204,12 @@ export function SettingsPage() {
}
};
const fetchProxyConfig = async () => {
const fetchCliproxyServerConfig = async () => {
try {
setProxyLoading(true);
setProxyError(null);
const data = await api.proxy.get();
setProxyConfig(data);
const data = await api.cliproxyServer.get();
setCliproxyServerConfig(data);
} catch (err) {
setProxyError((err as Error).message);
} finally {
@@ -426,7 +426,7 @@ export function SettingsPage() {
};
// Proxy functions
const saveProxyConfig = async (updates: Partial<ProxyConfig>) => {
const saveCliproxyServerConfig = async (updates: Partial<CliproxyServerConfig>) => {
if (!proxyConfig) return;
// Optimistic update
@@ -435,15 +435,15 @@ export function SettingsPage() {
fallback: { ...proxyConfig.fallback, ...updates.fallback },
local: { ...proxyConfig.local, ...updates.local },
};
setProxyConfig(optimisticConfig);
setCliproxyServerConfig(optimisticConfig);
setTestResult(null); // Clear previous test result on config change
try {
setProxySaving(true);
setProxyError(null);
const data = await api.proxy.update(updates);
setProxyConfig(data);
const data = await api.cliproxyServer.update(updates);
setCliproxyServerConfig(data);
setProxySuccess(true);
setTimeout(() => setProxySuccess(false), 1500);
// Silently refresh raw config
@@ -452,7 +452,7 @@ export function SettingsPage() {
.then((text) => text && setRawConfig(text))
.catch(() => {});
} catch (err) {
setProxyConfig(proxyConfig);
setCliproxyServerConfig(proxyConfig);
setProxyError((err as Error).message);
} finally {
setProxySaving(false);
@@ -473,7 +473,7 @@ export function SettingsPage() {
setProxyError(null);
setTestResult(null);
const result = await api.proxy.test({
const result = await api.cliproxyServer.test({
host,
port,
protocol,
@@ -586,9 +586,9 @@ export function SettingsPage() {
success={proxySuccess}
testResult={testResult}
testing={testing}
saveProxyConfig={saveProxyConfig}
saveCliproxyServerConfig={saveCliproxyServerConfig}
handleTestConnection={handleTestConnection}
fetchProxyConfig={fetchProxyConfig}
fetchCliproxyServerConfig={fetchCliproxyServerConfig}
fetchRawConfig={fetchRawConfig}
/>
)}
@@ -1284,16 +1284,16 @@ function GlobalEnvContent({
// Proxy Tab Content Component
interface ProxyContentProps {
config: ProxyConfig | null;
config: CliproxyServerConfig | null;
loading: boolean;
saving: boolean;
error: string | null;
success: boolean;
testResult: RemoteProxyStatus | null;
testing: boolean;
saveProxyConfig: (updates: Partial<ProxyConfig>) => void;
saveCliproxyServerConfig: (updates: Partial<CliproxyServerConfig>) => void;
handleTestConnection: () => void;
fetchProxyConfig: () => void;
fetchCliproxyServerConfig: () => void;
fetchRawConfig: () => void;
}
@@ -1305,9 +1305,9 @@ function ProxyContent({
success,
testResult,
testing,
saveProxyConfig,
saveCliproxyServerConfig,
handleTestConnection,
fetchProxyConfig,
fetchCliproxyServerConfig,
fetchRawConfig,
}: ProxyContentProps) {
// Memoized default config to avoid recreation
@@ -1359,7 +1359,7 @@ function ProxyContent({
const saveHost = () => {
const value = editedHost ?? displayHost;
if (value !== config?.remote.host) {
saveProxyConfig({ remote: { ...remoteConfig, host: value } });
saveCliproxyServerConfig({ remote: { ...remoteConfig, host: value } });
}
setEditedHost(null);
};
@@ -1367,7 +1367,7 @@ function ProxyContent({
const savePort = () => {
const port = parseInt(editedPort ?? displayPort, 10);
if (!isNaN(port) && port !== config?.remote.port) {
saveProxyConfig({ remote: { ...remoteConfig, port } });
saveCliproxyServerConfig({ remote: { ...remoteConfig, port } });
}
setEditedPort(null);
};
@@ -1375,7 +1375,7 @@ function ProxyContent({
const saveAuthToken = () => {
const value = editedAuthToken ?? displayAuthToken;
if (value !== config?.remote.auth_token) {
saveProxyConfig({ remote: { ...remoteConfig, auth_token: value } });
saveCliproxyServerConfig({ remote: { ...remoteConfig, auth_token: value } });
}
setEditedAuthToken(null);
};
@@ -1383,7 +1383,7 @@ function ProxyContent({
const saveLocalPort = () => {
const port = parseInt(editedLocalPort ?? displayLocalPort, 10);
if (!isNaN(port) && port !== config?.local.port) {
saveProxyConfig({ local: { ...localConfig, port } });
saveCliproxyServerConfig({ local: { ...localConfig, port } });
}
setEditedLocalPort(null);
};
@@ -1426,7 +1426,9 @@ function ProxyContent({
<div className="grid grid-cols-2 gap-3">
{/* Local Mode Card */}
<button
onClick={() => saveProxyConfig({ remote: { ...remoteConfig, enabled: false } })}
onClick={() =>
saveCliproxyServerConfig({ remote: { ...remoteConfig, enabled: false } })
}
disabled={saving}
className={`p-4 rounded-lg border-2 text-left transition-all ${
!isRemoteMode
@@ -1447,7 +1449,9 @@ function ProxyContent({
{/* Remote Mode Card */}
<button
onClick={() => saveProxyConfig({ remote: { ...remoteConfig, enabled: true } })}
onClick={() =>
saveCliproxyServerConfig({ remote: { ...remoteConfig, enabled: true } })
}
disabled={saving}
className={`p-4 rounded-lg border-2 text-left transition-all ${
isRemoteMode
@@ -1508,7 +1512,7 @@ function ProxyContent({
<Select
value={config?.remote.protocol || 'http'}
onValueChange={(value: 'http' | 'https') =>
saveProxyConfig({ remote: { ...remoteConfig, protocol: value } })
saveCliproxyServerConfig({ remote: { ...remoteConfig, protocol: value } })
}
disabled={saving}
>
@@ -1605,7 +1609,7 @@ function ProxyContent({
<Switch
checked={config?.fallback.enabled ?? true}
onCheckedChange={(checked) =>
saveProxyConfig({ fallback: { ...fallbackConfig, enabled: checked } })
saveCliproxyServerConfig({ fallback: { ...fallbackConfig, enabled: checked } })
}
disabled={saving || !isRemoteMode}
/>
@@ -1622,7 +1626,9 @@ function ProxyContent({
<Switch
checked={config?.fallback.auto_start ?? false}
onCheckedChange={(checked) =>
saveProxyConfig({ fallback: { ...fallbackConfig, auto_start: checked } })
saveCliproxyServerConfig({
fallback: { ...fallbackConfig, auto_start: checked },
})
}
disabled={saving || !isRemoteMode || !config?.fallback.enabled}
/>
@@ -1659,7 +1665,7 @@ function ProxyContent({
<Switch
checked={config?.local.auto_start ?? true}
onCheckedChange={(checked) =>
saveProxyConfig({ local: { ...localConfig, auto_start: checked } })
saveCliproxyServerConfig({ local: { ...localConfig, auto_start: checked } })
}
disabled={saving}
/>
@@ -1675,7 +1681,7 @@ function ProxyContent({
variant="outline"
size="sm"
onClick={() => {
fetchProxyConfig();
fetchCliproxyServerConfig();
fetchRawConfig();
}}
disabled={loading || saving}