minitap-mobile-use 2.2.0__py3-none-any.whl → 2.4.0__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.
Potentially problematic release.
This version of minitap-mobile-use might be problematic. Click here for more details.
- minitap/mobile_use/agents/contextor/contextor.py +6 -4
- minitap/mobile_use/agents/cortex/cortex.md +114 -27
- minitap/mobile_use/agents/cortex/cortex.py +8 -5
- minitap/mobile_use/agents/executor/executor.md +15 -10
- minitap/mobile_use/agents/executor/executor.py +6 -5
- minitap/mobile_use/agents/executor/utils.py +2 -1
- minitap/mobile_use/agents/hopper/hopper.py +6 -3
- minitap/mobile_use/agents/orchestrator/orchestrator.py +26 -11
- minitap/mobile_use/agents/outputter/outputter.py +6 -3
- minitap/mobile_use/agents/outputter/test_outputter.py +104 -42
- minitap/mobile_use/agents/planner/planner.md +20 -22
- minitap/mobile_use/agents/planner/planner.py +10 -7
- minitap/mobile_use/agents/planner/types.py +4 -2
- minitap/mobile_use/agents/planner/utils.py +14 -0
- minitap/mobile_use/agents/summarizer/summarizer.py +2 -2
- minitap/mobile_use/config.py +6 -1
- minitap/mobile_use/context.py +13 -3
- minitap/mobile_use/controllers/mobile_command_controller.py +1 -14
- minitap/mobile_use/graph/state.py +7 -3
- minitap/mobile_use/sdk/agent.py +204 -29
- minitap/mobile_use/sdk/examples/README.md +19 -1
- minitap/mobile_use/sdk/examples/platform_minimal_example.py +46 -0
- minitap/mobile_use/sdk/services/platform.py +244 -0
- minitap/mobile_use/sdk/types/__init__.py +14 -14
- minitap/mobile_use/sdk/types/exceptions.py +57 -0
- minitap/mobile_use/sdk/types/platform.py +125 -0
- minitap/mobile_use/sdk/types/task.py +60 -17
- minitap/mobile_use/servers/device_hardware_bridge.py +3 -2
- minitap/mobile_use/servers/stop_servers.py +11 -12
- minitap/mobile_use/servers/utils.py +6 -9
- minitap/mobile_use/services/llm.py +89 -5
- minitap/mobile_use/tools/index.py +2 -8
- minitap/mobile_use/tools/mobile/back.py +3 -3
- minitap/mobile_use/tools/mobile/clear_text.py +67 -38
- minitap/mobile_use/tools/mobile/erase_one_char.py +5 -4
- minitap/mobile_use/tools/mobile/{take_screenshot.py → glimpse_screen.py} +23 -15
- minitap/mobile_use/tools/mobile/input_text.py +67 -16
- minitap/mobile_use/tools/mobile/launch_app.py +54 -22
- minitap/mobile_use/tools/mobile/long_press_on.py +15 -8
- minitap/mobile_use/tools/mobile/open_link.py +15 -8
- minitap/mobile_use/tools/mobile/press_key.py +15 -8
- minitap/mobile_use/tools/mobile/stop_app.py +14 -8
- minitap/mobile_use/tools/mobile/swipe.py +11 -5
- minitap/mobile_use/tools/mobile/tap.py +103 -21
- minitap/mobile_use/tools/mobile/wait_for_animation_to_end.py +3 -3
- minitap/mobile_use/tools/test_utils.py +377 -0
- minitap/mobile_use/tools/types.py +35 -0
- minitap/mobile_use/tools/utils.py +149 -39
- minitap/mobile_use/utils/recorder.py +1 -1
- minitap/mobile_use/utils/test_ui_hierarchy.py +178 -0
- minitap/mobile_use/utils/ui_hierarchy.py +11 -4
- {minitap_mobile_use-2.2.0.dist-info → minitap_mobile_use-2.4.0.dist-info}/METADATA +6 -4
- minitap_mobile_use-2.4.0.dist-info/RECORD +99 -0
- minitap/mobile_use/tools/mobile/copy_text_from.py +0 -73
- minitap/mobile_use/tools/mobile/find_packages.py +0 -69
- minitap/mobile_use/tools/mobile/paste_text.py +0 -62
- minitap_mobile_use-2.2.0.dist-info/RECORD +0 -96
- {minitap_mobile_use-2.2.0.dist-info → minitap_mobile_use-2.4.0.dist-info}/WHEEL +0 -0
- {minitap_mobile_use-2.2.0.dist-info → minitap_mobile_use-2.4.0.dist-info}/entry_points.txt +0 -0
minitap/mobile_use/sdk/agent.py
CHANGED
|
@@ -3,23 +3,28 @@ import sys
|
|
|
3
3
|
import tempfile
|
|
4
4
|
import time
|
|
5
5
|
import uuid
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Callable, Coroutine
|
|
7
|
+
from datetime import UTC, datetime
|
|
7
8
|
from pathlib import Path
|
|
9
|
+
from shutil import which
|
|
8
10
|
from types import NoneType
|
|
9
|
-
from typing import TypeVar, overload
|
|
11
|
+
from typing import Any, TypeVar, overload
|
|
10
12
|
|
|
11
13
|
from adbutils import AdbClient
|
|
14
|
+
from dotenv import load_dotenv
|
|
12
15
|
from langchain_core.messages import AIMessage
|
|
13
16
|
from pydantic import BaseModel
|
|
14
17
|
|
|
15
18
|
from minitap.mobile_use.agents.outputter.outputter import outputter
|
|
19
|
+
from minitap.mobile_use.agents.planner.types import Subgoal
|
|
16
20
|
from minitap.mobile_use.clients.device_hardware_client import DeviceHardwareClient
|
|
17
21
|
from minitap.mobile_use.clients.screen_api_client import ScreenApiClient
|
|
18
|
-
from minitap.mobile_use.config import OutputConfig, record_events
|
|
22
|
+
from minitap.mobile_use.config import AgentNode, OutputConfig, record_events, settings
|
|
19
23
|
from minitap.mobile_use.context import (
|
|
20
24
|
DeviceContext,
|
|
21
25
|
DevicePlatform,
|
|
22
26
|
ExecutionSetup,
|
|
27
|
+
IsReplan,
|
|
23
28
|
MobileUseContext,
|
|
24
29
|
)
|
|
25
30
|
from minitap.mobile_use.controllers.mobile_command_controller import (
|
|
@@ -31,19 +36,26 @@ from minitap.mobile_use.graph.graph import get_graph
|
|
|
31
36
|
from minitap.mobile_use.graph.state import State
|
|
32
37
|
from minitap.mobile_use.sdk.builders.agent_config_builder import get_default_agent_config
|
|
33
38
|
from minitap.mobile_use.sdk.builders.task_request_builder import TaskRequestBuilder
|
|
34
|
-
from minitap.mobile_use.sdk.constants import
|
|
35
|
-
|
|
36
|
-
DEFAULT_SCREEN_API_BASE_URL,
|
|
37
|
-
)
|
|
39
|
+
from minitap.mobile_use.sdk.constants import DEFAULT_HW_BRIDGE_BASE_URL, DEFAULT_SCREEN_API_BASE_URL
|
|
40
|
+
from minitap.mobile_use.sdk.services.platform import PlatformService
|
|
38
41
|
from minitap.mobile_use.sdk.types.agent import AgentConfig
|
|
39
42
|
from minitap.mobile_use.sdk.types.exceptions import (
|
|
40
43
|
AgentNotInitializedError,
|
|
41
44
|
AgentProfileNotFoundError,
|
|
42
45
|
AgentTaskRequestError,
|
|
43
46
|
DeviceNotFoundError,
|
|
47
|
+
ExecutableNotFoundError,
|
|
48
|
+
PlatformServiceUninitializedError,
|
|
44
49
|
ServerStartupError,
|
|
45
50
|
)
|
|
46
|
-
from minitap.mobile_use.sdk.types.
|
|
51
|
+
from minitap.mobile_use.sdk.types.platform import TaskRunPlanResponse, TaskRunStatus
|
|
52
|
+
from minitap.mobile_use.sdk.types.task import (
|
|
53
|
+
AgentProfile,
|
|
54
|
+
PlatformTaskInfo,
|
|
55
|
+
PlatformTaskRequest,
|
|
56
|
+
Task,
|
|
57
|
+
TaskRequest,
|
|
58
|
+
)
|
|
47
59
|
from minitap.mobile_use.servers.device_hardware_bridge import BridgeStatus
|
|
48
60
|
from minitap.mobile_use.servers.start_servers import (
|
|
49
61
|
start_device_hardware_bridge,
|
|
@@ -63,6 +75,8 @@ logger = get_logger(__name__)
|
|
|
63
75
|
|
|
64
76
|
TOutput = TypeVar("TOutput", bound=BaseModel | None)
|
|
65
77
|
|
|
78
|
+
load_dotenv()
|
|
79
|
+
|
|
66
80
|
|
|
67
81
|
class Agent:
|
|
68
82
|
_config: AgentConfig
|
|
@@ -76,11 +90,23 @@ class Agent:
|
|
|
76
90
|
_hw_bridge_client: DeviceHardwareClient
|
|
77
91
|
_adb_client: AdbClient | None
|
|
78
92
|
|
|
79
|
-
def __init__(self, config: AgentConfig | None = None):
|
|
93
|
+
def __init__(self, *, config: AgentConfig | None = None):
|
|
80
94
|
self._config = config or get_default_agent_config()
|
|
81
95
|
self._tasks = []
|
|
82
96
|
self._tmp_traces_dir = Path(tempfile.gettempdir()) / "mobile-use-traces"
|
|
83
97
|
self._initialized = False
|
|
98
|
+
self._is_default_hw_bridge = (
|
|
99
|
+
self._config.servers.hw_bridge_base_url == DEFAULT_HW_BRIDGE_BASE_URL
|
|
100
|
+
)
|
|
101
|
+
self._is_default_screen_api = (
|
|
102
|
+
self._config.servers.screen_api_base_url == DEFAULT_SCREEN_API_BASE_URL
|
|
103
|
+
)
|
|
104
|
+
# Initialize platform service if API key is available in environment
|
|
105
|
+
# Note: Can also be initialized later with API key from request
|
|
106
|
+
if settings.MINITAP_API_KEY:
|
|
107
|
+
self._platform_service = PlatformService()
|
|
108
|
+
else:
|
|
109
|
+
self._platform_service = None
|
|
84
110
|
|
|
85
111
|
def init(
|
|
86
112
|
self,
|
|
@@ -88,6 +114,11 @@ class Agent:
|
|
|
88
114
|
retry_count: int = 5,
|
|
89
115
|
retry_wait_seconds: int = 5,
|
|
90
116
|
):
|
|
117
|
+
if not which("adb") and not which("xcrun"):
|
|
118
|
+
raise ExecutableNotFoundError("cli_tools")
|
|
119
|
+
if self._is_default_hw_bridge and not which("maestro"):
|
|
120
|
+
raise ExecutableNotFoundError("maestro")
|
|
121
|
+
|
|
91
122
|
if self._initialized:
|
|
92
123
|
logger.warning("Agent is already initialized. Skipping...")
|
|
93
124
|
return True
|
|
@@ -183,6 +214,12 @@ class Agent:
|
|
|
183
214
|
@overload
|
|
184
215
|
async def run_task(self, *, request: TaskRequest[TOutput]) -> TOutput | None: ...
|
|
185
216
|
|
|
217
|
+
@overload
|
|
218
|
+
async def run_task(self, *, request: PlatformTaskRequest[None]) -> str | dict | None: ...
|
|
219
|
+
|
|
220
|
+
@overload
|
|
221
|
+
async def run_task(self, *, request: PlatformTaskRequest[TOutput]) -> TOutput | None: ...
|
|
222
|
+
|
|
186
223
|
async def run_task(
|
|
187
224
|
self,
|
|
188
225
|
*,
|
|
@@ -190,10 +227,25 @@ class Agent:
|
|
|
190
227
|
output: type[TOutput] | str | None = None,
|
|
191
228
|
profile: str | AgentProfile | None = None,
|
|
192
229
|
name: str | None = None,
|
|
193
|
-
request: TaskRequest[TOutput] | None = None,
|
|
230
|
+
request: TaskRequest[TOutput] | PlatformTaskRequest[TOutput] | None = None,
|
|
194
231
|
) -> str | dict | TOutput | None:
|
|
195
232
|
if request is not None:
|
|
196
|
-
|
|
233
|
+
task_info = None
|
|
234
|
+
platform_service = None
|
|
235
|
+
if isinstance(request, PlatformTaskRequest):
|
|
236
|
+
# Initialize platform service with API key from request if provided
|
|
237
|
+
if request.api_key:
|
|
238
|
+
platform_service = PlatformService(api_key=request.api_key)
|
|
239
|
+
elif self._platform_service:
|
|
240
|
+
platform_service = self._platform_service
|
|
241
|
+
else:
|
|
242
|
+
raise PlatformServiceUninitializedError()
|
|
243
|
+
task_info = await platform_service.create_task_run(request=request)
|
|
244
|
+
self._config.agent_profiles[task_info.llm_profile.name] = task_info.llm_profile
|
|
245
|
+
request = task_info.task_request
|
|
246
|
+
return await self._run_task(
|
|
247
|
+
request=request, task_info=task_info, platform_service=platform_service
|
|
248
|
+
)
|
|
197
249
|
if goal is None:
|
|
198
250
|
raise AgentTaskRequestError("Goal is required")
|
|
199
251
|
task_request = self.new_task(goal=goal)
|
|
@@ -208,7 +260,12 @@ class Agent:
|
|
|
208
260
|
task_request.with_name(name=name)
|
|
209
261
|
return await self._run_task(task_request.build())
|
|
210
262
|
|
|
211
|
-
async def _run_task(
|
|
263
|
+
async def _run_task(
|
|
264
|
+
self,
|
|
265
|
+
request: TaskRequest[TOutput],
|
|
266
|
+
task_info: PlatformTaskInfo | None = None,
|
|
267
|
+
platform_service: PlatformService | None = None,
|
|
268
|
+
) -> str | dict | TOutput | None:
|
|
212
269
|
if not self._initialized:
|
|
213
270
|
raise AgentNotInitializedError()
|
|
214
271
|
|
|
@@ -220,22 +277,48 @@ class Agent:
|
|
|
220
277
|
agent_profile = self._config.default_profile
|
|
221
278
|
logger.info(str(agent_profile))
|
|
222
279
|
|
|
280
|
+
on_status_changed = None
|
|
281
|
+
on_agent_thought = None
|
|
282
|
+
on_plan_changes = None
|
|
283
|
+
task_id = str(uuid.uuid4())
|
|
284
|
+
if task_info:
|
|
285
|
+
on_status_changed = self._get_task_status_change_callback(
|
|
286
|
+
task_info=task_info, platform_service=platform_service
|
|
287
|
+
)
|
|
288
|
+
on_agent_thought = self._get_new_agent_thought_callback(
|
|
289
|
+
task_info=task_info, platform_service=platform_service
|
|
290
|
+
)
|
|
291
|
+
on_plan_changes = self._get_plan_changes_callback(
|
|
292
|
+
task_info=task_info, platform_service=platform_service
|
|
293
|
+
)
|
|
294
|
+
task_id = task_info.task_run.id
|
|
295
|
+
|
|
223
296
|
task = Task(
|
|
224
|
-
id=
|
|
297
|
+
id=task_id,
|
|
225
298
|
device=self._device_context,
|
|
226
|
-
status=
|
|
299
|
+
status="pending",
|
|
227
300
|
request=request,
|
|
228
301
|
created_at=datetime.now(),
|
|
302
|
+
on_status_changed=on_status_changed,
|
|
229
303
|
)
|
|
230
304
|
self._tasks.append(task)
|
|
231
305
|
task_name = task.get_name()
|
|
232
306
|
|
|
307
|
+
# Extract API key from platform service if available
|
|
308
|
+
api_key = None
|
|
309
|
+
if platform_service:
|
|
310
|
+
api_key = platform_service._api_key
|
|
311
|
+
|
|
233
312
|
context = MobileUseContext(
|
|
313
|
+
trace_id=task.id,
|
|
234
314
|
device=self._device_context,
|
|
235
315
|
hw_bridge_client=self._hw_bridge_client,
|
|
236
316
|
screen_api_client=self._screen_api_client,
|
|
237
317
|
adb_client=self._adb_client,
|
|
238
318
|
llm_config=agent_profile.llm_config,
|
|
319
|
+
on_agent_thought=on_agent_thought,
|
|
320
|
+
on_plan_changes=on_plan_changes,
|
|
321
|
+
minitap_api_key=api_key,
|
|
239
322
|
)
|
|
240
323
|
|
|
241
324
|
self._prepare_tracing(task=task, context=context)
|
|
@@ -258,7 +341,7 @@ class Agent:
|
|
|
258
341
|
output = None
|
|
259
342
|
try:
|
|
260
343
|
logger.info(f"[{task_name}] Invoking graph with input: {graph_input}")
|
|
261
|
-
task.status =
|
|
344
|
+
await task.set_status(status="running", message="Invoking graph...")
|
|
262
345
|
async for chunk in (await get_graph(context)).astream(
|
|
263
346
|
input=graph_input,
|
|
264
347
|
config={
|
|
@@ -290,7 +373,7 @@ class Agent:
|
|
|
290
373
|
if not last_state:
|
|
291
374
|
err = f"[{task_name}] No result received from graph"
|
|
292
375
|
logger.warning(err)
|
|
293
|
-
task.finalize(content=output, state=last_state_snapshot, error=err)
|
|
376
|
+
await task.finalize(content=output, state=last_state_snapshot, error=err)
|
|
294
377
|
return None
|
|
295
378
|
|
|
296
379
|
print_ai_response_to_stderr(graph_result=last_state)
|
|
@@ -302,16 +385,25 @@ class Agent:
|
|
|
302
385
|
state=last_state,
|
|
303
386
|
)
|
|
304
387
|
logger.info(f"✅ Automation '{task_name}' is success ✅")
|
|
305
|
-
task.finalize(content=output, state=last_state_snapshot)
|
|
388
|
+
await task.finalize(content=output, state=last_state_snapshot)
|
|
306
389
|
except asyncio.CancelledError:
|
|
307
390
|
err = f"[{task_name}] Task cancelled"
|
|
308
391
|
logger.warning(err)
|
|
309
|
-
task.finalize(
|
|
392
|
+
await task.finalize(
|
|
393
|
+
content=output,
|
|
394
|
+
state=last_state_snapshot,
|
|
395
|
+
error=err,
|
|
396
|
+
cancelled=True,
|
|
397
|
+
)
|
|
310
398
|
raise
|
|
311
399
|
except Exception as e:
|
|
312
400
|
err = f"[{task_name}] Error running automation: {e}"
|
|
313
401
|
logger.error(err)
|
|
314
|
-
task.finalize(
|
|
402
|
+
await task.finalize(
|
|
403
|
+
content=output,
|
|
404
|
+
state=last_state_snapshot,
|
|
405
|
+
error=err,
|
|
406
|
+
)
|
|
315
407
|
raise
|
|
316
408
|
finally:
|
|
317
409
|
self._finalize_tracing(task=task, context=context)
|
|
@@ -342,7 +434,9 @@ class Agent:
|
|
|
342
434
|
traces_output_path.mkdir(parents=True, exist_ok=True)
|
|
343
435
|
temp_trace_path.mkdir(parents=True, exist_ok=True)
|
|
344
436
|
context.execution_setup = ExecutionSetup(
|
|
345
|
-
traces_path=self._tmp_traces_dir,
|
|
437
|
+
traces_path=self._tmp_traces_dir,
|
|
438
|
+
trace_name=task_name,
|
|
439
|
+
enable_remote_tracing=task.request.enable_remote_tracing,
|
|
346
440
|
)
|
|
347
441
|
|
|
348
442
|
def _finalize_tracing(self, task: Task, context: MobileUseContext):
|
|
@@ -351,11 +445,11 @@ class Agent:
|
|
|
351
445
|
return
|
|
352
446
|
|
|
353
447
|
task_name = task.get_name()
|
|
354
|
-
status = "_PASS" if task.status ==
|
|
448
|
+
status = "_PASS" if task.status == "completed" else "_FAIL"
|
|
355
449
|
ts = task.created_at.strftime("%Y-%m-%dT%H-%M-%S")
|
|
356
|
-
new_name = f"{exec_setup_ctx.
|
|
450
|
+
new_name = f"{exec_setup_ctx.trace_name}{status}_{ts}"
|
|
357
451
|
|
|
358
|
-
temp_trace_path = (self._tmp_traces_dir / exec_setup_ctx.
|
|
452
|
+
temp_trace_path = (self._tmp_traces_dir / exec_setup_ctx.trace_name).resolve()
|
|
359
453
|
traces_output_path = Path(task.request.trace_path).resolve()
|
|
360
454
|
|
|
361
455
|
logger.info(f"[{task_name}] Compiling trace FROM FOLDER: " + str(temp_trace_path))
|
|
@@ -433,17 +527,11 @@ class Agent:
|
|
|
433
527
|
self._hw_bridge_client = DeviceHardwareClient(
|
|
434
528
|
base_url=self._config.servers.hw_bridge_base_url.to_url(),
|
|
435
529
|
)
|
|
436
|
-
self._is_default_hw_bridge = (
|
|
437
|
-
self._config.servers.hw_bridge_base_url == DEFAULT_HW_BRIDGE_BASE_URL
|
|
438
|
-
)
|
|
439
530
|
self._screen_api_client = ScreenApiClient(
|
|
440
531
|
base_url=self._config.servers.screen_api_base_url.to_url(),
|
|
441
532
|
retry_count=retry_count,
|
|
442
533
|
retry_wait_seconds=retry_wait_seconds,
|
|
443
534
|
)
|
|
444
|
-
self._is_default_screen_api = (
|
|
445
|
-
self._config.servers.screen_api_base_url == DEFAULT_SCREEN_API_BASE_URL
|
|
446
|
-
)
|
|
447
535
|
|
|
448
536
|
def _run_servers(self, device_id: str, platform: DevicePlatform) -> bool:
|
|
449
537
|
if self._is_default_hw_bridge:
|
|
@@ -496,7 +584,10 @@ class Agent:
|
|
|
496
584
|
|
|
497
585
|
def _check_device_screen_api_health(self) -> bool:
|
|
498
586
|
try:
|
|
587
|
+
# Required to know if the Screen API is up
|
|
499
588
|
self._screen_api_client.get_with_retry("/health", timeout=5)
|
|
589
|
+
# Required to know if the Screen API actually receives screenshot from the HW Bridge API
|
|
590
|
+
self._screen_api_client.get_with_retry("/screen-info", timeout=5)
|
|
500
591
|
return True
|
|
501
592
|
except Exception as e:
|
|
502
593
|
logger.error(f"Device Screen API health check failed: {e}")
|
|
@@ -519,6 +610,90 @@ class Agent:
|
|
|
519
610
|
device_height=screen_data.height,
|
|
520
611
|
)
|
|
521
612
|
|
|
613
|
+
def _get_task_status_change_callback(
|
|
614
|
+
self,
|
|
615
|
+
task_info: PlatformTaskInfo,
|
|
616
|
+
platform_service: PlatformService | None = None,
|
|
617
|
+
) -> Callable[[TaskRunStatus, str | None, Any | None], Coroutine]:
|
|
618
|
+
service = platform_service or self._platform_service
|
|
619
|
+
|
|
620
|
+
async def change_status(
|
|
621
|
+
status: TaskRunStatus,
|
|
622
|
+
message: str | None = None,
|
|
623
|
+
output: Any | None = None,
|
|
624
|
+
):
|
|
625
|
+
if not service:
|
|
626
|
+
raise PlatformServiceUninitializedError()
|
|
627
|
+
try:
|
|
628
|
+
await service.update_task_run_status(
|
|
629
|
+
task_run_id=task_info.task_run.id,
|
|
630
|
+
status=status,
|
|
631
|
+
message=message,
|
|
632
|
+
output=output,
|
|
633
|
+
)
|
|
634
|
+
except Exception as e:
|
|
635
|
+
logger.error(f"Failed to update task run status: {e}")
|
|
636
|
+
|
|
637
|
+
return change_status
|
|
638
|
+
|
|
639
|
+
def _get_plan_changes_callback(
|
|
640
|
+
self,
|
|
641
|
+
task_info: PlatformTaskInfo,
|
|
642
|
+
platform_service: PlatformService | None = None,
|
|
643
|
+
) -> Callable[[list[Subgoal], IsReplan], Coroutine]:
|
|
644
|
+
service = platform_service or self._platform_service
|
|
645
|
+
current_plan: TaskRunPlanResponse | None = None
|
|
646
|
+
|
|
647
|
+
async def update_plan(plan: list[Subgoal], is_replan: IsReplan):
|
|
648
|
+
nonlocal current_plan
|
|
649
|
+
|
|
650
|
+
if not service:
|
|
651
|
+
raise PlatformServiceUninitializedError()
|
|
652
|
+
try:
|
|
653
|
+
if is_replan and current_plan:
|
|
654
|
+
# End previous plan
|
|
655
|
+
await service.upsert_task_run_plan(
|
|
656
|
+
task_run_id=task_info.task_run.id,
|
|
657
|
+
started_at=current_plan.started_at,
|
|
658
|
+
plan=plan,
|
|
659
|
+
ended_at=datetime.now(UTC),
|
|
660
|
+
plan_id=current_plan.id,
|
|
661
|
+
)
|
|
662
|
+
current_plan = None
|
|
663
|
+
|
|
664
|
+
current_plan = await service.upsert_task_run_plan(
|
|
665
|
+
task_run_id=task_info.task_run.id,
|
|
666
|
+
started_at=current_plan.started_at if current_plan else datetime.now(UTC),
|
|
667
|
+
plan=plan,
|
|
668
|
+
ended_at=current_plan.ended_at if current_plan else None,
|
|
669
|
+
plan_id=current_plan.id if current_plan else None,
|
|
670
|
+
)
|
|
671
|
+
except Exception as e:
|
|
672
|
+
logger.error(f"Failed to update plan: {e}")
|
|
673
|
+
|
|
674
|
+
return update_plan
|
|
675
|
+
|
|
676
|
+
def _get_new_agent_thought_callback(
|
|
677
|
+
self,
|
|
678
|
+
task_info: PlatformTaskInfo,
|
|
679
|
+
platform_service: PlatformService | None = None,
|
|
680
|
+
) -> Callable[[AgentNode, str], Coroutine]:
|
|
681
|
+
service = platform_service or self._platform_service
|
|
682
|
+
|
|
683
|
+
async def add_agent_thought(agent: AgentNode, thought: str):
|
|
684
|
+
if not service:
|
|
685
|
+
raise PlatformServiceUninitializedError()
|
|
686
|
+
try:
|
|
687
|
+
await service.add_agent_thought(
|
|
688
|
+
task_run_id=task_info.task_run.id,
|
|
689
|
+
agent=agent,
|
|
690
|
+
thought=thought,
|
|
691
|
+
)
|
|
692
|
+
except Exception as e:
|
|
693
|
+
logger.error(f"Failed to add agent thought: {e}")
|
|
694
|
+
|
|
695
|
+
return add_agent_thought
|
|
696
|
+
|
|
522
697
|
|
|
523
698
|
def _validate_and_prepare_file(file_path: Path):
|
|
524
699
|
path_obj = Path(file_path)
|
|
@@ -3,12 +3,26 @@
|
|
|
3
3
|
Location: `src/mobile_use/sdk/examples/`
|
|
4
4
|
|
|
5
5
|
Run any example via:
|
|
6
|
+
|
|
6
7
|
- `python src/mobile_use/sdk/examples/<filename>.py`
|
|
7
8
|
|
|
8
9
|
## Practical Automation Examples
|
|
9
10
|
|
|
10
11
|
These examples demonstrate two different ways to use the SDK, each applying an appropriate level of complexity for the task at hand:
|
|
11
12
|
|
|
13
|
+
### platform_minimal_example.py - Painless integration with the Minitap platform
|
|
14
|
+
|
|
15
|
+
This script shows the simplest way to run minitap :
|
|
16
|
+
|
|
17
|
+
- Visit https://platform.minitap.ai to create a task and get your API key.
|
|
18
|
+
- Initialize the agent with your API key: Agent(minitap_api_key=...).
|
|
19
|
+
- Ask the agent to run one of the tasks you’ve set up in the Minitap platform
|
|
20
|
+
(e.g., "like-instagram-post").
|
|
21
|
+
- The task’s goal and settings live in the Minitap platform, you don’t need
|
|
22
|
+
to hardcode them here.
|
|
23
|
+
- If you’ve created different profiles (LLM configurations) in the Minitap platform (like "fast-config"),
|
|
24
|
+
you can pick which one to use with the `profile` field.
|
|
25
|
+
|
|
12
26
|
### simple_photo_organizer.py - Straightforward Approach
|
|
13
27
|
|
|
14
28
|
Demonstrates the simplest way to use the SDK for quick automation tasks:
|
|
@@ -32,7 +46,11 @@ Showcases more advanced SDK features while remaining practical:
|
|
|
32
46
|
|
|
33
47
|
## Usage Notes
|
|
34
48
|
|
|
35
|
-
- **Choosing an Approach**:
|
|
49
|
+
- **Choosing an Approach**:
|
|
50
|
+
|
|
51
|
+
- Use the direct approach (like `platform_minimal_example.py`) for painless setup using the Minitap platform. You can configure any task, save, run, and monitor them with a few clicks.
|
|
52
|
+
- Use the simple approach (like `simple_photo_organizer.py`) for straightforward tasks, you configure settings yourself and every LLM call happens on your device.
|
|
53
|
+
- Use the builder approach (like `smart_notification_assistant.py`) when you need more customization.
|
|
36
54
|
|
|
37
55
|
- **Device Detection**: The agent detects the first available device unless you specify one with `AgentConfigBuilder.for_device(...)`.
|
|
38
56
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Platform Usage - Minitap SDK with API Key Example
|
|
3
|
+
|
|
4
|
+
This example demonstrates how to use the mobile-use SDK via the Minitap platform:
|
|
5
|
+
- Agent with minitap_api_key
|
|
6
|
+
- PlatformTaskRequest with platform-provided task_id
|
|
7
|
+
- All task configuration (goal, output format, etc.) managed by platform UI
|
|
8
|
+
|
|
9
|
+
Platform Model:
|
|
10
|
+
- API key provides authentication and agent configuration
|
|
11
|
+
- task_id references pre-configured task from platform UI
|
|
12
|
+
- No goal, output_format, profile selection needed in code
|
|
13
|
+
- Everything bound to task_id + api_key combination
|
|
14
|
+
|
|
15
|
+
Run:
|
|
16
|
+
- python src/mobile_use/sdk/examples/platform_minimal_example.py
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
|
|
21
|
+
from minitap.mobile_use.sdk import Agent
|
|
22
|
+
from minitap.mobile_use.sdk.types import PlatformTaskRequest
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def main() -> None:
|
|
26
|
+
"""
|
|
27
|
+
Main execution function demonstrating minitap platform usage pattern.
|
|
28
|
+
|
|
29
|
+
Visit https://platform.minitap.ai to create a task, customize your profiles,
|
|
30
|
+
and get your API key.
|
|
31
|
+
Set MINITAP_API_KEY and MINITAP_API_BASE_URL environment variables.
|
|
32
|
+
"""
|
|
33
|
+
agent = Agent()
|
|
34
|
+
agent.init()
|
|
35
|
+
result = await agent.run_task(
|
|
36
|
+
request=PlatformTaskRequest(
|
|
37
|
+
task="your-task-name",
|
|
38
|
+
profile="your-profile-name",
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
print(result)
|
|
42
|
+
agent.clean()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
asyncio.run(main())
|