Class: AgUi::Middleware::A2ui
- Inherits:
-
Object
- Object
- AgUi::Middleware::A2ui
- Defined in:
- lib/ag_ui/middleware/a2ui.rb
Overview
A2UI (Tier-3 generative UI) — the Ruby port of CopilotKit's @ag-ui/a2ui-middleware transform (doc 09 §6).
Way in:
- injects the `render_a2ui` tool into env[:tools] (STATIC schema —
the catalog does NOT shape it)
- appends the catalog's component schema to the conversation as a
system message (that's how the model learns the vocabulary)
Way out, for each render_a2ui call the model made:
- converts the call args into an ACTIVITY_SNAPSHOT carrying
`a2ui_operations` (createSurface once per surface, then
updateComponents / updateDataModel), flat wire shape,
activityType "a2ui-surface", replace: true
- emits a synthetic TOOL_CALL_RESULT ({"status":"rendered"}) so the
next run's history shows the model its call completed
The TOOL_CALL_* events themselves pass through (ToolRouter emits them) and the run still ends — same multi-run model as client tools; the activity events are what the canvas renders.
Sits OUTSIDE ToolRouter in the pipeline:
use SystemPrompt; use A2ui, catalog: catalog; use ToolRouter
Constant Summary collapse
- TOOL_NAME =
"render_a2ui"- TOOL_DEFINITION =
{ "name" => TOOL_NAME, "description" => "Render a dynamic A2UI v0.9 surface with structured parameters. " \ "Follow the A2UI render tool usage guide provided in context.", "parameters" => { "type" => "object", "properties" => { "surfaceId" => { "type" => "string", "description" => "Unique surface identifier.", }, "components" => { "type" => "array", "description" => "A2UI v0.9 component array (flat format). The root component " \ "must have id \"root\".", "items" => { "type" => "object" }, }, "data" => { "type" => "object", "description" => "Initial data model for the surface. Written to the root path. " \ "Use for pre-filling form values (e.g. {\"form\": {\"name\": \"Alice\"}}) " \ "or providing data for components bound to data model paths.", }, }, "required" => %w[surfaceId components], }, }.freeze
- SCHEMA_CONTEXT_PREAMBLE =
"A2UI Component Schema — available components for generating UI " \ "surfaces. Use these component names and properties when creating " \ "A2UI operations."
- BASIC_CATALOG_ID =
"https://a2ui.org/specification/v0_9/basic_catalog.json"- MAX_ATTEMPTS =
Attempt cap for in-run regeneration (initial try + retries), matching the toolkit recovery default.
::AgUi::A2ui::Recovery::MAX_A2UI_ATTEMPTS
Instance Method Summary collapse
- #call(env) ⇒ Object
-
#initialize(app, catalog: nil, default_catalog_id: nil) ⇒ A2ui
constructor
A new instance of A2ui.
Constructor Details
#initialize(app, catalog: nil, default_catalog_id: nil) ⇒ A2ui
Returns a new instance of A2ui.
73 74 75 76 77 |
# File 'lib/ag_ui/middleware/a2ui.rb', line 73 def initialize(app, catalog: nil, default_catalog_id: nil) @app = app @catalog = catalog @default_catalog_id = default_catalog_id || catalog&.catalog_id end |
Instance Method Details
#call(env) ⇒ Object
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/ag_ui/middleware/a2ui.rb', line 83 def call(env) advertise(env) inject_catalog_schema(env) before = env[:messages].length @app.call(env) # Only this iteration's assistant message — seeded history can # carry old tool-call turns that must not re-render. appended = env[:messages][before..] || [] assistant = appended.reverse.find { |m| m.respond_to?(:tool_call?) && m.tool_call? } if assistant assistant.tool_calls.each do |tool_call| if tool_call.name == TOOL_NAME render(env, tool_call) end end end env end |