@lobehub/lobehub 2.0.0-next.310 → 2.0.0-next.312

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 (118) hide show
  1. package/.github/PULL_REQUEST_TEMPLATE.md +1 -1
  2. package/CHANGELOG.md +58 -0
  3. package/changelog/v1.json +21 -0
  4. package/docs/development/basic/chat-api.mdx +0 -1
  5. package/docs/development/basic/chat-api.zh-CN.mdx +0 -1
  6. package/e2e/README.md +1 -1
  7. package/e2e/src/features/community/detail-pages.feature +2 -2
  8. package/e2e/src/features/community/interactions.feature +5 -5
  9. package/e2e/src/features/community/smoke.feature +1 -1
  10. package/e2e/src/steps/community/detail-pages.steps.ts +6 -4
  11. package/e2e/src/steps/community/interactions.steps.ts +3 -3
  12. package/package.json +1 -1
  13. package/packages/builtin-tool-agent-builder/src/systemRole.ts +9 -0
  14. package/packages/model-runtime/src/core/BaseAI.ts +0 -2
  15. package/packages/model-runtime/src/core/ModelRuntime.test.ts +0 -37
  16. package/packages/model-runtime/src/core/ModelRuntime.ts +0 -5
  17. package/packages/model-runtime/src/core/RouterRuntime/baseRuntimeMap.ts +4 -0
  18. package/packages/model-runtime/src/core/RouterRuntime/createRuntime.test.ts +325 -200
  19. package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +205 -64
  20. package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +0 -14
  21. package/packages/model-runtime/src/providers/aihubmix/index.test.ts +14 -20
  22. package/packages/model-runtime/src/types/index.ts +0 -1
  23. package/packages/model-runtime/src/utils/createError.test.ts +0 -20
  24. package/packages/model-runtime/src/utils/createError.ts +0 -1
  25. package/public/favicon-32x-32-error.ico +0 -0
  26. package/public/favicon-32x32-done-dev.ico +0 -0
  27. package/public/favicon-32x32-done.ico +0 -0
  28. package/public/favicon-32x32-error-dev.ico +0 -0
  29. package/public/favicon-32x32-progress-dev.ico +0 -0
  30. package/public/favicon-32x32-progress.ico +0 -0
  31. package/public/favicon-done-dev.ico +0 -0
  32. package/public/favicon-done.ico +0 -0
  33. package/public/favicon-error-dev.ico +0 -0
  34. package/public/favicon-error.ico +0 -0
  35. package/public/favicon-progress-dev.ico +0 -0
  36. package/public/favicon-progress.ico +0 -0
  37. package/src/app/(backend)/market/agent/[[...segments]]/route.ts +3 -33
  38. package/src/app/(backend)/market/oidc/[[...segments]]/route.ts +5 -6
  39. package/src/app/(backend)/market/social/[[...segments]]/route.ts +5 -52
  40. package/src/app/(backend)/market/user/[username]/route.ts +3 -9
  41. package/src/app/(backend)/market/user/me/route.ts +3 -34
  42. package/src/app/[variants]/(main)/agent/profile/features/Header/AgentPublishButton/PublishResultModal.tsx +1 -1
  43. package/src/app/[variants]/(main)/community/(detail)/_layout/Header.tsx +15 -3
  44. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Overview/TagList.tsx +1 -1
  45. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Related/index.tsx +2 -2
  46. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/SystemRole/TagList.tsx +1 -1
  47. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/SystemRole/index.tsx +1 -1
  48. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Header.tsx +2 -2
  49. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/ActionButton/AddAgent.tsx +1 -1
  50. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/ActionButton/index.tsx +1 -1
  51. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Related/index.tsx +2 -2
  52. package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/StatusPage/index.tsx +2 -2
  53. package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +2 -2
  54. package/src/app/[variants]/(main)/community/(detail)/user/features/UserFavoriteAgents.tsx +1 -1
  55. package/src/app/[variants]/(main)/community/(list)/(home)/index.tsx +2 -2
  56. package/src/app/[variants]/(main)/community/(list)/(home)/loading.tsx +1 -1
  57. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/Client.tsx +5 -1
  58. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/Category/index.tsx +1 -1
  59. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/Item.tsx +1 -1
  60. package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/MarketSourceSwitch.tsx +1 -1
  61. package/src/app/[variants]/(main)/community/_layout/Sidebar/Header/Nav.tsx +2 -2
  62. package/src/app/[variants]/(main)/home/features/CommunityAgents/List.tsx +1 -1
  63. package/src/app/[variants]/(main)/home/features/CommunityAgents/index.tsx +1 -1
  64. package/src/app/[variants]/(mobile)/_layout/index.tsx +1 -1
  65. package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +6 -6
  66. package/src/app/[variants]/router/desktopRouter.config.tsx +8 -8
  67. package/src/app/[variants]/share/t/[id]/_layout/index.tsx +1 -1
  68. package/src/features/ChatMiniMap/useMinimapData.ts +1 -1
  69. package/src/features/CommandMenu/SearchResults.tsx +1 -1
  70. package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +20 -2
  71. package/src/features/Conversation/store/slices/virtuaList/action.ts +9 -0
  72. package/src/features/Electron/navigation/routeMetadata.ts +1 -1
  73. package/src/layout/GlobalProvider/FaviconProvider.tsx +92 -0
  74. package/src/layout/GlobalProvider/index.tsx +15 -11
  75. package/src/libs/next/config/define-config.ts +1 -1
  76. package/src/libs/trpc/lambda/middleware/marketSDK.ts +14 -23
  77. package/src/libs/trusted-client/index.ts +1 -1
  78. package/src/server/routers/lambda/market/index.ts +5 -0
  79. package/src/server/routers/lambda/market/oidc.ts +41 -61
  80. package/src/server/routers/tools/market.ts +12 -44
  81. package/src/server/services/agentRuntime/AgentRuntimeService.test.ts +7 -0
  82. package/src/server/services/agentRuntime/AgentRuntimeService.ts +1 -1
  83. package/src/server/services/aiAgent/__tests__/execAgent.threadId.test.ts +7 -0
  84. package/src/server/services/aiAgent/__tests__/execGroupSubAgentTask.test.ts +7 -0
  85. package/src/server/services/aiAgent/index.ts +9 -96
  86. package/src/server/services/discover/index.ts +11 -16
  87. package/src/server/services/market/index.ts +485 -0
  88. package/src/server/services/toolExecution/builtin.ts +11 -17
  89. package/src/server/services/toolExecution/index.ts +6 -2
  90. package/src/server/sitemap.test.ts +5 -5
  91. package/src/server/sitemap.ts +3 -3
  92. package/src/services/codeInterpreter.ts +0 -13
  93. package/packages/model-runtime/src/types/textToImage.ts +0 -36
  94. package/src/server/services/lobehubSkill/index.ts +0 -109
  95. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/DetailProvider.tsx +0 -0
  96. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Block.tsx +0 -0
  97. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Knowledge.tsx +0 -0
  98. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/KnowledgeItem.tsx +0 -0
  99. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/PluginItem.tsx +0 -0
  100. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Plugins.tsx +0 -0
  101. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/index.tsx +0 -0
  102. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Nav.tsx +0 -0
  103. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Overview/index.tsx +0 -0
  104. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Versions/index.tsx +0 -0
  105. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/index.tsx +0 -0
  106. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Related/Item.tsx +0 -0
  107. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Summary/index.tsx +0 -0
  108. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/TocList/index.tsx +0 -0
  109. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/index.tsx +0 -0
  110. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/index.tsx +0 -0
  111. /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/loading.tsx +0 -0
  112. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/_layout/index.tsx +0 -0
  113. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/_layout/style.ts +0 -0
  114. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/Category/useCategory.tsx +0 -0
  115. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/TokenTag.tsx +0 -0
  116. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/index.tsx +0 -0
  117. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/index.tsx +0 -0
  118. /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/loading.tsx +0 -0
