dynamicMessageTool function

AgentTool dynamicMessageTool({
  1. DynamicMessageCallback? callback,
})

Creates the dynamic_message 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 present widgets (the safe fallback).

Approval tier is ApprovalTier.read: presenting a widget mutates nothing by itself — the user interacts with it in the host's own chat surface. Execution is forced to ToolExecutionMode.sequential (like ask): concurrent presentations would clobber the host's single chat surface.

Implementation

AgentTool dynamicMessageTool({DynamicMessageCallback? callback}) {
  return AgentTool(
    name: 'dynamic_message',
    label: 'dynamic_message',
    tier: ApprovalTier.read,
    executionMode: ToolExecutionMode.sequential,
    description: dynamicMessageToolDescriptionPrompt,
    parameters: const {
      'type': 'object',
      'properties': {
        'title': {
          'type': 'string',
          'description':
              'Short widget title shown above the widget and '
              'used to prefix its events back to the model',
        },
        'jsSource': {
          'type': 'string',
          'description':
              'Widget JavaScript source (at most 65536 UTF-8 bytes); uses '
              'the same jsr.fa bridges as installed apps',
        },
        'initialState': {
          'type': 'object',
          'description':
              'Optional initial state object handed to the '
              'widget on boot',
        },
        'heightHint': {
          'type': 'number',
          'description': 'Preferred widget height in logical pixels (> 0)',
        },
      },
      'required': ['title', 'jsSource'],
    },
    execute: (arguments, cancelToken, onUpdate) async {
      cancelToken?.throwIfCancelled();
      final title = _validateTitle(arguments['title']);
      final jsSource = _validateJsSource(arguments['jsSource']);
      final initialState = _validateInitialState(arguments['initialState']);
      final heightHint = _validateHeightHint(arguments['heightHint']);
      final present = callback;
      if (present == null) {
        throw StateError(
          'This host cannot present interactive widgets (no dynamic message '
          'surface is installed). Present your content as plain text '
          'instead.',
        );
      }
      final request = DynamicMessageRequest(
        title: title,
        jsSource: jsSource,
        initialState: initialState,
        heightHint: heightHint,
      );
      final id = await _awaitPresent(present, request, cancelToken);
      if (id == null) {
        return ToolExecutionResult.text(
          'The host declined to present the widget (per-run presentation '
          'cap reached or no chat session). Present your content as plain '
          'text instead.',
        );
      }
      return ToolExecutionResult.text(
        "Dynamic message '$title' presented to the user (widget $id). "
        'User interactions with it arrive as [widget $title] user messages.',
      );
    },
  );
}