@langgraph-js/pure-graph 1.3.0 → 1.4.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 (71) hide show
  1. package/README.md +22 -14
  2. package/dist/adapter/nextjs/router.js +6 -6
  3. package/dist/global.d.ts +3 -2
  4. package/dist/storage/index.d.ts +4 -3
  5. package/dist/storage/index.js +13 -1
  6. package/dist/storage/pg/checkpoint.d.ts +2 -0
  7. package/dist/storage/pg/checkpoint.js +9 -0
  8. package/dist/storage/pg/threads.d.ts +43 -0
  9. package/dist/storage/pg/threads.js +304 -0
  10. package/dist/tsconfig.tsbuildinfo +1 -0
  11. package/package.json +38 -5
  12. package/.prettierrc +0 -11
  13. package/bun.lock +0 -209
  14. package/dist/adapter/hono/zod.d.ts +0 -203
  15. package/dist/adapter/hono/zod.js +0 -43
  16. package/dist/adapter/nextjs/zod.d.ts +0 -203
  17. package/dist/adapter/nextjs/zod.js +0 -60
  18. package/examples/nextjs/README.md +0 -36
  19. package/examples/nextjs/app/api/langgraph/[...path]/route.ts +0 -10
  20. package/examples/nextjs/app/favicon.ico +0 -0
  21. package/examples/nextjs/app/globals.css +0 -26
  22. package/examples/nextjs/app/layout.tsx +0 -34
  23. package/examples/nextjs/app/page.tsx +0 -211
  24. package/examples/nextjs/next.config.ts +0 -26
  25. package/examples/nextjs/package.json +0 -24
  26. package/examples/nextjs/postcss.config.mjs +0 -5
  27. package/examples/nextjs/tsconfig.json +0 -27
  28. package/packages/agent-graph/demo.json +0 -35
  29. package/packages/agent-graph/package.json +0 -18
  30. package/packages/agent-graph/src/index.ts +0 -47
  31. package/packages/agent-graph/src/tools/tavily.ts +0 -9
  32. package/packages/agent-graph/src/tools.ts +0 -38
  33. package/packages/agent-graph/src/types.ts +0 -42
  34. package/pnpm-workspace.yaml +0 -4
  35. package/src/adapter/hono/assistants.ts +0 -24
  36. package/src/adapter/hono/endpoint.ts +0 -3
  37. package/src/adapter/hono/index.ts +0 -14
  38. package/src/adapter/hono/runs.ts +0 -92
  39. package/src/adapter/hono/threads.ts +0 -37
  40. package/src/adapter/nextjs/endpoint.ts +0 -2
  41. package/src/adapter/nextjs/index.ts +0 -2
  42. package/src/adapter/nextjs/router.ts +0 -206
  43. package/src/adapter/nextjs/zod.ts +0 -66
  44. package/src/adapter/zod.ts +0 -144
  45. package/src/createEndpoint.ts +0 -116
  46. package/src/e.d.ts +0 -3
  47. package/src/global.ts +0 -11
  48. package/src/graph/stream.ts +0 -263
  49. package/src/graph/stringify.ts +0 -219
  50. package/src/index.ts +0 -6
  51. package/src/queue/JsonPlusSerializer.ts +0 -143
  52. package/src/queue/event_message.ts +0 -30
  53. package/src/queue/stream_queue.ts +0 -237
  54. package/src/storage/index.ts +0 -52
  55. package/src/storage/memory/checkpoint.ts +0 -2
  56. package/src/storage/memory/queue.ts +0 -91
  57. package/src/storage/memory/threads.ts +0 -183
  58. package/src/storage/redis/queue.ts +0 -148
  59. package/src/storage/sqlite/DB.ts +0 -16
  60. package/src/storage/sqlite/checkpoint.ts +0 -502
  61. package/src/storage/sqlite/threads.ts +0 -405
  62. package/src/storage/sqlite/type.ts +0 -12
  63. package/src/threads/index.ts +0 -37
  64. package/src/types.ts +0 -118
  65. package/src/utils/createEntrypointGraph.ts +0 -20
  66. package/src/utils/getGraph.ts +0 -44
  67. package/src/utils/getLangGraphCommand.ts +0 -21
  68. package/test/graph/entrypoint.ts +0 -21
  69. package/test/graph/index.ts +0 -60
  70. package/test/hono.ts +0 -15
  71. package/tsconfig.json +0 -20
