import { describe, expect, it } from "vitest"; import { importSillyTavernPreset } from "../src/preset/importer.js"; import { declarePresetTools, extractDeclaredPayload, } from "../src/preset/tool-declaration.js"; function importRaw(raw: unknown, name: string) { return importSillyTavernPreset(raw, { name }).preset; } describe("声明式工具(预设数据 → 真 tools)", () => { it("从 ToolBindings 的 form 生成 tools", () => { const preset = importRaw( { prompts: [{ identifier: "main", name: "main", role: "system", content: "MAIN" }], prompt_order: [ { character_id: 100001, order: [{ identifier: "main", enabled: true }] }, ], extensions: { SPreset: { ToolBindings: { aaa: { enabled: true, resolvedName: "game_content", form: { name: "game_content", description: "# 你的全部输出应当在此工具内。", parameters: [{ name: "content", type: "string", required: true }], }, }, }, }, }, }, "bindings", ); const declared = declarePresetTools(preset); expect(declared?.source).toBe("bindings"); expect(declared?.tools).toHaveLength(1); const fn = declared!.tools[0]!.function; expect(fn.name).toBe("game_content"); expect(fn.description).toContain("全部输出"); expect(fn.parameters).toEqual({ type: "object", properties: { content: { type: "string" } }, required: ["content"], }); expect(declared?.payloadFields).toEqual(["content"]); }); it("只有伪工具协议条目时从正文取工具名与字段", () => { const preset = importRaw( { prompts: [ { identifier: "guide-sys", name: "【ToolCall引导】系统声明", role: "system", content: "调用 %TOOL_NAME% 才能输出正文;执行宿 dispatch_stream_payload_34d98628087f3798。", }, { identifier: "guide-field", name: "【ToolCall引导】字段说明", role: "system", content: "stream_target = \"display_stream\"\n正文写在这里。", }, ], prompt_order: [ { character_id: 100001, order: [ { identifier: "guide-sys", enabled: true }, { identifier: "guide-field", enabled: true }, ], }, ], }, "protocol", ); const declared = declarePresetTools(preset); expect(declared?.source).toBe("protocol"); const fn = declared!.tools[0]!.function; expect(fn.name).toBe("dispatch_stream_payload_34d98628087f3798"); expect(declared?.payloadFields).toContain("display_stream"); const params = fn.parameters as { properties: Record; required: string[] }; expect(Object.keys(params.properties)).toContain("display_stream"); expect(params.required).toContain("display_stream"); }); it("没有工具协议就返回 null", () => { const preset = importRaw( { prompts: [{ identifier: "main", name: "main", role: "system", content: "MAIN" }], prompt_order: [ { character_id: 100001, order: [{ identifier: "main", enabled: true }] }, ], }, "plain", ); expect(declarePresetTools(preset)).toBeNull(); }); }); describe("extractDeclaredPayload", () => { it("按字段优先级取正文", () => { expect( extractDeclaredPayload(JSON.stringify({ display_stream: "正文" }), ["display_stream"]), ).toBe("正文"); }); it("字段不在时退回任意字符串字段", () => { expect(extractDeclaredPayload(JSON.stringify({ other: "兜底" }), ["content"])).toBe("兜底"); }); it("坏 JSON 返回 null", () => { expect(extractDeclaredPayload("{oops", ["content"])).toBeNull(); }); });