import { getGlobalManagementKey } from '@/components/AuthContext'; const API_BASE = '/api'; // Types matching backend structures export interface GeminiKey { 'api-key': string; prefix?: string; 'base-url'?: string; 'proxy-url'?: string; headers?: Record; 'excluded-models'?: string[]; } export interface ClaudeModel { name: string; alias?: string; } export interface ClaudeKey { 'api-key': string; prefix?: string; 'base-url'?: string; 'proxy-url'?: string; models?: ClaudeModel[]; headers?: Record; 'excluded-models'?: string[]; } export interface CodexModel { name: string; alias?: string; } export interface CodexKey { 'api-key': string; prefix?: string; 'base-url'?: string; 'proxy-url'?: string; models?: CodexModel[]; headers?: Record; 'excluded-models'?: string[]; } export interface VertexCompatModel { name: string; alias: string; } export interface VertexCompatKey { 'api-key': string; prefix?: string; 'base-url'?: string; 'proxy-url'?: string; headers?: Record; models?: VertexCompatModel[]; } export interface OpenAICompatibilityAPIKey { 'api-key': string; prefix?: string; } export interface OpenAICompatibilityModel { name: string; alias?: string; } export interface OpenAICompatibility { name: string; prefix?: string; 'base-url': string; 'api-key-entries'?: OpenAICompatibilityAPIKey[]; models?: OpenAICompatibilityModel[]; headers?: Record; } export interface OAuthModelAlias { from: string; to: string; } // Usage statistics types - matching backend structure export interface TokenStats { input_tokens: number; output_tokens: number; reasoning_tokens: number; cached_tokens: number; total_tokens: number; } export interface RequestDetail { timestamp: string; source: string; auth_index: string; tokens: TokenStats; failed: boolean; } export interface ModelSnapshot { total_requests: number; total_tokens: number; details?: RequestDetail[]; } export interface APISnapshot { total_requests: number; total_tokens: number; models: Record; } export interface UsageSnapshot { total_requests: number; success_count: number; failure_count: number; total_tokens: number; apis?: Record; requests_by_day?: Record; requests_by_hour?: Record; tokens_by_day?: Record; tokens_by_hour?: Record; } // Auth file types export interface AuthFile { name: string; provider: string; models?: string[]; enabled: boolean; created_at?: string; } // API Error class export class ApiError extends Error { status: number; detail: string; constructor(status: number, detail: string) { super(detail); this.status = status; this.detail = detail; this.name = 'ApiError'; } } // Connection state let isConnected = true; let connectionListeners: ((connected: boolean) => void)[] = []; export function onConnectionChange(listener: (connected: boolean) => void) { connectionListeners.push(listener); return () => { connectionListeners = connectionListeners.filter(l => l !== listener); }; } function setConnected(connected: boolean) { if (isConnected !== connected) { isConnected = connected; connectionListeners.forEach(l => l(connected)); } } export function getConnectionStatus() { return isConnected; } // Core fetch function with error handling async function fetchApi(endpoint: string, options?: RequestInit): Promise { try { const managementKey = getGlobalManagementKey(); const headers: Record = { 'Content-Type': 'application/json', ...options?.headers as Record, }; if (managementKey) { headers['Authorization'] = `Bearer ${managementKey}`; } const res = await fetch(`${API_BASE}${endpoint}`, { ...options, headers, }); setConnected(true); if (!res.ok) { const error = await res.json().catch(() => ({ detail: 'Request failed' })); throw new ApiError(res.status, error.error || error.detail || `HTTP ${res.status}`); } // Handle empty responses const text = await res.text(); if (!text) return {} as T; return JSON.parse(text); } catch (error) { if (error instanceof ApiError) { throw error; } // Network error - backend not reachable if (error instanceof TypeError && error.message.includes('fetch')) { setConnected(false); throw new ApiError(0, 'Cannot connect to backend server'); } setConnected(false); throw new ApiError(0, error instanceof Error ? error.message : 'Unknown error'); } } // ============ Health Check ============ export interface HealthStatus { status: string; version?: string; components?: { database?: boolean; providers?: boolean; }; } export async function checkHealth(): Promise { const res = await fetch('/api/health'); if (!res.ok) { throw new Error('Health check failed'); } setConnected(true); const data = await res.json(); return { status: data.status || 'healthy', version: data.version, components: data.components, }; } export async function checkReady(): Promise<{ ready: boolean }> { const res = await fetch('/api/ready'); if (!res.ok) { throw new Error('Ready check failed'); } return res.json(); } // ============ Usage Statistics ============ export async function getUsageStatistics(): Promise<{ usage: UsageSnapshot; failed_requests: number }> { return fetchApi('/usage'); } export async function exportUsageStatistics(): Promise<{ version: number; exported_at: string; usage: UsageSnapshot }> { return fetchApi('/usage/export'); } export async function importUsageStatistics(data: { version: number; usage: UsageSnapshot }): Promise<{ added: number; skipped: number; total_requests: number; failed_requests: number }> { return fetchApi('/usage/import', { method: 'POST', body: JSON.stringify(data), }); } // ============ Config ============ export async function getConfig(): Promise> { return fetchApi('/config'); } export async function getConfigYAML(): Promise { const res = await fetch(`${API_BASE}/config.yaml`); return res.text(); } export async function putConfigYAML(yaml: string): Promise<{ status: string }> { return fetchApi('/config.yaml', { method: 'PUT', headers: { 'Content-Type': 'text/yaml' }, body: yaml, }); } // ============ API Keys (Access Control) ============ export async function getAPIKeys(): Promise<{ 'api-keys': string[] }> { return fetchApi('/api-keys'); } export async function putAPIKeys(keys: string[]): Promise<{ status: string }> { return fetchApi('/api-keys', { method: 'PUT', body: JSON.stringify(keys), }); } export async function patchAPIKeys(data: { old?: string; new?: string; index?: number; value?: string }): Promise<{ status: string }> { return fetchApi('/api-keys', { method: 'PATCH', body: JSON.stringify(data), }); } export async function deleteAPIKey(params: { index?: number; value?: string }): Promise<{ status: string }> { const query = new URLSearchParams(); if (params.index !== undefined) query.set('index', params.index.toString()); if (params.value) query.set('value', params.value); return fetchApi(`/api-keys?${query}`, { method: 'DELETE' }); } // ============ Live Models API (/v1/models) ============ export interface V1Model { id: string; object: string; created?: number; owned_by?: string; } export interface V1ModelsResponse { object: string; data: V1Model[]; } /** * Fetch models from /v1/models endpoint using an API key for authentication. * This calls the OpenAI-compatible models endpoint directly (not the management API). */ export async function fetchV1Models(apiKey: string): Promise { const res = await fetch('/v1/models', { headers: { 'Authorization': `Bearer ${apiKey}`, }, }); if (!res.ok) { const error = await res.json().catch(() => ({ error: { message: 'Request failed' } })); throw new ApiError(res.status, error.error?.message || error.detail || `HTTP ${res.status}`); } return res.json(); } // ============ Gemini API Keys ============ export async function getGeminiKeys(): Promise<{ 'gemini-api-key': GeminiKey[] }> { return fetchApi('/gemini-api-key'); } export async function putGeminiKeys(keys: GeminiKey[]): Promise<{ status: string }> { return fetchApi('/gemini-api-key', { method: 'PUT', body: JSON.stringify(keys), }); } export async function patchGeminiKey(data: { index?: number; match?: string; value: Partial }): Promise<{ status: string }> { return fetchApi('/gemini-api-key', { method: 'PATCH', body: JSON.stringify(data), }); } export async function deleteGeminiKey(params: { 'api-key'?: string; index?: number }): Promise<{ status: string }> { const query = new URLSearchParams(); if (params['api-key']) query.set('api-key', params['api-key']); if (params.index !== undefined) query.set('index', params.index.toString()); return fetchApi(`/gemini-api-key?${query}`, { method: 'DELETE' }); } // ============ Claude API Keys ============ export async function getClaudeKeys(): Promise<{ 'claude-api-key': ClaudeKey[] }> { return fetchApi('/claude-api-key'); } export async function putClaudeKeys(keys: ClaudeKey[]): Promise<{ status: string }> { return fetchApi('/claude-api-key', { method: 'PUT', body: JSON.stringify(keys), }); } export async function patchClaudeKey(data: { index?: number; match?: string; value: Partial }): Promise<{ status: string }> { return fetchApi('/claude-api-key', { method: 'PATCH', body: JSON.stringify(data), }); } export async function deleteClaudeKey(params: { 'api-key'?: string; index?: number }): Promise<{ status: string }> { const query = new URLSearchParams(); if (params['api-key']) query.set('api-key', params['api-key']); if (params.index !== undefined) query.set('index', params.index.toString()); return fetchApi(`/claude-api-key?${query}`, { method: 'DELETE' }); } // ============ Codex API Keys ============ export async function getCodexKeys(): Promise<{ 'codex-api-key': CodexKey[] }> { return fetchApi('/codex-api-key'); } export async function putCodexKeys(keys: CodexKey[]): Promise<{ status: string }> { return fetchApi('/codex-api-key', { method: 'PUT', body: JSON.stringify(keys), }); } export async function patchCodexKey(data: { index?: number; match?: string; value: Partial }): Promise<{ status: string }> { return fetchApi('/codex-api-key', { method: 'PATCH', body: JSON.stringify(data), }); } export async function deleteCodexKey(params: { 'api-key'?: string; index?: number }): Promise<{ status: string }> { const query = new URLSearchParams(); if (params['api-key']) query.set('api-key', params['api-key']); if (params.index !== undefined) query.set('index', params.index.toString()); return fetchApi(`/codex-api-key?${query}`, { method: 'DELETE' }); } // ============ Vertex API Keys ============ export async function getVertexKeys(): Promise<{ 'vertex-api-key': VertexCompatKey[] }> { return fetchApi('/vertex-api-key'); } export async function putVertexKeys(keys: VertexCompatKey[]): Promise<{ status: string }> { return fetchApi('/vertex-api-key', { method: 'PUT', body: JSON.stringify(keys), }); } export async function patchVertexKey(data: { index?: number; match?: string; value: Partial }): Promise<{ status: string }> { return fetchApi('/vertex-api-key', { method: 'PATCH', body: JSON.stringify(data), }); } export async function deleteVertexKey(params: { 'api-key'?: string; index?: number }): Promise<{ status: string }> { const query = new URLSearchParams(); if (params['api-key']) query.set('api-key', params['api-key']); if (params.index !== undefined) query.set('index', params.index.toString()); return fetchApi(`/vertex-api-key?${query}`, { method: 'DELETE' }); } // ============ OpenAI Compatibility ============ export async function getOpenAICompat(): Promise<{ 'openai-compatibility': OpenAICompatibility[] }> { return fetchApi('/openai-compatibility'); } export async function putOpenAICompat(entries: OpenAICompatibility[]): Promise<{ status: string }> { return fetchApi('/openai-compatibility', { method: 'PUT', body: JSON.stringify(entries), }); } export async function patchOpenAICompat(data: { name?: string; index?: number; value: Partial }): Promise<{ status: string }> { return fetchApi('/openai-compatibility', { method: 'PATCH', body: JSON.stringify(data), }); } export async function deleteOpenAICompat(params: { name?: string; index?: number }): Promise<{ status: string }> { const query = new URLSearchParams(); if (params.name) query.set('name', params.name); if (params.index !== undefined) query.set('index', params.index.toString()); return fetchApi(`/openai-compatibility?${query}`, { method: 'DELETE' }); } // ============ OAuth Model Alias ============ export async function getOAuthModelAlias(): Promise<{ 'oauth-model-alias': Record }> { return fetchApi('/oauth-model-alias'); } export async function putOAuthModelAlias(data: Record): Promise<{ status: string }> { return fetchApi('/oauth-model-alias', { method: 'PUT', body: JSON.stringify(data), }); } export async function patchOAuthModelAlias(data: { provider?: string; channel?: string; aliases: OAuthModelAlias[] }): Promise<{ status: string }> { return fetchApi('/oauth-model-alias', { method: 'PATCH', body: JSON.stringify(data), }); } export async function deleteOAuthModelAlias(channel: string): Promise<{ status: string }> { return fetchApi(`/oauth-model-alias?channel=${encodeURIComponent(channel)}`, { method: 'DELETE' }); } // ============ OAuth Excluded Models ============ export async function getOAuthExcludedModels(): Promise<{ 'oauth-excluded-models': Record }> { return fetchApi('/oauth-excluded-models'); } export async function putOAuthExcludedModels(data: Record): Promise<{ status: string }> { return fetchApi('/oauth-excluded-models', { method: 'PUT', body: JSON.stringify(data), }); } export async function patchOAuthExcludedModels(data: { provider: string; models: string[] }): Promise<{ status: string }> { return fetchApi('/oauth-excluded-models', { method: 'PATCH', body: JSON.stringify(data), }); } export async function deleteOAuthExcludedModels(provider: string): Promise<{ status: string }> { return fetchApi(`/oauth-excluded-models?provider=${encodeURIComponent(provider)}`, { method: 'DELETE' }); } // ============ Auth Files ============ export async function listAuthFiles(): Promise<{ files: AuthFile[] }> { return fetchApi('/auth-files'); } export async function getAuthFileModels(name: string): Promise<{ models: string[] }> { return fetchApi(`/auth-files/models?name=${encodeURIComponent(name)}`); } export async function uploadAuthFile(file: File): Promise<{ status: string; name: string }> { const formData = new FormData(); formData.append('file', file); const res = await fetch(`${API_BASE}/auth-files`, { method: 'POST', body: formData, }); if (!res.ok) { const error = await res.json().catch(() => ({ error: 'Upload failed' })); throw new ApiError(res.status, error.error || 'Upload failed'); } return res.json(); } export async function deleteAuthFile(name: string): Promise<{ status: string }> { return fetchApi(`/auth-files?name=${encodeURIComponent(name)}`, { method: 'DELETE' }); } export async function patchAuthFileStatus(name: string, enabled: boolean): Promise<{ status: string }> { return fetchApi('/auth-files/status', { method: 'PATCH', body: JSON.stringify({ name, enabled }), }); } // ============ Debug & Logging ============ export async function getDebug(): Promise<{ debug: boolean }> { return fetchApi('/debug'); } export async function putDebug(value: boolean): Promise<{ status: string }> { return fetchApi('/debug', { method: 'PUT', body: JSON.stringify({ value }), }); } export async function getLoggingToFile(): Promise<{ 'logging-to-file': boolean }> { return fetchApi('/logging-to-file'); } export async function putLoggingToFile(value: boolean): Promise<{ status: string }> { return fetchApi('/logging-to-file', { method: 'PUT', body: JSON.stringify({ value }), }); } export async function getUsageStatisticsEnabled(): Promise<{ 'usage-statistics-enabled': boolean }> { return fetchApi('/usage-statistics-enabled'); } export async function putUsageStatisticsEnabled(value: boolean): Promise<{ status: string }> { return fetchApi('/usage-statistics-enabled', { method: 'PUT', body: JSON.stringify({ value }), }); } // ============ Logs ============ export interface LogLine { timestamp?: number; level?: string; message?: string; raw?: string; } export interface LogsResponse { lines?: LogLine[]; 'line-count'?: number; 'latest-timestamp'?: number; logs?: string; // Fallback for simple string response } export async function getLogs(after?: string, limit?: number): Promise { const params = new URLSearchParams(); if (after) params.append('after', after); if (limit) params.append('limit', limit.toString()); const query = params.toString() ? `?${params}` : ''; return fetchApi(`/logs${query}`); } export async function deleteLogs(): Promise<{ success: boolean; message: string; removed: number }> { return fetchApi('/logs', { method: 'DELETE' }); } export async function getRequestLog(): Promise<{ 'request-log': boolean }> { return fetchApi('/request-log'); } export async function putRequestLog(value: boolean): Promise<{ status: string }> { return fetchApi('/request-log', { method: 'PUT', body: JSON.stringify({ value }), }); } export async function getErrorLogs(): Promise<{ files: { name: string; size: number; modified: number }[] }> { return fetchApi('/request-error-logs'); } export async function downloadErrorLog(filename: string): Promise { // Use custom fetch to get blob/text directly since backend might return file content const managementKey = getGlobalManagementKey(); const headers: Record = {}; if (managementKey) { headers['Authorization'] = `Bearer ${managementKey}`; } const res = await fetch(`${API_BASE}/request-error-logs/${encodeURIComponent(filename)}`, { headers }); if (!res.ok) throw new Error('Download failed'); return res.text(); } // ============ Tools & Batch ============ export async function batchValidateKeys(keys: string[], provider: string): Promise<{ valid: string[]; invalid: string[] }> { return fetchApi('/batch/validate', { method: 'POST', body: JSON.stringify({ keys, provider }), }); } export async function batchAddKeys(keys: string[], provider: string): Promise<{ added: number; skipped: number }> { return fetchApi('/batch/add', { method: 'POST', body: JSON.stringify({ keys, provider }), }); } export async function createBackup(): Promise<{ id: string; url: string; created_at: string }> { return fetchApi('/backup/create', { method: 'POST' }); } export async function restoreBackup(file: File): Promise<{ status: string }> { const formData = new FormData(); formData.append('file', file); const managementKey = getGlobalManagementKey(); const headers: Record = {}; if (managementKey) { headers['Authorization'] = `Bearer ${managementKey}`; } const res = await fetch(`${API_BASE}/backup/restore`, { method: 'POST', headers, body: formData, }); if (!res.ok) { const error = await res.json().catch(() => ({ detail: 'Restore failed' })); throw new ApiError(res.status, error.error || error.detail); } return res.json(); } export async function testProvider(provider: string): Promise<{ status: 'success' | 'failed'; latency: number; error?: string }> { return fetchApi(`/test-provider?provider=${encodeURIComponent(provider)}`); } // ============ OAuth & Auth Files ============ export async function submitCallback(url: string): Promise<{ status: string }> { return fetchApi('/oauth/callback', { method: 'POST', body: JSON.stringify({ url }), }); } export async function iflowCookieAuth(cookie: string): Promise<{ status: string }> { return fetchApi('/oauth/iflow-cookie', { method: 'POST', body: JSON.stringify({ cookie }), }); } export async function importVertexCredential(file: File, location?: string): Promise<{ status: string }> { const formData = new FormData(); formData.append('file', file); if (location) formData.append('location', location); const managementKey = getGlobalManagementKey(); const headers: Record = {}; if (managementKey) { headers['Authorization'] = `Bearer ${managementKey}`; } const res = await fetch(`${API_BASE}/vertex-credential`, { method: 'POST', headers, body: formData, }); if (!res.ok) { const error = await res.json().catch(() => ({ detail: 'Import failed' })); throw new ApiError(res.status, error.error || error.detail); } return res.json(); } // ============ Routing ============ export async function getRoutingStrategy(): Promise<{ strategy: string }> { return fetchApi('/routing/strategy'); } export async function putRoutingStrategy(value: string): Promise<{ status: string }> { return fetchApi('/routing/strategy', { method: 'PUT', body: JSON.stringify({ value }), }); } // ============ Proxy URL ============ export async function getProxyURL(): Promise<{ 'proxy-url': string }> { return fetchApi('/proxy-url'); } export async function putProxyURL(value: string): Promise<{ status: string }> { return fetchApi('/proxy-url', { method: 'PUT', body: JSON.stringify({ value }), }); } export async function deleteProxyURL(): Promise<{ status: string }> { return fetchApi('/proxy-url', { method: 'DELETE' }); } // ============ Request Retry ============ export async function getRequestRetry(): Promise<{ 'request-retry': number }> { return fetchApi('/request-retry'); } export async function putRequestRetry(value: number): Promise<{ status: string }> { return fetchApi('/request-retry', { method: 'PUT', body: JSON.stringify({ value }), }); } // ============ OAuth Authentication URLs ============ export async function getAnthropicAuthURL(): Promise<{ url: string; state: string }> { return fetchApi('/anthropic-auth-url'); } export async function getCodexAuthURL(): Promise<{ url: string; state: string }> { return fetchApi('/codex-auth-url'); } export async function getGeminiCLIAuthURL(): Promise<{ url: string; state: string }> { return fetchApi('/gemini-cli-auth-url'); } export async function getAuthStatus(provider: string, state: string): Promise<{ status: string; error?: string }> { return fetchApi(`/get-auth-status?provider=${encodeURIComponent(provider)}&state=${encodeURIComponent(state)}`); } // ============ Version Info ============ export async function getLatestVersion(): Promise<{ version: string; current: string; update_available: boolean }> { return fetchApi('/latest-version'); } // ============ Utility functions ============ export function formatNumber(num: number): string { if (num === null || num === undefined) return '0'; if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; if (num >= 1000) return (num / 1000).toFixed(1) + 'K'; return num.toString(); } export function formatBytes(bytes: number): string { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } export function formatDate(dateStr: string): string { if (!dateStr) return '-'; const date = new Date(dateStr); return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } export function formatDuration(seconds: number): string { if (seconds < 60) return `${seconds}s`; if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`; const hours = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); if (hours < 24) return `${hours}h ${mins}m`; const days = Math.floor(hours / 24); return `${days}d ${hours % 24}h`; } export function formatLatency(ms: number): string { if (ms < 1000) return `${Math.round(ms)}ms`; return `${(ms / 1000).toFixed(2)}s`; } export function formatPercentage(value: number, decimals: number = 1): string { return `${value.toFixed(decimals)}%`; } export function getStatusColor(status: string): string { switch (status) { case 'healthy': case 'success': return 'text-white'; case 'degraded': case 'warning': return 'text-white/70'; case 'unhealthy': case 'error': return 'text-white/50'; default: return 'text-muted-foreground'; } } export function getRelativeTime(dateStr: string): string { if (!dateStr) return 'Never'; const date = new Date(dateStr); const now = new Date(); const diff = now.getTime() - date.getTime(); const seconds = Math.floor(diff / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (seconds < 60) return 'Just now'; if (minutes < 60) return `${minutes}m ago`; if (hours < 24) return `${hours}h ago`; if (days < 7) return `${days}d ago`; return formatDate(dateStr); } export function maskApiKey(key: string): string { if (!key || key.length < 8) return '****'; return key.substring(0, 4) + '****' + key.substring(key.length - 4); } // ============ Extended Auth Files ============ export interface AuthFileItem { name: string; type?: string; provider?: string; size?: number; modtime?: number; modified?: string; disabled?: boolean; auth_index?: number | string; authIndex?: number | string; runtime_only?: boolean; runtimeOnly?: boolean; } export async function getAuthFiles(): Promise<{ files: AuthFileItem[] }> { return fetchApi('/auth-files'); } export async function deleteAllAuthFiles(): Promise<{ status: string }> { return fetchApi('/auth-files?all=true', { method: 'DELETE' }); } export async function downloadAuthFile(name: string): Promise { const managementKey = getGlobalManagementKey(); const headers: Record = {}; if (managementKey) { headers['Authorization'] = `Bearer ${managementKey}`; } const res = await fetch(`${API_BASE}/auth-files/download?name=${encodeURIComponent(name)}`, { headers }); if (!res.ok) throw new Error('Download failed'); return res.text(); } export async function setAuthFileStatus(name: string, disabled: boolean): Promise<{ disabled: boolean }> { return fetchApi('/auth-files/status', { method: 'PATCH', body: JSON.stringify({ name, disabled }), }); } // ============ OAuth Providers ============ export type OAuthProvider = 'codex' | 'anthropic' | 'antigravity' | 'gemini-cli' | 'qwen' | 'iflow'; export interface OAuthStartResponse { status: string; url: string; state: string; } export interface OAuthStatusResponse { status: 'ok' | 'wait' | 'error'; error?: string; } // Get the correct auth URL endpoint for each provider function getAuthUrlEndpoint(provider: OAuthProvider): string { switch (provider) { case 'codex': return '/codex-auth-url'; case 'anthropic': return '/anthropic-auth-url'; case 'antigravity': return '/antigravity-auth-url'; case 'gemini-cli': return '/gemini-cli-auth-url'; case 'qwen': return '/qwen-auth-url'; case 'iflow': return '/iflow-auth-url'; default: return `/${provider}-auth-url`; } } export async function startOAuth(provider: OAuthProvider, options?: { projectId?: string }): Promise { const endpoint = getAuthUrlEndpoint(provider); const params = new URLSearchParams(); params.append('is_webui', '1'); if (options?.projectId && provider === 'gemini-cli') { params.append('project_id', options.projectId); } return fetchApi(`${endpoint}?${params}`); } export async function getOAuthStatus(state: string): Promise { return fetchApi(`/get-auth-status?state=${encodeURIComponent(state)}`); } export async function submitOAuthCallback(provider: OAuthProvider, redirectUrl: string): Promise<{ status: string }> { // Parse the redirect URL to extract code and state try { const url = new URL(redirectUrl); const code = url.searchParams.get('code') || ''; const state = url.searchParams.get('state') || ''; const error = url.searchParams.get('error') || ''; return fetchApi('/oauth-callback', { method: 'POST', body: JSON.stringify({ provider, code, state, error, redirect_url: redirectUrl }), }); } catch { return fetchApi('/oauth-callback', { method: 'POST', body: JSON.stringify({ provider, redirect_url: redirectUrl }), }); } } export interface IFlowCookieAuthResponse { status: 'ok' | 'error'; email?: string; expired?: string; saved_path?: string; type?: string; error?: string; } export async function iflowCookieAuthFull(cookie: string): Promise { return fetchApi('/iflow-auth-url', { method: 'POST', body: JSON.stringify({ cookie }), }); } export interface VertexImportResponse { project_id?: string; email?: string; location?: string; 'auth-file'?: string; auth_file?: string; } export async function importVertexCredentialFull(file: File, location?: string): Promise { const formData = new FormData(); formData.append('file', file); if (location) formData.append('location', location); const managementKey = getGlobalManagementKey(); const headers: Record = {}; if (managementKey) { headers['Authorization'] = `Bearer ${managementKey}`; } const res = await fetch(`${API_BASE}/vertex/import`, { method: 'POST', headers, body: formData, }); if (!res.ok) { const error = await res.json().catch(() => ({ detail: 'Import failed' })); throw new ApiError(res.status, error.error || error.detail); } return res.json(); } // ============ OAuth Model Mappings (for Auth Files) ============ export interface OAuthModelMappingEntry { name: string; alias: string; fork?: boolean; } export async function getOAuthModelMappings(): Promise> { return fetchApi('/oauth-model-mappings'); } export async function saveOAuthModelMappings(provider: string, mappings: OAuthModelMappingEntry[]): Promise<{ status: string }> { return fetchApi('/oauth-model-mappings', { method: 'PATCH', body: JSON.stringify({ provider, mappings }), }); } export async function deleteOAuthModelMappings(provider: string): Promise<{ status: string }> { return fetchApi(`/oauth-model-mappings?provider=${encodeURIComponent(provider)}`, { method: 'DELETE' }); }