import type { IncomingMessage, ServerResponse } from "node:http"; import { readWebuiAvatarSettings, writeWebuiAvatarSettings } from "../db/index.ts"; import { setExtensionEnabled } from "../settings/extension-settings.ts"; import { listMcpSettings, setMcpServerEnabled, setMcpToolEnabled } from "../settings/mcp-settings.ts"; import { listSkillSettings, setSkillEnabled } from "../settings/skills-settings.ts"; import { normalizeAvatarUrl } from "../services/avatars.ts"; import { listExtensionsForSettings, listLoadedExtensionsByPath, mergeExtensionToggleResponse, } from "../services/extensions-display.ts"; import { readModelsConfig, writeModelsConfig } from "../services/models-config.ts"; import { readSystemPrompt, writeSystemPrompt } from "../services/system-prompt.ts"; import type { WebUiContext } from "../types/context.ts"; import { json, readBody } from "../http/request.ts"; function listMcpTools(ctx: WebUiContext): Record[] { return listMcpSettings(ctx.config.paths.mcpConfigFile, ctx.config.paths.mcpCacheFile); } export function handleSettingsRoute( req: IncomingMessage, res: ServerResponse, ctx: WebUiContext, pathname: string, ): boolean { const { paths } = ctx.config; const { sendCmd } = ctx.rpc; if (req.method === "GET" && pathname === "/api/settings") { void listSkillSettings(paths.repoRoot, paths.agentDir) .then((skills) => listExtensionsForSettings(paths, sendCmd).then((extensions) => json(res, { ...readWebuiAvatarSettings(), webuiDbPath: ctx.db.dbPath, systemPrompt: readSystemPrompt(paths), systemPromptPath: paths.systemPromptFile, modelsConfig: readModelsConfig(paths), modelsConfigPath: paths.modelsConfigFile, extensionsPath: paths.agentExtensionsDir, mcpCachePath: paths.mcpCacheFile, mcpConfigPath: paths.mcpConfigFile, skills, extensions, mcpTools: listMcpTools(ctx), }), ), ) .catch((err) => json(res, { error: err.message }, 500)); return true; } if (req.method === "POST" && pathname === "/api/settings/skills/toggle") { void readBody(req) .then(async ({ path: skillPath, enabled }) => { if (typeof skillPath !== "string" || !skillPath.trim()) { throw new Error("skill path 无效"); } if (typeof enabled !== "boolean") { throw new Error("enabled 必须是 boolean"); } const skill = await setSkillEnabled(paths.repoRoot, paths.agentDir, skillPath.trim(), enabled); const reload = await sendCmd({ type: "reload" }); if (!reload.success) throw new Error(reload.error || "Agent 重新加载失败"); json(res, { ok: true, skill }); }) .catch((err) => json(res, { error: err.message }, 500)); return true; } if (req.method === "POST" && pathname === "/api/settings/extensions/toggle") { void readBody(req) .then(async ({ path: extensionPath, enabled }) => { if (typeof extensionPath !== "string" || !extensionPath.trim()) { throw new Error("extension path 无效"); } if (typeof enabled !== "boolean") { throw new Error("enabled 必须是 boolean"); } const extension = await setExtensionEnabled( paths.repoRoot, paths.agentDir, extensionPath.trim(), enabled, ); const reload = await sendCmd({ type: "reload" }); if (!reload.success) throw new Error(reload.error || "Agent 重新加载失败"); let loadedByPath = new Map(); try { loadedByPath = await listLoadedExtensionsByPath(paths, sendCmd); } catch { /* ignore */ } json(res, { ok: true, extension: mergeExtensionToggleResponse(paths, extension, loadedByPath), }); }) .catch((err) => json(res, { error: err.message }, 500)); return true; } if (req.method === "POST" && pathname === "/api/settings/mcp/server/toggle") { void readBody(req) .then(async ({ server, enabled }) => { if (typeof server !== "string" || !server.trim()) { throw new Error("server 名称无效"); } if (typeof enabled !== "boolean") { throw new Error("enabled 必须是 boolean"); } const entry = setMcpServerEnabled( paths.mcpConfigFile, paths.mcpCacheFile, server.trim(), enabled, ); const reload = await sendCmd({ type: "reload" }); if (!reload.success) throw new Error(reload.error || "Agent 重新加载失败"); json(res, { ok: true, server: entry }); }) .catch((err) => json(res, { error: err.message }, 500)); return true; } if (req.method === "POST" && pathname === "/api/settings/mcp/tool/toggle") { void readBody(req) .then(async ({ server, tool, enabled }) => { if (typeof server !== "string" || !server.trim()) { throw new Error("server 名称无效"); } if (typeof tool !== "string" || !tool.trim()) { throw new Error("tool 名称无效"); } if (typeof enabled !== "boolean") { throw new Error("enabled 必须是 boolean"); } const entry = setMcpToolEnabled( paths.mcpConfigFile, paths.mcpCacheFile, server.trim(), tool.trim(), enabled, ); const reload = await sendCmd({ type: "reload" }); if (!reload.success) throw new Error(reload.error || "Agent 重新加载失败"); json(res, { ok: true, tool: entry }); }) .catch((err) => json(res, { error: err.message }, 500)); return true; } if (req.method === "POST" && pathname === "/api/settings/reload") { void sendCmd({ type: "reload" }) .then((r: any) => (r.success ? json(res, { ok: true }) : json(res, { error: r.error }, 500))) .catch((err) => json(res, { error: err.message }, 500)); return true; } if (req.method === "POST" && pathname === "/api/settings/models-config") { void readBody(req) .then(async ({ modelsConfig }) => { if (typeof modelsConfig !== "string") { throw new Error("modelsConfig 必须是字符串"); } writeModelsConfig(paths, modelsConfig); const reload = await sendCmd({ type: "reload" }); if (!reload.success) throw new Error(reload.error || "Agent 重新加载失败"); json(res, { ok: true, modelsConfigPath: paths.modelsConfigFile }); }) .catch((err) => json(res, { error: err.message }, 500)); return true; } if (req.method === "POST" && pathname === "/api/settings/system-prompt") { void readBody(req) .then(async ({ systemPrompt }) => { if (typeof systemPrompt !== "string") { throw new Error("systemPrompt 必须是字符串"); } writeSystemPrompt(paths, systemPrompt); const reload = await sendCmd({ type: "reload" }); if (!reload.success) throw new Error(reload.error || "Agent 重新加载失败"); json(res, { ok: true, systemPromptPath: paths.systemPromptFile }); }) .catch((err) => json(res, { error: err.message }, 500)); return true; } if (req.method === "POST" && pathname === "/api/settings/avatars") { void readBody(req) .then(({ userAvatarUrl, agentAvatarUrl }) => { const settings = { userAvatarUrl: normalizeAvatarUrl(userAvatarUrl), agentAvatarUrl: normalizeAvatarUrl(agentAvatarUrl), }; writeWebuiAvatarSettings(settings); json(res, { ok: true, ...settings }); }) .catch((err) => json(res, { error: err.message }, 500)); return true; } return false; }