import { createUserMessage } from '@deepseek-ai/dsh-llm' import { afterEach, describe, expect, it } from 'vitest' import { Context } from '@deepseek-ai/cordis' import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import SessionStore, { SessionId } from '@deepseek-ai/dsh-session' import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl' import SessionPersistenceSqlite from '@deepseek-ai/dsh-session-persistence-sqlite' import SessionTitleService, { foldSessionTitle } from '@deepseek-ai/dsh-session-title' const CONFIG = { fallbackMaxWords: 5, fallbackMaxBytes: 40, maxTitleBytes: 80, } as const const roots: string[] = [] afterEach(async () => { for (const root of roots.splice(0)) await rm(root, { recursive: true, force: true }) }) async function appendPersistedTitle(ctx: Context, id: ReturnType): Promise { const session = ctx.sessions.create(id) session.append('turn/start', { turn: 1, }) session.append('user/message', createUserMessage({ content: [{ type: 'text', text: 'Persist this session title' }], source: { kind: 'user' }, }), { surfaceOp: 'append' }) session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) await ctx.sessionTitle.refresh(session) } async function expectPersistedTitle(ctx: Context, id: ReturnType): Promise { const loaded = await ctx.sessionPersistence.load(id) expect(foldSessionTitle(loaded.events)).toMatchObject({ title: 'Persist this session title', messageSeqs: [1], source: { kind: 'fallback' }, eventSeq: 3, }) expect(loaded.events.map(event => event.type)).toEqual([ 'turn/start', 'user/message', 'turn/end', 'session/title', ]) } describe('session title persistence round trips', () => { it('round-trips through a remounted JSONL backend', async () => { const root = await mkdtemp(join(tmpdir(), 'dsh-title-jsonl-')) roots.push(root) const id = SessionId('title-jsonl') const writer = new Context() await writer.plugin(SessionStore) await writer.plugin(SessionPersistenceJsonl, { root, compression: 'none' }) await writer.plugin(SessionTitleService, CONFIG) await appendPersistedTitle(writer, id) await writer.fiber.dispose() const reader = new Context() await reader.plugin(SessionStore) await reader.plugin(SessionPersistenceJsonl, { root, compression: 'none' }) await expectPersistedTitle(reader, id) await reader.fiber.dispose() }) it('round-trips through a remounted SQLite backend', async () => { const root = await mkdtemp(join(tmpdir(), 'dsh-title-sqlite-')) roots.push(root) const path = join(root, 'sessions.db') const id = SessionId('title-sqlite') const writer = new Context() await writer.plugin(SessionStore) await writer.plugin(SessionPersistenceSqlite, { path }) await writer.plugin(SessionTitleService, CONFIG) await appendPersistedTitle(writer, id) await writer.fiber.dispose() const reader = new Context() await reader.plugin(SessionStore) await reader.plugin(SessionPersistenceSqlite, { path }) await expectPersistedTitle(reader, id) await reader.fiber.dispose() }) })