/** * 用户上下文前置块测试(v0.7.3 P1-1) * * 锁定三类动态内容(日期时间 / 记忆 / 附件提示)在用户消息前置块的 * 分组结构与空值收缩行为 —— 它们从 system prompt 迁出的契约面。 */ import { describe, it, expect } from 'vitest'; import { buildUserContextPrefix, withUserContextPrefix } from '../user-context'; describe('buildUserContextPrefix', () => { it('恒含头部说明与日期时间分区(唯一无条件分区)', () => { const prefix = buildUserContextPrefix({ now: Date.UTC(2026, 7, 30, 6, 30) }); expect(prefix).toContain('[Contextual information for this message'); expect(prefix).toContain('## Current Date & Time'); }); it('无记忆/附件时不产出对应分区(空值收缩)', () => { const prefix = buildUserContextPrefix({ now: Date.now() }); expect(prefix).not.toContain('## Relevant Memories (Retrieved)'); expect(prefix).not.toContain('## User Attachments (Direct Upload)'); }); it('记忆分区:条目格式与截断口径(沿用原 system 注入契约)', () => { const prefix = buildUserContextPrefix({ now: Date.now(), memories: [ { id: 'm1', type: 'semantic', content: 'x'.repeat(500), source: 'agent_thought', importance: 0.9, score: 0.8, createdAt: Date.now(), }, ], }); expect(prefix).toContain('## Relevant Memories (Retrieved)'); expect(prefix).toMatch(/\[1\] \(semantic, 重要度: 0\.9\)/); // 内容截断到 200 字符 expect(prefix).toContain('x'.repeat(200)); expect(prefix).not.toContain('x'.repeat(201)); }); it('附件分区:图片提示禁止重复读图;文本截断标记透传', () => { const prefix = buildUserContextPrefix({ now: Date.now(), attachments: [ { name: 'shot.png', type: 'image' }, { name: 'big.log', type: 'text', truncated: true }, ], }); expect(prefix).toContain('## User Attachments (Direct Upload)'); expect(prefix).toContain('1. [image] shot.png'); expect(prefix).toContain('do NOT call view_image'); expect(prefix).toContain('2. [text file] big.log'); expect(prefix).toContain('TRUNCATED — only the first 512KB is included'); }); it('分区以 --- 分隔且以前缀分隔符收尾(调用方可直接拼接用户内容)', () => { const prefix = buildUserContextPrefix({ now: Date.now(), memories: [ { id: 'm', type: 'episodic', content: 'c', source: 'user_input', importance: 0.5, score: 0.5, createdAt: Date.now(), }, ], attachments: [{ name: 'a.txt', type: 'text' }], }); expect(prefix).toMatch(/---\s*$/); // 三个分区恰好两个内部 --- + 收尾 1 个 ---(共 3 个独立行) expect(prefix.match(/^---$/gm)?.length ?? 0).toBe(3); }); }); describe('withUserContextPrefix', () => { it('前置块与用户内容拼接(前置块自带收尾分隔符)', () => { const prefix = buildUserContextPrefix({ now: Date.now() }); const out = withUserContextPrefix(prefix, '你好,帮我写个脚本'); expect(out.startsWith(prefix)).toBe(true); expect(out.endsWith('你好,帮我写个脚本')).toBe(true); }); it('空前缀原样返回(契约防御)', () => { expect(withUserContextPrefix('', 'hello')).toBe('hello'); }); });