Choices
A Choice is the one way a block talks back. A person picks it, and its context goes to the agent as their next message. The host makes sure that message is one the person saw, and that a block answers once.
The flow
- A person picks a Choice. The page sends
chat.uiChoicewithchatId,itemId,blockId,revision,choiceId, the block's input values fromuiInputValues, and for a live block the read ids of the values on screen. - The backend finds the stored block at that revision. A reply that still streams, a subagent's reply or a changed block answers
stale-ui-block. - It checks the block was not answered. A second pick of the same Choice answers what the first did; another Choice gets
ui-already-answered. - It runs
resolveUiChoice(block, choiceId, values, queries). - It sends the selection's
contextas the next message, or queues it behind a running turn, and records the answer.
With @adecore/agents all of this is ChatCore.choose, behind the chat.uiChoice handler of the agent host. The handler answers only a client attached to the chat.
resolveUiChoice
import { resolveUiChoice } from '@adecore/intelligent-ui/runtime';
const { label, context, values } = resolveUiChoice(storedBlock, payload.choiceId, payload.values ?? {}, issuedQueryValues);It evaluates the stored block with the submitted values and returns a UiChoiceSelection:
| Field | Holds |
|---|---|
label | The Choice's text, on one line |
context | Its evaluated context, or the label when it has none |
values | The input values the message was evaluated with |
It throws a UiFailure with one of these codes:
| Code | Cause |
|---|---|
refused_choice | The block is unfinished or of another catalog version; the Choice is not there, hidden, disabled, outside Choices, or has no readable label |
refused_binding | A submitted name is not a declared input of the block |
invalid_value | A value no visible control could have produced, or of another type |
budget_exceeded | Evaluating the block ran out of budget |
The fourth argument holds query values. The caller supplies them from its own readings; the client's input map can never set a query.
What a person could have set
uiInputValues(block, state) lists the values of every variable a complete control of the block binds, and of every variable a Button action targets. That is what a page sends.
A submitted value is accepted when it equals the default, when a visible complete control holds it within the options it offers (a Segmented value one of its Options, a Checklist value made of its Items), or when a visible enabled Button sets exactly that value. A Button's @Set takes a constant for this reason: its value does not depend on the state it was pressed in. A control inside a Show that is false is not visible, so it cannot vouch for a value.
uiValidatedState(block, input, queries?, limits?) applies the same check and returns a UiState holding the values. Queries and links use it before they evaluate anything with client input.
Live blocks
A block with queries shows values a person may act on. So the backend sends only values it issued itself:
- The page sends
reads, the read id of each query's reading on screen. - For every query the block declares, the backend checks the read id names a reading it issued for the current arguments and still holds, or the frozen first reading while the query's arguments are still those of the defaults.
- A missing or unknown read id refuses the choice with
stale-ui-queryand the codestale-read. The page reads again and opens the choices.
The page closes the choices while a block reads, and while a changed input has not been read yet. uiLiveChoiceReason in agents-react says why.
Answers
The answer is recorded on the assistant item as uiAnswers[blockId], a ChatUiAnswer: the identities, the label, the values, sourceAt and older (whether a newer reply had come since), the time at, queued and the turnId. The user message carries the same origin as uiChoice. The message is logged before the answer, so a crash in between cannot send it twice.
A page draws an answered block with the label and time of its answer, and closes every input and Choice in it. Its values stay as they were sent, after a reload too. ChatHost.intelligentUi.sendChoice resolves sent or queued; without it, choices stay closed.