@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.
- package/dist/buffer.d.ts +87 -0
- package/dist/file.d.ts +96 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +134 -0
- package/package.json +29 -0
package/dist/buffer.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|