@lobehub/lobehub 2.0.0-next.307 → 2.0.0-next.309

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 (221) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/changelog/v1.json +21 -0
  3. package/locales/ar/agentGroup.json +5 -0
  4. package/locales/ar/chat.json +26 -0
  5. package/locales/ar/models.json +43 -5
  6. package/locales/ar/plugin.json +4 -5
  7. package/locales/ar/setting.json +11 -0
  8. package/locales/ar/subscription.json +2 -0
  9. package/locales/ar/tool.json +2 -0
  10. package/locales/bg-BG/agentGroup.json +5 -0
  11. package/locales/bg-BG/chat.json +26 -0
  12. package/locales/bg-BG/models.json +49 -3
  13. package/locales/bg-BG/plugin.json +4 -5
  14. package/locales/bg-BG/setting.json +11 -0
  15. package/locales/bg-BG/subscription.json +2 -0
  16. package/locales/bg-BG/tool.json +2 -0
  17. package/locales/de-DE/agentGroup.json +5 -0
  18. package/locales/de-DE/chat.json +26 -0
  19. package/locales/de-DE/models.json +48 -5
  20. package/locales/de-DE/plugin.json +4 -5
  21. package/locales/de-DE/setting.json +11 -0
  22. package/locales/de-DE/subscription.json +2 -0
  23. package/locales/de-DE/tool.json +2 -0
  24. package/locales/en-US/models.json +8 -6
  25. package/locales/en-US/plugin.json +3 -4
  26. package/locales/en-US/setting.json +10 -11
  27. package/locales/en-US/tool.json +2 -0
  28. package/locales/es-ES/agentGroup.json +5 -0
  29. package/locales/es-ES/chat.json +26 -0
  30. package/locales/es-ES/models.json +43 -5
  31. package/locales/es-ES/plugin.json +4 -5
  32. package/locales/es-ES/setting.json +11 -0
  33. package/locales/es-ES/subscription.json +2 -0
  34. package/locales/es-ES/tool.json +2 -0
  35. package/locales/fa-IR/agentGroup.json +5 -0
  36. package/locales/fa-IR/chat.json +26 -0
  37. package/locales/fa-IR/models.json +42 -5
  38. package/locales/fa-IR/plugin.json +4 -5
  39. package/locales/fa-IR/setting.json +11 -0
  40. package/locales/fa-IR/subscription.json +2 -0
  41. package/locales/fa-IR/tool.json +2 -0
  42. package/locales/fr-FR/agentGroup.json +5 -0
  43. package/locales/fr-FR/chat.json +26 -0
  44. package/locales/fr-FR/models.json +5 -5
  45. package/locales/fr-FR/plugin.json +4 -5
  46. package/locales/fr-FR/setting.json +11 -0
  47. package/locales/fr-FR/subscription.json +2 -0
  48. package/locales/fr-FR/tool.json +2 -0
  49. package/locales/it-IT/agentGroup.json +5 -0
  50. package/locales/it-IT/chat.json +26 -0
  51. package/locales/it-IT/models.json +1 -3
  52. package/locales/it-IT/plugin.json +4 -5
  53. package/locales/it-IT/setting.json +11 -0
  54. package/locales/it-IT/subscription.json +2 -0
  55. package/locales/it-IT/tool.json +2 -0
  56. package/locales/ja-JP/agentGroup.json +5 -0
  57. package/locales/ja-JP/chat.json +26 -0
  58. package/locales/ja-JP/models.json +1 -5
  59. package/locales/ja-JP/plugin.json +4 -5
  60. package/locales/ja-JP/setting.json +11 -0
  61. package/locales/ja-JP/subscription.json +2 -0
  62. package/locales/ja-JP/tool.json +2 -0
  63. package/locales/ko-KR/agentGroup.json +5 -0
  64. package/locales/ko-KR/chat.json +26 -0
  65. package/locales/ko-KR/models.json +1 -3
  66. package/locales/ko-KR/plugin.json +4 -5
  67. package/locales/ko-KR/setting.json +11 -0
  68. package/locales/ko-KR/subscription.json +2 -0
  69. package/locales/ko-KR/tool.json +2 -0
  70. package/locales/nl-NL/agentGroup.json +5 -0
  71. package/locales/nl-NL/chat.json +26 -0
  72. package/locales/nl-NL/models.json +35 -3
  73. package/locales/nl-NL/plugin.json +4 -5
  74. package/locales/nl-NL/setting.json +11 -0
  75. package/locales/nl-NL/subscription.json +2 -0
  76. package/locales/nl-NL/tool.json +2 -0
  77. package/locales/pl-PL/agentGroup.json +5 -0
  78. package/locales/pl-PL/chat.json +26 -0
  79. package/locales/pl-PL/models.json +1 -3
  80. package/locales/pl-PL/plugin.json +4 -5
  81. package/locales/pl-PL/setting.json +11 -0
  82. package/locales/pl-PL/subscription.json +2 -0
  83. package/locales/pl-PL/tool.json +2 -0
  84. package/locales/pt-BR/agentGroup.json +5 -0
  85. package/locales/pt-BR/chat.json +26 -0
  86. package/locales/pt-BR/models.json +50 -5
  87. package/locales/pt-BR/plugin.json +4 -5
  88. package/locales/pt-BR/setting.json +11 -0
  89. package/locales/pt-BR/subscription.json +2 -0
  90. package/locales/pt-BR/tool.json +2 -0
  91. package/locales/ru-RU/agentGroup.json +5 -0
  92. package/locales/ru-RU/chat.json +26 -0
  93. package/locales/ru-RU/models.json +22 -3
  94. package/locales/ru-RU/plugin.json +4 -5
  95. package/locales/ru-RU/setting.json +11 -0
  96. package/locales/ru-RU/subscription.json +2 -0
  97. package/locales/ru-RU/tool.json +2 -0
  98. package/locales/tr-TR/agentGroup.json +5 -0
  99. package/locales/tr-TR/chat.json +26 -0
  100. package/locales/tr-TR/models.json +36 -3
  101. package/locales/tr-TR/plugin.json +4 -5
  102. package/locales/tr-TR/setting.json +11 -0
  103. package/locales/tr-TR/subscription.json +2 -0
  104. package/locales/tr-TR/tool.json +2 -0
  105. package/locales/vi-VN/agentGroup.json +5 -0
  106. package/locales/vi-VN/chat.json +26 -0
  107. package/locales/vi-VN/models.json +1 -1
  108. package/locales/vi-VN/plugin.json +4 -5
  109. package/locales/vi-VN/setting.json +11 -0
  110. package/locales/vi-VN/subscription.json +2 -0
  111. package/locales/vi-VN/tool.json +2 -0
  112. package/locales/zh-CN/models.json +52 -5
  113. package/locales/zh-CN/plugin.json +6 -7
  114. package/locales/zh-CN/setting.json +10 -11
  115. package/locales/zh-CN/tool.json +2 -2
  116. package/locales/zh-TW/agentGroup.json +5 -0
  117. package/locales/zh-TW/chat.json +26 -0
  118. package/locales/zh-TW/models.json +54 -5
  119. package/locales/zh-TW/plugin.json +4 -5
  120. package/locales/zh-TW/setting.json +11 -0
  121. package/locales/zh-TW/subscription.json +2 -0
  122. package/locales/zh-TW/tool.json +2 -0
  123. package/package.json +1 -1
  124. package/packages/agent-runtime/src/types/state.ts +2 -0
  125. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/GetAgentInfo/index.tsx +68 -0
  126. package/packages/builtin-tool-group-agent-builder/src/client/Inspector/index.ts +3 -0
  127. package/packages/builtin-tool-group-management/src/client/Inspector/ExecuteAgentTask/index.tsx +52 -8
  128. package/packages/builtin-tool-group-management/src/client/Render/ExecuteTask/index.tsx +2 -21
  129. package/packages/builtin-tool-group-management/src/executor.test.ts +6 -4
  130. package/packages/builtin-tool-group-management/src/manifest.ts +5 -1
  131. package/packages/builtin-tool-group-management/src/types.ts +2 -0
  132. package/packages/builtin-tool-local-system/src/client/Render/WriteFile/index.tsx +48 -5
  133. package/packages/builtin-tool-local-system/src/client/Streaming/WriteFile/index.tsx +39 -0
  134. package/packages/builtin-tool-local-system/src/client/Streaming/index.ts +2 -0
  135. package/packages/builtin-tool-memory/src/manifest.ts +581 -19
  136. package/packages/model-bank/src/aiModels/qiniu.ts +24 -0
  137. package/packages/types/src/topic/thread.ts +2 -2
  138. package/packages/types/src/userMemory/layers.ts +19 -8
  139. package/packages/types/src/userMemory/shared.ts +7 -1
  140. package/src/app/[variants]/(auth)/_layout/index.tsx +0 -2
  141. package/src/app/[variants]/(auth)/layout.tsx +0 -2
  142. package/src/app/[variants]/(auth)/login/[[...login]]/page.tsx +1 -3
  143. package/src/app/[variants]/(auth)/signup/[[...signup]]/page.tsx +1 -3
  144. package/src/app/[variants]/(desktop)/desktop-onboarding/_layout/index.tsx +0 -2
  145. package/src/app/[variants]/(main)/_layout/index.tsx +0 -2
  146. package/src/app/[variants]/(main)/agent/_layout/index.tsx +0 -2
  147. package/src/app/[variants]/(main)/agent/features/Portal/index.tsx +0 -2
  148. package/src/app/[variants]/(main)/community/(list)/_layout/index.tsx +0 -2
  149. package/src/app/[variants]/(main)/community/(list)/assistant/_layout/index.tsx +0 -2
  150. package/src/app/[variants]/(main)/community/(list)/mcp/_layout/index.tsx +0 -2
  151. package/src/app/[variants]/(main)/community/(list)/model/_layout/index.tsx +0 -2
  152. package/src/app/[variants]/(main)/community/_layout/index.tsx +0 -2
  153. package/src/app/[variants]/(main)/group/_layout/index.tsx +0 -2
  154. package/src/app/[variants]/(main)/group/features/Conversation/Header/index.tsx +4 -2
  155. package/src/app/[variants]/(main)/group/features/Portal/index.tsx +0 -2
  156. package/src/app/[variants]/(main)/home/_layout/index.tsx +0 -2
  157. package/src/app/[variants]/(main)/home/index.tsx +0 -2
  158. package/src/app/[variants]/(main)/image/_layout/Topics/TopicUrlSync.tsx +0 -2
  159. package/src/app/[variants]/(main)/image/_layout/index.tsx +0 -2
  160. package/src/app/[variants]/(main)/memory/_layout/index.tsx +0 -2
  161. package/src/app/[variants]/(main)/page/_layout/index.tsx +0 -2
  162. package/src/app/[variants]/(main)/resource/(home)/_layout/index.tsx +0 -2
  163. package/src/app/[variants]/(main)/resource/_layout/index.tsx +0 -2
  164. package/src/app/[variants]/(main)/resource/library/_layout/index.tsx +0 -2
  165. package/src/app/[variants]/(main)/resource/library/features/Container.tsx +0 -2
  166. package/src/app/[variants]/(main)/settings/_layout/index.tsx +0 -2
  167. package/src/app/[variants]/(main)/settings/about/index.tsx +0 -2
  168. package/src/app/[variants]/(main)/settings/agent/index.tsx +0 -2
  169. package/src/app/[variants]/(main)/settings/apikey/index.tsx +0 -2
  170. package/src/app/[variants]/(main)/settings/chat-appearance/index.tsx +0 -2
  171. package/src/app/[variants]/(main)/settings/common/index.tsx +0 -2
  172. package/src/app/[variants]/(main)/settings/hotkey/index.tsx +0 -2
  173. package/src/app/[variants]/(main)/settings/image/index.tsx +0 -2
  174. package/src/app/[variants]/(main)/settings/memory/index.tsx +0 -2
  175. package/src/app/[variants]/(main)/settings/provider/(list)/index.tsx +0 -2
  176. package/src/app/[variants]/(main)/settings/proxy/index.tsx +0 -2
  177. package/src/app/[variants]/(main)/settings/security/index.tsx +1 -3
  178. package/src/app/[variants]/(main)/settings/storage/index.tsx +0 -2
  179. package/src/app/[variants]/(main)/settings/tts/index.tsx +0 -2
  180. package/src/app/[variants]/(mobile)/(home)/_layout/index.tsx +0 -2
  181. package/src/app/[variants]/(mobile)/_layout/index.tsx +1 -3
  182. package/src/app/[variants]/(mobile)/chat/_layout/index.tsx +0 -2
  183. package/src/app/[variants]/(mobile)/chat/settings/_layout/index.tsx +0 -2
  184. package/src/app/[variants]/(mobile)/community/(detail)/_layout/index.tsx +0 -2
  185. package/src/app/[variants]/(mobile)/community/(list)/_layout/index.tsx +0 -2
  186. package/src/app/[variants]/(mobile)/community/_layout/index.tsx +0 -2
  187. package/src/app/[variants]/(mobile)/router/MobileClientRouter.tsx +0 -2
  188. package/src/app/[variants]/(mobile)/settings/index.tsx +0 -2
  189. package/src/app/[variants]/onboarding/_layout/index.tsx +0 -2
  190. package/src/app/[variants]/router/DesktopClientRouter.tsx +0 -2
  191. package/src/components/ModelSelect/index.tsx +6 -56
  192. package/src/components/server/MobileNavLayout.tsx +0 -2
  193. package/src/components/server/ServerLayout.tsx +0 -2
  194. package/src/features/ModelSwitchPanel/components/Footer.tsx +0 -2
  195. package/src/features/ModelSwitchPanel/components/List/MultipleProvidersModelItem.tsx +0 -1
  196. package/src/features/ModelSwitchPanel/components/List/SingleProviderModelItem.tsx +0 -1
  197. package/src/features/ModelSwitchPanel/components/List/VirtualItemRenderer.tsx +0 -1
  198. package/src/features/ModelSwitchPanel/components/List/index.tsx +15 -13
  199. package/src/features/ModelSwitchPanel/components/PanelContent.tsx +0 -2
  200. package/src/features/ModelSwitchPanel/index.tsx +21 -23
  201. package/src/features/ResourceManager/components/Explorer/MasonryView/index.tsx +0 -2
  202. package/src/features/User/UserAvatar.tsx +0 -2
  203. package/src/locales/default/plugin.ts +3 -1
  204. package/src/server/modules/AgentRuntime/RuntimeExecutors.ts +11 -3
  205. package/src/server/modules/Mecha/AgentToolsEngine/index.ts +14 -6
  206. package/src/server/modules/Mecha/AgentToolsEngine/types.ts +4 -3
  207. package/src/server/routers/lambda/aiAgent.ts +10 -0
  208. package/src/server/services/agent/index.test.ts +155 -0
  209. package/src/server/services/agent/index.ts +25 -0
  210. package/src/server/services/agentRuntime/AgentRuntimeService.ts +2 -0
  211. package/src/server/services/agentRuntime/types.ts +1 -0
  212. package/src/server/services/aiAgent/__tests__/execAgent.threadId.test.ts +29 -9
  213. package/src/server/services/aiAgent/index.ts +175 -6
  214. package/src/server/services/lobehubSkill/index.ts +109 -0
  215. package/src/server/services/toolExecution/builtin.ts +28 -2
  216. package/src/server/services/toolExecution/types.ts +3 -0
  217. package/src/store/chat/agents/GroupOrchestration/__tests__/call-supervisor.test.ts +305 -0
  218. package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +2 -1
  219. package/src/store/chat/agents/createAgentExecutors.ts +4 -2
  220. package/src/store/chat/slices/plugin/actions/exector.ts +92 -0
  221. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +82 -177