@@ -30,7 +30,9 @@ const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent })
30
30
  const setScrollState = useConversationStore((s) => s.setScrollState);
31
31
  const resetVisibleItems = useConversationStore((s) => s.resetVisibleItems);
32
32
  const scrollToBottom = useConversationStore((s) => s.scrollToBottom);
33
+ const setActiveIndex = useConversationStore((s) => s.setActiveIndex);
33
34
  const atBottom = useConversationStore(virtuaListSelectors.atBottom);
35
+ const activeIndex = useConversationStore(virtuaListSelectors.activeIndex);
34
36
 
35
37
  // Check if at bottom based on scroll position
36
38
  const checkAtBottom = useCallback(() => {
@@ -46,6 +48,16 @@ const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent })
46
48
 
47
49
  // Handle scroll events
48
50
  const handleScroll = useCallback(() => {
51
+ const refForActive = virtuaRef.current;
52
+ const activeFromFindRaw =
53
+ refForActive && typeof refForActive.findItemIndex === 'function'
54
+ ? refForActive.findItemIndex(refForActive.scrollOffset + refForActive.viewportSize * 0.25)
55
+ : null;
56
+ const activeFromFind =
57
+ typeof activeFromFindRaw === 'number' && activeFromFindRaw >= 0 ? activeFromFindRaw : null;
58
+
59
+ if (activeFromFind !== activeIndex) setActiveIndex(activeFromFind);
60
+
49
61
  setScrollState({ isScrolling: true });
50
62
 
51
63
  // Check if at bottom
@@ -61,7 +73,7 @@ const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent })
61
73
  scrollEndTimerRef.current = setTimeout(() => {
62
74
  setScrollState({ isScrolling: false });
63
75
  }, 150);
