Initial import: live state on api.qbirr.com (server v0.6.3)
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Server-side conversation memory. Persists each run's user goal + final
|
||||
* assistant answer per session, and recalls the recent turns so a run has
|
||||
* context even when the plugin sends little/no history. Independent of the
|
||||
* plugin's own carry-forward.
|
||||
*/
|
||||
|
||||
import { getDb } from '../db/pool.js';
|
||||
import { newId } from '../lib/crypto.js';
|
||||
import type { RunContextMessage } from './types.js';
|
||||
|
||||
const MAX_RECALL = 12; // turns to prepend
|
||||
const MAX_CONTENT = 4000; // clip very long turns
|
||||
|
||||
export function saveTurn(session_id: string, role: 'user' | 'assistant', content: string, user_id?: string): void {
|
||||
if (!session_id || !content) return;
|
||||
getDb().prepare(
|
||||
'INSERT INTO conversation_turns (id, session_id, user_id, role, content) VALUES (?,?,?,?,?)',
|
||||
).run(newId(), session_id, user_id ?? null, role, content.slice(0, MAX_CONTENT));
|
||||
}
|
||||
|
||||
export function recallTurns(session_id: string, limit = MAX_RECALL): RunContextMessage[] {
|
||||
if (!session_id) return [];
|
||||
const rows = getDb().prepare(
|
||||
'SELECT role, content FROM conversation_turns WHERE session_id = ? ORDER BY created_at DESC LIMIT ?',
|
||||
).all(session_id, limit) as { role: string; content: string }[];
|
||||
return rows
|
||||
.reverse()
|
||||
.map((r) => ({ role: r.role === 'assistant' ? 'assistant' : 'user', content: r.content }) as RunContextMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge recalled memory with the context the plugin sent, de-duplicating
|
||||
* so we don't double-feed turns the plugin already included. Memory is
|
||||
* prepended (older), then the plugin's context (newer).
|
||||
*/
|
||||
export function mergeContext(session_id: string, pluginContext: RunContextMessage[]): RunContextMessage[] {
|
||||
const recalled = recallTurns(session_id);
|
||||
if (recalled.length === 0) return pluginContext;
|
||||
const seen = new Set(pluginContext.map((m) => `${m.role}:${m.content.slice(0, 80)}`));
|
||||
const fromMemory = recalled.filter((m) => !seen.has(`${m.role}:${m.content.slice(0, 80)}`));
|
||||
return [...fromMemory, ...pluginContext];
|
||||
}
|
||||
Reference in New Issue
Block a user