@@ -216,6 +216,161 @@ describe('AgentService', () => {
216
216
  });
217
217
  });
218
218
 
219
+ describe('getAgentConfig', () => {
220
+ it('should return null if agent does not exist', async () => {
221
+ const mockAgentModel = {
222
+ getAgentConfig: vi.fn().mockResolvedValue(null),
223
+ };
224
+
225
+ (AgentModel as any).mockImplementation(() => mockAgentModel);
226
+ (parseAgentConfig as any).mockReturnValue({});
227
+
228
+ const newService = new AgentService(mockDb, mockUserId);
229
+ const result = await newService.getAgentConfig('non-existent');
230
+
231
+ expect(result).toBeNull();
232
+ });
233
+
234
+ it('should support lookup by agent id', async () => {
235
+ const mockAgent = {
236
+ id: 'agent-123',
237
+ model: 'gpt-4',
238
+ systemRole: 'Test role',
239
+ };
240
+
241
+ const mockAgentModel = {
242
+ getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
243
+ };
244
+
245
+ (AgentModel as any).mockImplementation(() => mockAgentModel);
246
+ (parseAgentConfig as any).mockReturnValue({});
247
+
248
+ const newService = new AgentService(mockDb, mockUserId);
249
+ const result = await newService.getAgentConfig('agent-123');
250
+
251
+ expect(mockAgentModel.getAgentConfig).toHaveBeenCalledWith('agent-123');
252
+ expect(result?.id).toBe('agent-123');
253
+ expect(result?.model).toBe('gpt-4');
254
+ });
255
+
256
+ it('should support lookup by slug', async () => {
257
+ const mockAgent = {
258
+ id: 'agent-123',
259
+ model: 'claude-3',
260
+ slug: 'my-agent',
261
+ };
262
+
263
+ const mockAgentModel = {
264
+ getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
265
+ };
266
+
267
+ (AgentModel as any).mockImplementation(() => mockAgentModel);
268
+ (parseAgentConfig as any).mockReturnValue({});
269
+
270
+ const newService = new AgentService(mockDb, mockUserId);
271
+ const result = await newService.getAgentConfig('my-agent');
272
+
273
+ expect(mockAgentModel.getAgentConfig).toHaveBeenCalledWith('my-agent');
274
+ expect(result?.id).toBe('agent-123');
275
+ });
276
+
277
+ it('should merge DEFAULT_AGENT_CONFIG and serverDefaultAgentConfig with agent config', async () => {
278
+ const mockAgent = {
279
+ id: 'agent-1',
280
+ systemRole: 'Custom system role',
281
+ };
282
+ const serverDefaultConfig = { model: 'gpt-4', params: { temperature: 0.7 } };
283
+
284
+ const mockAgentModel = {
285
+ getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
286
+ };
287
+
288
+ (AgentModel as any).mockImplementation(() => mockAgentModel);
289
+ (parseAgentConfig as any).mockReturnValue(serverDefaultConfig);
290
+
291
+ const newService = new AgentService(mockDb, mockUserId);
292
+ const result = await newService.getAgentConfig('agent-1');
293
+
294
+ expect(result).toMatchObject({
295
+ chatConfig: DEFAULT_AGENT_CONFIG.chatConfig,
296
+ plugins: DEFAULT_AGENT_CONFIG.plugins,
297
+ tts: DEFAULT_AGENT_CONFIG.tts,
298
+ model: 'gpt-4',
299
+ params: { temperature: 0.7 },
300
+ id: 'agent-1',
301
+ systemRole: 'Custom system role',
302
+ });
303
+ });
304
+
305
+ it('should use default model/provider when agent has none', async () => {
306
+ const mockAgent = {
307
+ id: 'agent-1',
308
+ systemRole: 'Test',
309
+ // No model or provider set
310
+ };
311
+
312
+ const mockAgentModel = {
313
+ getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
314
+ };
315
+
316
+ (AgentModel as any).mockImplementation(() => mockAgentModel);
317
+ (parseAgentConfig as any).mockReturnValue({});
318
+
319
+ const newService = new AgentService(mockDb, mockUserId);
320
+ const result = await newService.getAgentConfig('agent-1');
321
+
322
+ // Should have default model/provider from DEFAULT_AGENT_CONFIG
323
+ expect(result?.model).toBe(DEFAULT_AGENT_CONFIG.model);
324
+ expect(result?.provider).toBe(DEFAULT_AGENT_CONFIG.provider);
325
+ });
326
+
327
+ it('should prioritize agent model/provider over defaults', async () => {
328
+ const mockAgent = {
329
+ id: 'agent-1',
330
+ model: 'claude-3-opus',
331
+ provider: 'anthropic',
332
+ };
333
+ const serverDefaultConfig = { model: 'gpt-4', provider: 'openai' };
334
+
335
+ const mockAgentModel = {
336
+ getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
337
+ };
338
+
339
+ (AgentModel as any).mockImplementation(() => mockAgentModel);
340
+ (parseAgentConfig as any).mockReturnValue(serverDefaultConfig);
341
+
342
+ const newService = new AgentService(mockDb, mockUserId);
343
+ const result = await newService.getAgentConfig('agent-1');
344
+
345
+ // Agent config should override server default
346
+ expect(result?.model).toBe('claude-3-opus');
347
+ expect(result?.provider).toBe('anthropic');
348
+ });
349
+
350
+ it('should merge user default agent config', async () => {
351
+ const mockAgent = {
352
+ id: 'agent-1',
353
+ };
354
+ const userDefaultConfig = { model: 'user-preferred-model', provider: 'user-provider' };
355
+
356
+ const mockAgentModel = {
357
+ getAgentConfig: vi.fn().mockResolvedValue(mockAgent),
358
+ };
359
+
360
+ (AgentModel as any).mockImplementation(() => mockAgentModel);
361
+ (parseAgentConfig as any).mockReturnValue({});
362
+ // Use mockResolvedValueOnce to avoid affecting subsequent tests
363
+ mockUserModel.getUserSettingsDefaultAgentConfig.mockResolvedValueOnce({ config: userDefaultConfig });
364
+
365
+ const newService = new AgentService(mockDb, mockUserId);
366
+ const result = await newService.getAgentConfig('agent-1');
367
+
368
+ // User default config should be applied
369
+ expect(result?.model).toBe('user-preferred-model');
370
+ expect(result?.provider).toBe('user-provider');
371
+ });
372
+ });
373
+
219
374
  describe('getAgentConfigById', () => {
220
375
  it('should return null if agent does not exist', async () => {
221
376
  const mockAgentModel = {
@@ -17,6 +17,12 @@ import { type UpdateAgentResult } from './type';
17
17
 
18
18
  const log = debug('lobe-agent:service');
19
19
 
20
+ /**
21
+ * Agent config with required id field.
22
+ * Used when returning agent config from database (id is always present).
23
+ */
24
+ export type AgentConfigWithId = LobeAgentConfig & { id: string };
25
+
20
26
  interface AgentWelcomeData {
21
27
  openQuestions: string[];
22
28
  welcomeMessage: string;
@@ -78,6 +84,25 @@ export class AgentService {
78
84
  return mergedConfig;
79
85
  }
80
86
 
87
+ /**
88
+ * Get agent config by ID or slug with default config merged.
89
+ * Supports both agentId and slug lookup.
90
+ *
91
+ * The returned agent config is merged with:
92
+ * 1. DEFAULT_AGENT_CONFIG (hardcoded defaults)
93
+ * 2. Server's globalDefaultAgentConfig (from environment variable DEFAULT_AGENT_CONFIG)
94
+ * 3. User's defaultAgentConfig (from user settings)
95
+ * 4. The actual agent config from database
96
+ */
97
+ async getAgentConfig(idOrSlug: string): Promise<AgentConfigWithId | null> {
98
+ const [agent, defaultAgentConfig] = await Promise.all([
99
+ this.agentModel.getAgentConfig(idOrSlug),
100
+ this.userModel.getUserSettingsDefaultAgentConfig(),
101
+ ]);
102
+
103
+ return this.mergeDefaultConfig(agent, defaultAgentConfig) as AgentConfigWithId | null;
104
+ }
105
+
81
106
  /**
82
107
  * Get agent config by ID with default config merged.
83
108
  *
@@ -231,6 +231,7 @@ export class AgentRuntimeService {
231
231
  initialMessages = [],
232
232
  appContext,
233
233
  toolManifestMap,
234
+ toolSourceMap,
234
235
  stepCallbacks,
235
236
  } = params;
236
237
 
@@ -258,6 +259,7 @@ export class AgentRuntimeService {
258
259
  status: 'idle',
259
260
  stepCount: 0,
260
261
  toolManifestMap,
262
+ toolSourceMap,
261
263
  tools,
262
264
  } as Partial<AgentState>;
263
265
 
@@ -87,6 +87,7 @@ export interface OperationCreationParams {
87
87
  */
88
88
  stepCallbacks?: StepLifecycleCallbacks;
89
89
  toolManifestMap: Record<string, LobeToolManifest>;
90
+ toolSourceMap?: Record<string, 'builtin' | 'plugin' | 'mcp' | 'klavis' | 'lobehubSkill'>;
90
91
  tools?: any[];
91
92
  userId?: string;
92
93
  }
@@ -29,6 +29,22 @@ vi.mock('@/database/models/agent', () => ({
29
29
  })),
30
30
  }));
31
31
 
32
+ // Mock AgentService
33
+ vi.mock('@/server/services/agent', () => ({
34
+ AgentService: vi.fn().mockImplementation(() => ({
35
+ getAgentConfig: vi.fn().mockResolvedValue({
36
+ chatConfig: {},
37
+ files: [],
38
+ id: 'agent-1',
39
+ knowledgeBases: [],
40
+ model: 'gpt-4',
41
+ plugins: [],
42
+ provider: 'openai',
43
+ systemRole: 'You are a helpful assistant',
44
+ }),
45
+ })),
46
+ }));
47
+
32
48
  // Mock PluginModel
33
49
  vi.mock('@/database/models/plugin', () => ({
34
50
  PluginModel: vi.fn().mockImplementation(() => ({
@@ -74,15 +90,19 @@ vi.mock('@/server/modules/Mecha', () => ({
74
90
  }));
75
91
 
76
92
  // Mock model-bank
77
- vi.mock('model-bank', () => ({
78
- LOBE_DEFAULT_MODEL_LIST: [
79
- {
80
- abilities: { functionCall: true, video: false, vision: true },
81
- id: 'gpt-4',
82
- providerId: 'openai',
83
- },
84
- ],
85
- }));
93
+ vi.mock('model-bank', async (importOriginal) => {
94
+ const actual = await importOriginal<typeof import('model-bank')>();
95
+ return {
96
+ ...actual,
97
+ LOBE_DEFAULT_MODEL_LIST: [
98
+ {
99
+ abilities: { functionCall: true, video: false, vision: true },
100
+ id: 'gpt-4',
101
+ providerId: 'openai',
102
+ },
103
+ ],
104
+ };
105
+ });
86
106
 
87
107
  describe('AiAgentService.execAgent - threadId handling', () => {
88
108
  let service: AiAgentService;
@@ -1,4 +1,5 @@
1
1
  import type { AgentRuntimeContext, AgentState } from '@lobechat/agent-runtime';
2
+ import type { LobeToolManifest } from '@lobechat/context-engine';
2
3
  import { type LobeChatDatabase } from '@lobechat/database';
3
4
  import type {
4
5
  ExecAgentParams,
@@ -10,6 +11,7 @@ import type {
10
11
  } from '@lobechat/types';
11
12
  import { ThreadStatus, ThreadType } from '@lobechat/types';
12
13
  import { nanoid } from '@lobechat/utils';
14
+ import { MarketSDK } from '@lobehub/market-sdk';
13
15
  import debug from 'debug';
14
16
 
15
17
  import { LOADING_FLAT } from '@/const/message';
@@ -18,16 +20,43 @@ import { MessageModel } from '@/database/models/message';
18
20
  import { PluginModel } from '@/database/models/plugin';
19
21
  import { ThreadModel } from '@/database/models/thread';
20
22
  import { TopicModel } from '@/database/models/topic';
23
+ import { UserModel } from '@/database/models/user';
24
+ import { generateTrustedClientToken } from '@/libs/trusted-client';
21
25
  import {
22
26
  type ServerAgentToolsContext,
23
27
  createServerAgentToolsEngine,
24
28
  serverMessagesEngine,
25
29
  } from '@/server/modules/Mecha';
30
+ import { AgentService } from '@/server/services/agent';
26
31
  import { AgentRuntimeService } from '@/server/services/agentRuntime';
27
32
  import type { StepLifecycleCallbacks } from '@/server/services/agentRuntime/types';
28
33
 
29
34
  const log = debug('lobe-server:ai-agent-service');
30
35
 
36
+ /**
37
+ * Format error for storage in thread metadata
38
+ * Handles Error objects which don't serialize properly with JSON.stringify
39
+ */
40
+ function formatErrorForMetadata(error: unknown): Record<string, any> | undefined {
41
+ if (!error) return undefined;
42
+
43
+ // Handle Error objects
44
+ if (error instanceof Error) {
45
+ return {
46
+ message: error.message,
47
+ name: error.name,
48
+ };
49
+ }
50
+
51
+ // Handle objects with message property (like ChatMessageError)
52
+ if (typeof error === 'object' && 'message' in error) {
53
+ return error as Record<string, any>;
54
+ }
55
+
56
+ // Fallback: wrap in object
57
+ return { message: String(error) };
58
+ }
59
+
31
60
  /**
32
61
  * Internal params for execAgent with step lifecycle callbacks
33
62
  * This extends the public ExecAgentParams with server-side only options
@@ -53,6 +82,7 @@ export class AiAgentService {
53
82
  private readonly userId: string;
54
83
  private readonly db: LobeChatDatabase;
55
84
  private readonly agentModel: AgentModel;
85
+ private readonly agentService: AgentService;
56
86
  private readonly messageModel: MessageModel;
57
87
  private readonly pluginModel: PluginModel;
58
88
  private readonly threadModel: ThreadModel;
@@ -63,6 +93,7 @@ export class AiAgentService {
63
93
  this.userId = userId;
64
94
  this.db = db;
65
95
  this.agentModel = new AgentModel(db, userId);
96
+ this.agentService = new AgentService(db, userId);
66
97
  this.messageModel = new MessageModel(db, userId);
67
98
  this.pluginModel = new PluginModel(db, userId);
68
99
  this.threadModel = new ThreadModel(db, userId);
@@ -106,8 +137,8 @@ export class AiAgentService {
106
137
 
107
138
  log('execAgent: identifier=%s, prompt=%s', identifier, prompt.slice(0, 50));
108
139
 
109
- // 1. Get agent configuration from database (supports both id and slug)
110
- const agentConfig = await this.agentModel.getAgentConfig(identifier);
140
+ // 1. Get agent configuration with default config merged (supports both id and slug)
141
+ const agentConfig = await this.agentService.getAgentConfig(identifier);
111
142
  if (!agentConfig) {
112
143
  throw new Error(`Agent not found: ${identifier}`);
113
144
  }
@@ -158,7 +189,11 @@ export class AiAgentService {
158
189
  return info?.abilities?.functionCall ?? true;
159
190
  };
160
191
 
161
- // 5. Create tools using Server AgentToolsEngine
192
+ // 5. Fetch LobeHub Skills manifests (temporary solution until LOBE-3517 is implemented)
193
+ const lobehubSkillManifests = await this.fetchLobehubSkillManifests();
194
+ log('execAgent: got %d lobehub skill manifests', lobehubSkillManifests.length);
195
+
196
+ // 6. Create tools using Server AgentToolsEngine
162
197
  const hasEnabledKnowledgeBases =
163
198
  agentConfig.knowledgeBases?.some((kb: { enabled?: boolean | null }) => kb.enabled === true) ??
164
199
  false;
@@ -169,6 +204,7 @@ export class AiAgentService {
169
204
  };
170
205
 
171
206
  const toolsEngine = createServerAgentToolsEngine(toolsContext, {
207
+ additionalManifests: lobehubSkillManifests,
172
208
  agentConfig: {
173
209
  chatConfig: agentConfig.chatConfig ?? undefined,
174
210
  plugins: agentConfig.plugins ?? undefined,
@@ -180,6 +216,8 @@ export class AiAgentService {
180
216
 
181
217
  // Generate tools and manifest map
182
218
  const pluginIds = agentConfig.plugins || [];
219
+ log('execAgent: agent configured plugins: %O', pluginIds);
220
+
183
221
  const toolsResult = toolsEngine.generateToolsDetailed({
184
222
  model,
185
223
  provider,
@@ -188,6 +226,12 @@ export class AiAgentService {
188
226
 
189
227
  const tools = toolsResult.tools;
190
228
 
229
+ // Log detailed tools generation result
230
+ if (toolsResult.filteredTools && toolsResult.filteredTools.length > 0) {
231
+ log('execAgent: filtered tools: %O', toolsResult.filteredTools);
232
+ }
233
+ log('execAgent: enabled tool ids: %O', toolsResult.enabledToolIds);
234
+
191
235
  // Get manifest map and convert from Map to Record
192
236
  const manifestMap = toolsEngine.getEnabledPluginManifests(pluginIds);
193
237
  const toolManifestMap: Record<string, any> = {};
@@ -195,7 +239,20 @@ export class AiAgentService {
195
239
  toolManifestMap[id] = manifest;
196
240
  });
197
241
 
198
- log('execAgent: generated %d tools', tools?.length ?? 0);
242
+ // Build toolSourceMap for routing tool execution
243
+ const toolSourceMap: Record<string, 'builtin' | 'plugin' | 'mcp' | 'klavis' | 'lobehubSkill'> =
244
+ {};
245
+ // Mark lobehub skills
246
+ for (const manifest of lobehubSkillManifests) {
247
+ toolSourceMap[manifest.identifier] = 'lobehubSkill';
248
+ }
249
+
250
+ log(
251
+ 'execAgent: generated %d tools from %d configured plugins, %d lobehub skills',
252
+ tools?.length ?? 0,
253
+ pluginIds.length,
254
+ lobehubSkillManifests.length,
255
+ );
199
256
 
200
257
  // 6. Get existing messages if provided
201
258
  let historyMessages: any[] = [];
@@ -301,7 +358,18 @@ export class AiAgentService {
301
358
  },
302
359
  };
303
360
 
304
- // 12. Create operation using AgentRuntimeService
361
+ // 12. Log final operation parameters summary
362
+ log(
363
+ 'execAgent: creating operation %s with params: model=%s, provider=%s, tools=%d, messages=%d, manifests=%d',
364
+ operationId,
365
+ model,
366
+ provider,
367
+ tools?.length ?? 0,
368
+ processedMessages.length,
369
+ Object.keys(toolManifestMap).length,
370
+ );
371
+
372
+ // 13. Create operation using AgentRuntimeService
305
373
  // Wrap in try-catch to handle operation startup failures (e.g., QStash unavailable)
306
374
  // If createOperation fails, we still have valid messages that need error info
307
375
  try {
@@ -320,6 +388,7 @@ export class AiAgentService {
320
388
  operationId,
321
389
  stepCallbacks,
322
390
  toolManifestMap,
391
+ toolSourceMap,
323
392
  tools,
324
393
  userId: this.userId,
325
394
  });
@@ -616,6 +685,11 @@ export class AiAgentService {
616
685
  }
617
686
  }
618
687
 
688
+ // Log error when task fails
689
+ if (reason === 'error' && finalState.error) {
690
+ console.error('execSubAgentTask: task failed for thread %s:', threadId, finalState.error);
691
+ }
692
+
619
693
  try {
620
694
  // Extract summary from last assistant message and update task message content
621
695
  const lastAssistantMessage = finalState.messages
@@ -630,12 +704,15 @@ export class AiAgentService {
630
704
  log('execSubAgentTask: updated task message %s with summary', sourceMessageId);
631
705
  }
632
706
 
707
+ // Format error for proper serialization (Error objects don't serialize with JSON.stringify)
708
+ const formattedError = formatErrorForMetadata(finalState.error);
709
+
633
710
  // Update Thread metadata
634
711
  await this.threadModel.update(threadId, {
635
712
  metadata: {
636
713
  completedAt,
637
714
  duration,
638
- error: finalState.error,
715
+ error: formattedError,
639
716
  operationId: finalState.operationId,
640
717
  startedAt,
641
718
  totalCost: finalState.cost?.total,
@@ -659,6 +736,98 @@ export class AiAgentService {
659
736
  };
660
737
  }
661
738
 
739
+ /**
740
+ * Fetch LobeHub Skills manifests from Market API
741
+ * This is a temporary solution until LOBE-3517 is implemented (store skills in DB)
742
+ */
743
+ private async fetchLobehubSkillManifests(): Promise<LobeToolManifest[]> {
744
+ try {
745
+ // 1. Get user info for trusted client token
746
+ const user = await UserModel.findById(this.db, this.userId);
747
+ if (!user?.email) {
748
+ log('fetchLobehubSkillManifests: user email not found, skipping');
749
+ return [];
750
+ }
751
+
752
+ // 2. Generate trusted client token
753
+ const trustedClientToken = generateTrustedClientToken({
754
+ email: user.email,
755
+ name: user.fullName || user.firstName || undefined,
756
+ userId: this.userId,
757
+ });
758
+
759
+ if (!trustedClientToken) {
760
+ log('fetchLobehubSkillManifests: trusted client not configured, skipping');
761
+ return [];
762
+ }
763
+
764
+ // 3. Create MarketSDK instance
765
+ const marketSDK = new MarketSDK({
766
+ baseURL: process.env.NEXT_PUBLIC_MARKET_BASE_URL,
767
+ trustedClientToken,
768
+ });
769
+
770
+ // 4. Get user's connected skills
771
+ const { connections } = await marketSDK.connect.listConnections();
772
+ if (!connections || connections.length === 0) {
773
+ log('fetchLobehubSkillManifests: no connected skills found');
774
+ return [];
775
+ }
776
+
777
+ log('fetchLobehubSkillManifests: found %d connected skills', connections.length);
778
+
779
+ // 5. Fetch tools for each connection and build manifests
780
+ const manifests: LobeToolManifest[] = [];
781
+
782
+ for (const connection of connections) {
783
+ try {
784
+ // Connection returns providerId (e.g., 'twitter', 'linear'), not numeric id
785
+ const providerId = (connection as any).providerId;
786
+ if (!providerId) {
787
+ log('fetchLobehubSkillManifests: connection missing providerId: %O', connection);
788
+ continue;
789
+ }
790
+ const providerName =
791
+ (connection as any).providerName || (connection as any).name || providerId;
792
+ const icon = (connection as any).icon;
793
+
794
+ const { tools } = await marketSDK.skills.listTools(providerId);
795
+ if (!tools || tools.length === 0) continue;
796
+
797
+ const manifest: LobeToolManifest = {
798
+ api: tools.map((tool: any) => ({
799
+ description: tool.description || '',
800
+ name: tool.name,
801
+ parameters: tool.inputSchema || { properties: {}, type: 'object' },
802
+ })),
803
+ identifier: providerId,
804
+ meta: {
805
+ avatar: icon || '🔗',
806
+ description: `LobeHub Skill: ${providerName}`,
807
+ tags: ['lobehub-skill', providerId],
808
+ title: providerName,
809
+ },
810
+ type: 'builtin',
811
+ };
812
+
813
+ manifests.push(manifest);
814
+ log(
815
+ 'fetchLobehubSkillManifests: built manifest for %s with %d tools',
816
+ providerId,
817
+ tools.length,
818
+ );
819
+ } catch (error) {
820
+ log('fetchLobehubSkillManifests: failed to fetch tools for connection: %O', error);
821
+ }
822
+ }
823
+
824
+ return manifests;
825
+ } catch (error) {
826
+ log('fetchLobehubSkillManifests: error fetching skills: %O', error);
827
+ return [];
828
+ }
829
+ }
830
+
662
831
  /**
663
832
  * Calculate total tokens from AgentState usage object
664
833
  * AgentState.usage is of type Usage from @lobechat/agent-runtime
@@ -0,0 +1,109 @@
1
+ import { type LobeChatDatabase } from '@lobechat/database';
2
+ import { MarketSDK } from '@lobehub/market-sdk';
3
+ import debug from 'debug';
4
+
5
+ import { UserModel } from '@/database/models/user';
6
+ import { generateTrustedClientToken } from '@/libs/trusted-client';
7
+
8
+ const log = debug('lobe-server:lobehub-skill-service');
9
+
10
+ export interface LobehubSkillExecuteParams {
11
+ args: Record<string, any>;
12
+ provider: string;
13
+ toolName: string;
14
+ }
15
+
16
+ export interface LobehubSkillExecuteResult {
17
+ content: string;
18
+ error?: { code: string; message?: string };
19
+ success: boolean;
20
+ }
21
+
22
+ export class LobehubSkillService {
23
+ private db: LobeChatDatabase;
24
+ private userId: string;
25
+ private marketSDK?: MarketSDK;
26
+
27
+ constructor(db: LobeChatDatabase, userId: string) {
28
+ this.db = db;
29
+ this.userId = userId;
30
+ }
31
+
32
+ /**
33
+ * Initialize MarketSDK with trusted client token
34
+ */
35
+ private async getMarketSDK(): Promise<MarketSDK | null> {
36
+ if (this.marketSDK) return this.marketSDK;
37
+
38
+ try {
39
+ const user = await UserModel.findById(this.db, this.userId);
40
+ if (!user?.email) {
41
+ log('getMarketSDK: user email not found');
42
+ return null;
43
+ }
44
+
45
+ const trustedClientToken = generateTrustedClientToken({
46
+ email: user.email,
47
+ name: user.fullName || user.firstName || undefined,
48
+ userId: this.userId,
49
+ });
50
+
51
+ if (!trustedClientToken) {
52
+ log('getMarketSDK: trusted client not configured');
53
+ return null;
54
+ }
55
+
56
+ this.marketSDK = new MarketSDK({
57
+ baseURL: process.env.NEXT_PUBLIC_MARKET_BASE_URL,
58
+ trustedClientToken,
59
+ });
60
+
61
+ return this.marketSDK;
62
+ } catch (error) {
63
+ log('getMarketSDK: error creating SDK: %O', error);
64
+ return null;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Execute a LobeHub Skill tool
70
+ */
71
+ async execute(params: LobehubSkillExecuteParams): Promise<LobehubSkillExecuteResult> {
72
+ const { provider, toolName, args } = params;
73
+
74
+ log('execute: %s/%s with args: %O', provider, toolName, args);
75
+
76
+ const sdk = await this.getMarketSDK();
77
+ if (!sdk) {
78
+ return {
79
+ content:
80
+ 'MarketSDK not available. Please ensure you are authenticated with LobeHub Market.',
81
+ error: { code: 'MARKET_SDK_NOT_AVAILABLE' },
82
+ success: false,
83
+ };
84
+ }
85
+
86
+ try {
87
+ const response = await sdk.skills.callTool(provider, {
88
+ args,
89
+ tool: toolName,
90
+ });
91
+
92
+ log('execute: response: %O', response);
93
+
94
+ return {
95
+ content: typeof response.data === 'string' ? response.data : JSON.stringify(response.data),
96
+ success: response.success,
97
+ };
98
+ } catch (error) {
99
+ const err = error as Error;
100
+ console.error('LobehubSkillService.execute error %s/%s: %O', provider, toolName, err);
101
+
102
+ return {
103
+ content: err.message,
104
+ error: { code: 'LOBEHUB_SKILL_ERROR', message: err.message },
105
+ success: false,
106
+ };
107
+ }
108
+ }
109
+ }