| import { Router } from 'express'; |
| import type { Request, Response } from 'express'; |
| import { z } from 'zod'; |
| import { getDb } from '../db/index.js'; |
| import { getAllPenalties } from '../services/router.js'; |
|
|
| export const fallbackRouter = Router(); |
|
|
| |
| fallbackRouter.get('/', (_req: Request, res: Response) => { |
| const db = getDb(); |
| const rows = db.prepare(` |
| SELECT fc.model_db_id, fc.priority, fc.enabled, |
| m.platform, m.model_id, m.display_name, m.intelligence_rank, |
| m.speed_rank, m.size_label, m.rpm_limit, m.rpd_limit, |
| m.monthly_token_budget |
| FROM fallback_config fc |
| JOIN models m ON m.id = fc.model_db_id |
| ORDER BY fc.priority ASC |
| `).all() as any[]; |
|
|
| |
| const keyCounts = db.prepare(` |
| SELECT platform, COUNT(*) as count |
| FROM api_keys WHERE enabled = 1 |
| GROUP BY platform |
| `).all() as { platform: string; count: number }[]; |
| const keyCountMap = new Map(keyCounts.map(k => [k.platform, k.count])); |
|
|
| |
| const penalties = getAllPenalties(); |
| const penaltyMap = new Map(penalties.map(p => [p.modelDbId, p])); |
|
|
| res.json(rows.map(r => { |
| const penalty = penaltyMap.get(r.model_db_id); |
| return { |
| modelDbId: r.model_db_id, |
| priority: r.priority, |
| effectivePriority: r.priority + (penalty?.penalty ?? 0), |
| penalty: penalty?.penalty ?? 0, |
| rateLimitHits: penalty?.count ?? 0, |
| enabled: r.enabled === 1, |
| platform: r.platform, |
| modelId: r.model_id, |
| displayName: r.display_name, |
| intelligenceRank: r.intelligence_rank, |
| speedRank: r.speed_rank, |
| sizeLabel: r.size_label, |
| rpmLimit: r.rpm_limit, |
| rpdLimit: r.rpd_limit, |
| monthlyTokenBudget: r.monthly_token_budget, |
| keyCount: keyCountMap.get(r.platform) ?? 0, |
| }; |
| })); |
| }); |
|
|
| const updateSchema = z.array(z.object({ |
| modelDbId: z.number(), |
| priority: z.number(), |
| enabled: z.boolean(), |
| })); |
|
|
| |
| fallbackRouter.put('/', (req: Request, res: Response) => { |
| const parsed = updateSchema.safeParse(req.body); |
| if (!parsed.success) { |
| res.status(400).json({ error: { message: parsed.error.errors.map(e => e.message).join(', ') } }); |
| return; |
| } |
|
|
| const db = getDb(); |
| const update = db.prepare(` |
| UPDATE fallback_config SET priority = ?, enabled = ? WHERE model_db_id = ? |
| `); |
|
|
| const updateAll = db.transaction(() => { |
| for (const entry of parsed.data) { |
| update.run(entry.priority, entry.enabled ? 1 : 0, entry.modelDbId); |
| } |
| }); |
| updateAll(); |
|
|
| res.json({ success: true }); |
| }); |
|
|
| |
| |
| const SORT_PRESETS: Record<string, string> = { |
| intelligence: 'm.intelligence_rank ASC', |
| speed: 'm.speed_rank ASC', |
| budget: "CASE m.monthly_token_budget WHEN '~120M' THEN 1 WHEN '~50-100M' THEN 2 WHEN '~30M' THEN 3 WHEN '~18-45M' THEN 4 WHEN '~18M' THEN 5 WHEN '~15M' THEN 6 WHEN '~12M' THEN 7 WHEN '~6M' THEN 8 WHEN '~5-10M' THEN 9 WHEN '~4M' THEN 10 ELSE 11 END ASC", |
| }; |
|
|
| fallbackRouter.post('/sort/:preset', (req: Request, res: Response) => { |
| const preset = String(req.params.preset); |
| const orderBy = SORT_PRESETS[preset]; |
| if (!orderBy) { |
| res.status(400).json({ error: { message: `Unknown preset: ${preset}. Use: intelligence, speed, budget` } }); |
| return; |
| } |
|
|
| const db = getDb(); |
| const models = db.prepare(`SELECT m.id FROM models m ORDER BY ${orderBy}`).all() as { id: number }[]; |
|
|
| const update = db.prepare('UPDATE fallback_config SET priority = ? WHERE model_db_id = ?'); |
| const reorder = db.transaction(() => { |
| for (let i = 0; i < models.length; i++) { |
| update.run(i + 1, models[i].id); |
| } |
| }); |
| reorder(); |
|
|
| res.json({ success: true, preset }); |
| }); |
|
|
| |
| fallbackRouter.get('/token-usage', (_req: Request, res: Response) => { |
| const db = getDb(); |
|
|
| |
| const platforms = db.prepare(` |
| SELECT DISTINCT ak.platform |
| FROM api_keys ak |
| WHERE ak.enabled = 1 |
| `).all() as { platform: string }[]; |
| const platformSet = new Set(platforms.map(p => p.platform)); |
|
|
| |
| const models = db.prepare(` |
| SELECT m.platform, m.model_id, m.display_name, m.monthly_token_budget, |
| fc.priority |
| FROM models m |
| JOIN fallback_config fc ON fc.model_db_id = m.id |
| WHERE m.enabled = 1 |
| ORDER BY fc.priority ASC |
| `).all() as { platform: string; model_id: string; display_name: string; monthly_token_budget: string; priority: number }[]; |
|
|
| function parseBudget(s: string): number { |
| const m = s.match(/~?([\d.]+)(?:-([\d.]+))?([MK])?/); |
| if (!m) return 0; |
| const high = parseFloat(m[2] ?? m[1]); |
| const unit = m[3] === 'M' ? 1_000_000 : m[3] === 'K' ? 1_000 : 1; |
| return high * unit; |
| } |
|
|
| |
| const modelBudgets = models |
| .filter(m => platformSet.has(m.platform)) |
| .map(m => ({ |
| displayName: m.display_name, |
| platform: m.platform, |
| budget: parseBudget(m.monthly_token_budget), |
| })); |
|
|
| const totalBudget = modelBudgets.reduce((s, m) => s + m.budget, 0); |
|
|
| |
| const usage = db.prepare(` |
| SELECT |
| COALESCE(SUM(input_tokens + output_tokens), 0) as total_used |
| FROM requests |
| WHERE created_at >= datetime('now', 'start of month') |
| `).get() as { total_used: number }; |
|
|
| res.json({ |
| totalBudget, |
| totalUsed: usage.total_used, |
| models: modelBudgets, |
| }); |
| }); |
|
|