@lobehub/lobehub 2.0.0-next.310 → 2.0.0-next.311
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 +25 -0
- package/changelog/v1.json +9 -0
- package/docs/development/basic/chat-api.mdx +0 -1
- package/docs/development/basic/chat-api.zh-CN.mdx +0 -1
- package/package.json +1 -1
- 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/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/features/ChatMiniMap/useMinimapData.ts +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/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/services/codeInterpreter.ts +0 -13
- package/packages/model-runtime/src/types/textToImage.ts +0 -36
- package/src/server/services/lobehubSkill/index.ts +0 -109
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { MarketSDK } from '@lobehub/market-sdk';
|
|
2
1
|
import { type NextRequest, NextResponse } from 'next/server';
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { MarketService } from '@/server/services/market';
|
|
5
4
|
|
|
6
5
|
type RouteContext = {
|
|
7
6
|
params: Promise<{
|
|
@@ -9,19 +8,6 @@ type RouteContext = {
|
|
|
9
8
|
}>;
|
|
10
9
|
};
|
|
11
10
|
|
|
12
|
-
const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Helper to get authorization header
|
|
16
|
-
*/
|
|
17
|
-
const getAccessToken = (req: NextRequest): string | undefined => {
|
|
18
|
-
const authHeader = req.headers.get('authorization');
|
|
19
|
-
if (authHeader?.startsWith('Bearer ')) {
|
|
20
|
-
return authHeader.slice(7);
|
|
21
|
-
}
|
|
22
|
-
return undefined;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
11
|
/**
|
|
26
12
|
* POST /market/social/follow
|
|
27
13
|
* POST /market/social/unfollow
|
|
@@ -34,22 +20,9 @@ const getAccessToken = (req: NextRequest): string | undefined => {
|
|
|
34
20
|
export const POST = async (req: NextRequest, context: RouteContext) => {
|
|
35
21
|
const { segments = [] } = await context.params;
|
|
36
22
|
const action = segments[0];
|
|
37
|
-
const accessToken = getAccessToken(req);
|
|
38
|
-
const trustedClientToken = await getTrustedClientTokenForSession();
|
|
39
|
-
|
|
40
|
-
const market = new MarketSDK({
|
|
41
|
-
accessToken,
|
|
42
|
-
baseURL: MARKET_BASE_URL,
|
|
43
|
-
trustedClientToken,
|
|
44
|
-
});
|
|
45
23
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return NextResponse.json(
|
|
49
|
-
{ error: 'unauthorized', message: 'Access token required' },
|
|
50
|
-
{ status: 401 },
|
|
51
|
-
);
|
|
52
|
-
}
|
|
24
|
+
const marketService = await MarketService.createFromRequest(req);
|
|
25
|
+
const market = marketService.market;
|
|
53
26
|
|
|
54
27
|
try {
|
|
55
28
|
const body = await req.json();
|
|
@@ -135,14 +108,9 @@ export const POST = async (req: NextRequest, context: RouteContext) => {
|
|
|
135
108
|
export const GET = async (req: NextRequest, context: RouteContext) => {
|
|
136
109
|
const { segments = [] } = await context.params;
|
|
137
110
|
const action = segments[0];
|
|
138
|
-
const accessToken = getAccessToken(req);
|
|
139
|
-
const trustedClientToken = await getTrustedClientTokenForSession();
|
|
140
111
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
baseURL: MARKET_BASE_URL,
|
|
144
|
-
trustedClientToken,
|
|
145
|
-
});
|
|
112
|
+
const marketService = await MarketService.createFromRequest(req);
|
|
113
|
+
const market = marketService.market;
|
|
146
114
|
|
|
147
115
|
const url = new URL(req.url);
|
|
148
116
|
const limit = url.searchParams.get('pageSize') || url.searchParams.get('limit');
|
|
@@ -158,9 +126,6 @@ export const GET = async (req: NextRequest, context: RouteContext) => {
|
|
|
158
126
|
// Follow queries
|
|
159
127
|
case 'follow-status': {
|
|
160
128
|
const targetUserId = Number(segments[1]);
|
|
161
|
-
if (!accessToken && !trustedClientToken) {
|
|
162
|
-
return NextResponse.json({ isFollowing: false, isMutual: false });
|
|
163
|
-
}
|
|
164
129
|
const result = await market.follows.checkFollowStatus(targetUserId);
|
|
165
130
|
return NextResponse.json(result);
|
|
166
131
|
}
|
|
@@ -193,9 +158,6 @@ export const GET = async (req: NextRequest, context: RouteContext) => {
|
|
|
193
158
|
case 'favorite-status': {
|
|
194
159
|
const targetType = segments[1] as 'agent' | 'plugin';
|
|
195
160
|
const targetIdOrIdentifier = segments[2];
|
|
196
|
-
if (!accessToken && !trustedClientToken) {
|
|
197
|
-
return NextResponse.json({ isFavorited: false });
|
|
198
|
-
}
|
|
199
161
|
// SDK accepts both number (targetId) and string (identifier)
|
|
200
162
|
const isNumeric = /^\d+$/.test(targetIdOrIdentifier);
|
|
201
163
|
const targetValue = isNumeric ? Number(targetIdOrIdentifier) : targetIdOrIdentifier;
|
|
@@ -204,12 +166,6 @@ export const GET = async (req: NextRequest, context: RouteContext) => {
|
|
|
204
166
|
}
|
|
205
167
|
|
|
206
168
|
case 'favorites': {
|
|
207
|
-
if (!accessToken) {
|
|
208
|
-
return NextResponse.json(
|
|
209
|
-
{ error: 'unauthorized', message: 'Access token required' },
|
|
210
|
-
{ status: 401 },
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
169
|
const result = await market.favorites.getMyFavorites(paginationParams);
|
|
214
170
|
return NextResponse.json(result);
|
|
215
171
|
}
|
|
@@ -236,9 +192,6 @@ export const GET = async (req: NextRequest, context: RouteContext) => {
|
|
|
236
192
|
case 'like-status': {
|
|
237
193
|
const targetType = segments[1] as 'agent' | 'plugin';
|
|
238
194
|
const targetIdOrIdentifier = segments[2];
|
|
239
|
-
if (!accessToken && !trustedClientToken) {
|
|
240
|
-
return NextResponse.json({ isLiked: false });
|
|
241
|
-
}
|
|
242
195
|
const isNumeric = /^\d+$/.test(targetIdOrIdentifier);
|
|
243
196
|
const targetValue = isNumeric ? Number(targetIdOrIdentifier) : targetIdOrIdentifier;
|
|
244
197
|
const result = await market.likes.checkLike(targetType, targetValue as number);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { MarketSDK } from '@lobehub/market-sdk';
|
|
2
1
|
import { type NextRequest, NextResponse } from 'next/server';
|
|
3
2
|
|
|
4
|
-
import {
|
|
3
|
+
import { MarketService } from '@/server/services/market';
|
|
5
4
|
|
|
6
5
|
type RouteContext = {
|
|
7
6
|
params: Promise<{
|
|
@@ -9,8 +8,6 @@ type RouteContext = {
|
|
|
9
8
|
}>;
|
|
10
9
|
};
|
|
11
10
|
|
|
12
|
-
const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
|
|
13
|
-
|
|
14
11
|
/**
|
|
15
12
|
* GET /market/user/[username]
|
|
16
13
|
*
|
|
@@ -20,12 +17,9 @@ const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://mark
|
|
|
20
17
|
export const GET = async (req: NextRequest, context: RouteContext) => {
|
|
21
18
|
const { username } = await context.params;
|
|
22
19
|
const decodedUsername = decodeURIComponent(username);
|
|
23
|
-
const trustedClientToken = await getTrustedClientTokenForSession();
|
|
24
20
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
trustedClientToken,
|
|
28
|
-
});
|
|
21
|
+
const marketService = await MarketService.createFromRequest(req);
|
|
22
|
+
const market = marketService.market;
|
|
29
23
|
|
|
30
24
|
try {
|
|
31
25
|
const response = await market.user.getUserInfo(decodedUsername);
|
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
import { MarketSDK } from '@lobehub/market-sdk';
|
|
2
1
|
import { type NextRequest, NextResponse } from 'next/server';
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
|
|
7
|
-
|
|
8
|
-
const extractAccessToken = (req: NextRequest) => {
|
|
9
|
-
const authorization = req.headers.get('authorization');
|
|
10
|
-
if (!authorization) return undefined;
|
|
11
|
-
|
|
12
|
-
const [scheme, token] = authorization.split(' ');
|
|
13
|
-
if (scheme?.toLowerCase() !== 'bearer' || !token) return undefined;
|
|
14
|
-
|
|
15
|
-
return token;
|
|
16
|
-
};
|
|
3
|
+
import { MarketService } from '@/server/services/market';
|
|
17
4
|
|
|
18
5
|
/**
|
|
19
6
|
* PUT /market/user/me
|
|
@@ -28,26 +15,8 @@ const extractAccessToken = (req: NextRequest) => {
|
|
|
28
15
|
* - meta?: { description?: string; socialLinks?: { github?: string; twitter?: string; website?: string } }
|
|
29
16
|
*/
|
|
30
17
|
export const PUT = async (req: NextRequest) => {
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const market = new MarketSDK({
|
|
35
|
-
accessToken,
|
|
36
|
-
baseURL: MARKET_BASE_URL,
|
|
37
|
-
trustedClientToken,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Only require accessToken if trusted client token is not available
|
|
41
|
-
if (!accessToken && !trustedClientToken) {
|
|
42
|
-
return NextResponse.json(
|
|
43
|
-
{
|
|
44
|
-
error: 'unauthorized',
|
|
45
|
-
message: 'Authentication required to update user profile',
|
|
46
|
-
status: 'error',
|
|
47
|
-
},
|
|
48
|
-
{ status: 401 },
|
|
49
|
-
);
|
|
50
|
-
}
|
|
18
|
+
const marketService = await MarketService.createFromRequest(req);
|
|
19
|
+
const market = marketService.market;
|
|
51
20
|
|
|
52
21
|
try {
|
|
53
22
|
const payload = await req.json();
|
|
@@ -91,7 +91,7 @@ export const useMinimapData = () => {
|
|
|
91
91
|
}
|
|
92
92
|
} else {
|
|
93
93
|
// No active index, go to first/last
|
|
94
|
-
targetPosition = direction === 'prev' ? indicators.length - 1
|
|
94
|
+
targetPosition = direction === 'prev' ? 0 : indicators.length - 1;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
const targetIndicator = indicators[targetPosition];
|
|
@@ -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
|
},
|
|
@@ -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(() => ({
|