82 lines
3.1 KiB
Swift
82 lines
3.1 KiB
Swift
import Foundation
|
|
|
|
public enum ClawdbotCanvasA2UIJSONL: Sendable {
|
|
public struct ParsedItem: Sendable {
|
|
public var lineNumber: Int
|
|
public var message: AnyCodable
|
|
|
|
public init(lineNumber: Int, message: AnyCodable) {
|
|
self.lineNumber = lineNumber
|
|
self.message = message
|
|
}
|
|
}
|
|
|
|
public static func parse(_ text: String) throws -> [ParsedItem] {
|
|
var out: [ParsedItem] = []
|
|
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 decoded = try JSONDecoder().decode(AnyCodable.self, from: data)
|
|
out.append(ParsedItem(lineNumber: lineNumber, message: decoded))
|
|
}
|
|
return out
|
|
}
|
|
|
|
public static func validateV0_8(_ items: [ParsedItem]) throws {
|
|
let allowed = Set([
|
|
"beginRendering",
|
|
"surfaceUpdate",
|
|
"dataModelUpdate",
|
|
"deleteSurface",
|
|
])
|
|
for item in items {
|
|
guard let dict = item.message.value as? [String: AnyCodable] 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)
|
|
""",
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
public static func decodeMessagesFromJSONL(_ text: String) throws -> [AnyCodable] {
|
|
let items = try self.parse(text)
|
|
try self.validateV0_8(items)
|
|
return items.map(\.message)
|
|
}
|
|
|
|
public static func encodeMessagesJSONArray(_ messages: [AnyCodable]) throws -> String {
|
|
let data = try JSONEncoder().encode(messages)
|
|
guard let json = String(data: data, encoding: .utf8) else {
|
|
throw NSError(domain: "A2UI", code: 10, userInfo: [
|
|
NSLocalizedDescriptionKey: "Failed to encode messages payload as UTF-8",
|
|
])
|
|
}
|
|
return json
|
|
}
|
|
}
|