@@ -189,4 +189,233 @@ describe("Memory Integration E2E Tests", () => {
189189 // Should have 10 unique messages
190190 expect ( new Set ( contents ) . size ) . toBe ( 10 ) ;
191191 } ) ;
192+
193+ describe ( "Context-Aware Memory Features" , ( ) => {
194+ it ( "should update a message and track version history" , async ( ) => {
195+ await memory . createThread ( "thread-1" , "user-1" ) ;
196+
197+ const saved = await memory . saveMessages ( "thread-1" , "user-1" , [
198+ { role : "user" as const , content : "Original message" } ,
199+ ] ) ;
200+ const messageId = saved [ 0 ] . id ;
201+
202+ // Update the message
203+ const updated = await memory . updateMessage ( messageId , {
204+ content : "Updated message" ,
205+ } ) ;
206+
207+ expect ( updated ) . toBeDefined ( ) ;
208+ expect ( updated ?. message . content ) . toBe ( "Updated message" ) ;
209+ expect ( updated ?. version ) . toBe ( 2 ) ;
210+
211+ // Get version history
212+ const versions = await memory . getMessageVersions ( messageId ) ;
213+ expect ( versions ) . toHaveLength ( 2 ) ;
214+ expect ( versions [ 0 ] . message . content ) . toBe ( "Original message" ) ;
215+ expect ( versions [ 1 ] . message . content ) . toBe ( "Updated message" ) ;
216+ } ) ;
217+
218+ it ( "should track token counts and get thread token total" , async ( ) => {
219+ await memory . createThread ( "thread-1" , "user-1" ) ;
220+
221+ // Save messages with token counts
222+ const messages = await memory . getMessages ( "thread-1" ) ;
223+ const saved = await memory . saveMessages ( "thread-1" , "user-1" , [
224+ { role : "user" as const , content : "Hello" } ,
225+ { role : "assistant" as const , content : "Hi there" } ,
226+ ] ) ;
227+
228+ // Manually add token counts (in real usage these would come from API)
229+ await storage . updateMessage ( saved [ 0 ] . id , { tokenCount : 10 } ) ;
230+ await storage . updateMessage ( saved [ 1 ] . id , { tokenCount : 15 } ) ;
231+
232+ const tokenCount = await memory . getThreadTokenCount ( "thread-1" ) ;
233+ expect ( tokenCount ) . toBe ( 25 ) ;
234+ } ) ;
235+
236+ it ( "should get messages within token budget" , async ( ) => {
237+ await memory . createThread ( "thread-1" , "user-1" ) ;
238+
239+ const saved = await memory . saveMessages ( "thread-1" , "user-1" , [
240+ { role : "user" as const , content : "Message 1" } ,
241+ { role : "assistant" as const , content : "Response 1" } ,
242+ { role : "user" as const , content : "Message 2" } ,
243+ { role : "assistant" as const , content : "Response 2" } ,
244+ ] ) ;
245+
246+ // Add token counts
247+ await storage . updateMessage ( saved [ 0 ] . id , { tokenCount : 20 , importance : 0.5 } ) ;
248+ await storage . updateMessage ( saved [ 1 ] . id , { tokenCount : 30 , importance : 0.5 } ) ;
249+ await storage . updateMessage ( saved [ 2 ] . id , { tokenCount : 25 , importance : 0.8 } ) ;
250+ await storage . updateMessage ( saved [ 3 ] . id , { tokenCount : 35 , importance : 0.8 } ) ;
251+
252+ // Get messages within 70 token budget
253+ const messages = await memory . getMessagesWithinBudget ( "thread-1" , 70 ) ;
254+
255+ // Should get the two highest importance messages (Message 2 + Response 2 = 60 tokens)
256+ expect ( messages . length ) . toBeGreaterThan ( 0 ) ;
257+ expect ( messages . length ) . toBeLessThanOrEqual ( 4 ) ;
258+ } ) ;
259+
260+ it ( "should manage cache control for messages" , async ( ) => {
261+ await memory . createThread ( "thread-1" , "user-1" ) ;
262+
263+ const saved = await memory . saveMessages ( "thread-1" , "user-1" , [
264+ { role : "user" as const , content : "Cached message" } ,
265+ { role : "assistant" as const , content : "Not cached" } ,
266+ ] ) ;
267+
268+ // Enable cache for first message
269+ const futureDate = new Date ( Date . now ( ) + 60000 ) ; // 1 minute from now
270+ await storage . updateMessage ( saved [ 0 ] . id , {
271+ cacheControl : { enabled : true , expiresAt : futureDate } ,
272+ } ) ;
273+
274+ const cachedMessages = await memory . getCachedMessages ( "thread-1" ) ;
275+ expect ( cachedMessages ) . toHaveLength ( 1 ) ;
276+ expect ( cachedMessages [ 0 ] . message . content ) . toBe ( "Cached message" ) ;
277+ } ) ;
278+
279+ it ( "should invalidate cache for messages" , async ( ) => {
280+ await memory . createThread ( "thread-1" , "user-1" ) ;
281+
282+ const saved = await memory . saveMessages ( "thread-1" , "user-1" , [
283+ { role : "user" as const , content : "Cached message" } ,
284+ ] ) ;
285+
286+ // Enable cache
287+ const futureDate = new Date ( Date . now ( ) + 60000 ) ;
288+ await storage . updateMessage ( saved [ 0 ] . id , {
289+ cacheControl : { enabled : true , expiresAt : futureDate } ,
290+ } ) ;
291+
292+ // Verify cache is active
293+ let cachedMessages = await memory . getCachedMessages ( "thread-1" ) ;
294+ expect ( cachedMessages ) . toHaveLength ( 1 ) ;
295+
296+ // Invalidate cache
297+ await memory . invalidateCache ( "thread-1" ) ;
298+
299+ // Verify cache is invalidated
300+ cachedMessages = await memory . getCachedMessages ( "thread-1" ) ;
301+ expect ( cachedMessages ) . toHaveLength ( 0 ) ;
302+ } ) ;
303+
304+ it ( "should filter messages by status" , async ( ) => {
305+ await memory . createThread ( "thread-1" , "user-1" ) ;
306+
307+ const saved = await memory . saveMessages ( "thread-1" , "user-1" , [
308+ { role : "user" as const , content : "Active message" } ,
309+ { role : "assistant" as const , content : "Archived message" } ,
310+ { role : "user" as const , content : "Deleted message" } ,
311+ ] ) ;
312+
313+ // Set different statuses
314+ await storage . updateMessage ( saved [ 0 ] . id , { status : "active" } ) ;
315+ await storage . updateMessage ( saved [ 1 ] . id , { status : "archived" } ) ;
316+ await storage . updateMessage ( saved [ 2 ] . id , { status : "deleted" } ) ;
317+
318+ const activeMessages = await memory . getMessagesByStatus ( "thread-1" , "active" ) ;
319+ expect ( activeMessages ) . toHaveLength ( 1 ) ;
320+ expect ( activeMessages [ 0 ] . message . content ) . toBe ( "Active message" ) ;
321+
322+ const archivedMessages = await memory . getMessagesByStatus ( "thread-1" , "archived" ) ;
323+ expect ( archivedMessages ) . toHaveLength ( 1 ) ;
324+ expect ( archivedMessages [ 0 ] . message . content ) . toBe ( "Archived message" ) ;
325+ } ) ;
326+
327+ it ( "should filter messages by importance threshold" , async ( ) => {
328+ await memory . createThread ( "thread-1" , "user-1" ) ;
329+
330+ const saved = await memory . saveMessages ( "thread-1" , "user-1" , [
331+ { role : "user" as const , content : "Low importance" } ,
332+ { role : "assistant" as const , content : "Medium importance" } ,
333+ { role : "user" as const , content : "High importance" } ,
334+ ] ) ;
335+
336+ // Set importance scores
337+ await storage . updateMessage ( saved [ 0 ] . id , { importance : 0.3 } ) ;
338+ await storage . updateMessage ( saved [ 1 ] . id , { importance : 0.6 } ) ;
339+ await storage . updateMessage ( saved [ 2 ] . id , { importance : 0.9 } ) ;
340+
341+ // Get messages with importance >= 0.5
342+ const importantMessages = await memory . getMessagesByImportance ( "thread-1" , 0.5 ) ;
343+ expect ( importantMessages ) . toHaveLength ( 2 ) ;
344+ expect ( importantMessages [ 0 ] . importance ) . toBeGreaterThanOrEqual ( 0.5 ) ;
345+ expect ( importantMessages [ 1 ] . importance ) . toBeGreaterThanOrEqual ( 0.5 ) ;
346+ } ) ;
347+
348+ it ( "should handle graceful degradation when storage doesn't support features" , async ( ) => {
349+ // Create a storage that doesn't implement optional methods
350+ class BasicStorage extends InMemoryStorage {
351+ updateMessage = undefined ;
352+ getMessageHistory = undefined ;
353+ }
354+
355+ const basicStorage = new BasicStorage ( ) ;
356+ const basicMemory = new Memory ( basicStorage ) ;
357+
358+ await basicMemory . createThread ( "thread-1" , "user-1" ) ;
359+ const saved = await basicMemory . saveMessages ( "thread-1" , "user-1" , [
360+ { role : "user" as const , content : "Test" } ,
361+ ] ) ;
362+
363+ // These should return null/empty without errors
364+ const updated = await basicMemory . updateMessage ( saved [ 0 ] . id , { content : "Updated" } ) ;
365+ expect ( updated ) . toBeNull ( ) ;
366+
367+ const versions = await basicMemory . getMessageVersions ( saved [ 0 ] . id ) ;
368+ expect ( versions ) . toEqual ( [ ] ) ;
369+ } ) ;
370+
371+ it ( "should use contextWindow config for token-aware selection" , async ( ) => {
372+ const configuredStorage = new InMemoryStorage ( ) ;
373+ const configuredMemory = new Memory ( configuredStorage , {
374+ contextWindow : {
375+ maxTokens : 100 ,
376+ strategy : "token-aware" ,
377+ } ,
378+ } ) ;
379+
380+ await configuredMemory . createThread ( "thread-1" , "user-1" ) ;
381+ const saved = await configuredMemory . saveMessages ( "thread-1" , "user-1" , [
382+ { role : "user" as const , content : "Message 1" } ,
383+ { role : "assistant" as const , content : "Response 1" } ,
384+ { role : "user" as const , content : "Message 2" } ,
385+ ] ) ;
386+
387+ // Add token counts
388+ await configuredStorage . updateMessage ( saved [ 0 ] . id , { tokenCount : 40 } ) ;
389+ await configuredStorage . updateMessage ( saved [ 1 ] . id , { tokenCount : 50 } ) ;
390+ await configuredStorage . updateMessage ( saved [ 2 ] . id , { tokenCount : 30 } ) ;
391+
392+ // Should use contextWindow.maxTokens from config
393+ const messages = await configuredMemory . getMessagesWithinBudget ( "thread-1" ) ;
394+ expect ( messages . length ) . toBeGreaterThan ( 0 ) ;
395+ } ) ;
396+
397+ it ( "should preserve message order when sorting by importance and recency" , async ( ) => {
398+ await memory . createThread ( "thread-1" , "user-1" ) ;
399+
400+ const saved = await memory . saveMessages ( "thread-1" , "user-1" , [
401+ { role : "user" as const , content : "First" } ,
402+ { role : "assistant" as const , content : "Second" } ,
403+ { role : "user" as const , content : "Third" } ,
404+ ] ) ;
405+
406+ // Set same importance, different times
407+ await storage . updateMessage ( saved [ 0 ] . id , { importance : 0.5 , tokenCount : 10 } ) ;
408+ await new Promise ( resolve => setTimeout ( resolve , 10 ) ) ;
409+ await storage . updateMessage ( saved [ 1 ] . id , { importance : 0.5 , tokenCount : 10 } ) ;
410+ await new Promise ( resolve => setTimeout ( resolve , 10 ) ) ;
411+ await storage . updateMessage ( saved [ 2 ] . id , { importance : 0.5 , tokenCount : 10 } ) ;
412+
413+ const messages = await memory . getMessagesWithinBudget ( "thread-1" , 30 ) ;
414+
415+ // Should maintain chronological order in result
416+ expect ( messages [ 0 ] . content ) . toBe ( "First" ) ;
417+ expect ( messages [ 1 ] . content ) . toBe ( "Second" ) ;
418+ expect ( messages [ 2 ] . content ) . toBe ( "Third" ) ;
419+ } ) ;
420+ } ) ;
192421} ) ;
0 commit comments