minitap-mobile-use 2.3.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 +2 -2
- minitap/mobile_use/agents/cortex/cortex.md +49 -8
- minitap/mobile_use/agents/cortex/cortex.py +8 -4
- minitap/mobile_use/agents/executor/executor.md +14 -11
- minitap/mobile_use/agents/executor/executor.py +6 -5
- 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/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 +188 -23
- 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 +27 -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 +1 -1
- minitap/mobile_use/servers/stop_servers.py +11 -12
- minitap/mobile_use/services/llm.py +89 -5
- minitap/mobile_use/tools/index.py +0 -6
- minitap/mobile_use/tools/mobile/back.py +3 -3
- minitap/mobile_use/tools/mobile/clear_text.py +24 -43
- minitap/mobile_use/tools/mobile/erase_one_char.py +5 -4
- minitap/mobile_use/tools/mobile/glimpse_screen.py +11 -7
- minitap/mobile_use/tools/mobile/input_text.py +21 -51
- 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 +104 -78
- minitap/mobile_use/tools/types.py +35 -0
- minitap/mobile_use/tools/utils.py +51 -48
- minitap/mobile_use/utils/recorder.py +1 -1
- minitap/mobile_use/utils/ui_hierarchy.py +9 -2
- {minitap_mobile_use-2.3.0.dist-info → minitap_mobile_use-2.4.0.dist-info}/METADATA +3 -1
- {minitap_mobile_use-2.3.0.dist-info → minitap_mobile_use-2.4.0.dist-info}/RECORD +51 -50
- minitap/mobile_use/tools/mobile/copy_text_from.py +0 -75
- minitap/mobile_use/tools/mobile/find_packages.py +0 -69
- minitap/mobile_use/tools/mobile/paste_text.py +0 -88
- {minitap_mobile_use-2.3.0.dist-info → minitap_mobile_use-2.4.0.dist-info}/WHEEL +0 -0
- {minitap_mobile_use-2.3.0.dist-info → minitap_mobile_use-2.4.0.dist-info}/entry_points.txt +0 -0
minitap/mobile_use/context.py
CHANGED
|
@@ -4,17 +4,19 @@ Context variables for global state management.
|
|
|
4
4
|
Uses ContextVar to avoid prop drilling and maintain clean function signatures.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
from collections.abc import Callable, Coroutine
|
|
7
8
|
from enum import Enum
|
|
8
9
|
from pathlib import Path
|
|
10
|
+
from typing import Literal
|
|
9
11
|
|
|
10
12
|
from adbutils import AdbClient
|
|
11
13
|
from openai import BaseModel
|
|
12
14
|
from pydantic import ConfigDict
|
|
13
|
-
from typing import Literal
|
|
14
15
|
|
|
16
|
+
from minitap.mobile_use.agents.planner.types import Subgoal
|
|
15
17
|
from minitap.mobile_use.clients.device_hardware_client import DeviceHardwareClient
|
|
16
18
|
from minitap.mobile_use.clients.screen_api_client import ScreenApiClient
|
|
17
|
-
from minitap.mobile_use.config import LLMConfig
|
|
19
|
+
from minitap.mobile_use.config import AgentNode, LLMConfig
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class DevicePlatform(str, Enum):
|
|
@@ -45,18 +47,26 @@ class ExecutionSetup(BaseModel):
|
|
|
45
47
|
"""Execution setup for a task."""
|
|
46
48
|
|
|
47
49
|
traces_path: Path
|
|
48
|
-
|
|
50
|
+
trace_name: str
|
|
51
|
+
enable_remote_tracing: bool
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
IsReplan = bool
|
|
49
55
|
|
|
50
56
|
|
|
51
57
|
class MobileUseContext(BaseModel):
|
|
52
58
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
53
59
|
|
|
60
|
+
trace_id: str
|
|
54
61
|
device: DeviceContext
|
|
55
62
|
hw_bridge_client: DeviceHardwareClient
|
|
56
63
|
screen_api_client: ScreenApiClient
|
|
57
64
|
llm_config: LLMConfig
|
|
58
65
|
adb_client: AdbClient | None = None
|
|
59
66
|
execution_setup: ExecutionSetup | None = None
|
|
67
|
+
on_agent_thought: Callable[[AgentNode, str], Coroutine] | None = None
|
|
68
|
+
on_plan_changes: Callable[[list[Subgoal], IsReplan], Coroutine] | None = None
|
|
69
|
+
minitap_api_key: str | None = None
|
|
60
70
|
|
|
61
71
|
def get_adb_client(self) -> AdbClient:
|
|
62
72
|
if self.adb_client is None:
|
|
@@ -243,20 +243,6 @@ def input_text(ctx: MobileUseContext, text: str, dry_run: bool = False):
|
|
|
243
243
|
return run_flow(ctx, [{"inputText": text}], dry_run=dry_run)
|
|
244
244
|
|
|
245
245
|
|
|
246
|
-
def copy_text_from(ctx: MobileUseContext, selector_request: SelectorRequest, dry_run: bool = False):
|
|
247
|
-
copy_text_from_body = selector_request.to_dict()
|
|
248
|
-
if not copy_text_from_body:
|
|
249
|
-
error = "Invalid copyTextFrom selector request, could not format yaml"
|
|
250
|
-
logger.error(error)
|
|
251
|
-
raise ControllerErrors(error)
|
|
252
|
-
flow_input = [{"copyTextFrom": copy_text_from_body}]
|
|
253
|
-
return run_flow(ctx, flow_input, dry_run=dry_run)
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
def paste_text(ctx: MobileUseContext, dry_run: bool = False):
|
|
257
|
-
return run_flow(ctx, ["pasteText"], dry_run=dry_run)
|
|
258
|
-
|
|
259
|
-
|
|
260
246
|
def erase_text(ctx: MobileUseContext, nb_chars: int | None = None, dry_run: bool = False):
|
|
261
247
|
"""
|
|
262
248
|
Removes characters from the currently selected textfield (if any)
|
|
@@ -333,6 +319,7 @@ def run_flow_with_wait_for_animation_to_end(
|
|
|
333
319
|
|
|
334
320
|
if __name__ == "__main__":
|
|
335
321
|
ctx = MobileUseContext(
|
|
322
|
+
trace_id="trace_id",
|
|
336
323
|
llm_config=initialize_llm_config(),
|
|
337
324
|
device=DeviceContext(
|
|
338
325
|
host_platform="WINDOWS",
|
|
@@ -54,7 +54,7 @@ class State(AgentStatePydantic):
|
|
|
54
54
|
take_last,
|
|
55
55
|
]
|
|
56
56
|
|
|
57
|
-
def
|
|
57
|
+
async def asanitize_update(
|
|
58
58
|
self,
|
|
59
59
|
ctx: MobileUseContext,
|
|
60
60
|
update: dict,
|
|
@@ -72,7 +72,7 @@ class State(AgentStatePydantic):
|
|
|
72
72
|
raise ValueError("agents_thoughts must be a str or list[str]")
|
|
73
73
|
if agent is None:
|
|
74
74
|
raise ValueError("Agent is required when updating the 'agents_thoughts' key")
|
|
75
|
-
update["agents_thoughts"] = _add_agent_thoughts(
|
|
75
|
+
update["agents_thoughts"] = await _add_agent_thoughts(
|
|
76
76
|
ctx=ctx,
|
|
77
77
|
old=self.agents_thoughts,
|
|
78
78
|
new=updated_agents_thoughts,
|
|
@@ -81,12 +81,16 @@ class State(AgentStatePydantic):
|
|
|
81
81
|
return update
|
|
82
82
|
|
|
83
83
|
|
|
84
|
-
def _add_agent_thoughts(
|
|
84
|
+
async def _add_agent_thoughts(
|
|
85
85
|
ctx: MobileUseContext,
|
|
86
86
|
old: list[str],
|
|
87
87
|
new: list[str],
|
|
88
88
|
agent: AgentNode,
|
|
89
89
|
) -> list[str]:
|
|
90
|
+
if ctx.on_agent_thought:
|
|
91
|
+
for thought in new:
|
|
92
|
+
await ctx.on_agent_thought(agent, thought)
|
|
93
|
+
|
|
90
94
|
named_thoughts = [f"[{agent}] {thought}" for thought in new]
|
|
91
95
|
if ctx.execution_setup:
|
|
92
96
|
record_interaction(ctx, response=AIMessage(content=str(named_thoughts)))
|
minitap/mobile_use/sdk/agent.py
CHANGED
|
@@ -3,24 +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
|
|
8
9
|
from shutil import which
|
|
9
10
|
from types import NoneType
|
|
10
|
-
from typing import TypeVar, overload
|
|
11
|
+
from typing import Any, TypeVar, overload
|
|
11
12
|
|
|
12
13
|
from adbutils import AdbClient
|
|
14
|
+
from dotenv import load_dotenv
|
|
13
15
|
from langchain_core.messages import AIMessage
|
|
14
16
|
from pydantic import BaseModel
|
|
15
17
|
|
|
16
18
|
from minitap.mobile_use.agents.outputter.outputter import outputter
|
|
19
|
+
from minitap.mobile_use.agents.planner.types import Subgoal
|
|
17
20
|
from minitap.mobile_use.clients.device_hardware_client import DeviceHardwareClient
|
|
18
21
|
from minitap.mobile_use.clients.screen_api_client import ScreenApiClient
|
|
19
|
-
from minitap.mobile_use.config import OutputConfig, record_events
|
|
22
|
+
from minitap.mobile_use.config import AgentNode, OutputConfig, record_events, settings
|
|
20
23
|
from minitap.mobile_use.context import (
|
|
21
24
|
DeviceContext,
|
|
22
25
|
DevicePlatform,
|
|
23
26
|
ExecutionSetup,
|
|
27
|
+
IsReplan,
|
|
24
28
|
MobileUseContext,
|
|
25
29
|
)
|
|
26
30
|
from minitap.mobile_use.controllers.mobile_command_controller import (
|
|
@@ -32,10 +36,8 @@ from minitap.mobile_use.graph.graph import get_graph
|
|
|
32
36
|
from minitap.mobile_use.graph.state import State
|
|
33
37
|
from minitap.mobile_use.sdk.builders.agent_config_builder import get_default_agent_config
|
|
34
38
|
from minitap.mobile_use.sdk.builders.task_request_builder import TaskRequestBuilder
|
|
35
|
-
from minitap.mobile_use.sdk.constants import
|
|
36
|
-
|
|
37
|
-
DEFAULT_SCREEN_API_BASE_URL,
|
|
38
|
-
)
|
|
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
|
|
39
41
|
from minitap.mobile_use.sdk.types.agent import AgentConfig
|
|
40
42
|
from minitap.mobile_use.sdk.types.exceptions import (
|
|
41
43
|
AgentNotInitializedError,
|
|
@@ -43,9 +45,17 @@ from minitap.mobile_use.sdk.types.exceptions import (
|
|
|
43
45
|
AgentTaskRequestError,
|
|
44
46
|
DeviceNotFoundError,
|
|
45
47
|
ExecutableNotFoundError,
|
|
48
|
+
PlatformServiceUninitializedError,
|
|
46
49
|
ServerStartupError,
|
|
47
50
|
)
|
|
48
|
-
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
|
+
)
|
|
49
59
|
from minitap.mobile_use.servers.device_hardware_bridge import BridgeStatus
|
|
50
60
|
from minitap.mobile_use.servers.start_servers import (
|
|
51
61
|
start_device_hardware_bridge,
|
|
@@ -65,6 +75,8 @@ logger = get_logger(__name__)
|
|
|
65
75
|
|
|
66
76
|
TOutput = TypeVar("TOutput", bound=BaseModel | None)
|
|
67
77
|
|
|
78
|
+
load_dotenv()
|
|
79
|
+
|
|
68
80
|
|
|
69
81
|
class Agent:
|
|
70
82
|
_config: AgentConfig
|
|
@@ -78,7 +90,7 @@ class Agent:
|
|
|
78
90
|
_hw_bridge_client: DeviceHardwareClient
|
|
79
91
|
_adb_client: AdbClient | None
|
|
80
92
|
|
|
81
|
-
def __init__(self, config: AgentConfig | None = None):
|
|
93
|
+
def __init__(self, *, config: AgentConfig | None = None):
|
|
82
94
|
self._config = config or get_default_agent_config()
|
|
83
95
|
self._tasks = []
|
|
84
96
|
self._tmp_traces_dir = Path(tempfile.gettempdir()) / "mobile-use-traces"
|
|
@@ -89,6 +101,12 @@ class Agent:
|
|
|
89
101
|
self._is_default_screen_api = (
|
|
90
102
|
self._config.servers.screen_api_base_url == DEFAULT_SCREEN_API_BASE_URL
|
|
91
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
|
|
92
110
|
|
|
93
111
|
def init(
|
|
94
112
|
self,
|
|
@@ -196,6 +214,12 @@ class Agent:
|
|
|
196
214
|
@overload
|
|
197
215
|
async def run_task(self, *, request: TaskRequest[TOutput]) -> TOutput | None: ...
|
|
198
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
|
+
|
|
199
223
|
async def run_task(
|
|
200
224
|
self,
|
|
201
225
|
*,
|
|
@@ -203,10 +227,25 @@ class Agent:
|
|
|
203
227
|
output: type[TOutput] | str | None = None,
|
|
204
228
|
profile: str | AgentProfile | None = None,
|
|
205
229
|
name: str | None = None,
|
|
206
|
-
request: TaskRequest[TOutput] | None = None,
|
|
230
|
+
request: TaskRequest[TOutput] | PlatformTaskRequest[TOutput] | None = None,
|
|
207
231
|
) -> str | dict | TOutput | None:
|
|
208
232
|
if request is not None:
|
|
209
|
-
|
|
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
|
+
)
|
|
210
249
|
if goal is None:
|
|
211
250
|
raise AgentTaskRequestError("Goal is required")
|
|
212
251
|
task_request = self.new_task(goal=goal)
|
|
@@ -221,7 +260,12 @@ class Agent:
|
|
|
221
260
|
task_request.with_name(name=name)
|
|
222
261
|
return await self._run_task(task_request.build())
|
|
223
262
|
|
|
224
|
-
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:
|
|
225
269
|
if not self._initialized:
|
|
226
270
|
raise AgentNotInitializedError()
|
|
227
271
|
|
|
@@ -233,22 +277,48 @@ class Agent:
|
|
|
233
277
|
agent_profile = self._config.default_profile
|
|
234
278
|
logger.info(str(agent_profile))
|
|
235
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
|
+
|
|
236
296
|
task = Task(
|
|
237
|
-
id=
|
|
297
|
+
id=task_id,
|
|
238
298
|
device=self._device_context,
|
|
239
|
-
status=
|
|
299
|
+
status="pending",
|
|
240
300
|
request=request,
|
|
241
301
|
created_at=datetime.now(),
|
|
302
|
+
on_status_changed=on_status_changed,
|
|
242
303
|
)
|
|
243
304
|
self._tasks.append(task)
|
|
244
305
|
task_name = task.get_name()
|
|
245
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
|
+
|
|
246
312
|
context = MobileUseContext(
|
|
313
|
+
trace_id=task.id,
|
|
247
314
|
device=self._device_context,
|
|
248
315
|
hw_bridge_client=self._hw_bridge_client,
|
|
249
316
|
screen_api_client=self._screen_api_client,
|
|
250
317
|
adb_client=self._adb_client,
|
|
251
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,
|
|
252
322
|
)
|
|
253
323
|
|
|
254
324
|
self._prepare_tracing(task=task, context=context)
|
|
@@ -271,7 +341,7 @@ class Agent:
|
|
|
271
341
|
output = None
|
|
272
342
|
try:
|
|
273
343
|
logger.info(f"[{task_name}] Invoking graph with input: {graph_input}")
|
|
274
|
-
task.status =
|
|
344
|
+
await task.set_status(status="running", message="Invoking graph...")
|
|
275
345
|
async for chunk in (await get_graph(context)).astream(
|
|
276
346
|
input=graph_input,
|
|
277
347
|
config={
|
|
@@ -303,7 +373,7 @@ class Agent:
|
|
|
303
373
|
if not last_state:
|
|
304
374
|
err = f"[{task_name}] No result received from graph"
|
|
305
375
|
logger.warning(err)
|
|
306
|
-
task.finalize(content=output, state=last_state_snapshot, error=err)
|
|
376
|
+
await task.finalize(content=output, state=last_state_snapshot, error=err)
|
|
307
377
|
return None
|
|
308
378
|
|
|
309
379
|
print_ai_response_to_stderr(graph_result=last_state)
|
|
@@ -315,16 +385,25 @@ class Agent:
|
|
|
315
385
|
state=last_state,
|
|
316
386
|
)
|
|
317
387
|
logger.info(f"✅ Automation '{task_name}' is success ✅")
|
|
318
|
-
task.finalize(content=output, state=last_state_snapshot)
|
|
388
|
+
await task.finalize(content=output, state=last_state_snapshot)
|
|
319
389
|
except asyncio.CancelledError:
|
|
320
390
|
err = f"[{task_name}] Task cancelled"
|
|
321
391
|
logger.warning(err)
|
|
322
|
-
task.finalize(
|
|
392
|
+
await task.finalize(
|
|
393
|
+
content=output,
|
|
394
|
+
state=last_state_snapshot,
|
|
395
|
+
error=err,
|
|
396
|
+
cancelled=True,
|
|
397
|
+
)
|
|
323
398
|
raise
|
|
324
399
|
except Exception as e:
|
|
325
400
|
err = f"[{task_name}] Error running automation: {e}"
|
|
326
401
|
logger.error(err)
|
|
327
|
-
task.finalize(
|
|
402
|
+
await task.finalize(
|
|
403
|
+
content=output,
|
|
404
|
+
state=last_state_snapshot,
|
|
405
|
+
error=err,
|
|
406
|
+
)
|
|
328
407
|
raise
|
|
329
408
|
finally:
|
|
330
409
|
self._finalize_tracing(task=task, context=context)
|
|
@@ -355,7 +434,9 @@ class Agent:
|
|
|
355
434
|
traces_output_path.mkdir(parents=True, exist_ok=True)
|
|
356
435
|
temp_trace_path.mkdir(parents=True, exist_ok=True)
|
|
357
436
|
context.execution_setup = ExecutionSetup(
|
|
358
|
-
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,
|
|
359
440
|
)
|
|
360
441
|
|
|
361
442
|
def _finalize_tracing(self, task: Task, context: MobileUseContext):
|
|
@@ -364,11 +445,11 @@ class Agent:
|
|
|
364
445
|
return
|
|
365
446
|
|
|
366
447
|
task_name = task.get_name()
|
|
367
|
-
status = "_PASS" if task.status ==
|
|
448
|
+
status = "_PASS" if task.status == "completed" else "_FAIL"
|
|
368
449
|
ts = task.created_at.strftime("%Y-%m-%dT%H-%M-%S")
|
|
369
|
-
new_name = f"{exec_setup_ctx.
|
|
450
|
+
new_name = f"{exec_setup_ctx.trace_name}{status}_{ts}"
|
|
370
451
|
|
|
371
|
-
temp_trace_path = (self._tmp_traces_dir / exec_setup_ctx.
|
|
452
|
+
temp_trace_path = (self._tmp_traces_dir / exec_setup_ctx.trace_name).resolve()
|
|
372
453
|
traces_output_path = Path(task.request.trace_path).resolve()
|
|
373
454
|
|
|
374
455
|
logger.info(f"[{task_name}] Compiling trace FROM FOLDER: " + str(temp_trace_path))
|
|
@@ -529,6 +610,90 @@ class Agent:
|
|
|
529
610
|
device_height=screen_data.height,
|
|
530
611
|
)
|
|
531
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
|
+
|
|
532
697
|
|
|
533
698
|
def _validate_and_prepare_file(file_path: Path):
|
|
534
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())
|