@lobehub/lobehub 2.0.0-next.296 → 2.0.0-next.297

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 (48) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/changelog/v1.json +9 -0
  3. package/package.json +2 -2
  4. package/packages/types/package.json +1 -1
  5. package/packages/types/src/discover/assistants.ts +4 -0
  6. package/packages/types/src/discover/groupAgents.ts +196 -0
  7. package/packages/types/src/discover/index.ts +5 -1
  8. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/DetailProvider.tsx +19 -0
  9. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Members/index.tsx +137 -0
  10. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Nav.tsx +88 -0
  11. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Overview/index.tsx +213 -0
  12. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Related/index.tsx +85 -0
  13. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/SystemRole/TagList.tsx +20 -0
  14. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/SystemRole/index.tsx +71 -0
  15. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/Versions/index.tsx +119 -0
  16. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Details/index.tsx +51 -0
  17. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Header.tsx +253 -0
  18. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/AddGroupAgent.tsx +222 -0
  19. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/ActionButton/index.tsx +34 -0
  20. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/Summary/index.tsx +42 -0
  21. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/Sidebar/index.tsx +41 -0
  22. package/src/app/[variants]/(main)/community/(detail)/group_agent/features/StatusPage/index.tsx +104 -0
  23. package/src/app/[variants]/(main)/community/(detail)/group_agent/index.tsx +103 -0
  24. package/src/app/[variants]/(main)/community/(detail)/group_agent/loading.tsx +1 -0
  25. package/src/app/[variants]/(main)/community/(detail)/user/features/DetailProvider.tsx +7 -1
  26. package/src/app/[variants]/(main)/community/(detail)/user/features/UserContent.tsx +2 -0
  27. package/src/app/[variants]/(main)/community/(detail)/user/features/UserGroupCard.tsx +186 -0
  28. package/src/app/[variants]/(main)/community/(detail)/user/features/UserGroupList.tsx +59 -0
  29. package/src/app/[variants]/(main)/community/(detail)/user/index.tsx +3 -1
  30. package/src/app/[variants]/(main)/community/(list)/assistant/features/List/Item.tsx +26 -8
  31. package/src/app/[variants]/(main)/community/(list)/assistant/index.tsx +1 -0
  32. package/src/app/[variants]/(main)/group/profile/features/GroupProfile/index.tsx +2 -0
  33. package/src/app/[variants]/(main)/group/profile/features/Header/AgentPublishButton/PublishResultModal.tsx +2 -1
  34. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/GroupForkConfirmModal.tsx +60 -0
  35. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/GroupPublishResultModal.tsx +62 -0
  36. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/PublishButton.tsx +122 -0
  37. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/index.tsx +46 -0
  38. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/types.ts +12 -0
  39. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/useMarketGroupPublish.ts +211 -0
  40. package/src/app/[variants]/(main)/group/profile/features/Header/GroupPublishButton/utils.ts +22 -0
  41. package/src/app/[variants]/router/desktopRouter.config.tsx +7 -0
  42. package/src/locales/default/setting.ts +12 -0
  43. package/src/server/routers/lambda/market/agentGroup.ts +296 -0
  44. package/src/server/routers/lambda/market/index.ts +134 -4
  45. package/src/server/services/discover/index.ts +123 -7
  46. package/src/services/discover.ts +55 -0
  47. package/src/store/discover/slices/groupAgent/action.ts +80 -0
  48. package/src/store/discover/store.ts +3 -0