@@ -1,91 +0,0 @@
1
- import { CancelEventMessage, EventMessage } from '../../queue/event_message.js';
2
- import { BaseStreamQueue } from '../../queue/stream_queue.js';
3
- import { BaseStreamQueueInterface } from '../../queue/stream_queue.js';
4
- /** 内存实现的消息队列,用于存储消息 */
5
- export class MemoryStreamQueue extends BaseStreamQueue implements BaseStreamQueueInterface {
6
- private data: EventMessage[] = [];
7
- async push(item: EventMessage): Promise<void> {
8
- const data = this.compressMessages ? ((await this.encodeData(item)) as unknown as EventMessage) : item;
9
- this.data.push(data);
10
- this.emit('dataChange', data);
11
- }
12
-
13
- onDataChange(listener: (data: EventMessage) => void): () => void {
14
- this.on('dataChange', async (item) => {
15
- listener(this.compressMessages ? ((await this.decodeData(item)) as EventMessage) : item);
16
- });
17
- return () => this.off('dataChange', listener);
18
- }
19
-
20
- /**
21
- * 异步生成器:支持 for await...of 方式消费队列数据
22
- */
23
- async *onDataReceive(): AsyncGenerator<EventMessage, void, unknown> {
24
- let queue: EventMessage[] = [];
25
- let pendingResolve: (() => void) | null = null;
26
- let isStreamEnded = false;
27
- const handleData = async (item: EventMessage) => {
28
- const data = this.compressMessages ? ((await this.decodeData(item as any)) as EventMessage) : item;
29
- queue.push(data);
30
- // 检查是否为流结束或错误信号
31
- if (
32
- data.event === '__stream_end__' ||
33
- data.event === '__stream_error__' ||
34
- data.event === '__stream_cancel__'
35
- ) {
36
- setTimeout(() => {
37
- isStreamEnded = true;
38
- if (pendingResolve) {
39
- pendingResolve();
40
- pendingResolve = null;
41
- }
42
- }, 300);
43
-
44
- if (data.event === '__stream_cancel__') {
45
- this.cancel();
46
- }
47
- }
48
-
49
- if (pendingResolve) {
50
- pendingResolve();
51
- pendingResolve = null;
52
- }
53
- };
54
- // todo 这个框架的事件监听的数据返回顺序有误
55
- this.on('dataChange', handleData as any);
56
-
57
- try {
58
- while (!isStreamEnded) {
59
- if (queue.length > 0) {
60
- for (const item of queue) {
61
- yield item;
62
- }
63
- queue = [];
64
- } else {
65
- await new Promise((resolve) => {
66
- pendingResolve = resolve as () => void;
67
- });
68
- }
69
- }
70
- } finally {
71
- this.off('dataChange', handleData as any);
72
- }
73
- }
74
-
75
- async getAll(): Promise<EventMessage[]> {
76
- return this.compressMessages
77
- ? ((await Promise.all(
78
- this.data.map((i) => this.decodeData(i as unknown as string | Uint8Array)),
79
- )) as unknown as EventMessage[])
80
- : this.data;
81
- }
82
-
83
- clear(): void {
84
- this.data = [];
85
- }
86
- public cancelSignal = new AbortController();
87
- cancel(): void {
88
- this.push(new CancelEventMessage());
89
- this.cancelSignal.abort('user cancel this run');
90
- }
91
- }
@@ -1,183 +0,0 @@
1
- import { BaseThreadsManager } from '../../threads/index.js';
2
- import {
3
- Command,
4
- Config,
5
- Metadata,
6
- OnConflictBehavior,
7
- Run,
8
- RunStatus,
9
- SortOrder,
10
- Thread,
11
- ThreadSortBy,
12
- ThreadStatus,
13
- } from '@langgraph-js/sdk';
14
- import { getGraph } from '../../utils/getGraph.js';
15
- import { serialiseAsDict } from '../../graph/stream.js';
16
-
17
- export class MemoryThreadsManager<ValuesType = unknown> implements BaseThreadsManager<ValuesType> {
18
- private threads: Thread<ValuesType>[] = [];
19
-
20
- async create(payload?: {
21
- metadata?: Metadata;
22
- threadId?: string;
23
- ifExists?: OnConflictBehavior;
24
- graphId?: string;
25
- supersteps?: Array<{ updates: Array<{ values: unknown; command?: Command; asNode: string }> }>;
26
- }): Promise<Thread<ValuesType>> {
27
- const threadId = payload?.threadId || crypto.randomUUID();
28
- if (payload?.ifExists === 'raise' && this.threads.some((t) => t.thread_id === threadId)) {
29
- throw new Error(`Thread with ID ${threadId} already exists.`);
30
- }
31
-
32
- const thread: Thread<ValuesType> = {
33
- thread_id: threadId,
34
- created_at: new Date().toISOString(),
35
- updated_at: new Date().toISOString(),
36
- metadata: payload?.metadata || {},
37
- status: 'idle',
38
- values: null as unknown as ValuesType,
39
- interrupts: {},
40
- };
41
- this.threads.push(thread);
42
- return thread;
43
- }
44
-
45
- async search(query?: {
46
- metadata?: Metadata;
47
- limit?: number;
48
- offset?: number;
49
- status?: ThreadStatus;
50
- sortBy?: ThreadSortBy;
51
- sortOrder?: SortOrder;
52
- }): Promise<Thread<ValuesType>[]> {
53
- let filteredThreads = [...this.threads];
54
- if (query?.status) {
55
- filteredThreads = filteredThreads.filter((t) => t.status === query.status);
56
- }
57
-
58
- if (query?.metadata) {
59
- for (const key in query.metadata) {
60
- if (Object.prototype.hasOwnProperty.call(query.metadata, key)) {
61
- filteredThreads = filteredThreads.filter(
62
- (t) => t.metadata && t.metadata[key] === query.metadata?.[key],
63
- );
64
- }
65
- }
66
- }
67
-
68
- if (query?.sortBy) {
69
- filteredThreads.sort((a, b) => {
70
- let aValue: any;
71
- let bValue: any;
72
-
73
- switch (query.sortBy) {
74
- case 'created_at':
75
- aValue = new Date(a.created_at).getTime();
76
- bValue = new Date(b.created_at).getTime();
77
- break;
78
- case 'updated_at':
79
- aValue = new Date(a.updated_at).getTime();
80
- bValue = new Date(b.updated_at).getTime();
81
- break;
82
- default:
83
- return 0;
84
- }
85
-
86
- if (query.sortOrder === 'desc') {
87
- return bValue - aValue;
88
- } else {
89
- return aValue - bValue;
90
- }
91
- });
92
- }
93
-
94
- const offset = query?.offset || 0;
95
- const limit = query?.limit || filteredThreads.length;
96
-
97
- return filteredThreads.slice(offset, offset + limit);
98
- }
99
-
100
- async get(threadId: string): Promise<Thread<ValuesType>> {
101
- const thread = this.threads.find((t) => t.thread_id === threadId);
102
- if (!thread) {
103
- throw new Error(`Thread with ID ${threadId} not found.`);
104
- }
105
- return thread;
106
- }
107
- async set(threadId: string, thread: Partial<Thread<ValuesType>>): Promise<void> {
108
- const index = this.threads.findIndex((t) => t.thread_id === threadId);
109
- if (index === -1) {
110
- throw new Error(`Thread with ID ${threadId} not found.`);
111
- }
112
- this.threads[index] = { ...this.threads[index], ...thread };
113
- }
114
- async delete(threadId: string): Promise<void> {
115
- const initialLength = this.threads.length;
116
- this.threads = this.threads.filter((t) => t.thread_id !== threadId);
117
- if (this.threads.length === initialLength) {
118
- throw new Error(`Thread with ID ${threadId} not found.`);
119
- }
120
- }
121
- async updateState(threadId: string, thread: Partial<Thread<ValuesType>>): Promise<Pick<Config, 'configurable'>> {
122
- const index = this.threads.findIndex((t) => t.thread_id === threadId) as number;
123
- if (index === -1) {
124
- throw new Error(`Thread with ID ${threadId} not found.`);
125
- }
126
- const targetThread = this.threads[index];
127
- if (targetThread.status === 'busy') {
128
- throw new Error(`Thread with ID ${threadId} is busy, can't update state.`);
129
- }
130
- this.threads[index] = { ...targetThread, values: thread.values as ValuesType };
131
- if (!targetThread.metadata?.graph_id) {
132
- throw new Error(`Thread with ID ${threadId} has no graph_id.`);
133
- }
134
- const graphId = targetThread.metadata?.graph_id as string;
135
- const config = {
136
- configurable: {
137
- thread_id: threadId,
138
- graph_id: graphId,
139
- },
140
- };
141
- const graph = await getGraph(graphId, config);
142
- const nextConfig = await graph.updateState(config, thread.values);
143
- const graphState = await graph.getState(config);
144
- await this.set(threadId, { values: JSON.parse(serialiseAsDict(graphState.values)) as ValuesType });
145
- return nextConfig;
146
- }
147
- runs: Run[] = [];
148
- async createRun(threadId: string, assistantId: string, payload?: { metadata?: Metadata }): Promise<Run> {
149
- const runId = crypto.randomUUID();
150
- const run: Run = {
151
- run_id: runId,
152
- thread_id: threadId,
153
- assistant_id: assistantId,
154
- created_at: new Date().toISOString(),
155
- updated_at: new Date().toISOString(),
156
- status: 'pending',
157
- metadata: payload?.metadata ?? {},
158
- multitask_strategy: 'reject',
159
- };
160
- this.runs.push(run);
161
- return run;
162
- }
163
- async listRuns(
164
- threadId: string,
165
- options?: { limit?: number; offset?: number; status?: RunStatus },
166
- ): Promise<Run[]> {
167
- let filteredRuns = [...this.runs];
168
- if (options?.status) {
169
- filteredRuns = filteredRuns.filter((r) => r.status === options.status);
170
- }
171
- if (options?.limit) {
172
- filteredRuns = filteredRuns.slice(options.offset || 0, (options.offset || 0) + options.limit);
173
- }
174
- return filteredRuns;
175
- }
176
- async updateRun(runId: string, run: Partial<Run>): Promise<void> {
177
- const index = this.runs.findIndex((r) => r.run_id === runId);
178
- if (index === -1) {
179
- throw new Error(`Run with ID ${runId} not found.`);
180
- }
181
- this.runs[index] = { ...this.runs[index], ...run };
182
- }
183
- }
@@ -1,148 +0,0 @@
1
- import { CancelEventMessage, EventMessage } from '../../queue/event_message.js';
2
- import { BaseStreamQueue } from '../../queue/stream_queue.js';
3
- import { BaseStreamQueueInterface } from '../../queue/stream_queue.js';
4
- import { createClient, RedisClientType } from 'redis';
5
-
6
- /**
7
- * Redis 实现的消息队列,用于存储消息
8
- */
9
- export class RedisStreamQueue extends BaseStreamQueue implements BaseStreamQueueInterface {
10
- static redis: RedisClientType = createClient({ url: process.env.REDIS_URL! });
11
- static subscriberRedis: RedisClientType = createClient({ url: process.env.REDIS_URL! });
12
- private redis: RedisClientType;
13
- private subscriberRedis: RedisClientType;
14
- private queueKey: string;
15
- private channelKey: string;
16
- private isConnected = false;
17
- public cancelSignal: AbortController;
18
-
19
- constructor(readonly id: string = 'default') {
20
- super(id, true);
21
- this.queueKey = `queue:${this.id}`;
22
- this.channelKey = `channel:${this.id}`;
23
- this.redis = RedisStreamQueue.redis;
24
- this.subscriberRedis = RedisStreamQueue.subscriberRedis;
25
- this.cancelSignal = new AbortController();
26
-
27
- // 连接 Redis 客户端
28
- this.redis.connect();
29
- this.subscriberRedis.connect();
30
- this.isConnected = true;
31
- }
32
-
33
- /**
34
- * 推送消息到 Redis 队列
35
- */
36
- async push(item: EventMessage): Promise<void> {
37
- const data = await this.encodeData(item);
38
- const serializedData = Buffer.from(data);
39
-
40
- // 推送到队列
41
- await this.redis.lPush(this.queueKey, serializedData);
42
-
43
- // 设置队列 TTL 为 300 秒
44
- await this.redis.expire(this.queueKey, 300);
45
-
46
- // 发布到频道通知有新数据
47
- await this.redis.publish(this.channelKey, serializedData);
48
-
49
- this.emit('dataChange', data);
50
- }
51
-
52
- /**
53
- * 异步生成器:支持 for await...of 方式消费队列数据
54
- */
55
- async *onDataReceive(): AsyncGenerator<EventMessage, void, unknown> {
56
- let queue: EventMessage[] = [];
57
- let pendingResolve: (() => void) | null = null;
58
- let isStreamEnded = false;
59
- const handleMessage = async (message: string) => {
60
- const data = (await this.decodeData(message)) as EventMessage;
61
- queue.push(data);
62
- // 检查是否为流结束或错误信号
63
- if (
64
- data.event === '__stream_end__' ||
65
- data.event === '__stream_error__' ||
66
- data.event === '__stream_cancel__'
67
- ) {
68
- setTimeout(() => {
69
- isStreamEnded = true;
70
- if (pendingResolve) {
71
- pendingResolve();
72
- pendingResolve = null;
73
- }
74
- }, 300);
75
-
76
- if (data.event === '__stream_cancel__') {
77
- this.cancel();
78
- }
79
- }
80
-
81
- if (pendingResolve) {
82
- pendingResolve();
83
- pendingResolve = null;
84
- }
85
- };
86
-
87
- // 订阅 Redis 频道
88
- await this.subscriberRedis.subscribe(this.channelKey, (message) => {
89
- handleMessage(message);
90
- });
91
-
92
- try {
93
- while (!isStreamEnded) {
94
- if (queue.length > 0) {
95
- for (const item of queue) {
96
- yield item;
97
- }
98
- queue = [];
99
- } else {
100
- await new Promise((resolve) => {
101
- pendingResolve = resolve as () => void;
102
- });
103
- }
104
- }
105
- } finally {
106
- await this.subscriberRedis.unsubscribe(this.channelKey);
107
- }
108
- }
109
-
110
- /**
111
- * 获取队列中的所有数据
112
- */
113
- async getAll(): Promise<EventMessage[]> {
114
- const data = await this.redis.lRange(this.queueKey, 0, -1);
115
-
116
- if (!data || data.length === 0) {
117
- return [];
118
- }
119
-
120
- if (this.compressMessages) {
121
- return (await Promise.all(
122
- data.map(async (item: string) => {
123
- const parsed = JSON.parse(item) as EventMessage;
124
- return (await this.decodeData(parsed as any)) as EventMessage;
125
- }),
126
- )) as EventMessage[];
127
- } else {
128
- return data.map((item: string) => JSON.parse(item) as EventMessage);
129
- }
130
- }
131
-
132
- /**
133
- * 清空队列
134
- */
135
- clear(): void {
136
- if (this.isConnected) {
137
- this.redis.del(this.queueKey);
138
- }
139
- }
140
-
141
- /**
142
- * 取消操作
143
- */
144
- cancel(): void {
145
- this.push(new CancelEventMessage());
146
- this.cancelSignal.abort('user cancel this run');
147
- }
148
- }
@@ -1,16 +0,0 @@
1
- import { DatabaseType } from './type';
2
-
3
- let Database: new (uri: string) => DatabaseType;
4
- /** @ts-ignore */
5
- if (globalThis.Bun) {
6
- console.log('Using Bun Sqlite, pid:', process.pid);
7
- const BunSqlite = await import('bun:sqlite');
8
- /** @ts-ignore */
9
- Database = BunSqlite.default;
10
- } else {
11
- /** @ts-ignore */
12
- const CommonSqlite = await import('better-sqlite3');
13
- Database = CommonSqlite.default;
14
- }
15
-
16
- export { Database };