Bonsai-Chat-WebGPU / src /lib /conversations.test.ts
WaveCut's picture
Persist chat tools and stream artifact drafts
179a355 verified
Raw
History Blame Contribute Delete
4.35 kB
import { describe, expect, it } from 'vitest';
import type { ChatMessage } from './contracts';
import {
normalizeConversationToolsEnabled,
normalizeConversationToolEvents,
planMessageRegeneration,
planUserMessageEdit,
} from './conversations';
const messages: ChatMessage[] = [
{ id: 'u1', role: 'user', content: 'First', timestamp: '10:00' },
{ id: 'a1', role: 'assistant', content: 'First answer', timestamp: '10:01' },
{ id: 'u2', role: 'user', content: 'Second', timestamp: '10:02' },
{ id: 'a2', role: 'assistant', content: 'Second answer', timestamp: '10:03' },
{ id: 'u3', role: 'user', content: 'Third', timestamp: '10:04' },
{ id: 'a3', role: 'assistant', content: 'Third answer', timestamp: '10:05' },
];
describe('planMessageRegeneration', () => {
it('keeps the selected user message and truncates everything after it', () => {
expect(planMessageRegeneration(messages, 'u2')).toEqual({
baseMessages: messages.slice(0, 2),
userMessage: messages[2],
discardedMessageIds: ['a2', 'u3', 'a3'],
});
});
it('replaces the selected assistant answer from its preceding user turn', () => {
expect(planMessageRegeneration(messages, 'a2')).toEqual({
baseMessages: messages.slice(0, 2),
userMessage: messages[2],
discardedMessageIds: ['a2', 'u3', 'a3'],
});
});
it('does not regenerate unknown or system-only turns', () => {
expect(planMessageRegeneration(messages, 'missing')).toBeNull();
expect(planMessageRegeneration([
{ id: 'system', role: 'system', content: 'Rules', timestamp: '10:00' },
], 'system')).toBeNull();
});
});
describe('planUserMessageEdit', () => {
it('keeps earlier history, edits the selected user turn, and discards everything after it', () => {
expect(planUserMessageEdit(messages, 'u2', ' Revised second ', 123)).toEqual({
baseMessages: messages.slice(0, 2),
userMessage: { ...messages[2], content: 'Revised second', editedAt: 123 },
discardedMessageIds: ['a2', 'u3', 'a3'],
});
});
it('rejects assistant, missing, and empty edits', () => {
expect(planUserMessageEdit(messages, 'a2', 'No')).toBeNull();
expect(planUserMessageEdit(messages, 'missing', 'No')).toBeNull();
expect(planUserMessageEdit(messages, 'u2', ' ')).toBeNull();
});
});
describe('normalizeConversationToolEvents', () => {
it('migrates old conversations by rebuilding the log from persisted tool runs', () => {
const withTools: ChatMessage[] = [{
id: 'assistant',
role: 'assistant',
content: 'Done',
timestamp: '10:01',
tools: [
{ id: 'complete', name: 'artifact_deploy', input: '{}', output: '{"ok":true}', state: 'complete' },
{ id: 'interrupted', name: 'artifact_write', input: '{}', state: 'running' },
],
}];
expect(normalizeConversationToolEvents(undefined, withTools)).toEqual([
{
id: 'complete',
timestamp: '10:01',
label: 'artifact_deploy',
detail: 'local result restored from chat history',
state: 'complete',
},
{
id: 'interrupted',
timestamp: '10:01',
label: 'artifact_write',
detail: 'interrupted before completion',
state: 'error',
},
]);
});
it('prefers saved live-event details for a known tool call', () => {
const withTools: ChatMessage[] = [{
id: 'assistant',
role: 'assistant',
content: '',
timestamp: '10:01',
tools: [{ id: 'call', name: 'artifact_test', input: '{}', output: '{}', state: 'complete' }],
}];
expect(normalizeConversationToolEvents([{
id: 'call',
timestamp: '10:01:01.500',
label: 'artifact_test',
detail: 'local result returned to model',
state: 'complete',
}], withTools)).toEqual([{
id: 'call',
timestamp: '10:01:01.500',
label: 'artifact_test',
detail: 'local result returned to model',
state: 'complete',
}]);
});
});
describe('normalizeConversationToolsEnabled', () => {
it('restores an enabled chat and keeps older records safely disabled', () => {
expect(normalizeConversationToolsEnabled(true)).toBe(true);
expect(normalizeConversationToolsEnabled(false)).toBe(false);
expect(normalizeConversationToolsEnabled(undefined)).toBe(false);
});
});