@@ -0,0 +1,296 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import debug from 'debug';
3
+ import { customAlphabet } from 'nanoid/non-secure';
4
+ import { z } from 'zod';
5
+
6
+ import { authedProcedure, router } from '@/libs/trpc/lambda';
7
+ import { marketSDK, marketUserInfo, serverDatabase } from '@/libs/trpc/lambda/middleware';
8
+ import { type TrustedClientUserInfo, generateTrustedClientToken } from '@/libs/trusted-client';
9
+
10
+ const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
11
+
12
+ interface MarketUserInfo {
13
+ accountId: number;
14
+ sub: string;
15
+ }
16
+
17
+ const log = debug('lambda-router:market:agent-group');
18
+
19
+ /**
20
+ * Generate a market identifier (8-character lowercase alphanumeric string)
21
+ * Format: [a-z0-9]{8}
22
+ */
23
+ const generateMarketIdentifier = () => {
24
+ const alphabet = '0123456789abcdefghijklmnopqrstuvwxyz';
25
+ const generate = customAlphabet(alphabet, 8);
26
+ return generate();
27
+ };
28
+
29
+ interface FetchMarketUserInfoOptions {
30
+ accessToken?: string;
31
+ userInfo?: TrustedClientUserInfo;
32
+ }
33
+
34
+ /**
35
+ * Fetch Market user info using either trustedClientToken or accessToken
36
+ * Returns the Market accountId which is different from LobeChat userId
37
+ */
38
+ const fetchMarketUserInfo = async (
39
+ options: FetchMarketUserInfoOptions,
40
+ ): Promise<MarketUserInfo | null> => {
41
+ const { userInfo, accessToken } = options;
42
+
43
+ try {
44
+ const userInfoUrl = `${MARKET_BASE_URL}/lobehub-oidc/userinfo`;
45
+ const headers: Record<string, string> = {
46
+ 'Content-Type': 'application/json',
47
+ };
48
+
49
+ if (userInfo) {
50
+ const trustedClientToken = generateTrustedClientToken(userInfo);
51
+ if (trustedClientToken) {
52
+ headers['x-lobe-trust-token'] = trustedClientToken;
53
+ log('Using trustedClientToken for user info fetch');
54
+ }
55
+ }
56
+
57
+ if (!headers['x-lobe-trust-token'] && accessToken) {
58
+ headers['Authorization'] = `Bearer ${accessToken}`;
59
+ log('Using accessToken for user info fetch');
60
+ }
61
+
62
+ if (!headers['x-lobe-trust-token'] && !headers['Authorization']) {
63
+ log('No authentication method available for fetching user info');
64
+ return null;
65
+ }
66
+
67
+ const response = await fetch(userInfoUrl, {
68
+ headers,
69
+ method: 'GET',
70
+ });
71
+
72
+ if (!response.ok) {
73
+ log('Failed to fetch Market user info: %s %s', response.status, response.statusText);
74
+ return null;
75
+ }
76
+
77
+ return (await response.json()) as MarketUserInfo;
78
+ } catch (error) {
79
+ log('Error fetching Market user info: %O', error);
80
+ return null;
81
+ }
82
+ };
83
+
84
+ // Authenticated procedure for agent group management
85
+ const agentGroupProcedure = authedProcedure
86
+ .use(serverDatabase)
87
+ .use(marketUserInfo)
88
+ .use(marketSDK)
89
+ .use(async ({ ctx, next }) => {
90
+ const { UserModel } = await import('@/database/models/user');
91
+ const userModel = new UserModel(ctx.serverDB, ctx.userId);
92
+
93
+ let marketOidcAccessToken: string | undefined;
94
+ try {
95
+ const userState = await userModel.getUserState(async () => ({}));
96
+ marketOidcAccessToken = userState.settings?.market?.accessToken;
97
+ log('marketOidcAccessToken from DB exists=%s', !!marketOidcAccessToken);
98
+ } catch (error) {
99
+ log('Failed to get marketOidcAccessToken from DB: %O', error);
100
+ }
101
+
102
+ return next({
103
+ ctx: {
104
+ marketOidcAccessToken,
105
+ },
106
+ });
107
+ });
108
+
109
+ // Schema definitions
110
+ const memberAgentSchema = z.object({
111
+ avatar: z.string().nullish(),
112
+ category: z.string().optional(),
113
+ config: z.record(z.any()),
114
+ description: z.string(),
115
+ displayOrder: z.number().optional(),
116
+ identifier: z.string(),
117
+ name: z.string(),
118
+ role: z.enum(['supervisor', 'participant']),
119
+ url: z.string(),
120
+ });
121
+
122
+ const publishOrCreateGroupSchema = z.object({
123
+ avatar: z.string().nullish(),
124
+ backgroundColor: z.string().nullish(),
125
+ category: z.string().optional(),
126
+ changelog: z.string().optional(),
127
+ config: z
128
+ .object({
129
+ allowDM: z.boolean().optional(),
130
+ openingMessage: z.string().optional(),
131
+ openingQuestions: z.array(z.string()).optional(),
132
+ revealDM: z.boolean().optional(),
133
+ systemPrompt: z.string().optional(),
134
+ })
135
+ .optional(),
136
+ description: z.string(),
137
+ identifier: z.string().optional(),
138
+ memberAgents: z.array(memberAgentSchema),
139
+ name: z.string(),
140
+ visibility: z.enum(['public', 'private', 'internal']).optional(),
141
+ });
142
+
143
+ export const agentGroupRouter = router({
144
+ /**
145
+ * Check if current user owns the specified group
146
+ */
147
+ checkOwnership: agentGroupProcedure
148
+ .input(z.object({ identifier: z.string() }))
149
+ .query(async ({ input, ctx }) => {
150
+ log('checkOwnership input: %O', input);
151
+
152
+ try {
153
+ const groupDetail = await ctx.marketSDK.agentGroups.getAgentGroupDetail(input.identifier);
154
+
155
+ if (!groupDetail) {
156
+ return {
157
+ exists: false,
158
+ isOwner: false,
159
+ originalGroup: null,
160
+ };
161
+ }
162
+
163
+ const userInfo = ctx.marketUserInfo as TrustedClientUserInfo | undefined;
164
+ const accessToken = (ctx as { marketOidcAccessToken?: string }).marketOidcAccessToken;
165
+ let currentAccountId: number | null = null;
166
+
167
+ const marketUserInfoResult = await fetchMarketUserInfo({ accessToken, userInfo });
168
+ currentAccountId = marketUserInfoResult?.accountId ?? null;
169
+
170
+ const ownerId = groupDetail.group.ownerId;
171
+ const isOwner = currentAccountId !== null && `${ownerId}` === `${currentAccountId}`;
172
+
173
+ log(
174
+ 'checkOwnership result: isOwner=%s, currentAccountId=%s, ownerId=%s',
175
+ isOwner,
176
+ currentAccountId,
177
+ ownerId,
178
+ );
179
+
180
+ return {
181
+ exists: true,
182
+ isOwner,
183
+ originalGroup: isOwner
184
+ ? null
185
+ : {
186
+ // TODO: Add author info from group detail
187
+ author: undefined,
188
+ avatar: groupDetail.group.avatar,
189
+ identifier: groupDetail.group.identifier,
190
+ name: groupDetail.group.name,
191
+ },
192
+ };
193
+ } catch (error) {
194
+ log('Error checking ownership: %O', error);
195
+ return {
196
+ exists: false,
197
+ isOwner: false,
198
+ originalGroup: null,
199
+ };
200
+ }
201
+ }),
202
+
203
+ /**
204
+ * Unified publish or create agent group flow
205
+ * 1. Check if identifier exists and if current user is owner
206
+ * 2. If not owner or no identifier, create new group
207
+ * 3. Create new version for the group if updating
208
+ */
209
+ publishOrCreate: agentGroupProcedure
210
+ .input(publishOrCreateGroupSchema)
211
+ .mutation(async ({ input, ctx }) => {
212
+ log('publishOrCreate input: %O', input);
213
+
214
+ const { identifier: inputIdentifier, name, memberAgents, ...groupData } = input;
215
+ let finalIdentifier = inputIdentifier;
216
+ let isNewGroup = false;
217
+
218
+ try {
219
+ // Step 1: Check ownership if identifier is provided
220
+ if (inputIdentifier) {
221
+ try {
222
+ const groupDetail =
223
+ await ctx.marketSDK.agentGroups.getAgentGroupDetail(inputIdentifier);
224
+ log('Group detail for ownership check: ownerId=%s', groupDetail?.group.ownerId);
225
+
226
+ const userInfo = ctx.marketUserInfo as TrustedClientUserInfo | undefined;
227
+ const accessToken = (ctx as { marketOidcAccessToken?: string }).marketOidcAccessToken;
228
+ let currentAccountId: number | null = null;
229
+
230
+ const marketUserInfoResult = await fetchMarketUserInfo({ accessToken, userInfo });
231
+ currentAccountId = marketUserInfoResult?.accountId ?? null;
232
+ log('Market user info: accountId=%s', currentAccountId);
233
+
234
+ const ownerId = groupDetail?.group.ownerId;
235
+
236
+ log('Ownership check: currentAccountId=%s, ownerId=%s', currentAccountId, ownerId);
237
+
238
+ if (!currentAccountId || `${ownerId}` !== `${currentAccountId}`) {
239
+ // Not the owner, need to create a new group
240
+ log('User is not owner, will create new group');
241
+ finalIdentifier = undefined;
242
+ isNewGroup = true;
243
+ }
244
+ } catch (detailError) {
245
+ // Group not found or error, create new
246
+ log('Group not found or error, will create new: %O', detailError);
247
+ finalIdentifier = undefined;
248
+ isNewGroup = true;
249
+ }
250
+ } else {
251
+ isNewGroup = true;
252
+ }
253
+
254
+ // Step 2: Create new group or update existing
255
+ if (!finalIdentifier || isNewGroup) {
256
+ // Generate a unique 8-character identifier
257
+ finalIdentifier = generateMarketIdentifier();
258
+ isNewGroup = true;
259
+
260
+ log('Creating new group with identifier: %s', finalIdentifier);
261
+
262
+ await ctx.marketSDK.agentGroups.createAgentGroup({
263
+ ...groupData,
264
+ identifier: finalIdentifier,
265
+ // @ts-ignore
266
+ memberAgents,
267
+ name,
268
+ });
269
+ } else {
270
+ // Update existing group - create new version
271
+ log('Creating new version for group: %s', finalIdentifier);
272
+
273
+ await ctx.marketSDK.agentGroups.createAgentGroupVersion({
274
+ ...groupData,
275
+ identifier: finalIdentifier,
276
+ // @ts-ignore
277
+ memberAgents,
278
+ name,
279
+ });
280
+ }
281
+
282
+ return {
283
+ identifier: finalIdentifier,
284
+ isNewGroup,
285
+ success: true,
286
+ };
287
+ } catch (error) {
288
+ log('Error in publishOrCreate: %O', error);
289
+ throw new TRPCError({
290
+ cause: error,
291
+ code: 'INTERNAL_SERVER_ERROR',
292
+ message: error instanceof Error ? error.message : 'Failed to publish group',
293
+ });
294
+ }
295
+ }),
296
+ });
@@ -17,6 +17,7 @@ import {
17
17
  } from '@/types/discover';
