semnet-snap-protocol
Version:
TypeScript reference implementation of the SNAP Protocol v1.1 - Agent Internet Edition
323 lines • 8.24 kB
JavaScript
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