askTool function
Creates the ask tool bound to callback.
When callback is null (headless/non-interactive host), executing the
tool throws — the agent loop converts it into an error tool result
telling the model this host cannot answer questions (the safe fallback).
The tool forces its tool-call batch to ToolExecutionMode.sequential
(omp's concurrency = "exclusive"): concurrent ask calls would clobber
the host's single question surface.
Implementation
AgentTool askTool({AskCallback? callback}) {
return AgentTool(
name: 'ask',
label: 'ask',
tier: ApprovalTier.read,
executionMode: ToolExecutionMode.sequential,
description: askToolDescriptionPrompt,
parameters: const {
'type': 'object',
'properties': {
'questions': {
'type': 'array',
'description': 'Questions to ask the user (at least one)',
'items': {
'type': 'object',
'properties': {
'question': {
'type': 'string',
'description': 'Question text shown to the user',
},
'options': {
'type': 'array',
'description':
'Picker options (2-5 concise, distinct options); omit '
'for a free-form answer',
'items': {
'type': 'object',
'properties': {
'label': {
'type': 'string',
'description': 'Short display label',
},
'description': {
'type': 'string',
'description':
'Optional explanatory text shown with the label',
},
},
'required': ['label'],
},
},
'multiSelect': {
'type': 'boolean',
'description': 'Allow multiple selections (default: false)',
},
'recommended': {
'type': ['integer', 'string'],
'description':
'Recommended option: 0-based index or exact label; '
'rendered as a "Recommended" badge',
},
},
'required': ['question'],
},
},
},
'required': ['questions'],
},
execute: (arguments, cancelToken, onUpdate) async {
cancelToken?.throwIfCancelled();
final questions = _parseQuestions(arguments['questions']);
final ask = callback;
if (ask == null) {
throw StateError(
'This host cannot answer questions interactively (no ask UI is '
'installed). Ask the user in plain text instead.',
);
}
final answers = await _awaitAnswers(ask, questions, cancelToken);
if (answers == null) {
return ToolExecutionResult.text(
'The user cancelled the question dialog without answering. '
'Proceed with a reasonable default, or ask in plain text if the '
'input is critical.',
);
}
return ToolExecutionResult.text(_formatAnswers(questions, answers));
},
);
}