import { Router } from 'express'; import { randomUUID } from 'crypto'; import Anthropic from '@anthropic-ai/sdk'; import OpenAI from 'openai'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import { getDb } from '../db'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const PROJECT_ROOT = path.resolve(__dirname, '..', '..', '..'); const SKILLS_CONFIG_PATH = path.join(PROJECT_ROOT, '.claude', 'skills', 'config.json'); export const configsRouter = Router(); configsRouter.get('/', (_req, res) => { const rows = getDb().prepare('SELECT * FROM configs ORDER BY key').all(); res.json(rows); }); configsRouter.get('/:key', (req, res) => { const row = getDb().prepare('SELECT * FROM configs WHERE key = ?').get(req.params.key); if (!row) return res.status(404).json({ error: 'Config not found' }); res.json(row); }); configsRouter.put('/:key', (req, res) => { const { value } = req.body; getDb().prepare(` INSERT INTO configs (id, key, value, updated_at) VALUES (?, ?, ?, datetime('now')) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at `).run(randomUUID(), req.params.key, JSON.stringify(value)); res.json({ key: req.params.key, ok: true }); }); // Skills config.json (pipeline tools configuration) configsRouter.get('/skills/file', (_req, res) => { try { const data = JSON.parse(fs.readFileSync(SKILLS_CONFIG_PATH, 'utf-8')); res.json(data); } catch { res.status(404).json({ error: 'Skills config not found' }); } }); configsRouter.put('/skills/file', (req, res) => { try { fs.writeFileSync(SKILLS_CONFIG_PATH, JSON.stringify(req.body, null, 2), 'utf-8'); res.json({ ok: true }); } catch (e) { res.status(500).json({ error: (e as Error).message }); } }); // Test API connection configsRouter.post('/test-connection', async (_req, res) => { const configRow = getDb().prepare('SELECT value FROM configs WHERE key = ?').get('api_keys') as { value: string } | undefined; if (!configRow) return res.json({ ok: false, error: '未配置 API Key' }); let apiKey = ''; let baseURL: string | undefined; let protocol: string = 'anthropic'; let model: string = 'claude-sonnet-4-6'; try { const cfg = JSON.parse(configRow.value); apiKey = cfg.ANTHROPIC_AUTH_TOKEN || ''; baseURL = cfg.ANTHROPIC_BASE_URL; protocol = cfg.PROTOCOL || 'anthropic'; model = cfg.ANTHROPIC_MODEL || model; } catch { return res.json({ ok: false, error: '配置解析失败' }); } if (!apiKey) return res.json({ ok: false, error: 'API Key 为空' }); try { if (protocol === 'openai') { const client = new OpenAI({ apiKey, baseURL: baseURL || 'https://api.openai.com/v1' }); const models = await client.models.list(); const modelIds: string[] = []; for await (const m of models) { modelIds.push(m.id); if (modelIds.length >= 5) break; } res.json({ ok: true, models: modelIds }); } else { const client = new Anthropic({ apiKey, baseURL }); // Anthropic SDK doesn't have a models.list() in all versions, try a minimal request try { await client.messages.create({ model, max_tokens: 1, messages: [{ role: 'user', content: 'hi' }], }); res.json({ ok: true, models: [model] }); } catch (e: unknown) { const err = e as { status?: number; message?: string }; if (err.status === 401) { res.json({ ok: false, error: `认证失败: ${err.message}` }); } else if (err.status === 400 || err.status === undefined) { res.json({ ok: true, models: [model] }); } else { res.json({ ok: false, error: `错误 (${err.status}): ${err.message}` }); } } } } catch (e) { res.json({ ok: false, error: (e as Error).message }); } });