Canvas: fix A2UI v0.8 rendering
This commit is contained in:
@@ -247,9 +247,12 @@ enum ControlRequestHandler {
|
||||
case .reset:
|
||||
js = """
|
||||
(() => {
|
||||
if (!globalThis.clawdisA2UI) { return "missing clawdisA2UI"; }
|
||||
globalThis.clawdisA2UI.reset();
|
||||
return "ok";
|
||||
try {
|
||||
if (!globalThis.clawdisA2UI) { return JSON.stringify({ ok: false, error: "missing clawdisA2UI" }); }
|
||||
return JSON.stringify(globalThis.clawdisA2UI.reset());
|
||||
} catch (e) {
|
||||
return JSON.stringify({ ok: false, error: String(e?.message ?? e), stack: e?.stack });
|
||||
}
|
||||
})()
|
||||
"""
|
||||
|
||||
@@ -257,43 +260,100 @@ enum ControlRequestHandler {
|
||||
guard let jsonl, !jsonl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
return Response(ok: false, message: "missing jsonl")
|
||||
}
|
||||
let messages: [Any]
|
||||
let items: [ParsedJSONLItem]
|
||||
do {
|
||||
messages = try Self.parseJSONL(jsonl)
|
||||
items = try Self.parseJSONL(jsonl)
|
||||
} catch {
|
||||
return Response(ok: false, message: "invalid jsonl: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
do {
|
||||
try Self.validateA2UIV0_8(items)
|
||||
} catch {
|
||||
return Response(ok: false, message: error.localizedDescription)
|
||||
}
|
||||
|
||||
let messages = items.map(\.value)
|
||||
let data = try JSONSerialization.data(withJSONObject: messages, options: [])
|
||||
let json = String(data: data, encoding: .utf8) ?? "[]"
|
||||
js = """
|
||||
(() => {
|
||||
if (!globalThis.clawdisA2UI) { return "missing clawdisA2UI"; }
|
||||
const messages = \(json);
|
||||
globalThis.clawdisA2UI.applyMessages(messages);
|
||||
return "ok";
|
||||
try {
|
||||
if (!globalThis.clawdisA2UI) { return JSON.stringify({ ok: false, error: "missing clawdisA2UI" }); }
|
||||
const messages = \(json);
|
||||
return JSON.stringify(globalThis.clawdisA2UI.applyMessages(messages));
|
||||
} catch (e) {
|
||||
return JSON.stringify({ ok: false, error: String(e?.message ?? e), stack: e?.stack });
|
||||
}
|
||||
})()
|
||||
"""
|
||||
}
|
||||
|
||||
let result = try await CanvasManager.shared.eval(sessionKey: session, javaScript: js)
|
||||
return Response(ok: true, payload: Data(result.utf8))
|
||||
|
||||
let payload = Data(result.utf8)
|
||||
if let obj = try? JSONSerialization.jsonObject(with: payload, options: []) as? [String: Any],
|
||||
let ok = obj["ok"] as? Bool
|
||||
{
|
||||
let error = obj["error"] as? String
|
||||
return Response(ok: ok, message: ok ? "" : (error ?? "A2UI error"), payload: payload)
|
||||
}
|
||||
|
||||
return Response(ok: true, payload: payload)
|
||||
} catch {
|
||||
return Response(ok: false, message: error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
private static func parseJSONL(_ text: String) throws -> [Any] {
|
||||
var out: [Any] = []
|
||||
for rawLine in text.split(whereSeparator: \.isNewline) {
|
||||
let line = rawLine.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
private struct ParsedJSONLItem {
|
||||
let lineNumber: Int
|
||||
let value: Any
|
||||
}
|
||||
|
||||
private static func parseJSONL(_ text: String) throws -> [ParsedJSONLItem] {
|
||||
var out: [ParsedJSONLItem] = []
|
||||
var lineNumber = 0
|
||||
for rawLine in text.split(omittingEmptySubsequences: false, whereSeparator: \.isNewline) {
|
||||
lineNumber += 1
|
||||
let line = String(rawLine).trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if line.isEmpty { continue }
|
||||
let data = Data(line.utf8)
|
||||
let obj = try JSONSerialization.jsonObject(with: data, options: [])
|
||||
out.append(obj)
|
||||
out.append(ParsedJSONLItem(lineNumber: lineNumber, value: obj))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
private static func validateA2UIV0_8(_ items: [ParsedJSONLItem]) throws {
|
||||
let allowed = Set(["beginRendering", "surfaceUpdate", "dataModelUpdate", "deleteSurface"])
|
||||
for item in items {
|
||||
guard let dict = item.value as? [String: Any] else {
|
||||
throw NSError(domain: "A2UI", code: 1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "A2UI JSONL line \(item.lineNumber): expected a JSON object",
|
||||
])
|
||||
}
|
||||
|
||||
if dict.keys.contains("createSurface") {
|
||||
throw NSError(domain: "A2UI", code: 2, userInfo: [
|
||||
NSLocalizedDescriptionKey: """
|
||||
A2UI JSONL line \(item.lineNumber): looks like A2UI v0.9 (`createSurface`).
|
||||
Canvas currently supports A2UI v0.8 server→client messages (`beginRendering`, `surfaceUpdate`, `dataModelUpdate`, `deleteSurface`).
|
||||
""",
|
||||
])
|
||||
}
|
||||
|
||||
let matched = dict.keys.filter { allowed.contains($0) }
|
||||
if matched.count != 1 {
|
||||
let found = dict.keys.sorted().joined(separator: ", ")
|
||||
throw NSError(domain: "A2UI", code: 3, userInfo: [
|
||||
NSLocalizedDescriptionKey: """
|
||||
A2UI JSONL line \(item.lineNumber): expected exactly one of \(allowed.sorted().joined(separator: ", ")); found: \(found)
|
||||
""",
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func waitForCanvasA2UI(session: String, timeoutMs: Int) async -> Bool {
|
||||
let clock = ContinuousClock()
|
||||
let deadline = clock.now.advanced(by: .milliseconds(timeoutMs))
|
||||
|
||||
Reference in New Issue
Block a user