fix: use A2UI message context
parent
fef1841fee
commit
406a94bf76
|
|
@ -102,14 +102,12 @@ final class NodeAppModel {
|
||||||
let contextJSON = ClawdisCanvasA2UIAction.compactJSON(userAction["context"])
|
let contextJSON = ClawdisCanvasA2UIAction.compactJSON(userAction["context"])
|
||||||
let sessionKey = "main"
|
let sessionKey = "main"
|
||||||
|
|
||||||
let message = ClawdisCanvasA2UIAction.formatAgentMessage(
|
let messageContext = ClawdisCanvasA2UIAction.AgentMessageContext(
|
||||||
actionName: name,
|
actionName: name,
|
||||||
sessionKey: sessionKey,
|
session: .init(key: sessionKey, surfaceId: surfaceId),
|
||||||
surfaceId: surfaceId,
|
component: .init(id: sourceComponentId, host: host, instanceId: instanceId),
|
||||||
sourceComponentId: sourceComponentId,
|
|
||||||
host: host,
|
|
||||||
instanceId: instanceId,
|
|
||||||
contextJSON: contextJSON)
|
contextJSON: contextJSON)
|
||||||
|
let message = ClawdisCanvasA2UIAction.formatAgentMessage(messageContext)
|
||||||
|
|
||||||
let ok: Bool
|
let ok: Bool
|
||||||
var errorText: String?
|
var errorText: String?
|
||||||
|
|
|
||||||
|
|
@ -654,14 +654,12 @@ private final class CanvasA2UIActionMessageHandler: NSObject, WKScriptMessageHan
|
||||||
let contextJSON = ClawdisCanvasA2UIAction.compactJSON(userAction["context"])
|
let contextJSON = ClawdisCanvasA2UIAction.compactJSON(userAction["context"])
|
||||||
|
|
||||||
// Token-efficient and unambiguous. The agent should treat this as a UI event and (by default) update Canvas.
|
// 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,
|
actionName: name,
|
||||||
sessionKey: self.sessionKey,
|
session: .init(key: self.sessionKey, surfaceId: surfaceId),
|
||||||
surfaceId: surfaceId,
|
component: .init(id: sourceComponentId, host: InstanceIdentity.displayName, instanceId: instanceId),
|
||||||
sourceComponentId: sourceComponentId,
|
|
||||||
host: InstanceIdentity.displayName,
|
|
||||||
instanceId: instanceId,
|
|
||||||
contextJSON: contextJSON)
|
contextJSON: contextJSON)
|
||||||
|
let text = ClawdisCanvasA2UIAction.formatAgentMessage(messageContext)
|
||||||
|
|
||||||
Task { [weak webView] in
|
Task { [weak webView] in
|
||||||
if AppStateStore.shared.connectionMode == .local {
|
if AppStateStore.shared.connectionMode == .local {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,42 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public enum ClawdisCanvasA2UIAction: Sendable {
|
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? {
|
public static func extractActionName(_ userAction: [String: Any]) -> String? {
|
||||||
let keys = ["name", "action"]
|
let keys = ["name", "action"]
|
||||||
for key in keys {
|
for key in keys {
|
||||||
|
|
@ -30,25 +66,16 @@ public enum ClawdisCanvasA2UIAction: Sendable {
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func formatAgentMessage(
|
public static func formatAgentMessage(_ context: AgentMessageContext) -> String {
|
||||||
actionName: String,
|
let ctxSuffix = context.contextJSON.flatMap { $0.isEmpty ? nil : " ctx=\($0)" } ?? ""
|
||||||
sessionKey: String,
|
|
||||||
surfaceId: String,
|
|
||||||
sourceComponentId: String,
|
|
||||||
host: String,
|
|
||||||
instanceId: String,
|
|
||||||
contextJSON: String?)
|
|
||||||
-> String
|
|
||||||
{
|
|
||||||
let ctxSuffix = contextJSON.flatMap { $0.isEmpty ? nil : " ctx=\($0)" } ?? ""
|
|
||||||
return [
|
return [
|
||||||
"CANVAS_A2UI",
|
"CANVAS_A2UI",
|
||||||
"action=\(self.sanitizeTagValue(actionName))",
|
"action=\(self.sanitizeTagValue(context.actionName))",
|
||||||
"session=\(self.sanitizeTagValue(sessionKey))",
|
"session=\(self.sanitizeTagValue(context.session.key))",
|
||||||
"surface=\(self.sanitizeTagValue(surfaceId))",
|
"surface=\(self.sanitizeTagValue(context.session.surfaceId))",
|
||||||
"component=\(self.sanitizeTagValue(sourceComponentId))",
|
"component=\(self.sanitizeTagValue(context.component.id))",
|
||||||
"host=\(self.sanitizeTagValue(host))",
|
"host=\(self.sanitizeTagValue(context.component.host))",
|
||||||
"instance=\(self.sanitizeTagValue(instanceId))\(ctxSuffix)",
|
"instance=\(self.sanitizeTagValue(context.component.instanceId))\(ctxSuffix)",
|
||||||
"default=update_canvas",
|
"default=update_canvas",
|
||||||
].joined(separator: " ")
|
].joined(separator: " ")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,12 @@ public enum ClawdisCanvasA2UIJSONL: Sendable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func validateV0_8(_ items: [ParsedItem]) throws {
|
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 {
|
for item in items {
|
||||||
guard let dict = item.message.value as? [String: AnyCodable] else {
|
guard let dict = item.message.value as? [String: AnyCodable] else {
|
||||||
throw NSError(domain: "A2UI", code: 1, userInfo: [
|
throw NSError(domain: "A2UI", code: 1, userInfo: [
|
||||||
|
|
@ -39,7 +44,8 @@ public enum ClawdisCanvasA2UIJSONL: Sendable {
|
||||||
throw NSError(domain: "A2UI", code: 2, userInfo: [
|
throw NSError(domain: "A2UI", code: 2, userInfo: [
|
||||||
NSLocalizedDescriptionKey: """
|
NSLocalizedDescriptionKey: """
|
||||||
A2UI JSONL line \(item.lineNumber): looks like A2UI v0.9 (`createSurface`).
|
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() {
|
@Test func formatAgentMessageIsTokenEfficientAndUnambiguous() {
|
||||||
let msg = ClawdisCanvasA2UIAction.formatAgentMessage(
|
let messageContext = ClawdisCanvasA2UIAction.AgentMessageContext(
|
||||||
actionName: "Get Weather",
|
actionName: "Get Weather",
|
||||||
sessionKey: "main",
|
session: .init(key: "main", surfaceId: "main"),
|
||||||
surfaceId: "main",
|
component: .init(id: "btnWeather", host: "Peter’s iPad", instanceId: "ipad16,6"),
|
||||||
sourceComponentId: "btnWeather",
|
|
||||||
host: "Peter’s iPad",
|
|
||||||
instanceId: "ipad16,6",
|
|
||||||
contextJSON: "{\"city\":\"Vienna\"}")
|
contextJSON: "{\"city\":\"Vienna\"}")
|
||||||
|
let msg = ClawdisCanvasA2UIAction.formatAgentMessage(messageContext)
|
||||||
|
|
||||||
#expect(msg.contains("CANVAS_A2UI "))
|
#expect(msg.contains("CANVAS_A2UI "))
|
||||||
#expect(msg.contains("action=Get_Weather"))
|
#expect(msg.contains("action=Get_Weather"))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue