@pod-protocol/sdk-js
Version:
Pod Protocol SDK for JavaScript
474 lines (420 loc) • 15.9 kB
JavaScript
;
var base = require('../base-B0PMFl1k.js');
var types = require('../types.js');
/**
* Discovery service for PoD Protocol SDK
*/
/**
* Service for search and discovery in the PoD Protocol
*
* @class DiscoveryService
* @extends BaseService
*/
class DiscoveryService extends base.BaseService {
/**
* Search for agents with filters
*
* @param {Object} filters - Search filters
* @param {number[]} [filters.capabilities] - Required capabilities
* @param {number} [filters.minReputation] - Minimum reputation score
* @param {string} [filters.query] - Search query for metadata
* @param {number} [filters.limit=50] - Maximum results
* @param {number} [filters.offset=0] - Result offset
* @returns {Promise<Object>} Search results with agents and metadata
*
* @example
* ```javascript
* const results = await client.discovery.searchAgents({
* capabilities: [AGENT_CAPABILITIES.TRADING, AGENT_CAPABILITIES.ANALYSIS],
* minReputation: 50,
* query: 'financial',
* limit: 20
* });
* ```
*/
async searchAgents(filters = {}) {
if (!this.isInitialized()) {
throw new Error('Service not initialized. Call client.initialize() first.');
}
try {
const accounts = await this.program.account.agentAccount.all();
let agents = accounts.map(account => ({
pubkey: account.publicKey,
capabilities: account.account.capabilities.toNumber(),
metadataUri: account.account.metadataUri,
reputation: account.account.reputation?.toNumber() || 0,
lastUpdated: account.account.lastUpdated?.toNumber() || Date.now()
}));
// Apply capability filters
if (filters.capabilities && filters.capabilities.length > 0) {
agents = agents.filter(agent => {
return filters.capabilities.every(capability =>
(agent.capabilities & capability) === capability
);
});
}
// Apply reputation filter
if (filters.minReputation !== undefined) {
agents = agents.filter(agent => agent.reputation >= filters.minReputation);
}
// Apply text search (simple metadata URI search)
if (filters.query) {
const query = filters.query.toLowerCase();
agents = agents.filter(agent =>
agent.metadataUri.toLowerCase().includes(query)
);
}
// Sort by reputation (highest first)
agents.sort((a, b) => b.reputation - a.reputation);
// Apply pagination
const offset = filters.offset || 0;
const limit = filters.limit || 50;
const totalCount = agents.length;
const paginatedAgents = agents.slice(offset, offset + limit);
return {
items: paginatedAgents,
totalCount,
hasMore: offset + limit < totalCount,
nextOffset: offset + limit < totalCount ? offset + limit : null
};
} catch (error) {
throw new Error(`Failed to search agents: ${error.message}`);
}
}
/**
* Search for channels with filters
*
* @param {Object} filters - Search filters
* @param {ChannelVisibility} [filters.visibility] - Channel visibility
* @param {number} [filters.minParticipants] - Minimum participants
* @param {number} [filters.maxFee] - Maximum fee per message
* @param {string} [filters.query] - Search query for name/description
* @param {number} [filters.limit=50] - Maximum results
* @param {number} [filters.offset=0] - Result offset
* @returns {Promise<Object>} Search results with channels
*
* @example
* ```javascript
* const results = await client.discovery.searchChannels({
* visibility: ChannelVisibility.PUBLIC,
* minParticipants: 5,
* query: 'trading',
* limit: 20
* });
* ```
*/
async searchChannels(filters = {}) {
if (!this.isInitialized()) {
throw new Error('Service not initialized. Call client.initialize() first.');
}
try {
const accounts = await this.program.account.channelAccount.all();
let channels = accounts.map(account => ({
pubkey: account.publicKey,
creator: account.account.creator,
name: account.account.name,
description: account.account.description,
visibility: this._convertChannelVisibilityFromProgram(account.account.visibility),
participantCount: account.account.currentParticipants || 0,
feePerMessage: account.account.feePerMessage?.toNumber() || 0,
createdAt: account.account.createdAt?.toNumber() || Date.now()
}));
// Apply visibility filter
if (filters.visibility) {
channels = channels.filter(channel => channel.visibility === filters.visibility);
}
// Apply participant count filter
if (filters.minParticipants !== undefined) {
channels = channels.filter(channel => channel.participantCount >= filters.minParticipants);
}
// Apply fee filter
if (filters.maxFee !== undefined) {
channels = channels.filter(channel => channel.feePerMessage <= filters.maxFee);
}
// Apply text search
if (filters.query) {
const query = filters.query.toLowerCase();
channels = channels.filter(channel =>
channel.name.toLowerCase().includes(query) ||
channel.description.toLowerCase().includes(query)
);
}
// Sort by participant count (highest first)
channels.sort((a, b) => b.participantCount - a.participantCount);
// Apply pagination
const offset = filters.offset || 0;
const limit = filters.limit || 50;
const totalCount = channels.length;
const paginatedChannels = channels.slice(offset, offset + limit);
return {
items: paginatedChannels,
totalCount,
hasMore: offset + limit < totalCount,
nextOffset: offset + limit < totalCount ? offset + limit : null
};
} catch (error) {
throw new Error(`Failed to search channels: ${error.message}`);
}
}
/**
* Search for messages with filters
*
* @param {Object} filters - Search filters
* @param {MessageType} [filters.messageType] - Type of message
* @param {string} [filters.status] - Message status
* @param {Object} [filters.dateRange] - Date range filter
* @param {number} [filters.dateRange.start] - Start timestamp
* @param {number} [filters.dateRange.end] - End timestamp
* @param {string} [filters.query] - Search query for content
* @param {number} [filters.limit=50] - Maximum results
* @param {number} [filters.offset=0] - Result offset
* @returns {Promise<Object>} Search results with messages
*
* @example
* ```javascript
* const results = await client.discovery.searchMessages({
* messageType: MessageType.DATA,
* query: 'trading signals',
* dateRange: {
* start: Date.now() - 7 * 24 * 60 * 60 * 1000, // 7 days ago
* end: Date.now()
* },
* limit: 100
* });
* ```
*/
async searchMessages(filters = {}) {
if (!this.isInitialized()) {
throw new Error('Service not initialized. Call client.initialize() first.');
}
try {
const accounts = await this.program.account.messageAccount.all();
let messages = accounts.map(account => ({
pubkey: account.publicKey,
sender: account.account.sender,
recipient: account.account.recipient,
payload: account.account.payload || '',
messageType: account.account.messageType,
status: account.account.status,
timestamp: account.account.timestamp?.toNumber() || Date.now()
}));
// Apply message type filter
if (filters.messageType) {
messages = messages.filter(message => message.messageType === filters.messageType);
}
// Apply status filter
if (filters.status) {
messages = messages.filter(message => message.status === filters.status);
}
// Apply date range filter
if (filters.dateRange) {
const { start, end } = filters.dateRange;
messages = messages.filter(message => {
const messageTime = message.timestamp * 1000;
return (!start || messageTime >= start) && (!end || messageTime <= end);
});
}
// Apply content search
if (filters.query) {
const query = filters.query.toLowerCase();
messages = messages.filter(message =>
message.payload.toLowerCase().includes(query)
);
}
// Sort by timestamp (newest first)
messages.sort((a, b) => b.timestamp - a.timestamp);
// Apply pagination
const offset = filters.offset || 0;
const limit = filters.limit || 50;
const totalCount = messages.length;
const paginatedMessages = messages.slice(offset, offset + limit);
return {
items: paginatedMessages,
totalCount,
hasMore: offset + limit < totalCount,
nextOffset: offset + limit < totalCount ? offset + limit : null
};
} catch (error) {
throw new Error(`Failed to search messages: ${error.message}`);
}
}
/**
* Get recommendations for agents, channels, or content
*
* @param {Object} options - Recommendation options
* @param {string} options.type - Type: 'agents', 'channels', or 'messages'
* @param {string} [options.basedOn] - Base recommendations on this entity
* @param {number} [options.limit=10] - Maximum recommendations
* @param {string} [options.algorithm='collaborative_filtering'] - Algorithm to use
* @returns {Promise<Array>} Array of recommendations
*
* @example
* ```javascript
* const recommendations = await client.discovery.getRecommendations({
* type: 'agents',
* basedOn: myAgentAddress,
* limit: 10
* });
* ```
*/
async getRecommendations(options) {
if (!this.isInitialized()) {
throw new Error('Service not initialized. Call client.initialize() first.');
}
try {
switch (options.type) {
case 'agents':
return this._getAgentRecommendations(options);
case 'channels':
return this._getChannelRecommendations(options);
case 'messages':
return this._getMessageRecommendations(options);
default:
throw new Error(`Unknown recommendation type: ${options.type}`);
}
} catch (error) {
throw new Error(`Failed to get recommendations: ${error.message}`);
}
}
/**
* Get trending content
*
* @param {Object} [options] - Options
* @param {string} [options.timeframe='24h'] - Timeframe: '1h', '24h', '7d', '30d'
* @param {number} [options.limit=20] - Maximum results
* @returns {Promise<Object>} Trending content
*
* @example
* ```javascript
* const trending = await client.discovery.getTrending({
* timeframe: '24h',
* limit: 10
* });
* ```
*/
async getTrending(options = {}) {
const timeframe = options.timeframe || '24h';
const limit = options.limit || 20;
// Calculate timeframe in milliseconds
const timeframes = {
'1h': 60 * 60 * 1000,
'24h': 24 * 60 * 60 * 1000,
'7d': 7 * 24 * 60 * 60 * 1000,
'30d': 30 * 24 * 60 * 60 * 1000
};
const timeframeMs = timeframes[timeframe] || timeframes['24h'];
const since = Date.now() - timeframeMs;
try {
// Get trending agents (by recent activity)
const trendingAgents = await this.searchAgents({
limit: limit
});
// Get trending channels (by recent participant growth)
const trendingChannels = await this.searchChannels({
limit: limit
});
// Get trending messages (by recent activity)
const trendingMessages = await this.searchMessages({
dateRange: { start: since, end: Date.now() },
limit: limit
});
return {
agents: trendingAgents.items.slice(0, limit),
channels: trendingChannels.items.slice(0, limit),
messages: trendingMessages.items.slice(0, limit),
timeframe,
generatedAt: Date.now()
};
} catch (error) {
throw new Error(`Failed to get trending content: ${error.message}`);
}
}
// Private helper methods
async _getAgentRecommendations(options) {
// Simple recommendation based on similar capabilities
const allAgents = await this.searchAgents({ limit: 1000 });
if (!options.basedOn) {
// Return top agents by reputation
return allAgents.items
.sort((a, b) => b.reputation - a.reputation)
.slice(0, options.limit || 10)
.map(agent => ({
itemId: agent.pubkey,
score: agent.reputation / 100,
reason: 'High reputation agent',
metadata: { type: 'agent', capabilities: agent.capabilities }
}));
}
// Get base agent
const baseAgent = allAgents.items.find(agent => agent.pubkey.equals(options.basedOn));
if (!baseAgent) {
return [];
}
// Find agents with similar capabilities
return allAgents.items
.filter(agent => !agent.pubkey.equals(options.basedOn))
.map(agent => {
const similarityScore = this._calculateCapabilitySimilarity(
baseAgent.capabilities,
agent.capabilities
);
return {
itemId: agent.pubkey,
score: similarityScore,
reason: 'Similar capabilities',
metadata: { type: 'agent', capabilities: agent.capabilities }
};
})
.sort((a, b) => b.score - a.score)
.slice(0, options.limit || 10);
}
async _getChannelRecommendations(options) {
const allChannels = await this.searchChannels({ limit: 1000 });
// Return most popular channels
return allChannels.items
.sort((a, b) => b.participantCount - a.participantCount)
.slice(0, options.limit || 10)
.map(channel => ({
itemId: channel.pubkey,
score: channel.participantCount / 100,
reason: 'Popular channel',
metadata: { type: 'channel', participantCount: channel.participantCount }
}));
}
async _getMessageRecommendations(options) {
const recentMessages = await this.searchMessages({
dateRange: { start: Date.now() - 24 * 60 * 60 * 1000, end: Date.now() },
limit: options.limit || 10
});
return recentMessages.items.map(message => ({
itemId: message.pubkey,
score: 1.0,
reason: 'Recent message',
metadata: { type: 'message', messageType: message.messageType }
}));
}
_calculateCapabilitySimilarity(capabilities1, capabilities2) {
// Calculate Jaccard similarity for capabilities
const common = capabilities1 & capabilities2;
const total = capabilities1 | capabilities2;
if (total === 0) return 0;
const commonCount = this._countBits(common);
const totalCount = this._countBits(total);
return commonCount / totalCount;
}
_countBits(n) {
let count = 0;
while (n) {
count += n & 1;
n >>= 1;
}
return count;
}
_convertChannelVisibilityFromProgram(programVisibility) {
if (programVisibility.public !== undefined) return types.ChannelVisibility.PUBLIC;
if (programVisibility.private !== undefined) return types.ChannelVisibility.PRIVATE;
return types.ChannelVisibility.PUBLIC;
}
}
exports.DiscoveryService = DiscoveryService;
//# sourceMappingURL=discovery.js.map