@librechat/agents 3.1.85 → 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 (166) hide show
  1. package/README.md +69 -0
  2. package/dist/cjs/agents/AgentContext.cjs +7 -2
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  4. package/dist/cjs/events.cjs +23 -0
  5. package/dist/cjs/events.cjs.map +1 -1
  6. package/dist/cjs/graphs/Graph.cjs +133 -18
  7. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs +1 -1
  9. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  10. package/dist/cjs/llm/anthropic/index.cjs +251 -53
  11. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  12. package/dist/cjs/llm/init.cjs +1 -5
  13. package/dist/cjs/llm/init.cjs.map +1 -1
  14. package/dist/cjs/llm/openai/index.cjs +113 -24
  15. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  16. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  17. package/dist/cjs/llm/openrouter/index.cjs +3 -1
  18. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  19. package/dist/cjs/main.cjs +18 -5
  20. package/dist/cjs/main.cjs.map +1 -1
  21. package/dist/cjs/openai/index.cjs +253 -0
  22. package/dist/cjs/openai/index.cjs.map +1 -0
  23. package/dist/cjs/responses/index.cjs +448 -0
  24. package/dist/cjs/responses/index.cjs.map +1 -0
  25. package/dist/cjs/run.cjs +108 -7
  26. package/dist/cjs/run.cjs.map +1 -1
  27. package/dist/cjs/session/AgentSession.cjs +1057 -0
  28. package/dist/cjs/session/AgentSession.cjs.map +1 -0
  29. package/dist/cjs/session/JsonlSessionStore.cjs +425 -0
  30. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -0
  31. package/dist/cjs/session/handlers.cjs +221 -0
  32. package/dist/cjs/session/handlers.cjs.map +1 -0
  33. package/dist/cjs/session/ids.cjs +22 -0
  34. package/dist/cjs/session/ids.cjs.map +1 -0
  35. package/dist/cjs/session/messageSerialization.cjs +179 -0
  36. package/dist/cjs/session/messageSerialization.cjs.map +1 -0
  37. package/dist/cjs/stream.cjs +472 -11
  38. package/dist/cjs/stream.cjs.map +1 -1
  39. package/dist/cjs/summarization/node.cjs +1 -1
  40. package/dist/cjs/summarization/node.cjs.map +1 -1
  41. package/dist/cjs/tools/ToolNode.cjs +177 -59
  42. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  43. package/dist/cjs/tools/eagerEventExecution.cjs +113 -0
  44. package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -0
  45. package/dist/cjs/tools/handlers.cjs +1 -1
  46. package/dist/cjs/tools/handlers.cjs.map +1 -1
  47. package/dist/cjs/tools/streamedToolCallSeals.cjs +42 -0
  48. package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -0
  49. package/dist/esm/agents/AgentContext.mjs +7 -2
  50. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  51. package/dist/esm/events.mjs +23 -1
  52. package/dist/esm/events.mjs.map +1 -1
  53. package/dist/esm/graphs/Graph.mjs +133 -18
  54. package/dist/esm/graphs/Graph.mjs.map +1 -1
  55. package/dist/esm/graphs/MultiAgentGraph.mjs +1 -1
  56. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  57. package/dist/esm/llm/anthropic/index.mjs +251 -53
  58. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  59. package/dist/esm/llm/init.mjs +1 -5
  60. package/dist/esm/llm/init.mjs.map +1 -1
  61. package/dist/esm/llm/openai/index.mjs +113 -25
  62. package/dist/esm/llm/openai/index.mjs.map +1 -1
  63. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  64. package/dist/esm/llm/openrouter/index.mjs +4 -2
  65. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  66. package/dist/esm/main.mjs +5 -1
  67. package/dist/esm/main.mjs.map +1 -1
  68. package/dist/esm/openai/index.mjs +246 -0
  69. package/dist/esm/openai/index.mjs.map +1 -0
  70. package/dist/esm/responses/index.mjs +440 -0
  71. package/dist/esm/responses/index.mjs.map +1 -0
  72. package/dist/esm/run.mjs +108 -7
  73. package/dist/esm/run.mjs.map +1 -1
  74. package/dist/esm/session/AgentSession.mjs +1054 -0
  75. package/dist/esm/session/AgentSession.mjs.map +1 -0
  76. package/dist/esm/session/JsonlSessionStore.mjs +422 -0
  77. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -0
  78. package/dist/esm/session/handlers.mjs +219 -0
  79. package/dist/esm/session/handlers.mjs.map +1 -0
  80. package/dist/esm/session/ids.mjs +17 -0
  81. package/dist/esm/session/ids.mjs.map +1 -0
  82. package/dist/esm/session/messageSerialization.mjs +173 -0
  83. package/dist/esm/session/messageSerialization.mjs.map +1 -0
  84. package/dist/esm/stream.mjs +473 -12
  85. package/dist/esm/stream.mjs.map +1 -1
  86. package/dist/esm/summarization/node.mjs +1 -1
  87. package/dist/esm/summarization/node.mjs.map +1 -1
  88. package/dist/esm/tools/ToolNode.mjs +177 -59
  89. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  90. package/dist/esm/tools/eagerEventExecution.mjs +107 -0
  91. package/dist/esm/tools/eagerEventExecution.mjs.map +1 -0
  92. package/dist/esm/tools/handlers.mjs +1 -1
  93. package/dist/esm/tools/handlers.mjs.map +1 -1
  94. package/dist/esm/tools/streamedToolCallSeals.mjs +36 -0
  95. package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -0
  96. package/dist/types/events.d.ts +1 -0
  97. package/dist/types/graphs/Graph.d.ts +24 -9
  98. package/dist/types/index.d.ts +1 -0
  99. package/dist/types/llm/openai/index.d.ts +1 -0
  100. package/dist/types/openai/index.d.ts +75 -0
  101. package/dist/types/responses/index.d.ts +97 -0
  102. package/dist/types/run.d.ts +2 -0
  103. package/dist/types/session/AgentSession.d.ts +32 -0
  104. package/dist/types/session/JsonlSessionStore.d.ts +67 -0
  105. package/dist/types/session/handlers.d.ts +8 -0
  106. package/dist/types/session/ids.d.ts +4 -0
  107. package/dist/types/session/index.d.ts +5 -0
  108. package/dist/types/session/messageSerialization.d.ts +7 -0
  109. package/dist/types/session/types.d.ts +191 -0
  110. package/dist/types/tools/ToolNode.d.ts +12 -1
  111. package/dist/types/tools/eagerEventExecution.d.ts +23 -0
  112. package/dist/types/tools/streamedToolCallSeals.d.ts +13 -0
  113. package/dist/types/types/hitl.d.ts +4 -0
  114. package/dist/types/types/run.d.ts +11 -1
  115. package/dist/types/types/tools.d.ts +36 -0
  116. package/package.json +19 -2
  117. package/src/__tests__/stream.eagerEventExecution.test.ts +2458 -0
  118. package/src/agents/AgentContext.ts +7 -2
  119. package/src/agents/__tests__/AgentContext.test.ts +254 -5
  120. package/src/events.ts +29 -0
  121. package/src/graphs/Graph.ts +224 -50
  122. package/src/graphs/MultiAgentGraph.ts +1 -1
  123. package/src/graphs/__tests__/composition.smoke.test.ts +30 -0
  124. package/src/index.ts +3 -0
  125. package/src/llm/anthropic/index.ts +356 -84
  126. package/src/llm/anthropic/llm.spec.ts +64 -0
  127. package/src/llm/custom-chat-models.smoke.test.ts +175 -4
  128. package/src/llm/openai/contentBlocks.test.ts +35 -0
  129. package/src/llm/openai/deepseek.test.ts +201 -2
  130. package/src/llm/openai/index.ts +171 -26
  131. package/src/llm/openai/utils/index.ts +22 -0
  132. package/src/llm/openrouter/index.ts +4 -2
  133. package/src/openai/__tests__/openai.test.ts +337 -0
  134. package/src/openai/index.ts +404 -0
  135. package/src/responses/__tests__/responses.test.ts +652 -0
  136. package/src/responses/index.ts +677 -0
  137. package/src/run.ts +158 -8
  138. package/src/scripts/compare_pi_vs_ours.ts +592 -173
  139. package/src/scripts/session_live.ts +548 -0
  140. package/src/session/AgentSession.ts +1432 -0
  141. package/src/session/JsonlSessionStore.ts +572 -0
  142. package/src/session/__tests__/JsonlSessionStore.test.ts +1410 -0
  143. package/src/session/__tests__/handlers.test.ts +161 -0
  144. package/src/session/handlers.ts +272 -0
  145. package/src/session/ids.ts +17 -0
  146. package/src/session/index.ts +44 -0
  147. package/src/session/messageSerialization.ts +207 -0
  148. package/src/session/types.ts +275 -0
  149. package/src/specs/custom-event-await.test.ts +89 -0
  150. package/src/specs/summarization.test.ts +1 -1
  151. package/src/stream.ts +755 -48
  152. package/src/summarization/node.ts +1 -1
  153. package/src/tools/ToolNode.ts +299 -126
  154. package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +373 -0
  155. package/src/tools/__tests__/handlers.test.ts +2 -1
  156. package/src/tools/__tests__/hitl.test.ts +206 -110
  157. package/src/tools/eagerEventExecution.ts +153 -0
  158. package/src/tools/handlers.ts +8 -4
  159. package/src/tools/streamedToolCallSeals.ts +57 -0
  160. package/src/types/hitl.ts +4 -0
  161. package/src/types/run.ts +11 -0
  162. package/src/types/tools.ts +36 -0
  163. package/dist/cjs/llm/text.cjs +0 -69
  164. package/dist/cjs/llm/text.cjs.map +0 -1
  165. package/dist/esm/llm/text.mjs +0 -67
  166. package/dist/esm/llm/text.mjs.map +0 -1
