@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
package/README.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Pure Graph
2
2
 
3
- Pure Graph is a library that provides a standard LangGraph endpoint for integrating into various frameworks like NextJS and Hono.js. This document will guide you on how to use Pure Graph in your projects.
3
+ Pure Graph is a library that provides a standard LangGraph endpoint for integrating into various frameworks like NextJS and Hono.js. It supports multiple storage backends (SQLite, PostgreSQL, Redis) and message queues. This document will guide you on how to use Pure Graph in your projects.
4
+
5
+ ## Features
6
+
7
+ - **Multiple Storage Backends**: Support for SQLite, PostgreSQL, Redis, and in-memory storage
8
+ - **Message Queue**: Redis-based stream queue with TTL support
9
+ - **Thread Management**: Comprehensive thread lifecycle management with status tracking
10
+ - **Framework Integration**: Native support for Next.js and Hono.js frameworks
4
11
 
5
12
  ## Installation
6
13
 
@@ -84,27 +91,28 @@ To integrate Pure Graph into a Hono.js project, follow these steps:
84
91
 
85
92
  Here are the environment variables you need to configure:
86
93
 
87
- - `SQLITE_DATABASE_URI`: Path to your SQLite database.
88
- - `CHECKPOINT_TYPE`: Type of checkpoint storage (e.g., `postgres`, `redis`, `shallow/redis`).
89
- - `REDIS_URL`: URL for Redis (required if using Redis).
94
+ - `SQLITE_DATABASE_URI`: Path to your SQLite database (e.g., `./.langgraph_api/chat.db`).
95
+ - `DATABASE_URL`: PostgreSQL connection string (required for PostgreSQL checkpoint storage).
96
+ - `CHECKPOINT_TYPE`: Type of checkpoint storage (optional, defaults to memory; options: `postgres`, `redis`, `shallow/redis`).
97
+ - `REDIS_URL`: URL for Redis (required if using Redis checkpoint or message queue).
90
98
 
91
99
  ## API Endpoints
92
100
 
93
101
  ### Assistants
94
102
 
95
- - **GET /assistants**: Search for assistants.
96
- - **GET /assistants/{assistantId}**: Retrieve a specific assistant graph.
103
+ - **GET /assistants**: Search for assistants.
104
+ - **GET /assistants/{assistantId}**: Retrieve a specific assistant graph.
97
105
 
98
106
  ### Threads
99
107
 
100
- - **POST /threads**: Create a new thread.
101
- - **GET /threads**: Search for threads.
102
- - **GET /threads/{threadId}**: Retrieve a specific thread.
103
- - **DELETE /threads/{threadId}**: Delete a specific thread.
108
+ - **POST /threads**: Create a new thread.
109
+ - **GET /threads**: Search for threads.
110
+ - **GET /threads/{threadId}**: Retrieve a specific thread.
111
+ - **DELETE /threads/{threadId}**: Delete a specific thread.
104
112
 
105
113
  ### Runs
106
114
 
