Merge pull request #383 from kaitranntt/fix/claude-provider-validation

fix(cliproxy): add claude provider to all validation arrays and UI types
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-01-27 21:26:22 -05:00
commit eba0ed277b
11 files changed
+91 -70

No files matched your search

@@ -35,6 +35,7 @@ export async function refreshToken(
case 'iflow':
case 'kiro':
case 'ghcp':
case 'claude':
return {
success: false,
error: `Token refresh not yet implemented for ${provider}`,
+3 -10
View File
@@ -17,20 +17,13 @@ import {
soloAccount,
} from '../../cliproxy/account-manager';
import type { CLIProxyProvider } from '../../cliproxy/types';
import { CLIPROXY_PROFILES } from '../../auth/profile-detector';
const router = Router();
const registry = new ProfileRegistry();
/** Valid CLIProxy providers */
const VALID_PROVIDERS: CLIProxyProvider[] = [
'gemini',
'codex',
'agy',
'qwen',
'iflow',
'kiro',
'ghcp',
];
/** Valid CLIProxy providers - derived from canonical CLIPROXY_PROFILES */
const VALID_PROVIDERS: CLIProxyProvider[] = [...CLIPROXY_PROFILES];
/** Check if provider is valid */
function isValidProvider(provider: string): provider is CLIProxyProvider {
+5 -10
View File
@@ -35,19 +35,12 @@ import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
import { tryKiroImport } from '../../cliproxy/auth/kiro-import';
import { getProviderTokenDir } from '../../cliproxy/auth/token-manager';
import type { CLIProxyProvider } from '../../cliproxy/types';
import { CLIPROXY_PROFILES } from '../../auth/profile-detector';
const router = Router();
// Valid providers list
const validProviders: CLIProxyProvider[] = [
'gemini',
'codex',
'agy',
'qwen',
'iflow',
'kiro',
'ghcp',
];
// Valid providers list - derived from canonical CLIPROXY_PROFILES
const validProviders: CLIProxyProvider[] = [...CLIPROXY_PROFILES];
/**
* GET /api/cliproxy/auth - Get auth status for built-in CLIProxy profiles
@@ -78,6 +71,8 @@ router.get('/', async (_req: Request, res: Response): Promise<void> => {
iflow: 'iflow',
kiro: 'kiro',
copilot: 'ghcp', // CLIProxyAPI returns 'copilot', we map to 'ghcp'
anthropic: 'claude', // CLIProxyAPI returns 'anthropic', we map to 'claude'
claude: 'claude',
};
// Update lastUsedAt for providers with recent activity
+3 -10
View File
@@ -14,6 +14,7 @@ import {
} from '../../cliproxy/stats-fetcher';
import { fetchAccountQuota } from '../../cliproxy/quota-fetcher';
import type { CLIProxyProvider } from '../../cliproxy/types';
import { CLIPROXY_PROFILES } from '../../auth/profile-detector';
import {
getCliproxyWritablePath,
getCliproxyConfigPath,
@@ -517,16 +518,8 @@ router.put('/models/:provider', async (req: Request, res: Response): Promise<voi
router.get('/quota/:provider/:accountId', async (req: Request, res: Response): Promise<void> => {
const { provider, accountId } = req.params;
// Validate provider
const validProviders: CLIProxyProvider[] = [
'agy',
'gemini',
'codex',
'qwen',
'iflow',
'kiro',
'ghcp',
];
// Validate provider - use canonical CLIPROXY_PROFILES
const validProviders: CLIProxyProvider[] = [...CLIPROXY_PROFILES];
if (!validProviders.includes(provider as CLIProxyProvider)) {
res.status(400).json({
error: 'Invalid provider',
+6 -12
View File
@@ -13,15 +13,14 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useCreateVariant, useCliproxyAuth } from '@/hooks/use-cliproxy';
import { usePrivacy } from '@/contexts/privacy-context';
const providers = ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp'] as const;
import { CLIPROXY_PROVIDERS, getProviderDisplayName } from '@/lib/provider-config';
const schema = z.object({
name: z
.string()
.min(1, 'Name is required')
.regex(/^[a-zA-Z][a-zA-Z0-9._-]*$/, 'Invalid variant name'),
provider: z.enum(providers, { message: 'Provider is required' }),
provider: z.enum(CLIPROXY_PROVIDERS, { message: 'Provider is required' }),
model: z.string().optional(),
account: z.string().optional(),
});
@@ -33,15 +32,10 @@ interface CliproxyDialogProps {
onClose: () => void;
}
const providerOptions = [
{ value: 'gemini', label: 'Google Gemini' },
{ value: 'codex', label: 'OpenAI Codex' },
{ value: 'agy', label: 'Antigravity' },
{ value: 'qwen', label: 'Alibaba Qwen' },
{ value: 'iflow', label: 'iFlow' },
{ value: 'kiro', label: 'Kiro (AWS)' },
{ value: 'ghcp', label: 'GitHub Copilot (OAuth)' },
];
const providerOptions = CLIPROXY_PROVIDERS.map((id) => ({
value: id,
label: getProviderDisplayName(id),
}));
export function CliproxyDialog({ open, onClose }: CliproxyDialogProps) {
const createMutation = useCreateVariant();
+5 -10
View File
@@ -10,6 +10,7 @@ import { RefreshCw, Loader2, AlertTriangle } from 'lucide-react';
import { useCliproxyAuth } from '@/hooks/use-cliproxy';
import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow';
import { cn } from '@/lib/utils';
import { CLIPROXY_PROVIDERS, getProviderDisplayName } from '@/lib/provider-config';
interface VersionInfo {
currentVersion: string;
@@ -135,16 +136,10 @@ export function CliproxyHeader({
.catch(() => {}); // Silently fail
}, []);
const providers = [
{ id: 'claude', displayName: 'Claude' },
{ id: 'gemini', displayName: 'Gemini' },
{ id: 'codex', displayName: 'Codex' },
{ id: 'agy', displayName: 'Agy' },
{ id: 'qwen', displayName: 'Qwen' },
{ id: 'iflow', displayName: 'iFlow' },
{ id: 'kiro', displayName: 'Kiro' },
{ id: 'ghcp', displayName: 'GitHub Copilot' },
];
const providers = CLIPROXY_PROVIDERS.map((id) => ({
id,
displayName: getProviderDisplayName(id),
}));
const getProviderStatus = (providerId: string) => {
const status = authData?.authStatus.find((s) => s.provider === providerId);
+31 -9
View File
@@ -1,20 +1,42 @@
/**
* Constants for Quick Setup Wizard
* Provider display info with custom ordering for wizard UI.
* Provider IDs must match CLIPROXY_PROVIDERS from provider-config.ts
*/
import type { ProviderOption } from './types';
import type { CLIProxyProvider } from '@/lib/provider-config';
export const PROVIDERS: ProviderOption[] = [
{ id: 'agy', name: 'Antigravity', description: 'Antigravity AI models' },
{ id: 'claude', name: 'Claude (Anthropic)', description: 'Claude Opus/Sonnet models' },
{ id: 'gemini', name: 'Google Gemini', description: 'Gemini Pro/Flash models' },
{ id: 'codex', name: 'OpenAI Codex', description: 'GPT-4 and codex models' },
{ id: 'qwen', name: 'Alibaba Qwen', description: 'Qwen Code models' },
{ id: 'iflow', name: 'iFlow', description: 'iFlow AI models' },
{ id: 'kiro', name: 'Kiro (AWS)', description: 'AWS CodeWhisperer models' },
{ id: 'ghcp', name: 'GitHub Copilot (OAuth)', description: 'GitHub Copilot via OAuth' },
/** Provider display info for wizard - ordered by recommendation */
const PROVIDER_INFO: Record<CLIProxyProvider, { name: string; description: string }> = {
agy: { name: 'Antigravity', description: 'Antigravity AI models' },
claude: { name: 'Claude (Anthropic)', description: 'Claude Opus/Sonnet models' },
gemini: { name: 'Google Gemini', description: 'Gemini Pro/Flash models' },
codex: { name: 'OpenAI Codex', description: 'GPT-4 and codex models' },
qwen: { name: 'Alibaba Qwen', description: 'Qwen Code models' },
iflow: { name: 'iFlow', description: 'iFlow AI models' },
kiro: { name: 'Kiro (AWS)', description: 'AWS CodeWhisperer models' },
ghcp: { name: 'GitHub Copilot (OAuth)', description: 'GitHub Copilot via OAuth' },
};
/** Wizard display order - most recommended first */
const WIZARD_PROVIDER_ORDER: CLIProxyProvider[] = [
'agy',
'claude',
'gemini',
'codex',
'qwen',
'iflow',
'kiro',
'ghcp',
];
export const PROVIDERS: ProviderOption[] = WIZARD_PROVIDER_ORDER.map((id) => ({
id,
name: PROVIDER_INFO[id].name,
description: PROVIDER_INFO[id].description,
}));
export const ALL_STEPS = ['provider', 'auth', 'variant', 'success'];
export function getStepProgress(step: string): number {
+2 -1
View File
@@ -22,6 +22,7 @@ import {
useCancelAuth,
} from '@/hooks/use-cliproxy';
import type { AuthStatus, OAuthAccount } from '@/lib/api-client';
import type { CLIProxyProvider } from '@/lib/provider-config';
import { applyDefaultPreset } from '@/lib/preset-utils';
import { usePrivacy } from '@/contexts/privacy-context';
import { toast } from 'sonner';
@@ -136,7 +137,7 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
try {
await createMutation.mutateAsync({
name: variantName,
provider: selectedProvider as 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow',
provider: selectedProvider as CLIProxyProvider,
model: modelName || undefined,
account: selectedAccount?.id,
});
+2 -3
View File
@@ -7,6 +7,7 @@ import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { api } from '@/lib/api-client';
import { isValidProvider } from '@/lib/provider-config';
interface AuthFlowState {
provider: string | null;
@@ -14,8 +15,6 @@ interface AuthFlowState {
error: string | null;
}
const VALID_PROVIDERS = ['gemini', 'codex', 'agy', 'qwen', 'iflow', 'kiro', 'ghcp'];
export function useCliproxyAuthFlow() {
const [state, setState] = useState<AuthFlowState>({
provider: null,
@@ -35,7 +34,7 @@ export function useCliproxyAuthFlow() {
const startAuth = useCallback(
async (provider: string) => {
if (!VALID_PROVIDERS.includes(provider)) {
if (!isValidProvider(provider)) {
setState({
provider: null,
isAuthenticating: false,
+6 -4
View File
@@ -3,6 +3,8 @@
* Phase 03: REST API Routes & CRUD
*/
import type { CLIProxyProvider } from './provider-config';
const BASE_URL = '/api';
async function request<T>(url: string, options?: RequestInit): Promise<T> {
@@ -47,7 +49,7 @@ export interface UpdateProfile {
export interface Variant {
name: string;
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp';
provider: CLIProxyProvider;
settings: string;
account?: string;
port?: number;
@@ -56,13 +58,13 @@ export interface Variant {
export interface CreateVariant {
name: string;
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp';
provider: CLIProxyProvider;
model?: string;
account?: string;
}
export interface UpdateVariant {
provider?: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp';
provider?: CLIProxyProvider;
model?: string;
account?: string;
}
@@ -72,7 +74,7 @@ export interface OAuthAccount {
id: string;
email?: string;
nickname?: string;
provider: 'gemini' | 'codex' | 'agy' | 'qwen' | 'iflow' | 'kiro' | 'ghcp';
provider: CLIProxyProvider;
isDefault: boolean;
tokenFile: string;
createdAt: string;
+27 -1
View File
@@ -1,8 +1,34 @@
/**
* Provider Configuration
* Shared constants for provider branding and assets
* Shared constants for CLIProxy providers - SINGLE SOURCE OF TRUTH for UI
*
* When adding a new provider, update CLIPROXY_PROVIDERS array and related mappings.
*/
/**
* Canonical list of CLIProxy provider IDs
* This is the UI's single source of truth for valid providers.
* Must stay in sync with backend's CLIPROXY_PROFILES in src/auth/profile-detector.ts
*/
export const CLIPROXY_PROVIDERS = [
'gemini',
'codex',
'agy',
'qwen',
'iflow',
'kiro',
'ghcp',
'claude',
] as const;
/** Union type for CLIProxy provider IDs */
export type CLIProxyProvider = (typeof CLIPROXY_PROVIDERS)[number];
/** Check if a string is a valid CLIProxy provider */
export function isValidProvider(provider: string): provider is CLIProxyProvider {
return CLIPROXY_PROVIDERS.includes(provider as CLIProxyProvider);
}
// Map provider names to asset filenames (only providers with actual logos)
export const PROVIDER_ASSETS: Record<string, string> = {
gemini: '/assets/providers/gemini-color.svg',