requestSecretTool function

AgentTool requestSecretTool({
  1. RequestSecretCallback? callback,
  2. bool unattended()?,
})

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.',
      );
    },
  );
}