107
- - **GET /threads/{threadId}/runs**: List runs in a thread.
108
- - **POST /threads/{threadId}/runs**: Create a new run.
109
- - **DELETE /threads/{threadId}/runs/{runId}**: Cancel a specific run.
110
- - **GET /threads/{threadId}/runs/{runId}/stream**: Stream run data.
115
+ - **GET /threads/{threadId}/runs**: List runs in a thread.
116
+ - **POST /threads/{threadId}/runs**: Create a new run.
117
+ - **DELETE /threads/{threadId}/runs/{runId}**: Cancel a specific run.
118
+ - **GET /threads/{threadId}/runs/{runId}/stream**: Stream run data.
@@ -39,7 +39,7 @@ export async function GET(req) {
39
39
  const match = pathname.match(/\/assistants\/([^/]+)\/graph$/);
40
40
  if (match) {
41
41
  const assistant_id = match[1];
42
- const xrayParam = url.searchParams.get('xray');
42
+ const xrayParam = url.searchParams.get('xray') ?? undefined;
43
43
  const queryParams = { xray: xrayParam };
44
44
  const { xray } = AssistantGraphQuerySchema.parse(queryParams);
45
45
  const data = await client.assistants.getGraph(assistant_id, {
@@ -62,9 +62,9 @@ export async function GET(req) {
62
62
  const match = pathname.match(/\/threads\/([0-9a-fA-F-]{36})\/runs$/);
63
63
  if (match) {
64
64
  const thread_id = match[1];
65
- const limit = url.searchParams.get('limit');
66
- const offset = url.searchParams.get('offset');
67
- const status = url.searchParams.get('status');
65
+ const limit = url.searchParams.get('limit') ?? undefined;
66
+ const offset = url.searchParams.get('offset') ?? undefined;
67
+ const status = url.searchParams.get('status') ?? undefined;
68
68
  const queryParams = { limit, offset, status };
69
69
  const { limit: parsedLimit, offset: parsedOffset, status: parsedStatus, } = RunListQuerySchema.parse(queryParams);
70
70
  const runs = await client.runs.list(thread_id, {
@@ -148,8 +148,8 @@ export async function POST(req) {
148
148
  if (match) {
149
149
  const thread_id = match[1];
150
150
  const run_id = match[2];
151
- const waitParam = url.searchParams.get('wait');
152
- const actionParam = url.searchParams.get('action');
151
+ const waitParam = url.searchParams.get('wait') ?? undefined;
152
+ const actionParam = url.searchParams.get('action') ?? undefined;
153
153
  const queryParams = {
154
154
  wait: waitParam ? waitParam === 'true' : false,
155
155
  action: actionParam ?? 'interrupt',
package/dist/global.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { SqliteSaver } from './storage/sqlite/checkpoint.js';
2
+ import type { PostgresSaver } from '@langchain/langgraph-checkpoint-postgres';
2
3
  export declare class LangGraphGlobal {
3
4
  static globalMessageQueue: import("./queue/stream_queue.js").StreamQueueManager<import("./queue/stream_queue.js").BaseStreamQueueInterface>;
4
- static globalCheckPointer: import("@langchain/langgraph-checkpoint-redis").RedisSaver | import("@langchain/langgraph-checkpoint-redis/shallow").ShallowRedisSaver | SqliteSaver | import("@langchain/langgraph-checkpoint").MemorySaver;
5
- static globalThreadsManager: import("./storage/sqlite/threads.js").SQLiteThreadsManager<unknown> | import("./storage/memory/threads.js").MemoryThreadsManager<unknown>;
5
+ static globalCheckPointer: import("@langchain/langgraph-checkpoint-redis").RedisSaver | import("@langchain/langgraph-checkpoint-redis/shallow").ShallowRedisSaver | PostgresSaver | SqliteSaver | import("@langchain/langgraph-checkpoint").MemorySaver;
6
+ static globalThreadsManager: import("./storage/pg/threads.js").PostgresThreadsManager<unknown> | import("./storage/sqlite/threads.js").SQLiteThreadsManager<unknown> | import("./storage/memory/threads.js").MemoryThreadsManager<unknown>;
6
7
  }
@@ -2,9 +2,10 @@ import { BaseStreamQueueInterface, StreamQueueManager } from '../queue/stream_qu
2
2
  import { MemorySaver } from './memory/checkpoint';
3
3
  import { MemoryThreadsManager } from './memory/threads';
4
4
  import type { SqliteSaver as SqliteSaverType } from './sqlite/checkpoint';
5
+ import type { PostgresSaver } from '@langchain/langgraph-checkpoint-postgres';
5
6
  import { SQLiteThreadsManager } from './sqlite/threads';
6
- export declare const createCheckPointer: () => Promise<import("@langchain/langgraph-checkpoint-redis").RedisSaver | import("@langchain/langgraph-checkpoint-redis/shallow").ShallowRedisSaver | SqliteSaverType | MemorySaver>;
7
+ export declare const createCheckPointer: () => Promise<import("@langchain/langgraph-checkpoint-redis").RedisSaver | import("@langchain/langgraph-checkpoint-redis/shallow").ShallowRedisSaver | PostgresSaver | SqliteSaverType | MemorySaver>;
7
8
  export declare const createMessageQueue: () => Promise<StreamQueueManager<BaseStreamQueueInterface>>;
8
9
  export declare const createThreadManager: (config: {
9
- checkpointer?: SqliteSaverType;
10
- }) => SQLiteThreadsManager<unknown> | MemoryThreadsManager<unknown>;
10
+ checkpointer?: SqliteSaverType | PostgresSaver;
11
+ }) => Promise<import("./pg/threads").PostgresThreadsManager<unknown> | SQLiteThreadsManager<unknown> | MemoryThreadsManager<unknown>>;
@@ -8,6 +8,7 @@ export const createCheckPointer = async () => {
8
8
  if ((process.env.REDIS_URL && process.env.CHECKPOINT_TYPE === 'redis') ||
9
9
  process.env.CHECKPOINT_TYPE === 'shallow/redis') {
10
10
  if (process.env.CHECKPOINT_TYPE === 'redis') {
11
+ console.log('Using redis as checkpoint');
11
12
  const { RedisSaver } = await import('@langchain/langgraph-checkpoint-redis');
12
13
  return await RedisSaver.fromUrl(process.env.REDIS_URL, {
13
14
  defaultTTL: 60, // TTL in minutes
@@ -15,11 +16,18 @@ export const createCheckPointer = async () => {
15
16
  });
16
17
  }
17
18
  if (process.env.CHECKPOINT_TYPE === 'shallow/redis') {
19
+ console.log('Using shallow redis as checkpoint');
18
20
  const { ShallowRedisSaver } = await import('@langchain/langgraph-checkpoint-redis/shallow');
19
21
  return await ShallowRedisSaver.fromUrl(process.env.REDIS_URL);
20
22
  }
21
23
  }
24
+ if (process.env.DATABASE_URL) {
25
+ console.log('Using postgres as checkpoint');
26
+ const { createPGCheckpoint } = await import('./pg/checkpoint');
27
+ return createPGCheckpoint();
28
+ }
22
29
  if (process.env.SQLITE_DATABASE_URI) {
30
+ console.log('Using sqlite as checkpoint');
23
31
  const { SqliteSaver } = await import('./sqlite/checkpoint');
24
32
  const db = SqliteSaver.fromConnString(process.env.SQLITE_DATABASE_URI);
25
33
  return db;
@@ -38,7 +46,11 @@ export const createMessageQueue = async () => {
38
46
  }
39
47
  return new StreamQueueManager(q);
40
48
  };
41
- export const createThreadManager = (config) => {
49
+ export const createThreadManager = async (config) => {
50
+ if (process.env.DATABASE_URL && config.checkpointer) {
51
+ const { PostgresThreadsManager } = await import('./pg/threads');
52
+ return new PostgresThreadsManager(config.checkpointer);
53
+ }
42
54
  if (process.env.SQLITE_DATABASE_URI && config.checkpointer) {
43
55
  return new SQLiteThreadsManager(config.checkpointer);
44
56
  }
@@ -0,0 +1,2 @@
1
+ import { PostgresSaver } from '@langchain/langgraph-checkpoint-postgres';
2
+ export declare const createPGCheckpoint: () => Promise<PostgresSaver>;
@@ -0,0 +1,9 @@
1
+ import { PostgresSaver } from '@langchain/langgraph-checkpoint-postgres';
2
+ export const createPGCheckpoint = async () => {
3
+ const checkpointer = PostgresSaver.fromConnString(process.env.DATABASE_URL);
4
+ if (process.env.DATABASE_INIT === 'true') {
5
+ console.log('Initializing postgres checkpoint');
6
+ await checkpointer.setup();
7
+ }
8
+ return checkpointer;
9
+ };
@@ -0,0 +1,43 @@
1
+ import { BaseThreadsManager } from '../../threads/index.js';
2
+ import { Command, Config, Metadata, OnConflictBehavior, Run, RunStatus, SortOrder, Thread, ThreadSortBy, ThreadStatus } from '@langgraph-js/sdk';
3
+ import type { PostgresSaver } from '@langchain/langgraph-checkpoint-postgres';
4
+ export declare class PostgresThreadsManager<ValuesType = unknown> implements BaseThreadsManager<ValuesType> {
5
+ private pool;
6
+ private isSetup;
7
+ constructor(checkpointer: PostgresSaver);
8
+ private setup;
9
+ create(payload?: {
10
+ metadata?: Metadata;
11
+ threadId?: string;
12
+ ifExists?: OnConflictBehavior;
13
+ graphId?: string;
14
+ supersteps?: Array<{
15
+ updates: Array<{
16
+ values: unknown;
17
+ command?: Command;
18
+ asNode: string;
19
+ }>;
20
+ }>;
21
+ }): Promise<Thread<ValuesType>>;
22
+ search(query?: {
23
+ metadata?: Metadata;
24
+ limit?: number;
25
+ offset?: number;
26
+ status?: ThreadStatus;
27
+ sortBy?: ThreadSortBy;
28
+ sortOrder?: SortOrder;
29
+ }): Promise<Thread<ValuesType>[]>;
30
+ get(threadId: string): Promise<Thread<ValuesType>>;
31
+ set(threadId: string, thread: Partial<Thread<ValuesType>>): Promise<void>;
32
+ updateState(threadId: string, thread: Partial<Thread<ValuesType>>): Promise<Pick<Config, 'configurable'>>;
33
+ delete(threadId: string): Promise<void>;
34
+ createRun(threadId: string, assistantId: string, payload?: {
35
+ metadata?: Metadata;
36
+ }): Promise<Run>;
37
+ listRuns(threadId: string, options?: {
38
+ limit?: number;
39
+ offset?: number;
40
+ status?: RunStatus;
41
+ }): Promise<Run[]>;
42
+ updateRun(runId: string, run: Partial<Run>): Promise<void>;
43
+ }
@@ -0,0 +1,304 @@
1
+ import { getGraph } from '../../utils/getGraph.js';
2
+ import { serialiseAsDict } from '../../graph/stream.js';
3
+ export class PostgresThreadsManager {
4
+ pool;
5
+ isSetup = false;
6
+ constructor(checkpointer) {
7
+ // 访问 PostgresSaver 的 pool 属性(虽然是 private,但在运行时可以访问)
8
+ this.pool = checkpointer.pool;
9
+ this.setup();
10
+ }
11
+ async setup() {
12
+ if (this.isSetup) {
13
+ return;
14
+ }
15
+ // 创建 threads 表
16
+ await this.pool.query(`
17
+ CREATE TABLE IF NOT EXISTS threads (
18
+ thread_id TEXT PRIMARY KEY,
19
+ created_at TIMESTAMP NOT NULL,
20
+ updated_at TIMESTAMP NOT NULL,
21
+ metadata JSONB NOT NULL DEFAULT '{}',
22
+ status TEXT NOT NULL DEFAULT 'idle',
23
+ "values" JSONB,
24
+ interrupts JSONB NOT NULL DEFAULT '{}'
25
+ )
26
+ `);
27
+ // 创建 runs 表
28
+ await this.pool.query(`
29
+ CREATE TABLE IF NOT EXISTS runs (
30
+ run_id TEXT PRIMARY KEY,
31
+ thread_id TEXT NOT NULL,
32
+ assistant_id TEXT NOT NULL,
33
+ created_at TIMESTAMP NOT NULL,
34
+ updated_at TIMESTAMP NOT NULL,
35
+ status TEXT NOT NULL DEFAULT 'pending',
36
+ metadata JSONB NOT NULL DEFAULT '{}',
37
+ multitask_strategy TEXT NOT NULL DEFAULT 'reject',
38
+ FOREIGN KEY (thread_id) REFERENCES threads(thread_id) ON DELETE CASCADE
39
+ )
40
+ `);
41
+ // 创建索引以提高查询性能
42
+ await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_threads_status ON threads(status)`);
43
+ await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_threads_created_at ON threads(created_at)`);
44
+ await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_threads_updated_at ON threads(updated_at)`);
45
+ await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_runs_thread_id ON runs(thread_id)`);
46
+ await this.pool.query(`CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status)`);
47
+ this.isSetup = true;
48
+ }
49
+ async create(payload) {
50
+ const threadId = payload?.threadId || crypto.randomUUID();
51
+ // 检查线程是否已存在
52
+ if (payload?.ifExists === 'raise') {
53
+ const result = await this.pool.query('SELECT thread_id FROM threads WHERE thread_id = $1', [threadId]);
54
+ if (result.rows.length > 0) {
55
+ throw new Error(`Thread with ID ${threadId} already exists.`);
56
+ }
57
+ }
58
+ const now = new Date();
59
+ const metadata = payload?.metadata || {};
60
+ const interrupts = {};
61
+ const thread = {
62
+ thread_id: threadId,
63
+ created_at: now.toISOString(),
64
+ updated_at: now.toISOString(),
65
+ metadata,
66
+ status: 'idle',
67
+ values: null,
68
+ interrupts,
69
+ };
70
+ // 插入到数据库
71
+ await this.pool.query(`
72
+ INSERT INTO threads (thread_id, created_at, updated_at, metadata, status, "values", interrupts)
73
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
74
+ `, [threadId, now, now, JSON.stringify(metadata), 'idle', null, JSON.stringify(interrupts)]);
75
+ return thread;
76
+ }
77
+ async search(query) {
78
+ let sql = 'SELECT * FROM threads';
79
+ const whereConditions = [];
80
+ const params = [];
81
+ let paramIndex = 1;
82
+ // 构建 WHERE 条件
83
+ if (query?.status) {
84
+ whereConditions.push(`status = $${paramIndex++}`);
85
+ params.push(query.status);
86
+ }
87
+ if (query?.metadata) {
88
+ for (const [key, value] of Object.entries(query.metadata)) {
89
+ whereConditions.push(`metadata->$${paramIndex} = $${paramIndex + 1}`);
90
+ params.push(key, JSON.stringify(value));
91
+ paramIndex += 2;
92
+ }
93
+ }
94
+ if (whereConditions.length > 0) {
95
+ sql += ' WHERE ' + whereConditions.join(' AND ');
96
+ }
97
+ // 添加排序
98
+ if (query?.sortBy) {
99
+ sql += ` ORDER BY ${query.sortBy}`;
100
+ if (query.sortOrder === 'desc') {
101
+ sql += ' DESC';
102
+ }
103
+ else {
104
+ sql += ' ASC';
105
+ }
106
+ }
107
+ // 添加分页
108
+ if (query?.limit) {
109
+ sql += ` LIMIT $${paramIndex++}`;
110
+ params.push(query.limit);
111
+ if (query?.offset) {
112
+ sql += ` OFFSET $${paramIndex++}`;
113
+ params.push(query.offset);
114
+ }
115
+ }
116
+ const result = await this.pool.query(sql, params);
117
+ return result.rows.map((row) => ({
118
+ thread_id: row.thread_id,
119
+ created_at: new Date(row.created_at).toISOString(),
120
+ updated_at: new Date(row.updated_at).toISOString(),
121
+ metadata: row.metadata,
122
+ status: row.status,
123
+ values: row.values || null,
124
+ interrupts: row.interrupts,
125
+ }));
126
+ }
127
+ async get(threadId) {
128
+ const result = await this.pool.query('SELECT * FROM threads WHERE thread_id = $1', [threadId]);
129
+ if (result.rows.length === 0) {
130
+ throw new Error(`Thread with ID ${threadId} not found.`);
131
+ }
132
+ const row = result.rows[0];
133
+ return {
134
+ thread_id: row.thread_id,
135
+ created_at: new Date(row.created_at).toISOString(),
136
+ updated_at: new Date(row.updated_at).toISOString(),
137
+ metadata: row.metadata,
138
+ status: row.status,
139
+ values: row.values || null,
140
+ interrupts: row.interrupts,
141
+ };
142
+ }
143
+ async set(threadId, thread) {
144
+ // 检查线程是否存在
145
+ const existingThread = await this.pool.query('SELECT thread_id FROM threads WHERE thread_id = $1', [threadId]);
146
+ if (existingThread.rows.length === 0) {
147
+ throw new Error(`Thread with ID ${threadId} not found.`);
148
+ }
149
+ const updateFields = [];
150
+ const values = [];
151
+ let paramIndex = 1;
152
+ if (thread.metadata !== undefined) {
153
+ updateFields.push(`metadata = $${paramIndex++}`);
154
+ values.push(JSON.stringify(thread.metadata));
155
+ }
156
+ if (thread.status !== undefined) {
157
+ updateFields.push(`status = $${paramIndex++}`);
158
+ values.push(thread.status);
159
+ }
160
+ if (thread.values !== undefined) {
161
+ updateFields.push(`"values" = $${paramIndex++}`);
162
+ values.push(thread.values ? JSON.stringify(thread.values) : null);
163
+ }
164
+ if (thread.interrupts !== undefined) {
165
+ updateFields.push(`interrupts = $${paramIndex++}`);
166
+ values.push(JSON.stringify(thread.interrupts));
167
+ }
168
+ // 总是更新 updated_at
169
+ updateFields.push(`updated_at = $${paramIndex++}`);
170
+ values.push(new Date());
171
+ if (updateFields.length > 0) {
172
+ values.push(threadId);
173
+ await this.pool.query(`
174
+ UPDATE threads
175
+ SET ${updateFields.join(', ')}
176
+ WHERE thread_id = $${paramIndex}
177
+ `, values);
178
+ }
179
+ }
180
+ async updateState(threadId, thread) {
181
+ // 从数据库查询线程信息
182
+ const result = await this.pool.query('SELECT * FROM threads WHERE thread_id = $1', [threadId]);
183
+ if (result.rows.length === 0) {
184
+ throw new Error(`Thread with ID ${threadId} not found.`);
185
+ }
186
+ const row = result.rows[0];
187
+ const targetThread = {
188
+ thread_id: row.thread_id,
189
+ created_at: new Date(row.created_at).toISOString(),
190
+ updated_at: new Date(row.updated_at).toISOString(),
191
+ metadata: row.metadata,
192
+ status: row.status,
193
+ values: row.values || null,
194
+ interrupts: row.interrupts,
195
+ };
196
+ if (targetThread.status === 'busy') {
197
+ throw new Error(`Thread with ID ${threadId} is busy, can't update state.`);
198
+ }
199
+ if (!targetThread.metadata?.graph_id) {
200
+ throw new Error(`Thread with ID ${threadId} has no graph_id.`);
201
+ }
202
+ const graphId = targetThread.metadata?.graph_id;
203
+ const config = {
204
+ configurable: {
205
+ thread_id: threadId,
206
+ graph_id: graphId,
207
+ },
208
+ };
209
+ const graph = await getGraph(graphId, config);
210
+ const nextConfig = await graph.updateState(config, thread.values);
211
+ const graphState = await graph.getState(config);
212
+ await this.set(threadId, { values: JSON.parse(serialiseAsDict(graphState.values)) });
213
+ return nextConfig;
214
+ }
215
+ async delete(threadId) {
216
+ const result = await this.pool.query('DELETE FROM threads WHERE thread_id = $1', [threadId]);
217
+ if (result.rowCount === 0) {
218
+ throw new Error(`Thread with ID ${threadId} not found.`);
219
+ }
220
+ }
221
+ async createRun(threadId, assistantId, payload) {
222
+ const runId = crypto.randomUUID();
223
+ const now = new Date();
224
+ const metadata = payload?.metadata ?? {};
225
+ const run = {
226
+ run_id: runId,
227
+ thread_id: threadId,
228
+ assistant_id: assistantId,
229
+ created_at: now.toISOString(),
230
+ updated_at: now.toISOString(),
231
+ status: 'pending',
232
+ metadata,
233
+ multitask_strategy: 'reject',
234
+ };
235
+ // 插入到数据库
236
+ await this.pool.query(`
237
+ INSERT INTO runs (run_id, thread_id, assistant_id, created_at, updated_at, status, metadata, multitask_strategy)
238
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
239
+ `, [runId, threadId, assistantId, now, now, 'pending', JSON.stringify(metadata), 'reject']);
240
+ return run;
241
+ }
242
+ async listRuns(threadId, options) {
243
+ let sql = 'SELECT * FROM runs WHERE thread_id = $1';
244
+ const params = [threadId];
245
+ let paramIndex = 2;
246
+ if (options?.status) {
247
+ sql += ` AND status = $${paramIndex++}`;
248
+ params.push(options.status);
249
+ }
250
+ sql += ' ORDER BY created_at DESC';
251
+ if (options?.limit) {
252
+ sql += ` LIMIT $${paramIndex++}`;
253
+ params.push(options.limit);
254
+ if (options?.offset) {
255
+ sql += ` OFFSET $${paramIndex++}`;
256
+ params.push(options.offset);
257
+ }
258
+ }
259
+ const result = await this.pool.query(sql, params);
260
+ return result.rows.map((row) => ({
261
+ run_id: row.run_id,
262
+ thread_id: row.thread_id,
263
+ assistant_id: row.assistant_id,
264
+ created_at: new Date(row.created_at).toISOString(),
265
+ updated_at: new Date(row.updated_at).toISOString(),
266
+ status: row.status,
267
+ metadata: row.metadata,
268
+ multitask_strategy: row.multitask_strategy,
269
+ }));
270
+ }
271
+ async updateRun(runId, run) {
272
+ // 检查运行是否存在
273
+ const existingRun = await this.pool.query('SELECT run_id FROM runs WHERE run_id = $1', [runId]);
274
+ if (existingRun.rows.length === 0) {
275
+ throw new Error(`Run with ID ${runId} not found.`);
276
+ }
277
+ const updateFields = [];
278
+ const values = [];
279
+ let paramIndex = 1;
280
+ if (run.status !== undefined) {
281
+ updateFields.push(`status = $${paramIndex++}`);
282
+ values.push(run.status);
283
+ }
284
+ if (run.metadata !== undefined) {
285
+ updateFields.push(`metadata = $${paramIndex++}`);
286
+ values.push(JSON.stringify(run.metadata));
287
+ }
288
+ if (run.multitask_strategy !== undefined) {
289
+ updateFields.push(`multitask_strategy = $${paramIndex++}`);
290
+ values.push(run.multitask_strategy);
291
+ }
292
+ // 总是更新 updated_at
293
+ updateFields.push(`updated_at = $${paramIndex++}`);
294
+ values.push(new Date());
295
+ if (updateFields.length > 0) {
296
+ values.push(runId);
297
+ await this.pool.query(`
298
+ UPDATE runs
299
+ SET ${updateFields.join(', ')}
300
+ WHERE run_id = $${paramIndex}
301
+ `, values);
302
+ }
303
+ }
304
+ }