64
- }, [checkAtBottom, setScrollState]);
76
+ }, [activeIndex, checkAtBottom, setActiveIndex, setScrollState]);
65
77
 
66
78
  const handleScrollEnd = useCallback(() => {
67
79
  setScrollState({ isScrolling: false });
@@ -77,12 +89,18 @@ const VirtualizedList = memo<VirtualizedListProps>(({ dataSource, itemContent })
77
89
  getViewportSize: () => ref.viewportSize,
78
90
  scrollToIndex: (index, options) => ref.scrollToIndex(index, options),
79
91
  });
92
+
93
+ // Seed active index once on mount (avoid requiring user scroll)
94
+ const initialActiveRaw = ref.findItemIndex(ref.scrollOffset + ref.viewportSize * 0.25);
95
+ const initialActive =
96
+ typeof initialActiveRaw === 'number' && initialActiveRaw >= 0 ? initialActiveRaw : null;
97
+ setActiveIndex(initialActive);
80
98
  }
81
99
 
82
100
  return () => {
83
101
  registerVirtuaScrollMethods(null);
84
102
  };
85
- }, [registerVirtuaScrollMethods]);
103
+ }, [registerVirtuaScrollMethods, setActiveIndex]);
86
104
 
87
105
  // Cleanup on unmount
