@langgraph-js/pure-graph 1.0.2 → 1.2.0

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 (87) hide show
  1. package/.prettierrc +11 -0
  2. package/README.md +104 -10
  3. package/bun.lock +209 -0
  4. package/dist/adapter/hono/assistants.js +3 -9
  5. package/dist/adapter/hono/endpoint.js +1 -2
  6. package/dist/adapter/hono/runs.js +6 -39
  7. package/dist/adapter/hono/threads.js +5 -46
  8. package/dist/adapter/nextjs/endpoint.d.ts +1 -0
  9. package/dist/adapter/nextjs/endpoint.js +2 -0
  10. package/dist/adapter/nextjs/index.d.ts +1 -0
  11. package/dist/adapter/nextjs/index.js +2 -0
  12. package/dist/adapter/nextjs/router.d.ts +5 -0
  13. package/dist/adapter/nextjs/router.js +168 -0
  14. package/dist/adapter/{hono → nextjs}/zod.d.ts +5 -5
  15. package/dist/adapter/{hono → nextjs}/zod.js +22 -5
  16. package/dist/adapter/zod.d.ts +577 -0
  17. package/dist/adapter/zod.js +119 -0
  18. package/dist/createEndpoint.d.ts +1 -2
  19. package/dist/createEndpoint.js +4 -3
  20. package/dist/global.d.ts +6 -4
  21. package/dist/global.js +10 -5
  22. package/dist/graph/stream.d.ts +1 -1
  23. package/dist/graph/stream.js +18 -10
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +1 -0
  26. package/dist/queue/stream_queue.d.ts +5 -3
  27. package/dist/queue/stream_queue.js +4 -2
  28. package/dist/storage/index.d.ts +9 -4
  29. package/dist/storage/index.js +38 -3
  30. package/dist/storage/redis/queue.d.ts +39 -0
  31. package/dist/storage/redis/queue.js +130 -0
  32. package/dist/storage/sqlite/DB.d.ts +3 -0
  33. package/dist/storage/sqlite/DB.js +14 -0
  34. package/dist/storage/sqlite/checkpoint.d.ts +18 -0
  35. package/dist/storage/sqlite/checkpoint.js +374 -0
  36. package/dist/storage/sqlite/threads.d.ts +43 -0
  37. package/dist/storage/sqlite/threads.js +266 -0
  38. package/dist/storage/sqlite/type.d.ts +15 -0
  39. package/dist/storage/sqlite/type.js +1 -0
  40. package/dist/utils/createEntrypointGraph.d.ts +14 -0
  41. package/dist/utils/createEntrypointGraph.js +11 -0
  42. package/dist/utils/getGraph.js +3 -3
  43. package/examples/nextjs/README.md +36 -0
  44. package/examples/nextjs/app/api/langgraph/[...path]/route.ts +10 -0
  45. package/examples/nextjs/app/favicon.ico +0 -0
  46. package/examples/nextjs/app/globals.css +26 -0
  47. package/examples/nextjs/app/layout.tsx +34 -0
  48. package/examples/nextjs/app/page.tsx +211 -0
  49. package/examples/nextjs/next.config.ts +26 -0
  50. package/examples/nextjs/package.json +24 -0
  51. package/examples/nextjs/postcss.config.mjs +5 -0
  52. package/examples/nextjs/tsconfig.json +27 -0
  53. package/package.json +9 -4
  54. package/packages/agent-graph/demo.json +35 -0
  55. package/packages/agent-graph/package.json +18 -0
  56. package/packages/agent-graph/src/index.ts +47 -0
  57. package/packages/agent-graph/src/tools/tavily.ts +9 -0
  58. package/packages/agent-graph/src/tools.ts +38 -0
  59. package/packages/agent-graph/src/types.ts +42 -0
  60. package/pnpm-workspace.yaml +4 -0
  61. package/src/adapter/hono/assistants.ts +16 -33
  62. package/src/adapter/hono/endpoint.ts +1 -2
  63. package/src/adapter/hono/runs.ts +15 -51
  64. package/src/adapter/hono/threads.ts +15 -70
  65. package/src/adapter/nextjs/endpoint.ts +2 -0
  66. package/src/adapter/nextjs/index.ts +2 -0
  67. package/src/adapter/nextjs/router.ts +193 -0
  68. package/src/adapter/{hono → nextjs}/zod.ts +22 -5
  69. package/src/adapter/zod.ts +135 -0
  70. package/src/createEndpoint.ts +12 -5
  71. package/src/e.d.ts +3 -0
  72. package/src/global.ts +11 -6
  73. package/src/graph/stream.ts +20 -10
  74. package/src/index.ts +1 -0
  75. package/src/queue/stream_queue.ts +6 -5
  76. package/src/storage/index.ts +42 -4
  77. package/src/storage/redis/queue.ts +148 -0
  78. package/src/storage/sqlite/DB.ts +16 -0
  79. package/src/storage/sqlite/checkpoint.ts +503 -0
  80. package/src/storage/sqlite/threads.ts +366 -0
  81. package/src/storage/sqlite/type.ts +12 -0
  82. package/src/utils/createEntrypointGraph.ts +20 -0
  83. package/src/utils/getGraph.ts +3 -3
  84. package/test/graph/entrypoint.ts +21 -0
  85. package/test/graph/index.ts +45 -6
  86. package/test/hono.ts +5 -0
  87. package/test/test.ts +0 -10
