@lobehub/lobehub 2.0.0-next.46 → 2.0.0-next.48

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 (254) hide show
  1. package/.env.example +11 -0
  2. package/CHANGELOG.md +42 -0
  3. package/apps/desktop/src/main/controllers/AuthCtr.ts +27 -2
  4. package/apps/desktop/src/main/core/infrastructure/ProtocolManager.ts +9 -4
  5. package/changelog/v1.json +14 -0
  6. package/docs/development/database-schema.dbml +2 -0
  7. package/docs/self-hosting/environment-variables/basic.mdx +49 -3
  8. package/docs/self-hosting/environment-variables/basic.zh-CN.mdx +49 -4
  9. package/locales/ar/discover.json +45 -0
  10. package/locales/ar/marketAuth.json +42 -0
  11. package/locales/ar/setting.json +94 -1
  12. package/locales/bg-BG/discover.json +45 -0
  13. package/locales/bg-BG/marketAuth.json +42 -0
  14. package/locales/bg-BG/setting.json +94 -1
  15. package/locales/de-DE/discover.json +45 -0
  16. package/locales/de-DE/marketAuth.json +42 -0
  17. package/locales/de-DE/setting.json +94 -1
  18. package/locales/en-US/discover.json +45 -0
  19. package/locales/en-US/marketAuth.json +42 -0
  20. package/locales/en-US/setting.json +94 -1
  21. package/locales/es-ES/discover.json +45 -0
  22. package/locales/es-ES/marketAuth.json +42 -0
  23. package/locales/es-ES/setting.json +94 -1
  24. package/locales/fa-IR/discover.json +45 -0
  25. package/locales/fa-IR/marketAuth.json +42 -0
  26. package/locales/fa-IR/setting.json +94 -1
  27. package/locales/fr-FR/discover.json +45 -0
  28. package/locales/fr-FR/marketAuth.json +42 -0
  29. package/locales/fr-FR/setting.json +94 -1
  30. package/locales/it-IT/discover.json +45 -0
  31. package/locales/it-IT/marketAuth.json +42 -0
  32. package/locales/it-IT/setting.json +94 -1
  33. package/locales/ja-JP/discover.json +45 -0
  34. package/locales/ja-JP/marketAuth.json +42 -0
  35. package/locales/ja-JP/setting.json +94 -1
  36. package/locales/ko-KR/discover.json +45 -0
  37. package/locales/ko-KR/marketAuth.json +42 -0
  38. package/locales/ko-KR/setting.json +94 -1
  39. package/locales/nl-NL/discover.json +45 -0
  40. package/locales/nl-NL/marketAuth.json +42 -0
  41. package/locales/nl-NL/setting.json +94 -1
  42. package/locales/pl-PL/discover.json +45 -0
  43. package/locales/pl-PL/marketAuth.json +42 -0
  44. package/locales/pl-PL/setting.json +94 -1
  45. package/locales/pt-BR/discover.json +45 -0
  46. package/locales/pt-BR/marketAuth.json +42 -0
  47. package/locales/pt-BR/setting.json +94 -1
  48. package/locales/ru-RU/discover.json +45 -0
  49. package/locales/ru-RU/marketAuth.json +42 -0
  50. package/locales/ru-RU/setting.json +94 -1
  51. package/locales/tr-TR/discover.json +45 -0
  52. package/locales/tr-TR/marketAuth.json +42 -0
  53. package/locales/tr-TR/setting.json +94 -1
  54. package/locales/vi-VN/discover.json +45 -0
  55. package/locales/vi-VN/marketAuth.json +42 -0
  56. package/locales/vi-VN/setting.json +94 -1
  57. package/locales/zh-CN/discover.json +45 -0
  58. package/locales/zh-CN/marketAuth.json +42 -0
  59. package/locales/zh-CN/setting.json +94 -1
  60. package/locales/zh-TW/discover.json +45 -0
  61. package/locales/zh-TW/marketAuth.json +42 -0
  62. package/locales/zh-TW/setting.json +94 -1
  63. package/package.json +27 -26
  64. package/packages/const/src/url.ts +1 -0
  65. package/packages/database/migrations/0044_add_tool_intervention.sql +1 -0
  66. package/packages/database/migrations/0044_high_toxin.sql +1 -0
  67. package/packages/database/migrations/0045_add_tool_intervention.sql +1 -0
  68. package/packages/database/migrations/meta/0039_snapshot.json +1 -1
  69. package/packages/database/migrations/meta/0044_snapshot.json +7813 -0
  70. package/packages/database/migrations/meta/0045_snapshot.json +8431 -0
  71. package/packages/database/migrations/meta/_journal.json +14 -0
  72. package/packages/database/src/core/migrations.json +36 -7
  73. package/packages/database/src/models/file.ts +15 -1
  74. package/packages/database/src/models/message.ts +1 -1
  75. package/packages/database/src/models/session.ts +42 -1
  76. package/packages/database/src/repositories/aiInfra/index.test.ts +1 -1
  77. package/packages/database/src/repositories/dataExporter/index.test.ts +1 -1
  78. package/packages/database/src/repositories/tableViewer/index.test.ts +1 -1
  79. package/packages/database/src/schemas/agent.ts +1 -0
  80. package/packages/database/src/schemas/message.ts +5 -8
  81. package/packages/electron-client-ipc/src/events/index.ts +6 -1
  82. package/packages/electron-client-ipc/src/events/remoteServer.ts +8 -0
  83. package/packages/fetch-sse/package.json +29 -0
  84. package/packages/{utils/src/fetch → fetch-sse/src}/__tests__/fetchSSE.test.ts +4 -4
  85. package/packages/{utils/src/fetch → fetch-sse/src}/__tests__/parseError.test.ts +7 -4
  86. package/packages/{utils/src/fetch → fetch-sse/src}/fetchSSE.ts +2 -2
  87. package/packages/{utils/src/fetch → fetch-sse/src}/parseError.ts +3 -3
  88. package/packages/model-bank/src/aiModels/mistral.ts +2 -1
  89. package/packages/model-runtime/src/core/contextBuilders/anthropic.test.ts +17 -11
  90. package/packages/model-runtime/src/core/contextBuilders/anthropic.ts +1 -1
  91. package/packages/model-runtime/src/core/contextBuilders/google.test.ts +1 -1
  92. package/packages/model-runtime/src/core/contextBuilders/google.ts +3 -6
  93. package/packages/model-runtime/src/core/contextBuilders/openai.test.ts +4 -2
  94. package/packages/model-runtime/src/core/contextBuilders/openai.ts +1 -1
  95. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.test.ts +1 -1
  96. package/packages/model-runtime/src/core/openaiCompatibleFactory/createImage.ts +1 -1
  97. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.test.ts +3 -6
  98. package/packages/model-runtime/src/core/streams/openai/responsesStream.test.ts +1 -1
  99. package/packages/model-runtime/src/helpers/mergeChatMethodOptions.ts +2 -1
  100. package/packages/model-runtime/src/providers/aihubmix/index.test.ts +1 -1
  101. package/packages/model-runtime/src/providers/anthropic/generateObject.test.ts +1 -1
  102. package/packages/model-runtime/src/providers/anthropic/index.test.ts +1 -1
  103. package/packages/model-runtime/src/providers/baichuan/index.test.ts +1 -1
  104. package/packages/model-runtime/src/providers/bedrock/index.test.ts +1 -1
  105. package/packages/model-runtime/src/providers/bfl/createImage.test.ts +4 -4
  106. package/packages/model-runtime/src/providers/bfl/createImage.ts +1 -1
  107. package/packages/model-runtime/src/providers/cloudflare/index.test.ts +1 -1
  108. package/packages/model-runtime/src/providers/cohere/index.test.ts +1 -1
  109. package/packages/model-runtime/src/providers/google/createImage.test.ts +2 -2
  110. package/packages/model-runtime/src/providers/google/createImage.ts +1 -1
  111. package/packages/model-runtime/src/providers/google/generateObject.test.ts +1 -1
  112. package/packages/model-runtime/src/providers/google/index.test.ts +1 -4
  113. package/packages/model-runtime/src/providers/groq/index.test.ts +1 -1
  114. package/packages/model-runtime/src/providers/hunyuan/index.test.ts +1 -1
  115. package/packages/model-runtime/src/providers/minimax/createImage.test.ts +1 -1
  116. package/packages/model-runtime/src/providers/mistral/index.test.ts +1 -1
  117. package/packages/model-runtime/src/providers/moonshot/index.test.ts +1 -1
  118. package/packages/model-runtime/src/providers/novita/index.test.ts +1 -1
  119. package/packages/model-runtime/src/providers/ollama/index.test.ts +43 -32
  120. package/packages/model-runtime/src/providers/ollama/index.ts +31 -7
  121. package/packages/model-runtime/src/providers/openrouter/index.test.ts +1 -1
  122. package/packages/model-runtime/src/providers/perplexity/index.test.ts +1 -1
  123. package/packages/model-runtime/src/providers/ppio/index.test.ts +1 -1
  124. package/packages/model-runtime/src/providers/qwen/createImage.test.ts +1 -1
  125. package/packages/model-runtime/src/providers/search1api/index.test.ts +1 -1
  126. package/packages/model-runtime/src/providers/siliconcloud/createImage.ts +1 -1
  127. package/packages/model-runtime/src/providers/taichu/index.test.ts +1 -1
  128. package/packages/model-runtime/src/providers/wenxin/index.test.ts +1 -1
  129. package/packages/model-runtime/src/providers/zhipu/index.test.ts +1 -1
  130. package/packages/model-runtime/src/utils/errorResponse.test.ts +1 -1
  131. package/packages/ssrf-safe-fetch/index.browser.ts +14 -0
  132. package/packages/ssrf-safe-fetch/package.json +8 -1
  133. package/packages/types/src/aiProvider.ts +1 -1
  134. package/packages/types/src/discover/assistants.ts +16 -0
  135. package/packages/types/src/document/index.ts +38 -38
  136. package/packages/types/src/exportConfig.ts +15 -15
  137. package/packages/types/src/generation/index.ts +5 -5
  138. package/packages/types/src/index.ts +1 -0
  139. package/packages/types/src/message/common/tools.ts +10 -0
  140. package/packages/types/src/message/db/item.ts +15 -1
  141. package/packages/types/src/message/ui/params.ts +15 -1
  142. package/packages/types/src/meta.ts +4 -0
  143. package/packages/types/src/openai/chat.ts +15 -15
  144. package/packages/types/src/plugins/mcp.ts +29 -29
  145. package/packages/types/src/plugins/protocol.ts +43 -43
  146. package/packages/types/src/search.ts +4 -4
  147. package/packages/types/src/session/agentSession.ts +2 -0
  148. package/packages/types/src/tool/plugin.ts +3 -3
  149. package/packages/utils/src/imageToBase64.ts +17 -10
  150. package/packages/utils/src/index.ts +1 -1
  151. package/src/app/(backend)/f/[id]/route.ts +55 -0
  152. package/src/app/(backend)/market/agent/[[...segments]]/route.ts +153 -0
  153. package/src/app/(backend)/market/oidc/[[...segments]]/route.ts +207 -0
  154. package/src/app/[variants]/(main)/(mobile)/me/settings/features/useCategory.tsx +1 -0
  155. package/src/app/[variants]/(main)/_layout/Desktop/SideBar/PinList/index.tsx +4 -2
  156. package/src/app/[variants]/(main)/chat/settings/features/AgentInfoDescription/index.tsx +349 -0
  157. package/src/app/[variants]/(main)/chat/settings/features/HeaderContent.tsx +2 -2
  158. package/src/app/[variants]/(main)/chat/settings/features/PublishResultModal/index.tsx +64 -0
  159. package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishButton.tsx +196 -0
  160. package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/MarketPublishModal.tsx +358 -0
  161. package/src/app/[variants]/(main)/chat/settings/features/SmartAgentActionButton/index.tsx +75 -0
  162. package/src/app/[variants]/(main)/discover/(detail)/assistant/AssistantDetailPage.tsx +11 -2
  163. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/Client.tsx +12 -1
  164. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Nav.tsx +19 -12
  165. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Overview/TagList.tsx +14 -5
  166. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Overview/index.tsx +2 -0
  167. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Related/index.tsx +14 -5
  168. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/SystemRole/TagList.tsx +14 -5
  169. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/SystemRole/index.tsx +43 -29
  170. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/Versions/index.tsx +137 -0
  171. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Details/index.tsx +2 -0
  172. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Header.tsx +9 -10
  173. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/ActionButton/AddAgent.tsx +105 -14
  174. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/Sidebar/Related/index.tsx +20 -6
  175. package/src/app/[variants]/(main)/discover/(detail)/assistant/[...slugs]/features/StatusPage/index.tsx +113 -0
  176. package/src/app/[variants]/(main)/discover/(detail)/features/Breadcrumb.tsx +4 -3
  177. package/src/app/[variants]/(main)/discover/(list)/_layout/Desktop/Nav.tsx +3 -1
  178. package/src/app/[variants]/(main)/discover/(list)/assistant/AssistantPage.tsx +4 -1
  179. package/src/app/[variants]/(main)/discover/(list)/assistant/Client.tsx +6 -2
  180. package/src/app/[variants]/(main)/discover/(list)/assistant/features/Category/index.tsx +7 -3
  181. package/src/app/[variants]/(main)/discover/(list)/assistant/features/List/Item.tsx +13 -2
  182. package/src/app/[variants]/(main)/discover/(list)/assistant/features/MarketSourceSwitch.tsx +64 -0
  183. package/src/app/[variants]/(main)/discover/(list)/features/SortButton/index.tsx +26 -7
  184. package/src/app/[variants]/(main)/profile/_layout/Desktop/index.tsx +10 -10
  185. package/src/app/[variants]/(main)/settings/_layout/type.ts +1 -1
  186. package/src/app/[variants]/(main)/settings/agent/index.tsx +11 -10
  187. package/src/app/[variants]/(main)/settings/common/index.tsx +1 -1
  188. package/src/app/[variants]/(main)/settings/page.tsx +13 -10
  189. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/Item.tsx +35 -36
  190. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/SearchResult.tsx +5 -5
  191. package/src/app/[variants]/(main)/settings/provider/_layout/Desktop/Container.tsx +10 -4
  192. package/src/app/market-auth-callback/layout.tsx +15 -0
  193. package/src/app/market-auth-callback/page.tsx +196 -0
  194. package/src/envs/app.ts +4 -3
  195. package/src/features/AgentSetting/AgentPrompt/TokenTag.tsx +3 -2
  196. package/src/features/AgentSetting/AgentTTS/SelectWithTTSPreview.tsx +1 -1
  197. package/src/features/AgentSetting/store/action.ts +1 -1
  198. package/src/features/ChatInput/ActionBar/STT/browser.tsx +1 -1
  199. package/src/features/ChatInput/ActionBar/STT/openai.tsx +1 -1
  200. package/src/features/Conversation/components/Extras/TTS/InitPlayer.tsx +1 -1
  201. package/src/features/Conversation/components/VirtualizedList/index.tsx +2 -1
  202. package/src/features/PluginTag/PluginStatus.tsx +1 -1
  203. package/src/features/PluginsUI/Render/MCPType/index.tsx +26 -6
  204. package/src/hooks/useAgentOwnershipCheck.ts +143 -0
  205. package/src/instrumentation.node.ts +3 -2
  206. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +364 -0
  207. package/src/layout/AuthProvider/MarketAuth/errors.ts +75 -0
  208. package/src/layout/AuthProvider/MarketAuth/index.ts +2 -0
  209. package/src/layout/AuthProvider/MarketAuth/oidc.ts +382 -0
  210. package/src/layout/AuthProvider/MarketAuth/types.ts +64 -0
  211. package/src/layout/AuthProvider/index.tsx +17 -4
  212. package/src/locales/default/discover.ts +46 -0
  213. package/src/locales/default/index.ts +2 -0
  214. package/src/locales/default/marketAuth.ts +42 -0
  215. package/src/locales/default/setting.ts +94 -1
  216. package/src/server/globalConfig/genServerAiProviderConfig.test.ts +5 -5
  217. package/src/server/globalConfig/genServerAiProviderConfig.ts +1 -1
  218. package/src/server/routers/desktop/mcp.ts +23 -8
  219. package/src/server/routers/lambda/market/index.ts +36 -14
  220. package/src/server/routers/lambda/message.ts +2 -2
  221. package/src/server/routers/tools/mcp.ts +24 -4
  222. package/src/server/services/discover/index.test.ts +153 -11
  223. package/src/server/services/discover/index.ts +339 -40
  224. package/src/server/services/file/impls/local.ts +4 -1
  225. package/src/server/services/file/index.ts +96 -1
  226. package/src/server/services/mcp/contentProcessor.ts +101 -0
  227. package/src/server/services/mcp/index.test.ts +52 -10
  228. package/src/server/services/mcp/index.ts +29 -26
  229. package/src/server/sitemap.ts +49 -35
  230. package/src/services/_url.ts +15 -1
  231. package/src/services/chat/chat.test.ts +5 -5
  232. package/src/services/chat/clientModelRuntime.test.ts +1 -1
  233. package/src/services/chat/index.ts +6 -6
  234. package/src/services/chat/types.ts +1 -2
  235. package/src/services/discover.ts +16 -5
  236. package/src/services/electron/remoteServer.ts +8 -1
  237. package/src/services/marketApi.ts +124 -0
  238. package/src/services/models.ts +2 -1
  239. package/src/services/session/index.ts +0 -14
  240. package/src/store/discover/slices/assistant/action.ts +20 -7
  241. package/{packages/utils/src → src/utils}/electron/desktopRemoteRPCFetch.ts +1 -1
  242. package/{packages/utils/src → src/utils/server}/parseModels.ts +1 -2
  243. package/src/utils/server/routeVariants.test.ts +340 -0
  244. package/vitest.config.mts +2 -0
  245. package/packages/model-runtime/src/utils/imageToBase64.test.ts +0 -91
  246. package/packages/model-runtime/src/utils/imageToBase64.ts +0 -62
  247. package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/SubmitAgentModal.tsx +0 -98
  248. package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/index.tsx +0 -35
  249. package/src/app/[variants]/(main)/chat/settings/features/SubmitAgentButton/style.ts +0 -47
  250. /package/packages/{utils/src/fetch → fetch-sse/src}/headers.ts +0 -0
  251. /package/packages/{utils/src/fetch → fetch-sse/src}/index.ts +0 -0
  252. /package/packages/{utils/src/fetch → fetch-sse/src}/request.ts +0 -0
  253. /package/{packages/utils/src → src/utils/server}/__snapshots__/parseModels.test.ts.snap +0 -0
  254. /package/{packages/utils/src → src/utils/server}/parseModels.test.ts +0 -0
