import { amilyHelper } from "../tavern-helper/main.js"; import * as charApi from "./char-api.js"; import { callAi } from "./api.js"; export const tools = { read_world_info: async ({ book_name, return_full = false }) => { const entries = await amilyHelper.getLorebookEntries(book_name); if (return_full) { return JSON.stringify(entries, null, 2); } const summary = entries.map(e => { let keys = e.key; if (Array.isArray(keys)) keys = keys.join(', '); return { uid: e.uid, keys: keys, comment: e.comment || keys || "未命名条目", }; }); return JSON.stringify({ info: "世界书条目索引。请使用带有 'uid' 的 'read_world_entry' 来读取具体内容。", total_entries: entries.length, entries: summary }, null, 2); }, read_world_entry: async ({ book_name, uid }) => { const entries = await amilyHelper.getLorebookEntries(book_name); const entry = entries.find(e => String(e.uid) === String(uid)); if (!entry) { return JSON.stringify({ status: "error", code: "ENTRY_NOT_FOUND", message: `在世界书 "${book_name}" 中未找到 UID 为 ${uid} 的条目。`, suggestion: "请使用 'read_world_info' 查看可用的 UID。" }); } return JSON.stringify({ status: "success", data: entry }, null, 2); }, write_world_info_entry: async ({ book_name, entries }) => { if (typeof entries === 'string') { try { const cleanEntries = entries.replace(/```json/g, '').replace(/```/g, '').trim(); entries = JSON.parse(cleanEntries); } catch (e) { return JSON.stringify({ status: "error", code: "INVALID_JSON", message: `'entries' 参数必须是有效的 JSON 数组。解析错误: ${e.message}` }); } } if (!Array.isArray(entries)) { if (typeof entries === 'object' && entries !== null) { entries = [entries]; } else { return JSON.stringify({ status: "error", code: "INVALID_TYPE", message: "'entries' 参数必须是数组或对象。" }); } } const updates = []; const creates = []; for (const entry of entries) { if (entry.uid !== undefined) { updates.push(entry); } else { creates.push(entry); } } let updatedCount = 0; let createdCount = 0; let errors = []; if (updates.length > 0) { const success = await amilyHelper.setLorebookEntries(book_name, updates); if (success) updatedCount = updates.length; else errors.push("更新条目失败。"); } if (creates.length > 0) { const success = await amilyHelper.createLorebookEntries(book_name, creates); if (success) createdCount = creates.length; else errors.push("创建条目失败。"); } if (errors.length > 0 && updatedCount === 0 && createdCount === 0) { return JSON.stringify({ status: "error", code: "WRITE_FAILED", message: errors.join(" ") }); } return JSON.stringify({ status: "success", message: `成功更新了 ${updatedCount} 个条目,创建了 ${createdCount} 个条目。`, data: { updated: updatedCount, created: createdCount } }); }, create_world_book: async ({ book_name }) => { const success = await amilyHelper.createLorebook(book_name); if (success) { return JSON.stringify({ status: "success", message: `世界书 "${book_name}" 创建成功。` }); } else { return JSON.stringify({ status: "error", code: "CREATE_FAILED", message: `创建世界书 "${book_name}" 失败。` }); } }, read_character_card: async ({ chid }) => { const char = charApi.getCharacter(chid); if (!char) { return JSON.stringify({ status: "error", code: "CHAR_NOT_FOUND", message: "未找到角色。" }); } const safeChar = { name: char.name, description: char.description, personality: char.personality, scenario: char.scenario, first_mes: char.first_mes, mes_example: char.mes_example, alternate_greetings: char.data?.alternate_greetings || [] }; return JSON.stringify({ status: "success", data: safeChar }, null, 2); }, update_character_card: async (args) => { const { chid, ...updates } = args; const finalUpdates = args.updates || updates; const success = await charApi.updateCharacter(chid, finalUpdates); if (success) { const updatedFields = Object.keys(finalUpdates).join(', '); return JSON.stringify({ status: "success", message: `角色卡更新成功 [ID: ${chid}]。`, data: { updated_fields: updatedFields } }); } else { return JSON.stringify({ status: "error", code: "UPDATE_FAILED", message: "更新角色卡失败。请确保您正在编辑当前选中的角色(暂不支持后台编辑其他角色)。" }); } }, edit_character_text: async ({ chid, field, diff }) => { const char = charApi.getCharacter(chid); if (!char) { return JSON.stringify({ status: "error", code: "CHAR_NOT_FOUND", message: "未找到角色。" }); } const allowedFields = ['description', 'personality', 'scenario', 'first_mes', 'mes_example']; if (!allowedFields.includes(field)) { return JSON.stringify({ status: "error", code: "INVALID_FIELD", message: `无效的字段。允许的字段: ${allowedFields.join(', ')}` }); } let content = char[field] || ''; const normalizedDiff = diff .replace(/-------\s*SEARCH/g, '------- SEARCH') .replace(/=======\s*/g, '=======') .replace(/\+\+\+\+\+\+\+\s*REPLACE/g, '+++++++ REPLACE'); const changes = normalizedDiff.split('------- SEARCH'); if (changes[0].trim() === '') changes.shift(); for (const change of changes) { const parts = change.split('======='); if (parts.length < 2) continue; const searchBlock = parts[0].trim(); const replaceBlock = parts[1].split('+++++++ REPLACE')[0].trim(); if (content.includes(searchBlock)) { content = content.replace(searchBlock, replaceBlock); continue; } const normalizedSearch = searchBlock.replace(/\r\n/g, '\n'); const lines = normalizedSearch.split('\n'); const regexPattern = lines.map(line => line.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\r?\\n'); const regex = new RegExp(regexPattern); const match = content.match(regex); if (match) { content = content.replace(match[0], replaceBlock); continue; } return JSON.stringify({ status: "error", code: "SEARCH_NOT_FOUND", message: `在字段 '${field}' 中未找到搜索块。`, suggestion: "请确保 SEARCH 块与现有内容完全匹配(包括空格)。" }); } const success = await charApi.updateCharacter(chid, { [field]: content }); if (success) { return JSON.stringify({ status: "success", message: `字段 '${field}' 更新成功。` }); } else { return JSON.stringify({ status: "error", code: "UPDATE_FAILED", message: `更新字段 '${field}' 失败。请确保您正在编辑当前选中的角色(暂不支持后台编辑其他角色)。` }); } }, edit_world_info_entry: async ({ book_name, uid, diff }) => { const entries = await amilyHelper.getLorebookEntries(book_name); const entry = entries.find(e => String(e.uid) === String(uid)); if (!entry) { return JSON.stringify({ status: "error", code: "ENTRY_NOT_FOUND", message: `在世界书 "${book_name}" 中未找到 UID 为 ${uid} 的条目。` }); } let content = entry.content || ''; const normalizedDiff = diff .replace(/-------\s*SEARCH/g, '------- SEARCH') .replace(/=======\s*/g, '=======') .replace(/\+\+\+\+\+\+\+\s*REPLACE/g, '+++++++ REPLACE'); const changes = normalizedDiff.split('------- SEARCH'); if (changes[0].trim() === '') changes.shift(); for (const change of changes) { const parts = change.split('======='); if (parts.length < 2) continue; const searchBlock = parts[0].trim(); const replaceBlock = parts[1].split('+++++++ REPLACE')[0].trim(); if (content.includes(searchBlock)) { content = content.replace(searchBlock, replaceBlock); continue; } const normalizedSearch = searchBlock.replace(/\r\n/g, '\n'); const lines = normalizedSearch.split('\n'); const regexPattern = lines.map(line => line.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('\\r?\\n'); const regex = new RegExp(regexPattern); const match = content.match(regex); if (match) { content = content.replace(match[0], replaceBlock); continue; } return JSON.stringify({ status: "error", code: "SEARCH_NOT_FOUND", message: `在条目内容中未找到搜索块。`, suggestion: "请确保 SEARCH 块与现有内容完全匹配(包括空格)。" }); } const success = await amilyHelper.setLorebookEntries(book_name, [{ uid: entry.uid, content: content }]); if (success) { return JSON.stringify({ status: "success", message: `条目 [${uid}] 更新成功。` }); } else { return JSON.stringify({ status: "error", code: "UPDATE_FAILED", message: `更新条目 [${uid}] 失败。` }); } }, manage_first_message: async ({ action, chid, index, message }) => { let success = false; switch (action) { case 'add': success = await charApi.addFirstMessage(chid, message); break; case 'update': success = await charApi.updateFirstMessage(chid, index, message); break; case 'remove': success = await charApi.removeFirstMessage(chid, index); break; default: return JSON.stringify({ status: "error", code: "INVALID_ACTION", message: "无效的操作。" }); } if (success) { return JSON.stringify({ status: "success", message: `开场白 ${action} 成功。` }); } else { return JSON.stringify({ status: "error", code: "ACTION_FAILED", message: `开场白 ${action} 失败。` }); } }, create_character: async ({ name }) => { const result = await charApi.createNewCharacter(name); if (result === -1) { return JSON.stringify({ status: "error", code: "CREATE_FAILED", message: "创建角色失败。" }); } if (result === -2) { return JSON.stringify({ status: "warning", code: "CREATE_PENDING", message: "角色创建请求已发送。请手动刷新。" }); } return JSON.stringify({ status: "success", message: `角色创建成功。`, data: { id: result } }); }, simulate_chat: async ({ chid, message }) => { const char = charApi.getCharacter(chid); if (!char) { return JSON.stringify({ status: "error", code: "CHAR_NOT_FOUND", message: "未找到角色。" }); } const systemPrompt = `You are roleplaying as ${char.name}. Description: ${char.description} Personality: ${char.personality} Scenario: ${char.scenario} Reply to the user's message in character. Stay in character.`; const messages = [ { role: 'system', content: systemPrompt }, { role: 'user', content: message } ]; try { const response = await callAi('executor', messages, { temperature: 0.9 }); return JSON.stringify({ status: "success", data: { character: char.name, response: response } }); } catch (error) { return JSON.stringify({ status: "error", code: "SIMULATION_FAILED", message: `模拟对话失败: ${error.message}` }); } }, set_style_reference: async ({ style }) => { return JSON.stringify({ status: "success", message: `样式参考已设置为: ${style}`, _action: "update_task_state", _updates: { style_reference: style } }); }, analyze_entities: async ({ text }) => { const systemPrompt = `You are an expert World Builder and Entity Extractor. Analyze the provided text and identify key entities that should have their own World Info (Lorebook) entries. Focus on: - Proper Nouns (People, Places, Organizations, Artifacts) - Unique Concepts (Magic systems, Historical events, Species) Return a JSON object with a "entities" array. Each entity should have: - "name": The name of the entity. - "type": The type (Person, Location, Organization, etc.). - "description": A brief summary based on the text (1-2 sentences). - "confidence": A score (0-1) of how important this entity seems. Output ONLY valid JSON.`; const messages = [ { role: 'system', content: systemPrompt }, { role: 'user', content: text } ]; try { const response = await callAi('executor', messages, { temperature: 0.1 }); const cleanResponse = response.replace(/```json/g, '').replace(/```/g, '').trim(); return cleanResponse; } catch (error) { return JSON.stringify({ status: "error", code: "ANALYSIS_FAILED", message: `实体分析失败: ${error.message}` }); } }, ask_user: async ({ question }) => { return JSON.stringify({ status: "success", message: `已向用户提问: ${question}`, _action: "stop_and_wait", data: { question } }); } }; export function getToolDefinitions() { return [ { name: "read_world_info", description: "读取世界书的索引(包含关键字和注释的条目列表)。不返回完整内容。", parameters: { type: "object", properties: { book_name: { type: "string", description: "世界书名称。" } }, required: ["book_name"] } }, { name: "read_world_entry", description: "读取特定世界书条目的完整内容。", parameters: { type: "object", properties: { book_name: { type: "string", description: "世界书名称。" }, uid: { type: "number", description: "要读取的条目 UID。" } }, required: ["book_name", "uid"] } }, { name: "write_world_info_entry", description: "创建或更新世界书中的条目。", parameters: { type: "object", properties: { book_name: { type: "string", description: "世界书名称。" }, entries: { type: "array", items: { type: "object", properties: { uid: { type: "number", description: "条目 ID(可选,用于更新)。" }, comment: { type: "string", description: "条目标题/注释。" }, content: { type: "string", description: "条目内容。" }, key: { type: "array", items: { type: "string" }, description: "关键字。" }, enabled: { type: "boolean", description: "是否启用。" }, constant: { type: "boolean", description: "常驻(蓝灯)。" }, position: { type: "string", enum: ["before_character_definition", "after_character_definition", "before_author_note", "after_author_note", "at_depth_as_system"], description: "插入位置。" }, depth: { type: "number", description: "插入深度。" }, scanDepth: { type: "number", description: "扫描深度。" }, exclude_recursion: { type: "boolean", description: "排除递归。" }, prevent_recursion: { type: "boolean", description: "防止递归。" } } } } }, required: ["book_name", "entries"] } }, { name: "create_world_book", description: "创建一个新的空世界书。", parameters: { type: "object", properties: { book_name: { type: "string", description: "新世界书的名称。" } }, required: ["book_name"] } }, { name: "read_character_card", description: "读取角色卡数据。", parameters: { type: "object", properties: { chid: { type: "number", description: "角色 ID。" } }, required: ["chid"] } }, { name: "update_character_card", description: "更新角色卡字段(覆盖)。", parameters: { type: "object", properties: { chid: { type: "number", description: "角色 ID。" }, name: { type: "string" }, description: { type: "string" }, personality: { type: "string" }, scenario: { type: "string" }, first_mes: { type: "string" }, mes_example: { type: "string" } }, required: ["chid"] } }, { name: "edit_character_text", description: "使用 搜索/替换 块编辑角色的特定文本字段。", parameters: { type: "object", properties: { chid: { type: "number", description: "角色 ID。" }, field: { type: "string", enum: ["description", "personality", "scenario", "first_mes", "mes_example"], description: "要编辑的字段。" }, diff: { type: "string", description: "一个或多个遵循此确切格式的 搜索/替换 块:\n------- SEARCH\n[exact content to find]\n=======\n[new content to replace with]\n+++++++ REPLACE" } }, required: ["chid", "field", "diff"] } }, { name: "edit_world_info_entry", description: "使用 搜索/替换 块编辑世界书条目的内容。", parameters: { type: "object", properties: { book_name: { type: "string", description: "世界书名称。" }, uid: { type: "number", description: "条目 UID。" }, diff: { type: "string", description: "一个或多个遵循此确切格式的 搜索/替换 块:\n------- SEARCH\n[exact content to find]\n=======\n[new content to replace with]\n+++++++ REPLACE" } }, required: ["book_name", "uid", "diff"] } }, { name: "manage_first_message", description: "添加、更新或删除候补开场白。", parameters: { type: "object", properties: { action: { type: "string", enum: ["add", "update", "remove"] }, chid: { type: "number", description: "角色 ID。" }, index: { type: "number", description: "开场白索引(更新/删除时必需)。" }, message: { type: "string", description: "开场白内容(添加/更新时必需)。" } }, required: ["action", "chid"] } }, { name: "create_character", description: "创建一个新角色卡。", parameters: { type: "object", properties: { name: { type: "string", description: "新角色的名字。" } }, required: ["name"] } }, { name: "simulate_chat", description: "与角色模拟对话以测试其性格和设定。", parameters: { type: "object", properties: { chid: { type: "number", description: "角色 ID。" }, message: { type: "string", description: "发送给角色的消息。" } }, required: ["chid", "message"] } }, { name: "set_style_reference", description: "设置生成内容的风格参考或模板(例如:'黑暗奇幻风格','莎士比亚风格','JSON格式模板')。", parameters: { type: "object", properties: { style: { type: "string", description: "风格描述或模板内容。" } }, required: ["style"] } }, { name: "analyze_entities", description: "分析文本并提取潜在的世界书条目(实体)。", parameters: { type: "object", properties: { text: { type: "string", description: "要分析的文本。" } }, required: ["text"] } }, { name: "ask_user", description: "向用户提问以获取更多信息或确认。这将暂停自动执行并等待用户回复。", parameters: { type: "object", properties: { question: { type: "string", description: "要问的问题。" } }, required: ["question"] } } ]; }