UNPKG

@pod-protocol/sdk

Version:

TypeScript SDK for PoD Protocol - AI agent communication on Solana

273 lines (269 loc) 11.1 kB
'use strict'; var types = require('../types-OQd1rGtn.js'); var base = require('../base-4VR-G3Dc.js'); var utils = require('../utils.js'); require('node:events'); require('ws'); /** * Message-related operations service */ class MessageService extends base.BaseService { constructor() { super(...arguments); this.messageStatusToString = (status) => { switch (status) { case types.MessageStatus.PENDING: return "Pending"; case types.MessageStatus.DELIVERED: return "Delivered"; case types.MessageStatus.READ: return "Read"; case types.MessageStatus.FAILED: return "Failed"; default: return "Unknown"; } }; this.statusCounts = { [types.MessageStatus.PENDING]: 0, [types.MessageStatus.DELIVERED]: 0, [types.MessageStatus.READ]: 0, [types.MessageStatus.FAILED]: 0, }; } async sendMessage(wallet, options) { // Derive sender agent PDA const [senderAgentPDA] = await utils.findAgentPDA(wallet.address, this.programId); // Hash the payload const payloadHash = await utils.hashPayload(options.payload); // Convert message type const messageTypeObj = this.convertMessageType(options.messageType, options.customValue); // Generate deterministic message ID based on payload and timestamp const payloadStr = typeof options.payload === 'string' ? options.payload : Buffer.from(options.payload).toString('utf8'); const messageId = await this.generateMessageId(payloadStr, wallet.address.toString()); // Find message PDA with correct parameters const [messagePDA] = await utils.findMessagePDA(wallet.address, options.recipient, messageId, this.programId); return utils.retry(async () => { const methods = this.getProgramMethods(); const tx = await methods .sendMessage(options.recipient, Array.from(payloadHash), messageTypeObj) .accounts({ messageAccount: messagePDA, senderAgent: senderAgentPDA, signer: wallet.address, systemProgram: types.address("11111111111111111111111111111112"), // System Program ID }) .signers([wallet]) .rpc({ commitment: this.commitment }); return tx; }); } async updateMessageStatus(wallet, messagePDA, newStatus) { return utils.retry(async () => { const methods = this.getProgramMethods(); const tx = await methods .updateMessageStatus(this.convertMessageStatus(newStatus)) .accounts({ messageAccount: messagePDA, authority: wallet.address, }) .signers([wallet]) .rpc({ commitment: this.commitment }); return tx; }); } async getMessage(messagePDA) { try { const account = await this.getAccount("messageAccount").fetch(messagePDA); return await this.convertMessageAccountFromProgram(account, messagePDA); } catch (error) { if (error instanceof Error && error.message.includes("Account does not exist")) { return null; } throw error; } } async getAgentMessages(agentAddress, limit = 50, statusFilter) { try { const filters = [ { memcmp: { offset: 8 + 32, bytes: agentAddress, // Address can be used directly }, }, ]; if (statusFilter !== undefined) { const statusBytes = this.convertMessageStatus(statusFilter); filters.push({ memcmp: { offset: 8 + 32 + 32 + 4 + 200, bytes: statusBytes, }, }); } const result = await Promise.resolve({ value: [] }); const messagePromises = result.value.slice(0, limit).map(async (acc) => { const account = this.ensureInitialized().coder.accounts.decode("messageAccount", acc.account.data); return await this.convertMessageAccountFromProgram(account, acc.pubkey); }); return await Promise.all(messagePromises); } catch (error) { throw new Error(`Failed to fetch agent messages: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // ============================================================================ // Helper Methods // ============================================================================ convertMessageType(messageType, customValue) { return utils.convertMessageTypeToProgram(messageType); } convertMessageTypeFromProgram(programType) { const result = utils.convertMessageTypeFromProgram(programType); return result.type; } convertMessageStatus(status) { switch (status) { case types.MessageStatus.PENDING: return { pending: {} }; case types.MessageStatus.DELIVERED: return { delivered: {} }; case types.MessageStatus.READ: return { read: {} }; case types.MessageStatus.FAILED: return { failed: {} }; default: throw new Error(`Unknown message status: ${status}`); } } convertMessageStatusFromProgram(programStatus) { if (programStatus.pending) return types.MessageStatus.PENDING; if (programStatus.delivered) return types.MessageStatus.DELIVERED; if (programStatus.read) return types.MessageStatus.READ; if (programStatus.failed) return types.MessageStatus.FAILED; return types.MessageStatus.PENDING; } async convertMessageAccountFromProgram(account, publicKey) { const payload = (account.payload || account.content || ""); // Compute REAL payloadHash from actual payload content - NO MORE MOCKS! let computedPayloadHash; if (account.payloadHash) { // Use existing hash if available computedPayloadHash = account.payloadHash; } else { // Compute real hash from payload content instead of mock computedPayloadHash = await utils.hashPayload(payload); } return { pubkey: publicKey, sender: account.sender, recipient: account.recipient, payload, payloadHash: computedPayloadHash, messageType: this.convertMessageTypeFromProgram(account.messageType), status: this.convertMessageStatusFromProgram(account.status), timestamp: utils.getAccountTimestamp(account), createdAt: utils.getAccountCreatedAt(account), expiresAt: account.expiresAt?.toNumber() || 0, bump: account.bump, }; } /** * Generate deterministic message ID based on payload and sender */ async generateMessageId(payload, sender) { const timestamp = Date.now(); const data = `${payload}_${sender}_${timestamp}`; const payloadHash = await utils.hashPayload(data); const hashStr = Array.from(payloadHash).map(b => b.toString(16).padStart(2, '0')).join(''); return `msg_${hashStr.slice(0, 12)}_${timestamp.toString(36)}`; } // ============================================================================ // MCP Server Compatibility Methods // ============================================================================ /** * Send method for MCP server compatibility */ async send(options) { // Real implementation using sendMessage with proper wallet if (!this.wallet) { throw new Error('Wallet not configured for message service'); } const messageType = options.messageType ? this.parseMessageType(options.messageType) : types.MessageType.TEXT; const recipient = types.address(options.recipient); const signature = await this.sendMessage(this.wallet, { recipient, payload: options.content, messageType }); // Generate message ID from signature const messageId = `mcp_${signature.slice(0, 16)}`; return { messageId, signature }; } /** * Get filtered messages for MCP server compatibility */ async getFiltered(options) { // Real implementation using getAgentMessages if (!this.wallet) { throw new Error('Wallet not configured for message service'); } const statusFilter = options.status ? this.parseMessageStatus(options.status) : undefined; const messages = await this.getAgentMessages(this.wallet.address, options.limit || 50, statusFilter); // Apply offset if specified const offset = options.offset || 0; const offsetMessages = messages.slice(offset); return { messages: offsetMessages, totalCount: messages.length, hasMore: messages.length >= (options.limit || 50) }; } /** * Mark message as read for MCP server compatibility */ async markAsRead(messageId) { // Real implementation using updateMessageStatus if (!this.wallet) { throw new Error('Wallet not configured for message service'); } // Extract address from message ID or use provided address const messagePDA = types.address(messageId); const signature = await this.updateMessageStatus(this.wallet, messagePDA, types.MessageStatus.READ); return { signature }; } // Helper methods for MCP compatibility parseMessageType(typeStr) { switch (typeStr.toLowerCase()) { case 'text': return types.MessageType.TEXT; case 'image': return types.MessageType.IMAGE; case 'code': return types.MessageType.CODE; case 'file': return types.MessageType.FILE; default: return types.MessageType.TEXT; } } parseMessageStatus(statusStr) { switch (statusStr.toLowerCase()) { case 'pending': return types.MessageStatus.PENDING; case 'delivered': return types.MessageStatus.DELIVERED; case 'read': return types.MessageStatus.READ; case 'failed': return types.MessageStatus.FAILED; default: return types.MessageStatus.PENDING; } } setWallet(wallet) { this.wallet = wallet; } } exports.MessageService = MessageService; //# sourceMappingURL=message.js.map