sshinmen's picture
fix(admin-ui): add missing lib folder with api.ts and utils.ts
a26a9de
Raw
History Blame Contribute Delete
31.5 kB
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<string, string>;
'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<string, string>;
'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<string, string>;
'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<string, string>;
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<string, string>;
}
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<string, ModelSnapshot>;
}
export interface UsageSnapshot {
total_requests: number;
success_count: number;
failure_count: number;
total_tokens: number;
apis?: Record<string, APISnapshot>;
requests_by_day?: Record<string, number>;
requests_by_hour?: Record<string, number>;
tokens_by_day?: Record<string, number>;
tokens_by_hour?: Record<string, number>;
}
// 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<T>(endpoint: string, options?: RequestInit): Promise<T> {
try {
const managementKey = getGlobalManagementKey();
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...options?.headers as Record<string, string>,
};
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<HealthStatus> {
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<Record<string, unknown>> {
return fetchApi('/config');
}
export async function getConfigYAML(): Promise<string> {
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<V1ModelsResponse> {
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<GeminiKey> }): 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<ClaudeKey> }): 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<CodexKey> }): 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<VertexCompatKey> }): 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<OpenAICompatibility> }): 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<string, OAuthModelAlias[]> }> {
return fetchApi('/oauth-model-alias');
}
export async function putOAuthModelAlias(data: Record<string, OAuthModelAlias[]>): 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<string, string[]> }> {
return fetchApi('/oauth-excluded-models');
}
export async function putOAuthExcludedModels(data: Record<string, string[]>): 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<LogsResponse> {
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<string> {
// Use custom fetch to get blob/text directly since backend might return file content
const managementKey = getGlobalManagementKey();
const headers: Record<string, string> = {};
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<string, string> = {};
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<string, string> = {};
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<string> {
const managementKey = getGlobalManagementKey();
const headers: Record<string, string> = {};
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<OAuthStartResponse> {
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<OAuthStatusResponse> {
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<IFlowCookieAuthResponse> {
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<VertexImportResponse> {
const formData = new FormData();
formData.append('file', file);
if (location) formData.append('location', location);
const managementKey = getGlobalManagementKey();
const headers: Record<string, string> = {};
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<Record<string, OAuthModelMappingEntry[]>> {
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' });
}