@macrow/copilotkit-langgraph-history 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,936 @@
1
+ import { EventType } from '@ag-ui/core';
2
+ import { LangGraphAgent } from '@copilotkit/runtime/langgraph';
3
+ import { AgentRunner } from '@copilotkitnext/runtime';
4
+ import { Client } from '@langchain/langgraph-sdk';
5
+ import { Observable } from 'rxjs';
6
+
7
+ // src/runner/history-hydrating-runner.ts
8
+
9
+ // src/runner/constants.ts
10
+ var DEFAULT_TIMEOUT = 30 * 60 * 1e3;
11
+ var DEFAULT_HISTORY_LIMIT = 100;
12
+ var MAX_HISTORY_LIMIT = 1e3;
13
+ function createIsolatedAgent(config) {
14
+ const timeout = config.clientTimeoutMs ?? DEFAULT_TIMEOUT;
15
+ const isolatedConfig = Object.freeze({
16
+ deploymentUrl: String(config.deploymentUrl),
17
+ graphId: String(config.graphId),
18
+ langsmithApiKey: config.langsmithApiKey ? String(config.langsmithApiKey) : void 0,
19
+ debug: Boolean(config.debug)
20
+ });
21
+ const agent = new LangGraphAgent(isolatedConfig);
22
+ const clientInternals = agent.client;
23
+ const expectedUrl = config.deploymentUrl.replace(/\/$/, "");
24
+ const actualUrl = clientInternals.apiUrl?.replace(/\/$/, "");
25
+ if (expectedUrl !== actualUrl) {
26
+ console.warn(
27
+ `[LangGraphHistory] URL mismatch detected! Expected: ${expectedUrl}, Got: ${actualUrl}. Replacing client.`
28
+ );
29
+ const newClient = new Client({
30
+ apiUrl: config.deploymentUrl,
31
+ apiKey: config.langsmithApiKey,
32
+ timeoutMs: timeout,
33
+ onRequest: config.onRequest
34
+ });
35
+ Object.assign(agent, { client: newClient });
36
+ }
37
+ return agent;
38
+ }
39
+
40
+ // src/utils/message-transformer.ts
41
+ function extractContent(content) {
42
+ if (typeof content === "string") {
43
+ return content;
44
+ }
45
+ if (Array.isArray(content)) {
46
+ return content.map((block) => {
47
+ if (block.type === "text" && block.text) {
48
+ return block.text;
49
+ }
50
+ return "";
51
+ }).filter((text) => text.length > 0).join("\n");
52
+ }
53
+ return "";
54
+ }
55
+ function transformMessages(messages, options) {
56
+ const result = [];
57
+ for (const msg of messages) {
58
+ try {
59
+ let transformed = null;
60
+ switch (msg.type) {
61
+ case "human": {
62
+ const content = extractContent(msg.content);
63
+ transformed = {
64
+ id: msg.id,
65
+ role: "user",
66
+ content
67
+ };
68
+ break;
69
+ }
70
+ case "ai": {
71
+ const content = extractContent(msg.content);
72
+ transformed = {
73
+ id: msg.id,
74
+ role: "assistant",
75
+ content: content || "",
76
+ toolCalls: msg.tool_calls?.map((toolCall) => ({
77
+ id: toolCall.id,
78
+ type: "function",
79
+ function: {
80
+ name: toolCall.name,
81
+ arguments: JSON.stringify(toolCall.args)
82
+ }
83
+ }))
84
+ };
85
+ break;
86
+ }
87
+ case "system": {
88
+ const content = extractContent(msg.content);
89
+ transformed = {
90
+ id: msg.id,
91
+ role: "system",
92
+ content
93
+ };
94
+ break;
95
+ }
96
+ case "tool": {
97
+ const content = extractContent(msg.content);
98
+ transformed = {
99
+ id: msg.id,
100
+ role: "tool",
101
+ content,
102
+ toolCallId: msg.tool_call_id
103
+ };
104
+ break;
105
+ }
106
+ default:
107
+ if (options?.debug) {
108
+ console.warn(
109
+ `[HistoryHydratingRunner] Unknown message type: ${msg.type}`
110
+ );
111
+ }
112
+ }
113
+ if (transformed) {
114
+ result.push(transformed);
115
+ }
116
+ } catch (error) {
117
+ if (options?.debug) {
118
+ console.warn(
119
+ "[HistoryHydratingRunner] Failed to transform message:",
120
+ msg,
121
+ error
122
+ );
123
+ }
124
+ }
125
+ }
126
+ return result;
127
+ }
128
+
129
+ // src/events/custom-events.ts
130
+ var CustomEventNames = /* @__PURE__ */ ((CustomEventNames2) => {
131
+ CustomEventNames2["CopilotKitManuallyEmitMessage"] = "copilotkit_manually_emit_message";
132
+ CustomEventNames2["CopilotKitManuallyEmitToolCall"] = "copilotkit_manually_emit_tool_call";
133
+ CustomEventNames2["CopilotKitManuallyEmitIntermediateState"] = "copilotkit_manually_emit_intermediate_state";
134
+ CustomEventNames2["CopilotKitExit"] = "copilotkit_exit";
135
+ return CustomEventNames2;
136
+ })(CustomEventNames || {});
137
+
138
+ // src/events/langgraph-events.ts
139
+ var LangGraphEventTypes = /* @__PURE__ */ ((LangGraphEventTypes2) => {
140
+ LangGraphEventTypes2["OnChatModelStart"] = "on_chat_model_start";
141
+ LangGraphEventTypes2["OnChatModelStream"] = "on_chat_model_stream";
142
+ LangGraphEventTypes2["OnChatModelEnd"] = "on_chat_model_end";
143
+ LangGraphEventTypes2["OnToolStart"] = "on_tool_start";
144
+ LangGraphEventTypes2["OnToolEnd"] = "on_tool_end";
145
+ LangGraphEventTypes2["OnChainStart"] = "on_chain_start";
146
+ LangGraphEventTypes2["OnChainEnd"] = "on_chain_end";
147
+ return LangGraphEventTypes2;
148
+ })(LangGraphEventTypes || {});
149
+
150
+ // src/utils/stream-processor.ts
151
+ async function processStreamChunk(chunk, context) {
152
+ const { event, data } = chunk;
153
+ let { runId } = context;
154
+ const { threadId, subscriber, startedMessages, startedToolCalls, debug } = context;
155
+ let manuallyEmittedState = context.manuallyEmittedState;
156
+ switch (event) {
157
+ case "metadata": {
158
+ const metadataData = data;
159
+ if (metadataData.run_id) {
160
+ runId = metadataData.run_id;
161
+ }
162
+ break;
163
+ }
164
+ case "events": {
165
+ const eventsData = data;
166
+ const rawEvent = {
167
+ type: "RAW",
168
+ event: eventsData.event,
169
+ name: eventsData.name,
170
+ data: eventsData.data,
171
+ run_id: eventsData.run_id,
172
+ metadata: chunk.metadata,
173
+ rawEvent: {
174
+ id: runId,
175
+ event: eventsData.event,
176
+ name: eventsData.name,
177
+ data: eventsData.data,
178
+ run_id: eventsData.run_id,
179
+ metadata: chunk.metadata
180
+ },
181
+ timestamp: Date.now(),
182
+ threadId,
183
+ runId
184
+ };
185
+ const eventType = eventsData.event;
186
+ const toolCallData = eventsData.data?.chunk?.tool_call_chunks?.[0];
187
+ const metadata = chunk.metadata || {};
188
+ const emitIntermediateState = metadata["copilotkit:emit-intermediate-state"];
189
+ const toolCallUsedToPredictState = emitIntermediateState?.some(
190
+ (predictStateTool) => predictStateTool.tool === toolCallData?.name
191
+ );
192
+ if (eventType === "on_chat_model_stream" /* OnChatModelStream */ && toolCallUsedToPredictState) {
193
+ subscriber.next({
194
+ type: "CUSTOM",
195
+ name: "PredictState",
196
+ value: metadata["copilotkit:emit-intermediate-state"],
197
+ rawEvent,
198
+ timestamp: Date.now(),
199
+ threadId,
200
+ runId
201
+ });
202
+ break;
203
+ }
204
+ if (eventType === "on_chat_model_stream" /* OnChatModelStream */) {
205
+ const messageChunk = eventsData.data?.chunk;
206
+ if (messageChunk?.content) {
207
+ if ("copilotkit:emit-messages" in metadata && metadata["copilotkit:emit-messages"] === false) {
208
+ break;
209
+ }
210
+ const messageId = eventsData.run_id || runId;
211
+ const delta = typeof messageChunk.content === "string" ? messageChunk.content : "";
212
+ if (startedMessages && !startedMessages.has(messageId)) {
213
+ subscriber.next({
214
+ type: "TEXT_MESSAGE_START",
215
+ role: "assistant",
216
+ messageId,
217
+ rawEvent,
218
+ timestamp: Date.now(),
219
+ threadId,
220
+ runId
221
+ });
222
+ startedMessages.add(messageId);
223
+ }
224
+ subscriber.next({
225
+ type: "TEXT_MESSAGE_CONTENT",
226
+ messageId,
227
+ delta,
228
+ rawEvent,
229
+ timestamp: Date.now(),
230
+ threadId,
231
+ runId
232
+ });
233
+ }
234
+ }
235
+ if (eventType === "on_chat_model_start" /* OnChatModelStart */) {
236
+ const eventMetadata = chunk.metadata || {};
237
+ if ("copilotkit:emit-messages" in eventMetadata && eventMetadata["copilotkit:emit-messages"] === false) {
238
+ break;
239
+ }
240
+ const messageId = eventsData.run_id || runId;
241
+ if (!startedMessages || !startedMessages.has(messageId)) {
242
+ subscriber.next({
243
+ type: "TEXT_MESSAGE_START",
244
+ role: "assistant",
245
+ messageId,
246
+ rawEvent,
247
+ timestamp: Date.now(),
248
+ threadId,
249
+ runId
250
+ });
251
+ if (startedMessages) {
252
+ startedMessages.add(messageId);
253
+ }
254
+ }
255
+ }
256
+ if (eventType === "on_chat_model_end" /* OnChatModelEnd */) {
257
+ const eventMetadata = chunk.metadata || {};
258
+ if ("copilotkit:emit-messages" in eventMetadata && eventMetadata["copilotkit:emit-messages"] === false) {
259
+ break;
260
+ }
261
+ const messageId = eventsData.run_id || runId;
262
+ if (startedMessages && !startedMessages.has(messageId)) {
263
+ subscriber.next({
264
+ type: "TEXT_MESSAGE_START",
265
+ role: "assistant",
266
+ messageId,
267
+ rawEvent,
268
+ timestamp: Date.now(),
269
+ threadId,
270
+ runId
271
+ });
272
+ startedMessages.add(messageId);
273
+ }
274
+ subscriber.next({
275
+ type: "TEXT_MESSAGE_END",
276
+ messageId,
277
+ rawEvent,
278
+ timestamp: Date.now(),
279
+ threadId,
280
+ runId
281
+ });
282
+ }
283
+ if (eventType === "on_tool_start" /* OnToolStart */) {
284
+ const eventMetadata = chunk.metadata || {};
285
+ if ("copilotkit:emit-tool-calls" in eventMetadata && eventMetadata["copilotkit:emit-tool-calls"] === false) {
286
+ break;
287
+ }
288
+ const toolData = eventsData.data?.input;
289
+ const toolName = eventsData.name;
290
+ const toolCallId = eventsData.run_id || runId;
291
+ if (!startedToolCalls || !startedToolCalls.has(toolCallId)) {
292
+ subscriber.next({
293
+ type: "TOOL_CALL_START",
294
+ toolCallId,
295
+ toolCallName: toolName,
296
+ parentMessageId: runId,
297
+ rawEvent,
298
+ timestamp: Date.now(),
299
+ threadId,
300
+ runId
301
+ });
302
+ if (startedToolCalls) {
303
+ startedToolCalls.add(toolCallId);
304
+ }
305
+ }
306
+ if (toolData) {
307
+ subscriber.next({
308
+ type: "TOOL_CALL_ARGS",
309
+ toolCallId,
310
+ delta: JSON.stringify(toolData),
311
+ rawEvent,
312
+ timestamp: Date.now(),
313
+ threadId,
314
+ runId
315
+ });
316
+ }
317
+ }
318
+ if (eventType === "on_tool_end" /* OnToolEnd */) {
319
+ const eventMetadata = chunk.metadata || {};
320
+ if ("copilotkit:emit-tool-calls" in eventMetadata && eventMetadata["copilotkit:emit-tool-calls"] === false) {
321
+ break;
322
+ }
323
+ const toolCallId = eventsData.run_id || runId;
324
+ const toolName = eventsData.name;
325
+ if (startedToolCalls && !startedToolCalls.has(toolCallId)) {
326
+ subscriber.next({
327
+ type: "TOOL_CALL_START",
328
+ toolCallId,
329
+ toolCallName: toolName,
330
+ parentMessageId: runId,
331
+ rawEvent,
332
+ timestamp: Date.now(),
333
+ threadId,
334
+ runId
335
+ });
336
+ startedToolCalls.add(toolCallId);
337
+ }
338
+ subscriber.next({
339
+ type: "TOOL_CALL_END",
340
+ toolCallId,
341
+ rawEvent,
342
+ timestamp: Date.now(),
343
+ threadId,
344
+ runId
345
+ });
346
+ }
347
+ subscriber.next({
348
+ type: "CUSTOM",
349
+ name: eventsData.event,
350
+ value: JSON.stringify(eventsData.data),
351
+ rawEvent,
352
+ timestamp: Date.now(),
353
+ threadId,
354
+ runId
355
+ });
356
+ break;
357
+ }
358
+ case "updates": {
359
+ const updatesData = data;
360
+ subscriber.next({
361
+ type: "STATE_SNAPSHOT",
362
+ snapshot: updatesData,
363
+ rawEvent: {
364
+ id: runId,
365
+ event: "updates",
366
+ data: updatesData
367
+ },
368
+ timestamp: Date.now(),
369
+ threadId,
370
+ runId
371
+ });
372
+ break;
373
+ }
374
+ case "values": {
375
+ const valuesData = data;
376
+ subscriber.next({
377
+ type: "STATE_SNAPSHOT",
378
+ snapshot: valuesData,
379
+ rawEvent: {
380
+ id: runId,
381
+ event: "values",
382
+ data: valuesData
383
+ },
384
+ timestamp: Date.now(),
385
+ threadId,
386
+ runId
387
+ });
388
+ break;
389
+ }
390
+ case "custom": {
391
+ const customData = data;
392
+ const result = handleCustomEvent(
393
+ customData,
394
+ threadId,
395
+ runId,
396
+ subscriber,
397
+ manuallyEmittedState
398
+ );
399
+ manuallyEmittedState = result.manuallyEmittedState;
400
+ break;
401
+ }
402
+ case "error": {
403
+ const errorData = data;
404
+ if (debug) {
405
+ console.error(
406
+ "[HistoryHydratingRunner] Stream error:",
407
+ errorData.message
408
+ );
409
+ }
410
+ subscriber.next({
411
+ type: "CUSTOM",
412
+ name: "on_error",
413
+ value: JSON.stringify(errorData),
414
+ rawEvent: {
415
+ id: runId,
416
+ error: errorData.error,
417
+ message: errorData.message
418
+ },
419
+ timestamp: Date.now(),
420
+ threadId,
421
+ runId
422
+ });
423
+ break;
424
+ }
425
+ default: {
426
+ if (debug) {
427
+ console.log(
428
+ `[HistoryHydratingRunner] Unhandled event type: ${event}`,
429
+ data
430
+ );
431
+ }
432
+ }
433
+ }
434
+ return { runId, manuallyEmittedState };
435
+ }
436
+ function handleCustomEvent(customData, threadId, runId, subscriber, manuallyEmittedState) {
437
+ const rawEvent = {
438
+ id: runId,
439
+ data: customData
440
+ };
441
+ const typedData = customData;
442
+ const eventName = typedData?.name || typedData?.event;
443
+ switch (eventName) {
444
+ case "copilotkit_manually_emit_message" /* CopilotKitManuallyEmitMessage */: {
445
+ const value = typedData.value;
446
+ const messageId = value?.message_id || runId;
447
+ const message = value?.message || "";
448
+ subscriber.next({
449
+ type: "TEXT_MESSAGE_START",
450
+ role: "assistant",
451
+ messageId,
452
+ rawEvent,
453
+ timestamp: Date.now(),
454
+ threadId,
455
+ runId
456
+ });
457
+ subscriber.next({
458
+ type: "TEXT_MESSAGE_CONTENT",
459
+ messageId,
460
+ delta: message,
461
+ rawEvent,
462
+ timestamp: Date.now(),
463
+ threadId,
464
+ runId
465
+ });
466
+ subscriber.next({
467
+ type: "TEXT_MESSAGE_END",
468
+ messageId,
469
+ rawEvent,
470
+ timestamp: Date.now(),
471
+ threadId,
472
+ runId
473
+ });
474
+ break;
475
+ }
476
+ case "copilotkit_manually_emit_tool_call" /* CopilotKitManuallyEmitToolCall */: {
477
+ const value = typedData.value;
478
+ const toolCallId = value?.id || runId;
479
+ const toolCallName = value?.name || "";
480
+ const args = value?.args || {};
481
+ subscriber.next({
482
+ type: "TOOL_CALL_START",
483
+ toolCallId,
484
+ toolCallName,
485
+ parentMessageId: toolCallId,
486
+ rawEvent,
487
+ timestamp: Date.now(),
488
+ threadId,
489
+ runId
490
+ });
491
+ subscriber.next({
492
+ type: "TOOL_CALL_ARGS",
493
+ toolCallId,
494
+ delta: JSON.stringify(args),
495
+ rawEvent,
496
+ timestamp: Date.now(),
497
+ threadId,
498
+ runId
499
+ });
500
+ subscriber.next({
501
+ type: "TOOL_CALL_END",
502
+ toolCallId,
503
+ rawEvent,
504
+ timestamp: Date.now(),
505
+ threadId,
506
+ runId
507
+ });
508
+ break;
509
+ }
510
+ case "copilotkit_manually_emit_intermediate_state" /* CopilotKitManuallyEmitIntermediateState */: {
511
+ manuallyEmittedState = typedData.value;
512
+ subscriber.next({
513
+ type: "STATE_SNAPSHOT",
514
+ snapshot: manuallyEmittedState,
515
+ rawEvent,
516
+ timestamp: Date.now(),
517
+ threadId,
518
+ runId
519
+ });
520
+ break;
521
+ }
522
+ case "copilotkit_exit" /* CopilotKitExit */: {
523
+ subscriber.next({
524
+ type: "CUSTOM",
525
+ name: "Exit",
526
+ value: true,
527
+ rawEvent,
528
+ timestamp: Date.now(),
529
+ threadId,
530
+ runId
531
+ });
532
+ break;
533
+ }
534
+ default: {
535
+ subscriber.next({
536
+ type: "CUSTOM",
537
+ name: eventName || "on_custom_event",
538
+ value: JSON.stringify(customData),
539
+ rawEvent,
540
+ timestamp: Date.now(),
541
+ threadId,
542
+ runId
543
+ });
544
+ }
545
+ }
546
+ return { manuallyEmittedState };
547
+ }
548
+
549
+ // src/runner/history-hydrating-runner.ts
550
+ var HistoryHydratingAgentRunner = class extends AgentRunner {
551
+ agent;
552
+ historyLimit;
553
+ debug;
554
+ stateExtractor;
555
+ activeRun = {};
556
+ /**
557
+ * Frozen agent config to prevent shared state contamination.
558
+ * We store the raw config values and create fresh Agent/Client instances per request.
559
+ * This is critical because Vercel serverless can bundle multiple routes together,
560
+ * causing module-level state to leak between different agent configurations.
561
+ */
562
+ frozenConfig;
563
+ constructor(config) {
564
+ super();
565
+ this.agent = config.agent;
566
+ this.debug = config.debug ?? false;
567
+ this.stateExtractor = config.stateExtractor;
568
+ this.historyLimit = Math.min(
569
+ config.historyLimit ?? DEFAULT_HISTORY_LIMIT,
570
+ MAX_HISTORY_LIMIT
571
+ );
572
+ this.frozenConfig = Object.freeze({
573
+ deploymentUrl: config.deploymentUrl,
574
+ graphId: config.graphId,
575
+ langsmithApiKey: config.langsmithApiKey,
576
+ clientTimeoutMs: config.clientTimeoutMs ?? DEFAULT_TIMEOUT,
577
+ onRequest: config.onRequest
578
+ });
579
+ }
580
+ /**
581
+ * Creates a fresh LangGraphAgent instance using the frozen config.
582
+ * Uses our isolated agent creator to prevent shared state contamination.
583
+ */
584
+ createFreshAgent() {
585
+ return createIsolatedAgent({
586
+ deploymentUrl: this.frozenConfig.deploymentUrl,
587
+ graphId: this.frozenConfig.graphId,
588
+ langsmithApiKey: this.frozenConfig.langsmithApiKey,
589
+ clientTimeoutMs: this.frozenConfig.clientTimeoutMs,
590
+ onRequest: this.frozenConfig.onRequest
591
+ });
592
+ }
593
+ /**
594
+ * Creates a fresh LangGraph Client instance using the frozen config.
595
+ * This prevents shared state contamination in serverless environments.
596
+ */
597
+ createFreshClient() {
598
+ return new Client({
599
+ apiUrl: this.frozenConfig.deploymentUrl,
600
+ apiKey: this.frozenConfig.langsmithApiKey,
601
+ timeoutMs: this.frozenConfig.clientTimeoutMs,
602
+ onRequest: this.frozenConfig.onRequest
603
+ });
604
+ }
605
+ /**
606
+ * Log a message if debug mode is enabled.
607
+ */
608
+ log(message, ...args) {
609
+ if (this.debug) {
610
+ console.log(`[HistoryHydratingRunner] ${message}`, ...args);
611
+ }
612
+ }
613
+ /**
614
+ * Log a warning.
615
+ */
616
+ warn(message, ...args) {
617
+ console.warn(`[HistoryHydratingRunner] ${message}`, ...args);
618
+ }
619
+ /**
620
+ * Log an error.
621
+ */
622
+ error(message, ...args) {
623
+ console.error(`[HistoryHydratingRunner] ${message}`, ...args);
624
+ }
625
+ /**
626
+ * Run the agent with a FRESH agent instance.
627
+ * CRITICAL: We cannot trust request.agent (cloned by CopilotKit) because
628
+ * its internal Client may have been corrupted by shared module state in
629
+ * Vercel serverless environments. Create a completely fresh agent with
630
+ * our frozen config to guarantee the correct deployment URL is used.
631
+ */
632
+ run(request) {
633
+ const freshAgent = this.createFreshAgent();
634
+ const inputWithProps = request.input;
635
+ const forwardedProps = inputWithProps.forwardedProps;
636
+ const existingState = request.input.state || {};
637
+ let enrichedState;
638
+ if (this.stateExtractor) {
639
+ const extractedState = this.stateExtractor(request.input, forwardedProps);
640
+ enrichedState = {
641
+ ...existingState,
642
+ ...extractedState
643
+ };
644
+ } else {
645
+ enrichedState = existingState;
646
+ }
647
+ this.log("State extraction:", {
648
+ hasStateExtractor: !!this.stateExtractor,
649
+ hasForwardedProps: !!forwardedProps,
650
+ hasState: !!request.input.state,
651
+ threadId: request.input.threadId
652
+ });
653
+ freshAgent.setState(enrichedState);
654
+ const inputWithState = {
655
+ ...request.input,
656
+ state: enrichedState
657
+ };
658
+ return freshAgent.run(inputWithState);
659
+ }
660
+ /**
661
+ * Delegate isRunning to the agent.
662
+ */
663
+ async isRunning() {
664
+ return this.agent.isRunning;
665
+ }
666
+ /**
667
+ * Delegate stop to the agent.
668
+ */
669
+ async stop(_request) {
670
+ const result = this.agent.abortRun();
671
+ return result !== void 0 ? result : true;
672
+ }
673
+ /**
674
+ * Override connect to add history hydration support.
675
+ *
676
+ * When reconnecting to a thread:
677
+ * 1. Fetches ALL thread history (checkpoints) from LangGraph
678
+ * 2. Extracts and deduplicates messages from all checkpoints
679
+ * 3. Transforms historical messages to CopilotKit format
680
+ * 4. Emits MESSAGES_SNAPSHOT and STATE_SNAPSHOT events
681
+ * 5. Completes the observable
682
+ */
683
+ connect(request) {
684
+ const { threadId } = request;
685
+ const client = this.createFreshClient();
686
+ return new Observable((subscriber) => {
687
+ const hydrate = async () => {
688
+ try {
689
+ const history = await client.threads.getHistory(threadId, {
690
+ limit: this.historyLimit > 0 ? this.historyLimit : DEFAULT_HISTORY_LIMIT
691
+ });
692
+ if (!history || history.length === 0) {
693
+ this.warn(`No history found for thread ${threadId}`);
694
+ const fallbackRunId = "hydration_" + Math.random().toString(36).slice(2);
695
+ subscriber.next({
696
+ type: EventType.RUN_STARTED,
697
+ timestamp: Date.now(),
698
+ threadId,
699
+ runId: fallbackRunId
700
+ });
701
+ subscriber.next({
702
+ type: EventType.MESSAGES_SNAPSHOT,
703
+ messages: [],
704
+ timestamp: Date.now(),
705
+ threadId,
706
+ runId: fallbackRunId
707
+ });
708
+ subscriber.next({
709
+ type: EventType.RUN_FINISHED,
710
+ timestamp: Date.now(),
711
+ threadId,
712
+ runId: fallbackRunId
713
+ });
714
+ subscriber.complete();
715
+ return;
716
+ }
717
+ const allMessages = [];
718
+ const seenMessageIds = /* @__PURE__ */ new Set();
719
+ for (const checkpoint of history.reverse()) {
720
+ const state = checkpoint;
721
+ if (state.values?.messages) {
722
+ const messages = state.values.messages || [];
723
+ for (const msg of messages) {
724
+ if (!seenMessageIds.has(msg.id)) {
725
+ seenMessageIds.add(msg.id);
726
+ allMessages.push(msg);
727
+ }
728
+ }
729
+ }
730
+ }
731
+ this.log(
732
+ `Loaded ${allMessages.length} unique messages from ${history.length} checkpoints`
733
+ );
734
+ const limitedMessages = this.historyLimit > 0 ? allMessages.slice(-this.historyLimit) : allMessages;
735
+ const transformedMessages = transformMessages(limitedMessages, {
736
+ debug: this.debug
737
+ });
738
+ let runId;
739
+ try {
740
+ const runs = await client.runs.list(threadId);
741
+ runId = runs && runs.length > 0 ? runs[0].run_id : "hydration_" + Math.random().toString(36).slice(2);
742
+ } catch (error) {
743
+ this.warn("Failed to fetch runs, using generated ID:", error);
744
+ runId = "hydration_" + Math.random().toString(36).slice(2);
745
+ }
746
+ subscriber.next({
747
+ type: EventType.RUN_STARTED,
748
+ timestamp: Date.now(),
749
+ threadId,
750
+ runId
751
+ });
752
+ subscriber.next({
753
+ type: EventType.MESSAGES_SNAPSHOT,
754
+ messages: transformedMessages,
755
+ timestamp: Date.now(),
756
+ threadId,
757
+ runId
758
+ });
759
+ const latestState = history[history.length - 1];
760
+ if (latestState.values) {
761
+ subscriber.next({
762
+ type: "STATE_SNAPSHOT",
763
+ snapshot: latestState.values,
764
+ rawEvent: {
765
+ id: runId,
766
+ event: "values",
767
+ data: latestState.values
768
+ },
769
+ timestamp: Date.now(),
770
+ threadId,
771
+ runId
772
+ });
773
+ }
774
+ const interruptedTask = latestState.tasks?.find(
775
+ (task) => task.interrupts && task.interrupts.length > 0
776
+ );
777
+ if (interruptedTask && interruptedTask.interrupts && interruptedTask.interrupts.length > 0) {
778
+ const interrupt = interruptedTask.interrupts[0];
779
+ const interruptValue = interrupt?.value;
780
+ subscriber.next({
781
+ type: "CUSTOM",
782
+ name: "on_interrupt",
783
+ value: JSON.stringify(interruptValue),
784
+ rawEvent: {
785
+ id: runId,
786
+ value: interruptValue
787
+ },
788
+ timestamp: Date.now(),
789
+ threadId,
790
+ runId
791
+ });
792
+ }
793
+ const isThreadBusy = latestState.next && latestState.next.length > 0;
794
+ let activeRun;
795
+ if (isThreadBusy) {
796
+ try {
797
+ const runs = await client.runs.list(threadId);
798
+ activeRun = runs?.find(
799
+ (run) => run.status === "running" || run.status === "pending"
800
+ );
801
+ } catch (error) {
802
+ this.warn("Failed to check for active runs:", error);
803
+ }
804
+ }
805
+ if (activeRun) {
806
+ this.log(`Joining active stream for run ${activeRun.run_id}`);
807
+ try {
808
+ await this.joinAndProcessStream(
809
+ client,
810
+ threadId,
811
+ activeRun.run_id,
812
+ subscriber
813
+ );
814
+ } catch (error) {
815
+ this.error("Error joining stream:", error);
816
+ }
817
+ } else {
818
+ subscriber.next({
819
+ type: EventType.RUN_FINISHED,
820
+ timestamp: Date.now(),
821
+ threadId,
822
+ runId
823
+ });
824
+ }
825
+ subscriber.complete();
826
+ } catch (error) {
827
+ this.error("Failed to hydrate history:", error);
828
+ const fallbackRunId = "hydration_error_" + Math.random().toString(36).slice(2);
829
+ subscriber.next({
830
+ type: EventType.RUN_STARTED,
831
+ timestamp: Date.now(),
832
+ threadId,
833
+ runId: fallbackRunId
834
+ });
835
+ subscriber.next({
836
+ type: EventType.MESSAGES_SNAPSHOT,
837
+ messages: [],
838
+ timestamp: Date.now(),
839
+ threadId,
840
+ runId: fallbackRunId
841
+ });
842
+ subscriber.next({
843
+ type: EventType.RUN_FINISHED,
844
+ timestamp: Date.now(),
845
+ threadId,
846
+ runId: fallbackRunId
847
+ });
848
+ subscriber.complete();
849
+ }
850
+ };
851
+ hydrate();
852
+ });
853
+ }
854
+ /**
855
+ * Joins an active stream and processes its events.
856
+ *
857
+ * This method connects to an already-running LangGraph execution and
858
+ * processes all incoming events, transforming them to BaseEvent format.
859
+ *
860
+ * Tracks started messages and tool calls to handle mid-stream joins where
861
+ * we might receive CONTENT/END events without having seen START events.
862
+ */
863
+ async joinAndProcessStream(client, threadId, runId, subscriber) {
864
+ const startedMessages = /* @__PURE__ */ new Set();
865
+ const startedToolCalls = /* @__PURE__ */ new Set();
866
+ try {
867
+ const stream = client.runs.joinStream(threadId, runId, {
868
+ streamMode: ["events", "values", "updates", "custom"]
869
+ });
870
+ let currentRunId = runId;
871
+ let manuallyEmittedState = this.activeRun.manuallyEmittedState;
872
+ for await (const chunk of stream) {
873
+ try {
874
+ const result = await processStreamChunk(chunk, {
875
+ threadId,
876
+ runId: currentRunId,
877
+ subscriber,
878
+ startedMessages,
879
+ startedToolCalls,
880
+ debug: this.debug,
881
+ manuallyEmittedState
882
+ });
883
+ currentRunId = result.runId;
884
+ manuallyEmittedState = result.manuallyEmittedState;
885
+ } catch (chunkError) {
886
+ this.error("Error processing stream chunk:", chunkError);
887
+ }
888
+ }
889
+ this.activeRun.manuallyEmittedState = manuallyEmittedState;
890
+ try {
891
+ const state = await client.threads.getState(threadId);
892
+ const threadState = state;
893
+ const interruptedTask = threadState.tasks?.find(
894
+ (task) => task.interrupts && task.interrupts.length > 0
895
+ );
896
+ if (interruptedTask && interruptedTask.interrupts && interruptedTask.interrupts.length > 0) {
897
+ const interrupt = interruptedTask.interrupts[0];
898
+ const interruptValue = interrupt?.value;
899
+ subscriber.next({
900
+ type: "CUSTOM",
901
+ name: "on_interrupt",
902
+ value: JSON.stringify(interruptValue),
903
+ rawEvent: {
904
+ id: currentRunId,
905
+ value: interruptValue
906
+ },
907
+ timestamp: Date.now(),
908
+ threadId,
909
+ runId: currentRunId
910
+ });
911
+ }
912
+ } catch (stateError) {
913
+ this.warn("Failed to check for interrupts after stream:", stateError);
914
+ }
915
+ subscriber.next({
916
+ type: EventType.RUN_FINISHED,
917
+ timestamp: Date.now(),
918
+ threadId,
919
+ runId: currentRunId
920
+ });
921
+ } catch (error) {
922
+ this.error("Error in joinAndProcessStream:", error);
923
+ subscriber.next({
924
+ type: EventType.RUN_FINISHED,
925
+ timestamp: Date.now(),
926
+ threadId,
927
+ runId
928
+ });
929
+ throw error;
930
+ }
931
+ }
932
+ };
933
+
934
+ export { CustomEventNames, DEFAULT_HISTORY_LIMIT, DEFAULT_TIMEOUT, HistoryHydratingAgentRunner, LangGraphEventTypes, MAX_HISTORY_LIMIT, createIsolatedAgent, extractContent, processStreamChunk, transformMessages };
935
+ //# sourceMappingURL=index.js.map
936
+ //# sourceMappingURL=index.js.map