UNPKG

semnet-snap-protocol

Version:

TypeScript reference implementation of the SNAP Protocol v1.1 - Agent Internet Edition

323 lines 8.24 kB
import { SNAPMessageSchema } from './types.js'; import { generateMessageId, canonicalizeObject } from './identity.js'; /** * Message builder for creating SNAP messages */ export class MessageBuilder { message; constructor(from) { this.message = { id: generateMessageId(), version: '1.0', from, timestamp: new Date().toISOString(), parts: [] }; } /** * Set the recipient agent */ to(recipient) { this.message.to = recipient; return this; } /** * Set conversation context */ context(contextId) { this.message.context = contextId; return this; } /** * Add payment information */ payment(payment) { this.message.payment = payment; return this; } /** * Add metadata */ metadata(metadata) { this.message.metadata = { ...this.message.metadata, ...metadata }; return this; } /** * Add a text part */ text(content, options) { const part = { type: 'text', content, ...(options?.encoding && { encoding: options.encoding }), ...(options && { metadata: { ...(options.format && { format: options.format }), ...(options.language && { language: options.language }) } }) }; this.message.parts.push(part); return this; } /** * Add a data part */ data(content, options) { const part = { type: 'data', content, ...(options?.schema && { schema: options.schema }), ...(options?.format && { metadata: { format: options.format } }) }; this.message.parts.push(part); return this; } /** * Add a file part */ file(content, options) { const part = { type: 'file', content, ...(options?.description && { metadata: { description: options.description } }) }; this.message.parts.push(part); return this; } /** * Add an image part */ image(content, options) { const part = { type: 'image', content, ...(options?.caption && { metadata: { caption: options.caption } }) }; this.message.parts.push(part); return this; } /** * Add an audio part */ audio(content, options) { const part = { type: 'audio', content, ...(options && { metadata: { ...(options.title && { title: options.title }), ...(options.artist && { artist: options.artist }) } }) }; this.message.parts.push(part); return this; } /** * Add a video part */ video(content, options) { const part = { type: 'video', content, ...(options && { metadata: { ...(options.title && { title: options.title }), ...(options.description && { description: options.description }) } }) }; this.message.parts.push(part); return this; } /** * Add a custom part */ part(part) { this.message.parts.push(part); return this; } /** * Set custom message ID */ id(messageId) { this.message.id = messageId; return this; } /** * Set custom timestamp */ timestamp(timestamp) { this.message.timestamp = timestamp; return this; } /** * Build and validate the message */ build() { if (!this.message.parts || this.message.parts.length === 0) { throw new Error('Message must have at least one part'); } const result = SNAPMessageSchema.parse(this.message); return result; } /** * Build message for signing (without signature field) */ buildForSigning() { const message = this.build(); const { signature, ...messageForSigning } = message; return messageForSigning; } } /** * Message utilities */ export class MessageUtils { /** * Create a simple text message */ static text(from, content, to) { const builder = new MessageBuilder(from).text(content); if (to) builder.to(to); return builder.build(); } /** * Create a data message */ static data(from, content, to) { const builder = new MessageBuilder(from).data(content); if (to) builder.to(to); return builder.build(); } /** * Create an error response message */ static error(from, errorMessage, to) { const builder = new MessageBuilder(from) .text(`Error: ${errorMessage}`) .metadata({ type: 'error' }); if (to) builder.to(to); return builder.build(); } /** * Extract text content from message parts */ static extractText(message) { return message.parts .filter((part) => part.type === 'text') .map(part => part.content); } /** * Extract data content from message parts */ static extractData(message) { return message.parts .filter((part) => part.type === 'data') .map(part => part.content); } /** * Get all file parts from message */ static extractFiles(message) { return message.parts.filter((part) => part.type === 'file'); } /** * Get all image parts from message */ static extractImages(message) { return message.parts.filter((part) => part.type === 'image'); } /** * Check if message has payment */ static hasPayment(message) { return !!message.payment; } /** * Get payment amount from message */ static getPaymentAmount(message) { return message.payment?.amount ?? null; } /** * Check if message is in a conversation context */ static hasContext(message) { return !!message.context; } /** * Get message size estimate in bytes */ static estimateSize(message) { return new Blob([JSON.stringify(message)]).size; } /** * Validate message structure */ static validate(message) { return SNAPMessageSchema.parse(message); } /** * Create canonical representation for signing */ static canonicalize(message) { return canonicalizeObject(message); } /** * Check if message is expired (based on timestamp) */ static isExpired(message, maxAgeMinutes = 5) { const messageTime = new Date(message.timestamp); const now = new Date(); const diffMinutes = (now.getTime() - messageTime.getTime()) / (1000 * 60); return diffMinutes > maxAgeMinutes; } /** * Create a reply message */ static reply(originalMessage, from) { return new MessageBuilder(from) .to(originalMessage.from) .context(originalMessage.context || originalMessage.id) .metadata({ replyTo: originalMessage.id, type: 'reply' }); } /** * Create a payment request message */ static paymentRequest(from, to, amount, description) { return new MessageBuilder(from) .to(to) .text(description) .payment({ amount, currency: 'SEMNET', from: to, to: from, memo: description, status: 'pending' }) .metadata({ type: 'payment-request' }) .build(); } } /** * Convenience function to create a message builder */ export function createMessage(from) { return new MessageBuilder(from); } //# sourceMappingURL=message.js.map