Skip to content

Commit 3302626

Browse files
committed
refactor: Simplify memory configuration and improve error handling
Remove unnecessary configuration options and improve developer experience: - Remove always-true config options (autoSave, autoInject, trackTokenUsage) - Remove unused config fields (strategy, retainRecentMessages) - Replace custom ID generation with UUID v4 - Fix deprecated .substr() to use .slice() - Improve error messages for unsupported storage operations - Update examples and tests This reduces MemoryConfig from 7 fields to 2, making the API simpler while maintaining all functionality.
1 parent bef8292 commit 3302626

File tree

4 files changed

+51
-62
lines changed

4 files changed

+51
-62
lines changed

examples/memory-usage.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ async function main() {
1414
// Create a memory instance with the storage
1515
const memory = new Memory(storage, {
1616
maxHistoryMessages: 10, // Keep last 10 messages in context
17-
autoInject: true, // Automatically inject history
18-
autoSave: true, // Automatically save messages
1917
});
2018

2119
// Create OpenRouter client with memory
@@ -103,8 +101,6 @@ async function main() {
103101
// You can create memory with custom config
104102
const customMemory = new Memory(new InMemoryStorage(), {
105103
maxHistoryMessages: 20, // Keep more history
106-
autoInject: true,
107-
autoSave: true,
108104
});
109105
console.log("Custom memory config:", customMemory.getConfig());
110106

@@ -113,14 +109,9 @@ async function main() {
113109

114110
const tokenStorage = new InMemoryStorage();
115111
const tokenMemory = new Memory(tokenStorage, {
116-
maxHistoryMessages: 10,
117-
autoInject: true,
118-
autoSave: true,
119112
contextWindow: {
120113
maxTokens: 1000,
121-
strategy: "token-aware",
122114
},
123-
trackTokenUsage: true,
124115
});
125116

126117
const tokenThreadId = "token-thread-1";

src/lib/memory/memory.ts

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Provides high-level API for managing threads, messages, resources, and working memory
44
*/
55

6+
import { randomUUID } from "node:crypto";
67
import type { Message } from "../../models/message.js";
78
import type { MemoryStorage } from "./storage/interface.js";
89
import type {
@@ -19,7 +20,7 @@ import type {
1920
/**
2021
* Resolved configuration with all defaults applied
2122
*/
22-
type ResolvedMemoryConfig = Required<Pick<MemoryConfig, 'maxHistoryMessages' | 'autoInject' | 'autoSave' | 'trackTokenUsage'>> & Pick<MemoryConfig, 'contextWindow'>;
23+
type ResolvedMemoryConfig = Required<Pick<MemoryConfig, 'maxHistoryMessages'>> & Pick<MemoryConfig, 'contextWindow'>;
2324

2425
/**
2526
* Memory class for managing conversation history, threads, and working memory
@@ -37,10 +38,7 @@ export class Memory {
3738
this.storage = storage;
3839
this.config = {
3940
maxHistoryMessages: config.maxHistoryMessages ?? 10,
40-
autoInject: config.autoInject ?? true,
41-
autoSave: config.autoSave ?? true,
4241
...(config.contextWindow !== undefined && { contextWindow: config.contextWindow }),
43-
trackTokenUsage: config.trackTokenUsage ?? false,
4442
};
4543
}
4644

@@ -272,29 +270,43 @@ export class Memory {
272270
* Update an existing message
273271
* @param messageId The message ID
274272
* @param updates Partial message updates
275-
* @returns The updated message, or null if storage doesn't support updates
273+
* @returns The updated message
274+
* @throws Error if storage doesn't support message updates
276275
*/
277276
async updateMessage(
278277
messageId: string,
279278
updates: Partial<Message>,
280-
): Promise<MemoryMessage | null> {
279+
): Promise<MemoryMessage> {
281280
if (!this.storage.updateMessage) {
282-
return null;
281+
throw new Error(
282+
'Message editing is not supported by this storage backend. ' +
283+
'Please use a storage implementation that provides the updateMessage method.'
284+
);
283285
}
284286

285-
return await this.storage.updateMessage(messageId, {
287+
const result = await this.storage.updateMessage(messageId, {
286288
message: updates as Message,
287289
} as Partial<MemoryMessage>);
290+
291+
if (!result) {
292+
throw new Error(`Message with ID "${messageId}" not found`);
293+
}
294+
295+
return result;
288296
}
289297

290298
/**
291299
* Get edit history for a message
292300
* @param messageId The message ID
293-
* @returns Array of message versions, or empty if storage doesn't support history
301+
* @returns Array of message versions (oldest to newest)
302+
* @throws Error if storage doesn't support message history
294303
*/
295304
async getMessageVersions(messageId: string): Promise<MemoryMessage[]> {
296305
if (!this.storage.getMessageHistory) {
297-
return [];
306+
throw new Error(
307+
'Message version history is not supported by this storage backend. ' +
308+
'Please use a storage implementation that provides the getMessageHistory method.'
309+
);
298310
}
299311

300312
return await this.storage.getMessageHistory(messageId);
@@ -305,22 +317,26 @@ export class Memory {
305317
/**
306318
* Get total token count for a thread
307319
* @param threadId The thread ID
308-
* @returns Token count, or 0 if storage doesn't support token counting
320+
* @returns Token count
321+
* @throws Error if storage doesn't support token counting
309322
*/
310323
async getThreadTokenCount(threadId: string): Promise<number> {
311324
if (!this.storage.getThreadTokenCount) {
312-
return 0;
325+
throw new Error(
326+
'Token counting is not supported by this storage backend. ' +
327+
'Please use a storage implementation that provides the getThreadTokenCount method.'
328+
);
313329
}
314330

315331
return await this.storage.getThreadTokenCount(threadId);
316332
}
317333

318334
/**
319335
* Get messages within a token budget
320-
* Uses contextWindow config if available, otherwise falls back to maxHistoryMessages
321336
* @param threadId The thread ID
322-
* @param maxTokens Optional max tokens (uses config if not provided)
337+
* @param maxTokens Max tokens (required - use config.contextWindow.maxTokens or provide explicitly)
323338
* @returns Array of messages within token budget
339+
* @throws Error if maxTokens not provided or storage doesn't support token-based selection
324340
*/
325341
async getMessagesWithinBudget(
326342
threadId: string,
@@ -329,9 +345,17 @@ export class Memory {
329345
const tokenLimit =
330346
maxTokens || this.config.contextWindow?.maxTokens;
331347

332-
if (!tokenLimit || !this.storage.getMessagesByTokenBudget) {
333-
// Fall back to regular getRecentMessages
334-
return await this.getRecentMessages(threadId);
348+
if (!tokenLimit) {
349+
throw new Error(
350+
'Token budget not specified. Please provide maxTokens parameter or configure contextWindow.maxTokens.'
351+
);
352+
}
353+
354+
if (!this.storage.getMessagesByTokenBudget) {
355+
throw new Error(
356+
'Token-based message selection is not supported by this storage backend. ' +
357+
'Please use a storage implementation that provides the getMessagesByTokenBudget method.'
358+
);
335359
}
336360

337361
const memoryMessages = await this.storage.getMessagesByTokenBudget(
@@ -346,11 +370,13 @@ export class Memory {
346370

347371
/**
348372
* Invalidate cache for messages
373+
* Note: This is a no-op if the storage backend doesn't support caching
349374
* @param threadId The thread ID
350375
* @param beforeDate Optional date - invalidate cache before this date
351376
*/
352377
async invalidateCache(threadId: string, beforeDate?: Date): Promise<void> {
353378
if (!this.storage.invalidateCache) {
379+
// No-op: Storage doesn't support caching
354380
return;
355381
}
356382

@@ -382,10 +408,10 @@ export class Memory {
382408

383409
/**
384410
* Generate a unique ID for messages
385-
* @returns A unique ID string
411+
* @returns A unique ID string (UUID v4)
386412
*/
387413
private generateId(): string {
388-
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
414+
return randomUUID();
389415
}
390416

391417
/**

src/lib/memory/types.ts

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -108,50 +108,23 @@ export interface ResourceWorkingMemory {
108108
export interface ContextWindowConfig {
109109
/** Maximum tokens to keep in context */
110110
maxTokens: number;
111-
/**
112-
* Strategy for selecting messages within token budget
113-
* - fifo: First in, first out (oldest messages dropped first)
114-
* - priority-based: Keep messages with highest importance scores
115-
* - token-aware: Smart selection based on tokens and recency
116-
*/
117-
strategy: "fifo" | "priority-based" | "token-aware";
118-
/** Always retain this many recent messages regardless of tokens */
119-
retainRecentMessages?: number;
120111
}
121112

122113
/**
123114
* Configuration options for the memory system
124115
*/
125116
export interface MemoryConfig {
126117
/**
127-
* Maximum number of messages to load from history when auto-injecting
118+
* Maximum number of messages to load from history
128119
* @default 10
129120
*/
130121
maxHistoryMessages?: number;
131122

132-
/**
133-
* Whether to enable auto-injection of conversation history
134-
* @default true
135-
*/
136-
autoInject?: boolean;
137-
138-
/**
139-
* Whether to enable auto-saving of messages
140-
* @default true
141-
*/
142-
autoSave?: boolean;
143-
144123
/**
145124
* Context window management configuration
146125
* When provided, overrides maxHistoryMessages with token-aware selection
147126
*/
148127
contextWindow?: ContextWindowConfig;
149-
150-
/**
151-
* Whether to track token usage for messages
152-
* @default false
153-
*/
154-
trackTokenUsage?: boolean;
155128
}
156129

157130
/**

tests/e2e/memory.test.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -295,20 +295,19 @@ describe("Memory Integration E2E Tests", () => {
295295
{ role: "user" as const, content: "Test" },
296296
]);
297297

298-
// These should return null/empty without errors
299-
const updated = await basicMemory.updateMessage(saved[0].id, { content: "Updated" });
300-
expect(updated).toBeNull();
298+
// These should throw errors with clear messages
299+
await expect(basicMemory.updateMessage(saved[0].id, { content: "Updated" }))
300+
.rejects.toThrow('Message editing is not supported by this storage backend');
301301

302-
const versions = await basicMemory.getMessageVersions(saved[0].id);
303-
expect(versions).toEqual([]);
302+
await expect(basicMemory.getMessageVersions(saved[0].id))
303+
.rejects.toThrow('Message version history is not supported by this storage backend');
304304
});
305305

306306
it("should use contextWindow config for token-aware selection", async () => {
307307
const configuredStorage = new InMemoryStorage();
308308
const configuredMemory = new Memory(configuredStorage, {
309309
contextWindow: {
310310
maxTokens: 100,
311-
strategy: "token-aware",
312311
},
313312
});
314313

0 commit comments

Comments
 (0)