@openfloor/protocol
Version:
Open Floor Protocol implementation for JavaScript/TypeScript - enables interoperable multi-agent conversations
902 lines • 33.8 kB
JavaScript
/**
* @fileoverview Envelope and Manifest implementation for the Open Floor Protocol
* Implements the Inter-Agent Message Specification v1.0.0 and Assistant Manifest Specification v1.0.0
* @author Open Voice Interoperability Initiative
* @version 0.0.1
* @license Apache-2.0
*/
import { isValidUri, createValidationError, hasRequiredProperties } from './utils';
/**
* Represents schema information for Open Floor protocol messages
*
* @example
* ```typescript
* const schema = new Schema({ version: '1.0.0' });
* const schemaWithUrl = new Schema({
* version: '1.0.0',
* url: 'https://example.com/schema.json'
* });
* ```
*/
export class Schema {
version;
url;
/**
* Creates a new Schema instance
* @param options - Schema configuration options
*/
constructor(options) {
this.version = options.version;
if (options.url !== undefined)
this.url = options.url;
}
toObject() {
const result = { version: this.version };
if (this.url) {
result.url = this.url;
}
return result;
}
toJSON() {
return JSON.stringify(this.toObject());
}
static fromObject(data) {
if (!hasRequiredProperties(data, ['version'])) {
throw new Error('Schema requires version');
}
return new Schema({
version: data.version,
...(data.url !== undefined ? { url: data.url } : {})
});
}
}
/**
* Represents identification information for an agent or conversant
*
* @example
* ```typescript
* const identification = new Identification({
* speakerUri: 'tag:example.com,2025:agent1',
* serviceUrl: 'https://example.com/agent',
* organization: 'Example Corp',
* conversationalName: 'Assistant',
* synopsis: 'Helpful AI assistant'
* });
* ```
*/
export class Identification {
speakerUri;
serviceUrl;
organization;
conversationalName;
department;
role;
synopsis;
/**
* Creates a new Identification instance
* @param options - Identification configuration options
* @throws Error if required fields are missing or invalid
*/
constructor(options) {
const { speakerUri, serviceUrl, organization, conversationalName, synopsis, department, role } = options;
if (!speakerUri)
throw new Error(createValidationError('Identification.speakerUri', speakerUri, 'non-empty string'));
if (!serviceUrl)
throw new Error(createValidationError('Identification.serviceUrl', serviceUrl, 'non-empty string'));
if (!organization)
throw new Error(createValidationError('Identification.organization', organization, 'non-empty string'));
if (!conversationalName)
throw new Error(createValidationError('Identification.conversationalName', conversationalName, 'non-empty string'));
if (!synopsis)
throw new Error(createValidationError('Identification.synopsis', synopsis, 'non-empty string'));
this.speakerUri = speakerUri;
this.serviceUrl = serviceUrl;
this.organization = organization;
this.conversationalName = conversationalName;
this.synopsis = synopsis;
if (department)
this.department = department;
if (role)
this.role = role;
}
toObject() {
const result = {
speakerUri: this.speakerUri,
serviceUrl: this.serviceUrl
};
if (this.organization)
result.organization = this.organization;
if (this.conversationalName)
result.conversationalName = this.conversationalName;
if (this.department)
result.department = this.department;
if (this.role)
result.role = this.role;
if (this.synopsis)
result.synopsis = this.synopsis;
return result;
}
toJSON() {
return JSON.stringify(this.toObject());
}
static fromObject(data) {
if (!hasRequiredProperties(data, ['speakerUri', 'serviceUrl', 'organization', 'conversationalName', 'synopsis'])) {
throw new Error('Identification requires speakerUri, serviceUrl, organization, conversationalName, and synopsis');
}
return new Identification({
speakerUri: data.speakerUri,
serviceUrl: data.serviceUrl,
organization: data.organization,
conversationalName: data.conversationalName,
synopsis: data.synopsis,
...(data.department !== undefined ? { department: data.department } : {}),
...(data.role !== undefined ? { role: data.role } : {})
});
}
}
/**
* Represents supported input/output layers for agent capabilities
*
* @example
* ```typescript
* const layers = new SupportedLayers({
* input: ['text', 'audio'],
* output: ['text', 'ssml', 'audio']
* });
* ```
*/
export class SupportedLayers {
input;
output;
/**
* Creates a new SupportedLayers instance
* @param options - SupportedLayers configuration options
*/
constructor(options = {}) {
this.input = Object.freeze(options.input || ['text']);
this.output = Object.freeze(options.output || ['text']);
}
toObject() {
return {
input: [...this.input],
output: [...this.output]
};
}
toJSON() {
return JSON.stringify(this.toObject());
}
static fromObject(data) {
return new SupportedLayers({
input: data.input,
output: data.output
});
}
}
/**
* Represents a single capability of an agent
*
* @example
* ```typescript
* const capability = new Capability({
* keyphrases: ['weather', 'forecast', 'temperature'],
* descriptions: ['Provides weather information and forecasts'],
* languages: ['en-US', 'en-GB'],
* supportedLayers: { input: ['text'], output: ['text', 'ssml'] }
* });
* ```
*/
export class Capability {
keyphrases;
descriptions;
languages;
supportedLayers;
/**
* Creates a new Capability instance
* @param options - Capability configuration options
* @throws Error if required fields are missing
*/
constructor(options) {
const { keyphrases, descriptions, languages, supportedLayers } = options;
if (!keyphrases || keyphrases.length === 0)
throw new Error(createValidationError('Capability.keyphrases', keyphrases, 'non-empty array of strings'));
if (!descriptions || descriptions.length === 0)
throw new Error(createValidationError('Capability.descriptions', descriptions, 'non-empty array of strings'));
this.keyphrases = Object.freeze([...keyphrases]);
this.descriptions = Object.freeze([...descriptions]);
if (languages)
this.languages = Object.freeze([...languages]);
if (supportedLayers)
this.supportedLayers = new SupportedLayers(supportedLayers);
else
this.supportedLayers = new SupportedLayers({});
}
toObject() {
const result = {
keyphrases: [...this.keyphrases],
descriptions: [...this.descriptions],
supportedLayers: this.supportedLayers.toObject()
};
if (this.languages) {
result.languages = [...this.languages];
}
return result;
}
toJSON() {
return JSON.stringify(this.toObject());
}
static fromObject(data) {
if (!hasRequiredProperties(data, ['keyphrases', 'descriptions'])) {
throw new Error('Capability requires keyphrases and descriptions');
}
return new Capability({
keyphrases: data.keyphrases,
descriptions: data.descriptions,
...(data.languages !== undefined ? { languages: data.languages } : {}),
...(data.supportedLayers !== undefined ? { supportedLayers: SupportedLayers.fromObject(data.supportedLayers).toObject() } : {})
});
}
}
/**
* Represents an agent manifest containing identification and capabilities
*
* @example
* ```typescript
* const manifest = new Manifest({
* identification: {
* speakerUri: 'tag:example.com,2025:weather-bot',
* serviceUrl: 'https://example.com/weather-bot',
* organization: 'Weather Corp',
* conversationalName: 'WeatherBot',
* synopsis: 'Weather information assistant'
* },
* capabilities: [{
* keyphrases: ['weather', 'forecast'],
* descriptions: ['Provides weather forecasts and current conditions']
* }]
* });
* ```
*/
export class Manifest {
identification;
capabilities;
/**
* Creates a new Manifest instance
* @param options - Manifest configuration options
* @throws Error if required fields are missing
*/
constructor(options) {
const { identification, capabilities = [] } = options;
if (!identification) {
throw new Error(createValidationError('Manifest.identification', identification, 'Identification object'));
}
this.identification = new Identification(identification);
this.capabilities = Object.freeze(capabilities.map(cap => new Capability(cap)));
}
toObject() {
return {
identification: this.identification.toObject(),
capabilities: this.capabilities.map(cap => cap.toObject())
};
}
toJSON() {
return JSON.stringify(this.toObject());
}
static fromObject(data) {
if (!hasRequiredProperties(data, ['identification', 'capabilities'])) {
throw new Error('Manifest requires identification and capabilities');
}
const idObj = data.identification;
if (!hasRequiredProperties(idObj, ['speakerUri', 'serviceUrl', 'organization', 'conversationalName', 'synopsis'])) {
throw new Error('Identification requires all required fields');
}
const capabilitiesArr = Array.isArray(data.capabilities)
? data.capabilities.map(capData => {
const capObj = capData;
if (!hasRequiredProperties(capObj, ['keyphrases', 'descriptions'])) {
throw new Error('Capability requires keyphrases and descriptions');
}
return {
keyphrases: capObj.keyphrases,
descriptions: capObj.descriptions,
...(capObj.languages !== undefined ? { languages: capObj.languages } : {}),
...(capObj.supportedLayers !== undefined ? { supportedLayers: capObj.supportedLayers } : {})
};
})
: [];
return new Manifest({
identification: {
speakerUri: idObj.speakerUri,
serviceUrl: idObj.serviceUrl,
organization: idObj.organization,
conversationalName: idObj.conversationalName,
synopsis: idObj.synopsis,
...(idObj.department !== undefined ? { department: idObj.department } : {}),
...(idObj.role !== undefined ? { role: idObj.role } : {})
},
capabilities: capabilitiesArr
});
}
}
/**
* Represents a conversant in a conversation with identification and persistent state
*
* @example
* ```typescript
* const conversant = new Conversant({
* identification: {
* speakerUri: 'tag:example.com,2025:user1',
* serviceUrl: 'https://example.com/user-proxy',
* conversationalName: 'User'
* },
* persistentState: { preferences: { language: 'en-US' } }
* });
* ```
*/
export class Conversant {
identification;
persistentState;
/**
* Creates a new Conversant instance
* @param options - Conversant configuration options
* @throws Error if identification is missing
*/
constructor(options) {
const { identification, persistentState = {} } = options;
if (!identification) {
throw new Error(createValidationError('Conversant.identification', identification, 'Identification object'));
}
this.identification = new Identification(identification);
this.persistentState = { ...persistentState };
}
toObject() {
const result = {
identification: this.identification.toObject()
};
if (Object.keys(this.persistentState).length > 0) {
result.persistentState = { ...this.persistentState };
}
return result;
}
toJSON() {
return JSON.stringify(this.toObject());
}
static fromObject(data) {
if (!hasRequiredProperties(data, ['identification'])) {
throw new Error('Conversant requires identification');
}
const idObj = data.identification;
if (!hasRequiredProperties(idObj, ['speakerUri', 'serviceUrl', 'organization', 'conversationalName', 'synopsis'])) {
throw new Error('Identification requires all required fields');
}
return new Conversant({
identification: {
speakerUri: idObj.speakerUri,
serviceUrl: idObj.serviceUrl,
organization: idObj.organization,
conversationalName: idObj.conversationalName,
synopsis: idObj.synopsis,
...(idObj.department !== undefined ? { department: idObj.department } : {}),
...(idObj.role !== undefined ? { role: idObj.role } : {})
},
...(data.persistentState !== undefined ? { persistentState: data.persistentState } : {})
});
}
}
/**
* Represents conversation metadata including ID and participants
*
* @example
* ```typescript
* const conversation = new Conversation({
* id: 'conv:12345',
* conversants: [{
* identification: {
* speakerUri: 'tag:example.com,2025:user1',
* serviceUrl: 'https://example.com/user-proxy'
* }
* }]
* });
* ```
*/
export class Conversation {
id;
conversants;
/**
* Creates a new Conversation instance
* @param options - Conversation configuration options
*/
constructor(options) {
const { id, conversants = [] } = options;
if (!id)
throw new Error('Conversation.id is required');
this.id = id;
this.conversants = Object.freeze(conversants.map(conv => new Conversant(conv)));
}
toObject() {
const result = { id: this.id };
if (this.conversants.length > 0) {
result.conversants = this.conversants.map(conv => conv.toObject());
}
return result;
}
toJSON() {
return JSON.stringify(this.toObject());
}
static fromObject(data) {
if (!hasRequiredProperties(data, ['id'])) {
throw new Error('Conversation requires id');
}
let conversantsArr = undefined;
if (Array.isArray(data.conversants)) {
conversantsArr = data.conversants.map(convData => {
const convObj = convData;
if (!hasRequiredProperties(convObj, ['identification'])) {
throw new Error('Conversant requires identification');
}
const idObj = convObj.identification;
if (!hasRequiredProperties(idObj, ['speakerUri', 'serviceUrl', 'organization', 'conversationalName', 'synopsis'])) {
throw new Error('Identification requires all required fields');
}
return {
identification: {
speakerUri: idObj.speakerUri,
serviceUrl: idObj.serviceUrl,
organization: idObj.organization,
conversationalName: idObj.conversationalName,
synopsis: idObj.synopsis,
...(idObj.department !== undefined ? { department: idObj.department } : {}),
...(idObj.role !== undefined ? { role: idObj.role } : {})
},
...(convObj.persistentState !== undefined ? { persistentState: convObj.persistentState } : {})
};
});
}
return new Conversation({
id: data.id,
...(conversantsArr ? { conversants: conversantsArr } : {})
});
}
}
/**
* Represents sender information for envelope messages
*
* @example
* ```typescript
* const sender = new Sender({
* speakerUri: 'tag:example.com,2025:agent1',
* serviceUrl: 'https://example.com/agent'
* });
* ```
*/
export class Sender {
speakerUri;
serviceUrl;
/**
* Creates a new Sender instance
* @param options - Sender configuration options
* @throws Error if speakerUri is missing or invalid
*/
constructor(options) {
const { speakerUri, serviceUrl } = options;
if (!speakerUri) {
throw new Error(createValidationError('Sender.speakerUri', speakerUri, 'non-empty string'));
}
if (!isValidUri(speakerUri)) {
throw new Error(createValidationError('Sender.speakerUri', speakerUri, 'valid URI format'));
}
this.speakerUri = speakerUri;
if (serviceUrl !== undefined)
this.serviceUrl = serviceUrl;
}
toObject() {
const result = { speakerUri: this.speakerUri };
if (this.serviceUrl) {
result.serviceUrl = this.serviceUrl;
}
return result;
}
toJSON() {
return JSON.stringify(this.toObject());
}
static fromObject(data) {
if (!hasRequiredProperties(data, ['speakerUri'])) {
throw new Error('Sender requires speakerUri');
}
return new Sender({
speakerUri: data.speakerUri,
...(data.serviceUrl !== undefined ? { serviceUrl: data.serviceUrl } : {})
});
}
}
/**
* Represents targeting information for events (who the event is addressed to)
*
* @example
* ```typescript
* // Address to specific agent
* const to1 = new To({
* speakerUri: 'tag:example.com,2025:agent1'
* });
*
* // Private message to specific service
* const to2 = new To({
* serviceUrl: 'https://example.com/agent',
* private: true
* });
* ```
*/
export class To {
speakerUri;
serviceUrl;
private;
/**
* Creates a new To instance
* @param options - To configuration options
* @throws Error if neither speakerUri nor serviceUrl is provided
*/
constructor(options) {
const { speakerUri, serviceUrl, private: isPrivate } = options;
if (!speakerUri && !serviceUrl) {
throw new Error('To requires at least speakerUri or serviceUrl');
}
if (speakerUri !== undefined)
this.speakerUri = speakerUri;
if (serviceUrl !== undefined)
this.serviceUrl = serviceUrl;
this.private = !!isPrivate;
}
toObject() {
const result = {};
if (this.speakerUri) {
result.speakerUri = this.speakerUri;
}
if (this.serviceUrl) {
result.serviceUrl = this.serviceUrl;
}
if (this.private) {
result.private = this.private;
}
return result;
}
toJSON() {
return JSON.stringify(this.toObject());
}
static fromObject(data) {
if (!data.speakerUri && !data.serviceUrl) {
throw new Error('To requires at least speakerUri or serviceUrl');
}
return new To({
...(data.speakerUri !== undefined ? { speakerUri: data.speakerUri } : {}),
...(data.serviceUrl !== undefined ? { serviceUrl: data.serviceUrl } : {}),
...(data.private !== undefined ? { private: data.private } : {})
});
}
}
/**
* Base class for all Open Floor Protocol events
*
* @example
* ```typescript
* const event = new Event({
* eventType: 'utterance',
* to: { speakerUri: 'tag:example.com,2025:agent1' },
* reason: 'User query',
* parameters: { dialogEvent: {...} }
* });
* ```
*/
export class Event {
eventType;
to;
reason;
parameters;
/**
* Creates a new Event instance
* @param options - Event configuration options
* @throws Error if eventType is missing or invalid
*/
constructor(options) {
const allowedEventTypes = [
'utterance', 'context', 'invite', 'uninvite', 'declineInvite', 'bye',
'getManifests', 'publishManifests', 'requestFloor', 'grantFloor', 'revokeFloor', 'yieldFloor'
];
if (!options.eventType || !allowedEventTypes.includes(options.eventType)) {
throw new Error(`Invalid eventType: ${options.eventType}`);
}
const { eventType, to, reason, parameters } = options;
this.eventType = eventType;
if (to !== undefined)
this.to = new To(to);
if (reason !== undefined)
this.reason = reason;
this.parameters = parameters ? { ...parameters } : {};
}
toObject() {
const result = { eventType: this.eventType };
if (this.to) {
result.to = this.to.toObject();
}
if (this.reason) {
result.reason = this.reason;
}
if (Object.keys(this.parameters).length > 0) {
result.parameters = { ...this.parameters };
}
return result;
}
toJSON() {
return JSON.stringify(this.toObject());
}
static fromObject(data) {
if (!hasRequiredProperties(data, ['eventType'])) {
throw new Error('Event requires eventType');
}
const eventTypeValue = data.eventType;
const validEventTypes = [
'utterance', 'context', 'invite', 'uninvite', 'declineInvite', 'bye',
'getManifests', 'publishManifests', 'requestFloor', 'grantFloor', 'revokeFloor', 'yieldFloor'
];
if (!validEventTypes.includes(eventTypeValue)) {
throw new Error('Invalid eventType');
}
const options = {
eventType: eventTypeValue,
...(data.to && typeof data.to === 'object' ? { to: To.fromObject(data.to).toObject() } : {}),
...(data.reason !== undefined ? { reason: data.reason } : {}),
...(data.parameters !== undefined ? { parameters: data.parameters } : {})
};
return new Event(options);
}
}
/**
* Represents an Open Floor Protocol message envelope
* Contains schema, conversation, sender, and events information
*
* @example
* ```typescript
* const envelope = new Envelope({
* conversation: { id: 'conv:12345' },
* sender: { speakerUri: 'tag:example.com,2025:agent1' },
* events: [{
* eventType: 'utterance',
* parameters: { dialogEvent: {...} }
* }]
* });
* ```
*/
export class Envelope {
schema;
conversation;
sender;
events;
/**
* Creates a new Envelope instance
* @param options - Envelope configuration options
* @throws Error if required fields are missing
*/
constructor(options) {
const { schema, conversation, sender, events } = options;
if (!schema)
throw new Error('Envelope.schema is required');
if (!conversation)
throw new Error('Envelope.conversation is required');
if (!sender)
throw new Error('Envelope.sender is required');
if (!events)
throw new Error('Envelope.events is required');
this.schema = new Schema(schema);
this.conversation = new Conversation(conversation);
this.sender = new Sender(sender);
this.events = Object.freeze(events.map(eventOpts => new Event(eventOpts)));
}
toObject() {
return {
schema: this.schema.toObject(),
conversation: this.conversation.toObject(),
sender: this.sender.toObject(),
events: this.events.map(event => event.toObject())
};
}
toJSON() {
return JSON.stringify(this.toObject());
}
/**
* Creates a wrapped payload for the envelope (adds openFloor wrapper)
*/
toPayload() {
return new Payload({
openFloor: {
schema: { version: this.schema.version, ...(this.schema.url ? { url: this.schema.url } : {}) },
conversation: {
id: this.conversation.id,
...(this.conversation.conversants && this.conversation.conversants.length > 0
? {
conversants: this.conversation.conversants.map(c => {
const obj = c.toObject();
if (!obj.identification || typeof obj.identification !== 'object' || !hasRequiredProperties(obj.identification, ['speakerUri', 'serviceUrl', 'organization', 'conversationalName', 'synopsis'])) {
throw new Error('Conversant.identification is missing required fields');
}
const idObj = obj.identification;
return {
identification: {
speakerUri: idObj.speakerUri,
serviceUrl: idObj.serviceUrl,
organization: idObj.organization,
conversationalName: idObj.conversationalName,
synopsis: idObj.synopsis,
...(idObj.department !== undefined ? { department: idObj.department } : {}),
...(idObj.role !== undefined ? { role: idObj.role } : {})
},
...(obj.persistentState !== undefined ? { persistentState: obj.persistentState } : {})
};
})
}
: {})
},
sender: { speakerUri: this.sender.speakerUri, ...(this.sender.serviceUrl ? { serviceUrl: this.sender.serviceUrl } : {}) },
events: this.events.map(e => {
const base = {
eventType: e.eventType,
parameters: e.parameters
};
if (e.to)
base.to = e.to.toObject();
if (e.reason)
base.reason = e.reason;
return base;
})
}
});
}
static fromObject(data) {
if (!hasRequiredProperties(data, ['schema', 'conversation', 'sender', 'events'])) {
throw new Error('Envelope requires schema, conversation, sender, and events');
}
const schemaObj = Schema.fromObject(data.schema);
const conversationObj = Conversation.fromObject(data.conversation);
const senderObj = Sender.fromObject(data.sender);
const eventsArr = Array.isArray(data.events)
? data.events.map(eventData => Event.fromObject(eventData))
: [];
return new Envelope({
schema: { version: schemaObj.version, ...(schemaObj.url ? { url: schemaObj.url } : {}) },
conversation: {
id: conversationObj.id,
...(conversationObj.conversants && conversationObj.conversants.length > 0
? {
conversants: conversationObj.conversants.map(c => {
const obj = c.toObject();
if (!obj.identification || typeof obj.identification !== 'object' || !hasRequiredProperties(obj.identification, ['speakerUri', 'serviceUrl', 'organization', 'conversationalName', 'synopsis'])) {
throw new Error('Conversant.identification is missing required fields');
}
const idObj = obj.identification;
return {
identification: {
speakerUri: idObj.speakerUri,
serviceUrl: idObj.serviceUrl,
organization: idObj.organization,
conversationalName: idObj.conversationalName,
synopsis: idObj.synopsis,
...(idObj.department !== undefined ? { department: idObj.department } : {}),
...(idObj.role !== undefined ? { role: idObj.role } : {})
},
...(obj.persistentState !== undefined ? { persistentState: obj.persistentState } : {})
};
})
}
: {})
},
sender: { speakerUri: senderObj.speakerUri, ...(senderObj.serviceUrl ? { serviceUrl: senderObj.serviceUrl } : {}) },
events: eventsArr.map(e => {
const base = {
eventType: e.eventType,
parameters: e.parameters
};
if (e.to)
base.to = e.to.toObject();
if (e.reason)
base.reason = e.reason;
return base;
})
});
}
}
/**
* Represents a payload wrapper that contains an Open Floor envelope
* This is the top-level structure as defined in the specification
*
* @example
* ```typescript
* const payload = new Payload({
* openFloor: {
* conversation: { id: 'conv:12345' },
* sender: { speakerUri: 'tag:example.com,2025:agent1' },
* events: []
* }
* });
* ```
*/
export class Payload {
openFloor;
/**
* Creates a new Payload instance
* @param options - Payload configuration options
* @throws Error if openFloor is missing
*/
constructor(options) {
const { openFloor } = options;
if (!openFloor)
throw new Error('Payload.openFloor is required');
this.openFloor = new Envelope(openFloor);
}
toObject() {
return {
openFloor: this.openFloor.toObject()
};
}
toJSON() {
return JSON.stringify(this.toObject());
}
static fromObject(data) {
if (!hasRequiredProperties(data, ['openFloor'])) {
throw new Error('Payload requires openFloor');
}
const openFloorObj = Envelope.fromObject(data.openFloor);
return new Payload({
openFloor: {
schema: { version: openFloorObj.schema.version, ...(openFloorObj.schema.url ? { url: openFloorObj.schema.url } : {}) },
conversation: {
id: openFloorObj.conversation.id,
...(openFloorObj.conversation.conversants && openFloorObj.conversation.conversants.length > 0
? {
conversants: openFloorObj.conversation.conversants.map(c => {
const obj = c.toObject();
if (!obj.identification || typeof obj.identification !== 'object' || !hasRequiredProperties(obj.identification, ['speakerUri', 'serviceUrl', 'organization', 'conversationalName', 'synopsis'])) {
throw new Error('Conversant.identification is missing required fields');
}
const idObj = obj.identification;
return {
identification: {
speakerUri: idObj.speakerUri,
serviceUrl: idObj.serviceUrl,
organization: idObj.organization,
conversationalName: idObj.conversationalName,
synopsis: idObj.synopsis,
...(idObj.department !== undefined ? { department: idObj.department } : {}),
...(idObj.role !== undefined ? { role: idObj.role } : {})
},
...(obj.persistentState !== undefined ? { persistentState: obj.persistentState } : {})
};
})
}
: {})
},
sender: { speakerUri: openFloorObj.sender.speakerUri, ...(openFloorObj.sender.serviceUrl ? { serviceUrl: openFloorObj.sender.serviceUrl } : {}) },
events: openFloorObj.events.map(e => {
const base = {
eventType: e.eventType,
parameters: e.parameters
};
if (e.to)
base.to = e.to.toObject();
if (e.reason)
base.reason = e.reason;
return base;
})
}
});
}
/**
* Creates a Payload from a JSON string
*/
static fromJSON(jsonString) {
try {
const data = JSON.parse(jsonString);
return Payload.fromObject(data);
}
catch (error) {
throw new Error(`Failed to parse JSON payload: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
}
//# sourceMappingURL=envelope.js.map