| import { readFileSync, writeFileSync, existsSync } from 'fs'; |
| import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'; |
| import type { Request, Response } from 'express'; |
| import { getConfig } from './config.js'; |
|
|
| |
| |
| |
| |
| export function apiGetConfig(_req: Request, res: Response): void { |
| const cfg = getConfig(); |
| res.json({ |
| cursor_model: cfg.cursorModel, |
| timeout: cfg.timeout, |
| max_auto_continue: cfg.maxAutoContinue, |
| max_history_messages: cfg.maxHistoryMessages, |
| thinking: cfg.thinking !== undefined ? { enabled: cfg.thinking.enabled } : null, |
| compression: { |
| enabled: cfg.compression?.enabled ?? false, |
| level: cfg.compression?.level ?? 1, |
| keep_recent: cfg.compression?.keepRecent ?? 10, |
| early_msg_max_chars: cfg.compression?.earlyMsgMaxChars ?? 4000, |
| }, |
| tools: { |
| schema_mode: cfg.tools?.schemaMode ?? 'full', |
| description_max_length: cfg.tools?.descriptionMaxLength ?? 0, |
| passthrough: cfg.tools?.passthrough ?? false, |
| disabled: cfg.tools?.disabled ?? false, |
| }, |
| sanitize_response: cfg.sanitizeEnabled, |
| refusal_patterns: cfg.refusalPatterns ?? [], |
| logging: cfg.logging ?? { file_enabled: false, dir: './logs', max_days: 7, persist_mode: 'summary' }, |
| }); |
| } |
|
|
| |
| |
| |
| |
| export function apiSaveConfig(req: Request, res: Response): void { |
| const body = req.body as Record<string, unknown>; |
|
|
| |
| if (body.cursor_model !== undefined && typeof body.cursor_model !== 'string') { |
| res.status(400).json({ error: 'cursor_model must be a string' }); return; |
| } |
| if (body.timeout !== undefined && (typeof body.timeout !== 'number' || body.timeout <= 0)) { |
| res.status(400).json({ error: 'timeout must be a positive number' }); return; |
| } |
| if (body.max_auto_continue !== undefined && typeof body.max_auto_continue !== 'number') { |
| res.status(400).json({ error: 'max_auto_continue must be a number' }); return; |
| } |
| if (body.max_history_messages !== undefined && typeof body.max_history_messages !== 'number') { |
| res.status(400).json({ error: 'max_history_messages must be a number' }); return; |
| } |
|
|
| try { |
| |
| let raw: Record<string, unknown> = {}; |
| if (existsSync('config.yaml')) { |
| raw = (parseYaml(readFileSync('config.yaml', 'utf-8')) as Record<string, unknown>) ?? {}; |
| } |
|
|
| |
| const changes: string[] = []; |
|
|
| |
| if (body.cursor_model !== undefined && body.cursor_model !== raw.cursor_model) { |
| changes.push(`cursor_model: ${raw.cursor_model ?? '(unset)'} → ${body.cursor_model}`); |
| raw.cursor_model = body.cursor_model; |
| } |
| if (body.timeout !== undefined && body.timeout !== raw.timeout) { |
| changes.push(`timeout: ${raw.timeout ?? '(unset)'} → ${body.timeout}`); |
| raw.timeout = body.timeout; |
| } |
| if (body.max_auto_continue !== undefined && body.max_auto_continue !== raw.max_auto_continue) { |
| changes.push(`max_auto_continue: ${raw.max_auto_continue ?? '(unset)'} → ${body.max_auto_continue}`); |
| raw.max_auto_continue = body.max_auto_continue; |
| } |
| if (body.max_history_messages !== undefined && body.max_history_messages !== raw.max_history_messages) { |
| changes.push(`max_history_messages: ${raw.max_history_messages ?? '(unset)'} → ${body.max_history_messages}`); |
| raw.max_history_messages = body.max_history_messages; |
| } |
| if (body.thinking !== undefined) { |
| const t = body.thinking as { enabled: boolean | null } | null; |
| const oldVal = JSON.stringify(raw.thinking); |
| if (t === null || t?.enabled === null) { |
| |
| if (raw.thinking !== undefined) { |
| changes.push(`thinking: ${oldVal} → (跟随客户端)`); |
| delete raw.thinking; |
| } |
| } else { |
| const newVal = JSON.stringify(t); |
| if (oldVal !== newVal) { |
| changes.push(`thinking: ${oldVal ?? '(unset)'} → ${newVal}`); |
| raw.thinking = t; |
| } |
| } |
| } |
| if (body.compression !== undefined) { |
| const oldVal = JSON.stringify(raw.compression); |
| const newVal = JSON.stringify(body.compression); |
| if (oldVal !== newVal) { |
| changes.push(`compression: (changed)`); |
| raw.compression = body.compression; |
| } |
| } |
| if (body.tools !== undefined) { |
| const oldVal = JSON.stringify(raw.tools); |
| const newVal = JSON.stringify(body.tools); |
| if (oldVal !== newVal) { |
| changes.push(`tools: (changed)`); |
| raw.tools = body.tools; |
| } |
| } |
| if (body.sanitize_response !== undefined && body.sanitize_response !== raw.sanitize_response) { |
| changes.push(`sanitize_response: ${raw.sanitize_response ?? '(unset)'} → ${body.sanitize_response}`); |
| raw.sanitize_response = body.sanitize_response; |
| } |
| if (body.refusal_patterns !== undefined) { |
| const oldVal = JSON.stringify(raw.refusal_patterns); |
| const newVal = JSON.stringify(body.refusal_patterns); |
| if (oldVal !== newVal) { |
| changes.push(`refusal_patterns: (changed)`); |
| raw.refusal_patterns = body.refusal_patterns; |
| } |
| } |
| if (body.logging !== undefined) { |
| const oldVal = JSON.stringify(raw.logging); |
| const newVal = JSON.stringify(body.logging); |
| if (oldVal !== newVal) { |
| changes.push(`logging: (changed)`); |
| raw.logging = body.logging; |
| } |
| } |
|
|
| if (changes.length === 0) { |
| res.json({ ok: true, changes: [] }); |
| return; |
| } |
|
|
| |
| writeFileSync('config.yaml', stringifyYaml(raw, { lineWidth: 0 }), 'utf-8'); |
|
|
| console.log(`[Config API] ✏️ 通过 UI 更新配置,${changes.length} 项变更:`); |
| changes.forEach(c => console.log(` └─ ${c}`)); |
|
|
| res.json({ ok: true, changes }); |
| } catch (e) { |
| console.error('[Config API] 写入 config.yaml 失败:', e); |
| res.status(500).json({ error: String(e) }); |
| } |
| } |
|
|