/** * Tests for new features: Sessions, Streaming, SONA */ const { test, describe } = require('node:test'); const assert = require('node:assert'); const { RuvLLM, SessionManager, StreamingGenerator, SonaCoordinator, TrajectoryBuilder, ReasoningBank, EwcManager, } = require('../dist/cjs/index.js'); describe('SessionManager', () => { test('should create session', () => { const llm = new RuvLLM(); const sessions = new SessionManager(llm); const session = sessions.create({ userId: 'test' }); assert.ok(session.id.startsWith('session-')); assert.strictEqual(session.messageCount, 0); assert.deepStrictEqual(session.metadata, { userId: 'test' }); }); test('should chat with context', () => { const llm = new RuvLLM(); const sessions = new SessionManager(llm); const session = sessions.create(); const response1 = sessions.chat(session.id, 'Hello'); const response2 = sessions.chat(session.id, 'How are you?'); assert.strictEqual(session.messages.length, 4); // 2 user + 2 assistant assert.ok(response1.text); assert.ok(response2.text); }); test('should get history', () => { const llm = new RuvLLM(); const sessions = new SessionManager(llm); const session = sessions.create(); sessions.chat(session.id, 'Message 1'); sessions.chat(session.id, 'Message 2'); const history = sessions.getHistory(session.id); assert.strictEqual(history.length, 4); const limited = sessions.getHistory(session.id, 2); assert.strictEqual(limited.length, 2); }); test('should export and import session', () => { const llm = new RuvLLM(); const sessions = new SessionManager(llm); const session = sessions.create({ key: 'value' }); sessions.chat(session.id, 'Test message'); const exported = sessions.export(session.id); assert.ok(exported); const imported = sessions.import(exported); assert.strictEqual(imported.id, session.id); assert.strictEqual(imported.messages.length, 2); }); test('should end session', () => { const llm = new RuvLLM(); const sessions = new SessionManager(llm); const session = sessions.create(); assert.ok(sessions.get(session.id)); sessions.end(session.id); assert.strictEqual(sessions.get(session.id), undefined); }); }); describe('StreamingGenerator', () => { test('should stream response', async () => { const llm = new RuvLLM(); const streamer = new StreamingGenerator(llm); const chunks = []; for await (const chunk of streamer.stream('Test prompt')) { chunks.push(chunk); } assert.ok(chunks.length > 0); assert.ok(chunks[chunks.length - 1].done); }); test('should collect stream', async () => { const llm = new RuvLLM(); const streamer = new StreamingGenerator(llm); const result = await streamer.collect('Test prompt'); assert.ok(typeof result === 'string'); }); test('should use callbacks', async () => { const llm = new RuvLLM(); const streamer = new StreamingGenerator(llm); let chunkCount = 0; let completed = false; await streamer.streamWithCallbacks('Test', { onChunk: () => chunkCount++, onComplete: () => { completed = true; }, }); assert.ok(chunkCount > 0); assert.ok(completed); }); }); describe('TrajectoryBuilder', () => { test('should build trajectory', () => { const builder = new TrajectoryBuilder(); const trajectory = builder .startStep('query', 'What is AI?') .endStep('AI is...', 0.95) .startStep('memory', 'searching') .endStep('found 3 results', 0.88) .complete('success'); assert.ok(trajectory.id.startsWith('traj-')); assert.strictEqual(trajectory.steps.length, 2); assert.strictEqual(trajectory.outcome, 'success'); assert.ok(trajectory.durationMs >= 0); }); test('should track step durations', () => { const builder = new TrajectoryBuilder(); builder.startStep('query', 'input'); // Small delay const start = Date.now(); while (Date.now() - start < 5) { /* wait */ } builder.endStep('output', 0.9); const trajectory = builder.complete('success'); assert.ok(trajectory.steps[0].durationMs >= 0); }); }); describe('ReasoningBank', () => { test('should store and retrieve patterns', () => { const bank = new ReasoningBank(0.5); // Lower threshold for testing const embedding = [0.1, 0.2, 0.3, 0.4, 0.5]; const id = bank.store('query_response', embedding); assert.ok(id.startsWith('pat-')); const pattern = bank.get(id); assert.ok(pattern); assert.strictEqual(pattern.type, 'query_response'); assert.strictEqual(pattern.successRate, 1.0); }); test('should find similar patterns', () => { const bank = new ReasoningBank(0.5); const emb1 = [1, 0, 0, 0, 0]; const emb2 = [0.9, 0.1, 0, 0, 0]; // Similar to emb1 bank.store('query_response', emb1); bank.store('routing', emb2); const similar = bank.findSimilar([1, 0, 0, 0, 0], 5); assert.ok(similar.length >= 1); }); test('should track usage', () => { const bank = new ReasoningBank(); const embedding = [0.1, 0.2, 0.3]; const id = bank.store('query_response', embedding); bank.recordUsage(id, true); bank.recordUsage(id, true); bank.recordUsage(id, false); const pattern = bank.get(id); assert.strictEqual(pattern.useCount, 3); assert.ok(pattern.successRate < 1.0); }); test('should provide stats', () => { const bank = new ReasoningBank(); bank.store('query_response', [0.1, 0.2]); bank.store('routing', [0.3, 0.4]); const stats = bank.stats(); assert.strictEqual(stats.totalPatterns, 2); assert.strictEqual(stats.byType['query_response'], 1); assert.strictEqual(stats.byType['routing'], 1); }); }); describe('EwcManager', () => { test('should register tasks', () => { const ewc = new EwcManager(1000); ewc.registerTask('task1', [0.1, 0.2, 0.3]); ewc.registerTask('task2', [0.4, 0.5, 0.6]); const stats = ewc.stats(); assert.strictEqual(stats.tasksLearned, 2); assert.strictEqual(stats.fisherComputed, true); }); test('should compute penalty', () => { const ewc = new EwcManager(1000); ewc.registerTask('task1', [0.5, 0.5, 0.5]); // Weights that differ from optimal should have higher penalty const penalty1 = ewc.computePenalty([0.5, 0.5, 0.5]); const penalty2 = ewc.computePenalty([1.0, 1.0, 1.0]); assert.ok(penalty2 > penalty1); }); }); describe('SonaCoordinator', () => { test('should create with config', () => { const sona = new SonaCoordinator({ instantLoopEnabled: true, ewcLambda: 5000, }); assert.ok(sona); const stats = sona.stats(); assert.ok(stats.patterns); assert.ok(stats.ewc); }); test('should record signals', () => { const sona = new SonaCoordinator(); sona.recordSignal({ requestId: 'req-123', quality: 0.9, type: 'positive', timestamp: new Date(), }); const stats = sona.stats(); assert.strictEqual(stats.signalsReceived, 1); }); test('should record trajectories', () => { const sona = new SonaCoordinator(); const builder = new TrajectoryBuilder(); const trajectory = builder .startStep('query', 'test') .endStep('response', 0.95) .complete('success'); sona.recordTrajectory(trajectory); const stats = sona.stats(); assert.strictEqual(stats.trajectoriesBuffered, 1); }); test('should run background loop', () => { const sona = new SonaCoordinator(); // Add some trajectories for (let i = 0; i < 3; i++) { const builder = new TrajectoryBuilder(); const trajectory = builder .startStep('query', `test ${i}`) .endStep(`response ${i}`, 0.95) .complete('success'); sona.recordTrajectory(trajectory); } const result = sona.runBackgroundLoop(); assert.strictEqual(result.trajectoriesProcessed, 3); }); });