18
18
 
19
19
  import { agentRouter } from './agent';
20
+ import { agentGroupRouter } from './agentGroup';
20
21
  import { oidcRouter } from './oidc';
21
22
  import { socialRouter } from './social';
22
23
  import { userRouter } from './user';
@@ -44,6 +45,9 @@ export const marketRouter = router({
44
45
  // ============================== Agent Management (authenticated) ==============================
45
46
  agent: agentRouter,
46
47
 
48
+ // ============================== Agent Group Management (authenticated) ==============================
49
+ agentGroup: agentGroupRouter,
50
+
47
51
  // ============================== Assistant Market ==============================
48
52
  getAssistantCategories: marketProcedure
49
53
  .input(
@@ -120,6 +124,7 @@ export const marketRouter = router({
120
124
  .object({
121
125
  category: z.string().optional(),
122
126
  connectionType: z.nativeEnum(McpConnectionType).optional(),
127
+ includeAgentGroup: z.boolean().optional(),
123
128
  locale: z.string().optional(),
124
129
  order: z.enum(['asc', 'desc']).optional(),
125
130
  ownerId: z.string().optional(),
@@ -145,6 +150,95 @@ export const marketRouter = router({
145
150
  }
146
151
  }),
147
152
 
153
+ // ============================== Group Agent Market (Discovery) ==============================
154
+ getGroupAgentCategories: marketProcedure
155
+ .input(
156
+ z
157
+ .object({
158
+ locale: z.string().optional(),
159
+ q: z.string().optional(),
160
+ })
161
+ .optional(),
162
+ )
163
+ .query(async ({ input, ctx }) => {
164
+ log('getGroupAgentCategories input: %O', input);
165
+
166
+ try {
167
+ return await ctx.discoverService.getGroupAgentCategories(input);
168
+ } catch (error) {
169
+ log('Error fetching group agent categories: %O', error);
170
+ throw new TRPCError({
171
+ code: 'INTERNAL_SERVER_ERROR',
172
+ message: 'Failed to fetch group agent categories',
173
+ });
174
+ }
175
+ }),
176
+
177
+ getGroupAgentDetail: marketProcedure
178
+ .input(
179
+ z.object({
180
+ identifier: z.string(),
181
+ locale: z.string().optional(),
182
+ version: z.string().optional(),
183
+ }),
184
+ )
185
+ .query(async ({ input, ctx }) => {
186
+ log('getGroupAgentDetail input: %O', input);
187
+
188
+ try {
189
+ return await ctx.discoverService.getGroupAgentDetail(input);
190
+ } catch (error) {
191
+ log('Error fetching group agent detail: %O', error);
192
+ throw new TRPCError({
193
+ code: 'INTERNAL_SERVER_ERROR',
194
+ message: 'Failed to fetch group agent detail',
195
+ });
196
+ }
197
+ }),
198
+
199
+ getGroupAgentIdentifiers: marketProcedure.query(async ({ ctx }) => {
200
+ log('getGroupAgentIdentifiers called');
201
+
202
+ try {
203
+ return await ctx.discoverService.getGroupAgentIdentifiers();
204
+ } catch (error) {
205
+ log('Error fetching group agent identifiers: %O', error);
206
+ throw new TRPCError({
207
+ code: 'INTERNAL_SERVER_ERROR',
208
+ message: 'Failed to fetch group agent identifiers',
209
+ });
210
+ }
211
+ }),
212
+
213
+ getGroupAgentList: marketProcedure
214
+ .input(
215
+ z
216
+ .object({
217
+ category: z.string().optional(),
218
+ locale: z.string().optional(),
219
+ order: z.enum(['asc', 'desc']).optional(),
220
+ ownerId: z.string().optional(),
221
+ page: z.number().optional(),
222
+ pageSize: z.number().optional(),
223
+ q: z.string().optional(),
224
+ sort: z.enum(['createdAt', 'updatedAt', 'name', 'recommended']).optional(),
225
+ })
226
+ .optional(),
227
+ )
228
+ .query(async ({ input, ctx }) => {
229
+ log('getGroupAgentList input: %O', input);
230
+
231
+ try {
232
+ return await ctx.discoverService.getGroupAgentList(input);
233
+ } catch (error) {
234
+ log('Error fetching group agent list: %O', error);
235
+ throw new TRPCError({
236
+ code: 'INTERNAL_SERVER_ERROR',
237
+ message: 'Failed to fetch group agent list',
238
+ });
239
+ }
240
+ }),
241
+
148
242
  getLegacyPluginList: marketProcedure
149
243
  .input(
150
244
  z
@@ -167,7 +261,7 @@ export const marketRouter = router({
167
261
  }),
168
262
 
169
263
  // ============================== MCP Market ==============================
170
- getMcpCategories: marketProcedure
264
+ getMcpCategories: marketProcedure
171
265
  .input(
172
266
  z
173
267
  .object({
@@ -351,7 +445,7 @@ getMcpCategories: marketProcedure
351
445
  }),
352
446
 
353
447
  // ============================== Plugin Market ==============================
354
- getPluginCategories: marketProcedure
448
+ getPluginCategories: marketProcedure
355
449
  .input(
356
450
  z
357
451
  .object({
@@ -439,7 +533,7 @@ getPluginCategories: marketProcedure
439
533
  }),
440
534
 
441
535
  // ============================== Providers ==============================
442
- getProviderDetail: marketProcedure
536
+ getProviderDetail: marketProcedure
443
537
  .input(
444
538
  z.object({
445
539
  identifier: z.string(),
@@ -503,7 +597,7 @@ getProviderDetail: marketProcedure
503
597
  }),
504
598
 
505
599
  // ============================== User Profile ==============================
506
- getUserInfo: marketProcedure
600
+ getUserInfo: marketProcedure
507
601
  .input(
508
602
  z.object({
509
603
  locale: z.string().optional(),
@@ -675,6 +769,42 @@ getUserInfo: marketProcedure
675
769
  }
676
770
  }),
677
771
 
772
+ reportGroupAgentEvent: marketProcedure
773
+ .input(
774
+ z.object({
775
+ event: z.enum(['add', 'chat', 'click']),
776
+ identifier: z.string(),
777
+ source: z.string().optional(),
778
+ }),
779
+ )
780
+ .mutation(async ({ input, ctx }) => {
781
+ log('reportGroupAgentEvent input: %O', input);
782
+
783
+ try {
784
+ await ctx.discoverService.createGroupAgentEvent(input);
785
+ return { success: true };
786
+ } catch (error) {
787
+ console.error('Error reporting Group Agent event: %O', error);
788
+ return { success: false };
789
+ }
790
+ }),
791
+
792
+ reportGroupAgentInstall: marketProcedure
793
+ .input(
794
+ z.object({
795
+ identifier: z.string(),
796
+ }),
797
+ )
798
+ .mutation(async ({ input, ctx }) => {
799
+ log('reportGroupAgentInstall input: %O', input);
800
+ try {
801
+ await ctx.discoverService.increaseGroupAgentInstallCount(input.identifier);
802
+ return { success: true };
803
+ } catch (error) {
804
+ log('Error reporting group agent installation: %O', error);
805
+ return { success: false };
806
+ }
807
+ }),
678
808
 
679
809
  reportMcpEvent: marketProcedure
680
810
  .input(
@@ -33,7 +33,7 @@ import {
33
33
  type ModelQueryParams,
34
34
  ModelSorts,
35
35
  type PluginListResponse,
36
- type PluginQueryParams as PluginQueryParams,
36
+ type PluginQueryParams,
37
37
  PluginSorts,
38
38
  type ProviderListResponse,
39
39
  type ProviderQueryParams,
@@ -718,6 +718,7 @@ export class DiscoverService {
718
718
  q,
719
719
  sort = AssistantSorts.CreatedAt,
720
720
  ownerId,
721
+ includeAgentGroup,
721
722
  } = rest;
722
723
 
723
724
  try {
@@ -740,9 +741,10 @@ export class DiscoverService {
740
741
  }
741
742
  }
742
743
 
743
- // @ts-ignore
744
744
  const data = await this.market.agents.getAgentList({
745
745
  category,
746
+ // includeAgentGroup may not be in SDK type definition yet, using 'as any'
747
+ includeAgentGroup,
746
748
  locale: normalizedLocale,
747
749
  order,
748
750
  ownerId,
@@ -752,7 +754,7 @@ export class DiscoverService {
752
754
  sort: apiSort,
753
755
  status: 'published',
754
756
  visibility: 'public',
755
- });
757
+ } as any);
756
758
 
757
759
  const transformedItems: DiscoverAssistantItem[] = (data.items || []).map((item: any) => {
758
760
  const normalizedAuthor = this.normalizeAuthorField(item.author);
@@ -773,6 +775,7 @@ export class DiscoverService {
773
775
  tags: item.tags || [],
774
776
  title: item.name || item.identifier,
775
777
  tokenUsage: item.tokenUsage || 0,
778
+ type: item.type,
776
779
  userName: normalizedAuthor.userName,
777
780
  };
778
781
  });
@@ -922,7 +925,6 @@ export class DiscoverService {
922
925
  await this.market.plugins.createEvent(params);
923
926
  };
924
927
 
925
-
926
928
  /**
927
929
  * report plugin call result to marketplace
928
930
  */
@@ -1735,14 +1737,18 @@ export class DiscoverService {
1735
1737
 
1736
1738
  try {
1737
1739
  // Call Market SDK to get user info
1738
- const response: UserInfoResponse = await this.market.user.getUserInfo(username, { locale });
1740
+ const response = (await this.market.user.getUserInfo(username, {
1741
+ locale,
1742
+ })) as UserInfoResponse & {
1743
+ agentGroups?: any[];
1744
+ };
1739
1745
 
1740
1746
  if (!response?.user) {
1741
1747
  log('getUserInfo: user not found for username=%s', username);
1742
1748
  return undefined;
1743
1749
  }
1744
1750
 
1745
- const { user, agents } = response;
1751
+ const { user, agents, agentGroups } = response;
1746
1752
 
1747
1753
  // Transform agents to DiscoverAssistantItem format
1748
1754
  const transformedAgents: DiscoverAssistantItem[] = (agents || []).map((agent: any) => ({
@@ -1763,7 +1769,27 @@ export class DiscoverService {
1763
1769
  tokenUsage: agent.tokenUsage || 0,
1764
1770
  }));
1765
1771
 
1772
+ // Transform agentGroups to DiscoverGroupAgentItem format
1773
+ const transformedAgentGroups = (agentGroups || []).map((group: any) => ({
1774
+ author: user.displayName || user.userName || user.namespace || '',
1775
+ avatar: group.avatar || '👥',
1776
+ category: group.category as any,
1777
+ createdAt: group.createdAt,
1778
+ description: group.description || '',
1779
+ homepage: `https://lobehub.com/discover/group_agent/${group.identifier}`,
1780
+ identifier: group.identifier,
1781
+ installCount: group.installCount || 0,
1782
+ isFeatured: group.isFeatured || false,
1783
+ isOfficial: group.isOfficial || false,
1784
+ memberCount: 0, // Will be populated from memberAgents in detail view
1785
+ schemaVersion: 1,
1786
+ tags: group.tags || [],
1787
+ title: group.name || group.identifier,
1788
+ updatedAt: group.updatedAt,
1789
+ }));
1790
+
1766
1791
  const result: DiscoverUserProfile = {
1792
+ agentGroups: transformedAgentGroups,
1767
1793
  agents: transformedAgents,
1768
1794
  user: {
1769
1795
  avatarUrl: user.avatarUrl || null,
@@ -1781,11 +1807,101 @@ export class DiscoverService {
1781
1807
  },
1782
1808
  };
1783
1809
 
1784
- log('getUserInfo: returning user profile with %d agents', result.agents.length);
1810
+ log(
1811
+ 'getUserInfo: returning user profile with %d agents and %d groups',
1812
+ result.agents.length,
1813
+ result.agentGroups?.length || 0,
1814
+ );
1785
1815
  return result;
1786
1816
  } catch (error) {
1787
1817
  log('getUserInfo: error fetching user info: %O', error);
1788
1818
  return undefined;
1789
1819
  }
1790
1820
  };
1821
+
1822
+ // ============================== Group Agent Market Methods ==============================
1823
+
1824
+ getGroupAgentCategories = async (params?: CategoryListQuery) => {
1825
+ try {
1826
+ // TODO: SDK method not yet available, using fallback
1827
+ const response = await (this.market.agentGroups as any).getAgentGroupCategories?.(params);
1828
+ return response || { items: [] };
1829
+ } catch (error) {
1830
+ log('getGroupAgentCategories: error: %O', error);
1831
+ return { items: [] };
1832
+ }
1833
+ };
1834
+
1835
+ getGroupAgentDetail = async (params: {
1836
+ identifier: string;
1837
+ locale?: string;
1838
+ version?: string;
1839
+ }) => {
1840
+ try {
1841
+ const response = await this.market.agentGroups.getAgentGroupDetail(params.identifier, {
1842
+ locale: params.locale,
1843
+ version: params.version ? Number(params.version) : undefined,
1844
+ });
1845
+ return response;
1846
+ } catch (error) {
1847
+ log('getGroupAgentDetail: error: %O', error);
1848
+ throw error;
1849
+ }
1850
+ };
1851
+
1852
+ getGroupAgentIdentifiers = async () => {
1853
+ try {
1854
+ // TODO: SDK method not yet available, using fallback
1855
+ const response = await (this.market.agentGroups as any).getAgentGroupIdentifiers?.();
1856
+ return response || { identifiers: [] };
1857
+ } catch (error) {
1858
+ log('getGroupAgentIdentifiers: error: %O', error);
1859
+ return { identifiers: [] };
1860
+ }
1861
+ };
1862
+
1863
+ getGroupAgentList = async (params?: {
1864
+ category?: string;
1865
+ locale?: string;
1866
+ order?: 'asc' | 'desc';
1867
+ ownerId?: string;
1868
+ page?: number;
1869
+ pageSize?: number;
1870
+ q?: string;
1871
+ sort?: 'createdAt' | 'updatedAt' | 'name' | 'recommended';
1872
+ }) => {
1873
+ try {
1874
+ const response = await this.market.agentGroups.getAgentGroupList({
1875
+ ...params,
1876
+ status: 'published' as any,
1877
+ visibility: 'public' as any,
1878
+ });
1879
+ return response;
1880
+ } catch (error) {
1881
+ log('getGroupAgentList: error: %O', error);
1882
+ return { currentPage: 1, items: [], totalCount: 0, totalPages: 1 };
1883
+ }
1884
+ };
1885
+
1886
+ createGroupAgentEvent = async (params: {
1887
+ event: 'add' | 'chat' | 'click';
1888
+ identifier: string;
1889
+ source?: string;
1890
+ }) => {
1891
+ try {
1892
+ // TODO: SDK method not yet available
1893
+ await (this.market.agentGroups as any).createAgentGroupEvent?.(params);
1894
+ } catch (error) {
1895
+ log('createGroupAgentEvent: error: %O', error);
1896
+ }
1897
+ };
1898
+
1899
+ increaseGroupAgentInstallCount = async (identifier: string) => {
1900
+ try {
1901
+ // TODO: SDK method not yet available
1902
+ await (this.market.agentGroups as any).increaseInstallCount?.(identifier);
1903
+ } catch (error) {
1904
+ log('increaseGroupAgentInstallCount: error: %O', error);
1905
+ }
1906
+ };
1791
1907
  }