import type { AgentRuntimeProvider, RuntimeAttachHandle, RuntimeAttachMode, RuntimeCapability, RuntimeCapabilitySet, RuntimeHealth, RuntimeMessage, RuntimeScope, RuntimeSession, RuntimeSessionState, RuntimeSessionTree, RuntimeStreamEvent, } from '@mosaicstack/types'; const HERMES_PROVIDER_ID = 'runtime.hermes'; const RUNTIME_CAPABILITIES: readonly RuntimeCapability[] = [ 'session.list', 'session.tree', 'session.stream', 'session.send', 'session.attach', 'session.terminate', ]; /** Legacy transport boundary. These shapes are intentionally adapter-local. */ export interface HermesLegacySession { conversation_id: string; agent_id: string; parent_conversation_id?: string; status: string; created_at: string; updated_at: string; } export interface HermesRuntimeTransport { capabilities(scope: RuntimeScope): Promise; health(scope: RuntimeScope): Promise<{ status: string; detail?: string }>; sessions(scope: RuntimeScope): Promise; stream( sessionId: string, cursor: string | undefined, scope: RuntimeScope, ): AsyncIterable; send(sessionId: string, message: RuntimeMessage, scope: RuntimeScope): Promise; attach( sessionId: string, mode: RuntimeAttachMode, scope: RuntimeScope, ): Promise; detach(attachmentId: string, scope: RuntimeScope): Promise; terminate(sessionId: string, approvalRef: string, scope: RuntimeScope): Promise; } export class HermesRuntimeProviderError extends Error { constructor( readonly code: 'capability_unsupported' | 'invalid_request', message: string, ) { super(message); this.name = HermesRuntimeProviderError.name; } } /** * Transitional Hermes adapter. Legacy identifiers and schemas do not cross this * boundary: callers only observe Mosaic AgentRuntimeProvider contracts. */ export class HermesRuntimeProvider implements AgentRuntimeProvider { readonly id = HERMES_PROVIDER_ID; constructor(private readonly transport: HermesRuntimeTransport) {} async capabilities(scope: RuntimeScope): Promise { const legacyCapabilities = await this.transport.capabilities(scope); return { supported: RUNTIME_CAPABILITIES.filter((capability) => legacyCapabilities.includes(capability), ), }; } async health(scope: RuntimeScope): Promise { const health = await this.transport.health(scope); return { status: health.status === 'healthy' || health.status === 'degraded' ? health.status : 'down', checkedAt: new Date().toISOString(), ...(health.detail ? { detail: health.detail } : {}), }; } async listSessions(scope: RuntimeScope): Promise { await this.requireCapability('session.list', scope); return (await this.transport.sessions(scope)).map((session) => this.session(session)); } async getSessionTree(scope: RuntimeScope): Promise { await this.requireCapability('session.tree', scope); const sessions = (await this.transport.sessions(scope)).map((session) => this.session(session)); const nodes = new Map( sessions.map((session) => [session.id, { session, children: [] }]), ); const roots: RuntimeSessionTree[] = []; for (const session of sessions) { const node = nodes.get(session.id)!; const parent = session.parentSessionId ? nodes.get(session.parentSessionId) : undefined; if (parent) parent.children.push(node); else roots.push(node); } return roots; } async *streamSession( sessionId: string, cursor: string | undefined, scope: RuntimeScope, ): AsyncIterable { await this.requireCapability('session.stream', scope); yield* this.transport.stream(sessionId, cursor, scope); } async sendMessage( sessionId: string, message: RuntimeMessage, scope: RuntimeScope, ): Promise { await this.requireCapability('session.send', scope); if (!message.content.trim()) throw new HermesRuntimeProviderError('invalid_request', 'Message content is required'); await this.transport.send(sessionId, message, scope); } async attach( sessionId: string, mode: RuntimeAttachMode, scope: RuntimeScope, ): Promise { await this.requireCapability('session.attach', scope); return this.transport.attach(sessionId, mode, scope); } async detach(attachmentId: string, scope: RuntimeScope): Promise { await this.transport.detach(attachmentId, scope); } async terminate(sessionId: string, approvalRef: string, scope: RuntimeScope): Promise { await this.requireCapability('session.terminate', scope); if (!approvalRef.trim()) throw new HermesRuntimeProviderError('invalid_request', 'Termination approval is required'); await this.transport.terminate(sessionId, approvalRef, scope); } private async requireCapability( capability: RuntimeCapability, scope: RuntimeScope, ): Promise { if (!(await this.capabilities(scope)).supported.includes(capability)) { throw new HermesRuntimeProviderError( 'capability_unsupported', `Hermes does not bridge ${capability}`, ); } } private session(value: HermesLegacySession): RuntimeSession { return { id: value.conversation_id, providerId: this.id, runtimeId: value.agent_id, ...(value.parent_conversation_id ? { parentSessionId: value.parent_conversation_id } : {}), state: state(value.status), createdAt: value.created_at, updatedAt: value.updated_at, }; } } function state(value: string): RuntimeSessionState { return ( ( { running: 'active', waiting: 'idle', starting: 'starting', stopped: 'stopped' } as Record< string, RuntimeSessionState > )[value] ?? 'failed' ); }