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