@langgraph-js/sdk 1.9.2 → 1.10.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.
@@ -0,0 +1,204 @@
1
+ import { RenderMessage } from "./LangGraphClient.js";
2
+ import { Message } from "@langchain/langgraph-sdk";
3
+ import { CallToolResult, UnionTool } from "./tool/createTool.js";
4
+ import { createChatStore } from "./ui-store/createChatStore.js";
5
+ /**
6
+ * @zh 测试任务接口
7
+ * @en Test task interface
8
+ */
9
+ interface TestTask {
10
+ /** 任务是否成功完成 */
11
+ success: boolean;
12
+ /** 执行任务的函数 */
13
+ runTask: (messages: readonly RenderMessage[]) => Promise<void>;
14
+ /** 任务失败时的回调函数 */
15
+ fail: () => void;
16
+ }
17
+ /**
18
+ * @zh LangGraph 测试工具,可以配合 vitest 等常用框架进行测试
19
+ * @en LangGraph test tool, can be used with vitest and other common frameworks for testing
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const testChat = new TestLangGraphChat(createLangGraphClient(), { debug: true });
24
+ * await testChat.humanInput("你好", async () => {
25
+ * const aiMessage = await testChat.waitFor("ai");
26
+ * expect(aiMessage.content).toBeDefined();
27
+ * });
28
+ * ```
29
+ */
30
+ export declare class TestLangGraphChat {
31
+ readonly store: ReturnType<typeof createChatStore>;
32
+ /** 是否开启调试模式 */
33
+ private debug;
34
+ /** 上次消息数量,用于检测消息变化 */
35
+ private lastLength;
36
+ /** 待处理的测试任务列表 */
37
+ protected processFunc: TestTask[];
38
+ /**
39
+ * @zh 构造函数,初始化测试环境
40
+ * @en Constructor, initialize test environment
41
+ */
42
+ constructor(store: ReturnType<typeof createChatStore>, options: {
43
+ debug?: boolean;
44
+ tools?: UnionTool<any, any, any>[];
45
+ });
46
+ /**
47
+ * @zh 获取当前所有渲染消息
48
+ * @en Get all current render messages
49
+ */
50
+ getMessages(): RenderMessage[];
51
+ /**
52
+ * @zh 添加工具到测试环境中,会自动包装工具的 execute 方法
53
+ * @en Add tools to test environment, automatically wraps tool execute methods
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const tools = [createUITool({ name: "test_tool", ... })];
58
+ * testChat.addTools(tools);
59
+ * ```
60
+ */
61
+ addTools(tools: UnionTool<any, any, any>[]): void;
62
+ /**
63
+ * @zh 检查所有待处理的测试任务,只有在消息数量发生变化时才执行检查
64
+ * @en Check all pending test tasks, only executes when message count changes
65
+ */
66
+ checkAllTask(messages: readonly RenderMessage[], options?: {
67
+ skipLengthCheck?: boolean;
68
+ }): void;
69
+ /**
70
+ * @zh 准备测试环境,初始化客户端连接
71
+ * @en Prepare test environment, initialize client connection
72
+ */
73
+ ready(): Promise<import("./LangGraphClient.js").LangGraphClient>;
74
+ /**
75
+ * @zh 模拟人类输入消息并等待测试任务完成,这是测试的核心方法
76
+ * @en Simulate human input and wait for test tasks to complete, this is the core test method
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * await testChat.humanInput("请帮我思考一下", async () => {
81
+ * const toolMessage = await testChat.waitFor("tool", "thinking");
82
+ * expect(toolMessage.tool_input).toBeDefined();
83
+ *
84
+ * const aiMessage = await testChat.waitFor("ai");
85
+ * expect(aiMessage.content).toContain("思考");
86
+ * });
87
+ * ```
88
+ */
89
+ humanInput(text: Message["content"], context: () => Promise<void>): Promise<[void, void]>;
90
+ /**
91
+ * @zh 等待特定类型的消息出现,创建异步等待任务
92
+ * @en Wait for specific type of message to appear, creates async waiting task
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * // 等待 AI 回复
97
+ * const aiMessage = await testChat.waitFor("ai");
98
+ *
99
+ * // 等待特定工具调用
100
+ * const toolMessage = await testChat.waitFor("tool", "sequential-thinking");
101
+ * ```
102
+ */
103
+ waitFor<D extends "tool" | "ai", T extends RenderMessage, N extends D extends "tool" ? string : undefined>(type: D, name?: N): Promise<T>;
104
+ /**
105
+ * @zh 响应前端工具调用,模拟用户对工具的响应
106
+ * @en Respond to frontend tool calls, simulates user response to tools
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * const toolMessage = await testChat.waitFor("tool", "ask_user_for_approve");
111
+ * await testChat.responseFeTool(toolMessage, "approved");
112
+ * ```
113
+ */
114
+ responseFeTool(message: RenderMessage, value: CallToolResult): Promise<RenderMessage>;
115
+ /**
116
+ * @zh 查找最后一条指定类型的消息,从消息数组末尾开始向前查找
117
+ * @en Find the last message of specified type, searches backwards from end of messages
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * // 查找最后一条 AI 消息
122
+ * const lastAI = testChat.findLast("ai");
123
+ *
124
+ * // 查找最后一条人类消息
125
+ * const lastHuman = testChat.findLast("human");
126
+ * ```
127
+ */
128
+ findLast(type: "human" | "ai" | "tool", options?: {
129
+ before?: (item: RenderMessage) => boolean;
130
+ }): (import("@langchain/langgraph-sdk").HumanMessage & {
131
+ name?: string;
132
+ node_name?: string;
133
+ tool_input?: string;
134
+ additional_kwargs?: {
135
+ done?: boolean;
136
+ tool_calls?: {
137
+ function: {
138
+ arguments: string;
139
+ };
140
+ }[];
141
+ };
142
+ usage_metadata?: {
143
+ total_tokens: number;
144
+ input_tokens: number;
145
+ output_tokens: number;
146
+ };
147
+ tool_call_id?: string;
148
+ response_metadata?: {
149
+ create_time: string;
150
+ };
151
+ spend_time?: number;
152
+ unique_id?: string;
153
+ done?: boolean;
154
+ }) | (import("@langchain/langgraph-sdk").AIMessage & {
155
+ name?: string;
156
+ node_name?: string;
157
+ tool_input?: string;
158
+ additional_kwargs?: {
159
+ done?: boolean;
160
+ tool_calls?: {
161
+ function: {
162
+ arguments: string;
163
+ };
164
+ }[];
165
+ };
166
+ usage_metadata?: {
167
+ total_tokens: number;
168
+ input_tokens: number;
169
+ output_tokens: number;
170
+ };
171
+ tool_call_id?: string;
172
+ response_metadata?: {
173
+ create_time: string;
174
+ };
175
+ spend_time?: number;
176
+ unique_id?: string;
177
+ done?: boolean;
178
+ }) | (import("@langchain/langgraph-sdk").ToolMessage & {
179
+ name?: string;
180
+ node_name?: string;
181
+ tool_input?: string;
182
+ additional_kwargs?: {
183
+ done?: boolean;
184
+ tool_calls?: {
185
+ function: {
186
+ arguments: string;
187
+ };
188
+ }[];
189
+ };
190
+ usage_metadata?: {
191
+ total_tokens: number;
192
+ input_tokens: number;
193
+ output_tokens: number;
194
+ };
195
+ tool_call_id?: string;
196
+ response_metadata?: {
197
+ create_time: string;
198
+ };
199
+ spend_time?: number;
200
+ unique_id?: string;
201
+ done?: boolean;
202
+ });
203
+ }
204
+ export {};
@@ -0,0 +1,226 @@
1
+ import { ToolRenderData } from "./tool/ToolUI.js";
2
+ /**
3
+ * @zh LangGraph 测试工具,可以配合 vitest 等常用框架进行测试
4
+ * @en LangGraph test tool, can be used with vitest and other common frameworks for testing
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * const testChat = new TestLangGraphChat(createLangGraphClient(), { debug: true });
9
+ * await testChat.humanInput("你好", async () => {
10
+ * const aiMessage = await testChat.waitFor("ai");
11
+ * expect(aiMessage.content).toBeDefined();
12
+ * });
13
+ * ```
14
+ */
15
+ export class TestLangGraphChat {
16
+ /**
17
+ * @zh 构造函数,初始化测试环境
18
+ * @en Constructor, initialize test environment
19
+ */
20
+ constructor(store, options) {
21
+ var _a;
22
+ this.store = store;
23
+ /** 是否开启调试模式 */
24
+ this.debug = false;
25
+ /** 上次消息数量,用于检测消息变化 */
26
+ this.lastLength = 0;
27
+ /** 待处理的测试任务列表 */
28
+ this.processFunc = [];
29
+ this.debug = (_a = options.debug) !== null && _a !== void 0 ? _a : false;
30
+ options.tools && this.addTools(options.tools);
31
+ const renderMessages = this.store.data.renderMessages;
32
+ // 订阅消息变化,自动检查任务完成状态
33
+ renderMessages.subscribe((messages) => {
34
+ this.checkAllTask(messages);
35
+ });
36
+ }
37
+ /**
38
+ * @zh 获取当前所有渲染消息
39
+ * @en Get all current render messages
40
+ */
41
+ getMessages() {
42
+ return this.store.data.renderMessages.get();
43
+ }
44
+ /**
45
+ * @zh 添加工具到测试环境中,会自动包装工具的 execute 方法
46
+ * @en Add tools to test environment, automatically wraps tool execute methods
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const tools = [createUITool({ name: "test_tool", ... })];
51
+ * testChat.addTools(tools);
52
+ * ```
53
+ */
54
+ addTools(tools) {
55
+ tools.forEach((tool) => {
56
+ if (tool.execute) {
57
+ const oldExecute = tool.execute;
58
+ // 包装原始的 execute 方法,在执行后触发任务检查
59
+ tool.execute = (...args) => {
60
+ setTimeout(() => {
61
+ this.checkAllTask(this.getMessages(), {
62
+ skipLengthCheck: true,
63
+ });
64
+ }, 100);
65
+ return oldExecute(...args);
66
+ };
67
+ }
68
+ });
69
+ this.store.mutations.setTools(tools);
70
+ }
71
+ /**
72
+ * @zh 检查所有待处理的测试任务,只有在消息数量发生变化时才执行检查
73
+ * @en Check all pending test tasks, only executes when message count changes
74
+ */
75
+ checkAllTask(messages, options = {}) {
76
+ // 只有 lastLength 发生变化时,才执行检查
77
+ if (!options.skipLengthCheck && this.lastLength === messages.length) {
78
+ return;
79
+ }
80
+ this.lastLength = messages.length;
81
+ // 执行所有未完成的任务
82
+ for (const task of this.processFunc) {
83
+ !task.success && task.runTask(options.skipLengthCheck ? messages : messages.slice(0, -1));
84
+ }
85
+ // 调试模式下打印最新消息
86
+ if (this.debug) {
87
+ console.log(messages[messages.length - (options.skipLengthCheck ? 1 : 2)]);
88
+ }
89
+ }
90
+ /**
91
+ * @zh 准备测试环境,初始化客户端连接
92
+ * @en Prepare test environment, initialize client connection
93
+ */
94
+ ready() {
95
+ return this.store.mutations.initClient();
96
+ }
97
+ /**
98
+ * @zh 模拟人类输入消息并等待测试任务完成,这是测试的核心方法
99
+ * @en Simulate human input and wait for test tasks to complete, this is the core test method
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * await testChat.humanInput("请帮我思考一下", async () => {
104
+ * const toolMessage = await testChat.waitFor("tool", "thinking");
105
+ * expect(toolMessage.tool_input).toBeDefined();
106
+ *
107
+ * const aiMessage = await testChat.waitFor("ai");
108
+ * expect(aiMessage.content).toContain("思考");
109
+ * });
110
+ * ```
111
+ */
112
+ async humanInput(text, context) {
113
+ await this.ready();
114
+ // console.log(text);
115
+ return Promise.all([
116
+ context(),
117
+ this.store.mutations
118
+ .sendMessage([
119
+ {
120
+ type: "human",
121
+ content: text,
122
+ },
123
+ ])
124
+ .then(() => {
125
+ this.checkAllTask(this.getMessages(), {
126
+ skipLengthCheck: true,
127
+ });
128
+ })
129
+ .then(async (res) => {
130
+ // 检查是否还有未完成的任务
131
+ const tasks = this.processFunc.filter((i) => {
132
+ return !i.success;
133
+ });
134
+ if (tasks.length) {
135
+ console.warn("still have ", tasks.length, " tasks");
136
+ await Promise.all(tasks.map((i) => i.fail()));
137
+ throw new Error("test task failed");
138
+ }
139
+ this.processFunc = [];
140
+ return res;
141
+ }),
142
+ ]);
143
+ }
144
+ /**
145
+ * @zh 等待特定类型的消息出现,创建异步等待任务
146
+ * @en Wait for specific type of message to appear, creates async waiting task
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * // 等待 AI 回复
151
+ * const aiMessage = await testChat.waitFor("ai");
152
+ *
153
+ * // 等待特定工具调用
154
+ * const toolMessage = await testChat.waitFor("tool", "sequential-thinking");
155
+ * ```
156
+ */
157
+ waitFor(type, name) {
158
+ return new Promise((resolve, reject) => {
159
+ this.processFunc.push({
160
+ success: false,
161
+ async runTask(messages) {
162
+ const lastMessage = messages[messages.length - 1];
163
+ if (!lastMessage) {
164
+ return;
165
+ }
166
+ // 检查消息类型和名称是否匹配
167
+ if (lastMessage.type === type && (name ? lastMessage.name === name : true)) {
168
+ resolve(lastMessage);
169
+ this.success = true;
170
+ }
171
+ },
172
+ fail() {
173
+ reject(new Error(`wait for ${type} ${name} failed`));
174
+ },
175
+ });
176
+ });
177
+ }
178
+ /**
179
+ * @zh 响应前端工具调用,模拟用户对工具的响应
180
+ * @en Respond to frontend tool calls, simulates user response to tools
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * const toolMessage = await testChat.waitFor("tool", "ask_user_for_approve");
185
+ * await testChat.responseFeTool(toolMessage, "approved");
186
+ * ```
187
+ */
188
+ async responseFeTool(message, value) {
189
+ if (message.content) {
190
+ throw new Error(`message is Done. content: ${message.content}`);
191
+ }
192
+ const tool = new ToolRenderData(message, this.store.data.client.get());
193
+ tool.response(value);
194
+ const messages = await this.waitFor("tool", message.name);
195
+ if (messages.content) {
196
+ return messages;
197
+ }
198
+ throw new Error("tool response failed");
199
+ }
200
+ /**
201
+ * @zh 查找最后一条指定类型的消息,从消息数组末尾开始向前查找
202
+ * @en Find the last message of specified type, searches backwards from end of messages
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * // 查找最后一条 AI 消息
207
+ * const lastAI = testChat.findLast("ai");
208
+ *
209
+ * // 查找最后一条人类消息
210
+ * const lastHuman = testChat.findLast("human");
211
+ * ```
212
+ */
213
+ findLast(type, options = {}) {
214
+ const messages = this.getMessages();
215
+ for (let i = messages.length - 1; i >= 0; i--) {
216
+ const item = messages[i];
217
+ if (type === item.type) {
218
+ return item;
219
+ }
220
+ if (options.before && options.before(item)) {
221
+ throw new Error(`${type} not found; before specified`);
222
+ }
223
+ }
224
+ throw new Error(`${type} not found `);
225
+ }
226
+ }
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export * from "./tool/index.js";
3
3
  export * from "@langchain/langgraph-sdk";
4
4
  export * from "./ui-store/index.js";
5
5
  export * from "./ToolManager.js";
6
+ export * from "./TestKit.js";
package/dist/index.js CHANGED
@@ -3,3 +3,4 @@ export * from "./tool/index.js";
3
3
  export * from "@langchain/langgraph-sdk";
4
4
  export * from "./ui-store/index.js";
5
5
  export * from "./ToolManager.js";
6
+ export * from "./TestKit.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langgraph-js/sdk",
3
- "version": "1.9.2",
3
+ "version": "1.10.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",
package/src/TestKit.ts ADDED
@@ -0,0 +1,262 @@
1
+ import { RenderMessage } from "./LangGraphClient.js";
2
+ import { Message } from "@langchain/langgraph-sdk";
3
+ import { CallToolResult, UnionTool } from "./tool/createTool.js";
4
+ import { ToolRenderData } from "./tool/ToolUI.js";
5
+ import { createChatStore } from "./ui-store/createChatStore.js";
6
+
7
+ /**
8
+ * @zh 测试任务接口
9
+ * @en Test task interface
10
+ */
11
+ interface TestTask {
12
+ /** 任务是否成功完成 */
13
+ success: boolean;
14
+ /** 执行任务的函数 */
15
+ runTask: (messages: readonly RenderMessage[]) => Promise<void>;
16
+ /** 任务失败时的回调函数 */
17
+ fail: () => void;
18
+ }
19
+
20
+ /**
21
+ * @zh LangGraph 测试工具,可以配合 vitest 等常用框架进行测试
22
+ * @en LangGraph test tool, can be used with vitest and other common frameworks for testing
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const testChat = new TestLangGraphChat(createLangGraphClient(), { debug: true });
27
+ * await testChat.humanInput("你好", async () => {
28
+ * const aiMessage = await testChat.waitFor("ai");
29
+ * expect(aiMessage.content).toBeDefined();
30
+ * });
31
+ * ```
32
+ */
33
+ export class TestLangGraphChat {
34
+ /** 是否开启调试模式 */
35
+ private debug = false;
36
+ /** 上次消息数量,用于检测消息变化 */
37
+ private lastLength = 0;
38
+ /** 待处理的测试任务列表 */
39
+ protected processFunc: TestTask[] = [];
40
+
41
+ /**
42
+ * @zh 构造函数,初始化测试环境
43
+ * @en Constructor, initialize test environment
44
+ */
45
+ constructor(
46
+ readonly store: ReturnType<typeof createChatStore>,
47
+ options: {
48
+ debug?: boolean;
49
+ tools?: UnionTool<any, any, any>[];
50
+ }
51
+ ) {
52
+ this.debug = options.debug ?? false;
53
+ options.tools && this.addTools(options.tools);
54
+ const renderMessages = this.store.data.renderMessages;
55
+
56
+ // 订阅消息变化,自动检查任务完成状态
57
+ renderMessages.subscribe((messages) => {
58
+ this.checkAllTask(messages);
59
+ });
60
+ }
61
+
62
+ /**
63
+ * @zh 获取当前所有渲染消息
64
+ * @en Get all current render messages
65
+ */
66
+ getMessages() {
67
+ return this.store.data.renderMessages.get();
68
+ }
69
+
70
+ /**
71
+ * @zh 添加工具到测试环境中,会自动包装工具的 execute 方法
72
+ * @en Add tools to test environment, automatically wraps tool execute methods
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const tools = [createUITool({ name: "test_tool", ... })];
77
+ * testChat.addTools(tools);
78
+ * ```
79
+ */
80
+ addTools(tools: UnionTool<any, any, any>[]) {
81
+ tools.forEach((tool) => {
82
+ if (tool.execute) {
83
+ const oldExecute = tool.execute;
84
+ // 包装原始的 execute 方法,在执行后触发任务检查
85
+ tool.execute = (...args) => {
86
+ setTimeout(() => {
87
+ this.checkAllTask(this.getMessages(), {
88
+ skipLengthCheck: true,
89
+ });
90
+ }, 100);
91
+ return oldExecute!(...args);
92
+ };
93
+ }
94
+ });
95
+ this.store.mutations.setTools(tools);
96
+ }
97
+
98
+ /**
99
+ * @zh 检查所有待处理的测试任务,只有在消息数量发生变化时才执行检查
100
+ * @en Check all pending test tasks, only executes when message count changes
101
+ */
102
+ checkAllTask(messages: readonly RenderMessage[], options: { skipLengthCheck?: boolean } = {}) {
103
+ // 只有 lastLength 发生变化时,才执行检查
104
+ if (!options.skipLengthCheck && this.lastLength === messages.length) {
105
+ return;
106
+ }
107
+ this.lastLength = messages.length;
108
+
109
+ // 执行所有未完成的任务
110
+ for (const task of this.processFunc) {
111
+ !task.success && task.runTask(options.skipLengthCheck ? messages : messages.slice(0, -1));
112
+ }
113
+
114
+ // 调试模式下打印最新消息
115
+ if (this.debug) {
116
+ console.log(messages[messages.length - (options.skipLengthCheck ? 1 : 2)]);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * @zh 准备测试环境,初始化客户端连接
122
+ * @en Prepare test environment, initialize client connection
123
+ */
124
+ ready() {
125
+ return this.store.mutations.initClient();
126
+ }
127
+
128
+ /**
129
+ * @zh 模拟人类输入消息并等待测试任务完成,这是测试的核心方法
130
+ * @en Simulate human input and wait for test tasks to complete, this is the core test method
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * await testChat.humanInput("请帮我思考一下", async () => {
135
+ * const toolMessage = await testChat.waitFor("tool", "thinking");
136
+ * expect(toolMessage.tool_input).toBeDefined();
137
+ *
138
+ * const aiMessage = await testChat.waitFor("ai");
139
+ * expect(aiMessage.content).toContain("思考");
140
+ * });
141
+ * ```
142
+ */
143
+ async humanInput(text: Message["content"], context: () => Promise<void>) {
144
+ await this.ready();
145
+ // console.log(text);
146
+ return Promise.all([
147
+ context(),
148
+ this.store.mutations
149
+ .sendMessage([
150
+ {
151
+ type: "human",
152
+ content: text,
153
+ },
154
+ ])
155
+ .then(() => {
156
+ this.checkAllTask(this.getMessages(), {
157
+ skipLengthCheck: true,
158
+ });
159
+ })
160
+ .then(async (res) => {
161
+ // 检查是否还有未完成的任务
162
+ const tasks = this.processFunc.filter((i) => {
163
+ return !i.success;
164
+ });
165
+ if (tasks.length) {
166
+ console.warn("still have ", tasks.length, " tasks");
167
+ await Promise.all(tasks.map((i) => i.fail()));
168
+ throw new Error("test task failed");
169
+ }
170
+ this.processFunc = [];
171
+ return res;
172
+ }),
173
+ ]);
174
+ }
175
+
176
+ /**
177
+ * @zh 等待特定类型的消息出现,创建异步等待任务
178
+ * @en Wait for specific type of message to appear, creates async waiting task
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * // 等待 AI 回复
183
+ * const aiMessage = await testChat.waitFor("ai");
184
+ *
185
+ * // 等待特定工具调用
186
+ * const toolMessage = await testChat.waitFor("tool", "sequential-thinking");
187
+ * ```
188
+ */
189
+ waitFor<D extends "tool" | "ai", T extends RenderMessage, N extends D extends "tool" ? string : undefined>(type: D, name?: N): Promise<T> {
190
+ return new Promise((resolve, reject) => {
191
+ this.processFunc.push({
192
+ success: false,
193
+ async runTask(messages) {
194
+ const lastMessage = messages[messages.length - 1];
195
+ if (!lastMessage) {
196
+ return;
197
+ }
198
+ // 检查消息类型和名称是否匹配
199
+ if (lastMessage.type === type && (name ? lastMessage.name === name : true)) {
200
+ resolve(lastMessage as T);
201
+ this.success = true;
202
+ }
203
+ },
204
+ fail() {
205
+ reject(new Error(`wait for ${type} ${name} failed`));
206
+ },
207
+ });
208
+ });
209
+ }
210
+
211
+ /**
212
+ * @zh 响应前端工具调用,模拟用户对工具的响应
213
+ * @en Respond to frontend tool calls, simulates user response to tools
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * const toolMessage = await testChat.waitFor("tool", "ask_user_for_approve");
218
+ * await testChat.responseFeTool(toolMessage, "approved");
219
+ * ```
220
+ */
221
+ async responseFeTool(message: RenderMessage, value: CallToolResult) {
222
+ if (message.content) {
223
+ throw new Error(`message is Done. content: ${message.content}`);
224
+ }
225
+ const tool = new ToolRenderData(message, this.store.data.client.get()!);
226
+ tool.response(value);
227
+ const messages = await this.waitFor("tool", message.name!);
228
+
229
+ if (messages.content) {
230
+ return messages;
231
+ }
232
+ throw new Error("tool response failed");
233
+ }
234
+
235
+ /**
236
+ * @zh 查找最后一条指定类型的消息,从消息数组末尾开始向前查找
237
+ * @en Find the last message of specified type, searches backwards from end of messages
238
+ *
239
+ * @example
240
+ * ```typescript
241
+ * // 查找最后一条 AI 消息
242
+ * const lastAI = testChat.findLast("ai");
243
+ *
244
+ * // 查找最后一条人类消息
245
+ * const lastHuman = testChat.findLast("human");
246
+ * ```
247
+ */
248
+ findLast(type: "human" | "ai" | "tool", options: { before?: (item: RenderMessage) => boolean } = {}) {
249
+ const messages = this.getMessages();
250
+
251
+ for (let i = messages.length - 1; i >= 0; i--) {
252
+ const item = messages[i];
253
+ if (type === item.type) {
254
+ return item;
255
+ }
256
+ if (options.before && options.before(item)) {
257
+ throw new Error(`${type} not found; before specified`);
258
+ }
259
+ }
260
+ throw new Error(`${type} not found `);
261
+ }
262
+ }
package/src/index.ts CHANGED
@@ -3,3 +3,4 @@ export * from "./tool/index.js";
3
3
  export * from "@langchain/langgraph-sdk";
4
4
  export * from "./ui-store/index.js";
5
5
  export * from "./ToolManager.js";
6
+ export * from "./TestKit.js";