@@ -0,0 +1,101 @@
1
+ import debug from 'debug';
2
+ import pMap from 'p-map';
3
+ import urlJoin from 'url-join';
4
+
5
+ import { appEnv } from '@/envs/app';
6
+ import { fileEnv } from '@/envs/file';
7
+ import { AudioContent, ImageContent, ToolCallContent } from '@/libs/mcp';
8
+ import { FileService } from '@/server/services/file';
9
+ import { nanoid } from '@/utils/uuid';
10
+
11
+ const log = debug('lobe-mcp:content-processor');
12
+
13
+ export type ProcessContentBlocksFn = (blocks: ToolCallContent[]) => Promise<ToolCallContent[]>;
14
+
15
+ /**
16
+ * 处理 MCP 返回的 content blocks
17
+ * - 上传图片/音频到存储并替换 data 为代理 URL
18
+ * - 保持其他类型的 block 不变
19
+ */
20
+ export const processContentBlocks = async (
21
+ blocks: ToolCallContent[],
22
+ fileService: FileService,
23
+ ): Promise<ToolCallContent[]> => {
24
+ // Use date-based sharding for privacy compliance (GDPR, CCPA)
25
+ const today = new Date().toISOString().split('T')[0]; // e.g., "2025-11-08"
26
+
27
+ return pMap(blocks, async (block) => {
28
+ if (block.type === 'image') {
29
+ const imageBlock = block as ImageContent;
30
+
31
+ // Extract file extension from mimeType (e.g., "image/png" -> "png")
32
+ const fileExtension = imageBlock.mimeType.split('/')[1] || 'png';
33
+
34
+ // Generate unique pathname with date-based sharding
35
+ const pathname = `${fileEnv.NEXT_PUBLIC_S3_FILE_PATH}/mcp/images/${today}/${nanoid()}.${fileExtension}`;
36
+
37
+ // Upload base64 image and get proxy URL
38
+ const { url } = await fileService.uploadBase64(imageBlock.data, pathname);
39
+
40
+ log(`Image uploaded, proxy URL: ${url}`);
41
+
42
+ return { ...block, data: url };
43
+ }
44
+
45
+ if (block.type === 'audio') {
46
+ const audioBlock = block as AudioContent;
47
+
48
+ // Extract file extension from mimeType (e.g., "audio/mp3" -> "mp3")
49
+ const fileExtension = audioBlock.mimeType.split('/')[1] || 'mp3';
50
+
51
+ // Generate unique pathname with date-based sharding
52
+ const pathname = `${fileEnv.NEXT_PUBLIC_S3_FILE_PATH}/mcp/audio/${today}/${nanoid()}.${fileExtension}`;
53
+
54
+ // Upload base64 audio and get proxy URL
55
+ const { url } = await fileService.uploadBase64(audioBlock.data, pathname);
56
+
57
+ log(`Audio uploaded, proxy URL: ${url}`);
58
+
59
+ return { ...block, data: url };
60
+ }
61
+
62
+ return block;
63
+ });
64
+ };
65
+
66
+ /**
67
+ * 将 content blocks 转换为字符串
68
+ * - text: 提取 text 字段
69
+ * - image/audio: 提取 data 字段(通常是上传后的代理 URL)
70
+ * - 其他: 返回空字符串
71
+ */
72
+ export const contentBlocksToString = (blocks: ToolCallContent[] | null | undefined): string => {
73
+ if (!blocks) return '';
74
+
75
+ return blocks
76
+ .map((item) => {
77
+ switch (item.type) {
78
+ case 'text': {
79
+ return item.text;
80
+ }
81
+
82
+ case 'image': {
83
+ return `![](${urlJoin(appEnv.APP_URL, item.data)})`;
84
+ }
85
+
86
+ case 'audio': {
87
+ return `<resource type="${item.type}" url="${urlJoin(appEnv.APP_URL, item.data)}" />`;
88
+ }
89
+
90
+ case 'resource': {
91
+ return `<resource type="${item.type}">${JSON.stringify(item.resource)}</resource>}`;
92
+ }
93
+
94
+ default: {
95
+ return '';
96
+ }
97
+ }
98
+ })
99
+ .filter(Boolean)
100
+ .join('\n\n');
101
+ };
@@ -39,7 +39,11 @@ describe('MCPService', () => {
39
39
  isError: false,
40
40
  });
