import assert from "node:assert/strict"; import test from "node:test"; import { GENERAL_NO_BIRTH_TIME_REFUSAL, guardGeneralNoBirthTimeOutput, guardPreciseTimingOutput, } from "../src/lib/timing-output-guard.ts"; import { streamTextResponse } from "../src/lib/stream-text-response.ts"; import { parseAgentReply } from "../src/lib/agent-reply.ts"; import { createBirthTimeModeOutputGuard } from "../src/lib/consultation-birth-time-mode.ts"; test("removes exact dates and months when precise timing is blocked", () => { const guarded = guardPreciseTimingOutput( "你会在2027年3月15日结婚,事业将在11月转折。", ); assert.doesNotMatch(guarded, /2027年3月15日|11月/); assert.match(guarded, /具体时间已省略/); }); test("removes guarantee conclusions when the evidence contract is incomplete", () => { const guarded = guardPreciseTimingOutput("我保证你一定会升职。"); assert.doesNotMatch(guarded, /保证你一定会升职/); assert.match(guarded, /保证性结论已省略/); assert.equal( guardPreciseTimingOutput("You will definitely get promoted."), "[保证性结论已省略].", ); }); test("guards a date that crosses streamed chunks", async () => { async function* reply() { yield "应期是2027年"; yield "3月15日,但我保证你一定会升职。"; } const response = streamTextResponse(reply(), { mode: "mastra", requestId: "00000000-0000-4000-8000-000000000098", transformText: guardPreciseTimingOutput, }); const text = await response.text(); assert.doesNotMatch(text, /2027年3月15日|保证你一定会升职/); assert.match(text, /保证性结论已省略/); }); test("guards only visible prose and preserves AYANAM blocks across arbitrary chunk boundaries", async () => { const suggestions = ''; const title = ""; const longVisiblePrefix = "一般知识不依赖个人星盘。".repeat(100); async function* reply() { yield `${longVisiblePrefix}正文说你将在2027年`; yield "3月15日一定会升职。\n\n"; } const response = streamTextResponse(reply(), { mode: "mastra", requestId: "00000000-0000-4000-8000-000000000099", transformText: createBirthTimeModeOutputGuard("general_no_birth_time", false), }); const text = await response.text(); const parsed = parseAgentReply(text); assert.doesNotMatch(text, /2027年3月15日|正文说你将在.*一定会升职/); assert.match(text, /^一般知识不依赖个人星盘/); assert.match(text, /具体时间已省略|保证性结论已省略/); assert.equal(text.includes(suggestions), true); assert.equal(text.includes(title), true); assert.doesNotMatch(text, / { const unsafeClaims = [ "你的七宫落入摩羯。", "盘面显示你的事业宫很强。", "你的金星落第七宫。", "你的上升落在巨蟹座。", "你的 D9 显示婚姻会晚一些。", "Your Venus is in the 7th house.", "Your ascendant falls in Cancer.", "Your D9 chart shows a strong marriage house.", ]; for (const claim of unsafeClaims) { const guarded = guardGeneralNoBirthTimeOutput(claim); assert.equal(guarded.includes(GENERAL_NO_BIRTH_TIME_REFUSAL), true, claim); assert.equal(guarded.includes(claim.replace(/[。.]$/, "")), false, claim); } assert.equal( guardGeneralNoBirthTimeOutput("第七宫在占星概念中常与关系相关。"), "第七宫在占星概念中常与关系相关。", ); assert.equal( guardGeneralNoBirthTimeOutput("Venus is generally associated with relating and values."), "Venus is generally associated with relating and values.", ); assert.equal( guardGeneralNoBirthTimeOutput("你问的第七宫,在占星概念中常与关系相关。"), "你问的第七宫,在占星概念中常与关系相关。", ); }); test("general mode rejects personalized chart predicates beyond possessive placements", () => { const unsafeClaims = [ "您的上升星座为巨蟹。", "你是巨蟹上升。", "您有一个巨蟹上升。", "You have Cancer rising.", "You are Cancer rising.", "You have a Cancer ascendant.", "你拥有巨蟹上升。", "In your chart, Venus occupies the seventh house.", "For you, Venus falls in the seventh house.", ]; for (const claim of unsafeClaims) { const guarded = guardGeneralNoBirthTimeOutput(claim); const punctuation = claim.endsWith("。") ? "。" : "."; assert.equal(guarded, `${GENERAL_NO_BIRTH_TIME_REFUSAL}${punctuation}`, claim); } }); test("general mode preserves educational chart questions and general knowledge", () => { const safeStatements = [ "你的问题是第七宫的一般含义。", "你问的是第七宫的一般含义。", "你问的第七宫在占星概念中常与关系相关。", "关于你的第七宫的一般含义。", "你是一个正在研究第七宫的学生。", "你可以先了解第七宫的一般概念。", "你有第七宫的一般问题。", "你有金星方面的问题,可以继续问。", "你是第七宫问题的提问者。", "第七宫在占星概念中常与关系相关。", "Your question is the general meaning of the seventh house.", "You asked about the seventh house.", "You have a question about the seventh house.", "The seventh house is associated with relationships.", ]; for (const statement of safeStatements) { assert.equal(guardGeneralNoBirthTimeOutput(statement), statement); } }); test("hidden AYANAM comments cannot split a personalized claim around the guard", async () => { const title = ""; const suggestions = ''; async function* reply() { yield "一般知识可以说明概念。你的金星落"; yield "第七宫。\n'; } const response = streamTextResponse(reply(), { mode: "mastra", requestId: "00000000-0000-4000-8000-000000000097", transformText: createBirthTimeModeOutputGuard("general_no_birth_time", false), }); const text = await response.text(); const parsed = parseAgentReply(text); assert.match(text, /一般知识可以说明概念/); assert.match(text, new RegExp(GENERAL_NO_BIRTH_TIME_REFUSAL)); assert.doesNotMatch(text, /你的\s*金星落第七宫/); assert.equal(text.includes(title), true); assert.equal(text.includes(suggestions), true); assert.equal(parsed.title, "一般占星咨询"); assert.doesNotMatch(parsed.text, /AYANAM_SUGGESTIONS|了解第七宫的一般概念/); });