@@ -0,0 +1,1432 @@
1
+ import { MemorySaver } from '@langchain/langgraph';
2
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
3
+ import type { RunnableConfig } from '@langchain/core/runnables';
4
+ import type {
5
+ BaseCheckpointSaver,
6
+ CheckpointTuple,
7
+ } from '@langchain/langgraph';
8
+ import type { HookRegistry } from '@/hooks';
9
+ import type {
10
+ AgentSessionConfig,
11
+ AgentSessionCheckpointing,
12
+ AgentSessionCheckpointLookupOptions,
13
+ AgentSessionCheckpointReference,
14
+ AgentSessionInput,
15
+ AgentSessionRunOptions,
16
+ AgentSessionRunResult,
17
+ AgentSessionStream,
18
+ AgentSessionStreamEvent,
19
+ SessionBranchOptions,
20
+ SessionCheckpointEntry,
21
+ SessionCompactOptions,
22
+ SessionEntry,
23
+ SessionForkOptions,
24
+ } from './types';
25
+ import type * as t from '@/types';
26
+ import { AgentContext } from '@/agents/AgentContext';
27
+ import { ContentTypes, GraphEvents } from '@/common';
28
+ import { Run } from '@/run';
29
+ import { createRunId, createSessionId } from './ids';
30
+ import { deserializeMessage } from './messageSerialization';
31
+ import { JsonlSessionStore } from './JsonlSessionStore';
32
+ import { createRunHandlers } from './handlers';
33
+ import { createSummarizeNode } from '@/summarization/node';
34
+
35
+ function isBaseMessage(value: unknown): value is BaseMessage {
36
+ return (
37
+ value instanceof BaseMessage ||
38
+ (value != null &&
39
+ typeof value === 'object' &&
40
+ '_getType' in value &&
41
+ typeof (value as { _getType?: unknown })._getType === 'function')
42
+ );
43
+ }
44
+
45
+ interface NormalizedSessionInput {
46
+ messages: BaseMessage[];
47
+ state: t.IState;
48
+ }
49
+
50
+ function normalizeInput(input: AgentSessionInput): NormalizedSessionInput {
51
+ if (typeof input === 'string') {
52
+ const messages = [new HumanMessage(input)];
53
+ return { messages, state: { messages } };
54
+ }
55
+ if (Array.isArray(input)) {
56
+ return { messages: input, state: { messages: input } };
57
+ }
58
+ if (isBaseMessage(input)) {
59
+ const messages = [input];
60
+ return { messages, state: { messages } };
61
+ }
62
+ return { messages: input.messages, state: input };
63
+ }
64
+
65
+ function contentToText(
66
+ content: Array<t.MessageContentComplex | undefined>
67
+ ): string {
68
+ const chunks: string[] = [];
69
+ for (const part of content) {
70
+ if (!part) {
71
+ continue;
72
+ }
73
+ if (part.type === ContentTypes.TEXT && typeof part.text === 'string') {
74
+ chunks.push(part.text);
75
+ }
76
+ }
77
+ return chunks.join('');
78
+ }
79
+
80
+ function normalizeConfig(config: AgentSessionConfig): {
81
+ runConfig: t.RunConfig;
82
+ cwd: string;
83
+ sessionPath?: string;
84
+ name?: string;
85
+ ephemeral?: boolean;
86
+ checkpointing?: AgentSessionCheckpointing;
87
+ } {
88
+ if ('runConfig' in config) {
89
+ return {
90
+ runConfig: config.runConfig,
91
+ cwd: config.cwd ?? process.cwd(),
92
+ sessionPath: config.sessionPath,
93
+ name: config.name,
94
+ ephemeral: config.ephemeral,
95
+ checkpointing: config.checkpointing,
96
+ };
97
+ }
98
+ const {
99
+ cwd,
100
+ sessionPath,
101
+ name,
102
+ ephemeral,
103
+ checkpointing,
104
+ sessionId: _sessionId,
105
+ ...runConfig
106
+ } = config;
107
+ return {
108
+ runConfig: {
109
+ ...runConfig,
110
+ runId: config.runId ?? createRunId(),
111
+ },
112
+ cwd: cwd ?? process.cwd(),
113
+ sessionPath,
114
+ name,
115
+ ephemeral,
116
+ checkpointing,
117
+ };
118
+ }
119
+
120
+ function isMissingSessionError(error: unknown): boolean {
121
+ if (error == null || typeof error !== 'object') {
122
+ return false;
123
+ }
124
+ const candidate = error as { code?: string; message?: string };
125
+ if (candidate.code === 'ENOENT') {
126
+ return true;
127
+ }
128
+ return candidate.message?.startsWith('Session not found:') === true;
129
+ }
130
+
131
+ async function createStore(params: {
132
+ cwd: string;
133
+ sessionPath?: string;
134
+ name?: string;
135
+ sessionId?: string;
136
+ ephemeral?: boolean;
137
+ }): Promise<JsonlSessionStore | undefined> {
138
+ if (params.ephemeral === true) {
139
+ return undefined;
140
+ }
141
+ if (params.sessionPath != null && params.sessionPath !== '') {
142
+ try {
143
+ return await JsonlSessionStore.openPath(params.sessionPath);
144
+ } catch (error) {
145
+ if (!isMissingSessionError(error)) {
146
+ throw error;
147
+ }
148
+ return JsonlSessionStore.create({
149
+ path: params.sessionPath,
150
+ cwd: params.cwd,
151
+ name: params.name,
152
+ sessionId: params.sessionId,
153
+ });
154
+ }
155
+ }
156
+ return JsonlSessionStore.create({
157
+ cwd: params.cwd,
158
+ name: params.name,
159
+ sessionId: params.sessionId,
160
+ });
161
+ }
162
+
163
+ type InitialSummary = NonNullable<t.AgentInputs['initialSummary']>;
164
+
165
+ function mergeInitialSummary(
166
+ existing: InitialSummary | undefined,
167
+ sessionSummary: InitialSummary | undefined
168
+ ): InitialSummary | undefined {
169
+ if (!existing) {
170
+ return sessionSummary;
171
+ }
172
+ if (!sessionSummary) {
173
+ return existing;
174
+ }
175
+ if (existing.text === sessionSummary.text) {
176
+ return existing;
177
+ }
178
+ return {
179
+ text: `${existing.text}\n\n${sessionSummary.text}`,
180
+ tokenCount: existing.tokenCount + sessionSummary.tokenCount,
181
+ };
182
+ }
183
+
184
+ function applyInitialSummaryToAgent(
185
+ agent: t.AgentInputs,
186
+ initialSummary: InitialSummary | undefined
187
+ ): t.AgentInputs {
188
+ const merged = mergeInitialSummary(agent.initialSummary, initialSummary);
189
+ return merged ? { ...agent, initialSummary: merged } : agent;
190
+ }
191
+
192
+ function applyInitialSummaryToGraphConfig(
193
+ graphConfig: t.RunConfig['graphConfig'],
194
+ initialSummary: InitialSummary | undefined
195
+ ): t.RunConfig['graphConfig'] {
196
+ if (!initialSummary) {
197
+ return graphConfig;
198
+ }
199
+ if ('agents' in graphConfig) {
200
+ return {
201
+ ...graphConfig,
202
+ agents: graphConfig.agents.map((agent) =>
203
+ applyInitialSummaryToAgent(agent, initialSummary)
204
+ ),
205
+ };
206
+ }
207
+ return {
208
+ ...graphConfig,
209
+ initialSummary: mergeInitialSummary(
210
+ graphConfig.initialSummary,
211
+ initialSummary
212
+ ),
213
+ };
214
+ }
215
+
216
+ interface SessionCheckpointingState {
217
+ enabled: boolean;
218
+ checkpointer?: BaseCheckpointSaver;
219
+ disableGraphCheckpointer?: boolean;
220
+ }
221
+
222
+ function isCheckpointSaver(value: unknown): value is BaseCheckpointSaver {
223
+ if (value == null || typeof value !== 'object') {
224
+ return false;
225
+ }
226
+ const candidate = value as Partial<BaseCheckpointSaver>;
227
+ return (
228
+ typeof candidate.getTuple === 'function' &&
229
+ typeof candidate.list === 'function' &&
230
+ typeof candidate.put === 'function' &&
231
+ typeof candidate.putWrites === 'function' &&
232
+ typeof candidate.deleteThread === 'function'
233
+ );
234
+ }
235
+
236
+ function getGraphCheckpointer(
237
+ graphConfig: t.RunConfig['graphConfig']
238
+ ): BaseCheckpointSaver | undefined {
239
+ const checkpointer = graphConfig.compileOptions?.checkpointer;
240
+ return isCheckpointSaver(checkpointer) ? checkpointer : undefined;
241
+ }
242
+
243
+ function createCheckpointingState(
244
+ runConfig: t.RunConfig,
245
+ checkpointing: AgentSessionCheckpointing | undefined
246
+ ): SessionCheckpointingState {
247
+ const graphCheckpointer = getGraphCheckpointer(runConfig.graphConfig);
248
+ if (checkpointing === false) {
249
+ return {
250
+ enabled: false,
251
+ disableGraphCheckpointer: true,
252
+ };
253
+ }
254
+ if (typeof checkpointing === 'object') {
255
+ if (checkpointing.enabled === false) {
256
+ return {
257
+ enabled: false,
258
+ disableGraphCheckpointer: true,
259
+ };
260
+ }
261
+ return {
262
+ enabled: true,
263
+ checkpointer:
264
+ checkpointing.checkpointer ?? graphCheckpointer ?? new MemorySaver(),
265
+ };
266
+ }
267
+ if (checkpointing === true || runConfig.humanInTheLoop?.enabled === true) {
268
+ return {
269
+ enabled: true,
270
+ checkpointer: graphCheckpointer ?? new MemorySaver(),
271
+ };
272
+ }
273
+ return {
274
+ enabled: graphCheckpointer != null,
275
+ checkpointer: graphCheckpointer,
276
+ };
277
+ }
278
+
279
+ function removeCheckpointerFromGraphConfig(
280
+ graphConfig: t.RunConfig['graphConfig']
281
+ ): t.RunConfig['graphConfig'] {
282
+ if (graphConfig.compileOptions?.checkpointer == null) {
283
+ return graphConfig;
284
+ }
285
+ const { checkpointer: _checkpointer, ...compileOptions } =
286
+ graphConfig.compileOptions;
287
+ return {
288
+ ...graphConfig,
289
+ compileOptions,
290
+ };
291
+ }
292
+
293
+ function applyCheckpointerToGraphConfig(
294
+ graphConfig: t.RunConfig['graphConfig'],
295
+ checkpointer: BaseCheckpointSaver | undefined
296
+ ): t.RunConfig['graphConfig'] {
297
+ if (!checkpointer) {
298
+ return graphConfig;
299
+ }
300
+ if (graphConfig.compileOptions?.checkpointer === checkpointer) {
301
+ return graphConfig;
302
+ }
303
+ return {
304
+ ...graphConfig,
305
+ compileOptions: {
306
+ ...(graphConfig.compileOptions ?? {}),
307
+ checkpointer,
308
+ },
309
+ };
310
+ }
311
+
312
+ function applyCheckpointingToGraphConfig(
313
+ graphConfig: t.RunConfig['graphConfig'],
314
+ checkpointing: SessionCheckpointingState
315
+ ): t.RunConfig['graphConfig'] {
316
+ if (checkpointing.disableGraphCheckpointer === true) {
317
+ return removeCheckpointerFromGraphConfig(graphConfig);
318
+ }
319
+ return applyCheckpointerToGraphConfig(
320
+ graphConfig,
321
+ checkpointing.checkpointer
322
+ );
323
+ }
324
+
325
+ function getConfigString(
326
+ config: RunnableConfig | undefined,
327
+ key: string
328
+ ): string | undefined {
329
+ const configurable = config?.configurable as
330
+ | Partial<Record<string, unknown>>
331
+ | undefined;
332
+ const value = configurable?.[key];
333
+ return typeof value === 'string' && value !== '' ? value : undefined;
334
+ }
335
+
336
+ function getConfigOptionalString(
337
+ config: RunnableConfig | undefined,
338
+ key: string
339
+ ): string | undefined {
340
+ const configurable = config?.configurable as
341
+ | Partial<Record<string, unknown>>
342
+ | undefined;
343
+ if (
344
+ configurable == null ||
345
+ !Object.prototype.hasOwnProperty.call(configurable, key)
346
+ ) {
347
+ return undefined;
348
+ }
349
+ const value = configurable[key];
350
+ return typeof value === 'string' ? value : undefined;
351
+ }
352
+
353
+ function createCallerConfig(
354
+ threadId: string,
355
+ options: AgentSessionRunOptions
356
+ ): RunnableConfig & { version: 'v1' | 'v2' } {
357
+ return {
358
+ recursionLimit: 50,
359
+ ...(options.config ?? {}),
360
+ configurable: {
361
+ ...(options.config?.configurable ?? {}),
362
+ thread_id: threadId,
363
+ },
364
+ version: options.config?.version ?? 'v2',
365
+ };
366
+ }
367
+
368
+ function createCheckpointLookupConfig(config: RunnableConfig): RunnableConfig {
369
+ const threadId = getConfigString(config, 'thread_id');
370
+ if (threadId == null) {
371
+ return config;
372
+ }
373
+ const checkpointNs = getConfigString(config, 'checkpoint_ns') ?? '';
374
+ const checkpointId = getConfigString(config, 'checkpoint_id');
375
+ return {
376
+ configurable: {
377
+ ...config.configurable,
378
+ thread_id: threadId,
379
+ checkpoint_ns: checkpointNs,
380
+ ...(checkpointId != null ? { checkpoint_id: checkpointId } : {}),
381
+ },
382
+ };
383
+ }
384
+
385
+ function applyCheckpointReferenceToConfig<
386
+ TConfig extends RunnableConfig & { version?: 'v1' | 'v2' },
387
+ >(
388
+ config: TConfig,
389
+ checkpoint:
390
+ | {
391
+ checkpointId?: string;
392
+ checkpointNs?: string;
393
+ }
394
+ | undefined,
395
+ options?: { overwrite?: boolean }
396
+ ): TConfig {
397
+ if (
398
+ checkpoint?.checkpointId == null ||
399
+ checkpoint.checkpointId === '' ||
400
+ (options?.overwrite !== true &&
401
+ getConfigString(config, 'checkpoint_id') != null)
402
+ ) {
403
+ return config;
404
+ }
405
+ return {
406
+ ...config,
407
+ configurable: {
408
+ ...config.configurable,
409
+ checkpoint_id: checkpoint.checkpointId,
410
+ checkpoint_ns: checkpoint.checkpointNs ?? '',
411
+ },
412
+ };
413
+ }
414
+
415
+ function getStoredCheckpointForConfig(
416
+ store: JsonlSessionStore | undefined,
417
+ threadId: string,
418
+ config: RunnableConfig
419
+ ): SessionCheckpointEntry | undefined {
420
+ const requestedCheckpointNs = getConfigOptionalString(
421
+ config,
422
+ 'checkpoint_ns'
423
+ );
424
+ if (requestedCheckpointNs == null) {
425
+ return store?.getLatestCheckpoint(threadId);
426
+ }
427
+ const checkpoints = store?.getCheckpoints(threadId) ?? [];
428
+ for (let i = checkpoints.length - 1; i >= 0; i--) {
429
+ const checkpoint = checkpoints[i];
430
+ if (checkpoint.data.source === 'reset') {
431
+ return undefined;
432
+ }
433
+ if ((checkpoint.data.checkpointNs ?? '') === requestedCheckpointNs) {
434
+ return checkpoint;
435
+ }
436
+ }
437
+ return undefined;
438
+ }
439
+
440
+ function createLatestCheckpointLookupConfig(
441
+ config: RunnableConfig
442
+ ): RunnableConfig {
443
+ const lookup = createCheckpointLookupConfig(config);
444
+ const { checkpoint_id: _checkpointId, ...configurable } =
445
+ lookup.configurable ?? {};
446
+ return { configurable };
447
+ }
448
+
449
+ async function getLatestCheckpointTuple(
450
+ checkpointer: BaseCheckpointSaver | undefined,
451
+ config: RunnableConfig
452
+ ): Promise<CheckpointTuple | undefined> {
453
+ if (!checkpointer) {
454
+ return undefined;
455
+ }
456
+ const lookupConfig = createLatestCheckpointLookupConfig(config);
457
+ const tuple = await checkpointer.getTuple(lookupConfig);
458
+ if (tuple) {
459
+ return tuple;
460
+ }
461
+ for await (const checkpoint of checkpointer.list(lookupConfig, {
462
+ limit: 1,
463
+ })) {
464
+ return checkpoint;
465
+ }
466
+ return undefined;
467
+ }
468
+
469
+ async function getSelectedCheckpointTuple(
470
+ checkpointer: BaseCheckpointSaver | undefined,
471
+ config: RunnableConfig
472
+ ): Promise<CheckpointTuple | undefined> {
473
+ return checkpointer?.getTuple(createCheckpointLookupConfig(config));
474
+ }
475
+
476
+ function createCheckpointReference(params: {
477
+ threadId: string;
478
+ tuple: CheckpointTuple;
479
+ }): AgentSessionCheckpointReference {
480
+ const checkpointNs =
481
+ getConfigString(params.tuple.config, 'checkpoint_ns') ?? '';
482
+ const parentCheckpointId = getConfigString(
483
+ params.tuple.parentConfig,
484
+ 'checkpoint_id'
485
+ );
486
+ return {
487
+ provider: 'langgraph',
488
+ threadId: params.threadId,
489
+ checkpointId: params.tuple.checkpoint.id,
490
+ checkpointNs,
491
+ ...(parentCheckpointId != null ? { parentCheckpointId } : {}),
492
+ };
493
+ }
494
+
495
+ function createSessionRunState(entries: SessionEntry[]): {
496
+ messages: BaseMessage[];
497
+ initialSummary?: InitialSummary;
498
+ } {
499
+ const messages: BaseMessage[] = [];
500
+ let initialSummary: InitialSummary | undefined;
501
+ for (const entry of entries) {
502
+ if (entry.type === 'summary') {
503
+ initialSummary = {
504
+ text: entry.data.text,
505
+ tokenCount:
506
+ typeof entry.data.tokenCount === 'number' &&
507
+ Number.isFinite(entry.data.tokenCount)
508
+ ? entry.data.tokenCount
509
+ : 0,
510
+ };
511
+ messages.length = 0;
512
+ continue;
513
+ }
514
+ if (entry.type === 'message') {
515
+ messages.push(deserializeMessage(entry.data.message));
516
+ }
517
+ }
518
+ return { messages, initialSummary };
519
+ }
520
+
521
+ function isMessageEntry(
522
+ entry: SessionEntry
523
+ ): entry is Extract<SessionEntry, { type: 'message' }> {
524
+ return entry.type === 'message';
525
+ }
526
+
527
+ function getSessionBranchTarget(
528
+ store: JsonlSessionStore,
529
+ entryId: string,
530
+ position: 'before' | 'at'
531
+ ): SessionEntry | undefined {
532
+ const entry = store.getEntry(entryId);
533
+ if (!entry) {
534
+ throw new Error(`Entry not found: ${entryId}`);
535
+ }
536
+ if (position === 'at') {
537
+ return entry;
538
+ }
539
+ return entry.parentId == null ? undefined : store.getEntry(entry.parentId);
540
+ }
541
+
542
+ function getAbandonedPathForBranch(
543
+ store: JsonlSessionStore,
544
+ previousLeafId: string | null,
545
+ targetLeafId: string | null
546
+ ): SessionEntry[] {
547
+ const previousPath = store.getPath(previousLeafId ?? undefined);
548
+ if (previousPath.length === 0) {
549
+ return [];
550
+ }
551
+ const targetPath = targetLeafId == null ? [] : store.getPath(targetLeafId);
552
+ const maxSharedLength = Math.min(previousPath.length, targetPath.length);
553
+ let sharedLength = 0;
554
+ while (
555
+ sharedLength < maxSharedLength &&
556
+ previousPath[sharedLength].id === targetPath[sharedLength].id
557
+ ) {
558
+ sharedLength++;
559
+ }
560
+ return previousPath.slice(sharedLength);
561
+ }
562
+
563
+ function createAgentInputFromGraphConfig(
564
+ graphConfig: t.RunConfig['graphConfig'],
565
+ initialSummary: InitialSummary | undefined,
566
+ retainRecentTurns: number | undefined,
567
+ instructions: string | undefined
568
+ ): t.AgentInputs {
569
+ let agent: t.AgentInputs;
570
+ if ('agents' in graphConfig) {
571
+ if (graphConfig.agents.length === 0) {
572
+ throw new Error('Cannot compact a session with no agents');
573
+ }
574
+ agent = graphConfig.agents[0];
575
+ } else {
576
+ const {
577
+ type: _type,
578
+ llmConfig,
579
+ signal: _signal,
580
+ tools = [],
581
+ ...agentInputs
582
+ } = graphConfig;
583
+ const { provider, ...clientOptions } = llmConfig;
584
+ agent = {
585
+ ...agentInputs,
586
+ tools,
587
+ provider,
588
+ clientOptions,
589
+ agentId: 'default',
590
+ };
591
+ }
592
+ const summarizationConfig: t.SummarizationConfig = {
593
+ ...(agent.summarizationConfig ?? {}),
594
+ ...(instructions != null && instructions !== ''
595
+ ? { prompt: instructions }
596
+ : {}),
597
+ retainRecent: {
598
+ ...(agent.summarizationConfig?.retainRecent ?? {}),
599
+ ...(retainRecentTurns != null ? { turns: retainRecentTurns } : {}),
600
+ },
601
+ };
602
+ return {
603
+ ...applyInitialSummaryToAgent(agent, initialSummary),
604
+ summarizationEnabled: true,
605
+ summarizationConfig,
606
+ };
607
+ }
608
+
609
+ function createManualCompactGraph(params: {
610
+ runId: string;
611
+ customHandlers?: Record<string, t.EventHandler>;
612
+ hooks?: HookRegistry;
613
+ }): {
614
+ graph: Parameters<typeof createSummarizeNode>[0]['graph'];
615
+ completedSummary?: t.SummaryContentBlock;
616
+ } {
617
+ const contentData: t.RunStep[] = [];
618
+ const contentIndexMap = new Map<string, number>();
619
+ const result: {
620
+ graph: Parameters<typeof createSummarizeNode>[0]['graph'];
621
+ completedSummary?: t.SummaryContentBlock;
622
+ } = {
623
+ graph: {
624
+ contentData,
625
+ contentIndexMap,
626
+ runId: params.runId,
627
+ isMultiAgent: false,
628
+ hookRegistry: params.hooks,
629
+ dispatchRunStep: async (runStep): Promise<void> => {
630
+ contentData.push(runStep);
631
+ contentIndexMap.set(runStep.id, runStep.index);
632
+ await params.customHandlers?.[GraphEvents.ON_RUN_STEP]?.handle(
633
+ GraphEvents.ON_RUN_STEP,
634
+ runStep
635
+ );
636
+ },
637
+ dispatchRunStepCompleted: async (stepId, completed): Promise<void> => {
638
+ const runStep = contentData.find((step) => step.id === stepId);
639
+ const resultWithStep = {
640
+ ...completed,
641
+ id: stepId,
642
+ index: runStep?.index ?? 0,
643
+ };
644
+ if (completed.type === 'summary') {
645
+ result.completedSummary = completed.summary;
646
+ }
647
+ await params.customHandlers?.[
648
+ GraphEvents.ON_RUN_STEP_COMPLETED
649
+ ]?.handle(GraphEvents.ON_RUN_STEP_COMPLETED, {
650
+ result: resultWithStep,
651
+ } as unknown as Parameters<t.EventHandler['handle']>[1]);
652
+ },
653
+ },
654
+ };
655
+ return result;
656
+ }
657
+
658
+ function getSummaryText(summary: t.SummaryContentBlock | undefined): string {
659
+ const firstBlock = summary?.content?.[0];
660
+ return firstBlock != null &&
661
+ typeof firstBlock === 'object' &&
662
+ 'text' in firstBlock &&
663
+ typeof firstBlock.text === 'string'
664
+ ? firstBlock.text
665
+ : '';
666
+ }
667
+
668
+ function getSummaryTokenCount(
669
+ summary: t.SummaryContentBlock | undefined
670
+ ): number {
671
+ return typeof summary?.tokenCount === 'number' &&
672
+ Number.isFinite(summary.tokenCount)
673
+ ? summary.tokenCount
674
+ : 0;
675
+ }
676
+
677
+ function filterRemoveMessages(messages: BaseMessage[]): BaseMessage[] {
678
+ return messages.filter((message) => message._getType() !== 'remove');
679
+ }
680
+
681
+ class LiveAgentSessionStream implements AgentSessionStream {
682
+ private resultPromise: Promise<AgentSessionRunResult> | undefined;
683
+ private readonly events: AgentSessionStreamEvent[] = [];
684
+ private readonly waiters: Array<{
685
+ resolve: (result: IteratorResult<AgentSessionStreamEvent>) => void;
686
+ reject: (error: unknown) => void;
687
+ }> = [];
688
+ private closed = false;
689
+ private failed = false;
690
+ private error: unknown;
691
+
692
+ setResultPromise(resultPromise: Promise<AgentSessionRunResult>): void {
693
+ this.resultPromise = resultPromise;
694
+ }
695
+
696
+ push(event: AgentSessionStreamEvent): void {
697
+ if (this.closed) {
698
+ return;
699
+ }
700
+ const waiter = this.waiters.shift();
701
+ if (waiter) {
702
+ waiter.resolve({ value: event, done: false });
703
+ return;
704
+ }
705
+ this.events.push(event);
706
+ }
707
+
708
+ complete(): void {
709
+ this.closed = true;
710
+ for (const waiter of this.waiters.splice(0)) {
711
+ waiter.resolve({
712
+ value: undefined as unknown as AgentSessionStreamEvent,
713
+ done: true,
714
+ });
715
+ }
716
+ }
717
+
718
+ fail(error: unknown): void {
719
+ this.error = error;
720
+ this.failed = true;
721
+ this.closed = true;
722
+ for (const waiter of this.waiters.splice(0)) {
723
+ waiter.reject(error);
724
+ }
725
+ }
726
+
727
+ private nextEvent(): Promise<IteratorResult<AgentSessionStreamEvent>> {
728
+ const event = this.events.shift();
729
+ if (event !== undefined) {
730
+ return Promise.resolve({ value: event, done: false });
731
+ }
732
+ if (this.failed) {
733
+ return Promise.reject(this.error);
734
+ }
735
+ if (this.closed) {
736
+ return Promise.resolve({
737
+ value: undefined as unknown as AgentSessionStreamEvent,
738
+ done: true,
739
+ });
740
+ }
741
+ return new Promise((resolve, reject) => {
742
+ this.waiters.push({ resolve, reject });
743
+ });
744
+ }
745
+
746
+ async *[Symbol.asyncIterator](): AsyncIterator<AgentSessionStreamEvent> {
747
+ for (;;) {
748
+ const next = await this.nextEvent();
749
+ if (next.done === true) {
750
+ return;
751
+ }
752
+ yield next.value;
753
+ }
754
+ }
755
+
756
+ async *toTextStream(): AsyncIterable<string> {
757
+ for await (const event of this) {
758
+ if (event.type !== 'message.delta' || event.data == null) {
759
+ continue;
760
+ }
761
+ const data = event.data;
762
+ if (typeof data === 'object' && !Array.isArray(data)) {
763
+ const delta = data.delta;
764
+ if (
765
+ delta != null &&
766
+ typeof delta === 'object' &&
767
+ !Array.isArray(delta)
768
+ ) {
769
+ const content = delta.content;
770
+ if (Array.isArray(content)) {
771
+ for (const part of content) {
772
+ if (
773
+ part != null &&
774
+ typeof part === 'object' &&
775
+ !Array.isArray(part) &&
776
+ typeof part.text === 'string'
777
+ ) {
778
+ yield part.text;
779
+ }
780
+ }
781
+ }
782
+ }
783
+ }
784
+ }
785
+ }
786
+
787
+ finalResult(): Promise<AgentSessionRunResult> {
788
+ if (!this.resultPromise) {
789
+ return Promise.reject(new Error('Session stream has not started'));
790
+ }
791
+ return this.resultPromise;
792
+ }
793
+ }
794
+
795
+ export class AgentSession {
796
+ private runConfig: t.RunConfig;
797
+ private store: JsonlSessionStore | undefined;
798
+ private calibrationRatio: number | undefined;
799
+ private checkpointing: SessionCheckpointingState;
800
+ cwd: string;
801
+ threadId: string;
802
+
803
+ private constructor(params: {
804
+ runConfig: t.RunConfig;
805
+ cwd: string;
806
+ threadId: string;
807
+ checkpointing: SessionCheckpointingState;
808
+ store?: JsonlSessionStore;
809
+ }) {
810
+ this.runConfig = params.runConfig;
811
+ this.cwd = params.cwd;
812
+ this.threadId = params.threadId;
813
+ this.store = params.store;
814
+ this.checkpointing = params.checkpointing;
815
+ }
816
+
817
+ static async create(config: AgentSessionConfig): Promise<AgentSession> {
818
+ const normalized = normalizeConfig(config);
819
+ const explicitSessionId =
820
+ 'sessionId' in config && typeof config.sessionId === 'string'
821
+ ? config.sessionId
822
+ : undefined;
823
+ const store = await createStore({
824
+ cwd: normalized.cwd,
825
+ sessionPath: normalized.sessionPath,
826
+ name: normalized.name,
827
+ sessionId: explicitSessionId,
828
+ ephemeral: normalized.ephemeral,
829
+ });
830
+ return new AgentSession({
831
+ runConfig: normalized.runConfig,
832
+ cwd: normalized.cwd,
833
+ threadId: store?.header.id ?? explicitSessionId ?? createSessionId(),
834
+ checkpointing: createCheckpointingState(
835
+ normalized.runConfig,
836
+ normalized.checkpointing
837
+ ),
838
+ store,
839
+ });
840
+ }
841
+
842
+ get sessionPath(): string | undefined {
843
+ return this.store?.path;
844
+ }
845
+
846
+ getSessionStore(): JsonlSessionStore | undefined {
847
+ return this.store;
848
+ }
849
+
850
+ getCheckpointer(): BaseCheckpointSaver | undefined {
851
+ return this.checkpointing.checkpointer;
852
+ }
853
+
854
+ async getLatestCheckpoint(
855
+ options: AgentSessionCheckpointLookupOptions = {}
856
+ ): Promise<AgentSessionCheckpointReference | undefined> {
857
+ const threadId = options.threadId ?? this.threadId;
858
+ const baseConfig = options.config ?? {};
859
+ const config = createCheckpointLookupConfig({
860
+ ...baseConfig,
861
+ configurable: {
862
+ ...(baseConfig.configurable ?? {}),
863
+ thread_id: threadId,
864
+ ...(options.checkpointNs != null
865
+ ? { checkpoint_ns: options.checkpointNs }
866
+ : {}),
867
+ },
868
+ });
869
+ const tuple = await getLatestCheckpointTuple(
870
+ this.checkpointing.checkpointer,
871
+ config
872
+ );
873
+ return tuple ? createCheckpointReference({ threadId, tuple }) : undefined;
874
+ }
875
+
876
+ private async hasCheckpointState(config: RunnableConfig): Promise<boolean> {
877
+ if (!this.checkpointing.enabled) {
878
+ return false;
879
+ }
880
+ const tuple = await getSelectedCheckpointTuple(
881
+ this.checkpointing.checkpointer,
882
+ config
883
+ );
884
+ return tuple != null;
885
+ }
886
+
887
+ private async recordCheckpoint(params: {
888
+ source: 'run' | 'resume';
889
+ runId: string;
890
+ threadId: string;
891
+ config: RunnableConfig;
892
+ checkpointId?: string;
893
+ checkpointNs?: string;
894
+ }): Promise<void> {
895
+ if (!this.checkpointing.enabled) {
896
+ return;
897
+ }
898
+ const checkpointConfig = applyCheckpointReferenceToConfig(
899
+ params.config,
900
+ {
901
+ checkpointId: params.checkpointId,
902
+ checkpointNs: params.checkpointNs,
903
+ },
904
+ { overwrite: true }
905
+ );
906
+ const tuple =
907
+ params.checkpointId != null && params.checkpointId !== ''
908
+ ? await getSelectedCheckpointTuple(
909
+ this.checkpointing.checkpointer,
910
+ checkpointConfig
911
+ )
912
+ : await getLatestCheckpointTuple(
913
+ this.checkpointing.checkpointer,
914
+ params.config
915
+ );
916
+ if (!tuple) {
917
+ return;
918
+ }
919
+ const reference = createCheckpointReference({
920
+ threadId: params.threadId,
921
+ tuple,
922
+ });
923
+ await this.store?.appendCheckpoint({
924
+ source: params.source,
925
+ runId: params.runId,
926
+ threadId: params.threadId,
927
+ checkpointId: reference.checkpointId,
928
+ checkpointNs: reference.checkpointNs,
929
+ parentCheckpointId: reference.parentCheckpointId,
930
+ });
931
+ }
932
+
933
+ private getCheckpointThreadIds(): string[] {
934
+ const threadIds = new Set<string>([this.threadId]);
935
+ for (const checkpoint of this.store?.getCheckpoints() ?? []) {
936
+ threadIds.add(checkpoint.data.threadId);
937
+ }
938
+ return [...threadIds];
939
+ }
940
+
941
+ private async resetCheckpointThreads(reason: string): Promise<void> {
942
+ const checkpointer = this.checkpointing.checkpointer;
943
+ if (!this.checkpointing.enabled || checkpointer == null) {
944
+ return;
945
+ }
946
+ for (const threadId of this.getCheckpointThreadIds()) {
947
+ await checkpointer.deleteThread(threadId);
948
+ await this.store?.appendCheckpoint({
949
+ source: 'reset',
950
+ threadId,
951
+ reason,
952
+ });
953
+ }
954
+ }
955
+
956
+ private async runInternal(
957
+ input: AgentSessionInput,
958
+ options: AgentSessionRunOptions = {},
959
+ onEvent?: (event: AgentSessionStreamEvent) => void
960
+ ): Promise<AgentSessionRunResult> {
961
+ const runId = options.runId ?? createRunId();
962
+ const threadId = options.threadId ?? this.threadId;
963
+ const isSessionThread = threadId === this.threadId;
964
+ const normalizedInput = normalizeInput(input);
965
+ const inputMessages = normalizedInput.messages;
966
+ const callerConfig = createCallerConfig(threadId, options);
967
+ const useCheckpointState = await this.hasCheckpointState(callerConfig);
968
+ let parentId = this.store?.getLeafEntry()?.id ?? null;
969
+ if (isSessionThread) {
970
+ for (const message of inputMessages) {
971
+ const entry = await this.store?.appendMessage(message, parentId);
972
+ parentId = entry?.id ?? parentId;
973
+ }
974
+ }
975
+ await this.store?.appendRunEvent('run.started', undefined, {
976
+ runId,
977
+ threadId,
978
+ });
979
+
980
+ const handlerResult = createRunHandlers({
981
+ runId,
982
+ threadId,
983
+ userHandlers: this.runConfig.customHandlers,
984
+ onEvent,
985
+ });
986
+ const emitTerminalEvent = (
987
+ event: Omit<
988
+ AgentSessionStreamEvent,
989
+ 'sequence' | 'runId' | 'threadId' | 'timestamp'
990
+ >
991
+ ): void => {
992
+ const streamEvent: AgentSessionStreamEvent = {
993
+ ...event,
994
+ sequence: handlerResult.events.length,
995
+ runId,
996
+ threadId,
997
+ timestamp: new Date().toISOString(),
998
+ };
999
+ handlerResult.events.push(streamEvent);
1000
+ onEvent?.(streamEvent);
1001
+ };
1002
+ const sessionState = createSessionRunState(
1003
+ isSessionThread ? (this.store?.getPath() ?? []) : []
1004
+ );
1005
+ try {
1006
+ const runConfig: t.RunConfig = {
1007
+ ...this.runConfig,
1008
+ runId,
1009
+ graphConfig: applyCheckpointingToGraphConfig(
1010
+ applyInitialSummaryToGraphConfig(
1011
+ this.runConfig.graphConfig,
1012
+ sessionState.initialSummary
1013
+ ),
1014
+ this.checkpointing
1015
+ ),
1016
+ returnContent: true,
1017
+ calibrationRatio: this.calibrationRatio,
1018
+ customHandlers: {
1019
+ ...(this.runConfig.customHandlers ?? {}),
1020
+ ...handlerResult.handlers,
1021
+ },
1022
+ };
1023
+ const run = await Run.create<t.IState>(runConfig);
1024
+ let messages = inputMessages;
1025
+ if (!useCheckpointState && sessionState.messages.length > 0) {
1026
+ messages = sessionState.messages;
1027
+ }
1028
+ const content = await run.processStream(
1029
+ { ...normalizedInput.state, messages },
1030
+ callerConfig,
1031
+ options.streamOptions
1032
+ );
1033
+ const runMessages = run.getRunMessages() ?? [];
1034
+ if (isSessionThread) {
1035
+ for (const message of runMessages) {
1036
+ await this.store?.appendMessage(message);
1037
+ }
1038
+ }
1039
+ this.calibrationRatio = run.getCalibrationRatio();
1040
+ const interrupt = run.getInterrupt();
1041
+ const haltedReason = run.getHaltReason();
1042
+ if (interrupt) {
1043
+ emitTerminalEvent({ type: 'run.interrupted' });
1044
+ await this.store?.appendRunEvent('run.interrupted', interrupt, {
1045
+ runId,
1046
+ threadId,
1047
+ });
1048
+ } else if (haltedReason != null && haltedReason !== '') {
1049
+ emitTerminalEvent({ type: 'run.halted', data: haltedReason });
1050
+ await this.store?.appendRunEvent('run.halted', haltedReason, {
1051
+ runId,
1052
+ threadId,
1053
+ });
1054
+ } else {
1055
+ emitTerminalEvent({ type: 'run.completed' });
1056
+ await this.store?.appendRunEvent('run.completed', undefined, {
1057
+ runId,
1058
+ threadId,
1059
+ });
1060
+ }
1061
+ await this.recordCheckpoint({
1062
+ source: 'run',
1063
+ runId,
1064
+ threadId,
1065
+ config: callerConfig,
1066
+ checkpointId: interrupt?.checkpointId,
1067
+ checkpointNs: interrupt?.checkpointNs,
1068
+ });
1069
+ const contentParts = (content ?? handlerResult.contentParts).filter(
1070
+ (part): part is t.MessageContentComplex => part != null
1071
+ );
1072
+ return {
1073
+ text: contentToText(contentParts),
1074
+ content: contentParts,
1075
+ messages: runMessages,
1076
+ usage: handlerResult.usage,
1077
+ steps: handlerResult.steps,
1078
+ interrupt,
1079
+ haltedReason,
1080
+ runId,
1081
+ threadId,
1082
+ };
1083
+ } catch (error) {
1084
+ emitTerminalEvent({
1085
+ type: 'run.failed',
1086
+ data: error instanceof Error ? error.message : String(error),
1087
+ });
1088
+ await this.store?.appendRunEvent('run.failed', error, {
1089
+ runId,
1090
+ threadId,
1091
+ });
1092
+ await this.recordCheckpoint({
1093
+ source: 'run',
1094
+ runId,
1095
+ threadId,
1096
+ config: callerConfig,
1097
+ });
1098
+ throw error;
1099
+ }
1100
+ }
1101
+
1102
+ async run(
1103
+ input: AgentSessionInput,
1104
+ options: AgentSessionRunOptions = {}
1105
+ ): Promise<AgentSessionRunResult> {
1106
+ return this.runInternal(input, options);
1107
+ }
1108
+
1109
+ stream(
1110
+ input: AgentSessionInput,
1111
+ options: AgentSessionRunOptions = {}
1112
+ ): AgentSessionStream {
1113
+ const stream = new LiveAgentSessionStream();
1114
+ const resultPromise = this.runInternal(input, options, (event) => {
1115
+ stream.push(event);
1116
+ }).then(
1117
+ (result) => {
1118
+ stream.complete();
1119
+ return result;
1120
+ },
1121
+ (error: unknown) => {
1122
+ stream.fail(error);
1123
+ throw error;
1124
+ }
1125
+ );
1126
+ stream.setResultPromise(resultPromise);
1127
+ return stream;
1128
+ }
1129
+
1130
+ async resumeSession(pathOrId?: string): Promise<AgentSession> {
1131
+ if (pathOrId == null || pathOrId === '') {
1132
+ const sessions = await JsonlSessionStore.list(this.cwd);
1133
+ if (sessions.length === 0) {
1134
+ throw new Error(`No sessions found for ${this.cwd}`);
1135
+ }
1136
+ this.store = await JsonlSessionStore.open(sessions[0].path);
1137
+ this.cwd = this.store.header.cwd;
1138
+ this.threadId = this.store.header.id;
1139
+ return this;
1140
+ }
1141
+ this.store = await JsonlSessionStore.open(pathOrId);
1142
+ this.cwd = this.store.header.cwd;
1143
+ this.threadId = this.store.header.id;
1144
+ return this;
1145
+ }
1146
+
1147
+ async clone(options: SessionForkOptions = {}): Promise<AgentSession> {
1148
+ if (!this.store) {
1149
+ throw new Error('Cannot clone an ephemeral session');
1150
+ }
1151
+ const store = await this.store.clone(options);
1152
+ return new AgentSession({
1153
+ runConfig: this.runConfig,
1154
+ cwd: store.header.cwd,
1155
+ threadId: store.header.id,
1156
+ checkpointing: this.checkpointing,
1157
+ store,
1158
+ });
1159
+ }
1160
+
1161
+ async fork(
1162
+ entryId: string,
1163
+ options: SessionForkOptions = {}
1164
+ ): Promise<AgentSession> {
1165
+ if (!this.store) {
1166
+ throw new Error('Cannot fork an ephemeral session');
1167
+ }
1168
+ const store = await this.store.fork(entryId, options);
1169
+ return new AgentSession({
1170
+ runConfig: this.runConfig,
1171
+ cwd: store.header.cwd,
1172
+ threadId: store.header.id,
1173
+ checkpointing: this.checkpointing,
1174
+ store,
1175
+ });
1176
+ }
1177
+
1178
+ async branch(
1179
+ entryId: string,
1180
+ options: SessionBranchOptions = {}
1181
+ ): Promise<void> {
1182
+ const store = this.store;
1183
+ if (!store) {
1184
+ throw new Error('Cannot branch an ephemeral session');
1185
+ }
1186
+ const target = getSessionBranchTarget(
1187
+ store,
1188
+ entryId,
1189
+ options.position ?? 'at'
1190
+ );
1191
+ const previousLeafId = store.getLeafEntry()?.id ?? null;
1192
+ let leafId = target?.id ?? null;
1193
+ const summarizeAbandoned = options.summarizeAbandoned;
1194
+ if (summarizeAbandoned !== undefined && summarizeAbandoned !== false) {
1195
+ const instructions =
1196
+ typeof summarizeAbandoned === 'object'
1197
+ ? summarizeAbandoned.instructions
1198
+ : undefined;
1199
+ const abandonedPath = getAbandonedPathForBranch(
1200
+ store,
1201
+ previousLeafId,
1202
+ leafId
1203
+ );
1204
+ const summary = await this.compactActivePath(
1205
+ { instructions, retainRecentTurns: 0 },
1206
+ leafId,
1207
+ abandonedPath
1208
+ );
1209
+ leafId = summary?.id ?? leafId;
1210
+ }
1211
+ if (leafId === previousLeafId) {
1212
+ return;
1213
+ }
1214
+ await store.setLeaf(leafId);
1215
+ await this.resetCheckpointThreads('branch');
1216
+ }
1217
+
1218
+ async compact(options: SessionCompactOptions = {}): Promise<void> {
1219
+ const summary = await this.compactActivePath(options, null);
1220
+ if (summary) {
1221
+ await this.resetCheckpointThreads('compact');
1222
+ }
1223
+ }
1224
+
1225
+ private async compactActivePath(
1226
+ options: SessionCompactOptions = {},
1227
+ parentId: string | null,
1228
+ path?: SessionEntry[]
1229
+ ): Promise<Extract<SessionEntry, { type: 'summary' }> | undefined> {
1230
+ const store = this.store;
1231
+ if (!store) {
1232
+ throw new Error('Cannot compact an ephemeral session');
1233
+ }
1234
+ const activePath = path ?? store.getPath();
1235
+ const sessionState = createSessionRunState(activePath);
1236
+ const messageEntries = activePath.filter(isMessageEntry);
1237
+ if (sessionState.messages.length === 0) {
1238
+ return undefined;
1239
+ }
1240
+ const compactRunId = createRunId();
1241
+ const agentContext = AgentContext.fromConfig(
1242
+ createAgentInputFromGraphConfig(
1243
+ this.runConfig.graphConfig,
1244
+ sessionState.initialSummary,
1245
+ options.retainRecentTurns,
1246
+ options.instructions
1247
+ ),
1248
+ this.runConfig.tokenCounter
1249
+ );
1250
+ const graph = createManualCompactGraph({
1251
+ runId: compactRunId,
1252
+ customHandlers: this.runConfig.customHandlers,
1253
+ hooks: this.runConfig.hooks,
1254
+ });
1255
+ const summarizeNode = createSummarizeNode({
1256
+ agentContext,
1257
+ graph: graph.graph,
1258
+ generateStepId: (stepKey): [string, number] => [
1259
+ `${stepKey}-${compactRunId}`,
1260
+ graph.graph.contentData.length,
1261
+ ],
1262
+ });
1263
+ const summarizedState = await summarizeNode(
1264
+ {
1265
+ messages: sessionState.messages,
1266
+ summarizationRequest: {
1267
+ remainingContextTokens: agentContext.maxContextTokens ?? 0,
1268
+ agentId: agentContext.agentId,
1269
+ },
1270
+ },
1271
+ {
1272
+ configurable: { thread_id: this.threadId },
1273
+ metadata: { run_id: compactRunId },
1274
+ }
1275
+ );
1276
+ const completedSummaryText = getSummaryText(graph.completedSummary);
1277
+ const contextSummaryText = agentContext.getSummaryText();
1278
+ let summaryText = completedSummaryText;
1279
+ if (summaryText === '' && contextSummaryText != null) {
1280
+ summaryText = contextSummaryText;
1281
+ }
1282
+ if (summaryText === '') {
1283
+ return undefined;
1284
+ }
1285
+ const retainedMessages = filterRemoveMessages(
1286
+ summarizedState.messages ?? []
1287
+ );
1288
+ let retainedEntryIds: string[] = [];
1289
+ if (retainedMessages.length > 0) {
1290
+ retainedEntryIds = messageEntries
1291
+ .slice(-retainedMessages.length)
1292
+ .map((entry) => entry.id);
1293
+ }
1294
+ const retainedEntryIdSet = new Set(retainedEntryIds);
1295
+ const summarized = messageEntries.filter(
1296
+ (entry) => !retainedEntryIdSet.has(entry.id)
1297
+ );
1298
+ const summary = await store.appendEntryForCompaction({
1299
+ text: summaryText,
1300
+ tokenCount: getSummaryTokenCount(graph.completedSummary),
1301
+ retainedEntryIds,
1302
+ summarizedEntryIds: summarized.map((entry) => entry.id),
1303
+ instructions: options.instructions,
1304
+ parentId,
1305
+ });
1306
+ let retainedParentId: string | null = summary.id;
1307
+ for (const message of retainedMessages) {
1308
+ const retainedMessage = await store.appendMessage(
1309
+ message,
1310
+ retainedParentId
1311
+ );
1312
+ retainedParentId = retainedMessage.id;
1313
+ }
1314
+ await store.appendCompactionEntry({
1315
+ summaryEntryId: summary.id,
1316
+ retainedEntryIds,
1317
+ summarizedEntryIds: summarized.map((entry) => entry.id),
1318
+ });
1319
+ return summary;
1320
+ }
1321
+
1322
+ async resumeInterrupt<TResume>(
1323
+ resumeValue: TResume,
1324
+ options: AgentSessionRunOptions = {}
1325
+ ): Promise<AgentSessionRunResult> {
1326
+ const runId = options.runId ?? createRunId();
1327
+ const threadId = options.threadId ?? this.threadId;
1328
+ const isSessionThread = threadId === this.threadId;
1329
+ const baseCallerConfig = createCallerConfig(threadId, options);
1330
+ const callerConfig = applyCheckpointReferenceToConfig(
1331
+ baseCallerConfig,
1332
+ getStoredCheckpointForConfig(this.store, threadId, baseCallerConfig)?.data
1333
+ );
1334
+ await this.store?.appendRunEvent('run.started', undefined, {
1335
+ runId,
1336
+ threadId,
1337
+ });
1338
+ const handlerResult = createRunHandlers({
1339
+ runId,
1340
+ threadId,
1341
+ userHandlers: this.runConfig.customHandlers,
1342
+ });
1343
+ const sessionState = createSessionRunState(
1344
+ isSessionThread ? (this.store?.getPath() ?? []) : []
1345
+ );
1346
+ try {
1347
+ const run = await Run.create<t.IState>({
1348
+ ...this.runConfig,
1349
+ runId,
1350
+ graphConfig: applyCheckpointingToGraphConfig(
1351
+ applyInitialSummaryToGraphConfig(
1352
+ this.runConfig.graphConfig,
1353
+ sessionState.initialSummary
1354
+ ),
1355
+ this.checkpointing
1356
+ ),
1357
+ returnContent: true,
1358
+ calibrationRatio: this.calibrationRatio,
1359
+ customHandlers: {
1360
+ ...(this.runConfig.customHandlers ?? {}),
1361
+ ...handlerResult.handlers,
1362
+ },
1363
+ });
1364
+ const content = await run.resume(resumeValue, callerConfig);
1365
+ const runMessages = run.getRunMessages() ?? [];
1366
+ if (isSessionThread) {
1367
+ for (const message of runMessages) {
1368
+ await this.store?.appendMessage(message);
1369
+ }
1370
+ }
1371
+ this.calibrationRatio = run.getCalibrationRatio();
1372
+ const interrupt = run.getInterrupt();
1373
+ const haltedReason = run.getHaltReason();
1374
+ if (interrupt) {
1375
+ await this.store?.appendRunEvent('run.interrupted', interrupt, {
1376
+ runId,
1377
+ threadId,
1378
+ });
1379
+ } else if (haltedReason != null && haltedReason !== '') {
1380
+ await this.store?.appendRunEvent('run.halted', haltedReason, {
1381
+ runId,
1382
+ threadId,
1383
+ });
1384
+ } else {
1385
+ await this.store?.appendRunEvent('run.completed', undefined, {
1386
+ runId,
1387
+ threadId,
1388
+ });
1389
+ }
1390
+ await this.recordCheckpoint({
1391
+ source: 'resume',
1392
+ runId,
1393
+ threadId,
1394
+ config: callerConfig,
1395
+ checkpointId: interrupt?.checkpointId,
1396
+ checkpointNs: interrupt?.checkpointNs,
1397
+ });
1398
+ const contentParts = (content ?? handlerResult.contentParts).filter(
1399
+ (part): part is t.MessageContentComplex => part != null
1400
+ );
1401
+ return {
1402
+ text: contentToText(contentParts),
1403
+ content: contentParts,
1404
+ messages: runMessages,
1405
+ usage: handlerResult.usage,
1406
+ steps: handlerResult.steps,
1407
+ interrupt,
1408
+ haltedReason,
1409
+ runId,
1410
+ threadId,
1411
+ };
1412
+ } catch (error) {
1413
+ await this.store?.appendRunEvent('run.failed', error, {
1414
+ runId,
1415
+ threadId,
1416
+ });
1417
+ await this.recordCheckpoint({
1418
+ source: 'resume',
1419
+ runId,
1420
+ threadId,
1421
+ config: callerConfig,
1422
+ });
1423
+ throw error;
1424
+ }
1425
+ }
1426
+ }
1427
+
1428
+ export function createAgentSession(
1429
+ config: AgentSessionConfig
1430
+ ): Promise<AgentSession> {
1431
+ return AgentSession.create(config);
1432
+ }