import type { GatewayRequest, OutboundMessage } from '../protocol.js'; import { makeResponse, makeError, ErrorCode } from '../protocol.js'; import type { Config } from '../../config/index.js'; export interface ConfigHandlerDeps { config: Config; } /** * Redact sensitive values from config before returning. * Replaces API keys, tokens, and passwords with "***". */ function redactConfig(config: Config): Record { const raw = JSON.parse(JSON.stringify(config)) as Record; // Redact telegram bot token const telegram = raw.telegram as Record | undefined; if (telegram?.bot_token) { telegram.bot_token = '***'; } // Redact model keys/tokens const models = raw.models as Record | undefined; if (models) { for (const tier of ['default', 'fast', 'complex', 'local'] as const) { const m = models[tier] as Record | undefined; if (m?.api_key) m.api_key = '***'; if (m?.auth_token) m.auth_token = '***'; } const localProviders = models.local_providers as Record> | undefined; if (localProviders) { for (const provider of Object.values(localProviders)) { if (provider.api_key) provider.api_key = '***'; if (provider.auth_token) provider.auth_token = '***'; } } } return raw; } /** Keys that are safe to update at runtime via config.patch. */ const PATCHABLE_KEYS: Record boolean> = { 'hooks.confirm': (config, value) => { if (!Array.isArray(value) || !value.every((v) => typeof v === 'string')) return false; config.hooks.confirm = value as string[]; return true; }, 'hooks.log': (config, value) => { if (!Array.isArray(value) || !value.every((v) => typeof v === 'string')) return false; config.hooks.log = value as string[]; return true; }, 'hooks.silent': (config, value) => { if (!Array.isArray(value) || !value.every((v) => typeof v === 'string')) return false; config.hooks.silent = value as string[]; return true; }, 'server.localhost': (config, value) => { if (typeof value !== 'boolean') return false; config.server.localhost = value; return true; }, }; export function createConfigHandlers(deps: ConfigHandlerDeps) { return { 'config.get': async (request: GatewayRequest): Promise => { return makeResponse(request.id, redactConfig(deps.config)); }, 'config.patch': async (request: GatewayRequest): Promise => { const patches = request.params?.patches; if (!patches || typeof patches !== 'object' || Array.isArray(patches)) { return makeError(request.id, ErrorCode.InvalidRequest, 'params.patches must be an object of { key: value } pairs'); } const applied: string[] = []; const rejected: string[] = []; for (const [key, value] of Object.entries(patches as Record)) { const patcher = PATCHABLE_KEYS[key]; if (!patcher) { rejected.push(key); continue; } const ok = patcher(deps.config, value); if (ok) { applied.push(key); } else { rejected.push(key); } } return makeResponse(request.id, { applied, rejected }); }, }; }