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/__init__.py
CHANGED
@@ -5,26 +5,17 @@ DroidRun - A framework for controlling Android devices through LLM agents.
|
|
5
5
|
__version__ = "0.3.0"
|
6
6
|
|
7
7
|
# Import main classes for easier access
|
8
|
-
from droidrun.agent.codeact.codeact_agent import CodeActAgent
|
9
|
-
from droidrun.agent.planner.planner_agent import PlannerAgent
|
10
|
-
from droidrun.agent.utils.executer import SimpleCodeExecutor
|
11
8
|
from droidrun.agent.utils.llm_picker import load_llm
|
12
9
|
from droidrun.adb.manager import DeviceManager
|
13
|
-
from droidrun.tools
|
14
|
-
from droidrun.tools.adb import AdbTools
|
15
|
-
from droidrun.tools.ios import IOSTools
|
10
|
+
from droidrun.tools import Tools, AdbTools, IOSTools
|
16
11
|
from droidrun.agent.droid import DroidAgent
|
17
12
|
|
18
13
|
|
19
14
|
# Make main components available at package level
|
20
15
|
__all__ = [
|
21
16
|
"DroidAgent",
|
22
|
-
"CodeActAgent",
|
23
|
-
"PlannerAgent",
|
24
17
|
"DeviceManager",
|
25
|
-
"Tools",
|
26
18
|
"load_llm",
|
27
|
-
"SimpleCodeExecutor",
|
28
19
|
"Tools",
|
29
20
|
"AdbTools",
|
30
21
|
"IOSTools",
|
droidrun/adb/device.py
CHANGED
@@ -10,12 +10,13 @@ import string
|
|
10
10
|
from typing import Dict, Optional, Tuple, List
|
11
11
|
from droidrun.adb.wrapper import ADBWrapper
|
12
12
|
|
13
|
+
|
13
14
|
class Device:
|
14
15
|
"""High-level representation of an Android device."""
|
15
16
|
|
16
17
|
def __init__(self, serial: str, adb: ADBWrapper):
|
17
18
|
"""Initialize device.
|
18
|
-
|
19
|
+
|
19
20
|
Args:
|
20
21
|
serial: Device serial number
|
21
22
|
adb: ADB wrapper instance
|
@@ -60,9 +61,13 @@ class Device:
|
|
60
61
|
"""Get SDK level."""
|
61
62
|
return await self.get_property("ro.build.version.sdk")
|
62
63
|
|
64
|
+
async def shell(self, command: str, timeout: float | None = None) -> str:
|
65
|
+
"""Execute a shell command on the device."""
|
66
|
+
return await self._adb.shell(self._serial, command, timeout)
|
67
|
+
|
63
68
|
async def tap(self, x: int, y: int) -> None:
|
64
69
|
"""Tap at coordinates.
|
65
|
-
|
70
|
+
|
66
71
|
Args:
|
67
72
|
x: X coordinate
|
68
73
|
y: Y coordinate
|
@@ -70,15 +75,10 @@ class Device:
|
|
70
75
|
await self._adb.shell(self._serial, f"input tap {x} {y}")
|
71
76
|
|
72
77
|
async def swipe(
|
73
|
-
self,
|
74
|
-
start_x: int,
|
75
|
-
start_y: int,
|
76
|
-
end_x: int,
|
77
|
-
end_y: int,
|
78
|
-
duration_ms: int = 300
|
78
|
+
self, start_x: int, start_y: int, end_x: int, end_y: int, duration_ms: int = 300
|
79
79
|
) -> None:
|
80
80
|
"""Perform swipe gesture.
|
81
|
-
|
81
|
+
|
82
82
|
Args:
|
83
83
|
start_x: Starting X coordinate
|
84
84
|
start_y: Starting Y coordinate
|
@@ -88,12 +88,12 @@ class Device:
|
|
88
88
|
"""
|
89
89
|
await self._adb.shell(
|
90
90
|
self._serial,
|
91
|
-
f"input swipe {start_x} {start_y} {end_x} {end_y} {duration_ms}"
|
91
|
+
f"input swipe {start_x} {start_y} {end_x} {end_y} {duration_ms}",
|
92
92
|
)
|
93
93
|
|
94
94
|
async def input_text(self, text: str) -> None:
|
95
95
|
"""Input text.
|
96
|
-
|
96
|
+
|
97
97
|
Args:
|
98
98
|
text: Text to input
|
99
99
|
"""
|
@@ -101,7 +101,7 @@ class Device:
|
|
101
101
|
|
102
102
|
async def press_key(self, keycode: int) -> None:
|
103
103
|
"""Press a key.
|
104
|
-
|
104
|
+
|
105
105
|
Args:
|
106
106
|
keycode: Android keycode to press
|
107
107
|
"""
|
@@ -111,10 +111,10 @@ class Device:
|
|
111
111
|
self,
|
112
112
|
package: str,
|
113
113
|
activity: str = ".MainActivity",
|
114
|
-
extras: Optional[Dict[str, str]] = None
|
114
|
+
extras: Optional[Dict[str, str]] = None,
|
115
115
|
) -> None:
|
116
116
|
"""Start an app activity.
|
117
|
-
|
117
|
+
|
118
118
|
Args:
|
119
119
|
package: Package name
|
120
120
|
activity: Activity name
|
@@ -125,48 +125,56 @@ class Device:
|
|
125
125
|
for key, value in extras.items():
|
126
126
|
cmd += f" -e {key} {value}"
|
127
127
|
await self._adb.shell(self._serial, cmd)
|
128
|
-
|
128
|
+
|
129
129
|
async def start_app(self, package: str, activity: str = "") -> str:
|
130
130
|
"""Start an app on the device.
|
131
|
-
|
131
|
+
|
132
132
|
Args:
|
133
133
|
package: Package name
|
134
134
|
activity: Optional activity name (if empty, launches default activity)
|
135
|
-
|
135
|
+
|
136
136
|
Returns:
|
137
137
|
Result message
|
138
138
|
"""
|
139
139
|
if activity:
|
140
140
|
if not activity.startswith(".") and "." not in activity:
|
141
141
|
activity = f".{activity}"
|
142
|
-
|
143
|
-
if
|
142
|
+
|
143
|
+
if (
|
144
|
+
not activity.startswith(".")
|
145
|
+
and "." in activity
|
146
|
+
and not activity.startswith(package)
|
147
|
+
):
|
144
148
|
# Fully qualified activity name
|
145
149
|
component = activity.split("/", 1)
|
146
|
-
return await self.start_activity(
|
147
|
-
|
150
|
+
return await self.start_activity(
|
151
|
+
component[0], component[1] if len(component) > 1 else activity
|
152
|
+
)
|
153
|
+
|
148
154
|
# Relative activity name
|
149
155
|
return await self.start_activity(package, activity)
|
150
|
-
|
156
|
+
|
151
157
|
# Start main activity using monkey
|
152
158
|
cmd = f"monkey -p {package} -c android.intent.category.LAUNCHER 1"
|
153
159
|
result = await self._adb.shell(self._serial, cmd)
|
154
160
|
return f"Started {package}"
|
155
|
-
|
156
|
-
async def install_app(
|
161
|
+
|
162
|
+
async def install_app(
|
163
|
+
self, apk_path: str, reinstall: bool = False, grant_permissions: bool = True
|
164
|
+
) -> str:
|
157
165
|
"""Install an APK on the device.
|
158
|
-
|
166
|
+
|
159
167
|
Args:
|
160
168
|
apk_path: Path to the APK file
|
161
169
|
reinstall: Whether to reinstall if app exists
|
162
170
|
grant_permissions: Whether to grant all requested permissions
|
163
|
-
|
171
|
+
|
164
172
|
Returns:
|
165
173
|
Installation result
|
166
174
|
"""
|
167
175
|
if not os.path.exists(apk_path):
|
168
176
|
return f"Error: APK file not found: {apk_path}"
|
169
|
-
|
177
|
+
|
170
178
|
# Build install command args
|
171
179
|
install_args = ["install"]
|
172
180
|
if reinstall:
|
@@ -174,28 +182,28 @@ class Device:
|
|
174
182
|
if grant_permissions:
|
175
183
|
install_args.append("-g")
|
176
184
|
install_args.append(apk_path)
|
177
|
-
|
185
|
+
|
178
186
|
try:
|
179
187
|
stdout, stderr = await self._adb._run_device_command(
|
180
188
|
self._serial,
|
181
189
|
install_args,
|
182
|
-
timeout=120 # Longer timeout for installation
|
190
|
+
timeout=120, # Longer timeout for installation
|
183
191
|
)
|
184
|
-
|
192
|
+
|
185
193
|
if "success" in stdout.lower():
|
186
194
|
return f"Successfully installed {os.path.basename(apk_path)}"
|
187
195
|
return f"Installation failed: {stdout or stderr}"
|
188
|
-
|
196
|
+
|
189
197
|
except Exception as e:
|
190
198
|
return f"Installation failed: {str(e)}"
|
191
|
-
|
199
|
+
|
192
200
|
async def uninstall_app(self, package: str, keep_data: bool = False) -> str:
|
193
201
|
"""Uninstall an app from the device.
|
194
|
-
|
202
|
+
|
195
203
|
Args:
|
196
204
|
package: Package name to uninstall
|
197
205
|
keep_data: Whether to keep app data and cache directories
|
198
|
-
|
206
|
+
|
199
207
|
Returns:
|
200
208
|
Uninstallation result
|
201
209
|
"""
|
@@ -203,41 +211,42 @@ class Device:
|
|
203
211
|
if keep_data:
|
204
212
|
cmd.append("-k")
|
205
213
|
cmd.append(package)
|
206
|
-
|
214
|
+
|
207
215
|
result = await self._adb.shell(self._serial, " ".join(cmd))
|
208
216
|
return result.strip()
|
209
|
-
|
217
|
+
|
210
218
|
async def take_screenshot(self, quality: int = 75) -> Tuple[str, bytes]:
|
211
219
|
"""Take a screenshot of the device and compress it.
|
212
|
-
|
220
|
+
|
213
221
|
Args:
|
214
222
|
quality: JPEG quality (1-100, lower means smaller file size)
|
215
|
-
|
223
|
+
|
216
224
|
Returns:
|
217
225
|
Tuple of (local file path, screenshot data as bytes)
|
218
226
|
"""
|
219
227
|
# Create a temporary file for the screenshot
|
220
228
|
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp:
|
221
229
|
screenshot_path = temp.name
|
222
|
-
|
230
|
+
|
223
231
|
try:
|
224
232
|
# Generate a random filename for the device
|
225
233
|
timestamp = int(time.time())
|
226
|
-
random_suffix = "".join(
|
234
|
+
random_suffix = "".join(
|
235
|
+
random.choices(string.ascii_lowercase + string.digits, k=8)
|
236
|
+
)
|
227
237
|
device_path = f"/sdcard/screenshot_{timestamp}_{random_suffix}.png"
|
228
|
-
|
238
|
+
|
229
239
|
# Take screenshot using screencap command
|
230
240
|
await self._adb.shell(self._serial, f"screencap -p {device_path}")
|
231
|
-
|
241
|
+
|
232
242
|
# Pull screenshot to local machine
|
233
243
|
await self._adb._run_device_command(
|
234
|
-
self._serial,
|
235
|
-
["pull", device_path, screenshot_path]
|
244
|
+
self._serial, ["pull", device_path, screenshot_path]
|
236
245
|
)
|
237
|
-
|
246
|
+
|
238
247
|
# Clean up on device
|
239
248
|
await self._adb.shell(self._serial, f"rm {device_path}")
|
240
|
-
|
249
|
+
|
241
250
|
# Read the screenshot file
|
242
251
|
with open(screenshot_path, "rb") as f:
|
243
252
|
screenshot_data = f.read()
|
@@ -249,12 +258,14 @@ class Device:
|
|
249
258
|
|
250
259
|
# Create buffer for the compressed image
|
251
260
|
buffer = io.BytesIO()
|
252
|
-
|
261
|
+
|
253
262
|
# Load the PNG data into a PIL Image
|
254
263
|
with Image.open(io.BytesIO(screenshot_data)) as img:
|
255
264
|
# Convert to RGB (removing alpha channel if present) and save as JPEG
|
256
265
|
converted_img = img.convert("RGB") if img.mode == "RGBA" else img
|
257
|
-
converted_img.save(
|
266
|
+
converted_img.save(
|
267
|
+
buffer, format="JPEG", quality=quality, optimize=True
|
268
|
+
)
|
258
269
|
compressed_data = buffer.getvalue()
|
259
270
|
|
260
271
|
# Get size reduction info for logging
|
@@ -263,6 +274,7 @@ class Device:
|
|
263
274
|
reduction = 100 - (jpg_size / png_size * 100) if png_size > 0 else 0
|
264
275
|
|
265
276
|
import logging
|
277
|
+
|
266
278
|
logger = logging.getLogger("droidrun")
|
267
279
|
logger.debug(
|
268
280
|
f"Screenshot compressed successfully: {png_size:.1f}KB → {jpg_size:.1f}KB ({reduction:.1f}% reduction)"
|
@@ -275,9 +287,11 @@ class Device:
|
|
275
287
|
return screenshot_path, screenshot_data
|
276
288
|
except Exception as e:
|
277
289
|
# If compression fails, return the original PNG data
|
278
|
-
logger.warning(
|
290
|
+
logger.warning(
|
291
|
+
f"Screenshot compression failed: {e}, returning uncompressed"
|
292
|
+
)
|
279
293
|
return screenshot_path, screenshot_data
|
280
|
-
|
294
|
+
|
281
295
|
except Exception as e:
|
282
296
|
# Clean up in case of error
|
283
297
|
try:
|
@@ -285,31 +299,47 @@ class Device:
|
|
285
299
|
except OSError:
|
286
300
|
pass
|
287
301
|
raise RuntimeError(f"Screenshot capture failed: {str(e)}")
|
288
|
-
|
289
|
-
async def list_packages(self, include_system_apps: bool = False) -> List[Dict[str, str]]:
|
290
|
-
"""List installed packages on the device.
|
291
302
|
|
303
|
+
def _parse_package_list(self, output: str) -> List[Dict[str, str]]:
|
304
|
+
"""Parse the output of 'pm list packages -f' command.
|
305
|
+
|
306
|
+
Args:
|
307
|
+
output: Raw command output from 'pm list packages -f'
|
308
|
+
|
309
|
+
Returns:
|
310
|
+
List of dictionaries containing package info with 'package' and 'path' keys
|
311
|
+
"""
|
312
|
+
apps = []
|
313
|
+
for line in output.splitlines():
|
314
|
+
if line.startswith("package:"):
|
315
|
+
# Format is: "package:/path/to/base.apk=com.package.name"
|
316
|
+
path_and_pkg = line[8:] # Strip "package:"
|
317
|
+
if "=" in path_and_pkg:
|
318
|
+
path, package = path_and_pkg.rsplit("=", 1)
|
319
|
+
apps.append({"package": package.strip(), "path": path.strip()})
|
320
|
+
return apps
|
321
|
+
|
322
|
+
async def list_packages(self, include_system_apps: bool = False) -> List[str]:
|
323
|
+
"""
|
324
|
+
List installed packages on the device.
|
325
|
+
|
292
326
|
Args:
|
293
|
-
include_system_apps: Whether to include system apps
|
294
|
-
|
327
|
+
include_system_apps: Whether to include system apps (default: False)
|
328
|
+
|
295
329
|
Returns:
|
296
|
-
List of package
|
330
|
+
List of package names
|
297
331
|
"""
|
332
|
+
# Use the direct ADB command to get packages with paths
|
298
333
|
cmd = ["pm", "list", "packages", "-f"]
|
299
334
|
if not include_system_apps:
|
300
335
|
cmd.append("-3")
|
301
|
-
|
302
|
-
output = await self.
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
"package": package,
|
312
|
-
"path": path
|
313
|
-
})
|
314
|
-
|
315
|
-
return packages
|
336
|
+
|
337
|
+
output = await self.shell(" ".join(cmd))
|
338
|
+
|
339
|
+
# Parse the package list using the function
|
340
|
+
packages = self._parse_package_list(output)
|
341
|
+
# Format package list for better readability
|
342
|
+
package_list = [pack["package"] for pack in packages]
|
343
|
+
#for package in package_list:
|
344
|
+
# print(package)
|
345
|
+
return package_list
|
droidrun/adb/manager.py
CHANGED
@@ -42,7 +42,7 @@ class DeviceManager:
|
|
42
42
|
|
43
43
|
return list(self._devices.values())
|
44
44
|
|
45
|
-
async def get_device(self, serial: str) -> Optional[Device]:
|
45
|
+
async def get_device(self, serial: str | None = None) -> Optional[Device]:
|
46
46
|
"""Get a specific device.
|
47
47
|
|
48
48
|
Args:
|
@@ -51,13 +51,13 @@ class DeviceManager:
|
|
51
51
|
Returns:
|
52
52
|
Device instance if found, None otherwise
|
53
53
|
"""
|
54
|
-
if serial in self._devices:
|
54
|
+
if serial and serial in self._devices:
|
55
55
|
return self._devices[serial]
|
56
56
|
|
57
57
|
# Try to find the device
|
58
58
|
devices = await self.list_devices()
|
59
59
|
for device in devices:
|
60
|
-
if device.serial == serial:
|
60
|
+
if device.serial == serial or not serial:
|
61
61
|
return device
|
62
62
|
|
63
63
|
return None
|
@@ -312,6 +312,7 @@ class CodeActAgent(Workflow):
|
|
312
312
|
{
|
313
313
|
"success": ev.success,
|
314
314
|
"reason": ev.reason,
|
315
|
+
"output": ev.reason,
|
315
316
|
"codeact_steps": self.steps_counter,
|
316
317
|
"code_executions": self.code_exec_counter,
|
317
318
|
}
|
@@ -321,7 +322,7 @@ class CodeActAgent(Workflow):
|
|
321
322
|
EpisodicMemoryEvent(episodic_memory=self.episodic_memory)
|
322
323
|
)
|
323
324
|
|
324
|
-
return StopEvent(result
|
325
|
+
return StopEvent(result)
|
325
326
|
|
326
327
|
async def _get_llm_response(
|
327
328
|
self, ctx: Context, chat_history: List[ChatMessage]
|
@@ -8,6 +8,7 @@ from typing import List
|
|
8
8
|
|
9
9
|
from llama_index.core.llms.llm import LLM
|
10
10
|
from llama_index.core.workflow import step, StartEvent, StopEvent, Workflow, Context
|
11
|
+
from llama_index.core.workflow.handler import WorkflowHandler
|
11
12
|
from droidrun.agent.droid.events import *
|
12
13
|
from droidrun.agent.codeact import CodeActAgent
|
13
14
|
from droidrun.agent.codeact.events import EpisodicMemoryEvent
|
@@ -21,6 +22,7 @@ from droidrun.agent.context import ContextInjectionManager
|
|
21
22
|
from droidrun.agent.context.agent_persona import AgentPersona
|
22
23
|
from droidrun.agent.context.personas import DEFAULT
|
23
24
|
from droidrun.agent.oneflows.reflector import Reflector
|
25
|
+
from droidrun.telemetry import capture, flush, DroidAgentInitEvent, DroidAgentFinalizeEvent
|
24
26
|
|
25
27
|
|
26
28
|
logger = logging.getLogger("droidrun")
|
@@ -86,7 +88,6 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
86
88
|
**kwargs: Additional keyword arguments to pass to the agents
|
87
89
|
"""
|
88
90
|
super().__init__(timeout=timeout ,*args,**kwargs)
|
89
|
-
|
90
91
|
# Configure default logging if not already configured
|
91
92
|
self._configure_default_logging(debug=debug)
|
92
93
|
|
@@ -146,8 +147,32 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
146
147
|
else:
|
147
148
|
logger.debug("🚫 Planning disabled - will execute tasks directly with CodeActAgent")
|
148
149
|
self.planner_agent = None
|
150
|
+
|
151
|
+
capture(
|
152
|
+
DroidAgentInitEvent(
|
153
|
+
goal=goal,
|
154
|
+
llm=llm.class_name(),
|
155
|
+
tools=",".join(self.tool_list),
|
156
|
+
personas=",".join([p.name for p in personas]),
|
157
|
+
max_steps=max_steps,
|
158
|
+
timeout=timeout,
|
159
|
+
vision=vision,
|
160
|
+
reasoning=reasoning,
|
161
|
+
reflection=reflection,
|
162
|
+
enable_tracing=enable_tracing,
|
163
|
+
debug=debug,
|
164
|
+
save_trajectories=save_trajectories,
|
165
|
+
)
|
166
|
+
)
|
167
|
+
|
149
168
|
|
150
169
|
logger.info("✅ DroidAgent initialized successfully.")
|
170
|
+
|
171
|
+
def run(self) -> WorkflowHandler:
|
172
|
+
"""
|
173
|
+
Run the DroidAgent workflow.
|
174
|
+
"""
|
175
|
+
return super().run()
|
151
176
|
|
152
177
|
@step
|
153
178
|
async def execute_task(
|
@@ -211,7 +236,7 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
211
236
|
try:
|
212
237
|
task = ev.task
|
213
238
|
if not self.reasoning:
|
214
|
-
return FinalizeEvent(success=ev.success, reason=ev.reason, task=[task], steps=ev.steps)
|
239
|
+
return FinalizeEvent(success=ev.success, reason=ev.reason, output=ev.reason, task=[task], tasks=[task], steps=ev.steps)
|
215
240
|
|
216
241
|
if self.reflection:
|
217
242
|
return ReflectionEvent(task=task)
|
@@ -223,7 +248,8 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
223
248
|
if self.debug:
|
224
249
|
import traceback
|
225
250
|
logger.error(traceback.format_exc())
|
226
|
-
|
251
|
+
tasks = self.task_manager.get_task_history()
|
252
|
+
return FinalizeEvent(success=False, reason=str(e), output=str(e), task=tasks, tasks=tasks, steps=self.step_counter)
|
227
253
|
|
228
254
|
|
229
255
|
@step
|
@@ -259,7 +285,9 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
259
285
|
) -> FinalizeEvent | CodeActExecuteEvent:
|
260
286
|
try:
|
261
287
|
if self.step_counter >= self.max_steps:
|
262
|
-
|
288
|
+
output = f"Reached maximum number of steps ({self.max_steps})"
|
289
|
+
tasks = self.task_manager.get_task_history()
|
290
|
+
return FinalizeEvent(success=False, reason=output, output=output, task=tasks, tasks=tasks, steps=self.step_counter)
|
263
291
|
self.step_counter += 1
|
264
292
|
|
265
293
|
if ev.reflection:
|
@@ -286,10 +314,13 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
286
314
|
|
287
315
|
if self.task_manager.goal_completed:
|
288
316
|
logger.info(f"✅ Goal completed: {self.task_manager.message}")
|
289
|
-
|
317
|
+
tasks = self.task_manager.get_task_history()
|
318
|
+
return FinalizeEvent(success=True, reason=self.task_manager.message, output=self.task_manager.message, task=tasks, tasks=tasks, steps=self.step_counter)
|
290
319
|
if not self.tasks:
|
291
320
|
logger.warning("No tasks generated by planner")
|
292
|
-
|
321
|
+
output = "Planner did not generate any tasks"
|
322
|
+
tasks = self.task_manager.get_task_history()
|
323
|
+
return FinalizeEvent(success=False, reason=output, output=output, task=tasks, tasks=tasks, steps=self.step_counter)
|
293
324
|
|
294
325
|
return CodeActExecuteEvent(task=next(self.task_iter), reflection=None)
|
295
326
|
|
@@ -298,7 +329,8 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
298
329
|
if self.debug:
|
299
330
|
import traceback
|
300
331
|
logger.error(traceback.format_exc())
|
301
|
-
|
332
|
+
tasks = self.task_manager.get_task_history()
|
333
|
+
return FinalizeEvent(success=False, reason=str(e), output=str(e), task=tasks, tasks=tasks, steps=self.step_counter)
|
302
334
|
|
303
335
|
|
304
336
|
@step
|
@@ -331,10 +363,21 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
331
363
|
@step
|
332
364
|
async def finalize(self, ctx: Context, ev: FinalizeEvent) -> StopEvent:
|
333
365
|
ctx.write_event_to_stream(ev)
|
366
|
+
capture(
|
367
|
+
DroidAgentFinalizeEvent(
|
368
|
+
tasks=",".join([f"{t.agent_type}:{t.description}" for t in ev.task]),
|
369
|
+
success=ev.success,
|
370
|
+
output=ev.output,
|
371
|
+
steps=ev.steps,
|
372
|
+
)
|
373
|
+
)
|
374
|
+
flush()
|
334
375
|
|
335
376
|
result = {
|
336
377
|
"success": ev.success,
|
378
|
+
# deprecated. use output instead.
|
337
379
|
"reason": ev.reason,
|
380
|
+
"output": ev.output,
|
338
381
|
"steps": ev.steps,
|
339
382
|
}
|
340
383
|
|
droidrun/agent/droid/events.py
CHANGED
@@ -16,8 +16,12 @@ class ReasoningLogicEvent(Event):
|
|
16
16
|
|
17
17
|
class FinalizeEvent(Event):
|
18
18
|
success: bool
|
19
|
+
# deprecated. use output instead.
|
19
20
|
reason: str
|
21
|
+
output: str
|
22
|
+
# deprecated. use tasks instead.
|
20
23
|
task: List[Task]
|
24
|
+
tasks: List[Task]
|
21
25
|
steps: int = 1
|
22
26
|
|
23
27
|
class TaskRunnerEvent(Event):
|
@@ -31,6 +31,7 @@ def load_llm(provider_name: str, **kwargs: Any) -> LLM:
|
|
31
31
|
raise ValueError("provider_name cannot be empty.")
|
32
32
|
if provider_name == "OpenAILike":
|
33
33
|
module_provider_part = "openai_like"
|
34
|
+
kwargs.setdefault("is_chat_model", True)
|
34
35
|
elif provider_name == "GoogleGenAI":
|
35
36
|
module_provider_part = "google_genai"
|
36
37
|
else:
|