@librechat/agents 3.1.86 → 3.1.87

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.
Files changed (160) hide show
  1. package/README.md +69 -0
  2. package/dist/cjs/events.cjs +23 -0
  3. package/dist/cjs/events.cjs.map +1 -1
  4. package/dist/cjs/graphs/Graph.cjs +133 -18
  5. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  6. package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  8. package/dist/cjs/llm/anthropic/index.cjs +251 -53
  9. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  10. package/dist/cjs/llm/init.cjs +1 -5
  11. package/dist/cjs/llm/init.cjs.map +1 -1
  12. package/dist/cjs/llm/openai/index.cjs +113 -24
  13. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  14. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  15. package/dist/cjs/llm/openrouter/index.cjs +3 -1
  16. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  17. package/dist/cjs/main.cjs +18 -5
  18. package/dist/cjs/main.cjs.map +1 -1
  19. package/dist/cjs/openai/index.cjs +253 -0
  20. package/dist/cjs/openai/index.cjs.map +1 -0
  21. package/dist/cjs/responses/index.cjs +448 -0
  22. package/dist/cjs/responses/index.cjs.map +1 -0
  23. package/dist/cjs/run.cjs +108 -7
  24. package/dist/cjs/run.cjs.map +1 -1
  25. package/dist/cjs/session/AgentSession.cjs +1057 -0
  26. package/dist/cjs/session/AgentSession.cjs.map +1 -0
  27. package/dist/cjs/session/JsonlSessionStore.cjs +425 -0
  28. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -0
  29. package/dist/cjs/session/handlers.cjs +221 -0
  30. package/dist/cjs/session/handlers.cjs.map +1 -0
  31. package/dist/cjs/session/ids.cjs +22 -0
  32. package/dist/cjs/session/ids.cjs.map +1 -0
  33. package/dist/cjs/session/messageSerialization.cjs +179 -0
  34. package/dist/cjs/session/messageSerialization.cjs.map +1 -0
  35. package/dist/cjs/stream.cjs +472 -11
  36. package/dist/cjs/stream.cjs.map +1 -1
  37. package/dist/cjs/summarization/node.cjs +1 -1
  38. package/dist/cjs/summarization/node.cjs.map +1 -1
  39. package/dist/cjs/tools/ToolNode.cjs +177 -59
  40. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  41. package/dist/cjs/tools/eagerEventExecution.cjs +113 -0
  42. package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -0
  43. package/dist/cjs/tools/handlers.cjs +1 -1
  44. package/dist/cjs/tools/handlers.cjs.map +1 -1
  45. package/dist/cjs/tools/streamedToolCallSeals.cjs +42 -0
  46. package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -0
  47. package/dist/esm/events.mjs +23 -1
  48. package/dist/esm/events.mjs.map +1 -1
  49. package/dist/esm/graphs/Graph.mjs +133 -18
  50. package/dist/esm/graphs/Graph.mjs.map +1 -1
  51. package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
  52. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  53. package/dist/esm/llm/anthropic/index.mjs +251 -53
  54. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  55. package/dist/esm/llm/init.mjs +1 -5
  56. package/dist/esm/llm/init.mjs.map +1 -1
  57. package/dist/esm/llm/openai/index.mjs +113 -25
  58. package/dist/esm/llm/openai/index.mjs.map +1 -1
  59. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  60. package/dist/esm/llm/openrouter/index.mjs +4 -2
  61. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  62. package/dist/esm/main.mjs +5 -1
  63. package/dist/esm/main.mjs.map +1 -1
  64. package/dist/esm/openai/index.mjs +246 -0
  65. package/dist/esm/openai/index.mjs.map +1 -0
  66. package/dist/esm/responses/index.mjs +440 -0
  67. package/dist/esm/responses/index.mjs.map +1 -0
  68. package/dist/esm/run.mjs +108 -7
  69. package/dist/esm/run.mjs.map +1 -1
  70. package/dist/esm/session/AgentSession.mjs +1054 -0
  71. package/dist/esm/session/AgentSession.mjs.map +1 -0
  72. package/dist/esm/session/JsonlSessionStore.mjs +422 -0
  73. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -0
  74. package/dist/esm/session/handlers.mjs +219 -0
  75. package/dist/esm/session/handlers.mjs.map +1 -0
  76. package/dist/esm/session/ids.mjs +17 -0
  77. package/dist/esm/session/ids.mjs.map +1 -0
  78. package/dist/esm/session/messageSerialization.mjs +173 -0
  79. package/dist/esm/session/messageSerialization.mjs.map +1 -0
  80. package/dist/esm/stream.mjs +473 -12
  81. package/dist/esm/stream.mjs.map +1 -1
  82. package/dist/esm/summarization/node.mjs +1 -1
  83. package/dist/esm/summarization/node.mjs.map +1 -1
  84. package/dist/esm/tools/ToolNode.mjs +177 -59
  85. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  86. package/dist/esm/tools/eagerEventExecution.mjs +107 -0
  87. package/dist/esm/tools/eagerEventExecution.mjs.map +1 -0
  88. package/dist/esm/tools/handlers.mjs +1 -1
  89. package/dist/esm/tools/handlers.mjs.map +1 -1
  90. package/dist/esm/tools/streamedToolCallSeals.mjs +36 -0
  91. package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -0
  92. package/dist/types/events.d.ts +1 -0
  93. package/dist/types/graphs/Graph.d.ts +24 -9
  94. package/dist/types/index.d.ts +1 -0
  95. package/dist/types/llm/openai/index.d.ts +1 -0
  96. package/dist/types/openai/index.d.ts +75 -0
  97. package/dist/types/responses/index.d.ts +97 -0
  98. package/dist/types/run.d.ts +2 -0
  99. package/dist/types/session/AgentSession.d.ts +32 -0
  100. package/dist/types/session/JsonlSessionStore.d.ts +67 -0
  101. package/dist/types/session/handlers.d.ts +8 -0
  102. package/dist/types/session/ids.d.ts +4 -0
  103. package/dist/types/session/index.d.ts +5 -0
  104. package/dist/types/session/messageSerialization.d.ts +7 -0
  105. package/dist/types/session/types.d.ts +191 -0
  106. package/dist/types/tools/ToolNode.d.ts +12 -1
  107. package/dist/types/tools/eagerEventExecution.d.ts +23 -0
  108. package/dist/types/tools/streamedToolCallSeals.d.ts +13 -0
  109. package/dist/types/types/hitl.d.ts +4 -0
  110. package/dist/types/types/run.d.ts +11 -1
  111. package/dist/types/types/tools.d.ts +36 -0
  112. package/package.json +19 -2
  113. package/src/__tests__/stream.eagerEventExecution.test.ts +2458 -0
  114. package/src/events.ts +29 -0
  115. package/src/graphs/Graph.ts +224 -50
  116. package/src/graphs/MultiAgentGraph.ts +1 -1
  117. package/src/graphs/__tests__/composition.smoke.test.ts +30 -0
  118. package/src/index.ts +3 -0
  119. package/src/llm/anthropic/index.ts +356 -84
  120. package/src/llm/anthropic/llm.spec.ts +64 -0
  121. package/src/llm/custom-chat-models.smoke.test.ts +175 -4
  122. package/src/llm/openai/contentBlocks.test.ts +35 -0
  123. package/src/llm/openai/deepseek.test.ts +201 -2
  124. package/src/llm/openai/index.ts +171 -26
  125. package/src/llm/openai/utils/index.ts +22 -0
  126. package/src/llm/openrouter/index.ts +4 -2
  127. package/src/openai/__tests__/openai.test.ts +337 -0
  128. package/src/openai/index.ts +404 -0
  129. package/src/responses/__tests__/responses.test.ts +652 -0
  130. package/src/responses/index.ts +677 -0
  131. package/src/run.ts +158 -8
  132. package/src/scripts/compare_pi_vs_ours.ts +592 -173
  133. package/src/scripts/session_live.ts +548 -0
  134. package/src/session/AgentSession.ts +1432 -0
  135. package/src/session/JsonlSessionStore.ts +572 -0
  136. package/src/session/__tests__/JsonlSessionStore.test.ts +1410 -0
  137. package/src/session/__tests__/handlers.test.ts +161 -0
  138. package/src/session/handlers.ts +272 -0
  139. package/src/session/ids.ts +17 -0
  140. package/src/session/index.ts +44 -0
  141. package/src/session/messageSerialization.ts +207 -0
  142. package/src/session/types.ts +275 -0
  143. package/src/specs/custom-event-await.test.ts +89 -0
  144. package/src/specs/summarization.test.ts +1 -1
  145. package/src/stream.ts +755 -48
  146. package/src/summarization/node.ts +1 -1
  147. package/src/tools/ToolNode.ts +299 -126
  148. package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +373 -0
  149. package/src/tools/__tests__/handlers.test.ts +2 -1
  150. package/src/tools/__tests__/hitl.test.ts +206 -110
  151. package/src/tools/eagerEventExecution.ts +153 -0
  152. package/src/tools/handlers.ts +8 -4
  153. package/src/tools/streamedToolCallSeals.ts +57 -0
  154. package/src/types/hitl.ts +4 -0
  155. package/src/types/run.ts +11 -0
  156. package/src/types/tools.ts +36 -0
  157. package/dist/cjs/llm/text.cjs +0 -69
  158. package/dist/cjs/llm/text.cjs.map +0 -1
  159. package/dist/esm/llm/text.mjs +0 -67
  160. package/dist/esm/llm/text.mjs.map +0 -1
