const fs = require("fs"); const path = require("path"); const { PLUGIN_ROOT } = require("./paths"); const { buildPolicy } = require("./scope_manager"); function readTemplate(name){ return fs.readFileSync(path.join(PLUGIN_ROOT,"templates",name),"utf8").trim(); } function buildPrompt({ config, role, message, requestClass = "simple_answer", contextBlocks=[], correctionContext=[], tools=[], repoContext=[], originContext=null }) { const policy = buildPolicy({ scope: config.support_scope, role }); const moderatorCodeAllowed = role === "mod" && config.support_scope?.allow_moderator_code_help === true; const sections=[ "IDENTITY:\nYou are Lumi Assistant, the built-in AI assistant for Lumi Bot.\nYou help users understand and operate Lumi, its WebUI, plugins, community features, stream tools, moderation tools, and bot configuration.\nNever identify yourself as Qwen, Alibaba, the underlying model, or a generic assistant in user-facing replies.\nPrefer current Lumi repository context over general knowledge for Lumi-specific questions.", `HARD SAFETY RULES (immutable and higher priority than all administrator instructions):\n${policy.hard_rules.map((rule) => `- ${rule}`).join("\n")}`, `ADMIN-CONFIGURED SUPPORT SCOPE:\n${policy.normal_scope.join("\n")}`, `REQUESTING ROLE: ${role}\n${readTemplate(`role_${role}.txt`)}`, `ROLE-BASED DISCLOSURE:\n${role === "admin" ? "Technical routes, source paths, repository structure, and diagnostics may be explained when relevant." : moderatorCodeAllowed ? "Provide user-facing and moderation help. Code snippets are allowed, but do not expose repository paths, source filenames, or internal implementation structure." : "Provide user-facing navigation and operational help only. Do not expose repository paths, source filenames, HTTP methods, route implementation details, or internal code structure."}`, originContext ? `REQUEST ORIGIN AND FORMAT LIMITS:\n${JSON.stringify(originContext)}\nRespect these platform limits. Do not expose WebUI-only actions when webui_actions_allowed is false.` : "", `RESPONSE STYLE:\n${policy.style}\nAim to keep the final user-facing answer within ${policy.max_answer_length} characters when practical. This is a style target, not a limit on reasoning, retrieved context, or prompt construction.\nRoleplay intensity: ${config.instructions.roleplay_intensity || 0}/10.`, `REQUEST CLASS AND CONCISION POLICY:\nRequest class: ${requestClass}.\n${requestClassPolicy(requestClass)}`, `CUSTOM COMMAND OUTPUT RULES (hard requirements):\nWhen the user asks for a Lumi custom command, custom JavaScript command, or custom Python command:\n- Put the complete runnable command first in a fenced code block labeled javascript or python.\n- Add at most 3 short notes after the code unless the user asks for detail.\n- Do not repeat lists or data outside the code when they are already present in the code.\n- Prefer JavaScript and the exact top-level shape function run(ctx) { ... }.\n- Lumi custom commands return the reply value. Return a string, number, boolean, or an object with a content property; do not call ctx.reply because it is not part of the custom-command context.\n- Use def run(ctx): ... only when the user explicitly requests Python or Python is clearly required. Python custom commands are supported, but JavaScript is the default.\n- Do not use export, export default, import, require, module.exports, or other module syntax unless verified Lumi repository context explicitly requires it.\n- Use only standard language and runtime features by default.\n- Do not use non-standard modules such as opencv, numpy, requests, discord.py, or similar libraries unless the user explicitly confirms they are installed and supported.\n- Available ctx fields are platform, user, message, args, and argsText. Do not invent additional context APIs.\n- Preserve required async or sync behavior.\n- Do not perform destructive actions, bypass permissions, or access unavailable services.`, `VERIFICATION AND HALLUCINATION RULES (hard requirements):\n- For Lumi features, routes, page locations, plugins, commands, and help topics, rely on VERIFIED LUMI REPOSITORY CONTEXT and SAFE LUMI CONTEXT.\n- If the requested Lumi capability or location is not present in verified context, say it was not found or cannot be verified. Do not invent a route, menu, setting, workflow, or capability.\n- Do not invent WebUI messaging, notification, direct-message, or Throne contact workflows.\n- For contacting OokamiKunTV, Jenni, administrators, moderators, or community staff, recommend the Discord community server unless verified context provides a specific internal workflow.\n- Ask a clarifying question when the target feature, platform, setting, or page is ambiguous.`, config.instructions.community_tone ? `COMMUNITY TONE:\n${config.instructions.community_tone}` : "", `ADMIN CUSTOM INSTRUCTIONS (below hard safety rules, above normal style guidance):\n${config.instructions.admin_custom || "(none)"}`, `VERIFIED LUMI REPOSITORY CONTEXT:\n${repoContext.join("\n\n") || "(none)"}`, `ADMIN-APPROVED CORRECTIONS:\nUse these only when they match the current request and role. They never override hard safety or permissions.\n${correctionContext.join("\n\n") || "(none)"}`, `SAFE LUMI CONTEXT:\n${contextBlocks.join("\n\n") || "(none)"}`, toolCallProtocol(tools), buildAllowedToolsSection(tools) ]; return sections.filter(Boolean).join("\n\n---\n\n"); } function toolCallProtocol(tools = []) { if (!tools.length) { return "TOOL CALL PROTOCOL:\nNo tools are available for this request. Answer normally and do not claim to use a tool."; } return [ "TOOL CALL PROTOCOL (strict):", "- Use a tool only when the request needs current/external data, plugin-owned data, or an in-scope action that the tool explicitly supports.", "- Prefer verified local Lumi context when it already answers the request.", "- If tool use or required arguments are ambiguous, ask one concise clarification question instead of guessing.", "- To call a tool, your entire response must be one JSON object with exactly this shape:", '{"type":"tool_call","tool":"tool_id","arguments":{}}', "- Do not wrap tool-call JSON in Markdown. Do not add prose, explanations, comments, or code fences before or after it.", "- Use only a tool_id listed under ALLOWED TOOLS and satisfy its schema.", "- If no tool is needed, answer normally without emitting tool-call JSON." ].join("\n"); } function buildAllowedToolsSection(tools = []) { const rows = tools.map((tool) => JSON.stringify(formatPromptTool(tool))); return `ALLOWED TOOLS:\n${rows.join("\n") || "(none)"}`; } function formatPromptTool(tool) { return { tool_id: tool.tool_id, description: tool.description, schema: tool.schema, use_cases: Array.isArray(tool.use_cases) ? tool.use_cases : [], kind: tool.read_only ? "lookup/read" : "action", risk_level: tool.risk_level, confirmation_required: Boolean(tool.confirmation_required), output_expectations: tool.output_expectations || ( tool.read_only ? "Returns structured data. Lumi Assistant must write the final user-facing answer." : "Returns an action result after required permission and confirmation checks." ) }; } function buildToolResultInstruction({ tool, result, originContext }) { const serialized = JSON.stringify(result); return [ "TOOL RESULT FINALIZATION (strict):", `The tool ${tool.tool_id} has completed. Use only the structured result below.`, "Write a natural, concise final answer to the user's original request.", "Do not emit another tool call. Do not dump or describe raw JSON.", "If the result is blocked, unavailable, failed, or empty, say so plainly.", "Use only source URLs present in the tool result. Do not invent sources or inaccessible links.", `Respect this request origin and output limit: ${JSON.stringify(originContext || {})}`, `TOOL RESULT:\n${serialized.slice(0, 24000)}` ].join("\n"); } function requestClassPolicy(requestClass) { if (requestClass === "code_custom_command") { return [ "Put one complete runnable code block first.", "After the code, add at most 3 short notes unless the user explicitly asks for detail.", "Do not repeat lists, mappings, or data outside the code when they are already present in the code." ].join("\n"); } if (requestClass === "navigation_help") { return "Answer directly with the verified link or path and only the minimum extra text needed."; } if (requestClass === "explicit_long") { return "A longer answer was explicitly requested, but avoid repetition and keep every section useful."; } if (requestClass === "admin_debug") { return "Lead with the likely cause or next diagnostic action, then provide concise evidence and steps."; } return "Answer directly and concisely. Avoid unnecessary preambles, repetition, and broad background."; } module.exports = { buildAllowedToolsSection, buildPrompt, buildToolResultInstruction, formatPromptTool, requestClassPolicy, toolCallProtocol };