Ta strona jest publikowana po angielsku i ukraińsku; poniżej wersja angielska.
Custom events: show results on your page
For developers, in code: an action your page renders — the event's fields, how the host receives it, the tool-to-event link that fires it deterministically, and the teaser and consent switches.
A custom event is an action the assistant emits and your page renders: open a product card, filter a list, show a modal. The assistant never draws your UI; it asks yours to. One listener on your page, written once.
Custom events are part of the Growth plan and above.
In the panel: Tools → New event (Tools and events). This page is the same event field by field, and the listener on your page.
Define it in the panel
| Field | What it is |
|---|---|
name |
a slug, unique per site — it becomes the action type, and your page listens on shukai:action:<name> |
description |
when to emit it, the model’s only signal when the event is model-driven |
fields |
the payload schema: { name, type, description, required }, type string · number · integer · boolean · array, up to 20 |
suppress_navigate |
drop any navigate from the same turn — your UI is showing the result, the assistant should not also move the visitor |
teaser |
end the turn with a short «found it — showing you now» line instead of a full answer |
confirm |
ask the visitor before emitting; the action arrives confirm: true, the widget holds it behind chips, and the server judges the reply in any wording |
scope, screens, agent |
as for tools: every screen, or only the screens that name it; one agent or all |
Receive it on the page
window.addEventListener("shukai:action:show_products", (e) => {
const { products } = e.detail.payload;
renderProductGrid(products);
});
event.detail is { type: "show_products", payload: { …fields } }; shukai:action fires too,
for every action. It is fire-and-forget: nothing is fed back to the model, and there is nothing to
cancel. The event form’s Simulate fires one with example values so you can see what your
listener receives, and prints a listener snippet to copy.
Two ways an event fires
Model-driven. The event is offered to the model as a tool; it emits it when the description says to, and only when every required field can be filled. Reliable for a clear trigger («open the cart when the visitor says they want to check out»).
Bound to a tool — deterministic. Set emits_event on a custom tool and the runtime emits the
event itself on every non-empty result, not when the model feels like it:
- an event with an
arrayfield gets the whole result list (trimmed by the tool’sresponse_path) in that field, and its scalar fields from the first item; - an event with scalar fields only is emitted for exactly one result item, matched by name;
- a field without
requiredcounts as required here, so declarerequired: falseexplicitly on optional fields.
A bound event is taken out of the model’s own tools, so the two routes never compete. This is what you want when the result is a list to show.
Precedence
One turn holds one offer. When several outcomes compete: a held page-action plan beats an event
teaser, which beats a page redirect. With confirm, a held event waits for the visitor’s yes;
with teaser, the answer is one line and the host’s UI is the answer.
Rendering well
- Render where the visitor is looking — the product grid, not a chat bubble — and keep the
widget’s own answer short with
teaser. - Keep a listener on every page the event can fire on: the event is sent whether or not something listens, and the page that listens is the one that shows the result.
- The widget is in the corner; a modal that covers it is fine, the widget steps aside for page actions, not for your UI.
Worked example
The marketing site’s own shop demo: a catalogue_search tool bound to a show_products event
with one array field. «A red dress for a wedding, under 2,000» calls the tool, the runtime
emits the event with the matches, and the site’s listener draws a product panel in the page’s
own look. Its sibling on the same site, subscription_search bound to subscription_lookup,
reads the real plan grid and draws the plans that fit the question. Both are built step by step,
endpoint included, in the worked example.