@lobehub/lobehub 2.0.0-next.225 → 2.0.0-next.227
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/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/docs/self-hosting/server-database/docker-compose.mdx +11 -0
- package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +11 -0
- package/locales/en-US/setting.json +4 -0
- package/locales/zh-CN/setting.json +4 -0
- package/package.json +2 -2
- package/packages/model-bank/src/aiModels/lobehub.ts +28 -0
- package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/ForkConfirmModal.tsx +67 -0
- package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/PublishButton.tsx +92 -105
- package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/index.tsx +13 -48
- package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/useMarketPublish.ts +69 -93
- package/src/business/client/hooks/useBusinessErrorAlertConfig.ts +9 -0
- package/src/business/client/hooks/useBusinessErrorContent.ts +13 -0
- package/src/business/server/lambda-routers/file.ts +12 -0
- package/src/features/Conversation/Error/index.tsx +13 -3
- package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +11 -28
- package/src/layout/AuthProvider/MarketAuth/ProfileSetupModal.tsx +30 -25
- package/src/layout/AuthProvider/MarketAuth/useMarketUserProfile.ts +4 -9
- package/src/libs/trpc/lambda/middleware/index.ts +1 -0
- package/src/libs/trpc/lambda/middleware/marketSDK.ts +68 -0
- package/src/locales/default/setting.ts +5 -0
- package/src/server/routers/lambda/__tests__/file.test.ts +7 -7
- package/src/server/routers/lambda/file.ts +12 -3
- package/src/server/routers/lambda/market/agent.ts +504 -0
- package/src/server/routers/lambda/market/index.ts +17 -0
- package/src/server/routers/lambda/market/oidc.ts +169 -0
- package/src/server/routers/lambda/market/social.ts +532 -0
- package/src/server/routers/lambda/market/user.ts +123 -0
- package/src/services/marketApi.ts +24 -84
- package/src/services/social.ts +70 -166
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TRPCError } from '@trpc/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
|
+
import { businessFileUploadCheck } from '@/business/server/lambda-routers/file';
|
|
4
5
|
import { checkFileStorageUsage } from '@/business/server/trpc-middlewares/lambda';
|
|
5
6
|
import { serverDBEnv } from '@/config/db';
|
|
6
7
|
import { AsyncTaskModel } from '@/database/models/asyncTask';
|
|
@@ -74,8 +75,16 @@ export const fileRouter = router({
|
|
|
74
75
|
// If metadata fetch fails, use original size from input
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
await businessFileUploadCheck({
|
|
79
|
+
actualSize,
|
|
80
|
+
clientIp: ctx.clientIp ?? undefined,
|
|
81
|
+
inputSize: input.size,
|
|
82
|
+
url: input.url,
|
|
83
|
+
userId: ctx.userId,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
if (actualSize < 0) {
|
|
87
|
+
throw new TRPCError({ code: 'BAD_REQUEST', message: 'File size cannot be negative' });
|
|
79
88
|
}
|
|
80
89
|
|
|
81
90
|
const { id } = await ctx.fileModel.create(
|
|
@@ -367,7 +376,7 @@ export const fileRouter = router({
|
|
|
367
376
|
|
|
368
377
|
if (!file) return;
|
|
369
378
|
|
|
370
|
-
//
|
|
379
|
+
// delete the file from S3 if it is not used by other files
|
|
371
380
|
await ctx.fileService.deleteFile(file.url!);
|
|
372
381
|
}),
|
|
373
382
|
|
|
@@ -0,0 +1,504 @@
|
|
|
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');
|
|
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
|
+
* Priority:
|
|
39
|
+
* 1. trustedClientToken (if userInfo is provided and TRUSTED_CLIENT_SECRET is configured)
|
|
40
|
+
* 2. accessToken (if provided, from OIDC flow)
|
|
41
|
+
*/
|
|
42
|
+
const fetchMarketUserInfo = async (
|
|
43
|
+
options: FetchMarketUserInfoOptions,
|
|
44
|
+
): Promise<MarketUserInfo | null> => {
|
|
45
|
+
const { userInfo, accessToken } = options;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const userInfoUrl = `${MARKET_BASE_URL}/lobehub-oidc/userinfo`;
|
|
49
|
+
const headers: Record<string, string> = {
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Try trustedClientToken first (if userInfo is available)
|
|
54
|
+
if (userInfo) {
|
|
55
|
+
const trustedClientToken = generateTrustedClientToken(userInfo);
|
|
56
|
+
if (trustedClientToken) {
|
|
57
|
+
headers['x-lobe-trust-token'] = trustedClientToken;
|
|
58
|
+
log('Using trustedClientToken for user info fetch');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Fall back to accessToken if no trustedClientToken
|
|
63
|
+
if (!headers['x-lobe-trust-token'] && accessToken) {
|
|
64
|
+
headers['Authorization'] = `Bearer ${accessToken}`;
|
|
65
|
+
log('Using accessToken for user info fetch');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// If neither authentication method is available, return null
|
|
69
|
+
if (!headers['x-lobe-trust-token'] && !headers['Authorization']) {
|
|
70
|
+
log('No authentication method available for fetching user info');
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const response = await fetch(userInfoUrl, {
|
|
75
|
+
headers,
|
|
76
|
+
method: 'GET',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
log('Failed to fetch Market user info: %s %s', response.status, response.statusText);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (await response.json()) as MarketUserInfo;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
log('Error fetching Market user info: %O', error);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Authenticated procedure for agent management
|
|
92
|
+
// Requires user to be logged in and has MarketSDK initialized
|
|
93
|
+
// Also fetches user's market accessToken from database for OIDC authentication
|
|
94
|
+
const agentProcedure = authedProcedure
|
|
95
|
+
.use(serverDatabase)
|
|
96
|
+
.use(marketUserInfo)
|
|
97
|
+
.use(marketSDK)
|
|
98
|
+
.use(async ({ ctx, next }) => {
|
|
99
|
+
// Import UserModel dynamically to avoid circular dependencies
|
|
100
|
+
const { UserModel } = await import('@/database/models/user');
|
|
101
|
+
const userModel = new UserModel(ctx.serverDB, ctx.userId);
|
|
102
|
+
|
|
103
|
+
// Get user's market accessToken from database (stored by MarketAuthProvider after OIDC login)
|
|
104
|
+
let marketOidcAccessToken: string | undefined;
|
|
105
|
+
try {
|
|
106
|
+
const userState = await userModel.getUserState(async () => ({}));
|
|
107
|
+
marketOidcAccessToken = userState.settings?.market?.accessToken;
|
|
108
|
+
log('marketOidcAccessToken from DB exists=%s', !!marketOidcAccessToken);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
log('Failed to get marketOidcAccessToken from DB: %O', error);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return next({
|
|
114
|
+
ctx: {
|
|
115
|
+
marketOidcAccessToken,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Schema definitions
|
|
121
|
+
const createAgentSchema = z.object({
|
|
122
|
+
homepage: z.string().optional(),
|
|
123
|
+
identifier: z.string(),
|
|
124
|
+
isFeatured: z.boolean().optional(),
|
|
125
|
+
name: z.string(),
|
|
126
|
+
status: z.enum(['published', 'unpublished', 'archived', 'deprecated']).optional(),
|
|
127
|
+
tokenUsage: z.number().optional(),
|
|
128
|
+
visibility: z.enum(['public', 'private', 'internal']).optional(),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const createAgentVersionSchema = z.object({
|
|
132
|
+
a2aProtocolVersion: z.string().optional(),
|
|
133
|
+
avatar: z.string().optional(),
|
|
134
|
+
category: z.string().optional(),
|
|
135
|
+
changelog: z.string().optional(),
|
|
136
|
+
config: z.record(z.any()).optional(),
|
|
137
|
+
defaultInputModes: z.array(z.string()).optional(),
|
|
138
|
+
defaultOutputModes: z.array(z.string()).optional(),
|
|
139
|
+
description: z.string().optional(),
|
|
140
|
+
documentationUrl: z.string().optional(),
|
|
141
|
+
extensions: z.array(z.record(z.any())).optional(),
|
|
142
|
+
hasPushNotifications: z.boolean().optional(),
|
|
143
|
+
hasStateTransitionHistory: z.boolean().optional(),
|
|
144
|
+
hasStreaming: z.boolean().optional(),
|
|
145
|
+
identifier: z.string(),
|
|
146
|
+
interfaces: z.array(z.record(z.any())).optional(),
|
|
147
|
+
name: z.string().optional(),
|
|
148
|
+
preferredTransport: z.string().optional(),
|
|
149
|
+
providerId: z.number().optional(),
|
|
150
|
+
securityRequirements: z.array(z.record(z.any())).optional(),
|
|
151
|
+
securitySchemes: z.record(z.any()).optional(),
|
|
152
|
+
setAsCurrent: z.boolean().optional(),
|
|
153
|
+
summary: z.string().optional(),
|
|
154
|
+
supportsAuthenticatedExtendedCard: z.boolean().optional(),
|
|
155
|
+
tokenUsage: z.number().optional(),
|
|
156
|
+
url: z.string().optional(),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const paginationSchema = z.object({
|
|
160
|
+
page: z.number().optional(),
|
|
161
|
+
pageSize: z.number().optional(),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Schema for the unified publish/create flow
|
|
165
|
+
const publishOrCreateSchema = z.object({
|
|
166
|
+
|
|
167
|
+
// Version data
|
|
168
|
+
avatar: z.string().optional(),
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
category: z.string().optional(),
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
changelog: z.string().optional(),
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
config: z.record(z.any()).optional(),
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
description: z.string().optional(),
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
editorData: z.record(z.any()).optional(),
|
|
186
|
+
|
|
187
|
+
// Agent basic info
|
|
188
|
+
identifier: z.string().optional(),
|
|
189
|
+
// Optional - if not provided or not owned, will create new
|
|
190
|
+
name: z.string(),
|
|
191
|
+
tags: z.array(z.string()).optional(),
|
|
192
|
+
tokenUsage: z.number().optional(),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
export const agentRouter = router({
|
|
196
|
+
/**
|
|
197
|
+
* Check if current user owns the specified agent
|
|
198
|
+
* Returns ownership status and original agent info for fork scenario
|
|
199
|
+
*/
|
|
200
|
+
checkOwnership: agentProcedure
|
|
201
|
+
.input(z.object({ identifier: z.string() }))
|
|
202
|
+
.query(async ({ input, ctx }) => {
|
|
203
|
+
log('checkOwnership input: %O', input);
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
// Get agent detail
|
|
207
|
+
const agentDetail = await ctx.marketSDK.agents.getAgentDetail(input.identifier);
|
|
208
|
+
|
|
209
|
+
if (!agentDetail) {
|
|
210
|
+
return {
|
|
211
|
+
exists: false,
|
|
212
|
+
isOwner: false,
|
|
213
|
+
originalAgent: null,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Get Market user info to get accountId
|
|
218
|
+
// Support both trustedClientToken and OIDC accessToken authentication
|
|
219
|
+
const userInfo = ctx.marketUserInfo as TrustedClientUserInfo | undefined;
|
|
220
|
+
const accessToken = (ctx as { marketOidcAccessToken?: string }).marketOidcAccessToken;
|
|
221
|
+
let currentAccountId: number | null = null;
|
|
222
|
+
|
|
223
|
+
const marketUserInfoResult = await fetchMarketUserInfo({ accessToken, userInfo });
|
|
224
|
+
currentAccountId = marketUserInfoResult?.accountId ?? null;
|
|
225
|
+
|
|
226
|
+
const ownerId = agentDetail.ownerId;
|
|
227
|
+
const isOwner = currentAccountId !== null && `${ownerId}` === `${currentAccountId}`;
|
|
228
|
+
|
|
229
|
+
log(
|
|
230
|
+
'checkOwnership result: isOwner=%s, currentAccountId=%s, ownerId=%s',
|
|
231
|
+
isOwner,
|
|
232
|
+
currentAccountId,
|
|
233
|
+
ownerId,
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
exists: true,
|
|
238
|
+
isOwner,
|
|
239
|
+
originalAgent: isOwner
|
|
240
|
+
? null
|
|
241
|
+
: {
|
|
242
|
+
author: agentDetail.author,
|
|
243
|
+
avatar: agentDetail.avatar,
|
|
244
|
+
identifier: agentDetail.identifier,
|
|
245
|
+
name: agentDetail.name,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
} catch (error) {
|
|
249
|
+
log('Error checking ownership: %O', error);
|
|
250
|
+
// If agent not found or error, treat as not existing
|
|
251
|
+
return {
|
|
252
|
+
exists: false,
|
|
253
|
+
isOwner: false,
|
|
254
|
+
originalAgent: null,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}),
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Create a new agent in the marketplace
|
|
261
|
+
* POST /market/agent/create
|
|
262
|
+
*/
|
|
263
|
+
createAgent: agentProcedure.input(createAgentSchema).mutation(async ({ input, ctx }) => {
|
|
264
|
+
log('createAgent input: %O', input);
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const response = await ctx.marketSDK.agents.createAgent(input);
|
|
268
|
+
return response;
|
|
269
|
+
} catch (error) {
|
|
270
|
+
log('Error creating agent: %O', error);
|
|
271
|
+
throw new TRPCError({
|
|
272
|
+
cause: error,
|
|
273
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
274
|
+
message: error instanceof Error ? error.message : 'Failed to create agent',
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}),
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Create a new version for an existing agent
|
|
281
|
+
* POST /market/agent/versions/create
|
|
282
|
+
*/
|
|
283
|
+
createAgentVersion: agentProcedure
|
|
284
|
+
.input(createAgentVersionSchema)
|
|
285
|
+
.mutation(async ({ input, ctx }) => {
|
|
286
|
+
log('createAgentVersion input: %O', input);
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const response = await ctx.marketSDK.agents.createAgentVersion(input);
|
|
290
|
+
return response;
|
|
291
|
+
} catch (error) {
|
|
292
|
+
log('Error creating agent version: %O', error);
|
|
293
|
+
throw new TRPCError({
|
|
294
|
+
cause: error,
|
|
295
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
296
|
+
message: error instanceof Error ? error.message : 'Failed to create agent version',
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}),
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Deprecate an agent (permanently hide, cannot be republished)
|
|
303
|
+
* POST /market/agent/:identifier/deprecate
|
|
304
|
+
*/
|
|
305
|
+
deprecateAgent: agentProcedure
|
|
306
|
+
.input(z.object({ identifier: z.string() }))
|
|
307
|
+
.mutation(async ({ input, ctx }) => {
|
|
308
|
+
log('deprecateAgent input: %O', input);
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const response = await ctx.marketSDK.agents.deprecate(input.identifier);
|
|
312
|
+
return response ?? { success: true };
|
|
313
|
+
} catch (error) {
|
|
314
|
+
log('Error deprecating agent: %O', error);
|
|
315
|
+
throw new TRPCError({
|
|
316
|
+
cause: error,
|
|
317
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
318
|
+
message: error instanceof Error ? error.message : 'Failed to deprecate agent',
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
}),
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get agent detail by identifier
|
|
325
|
+
* GET /market/agent/:identifier
|
|
326
|
+
*/
|
|
327
|
+
getAgentDetail: agentProcedure
|
|
328
|
+
.input(z.object({ identifier: z.string() }))
|
|
329
|
+
.query(async ({ input, ctx }) => {
|
|
330
|
+
log('getAgentDetail input: %O', input);
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const response = await ctx.marketSDK.agents.getAgentDetail(input.identifier);
|
|
334
|
+
return response;
|
|
335
|
+
} catch (error) {
|
|
336
|
+
log('Error getting agent detail: %O', error);
|
|
337
|
+
throw new TRPCError({
|
|
338
|
+
cause: error,
|
|
339
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
340
|
+
message: error instanceof Error ? error.message : 'Failed to get agent detail',
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}),
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Get own agents (requires authentication)
|
|
347
|
+
* GET /market/agent/own
|
|
348
|
+
*/
|
|
349
|
+
getOwnAgents: agentProcedure.input(paginationSchema.optional()).query(async ({ input, ctx }) => {
|
|
350
|
+
log('getOwnAgents input: %O', input);
|
|
351
|
+
|
|
352
|
+
try {
|
|
353
|
+
const response = await ctx.marketSDK.agents.getOwnAgents({
|
|
354
|
+
page: input?.page,
|
|
355
|
+
pageSize: input?.pageSize,
|
|
356
|
+
});
|
|
357
|
+
return response;
|
|
358
|
+
} catch (error) {
|
|
359
|
+
log('Error getting own agents: %O', error);
|
|
360
|
+
throw new TRPCError({
|
|
361
|
+
cause: error,
|
|
362
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
363
|
+
message: error instanceof Error ? error.message : 'Failed to get own agents',
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}),
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Publish an agent (make it visible in marketplace)
|
|
370
|
+
* POST /market/agent/:identifier/publish
|
|
371
|
+
*/
|
|
372
|
+
publishAgent: agentProcedure
|
|
373
|
+
.input(z.object({ identifier: z.string() }))
|
|
374
|
+
.mutation(async ({ input, ctx }) => {
|
|
375
|
+
log('publishAgent input: %O', input);
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
const response = await ctx.marketSDK.agents.publish(input.identifier);
|
|
379
|
+
return response ?? { success: true };
|
|
380
|
+
} catch (error) {
|
|
381
|
+
log('Error publishing agent: %O', error);
|
|
382
|
+
throw new TRPCError({
|
|
383
|
+
cause: error,
|
|
384
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
385
|
+
message: error instanceof Error ? error.message : 'Failed to publish agent',
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}),
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Unified publish or create agent flow
|
|
392
|
+
* This procedure handles the complete publish logic:
|
|
393
|
+
* 1. Check if identifier exists and if current user is owner
|
|
394
|
+
* 2. If not owner or no identifier, create new agent
|
|
395
|
+
* 3. Create new version for the agent
|
|
396
|
+
*
|
|
397
|
+
* Returns: { identifier, isNewAgent, success }
|
|
398
|
+
*/
|
|
399
|
+
publishOrCreate: agentProcedure.input(publishOrCreateSchema).mutation(async ({ input, ctx }) => {
|
|
400
|
+
log('publishOrCreate input: %O', input);
|
|
401
|
+
|
|
402
|
+
const { identifier: inputIdentifier, name, ...versionData } = input;
|
|
403
|
+
let finalIdentifier = inputIdentifier;
|
|
404
|
+
let isNewAgent = false;
|
|
405
|
+
|
|
406
|
+
try {
|
|
407
|
+
// Step 1: Check ownership if identifier is provided
|
|
408
|
+
if (inputIdentifier) {
|
|
409
|
+
try {
|
|
410
|
+
const agentDetail = await ctx.marketSDK.agents.getAgentDetail(inputIdentifier);
|
|
411
|
+
log('Agent detail for ownership check: ownerId=%s', agentDetail?.ownerId);
|
|
412
|
+
|
|
413
|
+
// Get Market user info to get accountId (Market's user ID)
|
|
414
|
+
// Support both trustedClientToken and OIDC accessToken authentication
|
|
415
|
+
const userInfo = ctx.marketUserInfo as TrustedClientUserInfo | undefined;
|
|
416
|
+
const accessToken = (ctx as { marketOidcAccessToken?: string }).marketOidcAccessToken;
|
|
417
|
+
let currentAccountId: number | null = null;
|
|
418
|
+
|
|
419
|
+
const marketUserInfoResult = await fetchMarketUserInfo({ accessToken, userInfo });
|
|
420
|
+
currentAccountId = marketUserInfoResult?.accountId ?? null;
|
|
421
|
+
log('Market user info: accountId=%s', currentAccountId);
|
|
422
|
+
|
|
423
|
+
const ownerId = agentDetail?.ownerId;
|
|
424
|
+
|
|
425
|
+
log('Ownership check: currentAccountId=%s, ownerId=%s', currentAccountId, ownerId);
|
|
426
|
+
|
|
427
|
+
if (!currentAccountId || `${ownerId}` !== `${currentAccountId}`) {
|
|
428
|
+
// Not the owner, need to create a new agent
|
|
429
|
+
log('User is not owner, will create new agent');
|
|
430
|
+
finalIdentifier = undefined;
|
|
431
|
+
isNewAgent = true;
|
|
432
|
+
}
|
|
433
|
+
} catch (detailError) {
|
|
434
|
+
// Agent not found or error, create new
|
|
435
|
+
log('Agent not found or error, will create new: %O', detailError);
|
|
436
|
+
finalIdentifier = undefined;
|
|
437
|
+
isNewAgent = true;
|
|
438
|
+
}
|
|
439
|
+
} else {
|
|
440
|
+
isNewAgent = true;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Step 2: Create new agent if needed
|
|
444
|
+
if (!finalIdentifier) {
|
|
445
|
+
// Generate a unique 8-character identifier
|
|
446
|
+
finalIdentifier = generateMarketIdentifier();
|
|
447
|
+
isNewAgent = true;
|
|
448
|
+
|
|
449
|
+
log('Creating new agent with identifier: %s', finalIdentifier);
|
|
450
|
+
|
|
451
|
+
await ctx.marketSDK.agents.createAgent({
|
|
452
|
+
identifier: finalIdentifier,
|
|
453
|
+
name,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Step 3: Create version for the agent
|
|
458
|
+
log('Creating version for agent: %s', finalIdentifier);
|
|
459
|
+
|
|
460
|
+
await ctx.marketSDK.agents.createAgentVersion({
|
|
461
|
+
...versionData,
|
|
462
|
+
identifier: finalIdentifier,
|
|
463
|
+
name,
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
return {
|
|
467
|
+
identifier: finalIdentifier,
|
|
468
|
+
isNewAgent,
|
|
469
|
+
success: true,
|
|
470
|
+
};
|
|
471
|
+
} catch (error) {
|
|
472
|
+
log('Error in publishOrCreate: %O', error);
|
|
473
|
+
throw new TRPCError({
|
|
474
|
+
cause: error,
|
|
475
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
476
|
+
message: error instanceof Error ? error.message : 'Failed to publish agent',
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}),
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Unpublish an agent (hide from marketplace, can be republished)
|
|
483
|
+
* POST /market/agent/:identifier/unpublish
|
|
484
|
+
*/
|
|
485
|
+
unpublishAgent: agentProcedure
|
|
486
|
+
.input(z.object({ identifier: z.string() }))
|
|
487
|
+
.mutation(async ({ input, ctx }) => {
|
|
488
|
+
log('unpublishAgent input: %O', input);
|
|
489
|
+
|
|
490
|
+
try {
|
|
491
|
+
const response = await ctx.marketSDK.agents.unpublish(input.identifier);
|
|
492
|
+
return response ?? { success: true };
|
|
493
|
+
} catch (error) {
|
|
494
|
+
log('Error unpublishing agent: %O', error);
|
|
495
|
+
throw new TRPCError({
|
|
496
|
+
cause: error,
|
|
497
|
+
code: 'INTERNAL_SERVER_ERROR',
|
|
498
|
+
message: error instanceof Error ? error.message : 'Failed to unpublish agent',
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}),
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
export type AgentRouter = typeof agentRouter;
|
|
@@ -16,6 +16,11 @@ import {
|
|
|
16
16
|
ProviderSorts,
|
|
17
17
|
} from '@/types/discover';
|
|
18
18
|
|
|
19
|
+
import { agentRouter } from './agent';
|
|
20
|
+
import { oidcRouter } from './oidc';
|
|
21
|
+
import { socialRouter } from './social';
|
|
22
|
+
import { userRouter } from './user';
|
|
23
|
+
|
|
19
24
|
const log = debug('lambda-router:market');
|
|
20
25
|
|
|
21
26
|
const marketSourceSchema = z.enum(['legacy', 'new']);
|
|
@@ -36,6 +41,9 @@ const marketProcedure = publicProcedure
|
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
export const marketRouter = router({
|
|
44
|
+
// ============================== Agent Management (authenticated) ==============================
|
|
45
|
+
agent: agentRouter,
|
|
46
|
+
|
|
39
47
|
// ============================== Assistant Market ==============================
|
|
40
48
|
getAssistantCategories: marketProcedure
|
|
41
49
|
.input(
|
|
@@ -516,6 +524,9 @@ export const marketRouter = router({
|
|
|
516
524
|
}
|
|
517
525
|
}),
|
|
518
526
|
|
|
527
|
+
// ============================== OIDC Authentication ==============================
|
|
528
|
+
oidc: oidcRouter,
|
|
529
|
+
|
|
519
530
|
registerClientInMarketplace: marketProcedure.input(z.object({})).mutation(async ({ ctx }) => {
|
|
520
531
|
return ctx.discoverService.registerClient({
|
|
521
532
|
userAgent: ctx.userAgent,
|
|
@@ -671,4 +682,10 @@ export const marketRouter = router({
|
|
|
671
682
|
return { success: false };
|
|
672
683
|
}
|
|
673
684
|
}),
|
|
685
|
+
|
|
686
|
+
// ============================== Social Features ==============================
|
|
687
|
+
social: socialRouter,
|
|
688
|
+
|
|
689
|
+
// ============================== User Profile ==============================
|
|
690
|
+
user: userRouter,
|
|
674
691
|
});
|