Failed to load usage statistics.
;
-
- const spinner = (
-
-
- {notifications.map((n) => {
- const style = getToastStyle(n.type);
- return (
-
-
-
{style.icon}
-
- {n.title ?
{n.title}
: null}
-
{n.message}
-
- {n.dismissible ? (
-
- ) : null}
-
-
- );
- })}
-
- {/* Mobile sidebar overlay */}
- {sidebarOpen && (
-
setSidebarOpen(false)}
- />
- )}
-
- {/* Sidebar - Desktop */}
-
-
-
-
- {/* Sidebar - Mobile */}
-
- setSidebarOpen(false)} />
-
-
- {/* Main content */}
-
- setSidebarOpen(true)} />
-
-
-
- );
-}
diff --git a/9router/src/shared/components/layouts/index.js b/9router/src/shared/components/layouts/index.js
deleted file mode 100644
index 4b6ac5fc..00000000
--- a/9router/src/shared/components/layouts/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// Layout Components - Export all
-export { default as DashboardLayout } from "./DashboardLayout";
-export { default as AuthLayout } from "./AuthLayout";
-
diff --git a/9router/src/shared/constants/cliTools.js b/9router/src/shared/constants/cliTools.js
deleted file mode 100644
index 69f9deeb..00000000
--- a/9router/src/shared/constants/cliTools.js
+++ /dev/null
@@ -1,249 +0,0 @@
-// MITM Tools — IDE tools intercepted via MITM proxy
-export const MITM_TOOLS = {
- antigravity: {
- id: "antigravity",
- name: "Antigravity",
- image: "/providers/antigravity.png",
- color: "#4285F4",
- description: "Google Antigravity IDE with MITM",
- configType: "mitm",
- mitmDomain: "daily-cloudcode-pa.googleapis.com",
- modelAliases: ["claude-opus-4-6-thinking", "claude-sonnet-4-6", "gemini-3-flash", "gpt-oss-120b-medium", "gemini-3-pro-high", "gemini-3-pro-low"],
- defaultModels: [
- { id: "gemini-3.1-pro-high", name: "Gemini 3.1 Pro High", alias: "gemini-3.1-pro-high" },
- { id: "gemini-3.1-pro-low", name: "Gemini 3.1 Pro Low", alias: "gemini-3.1-pro-low" },
- { id: "gemini-3-flash", name: "Gemini 3 Flash", alias: "gemini-3-flash" },
- { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", alias: "claude-sonnet-4-6" },
- { id: "claude-opus-4-6-thinking", name: "Claude Opus 4.6 Thinking", alias: "claude-opus-4-6-thinking" },
- { id: "gpt-oss-120b-medium", name: "GPT OSS 120B Medium", alias: "gpt-oss-120b-medium" },
- ],
- },
- copilot: {
- id: "copilot",
- name: "GitHub Copilot",
- image: "/providers/copilot.png",
- color: "#1F6FEB",
- description: "GitHub Copilot IDE with MITM",
- configType: "mitm",
- mitmDomain: "api.individual.githubcopilot.com",
- modelAliases: ["gpt-4o-mini", "claude-haiku-4.5", "gpt-4o", "gpt-5-mini"],
- defaultModels: [
- { id: "gpt-4o", name: "GPT-4o", alias: "gpt-4o" },
- { id: "gpt-4.1", name: "GPT-4.1", alias: "gpt-4.1" },
- { id: "claude-haiku-4.5", name: "Claude Haiku 4.5", alias: "claude-haiku-4.5" },
- ],
- },
- kiro: {
- id: "kiro",
- name: "Kiro",
- image: "/providers/kiro.png",
- color: "#FF6B00",
- description: "Kiro IDE with MITM",
- configType: "mitm",
- mitmDomain: "q.us-east-1.amazonaws.com",
- defaultModels: [
- { id: "claude-sonnet-4.5", name: "Claude Sonnet 4.5", alias: "claude-sonnet-4.5" },
- { id: "claude-sonnet-4", name: "Claude Sonnet 4", alias: "claude-sonnet-4" },
- { id: "claude-haiku-4.5", name: "Claude Haiku 4.5", alias: "claude-haiku-4.5" },
- { id: "deepseek-3.2", name: "DeepSeek 3.2", alias: "deepseek-3.2" },
- { id: "minimax-m2.1", name: "MiniMax M2.1", alias: "minimax-m2.1" },
- { id: "simple-task", name: "Qwen3 Coder Next", alias: "simple-task" },
- ],
- },
- // cursor: {
- // id: "cursor",
- // name: "Cursor",
- // image: "/providers/cursor.png",
- // color: "#000000",
- // description: "Cursor IDE with MITM",
- // configType: "mitm",
- // mitmDomain: "api2.cursor.sh",
- // defaultModels: [
- // { id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5", alias: "claude-sonnet-4-5" },
- // { id: "claude-opus-4", name: "Claude Opus 4", alias: "claude-opus-4" },
- // { id: "gpt-4o", name: "GPT-4o", alias: "gpt-4o" },
- // ],
- // },
-};
-
-// CLI Tools configuration
-export const CLI_TOOLS = {
- claude: {
- id: "claude",
- name: "Claude Code",
- icon: "terminal",
- color: "#D97757",
- description: "Anthropic Claude Code CLI",
- configType: "env",
- envVars: {
- baseUrl: "ANTHROPIC_BASE_URL",
- model: "ANTHROPIC_MODEL",
- opusModel: "ANTHROPIC_DEFAULT_OPUS_MODEL",
- sonnetModel: "ANTHROPIC_DEFAULT_SONNET_MODEL",
- haikuModel: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
- },
- modelAliases: ["default", "sonnet", "opus", "haiku", "opusplan"],
- settingsFile: "~/.claude/settings.json",
- defaultModels: [
- { id: "opus", name: "Claude Opus", alias: "opus", envKey: "ANTHROPIC_DEFAULT_OPUS_MODEL", defaultValue: "cc/claude-opus-4-6" },
- { id: "sonnet", name: "Claude Sonnet", alias: "sonnet", envKey: "ANTHROPIC_DEFAULT_SONNET_MODEL", defaultValue: "cc/claude-sonnet-4-6" },
- { id: "haiku", name: "Claude Haiku", alias: "haiku", envKey: "ANTHROPIC_DEFAULT_HAIKU_MODEL", defaultValue: "cc/claude-haiku-4-5-20251001" },
- ],
- },
- openclaw: {
- id: "openclaw",
- name: "Open Claw",
- image: "/providers/openclaw.png",
- color: "#FF6B35",
- description: "Open Claw AI Assistant",
- configType: "custom",
- },
- codex: {
- id: "codex",
- name: "OpenAI Codex CLI",
- image: "/providers/codex.png",
- color: "#10A37F",
- description: "OpenAI Codex CLI",
- configType: "custom",
- },
- opencode: {
- id: "opencode",
- name: "OpenCode",
- image: "/providers/opencode.png",
- color: "#E87040",
- description: "OpenCode AI Terminal Assistant",
- configType: "custom",
- },
- droid: {
- id: "droid",
- name: "Factory Droid",
- image: "/providers/droid.png",
- color: "#00D4FF",
- description: "Factory Droid AI Assistant",
- configType: "custom",
- },
- cursor: {
- id: "cursor",
- name: "Cursor",
- image: "/providers/cursor.png",
- color: "#000000",
- description: "Cursor AI Code Editor",
- configType: "guide",
- requiresExternalUrl: true,
- notes: [
- { type: "warning", text: "Requires Cursor Pro account to use this feature." },
- { type: "cloudCheck", text: "Cursor routes requests through its own server, so local endpoint is not supported. Please enable Tunnel or Cloud Endpoint in Settings." },
- ],
- guideSteps: [
- { step: 1, title: "Open Settings", desc: "Go to Settings → Models" },
- { step: 2, title: "Enable OpenAI API", desc: "Enable \"OpenAI API key\" option" },
- { step: 3, title: "Base URL", value: "{{baseUrl}}", copyable: true },
- { step: 4, title: "API Key", type: "apiKeySelector" },
- { step: 5, title: "Add Custom Model", desc: "Click \"View All Model\" → \"Add Custom Model\"" },
- { step: 6, title: "Select Model", type: "modelSelector" },
- ],
- },
- cline: {
- id: "cline",
- name: "Cline",
- image: "/providers/cline.png",
- color: "#00D1B2",
- description: "Cline AI Coding Assistant",
- configType: "guide",
- guideSteps: [
- { step: 1, title: "Open Settings", desc: "Go to Cline Settings panel" },
- { step: 2, title: "Select Provider", desc: "Choose API Provider → OpenAI Compatible" },
- { step: 3, title: "Base URL", value: "{{baseUrl}}/v1", copyable: true },
- { step: 4, title: "API Key", type: "apiKeySelector" },
- { step: 5, title: "Select Model", type: "modelSelector" },
- ],
- },
- kilo: {
- id: "kilo",
- name: "Kilo Code",
- image: "/providers/kilocode.png",
- color: "#FF6B6B",
- description: "Kilo Code AI Assistant",
- configType: "guide",
- guideSteps: [
- { step: 1, title: "Open Settings", desc: "Go to Kilo Code Settings panel" },
- { step: 2, title: "Select Provider", desc: "Choose API Provider → OpenAI Compatible" },
- { step: 3, title: "Base URL", value: "{{baseUrl}}/v1", copyable: true },
- { step: 4, title: "API Key", type: "apiKeySelector" },
- { step: 5, title: "Select Model", type: "modelSelector" },
- ],
- },
- roo: {
- id: "roo",
- name: "Roo",
- image: "/providers/roo.png",
- color: "#FF6B6B",
- description: "Roo AI Assistant",
- configType: "guide",
- guideSteps: [
- { step: 1, title: "Open Settings", desc: "Go to Roo Settings panel" },
- { step: 2, title: "Select Provider", desc: "Choose API Provider → Ollama" },
- { step: 3, title: "Base URL", value: "{{baseUrl}}", copyable: true },
- { step: 4, title: "API Key", type: "apiKeySelector" },
- { step: 5, title: "Select Model", type: "modelSelector" },
- ],
- },
- continue: {
- id: "continue",
- name: "Continue",
- image: "/providers/continue.png",
- color: "#7C3AED",
- description: "Continue AI Assistant",
- configType: "guide",
- guideSteps: [
- { step: 1, title: "Open Config", desc: "Open Continue configuration file" },
- { step: 2, title: "API Key", type: "apiKeySelector" },
- { step: 3, title: "Select Model", type: "modelSelector" },
- { step: 4, title: "Add Model Config", desc: "Add the following configuration to your models array:" },
- ],
- codeBlock: {
- language: "json",
- code: `{
- "apiBase": "{{baseUrl}}",
- "title": "{{model}}",
- "model": "{{model}}",
- "provider": "openai",
- "apiKey": "{{apiKey}}"
-}`,
- },
- },
- // HIDDEN: gemini-cli
- // "gemini-cli": {
- // id: "gemini-cli",
- // name: "Gemini CLI",
- // icon: "terminal",
- // color: "#4285F4",
- // description: "Google Gemini CLI",
- // configType: "env",
- // envVars: {
- // baseUrl: "GEMINI_API_BASE_URL",
- // model: "GEMINI_MODEL",
- // },
- // defaultModels: [
- // { id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", alias: "pro" },
- // { id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", alias: "flash" },
- // ],
- // },
-};
-
-// Get all provider models for mapping dropdown
-export const getProviderModelsForMapping = (providers) => {
- const result = [];
- providers.forEach(conn => {
- if (conn.isActive && (conn.testStatus === "active" || conn.testStatus === "success")) {
- result.push({
- connectionId: conn.id,
- provider: conn.provider,
- name: conn.name,
- models: conn.models || [],
- });
- }
- });
- return result;
-};
-
diff --git a/9router/src/shared/constants/colors.js b/9router/src/shared/constants/colors.js
deleted file mode 100644
index 873f3e51..00000000
--- a/9router/src/shared/constants/colors.js
+++ /dev/null
@@ -1,77 +0,0 @@
-// Claude-inspired color palette for Endpoint Proxy
-// Light theme: Warm beige/cream tones
-// Dark theme: Deep charcoal/brown tones
-
-export const COLORS = {
- // Primary - Warm Coral/Terracotta (Claude-like)
- primary: {
- DEFAULT: "#D97757",
- hover: "#C56243",
- light: "#E8A58C",
- dark: "#B0664D",
- },
-
- // Light theme backgrounds
- light: {
- bg: "#FBF9F6",
- bgAlt: "#F5F1ED",
- surface: "#FFFFFF",
- sidebar: "rgba(246, 246, 246, 0.8)",
- border: "rgba(0, 0, 0, 0.1)",
- textMain: "#383733",
- textMuted: "#75736E",
- },
-
- // Dark theme backgrounds
- dark: {
- bg: "#191918",
- bgAlt: "#1F1F1E",
- surface: "#242423",
- sidebar: "rgba(30, 30, 30, 0.8)",
- border: "rgba(255, 255, 255, 0.1)",
- textMain: "#ECEBE8",
- textMuted: "#9E9D99",
- },
-
- // Status colors
- status: {
- success: "#22C55E",
- successLight: "#DCFCE7",
- successDark: "#166534",
- warning: "#F59E0B",
- warningLight: "#FEF3C7",
- warningDark: "#92400E",
- error: "#EF4444",
- errorLight: "#FEE2E2",
- errorDark: "#991B1B",
- info: "#3B82F6",
- infoLight: "#DBEAFE",
- infoDark: "#1E40AF",
- },
-};
-
-// CSS Variables mapping for Tailwind
-export const CSS_VARIABLES = {
- light: {
- "--color-primary": COLORS.primary.DEFAULT,
- "--color-primary-hover": COLORS.primary.hover,
- "--color-bg": COLORS.light.bg,
- "--color-bg-alt": COLORS.light.bgAlt,
- "--color-surface": COLORS.light.surface,
- "--color-sidebar": COLORS.light.sidebar,
- "--color-border": COLORS.light.border,
- "--color-text-main": COLORS.light.textMain,
- "--color-text-muted": COLORS.light.textMuted,
- },
- dark: {
- "--color-primary": COLORS.primary.DEFAULT,
- "--color-primary-hover": COLORS.primary.hover,
- "--color-bg": COLORS.dark.bg,
- "--color-bg-alt": COLORS.dark.bgAlt,
- "--color-surface": COLORS.dark.surface,
- "--color-sidebar": COLORS.dark.sidebar,
- "--color-border": COLORS.dark.border,
- "--color-text-main": COLORS.dark.textMain,
- "--color-text-muted": COLORS.dark.textMuted,
- },
-};
diff --git a/9router/src/shared/constants/config.js b/9router/src/shared/constants/config.js
deleted file mode 100644
index 53a3e53e..00000000
--- a/9router/src/shared/constants/config.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import pkg from "../../../package.json" with { type: "json" };
-
-// App configuration
-export const APP_CONFIG = {
- name: "Endpoint Proxy",
- description: "AI Infrastructure Management",
- version: pkg.version,
-};
-
-// Theme configuration
-export const THEME_CONFIG = {
- storageKey: "theme",
- defaultTheme: "system", // "light" | "dark" | "system"
-};
-
-// Subscription
-export const SUBSCRIPTION_CONFIG = {
- price: 1.0,
- currency: "USD",
- interval: "month",
- planName: "Pro Plan",
-};
-
-// API endpoints
-export const API_ENDPOINTS = {
- users: "/api/users",
- providers: "/api/providers",
- payments: "/api/payments",
- auth: "/api/auth",
-};
-
-export const CONSOLE_LOG_CONFIG = {
- maxLines: 200,
- pollIntervalMs: 1000,
-};
-
-// Provider API endpoints (for display only)
-export const PROVIDER_ENDPOINTS = {
- openrouter: "https://openrouter.ai/api/v1/chat/completions",
- glm: "https://api.z.ai/api/anthropic/v1/messages",
- "glm-cn": "https://open.bigmodel.cn/api/coding/paas/v4/chat/completions",
- kimi: "https://api.kimi.com/coding/v1/messages",
- minimax: "https://api.minimax.io/anthropic/v1/messages",
- "minimax-cn": "https://api.minimaxi.com/anthropic/v1/messages",
- alicode: "https://coding.dashscope.aliyuncs.com/v1/chat/completions",
- "alicode-intl": "https://coding-intl.dashscope.aliyuncs.com/v1/chat/completions",
- openai: "https://api.openai.com/v1/chat/completions",
- anthropic: "https://api.anthropic.com/v1/messages",
- gemini: "https://generativelanguage.googleapis.com/v1beta/models",
- ollama: "https://ollama.com/api/chat",
- "ollama-local": "http://localhost:11434/api/chat",
-};
-
-// Re-export from providers.js for backward compatibility
-export {
- FREE_PROVIDERS,
- OAUTH_PROVIDERS,
- APIKEY_PROVIDERS,
- AI_PROVIDERS,
- AUTH_METHODS,
-} from "./providers.js";
-
-// Re-export from models.js for backward compatibility
-export {
- PROVIDER_MODELS,
- AI_MODELS,
-} from "./models.js";
diff --git a/9router/src/shared/constants/index.js b/9router/src/shared/constants/index.js
deleted file mode 100644
index 820029c5..00000000
--- a/9router/src/shared/constants/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-// Shared Constants - Export all
-export * from "./colors";
-export * from "./config";
-
diff --git a/9router/src/shared/constants/models.js b/9router/src/shared/constants/models.js
deleted file mode 100644
index 4ccbf062..00000000
--- a/9router/src/shared/constants/models.js
+++ /dev/null
@@ -1,35 +0,0 @@
-// Import directly from file to avoid pulling in server-side dependencies via index.js
-export {
- PROVIDER_MODELS,
- getProviderModels,
- getDefaultModel,
- isValidModel as isValidModelCore,
- findModelName,
- getModelTargetFormat,
- PROVIDER_ID_TO_ALIAS,
- getModelsByProviderId
-} from "open-sse/config/providerModels.js";
-
-import { AI_PROVIDERS, isOpenAICompatibleProvider } from "./providers.js";
-import { PROVIDER_MODELS as MODELS } from "open-sse/config/providerModels.js";
-
-// Providers that accept any model (passthrough)
-const PASSTHROUGH_PROVIDERS = new Set(
- Object.entries(AI_PROVIDERS)
- .filter(([, p]) => p.passthroughModels)
- .map(([key]) => key)
-);
-
-// Wrap isValidModel with passthrough providers
-export function isValidModel(aliasOrId, modelId) {
- if (isOpenAICompatibleProvider(aliasOrId)) return true;
- if (PASSTHROUGH_PROVIDERS.has(aliasOrId)) return true;
- const models = MODELS[aliasOrId];
- if (!models) return false;
- return models.some(m => m.id === modelId);
-}
-
-// Legacy AI_MODELS for backward compatibility
-export const AI_MODELS = Object.entries(MODELS).flatMap(([alias, models]) =>
- models.map(m => ({ provider: alias, model: m.id, name: m.name }))
-);
diff --git a/9router/src/shared/constants/pricing.js b/9router/src/shared/constants/pricing.js
deleted file mode 100644
index f96038e0..00000000
--- a/9router/src/shared/constants/pricing.js
+++ /dev/null
@@ -1,876 +0,0 @@
-// Default pricing rates for AI models
-// All rates are in dollars per million tokens ($/1M tokens)
-// Based on user-provided pricing for Antigravity models and industry standards for others
-
-export const DEFAULT_PRICING = {
- // OAuth Providers (using aliases)
-
- // Claude Code (cc)
- cc: {
- "claude-opus-4-6": {
- input: 5.00,
- output: 25.00,
- cached: 0.50,
- reasoning: 25.00,
- cache_creation: 6.25
- },
- "claude-opus-4-5-20251101": {
- input: 5.00,
- output: 25.00,
- cached: 0.50,
- reasoning: 25.00,
- cache_creation: 6.25
- },
- "claude-sonnet-4-6": {
- input: 3.00,
- output: 15.00,
- cached: 0.30,
- reasoning: 15.00,
- cache_creation: 3.75
- },
- "claude-sonnet-4-5-20250929": {
- input: 3.00,
- output: 15.00,
- cached: 0.30,
- reasoning: 15.00,
- cache_creation: 3.75
- },
- "claude-haiku-4-5-20251001": {
- input: 1.00,
- output: 5.00,
- cached: 0.10,
- reasoning: 5.00,
- cache_creation: 1.25
- }
- },
-
- // OpenAI Codex (cx)
- cx: {
- "gpt-5.3-codex": {
- input: 6.00,
- output: 24.00,
- cached: 3.00,
- reasoning: 36.00,
- cache_creation: 6.00
- },
- "gpt-5.3-codex-xhigh": {
- input: 10.00,
- output: 40.00,
- cached: 5.00,
- reasoning: 60.00,
- cache_creation: 10.00
- },
- "gpt-5.3-codex-high": {
- input: 8.00,
- output: 32.00,
- cached: 4.00,
- reasoning: 48.00,
- cache_creation: 8.00
- },
- "gpt-5.3-codex-low": {
- input: 4.00,
- output: 16.00,
- cached: 2.00,
- reasoning: 24.00,
- cache_creation: 4.00
- },
- "gpt-5.3-codex-none": {
- input: 3.00,
- output: 12.00,
- cached: 1.50,
- reasoning: 18.00,
- cache_creation: 3.00
- },
- "gpt-5.3-codex-spark": {
- input: 3.00,
- output: 12.00,
- cached: 0.30,
- reasoning: 12.00,
- cache_creation: 3.00
- },
- "gpt-5.2-codex": {
- input: 5.00,
- output: 20.00,
- cached: 2.50,
- reasoning: 30.00,
- cache_creation: 5.00
- },
- "gpt-5.2": {
- input: 5.00,
- output: 20.00,
- cached: 2.50,
- reasoning: 30.00,
- cache_creation: 5.00
- },
- "gpt-5.1-codex-max": {
- input: 8.00,
- output: 32.00,
- cached: 4.00,
- reasoning: 48.00,
- cache_creation: 8.00
- },
- "gpt-5.1-codex": {
- input: 4.00,
- output: 16.00,
- cached: 2.00,
- reasoning: 24.00,
- cache_creation: 4.00
- },
- "gpt-5.1-codex-mini": {
- input: 1.50,
- output: 6.00,
- cached: 0.75,
- reasoning: 9.00,
- cache_creation: 1.50
- },
- "gpt-5.1-codex-mini-high": {
- input: 2.00,
- output: 8.00,
- cached: 1.00,
- reasoning: 12.00,
- cache_creation: 2.00
- },
- "gpt-5.1": {
- input: 4.00,
- output: 16.00,
- cached: 2.00,
- reasoning: 24.00,
- cache_creation: 4.00
- },
- "gpt-5-codex": {
- input: 3.00,
- output: 12.00,
- cached: 1.50,
- reasoning: 18.00,
- cache_creation: 3.00
- },
- "gpt-5-codex-mini": {
- input: 1.00,
- output: 4.00,
- cached: 0.50,
- reasoning: 6.00,
- cache_creation: 1.00
- }
- },
-
- // Gemini CLI (gc)
- gc: {
- "gemini-3-flash-preview": {
- input: 0.50,
- output: 3.00,
- cached: 0.03,
- reasoning: 4.50,
- cache_creation: 0.50
- },
- "gemini-3-pro-preview": {
- input: 2.00,
- output: 12.00,
- cached: 0.25,
- reasoning: 18.00,
- cache_creation: 2.00
- },
- "gemini-2.5-pro": {
- input: 2.00,
- output: 12.00,
- cached: 0.25,
- reasoning: 18.00,
- cache_creation: 2.00
- },
- "gemini-2.5-flash": {
- input: 0.30,
- output: 2.50,
- cached: 0.03,
- reasoning: 3.75,
- cache_creation: 0.30
- },
- "gemini-2.5-flash-lite": {
- input: 0.15,
- output: 1.25,
- cached: 0.015,
- reasoning: 1.875,
- cache_creation: 0.15
- }
- },
-
- // Qwen Code (qw)
- qw: {
- "qwen3-coder-plus": {
- input: 1.00,
- output: 4.00,
- cached: 0.50,
- reasoning: 6.00,
- cache_creation: 1.00
- },
- "qwen3-coder-flash": {
- input: 0.50,
- output: 2.00,
- cached: 0.25,
- reasoning: 3.00,
- cache_creation: 0.50
- },
- "vision-model": {
- input: 1.50,
- output: 6.00,
- cached: 0.75,
- reasoning: 9.00,
- cache_creation: 1.50
- },
- "coder-model": {
- input: 1.50,
- output: 6.00,
- cached: 0.75,
- reasoning: 9.00,
- cache_creation: 1.50
- }
- },
-
- // iFlow AI (if)
- if: {
- "qwen3-coder-plus": {
- input: 1.00,
- output: 4.00,
- cached: 0.50,
- reasoning: 6.00,
- cache_creation: 1.00
- },
- "kimi-k2": {
- input: 1.00,
- output: 4.00,
- cached: 0.50,
- reasoning: 6.00,
- cache_creation: 1.00
- },
- "kimi-k2-thinking": {
- input: 1.50,
- output: 6.00,
- cached: 0.75,
- reasoning: 9.00,
- cache_creation: 1.50
- },
- "kimi-k2.5": {
- input: 1.20,
- output: 4.80,
- cached: 0.60,
- reasoning: 7.20,
- cache_creation: 1.20
- },
- "deepseek-r1": {
- input: 0.75,
- output: 3.00,
- cached: 0.375,
- reasoning: 4.50,
- cache_creation: 0.75
- },
- "deepseek-v3.2-chat": {
- input: 0.50,
- output: 2.00,
- cached: 0.25,
- reasoning: 3.00,
- cache_creation: 0.50
- },
- "deepseek-v3.2-reasoner": {
- input: 0.75,
- output: 3.00,
- cached: 0.375,
- reasoning: 4.50,
- cache_creation: 0.75
- },
- "minimax-m2.1": {
- input: 0.50,
- output: 2.00,
- cached: 0.25,
- reasoning: 3.00,
- cache_creation: 0.50
- },
- "minimax-m2.5": {
- input: 0.60,
- output: 2.40,
- cached: 0.30,
- reasoning: 3.60,
- cache_creation: 0.60
- },
- "glm-4.6": {
- input: 0.50,
- output: 2.00,
- cached: 0.25,
- reasoning: 3.00,
- cache_creation: 0.50
- },
- "glm-4.7": {
- input: 0.75,
- output: 3.00,
- cached: 0.375,
- reasoning: 4.50,
- cache_creation: 0.75
- },
- "glm-5": {
- input: 1.00,
- output: 4.00,
- cached: 0.50,
- reasoning: 6.00,
- cache_creation: 1.00
- }
- },
-
- // Antigravity (ag) - User-provided pricing
- ag: {
- "gemini-3.1-pro-low": {
- input: 2.00,
- output: 12.00,
- cached: 0.25,
- reasoning: 18.00,
- cache_creation: 2.00
- },
- "gemini-3.1-pro-high": {
- input: 4.00,
- output: 18.00,
- cached: 0.50,
- reasoning: 27.00,
- cache_creation: 4.00
- },
- "gemini-3-flash": {
- input: 0.50,
- output: 3.00,
- cached: 0.03,
- reasoning: 4.50,
- cache_creation: 0.50
- },
- "gemini-2.5-flash": {
- input: 0.30,
- output: 2.50,
- cached: 0.03,
- reasoning: 3.75,
- cache_creation: 0.30
- },
- "claude-sonnet-4-6": {
- input: 3.00,
- output: 15.00,
- cached: 0.30,
- reasoning: 22.50,
- cache_creation: 3.00
- },
- "claude-opus-4-5-thinking": {
- input: 5.00,
- output: 25.00,
- cached: 0.50,
- reasoning: 37.50,
- cache_creation: 5.00
- },
- "claude-opus-4-6-thinking": {
- input: 5.00,
- output: 25.00,
- cached: 0.50,
- reasoning: 37.50,
- cache_creation: 5.00
- },
- "gpt-oss-120b-medium": {
- input: 0.50,
- output: 2.00,
- cached: 0.25,
- reasoning: 3.00,
- cache_creation: 0.50
- }
- },
-
- // GitHub Copilot (gh)
- gh: {
- "gpt-3.5-turbo": {
- input: 0.50,
- output: 1.50,
- cached: 0.25,
- reasoning: 2.25,
- cache_creation: 0.50
- },
- "gpt-4": {
- input: 2.50,
- output: 10.00,
- cached: 1.25,
- reasoning: 15.00,
- cache_creation: 2.50
- },
- "gpt-4o": {
- input: 2.50,
- output: 10.00,
- cached: 1.25,
- reasoning: 15.00,
- cache_creation: 2.50
- },
- "gpt-4o-mini": {
- input: 0.15,
- output: 0.60,
- cached: 0.075,
- reasoning: 0.90,
- cache_creation: 0.15
- },
- "gpt-4.1": {
- input: 2.50,
- output: 10.00,
- cached: 1.25,
- reasoning: 15.00,
- cache_creation: 2.50
- },
- "gpt-5": {
- input: 3.00,
- output: 12.00,
- cached: 1.50,
- reasoning: 18.00,
- cache_creation: 3.00
- },
- "gpt-5-mini": {
- input: 0.75,
- output: 3.00,
- cached: 0.375,
- reasoning: 4.50,
- cache_creation: 0.75
- },
- "gpt-5-codex": {
- input: 3.00,
- output: 12.00,
- cached: 1.50,
- reasoning: 18.00,
- cache_creation: 3.00
- },
- "gpt-5.1": {
- input: 4.00,
- output: 16.00,
- cached: 2.00,
- reasoning: 24.00,
- cache_creation: 4.00
- },
- "gpt-5.1-codex": {
- input: 4.00,
- output: 16.00,
- cached: 2.00,
- reasoning: 24.00,
- cache_creation: 4.00
- },
- "gpt-5.1-codex-mini": {
- input: 1.50,
- output: 6.00,
- cached: 0.75,
- reasoning: 9.00,
- cache_creation: 1.50
- },
- "gpt-5.1-codex-max": {
- input: 8.00,
- output: 32.00,
- cached: 4.00,
- reasoning: 48.00,
- cache_creation: 8.00
- },
- "gpt-5.2": {
- input: 5.00,
- output: 20.00,
- cached: 2.50,
- reasoning: 30.00,
- cache_creation: 5.00
- },
- "gpt-5.2-codex": {
- input: 5.00,
- output: 20.00,
- cached: 2.50,
- reasoning: 30.00,
- cache_creation: 5.00
- },
- "gpt-5.3-codex": {
- input: 1.75,
- output: 14.00,
- cached: 0.175,
- reasoning: 14.00,
- cache_creation: 1.75
- },
- "claude-haiku-4.5": {
- input: 0.50,
- output: 2.50,
- cached: 0.05,
- reasoning: 3.75,
- cache_creation: 0.50
- },
- "claude-opus-4.1": {
- input: 5.00,
- output: 25.00,
- cached: 0.50,
- reasoning: 37.50,
- cache_creation: 5.00
- },
- "claude-opus-4.5": {
- input: 5.00,
- output: 25.00,
- cached: 0.50,
- reasoning: 37.50,
- cache_creation: 5.00
- },
- "claude-sonnet-4": {
- input: 3.00,
- output: 15.00,
- cached: 0.30,
- reasoning: 22.50,
- cache_creation: 3.00
- },
- "claude-sonnet-4.5": {
- input: 3.00,
- output: 15.00,
- cached: 0.30,
- reasoning: 22.50,
- cache_creation: 3.00
- },
- "claude-sonnet-4.6": {
- input: 3.00,
- output: 15.00,
- cached: 0.30,
- reasoning: 22.50,
- cache_creation: 3.00
- },
- "claude-opus-4.6": {
- input: 5.00,
- output: 25.00,
- cached: 0.50,
- reasoning: 37.50,
- cache_creation: 5.00
- },
- "gemini-2.5-pro": {
- input: 2.00,
- output: 12.00,
- cached: 0.25,
- reasoning: 18.00,
- cache_creation: 2.00
- },
- "gemini-3-flash-preview": {
- input: 0.50,
- output: 3.00,
- cached: 0.03,
- reasoning: 4.50,
- cache_creation: 0.50
- },
- "gemini-3-pro-preview": {
- input: 2.00,
- output: 12.00,
- cached: 0.25,
- reasoning: 18.00,
- cache_creation: 2.00
- },
- "grok-code-fast-1": {
- input: 0.50,
- output: 2.00,
- cached: 0.25,
- reasoning: 3.00,
- cache_creation: 0.50
- },
- "oswe-vscode-prime": {
- input: 1.00,
- output: 4.00,
- cached: 0.50,
- reasoning: 6.00,
- cache_creation: 1.00
- }
- },
-
- // Kiro AI (kr) - AWS CodeWhisperer
- kr: {
- "claude-sonnet-4.5": {
- input: 3.00,
- output: 15.00,
- cached: 0.30,
- reasoning: 22.50,
- cache_creation: 3.00
- },
- "claude-haiku-4.5": {
- input: 0.50,
- output: 2.50,
- cached: 0.05,
- reasoning: 3.75,
- cache_creation: 0.50
- }
- },
-
- // API Key Providers (alias = id)
-
- // OpenAI
- openai: {
- "gpt-4o": {
- input: 2.50,
- output: 10.00,
- cached: 1.25,
- reasoning: 15.00,
- cache_creation: 2.50
- },
- "gpt-4o-mini": {
- input: 0.15,
- output: 0.60,
- cached: 0.075,
- reasoning: 0.90,
- cache_creation: 0.15
- },
- "gpt-4-turbo": {
- input: 10.00,
- output: 30.00,
- cached: 5.00,
- reasoning: 45.00,
- cache_creation: 10.00
- },
- "o1": {
- input: 15.00,
- output: 60.00,
- cached: 7.50,
- reasoning: 90.00,
- cache_creation: 15.00
- },
- "o1-mini": {
- input: 3.00,
- output: 12.00,
- cached: 1.50,
- reasoning: 18.00,
- cache_creation: 3.00
- }
- },
-
- // Anthropic
- anthropic: {
- "claude-sonnet-4-20250514": {
- input: 3.00,
- output: 15.00,
- cached: 1.50,
- reasoning: 15.00,
- cache_creation: 3.00
- },
- "claude-opus-4-20250514": {
- input: 15.00,
- output: 25.00,
- cached: 7.50,
- reasoning: 112.50,
- cache_creation: 15.00
- },
- "claude-3-5-sonnet-20241022": {
- input: 3.00,
- output: 15.00,
- cached: 1.50,
- reasoning: 15.00,
- cache_creation: 3.00
- }
- },
-
- // Gemini
- gemini: {
- "gemini-3-pro-preview": {
- input: 2.00,
- output: 12.00,
- cached: 0.25,
- reasoning: 18.00,
- cache_creation: 2.00
- },
- "gemini-2.5-pro": {
- input: 2.00,
- output: 12.00,
- cached: 0.25,
- reasoning: 18.00,
- cache_creation: 2.00
- },
- "gemini-2.5-flash": {
- input: 0.30,
- output: 2.50,
- cached: 0.03,
- reasoning: 3.75,
- cache_creation: 0.30
- },
- "gemini-2.5-flash-lite": {
- input: 0.15,
- output: 1.25,
- cached: 0.015,
- reasoning: 1.875,
- cache_creation: 0.15
- }
- },
-
- // OpenRouter
- openrouter: {
- "auto": {
- input: 2.00,
- output: 8.00,
- cached: 1.00,
- reasoning: 12.00,
- cache_creation: 2.00
- }
- },
-
- // GLM
- glm: {
- "glm-4.7": {
- input: 0.75,
- output: 3.00,
- cached: 0.375,
- reasoning: 4.50,
- cache_creation: 0.75
- },
- "glm-4.6": {
- input: 0.50,
- output: 2.00,
- cached: 0.25,
- reasoning: 3.00,
- cache_creation: 0.50
- },
- "glm-4.6v": {
- input: 0.75,
- output: 3.00,
- cached: 0.375,
- reasoning: 4.50,
- cache_creation: 0.75
- }
- },
-
- // Kimi
- kimi: {
- "kimi-k2.5": {
- input: 1.20,
- output: 4.80,
- cached: 0.60,
- reasoning: 7.20,
- cache_creation: 1.20
- },
- "kimi-k2.5-thinking": {
- input: 1.80,
- output: 7.20,
- cached: 0.90,
- reasoning: 10.80,
- cache_creation: 1.80
- },
- "kimi-latest": {
- input: 1.00,
- output: 4.00,
- cached: 0.50,
- reasoning: 6.00,
- cache_creation: 1.00
- }
- },
-
- // MiniMax
- minimax: {
- "MiniMax-M2.7": {
- input: 0.50,
- output: 2.00,
- cached: 0.25,
- reasoning: 3.00,
- cache_creation: 0.50
- },
- "MiniMax-M2.5": {
- input: 0.50,
- output: 2.00,
- cached: 0.25,
- reasoning: 3.00,
- cache_creation: 0.50
- },
- "MiniMax-M2.1": {
- input: 0.50,
- output: 2.00,
- cached: 0.25,
- reasoning: 3.00,
- cache_creation: 0.50
- }
- },
-
- // DeepSeek (official API pricing: https://api-docs.deepseek.com/quick_start/pricing)
- deepseek: {
- "deepseek-chat": {
- input: 0.28,
- output: 0.42,
- cached: 0.028,
- reasoning: 0.42,
- cache_creation: 0.28
- },
- "deepseek-reasoner": {
- input: 0.28,
- output: 0.42,
- cached: 0.028,
- reasoning: 0.42,
- cache_creation: 0.28
- }
- }
-};
-
-/**
- * Provider-agnostic fallback pricing, keyed by model base name.
- * Used when provider-specific lookup fails (e.g. openrouter, cu, fireworks use prefixed model IDs).
- * Strip prefix before lookup: "deepseek/deepseek-chat" → "deepseek-chat"
- */
-export const MODEL_PRICING = {
- // DeepSeek
- "deepseek-chat": { input: 0.28, output: 0.42, cached: 0.028, reasoning: 0.42, cache_creation: 0.28 },
- "deepseek-reasoner": { input: 0.28, output: 0.42, cached: 0.028, reasoning: 0.42, cache_creation: 0.28 },
-};
-
-/**
- * Get pricing for a specific provider and model
- * @param {string} provider - Provider ID (e.g., "openai", "cc", "gc")
- * @param {string} model - Model ID
- * @returns {object|null} Pricing object or null if not found
- */
-export function getPricingForModel(provider, model) {
- if (!provider || !model) return null;
-
- const providerPricing = DEFAULT_PRICING[provider];
- if (!providerPricing) return null;
-
- return providerPricing[model] || null;
-}
-
-/**
- * Get all pricing data
- * @returns {object} All default pricing
- */
-export function getDefaultPricing() {
- return DEFAULT_PRICING;
-}
-
-/**
- * Format cost for display
- * @param {number} cost - Cost in dollars
- * @returns {string} Formatted cost string
- */
-export function formatCost(cost) {
- if (cost === null || cost === undefined || isNaN(cost)) return "$0.00";
- return `$${cost.toFixed(2)}`;
-}
-
-/**
- * Calculate cost from tokens and pricing
- * @param {object} tokens - Token counts
- * @param {object} pricing - Pricing object
- * @returns {number} Cost in dollars
- */
-export function calculateCostFromTokens(tokens, pricing) {
- if (!tokens || !pricing) return 0;
-
- let cost = 0;
-
- // Input tokens (non-cached)
- const inputTokens = tokens.prompt_tokens || tokens.input_tokens || 0;
- const cachedTokens = tokens.cached_tokens || tokens.cache_read_input_tokens || 0;
- const nonCachedInput = Math.max(0, inputTokens - cachedTokens);
-
- cost += (nonCachedInput * (pricing.input / 1000000));
-
- // Cached tokens
- if (cachedTokens > 0) {
- const cachedRate = pricing.cached || pricing.input; // Fallback to input rate
- cost += (cachedTokens * (cachedRate / 1000000));
- }
-
- // Output tokens
- const outputTokens = tokens.completion_tokens || tokens.output_tokens || 0;
- cost += (outputTokens * (pricing.output / 1000000));
-
- // Reasoning tokens
- const reasoningTokens = tokens.reasoning_tokens || 0;
- if (reasoningTokens > 0) {
- const reasoningRate = pricing.reasoning || pricing.output; // Fallback to output rate
- cost += (reasoningTokens * (reasoningRate / 1000000));
- }
-
- // Cache creation tokens
- const cacheCreationTokens = tokens.cache_creation_input_tokens || 0;
- if (cacheCreationTokens > 0) {
- const cacheCreationRate = pricing.cache_creation || pricing.input; // Fallback to input rate
- cost += (cacheCreationTokens * (cacheCreationRate / 1000000));
- }
-
- return cost;
-}
diff --git a/9router/src/shared/constants/providers.js b/9router/src/shared/constants/providers.js
deleted file mode 100644
index f1dc0eef..00000000
--- a/9router/src/shared/constants/providers.js
+++ /dev/null
@@ -1,120 +0,0 @@
-// Provider definitions
-
-// Free Providers
-export const FREE_PROVIDERS = {
- iflow: { id: "iflow", alias: "if", name: "iFlow AI", icon: "water_drop", color: "#6366F1" },
- qwen: { id: "qwen", alias: "qw", name: "Qwen Code", icon: "psychology", color: "#10B981" },
- "gemini-cli": { id: "gemini-cli", alias: "gc", name: "Gemini CLI", icon: "terminal", color: "#4285F4" },
- kiro: { id: "kiro", alias: "kr", name: "Kiro AI", icon: "psychology_alt", color: "#FF6B35" },
-};
-
-// OAuth Providers
-export const OAUTH_PROVIDERS = {
- claude: { id: "claude", alias: "cc", name: "Claude Code", icon: "smart_toy", color: "#D97757" },
- antigravity: { id: "antigravity", alias: "ag", name: "Antigravity", icon: "rocket_launch", color: "#F59E0B" },
- codex: { id: "codex", alias: "cx", name: "OpenAI Codex", icon: "code", color: "#3B82F6" },
- github: { id: "github", alias: "gh", name: "GitHub Copilot", icon: "code", color: "#333333" },
- cursor: { id: "cursor", alias: "cu", name: "Cursor IDE", icon: "edit_note", color: "#00D4AA" },
- // "kimi-coding": { id: "kimi-coding", alias: "kmc", name: "Kimi Coding", icon: "psychology", color: "#1E40AF", textIcon: "KC" },
- kilocode: { id: "kilocode", alias: "kc", name: "Kilo Code", icon: "code", color: "#FF6B35", textIcon: "KC" },
- cline: { id: "cline", alias: "cl", name: "Cline", icon: "smart_toy", color: "#5B9BD5", textIcon: "CL" },
-};
-
-export const APIKEY_PROVIDERS = {
- openrouter: { id: "openrouter", alias: "openrouter", name: "OpenRouter", icon: "router", color: "#F97316", textIcon: "OR", passthroughModels: true, website: "https://openrouter.ai" },
- glm: { id: "glm", alias: "glm", name: "GLM Coding", icon: "code", color: "#2563EB", textIcon: "GL", website: "https://open.bigmodel.cn" },
- "glm-cn": { id: "glm-cn", alias: "glm-cn", name: "GLM (China)", icon: "code", color: "#DC2626", textIcon: "GC", website: "https://open.bigmodel.cn" },
- kimi: { id: "kimi", alias: "kimi", name: "Kimi", icon: "psychology", color: "#1E3A8A", textIcon: "KM", website: "https://kimi.moonshot.cn" },
- minimax: { id: "minimax", alias: "minimax", name: "Minimax Coding", icon: "memory", color: "#7C3AED", textIcon: "MM", website: "https://www.minimaxi.com" },
- "minimax-cn": { id: "minimax-cn", alias: "minimax-cn", name: "Minimax (China)", icon: "memory", color: "#DC2626", textIcon: "MC", website: "https://www.minimaxi.com" },
- alicode: { id: "alicode", alias: "alicode", name: "Alibaba", icon: "cloud", color: "#FF6A00", textIcon: "ALi" },
- "alicode-intl": { id: "alicode-intl", alias: "alicode-intl", name: "Alibaba Intl", icon: "cloud", color: "#FF6A00", textIcon: "ALi" },
- openai: { id: "openai", alias: "openai", name: "OpenAI", icon: "auto_awesome", color: "#10A37F", textIcon: "OA", website: "https://platform.openai.com" },
- anthropic: { id: "anthropic", alias: "anthropic", name: "Anthropic", icon: "smart_toy", color: "#D97757", textIcon: "AN", website: "https://console.anthropic.com" },
- gemini: { id: "gemini", alias: "gemini", name: "Gemini", icon: "diamond", color: "#4285F4", textIcon: "GE", website: "https://ai.google.dev" },
- deepseek: { id: "deepseek", alias: "ds", name: "DeepSeek", icon: "bolt", color: "#4D6BFE", textIcon: "DS", website: "https://deepseek.com" },
- groq: { id: "groq", alias: "groq", name: "Groq", icon: "speed", color: "#F55036", textIcon: "GQ", website: "https://groq.com" },
- xai: { id: "xai", alias: "xai", name: "xAI (Grok)", icon: "auto_awesome", color: "#1DA1F2", textIcon: "XA", website: "https://x.ai" },
- mistral: { id: "mistral", alias: "mistral", name: "Mistral", icon: "air", color: "#FF7000", textIcon: "MI", website: "https://mistral.ai" },
- perplexity: { id: "perplexity", alias: "pplx", name: "Perplexity", icon: "search", color: "#20808D", textIcon: "PP", website: "https://www.perplexity.ai" },
- together: { id: "together", alias: "together", name: "Together AI", icon: "group_work", color: "#0F6FFF", textIcon: "TG", website: "https://www.together.ai" },
- fireworks: { id: "fireworks", alias: "fireworks", name: "Fireworks AI", icon: "local_fire_department", color: "#7B2EF2", textIcon: "FW", website: "https://fireworks.ai" },
- cerebras: { id: "cerebras", alias: "cerebras", name: "Cerebras", icon: "memory", color: "#FF4F00", textIcon: "CB", website: "https://www.cerebras.ai" },
- cohere: { id: "cohere", alias: "cohere", name: "Cohere", icon: "hub", color: "#39594D", textIcon: "CO", website: "https://cohere.com" },
- nvidia: { id: "nvidia", alias: "nvidia", name: "NVIDIA NIM", icon: "developer_board", color: "#76B900", textIcon: "NV", website: "https://developer.nvidia.com/nim" },
- nebius: { id: "nebius", alias: "nebius", name: "Nebius AI", icon: "cloud", color: "#6C5CE7", textIcon: "NB", website: "https://nebius.com" },
- siliconflow: { id: "siliconflow", alias: "siliconflow", name: "SiliconFlow", icon: "cloud_queue", color: "#5B6EF5", textIcon: "SF", website: "https://cloud.siliconflow.com" },
- hyperbolic: { id: "hyperbolic", alias: "hyp", name: "Hyperbolic", icon: "bolt", color: "#00D4FF", textIcon: "HY", website: "https://hyperbolic.xyz" },
- deepgram: { id: "deepgram", alias: "dg", name: "Deepgram", icon: "mic", color: "#13EF93", textIcon: "DG", website: "https://deepgram.com" },
- assemblyai: { id: "assemblyai", alias: "aai", name: "AssemblyAI", icon: "record_voice_over", color: "#0062FF", textIcon: "AA", website: "https://assemblyai.com" },
- nanobanana: { id: "nanobanana", alias: "nb", name: "NanoBanana", icon: "image", color: "#FFD700", textIcon: "NB", website: "https://nanobananaapi.ai" },
- chutes: { id: "chutes", alias: "ch", name: "Chutes AI", icon: "water_drop", color: "#ffffffff", textIcon: "CH", website: "https://chutes.ai" },
- ollama: { id: "ollama", alias: "ollama", name: "Ollama Cloud", icon: "cloud", color: "#ffffffff", textIcon: "OL", website: "https://ollama.com" },
- "ollama-local": { id: "ollama-local", alias: "ollama-local", name: "Ollama Local", icon: "cloud", color: "#ffffffff", textIcon: "OL", website: "https://ollama.com" },
- vertex: { id: "vertex", alias: "vx", name: "Vertex AI", icon: "cloud", color: "#4285F4", textIcon: "VX", website: "https://cloud.google.com/vertex-ai" },
- "vertex-partner": { id: "vertex-partner", alias: "vxp", name: "Vertex Partner", icon: "cloud", color: "#34A853", textIcon: "VP", website: "https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-partner-models" },
-};
-
-export const OPENAI_COMPATIBLE_PREFIX = "openai-compatible-";
-export const ANTHROPIC_COMPATIBLE_PREFIX = "anthropic-compatible-";
-
-export function isOpenAICompatibleProvider(providerId) {
- return typeof providerId === "string" && providerId.startsWith(OPENAI_COMPATIBLE_PREFIX);
-}
-
-export function isAnthropicCompatibleProvider(providerId) {
- return typeof providerId === "string" && providerId.startsWith(ANTHROPIC_COMPATIBLE_PREFIX);
-}
-
-// All providers (combined)
-export const AI_PROVIDERS = { ...FREE_PROVIDERS, ...OAUTH_PROVIDERS, ...APIKEY_PROVIDERS };
-
-// Auth methods
-export const AUTH_METHODS = {
- oauth: { id: "oauth", name: "OAuth", icon: "lock" },
- apikey: { id: "apikey", name: "API Key", icon: "key" },
-};
-
-// Helper: Get provider by alias
-export function getProviderByAlias(alias) {
- for (const provider of Object.values(AI_PROVIDERS)) {
- if (provider.alias === alias || provider.id === alias) {
- return provider;
- }
- }
- return null;
-}
-
-// Helper: Get provider ID from alias
-export function resolveProviderId(aliasOrId) {
- const provider = getProviderByAlias(aliasOrId);
- return provider?.id || aliasOrId;
-}
-
-// Helper: Get alias from provider ID
-export function getProviderAlias(providerId) {
- const provider = AI_PROVIDERS[providerId];
- return provider?.alias || providerId;
-}
-
-// Alias to ID mapping (for quick lookup)
-export const ALIAS_TO_ID = Object.values(AI_PROVIDERS).reduce((acc, p) => {
- acc[p.alias] = p.id;
- return acc;
-}, {});
-
-// ID to Alias mapping
-export const ID_TO_ALIAS = Object.values(AI_PROVIDERS).reduce((acc, p) => {
- acc[p.id] = p.alias;
- return acc;
-}, {});
-
-// Providers that support usage/quota API
-export const USAGE_SUPPORTED_PROVIDERS = [
- "claude",
- "antigravity",
- "kiro",
- "github",
- "codex",
- "kimi-coding",
-];
diff --git a/9router/src/shared/hooks/index.js b/9router/src/shared/hooks/index.js
deleted file mode 100644
index 68c81c2d..00000000
--- a/9router/src/shared/hooks/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// Shared Hooks - Export all
-export { useTheme } from "./useTheme";
diff --git a/9router/src/shared/hooks/useCopyToClipboard.js b/9router/src/shared/hooks/useCopyToClipboard.js
deleted file mode 100644
index 558aad89..00000000
--- a/9router/src/shared/hooks/useCopyToClipboard.js
+++ /dev/null
@@ -1,29 +0,0 @@
-"use client";
-
-import { useState, useCallback, useRef } from "react";
-
-/**
- * Hook for copy to clipboard with feedback
- * @param {number} resetDelay - Time in ms before resetting copied state (default: 2000)
- * @returns {{ copied: string|null, copy: (text: string, id?: string) => void }}
- */
-export function useCopyToClipboard(resetDelay = 2000) {
- const [copied, setCopied] = useState(null);
- const timeoutRef = useRef(null);
-
- const copy = useCallback((text, id = "default") => {
- navigator.clipboard.writeText(text);
- setCopied(id);
-
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current);
- }
-
- timeoutRef.current = setTimeout(() => {
- setCopied(null);
- }, resetDelay);
- }, [resetDelay]);
-
- return { copied, copy };
-}
-
diff --git a/9router/src/shared/hooks/useTheme.js b/9router/src/shared/hooks/useTheme.js
deleted file mode 100644
index 45dae992..00000000
--- a/9router/src/shared/hooks/useTheme.js
+++ /dev/null
@@ -1,60 +0,0 @@
-"use client";
-
-import { useEffect, useState, useSyncExternalStore } from "react";
-import useThemeStore from "@/store/themeStore";
-
-// Subscribe to system theme changes
-function subscribeToSystemTheme(callback) {
- if (typeof window === "undefined") return () => {};
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
- mediaQuery.addEventListener("change", callback);
- return () => mediaQuery.removeEventListener("change", callback);
-}
-
-// Get current system theme preference
-function getSystemThemeSnapshot() {
- if (typeof window === "undefined") return false;
- return window.matchMedia("(prefers-color-scheme: dark)").matches;
-}
-
-// Server snapshot always returns false
-function getServerSnapshot() {
- return false;
-}
-
-export function useTheme() {
- const { theme, setTheme, toggleTheme, initTheme } = useThemeStore();
-
- // Use useSyncExternalStore to safely subscribe to system theme
- const systemPrefersDark = useSyncExternalStore(
- subscribeToSystemTheme,
- getSystemThemeSnapshot,
- getServerSnapshot
- );
-
- useEffect(() => {
- initTheme();
- }, [initTheme]);
-
- // Listen for system theme changes when theme is "system"
- useEffect(() => {
- if (theme !== "system") return;
-
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
- const handleChange = () => initTheme();
-
- mediaQuery.addEventListener("change", handleChange);
- return () => mediaQuery.removeEventListener("change", handleChange);
- }, [theme, initTheme]);
-
- // Compute isDark from current state (no effect needed)
- const isDark = theme === "dark" || (theme === "system" && systemPrefersDark);
-
- return {
- theme,
- setTheme,
- toggleTheme,
- isDark,
- };
-}
-
diff --git a/9router/src/shared/services/cloudSyncScheduler.js b/9router/src/shared/services/cloudSyncScheduler.js
deleted file mode 100644
index 44de2489..00000000
--- a/9router/src/shared/services/cloudSyncScheduler.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import { getConsistentMachineId } from "@/shared/utils/machineId";
-import { isCloudEnabled } from "@/lib/localDb";
-
-const INTERNAL_BASE_URL =
- process.env.BASE_URL ||
- process.env.NEXT_PUBLIC_BASE_URL ||
- "http://localhost:20128";
-
-/**
- * Cloud sync scheduler
- */
-export class CloudSyncScheduler {
- constructor(machineId = null, intervalMinutes = 15) {
- this.machineId = machineId;
- this.intervalMinutes = intervalMinutes;
- this.intervalId = null;
- }
-
- /**
- * Initialize machine ID if not provided
- */
- async initializeMachineId() {
- if (!this.machineId) {
- this.machineId = await getConsistentMachineId();
- }
- }
-
- /**
- * Start periodic sync (delays first sync to allow server to be ready)
- */
- async start() {
- if (this.intervalId) {
- return;
- }
-
- await this.initializeMachineId();
-
- // Delay first sync by 30 seconds to ensure server is ready
- setTimeout(() => {
- this.syncWithRetry().catch(() => {});
- }, 30000);
-
- // Then sync periodically
- this.intervalId = setInterval(() => {
- this.syncWithRetry().catch(() => {});
- }, this.intervalMinutes * 60 * 1000);
- }
-
- /**
- * Stop periodic sync
- */
- stop() {
- if (this.intervalId) {
- clearInterval(this.intervalId);
- this.intervalId = null;
- }
- }
-
- /**
- * Sync with retry logic (exponential backoff)
- */
- async syncWithRetry(maxRetries = 1) {
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
- try {
- const result = await this.sync();
- return result;
- } catch (error) {
- if (attempt === maxRetries) {
- return null;
- }
-
- const delay = Math.min(1000 * Math.pow(2, attempt), 10000); // Max 10s
- await new Promise(resolve => setTimeout(resolve, delay));
- }
- }
- }
-
- /**
- * Perform sync via internal API route (handles token update to db.json)
- */
- async sync() {
- // Check if cloud is enabled
- const enabled = await isCloudEnabled();
- if (!enabled) {
- return null;
- }
-
- await this.initializeMachineId();
-
- // Call internal API route which handles both sync and token update
- const response = await fetch(`${INTERNAL_BASE_URL}/api/sync/cloud`, {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ machineId: this.machineId, action: "sync" })
- });
-
- if (!response.ok) {
- const errorData = await response.json().catch(() => ({}));
- throw new Error(errorData.error || "Sync failed");
- }
-
- const result = await response.json();
- return result;
- }
-
- /**
- * Check if scheduler is running
- */
- isRunning() {
- return this.intervalId !== null;
- }
-}
-
-// Export a singleton instance if needed
-let cloudSyncScheduler = null;
-
-export async function getCloudSyncScheduler(machineId = null, intervalMinutes = 15) {
- if (!cloudSyncScheduler) {
- cloudSyncScheduler = new CloudSyncScheduler(machineId, intervalMinutes);
- }
- return cloudSyncScheduler;
-}
diff --git a/9router/src/shared/services/initializeApp.js b/9router/src/shared/services/initializeApp.js
deleted file mode 100644
index 42c665fa..00000000
--- a/9router/src/shared/services/initializeApp.js
+++ /dev/null
@@ -1,228 +0,0 @@
-import { cleanupProviderConnections, getSettings, updateSettings, getApiKeys } from "@/lib/localDb";
-import { enableTunnel, isTunnelManuallyDisabled, isTunnelReconnecting } from "@/lib/tunnel/tunnelManager";
-import { killCloudflared, isCloudflaredRunning, ensureCloudflared } from "@/lib/tunnel/cloudflared";
-import { getMitmStatus, startMitm, loadEncryptedPassword, initDbHooks } from "@/mitm/manager";
-import { fileURLToPath } from "url";
-import { dirname, join } from "path";
-import { existsSync } from "fs";
-
-import os from "os";
-
-// Inject correct paths and DB hooks into manager.js (CJS) from ESM context.
-// Must run before any MITM function is called.
-(function bootstrapMitm() {
- // 1. Resolve server.js path from real ESM __filename (not bundled path)
- if (!process.env.MITM_SERVER_PATH) {
- try {
- const thisFile = fileURLToPath(import.meta.url);
- const appSrc = dirname(dirname(thisFile)); // src/
- const candidate = join(appSrc, "mitm", "server.js");
- if (existsSync(candidate)) {
- process.env.MITM_SERVER_PATH = candidate;
- }
- } catch { /* ignore */ }
- }
-
- // 2. Inject DB functions so manager.js (CJS) can save/load settings
- // without dynamic import issues inside webpack bundles
- try {
- initDbHooks(getSettings, updateSettings);
- } catch { /* ignore */ }
-})();
-
-// Multiple modules register SIGINT/SIGTERM handlers legitimately
-process.setMaxListeners(20);
-
-// Use global to survive Next.js hot reload — prevents duplicate intervals
-const g = global.__appSingleton ??= {
- signalHandlersRegistered: false,
- watchdogInterval: null,
- networkMonitorInterval: null,
- lastNetworkFingerprint: null,
- lastWatchdogTick: Date.now(),
- lastTunnelRestartAt: 0,
- tunnelRestartInProgress: false,
- mitmStartInProgress: false,
-};
-
-const WATCHDOG_INTERVAL_MS = 60000;
-const NETWORK_CHECK_INTERVAL_MS = 5000;
-const NETWORK_RESTART_COOLDOWN_MS = 30000;
-
-/**
- * Initialize app on startup
- * - Cleanup stale data
- * - Auto-reconnect tunnel if previously enabled
- * - Register shutdown handler to kill cloudflared
- * - Start watchdog to recover tunnel after sleep/wake
- */
-export async function initializeApp() {
- try {
- await cleanupProviderConnections();
-
- // Auto-reconnect tunnel if it was enabled before restart
- const settings = await getSettings();
- if (settings.tunnelEnabled && !isCloudflaredRunning()) {
- console.log("[InitApp] Tunnel was enabled, auto-reconnecting...");
- try {
- await enableTunnel();
- console.log("[InitApp] Tunnel reconnected");
- } catch (error) {
- console.log("[InitApp] Tunnel reconnect failed:", error.message);
- }
- }
-
- // Kill cloudflared on process exit (register once only)
- if (!g.signalHandlersRegistered) {
- const cleanup = () => {
- killCloudflared();
- process.exit();
- };
- process.on("SIGINT", cleanup);
- process.on("SIGTERM", cleanup);
- g.signalHandlersRegistered = true;
- }
-
- // Pre-download cloudflared binary in background
- ensureCloudflared().catch(() => {});
-
- // Watchdog: recover tunnel after process crash
- startWatchdog();
-
- // Network monitor: detect sleep/wake + network changes → restart tunnel
- startNetworkMonitor();
-
- // Auto-start MITM if it was enabled before restart
- autoStartMitm();
- } catch (error) {
- console.error("[InitApp] Error:", error);
- }
-}
-
-/** Auto-start MITM if it was enabled before restart */
-async function autoStartMitm() {
- if (g.mitmStartInProgress) return;
- g.mitmStartInProgress = true;
- try {
- const settings = await getSettings();
- if (!settings.mitmEnabled) return;
-
- const mitmStatus = await getMitmStatus();
- if (mitmStatus.running) return;
-
- const password = await loadEncryptedPassword();
- if (!password && process.platform !== "win32") {
- console.log("[InitApp] MITM was enabled but no saved password found, skipping auto-start");
- return;
- }
-
- // Need an active API key
- const keys = await getApiKeys();
- const activeKey = keys.find(k => k.isActive !== false);
-
- console.log("[InitApp] MITM was enabled, auto-starting...");
- await startMitm(activeKey?.key || "sk_9router", password);
- console.log("[InitApp] MITM auto-started");
- } catch (err) {
- console.log("[InitApp] MITM auto-start failed:", err.message);
- } finally {
- g.mitmStartInProgress = false;
- }
-}
-
-/** Periodically check tunnel process health and reconnect if crashed */
-function startWatchdog() {
- if (g.watchdogInterval) return;
- g.watchdogInterval = setInterval(async () => {
- try {
- if (isTunnelManuallyDisabled()) return;
- if (isTunnelReconnecting()) return;
- if (g.tunnelRestartInProgress) return;
- const settings = await getSettings();
- if (!settings.tunnelEnabled) return;
- if (isCloudflaredRunning()) return;
- console.log("[Watchdog] Tunnel process is down, attempting recovery...");
- g.tunnelRestartInProgress = true;
- try {
- await enableTunnel();
- console.log("[Watchdog] Tunnel recovered");
- } finally {
- g.tunnelRestartInProgress = false;
- }
- } catch (err) {
- console.log("[Watchdog] Recovery failed:", err.message);
- }
- }, WATCHDOG_INTERVAL_MS);
-
- if (g.watchdogInterval.unref) g.watchdogInterval.unref();
-}
-
-/** Get network fingerprint from active interfaces (IPv4 only) */
-function getNetworkFingerprint() {
- const interfaces = os.networkInterfaces();
- const active = [];
- for (const [name, addrs] of Object.entries(interfaces)) {
- if (!addrs) continue;
- for (const addr of addrs) {
- if (!addr.internal && addr.family === "IPv4") {
- active.push(`${name}:${addr.address}`);
- }
- }
- }
- return active.sort().join("|");
-}
-
-/** Monitor network changes + sleep/wake → kill and reconnect tunnel */
-function startNetworkMonitor() {
- if (g.networkMonitorInterval) return;
-
- g.lastNetworkFingerprint = getNetworkFingerprint();
- g.lastWatchdogTick = Date.now();
-
- g.networkMonitorInterval = setInterval(async () => {
- try {
- if (isTunnelManuallyDisabled()) return;
- const settings = await getSettings();
- if (!settings.tunnelEnabled) return;
-
- const now = Date.now();
- const elapsed = now - g.lastWatchdogTick;
- g.lastWatchdogTick = now;
-
- const currentFingerprint = getNetworkFingerprint();
- const networkChanged = currentFingerprint !== g.lastNetworkFingerprint;
- const wasSleep = elapsed > NETWORK_CHECK_INTERVAL_MS * 3;
-
- if (networkChanged) g.lastNetworkFingerprint = currentFingerprint;
-
- if (!networkChanged && !wasSleep) return;
-
- // Skip if restart already in progress or restarted recently
- if (g.tunnelRestartInProgress) return;
- if (isTunnelReconnecting()) return;
- if (now - g.lastTunnelRestartAt < NETWORK_RESTART_COOLDOWN_MS) return;
-
- const reason = wasSleep && networkChanged ? "sleep/wake + network change"
- : wasSleep ? "sleep/wake" : "network change";
- console.log(`[NetworkMonitor] ${reason} detected, restarting tunnel...`);
-
- g.tunnelRestartInProgress = true;
- g.lastTunnelRestartAt = now;
- try {
- killCloudflared();
- await new Promise(r => setTimeout(r, 2000));
- await enableTunnel();
- console.log("[NetworkMonitor] Tunnel restarted");
- g.lastNetworkFingerprint = getNetworkFingerprint();
- } finally {
- g.tunnelRestartInProgress = false;
- }
- } catch (err) {
- console.log("[NetworkMonitor] Tunnel restart failed:", err.message);
- }
- }, NETWORK_CHECK_INTERVAL_MS);
-
- if (g.networkMonitorInterval.unref) g.networkMonitorInterval.unref();
-}
-
-export default initializeApp;
diff --git a/9router/src/shared/services/initializeCloudSync.js b/9router/src/shared/services/initializeCloudSync.js
deleted file mode 100644
index b26a7d55..00000000
--- a/9router/src/shared/services/initializeCloudSync.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/* ========== CLOUD SYNC — COMMENTED OUT (replaced by Tunnel) ==========
-import { getCloudSyncScheduler } from "@/shared/services/cloudSyncScheduler";
-========== END CLOUD SYNC ========== */
-import { cleanupProviderConnections } from "@/lib/localDb";
-
-/**
- * Initialize cloud sync scheduler
- * This should be called when the application starts
- */
-export async function initializeCloudSync() {
- try {
- // Cleanup null fields from existing data
- await cleanupProviderConnections();
-
- /* ========== CLOUD SYNC — COMMENTED OUT (replaced by Tunnel) ==========
- // Create scheduler instance with default 15-minute interval
- const scheduler = await getCloudSyncScheduler(null, 15);
-
- // Start the scheduler
- await scheduler.start();
-
- return scheduler;
- ========== END CLOUD SYNC ========== */
- return null;
- } catch (error) {
- console.error("[CloudSync] Error initializing scheduler:", error);
- throw error;
- }
-}
-
-// For development/testing purposes
-if (typeof require !== "undefined" && require.main === module) {
- initializeCloudSync().catch(console.log);
-}
-
-export default initializeCloudSync;
-
diff --git a/9router/src/shared/utils/api.js b/9router/src/shared/utils/api.js
deleted file mode 100644
index 5712ef9e..00000000
--- a/9router/src/shared/utils/api.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/**
- * API utility functions for making HTTP requests
- */
-
-const DEFAULT_HEADERS = {
- "Content-Type": "application/json",
-};
-
-/**
- * Make a GET request
- * @param {string} url - API endpoint
- * @param {object} options - Fetch options
- * @returns {Promise