requestSecretTool function
Creates the request_secret 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 prompt for secrets (the safe
fallback).
unattended reports whether the session runs unattended (approval mode
unattended/autopilot — no user present). When it answers true, a
decline carries credentialHuntNudge (gh-1412); interactive sessions
keep the short decline text.
Approval tier is ApprovalTier.read: the tool mutates nothing by itself —
the user confirms the value in the host's own prompt. Execution is forced
to ToolExecutionMode.sequential (like ask): concurrent requests would
clobber the host's single prompt surface.
Implementation
AgentTool requestSecretTool({
RequestSecretCallback? callback,
bool Function()? unattended,
}) {
return AgentTool(
name: 'request_secret',
label: 'request_secret',
tier: ApprovalTier.read,
executionMode: ToolExecutionMode.sequential,
description: requestSecretToolDescriptionPrompt,
parameters: const {
'type': 'object',
'properties': {
'name': {
'type': 'string',
'description':
'Env var name to save the credential under (UPPER_SNAKE, e.g. '
'GITHUB_TOKEN); prefilled in the user prompt',
},
'reason': {
'type': 'string',
'description':
'Why the credential is needed; shown to the user in the prompt',
},
},
'required': ['name', 'reason'],
},
execute: (arguments, cancelToken, onUpdate) async {
cancelToken?.throwIfCancelled();
final name = _validateSecretName(arguments['name']);
final reason = _validateSecretReason(arguments['reason']);
final request = callback;
if (request == null) {
throw StateError(
'This host cannot request secrets interactively (no secret prompt '
'is installed). Ask the user to add the key in the settings Keys '
'section instead.',
);
}
final result = await _awaitResult(request, name, reason, cancelToken);
if (result == null) {
final declined = 'The user declined to provide $name.';
// gh-1412 P4/AC5: in an unattended run (no user behind the
// decline) a decline must read as "hunt the standard places",
// never as a dead end — the create-bucket class died here.
if (unattended?.call() ?? false) {
return ToolExecutionResult.text('$declined\n$credentialHuntNudge');
}
return ToolExecutionResult.text(declined);
}
final saved = result.name;
if (!result.persisted) {
return ToolExecutionResult.text(
'Secret $saved is available as \$$saved for this session only '
'(the host could not persist it).',
);
}
return ToolExecutionResult.text(
'Secret $saved saved and available as \$$saved.',
);
},
);
}