153 lines
5 KiB
TypeScript
153 lines
5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { adaptOllamaMessage } from './ollama-messages';
|
|
|
|
describe('adaptOllamaMessage', () => {
|
|
describe('system init', () => {
|
|
it('maps to init message', () => {
|
|
const result = adaptOllamaMessage({
|
|
type: 'system',
|
|
subtype: 'init',
|
|
session_id: 'sess-123',
|
|
model: 'qwen3:8b',
|
|
cwd: '/home/user/project',
|
|
});
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].type).toBe('init');
|
|
const content = result[0].content as any;
|
|
expect(content.sessionId).toBe('sess-123');
|
|
expect(content.model).toBe('qwen3:8b');
|
|
expect(content.cwd).toBe('/home/user/project');
|
|
});
|
|
});
|
|
|
|
describe('system status', () => {
|
|
it('maps non-init subtypes to status message', () => {
|
|
const result = adaptOllamaMessage({
|
|
type: 'system',
|
|
subtype: 'model_loaded',
|
|
status: 'Model loaded successfully',
|
|
});
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].type).toBe('status');
|
|
expect((result[0].content as any).subtype).toBe('model_loaded');
|
|
expect((result[0].content as any).message).toBe('Model loaded successfully');
|
|
});
|
|
});
|
|
|
|
describe('chunk — text content', () => {
|
|
it('maps streaming text to text message', () => {
|
|
const result = adaptOllamaMessage({
|
|
type: 'chunk',
|
|
message: { role: 'assistant', content: 'Hello world' },
|
|
done: false,
|
|
});
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].type).toBe('text');
|
|
expect((result[0].content as any).text).toBe('Hello world');
|
|
});
|
|
|
|
it('ignores empty content', () => {
|
|
const result = adaptOllamaMessage({
|
|
type: 'chunk',
|
|
message: { role: 'assistant', content: '' },
|
|
done: false,
|
|
});
|
|
expect(result).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe('chunk — thinking content', () => {
|
|
it('maps thinking field to thinking message', () => {
|
|
const result = adaptOllamaMessage({
|
|
type: 'chunk',
|
|
message: { role: 'assistant', content: '', thinking: 'Let me reason about this...' },
|
|
done: false,
|
|
});
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].type).toBe('thinking');
|
|
expect((result[0].content as any).text).toBe('Let me reason about this...');
|
|
});
|
|
|
|
it('emits both thinking and text when both present', () => {
|
|
const result = adaptOllamaMessage({
|
|
type: 'chunk',
|
|
message: { role: 'assistant', content: 'Answer', thinking: 'Reasoning' },
|
|
done: false,
|
|
});
|
|
expect(result).toHaveLength(2);
|
|
expect(result[0].type).toBe('thinking');
|
|
expect(result[1].type).toBe('text');
|
|
});
|
|
});
|
|
|
|
describe('chunk — done with token counts', () => {
|
|
it('maps final chunk to cost message', () => {
|
|
const result = adaptOllamaMessage({
|
|
type: 'chunk',
|
|
message: { role: 'assistant', content: '' },
|
|
done: true,
|
|
done_reason: 'stop',
|
|
prompt_eval_count: 500,
|
|
eval_count: 120,
|
|
eval_duration: 2_000_000_000,
|
|
total_duration: 3_000_000_000,
|
|
});
|
|
// Should have cost message (no text since content is empty)
|
|
const costMsg = result.find(m => m.type === 'cost');
|
|
expect(costMsg).toBeDefined();
|
|
const content = costMsg!.content as any;
|
|
expect(content.inputTokens).toBe(500);
|
|
expect(content.outputTokens).toBe(120);
|
|
expect(content.durationMs).toBe(2000);
|
|
expect(content.totalCostUsd).toBe(0);
|
|
expect(content.isError).toBe(false);
|
|
});
|
|
|
|
it('marks error done_reason as isError', () => {
|
|
const result = adaptOllamaMessage({
|
|
type: 'chunk',
|
|
message: { role: 'assistant', content: '' },
|
|
done: true,
|
|
done_reason: 'error',
|
|
prompt_eval_count: 0,
|
|
eval_count: 0,
|
|
});
|
|
const costMsg = result.find(m => m.type === 'cost');
|
|
expect(costMsg).toBeDefined();
|
|
expect((costMsg!.content as any).isError).toBe(true);
|
|
});
|
|
|
|
it('includes text + cost when final chunk has content', () => {
|
|
const result = adaptOllamaMessage({
|
|
type: 'chunk',
|
|
message: { role: 'assistant', content: '.' },
|
|
done: true,
|
|
done_reason: 'stop',
|
|
prompt_eval_count: 10,
|
|
eval_count: 5,
|
|
});
|
|
expect(result.some(m => m.type === 'text')).toBe(true);
|
|
expect(result.some(m => m.type === 'cost')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('error event', () => {
|
|
it('maps to error message', () => {
|
|
const result = adaptOllamaMessage({
|
|
type: 'error',
|
|
message: 'model not found',
|
|
});
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].type).toBe('error');
|
|
expect((result[0].content as any).message).toBe('model not found');
|
|
});
|
|
});
|
|
|
|
describe('unknown event type', () => {
|
|
it('maps to unknown message preserving raw data', () => {
|
|
const result = adaptOllamaMessage({ type: 'something_else', data: 'test' });
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].type).toBe('unknown');
|
|
});
|
|
});
|
|
});
|