import { afterEach, describe, expect, it } from 'vitest'; import { IdentityDB } from 'identitydb'; import { initializePersona, type PersonaBiographyResult, type PersonaFactExtractionResult } from '../src'; import type { ImageModelAdapter, StructuredModelAdapter } from '../src'; const openDbs: IdentityDB[] = []; async function createDb() { const db = await IdentityDB.connect({ client: 'sqlite', filename: ':memory:' }); await db.initialize(); openDbs.push(db); return db; } afterEach(async () => { while (openDbs.length > 0) { await openDbs.pop()!.close(); } }); describe('initializePersona', () => { it('generates a biography, extracts facts, and stores them in the persona space', async () => { const db = await createDb(); const prompts: string[] = []; const structured = createStructuredAdapter(prompts); const persona = await initializePersona(db, { id: 'minji', displayName: 'Minji', personality: 'Quiet, playful, observant.', history: 'Grew up near Busan and studied design.', values: ['loyalty', 'creative honesty'], likes: ['quiet cafés'], dislikes: ['loud arguments'], relationships: [{ name: 'Haru', relationship: 'best friend', description: 'childhood friend' }], currentDate: '2026-05-11', structuredModel: structured, }); expect(persona).toMatchObject({ id: 'minji', displayName: 'Minji', spaceName: 'persona:minji', biography: 'Minji grew up near the sea and values loyal friendships.', }); expect(prompts[0]).toContain('Quiet, playful, observant.'); expect(prompts[0]).toContain('creative honesty'); expect(prompts[0]).toContain('Haru'); expect(prompts[0]).toContain('Current date: 2026-05-11'); expect(prompts[1]).toContain('Minji grew up near the sea'); const facts = await db.getTopicFacts('Minji', { spaceName: persona.spaceName }); expect(facts.map((fact) => fact.statement)).toContain('Minji grew up near the sea.'); }); it('does not call the image adapter unless a profile image is requested', async () => { const db = await createDb(); const structured = createStructuredAdapter([]); let imageCalls = 0; const imageModel: ImageModelAdapter = { provider: 'fake-image', model: 'fake-image-model', async generateImage() { imageCalls += 1; return { url: 'https://example.test/minji.png' }; }, }; const persona = await initializePersona(db, { id: 'minji', displayName: 'Minji', personality: 'Quiet.', structuredModel: structured, imageModel, }); expect(persona.profileImageUrl).toBeUndefined(); expect(imageCalls).toBe(0); }); it('can generate and store a persona profile image when requested', async () => { const db = await createDb(); const structured = createStructuredAdapter([]); let imagePrompt = ''; const imageModel: ImageModelAdapter = { provider: 'fake-image', model: 'fake-image-model', async generateImage(request) { imagePrompt = request.prompt; return { url: 'https://example.test/minji.png' }; }, }; const persona = await initializePersona(db, { id: 'minji', displayName: 'Minji', personality: 'Quiet.', structuredModel: structured, imageModel, generateProfileImage: true, }); expect(imagePrompt).toContain('Minji'); expect(persona.profileImageUrl).toBe('https://example.test/minji.png'); const facts = await db.getTopicFacts('profile image', { spaceName: persona.spaceName }); expect(facts.map((fact) => fact.statement)).toContain('Minji has a generated profile image at https://example.test/minji.png.'); }); it('generates unique default persona spaces when no id is supplied', async () => { const db = await createDb(); const first = await initializePersona(db, { displayName: 'Minji', personality: 'Quiet.', structuredModel: createStructuredAdapter([]), }); const second = await initializePersona(db, { displayName: 'Minji', personality: 'Quiet.', structuredModel: createStructuredAdapter([]), }); expect(first.id).not.toBe(second.id); expect(first.spaceName).not.toBe(second.spaceName); }); it('rejects accidental reuse of an existing persona space by default', async () => { const db = await createDb(); await initializePersona(db, { id: 'minji', displayName: 'Minji', personality: 'Quiet.', structuredModel: createStructuredAdapter([]), }); await expect( initializePersona(db, { id: 'minji', displayName: 'Minji Again', personality: 'Different.', structuredModel: createStructuredAdapter([]), }), ).rejects.toThrow(/already exists/); }); it('allows explicit reuse of an existing persona space', async () => { const db = await createDb(); await initializePersona(db, { id: 'minji', displayName: 'Minji', personality: 'Quiet.', structuredModel: createStructuredAdapter([]), }); const persona = await initializePersona(db, { id: 'minji', displayName: 'Minji', personality: 'Quiet.', structuredModel: createStructuredAdapter([]), reuseExistingSpace: true, }); expect(persona.spaceName).toBe('persona:minji'); }); it('rejects malformed structured biography output', async () => { const db = await createDb(); const malformed: StructuredModelAdapter = { provider: 'fake-structured', model: 'fake-structured-model', async generateObject(): Promise { return { biography: '' } as TOutput; }, }; await expect( initializePersona(db, { id: 'broken', displayName: 'Broken', personality: 'Quiet.', structuredModel: malformed, }), ).rejects.toThrow(/biography/); expect(await db.getSpaceByName('persona:broken')).toBeNull(); }); it('rejects malformed extracted fact topics before creating a space', async () => { const db = await createDb(); const malformedFacts: StructuredModelAdapter = { provider: 'fake-structured', model: 'fake-structured-model', async generateObject(request: { prompt: string }): Promise { if (request.prompt.includes('Create a concrete')) { return { biography: 'Broken has a biography.' } as TOutput; } return { facts: [ { statement: 'Broken has a malformed topic.', topics: [{}], }, ], } as TOutput; }, }; await expect( initializePersona(db, { id: 'broken-topic', displayName: 'Broken Topic', personality: 'Quiet.', structuredModel: malformedFacts, }), ).rejects.toThrow(/topic name/); expect(await db.getSpaceByName('persona:broken-topic')).toBeNull(); }); it('requires an image model when profile image generation is requested', async () => { const db = await createDb(); await expect( initializePersona(db, { id: 'minji', displayName: 'Minji', personality: 'Quiet.', structuredModel: createStructuredAdapter([]), generateProfileImage: true, }), ).rejects.toThrow(/imageModel/); }); }); function createStructuredAdapter(prompts: string[]): StructuredModelAdapter { let call = 0; return { provider: 'fake-structured', model: 'fake-structured-model', async generateObject(request: { prompt: string }): Promise { prompts.push(request.prompt); call += 1; if (call === 1) { return { biography: 'Minji grew up near the sea and values loyal friendships.', } satisfies PersonaBiographyResult as TOutput; } return { facts: [ { statement: 'Minji grew up near the sea.', topics: [{ name: 'Minji' }, { name: 'sea' }], }, { statement: 'Minji values loyal friendships.', topics: [{ name: 'Minji' }, { name: 'loyal friendships' }], }, ], } satisfies PersonaFactExtractionResult as TOutput; }, }; }