88
106
  useEffect(() => {
@@ -32,6 +32,11 @@ export interface VirtuaListAction {
32
32
  options?: { align?: 'start' | 'center' | 'end'; smooth?: boolean },
33
33
  ) => void;
34
34
 
35
+ /**
36
+ * Set active index directly (derived from scroll position)
37
+ */
38
+ setActiveIndex: (index: number | null) => void;
39
+
35
40
  /**
36
41
  * Update scroll state (atBottom, isScrolling)
37
42
  */
@@ -107,6 +112,10 @@ export const virtuaListSlice: StateCreator<State & VirtuaListAction, [], [], Vir
107
112
  virtuaScrollMethods?.scrollToIndex(index, options);
108
113
  },
109
114
 
115
+ setActiveIndex: (index) => {
116
+ set({ activeIndex: index });
117
+ },
118
+
110
119
  setScrollState: (state) => {
111
120
  set(state);
112
121
  },
@@ -79,7 +79,7 @@ const routePatterns: RoutePattern[] = [
79
79
  // Community/Discover routes
80
80
  {
81
81
  icon: Compass,
82
- test: (p) => p.startsWith('/community/assistant'),
82
+ test: (p) => p.startsWith('/community/agent'),
83
83
  titleKey: 'navigation.discoverAssistants',
84
84
  },
85
85
  {
@@ -0,0 +1,92 @@
1
+ 'use client';
2
+
3
+ import { type ReactNode, createContext, memo, useCallback, useContext, useState } from 'react';
4
+
5
+ export type FaviconState = 'default' | 'done' | 'error' | 'progress';
6
+
7
+ interface FaviconContextValue {
8
+ currentState: FaviconState;
9
+ isDevMode: boolean;
10
+ setFavicon: (state: FaviconState) => void;
11
+ setIsDevMode: (isDev: boolean) => void;
12
+ }
13
+
14
+ const FaviconContext = createContext<FaviconContextValue | null>(null);
15
+
16
+ export const useFavicon = () => {
17
+ const context = useContext(FaviconContext);
18
+ if (!context) {
19
+ throw new Error('useFavicon must be used within FaviconProvider');
20
+ }
21
+ return context;
22
+ };
23
+
24
+ const stateToFileName: Record<FaviconState, string> = {
25
+ default: '',
26
+ done: '-done',
27
+ error: '-error',
28
+ progress: '-progress',
29
+ };
30
+
31
+ const getFaviconPath = (state: FaviconState, isDev: boolean, size?: '32x32'): string => {
32
+ const devSuffix = isDev ? '-dev' : '';
33
+ const stateSuffix = stateToFileName[state];
34
+ const sizeSuffix = size ? `-${size}` : '';
35
+ return `/favicon${sizeSuffix}${stateSuffix}${devSuffix}.ico`;
36
+ };
37
+
38
+ const updateFaviconDOM = (state: FaviconState, isDev: boolean) => {
39
+ if (typeof document === 'undefined') return;
40
+
41
+ const head = document.head;
42
+ const existingLinks = document.querySelectorAll<HTMLLinkElement>(
43
+ 'link[rel="icon"], link[rel="shortcut icon"]',
44
+ );
45
+
46
+ // Remove existing favicon links and create new ones to bust cache
47
+ existingLinks.forEach((link) => {
48
+ const oldHref = link.href;
49
+ const is32 = oldHref.includes('32x32');
50
+ const rel = link.rel;
51
+
52
+ // Remove old link
53
+ link.remove();
54
+
55
+ // Create new link with cache-busting query param
56
+ const newLink = document.createElement('link');
57
+ newLink.rel = rel;
58
+ newLink.href = `${getFaviconPath(state, isDev, is32 ? '32x32' : undefined)}?v=${Date.now()}`;
59
+ head.append(newLink);
60
+ });
61
+ };
62
+
63
+ const defaultIsDev = process.env.NODE_ENV === 'development';
64
+
65
+ export const FaviconProvider = memo<{ children: ReactNode }>(({ children }) => {
66
+ const [currentState, setCurrentState] = useState<FaviconState>('default');
67
+ const [isDevMode, setIsDevModeState] = useState<boolean>(defaultIsDev);
68
+
69
+ const setFavicon = useCallback(
70
+ (state: FaviconState) => {
71
+ setCurrentState(state);
72
+ updateFaviconDOM(state, isDevMode);
73
+ },
74
+ [isDevMode],
75
+ );
76
+
77
+ const setIsDevMode = useCallback(
78
+ (isDev: boolean) => {
79
+ setIsDevModeState(isDev);
80
+ updateFaviconDOM(currentState, isDev);
81
+ },
82
+ [currentState],
83
+ );
84
+
85
+ return (
86
+ <FaviconContext.Provider value={{ currentState, isDevMode, setFavicon, setIsDevMode }}>
87
+ {children}
88
+ </FaviconContext.Provider>
89
+ );
90
+ });
91
+
92
+ FaviconProvider.displayName = 'FaviconProvider';
@@ -14,6 +14,7 @@ import { ServerConfigStoreProvider } from '@/store/serverConfig/Provider';
14
14
  import { getAntdLocale } from '@/utils/locale';
15
15
 
16
16
  import AppTheme from './AppTheme';
17
+ import { FaviconProvider } from './FaviconProvider';
17
18
  import { GroupWizardProvider } from './GroupWizardProvider';
18
19
  import ImportSettings from './ImportSettings';
19
20
  import Locale from './Locale';
@@ -65,17 +66,20 @@ const GlobalLayout = async ({
65
66
  >
66
67
  <QueryProvider>
67
68
  <StoreInitialization />
68
- <GroupWizardProvider>
69
- <DragUploadProvider>
70
- <LazyMotion features={domMax}>
71
- <TooltipGroup layoutAnimation={false}>
72
- <LobeAnalyticsProviderWrapper>{children}</LobeAnalyticsProviderWrapper>
73
- </TooltipGroup>
74
- <ModalHost />
75
- <ContextMenuHost />
76
- </LazyMotion>
77
- </DragUploadProvider>
78
- </GroupWizardProvider>
69
+ <FaviconProvider>
70
+ {/* {process.env.NODE_ENV === 'development' && <FaviconTestPanel />} */}
71
+ <GroupWizardProvider>
72
+ <DragUploadProvider>
73
+ <LazyMotion features={domMax}>
74
+ <TooltipGroup layoutAnimation={false}>
75
+ <LobeAnalyticsProviderWrapper>{children}</LobeAnalyticsProviderWrapper>
76
+ </TooltipGroup>
77
+ <ModalHost />
78
+ <ContextMenuHost />
79
+ </LazyMotion>
80
+ </DragUploadProvider>
81
+ </GroupWizardProvider>
82
+ </FaviconProvider>
79
83
  </QueryProvider>
80
84
  <Suspense>
81
85
  {ENABLE_BUSINESS_FEATURES ? <ReferralProvider /> : null}
@@ -267,7 +267,7 @@ export function defineConfig(config: CustomNextConfig) {
267
267
  source: '/manifest.json',
268
268
  },
269
269
  {
270
- destination: '/community/assistant',
270
+ destination: '/community/agent',
271
271
  permanent: true,
272
272
  source: '/community/assistants',
273
273
  },
@@ -1,6 +1,5 @@
1
- import { MarketSDK } from '@lobehub/market-sdk';
2
-
3
- import { generateTrustedClientToken, type TrustedClientUserInfo } from '@/libs/trusted-client';
1
+ import { type TrustedClientUserInfo } from '@/libs/trusted-client';
2
+ import { MarketService } from '@/server/services/market';
4
3
 
5
4
  import { trpc } from '../init';
6
5
 
@@ -10,53 +9,45 @@ interface ContextWithMarketUserInfo {
10
9
  }
11
10
 
12
11
  /**
13
- * Middleware that initializes MarketSDK with proper authentication.
12
+ * Middleware that initializes MarketService with proper authentication.
14
13
  * This requires marketUserInfo middleware to be applied first.
15
14
  *
16
15
  * Provides:
17
- * - ctx.marketSDK: Initialized MarketSDK instance with trustedClientToken and optional accessToken
18
- * - ctx.trustedClientToken: The generated trusted client token (if available)
16
+ * - ctx.marketSDK: MarketSDK instance for backward compatibility
17
+ * - ctx.marketService: MarketService instance (recommended)
19
18
  */
20
19
  export const marketSDK = trpc.middleware(async (opts) => {
21
20
  const ctx = opts.ctx as ContextWithMarketUserInfo;
22
21
 
23
- // Generate trusted client token if user info is available
24
- const trustedClientToken = ctx.marketUserInfo
25
- ? generateTrustedClientToken(ctx.marketUserInfo)
26
- : undefined;
27
-
28
- // Initialize MarketSDK with both authentication methods
29
- const market = new MarketSDK({
22
+ // Initialize MarketService with authentication
23
+ const marketService = new MarketService({
30
24
  accessToken: ctx.marketAccessToken,
31
- baseURL: process.env.NEXT_PUBLIC_MARKET_BASE_URL,
32
- trustedClientToken,
25
+ userInfo: ctx.marketUserInfo,
33
26
  });
34
27
 
35
28
  return opts.next({
36
29
  ctx: {
37
- marketSDK: market,
38
- trustedClientToken,
30
+ marketSDK: marketService.market, // Backward compatibility
31
+ marketService, // New recommended way
39
32
  },
40
33
  });
41
34
  });
42
35
 
43
36
  /**
44
37
  * Middleware that requires authentication for Market API access.
45
- * This middleware ensures that either accessToken or trustedClientToken is available.
38
+ * This middleware ensures that either accessToken or marketUserInfo is available.
46
39
  * It should be used after marketUserInfo and marketSDK middlewares.
47
40
  *
48
41
  * Throws UNAUTHORIZED error if neither authentication method is available.
49
42
  */
50
43
  export const requireMarketAuth = trpc.middleware(async (opts) => {
51
- const ctx = opts.ctx as ContextWithMarketUserInfo & {
52
- trustedClientToken?: string;
53
- };
44
+ const ctx = opts.ctx as ContextWithMarketUserInfo;
54
45
 
55
46
  // Check if any authentication is available
56
47
  const hasAccessToken = !!ctx.marketAccessToken;
57
- const hasTrustedToken = !!ctx.trustedClientToken;
48
+ const hasUserInfo = !!ctx.marketUserInfo;
58
49
 
59
- if (!hasAccessToken && !hasTrustedToken) {
50
+ if (!hasAccessToken && !hasUserInfo) {
60
51
  const { TRPCError } = await import('@trpc/server');
61
52
  throw new TRPCError({
62
53
  code: 'UNAUTHORIZED',
@@ -3,7 +3,7 @@ import { buildTrustedClientPayload, createTrustedClientToken } from '@lobehub/ma
3
3
  import { appEnv } from '@/envs/app';
4
4
 
5
5
  export interface TrustedClientUserInfo {
6
- email: string;
6
+ email?: string;
7
7
  name?: string;
8
8
  userId: string;
9
9
  }
@@ -7,6 +7,7 @@ import { z } from 'zod';
7
7
  import { publicProcedure, router } from '@/libs/trpc/lambda';
8
8
  import { marketUserInfo, serverDatabase } from '@/libs/trpc/lambda/middleware';
9
9
  import { DiscoverService } from '@/server/services/discover';
10
+ import { MarketService } from '@/server/services/market';
10
11
  import {
11
12
  AssistantSorts,
12
13
  McpConnectionType,
@@ -37,6 +38,10 @@ const marketProcedure = publicProcedure
37
38
  accessToken: ctx.marketAccessToken,
38
39
  userInfo: ctx.marketUserInfo,
39
40
  }),
41
+ marketService: new MarketService({
42
+ accessToken: ctx.marketAccessToken,
43
+ userInfo: ctx.marketUserInfo,
44
+ }),
40
45
  },
41
46
  });
42
47
  });
@@ -1,18 +1,29 @@
1
- import { MarketSDK } from '@lobehub/market-sdk';
2
1
  import { TRPCError } from '@trpc/server';
3
2
  import debug from 'debug';
4
3
  import { z } from 'zod';
5
4
 
6
5
  import { publicProcedure, router } from '@/libs/trpc/lambda';
7
6
  import { marketUserInfo, serverDatabase } from '@/libs/trpc/lambda/middleware';
8
- import { generateTrustedClientToken } from '@/libs/trusted-client';
7
+ import { MarketService } from '@/server/services/market';
9
8
 
10
9
  const log = debug('lambda-router:market:oidc');
11
10
 
12
- const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
13
-
14
11
  // OIDC procedures are public (used during authentication flow)
15
- const oidcProcedure = publicProcedure.use(serverDatabase).use(marketUserInfo);
12
+ const oidcProcedure = publicProcedure
13
+ .use(serverDatabase)
14
+ .use(marketUserInfo)
15
+ .use(async ({ ctx, next }) => {
16
+ // Initialize MarketService (may be without auth for public endpoints)
17
+ const marketService = new MarketService({
18
+ userInfo: ctx.marketUserInfo,
19
+ });
20
+
21
+ return next({
22
+ ctx: {
23
+ marketService,
24
+ },
25
+ });
26
+ });
16
27
 
17
28
  export const oidcRouter = router({
18
29
  /**
@@ -28,19 +39,11 @@ export const oidcRouter = router({
28
39
  redirectUri: z.string(),
29
40
  }),
30
41
  )
31
- .mutation(async ({ input }) => {
42
+ .mutation(async ({ input, ctx }) => {
32
43
  log('exchangeAuthorizationCode input: %O', { ...input, code: '[REDACTED]' });
33
44
 
34
- const market = new MarketSDK({ baseURL: MARKET_BASE_URL });
35
-
36
45
  try {
37
- const response = await market.auth.exchangeOAuthToken({
38
- clientId: input.clientId,
39
- code: input.code,
40
- codeVerifier: input.codeVerifier,
41
- grantType: 'authorization_code',
42
- redirectUri: input.redirectUri,
43
- });
46
+ const response = await ctx.marketService.exchangeAuthorizationCode(input);
44
47
  return response;
45
48
  } catch (error) {
46
49
  log('Error exchanging authorization code: %O', error);
@@ -56,23 +59,23 @@ export const oidcRouter = router({
56
59
  * Get OAuth handoff information
57
60
  * GET /market/oidc/handoff?id=xxx
58
61
  */
59
- getOAuthHandoff: oidcProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => {
60
- log('getOAuthHandoff input: %O', input);
61
-
62
- const market = new MarketSDK({ baseURL: MARKET_BASE_URL });
63
-
64
- try {
65
- const handoff = await market.auth.getOAuthHandoff(input.id);
66
- return handoff;
67
- } catch (error) {
68
- log('Error getting OAuth handoff: %O', error);
69
- throw new TRPCError({
70
- cause: error,
71
- code: 'INTERNAL_SERVER_ERROR',
72
- message: error instanceof Error ? error.message : 'Failed to get OAuth handoff',
73
- });
74
- }
75
- }),
62
+ getOAuthHandoff: oidcProcedure
63
+ .input(z.object({ id: z.string() }))
64
+ .query(async ({ input, ctx }) => {
65
+ log('getOAuthHandoff input: %O', input);
66
+
67
+ try {
68
+ const handoff = await ctx.marketService.getOAuthHandoff(input.id);
69
+ return handoff;
70
+ } catch (error) {
71
+ log('Error getting OAuth handoff: %O', error);
72
+ throw new TRPCError({
73
+ cause: error,
74
+ code: 'INTERNAL_SERVER_ERROR',
75
+ message: error instanceof Error ? error.message : 'Failed to get OAuth handoff',
76
+ });
77
+ }
78
+ }),
76
79
 
77
80
  /**
78
81
  * Get user info from token or trusted client
@@ -83,37 +86,17 @@ export const oidcRouter = router({
83
86
  .mutation(async ({ input, ctx }) => {
84
87
  log('getUserInfo input: token=%s', input.token ? '[REDACTED]' : 'undefined');
85
88
 
86
- const market = new MarketSDK({ baseURL: MARKET_BASE_URL });
87
-
88
89
  try {
89
90
  // If token is provided, use it
90
91
  if (input.token) {
91
- const response = await market.auth.getUserInfo(input.token);
92
+ const response = await ctx.marketService.getUserInfo(input.token);
92
93
  return response;
93
94
  }
94
95
 
95
96
  // Otherwise, try to use trustedClientToken
96
97
  if (ctx.marketUserInfo) {
97
- const trustedClientToken = generateTrustedClientToken(ctx.marketUserInfo);
98
-
99
- if (trustedClientToken) {
100
- const userInfoUrl = `${MARKET_BASE_URL}/lobehub-oidc/userinfo`;
101
- const response = await fetch(userInfoUrl, {
102
- headers: {
103
- 'Content-Type': 'application/json',
104
- 'x-lobe-trust-token': trustedClientToken,
105
- },
106
- method: 'GET',
107
- });
108
-
109
- if (!response.ok) {
110
- throw new Error(
111
- `Failed to fetch user info: ${response.status} ${response.statusText}`,
112
- );
113
- }
114
-
115
- return await response.json();
116
- }
98
+ const response = await ctx.marketService.getUserInfoWithTrustedClient();
99
+ return response;
117
100
  }
118
101
 
119
102
  throw new TRPCError({
@@ -143,15 +126,12 @@ export const oidcRouter = router({
143
126
  refreshToken: z.string(),
144
127
  }),
145
128
  )
146
- .mutation(async ({ input }) => {
129
+ .mutation(async ({ input, ctx }) => {
147
130
  log('refreshToken input: %O', { ...input, refreshToken: '[REDACTED]' });
148
131
 
149
- const market = new MarketSDK({ baseURL: MARKET_BASE_URL });
150
-
151
132
  try {
152
- const response = await market.auth.exchangeOAuthToken({
153
- clientId: input.clientId,
154
- grantType: 'refresh_token',
133
+ const response = await ctx.marketService.refreshToken({
134
+ clientId: input.clientId || '',
155
135
  refreshToken: input.refreshToken,
156
136
  });
157
137
  return response;
@@ -1,4 +1,4 @@
1
- import { type CodeInterpreterToolName, MarketSDK } from '@lobehub/market-sdk';
1
+ import { type CodeInterpreterToolName } from '@lobehub/market-sdk';
2
2
  import { TRPCError } from '@trpc/server';
3
3
  import debug from 'debug';
4
4
  import { sha256 } from 'js-sha256';
@@ -8,10 +8,11 @@ import { type ToolCallContent } from '@/libs/mcp';
8
8
  import { authedProcedure, router } from '@/libs/trpc/lambda';
9
9
  import { marketUserInfo, serverDatabase, telemetry } from '@/libs/trpc/lambda/middleware';
10
10
  import { marketSDK, requireMarketAuth } from '@/libs/trpc/lambda/middleware/marketSDK';
11
- import { generateTrustedClientToken, isTrustedClientEnabled } from '@/libs/trusted-client';
11
+ import { isTrustedClientEnabled } from '@/libs/trusted-client';
12
12
  import { FileS3 } from '@/server/modules/S3';
13
13
  import { DiscoverService } from '@/server/services/discover';
14
14
  import { FileService } from '@/server/services/file';
15
+ import { MarketService } from '@/server/services/market';
15
16
  import {
16
17
  contentBlocksToString,
17
18
  processContentBlocks,
@@ -37,6 +38,10 @@ const marketToolProcedure = authedProcedure
37
38
  userInfo: ctx.marketUserInfo,
38
39
  }),
39
40
  fileService: new FileService(ctx.serverDB, ctx.userId),
41
+ marketService: new MarketService({
42
+ accessToken: ctx.marketAccessToken,
43
+ userInfo: ctx.marketUserInfo,
44
+ }),
40
45
  userModel,
41
46
  },
42
47
  });
@@ -79,7 +84,6 @@ const metaSchema = z
79
84
 
80
85
  // Schema for code interpreter tool call request
81
86
  const callCodeInterpreterToolSchema = z.object({
82
- marketAccessToken: z.string().optional(),
83
87
  params: z.record(z.any()),
84
88
  toolName: z.string(),
85
89
  topicId: z.string(),
@@ -224,27 +228,17 @@ export const marketRouter = router({
224
228
  callCodeInterpreterTool: marketToolProcedure
225
229
  .input(callCodeInterpreterToolSchema)
226
230
  .mutation(async ({ input, ctx }) => {
227
- const { toolName, params, userId, topicId, marketAccessToken } = input;
231
+ const { toolName, params, userId, topicId } = input;
228
232
 
229
233
  log('Calling cloud code interpreter tool: %s with params: %O', toolName, {
230
234
  params,
231
235
  topicId,
232
236
  userId,
233
237
  });
234
- log('Market access token available: %s', marketAccessToken ? 'yes' : 'no');
235
-
236
- // Generate trusted client token if user info is available
237
- const trustedClientToken = ctx.marketUserInfo
238
- ? generateTrustedClientToken(ctx.marketUserInfo)
239
- : undefined;
240
238
 
241
239
  try {
242
- // Initialize MarketSDK with market access token and trusted client token
243
- const market = new MarketSDK({
244
- accessToken: marketAccessToken,
245
- baseURL: process.env.NEXT_PUBLIC_MARKET_BASE_URL,
246
- trustedClientToken,
247
- });
240
+ // Use marketService from ctx
241
+ const market = ctx.marketService.market;
248
242
 
249
243
  // Call market-sdk's runBuildInTool
250
244
  const response = await market.plugins.runBuildInTool(
@@ -555,34 +549,8 @@ export const marketRouter = router({
555
549
  const uploadUrl = await s3.createPreSignedUrl(key);
556
550
  log('Generated upload URL for key: %s', key);
557
551
 
558
- // Step 2: Generate trusted client token if user info is available
559
- const trustedClientToken = ctx.marketUserInfo
560
- ? generateTrustedClientToken(ctx.marketUserInfo)
561
- : undefined;
562
-
563
- // Only require user accessToken if trusted client is not available
564
- let userAccessToken: string | undefined;
565
- if (!trustedClientToken) {
566
- const userState = await ctx.userModel.getUserState(async () => ({}));
567
- userAccessToken = userState.settings?.market?.accessToken;
568
-
569
- if (!userAccessToken) {
570
- return {
571
- error: { message: 'User access token not found. Please sign in to Market first.' },
572
- filename,
573
- success: false,
574
- } as ExportAndUploadFileResult;
575
- }
576
- } else {
577
- log('Using trusted client authentication for exportAndUploadFile');
578
- }
579
-
580
- // Initialize MarketSDK
581
- const market = new MarketSDK({
582
- accessToken: userAccessToken,
583
- baseURL: process.env.NEXT_PUBLIC_MARKET_BASE_URL,
584
- trustedClientToken,
585
- });
552
+ // Step 2: Use MarketService from ctx
553
+ const market = ctx.marketService.market;
586
554
 
587
555
  // Step 3: Call sandbox's exportFile tool with the upload URL
588
556
  const response = await market.plugins.runBuildInTool(
@@ -3,6 +3,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import { AgentRuntimeService } from './AgentRuntimeService';
4
4
  import type { AgentExecutionParams, OperationCreationParams, StartExecutionParams } from './types';
5
5
 
6
+ // Mock trusted client to avoid server-side env access
7
+ vi.mock('@/libs/trusted-client', () => ({
8
+ generateTrustedClientToken: vi.fn().mockReturnValue(undefined),
9
+ getTrustedClientTokenForSession: vi.fn().mockResolvedValue(undefined),
10
+ isTrustedClientEnabled: vi.fn().mockReturnValue(false),
11
+ }));
12
+
6
13
  // Mock database and models
7
14
  vi.mock('@/database/models/message', () => ({
8
15
  MessageModel: vi.fn().mockImplementation(() => ({
@@ -152,7 +152,7 @@ export class AgentRuntimeService {
152
152
 
153
153
  // Initialize ToolExecutionService with dependencies
154
154
  const pluginGatewayService = new PluginGatewayService();
155
- const builtinToolsExecutor = new BuiltinToolsExecutor();
155
+ const builtinToolsExecutor = new BuiltinToolsExecutor(db, userId);
156
156
 
157
157
  this.toolExecutionService = new ToolExecutionService({
158
158
  builtinToolsExecutor,
@@ -2,6 +2,13 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
2
 
3
3
  import { AiAgentService } from '../index';
4
4
 
5
+ // Mock trusted client to avoid server-side env access
6
+ vi.mock('@/libs/trusted-client', () => ({
7
+ generateTrustedClientToken: vi.fn().mockReturnValue(undefined),
8
+ getTrustedClientTokenForSession: vi.fn().mockResolvedValue(undefined),
9
+ isTrustedClientEnabled: vi.fn().mockReturnValue(false),
10
+ }));
11
+
5
12
  // Mock MessageModel to capture create calls
6
13
  const mockMessageCreate = vi.fn();
7
14
 
@@ -3,6 +3,13 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
 
4
4
  import { AiAgentService } from '../index';
5
5
 
6
+ // Mock trusted client to avoid server-side env access
7
+ vi.mock('@/libs/trusted-client', () => ({
8
+ generateTrustedClientToken: vi.fn().mockReturnValue(undefined),
9
+ getTrustedClientTokenForSession: vi.fn().mockResolvedValue(undefined),
10
+ isTrustedClientEnabled: vi.fn().mockReturnValue(false),
11
+ }));
12
+
6
13
  // Mock ThreadModel
7
14
  const mockThreadModel = {
8
15
  create: vi.fn(),