@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.
- package/.github/PULL_REQUEST_TEMPLATE.md +1 -1
- package/CHANGELOG.md +58 -0
- package/changelog/v1.json +21 -0
- package/docs/development/basic/chat-api.mdx +0 -1
- package/docs/development/basic/chat-api.zh-CN.mdx +0 -1
- package/e2e/README.md +1 -1
- package/e2e/src/features/community/detail-pages.feature +2 -2
- package/e2e/src/features/community/interactions.feature +5 -5
- package/e2e/src/features/community/smoke.feature +1 -1
- package/e2e/src/steps/community/detail-pages.steps.ts +6 -4
- package/e2e/src/steps/community/interactions.steps.ts +3 -3
- package/package.json +1 -1
- package/packages/builtin-tool-agent-builder/src/systemRole.ts +9 -0
- package/packages/model-runtime/src/core/BaseAI.ts +0 -2
- package/packages/model-runtime/src/core/ModelRuntime.test.ts +0 -37
- package/packages/model-runtime/src/core/ModelRuntime.ts +0 -5
- package/packages/model-runtime/src/core/RouterRuntime/baseRuntimeMap.ts +4 -0
- package/packages/model-runtime/src/core/RouterRuntime/createRuntime.test.ts +325 -200
- package/packages/model-runtime/src/core/RouterRuntime/createRuntime.ts +205 -64
- package/packages/model-runtime/src/core/openaiCompatibleFactory/index.ts +0 -14
- package/packages/model-runtime/src/providers/aihubmix/index.test.ts +14 -20
- package/packages/model-runtime/src/types/index.ts +0 -1
- package/packages/model-runtime/src/utils/createError.test.ts +0 -20
- package/packages/model-runtime/src/utils/createError.ts +0 -1
- package/public/favicon-32x-32-error.ico +0 -0
- package/public/favicon-32x32-done-dev.ico +0 -0
- package/public/favicon-32x32-done.ico +0 -0
- package/public/favicon-32x32-error-dev.ico +0 -0
- package/public/favicon-32x32-progress-dev.ico +0 -0
- package/public/favicon-32x32-progress.ico +0 -0
- package/public/favicon-done-dev.ico +0 -0
- package/public/favicon-done.ico +0 -0
- package/public/favicon-error-dev.ico +0 -0
- package/public/favicon-error.ico +0 -0
- package/public/favicon-progress-dev.ico +0 -0
- package/public/favicon-progress.ico +0 -0
- package/src/app/(backend)/market/agent/[[...segments]]/route.ts +3 -33
- package/src/app/(backend)/market/oidc/[[...segments]]/route.ts +5 -6
- package/src/app/(backend)/market/social/[[...segments]]/route.ts +5 -52
- package/src/app/(backend)/market/user/[username]/route.ts +3 -9
- package/src/app/(backend)/market/user/me/route.ts +3 -34
- package/src/app/[variants]/(main)/agent/profile/features/Header/AgentPublishButton/PublishResultModal.tsx +1 -1
- package/src/app/[variants]/(main)/community/(detail)/_layout/Header.tsx +15 -3
- package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Overview/TagList.tsx +1 -1
- package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Related/index.tsx +2 -2
- package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/SystemRole/TagList.tsx +1 -1
- package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/SystemRole/index.tsx +1 -1
- package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Header.tsx +2 -2
- package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/ActionButton/AddAgent.tsx +1 -1
- package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/ActionButton/index.tsx +1 -1
- package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Related/index.tsx +2 -2
- package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/StatusPage/index.tsx +2 -2
- package/src/app/[variants]/(main)/community/(detail)/user/features/UserAgentCard.tsx +2 -2
- package/src/app/[variants]/(main)/community/(detail)/user/features/UserFavoriteAgents.tsx +1 -1
- package/src/app/[variants]/(main)/community/(list)/(home)/index.tsx +2 -2
- package/src/app/[variants]/(main)/community/(list)/(home)/loading.tsx +1 -1
- package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/Client.tsx +5 -1
- package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/Category/index.tsx +1 -1
- package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/Item.tsx +1 -1
- package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/MarketSourceSwitch.tsx +1 -1
- package/src/app/[variants]/(main)/community/_layout/Sidebar/Header/Nav.tsx +2 -2
- package/src/app/[variants]/(main)/home/features/CommunityAgents/List.tsx +1 -1
- package/src/app/[variants]/(main)/home/features/CommunityAgents/index.tsx +1 -1
- package/src/app/[variants]/(mobile)/_layout/index.tsx +1 -1
- package/src/app/[variants]/(mobile)/router/mobileRouter.config.tsx +6 -6
- package/src/app/[variants]/router/desktopRouter.config.tsx +8 -8
- package/src/app/[variants]/share/t/[id]/_layout/index.tsx +1 -1
- package/src/features/ChatMiniMap/useMinimapData.ts +1 -1
- package/src/features/CommandMenu/SearchResults.tsx +1 -1
- package/src/features/Conversation/ChatList/components/VirtualizedList.tsx +20 -2
- package/src/features/Conversation/store/slices/virtuaList/action.ts +9 -0
- package/src/features/Electron/navigation/routeMetadata.ts +1 -1
- package/src/layout/GlobalProvider/FaviconProvider.tsx +92 -0
- package/src/layout/GlobalProvider/index.tsx +15 -11
- package/src/libs/next/config/define-config.ts +1 -1
- package/src/libs/trpc/lambda/middleware/marketSDK.ts +14 -23
- package/src/libs/trusted-client/index.ts +1 -1
- package/src/server/routers/lambda/market/index.ts +5 -0
- package/src/server/routers/lambda/market/oidc.ts +41 -61
- package/src/server/routers/tools/market.ts +12 -44
- package/src/server/services/agentRuntime/AgentRuntimeService.test.ts +7 -0
- package/src/server/services/agentRuntime/AgentRuntimeService.ts +1 -1
- package/src/server/services/aiAgent/__tests__/execAgent.threadId.test.ts +7 -0
- package/src/server/services/aiAgent/__tests__/execGroupSubAgentTask.test.ts +7 -0
- package/src/server/services/aiAgent/index.ts +9 -96
- package/src/server/services/discover/index.ts +11 -16
- package/src/server/services/market/index.ts +485 -0
- package/src/server/services/toolExecution/builtin.ts +11 -17
- package/src/server/services/toolExecution/index.ts +6 -2
- package/src/server/sitemap.test.ts +5 -5
- package/src/server/sitemap.ts +3 -3
- package/src/services/codeInterpreter.ts +0 -13
- package/packages/model-runtime/src/types/textToImage.ts +0 -36
- package/src/server/services/lobehubSkill/index.ts +0 -109
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/DetailProvider.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Block.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Knowledge.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/KnowledgeItem.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/PluginItem.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/Plugins.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Capabilities/index.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Nav.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Overview/index.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/Versions/index.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Details/index.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Related/Item.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/Summary/index.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/TocList/index.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/features/Sidebar/index.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/index.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(detail)/{assistant → agent}/loading.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/_layout/index.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/_layout/style.ts +0 -0
- /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/Category/useCategory.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/TokenTag.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/features/List/index.tsx +0 -0
- /package/src/app/[variants]/(main)/community/(list)/{assistant → agent}/index.tsx +0 -0
- /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
|
},
|
|
@@ -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
|
-
<
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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:
|
|
18
|
-
* - ctx.
|
|
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
|
-
//
|
|
24
|
-
const
|
|
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
|
-
|
|
32
|
-
trustedClientToken,
|
|
25
|
+
userInfo: ctx.marketUserInfo,
|
|
33
26
|
});
|
|
34
27
|
|
|
35
28
|
return opts.next({
|
|
36
29
|
ctx: {
|
|
37
|
-
marketSDK: market,
|
|
38
|
-
|
|
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
|
|
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
|
|
48
|
+
const hasUserInfo = !!ctx.marketUserInfo;
|
|
58
49
|
|
|
59
|
-
if (!hasAccessToken && !
|
|
50
|
+
if (!hasAccessToken && !hasUserInfo) {
|
|
60
51
|
const { TRPCError } = await import('@trpc/server');
|
|
61
52
|
throw new TRPCError({
|
|
62
53
|
code: 'UNAUTHORIZED',
|
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
//
|
|
243
|
-
const market =
|
|
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:
|
|
559
|
-
const
|
|
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(),
|