droidrun 0.3.0__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 +22 -12
- droidrun/agent/context/personas/__init__.py +0 -2
- droidrun/agent/context/personas/default.py +1 -1
- droidrun/agent/droid/droid_agent.py +56 -8
- droidrun/agent/droid/events.py +4 -0
- droidrun/agent/planner/planner_agent.py +32 -12
- droidrun/agent/utils/chat_utils.py +4 -7
- droidrun/agent/utils/llm_picker.py +1 -0
- droidrun/cli/main.py +163 -78
- 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 +199 -407
- droidrun/tools/ios.py +10 -5
- droidrun/tools/tools.py +42 -11
- {droidrun-0.3.0.dist-info → droidrun-0.3.2.dist-info}/METADATA +19 -29
- {droidrun-0.3.0.dist-info → droidrun-0.3.2.dist-info}/RECORD +24 -23
- droidrun/agent/context/personas/extractor.py +0 -52
- droidrun/agent/context/todo.txt +0 -4
- droidrun/run.py +0 -105
- {droidrun-0.3.0.dist-info → droidrun-0.3.2.dist-info}/WHEEL +0 -0
- {droidrun-0.3.0.dist-info → droidrun-0.3.2.dist-info}/entry_points.txt +0 -0
- {droidrun-0.3.0.dist-info → droidrun-0.3.2.dist-info}/licenses/LICENSE +0 -0
droidrun/cli/main.py
CHANGED
@@ -7,13 +7,21 @@ import click
|
|
7
7
|
import os
|
8
8
|
import logging
|
9
9
|
import warnings
|
10
|
+
from contextlib import nullcontext
|
10
11
|
from rich.console import Console
|
11
12
|
from droidrun.agent.droid import DroidAgent
|
12
13
|
from droidrun.agent.utils.llm_picker import load_llm
|
13
14
|
from droidrun.adb import DeviceManager
|
14
|
-
from droidrun.tools import AdbTools, IOSTools
|
15
|
+
from droidrun.tools import AdbTools, IOSTools
|
15
16
|
from functools import wraps
|
16
17
|
from droidrun.cli.logs import LogHandler
|
18
|
+
from droidrun.telemetry import print_telemetry_message
|
19
|
+
from droidrun.portal import (
|
20
|
+
download_portal_apk,
|
21
|
+
enable_portal_accessibility,
|
22
|
+
PORTAL_PACKAGE_NAME,
|
23
|
+
ping_portal,
|
24
|
+
)
|
17
25
|
|
18
26
|
# Suppress all warnings
|
19
27
|
warnings.filterwarnings("ignore")
|
@@ -36,7 +44,6 @@ def configure_logging(goal: str, debug: bool):
|
|
36
44
|
)
|
37
45
|
logger.addHandler(handler)
|
38
46
|
|
39
|
-
|
40
47
|
logger.setLevel(logging.DEBUG if debug else logging.INFO)
|
41
48
|
logger.propagate = False
|
42
49
|
|
@@ -59,6 +66,8 @@ async def run_command(
|
|
59
66
|
model: str,
|
60
67
|
steps: int,
|
61
68
|
base_url: str,
|
69
|
+
api_base: str,
|
70
|
+
vision: bool,
|
62
71
|
reasoning: bool,
|
63
72
|
reflection: bool,
|
64
73
|
tracing: bool,
|
@@ -76,6 +85,7 @@ async def run_command(
|
|
76
85
|
with log_handler.render() as live:
|
77
86
|
try:
|
78
87
|
logger.info(f"🚀 Starting: {command}")
|
88
|
+
print_telemetry_message()
|
79
89
|
|
80
90
|
if not kwargs.get("temperature"):
|
81
91
|
kwargs["temperature"] = 0
|
@@ -92,7 +102,9 @@ async def run_command(
|
|
92
102
|
device = devices[0].serial
|
93
103
|
logger.info(f"📱 Using device: {device}")
|
94
104
|
elif device is None and ios:
|
95
|
-
raise ValueError(
|
105
|
+
raise ValueError(
|
106
|
+
"iOS device not specified. Please specify the device base url (http://device-ip:6643) via --device"
|
107
|
+
)
|
96
108
|
else:
|
97
109
|
logger.info(f"📱 Using device: {device}")
|
98
110
|
|
@@ -101,7 +113,11 @@ async def run_command(
|
|
101
113
|
# LLM setup
|
102
114
|
log_handler.update_step("Initializing LLM...")
|
103
115
|
llm = load_llm(
|
104
|
-
provider_name=provider,
|
116
|
+
provider_name=provider,
|
117
|
+
model=model,
|
118
|
+
base_url=base_url,
|
119
|
+
api_base=api_base,
|
120
|
+
**kwargs,
|
105
121
|
)
|
106
122
|
logger.info(f"🧠 LLM ready: {provider}/{model}")
|
107
123
|
|
@@ -120,11 +136,12 @@ async def run_command(
|
|
120
136
|
tools=tools,
|
121
137
|
max_steps=steps,
|
122
138
|
timeout=1000,
|
139
|
+
vision=vision,
|
123
140
|
reasoning=reasoning,
|
124
141
|
reflection=reflection,
|
125
142
|
enable_tracing=tracing,
|
126
143
|
debug=debug,
|
127
|
-
save_trajectories=save_trajectory
|
144
|
+
save_trajectories=save_trajectory,
|
128
145
|
)
|
129
146
|
|
130
147
|
logger.info("▶️ Starting agent execution...")
|
@@ -176,14 +193,14 @@ class DroidRunCLI(click.Group):
|
|
176
193
|
@click.option(
|
177
194
|
"--provider",
|
178
195
|
"-p",
|
179
|
-
help="LLM provider (OpenAI, Ollama, Anthropic,
|
180
|
-
default="
|
196
|
+
help="LLM provider (OpenAI, Ollama, Anthropic, GoogleGenAI, DeepSeek)",
|
197
|
+
default="GoogleGenAI",
|
181
198
|
)
|
182
199
|
@click.option(
|
183
200
|
"--model",
|
184
201
|
"-m",
|
185
202
|
help="LLM model name",
|
186
|
-
default="models/gemini-2.5-
|
203
|
+
default="models/gemini-2.5-flash",
|
187
204
|
)
|
188
205
|
@click.option("--temperature", type=float, help="Temperature for LLM", default=0.2)
|
189
206
|
@click.option("--steps", type=int, help="Maximum number of steps", default=15)
|
@@ -194,10 +211,24 @@ class DroidRunCLI(click.Group):
|
|
194
211
|
default=None,
|
195
212
|
)
|
196
213
|
@click.option(
|
197
|
-
"--
|
214
|
+
"--api_base",
|
215
|
+
help="Base URL for API (e.g., OpenAI, OpenAI-Like)",
|
216
|
+
default=None,
|
198
217
|
)
|
199
218
|
@click.option(
|
200
|
-
"--
|
219
|
+
"--vision",
|
220
|
+
is_flag=True,
|
221
|
+
help="Enable vision capabilites by using screenshots",
|
222
|
+
default=False,
|
223
|
+
)
|
224
|
+
@click.option(
|
225
|
+
"--reasoning", is_flag=True, help="Enable planning with reasoning", default=False
|
226
|
+
)
|
227
|
+
@click.option(
|
228
|
+
"--reflection",
|
229
|
+
is_flag=True,
|
230
|
+
help="Enable reflection step for higher reasoning",
|
231
|
+
default=False,
|
201
232
|
)
|
202
233
|
@click.option(
|
203
234
|
"--tracing", is_flag=True, help="Enable Arize Phoenix tracing", default=False
|
@@ -218,7 +249,9 @@ def cli(
|
|
218
249
|
model: str,
|
219
250
|
steps: int,
|
220
251
|
base_url: str,
|
252
|
+
api_base: str,
|
221
253
|
temperature: float,
|
254
|
+
vision: bool,
|
222
255
|
reasoning: bool,
|
223
256
|
reflection: bool,
|
224
257
|
tracing: bool,
|
@@ -235,14 +268,14 @@ def cli(
|
|
235
268
|
@click.option(
|
236
269
|
"--provider",
|
237
270
|
"-p",
|
238
|
-
help="LLM provider (OpenAI, Ollama, Anthropic,
|
239
|
-
default="
|
271
|
+
help="LLM provider (OpenAI, Ollama, Anthropic, GoogleGenAI, DeepSeek)",
|
272
|
+
default="GoogleGenAI",
|
240
273
|
)
|
241
274
|
@click.option(
|
242
275
|
"--model",
|
243
276
|
"-m",
|
244
277
|
help="LLM model name",
|
245
|
-
default="models/gemini-2.5-
|
278
|
+
default="models/gemini-2.5-flash",
|
246
279
|
)
|
247
280
|
@click.option("--temperature", type=float, help="Temperature for LLM", default=0.2)
|
248
281
|
@click.option("--steps", type=int, help="Maximum number of steps", default=15)
|
@@ -253,10 +286,24 @@ def cli(
|
|
253
286
|
default=None,
|
254
287
|
)
|
255
288
|
@click.option(
|
256
|
-
"--
|
289
|
+
"--api_base",
|
290
|
+
help="Base URL for API (e.g., OpenAI or OpenAI-Like)",
|
291
|
+
default=None,
|
292
|
+
)
|
293
|
+
@click.option(
|
294
|
+
"--vision",
|
295
|
+
is_flag=True,
|
296
|
+
help="Enable vision capabilites by using screenshots",
|
297
|
+
default=False,
|
257
298
|
)
|
258
299
|
@click.option(
|
259
|
-
"--
|
300
|
+
"--reasoning", is_flag=True, help="Enable planning with reasoning", default=False
|
301
|
+
)
|
302
|
+
@click.option(
|
303
|
+
"--reflection",
|
304
|
+
is_flag=True,
|
305
|
+
help="Enable reflection step for higher reasoning",
|
306
|
+
default=False,
|
260
307
|
)
|
261
308
|
@click.option(
|
262
309
|
"--tracing", is_flag=True, help="Enable Arize Phoenix tracing", default=False
|
@@ -270,9 +317,7 @@ def cli(
|
|
270
317
|
help="Save agent trajectory to file",
|
271
318
|
default=False,
|
272
319
|
)
|
273
|
-
@click.option(
|
274
|
-
"--ios", is_flag=True, help="Run on iOS device", default=False
|
275
|
-
)
|
320
|
+
@click.option("--ios", is_flag=True, help="Run on iOS device", default=False)
|
276
321
|
def run(
|
277
322
|
command: str,
|
278
323
|
device: str | None,
|
@@ -280,7 +325,9 @@ def run(
|
|
280
325
|
model: str,
|
281
326
|
steps: int,
|
282
327
|
base_url: str,
|
328
|
+
api_base: str,
|
283
329
|
temperature: float,
|
330
|
+
vision: bool,
|
284
331
|
reasoning: bool,
|
285
332
|
reflection: bool,
|
286
333
|
tracing: bool,
|
@@ -297,13 +344,15 @@ def run(
|
|
297
344
|
model,
|
298
345
|
steps,
|
299
346
|
base_url,
|
347
|
+
api_base,
|
348
|
+
vision,
|
300
349
|
reasoning,
|
301
350
|
reflection,
|
302
351
|
tracing,
|
303
352
|
debug,
|
304
353
|
temperature=temperature,
|
305
354
|
save_trajectory=save_trajectory,
|
306
|
-
ios=ios
|
355
|
+
ios=ios,
|
307
356
|
)
|
308
357
|
|
309
358
|
|
@@ -325,17 +374,17 @@ async def devices():
|
|
325
374
|
|
326
375
|
|
327
376
|
@cli.command()
|
328
|
-
@click.argument("
|
377
|
+
@click.argument("serial")
|
329
378
|
@click.option("--port", "-p", default=5555, help="ADB port (default: 5555)")
|
330
379
|
@coro
|
331
|
-
async def connect(
|
380
|
+
async def connect(serial: str, port: int):
|
332
381
|
"""Connect to a device over TCP/IP."""
|
333
382
|
try:
|
334
|
-
device = await device_manager.connect(
|
383
|
+
device = await device_manager.connect(serial, port)
|
335
384
|
if device:
|
336
|
-
console.print(f"[green]Successfully connected to {
|
385
|
+
console.print(f"[green]Successfully connected to {serial}:{port}[/]")
|
337
386
|
else:
|
338
|
-
console.print(f"[red]Failed to connect to {
|
387
|
+
console.print(f"[red]Failed to connect to {serial}:{port}[/]")
|
339
388
|
except Exception as e:
|
340
389
|
console.print(f"[red]Error connecting to device: {e}[/]")
|
341
390
|
|
@@ -356,16 +405,15 @@ async def disconnect(serial: str):
|
|
356
405
|
|
357
406
|
|
358
407
|
@cli.command()
|
359
|
-
@click.option("--path", required=True, help="Path to the APK file to install")
|
360
408
|
@click.option("--device", "-d", help="Device serial number or IP address", default=None)
|
409
|
+
@click.option("--path", help="Path to the Droidrun Portal APK to install on the device. If not provided, the latest portal apk version will be downloaded and installed.", default=None)
|
410
|
+
@click.option(
|
411
|
+
"--debug", is_flag=True, help="Enable verbose debug logging", default=False
|
412
|
+
)
|
361
413
|
@coro
|
362
|
-
async def setup(path: str, device: str | None):
|
363
|
-
"""Install
|
414
|
+
async def setup(path: str | None, device: str | None, debug: bool):
|
415
|
+
"""Install and enable the DroidRun Portal on a device."""
|
364
416
|
try:
|
365
|
-
if not os.path.exists(path):
|
366
|
-
console.print(f"[bold red]Error:[/] APK file not found at {path}")
|
367
|
-
return
|
368
|
-
|
369
417
|
if not device:
|
370
418
|
devices = await device_manager.list_devices()
|
371
419
|
if not devices:
|
@@ -381,66 +429,99 @@ async def setup(path: str, device: str | None):
|
|
381
429
|
f"[bold red]Error:[/] Could not get device object for {device}"
|
382
430
|
)
|
383
431
|
return
|
384
|
-
tools = Tools(serial=device)
|
385
|
-
console.print(f"[bold blue]Step 1/2: Installing APK:[/] {path}")
|
386
|
-
result = await tools.install_app(path, False, True)
|
387
432
|
|
388
|
-
if
|
389
|
-
console.print(
|
390
|
-
|
433
|
+
if not path:
|
434
|
+
console.print("[bold blue]Downloading DroidRun Portal APK...[/]")
|
435
|
+
apk_context = download_portal_apk(debug)
|
391
436
|
else:
|
392
|
-
console.print(f"[bold
|
437
|
+
console.print(f"[bold blue]Using provided APK:[/] {path}")
|
438
|
+
apk_context = nullcontext(path)
|
393
439
|
|
394
|
-
|
440
|
+
with apk_context as apk_path:
|
441
|
+
if not os.path.exists(apk_path):
|
442
|
+
console.print(f"[bold red]Error:[/] APK file not found at {apk_path}")
|
443
|
+
return
|
395
444
|
|
396
|
-
|
445
|
+
console.print(f"[bold blue]Step 1/2: Installing APK:[/] {apk_path}")
|
446
|
+
result = await device_obj.install_app(apk_path, True, True)
|
397
447
|
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
448
|
+
if "Error" in result:
|
449
|
+
console.print(f"[bold red]Installation failed:[/] {result}")
|
450
|
+
return
|
451
|
+
else:
|
452
|
+
console.print(f"[bold green]Installation successful![/]")
|
403
453
|
|
404
|
-
|
405
|
-
device, "settings put secure accessibility_enabled 1"
|
406
|
-
)
|
454
|
+
console.print(f"[bold blue]Step 2/2: Enabling accessibility service[/]")
|
407
455
|
|
408
|
-
|
409
|
-
|
410
|
-
"\n[bold green]Setup complete![/] The DroidRun Portal is now installed and ready to use."
|
411
|
-
)
|
456
|
+
try:
|
457
|
+
await enable_portal_accessibility(device_obj)
|
412
458
|
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
console.print(
|
418
|
-
"[yellow]Opening accessibility settings for manual configuration...[/]"
|
419
|
-
)
|
459
|
+
console.print("[green]Accessibility service enabled successfully![/]")
|
460
|
+
console.print(
|
461
|
+
"\n[bold green]Setup complete![/] The DroidRun Portal is now installed and ready to use."
|
462
|
+
)
|
420
463
|
|
421
|
-
|
422
|
-
|
423
|
-
|
464
|
+
except Exception as e:
|
465
|
+
console.print(
|
466
|
+
f"[yellow]Could not automatically enable accessibility service: {e}[/]"
|
467
|
+
)
|
468
|
+
console.print(
|
469
|
+
"[yellow]Opening accessibility settings for manual configuration...[/]"
|
470
|
+
)
|
471
|
+
|
472
|
+
await device_obj.shell(
|
473
|
+
"am start -a android.settings.ACCESSIBILITY_SETTINGS"
|
474
|
+
)
|
475
|
+
|
476
|
+
console.print(
|
477
|
+
"\n[yellow]Please complete the following steps on your device:[/]"
|
478
|
+
)
|
479
|
+
console.print(
|
480
|
+
f"1. Find [bold]{PORTAL_PACKAGE_NAME}[/] in the accessibility services list"
|
481
|
+
)
|
482
|
+
console.print("2. Tap on the service name")
|
483
|
+
console.print(
|
484
|
+
"3. Toggle the switch to [bold]ON[/] to enable the service"
|
485
|
+
)
|
486
|
+
console.print("4. Accept any permission dialogs that appear")
|
487
|
+
|
488
|
+
console.print(
|
489
|
+
"\n[bold green]APK installation complete![/] Please manually enable the accessibility service using the steps above."
|
490
|
+
)
|
424
491
|
|
425
|
-
|
426
|
-
|
427
|
-
)
|
428
|
-
console.print(
|
429
|
-
f"1. Find [bold]{package}[/] in the accessibility services list"
|
430
|
-
)
|
431
|
-
console.print("2. Tap on the service name")
|
432
|
-
console.print("3. Toggle the switch to [bold]ON[/] to enable the service")
|
433
|
-
console.print("4. Accept any permission dialogs that appear")
|
492
|
+
except Exception as e:
|
493
|
+
console.print(f"[bold red]Error:[/] {e}")
|
434
494
|
|
435
|
-
|
436
|
-
|
437
|
-
|
495
|
+
if debug:
|
496
|
+
import traceback
|
497
|
+
|
498
|
+
traceback.print_exc()
|
499
|
+
|
500
|
+
|
501
|
+
@cli.command()
|
502
|
+
@click.option("--device", "-d", help="Device serial number or IP address", default=None)
|
503
|
+
@click.option(
|
504
|
+
"--debug", is_flag=True, help="Enable verbose debug logging", default=False
|
505
|
+
)
|
506
|
+
@coro
|
507
|
+
async def ping(device: str | None, debug: bool):
|
508
|
+
"""Ping a device to check if it is ready and accessible."""
|
509
|
+
try:
|
510
|
+
device_obj = await device_manager.get_device(device)
|
511
|
+
if not device_obj:
|
512
|
+
console.print(f"[bold red]Error:[/] Could not find device {device}")
|
513
|
+
return
|
438
514
|
|
515
|
+
await ping_portal(device_obj, debug)
|
516
|
+
console.print(
|
517
|
+
"[bold green]Portal is installed and accessible. You're good to go![/]"
|
518
|
+
)
|
439
519
|
except Exception as e:
|
440
520
|
console.print(f"[bold red]Error:[/] {e}")
|
441
|
-
|
521
|
+
if debug:
|
522
|
+
import traceback
|
442
523
|
|
443
|
-
|
524
|
+
traceback.print_exc()
|
444
525
|
|
445
526
|
|
446
527
|
if __name__ == "__main__":
|
@@ -449,13 +530,15 @@ if __name__ == "__main__":
|
|
449
530
|
provider = "GoogleGenAI"
|
450
531
|
model = "models/gemini-2.5-flash"
|
451
532
|
temperature = 0
|
452
|
-
api_key = os.getenv("
|
533
|
+
api_key = os.getenv("GOOGLE_API_KEY")
|
453
534
|
steps = 15
|
535
|
+
vision = True
|
454
536
|
reasoning = True
|
455
537
|
reflection = False
|
456
538
|
tracing = True
|
457
539
|
debug = True
|
458
540
|
base_url = None
|
541
|
+
api_base = None
|
459
542
|
ios = False
|
460
543
|
run_command(
|
461
544
|
command=command,
|
@@ -464,11 +547,13 @@ if __name__ == "__main__":
|
|
464
547
|
model=model,
|
465
548
|
steps=steps,
|
466
549
|
temperature=temperature,
|
550
|
+
vision=vision,
|
467
551
|
reasoning=reasoning,
|
468
552
|
reflection=reflection,
|
469
553
|
tracing=tracing,
|
470
554
|
debug=debug,
|
471
555
|
base_url=base_url,
|
556
|
+
api_base=api_base,
|
472
557
|
api_key=api_key,
|
473
|
-
ios=ios
|
558
|
+
ios=ios,
|
474
559
|
)
|
droidrun/portal.py
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
import requests
|
2
|
+
import tempfile
|
3
|
+
import os
|
4
|
+
import contextlib
|
5
|
+
from droidrun.adb import Device, DeviceManager
|
6
|
+
import asyncio
|
7
|
+
|
8
|
+
REPO = "droidrun/droidrun-portal"
|
9
|
+
ASSET_NAME = "droidrun-portal"
|
10
|
+
GITHUB_API_HOSTS = ["https://api.github.com", "https://ungh.cc"]
|
11
|
+
|
12
|
+
PORTAL_PACKAGE_NAME = "com.droidrun.portal"
|
13
|
+
A11Y_SERVICE_NAME = (
|
14
|
+
f"{PORTAL_PACKAGE_NAME}/com.droidrun.portal.DroidrunAccessibilityService"
|
15
|
+
)
|
16
|
+
|
17
|
+
|
18
|
+
def get_latest_release_assets(debug: bool = False):
|
19
|
+
for host in GITHUB_API_HOSTS:
|
20
|
+
url = f"{host}/repos/{REPO}/releases/latest"
|
21
|
+
response = requests.get(url)
|
22
|
+
if response.status_code == 200:
|
23
|
+
if debug:
|
24
|
+
print(f"Using GitHub release on {host}")
|
25
|
+
break
|
26
|
+
|
27
|
+
response.raise_for_status()
|
28
|
+
latest_release = response.json()
|
29
|
+
|
30
|
+
if "release" in latest_release:
|
31
|
+
assets = latest_release["release"]["assets"]
|
32
|
+
else:
|
33
|
+
assets = latest_release.get("assets", [])
|
34
|
+
|
35
|
+
return assets
|
36
|
+
|
37
|
+
|
38
|
+
@contextlib.contextmanager
|
39
|
+
def download_portal_apk(debug: bool = False):
|
40
|
+
assets = get_latest_release_assets(debug)
|
41
|
+
|
42
|
+
asset_url = None
|
43
|
+
for asset in assets:
|
44
|
+
if (
|
45
|
+
"browser_download_url" in asset
|
46
|
+
and "name" in asset
|
47
|
+
and asset["name"].startswith(ASSET_NAME)
|
48
|
+
):
|
49
|
+
asset_url = asset["browser_download_url"]
|
50
|
+
break
|
51
|
+
elif "downloadUrl" in asset and os.path.basename(
|
52
|
+
asset["downloadUrl"]
|
53
|
+
).startswith(ASSET_NAME):
|
54
|
+
asset_url = asset["downloadUrl"]
|
55
|
+
break
|
56
|
+
else:
|
57
|
+
if debug:
|
58
|
+
print(asset)
|
59
|
+
|
60
|
+
if not asset_url:
|
61
|
+
raise Exception(f"Asset named '{ASSET_NAME}' not found in the latest release.")
|
62
|
+
|
63
|
+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".apk")
|
64
|
+
try:
|
65
|
+
r = requests.get(asset_url, stream=True)
|
66
|
+
r.raise_for_status()
|
67
|
+
for chunk in r.iter_content(chunk_size=8192):
|
68
|
+
if chunk:
|
69
|
+
tmp.write(chunk)
|
70
|
+
tmp.close()
|
71
|
+
yield tmp.name
|
72
|
+
finally:
|
73
|
+
if os.path.exists(tmp.name):
|
74
|
+
os.unlink(tmp.name)
|
75
|
+
|
76
|
+
|
77
|
+
async def enable_portal_accessibility(device: Device):
|
78
|
+
await device.shell(
|
79
|
+
f"settings put secure enabled_accessibility_services {A11Y_SERVICE_NAME}"
|
80
|
+
)
|
81
|
+
await device.shell("settings put secure accessibility_enabled 1")
|
82
|
+
|
83
|
+
|
84
|
+
async def check_portal_accessibility(device: Device, debug: bool = False) -> bool:
|
85
|
+
a11y_services = await device.shell(
|
86
|
+
"settings get secure enabled_accessibility_services"
|
87
|
+
)
|
88
|
+
if not A11Y_SERVICE_NAME in a11y_services:
|
89
|
+
if debug:
|
90
|
+
print(a11y_services)
|
91
|
+
return False
|
92
|
+
|
93
|
+
a11y_enabled = await device.shell("settings get secure accessibility_enabled")
|
94
|
+
if a11y_enabled != "1":
|
95
|
+
if debug:
|
96
|
+
print(a11y_enabled)
|
97
|
+
return False
|
98
|
+
|
99
|
+
return True
|
100
|
+
|
101
|
+
|
102
|
+
async def ping_portal(device: Device, debug: bool = False):
|
103
|
+
"""
|
104
|
+
Ping the Droidrun Portal to check if it is installed and accessible.
|
105
|
+
"""
|
106
|
+
try:
|
107
|
+
packages = await device.list_packages()
|
108
|
+
except Exception as e:
|
109
|
+
raise Exception(f"Failed to list packages: {e}")
|
110
|
+
|
111
|
+
if not PORTAL_PACKAGE_NAME in packages:
|
112
|
+
if debug:
|
113
|
+
print(packages)
|
114
|
+
raise Exception("Portal is not installed on the device")
|
115
|
+
|
116
|
+
if not await check_portal_accessibility(device, debug):
|
117
|
+
await device.shell("am start -a android.settings.ACCESSIBILITY_SETTINGS")
|
118
|
+
raise Exception(
|
119
|
+
"Droidrun Portal is not enabled as an accessibility service on the device"
|
120
|
+
)
|
121
|
+
|
122
|
+
try:
|
123
|
+
state = await device.shell(
|
124
|
+
"content query --uri content://com.droidrun.portal/state"
|
125
|
+
)
|
126
|
+
if not "Row: 0 result=" in state:
|
127
|
+
raise Exception("Failed to get state from Droidrun Portal")
|
128
|
+
|
129
|
+
except Exception as e:
|
130
|
+
raise Exception(f"Droidrun Portal is not reachable: {e}")
|
131
|
+
|
132
|
+
|
133
|
+
async def test():
|
134
|
+
device = await DeviceManager().get_device()
|
135
|
+
await ping_portal(device, debug=False)
|
136
|
+
|
137
|
+
|
138
|
+
if __name__ == "__main__":
|
139
|
+
asyncio.run(test())
|
@@ -0,0 +1,27 @@
|
|
1
|
+
from typing import List
|
2
|
+
from droidrun.agent.context import Task
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
class TelemetryEvent(BaseModel):
|
6
|
+
pass
|
7
|
+
|
8
|
+
class DroidAgentInitEvent(TelemetryEvent):
|
9
|
+
goal: str
|
10
|
+
llm: str
|
11
|
+
tools: str
|
12
|
+
personas: str
|
13
|
+
max_steps: int
|
14
|
+
timeout: int
|
15
|
+
vision: bool
|
16
|
+
reasoning: bool
|
17
|
+
reflection: bool
|
18
|
+
enable_tracing: bool
|
19
|
+
debug: bool
|
20
|
+
save_trajectories: bool
|
21
|
+
|
22
|
+
|
23
|
+
class DroidAgentFinalizeEvent(TelemetryEvent):
|
24
|
+
tasks: str
|
25
|
+
success: bool
|
26
|
+
output: str
|
27
|
+
steps: int
|