import { describe, expect, it } from 'vitest'; import { InMemoryMemoryStore, Persona, type ReplyGenerationInput } from '../src'; describe('Conversation API', () => { it('loads mandatory memory, schedules, availability, and formatted history before replying', async () => { const memory = new InMemoryMemoryStore(); let captured: ReplyGenerationInput | undefined; const persona = new Persona('Mina', 'Mina likes quiet cafes.', { memory, now: '2026-05-01T10:00:00.000Z', models: { conversation: { async generateReply(input) { captured = input; return { messages: ['카페에 있었어.', '너는 뭐해?'] }; }, }, schedule: { async generateDailySchedule() { return [ { startTime: '09:00', endTime: '18:00', activity: 'work', title: 'Work', availabilityMode: 'do-not-disturb' as const }, ]; }, async generateMonthlySchedule() { return []; }, }, }, }); const space = await persona.ready(); await memory.addFact(space.id, { statement: 'The user is close to Mina.', topics: ['user', 'persona'] }); await persona.createDailySchedule('2026-05-01T10:00:00.000Z', 'study for an exam'); const reply = await persona.sendMessage({ datetime: '2026-05-01T12:00:00.000Z', messageHistory: [ { sender: 'persona', time: '2026-04-30T23:00:00.000Z', content: '다음에 보자' }, { sender: 'user', time: '2026-05-01T12:00:00.000Z', content: '지금 뭐해?' }, ], }); expect(reply.messages).toEqual(['카페에 있었어.', '너는 뭐해?']); expect(captured?.context.formattedMessageHistory).toContain('Mina@2026-04-30T23:00:00.000Z: 다음에 보자'); expect(captured?.context.formattedMessageHistory).toContain('user@2026-05-01T12:00:00.000Z: 지금 뭐해?'); expect(captured?.context.memorySummary).toContain('The user is close to Mina.'); expect(captured?.context.scheduleEntries.length).toBeGreaterThan(0); expect(captured?.context.availability.ranges.length).toBeGreaterThan(0); expect(captured?.instruction).toContain('send_message'); }); it('explicitly tells the response model when mandatory memory is missing', async () => { const memory = new InMemoryMemoryStore(); let memorySummary = ''; const persona = new Persona('Mina', 'Mina is new.', { memory, now: '2026-05-01T10:00:00.000Z', models: { initialization: { async extractInitialFacts() { return []; } }, conversation: { async generateReply(input) { memorySummary = input.context.memorySummary; return { messages: ['잘 모르겠어.'] }; }, }, }, }); await persona.ready(); await persona.sendMessage({ datetime: '2026-05-01T12:00:00.000Z', messageHistory: [{ sender: 'user', time: '2026-05-01T12:00:00.000Z', content: '나 기억해?' }], }); expect(memorySummary).toBe('기억이 없음'); }); it('lets a rewrite model discard a stale draft when new user messages arrive', async () => { const memory = new InMemoryMemoryStore(); const persona = new Persona('Mina', 'Mina is concise.', { memory, now: '2026-05-01T10:00:00.000Z', models: { conversation: { async generateReply() { return { messages: ['첫 답장'] }; } }, rewrite: { async decide() { return { rewrite: true, draft: { messages: ['새 메시지까지 보고 답장'] }, reason: 'latest user message changes intent' }; }, }, }, }); await persona.ready(); const reply = await persona.sendMessage({ datetime: '2026-05-01T12:00:00.000Z', messageHistory: [{ sender: 'user', time: '2026-05-01T12:00:00.000Z', content: '안녕' }], getLatestMessageHistory: async () => [ { sender: 'user', time: '2026-05-01T12:00:00.000Z', content: '안녕' }, { sender: 'user', time: '2026-05-01T12:00:05.000Z', content: '아 맞다, 지금 바빠?' }, ], }); expect(reply.messages).toEqual(['새 메시지까지 보고 답장']); }); it('can proactively start a conversation with the same mandatory context pipeline', async () => { const memory = new InMemoryMemoryStore(); let mode = ''; const persona = new Persona('Mina', 'Mina starts soft conversations.', { memory, now: '2026-05-01T10:00:00.000Z', models: { conversation: { async generateReply(input) { mode = input.mode; return { messages: ['오늘 좀 조용하네.'] }; }, }, }, }); await persona.ready(); const started = await persona.startConversation({ datetime: '2026-05-01T20:00:00.000Z', messageHistory: [] }); expect(mode).toBe('start-conversation'); expect(started.messages).toEqual(['오늘 좀 조용하네.']); }); });