Skip to main content
The question tool lets an agent stop and ask the user to choose between options before it continues. The agent’s reply pauses at the question, the answer is saved with the chat, and the model resumes in the same assistant message with the answer as the tool result. Questions work in the admin chat, in @frogbotai/ui, and over HTTP for headless clients. They are opt-in per agent.

Add the tool

Import question from frogbot/tools and list it on the agent that may ask.
Adding it to the root tools list offers it to every agent. Questions and answers are stored with the chat, so a waiting question survives a reload or a restart.

Question and answer shape

The model sends one or more questions in a single call. The answer has one entry per question, in the same order.
selected holds option labels exactly as they were offered. FrogBot rejects an answer that picks an option that was not offered, answers the wrong header, selects more than one option on a single-choice question, or includes custom when the question disallows it. Both types are exported as Zod schemas and TypeScript types: QuestionInput and QuestionOutput from frogbot/tools.

Answer in the chat UI

The admin chat renders questions as a card. While a question waits, the card is docked above the composer, which is hidden until the question is answered or dismissed; the answered card then shows in the conversation. A single single-choice question submits on click. Several questions step through one at a time, and questions that allow it show a text field for a custom answer. In your own app, register the card with ChatProvider.
Answering uses the standard AI SDK client-tool flow: the card calls addToolOutput, and the chat resubmits once every call in the step has an output. FrogBot validates the answer against the stored call rather than trusting the browser’s copy, and streams the continuation into the same assistant message. Dismiss declines the question. The model is not called again; the call is recorded as a tool error with the text Dismissed by the user., and the chat is ready for the next message.

Answer over HTTP

A JSON request that reaches a question returns status: "awaiting-input" and the pending calls instead of a final reply.
List the calls that are still waiting at any time.
The response includes the chat’s turn state and a pending array. Settle a call with its output.
When that answer settles the last pending call in the step, FrogBot continues the turn and responds with the same JSON shape as the original request, plus the settlement. If other calls in the step are still waiting, the response has status: "awaiting-input" and the remaining pending calls. Send { "toolCallId": "call_1", "dismissed": true } instead of output to dismiss. A body without toolCallId and exactly one of output or dismissed: true returns 400 without a code. When the chat UI resubmits answers, the same codes apply, plus pending-calls while other calls in the step still need an answer and turn-in-progress when another request already continued the turn. The first valid answer wins. Answers from two tabs or two participants settle once and continue once.

Who can answer

Anyone who can continue the chat can answer its questions. Over HTTP, that is the chat’s owner; for a chat without an owner, it is any anonymous caller who knows the chat ID. FrogBot records who answered or dismissed each call, and when, in the message’s settlements field. A question in a channel conversation is answered only in its channel. Any participant who passes the agent’s access check can answer, not only the person whose message started the turn. See Slack, Discord, Microsoft Teams, Telegram, GitHub, and Linear.

Channel conversations

A conversation started in a channel, such as a Slack thread, is written only in that channel: its messages, its answers, and the agent’s replies. Its owner can open it in the admin chat to read it, but not to write to it.
  • A notice replaces the composer: “This conversation happens in Slack. Continue it there, or branch it into a new chat.”
  • A waiting question shows “Waiting for an answer in Slack” with its questions, and no controls. Answered and closed questions look the same as in any chat.
  • Messages have no Edit action.
  • Branch on the notice copies the whole conversation into a new private chat, and Branch on an assistant message copies it up to that message. The branch is an ordinary web chat. Questions still waiting in the channel arrive in it closed, and nothing written in it reaches the channel.
  • New channel messages appear when you reload the chat.
HTTP and programmatic writes to a channel conversation fail with 409 and code: "channel-chat". See who can write a chat.

Where questions are offered

FrogBot only offers the tool to the model when the caller can answer it. If a model calls a withheld tool anyway, the call becomes a tool error and the turn keeps running, so nothing is left waiting.

Build a client tool

question is a client tool: it declares an outputSchema and a client configuration instead of execute. Define your own the same way when the answer has to come from the user or the browser.
A client tool takes the slug, description, inputSchema, and component tool options, plus: Render the call with a tool renderer. The renderer receives addToolOutput and dismiss while the call is waiting. When a step contains a client tool call, FrogBot does not run server tools emitted in the same step. They return a skipped result so the model can call them again after it has the answer. See turns and queued messages for how a waiting chat handles new messages.