scripts-a2ui-renderer-bridge
#!/usr/bin/env python3 """ A2UI Renderer Bridge
Bridge CODITECT agent outputs to A2UI renderers. Converts structured agent responses to A2UI JSON format.
Usage: python3 a2ui-renderer-bridge.py --input agent_output.json --output a2ui.json python3 a2ui-renderer-bridge.py --serve --port 3001 echo '{"type": "dashboard", "data": {...}}' | python3 a2ui-renderer-bridge.py """
import argparse import json import sys from dataclasses import dataclass from typing import Any import http.server import socketserver import os
A2UI_VERSION = "0.8"
@dataclass class A2UIComponent: """A2UI component representation.""" id: str type: str properties: dict = None children: list = None
def to_dict(self) -> dict:
result = {"id": self.id, "type": self.type}
if self.properties:
result["properties"] = self.properties
if self.children:
result["children"] = self.children
return result
class A2UIBuilder: """Build A2UI JSON from structured data."""
def __init__(self):
self.components = []
self.data = {}
self.bindings = {}
self._id_counter = 0
def _next_id(self, prefix: str = "comp") -> str:
self._id_counter += 1
return f"{prefix}-{self._id_counter}"
def add_component(self, component: A2UIComponent) -> str:
"""Add a component and return its ID."""
self.components.append(component)
return component.id
def set_data(self, key: str, value: Any) -> None:
"""Set data for binding."""
self.data[key] = value
def add_binding(self, component_id: str, path: str) -> None:
"""Add a data binding."""
self.bindings[component_id] = path
def build(self) -> dict:
"""Build final A2UI JSON."""
return {
"type": "a2ui",
"version": A2UI_VERSION,
"components": [c.to_dict() for c in self.components],
"data": self.data,
"bindings": self.bindings if self.bindings else None,
}
def create_dashboard(title: str, metrics: list[dict]) -> dict: """Create a dashboard A2UI from metrics data.""" builder = A2UIBuilder()
# Root container
root = A2UIComponent(
id="root",
type="container",
children=["header", "metrics-grid"],
properties={"layout": "vertical", "gap": 24, "padding": 16}
)
builder.add_component(root)
# Header
header = A2UIComponent(
id="header",
type="text",
properties={"variant": "h1", "content": title}
)
builder.add_component(header)
# Metrics grid
metric_ids = []
for i, metric in enumerate(metrics):
card_id = f"metric-{i}"
label_id = f"metric-{i}-label"
value_id = f"metric-{i}-value"
metric_ids.append(card_id)
card = A2UIComponent(
id=card_id,
type="card",
children=[label_id, value_id],
properties={"elevation": 2}
)
builder.add_component(card)
label = A2UIComponent(
id=label_id,
type="text",
properties={"variant": "caption", "content": metric.get("label", "")}
)
builder.add_component(label)
value = A2UIComponent(
id=value_id,
type="text",
properties={"variant": "h2", "content": f"{{{{data.metrics.{i}.value}}}}"}
)
builder.add_component(value)
grid = A2UIComponent(
id="metrics-grid",
type="grid",
children=metric_ids,
properties={"columns": min(len(metrics), 4), "gap": 16}
)
builder.add_component(grid)
# Set data
builder.set_data("metrics", [{"value": m.get("value", "")} for m in metrics])
return builder.build()
def create_form(title: str, fields: list[dict]) -> dict: """Create a form A2UI from field definitions.""" builder = A2UIBuilder()
# Root container
field_ids = [f"field-{i}" for i in range(len(fields))]
root = A2UIComponent(
id="root",
type="container",
children=["header", "fields", "submit"],
properties={"layout": "vertical", "gap": 16, "padding": 16}
)
builder.add_component(root)
# Header
header = A2UIComponent(
id="header",
type="text",
properties={"variant": "h2", "content": title}
)
builder.add_component(header)
# Fields container
fields_container = A2UIComponent(
id="fields",
type="stack",
children=field_ids,
properties={"direction": "vertical", "spacing": 12}
)
builder.add_component(fields_container)
# Individual fields
for i, field in enumerate(fields):
field_type = field.get("type", "text-field")
field_comp = A2UIComponent(
id=f"field-{i}",
type=field_type,
properties={
"label": field.get("label", f"Field {i}"),
"placeholder": field.get("placeholder", ""),
"required": field.get("required", False),
}
)
if field.get("options"):
field_comp.properties["options"] = field["options"]
builder.add_component(field_comp)
# Submit button
submit = A2UIComponent(
id="submit",
type="button",
properties={
"label": "Submit",
"variant": "primary",
"action": "submit-form"
}
)
builder.add_component(submit)
return builder.build()
def create_list_view(title: str, items: list[dict], item_template: dict = None) -> dict: """Create a list view A2UI.""" builder = A2UIBuilder()
# Root container
root = A2UIComponent(
id="root",
type="container",
children=["header", "list"],
properties={"layout": "vertical", "gap": 16}
)
builder.add_component(root)
# Header
header = A2UIComponent(
id="header",
type="text",
properties={"variant": "h2", "content": title}
)
builder.add_component(header)
# List
list_comp = A2UIComponent(
id="list",
type="list",
children=["item-template"],
properties={"items": "{{data.items}}"}
)
builder.add_component(list_comp)
# Item template
template = A2UIComponent(
id="item-template",
type="card",
children=["item-title", "item-description"],
properties={"elevation": 1}
)
builder.add_component(template)
item_title = A2UIComponent(
id="item-title",
type="text",
properties={"variant": "h4", "content": "{{item.title}}"}
)
builder.add_component(item_title)
item_desc = A2UIComponent(
id="item-description",
type="text",
properties={"variant": "body", "content": "{{item.description}}"}
)
builder.add_component(item_desc)
# Set data
builder.set_data("items", items)
return builder.build()
def convert_agent_output(output: dict) -> dict: """Convert structured agent output to A2UI JSON.""" output_type = output.get("type", "").lower()
if output_type == "dashboard":
return create_dashboard(
title=output.get("title", "Dashboard"),
metrics=output.get("metrics", [])
)
elif output_type == "form":
return create_form(
title=output.get("title", "Form"),
fields=output.get("fields", [])
)
elif output_type == "list":
return create_list_view(
title=output.get("title", "Items"),
items=output.get("items", [])
)
elif output_type == "a2ui":
# Already A2UI format
return output
else:
raise ValueError(f"Unknown output type: {output_type}")
def validate_a2ui(a2ui: dict) -> list[str]: """Validate A2UI JSON structure.""" errors = []
if a2ui.get("type") != "a2ui":
errors.append("Missing or invalid 'type' field")
if "version" not in a2ui:
errors.append("Missing 'version' field")
components = a2ui.get("components", [])
if not components:
errors.append("Empty or missing 'components' array")
return errors
ids = set()
for comp in components:
comp_id = comp.get("id")
if not comp_id:
errors.append(f"Component missing 'id': {comp}")
elif comp_id in ids:
errors.append(f"Duplicate component id: {comp_id}")
else:
ids.add(comp_id)
if "type" not in comp:
errors.append(f"Component {comp_id} missing 'type'")
return errors
class A2UIHandler(http.server.SimpleHTTPRequestHandler): """HTTP handler for A2UI bridge server."""
def do_POST(self):
"""Handle POST requests to convert agent output."""
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
try:
input_data = json.loads(post_data)
a2ui = convert_agent_output(input_data)
errors = validate_a2ui(a2ui)
if errors:
self.send_response(400)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"errors": errors}).encode())
else:
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(a2ui, indent=2).encode())
except Exception as e:
self.send_response(500)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({"error": str(e)}).encode())
def main(): parser = argparse.ArgumentParser(description="A2UI Renderer Bridge") parser.add_argument("--input", "-i", help="Input JSON file") parser.add_argument("--output", "-o", help="Output A2UI JSON file") parser.add_argument("--serve", action="store_true", help="Run as HTTP server") parser.add_argument("--port", type=int, default=3001, help="Server port") parser.add_argument("--validate", "-v", help="Validate A2UI file")
args = parser.parse_args()
if args.validate:
with open(args.validate) as f:
a2ui = json.load(f)
errors = validate_a2ui(a2ui)
if errors:
print("Validation errors:")
for e in errors:
print(f" - {e}")
sys.exit(1)
else:
print("Valid A2UI JSON")
sys.exit(0)
if args.serve:
with socketserver.TCPServer(("", args.port), A2UIHandler) as httpd:
print(f"A2UI Bridge Server running on port {args.port}")
print(f"POST JSON to http://localhost:{args.port}/")
httpd.serve_forever()
elif args.input:
with open(args.input) as f:
input_data = json.load(f)
a2ui = convert_agent_output(input_data)
if args.output:
with open(args.output, 'w') as f:
json.dump(a2ui, f, indent=2)
print(f"Written to {args.output}")
else:
print(json.dumps(a2ui, indent=2))
else:
# Read from stdin
input_data = json.load(sys.stdin)
a2ui = convert_agent_output(input_data)
print(json.dumps(a2ui, indent=2))
if name == "main": main()