@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.
@@ -0,0 +1,647 @@
1
+ import { EventType, BaseEvent } from '@ag-ui/core';
2
+ import { AgentRunnerRunRequest, AgentRunner, AgentRunnerStopRequest, AgentRunnerConnectRequest } from '@copilotkitnext/runtime';
3
+ import { Observable } from 'rxjs';
4
+ import { LangGraphAgent } from '@copilotkit/runtime/langgraph';
5
+ import { RequestHook } from '@langchain/langgraph-sdk/client';
6
+
7
+ /**
8
+ * Configuration for the HistoryHydratingAgentRunner.
9
+ */
10
+ interface HistoryHydratingRunnerConfig {
11
+ /**
12
+ * The LangGraphAgent instance to delegate run() calls to.
13
+ */
14
+ agent: LangGraphAgent;
15
+ /**
16
+ * LangGraph deployment URL (required).
17
+ * Used to create fresh Client instances for history fetching.
18
+ */
19
+ deploymentUrl: string;
20
+ /**
21
+ * Graph ID for the agent.
22
+ */
23
+ graphId: string;
24
+ /**
25
+ * LangSmith API key for authentication (optional).
26
+ */
27
+ langsmithApiKey?: string;
28
+ /**
29
+ * Maximum number of history checkpoints to fetch.
30
+ * Default: 100, Maximum: 1000 (LangGraph API limit)
31
+ */
32
+ historyLimit?: number;
33
+ /**
34
+ * Client timeout in milliseconds.
35
+ * Default: 1800000 (30 minutes) - supports long-running agents.
36
+ */
37
+ clientTimeoutMs?: number;
38
+ /**
39
+ * Enable debug logging.
40
+ * Default: false
41
+ */
42
+ debug?: boolean;
43
+ /**
44
+ * Optional function to extract additional state from the request.
45
+ * Called during run() to enrich the state passed to the agent.
46
+ *
47
+ * @param input - The run request input
48
+ * @param forwardedProps - Optional forwarded props from CopilotKit
49
+ * @returns State object to merge with existing state
50
+ */
51
+ stateExtractor?: StateExtractor;
52
+ /**
53
+ * Optional request hook for the client.
54
+ */
55
+ onRequest?: RequestHook;
56
+ }
57
+ /**
58
+ * Function type for extracting state from run requests.
59
+ */
60
+ type StateExtractor = (input: AgentRunnerRunRequest["input"], forwardedProps?: Record<string, unknown>) => Record<string, unknown>;
61
+ /**
62
+ * LangGraph message format from thread state.
63
+ */
64
+ interface LangGraphMessage {
65
+ id: string;
66
+ type: "human" | "ai" | "tool" | "system";
67
+ content: string | Array<{
68
+ type: string;
69
+ text?: string;
70
+ }>;
71
+ tool_calls?: Array<{
72
+ id: string;
73
+ name: string;
74
+ args: Record<string, unknown>;
75
+ }>;
76
+ tool_call_id?: string;
77
+ }
78
+ /**
79
+ * Thread state from LangGraph checkpoint.
80
+ */
81
+ interface ThreadState {
82
+ values: {
83
+ messages?: LangGraphMessage[];
84
+ [key: string]: unknown;
85
+ };
86
+ next: string[];
87
+ config?: unknown;
88
+ created_at?: string;
89
+ parent_config?: unknown;
90
+ tasks?: Array<{
91
+ id: string;
92
+ name: string;
93
+ interrupts?: Array<{
94
+ value?: unknown;
95
+ [key: string]: unknown;
96
+ }>;
97
+ [key: string]: unknown;
98
+ }>;
99
+ checkpoint: unknown;
100
+ metadata: unknown;
101
+ parent_checkpoint?: unknown;
102
+ }
103
+ /**
104
+ * Tool used to predict state (for intermediate state emission).
105
+ */
106
+ interface PredictStateTool {
107
+ tool: string;
108
+ state_key: string;
109
+ tool_argument: string;
110
+ }
111
+ /**
112
+ * Frozen agent config to prevent shared state contamination.
113
+ */
114
+ interface FrozenAgentConfig {
115
+ deploymentUrl: string;
116
+ graphId: string;
117
+ langsmithApiKey?: string;
118
+ clientTimeoutMs: number;
119
+ onRequest?: RequestHook;
120
+ }
121
+
122
+ /**
123
+ * HistoryHydratingAgentRunner
124
+ *
125
+ * A custom AgentRunner that extends CopilotKit's base runner to add
126
+ * message history hydration support for LangGraph threads.
127
+ *
128
+ * Fixes the issue where page refreshes don't load historical messages
129
+ * by fetching thread state and emitting MESSAGES_SNAPSHOT events.
130
+ */
131
+
132
+ /**
133
+ * Custom AgentRunner that extends CopilotKit's base runner to add
134
+ * message history hydration support for LangGraph threads.
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * import { HistoryHydratingAgentRunner, createIsolatedAgent } from 'copilotkit-langgraph-history';
139
+ *
140
+ * const agent = createIsolatedAgent({
141
+ * deploymentUrl: process.env.LANGGRAPH_DEPLOYMENT_URL!,
142
+ * graphId: "my-agent",
143
+ * langsmithApiKey: process.env.LANGSMITH_API_KEY,
144
+ * });
145
+ *
146
+ * const runner = new HistoryHydratingAgentRunner({
147
+ * agent,
148
+ * deploymentUrl: process.env.LANGGRAPH_DEPLOYMENT_URL!,
149
+ * graphId: "my-agent",
150
+ * langsmithApiKey: process.env.LANGSMITH_API_KEY,
151
+ * historyLimit: 100,
152
+ * });
153
+ *
154
+ * const runtime = new CopilotRuntime({
155
+ * agents: { "my-agent": agent },
156
+ * runner,
157
+ * });
158
+ * ```
159
+ */
160
+ declare class HistoryHydratingAgentRunner extends AgentRunner {
161
+ private agent;
162
+ private historyLimit;
163
+ private debug;
164
+ private stateExtractor?;
165
+ private activeRun;
166
+ /**
167
+ * Frozen agent config to prevent shared state contamination.
168
+ * We store the raw config values and create fresh Agent/Client instances per request.
169
+ * This is critical because Vercel serverless can bundle multiple routes together,
170
+ * causing module-level state to leak between different agent configurations.
171
+ */
172
+ private readonly frozenConfig;
173
+ constructor(config: HistoryHydratingRunnerConfig);
174
+ /**
175
+ * Creates a fresh LangGraphAgent instance using the frozen config.
176
+ * Uses our isolated agent creator to prevent shared state contamination.
177
+ */
178
+ private createFreshAgent;
179
+ /**
180
+ * Creates a fresh LangGraph Client instance using the frozen config.
181
+ * This prevents shared state contamination in serverless environments.
182
+ */
183
+ private createFreshClient;
184
+ /**
185
+ * Log a message if debug mode is enabled.
186
+ */
187
+ private log;
188
+ /**
189
+ * Log a warning.
190
+ */
191
+ private warn;
192
+ /**
193
+ * Log an error.
194
+ */
195
+ private error;
196
+ /**
197
+ * Run the agent with a FRESH agent instance.
198
+ * CRITICAL: We cannot trust request.agent (cloned by CopilotKit) because
199
+ * its internal Client may have been corrupted by shared module state in
200
+ * Vercel serverless environments. Create a completely fresh agent with
201
+ * our frozen config to guarantee the correct deployment URL is used.
202
+ */
203
+ run(request: AgentRunnerRunRequest): Observable<{
204
+ type: EventType.TEXT_MESSAGE_START;
205
+ role: "developer" | "system" | "assistant" | "user";
206
+ messageId: string;
207
+ timestamp?: number | undefined;
208
+ rawEvent?: any;
209
+ } | {
210
+ type: EventType.TEXT_MESSAGE_CONTENT;
211
+ messageId: string;
212
+ delta: string;
213
+ timestamp?: number | undefined;
214
+ rawEvent?: any;
215
+ } | {
216
+ type: EventType.TEXT_MESSAGE_END;
217
+ messageId: string;
218
+ timestamp?: number | undefined;
219
+ rawEvent?: any;
220
+ } | {
221
+ type: EventType.TOOL_CALL_START;
222
+ toolCallId: string;
223
+ toolCallName: string;
224
+ timestamp?: number | undefined;
225
+ rawEvent?: any;
226
+ parentMessageId?: string | undefined;
227
+ } | {
228
+ type: EventType.TOOL_CALL_ARGS;
229
+ toolCallId: string;
230
+ delta: string;
231
+ timestamp?: number | undefined;
232
+ rawEvent?: any;
233
+ } | {
234
+ type: EventType.TOOL_CALL_END;
235
+ toolCallId: string;
236
+ timestamp?: number | undefined;
237
+ rawEvent?: any;
238
+ } | {
239
+ type: EventType.THINKING_TEXT_MESSAGE_START;
240
+ timestamp?: number | undefined;
241
+ rawEvent?: any;
242
+ } | {
243
+ type: EventType.THINKING_TEXT_MESSAGE_CONTENT;
244
+ delta: string;
245
+ timestamp?: number | undefined;
246
+ rawEvent?: any;
247
+ } | {
248
+ type: EventType.THINKING_TEXT_MESSAGE_END;
249
+ timestamp?: number | undefined;
250
+ rawEvent?: any;
251
+ } | {
252
+ type: EventType.TOOL_CALL_RESULT;
253
+ content: string;
254
+ toolCallId: string;
255
+ messageId: string;
256
+ role?: "tool" | undefined;
257
+ timestamp?: number | undefined;
258
+ rawEvent?: any;
259
+ } | {
260
+ type: EventType.THINKING_START;
261
+ timestamp?: number | undefined;
262
+ rawEvent?: any;
263
+ title?: string | undefined;
264
+ } | {
265
+ type: EventType.THINKING_END;
266
+ timestamp?: number | undefined;
267
+ rawEvent?: any;
268
+ } | {
269
+ type: EventType.STATE_SNAPSHOT;
270
+ timestamp?: number | undefined;
271
+ rawEvent?: any;
272
+ snapshot?: any;
273
+ } | {
274
+ type: EventType.STATE_DELTA;
275
+ delta: any[];
276
+ timestamp?: number | undefined;
277
+ rawEvent?: any;
278
+ } | {
279
+ type: EventType.MESSAGES_SNAPSHOT;
280
+ messages: ({
281
+ id: string;
282
+ role: "developer";
283
+ content: string;
284
+ name?: string | undefined;
285
+ } | {
286
+ id: string;
287
+ role: "system";
288
+ content: string;
289
+ name?: string | undefined;
290
+ } | {
291
+ id: string;
292
+ role: "assistant";
293
+ name?: string | undefined;
294
+ content?: string | undefined;
295
+ toolCalls?: {
296
+ function: {
297
+ name: string;
298
+ arguments: string;
299
+ };
300
+ type: "function";
301
+ id: string;
302
+ }[] | undefined;
303
+ } | {
304
+ id: string;
305
+ role: "user";
306
+ content: string | ({
307
+ type: "text";
308
+ text: string;
309
+ } | {
310
+ type: "binary";
311
+ mimeType: string;
312
+ id?: string | undefined;
313
+ url?: string | undefined;
314
+ data?: string | undefined;
315
+ filename?: string | undefined;
316
+ })[];
317
+ name?: string | undefined;
318
+ } | {
319
+ id: string;
320
+ role: "tool";
321
+ content: string;
322
+ toolCallId: string;
323
+ error?: string | undefined;
324
+ } | {
325
+ id: string;
326
+ role: "activity";
327
+ content: Record<string, any>;
328
+ activityType: string;
329
+ })[];
330
+ timestamp?: number | undefined;
331
+ rawEvent?: any;
332
+ } | {
333
+ type: EventType.RAW;
334
+ timestamp?: number | undefined;
335
+ rawEvent?: any;
336
+ event?: any;
337
+ source?: string | undefined;
338
+ } | {
339
+ name: string;
340
+ type: EventType.CUSTOM;
341
+ value?: any;
342
+ timestamp?: number | undefined;
343
+ rawEvent?: any;
344
+ } | {
345
+ type: EventType.RUN_STARTED;
346
+ threadId: string;
347
+ runId: string;
348
+ parentRunId?: string | undefined;
349
+ timestamp?: number | undefined;
350
+ rawEvent?: any;
351
+ input?: {
352
+ threadId: string;
353
+ runId: string;
354
+ messages: ({
355
+ id: string;
356
+ role: "developer";
357
+ content: string;
358
+ name?: string | undefined;
359
+ } | {
360
+ id: string;
361
+ role: "system";
362
+ content: string;
363
+ name?: string | undefined;
364
+ } | {
365
+ id: string;
366
+ role: "assistant";
367
+ name?: string | undefined;
368
+ content?: string | undefined;
369
+ toolCalls?: {
370
+ function: {
371
+ name: string;
372
+ arguments: string;
373
+ };
374
+ type: "function";
375
+ id: string;
376
+ }[] | undefined;
377
+ } | {
378
+ id: string;
379
+ role: "user";
380
+ content: string | ({
381
+ type: "text";
382
+ text: string;
383
+ } | {
384
+ type: "binary";
385
+ mimeType: string;
386
+ id?: string | undefined;
387
+ url?: string | undefined;
388
+ data?: string | undefined;
389
+ filename?: string | undefined;
390
+ })[];
391
+ name?: string | undefined;
392
+ } | {
393
+ id: string;
394
+ role: "tool";
395
+ content: string;
396
+ toolCallId: string;
397
+ error?: string | undefined;
398
+ } | {
399
+ id: string;
400
+ role: "activity";
401
+ content: Record<string, any>;
402
+ activityType: string;
403
+ })[];
404
+ tools: {
405
+ name: string;
406
+ description: string;
407
+ parameters?: any;
408
+ }[];
409
+ context: {
410
+ value: string;
411
+ description: string;
412
+ }[];
413
+ parentRunId?: string | undefined;
414
+ state?: any;
415
+ forwardedProps?: any;
416
+ } | undefined;
417
+ } | {
418
+ type: EventType.RUN_FINISHED;
419
+ threadId: string;
420
+ runId: string;
421
+ timestamp?: number | undefined;
422
+ rawEvent?: any;
423
+ result?: any;
424
+ } | {
425
+ message: string;
426
+ type: EventType.RUN_ERROR;
427
+ code?: string | undefined;
428
+ timestamp?: number | undefined;
429
+ rawEvent?: any;
430
+ } | {
431
+ type: EventType.STEP_STARTED;
432
+ stepName: string;
433
+ timestamp?: number | undefined;
434
+ rawEvent?: any;
435
+ } | {
436
+ type: EventType.STEP_FINISHED;
437
+ stepName: string;
438
+ timestamp?: number | undefined;
439
+ rawEvent?: any;
440
+ } | {
441
+ type: EventType;
442
+ name: string;
443
+ value: any;
444
+ }>;
445
+ /**
446
+ * Delegate isRunning to the agent.
447
+ */
448
+ isRunning(): Promise<boolean>;
449
+ /**
450
+ * Delegate stop to the agent.
451
+ */
452
+ stop(_request: AgentRunnerStopRequest): Promise<boolean | undefined>;
453
+ /**
454
+ * Override connect to add history hydration support.
455
+ *
456
+ * When reconnecting to a thread:
457
+ * 1. Fetches ALL thread history (checkpoints) from LangGraph
458
+ * 2. Extracts and deduplicates messages from all checkpoints
459
+ * 3. Transforms historical messages to CopilotKit format
460
+ * 4. Emits MESSAGES_SNAPSHOT and STATE_SNAPSHOT events
461
+ * 5. Completes the observable
462
+ */
463
+ connect(request: AgentRunnerConnectRequest): Observable<BaseEvent>;
464
+ /**
465
+ * Joins an active stream and processes its events.
466
+ *
467
+ * This method connects to an already-running LangGraph execution and
468
+ * processes all incoming events, transforming them to BaseEvent format.
469
+ *
470
+ * Tracks started messages and tool calls to handle mid-stream joins where
471
+ * we might receive CONTENT/END events without having seen START events.
472
+ */
473
+ private joinAndProcessStream;
474
+ }
475
+
476
+ /**
477
+ * LangGraph Agent Isolation Utilities
478
+ *
479
+ * Fixes shared state contamination in Vercel serverless (Fluid Compute)
480
+ * where CopilotKit's LangGraphAgent can get wrong deploymentUrl due to
481
+ * module-level state being shared between bundled routes.
482
+ *
483
+ * Root cause: CopilotKit's clone() passes config by reference, not by value.
484
+ * Our fix: Create completely isolated agents with verified URLs.
485
+ */
486
+
487
+ /**
488
+ * Configuration for creating an isolated LangGraph agent.
489
+ */
490
+ interface CreateIsolatedAgentConfig {
491
+ /**
492
+ * LangGraph deployment URL.
493
+ */
494
+ deploymentUrl: string;
495
+ /**
496
+ * Graph ID for the agent.
497
+ */
498
+ graphId: string;
499
+ /**
500
+ * LangSmith API key for authentication (optional).
501
+ */
502
+ langsmithApiKey?: string;
503
+ /**
504
+ * Client timeout in milliseconds.
505
+ * Default: 1800000 (30 minutes)
506
+ */
507
+ clientTimeoutMs?: number;
508
+ /**
509
+ * Enable debug mode on the agent.
510
+ */
511
+ debug?: boolean;
512
+ /**
513
+ * Optional request hook for the client.
514
+ */
515
+ onRequest?: RequestHook;
516
+ }
517
+ /**
518
+ * Creates a completely isolated LangGraphAgent that cannot be contaminated
519
+ * by shared module state. This is the "nuclear option" fix for serverless
520
+ * environments like Vercel Fluid Compute.
521
+ *
522
+ * Key features:
523
+ * 1. Creates agent with fresh, frozen config
524
+ * 2. Verifies the internal client has correct URL
525
+ * 3. Force-replaces client if contamination detected
526
+ *
527
+ * @example
528
+ * ```typescript
529
+ * const agent = createIsolatedAgent({
530
+ * deploymentUrl: process.env.LANGGRAPH_DEPLOYMENT_URL!,
531
+ * graphId: "my-agent",
532
+ * langsmithApiKey: process.env.LANGSMITH_API_KEY,
533
+ * });
534
+ * ```
535
+ */
536
+ declare function createIsolatedAgent(config: CreateIsolatedAgentConfig): LangGraphAgent;
537
+
538
+ /**
539
+ * Default timeout for LangGraph Client HTTP requests (30 minutes).
540
+ * Long timeout supports long-running agent workflows.
541
+ */
542
+ declare const DEFAULT_TIMEOUT: number;
543
+ /**
544
+ * Default number of history checkpoints to fetch.
545
+ */
546
+ declare const DEFAULT_HISTORY_LIMIT = 100;
547
+ /**
548
+ * Maximum history limit allowed by the LangGraph API.
549
+ */
550
+ declare const MAX_HISTORY_LIMIT = 1000;
551
+
552
+ /**
553
+ * Custom event names that CopilotKit uses for manual emissions.
554
+ * These match exactly what CopilotKit's LangGraphAgent expects.
555
+ */
556
+ declare enum CustomEventNames {
557
+ CopilotKitManuallyEmitMessage = "copilotkit_manually_emit_message",
558
+ CopilotKitManuallyEmitToolCall = "copilotkit_manually_emit_tool_call",
559
+ CopilotKitManuallyEmitIntermediateState = "copilotkit_manually_emit_intermediate_state",
560
+ CopilotKitExit = "copilotkit_exit"
561
+ }
562
+
563
+ /**
564
+ * LangGraph event types for stream processing.
565
+ * These correspond to LangChain/LangGraph lifecycle events.
566
+ */
567
+ declare enum LangGraphEventTypes {
568
+ OnChatModelStart = "on_chat_model_start",
569
+ OnChatModelStream = "on_chat_model_stream",
570
+ OnChatModelEnd = "on_chat_model_end",
571
+ OnToolStart = "on_tool_start",
572
+ OnToolEnd = "on_tool_end",
573
+ OnChainStart = "on_chain_start",
574
+ OnChainEnd = "on_chain_end"
575
+ }
576
+
577
+ /**
578
+ * Transformed message in CopilotKit format.
579
+ */
580
+ interface TransformedMessage {
581
+ id: string;
582
+ role: "user" | "assistant" | "system" | "tool";
583
+ content: string;
584
+ toolCalls?: Array<{
585
+ id: string;
586
+ type: "function";
587
+ function: {
588
+ name: string;
589
+ arguments: string;
590
+ };
591
+ }>;
592
+ toolCallId?: string;
593
+ }
594
+ /**
595
+ * Extracts text content from LangGraph message content.
596
+ * Handles both string and array formats.
597
+ */
598
+ declare function extractContent(content: string | Array<{
599
+ type: string;
600
+ text?: string;
601
+ }>): string;
602
+ /**
603
+ * Transforms LangGraph messages to CopilotKit message format.
604
+ *
605
+ * Based on the `ut` function from @ag-ui/langgraph but adapted
606
+ * for standalone use.
607
+ */
608
+ declare function transformMessages(messages: LangGraphMessage[], options?: {
609
+ debug?: boolean;
610
+ }): TransformedMessage[];
611
+
612
+ /**
613
+ * Context for stream processing.
614
+ */
615
+ interface StreamProcessorContext {
616
+ threadId: string;
617
+ runId: string;
618
+ subscriber: {
619
+ next: (event: BaseEvent) => void;
620
+ };
621
+ startedMessages?: Set<string>;
622
+ startedToolCalls?: Set<string>;
623
+ debug?: boolean;
624
+ manuallyEmittedState?: Record<string, unknown>;
625
+ }
626
+ /**
627
+ * Stream chunk from LangGraph.
628
+ */
629
+ interface StreamChunk {
630
+ id?: string;
631
+ event: string;
632
+ data: unknown;
633
+ metadata?: Record<string, unknown>;
634
+ }
635
+ /**
636
+ * Processes a single stream chunk and transforms it to BaseEvent format.
637
+ *
638
+ * Based on CopilotKit's event processing patterns from the agent's .run method.
639
+ * Handles all event types including custom events, metadata filtering, and
640
+ * transformations for TEXT_MESSAGE and TOOL_CALL events.
641
+ */
642
+ declare function processStreamChunk(chunk: StreamChunk, context: StreamProcessorContext): Promise<{
643
+ runId: string;
644
+ manuallyEmittedState?: Record<string, unknown>;
645
+ }>;
646
+
647
+ export { type CreateIsolatedAgentConfig, CustomEventNames, DEFAULT_HISTORY_LIMIT, DEFAULT_TIMEOUT, type FrozenAgentConfig, HistoryHydratingAgentRunner, type HistoryHydratingRunnerConfig, LangGraphEventTypes, type LangGraphMessage, MAX_HISTORY_LIMIT, type PredictStateTool, type StateExtractor, type StreamChunk, type StreamProcessorContext, type ThreadState, type TransformedMessage, createIsolatedAgent, extractContent, processStreamChunk, transformMessages };