41
41
 
42
- const result = await mcpService.callTool(mockParams, 'testTool', '{}');
42
+ const result = await mcpService.callTool({
43
+ clientParams: mockParams,
44
+ toolName: 'testTool',
45
+ argsStr: '{}',
46
+ });
43
47
 
44
48
  expect(result.content).toBe('');
45
49
  expect(result.success).toBe(true);
@@ -52,7 +56,11 @@ describe('MCPService', () => {
52
56
  isError: false,
53
57
  });
54
58
 
55
- const result = await mcpService.callTool(mockParams, 'testTool', '{}');
59
+ const result = await mcpService.callTool({
60
+ clientParams: mockParams,
61
+ toolName: 'testTool',
62
+ argsStr: '{}',
63
+ });
56
64
 
57
65
  expect(result.content).toBe('');
58
66
  expect(result.success).toBe(true);
@@ -65,7 +73,11 @@ describe('MCPService', () => {
65
73
  isError: false,
66
74
  });
67
75
 
68
- const result = await mcpService.callTool(mockParams, 'testTool', '{}');
76
+ const result = await mcpService.callTool({
77
+ clientParams: mockParams,
78
+ toolName: 'testTool',
79
+ argsStr: '{}',
80
+ });
69
81
 
70
82
  expect(result.content).toBe(JSON.stringify(jsonData));
