droidrun 0.3.1__py3-none-any.whl → 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- droidrun/__init__.py +1 -10
- droidrun/adb/device.py +101 -71
- droidrun/adb/manager.py +3 -3
- droidrun/agent/codeact/codeact_agent.py +2 -1
- droidrun/agent/context/personas/__init__.py +0 -2
- droidrun/agent/droid/droid_agent.py +50 -7
- droidrun/agent/droid/events.py +4 -0
- droidrun/agent/utils/llm_picker.py +1 -0
- droidrun/cli/main.py +126 -70
- droidrun/portal.py +139 -0
- droidrun/telemetry/__init__.py +4 -0
- droidrun/telemetry/events.py +27 -0
- droidrun/telemetry/tracker.py +83 -0
- droidrun/tools/adb.py +82 -218
- droidrun/tools/ios.py +6 -3
- droidrun/tools/tools.py +41 -6
- {droidrun-0.3.1.dist-info → droidrun-0.3.2.dist-info}/METADATA +17 -28
- {droidrun-0.3.1.dist-info → droidrun-0.3.2.dist-info}/RECORD +21 -18
- droidrun/agent/context/personas/extractor.py +0 -52
- {droidrun-0.3.1.dist-info → droidrun-0.3.2.dist-info}/WHEEL +0 -0
- {droidrun-0.3.1.dist-info → droidrun-0.3.2.dist-info}/entry_points.txt +0 -0
- {droidrun-0.3.1.dist-info → droidrun-0.3.2.dist-info}/licenses/LICENSE +0 -0
droidrun/tools/adb.py
CHANGED
@@ -3,29 +3,31 @@ UI Actions - Core UI interaction tools for Android device control.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import os
|
6
|
-
import re
|
7
6
|
import json
|
8
7
|
import time
|
9
|
-
import tempfile
|
10
8
|
import asyncio
|
11
|
-
import aiofiles
|
12
|
-
import contextlib
|
13
9
|
import logging
|
14
|
-
from typing import Optional, Dict, Tuple, List, Any
|
10
|
+
from typing import Optional, Dict, Tuple, List, Any, Type, Self
|
15
11
|
from droidrun.adb.device import Device
|
16
12
|
from droidrun.adb.manager import DeviceManager
|
17
13
|
from droidrun.tools.tools import Tools
|
18
14
|
|
19
15
|
logger = logging.getLogger("droidrun-adb-tools")
|
20
16
|
|
17
|
+
|
21
18
|
class AdbTools(Tools):
|
22
19
|
"""Core UI interaction tools for Android device control."""
|
23
20
|
|
24
|
-
def __init__(self, serial: str
|
21
|
+
def __init__(self, serial: str) -> None:
|
22
|
+
"""Initialize the AdbTools instance.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
serial: Device serial number
|
26
|
+
"""
|
27
|
+
self.device_manager = DeviceManager()
|
25
28
|
# Instance‐level cache for clickable elements (index-based tapping)
|
26
29
|
self.clickable_elements_cache: List[Dict[str, Any]] = []
|
27
30
|
self.serial = serial
|
28
|
-
self.device_manager = DeviceManager()
|
29
31
|
self.last_screenshot = None
|
30
32
|
self.reason = None
|
31
33
|
self.success = None
|
@@ -35,19 +37,38 @@ class AdbTools(Tools):
|
|
35
37
|
# Store all screenshots with timestamps
|
36
38
|
self.screenshots: List[Dict[str, Any]] = []
|
37
39
|
|
38
|
-
|
40
|
+
@classmethod
|
41
|
+
async def create(cls: Type[Self], serial: str = None) -> Self:
|
42
|
+
"""Create an AdbTools instance.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
serial: Optional device serial number. If not provided, the first device found will be used.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
AdbTools instance
|
49
|
+
"""
|
50
|
+
if not serial:
|
51
|
+
dvm = DeviceManager()
|
52
|
+
devices = await dvm.list_devices()
|
53
|
+
if not devices or len(devices) < 1:
|
54
|
+
raise ValueError("No devices found")
|
55
|
+
serial = devices[0].serial
|
56
|
+
|
57
|
+
return AdbTools(serial)
|
58
|
+
|
59
|
+
def _get_device_serial(self) -> str:
|
39
60
|
"""Get the device serial from the instance or environment variable."""
|
40
61
|
# First try using the instance's serial
|
41
62
|
if self.serial:
|
42
63
|
return self.serial
|
43
64
|
|
44
|
-
async def
|
65
|
+
async def _get_device(self) -> Optional[Device]:
|
45
66
|
"""Get the device instance using the instance's serial or from environment variable.
|
46
67
|
|
47
68
|
Returns:
|
48
69
|
Device instance or None if not found
|
49
70
|
"""
|
50
|
-
serial = self.
|
71
|
+
serial = self._get_device_serial()
|
51
72
|
if not serial:
|
52
73
|
raise ValueError("No device serial specified - set device_serial parameter")
|
53
74
|
|
@@ -57,63 +78,46 @@ class AdbTools(Tools):
|
|
57
78
|
|
58
79
|
return device
|
59
80
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
Args:
|
64
|
-
output: Raw command output from 'pm list packages -f'
|
65
|
-
|
66
|
-
Returns:
|
67
|
-
List of dictionaries containing package info with 'package' and 'path' keys
|
68
|
-
"""
|
69
|
-
apps = []
|
70
|
-
for line in output.splitlines():
|
71
|
-
if line.startswith("package:"):
|
72
|
-
# Format is: "package:/path/to/base.apk=com.package.name"
|
73
|
-
path_and_pkg = line[8:] # Strip "package:"
|
74
|
-
if "=" in path_and_pkg:
|
75
|
-
path, package = path_and_pkg.rsplit("=", 1)
|
76
|
-
apps.append({"package": package.strip(), "path": path.strip()})
|
77
|
-
return apps
|
78
|
-
|
79
|
-
def _parse_content_provider_output(self, raw_output: str) -> Optional[Dict[str, Any]]:
|
81
|
+
def _parse_content_provider_output(
|
82
|
+
self, raw_output: str
|
83
|
+
) -> Optional[Dict[str, Any]]:
|
80
84
|
"""
|
81
85
|
Parse the raw ADB content provider output and extract JSON data.
|
82
|
-
|
86
|
+
|
83
87
|
Args:
|
84
88
|
raw_output (str): Raw output from ADB content query command
|
85
|
-
|
89
|
+
|
86
90
|
Returns:
|
87
91
|
dict: Parsed JSON data or None if parsing failed
|
88
92
|
"""
|
89
93
|
# The ADB content query output format is: "Row: 0 result={json_data}"
|
90
94
|
# We need to extract the JSON part after "result="
|
91
|
-
lines = raw_output.strip().split(
|
92
|
-
|
95
|
+
lines = raw_output.strip().split("\n")
|
96
|
+
|
93
97
|
for line in lines:
|
94
98
|
line = line.strip()
|
95
|
-
|
99
|
+
|
96
100
|
# Look for lines that contain "result=" pattern
|
97
101
|
if "result=" in line:
|
98
102
|
# Extract everything after "result="
|
99
103
|
result_start = line.find("result=") + 7
|
100
104
|
json_str = line[result_start:]
|
101
|
-
|
105
|
+
|
102
106
|
try:
|
103
107
|
# Parse the JSON string
|
104
108
|
json_data = json.loads(json_str)
|
105
109
|
return json_data
|
106
110
|
except json.JSONDecodeError:
|
107
111
|
continue
|
108
|
-
|
112
|
+
|
109
113
|
# Fallback: try to parse lines that start with { or [
|
110
|
-
elif line.startswith(
|
114
|
+
elif line.startswith("{") or line.startswith("["):
|
111
115
|
try:
|
112
116
|
json_data = json.loads(line)
|
113
117
|
return json_data
|
114
118
|
except json.JSONDecodeError:
|
115
119
|
continue
|
116
|
-
|
120
|
+
|
117
121
|
# If no valid JSON found in individual lines, try the entire output
|
118
122
|
try:
|
119
123
|
json_data = json.loads(raw_output.strip())
|
@@ -199,7 +203,7 @@ class AdbTools(Tools):
|
|
199
203
|
if not device:
|
200
204
|
return f"Error: Device {serial} not found"
|
201
205
|
else:
|
202
|
-
device = await self.
|
206
|
+
device = await self._get_device()
|
203
207
|
|
204
208
|
await device.tap(x, y)
|
205
209
|
|
@@ -246,7 +250,7 @@ class AdbTools(Tools):
|
|
246
250
|
if not device:
|
247
251
|
return f"Error: Device {self.serial} not found"
|
248
252
|
else:
|
249
|
-
device = await self.
|
253
|
+
device = await self._get_device()
|
250
254
|
|
251
255
|
await device.tap(x, y)
|
252
256
|
print(f"Tapped at coordinates ({x}, {y})")
|
@@ -292,11 +296,13 @@ class AdbTools(Tools):
|
|
292
296
|
if not device:
|
293
297
|
return f"Error: Device {self.serial} not found"
|
294
298
|
else:
|
295
|
-
device = await self.
|
299
|
+
device = await self._get_device()
|
296
300
|
|
297
301
|
await device.swipe(start_x, start_y, end_x, end_y, duration_ms)
|
298
302
|
await asyncio.sleep(1)
|
299
|
-
print(
|
303
|
+
print(
|
304
|
+
f"Swiped from ({start_x}, {start_y}) to ({end_x}, {end_y}) in {duration_ms}ms"
|
305
|
+
)
|
300
306
|
return True
|
301
307
|
except ValueError as e:
|
302
308
|
print(f"Error: {str(e)}")
|
@@ -319,7 +325,7 @@ class AdbTools(Tools):
|
|
319
325
|
if not device:
|
320
326
|
return f"Error: Device {serial} not found"
|
321
327
|
else:
|
322
|
-
device = await self.
|
328
|
+
device = await self._get_device()
|
323
329
|
|
324
330
|
# Save the current keyboard
|
325
331
|
original_ime = await device._adb.shell(
|
@@ -372,7 +378,7 @@ class AdbTools(Tools):
|
|
372
378
|
if not device:
|
373
379
|
return f"Error: Device {self.serial} not found"
|
374
380
|
else:
|
375
|
-
device = await self.
|
381
|
+
device = await self._get_device()
|
376
382
|
|
377
383
|
await device.press_key(3)
|
378
384
|
return f"Pressed key BACK"
|
@@ -398,7 +404,7 @@ class AdbTools(Tools):
|
|
398
404
|
if not device:
|
399
405
|
return f"Error: Device {self.serial} not found"
|
400
406
|
else:
|
401
|
-
device = await self.
|
407
|
+
device = await self._get_device()
|
402
408
|
|
403
409
|
key_names = {
|
404
410
|
66: "ENTER",
|
@@ -427,7 +433,7 @@ class AdbTools(Tools):
|
|
427
433
|
if not device:
|
428
434
|
return f"Error: Device {self.serial} not found"
|
429
435
|
else:
|
430
|
-
device = await self.
|
436
|
+
device = await self._get_device()
|
431
437
|
|
432
438
|
result = await device.start_app(package, activity)
|
433
439
|
return result
|
@@ -451,7 +457,7 @@ class AdbTools(Tools):
|
|
451
457
|
if not device:
|
452
458
|
return f"Error: Device {self.serial} not found"
|
453
459
|
else:
|
454
|
-
device = await self.
|
460
|
+
device = await self._get_device()
|
455
461
|
|
456
462
|
if not os.path.exists(apk_path):
|
457
463
|
return f"Error: APK file not found at {apk_path}"
|
@@ -473,7 +479,7 @@ class AdbTools(Tools):
|
|
473
479
|
if not device:
|
474
480
|
raise ValueError(f"Device {self.serial} not found")
|
475
481
|
else:
|
476
|
-
device = await self.
|
482
|
+
device = await self._get_device()
|
477
483
|
screen_tuple = await device.take_screenshot()
|
478
484
|
self.last_screenshot = screen_tuple[1]
|
479
485
|
|
@@ -505,158 +511,12 @@ class AdbTools(Tools):
|
|
505
511
|
if not device:
|
506
512
|
raise ValueError(f"Device {self.serial} not found")
|
507
513
|
else:
|
508
|
-
device = await self.
|
509
|
-
|
510
|
-
|
511
|
-
cmd = ["pm", "list", "packages", "-f"]
|
512
|
-
if not include_system_apps:
|
513
|
-
cmd.append("-3")
|
514
|
-
|
515
|
-
output = await device._adb.shell(device._serial, " ".join(cmd))
|
516
|
-
|
517
|
-
# Parse the package list using the function
|
518
|
-
packages = self.parse_package_list(output)
|
519
|
-
# Format package list for better readability
|
520
|
-
package_list = [pack["package"] for pack in packages]
|
521
|
-
for package in package_list:
|
522
|
-
print(package)
|
523
|
-
return package_list
|
514
|
+
device = await self._get_device()
|
515
|
+
|
516
|
+
return await device.list_packages(include_system_apps)
|
524
517
|
except ValueError as e:
|
525
518
|
raise ValueError(f"Error listing packages: {str(e)}")
|
526
519
|
|
527
|
-
async def extract(self, filename: Optional[str] = None) -> str:
|
528
|
-
"""Extract and save the current UI state to a JSON file.
|
529
|
-
|
530
|
-
This function captures the current UI state including all UI elements
|
531
|
-
and saves it to a JSON file for later analysis or reference.
|
532
|
-
|
533
|
-
Args:
|
534
|
-
filename: Optional filename to save the UI state (defaults to ui_state_TIMESTAMP.json)
|
535
|
-
|
536
|
-
Returns:
|
537
|
-
Path to the saved JSON file
|
538
|
-
"""
|
539
|
-
try:
|
540
|
-
# Generate default filename if not provided
|
541
|
-
if not filename:
|
542
|
-
timestamp = int(time.time())
|
543
|
-
filename = f"ui_state_{timestamp}.json"
|
544
|
-
|
545
|
-
# Ensure the filename ends with .json
|
546
|
-
if not filename.endswith(".json"):
|
547
|
-
filename += ".json"
|
548
|
-
|
549
|
-
# Get the UI elements
|
550
|
-
ui_elements = await self.get_all_elements(self.serial)
|
551
|
-
|
552
|
-
# Save to file
|
553
|
-
save_path = os.path.abspath(filename)
|
554
|
-
async with aiofiles.open(save_path, "w", encoding="utf-8") as f:
|
555
|
-
await f.write(json.dumps(ui_elements, indent=2))
|
556
|
-
|
557
|
-
return f"UI state extracted and saved to {save_path}"
|
558
|
-
|
559
|
-
except Exception as e:
|
560
|
-
return f"Error extracting UI state: {e}"
|
561
|
-
|
562
|
-
async def get_all_elements(self) -> Dict[str, Any]:
|
563
|
-
"""
|
564
|
-
Get all UI elements from the device, including non-interactive elements.
|
565
|
-
|
566
|
-
This function interacts with the TopViewService app installed on the device
|
567
|
-
to capture all UI elements, even those that are not interactive. This provides
|
568
|
-
a complete view of the UI hierarchy for analysis or debugging purposes.
|
569
|
-
|
570
|
-
Returns:
|
571
|
-
Dictionary containing all UI elements extracted from the device screen
|
572
|
-
"""
|
573
|
-
try:
|
574
|
-
# Get the device
|
575
|
-
device = await self.device_manager.get_device(self.serial)
|
576
|
-
if not device:
|
577
|
-
raise ValueError(f"Device {self.serial} not found")
|
578
|
-
|
579
|
-
# Create a temporary file for the JSON
|
580
|
-
with tempfile.NamedTemporaryFile(suffix=".json") as temp:
|
581
|
-
local_path = temp.name
|
582
|
-
|
583
|
-
try:
|
584
|
-
# Clear logcat to make it easier to find our output
|
585
|
-
await device._adb.shell(device._serial, "logcat -c")
|
586
|
-
|
587
|
-
# Trigger the custom service via broadcast to get ALL elements
|
588
|
-
await device._adb.shell(
|
589
|
-
device._serial,
|
590
|
-
"am broadcast -a com.droidrun.portal.GET_ALL_ELEMENTS",
|
591
|
-
)
|
592
|
-
|
593
|
-
# Poll for the JSON file path
|
594
|
-
start_time = asyncio.get_event_loop().time()
|
595
|
-
max_wait_time = 10 # Maximum wait time in seconds
|
596
|
-
poll_interval = 0.2 # Check every 200ms
|
597
|
-
|
598
|
-
device_path = None
|
599
|
-
while asyncio.get_event_loop().time() - start_time < max_wait_time:
|
600
|
-
# Check logcat for the file path
|
601
|
-
logcat_output = await device._adb.shell(
|
602
|
-
device._serial,
|
603
|
-
'logcat -d | grep "DROIDRUN_FILE" | grep "JSON data written to" | tail -1',
|
604
|
-
)
|
605
|
-
|
606
|
-
# Parse the file path if present
|
607
|
-
match = re.search(r"JSON data written to: (.*)", logcat_output)
|
608
|
-
if match:
|
609
|
-
device_path = match.group(1).strip()
|
610
|
-
break
|
611
|
-
|
612
|
-
# Wait before polling again
|
613
|
-
await asyncio.sleep(poll_interval)
|
614
|
-
|
615
|
-
# Check if we found the file path
|
616
|
-
if not device_path:
|
617
|
-
raise ValueError(
|
618
|
-
f"Failed to find the JSON file path in logcat after {max_wait_time} seconds"
|
619
|
-
)
|
620
|
-
|
621
|
-
logger.debug(f"Pulling file from {device_path} to {local_path}")
|
622
|
-
# Pull the JSON file from the device
|
623
|
-
await device._adb.pull_file(device._serial, device_path, local_path)
|
624
|
-
|
625
|
-
# Read the JSON file
|
626
|
-
async with aiofiles.open(local_path, "r", encoding="utf-8") as f:
|
627
|
-
json_content = await f.read()
|
628
|
-
|
629
|
-
# Clean up the temporary file
|
630
|
-
with contextlib.suppress(OSError):
|
631
|
-
os.unlink(local_path)
|
632
|
-
|
633
|
-
# Try to parse the JSON
|
634
|
-
import json
|
635
|
-
|
636
|
-
try:
|
637
|
-
ui_data = json.loads(json_content)
|
638
|
-
|
639
|
-
return {
|
640
|
-
"all_elements": ui_data,
|
641
|
-
"count": (
|
642
|
-
len(ui_data)
|
643
|
-
if isinstance(ui_data, list)
|
644
|
-
else sum(1 for _ in ui_data.get("elements", []))
|
645
|
-
),
|
646
|
-
"message": "Retrieved all UI elements from the device screen",
|
647
|
-
}
|
648
|
-
except json.JSONDecodeError:
|
649
|
-
raise ValueError("Failed to parse UI elements JSON data")
|
650
|
-
|
651
|
-
except Exception as e:
|
652
|
-
# Clean up in case of error
|
653
|
-
with contextlib.suppress(OSError):
|
654
|
-
os.unlink(local_path)
|
655
|
-
raise ValueError(f"Error retrieving all UI elements: {e}")
|
656
|
-
|
657
|
-
except Exception as e:
|
658
|
-
raise ValueError(f"Error getting all UI elements: {e}")
|
659
|
-
|
660
520
|
def complete(self, success: bool, reason: str = ""):
|
661
521
|
"""
|
662
522
|
Mark the task as finished.
|
@@ -722,26 +582,26 @@ class AdbTools(Tools):
|
|
722
582
|
Returns:
|
723
583
|
Dictionary containing both 'a11y_tree' and 'phone_state' data
|
724
584
|
"""
|
725
|
-
|
585
|
+
|
726
586
|
try:
|
727
587
|
if serial:
|
728
588
|
device = await self.device_manager.get_device(serial)
|
729
589
|
if not device:
|
730
590
|
raise ValueError(f"Device {serial} not found")
|
731
591
|
else:
|
732
|
-
device = await self.
|
592
|
+
device = await self._get_device()
|
733
593
|
|
734
594
|
adb_output = await device._adb.shell(
|
735
595
|
device._serial,
|
736
|
-
|
596
|
+
"content query --uri content://com.droidrun.portal/state",
|
737
597
|
)
|
738
598
|
|
739
599
|
state_data = self._parse_content_provider_output(adb_output)
|
740
|
-
|
600
|
+
|
741
601
|
if state_data is None:
|
742
602
|
return {
|
743
603
|
"error": "Parse Error",
|
744
|
-
"message": "Failed to parse state data from ContentProvider response"
|
604
|
+
"message": "Failed to parse state data from ContentProvider response",
|
745
605
|
}
|
746
606
|
|
747
607
|
if isinstance(state_data, dict) and "data" in state_data:
|
@@ -751,25 +611,25 @@ class AdbTools(Tools):
|
|
751
611
|
except json.JSONDecodeError:
|
752
612
|
return {
|
753
613
|
"error": "Parse Error",
|
754
|
-
"message": "Failed to parse JSON data from ContentProvider data field"
|
614
|
+
"message": "Failed to parse JSON data from ContentProvider data field",
|
755
615
|
}
|
756
616
|
else:
|
757
617
|
return {
|
758
618
|
"error": "Format Error",
|
759
|
-
"message": f"Unexpected state data format: {type(state_data)}"
|
619
|
+
"message": f"Unexpected state data format: {type(state_data)}",
|
760
620
|
}
|
761
621
|
|
762
622
|
# Validate that both a11y_tree and phone_state are present
|
763
623
|
if "a11y_tree" not in combined_data:
|
764
624
|
return {
|
765
625
|
"error": "Missing Data",
|
766
|
-
"message": "a11y_tree not found in combined state data"
|
626
|
+
"message": "a11y_tree not found in combined state data",
|
767
627
|
}
|
768
|
-
|
628
|
+
|
769
629
|
if "phone_state" not in combined_data:
|
770
630
|
return {
|
771
|
-
"error": "Missing Data",
|
772
|
-
"message": "phone_state not found in combined state data"
|
631
|
+
"error": "Missing Data",
|
632
|
+
"message": "phone_state not found in combined state data",
|
773
633
|
}
|
774
634
|
|
775
635
|
# Filter out the "type" attribute from all a11y_tree elements
|
@@ -777,9 +637,7 @@ class AdbTools(Tools):
|
|
777
637
|
filtered_elements = []
|
778
638
|
for element in elements:
|
779
639
|
# Create a copy of the element without the "type" attribute
|
780
|
-
filtered_element = {
|
781
|
-
k: v for k, v in element.items() if k != "type"
|
782
|
-
}
|
640
|
+
filtered_element = {k: v for k, v in element.items() if k != "type"}
|
783
641
|
|
784
642
|
# Also filter children if present
|
785
643
|
if "children" in filtered_element:
|
@@ -789,19 +647,25 @@ class AdbTools(Tools):
|
|
789
647
|
]
|
790
648
|
|
791
649
|
filtered_elements.append(filtered_element)
|
792
|
-
|
650
|
+
|
793
651
|
self.clickable_elements_cache = filtered_elements
|
794
|
-
|
652
|
+
|
795
653
|
return {
|
796
654
|
"a11y_tree": filtered_elements,
|
797
|
-
"phone_state": combined_data["phone_state"]
|
655
|
+
"phone_state": combined_data["phone_state"],
|
798
656
|
}
|
799
657
|
|
800
658
|
except Exception as e:
|
801
|
-
return {
|
659
|
+
return {
|
660
|
+
"error": str(e),
|
661
|
+
"message": f"Error getting combined state: {str(e)}",
|
662
|
+
}
|
663
|
+
|
802
664
|
|
803
665
|
if __name__ == "__main__":
|
666
|
+
|
804
667
|
async def main():
|
805
|
-
tools = AdbTools()
|
668
|
+
tools = await AdbTools.create()
|
669
|
+
print(tools.serial)
|
806
670
|
|
807
|
-
asyncio.run(main())
|
671
|
+
asyncio.run(main())
|
droidrun/tools/ios.py
CHANGED
@@ -39,6 +39,12 @@ class IOSTools(Tools):
|
|
39
39
|
"""Core UI interaction tools for iOS device control."""
|
40
40
|
|
41
41
|
def __init__(self, url: str, bundle_identifiers: List[str] = []) -> None:
|
42
|
+
"""Initialize the IOSTools instance.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
url: iOS device URL. This is the URL of the iOS device. It is used to send requests to the iOS device.
|
46
|
+
bundle_identifiers: List of bundle identifiers to include in the list of packages
|
47
|
+
"""
|
42
48
|
self.clickable_elements_cache: List[Dict[str, Any]] = []
|
43
49
|
self.url = url
|
44
50
|
self.last_screenshot = None
|
@@ -536,9 +542,6 @@ class IOSTools(Tools):
|
|
536
542
|
all_packages.update(SYSTEM_BUNDLE_IDENTIFIERS)
|
537
543
|
return sorted(list(all_packages))
|
538
544
|
|
539
|
-
async def extract(self, filename: str | None = None) -> str:
|
540
|
-
# TODO
|
541
|
-
return "not implemented"
|
542
545
|
|
543
546
|
async def remember(self, information: str) -> str:
|
544
547
|
"""
|
droidrun/tools/tools.py
CHANGED
@@ -8,12 +8,23 @@ logger = logging.getLogger(__name__)
|
|
8
8
|
|
9
9
|
|
10
10
|
class Tools(ABC):
|
11
|
+
"""
|
12
|
+
Abstract base class for all tools.
|
13
|
+
This class provides a common interface for all tools to implement.
|
14
|
+
"""
|
15
|
+
|
11
16
|
@abstractmethod
|
12
17
|
async def get_state(self) -> Dict[str, Any]:
|
18
|
+
"""
|
19
|
+
Get the current state of the tool.
|
20
|
+
"""
|
13
21
|
pass
|
14
22
|
|
15
23
|
@abstractmethod
|
16
24
|
async def tap_by_index(self, index: int) -> bool:
|
25
|
+
"""
|
26
|
+
Tap the element at the given index.
|
27
|
+
"""
|
17
28
|
pass
|
18
29
|
|
19
30
|
#@abstractmethod
|
@@ -24,46 +35,72 @@ class Tools(ABC):
|
|
24
35
|
async def swipe(
|
25
36
|
self, start_x: int, start_y: int, end_x: int, end_y: int, duration_ms: int = 300
|
26
37
|
) -> bool:
|
38
|
+
"""
|
39
|
+
Swipe from the given start coordinates to the given end coordinates.
|
40
|
+
"""
|
27
41
|
pass
|
28
42
|
|
29
43
|
@abstractmethod
|
30
44
|
async def input_text(self, text: str) -> bool:
|
45
|
+
"""
|
46
|
+
Input the given text into a focused input field.
|
47
|
+
"""
|
31
48
|
pass
|
32
49
|
|
33
50
|
@abstractmethod
|
34
51
|
async def back(self) -> bool:
|
52
|
+
"""
|
53
|
+
Press the back button.
|
54
|
+
"""
|
35
55
|
pass
|
36
56
|
|
37
57
|
@abstractmethod
|
38
58
|
async def press_key(self, keycode: int) -> bool:
|
59
|
+
"""
|
60
|
+
Enter the given keycode.
|
61
|
+
"""
|
39
62
|
pass
|
40
63
|
|
41
64
|
@abstractmethod
|
42
65
|
async def start_app(self, package: str, activity: str = "") -> bool:
|
66
|
+
"""
|
67
|
+
Start the given app.
|
68
|
+
"""
|
43
69
|
pass
|
44
70
|
|
45
71
|
@abstractmethod
|
46
72
|
async def take_screenshot(self) -> Tuple[str, bytes]:
|
73
|
+
"""
|
74
|
+
Take a screenshot of the device.
|
75
|
+
"""
|
47
76
|
pass
|
48
77
|
|
49
78
|
@abstractmethod
|
50
79
|
async def list_packages(self, include_system_apps: bool = False) -> List[str]:
|
80
|
+
"""
|
81
|
+
List all packages on the device.
|
82
|
+
"""
|
51
83
|
pass
|
52
84
|
|
53
85
|
@abstractmethod
|
54
86
|
async def remember(self, information: str) -> str:
|
87
|
+
"""
|
88
|
+
Remember the given information. This is used to store information in the tool's memory.
|
89
|
+
"""
|
55
90
|
pass
|
56
91
|
|
57
92
|
@abstractmethod
|
58
93
|
async def get_memory(self) -> List[str]:
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
async def extract(self, filename: Optional[str] = None) -> str:
|
94
|
+
"""
|
95
|
+
Get the memory of the tool.
|
96
|
+
"""
|
63
97
|
pass
|
64
98
|
|
65
99
|
@abstractmethod
|
66
100
|
def complete(self, success: bool, reason: str = "") -> bool:
|
101
|
+
"""
|
102
|
+
Complete the tool. This is used to indicate that the tool has completed its task.
|
103
|
+
"""
|
67
104
|
pass
|
68
105
|
|
69
106
|
|
@@ -84,12 +121,10 @@ def describe_tools(tools: Tools) -> Dict[str, Callable[..., Any]]:
|
|
84
121
|
"input_text": tools.input_text,
|
85
122
|
"press_key": tools.press_key,
|
86
123
|
"tap_by_index": tools.tap_by_index,
|
87
|
-
# "tap_by_coordinates": tools_instance.tap_by_coordinates,
|
88
124
|
# App management
|
89
125
|
"start_app": tools.start_app,
|
90
126
|
"list_packages": tools.list_packages,
|
91
127
|
# state management
|
92
|
-
"extract": tools.extract,
|
93
128
|
"remember": tools.remember,
|
94
129
|
"complete": tools.complete,
|
95
130
|
}
|