Merge pull request #161 from kaitranntt/kai/feat/remote-proxy-config

fix(cliproxy): use /v1/models for remote proxy health check
This commit is contained in:
Kai (Tam Nhu) Tran
2025-12-20 18:33:17 -05:00
committed by GitHub
3 changed files with 49 additions and 41 deletions
+7 -3
View File
@@ -48,9 +48,10 @@ const DEFAULT_TIMEOUT_MS = 2000;
/**
* Get default port for protocol
* HTTP defaults to 8317 (CLIProxyAPI default), HTTPS to 443 (standard SSL)
*/
function getDefaultPort(protocol: 'http' | 'https'): number {
return protocol === 'https' ? 443 : 80;
return protocol === 'https' ? 443 : 8317;
}
/**
@@ -139,6 +140,9 @@ function createHttpsAgent(allowSelfSigned: boolean): https.Agent | undefined {
/**
* Check health of remote CLIProxyAPI instance
*
* Uses /v1/models endpoint for health check since CLIProxyAPI doesn't expose /health.
* This endpoint is always available and returns 200 when the server is operational.
*
* @param config Remote proxy client configuration
* @returns RemoteProxyStatus with reachability and latency
*/
@@ -157,8 +161,8 @@ export async function checkRemoteProxy(
};
}
// Use smart URL building - omit port if it's the default for the protocol
const url = buildProxyUrl(host, port, protocol, '/health');
// Use /v1/models as health check - CLIProxyAPI doesn't have /health endpoint
const url = buildProxyUrl(host, port, protocol, '/v1/models');
const startTime = Date.now();
try {
@@ -105,14 +105,15 @@ describe('remote-proxy-client', () => {
});
describe('health check URL construction', () => {
it('should construct correct health check URL pattern', () => {
// CLIProxyAPI uses /v1/models for health checks (no /health endpoint)
it('should construct correct health check URL pattern using /v1/models', () => {
const config: RemoteProxyClientConfig = {
host: '192.168.1.100',
port: 8317,
protocol: 'http',
};
const expectedUrl = `${config.protocol}://${config.host}:${config.port}/health`;
expect(expectedUrl).toBe('http://192.168.1.100:8317/health');
const expectedUrl = `${config.protocol}://${config.host}:${config.port}/v1/models`;
expect(expectedUrl).toBe('http://192.168.1.100:8317/v1/models');
});
it('should construct HTTPS URL when protocol is https', () => {
@@ -121,8 +122,8 @@ describe('remote-proxy-client', () => {
port: 443,
protocol: 'https',
};
const expectedUrl = `${config.protocol}://${config.host}:${config.port}/health`;
expect(expectedUrl).toBe('https://secure.example.com:443/health');
const expectedUrl = `${config.protocol}://${config.host}:${config.port}/v1/models`;
expect(expectedUrl).toBe('https://secure.example.com:443/v1/models');
});
});
});
+36 -33
View File
@@ -1322,7 +1322,8 @@ function ProxyContent({
const defaultLocal = { port: 8317, auto_start: true };
// Helper to get default port based on protocol
const getDefaultPort = (protocol: 'http' | 'https') => (protocol === 'https' ? 443 : 80);
// HTTP defaults to 8317 (CLIProxyAPI default), HTTPS to 443 (standard SSL)
const getDefaultPort = (protocol: 'http' | 'https') => (protocol === 'https' ? 443 : 8317);
// Sync local state with config (using refs to avoid lint warnings)
const hostInput = config?.remote.host ?? '';
@@ -1650,42 +1651,44 @@ function ProxyContent({
</div>
</div>
{/* Local Proxy Settings */}
<div className="space-y-3">
<h3 className="text-base font-medium">Local Proxy</h3>
<div className="space-y-3 p-4 rounded-lg border bg-muted/30">
{/* Port */}
<div className="space-y-1">
<label className="text-sm text-muted-foreground">Port</label>
<Input
type="number"
value={displayLocalPort}
onChange={(e) => setEditedLocalPort(e.target.value)}
onBlur={saveLocalPort}
placeholder="8317"
className="font-mono max-w-32"
disabled={saving}
/>
</div>
{/* Local Proxy Settings - Only show in Local mode */}
{!isRemoteMode && (
<div className="space-y-3">
<h3 className="text-base font-medium">Local Proxy</h3>
<div className="space-y-3 p-4 rounded-lg border bg-muted/30">
{/* Port */}
<div className="space-y-1">
<label className="text-sm text-muted-foreground">Port</label>
<Input
type="number"
value={displayLocalPort}
onChange={(e) => setEditedLocalPort(e.target.value)}
onBlur={saveLocalPort}
placeholder="8317"
className="font-mono max-w-32"
disabled={saving}
/>
</div>
{/* Auto-start */}
<div className="flex items-center justify-between">
<div>
<p className="font-medium text-sm">Auto-start</p>
<p className="text-xs text-muted-foreground">
Start local proxy automatically when needed
</p>
{/* Auto-start */}
<div className="flex items-center justify-between">
<div>
<p className="font-medium text-sm">Auto-start</p>
<p className="text-xs text-muted-foreground">
Start local proxy automatically when needed
</p>
</div>
<Switch
checked={config?.local.auto_start ?? true}
onCheckedChange={(checked) =>
saveCliproxyServerConfig({ local: { ...localConfig, auto_start: checked } })
}
disabled={saving}
/>
</div>
<Switch
checked={config?.local.auto_start ?? true}
onCheckedChange={(checked) =>
saveCliproxyServerConfig({ local: { ...localConfig, auto_start: checked } })
}
disabled={saving}
/>
</div>
</div>
</div>
)}
</div>
</ScrollArea>