@@ -0,0 +1,119 @@
1
+ import z from 'zod';
2
+ export const AssistantConfigurable = z
3
+ .object({
4
+ thread_id: z.string().optional(),
5
+ thread_ts: z.string().optional(),
6
+ })
7
+ .catchall(z.unknown());
8
+ export const AssistantConfig = z
9
+ .object({
10
+ tags: z.array(z.string()).optional(),
11
+ recursion_limit: z.number().int().optional(),
12
+ configurable: AssistantConfigurable.optional(),
13
+ })
14
+ .catchall(z.unknown())
15
+ .describe('The configuration of an assistant.');
16
+ export const Assistant = z.object({
17
+ assistant_id: z.string().uuid(),
18
+ graph_id: z.string(),
19
+ config: AssistantConfig,
20
+ created_at: z.string(),
21
+ updated_at: z.string(),
22
+ metadata: z.object({}).catchall(z.any()),
23
+ });
24
+ export const MetadataSchema = z
25
+ .object({
26
+ source: z.union([z.literal('input'), z.literal('loop'), z.literal('update'), z.string()]).optional(),
27
+ step: z.number().optional(),
28
+ writes: z.record(z.unknown()).nullable().optional(),
29
+ parents: z.record(z.string()).optional(),
30
+ })
31
+ .catchall(z.unknown());
32
+ export const SendSchema = z.object({
33
+ node: z.string(),
34
+ input: z.unknown().nullable(),
35
+ });
36
+ export const CommandSchema = z.object({
37
+ update: z
38
+ .union([z.record(z.unknown()), z.array(z.tuple([z.string(), z.unknown()]))])
39
+ .nullable()
40
+ .optional(),
41
+ resume: z.unknown().optional(),
42
+ goto: z.union([SendSchema, z.array(SendSchema), z.string(), z.array(z.string())]).optional(),
43
+ });
44
+ // 公共的查询参数验证 schema
45
+ export const PaginationQuerySchema = z.object({
46
+ limit: z.number().int().optional(),
47
+ offset: z.number().int().optional(),
48
+ });
49
+ export const ThreadIdParamSchema = z.object({
50
+ thread_id: z.string().uuid(),
51
+ });
52
+ export const RunIdParamSchema = z.object({
53
+ thread_id: z.string().uuid(),
54
+ run_id: z.string().uuid(),
55
+ });
56
+ // Assistants 相关的 schema
57
+ export const AssistantsSearchSchema = z.object({
58
+ graph_id: z.string().optional(),
59
+ metadata: MetadataSchema.optional(),
60
+ limit: z.number().int().optional(),
61
+ offset: z.number().int().optional(),
62
+ });
63
+ export const AssistantGraphQuerySchema = z.object({
64
+ xray: z.string().optional(),
65
+ });
66
+ // Runs 相关的 schema
67
+ export const RunStreamPayloadSchema = z
68
+ .object({
69
+ assistant_id: z.union([z.string().uuid(), z.string()]),
70
+ checkpoint_id: z.string().optional(),
71
+ input: z.any().optional(),
72
+ command: CommandSchema.optional(),
73
+ metadata: MetadataSchema.optional(),
74
+ config: AssistantConfig.optional(),
75
+ webhook: z.string().optional(),
76
+ interrupt_before: z.union([z.literal('*'), z.array(z.string())]).optional(),
77
+ interrupt_after: z.union([z.literal('*'), z.array(z.string())]).optional(),
78
+ on_disconnect: z.enum(['cancel', 'continue']).optional().default('continue'),
79
+ multitask_strategy: z.enum(['reject', 'rollback', 'interrupt', 'enqueue']).optional(),
80
+ stream_mode: z
81
+ .array(z.enum(['values', 'messages', 'messages-tuple', 'updates', 'events', 'debug', 'custom']))
82
+ .optional(),
83
+ stream_subgraphs: z.boolean().optional(),
84
+ stream_resumable: z.boolean().optional(),
85
+ after_seconds: z.number().optional(),
86
+ if_not_exists: z.enum(['create', 'reject']).optional(),
87
+ on_completion: z.enum(['complete', 'continue']).optional(),
88
+ feedback_keys: z.array(z.string()).optional(),
89
+ langsmith_tracer: z.unknown().optional(),
90
+ })
91
+ .describe('Payload for creating a stateful run.');
92
+ export const RunListQuerySchema = z.object({
93
+ limit: z.coerce.number().int().optional(),
94
+ offset: z.coerce.number().int().optional(),
95
+ status: z.enum(['pending', 'running', 'error', 'success', 'timeout', 'interrupted']).optional(),
96
+ });
97
+ export const RunCancelQuerySchema = z.object({
98
+ wait: z.coerce.boolean().optional().default(false),
99
+ action: z.enum(['interrupt', 'rollback']).optional().default('interrupt'),
100
+ });
101
+ // Threads 相关的 schema
102
+ export const ThreadCreatePayloadSchema = z
103
+ .object({
104
+ thread_id: z.string().uuid().describe('The ID of the thread. If not provided, an ID is generated.').optional(),
105
+ metadata: MetadataSchema.optional(),
106
+ if_exists: z.union([z.literal('raise'), z.literal('do_nothing')]).optional(),
107
+ })
108
+ .describe('Payload for creating a thread.');
109
+ export const ThreadSearchPayloadSchema = z
110
+ .object({
111
+ metadata: z.record(z.unknown()).describe('Metadata to search for.').optional(),
112
+ status: z.enum(['idle', 'busy', 'interrupted', 'error']).describe('Filter by thread status.').optional(),
113
+ values: z.record(z.unknown()).describe('Filter by thread values.').optional(),
114
+ limit: z.number().int().gte(1).lte(1000).describe('Maximum number to return.').optional(),
115
+ offset: z.number().int().gte(0).describe('Offset to start from.').optional(),
116
+ sort_by: z.enum(['thread_id', 'status', 'created_at', 'updated_at']).describe('Sort by field.').optional(),
117
+ sort_order: z.enum(['asc', 'desc']).describe('Sort order.').optional(),
118
+ })
119
+ .describe('Payload for listing threads.');
@@ -1,5 +1,4 @@
1
- import { BaseThreadsManager } from './threads/index.js';
2
1
  import { ILangGraphClient } from './types.js';
