Download src/controllers/agentController.ts from nexusbert/zurri: direct link, hf CLI and curl.
- Browser
- Download file 13.2 kB
-
https://huggingface.co/spaces/nexusbert/zurri/resolve/main/src/controllers/agentController.ts
- Command line
-
hf download hf://spaces/nexusbert/zurri/src/controllers/agentController.ts
-
curl -L -o agentController.ts https://huggingface.co/spaces/nexusbert/zurri/resolve/main/src/controllers/agentController.ts
13.2 kB
| import { Request, Response } from 'express'; | |
| import { AppDataSource } from '../config/database'; | |
| import { Agent, AgentStatus } from '../entities/Agent'; | |
| import { User } from '../entities/User'; | |
| import { IpfsService } from '../services/ipfsService'; | |
| const agentRepository = AppDataSource.getRepository(Agent); | |
| const ipfsService = new IpfsService(); | |
| interface MulterFile { | |
| fieldname: string; | |
| originalname: string; | |
| encoding: string; | |
| mimetype: string; | |
| size: number; | |
| buffer: Buffer; | |
| } | |
| export class AgentController { | |
| /** | |
| * List all approved agents (marketplace) | |
| */ | |
| async listAgents(req: Request, res: Response) { | |
| try { | |
| const { page = 1, limit = 20, search, sortBy = 'createdAt' } = req.query; | |
| const query = agentRepository | |
| .createQueryBuilder('agent') | |
| .where('agent.status = :status', { status: AgentStatus.APPROVED }); | |
| if (search) { | |
| query.andWhere( | |
| '(agent.name ILIKE :search OR agent.description ILIKE :search OR agent.category ILIKE :search)', | |
| { search: `%${search}%` } | |
| ); | |
| } | |
| // Filter by category if provided | |
| if (req.query.category) { | |
| query.andWhere('agent.category = :category', { category: req.query.category }); | |
| } | |
| const sortOrder = req.query.sortOrder === 'asc' ? 'ASC' : 'DESC'; | |
| query.orderBy(`agent.${sortBy}`, sortOrder); | |
| const skip = (Number(page) - 1) * Number(limit); | |
| query.skip(skip).take(Number(limit)); | |
| const [agents, total] = await query.getManyAndCount(); | |
| // Format agents to match expected structure | |
| const formattedAgents = agents.map(agent => ({ | |
| ...agent, | |
| avatar: agent.avatar, | |
| reputation: agent.reputation || 0, | |
| capabilities: agent.capabilities || [], | |
| // Hide endpoint from public listings | |
| endpoint: undefined, | |
| })); | |
| res.json({ | |
| agents: formattedAgents, | |
| pagination: { | |
| page: Number(page), | |
| limit: Number(limit), | |
| total, | |
| totalPages: Math.ceil(total / Number(limit)), | |
| }, | |
| }); | |
| } catch (error) { | |
| console.error('List agents error:', error); | |
| res.status(500).json({ error: 'Failed to list agents' }); | |
| } | |
| } | |
| /** | |
| * Get single agent by ID | |
| */ | |
| async getAgent(req: Request, res: Response) { | |
| try { | |
| const { id } = req.params; | |
| const agent = await agentRepository.findOne({ | |
| where: { id }, | |
| }); | |
| if (!agent) { | |
| return res.status(404).json({ error: 'Agent not found' }); | |
| } | |
| // Only show endpoint to agent creator or admins | |
| const userId = (req as any).user?.id; | |
| if (agent.creatorId !== userId && !(req as any).user?.isAdmin) { | |
| // Create a copy without the endpoint for security | |
| const { endpoint, ...agentWithoutEndpoint } = agent; | |
| res.json({ | |
| ...agentWithoutEndpoint, | |
| // Format response to match expected structure | |
| avatar: agentWithoutEndpoint.avatar, | |
| reputation: agent.reputation || 0, | |
| capabilities: agent.capabilities || [], | |
| }); | |
| return; | |
| } | |
| // Format response to match expected structure | |
| res.json({ | |
| ...agent, | |
| reputation: agent.reputation || 0, | |
| capabilities: agent.capabilities || [], | |
| }); | |
| } catch (error) { | |
| console.error('Get agent error:', error); | |
| res.status(500).json({ error: 'Failed to get agent' }); | |
| } | |
| } | |
| /** | |
| * Create new agent listing (creator) | |
| */ | |
| async createAgent(req: Request, res: Response) { | |
| try { | |
| const userId = (req as any).user.id; | |
| const isAdmin = (req as any).user.isAdmin; | |
| // Check if user is a creator or admin | |
| const userRepository = AppDataSource.getRepository(User); | |
| const user = await userRepository.findOne({ where: { id: userId } }); | |
| if (!user) { | |
| return res.status(404).json({ error: 'User not found' }); | |
| } | |
| if (!user.isCreator && !isAdmin) { | |
| return res.status(403).json({ | |
| error: 'You must be a creator to create agents', | |
| message: 'Please become a creator first by calling POST /api/users/become-creator', | |
| becomeCreatorEndpoint: '/api/users/become-creator', | |
| }); | |
| } | |
| const avatarFile = (req as any).file as MulterFile | undefined; | |
| const { | |
| name, | |
| description, | |
| endpoint, | |
| category, | |
| capabilities, | |
| price, | |
| pointsPerTask, // Required: Points charged per task (1 point = $0.05) | |
| isSubscription = false, // Default to pay-per-task | |
| subscriptionDuration, | |
| promptTemplate, | |
| metadata, | |
| } = req.body; | |
| // Parse capabilities if it's a string | |
| let parsedCapabilities: string[] = []; | |
| if (capabilities) { | |
| if (typeof capabilities === 'string') { | |
| try { | |
| parsedCapabilities = JSON.parse(capabilities); | |
| } catch { | |
| parsedCapabilities = [capabilities]; | |
| } | |
| } else if (Array.isArray(capabilities)) { | |
| parsedCapabilities = capabilities; | |
| } | |
| } | |
| // Validate required fields | |
| if (!name || !description || !endpoint || pointsPerTask === undefined) { | |
| return res.status(400).json({ | |
| error: 'Missing required fields. Name, description, endpoint, and pointsPerTask are required.', | |
| note: 'pointsPerTask is the number of points charged per task (1 point = $0.05)' | |
| }); | |
| } | |
| // Validate pointsPerTask | |
| if (pointsPerTask < 0) { | |
| return res.status(400).json({ error: 'pointsPerTask must be 0 or greater (0 = free agent)' }); | |
| } | |
| // Validate capabilities is an array | |
| if (!Array.isArray(parsedCapabilities)) { | |
| return res.status(400).json({ error: 'Capabilities must be an array' }); | |
| } | |
| // Upload avatar to IPFS if provided | |
| let avatarUrl: string | undefined; | |
| if (avatarFile) { | |
| try { | |
| const fileObj = new File( | |
| [avatarFile.buffer], | |
| avatarFile.originalname || `avatar-${Date.now()}.${avatarFile.mimetype.split('/')[1]}`, | |
| { type: avatarFile.mimetype } | |
| ); | |
| const upload = await ipfsService.uploadFile(fileObj); | |
| avatarUrl = ipfsService.getGatewayUrl(upload.cid); | |
| } catch (error) { | |
| console.error('Avatar upload error:', error); | |
| return res.status(500).json({ error: 'Failed to upload avatar image' }); | |
| } | |
| } | |
| // Upload metadata to IPFS | |
| let ipfsHash: string | undefined; | |
| try { | |
| ipfsHash = await ipfsService.uploadMetadata({ | |
| name, | |
| description, | |
| endpoint, | |
| price, | |
| creatorId: userId, | |
| category, | |
| capabilities: parsedCapabilities, | |
| metadata, | |
| }); | |
| } catch (error) { | |
| console.warn('IPFS upload failed, continuing without IPFS hash'); | |
| } | |
| // Use price as fallback for pointsPerTask if not provided (backward compatibility) | |
| const finalPointsPerTask = pointsPerTask !== undefined ? pointsPerTask : (price ? price * 20 : 0); // 1 dollar = 20 points | |
| const agent = agentRepository.create({ | |
| name, | |
| avatar: avatarUrl, | |
| description, | |
| endpoint, | |
| category, | |
| capabilities: parsedCapabilities, | |
| price: price || pointsPerTask * 0.05, // Convert points to dollars for legacy field | |
| pointsPerTask: finalPointsPerTask, | |
| isSubscription: false, // All agents now use pay-per-task model | |
| subscriptionDuration: undefined, // Not used in pay-per-task model | |
| promptTemplate, | |
| metadata: metadata ? (typeof metadata === 'string' ? JSON.parse(metadata) : metadata) : undefined, | |
| creatorId: userId, | |
| ipfsHash, | |
| status: AgentStatus.PENDING, | |
| reputation: 0, // Start at 0, will be updated based on ratings | |
| ratingCount: 0, | |
| }); | |
| const savedAgent = await agentRepository.save(agent); | |
| res.status(201).json(savedAgent); | |
| } catch (error: any) { | |
| console.error('Create agent error:', error); | |
| res.status(500).json({ error: error.message || 'Failed to create agent' }); | |
| } | |
| } | |
| /** | |
| * Update agent (creator only) | |
| */ | |
| async updateAgent(req: Request, res: Response) { | |
| try { | |
| const { id } = req.params; | |
| const userId = (req as any).user.id; | |
| const isAdmin = (req as any).user.isAdmin; | |
| const agent = await agentRepository.findOne({ | |
| where: { id }, | |
| }); | |
| if (!agent) { | |
| return res.status(404).json({ error: 'Agent not found' }); | |
| } | |
| // Only creator or admin can update | |
| if (agent.creatorId !== userId && !isAdmin) { | |
| return res.status(403).json({ error: 'Unauthorized' }); | |
| } | |
| // Can't update if approved (requires re-submission) | |
| if (agent.status === AgentStatus.APPROVED && agent.creatorId !== userId) { | |
| return res.status(400).json({ | |
| error: 'Approved agents cannot be updated. Create a new listing.', | |
| }); | |
| } | |
| const avatarFile = (req as any).file as MulterFile | undefined; | |
| const updateData = req.body; | |
| delete updateData.status; // Don't allow status updates via this endpoint | |
| delete updateData.creatorId; // Don't allow creator change | |
| delete updateData.reputation; // Reputation is calculated from ratings | |
| delete updateData.ratingCount; // Rating count is managed by system | |
| delete updateData.avatar; // Avatar comes from file upload, not body | |
| // Parse capabilities if it's a string | |
| if (updateData.capabilities) { | |
| if (typeof updateData.capabilities === 'string') { | |
| try { | |
| updateData.capabilities = JSON.parse(updateData.capabilities); | |
| } catch { | |
| updateData.capabilities = [updateData.capabilities]; | |
| } | |
| } | |
| } | |
| // Validate capabilities if provided | |
| if (updateData.capabilities && !Array.isArray(updateData.capabilities)) { | |
| return res.status(400).json({ error: 'Capabilities must be an array' }); | |
| } | |
| // Upload new avatar if provided | |
| if (avatarFile) { | |
| try { | |
| const fileObj = new File( | |
| [avatarFile.buffer], | |
| avatarFile.originalname || `avatar-${Date.now()}.${avatarFile.mimetype.split('/')[1]}`, | |
| { type: avatarFile.mimetype } | |
| ); | |
| const upload = await ipfsService.uploadFile(fileObj); | |
| updateData.avatar = ipfsService.getGatewayUrl(upload.cid); | |
| } catch (error) { | |
| console.error('Avatar upload error:', error); | |
| return res.status(500).json({ error: 'Failed to upload avatar image' }); | |
| } | |
| } | |
| Object.assign(agent, updateData); | |
| // Parse metadata if it's a string | |
| if (updateData.metadata && typeof updateData.metadata === 'string') { | |
| try { | |
| updateData.metadata = JSON.parse(updateData.metadata); | |
| } catch { | |
| // Keep as string if not valid JSON | |
| } | |
| } | |
| // Update IPFS if metadata changed | |
| if (updateData.name || updateData.description || updateData.price || updateData.category || updateData.capabilities) { | |
| try { | |
| agent.ipfsHash = await ipfsService.uploadMetadata({ | |
| name: agent.name, | |
| description: agent.description, | |
| endpoint: agent.endpoint, | |
| price: agent.price, | |
| creatorId: agent.creatorId, | |
| category: agent.category, | |
| capabilities: agent.capabilities, | |
| metadata: agent.metadata, | |
| }); | |
| } catch (error) { | |
| console.warn('IPFS update failed'); | |
| } | |
| } | |
| const updatedAgent = await agentRepository.save(agent); | |
| res.json(updatedAgent); | |
| } catch (error) { | |
| console.error('Update agent error:', error); | |
| res.status(500).json({ error: 'Failed to update agent' }); | |
| } | |
| } | |
| /** | |
| * Delete agent (creator only) | |
| */ | |
| async deleteAgent(req: Request, res: Response) { | |
| try { | |
| const { id } = req.params; | |
| const userId = (req as any).user.id; | |
| const isAdmin = (req as any).user.isAdmin; | |
| const agent = await agentRepository.findOne({ | |
| where: { id }, | |
| }); | |
| if (!agent) { | |
| return res.status(404).json({ error: 'Agent not found' }); | |
| } | |
| if (agent.creatorId !== userId && !isAdmin) { | |
| return res.status(403).json({ error: 'Unauthorized' }); | |
| } | |
| await agentRepository.remove(agent); | |
| res.json({ message: 'Agent deleted' }); | |
| } catch (error) { | |
| console.error('Delete agent error:', error); | |
| res.status(500).json({ error: 'Failed to delete agent' }); | |
| } | |
| } | |
| /** | |
| * Get user's agents (creator) | |
| */ | |
| async getMyAgents(req: Request, res: Response) { | |
| try { | |
| const userId = (req as any).user.id; | |
| const agents = await agentRepository.find({ | |
| where: { creatorId: userId }, | |
| order: { createdAt: 'DESC' }, | |
| }); | |
| res.json(agents); | |
| } catch (error) { | |
| console.error('Get my agents error:', error); | |
| res.status(500).json({ error: 'Failed to get your agents' }); | |
| } | |
| } | |
| } | |