@simulacra-ai/session 0.0.1

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/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # Simulacra Session
2
+
3
+ Session persistence for the Simulacra conversation engine. Manages saving, loading, and labeling conversation sessions with pluggable storage backends.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @simulacra-ai/core @simulacra-ai/session
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { SessionManager, FileSessionStore } from "@simulacra-ai/session";
15
+
16
+ const store = new FileSessionStore("./sessions");
17
+ const session = new SessionManager(store, conversation);
18
+
19
+ session.start_new("my first session");
20
+ // ... conversation happens ...
21
+ await session.save();
22
+ ```
23
+
24
+ With `auto_save` (default `true`), messages are saved automatically after each model response.
25
+
26
+ ## SessionManager
27
+
28
+ `SessionManager` coordinates a conversation with a storage backend and handles the lifecycle of sessions: creation, loading, saving, and disposal.
29
+
30
+ ```typescript
31
+ new SessionManager(store, conversation, options?)
32
+ ```
33
+
34
+ Option|Type|Default|Description
35
+ -|-|-|-
36
+ `auto_save`|`boolean`|`true`|Save after every `message_complete` event
37
+ `auto_slug`|`boolean`|`true`|Derive a label from the first user message
38
+
39
+ ### Methods
40
+
41
+ Method|Description
42
+ -|-
43
+ `start_new(label?)`|Begin a new session, returns the session ID
44
+ `load(id?)`|Load a session by ID, or the most recent if omitted
45
+ `save(metadata?)`|Persist current messages and metadata
46
+ `fork(parent_id, options?)`|Create a child session branching from a parent
47
+ `list()`|List all sessions from the store
48
+ `delete(id)`|Remove a session
49
+ `rename(id, label)`|Change a session's label
50
+
51
+ ### Events
52
+
53
+ Event|Payload|When
54
+ -|-|-
55
+ `load`|`{ id, messages }`|Session loaded from store
56
+ `save`|`{ id, messages }`|Session written to store
57
+ `dispose`|(none)|Manager disposed
58
+
59
+ ### Auto-Slug
60
+
61
+ When `auto_slug` is enabled, `SessionManager` derives a label from the first ~50 characters of the first user message (trimmed to a word boundary). This runs once on the first save; explicitly setting a label via `start_new(label)` or `rename()` takes precedence.
62
+
63
+ ### Child Sessions
64
+
65
+ `SessionManager` listens for the conversation's `create_child` event. When a child conversation is spawned — via orchestration, checkpoints, or `spawn_child` — the session manager automatically creates a child session backed by a detached fork. Child sessions auto-save independently and are disposed when the child conversation ends.
66
+
67
+ Checkpoint children have `auto_slug` disabled since their sessions are internal.
68
+
69
+ ## Built-in Stores
70
+
71
+ ### FileSessionStore
72
+
73
+ Persists sessions as JSON files on disk. Each session is a single `{id}.json` file.
74
+
75
+ ```typescript
76
+ import { FileSessionStore } from "@simulacra-ai/session";
77
+
78
+ const store = new FileSessionStore("./data/sessions");
79
+ ```
80
+
81
+ Child session relationships are indexed using hard links under `{parent-id}-forks/` directories.
82
+
83
+ ### InMemorySessionStore
84
+
85
+ Non-persistent store for testing. Sessions live in a `Map` and are lost on process exit.
86
+
87
+ ```typescript
88
+ import { InMemorySessionStore } from "@simulacra-ai/session";
89
+
90
+ const store = new InMemorySessionStore();
91
+ ```
92
+
93
+ ## SessionStore Interface
94
+
95
+ Custom storage backends implement the `SessionStore` interface. See the [extensibility guide](EXTENSIBILITY.md) for implementation details and examples.
96
+
97
+ ```typescript
98
+ interface SessionStore {
99
+ list(): Promise<SessionMetadata[]>;
100
+ load(id: string): Promise<{ metadata: SessionMetadata; messages: Message[] } | undefined>;
101
+ save(id: string, messages: Message[], metadata?: Partial<SessionMetadata>): Promise<void>;
102
+ delete(id: string): Promise<boolean>;
103
+ }
104
+ ```
105
+
106
+ ## License
107
+
108
+ MIT
@@ -0,0 +1,60 @@
1
+ import type { Message } from "@simulacra-ai/core";
2
+ import type { SessionMetadata, SessionStore } from "./types.ts";
3
+ /**
4
+ * A file-based implementation of SessionStore.
5
+ *
6
+ * This store persists sessions as JSON files in a specified directory. Each session
7
+ * is stored in a separate file named with its session ID. The store also maintains
8
+ * hard links for fork relationships to enable efficient querying of session trees.
9
+ */
10
+ export declare class FileSessionStore implements SessionStore {
11
+ #private;
12
+ /**
13
+ * Creates a new FileSessionStore instance.
14
+ *
15
+ * @param root - The absolute path to the directory where session files will be stored.
16
+ */
17
+ constructor(root: string);
18
+ /**
19
+ * Lists all sessions stored in the file system.
20
+ *
21
+ * Reads all JSON files from the root directory and parses their metadata.
22
+ *
23
+ * @returns A promise that resolves to an array of session metadata, sorted by most recently updated first.
24
+ */
25
+ list(): Promise<SessionMetadata[]>;
26
+ /**
27
+ * Loads a session from the file system.
28
+ *
29
+ * @param id - The unique identifier of the session to load.
30
+ * @returns A promise that resolves to the session metadata and messages, or undefined if not found.
31
+ */
32
+ load(id: string): Promise<{
33
+ metadata: SessionMetadata;
34
+ messages: Message[];
35
+ } | undefined>;
36
+ /**
37
+ * Saves a session to the file system.
38
+ *
39
+ * Creates a new session file if the ID does not exist, or updates an existing file.
40
+ * Automatically updates the updated_at timestamp and message_count. If the session
41
+ * has a parent, creates a hard link in the parent's fork directory for efficient querying.
42
+ *
43
+ * @param id - The unique identifier of the session.
44
+ * @param messages - The messages to store for this session.
45
+ * @param metadata - Optional partial metadata to merge with existing metadata.
46
+ * @returns A promise that resolves when the save operation is complete.
47
+ */
48
+ save(id: string, messages: Message[], metadata?: Partial<SessionMetadata>): Promise<void>;
49
+ /**
50
+ * Deletes a session from the file system.
51
+ *
52
+ * Removes the session file, any hard links in parent fork directories, and the session's
53
+ * own fork directory if it exists.
54
+ *
55
+ * @param id - The unique identifier of the session to delete.
56
+ * @returns A promise that resolves to true if the session was deleted, false if it was not found.
57
+ */
58
+ delete(id: string): Promise<boolean>;
59
+ }
60
+ //# sourceMappingURL=file-session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-session-store.d.ts","sourceRoot":"","sources":["../src/file-session-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAOhE;;;;;;GAMG;AACH,qBAAa,gBAAiB,YAAW,YAAY;;IAGnD;;;;OAIG;gBACS,IAAI,EAAE,MAAM;IAIxB;;;;;;OAMG;IACG,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAqBxC;;;;;OAKG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM;kBAyGuB,eAAe;;;IArG3D;;;;;;;;;;;OAWG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC;IA2B/E;;;;;;;;OAQG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM;CA0ExB"}
@@ -0,0 +1,166 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ /**
4
+ * A file-based implementation of SessionStore.
5
+ *
6
+ * This store persists sessions as JSON files in a specified directory. Each session
7
+ * is stored in a separate file named with its session ID. The store also maintains
8
+ * hard links for fork relationships to enable efficient querying of session trees.
9
+ */
10
+ export class FileSessionStore {
11
+ #root;
12
+ /**
13
+ * Creates a new FileSessionStore instance.
14
+ *
15
+ * @param root - The absolute path to the directory where session files will be stored.
16
+ */
17
+ constructor(root) {
18
+ this.#root = root;
19
+ }
20
+ /**
21
+ * Lists all sessions stored in the file system.
22
+ *
23
+ * Reads all JSON files from the root directory and parses their metadata.
24
+ *
25
+ * @returns A promise that resolves to an array of session metadata, sorted by most recently updated first.
26
+ */
27
+ async list() {
28
+ await this.#ensure_dir();
29
+ const sessions = [];
30
+ const entries = await fs.readdir(this.#root, { withFileTypes: true });
31
+ for (const entry of entries) {
32
+ if (!entry.isFile() || !entry.name.endsWith(".json")) {
33
+ continue;
34
+ }
35
+ const session = await this.#read_session(path.join(this.#root, entry.name), entry.name.replace(/\.json$/, ""));
36
+ if (session) {
37
+ sessions.push(session);
38
+ }
39
+ }
40
+ return sessions.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
41
+ }
42
+ /**
43
+ * Loads a session from the file system.
44
+ *
45
+ * @param id - The unique identifier of the session to load.
46
+ * @returns A promise that resolves to the session metadata and messages, or undefined if not found.
47
+ */
48
+ async load(id) {
49
+ return this.#read_file(path.join(this.#root, `${id}.json`), id);
50
+ }
51
+ /**
52
+ * Saves a session to the file system.
53
+ *
54
+ * Creates a new session file if the ID does not exist, or updates an existing file.
55
+ * Automatically updates the updated_at timestamp and message_count. If the session
56
+ * has a parent, creates a hard link in the parent's fork directory for efficient querying.
57
+ *
58
+ * @param id - The unique identifier of the session.
59
+ * @param messages - The messages to store for this session.
60
+ * @param metadata - Optional partial metadata to merge with existing metadata.
61
+ * @returns A promise that resolves when the save operation is complete.
62
+ */
63
+ async save(id, messages, metadata) {
64
+ const now = new Date().toISOString();
65
+ await this.#ensure_dir();
66
+ const file_path = path.join(this.#root, `${id}.json`);
67
+ const existing = await this.#read_file(file_path, id);
68
+ const existing_metadata = existing?.metadata ?? {};
69
+ const parent_id = metadata?.parent_id ?? existing_metadata.parent_id;
70
+ const file = {
71
+ metadata: {
72
+ ...existing_metadata,
73
+ created_at: existing_metadata.created_at ?? now,
74
+ updated_at: now,
75
+ message_count: messages.length,
76
+ ...metadata,
77
+ },
78
+ messages,
79
+ };
80
+ await fs.writeFile(file_path, JSON.stringify(file, null, 2), "utf8");
81
+ if (parent_id) {
82
+ await this.#ensure_fork_link(parent_id, id);
83
+ }
84
+ }
85
+ /**
86
+ * Deletes a session from the file system.
87
+ *
88
+ * Removes the session file, any hard links in parent fork directories, and the session's
89
+ * own fork directory if it exists.
90
+ *
91
+ * @param id - The unique identifier of the session to delete.
92
+ * @returns A promise that resolves to true if the session was deleted, false if it was not found.
93
+ */
94
+ async delete(id) {
95
+ const file_path = path.join(this.#root, `${id}.json`);
96
+ try {
97
+ const result = await this.#read_file(file_path, id);
98
+ await fs.unlink(file_path);
99
+ if (result?.metadata.parent_id) {
100
+ const link = path.join(this.#root, `${result.metadata.parent_id}-forks`, `${id}.json`);
101
+ try {
102
+ await fs.unlink(link);
103
+ }
104
+ catch {
105
+ /* link may not exist */
106
+ }
107
+ }
108
+ const fork_dir = path.join(this.#root, `${id}-forks`);
109
+ try {
110
+ await fs.rm(fork_dir, { recursive: true });
111
+ }
112
+ catch {
113
+ /* no forks */
114
+ }
115
+ return true;
116
+ }
117
+ catch {
118
+ return false;
119
+ }
120
+ }
121
+ async #ensure_fork_link(parent_id, fork_id) {
122
+ const fork_dir = path.join(this.#root, `${parent_id}-forks`);
123
+ await fs.mkdir(fork_dir, { recursive: true });
124
+ const canonical = path.join(this.#root, `${fork_id}.json`);
125
+ const link = path.join(fork_dir, `${fork_id}.json`);
126
+ try {
127
+ await fs.unlink(link);
128
+ }
129
+ catch {
130
+ /* doesn't exist yet */
131
+ }
132
+ try {
133
+ await fs.link(canonical, link);
134
+ }
135
+ catch {
136
+ // hard links can fail across filesystems — fall back to no index
137
+ }
138
+ }
139
+ async #read_file(file_path, id) {
140
+ try {
141
+ const content = await fs.readFile(file_path, "utf8");
142
+ const data = JSON.parse(content);
143
+ return {
144
+ metadata: { id, ...data.metadata },
145
+ messages: data.messages,
146
+ };
147
+ }
148
+ catch {
149
+ return undefined;
150
+ }
151
+ }
152
+ async #read_session(file_path, id) {
153
+ try {
154
+ const content = await fs.readFile(file_path, "utf8");
155
+ const data = JSON.parse(content);
156
+ return { id, ...data.metadata };
157
+ }
158
+ catch {
159
+ return undefined;
160
+ }
161
+ }
162
+ async #ensure_dir() {
163
+ await fs.mkdir(this.#root, { recursive: true });
164
+ }
165
+ }
166
+ //# sourceMappingURL=file-session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-session-store.js","sourceRoot":"","sources":["../src/file-session-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { Message } from "@simulacra-ai/core";
2
+ import type { SessionMetadata, SessionStore } from "./types.ts";
3
+ /**
4
+ * An in-memory implementation of SessionStore.
5
+ *
6
+ * This store keeps all session data in memory using a Map. Data is not persisted
7
+ * across process restarts. Useful for testing or scenarios where persistence is not required.
8
+ */
9
+ export declare class InMemorySessionStore implements SessionStore {
10
+ #private;
11
+ /**
12
+ * Lists all sessions stored in memory.
13
+ *
14
+ * @returns A promise that resolves to an array of session metadata, sorted by most recently updated first.
15
+ */
16
+ list(): Promise<SessionMetadata[]>;
17
+ /**
18
+ * Loads a session from memory.
19
+ *
20
+ * Returns a deep clone of the stored data to prevent external mutations.
21
+ *
22
+ * @param id - The unique identifier of the session to load.
23
+ * @returns A promise that resolves to the session metadata and messages, or undefined if not found.
24
+ */
25
+ load(id: string): Promise<{
26
+ metadata: {
27
+ id: string;
28
+ created_at: string;
29
+ updated_at: string;
30
+ provider?: string;
31
+ model?: string;
32
+ label?: string;
33
+ message_count: number;
34
+ parent_id?: string;
35
+ fork_message_id?: string;
36
+ detached?: boolean;
37
+ is_checkpoint?: boolean;
38
+ checkpoint_state?: import("@simulacra-ai/core").CheckpointState;
39
+ };
40
+ messages: Message[];
41
+ } | undefined>;
42
+ /**
43
+ * Saves a session to memory.
44
+ *
45
+ * Creates a new session if the ID does not exist, or updates an existing session.
46
+ * Automatically updates the updated_at timestamp and message_count.
47
+ *
48
+ * @param id - The unique identifier of the session.
49
+ * @param messages - The messages to store for this session.
50
+ * @param metadata - Optional partial metadata to merge with existing metadata.
51
+ * @returns A promise that resolves when the save operation is complete.
52
+ */
53
+ save(id: string, messages: Message[], metadata?: Partial<SessionMetadata>): Promise<void>;
54
+ /**
55
+ * Deletes a session from memory.
56
+ *
57
+ * @param id - The unique identifier of the session to delete.
58
+ * @returns A promise that resolves to true if the session was deleted, false if it was not found.
59
+ */
60
+ delete(id: string): Promise<boolean>;
61
+ }
62
+ //# sourceMappingURL=in-memory-session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-session-store.d.ts","sourceRoot":"","sources":["../src/in-memory-session-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;GAKG;AACH,qBAAa,oBAAqB,YAAW,YAAY;;IAGvD;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAMxC;;;;;;;OAOG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;IAWrB;;;;;;;;;;OAUG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC;IAiB/E;;;;;OAKG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM;CAGxB"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * An in-memory implementation of SessionStore.
3
+ *
4
+ * This store keeps all session data in memory using a Map. Data is not persisted
5
+ * across process restarts. Useful for testing or scenarios where persistence is not required.
6
+ */
7
+ export class InMemorySessionStore {
8
+ #sessions = new Map();
9
+ /**
10
+ * Lists all sessions stored in memory.
11
+ *
12
+ * @returns A promise that resolves to an array of session metadata, sorted by most recently updated first.
13
+ */
14
+ async list() {
15
+ return [...this.#sessions.values()]
16
+ .map((s) => s.metadata)
17
+ .sort((a, b) => b.updated_at.localeCompare(a.updated_at));
18
+ }
19
+ /**
20
+ * Loads a session from memory.
21
+ *
22
+ * Returns a deep clone of the stored data to prevent external mutations.
23
+ *
24
+ * @param id - The unique identifier of the session to load.
25
+ * @returns A promise that resolves to the session metadata and messages, or undefined if not found.
26
+ */
27
+ async load(id) {
28
+ const entry = this.#sessions.get(id);
29
+ if (!entry) {
30
+ return undefined;
31
+ }
32
+ return {
33
+ metadata: { ...entry.metadata },
34
+ messages: structuredClone(entry.messages),
35
+ };
36
+ }
37
+ /**
38
+ * Saves a session to memory.
39
+ *
40
+ * Creates a new session if the ID does not exist, or updates an existing session.
41
+ * Automatically updates the updated_at timestamp and message_count.
42
+ *
43
+ * @param id - The unique identifier of the session.
44
+ * @param messages - The messages to store for this session.
45
+ * @param metadata - Optional partial metadata to merge with existing metadata.
46
+ * @returns A promise that resolves when the save operation is complete.
47
+ */
48
+ async save(id, messages, metadata) {
49
+ const now = new Date().toISOString();
50
+ const existing = this.#sessions.get(id);
51
+ this.#sessions.set(id, {
52
+ metadata: {
53
+ id,
54
+ ...existing?.metadata,
55
+ created_at: existing?.metadata.created_at ?? now,
56
+ updated_at: now,
57
+ message_count: messages.length,
58
+ ...metadata,
59
+ },
60
+ messages: structuredClone(messages),
61
+ });
62
+ }
63
+ /**
64
+ * Deletes a session from memory.
65
+ *
66
+ * @param id - The unique identifier of the session to delete.
67
+ * @returns A promise that resolves to true if the session was deleted, false if it was not found.
68
+ */
69
+ async delete(id) {
70
+ return this.#sessions.delete(id);
71
+ }
72
+ }
73
+ //# sourceMappingURL=in-memory-session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-session-store.js","sourceRoot":"","sources":["../src/in-memory-session-store.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,OAAO,oBAAoB;IACtB,SAAS,GAAG,IAAI,GAAG,EAA8D,CAAC;IAE3F;;;;OAIG;IACH,KAAK,CAAC,IAAI;QACR,OAAO,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;aAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;aACtB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,IAAI,CAAC,EAAU;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO;YACL,QAAQ,EAAE,EAAE,GAAG,KAAK,CAAC,QAAQ,EAAE;YAC/B,QAAQ,EAAE,eAAe,CAAC,KAAK,CAAC,QAAQ,CAAC;SAC1C,CAAC;IACJ,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,IAAI,CAAC,EAAU,EAAE,QAAmB,EAAE,QAAmC;QAC7E,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAExC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE;YACrB,QAAQ,EAAE;gBACR,EAAE;gBACF,GAAG,QAAQ,EAAE,QAAQ;gBACrB,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,UAAU,IAAI,GAAG;gBAChD,UAAU,EAAE,GAAG;gBACf,aAAa,EAAE,QAAQ,CAAC,MAAM;gBAC9B,GAAG,QAAQ;aACZ;YACD,QAAQ,EAAE,eAAe,CAAC,QAAQ,CAAC;SACpC,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ export type { SessionMetadata, SessionStore, SessionManagerOptions, SessionManagerEvents, } from "./types.ts";
2
+ export { SessionManager } from "./session-manager.ts";
3
+ export { FileSessionStore } from "./file-session-store.ts";
4
+ export { InMemorySessionStore } from "./in-memory-session-store.ts";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,YAAY,EACZ,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { SessionManager } from "./session-manager.js";
2
+ export { FileSessionStore } from "./file-session-store.js";
3
+ export { InMemorySessionStore } from "./in-memory-session-store.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,131 @@
1
+ import type { Conversation } from "@simulacra-ai/core";
2
+ import type { SessionMetadata, SessionStore, SessionManagerOptions, SessionManagerEvents } from "./types.ts";
3
+ /**
4
+ * Manages conversation sessions including creation, loading, saving, and forking.
5
+ *
6
+ * The SessionManager acts as a bridge between a Conversation instance and a SessionStore,
7
+ * handling session lifecycle, automatic saving, and session tree management through forking.
8
+ * It supports disposable resource management and event emission for session operations.
9
+ */
10
+ export declare class SessionManager {
11
+ #private;
12
+ /**
13
+ * Creates a new SessionManager instance.
14
+ *
15
+ * @param store - The storage backend to use for persisting sessions.
16
+ * @param conversation - The conversation instance to manage.
17
+ * @param options - Optional configuration for session management behavior.
18
+ */
19
+ constructor(store: SessionStore, conversation: Conversation, options?: SessionManagerOptions);
20
+ /**
21
+ * The ID of the currently active session.
22
+ *
23
+ * @returns The session ID if a session is active, otherwise undefined.
24
+ */
25
+ get current_session_id(): string | undefined;
26
+ /**
27
+ * Whether a session is currently loaded.
28
+ *
29
+ * @returns True if a session is active, false otherwise.
30
+ */
31
+ get is_loaded(): boolean;
32
+ /**
33
+ * Disposes of the SessionManager and cleans up resources.
34
+ *
35
+ * This method removes event listeners, disposes child sessions, and emits the dispose event.
36
+ * It is called automatically when the associated conversation is disposed or when using
37
+ * explicit resource management.
38
+ */
39
+ [Symbol.dispose](): void;
40
+ /**
41
+ * Starts a new session with a freshly generated ID.
42
+ *
43
+ * This method clears the conversation history and creates a new session. If auto_save
44
+ * is enabled or a label is provided, the session is immediately saved to the store.
45
+ *
46
+ * @param label - Optional label to assign to the new session.
47
+ * @returns The generated session ID.
48
+ */
49
+ start_new(label?: string): string;
50
+ /**
51
+ * Creates a new session as a fork of an existing parent session.
52
+ *
53
+ * A fork creates a new session that inherits messages from the parent session up to
54
+ * the fork point. If detached, the fork does not inherit any messages from the parent
55
+ * but still maintains a parent reference for organizational purposes.
56
+ *
57
+ * @param parent_session_id - The ID of the session to fork from.
58
+ * @param options - Optional configuration for the fork operation.
59
+ * @param options.detached - Whether to create a detached fork that does not inherit parent messages.
60
+ * @returns A promise that resolves to the generated session ID for the fork.
61
+ */
62
+ fork(parent_session_id: string, options?: {
63
+ detached?: boolean;
64
+ }): Promise<string>;
65
+ /**
66
+ * Loads a session by ID or loads the most recent session if no ID is provided.
67
+ *
68
+ * If no session ID is provided and no sessions exist in the store, this method
69
+ * starts a new session automatically. Loading a session resolves its full message
70
+ * history by recursively following parent references.
71
+ *
72
+ * @param id - The ID of the session to load, or undefined to load the most recent session.
73
+ * @returns A promise that resolves when the session is loaded.
74
+ * @throws {Error} If the specified session ID is not found in the store.
75
+ */
76
+ load(id?: string): Promise<void>;
77
+ /**
78
+ * Saves the current session to the store.
79
+ *
80
+ * This method persists only the messages owned by this session, excluding any messages
81
+ * inherited from parent sessions. If auto_slug is enabled and no label has been set,
82
+ * a label is automatically generated from the first user message.
83
+ *
84
+ * @param metadata - Optional partial metadata to update on the session.
85
+ * @returns A promise that resolves when the save operation is complete.
86
+ * @throws {Error} If no session is currently active.
87
+ */
88
+ save(metadata?: Partial<Pick<SessionMetadata, "label" | "provider" | "model" | "parent_id" | "fork_message_id" | "detached">>): Promise<void>;
89
+ /**
90
+ * Lists all sessions stored in the store.
91
+ *
92
+ * @returns A promise that resolves to an array of session metadata, typically sorted by last update time.
93
+ */
94
+ list(): Promise<SessionMetadata[]>;
95
+ /**
96
+ * Deletes a session from the store.
97
+ *
98
+ * If the deleted session is currently active, the conversation is cleared and the
99
+ * current session is unset.
100
+ *
101
+ * @param id - The ID of the session to delete.
102
+ * @returns A promise that resolves to true if the session was deleted, false if it was not found.
103
+ */
104
+ delete(id: string): Promise<boolean>;
105
+ /**
106
+ * Renames a session by updating its label.
107
+ *
108
+ * @param id - The ID of the session to rename.
109
+ * @param label - The new label to assign to the session.
110
+ * @returns A promise that resolves when the rename operation is complete.
111
+ * @throws {Error} If the specified session ID is not found in the store.
112
+ */
113
+ rename(id: string, label: string): Promise<void>;
114
+ /**
115
+ * Registers an event listener for the specified event.
116
+ *
117
+ * @param event - The name of the event to listen for.
118
+ * @param listener - The callback function to invoke when the event is emitted.
119
+ * @returns This SessionManager instance for method chaining.
120
+ */
121
+ on<E extends keyof SessionManagerEvents>(event: E, listener: (...args: SessionManagerEvents[E]) => void): this;
122
+ /**
123
+ * Removes an event listener for the specified event.
124
+ *
125
+ * @param event - The name of the event to stop listening for.
126
+ * @param listener - The callback function to remove.
127
+ * @returns This SessionManager instance for method chaining.
128
+ */
129
+ off<E extends keyof SessionManagerEvents>(event: E, listener: (...args: SessionManagerEvents[E]) => void): this;
130
+ }
131
+ //# sourceMappingURL=session-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAW,MAAM,oBAAoB,CAAC;AAChE,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,qBAAa,cAAc;;IAazB;;;;;;OAMG;gBACS,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,qBAAqB;IAa5F;;;;OAIG;IACH,IAAI,kBAAkB,uBAErB;IAED;;;;OAIG;IACH,IAAI,SAAS,YAEZ;IAED;;;;;;OAMG;IACH,CAAC,MAAM,CAAC,OAAO,CAAC;IAkBhB;;;;;;;;OAQG;IACH,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM;IAmBjC;;;;;;;;;;;OAWG;IACG,IAAI,CAAC,iBAAiB,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAqCxF;;;;;;;;;;OAUG;IACG,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BtC;;;;;;;;;;OAUG;IACG,IAAI,CACR,QAAQ,CAAC,EAAE,OAAO,CAChB,IAAI,CACF,eAAe,EACf,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,WAAW,GAAG,iBAAiB,GAAG,UAAU,CAC9E,CACF;IAkCH;;;;OAIG;IACG,IAAI;IAIV;;;;;;;;OAQG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM;IAQvB;;;;;;;OAOG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAWtC;;;;;;OAMG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,oBAAoB,EACrC,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,oBAAoB,CAAC,CAAC,CAAC,KAAK,IAAI,GACnD,IAAI;IAMP;;;;;;OAMG;IACH,GAAG,CAAC,CAAC,SAAS,MAAM,oBAAoB,EACtC,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,oBAAoB,CAAC,CAAC,CAAC,KAAK,IAAI,GACnD,IAAI;CA6FR"}
@@ -0,0 +1,366 @@
1
+ var _a;
2
+ import { randomUUID } from "node:crypto";
3
+ import EventEmitter from "node:events";
4
+ /**
5
+ * Manages conversation sessions including creation, loading, saving, and forking.
6
+ *
7
+ * The SessionManager acts as a bridge between a Conversation instance and a SessionStore,
8
+ * handling session lifecycle, automatic saving, and session tree management through forking.
9
+ * It supports disposable resource management and event emission for session operations.
10
+ */
11
+ export class SessionManager {
12
+ #store;
13
+ #conversation;
14
+ #auto_save;
15
+ #auto_slug;
16
+ #event_emitter = new EventEmitter();
17
+ #child_sessions = [];
18
+ #session_id;
19
+ #fork_offset = 0;
20
+ #has_label = false;
21
+ #disposed = false;
22
+ /**
23
+ * Creates a new SessionManager instance.
24
+ *
25
+ * @param store - The storage backend to use for persisting sessions.
26
+ * @param conversation - The conversation instance to manage.
27
+ * @param options - Optional configuration for session management behavior.
28
+ */
29
+ constructor(store, conversation, options) {
30
+ this.#store = store;
31
+ this.#conversation = conversation;
32
+ this.#auto_save = options?.auto_save ?? true;
33
+ this.#auto_slug = options?.auto_slug ?? true;
34
+ if (this.#auto_save) {
35
+ this.#conversation.on("message_complete", this.#on_message_complete);
36
+ }
37
+ this.#conversation.on("create_child", this.#on_create_child);
38
+ this.#conversation.once("dispose", this.#on_conversation_dispose);
39
+ }
40
+ /**
41
+ * The ID of the currently active session.
42
+ *
43
+ * @returns The session ID if a session is active, otherwise undefined.
44
+ */
45
+ get current_session_id() {
46
+ return this.#session_id;
47
+ }
48
+ /**
49
+ * Whether a session is currently loaded.
50
+ *
51
+ * @returns True if a session is active, false otherwise.
52
+ */
53
+ get is_loaded() {
54
+ return !!this.#session_id;
55
+ }
56
+ /**
57
+ * Disposes of the SessionManager and cleans up resources.
58
+ *
59
+ * This method removes event listeners, disposes child sessions, and emits the dispose event.
60
+ * It is called automatically when the associated conversation is disposed or when using
61
+ * explicit resource management.
62
+ */
63
+ [Symbol.dispose]() {
64
+ if (this.#disposed) {
65
+ return;
66
+ }
67
+ this.#disposed = true;
68
+ for (const child of this.#child_sessions) {
69
+ child[Symbol.dispose]();
70
+ }
71
+ this.#child_sessions.length = 0;
72
+ this.#conversation.off("message_complete", this.#on_message_complete);
73
+ this.#conversation.off("create_child", this.#on_create_child);
74
+ this.#conversation.off("dispose", this.#on_conversation_dispose);
75
+ this.#event_emitter.emit("dispose");
76
+ this.#event_emitter.removeAllListeners();
77
+ }
78
+ /**
79
+ * Starts a new session with a freshly generated ID.
80
+ *
81
+ * This method clears the conversation history and creates a new session. If auto_save
82
+ * is enabled or a label is provided, the session is immediately saved to the store.
83
+ *
84
+ * @param label - Optional label to assign to the new session.
85
+ * @returns The generated session ID.
86
+ */
87
+ start_new(label) {
88
+ this.#session_id = randomUUID();
89
+ this.#fork_offset = 0;
90
+ this.#has_label = !!label;
91
+ if (this.#conversation.messages.length > 0) {
92
+ this.#conversation.clear();
93
+ }
94
+ if (label || this.#auto_save) {
95
+ this.save({ label }).catch((error) => {
96
+ this.#event_emitter.emit("lifecycle_error", {
97
+ error,
98
+ operation: "save",
99
+ context: { session_id: this.#session_id },
100
+ });
101
+ });
102
+ }
103
+ return this.#session_id;
104
+ }
105
+ /**
106
+ * Creates a new session as a fork of an existing parent session.
107
+ *
108
+ * A fork creates a new session that inherits messages from the parent session up to
109
+ * the fork point. If detached, the fork does not inherit any messages from the parent
110
+ * but still maintains a parent reference for organizational purposes.
111
+ *
112
+ * @param parent_session_id - The ID of the session to fork from.
113
+ * @param options - Optional configuration for the fork operation.
114
+ * @param options.detached - Whether to create a detached fork that does not inherit parent messages.
115
+ * @returns A promise that resolves to the generated session ID for the fork.
116
+ */
117
+ async fork(parent_session_id, options) {
118
+ const detached = options?.detached ?? false;
119
+ this.#session_id = randomUUID();
120
+ this.#has_label = false;
121
+ if (!detached) {
122
+ this.#conversation.clear();
123
+ }
124
+ let fork_message_id;
125
+ if (!detached) {
126
+ const messages = await this.#resolve_messages(parent_session_id);
127
+ if (messages.length > 0) {
128
+ this.#conversation.load(messages);
129
+ this.#fork_offset = messages.length;
130
+ fork_message_id = messages.at(-1)?.id;
131
+ }
132
+ else {
133
+ this.#fork_offset = 0;
134
+ }
135
+ }
136
+ else {
137
+ this.#fork_offset = 0;
138
+ }
139
+ const parent_result = await this.#store.load(parent_session_id);
140
+ if (parent_result?.metadata.checkpoint_state) {
141
+ this.#conversation.checkpoint_state = parent_result.metadata.checkpoint_state;
142
+ }
143
+ await this.save({
144
+ parent_id: parent_session_id,
145
+ fork_message_id,
146
+ detached,
147
+ });
148
+ return this.#session_id;
149
+ }
150
+ /**
151
+ * Loads a session by ID or loads the most recent session if no ID is provided.
152
+ *
153
+ * If no session ID is provided and no sessions exist in the store, this method
154
+ * starts a new session automatically. Loading a session resolves its full message
155
+ * history by recursively following parent references.
156
+ *
157
+ * @param id - The ID of the session to load, or undefined to load the most recent session.
158
+ * @returns A promise that resolves when the session is loaded.
159
+ * @throws {Error} If the specified session ID is not found in the store.
160
+ */
161
+ async load(id) {
162
+ if (id) {
163
+ const messages = await this.#resolve_messages(id);
164
+ const result = await this.#store.load(id);
165
+ if (!result) {
166
+ throw new Error(`session not found: ${id}`);
167
+ }
168
+ this.#session_id = id;
169
+ this.#has_label = !!result.metadata.label;
170
+ this.#conversation.clear();
171
+ this.#fork_offset = messages.length - result.messages.length;
172
+ this.#conversation.load(messages);
173
+ this.#conversation.checkpoint_state = result.metadata.checkpoint_state;
174
+ this.#emit_load(messages);
175
+ return;
176
+ }
177
+ const sessions = await this.#store.list();
178
+ if (!sessions.length) {
179
+ this.start_new();
180
+ return;
181
+ }
182
+ const latest = sessions[0];
183
+ return this.load(latest.id);
184
+ }
185
+ /**
186
+ * Saves the current session to the store.
187
+ *
188
+ * This method persists only the messages owned by this session, excluding any messages
189
+ * inherited from parent sessions. If auto_slug is enabled and no label has been set,
190
+ * a label is automatically generated from the first user message.
191
+ *
192
+ * @param metadata - Optional partial metadata to update on the session.
193
+ * @returns A promise that resolves when the save operation is complete.
194
+ * @throws {Error} If no session is currently active.
195
+ */
196
+ async save(metadata) {
197
+ if (!this.#session_id) {
198
+ throw new Error("no active session");
199
+ }
200
+ const all_messages = this.#conversation.messages;
201
+ const owned_messages = [...all_messages].slice(this.#fork_offset);
202
+ if (this.#auto_slug && !metadata?.label && !this.#has_label) {
203
+ const slug = _a.#derive_slug(all_messages);
204
+ if (slug) {
205
+ metadata = { ...metadata, label: slug };
206
+ this.#has_label = true;
207
+ }
208
+ }
209
+ const conversation_metadata = { ...metadata };
210
+ if (this.#conversation.is_checkpoint) {
211
+ conversation_metadata.is_checkpoint = true;
212
+ }
213
+ const checkpoint_state = this.#conversation.checkpoint_state;
214
+ if (checkpoint_state) {
215
+ conversation_metadata.checkpoint_state = { ...checkpoint_state };
216
+ }
217
+ await this.#store.save(this.#session_id, owned_messages, conversation_metadata);
218
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
219
+ this.#event_emitter.emit("save", {
220
+ id: this.#session_id,
221
+ messages: Object.freeze([...all_messages]),
222
+ });
223
+ }
224
+ /**
225
+ * Lists all sessions stored in the store.
226
+ *
227
+ * @returns A promise that resolves to an array of session metadata, typically sorted by last update time.
228
+ */
229
+ async list() {
230
+ return this.#store.list();
231
+ }
232
+ /**
233
+ * Deletes a session from the store.
234
+ *
235
+ * If the deleted session is currently active, the conversation is cleared and the
236
+ * current session is unset.
237
+ *
238
+ * @param id - The ID of the session to delete.
239
+ * @returns A promise that resolves to true if the session was deleted, false if it was not found.
240
+ */
241
+ async delete(id) {
242
+ if (id === this.#session_id) {
243
+ this.#session_id = undefined;
244
+ this.#conversation.clear();
245
+ }
246
+ return this.#store.delete(id);
247
+ }
248
+ /**
249
+ * Renames a session by updating its label.
250
+ *
251
+ * @param id - The ID of the session to rename.
252
+ * @param label - The new label to assign to the session.
253
+ * @returns A promise that resolves when the rename operation is complete.
254
+ * @throws {Error} If the specified session ID is not found in the store.
255
+ */
256
+ async rename(id, label) {
257
+ const result = await this.#store.load(id);
258
+ if (!result) {
259
+ throw new Error(`session not found: ${id}`);
260
+ }
261
+ await this.#store.save(id, result.messages, { label });
262
+ if (id === this.#session_id) {
263
+ this.#has_label = true;
264
+ }
265
+ }
266
+ /**
267
+ * Registers an event listener for the specified event.
268
+ *
269
+ * @param event - The name of the event to listen for.
270
+ * @param listener - The callback function to invoke when the event is emitted.
271
+ * @returns This SessionManager instance for method chaining.
272
+ */
273
+ on(event, listener) {
274
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
275
+ this.#event_emitter.on(event, listener);
276
+ return this;
277
+ }
278
+ /**
279
+ * Removes an event listener for the specified event.
280
+ *
281
+ * @param event - The name of the event to stop listening for.
282
+ * @param listener - The callback function to remove.
283
+ * @returns This SessionManager instance for method chaining.
284
+ */
285
+ off(event, listener) {
286
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
287
+ this.#event_emitter.off(event, listener);
288
+ return this;
289
+ }
290
+ #emit_load(messages) {
291
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
292
+ this.#event_emitter.emit("load", {
293
+ id: this.#session_id,
294
+ messages: Object.freeze([...messages]),
295
+ });
296
+ }
297
+ async #resolve_messages(id) {
298
+ const result = await this.#store.load(id);
299
+ if (!result) {
300
+ return [];
301
+ }
302
+ const { metadata, messages } = result;
303
+ if (metadata.parent_id && !metadata.detached) {
304
+ const parent_messages = await this.#resolve_messages(metadata.parent_id);
305
+ if (metadata.fork_message_id) {
306
+ const cut = parent_messages.findIndex((m) => m.id === metadata.fork_message_id);
307
+ if (cut >= 0) {
308
+ return [...parent_messages.slice(0, cut + 1), ...messages];
309
+ }
310
+ }
311
+ return [...parent_messages, ...messages];
312
+ }
313
+ return messages;
314
+ }
315
+ static #derive_slug(messages) {
316
+ const first_user = messages.find((m) => m.role === "user");
317
+ if (!first_user) {
318
+ return undefined;
319
+ }
320
+ const text_content = first_user.content.find((c) => c.type === "text");
321
+ if (!text_content || text_content.type !== "text" || !text_content.text) {
322
+ return undefined;
323
+ }
324
+ const raw = text_content.text.trim();
325
+ if (!raw) {
326
+ return undefined;
327
+ }
328
+ const truncated = raw.slice(0, 60);
329
+ const at_boundary = truncated.replace(/\s+\S*$/, "");
330
+ return (at_boundary || truncated).slice(0, 50);
331
+ }
332
+ #on_create_child = (child) => {
333
+ if (!this.#session_id) {
334
+ return;
335
+ }
336
+ const child_session = new _a(this.#store, child, {
337
+ auto_save: this.#auto_save,
338
+ auto_slug: !child.is_checkpoint && this.#auto_slug,
339
+ });
340
+ child_session.fork(this.#session_id, { detached: true }).catch((error) => {
341
+ this.#event_emitter.emit("lifecycle_error", { error, operation: "fork" });
342
+ });
343
+ this.#child_sessions.push(child_session);
344
+ child.once("dispose", () => {
345
+ child_session[Symbol.dispose]();
346
+ const idx = this.#child_sessions.indexOf(child_session);
347
+ if (idx >= 0) {
348
+ this.#child_sessions.splice(idx, 1);
349
+ }
350
+ });
351
+ };
352
+ #on_message_complete = () => {
353
+ if (this.#session_id) {
354
+ this.save().catch((error) => {
355
+ this.#event_emitter.emit("lifecycle_error", {
356
+ error,
357
+ operation: "save",
358
+ context: { session_id: this.#session_id },
359
+ });
360
+ });
361
+ }
362
+ };
363
+ #on_conversation_dispose = () => this[Symbol.dispose]();
364
+ }
365
+ _a = SessionManager;
366
+ //# sourceMappingURL=session-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../src/session-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ import type { CheckpointState, LifecycleErrorEvent, Message } from "@simulacra-ai/core";
2
+ /**
3
+ * Metadata that describes a conversation session.
4
+ *
5
+ * Sessions can form a tree structure through forking, where a child session
6
+ * can inherit messages from its parent session up to a specific fork point.
7
+ */
8
+ export interface SessionMetadata {
9
+ /** The unique identifier for the session. */
10
+ id: string;
11
+ /** ISO 8601 timestamp when the session was created. */
12
+ created_at: string;
13
+ /** ISO 8601 timestamp when the session was last updated. */
14
+ updated_at: string;
15
+ /** The AI provider used in this session (e.g., "anthropic", "openai"). */
16
+ provider?: string;
17
+ /** The model identifier used in this session (e.g., "claude-3-5-sonnet-20241022"). */
18
+ model?: string;
19
+ /** A human-readable label or title for the session. */
20
+ label?: string;
21
+ /** The number of messages stored in this session. */
22
+ message_count: number;
23
+ /** The ID of the parent session if this is a fork. */
24
+ parent_id?: string;
25
+ /** The ID of the last message from the parent session included in this fork. */
26
+ fork_message_id?: string;
27
+ /** Whether this fork is detached and does not inherit parent messages. */
28
+ detached?: boolean;
29
+ /** Whether this session is a checkpoint summarization session. */
30
+ is_checkpoint?: boolean;
31
+ /** The latest checkpoint state for this conversation. */
32
+ checkpoint_state?: CheckpointState;
33
+ }
34
+ /**
35
+ * A storage backend for conversation sessions.
36
+ *
37
+ * Implementations are responsible for persisting sessions and their messages,
38
+ * whether in memory, on disk, or in a database.
39
+ */
40
+ export interface SessionStore {
41
+ /**
42
+ * Lists all stored sessions.
43
+ *
44
+ * @returns A promise that resolves to an array of session metadata, typically sorted by last update time.
45
+ */
46
+ list(): Promise<SessionMetadata[]>;
47
+ /**
48
+ * Loads a session by its ID.
49
+ *
50
+ * @param id - The unique identifier of the session to load.
51
+ * @returns A promise that resolves to the session metadata and messages, or undefined if not found.
52
+ */
53
+ load(id: string): Promise<{
54
+ metadata: SessionMetadata;
55
+ messages: Message[];
56
+ } | undefined>;
57
+ /**
58
+ * Saves a session with the given messages and metadata.
59
+ *
60
+ * If the session already exists, it updates the existing entry. If it does not exist, it creates a new one.
61
+ *
62
+ * @param id - The unique identifier of the session.
63
+ * @param messages - The messages to store for this session.
64
+ * @param metadata - Optional partial metadata to merge with existing or create new metadata.
65
+ * @returns A promise that resolves when the save operation is complete.
66
+ */
67
+ save(id: string, messages: Message[], metadata?: Partial<SessionMetadata>): Promise<void>;
68
+ /**
69
+ * Deletes a session by its ID.
70
+ *
71
+ * @param id - The unique identifier of the session to delete.
72
+ * @returns A promise that resolves to true if the session was deleted, false if it was not found.
73
+ */
74
+ delete(id: string): Promise<boolean>;
75
+ }
76
+ /**
77
+ * Configuration options for SessionManager behavior.
78
+ */
79
+ export interface SessionManagerOptions {
80
+ /**
81
+ * Whether to automatically save the session after each completed message.
82
+ *
83
+ * @defaultValue true
84
+ */
85
+ auto_save?: boolean;
86
+ /**
87
+ * Whether to automatically generate a label from the first user message.
88
+ *
89
+ * @defaultValue true
90
+ */
91
+ auto_slug?: boolean;
92
+ }
93
+ /**
94
+ * Events emitted by SessionManager instances.
95
+ *
96
+ * Each event key maps to a tuple representing the arguments passed to event listeners.
97
+ */
98
+ export interface SessionManagerEvents {
99
+ /** Emitted when a session is loaded. */
100
+ load: [{
101
+ id: string;
102
+ messages: Readonly<Message[]>;
103
+ }];
104
+ /** Emitted when a session is saved. */
105
+ save: [{
106
+ id: string;
107
+ messages: Readonly<Message[]>;
108
+ }];
109
+ /** Emitted when an infrastructure or lifecycle operation fails. */
110
+ lifecycle_error: [LifecycleErrorEvent];
111
+ /** Emitted when the SessionManager is disposed. */
112
+ dispose: [];
113
+ }
114
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAExF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,yDAAyD;IACzD,gBAAgB,CAAC,EAAE,eAAe,CAAC;CACpC;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAEnC;;;;;OAKG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,eAAe,CAAC;QAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;KAAE,GAAG,SAAS,CAAC,CAAC;IAE1F;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,QAAQ,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1F;;;;;OAKG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,IAAI,EAAE,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IACtD,uCAAuC;IACvC,IAAI,EAAE,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;IACtD,mEAAmE;IACnE,eAAe,EAAE,CAAC,mBAAmB,CAAC,CAAC;IACvC,mDAAmD;IACnD,OAAO,EAAE,EAAE,CAAC;CACb"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@simulacra-ai/session",
3
+ "version": "0.0.1",
4
+ "description": "Session persistence for the Simulacra conversation engine — pluggable storage with file and in-memory providers",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ }
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "clean": "rm -rf dist *.tsbuildinfo"
18
+ },
19
+ "peerDependencies": {
20
+ "@simulacra-ai/core": "0.0.1"
21
+ },
22
+ "license": "MIT"
23
+ }