UNPKG

@openfloor/protocol

Version:

Open Floor Protocol implementation for JavaScript/TypeScript - enables interoperable multi-agent conversations

601 lines 19.8 kB
/** * @fileoverview Event classes for the Open Floor Protocol * Implements all event types from the Inter-Agent Message Specification v1.0.0 * @author Open Voice Interoperability Initiative * @version 0.0.1 * @license Apache-2.0 */ import { Event, To } from './envelope'; import { DialogEvent } from './dialog-event'; import { Manifest } from './envelope'; import { createValidationError } from './utils'; /** * Represents an utterance event containing spoken or written dialog * This is the primary communication event between conversants * * @example * ```typescript * const utteranceEvent = new UtteranceEvent({ * dialogEvent: { * speakerUri: 'tag:example.com,2025:user1', * features: { * text: { mimeType: 'text/plain', tokens: [{ value: 'Hello world' }] } * } * }, * to: { speakerUri: 'tag:example.com,2025:agent1' } * }); * ``` */ export class UtteranceEvent extends Event { dialogEvent; /** * Creates a new UtteranceEvent instance * @param options - UtteranceEvent configuration options * @throws Error if dialogEvent is missing */ constructor(options) { const { dialogEvent, to, reason } = options; if (!dialogEvent) { throw new Error(createValidationError('UtteranceEvent.dialogEvent', dialogEvent, 'DialogEvent object')); } const dialogEventInstance = new DialogEvent(dialogEvent); const baseOptions = { eventType: 'utterance', parameters: { dialogEvent: dialogEventInstance.toObject() } }; if (to !== undefined) baseOptions.to = to; if (reason !== undefined) baseOptions.reason = reason; super(baseOptions); this.dialogEvent = dialogEventInstance; } static fromObject(data) { if (!data.parameters || typeof data.parameters !== 'object') { throw new Error('UtteranceEvent requires parameters with dialogEvent'); } const params = data.parameters; if (!params.dialogEvent || typeof params.dialogEvent !== 'object') { throw new Error('UtteranceEvent requires dialogEvent parameter'); } const options = { dialogEvent: DialogEvent.fromObject(params.dialogEvent).toObject() }; if (data.reason !== undefined) options.reason = data.reason; if (data.to && typeof data.to === 'object') { options.to = To.fromObject(data.to).toObject(); } return new UtteranceEvent(options); } } /** * Represents a context event providing additional information to recipient agents * Contains dialog history and other contextual parameters * * @example * ```typescript * const contextEvent = new ContextEvent({ * dialogHistory: [ * { * speakerUri: 'tag:example.com,2025:user1', * features: { text: { mimeType: 'text/plain', tokens: [{ value: 'Hello' }] } } * } * ], * sessionData: { userId: '12345' } * }); * ``` */ export class ContextEvent extends Event { dialogHistory; /** * Creates a new ContextEvent instance * @param options - ContextEvent configuration options */ constructor(options) { const { dialogHistory = [], to, reason, ...additionalParams } = options; const dialogHistoryInstance = dialogHistory.map(eventData => new DialogEvent(eventData)); const parameters = { dialogHistory: dialogHistoryInstance.map(event => event.toObject()), ...additionalParams }; const baseOptions = { eventType: 'context', parameters }; if (to !== undefined) baseOptions.to = to; if (reason !== undefined) baseOptions.reason = reason; super(baseOptions); this.dialogHistory = dialogHistoryInstance; } static fromObject(data) { const params = data.parameters || {}; const options = { dialogHistory: (params.dialogHistory || []).map(eventData => DialogEvent.fromObject(eventData).toObject()), ...params }; if (typeof data.reason === 'string') options.reason = data.reason; if (data.to && typeof data.to === 'object') options.to = To.fromObject(data.to).toObject(); return new ContextEvent(options); } } /** * Represents an invitation for an agent to join the conversation * This is a bare event with no required parameters * * @example * ```typescript * const inviteEvent = new InviteEvent({ * to: { serviceUrl: 'https://example.com/agent' } * }); * ``` */ export class InviteEvent extends Event { /** * Creates a new InviteEvent instance * @param options - InviteEvent configuration options */ constructor(options = {}) { const baseOptions = { eventType: 'invite', parameters: {} }; if (options.to !== undefined) baseOptions.to = options.to; if (options.reason !== undefined) baseOptions.reason = options.reason; super(baseOptions); } static fromObject(data) { const options = {}; if (data.to && typeof data.to === 'object') options.to = To.fromObject(data.to).toObject(); if (typeof data.reason === 'string') options.reason = data.reason; return new InviteEvent(options); } } /** * Represents removing an agent from the conversation * Supports reason tokens for why the agent is being uninvited * * @example * ```typescript * const uninviteEvent = new UninviteEvent({ * to: { speakerUri: 'tag:example.com,2025:agent1' }, * reason: '@timedOut' * }); * ``` */ export class UninviteEvent extends Event { /** * Creates a new UninviteEvent instance * @param options - UninviteEvent configuration options */ constructor(options = {}) { const baseOptions = { eventType: 'uninvite', parameters: {} }; if (options.to !== undefined) baseOptions.to = options.to; if (options.reason !== undefined) baseOptions.reason = options.reason; super(baseOptions); } static fromObject(data) { const options = {}; if (data.to && typeof data.to === 'object') options.to = To.fromObject(data.to).toObject(); if (typeof data.reason === 'string') options.reason = data.reason; return new UninviteEvent(options); } } /** * Represents declining an invitation to join the conversation * Sent in response to an invite event * * @example * ```typescript * const declineEvent = new DeclineInviteEvent({ * to: { speakerUri: 'tag:example.com,2025:convener' }, * reason: '@unavailable' * }); * ``` */ export class DeclineInviteEvent extends Event { /** * Creates a new DeclineInviteEvent instance * @param options - DeclineInviteEvent configuration options */ constructor(options = {}) { const baseOptions = { eventType: 'declineInvite', parameters: {} }; if (options.to !== undefined) baseOptions.to = options.to; if (options.reason !== undefined) baseOptions.reason = options.reason; super(baseOptions); } static fromObject(data) { const options = {}; if (data.to && typeof data.to === 'object') options.to = To.fromObject(data.to).toObject(); if (typeof data.reason === 'string') options.reason = data.reason; return new DeclineInviteEvent(options); } } /** * Represents an agent leaving the conversation * This is a bare event with no required parameters * * @example * ```typescript * const byeEvent = new ByeEvent({ * reason: 'Task completed' * }); * ``` */ export class ByeEvent extends Event { /** * Creates a new ByeEvent instance * @param options - ByeEvent configuration options */ constructor(options = {}) { const baseOptions = { eventType: 'bye', parameters: {} }; if (options.to !== undefined) baseOptions.to = options.to; if (options.reason !== undefined) baseOptions.reason = options.reason; super(baseOptions); } static fromObject(data) { const options = {}; if (data.to && typeof data.to === 'object') options.to = To.fromObject(data.to).toObject(); if (typeof data.reason === 'string') options.reason = data.reason; return new ByeEvent(options); } } /** * Represents a request for agent manifests * Used for agent discovery and capability determination * * @example * ```typescript * const getManifestsEvent = new GetManifestsEvent({ * to: { serviceUrl: 'https://example.com/discovery-agent' }, * recommendScope: 'external' * }); * ``` */ export class GetManifestsEvent extends Event { recommendScope; /** * Creates a new GetManifestsEvent instance * @param options - GetManifestsEvent configuration options */ constructor(options) { const { recommendScope = 'internal', to, reason } = options; const baseOptions = { eventType: 'getManifests', parameters: { recommendScope } }; if (to !== undefined) baseOptions.to = to; if (reason !== undefined) baseOptions.reason = reason; super(baseOptions); this.recommendScope = recommendScope; } static fromObject(data) { const recommendScope = data.parameters?.recommendScope ?? 'internal'; const options = { recommendScope }; if (typeof data.reason === 'string') options.reason = data.reason; if (data.to && typeof data.to === 'object') options.to = To.fromObject(data.to).toObject(); return new GetManifestsEvent(options); } } /** * Represents publishing agent manifests * Contains arrays of servicing and discovery manifests * * @example * ```typescript * const publishEvent = new PublishManifestsEvent({ * servicingManifests: [{ * identification: { * speakerUri: 'tag:example.com,2025:weather-bot', * serviceUrl: 'https://example.com/weather' * }, * capabilities: [] * }], * discoveryManifests: [] * }); * ``` */ export class PublishManifestsEvent extends Event { servicingManifests; discoveryManifests; /** * Creates a new PublishManifestsEvent instance * @param options - PublishManifestsEvent configuration options */ constructor(options) { const { servicingManifests = [], discoveryManifests = [], to, reason } = options; const servicingInstances = servicingManifests.map(manifest => new Manifest(manifest)); const discoveryInstances = discoveryManifests.map(manifest => new Manifest(manifest)); const baseOptions = { eventType: 'publishManifests', parameters: { servicingManifests: servicingInstances.map(m => m.toObject()), discoveryManifests: discoveryInstances.map(m => m.toObject()) } }; if (to !== undefined) baseOptions.to = to; if (reason !== undefined) baseOptions.reason = reason; super(baseOptions); this.servicingManifests = Object.freeze(servicingInstances); this.discoveryManifests = Object.freeze(discoveryInstances); } static fromObject(data) { const params = data.parameters || {}; const options = { servicingManifests: Array.isArray(params.servicingManifests) ? params.servicingManifests : [], discoveryManifests: Array.isArray(params.discoveryManifests) ? params.discoveryManifests : [] }; if (typeof data.reason === 'string') options.reason = data.reason; if (data.to && typeof data.to === 'object') options.to = To.fromObject(data.to).toObject(); return new PublishManifestsEvent(options); } } /** * Represents a request for the conversational floor * Used in multi-party conversations for floor management * * @example * ```typescript * const requestFloorEvent = new RequestFloorEvent({ * to: { speakerUri: 'tag:example.com,2025:convener' }, * reason: 'Need to add information' * }); * ``` */ export class RequestFloorEvent extends Event { /** * Creates a new RequestFloorEvent instance * @param options - RequestFloorEvent configuration options */ constructor(options = {}) { const baseOptions = { eventType: 'requestFloor', parameters: {} }; if (options.to !== undefined) baseOptions.to = options.to; if (options.reason !== undefined) baseOptions.reason = options.reason; super(baseOptions); } static fromObject(data) { const options = {}; if (data.to && typeof data.to === 'object') options.to = To.fromObject(data.to).toObject(); if (typeof data.reason === 'string') options.reason = data.reason; return new RequestFloorEvent(options); } } /** * Represents granting the conversational floor to an agent * Used by convener agents to manage multi-party conversations * * @example * ```typescript * const grantFloorEvent = new GrantFloorEvent({ * to: { speakerUri: 'tag:example.com,2025:agent1' } * }); * ``` */ export class GrantFloorEvent extends Event { /** * Creates a new GrantFloorEvent instance * @param options - GrantFloorEvent configuration options */ constructor(options = {}) { const baseOptions = { eventType: 'grantFloor', parameters: {} }; if (options.to !== undefined) baseOptions.to = options.to; if (options.reason !== undefined) baseOptions.reason = options.reason; super(baseOptions); } static fromObject(data) { const options = {}; if (data.to && typeof data.to === 'object') options.to = To.fromObject(data.to).toObject(); if (typeof data.reason === 'string') options.reason = data.reason; return new GrantFloorEvent(options); } } /** * Represents revoking the conversational floor from an agent * Used by convener agents with special reason tokens * * @example * ```typescript * const revokeFloorEvent = new RevokeFloorEvent({ * to: { speakerUri: 'tag:example.com,2025:agent1' }, * reason: '@timedOut' * }); * ``` */ export class RevokeFloorEvent extends Event { /** * Creates a new RevokeFloorEvent instance * @param options - RevokeFloorEvent configuration options */ constructor(options = {}) { const baseOptions = { eventType: 'revokeFloor', parameters: {} }; if (options.to !== undefined) baseOptions.to = options.to; if (options.reason !== undefined) baseOptions.reason = options.reason; super(baseOptions); } static fromObject(data) { const options = {}; if (data.to && typeof data.to === 'object') options.to = To.fromObject(data.to).toObject(); if (typeof data.reason === 'string') options.reason = data.reason; return new RevokeFloorEvent(options); } } /** * Represents yielding the conversational floor * Sent by agents to indicate they no longer wish to speak * * @example * ```typescript * const yieldFloorEvent = new YieldFloorEvent({ * reason: '@complete' * }); * ``` */ export class YieldFloorEvent extends Event { /** * Creates a new YieldFloorEvent instance * @param options - YieldFloorEvent configuration options */ constructor(options = {}) { const baseOptions = { eventType: 'yieldFloor', parameters: {} }; if (options.to !== undefined) baseOptions.to = options.to; if (options.reason !== undefined) baseOptions.reason = options.reason; super(baseOptions); } static fromObject(data) { const options = {}; if (data.to && typeof data.to === 'object') options.to = To.fromObject(data.to).toObject(); if (typeof data.reason === 'string') options.reason = data.reason; return new YieldFloorEvent(options); } } /** * Event factory function to create appropriate event instances from generic event data * Automatically determines the correct event class based on eventType * * @param data - Raw event data object * @returns Appropriate Event instance * @throws Error if eventType is unknown * * @example * ```typescript * const eventData = { * eventType: 'utterance', * parameters: { dialogEvent: {...} } * }; * const event = createEvent(eventData); // Returns UtteranceEvent instance * ``` */ export function createEvent(data) { const eventType = data.eventType; switch (eventType) { case 'utterance': return UtteranceEvent.fromObject(data); case 'context': return ContextEvent.fromObject(data); case 'invite': return InviteEvent.fromObject(data); case 'uninvite': return UninviteEvent.fromObject(data); case 'declineInvite': return DeclineInviteEvent.fromObject(data); case 'bye': return ByeEvent.fromObject(data); case 'getManifests': return GetManifestsEvent.fromObject(data); case 'publishManifests': return PublishManifestsEvent.fromObject(data); case 'requestFloor': return RequestFloorEvent.fromObject(data); case 'grantFloor': return GrantFloorEvent.fromObject(data); case 'revokeFloor': return RevokeFloorEvent.fromObject(data); case 'yieldFloor': return YieldFloorEvent.fromObject(data); default: throw new Error(`Unknown event type: ${eventType}`); } } /** * Type guard to check if an event is an UtteranceEvent */ export function isUtteranceEvent(event) { return event.eventType === 'utterance'; } /** * Type guard to check if an event is a ContextEvent */ export function isContextEvent(event) { return event.eventType === 'context'; } /** * Type guard to check if an event is an InviteEvent */ export function isInviteEvent(event) { return event.eventType === 'invite'; } /** * Type guard to check if an event is an UninviteEvent */ export function isUninviteEvent(event) { return event.eventType === 'uninvite'; } /** * Type guard to check if an event is a DeclineInviteEvent */ export function isDeclineInviteEvent(event) { return event.eventType === 'declineInvite'; } /** * Type guard to check if an event is a ByeEvent */ export function isByeEvent(event) { return event.eventType === 'bye'; } /** * Type guard to check if an event is a GetManifestsEvent */ export function isGetManifestsEvent(event) { return event.eventType === 'getManifests'; } /** * Type guard to check if an event is a PublishManifestsEvent */ export function isPublishManifestsEvent(event) { return event.eventType === 'publishManifests'; } /** * Type guard to check if an event is a RequestFloorEvent */ export function isRequestFloorEvent(event) { return event.eventType === 'requestFloor'; } /** * Type guard to check if an event is a GrantFloorEvent */ export function isGrantFloorEvent(event) { return event.eventType === 'grantFloor'; } /** * Type guard to check if an event is a RevokeFloorEvent */ export function isRevokeFloorEvent(event) { return event.eventType === 'revokeFloor'; } /** * Type guard to check if an event is a YieldFloorEvent */ export function isYieldFloorEvent(event) { return event.eventType === 'yieldFloor'; } export { Event }; //# sourceMappingURL=events.js.map