feat(phase-4): WS4 — Agent Marketplace (public registry, pagination, filters)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -50,6 +50,7 @@ interface AgentWithDIDKeyRow {
|
||||
key_type: string | null;
|
||||
curve: string | null;
|
||||
key_created_at: Date | null;
|
||||
is_public: boolean | null;
|
||||
}
|
||||
|
||||
/** Result of the internal agent+key lookup. */
|
||||
@@ -455,6 +456,7 @@ export class DIDService {
|
||||
updatedAt: row.updated_at,
|
||||
did: row.did ?? undefined,
|
||||
didCreatedAt: row.did_created_at ?? undefined,
|
||||
isPublic: row.is_public ?? false,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
106
src/services/MarketplaceService.ts
Normal file
106
src/services/MarketplaceService.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* Marketplace Service for SentryAgent.ai AgentIdP.
|
||||
* Business logic for the public Agent Marketplace.
|
||||
* Only exposes agents where is_public = true AND status = active.
|
||||
*/
|
||||
|
||||
import { AgentRepository } from '../repositories/AgentRepository.js';
|
||||
import { AgentNotFoundError } from '../utils/errors.js';
|
||||
import {
|
||||
IMarketplaceFilters,
|
||||
IMarketplaceAgentCard,
|
||||
IPaginatedMarketplaceResponse,
|
||||
IMinimalDIDDocument,
|
||||
IAgent,
|
||||
} from '../types/index.js';
|
||||
|
||||
/**
|
||||
* Builds a minimal W3C DID Document from agent data when no key material is present.
|
||||
* Used as a fallback when the agent has a DID assigned but no key record exists yet,
|
||||
* or when constructing the document purely from agent identity data.
|
||||
*
|
||||
* @param agent - The agent record.
|
||||
* @returns A minimal DID document, or null if the agent has no DID.
|
||||
*/
|
||||
function buildMinimalDIDDocument(agent: IAgent): IMinimalDIDDocument | null {
|
||||
if (!agent.did) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/did/v1',
|
||||
'https://w3id.org/security/suites/jws-2020/v1',
|
||||
],
|
||||
id: agent.did,
|
||||
controller: agent.did,
|
||||
verificationMethod: [],
|
||||
authentication: [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps an IAgent record to the public IMarketplaceAgentCard shape.
|
||||
* Strips all private fields (email, organizationId, internal status details).
|
||||
*
|
||||
* @param agent - The internal agent record.
|
||||
* @returns The public-facing marketplace agent card.
|
||||
*/
|
||||
function mapAgentToCard(agent: IAgent): IMarketplaceAgentCard {
|
||||
return {
|
||||
agentId: agent.agentId,
|
||||
agentType: agent.agentType,
|
||||
version: agent.version,
|
||||
capabilities: agent.capabilities,
|
||||
owner: agent.owner,
|
||||
deploymentEnv: agent.deploymentEnv,
|
||||
did: agent.did ?? null,
|
||||
didDocument: buildMinimalDIDDocument(agent),
|
||||
publishedAt: agent.updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Service for the public Agent Marketplace.
|
||||
* All methods enforce the is_public = true constraint via the repository.
|
||||
*/
|
||||
export class MarketplaceService {
|
||||
/**
|
||||
* @param agentRepository - The agent data repository.
|
||||
*/
|
||||
constructor(private readonly agentRepository: AgentRepository) {}
|
||||
|
||||
/**
|
||||
* Returns a paginated, optionally filtered list of public agents.
|
||||
* Supports free-text search (?q=), capability filter (?capability=),
|
||||
* and publisher filter (?publisher=).
|
||||
*
|
||||
* @param filters - Marketplace filter and pagination criteria.
|
||||
* @returns Paginated public agent card listing.
|
||||
*/
|
||||
async listPublicAgents(filters: IMarketplaceFilters): Promise<IPaginatedMarketplaceResponse> {
|
||||
const { agents, total } = await this.agentRepository.findPublicAgents(filters);
|
||||
return {
|
||||
data: agents.map(mapAgentToCard),
|
||||
total,
|
||||
page: filters.page,
|
||||
limit: filters.limit,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a single public agent with its DID document and agent card.
|
||||
* Only succeeds when the agent is both public (is_public = true) and active.
|
||||
*
|
||||
* @param agentId - The agent UUID to retrieve.
|
||||
* @returns The public marketplace agent card.
|
||||
* @throws AgentNotFoundError if the agent does not exist, is private, or is not active.
|
||||
*/
|
||||
async getPublicAgent(agentId: string): Promise<IMarketplaceAgentCard> {
|
||||
const agent = await this.agentRepository.findPublicById(agentId);
|
||||
if (!agent) {
|
||||
throw new AgentNotFoundError(agentId);
|
||||
}
|
||||
return mapAgentToCard(agent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user