Spaces:
Sleeping
Sleeping
| import { describe, it, expect, beforeEach, afterEach } from 'vitest'; | |
| import { | |
| getContextHandoffMode, | |
| recordIncomingMessages, | |
| maybeInjectContextHandoff, | |
| recordSuccessfulModel, | |
| hasPriorModel, | |
| HANDOFF_MAX_TOKENS, | |
| _clearStoreForTesting, | |
| } from '../../services/context-handoff.js'; | |
| const msg = (role: string, content: string) => ({ role, content } as any); | |
| const userMsg = msg('user', 'hello'); | |
| const assistantMsg = msg('assistant', 'hi there'); | |
| const messages = [userMsg, assistantMsg]; | |
| beforeEach(() => { | |
| _clearStoreForTesting(); | |
| delete process.env.FREELLMAPI_CONTEXT_HANDOFF; | |
| }); | |
| afterEach(() => { | |
| delete process.env.FREELLMAPI_CONTEXT_HANDOFF; | |
| }); | |
| describe('getContextHandoffMode', () => { | |
| it('defaults to off', () => { | |
| expect(getContextHandoffMode()).toBe('off'); | |
| }); | |
| it('reads on_model_switch', () => { | |
| process.env.FREELLMAPI_CONTEXT_HANDOFF = 'on_model_switch'; | |
| expect(getContextHandoffMode()).toBe('on_model_switch'); | |
| }); | |
| it('treats unknown value as off', () => { | |
| process.env.FREELLMAPI_CONTEXT_HANDOFF = 'always'; | |
| expect(getContextHandoffMode()).toBe('off'); | |
| }); | |
| }); | |
| describe('maybeInjectContextHandoff — off mode', () => { | |
| it('returns original messages unchanged when mode is off', () => { | |
| const result = maybeInjectContextHandoff({ | |
| mode: 'off', | |
| sessionKey: 'sess1', | |
| messages, | |
| selectedModelKey: 'groq:llama-3', | |
| }); | |
| expect(result.injected).toBe(false); | |
| expect(result.messages).toBe(messages); | |
| }); | |
| }); | |
| describe('maybeInjectContextHandoff — on_model_switch', () => { | |
| const mode = 'on_model_switch'; | |
| it('no handoff on first request (no prior model recorded)', () => { | |
| const result = maybeInjectContextHandoff({ | |
| mode, | |
| sessionKey: 'sess1', | |
| messages, | |
| selectedModelKey: 'groq:llama-3', | |
| }); | |
| expect(result.injected).toBe(false); | |
| expect(result.messages).toBe(messages); | |
| }); | |
| it('no handoff when same model continues', () => { | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| const result = maybeInjectContextHandoff({ | |
| mode, | |
| sessionKey: 'sess1', | |
| messages, | |
| selectedModelKey: 'groq:llama-3', | |
| }); | |
| expect(result.injected).toBe(false); | |
| }); | |
| it('injects handoff when model switches', () => { | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| const result = maybeInjectContextHandoff({ | |
| mode, | |
| sessionKey: 'sess1', | |
| messages, | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(result.injected).toBe(true); | |
| const injected = result.messages.find(m => m.role === 'system'); | |
| expect(injected).toBeDefined(); | |
| expect(injected!.content).toContain('FreeLLMAPI context handoff:'); | |
| expect(injected!.content).toContain('groq:llama-3'); | |
| expect(injected!.content).toContain('google:gemini-flash'); | |
| }); | |
| it('no duplicate handoff on next request after model B succeeds', () => { | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| // First request: model switches to google | |
| const r1 = maybeInjectContextHandoff({ | |
| mode, | |
| sessionKey: 'sess1', | |
| messages, | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(r1.injected).toBe(true); | |
| // Record model B as successful | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'google:gemini-flash' }); | |
| // Second request on same model B | |
| const r2 = maybeInjectContextHandoff({ | |
| mode, | |
| sessionKey: 'sess1', | |
| messages, | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(r2.injected).toBe(false); | |
| }); | |
| it('injects again if model switches a second time', () => { | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'google:gemini-flash' }); | |
| const result = maybeInjectContextHandoff({ | |
| mode, | |
| sessionKey: 'sess1', | |
| messages, | |
| selectedModelKey: 'cerebras:qwen-3', | |
| }); | |
| expect(result.injected).toBe(true); | |
| expect(result.messages[0].content).toContain('google:gemini-flash'); | |
| expect(result.messages[0].content).toContain('cerebras:qwen-3'); | |
| }); | |
| it('does not duplicate if handoff system message already present as plain string', () => { | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| const existingHandoff = msg('system', 'FreeLLMAPI context handoff: prior injection'); | |
| const result = maybeInjectContextHandoff({ | |
| mode, | |
| sessionKey: 'sess1', | |
| messages: [existingHandoff, ...messages], | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(result.injected).toBe(false); | |
| }); | |
| it('does not duplicate if handoff system message present as array-content (OpenCode format)', () => { | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| const arrayHandoff = { | |
| role: 'system', | |
| content: [{ type: 'text', text: 'FreeLLMAPI context handoff: prior injection' }], | |
| } as any; | |
| const result = maybeInjectContextHandoff({ | |
| mode, | |
| sessionKey: 'sess1', | |
| messages: [arrayHandoff, ...messages], | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(result.injected).toBe(false); | |
| }); | |
| it('inserts handoff after existing system messages', () => { | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| const sysMsg = msg('system', 'You are a helpful assistant'); | |
| const withSystem = [sysMsg, ...messages]; | |
| const result = maybeInjectContextHandoff({ | |
| mode, | |
| sessionKey: 'sess1', | |
| messages: withSystem, | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(result.injected).toBe(true); | |
| expect(result.messages[0]).toBe(sysMsg); | |
| expect(result.messages[1].role).toBe('system'); | |
| expect(result.messages[1].content).toContain('FreeLLMAPI context handoff:'); | |
| }); | |
| it('isolates sessions by sessionKey', () => { | |
| recordIncomingMessages('sess-a', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess-a', modelKey: 'groq:llama-3' }); | |
| // sess-b has no prior model | |
| const result = maybeInjectContextHandoff({ | |
| mode, | |
| sessionKey: 'sess-b', | |
| messages, | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(result.injected).toBe(false); | |
| }); | |
| }); | |
| describe('recordIncomingMessages', () => { | |
| it('truncates long content per message', () => { | |
| const longMsg = msg('user', 'x'.repeat(1000)); | |
| recordIncomingMessages('sess1', [longMsg]); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| const result = maybeInjectContextHandoff({ | |
| mode: 'on_model_switch', | |
| sessionKey: 'sess1', | |
| messages: [msg('user', 'new question')], | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(result.injected).toBe(true); | |
| expect((result.messages[0].content as string).length).toBeLessThanOrEqual(6500); | |
| }); | |
| it('extracts text from array-content messages (OpenCode/Continue.dev format)', () => { | |
| const arrayMsg = { role: 'user', content: [{ type: 'text', text: 'hello from opencode' }] } as any; | |
| recordIncomingMessages('sess1', [arrayMsg]); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| const result = maybeInjectContextHandoff({ | |
| mode: 'on_model_switch', | |
| sessionKey: 'sess1', | |
| messages: [msg('user', 'follow up')], | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(result.injected).toBe(true); | |
| // Summary must contain the extracted text, not raw JSON | |
| expect(result.messages[0].content as string).toContain('hello from opencode'); | |
| expect(result.messages[0].content as string).not.toContain('"type":"text"'); | |
| }); | |
| it('keeps only last MAX_RECENT_MESSAGES user/assistant messages', () => { | |
| const many = Array.from({ length: 20 }, (_, i) => | |
| i % 2 === 0 ? msg('user', `q${i}`) : msg('assistant', `a${i}`), | |
| ); | |
| recordIncomingMessages('sess1', many); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| const result = maybeInjectContextHandoff({ | |
| mode: 'on_model_switch', | |
| sessionKey: 'sess1', | |
| messages: [msg('user', 'follow up')], | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(result.injected).toBe(true); | |
| expect((result.messages[0].content as string)).not.toContain('User: q0\n'); | |
| }); | |
| it('store hard-evicts oldest entries when all sessions are active and over MAX_STORE_SIZE', () => { | |
| // Fill 510 entries — all within TTL so TTL-prune removes nothing. | |
| // The LRU eviction path must still bring it back to MAX_STORE_SIZE (500). | |
| for (let i = 0; i < 510; i++) { | |
| recordIncomingMessages(`sess-fill-${i}`, [msg('user', `hello ${i}`)]); | |
| } | |
| // One more write triggers the eviction; store must not exceed 500 after. | |
| recordIncomingMessages('sess-trigger', [msg('user', 'trigger')]); | |
| // We can't read store.size directly, but the operation must not throw | |
| // and a subsequent inject must still work correctly. | |
| expect(() => recordIncomingMessages('sess-final', [msg('user', 'ok')])).not.toThrow(); | |
| }); | |
| it('resets lastModelKey when incoming payload has no assistant messages (fresh conversation on reused session ID)', () => { | |
| // Simulate: model recorded from previous conversation, then client reuses | |
| // same session ID for a brand-new conversation (no assistant turns yet). | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| // New conversation starts — only a user message, no assistant | |
| recordIncomingMessages('sess1', [msg('user', 'brand new question')]); | |
| // Should NOT inject — lastModelKey was cleared by fresh-convo heuristic | |
| const result = maybeInjectContextHandoff({ | |
| mode: 'on_model_switch', | |
| sessionKey: 'sess1', | |
| messages: [msg('user', 'brand new question')], | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(result.injected).toBe(false); | |
| }); | |
| it('preserves lastModelKey across recordIncomingMessages calls', () => { | |
| // Simulate: recordSuccessfulModel writes lastModelKey, then next turn's | |
| // recordIncomingMessages must not wipe it. | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| // Second call (next turn) must preserve lastModelKey | |
| recordIncomingMessages('sess1', [msg('user', 'turn 2'), msg('assistant', 'reply 2')]); | |
| const result = maybeInjectContextHandoff({ | |
| mode: 'on_model_switch', | |
| sessionKey: 'sess1', | |
| messages: [msg('user', 'turn 3')], | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(result.injected).toBe(true); | |
| expect(result.messages[0].content as string).toContain('groq:llama-3'); | |
| }); | |
| }); | |
| describe('HANDOFF_MAX_TOKENS', () => { | |
| it('is a positive number exported for proxy routing estimate', () => { | |
| expect(HANDOFF_MAX_TOKENS).toBeGreaterThan(0); | |
| expect(typeof HANDOFF_MAX_TOKENS).toBe('number'); | |
| }); | |
| }); | |
| describe('hasPriorModel', () => { | |
| it('is false for an unknown session', () => { | |
| expect(hasPriorModel('nope')).toBe(false); | |
| }); | |
| it('is false for an empty session key', () => { | |
| expect(hasPriorModel('')).toBe(false); | |
| }); | |
| it('is false after recordIncomingMessages alone (no model yet)', () => { | |
| recordIncomingMessages('sess1', messages); | |
| expect(hasPriorModel('sess1')).toBe(false); | |
| }); | |
| it('is true once a model has succeeded for the session', () => { | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| expect(hasPriorModel('sess1')).toBe(true); | |
| }); | |
| it('flips back to false when a fresh conversation clears the prior model', () => { | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| // Reused session ID, brand-new conversation (no assistant turns) → cleared. | |
| recordIncomingMessages('sess1', [msg('user', 'brand new')]); | |
| expect(hasPriorModel('sess1')).toBe(false); | |
| }); | |
| }); | |
| describe('injectedTokens', () => { | |
| it('is 0 when no handoff is injected', () => { | |
| const result = maybeInjectContextHandoff({ | |
| mode: 'on_model_switch', | |
| sessionKey: 'sess1', | |
| messages, | |
| selectedModelKey: 'groq:llama-3', | |
| }); | |
| expect(result.injected).toBe(false); | |
| expect(result.injectedTokens).toBe(0); | |
| }); | |
| it('is a positive estimate when a handoff is injected', () => { | |
| recordIncomingMessages('sess1', messages); | |
| recordSuccessfulModel({ sessionKey: 'sess1', modelKey: 'groq:llama-3' }); | |
| const result = maybeInjectContextHandoff({ | |
| mode: 'on_model_switch', | |
| sessionKey: 'sess1', | |
| messages, | |
| selectedModelKey: 'google:gemini-flash', | |
| }); | |
| expect(result.injected).toBe(true); | |
| expect(result.injectedTokens).toBeGreaterThan(0); | |
| // Never exceeds the conservative routing-pad upper bound. | |
| expect(result.injectedTokens).toBeLessThanOrEqual(HANDOFF_MAX_TOKENS); | |
| }); | |
| }); | |