browserTools function
List<AgentTool>
browserTools({
- required BrowserController controller,
- required Future<
String> saveScreenshot(- Uint8List png
Builds the browser tool family over controller. Every tool is exec-tier
(the AgentTool default) and throws BrowserToolException on failure.
Implementation
List<AgentTool> browserTools({
required BrowserController controller,
required Future<String> Function(Uint8List png) saveScreenshot,
}) {
AgentTool tool(
String name,
String description,
Map<String, dynamic> parameters,
Future<ToolExecutionResult> Function(Map<String, dynamic>) run,
) => AgentTool(
name: name,
label: name,
description: description,
parameters: parameters,
execute: (arguments, cancelToken, onUpdate) async {
cancelToken?.throwIfCancelled();
return run(arguments);
},
);
final tabIdProp = {'type': 'integer', 'description': _tabPin};
return [
tool(
'browser_navigate',
'Navigates a browser tab to a URL. Returns the tab id, final URL, and '
'page title — use the returned tabId to pin later ops. '
'$_restrictedNote',
{
'type': 'object',
'properties': {
'url': {'type': 'string', 'description': 'URL to navigate to'},
'tabId': tabIdProp,
},
'required': ['url'],
},
(args) async {
final nav = await controller.navigate(
_reqString(args, 'url'),
tabId: args['tabId'] as int?,
);
return ToolExecutionResult.text(
'navigated tab ${nav.tabId} to ${nav.url} — ${nav.title}',
);
},
),
tool(
'browser_tabs',
'Lists open tabs with id, url, title, the active one, and the tab '
'group. Tabs this agent opened live in a labelled task group. Use '
'a listed id as tabId to pin later ops.',
{'type': 'object', 'properties': {}},
(args) async {
final tabs = await controller.listTabs();
if (tabs.isEmpty) {
return ToolExecutionResult.text('no tabs open');
}
return ToolExecutionResult.text(
[
for (final tab in tabs)
'tab ${tab.id}: ${tab.title} ${tab.url}'
'${tab.active ? ' — ACTIVE' : ''}'
'${tab.groupId == null ? '' : ' — group ${tab.groupId}'}',
].join('\n'),
);
},
),
tool(
'browser_switch_tab',
'Makes the given tab the active one (later unpinned ops use it).',
{
'type': 'object',
'properties': {
'tabId': {'type': 'integer', 'description': 'tab to activate'},
},
'required': ['tabId'],
},
(args) async {
final tabId = _reqInt(args, 'tabId');
await controller.switchTab(tabId);
return ToolExecutionResult.text('switched to tab $tabId');
},
),
tool(
'browser_click',
'Clicks the element matching a CSS selector. Run browser_read_dom '
'first to discover selectors. $_restrictedNote',
{
'type': 'object',
'properties': {
'selector': {
'type': 'string',
'description': 'CSS selector of the element to click',
},
'tabId': tabIdProp,
},
'required': ['selector'],
},
(args) async {
final selector = _reqString(args, 'selector');
await controller.click(selector, tabId: args['tabId'] as int?);
return ToolExecutionResult.text('clicked $selector');
},
),
tool(
'browser_type',
'Types text into the element matching a CSS selector; submit=true '
'presses Enter afterwards. $_restrictedNote',
{
'type': 'object',
'properties': {
'selector': {
'type': 'string',
'description': 'CSS selector of the input element',
},
'text': {'type': 'string', 'description': 'text to type'},
'submit': {
'type': 'boolean',
'description': 'press Enter after typing (default false)',
},
'tabId': tabIdProp,
},
'required': ['selector', 'text'],
},
(args) async {
final selector = _reqString(args, 'selector');
final text = _reqString(args, 'text');
final submit = _optBool(args, 'submit');
await controller.type(
selector,
text,
submit: submit,
tabId: args['tabId'] as int?,
);
return ToolExecutionResult.text(
'typed ${text.length} characters into $selector'
'${submit ? ' and pressed Enter' : ''}',
);
},
),
tool(
'browser_press_key',
'Presses a key — Enter, Tab, Escape, Backspace, Delete, ArrowDown, '
'ArrowUp, ArrowLeft, ArrowRight, Home, End, PageUp, PageDown — '
'optionally focused on a CSS selector. $_restrictedNote',
{
'type': 'object',
'properties': {
'key': {
'type': 'string',
'description': 'key name, e.g. Enter, Tab, Escape, ArrowDown',
},
'selector': {
'type': 'string',
'description': 'CSS selector to focus first (optional)',
},
'tabId': tabIdProp,
},
'required': ['key'],
},
(args) async {
final key = _reqString(args, 'key');
await controller.pressKey(
key,
selector: args['selector'] as String?,
tabId: args['tabId'] as int?,
);
return ToolExecutionResult.text('pressed $key');
},
),
tool(
'browser_select',
'Selects an option by value in the <select> matching a CSS selector. '
'$_restrictedNote',
{
'type': 'object',
'properties': {
'selector': {
'type': 'string',
'description': 'CSS selector of the <select> element',
},
'value': {'type': 'string', 'description': 'option value to select'},
'tabId': tabIdProp,
},
'required': ['selector', 'value'],
},
(args) async {
final selector = _reqString(args, 'selector');
final value = _reqString(args, 'value');
await controller.select(selector, value, tabId: args['tabId'] as int?);
return ToolExecutionResult.text('selected "$value" in $selector');
},
),
tool(
'browser_read_dom',
'Reads a bounded serialized DOM subtree where every element carries '
'its CSS selector — call this BEFORE browser_click/browser_type '
'to discover selectors. nodeCount/truncated tell you when to '
'narrow with selector or maxNodes. $_restrictedNote',
{
'type': 'object',
'properties': {
'selector': {
'type': 'string',
'description': 'CSS selector of the subtree root (default: page)',
},
'maxNodes': {
'type': 'integer',
'description': 'node budget (default 500, max 5000)',
},
'includeShadow': {
'type': 'boolean',
'description': 'include shadow DOM (default false)',
},
'tabId': tabIdProp,
},
},
(args) async {
final maxNodes = args['maxNodes'] as int?;
if (maxNodes != null && maxNodes > 5000) {
throw BrowserToolException('bad_args', 'maxNodes must be <= 5000');
}
final dom = await controller.readDom(
selector: args['selector'] as String?,
maxNodes: maxNodes,
includeShadow: _optBool(args, 'includeShadow'),
tabId: args['tabId'] as int?,
);
return ToolExecutionResult.text(
'${dom.dom}\n'
'[${dom.nodeCount} nodes'
'${dom.truncated ? ', TRUNCATED — narrow with selector '
'or maxNodes' : ''}]',
);
},
),
tool(
'browser_eval',
'Evaluates JavaScript in the page\'s isolated world; async code is '
'supported (a returned Promise is awaited and its resolution is '
'the result). Prefer the dedicated browser_* tools for DOM '
'actions. $_restrictedNote',
{
'type': 'object',
'properties': {
'code': {'type': 'string', 'description': 'JavaScript to evaluate'},
'tabId': tabIdProp,
},
'required': ['code'],
},
(args) async {
final result = await controller.evalCode(
_reqString(args, 'code'),
tabId: args['tabId'] as int?,
);
return ToolExecutionResult.text('eval result: ${jsonEncode(result)}');
},
),
tool(
'browser_screenshot',
'Captures a PNG screenshot of a tab. Returns the saved file path and '
'the image inline. $_restrictedNote',
{
'type': 'object',
'properties': {'tabId': tabIdProp},
},
(args) async {
final png = await controller.screenshot(tabId: args['tabId'] as int?);
final path = await saveScreenshot(png);
return ToolExecutionResult(
content: [
TextContent(
text: '\nsaved screenshot to $path',
),
ImageContent(data: base64Encode(png), mimeType: 'image/png'),
],
);
},
),
tool(
'browser_wait_for',
'Waits until text appears or a CSS selector matches a tab (give one '
'of them), up to timeoutMs — use after navigation or clicks that '
'trigger async updates. $_restrictedNote',
{
'type': 'object',
'properties': {
'selector': {
'type': 'string',
'description': 'CSS selector to wait for',
},
'text': {'type': 'string', 'description': 'text to wait for'},
'timeoutMs': {
'type': 'integer',
'description': 'give up after this long (default 10000, max 30000)',
},
'tabId': tabIdProp,
},
},
(args) async {
final selector = args['selector'] as String?;
final text = args['text'] as String?;
final timeoutMs = args['timeoutMs'] as int? ?? 10000;
if (timeoutMs > 30000) {
throw BrowserToolException('bad_args', 'timeoutMs must be <= 30000');
}
if (selector == null && text == null) {
throw BrowserToolException(
'bad_args',
'one of selector or text is required',
);
}
final wait = await controller.waitFor(
selector: selector,
text: text,
timeoutMs: timeoutMs,
tabId: args['tabId'] as int?,
);
final target = selector ?? text;
return ToolExecutionResult.text(
'found "$target" after ${wait.waitedMs}ms',
);
},
),
];
}