@@ -0,0 +1,572 @@
1
+ import { homedir } from 'os';
2
+ import { createHash } from 'crypto';
3
+ import { basename, dirname, isAbsolute, join, resolve } from 'path';
4
+ import {
5
+ access,
6
+ appendFile,
7
+ mkdir,
8
+ readFile,
9
+ readdir,
10
+ stat,
11
+ writeFile,
12
+ } from 'fs/promises';
13
+ import type {
14
+ CreateSessionFileOptions,
15
+ SessionBranchOptions,
16
+ SessionEntry,
17
+ SessionForkOptions,
18
+ SessionHeader,
19
+ SessionLabelEntry,
20
+ SessionListItem,
21
+ SessionMessageEntry,
22
+ SessionCheckpointEntry,
23
+ SessionCompactionEntry,
24
+ SessionRunEventEntry,
25
+ SessionSummaryEntry,
26
+ SessionStateEntry,
27
+ SessionTreeNode,
28
+ } from './types';
29
+ import type { BaseMessage } from '@langchain/core/messages';
30
+ import { SystemMessage } from '@langchain/core/messages';
31
+ import { createEntryId, createSessionId, createTimestamp } from './ids';
32
+ import {
33
+ deserializeMessage,
34
+ getMessageRole,
35
+ serializeMessage,
36
+ toJsonValue,
37
+ } from './messageSerialization';
38
+
39
+ const SESSION_VERSION = 1;
40
+ const DEFAULT_SESSION_ROOT = join(
41
+ homedir(),
42
+ '.librechat',
43
+ 'agents',
44
+ 'sessions'
45
+ );
46
+
47
+ function sanitizeCwd(cwd: string): string {
48
+ const normalized = resolve(cwd);
49
+ return createHash('sha256').update(normalized).digest('hex');
50
+ }
51
+
52
+ function createSessionPath(options: CreateSessionFileOptions): string {
53
+ const sessionId = options.sessionId ?? createSessionId();
54
+ const fileName = `${new Date().toISOString().replace(/[:.]/g, '-')}_${sessionId}.jsonl`;
55
+ return join(DEFAULT_SESSION_ROOT, sanitizeCwd(options.cwd), fileName);
56
+ }
57
+
58
+ async function pathExists(path: string): Promise<boolean> {
59
+ try {
60
+ await access(path);
61
+ return true;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+
67
+ function parseLine(line: string): SessionHeader | SessionEntry | undefined {
68
+ if (line.trim() === '') {
69
+ return undefined;
70
+ }
71
+ try {
72
+ const parsed = JSON.parse(line) as SessionHeader | SessionEntry;
73
+ if (parsed.type === 'session') {
74
+ return parsed;
75
+ }
76
+ if ('id' in parsed && 'parentId' in parsed && 'data' in parsed) {
77
+ return parsed;
78
+ }
79
+ return undefined;
80
+ } catch {
81
+ return undefined;
82
+ }
83
+ }
84
+
85
+ function createHeader(options: CreateSessionFileOptions): SessionHeader {
86
+ return {
87
+ type: 'session',
88
+ version: SESSION_VERSION,
89
+ id: options.sessionId ?? createSessionId(),
90
+ timestamp: createTimestamp(),
91
+ cwd: resolve(options.cwd),
92
+ ...(options.name != null && options.name !== ''
93
+ ? { name: options.name }
94
+ : {}),
95
+ ...(options.parentSession != null && options.parentSession !== ''
96
+ ? { parentSession: options.parentSession }
97
+ : {}),
98
+ };
99
+ }
100
+
101
+ function sortEntries(entries: SessionEntry[]): SessionEntry[] {
102
+ return [...entries].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
103
+ }
104
+
105
+ export class JsonlSessionStore {
106
+ readonly path: string;
107
+ readonly header: SessionHeader;
108
+ private entries: SessionEntry[];
109
+
110
+ private constructor(params: {
111
+ path: string;
112
+ header: SessionHeader;
113
+ entries: SessionEntry[];
114
+ }) {
115
+ this.path = params.path;
116
+ this.header = params.header;
117
+ this.entries = sortEntries(params.entries);
118
+ }
119
+
120
+ static getDefaultRoot(): string {
121
+ return DEFAULT_SESSION_ROOT;
122
+ }
123
+
124
+ static async create(
125
+ options: CreateSessionFileOptions & { path?: string }
126
+ ): Promise<JsonlSessionStore> {
127
+ const sessionId = options.sessionId ?? createSessionId();
128
+ const path =
129
+ options.path != null && options.path !== ''
130
+ ? resolve(options.path)
131
+ : createSessionPath({ ...options, sessionId });
132
+ const header = createHeader({ ...options, sessionId });
133
+ await mkdir(dirname(path), { recursive: true });
134
+ await writeFile(path, `${JSON.stringify(header)}\n`, {
135
+ encoding: 'utf8',
136
+ flag: 'wx',
137
+ });
138
+ return new JsonlSessionStore({ path, header, entries: [] });
139
+ }
140
+
141
+ static async open(pathOrId: string): Promise<JsonlSessionStore> {
142
+ const path = await JsonlSessionStore.resolvePath(pathOrId);
143
+ return JsonlSessionStore.openPath(path);
144
+ }
145
+
146
+ static async openPath(path: string): Promise<JsonlSessionStore> {
147
+ const resolved = resolve(path);
148
+ const raw = await readFile(resolved, 'utf8');
149
+ const parsed = raw
150
+ .split('\n')
151
+ .map((line) => parseLine(line))
152
+ .filter((line): line is SessionHeader | SessionEntry => line != null);
153
+ const header = parsed.find(
154
+ (line): line is SessionHeader => line.type === 'session'
155
+ );
156
+ if (!header) {
157
+ throw new Error(`Invalid session file: ${resolved}`);
158
+ }
159
+ const entries = parsed.filter(
160
+ (line): line is SessionEntry => line.type !== 'session'
161
+ );
162
+ return new JsonlSessionStore({ path: resolved, header, entries });
163
+ }
164
+
165
+ static async resolvePath(pathOrId: string): Promise<string> {
166
+ const candidate = isAbsolute(pathOrId) ? pathOrId : resolve(pathOrId);
167
+ if (await pathExists(candidate)) {
168
+ return candidate;
169
+ }
170
+ const sessions = await JsonlSessionStore.listAll();
171
+ const matches = sessions.filter(
172
+ (item) =>
173
+ item.id.startsWith(pathOrId) || basename(item.path).includes(pathOrId)
174
+ );
175
+ if (matches.length === 1) {
176
+ return matches[0].path;
177
+ }
178
+ if (matches.length > 1) {
179
+ throw new Error(`Session id "${pathOrId}" is ambiguous`);
180
+ }
181
+ throw new Error(`Session not found: ${pathOrId}`);
182
+ }
183
+
184
+ static async list(cwd: string): Promise<SessionListItem[]> {
185
+ const dir = join(DEFAULT_SESSION_ROOT, sanitizeCwd(cwd));
186
+ return JsonlSessionStore.listDirectory(dir);
187
+ }
188
+
189
+ static async listAll(
190
+ root: string = DEFAULT_SESSION_ROOT
191
+ ): Promise<SessionListItem[]> {
192
+ if (!(await pathExists(root))) {
193
+ return [];
194
+ }
195
+ const dirs = await readdir(root, { withFileTypes: true });
196
+ const lists = await Promise.all(
197
+ dirs
198
+ .filter((entry) => entry.isDirectory())
199
+ .map((entry) => JsonlSessionStore.listDirectory(join(root, entry.name)))
200
+ );
201
+ return lists.flat().sort((a, b) => b.timestamp.localeCompare(a.timestamp));
202
+ }
203
+
204
+ private static async listDirectory(dir: string): Promise<SessionListItem[]> {
205
+ if (!(await pathExists(dir))) {
206
+ return [];
207
+ }
208
+ const files = await readdir(dir, { withFileTypes: true });
209
+ const candidates = files
210
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl'))
211
+ .map((entry) => join(dir, entry.name));
212
+ const items = await Promise.all(
213
+ candidates.map(async (path): Promise<SessionListItem | undefined> => {
214
+ try {
215
+ const store = await JsonlSessionStore.open(path);
216
+ return {
217
+ id: store.header.id,
218
+ path,
219
+ cwd: store.header.cwd,
220
+ timestamp: store.header.timestamp,
221
+ name: store.header.name,
222
+ leafId: store.getLeafEntry()?.id ?? null,
223
+ };
224
+ } catch {
225
+ return undefined;
226
+ }
227
+ })
228
+ );
229
+ return items
230
+ .filter((item): item is SessionListItem => item != null)
231
+ .sort((a, b) => b.timestamp.localeCompare(a.timestamp));
232
+ }
233
+
234
+ getEntries(): SessionEntry[] {
235
+ return [...this.entries];
236
+ }
237
+
238
+ getEntry(id: string): SessionEntry | undefined {
239
+ return this.entries.find((entry) => entry.id === id);
240
+ }
241
+
242
+ getChildren(id: string): SessionEntry[] {
243
+ return this.entries.filter((entry) => entry.parentId === id);
244
+ }
245
+
246
+ getTree(): SessionTreeNode[] {
247
+ const byParent = new Map<string | null, SessionEntry[]>();
248
+ for (const entry of this.entries) {
249
+ const siblings = byParent.get(entry.parentId) ?? [];
250
+ siblings.push(entry);
251
+ byParent.set(entry.parentId, siblings);
252
+ }
253
+ const build = (parentId: string | null): SessionTreeNode[] =>
254
+ (byParent.get(parentId) ?? []).map((entry) => ({
255
+ entry,
256
+ children: build(entry.id),
257
+ }));
258
+ return build(null);
259
+ }
260
+
261
+ getLeafEntry(): SessionEntry | undefined {
262
+ const state = [...this.entries]
263
+ .reverse()
264
+ .find(
265
+ (entry): entry is SessionStateEntry => entry.type === 'session_state'
266
+ );
267
+ if (state) {
268
+ return state.data.leafId == null
269
+ ? undefined
270
+ : this.getEntry(state.data.leafId);
271
+ }
272
+ return [...this.entries]
273
+ .reverse()
274
+ .find((entry) => entry.type === 'message' || entry.type === 'summary');
275
+ }
276
+
277
+ getPath(
278
+ entryId: string | undefined = this.getLeafEntry()?.id
279
+ ): SessionEntry[] {
280
+ if (entryId == null || entryId === '') {
281
+ return [];
282
+ }
283
+ const byId = new Map(this.entries.map((entry) => [entry.id, entry]));
284
+ const path: SessionEntry[] = [];
285
+ let current: SessionEntry | undefined = byId.get(entryId);
286
+ while (current) {
287
+ path.push(current);
288
+ current =
289
+ current.parentId == null ? undefined : byId.get(current.parentId);
290
+ }
291
+ return path.reverse();
292
+ }
293
+
294
+ getMessages(entryId?: string): BaseMessage[] {
295
+ const messages: BaseMessage[] = [];
296
+ for (const entry of this.getPath(entryId)) {
297
+ if (entry.type === 'message') {
298
+ messages.push(deserializeMessage(entry.data.message));
299
+ } else if (entry.type === 'summary') {
300
+ messages.push(new SystemMessage(entry.data.text));
301
+ }
302
+ }
303
+ return messages;
304
+ }
305
+
306
+ getForkPoints(): SessionMessageEntry[] {
307
+ return this.entries.filter(
308
+ (entry): entry is SessionMessageEntry =>
309
+ entry.type === 'message' && entry.data.role === 'user'
310
+ );
311
+ }
312
+
313
+ getLabel(entryId: string): string | undefined {
314
+ return [...this.entries]
315
+ .reverse()
316
+ .find(
317
+ (entry): entry is SessionLabelEntry =>
318
+ entry.type === 'label' && entry.data.targetEntryId === entryId
319
+ )?.data.label;
320
+ }
321
+
322
+ async setLabel(entryId: string, label: string): Promise<SessionLabelEntry> {
323
+ return this.appendEntry<SessionLabelEntry>({
324
+ type: 'label',
325
+ parentId: this.getLeafEntry()?.id ?? null,
326
+ data: { targetEntryId: entryId, label },
327
+ });
328
+ }
329
+
330
+ async appendMessage(
331
+ message: BaseMessage,
332
+ parentId: string | null = this.getLeafEntry()?.id ?? null
333
+ ): Promise<SessionMessageEntry> {
334
+ const entry = await this.appendEntry<SessionMessageEntry>({
335
+ type: 'message',
336
+ parentId,
337
+ data: {
338
+ role: getMessageRole(message),
339
+ message: serializeMessage(message),
340
+ },
341
+ });
342
+ await this.setLeaf(entry.id);
343
+ return entry;
344
+ }
345
+
346
+ async appendRunEvent(
347
+ event: string,
348
+ payload?: unknown,
349
+ params?: { runId?: string; threadId?: string }
350
+ ): Promise<SessionRunEventEntry> {
351
+ return this.appendEntry<SessionRunEventEntry>({
352
+ type: 'run_event',
353
+ parentId: this.getLeafEntry()?.id ?? null,
354
+ data: {
355
+ event,
356
+ ...(params?.runId != null && params.runId !== ''
357
+ ? { runId: params.runId }
358
+ : {}),
359
+ ...(params?.threadId != null && params.threadId !== ''
360
+ ? { threadId: params.threadId }
361
+ : {}),
362
+ ...(typeof payload !== 'undefined'
363
+ ? { payload: toJsonValue(payload) }
364
+ : {}),
365
+ },
366
+ });
367
+ }
368
+
369
+ async setLeaf(leafId: string | null): Promise<SessionStateEntry> {
370
+ return this.appendEntry<SessionStateEntry>({
371
+ type: 'session_state',
372
+ parentId: leafId,
373
+ data: { leafId },
374
+ });
375
+ }
376
+
377
+ async appendEntryForCompaction(params: {
378
+ text: string;
379
+ tokenCount?: number;
380
+ retainedEntryIds: string[];
381
+ summarizedEntryIds: string[];
382
+ instructions?: string;
383
+ parentId?: string | null;
384
+ }): Promise<SessionSummaryEntry> {
385
+ const tokenCount =
386
+ typeof params.tokenCount === 'number' &&
387
+ Number.isFinite(params.tokenCount) &&
388
+ params.tokenCount >= 0
389
+ ? params.tokenCount
390
+ : undefined;
391
+ const entry = await this.appendEntry<SessionSummaryEntry>({
392
+ type: 'summary',
393
+ parentId:
394
+ 'parentId' in params
395
+ ? (params.parentId ?? null)
396
+ : (this.getLeafEntry()?.id ?? null),
397
+ data: {
398
+ text: params.text,
399
+ ...(tokenCount != null ? { tokenCount } : {}),
400
+ retainedEntryIds: params.retainedEntryIds,
401
+ summarizedEntryIds: params.summarizedEntryIds,
402
+ ...(params.instructions != null && params.instructions !== ''
403
+ ? { instructions: params.instructions }
404
+ : {}),
405
+ },
406
+ });
407
+ await this.setLeaf(entry.id);
408
+ return entry;
409
+ }
410
+
411
+ async appendCompactionEntry(params: {
412
+ summaryEntryId: string;
413
+ retainedEntryIds: string[];
414
+ summarizedEntryIds: string[];
415
+ }): Promise<SessionCompactionEntry> {
416
+ return this.appendEntry<SessionCompactionEntry>({
417
+ type: 'compaction',
418
+ parentId: this.getLeafEntry()?.id ?? null,
419
+ data: {
420
+ summaryEntryId: params.summaryEntryId,
421
+ retainedEntryIds: params.retainedEntryIds,
422
+ summarizedEntryIds: params.summarizedEntryIds,
423
+ },
424
+ });
425
+ }
426
+
427
+ async appendCheckpoint(params: {
428
+ source: SessionCheckpointEntry['data']['source'];
429
+ threadId: string;
430
+ runId?: string;
431
+ checkpointId?: string;
432
+ checkpointNs?: string;
433
+ parentCheckpointId?: string;
434
+ reason?: string;
435
+ }): Promise<SessionCheckpointEntry> {
436
+ return this.appendEntry<SessionCheckpointEntry>({
437
+ type: 'checkpoint',
438
+ parentId: this.getLeafEntry()?.id ?? null,
439
+ data: {
440
+ provider: 'langgraph',
441
+ source: params.source,
442
+ threadId: params.threadId,
443
+ ...(params.runId != null && params.runId !== ''
444
+ ? { runId: params.runId }
445
+ : {}),
446
+ ...(params.checkpointId != null && params.checkpointId !== ''
447
+ ? { checkpointId: params.checkpointId }
448
+ : {}),
449
+ ...(params.checkpointNs != null
450
+ ? { checkpointNs: params.checkpointNs }
451
+ : {}),
452
+ ...(params.parentCheckpointId != null &&
453
+ params.parentCheckpointId !== ''
454
+ ? { parentCheckpointId: params.parentCheckpointId }
455
+ : {}),
456
+ ...(params.reason != null && params.reason !== ''
457
+ ? { reason: params.reason }
458
+ : {}),
459
+ },
460
+ });
461
+ }
462
+
463
+ getCheckpoints(threadId?: string): SessionCheckpointEntry[] {
464
+ return this.entries.filter(
465
+ (entry): entry is SessionCheckpointEntry =>
466
+ entry.type === 'checkpoint' &&
467
+ (threadId == null || entry.data.threadId === threadId)
468
+ );
469
+ }
470
+
471
+ getLatestCheckpoint(threadId?: string): SessionCheckpointEntry | undefined {
472
+ const checkpoints = this.getCheckpoints(threadId);
473
+ for (let i = checkpoints.length - 1; i >= 0; i--) {
474
+ const checkpoint = checkpoints[i];
475
+ if (checkpoint.data.source === 'reset') {
476
+ return undefined;
477
+ }
478
+ return checkpoint;
479
+ }
480
+ return undefined;
481
+ }
482
+
483
+ async branch(
484
+ entryId: string,
485
+ options: SessionBranchOptions = {}
486
+ ): Promise<SessionEntry | undefined> {
487
+ const target = this.getBranchTarget(entryId, options.position ?? 'at');
488
+ await this.setLeaf(target?.id ?? null);
489
+ return target;
490
+ }
491
+
492
+ async createBranchedSession(
493
+ entryId: string | undefined = this.getLeafEntry()?.id,
494
+ options: SessionForkOptions = {}
495
+ ): Promise<JsonlSessionStore> {
496
+ const target =
497
+ entryId != null && entryId !== ''
498
+ ? this.getBranchTarget(entryId, options.position ?? 'at')
499
+ : this.getLeafEntry();
500
+ const newStore = await JsonlSessionStore.create({
501
+ cwd: options.cwd ?? this.header.cwd,
502
+ name: options.name ?? this.header.name,
503
+ parentSession: this.path,
504
+ });
505
+ const pathEntries = target ? this.getPath(target.id) : [];
506
+ for (const entry of pathEntries) {
507
+ await newStore.appendExistingEntry(entry);
508
+ }
509
+ await newStore.setLeaf(target?.id ?? null);
510
+ return newStore;
511
+ }
512
+
513
+ async clone(options: SessionForkOptions = {}): Promise<JsonlSessionStore> {
514
+ return this.createBranchedSession(this.getLeafEntry()?.id, {
515
+ ...options,
516
+ position: 'at',
517
+ });
518
+ }
519
+
520
+ async fork(
521
+ entryId: string,
522
+ options: SessionForkOptions = {}
523
+ ): Promise<JsonlSessionStore> {
524
+ return this.createBranchedSession(entryId, {
525
+ ...options,
526
+ position: options.position ?? 'before',
527
+ });
528
+ }
529
+
530
+ private getBranchTarget(
531
+ entryId: string,
532
+ position: 'before' | 'at'
533
+ ): SessionEntry | undefined {
534
+ const entry = this.getEntry(entryId);
535
+ if (!entry) {
536
+ throw new Error(`Entry not found: ${entryId}`);
537
+ }
538
+ if (position === 'at') {
539
+ return entry;
540
+ }
541
+ return entry.parentId == null ? undefined : this.getEntry(entry.parentId);
542
+ }
543
+
544
+ private async appendExistingEntry(entry: SessionEntry): Promise<void> {
545
+ this.entries.push(entry);
546
+ await appendFile(this.path, `${JSON.stringify(entry)}\n`, 'utf8');
547
+ }
548
+
549
+ private async appendEntry<TEntry extends SessionEntry>(params: {
550
+ type: TEntry['type'];
551
+ parentId: string | null;
552
+ data: TEntry['data'];
553
+ }): Promise<TEntry> {
554
+ const entry = {
555
+ type: params.type,
556
+ id: createEntryId(),
557
+ parentId: params.parentId,
558
+ timestamp: createTimestamp(),
559
+ data: params.data,
560
+ } as TEntry;
561
+ this.entries.push(entry);
562
+ await appendFile(this.path, `${JSON.stringify(entry)}\n`, 'utf8');
563
+ return entry;
564
+ }
565
+
566
+ async existsOnDisk(): Promise<boolean> {
567
+ const fileStat = await stat(this.path).catch(() => undefined);
568
+ return fileStat?.isFile() === true;
569
+ }
570
+ }
571
+
572
+ export const SessionManager = JsonlSessionStore;