/** DTOs for the internal bridge API consumed by agent-comms host daemons. */ export interface BridgeMessageDto { room_id: string; /** Agent slug (localpart suffix), e.g. "pi0-web1". */ agent: string; body: string; thread_root?: string; msgtype?: string; /** Optional protocol payload merged into content (e.g. org.uscllc.agent). */ extra_content?: Record; } export interface BridgeTypingDto { room_id: string; agent: string; typing: boolean; } const AGENT_SLUG_RE = /^[a-z0-9][a-z0-9_.-]*$/; const assertAgentSlug = (agent: unknown): void => { if (typeof agent !== 'string' || !AGENT_SLUG_RE.test(agent.toLowerCase())) { throw new Error('agent must match [a-z0-9][a-z0-9_.-]*'); } }; export function validateBridgeMessage(input: unknown): asserts input is BridgeMessageDto { const o = input as Partial | null | undefined; if (!o || typeof o !== 'object') throw new Error('payload must be an object'); if (typeof o.room_id !== 'string' || !o.room_id.startsWith('!')) throw new Error('room_id must be a Matrix room id'); assertAgentSlug(o.agent); if (typeof o.body !== 'string') throw new Error('body must be a string'); if (o.thread_root !== undefined && typeof o.thread_root !== 'string') throw new Error('thread_root must be a string'); if ( o.extra_content !== undefined && (typeof o.extra_content !== 'object' || o.extra_content === null) ) { throw new Error('extra_content must be an object'); } } export function validateBridgeTyping(input: unknown): asserts input is BridgeTypingDto { const o = input as Partial | null | undefined; if (!o || typeof o !== 'object') throw new Error('payload must be an object'); if (typeof o.room_id !== 'string' || !o.room_id.startsWith('!')) throw new Error('room_id must be a Matrix room id'); assertAgentSlug(o.agent); if (typeof o.typing !== 'boolean') throw new Error('typing must be a boolean'); } export interface ProvisionRoomDto { name: string; alias?: string; topic?: string; invite?: string[]; space_id?: string; } export function validateProvisionRoom(input: unknown): asserts input is ProvisionRoomDto { const o = input as Partial | null | undefined; if (!o || typeof o !== 'object') throw new Error('payload must be an object'); if (typeof o.name !== 'string' || o.name.length === 0) throw new Error('name is required'); if (o.alias !== undefined && (!/^[a-z0-9_.-]+$/.test(o.alias) || o.alias.length > 200)) { throw new Error('alias must match [a-z0-9_.-]+ (max 200 chars)'); } if (o.invite !== undefined) { if ( !Array.isArray(o.invite) || o.invite.some((u) => typeof u !== 'string' || !u.startsWith('@')) ) { throw new Error('invite must be a list of Matrix user ids'); } if (o.invite.length > 50) { throw new Error('invite list exceeds maximum of 50'); } } if (o.space_id !== undefined && (typeof o.space_id !== 'string' || !o.space_id.startsWith('!'))) { throw new Error('space_id must be a Matrix room id'); } }