3
2
  export { registerGraph } from './utils/getGraph.js';
4
3
  export declare const AssistantEndpoint: ILangGraphClient['assistants'];
5
- export declare const createEndpoint: (threads: BaseThreadsManager) => ILangGraphClient;
4
+ export declare const createEndpoint: () => ILangGraphClient;
@@ -1,6 +1,6 @@
1
1
  import { streamState } from './graph/stream.js';
2
2
  import { getGraph, GRAPHS } from './utils/getGraph.js';
3
- import { globalMessageQueue } from './global.js';
3
+ import { LangGraphGlobal } from './global.js';
4
4
  export { registerGraph } from './utils/getGraph.js';
5
5
  export const AssistantEndpoint = {
6
6
  async search(query) {
@@ -41,7 +41,8 @@ export const AssistantEndpoint = {
41
41
  return drawable.toJSON();
42
42
  },
43
43
  };
44
- export const createEndpoint = (threads) => {
44
+ export const createEndpoint = () => {
45
+ const threads = LangGraphGlobal.globalThreadsManager;
45
46
  return {
46
47
  assistants: AssistantEndpoint,
47
48
  threads,
@@ -50,7 +51,7 @@ export const createEndpoint = (threads) => {
50
51
  return threads.listRuns(threadId, options);
51
52
  },
52
53
  async cancel(threadId, runId, wait, action) {
53
- return globalMessageQueue.cancelQueue(runId);
54
+ return LangGraphGlobal.globalMessageQueue.cancelQueue(runId);
54
55
  },
55
56
  async *stream(threadId, assistantId, payload) {
56
57
  if (!payload.config) {
package/dist/global.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- /** 全局队列管理器 */
2
- export declare const globalMessageQueue: import("./queue/stream_queue.js").StreamQueueManager<import("./storage/memory/queue.js").MemoryStreamQueue>;
3
- /** 全局 Checkpointer */
4
- export declare const globalCheckPointer: import("@langchain/langgraph-checkpoint").MemorySaver;
1
+ import type { SqliteSaver } from './storage/sqlite/checkpoint.js';
2
+ export declare class LangGraphGlobal {
3
+ static globalMessageQueue: import("./queue/stream_queue.js").StreamQueueManager<import("./queue/stream_queue.js").BaseStreamQueueInterface>;
4
+ static globalCheckPointer: SqliteSaver | import("@langchain/langgraph-checkpoint-redis").RedisSaver | import("@langchain/langgraph-checkpoint-redis/shallow").ShallowRedisSaver | import("@langchain/langgraph-checkpoint").MemorySaver;
5
+ static globalThreadsManager: import("./storage/sqlite/threads.js").SQLiteThreadsManager<unknown> | import("./storage/memory/threads.js").MemoryThreadsManager<unknown>;
6
+ }
package/dist/global.js CHANGED
@@ -1,5 +1,10 @@
1
- import { createCheckPointer, createMessageQueue } from './storage/index.js';
2
- /** 全局队列管理器 */
3
- export const globalMessageQueue = createMessageQueue();
4
- /** 全局 Checkpointer */
5
- export const globalCheckPointer = createCheckPointer();
1
+ import { createCheckPointer, createMessageQueue, createThreadManager } from './storage/index.js';
2
+ const [globalMessageQueue, globalCheckPointer] = await Promise.all([createMessageQueue(), createCheckPointer()]);
3
+ const globalThreadsManager = await createThreadManager({
4
+ checkpointer: globalCheckPointer,
5
+ });
6
+ export class LangGraphGlobal {
7
+ static globalMessageQueue = globalMessageQueue;
8
+ static globalCheckPointer = globalCheckPointer;
9
+ static globalThreadsManager = globalThreadsManager;
10
+ }
@@ -23,7 +23,7 @@ export declare function createStreamFromQueue(queueId: string): AsyncGenerator<{
23
23
  event: string;
24
24
  data: unknown;
25
25
  }>;
26
- export declare const serialiseAsDict: (obj: unknown) => string;
26
+ export declare const serialiseAsDict: (obj: unknown, indent?: number) => string;
27
27
  /**
28
28
  * 兼容性函数:保持原有 API,同时使用队列模式
29
29
  * @param run 运行配置
@@ -1,6 +1,6 @@
1
1
  import { isBaseMessage } from '@langchain/core/messages';
2
2
  import { getLangGraphCommand } from '../utils/getLangGraphCommand.js';
3
- import { globalMessageQueue } from '../global.js';
3
+ import { LangGraphGlobal } from '../global.js';
4
4
  import { EventMessage, StreamErrorEventMessage, StreamEndEventMessage } from '../queue/event_message.js';
5
5
  export async function streamStateWithQueue(threads, run, queue, payload, options) {
6
6
  const kwargs = payload;
@@ -22,7 +22,11 @@ export async function streamStateWithQueue(threads, run, queue, payload, options
22
22
  if (userStreamMode.includes('messages')) {
23
23
  libStreamMode.add('values');
24
24
  }
25
- await queue.push(new EventMessage('metadata', { run_id: run.run_id, attempt: options.attempt, graph_id: graphId }));
25
+ await queue.push(new EventMessage('metadata', {
26
+ run_id: run.run_id,
27
+ attempt: options.attempt,
28
+ graph_id: graphId,
29
+ }));
26
30
  const metadata = {
27
31
  ...payload.config?.metadata,
28
32
  run_attempt: options.attempt,
@@ -65,7 +69,9 @@ export async function streamStateWithQueue(threads, run, queue, payload, options
65
69
  }
66
70
  }
67
71
  if (mode === 'values') {
68
- await threads.set(run.thread_id, { values: JSON.parse(serialiseAsDict(data)) });
72
+ await threads.set(run.thread_id, {
73
+ values: data ? JSON.parse(serialiseAsDict(data)) : '',
74
+ });
69
75
  }
70
76
  }
71
77
  else if (userStreamMode.includes('events')) {
@@ -105,7 +111,9 @@ export async function streamStateWithQueue(threads, run, queue, payload, options
105
111
  continue;
106
112
  if (messages[message.id] == null) {
107
113
  messages[message.id] = message;
108
- await queue.push(new EventMessage('messages/metadata', { [message.id]: { metadata: event.metadata } }));
114
+ await queue.push(new EventMessage('messages/metadata', {
115
+ [message.id]: { metadata: event.metadata },
116
+ }));
109
117
  }
110
118
  else {
111
119
  messages[message.id] = messages[message.id].concat(message);
@@ -127,10 +135,10 @@ export async function streamStateWithQueue(threads, run, queue, payload, options
127
135
  * @returns 数据流生成器
128
136
  */
129
137
  export async function* createStreamFromQueue(queueId) {
130
- const queue = globalMessageQueue.getQueue(queueId);
138
+ const queue = LangGraphGlobal.globalMessageQueue.getQueue(queueId);
131
139
  return queue.onDataReceive();
132
140
  }
133
- export const serialiseAsDict = (obj) => {
141
+ export const serialiseAsDict = (obj, indent = 2) => {
134
142
  return JSON.stringify(obj, function (key, value) {
135
143
  const rawValue = this[key];
136
144
  if (rawValue != null &&
@@ -142,7 +150,7 @@ export const serialiseAsDict = (obj) => {
142
150
  return { ...data, type };
143
151
  }
144
152
  return value;
145
- }, 2);
153
+ }, indent);
146
154
  };
147
155
  /**
148
156
  * 兼容性函数:保持原有 API,同时使用队列模式
@@ -159,12 +167,12 @@ export async function* streamState(threads, run, payload, options) {
159
167
  // 启动队列推送任务(在后台异步执行)
160
168
  await threads.set(threadId, { status: 'busy' });
161
169
  await threads.updateRun(run.run_id, { status: 'running' });
162
- const queue = globalMessageQueue.createQueue(queueId);
170
+ const queue = LangGraphGlobal.globalMessageQueue.createQueue(queueId);
163
171
  const state = queue.onDataReceive();
164
172
  streamStateWithQueue(threads, run, queue, payload, options).catch((error) => {
165
173
  console.error('Queue task error:', error);
166
174
  // 如果生产者出错,向队列推送错误信号
167
- globalMessageQueue.pushToQueue(queueId, new StreamErrorEventMessage(error));
175
+ LangGraphGlobal.globalMessageQueue.pushToQueue(queueId, new StreamErrorEventMessage(error));
168
176
  // TODO 不知道这里需不需要错误处理
169
177
  });
170
178
  for await (const data of state) {
@@ -182,6 +190,6 @@ export async function* streamState(threads, run, payload, options) {
182
190
  finally {
183
191
  // 在完成后清理队列
184
192
  await threads.set(threadId, { status: 'idle' });
185
- globalMessageQueue.removeQueue(queueId);
193
+ LangGraphGlobal.globalMessageQueue.removeQueue(queueId);
186
194
  }
187
195
  }
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export * from './createEndpoint';
2
2
  export * from './types';
3
3
  export * from './global';
4
4
  export * from './threads/index';
5
+ export * from './utils/createEntrypointGraph';
package/dist/index.js CHANGED
@@ -2,3 +2,4 @@ export * from './createEndpoint';
2
2
  export * from './types';
3
3
  export * from './global';
4
4
  export * from './threads/index';
5
+ export * from './utils/createEntrypointGraph';
@@ -16,6 +16,7 @@ interface StreamQueueEvents<T extends EventMessage> {
16
16
  * Base stream queue class
17
17
  */
18
18
  export declare class BaseStreamQueue extends EventEmitter<StreamQueueEvents<EventMessage>> {
19
+ readonly id: string;
19
20
  readonly compressMessages: boolean;
20
21
  /** 序列化器实例 / Serializer instance */
21
22
  serializer: JsonPlusSerializer;
@@ -24,7 +25,7 @@ export declare class BaseStreamQueue extends EventEmitter<StreamQueueEvents<Even
24
25
  * Constructor
25
26
  * @param compressMessages 是否压缩消息 / Whether to compress messages
26
27
  */
27
- constructor(compressMessages?: boolean);
28
+ constructor(id: string, compressMessages?: boolean);
28
29
  /**
29
30
  * 编码数据为 Uint8Array
30
31
  * Encode data to Uint8Array
@@ -45,6 +46,7 @@ export declare class BaseStreamQueue extends EventEmitter<StreamQueueEvents<Even
45
46
  * Base stream queue interface
46
47
  */
47
48
  export interface BaseStreamQueueInterface {
49
+ id: string;
48
50
  /** 是否压缩消息 / Whether to compress messages */
49
51
  compressMessages: boolean;
50
52
  /**
@@ -63,7 +65,7 @@ export interface BaseStreamQueueInterface {
63
65
  * @param listener 数据变化监听器 / Data change listener
64
66
  * @returns 取消监听函数 / Unsubscribe function
65
67
  */
66
- onDataChange(listener: (data: EventMessage) => void): () => void;
68
+ onDataReceive(): AsyncGenerator<EventMessage, void, unknown>;
67
69
  /** 取消信号控制器 / Cancel signal controller */
68
70
  cancelSignal: AbortController;
69
71
  /** 取消操作 / Cancel operation */
@@ -86,7 +88,7 @@ export declare class StreamQueueManager<Q extends BaseStreamQueueInterface> {
86
88
  * @param queueConstructor 队列构造函数 / Queue constructor
87
89
  * @param options 配置选项 / Configuration options
88
90
  */
89
- constructor(queueConstructor: new (compressMessages: boolean) => Q, options?: {
91
+ constructor(queueConstructor: new (id: string) => Q, options?: {
90
92
  /** 默认是否压缩消息 / Default compress messages setting */
91
93
  defaultCompressMessages?: boolean;
92
94
  });
@@ -5,6 +5,7 @@ import { JsonPlusSerializer } from './JsonPlusSerializer.js';
5
5
  * Base stream queue class
6
6
  */
7
7
  export class BaseStreamQueue extends EventEmitter {
8
+ id;
8
9
  compressMessages;
9
10
  /** 序列化器实例 / Serializer instance */
10
11
  serializer = new JsonPlusSerializer();
@@ -13,8 +14,9 @@ export class BaseStreamQueue extends EventEmitter {
13
14
  * Constructor
14
15
  * @param compressMessages 是否压缩消息 / Whether to compress messages
15
16
  */
16
- constructor(compressMessages = true) {
17
+ constructor(id, compressMessages = true) {
17
18
  super();
19
+ this.id = id;
18
20
  this.compressMessages = compressMessages;
19
21
  }
20
22
  /**
@@ -68,7 +70,7 @@ export class StreamQueueManager {
68
70
  */
69
71
  createQueue(id, compressMessages) {
70
72
  const compress = compressMessages ?? this.defaultCompressMessages;
71
- this.queues.set(id, new this.queueConstructor(compress));
73
+ this.queues.set(id, new this.queueConstructor(id));
72
74
  return this.queues.get(id);
73
75
  }
74
76
  /**
@@ -1,5 +1,10 @@
1
- import { StreamQueueManager } from '../queue/stream_queue';
1
+ import { BaseStreamQueueInterface, StreamQueueManager } from '../queue/stream_queue';
2
2
  import { MemorySaver } from './memory/checkpoint';
3
- import { MemoryStreamQueue } from './memory/queue';
4
- export declare const createCheckPointer: () => MemorySaver;
5
- export declare const createMessageQueue: () => StreamQueueManager<MemoryStreamQueue>;
3
+ import { MemoryThreadsManager } from './memory/threads';
4
+ import type { SqliteSaver as SqliteSaverType } from './sqlite/checkpoint';
5
+ import { SQLiteThreadsManager } from './sqlite/threads';
6
+ export declare const createCheckPointer: () => Promise<SqliteSaverType | import("@langchain/langgraph-checkpoint-redis").RedisSaver | import("@langchain/langgraph-checkpoint-redis/shallow").ShallowRedisSaver | MemorySaver>;
7
+ export declare const createMessageQueue: () => Promise<StreamQueueManager<BaseStreamQueueInterface>>;
8
+ export declare const createThreadManager: (config: {
9
+ checkpointer?: SqliteSaverType;
10
+ }) => SQLiteThreadsManager<unknown> | MemoryThreadsManager<unknown>;
@@ -1,11 +1,46 @@
1
1
  import { StreamQueueManager } from '../queue/stream_queue';
2
2
  import { MemorySaver } from './memory/checkpoint';
3
3
  import { MemoryStreamQueue } from './memory/queue';
4
+ import { MemoryThreadsManager } from './memory/threads';
5
+ import { SQLiteThreadsManager } from './sqlite/threads';
4
6
  // 所有的适配实现,都请写到这里,通过环境变量进行判断使用哪种方式进行适配
5
- export const createCheckPointer = () => {
7
+ export const createCheckPointer = async () => {
8
+ if ((process.env.REDIS_URL && process.env.CHECKPOINT_TYPE === 'redis') ||
9
+ process.env.CHECKPOINT_TYPE === 'shallow/redis') {
10
+ if (process.env.CHECKPOINT_TYPE === 'redis') {
11
+ const { RedisSaver } = await import('@langchain/langgraph-checkpoint-redis');
12
+ return await RedisSaver.fromUrl(process.env.REDIS_URL, {
13
+ defaultTTL: 60, // TTL in minutes
14
+ refreshOnRead: true,
15
+ });
16
+ }
17
+ if (process.env.CHECKPOINT_TYPE === 'shallow/redis') {
18
+ const { ShallowRedisSaver } = await import('@langchain/langgraph-checkpoint-redis/shallow');
19
+ return await ShallowRedisSaver.fromUrl(process.env.REDIS_URL);
20
+ }
21
+ }
22
+ if (process.env.SQLITE_DATABASE_URI) {
23
+ const { SqliteSaver } = await import('./sqlite/checkpoint');
24
+ const db = SqliteSaver.fromConnString(process.env.SQLITE_DATABASE_URI);
25
+ return db;
26
+ }
6
27
  return new MemorySaver();
7
28
  };
8
- export const createMessageQueue = () => {
9
- const q = MemoryStreamQueue;
29
+ export const createMessageQueue = async () => {
30
+ let q;
31
+ if (process.env.REDIS_URL) {
32
+ console.log('Using redis as stream queue');
33
+ const { RedisStreamQueue } = await import('./redis/queue');
34
+ q = RedisStreamQueue;
35
+ }
36
+ else {
37
+ q = MemoryStreamQueue;
38
+ }
10
39
  return new StreamQueueManager(q);
11
40
  };
41
+ export const createThreadManager = (config) => {
42
+ if (process.env.SQLITE_DATABASE_URI && config.checkpointer) {
43
+ return new SQLiteThreadsManager(config.checkpointer);
44
+ }
45
+ return new MemoryThreadsManager();
46
+ };
@@ -0,0 +1,39 @@
1
+ import { EventMessage } from '../../queue/event_message.js';
2
+ import { BaseStreamQueue } from '../../queue/stream_queue.js';
3
+ import { BaseStreamQueueInterface } from '../../queue/stream_queue.js';
4
+ import { RedisClientType } from 'redis';
5
+ /**
6
+ * Redis 实现的消息队列,用于存储消息
7
+ */
8
+ export declare class RedisStreamQueue extends BaseStreamQueue implements BaseStreamQueueInterface {
9
+ readonly id: string;
10
+ static redis: RedisClientType;
11
+ static subscriberRedis: RedisClientType;
12
+ private redis;
13
+ private subscriberRedis;
14
+ private queueKey;
15
+ private channelKey;
16
+ private isConnected;
17
+ cancelSignal: AbortController;
18
+ constructor(id?: string);
19
+ /**
20
+ * 推送消息到 Redis 队列
21
+ */
22
+ push(item: EventMessage): Promise<void>;
23
+ /**
24
+ * 异步生成器:支持 for await...of 方式消费队列数据
25
+ */
26
+ onDataReceive(): AsyncGenerator<EventMessage, void, unknown>;
27
+ /**
28
+ * 获取队列中的所有数据
29
+ */
30
+ getAll(): Promise<EventMessage[]>;
31
+ /**
32
+ * 清空队列
33
+ */
34
+ clear(): void;
35
+ /**
36
+ * 取消操作
37
+ */
38
+ cancel(): void;
39
+ }
@@ -0,0 +1,130 @@
1
+ import { CancelEventMessage } from '../../queue/event_message.js';
2
+ import { BaseStreamQueue } from '../../queue/stream_queue.js';
3
+ import { createClient } from 'redis';
4
+ /**
5
+ * Redis 实现的消息队列,用于存储消息
6
+ */
7
+ export class RedisStreamQueue extends BaseStreamQueue {
8
+ id;
9
+ static redis = createClient({ url: process.env.REDIS_URL });
10
+ static subscriberRedis = createClient({ url: process.env.REDIS_URL });
11
+ redis;
12
+ subscriberRedis;
13
+ queueKey;
14
+ channelKey;
15
+ isConnected = false;
16
+ cancelSignal;
17
+ constructor(id = 'default') {
18
+ super(id, true);
19
+ this.id = id;
20
+ this.queueKey = `queue:${this.id}`;
21
+ this.channelKey = `channel:${this.id}`;
22
+ this.redis = RedisStreamQueue.redis;
23
+ this.subscriberRedis = RedisStreamQueue.subscriberRedis;
24
+ this.cancelSignal = new AbortController();
25
+ // 连接 Redis 客户端
26
+ this.redis.connect();
27
+ this.subscriberRedis.connect();
28
+ this.isConnected = true;
29
+ }
30
+ /**
31
+ * 推送消息到 Redis 队列
32
+ */
33
+ async push(item) {
34
+ const data = await this.encodeData(item);
35
+ const serializedData = Buffer.from(data);
36
+ // 推送到队列
37
+ await this.redis.lPush(this.queueKey, serializedData);
38
+ // 设置队列 TTL 为 300 秒
39
+ await this.redis.expire(this.queueKey, 300);
40
+ // 发布到频道通知有新数据
41
+ await this.redis.publish(this.channelKey, serializedData);
42
+ this.emit('dataChange', data);
43
+ }
44
+ /**
45
+ * 异步生成器:支持 for await...of 方式消费队列数据
46
+ */
47
+ async *onDataReceive() {
48
+ let queue = [];
49
+ let pendingResolve = null;
50
+ let isStreamEnded = false;
51
+ const handleMessage = async (message) => {
52
+ const data = (await this.decodeData(message));
53
+ queue.push(data);
54
+ // 检查是否为流结束或错误信号
55
+ if (data.event === '__stream_end__' ||
56
+ data.event === '__stream_error__' ||
57
+ data.event === '__stream_cancel__') {
58
+ setTimeout(() => {
59
+ isStreamEnded = true;
60
+ if (pendingResolve) {
61
+ pendingResolve();
62
+ pendingResolve = null;
63
+ }
64
+ }, 300);
65
+ if (data.event === '__stream_cancel__') {
66
+ this.cancel();
67
+ }
68
+ }
69
+ if (pendingResolve) {
70
+ pendingResolve();
71
+ pendingResolve = null;
72
+ }
73
+ };
74
+ // 订阅 Redis 频道
75
+ await this.subscriberRedis.subscribe(this.channelKey, (message) => {
76
+ handleMessage(message);
77
+ });
78
+ try {
79
+ while (!isStreamEnded) {
80
+ if (queue.length > 0) {
81
+ for (const item of queue) {
82
+ yield item;
83
+ }
84
+ queue = [];
85
+ }
86
+ else {
87
+ await new Promise((resolve) => {
88
+ pendingResolve = resolve;
89
+ });
90
+ }
91
+ }
92
+ }
93
+ finally {
94
+ await this.subscriberRedis.unsubscribe(this.channelKey);
95
+ }
96
+ }
97
+ /**
98
+ * 获取队列中的所有数据
99
+ */
100
+ async getAll() {
101
+ const data = await this.redis.lRange(this.queueKey, 0, -1);
102
+ if (!data || data.length === 0) {
103
+ return [];
104
+ }
105
+ if (this.compressMessages) {
106
+ return (await Promise.all(data.map(async (item) => {
107
+ const parsed = JSON.parse(item);
108
+ return (await this.decodeData(parsed));
109
+ })));
110
+ }
111
+ else {
112
+ return data.map((item) => JSON.parse(item));
113
+ }
114
+ }
115
+ /**
116
+ * 清空队列
117
+ */
118
+ clear() {
119
+ if (this.isConnected) {
120
+ this.redis.del(this.queueKey);
121
+ }
122
+ }
123
+ /**
124
+ * 取消操作
125
+ */
126
+ cancel() {
127
+ this.push(new CancelEventMessage());
128
+ this.cancelSignal.abort('user cancel this run');
129
+ }
130
+ }
@@ -0,0 +1,3 @@
1
+ import { DatabaseType } from './type';
2
+ declare let Database: new (uri: string) => DatabaseType;
3
+ export { Database };
@@ -0,0 +1,14 @@
1
+ let Database;
2
+ /** @ts-ignore */
3
+ if (globalThis.Bun) {
4
+ console.log('Using Bun Sqlite, pid:', process.pid);
5
+ const BunSqlite = await import('bun:sqlite');
6
+ /** @ts-ignore */
7
+ Database = BunSqlite.default;
8
+ }
9
+ else {
10
+ /** @ts-ignore */
11
+ const CommonSqlite = await import('better-sqlite3');
12
+ Database = CommonSqlite.default;
13
+ }
14
+ export { Database };
@@ -0,0 +1,18 @@
1
+ import { DatabaseType } from './type.js';
2
+ import type { RunnableConfig } from '@langchain/core/runnables';
3
+ import { BaseCheckpointSaver, type Checkpoint, type CheckpointListOptions, type CheckpointTuple, type SerializerProtocol, type PendingWrite, type CheckpointMetadata } from '@langchain/langgraph-checkpoint';
4
+ export declare class SqliteSaver extends BaseCheckpointSaver {
5
+ db: DatabaseType;
6
+ protected isSetup: boolean;
7
+ protected withoutCheckpoint: any;
8
+ protected withCheckpoint: any;
9
+ constructor(db: DatabaseType, serde?: SerializerProtocol);
10
+ static fromConnString(connStringOrLocalPath: string): SqliteSaver;
11
+ protected setup(): void;
12
+ getTuple(config: RunnableConfig): Promise<CheckpointTuple | undefined>;
13
+ list(config: RunnableConfig, options?: CheckpointListOptions): AsyncGenerator<CheckpointTuple>;
14
+ put(config: RunnableConfig, checkpoint: Checkpoint, metadata: CheckpointMetadata): Promise<RunnableConfig>;
15
+ putWrites(config: RunnableConfig, writes: PendingWrite[], taskId: string): Promise<void>;
16
+ deleteThread(threadId: string): Promise<void>;
17
+ protected migratePendingSends(checkpoint: Checkpoint, threadId: string, parentCheckpointId: string): Promise<void>;
18
+ }