/* * Platform-neutral LineUp presentation kernel. * * It converts untrusted transport payloads into a small, validated set of * ConversationItems. UI hosts register trusted renderers for those items; * neither a renderer nor a UI Surface needs to parse IM payloads directly. */ (function exposeLineUpKernel(root, factory) { const api = factory(); if (typeof module === 'object' && module.exports) module.exports = api; root.LineUp = Object.assign(root.LineUp || {}, api); })(typeof globalThis === 'undefined' ? window : globalThis, function createKernel() { const PROTOCOL_VERSION = 1; const MAX_PAYLOAD_BYTES = 192 * 1024; const UI_TYPES = new Set(['lineup.v1.ui.open', 'lineup.v1.ui.patch', 'lineup.v1.ui.close']); function isObject(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); } function byteLength(value) { return new TextEncoder().encode(String(value)).length; } function text(value) { return typeof value === 'string' ? value : ''; } function item(kind, data, envelope) { return Object.freeze({ kind, data: Object.freeze(data), envelope: envelope || null, receivedAt: Date.now() }); } class ConversationItemFactory { decode(rawPayload) { const raw = String(rawPayload ?? ''); if (byteLength(raw) > MAX_PAYLOAD_BYTES) return item('protocol-fallback', { reason: 'payload_too_large' }); let value; try { value = JSON.parse(raw); } catch { return item('markdown', { markdown: raw, source: 'plain' }); } if (!isObject(value) || typeof value.type !== 'string') return item('markdown', { markdown: raw, source: 'plain' }); if (value.v !== PROTOCOL_VERSION || !value.type.startsWith('lineup.v1.')) return this.legacy(value); return this.envelope(value); } envelope(envelope) { const payload = isObject(envelope.payload) ? envelope.payload : {}; switch (envelope.type) { case 'lineup.v1.text': return item('markdown', { markdown: text(payload.text), source: 'agent' }, envelope); case 'lineup.v1.agent.status': return item('agent-status', { status: text(payload.status) || 'unknown', detail: text(payload.detail) }, envelope); case 'lineup.v1.agent.progress': return item('progress', { title: text(payload.title) || '处理中', percent: Number(payload.percent) || 0, status: text(payload.status) }, envelope); case 'lineup.v1.error': return item('agent-error', { code: text(payload.code), message: text(payload.message) || '未知错误' }, envelope); case 'lineup.v1.app.call': return item('app-call', payload, envelope); default: if (UI_TYPES.has(envelope.type)) return item(envelope.type.slice('lineup.v1.'.length), payload, envelope); return item('protocol-fallback', { reason: 'unsupported_type', type: envelope.type }, envelope); } } legacy(value) { if (value.v === undefined && typeof value.type === 'string') return item('legacy-card', value); return item('protocol-fallback', { reason: 'unsupported_version', version: value.v, type: value.type }); } } class RendererRegistry { constructor() { this.renderers = new Map(); } register(kind, renderer) { if (typeof kind !== 'string' || typeof renderer !== 'function') throw new TypeError('Renderer registration requires a kind and a function'); this.renderers.set(kind, renderer); return this; } render(conversationItem, context) { const renderer = this.renderers.get(conversationItem.kind) || this.renderers.get('protocol-fallback'); if (!renderer) throw new Error(`No renderer registered for ${conversationItem.kind}`); return renderer(conversationItem, context); } } return { PROTOCOL_VERSION, MAX_PAYLOAD_BYTES, ConversationItemFactory, RendererRegistry, isObject }; });