///|
let mcp_protocol_version : String = "2024-11-05"
///|
priv enum ToolInvocation {
ReadSemantics(@core.SemanticsGeneration?)
PerformAction(@agent.AgentActionRequest)
DiagnosticsReadState
DiagnosticsReadPaint
DiagnosticsDispatchEvent(@core.AppEvent)
DiagnosticsDispatchCommand(@core.CommandIntent)
}
///|
fn[Host : @agent.AgentHost] semantic_tool_registry(
_host : Host,
) -> Array[ToolSpec[Host]] {
[
{
name: "read_semantics",
description: "Read an atomically committed semantics snapshot or delta.",
input: read_semantics_input_codec(),
output: {
schema: tool_output_schema(semantics_read_value_schema()),
dispatch: dispatch_read_semantics_tool,
},
},
{
name: "perform_action",
description: "Perform a typed action on a committed semantics target.",
input: perform_action_input_codec(),
output: {
schema: tool_output_schema(action_receipt_value_schema()),
dispatch: dispatch_perform_action_tool,
},
},
]
}
///|
fn[Host : @agent.AgentDiagnosticsHost] diagnostics_tool_registry(
_host : Host,
) -> Array[ToolSpec[Host]] {
[
{
name: "diagnostics_read_state",
description: "Read aggregate runtime lifecycle counters.",
input: {
schema: object_schema(Json::empty_object()),
decode: decode_diagnostics_read_state_invocation,
},
output: {
schema: tool_output_schema(({ "type": "object" } : Json)),
dispatch: dispatch_diagnostics_read_state_tool,
},
},
{
name: "diagnostics_read_paint",
description: "Read the current paint command summary.",
input: {
schema: object_schema(Json::empty_object()),
decode: decode_diagnostics_read_paint_invocation,
},
output: {
schema: tool_output_schema(({ "type": "object" } : Json)),
dispatch: dispatch_diagnostics_read_paint_tool,
},
},
{
name: "diagnostics_dispatch_event",
description: "Inject a raw diagnostic input event.",
input: {
schema: object_schema(({ "event": { "type": "object" } } : Json), required=[
"event",
]),
decode: decode_diagnostics_dispatch_event_invocation,
},
output: {
schema: tool_output_schema(({ "type": "object" } : Json)),
dispatch: dispatch_diagnostics_event_tool,
},
},
{
name: "diagnostics_dispatch_command",
description: "Dispatch a global diagnostic command intent.",
input: {
schema: object_schema(({ "intent": { "type": "string" } } : Json), required=[
"intent",
]),
decode: decode_diagnostics_dispatch_command_invocation,
},
output: {
schema: tool_output_schema(({ "type": "object" } : Json)),
dispatch: dispatch_diagnostics_command_tool,
},
},
]
}
///|
fn[Host : @agent.AgentHost + @agent.AgentDiagnosticsHost] full_tool_registry(
host : Host,
) -> Array[ToolSpec[Host]] {
let tools = semantic_tool_registry(host)
tools.append(diagnostics_tool_registry(host))
tools
}
///|
fn[Host] ToolSpec::descriptor(self : ToolSpec[Host]) -> Json {
(
{
"name": self.name,
"description": self.description,
"inputSchema": self.input.schema,
"outputSchema": self.output.schema,
} : Json)
}
///|
fn[Host] ToolSpec::invoke(
self : ToolSpec[Host],
host : Host,
id : Json,
arguments : Map[String, Json],
) -> Json {
match (self.input.decode)(arguments) {
Err(message) => tool_failure(id, "invalid_arguments", message)
Ok(invocation) => (self.output.dispatch)(host, id, invocation)
}
}
///|
fn[Host] find_tool(
name : String,
registry : Array[ToolSpec[Host]],
) -> ToolSpec[Host]? {
for spec in registry {
if spec.name == name {
return Some(spec)
}
}
None
}
///|
fn json_null() -> Json {
(None : String?).to_json()
}
///|
pub fn rpc_error(id : Json, code : Int, message : String) -> Json {
(
{
"jsonrpc": "2.0",
"id": id,
"error": { "code": code, "message": message },
} : Json)
}
///|
pub fn rpc_error_no_id(code : Int, message : String) -> Json {
rpc_error(json_null(), code, message)
}
///|
fn rpc_result(id : Json, result : Json) -> Json {
({ "jsonrpc": "2.0", "id": id, "result": result } : Json)
}
///|
fn rpc_id(request : Json) -> Json {
match request {
Object(fields) => fields.get("id").unwrap_or(json_null())
_ => json_null()
}
}
///|
fn rpc_method(request : Json) -> String? {
match request {
Object(fields) =>
match fields.get("jsonrpc") {
Some(String("2.0")) =>
match fields.get("method") {
Some(String(method_name)) => Some(method_name)
_ => None
}
_ => None
}
_ => None
}
}
///|
fn tool_envelope_success(value : Json) -> Json {
({ "ok": true, "value": value } : Json)
}
///|
fn tool_envelope_error(code : String, message : String, details : Json) -> Json {
(
{
"ok": false,
"error": { "code": code, "message": message, "details": details },
} : Json)
}
///|
fn tool_response(id : Json, envelope : Json, is_error : Bool) -> Json {
let result : Json = {
"content": [{ "type": "text", "text": envelope.stringify() }],
"structuredContent": envelope,
"isError": is_error,
}
rpc_result(id, result)
}
///|
fn tool_success(id : Json, value : Json) -> Json {
tool_response(id, tool_envelope_success(value), false)
}
///|
fn tool_failure(
id : Json,
code : String,
message : String,
details? : Json = Json::empty_object(),
) -> Json {
tool_response(id, tool_envelope_error(code, message, details), true)
}
///|
fn semantics_action_kind_code(kind : @core.SemanticsActionKind) -> String {
match kind {
Activate => "activate"
Focus => "focus"
SetText => "set_text"
Submit => "submit"
Scroll => "scroll"
Select => "select"
Expand => "expand"
Collapse => "collapse"
Dismiss => "dismiss"
Increment => "increment"
Decrement => "decrement"
SetNumericValue => "set_numeric_value"
SetSelection => "set_selection"
ShowMenu => "show_menu"
}
}
///|
fn semantics_target_json(target : @core.SemanticsTarget) -> Json {
match target {
BySemanticId(id) => ({ "kind": "semantic_id", "id": id } : Json)
ByNodeId(id) => ({ "kind": "node_id", "id": id } : Json)
ByPath(segments) =>
(
{ "kind": "path", "path": segments.map(semantics_path_segment_json) } :
Json)
}
}
///|
fn semantics_path_segment_json(segment : @core.SemanticsPathSegment) -> Json {
(
{
"index": segment.index,
"role": match segment.role {
Some(role) => semantics_role_code(role)
None => json_null()
},
"label": optional_string_json(segment.label),
"value": optional_string_json(segment.value),
"semantic_id": optional_semantic_id_json(segment.semantic_id),
} : Json)
}
///|
fn optional_string_json(value : String?) -> Json {
match value {
Some(text) => text.to_json()
None => json_null()
}
}
///|
fn agent_action_error_details(error : @agent.AgentActionError) -> Json {
match error {
HostClosed | ReentrantDispatch => Json::empty_object()
StaleGeneration(expected, current) =>
({ "expected": expected, "current": current } : Json)
TargetNotFound(target) | TargetDisabled(target) =>
({ "target": semantics_target_json(target) } : Json)
TargetAmbiguous(id) => ({ "semantic_id": id } : Json)
ActionUnavailable(target, kind) | HandlerRejected(target, kind) =>
(
{
"target": semantics_target_json(target),
"action": semantics_action_kind_code(kind),
} : Json)
}
}
///|
fn initialize_result() -> Json {
(
{
"protocolVersion": mcp_protocol_version,
"capabilities": { "tools": Json::empty_object() },
"serverInfo": { "name": "moui-agent", "version": "0.1.10" },
} : Json)
}
///|
fn[Host] list_tools(id : Json, registry : Array[ToolSpec[Host]]) -> Json {
let tools : Array[Json] = registry.map(spec => spec.descriptor())
rpc_result(id, ({ "tools": tools } : Json))
}
///|
pub fn[Host : @agent.AgentHost] route(host : Host, request : Json) -> Json {
let id = rpc_id(request)
let registry = semantic_tool_registry(host)
match rpc_method(request) {
None => rpc_error(id, -32600, "Invalid Request: missing method")
Some("initialize") => rpc_result(id, initialize_result())
Some("tools/list") => list_tools(id, registry)
Some("tools/call") => route_tool_call(host, id, request, registry)
Some(method_name) =>
rpc_error(id, -32601, "Method not found: \{method_name}")
}
}
///|
pub fn[Host : @agent.AgentHost + @agent.AgentDiagnosticsHost] route_with_diagnostics(
host : Host,
request : Json,
) -> Json {
let id = rpc_id(request)
let registry = full_tool_registry(host)
match rpc_method(request) {
None => rpc_error(id, -32600, "Invalid Request: missing method")
Some("initialize") => rpc_result(id, initialize_result())
Some("tools/list") => list_tools(id, registry)
Some("tools/call") => route_tool_call(host, id, request, registry)
Some(method_name) =>
rpc_error(id, -32601, "Method not found: \{method_name}")
}
}
///|
fn call_envelope(
id : Json,
request : Json,
) -> Result[(String, Map[String, Json]), Json] {
guard request is Object(fields) else {
return Err(rpc_error(id, -32600, "Invalid Request"))
}
guard fields.get("params") is Some(Object(params)) else {
return Err(rpc_error(id, -32602, "Invalid tools/call params object"))
}
guard params.get("name") is Some(String(name)) else {
return Err(rpc_error(id, -32602, "Invalid tools/call tool name"))
}
let arguments = match params.get("arguments") {
None => Map([])
Some(Object(arguments)) => arguments
_ =>
return Err(rpc_error(id, -32602, "Invalid tools/call arguments object"))
}
Ok((name, arguments))
}
///|
fn[Host] route_tool_call(
host : Host,
id : Json,
request : Json,
registry : Array[ToolSpec[Host]],
) -> Json {
match call_envelope(id, request) {
Err(response) => response
Ok((name, arguments)) =>
match find_tool(name, registry) {
None => tool_failure(id, "unknown_tool", "Unknown tool: \{name}")
Some(spec) => spec.invoke(host, id, arguments)
}
}
}
///|
fn[Host : @agent.AgentHost] dispatch_read_semantics_tool(
host : Host,
id : Json,
invocation : ToolInvocation,
) -> Json {
match invocation {
ReadSemantics(since) =>
match host.read_semantics(since) {
Ok(read) => tool_success(id, agent_semantics_read_json(read))
Err(error) =>
tool_failure(
id,
error.code(),
"Agent host could not read committed semantics.",
)
}
_ => tool_registry_mismatch(id)
}
}
///|
fn[Host : @agent.AgentHost] dispatch_perform_action_tool(
host : Host,
id : Json,
invocation : ToolInvocation,
) -> Json {
match invocation {
PerformAction(action_request) =>
match host.perform_action(action_request) {
Ok(receipt) => tool_success(id, receipt.to_json())
Err(error) =>
tool_failure(
id,
error.code(),
"Semantic action was rejected.",
details=agent_action_error_details(error),
)
}
_ => tool_registry_mismatch(id)
}
}
///|
fn[Host : @agent.AgentDiagnosticsHost] dispatch_diagnostics_read_state_tool(
host : Host,
id : Json,
invocation : ToolInvocation,
) -> Json {
match invocation {
DiagnosticsReadState =>
match host.runtime_state() {
Ok(value) => tool_success(id, value.to_json())
Err(error) =>
tool_failure(id, error.code(), "Diagnostics host is closed.")
}
_ => tool_registry_mismatch(id)
}
}
///|
fn[Host : @agent.AgentDiagnosticsHost] dispatch_diagnostics_read_paint_tool(
host : Host,
id : Json,
invocation : ToolInvocation,
) -> Json {
match invocation {
DiagnosticsReadPaint =>
match host.paint_summary() {
Ok(value) => tool_success(id, value.to_json())
Err(error) =>
tool_failure(id, error.code(), "Diagnostics host is closed.")
}
_ => tool_registry_mismatch(id)
}
}
///|
fn[Host : @agent.AgentDiagnosticsHost] dispatch_diagnostics_event_tool(
host : Host,
id : Json,
invocation : ToolInvocation,
) -> Json {
match invocation {
DiagnosticsDispatchEvent(event) =>
match host.dispatch_event(event) {
Ok(value) => tool_success(id, value.to_json())
Err(error) =>
tool_failure(id, error.code(), "Diagnostics host is closed.")
}
_ => tool_registry_mismatch(id)
}
}
///|
fn[Host : @agent.AgentDiagnosticsHost] dispatch_diagnostics_command_tool(
host : Host,
id : Json,
invocation : ToolInvocation,
) -> Json {
match invocation {
DiagnosticsDispatchCommand(intent) =>
match host.dispatch_command(intent) {
Ok(value) => tool_success(id, value.to_json())
Err(error) =>
tool_failure(id, error.code(), "Diagnostics host is closed.")
}
_ => tool_registry_mismatch(id)
}
}
///|
fn tool_registry_mismatch(id : Json) -> Json {
tool_failure(
id, "internal_tool_registry_error", "Tool registry decoder and dispatcher do not match.",
)
}
///|
fn fields_are_exact(
fields : Map[String, Json],
allowed : Array[String],
) -> Bool {
for name, _ in fields {
if !allowed.contains(name) {
return false
}
}
true
}
///|
fn decode_read_semantics_invocation_with_codec(
arguments : Map[String, Json],
generation : JsonCodec[UInt64],
) -> Result[ToolInvocation, String] {
if !fields_are_exact(arguments, ["since"]) {
return Err("read_semantics contains unknown arguments.")
}
match decode_since_with_codec(arguments, generation) {
Ok(since) => Ok(ReadSemantics(since))
Err(message) => Err(message)
}
}
///|
fn decode_action_request_with_codecs(
arguments : Map[String, Json],
target : JsonCodec[@core.SemanticsTarget],
action : JsonCodec[@core.SemanticsAction],
precondition : JsonCodec[@agent.AgentGenerationPrecondition],
) -> Result[ToolInvocation, String] {
if !fields_are_exact(arguments, ["target", "action", "precondition"]) {
return Err("perform_action contains unknown arguments.")
}
guard arguments.get("target") is Some(target_json) else {
return Err("Missing tagged 'target' object.")
}
guard arguments.get("action") is Some(action_json) else {
return Err("Missing tagged 'action' object.")
}
guard arguments.get("precondition") is Some(precondition_json) else {
return Err("Missing tagged 'precondition' object.")
}
match
(
(target.decode)(target_json),
(action.decode)(action_json),
(precondition.decode)(precondition_json),
) {
(Ok(target), Ok(action), Ok(precondition)) =>
Ok(PerformAction({ target, action, precondition, }))
(Err(message), _, _) => Err(message)
(_, Err(message), _) => Err(message)
(_, _, Err(message)) => Err(message)
}
}
///|
fn decode_diagnostics_read_state_invocation(
arguments : Map[String, Json],
) -> Result[ToolInvocation, String] {
if arguments.length() == 0 {
Ok(DiagnosticsReadState)
} else {
Err("diagnostics_read_state does not accept arguments.")
}
}
///|
fn decode_diagnostics_read_paint_invocation(
arguments : Map[String, Json],
) -> Result[ToolInvocation, String] {
if arguments.length() == 0 {
Ok(DiagnosticsReadPaint)
} else {
Err("diagnostics_read_paint does not accept arguments.")
}
}
///|
fn decode_diagnostics_dispatch_event_invocation(
arguments : Map[String, Json],
) -> Result[ToolInvocation, String] {
if !fields_are_exact(arguments, ["event"]) {
return Err("diagnostics_dispatch_event contains unknown arguments.")
}
match decode_event_argument(arguments) {
Some(event) => Ok(DiagnosticsDispatchEvent(event))
None => Err("Invalid event payload.")
}
}
///|
fn decode_diagnostics_dispatch_command_invocation(
arguments : Map[String, Json],
) -> Result[ToolInvocation, String] {
if !fields_are_exact(arguments, ["intent"]) {
return Err("diagnostics_dispatch_command contains unknown arguments.")
}
match decode_command_argument(arguments) {
Some(intent) => Ok(DiagnosticsDispatchCommand(intent))
None => Err("Invalid command intent.")
}
}
///|
fn decode_since_with_codec(
arguments : Map[String, Json],
generation : JsonCodec[UInt64],
) -> Result[@core.SemanticsGeneration?, String] {
match arguments.get("since") {
None => Ok(None)
Some(value) =>
match (generation.decode)(value) {
Ok(generation) => Ok(Some(@core.SemanticsGeneration::new(generation)))
Err(_) => Err("'since' must be a canonical UInt64 decimal string.")
}
}
}
///|
fn json_string_field(fields : Map[String, Json], name : String) -> String? {
match fields.get(name) {
Some(String(value)) => Some(value)
_ => None
}
}
///|
fn decode_target_with_codecs(
fields : Map[String, Json],
semantic_id : JsonCodec[@core.SemanticId],
node_id : JsonCodec[UInt64],
) -> Result[@core.SemanticsTarget, String] {
if !fields_are_exact(fields, ["kind", "id"]) {
return Err("Target contains unknown fields.")
}
guard json_string_field(fields, "kind") is Some(kind) else {
return Err("Target requires string 'kind'.")
}
guard fields.get("id") is Some(value) else {
return Err("Target requires 'id'.")
}
match kind {
"semantic_id" =>
match (semantic_id.decode)(value) {
Ok(id) => Ok(BySemanticId(id))
Err(message) => Err(message)
}
"node_id" =>
match (node_id.decode)(value) {
Ok(id) => Ok(ByNodeId(@core.SemanticsNodeId::new(id)))
Err(_) => Err("Invalid node_id.")
}
_ => Err("Unknown target kind.")
}
}
///|
fn decode_action(
fields : Map[String, Json],
) -> Result[@core.SemanticsAction, String] {
guard json_string_field(fields, "kind") is Some(kind) else {
return Err("Action requires string 'kind'.")
}
match kind {
"activate" =>
if fields_are_exact(fields, ["kind"]) {
Ok(Activate)
} else {
Err("activate contains unknown fields.")
}
"focus" =>
if fields_are_exact(fields, ["kind"]) {
Ok(Focus)
} else {
Err("focus contains unknown fields.")
}
"set_text" =>
match
(
fields_are_exact(fields, ["kind", "value"]),
json_string_field(fields, "value"),
) {
(false, _) => Err("set_text contains unknown fields.")
(true, Some(value)) => Ok(SetText(value))
(true, None) => Err("set_text requires string 'value'.")
}
"submit" =>
if fields_are_exact(fields, ["kind"]) {
Ok(Submit)
} else {
Err("submit contains unknown fields.")
}
"scroll" =>
match
(
fields_are_exact(fields, ["kind", "direction"]),
json_string_field(fields, "direction"),
) {
(false, _) => Err("scroll contains unknown fields.")
(true, Some(direction)) =>
match decode_scroll_direction(direction) {
Some(value) => Ok(Scroll(value))
None => Err("Invalid scroll direction.")
}
(true, None) => Err("scroll requires string 'direction'.")
}
"select" =>
if fields_are_exact(fields, ["kind"]) {
Ok(Select)
} else {
Err("select contains unknown fields.")
}
"expand" =>
if fields_are_exact(fields, ["kind"]) {
Ok(Expand)
} else {
Err("expand contains unknown fields.")
}
"collapse" =>
if fields_are_exact(fields, ["kind"]) {
Ok(Collapse)
} else {
Err("collapse contains unknown fields.")
}
"dismiss" =>
if fields_are_exact(fields, ["kind"]) {
Ok(Dismiss)
} else {
Err("dismiss contains unknown fields.")
}
"increment" =>
if fields_are_exact(fields, ["kind"]) {
Ok(Increment)
} else {
Err("increment contains unknown fields.")
}
"decrement" =>
if fields_are_exact(fields, ["kind"]) {
Ok(Decrement)
} else {
Err("decrement contains unknown fields.")
}
"set_numeric_value" =>
match fields.get("value") {
Some(Number(value, repr=_)) if fields_are_exact(fields, [
"kind", "value",
]) => Ok(SetNumericValue(value))
Some(_) => Err("set_numeric_value requires numeric 'value'.")
None => Err("set_numeric_value requires numeric 'value'.")
}
"set_selection" =>
match (fields.get("start"), fields.get("end")) {
(Some(Number(start, repr=_)), Some(Number(end, repr=_))) if fields_are_exact(
fields,
["kind", "start", "end"],
) &&
start >= 0.0 &&
end >= 0.0 &&
start == start.to_int().to_double() &&
end == end.to_int().to_double() =>
Ok(
SetSelection(
@core.TextRange::new(start=start.to_int(), end=end.to_int()),
),
)
_ => Err("set_selection requires non-negative integer start and end.")
}
"show_menu" =>
if fields_are_exact(fields, ["kind"]) {
Ok(ShowMenu)
} else {
Err("show_menu contains unknown fields.")
}
_ => Err("Unknown action kind.")
}
}
///|
fn decode_scroll_direction(value : String) -> @core.SemanticsScrollDirection? {
match value {
"forward" => Some(Forward)
"backward" => Some(Backward)
"up" => Some(Up)
"down" => Some(Down)
"left" => Some(Left)
"right" => Some(Right)
_ => None
}
}
///|
fn decode_precondition_with_codec(
fields : Map[String, Json],
generation : JsonCodec[UInt64],
) -> Result[@agent.AgentGenerationPrecondition, String] {
guard json_string_field(fields, "kind") is Some(kind) else {
return Err("Precondition requires string 'kind'.")
}
match kind {
"latest" =>
if fields_are_exact(fields, ["kind"]) {
Ok(Latest)
} else {
Err("latest precondition contains unknown fields.")
}
"exact" =>
match
(
fields_are_exact(fields, ["kind", "generation"]),
fields.get("generation"),
) {
(false, _) => Err("exact precondition contains unknown fields.")
(true, Some(value)) =>
match (generation.decode)(value) {
Ok(generation) =>
Ok(Exact(@core.SemanticsGeneration::new(generation)))
Err(_) => Err("Invalid exact generation.")
}
(true, None) => Err("exact precondition requires string 'generation'.")
}
_ => Err("Unknown precondition kind.")
}
}
///|
fn agent_semantics_issue_json(issue : @agent.AgentSemanticsIssue) -> Json {
match issue {
DuplicateSemanticId(id, nodes) =>
(
{ "kind": "duplicate_semantic_id", "semantic_id": id, "nodes": nodes } :
Json)
MissingRelationTarget(source, kind, id) =>
(
{
"kind": "missing_relation_target",
"source": source,
"relation": semantics_relation_kind_code(kind),
"semantic_id": id,
} : Json)
AmbiguousRelationTarget(source, kind, id, nodes) =>
(
{
"kind": "ambiguous_relation_target",
"source": source,
"relation": semantics_relation_kind_code(kind),
"semantic_id": id,
"nodes": nodes,
} : Json)
}
}
///|
fn semantics_relation_kind_code(kind : @core.SemanticsRelationKind) -> String {
match kind {
LabelledBy => "labelled_by"
DescribedBy => "described_by"
Controls => "controls"
ErrorMessage => "error_message"
ActiveDescendant => "active_descendant"
}
}
///|
fn semantics_role_code(role : @core.SemanticsRole) -> String {
match role {
None => "none"
Text => "text"
Link => "link"
Heading => "heading"
Navigation => "navigation"
Main => "main"
Button => "button"
Checkbox => "checkbox"
TextField => "text_field"
List => "list"
ListItem => "list_item"
Grid => "grid"
ScrollView => "scroll_view"
Switch => "switch"
Radio => "radio"
Slider => "slider"
Progress => "progress"
Tab => "tab"
Dialog => "dialog"
Tooltip => "tooltip"
Menu => "menu"
ButtonGroup => "button_group"
Form => "form"
FormField => "form_field"
Tree => "tree"
TreeItem => "tree_item"
Group => "group"
WebView => "web_view"
Image => "image"
Alert => "alert"
Status => "status"
ComboBox => "combo_box"
Option => "option"
Table => "table"
Row => "row"
Cell => "cell"
Separator => "separator"
}
}
///|
fn optional_node_id_json(value : @core.SemanticsNodeId?) -> Json {
match value {
Some(node_id) => node_id.to_json()
None => json_null()
}
}
///|
fn optional_semantic_id_json(value : @core.SemanticId?) -> Json {
match value {
Some(semantic_id) => semantic_id.to_json()
None => json_null()
}
}
///|
fn optional_int_json(value : Int?) -> Json {
match value {
Some(number) => number.to_json()
None => json_null()
}
}
///|
fn optional_checked_json(value : @core.SemanticsCheckedState?) -> Json {
match value {
Some(Unchecked) => "unchecked".to_json()
Some(Checked) => "checked".to_json()
Some(Mixed) => "mixed".to_json()
None => json_null()
}
}
///|
fn semantics_state_json(state : @core.SemanticsState) -> Json {
(
{
"focused": state.focused,
"disabled": state.disabled,
"selected": state.selected,
"pressed": state.pressed,
"checked": optional_checked_json(state.checked),
"expanded": state.expanded,
"invalid": state.invalid,
"required": state.required,
"read_only": state.read_only,
"busy": state.busy,
"multiline": state.multiline,
"password": state.password,
"modal": state.modal,
} : Json)
}
///|
fn semantics_live_code(value : @core.SemanticsLive) -> String {
match value {
Off => "off"
Polite => "polite"
Assertive => "assertive"
}
}
///|
fn accessibility_announcement_json(
announcement : @agent.AgentAccessibilityAnnouncement,
) -> Json {
(
{
"node_id": announcement.node_id,
"generation": announcement.generation,
"live": semantics_live_code(announcement.live),
"atomic": announcement.is_atomic,
"text": announcement.text,
} : Json)
}
///|
fn semantics_node_json(node : @core.SemanticsNode) -> Json {
(
{
"node_id": node.node_id,
"semantic_id": optional_semantic_id_json(node.semantic_id),
"role": semantics_role_code(node.role),
"level": optional_int_json(node.level),
"url": node.url,
"label": node.label,
"value": node.value,
"description": node.description,
"state": semantics_state_json(node.state),
"numeric": node.numeric,
"text": node.text,
"collection": node.collection,
"relations": node.relations,
"live": semantics_live_code(node.live),
"atomic": node.live_atomic,
"actions": node.actions.map(semantics_action_kind_code),
"focus_order": optional_int_json(node.focus_order),
"frame": node.frame,
"children": node.children,
} : Json)
}
///|
fn agent_semantics_read_json(read : @agent.AgentSemanticsRead) -> Json {
match read {
Full(snapshot) =>
(
{
"kind": "full",
"generation": snapshot.generation,
"root": optional_node_id_json(snapshot.root),
"focused": optional_node_id_json(snapshot.focused),
"nodes": snapshot.nodes.map(semantics_node_json),
"issues": snapshot.issues.map(agent_semantics_issue_json),
} : Json)
Delta(delta) =>
(
{
"kind": "delta",
"from": delta.from,
"to": delta.to,
"root": optional_node_id_json(delta.root),
"focused": optional_node_id_json(delta.focused),
"upserted": delta.upserted.map(semantics_node_json),
"removed": delta.removed,
"issues": delta.issues.map(agent_semantics_issue_json),
"announcements": delta.announcements.map(
accessibility_announcement_json,
),
} : Json)
Unchanged(generation) =>
({ "kind": "unchanged", "generation": generation } : Json)
}
}
///|
fn decode_command_argument(
arguments : Map[String, Json],
) -> @core.CommandIntent? {
guard json_string_field(arguments, "intent") is Some(intent) else {
return None
}
match intent {
"Activate" => Some(Activate)
"Submit" => Some(Submit)
"Cancel" => Some(Cancel)
"Copy" => Some(Copy)
"Cut" => Some(Cut)
"Paste" => Some(Paste)
"Undo" => Some(Undo)
"Redo" => Some(Redo)
"SelectAll" => Some(SelectAll)
"OpenContextMenu" => Some(OpenContextMenu)
_ => None
}
}
///|
fn decode_event_argument(arguments : Map[String, Json]) -> @core.AppEvent? {
guard arguments.get("event") is Some(Object(fields)) else { return None }
guard json_string_field(fields, "kind") is Some("Pointer") else {
return None
}
guard fields.get("position") is Some(Object(position)) else { return None }
guard position.get("x") is Some(Number(x, repr=_)) else { return None }
guard position.get("y") is Some(Number(y, repr=_)) else { return None }
guard json_string_field(fields, "phase") is Some(phase) else { return None }
let pointer_phase = match phase {
"Down" => @core.PointerPhase::Down
"Up" => Up
"Move" => Move
"Cancel" => Cancel
"Exit" => Exit
_ => return None
}
Some(
Pointer(
@core.PointerEvent::new(
position=@core.Point::new(x~, y~),
phase=pointer_phase,
),
),
)
}