@lobehub/lobehub 2.0.0-next.374 → 2.0.0-next.375

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 (27) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/locales/en-US/chat.json +13 -13
  4. package/locales/zh-CN/chat.json +14 -14
  5. package/package.json +1 -1
  6. package/packages/builtin-tool-group-agent-builder/src/ExecutionRuntime/index.ts +31 -9
  7. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/InviteAgent/index.tsx +28 -12
  8. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/RemoveAgent/index.tsx +28 -12
  9. package/packages/builtin-tool-group-agent-builder/src/types.ts +8 -0
  10. package/packages/context-engine/src/engine/tools/ToolsEngine.ts +16 -10
  11. package/packages/context-engine/src/engine/tools/__tests__/ToolsEngine.test.ts +142 -0
  12. package/packages/context-engine/src/engine/tools/types.ts +6 -0
  13. package/src/features/Conversation/ChatItem/ChatItem.tsx +4 -7
  14. package/src/features/Conversation/ChatItem/style.ts +0 -3
  15. package/src/features/Conversation/ChatItem/type.ts +4 -1
  16. package/src/features/Conversation/Messages/Assistant/index.tsx +6 -2
  17. package/src/features/Conversation/Messages/AssistantGroup/index.tsx +3 -3
  18. package/src/features/Conversation/Messages/Supervisor/index.tsx +6 -2
  19. package/src/features/Conversation/Messages/Task/index.tsx +6 -2
  20. package/src/features/Conversation/Messages/components/useNewScreen.test.ts +331 -0
  21. package/src/features/Conversation/Messages/components/useNewScreen.ts +80 -5
  22. package/src/locales/default/chat.ts +13 -13
  23. package/src/services/chat/chat.test.ts +125 -45
  24. package/src/services/chat/index.ts +10 -14
  25. package/src/services/chat/mecha/agentConfigResolver.ts +8 -0
  26. package/src/store/chat/slices/aiChat/actions/__tests__/streamingExecutor.test.ts +29 -0
  27. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +27 -19
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.375](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.374...v2.0.0-next.375)
6
+
7
+ <sup>Released on **2026-01-25**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Broadcast tools calling and improve auto scroll.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Broadcast tools calling and improve auto scroll, closes [#11804](https://github.com/lobehub/lobe-chat/issues/11804) ([c352915](https://github.com/lobehub/lobe-chat/commit/c352915))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.374](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.373...v2.0.0-next.374)
6
31
 
7
32
  <sup>Released on **2026-01-25**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Broadcast tools calling and improve auto scroll."
6
+ ]
7
+ },
8
+ "date": "2026-01-25",
9
+ "version": "2.0.0-next.375"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "features": [
@@ -36,7 +36,7 @@
36
36
  "compression.summary": "Summary",
37
37
  "confirmClearCurrentMessages": "You are about to clear the current session messages. Once cleared, they cannot be retrieved. Please confirm your action.",
38
38
  "confirmRemoveChatGroupItemAlert": "This Group will be deleted. Group-specific assistants will also be deleted, while external assistants will not be affected.",
39
- "confirmRemoveGroupItemAlert": "You are about to delete this group. After deletion, its agents will be moved to the default list. Please confirm your action.",
39
+ "confirmRemoveGroupItemAlert": "You are about to delete this category. After deletion, its agents will be moved to the default list. Please confirm your action.",
40
40
  "confirmRemoveGroupSuccess": "Group deleted successfully",
41
41
  "confirmRemoveSessionItemAlert": "You are about to delete this agent. Once deleted, it cannot be retrieved. Please confirm your action.",
42
42
  "confirmRemoveSessionSuccess": "Agent removed successfully",
@@ -239,28 +239,28 @@
239
239
  "searchAgents": "Search agents...",
240
240
  "selectedAgents": "Selected agents",
241
241
  "sendPlaceholder": "Ask, create, or start a task, <hotkey><hotkey/>",
242
- "sessionGroup.config": "Group Management",
243
- "sessionGroup.confirmRemoveGroupAlert": "This group is about to be deleted. After deletion, the agents in this group will be moved to the default list. Please confirm your operation.",
242
+ "sessionGroup.config": "Category Management",
243
+ "sessionGroup.confirmRemoveGroupAlert": "This category is about to be deleted. After deletion, the agents in this category will be moved to the default list. Please confirm your operation.",
244
244
  "sessionGroup.createAgentSuccess": "Agent created successfully",
245
- "sessionGroup.createGroup": "Add New Group",
246
- "sessionGroup.createGroupFailed": "Failed to create group chat",
247
- "sessionGroup.createGroupSuccess": "Group chat created successfully",
245
+ "sessionGroup.createGroup": "Add New Category",
246
+ "sessionGroup.createGroupFailed": "Failed to create category",
247
+ "sessionGroup.createGroupSuccess": "Category created successfully",
248
248
  "sessionGroup.createSuccess": "Created successfully",
249
249
  "sessionGroup.creatingAgent": "Creating agent...",
250
- "sessionGroup.groupName": "Group Name",
251
- "sessionGroup.inputPlaceholder": "Please enter group name...",
252
- "sessionGroup.moveGroup": "Move to Group",
253
- "sessionGroup.newGroup": "New Group",
250
+ "sessionGroup.groupName": "Category Name",
251
+ "sessionGroup.inputPlaceholder": "Please enter category name...",
252
+ "sessionGroup.moveGroup": "Move to Category",
253
+ "sessionGroup.newGroup": "New Category",
254
254
  "sessionGroup.noAvailableAgents": "No available agents",
255
255
  "sessionGroup.noMatchingAgents": "No matching agents found",
256
256
  "sessionGroup.noSelectedAgents": "Please select agents",
257
- "sessionGroup.rename": "Rename Group",
257
+ "sessionGroup.rename": "Rename Category",
258
258
  "sessionGroup.renameSuccess": "Renamed successfully",
259
259
  "sessionGroup.searchAgents": "Search agents",
260
260
  "sessionGroup.selectedAgents": "Selected agents ({{count}})",
261
261
  "sessionGroup.sortSuccess": "Reorder successful",
262
- "sessionGroup.sorting": "Group sorting updating...",
263
- "sessionGroup.tooLong": "Group name length should be between 1-20",
262
+ "sessionGroup.sorting": "Category sorting updating...",
263
+ "sessionGroup.tooLong": "Category name length should be between 1-20",
264
264
  "shareModal.copy": "Copy",
265
265
  "shareModal.copyLink": "Copy Link",
266
266
  "shareModal.copyLinkSuccess": "Link copied",
@@ -36,7 +36,7 @@
36
36
  "compression.summary": "摘要",
37
37
  "confirmClearCurrentMessages": "确认清空当前会话消息吗?清空后无法恢复",
38
38
  "confirmRemoveChatGroupItemAlert": "确认删除该群组吗?群组专有助理将会被同样删除,外部助理不受影响",
39
- "confirmRemoveGroupItemAlert": "确认删除该分组吗?分组内的助理会移回默认列表",
39
+ "confirmRemoveGroupItemAlert": "确认删除该分类吗?分类内的助理会移回默认列表",
40
40
  "confirmRemoveGroupSuccess": "群组已删除",
41
41
  "confirmRemoveSessionItemAlert": "确认删除该助理吗?删除后无法恢复",
42
42
  "confirmRemoveSessionSuccess": "助理已删除",
@@ -239,28 +239,28 @@
239
239
  "searchAgents": "搜索助理…",
240
240
  "selectedAgents": "已选助理",
241
241
  "sendPlaceholder": "从任何想法开始… <hotkey><hotkey/>",
242
- "sessionGroup.config": "分组管理",
243
- "sessionGroup.confirmRemoveGroupAlert": "确认删除该分组吗?分组内的助理会移回默认列表",
242
+ "sessionGroup.config": "分类管理",
243
+ "sessionGroup.confirmRemoveGroupAlert": "确认删除该分类吗?分类内的助理会移回默认列表",
244
244
  "sessionGroup.createAgentSuccess": "助理创建成功",
245
- "sessionGroup.createGroup": "添加新分组",
246
- "sessionGroup.createGroupFailed": "群组创建失败",
247
- "sessionGroup.createGroupSuccess": "群组创建成功",
248
- "sessionGroup.createSuccess": "分组创建成功",
245
+ "sessionGroup.createGroup": "添加新分类",
246
+ "sessionGroup.createGroupFailed": "分类创建失败",
247
+ "sessionGroup.createGroupSuccess": "分类创建成功",
248
+ "sessionGroup.createSuccess": "分类创建成功",
249
249
  "sessionGroup.creatingAgent": "正在创建助理…",
250
- "sessionGroup.groupName": "分组名称",
251
- "sessionGroup.inputPlaceholder": "请输入分组名称…",
252
- "sessionGroup.moveGroup": "移动到分组",
253
- "sessionGroup.newGroup": "新分组",
250
+ "sessionGroup.groupName": "分类名称",
251
+ "sessionGroup.inputPlaceholder": "请输入分类名称…",
252
+ "sessionGroup.moveGroup": "移动到分类",
253
+ "sessionGroup.newGroup": "新分类",
254
254
  "sessionGroup.noAvailableAgents": "暂无可用助理",
255
255
  "sessionGroup.noMatchingAgents": "未找到匹配的助理",
256
256
  "sessionGroup.noSelectedAgents": "请选择助理",
257
- "sessionGroup.rename": "重命名分组",
257
+ "sessionGroup.rename": "重命名分类",
258
258
  "sessionGroup.renameSuccess": "重命名成功",
259
259
  "sessionGroup.searchAgents": "搜索助理",
260
260
  "sessionGroup.selectedAgents": "已选助理({{count}})",
261
261
  "sessionGroup.sortSuccess": "排序已更新",
262
- "sessionGroup.sorting": "正在更新排序…",
263
- "sessionGroup.tooLong": "分组名称长度需为 1–20 个字符",
262
+ "sessionGroup.sorting": "正在更新分类排序…",
263
+ "sessionGroup.tooLong": "分类名称长度需为 1–20 个字符",
264
264
  "shareModal.copy": "复制",
265
265
  "shareModal.copyLink": "复制链接",
266
266
  "shareModal.copyLinkSuccess": "链接已复制",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.374",
3
+ "version": "2.0.0-next.375",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -256,13 +256,15 @@ export class GroupAgentBuilderExecutionRuntime {
256
256
 
257
257
  // Check if agent is already in the group
258
258
  const existingAgents = group.agents || [];
259
- const isAlreadyInGroup = existingAgents.some((a) => a.id === args.agentId);
259
+ const existingAgent = existingAgents.find((a) => a.id === args.agentId);
260
260
 
261
- if (isAlreadyInGroup) {
261
+ if (existingAgent) {
262
262
  return {
263
- content: `Agent ${args.agentId} is already in the group`,
263
+ content: `Agent ${existingAgent.title || args.agentId} is already in the group`,
264
264
  state: {
265
+ agentAvatar: existingAgent.avatar,
265
266
  agentId: args.agentId,
267
+ agentName: existingAgent.title,
266
268
  success: false,
267
269
  } as InviteAgentState,
268
270
  success: false,
@@ -277,12 +279,22 @@ export class GroupAgentBuilderExecutionRuntime {
277
279
 
278
280
  const wasAdded = result.added.length > 0;
279
281
 
282
+ // Get the agent info from the updated group
283
+ const updatedGroup = agentGroupSelectors.getGroupById(groupId)(getChatGroupStoreState());
284
+ const addedAgent = updatedGroup?.agents?.find((a) => a.id === args.agentId);
285
+ const agentName = addedAgent?.title;
286
+ const agentAvatar = addedAgent?.avatar;
287
+
288
+ const agentDisplay = agentName ? `${agentName} (ID: ${args.agentId})` : args.agentId;
289
+
280
290
  return {
281
291
  content: wasAdded
282
- ? `Successfully invited agent ${args.agentId} to the group`
283
- : `Agent ${args.agentId} was already in the group`,
292
+ ? `Successfully invited agent ${agentDisplay} to the group`
293
+ : `Agent ${agentDisplay} was already in the group`,
284
294
  state: {
295
+ agentAvatar,
285
296
  agentId: args.agentId,
297
+ agentName,
286
298
  success: wasAdded,
287
299
  } as InviteAgentState,
288
300
  success: wasAdded,
@@ -315,9 +327,9 @@ export class GroupAgentBuilderExecutionRuntime {
315
327
 
316
328
  // Check if agent is in the group
317
329
  const existingAgents = group.agents || [];
318
- const isInGroup = existingAgents.some((a) => a.id === args.agentId);
330
+ const agent = existingAgents.find((a) => a.id === args.agentId);
319
331
 
320
- if (!isInGroup) {
332
+ if (!agent) {
321
333
  return {
322
334
  content: `Agent ${args.agentId} is not in the group`,
323
335
  state: {
@@ -328,12 +340,20 @@ export class GroupAgentBuilderExecutionRuntime {
328
340
  };
329
341
  }
330
342
 
343
+ // Get agent info before removing
344
+ const agentName = agent.title;
345
+ const agentAvatar = agent.avatar;
346
+
347
+ const agentDisplay = agentName ? `${agentName} (ID: ${args.agentId})` : args.agentId;
348
+
331
349
  // Check if this is the supervisor agent (cannot be removed)
332
350
  if (group.supervisorAgentId === args.agentId) {
333
351
  return {
334
- content: `Cannot remove supervisor agent ${args.agentId} from the group`,
352
+ content: `Cannot remove supervisor agent ${agentDisplay} from the group`,
335
353
  state: {
354
+ agentAvatar,
336
355
  agentId: args.agentId,
356
+ agentName,
337
357
  success: false,
338
358
  } as RemoveAgentState,
339
359
  success: false,
@@ -347,9 +367,11 @@ export class GroupAgentBuilderExecutionRuntime {
347
367
  await state.refreshGroupDetail(groupId);
348
368
 
349
369
  return {
350
- content: `Successfully removed agent ${args.agentId} from the group`,
370
+ content: `Successfully removed agent ${agentDisplay} from the group`,
351
371
  state: {
372
+ agentAvatar,
352
373
  agentId: args.agentId,
374
+ agentName,
353
375
  success: true,
354
376
  } as RemoveAgentState,
355
377
  success: true,
@@ -1,19 +1,31 @@
1
1
  'use client';
2
2
 
3
3
  import type { BuiltinInspectorProps } from '@lobechat/types';
4
+ import { Avatar, Flexbox } from '@lobehub/ui';
4
5
  import { createStaticStyles, cssVar, cx } from 'antd-style';
5
6
  import { Check } from 'lucide-react';
6
7
  import { memo } from 'react';
7
8
  import { useTranslation } from 'react-i18next';
8
9
 
9
- import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
10
+ import { shinyTextStyles } from '@/styles';
10
11
 
11
12
  import type { InviteAgentParams, InviteAgentState } from '../../../types';
12
13
 
13
- const styles = createStaticStyles(({ css }) => ({
14
+ const styles = createStaticStyles(({ css, cssVar: cv }) => ({
15
+ root: css`
16
+ overflow: hidden;
17
+ display: flex;
18
+ gap: 8px;
19
+ align-items: center;
20
+ `,
14
21
  statusIcon: css`
22
+ flex-shrink: 0;
15
23
  margin-block-end: -2px;
16
- margin-inline-start: 4px;
24
+ `,
25
+ title: css`
26
+ flex-shrink: 0;
27
+ color: ${cv.colorTextSecondary};
28
+ white-space: nowrap;
17
29
  `,
18
30
  }));
19
31
 
@@ -24,11 +36,12 @@ export const InviteAgentInspector = memo<
24
36
 
25
37
  const agentId = args?.agentId || partialArgs?.agentId;
26
38
  const displayName = pluginState?.agentName || agentId;
39
+ const avatar = pluginState?.agentAvatar;
27
40
 
28
41
  // Initial streaming state
29
42
  if (isArgumentsStreaming && !agentId) {
30
43
  return (
31
- <div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
44
+ <div className={cx(styles.root, shinyTextStyles.shinyText)}>
32
45
  <span>{t('builtins.lobe-group-agent-builder.apiName.inviteAgent')}</span>
33
46
  </div>
34
47
  );
@@ -37,18 +50,21 @@ export const InviteAgentInspector = memo<
37
50
  const isSuccess = pluginState?.success;
38
51
 
39
52
  return (
40
- <div
41
- className={cx(
42
- inspectorTextStyles.root,
43
- (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
44
- )}
53
+ <Flexbox
54
+ align={'center'}
55
+ className={cx(styles.root, (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText)}
56
+ gap={8}
57
+ horizontal
45
58
  >
46
- <span>{t('builtins.lobe-group-agent-builder.apiName.inviteAgent')}: </span>
47
- {displayName && <span className={highlightTextStyles.primary}>{displayName}</span>}
59
+ <span className={styles.title}>
60
+ {t('builtins.lobe-group-agent-builder.apiName.inviteAgent')}:
61
+ </span>
62
+ {avatar && <Avatar avatar={avatar} shape={'square'} size={20} title={displayName || undefined} />}
63
+ {displayName && <span>{displayName}</span>}
48
64
  {!isLoading && isSuccess && (
49
65
  <Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
50
66
  )}
51
- </div>
67
+ </Flexbox>
52
68
  );
53
69
  });
54
70
 
@@ -1,19 +1,31 @@
1
1
  'use client';
2
2
 
3
3
  import type { BuiltinInspectorProps } from '@lobechat/types';
4
+ import { Avatar, Flexbox } from '@lobehub/ui';
4
5
  import { createStaticStyles, cssVar, cx } from 'antd-style';
5
6
  import { Check } from 'lucide-react';
6
7
  import { memo } from 'react';
7
8
  import { useTranslation } from 'react-i18next';
8
9
 
9
- import { highlightTextStyles, inspectorTextStyles, shinyTextStyles } from '@/styles';
10
+ import { shinyTextStyles } from '@/styles';
10
11
 
11
12
  import type { RemoveAgentParams, RemoveAgentState } from '../../../types';
12
13
 
13
- const styles = createStaticStyles(({ css }) => ({
14
+ const styles = createStaticStyles(({ css, cssVar: cv }) => ({
15
+ root: css`
16
+ overflow: hidden;
17
+ display: flex;
18
+ gap: 8px;
19
+ align-items: center;
20
+ `,
14
21
  statusIcon: css`
22
+ flex-shrink: 0;
15
23
  margin-block-end: -2px;
16
- margin-inline-start: 4px;
24
+ `,
25
+ title: css`
26
+ flex-shrink: 0;
27
+ color: ${cv.colorTextSecondary};
28
+ white-space: nowrap;
17
29
  `,
18
30
  }));
19
31
 
@@ -24,11 +36,12 @@ export const RemoveAgentInspector = memo<
24
36
 
25
37
  const agentId = args?.agentId || partialArgs?.agentId;
26
38
  const displayName = pluginState?.agentName || agentId;
39
+ const avatar = pluginState?.agentAvatar;
27
40
 
28
41
  // Initial streaming state
29
42
  if (isArgumentsStreaming && !agentId) {
30
43
  return (
31
- <div className={cx(inspectorTextStyles.root, shinyTextStyles.shinyText)}>
44
+ <div className={cx(styles.root, shinyTextStyles.shinyText)}>
32
45
  <span>{t('builtins.lobe-group-agent-builder.apiName.removeAgent')}</span>
33
46
  </div>
34
47
  );
@@ -37,18 +50,21 @@ export const RemoveAgentInspector = memo<
37
50
  const isSuccess = pluginState?.success;
38
51
 
39
52
  return (
40
- <div
41
- className={cx(
42
- inspectorTextStyles.root,
43
- (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText,
44
- )}
53
+ <Flexbox
54
+ align={'center'}
55
+ className={cx(styles.root, (isArgumentsStreaming || isLoading) && shinyTextStyles.shinyText)}
56
+ gap={8}
57
+ horizontal
45
58
  >
46
- <span>{t('builtins.lobe-group-agent-builder.apiName.removeAgent')}: </span>
47
- {displayName && <span className={highlightTextStyles.primary}>{displayName}</span>}
59
+ <span className={styles.title}>
60
+ {t('builtins.lobe-group-agent-builder.apiName.removeAgent')}:
61
+ </span>
62
+ {avatar && <Avatar avatar={avatar} shape={'square'} size={20} title={displayName || undefined} />}
63
+ {displayName && <span>{displayName}</span>}
48
64
  {!isLoading && isSuccess && (
49
65
  <Check className={styles.statusIcon} color={cssVar.colorSuccess} size={14} />
50
66
  )}
51
- </div>
67
+ </Flexbox>
52
68
  );
53
69
  });
54
70
 
@@ -265,6 +265,10 @@ export interface CreateAgentState {
265
265
  }
266
266
 
267
267
  export interface InviteAgentState {
268
+ /**
269
+ * Agent avatar (emoji or URL)
270
+ */
271
+ agentAvatar?: string;
268
272
  /**
269
273
  * Agent identifier that was invited
270
274
  */
@@ -280,6 +284,10 @@ export interface InviteAgentState {
280
284
  }
281
285
 
282
286
  export interface RemoveAgentState {
287
+ /**
288
+ * Agent avatar (emoji or URL)
289
+ */
290
+ agentAvatar?: string;
283
291
  /**
284
292
  * Agent identifier that was removed
285
293
  */
@@ -53,17 +53,20 @@ export class ToolsEngine {
53
53
  * @returns Processed tools array, or undefined if tools should not be enabled
54
54
  */
55
55
  generateTools(params: GenerateToolsParams): UniformTool[] | undefined {
56
- const { toolIds = [], model, provider, context } = params;
56
+ const { toolIds = [], model, provider, context, skipDefaultTools } = params;
57
57
 
58
- // Merge user-provided tool IDs with default tool IDs
59
- const allToolIds = [...new Set([...toolIds, ...this.defaultToolIds])];
58
+ // Merge user-provided tool IDs with default tool IDs (unless skipDefaultTools is true)
59
+ const allToolIds = skipDefaultTools
60
+ ? toolIds
61
+ : [...new Set([...toolIds, ...this.defaultToolIds])];
60
62
 
61
63
  log(
62
- 'Generating tools for model=%s, provider=%s, pluginIds=%o (includes %d default tools)',
64
+ 'Generating tools for model=%s, provider=%s, pluginIds=%o (skipDefaultTools=%s, includes %d default tools)',
63
65
  model,
64
66
  provider,
65
67
  allToolIds,
66
- this.defaultToolIds.length,
68
+ skipDefaultTools,
69
+ skipDefaultTools ? 0 : this.defaultToolIds.length,
67
70
  );
68
71
 
69
72
  // 1. Check if model supports Function Calling
@@ -94,17 +97,20 @@ export class ToolsEngine {
94
97
  * @returns Detailed tools generation result
95
98
  */
96
99
  generateToolsDetailed(params: GenerateToolsParams): ToolsGenerationResult {
97
- const { toolIds = [], model, provider, context } = params;
100
+ const { toolIds = [], model, provider, context, skipDefaultTools } = params;
98
101
 
99
- // Merge user-provided tool IDs with default tool IDs and deduplicate
100
- const allToolIds = [...new Set([...toolIds, ...this.defaultToolIds])];
102
+ // Merge user-provided tool IDs with default tool IDs and deduplicate (unless skipDefaultTools is true)
103
+ const allToolIds = skipDefaultTools
104
+ ? toolIds
105
+ : [...new Set([...toolIds, ...this.defaultToolIds])];
101
106
 
102
107
  log(
103
- 'Generating detailed tools for model=%s, provider=%s, pluginIds=%o (includes %d default tools)',
108
+ 'Generating detailed tools for model=%s, provider=%s, pluginIds=%o (skipDefaultTools=%s, includes %d default tools)',
104
109
  model,
105
110
  provider,
106
111
  allToolIds,
107
- this.defaultToolIds.length,
112
+ skipDefaultTools,
113
+ skipDefaultTools ? 0 : this.defaultToolIds.length,
108
114
  );
109
115
 
110
116
  // Check if model supports Function Calling
@@ -1135,4 +1135,146 @@ describe('ToolsEngine', () => {
1135
1135
  expect(result).toHaveLength(2);
1136
1136
  });
1137
1137
  });
1138
+
1139
+ describe('skipDefaultTools', () => {
1140
+ it('should not include default tools when skipDefaultTools is true in generateTools', () => {
1141
+ const engine = new ToolsEngine({
1142
+ manifestSchemas: [mockWebBrowsingManifest, mockDalleManifest],
1143
+ defaultToolIds: ['lobe-web-browsing'],
1144
+ enableChecker: () => true,
1145
+ functionCallChecker: () => true,
1146
+ });
1147
+
1148
+ const result = engine.generateTools({
1149
+ toolIds: ['dalle'],
1150
+ model: 'gpt-4',
1151
+ provider: 'openai',
1152
+ skipDefaultTools: true,
1153
+ });
1154
+
1155
+ // Should only include dalle, not the default lobe-web-browsing
1156
+ expect(result).toHaveLength(1);
1157
+ expect(result![0].function.name).toBe('dalle____generateImage____builtin');
1158
+ });
1159
+
1160
+ it('should not include default tools when skipDefaultTools is true in generateToolsDetailed', () => {
1161
+ const engine = new ToolsEngine({
1162
+ manifestSchemas: [mockWebBrowsingManifest, mockDalleManifest],
1163
+ defaultToolIds: ['lobe-web-browsing'],
1164
+ enableChecker: () => true,
1165
+ functionCallChecker: () => true,
1166
+ });
1167
+
1168
+ const result = engine.generateToolsDetailed({
1169
+ toolIds: ['dalle'],
1170
+ model: 'gpt-4',
1171
+ provider: 'openai',
1172
+ skipDefaultTools: true,
1173
+ });
1174
+
1175
+ // Should only include dalle, not the default lobe-web-browsing
1176
+ expect(result.tools).toHaveLength(1);
1177
+ expect(result.enabledToolIds).toEqual(['dalle']);
1178
+ expect(result.enabledManifests).toHaveLength(1);
1179
+ expect(result.enabledManifests[0].identifier).toBe('dalle');
1180
+ });
1181
+
1182
+ it('should return undefined when skipDefaultTools is true and toolIds is empty', () => {
1183
+ const engine = new ToolsEngine({
1184
+ manifestSchemas: [mockWebBrowsingManifest, mockDalleManifest],
1185
+ defaultToolIds: ['lobe-web-browsing', 'dalle'],
1186
+ enableChecker: () => true,
1187
+ functionCallChecker: () => true,
1188
+ });
1189
+
1190
+ const result = engine.generateTools({
1191
+ toolIds: [],
1192
+ model: 'gpt-4',
1193
+ provider: 'openai',
1194
+ skipDefaultTools: true,
1195
+ });
1196
+
1197
+ // Should return undefined when no tools are available
1198
+ expect(result).toBeUndefined();
1199
+ });
1200
+
1201
+ it('should return empty results when skipDefaultTools is true and toolIds is empty in generateToolsDetailed', () => {
1202
+ const engine = new ToolsEngine({
1203
+ manifestSchemas: [mockWebBrowsingManifest, mockDalleManifest],
1204
+ defaultToolIds: ['lobe-web-browsing', 'dalle'],
1205
+ enableChecker: () => true,
1206
+ functionCallChecker: () => true,
1207
+ });
1208
+
1209
+ const result = engine.generateToolsDetailed({
1210
+ toolIds: [],
1211
+ model: 'gpt-4',
1212
+ provider: 'openai',
1213
+ skipDefaultTools: true,
1214
+ });
1215
+
1216
+ // Should return empty results
1217
+ expect(result.tools).toBeUndefined();
1218
+ expect(result.enabledToolIds).toEqual([]);
1219
+ expect(result.enabledManifests).toHaveLength(0);
1220
+ });
1221
+
1222
+ it('should include default tools when skipDefaultTools is false', () => {
1223
+ const engine = new ToolsEngine({
1224
+ manifestSchemas: [mockWebBrowsingManifest, mockDalleManifest],
1225
+ defaultToolIds: ['lobe-web-browsing'],
1226
+ enableChecker: () => true,
1227
+ functionCallChecker: () => true,
1228
+ });
1229
+
1230
+ const result = engine.generateTools({
1231
+ toolIds: ['dalle'],
1232
+ model: 'gpt-4',
1233
+ provider: 'openai',
1234
+ skipDefaultTools: false,
1235
+ });
1236
+
1237
+ // Should include both dalle and the default lobe-web-browsing
1238
+ expect(result).toHaveLength(2);
1239
+ });
1240
+
1241
+ it('should include default tools when skipDefaultTools is undefined (default behavior)', () => {
1242
+ const engine = new ToolsEngine({
1243
+ manifestSchemas: [mockWebBrowsingManifest, mockDalleManifest],
1244
+ defaultToolIds: ['lobe-web-browsing'],
1245
+ enableChecker: () => true,
1246
+ functionCallChecker: () => true,
1247
+ });
1248
+
1249
+ const result = engine.generateTools({
1250
+ toolIds: ['dalle'],
1251
+ model: 'gpt-4',
1252
+ provider: 'openai',
1253
+ // skipDefaultTools not specified
1254
+ });
1255
+
1256
+ // Should include both dalle and the default lobe-web-browsing
1257
+ expect(result).toHaveLength(2);
1258
+ });
1259
+
1260
+ it('should work correctly with empty defaultToolIds', () => {
1261
+ const engine = new ToolsEngine({
1262
+ manifestSchemas: [mockWebBrowsingManifest, mockDalleManifest],
1263
+ defaultToolIds: [],
1264
+ enableChecker: () => true,
1265
+ functionCallChecker: () => true,
1266
+ });
1267
+
1268
+ const result = engine.generateTools({
1269
+ toolIds: ['dalle'],
1270
+ model: 'gpt-4',
1271
+ provider: 'openai',
1272
+ skipDefaultTools: true,
1273
+ });
1274
+
1275
+ // Should only include dalle
1276
+ expect(result).toHaveLength(1);
1277
+ expect(result![0].function.name).toBe('dalle____generateImage____builtin');
1278
+ });
1279
+ });
1138
1280
  });
@@ -72,6 +72,12 @@ export interface GenerateToolsParams {
72
72
  model: string;
73
73
  /** Provider name */
74
74
  provider: string;
75
+ /**
76
+ * Whether to skip merging default tools.
77
+ * When true, only the explicitly provided toolIds will be used.
78
+ * Useful for broadcast scenarios where tools should be completely disabled.
79
+ */
80
+ skipDefaultTools?: boolean;
75
81
  /** List of tool IDs to enable */
76
82
  toolIds?: string[];
77
83
  }