@agentier/memory 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,87 @@
1
+ import type { MemoryProvider, Message } from '@agentier/core';
2
+ /**
3
+ * Configuration options for {@link BufferMemory}.
4
+ */
5
+ export interface BufferMemoryOptions {
6
+ /**
7
+ * Maximum number of estimated tokens to retain in the conversation history.
8
+ * When exceeded, older non-system messages are dropped (FIFO).
9
+ */
10
+ maxTokens?: number;
11
+ /**
12
+ * Maximum number of messages to retain in the conversation history.
13
+ * System messages are always preserved; the limit applies to the remainder.
14
+ */
15
+ maxMessages?: number;
16
+ /**
17
+ * Strategy used when trimming messages that exceed the limits.
18
+ * Currently only `'fifo'` (first-in, first-out) is supported.
19
+ */
20
+ trimStrategy?: 'fifo';
21
+ }
22
+ /**
23
+ * An in-memory {@link MemoryProvider} that stores conversation history in a
24
+ * `Map` keyed by session ID. Messages can be automatically trimmed by count
25
+ * or estimated token budget so the context window stays within bounds.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { BufferMemory } from '@agentier/memory'
30
+ *
31
+ * const memory = new BufferMemory({ maxMessages: 50, maxTokens: 4096 })
32
+ *
33
+ * await memory.save('session-1', [
34
+ * { role: 'user', content: 'Hello' },
35
+ * { role: 'assistant', content: 'Hi there!' },
36
+ * ])
37
+ *
38
+ * const history = await memory.load('session-1')
39
+ * ```
40
+ */
41
+ export declare class BufferMemory implements MemoryProvider {
42
+ /** @internal Map of session IDs to their message arrays. */
43
+ private sessions;
44
+ /** @internal Maximum token budget (undefined = unlimited). */
45
+ private maxTokens;
46
+ /** @internal Maximum message count (undefined = unlimited). */
47
+ private maxMessages;
48
+ /**
49
+ * Creates a new `BufferMemory` instance.
50
+ *
51
+ * @param options - Optional configuration for message limits and trimming.
52
+ */
53
+ constructor(options?: BufferMemoryOptions);
54
+ /**
55
+ * Loads the conversation history for a given session.
56
+ *
57
+ * @param sessionId - Unique identifier for the conversation session.
58
+ * @returns A shallow copy of the stored messages, or an empty array if the
59
+ * session does not exist.
60
+ */
61
+ load(sessionId: string): Promise<Message[]>;
62
+ /**
63
+ * Saves messages for a session, applying any configured trimming limits
64
+ * before storing.
65
+ *
66
+ * @param sessionId - Unique identifier for the conversation session.
67
+ * @param messages - The full list of messages to persist.
68
+ */
69
+ save(sessionId: string, messages: Message[]): Promise<void>;
70
+ /**
71
+ * Removes all stored messages for a session.
72
+ *
73
+ * @param sessionId - Unique identifier for the conversation session to clear.
74
+ */
75
+ clear(sessionId: string): Promise<void>;
76
+ /**
77
+ * Applies the configured `maxMessages` and `maxTokens` limits to a list of
78
+ * messages. System messages are always preserved; only non-system messages
79
+ * are subject to trimming (most-recent messages are kept).
80
+ *
81
+ * @internal
82
+ * @param messages - The messages to trim.
83
+ * @returns A new array containing only the messages that fit within the
84
+ * configured limits.
85
+ */
86
+ private trimMessages;
87
+ }
package/dist/file.d.ts ADDED
@@ -0,0 +1,96 @@
1
+ import type { MemoryProvider, Message } from '@agentier/core';
2
+ /**
3
+ * Configuration options for {@link FileMemory}.
4
+ */
5
+ export interface FileMemoryOptions {
6
+ /**
7
+ * Path to the storage location. If the path ends with `.json`, all sessions
8
+ * are stored in that single file (keyed by session ID). Otherwise the path
9
+ * is treated as a directory and each session gets its own
10
+ * `<sessionId>.json` file.
11
+ */
12
+ path: string;
13
+ /**
14
+ * Maximum number of messages to retain per session.
15
+ * System messages are always preserved; the limit applies to the remainder.
16
+ */
17
+ maxMessages?: number;
18
+ }
19
+ /**
20
+ * A filesystem-backed {@link MemoryProvider} that persists conversation
21
+ * history as JSON files on disk.
22
+ *
23
+ * Two storage modes are supported depending on the `path` option:
24
+ * - **Directory mode** (`path` does not end with `.json`) -- each session is
25
+ * written to its own `<sessionId>.json` file inside the directory.
26
+ * - **Single-file mode** (`path` ends with `.json`) -- all sessions are
27
+ * stored as keys in a single JSON object.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { FileMemory } from '@agentier/memory'
32
+ *
33
+ * // Directory mode -- one file per session
34
+ * const memory = new FileMemory({ path: './data/sessions' })
35
+ *
36
+ * // Single-file mode -- all sessions in one file
37
+ * const shared = new FileMemory({ path: './data/memory.json', maxMessages: 100 })
38
+ * ```
39
+ */
40
+ export declare class FileMemory implements MemoryProvider {
41
+ /** @internal Absolute or relative base path for storage. */
42
+ private basePath;
43
+ /** @internal Whether the provider is operating in directory mode. */
44
+ private isDirectory;
45
+ /** @internal Optional cap on persisted message count. */
46
+ private maxMessages;
47
+ /**
48
+ * Creates a new `FileMemory` instance.
49
+ *
50
+ * @param options - Configuration specifying the storage path and optional
51
+ * message limit.
52
+ */
53
+ constructor(options: FileMemoryOptions);
54
+ /**
55
+ * Resolves the on-disk file path for a given session.
56
+ *
57
+ * @internal
58
+ * @param sessionId - The session identifier.
59
+ * @returns The absolute or relative path to the JSON file.
60
+ */
61
+ private getFilePath;
62
+ /**
63
+ * Returns the JSON key used to store messages for the session.
64
+ *
65
+ * @internal
66
+ * @param sessionId - The session identifier.
67
+ * @returns `'messages'` in directory mode, or the session ID in
68
+ * single-file mode.
69
+ */
70
+ private getStorageKey;
71
+ /**
72
+ * Loads the conversation history for a given session from disk.
73
+ *
74
+ * @param sessionId - Unique identifier for the conversation session.
75
+ * @returns The stored messages, or an empty array if the session file does
76
+ * not exist or cannot be parsed.
77
+ */
78
+ load(sessionId: string): Promise<Message[]>;
79
+ /**
80
+ * Persists messages for a session to disk, applying the optional
81
+ * `maxMessages` limit before writing. Parent directories are created
82
+ * automatically if they do not exist.
83
+ *
84
+ * @param sessionId - Unique identifier for the conversation session.
85
+ * @param messages - The full list of messages to persist.
86
+ */
87
+ save(sessionId: string, messages: Message[]): Promise<void>;
88
+ /**
89
+ * Removes conversation history for a session. In directory mode the
90
+ * session file is deleted; in single-file mode the session key is removed
91
+ * from the shared JSON object.
92
+ *
93
+ * @param sessionId - Unique identifier for the conversation session to clear.
94
+ */
95
+ clear(sessionId: string): Promise<void>;
96
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @module @agentier/memory
3
+ *
4
+ * Provides pluggable memory providers for persisting agent conversation
5
+ * history. Two built-in implementations are included:
6
+ *
7
+ * - {@link BufferMemory} -- fast, in-memory storage with optional token/message
8
+ * limits.
9
+ * - {@link FileMemory} -- filesystem-backed storage using JSON files.
10
+ */
11
+ export { BufferMemory } from './buffer';
12
+ export type { BufferMemoryOptions } from './buffer';
13
+ export { FileMemory } from './file';
14
+ export type { FileMemoryOptions } from './file';
package/dist/index.js ADDED
@@ -0,0 +1,134 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
4
+ // src/buffer.ts
5
+ function estimateTokens(message) {
6
+ const content = message.content ?? "";
7
+ return Math.ceil(content.length / 4);
8
+ }
9
+
10
+ class BufferMemory {
11
+ sessions = new Map;
12
+ maxTokens;
13
+ maxMessages;
14
+ constructor(options) {
15
+ this.maxTokens = options?.maxTokens;
16
+ this.maxMessages = options?.maxMessages;
17
+ }
18
+ async load(sessionId) {
19
+ return [...this.sessions.get(sessionId) ?? []];
20
+ }
21
+ async save(sessionId, messages) {
22
+ let trimmed = [...messages];
23
+ trimmed = this.trimMessages(trimmed);
24
+ this.sessions.set(sessionId, trimmed);
25
+ }
26
+ async clear(sessionId) {
27
+ this.sessions.delete(sessionId);
28
+ }
29
+ trimMessages(messages) {
30
+ let result = [...messages];
31
+ if (this.maxMessages && result.length > this.maxMessages) {
32
+ const system = result.filter((m) => m.role === "system");
33
+ const rest = result.filter((m) => m.role !== "system");
34
+ const keep = rest.slice(rest.length - (this.maxMessages - system.length));
35
+ result = [...system, ...keep];
36
+ }
37
+ if (this.maxTokens) {
38
+ const system = result.filter((m) => m.role === "system");
39
+ const rest = result.filter((m) => m.role !== "system");
40
+ let totalTokens = system.reduce((sum, m) => sum + estimateTokens(m), 0);
41
+ const kept = [];
42
+ for (let i = rest.length - 1;i >= 0; i--) {
43
+ const msgTokens = estimateTokens(rest[i]);
44
+ if (totalTokens + msgTokens > this.maxTokens)
45
+ break;
46
+ kept.unshift(rest[i]);
47
+ totalTokens += msgTokens;
48
+ }
49
+ result = [...system, ...kept];
50
+ }
51
+ return result;
52
+ }
53
+ }
54
+ // src/file.ts
55
+ import { readFile, writeFile, mkdir } from "fs/promises";
56
+ import { join, dirname } from "path";
57
+ import { existsSync } from "fs";
58
+
59
+ class FileMemory {
60
+ basePath;
61
+ isDirectory;
62
+ maxMessages;
63
+ constructor(options) {
64
+ this.basePath = options.path;
65
+ this.isDirectory = !options.path.endsWith(".json");
66
+ this.maxMessages = options.maxMessages;
67
+ }
68
+ getFilePath(sessionId) {
69
+ if (this.isDirectory) {
70
+ return join(this.basePath, `${sessionId}.json`);
71
+ }
72
+ return this.basePath;
73
+ }
74
+ getStorageKey(sessionId) {
75
+ return this.isDirectory ? "messages" : sessionId;
76
+ }
77
+ async load(sessionId) {
78
+ const filePath = this.getFilePath(sessionId);
79
+ try {
80
+ const content = await readFile(filePath, "utf-8");
81
+ const data = JSON.parse(content);
82
+ if (this.isDirectory) {
83
+ return Array.isArray(data) ? data : [];
84
+ }
85
+ return Array.isArray(data[sessionId]) ? data[sessionId] : [];
86
+ } catch {
87
+ return [];
88
+ }
89
+ }
90
+ async save(sessionId, messages) {
91
+ const filePath = this.getFilePath(sessionId);
92
+ let trimmed = messages;
93
+ if (this.maxMessages && messages.length > this.maxMessages) {
94
+ const system = messages.filter((m) => m.role === "system");
95
+ const rest = messages.filter((m) => m.role !== "system");
96
+ trimmed = [...system, ...rest.slice(rest.length - (this.maxMessages - system.length))];
97
+ }
98
+ const dir = dirname(filePath);
99
+ if (!existsSync(dir)) {
100
+ await mkdir(dir, { recursive: true });
101
+ }
102
+ if (this.isDirectory) {
103
+ await writeFile(filePath, JSON.stringify(trimmed, null, 2), "utf-8");
104
+ } else {
105
+ let data = {};
106
+ try {
107
+ const content = await readFile(filePath, "utf-8");
108
+ data = JSON.parse(content);
109
+ } catch {}
110
+ data[sessionId] = trimmed;
111
+ await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
112
+ }
113
+ }
114
+ async clear(sessionId) {
115
+ if (this.isDirectory) {
116
+ const filePath = this.getFilePath(sessionId);
117
+ try {
118
+ const { unlink } = await import("fs/promises");
119
+ await unlink(filePath);
120
+ } catch {}
121
+ } else {
122
+ try {
123
+ const content = await readFile(this.basePath, "utf-8");
124
+ const data = JSON.parse(content);
125
+ delete data[sessionId];
126
+ await writeFile(this.basePath, JSON.stringify(data, null, 2), "utf-8");
127
+ } catch {}
128
+ }
129
+ }
130
+ }
131
+ export {
132
+ FileMemory,
133
+ BufferMemory
134
+ };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@agentier/memory",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "scripts": {
17
+ "build": "bun build ./src/index.ts --outdir ./dist --target node",
18
+ "test": "bun test",
19
+ "typecheck": "tsc --noEmit"
20
+ },
21
+ "peerDependencies": {
22
+ "@agentier/core": "^0.1.0"
23
+ },
24
+ "devDependencies": {
25
+ "@agentier/core": "workspace:*",
26
+ "typescript": "^5.5.0",
27
+ "@types/bun": "latest"
28
+ }
29
+ }