- 装配只认 prompt_order.enabled;squash_system_messages 只合并相邻 system(新增 system 档,连接符按酒馆用单换行)
- 包装层:new_chat_prompt、示例对话(new_example_chat_prompt + <START> 切段 + name)、角色卡三块与 personality_format/scenario_format、{{user}}/{{char}} 取值、任务契约并进预设条目
- 工具协议:按 ToolBindings / 伪工具协议数据声明真 tools(presetToolPolicy=preset),工具描述用到的条目不再重复作为消息发出
- 正则:promptOnly 作用于对话历史(含 depth 过滤)、写入时脚本改写回复;{{//}} 注释按配平扫描
- tools/st-request-diff:真假上游抓包对比工具、三份预设的机制结论与实测报告
186 lines
6.0 KiB
TypeScript
186 lines
6.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { expandAtDirectives, expandPresetDirectives } from "../src/directives/expand.js";
|
|
import { collectMacroNames, createMacroVars, expandMacros } from "../src/directives/macros.js";
|
|
import { assemblePresetMessages } from "../src/preset/assembler.js";
|
|
import type { PresetPackage } from "../src/types/preset.js";
|
|
|
|
describe("expandMacros", () => {
|
|
it("strips {{// }} comments, including multi-line ones", () => {
|
|
const r = expandMacros("前{{// \n声明:禁止外传\n\n}}后");
|
|
expect(r.text).toBe("前后");
|
|
expect(r.hits.map((h) => h.kind)).toEqual(["comment"]);
|
|
});
|
|
|
|
it("does not expand @ tokens inside a comment", () => {
|
|
const r = expandAtDirectives("{{// 不要动 @玩家 }}", {
|
|
persona: { name: "阿泽" },
|
|
});
|
|
expect(r.text).toBe("");
|
|
expect(r.hits.map((h) => h.kind)).toEqual(["comment"]);
|
|
});
|
|
|
|
it("{{trim}} expands to empty and asks caller to trim", () => {
|
|
const r = expandMacros(" {{// 备注 }}{{trim}}\n正文 ");
|
|
expect(r.text).toBe(" \n正文 ");
|
|
expect(r.trim).toBe(true);
|
|
expect(expandAtDirectives(" {{trim}}\n正文 ").text).toBe("正文");
|
|
});
|
|
|
|
it("{{user}} and {{char}}", () => {
|
|
const r = expandMacros("{{user}} 与 {{char}}", {
|
|
user: "林晚",
|
|
char: "长夜行",
|
|
});
|
|
expect(r.text).toBe("林晚 与 长夜行");
|
|
});
|
|
|
|
it("{{char}} falls back to 作品", () => {
|
|
expect(expandMacros("{{char}}").text).toBe("作品");
|
|
});
|
|
|
|
it("{{persona}} expands to the persona description", () => {
|
|
expect(expandMacros("{{persona}}", { persona: "二十岁,缄默" }).text).toBe(
|
|
"二十岁,缄默",
|
|
);
|
|
// 没设人设 → 空串(与酒馆一致)
|
|
expect(expandMacros("[{{persona}}]").text).toBe("[]");
|
|
});
|
|
|
|
it("treats {{persona}} as a known macro in the import report", () => {
|
|
expect(collectMacroNames("{{persona}} {{charPrompt}}")).toContain(
|
|
"{{persona}}",
|
|
);
|
|
});
|
|
|
|
it("setvar then getvar in the same scope", () => {
|
|
const vars = createMacroVars();
|
|
expect(expandMacros("{{setvar::a::你好}}", { vars }).text).toBe("");
|
|
expect(expandMacros("{{getvar::a}}!", { vars }).text).toBe("你好!");
|
|
});
|
|
|
|
it("getvar on unset variable is empty", () => {
|
|
expect(expandMacros("[{{getvar::nope}}]").text).toBe("[]");
|
|
});
|
|
|
|
it("keeps whitespace inside setvar values", () => {
|
|
const vars = createMacroVars();
|
|
expandMacros("{{setvar::set1:: }}", { vars });
|
|
expect(vars.get("set1")).toBe(" ");
|
|
});
|
|
|
|
it("addvar appends", () => {
|
|
const vars = createMacroVars();
|
|
expandMacros("{{setvar::k::a}}{{addvar::k::b}}", { vars });
|
|
expect(vars.get("k")).toBe("ab");
|
|
});
|
|
|
|
it("{{roll N}} yields an integer in range", () => {
|
|
for (let i = 0; i < 20; i++) {
|
|
const value = Number(expandMacros("{{roll 60}}").text);
|
|
expect(Number.isInteger(value)).toBe(true);
|
|
expect(value).toBeGreaterThanOrEqual(1);
|
|
expect(value).toBeLessThanOrEqual(60);
|
|
}
|
|
});
|
|
|
|
it("{{roll:1d6}} and {{roll::d9}} roll dice", () => {
|
|
for (let i = 0; i < 20; i++) {
|
|
const six = Number(expandMacros("{{roll:1d6}}").text);
|
|
expect(six).toBeGreaterThanOrEqual(1);
|
|
expect(six).toBeLessThanOrEqual(6);
|
|
const nine = Number(expandMacros("{{roll::d9}}").text);
|
|
expect(nine).toBeGreaterThanOrEqual(1);
|
|
expect(nine).toBeLessThanOrEqual(9);
|
|
}
|
|
});
|
|
|
|
it("does not roll when allowDice is false", () => {
|
|
expect(expandMacros("{{roll 60}}", { allowDice: false }).text).toBe(
|
|
"{{roll 60}}",
|
|
);
|
|
});
|
|
|
|
it("leaves unknown macros untouched", () => {
|
|
const r = expandMacros("{{turnPlus1}} {{MEMORY_TABLE}} {{ }}");
|
|
expect(r.text).toBe("{{turnPlus1}} {{MEMORY_TABLE}} {{ }}");
|
|
expect(r.hits).toEqual([]);
|
|
});
|
|
|
|
it("collectMacroNames reports unknown macros by raw text", () => {
|
|
const names = collectMacroNames(
|
|
"{{// c}}{{user}}{{setvar::a::b}}{{turnPlus1}}{{roll 9}}",
|
|
);
|
|
expect(names).toContain("{{comment}}");
|
|
expect(names).toContain("{{user}}");
|
|
expect(names).toContain("{{setvar}}");
|
|
expect(names).toContain("{{roll}}");
|
|
expect(names.some((n) => n.includes("turnPlus1"))).toBe(true);
|
|
});
|
|
});
|
|
|
|
function presetWith(prompts: Array<{ id: string; content: string }>): PresetPackage {
|
|
return {
|
|
id: "p",
|
|
name: "p",
|
|
source: "sillytavern",
|
|
prompts: prompts.map((p) => ({
|
|
id: p.id,
|
|
name: p.id,
|
|
enabled: true,
|
|
role: "system" as const,
|
|
content: p.content,
|
|
marker: false,
|
|
sourceIdentifier: p.id,
|
|
})),
|
|
promptOrder: prompts.map((p, i) => ({
|
|
promptId: p.id,
|
|
enabled: true,
|
|
orderIndex: i,
|
|
})),
|
|
generation: {},
|
|
unsupported: [],
|
|
importedAt: "2026-01-01T00:00:00.000Z",
|
|
};
|
|
}
|
|
|
|
describe("preset assembly macro scope", () => {
|
|
it("setvar in an earlier entry is visible to getvar in a later entry", () => {
|
|
const preset = presetWith([
|
|
{ id: "a", content: "{{setvar::set1:: }}{{// 预设说明 }}" },
|
|
{ id: "b", content: "规则{{getvar::set1}}结束" },
|
|
]);
|
|
|
|
const vars = createMacroVars();
|
|
const messages = assemblePresetMessages(
|
|
preset,
|
|
() => null,
|
|
(content) => expandPresetDirectives(content, { vars, user: "阿泽" }).text,
|
|
);
|
|
|
|
expect(messages.map((m) => m.content)).toEqual(["规则 结束"]);
|
|
});
|
|
|
|
it("expands {{char}} from assembly context", () => {
|
|
const preset = presetWith([{ id: "a", content: "<{{char}}>正文</{{char}}>" }]);
|
|
const messages = assemblePresetMessages(
|
|
preset,
|
|
() => null,
|
|
(content) => expandPresetDirectives(content, { charName: "长夜行" }).text,
|
|
);
|
|
expect(messages[0]?.content).toBe("<长夜行>正文</长夜行>");
|
|
});
|
|
});
|
|
|
|
describe("{{// 注释 }} 配平", () => {
|
|
it("注释里嵌套宏不会漏出尾巴", () => {
|
|
const text = expandMacros("{{// 注释:把 {{user}} 换成别的\n}}|用户:{{user}}|", {
|
|
user: "User",
|
|
}).text;
|
|
expect(text).toBe("|用户:User|");
|
|
});
|
|
|
|
it("普通注释照旧吃掉", () => {
|
|
expect(expandMacros("A{{// 说明 }}B", {}).text).toBe("AB");
|
|
});
|
|
});
|