@langgraph-js/sdk 1.3.2 → 1.5.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.
@@ -119,6 +119,7 @@ export declare class LangGraphClient extends Client {
119
119
  /** 图发过来的更新信息 */
120
120
  graphMessages: RenderMessage[];
121
121
  cloneMessage(message: Message): Message;
122
+ private updateStreamingMessage;
122
123
  private replaceMessageWithValuesMessage;
123
124
  /**
124
125
  * @zh 用于 UI 中的流式渲染中的消息。
@@ -164,6 +165,8 @@ export declare class LangGraphClient extends Client {
164
165
  * @en Sends a message to the LangGraph backend.
165
166
  */
166
167
  sendMessage(input: string | Message[], { extraParams, _debug, command }?: SendMessageOptions): Promise<any[]>;
168
+ /** 子图的数据需要通过 merge 的方式重新进行合并更新 */
169
+ private mergeSubGraphMessagesToStreamingMessages;
167
170
  private runFETool;
168
171
  private callFETool;
169
172
  /**
@@ -126,6 +126,14 @@ export class LangGraphClient extends Client {
126
126
  cloneMessage(message) {
127
127
  return JSON.parse(JSON.stringify(message));
128
128
  }
129
+ updateStreamingMessage(message) {
130
+ const lastMessage = this.streamingMessage[this.streamingMessage.length - 1];
131
+ if (!(lastMessage === null || lastMessage === void 0 ? void 0 : lastMessage.id) || message.id !== lastMessage.id) {
132
+ this.streamingMessage.push(message);
133
+ return;
134
+ }
135
+ this.streamingMessage[this.streamingMessage.length - 1] = message;
136
+ }
129
137
  replaceMessageWithValuesMessage(message, isTool = false) {
130
138
  const key = (isTool ? "tool_call_id" : "id");
131
139
  const valuesMessage = this.graphMessages.find((i) => i[key] === message[key]);
@@ -145,8 +153,10 @@ export class LangGraphClient extends Client {
145
153
  get renderMessage() {
146
154
  var _a;
147
155
  const previousMessage = new Map();
156
+ const closedToolCallIds = new Set();
148
157
  const result = [];
149
158
  const inputMessages = [...this.graphMessages, ...this.streamingMessage];
159
+ console.log(inputMessages);
150
160
  // 从后往前遍历,这样可以保证最新的消息在前面
151
161
  for (let i = inputMessages.length - 1; i >= 0; i--) {
152
162
  const message = this.cloneMessage(inputMessages[i]);
@@ -164,7 +174,11 @@ export class LangGraphClient extends Client {
164
174
  previousMessage.set(message.id, m);
165
175
  /** @ts-ignore */
166
176
  const tool_calls = ((_a = m.tool_calls) === null || _a === void 0 ? void 0 : _a.length) ? m.tool_calls : m.tool_call_chunks;
167
- const new_tool_calls = tool_calls.map((tool, index) => {
177
+ const new_tool_calls = tool_calls
178
+ .filter((i) => {
179
+ return !closedToolCallIds.has(i.id);
180
+ })
181
+ .map((tool, index) => {
168
182
  var _a, _b, _c, _d;
169
183
  return this.replaceMessageWithValuesMessage({
170
184
  type: "tool",
@@ -186,6 +200,9 @@ export class LangGraphClient extends Client {
186
200
  result.unshift(m);
187
201
  }
188
202
  else {
203
+ if (message.type === "tool" && message.tool_call_id) {
204
+ closedToolCallIds.add(message.tool_call_id);
205
+ }
189
206
  // 记录这个 id 的消息,并添加到结果中
190
207
  const m = this.replaceMessageWithValuesMessage(message);
191
208
  previousMessage.set(message.id, m);
@@ -364,7 +381,7 @@ export class LangGraphClient extends Client {
364
381
  }
365
382
  else if (chunk.event === "messages/partial") {
366
383
  for (const message of chunk.data) {
367
- this.streamingMessage.push(message);
384
+ this.updateStreamingMessage(message);
368
385
  }
369
386
  this.emitStreamingUpdate({
370
387
  type: "message",
@@ -372,7 +389,7 @@ export class LangGraphClient extends Client {
372
389
  });
373
390
  continue;
374
391
  }
375
- else if (chunk.event.startsWith("values")) {
392
+ else if (chunk.event === "values") {
376
393
  const data = chunk.data;
377
394
  if (data.messages) {
378
395
  const isResume = !!(command === null || command === void 0 ? void 0 : command.resume);
@@ -390,6 +407,12 @@ export class LangGraphClient extends Client {
390
407
  this.streamingMessage = [];
391
408
  continue;
392
409
  }
410
+ else if (chunk.event.startsWith("values|")) {
411
+ // 这个 values 必然是子 values
412
+ if (chunk.data.messages) {
413
+ this.mergeSubGraphMessagesToStreamingMessages(chunk.data.messages);
414
+ }
415
+ }
393
416
  }
394
417
  this.streamingMessage = [];
395
418
  const data = await this.runFETool();
@@ -403,6 +426,23 @@ export class LangGraphClient extends Client {
403
426
  });
404
427
  return streamRecord;
405
428
  }
429
+ /** 子图的数据需要通过 merge 的方式重新进行合并更新 */
430
+ mergeSubGraphMessagesToStreamingMessages(messages) {
431
+ const map = new Map(messages.filter((i) => i.id).map((i) => [i.id, i]));
432
+ this.streamingMessage.forEach((i) => {
433
+ if (map.has(i.id)) {
434
+ const newValue = map.get(i.id);
435
+ Object.assign(i, newValue);
436
+ map.delete(i.id);
437
+ }
438
+ });
439
+ // 剩余的 message 一定不在 streamMessage 中
440
+ map.forEach((i) => {
441
+ if (i.type === "tool" && i.tool_call_id) {
442
+ this.streamingMessage.push(i);
443
+ }
444
+ });
445
+ }
406
446
  runFETool() {
407
447
  var _a;
408
448
  const data = this.graphMessages;
@@ -0,0 +1,10 @@
1
+ import { RenderMessage } from "../LangGraphClient.js";
2
+ import { LangGraphClient } from "../LangGraphClient.js";
3
+ export declare class ToolRenderData<D> {
4
+ message: RenderMessage;
5
+ client: LangGraphClient;
6
+ constructor(message: RenderMessage, client: LangGraphClient);
7
+ get state(): "done" | "idle";
8
+ get input(): any;
9
+ response(data: D): void;
10
+ }
@@ -0,0 +1,24 @@
1
+ export class ToolRenderData {
2
+ constructor(message, client) {
3
+ this.message = message;
4
+ this.client = client;
5
+ }
6
+ get state() {
7
+ var _a, _b;
8
+ if (this.message.type === "tool" && ((_b = (_a = this.message) === null || _a === void 0 ? void 0 : _a.additional_kwargs) === null || _b === void 0 ? void 0 : _b.done)) {
9
+ return "done";
10
+ }
11
+ return "idle";
12
+ }
13
+ get input() {
14
+ try {
15
+ return JSON.parse(this.message.tool_input);
16
+ }
17
+ catch (e) {
18
+ return null;
19
+ }
20
+ }
21
+ response(data) {
22
+ this.client.doneFEToolWaiting(this.message.id, JSON.stringify(data));
23
+ }
24
+ }
@@ -1,7 +1,8 @@
1
1
  import { z, ZodRawShape, ZodTypeAny } from "zod";
2
2
  import { Action, Parameter } from "./copilotkit-actions.js";
3
3
  import { Message } from "@langchain/langgraph-sdk";
4
- export interface UnionTool<Args extends ZodRawShape> {
4
+ import { ToolRenderData } from "./ToolUI.js";
5
+ export interface UnionTool<Args extends ZodRawShape, Child extends Object = Object> {
5
6
  name: string;
6
7
  description: string;
7
8
  parameters: Args;
@@ -10,6 +11,8 @@ export interface UnionTool<Args extends ZodRawShape> {
10
11
  execute: ToolCallback<Args>;
11
12
  /** 工具执行成功后触发的附加消息 */
12
13
  callbackMessage?: (result: CallToolResult) => Message[];
14
+ render?: <D>(tool: ToolRenderData<D>) => Child;
15
+ onlyRender?: boolean;
13
16
  }
14
17
  export type ToolCallback<Args extends ZodRawShape> = (args: z.objectOutputType<Args, ZodTypeAny>, context?: any) => CallToolResult | Promise<CallToolResult>;
15
18
  export type CallToolResult = string | {
@@ -17,7 +20,7 @@ export type CallToolResult = string | {
17
20
  text: string;
18
21
  }[];
19
22
  /** 用于格式校验 */
20
- export declare const createTool: <Args extends ZodRawShape>(tool: UnionTool<Args>) => UnionTool<Args>;
23
+ export declare const createTool: <Args extends ZodRawShape>(tool: UnionTool<Args>) => UnionTool<Args, Object>;
21
24
  /** 提供一种兼容 copilotkit 的定义方式,简化定义形式
22
25
  * 来自 copilotkit 的 frontend action
23
26
  */
@@ -45,3 +48,18 @@ export declare const createMCPTool: <Args extends ZodRawShape>(tool: UnionTool<A
45
48
  }[];
46
49
  isError: boolean;
47
50
  }>))[];
51
+ export declare const createToolUI: <Args extends Parameter[] | [] = [], Child extends Object = {}>(tool: Action<Args> & {
52
+ render?: (tool: ToolRenderData<any>) => Child;
53
+ onlyRender?: boolean;
54
+ }) => {
55
+ render: ((tool: ToolRenderData<any>) => Child) | undefined;
56
+ onlyRender: boolean | undefined;
57
+ name: string;
58
+ description: string;
59
+ parameters: z.ZodRawShape;
60
+ /** 是否直接返回工具结果,而不是通过消息返回 */
61
+ returnDirect?: boolean;
62
+ execute: ToolCallback<z.ZodRawShape>;
63
+ /** 工具执行成功后触发的附加消息 */
64
+ callbackMessage?: (result: CallToolResult) => Message[];
65
+ };
@@ -59,3 +59,10 @@ export const createMCPTool = (tool) => {
59
59
  },
60
60
  ];
61
61
  };
62
+ export const createToolUI = (tool) => {
63
+ return {
64
+ ...createFETool(tool),
65
+ render: tool.render,
66
+ onlyRender: tool.onlyRender,
67
+ };
68
+ };
@@ -1,2 +1,3 @@
1
1
  export * from "./createTool.js";
2
2
  export * from "./copilotkit-actions.js";
3
+ export * from "./ToolUI.js";
@@ -1,2 +1,3 @@
1
1
  export * from "./createTool.js";
2
2
  export * from "./copilotkit-actions.js";
3
+ export * from "./ToolUI.js";
@@ -87,5 +87,6 @@ export declare const createChatStore: (initClientName: string, config: LangGraph
87
87
  deleteHistoryChat(thread: Thread<{
88
88
  messages: Message[];
89
89
  }>): Promise<void>;
90
+ getToolUIRender: (tool_name: string) => ((message: RenderMessage) => Object) | null;
90
91
  };
91
92
  };
@@ -1,6 +1,7 @@
1
1
  import { atom } from "nanostores";
2
2
  import { LangGraphClient } from "../LangGraphClient.js";
3
3
  import { rafDebounce } from "./rafDebounce.js";
4
+ import { ToolRenderData } from "../tool/ToolUI.js";
4
5
  /**
5
6
  * @zh 格式化日期对象为时间字符串。
6
7
  * @en Formats a Date object into a time string.
@@ -189,6 +190,12 @@ export const createChatStore = (initClientName, config, context = {}) => {
189
190
  const prev = historyList.get();
190
191
  historyList.set([thread, ...prev]);
191
192
  };
193
+ const getToolUIRender = (tool_name) => {
194
+ var _a;
195
+ const toolsDefine = client.get().tools.getAllTools();
196
+ const tool = (_a = toolsDefine.find((i) => i.name === tool_name)) === null || _a === void 0 ? void 0 : _a.render;
197
+ return tool ? (message) => tool(new ToolRenderData(message, client.get())) : null;
198
+ };
192
199
  return {
193
200
  data: {
194
201
  client,
@@ -271,6 +278,7 @@ export const createChatStore = (initClientName, config, context = {}) => {
271
278
  await ((_a = client.get()) === null || _a === void 0 ? void 0 : _a.threads.delete(thread.thread_id));
272
279
  await refreshHistoryList();
273
280
  },
281
+ getToolUIRender,
274
282
  },
275
283
  };
276
284
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langgraph-js/sdk",
3
- "version": "1.3.2",
3
+ "version": "1.5.0",
4
4
  "description": "The UI SDK for LangGraph - seamlessly integrate your AI agents with frontend interfaces",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -172,6 +172,7 @@ export class LangGraphClient extends Client {
172
172
  sortOrder: "desc",
173
173
  });
174
174
  }
175
+
175
176
  /**
176
177
  * @zh 从历史中恢复 Thread 数据。
177
178
  * @en Resets the Thread data from history.
@@ -198,6 +199,14 @@ export class LangGraphClient extends Client {
198
199
  cloneMessage(message: Message): Message {
199
200
  return JSON.parse(JSON.stringify(message));
200
201
  }
202
+ private updateStreamingMessage(message: RenderMessage) {
203
+ const lastMessage = this.streamingMessage[this.streamingMessage.length - 1];
204
+ if (!lastMessage?.id || message.id !== lastMessage.id) {
205
+ this.streamingMessage.push(message);
206
+ return;
207
+ }
208
+ this.streamingMessage[this.streamingMessage.length - 1] = message;
209
+ }
201
210
  private replaceMessageWithValuesMessage(message: AIMessage | ToolMessage, isTool = false): Message {
202
211
  const key = (isTool ? "tool_call_id" : "id") as any as "id";
203
212
  const valuesMessage = this.graphMessages.find((i) => i[key] === message[key]);
@@ -217,9 +226,10 @@ export class LangGraphClient extends Client {
217
226
  */
218
227
  get renderMessage() {
219
228
  const previousMessage = new Map<string, Message>();
229
+ const closedToolCallIds = new Set<string>();
220
230
  const result: Message[] = [];
221
231
  const inputMessages = [...this.graphMessages, ...this.streamingMessage];
222
-
232
+ console.log(inputMessages);
223
233
  // 从后往前遍历,这样可以保证最新的消息在前面
224
234
  for (let i = inputMessages.length - 1; i >= 0; i--) {
225
235
  const message = this.cloneMessage(inputMessages[i]);
@@ -241,21 +251,25 @@ export class LangGraphClient extends Client {
241
251
 
242
252
  /** @ts-ignore */
243
253
  const tool_calls: NonNullable<AIMessage["tool_calls"]> = (m as AIMessage).tool_calls?.length ? (m as AIMessage).tool_calls : (m as RenderMessage).tool_call_chunks;
244
- const new_tool_calls = tool_calls!.map((tool, index) => {
245
- return this.replaceMessageWithValuesMessage(
246
- {
247
- type: "tool",
248
- additional_kwargs: {},
249
- /** @ts-ignore */
250
- tool_input: m.additional_kwargs?.tool_calls?.[index]?.function?.arguments,
251
- id: tool.id,
252
- name: tool.name,
253
- response_metadata: {},
254
- tool_call_id: tool.id!,
255
- },
256
- true
257
- );
258
- });
254
+ const new_tool_calls = tool_calls
255
+ .filter((i) => {
256
+ return !closedToolCallIds.has(i.id!);
257
+ })!
258
+ .map((tool, index) => {
259
+ return this.replaceMessageWithValuesMessage(
260
+ {
261
+ type: "tool",
262
+ additional_kwargs: {},
263
+ /** @ts-ignore */
264
+ tool_input: m.additional_kwargs?.tool_calls?.[index]?.function?.arguments,
265
+ id: tool.id,
266
+ name: tool.name,
267
+ response_metadata: {},
268
+ tool_call_id: tool.id!,
269
+ },
270
+ true
271
+ );
272
+ });
259
273
  for (const tool of new_tool_calls) {
260
274
  if (!previousMessage.has(tool.id!)) {
261
275
  result.unshift(tool);
@@ -264,6 +278,9 @@ export class LangGraphClient extends Client {
264
278
  }
265
279
  result.unshift(m);
266
280
  } else {
281
+ if (message.type === "tool" && message.tool_call_id) {
282
+ closedToolCallIds.add(message.tool_call_id);
283
+ }
267
284
  // 记录这个 id 的消息,并添加到结果中
268
285
  const m = this.replaceMessageWithValuesMessage(message as AIMessage);
269
286
  previousMessage.set(message.id, m);
@@ -450,14 +467,14 @@ export class LangGraphClient extends Client {
450
467
  });
451
468
  } else if (chunk.event === "messages/partial") {
452
469
  for (const message of chunk.data) {
453
- this.streamingMessage.push(message);
470
+ this.updateStreamingMessage(message);
454
471
  }
455
472
  this.emitStreamingUpdate({
456
473
  type: "message",
457
474
  data: chunk,
458
475
  });
459
476
  continue;
460
- } else if (chunk.event.startsWith("values")) {
477
+ } else if (chunk.event === "values") {
461
478
  const data = chunk.data as { messages: Message[] };
462
479
 
463
480
  if (data.messages) {
@@ -475,6 +492,11 @@ export class LangGraphClient extends Client {
475
492
  this.graphState = chunk.data;
476
493
  this.streamingMessage = [];
477
494
  continue;
495
+ } else if (chunk.event.startsWith("values|")) {
496
+ // 这个 values 必然是子 values
497
+ if (chunk.data.messages) {
498
+ this.mergeSubGraphMessagesToStreamingMessages(chunk.data.messages);
499
+ }
478
500
  }
479
501
  }
480
502
  this.streamingMessage = [];
@@ -488,6 +510,23 @@ export class LangGraphClient extends Client {
488
510
  });
489
511
  return streamRecord;
490
512
  }
513
+ /** 子图的数据需要通过 merge 的方式重新进行合并更新 */
514
+ private mergeSubGraphMessagesToStreamingMessages(messages: Message[]) {
515
+ const map = new Map(messages.filter((i) => i.id).map((i) => [i.id!, i]));
516
+ this.streamingMessage.forEach((i) => {
517
+ if (map.has(i.id!)) {
518
+ const newValue = map.get(i.id!)!;
519
+ Object.assign(i, newValue);
520
+ map.delete(i.id!);
521
+ }
522
+ });
523
+ // 剩余的 message 一定不在 streamMessage 中
524
+ map.forEach((i) => {
525
+ if (i.type === "tool" && i.tool_call_id) {
526
+ this.streamingMessage.push(i as RenderMessage);
527
+ }
528
+ });
529
+ }
491
530
  private runFETool() {
492
531
  const data = this.graphMessages;
493
532
  const lastMessage = data[data.length - 1];
@@ -0,0 +1,26 @@
1
+ import { RenderMessage } from "../LangGraphClient.js";
2
+
3
+ import { LangGraphClient } from "../LangGraphClient.js";
4
+
5
+ export class ToolRenderData<D> {
6
+ constructor(
7
+ public message: RenderMessage,
8
+ public client: LangGraphClient
9
+ ) {}
10
+ get state() {
11
+ if (this.message.type === "tool" && this.message?.additional_kwargs?.done) {
12
+ return "done";
13
+ }
14
+ return "idle";
15
+ }
16
+ get input() {
17
+ try {
18
+ return JSON.parse(this.message.tool_input!);
19
+ } catch (e) {
20
+ return null;
21
+ }
22
+ }
23
+ response(data: D) {
24
+ this.client.doneFEToolWaiting(this.message.id!, JSON.stringify(data));
25
+ }
26
+ }
@@ -3,8 +3,9 @@ import { z, ZodRawShape, ZodTypeAny } from "zod";
3
3
  import { Action, Parameter } from "./copilotkit-actions.js";
4
4
  import { zodToJsonSchema } from "zod-to-json-schema";
5
5
  import { Message } from "@langchain/langgraph-sdk";
6
+ import { ToolRenderData } from "./ToolUI.js";
6
7
 
7
- export interface UnionTool<Args extends ZodRawShape> {
8
+ export interface UnionTool<Args extends ZodRawShape, Child extends Object = Object> {
8
9
  name: string;
9
10
  description: string;
10
11
  parameters: Args;
@@ -13,6 +14,8 @@ export interface UnionTool<Args extends ZodRawShape> {
13
14
  execute: ToolCallback<Args>;
14
15
  /** 工具执行成功后触发的附加消息 */
15
16
  callbackMessage?: (result: CallToolResult) => Message[];
17
+ render?: <D>(tool: ToolRenderData<D>) => Child;
18
+ onlyRender?: boolean;
16
19
  }
17
20
  export type ToolCallback<Args extends ZodRawShape> = (args: z.objectOutputType<Args, ZodTypeAny>, context?: any) => CallToolResult | Promise<CallToolResult>;
18
21
 
@@ -76,3 +79,11 @@ export const createMCPTool = <Args extends ZodRawShape>(tool: UnionTool<Args>) =
76
79
  },
77
80
  ];
78
81
  };
82
+
83
+ export const createToolUI = <Args extends Parameter[] | [] = [], Child extends Object = {}>(tool: Action<Args> & { render?: (tool: ToolRenderData<any>) => Child; onlyRender?: boolean }) => {
84
+ return {
85
+ ...createFETool(tool),
86
+ render: tool.render,
87
+ onlyRender: tool.onlyRender,
88
+ };
89
+ };
package/src/tool/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./createTool.js";
2
2
  export * from "./copilotkit-actions.js";
3
+ export * from "./ToolUI.js";
@@ -2,6 +2,7 @@ import { atom } from "nanostores";
2
2
  import { LangGraphClient, LangGraphClientConfig, RenderMessage, SendMessageOptions } from "../LangGraphClient.js";
3
3
  import { AssistantGraph, Message, Thread } from "@langchain/langgraph-sdk";
4
4
  import { rafDebounce } from "./rafDebounce.js";
5
+ import { ToolRenderData } from "../tool/ToolUI.js";
5
6
 
6
7
  /**
7
8
  * @zh 格式化日期对象为时间字符串。
@@ -198,7 +199,11 @@ export const createChatStore = (
198
199
  const prev = historyList.get();
199
200
  historyList.set([thread, ...prev]);
200
201
  };
201
-
202
+ const getToolUIRender = (tool_name: string) => {
203
+ const toolsDefine = client.get()!.tools.getAllTools();
204
+ const tool = toolsDefine.find((i) => i.name === tool_name!)?.render;
205
+ return tool ? (message: RenderMessage) => tool(new ToolRenderData(message, client.get()!)) : null;
206
+ };
202
207
  return {
203
208
  data: {
204
209
  client,
@@ -282,6 +287,7 @@ export const createChatStore = (
282
287
  await client.get()?.threads.delete(thread.thread_id);
283
288
  await refreshHistoryList();
284
289
  },
290
+ getToolUIRender,
285
291
  },
286
292
  };
287
293
  };