71
83
  expect(result.success).toBe(true);
@@ -78,7 +90,11 @@ describe('MCPService', () => {
78
90
  isError: false,
79
91
  });
80
92
 
81
- const result = await mcpService.callTool(mockParams, 'testTool', '{}');
93
+ const result = await mcpService.callTool({
94
+ clientParams: mockParams,
95
+ toolName: 'testTool',
96
+ argsStr: '{}',
97
+ });
82
98
 
83
99
  expect(result.content).toBe(textData);
84
100
  expect(result.success).toBe(true);
@@ -92,7 +108,11 @@ describe('MCPService', () => {
92
108
  isError: false,
93
109
  });
94
110
 
95
- const result = await mcpService.callTool(mockParams, 'testTool', '{}');
111
+ const result = await mcpService.callTool({
112
+ clientParams: mockParams,
113
+ toolName: 'testTool',
114
+ argsStr: '{}',
115
+ });
96
116
 
97
117
  expect(result.content).toBe('');
98
118
  expect(result.success).toBe(true);
@@ -111,7 +131,11 @@ describe('MCPService', () => {
111
131
  isError: false,
112
132
  });
113
133
 
114
- const result = await mcpService.callTool(mockParams, 'testTool', '{}');
134
+ const result = await mcpService.callTool({
135
+ clientParams: mockParams,
136
+ toolName: 'testTool',
137
+ argsStr: '{}',
138
+ });
115
139
 
