Questions
This page lists the question API. For how questions behave across interruption, expiry and authority, see Questions. To answer one, see Answer questions.
turn.ask()
turn.ask<const Q extends Question>(
question: Q,
options?: { signal?: AbortSignal },
): Promise<AskAnswer<AnswerFor<Q>>>
Available in root Sessions only. Await every question before the handler returns; a Turn that returns with an unjoined question fails.
const response = await turn.ask({
prompt: [{ type: "text", text: "Publish this revision?" }],
type: "choice",
options: [
{ id: "approve", label: "Approve", value: true },
{ id: "decline", label: "Decline", value: false },
],
})
// response.answer: { selected: [{ id: "approve", value: true }] }
// response.answeredBy: { kind: "user", id: "..." }
validateQuestion()
import { ContentError, validateQuestion } from "@helmr/sdk"
// Accepts unknown input and narrows it to Question on success.
validateQuestion(value: unknown): asserts value is Question
Checks the same definition rules and limits as turn.ask without modifying the
input, storing a question, or displaying it. A string prompt remains a string.
Success returns undefined; failure throws ContentError with a code and
human-readable message. Codes are invalid_arguments,
content_kind_unsupported, or content_limit_exceeded. Use the code for program
logic; messages explain the failure and are not stable identifiers.
To check several generated questions before displaying the first, validate all
of them before calling turn.ask:
for (const question of questions) validateQuestion(question)
for (const question of questions) {
const response = await turn.ask(question)
// Handle each answer.
}
Validation checks the definition at the time of the call. turn.ask validates
again when called. Validation does not reserve output budget, check Session
state or authority, or guarantee admission or all-or-none success of several
questions. Handle execution failures and cancellation normally.
Types
type Question = TextQuestion | ChoiceQuestion
// Both variants accept prompt: HumanContent and optional title: string.
// TextQuestion has type: "text".
// ChoiceQuestion has type: "choice", options, and optional
// multiple, allowCustom, allowNotes (all false by default).
type AnswerSubmission = string | Readonly<{
selectedIds: readonly string[]
custom?: string
notes?: string
}>
prompt accepts a string or a Content array. Read models retain normalized
Content arrays. Text questions forbid choice-only fields.
Answer shapes
Text questions resolve to a string. Choice questions resolve to
{ selected: [{ id, value }], custom?, notes? }. The inferred selection preserves
each declared ID’s associated value type.
Remote callers submit selectedIds; the server resolves values from the
immutable question and returns selections in declaration order. Duplicate or
unknown IDs are rejected. Single choice requires exactly one selection, or an
allowed nonempty custom answer with no selection. Multiple choice requires at
least one selection or allowed custom answer. custom requires allowCustom;
notes requires allowNotes. Notes supplement an answer and cannot satisfy the
question alone. Both text fields must be nonempty when supplied.
turn.ask returns { answer, answeredBy }, where answeredBy identifies the
verified user or API key. Invalid submissions return answer_invalid.
Limits
| Item | Limit |
|---|---|
| Question | 256 KiB encoded JSON |
Optional title |
Nonblank, at most 256 UTF-8 bytes |
| Prompt | 64 Content parts |
| Choice options | 1–100 |
Option id |
1–128 bytes, unique |
Option label |
256 bytes |
Option description |
2 KiB |
| Answer | 64 KiB |
Choice custom or notes |
8 KiB |
A choice question is also rejected when its largest possible answer would exceed 64 KiB. Questions count toward the Turn’s 8 MiB output budget. Keep credentials out of question content.
Client methods
const turn = client.sessions.ref(sessionId).turns.ref(turnId)
turn.asks.list(query?: { cursor?: string; limit?: number }, options?): Promise<AskPage>
turn.asks.get(askId: string, options?): Promise<AskState>
turn.asks.answer(
askId: string,
request: { answer: AnswerSubmission; answerId: string },
options?,
): Promise<AskAnswerReceipt>
AskPage is { items: AskState[], nextCursor? }; limit is 1–100.
answerId is required, nonempty, at most 512 bytes. Reuse the same
answerId and answer to retry an uncertain response.
AskState field |
Type |
|---|---|
id, sessionId, turnId |
string |
status |
"pending" | "answered" | "cancelled" |
question |
NormalizedQuestion, optional |
answer |
JsonValue, optional |
payloadExpired |
true, optional |
createdAt |
string |
answeredAt, cancelledAt |
string, optional |
answeredByUserId, answeredByApiKeyId |
string, optional |
AskAnswerReceipt is AskState without question.
An API key needs current Role access to answer in the target Session; see
Authentication. The CLI equivalents are
helmr session turn ask list|get|answer; see
helmr session.