Widgets
Every component an agent can show, its props, and the limits Cencori checks.
A widget is a JSON tree of components. Every component has a type and its props. Usually the root is a Card.
{
"type": "Card",
"title": "Revenue",
"children": [
{ "type": "Stat", "label": "November", "value": 48210, "format": "currency", "currency": "USD", "delta": 0.12, "deltaFormat": "percent" },
{ "type": "Chart", "chart": "bar", "x": "month", "series": [{ "key": "revenue", "label": "Revenue" }], "format": "currency", "currency": "USD",
"data": [{ "month": "Sep", "revenue": 39100 }, { "month": "Oct", "revenue": 43050 }, { "month": "Nov", "revenue": 48210 }] }
],
"actions": [{ "type": "Button", "label": "Break down by product", "action": { "type": "breakdown", "payload": { "by": "product" } } }]
}Where widgets come from
Either way the widget is checked against this spec before it's shown. With render_ui, an invalid widget goes back to the model with the errors (widget.children[2].color: expected one of "default", "muted", ...) and it tries again. From your tool, an invalid widget isn't shown and the SDK logs why.
Layout
gap is none, xs, sm, md or lg.
Content
Data
format is number, compact, currency, percent, date or datetime. currency is an ISO code: USD, NGN, EUR. Values are formatted in the viewer's locale.
Interaction
An action is { type, payload?, handler? }. See Actions and forms.
Colors
default, muted, accent, success, warning, danger, info. They follow the chat's theme in light and dark mode.
Limits
Unknown props are dropped. URLs must be https (links may also be mailto:).
Checking a widget yourself
import { validateWidget } from 'cencori/agents';
const result = validateWidget(widget);
if (!result.ok) console.log(result.errors);
