Documentation
¶
Overview ¶
Package askuserquestion implements Claude Code's AskUserQuestion tool as a portable Go library that plugs into any charm.land/fantasy-based agent host.
The tool is host-rendered: the model emits a structured tool call, the host renders a picker UI, the user selects, and the canonical answer string is returned as the tool result. This package owns the schema, validation, and canonical formatting; hosts supply a Resolver that actually talks to the user (TUI modal, stdin prompt, web form, mock, whatever).
Index ¶
- Constants
- Variables
- func Description() string
- func Format(req Request, answers []Answer) string
- func NewTool(r Resolver) fantasy.AgentTool
- func NewToolNamed(name string, r Resolver) fantasy.AgentTool
- func Validate(p Params) error
- type Answer
- type Option
- type Params
- type Question
- type Request
- type Resolver
- type ResolverFunc
- type StaticResolver
- type StdinResolver
Constants ¶
const ( MinOptions = 2 MaxOptions = 4 )
MinOptions / MaxOptions bound the options-per-question range.
const MaxQuestions = 4
MaxQuestions is the upper bound on questions per call (Claude Agent SDK).
const RecommendedHeaderLen = 12
RecommendedHeaderLen is the soft cap on Header length from the SDK prose. It is advisory: Validate does not reject headers longer than this so a misbehaving model does not break the conversation, but renderers are expected to truncate to this width.
const SDKToolName = "AskUserQuestion"
SDKToolName is the spelling used by Anthropic's Claude Agent SDK.
const ToolName = "ask_user_question"
ToolName is the canonical tool name exposed to the model.
The original Claude Agent SDK calls it "AskUserQuestion". We expose snake case as the default to match Crush's naming conventions; hosts that want the SDK-compatible spelling can pass a custom name to NewToolNamed.
Variables ¶
var ( // ErrNoQuestions means the model called the tool with an empty questions // array. The SDK requires at least one question. ErrNoQuestions = errors.New("askuserquestion: questions must be a non-empty array") // ErrTooManyQuestions means more than 4 questions were supplied. ErrTooManyQuestions = errors.New("askuserquestion: maximum 4 questions allowed") // ErrEmptyQuestion means a Question.Question field was empty. ErrEmptyQuestion = errors.New("askuserquestion: each question must have non-empty question text") // ErrEmptyHeader means a Question.Header field was empty. ErrEmptyHeader = errors.New("askuserquestion: each question must have a non-empty header") // ErrOptionCount means options length was outside [2, 4]. ErrOptionCount = errors.New("askuserquestion: each question must have 2-4 options") // ErrEmptyLabel means an Option.Label field was empty. ErrEmptyLabel = errors.New("askuserquestion: each option must have a non-empty label") // ErrDuplicateLabel means a single question listed the same label twice. ErrDuplicateLabel = errors.New("askuserquestion: option labels within a question must be unique") )
Validation errors. Wrapped by Validate so callers can check with errors.Is.
var ErrResolverCancelled = errors.New("askuserquestion: resolver cancelled by user")
ErrResolverCancelled is returned by Resolvers when the user dismisses or supersedes the request (for example by sending a fresh chat message before answering). The tool surfaces this as a non-error tool result so the conversation stays valid.
Functions ¶
func Description ¶
func Description() string
Description returns the canonical tool description shipped with the SDK. Exposed so hosts that build their own ToolInfo can reuse the exact prose Claude was trained against.
func Format ¶
Format renders the user's answers into the canonical tool-result string used by the Claude Agent SDK. The format is:
<question 1> - <label> - <label> <question 2> <label>
Rules:
- questions are joined by a blank line in the original Question order
- if Answer.Other is set, its text is emitted verbatim as the body
- if Question.MultiSelect is true, each selected label is prefixed "- "
- if Question.MultiSelect is false, the single label is emitted bare
- answers are paired by index; missing answers are skipped
Format is deterministic and side-effect-free; it is the only canonical answer formatter in this package.
func NewTool ¶
NewTool builds a fantasy.AgentTool using the default tool name (ToolName) and the given Resolver. The returned tool validates inputs against the Claude Agent SDK schema, hands valid requests to the Resolver, and formats the user's answers using Format.
On validation failure the tool returns a non-error ToolResponse with IsError=true so the model can self-correct on the next turn. On resolver cancellation (ErrResolverCancelled) it returns a non-error response with a stable "[cancelled]" body so the conversation stays valid.
func NewToolNamed ¶
NewToolNamed is like NewTool but lets the host override the tool name. Pass SDKToolName ("AskUserQuestion") for strict Claude Agent SDK parity.
func Validate ¶
Validate enforces the structural rules from the Claude Agent SDK schema:
- 1 <= len(Questions) <= MaxQuestions
- each Question has non-empty Question and Header
- MinOptions <= len(Options) <= MaxOptions
- each Option has a non-empty Label
- Option labels are unique within a question
Validate returns one of the sentinel errors from errors.go so callers can branch on errors.Is. The header length cap is advisory and not checked.
Types ¶
type Answer ¶
type Answer struct {
// Question is the original question text. Used to pair answers back to
// questions when order is unstable.
Question string
// Selected is the list of option labels the user chose. For single-select
// questions this has at most one element.
Selected []string
// Other is free-text the user typed when they picked the implicit "Other"
// option. If set, Selected is ignored by Format.
Other string
}
Answer is the user's response to a single Question.
Exactly one of Selected or Other should be populated. Hosts that allow the user to pick "Other" set Other to the typed text and leave Selected empty.
type Option ¶
type Option struct {
// Label is the short display text for this option.
Label string `json:"label" description:"The display text for this option"`
// Description explains what selecting this option means.
Description string `json:"description" description:"Explanation of what this option means"`
}
Option is one choice within a Question.
type Params ¶
type Params struct {
// Questions is the ordered list of questions to ask. 1-4 entries.
Questions []Question `json:"questions" description:"Questions to ask the user (1-4 questions)"`
}
Params is the top-level input schema for the tool.
JSON tags match the Claude Agent SDK wire format exactly so models trained against the SDK can drive this implementation without prompt adjustments.
type Question ¶
type Question struct {
// Question is the full prompt shown to the user.
Question string `json:"question" description:"The complete question to ask the user"`
// Header is a very short label (<= 12 chars) used as a column header or
// compact title in the picker UI. Examples: "Auth method", "Library".
Header string `json:"header" description:"Very short label (max 12 chars). Examples: \"Auth method\", \"Library\""`
// Options are the available choices. 2-4 entries.
Options []Option `json:"options" description:"The available choices (2-4 options)"`
// MultiSelect allows the user to pick more than one option.
MultiSelect bool `json:"multiSelect" description:"Set to true to allow multiple selections"`
}
Question is a single question with multiple-choice options.
type Request ¶
type Request struct {
// ToolCallID is the fantasy.ToolCall.ID for this invocation.
ToolCallID string
// Questions is the validated question set.
Questions []Question
}
Request is what the tool hands to a Resolver. It carries the model-supplied questions plus the upstream tool-call ID so hosts that route through a pubsub broker or stream-json child can correlate the reply.
type Resolver ¶
Resolver is the host-supplied seam that actually asks the user. The library validates the model's input, builds a Request, and hands it to Ask. The Resolver returns one Answer per Question (in the same order) or an error.
Implementations should:
- respect ctx cancellation (e.g. the user closed the session)
- return ErrResolverCancelled when the user dismisses/supersedes
- never return more answers than there are questions
type ResolverFunc ¶
ResolverFunc adapts a plain function into a Resolver.
type StaticResolver ¶
StaticResolver returns the same canned answers for every call. Useful for tests, dry runs, and golden-file snapshots.
type StdinResolver ¶
StdinResolver is a minimal CLI resolver: it prints each question to Out and reads selections from In. Options are numbered starting at 1; the user types the number, a comma-separated list of numbers for multi-select, or the literal word "other" followed by free text. Empty input cancels.
This resolver exists primarily for examples and headless smoke tests. Production hosts should implement a richer UI (TUI modal, web form, etc.).
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
cli
command
Command askuserquestion-demo wires the askuserquestion tool to stdin/stdout so you can see the full request/answer loop without a real model.
|
Command askuserquestion-demo wires the askuserquestion tool to stdin/stdout so you can see the full request/answer loop without a real model. |