fix: use A2UI message context
This commit is contained in:
@@ -102,14 +102,12 @@ final class NodeAppModel {
|
||||
let contextJSON = ClawdisCanvasA2UIAction.compactJSON(userAction["context"])
|
||||
let sessionKey = "main"
|
||||
|
||||
let message = ClawdisCanvasA2UIAction.formatAgentMessage(
|
||||
let messageContext = ClawdisCanvasA2UIAction.AgentMessageContext(
|
||||
actionName: name,
|
||||
sessionKey: sessionKey,
|
||||
surfaceId: surfaceId,
|
||||
sourceComponentId: sourceComponentId,
|
||||
host: host,
|
||||
instanceId: instanceId,
|
||||
session: .init(key: sessionKey, surfaceId: surfaceId),
|
||||
component: .init(id: sourceComponentId, host: host, instanceId: instanceId),
|
||||
contextJSON: contextJSON)
|
||||
let message = ClawdisCanvasA2UIAction.formatAgentMessage(messageContext)
|
||||
|
||||
let ok: Bool
|
||||
var errorText: String?
|
||||
|
||||
@@ -654,14 +654,12 @@ private final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHan
|
||||
let contextJSON = ClawdisCanvasA2UIAction.compactJSON(userAction["context"])
|
||||
|
||||
// Token-efficient and unambiguous. The agent should treat this as a UI event and (by default) update Canvas.
|
||||
let text = ClawdisCanvasA2UIAction.formatAgentMessage(
|
||||
let messageContext = ClawdisCanvasA2UIAction.AgentMessageContext(
|
||||
actionName: name,
|
||||
sessionKey: self.sessionKey,
|
||||
surfaceId: surfaceId,
|
||||
sourceComponentId: sourceComponentId,
|
||||
host: InstanceIdentity.displayName,
|
||||
instanceId: instanceId,
|
||||
session: .init(key: self.sessionKey, surfaceId: surfaceId),
|
||||
component: .init(id: sourceComponentId, host: InstanceIdentity.displayName, instanceId: instanceId),
|
||||
contextJSON: contextJSON)
|
||||
let text = ClawdisCanvasA2UIAction.formatAgentMessage(messageContext)
|
||||
|
||||
Task { [weak webView] in
|
||||
if AppStateStore.shared.connectionMode == .local {
|
||||
|
||||
@@ -1,6 +1,42 @@
|
||||
import Foundation
|
||||
|
||||
public enum ClawdisCanvasA2UIAction: Sendable {
|
||||
public struct AgentMessageContext: Sendable {
|
||||
public struct Session: Sendable {
|
||||
public var key: String
|
||||
public var surfaceId: String
|
||||
|
||||
public init(key: String, surfaceId: String) {
|
||||
self.key = key
|
||||
self.surfaceId = surfaceId
|
||||
}
|
||||
}
|
||||
|
||||
public struct Component: Sendable {
|
||||
public var id: String
|
||||
public var host: String
|
||||
public var instanceId: String
|
||||
|
||||
public init(id: String, host: String, instanceId: String) {
|
||||
self.id = id
|
||||
self.host = host
|
||||
self.instanceId = instanceId
|
||||
}
|
||||
}
|
||||
|
||||
public var actionName: String
|
||||
public var session: Session
|
||||
public var component: Component
|
||||
public var contextJSON: String?
|
||||
|
||||
public init(actionName: String, session: Session, component: Component, contextJSON: String?) {
|
||||
self.actionName = actionName
|
||||
self.session = session
|
||||
self.component = component
|
||||
self.contextJSON = contextJSON
|
||||
}
|
||||
}
|
||||
|
||||
public static func extractActionName(_ userAction: [String: Any]) -> String? {
|
||||
let keys = ["name", "action"]
|
||||
for key in keys {
|
||||
@@ -30,25 +66,16 @@ public enum ClawdisCanvasA2UIAction: Sendable {
|
||||
return str
|
||||
}
|
||||
|
||||
public static func formatAgentMessage(
|
||||
actionName: String,
|
||||
sessionKey: String,
|
||||
surfaceId: String,
|
||||
sourceComponentId: String,
|
||||
host: String,
|
||||
instanceId: String,
|
||||
contextJSON: String?)
|
||||
-> String
|
||||
{
|
||||
let ctxSuffix = contextJSON.flatMap { $0.isEmpty ? nil : " ctx=\($0)" } ?? ""
|
||||
public static func formatAgentMessage(_ context: AgentMessageContext) -> String {
|
||||
let ctxSuffix = context.contextJSON.flatMap { $0.isEmpty ? nil : " ctx=\($0)" } ?? ""
|
||||
return [
|
||||
"CANVAS_A2UI",
|
||||
"action=\(self.sanitizeTagValue(actionName))",
|
||||
"session=\(self.sanitizeTagValue(sessionKey))",
|
||||
"surface=\(self.sanitizeTagValue(surfaceId))",
|
||||
"component=\(self.sanitizeTagValue(sourceComponentId))",
|
||||
"host=\(self.sanitizeTagValue(host))",
|
||||
"instance=\(self.sanitizeTagValue(instanceId))\(ctxSuffix)",
|
||||
"action=\(self.sanitizeTagValue(context.actionName))",
|
||||
"session=\(self.sanitizeTagValue(context.session.key))",
|
||||
"surface=\(self.sanitizeTagValue(context.session.surfaceId))",
|
||||
"component=\(self.sanitizeTagValue(context.component.id))",
|
||||
"host=\(self.sanitizeTagValue(context.component.host))",
|
||||
"instance=\(self.sanitizeTagValue(context.component.instanceId))\(ctxSuffix)",
|
||||
"default=update_canvas",
|
||||
].joined(separator: " ")
|
||||
}
|
||||
|
||||
@@ -27,7 +27,12 @@ public enum ClawdisCanvasA2UIJSONL: Sendable {
|
||||
}
|
||||
|
||||
public static func validateV0_8(_ items: [ParsedItem]) throws {
|
||||
let allowed = Set(["beginRendering", "surfaceUpdate", "dataModelUpdate", "deleteSurface"])
|
||||
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: [
|
||||
@@ -39,7 +44,8 @@ public enum ClawdisCanvasA2UIJSONL: Sendable {
|
||||
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`).
|
||||
Canvas currently supports A2UI v0.8 server→client messages
|
||||
(`beginRendering`, `surfaceUpdate`, `dataModelUpdate`, `deleteSurface`).
|
||||
""",
|
||||
])
|
||||
}
|
||||
|
||||
@@ -17,14 +17,12 @@ import Testing
|
||||
}
|
||||
|
||||
@Test func formatAgentMessageIsTokenEfficientAndUnambiguous() {
|
||||
let msg = ClawdisCanvasA2UIAction.formatAgentMessage(
|
||||
let messageContext = ClawdisCanvasA2UIAction.AgentMessageContext(
|
||||
actionName: "Get Weather",
|
||||
sessionKey: "main",
|
||||
surfaceId: "main",
|
||||
sourceComponentId: "btnWeather",
|
||||
host: "Peter’s iPad",
|
||||
instanceId: "ipad16,6",
|
||||
session: .init(key: "main", surfaceId: "main"),
|
||||
component: .init(id: "btnWeather", host: "Peter’s iPad", instanceId: "ipad16,6"),
|
||||
contextJSON: "{\"city\":\"Vienna\"}")
|
||||
let msg = ClawdisCanvasA2UIAction.formatAgentMessage(messageContext)
|
||||
|
||||
#expect(msg.contains("CANVAS_A2UI "))
|
||||
#expect(msg.contains("action=Get_Weather"))
|
||||
|
||||
Reference in New Issue
Block a user