/** 上下文构建用例(任务 A3.8):预算、折叠、追溯、钉住引用。 */ import assert from 'node:assert/strict'; import { test } from 'node:test'; import { buildContext } from '@aps/server/app/context-builder'; const message = (id, role, text) => ({ messageId: id, role, ts: 1, text }); test('历史很短时全部保留,不产生摘要', () => { const messages = [message('m1', 'user', '你好'), message('m2', 'assistant', '你好,有什么可以帮你')]; const result = buildContext({ messages }); assert.ok(result.prefix.includes('【最近的对话】')); assert.deepEqual(result.includedMessageIds, ['m1', 'm2']); assert.deepEqual(result.droppedMessageIds, []); assert.equal(result.summary, ''); }); test('空历史不注入任何前缀', () => { assert.equal(buildContext({ messages: [] }).prefix, ''); }); test('超预算时老消息折叠进摘要,且能追到原始消息 id', () => { const messages = []; for (let index = 0; index < 40; index += 1) { messages.push(message(`m${index}`, index % 2 === 0 ? 'user' : 'assistant', `第 ${index} 条消息,`.repeat(20))); } const result = buildContext({ messages, budget: { maxChars: 1200, recentMessages: 4, summaryMaxChars: 800 } }); assert.ok(result.droppedMessageIds.length > 0, '应有消息被折叠'); assert.equal(result.summarySourceIds.length, result.droppedMessageIds.length, '摘要来源应与折叠集合一致'); assert.ok(result.summary.length > 0); // 摘要里逐条带来源 id,任何结论都能追回原文 for (const id of result.summarySourceIds.slice(0, 3)) { assert.ok(result.summary.includes(id), `摘要应包含来源 ${id}`); } // 最近若干轮必须原样保留 const lastIds = messages.slice(-4).map((item) => item.messageId); for (const id of lastIds) assert.ok(result.includedMessageIds.includes(id), `${id} 应保留`); }); test('工具执行只以引用进摘要,不进历史原文', () => { const messages = Array.from({ length: 30 }, (_, index) => message(`m${index}`, index % 2 === 0 ? 'user' : 'assistant', `内容 ${index} `.repeat(40)), ); const result = buildContext({ messages, executions: [ { capabilityId: 'fake.echo', status: 'succeeded', executionId: 'exec-123', paramsDigest: 'sha256:abc', createdAt: 1 }, ], budget: { maxChars: 900, recentMessages: 2, summaryMaxChars: 600 }, }); assert.ok(result.prefix.includes('【相关工具执行(只给引用,需要细节请调对应能力)】'), result.prefix.slice(0, 200)); assert.ok(result.prefix.includes('exec-123'), '应给出执行号引用'); assert.ok(result.prefix.includes('sha256:abc'), '应给出参数摘要'); assert.ok(!result.prefix.includes('"echo"'), '不应把工具结果原文塞进上下文'); }); test('钉住的消息即使很老也保留', () => { const messages = Array.from({ length: 30 }, (_, index) => message(`m${index}`, index % 2 === 0 ? 'user' : 'assistant', `内容 ${index} `.repeat(40)), ); const result = buildContext({ messages, pinnedMessageIds: ['m0'], budget: { maxChars: 800, recentMessages: 2, summaryMaxChars: 400 } }); assert.ok(result.includedMessageIds.includes('m0'), '钉住的消息必须保留'); assert.ok(!result.droppedMessageIds.includes('m0')); });