const A2UI_ACTION_KEYS = [ "beginRendering", "surfaceUpdate", "dataModelUpdate", "deleteSurface", "createSurface", ] as const; export type A2UIVersion = "v0.8" | "v0.9"; export function buildA2UITextJsonl(text: string) { const surfaceId = "main"; const rootId = "root"; const textId = "text"; const payloads = [ { surfaceUpdate: { surfaceId, components: [ { id: rootId, component: { Column: { children: { explicitList: [textId] } } }, }, { id: textId, component: { Text: { text: { literalString: text }, usageHint: "body" }, }, }, ], }, }, { beginRendering: { surfaceId, root: rootId } }, ]; return payloads.map((payload) => JSON.stringify(payload)).join("\n"); } export function validateA2UIJsonl(jsonl: string) { const lines = jsonl.split(/\r?\n/); const errors: string[] = []; let sawV08 = false; let sawV09 = false; let messageCount = 0; lines.forEach((line, idx) => { const trimmed = line.trim(); if (!trimmed) return; messageCount += 1; let obj: unknown; try { obj = JSON.parse(trimmed) as unknown; } catch (err) { errors.push(`line ${idx + 1}: ${String(err)}`); return; } if (!obj || typeof obj !== "object" || Array.isArray(obj)) { errors.push(`line ${idx + 1}: expected JSON object`); return; } const record = obj as Record; const actionKeys = A2UI_ACTION_KEYS.filter((key) => key in record); if (actionKeys.length !== 1) { errors.push( `line ${idx + 1}: expected exactly one action key (${A2UI_ACTION_KEYS.join( ", ", )})`, ); return; } if (actionKeys[0] === "createSurface") { sawV09 = true; } else { sawV08 = true; } }); if (messageCount === 0) { errors.push("no JSONL messages found"); } if (sawV08 && sawV09) { errors.push("mixed A2UI v0.8 and v0.9 messages in one file"); } if (errors.length > 0) { throw new Error(`Invalid A2UI JSONL:\n- ${errors.join("\n- ")}`); } const version: A2UIVersion = sawV09 ? "v0.9" : "v0.8"; return { version, messageCount }; }