@langgraph-js/sdk 3.4.0 → 3.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.
@@ -18,7 +18,6 @@ export type RenderMessage = Message & {
18
18
  };
19
19
  }[];
20
20
  };
21
- sub_agent_messages?: RenderMessage[];
22
21
  usage_metadata?: {
23
22
  total_tokens: number;
24
23
  input_tokens: number;
@@ -28,6 +27,7 @@ export type RenderMessage = Message & {
28
27
  response_metadata?: {
29
28
  create_time: string;
30
29
  };
30
+ sub_messages?: RenderMessage[];
31
31
  /** 耗时 */
32
32
  spend_time?: number;
33
33
  /** 渲染时的唯一 id,聚合而来*/
@@ -43,6 +43,20 @@ export type SendMessageOptions = {
43
43
  command?: Command;
44
44
  joinRunId?: string;
45
45
  };
46
+ export type InterruptData = {
47
+ id: string;
48
+ value: {
49
+ actionRequests: {
50
+ name: string;
51
+ description: string;
52
+ args: any;
53
+ }[];
54
+ reviewConfigs: {
55
+ actionName: string;
56
+ allowedDecisions: ("approve" | "edit" | "reject")[];
57
+ }[];
58
+ };
59
+ }[];
46
60
  export interface LangGraphClientConfig {
47
61
  apiUrl?: string;
48
62
  apiKey?: string;
@@ -119,8 +133,6 @@ export declare class LangGraphClient<TStateType = unknown> extends EventEmitter<
119
133
  run_id: string;
120
134
  };
121
135
  stopController: AbortController | null;
122
- /** 用于存储 subAgent 状态数据的键 */
123
- subAgentsKey: string;
124
136
  /** Message 处理器 */
125
137
  private messageProcessor;
126
138
  constructor(config: LangGraphClientConfig);
@@ -128,14 +140,13 @@ export declare class LangGraphClient<TStateType = unknown> extends EventEmitter<
128
140
  get assistants(): {
129
141
  search(query?: {
130
142
  graphId?: string;
131
- metadata? /**
132
- * Specify a custom fetch implementation.
133
- *
134
- * By default we expect the `fetch` is available in the global scope.
135
- */: import("@langchain/langgraph-sdk").Metadata;
143
+ metadata?: import("@langchain/langgraph-sdk").Metadata;
136
144
  limit?: number;
137
145
  offset?: number;
138
- sortBy?: import("@langgraph-js/pure-graph/dist/types.js").AssistantSortBy;
146
+ sortBy? /**
147
+ * The maximum number of retries that can be made for a single call,
148
+ * with an exponential backoff between each attempt. Defaults to 6.
149
+ */: import("@langgraph-js/pure-graph/dist/types.js").AssistantSortBy;
139
150
  sortOrder?: import("@langgraph-js/pure-graph/dist/types.js").SortOrder;
140
151
  }): Promise<Assistant[]>;
141
152
  getGraph(assistantId: string, options?: {
@@ -223,6 +234,7 @@ export declare class LangGraphClient<TStateType = unknown> extends EventEmitter<
223
234
  messages: Message[];
224
235
  }>;
225
236
  messagesMetadata: {};
237
+ humanInTheLoop: InterruptData | null;
226
238
  /**
227
239
  * @zh 发送消息到 LangGraph 后端。
228
240
  * @en Sends a message to the LangGraph backend.
@@ -15,14 +15,13 @@ export class LangGraphClient extends EventEmitter {
15
15
  this.availableAssistants = [];
16
16
  this.graphState = {};
17
17
  this.stopController = null;
18
- /** 用于存储 subAgent 状态数据的键 */
19
- this.subAgentsKey = "task_store";
20
18
  this.messagesMetadata = {};
19
+ this.humanInTheLoop = null;
21
20
  /** 当前子图位置,但是依赖 stream,不太适合稳定使用*/
22
21
  this.graphPosition = "";
23
22
  this.extraParams = {};
24
23
  this.client = config.client;
25
- this.messageProcessor = new MessageProcessor(this.subAgentsKey);
24
+ this.messageProcessor = new MessageProcessor();
26
25
  }
27
26
  /** 代理 assistants 属性到内部 client */
28
27
  get assistants() {
@@ -210,7 +209,6 @@ export class LangGraphClient extends EventEmitter {
210
209
  * @en Sends a message to the LangGraph backend.
211
210
  */
212
211
  async sendMessage(input, { joinRunId, extraParams, _debug, command } = {}) {
213
- var _a;
214
212
  if (!this.currentAssistant) {
215
213
  throw new Error("Thread or Assistant not initialized");
216
214
  }
@@ -277,7 +275,10 @@ export class LangGraphClient extends EventEmitter {
277
275
  }
278
276
  else if (chunk.event === "values") {
279
277
  const data = chunk.data;
280
- if (data.messages) {
278
+ if (data.__interrupt__) {
279
+ this.humanInTheLoop = data.__interrupt__;
280
+ }
281
+ else if (data.messages) {
281
282
  const isResume = !!(command === null || command === void 0 ? void 0 : command.resume);
282
283
  const isLongerThanLocal = data.messages.length >= this.messageProcessor.getGraphMessages().length;
283
284
  // resume 情况下,长度低于前端 message 的统统不接受
@@ -290,10 +291,6 @@ export class LangGraphClient extends EventEmitter {
290
291
  continue;
291
292
  }
292
293
  else if (chunk.event.startsWith("values|")) {
293
- // 这个 values 必然是子 values
294
- if ((_a = chunk.data) === null || _a === void 0 ? void 0 : _a.messages) {
295
- this.messageProcessor.mergeSubGraphMessagesToStreamingMessages(chunk.data.messages);
296
- }
297
294
  this.graphPosition = chunk.event.split("|")[1];
298
295
  }
299
296
  }
@@ -328,17 +325,15 @@ export class LangGraphClient extends EventEmitter {
328
325
  // 如果最后一条消息是前端工具消息,则调用工具
329
326
  if (lastMessage.type === "ai" && ((_a = lastMessage.tool_calls) === null || _a === void 0 ? void 0 : _a.length)) {
330
327
  const result = lastMessage.tool_calls.map((tool) => {
331
- if (this.tools.getTool(tool.name)) {
332
- const toolMessage = {
333
- ...tool,
334
- tool_call_id: tool.id,
335
- /** @ts-ignore */
336
- tool_input: JSON.stringify(tool.args),
337
- additional_kwargs: {},
338
- };
339
- // json 校验
340
- return this.callFETool(toolMessage, tool.args);
341
- }
328
+ const toolMessage = {
329
+ ...tool,
330
+ tool_call_id: tool.id,
331
+ /** @ts-ignore */
332
+ tool_input: JSON.stringify(tool.args),
333
+ additional_kwargs: {},
334
+ };
335
+ // json 校验
336
+ return this.callFETool(toolMessage, tool.args);
342
337
  });
343
338
  this.currentThread.status = "interrupted"; // 修复某些机制下,状态不为 interrupted 与后端有差异
344
339
  return Promise.all(result);
@@ -13,12 +13,11 @@ export declare class StreamingMessageType {
13
13
  * @en The MessageProcessor class is used to uniformly handle Message-related logic and avoid duplicate processing.
14
14
  */
15
15
  export declare class MessageProcessor {
16
- private subAgentsKey;
17
16
  /** 流式消息缓存 */
18
17
  private streamingMessage;
19
18
  /** 图发过来的更新信息 */
20
19
  private graphMessages;
21
- constructor(subAgentsKey?: string);
20
+ constructor();
22
21
  /**
23
22
  * @zh 获取流式消息
24
23
  * @en Get streaming messages
@@ -54,11 +53,6 @@ export declare class MessageProcessor {
54
53
  * @en Combine graphMessages and streamingMessage and return a new message array
55
54
  */
56
55
  combineGraphMessagesWithStreamingMessages(): RenderMessage[];
57
- /**
58
- * @zh 子图的数据需要通过 merge 的方式重新进行合并更新
59
- * @en Subgraph data needs to be merged and updated through merge method
60
- */
61
- mergeSubGraphMessagesToStreamingMessages(messages: Message[]): void;
62
56
  /**
63
57
  * @zh 克隆消息对象
64
58
  * @en Clone message object
@@ -74,27 +68,21 @@ export declare class MessageProcessor {
74
68
  * @en Composes tool messages, associating AI tool calls with tool execution results.
75
69
  */
76
70
  composeToolMessages(messages: RenderMessage[]): RenderMessage[];
77
- /**
78
- * @zh 转换 subAgent 消息为工具的子消息
79
- * @en Convert subAgent messages to tool sub-messages
80
- */
81
- convertSubAgentMessages(messages: RenderMessage[], graphState: any, messagesMetadata: Record<string, {
82
- subagent_id?: string;
83
- }>): RenderMessage[];
84
71
  /**
85
72
  * @zh 生成用于 UI 中的流式渲染的消息
86
73
  * @en Generate messages used for streaming rendering in the UI
87
74
  */
88
75
  renderMessages(graphState: any, getGraphNodeNow: () => {
89
76
  name: string;
90
- }, messagesMetadata: Record<string, {
91
- subagent_id?: string;
92
- }>): RenderMessage[];
77
+ }, messagesMetadata: Record<string, any>): RenderMessage[];
78
+ foldTreeMessages(messages: RenderMessage[], graphState?: {
79
+ task_store?: Record<string, {
80
+ messages: RenderMessage[];
81
+ }>;
82
+ }, messagesMetadata?: Record<string, any>): RenderMessage[];
93
83
  /**
94
84
  * @zh 统一的消息处理入口,按顺序执行所有处理步骤
95
85
  * @en Unified message processing entry point, executing all processing steps in order
96
86
  */
97
- processMessages(messages: RenderMessage[], graphState: any, messagesMetadata: Record<string, {
98
- subagent_id?: string;
99
- }>): RenderMessage[];
87
+ processMessages(messages: RenderMessage[], graphState?: any, messagesMetadata?: Record<string, any>): RenderMessage[];
100
88
  }
@@ -17,12 +17,11 @@ export class StreamingMessageType {
17
17
  * @en The MessageProcessor class is used to uniformly handle Message-related logic and avoid duplicate processing.
18
18
  */
19
19
  export class MessageProcessor {
20
- constructor(subAgentsKey = "task_store") {
20
+ constructor() {
21
21
  /** 流式消息缓存 */
22
22
  this.streamingMessage = [];
23
23
  /** 图发过来的更新信息 */
24
24
  this.graphMessages = [];
25
- this.subAgentsKey = subAgentsKey;
26
25
  }
27
26
  /**
28
27
  * @zh 获取流式消息
@@ -89,26 +88,6 @@ export class MessageProcessor {
89
88
  ...idMap.values(),
90
89
  ];
91
90
  }
92
- /**
93
- * @zh 子图的数据需要通过 merge 的方式重新进行合并更新
94
- * @en Subgraph data needs to be merged and updated through merge method
95
- */
96
- mergeSubGraphMessagesToStreamingMessages(messages) {
97
- const map = new Map(messages.filter((i) => i.id).map((i) => [i.id, i]));
98
- this.streamingMessage.forEach((i) => {
99
- if (map.has(i.id)) {
100
- const newValue = map.get(i.id);
101
- Object.assign(i, newValue);
102
- map.delete(i.id);
103
- }
104
- });
105
- // 剩余的 message 一定不在 streamMessage 中
106
- map.forEach((i) => {
107
- if (i.type === "tool" && i.tool_call_id) {
108
- this.streamingMessage.push(i);
109
- }
110
- });
111
- }
112
91
  /**
113
92
  * @zh 克隆消息对象
114
93
  * @en Clone message object
@@ -190,59 +169,6 @@ export class MessageProcessor {
190
169
  }
191
170
  return result;
192
171
  }
193
- /**
194
- * @zh 转换 subAgent 消息为工具的子消息
195
- * @en Convert subAgent messages to tool sub-messages
196
- */
197
- convertSubAgentMessages(messages, graphState, messagesMetadata) {
198
- const origin_task_store = graphState[this.subAgentsKey];
199
- if (!origin_task_store)
200
- return messages;
201
- const task_store = JSON.parse(JSON.stringify(origin_task_store));
202
- /** 获取 subAgent 消息的 id,用于流式过程中对数据进行标记 */
203
- messages
204
- .filter((i) => {
205
- var _a, _b;
206
- return ((_a = messagesMetadata[i.id]) === null || _a === void 0 ? void 0 : _a.subagent_id) || ((_b = i.node_name) === null || _b === void 0 ? void 0 : _b.startsWith("subagent_"));
207
- })
208
- .forEach((i) => {
209
- var _a;
210
- const tool_call_id = ((_a = messagesMetadata[i.id]) === null || _a === void 0 ? void 0 : _a.subagent_id) || i.node_name.replace("subagent_", "");
211
- const store = task_store[tool_call_id];
212
- if (store) {
213
- // 根据 id 进行去重
214
- const exists = store.messages.some((msg) => msg.id === i.id);
215
- if (!exists) {
216
- store.messages.push(i);
217
- }
218
- }
219
- else {
220
- task_store[tool_call_id] = {
221
- messages: [i],
222
- };
223
- }
224
- });
225
- const ignoreIds = new Set();
226
- Object.values(task_store).forEach((task) => {
227
- task.messages.forEach((message) => {
228
- ignoreIds.add(message.id);
229
- });
230
- });
231
- const result = [];
232
- for (const message of messages) {
233
- if (message.type === "tool" && message.tool_call_id) {
234
- const task = task_store[message.tool_call_id];
235
- if (task) {
236
- // 递归处理子消息,但避免重复处理
237
- message.sub_agent_messages = this.processMessages(task.messages, task, messagesMetadata);
238
- }
239
- }
240
- if (message.id && ignoreIds.has(message.id))
241
- continue;
242
- result.push(message);
243
- }
244
- return result;
245
- }
246
172
  /**
247
173
  * @zh 生成用于 UI 中的流式渲染的消息
248
174
  * @en Generate messages used for streaming rendering in the UI
@@ -307,6 +233,51 @@ export class MessageProcessor {
307
233
  }
308
234
  return this.processMessages(result, graphState, messagesMetadata);
309
235
  }
236
+ foldTreeMessages(messages, graphState, messagesMetadata) {
237
+ const state_sub_messages = Object.entries((graphState === null || graphState === void 0 ? void 0 : graphState.task_store) || {}).map(([key, value]) => [key, value.messages]);
238
+ const state_sub_messages_map = new Map(state_sub_messages);
239
+ const nonRootMessageId = new Set();
240
+ const parentPointer = new Map(Object.entries(messagesMetadata || {})
241
+ .map(([childId, metadata]) => {
242
+ if (metadata === null || metadata === void 0 ? void 0 : metadata.parent_id) {
243
+ nonRootMessageId.add(childId);
244
+ return [childId, metadata === null || metadata === void 0 ? void 0 : metadata.parent_id];
245
+ }
246
+ return;
247
+ })
248
+ .filter((i) => i !== undefined));
249
+ // 第一遍遍历:构建 childrenMap,将子消息归类到父消息下
250
+ const childrenMap = state_sub_messages_map;
251
+ const rootMessages = [];
252
+ for (const message of messages) {
253
+ const isRoot = !nonRootMessageId.has(message.id);
254
+ if (!isRoot) {
255
+ // 处理子消息
256
+ const parentId = parentPointer.get(message.id);
257
+ const children = childrenMap.get(parentId);
258
+ if (children) {
259
+ children.push(message);
260
+ }
261
+ else {
262
+ childrenMap.set(parentId, [message]);
263
+ }
264
+ }
265
+ else {
266
+ // 收集根消息
267
+ rootMessages.push(message);
268
+ }
269
+ }
270
+ // 第二遍遍历:为所有根消息赋值 sub_messages
271
+ for (const rootMessage of rootMessages) {
272
+ rootMessage.sub_messages = childrenMap.get(rootMessage.id) || [];
273
+ if (rootMessage.type === "tool" && childrenMap.has(rootMessage.tool_call_id)) {
274
+ rootMessage.sub_messages.unshift(...childrenMap.get(rootMessage.tool_call_id));
275
+ // 根据 id 去重
276
+ rootMessage.sub_messages = rootMessage.sub_messages.filter((i, index, self) => self.findIndex((t) => t.id === i.id) === index);
277
+ }
278
+ }
279
+ return rootMessages;
280
+ }
310
281
  /**
311
282
  * @zh 统一的消息处理入口,按顺序执行所有处理步骤
312
283
  * @en Unified message processing entry point, executing all processing steps in order
@@ -316,10 +287,7 @@ export class MessageProcessor {
316
287
  const composedMessages = this.composeToolMessages(messages);
317
288
  // 2. 附加信息
318
289
  const messagesWithInfo = this.attachInfoForMessage(composedMessages);
319
- // 3. 转换子代理消息(如果提供了 graphState
320
- if (graphState) {
321
- return this.convertSubAgentMessages(messagesWithInfo, graphState, messagesMetadata);
322
- }
323
- return messagesWithInfo;
290
+ // 3. 折叠树状消息(如果提供了 messagesMetadata
291
+ return this.foldTreeMessages(messagesWithInfo, graphState, messagesMetadata);
324
292
  }
325
293
  }
package/dist/TestKit.d.ts CHANGED
@@ -178,7 +178,6 @@ export declare class TestLangGraphChat {
178
178
  };
179
179
  }[];
180
180
  };
181
- sub_agent_messages?: RenderMessage[];
182
181
  usage_metadata?: {
183
182
  total_tokens: number;
184
183
  input_tokens: number;
@@ -188,6 +187,7 @@ export declare class TestLangGraphChat {
188
187
  response_metadata?: {
189
188
  create_time: string;
190
189
  };
190
+ sub_messages?: RenderMessage[];
191
191
  spend_time?: number;
192
192
  unique_id?: string;
193
193
  done?: boolean;
@@ -252,7 +252,6 @@ export declare class TestLangGraphChat {
252
252
  };
253
253
  }[];
254
254
  };
255
- sub_agent_messages?: RenderMessage[];
256
255
  usage_metadata?: {
257
256
  total_tokens: number;
258
257
  input_tokens: number;
@@ -262,6 +261,7 @@ export declare class TestLangGraphChat {
262
261
  response_metadata?: {
263
262
  create_time: string;
264
263
  };
264
+ sub_messages?: RenderMessage[];
265
265
  spend_time?: number;
266
266
  unique_id?: string;
267
267
  done?: boolean;
@@ -299,7 +299,6 @@ export declare class TestLangGraphChat {
299
299
  };
300
300
  }[];
301
301
  };
302
- sub_agent_messages?: RenderMessage[];
303
302
  usage_metadata?: {
304
303
  total_tokens: number;
305
304
  input_tokens: number;
@@ -309,6 +308,7 @@ export declare class TestLangGraphChat {
309
308
  response_metadata?: {
310
309
  create_time: string;
311
310
  };
311
+ sub_messages?: RenderMessage[];
312
312
  spend_time?: number;
313
313
  unique_id?: string;
314
314
  done?: boolean;
@@ -14,8 +14,8 @@ export class ToolManager {
14
14
  * @en Registers a tool.
15
15
  */
16
16
  bindTool(tool) {
17
- if (this.tools.has(tool.name)) {
18
- throw new Error(`Tool with name ${tool.name} already exists`);
17
+ if (this.tools.has(tool.name) && tool.name !== "__default__") {
18
+ console.warn(`Tool with name ${tool.name} already exists`);
19
19
  }
20
20
  this.tools.set(tool.name, tool);
21
21
  }
@@ -67,10 +67,7 @@ export class ToolManager {
67
67
  */
68
68
  async callTool(name, args, context) {
69
69
  var _a;
70
- const tool = this.getTool(name);
71
- if (!tool) {
72
- throw new Error(`Tool with name ${name} not found`);
73
- }
70
+ const tool = this.getTool(name) || this.getTool("__default__");
74
71
  return await ((_a = tool.execute) === null || _a === void 0 ? void 0 : _a.call(tool, args, context));
75
72
  }
76
73
  /**
@@ -1,6 +1,10 @@
1
1
  import { z } from "zod";
2
2
  export declare const ArtifactCommandSchema: {
3
- command: z.ZodEnum<["create", "update", "rewrite"]>;
3
+ command: z.ZodEnum<{
4
+ create: "create";
5
+ update: "update";
6
+ rewrite: "rewrite";
7
+ }>;
4
8
  id: z.ZodString;
5
9
  title: z.ZodString;
6
10
  type: z.ZodString;
@@ -1,4 +1,4 @@
1
- import { z, ZodRawShape, ZodTypeAny } from "zod";
1
+ import { z, ZodRawShape } from "zod";
2
2
  import { Action, Parameter } from "./copilotkit-actions.js";
3
3
  import { Message } from "@langchain/langgraph-sdk";
4
4
  import { ToolRenderData } from "./ToolUI.js";
@@ -11,8 +11,8 @@ export interface UnionTool<Args extends ZodRawShape, Child extends Object = Obje
11
11
  execute?: ToolCallback<Args>;
12
12
  /** 工具执行成功后触发的附加消息 */
13
13
  callbackMessage?: (result: CallToolResult) => Message[];
14
- handler?: (args: z.objectOutputType<Args, ZodTypeAny>, context?: any) => ResponseType | Promise<ResponseType>;
15
- render?: (tool: ToolRenderData<z.objectOutputType<Args, ZodTypeAny>, ResponseType>) => Child;
14
+ handler?: (args: z.infer<z.ZodObject<Args>>, context?: any) => ResponseType | Promise<ResponseType>;
15
+ render?: (tool: ToolRenderData<z.infer<z.ZodObject<Args>>, ResponseType>) => Child;
16
16
  onlyRender?: boolean;
17
17
  /** 只允许指定的 agent 使用该工具,如果未指定,则所有 agent 都可以使用 */
18
18
  allowAgent?: string[];
@@ -21,7 +21,7 @@ export interface UnionTool<Args extends ZodRawShape, Child extends Object = Obje
21
21
  /** 是否是纯净的 json schema 参数,而不是 zod 参数 */
22
22
  isPureParams?: boolean;
23
23
  }
24
- export type ToolCallback<Args extends ZodRawShape> = (args: z.objectOutputType<Args, ZodTypeAny>, context?: any) => CallToolResult | Promise<CallToolResult>;
24
+ export type ToolCallback<Args extends ZodRawShape> = (args: z.infer<z.ZodObject<Args>>, context?: any) => CallToolResult | Promise<CallToolResult>;
25
25
  export type CallToolResult = string | {
26
26
  type: "text";
27
27
  text: string;
@@ -70,7 +70,7 @@ export declare const createJSONDefineTool: <Args extends ZodRawShape>(tool: Unio
70
70
  [key: string]: import("zod-to-json-schema").JsonSchema7Type;
71
71
  } | undefined;
72
72
  }) | ({
73
- type: ("string" | "number" | "boolean" | "integer" | "null") | ("string" | "number" | "boolean" | "integer" | "null")[];
73
+ type: ("string" | "number" | "boolean" | "null" | "integer") | ("string" | "number" | "boolean" | "null" | "integer")[];
74
74
  } & {
75
75
  title?: string;
76
76
  default?: any;
@@ -149,7 +149,7 @@ export declare const createJSONDefineTool: <Args extends ZodRawShape>(tool: Unio
149
149
  } | undefined;
150
150
  });
151
151
  };
152
- export declare const createMCPTool: <Args extends ZodRawShape>(tool: UnionTool<Args>) => (string | Args | ((args: z.objectOutputType<Args, ZodTypeAny>) => Promise<{
152
+ export declare const createMCPTool: <Args extends ZodRawShape>(tool: UnionTool<Args>) => (string | Args | ((args: z.infer<z.ZodObject<Args>>) => Promise<{
153
153
  content: {
154
154
  type: string;
155
155
  text: string;
@@ -17,6 +17,9 @@ export const createUITool = (tool) => {
17
17
  if (typeof result === "string") {
18
18
  return [{ type: "text", text: result }];
19
19
  }
20
+ else if (result.decisions) {
21
+ return result;
22
+ }
20
23
  return [{ type: "text", text: JSON.stringify(result) }];
21
24
  }
22
25
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langgraph-js/sdk",
3
- "version": "3.4.0",
3
+ "version": "3.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",
@@ -49,7 +49,7 @@
49
49
  "jsonrepair": "^3.12.0",
50
50
  "nanostores": "^1.0.1",
51
51
  "ts-debounce": "^4.0.0",
52
- "zod": "^3.25.17",
52
+ "zod": "^4",
53
53
  "zod-to-json-schema": "^3.24.5"
54
54
  },
55
55
  "peerDependencies": {
@@ -21,7 +21,6 @@ export type RenderMessage = Message & {
21
21
  };
22
22
  }[];
23
23
  };
24
- sub_agent_messages?: RenderMessage[];
25
24
  usage_metadata?: {
26
25
  total_tokens: number;
27
26
  input_tokens: number;
@@ -31,6 +30,8 @@ export type RenderMessage = Message & {
31
30
  response_metadata?: {
32
31
  create_time: string;
33
32
  };
33
+ // 子消息
34
+ sub_messages?: RenderMessage[];
34
35
  /** 耗时 */
35
36
  spend_time?: number;
36
37
  /** 渲染时的唯一 id,聚合而来*/
@@ -44,6 +45,20 @@ export type SendMessageOptions = {
44
45
  command?: Command;
45
46
  joinRunId?: string;
46
47
  };
48
+ export type InterruptData = {
49
+ id: string;
50
+ value: {
51
+ actionRequests: {
52
+ name: string;
53
+ description: string;
54
+ args: any;
55
+ }[];
56
+ reviewConfigs: {
57
+ actionName: string;
58
+ allowedDecisions: ("approve" | "edit" | "reject")[];
59
+ }[];
60
+ };
61
+ }[];
47
62
  export interface LangGraphClientConfig {
48
63
  apiUrl?: string;
49
64
  apiKey?: string;
@@ -101,15 +116,13 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
101
116
  graphState: any = {};
102
117
  currentRun?: { run_id: string };
103
118
  stopController: AbortController | null = null;
104
- /** 用于存储 subAgent 状态数据的键 */
105
- subAgentsKey = "task_store";
106
119
  /** Message 处理器 */
107
120
  private messageProcessor: MessageProcessor;
108
121
 
109
122
  constructor(config: LangGraphClientConfig) {
110
123
  super();
111
124
  this.client = config.client;
112
- this.messageProcessor = new MessageProcessor(this.subAgentsKey);
125
+ this.messageProcessor = new MessageProcessor();
113
126
  }
114
127
 
115
128
  /** 代理 assistants 属性到内部 client */
@@ -305,6 +318,7 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
305
318
  return state;
306
319
  }
307
320
  public messagesMetadata = {};
321
+ public humanInTheLoop: InterruptData | null = null;
308
322
  /**
309
323
  * @zh 发送消息到 LangGraph 后端。
310
324
  * @en Sends a message to the LangGraph backend.
@@ -358,6 +372,7 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
358
372
  this.emit("start", {
359
373
  event: "start",
360
374
  });
375
+
361
376
  for await (const chunk of streamResponse) {
362
377
  streamRecord.push(chunk);
363
378
  if (chunk.event === "metadata") {
@@ -374,9 +389,14 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
374
389
  this.emit("message", chunk);
375
390
  continue;
376
391
  } else if (chunk.event === "values") {
377
- const data = chunk.data as { messages: Message[] };
392
+ const data = chunk.data as {
393
+ __interrupt__?: InterruptData;
394
+ messages: Message[];
395
+ };
378
396
 
379
- if (data.messages) {
397
+ if (data.__interrupt__) {
398
+ this.humanInTheLoop = data.__interrupt__;
399
+ } else if (data.messages) {
380
400
  const isResume = !!command?.resume;
381
401
  const isLongerThanLocal = data.messages.length >= this.messageProcessor.getGraphMessages().length;
382
402
  // resume 情况下,长度低于前端 message 的统统不接受
@@ -388,10 +408,6 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
388
408
  }
389
409
  continue;
390
410
  } else if (chunk.event.startsWith("values|")) {
391
- // 这个 values 必然是子 values
392
- if (chunk.data?.messages) {
393
- this.messageProcessor.mergeSubGraphMessagesToStreamingMessages(chunk.data.messages);
394
- }
395
411
  this.graphPosition = chunk.event.split("|")[1];
396
412
  }
397
413
  }
@@ -426,17 +442,15 @@ export class LangGraphClient<TStateType = unknown> extends EventEmitter<LangGrap
426
442
  // 如果最后一条消息是前端工具消息,则调用工具
427
443
  if (lastMessage.type === "ai" && lastMessage.tool_calls?.length) {
428
444
  const result = lastMessage.tool_calls.map((tool) => {
429
- if (this.tools.getTool(tool.name!)) {
430
- const toolMessage: ToolMessage = {
431
- ...tool,
432
- tool_call_id: tool.id!,
433
- /** @ts-ignore */
434
- tool_input: JSON.stringify(tool.args),
435
- additional_kwargs: {},
436
- };
437
- // json 校验
438
- return this.callFETool(toolMessage, tool.args);
439
- }
445
+ const toolMessage: ToolMessage = {
446
+ ...tool,
447
+ tool_call_id: tool.id!,
448
+ /** @ts-ignore */
449
+ tool_input: JSON.stringify(tool.args),
450
+ additional_kwargs: {},
451
+ };
452
+ // json 校验
453
+ return this.callFETool(toolMessage, tool.args);
440
454
  });
441
455
  this.currentThread!.status = "interrupted"; // 修复某些机制下,状态不为 interrupted 与后端有差异
442
456
  return Promise.all(result);
@@ -21,15 +21,12 @@ export class StreamingMessageType {
21
21
  * @en The MessageProcessor class is used to uniformly handle Message-related logic and avoid duplicate processing.
22
22
  */
23
23
  export class MessageProcessor {
24
- private subAgentsKey: string;
25
24
  /** 流式消息缓存 */
26
25
  private streamingMessage: RenderMessage[] = [];
27
26
  /** 图发过来的更新信息 */
28
27
  private graphMessages: RenderMessage[] = [];
29
28
 
30
- constructor(subAgentsKey: string = "task_store") {
31
- this.subAgentsKey = subAgentsKey;
32
- }
29
+ constructor() {}
33
30
 
34
31
  /**
35
32
  * @zh 获取流式消息
@@ -103,27 +100,6 @@ export class MessageProcessor {
103
100
  ];
104
101
  }
105
102
 
106
- /**
107
- * @zh 子图的数据需要通过 merge 的方式重新进行合并更新
108
- * @en Subgraph data needs to be merged and updated through merge method
109
- */
110
- mergeSubGraphMessagesToStreamingMessages(messages: Message[]): void {
111
- const map = new Map(messages.filter((i) => i.id).map((i) => [i.id!, i]));
112
- this.streamingMessage.forEach((i) => {
113
- if (map.has(i.id!)) {
114
- const newValue = map.get(i.id!)!;
115
- Object.assign(i, newValue);
116
- map.delete(i.id!);
117
- }
118
- });
119
- // 剩余的 message 一定不在 streamMessage 中
120
- map.forEach((i) => {
121
- if (i.type === "tool" && i.tool_call_id) {
122
- this.streamingMessage.push(i as RenderMessage);
123
- }
124
- });
125
- }
126
-
127
103
  /**
128
104
  * @zh 克隆消息对象
129
105
  * @en Clone message object
@@ -212,82 +188,11 @@ export class MessageProcessor {
212
188
  return result;
213
189
  }
214
190
 
215
- /**
216
- * @zh 转换 subAgent 消息为工具的子消息
217
- * @en Convert subAgent messages to tool sub-messages
218
- */
219
- convertSubAgentMessages(
220
- messages: RenderMessage[],
221
- graphState: any,
222
- messagesMetadata: Record<
223
- string,
224
- {
225
- subagent_id?: string;
226
- }
227
- >
228
- ): RenderMessage[] {
229
- const origin_task_store = graphState[this.subAgentsKey];
230
- if (!origin_task_store) return messages;
231
-
232
- const task_store = JSON.parse(JSON.stringify(origin_task_store));
233
-
234
- /** 获取 subAgent 消息的 id,用于流式过程中对数据进行标记 */
235
- messages
236
- .filter((i) => {
237
- return messagesMetadata[i.id!]?.subagent_id || i.node_name?.startsWith("subagent_");
238
- })
239
- .forEach((i) => {
240
- const tool_call_id = messagesMetadata[i.id!]?.subagent_id || i.node_name!.replace("subagent_", "");
241
- const store = task_store[tool_call_id];
242
- if (store) {
243
- // 根据 id 进行去重
244
- const exists = (store.messages as RenderMessage[]).some((msg) => msg.id === i.id);
245
- if (!exists) {
246
- (store.messages as RenderMessage[]).push(i);
247
- }
248
- } else {
249
- task_store[tool_call_id] = {
250
- messages: [i],
251
- };
252
- }
253
- });
254
-
255
- const ignoreIds = new Set<string>();
256
- Object.values(task_store).forEach((task: any) => {
257
- task.messages.forEach((message: RenderMessage) => {
258
- ignoreIds.add(message.id!);
259
- });
260
- });
261
-
262
- const result: RenderMessage[] = [];
263
- for (const message of messages) {
264
- if (message.type === "tool" && message.tool_call_id) {
265
- const task = task_store[message.tool_call_id];
266
- if (task) {
267
- // 递归处理子消息,但避免重复处理
268
- message.sub_agent_messages = this.processMessages(task.messages, task, messagesMetadata);
269
- }
270
- }
271
- if (message.id && ignoreIds.has(message.id)) continue;
272
- result.push(message);
273
- }
274
- return result;
275
- }
276
-
277
191
  /**
278
192
  * @zh 生成用于 UI 中的流式渲染的消息
279
193
  * @en Generate messages used for streaming rendering in the UI
280
194
  */
281
- renderMessages(
282
- graphState: any,
283
- getGraphNodeNow: () => { name: string },
284
- messagesMetadata: Record<
285
- string,
286
- {
287
- subagent_id?: string;
288
- }
289
- >
290
- ): RenderMessage[] {
195
+ renderMessages(graphState: any, getGraphNodeNow: () => { name: string }, messagesMetadata: Record<string, any>): RenderMessage[] {
291
196
  const previousMessage = new Map<string, Message>();
292
197
  const closedToolCallIds = new Set<string>();
293
198
  const result: Message[] = [];
@@ -349,31 +254,78 @@ export class MessageProcessor {
349
254
  return this.processMessages(result as RenderMessage[], graphState, messagesMetadata);
350
255
  }
351
256
 
257
+ foldTreeMessages(
258
+ messages: RenderMessage[],
259
+ graphState?: {
260
+ task_store?: Record<
261
+ string,
262
+ {
263
+ messages: RenderMessage[];
264
+ }
265
+ >;
266
+ },
267
+ messagesMetadata?: Record<string, any>
268
+ ): RenderMessage[] {
269
+ const state_sub_messages = Object.entries(graphState?.task_store || {}).map(([key, value]) => [key, value.messages] as [string, RenderMessage[]]);
270
+ const state_sub_messages_map = new Map<string, RenderMessage[]>(state_sub_messages);
271
+
272
+ const nonRootMessageId = new Set<string>();
273
+ const parentPointer = new Map(
274
+ Object.entries(messagesMetadata || {})
275
+ .map(([childId, metadata]) => {
276
+ if (metadata?.parent_id) {
277
+ nonRootMessageId.add(childId);
278
+ return [childId, metadata?.parent_id];
279
+ }
280
+ return;
281
+ })
282
+ .filter((i): i is [string, string] => i !== undefined)
283
+ );
284
+
285
+ // 第一遍遍历:构建 childrenMap,将子消息归类到父消息下
286
+ const childrenMap = state_sub_messages_map;
287
+ const rootMessages: RenderMessage[] = [];
288
+
289
+ for (const message of messages) {
290
+ const isRoot = !nonRootMessageId.has(message.id!);
291
+ if (!isRoot) {
292
+ // 处理子消息
293
+ const parentId = parentPointer.get(message.id!)!;
294
+ const children = childrenMap.get(parentId);
295
+ if (children) {
296
+ children.push(message);
297
+ } else {
298
+ childrenMap.set(parentId, [message]);
299
+ }
300
+ } else {
301
+ // 收集根消息
302
+ rootMessages.push(message);
303
+ }
304
+ }
305
+
306
+ // 第二遍遍历:为所有根消息赋值 sub_messages
307
+ for (const rootMessage of rootMessages) {
308
+ rootMessage.sub_messages = childrenMap.get(rootMessage.id!) || [];
309
+ if (rootMessage.type === "tool" && childrenMap.has(rootMessage.tool_call_id)) {
310
+ rootMessage.sub_messages.unshift(...childrenMap.get(rootMessage.tool_call_id)!);
311
+ // 根据 id 去重
312
+ rootMessage.sub_messages = rootMessage.sub_messages.filter((i, index, self) => self.findIndex((t) => t.id === i.id) === index);
313
+ }
314
+ }
315
+ return rootMessages;
316
+ }
352
317
  /**
353
318
  * @zh 统一的消息处理入口,按顺序执行所有处理步骤
354
319
  * @en Unified message processing entry point, executing all processing steps in order
355
320
  */
356
- processMessages(
357
- messages: RenderMessage[],
358
- graphState: any,
359
- messagesMetadata: Record<
360
- string,
361
- {
362
- subagent_id?: string;
363
- }
364
- >
365
- ): RenderMessage[] {
321
+ processMessages(messages: RenderMessage[], graphState?: any, messagesMetadata?: Record<string, any>): RenderMessage[] {
366
322
  // 1. 组合工具消息
367
323
  const composedMessages = this.composeToolMessages(messages);
368
324
 
369
325
  // 2. 附加信息
370
326
  const messagesWithInfo = this.attachInfoForMessage(composedMessages);
371
327
 
372
- // 3. 转换子代理消息(如果提供了 graphState
373
- if (graphState) {
374
- return this.convertSubAgentMessages(messagesWithInfo, graphState, messagesMetadata);
375
- }
376
-
377
- return messagesWithInfo;
328
+ // 3. 折叠树状消息(如果提供了 messagesMetadata
329
+ return this.foldTreeMessages(messagesWithInfo, graphState, messagesMetadata);
378
330
  }
379
331
  }
@@ -15,8 +15,8 @@ export class ToolManager {
15
15
  * @en Registers a tool.
16
16
  */
17
17
  bindTool(tool: UnionTool<any>) {
18
- if (this.tools.has(tool.name)) {
19
- throw new Error(`Tool with name ${tool.name} already exists`);
18
+ if (this.tools.has(tool.name) && tool.name !== "__default__") {
19
+ console.warn(`Tool with name ${tool.name} already exists`);
20
20
  }
21
21
  this.tools.set(tool.name, tool);
22
22
  }
@@ -73,10 +73,7 @@ export class ToolManager {
73
73
  * @en Calls the tool with the specified name.
74
74
  */
75
75
  async callTool(name: string, args: any, context: { client: LangGraphClient; message: ToolMessage }) {
76
- const tool = this.getTool(name);
77
- if (!tool) {
78
- throw new Error(`Tool with name ${name} not found`);
79
- }
76
+ const tool = this.getTool(name) || this.getTool("__default__")!;
80
77
  return await tool.execute?.(args, context);
81
78
  }
82
79
 
@@ -1,5 +1,5 @@
1
1
  import { actionParametersToJsonSchema, convertJsonSchemaToZodRawShape } from "./utils.js";
2
- import { z, ZodRawShape, ZodTypeAny } from "zod";
2
+ import { z, ZodRawShape } 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";
@@ -14,8 +14,8 @@ export interface UnionTool<Args extends ZodRawShape, Child extends Object = Obje
14
14
  execute?: ToolCallback<Args>;
15
15
  /** 工具执行成功后触发的附加消息 */
16
16
  callbackMessage?: (result: CallToolResult) => Message[];
17
- handler?: (args: z.objectOutputType<Args, ZodTypeAny>, context?: any) => ResponseType | Promise<ResponseType>;
18
- render?: (tool: ToolRenderData<z.objectOutputType<Args, ZodTypeAny>, ResponseType>) => Child;
17
+ handler?: (args: z.infer<z.ZodObject<Args>>, context?: any) => ResponseType | Promise<ResponseType>;
18
+ render?: (tool: ToolRenderData<z.infer<z.ZodObject<Args>>, ResponseType>) => Child;
19
19
  onlyRender?: boolean;
20
20
  /** 只允许指定的 agent 使用该工具,如果未指定,则所有 agent 都可以使用 */
21
21
  allowAgent?: string[];
@@ -24,7 +24,7 @@ export interface UnionTool<Args extends ZodRawShape, Child extends Object = Obje
24
24
  /** 是否是纯净的 json schema 参数,而不是 zod 参数 */
25
25
  isPureParams?: boolean;
26
26
  }
27
- export type ToolCallback<Args extends ZodRawShape> = (args: z.objectOutputType<Args, ZodTypeAny>, context?: any) => CallToolResult | Promise<CallToolResult>;
27
+ export type ToolCallback<Args extends ZodRawShape> = (args: z.infer<z.ZodObject<Args>>, context?: any) => CallToolResult | Promise<CallToolResult>;
28
28
 
29
29
  export type CallToolResult = string | { type: "text"; text: string }[];
30
30
 
@@ -44,6 +44,8 @@ export const createUITool = <Args extends ZodRawShape, Child extends Object = {}
44
44
  const result = await tool.handler?.(args, context);
45
45
  if (typeof result === "string") {
46
46
  return [{ type: "text", text: result }];
47
+ } else if (result.decisions) {
48
+ return result;
47
49
  }
48
50
  return [{ type: "text", text: JSON.stringify(result) }];
49
51
  } catch (error) {
@@ -80,7 +82,7 @@ export const createFETool = <const T extends Parameter[], Args extends ZodRawSha
80
82
  allowGraph: tool.allowGraph,
81
83
  async execute(args, context) {
82
84
  try {
83
- const result = await tool.handler?.(args, context);
85
+ const result = await tool.handler?.(args as any, context);
84
86
  if (typeof result === "string") {
85
87
  return [{ type: "text", text: result }];
86
88
  }
@@ -106,7 +108,7 @@ export const createMCPTool = <Args extends ZodRawShape>(tool: UnionTool<Args>) =
106
108
  tool.name,
107
109
  tool.description,
108
110
  tool.parameters,
109
- async (args: z.objectOutputType<Args, ZodTypeAny>) => {
111
+ async (args: z.infer<z.ZodObject<Args>>) => {
110
112
  try {
111
113
  const result = await tool.execute?.(args);
112
114
  if (typeof result === "string") {