116
140
  expect(result.content).toBe('First message\n\nSecond message\n\n{"json": "data"}');
117
141
  expect(result.success).toBe(true);
@@ -129,7 +153,11 @@ describe('MCPService', () => {
129
153
  isError: false,
130
154
  });
131
155
 
132
- const result = await mcpService.callTool(mockParams, 'testTool', '{}');
156
+ const result = await mcpService.callTool({
157
+ clientParams: mockParams,
158
+ toolName: 'testTool',
159
+ argsStr: '{}',
160
+ });
133
161
 
134
162
  expect(result.content).toBe('First message\n\nSecond message');
135
163
  expect(result.success).toBe(true);
@@ -144,7 +172,11 @@ describe('MCPService', () => {
144
172
 
145
173
  mockClient.callTool.mockResolvedValue(errorResult);
146
174
 
147
- const result = await mcpService.callTool(mockParams, 'testTool', '{}');
175
+ const result = await mcpService.callTool({
176
+ clientParams: mockParams,
177
+ toolName: 'testTool',
178
+ argsStr: '{}',
179
+ });
148
180
 
149
181
  expect(result.content).toBe('Error occurred');
150
182
  expect(result.success).toBe(true);
@@ -155,7 +187,13 @@ describe('MCPService', () => {
155
187
  const error = new Error('MCP client error');
156
188
  mockClient.callTool.mockRejectedValue(error);
157
189
 
158
- await expect(mcpService.callTool(mockParams, 'testTool', '{}')).rejects.toThrow(TRPCError);
190
+ await expect(
191
+ mcpService.callTool({
192
+ clientParams: mockParams,
193
+ toolName: 'testTool',
194
+ argsStr: '{}',
195
+ }),
196
+ ).rejects.toThrow(TRPCError);
159
197
  });
160
198
 
161
199
  it('should parse args string correctly', async () => {
@@ -167,7 +205,11 @@ describe('MCPService', () => {
167
205
  isError: false,
168
206
  });
169
207
 
170
- await mcpService.callTool(mockParams, 'testTool', argsString);
208
+ await mcpService.callTool({
209
+ clientParams: mockParams,
210
+ toolName: 'testTool',
211
+ argsStr: argsString,
212
+ });
171
213
 
172
214
  expect(mockClient.callTool).toHaveBeenCalledWith('testTool', argsObject);
173
215
  });
@@ -15,6 +15,7 @@ import {
15
15
  StdioMCPParams,
16
16
  } from '@/libs/mcp';
17
17
 
18
+ import { ProcessContentBlocksFn, contentBlocksToString } from './contentProcessor';
18
19
  import { mcpSystemDepsCheckService } from './deps';
19
20
 
20
21
  const log = debug('lobe-mcp:service');
@@ -154,12 +155,19 @@ export class MCPService {
154
155
  }
155
156
  }
156
157
 
157
- // callTool now accepts MCPClientParams, toolName, and args
158
- async callTool(params: MCPClientParams, toolName: string, argsStr: any): Promise<any> {
159
- const client = await this.getClient(params); // Get client using params
158
+ // callTool now accepts an object with clientParams, toolName, argsStr, and processContentBlocks
159
+ async callTool(options: {
160
+ argsStr: any;
161
+ clientParams: MCPClientParams;
162
+ processContentBlocks?: ProcessContentBlocksFn;
163
+ toolName: string;
164
+ }): Promise<any> {
165
+ const { clientParams, toolName, argsStr, processContentBlocks } = options;
166
+
167
+ const client = await this.getClient(clientParams); // Get client using params
160
168
 
161
169
  const args = safeParseJSON(argsStr);
162
- const loggableParams = this.sanitizeForLogging(params);
170
+ const loggableParams = this.sanitizeForLogging(clientParams);
163
171
 
164
172
  log(
165
173
  `Calling tool "${toolName}" using client for params: %O with args: %O`,
@@ -170,32 +178,27 @@ export class MCPService {
170
178
  try {
171
179
  // Delegate the call to the MCPClient instance
172
180
  const result = await client.callTool(toolName, args); // Pass args directly
181
+
182
+ // Process content blocks (upload images, etc.)
183
+ const newContent =
184
+ result.isError || !processContentBlocks
185
+ ? result.content
186
+ : await processContentBlocks(result.content);
187
+
188
+ // Convert content blocks to string
189
+ const content = contentBlocksToString(newContent);
190
+
191
+ const state = { ...result, content: newContent };
192
+
173
193
  log(
174
194
  `Tool "${toolName}" called successfully for params: %O, result: %O`,
175
195
  loggableParams,
176
- result,
196
+ state,
177
197
  );
178
198
 
179
- // TODO: map more type
180
- const content = result.content
181
- ? result.content
182
- .map((item) => {
183
- switch (item.type) {
184
- case 'text': {
185
- return item.text;
186
- }
187
- default: {
188
- return '';
189
- }
190
- }
191
- })
192
- .filter(Boolean)
193
- .join('\n\n')
194
- : '';
195
-
196
- if (result.isError) return { content, state: result, success: true };
197
-
198
- return { content, state: result, success: true };
199
+ if (result.isError) return { content, state, success: true };
200
+
201
+ return { content, state, success: true };
199
202
  } catch (error) {
200
203
  if (error instanceof McpError) {
201
204
  const mcpError = error as McpError;
@@ -213,7 +216,7 @@ export class MCPService {
213
216
 
214
217
  console.error(
215
218
  `Error calling tool "${toolName}" for params %O:`,
216
- this.sanitizeForLogging(params),
219
+ this.sanitizeForLogging(clientParams),
217
220
  error,
218
221
  );
219
222
  // Propagate a TRPCError
@@ -210,20 +210,24 @@ export class Sitemap {
210
210
  const endIndex = startIndex + ITEMS_PER_PAGE;
211
211
  const pageAssistants = list.slice(startIndex, endIndex);
212
212
 
213
- const sitmap = pageAssistants.map((item) =>
214
- this._genSitemap(urlJoin('/discover/assistant', item.identifier), {
215
- lastModified: item?.lastModified || LAST_MODIFIED,
216
- }),
217
- );
213
+ const sitmap = pageAssistants
214
+ .filter((item) => item.identifier) // 过滤掉 identifier 为空的项目
215
+ .map((item) =>
216
+ this._genSitemap(urlJoin('/discover/assistant', item.identifier), {
217
+ lastModified: item?.lastModified || LAST_MODIFIED,
218
+ }),
219
+ );
218
220
  return flatten(sitmap);
219
221
  }
220
222
 
221
223
  // 如果没有指定页数,返回所有(向后兼容)
222
- const sitmap = list.map((item) =>
223
- this._genSitemap(urlJoin('/discover/assistant', item.identifier), {
224
- lastModified: item?.lastModified || LAST_MODIFIED,
225
- }),
226
- );
224
+ const sitmap = list
225
+ .filter((item) => item.identifier) // 过滤掉 identifier 为空的项目
226
+ .map((item) =>
227
+ this._genSitemap(urlJoin('/discover/assistant', item.identifier), {
228
+ lastModified: item?.lastModified || LAST_MODIFIED,
229
+ }),
230
+ );
227
231
  return flatten(sitmap);
228
232
  }
229
233
 
@@ -235,20 +239,24 @@ export class Sitemap {
235
239
  const endIndex = startIndex + ITEMS_PER_PAGE;
236
240
  const pagePlugins = list.slice(startIndex, endIndex);
237
241
 
238
- const sitmap = pagePlugins.map((item) =>
239
- this._genSitemap(urlJoin('/discover/plugin', item.identifier), {
240
- lastModified: item?.lastModified || LAST_MODIFIED,
241
- }),
242
- );
242
+ const sitmap = pagePlugins
243
+ .filter((item) => item.identifier) // 过滤掉 identifier 为空的项目
244
+ .map((item) =>
245
+ this._genSitemap(urlJoin('/discover/plugin', item.identifier), {
246
+ lastModified: item?.lastModified || LAST_MODIFIED,
247
+ }),
248
+ );
243
249
  return flatten(sitmap);
244
250
  }
245
251
 
246
252
  // 如果没有指定页数,返回所有(向后兼容)
247
- const sitmap = list.map((item) =>
248
- this._genSitemap(urlJoin('/discover/plugin', item.identifier), {
249
- lastModified: item?.lastModified || LAST_MODIFIED,
250
- }),
251
- );
253
+ const sitmap = list
254
+ .filter((item) => item.identifier) // 过滤掉 identifier 为空的项目
255
+ .map((item) =>
256
+ this._genSitemap(urlJoin('/discover/plugin', item.identifier), {
257
+ lastModified: item?.lastModified || LAST_MODIFIED,
258
+ }),
259
+ );
252
260
  return flatten(sitmap);
253
261
  }
254
262
 
@@ -260,30 +268,36 @@ export class Sitemap {
260
268
  const endIndex = startIndex + ITEMS_PER_PAGE;
261
269
  const pageModels = list.slice(startIndex, endIndex);
262
270
 
263
- const sitmap = pageModels.map((item) =>
264
- this._genSitemap(urlJoin('/discover/model', item.identifier), {
265
- lastModified: item?.lastModified || LAST_MODIFIED,
266
- }),
267
- );
271
+ const sitmap = pageModels
272
+ .filter((item) => item.identifier) // 过滤掉 identifier 为空的项目
273
+ .map((item) =>
274
+ this._genSitemap(urlJoin('/discover/model', item.identifier), {
275
+ lastModified: item?.lastModified || LAST_MODIFIED,
276
+ }),
277
+ );
268
278
  return flatten(sitmap);
269
279
  }
270
280
 
271
281
  // 如果没有指定页数,返回所有(向后兼容)
272
- const sitmap = list.map((item) =>
273
- this._genSitemap(urlJoin('/discover/model', item.identifier), {
274
- lastModified: item?.lastModified || LAST_MODIFIED,
275
- }),
276
- );
282
+ const sitmap = list
283
+ .filter((item) => item.identifier) // 过滤掉 identifier 为空的项目
284
+ .map((item) =>
285
+ this._genSitemap(urlJoin('/discover/model', item.identifier), {
286
+ lastModified: item?.lastModified || LAST_MODIFIED,
287
+ }),
288
+ );
277
289
  return flatten(sitmap);
278
290
  }
279
291
 
280
292
  async getProviders(): Promise<MetadataRoute.Sitemap> {
281
293
  const list = await this.discoverService.getProviderIdentifiers();
282
- const sitmap = list.map((item) =>
283
- this._genSitemap(urlJoin('/discover/provider', item.identifier), {
284
- lastModified: item?.lastModified || LAST_MODIFIED,
285
- }),
286
- );
294
+ const sitmap = list
295
+ .filter((item) => item.identifier) // 过滤掉 identifier 为空的项目
296
+ .map((item) =>
297
+ this._genSitemap(urlJoin('/discover/provider', item.identifier), {
298
+ lastModified: item?.lastModified || LAST_MODIFIED,
299
+ }),
300
+ );
287
301
  return flatten(sitmap);
288
302
  }
289
303
 
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix */
2
2
 
3
-
4
3
  export const API_ENDPOINTS = {
5
4
  oauth: '/api/auth',
6
5
 
@@ -30,3 +29,18 @@ export const API_ENDPOINTS = {
30
29
  edge: '/webapi/tts/edge',
31
30
  microsoft: '/webapi/tts/microsoft',
32
31
  };
32
+
33
+ export const MARKET_OIDC_ENDPOINTS = {
34
+ auth: '/lobehub-oidc/auth',
35
+ token: '/market/oidc/token',
36
+ userinfo: '/market/oidc/userinfo',
37
+ handoff: '/market/oidc/handoff',
38
+ desktopCallback: '/lobehub-oidc/callback/desktop',
39
+ };
40
+
41
+ export const MARKET_ENDPOINTS = {
42
+ base: '/market',
43
+ createAgent: '/market/agent/create',
44
+ getAgentDetail: (identifier: string) => `/market/agent/${encodeURIComponent(identifier)}`,
45
+ createAgentVersion: '/market/agent/versions/create',
46
+ };
@@ -27,7 +27,7 @@ vi.stubGlobal(
27
27
  );
28
28
 
29
29
  // Mock image processing utilities
30
- vi.mock('@/utils/fetch', async (importOriginal) => {
30
+ vi.mock('@lobechat/fetch-sse', async (importOriginal) => {
31
31
  const module = await importOriginal();
32
32
 
33
33
  return { ...(module as any), getMessageError: vi.fn() };
@@ -988,7 +988,7 @@ describe('ChatService', () => {
988
988
 
989
989
  beforeEach(async () => {
990
990
  // Setup common fetchSSE mock for getChatCompletion tests
991
- const { fetchSSE } = await import('@/utils/fetch');
991
+ const { fetchSSE } = await import('@lobechat/fetch-sse');
992
992
  mockFetchSSE = vi.fn().mockResolvedValue(new Response('mock response'));
993
993
  vi.mocked(fetchSSE).mockImplementation(mockFetchSSE);
994
994
  });
@@ -1049,7 +1049,7 @@ describe('ChatService', () => {
1049
1049
 
1050
1050
  it('should return InvalidAccessCode error when enableFetchOnClient is true and auth is enabled but user is not signed in', async () => {
1051
1051
  // Mock fetchSSE to call onErrorHandle with the error
1052
- const { fetchSSE } = await import('@/utils/fetch');
1052
+ const { fetchSSE } = await import('@lobechat/fetch-sse');
1053
1053
 
1054
1054
  const mockFetchSSEWithError = vi.fn().mockImplementation((url, options) => {
1055
1055
  // Simulate the error being caught and passed to onErrorHandle
@@ -1211,8 +1211,8 @@ vi.mock('../_auth', async (importOriginal) => {
1211
1211
  describe('ChatService private methods', () => {
1212
1212
  describe('getChatCompletion', () => {
1213
1213
  it('should merge responseAnimation styles correctly', async () => {
1214
- const { fetchSSE } = await import('@/utils/fetch');
1215
- vi.mock('@/utils/fetch', async (importOriginal) => {
1214
+ const { fetchSSE } = await import('@lobechat/fetch-sse');
1215
+ vi.mock('@lobechat/fetch-sse', async (importOriginal) => {
1216
1216
  const module = await importOriginal();
1217
1217
  return {
1218
1218
  ...(module as any),
@@ -38,7 +38,7 @@ vi.stubGlobal(
38
38
  vi.fn(() => Promise.resolve(new Response(JSON.stringify({ some: 'data' })))),
39
39
  );
40
40
 
41
- vi.mock('@/utils/fetch', async (importOriginal) => {
41
+ vi.mock('@lobechat/fetch-sse', async (importOriginal) => {
42
42
  const module = await importOriginal();
43
43
 
44
44
  return { ...(module as any), getMessageError: vi.fn() };
@@ -1,3 +1,9 @@
1
+ import {
2
+ FetchSSEOptions,
3
+ fetchSSE,
4
+ getMessageError,
5
+ standardizeAnimationStyle,
6
+ } from '@lobechat/fetch-sse';
1
7
  import { AgentRuntimeError, ChatCompletionErrorPayload } from '@lobechat/model-runtime';
2
8
  import { ChatErrorType, TracePayload, TraceTagMap, UIChatMessage } from '@lobechat/types';
3
9
  import { PluginRequestPayload, createHeadersWithPluginSettings } from '@lobehub/chat-plugin-sdk';
@@ -25,12 +31,6 @@ import {
25
31
  import type { ChatStreamPayload, OpenAIChatMessage } from '@/types/openai/chat';
26
32
  import { fetchWithInvokeStream } from '@/utils/electron/desktopRemoteRPCFetch';
27
33
  import { createErrorResponse } from '@/utils/errorResponse';
28
- import {
29
- FetchSSEOptions,
30
- fetchSSE,
31
- getMessageError,
32
- standardizeAnimationStyle,
33
- } from '@/utils/fetch';
34
34
  import { createTraceHeader, getTraceId } from '@/utils/trace';
35
35
 
36
36
  import { createHeaderWithAuth } from '../_auth';
@@ -1,7 +1,6 @@
1
+ import { FetchSSEOptions } from '@lobechat/fetch-sse';
1
2
  import { TracePayload } from '@lobechat/types';
2
3
 
3
- import { FetchSSEOptions } from '@/utils/fetch';
4
-
5
4
  export interface FetchOptions extends FetchSSEOptions {
6
5
  historySummary?: string;
7
6
  signal?: AbortSignal | undefined;
@@ -7,6 +7,7 @@ import { useUserStore } from '@/store/user';
7
7
  import { preferenceSelectors } from '@/store/user/selectors';
8
8
  import {
9
9
  AssistantListResponse,
10
+ AssistantMarketSource,
10
11
  AssistantQueryParams,
11
12
  DiscoverAssistantDetail,
12
13
  DiscoverMcpDetail,
@@ -30,27 +31,37 @@ class DiscoverService {
30
31
  private _isRetrying = false;
31
32
 
32
33
  // ============================== Assistant Market ==============================
33
- getAssistantCategories = async (params: CategoryListQuery = {}): Promise<CategoryItem[]> => {
34
+ getAssistantCategories = async (
35
+ params: CategoryListQuery & { source?: AssistantMarketSource } = {},
36
+ ): Promise<CategoryItem[]> => {
34
37
  const locale = globalHelpers.getCurrentLanguage();
38
+ const { source, ...rest } = params;
35
39
  return lambdaClient.market.getAssistantCategories.query({
36
- ...params,
40
+ ...rest,
37
41
  locale,
42
+ source,
38
43
  });
39
44
  };
40
45
 
41
46
  getAssistantDetail = async (params: {
42
47
  identifier: string;
43
48
  locale?: string;
49
+ source?: AssistantMarketSource;
50
+ version?: string;
44
51
  }): Promise<DiscoverAssistantDetail | undefined> => {
45
52
  const locale = globalHelpers.getCurrentLanguage();
46
53
  return lambdaClient.market.getAssistantDetail.query({
47
- ...params,
54
+ identifier: params.identifier,
48
55
  locale,
56
+ source: params.source,
57
+ version: params.version,
49
58
  });
50
59
  };
51
60
 
52
- getAssistantIdentifiers = async (): Promise<IdentifiersResponse> => {
53
- return lambdaClient.market.getAssistantIdentifiers.query();
61
+ getAssistantIdentifiers = async (
62
+ params: { source?: AssistantMarketSource } = {},
63
+ ): Promise<IdentifiersResponse> => {
64
+ return lambdaClient.market.getAssistantIdentifiers.query(params);
54
65
  };
55
66
 
56
67
  getAssistantList = async (params: AssistantQueryParams = {}): Promise<AssistantListResponse> => {
@@ -1,4 +1,4 @@
1
- import { DataSyncConfig, dispatch } from '@lobechat/electron-client-ipc';
1
+ import { DataSyncConfig, MarketAuthorizationParams, dispatch } from '@lobechat/electron-client-ipc';
2
2
 
3
3
  class RemoteServerService {
4
4
  /**
@@ -28,6 +28,13 @@ class RemoteServerService {
28
28
  requestAuthorization = async (config: DataSyncConfig) => {
29
29
  return dispatch('requestAuthorization', config);
30
30
  };
31
+
32
+ /**
33
+ * 请求 Market 授权
34
+ */
35
+ requestMarketAuthorization = async (params: MarketAuthorizationParams) => {
36
+ return dispatch('requestMarketAuthorization', params);
37
+ };
31
38
  }
32
39
 
33
40
  export const remoteServerService = new RemoteServerService();